Merge package:lints into the core monorepo
diff --git a/.github/ISSUE_TEMPLATE/args.md b/.github/ISSUE_TEMPLATE/args.md
new file mode 100644
index 0000000..0a99d17
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/args.md
@@ -0,0 +1,5 @@
+---
+name: "package:args"
+about: "Create a bug or file a feature request against package:args."
+labels: "package:args"
+---
diff --git a/.github/ISSUE_TEMPLATE/async.md b/.github/ISSUE_TEMPLATE/async.md
new file mode 100644
index 0000000..e85a443
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/async.md
@@ -0,0 +1,5 @@
+---
+name: "package:async"
+about: "Create a bug or file a feature request against package:async."
+labels: "package:async"
+---
diff --git a/.github/ISSUE_TEMPLATE/characters.md b/.github/ISSUE_TEMPLATE/characters.md
new file mode 100644
index 0000000..1b094c3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/characters.md
@@ -0,0 +1,5 @@
+---
+name: "package:characters"
+about: "Create a bug or file a feature request against package:characters."
+labels: "package:characters"
+---
diff --git a/.github/ISSUE_TEMPLATE/collection.md b/.github/ISSUE_TEMPLATE/collection.md
new file mode 100644
index 0000000..8424609
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/collection.md
@@ -0,0 +1,5 @@
+---
+name: "package:collection"
+about: "Create a bug or file a feature request against package:collection."
+labels: "package:collection"
+---
diff --git a/.github/ISSUE_TEMPLATE/convert.md b/.github/ISSUE_TEMPLATE/convert.md
new file mode 100644
index 0000000..649ffc1
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/convert.md
@@ -0,0 +1,5 @@
+---
+name: "package:convert"
+about: "Create a bug or file a feature request against package:convert."
+labels: "package:convert"
+---
diff --git a/.github/ISSUE_TEMPLATE/crypto.md b/.github/ISSUE_TEMPLATE/crypto.md
new file mode 100644
index 0000000..d115e91
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/crypto.md
@@ -0,0 +1,5 @@
+---
+name: "package:crypto"
+about: "Create a bug or file a feature request against package:crypto."
+labels: "package:crypto"
+---
diff --git a/.github/ISSUE_TEMPLATE/fixnum.md b/.github/ISSUE_TEMPLATE/fixnum.md
new file mode 100644
index 0000000..80f858b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/fixnum.md
@@ -0,0 +1,5 @@
+---
+name: "package:fixnum"
+about: "Create a bug or file a feature request against package:fixnum."
+labels: "package:fixnum"
+---
diff --git a/.github/ISSUE_TEMPLATE/logging.md b/.github/ISSUE_TEMPLATE/logging.md
new file mode 100644
index 0000000..538b1b1
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/logging.md
@@ -0,0 +1,5 @@
+---
+name: "package:logging"
+about: "Create a bug or file a feature request against package:logging."
+labels: "package:logging"
+---
diff --git a/.github/ISSUE_TEMPLATE/os_detect.md b/.github/ISSUE_TEMPLATE/os_detect.md
new file mode 100644
index 0000000..d93e8b2
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/os_detect.md
@@ -0,0 +1,5 @@
+---
+name: "package:os_detect"
+about: "Create a bug or file a feature request against package:os_detect."
+labels: "package:os_detect"
+---
diff --git a/.github/ISSUE_TEMPLATE/path.md b/.github/ISSUE_TEMPLATE/path.md
new file mode 100644
index 0000000..e9073cc
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/path.md
@@ -0,0 +1,5 @@
+---
+name: "package:path"
+about: "Create a bug or file a feature request against package:path."
+labels: "package:path"
+---
diff --git a/.github/ISSUE_TEMPLATE/platform.md b/.github/ISSUE_TEMPLATE/platform.md
new file mode 100644
index 0000000..52b9fed
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/platform.md
@@ -0,0 +1,5 @@
+---
+name: "package:platform"
+about: "Create a bug or file a feature request against package:platform."
+labels: "package:platform"
+---
diff --git a/.github/ISSUE_TEMPLATE/typed_data.md b/.github/ISSUE_TEMPLATE/typed_data.md
new file mode 100644
index 0000000..85f9d68
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/typed_data.md
@@ -0,0 +1,5 @@
+---
+name: "package:typed_data"
+about: "Create a bug or file a feature request against package:typed_data."
+labels: "package:typed_data"
+---
diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
new file mode 100644
index 0000000..cde02ad
--- /dev/null
+++ b/.github/dependabot.yaml
@@ -0,0 +1,15 @@
+# Dependabot configuration file.
+# See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates
+version: 2
+
+updates:
+ - package-ecosystem: github-actions
+ directory: /
+ schedule:
+ interval: monthly
+ labels:
+ - autosubmit
+ groups:
+ github-actions:
+ patterns:
+ - "*"
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 0000000..14b3afe
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,53 @@
+# Configuration for .github/workflows/pull_request_label.yml.
+
+'type-infra':
+ - changed-files:
+ - any-glob-to-any-file: '.github/**'
+
+'package:args':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/args/**'
+
+'package:async':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/async/**'
+
+'package:characters':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/characters/**'
+
+'package:collection':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/collection/**'
+
+'package:convert':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/convert/**'
+
+'package:crypto':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/crypto/**'
+
+'package:fixnum':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/fixnum/**'
+
+'package:logging':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/logging/**'
+
+'package:os_detect':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/os_detect/**'
+
+'package:path':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/path/**'
+
+'package:platform':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/platform/**'
+
+'package:typed_data':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/typed_data/**'
diff --git a/.github/workflows/args.yaml b/.github/workflows/args.yaml
new file mode 100644
index 0000000..bef26b6
--- /dev/null
+++ b/.github/workflows/args.yaml
@@ -0,0 +1,72 @@
+name: package:args
+
+on:
+ # Run CI on pushes to the main branch, and on PRs against main.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/args.yaml'
+ - 'pkgs/args/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/args.yaml'
+ - 'pkgs/args/**'
+ schedule:
+ - cron: "0 0 * * 0"
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/args/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ if: always() && steps.install.outcome == 'success'
+ run: dart format --output=none --set-exit-if-changed .
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of three dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev, (stable)
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ sdk: ['3.3', dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/async.yaml b/.github/workflows/async.yaml
new file mode 100644
index 0000000..e086f23
--- /dev/null
+++ b/.github/workflows/async.yaml
@@ -0,0 +1,75 @@
+name: package:async
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/async.yaml'
+ - 'pkgs/async/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/async.yaml'
+ - 'pkgs/async/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/async/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - run: dart test --platform chrome --compiler dart2js
+ if: always() && steps.install.outcome == 'success'
+ - run: dart test --platform chrome --compiler dart2wasm
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/characters.yaml b/.github/workflows/characters.yaml
new file mode 100644
index 0000000..bb0c5f1
--- /dev/null
+++ b/.github/workflows/characters.yaml
@@ -0,0 +1,72 @@
+name: package:characters
+
+on:
+ # Run CI on pushes to the main branch, and on PRs against main.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/characters.yaml'
+ - 'pkgs/characters/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/characters.yaml'
+ - 'pkgs/characters/**'
+ schedule:
+ - cron: "0 0 * * 0"
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/characters/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against dev, stable, and 2.19.0 (the package's lower bound).
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev, stable, 3.4]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: matrix.sdk == 'dev' && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest
+ # 2. Release channel: dev, stable, and 2.19.0 (the package's lower bound)
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ sdk: [dev, stable, 3.4]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/collection.yaml b/.github/workflows/collection.yaml
new file mode 100644
index 0000000..247d91f
--- /dev/null
+++ b/.github/workflows/collection.yaml
@@ -0,0 +1,77 @@
+name: package:collection
+
+on:
+ # Run CI on pushes to the main branch, and on PRs against main.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/collection.yaml'
+ - 'pkgs/collection/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/collection.yaml'
+ - 'pkgs/collection/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/collection/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm --test-randomize-ordering-seed=random
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome --test-randomize-ordering-seed=random
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests - wasm
+ run: dart test --platform chrome --compiler dart2wasm
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/convert.yaml b/.github/workflows/convert.yaml
new file mode 100644
index 0000000..1dad964
--- /dev/null
+++ b/.github/workflows/convert.yaml
@@ -0,0 +1,76 @@
+name: package:convert
+
+on:
+ # Run CI on pushes to the main branch, and on PRs against main.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/convert.yaml'
+ - 'pkgs/convert/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/convert.yaml'
+ - 'pkgs/convert/**'
+ schedule:
+ - cron: "0 0 * * 0"
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/convert/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release sdk: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests - wasm
+ run: dart test --platform chrome --compiler dart2wasm
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/crypto.yaml b/.github/workflows/crypto.yaml
new file mode 100644
index 0000000..f381f31
--- /dev/null
+++ b/.github/workflows/crypto.yaml
@@ -0,0 +1,77 @@
+name: package:crypto
+
+on:
+ # Run CI on pushes to the main branch, and on PRs against main.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/crypto.yaml'
+ - 'pkgs/crypto/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/crypto.yaml'
+ - 'pkgs/crypto/**'
+ schedule:
+ - cron: "0 0 * * 0"
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/crypto/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests - wasm
+ # TODO: investigate why we get hangs when concurrency is > 1
+ run: dart test --platform chrome --compiler dart2wasm -j 1
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/fixnum.yaml b/.github/workflows/fixnum.yaml
new file mode 100644
index 0000000..06befb2
--- /dev/null
+++ b/.github/workflows/fixnum.yaml
@@ -0,0 +1,73 @@
+name: package:fixnum
+
+on:
+ # Run CI on pushes to the main branch, and on PRs against main.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/fixnum.yaml'
+ - 'pkgs/fixnum/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/fixnum.yaml'
+ - 'pkgs/fixnum/**'
+ schedule:
+ - cron: "0 0 * * 0"
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/fixnum/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [3.1.0, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.1.0, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/health.yaml b/.github/workflows/health.yaml
new file mode 100644
index 0000000..cf26962
--- /dev/null
+++ b/.github/workflows/health.yaml
@@ -0,0 +1,14 @@
+name: Health
+on:
+ pull_request:
+ branches: [ main ]
+ types: [opened, synchronize, reopened, labeled, unlabeled]
+
+jobs:
+ health:
+ uses: dart-lang/ecosystem/.github/workflows/health.yaml@main
+ with:
+ ignore_coverage: "**.mock.dart,**.g.dart"
+ ignore_license: "**.mock.dart,**.g.dart,**.mocks.dart,pkgs/platform/*"
+ permissions:
+ pull-requests: write
diff --git a/.github/workflows/logging.yaml b/.github/workflows/logging.yaml
new file mode 100644
index 0000000..ee66915
--- /dev/null
+++ b/.github/workflows/logging.yaml
@@ -0,0 +1,70 @@
+name: package:logging
+
+on:
+ # Run CI on pushes to the main branch, and on PRs against main.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/logging.yaml'
+ - 'pkgs/logging/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/logging.yaml'
+ - 'pkgs/logging/**'
+ schedule:
+ - cron: "0 0 * * 0"
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/logging/
+
+jobs:
+ # Check code formatting and static analysis.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix of platforms and sdk versions.
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/os_detect.yaml b/.github/workflows/os_detect.yaml
new file mode 100644
index 0000000..35d6b06
--- /dev/null
+++ b/.github/workflows/os_detect.yaml
@@ -0,0 +1,73 @@
+name: package:os_detect
+
+on:
+ # Run CI on pushes to the main branch, and on PRs against main.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/os_detect.yaml'
+ - 'pkgs/os_detect/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/os_detect.yaml'
+ - 'pkgs/os_detect/**'
+ schedule:
+ - cron: "0 0 * * 0"
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/os_detect/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ sdk: [3.5, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/path.yaml b/.github/workflows/path.yaml
new file mode 100644
index 0000000..c35e02c
--- /dev/null
+++ b/.github/workflows/path.yaml
@@ -0,0 +1,64 @@
+name: package:path
+
+on:
+ # Run CI on pushes to the main branch, and on PRs against main.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/path.yaml'
+ - 'pkgs/path/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/path.yaml'
+ - 'pkgs/path/**'
+ schedule:
+ - cron: "0 0 * * 0"
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/path/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev and stable.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - run: dart pub get
+ - run: dart test --platform vm,chrome
+ - name: Run Chrome tests - wasm
+ run: dart test --platform chrome --compiler dart2wasm
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/platform.yaml b/.github/workflows/platform.yaml
new file mode 100644
index 0000000..981c463
--- /dev/null
+++ b/.github/workflows/platform.yaml
@@ -0,0 +1,52 @@
+name: package:platform
+
+on:
+ # Run CI on pushes to the main branch, and on PRs against main.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/platform.yaml'
+ - 'pkgs/platform/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/platform.yaml'
+ - 'pkgs/platform/**'
+ schedule:
+ - cron: "0 0 * * 0"
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/platform/
+
+jobs:
+ correctness:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - name: Install dependencies
+ run: dart pub upgrade
+ - name: Verify formatting
+ run: dart format --output=none --set-exit-if-changed .
+ - name: Analyze project source
+ run: dart analyze --fatal-infos
+ test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ sdk: [stable, beta, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - name: Install dependencies
+ run: dart pub upgrade
+ - name: Run Tests
+ run: dart test
diff --git a/.github/workflows/post_summaries.yaml b/.github/workflows/post_summaries.yaml
new file mode 100644
index 0000000..f5c8be8
--- /dev/null
+++ b/.github/workflows/post_summaries.yaml
@@ -0,0 +1,17 @@
+name: Comment on the pull request
+
+on:
+ # Trigger this workflow after the Health workflow completes. This workflow will have permissions to
+ # do things like create comments on the PR, even if the original workflow couldn't.
+ workflow_run:
+ workflows:
+ - Publish
+ - Health
+ types:
+ - completed
+
+jobs:
+ upload:
+ uses: dart-lang/ecosystem/.github/workflows/post_summaries.yaml@main
+ permissions:
+ pull-requests: write
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
new file mode 100644
index 0000000..52acac4
--- /dev/null
+++ b/.github/workflows/publish.yaml
@@ -0,0 +1,17 @@
+# A CI configuration to auto-publish pub packages.
+
+name: Publish
+
+on:
+ pull_request:
+ branches: [ main ]
+ push:
+ tags: [ '[A-z]+-v[0-9]+.[0-9]+.[0-9]+' ]
+
+jobs:
+ publish:
+ if: ${{ github.repository_owner == 'dart-lang' }}
+ uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main
+ with:
+ write-comments: false
+
diff --git a/.github/workflows/pull_request_label.yaml b/.github/workflows/pull_request_label.yaml
new file mode 100644
index 0000000..54e3df5
--- /dev/null
+++ b/.github/workflows/pull_request_label.yaml
@@ -0,0 +1,22 @@
+# This workflow applies labels to pull requests based on the paths that are
+# modified in the pull request.
+#
+# Edit `.github/labeler.yml` to configure labels. For more information, see
+# https://github.com/actions/labeler.
+
+name: Pull Request Labeler
+permissions: read-all
+
+on:
+ pull_request_target
+
+jobs:
+ label:
+ permissions:
+ pull-requests: write
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9
+ with:
+ repo-token: "${{ secrets.GITHUB_TOKEN }}"
+ sync-labels: true
diff --git a/.github/workflows/typed_data.yaml b/.github/workflows/typed_data.yaml
new file mode 100644
index 0000000..2ea36f7
--- /dev/null
+++ b/.github/workflows/typed_data.yaml
@@ -0,0 +1,76 @@
+name: package:typed_data
+
+on:
+ # Run CI on pushes to the main branch, and on PRs against main.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/typed_data.yaml'
+ - 'pkgs/typed_data/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/typed_data.yaml'
+ - 'pkgs/typed_data/**'
+ schedule:
+ - cron: "0 0 * * 0"
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/typed_data/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.5, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests - wasm
+ run: dart test --platform chrome --compiler dart2wasm
+ if: always() && steps.install.outcome == 'success'
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..5b4fcef
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,51 @@
+# How to Contribute
+
+We'd love to accept your patches and contributions to this project. There are
+just a few small guidelines you need to follow.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement (CLA). You (or your employer) retain the copyright to your
+contribution; this simply gives us permission to use and redistribute your
+contributions as part of the project. Head over to
+<https://cla.developers.google.com/> to see your current agreements on file or
+to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
+## Code Reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose. Consult
+[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
+information on using pull requests.
+
+## Coding style
+
+The Dart source code in this repo follows the:
+
+ * [Dart style guide](https://dart.dev/guides/language/effective-dart/style)
+
+You should familiarize yourself with those guidelines.
+
+## File headers
+
+All files in the Dart project must start with the following header; if you add a
+new file please also add this. The year should be a single number stating the
+year the file was created (don't use a range like "2011-2012"). Additionally, if
+you edit an existing file, you shouldn't update the year.
+
+ // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+ // for details. All rights reserved. Use of this source code is governed by a
+ // BSD-style license that can be found in the LICENSE file.
+
+## Community Guidelines
+
+This project follows
+[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
+
+We pledge to maintain an open and welcoming environment. For details, see our
+[code of conduct](https://dart.dev/code-of-conduct).
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c8e3acb
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2024, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f75f81c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,30 @@
+<!-- [](https://github.com/dart-lang/core/actions/workflows/dart.yml) -->
+
+## Overview
+
+This repository is home to various Dart packages under the [dart.dev](https://pub.dev/publishers/dart.dev/packages) publisher.
+
+## Packages
+
+| Package | Description | Issues | Version |
+| --- | --- | --- | --- |
+| [args](pkgs/args/) | Library for defining parsers for parsing raw command-line arguments into a set of options and values using GNU and POSIX style options. | [](https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aargs) | [](https://pub.dev/packages/args) |
+| [async](pkgs/async/) | Utility functions and classes related to the 'dart:async' library. | [](https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aasync) | [](https://pub.dev/packages/async) |
+| [characters](pkgs/characters/) | String replacement with operations that are Unicode/grapheme cluster aware. | [](https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acharacters) | [](https://pub.dev/packages/characters) |
+| [collection](pkgs/collection/) | Collections and utilities functions and classes related to collections. | [](https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acollection) | [](https://pub.dev/packages/collection) |
+| [convert](pkgs/convert/) | Utilities for converting between data representations. Provides a number of Sink, Codec, Decoder, and Encoder types. | [](https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aconvert) | [](https://pub.dev/packages/convert) |
+| [crypto](pkgs/crypto/) | Implementations of SHA, MD5, and HMAC cryptographic functions. | [](https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acrypto) | [](https://pub.dev/packages/crypto) |
+| [fixnum](pkgs/fixnum/) | Library for 32- and 64-bit signed fixed-width integers with consistent behavior between native and JS runtimes. | [](https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Afixnum) | [](https://pub.dev/packages/fixnum) |
+| [logging](pkgs/logging/) | Provides APIs for debugging and error logging, similar to loggers in other languages, such as the Closure JS Logger and java.util.logging.Logger. | [](https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Alogging) | [](https://pub.dev/packages/logging) |
+| [os_detect](pkgs/os_detect/) | Platform independent OS detection. | [](https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aos_detect) | [](https://pub.dev/packages/os_detect) |
+| [path](pkgs/path/) | A string-based path manipulation library. | [](https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Apath) | [](https://pub.dev/packages/path) |
+| [platform](pkgs/platform/) | A pluggable, mockable platform information abstraction for Dart. | [](https://github.com/dart-lang/core/issues) | [](https://pub.dev/packages/platform) |
+| [typed_data](pkgs/typed_data/) | Utility functions and classes related to the dart:typed_data library. | [](https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Atyped_data) | [](https://pub.dev/packages/typed_data) |
+
+## Publishing automation
+
+For information about our publishing automation and release process, see
+https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
+
+For additional information about contributing, see our
+[contributing](CONTRIBUTING.md) page.
diff --git a/pkgs/args/.gitignore b/pkgs/args/.gitignore
new file mode 100644
index 0000000..813a31e
--- /dev/null
+++ b/pkgs/args/.gitignore
@@ -0,0 +1,16 @@
+# Don’t commit the following directories created by pub.
+.buildlog
+.pub/
+.dart_tool/
+build/
+packages
+.packages
+
+# Or the files created by dart2js.
+*.dart.js
+*.js_
+*.js.deps
+*.js.map
+
+# Include when developing application packages.
+pubspec.lock
diff --git a/pkgs/args/.test_config b/pkgs/args/.test_config
new file mode 100644
index 0000000..412fc5c
--- /dev/null
+++ b/pkgs/args/.test_config
@@ -0,0 +1,3 @@
+{
+ "test_package": true
+}
\ No newline at end of file
diff --git a/pkgs/args/CHANGELOG.md b/pkgs/args/CHANGELOG.md
new file mode 100644
index 0000000..b97daf5
--- /dev/null
+++ b/pkgs/args/CHANGELOG.md
@@ -0,0 +1,352 @@
+## 2.6.1-wip
+
+* Fix the repository URL in `pubspec.yaml`.
+* Added option `hideNegatedUsage` to `ArgParser.flag()` allowing a flag to be
+ `negatable` without showing it in the usage text.
+
+## 2.6.0
+
+* Added source argument when throwing a `ArgParserException`.
+* Fix inconsistent `FormatException` messages
+* Require Dart 3.3
+* Move to `dart-lang/core` monorepo.
+
+## 2.5.0
+
+* Introduce new typed `ArgResults` `flag(String)`, `option(String)`, and
+ `multiOption(String)` methods.
+* Require Dart 3.0.
+
+## 2.4.2
+
+* Change the validation of `mandatory` options; they now perform validation when
+ the value is retrieved (from the `ArgResults` object), instead of when the
+ args are parsed.
+* Require Dart 2.19.
+
+## 2.4.1
+
+* Add a `CONTRIBUTING.md` file; move the publishing automation docs from the
+ readme into the contributing doc.
+* Added package topics to the pubspec file.
+
+## 2.4.0
+
+* Command suggestions will now also suggest based on aliases of a command.
+* Introduce getter `Command.suggestionAliases` for names that cannot be used as
+ aliases, but will trigger suggestions.
+
+## 2.3.2
+
+* Require Dart 2.18
+
+## 2.3.1
+
+* Switch to using package:lints.
+* Address an issue with the readme API documentation (#211).
+* Populate the pubspec `repository` field.
+
+## 2.3.0
+
+* Add the ability to group commands by category in usage text.
+
+## 2.2.0
+
+* Suggest similar commands if an unknown command is encountered, when using the
+ `CommandRunner`.
+ * The max edit distance for suggestions defaults to 2, but can be configured
+ using the `suggestionDistanceLimit` parameter on the constructor. You can
+ set it to `0` to disable the feature.
+
+## 2.1.1
+
+* Fix a bug with `mandatory` options which caused a null assertion failure when
+ used within a command.
+
+## 2.1.0
+
+* Add a `mandatory` argument to require the presence of an option.
+* Add `aliases` named argument to `addFlag`, `addOption`, and `addMultiOption`,
+ as well as a public `findByNameOrAlias` method on `ArgParser`. This allows
+ you to provide aliases for an argument name, which eases the transition from
+ one argument name to another.
+
+## 2.0.0
+
+* Stable null safety release.
+
+## 2.0.0-nullsafety.0
+
+* Migrate to null safety.
+* **BREAKING** Remove APIs that had been marked as deprecated:
+
+ * Instead of the `allowMulti` and `splitCommas` arguments to
+ `ArgParser.addOption()`, use `ArgParser.addMultiOption()`.
+ * Instead of `ArgParser.getUsage()`, use `ArgParser.usage`.
+ * Instead of `Option.abbreviation`, use `Option.abbr`.
+ * Instead of `Option.defaultValue`, use `Option.defaultsTo`.
+ * Instead of `OptionType.FLAG/SINGLE/MULTIPLE`, use
+ `OptionType.flag/single/multiple`.
+* Add a more specific function type to the `callback` argument of `addOption`.
+
+## 1.6.0
+
+* Remove `help` from the list of commands in usage.
+* Remove the blank lines in usage which separated the help for options that
+ happened to span multiple lines.
+
+## 1.5.4
+
+* Fix a bug with option names containing underscores.
+* Point towards `CommandRunner` in the docs for `ArgParser.addCommand` since it
+ is what most authors will want to use instead.
+
+## 1.5.3
+
+* Improve arg parsing performance: use queues instead of lists internally to
+ get linear instead of quadratic performance, which is important for large
+ numbers of args (>1000). And, use simple string manipulation instead of
+ regular expressions for a 1.5x improvement everywhere.
+* No longer automatically add a 'help' option to commands that don't validate
+ their arguments (fix #123).
+
+## 1.5.2
+
+* Added support for `usageLineLength` in `CommandRunner`
+
+## 1.5.1
+
+* Added more comprehensive word wrapping when `usageLineLength` is set.
+
+## 1.5.0
+
+* Add `usageLineLength` to control word wrapping usage text.
+
+## 1.4.4
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 1.4.3
+
+* Display the default values for options with `allowedHelp` specified.
+
+## 1.4.2
+
+* Narrow the SDK constraint to only allow SDK versions that support `FutureOr`.
+
+## 1.4.1
+
+* Fix the way default values for multi-valued options are printed in argument
+ usage.
+
+## 1.4.0
+
+* Deprecated `OptionType.FLAG`, `OptionType.SINGLE`, and `OptionType.MULTIPLE`
+ in favor of `OptionType.flag`, `OptionType.single`, and `OptionType.multiple`
+ which follow the style guide.
+
+* Deprecated `Option.abbreviation` and `Option.defaultValue` in favor of
+ `Option.abbr` and `Option.defaultsTo`. This makes all of `Option`'s fields
+ match the corresponding parameters to `ArgParser.addOption()`.
+
+* Deprecated the `allowMultiple` and `splitCommas` arguments to
+ `ArgParser.addOption()` in favor of a separate `ArgParser.addMultiOption()`
+ method. This allows us to provide more accurate type information, and to avoid
+ adding flags that only make sense for multi-options in places where they might
+ be usable for single-value options.
+
+## 1.3.0
+
+* Type `Command.run()`'s return value as `FutureOr<T>`.
+
+## 1.2.0
+
+* Type the `callback` parameter to `ArgParser.addOption()` as `Function` rather
+ than `void Function(value)`. This allows strong-mode users to write `callback:
+ (String value) { ... }` rather than having to manually cast `value` to a
+ `String` (or a `List<String>` with `allowMultiple: true`).
+
+## 1.1.0
+
+* `ArgParser.parse()` now takes an `Iterable<String>` rather than a
+ `List<String>`.
+
+* `ArgParser.addOption()`'s `allowed` option now takes an `Iterable<String>`
+ rather than a `List<String>`.
+
+## 1.0.2
+
+* Fix analyzer warning
+
+## 1.0.1
+
+* Fix a fuzzy arrow type warning.
+
+## 1.0.0
+
+* **Breaking change**: The `allowTrailingOptions` argument to `new
+ ArgumentParser()` defaults to `true` instead of `false`.
+
+* Add `new ArgParser.allowAnything()`. This allows any input, without parsing
+ any options.
+
+## 0.13.7
+
+* Add explicit support for forwarding the value returned by `Command.run()` to
+ `CommandRunner.run()`. This worked unintentionally prior to 0.13.6+1.
+
+* Add type arguments to `CommandRunner` and `Command` to indicate the return
+ values of the `run()` functions.
+
+## 0.13.6+1
+
+* When a `CommandRunner` is passed `--help` before any commands, it now prints
+ the usage of the chosen command.
+
+## 0.13.6
+
+* `ArgParser.parse()` now throws an `ArgParserException`, which implements
+ `FormatException` and has a field that lists the commands that were parsed.
+
+* If `CommandRunner.run()` encounters a parse error for a subcommand, it now
+ prints the subcommand's usage rather than the global usage.
+
+## 0.13.5
+
+* Allow `CommandRunner.argParser` and `Command.argParser` to be overridden in
+ strong mode.
+
+## 0.13.4+2
+
+* Fix a minor documentation error.
+
+## 0.13.4+1
+
+* Ensure that multiple-value arguments produce reified `List<String>`s.
+
+## 0.13.4
+
+* By default, only the first line of a command's description is included in its
+ parent runner's usage string. This returns to the default behavior from
+ before 0.13.3+1.
+
+* A `Command.summary` getter has been added to explicitly control the summary
+ that appears in the parent runner's usage string. This getter defaults to the
+ first line of the description, but can be overridden if the user wants a
+ multi-line summary.
+
+## 0.13.3+6
+
+* README fixes.
+
+## 0.13.3+5
+
+* Make strong mode clean.
+
+## 0.13.3+4
+
+* Use the proper `usage` getter in the README.
+
+## 0.13.3+3
+
+* Add an explicit default value for the `allowTrailingOptions` parameter to `new
+ ArgParser()`. This doesn't change the behavior at all; the option already
+ defaulted to `false`, and passing in `null` still works.
+
+## 0.13.3+2
+
+* Documentation fixes.
+
+## 0.13.3+1
+
+* Print all lines of multi-line command descriptions.
+
+## 0.13.2
+
+* Allow option values that look like options. This more closely matches the
+ behavior of [`getopt`][getopt], the *de facto* standard for option parsing.
+
+[getopt]: https://man7.org/linux/man-pages/man3/getopt.3.html
+
+## 0.13.1
+
+* Add `ArgParser.addSeparator()`. Separators allow users to group their options
+ in the usage text.
+
+## 0.13.0
+
+* **Breaking change**: An option that allows multiple values will now
+ automatically split apart comma-separated values. This can be controlled with
+ the `splitCommas` option.
+
+## 0.12.2+6
+
+* Remove the dependency on the `collection` package.
+
+## 0.12.2+5
+
+* Add syntax highlighting to the README.
+
+## 0.12.2+4
+
+* Add an example of using command-line arguments to the README.
+
+## 0.12.2+3
+
+* Fixed implementation of ArgResults.options to really use Iterable<String>
+ instead of Iterable<dynamic> cast to Iterable<String>.
+
+## 0.12.2+2
+
+* Updated dependency constraint on `unittest`.
+
+* Formatted source code.
+
+* Fixed use of deprecated API in example.
+
+## 0.12.2+1
+
+* Fix the built-in `help` command for `CommandRunner`.
+
+## 0.12.2
+
+* Add `CommandRunner` and `Command` classes which make it easy to build a
+ command-based command-line application.
+
+* Add an `ArgResults.arguments` field, which contains the original argument list.
+
+## 0.12.1
+
+* Replace `ArgParser.getUsage()` with `ArgParser.usage`, a getter.
+ `ArgParser.getUsage()` is now deprecated, to be removed in args version 1.0.0.
+
+## 0.12.0+2
+
+* Widen the version constraint on the `collection` package.
+
+## 0.12.0+1
+
+* Remove the documentation link from the pubspec so this is linked to
+ pub.dev by default.
+
+## 0.12.0
+
+* Removed public constructors for `ArgResults` and `Option`.
+
+* `ArgResults.wasParsed()` can be used to determine if an option was actually
+ parsed or the default value is being returned.
+
+* Replaced `isFlag` and `allowMultiple` fields in the `Option` class with a
+ three-value `OptionType` enum.
+
+* Options may define `valueHelp` which will then be shown in the usage.
+
+## 0.11.0
+
+* Move handling trailing options from `ArgParser.parse()` into `ArgParser`
+ itself. This lets subcommands have different behavior for how they handle
+ trailing options.
+
+## 0.10.0+2
+
+* Usage ignores hidden options when determining column widths.
diff --git a/pkgs/args/CONTRIBUTING.md b/pkgs/args/CONTRIBUTING.md
new file mode 100644
index 0000000..d8db4ac
--- /dev/null
+++ b/pkgs/args/CONTRIBUTING.md
@@ -0,0 +1,56 @@
+# How to Contribute
+
+We'd love to accept your patches and contributions to this project. There are
+just a few small guidelines you need to follow.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement (CLA). You (or your employer) retain the copyright to your
+contribution; this simply gives us permission to use and redistribute your
+contributions as part of the project. Head over to
+<https://cla.developers.google.com/> to see your current agreements on file or
+to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
+## Code Reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose. Consult
+[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
+information on using pull requests.
+
+## Coding style
+
+The Dart source code in this repo follows the:
+
+ * [Dart style guide](https://dart.dev/guides/language/effective-dart/style)
+
+You should familiarize yourself with those guidelines.
+
+## File headers
+
+All files in the Dart project must start with the following header; if you add a
+new file please also add this. The year should be a single number stating the
+year the file was created (don't use a range like "2011-2012"). Additionally, if
+you edit an existing file, you shouldn't update the year.
+
+ // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+ // for details. All rights reserved. Use of this source code is governed by a
+ // BSD-style license that can be found in the LICENSE file.
+
+## Publishing automation
+
+For information about our publishing automation and release process, see
+https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
+
+## Community Guidelines
+
+This project follows
+[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
+
+We pledge to maintain an open and welcoming environment. For details, see our
+[code of conduct](https://dart.dev/code-of-conduct).
diff --git a/pkgs/args/LICENSE b/pkgs/args/LICENSE
new file mode 100644
index 0000000..ab3bfa0
--- /dev/null
+++ b/pkgs/args/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2013, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/args/README.md b/pkgs/args/README.md
new file mode 100644
index 0000000..05f36eb
--- /dev/null
+++ b/pkgs/args/README.md
@@ -0,0 +1,459 @@
+[](https://github.com/dart-lang/core/actions/workflows/args.yaml)
+[](https://pub.dev/packages/args)
+[](https://pub.dev/packages/args/publisher)
+
+Parses raw command-line arguments into a set of options and values.
+
+This library supports [GNU][] and [POSIX][] style options, and it works in both
+server-side and client-side apps.
+
+## Defining options
+
+First create an [ArgParser][]:
+
+ var parser = ArgParser();
+
+Then define a set of options on that parser using [addOption()][addOption] and
+[addFlag()][addFlag]. Here's the minimal way to create an option named "name":
+
+ parser.addOption('name');
+
+When an option can only be set or unset (as opposed to taking a string value),
+use a flag:
+
+```dart
+parser.addFlag('name');
+```
+
+Flag options, by default, accept a 'no-' prefix to negate the option. You can
+disable the 'no-' prefix using the `negatable` parameter:
+
+```dart
+parser.addFlag('name', negatable: false);
+```
+
+*Note:* From here on out, "option" refers to both regular options and flags. In
+cases where the distinction matters, we'll use "non-flag option."
+
+Options can have an optional single-character abbreviation, specified with the
+`abbr` parameter:
+
+```dart
+parser.addOption('mode', abbr: 'm');
+parser.addFlag('verbose', abbr: 'v');
+```
+
+Options can also have a default value, specified with the `defaultsTo`
+parameter. The default value is used when arguments don't specify the option.
+
+```dart
+parser.addOption('mode', defaultsTo: 'debug');
+parser.addFlag('verbose', defaultsTo: false);
+```
+
+The default value for non-flag options can be any string. For flags, it must
+be a `bool`.
+
+To validate a non-flag option, you can use the `allowed` parameter to provide an
+allowed set of values. When you do, the parser throws an
+[`ArgParserException`][ArgParserException] if the value for an option is not in
+the allowed set. Here's an example of specifying allowed values:
+
+```dart
+parser.addOption('mode', allowed: ['debug', 'release']);
+```
+
+You can use the `callback` parameter to associate a function with an option.
+Later, when parsing occurs, the callback function is invoked with the value of
+the option:
+
+```dart
+parser.addOption('mode', callback: (mode) => print('Got mode $mode'));
+parser.addFlag('verbose', callback: (verbose) {
+ if (verbose) print('Verbose');
+});
+```
+
+The callbacks for all options are called whenever a set of arguments is parsed.
+If an option isn't provided in the args, its callback is passed the default
+value, or `null` if no default value is set.
+
+If an option is `mandatory` but not provided, the results object throws an
+[`ArgumentError`][ArgumentError] on retrieval.
+
+```dart
+parser.addOption('mode', mandatory: true);
+```
+
+## Parsing arguments
+
+Once you have an [ArgParser][] set up with some options and flags, you use it by
+calling [ArgParser.parse()][parse] with a set of arguments:
+
+```dart
+var results = parser.parse(['some', 'command', 'line', 'args']);
+```
+
+These arguments usually come from the arguments to `main()`. For example:
+
+ main(List<String> args) {
+ // ...
+ var results = parser.parse(args);
+ }
+
+However, you can pass in any list of strings. The `parse()` method returns an
+instance of [ArgResults][], a map-like object that contains the values of the
+parsed options.
+
+```dart
+var parser = ArgParser();
+parser.addOption('mode');
+parser.addFlag('verbose', defaultsTo: true);
+var results = parser.parse(['--mode', 'debug', 'something', 'else']);
+
+print(results.option('mode')); // debug
+print(results.flag('verbose')); // true
+```
+
+By default, the `parse()` method allows additional flags and options to be
+passed after positional parameters unless `--` is used to indicate that all
+further parameters will be positional. The positional arguments go into
+[ArgResults.rest][rest].
+
+```dart
+print(results.rest); // ['something', 'else']
+```
+
+To stop parsing options as soon as a positional argument is found,
+`allowTrailingOptions: false` when creating the [ArgParser][].
+
+## Specifying options
+
+To actually pass in options and flags on the command line, use GNU or POSIX
+style. Consider this option:
+
+```dart
+parser.addOption('name', abbr: 'n');
+```
+
+You can specify its value on the command line using any of the following:
+
+```
+--name=somevalue
+--name somevalue
+-nsomevalue
+-n somevalue
+```
+
+Consider this flag:
+
+```dart
+parser.addFlag('name', abbr: 'n');
+```
+
+You can set it to true using one of the following:
+
+```
+--name
+-n
+```
+
+You can set it to false using the following:
+
+```
+--no-name
+```
+
+Multiple flag abbreviations can be collapsed into a single argument. Say you
+define these flags:
+
+```dart
+parser
+ ..addFlag('verbose', abbr: 'v')
+ ..addFlag('french', abbr: 'f')
+ ..addFlag('iambic-pentameter', abbr: 'i');
+```
+
+You can set all three flags at once:
+
+```
+-vfi
+```
+
+By default, an option has only a single value, with later option values
+overriding earlier ones; for example:
+
+```dart
+var parser = ArgParser();
+parser.addOption('mode');
+var results = parser.parse(['--mode', 'on', '--mode', 'off']);
+print(results.option('mode')); // prints 'off'
+```
+
+Multiple values can be parsed with `addMultiOption()`. With this method, an
+option can occur multiple times, and the `parse()` method returns a list of
+values:
+
+```dart
+var parser = ArgParser();
+parser.addMultiOption('mode');
+var results = parser.parse(['--mode', 'on', '--mode', 'off']);
+print(results.multiOption('mode')); // prints '[on, off]'
+```
+
+By default, values for a multi-valued option may also be separated with commas:
+
+```dart
+var parser = ArgParser();
+parser.addMultiOption('mode');
+var results = parser.parse(['--mode', 'on,off']);
+print(results.multiOption('mode')); // prints '[on, off]'
+```
+
+This can be disabled by passing `splitCommas: false`.
+
+## Defining commands
+
+In addition to *options*, you can also define *commands*. A command is a named
+argument that has its own set of options. For example, consider this shell
+command:
+
+```
+$ git commit -a
+```
+
+The executable is `git`, the command is `commit`, and the `-a` option is an
+option passed to the command. You can add a command using the [addCommand][]
+method:
+
+```dart
+var parser = ArgParser();
+var command = parser.addCommand('commit');
+```
+
+It returns another [ArgParser][], which you can then use to define options
+specific to that command. If you already have an [ArgParser][] for the command's
+options, you can pass it in:
+
+```dart
+var parser = ArgParser();
+var command = ArgParser();
+parser.addCommand('commit', command);
+```
+
+The [ArgParser][] for a command can then define options or flags:
+
+```dart
+command.addFlag('all', abbr: 'a');
+```
+
+You can add multiple commands to the same parser so that a user can select one
+from a range of possible commands. When parsing an argument list, you can then
+determine which command was entered and what options were provided for it.
+
+```dart
+var results = parser.parse(['commit', '-a']);
+print(results.command.name); // "commit"
+print(results.command['all']); // true
+```
+
+Options for a command must appear after the command in the argument list. For
+example, given the above parser, `"git -a commit"` is *not* valid. The parser
+tries to find the right-most command that accepts an option. For example:
+
+```dart
+var parser = ArgParser();
+parser.addFlag('all', abbr: 'a');
+var command = parser.addCommand('commit');
+command.addFlag('all', abbr: 'a');
+
+var results = parser.parse(['commit', '-a']);
+print(results.command['all']); // true
+```
+
+Here, both the top-level parser and the `"commit"` command can accept a `"-a"`
+(which is probably a bad command line interface, admittedly). In that case, when
+`"-a"` appears after `"commit"`, it is applied to that command. If it appears to
+the left of `"commit"`, it is given to the top-level parser.
+
+## Dispatching Commands
+
+If you're writing a command-based application, you can use the [CommandRunner][]
+and [Command][] classes to help structure it. [CommandRunner][] has built-in
+support for dispatching to [Command][]s based on command-line arguments, as well
+as handling `--help` flags and invalid arguments.
+
+When using the [CommandRunner][] it replaces the [ArgParser][].
+
+In the following example we build a dart application called `dgit` that takes commands `commit` and `stash`.
+
+The [CommandRunner][] takes an `executableName` which is used to generate the help message.
+
+e.g.
+`dgit commit -a`
+
+File `dgit.dart`
+
+```dart
+void main(List<String> args) {
+ var runner = CommandRunner("dgit", "A dart implementation of distributed version control.")
+ ..addCommand(CommitCommand())
+ ..addCommand(StashCommand())
+ ..run(args);
+}
+```
+
+When the above `run(args)` line executes it parses the command line args looking for one of the commands (`commit` or `stash`).
+
+If the [CommandRunner][] finds a matching command then the [CommandRunner][] calls the overridden `run()` method on the matching command (e.g. CommitCommand().run).
+
+Commands are defined by extending the [Command][] class. For example:
+
+```dart
+class CommitCommand extends Command {
+ // The [name] and [description] properties must be defined by every
+ // subclass.
+ final name = "commit";
+ final description = "Record changes to the repository.";
+
+ CommitCommand() {
+ // we can add command specific arguments here.
+ // [argParser] is automatically created by the parent class.
+ argParser.addFlag('all', abbr: 'a');
+ }
+
+ // [run] may also return a Future.
+ void run() {
+ // [argResults] is set before [run()] is called and contains the flags/options
+ // passed to this command.
+ print(argResults.flag('all'));
+ }
+}
+```
+
+### CommandRunner Arguments
+The [CommandRunner][] allows you to specify both global args as well as command specific arguments (and even sub-command specific arguments).
+
+#### Global Arguments
+Add argments directly to the [CommandRunner] to specify global arguments:
+
+Adding global arguments
+
+```dart
+var runner = CommandRunner('dgit', "A dart implementation of distributed version control.");
+// add global flag
+runner.argParser.addFlag('verbose', abbr: 'v', help: 'increase logging');
+```
+
+#### Command specific Arguments
+Add arguments to each [Command][] to specify [Command][] specific arguments.
+
+```dart
+ CommitCommand() {
+ // we can add command specific arguments here.
+ // [argParser] is automatically created by the parent class.
+ argParser.addFlag('all', abbr: 'a');
+ }
+```
+
+### SubCommands
+
+Commands can also have subcommands, which are added with [addSubcommand][]. A
+command with subcommands can't run its own code, so [run][] doesn't need to be
+implemented. For example:
+
+```dart
+class StashCommand extends Command {
+ final String name = "stash";
+ final String description = "Stash changes in the working directory.";
+
+ StashCommand() {
+ addSubcommand(StashSaveCommand());
+ addSubcommand(StashListCommand());
+ }
+}
+```
+
+### Default Help Command
+
+[CommandRunner][] automatically adds a `help` command that displays usage
+information for commands, as well as support for the `--help` flag for all
+commands. If it encounters an error parsing the arguments or processing a
+command, it throws a [UsageException][]; your `main()` method should catch these and
+print them appropriately. For example:
+
+```dart
+runner.run(arguments).catchError((error) {
+ if (error is! UsageException) throw error;
+ print(error);
+ exit(64); // Exit code 64 indicates a usage error.
+});
+```
+
+## Displaying usage
+
+You can automatically generate nice help text, suitable for use as the output of
+`--help`. To display good usage information, you should provide some help text
+when you create your options.
+
+To define help text for an entire option, use the `help:` parameter:
+
+```dart
+parser.addOption('mode', help: 'The compiler configuration',
+ allowed: ['debug', 'release']);
+parser.addFlag('verbose', help: 'Show additional diagnostic info');
+```
+
+For non-flag options, you can also provide a help string for the parameter:
+
+```dart
+parser.addOption('out', help: 'The output path', valueHelp: 'path',
+ allowed: ['debug', 'release']);
+```
+
+For non-flag options, you can also provide detailed help for each expected value
+by using the `allowedHelp:` parameter:
+
+```dart
+parser.addOption('arch', help: 'The architecture to compile for',
+ allowedHelp: {
+ 'ia32': 'Intel x86',
+ 'arm': 'ARM Holding 32-bit chip'
+ });
+```
+
+To display the help, use the [usage][usage] getter:
+
+```dart
+print(parser.usage);
+```
+
+The resulting string looks something like this:
+
+```
+--mode The compiler configuration
+ [debug, release]
+
+--out=<path> The output path
+--[no-]verbose Show additional diagnostic info
+--arch The architecture to compile for
+ [arm] ARM Holding 32-bit chip
+ [ia32] Intel x86
+```
+
+[posix]: https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
+[gnu]: https://www.gnu.org/prep/standards/standards.html#Command_002dLine-Interfaces
+[ArgParser]: https://pub.dev/documentation/args/latest/args/ArgParser/ArgParser.html
+[ArgParserException]: https://pub.dev/documentation/args/latest/args/ArgParserException-class.html
+[ArgResults]: https://pub.dev/documentation/args/latest/args/ArgResults-class.html
+[CommandRunner]: https://pub.dev/documentation/args/latest/command_runner/CommandRunner-class.html
+[Command]: https://pub.dev/documentation/args/latest/command_runner/Command-class.html
+[UsageException]: https://pub.dev/documentation/args/latest/command_runner/UsageException-class.html
+[addOption]: https://pub.dev/documentation/args/latest/args/ArgParser/addOption.html
+[addFlag]: https://pub.dev/documentation/args/latest/args/ArgParser/addFlag.html
+[parse]: https://pub.dev/documentation/args/latest/args/ArgParser/parse.html
+[rest]: https://pub.dev/documentation/args/latest/args/ArgResults/rest.html
+[addCommand]: https://pub.dev/documentation/args/latest/args/ArgParser/addCommand.html
+[usage]: https://pub.dev/documentation/args/latest/args/ArgParser/usage.html
+[addSubcommand]: https://pub.dev/documentation/args/latest/command_runner/Command/addSubcommand.html
+[run]: https://pub.dev/documentation/args/latest/command_runner/CommandRunner/run.html
diff --git a/pkgs/args/analysis_options.yaml b/pkgs/args/analysis_options.yaml
new file mode 100644
index 0000000..4ea2bd7
--- /dev/null
+++ b/pkgs/args/analysis_options.yaml
@@ -0,0 +1,12 @@
+# https://dart.dev/guides/language/analysis-options
+
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+linter:
+ rules:
+ - avoid_unused_constructor_parameters
+ - cancel_subscriptions
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
diff --git a/pkgs/args/example/arg_parser/README.md b/pkgs/args/example/arg_parser/README.md
new file mode 100644
index 0000000..c137bcd
--- /dev/null
+++ b/pkgs/args/example/arg_parser/README.md
@@ -0,0 +1,3 @@
+# Example of using `ArgParser`
+
+`dart run example.dart`
diff --git a/pkgs/args/example/arg_parser/example.dart b/pkgs/args/example/arg_parser/example.dart
new file mode 100644
index 0000000..0a6cc97
--- /dev/null
+++ b/pkgs/args/example/arg_parser/example.dart
@@ -0,0 +1,169 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This is an example of converting the args in test.dart to use this API.
+/// It shows what it looks like to build an [ArgParser] and then, when the code
+/// is run, demonstrates what the generated usage text looks like.
+library;
+
+import 'dart:io';
+
+import 'package:args/args.dart';
+
+void main() {
+ var parser = ArgParser();
+
+ parser.addSeparator('===== Platform');
+
+ parser.addOption('compiler',
+ abbr: 'c',
+ defaultsTo: 'none',
+ help: 'Specify any compilation step (if needed).',
+ allowed: [
+ 'none',
+ 'dart2js',
+ 'dartc'
+ ],
+ allowedHelp: {
+ 'none': 'Do not compile the Dart code (run native Dart code on the'
+ ' VM).\n(only valid with the following runtimes: vm, drt)',
+ 'dart2js': 'Compile dart code to JavaScript by running dart2js.\n'
+ '(only valid with the following runtimes: d8, drt, chrome\n'
+ 'safari, ie, firefox, opera, none (compile only))',
+ 'dartc': 'Perform static analysis on Dart code by running dartc.\n'
+ '(only valid with the following runtimes: none)',
+ });
+
+ parser.addOption('runtime',
+ abbr: 'r',
+ defaultsTo: 'vm',
+ help: 'Where the tests should be run.',
+ allowed: [
+ 'vm',
+ 'd8',
+ 'drt',
+ 'dartium',
+ 'ff',
+ 'firefox',
+ 'chrome',
+ 'safari',
+ 'ie',
+ 'opera',
+ 'none'
+ ],
+ allowedHelp: {
+ 'vm': 'Run Dart code on the standalone dart vm.',
+ 'd8': 'Run JavaScript from the command line using v8.',
+ 'drt': 'Run Dart or JavaScript in the headless version of Chrome,\n'
+ 'content shell.',
+ 'dartium': 'Run Dart or JavaScript in Dartium.',
+ 'ff': 'Run JavaScript in Firefox',
+ 'chrome': 'Run JavaScript in Chrome',
+ 'safari': 'Run JavaScript in Safari',
+ 'ie': 'Run JavaScript in Internet Explorer',
+ 'opera': 'Run JavaScript in Opera',
+ 'none': 'No runtime, compile only (for example, used for dartc static\n'
+ 'analysis tests).',
+ });
+
+ parser.addOption('arch',
+ abbr: 'a',
+ defaultsTo: 'ia32',
+ help: 'The architecture to run tests for',
+ allowed: ['all', 'ia32', 'x64', 'simarm']);
+
+ parser.addOption('system',
+ abbr: 's',
+ defaultsTo: Platform.operatingSystem,
+ help: 'The operating system to run tests on',
+ allowed: ['linux', 'macos', 'windows']);
+
+ parser.addSeparator('===== Runtime');
+
+ parser.addOption('mode',
+ abbr: 'm',
+ defaultsTo: 'debug',
+ help: 'Mode in which to run the tests',
+ allowed: ['all', 'debug', 'release']);
+
+ parser.addFlag('checked',
+ defaultsTo: false, help: 'Run tests in checked mode');
+
+ parser.addFlag('host-checked',
+ defaultsTo: false, help: 'Run compiler in checked mode');
+
+ parser.addOption('timeout', abbr: 't', help: 'Timeout in seconds');
+
+ parser.addOption('tasks',
+ abbr: 'j',
+ defaultsTo: Platform.numberOfProcessors.toString(),
+ help: 'The number of parallel tasks to run');
+
+ parser.addOption('shards',
+ defaultsTo: '1',
+ help: 'The number of instances that the tests will be sharded over');
+
+ parser.addOption('shard',
+ defaultsTo: '1',
+ help: 'The index of this instance when running in sharded mode');
+
+ parser.addFlag('valgrind',
+ defaultsTo: false, help: 'Run tests through valgrind');
+
+ parser.addSeparator('===== Output');
+
+ parser.addOption('progress',
+ abbr: 'p',
+ defaultsTo: 'compact',
+ help: 'Progress indication mode',
+ allowed: [
+ 'compact',
+ 'color',
+ 'line',
+ 'verbose',
+ 'silent',
+ 'status',
+ 'buildbot'
+ ]);
+
+ parser.addFlag('report',
+ defaultsTo: false,
+ help: 'Print a summary report of the number of tests, by expectation');
+
+ parser.addFlag('verbose',
+ abbr: 'v', defaultsTo: false, help: 'Verbose output');
+
+ parser.addFlag('list',
+ defaultsTo: false, help: 'List tests only, do not run them');
+
+ parser.addFlag('time',
+ help: 'Print timing information after running tests', defaultsTo: false);
+
+ parser.addFlag('batch',
+ abbr: 'b', help: 'Run browser tests in batch mode', defaultsTo: true);
+
+ parser.addSeparator('===== Miscellaneous');
+
+ parser.addFlag('keep-generated-tests',
+ defaultsTo: false,
+ help: 'Keep the generated files in the temporary directory');
+
+ parser.addOption('special-command', help: """
+Special command support. Wraps the command line in
+a special command. The special command should contain
+an '@' character which will be replaced by the normal
+command.
+
+For example if the normal command that will be executed
+is 'dart file.dart' and you specify special command
+'python -u valgrind.py @ suffix' the final command will be
+'python -u valgrind.py dart file.dart suffix'""");
+
+ parser.addOption('dart', help: 'Path to dart executable');
+ parser.addOption('drt', help: 'Path to content shell executable');
+ parser.addOption('dartium', help: 'Path to Dartium Chrome executable');
+ parser.addOption('mandatory', help: 'A mandatory option', mandatory: true);
+
+ print(parser.usage);
+}
diff --git a/pkgs/args/example/arg_parser/pubspec.yaml b/pkgs/args/example/arg_parser/pubspec.yaml
new file mode 100644
index 0000000..9cb1cbd
--- /dev/null
+++ b/pkgs/args/example/arg_parser/pubspec.yaml
@@ -0,0 +1,15 @@
+# Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+name: arg_parser_example
+version: 1.0.0
+description: An example of using ArgParser
+publish_to: 'none'
+
+environment:
+ sdk: ^3.0.0
+
+dependencies:
+ args:
+ path: ../..
diff --git a/pkgs/args/example/command_runner/README.md b/pkgs/args/example/command_runner/README.md
new file mode 100644
index 0000000..48bf60e
--- /dev/null
+++ b/pkgs/args/example/command_runner/README.md
@@ -0,0 +1,5 @@
+# Example of using `CommandRunner`
+
+This example uses `CommandRunner` to create a tool for drawing ascii art shapes.
+
+`dart run draw.dart circle --radius=10`
diff --git a/pkgs/args/example/command_runner/draw.dart b/pkgs/args/example/command_runner/draw.dart
new file mode 100644
index 0000000..018bf59
--- /dev/null
+++ b/pkgs/args/example/command_runner/draw.dart
@@ -0,0 +1,142 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:math';
+
+import 'package:args/command_runner.dart';
+
+void main(List<String> args) async {
+ final runner = CommandRunner<String>('draw', 'Draws shapes')
+ ..addCommand(SquareCommand())
+ ..addCommand(CircleCommand())
+ ..addCommand(TriangleCommand());
+ runner.argParser.addOption('char', help: 'The character to use for drawing');
+ final output = await runner.run(args);
+ print(output);
+}
+
+class SquareCommand extends Command<String> {
+ SquareCommand() {
+ argParser.addOption('size', help: 'Size of the square');
+ }
+
+ @override
+ String get name => 'square';
+
+ @override
+ String get description => 'Draws a square';
+
+ @override
+ List<String> get aliases => ['s'];
+
+ @override
+ FutureOr<String>? run() {
+ final size = int.parse(argResults?.option('size') ?? '20');
+ final char = globalResults?.option('char')?[0] ?? '#';
+ return draw(size, size, char, (x, y) => true);
+ }
+}
+
+class CircleCommand extends Command<String> {
+ CircleCommand() {
+ argParser.addOption('radius', help: 'Radius of the circle');
+ }
+
+ @override
+ String get name => 'circle';
+
+ @override
+ String get description => 'Draws a circle';
+
+ @override
+ List<String> get aliases => ['c'];
+
+ @override
+ FutureOr<String>? run() {
+ final size = 2 * int.parse(argResults?.option('radius') ?? '10');
+ final char = globalResults?.option('char')?[0] ?? '#';
+ return draw(size, size, char, (x, y) => x * x + y * y < 1);
+ }
+}
+
+class TriangleCommand extends Command<String> {
+ TriangleCommand() {
+ addSubcommand(EquilateralTriangleCommand());
+ addSubcommand(IsoscelesTriangleCommand());
+ }
+
+ @override
+ String get name => 'triangle';
+
+ @override
+ String get description => 'Draws a triangle';
+
+ @override
+ List<String> get aliases => ['t'];
+}
+
+class EquilateralTriangleCommand extends Command<String> {
+ EquilateralTriangleCommand() {
+ argParser.addOption('size', help: 'Size of the triangle');
+ }
+
+ @override
+ String get name => 'equilateral';
+
+ @override
+ String get description => 'Draws an equilateral triangle';
+
+ @override
+ List<String> get aliases => ['e'];
+
+ @override
+ FutureOr<String>? run() {
+ final size = int.parse(argResults?.option('size') ?? '20');
+ final char = globalResults?.option('char')?[0] ?? '#';
+ return drawTriangle(size, size * sqrt(3) ~/ 2, char);
+ }
+}
+
+class IsoscelesTriangleCommand extends Command<String> {
+ IsoscelesTriangleCommand() {
+ argParser.addOption('width', help: 'Width of the triangle');
+ argParser.addOption('height', help: 'Height of the triangle');
+ }
+
+ @override
+ String get name => 'isosceles';
+
+ @override
+ String get description => 'Draws an isosceles triangle';
+
+ @override
+ List<String> get aliases => ['i'];
+
+ @override
+ FutureOr<String>? run() {
+ final width = int.parse(argResults?.option('width') ?? '50');
+ final height = int.parse(argResults?.option('height') ?? '10');
+ final char = globalResults?.option('char')?[0] ?? '#';
+ return drawTriangle(width, height, char);
+ }
+}
+
+String draw(
+ int width, int height, String char, bool Function(double, double) pixel) {
+ final out = StringBuffer();
+ for (var y = 0; y <= height; ++y) {
+ final ty = 2 * y / height - 1;
+ for (var x = 0; x <= width; ++x) {
+ final tx = 2 * x / width - 1;
+ out.write(pixel(tx, ty) ? char : ' ');
+ }
+ out.write('\n');
+ }
+ return out.toString();
+}
+
+String drawTriangle(int width, int height, String char) {
+ return draw(width, height, char, (x, y) => x.abs() <= (1 + y) / 2);
+}
diff --git a/pkgs/args/example/command_runner/pubspec.yaml b/pkgs/args/example/command_runner/pubspec.yaml
new file mode 100644
index 0000000..636b464
--- /dev/null
+++ b/pkgs/args/example/command_runner/pubspec.yaml
@@ -0,0 +1,15 @@
+# Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+name: command_runner_example
+version: 1.0.0
+description: An example of using CommandRunner
+publish_to: 'none'
+
+environment:
+ sdk: ^3.0.0
+
+dependencies:
+ args:
+ path: ../..
diff --git a/pkgs/args/lib/args.dart b/pkgs/args/lib/args.dart
new file mode 100644
index 0000000..6011d1e
--- /dev/null
+++ b/pkgs/args/lib/args.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'src/arg_parser.dart' show ArgParser;
+export 'src/arg_parser_exception.dart' show ArgParserException;
+export 'src/arg_results.dart' show ArgResults;
+export 'src/option.dart' show Option, OptionType;
diff --git a/pkgs/args/lib/command_runner.dart b/pkgs/args/lib/command_runner.dart
new file mode 100644
index 0000000..e72a08d
--- /dev/null
+++ b/pkgs/args/lib/command_runner.dart
@@ -0,0 +1,559 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:math' as math;
+
+import 'src/arg_parser.dart';
+import 'src/arg_parser_exception.dart';
+import 'src/arg_results.dart';
+import 'src/help_command.dart';
+import 'src/usage_exception.dart';
+import 'src/utils.dart';
+
+export 'src/usage_exception.dart';
+
+/// A class for invoking [Command]s based on raw command-line arguments.
+///
+/// The type argument `T` represents the type returned by [Command.run] and
+/// [CommandRunner.run]; it can be ommitted if you're not using the return
+/// values.
+class CommandRunner<T> {
+ /// The name of the executable being run.
+ ///
+ /// Used for error reporting and [usage].
+ final String executableName;
+
+ /// A short description of this executable.
+ final String description;
+
+ /// A single-line template for how to invoke this executable.
+ ///
+ /// Defaults to `"$executableName <command> arguments`". Subclasses can
+ /// override this for a more specific template.
+ String get invocation => '$executableName <command> [arguments]';
+
+ /// Generates a string displaying usage information for the executable.
+ ///
+ /// This includes usage for the global arguments as well as a list of
+ /// top-level commands.
+ String get usage => _wrap('$description\n\n') + _usageWithoutDescription;
+
+ /// An optional footer for [usage].
+ ///
+ /// If a subclass overrides this to return a string, it will automatically be
+ /// added to the end of [usage].
+ String? get usageFooter => null;
+
+ /// Returns [usage] with [description] removed from the beginning.
+ String get _usageWithoutDescription {
+ var usagePrefix = 'Usage:';
+ var buffer = StringBuffer();
+ buffer.writeln(
+ '$usagePrefix ${_wrap(invocation, hangingIndent: usagePrefix.length)}\n',
+ );
+ buffer.writeln(_wrap('Global options:'));
+ buffer.writeln('${argParser.usage}\n');
+ buffer.writeln(
+ '${_getCommandUsage(_commands, lineLength: argParser.usageLineLength)}\n',
+ );
+ buffer.write(_wrap(
+ 'Run "$executableName help <command>" for more information about a '
+ 'command.'));
+ if (usageFooter != null) {
+ buffer.write('\n${_wrap(usageFooter!)}');
+ }
+ return buffer.toString();
+ }
+
+ /// An unmodifiable view of all top-level commands defined for this runner.
+ Map<String, Command<T>> get commands => UnmodifiableMapView(_commands);
+ final _commands = <String, Command<T>>{};
+
+ /// The top-level argument parser.
+ ///
+ /// Global options should be registered with this parser; they'll end up
+ /// available via [Command.globalResults]. Commands should be registered with
+ /// [addCommand] rather than directly on the parser.
+ ArgParser get argParser => _argParser;
+ final ArgParser _argParser;
+
+ /// The maximum edit distance allowed when suggesting possible intended
+ /// commands.
+ ///
+ /// Set to `0` in order to disable suggestions, defaults to `2`.
+ final int suggestionDistanceLimit;
+
+ CommandRunner(this.executableName, this.description,
+ {int? usageLineLength, this.suggestionDistanceLimit = 2})
+ : _argParser = ArgParser(usageLineLength: usageLineLength) {
+ argParser.addFlag('help',
+ abbr: 'h', negatable: false, help: 'Print this usage information.');
+ addCommand(HelpCommand<T>());
+ }
+
+ /// Prints the usage information for this runner.
+ ///
+ /// This is called internally by [run] and can be overridden by subclasses to
+ /// control how output is displayed or integrate with a logging system.
+ void printUsage() => print(usage);
+
+ /// Throws a [UsageException] with [message].
+ Never usageException(String message) =>
+ throw UsageException(message, _usageWithoutDescription);
+
+ /// Adds [Command] as a top-level command to this runner.
+ void addCommand(Command<T> command) {
+ var names = [command.name, ...command.aliases];
+ for (var name in names) {
+ _commands[name] = command;
+ argParser.addCommand(name, command.argParser);
+ }
+ command._runner = this;
+ }
+
+ /// Parses [args] and invokes [Command.run] on the chosen command.
+ ///
+ /// This always returns a [Future] in case the command is asynchronous. The
+ /// [Future] will throw a [UsageException] if [args] was invalid.
+ Future<T?> run(Iterable<String> args) =>
+ Future.sync(() => runCommand(parse(args)));
+
+ /// Parses [args] and returns the result, converting an [ArgParserException]
+ /// to a [UsageException].
+ ///
+ /// This is notionally a protected method. It may be overridden or called from
+ /// subclasses, but it shouldn't be called externally.
+ ArgResults parse(Iterable<String> args) {
+ try {
+ return argParser.parse(args);
+ } on ArgParserException catch (error) {
+ if (error.commands.isEmpty) usageException(error.message);
+
+ var command = commands[error.commands.first]!;
+ for (var commandName in error.commands.skip(1)) {
+ command = command.subcommands[commandName]!;
+ }
+
+ command.usageException(error.message);
+ }
+ }
+
+ /// Runs the command specified by [topLevelResults].
+ ///
+ /// This is notionally a protected method. It may be overridden or called from
+ /// subclasses, but it shouldn't be called externally.
+ ///
+ /// It's useful to override this to handle global flags and/or wrap the entire
+ /// command in a block. For example, you might handle the `--verbose` flag
+ /// here to enable verbose logging before running the command.
+ ///
+ /// This returns the return value of [Command.run].
+ Future<T?> runCommand(ArgResults topLevelResults) async {
+ var argResults = topLevelResults;
+ var commands = _commands;
+ Command? command;
+ var commandString = executableName;
+
+ while (commands.isNotEmpty) {
+ if (argResults.command == null) {
+ if (argResults.rest.isEmpty) {
+ if (command == null) {
+ // No top-level command was chosen.
+ printUsage();
+ return null;
+ }
+
+ command.usageException('Missing subcommand for "$commandString".');
+ } else {
+ var requested = argResults.rest[0];
+
+ // Build up a help message containing similar commands, if found.
+ var similarCommands =
+ _similarCommandsText(requested, commands.values);
+
+ if (command == null) {
+ usageException(
+ 'Could not find a command named "$requested".$similarCommands');
+ }
+
+ command.usageException('Could not find a subcommand named '
+ '"$requested" for "$commandString".$similarCommands');
+ }
+ }
+
+ // Step into the command.
+ argResults = argResults.command!;
+ command = commands[argResults.name]!;
+ command._globalResults = topLevelResults;
+ command._argResults = argResults;
+ commands = command._subcommands as Map<String, Command<T>>;
+ commandString += ' ${argResults.name}';
+
+ if (argResults.options.contains('help') && argResults.flag('help')) {
+ command.printUsage();
+ return null;
+ }
+ }
+
+ if (topLevelResults.flag('help')) {
+ command!.printUsage();
+ return null;
+ }
+
+ // Make sure there aren't unexpected arguments.
+ if (!command!.takesArguments && argResults.rest.isNotEmpty) {
+ command.usageException(
+ 'Command "${argResults.name}" does not take any arguments.');
+ }
+
+ return (await command.run()) as T?;
+ }
+
+ // Returns help text for commands similar to `name`, in sorted order.
+ String _similarCommandsText(String name, Iterable<Command<T>> commands) {
+ if (suggestionDistanceLimit <= 0) return '';
+ var distances = <Command<T>, int>{};
+ var candidates =
+ SplayTreeSet<Command<T>>((a, b) => distances[a]! - distances[b]!);
+ for (var command in commands) {
+ if (command.hidden) continue;
+ for (var alias in [
+ command.name,
+ ...command.aliases,
+ ...command.suggestionAliases
+ ]) {
+ var distance = _editDistance(name, alias);
+ if (distance <= suggestionDistanceLimit) {
+ distances[command] =
+ math.min(distances[command] ?? distance, distance);
+ candidates.add(command);
+ }
+ }
+ }
+ if (candidates.isEmpty) return '';
+
+ var similar = StringBuffer();
+ similar
+ ..writeln()
+ ..writeln()
+ ..writeln('Did you mean one of these?');
+ for (var command in candidates) {
+ similar.writeln(' ${command.name}');
+ }
+
+ return similar.toString();
+ }
+
+ String _wrap(String text, {int? hangingIndent}) => wrapText(text,
+ length: argParser.usageLineLength, hangingIndent: hangingIndent);
+}
+
+/// A single command.
+///
+/// A command is known as a "leaf command" if it has no subcommands and is meant
+/// to be run. Leaf commands must override [run].
+///
+/// A command with subcommands is known as a "branch command" and cannot be run
+/// itself. It should call [addSubcommand] (often from the constructor) to
+/// register subcommands.
+abstract class Command<T> {
+ /// The name of this command.
+ String get name;
+
+ /// A description of this command, included in [usage].
+ String get description;
+
+ /// A short description of this command, included in [parent]'s
+ /// [CommandRunner.usage].
+ ///
+ /// This defaults to the first line of [description].
+ String get summary => description.split('\n').first;
+
+ /// The command's category.
+ ///
+ /// Displayed in [parent]'s [CommandRunner.usage]. Commands with categories
+ /// will be grouped together, and displayed after commands without a category.
+ String get category => '';
+
+ /// A single-line template for how to invoke this command (e.g. `"pub get
+ /// `package`"`).
+ String get invocation {
+ var parents = [name];
+ for (var command = parent; command != null; command = command.parent) {
+ parents.add(command.name);
+ }
+ parents.add(runner!.executableName);
+
+ var invocation = parents.reversed.join(' ');
+ return _subcommands.isNotEmpty
+ ? '$invocation <subcommand> [arguments]'
+ : '$invocation [arguments]';
+ }
+
+ /// The command's parent command, if this is a subcommand.
+ ///
+ /// This will be `null` until [addSubcommand] has been called with
+ /// this command.
+ Command<T>? get parent => _parent;
+ Command<T>? _parent;
+
+ /// The command runner for this command.
+ ///
+ /// This will be `null` until [CommandRunner.addCommand] has been called with
+ /// this command or one of its parents.
+ CommandRunner<T>? get runner {
+ if (parent == null) return _runner;
+ return parent!.runner;
+ }
+
+ CommandRunner<T>? _runner;
+
+ /// The parsed global argument results.
+ ///
+ /// This will be `null` until just before [Command.run] is called.
+ ArgResults? get globalResults => _globalResults;
+ ArgResults? _globalResults;
+
+ /// The parsed argument results for this command.
+ ///
+ /// This will be `null` until just before [Command.run] is called.
+ ArgResults? get argResults => _argResults;
+ ArgResults? _argResults;
+
+ /// The argument parser for this command.
+ ///
+ /// Options for this command should be registered with this parser (often in
+ /// the constructor); they'll end up available via [argResults]. Subcommands
+ /// should be registered with [addSubcommand] rather than directly on the
+ /// parser.
+ ///
+ /// This can be overridden to change the arguments passed to the `ArgParser`
+ /// constructor.
+ ArgParser get argParser => _argParser;
+ final _argParser = ArgParser();
+
+ /// Generates a string displaying usage information for this command.
+ ///
+ /// This includes usage for the command's arguments as well as a list of
+ /// subcommands, if there are any.
+ String get usage => _wrap('$description\n\n') + _usageWithoutDescription;
+
+ /// An optional footer for [usage].
+ ///
+ /// If a subclass overrides this to return a string, it will automatically be
+ /// added to the end of [usage].
+ String? get usageFooter => null;
+
+ String _wrap(String text, {int? hangingIndent}) {
+ return wrapText(text,
+ length: argParser.usageLineLength, hangingIndent: hangingIndent);
+ }
+
+ /// Returns [usage] with [description] removed from the beginning.
+ String get _usageWithoutDescription {
+ var length = argParser.usageLineLength;
+ var usagePrefix = 'Usage: ';
+ var buffer = StringBuffer()
+ ..writeln(
+ usagePrefix + _wrap(invocation, hangingIndent: usagePrefix.length))
+ ..writeln(argParser.usage);
+
+ if (_subcommands.isNotEmpty) {
+ buffer.writeln();
+ buffer.writeln(_getCommandUsage(
+ _subcommands,
+ isSubcommand: true,
+ lineLength: length,
+ ));
+ }
+
+ buffer.writeln();
+ buffer.write(
+ _wrap('Run "${runner!.executableName} help" to see global options.'));
+
+ if (usageFooter != null) {
+ buffer.writeln();
+ buffer.write(_wrap(usageFooter!));
+ }
+
+ return buffer.toString();
+ }
+
+ /// An unmodifiable view of all sublevel commands of this command.
+ Map<String, Command<T>> get subcommands => UnmodifiableMapView(_subcommands);
+ final _subcommands = <String, Command<T>>{};
+
+ /// Whether or not this command should be hidden from help listings.
+ ///
+ /// This is intended to be overridden by commands that want to mark themselves
+ /// hidden.
+ ///
+ /// By default, leaf commands are always visible. Branch commands are visible
+ /// as long as any of their leaf commands are visible.
+ bool get hidden {
+ // Leaf commands are visible by default.
+ if (_subcommands.isEmpty) return false;
+
+ // Otherwise, a command is hidden if all of its subcommands are.
+ return _subcommands.values.every((subcommand) => subcommand.hidden);
+ }
+
+ /// Whether or not this command takes positional arguments in addition to
+ /// options.
+ ///
+ /// If false, [CommandRunner.run] will throw a [UsageException] if arguments
+ /// are provided. Defaults to true.
+ ///
+ /// This is intended to be overridden by commands that don't want to receive
+ /// arguments. It has no effect for branch commands.
+ bool get takesArguments => true;
+
+ /// Alternate names for this command.
+ ///
+ /// These names won't be used in the documentation, but they will work when
+ /// invoked on the command line.
+ ///
+ /// This is intended to be overridden.
+ List<String> get aliases => const [];
+
+ /// Alternate non-functional names for this command.
+ ///
+ /// These names won't be used in the documentation, and also they won't work
+ /// when invoked on the command line. But if an unknown command is used it
+ /// will be matched against this when creating suggestions.
+ ///
+ /// A name does not have to be repeated both here and in [aliases].
+ ///
+ /// This is intended to be overridden.
+ List<String> get suggestionAliases => const [];
+
+ Command() {
+ if (!argParser.allowsAnything) {
+ argParser.addFlag('help',
+ abbr: 'h', negatable: false, help: 'Print this usage information.');
+ }
+ }
+
+ /// Runs this command.
+ ///
+ /// The return value is wrapped in a `Future` if necessary and returned by
+ /// [CommandRunner.runCommand].
+ FutureOr<T>? run() {
+ throw UnimplementedError(_wrap('Leaf command $this must implement run().'));
+ }
+
+ /// Adds [Command] as a subcommand of this.
+ void addSubcommand(Command<T> command) {
+ var names = [command.name, ...command.aliases];
+ for (var name in names) {
+ _subcommands[name] = command;
+ argParser.addCommand(name, command.argParser);
+ }
+ command._parent = this;
+ }
+
+ /// Prints the usage information for this command.
+ ///
+ /// This is called internally by [run] and can be overridden by subclasses to
+ /// control how output is displayed or integrate with a logging system.
+ void printUsage() => print(usage);
+
+ /// Throws a [UsageException] with [message].
+ Never usageException(String message) =>
+ throw UsageException(_wrap(message), _usageWithoutDescription);
+}
+
+/// Returns a string representation of [commands] fit for use in a usage string.
+///
+/// [isSubcommand] indicates whether the commands should be called "commands" or
+/// "subcommands".
+String _getCommandUsage(Map<String, Command> commands,
+ {bool isSubcommand = false, int? lineLength}) {
+ // Don't include aliases.
+ var names =
+ commands.keys.where((name) => !commands[name]!.aliases.contains(name));
+
+ // Filter out hidden ones, unless they are all hidden.
+ var visible = names.where((name) => !commands[name]!.hidden);
+ if (visible.isNotEmpty) names = visible;
+
+ // Show the commands alphabetically.
+ names = names.toList()..sort();
+
+ // Group the commands by category.
+ var commandsByCategory = SplayTreeMap<String, List<Command>>();
+ for (var name in names) {
+ var category = commands[name]!.category;
+ commandsByCategory.putIfAbsent(category, () => []).add(commands[name]!);
+ }
+ final categories = commandsByCategory.keys.toList();
+
+ var length = names.map((name) => name.length).reduce(math.max);
+
+ var buffer = StringBuffer('Available ${isSubcommand ? "sub" : ""}commands:');
+ var columnStart = length + 5;
+ for (var category in categories) {
+ if (category != '') {
+ buffer.writeln();
+ buffer.writeln();
+ buffer.write(category);
+ }
+ for (var command in commandsByCategory[category]!) {
+ var lines = wrapTextAsLines(command.summary,
+ start: columnStart, length: lineLength);
+ buffer.writeln();
+ buffer.write(' ${padRight(command.name, length)} ${lines.first}');
+
+ for (var line in lines.skip(1)) {
+ buffer.writeln();
+ buffer.write(' ' * columnStart);
+ buffer.write(line);
+ }
+ }
+ }
+
+ return buffer.toString();
+}
+
+/// Returns the edit distance between `from` and `to`.
+//
+/// Allows for edits, deletes, substitutions, and swaps all as single cost.
+///
+/// See https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance#Optimal_string_alignment_distance
+int _editDistance(String from, String to) {
+ // Add a space in front to mimic indexing by 1 instead of 0.
+ from = ' $from';
+ to = ' $to';
+ var distances = [
+ for (var i = 0; i < from.length; i++)
+ [
+ for (var j = 0; j < to.length; j++)
+ if (i == 0) j else if (j == 0) i else 0,
+ ],
+ ];
+
+ for (var i = 1; i < from.length; i++) {
+ for (var j = 1; j < to.length; j++) {
+ // Removals from `from`.
+ var min = distances[i - 1][j] + 1;
+ // Additions to `from`.
+ min = math.min(min, distances[i][j - 1] + 1);
+ // Substitutions (and equality).
+ min = math.min(
+ min,
+ distances[i - 1][j - 1] +
+ // Cost is zero if substitution was not actually necessary.
+ (from[i] == to[j] ? 0 : 1));
+ // Allows for basic swaps, but no additional edits of swapped regions.
+ if (i > 1 && j > 1 && from[i] == to[j - 1] && from[i - 1] == to[j]) {
+ min = math.min(min, distances[i - 2][j - 2] + 1);
+ }
+ distances[i][j] = min;
+ }
+ }
+
+ return distances.last.last;
+}
diff --git a/pkgs/args/lib/src/allow_anything_parser.dart b/pkgs/args/lib/src/allow_anything_parser.dart
new file mode 100644
index 0000000..69472b3
--- /dev/null
+++ b/pkgs/args/lib/src/allow_anything_parser.dart
@@ -0,0 +1,107 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'arg_parser.dart';
+import 'arg_results.dart';
+import 'option.dart';
+import 'parser.dart';
+
+/// An ArgParser that treats *all input* as non-option arguments.
+class AllowAnythingParser implements ArgParser {
+ @override
+ Map<String, Option> get options => const {};
+ @override
+ Map<String, ArgParser> get commands => const {};
+ @override
+ bool get allowTrailingOptions => false;
+ @override
+ bool get allowsAnything => true;
+ @override
+ int? get usageLineLength => null;
+
+ @override
+ ArgParser addCommand(String name, [ArgParser? parser]) {
+ throw UnsupportedError(
+ "ArgParser.allowAnything().addCommands() isn't supported.");
+ }
+
+ @override
+ void addFlag(String name,
+ {String? abbr,
+ String? help,
+ bool? defaultsTo = false,
+ bool negatable = true,
+ void Function(bool)? callback,
+ bool hide = false,
+ bool hideNegatedUsage = false,
+ List<String> aliases = const []}) {
+ throw UnsupportedError(
+ "ArgParser.allowAnything().addFlag() isn't supported.");
+ }
+
+ @override
+ void addOption(String name,
+ {String? abbr,
+ String? help,
+ String? valueHelp,
+ Iterable<String>? allowed,
+ Map<String, String>? allowedHelp,
+ String? defaultsTo,
+ void Function(String?)? callback,
+ bool allowMultiple = false,
+ bool? splitCommas,
+ bool mandatory = false,
+ bool hide = false,
+ List<String> aliases = const []}) {
+ throw UnsupportedError(
+ "ArgParser.allowAnything().addOption() isn't supported.");
+ }
+
+ @override
+ void addMultiOption(String name,
+ {String? abbr,
+ String? help,
+ String? valueHelp,
+ Iterable<String>? allowed,
+ Map<String, String>? allowedHelp,
+ Iterable<String>? defaultsTo,
+ void Function(List<String>)? callback,
+ bool splitCommas = true,
+ bool hide = false,
+ List<String> aliases = const []}) {
+ throw UnsupportedError(
+ "ArgParser.allowAnything().addMultiOption() isn't supported.");
+ }
+
+ @override
+ void addSeparator(String text) {
+ throw UnsupportedError(
+ "ArgParser.allowAnything().addSeparator() isn't supported.");
+ }
+
+ @override
+ ArgResults parse(Iterable<String> args) =>
+ Parser(null, this, Queue.of(args)).parse();
+
+ @override
+ String get usage => '';
+
+ @override
+ dynamic defaultFor(String option) {
+ throw ArgumentError('No option named $option');
+ }
+
+ @override
+ dynamic getDefault(String option) {
+ throw ArgumentError('No option named $option');
+ }
+
+ @override
+ Option? findByAbbreviation(String abbr) => null;
+
+ @override
+ Option? findByNameOrAlias(String name) => null;
+}
diff --git a/pkgs/args/lib/src/arg_parser.dart b/pkgs/args/lib/src/arg_parser.dart
new file mode 100644
index 0000000..50c3991
--- /dev/null
+++ b/pkgs/args/lib/src/arg_parser.dart
@@ -0,0 +1,383 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'allow_anything_parser.dart';
+import 'arg_results.dart';
+import 'option.dart';
+import 'parser.dart';
+import 'usage.dart';
+
+/// A class for taking a list of raw command line arguments and parsing out
+/// options and flags from them.
+class ArgParser {
+ final Map<String, Option> _options;
+ final Map<String, ArgParser> _commands;
+
+ /// A map of aliases to the option names they alias.
+ final Map<String, String> _aliases;
+
+ /// The options that have been defined for this parser.
+ final Map<String, Option> options;
+
+ /// The commands that have been defined for this parser.
+ final Map<String, ArgParser> commands;
+
+ /// A list of the [Option]s in [options] intermingled with [String]
+ /// separators.
+ final _optionsAndSeparators = <Object>[];
+
+ /// Whether or not this parser parses options that appear after non-option
+ /// arguments.
+ final bool allowTrailingOptions;
+
+ /// An optional maximum line length for [usage] messages.
+ ///
+ /// If specified, then help messages in the usage are wrapped at the given
+ /// column, after taking into account the width of the options. Will refuse to
+ /// wrap help text to less than 10 characters of help text per line if there
+ /// isn't enough space on the line. It preserves embedded newlines, and
+ /// attempts to wrap at whitespace breaks (although it will split words if
+ /// there is no whitespace at which to split).
+ ///
+ /// If null (the default), help messages are not wrapped.
+ final int? usageLineLength;
+
+ /// Whether or not this parser treats unrecognized options as non-option
+ /// arguments.
+ bool get allowsAnything => false;
+
+ /// Creates a new ArgParser.
+ ///
+ /// If [allowTrailingOptions] is `true` (the default), the parser will parse
+ /// flags and options that appear after positional arguments. If it's `false`,
+ /// the parser stops parsing as soon as it finds an argument that is neither
+ /// an option nor a command.
+ factory ArgParser({bool allowTrailingOptions = true, int? usageLineLength}) =>
+ ArgParser._(<String, Option>{}, <String, ArgParser>{}, <String, String>{},
+ allowTrailingOptions: allowTrailingOptions,
+ usageLineLength: usageLineLength);
+
+ /// Creates a new ArgParser that treats *all input* as non-option arguments.
+ ///
+ /// This is intended to allow arguments to be passed through to child
+ /// processes without needing to be redefined in the parent.
+ ///
+ /// Options may not be defined for this parser.
+ factory ArgParser.allowAnything() = AllowAnythingParser;
+
+ ArgParser._(Map<String, Option> options, Map<String, ArgParser> commands,
+ this._aliases,
+ {this.allowTrailingOptions = true, this.usageLineLength})
+ : _options = options,
+ options = UnmodifiableMapView(options),
+ _commands = commands,
+ commands = UnmodifiableMapView(commands);
+
+ /// Defines a command.
+ ///
+ /// A command is a named argument which may in turn define its own options and
+ /// subcommands using the given parser. If [parser] is omitted, implicitly
+ /// creates a new one. Returns the parser for the command.
+ ///
+ /// Note that adding commands this way will not impact the [usage] string. To
+ /// add commands which are included in the usage string see `CommandRunner`.
+ ArgParser addCommand(String name, [ArgParser? parser]) {
+ // Make sure the name isn't in use.
+ if (_commands.containsKey(name)) {
+ throw ArgumentError('Duplicate command "$name".');
+ }
+
+ parser ??= ArgParser();
+ _commands[name] = parser;
+ return parser;
+ }
+
+ /// Defines a boolean flag.
+ ///
+ /// This adds an [Option] with the given properties to [options].
+ ///
+ /// The [abbr] argument is a single-character string that can be used as a
+ /// shorthand for this flag. For example, `abbr: "a"` will allow the user to
+ /// pass `-a` to enable the flag.
+ ///
+ /// The [help] argument is used by [usage] to describe this flag.
+ ///
+ /// The [defaultsTo] argument indicates the value this flag will have if the
+ /// user doesn't explicitly pass it in.
+ ///
+ /// The [negatable] argument indicates whether this flag's value can be set to
+ /// `false`. For example, if [name] is `flag`, the user can pass `--no-flag`
+ /// to set its value to `false`.
+ ///
+ /// The [callback] argument is invoked with the flag's value when the flag
+ /// is parsed. Note that this makes argument parsing order-dependent in ways
+ /// that are often surprising, and its use is discouraged in favor of reading
+ /// values from the [ArgResults].
+ ///
+ /// If [hide] is `true`, this option won't be included in [usage].
+ ///
+ /// If [hideNegatedUsage] is `true`, the fact that this flag can be negated
+ /// will not be documented in [usage].
+ /// It is an error for [hideNegatedUsage] to be `true` if [negatable] is
+ /// `false`.
+ ///
+ /// If [aliases] is provided, these are used as aliases for [name]. These
+ /// aliases will not appear as keys in the [options] map.
+ ///
+ /// Throws an [ArgumentError] if:
+ ///
+ /// * There is already an option named [name].
+ /// * There is already an option using abbreviation [abbr].
+ void addFlag(String name,
+ {String? abbr,
+ String? help,
+ bool? defaultsTo = false,
+ bool negatable = true,
+ void Function(bool)? callback,
+ bool hide = false,
+ bool hideNegatedUsage = false,
+ List<String> aliases = const []}) {
+ _addOption(
+ name,
+ abbr,
+ help,
+ null,
+ null,
+ null,
+ defaultsTo,
+ callback == null ? null : (bool value) => callback(value),
+ OptionType.flag,
+ negatable: negatable,
+ hide: hide,
+ hideNegatedUsage: hideNegatedUsage,
+ aliases: aliases);
+ }
+
+ /// Defines an option that takes a value.
+ ///
+ /// This adds an [Option] with the given properties to [options].
+ ///
+ /// The [abbr] argument is a single-character string that can be used as a
+ /// shorthand for this option. For example, `abbr: "a"` will allow the user to
+ /// pass `-a value` or `-avalue`.
+ ///
+ /// The [help] argument is used by [usage] to describe this option.
+ ///
+ /// The [valueHelp] argument is used by [usage] as a name for the value this
+ /// option takes. For example, `valueHelp: "FOO"` will include
+ /// `--option=<FOO>` rather than just `--option` in the usage string.
+ ///
+ /// The [allowed] argument is a list of valid values for this option. If
+ /// it's non-`null` and the user passes a value that's not included in the
+ /// list, [parse] will throw a [FormatException]. The allowed values will also
+ /// be included in [usage].
+ ///
+ /// The [allowedHelp] argument is a map from values in [allowed] to
+ /// documentation for those values that will be included in [usage].
+ ///
+ /// The [defaultsTo] argument indicates the value this option will have if the
+ /// user doesn't explicitly pass it in (or `null` by default).
+ ///
+ /// The [callback] argument is invoked with the option's value when the option
+ /// is parsed, or with `null` if the option was not parsed.
+ /// Note that this makes argument parsing order-dependent in ways that are
+ /// often surprising, and its use is discouraged in favor of reading values
+ /// from the [ArgResults].
+ ///
+ /// If [hide] is `true`, this option won't be included in [usage].
+ ///
+ /// If [aliases] is provided, these are used as aliases for [name]. These
+ /// aliases will not appear as keys in the [options] map.
+ ///
+ /// Throws an [ArgumentError] if:
+ ///
+ /// * There is already an option with name [name].
+ /// * There is already an option using abbreviation [abbr].
+ void addOption(String name,
+ {String? abbr,
+ String? help,
+ String? valueHelp,
+ Iterable<String>? allowed,
+ Map<String, String>? allowedHelp,
+ String? defaultsTo,
+ void Function(String?)? callback,
+ bool mandatory = false,
+ bool hide = false,
+ List<String> aliases = const []}) {
+ _addOption(name, abbr, help, valueHelp, allowed, allowedHelp, defaultsTo,
+ callback, OptionType.single,
+ mandatory: mandatory, hide: hide, aliases: aliases);
+ }
+
+ /// Defines an option that takes multiple values.
+ ///
+ /// The [abbr] argument is a single-character string that can be used as a
+ /// shorthand for this option. For example, `abbr: "a"` will allow the user to
+ /// pass `-a value` or `-avalue`.
+ ///
+ /// The [help] argument is used by [usage] to describe this option.
+ ///
+ /// The [valueHelp] argument is used by [usage] as a name for the value this
+ /// argument takes. For example, `valueHelp: "FOO"` will include
+ /// `--option=<FOO>` rather than just `--option` in the usage string.
+ ///
+ /// The [allowed] argument is a list of valid values for this argument. If
+ /// it's non-`null` and the user passes a value that's not included in the
+ /// list, [parse] will throw a [FormatException]. The allowed values will also
+ /// be included in [usage].
+ ///
+ /// The [allowedHelp] argument is a map from values in [allowed] to
+ /// documentation for those values that will be included in [usage].
+ ///
+ /// The [defaultsTo] argument indicates the values this option will have if
+ /// the user doesn't explicitly pass it in (or `[]` by default).
+ ///
+ /// The [callback] argument is invoked with the option's value when the option
+ /// is parsed. Note that this makes argument parsing order-dependent in ways
+ /// that are often surprising, and its use is discouraged in favor of reading
+ /// values from the [ArgResults].
+ ///
+ /// If [splitCommas] is `true` (the default), multiple options may be passed
+ /// by writing `--option a,b` in addition to `--option a --option b`.
+ ///
+ /// If [hide] is `true`, this option won't be included in [usage].
+ ///
+ /// If [aliases] is provided, these are used as aliases for [name]. These
+ /// aliases will not appear as keys in the [options] map.
+ ///
+ /// Throws an [ArgumentError] if:
+ ///
+ /// * There is already an option with name [name].
+ /// * There is already an option using abbreviation [abbr].
+ void addMultiOption(String name,
+ {String? abbr,
+ String? help,
+ String? valueHelp,
+ Iterable<String>? allowed,
+ Map<String, String>? allowedHelp,
+ Iterable<String>? defaultsTo,
+ void Function(List<String>)? callback,
+ bool splitCommas = true,
+ bool hide = false,
+ List<String> aliases = const []}) {
+ _addOption(
+ name,
+ abbr,
+ help,
+ valueHelp,
+ allowed,
+ allowedHelp,
+ defaultsTo?.toList() ?? <String>[],
+ callback == null ? null : (List<String> value) => callback(value),
+ OptionType.multiple,
+ splitCommas: splitCommas,
+ hide: hide,
+ aliases: aliases);
+ }
+
+ void _addOption(
+ String name,
+ String? abbr,
+ String? help,
+ String? valueHelp,
+ Iterable<String>? allowed,
+ Map<String, String>? allowedHelp,
+ Object? defaultsTo,
+ Function? callback,
+ OptionType type,
+ {bool negatable = false,
+ bool? splitCommas,
+ bool mandatory = false,
+ bool hide = false,
+ bool hideNegatedUsage = false,
+ List<String> aliases = const []}) {
+ var allNames = [name, ...aliases];
+ if (allNames.any((name) => findByNameOrAlias(name) != null)) {
+ throw ArgumentError('Duplicate option or alias "$name".');
+ }
+
+ // Make sure the abbreviation isn't too long or in use.
+ if (abbr != null) {
+ var existing = findByAbbreviation(abbr);
+ if (existing != null) {
+ throw ArgumentError(
+ 'Abbreviation "$abbr" is already used by "${existing.name}".');
+ }
+ }
+
+ // Make sure the option is not mandatory with a default value.
+ if (mandatory && defaultsTo != null) {
+ throw ArgumentError(
+ 'The option $name cannot be mandatory and have a default value.');
+ }
+
+ if (!negatable && hideNegatedUsage) {
+ throw ArgumentError(
+ 'The option $name cannot have `hideNegatedUsage` '
+ 'without being negatable.',
+ );
+ }
+
+ var option = newOption(name, abbr, help, valueHelp, allowed, allowedHelp,
+ defaultsTo, callback, type,
+ negatable: negatable,
+ splitCommas: splitCommas,
+ mandatory: mandatory,
+ hide: hide,
+ hideNegatedUsage: hideNegatedUsage,
+ aliases: aliases);
+ _options[name] = option;
+ _optionsAndSeparators.add(option);
+ for (var alias in aliases) {
+ _aliases[alias] = name;
+ }
+ }
+
+ /// Adds a separator line to the usage.
+ ///
+ /// In the usage text for the parser, this will appear between any options
+ /// added before this call and ones added after it.
+ void addSeparator(String text) {
+ _optionsAndSeparators.add(text);
+ }
+
+ /// Parses [args], a list of command-line arguments, matches them against the
+ /// flags and options defined by this parser, and returns the result.
+ ArgResults parse(Iterable<String> args) =>
+ Parser(null, this, Queue.of(args)).parse();
+
+ /// Generates a string displaying usage information for the defined options.
+ ///
+ /// This is basically the help text shown on the command line.
+ String get usage {
+ return generateUsage(_optionsAndSeparators, lineLength: usageLineLength);
+ }
+
+ /// Returns the default value for [option].
+ dynamic defaultFor(String option) {
+ var value = findByNameOrAlias(option);
+ if (value == null) {
+ throw ArgumentError('No option named $option');
+ }
+ return value.defaultsTo;
+ }
+
+ @Deprecated('Use defaultFor instead.')
+ dynamic getDefault(String option) => defaultFor(option);
+
+ /// Finds the option whose abbreviation is [abbr], or `null` if no option has
+ /// that abbreviation.
+ Option? findByAbbreviation(String abbr) {
+ for (var option in options.values) {
+ if (option.abbr == abbr) return option;
+ }
+ return null;
+ }
+
+ /// Finds the option whose name or alias matches [name], or `null` if no
+ /// option has that name or alias.
+ Option? findByNameOrAlias(String name) => options[_aliases[name] ?? name];
+}
diff --git a/pkgs/args/lib/src/arg_parser_exception.dart b/pkgs/args/lib/src/arg_parser_exception.dart
new file mode 100644
index 0000000..fbee82b
--- /dev/null
+++ b/pkgs/args/lib/src/arg_parser_exception.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// An exception thrown by `ArgParser`.
+class ArgParserException extends FormatException {
+ /// The command(s) that were parsed before discovering the error.
+ ///
+ /// This will be empty if the error was on the root parser.
+ final List<String> commands;
+
+ /// The name of the argument that was being parsed when the error was
+ /// discovered.
+ final String? argumentName;
+
+ ArgParserException(super.message,
+ [Iterable<String>? commands,
+ this.argumentName,
+ super.source,
+ super.offset])
+ : commands = commands == null ? const [] : List.unmodifiable(commands);
+}
diff --git a/pkgs/args/lib/src/arg_results.dart b/pkgs/args/lib/src/arg_results.dart
new file mode 100644
index 0000000..72c4410
--- /dev/null
+++ b/pkgs/args/lib/src/arg_results.dart
@@ -0,0 +1,151 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'arg_parser.dart';
+
+/// Creates a new [ArgResults].
+///
+/// Since [ArgResults] doesn't have a public constructor, this lets [ArgParser]
+/// get to it. This function isn't exported to the public API of the package.
+ArgResults newArgResults(
+ ArgParser parser,
+ Map<String, dynamic> parsed,
+ String? name,
+ ArgResults? command,
+ List<String> rest,
+ List<String> arguments) {
+ return ArgResults._(parser, parsed, name, command, rest, arguments);
+}
+
+/// The results of parsing a series of command line arguments using
+/// [ArgParser.parse].
+///
+/// Includes the parsed options and any remaining unparsed command line
+/// arguments.
+class ArgResults {
+ /// The [ArgParser] whose options were parsed for these results.
+ final ArgParser _parser;
+
+ /// The option values that were parsed from arguments.
+ final Map<String, dynamic> _parsed;
+
+ /// The name of the command for which these options are parsed, or `null` if
+ /// these are the top-level results.
+ final String? name;
+
+ /// The command that was selected, or `null` if none was.
+ ///
+ /// This will contain the options that were selected for that command.
+ final ArgResults? command;
+
+ /// The remaining command-line arguments that were not parsed as options or
+ /// flags.
+ ///
+ /// If `--` was used to separate the options from the remaining arguments,
+ /// it will not be included in this list unless parsing stopped before the
+ /// `--` was reached.
+ final List<String> rest;
+
+ /// The original arguments that were parsed.
+ final List<String> arguments;
+
+ ArgResults._(this._parser, this._parsed, this.name, this.command,
+ List<String> rest, List<String> arguments)
+ : rest = UnmodifiableListView(rest),
+ arguments = UnmodifiableListView(arguments);
+
+ /// Returns the parsed or default command-line option named [name].
+ ///
+ /// [name] must be a valid option name in the parser.
+ ///
+ /// > [!Note]
+ /// > Callers should prefer using the more strongly typed methods - [flag] for
+ /// > flags, [option] for options, and [multiOption] for multi-options.
+ dynamic operator [](String name) {
+ if (!_parser.options.containsKey(name)) {
+ throw ArgumentError('Could not find an option named "--$name".');
+ }
+
+ final option = _parser.options[name]!;
+ if (option.mandatory && !_parsed.containsKey(name)) {
+ throw ArgumentError('Option $name is mandatory.');
+ }
+
+ return option.valueOrDefault(_parsed[name]);
+ }
+
+ /// Returns the parsed or default command-line flag named [name].
+ ///
+ /// [name] must be a valid flag name in the parser.
+ bool flag(String name) {
+ var option = _parser.options[name];
+ if (option == null) {
+ throw ArgumentError('Could not find an option named "--$name".');
+ }
+ if (!option.isFlag) {
+ throw ArgumentError('"$name" is not a flag.');
+ }
+ return option.valueOrDefault(_parsed[name]) as bool;
+ }
+
+ /// Returns the parsed or default command-line option named [name].
+ ///
+ /// [name] must be a valid option name in the parser.
+ String? option(String name) {
+ var option = _parser.options[name];
+ if (option == null) {
+ throw ArgumentError('Could not find an option named "--$name".');
+ }
+ if (!option.isSingle) {
+ throw ArgumentError('"$name" is a multi-option.');
+ }
+ return option.valueOrDefault(_parsed[name]) as String?;
+ }
+
+ /// Returns the list of parsed (or default) command-line options for [name].
+ ///
+ /// [name] must be a valid option name in the parser.
+ List<String> multiOption(String name) {
+ var option = _parser.options[name];
+ if (option == null) {
+ throw ArgumentError('Could not find an option named "--$name".');
+ }
+ if (!option.isMultiple) {
+ throw ArgumentError('"$name" is not a multi-option.');
+ }
+ return option.valueOrDefault(_parsed[name]) as List<String>;
+ }
+
+ /// The names of the available options.
+ ///
+ /// Includes the options whose values were parsed or that have defaults.
+ /// Options that weren't present and have no default are omitted.
+ Iterable<String> get options {
+ var result = _parsed.keys.toSet();
+
+ // Include the options that have defaults.
+ _parser.options.forEach((name, option) {
+ if (option.defaultsTo != null) result.add(name);
+ });
+
+ return result;
+ }
+
+ /// Returns `true` if the option with [name] was parsed from an actual
+ /// argument.
+ ///
+ /// Returns `false` if it wasn't provided and the default value or no default
+ /// value would be used instead.
+ ///
+ /// [name] must be a valid option name in the parser.
+ bool wasParsed(String name) {
+ if (!_parser.options.containsKey(name)) {
+ throw ArgumentError('Could not find an option named "--$name".');
+ }
+
+ return _parsed.containsKey(name);
+ }
+}
diff --git a/pkgs/args/lib/src/help_command.dart b/pkgs/args/lib/src/help_command.dart
new file mode 100644
index 0000000..f04d014
--- /dev/null
+++ b/pkgs/args/lib/src/help_command.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../command_runner.dart';
+
+/// The built-in help command that's added to every [CommandRunner].
+///
+/// This command displays help information for the various subcommands.
+class HelpCommand<T> extends Command<T> {
+ @override
+ final name = 'help';
+
+ @override
+ String get description =>
+ 'Display help information for ${runner!.executableName}.';
+
+ @override
+ String get invocation => '${runner!.executableName} help [command]';
+
+ @override
+ bool get hidden => true;
+
+ @override
+ Null run() {
+ // Show the default help if no command was specified.
+ if (argResults!.rest.isEmpty) {
+ runner!.printUsage();
+ return;
+ }
+
+ // Walk the command tree to show help for the selected command or
+ // subcommand.
+ var commands = runner!.commands;
+ Command<T>? command;
+ var commandString = runner!.executableName;
+
+ for (var name in argResults!.rest) {
+ if (commands.isEmpty) {
+ command!.usageException(
+ 'Command "$commandString" does not expect a subcommand.');
+ }
+
+ if (commands[name] == null) {
+ if (command == null) {
+ runner!.usageException('Could not find a command named "$name".');
+ }
+
+ command.usageException(
+ 'Could not find a subcommand named "$name" for "$commandString".');
+ }
+
+ command = commands[name];
+ commands = command!.subcommands;
+ commandString += ' $name';
+ }
+
+ command!.printUsage();
+ }
+}
diff --git a/pkgs/args/lib/src/option.dart b/pkgs/args/lib/src/option.dart
new file mode 100644
index 0000000..463e5e2
--- /dev/null
+++ b/pkgs/args/lib/src/option.dart
@@ -0,0 +1,200 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Creates a new [Option].
+///
+/// Since [Option] doesn't have a public constructor, this lets `ArgParser`
+/// get to it. This function isn't exported to the public API of the package.
+Option newOption(
+ String name,
+ String? abbr,
+ String? help,
+ String? valueHelp,
+ Iterable<String>? allowed,
+ Map<String, String>? allowedHelp,
+ Object? defaultsTo,
+ Function? callback,
+ OptionType type,
+ {bool? negatable,
+ bool? splitCommas,
+ bool mandatory = false,
+ bool hide = false,
+ bool hideNegatedUsage = false,
+ List<String> aliases = const []}) {
+ return Option._(name, abbr, help, valueHelp, allowed, allowedHelp, defaultsTo,
+ callback, type,
+ negatable: negatable,
+ splitCommas: splitCommas,
+ mandatory: mandatory,
+ hide: hide,
+ hideNegatedUsage: hideNegatedUsage,
+ aliases: aliases);
+}
+
+/// A command-line option.
+///
+/// This represents both boolean flags and options which take a value.
+class Option {
+ /// The name of the option that the user passes as an argument.
+ final String name;
+
+ /// A single-character string that can be used as a shorthand for this option.
+ ///
+ /// For example, `abbr: "a"` will allow the user to pass `-a value` or
+ /// `-avalue`.
+ final String? abbr;
+
+ /// A description of this option.
+ final String? help;
+
+ /// A name for the value this option takes.
+ final String? valueHelp;
+
+ /// A list of valid values for this option.
+ final List<String>? allowed;
+
+ /// A map from values in [allowed] to documentation for those values.
+ final Map<String, String>? allowedHelp;
+
+ /// The value this option will have if the user doesn't explicitly pass it.
+ final dynamic defaultsTo;
+
+ /// Whether this flag's value can be set to `false`.
+ ///
+ /// For example, if [name] is `flag`, the user can pass `--no-flag` to set its
+ /// value to `false`.
+ ///
+ /// This is `null` unless [type] is [OptionType.flag].
+ final bool? negatable;
+
+ /// Whether to document that this flag is [negatable].
+ ///
+ /// This is `null` unless [type] is [OptionType.flag].
+ final bool? hideNegatedUsage;
+
+ /// The callback to invoke with the option's value when the option is parsed.
+ final Function? callback;
+
+ /// Whether this is a flag, a single value option, or a multi-value option.
+ final OptionType type;
+
+ /// Whether multiple values may be passed by writing `--option a,b` in
+ /// addition to `--option a --option b`.
+ final bool splitCommas;
+
+ /// Whether this option must be provided for correct usage.
+ final bool mandatory;
+
+ /// Whether this option should be hidden from usage documentation.
+ final bool hide;
+
+ /// All aliases for [name].
+ final List<String> aliases;
+
+ /// Whether the option is boolean-valued flag.
+ bool get isFlag => type == OptionType.flag;
+
+ /// Whether the option takes a single value.
+ bool get isSingle => type == OptionType.single;
+
+ /// Whether the option allows multiple values.
+ bool get isMultiple => type == OptionType.multiple;
+
+ Option._(
+ this.name,
+ this.abbr,
+ this.help,
+ this.valueHelp,
+ Iterable<String>? allowed,
+ Map<String, String>? allowedHelp,
+ this.defaultsTo,
+ this.callback,
+ this.type,
+ {this.negatable,
+ bool? splitCommas,
+ this.mandatory = false,
+ this.hide = false,
+ this.hideNegatedUsage,
+ this.aliases = const []})
+ : allowed = allowed == null ? null : List.unmodifiable(allowed),
+ allowedHelp =
+ allowedHelp == null ? null : Map.unmodifiable(allowedHelp),
+ // If the user doesn't specify [splitCommas], it defaults to true for
+ // multiple options.
+ splitCommas = splitCommas ?? type == OptionType.multiple {
+ if (name.isEmpty) {
+ throw ArgumentError('Name cannot be empty.');
+ } else if (name.startsWith('-')) {
+ throw ArgumentError('Name $name cannot start with "-".');
+ }
+
+ // Ensure name does not contain any invalid characters.
+ if (_invalidChars.hasMatch(name)) {
+ throw ArgumentError('Name "$name" contains invalid characters.');
+ }
+
+ var abbr = this.abbr;
+ if (abbr != null) {
+ if (abbr.length != 1) {
+ throw ArgumentError('Abbreviation must be null or have length 1.');
+ } else if (abbr == '-') {
+ throw ArgumentError('Abbreviation cannot be "-".');
+ }
+
+ if (_invalidChars.hasMatch(abbr)) {
+ throw ArgumentError('Abbreviation is an invalid character.');
+ }
+ }
+ }
+
+ /// Returns [value] if non-`null`, otherwise returns the default value for
+ /// this option.
+ ///
+ /// For single-valued options, it will be [defaultsTo] if set or `null`
+ /// otherwise. For multiple-valued options, it will be an empty list or a
+ /// list containing [defaultsTo] if set.
+ dynamic valueOrDefault(Object? value) {
+ if (value != null) return value;
+ if (isMultiple) return defaultsTo ?? <String>[];
+ return defaultsTo;
+ }
+
+ @Deprecated('Use valueOrDefault instead.')
+ dynamic getOrDefault(Object? value) => valueOrDefault(value);
+
+ static final _invalidChars = RegExp(r'''[ \t\r\n"'\\/]''');
+}
+
+/// What kinds of values an option accepts.
+class OptionType {
+ /// An option that can only be `true` or `false`.
+ ///
+ /// The presence of the option name itself in the argument list means `true`.
+ static const flag = OptionType._('OptionType.flag');
+
+ /// An option that takes a single value.
+ ///
+ /// Examples:
+ ///
+ /// --mode debug
+ /// -mdebug
+ /// --mode=debug
+ ///
+ /// If the option is passed more than once, the last one wins.
+ static const single = OptionType._('OptionType.single');
+
+ /// An option that allows multiple values.
+ ///
+ /// Example:
+ ///
+ /// --output text --output xml
+ ///
+ /// In the parsed `ArgResults`, a multiple-valued option will always return
+ /// a list, even if one or no values were passed.
+ static const multiple = OptionType._('OptionType.multiple');
+
+ final String name;
+
+ const OptionType._(this.name);
+}
diff --git a/pkgs/args/lib/src/parser.dart b/pkgs/args/lib/src/parser.dart
new file mode 100644
index 0000000..660e56d
--- /dev/null
+++ b/pkgs/args/lib/src/parser.dart
@@ -0,0 +1,381 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'arg_parser.dart';
+import 'arg_parser_exception.dart';
+import 'arg_results.dart';
+import 'option.dart';
+
+/// The actual argument parsing class.
+///
+/// Unlike [ArgParser] which is really more an "arg grammar", this is the class
+/// that does the parsing and holds the mutable state required during a parse.
+class Parser {
+ /// If parser is parsing a command's options, this will be the name of the
+ /// command. For top-level results, this returns `null`.
+ final String? _commandName;
+
+ /// The parser for the supercommand of this command parser, or `null` if this
+ /// is the top-level parser.
+ final Parser? _parent;
+
+ /// The grammar being parsed.
+ final ArgParser _grammar;
+
+ /// The arguments being parsed.
+ final Queue<String> _args;
+
+ /// The remaining non-option, non-command arguments.
+ final List<String> _rest;
+
+ /// The accumulated parsed options.
+ final Map<String, dynamic> _results = <String, dynamic>{};
+
+ Parser(this._commandName, this._grammar, this._args,
+ [this._parent, List<String>? rest])
+ : _rest = [...?rest];
+
+ /// The current argument being parsed.
+ String get _current => _args.first;
+
+ /// Parses the arguments. This can only be called once.
+ ArgResults parse() {
+ var arguments = _args.toList();
+ if (_grammar.allowsAnything) {
+ return newArgResults(
+ _grammar, const {}, _commandName, null, arguments, arguments);
+ }
+
+ ArgResults? commandResults;
+
+ // Parse the args.
+ while (_args.isNotEmpty) {
+ if (_current == '--') {
+ // Reached the argument terminator, so stop here.
+ _args.removeFirst();
+ break;
+ }
+
+ // Try to parse the current argument as a command. This happens before
+ // options so that commands can have option-like names.
+ var command = _grammar.commands[_current];
+ if (command != null) {
+ _validate(_rest.isEmpty, 'Cannot specify arguments before a command.',
+ _current);
+ var commandName = _args.removeFirst();
+ var commandParser = Parser(commandName, command, _args, this, _rest);
+
+ try {
+ commandResults = commandParser.parse();
+ } on ArgParserException catch (error) {
+ throw ArgParserException(
+ error.message,
+ [commandName, ...error.commands],
+ error.argumentName,
+ error.source,
+ error.offset);
+ }
+
+ // All remaining arguments were passed to command so clear them here.
+ _rest.clear();
+ break;
+ }
+
+ // Try to parse the current argument as an option. Note that the order
+ // here matters.
+ if (_parseSoloOption()) continue;
+ if (_parseAbbreviation(this)) continue;
+ if (_parseLongOption()) continue;
+
+ // This argument is neither option nor command, so stop parsing unless
+ // the [allowTrailingOptions] option is set.
+ if (!_grammar.allowTrailingOptions) break;
+ _rest.add(_args.removeFirst());
+ }
+
+ // Check if mandatory and invoke existing callbacks.
+ _grammar.options.forEach((name, option) {
+ var parsedOption = _results[name];
+
+ var callback = option.callback;
+ if (callback == null) return;
+
+ // Check if an option is mandatory and was passed; if not, throw an
+ // exception.
+ if (option.mandatory && parsedOption == null) {
+ throw ArgParserException('Option $name is mandatory.', null, name);
+ }
+
+ // ignore: avoid_dynamic_calls
+ callback(option.valueOrDefault(parsedOption));
+ });
+
+ // Add in the leftover arguments we didn't parse to the innermost command.
+ _rest.addAll(_args);
+ _args.clear();
+ return newArgResults(
+ _grammar, _results, _commandName, commandResults, _rest, arguments);
+ }
+
+ /// Pulls the value for [option] from the second argument in [_args].
+ ///
+ /// Validates that there is a valid value there.
+ void _readNextArgAsValue(Option option, String arg) {
+ // Take the option argument from the next command line arg.
+ _validate(_args.isNotEmpty, 'Missing argument for "$arg".', arg);
+
+ _setOption(_results, option, _current, arg);
+ _args.removeFirst();
+ }
+
+ /// Tries to parse the current argument as a "solo" option, which is a single
+ /// hyphen followed by a single letter.
+ ///
+ /// We treat this differently than collapsed abbreviations (like "-abc") to
+ /// handle the possible value that may follow it.
+ bool _parseSoloOption() {
+ // Hand coded regexp: r'^-([a-zA-Z0-9])$'
+ // Length must be two, hyphen followed by any letter/digit.
+ if (_current.length != 2) return false;
+ if (!_current.startsWith('-')) return false;
+ var opt = _current[1];
+ if (!_isLetterOrDigit(opt.codeUnitAt(0))) return false;
+ return _handleSoloOption(opt);
+ }
+
+ bool _handleSoloOption(String opt) {
+ var option = _grammar.findByAbbreviation(opt);
+ if (option == null) {
+ // Walk up to the parent command if possible.
+ _validate(_parent != null, 'Could not find an option or flag "-$opt".',
+ '-$opt');
+ return _parent!._handleSoloOption(opt);
+ }
+
+ _args.removeFirst();
+
+ if (option.isFlag) {
+ _setFlag(_results, option, true);
+ } else {
+ _readNextArgAsValue(option, '-$opt');
+ }
+
+ return true;
+ }
+
+ /// Tries to parse the current argument as a series of collapsed abbreviations
+ /// (like "-abc") or a single abbreviation with the value directly attached
+ /// to it (like "-mrelease").
+ bool _parseAbbreviation(Parser innermostCommand) {
+ // Hand coded regexp: r'^-([a-zA-Z0-9]+)(.*)$'
+ // Hyphen then at least one letter/digit then zero or more
+ // anything-but-newlines.
+ if (_current.length < 2) return false;
+ if (!_current.startsWith('-')) return false;
+
+ // Find where we go from letters/digits to rest.
+ var index = 1;
+ while (index < _current.length &&
+ _isLetterOrDigit(_current.codeUnitAt(index))) {
+ ++index;
+ }
+ // Must be at least one letter/digit.
+ if (index == 1) return false;
+
+ // If the first character is the abbreviation for a non-flag option, then
+ // the rest is the value.
+ var lettersAndDigits = _current.substring(1, index);
+ var rest = _current.substring(index);
+ if (rest.contains('\n') || rest.contains('\r')) return false;
+ return _handleAbbreviation(lettersAndDigits, rest, innermostCommand);
+ }
+
+ bool _handleAbbreviation(
+ String lettersAndDigits, String rest, Parser innermostCommand) {
+ var c = lettersAndDigits.substring(0, 1);
+ var first = _grammar.findByAbbreviation(c);
+ if (first == null) {
+ // Walk up to the parent command if possible.
+ _validate(_parent != null,
+ 'Could not find an option with short name "-$c".', '-$c');
+ return _parent!
+ ._handleAbbreviation(lettersAndDigits, rest, innermostCommand);
+ } else if (!first.isFlag) {
+ // The first character is a non-flag option, so the rest must be the
+ // value.
+ var value = '${lettersAndDigits.substring(1)}$rest';
+ _setOption(_results, first, value, '-$c');
+ } else {
+ // If we got some non-flag characters, then it must be a value, but
+ // if we got here, it's a flag, which is wrong.
+ _validate(
+ rest == '',
+ 'Option "-$c" is a flag and cannot handle value '
+ '"${lettersAndDigits.substring(1)}$rest".',
+ '-$c');
+
+ // Not an option, so all characters should be flags.
+ // We use "innermostCommand" here so that if a parent command parses the
+ // *first* letter, subcommands can still be found to parse the other
+ // letters.
+ for (var i = 0; i < lettersAndDigits.length; i++) {
+ var c = lettersAndDigits.substring(i, i + 1);
+ innermostCommand._parseShortFlag(c);
+ }
+ }
+
+ _args.removeFirst();
+ return true;
+ }
+
+ void _parseShortFlag(String c) {
+ var option = _grammar.findByAbbreviation(c);
+ if (option == null) {
+ // Walk up to the parent command if possible.
+ _validate(_parent != null,
+ 'Could not find an option with short name "-$c".', '-$c');
+ _parent!._parseShortFlag(c);
+ return;
+ }
+
+ // In a list of short options, only the first can be a non-flag. If
+ // we get here we've checked that already.
+ _validate(option.isFlag,
+ 'Option "-$c" must be a flag to be in a collapsed "-".', '-$c');
+
+ _setFlag(_results, option, true);
+ }
+
+ /// Tries to parse the current argument as a long-form named option, which
+ /// may include a value like "--mode=release" or "--mode release".
+ bool _parseLongOption() {
+ // Hand coded regexp: r'^--([a-zA-Z\-_0-9]+)(=(.*))?$'
+ // Two hyphens then at least one letter/digit/hyphen, optionally an equal
+ // sign followed by zero or more anything-but-newlines.
+
+ if (!_current.startsWith('--')) return false;
+
+ var index = _current.indexOf('=');
+ var name =
+ index == -1 ? _current.substring(2) : _current.substring(2, index);
+ for (var i = 0; i != name.length; ++i) {
+ if (!_isLetterDigitHyphenOrUnderscore(name.codeUnitAt(i))) return false;
+ }
+ var value = index == -1 ? null : _current.substring(index + 1);
+ if (value != null && (value.contains('\n') || value.contains('\r'))) {
+ return false;
+ }
+ return _handleLongOption(name, value);
+ }
+
+ bool _handleLongOption(String name, String? value) {
+ var option = _grammar.findByNameOrAlias(name);
+ if (option != null) {
+ _args.removeFirst();
+ if (option.isFlag) {
+ _validate(value == null,
+ 'Flag option "--$name" should not be given a value.', '--$name');
+
+ _setFlag(_results, option, true);
+ } else if (value != null) {
+ // We have a value like --foo=bar.
+ _setOption(_results, option, value, '--$name');
+ } else {
+ // Option like --foo, so look for the value as the next arg.
+ _readNextArgAsValue(option, '--$name');
+ }
+ } else if (name.startsWith('no-')) {
+ // See if it's a negated flag.
+ var positiveName = name.substring('no-'.length);
+ option = _grammar.findByNameOrAlias(positiveName);
+ if (option == null) {
+ // Walk up to the parent command if possible.
+ _validate(_parent != null, 'Could not find an option named "--$name".',
+ '--$name');
+ return _parent!._handleLongOption(name, value);
+ }
+
+ _args.removeFirst();
+ _validate(
+ option.isFlag, 'Cannot negate non-flag option "--$name".', '--$name');
+ _validate(
+ option.negatable!, 'Cannot negate option "--$name".', '--$name');
+
+ _setFlag(_results, option, false);
+ } else {
+ // Walk up to the parent command if possible.
+ _validate(_parent != null, 'Could not find an option named "--$name".',
+ '--$name');
+ return _parent!._handleLongOption(name, value);
+ }
+
+ return true;
+ }
+
+ /// Called during parsing to validate the arguments.
+ ///
+ /// Throws an [ArgParserException] if [condition] is `false`.
+ void _validate(bool condition, String message,
+ [String? args, List<String>? source, int? offset]) {
+ if (!condition) {
+ throw ArgParserException(message, null, args, source, offset);
+ }
+ }
+
+ /// Validates and stores [value] as the value for [option], which must not be
+ /// a flag.
+ void _setOption(Map results, Option option, String value, String arg) {
+ assert(!option.isFlag);
+
+ if (!option.isMultiple) {
+ _validateAllowed(option, value, arg);
+ results[option.name] = value;
+ return;
+ }
+
+ var list = results.putIfAbsent(option.name, () => <String>[]) as List;
+
+ if (option.splitCommas) {
+ for (var element in value.split(',')) {
+ _validateAllowed(option, element, arg);
+ list.add(element);
+ }
+ } else {
+ _validateAllowed(option, value, arg);
+ list.add(value);
+ }
+ }
+
+ /// Validates and stores [value] as the value for [option], which must be a
+ /// flag.
+ void _setFlag(Map results, Option option, bool value) {
+ assert(option.isFlag);
+ results[option.name] = value;
+ }
+
+ /// Validates that [value] is allowed as a value of [option].
+ void _validateAllowed(Option option, String value, String arg) {
+ if (option.allowed == null) return;
+
+ _validate(option.allowed!.contains(value),
+ '"$value" is not an allowed value for option "$arg".', arg);
+ }
+}
+
+bool _isLetterOrDigit(int codeUnit) =>
+ // Uppercase letters.
+ (codeUnit >= 65 && codeUnit <= 90) ||
+ // Lowercase letters.
+ (codeUnit >= 97 && codeUnit <= 122) ||
+ // Digits.
+ (codeUnit >= 48 && codeUnit <= 57);
+
+bool _isLetterDigitHyphenOrUnderscore(int codeUnit) =>
+ _isLetterOrDigit(codeUnit) ||
+ // Hyphen.
+ codeUnit == 45 ||
+ // Underscore.
+ codeUnit == 95;
diff --git a/pkgs/args/lib/src/usage.dart b/pkgs/args/lib/src/usage.dart
new file mode 100644
index 0000000..bd39f11
--- /dev/null
+++ b/pkgs/args/lib/src/usage.dart
@@ -0,0 +1,255 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:math' as math;
+
+import '../args.dart';
+import 'utils.dart';
+
+/// Generates a string of usage (i.e. help) text for a list of options.
+///
+/// Internally, it works like a tabular printer. The output is divided into
+/// three horizontal columns, like so:
+///
+/// -h, --help Prints the usage information
+/// | | | |
+///
+/// It builds the usage text up one column at a time and handles padding with
+/// spaces and wrapping to the next line to keep the cells correctly lined up.
+///
+/// [lineLength] specifies the horizontal character position at which the help
+/// text is wrapped. Help that extends past this column will be wrapped at the
+/// nearest whitespace (or truncated if there is no available whitespace). If
+/// `null` there will not be any wrapping.
+String generateUsage(List optionsAndSeparators, {int? lineLength}) =>
+ _Usage(optionsAndSeparators, lineLength).generate();
+
+class _Usage {
+ /// Abbreviation, long name, help.
+ static const _columnCount = 3;
+
+ /// A list of the [Option]s intermingled with [String] separators.
+ final List _optionsAndSeparators;
+
+ /// The working buffer for the generated usage text.
+ final _buffer = StringBuffer();
+
+ /// The column that the "cursor" is currently on.
+ ///
+ /// If the next call to [write()] is not for this column, it will correctly
+ /// handle advancing to the next column (and possibly the next row).
+ int _currentColumn = 0;
+
+ /// The width in characters of each column.
+ late final _columnWidths = _calculateColumnWidths();
+
+ /// How many newlines need to be rendered before the next bit of text can be
+ /// written.
+ ///
+ /// We do this lazily so that the last bit of usage doesn't have dangling
+ /// newlines. We only write newlines right *before* we write some real
+ /// content.
+ int _newlinesNeeded = 0;
+
+ /// The horizontal character position at which help text is wrapped.
+ ///
+ /// Help that extends past this column will be wrapped at the nearest
+ /// whitespace (or truncated if there is no available whitespace).
+ final int? lineLength;
+
+ _Usage(this._optionsAndSeparators, this.lineLength);
+
+ /// Generates a string displaying usage information for the defined options.
+ /// This is basically the help text shown on the command line.
+ String generate() {
+ for (var optionOrSeparator in _optionsAndSeparators) {
+ if (optionOrSeparator is String) {
+ _writeSeparator(optionOrSeparator);
+ continue;
+ }
+ var option = optionOrSeparator as Option;
+ if (option.hide) continue;
+ _writeOption(option);
+ }
+
+ return _buffer.toString();
+ }
+
+ void _writeSeparator(String separator) {
+ // Ensure that there's always a blank line before a separator.
+ if (_buffer.isNotEmpty) _buffer.write('\n\n');
+ _buffer.write(separator);
+ _newlinesNeeded = 1;
+ }
+
+ void _writeOption(Option option) {
+ _write(0, _abbreviation(option));
+ _write(1, '${_longOption(option)}${_mandatoryOption(option)}');
+
+ if (option.help != null) _write(2, option.help!);
+
+ if (option.allowedHelp != null) {
+ var allowedNames = option.allowedHelp!.keys.toList();
+ allowedNames.sort();
+ _newline();
+ for (var name in allowedNames) {
+ _write(1, _allowedTitle(option, name));
+ _write(2, option.allowedHelp![name]!);
+ }
+ _newline();
+ } else if (option.allowed != null) {
+ _write(2, _buildAllowedList(option));
+ } else if (option.isFlag) {
+ if (option.defaultsTo == true) {
+ _write(2, '(defaults to on)');
+ }
+ } else if (option.isMultiple) {
+ if (option.defaultsTo != null &&
+ (option.defaultsTo as Iterable).isNotEmpty) {
+ var defaults =
+ (option.defaultsTo as List).map((value) => '"$value"').join(', ');
+ _write(2, '(defaults to $defaults)');
+ }
+ } else if (option.defaultsTo != null) {
+ _write(2, '(defaults to "${option.defaultsTo}")');
+ }
+ }
+
+ String _abbreviation(Option option) =>
+ option.abbr == null ? '' : '-${option.abbr}, ';
+
+ String _longOption(Option option) {
+ String result;
+ if (option.negatable! && !option.hideNegatedUsage!) {
+ result = '--[no-]${option.name}';
+ } else {
+ result = '--${option.name}';
+ }
+
+ if (option.valueHelp != null) result += '=<${option.valueHelp}>';
+
+ return result;
+ }
+
+ String _mandatoryOption(Option option) {
+ return option.mandatory ? ' (mandatory)' : '';
+ }
+
+ String _allowedTitle(Option option, String allowed) {
+ var isDefault = option.defaultsTo is List
+ ? (option.defaultsTo as List).contains(allowed)
+ : option.defaultsTo == allowed;
+ return ' [$allowed]${isDefault ? ' (default)' : ''}';
+ }
+
+ List<int> _calculateColumnWidths() {
+ var abbr = 0;
+ var title = 0;
+ for (var option in _optionsAndSeparators) {
+ if (option is! Option) continue;
+ if (option.hide) continue;
+
+ // Make room in the first column if there are abbreviations.
+ abbr = math.max(abbr, _abbreviation(option).length);
+
+ // Make room for the option.
+ title = math.max(
+ title, _longOption(option).length + _mandatoryOption(option).length);
+
+ // Make room for the allowed help.
+ if (option.allowedHelp != null) {
+ for (var allowed in option.allowedHelp!.keys) {
+ title = math.max(title, _allowedTitle(option, allowed).length);
+ }
+ }
+ }
+
+ // Leave a gutter between the columns.
+ title += 4;
+ return [abbr, title];
+ }
+
+ void _newline() {
+ _newlinesNeeded++;
+ _currentColumn = 0;
+ }
+
+ void _write(int column, String text) {
+ var lines = text.split('\n');
+ // If we are writing the last column, word wrap it to fit.
+ if (column == _columnWidths.length && lineLength != null) {
+ var start =
+ _columnWidths.take(column).reduce((start, width) => start + width);
+ lines = [
+ for (var line in lines)
+ ...wrapTextAsLines(line, start: start, length: lineLength),
+ ];
+ }
+
+ // Strip leading and trailing empty lines.
+ while (lines.isNotEmpty && lines.first.trim() == '') {
+ lines.removeAt(0);
+ }
+ while (lines.isNotEmpty && lines.last.trim() == '') {
+ lines.removeLast();
+ }
+
+ for (var line in lines) {
+ _writeLine(column, line);
+ }
+ }
+
+ void _writeLine(int column, String text) {
+ // Write any pending newlines.
+ while (_newlinesNeeded > 0) {
+ _buffer.write('\n');
+ _newlinesNeeded--;
+ }
+
+ // Advance until we are at the right column (which may mean wrapping around
+ // to the next line.
+ while (_currentColumn != column) {
+ if (_currentColumn < _columnCount - 1) {
+ _buffer.write(' ' * _columnWidths[_currentColumn]);
+ } else {
+ _buffer.write('\n');
+ }
+ _currentColumn = (_currentColumn + 1) % _columnCount;
+ }
+
+ if (column < _columnWidths.length) {
+ // Fixed-size column, so pad it.
+ _buffer.write(text.padRight(_columnWidths[column]));
+ } else {
+ // The last column, so just write it.
+ _buffer.write(text);
+ }
+
+ // Advance to the next column.
+ _currentColumn = (_currentColumn + 1) % _columnCount;
+
+ // If we reached the last column, we need to wrap to the next line.
+ if (column == _columnCount - 1) _newlinesNeeded++;
+ }
+
+ String _buildAllowedList(Option option) {
+ var isDefault = option.defaultsTo is List
+ ? (option.defaultsTo as List).contains
+ : (String value) => value == option.defaultsTo;
+
+ var allowedBuffer = StringBuffer();
+ allowedBuffer.write('[');
+ var first = true;
+ for (var allowed in option.allowed!) {
+ if (!first) allowedBuffer.write(', ');
+ allowedBuffer.write(allowed);
+ if (isDefault(allowed)) {
+ allowedBuffer.write(' (default)');
+ }
+ first = false;
+ }
+ allowedBuffer.write(']');
+ return allowedBuffer.toString();
+ }
+}
diff --git a/pkgs/args/lib/src/usage_exception.dart b/pkgs/args/lib/src/usage_exception.dart
new file mode 100644
index 0000000..fc8910e
--- /dev/null
+++ b/pkgs/args/lib/src/usage_exception.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+class UsageException implements Exception {
+ final String message;
+ final String usage;
+
+ UsageException(this.message, this.usage);
+
+ @override
+ String toString() => '$message\n\n$usage';
+}
diff --git a/pkgs/args/lib/src/utils.dart b/pkgs/args/lib/src/utils.dart
new file mode 100644
index 0000000..ae5e093
--- /dev/null
+++ b/pkgs/args/lib/src/utils.dart
@@ -0,0 +1,143 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+import 'dart:math' as math;
+
+/// Pads [source] to [length] by adding spaces at the end.
+String padRight(String source, int length) =>
+ source + ' ' * (length - source.length);
+
+/// Wraps a block of text into lines no longer than [length].
+///
+/// Tries to split at whitespace, but if that's not good enough to keep it
+/// under the limit, then it splits in the middle of a word.
+///
+/// Preserves indentation (leading whitespace) for each line (delimited by '\n')
+/// in the input, and indents wrapped lines the same amount.
+///
+/// If [hangingIndent] is supplied, then that many spaces are added to each
+/// line, except for the first line. This is useful for flowing text with a
+/// heading prefix (e.g. "Usage: "):
+///
+/// ```dart
+/// var prefix = "Usage: ";
+/// print(
+/// prefix + wrapText(invocation, hangingIndent: prefix.length, length: 40),
+/// );
+/// ```
+///
+/// yields:
+/// ```
+/// Usage: app main_command <subcommand>
+/// [arguments]
+/// ```
+///
+/// If [length] is not specified, then no wrapping occurs, and the original
+/// [text] is returned unchanged.
+String wrapText(String text, {int? length, int? hangingIndent}) {
+ if (length == null) return text;
+ hangingIndent ??= 0;
+ var splitText = text.split('\n');
+ var result = <String>[];
+ for (var line in splitText) {
+ var trimmedText = line.trimLeft();
+ final leadingWhitespace =
+ line.substring(0, line.length - trimmedText.length);
+ List<String> notIndented;
+ if (hangingIndent != 0) {
+ // When we have a hanging indent, we want to wrap the first line at one
+ // width, and the rest at another (offset by hangingIndent), so we wrap
+ // them twice and recombine.
+ var firstLineWrap = wrapTextAsLines(trimmedText,
+ length: length - leadingWhitespace.length);
+ notIndented = [firstLineWrap.removeAt(0)];
+ trimmedText = trimmedText.substring(notIndented[0].length).trimLeft();
+ if (firstLineWrap.isNotEmpty) {
+ notIndented.addAll(wrapTextAsLines(trimmedText,
+ length: length - leadingWhitespace.length - hangingIndent));
+ }
+ } else {
+ notIndented = wrapTextAsLines(trimmedText,
+ length: length - leadingWhitespace.length);
+ }
+ String? hangingIndentString;
+ result.addAll(notIndented.map<String>((String line) {
+ // Don't return any lines with just whitespace on them.
+ if (line.isEmpty) return '';
+ var result = '${hangingIndentString ?? ''}$leadingWhitespace$line';
+ hangingIndentString ??= ' ' * hangingIndent!;
+ return result;
+ }));
+ }
+ return result.join('\n');
+}
+
+/// Wraps a block of text into lines no longer than [length],
+/// starting at the [start] column, and returns the result as a list of strings.
+///
+/// Tries to split at whitespace, but if that's not good enough to keep it
+/// under the limit, then splits in the middle of a word. Preserves embedded
+/// newlines, but not indentation (it trims whitespace from each line).
+///
+/// If [length] is not specified, then no wrapping occurs, and the original
+/// [text] is returned after splitting it on newlines. Whitespace is not trimmed
+/// in this case.
+List<String> wrapTextAsLines(String text, {int start = 0, int? length}) {
+ assert(start >= 0);
+
+ /// Returns true if the code unit at [index] in [text] is a whitespace
+ /// character.
+ ///
+ /// Based on: https://en.wikipedia.org/wiki/Whitespace_character#Unicode
+ bool isWhitespace(String text, int index) {
+ var rune = text.codeUnitAt(index);
+ return rune >= 0x0009 && rune <= 0x000D ||
+ rune == 0x0020 ||
+ rune == 0x0085 ||
+ rune == 0x1680 ||
+ rune == 0x180E ||
+ rune >= 0x2000 && rune <= 0x200A ||
+ rune == 0x2028 ||
+ rune == 0x2029 ||
+ rune == 0x202F ||
+ rune == 0x205F ||
+ rune == 0x3000 ||
+ rune == 0xFEFF;
+ }
+
+ if (length == null) return text.split('\n');
+
+ var result = <String>[];
+ var effectiveLength = math.max(length - start, 10);
+ for (var line in text.split('\n')) {
+ line = line.trim();
+ if (line.length <= effectiveLength) {
+ result.add(line);
+ continue;
+ }
+
+ var currentLineStart = 0;
+ int? lastWhitespace;
+ for (var i = 0; i < line.length; ++i) {
+ if (isWhitespace(line, i)) lastWhitespace = i;
+
+ if (i - currentLineStart >= effectiveLength) {
+ // Back up to the last whitespace, unless there wasn't any, in which
+ // case we just split where we are.
+ if (lastWhitespace != null) i = lastWhitespace;
+
+ result.add(line.substring(currentLineStart, i).trim());
+
+ // Skip any intervening whitespace.
+ while (isWhitespace(line, i) && i < line.length) {
+ i++;
+ }
+
+ currentLineStart = i;
+ lastWhitespace = null;
+ }
+ }
+ result.add(line.substring(currentLineStart).trim());
+ }
+ return result;
+}
diff --git a/pkgs/args/pubspec.yaml b/pkgs/args/pubspec.yaml
new file mode 100644
index 0000000..96a0e69
--- /dev/null
+++ b/pkgs/args/pubspec.yaml
@@ -0,0 +1,17 @@
+name: args
+version: 2.6.1-wip
+description: >-
+ Library for defining parsers for parsing raw command-line arguments into a set
+ of options and values using GNU and POSIX style options.
+repository: https://github.com/dart-lang/core/tree/main/pkgs/args
+issue_tracker: https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aargs
+
+topics:
+ - cli
+
+environment:
+ sdk: ^3.3.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
diff --git a/pkgs/args/test/allow_anything_test.dart b/pkgs/args/test/allow_anything_test.dart
new file mode 100644
index 0000000..52e22b7
--- /dev/null
+++ b/pkgs/args/test/allow_anything_test.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:test/test.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ group('new ArgParser.allowAnything()', () {
+ late ArgParser parser;
+ setUp(() {
+ parser = ArgParser.allowAnything();
+ });
+
+ test('exposes empty values', () {
+ expect(parser.options, isEmpty);
+ expect(parser.commands, isEmpty);
+ expect(parser.allowTrailingOptions, isFalse);
+ expect(parser.allowsAnything, isTrue);
+ expect(parser.usage, isEmpty);
+ expect(parser.findByAbbreviation('a'), isNull);
+ });
+
+ test('mutation methods throw errors', () {
+ expect(() => parser.addCommand('command'), throwsUnsupportedError);
+ expect(() => parser.addFlag('flag'), throwsUnsupportedError);
+ expect(() => parser.addOption('option'), throwsUnsupportedError);
+ expect(() => parser.addSeparator('==='), throwsUnsupportedError);
+ });
+
+ test('getDefault() throws an error', () {
+ expect(() => parser.defaultFor('option'), throwsArgumentError);
+ });
+
+ test('parses all values as rest arguments', () {
+ var results = parser.parse(['--foo', '-abc', '--', 'bar']);
+ expect(results.options, isEmpty);
+ expect(results.rest, equals(['--foo', '-abc', '--', 'bar']));
+ expect(results.arguments, equals(['--foo', '-abc', '--', 'bar']));
+ expect(results.command, isNull);
+ expect(results.name, isNull);
+ });
+
+ test('works as a subcommand', () {
+ var commandParser = ArgParser()..addCommand('command', parser);
+ var results =
+ commandParser.parse(['command', '--foo', '-abc', '--', 'bar']);
+ expect(results.command!.options, isEmpty);
+ expect(results.command!.rest, equals(['--foo', '-abc', '--', 'bar']));
+ expect(
+ results.command!.arguments, equals(['--foo', '-abc', '--', 'bar']));
+ expect(results.command!.name, equals('command'));
+ });
+
+ test('works as a subcommand in a CommandRunner', () async {
+ var commandRunner =
+ CommandRunner<void>('command', 'Description of command');
+ var command = AllowAnythingCommand();
+ commandRunner.addCommand(command);
+
+ await commandRunner.run([command.name, '--foo', '--bar', '-b', 'qux']);
+ });
+ });
+}
diff --git a/pkgs/args/test/args_test.dart b/pkgs/args/test/args_test.dart
new file mode 100644
index 0000000..04bbc47
--- /dev/null
+++ b/pkgs/args/test/args_test.dart
@@ -0,0 +1,349 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:args/args.dart';
+import 'package:test/test.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ group('ArgParser.addFlag()', () {
+ test('throws ArgumentError if the flag already exists', () {
+ var parser = ArgParser();
+ parser.addFlag('foo');
+ throwsIllegalArg(() => parser.addFlag('foo'));
+ });
+
+ test('throws ArgumentError if the option already exists', () {
+ var parser = ArgParser();
+ parser.addOption('foo');
+ throwsIllegalArg(() => parser.addFlag('foo'));
+ });
+
+ test('throws ArgumentError if the abbreviation exists', () {
+ var parser = ArgParser();
+ parser.addFlag('foo', abbr: 'f');
+ throwsIllegalArg(() => parser.addFlag('flummox', abbr: 'f'));
+ });
+
+ test(
+ 'throws ArgumentError if the abbreviation is longer '
+ 'than one character', () {
+ var parser = ArgParser();
+ throwsIllegalArg(() => parser.addFlag('flummox', abbr: 'flu'));
+ });
+
+ test('throws ArgumentError if a flag name is invalid', () {
+ var parser = ArgParser();
+
+ for (var name in _invalidOptions) {
+ var reason = '${Error.safeToString(name)} is not valid';
+ throwsIllegalArg(() => parser.addFlag(name), reason: reason);
+ }
+ });
+
+ test('accepts valid flag names', () {
+ var parser = ArgParser();
+
+ for (var name in _validOptions) {
+ var reason = '${Error.safeToString(name)} is valid';
+ expect(() => parser.addFlag(name), returnsNormally, reason: reason);
+ }
+ });
+ });
+
+ group('ArgParser.addOption()', () {
+ test('throws ArgumentError if the flag already exists', () {
+ var parser = ArgParser();
+ parser.addFlag('foo');
+ throwsIllegalArg(() => parser.addOption('foo'));
+ });
+
+ test('throws ArgumentError if the option already exists', () {
+ var parser = ArgParser();
+ parser.addOption('foo');
+ throwsIllegalArg(() => parser.addOption('foo'));
+ });
+
+ test('throws ArgumentError if the abbreviation exists', () {
+ var parser = ArgParser();
+ parser.addFlag('foo', abbr: 'f');
+ throwsIllegalArg(() => parser.addOption('flummox', abbr: 'f'));
+ });
+
+ test(
+ 'throws ArgumentError if the abbreviation is longer '
+ 'than one character', () {
+ var parser = ArgParser();
+ throwsIllegalArg(() => parser.addOption('flummox', abbr: 'flu'));
+ });
+
+ test('throws ArgumentError if the abbreviation is empty', () {
+ var parser = ArgParser();
+ throwsIllegalArg(() => parser.addOption('flummox', abbr: ''));
+ });
+
+ test('throws ArgumentError if the abbreviation is an invalid value', () {
+ var parser = ArgParser();
+ for (var name in _invalidOptions) {
+ throwsIllegalArg(() => parser.addOption('flummox', abbr: name));
+ }
+ });
+
+ test('throws ArgumentError if the abbreviation is a dash', () {
+ var parser = ArgParser();
+ throwsIllegalArg(() => parser.addOption('flummox', abbr: '-'));
+ });
+
+ test('allows explict null value for "abbr"', () {
+ var parser = ArgParser();
+ expect(() => parser.addOption('flummox', abbr: null), returnsNormally);
+ });
+
+ test('throws ArgumentError if an option name is invalid', () {
+ var parser = ArgParser();
+
+ for (var name in _invalidOptions) {
+ var reason = '${Error.safeToString(name)} is not valid';
+ throwsIllegalArg(() => parser.addOption(name), reason: reason);
+ }
+ });
+
+ test('accepts valid option names', () {
+ var parser = ArgParser();
+
+ for (var name in _validOptions) {
+ var reason = '${Error.safeToString(name)} is valid';
+ expect(() => parser.addOption(name), returnsNormally, reason: reason);
+ }
+ });
+ });
+
+ group('ArgParser.getDefault()', () {
+ test('returns the default value for an option', () {
+ var parser = ArgParser();
+ parser.addOption('mode', defaultsTo: 'debug');
+ expect(parser.defaultFor('mode'), 'debug');
+ });
+
+ test('throws if the option is unknown', () {
+ var parser = ArgParser();
+ parser.addOption('mode', defaultsTo: 'debug');
+ throwsIllegalArg(() => parser.defaultFor('undefined'));
+ });
+ });
+
+ group('ArgParser.commands', () {
+ test('returns an empty map if there are no commands', () {
+ var parser = ArgParser();
+ expect(parser.commands, isEmpty);
+ });
+
+ test('returns the commands that were added', () {
+ var parser = ArgParser();
+ parser.addCommand('hide');
+ parser.addCommand('seek');
+ expect(parser.commands, hasLength(2));
+ expect(parser.commands['hide'], isNotNull);
+ expect(parser.commands['seek'], isNotNull);
+ });
+
+ test('iterates over the commands in the order they were added', () {
+ var parser = ArgParser();
+ parser.addCommand('a');
+ parser.addCommand('d');
+ parser.addCommand('b');
+ parser.addCommand('c');
+ expect(parser.commands.keys, equals(['a', 'd', 'b', 'c']));
+ });
+ });
+
+ group('ArgParser.options', () {
+ test('returns an empty map if there are no options', () {
+ var parser = ArgParser();
+ expect(parser.options, isEmpty);
+ });
+
+ test('returns the options that were added', () {
+ var parser = ArgParser();
+ parser.addFlag('hide');
+ parser.addOption('seek');
+ expect(parser.options, hasLength(2));
+ expect(parser.options['hide'], isNotNull);
+ expect(parser.options['seek'], isNotNull);
+ });
+
+ test('iterates over the options in the order they were added', () {
+ var parser = ArgParser();
+ parser.addFlag('a');
+ parser.addOption('d');
+ parser.addFlag('b');
+ parser.addOption('c');
+ expect(parser.options.keys, equals(['a', 'd', 'b', 'c']));
+ });
+ });
+
+ group('ArgParser.findByNameOrAlias', () {
+ test('returns null if there is no match', () {
+ var parser = ArgParser();
+ expect(parser.findByNameOrAlias('a'), isNull);
+ });
+
+ test('can find options by alias', () {
+ var parser = ArgParser()..addOption('a', aliases: ['b']);
+ expect(parser.findByNameOrAlias('b'),
+ isA<Option>().having((o) => o.name, 'name', 'a'));
+ });
+
+ test('can find flags by alias', () {
+ var parser = ArgParser()..addFlag('a', aliases: ['b']);
+ expect(parser.findByNameOrAlias('b'),
+ isA<Option>().having((o) => o.name, 'name', 'a'));
+ });
+
+ test('does not allow duplicate aliases', () {
+ var parser = ArgParser()..addOption('a', aliases: ['b']);
+ throwsIllegalArg(() => parser.addOption('c', aliases: ['b']));
+ });
+
+ test('does not allow aliases that conflict with existing names', () {
+ var parser = ArgParser()..addOption('a', aliases: ['b']);
+ throwsIllegalArg(() => parser.addOption('c', aliases: ['a']));
+ });
+
+ test('does not allow names that conflict with existing aliases', () {
+ var parser = ArgParser()..addOption('a', aliases: ['b']);
+ throwsIllegalArg(() => parser.addOption('b'));
+ });
+ });
+
+ group('ArgResults', () {
+ group('options', () {
+ test('returns the provided options', () {
+ var parser = ArgParser();
+ parser.addFlag('woof');
+ parser.addOption('meow');
+
+ parser.addOption('missing-option');
+ parser.addFlag('missing-flag', defaultsTo: null);
+
+ var args = parser.parse(['--woof', '--meow', 'kitty']);
+ expect(args.options, hasLength(2));
+ expect(args.options, contains('woof'));
+ expect(args.options, contains('meow'));
+ });
+
+ test('includes defaulted options', () {
+ var parser = ArgParser();
+ parser.addFlag('woof', defaultsTo: false);
+ parser.addOption('meow', defaultsTo: 'kitty');
+
+ // Flags normally have a default value.
+ parser.addFlag('moo');
+
+ parser.addOption('missing-option');
+ parser.addFlag('missing-flag', defaultsTo: null);
+
+ var args = parser.parse([]);
+ expect(args.options, hasLength(3));
+ expect(args.options, contains('woof'));
+ expect(args.options, contains('meow'));
+ expect(args.options, contains('moo'));
+ });
+ });
+
+ test('[] throws if the name is not an option', () {
+ var results = ArgParser().parse([]);
+ throwsIllegalArg(() => results['unknown']);
+ });
+
+ test('rest cannot be modified', () {
+ var results = ArgParser().parse([]);
+ expect(() => results.rest.add('oops'), throwsUnsupportedError);
+ });
+
+ test('.arguments returns the original argument list', () {
+ var parser = ArgParser();
+ parser.addFlag('foo');
+
+ var results = parser.parse(['--foo']);
+ expect(results.arguments, equals(['--foo']));
+ });
+
+ group('.wasParsed()', () {
+ test('throws if the name is not an option', () {
+ var results = ArgParser().parse([]);
+ throwsIllegalArg(() => results.wasParsed('unknown'));
+ });
+
+ test('returns true for parsed options', () {
+ var parser = ArgParser();
+ parser.addFlag('fast');
+ parser.addFlag('verbose');
+ parser.addOption('mode');
+ parser.addOption('output');
+
+ var results = parser.parse(['--fast', '--mode=debug']);
+
+ expect(results.wasParsed('fast'), isTrue);
+ expect(results.wasParsed('verbose'), isFalse);
+ expect(results.wasParsed('mode'), isTrue);
+ expect(results.wasParsed('output'), isFalse);
+ });
+ });
+ });
+
+ group('Option', () {
+ test('.valueOrDefault() returns a type-specific default value', () {
+ var parser = ArgParser();
+ parser.addFlag('flag-no', defaultsTo: null);
+ parser.addFlag('flag-def', defaultsTo: true);
+ parser.addOption('single-no');
+ parser.addOption('single-def', defaultsTo: 'def');
+ parser.addMultiOption('multi-no');
+ parser.addMultiOption('multi-def', defaultsTo: ['def']);
+
+ expect(parser.options['flag-no']!.valueOrDefault(null), equals(null));
+ expect(parser.options['flag-no']!.valueOrDefault(false), equals(false));
+ expect(parser.options['flag-def']!.valueOrDefault(null), equals(true));
+ expect(parser.options['flag-def']!.valueOrDefault(false), equals(false));
+ expect(parser.options['single-no']!.valueOrDefault(null), equals(null));
+ expect(parser.options['single-no']!.valueOrDefault('v'), equals('v'));
+ expect(parser.options['single-def']!.valueOrDefault(null), equals('def'));
+ expect(parser.options['single-def']!.valueOrDefault('v'), equals('v'));
+ expect(parser.options['multi-no']!.valueOrDefault(null), equals([]));
+ expect(parser.options['multi-no']!.valueOrDefault(['v']), equals(['v']));
+ expect(
+ parser.options['multi-def']!.valueOrDefault(null), equals(['def']));
+ expect(parser.options['multi-def']!.valueOrDefault(['v']), equals(['v']));
+ });
+ });
+}
+
+const _invalidOptions = [
+ ' ',
+ '',
+ '-',
+ '--',
+ '--foo',
+ ' with space',
+ 'with\ttab',
+ 'with\rcarriage\rreturn',
+ 'with\nline\nfeed',
+ "'singlequotes'",
+ '"doublequotes"',
+ 'back\\slash',
+ 'forward/slash'
+];
+
+const _validOptions = [
+ 'a', // One character.
+ 'contains-dash',
+ 'contains_underscore',
+ 'ends-with-dash-',
+ 'contains--doubledash--',
+ '1starts-with-number',
+ 'contains-a-1number',
+ 'ends-with-a-number8'
+];
diff --git a/pkgs/args/test/command_parse_test.dart b/pkgs/args/test/command_parse_test.dart
new file mode 100644
index 0000000..b6437c1
--- /dev/null
+++ b/pkgs/args/test/command_parse_test.dart
@@ -0,0 +1,204 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:args/args.dart';
+import 'package:test/test.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ group('ArgParser.addCommand()', () {
+ test('creates a new ArgParser if none is given', () {
+ var parser = ArgParser();
+ var command = parser.addCommand('install');
+ expect(parser.commands['install'], equals(command));
+ expect(command, const TypeMatcher<ArgParser>());
+ });
+
+ test('uses the command parser if given one', () {
+ var parser = ArgParser();
+ var command = ArgParser();
+ var result = parser.addCommand('install', command);
+ expect(parser.commands['install'], equals(command));
+ expect(result, equals(command));
+ });
+
+ test('throws on a duplicate command name', () {
+ var parser = ArgParser();
+ parser.addCommand('install');
+ throwsIllegalArg(() => parser.addCommand('install'));
+ });
+ });
+
+ group('ArgParser.parse()', () {
+ test('parses a command', () {
+ var parser = ArgParser()..addCommand('install');
+
+ var args = parser.parse(['install']);
+
+ expect(args.command!.name, equals('install'));
+ expect(args.rest, isEmpty);
+ });
+
+ test('parses a command option', () {
+ var parser = ArgParser();
+ var command = parser.addCommand('install');
+ command.addOption('path');
+
+ var args = parser.parse(['install', '--path', 'some/path']);
+ expect(args.command!['path'], equals('some/path'));
+ });
+
+ test('parses a parent solo option before the command', () {
+ var parser = ArgParser()
+ ..addOption('mode', abbr: 'm')
+ ..addCommand('install');
+
+ var args = parser.parse(['-m', 'debug', 'install']);
+ expect(args['mode'], equals('debug'));
+ expect(args.command!.name, equals('install'));
+ });
+
+ test('parses a parent solo option after the command', () {
+ var parser = ArgParser()
+ ..addOption('mode', abbr: 'm')
+ ..addCommand('install');
+
+ var args = parser.parse(['install', '-m', 'debug']);
+ expect(args['mode'], equals('debug'));
+ expect(args.command!.name, equals('install'));
+ });
+
+ test('parses a parent option before the command', () {
+ var parser = ArgParser()
+ ..addFlag('verbose')
+ ..addCommand('install');
+
+ var args = parser.parse(['--verbose', 'install']);
+ expect(args['verbose'], isTrue);
+ expect(args.command!.name, equals('install'));
+ });
+
+ test('parses a parent option after the command', () {
+ var parser = ArgParser()
+ ..addFlag('verbose')
+ ..addCommand('install');
+
+ var args = parser.parse(['install', '--verbose']);
+ expect(args['verbose'], isTrue);
+ expect(args.command!.name, equals('install'));
+ });
+
+ test('parses a parent negated option before the command', () {
+ var parser = ArgParser()
+ ..addFlag('verbose', defaultsTo: true)
+ ..addCommand('install');
+
+ var args = parser.parse(['--no-verbose', 'install']);
+ expect(args['verbose'], isFalse);
+ expect(args.command!.name, equals('install'));
+ });
+
+ test('parses a parent negated option after the command', () {
+ var parser = ArgParser()
+ ..addFlag('verbose', defaultsTo: true)
+ ..addCommand('install');
+
+ var args = parser.parse(['install', '--no-verbose']);
+ expect(args['verbose'], isFalse);
+ expect(args.command!.name, equals('install'));
+ });
+
+ test('parses a parent abbreviation before the command', () {
+ var parser = ArgParser()
+ ..addFlag('debug', abbr: 'd')
+ ..addFlag('verbose', abbr: 'v')
+ ..addCommand('install');
+
+ var args = parser.parse(['-dv', 'install']);
+ expect(args['debug'], isTrue);
+ expect(args['verbose'], isTrue);
+ expect(args.command!.name, equals('install'));
+ });
+
+ test('parses a parent abbreviation after the command', () {
+ var parser = ArgParser()
+ ..addFlag('debug', abbr: 'd')
+ ..addFlag('verbose', abbr: 'v')
+ ..addCommand('install');
+
+ var args = parser.parse(['install', '-dv']);
+ expect(args['debug'], isTrue);
+ expect(args['verbose'], isTrue);
+ expect(args.command!.name, equals('install'));
+ });
+
+ test('does not parse a solo command option before the command', () {
+ var parser = ArgParser();
+ var command = parser.addCommand('install');
+ command.addOption('path', abbr: 'p');
+
+ throwsFormat(parser, ['-p', 'foo', 'install']);
+ });
+
+ test('does not parse a command option before the command', () {
+ var parser = ArgParser();
+ var command = parser.addCommand('install');
+ command.addOption('path');
+
+ throwsFormat(parser, ['--path', 'foo', 'install']);
+ });
+
+ test('does not parse a command abbreviation before the command', () {
+ var parser = ArgParser();
+ var command = parser.addCommand('install');
+ command.addFlag('debug', abbr: 'd');
+ command.addFlag('verbose', abbr: 'v');
+
+ throwsFormat(parser, ['-dv', 'install']);
+ });
+
+ test('assigns collapsed options to the proper command', () {
+ var parser = ArgParser();
+ parser.addFlag('apple', abbr: 'a');
+ var command = parser.addCommand('cmd');
+ command.addFlag('banana', abbr: 'b');
+ var subcommand = command.addCommand('subcmd');
+ subcommand.addFlag('cherry', abbr: 'c');
+
+ var args = parser.parse(['cmd', 'subcmd', '-abc']);
+ expect(args['apple'], isTrue);
+ expect(args.command!.name, equals('cmd'));
+ expect(args.command!['banana'], isTrue);
+ expect(args.command!.command!.name, equals('subcmd'));
+ expect(args.command!.command!['cherry'], isTrue);
+ });
+
+ test('option is given to innermost command that can take it', () {
+ var parser = ArgParser();
+ parser.addFlag('verbose');
+ parser.addCommand('cmd')
+ ..addFlag('verbose')
+ ..addCommand('subcmd');
+
+ var args = parser.parse(['cmd', 'subcmd', '--verbose']);
+ expect(args['verbose'], isFalse);
+ expect(args.command!.name, equals('cmd'));
+ expect(args.command!['verbose'], isTrue);
+ expect(args.command!.command!.name, equals('subcmd'));
+ });
+
+ test('remaining arguments are given to the innermost command', () {
+ var parser = ArgParser();
+ parser.addCommand('cmd').addCommand('subcmd');
+
+ var args = parser.parse(['cmd', 'subcmd', 'other', 'stuff']);
+ expect(args.command!.name, equals('cmd'));
+ expect(args.rest, isEmpty);
+ expect(args.command!.command!.name, equals('subcmd'));
+ expect(args.command!.rest, isEmpty);
+ expect(args.command!.command!.rest, equals(['other', 'stuff']));
+ });
+ });
+}
diff --git a/pkgs/args/test/command_runner_test.dart b/pkgs/args/test/command_runner_test.dart
new file mode 100644
index 0000000..b9fde8a
--- /dev/null
+++ b/pkgs/args/test/command_runner_test.dart
@@ -0,0 +1,759 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:args/command_runner.dart';
+import 'package:test/test.dart';
+
+import 'test_utils.dart';
+
+const _defaultUsage = '''
+Usage: test <command> [arguments]
+
+Global options:
+-h, --help Print this usage information.
+
+Available commands:
+ help Display help information for test.
+
+Run "test help <command>" for more information about a command.''';
+
+void main() {
+ late CommandRunner runner;
+ setUp(() {
+ runner = CommandRunner('test', 'A test command runner.');
+ });
+
+ test('.invocation has a sane default', () {
+ expect(runner.invocation, equals('test <command> [arguments]'));
+ });
+
+ group('.usage', () {
+ test('returns the usage string', () {
+ expect(runner.usage, equals('''
+A test command runner.
+
+$_defaultUsage'''));
+ });
+
+ test('contains custom commands', () {
+ runner.addCommand(FooCommand());
+
+ expect(runner.usage, equals('''
+A test command runner.
+
+Usage: test <command> [arguments]
+
+Global options:
+-h, --help Print this usage information.
+
+Available commands:
+ foo Set a value.
+
+Run "test help <command>" for more information about a command.'''));
+ });
+
+ group('displays categories', () {
+ test('when some commands are categorized', () {
+ runner.addCommand(Category1Command());
+ runner.addCommand(Category2Command());
+ runner.addCommand(FooCommand());
+
+ expect(runner.usage, equals('''
+A test command runner.
+
+Usage: test <command> [arguments]
+
+Global options:
+-h, --help Print this usage information.
+
+Available commands:
+ foo Set a value.
+
+Displayers
+ baz Display a value.
+
+Printers
+ bar Print a value.
+
+Run "test help <command>" for more information about a command.'''));
+ });
+
+ test('except when all commands in a category are hidden', () {
+ runner.addCommand(Category1Command());
+ runner.addCommand(HiddenCategorizedCommand());
+
+ expect(runner.usage, equals('''
+A test command runner.
+
+Usage: test <command> [arguments]
+
+Global options:
+-h, --help Print this usage information.
+
+Available commands:
+
+Printers
+ bar Print a value.
+
+Run "test help <command>" for more information about a command.'''));
+ });
+
+ test('when all commands are categorized', () {
+ runner.addCommand(Category1Command());
+ runner.addCommand(Category2Command());
+
+ expect(runner.usage, equals('''
+A test command runner.
+
+Usage: test <command> [arguments]
+
+Global options:
+-h, --help Print this usage information.
+
+Available commands:
+
+Displayers
+ baz Display a value.
+
+Printers
+ bar Print a value.
+
+Run "test help <command>" for more information about a command.'''));
+ });
+
+ test('when multiple commands are in a category', () {
+ runner.addCommand(Category1Command());
+ runner.addCommand(Category2Command());
+ runner.addCommand(Category2Command2());
+
+ expect(runner.usage, equals('''
+A test command runner.
+
+Usage: test <command> [arguments]
+
+Global options:
+-h, --help Print this usage information.
+
+Available commands:
+
+Displayers
+ baz Display a value.
+ baz2 Display another value.
+
+Printers
+ bar Print a value.
+
+Run "test help <command>" for more information about a command.'''));
+ });
+ });
+
+ test('truncates newlines in command descriptions by default', () {
+ runner.addCommand(MultilineCommand());
+
+ expect(runner.usage, equals('''
+A test command runner.
+
+Usage: test <command> [arguments]
+
+Global options:
+-h, --help Print this usage information.
+
+Available commands:
+ multiline Multi
+
+Run "test help <command>" for more information about a command.'''));
+ });
+
+ test('supports newlines in command summaries', () {
+ runner.addCommand(MultilineSummaryCommand());
+
+ expect(runner.usage, equals('''
+A test command runner.
+
+Usage: test <command> [arguments]
+
+Global options:
+-h, --help Print this usage information.
+
+Available commands:
+ multiline Multi
+ line.
+
+Run "test help <command>" for more information about a command.'''));
+ });
+
+ test('contains custom options', () {
+ runner.argParser.addFlag('foo', help: 'Do something.');
+
+ expect(runner.usage, equals('''
+A test command runner.
+
+Usage: test <command> [arguments]
+
+Global options:
+-h, --help Print this usage information.
+ --[no-]foo Do something.
+
+Available commands:
+ help Display help information for test.
+
+Run "test help <command>" for more information about a command.'''));
+ });
+
+ test("doesn't print hidden commands", () {
+ runner
+ ..addCommand(HiddenCommand())
+ ..addCommand(HiddenCategorizedCommand())
+ ..addCommand(FooCommand());
+
+ expect(runner.usage, equals('''
+A test command runner.
+
+Usage: test <command> [arguments]
+
+Global options:
+-h, --help Print this usage information.
+
+Available commands:
+ foo Set a value.
+
+Run "test help <command>" for more information about a command.'''));
+ });
+
+ test("doesn't print aliases", () {
+ runner.addCommand(AliasedCommand());
+
+ expect(runner.usage, equals('''
+A test command runner.
+
+Usage: test <command> [arguments]
+
+Global options:
+-h, --help Print this usage information.
+
+Available commands:
+ aliased Set a value.
+
+Run "test help <command>" for more information about a command.'''));
+ });
+
+ test('respects usageLineLength', () {
+ runner = CommandRunner('name', 'desc', usageLineLength: 35);
+ expect(runner.usage, equals('''
+desc
+
+Usage: name <command> [arguments]
+
+Global options:
+-h, --help Print this usage
+ information.
+
+Available commands:
+ help Display help information
+ for name.
+
+Run "name help <command>" for more
+information about a command.'''));
+ });
+ });
+
+ test('usageException splits up the message and usage', () {
+ expect(() => runner.usageException('message'),
+ throwsUsageException('message', _defaultUsage));
+ });
+
+ group('run()', () {
+ test('runs a command', () {
+ var command = FooCommand();
+ runner.addCommand(command);
+
+ expect(
+ runner.run(['foo']).then((_) {
+ expect(command.hasRun, isTrue);
+ }),
+ completes);
+ });
+
+ test('runs an asynchronous command', () {
+ var command = AsyncCommand();
+ runner.addCommand(command);
+
+ expect(
+ runner.run(['async']).then((_) {
+ expect(command.hasRun, isTrue);
+ }),
+ completes);
+ });
+
+ test('runs a command with a return value', () {
+ var runner = CommandRunner<int>('test', '');
+ var command = ValueCommand();
+ runner.addCommand(command);
+
+ expect(runner.run(['foo']), completion(equals(12)));
+ });
+
+ test('runs a command with an asynchronous return value', () {
+ var runner = CommandRunner<String>('test', '');
+ var command = AsyncValueCommand();
+ runner.addCommand(command);
+
+ expect(runner.run(['foo']), completion(equals('hi')));
+ });
+
+ test('runs a hidden comand', () {
+ var command = HiddenCommand();
+ runner.addCommand(command);
+
+ expect(
+ runner.run(['hidden']).then((_) {
+ expect(command.hasRun, isTrue);
+ }),
+ completes);
+ });
+
+ test('runs an aliased comand', () {
+ var command = AliasedCommand();
+ runner.addCommand(command);
+
+ expect(
+ runner.run(['als']).then((_) {
+ expect(command.hasRun, isTrue);
+ }),
+ completes);
+ });
+
+ test('runs a subcommand', () {
+ var command = AsyncCommand();
+ runner.addCommand(FooCommand()..addSubcommand(command));
+
+ expect(
+ runner.run(['foo', 'async']).then((_) {
+ expect(command.hasRun, isTrue);
+ }),
+ completes);
+ });
+
+ group('suggests similar commands', () {
+ test('deletions', () {
+ var command = FooCommand();
+ runner.addCommand(command);
+
+ for (var typo in ['afoo', 'foao', 'fooa']) {
+ expect(() => runner.run([typo]), throwsUsageException('''
+Could not find a command named "$typo".
+
+Did you mean one of these?
+ foo
+''', anything));
+ }
+ });
+
+ test('additions', () {
+ var command = LongCommand();
+ runner.addCommand(command);
+
+ for (var typo in ['ong', 'lng', 'lon']) {
+ expect(() => runner.run([typo]), throwsUsageException('''
+Could not find a command named "$typo".
+
+Did you mean one of these?
+ long
+''', anything));
+ }
+ });
+
+ test('substitutions', () {
+ var command = LongCommand();
+ runner.addCommand(command);
+
+ for (var typo in ['aong', 'lang', 'lona']) {
+ expect(() => runner.run([typo]), throwsUsageException('''
+Could not find a command named "$typo".
+
+Did you mean one of these?
+ long
+''', anything));
+ }
+ });
+
+ test('swaps', () {
+ var command = LongCommand();
+ runner.addCommand(command);
+
+ for (var typo in ['olng', 'lnog', 'logn']) {
+ expect(() => runner.run([typo]), throwsUsageException('''
+Could not find a command named "$typo".
+
+Did you mean one of these?
+ long
+''', anything));
+ }
+ });
+
+ test('combinations', () {
+ var command = LongCommand();
+ runner.addCommand(command);
+
+ for (var typo in ['oln', 'on', 'lgn', 'alogn']) {
+ expect(() => runner.run([typo]), throwsUsageException('''
+Could not find a command named "$typo".
+
+Did you mean one of these?
+ long
+''', anything));
+ }
+ });
+
+ test('sorts by relevance', () {
+ var a = CustomNameCommand('abcd');
+ runner.addCommand(a);
+ var b = CustomNameCommand('bcd');
+ runner.addCommand(b);
+
+ expect(() => runner.run(['abdc']), throwsUsageException('''
+Could not find a command named "abdc".
+
+Did you mean one of these?
+ abcd
+ bcd
+''', anything));
+
+ expect(() => runner.run(['bdc']), throwsUsageException('''
+Could not find a command named "bdc".
+
+Did you mean one of these?
+ bcd
+ abcd
+''', anything));
+ });
+
+ test('omits commands with an edit distance over 2', () {
+ var command = LongCommand();
+ runner.addCommand(command);
+
+ for (var typo in ['llllong', 'aolgn', 'abcg', 'longggg']) {
+ expect(
+ () => runner.run([typo]),
+ throwsUsageException(
+ 'Could not find a command named "$typo".', anything));
+ }
+ });
+
+ test('max edit distance is configurable', () {
+ runner = CommandRunner('test', 'A test command runner.',
+ suggestionDistanceLimit: 1)
+ ..addCommand(LongCommand());
+ expect(
+ () => runner.run(['ng']),
+ throwsUsageException(
+ 'Could not find a command named "ng".', anything));
+
+ runner = CommandRunner('test', 'A test command runner.',
+ suggestionDistanceLimit: 3)
+ ..addCommand(LongCommand());
+ expect(() => runner.run(['g']), throwsUsageException('''
+Could not find a command named "g".
+
+Did you mean one of these?
+ long
+''', anything));
+ });
+
+ test('supports subcommands', () {
+ var command = FooCommand();
+ command.addSubcommand(LongCommand());
+ runner.addCommand(command);
+ expect(() => runner.run(['foo', 'ong']), throwsUsageException('''
+Could not find a subcommand named "ong" for "test foo".
+
+Did you mean one of these?
+ long
+''', anything));
+ });
+
+ test('doesn\'t show hidden commands', () {
+ var command = HiddenCommand();
+ runner.addCommand(command);
+ expect(
+ () => runner.run(['hidde']),
+ throwsUsageException(
+ 'Could not find a command named "hidde".', anything));
+ });
+
+ test('Suggests based on aliases', () {
+ var command = AliasedCommand();
+ runner.addCommand(command);
+ expect(() => runner.run(['rename']), throwsUsageException('''
+Could not find a command named "rename".
+
+Did you mean one of these?
+ aliased
+''', anything));
+ });
+
+ test('Suggests based on suggestedAliases', () {
+ var command = SuggestionAliasedCommand();
+ runner.addCommand(command);
+ expect(() => runner.run(['renamed']), throwsUsageException('''
+Could not find a command named "renamed".
+
+Did you mean one of these?
+ aliased
+''', anything));
+ });
+ });
+
+ group('with --help', () {
+ test('with no command prints the usage', () {
+ expect(() => runner.run(['--help']), prints('''
+A test command runner.
+
+$_defaultUsage
+'''));
+ });
+
+ test('with a preceding command prints the usage for that command', () {
+ var command = FooCommand();
+ runner.addCommand(command);
+
+ expect(() => runner.run(['foo', '--help']), prints('''
+Set a value.
+
+Usage: test foo [arguments]
+-h, --help Print this usage information.
+
+Run "test help" to see global options.
+'''));
+ });
+
+ test('with a following command prints the usage for that command', () {
+ var command = FooCommand();
+ runner.addCommand(command);
+
+ expect(() => runner.run(['--help', 'foo']), prints('''
+Set a value.
+
+Usage: test foo [arguments]
+-h, --help Print this usage information.
+
+Run "test help" to see global options.
+'''));
+ });
+ });
+
+ group('with help command', () {
+ test('with no command prints the usage', () {
+ expect(() => runner.run(['help']), prints('''
+A test command runner.
+
+$_defaultUsage
+'''));
+ });
+
+ test('with a command prints the usage for that command', () {
+ var command = FooCommand();
+ runner.addCommand(command);
+
+ expect(() => runner.run(['help', 'foo']), prints('''
+Set a value.
+
+Usage: test foo [arguments]
+-h, --help Print this usage information.
+
+Run "test help" to see global options.
+'''));
+ });
+
+ test('prints its own usage', () {
+ expect(() => runner.run(['help', 'help']), prints('''
+Display help information for test.
+
+Usage: test help [command]
+-h, --help Print this usage information.
+
+Run "test help" to see global options.
+'''));
+ });
+ });
+
+ group('with an invalid argument', () {
+ test('at the root throws the root usage', () {
+ expect(
+ runner.run(['--asdf']),
+ throwsUsageException(
+ 'Could not find an option named "--asdf".', _defaultUsage));
+ });
+
+ test('for a command throws the command usage', () {
+ var command = FooCommand();
+ runner.addCommand(command);
+
+ expect(runner.run(['foo', '--asdf']),
+ throwsUsageException('Could not find an option named "--asdf".', '''
+Usage: test foo [arguments]
+-h, --help Print this usage information.
+
+Run "test help" to see global options.'''));
+ });
+ });
+ });
+
+ group('with a footer', () {
+ setUp(() {
+ runner = CommandRunnerWithFooter('test', 'A test command runner.');
+ });
+
+ test('includes the footer in the usage string', () {
+ expect(runner.usage, equals('''
+A test command runner.
+
+$_defaultUsage
+Also, footer!'''));
+ });
+
+ test('includes the footer in usage errors', () {
+ expect(
+ runner.run(['--bad']),
+ throwsUsageException('Could not find an option named "--bad".',
+ '$_defaultUsage\nAlso, footer!'));
+ });
+ });
+
+ group('with a footer and wrapping', () {
+ setUp(() {
+ runner =
+ CommandRunnerWithFooterAndWrapping('test', 'A test command runner.');
+ });
+ test('includes the footer in the usage string', () {
+ expect(runner.usage, equals('''
+A test command runner.
+
+Usage: test <command> [arguments]
+
+Global options:
+-h, --help Print this usage
+ information.
+
+Available commands:
+ help Display help information for
+ test.
+
+Run "test help <command>" for more
+information about a command.
+LONG footer! This is a long footer, so
+we can check wrapping on long footer
+messages.
+
+And make sure that they preserve
+newlines properly.'''));
+ });
+
+ test('includes the footer in usage errors', () {
+ expect(runner.run(['--bad']),
+ throwsUsageException('Could not find an option named "--bad".', '''
+Usage: test <command> [arguments]
+
+Global options:
+-h, --help Print this usage
+ information.
+
+Available commands:
+ help Display help information for
+ test.
+
+Run "test help <command>" for more
+information about a command.
+LONG footer! This is a long footer, so
+we can check wrapping on long footer
+messages.
+
+And make sure that they preserve
+newlines properly.'''));
+ });
+ });
+
+ group('throws a useful error when', () {
+ test('arg parsing fails', () {
+ expect(
+ runner.run(['--bad']),
+ throwsUsageException(
+ 'Could not find an option named "--bad".', _defaultUsage));
+ });
+
+ test("a top-level command doesn't exist", () {
+ expect(
+ runner.run(['bad']),
+ throwsUsageException(
+ 'Could not find a command named "bad".', _defaultUsage));
+ });
+
+ test("a subcommand doesn't exist", () {
+ runner.addCommand(FooCommand()..addSubcommand(AsyncCommand()));
+
+ expect(runner.run(['foo bad']),
+ throwsUsageException('Could not find a command named "foo bad".', '''
+Usage: test <command> [arguments]
+
+Global options:
+-h, --help Print this usage information.
+
+Available commands:
+ foo Set a value.
+
+Run "test help <command>" for more information about a command.'''));
+ });
+
+ test("a subcommand wasn't passed", () {
+ runner.addCommand(FooCommand()..addSubcommand(AsyncCommand()));
+
+ expect(runner.run(['foo']),
+ throwsUsageException('Missing subcommand for "test foo".', '''
+Usage: test foo <subcommand> [arguments]
+-h, --help Print this usage information.
+
+Available subcommands:
+ async Set a value asynchronously.
+
+Run "test help" to see global options.'''));
+ });
+
+ test("a command that doesn't take arguments was given them", () {
+ runner.addCommand(FooCommand());
+
+ expect(runner.run(['foo', 'bar']),
+ throwsUsageException('Command "foo" does not take any arguments.', '''
+Usage: test foo [arguments]
+-h, --help Print this usage information.
+
+Run "test help" to see global options.'''));
+ });
+ });
+
+ test('mandatory options in commands', () async {
+ var subcommand = _MandatoryOptionCommand();
+ runner.addCommand(subcommand);
+ expect(
+ () => runner.run([subcommand.name]),
+ throwsA(isA<ArgumentError>().having((e) => e.message, 'message',
+ contains('Option mandatory-option is mandatory'))));
+ expect(await runner.run([subcommand.name, '--mandatory-option', 'foo']),
+ 'foo');
+ });
+}
+
+class _MandatoryOptionCommand extends Command {
+ _MandatoryOptionCommand() {
+ argParser.addOption('mandatory-option', mandatory: true);
+ }
+
+ @override
+ String get description => 'A command with a mandatory option';
+
+ @override
+ String get name => 'mandatory-option-command';
+
+ @override
+ String run() => argResults!['mandatory-option'] as String;
+}
diff --git a/pkgs/args/test/command_test.dart b/pkgs/args/test/command_test.dart
new file mode 100644
index 0000000..555cc8d
--- /dev/null
+++ b/pkgs/args/test/command_test.dart
@@ -0,0 +1,158 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:args/command_runner.dart';
+import 'package:test/test.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ late FooCommand foo;
+ setUp(() {
+ foo = FooCommand();
+
+ // Make sure [Command.runner] is set up.
+ CommandRunner<void>('test', 'A test command runner.').addCommand(foo);
+ });
+
+ group('.invocation has a sane default', () {
+ test('without subcommands', () {
+ expect(foo.invocation, equals('test foo [arguments]'));
+ });
+
+ test('with subcommands', () {
+ foo.addSubcommand(AsyncCommand());
+ expect(foo.invocation, equals('test foo <subcommand> [arguments]'));
+ });
+
+ test('for a subcommand', () {
+ var async = AsyncCommand();
+ foo.addSubcommand(async);
+
+ expect(async.invocation, equals('test foo async [arguments]'));
+ });
+ });
+
+ group('.usage', () {
+ test('returns the usage string', () {
+ expect(foo.usage, equals('''
+Set a value.
+
+Usage: test foo [arguments]
+-h, --help Print this usage information.
+
+Run "test help" to see global options.'''));
+ });
+
+ test('contains custom options', () {
+ foo.argParser.addFlag('flag', help: 'Do something.');
+
+ expect(foo.usage, equals('''
+Set a value.
+
+Usage: test foo [arguments]
+-h, --help Print this usage information.
+ --[no-]flag Do something.
+
+Run "test help" to see global options.'''));
+ });
+
+ test("doesn't print hidden subcommands", () {
+ foo.addSubcommand(AsyncCommand());
+ foo.addSubcommand(HiddenCommand());
+
+ expect(foo.usage, equals('''
+Set a value.
+
+Usage: test foo <subcommand> [arguments]
+-h, --help Print this usage information.
+
+Available subcommands:
+ async Set a value asynchronously.
+
+Run "test help" to see global options.'''));
+ });
+
+ test("doesn't print subcommand aliases", () {
+ foo.addSubcommand(AliasedCommand());
+
+ expect(foo.usage, equals('''
+Set a value.
+
+Usage: test foo <subcommand> [arguments]
+-h, --help Print this usage information.
+
+Available subcommands:
+ aliased Set a value.
+
+Run "test help" to see global options.'''));
+ });
+
+ test('wraps long command descriptions with subcommands', () {
+ var wrapping = WrappingCommand();
+
+ // Make sure [Command.runner] is set up.
+ CommandRunner<void>('longtest', 'A long-lined test command runner.')
+ .addCommand(wrapping);
+
+ wrapping.addSubcommand(LongCommand());
+ expect(wrapping.usage, equals('''
+This command overrides the argParser so
+that it will wrap long lines.
+
+Usage: longtest wrapping <subcommand>
+ [arguments]
+-h, --help Print this usage
+ information.
+
+Available subcommands:
+ long This command has a long
+ description that needs to be
+ wrapped sometimes.
+
+Run "longtest help" to see global
+options.'''));
+ });
+
+ test('wraps long command descriptions', () {
+ var longCommand = LongCommand();
+
+ // Make sure [Command.runner] is set up.
+ CommandRunner<void>('longtest', 'A long-lined test command runner.')
+ .addCommand(longCommand);
+
+ expect(longCommand.usage, equals('''
+This command has a long description that
+needs to be wrapped sometimes.
+It has embedded newlines,
+ and indented lines that also need
+ to be wrapped and have their
+ indentation preserved.
+0123456789012345678901234567890123456789
+0123456789012345678901234567890123456789
+01234567890123456789
+
+Usage: longtest long [arguments]
+-h, --help Print this usage
+ information.
+
+Run "longtest help" to see global
+options.'''));
+ });
+ });
+
+ test('usageException splits up the message and usage', () {
+ expect(
+ () => foo.usageException('message'), throwsUsageException('message', '''
+Usage: test foo [arguments]
+-h, --help Print this usage information.
+
+Run "test help" to see global options.'''));
+ });
+
+ test('considers a command hidden if all its subcommands are hidden', () {
+ foo.addSubcommand(HiddenCommand());
+ expect(foo.hidden, isTrue);
+ });
+}
diff --git a/pkgs/args/test/parse_performance_test.dart b/pkgs/args/test/parse_performance_test.dart
new file mode 100644
index 0000000..b099379
--- /dev/null
+++ b/pkgs/args/test/parse_performance_test.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:args/args.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('ArgParser.parse() is fast', () {
+ test('for short flags', () {
+ _testParserPerformance(ArgParser()..addFlag('short', abbr: 's'), '-s');
+ });
+
+ test('for abbreviations', () {
+ _testParserPerformance(
+ ArgParser()
+ ..addFlag('short', abbr: 's')
+ ..addFlag('short2', abbr: 't')
+ ..addFlag('short3', abbr: 'u')
+ ..addFlag('short4', abbr: 'v'),
+ '-stuv');
+ });
+
+ test('for long flags', () {
+ _testParserPerformance(ArgParser()..addFlag('long-flag'), '--long-flag');
+ });
+
+ test('for long options with =', () {
+ _testParserPerformance(ArgParser()..addOption('long-option-name'),
+ '--long-option-name=long-option-value');
+ });
+ });
+}
+
+/// Tests how quickly [parser] parses [string].
+///
+/// Checks that a 10x increase in arg count does not lead to greater than 30x
+/// increase in parse time.
+void _testParserPerformance(ArgParser parser, String string) {
+ var baseSize = 50000;
+ var baseList = List<String>.generate(baseSize, (_) => string);
+
+ var multiplier = 10;
+ var largeList = List<String>.generate(baseSize * multiplier, (_) => string);
+
+ ArgResults baseAction() => parser.parse(baseList);
+ ArgResults largeAction() => parser.parse(largeList);
+
+ // Warm up JIT.
+ baseAction();
+ largeAction();
+
+ var baseTime = _time(baseAction);
+ var largeTime = _time(largeAction);
+
+ print('Parsed $baseSize elements in ${baseTime}ms, '
+ '${baseSize * multiplier} elements in ${largeTime}ms.');
+
+ expect(
+ largeTime,
+ lessThan(baseTime * multiplier * 3),
+ reason:
+ 'Comparing large data set time ${largeTime}ms to small data set time '
+ '${baseTime}ms. Data set increased ${multiplier}x, time is allowed to '
+ 'increase up to ${multiplier * 3}x, but it increased '
+ '${largeTime ~/ baseTime}x.',
+ );
+}
+
+int _time(void Function() function) {
+ var stopwatch = Stopwatch()..start();
+ function();
+ return stopwatch.elapsedMilliseconds;
+}
diff --git a/pkgs/args/test/parse_test.dart b/pkgs/args/test/parse_test.dart
new file mode 100644
index 0000000..9501b5d
--- /dev/null
+++ b/pkgs/args/test/parse_test.dart
@@ -0,0 +1,819 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:args/args.dart';
+import 'package:test/test.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ group('ArgParser.parse()', () {
+ test('does not destructively modify the argument list', () {
+ var parser = ArgParser();
+ parser.addFlag('verbose');
+
+ var args = ['--verbose'];
+ var results = parser.parse(args);
+ expect(args, equals(['--verbose']));
+ expect(results['verbose'], isTrue);
+ });
+
+ group('flags', () {
+ test('are true if present', () {
+ var parser = ArgParser();
+ parser.addFlag('verbose');
+
+ var args = parser.parse(['--verbose']);
+ expect(args['verbose'], isTrue);
+ });
+
+ test('default if missing', () {
+ var parser = ArgParser();
+ parser.addFlag('a', defaultsTo: true);
+ parser.addFlag('b', defaultsTo: false);
+
+ var args = parser.parse([]);
+ expect(args['a'], isTrue);
+ expect(args['b'], isFalse);
+ });
+
+ test('are false if missing with no default', () {
+ var parser = ArgParser();
+ parser.addFlag('verbose');
+
+ var args = parser.parse([]);
+ expect(args['verbose'], isFalse);
+ });
+
+ test('throws if given a value', () {
+ var parser = ArgParser();
+ parser.addFlag('verbose');
+
+ throwsFormat(parser, ['--verbose=true']);
+ });
+
+ test('are case-sensitive', () {
+ var parser = ArgParser();
+ parser.addFlag('verbose');
+ parser.addFlag('Verbose');
+ var results = parser.parse(['--verbose']);
+ expect(results['verbose'], isTrue);
+ expect(results['Verbose'], isFalse);
+ });
+
+ test('match letters, numbers, hyphens and underscores', () {
+ var parser = ArgParser();
+ var allCharacters =
+ 'abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789';
+ parser.addFlag(allCharacters);
+ var results = parser.parse(['--$allCharacters']);
+ expect(results[allCharacters], isTrue);
+ });
+
+ test('can match by alias', () {
+ var parser = ArgParser()..addFlag('a', aliases: ['b']);
+ var results = parser.parse(['--b']);
+ expect(results['a'], isTrue);
+ });
+
+ test('can be negated by alias', () {
+ var parser = ArgParser()
+ ..addFlag('a', aliases: ['b'], defaultsTo: true, negatable: true);
+ var results = parser.parse(['--no-b']);
+ expect(results['a'], isFalse);
+ });
+
+ test('throws if requested as a multi-option', () {
+ var parser = ArgParser();
+ parser.addFlag('a', defaultsTo: true);
+ var results = parser.parse(['--a']);
+ throwsIllegalArg(() => results.multiOption('a'));
+ });
+ });
+
+ group('flag()', () {
+ test('returns true if present', () {
+ var parser = ArgParser();
+ parser.addFlag('verbose');
+
+ var args = parser.parse(['--verbose']);
+ expect(args.flag('verbose'), isTrue);
+ });
+
+ test('returns default if missing', () {
+ var parser = ArgParser();
+ parser.addFlag('a', defaultsTo: true);
+ parser.addFlag('b', defaultsTo: false);
+
+ var args = parser.parse([]);
+ expect(args.flag('a'), isTrue);
+ expect(args.flag('b'), isFalse);
+ });
+
+ test('are false if missing with no default', () {
+ var parser = ArgParser();
+ parser.addFlag('verbose');
+
+ var args = parser.parse([]);
+ expect(args.flag('verbose'), isFalse);
+ });
+
+ test('are case-sensitive', () {
+ var parser = ArgParser();
+ parser.addFlag('verbose');
+ parser.addFlag('Verbose');
+ var results = parser.parse(['--verbose']);
+ expect(results.flag('verbose'), isTrue);
+ expect(results.flag('Verbose'), isFalse);
+ });
+
+ test('match letters, numbers, hyphens and underscores', () {
+ var parser = ArgParser();
+ var allCharacters =
+ 'abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789';
+ parser.addFlag(allCharacters);
+ var results = parser.parse(['--$allCharacters']);
+ expect(results.flag(allCharacters), isTrue);
+ });
+
+ test('can match by alias', () {
+ var parser = ArgParser()..addFlag('a', aliases: ['b']);
+ var results = parser.parse(['--b']);
+ expect(results.flag('a'), isTrue);
+ });
+
+ test('can be negated by alias', () {
+ var parser = ArgParser()
+ ..addFlag('a', aliases: ['b'], defaultsTo: true, negatable: true);
+ var results = parser.parse(['--no-b']);
+ expect(results.flag('a'), isFalse);
+ });
+
+ test('throws if requested as a multi-option', () {
+ var parser = ArgParser();
+ parser.addFlag('a', defaultsTo: true);
+ var results = parser.parse(['--a']);
+ throwsIllegalArg(() => results.multiOption('a'));
+ });
+ });
+
+ group('flags negated with "no-"', () {
+ test('set the flag to false', () {
+ var parser = ArgParser();
+ parser.addFlag('verbose');
+
+ var args = parser.parse(['--no-verbose']);
+ expect(args['verbose'], isFalse);
+ });
+
+ test('set the flag to true if the flag actually starts with "no-"', () {
+ var parser = ArgParser();
+ parser.addFlag('no-body');
+
+ var args = parser.parse(['--no-body']);
+ expect(args['no-body'], isTrue);
+ });
+
+ test('are not preferred over a colliding one without', () {
+ var parser = ArgParser();
+ parser.addFlag('no-strum');
+ parser.addFlag('strum');
+
+ var args = parser.parse(['--no-strum']);
+ expect(args['no-strum'], isTrue);
+ expect(args['strum'], isFalse);
+ });
+
+ test('fail for non-negatable flags', () {
+ var parser = ArgParser();
+ parser.addFlag('strum', negatable: false);
+
+ throwsFormat(parser, ['--no-strum']);
+ });
+ });
+
+ group('callbacks', () {
+ test('for present flags are invoked with the value', () {
+ bool? a;
+ var parser = ArgParser();
+ parser.addFlag('a', callback: (value) => a = value);
+
+ parser.parse(['--a']);
+ expect(a, isTrue);
+ });
+
+ test('for absent flags are invoked with the default value', () {
+ bool? a;
+ var parser = ArgParser();
+ parser.addFlag('a', defaultsTo: false, callback: (value) => a = value);
+
+ parser.parse([]);
+ expect(a, isFalse);
+ });
+
+ test('are invoked even if the flag is not present', () {
+ var a = true;
+ var parser = ArgParser();
+ parser.addFlag('a', callback: (value) => a = value);
+
+ parser.parse([]);
+ expect(a, isFalse);
+ });
+
+ test('for present options are invoked with the value', () {
+ String? a;
+ var parser = ArgParser();
+ parser.addOption('a', callback: (value) => a = value);
+
+ parser.parse(['--a=v']);
+ expect(a, equals('v'));
+ });
+
+ test('for absent options are invoked with the default value', () {
+ var parser = ArgParser();
+ parser.addOption('a',
+ defaultsTo: 'v',
+ callback: expectAsync1((value) => expect(value, 'v')));
+
+ parser.parse([]);
+ });
+
+ test('for absent options are invoked with null if there is no default',
+ () {
+ var parser = ArgParser();
+ parser.addOption('a',
+ callback: expectAsync1((value) => expect(value, isNull)));
+
+ parser.parse([]);
+ });
+
+ group('with addMultiOption', () {
+ test('for multiple present, options are invoked with value as a list',
+ () {
+ List<String>? a;
+ var parser = ArgParser();
+ parser.addMultiOption('a', callback: (value) => a = value);
+
+ parser.parse(['--a=v', '--a=x']);
+ expect(a, equals(['v', 'x']));
+
+ // This reified type is important in strong mode so that people can
+ // safely write "as List<String>".
+ expect(a, isA<List<String>>());
+ });
+
+ test(
+ 'for single present, options are invoked with value as a single '
+ 'element list', () {
+ List<String>? a;
+ var parser = ArgParser();
+ parser.addMultiOption('a', callback: (value) => a = value);
+
+ parser.parse(['--a=v']);
+ expect(a, equals(['v']));
+ });
+
+ test('for absent, options are invoked with default value', () {
+ List<String>? a;
+ var parser = ArgParser();
+ parser.addMultiOption('a',
+ defaultsTo: ['v', 'w'], callback: (value) => a = value);
+
+ parser.parse([]);
+ expect(a, equals(['v', 'w']));
+ });
+
+ test('for absent, options are invoked with value as an empty list', () {
+ List<String>? a;
+ var parser = ArgParser();
+ parser.addMultiOption('a', callback: (value) => a = value);
+
+ parser.parse([]);
+ expect(a, isEmpty);
+ });
+
+ test('parses comma-separated strings', () {
+ List<String>? a;
+ var parser = ArgParser();
+ parser.addMultiOption('a', callback: (value) => a = value);
+
+ parser.parse(['--a=v,w', '--a=x']);
+ expect(a, equals(['v', 'w', 'x']));
+ });
+
+ test("doesn't parse comma-separated strings with splitCommas: false",
+ () {
+ List<String>? a;
+ var parser = ArgParser();
+ parser.addMultiOption('a',
+ splitCommas: false, callback: (value) => a = value);
+
+ parser.parse(['--a=v,w', '--a=x']);
+ expect(a, equals(['v,w', 'x']));
+ });
+
+ test('parses empty strings', () {
+ List<String>? a;
+ var parser = ArgParser();
+ parser.addMultiOption('a', callback: (value) => a = value);
+
+ parser.parse(['--a=,v', '--a=w,', '--a=,', '--a=x,,y', '--a', '']);
+ expect(a, equals(['', 'v', 'w', '', '', '', 'x', '', 'y', '']));
+ });
+
+ test('with allowed parses comma-separated strings', () {
+ List<String>? a;
+ var parser = ArgParser();
+ parser.addMultiOption('a',
+ allowed: ['v', 'w', 'x'], callback: (value) => a = value);
+
+ parser.parse(['--a=v,w', '--a=x']);
+ expect(a, equals(['v', 'w', 'x']));
+ });
+
+ test('can mix and match alias and regular name', () {
+ var parser = ArgParser()..addMultiOption('a', aliases: ['b']);
+ var results = parser.parse(['--a=1', '--b=2']);
+ expect(results['a'], ['1', '2']);
+ });
+ });
+ });
+
+ group('abbreviations', () {
+ test('are parsed with a preceding "-"', () {
+ var parser = ArgParser();
+ parser.addFlag('arg', abbr: 'a');
+
+ var args = parser.parse(['-a']);
+ expect(args['arg'], isTrue);
+ });
+
+ test('can use multiple after a single "-"', () {
+ var parser = ArgParser();
+ parser.addFlag('first', abbr: 'f');
+ parser.addFlag('second', abbr: 's');
+ parser.addFlag('third', abbr: 't');
+
+ var args = parser.parse(['-tf']);
+ expect(args['first'], isTrue);
+ expect(args['second'], isFalse);
+ expect(args['third'], isTrue);
+ });
+
+ test('can have multiple "-" args', () {
+ var parser = ArgParser();
+ parser.addFlag('first', abbr: 'f');
+ parser.addFlag('second', abbr: 's');
+ parser.addFlag('third', abbr: 't');
+
+ var args = parser.parse(['-s', '-tf']);
+ expect(args['first'], isTrue);
+ expect(args['second'], isTrue);
+ expect(args['third'], isTrue);
+ });
+
+ test('can take arguments without a space separating', () {
+ var parser = ArgParser();
+ parser.addOption('file', abbr: 'f');
+
+ var args = parser.parse(['-flip']);
+ expect(args['file'], equals('lip'));
+ });
+
+ test('can take arguments with a space separating', () {
+ var parser = ArgParser();
+ parser.addOption('file', abbr: 'f');
+
+ var args = parser.parse(['-f', 'name']);
+ expect(args['file'], equals('name'));
+ });
+
+ test('allow non-option characters in the value', () {
+ var parser = ArgParser();
+ parser.addOption('apple', abbr: 'a');
+
+ var args = parser.parse(['-ab?!c']);
+ expect(args['apple'], equals('b?!c'));
+ });
+
+ test('throw if unknown', () {
+ var parser = ArgParser();
+ throwsFormat(parser, ['-f']);
+ });
+
+ test('throw if the value is missing', () {
+ var parser = ArgParser();
+ parser.addOption('file', abbr: 'f');
+
+ throwsFormat(parser, ['-f']);
+ });
+
+ test('does not throw if the value looks like an option', () {
+ var parser = ArgParser();
+ parser.addOption('file', abbr: 'f');
+ parser.addOption('other');
+
+ expect(parser.parse(['-f', '--other'])['file'], equals('--other'));
+ expect(parser.parse(['-f', '--unknown'])['file'], equals('--unknown'));
+ expect(parser.parse(['-f', '-abbr'])['file'], equals('-abbr'));
+ expect(parser.parse(['-f', '--'])['file'], equals('--'));
+ });
+
+ test('throw if the value is not allowed', () {
+ var parser = ArgParser();
+ parser.addOption('mode', abbr: 'm', allowed: ['debug', 'release']);
+
+ throwsFormat(parser, ['-mprofile']);
+ });
+
+ group('throw if a comma-separated value is not allowed', () {
+ test('with addMultiOption', () {
+ var parser = ArgParser();
+ parser
+ .addMultiOption('mode', abbr: 'm', allowed: ['debug', 'release']);
+
+ throwsFormat(parser, ['-mdebug,profile']);
+ });
+ });
+
+ test('throw if any but the first is not a flag', () {
+ var parser = ArgParser();
+ parser.addFlag('apple', abbr: 'a');
+ parser.addOption('banana', abbr: 'b'); // Takes an argument.
+ parser.addFlag('cherry', abbr: 'c');
+
+ throwsFormat(parser, ['-abc']);
+ });
+
+ test('throw if it has a value but the option is a flag', () {
+ var parser = ArgParser();
+ parser.addFlag('apple', abbr: 'a');
+ parser.addFlag('banana', abbr: 'b');
+
+ // The '?!' means this can only be understood as '--apple b?!c'.
+ throwsFormat(parser, ['-ab?!c']);
+ });
+
+ test('are case-sensitive', () {
+ var parser = ArgParser();
+ parser.addFlag('file', abbr: 'f');
+ parser.addFlag('force', abbr: 'F');
+ var results = parser.parse(['-f']);
+ expect(results['file'], isTrue);
+ expect(results['force'], isFalse);
+ });
+ });
+
+ group('options', () {
+ test('are parsed if present', () {
+ var parser = ArgParser();
+ parser.addOption('mode');
+ var args = parser.parse(['--mode=release']);
+ expect(args['mode'], equals('release'));
+ });
+
+ test('are null if not present', () {
+ var parser = ArgParser();
+ parser.addOption('mode');
+ var args = parser.parse([]);
+ expect(args['mode'], isNull);
+ });
+
+ test('default if missing', () {
+ var parser = ArgParser();
+ parser.addOption('mode', defaultsTo: 'debug');
+ var args = parser.parse([]);
+ expect(args['mode'], equals('debug'));
+ });
+
+ test('allow the value to be separated by whitespace', () {
+ var parser = ArgParser();
+ parser.addOption('mode');
+ var args = parser.parse(['--mode', 'release']);
+ expect(args['mode'], equals('release'));
+ });
+
+ test('throw if unknown', () {
+ var parser = ArgParser();
+ throwsFormat(parser, ['--unknown']);
+ throwsFormat(parser, ['--nobody']); // Starts with "no".
+ });
+
+ test('throw if the arg does not include a value', () {
+ var parser = ArgParser();
+ parser.addOption('mode');
+ throwsFormat(parser, ['--mode']);
+ });
+
+ test('do not throw if the value looks like an option', () {
+ var parser = ArgParser();
+ parser.addOption('mode');
+ parser.addOption('other');
+
+ expect(parser.parse(['--mode', '--other'])['mode'], equals('--other'));
+ expect(
+ parser.parse(['--mode', '--unknown'])['mode'], equals('--unknown'));
+ expect(parser.parse(['--mode', '-abbr'])['mode'], equals('-abbr'));
+ expect(parser.parse(['--mode', '--'])['mode'], equals('--'));
+ });
+
+ test('do not throw if the value is in the allowed set', () {
+ var parser = ArgParser();
+ parser.addOption('mode', allowed: ['debug', 'release']);
+ var args = parser.parse(['--mode=debug']);
+ expect(args['mode'], equals('debug'));
+ });
+
+ test('do not throw if there is no allowed set with allowedHelp', () {
+ var parser = ArgParser();
+ parser.addOption('mode', allowedHelp: {
+ 'debug': 'During development.',
+ 'release': 'For customers.'
+ });
+ var args = parser.parse(['--mode=profile']);
+ expect(args['mode'], equals('profile'));
+ });
+
+ test('throw if the value is not in the allowed set', () {
+ var parser = ArgParser();
+ parser.addOption('mode', allowed: ['debug', 'release']);
+ throwsFormat(parser, ['--mode=profile']);
+ });
+
+ test('returns last provided value', () {
+ var parser = ArgParser();
+ parser.addOption('define');
+ var args = parser.parse(['--define=1', '--define=2']);
+ expect(args['define'], equals('2'));
+ });
+
+ test('throw if requested as a multi-option', () {
+ var parser = ArgParser();
+ parser.addOption('a', defaultsTo: 'b');
+ var results = parser.parse(['--a=c']);
+ throwsIllegalArg(() => results.multiOption('a'));
+ });
+
+ group('returns a List', () {
+ test('with addMultiOption', () {
+ var parser = ArgParser();
+ parser.addMultiOption('define');
+ var args = parser.parse(['--define=1']);
+ expect(args['define'], equals(['1']));
+ args = parser.parse(['--define=1', '--define=2']);
+ expect(args['define'], equals(['1', '2']));
+ });
+ });
+
+ group('returns the default value if not explicitly set', () {
+ test('with addMultiOption', () {
+ var parser = ArgParser();
+ parser.addMultiOption('define', defaultsTo: ['0']);
+ var args = parser.parse(['']);
+ expect(args['define'], equals(['0']));
+ });
+ });
+
+ test('are case-sensitive', () {
+ var parser = ArgParser();
+ parser.addOption('verbose', defaultsTo: 'no');
+ parser.addOption('Verbose', defaultsTo: 'no');
+ var results = parser.parse(['--verbose', 'chatty']);
+ expect(results['verbose'], equals('chatty'));
+ expect(results['Verbose'], equals('no'));
+ });
+
+ test('can be set by alias', () {
+ var parser = ArgParser()..addOption('a', aliases: ['b']);
+ var results = parser.parse(['--b=1']);
+ expect(results['a'], '1');
+ });
+
+ group('mandatory', () {
+ test('throw if no args', () {
+ var parser = ArgParser();
+ parser.addOption('username', mandatory: true);
+ var results = parser.parse([]);
+ expect(() => results['username'], throwsA(isA<ArgumentError>()));
+ });
+
+ test('throw if no mandatory args', () {
+ var parser = ArgParser();
+ parser.addOption('test');
+ parser.addOption('username', mandatory: true);
+ var results = parser.parse(['--test', 'test']);
+ expect(results['test'], equals('test'));
+ expect(() => results['username'], throwsA(isA<ArgumentError>()));
+ });
+
+ test('parse successfully', () {
+ var parser = ArgParser();
+ parser.addOption('test', mandatory: true);
+ var results = parser.parse(['--test', 'test']);
+ expect(results['test'], equals('test'));
+ });
+
+ test('throws when value retrieved', () {
+ var parser = ArgParser();
+ parser.addFlag('help', abbr: 'h', negatable: false);
+ parser.addOption('test', mandatory: true);
+ var results = parser.parse(['-h']);
+ expect(results['help'], true);
+ expect(() => results['test'], throwsA(isA<ArgumentError>()));
+ });
+ });
+ });
+
+ group('option()', () {
+ test('are parsed if present', () {
+ var parser = ArgParser();
+ parser.addOption('mode');
+ var args = parser.parse(['--mode=release']);
+ expect(args.option('mode'), equals('release'));
+ });
+
+ test('are null if not present', () {
+ var parser = ArgParser();
+ parser.addOption('mode');
+ var args = parser.parse([]);
+ expect(args.option('mode'), isNull);
+ });
+
+ test('default if missing', () {
+ var parser = ArgParser();
+ parser.addOption('mode', defaultsTo: 'debug');
+ var args = parser.parse([]);
+ expect(args.option('mode'), equals('debug'));
+ });
+
+ test('allow the value to be separated by whitespace', () {
+ var parser = ArgParser();
+ parser.addOption('mode');
+ var args = parser.parse(['--mode', 'release']);
+ expect(args.option('mode'), equals('release'));
+ });
+
+ test('do not throw if the value is in the allowed set', () {
+ var parser = ArgParser();
+ parser.addOption('mode', allowed: ['debug', 'release']);
+ var args = parser.parse(['--mode=debug']);
+ expect(args.option('mode'), equals('debug'));
+ });
+
+ test('do not throw if there is no allowed set with allowedHelp', () {
+ var parser = ArgParser();
+ parser.addOption('mode', allowedHelp: {
+ 'debug': 'During development.',
+ 'release': 'For customers.'
+ });
+ var args = parser.parse(['--mode=profile']);
+ expect(args.option('mode'), equals('profile'));
+ });
+
+ test('returns last provided value', () {
+ var parser = ArgParser();
+ parser.addOption('define');
+ var args = parser.parse(['--define=1', '--define=2']);
+ expect(args.option('define'), equals('2'));
+ });
+
+ test('throw if requested as a multi-option', () {
+ var parser = ArgParser();
+ parser.addOption('a', defaultsTo: 'b');
+ var results = parser.parse(['--a=c']);
+ throwsIllegalArg(() => results.multiOption('a'));
+ });
+
+ group('returns a List', () {
+ test('with addMultiOption', () {
+ var parser = ArgParser();
+ parser.addMultiOption('define');
+ var args = parser.parse(['--define=1']);
+ expect(args.multiOption('define'), equals(['1']));
+ args = parser.parse(['--define=1', '--define=2']);
+ expect(args.multiOption('define'), equals(['1', '2']));
+ });
+ });
+
+ group('returns the default value if not explicitly set', () {
+ test('with addMultiOption', () {
+ var parser = ArgParser();
+ parser.addMultiOption('define', defaultsTo: ['0']);
+ var args = parser.parse(['']);
+ expect(args.multiOption('define'), equals(['0']));
+ });
+ });
+
+ test('are case-sensitive', () {
+ var parser = ArgParser();
+ parser.addOption('verbose', defaultsTo: 'no');
+ parser.addOption('Verbose', defaultsTo: 'no');
+ var results = parser.parse(['--verbose', 'chatty']);
+ expect(results.option('verbose'), equals('chatty'));
+ expect(results.option('Verbose'), equals('no'));
+ });
+
+ test('can be set by alias', () {
+ var parser = ArgParser()..addOption('a', aliases: ['b']);
+ var results = parser.parse(['--b=1']);
+ expect(results.option('a'), '1');
+ });
+
+ group('mandatory', () {
+ test('parse successfully', () {
+ var parser = ArgParser();
+ parser.addOption('test', mandatory: true);
+ var results = parser.parse(['--test', 'test']);
+ expect(results.option('test'), equals('test'));
+ });
+ });
+ });
+
+ group('remaining args', () {
+ test('stops parsing args when a non-option-like arg is encountered', () {
+ var parser = ArgParser();
+ parser.addFlag('woof');
+ parser.addOption('meow');
+ parser.addOption('tweet', defaultsTo: 'bird');
+
+ var results = parser.parse(['--woof', '--meow', 'v', 'not', 'option']);
+ expect(results['woof'], isTrue);
+ expect(results['meow'], equals('v'));
+ expect(results['tweet'], equals('bird'));
+ expect(results.rest, equals(['not', 'option']));
+ });
+
+ test('consumes "--" and stops', () {
+ var parser = ArgParser();
+ parser.addFlag('woof', defaultsTo: false);
+ parser.addOption('meow', defaultsTo: 'kitty');
+
+ var results = parser.parse(['--woof', '--', '--meow']);
+ expect(results['woof'], isTrue);
+ expect(results['meow'], equals('kitty'));
+ expect(results.rest, equals(['--meow']));
+ });
+
+ test(
+ 'with allowTrailingOptions: false, leaves "--" if not the first '
+ 'non-option', () {
+ var parser = ArgParser(allowTrailingOptions: false);
+ parser.addFlag('woof');
+
+ var results = parser.parse(['--woof', 'stop', '--', 'arg']);
+ expect(results['woof'], isTrue);
+ expect(results.rest, equals(['stop', '--', 'arg']));
+ });
+ });
+
+ group('ArgParser Exception Tests', () {
+ test('throws exception for unknown option', () {
+ var parser = ArgParser();
+ throwsArgParserException(parser, ['--verbose'],
+ 'Could not find an option named "--verbose".', [], '--verbose');
+ throwsArgParserException(
+ parser, ['-v'], 'Could not find an option or flag "-v".', [], '-v');
+ });
+
+ test('throws exception for flag with value', () {
+ var parser = ArgParser();
+ parser.addFlag('flag', abbr: 'f');
+ throwsArgParserException(parser, ['--flag=1'],
+ 'Flag option "--flag" should not be given a value.', [], '--flag');
+ throwsArgParserException(parser, ['-f=1'],
+ 'Option "-f" is a flag and cannot handle value "=1".', [], '-f');
+ });
+
+ test('throws exception after parsing multiple options', () {
+ var parser = ArgParser();
+ parser.addOption('first');
+ parser.addOption('second');
+ throwsArgParserException(
+ parser,
+ ['--first', '1', '--second', '2', '--verbose', '3'],
+ 'Could not find an option named "--verbose".',
+ [],
+ '--verbose');
+ });
+
+ test('throws exception for option with invalid value', () {
+ var parser = ArgParser();
+ parser.addOption('first', allowed: ['a', 'b']);
+ throwsArgParserException(parser, ['--first', 'c'],
+ '"c" is not an allowed value for option "--first".', [], '--first');
+ });
+
+ test('throws exception after parsing command', () {
+ var parser = ArgParser();
+ parser.addCommand('command', ArgParser());
+ throwsArgParserException(
+ parser,
+ ['command', '--verbose'],
+ 'Could not find an option named "--verbose".',
+ ['command'],
+ '--verbose');
+ });
+ });
+ });
+}
diff --git a/pkgs/args/test/test_utils.dart b/pkgs/args/test/test_utils.dart
new file mode 100644
index 0000000..f7d8b8a
--- /dev/null
+++ b/pkgs/args/test/test_utils.dart
@@ -0,0 +1,368 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:test/test.dart';
+
+class CommandRunnerWithFooter extends CommandRunner {
+ @override
+ String get usageFooter => 'Also, footer!';
+
+ CommandRunnerWithFooter(super.executableName, super.description);
+}
+
+class CommandRunnerWithFooterAndWrapping extends CommandRunner {
+ @override
+ String get usageFooter => 'LONG footer! '
+ 'This is a long footer, so we can check wrapping on long footer messages.'
+ '\n\n'
+ 'And make sure that they preserve newlines properly.';
+
+ @override
+ ArgParser get argParser => _argParser;
+ final _argParser = ArgParser(usageLineLength: 40);
+
+ CommandRunnerWithFooterAndWrapping(super.executableName, super.description);
+}
+
+class FooCommand extends Command {
+ bool hasRun = false;
+
+ @override
+ final name = 'foo';
+
+ @override
+ final description = 'Set a value.';
+
+ @override
+ final takesArguments = false;
+
+ @override
+ void run() {
+ hasRun = true;
+ }
+}
+
+class ValueCommand extends Command<int> {
+ @override
+ final name = 'foo';
+
+ @override
+ final description = 'Return a value.';
+
+ @override
+ final takesArguments = false;
+
+ @override
+ int run() => 12;
+}
+
+class AsyncValueCommand extends Command<String> {
+ @override
+ final name = 'foo';
+
+ @override
+ final description = 'Return a future.';
+
+ @override
+ final takesArguments = false;
+
+ @override
+ Future<String> run() async => 'hi';
+}
+
+class Category1Command extends Command {
+ bool hasRun = false;
+
+ @override
+ final name = 'bar';
+
+ @override
+ final description = 'Print a value.';
+
+ @override
+ final category = 'Printers';
+
+ @override
+ final takesArguments = false;
+
+ @override
+ void run() {
+ hasRun = true;
+ }
+}
+
+class Category2Command extends Command {
+ bool hasRun = false;
+
+ @override
+ final name = 'baz';
+
+ @override
+ final description = 'Display a value.';
+
+ @override
+ final category = 'Displayers';
+
+ @override
+ final takesArguments = false;
+
+ @override
+ void run() {
+ hasRun = true;
+ }
+}
+
+class Category2Command2 extends Command {
+ bool hasRun = false;
+
+ @override
+ final name = 'baz2';
+
+ @override
+ final description = 'Display another value.';
+
+ @override
+ final category = 'Displayers';
+
+ @override
+ final takesArguments = false;
+
+ @override
+ void run() {
+ hasRun = true;
+ }
+}
+
+class MultilineCommand extends Command {
+ bool hasRun = false;
+
+ @override
+ final name = 'multiline';
+
+ @override
+ final description = 'Multi\nline.';
+
+ @override
+ final takesArguments = false;
+
+ @override
+ void run() {
+ hasRun = true;
+ }
+}
+
+class WrappingCommand extends Command {
+ bool hasRun = false;
+
+ @override
+ ArgParser get argParser => _argParser;
+ final _argParser = ArgParser(usageLineLength: 40);
+
+ @override
+ final name = 'wrapping';
+
+ @override
+ final description =
+ 'This command overrides the argParser so that it will wrap long lines.';
+
+ @override
+ final takesArguments = false;
+
+ @override
+ void run() {
+ hasRun = true;
+ }
+}
+
+class LongCommand extends Command {
+ bool hasRun = false;
+
+ @override
+ ArgParser get argParser => _argParser;
+ final _argParser = ArgParser(usageLineLength: 40);
+
+ @override
+ final name = 'long';
+
+ @override
+ final description =
+ 'This command has a long description that needs to be wrapped '
+ 'sometimes.\nIt has embedded newlines,\n'
+ ' and indented lines that also need to be wrapped and have their '
+ 'indentation preserved.\n${'0123456789' * 10}';
+
+ @override
+ final takesArguments = false;
+
+ @override
+ void run() {
+ hasRun = true;
+ }
+}
+
+class MultilineSummaryCommand extends MultilineCommand {
+ @override
+ String get summary => description;
+}
+
+class HiddenCommand extends Command {
+ bool hasRun = false;
+
+ @override
+ final name = 'hidden';
+
+ @override
+ final description = 'Set a value.';
+
+ @override
+ final hidden = true;
+
+ @override
+ final takesArguments = false;
+
+ @override
+ void run() {
+ hasRun = true;
+ }
+}
+
+class HiddenCategorizedCommand extends Command {
+ bool hasRun = false;
+
+ @override
+ final name = 'hiddencategorized';
+
+ @override
+ final description = 'Set a value.';
+
+ @override
+ final category = 'Some category';
+
+ @override
+ final hidden = true;
+
+ @override
+ final takesArguments = false;
+
+ @override
+ void run() {
+ hasRun = true;
+ }
+}
+
+class AliasedCommand extends Command {
+ bool hasRun = false;
+
+ @override
+ final name = 'aliased';
+
+ @override
+ final description = 'Set a value.';
+
+ @override
+ final takesArguments = false;
+
+ @override
+ final aliases = const ['alias', 'als', 'renamed'];
+
+ @override
+ void run() {
+ hasRun = true;
+ }
+}
+
+class SuggestionAliasedCommand extends Command {
+ bool hasRun = false;
+
+ @override
+ final name = 'aliased';
+
+ @override
+ final description = 'Set a value.';
+
+ @override
+ final takesArguments = false;
+
+ @override
+ final suggestionAliases = const ['renamed'];
+
+ @override
+ void run() {
+ hasRun = true;
+ }
+}
+
+class AsyncCommand extends Command {
+ bool hasRun = false;
+
+ @override
+ final name = 'async';
+
+ @override
+ final description = 'Set a value asynchronously.';
+
+ @override
+ final takesArguments = false;
+
+ @override
+ Future run() => Future<void>.value().then((_) => hasRun = true);
+}
+
+class AllowAnythingCommand extends Command {
+ bool hasRun = false;
+
+ @override
+ final name = 'allowAnything';
+
+ @override
+ final description = 'A command using allowAnything.';
+
+ @override
+ final argParser = ArgParser.allowAnything();
+
+ @override
+ void run() {
+ hasRun = true;
+ }
+}
+
+class CustomNameCommand extends Command {
+ @override
+ final String name;
+
+ CustomNameCommand(this.name);
+
+ @override
+ String get description => 'A command with a custom name';
+}
+
+void throwsIllegalArg(void Function() function, {String? reason}) {
+ expect(function, throwsArgumentError, reason: reason);
+}
+
+void throwsFormat(ArgParser parser, List<String> args, {String? reason}) {
+ expect(() => parser.parse(args), throwsA(isA<FormatException>()),
+ reason: reason);
+}
+
+void throwsArgParserException(ArgParser parser, List<String> args,
+ String message, List<String> commands, String arg) {
+ try {
+ parser.parse(args);
+ fail('Expected an ArgParserException');
+ } on ArgParserException catch (e) {
+ expect(e.message, message);
+ expect(e.commands, commands);
+ expect(e.argumentName, arg);
+ } catch (e) {
+ fail('Expected an ArgParserException, but got $e');
+ }
+}
+
+Matcher throwsUsageException(Object? message, Object? usage) =>
+ throwsA(isA<UsageException>()
+ .having((e) => e.message, 'message', message)
+ .having((e) => e.usage, 'usage', usage));
diff --git a/pkgs/args/test/trailing_options_test.dart b/pkgs/args/test/trailing_options_test.dart
new file mode 100644
index 0000000..db502d1
--- /dev/null
+++ b/pkgs/args/test/trailing_options_test.dart
@@ -0,0 +1,100 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:args/args.dart';
+import 'package:test/test.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ test('allowTrailingOptions defaults to true', () {
+ var parser = ArgParser();
+ expect(parser.allowTrailingOptions, isTrue);
+ });
+
+ group('when trailing options are allowed', () {
+ late ArgParser parser;
+ setUp(() {
+ parser = ArgParser(allowTrailingOptions: true);
+ });
+
+ void expectThrows(List<String> args, String arg) {
+ throwsFormat(parser, args, reason: 'with allowTrailingOptions: true');
+ }
+
+ test('collects non-options in rest', () {
+ parser.addFlag('flag');
+ parser.addOption('opt', abbr: 'o');
+ var results = parser.parse(['a', '--flag', 'b', '-o', 'value', 'c']);
+ expect(results['flag'], isTrue);
+ expect(results['opt'], equals('value'));
+ expect(results.rest, equals(['a', 'b', 'c']));
+ });
+
+ test('stops parsing options at "--"', () {
+ parser.addFlag('flag');
+ parser.addOption('opt', abbr: 'o');
+ var results = parser.parse(['a', '--flag', '--', '-ovalue', 'c']);
+ expect(results['flag'], isTrue);
+ expect(results.rest, equals(['a', '-ovalue', 'c']));
+ });
+
+ test('only consumes first "--"', () {
+ parser.addFlag('flag', abbr: 'f');
+ parser.addOption('opt', abbr: 'o');
+ var results = parser.parse(['a', '--', 'b', '--', 'c']);
+ expect(results.rest, equals(['a', 'b', '--', 'c']));
+ });
+
+ test('parses a trailing flag', () {
+ parser.addFlag('flag');
+ var results = parser.parse(['arg', '--flag']);
+
+ expect(results['flag'], isTrue);
+ expect(results.rest, equals(['arg']));
+ });
+
+ test('throws on a trailing option missing its value', () {
+ parser.addOption('opt');
+ expectThrows(['arg', '--opt'], '--opt');
+ });
+
+ test('parses a trailing option', () {
+ parser.addOption('opt');
+ var results = parser.parse(['arg', '--opt', 'v']);
+ expect(results['opt'], equals('v'));
+ expect(results.rest, equals(['arg']));
+ });
+
+ test('throws on a trailing unknown flag', () {
+ expectThrows(['arg', '--xflag'], '--xflag');
+ });
+
+ test('throws on a trailing unknown option and value', () {
+ expectThrows(['arg', '--xopt', 'v'], '--xopt');
+ });
+
+ test('throws on a command', () {
+ parser.addCommand('com');
+ expectThrows(['arg', 'com'], 'com');
+ });
+ });
+
+ test("uses the innermost command's trailing options behavior", () {
+ var parser = ArgParser(allowTrailingOptions: true);
+ parser.addFlag('flag', abbr: 'f');
+ var command =
+ parser.addCommand('cmd', ArgParser(allowTrailingOptions: false));
+ command.addFlag('verbose', abbr: 'v');
+
+ var results = parser.parse(['a', '-f', 'b']);
+ expect(results['flag'], isTrue);
+ expect(results.rest, equals(['a', 'b']));
+
+ results = parser.parse(['cmd', '-f', 'a', '-v', '--unknown']);
+ expect(results['flag'], isTrue); // Not trailing.
+ expect(results.command!['verbose'], isFalse);
+ expect(results.command!.rest, equals(['a', '-v', '--unknown']));
+ });
+}
diff --git a/pkgs/args/test/usage_test.dart b/pkgs/args/test/usage_test.dart
new file mode 100644
index 0000000..6f5315b
--- /dev/null
+++ b/pkgs/args/test/usage_test.dart
@@ -0,0 +1,547 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:args/args.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('ArgParser.usage', () {
+ test('negatable flags show "no-" in title', () {
+ var parser = ArgParser();
+ parser.addFlag('mode', help: 'The mode');
+
+ validateUsage(parser, '''
+ --[no-]mode The mode
+ ''');
+ });
+
+ test('negatable flags with hideNegatedUsage don\'t show "no-" in title',
+ () {
+ var parser = ArgParser();
+ parser.addFlag('mode', help: 'The mode', hideNegatedUsage: true);
+
+ validateUsage(parser, '''
+ --mode The mode
+ ''');
+ });
+
+ test('non-negatable flags don\'t show "no-" in title', () {
+ var parser = ArgParser();
+ parser.addFlag('mode', negatable: false, help: 'The mode');
+
+ validateUsage(parser, '''
+ --mode The mode
+ ''');
+ });
+
+ test('if there are no abbreviations, there is no column for them', () {
+ var parser = ArgParser();
+ parser.addFlag('mode', help: 'The mode');
+
+ validateUsage(parser, '''
+ --[no-]mode The mode
+ ''');
+ });
+
+ test('options are lined up past abbreviations', () {
+ var parser = ArgParser();
+ parser.addFlag('mode', abbr: 'm', help: 'The mode');
+ parser.addOption('long', help: 'Lacks an abbreviation');
+
+ validateUsage(parser, '''
+ -m, --[no-]mode The mode
+ --long Lacks an abbreviation
+ ''');
+ });
+
+ test('help text is lined up past the longest option', () {
+ var parser = ArgParser();
+ parser.addFlag('mode', abbr: 'm', help: 'Lined up with below');
+ parser.addOption('a-really-long-name', help: 'Its help text');
+
+ validateUsage(parser, '''
+ -m, --[no-]mode Lined up with below
+ --a-really-long-name Its help text
+ ''');
+ });
+
+ test('leading empty lines are ignored in help text', () {
+ var parser = ArgParser();
+ parser.addFlag('mode', help: '\n\n\n\nAfter newlines');
+
+ validateUsage(parser, '''
+ --[no-]mode After newlines
+ ''');
+ });
+
+ test('trailing empty lines are ignored in help text', () {
+ var parser = ArgParser();
+ parser.addFlag('mode', help: 'Before newlines\n\n\n\n');
+
+ validateUsage(parser, '''
+ --[no-]mode Before newlines
+ ''');
+ });
+
+ test('options are documented in the order they were added', () {
+ var parser = ArgParser();
+ parser.addFlag('zebra', help: 'First');
+ parser.addFlag('monkey', help: 'Second');
+ parser.addFlag('wombat', help: 'Third');
+
+ validateUsage(parser, '''
+ --[no-]zebra First
+ --[no-]monkey Second
+ --[no-]wombat Third
+ ''');
+ });
+
+ test('the default value for a flag is shown if on', () {
+ var parser = ArgParser();
+ parser.addFlag('affirm', help: 'Should be on', defaultsTo: true);
+ parser.addFlag('negate', help: 'Should be off', defaultsTo: false);
+ parser.addFlag('null', help: 'Should be null', defaultsTo: null);
+
+ validateUsage(parser, '''
+ --[no-]affirm Should be on
+ (defaults to on)
+ --[no-]negate Should be off
+ --[no-]null Should be null
+ ''');
+ });
+
+ test('the default value for an option with no allowed list is shown', () {
+ var parser = ArgParser();
+ parser.addOption('single',
+ help: 'Can be anything', defaultsTo: 'whatevs');
+ parser.addMultiOption('multiple',
+ help: 'Can be anything', defaultsTo: ['whatevs']);
+
+ validateUsage(parser, '''
+ --single Can be anything
+ (defaults to "whatevs")
+ --multiple Can be anything
+ (defaults to "whatevs")
+ ''');
+ });
+
+ test('multiple default values for an option with no allowed list are shown',
+ () {
+ var parser = ArgParser();
+ parser.addMultiOption('any',
+ help: 'Can be anything', defaultsTo: ['some', 'stuff']);
+
+ validateUsage(parser, '''
+ --any Can be anything
+ (defaults to "some", "stuff")
+ ''');
+ });
+
+ test('no default values are shown for a multi option with an empty default',
+ () {
+ var parser = ArgParser();
+ parser.addMultiOption('implicit', help: 'Implicit default');
+ parser
+ .addMultiOption('explicit', help: 'Explicit default', defaultsTo: []);
+
+ validateUsage(parser, '''
+ --implicit Implicit default
+ --explicit Explicit default
+ ''');
+ });
+
+ test('the value help is shown', () {
+ var parser = ArgParser();
+ parser.addOption('out',
+ abbr: 'o', help: 'Where to write file', valueHelp: 'path');
+
+ validateUsage(parser, '''
+ -o, --out=<path> Where to write file
+ ''');
+ });
+
+ test('the allowed list is shown', () {
+ var parser = ArgParser();
+ parser.addOption('suit',
+ help: 'Like in cards',
+ allowed: ['spades', 'clubs', 'hearts', 'diamonds']);
+
+ validateUsage(parser, '''
+ --suit Like in cards
+ [spades, clubs, hearts, diamonds]
+ ''');
+ });
+
+ test('the default is highlighted in the allowed list', () {
+ var parser = ArgParser();
+ parser.addOption('suit',
+ help: 'Like in cards',
+ defaultsTo: 'clubs',
+ allowed: ['spades', 'clubs', 'hearts', 'diamonds']);
+
+ validateUsage(parser, '''
+ --suit Like in cards
+ [spades, clubs (default), hearts, diamonds]
+ ''');
+ });
+
+ test('multiple defaults are highlighted in the allowed list', () {
+ var parser = ArgParser();
+ parser.addMultiOption('suit',
+ help: 'Like in cards',
+ defaultsTo: ['clubs', 'diamonds'],
+ allowed: ['spades', 'clubs', 'hearts', 'diamonds']);
+
+ validateUsage(parser, '''
+ --suit Like in cards
+ [spades, clubs (default), hearts, diamonds (default)]
+ ''');
+ });
+
+ test('the allowed help is shown', () {
+ var parser = ArgParser();
+ parser.addOption('suit', help: 'Like in cards', allowed: [
+ 'spades',
+ 'clubs',
+ 'diamonds',
+ 'hearts'
+ ], allowedHelp: {
+ 'spades': 'Swords of a soldier',
+ 'clubs': 'Weapons of war',
+ 'diamonds': 'Money for this art',
+ 'hearts': 'The shape of my heart'
+ });
+
+ validateUsage(parser, '''
+ --suit Like in cards
+
+ [clubs] Weapons of war
+ [diamonds] Money for this art
+ [hearts] The shape of my heart
+ [spades] Swords of a soldier
+ ''');
+ });
+
+ test('the default is highlighted in the allowed help', () {
+ var parser = ArgParser();
+ parser.addOption('suit',
+ help: 'Like in cards',
+ defaultsTo: 'clubs',
+ allowed: [
+ 'spades',
+ 'clubs',
+ 'diamonds',
+ 'hearts'
+ ],
+ allowedHelp: {
+ 'spades': 'Swords of a soldier',
+ 'clubs': 'Weapons of war',
+ 'diamonds': 'Money for this art',
+ 'hearts': 'The shape of my heart'
+ });
+
+ validateUsage(parser, '''
+ --suit Like in cards
+
+ [clubs] (default) Weapons of war
+ [diamonds] Money for this art
+ [hearts] The shape of my heart
+ [spades] Swords of a soldier
+ ''');
+ });
+
+ test('multiple defaults are highlighted in the allowed help', () {
+ var parser = ArgParser();
+ parser.addMultiOption('suit', help: 'Like in cards', defaultsTo: [
+ 'clubs',
+ 'hearts'
+ ], allowed: [
+ 'spades',
+ 'clubs',
+ 'diamonds',
+ 'hearts'
+ ], allowedHelp: {
+ 'spades': 'Swords of a soldier',
+ 'clubs': 'Weapons of war',
+ 'diamonds': 'Money for this art',
+ 'hearts': 'The shape of my heart'
+ });
+
+ validateUsage(parser, '''
+ --suit Like in cards
+
+ [clubs] (default) Weapons of war
+ [diamonds] Money for this art
+ [hearts] (default) The shape of my heart
+ [spades] Swords of a soldier
+ ''');
+ });
+
+ test("hidden options don't appear in the help", () {
+ var parser = ArgParser();
+ parser.addOption('first', help: 'The first option');
+ parser.addOption('second', hide: true);
+ parser.addOption('third', help: 'The third option');
+
+ validateUsage(parser, '''
+ --first The first option
+ --third The third option
+ ''');
+ });
+
+ test("hidden flags don't appear in the help", () {
+ var parser = ArgParser();
+ parser.addFlag('first', help: 'The first flag');
+ parser.addFlag('second', hide: true);
+ parser.addFlag('third', help: 'The third flag');
+
+ validateUsage(parser, '''
+ --[no-]first The first flag
+ --[no-]third The third flag
+ ''');
+ });
+
+ test("hidden options don't affect spacing", () {
+ var parser = ArgParser();
+ parser.addFlag('first', help: 'The first flag');
+ parser.addFlag('second-very-long-option', hide: true);
+ parser.addFlag('third', help: 'The third flag');
+
+ validateUsage(parser, '''
+ --[no-]first The first flag
+ --[no-]third The third flag
+ ''');
+ });
+
+ test('help strings are not wrapped if usageLineLength is null', () {
+ var parser = ArgParser(usageLineLength: null);
+ parser.addFlag('long',
+ help: 'The flag with a really long help text that will not '
+ 'be wrapped.');
+ validateUsage(parser, '''
+ --[no-]long The flag with a really long help text that will not be wrapped.
+ ''');
+ });
+
+ test('help strings are wrapped properly when usageLineLength is specified',
+ () {
+ var parser = ArgParser(usageLineLength: 60);
+ parser.addFlag('long',
+ help: 'The flag with a really long help text that will be wrapped.');
+ parser.addFlag('longNewline',
+ help: 'The flag with a really long help text and newlines\n\nthat '
+ 'will still be wrapped because it is really long.');
+ parser.addFlag(
+ 'solid',
+ help: 'The-flag-with-no-whitespace-that-will-be-wrapped-by-splitting-a'
+ '-word.',
+ );
+ parser.addFlag('longWhitespace',
+ help:
+ ' The flag with a really long help text and whitespace '
+ 'at the start.');
+ parser.addFlag('longTrailspace',
+ help:
+ 'The flag with a really long help text and whitespace at the end.'
+ ' ');
+ parser.addFlag('small1', help: ' a ');
+ parser.addFlag('small2', help: ' a');
+ parser.addFlag('small3', help: 'a ');
+ validateUsage(parser, '''
+ --[no-]long The flag with a really long help
+ text that will be wrapped.
+ --[no-]longNewline The flag with a really long help
+ text and newlines
+
+ that will still be wrapped because
+ it is really long.
+ --[no-]solid The-flag-with-no-whitespace-that-wi
+ ll-be-wrapped-by-splitting-a-word.
+ --[no-]longWhitespace The flag with a really long help
+ text and whitespace at the start.
+ --[no-]longTrailspace The flag with a really long help
+ text and whitespace at the end.
+ --[no-]small1 a
+ --[no-]small2 a
+ --[no-]small3 a
+ ''');
+ });
+
+ test(
+ 'help strings are wrapped with at 10 chars when usageLineLength is '
+ 'smaller than available space', () {
+ var parser = ArgParser(usageLineLength: 1);
+ parser.addFlag('long',
+ help: 'The flag with a really long help text that will be wrapped.');
+ parser.addFlag('longNewline',
+ help:
+ 'The flag with a really long help text and newlines\n\nthat will '
+ 'still be wrapped because it is really long.');
+ parser.addFlag(
+ 'solid',
+ help: 'The-flag-with-no-whitespace-that-will-be-wrapped-by-splitting-a-'
+ 'word.',
+ );
+ parser.addFlag('small1', help: ' a ');
+ parser.addFlag('small2', help: ' a');
+ parser.addFlag('small3', help: 'a ');
+ validateUsage(parser, '''
+ --[no-]long The flag
+ with a
+ really
+ long help
+ text that
+ will be
+ wrapped.
+ --[no-]longNewline The flag
+ with a
+ really
+ long help
+ text and
+ newlines
+
+ that will
+ still be
+ wrapped
+ because it
+ is really
+ long.
+ --[no-]solid The-flag-w
+ ith-no-whi
+ tespace-th
+ at-will-be
+ -wrapped-b
+ y-splittin
+ g-a-word.
+ --[no-]small1 a
+ --[no-]small2 a
+ --[no-]small3 a
+ ''');
+ });
+
+ test('display "mandatory" after a mandatory option', () {
+ var parser = ArgParser();
+ parser.addOption('test', mandatory: true);
+ validateUsage(parser, '''
+ --test (mandatory)
+ ''');
+ });
+
+ test('throw argument error if option is mandatory with a default value',
+ () {
+ var parser = ArgParser();
+ expect(
+ () => parser.addOption('test', mandatory: true, defaultsTo: 'test'),
+ throwsArgumentError);
+ });
+
+ group('separators', () {
+ test("separates options where it's placed", () {
+ var parser = ArgParser();
+ parser.addFlag('zebra', help: 'First');
+ parser.addSeparator('Primate:');
+ parser.addFlag('monkey', help: 'Second');
+ parser.addSeparator('Marsupial:');
+ parser.addFlag('wombat', help: 'Third');
+
+ validateUsage(parser, '''
+ --[no-]zebra First
+
+ Primate:
+ --[no-]monkey Second
+
+ Marsupial:
+ --[no-]wombat Third
+ ''');
+ });
+
+ test("doesn't add extra newlines after a multiline option", () {
+ var parser = ArgParser();
+ parser.addFlag('zebra', help: 'Multi\nline');
+ parser.addSeparator('Primate:');
+ parser.addFlag('monkey', help: 'Second');
+
+ validateUsage(parser, '''
+ --[no-]zebra Multi
+ line
+
+ Primate:
+ --[no-]monkey Second
+ ''');
+ });
+
+ test("doesn't add newlines if it's the first component", () {
+ var parser = ArgParser();
+ parser.addSeparator('Equine:');
+ parser.addFlag('zebra', help: 'First');
+
+ validateUsage(parser, '''
+ Equine:
+ --[no-]zebra First
+ ''');
+ });
+
+ test("doesn't add trailing newlines if it's the last component", () {
+ var parser = ArgParser();
+ parser.addFlag('zebra', help: 'First');
+ parser.addSeparator('Primate:');
+
+ validateUsage(parser, '''
+ --[no-]zebra First
+
+ Primate:
+ ''');
+ });
+
+ test('adds a newline after another separator', () {
+ var parser = ArgParser();
+ parser.addSeparator('First');
+ parser.addSeparator('Second');
+
+ validateUsage(parser, '''
+ First
+
+ Second
+ ''');
+ });
+ });
+ });
+}
+
+void validateUsage(ArgParser parser, String expected) {
+ expected = _unindentString(expected);
+ expect(parser.usage, equals(expected));
+}
+
+String _unindentString(String text) {
+ var lines = text.split('\n');
+
+ // Count the indentation of the last line.
+ var whitespace = RegExp('^ *');
+ var indent = whitespace.firstMatch(lines[lines.length - 1])![0]!.length;
+
+ // Drop the last line. It only exists for specifying indentation.
+ lines.removeLast();
+
+ // Strip indentation from the remaining lines.
+ for (var i = 0; i < lines.length; i++) {
+ var line = lines[i];
+ if (line.length <= indent) {
+ // It's short, so it must be nothing but whitespace.
+ if (line.trim() != '') {
+ throw ArgumentError('Line "$line" does not have enough indentation.');
+ }
+
+ lines[i] = '';
+ } else {
+ if (line.substring(0, indent).trim() != '') {
+ throw ArgumentError('Line "$line" does not have enough indentation.');
+ }
+
+ lines[i] = line.substring(indent);
+ }
+ }
+
+ return lines.join('\n');
+}
diff --git a/pkgs/args/test/utils_test.dart b/pkgs/args/test/utils_test.dart
new file mode 100644
index 0000000..3cc45b8
--- /dev/null
+++ b/pkgs/args/test/utils_test.dart
@@ -0,0 +1,216 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:args/src/utils.dart';
+import 'package:test/test.dart';
+
+const _lineLength = 40;
+const _longLine = 'This is a long line that needs to be wrapped.';
+final _longLineWithNewlines =
+ 'This is a long line with newlines that\nneeds to be wrapped.\n\n'
+ '${'0123456789' * 5}';
+final _indentedLongLineWithNewlines =
+ ' This is an indented long line with newlines that\nneeds to be wrapped.'
+ '\n\tAnd preserves tabs.\n \n ${'0123456789' * 5}';
+const _shortLine = 'Short line.';
+const _indentedLongLine = ' This is an indented long line that needs to be '
+ 'wrapped and indentation preserved.';
+
+void main() {
+ group('padding', () {
+ test('can pad on the right.', () {
+ expect(padRight('foo', 6), equals('foo '));
+ });
+ });
+ group('text wrapping', () {
+ test("doesn't wrap short lines.", () {
+ expect(wrapText(_shortLine, length: _lineLength), equals(_shortLine));
+ });
+ test("doesn't wrap at all if not given a length", () {
+ expect(wrapText(_longLine), equals(_longLine));
+ });
+ test('able to wrap long lines', () {
+ expect(wrapText(_longLine, length: _lineLength), equals('''
+This is a long line that needs to be
+wrapped.'''));
+ });
+ test('wrap long lines with no whitespace', () {
+ expect(wrapText('0123456789' * 5, length: _lineLength), equals('''
+0123456789012345678901234567890123456789
+0123456789'''));
+ });
+ test('refuses to wrap to a column smaller than 10 characters', () {
+ expect(wrapText('$_longLine ${'0123456789' * 4}', length: 1), equals('''
+This is a
+long line
+that needs
+to be
+wrapped.
+0123456789
+0123456789
+0123456789
+0123456789'''));
+ });
+ test('preserves indentation', () {
+ expect(wrapText(_indentedLongLine, length: _lineLength), equals('''
+ This is an indented long line that
+ needs to be wrapped and indentation
+ preserved.'''));
+ });
+ test('preserves indentation and stripping trailing whitespace', () {
+ expect(wrapText('$_indentedLongLine ', length: _lineLength), equals('''
+ This is an indented long line that
+ needs to be wrapped and indentation
+ preserved.'''));
+ });
+ test('wraps text with newlines', () {
+ expect(wrapText(_longLineWithNewlines, length: _lineLength), equals('''
+This is a long line with newlines that
+needs to be wrapped.
+
+0123456789012345678901234567890123456789
+0123456789'''));
+ });
+ test('preserves indentation in the presence of newlines', () {
+ expect(wrapText(_indentedLongLineWithNewlines, length: _lineLength),
+ equals('''
+ This is an indented long line with
+ newlines that
+needs to be wrapped.
+\tAnd preserves tabs.
+
+ 01234567890123456789012345678901234567
+ 890123456789'''));
+ });
+ test('removes trailing whitespace when wrapping', () {
+ expect(wrapText('$_longLine \t', length: _lineLength), equals('''
+This is a long line that needs to be
+wrapped.'''));
+ });
+ test('preserves trailing whitespace when not wrapping', () {
+ expect(wrapText('$_longLine \t'), equals('$_longLine \t'));
+ });
+ test('honors hangingIndent parameter', () {
+ expect(
+ wrapText(_longLine, length: _lineLength, hangingIndent: 6), equals('''
+This is a long line that needs to be
+ wrapped.'''));
+ });
+ test('handles hangingIndent with a single unwrapped line.', () {
+ expect(wrapText(_shortLine, length: _lineLength, hangingIndent: 6),
+ equals('''
+Short line.'''));
+ });
+ test(
+ 'handles hangingIndent with two unwrapped lines and the second is empty.',
+ () {
+ expect(wrapText('$_shortLine\n', length: _lineLength, hangingIndent: 6),
+ equals('''
+Short line.
+'''));
+ },
+ );
+ test('honors hangingIndent parameter on already indented line.', () {
+ expect(wrapText(_indentedLongLine, length: _lineLength, hangingIndent: 6),
+ equals('''
+ This is an indented long line that
+ needs to be wrapped and
+ indentation preserved.'''));
+ });
+ test('honors hangingIndent parameter on already indented line.', () {
+ expect(
+ wrapText(_indentedLongLineWithNewlines,
+ length: _lineLength, hangingIndent: 6),
+ equals('''
+ This is an indented long line with
+ newlines that
+needs to be wrapped.
+ And preserves tabs.
+
+ 01234567890123456789012345678901234567
+ 890123456789'''));
+ });
+ });
+ group('text wrapping as lines', () {
+ test("doesn't wrap short lines.", () {
+ expect(wrapTextAsLines(_shortLine, length: _lineLength),
+ equals([_shortLine]));
+ });
+ test("doesn't wrap at all if not given a length", () {
+ expect(wrapTextAsLines(_longLine), equals([_longLine]));
+ });
+ test('able to wrap long lines', () {
+ expect(wrapTextAsLines(_longLine, length: _lineLength),
+ equals(['This is a long line that needs to be', 'wrapped.']));
+ });
+ test('wrap long lines with no whitespace', () {
+ expect(wrapTextAsLines('0123456789' * 5, length: _lineLength),
+ equals(['0123456789012345678901234567890123456789', '0123456789']));
+ });
+
+ test('refuses to wrap to a column smaller than 10 characters', () {
+ expect(
+ wrapTextAsLines('$_longLine ${'0123456789' * 4}', length: 1),
+ equals([
+ 'This is a',
+ 'long line',
+ 'that needs',
+ 'to be',
+ 'wrapped.',
+ '0123456789',
+ '0123456789',
+ '0123456789',
+ '0123456789'
+ ]));
+ });
+ test("doesn't preserve indentation", () {
+ expect(
+ wrapTextAsLines(_indentedLongLine, length: _lineLength),
+ equals([
+ 'This is an indented long line that needs',
+ 'to be wrapped and indentation preserved.'
+ ]));
+ });
+ test('strips trailing whitespace', () {
+ expect(
+ wrapTextAsLines('$_indentedLongLine ', length: _lineLength),
+ equals([
+ 'This is an indented long line that needs',
+ 'to be wrapped and indentation preserved.'
+ ]));
+ });
+ test('splits text with newlines properly', () {
+ expect(
+ wrapTextAsLines(_longLineWithNewlines, length: _lineLength),
+ equals([
+ 'This is a long line with newlines that',
+ 'needs to be wrapped.',
+ '',
+ '0123456789012345678901234567890123456789',
+ '0123456789'
+ ]));
+ });
+ test('does not preserves indentation in the presence of newlines', () {
+ expect(
+ wrapTextAsLines(_indentedLongLineWithNewlines, length: _lineLength),
+ equals([
+ 'This is an indented long line with',
+ 'newlines that',
+ 'needs to be wrapped.',
+ 'And preserves tabs.',
+ '',
+ '0123456789012345678901234567890123456789',
+ '0123456789'
+ ]));
+ });
+ test('removes trailing whitespace when wrapping', () {
+ expect(wrapTextAsLines('$_longLine \t', length: _lineLength),
+ equals(['This is a long line that needs to be', 'wrapped.']));
+ });
+ test('preserves trailing whitespace when not wrapping', () {
+ expect(
+ wrapTextAsLines('$_longLine \t'), equals(['$_longLine \t']));
+ });
+ });
+}
diff --git a/pkgs/async/.gitignore b/pkgs/async/.gitignore
new file mode 100644
index 0000000..29b5591
--- /dev/null
+++ b/pkgs/async/.gitignore
@@ -0,0 +1,11 @@
+# See https://dart.dev/guides/libraries/private-files
+
+.buildlog
+.DS_Store
+.idea
+
+.dart_tool/
+.settings/
+build/
+pubspec.lock
+.packages
diff --git a/pkgs/async/AUTHORS b/pkgs/async/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/async/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/async/CHANGELOG.md b/pkgs/async/CHANGELOG.md
new file mode 100644
index 0000000..06ac7d1
--- /dev/null
+++ b/pkgs/async/CHANGELOG.md
@@ -0,0 +1,378 @@
+## 2.12.0
+
+- Require Dart 3.4.
+- Move to `dart-lang/core` monorepo.
+
+## 2.11.0
+
+* Add `CancelableOperation.fromValue`.
+* Add `StreamExtensions.listenAndBuffer`, which buffers events from a stream
+ before it has a listener.
+
+## 2.10.0
+
+* Add `CancelableOperation.thenOperation` which gives more flexibility to
+ complete the resulting operation.
+* Add `CancelableCompleter.completeOperation`.
+* Require Dart 2.18.
+
+## 2.9.0
+
+* **Potentially Breaking** The default `propagateCancel` argument to
+ `CancelableOperation.then` changed from `false` to `true`. In most usages this
+ won't have a meaningful difference in behavior, but in usages where the
+ behavior is important propagation is the more common need. If there are any
+ `CancelableOperation` with multiple listeners where canceling subsequent
+ computation using `.then` shouldn't also cancel the original operation, pass
+ `propagateCancel: false`.
+* Add `StreamExtensions.firstOrNull`.
+* Add a `CancelableOperation.fromSubscription()` static factory.
+* Add a `CancelableOperation.race()` static method.
+* Update `StreamGroup` methods that return a `Future<dynamic>` today to return
+ a `Future<void>` instead.
+* Deprecated `AsyncCache.fetchStream`.
+* Make `AsyncCache.ephemeral` invalidate itself immediately when the returned
+ future completes, rather than wait for a later timer event.
+
+## 2.8.2
+
+* Deprecate `EventSinkBase`, `StreamSinkBase`, `IOSinkBase`.
+
+## 2.8.1
+
+* Don't ignore broadcast streams added to a `StreamGroup` that doesn't have an
+ active listener but previously had listeners and contains a single
+ subscription inner stream.
+
+## 2.8.0
+
+* Add `EventSinkBase`, `StreamSinkBase`, and `IOSinkBase` classes to make it
+ easier to implement custom sinks.
+* Improve performance for `ChunkedStreamReader` by creating fewer internal
+ sublists and specializing to create views for `Uint8List` chunks.
+
+## 2.7.0
+
+* Add a `Stream.slices()` extension method.
+* Fix a bug where `CancelableOperation.then` may invoke the `onValue` callback,
+ even if it had been canceled before `CancelableOperation.value` completes.
+* Fix a bug in `CancelableOperation.isComplete` where it may appear to be
+ complete and no longer be cancelable when it in fact could still be canceled.
+
+## 2.6.1
+
+* When `StreamGroup.stream.listen()` is called, gracefully handle component
+ streams throwing errors when their `Stream.listen()` methods are called.
+
+## 2.6.0
+
+* Add a `StreamCloser` class, which is a `StreamTransformer` that allows the
+ caller to force the stream to emit a done event.
+* Added `ChunkedStreamReader` for reading _chunked streams_ without managing
+ buffers.
+* Add extensions on `StreamSink`, including `StreamSink.transform()` for
+ applying `StreamSinkTransformer`s and `StreamSink.rejectErrors()`.
+* Add `StreamGroup.isIdle` and `StreamGroup.onIdle`.
+* Add `StreamGroup.isClosed` and `FutureGroup.isClosed` getters.
+
+## 2.5.0
+
+* Stable release for null safety.
+
+## 2.5.0-nullsafety.3
+
+* Update SDK constraints to `>=2.12.0-0 <3.0.0` based on beta release
+ guidelines.
+
+## 2.5.0-nullsafety.2
+
+* Remove the unusable setter `CancelableOperation.operation=`. This was
+ mistakenly added to the public API but could never be called.
+* Allow 2.12.0 dev SDK versions.
+
+## 2.5.0-nullsafety.1
+
+* Allow 2.10 stable and 2.11.0 dev SDK versions.
+
+## 2.5.0-nullsafety
+
+* Migrate this package to null safety.
+
+## 2.4.2
+
+* `StreamQueue` starts listening immediately to broadcast strings.
+
+## 2.4.1
+
+* Deprecate `DelegatingStream.typed`. Use `Stream.cast` instead.
+* Deprecate `DelegatingStreamSubcription.typed` and
+ `DelegatingStreamConsumer.typed`. For each of these the `Stream` should be
+ cast to the correct type before being used.
+* Deprecate `DelegatingStreamSink.typed`. `DelegatingSink.typed`,
+ `DelegatingEventSink.typed`, `DelegatingStreamConsumer.typed`. For each of
+ these a new `StreamController` can be constructed to forward to the sink.
+ `StreamController<T>()..stream.cast<S>().pipe(sink)`
+* Deprecate `typedStreamTransformer`. Cast after transforming instead.
+* Deprecate `StreamSinkTransformer.typed` since there was no usage.
+* Improve docs for `CancelablOperation.fromFuture`, indicate that `isCompleted`
+ starts `true`.
+
+## 2.4.0
+
+* Add `StreamGroup.mergeBroadcast()` utility.
+
+## 2.3.0
+
+* Implement `RestartableTimer.tick`.
+
+## 2.2.0
+
+* Add `then` to `CancelableOperation`.
+
+## 2.1.0
+
+* Fix `CancelableOperation.valueOrCancellation`'s type signature
+* Add `isCanceled` and `isCompleted` to `CancelableOperation`.
+
+## 2.0.8
+
+* Set max SDK version to `<3.0.0`.
+* Deprecate `DelegatingFuture.typed`, it is not necessary in Dart 2.
+
+## 2.0.7
+
+* Fix Dart 2 runtime errors.
+* Stop using deprecated constants from the SDK.
+
+## 2.0.6
+
+* Add further support for Dart 2.0 library changes to `Stream`.
+
+## 2.0.5
+
+* Fix Dart 2.0 [runtime cast errors][sdk#27223] in `StreamQueue`.
+
+[sdk#27223]: https://github.com/dart-lang/sdk/issues/27223
+
+## 2.0.4
+
+* Add support for Dart 2.0 library changes to `Stream` and `StreamTransformer`.
+ Changed classes that implement `StreamTransformer` to extend
+ `StreamTransformerBase`, and changed signatures of `firstWhere`, `lastWhere`,
+ and `singleWhere` on classes extending `Stream`. See
+ also [issue 31847][sdk#31847].
+
+ [sdk#31847]: https://github.com/dart-lang/sdk/issues/31847
+
+## 2.0.3
+
+* Fix a bug in `StreamQueue.startTransaction()` and related methods when
+ rejecting a transaction that isn't the oldest request in the queue.
+
+## 2.0.2
+
+* Add support for Dart 2.0 library changes to class `Timer`.
+
+## 2.0.1
+
+* Fix a fuzzy arrow type warning.
+
+## 2.0.0
+* Remove deprecated public `result.dart` and `stream_zip.dart` libraries and
+ deprecated classes `ReleaseStreamTransformer` and `CaptureStreamTransformer`.
+
+* Add `captureAll` and `flattenList` static methods to `Result`.
+
+* Change `ErrorResult` to not be generic and always be a `Result<Null>`.
+ That makes an error independent of the type of result it occurs instead of.
+
+## 1.13.3
+
+* Make `TypeSafeStream` extend `Stream` instead of implementing it. This ensures
+ that new methods on `Stream` are automatically picked up, they will go through
+ the `listen` method which type-checks every event.
+
+## 1.13.2
+
+* Fix a type-warning.
+
+## 1.13.1
+
+* Use `FutureOr` for various APIs that had previously used `dynamic`.
+
+## 1.13.0
+
+* Add `collectBytes` and `collectBytesCancelable` functions which collects
+ list-of-byte events into a single byte list.
+
+* Fix a bug where rejecting a `StreamQueueTransaction` would throw a
+ `StateError` if `StreamQueue.rest` had been called on one of its child queues.
+
+* `StreamQueue.withTransaction()` now properly returns whether or not the
+ transaction was committed.
+
+## 1.12.0
+
+* Add an `AsyncCache` class that caches asynchronous operations for a period of
+ time.
+
+* Add `StreamQueue.peek` and `StreamQueue.lookAheead`.
+ These allow users to look at events without consuming them.
+
+* Add `StreamQueue.startTransaction()` and `StreamQueue.withTransaction()`.
+ These allow users to conditionally consume events based on their values.
+
+* Add `StreamQueue.cancelable()`, which allows users to easily make a
+ `CancelableOperation` that can be canceled without affecting the queue.
+
+* Add `StreamQueue.eventsDispatched` which counts the number of events that have
+ been dispatched by a given queue.
+
+* Add a `subscriptionTransformer()` function to create `StreamTransformer`s that
+ modify the behavior of subscriptions to a stream.
+
+## 1.11.3
+
+* Fix strong-mode warning against the signature of Future.then
+
+## 1.11.1
+
+* Fix new strong-mode warnings introduced in Dart 1.17.0.
+
+## 1.11.0
+
+* Add a `typedStreamTransformer()` function. This wraps an untyped
+ `StreamTransformer` with the correct type parameters, and asserts the types of
+ events as they're emitted from the transformed stream.
+
+* Add a `StreamSinkTransformer.typed()` static method. This wraps an untyped
+ `StreamSinkTransformer` with the correct type parameters, and asserts the
+ types of arguments passed in to the resulting sink.
+
+## 1.10.0
+
+* Add `DelegatingFuture.typed()`, `DelegatingStreamSubscription.typed()`,
+ `DelegatingStreamConsumer.typed()`, `DelegatingSink.typed()`,
+ `DelegatingEventSink.typed()`, and `DelegatingStreamSink.typed()` static
+ methods. These wrap untyped instances of these classes with the correct type
+ parameter, and assert the types of values as they're accessed.
+
+* Add a `DelegatingStream` class. This is behaviorally identical to `StreamView`
+ from `dart:async`, but it follows this package's naming conventions and
+ provides a `DelegatingStream.typed()` static method.
+
+* Fix all strong mode warnings and add generic method annotations.
+
+* `new StreamQueue()`, `new SubscriptionStream()`, `new
+ DelegatingStreamSubscription()`, `new DelegatingStreamConsumer()`, `new
+ DelegatingSink()`, `new DelegatingEventSink()`, and `new
+ DelegatingStreamSink()` now take arguments with generic type arguments (for
+ example `Stream<T>`) rather than without (for example `Stream<dynamic>`).
+ Passing a type that wasn't `is`-compatible with the fully-specified generic
+ would already throw an error under some circumstances, so this is not
+ considered a breaking change.
+
+* `ErrorResult` now takes a type parameter.
+
+* `Result.asError` now returns a `Result<T>`.
+
+## 1.9.0
+
+* Deprecate top-level libraries other than `package:async/async.dart`, which
+ exports these libraries' interfaces.
+
+* Add `Result.captureStreamTransformer`, `Result.releaseStreamTransformer`,
+ `Result.captureSinkTransformer`, and `Result.releaseSinkTransformer`.
+
+* Deprecate `CaptureStreamTransformer`, `ReleaseStreamTransformer`,
+ `CaptureSink`, and `ReleaseSink`. `Result.captureStreamTransformer`,
+ `Result.releaseStreamTransformer`, `Result.captureSinkTransformer`, and
+ `Result.releaseSinkTransformer` should be used instead.
+
+## 1.8.0
+
+- Added `StreamSinkCompleter`, for creating a `StreamSink` now and providing its
+ destination later as another sink.
+
+- Added `StreamCompleter.setError`, a shortcut for emitting a single error event
+ on the resulting stream.
+
+- Added `NullStreamSink`, an implementation of `StreamSink` that discards all
+ events.
+
+## 1.7.0
+
+- Added `SingleSubscriptionTransformer`, a `StreamTransformer` that converts a
+ broadcast stream into a single-subscription stream.
+
+## 1.6.0
+
+- Added `CancelableOperation.valueOrCancellation()`, which allows users to be
+ notified when an operation is canceled elsewhere.
+
+- Added `StreamSinkTransformer` which transforms events before they're passed to
+ a `StreamSink`, similarly to how `StreamTransformer` transforms events after
+ they're emitted by a stream.
+
+## 1.5.0
+
+- Added `LazyStream`, which forwards to the return value of a callback that's
+ only called when the stream is listened to.
+
+## 1.4.0
+
+- Added `AsyncMemoizer.future`, which allows the result to be accessed before
+ `runOnce()` is called.
+
+- Added `CancelableOperation`, an asynchronous operation that can be canceled.
+ It can be created using a `CancelableCompleter`.
+
+- Added `RestartableTimer`, a non-periodic timer that can be reset over and
+ over.
+
+## 1.3.0
+
+- Added `StreamCompleter` class for creating a stream now and providing its
+ events later as another stream.
+
+- Added `StreamQueue` class which allows requesting events from a stream
+ before they are avilable. It is like a `StreamIterator` that can queue
+ requests.
+
+- Added `SubscriptionStream` which creates a single-subscription stream
+ from an existing stream subscription.
+
+- Added a `ResultFuture` class for synchronously accessing the result of a
+ wrapped future.
+
+- Added `FutureGroup.onIdle` and `FutureGroup.isIdle`, which provide visibility
+ into whether a group is actively waiting on any futures.
+
+- Add an `AsyncMemoizer` class for running an asynchronous block of code exactly
+ once.
+
+- Added delegating wrapper classes for a number of core async types:
+ `DelegatingFuture`, `DelegatingStreamConsumer`, `DelegatingStreamController`,
+ `DelegatingSink`, `DelegatingEventSink`, `DelegatingStreamSink`, and
+ `DelegatingStreamSubscription`. These are all simple wrappers that forward all
+ calls to the wrapped objects. They can be used to expose only the desired
+ interface for subclasses, or extended to add extra functionality.
+
+## 1.2.0
+
+- Added a `FutureGroup` class for waiting for a group of futures, potentially of
+ unknown size, to complete.
+
+- Added a `StreamGroup` class for merging the events of a group of streams,
+ potentially of unknown size.
+
+- Added a `StreamSplitter` class for splitting a stream into multiple new
+ streams.
+
+## 1.1.1
+
+- Updated SDK version constraint to at least 1.9.0.
+
+## 1.1.0
+
+- ChangeLog starts here.
diff --git a/pkgs/async/LICENSE b/pkgs/async/LICENSE
new file mode 100644
index 0000000..dbd2843
--- /dev/null
+++ b/pkgs/async/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2015, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/async/README.md b/pkgs/async/README.md
new file mode 100644
index 0000000..cb074d0
--- /dev/null
+++ b/pkgs/async/README.md
@@ -0,0 +1,99 @@
+[](https://github.com/dart-lang/core/actions/workflows/async.yaml)
+[](https://pub.dev/packages/async)
+[](https://pub.dev/packages/async/publisher)
+
+Contains utility classes in the style of `dart:async` to work with asynchronous
+computations.
+
+## Package API
+
+* The [`AsyncCache`][AsyncCache] class allows expensive asynchronous
+ computations values to be cached for a period of time.
+
+* The [`AsyncMemoizer`][AsyncMemoizer] class makes it easy to only run an
+ asynchronous operation once on demand.
+
+* The [`CancelableOperation`][CancelableOperation] class defines an operation
+ that can be canceled by its consumer. The producer can then listen for this
+ cancellation and stop producing the future when it's received. It can be
+ created using a [`CancelableCompleter`][CancelableCompleter].
+
+* The delegating wrapper classes allow users to easily add functionality on top
+ of existing instances of core types from `dart:async`. These include
+ [`DelegatingFuture`][DelegatingFuture],
+ [`DelegatingStream`][DelegatingStream],
+ [`DelegatingStreamSubscription`][DelegatingStreamSubscription],
+ [`DelegatingStreamConsumer`][DelegatingStreamConsumer],
+ [`DelegatingSink`][DelegatingSink],
+ [`DelegatingEventSink`][DelegatingEventSink], and
+ [`DelegatingStreamSink`][DelegatingStreamSink].
+
+* The [`FutureGroup`][FutureGroup] class makes it easy to wait until a group of
+ futures that may change over time completes.
+
+* The [`LazyStream`][LazyStream] class allows a stream to be initialized lazily
+ when `.listen()` is first called.
+
+* The [`NullStreamSink`][NullStreamSink] class is an implementation of
+ `StreamSink` that discards all events.
+
+* The [`RestartableTimer`][RestartableTimer] class extends `Timer` with a
+ `reset()` method.
+
+* The [`Result`][Result] class that can hold either a value or an error. It
+ provides various utilities for converting to and from `Future`s and `Stream`s.
+
+* The [`StreamGroup`][StreamGroup] class merges a collection of streams into a
+ single output stream.
+
+* The [`StreamQueue`][StreamQueue] class allows a stream to be consumed
+ event-by-event rather than being pushed whichever events as soon as they
+ arrive.
+
+* The [`StreamSplitter`][StreamSplitter] class allows a stream to be duplicated
+ into multiple identical streams.
+
+* The [`StreamZip`][StreamZip] class combines multiple streams into a single
+ stream of lists of events.
+
+* This package contains a number of [`StreamTransformer`][StreamTransformer]s.
+ [`SingleSubscriptionTransformer`][SingleSubscriptionTransformer] converts a
+ broadcast stream to a single-subscription stream, and
+ [`typedStreamTransformer`][typedStreamTransformer] casts the type of a
+ `Stream`. It also defines a transformer type for [`StreamSink`][StreamSink]s,
+ [`StreamSinkTransformer`][StreamSinkTransformer].
+
+* The [`SubscriptionStream`][SubscriptionStream] class wraps a
+ `StreamSubscription` so it can be re-used as a `Stream`.
+
+[AsyncCache]: https://pub.dev/documentation/async/latest/async/AsyncCache-class.html
+[AsyncMemoizer]: https://pub.dev/documentation/async/latest/async/AsyncMemoizer-class.html
+[CancelableCompleter]: https://pub.dev/documentation/async/latest/async/CancelableCompleter-class.html
+[CancelableOperation]: https://pub.dev/documentation/async/latest/async/CancelableOperation-class.html
+[DelegatingEventSink]: https://pub.dev/documentation/async/latest/async/DelegatingEventSink-class.html
+[DelegatingFuture]: https://pub.dev/documentation/async/latest/async/DelegatingFuture-class.html
+[DelegatingSink]: https://pub.dev/documentation/async/latest/async/DelegatingSink-class.html
+[DelegatingStreamConsumer]: https://pub.dev/documentation/async/latest/async/DelegatingStreamConsumer-class.html
+[DelegatingStreamSink]: https://pub.dev/documentation/async/latest/async/DelegatingStreamSink-class.html
+[DelegatingStreamSubscription]: https://pub.dev/documentation/async/latest/async/DelegatingStreamSubscription-class.html
+[DelegatingStream]: https://pub.dev/documentation/async/latest/async/DelegatingStream-class.html
+[FutureGroup]: https://pub.dev/documentation/async/latest/async/FutureGroup-class.html
+[LazyStream]: https://pub.dev/documentation/async/latest/async/LazyStream-class.html
+[NullStreamSink]: https://pub.dev/documentation/async/latest/async/NullStreamSink-class.html
+[RestartableTimer]: https://pub.dev/documentation/async/latest/async/RestartableTimer-class.html
+[Result]: https://pub.dev/documentation/async/latest/async/Result-class.html
+[SingleSubscriptionTransformer]: https://pub.dev/documentation/async/latest/async/SingleSubscriptionTransformer-class.html
+[StreamGroup]: https://pub.dev/documentation/async/latest/async/StreamGroup-class.html
+[StreamQueue]: https://pub.dev/documentation/async/latest/async/StreamQueue-class.html
+[StreamSinkTransformer]: https://pub.dev/documentation/async/latest/async/StreamSinkTransformer-class.html
+[StreamSink]: https://api.dart.dev/stable/dart-async/StreamSink-class.html
+[StreamSplitter]: https://pub.dev/documentation/async/latest/async/StreamSplitter-class.html
+[StreamTransformer]: https://api.dart.dev/stable/dart-async/StreamTransformer-class.html
+[StreamZip]: https://pub.dev/documentation/async/latest/async/StreamZip-class.html
+[SubscriptionStream]: https://pub.dev/documentation/async/latest/async/SubscriptionStream-class.html
+[typedStreamTransformer]: https://pub.dev/documentation/async/latest/async/typedStreamTransformer.html
+
+## Publishing automation
+
+For information about our publishing automation and release process, see
+https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
diff --git a/pkgs/async/analysis_options.yaml b/pkgs/async/analysis_options.yaml
new file mode 100644
index 0000000..519bd43
--- /dev/null
+++ b/pkgs/async/analysis_options.yaml
@@ -0,0 +1,20 @@
+# https://dart.dev/tools/analysis#the-analysis-options-file
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ errors:
+ only_throw_errors: ignore
+ unawaited_futures: ignore
+ inference_failure_on_instance_creation: ignore
+ inference_failure_on_function_invocation: ignore
+ inference_failure_on_collection_literal: ignore
+
+linter:
+ rules:
+ - avoid_unused_constructor_parameters
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
diff --git a/pkgs/async/lib/async.dart b/pkgs/async/lib/async.dart
new file mode 100644
index 0000000..67480e6
--- /dev/null
+++ b/pkgs/async/lib/async.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Utilities that expand on the asynchronous features of the `dart:async`
+/// library.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=r0tHiCjW2w0}
+library;
+
+export 'src/async_cache.dart';
+export 'src/async_memoizer.dart';
+export 'src/byte_collector.dart';
+export 'src/cancelable_operation.dart';
+export 'src/chunked_stream_reader.dart';
+export 'src/delegate/event_sink.dart';
+export 'src/delegate/future.dart';
+export 'src/delegate/sink.dart';
+export 'src/delegate/stream.dart';
+export 'src/delegate/stream_consumer.dart';
+export 'src/delegate/stream_sink.dart';
+export 'src/delegate/stream_subscription.dart';
+export 'src/future_group.dart';
+export 'src/lazy_stream.dart';
+export 'src/null_stream_sink.dart';
+export 'src/restartable_timer.dart';
+export 'src/result/error.dart';
+export 'src/result/future.dart';
+export 'src/result/result.dart';
+export 'src/result/value.dart';
+export 'src/single_subscription_transformer.dart';
+export 'src/sink_base.dart';
+export 'src/stream_closer.dart';
+export 'src/stream_completer.dart';
+export 'src/stream_extensions.dart';
+export 'src/stream_group.dart';
+export 'src/stream_queue.dart';
+export 'src/stream_sink_completer.dart';
+export 'src/stream_sink_extensions.dart';
+export 'src/stream_sink_transformer.dart';
+export 'src/stream_splitter.dart';
+export 'src/stream_subscription_transformer.dart';
+export 'src/stream_zip.dart';
+export 'src/subscription_stream.dart';
+export 'src/typed_stream_transformer.dart';
diff --git a/pkgs/async/lib/src/async_cache.dart b/pkgs/async/lib/src/async_cache.dart
new file mode 100644
index 0000000..6fc7cb0
--- /dev/null
+++ b/pkgs/async/lib/src/async_cache.dart
@@ -0,0 +1,114 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import '../async.dart';
+
+/// Runs asynchronous functions and caches the result for a period of time.
+///
+/// This class exists to cover the pattern of having potentially expensive code
+/// such as file I/O, network access, or isolate computation that's unlikely to
+/// change quickly run fewer times. For example:
+///
+/// ```dart
+/// final _usersCache = new AsyncCache<List<String>>(const Duration(hours: 1));
+///
+/// /// Uses the cache if it exists, otherwise calls the closure:
+/// Future<List<String>> get onlineUsers => _usersCache.fetch(() {
+/// // Actually fetch online users here.
+/// });
+/// ```
+///
+/// This class's timing can be mocked using
+/// [`fake_async`](https://pub.dev/packages/fake_async).
+class AsyncCache<T> {
+ /// How long cached values stay fresh.
+ ///
+ /// Set to `null` for ephemeral caches, which only stay alive until the
+ /// future completes.
+ final Duration? _duration;
+
+ /// Cached results of a previous `fetchStream` call.
+ StreamSplitter<T>? _cachedStreamSplitter;
+
+ /// Cached results of a previous [fetch] call.
+ Future<T>? _cachedValueFuture;
+
+ /// Fires when the cache should be considered stale.
+ Timer? _stale;
+
+ /// Creates a cache that invalidates its contents after [duration] has passed.
+ ///
+ /// The [duration] starts counting after the Future returned by [fetch]
+ /// completes, or after the Stream returned by `fetchStream` emits a done
+ /// event.
+ AsyncCache(Duration duration) : _duration = duration;
+
+ /// Creates a cache that invalidates after an in-flight request is complete.
+ ///
+ /// An ephemeral cache guarantees that a callback function will only be
+ /// executed at most once concurrently. This is useful for requests for which
+ /// data is updated frequently but stale data is acceptable.
+ AsyncCache.ephemeral() : _duration = null;
+
+ /// Returns a cached value from a previous call to [fetch], or runs [callback]
+ /// to compute a new one.
+ ///
+ /// If [fetch] has been called recently enough, returns its previous return
+ /// value. Otherwise, runs [callback] and returns its new return value.
+ Future<T> fetch(Future<T> Function() callback) async {
+ if (_cachedStreamSplitter != null) {
+ throw StateError('Previously used to cache via `fetchStream`');
+ }
+ return _cachedValueFuture ??= callback()
+ ..whenComplete(_startStaleTimer).ignore();
+ }
+
+ /// Returns a cached stream from a previous call to [fetchStream], or runs
+ /// [callback] to compute a new stream.
+ ///
+ /// If [fetchStream] has been called recently enough, returns a copy of its
+ /// previous return value. Otherwise, runs [callback] and returns its new
+ /// return value.
+ ///
+ /// Each call to this function returns a stream which replays the same events,
+ /// which means that all stream events are cached until this cache is
+ /// invalidated.
+ ///
+ /// Only starts counting time after the stream has been listened to,
+ /// and it has completed with a `done` event.
+ @Deprecated('Feature will be removed')
+ Stream<T> fetchStream(Stream<T> Function() callback) {
+ if (_cachedValueFuture != null) {
+ throw StateError('Previously used to cache via `fetch`');
+ }
+ var splitter = _cachedStreamSplitter ??= StreamSplitter(
+ callback().transform(StreamTransformer.fromHandlers(handleDone: (sink) {
+ _startStaleTimer();
+ sink.close();
+ })));
+ return splitter.split();
+ }
+
+ /// Removes any cached value.
+ void invalidate() {
+ // TODO: This does not return a future, but probably should.
+ _cachedValueFuture = null;
+ // TODO: This does not await, but probably should.
+ _cachedStreamSplitter?.close();
+ _cachedStreamSplitter = null;
+ _stale?.cancel();
+ _stale = null;
+ }
+
+ void _startStaleTimer() {
+ var duration = _duration;
+ if (duration != null) {
+ _stale = Timer(duration, invalidate);
+ } else {
+ invalidate();
+ }
+ }
+}
diff --git a/pkgs/async/lib/src/async_memoizer.dart b/pkgs/async/lib/src/async_memoizer.dart
new file mode 100644
index 0000000..c05c927
--- /dev/null
+++ b/pkgs/async/lib/src/async_memoizer.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// A class for running an asynchronous function exactly once and caching its
+/// result.
+///
+/// An `AsyncMemoizer` is used when some function may be run multiple times in
+/// order to get its result, but it only actually needs to be run once for its
+/// effect. To memoize the result of an async function, you can create a
+/// memoizer outside the function (for example as an instance field if you want
+/// to memoize the result of a method), and then wrap the function's body in a
+/// call to [runOnce].
+///
+/// This is useful for methods like `close()` and getters that need to do
+/// asynchronous work. For example:
+///
+/// ```dart
+/// class SomeResource {
+/// final _closeMemo = AsyncMemoizer();
+///
+/// Future close() => _closeMemo.runOnce(() {
+/// // ...
+/// });
+/// }
+/// ```
+class AsyncMemoizer<T> {
+ /// The future containing the method's result.
+ ///
+ /// This can be accessed at any time, and will fire once [runOnce] is called.
+ Future<T> get future => _completer.future;
+ final _completer = Completer<T>();
+
+ /// Whether [runOnce] has been called yet.
+ bool get hasRun => _completer.isCompleted;
+
+ /// Runs the function, [computation], if it hasn't been run before.
+ ///
+ /// If [runOnce] has already been called, this returns the original result.
+ Future<T> runOnce(FutureOr<T> Function() computation) {
+ if (!hasRun) _completer.complete(Future.sync(computation));
+ return future;
+ }
+}
diff --git a/pkgs/async/lib/src/byte_collector.dart b/pkgs/async/lib/src/byte_collector.dart
new file mode 100644
index 0000000..0c93761
--- /dev/null
+++ b/pkgs/async/lib/src/byte_collector.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:typed_data';
+import 'cancelable_operation.dart';
+
+/// Collects an asynchronous sequence of byte lists into a single list of bytes.
+///
+/// If the [source] stream emits an error event,
+/// the collection fails and the returned future completes with the same error.
+///
+/// If any of the input data are not valid bytes, they will be truncated to
+/// an eight-bit unsigned value in the resulting list.
+Future<Uint8List> collectBytes(Stream<List<int>> source) {
+ return _collectBytes(source, (_, result) => result);
+}
+
+/// Collects an asynchronous sequence of byte lists into a single list of bytes.
+///
+/// Returns a [CancelableOperation] that provides the result future and a way
+/// to cancel the collection early.
+///
+/// If the [source] stream emits an error event,
+/// the collection fails and the returned future completes with the same error.
+///
+/// If any of the input data are not valid bytes, they will be truncated to
+/// an eight-bit unsigned value in the resulting list.
+CancelableOperation<Uint8List> collectBytesCancelable(
+ Stream<List<int>> source) {
+ return _collectBytes(
+ source,
+ (subscription, result) => CancelableOperation.fromFuture(result,
+ onCancel: subscription.cancel));
+}
+
+/// Generalization over [collectBytes] and [collectBytesCancelable].
+///
+/// Performs all the same operations, but the final result is created
+/// by the [result] function, which has access to the stream subscription
+/// so it can cancel the operation.
+T _collectBytes<T>(Stream<List<int>> source,
+ T Function(StreamSubscription<List<int>>, Future<Uint8List>) result) {
+ var bytes = BytesBuilder(copy: false);
+ var completer = Completer<Uint8List>.sync();
+ var subscription =
+ source.listen(bytes.add, onError: completer.completeError, onDone: () {
+ completer.complete(bytes.takeBytes());
+ }, cancelOnError: true);
+ return result(subscription, completer.future);
+}
diff --git a/pkgs/async/lib/src/cancelable_operation.dart b/pkgs/async/lib/src/cancelable_operation.dart
new file mode 100644
index 0000000..2610613
--- /dev/null
+++ b/pkgs/async/lib/src/cancelable_operation.dart
@@ -0,0 +1,563 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// An asynchronous operation that can be cancelled.
+///
+/// The value of this operation is exposed as [value]. When this operation is
+/// cancelled, [value] won't complete either successfully or with an error. If
+/// [value] has already completed, cancelling the operation does nothing.
+class CancelableOperation<T> {
+ /// The completer that produced this operation.
+ ///
+ /// That completer is canceled when [cancel] is called.
+ CancelableCompleter<T> _completer;
+
+ CancelableOperation._(this._completer);
+
+ /// Creates a [CancelableOperation] with the same result as the [result]
+ /// future.
+ ///
+ /// When this operation is canceled, [onCancel] will be called and any value
+ /// or error later produced by [result] will be discarded.
+ /// If [onCancel] returns a [Future], it will be returned by [cancel].
+ ///
+ /// The [onCancel] function will be called synchronously
+ /// when the new operation is canceled, and will be called at most once.
+ ///
+ /// Calling this constructor is equivalent to creating a
+ /// [CancelableCompleter] and completing it with [result].
+ factory CancelableOperation.fromFuture(Future<T> result,
+ {FutureOr Function()? onCancel}) =>
+ (CancelableCompleter<T>(onCancel: onCancel)..complete(result)).operation;
+
+ /// Creates a [CancelableOperation] which completes to [value].
+ ///
+ /// Canceling this operation does nothing.
+ ///
+ /// Calling this constructor is equivalent to creating a
+ /// [CancelableCompleter] and completing it with [value].
+ factory CancelableOperation.fromValue(T value) =>
+ (CancelableCompleter<T>()..complete(value)).operation;
+
+ /// Creates a [CancelableOperation] wrapping [subscription].
+ ///
+ /// This overrides [StreamSubscription.onDone] and
+ /// [StreamSubscription.onError] so that the returned operation will complete
+ /// when the subscription completes or emits an error.
+ /// When this operation is canceled or when it emits an error, the
+ /// subscription will be canceled (unlike
+ /// `CancelableOperation.fromFuture(subscription.asFuture())`).
+ static CancelableOperation<void> fromSubscription(
+ StreamSubscription<void> subscription) {
+ var completer = CancelableCompleter<void>(onCancel: subscription.cancel);
+ subscription.onDone(completer.complete);
+ subscription.onError((Object error, StackTrace stackTrace) {
+ subscription.cancel().whenComplete(() {
+ completer.completeError(error, stackTrace);
+ });
+ });
+ return completer.operation;
+ }
+
+ /// Creates a [CancelableOperation] that completes with the value of the first
+ /// of [operations] to complete.
+ ///
+ /// Once any of [operations] completes, its result is forwarded to the
+ /// new [CancelableOperation] and the rest are cancelled. If the
+ /// new operation is cancelled, all the [operations] are cancelled as
+ /// well.
+ static CancelableOperation<T> race<T>(
+ Iterable<CancelableOperation<T>> operations) {
+ operations = operations.toList();
+ if (operations.isEmpty) {
+ throw ArgumentError('May not be empty', 'operations');
+ }
+
+ var done = false;
+ // Note: if one or more of the completers have already completed,
+ // they're not actually cancelled by this.
+ Future<void> cancelAll() {
+ done = true;
+ return Future.wait([
+ for (var operation in operations)
+ if (!operation.isCanceled) operation.cancel()
+ ]);
+ }
+
+ var completer = CancelableCompleter<T>(onCancel: cancelAll);
+ for (var operation in operations) {
+ operation.then((value) {
+ if (!done) cancelAll().whenComplete(() => completer.complete(value));
+ }, onError: (error, stackTrace) {
+ if (!done) {
+ cancelAll()
+ .whenComplete(() => completer.completeError(error, stackTrace));
+ }
+ }, propagateCancel: false);
+ }
+
+ return completer.operation;
+ }
+
+ /// The result of this operation, if not cancelled.
+ ///
+ /// This future will not complete if the operation is cancelled.
+ /// Use [valueOrCancellation] for a future which completes
+ /// both if the operation is cancelled and if it isn't.
+ Future<T> get value => _completer._inner?.future ?? Completer<T>().future;
+
+ /// Creates a [Stream] containing the result of this operation.
+ ///
+ /// This is like `value.asStream()`, but if a subscription to the stream is
+ /// canceled, this operation is as well.
+ Stream<T> asStream() {
+ var controller =
+ StreamController<T>(sync: true, onCancel: _completer._cancel);
+
+ _completer._inner?.future.then((value) {
+ controller.add(value);
+ controller.close();
+ }, onError: (Object error, StackTrace stackTrace) {
+ controller.addError(error, stackTrace);
+ controller.close();
+ });
+ return controller.stream;
+ }
+
+ /// Creates a [Future] that completes when this operation completes *or* when
+ /// it's cancelled.
+ ///
+ /// If this operation completes, this completes to the same result as [value].
+ /// If this operation is cancelled, the returned future waits for the future
+ /// returned by [cancel], then completes to [cancellationValue].
+ Future<T?> valueOrCancellation([T? cancellationValue]) {
+ var completer = Completer<T?>.sync();
+ value.then(completer.complete, onError: completer.completeError);
+
+ _completer._cancelCompleter?.future.then((_) {
+ completer.complete(cancellationValue);
+ }, onError: completer.completeError);
+
+ return completer.future;
+ }
+
+ /// Creates a new cancelable operation to be completed when this operation
+ /// completes normally or as an error, or is cancelled.
+ ///
+ /// If this operation completes normally the value is passed to [onValue]
+ /// and the returned operation is completed with the result.
+ ///
+ /// If this operation completes as an error, and no [onError] callback is
+ /// provided, the returned operation is completed with the same error and
+ /// stack trace.
+ /// If this operation completes as an error, and an [onError] callback is
+ /// provided, the returned operation is completed with the result.
+ ///
+ /// If this operation is canceled, and no [onCancel] callback is provided,
+ /// the returned operation is canceled.
+ /// If this operation is canceled, and an [onCancel] callback is provided,
+ /// the returned operation is completed with the result.
+ ///
+ /// At most one of [onValue], [onError], or [onCancel] will be called.
+ /// If any of [onValue], [onError], or [onCancel] throw a synchronous error,
+ /// or return a `Future` that completes as an error, the error will be
+ /// forwarded through the returned operation.
+ ///
+ /// If the returned operation is canceled before this operation completes or
+ /// is canceled, the [onValue], [onError], and [onCancel] callbacks will not
+ /// be invoked. If [propagateCancel] is `true` (the default) then this
+ /// operation is canceled as well. Pass `false` if there are multiple
+ /// listeners on this operation and canceling the [onValue], [onError], and
+ /// [onCancel] callbacks should not cancel the other listeners.
+ CancelableOperation<R> then<R>(FutureOr<R> Function(T) onValue,
+ {FutureOr<R> Function(Object, StackTrace)? onError,
+ FutureOr<R> Function()? onCancel,
+ bool propagateCancel = true}) =>
+ thenOperation<R>((value, completer) {
+ completer.complete(onValue(value));
+ },
+ onError: onError == null
+ ? null
+ : (error, stackTrace, completer) {
+ completer.complete(onError(error, stackTrace));
+ },
+ onCancel: onCancel == null
+ ? null
+ : (completer) {
+ completer.complete(onCancel());
+ },
+ propagateCancel: propagateCancel);
+
+ /// Creates a new cancelable operation to be completed when this operation
+ /// completes normally or as an error, or is cancelled.
+ ///
+ /// If this operation completes normally the value is passed to [onValue]
+ /// with a [CancelableCompleter] controlling the returned operation.
+ ///
+ /// If this operation completes as an error, and no [onError] callback is
+ /// provided, the returned operation is completed with the same error and
+ /// stack trace.
+ /// If this operation completes as an error, and an [onError] callback is
+ /// provided, the error and stack trace are passed to [onError] with a
+ /// [CancelableCompleter] controlling the returned operation.
+ ///
+ /// If this operation is canceled, and no [onCancel] callback is provided,
+ /// the returned operation is canceled.
+ /// If this operation is canceled, and an [onCancel] callback is provided,
+ /// the [onCancel] callback is called with a [CancelableCompleter] controlling
+ /// the returned operation.
+ ///
+ /// At most one of [onValue], [onError], or [onCancel] will be called.
+ /// If any of [onValue], [onError], or [onCancel] throw a synchronous error,
+ /// or return a `Future` that completes as an error, the error will be
+ /// forwarded through the returned operation.
+ ///
+ /// If the returned operation is canceled before this operation completes or
+ /// is canceled, the [onValue], [onError], and [onCancel] callbacks will not
+ /// be invoked. If [propagateCancel] is `true` (the default) then this
+ /// operation is canceled as well. Pass `false` if there are multiple
+ /// listeners on this operation and canceling the [onValue], [onError], and
+ /// [onCancel] callbacks should not cancel the other listeners.
+ CancelableOperation<R> thenOperation<R>(
+ FutureOr<void> Function(T, CancelableCompleter<R>) onValue,
+ {FutureOr<void> Function(Object, StackTrace, CancelableCompleter<R>)?
+ onError,
+ FutureOr<void> Function(CancelableCompleter<R>)? onCancel,
+ bool propagateCancel = true}) {
+ final completer = CancelableCompleter<R>(
+ onCancel: propagateCancel ? _cancelIfNotCanceled : null);
+
+ // if `_completer._inner` completes before `completer` is cancelled
+ // call `onValue` or `onError` with the result, and complete `completer`
+ // with the result of that call (unless cancelled in the meantime).
+ //
+ // If `_completer._cancelCompleter` completes (always with a value)
+ // before `completer` is cancelled, then call `onCancel` (if supplied)
+ // with that that value and complete `completer` with the result of that
+ // call (unless cancelled in the meantime).
+ //
+ // If any of the callbacks throw synchronously, the `completer` is
+ // completed with that error.
+ //
+ // If no `onCancel` is provided, and `_completer._cancelCompleter`
+ // completes before `completer` is cancelled,
+ // then cancel `cancelCompleter`. (Cancelling twice is safe.)
+
+ _completer._inner?.future.then<void>((value) async {
+ if (completer.isCanceled) return;
+ try {
+ await onValue(value, completer);
+ } catch (error, stack) {
+ completer.completeError(error, stack);
+ }
+ },
+ onError: onError == null
+ ? completer.completeError // Is ignored if already cancelled.
+ : (Object error, StackTrace stack) async {
+ if (completer.isCanceled) return;
+ try {
+ await onError(error, stack, completer);
+ } catch (error2, stack2) {
+ completer.completeErrorIfPending(
+ error2, identical(error, error2) ? stack : stack2);
+ }
+ });
+ final cancelForwarder = _CancelForwarder<R>(completer, onCancel);
+ if (_completer.isCanceled) {
+ cancelForwarder._forward();
+ } else {
+ (_completer._cancelForwarders ??= []).add(cancelForwarder);
+ }
+ return completer.operation;
+ }
+
+ /// Cancels this operation.
+ ///
+ /// If this operation [isCompleted] or [isCanceled] this call is ignored.
+ /// Returns the result of the `onCancel` callback, if one exists.
+ Future cancel() => _completer._cancel();
+
+ Future<void>? _cancelIfNotCanceled() => isCanceled ? null : cancel();
+
+ /// Whether this operation has been canceled before it completed.
+ bool get isCanceled => _completer._isCanceled;
+
+ /// Whether the result of this operation is ready.
+ ///
+ /// When ready, the [value] future is completed with the result value
+ /// or error, and this operation can no longer be cancelled.
+ /// An operation may be complete before the listeners on [value] are invoked.
+ bool get isCompleted => _completer._isCompleted;
+}
+
+/// A completer for a [CancelableOperation].
+class CancelableCompleter<T> {
+ // The cancelable completer is in one of the following states:
+ // * Initial:
+ // _inner != null
+ // _cancelCompleter != null
+ // _mayComplete: true
+ //
+ // * Async-completed: `complete` called with a future while Initial.
+ // _inner != null
+ // _cancelCompleter != null
+ // _mayComplete: false
+ //
+ // * Completed: `complete` called with a value or `completeError` called
+ // while Initial, or the future passed in Async-completed completes
+ // while AsyncCompleted.
+ // _inner != null
+ // _cancelCompleter == null
+ // _mayComplete: false
+ //
+ // * Cancelled may-complete: `_cancel` called while Initial.
+ // Allows calling `complete`/`completeError` even if it does nothing.
+ // _inner == null
+ // _cancelCompleter != null
+ // _mayComplete: true
+ //
+ // * Cancelled can't-complete: `_cancel` called while Async-completed.
+ // _inner == null
+ // _cancelCompleter != null
+ // _mayComplete: false
+
+ /// The completer for the wrapped future.
+ ///
+ /// At most one of `_inner.future` and `_cancelCompleter.future` will
+ /// ever complete.
+ /// Set to `null` when when the operation is canceled, because then
+ /// it's guaranteed that this completer will never complete.
+ Completer<T>? _inner = Completer<T>();
+
+ /// Completed when `cancel` is called.
+ ///
+ /// At most one of `_inner.future` and `_cancelCompleter.future` will
+ /// ever complete.
+ /// Set to `null` when [_inner] is completed, because then it's
+ /// guaranteed that this completer will never complete.
+ Completer<Object?>? _cancelCompleter = Completer<Object?>();
+
+ /// The callback to call if the operation is canceled.
+ final FutureOr<void> Function()? _onCancel;
+
+ /// Additional cancellations to forward during cancel.
+ ///
+ /// When a cancelable operation is chained through `then` or `thenOperation` a
+ /// cancellation on the original operation will synchronously cancel the
+ /// chained operations.
+ List<_CancelForwarder>? _cancelForwarders;
+
+ /// Whether [complete] or [completeError] may still be called.
+ ///
+ /// Set to false when calling either.
+ ///
+ /// When completing by calling [complete] with a future,
+ /// it's still possible to cancel until the result is actually
+ /// available.
+ /// You are also allowed to call [complete] or [completeError]
+ /// after the operation has been canceled, as long as you only call it once.
+ /// It just won't do anything after the operation is cancelled.
+ /// This value only guards the calls to [complete] and [completeError].
+ bool _mayComplete = true;
+
+ /// The operation controlled by this completer.
+ late final operation = CancelableOperation<T>._(this);
+
+ /// Creates a new completer for a [CancelableOperation].
+ ///
+ /// The cancelable [operation] can be completed using
+ /// [complete] or [completeError].
+ ///
+ /// The [onCancel] function is called if the [operation] is canceled,
+ /// by calling [CancelableOperation.cancel]
+ /// before the operation has completed.
+ /// If [onCancel] returns a [Future],
+ /// that future is also returned by [CancelableOperation.cancel].
+ ///
+ /// The [onCancel] function will be called at most once.
+ CancelableCompleter({FutureOr Function()? onCancel}) : _onCancel = onCancel;
+
+ /// Whether the [_inner] completer has been completed.
+ ///
+ /// At this point it's no longer possible to cancel the operation.
+ bool get _isCompleted => _cancelCompleter == null;
+
+ /// Whether the completer was canceled before the result was ready.
+ ///
+ /// At this point, it's no longer possible to complete the operation.
+ bool get _isCanceled => _inner == null;
+
+ /// Whether the [complete] or [completeError] have been called.
+ ///
+ /// Once this completer has been completed with either a result or error,
+ /// neither method may be called again.
+ ///
+ /// If [complete] was called with a [Future] argument, this completer may be
+ /// completed before it's [operation] is completed. In that case the
+ /// [operation] may still be canceled before the result is available.
+ bool get isCompleted => !_mayComplete;
+
+ /// Whether the completer was canceled before the result was ready.
+ bool get isCanceled => _isCanceled;
+
+ /// Completes [operation] with [value].
+ ///
+ /// If [value] is a [Future] the [operation] will complete
+ /// with the result of that `Future` once it is available.
+ /// In that case [isCompleted] will be `true` before the [operation]
+ /// is complete.
+ ///
+ /// If the type [T] is not nullable [value] may be not be omitted or `null`.
+ ///
+ /// This method may not be called after either [complete] or [completeError]
+ /// has been called once.
+ /// The [isCompleted] is true when either of these methods have been called.
+ void complete([FutureOr<T>? value]) {
+ if (!_mayComplete) throw StateError('Operation already completed');
+ _mayComplete = false;
+
+ if (value is! Future<T>) {
+ _completeNow()?.complete(value);
+ return;
+ }
+
+ if (_inner == null) {
+ // Make sure errors from [value] aren't top-leveled.
+ value.ignore();
+ return;
+ }
+
+ value.then((result) {
+ _completeNow()?.complete(result);
+ }, onError: (Object error, StackTrace stackTrace) {
+ _completeNow()?.completeError(error, stackTrace);
+ });
+ }
+
+ /// Makes this [CancelableCompleter.operation] complete with the same result
+ /// as [result].
+ ///
+ /// If [propagateCancel] is `true` (the default), and the [operation] of this
+ /// completer is canceled before [result] completes, then [result] is also
+ /// canceled.
+ void completeOperation(CancelableOperation<T> result,
+ {bool propagateCancel = true}) {
+ if (!_mayComplete) throw StateError('Already completed');
+ _mayComplete = false;
+ if (isCanceled) {
+ if (propagateCancel) result.cancel();
+ result.value.ignore();
+ return;
+ }
+ result.then<void>((value) {
+ _inner?.complete(
+ value); // _inner is set to null if this.operation is cancelled.
+ }, onError: (error, stack) {
+ _inner?.completeError(error, stack);
+ }, onCancel: () {
+ operation.cancel();
+ });
+ if (propagateCancel) {
+ _cancelCompleter?.future.whenComplete(result.cancel);
+ }
+ }
+
+ /// Completer to use for completing with a result.
+ ///
+ /// Returns `null` if it's not possible to complete any more.
+ /// Sets [_cancelCompleter] to `null` if returning non-`null`.
+ Completer<T>? _completeNow() {
+ var inner = _inner;
+ if (inner == null) return null;
+ _cancelCompleter = null;
+ return inner;
+ }
+
+ /// Completes [operation] with [error] and [stackTrace].
+ ///
+ /// This method may not be called after either [complete] or [completeError]
+ /// has been called once.
+ /// The [isCompleted] is true when either of these methods have been called.
+ void completeError(Object error, [StackTrace? stackTrace]) {
+ if (!_mayComplete) throw StateError('Operation already completed');
+ _mayComplete = false;
+ _completeNow()?.completeError(error, stackTrace);
+ }
+
+ /// Cancels the operation.
+ ///
+ /// If the operation has already completed, prior to being cancelled,
+ /// this method does nothing.
+ /// If the operation has already been cancelled, this method returns
+ /// the same result as the first call to `_cancel`.
+ ///
+ /// The result of the operation may only be available some time after
+ /// the completer has been completed (using [complete] or [completeError],
+ /// which sets [isCompleted] to true) if completed with a [Future].
+ /// The completer can be cancelled until the result becomes available,
+ /// even if [isCompleted] is true.
+ Future<void> _cancel() {
+ var cancelCompleter = _cancelCompleter;
+ if (cancelCompleter == null) return Future.value(null);
+
+ if (_inner != null) {
+ _inner = null;
+ cancelCompleter.complete(_invokeCancelCallbacks());
+ }
+ return cancelCompleter.future;
+ }
+
+ /// Invoke [_onCancel] and forward to other completers in [_cancelForwarders].
+ ///
+ /// Returns the same value as [_onCancel]. Legacy uses may return a value
+ /// despite the signature having `void` return.
+ Future<Object?> _invokeCancelCallbacks() async {
+ final FutureOr<Object?> toReturn = _onCancel?.call();
+ final isFuture = toReturn is Future;
+ final cancelFutures = <Future<Object?>>[
+ if (isFuture) toReturn,
+ ...?_cancelForwarders?.map(_forward).nonNulls
+ ];
+ final results = (isFuture && cancelFutures.length == 1)
+ ? [await toReturn]
+ : cancelFutures.isNotEmpty
+ ? await Future.wait(cancelFutures)
+ : const [];
+ return isFuture ? results.first : toReturn;
+ }
+}
+
+class _CancelForwarder<T> {
+ final CancelableCompleter<T> completer;
+ final FutureOr<void> Function(CancelableCompleter<T>)? onCancel;
+ _CancelForwarder(this.completer, this.onCancel);
+
+ Future<void>? _forward() {
+ if (completer.isCanceled) return null;
+ final onCancel = this.onCancel;
+ if (onCancel == null) return completer._cancel();
+ try {
+ final result = onCancel(completer);
+ if (result is Future) {
+ return result.catchError(completer.completeErrorIfPending);
+ }
+ } catch (error, stack) {
+ completer.completeErrorIfPending(error, stack);
+ }
+ return null;
+ }
+}
+
+// Helper function to avoid a closure for `List<_CancelForwarder>.map`.
+Future<void>? _forward(_CancelForwarder<Object?> forwarder) =>
+ forwarder._forward();
+
+extension on CancelableCompleter {
+ void completeErrorIfPending(Object error, StackTrace stackTrace) {
+ if (isCompleted) return;
+ completeError(error, stackTrace);
+ }
+}
diff --git a/pkgs/async/lib/src/chunked_stream_reader.dart b/pkgs/async/lib/src/chunked_stream_reader.dart
new file mode 100644
index 0000000..1d92216
--- /dev/null
+++ b/pkgs/async/lib/src/chunked_stream_reader.dart
@@ -0,0 +1,216 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'byte_collector.dart' show collectBytes;
+
+/// Utility class for reading elements from a _chunked stream_.
+///
+/// A _chunked stream_ is a stream where each event is a chunk of elements.
+/// Byte-streams with the type `Stream<List<int>>` is common of example of this.
+/// As illustrated in the example below, this utility class makes it easy to
+/// read a _chunked stream_ using custom chunk sizes and sub-stream sizes,
+/// without managing partially read chunks.
+///
+/// ```dart
+/// final r = ChunkedStreamReader(File('myfile.txt').openRead());
+/// try {
+/// // Read the first 4 bytes
+/// final firstBytes = await r.readChunk(4);
+/// if (firstBytes.length < 4) {
+/// throw Exception('myfile.txt has less than 4 bytes');
+/// }
+///
+/// // Read next 8 kilobytes as a substream
+/// Stream<List<int>> substream = r.readStream(8 * 1024);
+///
+/// ...
+/// } finally {
+/// // We always cancel the ChunkedStreamReader, this ensures the underlying
+/// // stream is cancelled.
+/// r.cancel();
+/// }
+/// ```
+///
+/// The read-operations [readChunk] and [readStream] must not be invoked until
+/// the future from a previous call has completed.
+class ChunkedStreamReader<T> {
+ /// Iterator over underlying stream.
+ ///
+ /// The reader requests data from this input whenever requests on the
+ /// reader cannot be fulfilled with the already fetched data.
+ final StreamIterator<List<T>> _input;
+
+ /// Sentinel value used for [_buffer] when we have no value.
+ final List<T> _emptyList = const [];
+
+ /// Last partially consumed chunk received from [_input].
+ ///
+ /// Elements up to [_offset] have already been consumed and should not be
+ /// consumed again.
+ List<T> _buffer = <T>[];
+
+ /// Offset into [_buffer] after data which have already been emitted.
+ ///
+ /// The offset is between `0` and `_buffer.length`, both inclusive.
+ /// The data in [_buffer] from [_offset] and forward have not yet been
+ /// emitted by the chunked stream reader, the data before [_offset] has.
+ int _offset = 0;
+
+ /// Whether a read request is currently being processed.
+ ///
+ /// Is `true` while a request is in progress.
+ /// While a read request, like [readChunk] or [readStream], is being
+ /// processed, no new requests can be made.
+ /// New read attempts will throw instead.
+ bool _reading = false;
+
+ factory ChunkedStreamReader(Stream<List<T>> stream) =>
+ ChunkedStreamReader._(StreamIterator(stream));
+
+ ChunkedStreamReader._(this._input);
+
+ /// Read next [size] elements from _chunked stream_, buffering to create a
+ /// chunk with [size] elements.
+ ///
+ /// This will read _chunks_ from the underlying _chunked stream_ until [size]
+ /// elements have been buffered, or end-of-stream, then it returns the first
+ /// [size] buffered elements.
+ ///
+ /// If end-of-stream is encountered before [size] elements is read, this
+ /// returns a list with fewer than [size] elements (indicating end-of-stream).
+ ///
+ /// If the underlying stream throws, the stream is cancelled, the exception is
+ /// propogated and further read operations will fail.
+ ///
+ /// Throws, if another read operation is on-going.
+ Future<List<T>> readChunk(int size) async {
+ final result = <T>[];
+ await for (final chunk in readStream(size)) {
+ result.addAll(chunk);
+ }
+ return result;
+ }
+
+ /// Read next [size] elements from _chunked stream_ as a sub-stream.
+ ///
+ /// This will pass-through _chunks_ from the underlying _chunked stream_ until
+ /// [size] elements have been returned, or end-of-stream has been encountered.
+ ///
+ /// If end-of-stream is encountered before [size] elements is read, this
+ /// returns a list with fewer than [size] elements (indicating end-of-stream).
+ ///
+ /// If the underlying stream throws, the stream is cancelled, the exception is
+ /// propogated and further read operations will fail.
+ ///
+ /// If the sub-stream returned from [readStream] is cancelled the remaining
+ /// unread elements up-to [size] are drained, allowing subsequent
+ /// read-operations to proceed after cancellation.
+ ///
+ /// Throws, if another read-operation is on-going.
+ Stream<List<T>> readStream(int size) {
+ RangeError.checkNotNegative(size, 'size');
+ if (_reading) {
+ throw StateError('Concurrent read operations are not allowed!');
+ }
+ _reading = true;
+
+ Stream<List<T>> substream() async* {
+ // While we have data to read
+ while (size > 0) {
+ // Read something into the buffer, if buffer has been consumed.
+ assert(_offset <= _buffer.length);
+ if (_offset == _buffer.length) {
+ if (!(await _input.moveNext())) {
+ // Don't attempt to read more data, as there is no more data.
+ size = 0;
+ _reading = false;
+ break;
+ }
+ _buffer = _input.current;
+ _offset = 0;
+ }
+
+ final remainingBuffer = _buffer.length - _offset;
+ if (remainingBuffer > 0) {
+ if (remainingBuffer >= size) {
+ List<T> output;
+ if (_buffer is Uint8List) {
+ output = Uint8List.sublistView(
+ _buffer as Uint8List, _offset, _offset + size) as List<T>;
+ } else {
+ output = _buffer.sublist(_offset, _offset + size);
+ }
+ _offset += size;
+ size = 0;
+ yield output;
+ _reading = false;
+ break;
+ }
+
+ final output = _offset == 0 ? _buffer : _buffer.sublist(_offset);
+ size -= remainingBuffer;
+ _buffer = _emptyList;
+ _offset = 0;
+ yield output;
+ }
+ }
+ }
+
+ final c = StreamController<List<T>>();
+ c.onListen = () => c.addStream(substream()).whenComplete(c.close);
+ c.onCancel = () async {
+ while (size > 0) {
+ assert(_offset <= _buffer.length);
+ if (_buffer.length == _offset) {
+ if (!await _input.moveNext()) {
+ size = 0; // no more data
+ break;
+ }
+ _buffer = _input.current;
+ _offset = 0;
+ }
+
+ final remainingBuffer = _buffer.length - _offset;
+ if (remainingBuffer >= size) {
+ _offset += size;
+ size = 0;
+ break;
+ }
+
+ size -= remainingBuffer;
+ _buffer = _emptyList;
+ _offset = 0;
+ }
+ _reading = false;
+ };
+
+ return c.stream;
+ }
+
+ /// Cancel the underlying _chunked stream_.
+ ///
+ /// If a future from [readChunk] or [readStream] is still pending then
+ /// [cancel] behaves as if the underlying stream ended early. That is a future
+ /// from [readChunk] may return a partial chunk smaller than the request size.
+ ///
+ /// It is always safe to call [cancel], even if the underlying stream was read
+ /// to completion.
+ ///
+ /// It can be a good idea to call [cancel] in a `finally`-block when done
+ /// using the [ChunkedStreamReader], this mitigates risk of leaking resources.
+ Future<void> cancel() async => await _input.cancel();
+}
+
+/// Extensions for using [ChunkedStreamReader] with byte-streams.
+extension ChunkedStreamReaderByteStreamExt on ChunkedStreamReader<int> {
+ /// Read bytes into a [Uint8List].
+ ///
+ /// This does the same as [readChunk], except it uses [collectBytes] to create
+ /// a [Uint8List], which offers better performance.
+ Future<Uint8List> readBytes(int size) async =>
+ await collectBytes(readStream(size));
+}
diff --git a/pkgs/async/lib/src/delegate/event_sink.dart b/pkgs/async/lib/src/delegate/event_sink.dart
new file mode 100644
index 0000000..34c119b
--- /dev/null
+++ b/pkgs/async/lib/src/delegate/event_sink.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// Simple delegating wrapper around an [EventSink].
+///
+/// Subclasses can override individual methods, or use this to expose only the
+/// [EventSink] methods of a subclass.
+class DelegatingEventSink<T> implements EventSink<T> {
+ final EventSink _sink;
+
+ /// Create a delegating sink forwarding calls to [sink].
+ DelegatingEventSink(EventSink<T> sink) : _sink = sink;
+
+ DelegatingEventSink._(this._sink);
+
+ /// Creates a wrapper that coerces the type of [sink].
+ ///
+ /// Unlike [DelegatingEventSink.new], this only requires its argument to be an
+ /// instance of `EventSink`, not `EventSink<T>`. This means that calls to
+ /// [add] may throw a [TypeError] if the argument type doesn't match the
+ /// reified type of [sink].
+ @Deprecated(
+ 'Use StreamController<T>(sync: true)..stream.cast<S>().pipe(sink)')
+ static EventSink<T> typed<T>(EventSink sink) =>
+ sink is EventSink<T> ? sink : DelegatingEventSink._(sink);
+
+ @override
+ void add(T data) {
+ _sink.add(data);
+ }
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ _sink.addError(error, stackTrace);
+ }
+
+ @override
+ void close() {
+ _sink.close();
+ }
+}
diff --git a/pkgs/async/lib/src/delegate/future.dart b/pkgs/async/lib/src/delegate/future.dart
new file mode 100644
index 0000000..2179de6
--- /dev/null
+++ b/pkgs/async/lib/src/delegate/future.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// A wrapper that forwards calls to a [Future].
+class DelegatingFuture<T> implements Future<T> {
+ /// The wrapped [Future].
+ final Future<T> _future;
+
+ DelegatingFuture(this._future);
+
+ /// Creates a wrapper which throws if [future]'s value isn't an instance of
+ /// `T`.
+ ///
+ /// This soundly converts a [Future] to a `Future<T>`, regardless of its
+ /// original generic type, by asserting that its value is an instance of `T`
+ /// whenever it's provided. If it's not, the future throws a [TypeError].
+ @Deprecated('Use future.then((v) => v as T) instead.')
+ static Future<T> typed<T>(Future future) =>
+ future is Future<T> ? future : future.then((v) => v as T);
+
+ @override
+ Stream<T> asStream() => _future.asStream();
+
+ @override
+ Future<T> catchError(Function onError, {bool Function(Object error)? test}) =>
+ _future.catchError(onError, test: test);
+
+ @override
+ Future<S> then<S>(FutureOr<S> Function(T) onValue, {Function? onError}) =>
+ _future.then(onValue, onError: onError);
+
+ @override
+ Future<T> whenComplete(FutureOr Function() action) =>
+ _future.whenComplete(action);
+
+ @override
+ Future<T> timeout(Duration timeLimit, {FutureOr<T> Function()? onTimeout}) =>
+ _future.timeout(timeLimit, onTimeout: onTimeout);
+}
diff --git a/pkgs/async/lib/src/delegate/sink.dart b/pkgs/async/lib/src/delegate/sink.dart
new file mode 100644
index 0000000..a1954f0
--- /dev/null
+++ b/pkgs/async/lib/src/delegate/sink.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Simple delegating wrapper around a [Sink].
+///
+/// Subclasses can override individual methods, or use this to expose only the
+/// [Sink] methods of a subclass.
+class DelegatingSink<T> implements Sink<T> {
+ final Sink _sink;
+
+ /// Create a delegating sink forwarding calls to [sink].
+ DelegatingSink(Sink<T> sink) : _sink = sink;
+
+ DelegatingSink._(this._sink);
+
+ /// Creates a wrapper that coerces the type of [sink].
+ ///
+ /// Unlike [DelegatingSink.new], this only requires its argument to be an
+ /// instance of `Sink`, not `Sink<T>`. This means that calls to [add] may
+ /// throw a [TypeError] if the argument type doesn't match the reified type of
+ /// [sink].
+ @Deprecated(
+ 'Use StreamController<T>(sync: true)..stream.cast<S>().pipe(sink)')
+ static Sink<T> typed<T>(Sink sink) =>
+ sink is Sink<T> ? sink : DelegatingSink._(sink);
+
+ @override
+ void add(T data) {
+ _sink.add(data);
+ }
+
+ @override
+ void close() {
+ _sink.close();
+ }
+}
diff --git a/pkgs/async/lib/src/delegate/stream.dart b/pkgs/async/lib/src/delegate/stream.dart
new file mode 100644
index 0000000..68992d5
--- /dev/null
+++ b/pkgs/async/lib/src/delegate/stream.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// Simple delegating wrapper around a [Stream].
+///
+/// Subclasses can override individual methods, or use this to expose only the
+/// [Stream] methods of a subclass.
+///
+/// Note that this is identical to [StreamView] in `dart:async`. It's provided
+/// under this name for consistency with other `Delegating*` classes.
+class DelegatingStream<T> extends StreamView<T> {
+ DelegatingStream(super.stream);
+
+ /// Creates a wrapper which throws if [stream]'s events aren't instances of
+ /// `T`.
+ ///
+ /// This soundly converts a [Stream] to a `Stream<T>`, regardless of its
+ /// original generic type, by asserting that its events are instances of `T`
+ /// whenever they're provided. If they're not, the stream throws a
+ /// [TypeError].
+ @Deprecated('Use stream.cast instead')
+ static Stream<T> typed<T>(Stream stream) => stream.cast();
+}
diff --git a/pkgs/async/lib/src/delegate/stream_consumer.dart b/pkgs/async/lib/src/delegate/stream_consumer.dart
new file mode 100644
index 0000000..c911c41
--- /dev/null
+++ b/pkgs/async/lib/src/delegate/stream_consumer.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// Simple delegating wrapper around a [StreamConsumer].
+///
+/// Subclasses can override individual methods, or use this to expose only the
+/// [StreamConsumer] methods of a subclass.
+class DelegatingStreamConsumer<T> implements StreamConsumer<T> {
+ final StreamConsumer _consumer;
+
+ /// Create a delegating consumer forwarding calls to [consumer].
+ DelegatingStreamConsumer(StreamConsumer<T> consumer) : _consumer = consumer;
+
+ DelegatingStreamConsumer._(this._consumer);
+
+ /// Creates a wrapper that coerces the type of [consumer].
+ ///
+ /// Unlike [StreamConsumer.new], this only requires its argument to be an
+ /// instance of `StreamConsumer`, not `StreamConsumer<T>`. This means that
+ /// calls to [addStream] may throw a [TypeError] if the argument type doesn't
+ /// match the reified type of [consumer].
+ @Deprecated(
+ 'Use StreamController<T>(sync: true)..stream.cast<S>().pipe(sink)')
+ static StreamConsumer<T> typed<T>(StreamConsumer consumer) =>
+ consumer is StreamConsumer<T>
+ ? consumer
+ : DelegatingStreamConsumer._(consumer);
+
+ @override
+ Future addStream(Stream<T> stream) => _consumer.addStream(stream);
+
+ @override
+ Future close() => _consumer.close();
+}
diff --git a/pkgs/async/lib/src/delegate/stream_sink.dart b/pkgs/async/lib/src/delegate/stream_sink.dart
new file mode 100644
index 0000000..e6edd2f
--- /dev/null
+++ b/pkgs/async/lib/src/delegate/stream_sink.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// Simple delegating wrapper around a [StreamSink].
+///
+/// Subclasses can override individual methods, or use this to expose only the
+/// [StreamSink] methods of a subclass.
+class DelegatingStreamSink<T> implements StreamSink<T> {
+ final StreamSink _sink;
+
+ @override
+ Future get done => _sink.done;
+
+ /// Create delegating sink forwarding calls to [sink].
+ DelegatingStreamSink(StreamSink<T> sink) : _sink = sink;
+
+ DelegatingStreamSink._(this._sink);
+
+ /// Creates a wrapper that coerces the type of [sink].
+ ///
+ /// Unlike [StreamSink.new], this only requires its argument to be an instance
+ /// of `StreamSink`, not `StreamSink<T>`. This means that calls to [add] may
+ /// throw a [TypeError] if the argument type doesn't match the reified type of
+ /// [sink].
+ @Deprecated(
+ 'Use StreamController<T>(sync: true)..stream.cast<S>().pipe(sink)')
+ static StreamSink<T> typed<T>(StreamSink sink) =>
+ sink is StreamSink<T> ? sink : DelegatingStreamSink._(sink);
+
+ @override
+ void add(T data) {
+ _sink.add(data);
+ }
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ _sink.addError(error, stackTrace);
+ }
+
+ @override
+ Future addStream(Stream<T> stream) => _sink.addStream(stream);
+
+ @override
+ Future close() => _sink.close();
+}
diff --git a/pkgs/async/lib/src/delegate/stream_subscription.dart b/pkgs/async/lib/src/delegate/stream_subscription.dart
new file mode 100644
index 0000000..581404a
--- /dev/null
+++ b/pkgs/async/lib/src/delegate/stream_subscription.dart
@@ -0,0 +1,66 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import '../typed/stream_subscription.dart';
+
+/// Simple delegating wrapper around a [StreamSubscription].
+///
+/// Subclasses can override individual methods.
+class DelegatingStreamSubscription<T> implements StreamSubscription<T> {
+ final StreamSubscription<T> _source;
+
+ /// Create delegating subscription forwarding calls to [sourceSubscription].
+ DelegatingStreamSubscription(StreamSubscription<T> sourceSubscription)
+ : _source = sourceSubscription;
+
+ /// Creates a wrapper which throws if [subscription]'s events aren't instances
+ /// of `T`.
+ ///
+ /// This soundly converts a [StreamSubscription] to a `StreamSubscription<T>`,
+ /// regardless of its original generic type, by asserting that its events are
+ /// instances of `T` whenever they're provided. If they're not, the
+ /// subscription throws a [TypeError].
+ @Deprecated('Use Stream.cast instead')
+ // TODO - Remove `TypeSafeStreamSubscription` and tests when removing this.
+ static StreamSubscription<T> typed<T>(StreamSubscription subscription) =>
+ subscription is StreamSubscription<T>
+ ? subscription
+ : TypeSafeStreamSubscription<T>(subscription);
+
+ @override
+ void onData(void Function(T)? handleData) {
+ _source.onData(handleData);
+ }
+
+ @override
+ void onError(Function? handleError) {
+ _source.onError(handleError);
+ }
+
+ @override
+ void onDone(void Function()? handleDone) {
+ _source.onDone(handleDone);
+ }
+
+ @override
+ void pause([Future? resumeFuture]) {
+ _source.pause(resumeFuture);
+ }
+
+ @override
+ void resume() {
+ _source.resume();
+ }
+
+ @override
+ Future cancel() => _source.cancel();
+
+ @override
+ Future<E> asFuture<E>([E? futureValue]) => _source.asFuture(futureValue);
+
+ @override
+ bool get isPaused => _source.isPaused;
+}
diff --git a/pkgs/async/lib/src/future_group.dart b/pkgs/async/lib/src/future_group.dart
new file mode 100644
index 0000000..daf985d
--- /dev/null
+++ b/pkgs/async/lib/src/future_group.dart
@@ -0,0 +1,107 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// A collection of futures waits until all added [Future]s complete.
+///
+/// Futures are added to the group with [add]. Once you're finished adding
+/// futures, signal that by calling [close]. Then, once all added futures have
+/// completed, [future] will complete with a list of values from the futures in
+/// the group, in the order they were added.
+///
+/// If any added future completes with an error, [future] will emit that error
+/// and the group will be closed, regardless of the state of other futures in
+/// the group.
+///
+/// This is similar to [Future.wait] with `eagerError` set to `true`, except
+/// that a [FutureGroup] can have futures added gradually over time rather than
+/// needing them all at once.
+class FutureGroup<T> implements Sink<Future<T>> {
+ /// The number of futures that have yet to complete.
+ var _pending = 0;
+
+ /// Whether the group is closed, meaning that no more futures may be added.
+ bool get isClosed => _closed;
+
+ var _closed = false;
+
+ /// The future that fires once [close] has been called and all futures in the
+ /// group have completed.
+ ///
+ /// This will also complete with an error if any of the futures in the group
+ /// fails, regardless of whether [close] was called.
+ Future<List<T>> get future => _completer.future;
+ final _completer = Completer<List<T>>();
+
+ /// Whether this group contains no futures.
+ ///
+ /// A [FutureGroup] is idle when it contains no futures, which is the case for
+ /// a newly created group or one where all added futures have been removed or
+ /// completed.
+ bool get isIdle => _pending == 0;
+
+ /// A broadcast stream that emits an event whenever this group becomes idle.
+ ///
+ /// A [FutureGroup] is idle when it contains no futures, which is the case for
+ /// a newly created group or one where all added futures have been removed or
+ /// completed.
+ ///
+ /// This stream will close when this group is idle *and* [close] has been
+ /// called.
+ ///
+ /// Events are delivered asynchronously, so it's possible for the group to
+ /// become active again before the event is delivered.
+ Stream get onIdle =>
+ (_onIdleController ??= StreamController.broadcast(sync: true)).stream;
+
+ StreamController? _onIdleController;
+
+ /// The values emitted by the futures that have been added to the group, in
+ /// the order they were added.
+ ///
+ /// The slots for futures that haven't completed yet are `null`.
+ final _values = <T?>[];
+
+ /// Wait for [task] to complete.
+ @override
+ void add(Future<T> task) {
+ if (_closed) throw StateError('The FutureGroup is closed.');
+
+ // Ensure that future values are put into [values] in the same order they're
+ // added to the group by pre-allocating a slot for them and recording its
+ // index.
+ var index = _values.length;
+ _values.add(null);
+
+ _pending++;
+ task.then((value) {
+ if (_completer.isCompleted) return null;
+
+ _pending--;
+ _values[index] = value;
+
+ if (_pending != 0) return null;
+ var onIdleController = _onIdleController;
+ if (onIdleController != null) onIdleController.add(null);
+
+ if (!_closed) return null;
+ if (onIdleController != null) onIdleController.close();
+ _completer.complete(_values.whereType<T>().toList());
+ }).catchError((Object error, StackTrace stackTrace) {
+ if (_completer.isCompleted) return null;
+ _completer.completeError(error, stackTrace);
+ });
+ }
+
+ /// Signals to the group that the caller is done adding futures, and so
+ /// [future] should fire once all added futures have completed.
+ @override
+ void close() {
+ _closed = true;
+ if (_pending != 0) return;
+ if (_completer.isCompleted) return;
+ _completer.complete(_values.whereType<T>().toList());
+ }
+}
diff --git a/pkgs/async/lib/src/lazy_stream.dart b/pkgs/async/lib/src/lazy_stream.dart
new file mode 100644
index 0000000..e0facaa
--- /dev/null
+++ b/pkgs/async/lib/src/lazy_stream.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'stream_completer.dart';
+
+/// A [Stream] wrapper that forwards to another [Stream] that's initialized
+/// lazily.
+///
+/// This class allows a concrete `Stream` to be created only once it has a
+/// listener. It's useful to wrapping APIs that do expensive computation to
+/// produce a `Stream`.
+class LazyStream<T> extends Stream<T> {
+ /// The callback that's called to create the inner stream.
+ FutureOr<Stream<T>> Function()? _callback;
+
+ /// Creates a single-subscription `Stream` that calls [callback] when it gets
+ /// a listener and forwards to the returned stream.
+ LazyStream(FutureOr<Stream<T>> Function() callback) : _callback = callback {
+ // Explicitly check for null because we null out [_callback] internally.
+ if (_callback == null) throw ArgumentError.notNull('callback');
+ }
+
+ @override
+ StreamSubscription<T> listen(void Function(T)? onData,
+ {Function? onError, void Function()? onDone, bool? cancelOnError}) {
+ var callback = _callback;
+ if (callback == null) {
+ throw StateError('Stream has already been listened to.');
+ }
+
+ // Null out the callback before we invoke it to ensure that even while
+ // running it, this can't be called twice.
+ _callback = null;
+ var result = callback();
+
+ Stream<T> stream;
+ if (result is Future<Stream<T>>) {
+ stream = StreamCompleter.fromFuture(result);
+ } else {
+ stream = result;
+ }
+
+ return stream.listen(onData,
+ onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+ }
+}
diff --git a/pkgs/async/lib/src/null_stream_sink.dart b/pkgs/async/lib/src/null_stream_sink.dart
new file mode 100644
index 0000000..4d9bad0
--- /dev/null
+++ b/pkgs/async/lib/src/null_stream_sink.dart
@@ -0,0 +1,93 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// A [StreamSink] that discards all events.
+///
+/// The sink silently drops events until [close] is called, at which point it
+/// throws [StateError]s when events are added. This is the same behavior as a
+/// sink whose remote end has closed, such as when a `WebSocket` connection has
+/// been closed.
+///
+/// This can be used when a sink is needed but no events are actually intended
+/// to be added. The [NullStreamSink.error] constructor can be used to
+/// represent errors when creating a sink, since [StreamSink.done] exposes sink
+/// errors. For example:
+///
+/// ```dart
+/// StreamSink<List<int>> openForWrite(String filename) {
+/// try {
+/// return RandomAccessSink(File(filename).openSync());
+/// } on IOException catch (error, stackTrace) {
+/// return NullStreamSink.error(error, stackTrace);
+/// }
+/// }
+/// ```
+class NullStreamSink<T> implements StreamSink<T> {
+ @override
+ final Future done;
+
+ /// Whether the sink has been closed.
+ var _closed = false;
+
+ /// Whether an [addStream] call is pending.
+ ///
+ /// We don't actually add any events from streams, but it does return the
+ /// [StreamSubscription.cancel] future so to be [StreamSink]-complaint we
+ /// reject events until that completes.
+ var _addingStream = false;
+
+ /// Creates a null sink.
+ ///
+ /// If [done] is passed, it's used as the [StreamSink.done] future. Otherwise,
+ /// a completed future is used.
+ NullStreamSink({Future? done}) : done = done ?? Future.value();
+
+ /// Creates a null sink whose [done] future emits [error].
+ ///
+ /// Note that this error will not be considered uncaught.
+ NullStreamSink.error(Object error, [StackTrace? stackTrace])
+ : done = Future.error(error, stackTrace)
+ // Don't top-level the error. This gives the user a change to call
+ // [close] or [done], and matches the behavior of a remote endpoint
+ // experiencing an error.
+ ..catchError((_) {});
+
+ @override
+ void add(T data) {
+ _checkEventAllowed();
+ }
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ _checkEventAllowed();
+ }
+
+ @override
+ Future addStream(Stream<T> stream) {
+ _checkEventAllowed();
+
+ _addingStream = true;
+ var future = stream.listen(null).cancel();
+ return future.whenComplete(() {
+ _addingStream = false;
+ });
+ }
+
+ /// Throws a [StateError] if [close] has been called or an [addStream] call is
+ /// pending.
+ void _checkEventAllowed() {
+ if (_closed) throw StateError('Cannot add to a closed sink.');
+ if (_addingStream) {
+ throw StateError('Cannot add to a sink while adding a stream.');
+ }
+ }
+
+ @override
+ Future close() {
+ _closed = true;
+ return done;
+ }
+}
diff --git a/pkgs/async/lib/src/restartable_timer.dart b/pkgs/async/lib/src/restartable_timer.dart
new file mode 100644
index 0000000..1cff458
--- /dev/null
+++ b/pkgs/async/lib/src/restartable_timer.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// A non-periodic timer that can be restarted any number of times.
+///
+/// Once restarted (via [reset]), the timer counts down from its original
+/// duration again.
+class RestartableTimer implements Timer {
+ /// The duration of the timer.
+ final Duration _duration;
+
+ /// The callback to call when the timer fires.
+ final ZoneCallback _callback;
+
+ /// The timer for the current or most recent countdown.
+ ///
+ /// This timer is canceled and overwritten every time this [RestartableTimer]
+ /// is reset.
+ Timer _timer;
+
+ /// Creates a new timer.
+ ///
+ /// The [_callback] function is invoked after the given [_duration]. Unlike a
+ /// normal non-periodic [Timer], [_callback] may be called more than once.
+ RestartableTimer(this._duration, this._callback)
+ : _timer = Timer(_duration, _callback);
+
+ @override
+ bool get isActive => _timer.isActive;
+
+ /// Restarts the timer so that it counts down from its original duration
+ /// again.
+ ///
+ /// This restarts the timer even if it has already fired or has been canceled.
+ void reset() {
+ _timer.cancel();
+ _timer = Timer(_duration, _callback);
+ }
+
+ @override
+ void cancel() {
+ _timer.cancel();
+ }
+
+ /// The number of durations preceding the most recent timer event on the most
+ /// recent countdown.
+ ///
+ /// Calls to [reset] will also reset the tick so subsequent tick values may
+ /// not be strictly larger than previous values.
+ @override
+ int get tick => _timer.tick;
+}
diff --git a/pkgs/async/lib/src/result/capture_sink.dart b/pkgs/async/lib/src/result/capture_sink.dart
new file mode 100644
index 0000000..562f5f9
--- /dev/null
+++ b/pkgs/async/lib/src/result/capture_sink.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'result.dart';
+
+/// Used by [Result.captureSink].
+class CaptureSink<T> implements EventSink<T> {
+ final EventSink<Result<T>> _sink;
+
+ CaptureSink(EventSink<Result<T>> sink) : _sink = sink;
+
+ @override
+ void add(T value) {
+ _sink.add(Result<T>.value(value));
+ }
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ _sink.add(Result.error(error, stackTrace));
+ }
+
+ @override
+ void close() {
+ _sink.close();
+ }
+}
diff --git a/pkgs/async/lib/src/result/capture_transformer.dart b/pkgs/async/lib/src/result/capture_transformer.dart
new file mode 100644
index 0000000..39aaef9
--- /dev/null
+++ b/pkgs/async/lib/src/result/capture_transformer.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'capture_sink.dart';
+import 'result.dart';
+
+/// A stream transformer that captures a stream of events into [Result]s.
+///
+/// The result of the transformation is a stream of [Result] values and no
+/// error events. Exposed by [Result.captureStream].
+class CaptureStreamTransformer<T> extends StreamTransformerBase<T, Result<T>> {
+ const CaptureStreamTransformer();
+
+ @override
+ Stream<Result<T>> bind(Stream<T> source) =>
+ Stream<Result<T>>.eventTransformed(source, CaptureSink<T>.new);
+}
diff --git a/pkgs/async/lib/src/result/error.dart b/pkgs/async/lib/src/result/error.dart
new file mode 100644
index 0000000..48f71b1
--- /dev/null
+++ b/pkgs/async/lib/src/result/error.dart
@@ -0,0 +1,66 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'result.dart';
+import 'value.dart';
+
+/// A result representing a thrown error.
+class ErrorResult implements Result<Never> {
+ /// The error object that was thrown.
+ final Object error;
+
+ /// The stack trace corresponding to where [error] was thrown.
+ final StackTrace stackTrace;
+
+ @override
+ bool get isValue => false;
+ @override
+ bool get isError => true;
+ @override
+ ValueResult<Never>? get asValue => null;
+ @override
+ ErrorResult get asError => this;
+
+ ErrorResult(this.error, [StackTrace? stackTrace])
+ : stackTrace = stackTrace ?? AsyncError.defaultStackTrace(error);
+
+ @override
+ void complete(Completer completer) {
+ completer.completeError(error, stackTrace);
+ }
+
+ @override
+ void addTo(EventSink sink) {
+ sink.addError(error, stackTrace);
+ }
+
+ @override
+ Future<Never> get asFuture => Future<Never>.error(error, stackTrace);
+
+ /// Calls an error handler with the error and stacktrace.
+ ///
+ /// An async error handler function is either a function expecting two
+ /// arguments, which will be called with the error and the stack trace, or it
+ /// has to be a function expecting only one argument, which will be called
+ /// with only the error.
+ void handle(Function errorHandler) {
+ if (errorHandler is ZoneBinaryCallback) {
+ errorHandler(error, stackTrace);
+ } else {
+ (errorHandler as ZoneUnaryCallback)(error);
+ }
+ }
+
+ @override
+ int get hashCode => error.hashCode ^ stackTrace.hashCode ^ 0x1d61823f;
+
+ /// This is equal only to an error result with equal [error] and [stackTrace].
+ @override
+ bool operator ==(Object other) =>
+ other is ErrorResult &&
+ error == other.error &&
+ stackTrace == other.stackTrace;
+}
diff --git a/pkgs/async/lib/src/result/future.dart b/pkgs/async/lib/src/result/future.dart
new file mode 100644
index 0000000..a8dd316
--- /dev/null
+++ b/pkgs/async/lib/src/result/future.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../delegate/future.dart';
+import 'result.dart';
+
+/// A [Future] wrapper that provides synchronous access to the result of the
+/// wrapped [Future] once it's completed.
+class ResultFuture<T> extends DelegatingFuture<T> {
+ /// Whether the future has fired and [result] is available.
+ bool get isComplete => result != null;
+
+ /// The result of the wrapped [Future], if it's completed.
+ ///
+ /// If it hasn't completed yet, this will be `null`.
+ Result<T>? get result => _result;
+ Result<T>? _result;
+
+ ResultFuture(Future<T> future) : super(future) {
+ Result.capture(future).then((result) {
+ _result = result;
+ });
+ }
+}
diff --git a/pkgs/async/lib/src/result/release_sink.dart b/pkgs/async/lib/src/result/release_sink.dart
new file mode 100644
index 0000000..bf6dd50
--- /dev/null
+++ b/pkgs/async/lib/src/result/release_sink.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'result.dart';
+
+/// Used by [Result.releaseSink].
+class ReleaseSink<T> implements EventSink<Result<T>> {
+ final EventSink<T> _sink;
+
+ ReleaseSink(this._sink);
+
+ @override
+ void add(Result<T> result) {
+ result.addTo(_sink);
+ }
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ // Errors may be added by intermediate processing, even if it is never
+ // added by CaptureSink.
+ _sink.addError(error, stackTrace);
+ }
+
+ @override
+ void close() {
+ _sink.close();
+ }
+}
diff --git a/pkgs/async/lib/src/result/release_transformer.dart b/pkgs/async/lib/src/result/release_transformer.dart
new file mode 100644
index 0000000..2f80d71
--- /dev/null
+++ b/pkgs/async/lib/src/result/release_transformer.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'release_sink.dart';
+import 'result.dart';
+
+/// A transformer that releases result events as data and error events.
+class ReleaseStreamTransformer<T> extends StreamTransformerBase<Result<T>, T> {
+ const ReleaseStreamTransformer();
+
+ @override
+ Stream<T> bind(Stream<Result<T>> source) {
+ return Stream<T>.eventTransformed(source, _createSink);
+ }
+
+ // Since Stream.eventTransformed is not generic, this method can be static.
+ static EventSink<Result> _createSink(EventSink sink) => ReleaseSink(sink);
+}
diff --git a/pkgs/async/lib/src/result/result.dart b/pkgs/async/lib/src/result/result.dart
new file mode 100644
index 0000000..124ccef
--- /dev/null
+++ b/pkgs/async/lib/src/result/result.dart
@@ -0,0 +1,223 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import '../stream_sink_transformer.dart';
+import 'capture_sink.dart';
+import 'capture_transformer.dart';
+import 'error.dart';
+import 'release_sink.dart';
+import 'release_transformer.dart';
+import 'value.dart';
+
+/// The result of a computation.
+///
+/// Capturing a result (either a returned value or a thrown error) means
+/// converting it into a [Result] - either a [ValueResult] or an [ErrorResult].
+///
+/// This value can release itself by writing itself either to an [EventSink] or
+/// a [Completer], or by becoming a [Future].
+///
+/// A [Future] represents a potential result, one that might not have been
+/// computed yet, and a [Result] is always a completed and available result.
+abstract class Result<T> {
+ /// A stream transformer that captures a stream of events into [Result]s.
+ ///
+ /// The result of the transformation is a stream of [Result] values and no
+ /// error events. This is the transformer used by [captureStream].
+ static const StreamTransformer<Object, Result<Object>>
+ captureStreamTransformer = CaptureStreamTransformer<Object>();
+
+ /// A stream transformer that releases a stream of result events.
+ ///
+ /// The result of the transformation is a stream of values and error events.
+ /// This is the transformer used by [releaseStream].
+ static const StreamTransformer<Result<Object>, Object>
+ releaseStreamTransformer = ReleaseStreamTransformer<Object>();
+
+ /// A sink transformer that captures events into [Result]s.
+ ///
+ /// The result of the transformation is a sink that only forwards [Result]
+ /// values and no error events.
+ static const StreamSinkTransformer<Object, Result<Object>>
+ captureSinkTransformer =
+ StreamSinkTransformer<Object, Result<Object>>.fromStreamTransformer(
+ CaptureStreamTransformer<Object>());
+
+ /// A sink transformer that releases result events.
+ ///
+ /// The result of the transformation is a sink that forwards of values and
+ /// error events.
+ static const StreamSinkTransformer<Result<Object>, Object>
+ releaseSinkTransformer =
+ StreamSinkTransformer<Result<Object>, Object>.fromStreamTransformer(
+ ReleaseStreamTransformer<Object>());
+
+ /// Creates a `Result` with the result of calling [computation].
+ ///
+ /// This generates either a [ValueResult] with the value returned by
+ /// calling `computation`, or an [ErrorResult] with an error thrown by
+ /// the call.
+ factory Result(T Function() computation) {
+ try {
+ return ValueResult<T>(computation());
+ } on Object catch (e, s) {
+ return ErrorResult(e, s);
+ }
+ }
+
+ /// Creates a `Result` holding a value.
+ ///
+ /// Alias for [ValueResult.new].
+ factory Result.value(T value) = ValueResult<T>;
+
+ /// Creates a `Result` holding an error.
+ ///
+ /// Alias for [ErrorResult.new].
+ factory Result.error(Object error, [StackTrace? stackTrace]) =>
+ ErrorResult(error, stackTrace);
+
+ /// Captures the result of a future into a `Result` future.
+ ///
+ /// The resulting future will never have an error.
+ /// Errors have been converted to an [ErrorResult] value.
+ static Future<Result<T>> capture<T>(Future<T> future) {
+ return future.then(ValueResult.new, onError: ErrorResult.new);
+ }
+
+ /// Captures each future in [elements],
+ ///
+ /// Returns a (future of) a list of results for each element in [elements],
+ /// in iteration order.
+ /// Each future in [elements] is [capture]d and each non-future is
+ /// wrapped as a [Result.value].
+ /// The returned future will never have an error.
+ static Future<List<Result<T>>> captureAll<T>(Iterable<FutureOr<T>> elements) {
+ var results = <Result<T>?>[];
+ var pending = 0;
+ late Completer<List<Result<T>>> completer;
+ for (var element in elements) {
+ if (element is Future<T>) {
+ var i = results.length;
+ results.add(null);
+ pending++;
+ Result.capture<T>(element).then((result) {
+ results[i] = result;
+ if (--pending == 0) {
+ completer.complete(List.from(results));
+ }
+ });
+ } else {
+ results.add(Result<T>.value(element));
+ }
+ }
+ if (pending == 0) {
+ return Future.value(List.from(results));
+ }
+ completer = Completer<List<Result<T>>>();
+ return completer.future;
+ }
+
+ /// Releases the result of a captured future.
+ ///
+ /// Converts the [Result] value of the given [future] to a value or error
+ /// completion of the returned future.
+ ///
+ /// If [future] completes with an error, the returned future completes with
+ /// the same error.
+ static Future<T> release<T>(Future<Result<T>> future) =>
+ future.then<T>((result) => result.asFuture);
+
+ /// Captures the results of a stream into a stream of [Result] values.
+ ///
+ /// The returned stream will not have any error events.
+ /// Errors from the source stream have been converted to [ErrorResult]s.
+ static Stream<Result<T>> captureStream<T>(Stream<T> source) =>
+ source.transform(CaptureStreamTransformer<T>());
+
+ /// Releases a stream of [source] values into a stream of the results.
+ ///
+ /// `Result` values of the source stream become value or error events in
+ /// the returned stream as appropriate.
+ /// Errors from the source stream become errors in the returned stream.
+ static Stream<T> releaseStream<T>(Stream<Result<T>> source) =>
+ source.transform(ReleaseStreamTransformer<T>());
+
+ /// Releases results added to the returned sink as data and errors on [sink].
+ ///
+ /// A [Result] added to the returned sink is added as a data or error event
+ /// on [sink]. Errors added to the returned sink are forwarded directly to
+ /// [sink] and so is the [EventSink.close] calls.
+ static EventSink<Result<T>> releaseSink<T>(EventSink<T> sink) =>
+ ReleaseSink<T>(sink);
+
+ /// Captures the events of the returned sink into results on [sink].
+ ///
+ /// Data and error events added to the returned sink are captured into
+ /// [Result] values and added as data events on the provided [sink].
+ /// No error events are ever added to [sink].
+ ///
+ /// When the returned sink is closed, so is [sink].
+ static EventSink<T> captureSink<T>(EventSink<Result<T>> sink) =>
+ CaptureSink<T>(sink);
+
+ /// Converts a result of a result to a single result.
+ ///
+ /// If the result is an error, or it is a `Result` value
+ /// which is then an error, then a result with that error is returned.
+ /// Otherwise both levels of results are value results, and a single
+ /// result with the value is returned.
+ static Result<T> flatten<T>(Result<Result<T>> result) {
+ if (result.isValue) return result.asValue!.value;
+ return result.asError!;
+ }
+
+ /// Converts a sequence of results to a result of a list.
+ ///
+ /// Returns either a list of values if [results] doesn't contain any errors,
+ /// or the first error result in [results].
+ static Result<List<T>> flattenAll<T>(Iterable<Result<T>> results) {
+ var values = <T>[];
+ for (var result in results) {
+ if (result.isValue) {
+ values.add(result.asValue!.value);
+ } else {
+ return result.asError!;
+ }
+ }
+ return Result<List<T>>.value(values);
+ }
+
+ /// Whether this result is a value result.
+ ///
+ /// Always the opposite of [isError].
+ bool get isValue;
+
+ /// Whether this result is an error result.
+ ///
+ /// Always the opposite of [isValue].
+ bool get isError;
+
+ /// If this is a value result, returns itself.
+ ///
+ /// Otherwise returns `null`.
+ ValueResult<T>? get asValue;
+
+ /// If this is an error result, returns itself.
+ ///
+ /// Otherwise returns `null`.
+ ErrorResult? get asError;
+
+ /// Completes a completer with this result.
+ void complete(Completer<T> completer);
+
+ /// Adds this result to an [EventSink].
+ ///
+ /// Calls the sink's `add` or `addError` method as appropriate.
+ void addTo(EventSink<T> sink);
+
+ /// A future that has been completed with this result as a value or an error.
+ Future<T> get asFuture;
+}
diff --git a/pkgs/async/lib/src/result/value.dart b/pkgs/async/lib/src/result/value.dart
new file mode 100644
index 0000000..3872dd0
--- /dev/null
+++ b/pkgs/async/lib/src/result/value.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'error.dart';
+import 'result.dart';
+
+/// A result representing a returned value.
+class ValueResult<T> implements Result<T> {
+ /// The result of a successful computation.
+ final T value;
+
+ @override
+ bool get isValue => true;
+ @override
+ bool get isError => false;
+ @override
+ ValueResult<T> get asValue => this;
+ @override
+ ErrorResult? get asError => null;
+
+ ValueResult(this.value);
+
+ @override
+ void complete(Completer<T> completer) {
+ completer.complete(value);
+ }
+
+ @override
+ void addTo(EventSink<T> sink) {
+ sink.add(value);
+ }
+
+ @override
+ Future<T> get asFuture => Future.value(value);
+
+ @override
+ int get hashCode => value.hashCode ^ 0x323f1d61;
+
+ @override
+ bool operator ==(Object other) =>
+ other is ValueResult && value == other.value;
+}
diff --git a/pkgs/async/lib/src/single_subscription_transformer.dart b/pkgs/async/lib/src/single_subscription_transformer.dart
new file mode 100644
index 0000000..ba6f0d2
--- /dev/null
+++ b/pkgs/async/lib/src/single_subscription_transformer.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// A transformer that converts a broadcast stream into a single-subscription
+/// stream.
+///
+/// This buffers the broadcast stream's events, which means that it starts
+/// listening to a stream as soon as it's bound.
+///
+/// This also casts the source stream's events to type `T`. If the cast fails,
+/// the result stream will emit a [TypeError]. This behavior is deprecated, and
+/// should not be relied upon.
+class SingleSubscriptionTransformer<S, T> extends StreamTransformerBase<S, T> {
+ const SingleSubscriptionTransformer();
+
+ @override
+ Stream<T> bind(Stream<S> stream) {
+ late StreamSubscription<S> subscription;
+ var controller =
+ StreamController<T>(sync: true, onCancel: () => subscription.cancel());
+ subscription = stream.listen((value) {
+ // TODO(nweiz): When we release a new major version, get rid of the second
+ // type parameter and avoid this conversion.
+ try {
+ controller.add(value as T);
+ // ignore: avoid_catching_errors
+ } on TypeError catch (error, stackTrace) {
+ controller.addError(error, stackTrace);
+ }
+ }, onError: controller.addError, onDone: controller.close);
+ return controller.stream;
+ }
+}
diff --git a/pkgs/async/lib/src/sink_base.dart b/pkgs/async/lib/src/sink_base.dart
new file mode 100644
index 0000000..f1d7d14
--- /dev/null
+++ b/pkgs/async/lib/src/sink_base.dart
@@ -0,0 +1,171 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:meta/meta.dart';
+
+import 'async_memoizer.dart';
+
+/// An abstract class that implements [EventSink] in terms of [onAdd],
+/// [onError], and [onClose] methods.
+///
+/// This takes care of ensuring that events can't be added after [close] is
+/// called.
+@Deprecated('Will be removed in the next major release')
+abstract class EventSinkBase<T> implements EventSink<T> {
+ /// Whether [close] has been called and no more events may be written.
+ bool get _closed => _closeMemo.hasRun;
+
+ @override
+ void add(T data) {
+ _checkCanAddEvent();
+ onAdd(data);
+ }
+
+ /// A method that handles data events that are passed to the sink.
+ @visibleForOverriding
+ void onAdd(T data);
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ _checkCanAddEvent();
+ onError(error, stackTrace);
+ }
+
+ /// A method that handles error events that are passed to the sink.
+ @visibleForOverriding
+ void onError(Object error, [StackTrace? stackTrace]);
+
+ @override
+ Future<void> close() => _closeMemo.runOnce(onClose);
+ final _closeMemo = AsyncMemoizer<void>();
+
+ /// A method that handles the sink being closed.
+ ///
+ /// This may return a future that completes once the stream sink has shut
+ /// down. If cleaning up can fail, the error may be reported in the returned
+ /// future.
+ @visibleForOverriding
+ FutureOr<void> onClose();
+
+ /// Asserts that the sink is in a state where adding an event is valid.
+ void _checkCanAddEvent() {
+ if (_closed) throw StateError('Cannot add event after closing');
+ }
+}
+
+/// An abstract class that implements [StreamSink] in terms of [onAdd],
+/// [onError], and [onClose] methods.
+///
+/// This takes care of ensuring that events can't be added after [close] is
+/// called or during a call to [addStream].
+@Deprecated('Will be removed in the next major release')
+abstract class StreamSinkBase<T> extends EventSinkBase<T>
+ implements StreamSink<T> {
+ /// Whether a call to [addStream] is ongoing.
+ bool _addingStream = false;
+
+ @override
+ Future<void> get done => _closeMemo.future;
+
+ @override
+ Future<void> addStream(Stream<T> stream) {
+ _checkCanAddEvent();
+
+ _addingStream = true;
+ var completer = Completer<void>.sync();
+ stream.listen(onAdd, onError: onError, onDone: () {
+ _addingStream = false;
+ completer.complete();
+ });
+ return completer.future;
+ }
+
+ @override
+ Future<void> close() {
+ if (_addingStream) throw StateError('StreamSink is bound to a stream');
+ return super.close();
+ }
+
+ @override
+ void _checkCanAddEvent() {
+ super._checkCanAddEvent();
+ if (_addingStream) throw StateError('StreamSink is bound to a stream');
+ }
+}
+
+/// An abstract class that implements `dart:io`'s `IOSink`'s API in terms of
+/// [onAdd], [onError], [onClose], and [onFlush] methods.
+///
+/// Because `IOSink` is defined in `dart:io`, this can't officially implement
+/// it. However, it's designed to match its API exactly so that subclasses can
+/// implement `IOSink` without any additional modifications.
+///
+/// This takes care of ensuring that events can't be added after [close] is
+/// called or during a call to [addStream].
+@Deprecated('Will be removed in the next major release')
+abstract class IOSinkBase extends StreamSinkBase<List<int>> {
+ /// See `IOSink.encoding` from `dart:io`.
+ Encoding encoding;
+
+ IOSinkBase([this.encoding = utf8]);
+
+ /// See `IOSink.flush` from `dart:io`.
+ ///
+ /// Because this base class doesn't do any buffering of its own, [flush]
+ /// always completes immediately.
+ ///
+ /// Subclasses that do buffer events should override [flush] to complete once
+ /// all events are delivered. They should also call `super.flush()` at the
+ /// beginning of the method to throw a [StateError] if the sink is currently
+ /// adding a stream.
+ Future<void> flush() {
+ if (_addingStream) throw StateError('StreamSink is bound to a stream');
+ if (_closed) return Future.value();
+
+ _addingStream = true;
+ return onFlush().whenComplete(() {
+ _addingStream = false;
+ });
+ }
+
+ /// Flushes any buffered data to the underlying consumer, and returns a future
+ /// that completes once the consumer has accepted all data.
+ @visibleForOverriding
+ Future<void> onFlush();
+
+ /// See [StringSink.write].
+ void write(Object? object) {
+ var string = object.toString();
+ if (string.isEmpty) return;
+ add(encoding.encode(string));
+ }
+
+ /// See [StringSink.writeAll].
+ void writeAll(Iterable<Object?> objects, [String separator = '']) {
+ var first = true;
+ for (var object in objects) {
+ if (first) {
+ first = false;
+ } else {
+ write(separator);
+ }
+
+ write(object);
+ }
+ }
+
+ /// See [StringSink.writeln].
+ void writeln([Object? object = '']) {
+ write(object);
+ write('\n');
+ }
+
+ /// See [StringSink.writeCharCode].
+ void writeCharCode(int charCode) {
+ write(String.fromCharCode(charCode));
+ }
+}
diff --git a/pkgs/async/lib/src/stream_closer.dart b/pkgs/async/lib/src/stream_closer.dart
new file mode 100644
index 0000000..9154624
--- /dev/null
+++ b/pkgs/async/lib/src/stream_closer.dart
@@ -0,0 +1,108 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+
+/// A [StreamTransformer] that allows the caller to forcibly close the
+/// transformed [Stream](s).
+///
+/// When [close] is called, any stream (or streams) transformed by this
+/// transformer that haven't already completed or been cancelled will emit a
+/// done event and cancel their underlying subscriptions.
+///
+/// Note that unlike most [StreamTransformer]s, each instance of [StreamCloser]
+/// has its own state (whether or not it's been closed), so it's a good idea to
+/// construct a new one for each use unless you need to close multiple streams
+/// at the same time.
+@sealed
+class StreamCloser<T> extends StreamTransformerBase<T, T> {
+ /// The subscriptions to streams passed to [bind].
+ final _subscriptions = <StreamSubscription<T>>{};
+
+ /// The controllers for streams returned by [bind].
+ final _controllers = <StreamController<T>>{};
+
+ /// Closes all transformed streams.
+ ///
+ /// Returns a future that completes when all inner subscriptions'
+ /// [StreamSubscription.cancel] futures have completed. Note that a stream's
+ /// subscription won't be canceled until the transformed stream has a
+ /// listener.
+ ///
+ /// If a transformed stream is listened to after [close] is called, the
+ /// original stream will be listened to and then the subscription immediately
+ /// canceled. If that cancellation throws an error, it will be silently
+ /// ignored.
+ Future<void> close() => _closeFuture ??= () {
+ var futures = [
+ for (var subscription in _subscriptions) subscription.cancel()
+ ];
+ _subscriptions.clear();
+
+ var controllers = _controllers.toList();
+ _controllers.clear();
+ scheduleMicrotask(() {
+ for (var controller in controllers) {
+ scheduleMicrotask(controller.close);
+ }
+ });
+
+ return Future.wait(futures, eagerError: true);
+ }();
+ Future<void>? _closeFuture;
+
+ /// Whether [close] has been called.
+ bool get isClosed => _closeFuture != null;
+
+ @override
+ Stream<T> bind(Stream<T> stream) {
+ var controller = stream.isBroadcast
+ ? StreamController<T>.broadcast(sync: true)
+ : StreamController<T>(sync: true);
+
+ controller.onListen = () {
+ if (isClosed) {
+ // Ignore errors here, because otherwise there would be no way for the
+ // user to handle them gracefully.
+ stream.listen(null).cancel().catchError((_) {});
+ return;
+ }
+
+ var subscription =
+ stream.listen(controller.add, onError: controller.addError);
+ subscription.onDone(() {
+ _subscriptions.remove(subscription);
+ _controllers.remove(controller);
+ controller.close();
+ });
+ _subscriptions.add(subscription);
+
+ if (!stream.isBroadcast) {
+ controller.onPause = subscription.pause;
+ controller.onResume = subscription.resume;
+ }
+
+ controller.onCancel = () {
+ _controllers.remove(controller);
+
+ // If the subscription has already been removed, that indicates that the
+ // underlying stream has been cancelled by [close] and its cancellation
+ // future has been handled there. In that case, we shouldn't forward it
+ // here as well.
+ if (_subscriptions.remove(subscription)) return subscription.cancel();
+ return null;
+ };
+ };
+
+ if (isClosed) {
+ controller.close();
+ } else {
+ _controllers.add(controller);
+ }
+
+ return controller.stream;
+ }
+}
diff --git a/pkgs/async/lib/src/stream_completer.dart b/pkgs/async/lib/src/stream_completer.dart
new file mode 100644
index 0000000..27034c2
--- /dev/null
+++ b/pkgs/async/lib/src/stream_completer.dart
@@ -0,0 +1,182 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// A single-subscription [stream] where the contents are provided later.
+///
+/// It is generally recommended that you never create a `Future<Stream>`
+/// because you can just directly create a stream that doesn't do anything
+/// until it's ready to do so.
+/// This class can be used to create such a stream.
+///
+/// The [stream] is a normal stream that you can listen to immediately,
+/// but until either [setSourceStream] or [setEmpty] is called,
+/// the stream won't produce any events.
+///
+/// The same effect can be achieved by using a [StreamController]
+/// and adding the stream using `addStream` when both
+/// the controller's stream is listened to and the source stream is ready.
+/// This class attempts to shortcut some of the overhead when possible.
+/// For example, if the [stream] is only listened to
+/// after the source stream has been set,
+/// the listen is performed directly on the source stream.
+class StreamCompleter<T> {
+ /// The stream doing the actual work, is returned by [stream].
+ final _stream = _CompleterStream<T>();
+
+ /// Convert a `Future<Stream>` to a `Stream`.
+ ///
+ /// This creates a stream using a stream completer,
+ /// and sets the source stream to the result of the future when the
+ /// future completes.
+ ///
+ /// If the future completes with an error, the returned stream will
+ /// instead contain just that error.
+ static Stream<T> fromFuture<T>(Future<Stream<T>> streamFuture) {
+ var completer = StreamCompleter<T>();
+ streamFuture.then(completer.setSourceStream, onError: completer.setError);
+ return completer.stream;
+ }
+
+ /// The stream of this completer.
+ ///
+ /// This stream is always a single-subscription stream.
+ ///
+ /// When a source stream is provided, its events will be forwarded to
+ /// listeners on this stream.
+ ///
+ /// The stream can be listened either before or after a source stream
+ /// is set.
+ Stream<T> get stream => _stream;
+
+ /// Set a stream as the source of events for the [StreamCompleter]'s
+ /// [stream].
+ ///
+ /// The completer's `stream` will act exactly as [sourceStream].
+ ///
+ /// If the source stream is set before [stream] is listened to,
+ /// the listen call on [stream] is forwarded directly to [sourceStream].
+ ///
+ /// If [stream] is listened to before setting the source stream,
+ /// an intermediate subscription is created. It looks like a completely
+ /// normal subscription, and can be paused or canceled, but it won't
+ /// produce any events until a source stream is provided.
+ ///
+ /// If the `stream` subscription is canceled before a source stream is set,
+ /// the source stream will be listened to and immediately canceled again.
+ ///
+ /// Otherwise, when the source stream is then set,
+ /// it is immediately listened to, and its events are forwarded to the
+ /// existing subscription.
+ ///
+ /// Any one of [setSourceStream], [setEmpty], and [setError] may be called at
+ /// most once. Trying to call any of them again will fail.
+ void setSourceStream(Stream<T> sourceStream) {
+ if (_stream._isSourceStreamSet) {
+ throw StateError('Source stream already set');
+ }
+ _stream._setSourceStream(sourceStream);
+ }
+
+ /// Equivalent to setting an empty stream using [setSourceStream].
+ ///
+ /// Any one of [setSourceStream], [setEmpty], and [setError] may be called at
+ /// most once. Trying to call any of them again will fail.
+ void setEmpty() {
+ if (_stream._isSourceStreamSet) {
+ throw StateError('Source stream already set');
+ }
+ _stream._setEmpty();
+ }
+
+ /// Completes this to a stream that emits [error] and then closes.
+ ///
+ /// This is useful when the process of creating the data for the stream fails.
+ ///
+ /// Any one of [setSourceStream], [setEmpty], and [setError] may be called at
+ /// most once. Trying to call any of them again will fail.
+ void setError(Object error, [StackTrace? stackTrace]) {
+ setSourceStream(Stream.fromFuture(Future.error(error, stackTrace)));
+ }
+}
+
+/// Stream completed by [StreamCompleter].
+class _CompleterStream<T> extends Stream<T> {
+ /// Controller for an intermediate stream.
+ ///
+ /// Created if the user listens on this stream before the source stream
+ /// is set, or if using [_setEmpty] so there is no source stream.
+ StreamController<T>? _controller;
+
+ /// Source stream for the events provided by this stream.
+ ///
+ /// Set when the completer sets the source stream using [_setSourceStream]
+ /// or [_setEmpty].
+ Stream<T>? _sourceStream;
+
+ @override
+ StreamSubscription<T> listen(void Function(T)? onData,
+ {Function? onError, void Function()? onDone, bool? cancelOnError}) {
+ if (_controller == null) {
+ var sourceStream = _sourceStream;
+ if (sourceStream != null && !sourceStream.isBroadcast) {
+ // If the source stream is itself single subscription,
+ // just listen to it directly instead of creating a controller.
+ return sourceStream.listen(onData,
+ onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+ }
+ _ensureController();
+ if (_sourceStream != null) {
+ _linkStreamToController();
+ }
+ }
+ return _controller!.stream.listen(onData,
+ onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+ }
+
+ /// Whether a source stream has been set.
+ ///
+ /// Used to throw an error if trying to set a source stream twice.
+ bool get _isSourceStreamSet => _sourceStream != null;
+
+ /// Sets the source stream providing the events for this stream.
+ ///
+ /// If set before the user listens, listen calls will be directed directly
+ /// to the source stream. If the user listenes earlier, and intermediate
+ /// stream is created using a stream controller, and the source stream is
+ /// linked into that stream later.
+ void _setSourceStream(Stream<T> sourceStream) {
+ assert(_sourceStream == null);
+ _sourceStream = sourceStream;
+ if (_controller != null) {
+ // User has already listened, so provide the data through controller.
+ _linkStreamToController();
+ }
+ }
+
+ /// Links source stream to controller when both are available.
+ void _linkStreamToController() {
+ var controller = _controller!;
+ controller
+ .addStream(_sourceStream!, cancelOnError: false)
+ .whenComplete(controller.close);
+ }
+
+ /// Sets an empty source stream.
+ ///
+ /// Uses [_controller] for the stream, then closes the controller
+ /// immediately.
+ void _setEmpty() {
+ assert(_sourceStream == null);
+ var controller = _ensureController();
+ _sourceStream = controller.stream; // Mark stream as set.
+ controller.close();
+ }
+
+ // Creates the [_controller].
+ StreamController<T> _ensureController() {
+ return _controller ??= StreamController<T>(sync: true);
+ }
+}
diff --git a/pkgs/async/lib/src/stream_extensions.dart b/pkgs/async/lib/src/stream_extensions.dart
new file mode 100644
index 0000000..4ba9254
--- /dev/null
+++ b/pkgs/async/lib/src/stream_extensions.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// Utility extensions on [Stream].
+extension StreamExtensions<T> on Stream<T> {
+ /// Creates a stream whose elements are contiguous slices of `this`.
+ ///
+ /// Each slice is [length] elements long, except for the last one which may be
+ /// shorter if `this` emits too few elements. Each slice begins after the
+ /// last one ends.
+ ///
+ /// For example, `Stream.fromIterable([1, 2, 3, 4, 5]).slices(2)` emits
+ /// `([1, 2], [3, 4], [5])`.
+ ///
+ /// Errors are forwarded to the result stream immediately when they occur,
+ /// even if previous data events have not been emitted because the next slice
+ /// is not complete yet.
+ Stream<List<T>> slices(int length) {
+ if (length < 1) throw RangeError.range(length, 1, null, 'length');
+
+ var slice = <T>[];
+ return transform(StreamTransformer.fromHandlers(handleData: (data, sink) {
+ slice.add(data);
+ if (slice.length == length) {
+ sink.add(slice);
+ slice = [];
+ }
+ }, handleDone: (sink) {
+ if (slice.isNotEmpty) sink.add(slice);
+ sink.close();
+ }));
+ }
+
+ /// A future which completes with the first event of this stream, or with
+ /// `null`.
+ ///
+ /// This stream is listened to, and if it emits any event, whether a data
+ /// event or an error event, the future completes with the same data value or
+ /// error. If the stream ends without emitting any events, the future is
+ /// completed with `null`.
+ Future<T?> get firstOrNull {
+ var completer = Completer<T?>.sync();
+ final subscription = listen(null,
+ onError: completer.completeError,
+ onDone: completer.complete,
+ cancelOnError: true);
+ subscription.onData((event) {
+ subscription.cancel().whenComplete(() {
+ completer.complete(event);
+ });
+ });
+ return completer.future;
+ }
+
+ /// Eagerly listens to this stream and buffers events until needed.
+ ///
+ /// The returned stream will emit the same events as this stream, starting
+ /// from when this method is called. The events are delayed until the returned
+ /// stream is listened to, at which point all buffered events will be emitted
+ /// in order, and then further events from this stream will be emitted as they
+ /// arrive.
+ ///
+ /// The buffer will retain all events until the returned stream is listened
+ /// to, so if the stream can emit arbitrary amounts of data, callers should be
+ /// careful to listen to the stream eventually or call
+ /// `stream.listen(null).cancel()` to discard the buffered data if it becomes
+ /// clear that the data isn't not needed.
+ Stream<T> listenAndBuffer() {
+ var controller = StreamController<T>(sync: true);
+ var subscription = listen(controller.add,
+ onError: controller.addError, onDone: controller.close);
+ controller
+ ..onPause = subscription.pause
+ ..onResume = subscription.resume
+ ..onCancel = subscription.cancel;
+ return controller.stream;
+ }
+}
diff --git a/pkgs/async/lib/src/stream_group.dart b/pkgs/async/lib/src/stream_group.dart
new file mode 100644
index 0000000..502a111
--- /dev/null
+++ b/pkgs/async/lib/src/stream_group.dart
@@ -0,0 +1,336 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// A collection of streams whose events are unified and sent through a central
+/// stream.
+///
+/// Both errors and data events are forwarded through [stream]. The streams in
+/// the group won't be listened to until [stream] has a listener. **Note that
+/// this means that events emitted by broadcast streams will be dropped until
+/// [stream] has a listener.**
+///
+/// If the `StreamGroup` is constructed using [StreamGroup.new], [stream] will
+/// be single-subscription. In this case, if [stream] is paused or canceled, all
+/// streams in the group will likewise be paused or canceled, respectively.
+///
+/// If the `StreamGroup` is constructed using [StreamGroup.broadcast],
+/// [stream] will be a broadcast stream. In this case, the streams in the group
+/// will never be paused and single-subscription streams in the group will never
+/// be canceled. **Note that single-subscription streams in a broadcast group
+/// may drop events if a listener is added and later removed.** Broadcast
+/// streams in the group will be canceled once [stream] has no listeners, and
+/// will be listened to again once [stream] has listeners.
+///
+/// [stream] won't close until [close] is called on the group *and* every stream
+/// in the group closes.
+class StreamGroup<T> implements Sink<Stream<T>> {
+ /// The stream through which all events from streams in the group are emitted.
+ Stream<T> get stream => _controller.stream;
+ late StreamController<T> _controller;
+
+ /// Whether the group is closed, meaning that no more streams may be added.
+ bool get isClosed => _closed;
+
+ var _closed = false;
+
+ /// The current state of the group.
+ ///
+ /// See [_StreamGroupState] for detailed descriptions of each state.
+ var _state = _StreamGroupState.dormant;
+
+ /// Whether this group contains no streams.
+ ///
+ /// A [StreamGroup] is idle when it contains no streams, which is the case for
+ /// a newly created group or one where all added streams have been emitted
+ /// done events (or been [remove]d).
+ ///
+ /// If this is a single-subscription group, then cancelling the subscription
+ /// to [stream] will also remove all streams.
+ bool get isIdle => _subscriptions.isEmpty;
+
+ /// A broadcast stream that emits an event whenever this group becomes idle.
+ ///
+ /// A [StreamGroup] is idle when it contains no streams, which is the case for
+ /// a newly created group or one where all added streams have been emitted
+ /// done events (or been [remove]d).
+ ///
+ /// This stream will close when either:
+ ///
+ /// * This group is idle *and* [close] has been called, or
+ /// * [stream]'s subscription has been cancelled (if this is a
+ /// single-subscription group).
+ ///
+ /// Note that:
+ ///
+ /// * Events won't be emitted on this stream until [stream] has been listened
+ /// to.
+ /// * Events are delivered asynchronously, so it's possible for the group to
+ /// become active again before the event is delivered.
+ Stream<void> get onIdle =>
+ (_onIdleController ??= StreamController.broadcast()).stream;
+
+ StreamController<void>? _onIdleController;
+
+ /// Streams that have been added to the group, and their subscriptions if they
+ /// have been subscribed to.
+ ///
+ /// The subscriptions will be null until the group has a listener registered.
+ /// If it's a broadcast group and it goes dormant again, broadcast stream
+ /// subscriptions will be canceled and set to null again. Single-subscriber
+ /// stream subscriptions will be left intact, since they can't be
+ /// re-subscribed.
+ final _subscriptions = <Stream<T>, StreamSubscription<T>?>{};
+
+ /// Merges the events from [streams] into a single single-subscription stream.
+ ///
+ /// This is equivalent to adding [streams] to a group, closing that group, and
+ /// returning its stream.
+ static Stream<T> merge<T>(Iterable<Stream<T>> streams) {
+ var group = StreamGroup<T>();
+ streams.forEach(group.add);
+ group.close();
+ return group.stream;
+ }
+
+ /// Merges the events from [streams] into a single broadcast stream.
+ ///
+ /// This is equivalent to adding [streams] to a broadcast group, closing that
+ /// group, and returning its stream.
+ static Stream<T> mergeBroadcast<T>(Iterable<Stream<T>> streams) {
+ var group = StreamGroup<T>.broadcast();
+ streams.forEach(group.add);
+ group.close();
+ return group.stream;
+ }
+
+ /// Creates a new stream group where [stream] is single-subscriber.
+ StreamGroup() {
+ _controller = StreamController<T>(
+ onListen: _onListen,
+ onPause: _onPause,
+ onResume: _onResume,
+ onCancel: _onCancel,
+ sync: true);
+ }
+
+ /// Creates a new stream group where [stream] is a broadcast stream.
+ StreamGroup.broadcast() {
+ _controller = StreamController<T>.broadcast(
+ onListen: _onListen, onCancel: _onCancelBroadcast, sync: true);
+ }
+
+ /// Adds [stream] as a member of this group.
+ ///
+ /// Any events from [stream] will be emitted through [this.stream]. If this
+ /// group has a listener, [stream] will be listened to immediately; otherwise
+ /// it will only be listened to once this group gets a listener.
+ ///
+ /// If this is a single-subscription group and its subscription has been
+ /// canceled, [stream] will be canceled as soon as its added. If this returns
+ /// a [Future], it will be returned from [add]. Otherwise, [add] returns
+ /// `null`.
+ ///
+ /// Throws a [StateError] if this group is closed.
+ @override
+ Future<void>? add(Stream<T> stream) {
+ if (_closed) {
+ throw StateError("Can't add a Stream to a closed StreamGroup.");
+ }
+
+ if (_state == _StreamGroupState.dormant) {
+ _subscriptions.putIfAbsent(stream, () => null);
+ } else if (_state == _StreamGroupState.canceled) {
+ // Listen to the stream and cancel it immediately so that no one else can
+ // listen, for consistency. If the stream has an onCancel listener this
+ // will also fire that, which may help it clean up resources.
+ return stream.listen(null).cancel();
+ } else {
+ _subscriptions.putIfAbsent(stream, () => _listenToStream(stream));
+ }
+
+ return null;
+ }
+
+ /// Removes [stream] as a member of this group.
+ ///
+ /// No further events from [stream] will be emitted through this group. If
+ /// [stream] has been listened to, its subscription will be canceled.
+ ///
+ /// If [stream] has been listened to, this *synchronously* cancels its
+ /// subscription. This means that any events from [stream] that haven't yet
+ /// been emitted through this group will not be.
+ ///
+ /// If [stream]'s subscription is canceled, this returns
+ /// [StreamSubscription.cancel]'s return value. Otherwise, it returns `null`.
+ Future<void>? remove(Stream<T> stream) {
+ var subscription = _subscriptions.remove(stream);
+ var future = subscription?.cancel();
+
+ if (_subscriptions.isEmpty) {
+ _onIdleController?.add(null);
+ if (_closed) {
+ _onIdleController?.close();
+ scheduleMicrotask(_controller.close);
+ }
+ }
+
+ return future;
+ }
+
+ /// A callback called when [stream] is listened to.
+ ///
+ /// This is called for both single-subscription and broadcast groups.
+ void _onListen() {
+ _state = _StreamGroupState.listening;
+
+ for (var entry in [..._subscriptions.entries]) {
+ // If this is a broadcast group and this isn't the first time it's been
+ // listened to, there may still be some subscriptions to
+ // single-subscription streams.
+ if (entry.value != null) continue;
+
+ var stream = entry.key;
+ try {
+ _subscriptions[stream] = _listenToStream(stream);
+ } catch (error) {
+ // If [Stream.listen] throws a synchronous error (for example because
+ // the stream has already been listened to), cancel all subscriptions
+ // and rethrow the error.
+ _onCancel()?.catchError((_) {});
+ rethrow;
+ }
+ }
+ }
+
+ /// A callback called when [stream] is paused.
+ void _onPause() {
+ _state = _StreamGroupState.paused;
+ for (var subscription in _subscriptions.values) {
+ subscription!.pause();
+ }
+ }
+
+ /// A callback called when [stream] is resumed.
+ void _onResume() {
+ _state = _StreamGroupState.listening;
+ for (var subscription in _subscriptions.values) {
+ subscription!.resume();
+ }
+ }
+
+ /// A callback called when [stream] is canceled.
+ ///
+ /// This is only called for single-subscription groups.
+ Future<void>? _onCancel() {
+ _state = _StreamGroupState.canceled;
+
+ var futures = _subscriptions.entries
+ .map((entry) {
+ var subscription = entry.value;
+ try {
+ if (subscription != null) return subscription.cancel();
+ return entry.key.listen(null).cancel();
+ } catch (_) {
+ return null;
+ }
+ })
+ .nonNulls
+ .toList();
+
+ _subscriptions.clear();
+
+ var onIdleController = _onIdleController;
+ if (onIdleController != null && !onIdleController.isClosed) {
+ onIdleController.add(null);
+ onIdleController.close();
+ }
+
+ return futures.isEmpty ? null : Future.wait(futures);
+ }
+
+ /// A callback called when [stream]'s last listener is canceled.
+ ///
+ /// This is only called for broadcast groups.
+ void _onCancelBroadcast() {
+ _state = _StreamGroupState.dormant;
+
+ _subscriptions.forEach((stream, subscription) {
+ // Cancel the broadcast streams, since we can re-listen to those later,
+ // but allow the single-subscription streams to keep firing. Their events
+ // will still be added to [_controller], but then they'll be dropped since
+ // it has no listeners.
+ if (!stream.isBroadcast) return;
+ subscription!.cancel();
+ _subscriptions[stream] = null;
+ });
+ }
+
+ /// Starts actively forwarding events from [stream] to [_controller].
+ ///
+ /// This will pause the resulting subscription if `this` is paused.
+ StreamSubscription<T> _listenToStream(Stream<T> stream) {
+ var subscription = stream.listen(_controller.add,
+ onError: _controller.addError, onDone: () => remove(stream));
+ if (_state == _StreamGroupState.paused) subscription.pause();
+ return subscription;
+ }
+
+ /// Closes the group, indicating that no more streams will be added.
+ ///
+ /// If there are no streams in the group, [stream] is closed immediately.
+ /// Otherwise, [stream] will close once all streams in the group close.
+ ///
+ /// Returns a [Future] that completes once [stream] has actually been closed.
+ @override
+ Future<void> close() {
+ if (_closed) return _controller.done;
+
+ _closed = true;
+ if (_subscriptions.isEmpty) _controller.close();
+
+ return _controller.done;
+ }
+}
+
+/// An enum of possible states of a [StreamGroup].
+class _StreamGroupState {
+ /// The group has no listeners.
+ ///
+ /// New streams added to the group will be listened once the group has a
+ /// listener.
+ static const dormant = _StreamGroupState('dormant');
+
+ /// The group has one or more listeners and is actively firing events.
+ ///
+ /// New streams added to the group will be immediately listeners.
+ static const listening = _StreamGroupState('listening');
+
+ /// The group is paused and no more events will be fired until it resumes.
+ ///
+ /// New streams added to the group will be listened to, but then paused. They
+ /// will be resumed once the group itself is resumed.
+ ///
+ /// This state is only used by single-subscriber groups.
+ static const paused = _StreamGroupState('paused');
+
+ /// The group is canceled and no more events will be fired ever.
+ ///
+ /// New streams added to the group will be listened to, canceled, and
+ /// discarded.
+ ///
+ /// This state is only used by single-subscriber groups.
+ static const canceled = _StreamGroupState('canceled');
+
+ /// The name of the state.
+ ///
+ /// Used for debugging.
+ final String name;
+
+ const _StreamGroupState(this.name);
+
+ @override
+ String toString() => name;
+}
diff --git a/pkgs/async/lib/src/stream_queue.dart b/pkgs/async/lib/src/stream_queue.dart
new file mode 100644
index 0000000..c5c0c19
--- /dev/null
+++ b/pkgs/async/lib/src/stream_queue.dart
@@ -0,0 +1,961 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'package:collection/collection.dart' show QueueList;
+
+import 'cancelable_operation.dart';
+import 'result/result.dart';
+import 'stream_completer.dart';
+import 'stream_splitter.dart';
+import 'subscription_stream.dart';
+
+/// An asynchronous pull-based interface for accessing stream events.
+///
+/// Wraps a stream and makes individual events available on request.
+///
+/// You can request (and reserve) one or more events from the stream,
+/// and after all previous requests have been fulfilled, stream events
+/// go towards fulfilling your request.
+///
+/// For example, if you ask for [next] two times, the returned futures
+/// will be completed by the next two unrequested events from the stream.
+///
+/// The stream subscription is paused when there are no active
+/// requests.
+///
+/// Some streams, including broadcast streams, will buffer
+/// events while paused, so waiting too long between requests may
+/// cause memory bloat somewhere else.
+///
+/// This is similar to, but more convenient than, a [StreamIterator].
+/// A `StreamIterator` requires you to manually check when a new event is
+/// available and you can only access the value of that event until you
+/// check for the next one. A `StreamQueue` allows you to request, for example,
+/// three events at a time, either individually, as a group using [take]
+/// or [skip], or in any combination.
+///
+/// You can also ask to have the [rest] of the stream provided as
+/// a new stream. This allows, for example, taking the first event
+/// out of a stream and continuing to use the rest of the stream as a stream.
+///
+/// Example:
+///
+/// var events = StreamQueue<String>(someStreamOfLines);
+/// var first = await events.next;
+/// while (first.startsWith('#')) {
+/// // Skip comments.
+/// first = await events.next;
+/// }
+///
+/// if (first.startsWith(MAGIC_MARKER)) {
+/// var headerCount =
+/// first.parseInt(first.substring(MAGIC_MARKER.length + 1));
+/// handleMessage(headers: await events.take(headerCount),
+/// body: events.rest);
+/// return;
+/// }
+/// // Error handling.
+///
+/// When you need no further events the `StreamQueue` should be closed
+/// using [cancel]. This releases the underlying stream subscription.
+class StreamQueue<T> {
+ // This class maintains two queues: one of events and one of requests.
+ // The active request (the one in front of the queue) is called with
+ // the current event queue when it becomes active, every time a
+ // new event arrives, and when the event source closes.
+ //
+ // If the request returns `true`, it's complete and will be removed from the
+ // request queue.
+ // If the request returns `false`, it needs more events, and will be called
+ // again when new events are available. It may trigger a call itself by
+ // calling [_updateRequests].
+ // The request can remove events that it uses, or keep them in the event
+ // queue until it has all that it needs.
+ //
+ // This model is very flexible and easily extensible.
+ // It allows requests that don't consume events (like [hasNext]) or
+ // potentially a request that takes either five or zero events, determined
+ // by the content of the fifth event.
+
+ final Stream<T> _source;
+
+ /// Subscription on [_source] while listening for events.
+ ///
+ /// Set to subscription when listening, and set to `null` when the
+ /// subscription is done (and [_isDone] is set to true).
+ StreamSubscription<T>? _subscription;
+
+ /// Whether the event source is done.
+ bool _isDone = false;
+
+ /// Whether a closing operation has been performed on the stream queue.
+ ///
+ /// Closing operations are [cancel] and [rest].
+ bool _isClosed = false;
+
+ /// The number of events dispatched by this queue.
+ ///
+ /// This counts error events. It doesn't count done events, or events
+ /// dispatched to a stream returned by [rest].
+ int get eventsDispatched => _eventsReceived - _eventQueue.length;
+
+ /// The number of events received by this queue.
+ var _eventsReceived = 0;
+
+ /// Queue of events not used by a request yet.
+ final QueueList<Result<T>> _eventQueue = QueueList();
+
+ /// Queue of pending requests.
+ ///
+ /// Access through methods below to ensure consistency.
+ final Queue<_EventRequest> _requestQueue = Queue();
+
+ /// Create a `StreamQueue` of the events of [source].
+ factory StreamQueue(Stream<T> source) => StreamQueue._(source);
+
+ // Private generative constructor to avoid subclasses.
+ StreamQueue._(this._source) {
+ // Start listening immediately if we could otherwise lose events.
+ if (_source.isBroadcast) {
+ _ensureListening();
+ _pause();
+ }
+ }
+
+ /// Whether the stream has any more events.
+ ///
+ /// Returns a future that completes with `true` if the stream has any
+ /// more events, whether data or error.
+ /// If the stream closes without producing any more events, the returned
+ /// future completes with `false`.
+ ///
+ /// Can be used before using [next] to avoid getting an error in the
+ /// future returned by `next` in the case where there are no more events.
+ /// Another alternative is to use `take(1)` which returns either zero or
+ /// one events.
+ Future<bool> get hasNext {
+ _checkNotClosed();
+ var hasNextRequest = _HasNextRequest<T>();
+ _addRequest(hasNextRequest);
+ return hasNextRequest.future;
+ }
+
+ /// Look at the next [count] data events without consuming them.
+ ///
+ /// Works like [take] except that the events are left in the queue.
+ /// If one of the next [count] events is an error, the returned future
+ /// completes with this error, and the error is still left in the queue.
+ Future<List<T>> lookAhead(int count) {
+ RangeError.checkNotNegative(count, 'count');
+ _checkNotClosed();
+ var request = _LookAheadRequest<T>(count);
+ _addRequest(request);
+ return request.future;
+ }
+
+ /// Requests the next (yet unrequested) event from the stream.
+ ///
+ /// When the requested event arrives, the returned future is completed with
+ /// the event.
+ /// If the event is a data event, the returned future completes
+ /// with its value.
+ /// If the event is an error event, the returned future completes with
+ /// its error and stack trace.
+ /// If the stream closes before an event arrives, the returned future
+ /// completes with a [StateError].
+ ///
+ /// It's possible to have several pending [next] calls (or other requests),
+ /// and they will be completed in the order they were requested, by the
+ /// first events that were not consumed by previous requeusts.
+ Future<T> get next {
+ _checkNotClosed();
+ var nextRequest = _NextRequest<T>();
+ _addRequest(nextRequest);
+ return nextRequest.future;
+ }
+
+ /// Looks at the next (yet unrequested) event from the stream.
+ ///
+ /// Like [next] except that the event is not consumed.
+ /// If the next event is an error event, it stays in the queue.
+ Future<T> get peek {
+ _checkNotClosed();
+ var nextRequest = _PeekRequest<T>();
+ _addRequest(nextRequest);
+ return nextRequest.future;
+ }
+
+ /// A stream of all the remaning events of the source stream.
+ ///
+ /// All requested [next], [skip] or [take] operations are completed
+ /// first, and then any remaining events are provided as events of
+ /// the returned stream.
+ ///
+ /// Using `rest` closes this stream queue. After getting the
+ /// `rest` the caller may no longer request other events, like
+ /// after calling [cancel].
+ Stream<T> get rest {
+ _checkNotClosed();
+ var request = _RestRequest<T>(this);
+ _isClosed = true;
+ _addRequest(request);
+ return request.stream;
+ }
+
+ /// Skips the next [count] *data* events.
+ ///
+ /// The [count] must be non-negative.
+ ///
+ /// When successful, this is equivalent to using [take]
+ /// and ignoring the result.
+ ///
+ /// If an error occurs before `count` data events have been skipped,
+ /// the returned future completes with that error instead.
+ ///
+ /// If the stream closes before `count` data events,
+ /// the remaining unskipped event count is returned.
+ /// If the returned future completes with the integer `0`,
+ /// then all events were succssfully skipped. If the value
+ /// is greater than zero then the stream ended early.
+ Future<int> skip(int count) {
+ RangeError.checkNotNegative(count, 'count');
+ _checkNotClosed();
+ var request = _SkipRequest<T>(count);
+ _addRequest(request);
+ return request.future;
+ }
+
+ /// Requests the next [count] data events as a list.
+ ///
+ /// The [count] must be non-negative.
+ ///
+ /// Equivalent to calling [next] `count` times and
+ /// storing the data values in a list.
+ ///
+ /// If an error occurs before `count` data events has
+ /// been collected, the returned future completes with
+ /// that error instead.
+ ///
+ /// If the stream closes before `count` data events,
+ /// the returned future completes with the list
+ /// of data collected so far. That is, the returned
+ /// list may have fewer than [count] elements.
+ Future<List<T>> take(int count) {
+ RangeError.checkNotNegative(count, 'count');
+ _checkNotClosed();
+ var request = _TakeRequest<T>(count);
+ _addRequest(request);
+ return request.future;
+ }
+
+ /// Requests a transaction that can conditionally consume events.
+ ///
+ /// The transaction can create copies of this queue at the current position
+ /// using [StreamQueueTransaction.newQueue]. Each of these queues is
+ /// independent of one another and of the parent queue. The transaction
+ /// finishes when one of two methods is called:
+ ///
+ /// * [StreamQueueTransaction.commit] updates the parent queue's position to
+ /// match that of one of the copies.
+ ///
+ /// * [StreamQueueTransaction.reject] causes the parent queue to continue as
+ /// though [startTransaction] hadn't been called.
+ ///
+ /// Until the transaction finishes, this queue won't emit any events.
+ ///
+ /// See also [withTransaction] and [cancelable].
+ ///
+ /// ```dart
+ /// /// Consumes all empty lines from the beginning of [lines].
+ /// Future<void> consumeEmptyLines(StreamQueue<String> lines) async {
+ /// while (await lines.hasNext) {
+ /// var transaction = lines.startTransaction();
+ /// var queue = transaction.newQueue();
+ /// if ((await queue.next).isNotEmpty) {
+ /// transaction.reject();
+ /// return;
+ /// } else {
+ /// transaction.commit(queue);
+ /// }
+ /// }
+ /// }
+ /// ```
+ StreamQueueTransaction<T> startTransaction() {
+ _checkNotClosed();
+
+ var request = _TransactionRequest(this);
+ _addRequest(request);
+ return request.transaction;
+ }
+
+ /// Passes a copy of this queue to [callback], and updates this queue to match
+ /// the copy's position if [callback] returns `true`.
+ ///
+ /// This queue won't emit any events until [callback] returns. If it returns
+ /// `false`, this queue continues as though [withTransaction] hadn't been
+ /// called. If it throws an error, this updates this queue to match the copy's
+ /// position and throws the error from the returned `Future`.
+ ///
+ /// Returns the same value as [callback].
+ ///
+ /// See also [startTransaction] and [cancelable].
+ ///
+ /// ```dart
+ /// /// Consumes all empty lines from the beginning of [lines].
+ /// Future<void> consumeEmptyLines(StreamQueue<String> lines) async {
+ /// while (await lines.hasNext) {
+ /// // Consume a line if it's empty, otherwise return.
+ /// if (!await lines.withTransaction(
+ /// (queue) async => (await queue.next).isEmpty)) {
+ /// return;
+ /// }
+ /// }
+ /// }
+ /// ```
+ Future<bool> withTransaction(
+ Future<bool> Function(StreamQueue<T>) callback) async {
+ var transaction = startTransaction();
+
+ var queue = transaction.newQueue();
+ bool result;
+ try {
+ result = await callback(queue);
+ } catch (_) {
+ transaction.commit(queue);
+ rethrow;
+ }
+ if (result) {
+ transaction.commit(queue);
+ } else {
+ transaction.reject();
+ }
+ return result;
+ }
+
+ /// Passes a copy of this queue to [callback], and updates this queue to match
+ /// the copy's position once [callback] completes.
+ ///
+ /// If the returned [CancelableOperation] is canceled, this queue instead
+ /// continues as though [cancelable] hadn't been called. Otherwise, it emits
+ /// the same value or error as [callback].
+ ///
+ /// See also [startTransaction] and [withTransaction].
+ ///
+ /// ```dart
+ /// final _stdinQueue = StreamQueue(stdin);
+ ///
+ /// /// Returns an operation that completes when the user sends a line to
+ /// /// standard input.
+ /// ///
+ /// /// If the operation is canceled, stops waiting for user input.
+ /// CancelableOperation<String> nextStdinLine() =>
+ /// _stdinQueue.cancelable((queue) => queue.next);
+ /// ```
+ CancelableOperation<S> cancelable<S>(
+ Future<S> Function(StreamQueue<T>) callback) {
+ var transaction = startTransaction();
+ var completer = CancelableCompleter<S>(onCancel: () {
+ transaction.reject();
+ });
+
+ var queue = transaction.newQueue();
+ completer.complete(callback(queue).whenComplete(() {
+ if (!completer.isCanceled) transaction.commit(queue);
+ }));
+
+ return completer.operation;
+ }
+
+ /// Cancels the underlying event source.
+ ///
+ /// If [immediate] is `false` (the default), the cancel operation waits until
+ /// all previously requested events have been processed, then it cancels the
+ /// subscription providing the events.
+ ///
+ /// If [immediate] is `true`, the source is instead canceled
+ /// immediately. Any pending events are completed as though the underlying
+ /// stream had closed.
+ ///
+ /// The returned future completes with the result of calling
+ /// `cancel` on the subscription to the source stream.
+ ///
+ /// After calling `cancel`, no further events can be requested.
+ /// None of [lookAhead], [next], [peek], [rest], [skip], [take] or [cancel]
+ /// may be called again.
+ Future? cancel({bool immediate = false}) {
+ _checkNotClosed();
+ _isClosed = true;
+
+ if (!immediate) {
+ var request = _CancelRequest<T>(this);
+ _addRequest(request);
+ return request.future;
+ }
+
+ if (_isDone && _eventQueue.isEmpty) return Future.value();
+ return _cancel();
+ }
+
+ // ------------------------------------------------------------------
+ // Methods that may be called from the request implementations to
+ // control the event stream.
+
+ /// Matches events with requests.
+ ///
+ /// Called after receiving an event or when the event source closes.
+ ///
+ /// May be called by requests which have returned `false` (saying they
+ /// are not yet done) so they can be checked again before any new
+ /// events arrive.
+ /// Any request returing `false` from `update` when `isDone` is `true`
+ /// *must* call `_updateRequests` when they are ready to continue
+ /// (since no further events will trigger the call).
+ void _updateRequests() {
+ while (_requestQueue.isNotEmpty) {
+ if (_requestQueue.first.update(_eventQueue, _isDone)) {
+ _requestQueue.removeFirst();
+ } else {
+ return;
+ }
+ }
+
+ if (!_isDone) {
+ _pause();
+ }
+ }
+
+ /// Extracts a stream from the event source and makes this stream queue
+ /// unusable.
+ ///
+ /// Can only be used by the very last request (the stream queue must
+ /// be closed by that request).
+ /// Only used by [rest].
+ Stream<T> _extractStream() {
+ assert(_isClosed);
+ if (_isDone) {
+ return Stream<T>.empty();
+ }
+ _isDone = true;
+
+ var subscription = _subscription;
+ if (subscription == null) {
+ return _source;
+ }
+ _subscription = null;
+
+ var wasPaused = subscription.isPaused;
+ var result = SubscriptionStream<T>(subscription);
+ // Resume after creating stream because that pauses the subscription too.
+ // This way there won't be a short resumption in the middle.
+ if (wasPaused) subscription.resume();
+ return result;
+ }
+
+ /// Requests that the event source pauses events.
+ ///
+ /// This is called automatically when the request queue is empty.
+ ///
+ /// The event source is restarted by the next call to [_ensureListening].
+ void _pause() {
+ _subscription!.pause();
+ }
+
+ /// Ensures that we are listening on events from the event source.
+ ///
+ /// Starts listening for the first time or resumes after a [_pause].
+ ///
+ /// Is called automatically if a request requires more events.
+ void _ensureListening() {
+ if (_isDone) return;
+ if (_subscription == null) {
+ _subscription = _source.listen((data) {
+ _addResult(Result.value(data));
+ }, onError: (Object error, StackTrace stackTrace) {
+ _addResult(Result.error(error, stackTrace));
+ }, onDone: () {
+ _subscription = null;
+ _close();
+ });
+ } else {
+ _subscription!.resume();
+ }
+ }
+
+ /// Cancels the underlying event source.
+ Future? _cancel() {
+ if (_isDone) return null;
+ _subscription ??= _source.listen(null);
+ var future = _subscription!.cancel();
+ _close();
+ return future;
+ }
+
+ // ------------------------------------------------------------------
+ // Methods called by the event source to add events or say that it's
+ // done.
+
+ /// Called when the event source adds a new data or error event.
+ /// Always calls [_updateRequests] after adding.
+ void _addResult(Result<T> result) {
+ _eventsReceived++;
+ _eventQueue.add(result);
+ _updateRequests();
+ }
+
+ /// Called when the event source is done.
+ /// Always calls [_updateRequests] after adding.
+ void _close() {
+ _isDone = true;
+ _updateRequests();
+ }
+
+ // ------------------------------------------------------------------
+ // Internal helper methods.
+
+ /// Throws an error if [cancel] or [rest] have already been called.
+ void _checkNotClosed() {
+ if (_isClosed) throw StateError('Already cancelled');
+ }
+
+ /// Adds a new request to the queue.
+ ///
+ /// If the request queue is empty and the request can be completed
+ /// immediately, it skips the queue.
+ void _addRequest(_EventRequest<T> request) {
+ if (_requestQueue.isEmpty) {
+ if (request.update(_eventQueue, _isDone)) return;
+ _ensureListening();
+ }
+ _requestQueue.add(request);
+ }
+}
+
+/// A transaction on a [StreamQueue], created by [StreamQueue.startTransaction].
+///
+/// Copies of the parent queue may be created using [newQueue]. Calling [commit]
+/// moves the parent queue to a copy's position, and calling [reject] causes it
+/// to continue as though [StreamQueue.startTransaction] was never called.
+class StreamQueueTransaction<T> {
+ /// The parent queue on which this transaction is active.
+ final StreamQueue<T> _parent;
+
+ /// The splitter that produces copies of the parent queue's stream.
+ final StreamSplitter<T> _splitter;
+
+ /// Queues created using [newQueue].
+ final _queues = <StreamQueue>{};
+
+ /// Whether [commit] has been called.
+ var _committed = false;
+
+ /// Whether [reject] has been called.
+ var _rejected = false;
+
+ StreamQueueTransaction._(this._parent, Stream<T> source)
+ : _splitter = StreamSplitter(source);
+
+ /// Creates a new copy of the parent queue.
+ ///
+ /// This copy starts at the parent queue's position when
+ /// [StreamQueue.startTransaction] was called. Its position can be committed
+ /// to the parent queue using [commit].
+ StreamQueue<T> newQueue() {
+ var queue = StreamQueue(_splitter.split());
+ _queues.add(queue);
+ return queue;
+ }
+
+ /// Commits a queue created using [newQueue].
+ ///
+ /// The parent queue's position is updated to be the same as [queue]'s.
+ /// Further requests on all queues created by this transaction, including
+ /// [queue], will complete as though `cancel` were called with `immediate:
+ /// true`.
+ ///
+ /// Throws a [StateError] if [commit] or [reject] have already been called, or
+ /// if there are pending requests on [queue].
+ void commit(StreamQueue<T> queue) {
+ _assertActive();
+ if (!_queues.contains(queue)) {
+ throw ArgumentError("Queue doesn't belong to this transaction.");
+ } else if (queue._requestQueue.isNotEmpty) {
+ throw StateError("A queue with pending requests can't be committed.");
+ }
+ _committed = true;
+
+ // Remove all events from the parent queue that were consumed by the
+ // child queue.
+ for (var j = 0; j < queue.eventsDispatched; j++) {
+ _parent._eventQueue.removeFirst();
+ }
+
+ _done();
+ }
+
+ /// Rejects this transaction without updating the parent queue.
+ ///
+ /// The parent will continue as though [StreamQueue.startTransaction] hadn't
+ /// been called. Further requests on all queues created by this transaction
+ /// will complete as though `cancel` were called with `immediate: true`.
+ ///
+ /// Throws a [StateError] if [commit] or [reject] have already been called.
+ void reject() {
+ _assertActive();
+ _rejected = true;
+ _done();
+ }
+
+ // Cancels all [_queues], removes the [_TransactionRequest] from [_parent]'s
+ // request queue, and runs the next request.
+ void _done() {
+ _splitter.close();
+ for (var queue in _queues) {
+ queue._cancel();
+ }
+ // If this is the active request in the queue, mark it as finished.
+ var currentRequest = _parent._requestQueue.first;
+ if (currentRequest is _TransactionRequest &&
+ currentRequest.transaction == this) {
+ _parent._requestQueue.removeFirst();
+ _parent._updateRequests();
+ }
+ }
+
+ /// Throws a [StateError] if [commit] or [reject] has already been called.
+ void _assertActive() {
+ if (_committed) {
+ throw StateError('This transaction has already been accepted.');
+ } else if (_rejected) {
+ throw StateError('This transaction has already been rejected.');
+ }
+ }
+}
+
+/// Request object that receives events when they arrive, until fulfilled.
+///
+/// Each request that cannot be fulfilled immediately is represented by
+/// an `_EventRequest` object in the request queue.
+///
+/// Events from the source stream are sent to the first request in the
+/// queue until it reports itself as `isComplete`.
+///
+/// When the first request in the queue `isComplete`, either when becoming
+/// the first request or after receiving an event, its `close` methods is
+/// called.
+///
+/// The `close` method is also called immediately when the source stream
+/// is done.
+abstract class _EventRequest<T> {
+ /// Handle available events.
+ ///
+ /// The available events are provided as a queue. The `update` function
+ /// should only remove events from the front of the event queue, e.g.,
+ /// using `removeFirst`.
+ ///
+ /// Returns `true` if the request is completed, or `false` if it needs
+ /// more events.
+ /// The call may keep events in the queue until the requeust is complete,
+ /// or it may remove them immediately.
+ ///
+ /// If the method returns true, the request is considered fulfilled, and
+ /// will never be called again.
+ ///
+ /// This method is called when a request reaches the front of the request
+ /// queue, and if it returns `false`, it's called again every time a new event
+ /// becomes available, or when the stream closes.
+ /// If the function returns `false` when the stream has already closed
+ /// ([isDone] is true), then the request must call
+ /// [StreamQueue._updateRequests] itself when it's ready to continue.
+ bool update(QueueList<Result<T>> events, bool isDone);
+}
+
+/// Request for a [StreamQueue.next] call.
+///
+/// Completes the returned future when receiving the first event,
+/// and is then complete.
+class _NextRequest<T> implements _EventRequest<T> {
+ /// Completer for the future returned by [StreamQueue.next].
+ final _completer = Completer<T>();
+
+ _NextRequest();
+
+ Future<T> get future => _completer.future;
+
+ @override
+ bool update(QueueList<Result<T>> events, bool isDone) {
+ if (events.isNotEmpty) {
+ events.removeFirst().complete(_completer);
+ return true;
+ }
+ if (isDone) {
+ _completer.completeError(StateError('No elements'), StackTrace.current);
+ return true;
+ }
+ return false;
+ }
+}
+
+/// Request for a [StreamQueue.peek] call.
+///
+/// Completes the returned future when receiving the first event,
+/// and is then complete, but doesn't consume the event.
+class _PeekRequest<T> implements _EventRequest<T> {
+ /// Completer for the future returned by [StreamQueue.next].
+ final _completer = Completer<T>();
+
+ _PeekRequest();
+
+ Future<T> get future => _completer.future;
+
+ @override
+ bool update(QueueList<Result<T>> events, bool isDone) {
+ if (events.isNotEmpty) {
+ events.first.complete(_completer);
+ return true;
+ }
+ if (isDone) {
+ _completer.completeError(StateError('No elements'), StackTrace.current);
+ return true;
+ }
+ return false;
+ }
+}
+
+/// Request for a [StreamQueue.skip] call.
+class _SkipRequest<T> implements _EventRequest<T> {
+ /// Completer for the future returned by the skip call.
+ final _completer = Completer<int>();
+
+ /// Number of remaining events to skip.
+ ///
+ /// The request `isComplete` when the values reaches zero.
+ ///
+ /// Decremented when an event is seen.
+ /// Set to zero when an error is seen since errors abort the skip request.
+ int _eventsToSkip;
+
+ _SkipRequest(this._eventsToSkip);
+
+ /// The future completed when the correct number of events have been skipped.
+ Future<int> get future => _completer.future;
+
+ @override
+ bool update(QueueList<Result<T>> events, bool isDone) {
+ while (_eventsToSkip > 0) {
+ if (events.isEmpty) {
+ if (isDone) break;
+ return false;
+ }
+ _eventsToSkip--;
+
+ var event = events.removeFirst();
+ if (event.isError) {
+ _completer.completeError(
+ event.asError!.error, event.asError!.stackTrace);
+ return true;
+ }
+ }
+ _completer.complete(_eventsToSkip);
+ return true;
+ }
+}
+
+/// Common superclass for [_TakeRequest] and [_LookAheadRequest].
+abstract class _ListRequest<T> implements _EventRequest<T> {
+ /// Completer for the future returned by the take call.
+ final _completer = Completer<List<T>>();
+
+ /// List collecting events until enough have been seen.
+ final _list = <T>[];
+
+ /// Number of events to capture.
+ ///
+ /// The request `isComplete` when the length of [_list] reaches
+ /// this value.
+ final int _eventsToTake;
+
+ _ListRequest(this._eventsToTake);
+
+ /// The future completed when the correct number of events have been captured.
+ Future<List<T>> get future => _completer.future;
+}
+
+/// Request for a [StreamQueue.take] call.
+class _TakeRequest<T> extends _ListRequest<T> {
+ _TakeRequest(super.eventsToTake);
+
+ @override
+ bool update(QueueList<Result<T>> events, bool isDone) {
+ while (_list.length < _eventsToTake) {
+ if (events.isEmpty) {
+ if (isDone) break;
+ return false;
+ }
+
+ var event = events.removeFirst();
+ if (event.isError) {
+ event.asError!.complete(_completer);
+ return true;
+ }
+ _list.add(event.asValue!.value);
+ }
+ _completer.complete(_list);
+ return true;
+ }
+}
+
+/// Request for a [StreamQueue.lookAhead] call.
+class _LookAheadRequest<T> extends _ListRequest<T> {
+ _LookAheadRequest(super.eventsToTake);
+
+ @override
+ bool update(QueueList<Result<T>> events, bool isDone) {
+ while (_list.length < _eventsToTake) {
+ if (events.length == _list.length) {
+ if (isDone) break;
+ return false;
+ }
+ var event = events.elementAt(_list.length);
+ if (event.isError) {
+ event.asError!.complete(_completer);
+ return true;
+ }
+ _list.add(event.asValue!.value);
+ }
+ _completer.complete(_list);
+ return true;
+ }
+}
+
+/// Request for a [StreamQueue.cancel] call.
+///
+/// The request needs no events, it just waits in the request queue
+/// until all previous events are fulfilled, then it cancels the stream queue
+/// source subscription.
+class _CancelRequest<T> implements _EventRequest<T> {
+ /// Completer for the future returned by the `cancel` call.
+ final _completer = Completer<void>();
+
+ /// When the event is completed, it needs to cancel the active subscription
+ /// of the `StreamQueue` object, if any.
+ final StreamQueue _streamQueue;
+
+ _CancelRequest(this._streamQueue);
+
+ /// The future completed when the cancel request is completed.
+ Future get future => _completer.future;
+
+ @override
+ bool update(QueueList<Result<T>> events, bool isDone) {
+ if (_streamQueue._isDone) {
+ _completer.complete();
+ } else {
+ _streamQueue._ensureListening();
+ _completer.complete(_streamQueue._extractStream().listen(null).cancel());
+ }
+ return true;
+ }
+}
+
+/// Request for a [StreamQueue.rest] call.
+///
+/// The request is always complete, it just waits in the request queue
+/// until all previous events are fulfilled, then it takes over the
+/// stream events subscription and creates a stream from it.
+class _RestRequest<T> implements _EventRequest<T> {
+ /// Completer for the stream returned by the `rest` call.
+ final _completer = StreamCompleter<T>();
+
+ /// The [StreamQueue] object that has this request queued.
+ ///
+ /// When the event is completed, it needs to cancel the active subscription
+ /// of the `StreamQueue` object, if any.
+ final StreamQueue<T> _streamQueue;
+
+ _RestRequest(this._streamQueue);
+
+ /// The stream which will contain the remaining events of [_streamQueue].
+ Stream<T> get stream => _completer.stream;
+
+ @override
+ bool update(QueueList<Result<T>> events, bool isDone) {
+ if (events.isEmpty) {
+ if (_streamQueue._isDone) {
+ _completer.setEmpty();
+ } else {
+ _completer.setSourceStream(_streamQueue._extractStream());
+ }
+ } else {
+ // There are prefetched events which needs to be added before the
+ // remaining stream.
+ var controller = StreamController<T>();
+ for (var event in events) {
+ event.addTo(controller);
+ }
+ controller
+ .addStream(_streamQueue._extractStream(), cancelOnError: false)
+ .whenComplete(controller.close);
+ _completer.setSourceStream(controller.stream);
+ }
+ return true;
+ }
+}
+
+/// Request for a [StreamQueue.hasNext] call.
+///
+/// Completes the [future] with `true` if it sees any event,
+/// but doesn't consume the event.
+/// If the request is closed without seeing an event, then
+/// the [future] is completed with `false`.
+class _HasNextRequest<T> implements _EventRequest<T> {
+ final _completer = Completer<bool>();
+
+ Future<bool> get future => _completer.future;
+
+ @override
+ bool update(QueueList<Result<T>> events, bool isDone) {
+ if (events.isNotEmpty) {
+ _completer.complete(true);
+ return true;
+ }
+ if (isDone) {
+ _completer.complete(false);
+ return true;
+ }
+ return false;
+ }
+}
+
+/// Request for a [StreamQueue.startTransaction] call.
+///
+/// This request isn't complete until the user calls
+/// [StreamQueueTransaction.commit] or [StreamQueueTransaction.reject], at which
+/// point it manually removes itself from the request queue and calls
+/// [StreamQueue._updateRequests].
+class _TransactionRequest<T> implements _EventRequest<T> {
+ /// The transaction created by this request.
+ late final StreamQueueTransaction<T> transaction;
+
+ /// The controller that passes events to [transaction].
+ final _controller = StreamController<T>(sync: true);
+
+ /// The number of events passed to [_controller] so far.
+ var _eventsSent = 0;
+
+ _TransactionRequest(StreamQueue<T> parent) {
+ transaction = StreamQueueTransaction._(parent, _controller.stream);
+ }
+
+ @override
+ bool update(QueueList<Result<T>> events, bool isDone) {
+ while (_eventsSent < events.length) {
+ events[_eventsSent++].addTo(_controller);
+ }
+ if (isDone && !_controller.isClosed) _controller.close();
+ return transaction._committed || transaction._rejected;
+ }
+}
diff --git a/pkgs/async/lib/src/stream_sink_completer.dart b/pkgs/async/lib/src/stream_sink_completer.dart
new file mode 100644
index 0000000..dc2f6df
--- /dev/null
+++ b/pkgs/async/lib/src/stream_sink_completer.dart
@@ -0,0 +1,180 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'null_stream_sink.dart';
+
+/// A [sink] where the destination is provided later.
+///
+/// The [sink] is a normal sink that you can add events to to immediately, but
+/// until [setDestinationSink] is called, the events will be buffered.
+///
+/// The same effect can be achieved by using a [StreamController] and adding it
+/// to the sink using [StreamConsumer.addStream] when the destination sink is
+/// ready. This
+/// class attempts to shortcut some of the overhead when possible. For example,
+/// if the [sink] only has events added after the destination sink has been set,
+/// those events are added directly to the sink.
+class StreamSinkCompleter<T> {
+ /// The sink for this completer.
+ ///
+ /// When a destination sink is provided, events that have been passed to the
+ /// sink will be forwarded to the destination.
+ ///
+ /// Events can be added to the sink either before or after a destination sink
+ /// is set.
+ final StreamSink<T> sink = _CompleterSink<T>();
+
+ /// Returns [sink] typed as a [_CompleterSink].
+ _CompleterSink<T> get _sink => sink as _CompleterSink<T>;
+
+ /// Convert a `Future<StreamSink>` to a `StreamSink`.
+ ///
+ /// This creates a sink using a sink completer, and sets the destination sink
+ /// to the result of the future when the future completes.
+ ///
+ /// If the future completes with an error, the returned sink will instead
+ /// be closed. Its [StreamSink.done] future will contain the error.
+ static StreamSink<T> fromFuture<T>(Future<StreamSink<T>> sinkFuture) {
+ var completer = StreamSinkCompleter<T>();
+ sinkFuture.then(completer.setDestinationSink, onError: completer.setError);
+ return completer.sink;
+ }
+
+ /// Sets a sink as the destination for events from the [StreamSinkCompleter]'s
+ /// [sink].
+ ///
+ /// The completer's [sink] will act exactly as [destinationSink].
+ ///
+ /// If the destination sink is set before events are added to [sink], further
+ /// events are forwarded directly to [destinationSink].
+ ///
+ /// If events are added to [sink] before setting the destination sink, they're
+ /// buffered until the destination is available.
+ ///
+ /// A destination sink may be set at most once.
+ ///
+ /// Either of [setDestinationSink] or [setError] may be called at most once.
+ /// Trying to call either of them again will fail.
+ void setDestinationSink(StreamSink<T> destinationSink) {
+ if (_sink._destinationSink != null) {
+ throw StateError('Destination sink already set');
+ }
+ _sink._setDestinationSink(destinationSink);
+ }
+
+ /// Completes this to a closed sink whose [StreamSink.done] future emits
+ /// [error].
+ ///
+ /// This is useful when the process of loading the sink fails.
+ ///
+ /// Either of [setDestinationSink] or [setError] may be called at most once.
+ /// Trying to call either of them again will fail.
+ void setError(Object error, [StackTrace? stackTrace]) {
+ setDestinationSink(NullStreamSink.error(error, stackTrace));
+ }
+}
+
+/// [StreamSink] completed by [StreamSinkCompleter].
+class _CompleterSink<T> implements StreamSink<T> {
+ /// Controller for an intermediate sink.
+ ///
+ /// Created if the user adds events to this sink before the destination sink
+ /// is set.
+ StreamController<T>? _controller;
+
+ /// Completer for [done].
+ ///
+ /// Created if the user requests the [done] future before the destination sink
+ /// is set.
+ Completer? _doneCompleter;
+
+ /// Destination sink for the events added to this sink.
+ ///
+ /// Set when [StreamSinkCompleter.setDestinationSink] is called.
+ StreamSink<T>? _destinationSink;
+
+ /// Whether events should be sent directly to [_destinationSink], as opposed
+ /// to going through [_controller].
+ bool get _canSendDirectly => _controller == null && _destinationSink != null;
+
+ @override
+ Future get done {
+ if (_doneCompleter != null) return _doneCompleter!.future;
+ if (_destinationSink == null) {
+ _doneCompleter = Completer.sync();
+ return _doneCompleter!.future;
+ }
+ return _destinationSink!.done;
+ }
+
+ @override
+ void add(T event) {
+ if (_canSendDirectly) {
+ _destinationSink!.add(event);
+ } else {
+ _ensureController().add(event);
+ }
+ }
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ if (_canSendDirectly) {
+ _destinationSink!.addError(error, stackTrace);
+ } else {
+ _ensureController().addError(error, stackTrace);
+ }
+ }
+
+ @override
+ Future addStream(Stream<T> stream) {
+ if (_canSendDirectly) return _destinationSink!.addStream(stream);
+
+ return _ensureController().addStream(stream, cancelOnError: false);
+ }
+
+ @override
+ Future close() {
+ if (_canSendDirectly) {
+ _destinationSink!.close();
+ } else {
+ _ensureController().close();
+ }
+ return done;
+ }
+
+ /// Create [_controller] if it doesn't yet exist.
+ StreamController<T> _ensureController() {
+ return _controller ??= StreamController(sync: true);
+ }
+
+ /// Sets the destination sink to which events from this sink will be provided.
+ ///
+ /// If set before the user adds events, events will be added directly to the
+ /// destination sink. If the user adds events earlier, an intermediate sink is
+ /// created using a stream controller, and the destination sink is linked to
+ /// it later.
+ void _setDestinationSink(StreamSink<T> sink) {
+ assert(_destinationSink == null);
+ _destinationSink = sink;
+
+ // If the user has already added data, it's buffered in the controller, so
+ // we add it to the sink.
+ if (_controller != null) {
+ // Catch any error that may come from [addStream] or [sink.close]. They'll
+ // be reported through [done] anyway.
+ sink
+ .addStream(_controller!.stream)
+ .whenComplete(sink.close)
+ .catchError((_) {});
+ }
+
+ // If the user has already asked when the sink is done, connect the sink's
+ // done callback to that completer.
+ if (_doneCompleter != null) {
+ _doneCompleter!.complete(sink.done);
+ }
+ }
+}
diff --git a/pkgs/async/lib/src/stream_sink_extensions.dart b/pkgs/async/lib/src/stream_sink_extensions.dart
new file mode 100644
index 0000000..a82cfe1
--- /dev/null
+++ b/pkgs/async/lib/src/stream_sink_extensions.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'stream_sink_transformer.dart';
+import 'stream_sink_transformer/reject_errors.dart';
+
+/// Extensions on [StreamSink] to make stream transformations more fluent.
+extension StreamSinkExtensions<T> on StreamSink<T> {
+ /// Transforms a [StreamSink] using [transformer].
+ StreamSink<S> transform<S>(StreamSinkTransformer<S, T> transformer) =>
+ transformer.bind(this);
+
+ /// Returns a [StreamSink] that forwards to `this` but rejects errors.
+ ///
+ /// If an error is passed (either by [addError] or [addStream]), the
+ /// underlying sink will be closed and the error will be forwarded to the
+ /// returned sink's [StreamSink.done] future. Further events will be ignored.
+ StreamSink<T> rejectErrors() => RejectErrorsSink(this);
+}
diff --git a/pkgs/async/lib/src/stream_sink_transformer.dart b/pkgs/async/lib/src/stream_sink_transformer.dart
new file mode 100644
index 0000000..c1ed747
--- /dev/null
+++ b/pkgs/async/lib/src/stream_sink_transformer.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'stream_sink_transformer/handler_transformer.dart';
+import 'stream_sink_transformer/stream_transformer_wrapper.dart';
+import 'stream_sink_transformer/typed.dart';
+
+/// A [StreamSinkTransformer] transforms the events being passed to a sink.
+///
+/// This works on the same principle as a [StreamTransformer]. Each transformer
+/// defines a [bind] method that takes in the original [StreamSink] and returns
+/// the transformed version. However, where a [StreamTransformer] transforms
+/// events after they leave the stream, this transforms them before they enter
+/// the sink.
+///
+/// Transformers must be able to have `bind` called used multiple times.
+abstract class StreamSinkTransformer<S, T> {
+ /// Creates a [StreamSinkTransformer] that transforms events and errors
+ /// using [transformer].
+ ///
+ /// This is equivalent to piping all events from the outer sink through a
+ /// stream transformed by [transformer] and from there into the inner sink.
+ const factory StreamSinkTransformer.fromStreamTransformer(
+ StreamTransformer<S, T> transformer) = StreamTransformerWrapper<S, T>;
+
+ /// Creates a [StreamSinkTransformer] that delegates events to the given
+ /// handlers.
+ ///
+ /// The handlers work exactly as they do for [StreamTransformer.fromHandlers].
+ /// They're called for each incoming event, and any actions on the sink
+ /// they're passed are forwarded to the inner sink. If a handler is omitted,
+ /// the event is passed through unaltered.
+ factory StreamSinkTransformer.fromHandlers(
+ {void Function(S, EventSink<T>)? handleData,
+ void Function(Object, StackTrace, EventSink<T>)? handleError,
+ void Function(EventSink<T>)? handleDone}) {
+ return HandlerTransformer<S, T>(handleData, handleError, handleDone);
+ }
+
+ /// Transforms the events passed to [sink].
+ ///
+ /// Creates a new sink. When events are passed to the returned sink, it will
+ /// transform them and pass the transformed versions to [sink].
+ StreamSink<S> bind(StreamSink<T> sink);
+
+ /// Creates a wrapper that coerces the type of [transformer].
+ ///
+ /// This soundly converts a [StreamSinkTransformer] to a
+ /// `StreamSinkTransformer<S, T>`, regardless of its original generic type.
+ /// This means that calls to [StreamSink.add] on the returned sink may throw a
+ /// [TypeError] if the argument type doesn't match the reified type of the
+ /// sink.
+ @Deprecated('Will be removed in future version')
+ // TODO remove TypeSafeStreamSinkTransformer
+ static StreamSinkTransformer<S, T> typed<S, T>(
+ StreamSinkTransformer transformer) =>
+ transformer is StreamSinkTransformer<S, T>
+ ? transformer
+ : TypeSafeStreamSinkTransformer(transformer);
+}
diff --git a/pkgs/async/lib/src/stream_sink_transformer/handler_transformer.dart b/pkgs/async/lib/src/stream_sink_transformer/handler_transformer.dart
new file mode 100644
index 0000000..496c7ca
--- /dev/null
+++ b/pkgs/async/lib/src/stream_sink_transformer/handler_transformer.dart
@@ -0,0 +1,110 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import '../delegate/stream_sink.dart';
+import '../stream_sink_transformer.dart';
+
+/// The type of the callback for handling data events.
+typedef HandleData<S, T> = void Function(S data, EventSink<T> sink);
+
+/// The type of the callback for handling error events.
+typedef HandleError<T> = void Function(Object error, StackTrace, EventSink<T>);
+
+/// The type of the callback for handling done events.
+typedef HandleDone<T> = void Function(EventSink<T> sink);
+
+/// A [StreamSinkTransformer] that delegates events to the given handlers.
+class HandlerTransformer<S, T> implements StreamSinkTransformer<S, T> {
+ /// The handler for data events.
+ final HandleData<S, T>? _handleData;
+
+ /// The handler for error events.
+ final HandleError<T>? _handleError;
+
+ /// The handler for done events.
+ final HandleDone<T>? _handleDone;
+
+ HandlerTransformer(this._handleData, this._handleError, this._handleDone);
+
+ @override
+ StreamSink<S> bind(StreamSink<T> sink) => _HandlerSink<S, T>(this, sink);
+}
+
+/// A sink created by [HandlerTransformer].
+class _HandlerSink<S, T> implements StreamSink<S> {
+ /// The transformer that created this sink.
+ final HandlerTransformer<S, T> _transformer;
+
+ /// The original sink that's being transformed.
+ final StreamSink<T> _inner;
+
+ /// The wrapper for [_inner] whose [StreamSink.close] method can't emit
+ /// errors.
+ final StreamSink<T> _safeCloseInner;
+
+ @override
+ Future get done => _inner.done;
+
+ _HandlerSink(this._transformer, StreamSink<T> inner)
+ : _inner = inner,
+ _safeCloseInner = _SafeCloseSink<T>(inner);
+
+ @override
+ void add(S event) {
+ var handleData = _transformer._handleData;
+ if (handleData == null) {
+ _inner.add(event as T);
+ } else {
+ handleData(event, _safeCloseInner);
+ }
+ }
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ var handleError = _transformer._handleError;
+ if (handleError == null) {
+ _inner.addError(error, stackTrace);
+ } else {
+ handleError(error, stackTrace ?? AsyncError.defaultStackTrace(error),
+ _safeCloseInner);
+ }
+ }
+
+ @override
+ Future addStream(Stream<S> stream) {
+ return _inner.addStream(stream.transform(
+ StreamTransformer<S, T>.fromHandlers(
+ handleData: _transformer._handleData,
+ handleError: _transformer._handleError,
+ handleDone: _closeSink)));
+ }
+
+ @override
+ Future close() {
+ var handleDone = _transformer._handleDone;
+ if (handleDone == null) return _inner.close();
+
+ handleDone(_safeCloseInner);
+ return _inner.done;
+ }
+}
+
+/// A wrapper for [StreamSink]s that swallows any errors returned by [close].
+///
+/// [HandlerTransformer] passes this to its handlers to ensure that when they
+/// call [close], they don't leave any dangling [Future]s behind that might emit
+/// unhandleable errors.
+class _SafeCloseSink<T> extends DelegatingStreamSink<T> {
+ _SafeCloseSink(super.inner);
+
+ @override
+ Future close() => super.close().catchError((_) {});
+}
+
+/// A function to pass as a [StreamTransformer]'s `handleDone` callback.
+void _closeSink(EventSink sink) {
+ sink.close();
+}
diff --git a/pkgs/async/lib/src/stream_sink_transformer/reject_errors.dart b/pkgs/async/lib/src/stream_sink_transformer/reject_errors.dart
new file mode 100644
index 0000000..6d077f4
--- /dev/null
+++ b/pkgs/async/lib/src/stream_sink_transformer/reject_errors.dart
@@ -0,0 +1,130 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// A [StreamSink] wrapper that rejects all errors passed into the sink.
+class RejectErrorsSink<T> implements StreamSink<T> {
+ /// The target sink.
+ final StreamSink<T> _inner;
+
+ @override
+ Future<void> get done => _doneCompleter.future;
+ final _doneCompleter = Completer<void>();
+
+ /// Whether the user has called [close].
+ ///
+ /// If [_closed] is true, [_canceled] must be true and [_inAddStream] must be
+ /// false.
+ bool _closed = false;
+
+ /// The subscription to the stream passed to [addStream], if a stream is
+ /// currently being added.
+ StreamSubscription<T>? _addStreamSubscription;
+
+ /// The completer for the future returned by [addStream], if a stream is
+ /// currently being added.
+ Completer<void>? _addStreamCompleter;
+
+ /// Whether we're currently adding a stream with [addStream].
+ bool get _inAddStream => _addStreamSubscription != null;
+
+ RejectErrorsSink(this._inner) {
+ _inner.done.then((value) {
+ _cancelAddStream();
+ if (!_canceled) _doneCompleter.complete(value);
+ }).onError<Object>((error, stackTrace) {
+ _cancelAddStream();
+ if (!_canceled) _doneCompleter.completeError(error, stackTrace);
+ });
+ }
+
+ /// Whether the underlying sink is no longer receiving events.
+ ///
+ /// This can happen if:
+ ///
+ /// * [close] has been called,
+ /// * an error has been passed,
+ /// * or the underlying [StreamSink.done] has completed.
+ ///
+ /// If [_canceled] is true, [_inAddStream] must be false.
+ bool get _canceled => _doneCompleter.isCompleted;
+
+ @override
+ void add(T data) {
+ if (_closed) throw StateError('Cannot add event after closing.');
+ if (_inAddStream) {
+ throw StateError('Cannot add event while adding stream.');
+ }
+ if (_canceled) return;
+
+ _inner.add(data);
+ }
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ if (_closed) throw StateError('Cannot add event after closing.');
+ if (_inAddStream) {
+ throw StateError('Cannot add event while adding stream.');
+ }
+ if (_canceled) return;
+
+ _addError(error, stackTrace);
+ }
+
+ /// Like [addError], but doesn't check to ensure that an error can be added.
+ ///
+ /// This is called from [addStream], so it shouldn't fail if a stream is being
+ /// added.
+ void _addError(Object error, [StackTrace? stackTrace]) {
+ _cancelAddStream();
+ _doneCompleter.completeError(error, stackTrace);
+
+ // Ignore errors from the inner sink. We're already surfacing one error, and
+ // if the user handles it we don't want them to have another top-level.
+ _inner.close().catchError((_) {});
+ }
+
+ @override
+ Future<void> addStream(Stream<T> stream) {
+ if (_closed) throw StateError('Cannot add stream after closing.');
+ if (_inAddStream) {
+ throw StateError('Cannot add stream while adding stream.');
+ }
+ if (_canceled) return Future.value();
+
+ var addStreamCompleter = _addStreamCompleter = Completer.sync();
+ _addStreamSubscription = stream.listen(_inner.add,
+ onError: _addError, onDone: addStreamCompleter.complete);
+ return addStreamCompleter.future.then((_) {
+ _addStreamCompleter = null;
+ _addStreamSubscription = null;
+ });
+ }
+
+ @override
+ Future<void> close() {
+ if (_inAddStream) {
+ throw StateError('Cannot close sink while adding stream.');
+ }
+
+ if (_closed) return done;
+ _closed = true;
+
+ if (!_canceled) {
+ // ignore: void_checks
+ _doneCompleter.complete(_inner.close());
+ }
+ return done;
+ }
+
+ /// If an [addStream] call is active, cancel its subscription and complete its
+ /// completer.
+ void _cancelAddStream() {
+ if (!_inAddStream) return;
+ _addStreamCompleter!.complete(_addStreamSubscription!.cancel());
+ _addStreamCompleter = null;
+ _addStreamSubscription = null;
+ }
+}
diff --git a/pkgs/async/lib/src/stream_sink_transformer/stream_transformer_wrapper.dart b/pkgs/async/lib/src/stream_sink_transformer/stream_transformer_wrapper.dart
new file mode 100644
index 0000000..b30e8ad
--- /dev/null
+++ b/pkgs/async/lib/src/stream_sink_transformer/stream_transformer_wrapper.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import '../stream_sink_transformer.dart';
+
+/// A [StreamSinkTransformer] that wraps a pre-existing [StreamTransformer].
+class StreamTransformerWrapper<S, T> implements StreamSinkTransformer<S, T> {
+ /// The wrapped transformer.
+ final StreamTransformer<S, T> _transformer;
+
+ const StreamTransformerWrapper(this._transformer);
+
+ @override
+ StreamSink<S> bind(StreamSink<T> sink) =>
+ _StreamTransformerWrapperSink<S, T>(_transformer, sink);
+}
+
+/// A sink created by [StreamTransformerWrapper].
+class _StreamTransformerWrapperSink<S, T> implements StreamSink<S> {
+ /// The controller through which events are passed.
+ ///
+ /// This is used to create a stream that can be transformed by the wrapped
+ /// transformer.
+ final _controller = StreamController<S>(sync: true);
+
+ /// The original sink that's being transformed.
+ final StreamSink<T> _inner;
+
+ @override
+ Future get done => _inner.done;
+
+ _StreamTransformerWrapperSink(
+ StreamTransformer<S, T> transformer, this._inner) {
+ _controller.stream
+ .transform(transformer)
+ .listen(_inner.add, onError: _inner.addError, onDone: () {
+ // Ignore any errors that come from this call to [_inner.close]. The
+ // user can access them through [done] or the value returned from
+ // [this.close], and we don't want them to get top-leveled.
+ _inner.close().catchError((_) {});
+ });
+ }
+
+ @override
+ void add(S event) {
+ _controller.add(event);
+ }
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ _controller.addError(error, stackTrace);
+ }
+
+ @override
+ Future addStream(Stream<S> stream) => _controller.addStream(stream);
+
+ @override
+ Future close() {
+ _controller.close();
+ return _inner.done;
+ }
+}
diff --git a/pkgs/async/lib/src/stream_sink_transformer/typed.dart b/pkgs/async/lib/src/stream_sink_transformer/typed.dart
new file mode 100644
index 0000000..4743c94
--- /dev/null
+++ b/pkgs/async/lib/src/stream_sink_transformer/typed.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import '../stream_sink_transformer.dart';
+
+/// A wrapper that coerces the generic type of the sink returned by an inner
+/// transformer to `S`.
+class TypeSafeStreamSinkTransformer<S, T>
+ implements StreamSinkTransformer<S, T> {
+ final StreamSinkTransformer _inner;
+
+ TypeSafeStreamSinkTransformer(this._inner);
+
+ @override
+ StreamSink<S> bind(StreamSink<T> sink) => StreamController(sync: true)
+ ..stream.cast<dynamic>().pipe(_inner.bind(sink));
+}
diff --git a/pkgs/async/lib/src/stream_splitter.dart b/pkgs/async/lib/src/stream_splitter.dart
new file mode 100644
index 0000000..f7377d6
--- /dev/null
+++ b/pkgs/async/lib/src/stream_splitter.dart
@@ -0,0 +1,207 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'future_group.dart';
+import 'result/result.dart';
+
+/// A class that splits a single source stream into an arbitrary number of
+/// (single-subscription) streams (called "branch") that emit the same events.
+///
+/// Each branch will emit all the same values and errors as the source stream,
+/// regardless of which values have been emitted on other branches. This means
+/// that the splitter stores every event that has been emitted so far, which may
+/// consume a lot of memory. The user can call [close] to indicate that no more
+/// branches will be created, and this memory will be released.
+///
+/// The source stream is only listened to once a branch is created *and listened
+/// to*. It's paused when all branches are paused *or when all branches are
+/// canceled*, and resumed once there's at least one branch that's listening and
+/// unpaused. It's not canceled unless no branches are listening and [close] has
+/// been called.
+class StreamSplitter<T> {
+ /// The wrapped stream.
+ final Stream<T> _stream;
+
+ /// The subscription to [_stream].
+ ///
+ /// This will be `null` until a branch has a listener.
+ StreamSubscription<T>? _subscription;
+
+ /// The buffer of events or errors that have already been emitted by
+ /// [_stream].
+ final _buffer = <Result<T>>[];
+
+ /// The controllers for branches that are listening for future events from
+ /// [_stream].
+ ///
+ /// Once a branch is canceled, it's removed from this list. When [_stream] is
+ /// done, all branches are removed.
+ final _controllers = <StreamController<T>>{};
+
+ /// A group of futures returned by [close].
+ ///
+ /// This is used to ensure that [close] doesn't complete until all
+ /// [StreamController.close] and [StreamSubscription.cancel] calls complete.
+ final _closeGroup = FutureGroup();
+
+ /// Whether [_stream] is done emitting events.
+ var _isDone = false;
+
+ /// Whether [close] has been called.
+ var _isClosed = false;
+
+ /// Splits [stream] into [count] identical streams.
+ ///
+ /// [count] defaults to 2. This is the same as creating [count] branches and
+ /// then closing the [StreamSplitter].
+ static List<Stream<T>> splitFrom<T>(Stream<T> stream, [int? count]) {
+ count ??= 2;
+ var splitter = StreamSplitter<T>(stream);
+ var streams = List<Stream<T>>.generate(count, (_) => splitter.split());
+ splitter.close();
+ return streams;
+ }
+
+ StreamSplitter(this._stream);
+
+ /// Returns a single-subscription stream that's a copy of the input stream.
+ ///
+ /// This will throw a [StateError] if [close] has been called.
+ Stream<T> split() {
+ if (_isClosed) {
+ throw StateError("Can't call split() on a closed StreamSplitter.");
+ }
+
+ var controller = StreamController<T>(
+ onListen: _onListen, onPause: _onPause, onResume: _onResume);
+ controller.onCancel = () => _onCancel(controller);
+
+ for (var result in _buffer) {
+ result.addTo(controller);
+ }
+
+ if (_isDone) {
+ _closeGroup.add(controller.close());
+ } else {
+ _controllers.add(controller);
+ }
+
+ return controller.stream;
+ }
+
+ /// Indicates that no more branches will be requested via [split].
+ ///
+ /// This clears the internal buffer of events. If there are no branches or all
+ /// branches have been canceled, this cancels the subscription to the input
+ /// stream.
+ ///
+ /// Returns a [Future] that completes once all events have been processed by
+ /// all branches and (if applicable) the subscription to the input stream has
+ /// been canceled.
+ Future close() {
+ if (_isClosed) return _closeGroup.future;
+ _isClosed = true;
+
+ _buffer.clear();
+ if (_controllers.isEmpty) _cancelSubscription();
+
+ return _closeGroup.future;
+ }
+
+ /// Cancel [_subscription] and close [_closeGroup].
+ ///
+ /// This should be called after all the branches' subscriptions have been
+ /// canceled and the splitter has been closed. In that case, we won't use the
+ /// events from [_subscription] any more, since there's nothing to pipe them
+ /// to and no more branches will be created. If [_subscription] is done,
+ /// canceling it will be a no-op.
+ ///
+ /// This may also be called before any branches have been created, in which
+ /// case [_subscription] will be `null`.
+ void _cancelSubscription() {
+ assert(_controllers.isEmpty);
+ assert(_isClosed);
+
+ Future? future;
+ if (_subscription != null) future = _subscription!.cancel();
+ if (future != null) _closeGroup.add(future);
+ _closeGroup.close();
+ }
+
+ // StreamController events
+
+ /// Subscribe to [_stream] if we haven't yet done so, and resume the
+ /// subscription if we have.
+ void _onListen() {
+ if (_isDone) return;
+
+ if (_subscription != null) {
+ // Resume the subscription in case it was paused, either because all the
+ // controllers were paused or because the last one was canceled. If it
+ // wasn't paused, this will be a no-op.
+ _subscription!.resume();
+ } else {
+ _subscription =
+ _stream.listen(_onData, onError: _onError, onDone: _onDone);
+ }
+ }
+
+ /// Pauses [_subscription] if every controller is paused.
+ void _onPause() {
+ if (!_controllers.every((controller) => controller.isPaused)) return;
+ _subscription!.pause();
+ }
+
+ /// Resumes [_subscription].
+ ///
+ /// If [_subscription] wasn't paused, this is a no-op.
+ void _onResume() {
+ _subscription!.resume();
+ }
+
+ /// Removes [controller] from [_controllers] and cancels or pauses
+ /// [_subscription] as appropriate.
+ ///
+ /// Since the controller emitting a done event will cause it to register as
+ /// canceled, this is the only way that a controller is ever removed from
+ /// [_controllers].
+ void _onCancel(StreamController controller) {
+ _controllers.remove(controller);
+ if (_controllers.isNotEmpty) return;
+
+ if (_isClosed) {
+ _cancelSubscription();
+ } else {
+ _subscription!.pause();
+ }
+ }
+
+ // Stream events
+
+ /// Buffers [data] and passes it to [_controllers].
+ void _onData(T data) {
+ if (!_isClosed) _buffer.add(Result.value(data));
+ for (var controller in _controllers) {
+ controller.add(data);
+ }
+ }
+
+ /// Buffers [error] and passes it to [_controllers].
+ void _onError(Object error, StackTrace stackTrace) {
+ if (!_isClosed) _buffer.add(Result.error(error, stackTrace));
+ for (var controller in _controllers) {
+ controller.addError(error, stackTrace);
+ }
+ }
+
+ /// Marks [_controllers] as done.
+ void _onDone() {
+ _isDone = true;
+ for (var controller in _controllers) {
+ _closeGroup.add(controller.close());
+ }
+ }
+}
diff --git a/pkgs/async/lib/src/stream_subscription_transformer.dart b/pkgs/async/lib/src/stream_subscription_transformer.dart
new file mode 100644
index 0000000..d03ea70
--- /dev/null
+++ b/pkgs/async/lib/src/stream_subscription_transformer.dart
@@ -0,0 +1,114 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'async_memoizer.dart';
+
+typedef _AsyncHandler<T> = Future Function(StreamSubscription<T> inner);
+
+typedef _VoidHandler<T> = void Function(StreamSubscription<T> inner);
+
+/// Creates a [StreamTransformer] that modifies the behavior of subscriptions to
+/// a stream.
+///
+/// When [StreamSubscription.cancel], [StreamSubscription.pause], or
+/// [StreamSubscription.resume] is called, the corresponding handler is invoked.
+/// By default, handlers just forward to the underlying subscription.
+///
+/// Guarantees that none of the [StreamSubscription] callbacks and none of the
+/// callbacks passed to `subscriptionTransformer()` will be invoked once the
+/// transformed [StreamSubscription] has been canceled and `handleCancel()` has
+/// run. The [handlePause] and [handleResume] are invoked regardless of whether
+/// the subscription is paused already or not.
+///
+/// In order to preserve [StreamSubscription] guarantees, **all callbacks must
+/// synchronously call the corresponding method** on the inner
+/// [StreamSubscription]: [handleCancel] must call `cancel()`, [handlePause]
+/// must call `pause()`, and [handleResume] must call `resume()`.
+StreamTransformer<T, T> subscriptionTransformer<T>(
+ {Future Function(StreamSubscription<T>)? handleCancel,
+ void Function(StreamSubscription<T>)? handlePause,
+ void Function(StreamSubscription<T>)? handleResume}) {
+ return StreamTransformer((stream, cancelOnError) {
+ return _TransformedSubscription(
+ stream.listen(null, cancelOnError: cancelOnError),
+ handleCancel ?? (inner) => inner.cancel(),
+ handlePause ??
+ (inner) {
+ inner.pause();
+ },
+ handleResume ??
+ (inner) {
+ inner.resume();
+ });
+ });
+}
+
+/// A [StreamSubscription] wrapper that calls callbacks for subscription
+/// methods.
+class _TransformedSubscription<T> implements StreamSubscription<T> {
+ /// The wrapped subscription.
+ StreamSubscription<T>? _inner;
+
+ /// The callback to run when [cancel] is called.
+ final _AsyncHandler<T> _handleCancel;
+
+ /// The callback to run when [pause] is called.
+ final _VoidHandler<T> _handlePause;
+
+ /// The callback to run when [resume] is called.
+ final _VoidHandler<T> _handleResume;
+
+ @override
+ bool get isPaused => _inner?.isPaused ?? false;
+
+ _TransformedSubscription(
+ this._inner, this._handleCancel, this._handlePause, this._handleResume);
+
+ @override
+ void onData(void Function(T)? handleData) {
+ _inner?.onData(handleData);
+ }
+
+ @override
+ void onError(Function? handleError) {
+ _inner?.onError(handleError);
+ }
+
+ @override
+ void onDone(void Function()? handleDone) {
+ _inner?.onDone(handleDone);
+ }
+
+ @override
+ Future cancel() => _cancelMemoizer.runOnce(() {
+ var inner = _inner!;
+ inner.onData(null);
+ inner.onDone(null);
+
+ // Setting onError to null will cause errors to be top-leveled.
+ inner.onError((_, __) {});
+ _inner = null;
+ return _handleCancel(inner);
+ });
+ final _cancelMemoizer = AsyncMemoizer();
+
+ @override
+ void pause([Future? resumeFuture]) {
+ if (_cancelMemoizer.hasRun) return;
+ if (resumeFuture != null) resumeFuture.whenComplete(resume);
+ _handlePause(_inner!);
+ }
+
+ @override
+ void resume() {
+ if (_cancelMemoizer.hasRun) return;
+ _handleResume(_inner!);
+ }
+
+ @override
+ Future<E> asFuture<E>([E? futureValue]) =>
+ _inner?.asFuture(futureValue) ?? Completer<E>().future;
+}
diff --git a/pkgs/async/lib/src/stream_zip.dart b/pkgs/async/lib/src/stream_zip.dart
new file mode 100644
index 0000000..f5b8296
--- /dev/null
+++ b/pkgs/async/lib/src/stream_zip.dart
@@ -0,0 +1,114 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// A stream that combines the values of other streams.
+///
+/// This emits lists of collected values from each input stream. The first list
+/// contains the first value emitted by each stream, the second contains the
+/// second value, and so on. The lists have the same ordering as the iterable
+/// passed to [StreamZip.new].
+///
+/// Any errors from any of the streams are forwarded directly to this stream.
+class StreamZip<T> extends Stream<List<T>> {
+ final Iterable<Stream<T>> _streams;
+
+ StreamZip(Iterable<Stream<T>> streams) : _streams = streams;
+
+ @override
+ StreamSubscription<List<T>> listen(void Function(List<T>)? onData,
+ {Function? onError, void Function()? onDone, bool? cancelOnError}) {
+ cancelOnError = identical(true, cancelOnError);
+ var subscriptions = <StreamSubscription<T>>[];
+ late StreamController<List<T>> controller;
+ late List<T?> current;
+ var dataCount = 0;
+
+ /// Called for each data from a subscription in [subscriptions].
+ void handleData(int index, T data) {
+ current[index] = data;
+ dataCount++;
+ if (dataCount == subscriptions.length) {
+ var data = List<T>.from(current);
+ current = List<T?>.filled(subscriptions.length, null);
+ dataCount = 0;
+ for (var i = 0; i < subscriptions.length; i++) {
+ if (i != index) subscriptions[i].resume();
+ }
+ controller.add(data);
+ } else {
+ subscriptions[index].pause();
+ }
+ }
+
+ /// Called for each error from a subscription in [subscriptions].
+ /// Except if [cancelOnError] is true, in which case the function below
+ /// is used instead.
+ void handleError(Object error, StackTrace stackTrace) {
+ controller.addError(error, stackTrace);
+ }
+
+ /// Called when a subscription has an error and [cancelOnError] is true.
+ ///
+ /// Prematurely cancels all subscriptions since we know that we won't
+ /// be needing any more values.
+ void handleErrorCancel(Object error, StackTrace stackTrace) {
+ for (var i = 0; i < subscriptions.length; i++) {
+ subscriptions[i].cancel();
+ }
+ controller.addError(error, stackTrace);
+ }
+
+ void handleDone() {
+ for (var i = 0; i < subscriptions.length; i++) {
+ subscriptions[i].cancel();
+ }
+ controller.close();
+ }
+
+ try {
+ for (var stream in _streams) {
+ var index = subscriptions.length;
+ subscriptions.add(stream.listen((data) {
+ handleData(index, data);
+ },
+ onError: cancelOnError ? handleError : handleErrorCancel,
+ onDone: handleDone,
+ cancelOnError: cancelOnError));
+ }
+ } catch (e) {
+ for (var i = subscriptions.length - 1; i >= 0; i--) {
+ subscriptions[i].cancel();
+ }
+ rethrow;
+ }
+
+ current = List<T?>.filled(subscriptions.length, null);
+
+ controller = StreamController<List<T>>(onPause: () {
+ for (var i = 0; i < subscriptions.length; i++) {
+ // This may pause some subscriptions more than once.
+ // These will not be resumed by onResume below, but must wait for the
+ // next round.
+ subscriptions[i].pause();
+ }
+ }, onResume: () {
+ for (var i = 0; i < subscriptions.length; i++) {
+ subscriptions[i].resume();
+ }
+ }, onCancel: () {
+ for (var i = 0; i < subscriptions.length; i++) {
+ // Canceling more than once is safe.
+ subscriptions[i].cancel();
+ }
+ });
+
+ if (subscriptions.isEmpty) {
+ controller.close();
+ }
+ return controller.stream.listen(onData,
+ onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+ }
+}
diff --git a/pkgs/async/lib/src/subscription_stream.dart b/pkgs/async/lib/src/subscription_stream.dart
new file mode 100644
index 0000000..9ce4942
--- /dev/null
+++ b/pkgs/async/lib/src/subscription_stream.dart
@@ -0,0 +1,87 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'delegate/stream_subscription.dart';
+
+/// A [Stream] adapter for a [StreamSubscription].
+///
+/// This class allows a `StreamSubscription` to be treated as a `Stream`.
+///
+/// The subscription is paused until the stream is listened to,
+/// then it is resumed and the events are passed on to the
+/// stream's new subscription.
+///
+/// This class assumes that it has control over the original subscription.
+/// If other code is accessing the subscription, results may be unpredictable.
+class SubscriptionStream<T> extends Stream<T> {
+ /// The subscription providing the events for this stream.
+ StreamSubscription<T>? _source;
+
+ /// Create a single-subscription `Stream` from [subscription].
+ ///
+ /// The `subscription` should not be paused. This class will not resume prior
+ /// pauses, so being paused is indistinguishable from not providing any
+ /// events.
+ ///
+ /// If the `subscription` doesn't send any `done` events, neither will this
+ /// stream. That may be an issue if `subscription` was made to cancel on
+ /// an error.
+ SubscriptionStream(StreamSubscription<T> subscription)
+ : _source = subscription {
+ var source = _source!;
+ source.pause();
+ // Clear callbacks to avoid keeping them alive unnecessarily.
+ source.onData(null);
+ source.onError(null);
+ source.onDone(null);
+ }
+
+ @override
+ StreamSubscription<T> listen(void Function(T)? onData,
+ {Function? onError, void Function()? onDone, bool? cancelOnError}) {
+ var subscription = _source;
+ if (subscription == null) {
+ throw StateError('Stream has already been listened to.');
+ }
+ cancelOnError = (true == cancelOnError);
+ _source = null;
+
+ var result = cancelOnError
+ ? _CancelOnErrorSubscriptionWrapper<T>(subscription)
+ : subscription;
+ result.onData(onData);
+ result.onError(onError);
+ result.onDone(onDone);
+ subscription.resume();
+ return result;
+ }
+}
+
+/// Subscription wrapper that cancels on error.
+///
+/// Used by [SubscriptionStream] when forwarding a subscription
+/// created with `cancelOnError` as `true` to one with (assumed)
+/// `cancelOnError` as `false`. It automatically cancels the
+/// source subscription on the first error.
+class _CancelOnErrorSubscriptionWrapper<T>
+ extends DelegatingStreamSubscription<T> {
+ _CancelOnErrorSubscriptionWrapper(super.subscription);
+
+ @override
+ void onError(Function? handleError) {
+ // Cancel when receiving an error.
+ super.onError((Object error, StackTrace stackTrace) {
+ // Wait for the cancel to complete before sending the error event.
+ super.cancel().whenComplete(() {
+ if (handleError is ZoneBinaryCallback) {
+ handleError(error, stackTrace);
+ } else if (handleError != null) {
+ (handleError as ZoneUnaryCallback)(error);
+ }
+ });
+ });
+ }
+}
diff --git a/pkgs/async/lib/src/typed/stream_subscription.dart b/pkgs/async/lib/src/typed/stream_subscription.dart
new file mode 100644
index 0000000..fe91656
--- /dev/null
+++ b/pkgs/async/lib/src/typed/stream_subscription.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+class TypeSafeStreamSubscription<T> implements StreamSubscription<T> {
+ final StreamSubscription _subscription;
+
+ @override
+ bool get isPaused => _subscription.isPaused;
+
+ TypeSafeStreamSubscription(this._subscription);
+
+ @override
+ void onData(void Function(T)? handleData) {
+ if (handleData == null) return _subscription.onData(null);
+ _subscription.onData((data) => handleData(data as T));
+ }
+
+ @override
+ void onError(Function? handleError) {
+ _subscription.onError(handleError);
+ }
+
+ @override
+ void onDone(void Function()? handleDone) {
+ _subscription.onDone(handleDone);
+ }
+
+ @override
+ void pause([Future? resumeFuture]) {
+ _subscription.pause(resumeFuture);
+ }
+
+ @override
+ void resume() {
+ _subscription.resume();
+ }
+
+ @override
+ Future cancel() => _subscription.cancel();
+
+ @override
+ Future<E> asFuture<E>([E? futureValue]) =>
+ _subscription.asFuture(futureValue);
+}
diff --git a/pkgs/async/lib/src/typed_stream_transformer.dart b/pkgs/async/lib/src/typed_stream_transformer.dart
new file mode 100644
index 0000000..8a39228
--- /dev/null
+++ b/pkgs/async/lib/src/typed_stream_transformer.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+/// Creates a wrapper that coerces the type of [transformer].
+///
+/// This soundly converts a [StreamTransformer] to a `StreamTransformer<S, T>`,
+/// regardless of its original generic type, by asserting that the events
+/// emitted by the transformed stream are instances of `T` whenever they're
+/// provided. If they're not, the stream throws a [TypeError].
+@Deprecated('Use Stream.cast after binding a transformer instead')
+StreamTransformer<S, T> typedStreamTransformer<S, T>(
+ StreamTransformer transformer) =>
+ transformer is StreamTransformer<S, T>
+ ? transformer
+ : _TypeSafeStreamTransformer(transformer);
+
+/// A wrapper that coerces the type of the stream returned by an inner
+/// transformer.
+class _TypeSafeStreamTransformer<S, T> extends StreamTransformerBase<S, T> {
+ final StreamTransformer _inner;
+
+ _TypeSafeStreamTransformer(this._inner);
+
+ @override
+ Stream<T> bind(Stream<S> stream) => _inner.bind(stream).cast();
+}
diff --git a/pkgs/async/pubspec.yaml b/pkgs/async/pubspec.yaml
new file mode 100644
index 0000000..be05c85
--- /dev/null
+++ b/pkgs/async/pubspec.yaml
@@ -0,0 +1,21 @@
+name: async
+version: 2.12.0
+description: Utility functions and classes related to the 'dart:async' library.
+repository: https://github.com/dart-lang/core/tree/main/pkgs/async
+issue_tracker: https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aasync
+
+topics:
+ - async
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ collection: ^1.15.0
+ meta: ^1.3.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ fake_async: ^1.2.0
+ stack_trace: ^1.10.0
+ test: ^1.16.6
diff --git a/pkgs/async/test/async_cache_test.dart b/pkgs/async/test/async_cache_test.dart
new file mode 100644
index 0000000..f7c8caa
--- /dev/null
+++ b/pkgs/async/test/async_cache_test.dart
@@ -0,0 +1,189 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: deprecated_member_use_from_same_package
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:fake_async/fake_async.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late AsyncCache<String> cache;
+
+ setUp(() {
+ // Create a cache that is fresh for an hour.
+ cache = AsyncCache(const Duration(hours: 1));
+ });
+
+ test('should fetch via a callback when no cache exists', () async {
+ expect(await cache.fetch(() async => 'Expensive'), 'Expensive');
+ });
+
+ test('should not fetch via callback when a cache exists', () async {
+ await cache.fetch(() async => 'Expensive');
+ expect(await cache.fetch(expectAsync0(() async => 'fake', count: 0)),
+ 'Expensive');
+ });
+
+ group('ephemeral cache', () {
+ test('should not fetch via callback when a future is in-flight', () async {
+ // No actual caching is done, just avoid duplicate requests.
+ cache = AsyncCache.ephemeral();
+
+ var completer = Completer<String>();
+ expect(cache.fetch(() => completer.future), completion('Expensive'));
+ expect(cache.fetch(expectAsync0(() async => 'fake', count: 0)),
+ completion('Expensive'));
+ completer.complete('Expensive');
+ });
+
+ test('should fetch via callback when the in-flight future completes',
+ () async {
+ // No actual caching is done, just avoid duplicate requests.
+ cache = AsyncCache.ephemeral();
+
+ var fetched = cache.fetch(() async => 'first');
+ expect(fetched, completion('first'));
+ expect(
+ cache.fetch(expectAsync0(() async => fail('not called'), count: 0)),
+ completion('first'));
+ await fetched;
+ expect(cache.fetch(() async => 'second'), completion('second'));
+ });
+
+ test('should invalidate even if the future throws an exception', () async {
+ cache = AsyncCache.ephemeral();
+
+ Future<String> throwingCall() async => throw Exception();
+ await expectLater(cache.fetch(throwingCall), throwsA(isException));
+ // To let the timer invalidate the cache
+ await Future<void>.delayed(const Duration(milliseconds: 5));
+
+ Future<String> call() async => 'Completed';
+ expect(await cache.fetch(call), 'Completed', reason: 'Cache invalidates');
+ });
+ });
+
+ test('should fetch via a callback again when cache expires', () {
+ FakeAsync().run((fakeAsync) async {
+ var timesCalled = 0;
+ Future<String> call() async => 'Called ${++timesCalled}';
+ expect(await cache.fetch(call), 'Called 1');
+ expect(await cache.fetch(call), 'Called 1', reason: 'Cache still fresh');
+
+ fakeAsync.elapse(const Duration(hours: 1) - const Duration(seconds: 1));
+ expect(await cache.fetch(call), 'Called 1', reason: 'Cache still fresh');
+
+ fakeAsync.elapse(const Duration(seconds: 1));
+ expect(await cache.fetch(call), 'Called 2');
+ expect(await cache.fetch(call), 'Called 2', reason: 'Cache fresh again');
+
+ fakeAsync.elapse(const Duration(hours: 1));
+ expect(await cache.fetch(call), 'Called 3');
+ });
+ });
+
+ test('should fetch via a callback when manually invalidated', () async {
+ var timesCalled = 0;
+ Future<String> call() async => 'Called ${++timesCalled}';
+ expect(await cache.fetch(call), 'Called 1');
+ cache.invalidate();
+ expect(await cache.fetch(call), 'Called 2');
+ cache.invalidate();
+ expect(await cache.fetch(call), 'Called 3');
+ });
+
+ test('should fetch a stream via a callback', () async {
+ expect(
+ await cache.fetchStream(expectAsync0(() {
+ return Stream.fromIterable(['1', '2', '3']);
+ })).toList(),
+ ['1', '2', '3']);
+ });
+
+ test('should not fetch stream via callback when a cache exists', () async {
+ await cache.fetchStream(() async* {
+ yield '1';
+ yield '2';
+ yield '3';
+ }).toList();
+ expect(
+ await cache.fetchStream(expectAsync0(Stream.empty, count: 0)).toList(),
+ ['1', '2', '3']);
+ });
+
+ test('should not fetch stream via callback when request in flight', () async {
+ // Unlike the above test, we want to verify that we don't make multiple
+ // calls if a cache is being filled currently, and instead wait for that
+ // cache to be completed.
+ var controller = StreamController<String>();
+ Stream<String> call() => controller.stream;
+ expect(cache.fetchStream(call).toList(), completion(['1', '2', '3']));
+ controller.add('1');
+ controller.add('2');
+ await Future.value();
+ expect(cache.fetchStream(call).toList(), completion(['1', '2', '3']));
+ controller.add('3');
+ await controller.close();
+ });
+
+ test('should fetch stream via a callback again when cache expires', () {
+ FakeAsync().run((fakeAsync) async {
+ var timesCalled = 0;
+ Stream<String> call() {
+ return Stream.fromIterable(['Called ${++timesCalled}']);
+ }
+
+ expect(await cache.fetchStream(call).toList(), ['Called 1']);
+ expect(await cache.fetchStream(call).toList(), ['Called 1'],
+ reason: 'Cache still fresh');
+
+ fakeAsync.elapse(const Duration(hours: 1) - const Duration(seconds: 1));
+ expect(await cache.fetchStream(call).toList(), ['Called 1'],
+ reason: 'Cache still fresh');
+
+ fakeAsync.elapse(const Duration(seconds: 1));
+ expect(await cache.fetchStream(call).toList(), ['Called 2']);
+ expect(await cache.fetchStream(call).toList(), ['Called 2'],
+ reason: 'Cache fresh again');
+
+ fakeAsync.elapse(const Duration(hours: 1));
+ expect(await cache.fetchStream(call).toList(), ['Called 3']);
+ });
+ });
+
+ test('should fetch via a callback when manually invalidated', () async {
+ var timesCalled = 0;
+ Stream<String> call() {
+ return Stream.fromIterable(['Called ${++timesCalled}']);
+ }
+
+ expect(await cache.fetchStream(call).toList(), ['Called 1']);
+ cache.invalidate();
+ expect(await cache.fetchStream(call).toList(), ['Called 2']);
+ cache.invalidate();
+ expect(await cache.fetchStream(call).toList(), ['Called 3']);
+ });
+
+ test('should cancel a cached stream without affecting others', () async {
+ Stream<String> call() => Stream.fromIterable(['1', '2', '3']);
+
+ expect(cache.fetchStream(call).toList(), completion(['1', '2', '3']));
+
+ // Listens to the stream for the initial value, then cancels subscription.
+ expect(await cache.fetchStream(call).first, '1');
+ });
+
+ test('should pause a cached stream without affecting others', () async {
+ Stream<String> call() => Stream.fromIterable(['1', '2', '3']);
+
+ late StreamSubscription sub;
+ sub = cache.fetchStream(call).listen(expectAsync1((event) {
+ if (event == '1') sub.pause();
+ }));
+ expect(cache.fetchStream(call).toList(), completion(['1', '2', '3']));
+ });
+}
diff --git a/pkgs/async/test/async_memoizer_test.dart b/pkgs/async/test/async_memoizer_test.dart
new file mode 100644
index 0000000..490b389
--- /dev/null
+++ b/pkgs/async/test/async_memoizer_test.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late AsyncMemoizer cache;
+ setUp(() => cache = AsyncMemoizer());
+
+ test('runs the function only the first time runOnce() is called', () async {
+ var count = 0;
+ expect(await cache.runOnce(() => count++), equals(0));
+ expect(count, equals(1));
+
+ expect(await cache.runOnce(() => count++), equals(0));
+ expect(count, equals(1));
+ });
+
+ test('forwards the return value from the function', () async {
+ expect(cache.future, completion(equals('value')));
+ expect(cache.runOnce(() => 'value'), completion(equals('value')));
+ expect(cache.runOnce(() {}), completion(equals('value')));
+ });
+
+ test('forwards the return value from an async function', () async {
+ expect(cache.future, completion(equals('value')));
+ expect(cache.runOnce(() async => 'value'), completion(equals('value')));
+ expect(cache.runOnce(() {}), completion(equals('value')));
+ });
+
+ test('forwards the error from an async function', () async {
+ expect(cache.future, throwsA('error'));
+ expect(cache.runOnce(() async => throw 'error'), throwsA('error'));
+ expect(cache.runOnce(() {}), throwsA('error'));
+ });
+}
diff --git a/pkgs/async/test/byte_collection_test.dart b/pkgs/async/test/byte_collection_test.dart
new file mode 100644
index 0000000..67f319b
--- /dev/null
+++ b/pkgs/async/test/byte_collection_test.dart
@@ -0,0 +1,90 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('collectBytes', () {
+ test('simple list and overflow', () {
+ var result = collectBytes(Stream.fromIterable([
+ [0],
+ [1],
+ [2],
+ [256]
+ ]));
+ expect(result, completion([0, 1, 2, 0]));
+ });
+
+ test('no events', () {
+ var result = collectBytes(Stream.fromIterable([]));
+ expect(result, completion([]));
+ });
+
+ test('empty events', () {
+ var result = collectBytes(Stream.fromIterable([[], []]));
+ expect(result, completion([]));
+ });
+
+ test('error event', () {
+ var result = collectBytes(Stream.fromIterable(
+ Iterable.generate(3, (n) => n == 2 ? throw 'badness' : [n])));
+ expect(result, throwsA('badness'));
+ });
+ });
+
+ group('collectBytes', () {
+ test('simple list and overflow', () {
+ var result = collectBytesCancelable(Stream.fromIterable([
+ [0],
+ [1],
+ [2],
+ [256]
+ ]));
+ expect(result.value, completion([0, 1, 2, 0]));
+ });
+
+ test('no events', () {
+ var result = collectBytesCancelable(Stream.fromIterable([]));
+ expect(result.value, completion([]));
+ });
+
+ test('empty events', () {
+ var result = collectBytesCancelable(Stream.fromIterable([[], []]));
+ expect(result.value, completion([]));
+ });
+
+ test('error event', () {
+ var result = collectBytesCancelable(Stream.fromIterable(
+ Iterable.generate(3, (n) => n == 2 ? throw 'badness' : [n])));
+ expect(result.value, throwsA('badness'));
+ });
+
+ test('cancelled', () async {
+ var sc = StreamController<List<int>>();
+ var result = collectBytesCancelable(sc.stream);
+ // Value never completes.
+ result.value.whenComplete(expectAsync0(() {}, count: 0));
+
+ expect(sc.hasListener, isTrue);
+ sc.add([1, 2]);
+ await nextTimerTick();
+ expect(sc.hasListener, isTrue);
+ sc.add([3, 4]);
+ await nextTimerTick();
+ expect(sc.hasListener, isTrue);
+ result.cancel();
+ expect(sc.hasListener, isFalse); // Cancelled immediately.
+ var replacement = await result.valueOrCancellation();
+ expect(replacement, isNull);
+ await nextTimerTick();
+ sc.close();
+ await nextTimerTick();
+ });
+ });
+}
+
+Future nextTimerTick() => Future(() {});
diff --git a/pkgs/async/test/cancelable_operation_test.dart b/pkgs/async/test/cancelable_operation_test.dart
new file mode 100644
index 0000000..3b096e4
--- /dev/null
+++ b/pkgs/async/test/cancelable_operation_test.dart
@@ -0,0 +1,934 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: unawaited_futures
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('without being canceled', () {
+ late CancelableCompleter completer;
+ setUp(() {
+ completer = CancelableCompleter(onCancel: expectAsync0(() {}, count: 0));
+ });
+
+ test('sends values to the future', () {
+ expect(completer.operation.value, completion(equals(1)));
+ expect(completer.isCompleted, isFalse);
+ completer.complete(1);
+ expect(completer.isCompleted, isTrue);
+ });
+
+ test('sends null values to the future', () {
+ expect(completer.operation.value, completion(equals(null)));
+ expect(completer.isCompleted, isFalse);
+ completer.complete(null);
+ expect(completer.isCompleted, isTrue);
+ });
+
+ test('sends errors to the future', () {
+ expect(completer.operation.value, throwsA('error'));
+ expect(completer.isCompleted, isFalse);
+ completer.completeError('error');
+ expect(completer.isCompleted, isTrue);
+ });
+
+ test('sends values in a future to the future', () {
+ expect(completer.operation.value, completion(equals(1)));
+ expect(completer.isCompleted, isFalse);
+ completer.complete(Future.value(1));
+ expect(completer.isCompleted, isTrue);
+ });
+
+ test('sends errors in a future to the future', () async {
+ expect(completer.operation.value, throwsA('error'));
+ expect(completer.isCompleted, isFalse);
+ expect(completer.operation.isCompleted, isFalse);
+ completer.complete(Future.error('error'));
+ expect(completer.isCompleted, isTrue);
+ await flushMicrotasks();
+ expect(completer.operation.isCompleted, isTrue);
+ });
+
+ test('sends values from a cancelable operation to the future', () {
+ expect(completer.operation.value, completion(equals(1)));
+ completer
+ .completeOperation(CancelableOperation.fromFuture(Future.value(1)));
+ });
+
+ test('sends values from a completed cancelable operation to the future',
+ () async {
+ final operation = CancelableOperation.fromFuture(Future.value(1));
+ await operation.value;
+ expect(completer.operation.value, completion(equals(1)));
+ completer.completeOperation(operation);
+ });
+
+ test('sends errors from a cancelable operation to the future', () {
+ expect(completer.operation.value, throwsA('error'));
+ completer.completeOperation(
+ CancelableOperation.fromFuture(Future.error('error')..ignore()));
+ });
+
+ test('sends errors from a completed cancelable operation to the future',
+ () async {
+ final operation =
+ CancelableOperation.fromFuture(Future.error('error')..ignore());
+ try {
+ await operation.value;
+ } on Object {
+ // ignore
+ }
+ expect(completer.operation.value, throwsA('error'));
+ completer.completeOperation(operation);
+ });
+
+ test('sends values to valueOrCancellation', () {
+ expect(completer.operation.valueOrCancellation(), completion(equals(1)));
+ completer.complete(1);
+ });
+
+ test('sends errors to valueOrCancellation', () {
+ expect(completer.operation.valueOrCancellation(), throwsA('error'));
+ completer.completeError('error');
+ });
+
+ test('chains null values through .then calls', () async {
+ var operation = CancelableOperation.fromFuture(Future.value(null));
+ expect(await operation.then((_) {}).value, null);
+ });
+
+ test('is not complete until the result is available', () async {
+ var backingWork = Completer<void>();
+ var operation = CancelableOperation.fromFuture(backingWork.future);
+ expect(operation.isCompleted, isFalse);
+ backingWork.complete();
+ await backingWork.future;
+ expect(operation.isCompleted, isTrue);
+ });
+
+ group('throws a StateError if completed', () {
+ test('successfully twice', () {
+ completer.complete(1);
+ expect(() => completer.complete(1), throwsStateError);
+ });
+
+ test('successfully then unsuccessfully', () {
+ completer.complete(1);
+ expect(() => completer.completeError('error'), throwsStateError);
+ });
+
+ test('unsuccessfully twice', () {
+ expect(completer.operation.value, throwsA('error'));
+ completer.completeError('error');
+ expect(() => completer.completeError('error'), throwsStateError);
+ });
+
+ test('successfully then with a future', () {
+ completer.complete(1);
+ expect(() => completer.complete(Completer<void>().future),
+ throwsStateError);
+ });
+
+ test('with a future then successfully', () {
+ completer.complete(Completer<void>().future);
+ expect(() => completer.complete(1), throwsStateError);
+ });
+
+ test('with a future twice', () {
+ completer.complete(Completer<void>().future);
+ expect(() => completer.complete(Completer<void>().future),
+ throwsStateError);
+ });
+ });
+
+ group('CancelableOperation.fromFuture', () {
+ test('forwards values', () {
+ var operation = CancelableOperation.fromFuture(Future.value(1));
+ expect(operation.value, completion(equals(1)));
+ });
+
+ test('forwards errors', () {
+ var operation = CancelableOperation.fromFuture(Future.error('error'));
+ expect(operation.value, throwsA('error'));
+ });
+ });
+
+ group('CancelableOperation.fromSubscription', () {
+ test('forwards a done event once it completes', () async {
+ var controller = StreamController<void>();
+ var operationCompleted = false;
+ CancelableOperation.fromSubscription(controller.stream.listen(null))
+ .then((_) {
+ operationCompleted = true;
+ });
+
+ await flushMicrotasks();
+ expect(operationCompleted, isFalse);
+
+ controller.close();
+ await flushMicrotasks();
+ expect(operationCompleted, isTrue);
+ });
+
+ test('forwards errors', () {
+ var operation = CancelableOperation.fromSubscription(
+ Stream.error('error').listen(null));
+ expect(operation.value, throwsA('error'));
+ });
+ });
+ });
+
+ group('when canceled', () {
+ test('causes the future never to fire', () async {
+ var completer = CancelableCompleter<void>();
+ completer.operation.value.whenComplete(expectAsync0(() {}, count: 0));
+ completer.operation.cancel();
+
+ // Give the future plenty of time to fire if it's going to.
+ await flushMicrotasks();
+ completer.complete();
+ await flushMicrotasks();
+ });
+
+ test('fires onCancel', () {
+ var canceled = false;
+ late CancelableCompleter completer;
+ completer = CancelableCompleter(onCancel: expectAsync0(() {
+ expect(completer.isCanceled, isTrue);
+ canceled = true;
+ }));
+
+ expect(canceled, isFalse);
+ expect(completer.isCanceled, isFalse);
+ expect(completer.operation.isCanceled, isFalse);
+ expect(completer.isCompleted, isFalse);
+ expect(completer.operation.isCompleted, isFalse);
+ completer.operation.cancel();
+ expect(canceled, isTrue);
+ expect(completer.isCanceled, isTrue);
+ expect(completer.operation.isCanceled, isTrue);
+ expect(completer.isCompleted, isFalse);
+ expect(completer.operation.isCompleted, isFalse);
+ });
+
+ test('returns the onCancel future each time cancel is called', () {
+ var completer = CancelableCompleter(onCancel: expectAsync0(() {
+ return Future.value(1);
+ }));
+ expect(completer.operation.cancel(), completion(equals(1)));
+ expect(completer.operation.cancel(), completion(equals(1)));
+ expect(completer.operation.cancel(), completion(equals(1)));
+ });
+
+ test("returns a future even if onCancel doesn't", () {
+ var completer = CancelableCompleter(onCancel: expectAsync0(() {}));
+ expect(completer.operation.cancel(), completes);
+ });
+
+ test("doesn't call onCancel if the completer has completed", () {
+ var completer =
+ CancelableCompleter(onCancel: expectAsync0(() {}, count: 0));
+ completer.complete(1);
+ expect(completer.operation.value, completion(equals(1)));
+ expect(completer.operation.cancel(), completes);
+ });
+
+ test(
+ 'does call onCancel if the completer has completed to an unfired '
+ 'Future', () {
+ var completer = CancelableCompleter(onCancel: expectAsync0(() {}));
+ completer.complete(Completer<void>().future);
+ expect(completer.operation.cancel(), completes);
+ });
+
+ test(
+ "doesn't call onCancel if the completer has completed to a fired "
+ 'Future', () async {
+ var completer =
+ CancelableCompleter(onCancel: expectAsync0(() {}, count: 0));
+ completer.complete(Future.value(1));
+ await completer.operation.value;
+ expect(completer.operation.cancel(), completes);
+ });
+
+ test('can be completed once after being canceled', () async {
+ var completer = CancelableCompleter<int>();
+ completer.operation.value.whenComplete(expectAsync0(() {}, count: 0));
+ await completer.operation.cancel();
+ completer.complete(1);
+ expect(() => completer.complete(1), throwsStateError);
+ });
+
+ test('fires valueOrCancellation with the given value', () {
+ var completer = CancelableCompleter<int>();
+ expect(completer.operation.valueOrCancellation(1), completion(equals(1)));
+ completer.operation.cancel();
+ });
+
+ test('pipes an error through valueOrCancellation', () {
+ var completer = CancelableCompleter(onCancel: () {
+ throw 'error';
+ });
+ expect(completer.operation.valueOrCancellation(1), throwsA('error'));
+ completer.operation.cancel();
+ });
+
+ test('valueOrCancellation waits on the onCancel future', () async {
+ var innerCompleter = Completer<void>();
+ var completer =
+ CancelableCompleter(onCancel: () => innerCompleter.future);
+
+ var fired = false;
+ completer.operation.valueOrCancellation().then((_) {
+ fired = true;
+ });
+
+ completer.operation.cancel();
+ await flushMicrotasks();
+ expect(fired, isFalse);
+
+ innerCompleter.complete();
+ await flushMicrotasks();
+ expect(fired, isTrue);
+ });
+
+ test('CancelableOperation.fromSubscription() cancels the subscription',
+ () async {
+ var cancelCompleter = Completer<void>();
+ var canceled = false;
+ var controller = StreamController<void>(onCancel: () {
+ canceled = true;
+ return cancelCompleter.future;
+ });
+ var operation =
+ CancelableOperation.fromSubscription(controller.stream.listen(null));
+
+ await flushMicrotasks();
+ expect(canceled, isFalse);
+
+ // The `cancel()` call shouldn't complete until
+ // `StreamSubscription.cancel` completes.
+ var cancelCompleted = false;
+ expect(
+ operation.cancel().then((_) {
+ cancelCompleted = true;
+ }),
+ completes);
+ await flushMicrotasks();
+ expect(canceled, isTrue);
+ expect(cancelCompleted, isFalse);
+
+ cancelCompleter.complete();
+ await flushMicrotasks();
+ expect(cancelCompleted, isTrue);
+ });
+
+ group('completeOperation', () {
+ test('sends cancellation from a cancelable operation', () async {
+ final completer = CancelableCompleter<void>();
+ completer.operation.value.whenComplete(expectAsync0(() {}, count: 0));
+ completer
+ .completeOperation(CancelableCompleter<void>().operation..cancel());
+ await completer.operation.valueOrCancellation();
+ expect(completer.operation.isCanceled, true);
+ });
+
+ test('sends errors from a completed cancelable operation to the future',
+ () async {
+ final operation = CancelableCompleter<void>().operation..cancel();
+ await operation.valueOrCancellation();
+ final completer = CancelableCompleter<void>();
+ completer.operation.value.whenComplete(expectAsync0(() {}, count: 0));
+ completer.completeOperation(operation);
+ await completer.operation.valueOrCancellation();
+ expect(completer.operation.isCanceled, true);
+ });
+
+ test('propagates cancellation', () {
+ final completer = CancelableCompleter<void>();
+ final operation =
+ CancelableCompleter<void>(onCancel: expectAsync0(() {}, count: 1))
+ .operation;
+ completer.completeOperation(operation);
+ completer.operation.cancel();
+ });
+
+ test('propagates cancellation from already canceld completer', () async {
+ final completer = CancelableCompleter<void>()..operation.cancel();
+ await completer.operation.valueOrCancellation();
+ final operation =
+ CancelableCompleter<void>(onCancel: expectAsync0(() {}, count: 1))
+ .operation;
+ completer.completeOperation(operation);
+ });
+ test('cancel propagation can be disabled', () {
+ final completer = CancelableCompleter<void>();
+ final operation =
+ CancelableCompleter<void>(onCancel: expectAsync0(() {}, count: 0))
+ .operation;
+ completer.completeOperation(operation, propagateCancel: false);
+ completer.operation.cancel();
+ });
+
+ test('cancel propagation can be disabled from already canceled completed',
+ () async {
+ final completer = CancelableCompleter<void>()..operation.cancel();
+ await completer.operation.valueOrCancellation();
+ final operation =
+ CancelableCompleter<void>(onCancel: expectAsync0(() {}, count: 0))
+ .operation;
+ completer.completeOperation(operation, propagateCancel: false);
+ });
+ });
+ });
+
+ group('asStream()', () {
+ test('emits a value and then closes', () {
+ var completer = CancelableCompleter<int>();
+ expect(completer.operation.asStream().toList(), completion(equals([1])));
+ completer.complete(1);
+ });
+
+ test('emits an error and then closes', () {
+ var completer = CancelableCompleter<void>();
+ var queue = StreamQueue(completer.operation.asStream());
+ expect(queue.next, throwsA('error'));
+ expect(queue.hasNext, completion(isFalse));
+ completer.completeError('error');
+ });
+
+ test('cancels the completer when the subscription is canceled', () {
+ var completer = CancelableCompleter(onCancel: expectAsync0(() {}));
+ var sub =
+ completer.operation.asStream().listen(expectAsync1((_) {}, count: 0));
+ completer.operation.value.whenComplete(expectAsync0(() {}, count: 0));
+ sub.cancel();
+ expect(completer.isCanceled, isTrue);
+ });
+ });
+
+ group('then', () {
+ FutureOr<String> Function(int)? onValue;
+ FutureOr<String> Function(Object, StackTrace)? onError;
+ FutureOr<String> Function()? onCancel;
+ late bool propagateCancel;
+ late CancelableCompleter<int> originalCompleter;
+
+ setUp(() {
+ // Initialize all functions to ones that expect to not be called.
+ onValue = expectAsync1((_) => 'Fake', count: 0, id: 'onValue');
+ onError = expectAsync2((e, s) => 'Fake', count: 0, id: 'onError');
+ onCancel = expectAsync0(() => 'Fake', count: 0, id: 'onCancel');
+ propagateCancel = false;
+ originalCompleter = CancelableCompleter<int>();
+ });
+
+ CancelableOperation<String> runThen() {
+ return originalCompleter.operation.then(onValue!,
+ onError: onError,
+ onCancel: onCancel,
+ propagateCancel: propagateCancel);
+ }
+
+ group('original operation completes successfully', () {
+ test('onValue completes successfully', () {
+ onValue = expectAsync1((v) => v.toString(), count: 1, id: 'onValue');
+
+ expect(runThen().value, completion('1'));
+ originalCompleter.complete(1);
+ });
+
+ test('onValue throws error', () {
+ // expectAsync1 only works with functions that do not throw.
+ onValue = (_) => throw 'error';
+
+ expect(runThen().value, throwsA('error'));
+ originalCompleter.complete(1);
+ });
+
+ test('onValue returns Future that throws error', () {
+ onValue =
+ expectAsync1((v) => Future.error('error'), count: 1, id: 'onValue');
+
+ expect(runThen().value, throwsA('error'));
+ originalCompleter.complete(1);
+ });
+
+ test('and returned operation is canceled with propagateCancel = false',
+ () async {
+ propagateCancel = false;
+
+ runThen().cancel();
+
+ // onValue should not be called.
+ originalCompleter.complete(1);
+ });
+ });
+
+ group('original operation completes with error', () {
+ test('onError not set', () {
+ onError = null;
+
+ expect(runThen().value, throwsA('error'));
+ originalCompleter.completeError('error');
+ });
+
+ test('onError completes successfully', () {
+ onError = expectAsync2((e, s) => 'onError caught $e',
+ count: 1, id: 'onError');
+
+ expect(runThen().value, completion('onError caught error'));
+ originalCompleter.completeError('error');
+ });
+
+ test('onError throws', () {
+ // expectAsync2 does not work with functions that throw.
+ onError = (e, s) => throw 'onError caught $e';
+
+ expect(runThen().value, throwsA('onError caught error'));
+ originalCompleter.completeError('error');
+ });
+
+ test('onError returns Future that throws', () {
+ onError = expectAsync2((e, s) => Future.error('onError caught $e'),
+ count: 1, id: 'onError');
+
+ expect(runThen().value, throwsA('onError caught error'));
+ originalCompleter.completeError('error');
+ });
+
+ test('and returned operation is canceled with propagateCancel = false',
+ () async {
+ propagateCancel = false;
+
+ runThen().cancel();
+
+ // onError should not be called.
+ originalCompleter.completeError('error');
+ });
+ });
+
+ group('original operation canceled', () {
+ test('onCancel not set', () async {
+ onCancel = null;
+
+ final operation = runThen();
+
+ await expectLater(originalCompleter.operation.cancel(), completes);
+ expect(operation.isCanceled, true);
+ });
+
+ test('onCancel completes successfully', () {
+ onCancel = expectAsync0(() => 'canceled', count: 1, id: 'onCancel');
+
+ expect(runThen().value, completion('canceled'));
+ originalCompleter.operation.cancel();
+ });
+
+ test('onCancel throws error', () {
+ // expectAsync0 only works with functions that do not throw.
+ onCancel = () => throw 'error';
+
+ expect(runThen().value, throwsA('error'));
+ originalCompleter.operation.cancel();
+ });
+
+ test('onCancel returns Future that throws error', () {
+ onCancel =
+ expectAsync0(() => Future.error('error'), count: 1, id: 'onCancel');
+
+ expect(runThen().value, throwsA('error'));
+ originalCompleter.operation.cancel();
+ });
+
+ test('after completing with a future does not invoke `onValue`',
+ () async {
+ onValue = expectAsync1((_) => '', count: 0);
+ onCancel = null;
+ var operation = runThen();
+ var workCompleter = Completer<int>();
+ originalCompleter.complete(workCompleter.future);
+ var cancelation = originalCompleter.operation.cancel();
+ expect(originalCompleter.isCanceled, true);
+ workCompleter.complete(0);
+ await cancelation;
+ expect(operation.isCanceled, true);
+ await workCompleter.future;
+ });
+
+ test('after the value is completed invokes `onValue`', () {
+ onValue = expectAsync1((_) => 'foo', count: 1);
+ onCancel = expectAsync1((_) => '', count: 0);
+ originalCompleter.complete(0);
+ originalCompleter.operation.cancel();
+ var operation = runThen();
+ expect(operation.value, completion('foo'));
+ expect(operation.isCanceled, false);
+ });
+
+ test('waits for chained cancellation', () async {
+ var completer = CancelableCompleter<void>();
+ var chainedOperation = completer.operation
+ .then((_) => Future<void>.delayed(const Duration(milliseconds: 1)))
+ .then((_) => Future<void>.delayed(const Duration(milliseconds: 1)));
+
+ await completer.operation.cancel();
+ expect(completer.operation.isCanceled, true);
+ expect(chainedOperation.isCanceled, true);
+ });
+ });
+
+ group('returned operation canceled', () {
+ test('propagateCancel is true', () async {
+ propagateCancel = true;
+
+ await runThen().cancel();
+
+ expect(originalCompleter.isCanceled, true);
+ });
+
+ test('propagateCancel is false', () async {
+ propagateCancel = false;
+
+ await runThen().cancel();
+
+ expect(originalCompleter.isCanceled, false);
+ });
+
+ test('onValue callback not called after cancel', () async {
+ var called = false;
+ onValue = expectAsync1((_) {
+ called = true;
+ fail('onValue unreachable');
+ }, count: 0);
+
+ await runThen().cancel();
+ originalCompleter.complete(0);
+ await flushMicrotasks();
+ expect(called, false);
+ });
+
+ test('onError callback not called after cancel', () async {
+ var called = false;
+ onError = expectAsync2((_, __) {
+ called = true;
+ fail('onError unreachable');
+ }, count: 0);
+
+ await runThen().cancel();
+ originalCompleter.completeError('Error', StackTrace.empty);
+ await flushMicrotasks();
+ expect(called, false);
+ });
+
+ test('onCancel callback not called after cancel', () async {
+ var called = false;
+ onCancel = expectAsync0(() {
+ called = true;
+ fail('onCancel unreachable');
+ }, count: 0);
+
+ await runThen().cancel();
+ await originalCompleter.operation.cancel();
+ await flushMicrotasks();
+ expect(called, false);
+ });
+ });
+ });
+
+ group('thenOperation', () {
+ late void Function(int, CancelableCompleter<String>) onValue;
+ void Function(Object, StackTrace, CancelableCompleter<String>)? onError;
+ void Function(CancelableCompleter<String>)? onCancel;
+ late bool propagateCancel;
+ late CancelableCompleter<int> originalCompleter;
+
+ setUp(() {
+ // Initialize all functions to ones that expect to not be called.
+ onValue = expectAsync2((value, completer) => completer.complete('$value'),
+ count: 0, id: 'onValue');
+ onError = null;
+ onCancel = null;
+ propagateCancel = false;
+ originalCompleter = CancelableCompleter<int>();
+ });
+
+ CancelableOperation<String> runThenOperation() {
+ return originalCompleter.operation.thenOperation(onValue,
+ onError: onError,
+ onCancel: onCancel,
+ propagateCancel: propagateCancel);
+ }
+
+ group('original operation completes successfully', () {
+ test('onValue completes successfully', () {
+ onValue =
+ expectAsync2((v, c) => c.complete('$v'), count: 1, id: 'onValue');
+
+ expect(runThenOperation().value, completion('1'));
+ originalCompleter.complete(1);
+ });
+
+ test('onValue throws error', () {
+ // expectAsync1 only works with functions that do not throw.
+ onValue = (_, __) => throw 'error';
+
+ expect(runThenOperation().value, throwsA('error'));
+ originalCompleter.complete(1);
+ });
+
+ test('onValue completes operation as error', () {
+ onValue = expectAsync2(
+ (_, completer) => completer.completeError('error'),
+ count: 1,
+ id: 'onValue');
+
+ expect(runThenOperation().value, throwsA('error'));
+ originalCompleter.complete(1);
+ });
+
+ test('onValue returns a Future that throws error', () {
+ onValue = expectAsync2((_, completer) => Future.error('error'),
+ count: 1, id: 'onValue');
+
+ expect(runThenOperation().value, throwsA('error'));
+ originalCompleter.complete(1);
+ });
+
+ test('and returned operation is canceled', () async {
+ onValue = expectAsync2((_, __) => throw 'never called', count: 0);
+ runThenOperation().cancel();
+ // onValue should not be called.
+ originalCompleter.complete(1);
+ });
+ });
+
+ group('original operation completes with error', () {
+ test('onError not set', () {
+ onError = null;
+
+ expect(runThenOperation().value, throwsA('error'));
+ originalCompleter.completeError('error');
+ });
+
+ test('onError completes operation', () {
+ onError = expectAsync3((e, s, c) => c.complete('onError caught $e'),
+ count: 1, id: 'onError');
+
+ expect(runThenOperation().value, completion('onError caught error'));
+ originalCompleter.completeError('error');
+ });
+
+ test('onError throws', () {
+ // expectAsync3 does not work with functions that throw.
+ onError = (e, s, c) => throw 'onError caught $e';
+
+ expect(runThenOperation().value, throwsA('onError caught error'));
+ originalCompleter.completeError('error');
+ });
+
+ test('onError returns Future that throws error', () {
+ onError = expectAsync3((e, s, c) => Future.error('onError caught $e'),
+ count: 1, id: 'onError');
+
+ expect(runThenOperation().value, throwsA('onError caught error'));
+ originalCompleter.completeError('error');
+ });
+
+ test('onError completes operation as an error', () {
+ onError = expectAsync3(
+ (e, s, c) => c.completeError('onError caught $e'),
+ count: 1,
+ id: 'onError');
+
+ expect(runThenOperation().value, throwsA('onError caught error'));
+ originalCompleter.completeError('error');
+ });
+
+ test('and returned operation is canceled with propagateCancel = false',
+ () async {
+ onError = expectAsync3((e, s, c) {}, count: 0);
+
+ runThenOperation().cancel();
+
+ // onError should not be called.
+ originalCompleter.completeError('error');
+ });
+ });
+
+ group('original operation canceled', () {
+ test('onCancel not set', () async {
+ onCancel = null;
+
+ final operation = runThenOperation();
+
+ await expectLater(originalCompleter.operation.cancel(), completes);
+ expect(operation.isCanceled, true);
+ });
+
+ test('onCancel completes successfully', () {
+ onCancel = expectAsync1((c) => c.complete('canceled'),
+ count: 1, id: 'onCancel');
+
+ expect(runThenOperation().value, completion('canceled'));
+ originalCompleter.operation.cancel();
+ });
+
+ test('onCancel throws error', () {
+ // expectAsync0 only works with functions that do not throw.
+ onCancel = (_) => throw 'error';
+
+ expect(runThenOperation().value, throwsA('error'));
+ originalCompleter.operation.cancel();
+ });
+
+ test('onCancel completes operation as error', () {
+ onCancel = expectAsync1((c) => c.completeError('error'),
+ count: 1, id: 'onCancel');
+
+ expect(runThenOperation().value, throwsA('error'));
+ originalCompleter.operation.cancel();
+ });
+
+ test('onCancel returns Future that throws error', () {
+ onCancel = expectAsync1((c) => Future.error('error'),
+ count: 1, id: 'onCancel');
+
+ expect(runThenOperation().value, throwsA('error'));
+ originalCompleter.operation.cancel();
+ });
+
+ test('after completing with a future does not invoke `onValue`',
+ () async {
+ onValue = expectAsync2((_, __) {}, count: 0);
+ onCancel = null;
+ var operation = runThenOperation();
+ var workCompleter = Completer<int>();
+ originalCompleter.complete(workCompleter.future);
+ var cancelation = originalCompleter.operation.cancel();
+ expect(originalCompleter.isCanceled, true);
+ workCompleter.complete(0);
+ await cancelation;
+ expect(operation.isCanceled, true);
+ await workCompleter.future;
+ });
+
+ test('after the value is completed invokes `onValue`', () {
+ onValue = expectAsync2((v, c) => c.complete('foo'), count: 1);
+ onCancel = expectAsync1((_) {}, count: 0);
+ originalCompleter.complete(0);
+ originalCompleter.operation.cancel();
+ var operation = runThenOperation();
+ expect(operation.value, completion('foo'));
+ expect(operation.isCanceled, false);
+ });
+ });
+
+ group('returned operation canceled', () {
+ test('propagateCancel is true', () async {
+ propagateCancel = true;
+
+ await runThenOperation().cancel();
+
+ expect(originalCompleter.isCanceled, true);
+ });
+
+ test('propagateCancel is false', () async {
+ propagateCancel = false;
+
+ await runThenOperation().cancel();
+
+ expect(originalCompleter.isCanceled, false);
+ });
+
+ test('onValue callback not called after cancel', () async {
+ onValue = expectAsync2((_, c) {}, count: 0);
+
+ await runThenOperation().cancel();
+ originalCompleter.complete(0);
+ });
+
+ test('onError callback not called after cancel', () async {
+ onError = expectAsync3((_, __, ___) {}, count: 0);
+
+ await runThenOperation().cancel();
+ originalCompleter.completeError('Error', StackTrace.empty);
+ });
+
+ test('onCancel callback not called after cancel', () async {
+ onCancel = expectAsync1((_) {}, count: 0);
+
+ await runThenOperation().cancel();
+ await originalCompleter.operation.cancel();
+ });
+ });
+ });
+
+ group('race()', () {
+ late bool canceled1;
+ late CancelableCompleter<int> completer1;
+ late bool canceled2;
+ late CancelableCompleter<int> completer2;
+ late bool canceled3;
+ late CancelableCompleter<int> completer3;
+ late CancelableOperation<int> operation;
+ setUp(() {
+ canceled1 = false;
+ completer1 = CancelableCompleter<int>(onCancel: () {
+ canceled1 = true;
+ });
+
+ canceled2 = false;
+ completer2 = CancelableCompleter<int>(onCancel: () {
+ canceled2 = true;
+ });
+
+ canceled3 = false;
+ completer3 = CancelableCompleter<int>(onCancel: () {
+ canceled3 = true;
+ });
+
+ operation = CancelableOperation.race(
+ [completer1.operation, completer2.operation, completer3.operation]);
+ });
+
+ test('returns the first value to complete', () {
+ completer1.complete(1);
+ completer2.complete(2);
+ completer3.complete(3);
+
+ expect(operation.value, completion(equals(1)));
+ });
+
+ test('throws the first error to complete', () {
+ completer1.completeError('error 1');
+ completer2.completeError('error 2');
+ completer3.completeError('error 3');
+
+ expect(operation.value, throwsA('error 1'));
+ });
+
+ test('cancels any completers that haven\'t completed', () async {
+ completer1.complete(1);
+ await expectLater(operation.value, completion(equals(1)));
+ expect(canceled1, isFalse);
+ expect(canceled2, isTrue);
+ expect(canceled3, isTrue);
+ });
+
+ test('cancels all completers when the operation is completed', () async {
+ await operation.cancel();
+
+ expect(canceled1, isTrue);
+ expect(canceled2, isTrue);
+ expect(canceled3, isTrue);
+ });
+ });
+}
diff --git a/pkgs/async/test/chunked_stream_reader.dart b/pkgs/async/test/chunked_stream_reader.dart
new file mode 100644
index 0000000..2fc1e8b
--- /dev/null
+++ b/pkgs/async/test/chunked_stream_reader.dart
@@ -0,0 +1,482 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('readChunk() chunk by chunk', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ yield [6, 7, 8, 9];
+ yield [10];
+ }());
+
+ expect(await r.readChunk(2), equals([1, 2]));
+ expect(await r.readChunk(3), equals([3, 4, 5]));
+ expect(await r.readChunk(4), equals([6, 7, 8, 9]));
+ expect(await r.readChunk(1), equals([10]));
+ expect(await r.readChunk(1), equals([]));
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readChunk() element by element', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ yield [6, 7, 8, 9];
+ yield [10];
+ }());
+
+ for (var i = 0; i < 10; i++) {
+ expect(await r.readChunk(1), equals([i + 1]));
+ }
+ expect(await r.readChunk(1), equals([]));
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readChunk() exact elements', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ yield [6, 7, 8, 9];
+ yield [10];
+ }());
+
+ expect(await r.readChunk(10), equals([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));
+ expect(await r.readChunk(1), equals([]));
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readChunk() past end', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ yield [6, 7, 8, 9];
+ yield [10];
+ }());
+
+ expect(await r.readChunk(20), equals([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));
+ expect(await r.readChunk(1), equals([]));
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readChunk() chunks of 2 elements', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ yield [6, 7, 8, 9];
+ yield [10];
+ }());
+
+ expect(await r.readChunk(2), equals([1, 2]));
+ expect(await r.readChunk(2), equals([3, 4]));
+ expect(await r.readChunk(2), equals([5, 6]));
+ expect(await r.readChunk(2), equals([7, 8]));
+ expect(await r.readChunk(2), equals([9, 10]));
+ expect(await r.readChunk(1), equals([]));
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readChunk() chunks of 3 elements', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ yield [6, 7, 8, 9];
+ yield [10];
+ }());
+
+ expect(await r.readChunk(3), equals([1, 2, 3]));
+ expect(await r.readChunk(3), equals([4, 5, 6]));
+ expect(await r.readChunk(3), equals([7, 8, 9]));
+ expect(await r.readChunk(3), equals([10]));
+ expect(await r.readChunk(1), equals([]));
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readChunk() cancel half way', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ yield [6, 7, 8, 9];
+ yield [10];
+ }());
+
+ expect(await r.readChunk(5), equals([1, 2, 3, 4, 5]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readChunk() propagates exception', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ throw Exception('stopping here');
+ }());
+
+ expect(await r.readChunk(3), equals([1, 2, 3]));
+ await expectLater(r.readChunk(3), throwsException);
+
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readStream() forwards chunks', () async {
+ final chunk2 = [3, 4, 5];
+ final chunk3 = [6, 7, 8, 9];
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield chunk2;
+ yield chunk3;
+ yield [10];
+ }());
+
+ expect(await r.readChunk(1), equals([1]));
+ final i = StreamIterator(r.readStream(9));
+ expect(await i.moveNext(), isTrue);
+ expect(i.current, equals([2]));
+
+ // We must forward the exact chunks otherwise it's not efficient!
+ // Hence, we have a reference equality check here.
+ expect(await i.moveNext(), isTrue);
+ expect(i.current, equals([3, 4, 5]));
+ expect(i.current == chunk2, isTrue);
+
+ expect(await i.moveNext(), isTrue);
+ expect(i.current, equals([6, 7, 8, 9]));
+ expect(i.current == chunk3, isTrue);
+
+ expect(await i.moveNext(), isTrue);
+ expect(i.current, equals([10]));
+ expect(await i.moveNext(), isFalse);
+
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readStream() cancel at the exact end', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ yield [6, 7, 8, 9];
+ yield [10];
+ }());
+
+ expect(await r.readChunk(1), equals([1]));
+ final i = StreamIterator(r.readStream(7));
+ expect(await i.moveNext(), isTrue);
+ expect(i.current, equals([2]));
+
+ expect(await i.moveNext(), isTrue);
+ expect(i.current, equals([3, 4, 5]));
+
+ expect(await i.moveNext(), isTrue);
+ expect(i.current, equals([6, 7, 8]));
+
+ await i.cancel(); // cancel substream just as it's ending
+
+ expect(await r.readChunk(2), equals([9, 10]));
+
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readStream() cancel at the exact end on chunk boundary', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ yield [6, 7, 8, 9];
+ yield [10];
+ }());
+
+ expect(await r.readChunk(1), equals([1]));
+ final i = StreamIterator(r.readStream(8));
+ expect(await i.moveNext(), isTrue);
+ expect(i.current, equals([2]));
+
+ expect(await i.moveNext(), isTrue);
+ expect(i.current, equals([3, 4, 5]));
+
+ expect(await i.moveNext(), isTrue);
+ expect(i.current, equals([6, 7, 8, 9]));
+
+ await i.cancel(); // cancel substream just as it's ending
+
+ expect(await r.readChunk(2), equals([10]));
+
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readStream() is drained when canceled', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ yield [6, 7, 8, 9];
+ yield [10];
+ }());
+
+ expect(await r.readChunk(1), equals([1]));
+ final i = StreamIterator(r.readStream(7));
+ expect(await i.moveNext(), isTrue);
+ expect(i.current, equals([2]));
+ // Cancelling here should skip the remainder of the substream
+ // and we continue to read 9 and 10 from r
+ await i.cancel();
+
+ expect(await r.readChunk(2), equals([9, 10]));
+
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readStream() concurrent reads is forbidden', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ yield [6, 7, 8, 9];
+ yield [10];
+ }());
+
+ expect(await r.readChunk(1), equals([1]));
+ // Notice we are not reading this substream:
+ r.readStream(7);
+
+ expectLater(r.readChunk(2), throwsStateError);
+ });
+
+ test('readStream() supports draining', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ yield [6, 7, 8, 9];
+ yield [10];
+ }());
+
+ expect(await r.readChunk(1), equals([1]));
+ await r.readStream(7).drain();
+ expect(await r.readChunk(2), equals([9, 10]));
+
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('nested ChunkedStreamReader', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ yield [6, 7, 8, 9];
+ yield [10];
+ }());
+
+ expect(await r.readChunk(1), equals([1]));
+ final r2 = ChunkedStreamReader(r.readStream(7));
+ expect(await r2.readChunk(2), equals([2, 3]));
+ expect(await r2.readChunk(1), equals([4]));
+ await r2.cancel();
+
+ expect(await r.readChunk(2), equals([9, 10]));
+
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readBytes() chunks of 3 elements', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2];
+ yield [3, 4, 5];
+ yield [6, 7, 8, 9];
+ yield [10];
+ }());
+
+ expect(await r.readBytes(3), allOf(equals([1, 2, 3]), isA<Uint8List>()));
+ expect(await r.readBytes(3), allOf(equals([4, 5, 6]), isA<Uint8List>()));
+ expect(await r.readBytes(3), allOf(equals([7, 8, 9]), isA<Uint8List>()));
+ expect(await r.readBytes(3), allOf(equals([10]), isA<Uint8List>()));
+ expect(await r.readBytes(1), equals([]));
+ expect(await r.readBytes(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readBytes(1), equals([]));
+ });
+
+ test('readChunk() until exact end of stream', () async {
+ final stream = Stream.fromIterable(Iterable.generate(
+ 10,
+ (_) => Uint8List(512),
+ ));
+
+ final r = ChunkedStreamReader(stream);
+ while (true) {
+ final c = await r.readBytes(1024);
+ if (c.isEmpty) {
+ break;
+ }
+ }
+ });
+
+ test('cancel while readChunk() is pending', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2, 3];
+ // This will hang forever, so we will call cancel()
+ await Completer<void>().future;
+ yield [4]; // this should never be reachable
+ fail('unreachable!');
+ }());
+
+ expect(await r.readBytes(2), equals([1, 2]));
+
+ final future = r.readChunk(2);
+
+ // Wait a tiny bit and cancel
+ await Future.microtask(() => null);
+ r.cancel();
+
+ expect(await future, hasLength(lessThan(2)));
+ });
+
+ test('cancel while readStream() is pending', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield [1, 2, 3];
+ // This will hang forever, so we will call cancel()
+ await Completer<void>().future;
+ yield [4]; // this should never be reachable
+ fail('unreachable!');
+ }());
+
+ expect(await collectBytes(r.readStream(2)), equals([1, 2]));
+
+ final stream = r.readStream(2);
+
+ // Wait a tiny bit and cancel
+ await Future.microtask(() => null);
+ r.cancel();
+
+ expect(await collectBytes(stream), hasLength(lessThan(2)));
+ });
+
+ test('readChunk() chunk by chunk (Uint8List)', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield Uint8List.fromList([1, 2]);
+ yield Uint8List.fromList([3, 4, 5]);
+ yield Uint8List.fromList([6, 7, 8, 9]);
+ yield Uint8List.fromList([10]);
+ }());
+
+ expect(await r.readChunk(2), equals([1, 2]));
+ expect(await r.readChunk(3), equals([3, 4, 5]));
+ expect(await r.readChunk(4), equals([6, 7, 8, 9]));
+ expect(await r.readChunk(1), equals([10]));
+ expect(await r.readChunk(1), equals([]));
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readChunk() element by element (Uint8List)', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield Uint8List.fromList([1, 2]);
+ yield Uint8List.fromList([3, 4, 5]);
+ yield Uint8List.fromList([6, 7, 8, 9]);
+ yield Uint8List.fromList([10]);
+ }());
+
+ for (var i = 0; i < 10; i++) {
+ expect(await r.readChunk(1), equals([i + 1]));
+ }
+ expect(await r.readChunk(1), equals([]));
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readChunk() exact elements (Uint8List)', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield Uint8List.fromList([1, 2]);
+ yield Uint8List.fromList([3, 4, 5]);
+ yield Uint8List.fromList([6, 7, 8, 9]);
+ yield Uint8List.fromList([10]);
+ }());
+
+ expect(await r.readChunk(10), equals([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));
+ expect(await r.readChunk(1), equals([]));
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readChunk() past end (Uint8List)', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield Uint8List.fromList([1, 2]);
+ yield Uint8List.fromList([3, 4, 5]);
+ yield Uint8List.fromList([6, 7, 8, 9]);
+ yield Uint8List.fromList([10]);
+ }());
+
+ expect(await r.readChunk(20), equals([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));
+ expect(await r.readChunk(1), equals([]));
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readChunk() chunks of 2 elements (Uint8List)', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield Uint8List.fromList([1, 2]);
+ yield Uint8List.fromList([3, 4, 5]);
+ yield Uint8List.fromList([6, 7, 8, 9]);
+ yield Uint8List.fromList([10]);
+ }());
+
+ expect(await r.readChunk(2), equals([1, 2]));
+ expect(await r.readChunk(2), equals([3, 4]));
+ expect(await r.readChunk(2), equals([5, 6]));
+ expect(await r.readChunk(2), equals([7, 8]));
+ expect(await r.readChunk(2), equals([9, 10]));
+ expect(await r.readChunk(1), equals([]));
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+
+ test('readChunk() chunks of 3 elements (Uint8List)', () async {
+ final r = ChunkedStreamReader(() async* {
+ yield Uint8List.fromList([1, 2]);
+ yield Uint8List.fromList([3, 4, 5]);
+ yield Uint8List.fromList([6, 7, 8, 9]);
+ yield Uint8List.fromList([10]);
+ }());
+
+ expect(await r.readChunk(3), equals([1, 2, 3]));
+ expect(await r.readChunk(3), equals([4, 5, 6]));
+ expect(await r.readChunk(3), equals([7, 8, 9]));
+ expect(await r.readChunk(3), equals([10]));
+ expect(await r.readChunk(1), equals([]));
+ expect(await r.readChunk(1), equals([]));
+ await r.cancel(); // check this is okay!
+ expect(await r.readChunk(1), equals([]));
+ });
+}
diff --git a/pkgs/async/test/future_group_test.dart b/pkgs/async/test/future_group_test.dart
new file mode 100644
index 0000000..9729c06
--- /dev/null
+++ b/pkgs/async/test/future_group_test.dart
@@ -0,0 +1,224 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/src/future_group.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ late FutureGroup futureGroup;
+ setUp(() {
+ futureGroup = FutureGroup();
+ });
+
+ group('with no futures', () {
+ test('never completes if nothing happens', () async {
+ var completed = false;
+ futureGroup.future.then((_) => completed = true);
+
+ await flushMicrotasks();
+ expect(completed, isFalse);
+ });
+
+ test("completes once it's closed", () {
+ expect(futureGroup.future, completion(isEmpty));
+ expect(futureGroup.isClosed, isFalse);
+ futureGroup.close();
+ expect(futureGroup.isClosed, isTrue);
+ });
+ });
+
+ group('with a future that already completed', () {
+ test('never completes if nothing happens', () async {
+ futureGroup.add(Future.value());
+ await flushMicrotasks();
+
+ var completed = false;
+ futureGroup.future.then((_) => completed = true);
+
+ await flushMicrotasks();
+ expect(completed, isFalse);
+ });
+
+ test("completes once it's closed", () async {
+ futureGroup.add(Future.value());
+ await flushMicrotasks();
+
+ expect(futureGroup.future, completes);
+ expect(futureGroup.isClosed, isFalse);
+ futureGroup.close();
+ expect(futureGroup.isClosed, isTrue);
+ });
+
+ test("completes to that future's value", () {
+ futureGroup.add(Future.value(1));
+ futureGroup.close();
+ expect(futureGroup.future, completion(equals([1])));
+ });
+
+ test("completes to that future's error, even if it's not closed", () {
+ futureGroup.add(Future.error('error'));
+ expect(futureGroup.future, throwsA('error'));
+ });
+ });
+
+ test('completes once all contained futures complete', () async {
+ var completer1 = Completer<void>();
+ var completer2 = Completer<void>();
+ var completer3 = Completer<void>();
+
+ futureGroup.add(completer1.future);
+ futureGroup.add(completer2.future);
+ futureGroup.add(completer3.future);
+ futureGroup.close();
+
+ var completed = false;
+ futureGroup.future.then((_) => completed = true);
+
+ completer1.complete();
+ await flushMicrotasks();
+ expect(completed, isFalse);
+
+ completer2.complete();
+ await flushMicrotasks();
+ expect(completed, isFalse);
+
+ completer3.complete();
+ await flushMicrotasks();
+ expect(completed, isTrue);
+ });
+
+ test('completes to the values of the futures in order of addition', () {
+ var completer1 = Completer<int>();
+ var completer2 = Completer<int>();
+ var completer3 = Completer<int>();
+
+ futureGroup.add(completer1.future);
+ futureGroup.add(completer2.future);
+ futureGroup.add(completer3.future);
+ futureGroup.close();
+
+ // Complete the completers in reverse order to prove that that doesn't
+ // affect the result order.
+ completer3.complete(3);
+ completer2.complete(2);
+ completer1.complete(1);
+ expect(futureGroup.future, completion(equals([1, 2, 3])));
+ });
+
+ test("completes to the first error to be emitted, even if it's not closed",
+ () {
+ var completer1 = Completer<void>();
+ var completer2 = Completer<void>();
+ var completer3 = Completer<void>();
+
+ futureGroup.add(completer1.future);
+ futureGroup.add(completer2.future);
+ futureGroup.add(completer3.future);
+
+ completer2.completeError('error 2');
+ completer1.completeError('error 1');
+ expect(futureGroup.future, throwsA('error 2'));
+ });
+
+ group('onIdle:', () {
+ test('emits an event when the last pending future completes', () async {
+ var idle = false;
+ futureGroup.onIdle.listen((_) => idle = true);
+
+ var completer1 = Completer<void>();
+ var completer2 = Completer<void>();
+ var completer3 = Completer<void>();
+
+ futureGroup.add(completer1.future);
+ futureGroup.add(completer2.future);
+ futureGroup.add(completer3.future);
+
+ await flushMicrotasks();
+ expect(idle, isFalse);
+ expect(futureGroup.isIdle, isFalse);
+
+ completer1.complete();
+ await flushMicrotasks();
+ expect(idle, isFalse);
+ expect(futureGroup.isIdle, isFalse);
+
+ completer2.complete();
+ await flushMicrotasks();
+ expect(idle, isFalse);
+ expect(futureGroup.isIdle, isFalse);
+
+ completer3.complete();
+ await flushMicrotasks();
+ expect(idle, isTrue);
+ expect(futureGroup.isIdle, isTrue);
+ });
+
+ test('emits an event each time it becomes idle', () async {
+ var idle = false;
+ futureGroup.onIdle.listen((_) => idle = true);
+
+ var completer = Completer<void>();
+ futureGroup.add(completer.future);
+
+ completer.complete();
+ await flushMicrotasks();
+ expect(idle, isTrue);
+ expect(futureGroup.isIdle, isTrue);
+
+ idle = false;
+ completer = Completer<void>();
+ futureGroup.add(completer.future);
+
+ await flushMicrotasks();
+ expect(idle, isFalse);
+ expect(futureGroup.isIdle, isFalse);
+
+ completer.complete();
+ await flushMicrotasks();
+ expect(idle, isTrue);
+ expect(futureGroup.isIdle, isTrue);
+ });
+
+ test('emits an event when the group closes', () async {
+ // It's important that the order of events here stays consistent over
+ // time, since code may rely on it in subtle ways.
+ var idle = false;
+ var onIdleDone = false;
+ var futureFired = false;
+
+ futureGroup.onIdle.listen(expectAsync1((_) {
+ expect(futureFired, isFalse);
+ idle = true;
+ }), onDone: expectAsync0(() {
+ expect(idle, isTrue);
+ expect(futureFired, isFalse);
+ onIdleDone = true;
+ }));
+
+ futureGroup.future.then(expectAsync1((_) {
+ expect(idle, isTrue);
+ expect(onIdleDone, isTrue);
+ futureFired = true;
+ }));
+
+ var completer = Completer<void>();
+ futureGroup.add(completer.future);
+ futureGroup.close();
+
+ await flushMicrotasks();
+ expect(idle, isFalse);
+ expect(futureGroup.isIdle, isFalse);
+
+ completer.complete();
+ await flushMicrotasks();
+ expect(idle, isTrue);
+ expect(futureGroup.isIdle, isTrue);
+ expect(futureFired, isTrue);
+ });
+ });
+}
diff --git a/pkgs/async/test/io_sink_impl.dart b/pkgs/async/test/io_sink_impl.dart
new file mode 100644
index 0000000..ccc23c2
--- /dev/null
+++ b/pkgs/async/test/io_sink_impl.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@Deprecated('Tests deprecated functionality')
+library;
+
+import 'dart:io';
+
+import 'package:async/async.dart';
+
+/// This class isn't used, it's just used to verify that [IOSinkBase] produces a
+/// valid implementation of [IOSink].
+class IOSinkImpl extends IOSinkBase implements IOSink {
+ @override
+ void onAdd(List<int> data) {}
+
+ @override
+ void onError(Object error, [StackTrace? stackTrace]) {}
+
+ @override
+ void onClose() {}
+
+ @override
+ Future<void> onFlush() => Future.value();
+}
diff --git a/pkgs/async/test/lazy_stream_test.dart b/pkgs/async/test/lazy_stream_test.dart
new file mode 100644
index 0000000..9785b2e
--- /dev/null
+++ b/pkgs/async/test/lazy_stream_test.dart
@@ -0,0 +1,102 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ test('calls the callback when the stream is listened', () async {
+ var callbackCalled = false;
+ var stream = LazyStream(expectAsync0(() {
+ callbackCalled = true;
+ return const Stream.empty();
+ }));
+
+ await flushMicrotasks();
+ expect(callbackCalled, isFalse);
+
+ stream.listen(null);
+ expect(callbackCalled, isTrue);
+ });
+
+ test('calls the callback when the stream is listened', () async {
+ var callbackCalled = false;
+ var stream = LazyStream(expectAsync0(() {
+ callbackCalled = true;
+ return const Stream.empty();
+ }));
+
+ await flushMicrotasks();
+ expect(callbackCalled, isFalse);
+
+ stream.listen(null);
+ expect(callbackCalled, isTrue);
+ });
+
+ test('forwards to a synchronously-provided stream', () async {
+ var controller = StreamController<int>();
+ var stream = LazyStream(expectAsync0(() => controller.stream));
+
+ var events = [];
+ stream.listen(events.add);
+
+ controller.add(1);
+ await flushMicrotasks();
+ expect(events, equals([1]));
+
+ controller.add(2);
+ await flushMicrotasks();
+ expect(events, equals([1, 2]));
+
+ controller.add(3);
+ await flushMicrotasks();
+ expect(events, equals([1, 2, 3]));
+
+ controller.close();
+ });
+
+ test('forwards to an asynchronously-provided stream', () async {
+ var controller = StreamController<int>();
+ var stream = LazyStream(expectAsync0(() async => controller.stream));
+
+ var events = [];
+ stream.listen(events.add);
+
+ controller.add(1);
+ await flushMicrotasks();
+ expect(events, equals([1]));
+
+ controller.add(2);
+ await flushMicrotasks();
+ expect(events, equals([1, 2]));
+
+ controller.add(3);
+ await flushMicrotasks();
+ expect(events, equals([1, 2, 3]));
+
+ controller.close();
+ });
+
+ test("a lazy stream can't be listened to multiple times", () {
+ var stream = LazyStream(expectAsync0(Stream.empty));
+ expect(stream.isBroadcast, isFalse);
+
+ stream.listen(null);
+ expect(() => stream.listen(null), throwsStateError);
+ expect(() => stream.listen(null), throwsStateError);
+ });
+
+ test("a lazy stream can't be listened to from within its callback", () {
+ late LazyStream stream;
+ stream = LazyStream(expectAsync0(() {
+ expect(() => stream.listen(null), throwsStateError);
+ return const Stream.empty();
+ }));
+ stream.listen(null);
+ });
+}
diff --git a/pkgs/async/test/null_stream_sink_test.dart b/pkgs/async/test/null_stream_sink_test.dart
new file mode 100644
index 0000000..16d6986
--- /dev/null
+++ b/pkgs/async/test/null_stream_sink_test.dart
@@ -0,0 +1,113 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('constructors', () {
+ test('done defaults to a completed future', () {
+ var sink = NullStreamSink();
+ expect(sink.done, completes);
+ });
+
+ test('a custom future may be passed to done', () async {
+ var completer = Completer<void>();
+ var sink = NullStreamSink(done: completer.future);
+
+ var doneFired = false;
+ sink.done.then((_) {
+ doneFired = true;
+ });
+ await flushMicrotasks();
+ expect(doneFired, isFalse);
+
+ completer.complete();
+ await flushMicrotasks();
+ expect(doneFired, isTrue);
+ });
+
+ test('NullStreamSink.error passes an error to done', () {
+ var sink = NullStreamSink.error('oh no');
+ expect(sink.done, throwsA('oh no'));
+ });
+ });
+
+ group('events', () {
+ test('are silently dropped before close', () {
+ var sink = NullStreamSink();
+ sink.add(1);
+ sink.addError('oh no');
+ });
+
+ test('throw StateErrors after close', () {
+ var sink = NullStreamSink();
+ expect(sink.close(), completes);
+
+ expect(() => sink.add(1), throwsStateError);
+ expect(() => sink.addError('oh no'), throwsStateError);
+ expect(() => sink.addStream(const Stream.empty()), throwsStateError);
+ });
+
+ group('addStream', () {
+ test('listens to the stream then cancels immediately', () async {
+ var sink = NullStreamSink();
+ var canceled = false;
+ var controller = StreamController(onCancel: () {
+ canceled = true;
+ });
+
+ expect(sink.addStream(controller.stream), completes);
+ await flushMicrotasks();
+ expect(canceled, isTrue);
+ });
+
+ test('returns the cancel future', () async {
+ var completer = Completer<void>();
+ var sink = NullStreamSink();
+ var controller = StreamController(onCancel: () => completer.future);
+
+ var addStreamFired = false;
+ sink.addStream(controller.stream).then((_) {
+ addStreamFired = true;
+ });
+ await flushMicrotasks();
+ expect(addStreamFired, isFalse);
+
+ completer.complete();
+ await flushMicrotasks();
+ expect(addStreamFired, isTrue);
+ });
+
+ test('pipes errors from the cancel future through addStream', () async {
+ var sink = NullStreamSink();
+ var controller = StreamController(onCancel: () => throw 'oh no');
+ expect(sink.addStream(controller.stream), throwsA('oh no'));
+ });
+
+ test('causes events to throw StateErrors until the future completes',
+ () async {
+ var sink = NullStreamSink();
+ var future = sink.addStream(const Stream.empty());
+ expect(() => sink.add(1), throwsStateError);
+ expect(() => sink.addError('oh no'), throwsStateError);
+ expect(() => sink.addStream(const Stream.empty()), throwsStateError);
+
+ await future;
+ sink.add(1);
+ sink.addError('oh no');
+ expect(sink.addStream(const Stream.empty()), completes);
+ });
+ });
+ });
+
+ test('close returns the done future', () {
+ var sink = NullStreamSink.error('oh no');
+ expect(sink.close(), throwsA('oh no'));
+ });
+}
diff --git a/pkgs/async/test/reject_errors_test.dart b/pkgs/async/test/reject_errors_test.dart
new file mode 100644
index 0000000..27e3c25
--- /dev/null
+++ b/pkgs/async/test/reject_errors_test.dart
@@ -0,0 +1,208 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE filevents.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late StreamController controller;
+ setUp(() {
+ controller = StreamController<void>();
+ });
+
+ test('passes through data events', () {
+ controller.sink.rejectErrors()
+ ..add(1)
+ ..add(2)
+ ..add(3);
+ expect(controller.stream, emitsInOrder([1, 2, 3]));
+ });
+
+ test('passes through close events', () {
+ controller.sink.rejectErrors()
+ ..add(1)
+ ..close();
+ expect(controller.stream, emitsInOrder([1, emitsDone]));
+ });
+
+ test('passes through data events from addStream()', () {
+ controller.sink.rejectErrors().addStream(Stream.fromIterable([1, 2, 3]));
+ expect(controller.stream, emitsInOrder([1, 2, 3]));
+ });
+
+ test('allows multiple addStream() calls', () async {
+ var transformed = controller.sink.rejectErrors();
+ await transformed.addStream(Stream.fromIterable([1, 2, 3]));
+ await transformed.addStream(Stream.fromIterable([4, 5, 6]));
+ expect(controller.stream, emitsInOrder([1, 2, 3, 4, 5, 6]));
+ });
+
+ group('on addError()', () {
+ test('forwards the error to done', () {
+ var transformed = controller.sink.rejectErrors();
+ transformed.addError('oh no');
+ expect(transformed.done, throwsA('oh no'));
+ });
+
+ test('closes the underlying sink', () {
+ var transformed = controller.sink.rejectErrors();
+ transformed.addError('oh no');
+ transformed.done.catchError((_) {});
+
+ expect(controller.stream, emitsDone);
+ });
+
+ test('ignores further events', () async {
+ var transformed = controller.sink.rejectErrors();
+ transformed.addError('oh no');
+ transformed.done.catchError((_) {});
+ expect(controller.stream, emitsDone);
+
+ // Try adding events synchronously and asynchronously and verify that they
+ // don't throw and also aren't passed to the underlying sink.
+ transformed
+ ..add(1)
+ ..addError('another');
+ await pumpEventQueue();
+ transformed
+ ..add(2)
+ ..addError('yet another');
+ });
+
+ test('cancels the current subscription', () async {
+ var inputCanceled = false;
+ var inputController =
+ StreamController(onCancel: () => inputCanceled = true);
+
+ var transformed = controller.sink.rejectErrors()
+ ..addStream(inputController.stream);
+ inputController.addError('oh no');
+ transformed.done.catchError((_) {});
+
+ await pumpEventQueue();
+ expect(inputCanceled, isTrue);
+ });
+ });
+
+ group('when the inner sink\'s done future completes', () {
+ test('done completes', () async {
+ var completer = Completer<void>();
+ var transformed = NullStreamSink(done: completer.future).rejectErrors();
+
+ var doneCompleted = false;
+ transformed.done.then((_) => doneCompleted = true);
+ await pumpEventQueue();
+ expect(doneCompleted, isFalse);
+
+ completer.complete();
+ await pumpEventQueue();
+ expect(doneCompleted, isTrue);
+ });
+
+ test('an outstanding addStream() completes', () async {
+ var completer = Completer<void>();
+ var transformed = NullStreamSink(done: completer.future).rejectErrors();
+
+ var addStreamCompleted = false;
+ transformed
+ .addStream(StreamController().stream)
+ .then((_) => addStreamCompleted = true);
+ await pumpEventQueue();
+ expect(addStreamCompleted, isFalse);
+
+ completer.complete();
+ await pumpEventQueue();
+ expect(addStreamCompleted, isTrue);
+ });
+
+ test('an outstanding addStream()\'s subscription is cancelled', () async {
+ var completer = Completer<void>();
+ var transformed = NullStreamSink(done: completer.future).rejectErrors();
+
+ var addStreamCancelled = false;
+ transformed.addStream(
+ StreamController(onCancel: () => addStreamCancelled = true).stream);
+ await pumpEventQueue();
+ expect(addStreamCancelled, isFalse);
+
+ completer.complete();
+ await pumpEventQueue();
+ expect(addStreamCancelled, isTrue);
+ });
+
+ test('forwards an outstanding addStream()\'s cancellation error', () async {
+ var completer = Completer<void>();
+ var transformed = NullStreamSink(done: completer.future).rejectErrors();
+
+ expect(
+ transformed.addStream(
+ StreamController(onCancel: () => throw 'oh no').stream),
+ throwsA('oh no'));
+ completer.complete();
+ });
+
+ group('forwards its error', () {
+ test('through done', () async {
+ expect(NullStreamSink(done: Future.error('oh no')).rejectErrors().done,
+ throwsA('oh no'));
+ });
+
+ test('through close', () async {
+ expect(
+ NullStreamSink(done: Future.error('oh no')).rejectErrors().close(),
+ throwsA('oh no'));
+ });
+ });
+ });
+
+ group('after closing', () {
+ test('throws on add()', () {
+ var sink = controller.sink.rejectErrors()..close();
+ expect(() => sink.add(1), throwsStateError);
+ });
+
+ test('throws on addError()', () {
+ var sink = controller.sink.rejectErrors()..close();
+ expect(() => sink.addError('oh no'), throwsStateError);
+ });
+
+ test('throws on addStream()', () {
+ var sink = controller.sink.rejectErrors()..close();
+ expect(() => sink.addStream(const Stream.empty()), throwsStateError);
+ });
+
+ test('allows close()', () {
+ var sink = controller.sink.rejectErrors()..close();
+ sink.close(); // Shouldn't throw
+ });
+ });
+
+ group('during an active addStream()', () {
+ test('throws on add()', () {
+ var sink = controller.sink.rejectErrors()
+ ..addStream(StreamController().stream);
+ expect(() => sink.add(1), throwsStateError);
+ });
+
+ test('throws on addError()', () {
+ var sink = controller.sink.rejectErrors()
+ ..addStream(StreamController().stream);
+ expect(() => sink.addError('oh no'), throwsStateError);
+ });
+
+ test('throws on addStream()', () {
+ var sink = controller.sink.rejectErrors()
+ ..addStream(StreamController().stream);
+ expect(() => sink.addStream(const Stream.empty()), throwsStateError);
+ });
+
+ test('throws on close()', () {
+ var sink = controller.sink.rejectErrors()
+ ..addStream(StreamController().stream);
+ expect(() => sink.close(), throwsStateError);
+ });
+ });
+}
diff --git a/pkgs/async/test/restartable_timer_test.dart b/pkgs/async/test/restartable_timer_test.dart
new file mode 100644
index 0000000..4aab287
--- /dev/null
+++ b/pkgs/async/test/restartable_timer_test.dart
@@ -0,0 +1,107 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:async/async.dart';
+import 'package:fake_async/fake_async.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('runs the callback once the duration has elapsed', () {
+ FakeAsync().run((async) {
+ var fired = false;
+ RestartableTimer(const Duration(seconds: 5), () {
+ fired = true;
+ });
+
+ async.elapse(const Duration(seconds: 4));
+ expect(fired, isFalse);
+
+ async.elapse(const Duration(seconds: 1));
+ expect(fired, isTrue);
+ });
+ });
+
+ test("doesn't run the callback if the timer is canceled", () {
+ FakeAsync().run((async) {
+ var fired = false;
+ var timer = RestartableTimer(const Duration(seconds: 5), () {
+ fired = true;
+ });
+
+ async.elapse(const Duration(seconds: 4));
+ expect(fired, isFalse);
+ timer.cancel();
+
+ async.elapse(const Duration(seconds: 4));
+ expect(fired, isFalse);
+ });
+ });
+
+ test('resets the duration if the timer is reset before it fires', () {
+ FakeAsync().run((async) {
+ var fired = false;
+ var timer = RestartableTimer(const Duration(seconds: 5), () {
+ fired = true;
+ });
+
+ async.elapse(const Duration(seconds: 4));
+ expect(fired, isFalse);
+ timer.reset();
+
+ async.elapse(const Duration(seconds: 4));
+ expect(fired, isFalse);
+
+ async.elapse(const Duration(seconds: 1));
+ expect(fired, isTrue);
+ });
+ });
+
+ test('re-runs the callback if the timer is reset after firing', () {
+ FakeAsync().run((async) {
+ var fired = 0;
+ var timer = RestartableTimer(const Duration(seconds: 5), () {
+ fired++;
+ });
+
+ async.elapse(const Duration(seconds: 5));
+ expect(fired, equals(1));
+ timer.reset();
+
+ async.elapse(const Duration(seconds: 5));
+ expect(fired, equals(2));
+ timer.reset();
+
+ async.elapse(const Duration(seconds: 5));
+ expect(fired, equals(3));
+ });
+ });
+
+ test('runs the callback if the timer is reset after being canceled', () {
+ FakeAsync().run((async) {
+ var fired = false;
+ var timer = RestartableTimer(const Duration(seconds: 5), () {
+ fired = true;
+ });
+
+ async.elapse(const Duration(seconds: 4));
+ expect(fired, isFalse);
+ timer.cancel();
+
+ async.elapse(const Duration(seconds: 4));
+ expect(fired, isFalse);
+ timer.reset();
+
+ async.elapse(const Duration(seconds: 5));
+ expect(fired, isTrue);
+ });
+ });
+
+ test("only runs the callback once if the timer isn't reset", () {
+ FakeAsync().run((async) {
+ RestartableTimer(
+ const Duration(seconds: 5), expectAsync0(() {}, count: 1));
+ async.elapse(const Duration(seconds: 10));
+ });
+ });
+}
diff --git a/pkgs/async/test/result/result_captureAll_test.dart b/pkgs/async/test/result/result_captureAll_test.dart
new file mode 100644
index 0000000..e85999e
--- /dev/null
+++ b/pkgs/async/test/result/result_captureAll_test.dart
@@ -0,0 +1,195 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: file_names
+
+import 'dart:async';
+import 'dart:math' show Random;
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+final someStack = StackTrace.current;
+
+Result<int> res(int n) => Result<int>.value(n);
+
+Result err(int n) => ErrorResult('$n', someStack);
+
+/// Helper function creating an iterable of futures.
+Iterable<Future<int>> futures(int count,
+ {bool Function(int index)? throwWhen}) sync* {
+ for (var i = 0; i < count; i++) {
+ if (throwWhen != null && throwWhen(i)) {
+ yield Future<int>.error('$i', someStack);
+ } else {
+ yield Future<int>.value(i);
+ }
+ }
+}
+
+void main() {
+ test('empty', () async {
+ var all = await Result.captureAll<int>(futures(0));
+ expect(all, []);
+ });
+
+ group('futures only,', () {
+ test('single', () async {
+ var all = await Result.captureAll<int>(futures(1));
+ expect(all, [res(0)]);
+ });
+
+ test('multiple', () async {
+ var all = await Result.captureAll<int>(futures(3));
+ expect(all, [res(0), res(1), res(2)]);
+ });
+
+ test('error only', () async {
+ var all =
+ await Result.captureAll<int>(futures(1, throwWhen: (_) => true));
+ expect(all, [err(0)]);
+ });
+
+ test('multiple error only', () async {
+ var all =
+ await Result.captureAll<int>(futures(3, throwWhen: (_) => true));
+ expect(all, [err(0), err(1), err(2)]);
+ });
+
+ test('mixed error and value', () async {
+ var all =
+ await Result.captureAll<int>(futures(4, throwWhen: (x) => x.isOdd));
+ expect(all, [res(0), err(1), res(2), err(3)]);
+ });
+
+ test('completion permutation 1-2-3', () async {
+ var cs = List.generate(3, (_) => Completer<int>());
+ var all = Result.captureAll<int>(cs.map((c) => c.future));
+ expect(all, completion([res(1), res(2), err(3)]));
+ await _microTask();
+ cs[0].complete(1);
+ await _microTask();
+ cs[1].complete(2);
+ await _microTask();
+ cs[2].completeError('3', someStack);
+ });
+
+ test('completion permutation 1-3-2', () async {
+ var cs = List.generate(3, (_) => Completer<int>());
+ var all = Result.captureAll<int>(cs.map((c) => c.future));
+ expect(all, completion([res(1), res(2), err(3)]));
+ await _microTask();
+ cs[0].complete(1);
+ await _microTask();
+ cs[2].completeError('3', someStack);
+ await _microTask();
+ cs[1].complete(2);
+ });
+
+ test('completion permutation 2-1-3', () async {
+ var cs = List.generate(3, (_) => Completer<int>());
+ var all = Result.captureAll<int>(cs.map((c) => c.future));
+ expect(all, completion([res(1), res(2), err(3)]));
+ await _microTask();
+ cs[1].complete(2);
+ await _microTask();
+ cs[0].complete(1);
+ await _microTask();
+ cs[2].completeError('3', someStack);
+ });
+
+ test('completion permutation 2-3-1', () async {
+ var cs = List.generate(3, (_) => Completer<int>());
+ var all = Result.captureAll<int>(cs.map((c) => c.future));
+ expect(all, completion([res(1), res(2), err(3)]));
+ await _microTask();
+ cs[1].complete(2);
+ await _microTask();
+ cs[2].completeError('3', someStack);
+ await _microTask();
+ cs[0].complete(1);
+ });
+
+ test('completion permutation 3-1-2', () async {
+ var cs = List.generate(3, (_) => Completer<int>());
+ var all = Result.captureAll<int>(cs.map((c) => c.future));
+ expect(all, completion([res(1), res(2), err(3)]));
+ await _microTask();
+ cs[2].completeError('3', someStack);
+ await _microTask();
+ cs[0].complete(1);
+ await _microTask();
+ cs[1].complete(2);
+ });
+
+ test('completion permutation 3-2-1', () async {
+ var cs = List.generate(3, (_) => Completer<int>());
+ var all = Result.captureAll<int>(cs.map((c) => c.future));
+ expect(all, completion([res(1), res(2), err(3)]));
+ await _microTask();
+ cs[2].completeError('3', someStack);
+ await _microTask();
+ cs[1].complete(2);
+ await _microTask();
+ cs[0].complete(1);
+ });
+
+ var seed = Random().nextInt(0x100000000);
+ var n = 25; // max 32, otherwise rnd.nextInt(1<<n) won't work.
+ test('randomized #$n seed:${seed.toRadixString(16)}', () async {
+ var cs = List.generate(n, (_) => Completer<int>());
+ var all = Result.captureAll<int>(cs.map((c) => c.future));
+ var rnd = Random(seed);
+ var throwFlags = rnd.nextInt(1 << n); // Bit-flag for throwing.
+ bool throws(int index) => (throwFlags & (1 << index)) != 0;
+ var expected = List.generate(n, (x) => throws(x) ? err(x) : res(x));
+
+ expect(all, completion(expected));
+
+ var completeFunctions = List<void Function()>.generate(n, (i) {
+ var c = cs[i];
+ return () =>
+ throws(i) ? c.completeError('$i', someStack) : c.complete(i);
+ });
+ completeFunctions.shuffle(rnd);
+ for (var i = 0; i < n; i++) {
+ await _microTask();
+ completeFunctions[i]();
+ }
+ });
+ });
+ group('values only,', () {
+ test('single', () async {
+ var all = await Result.captureAll<int>(<int>[1]);
+ expect(all, [res(1)]);
+ });
+ test('multiple', () async {
+ var all = await Result.captureAll<int>(<int>[1, 2, 3]);
+ expect(all, [res(1), res(2), res(3)]);
+ });
+ });
+ group('mixed futures and values,', () {
+ test('no error', () async {
+ var all = await Result.captureAll<int>(<FutureOr<int>>[
+ 1,
+ Future<int>(() => 2),
+ 3,
+ Future<int>.value(4),
+ ]);
+ expect(all, [res(1), res(2), res(3), res(4)]);
+ });
+ test('error', () async {
+ var all = await Result.captureAll<int>(<FutureOr<int>>[
+ 1,
+ Future<int>(() => 2),
+ 3,
+ Future<int>(() async => await Future.error('4', someStack)),
+ Future<int>.value(5)
+ ]);
+ expect(all, [res(1), res(2), res(3), err(4), res(5)]);
+ });
+ });
+}
+
+Future<void> _microTask() => Future.microtask(() {});
diff --git a/pkgs/async/test/result/result_flattenAll_test.dart b/pkgs/async/test/result/result_flattenAll_test.dart
new file mode 100644
index 0000000..0d2b963
--- /dev/null
+++ b/pkgs/async/test/result/result_flattenAll_test.dart
@@ -0,0 +1,57 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: file_names
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+final someStack = StackTrace.current;
+Result<T> res<T>(T n) => Result<T>.value(n);
+Result<T> err<T>(int n) => ErrorResult('$n', someStack);
+
+/// Helper function creating an iterable of results.
+Iterable<Result<int>> results(int count,
+ {bool Function(int index)? throwWhen}) sync* {
+ for (var i = 0; i < count; i++) {
+ if (throwWhen != null && throwWhen(i)) {
+ yield err(i);
+ } else {
+ yield res(i);
+ }
+ }
+}
+
+void main() {
+ void expectAll<T>(Result<T> result, Result<T> expectation) {
+ if (expectation.isError) {
+ expect(result, expectation);
+ } else {
+ expect(result.isValue, true);
+ expect(result.asValue!.value, expectation.asValue!.value);
+ }
+ }
+
+ test('empty', () {
+ expectAll(Result.flattenAll<int>(results(0)), res([]));
+ });
+ test('single value', () {
+ expectAll(Result.flattenAll<int>(results(1)), res([0]));
+ });
+ test('single error', () {
+ expectAll(
+ Result.flattenAll<int>(results(1, throwWhen: (_) => true)), err(0));
+ });
+ test('multiple values', () {
+ expectAll(Result.flattenAll<int>(results(5)), res([0, 1, 2, 3, 4]));
+ });
+ test('multiple errors', () {
+ expectAll(Result.flattenAll<int>(results(5, throwWhen: (x) => x.isOdd)),
+ err(1)); // First error is result.
+ });
+ test('error last', () {
+ expectAll(
+ Result.flattenAll<int>(results(5, throwWhen: (x) => x == 4)), err(4));
+ });
+}
diff --git a/pkgs/async/test/result/result_future_test.dart b/pkgs/async/test/result/result_future_test.dart
new file mode 100644
index 0000000..de21884
--- /dev/null
+++ b/pkgs/async/test/result/result_future_test.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late Completer completer;
+ late ResultFuture future;
+ setUp(() {
+ completer = Completer<void>();
+ future = ResultFuture(completer.future);
+ });
+
+ test('before completion, result is null', () {
+ expect(future.result, isNull);
+ });
+
+ test('after successful completion, result is the value of the future', () {
+ completer.complete(12);
+
+ // The completer calls its listeners asynchronously. We have to wait
+ // before we can access the result.
+ expect(future.then((_) => future.result!.asValue!.value),
+ completion(equals(12)));
+ });
+
+ test("after an error completion, result is the future's error", () {
+ var trace = Trace.current();
+ completer.completeError('error', trace);
+
+ // The completer calls its listeners asynchronously. We have to wait
+ // before we can access the result.
+ return future.catchError((_) {}).then((_) {
+ var error = future.result!.asError!;
+ expect(error.error, equals('error'));
+ expect(error.stackTrace, equals(trace));
+ });
+ });
+}
diff --git a/pkgs/async/test/result/result_test.dart b/pkgs/async/test/result/result_test.dart
new file mode 100644
index 0000000..13a5d53
--- /dev/null
+++ b/pkgs/async/test/result/result_test.dart
@@ -0,0 +1,358 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'package:async/async.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test/test.dart';
+
+void main() {
+ var stack = Trace.current();
+
+ test('create result value', () {
+ var result = Result<int>.value(42);
+ expect(result.isValue, isTrue);
+ expect(result.isError, isFalse);
+ ValueResult value = result.asValue!;
+ expect(value.value, equals(42));
+ });
+
+ test('create result value 2', () {
+ Result<int> result = ValueResult<int>(42);
+ expect(result.isValue, isTrue);
+ expect(result.isError, isFalse);
+ var value = result.asValue!;
+ expect(value.value, equals(42));
+ });
+
+ test('create result error', () {
+ var result = Result<bool>.error('BAD', stack);
+ expect(result.isValue, isFalse);
+ expect(result.isError, isTrue);
+ var error = result.asError!;
+ expect(error.error, equals('BAD'));
+ expect(error.stackTrace, same(stack));
+ });
+
+ test('create result error 2', () {
+ var result = ErrorResult('BAD', stack);
+ expect(result.isValue, isFalse);
+ expect(result.isError, isTrue);
+ var error = result.asError;
+ expect(error.error, equals('BAD'));
+ expect(error.stackTrace, same(stack));
+ });
+
+ test('create result error no stack', () {
+ var result = Result<bool>.error('BAD');
+ expect(result.isValue, isFalse);
+ expect(result.isError, isTrue);
+ var error = result.asError!;
+ expect(error.error, equals('BAD'));
+ // A default stack trace is created
+ expect(error.stackTrace, isNotNull);
+ });
+
+ test('complete with value', () {
+ Result<int> result = ValueResult<int>(42);
+ var c = Completer<int>();
+ c.future.then(expectAsync1((int v) {
+ expect(v, equals(42));
+ }), onError: (Object? e, s) {
+ fail('Unexpected error');
+ });
+ result.complete(c);
+ });
+
+ test('complete with error', () {
+ Result<bool> result = ErrorResult('BAD', stack);
+ var c = Completer<bool>();
+ c.future.then((bool v) {
+ fail('Unexpected value $v');
+ }).then<void>((_) {}, onError: expectAsync2((e, s) {
+ expect(e, equals('BAD'));
+ expect(s, same(stack));
+ }));
+ result.complete(c);
+ });
+
+ test('add sink value', () {
+ var result = ValueResult<int>(42);
+ EventSink<int> sink = TestSink(onData: expectAsync1((v) {
+ expect(v, equals(42));
+ }));
+ result.addTo(sink);
+ });
+
+ test('add sink error', () {
+ Result<bool> result = ErrorResult('BAD', stack);
+ EventSink<bool> sink = TestSink(onError: expectAsync2((e, s) {
+ expect(e, equals('BAD'));
+ expect(s, same(stack));
+ }));
+ result.addTo(sink);
+ });
+
+ test('value as future', () {
+ Result<int> result = ValueResult<int>(42);
+ result.asFuture.then(expectAsync1((int v) {
+ expect(v, equals(42));
+ }), onError: (Object? e, s) {
+ fail('Unexpected error');
+ });
+ });
+
+ test('error as future', () {
+ Result<bool> result = ErrorResult('BAD', stack);
+ result.asFuture.then((bool v) {
+ fail('Unexpected value $v');
+ }).then<void>((_) {}, onError: expectAsync2((e, s) {
+ expect(e, equals('BAD'));
+ expect(s, same(stack));
+ }));
+ });
+
+ test('capture future value', () {
+ var value = Future<int>.value(42);
+ Result.capture(value).then(expectAsync1((Result result) {
+ expect(result.isValue, isTrue);
+ expect(result.isError, isFalse);
+ var value = result.asValue!;
+ expect(value.value, equals(42));
+ }), onError: (Object? e, s) {
+ fail('Unexpected error: $e');
+ });
+ });
+
+ test('capture future error', () {
+ var value = Future<bool>.error('BAD', stack);
+ Result.capture(value).then(expectAsync1((Result result) {
+ expect(result.isValue, isFalse);
+ expect(result.isError, isTrue);
+ var error = result.asError!;
+ expect(error.error, equals('BAD'));
+ expect(error.stackTrace, same(stack));
+ }), onError: (Object? e, s) {
+ fail('Unexpected error: $e');
+ });
+ });
+
+ test('release future value', () {
+ var future = Future<Result<int>>.value(Result<int>.value(42));
+ Result.release(future).then(expectAsync1((v) {
+ expect(v, equals(42));
+ }), onError: (Object? e, s) {
+ fail('Unexpected error: $e');
+ });
+ });
+
+ test('release future error', () {
+ // An error in the result is unwrapped and reified by release.
+ var future = Future<Result<bool>>.value(Result<bool>.error('BAD', stack));
+ Result.release(future).then((v) {
+ fail('Unexpected value: $v');
+ }).then<void>((_) {}, onError: expectAsync2((e, s) {
+ expect(e, equals('BAD'));
+ expect(s, same(stack));
+ }));
+ });
+
+ test('release future real error', () {
+ // An error in the error lane is passed through by release.
+ var future = Future<Result<bool>>.error('BAD', stack);
+ Result.release(future).then((v) {
+ fail('Unexpected value: $v');
+ }).then<void>((_) {}, onError: expectAsync2((e, s) {
+ expect(e, equals('BAD'));
+ expect(s, same(stack));
+ }));
+ });
+
+ test('capture stream', () {
+ var c = StreamController<int>();
+ var stream = Result.captureStream(c.stream);
+ var expectedList = Queue.of(
+ [Result.value(42), Result.error('BAD', stack), Result.value(37)]);
+ void listener(Result actual) {
+ expect(expectedList.isEmpty, isFalse);
+ expectResult(actual, expectedList.removeFirst());
+ }
+
+ stream.listen(expectAsync1(listener, count: 3),
+ onDone: expectAsync0(() {}), cancelOnError: true);
+ c.add(42);
+ c.addError('BAD', stack);
+ c.add(37);
+ c.close();
+ });
+
+ test('release stream', () {
+ var c = StreamController<Result<int>>();
+ var stream = Result.releaseStream(c.stream);
+ var events = [
+ Result<int>.value(42),
+ Result<int>.error('BAD', stack),
+ Result<int>.value(37)
+ ];
+ // Expect the data events, and an extra error event.
+ var expectedList = Queue.of(events)..add(Result.error('BAD2', stack));
+
+ void dataListener(int v) {
+ expect(expectedList.isEmpty, isFalse);
+ Result expected = expectedList.removeFirst();
+ expect(expected.isValue, isTrue);
+ expect(v, equals(expected.asValue!.value));
+ }
+
+ void errorListener(Object error, StackTrace stackTrace) {
+ expect(expectedList.isEmpty, isFalse);
+ Result expected = expectedList.removeFirst();
+ expect(expected.isError, isTrue);
+ expect(error, equals(expected.asError!.error));
+ expect(stackTrace, same(expected.asError!.stackTrace));
+ }
+
+ stream.listen(expectAsync1(dataListener, count: 2),
+ onError: expectAsync2(errorListener, count: 2),
+ onDone: expectAsync0(() {}));
+ for (var result in events) {
+ c.add(result); // Result value or error in data line.
+ }
+ c.addError('BAD2', stack); // Error in error line.
+ c.close();
+ });
+
+ test('release stream cancel on error', () {
+ var c = StreamController<Result<int>>();
+ var stream = Result.releaseStream(c.stream);
+ stream.listen(expectAsync1((v) {
+ expect(v, equals(42));
+ }), onError: expectAsync2((e, s) {
+ expect(e, equals('BAD'));
+ expect(s, same(stack));
+ }), onDone: () {
+ fail('Unexpected done event');
+ }, cancelOnError: true);
+ c.add(Result.value(42));
+ c.add(Result.error('BAD', stack));
+ c.add(Result.value(37));
+ c.close();
+ });
+
+ test('flatten error 1', () {
+ var error = Result<int>.error('BAD', stack);
+ var flattened = Result.flatten(Result<Result<int>>.error('BAD', stack));
+ expectResult(flattened, error);
+ });
+
+ test('flatten error 2', () {
+ var error = Result<int>.error('BAD', stack);
+ var result = Result<Result<int>>.value(error);
+ var flattened = Result.flatten(result);
+ expectResult(flattened, error);
+ });
+
+ test('flatten value', () {
+ var result = Result<Result<int>>.value(Result<int>.value(42));
+ expectResult(Result.flatten(result), Result<int>.value(42));
+ });
+
+ test('handle unary', () {
+ var result = ErrorResult('error', stack);
+ var called = false;
+ result.handle((Object? error) {
+ called = true;
+ expect(error, 'error');
+ });
+ expect(called, isTrue);
+ });
+
+ test('handle binary', () {
+ var result = ErrorResult('error', stack);
+ var called = false;
+ result.handle((Object? error, Object? stackTrace) {
+ called = true;
+ expect(error, 'error');
+ expect(stackTrace, same(stack));
+ });
+ expect(called, isTrue);
+ });
+
+ test('handle unary and binary', () {
+ var result = ErrorResult('error', stack);
+ var called = false;
+ result.handle((Object? error, [Object? stackTrace]) {
+ called = true;
+ expect(error, 'error');
+ expect(stackTrace, same(stack));
+ });
+ expect(called, isTrue);
+ });
+
+ test('handle neither unary nor binary', () {
+ var result = ErrorResult('error', stack);
+ expect(() => result.handle(() => fail('unreachable')), throwsA(anything));
+ expect(() => result.handle((a, b, c) => fail('unreachable')),
+ throwsA(anything));
+ expect(() => result.handle((a, b, {c}) => fail('unreachable')),
+ throwsA(anything));
+ expect(() => result.handle((a, {b}) => fail('unreachable')),
+ throwsA(anything));
+ expect(() => result.handle(({a, b}) => fail('unreachable')),
+ throwsA(anything));
+ expect(
+ () => result.handle(({a}) => fail('unreachable')), throwsA(anything));
+ });
+}
+
+void expectResult(Result actual, Result expected) {
+ expect(actual.isValue, equals(expected.isValue));
+ expect(actual.isError, equals(expected.isError));
+ if (actual.isValue) {
+ expect(actual.asValue!.value, equals(expected.asValue!.value));
+ } else {
+ expect(actual.asError!.error, equals(expected.asError!.error));
+ expect(actual.asError!.stackTrace, same(expected.asError!.stackTrace));
+ }
+}
+
+class TestSink<T> implements EventSink<T> {
+ final void Function(T) onData;
+ final void Function(dynamic, StackTrace) onError;
+ final void Function() onDone;
+
+ TestSink(
+ {this.onData = _nullData,
+ this.onError = _nullError,
+ this.onDone = _nullDone});
+
+ @override
+ void add(T value) {
+ onData(value);
+ }
+
+ @override
+ void addError(Object error, [StackTrace? stack]) {
+ onError(error, stack ?? StackTrace.fromString(''));
+ }
+
+ @override
+ void close() {
+ onDone();
+ }
+
+ static void _nullData(dynamic value) {
+ fail('Unexpected sink add: $value');
+ }
+
+ static void _nullError(dynamic e, StackTrace s) {
+ fail('Unexpected sink addError: $e');
+ }
+
+ static void _nullDone() {
+ fail('Unepxected sink close');
+ }
+}
diff --git a/pkgs/async/test/single_subscription_transformer_test.dart b/pkgs/async/test/single_subscription_transformer_test.dart
new file mode 100644
index 0000000..95b321b
--- /dev/null
+++ b/pkgs/async/test/single_subscription_transformer_test.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ test("buffers events as soon as it's bound", () async {
+ var controller = StreamController.broadcast();
+ var stream =
+ controller.stream.transform(const SingleSubscriptionTransformer());
+
+ // Add events before [stream] has a listener to be sure it buffers them.
+ controller.add(1);
+ controller.add(2);
+ await flushMicrotasks();
+
+ expect(stream.toList(), completion(equals([1, 2, 3, 4])));
+ await flushMicrotasks();
+
+ controller.add(3);
+ controller.add(4);
+ controller.close();
+ });
+
+ test("cancels the subscription to the broadcast stream when it's canceled",
+ () async {
+ var canceled = false;
+ var controller = StreamController.broadcast(onCancel: () {
+ canceled = true;
+ });
+ var stream =
+ controller.stream.transform(const SingleSubscriptionTransformer());
+ await flushMicrotasks();
+ expect(canceled, isFalse);
+
+ var subscription = stream.listen(null);
+ await flushMicrotasks();
+ expect(canceled, isFalse);
+
+ subscription.cancel();
+ await flushMicrotasks();
+ expect(canceled, isTrue);
+ });
+}
diff --git a/pkgs/async/test/sink_base_test.dart b/pkgs/async/test/sink_base_test.dart
new file mode 100644
index 0000000..ee324f5
--- /dev/null
+++ b/pkgs/async/test/sink_base_test.dart
@@ -0,0 +1,404 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@Deprecated('Tests deprecated functionality')
+library;
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+const int letterA = 0x41;
+
+void main() {
+ // We don't explicitly test [EventSinkBase] because it shares all the relevant
+ // implementation with [StreamSinkBase].
+ group('StreamSinkBase', () {
+ test('forwards add() to onAdd()', () {
+ var sink = _StreamSink(onAdd: expectAsync1((value) {
+ expect(value, equals(123));
+ }));
+ sink.add(123);
+ });
+
+ test('forwards addError() to onError()', () {
+ var sink = _StreamSink(onError: expectAsync2((error, [stackTrace]) {
+ expect(error, equals('oh no'));
+ expect(stackTrace, isA<StackTrace>());
+ }));
+ sink.addError('oh no', StackTrace.current);
+ });
+
+ test('forwards addStream() to onAdd() and onError()', () {
+ var sink = _StreamSink(
+ onAdd: expectAsync1((value) {
+ expect(value, equals(123));
+ }, count: 1),
+ onError: expectAsync2((error, [stackTrace]) {
+ expect(error, equals('oh no'));
+ expect(stackTrace, isA<StackTrace>());
+ }));
+
+ var controller = StreamController<int>();
+ sink.addStream(controller.stream);
+
+ controller.add(123);
+ controller.addError('oh no', StackTrace.current);
+ });
+
+ test('addStream() returns once the stream closes', () async {
+ var sink = _StreamSink();
+ var controller = StreamController<int>();
+ var addStreamCompleted = false;
+ sink.addStream(controller.stream).then((_) => addStreamCompleted = true);
+
+ await pumpEventQueue();
+ expect(addStreamCompleted, isFalse);
+
+ controller.addError('oh no', StackTrace.current);
+ await pumpEventQueue();
+ expect(addStreamCompleted, isFalse);
+
+ controller.close();
+ await pumpEventQueue();
+ expect(addStreamCompleted, isTrue);
+ });
+
+ test('forwards close() to onClose()', () {
+ var sink = _StreamSink(onClose: expectAsync0(() {}));
+ expect(sink.close(), completes);
+ });
+
+ test('onClose() is only invoked once', () {
+ var sink = _StreamSink(onClose: expectAsync0(() {}, count: 1));
+ expect(sink.close(), completes);
+ expect(sink.close(), completes);
+ expect(sink.close(), completes);
+ });
+
+ test('all invocations of close() return the same future', () async {
+ var completer = Completer<void>();
+ var sink = _StreamSink(onClose: expectAsync0(() => completer.future));
+
+ var close1Completed = false;
+ sink.close().then((_) => close1Completed = true);
+
+ var close2Completed = false;
+ sink.close().then((_) => close2Completed = true);
+
+ var doneCompleted = false;
+ sink.done.then((_) => doneCompleted = true);
+
+ await pumpEventQueue();
+ expect(close1Completed, isFalse);
+ expect(close2Completed, isFalse);
+ expect(doneCompleted, isFalse);
+
+ completer.complete();
+ await pumpEventQueue();
+ expect(close1Completed, isTrue);
+ expect(close2Completed, isTrue);
+ expect(doneCompleted, isTrue);
+ });
+
+ test('done returns a future that completes once close() completes',
+ () async {
+ var completer = Completer<void>();
+ var sink = _StreamSink(onClose: expectAsync0(() => completer.future));
+
+ var doneCompleted = false;
+ sink.done.then((_) => doneCompleted = true);
+
+ await pumpEventQueue();
+ expect(doneCompleted, isFalse);
+
+ expect(sink.close(), completes);
+ await pumpEventQueue();
+ expect(doneCompleted, isFalse);
+
+ completer.complete();
+ await pumpEventQueue();
+ expect(doneCompleted, isTrue);
+ });
+
+ group('during addStream()', () {
+ test('add() throws an error', () {
+ var sink = _StreamSink(onAdd: expectAsync1((_) {}, count: 0));
+ sink.addStream(StreamController<int>().stream);
+ expect(() => sink.add(1), throwsStateError);
+ });
+
+ test('addError() throws an error', () {
+ var sink = _StreamSink(onError: expectAsync2((_, [__]) {}, count: 0));
+ sink.addStream(StreamController<int>().stream);
+ expect(() => sink.addError('oh no'), throwsStateError);
+ });
+
+ test('addStream() throws an error', () {
+ var sink = _StreamSink(onAdd: expectAsync1((_) {}, count: 0));
+ sink.addStream(StreamController<int>().stream);
+ expect(() => sink.addStream(Stream.value(123)), throwsStateError);
+ });
+
+ test('close() throws an error', () {
+ var sink = _StreamSink(onClose: expectAsync0(() {}, count: 0));
+ sink.addStream(StreamController<int>().stream);
+ expect(() => sink.close(), throwsStateError);
+ });
+ });
+
+ group("once it's closed", () {
+ test('add() throws an error', () {
+ var sink = _StreamSink(onAdd: expectAsync1((_) {}, count: 0));
+ expect(sink.close(), completes);
+ expect(() => sink.add(1), throwsStateError);
+ });
+
+ test('addError() throws an error', () {
+ var sink = _StreamSink(onError: expectAsync2((_, [__]) {}, count: 0));
+ expect(sink.close(), completes);
+ expect(() => sink.addError('oh no'), throwsStateError);
+ });
+
+ test('addStream() throws an error', () {
+ var sink = _StreamSink(onAdd: expectAsync1((_) {}, count: 0));
+ expect(sink.close(), completes);
+ expect(() => sink.addStream(Stream.value(123)), throwsStateError);
+ });
+ });
+ });
+
+ group('IOSinkBase', () {
+ group('write()', () {
+ test("doesn't call add() for the empty string", () async {
+ var sink = _IOSink(onAdd: expectAsync1((_) {}, count: 0));
+ sink.write('');
+ });
+
+ test('converts the text to data and passes it to add', () async {
+ var sink = _IOSink(onAdd: expectAsync1((data) {
+ expect(data, equals(utf8.encode('hello')));
+ }));
+ sink.write('hello');
+ });
+
+ test('calls Object.toString()', () async {
+ var sink = _IOSink(onAdd: expectAsync1((data) {
+ expect(data, equals(utf8.encode('123')));
+ }));
+ sink.write(123);
+ });
+
+ test('respects the encoding', () async {
+ var sink = _IOSink(
+ onAdd: expectAsync1((data) {
+ expect(data, equals(latin1.encode('Æ')));
+ }),
+ encoding: latin1);
+ sink.write('Æ');
+ });
+
+ test('throws if the sink is closed', () async {
+ var sink = _IOSink(onAdd: expectAsync1((_) {}, count: 0));
+ expect(sink.close(), completes);
+ expect(() => sink.write('hello'), throwsStateError);
+ });
+ });
+
+ group('writeAll()', () {
+ test('writes nothing for an empty iterable', () async {
+ var sink = _IOSink(onAdd: expectAsync1((_) {}, count: 0));
+ sink.writeAll([]);
+ });
+
+ test('writes each object in the iterable', () async {
+ var chunks = <List<int>>[];
+ var sink = _IOSink(
+ onAdd: expectAsync1((data) {
+ chunks.add(data);
+ }, count: 3));
+
+ sink.writeAll(['hello', null, 123]);
+ expect(chunks, equals(['hello', 'null', '123'].map(utf8.encode)));
+ });
+
+ test('writes separators between each object', () async {
+ var chunks = <List<int>>[];
+ var sink = _IOSink(
+ onAdd: expectAsync1((data) {
+ chunks.add(data);
+ }, count: 5));
+
+ sink.writeAll(['hello', null, 123], '/');
+ expect(chunks,
+ equals(['hello', '/', 'null', '/', '123'].map(utf8.encode)));
+ });
+
+ test('throws if the sink is closed', () async {
+ var sink = _IOSink(onAdd: expectAsync1((_) {}, count: 0));
+ expect(sink.close(), completes);
+ expect(() => sink.writeAll(['hello']), throwsStateError);
+ });
+ });
+
+ group('writeln()', () {
+ test('only writes a newline by default', () async {
+ var sink = _IOSink(
+ onAdd: expectAsync1((data) {
+ expect(data, equals(utf8.encode('\n')));
+ }, count: 1));
+ sink.writeln();
+ });
+
+ test('writes the object followed by a newline', () async {
+ var chunks = <List<int>>[];
+ var sink = _IOSink(
+ onAdd: expectAsync1((data) {
+ chunks.add(data);
+ }, count: 2));
+ sink.writeln(123);
+
+ expect(chunks, equals(['123', '\n'].map(utf8.encode)));
+ });
+
+ test('throws if the sink is closed', () async {
+ var sink = _IOSink(onAdd: expectAsync1((_) {}, count: 0));
+ expect(sink.close(), completes);
+ expect(() => sink.writeln(), throwsStateError);
+ });
+ });
+
+ group('writeCharCode()', () {
+ test('writes the character code', () async {
+ var sink = _IOSink(onAdd: expectAsync1((data) {
+ expect(data, equals(utf8.encode('A')));
+ }));
+ sink.writeCharCode(letterA);
+ });
+
+ test('respects the encoding', () async {
+ var sink = _IOSink(
+ onAdd: expectAsync1((data) {
+ expect(data, equals(latin1.encode('Æ')));
+ }),
+ encoding: latin1);
+ sink.writeCharCode('Æ'.runes.first);
+ });
+
+ test('throws if the sink is closed', () async {
+ var sink = _IOSink(onAdd: expectAsync1((_) {}, count: 0));
+ expect(sink.close(), completes);
+ expect(() => sink.writeCharCode(letterA), throwsStateError);
+ });
+ });
+
+ group('flush()', () {
+ test('returns a future that completes when onFlush() is done', () async {
+ var completer = Completer<void>();
+ var sink = _IOSink(onFlush: expectAsync0(() => completer.future));
+
+ var flushDone = false;
+ sink.flush().then((_) => flushDone = true);
+
+ await pumpEventQueue();
+ expect(flushDone, isFalse);
+
+ completer.complete();
+ await pumpEventQueue();
+ expect(flushDone, isTrue);
+ });
+
+ test('does nothing after close() is called', () {
+ var sink = _IOSink(onFlush: expectAsync0(Future.value, count: 0));
+ expect(sink.close(), completes);
+ expect(sink.flush(), completes);
+ });
+
+ test("can't be called during addStream()", () {
+ var sink = _IOSink(onFlush: expectAsync0(Future.value, count: 0));
+ sink.addStream(StreamController<List<int>>().stream);
+ expect(() => sink.flush(), throwsStateError);
+ });
+
+ test('locks the sink as though a stream was being added', () {
+ var sink =
+ _IOSink(onFlush: expectAsync0(() => Completer<void>().future));
+ sink.flush();
+ expect(() => sink.add([0]), throwsStateError);
+ expect(() => sink.addError('oh no'), throwsStateError);
+ expect(() => sink.addStream(const Stream.empty()), throwsStateError);
+ expect(() => sink.flush(), throwsStateError);
+ expect(() => sink.close(), throwsStateError);
+ });
+ });
+ });
+}
+
+/// A subclass of [StreamSinkBase] that takes all the overridable methods as
+/// callbacks, for ease of testing.
+class _StreamSink extends StreamSinkBase<int> {
+ final void Function(int value) _onAdd;
+ final void Function(Object error, [StackTrace? stackTrace]) _onError;
+ final FutureOr<void> Function() _onClose;
+
+ _StreamSink(
+ {void Function(int value)? onAdd,
+ void Function(Object error, [StackTrace? stackTrace])? onError,
+ FutureOr<void> Function()? onClose})
+ : _onAdd = onAdd ?? ((_) {}),
+ _onError = onError ?? ((_, [__]) {}),
+ _onClose = onClose ?? (() {});
+
+ @override
+ void onAdd(int value) {
+ _onAdd(value);
+ }
+
+ @override
+ void onError(Object error, [StackTrace? stackTrace]) {
+ _onError(error, stackTrace);
+ }
+
+ @override
+ FutureOr<void> onClose() => _onClose();
+}
+
+/// A subclass of [IOSinkBase] that takes all the overridable methods as
+/// callbacks, for ease of testing.
+class _IOSink extends IOSinkBase {
+ final void Function(List<int> value) _onAdd;
+ final void Function(Object error, [StackTrace? stackTrace]) _onError;
+ final FutureOr<void> Function() _onClose;
+ final Future<void> Function() _onFlush;
+
+ _IOSink(
+ {void Function(List<int> value)? onAdd,
+ void Function(Object error, [StackTrace? stackTrace])? onError,
+ FutureOr<void> Function()? onClose,
+ Future<void> Function()? onFlush,
+ Encoding encoding = utf8})
+ : _onAdd = onAdd ?? ((_) {}),
+ _onError = onError ?? ((_, [__]) {}),
+ _onClose = onClose ?? (() {}),
+ _onFlush = onFlush ?? Future.value,
+ super(encoding);
+
+ @override
+ void onAdd(List<int> value) {
+ _onAdd(value);
+ }
+
+ @override
+ void onError(Object error, [StackTrace? stackTrace]) {
+ _onError(error, stackTrace);
+ }
+
+ @override
+ FutureOr<void> onClose() => _onClose();
+
+ @override
+ Future<void> onFlush() => _onFlush();
+}
diff --git a/pkgs/async/test/stream_closer_test.dart b/pkgs/async/test/stream_closer_test.dart
new file mode 100644
index 0000000..a2bad1a
--- /dev/null
+++ b/pkgs/async/test/stream_closer_test.dart
@@ -0,0 +1,208 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ late StreamCloser<int> closer;
+ setUp(() {
+ closer = StreamCloser();
+ });
+
+ group('when the closer is never closed', () {
+ test('forwards data and done events', () {
+ expect(
+ createStream().transform(closer).toList(), completion([1, 2, 3, 4]));
+ });
+
+ test('forwards error events', () {
+ expect(Stream<int>.error('oh no').transform(closer).toList(),
+ throwsA('oh no'));
+ });
+
+ test('transforms a broadcast stream into a broadcast stream', () {
+ expect(const Stream<int>.empty().transform(closer).isBroadcast, isTrue);
+ });
+
+ test("doesn't eagerly listen", () {
+ var controller = StreamController<int>();
+ var transformed = controller.stream.transform(closer);
+ expect(controller.hasListener, isFalse);
+
+ transformed.listen(null);
+ expect(controller.hasListener, isTrue);
+ });
+
+ test('forwards pause and resume', () {
+ var controller = StreamController<int>();
+ var transformed = controller.stream.transform(closer);
+
+ var subscription = transformed.listen(null);
+ expect(controller.isPaused, isFalse);
+ subscription.pause();
+ expect(controller.isPaused, isTrue);
+ subscription.resume();
+ expect(controller.isPaused, isFalse);
+ });
+
+ test('forwards cancel', () {
+ var isCancelled = false;
+ var controller =
+ StreamController<int>(onCancel: () => isCancelled = true);
+ var transformed = controller.stream.transform(closer);
+
+ expect(isCancelled, isFalse);
+ var subscription = transformed.listen(null);
+ expect(isCancelled, isFalse);
+ subscription.cancel();
+ expect(isCancelled, isTrue);
+ });
+
+ test('forwards errors from cancel', () {
+ var controller = StreamController<int>(onCancel: () => throw 'oh no');
+
+ expect(controller.stream.transform(closer).listen(null).cancel(),
+ throwsA('oh no'));
+ });
+ });
+
+ group('when a stream is added before the closer is closed', () {
+ test('the stream emits a close event once the closer is closed', () async {
+ var queue = StreamQueue(createStream().transform(closer));
+ await expectLater(queue, emits(1));
+ await expectLater(queue, emits(2));
+ expect(closer.close(), completes);
+ expect(queue, emitsDone);
+ });
+
+ test('the inner subscription is canceled once the closer is closed', () {
+ var isCancelled = false;
+ var controller =
+ StreamController<int>(onCancel: () => isCancelled = true);
+
+ expect(controller.stream.transform(closer), emitsDone);
+ expect(closer.close(), completes);
+ expect(isCancelled, isTrue);
+ });
+
+ test('closer.close() forwards errors from StreamSubscription.cancel()', () {
+ var controller = StreamController<int>(onCancel: () => throw 'oh no');
+
+ expect(controller.stream.transform(closer), emitsDone);
+ expect(closer.close(), throwsA('oh no'));
+ });
+
+ test('closer.close() works even if a stream has already completed',
+ () async {
+ expect(await createStream().transform(closer).toList(),
+ equals([1, 2, 3, 4]));
+ expect(closer.close(), completes);
+ });
+
+ test('closer.close() works even if a stream has already been canceled',
+ () async {
+ createStream().transform(closer).listen(null).cancel();
+ expect(closer.close(), completes);
+ });
+
+ group('but listened afterwards', () {
+ test('the output stream immediately emits done', () {
+ var stream = createStream().transform(closer);
+ expect(closer.close(), completes);
+ expect(stream, emitsDone);
+ });
+
+ test(
+ 'the underlying subscription is never listened if the stream is '
+ 'never listened', () async {
+ var controller =
+ StreamController<int>(onListen: expectAsync0(() {}, count: 0));
+ controller.stream.transform(closer);
+
+ expect(closer.close(), completes);
+
+ await pumpEventQueue();
+ });
+
+ test(
+ 'the underlying subscription is listened and then canceled once the '
+ 'stream is listened', () {
+ var controller = StreamController<int>(
+ onListen: expectAsync0(() {}), onCancel: expectAsync0(() {}));
+ var stream = controller.stream.transform(closer);
+
+ expect(closer.close(), completes);
+
+ stream.listen(null);
+ });
+
+ test('Subscription.cancel() errors are silently ignored', () async {
+ var controller =
+ StreamController<int>(onCancel: expectAsync0(() => throw 'oh no'));
+ var stream = controller.stream.transform(closer);
+
+ expect(closer.close(), completes);
+
+ stream.listen(null);
+ await pumpEventQueue();
+ });
+ });
+ });
+
+ group('when a stream is added after the closer is closed', () {
+ test('the output stream immediately emits done', () {
+ expect(closer.close(), completes);
+ expect(createStream().transform(closer), emitsDone);
+ });
+
+ test(
+ 'the underlying subscription is never listened if the stream is never '
+ 'listened', () async {
+ expect(closer.close(), completes);
+
+ var controller =
+ StreamController<int>(onListen: expectAsync0(() {}, count: 0));
+ controller.stream.transform(closer);
+
+ await pumpEventQueue();
+ });
+
+ test(
+ 'the underlying subscription is listened and then canceled once the '
+ 'stream is listened', () {
+ expect(closer.close(), completes);
+
+ var controller = StreamController<int>(
+ onListen: expectAsync0(() {}), onCancel: expectAsync0(() {}));
+
+ controller.stream.transform(closer).listen(null);
+ });
+
+ test('Subscription.cancel() errors are silently ignored', () async {
+ expect(closer.close(), completes);
+
+ var controller =
+ StreamController<int>(onCancel: expectAsync0(() => throw 'oh no'));
+
+ controller.stream.transform(closer).listen(null);
+
+ await pumpEventQueue();
+ });
+ });
+}
+
+Stream<int> createStream() async* {
+ yield 1;
+ await flushMicrotasks();
+ yield 2;
+ await flushMicrotasks();
+ yield 3;
+ await flushMicrotasks();
+ yield 4;
+}
diff --git a/pkgs/async/test/stream_completer_test.dart b/pkgs/async/test/stream_completer_test.dart
new file mode 100644
index 0000000..f58162e
--- /dev/null
+++ b/pkgs/async/test/stream_completer_test.dart
@@ -0,0 +1,365 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart' show StreamCompleter;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ test('a stream is linked before listening', () async {
+ var completer = StreamCompleter<void>();
+ completer.setSourceStream(createStream());
+ expect(completer.stream.toList(), completion([1, 2, 3, 4]));
+ });
+
+ test('listened to before a stream is linked', () async {
+ var completer = StreamCompleter<void>();
+ var done = completer.stream.toList();
+ await flushMicrotasks();
+ completer.setSourceStream(createStream());
+ expect(done, completion([1, 2, 3, 4]));
+ });
+
+ test("cancel before linking a stream doesn't listen on stream", () async {
+ var completer = StreamCompleter<void>();
+ var subscription = completer.stream.listen(null);
+ subscription.pause(); // Should be ignored.
+ subscription.cancel();
+ completer.setSourceStream(UnusableStream()); // Doesn't throw.
+ });
+
+ test('listen and pause before linking stream', () async {
+ var controller = StreamCompleter<void>();
+ var events = [];
+ var subscription = controller.stream.listen(events.add);
+ var done = subscription.asFuture();
+ subscription.pause();
+ var sourceController = StreamController<int>();
+ sourceController
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..add(4);
+ controller.setSourceStream(sourceController.stream);
+ await flushMicrotasks();
+ expect(sourceController.hasListener, isTrue);
+ expect(sourceController.isPaused, isTrue);
+ expect(events, []);
+ subscription.resume();
+ await flushMicrotasks();
+ expect(sourceController.hasListener, isTrue);
+ expect(sourceController.isPaused, isFalse);
+ expect(events, [1, 2, 3, 4]);
+ sourceController.close();
+ await done;
+ expect(events, [1, 2, 3, 4]);
+ });
+
+ test('pause more than once', () async {
+ var completer = StreamCompleter<void>();
+ var events = [];
+ var subscription = completer.stream.listen(events.add);
+ var done = subscription.asFuture();
+ subscription.pause();
+ subscription.pause();
+ subscription.pause();
+ completer.setSourceStream(createStream());
+ for (var i = 0; i < 3; i++) {
+ await flushMicrotasks();
+ expect(events, []);
+ subscription.resume();
+ }
+ await done;
+ expect(events, [1, 2, 3, 4]);
+ });
+
+ test('cancel new stream before source is done', () async {
+ var completer = StreamCompleter<int>();
+ var lastEvent = -1;
+ var controller = StreamController<int>();
+ late StreamSubscription subscription;
+ subscription = completer.stream.listen((value) {
+ expect(value, lessThan(3));
+ lastEvent = value;
+ if (value == 2) {
+ subscription.cancel();
+ }
+ },
+ onError: unreachable('error'),
+ onDone: unreachable('done'),
+ cancelOnError: true);
+ completer.setSourceStream(controller.stream);
+ expect(controller.hasListener, isTrue);
+
+ await flushMicrotasks();
+ expect(controller.hasListener, isTrue);
+ controller.add(1);
+
+ await flushMicrotasks();
+ expect(lastEvent, 1);
+ expect(controller.hasListener, isTrue);
+ controller.add(2);
+
+ await flushMicrotasks();
+ expect(lastEvent, 2);
+ expect(controller.hasListener, isFalse);
+ });
+
+ test('complete with setEmpty before listening', () async {
+ var completer = StreamCompleter<void>();
+ completer.setEmpty();
+ var done = Completer<void>();
+ completer.stream.listen(unreachable('data'),
+ onError: unreachable('error'), onDone: done.complete);
+ await done.future;
+ });
+
+ test('complete with setEmpty after listening', () async {
+ var completer = StreamCompleter<void>();
+ var done = Completer<void>();
+ completer.stream.listen(unreachable('data'),
+ onError: unreachable('error'), onDone: done.complete);
+ completer.setEmpty();
+ await done.future;
+ });
+
+ test("source stream isn't listened to until completer stream is", () async {
+ var completer = StreamCompleter<void>();
+ late StreamController controller;
+ controller = StreamController(onListen: () {
+ scheduleMicrotask(controller.close);
+ });
+
+ completer.setSourceStream(controller.stream);
+ await flushMicrotasks();
+ expect(controller.hasListener, isFalse);
+ var subscription = completer.stream.listen(null);
+ expect(controller.hasListener, isTrue);
+ await subscription.asFuture();
+ });
+
+ test('cancelOnError true when listening before linking stream', () async {
+ var completer = StreamCompleter<Object>();
+ Object lastEvent = -1;
+ var controller = StreamController<Object>();
+ completer.stream.listen((value) {
+ expect(value, lessThan(3));
+ lastEvent = value;
+ }, onError: (Object value) {
+ expect(value, '3');
+ lastEvent = value;
+ }, onDone: unreachable('done'), cancelOnError: true);
+ completer.setSourceStream(controller.stream);
+ expect(controller.hasListener, isTrue);
+
+ await flushMicrotasks();
+ expect(controller.hasListener, isTrue);
+ controller.add(1);
+
+ await flushMicrotasks();
+ expect(lastEvent, 1);
+ expect(controller.hasListener, isTrue);
+ controller.add(2);
+
+ await flushMicrotasks();
+ expect(lastEvent, 2);
+ expect(controller.hasListener, isTrue);
+ controller.addError('3');
+
+ await flushMicrotasks();
+ expect(lastEvent, '3');
+ expect(controller.hasListener, isFalse);
+ });
+
+ test('cancelOnError true when listening after linking stream', () async {
+ var completer = StreamCompleter<Object>();
+ Object lastEvent = -1;
+ var controller = StreamController<Object>();
+ completer.setSourceStream(controller.stream);
+ controller.add(1);
+ expect(controller.hasListener, isFalse);
+
+ completer.stream.listen((value) {
+ expect(value, lessThan(3));
+ lastEvent = value;
+ }, onError: (Object value) {
+ expect(value, '3');
+ lastEvent = value;
+ }, onDone: unreachable('done'), cancelOnError: true);
+
+ expect(controller.hasListener, isTrue);
+
+ await flushMicrotasks();
+ expect(lastEvent, 1);
+ expect(controller.hasListener, isTrue);
+ controller.add(2);
+
+ await flushMicrotasks();
+ expect(lastEvent, 2);
+ expect(controller.hasListener, isTrue);
+ controller.addError('3');
+
+ await flushMicrotasks();
+ expect(controller.hasListener, isFalse);
+ });
+
+ test('linking a stream after setSourceStream before listen', () async {
+ var completer = StreamCompleter<void>();
+ completer.setSourceStream(createStream());
+ expect(() => completer.setSourceStream(createStream()), throwsStateError);
+ expect(() => completer.setEmpty(), throwsStateError);
+ await completer.stream.toList();
+ // Still fails after source is done
+ expect(() => completer.setSourceStream(createStream()), throwsStateError);
+ expect(() => completer.setEmpty(), throwsStateError);
+ });
+
+ test('linking a stream after setSourceStream after listen', () async {
+ var completer = StreamCompleter<void>();
+ var list = completer.stream.toList();
+ completer.setSourceStream(createStream());
+ expect(() => completer.setSourceStream(createStream()), throwsStateError);
+ expect(() => completer.setEmpty(), throwsStateError);
+ await list;
+ // Still fails after source is done.
+ expect(() => completer.setSourceStream(createStream()), throwsStateError);
+ expect(() => completer.setEmpty(), throwsStateError);
+ });
+
+ test('linking a stream after setEmpty before listen', () async {
+ var completer = StreamCompleter<void>();
+ completer.setEmpty();
+ expect(() => completer.setSourceStream(createStream()), throwsStateError);
+ expect(() => completer.setEmpty(), throwsStateError);
+ await completer.stream.toList();
+ // Still fails after source is done
+ expect(() => completer.setSourceStream(createStream()), throwsStateError);
+ expect(() => completer.setEmpty(), throwsStateError);
+ });
+
+ test('linking a stream after setEmpty() after listen', () async {
+ var completer = StreamCompleter<void>();
+ var list = completer.stream.toList();
+ completer.setEmpty();
+ expect(() => completer.setSourceStream(createStream()), throwsStateError);
+ expect(() => completer.setEmpty(), throwsStateError);
+ await list;
+ // Still fails after source is done.
+ expect(() => completer.setSourceStream(createStream()), throwsStateError);
+ expect(() => completer.setEmpty(), throwsStateError);
+ });
+
+ test('listening more than once after setting stream', () async {
+ var completer = StreamCompleter<void>();
+ completer.setSourceStream(createStream());
+ var list = completer.stream.toList();
+ expect(() => completer.stream.toList(), throwsStateError);
+ await list;
+ expect(() => completer.stream.toList(), throwsStateError);
+ });
+
+ test('listening more than once before setting stream', () async {
+ var completer = StreamCompleter<void>();
+ completer.stream.toList();
+ expect(() => completer.stream.toList(), throwsStateError);
+ });
+
+ test('setting onData etc. before and after setting stream', () async {
+ var completer = StreamCompleter<int>();
+ var controller = StreamController<int>();
+ var subscription = completer.stream.listen(null);
+ Object lastEvent = 0;
+ subscription.onData((value) => lastEvent = value);
+ subscription.onError((Object value) => lastEvent = '$value');
+ subscription.onDone(() => lastEvent = -1);
+ completer.setSourceStream(controller.stream);
+ await flushMicrotasks();
+ controller.add(1);
+ await flushMicrotasks();
+ expect(lastEvent, 1);
+ controller.addError(2);
+ await flushMicrotasks();
+ expect(lastEvent, '2');
+ subscription.onData((value) => lastEvent = -value);
+ subscription.onError((Object value) => lastEvent = '${-(value as int)}');
+ controller.add(1);
+ await flushMicrotasks();
+ expect(lastEvent, -1);
+ controller.addError(2);
+ await flushMicrotasks();
+ expect(lastEvent, '-2');
+ controller.close();
+ await flushMicrotasks();
+ expect(lastEvent, -1);
+ });
+
+ test('pause w/ resume future accross setting stream', () async {
+ var completer = StreamCompleter<void>();
+ var resume = Completer<void>();
+ var subscription = completer.stream.listen(unreachable('data'));
+ subscription.pause(resume.future);
+ await flushMicrotasks();
+ completer.setSourceStream(createStream());
+ await flushMicrotasks();
+ resume.complete();
+ var events = [];
+ subscription.onData(events.add);
+ await subscription.asFuture();
+ expect(events, [1, 2, 3, 4]);
+ });
+
+ test('asFuture with error accross setting stream', () async {
+ var completer = StreamCompleter<void>();
+ var controller = StreamController<void>();
+ var subscription =
+ completer.stream.listen(unreachable('data'), cancelOnError: false);
+ var done = subscription.asFuture();
+ expect(controller.hasListener, isFalse);
+ completer.setSourceStream(controller.stream);
+ await flushMicrotasks();
+ expect(controller.hasListener, isTrue);
+ controller.addError(42);
+ await done.then(unreachable('data'), onError: (Object error) {
+ expect(error, 42);
+ });
+ expect(controller.hasListener, isFalse);
+ });
+
+ group('setError()', () {
+ test('produces a stream that emits a single error', () {
+ var completer = StreamCompleter<void>();
+ completer.stream.listen(unreachable('data'),
+ onError: expectAsync2((error, stackTrace) {
+ expect(error, equals('oh no'));
+ }), onDone: expectAsync0(() {}));
+
+ completer.setError('oh no');
+ });
+
+ test('produces a stream that emits a single error on a later listen',
+ () async {
+ var completer = StreamCompleter<void>();
+ completer.setError('oh no');
+ await flushMicrotasks();
+
+ completer.stream.listen(unreachable('data'),
+ onError: expectAsync2((error, stackTrace) {
+ expect(error, equals('oh no'));
+ }), onDone: expectAsync0(() {}));
+ });
+ });
+}
+
+Stream<int> createStream() async* {
+ yield 1;
+ await flushMicrotasks();
+ yield 2;
+ await flushMicrotasks();
+ yield 3;
+ await flushMicrotasks();
+ yield 4;
+}
diff --git a/pkgs/async/test/stream_extensions_test.dart b/pkgs/async/test/stream_extensions_test.dart
new file mode 100644
index 0000000..b43dedc
--- /dev/null
+++ b/pkgs/async/test/stream_extensions_test.dart
@@ -0,0 +1,185 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE filevents.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('.slices', () {
+ test('empty', () {
+ expect(const Stream.empty().slices(1).toList(), completion(equals([])));
+ });
+
+ test('with the same length as the iterable', () {
+ expect(
+ Stream.fromIterable([1, 2, 3]).slices(3).toList(),
+ completion(equals([
+ [1, 2, 3]
+ ])));
+ });
+
+ test('with a longer length than the iterable', () {
+ expect(
+ Stream.fromIterable([1, 2, 3]).slices(5).toList(),
+ completion(equals([
+ [1, 2, 3]
+ ])));
+ });
+
+ test('with a shorter length than the iterable', () {
+ expect(
+ Stream.fromIterable([1, 2, 3]).slices(2).toList(),
+ completion(equals([
+ [1, 2],
+ [3]
+ ])));
+ });
+
+ test('with length divisible by the iterable\'s', () {
+ expect(
+ Stream.fromIterable([1, 2, 3, 4]).slices(2).toList(),
+ completion(equals([
+ [1, 2],
+ [3, 4]
+ ])));
+ });
+
+ test('refuses negative length', () {
+ expect(() => Stream.fromIterable([1]).slices(-1), throwsRangeError);
+ });
+
+ test('refuses length 0', () {
+ expect(() => Stream.fromIterable([1]).slices(0), throwsRangeError);
+ });
+ });
+
+ group('.firstOrNull', () {
+ test('returns the first data event', () {
+ expect(
+ Stream.fromIterable([1, 2, 3, 4]).firstOrNull, completion(equals(1)));
+ });
+
+ test('returns the first error event', () {
+ expect(Stream.error('oh no').firstOrNull, throwsA('oh no'));
+ });
+
+ test('returns null for an empty stream', () {
+ expect(const Stream.empty().firstOrNull, completion(isNull));
+ });
+
+ test('cancels the subscription after an event', () async {
+ var isCancelled = false;
+ var controller = StreamController<int>(onCancel: () {
+ isCancelled = true;
+ });
+ controller.add(1);
+
+ await expectLater(controller.stream.firstOrNull, completion(equals(1)));
+ expect(isCancelled, isTrue);
+ });
+
+ test('cancels the subscription after an error', () async {
+ var isCancelled = false;
+ var controller = StreamController<int>(onCancel: () {
+ isCancelled = true;
+ });
+ controller.addError('oh no');
+
+ await expectLater(controller.stream.firstOrNull, throwsA('oh no'));
+ expect(isCancelled, isTrue);
+ });
+ });
+
+ group('.listenAndBuffer', () {
+ test('emits events added before the listenAndBuffer is listened', () async {
+ var controller = StreamController<int>()
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..close();
+ var stream = controller.stream.listenAndBuffer();
+ await pumpEventQueue();
+
+ expectLater(stream, emitsInOrder([1, 2, 3, emitsDone]));
+ });
+
+ test('emits events added after the listenAndBuffer is listened', () async {
+ var controller = StreamController<int>();
+ var stream = controller.stream.listenAndBuffer();
+ expectLater(stream, emitsInOrder([1, 2, 3, emitsDone]));
+ await pumpEventQueue();
+
+ controller
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..close();
+ });
+
+ test('emits events added before and after the listenAndBuffer is listened',
+ () async {
+ var controller = StreamController<int>()
+ ..add(1)
+ ..add(2)
+ ..add(3);
+ var stream = controller.stream.listenAndBuffer();
+ expectLater(stream, emitsInOrder([1, 2, 3, 4, 5, 6, emitsDone]));
+ await pumpEventQueue();
+
+ controller
+ ..add(4)
+ ..add(5)
+ ..add(6)
+ ..close();
+ });
+
+ test('listens as soon as listenAndBuffer() is called', () async {
+ var listened = false;
+ var controller = StreamController<int>(onListen: () {
+ listened = true;
+ });
+ controller.stream.listenAndBuffer();
+ expect(listened, isTrue);
+ });
+
+ test('forwards pause and resume', () async {
+ var controller = StreamController<int>();
+ var stream = controller.stream.listenAndBuffer();
+ expect(controller.isPaused, isFalse);
+ var subscription = stream.listen(null);
+ expect(controller.isPaused, isFalse);
+ subscription.pause();
+ expect(controller.isPaused, isTrue);
+ subscription.resume();
+ expect(controller.isPaused, isFalse);
+ });
+
+ test('forwards cancel', () async {
+ var completer = Completer<void>();
+ var canceled = false;
+ var controller = StreamController<int>(onCancel: () {
+ canceled = true;
+ return completer.future;
+ });
+ var stream = controller.stream.listenAndBuffer();
+ expect(canceled, isFalse);
+ var subscription = stream.listen(null);
+ expect(canceled, isFalse);
+
+ var cancelCompleted = false;
+ subscription.cancel().then((_) {
+ cancelCompleted = true;
+ });
+ expect(canceled, isTrue);
+ await pumpEventQueue();
+ expect(cancelCompleted, isFalse);
+
+ completer.complete();
+ await pumpEventQueue();
+ expect(cancelCompleted, isTrue);
+ });
+ });
+}
diff --git a/pkgs/async/test/stream_group_test.dart b/pkgs/async/test/stream_group_test.dart
new file mode 100644
index 0000000..3700120
--- /dev/null
+++ b/pkgs/async/test/stream_group_test.dart
@@ -0,0 +1,926 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('single-subscription', () {
+ late StreamGroup<String> streamGroup;
+ setUp(() {
+ streamGroup = StreamGroup<String>();
+ });
+
+ test('buffers events from multiple sources', () async {
+ var controller1 = StreamController<String>();
+ streamGroup.add(controller1.stream);
+ controller1.add('first');
+ controller1.close();
+
+ var controller2 = StreamController<String>();
+ streamGroup.add(controller2.stream);
+ controller2.add('second');
+ controller2.close();
+
+ await flushMicrotasks();
+
+ expect(streamGroup.close(), completes);
+
+ expect(streamGroup.stream.toList(),
+ completion(unorderedEquals(['first', 'second'])));
+ });
+
+ test('buffers errors from multiple sources', () async {
+ var controller1 = StreamController<String>();
+ streamGroup.add(controller1.stream);
+ controller1.addError('first');
+ controller1.close();
+
+ var controller2 = StreamController<String>();
+ streamGroup.add(controller2.stream);
+ controller2.addError('second');
+ controller2.close();
+
+ await flushMicrotasks();
+
+ expect(streamGroup.close(), completes);
+
+ var transformed = streamGroup.stream.transform(
+ StreamTransformer<String, String>.fromHandlers(
+ handleError: (error, _, sink) => sink.add('error: $error')));
+ expect(transformed.toList(),
+ completion(equals(['error: first', 'error: second'])));
+ });
+
+ test('buffers events and errors together', () async {
+ var controller = StreamController<String>();
+ streamGroup.add(controller.stream);
+
+ controller.add('first');
+ controller.addError('second');
+ controller.add('third');
+ controller.addError('fourth');
+ controller.addError('fifth');
+ controller.add('sixth');
+ controller.close();
+
+ await flushMicrotasks();
+
+ expect(streamGroup.close(), completes);
+
+ var transformed = streamGroup.stream.transform(
+ StreamTransformer<String, String>.fromHandlers(
+ handleData: (data, sink) => sink.add('data: $data'),
+ handleError: (error, _, sink) => sink.add('error: $error')));
+ expect(
+ transformed.toList(),
+ completion(equals([
+ 'data: first',
+ 'error: second',
+ 'data: third',
+ 'error: fourth',
+ 'error: fifth',
+ 'data: sixth'
+ ])));
+ });
+
+ test("emits events once there's a listener", () {
+ var controller = StreamController<String>();
+ streamGroup.add(controller.stream);
+
+ expect(
+ streamGroup.stream.toList(), completion(equals(['first', 'second'])));
+
+ controller.add('first');
+ controller.add('second');
+ controller.close();
+
+ expect(streamGroup.close(), completes);
+ });
+
+ test("doesn't buffer events from a broadcast stream", () async {
+ var controller = StreamController<String>.broadcast();
+ streamGroup.add(controller.stream);
+
+ controller.add('first');
+ controller.add('second');
+ controller.close();
+
+ await flushMicrotasks();
+
+ expect(streamGroup.close(), completes);
+ expect(streamGroup.stream.toList(), completion(isEmpty));
+ });
+
+ test('when paused, buffers events from a broadcast stream', () async {
+ var controller = StreamController<String>.broadcast();
+ streamGroup.add(controller.stream);
+
+ var events = [];
+ var subscription = streamGroup.stream.listen(events.add);
+ subscription.pause();
+
+ controller.add('first');
+ controller.add('second');
+ controller.close();
+ await flushMicrotasks();
+
+ subscription.resume();
+ expect(streamGroup.close(), completes);
+ await flushMicrotasks();
+
+ expect(events, equals(['first', 'second']));
+ });
+
+ test("emits events from a broadcast stream once there's a listener", () {
+ var controller = StreamController<String>.broadcast();
+ streamGroup.add(controller.stream);
+
+ expect(
+ streamGroup.stream.toList(), completion(equals(['first', 'second'])));
+
+ controller.add('first');
+ controller.add('second');
+ controller.close();
+
+ expect(streamGroup.close(), completes);
+ });
+
+ test('forwards cancel errors', () async {
+ var subscription = streamGroup.stream.listen(null);
+
+ var controller = StreamController<String>(onCancel: () => throw 'error');
+ streamGroup.add(controller.stream);
+ await flushMicrotasks();
+
+ expect(subscription.cancel(), throwsA('error'));
+ });
+
+ test('forwards a cancel future', () async {
+ var subscription = streamGroup.stream.listen(null);
+
+ var completer = Completer<void>();
+ var controller =
+ StreamController<String>(onCancel: () => completer.future);
+ streamGroup.add(controller.stream);
+ await flushMicrotasks();
+
+ var fired = false;
+ subscription.cancel().then((_) => fired = true);
+
+ await flushMicrotasks();
+ expect(fired, isFalse);
+
+ completer.complete();
+ await flushMicrotasks();
+ expect(fired, isTrue);
+ });
+
+ test(
+ 'add() while active pauses the stream if the group is paused, then '
+ 'resumes once the group resumes', () async {
+ var subscription = streamGroup.stream.listen(null);
+ await flushMicrotasks();
+
+ var paused = false;
+ var controller = StreamController<String>(
+ onPause: () => paused = true, onResume: () => paused = false);
+
+ subscription.pause();
+ await flushMicrotasks();
+
+ streamGroup.add(controller.stream);
+ await flushMicrotasks();
+ expect(paused, isTrue);
+
+ subscription.resume();
+ await flushMicrotasks();
+ expect(paused, isFalse);
+ });
+
+ group('add() while canceled', () {
+ setUp(() async {
+ streamGroup.stream.listen(null).cancel();
+ await flushMicrotasks();
+ });
+
+ test('immediately listens to and cancels the stream', () async {
+ var listened = false;
+ var canceled = false;
+ var controller = StreamController<String>(onListen: () {
+ listened = true;
+ }, onCancel: expectAsync0(() {
+ expect(listened, isTrue);
+ canceled = true;
+ }));
+
+ streamGroup.add(controller.stream);
+ await flushMicrotasks();
+ expect(listened, isTrue);
+ expect(canceled, isTrue);
+ });
+
+ test('forwards cancel errors', () {
+ var controller =
+ StreamController<String>(onCancel: () => throw 'error');
+
+ expect(streamGroup.add(controller.stream), throwsA('error'));
+ });
+
+ test('forwards a cancel future', () async {
+ var completer = Completer<void>();
+ var controller =
+ StreamController<String>(onCancel: () => completer.future);
+
+ var fired = false;
+ streamGroup.add(controller.stream)!.then((_) => fired = true);
+
+ await flushMicrotasks();
+ expect(fired, isFalse);
+
+ completer.complete();
+ await flushMicrotasks();
+ expect(fired, isTrue);
+ });
+ });
+
+ group('when listen() throws an error', () {
+ late Stream<String> alreadyListened;
+ setUp(() {
+ alreadyListened = Stream.value('foo')..listen(null);
+ });
+
+ group('listen()', () {
+ test('rethrows that error', () {
+ streamGroup.add(alreadyListened);
+
+ // We can't use expect(..., throwsStateError) here bceause of
+ // dart-lang/sdk#45815.
+ runZonedGuarded(
+ () => streamGroup.stream.listen(expectAsync1((_) {}, count: 0)),
+ expectAsync2((error, _) => expect(error, isStateError)));
+ });
+
+ test('cancels other subscriptions', () async {
+ var firstCancelled = false;
+ var first =
+ StreamController<String>(onCancel: () => firstCancelled = true);
+ streamGroup.add(first.stream);
+
+ streamGroup.add(alreadyListened);
+
+ var lastCancelled = false;
+ var last =
+ StreamController<String>(onCancel: () => lastCancelled = true);
+ streamGroup.add(last.stream);
+
+ runZonedGuarded(() => streamGroup.stream.listen(null), (_, __) {});
+
+ expect(firstCancelled, isTrue);
+ expect(lastCancelled, isTrue);
+ });
+
+ // There really shouldn't even be a subscription here, but due to
+ // dart-lang/sdk#45815 there is.
+ group('canceling the subscription is a no-op', () {
+ test('synchronously', () {
+ streamGroup.add(alreadyListened);
+
+ var subscription = runZonedGuarded(
+ () => streamGroup.stream.listen(null),
+ expectAsync2((_, __) {}, count: 1));
+
+ expect(subscription!.cancel(), completes);
+ });
+
+ test('asynchronously', () async {
+ streamGroup.add(alreadyListened);
+
+ var subscription = runZonedGuarded(
+ () => streamGroup.stream.listen(null),
+ expectAsync2((_, __) {}, count: 1));
+
+ await pumpEventQueue();
+ expect(subscription!.cancel(), completes);
+ });
+ });
+ });
+ });
+ });
+
+ group('broadcast', () {
+ late StreamGroup<String> streamGroup;
+ setUp(() {
+ streamGroup = StreamGroup<String>.broadcast();
+ });
+
+ test('buffers events from multiple sources', () async {
+ var controller1 = StreamController<String>();
+ streamGroup.add(controller1.stream);
+ controller1.add('first');
+ controller1.close();
+
+ var controller2 = StreamController<String>();
+ streamGroup.add(controller2.stream);
+ controller2.add('second');
+ controller2.close();
+
+ await flushMicrotasks();
+
+ expect(streamGroup.close(), completes);
+
+ expect(
+ streamGroup.stream.toList(), completion(equals(['first', 'second'])));
+ });
+
+ test("emits events from multiple sources once there's a listener", () {
+ var controller1 = StreamController<String>();
+ streamGroup.add(controller1.stream);
+
+ var controller2 = StreamController<String>();
+ streamGroup.add(controller2.stream);
+
+ expect(
+ streamGroup.stream.toList(), completion(equals(['first', 'second'])));
+
+ controller1.add('first');
+ controller2.add('second');
+ controller1.close();
+ controller2.close();
+
+ expect(streamGroup.close(), completes);
+ });
+
+ test("doesn't buffer events once a listener has been added and removed",
+ () async {
+ var controller = StreamController<String>();
+ streamGroup.add(controller.stream);
+
+ streamGroup.stream.listen(null).cancel();
+ await flushMicrotasks();
+
+ controller.add('first');
+ controller.addError('second');
+ controller.close();
+
+ await flushMicrotasks();
+
+ expect(streamGroup.close(), completes);
+ expect(streamGroup.stream.toList(), completion(isEmpty));
+ });
+
+ test("doesn't buffer events from a broadcast stream", () async {
+ var controller = StreamController<String>.broadcast();
+ streamGroup.add(controller.stream);
+ controller.add('first');
+ controller.addError('second');
+ controller.close();
+
+ await flushMicrotasks();
+
+ expect(streamGroup.close(), completes);
+ expect(streamGroup.stream.toList(), completion(isEmpty));
+ });
+
+ test("emits events from a broadcast stream once there's a listener", () {
+ var controller = StreamController<String>.broadcast();
+ streamGroup.add(controller.stream);
+
+ expect(
+ streamGroup.stream.toList(), completion(equals(['first', 'second'])));
+
+ controller.add('first');
+ controller.add('second');
+ controller.close();
+
+ expect(streamGroup.close(), completes);
+ });
+
+ test('cancels and re-listens broadcast streams', () async {
+ var subscription = streamGroup.stream.listen(null);
+
+ var controller = StreamController<String>.broadcast();
+
+ streamGroup.add(controller.stream);
+ await flushMicrotasks();
+ expect(controller.hasListener, isTrue);
+
+ subscription.cancel();
+ await flushMicrotasks();
+ expect(controller.hasListener, isFalse);
+
+ streamGroup.stream.listen(null);
+ await flushMicrotasks();
+ expect(controller.hasListener, isTrue);
+ });
+
+ test(
+ 'listens on streams that follow single-subscription streams when '
+ 'relistening after a cancel', () async {
+ var controller1 = StreamController<String>();
+ streamGroup.add(controller1.stream);
+ streamGroup.stream.listen(null).cancel();
+
+ var controller2 = StreamController<String>();
+ streamGroup.add(controller2.stream);
+
+ var emitted = <String>[];
+ streamGroup.stream.listen(emitted.add);
+ controller1.add('one');
+ controller2.add('two');
+ await flushMicrotasks();
+ expect(emitted, ['one', 'two']);
+ });
+
+ test('never cancels single-subscription streams', () async {
+ var subscription = streamGroup.stream.listen(null);
+
+ var controller =
+ StreamController<String>(onCancel: expectAsync0(() {}, count: 0));
+
+ streamGroup.add(controller.stream);
+ await flushMicrotasks();
+
+ subscription.cancel();
+ await flushMicrotasks();
+
+ streamGroup.stream.listen(null);
+ await flushMicrotasks();
+ });
+
+ test('drops events from a single-subscription stream while dormant',
+ () async {
+ var events = [];
+ var subscription = streamGroup.stream.listen(events.add);
+
+ var controller = StreamController<String>();
+ streamGroup.add(controller.stream);
+ await flushMicrotasks();
+
+ controller.add('first');
+ await flushMicrotasks();
+ expect(events, equals(['first']));
+
+ subscription.cancel();
+ controller.add('second');
+ await flushMicrotasks();
+ expect(events, equals(['first']));
+
+ streamGroup.stream.listen(events.add);
+ controller.add('third');
+ await flushMicrotasks();
+ expect(events, equals(['first', 'third']));
+ });
+
+ test('a single-subscription stream can be removed while dormant', () async {
+ var controller = StreamController<String>();
+ streamGroup.add(controller.stream);
+ await flushMicrotasks();
+
+ streamGroup.stream.listen(null).cancel();
+ await flushMicrotasks();
+
+ streamGroup.remove(controller.stream);
+ expect(controller.hasListener, isFalse);
+ await flushMicrotasks();
+
+ expect(streamGroup.stream.toList(), completion(isEmpty));
+ controller.add('first');
+ expect(streamGroup.close(), completes);
+ });
+ });
+
+ group('regardless of type', () {
+ group('single-subscription', () {
+ regardlessOfType(StreamGroup<String>.new);
+ });
+
+ group('broadcast', () {
+ regardlessOfType(StreamGroup<String>.broadcast);
+ });
+ });
+
+ test('merge() emits events from all components streams', () async {
+ var controller1 = StreamController<String>();
+ var controller2 = StreamController<String>();
+
+ var merged = StreamGroup.merge([controller1.stream, controller2.stream]);
+
+ controller1.add('first');
+ controller1.close();
+ controller2.add('second');
+ controller2.close();
+
+ expect(await merged.toList(), ['first', 'second']);
+ });
+
+ test('mergeBroadcast() emits events from all components streams', () async {
+ var controller1 = StreamController<String>();
+ var controller2 = StreamController<String>();
+
+ var merged =
+ StreamGroup.mergeBroadcast([controller1.stream, controller2.stream]);
+
+ controller1.add('first');
+ controller1.close();
+ controller2.add('second');
+ controller2.close();
+
+ expect(merged.isBroadcast, isTrue);
+
+ expect(await merged.toList(), ['first', 'second']);
+ });
+}
+
+void regardlessOfType(StreamGroup<String> Function() newStreamGroup) {
+ late StreamGroup<String> streamGroup;
+ setUp(() {
+ streamGroup = newStreamGroup();
+ });
+
+ group('add()', () {
+ group('while dormant', () {
+ test("doesn't listen to the stream until the group is listened to",
+ () async {
+ var controller = StreamController<String>();
+
+ expect(streamGroup.add(controller.stream), isNull);
+ await flushMicrotasks();
+ expect(controller.hasListener, isFalse);
+
+ streamGroup.stream.listen(null);
+ await flushMicrotasks();
+ expect(controller.hasListener, isTrue);
+ });
+
+ test('is a no-op if the stream is already in the group', () {
+ var controller = StreamController<String>();
+ streamGroup.add(controller.stream);
+ streamGroup.add(controller.stream);
+ streamGroup.add(controller.stream);
+
+ // If the stream was actually listened to multiple times, this would
+ // throw a StateError.
+ streamGroup.stream.listen(null);
+ });
+ });
+
+ group('while active', () {
+ setUp(() async {
+ streamGroup.stream.listen(null);
+ await flushMicrotasks();
+ });
+
+ test('listens to the stream immediately', () async {
+ var controller = StreamController<String>();
+
+ expect(streamGroup.add(controller.stream), isNull);
+ await flushMicrotasks();
+ expect(controller.hasListener, isTrue);
+ });
+
+ test('is a no-op if the stream is already in the group', () async {
+ var controller = StreamController<String>();
+
+ // If the stream were actually listened to more than once, future
+ // calls to [add] would throw [StateError]s.
+ streamGroup.add(controller.stream);
+ streamGroup.add(controller.stream);
+ streamGroup.add(controller.stream);
+ });
+ });
+ });
+
+ group('remove()', () {
+ group('while dormant', () {
+ test("stops emitting events for a stream that's removed", () async {
+ var controller = StreamController<String>();
+ streamGroup.add(controller.stream);
+
+ expect(streamGroup.stream.toList(), completion(equals(['first'])));
+
+ controller.add('first');
+ await flushMicrotasks();
+ controller.add('second');
+
+ expect(streamGroup.remove(controller.stream), completion(null));
+ expect(streamGroup.close(), completes);
+ });
+
+ test('is a no-op for an unknown stream', () {
+ var controller = StreamController<String>();
+ expect(streamGroup.remove(controller.stream), isNull);
+ });
+
+ test('and closed closes the group when the last stream is removed',
+ () async {
+ var controller1 = StreamController<String>();
+ var controller2 = StreamController<String>();
+
+ streamGroup.add(controller1.stream);
+ streamGroup.add(controller2.stream);
+ await flushMicrotasks();
+
+ expect(streamGroup.isClosed, isFalse);
+ streamGroup.close();
+ expect(streamGroup.isClosed, isTrue);
+
+ streamGroup.remove(controller1.stream);
+ await flushMicrotasks();
+
+ streamGroup.remove(controller2.stream);
+ await flushMicrotasks();
+
+ expect(streamGroup.stream.toList(), completion(isEmpty));
+ });
+ });
+
+ group('while listening', () {
+ test("doesn't emit events from a removed stream", () {
+ var controller = StreamController<String>();
+ streamGroup.add(controller.stream);
+
+ // The subscription to [controller.stream] is canceled synchronously, so
+ // the first event is dropped even though it was added before the
+ // removal. This is documented in [StreamGroup.remove].
+ expect(streamGroup.stream.toList(), completion(isEmpty));
+
+ controller.add('first');
+ expect(streamGroup.remove(controller.stream), completion(null));
+ controller.add('second');
+
+ expect(streamGroup.close(), completes);
+ });
+
+ test("cancels the stream's subscription", () async {
+ var controller = StreamController<String>();
+ streamGroup.add(controller.stream);
+
+ streamGroup.stream.listen(null);
+ await flushMicrotasks();
+ expect(controller.hasListener, isTrue);
+
+ streamGroup.remove(controller.stream);
+ await flushMicrotasks();
+ expect(controller.hasListener, isFalse);
+ });
+
+ test('forwards cancel errors', () async {
+ var controller =
+ StreamController<String>(onCancel: () => throw 'error');
+ streamGroup.add(controller.stream);
+
+ streamGroup.stream.listen(null);
+ await flushMicrotasks();
+
+ expect(streamGroup.remove(controller.stream), throwsA('error'));
+ });
+
+ test('forwards cancel futures', () async {
+ var completer = Completer<void>();
+ var controller =
+ StreamController<String>(onCancel: () => completer.future);
+
+ streamGroup.stream.listen(null);
+ await flushMicrotasks();
+
+ streamGroup.add(controller.stream);
+ await flushMicrotasks();
+
+ var fired = false;
+ streamGroup.remove(controller.stream)!.then((_) => fired = true);
+
+ await flushMicrotasks();
+ expect(fired, isFalse);
+
+ completer.complete();
+ await flushMicrotasks();
+ expect(fired, isTrue);
+ });
+
+ test('is a no-op for an unknown stream', () async {
+ var controller = StreamController<String>();
+ streamGroup.stream.listen(null);
+ await flushMicrotasks();
+
+ expect(streamGroup.remove(controller.stream), isNull);
+ });
+
+ test('and closed closes the group when the last stream is removed',
+ () async {
+ var done = false;
+ streamGroup.stream.listen(null, onDone: () => done = true);
+ await flushMicrotasks();
+
+ var controller1 = StreamController<String>();
+ var controller2 = StreamController<String>();
+
+ streamGroup.add(controller1.stream);
+ streamGroup.add(controller2.stream);
+ await flushMicrotasks();
+
+ streamGroup.close();
+
+ streamGroup.remove(controller1.stream);
+ await flushMicrotasks();
+ expect(done, isFalse);
+
+ streamGroup.remove(controller2.stream);
+ await flushMicrotasks();
+ expect(done, isTrue);
+ });
+ });
+ });
+
+ group('close()', () {
+ group('while dormant', () {
+ test('if there are no streams, closes the group', () {
+ expect(streamGroup.close(), completes);
+ expect(streamGroup.stream.toList(), completion(isEmpty));
+ });
+
+ test(
+ 'if there are streams, closes the group once those streams close '
+ "and there's a listener", () async {
+ var controller1 = StreamController<String>();
+ var controller2 = StreamController<String>();
+
+ streamGroup.add(controller1.stream);
+ streamGroup.add(controller2.stream);
+ await flushMicrotasks();
+
+ streamGroup.close();
+
+ controller1.close();
+ controller2.close();
+ expect(streamGroup.stream.toList(), completion(isEmpty));
+ });
+ });
+
+ group('while active', () {
+ test('if there are no streams, closes the group', () {
+ expect(streamGroup.stream.toList(), completion(isEmpty));
+ expect(streamGroup.close(), completes);
+ });
+
+ test('if there are streams, closes the group once those streams close',
+ () async {
+ var done = false;
+ streamGroup.stream.listen(null, onDone: () => done = true);
+ await flushMicrotasks();
+
+ var controller1 = StreamController<String>();
+ var controller2 = StreamController<String>();
+
+ streamGroup.add(controller1.stream);
+ streamGroup.add(controller2.stream);
+ await flushMicrotasks();
+
+ streamGroup.close();
+ await flushMicrotasks();
+ expect(done, isFalse);
+
+ controller1.close();
+ await flushMicrotasks();
+ expect(done, isFalse);
+
+ controller2.close();
+ await flushMicrotasks();
+ expect(done, isTrue);
+ });
+ });
+
+ test('returns a Future that completes once all events are dispatched',
+ () async {
+ var events = [];
+ streamGroup.stream.listen(events.add);
+
+ var controller = StreamController<String>();
+ streamGroup.add(controller.stream);
+ await flushMicrotasks();
+
+ // Add a bunch of events. Each of these will get dispatched in a
+ // separate microtask, so we can test that [close] only completes once
+ // all of them have dispatched.
+ controller.add('one');
+ controller.add('two');
+ controller.add('three');
+ controller.add('four');
+ controller.add('five');
+ controller.add('six');
+ controller.close();
+
+ await streamGroup.close();
+ expect(events, equals(['one', 'two', 'three', 'four', 'five', 'six']));
+ });
+ });
+
+ group('onIdle', () {
+ test('emits an event when the last pending stream emits done', () async {
+ streamGroup.stream.listen(null);
+
+ var idle = false;
+ streamGroup.onIdle.listen((_) => idle = true);
+
+ var controller1 = StreamController<String>();
+ var controller2 = StreamController<String>();
+ var controller3 = StreamController<String>();
+
+ streamGroup.add(controller1.stream);
+ streamGroup.add(controller2.stream);
+ streamGroup.add(controller3.stream);
+
+ await flushMicrotasks();
+ expect(idle, isFalse);
+ expect(streamGroup.isIdle, isFalse);
+
+ controller1.close();
+ await flushMicrotasks();
+ expect(idle, isFalse);
+ expect(streamGroup.isIdle, isFalse);
+
+ controller2.close();
+ await flushMicrotasks();
+ expect(idle, isFalse);
+ expect(streamGroup.isIdle, isFalse);
+
+ controller3.close();
+ await flushMicrotasks();
+ expect(idle, isTrue);
+ expect(streamGroup.isIdle, isTrue);
+ });
+
+ test('emits an event each time it becomes idle', () async {
+ streamGroup.stream.listen(null);
+
+ var idle = false;
+ streamGroup.onIdle.listen((_) => idle = true);
+
+ var controller = StreamController<String>();
+ streamGroup.add(controller.stream);
+
+ controller.close();
+ await flushMicrotasks();
+ expect(idle, isTrue);
+ expect(streamGroup.isIdle, isTrue);
+
+ idle = false;
+ controller = StreamController<String>();
+ streamGroup.add(controller.stream);
+
+ await flushMicrotasks();
+ expect(idle, isFalse);
+ expect(streamGroup.isIdle, isFalse);
+
+ controller.close();
+ await flushMicrotasks();
+ expect(idle, isTrue);
+ expect(streamGroup.isIdle, isTrue);
+ });
+
+ test('emits an event when the group closes', () async {
+ // It's important that the order of events here stays consistent over
+ // time, since code may rely on it in subtle ways. Note that this is *not*
+ // an official guarantee, so the authors of `async` are free to change
+ // this behavior if they need to.
+ var idle = false;
+ var onIdleDone = false;
+ var streamClosed = false;
+
+ streamGroup.onIdle.listen(expectAsync1((_) {
+ expect(streamClosed, isFalse);
+ idle = true;
+ }), onDone: expectAsync0(() {
+ expect(idle, isTrue);
+ expect(streamClosed, isTrue);
+ onIdleDone = true;
+ }));
+
+ streamGroup.stream.drain().then(expectAsync1((_) {
+ expect(idle, isTrue);
+ expect(onIdleDone, isFalse);
+ streamClosed = true;
+ }));
+
+ var controller = StreamController<String>();
+ streamGroup.add(controller.stream);
+ streamGroup.close();
+
+ await flushMicrotasks();
+ expect(idle, isFalse);
+ expect(streamGroup.isIdle, isFalse);
+
+ controller.close();
+ await flushMicrotasks();
+ expect(idle, isTrue);
+ expect(streamGroup.isIdle, isTrue);
+ expect(streamClosed, isTrue);
+ });
+ });
+}
+
+/// Wait for all microtasks to complete.
+Future flushMicrotasks() => Future<void>.delayed(Duration.zero);
diff --git a/pkgs/async/test/stream_queue_test.dart b/pkgs/async/test/stream_queue_test.dart
new file mode 100644
index 0000000..cd4433a
--- /dev/null
+++ b/pkgs/async/test/stream_queue_test.dart
@@ -0,0 +1,1214 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE filevents.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('source stream', () {
+ test('is listened to on first request, paused between requests', () async {
+ var controller = StreamController<int>();
+ var events = StreamQueue<int>(controller.stream);
+ await flushMicrotasks();
+ expect(controller.hasListener, isFalse);
+
+ var next = events.next;
+ expect(controller.hasListener, isTrue);
+ expect(controller.isPaused, isFalse);
+
+ controller.add(1);
+
+ expect(await next, 1);
+ expect(controller.hasListener, isTrue);
+ expect(controller.isPaused, isTrue);
+
+ next = events.next;
+ expect(controller.hasListener, isTrue);
+ expect(controller.isPaused, isFalse);
+
+ controller.add(2);
+
+ expect(await next, 2);
+ expect(controller.hasListener, isTrue);
+ expect(controller.isPaused, isTrue);
+
+ events.cancel();
+ expect(controller.hasListener, isFalse);
+ });
+ });
+
+ group('eventsDispatched', () {
+ test('increments after a next future completes', () async {
+ var events = StreamQueue<int>(createStream());
+
+ expect(events.eventsDispatched, equals(0));
+ await flushMicrotasks();
+ expect(events.eventsDispatched, equals(0));
+
+ var next = events.next;
+ expect(events.eventsDispatched, equals(0));
+
+ await next;
+ expect(events.eventsDispatched, equals(1));
+
+ await events.next;
+ expect(events.eventsDispatched, equals(2));
+ });
+
+ test('increments multiple times for multi-value requests', () async {
+ var events = StreamQueue<int>(createStream());
+ await events.take(3);
+ expect(events.eventsDispatched, equals(3));
+ });
+
+ test('increments multiple times for an accepted transaction', () async {
+ var events = StreamQueue<int>(createStream());
+ await events.withTransaction((queue) async {
+ await queue.next;
+ await queue.next;
+ return true;
+ });
+ expect(events.eventsDispatched, equals(2));
+ });
+
+ test("doesn't increment for rest requests", () async {
+ var events = StreamQueue<int>(createStream());
+ await events.rest.toList();
+ expect(events.eventsDispatched, equals(0));
+ });
+ });
+
+ group('lookAhead operation', () {
+ test('as simple list of events', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.lookAhead(4), [1, 2, 3, 4]);
+ expect(await events.next, 1);
+ expect(await events.lookAhead(2), [2, 3]);
+ expect(await events.take(2), [2, 3]);
+ expect(await events.next, 4);
+ await events.cancel();
+ });
+
+ test('of 0 events', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(events.lookAhead(0), completion([]));
+ expect(events.next, completion(1));
+ expect(events.lookAhead(0), completion([]));
+ expect(events.next, completion(2));
+ expect(events.lookAhead(0), completion([]));
+ expect(events.next, completion(3));
+ expect(events.lookAhead(0), completion([]));
+ expect(events.next, completion(4));
+ expect(events.lookAhead(0), completion([]));
+ expect(events.lookAhead(5), completion([]));
+ expect(events.next, throwsStateError);
+ await events.cancel();
+ });
+
+ test('with bad arguments throws', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(() => events.lookAhead(-1), throwsArgumentError);
+ expect(await events.next, 1); // Did not consume event.
+ expect(() => events.lookAhead(-1), throwsArgumentError);
+ expect(await events.next, 2); // Did not consume event.
+ await events.cancel();
+ });
+
+ test('of too many arguments', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.lookAhead(6), [1, 2, 3, 4]);
+ await events.cancel();
+ });
+
+ test('too large later', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.next, 2);
+ expect(await events.lookAhead(6), [3, 4]);
+ await events.cancel();
+ });
+
+ test('error', () async {
+ var events = StreamQueue<int>(createErrorStream());
+ expect(events.lookAhead(4), throwsA('To err is divine!'));
+ expect(events.take(4), throwsA('To err is divine!'));
+ expect(await events.next, 4);
+ await events.cancel();
+ });
+ });
+
+ group('next operation', () {
+ test('simple sequence of requests', () async {
+ var events = StreamQueue<int>(createStream());
+ for (var i = 1; i <= 4; i++) {
+ expect(await events.next, i);
+ }
+ expect(events.next, throwsStateError);
+ });
+
+ test('multiple requests at the same time', () async {
+ var events = StreamQueue<int>(createStream());
+ var result = await Future.wait(
+ [events.next, events.next, events.next, events.next]);
+ expect(result, [1, 2, 3, 4]);
+ await events.cancel();
+ });
+
+ test('sequence of requests with error', () async {
+ var events = StreamQueue<int>(createErrorStream());
+ expect(await events.next, 1);
+ expect(await events.next, 2);
+ expect(events.next, throwsA('To err is divine!'));
+ expect(await events.next, 4);
+ await events.cancel();
+ });
+ });
+
+ group('skip operation', () {
+ test('of two elements in the middle of sequence', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.skip(2), 0);
+ expect(await events.next, 4);
+ await events.cancel();
+ });
+
+ test('with negative/bad arguments throws', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(() => events.skip(-1), throwsArgumentError);
+ // A non-int throws either a type error or an argument error,
+ // depending on whether it's checked mode or not.
+ expect(await events.next, 1); // Did not consume event.
+ expect(() => events.skip(-1), throwsArgumentError);
+ expect(await events.next, 2); // Did not consume event.
+ await events.cancel();
+ });
+
+ test('of 0 elements works', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(events.skip(0), completion(0));
+ expect(events.next, completion(1));
+ expect(events.skip(0), completion(0));
+ expect(events.next, completion(2));
+ expect(events.skip(0), completion(0));
+ expect(events.next, completion(3));
+ expect(events.skip(0), completion(0));
+ expect(events.next, completion(4));
+ expect(events.skip(0), completion(0));
+ expect(events.skip(5), completion(5));
+ expect(events.next, throwsStateError);
+ await events.cancel();
+ });
+
+ test('of too many events ends at stream start', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.skip(6), 2);
+ await events.cancel();
+ });
+
+ test('of too many events after some events', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.next, 2);
+ expect(await events.skip(6), 4);
+ await events.cancel();
+ });
+
+ test('of too many events ends at stream end', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.next, 2);
+ expect(await events.next, 3);
+ expect(await events.next, 4);
+ expect(await events.skip(2), 2);
+ await events.cancel();
+ });
+
+ test('of events with error', () async {
+ var events = StreamQueue<int>(createErrorStream());
+ expect(events.skip(4), throwsA('To err is divine!'));
+ expect(await events.next, 4);
+ await events.cancel();
+ });
+
+ test('of events with error, and skip again after', () async {
+ var events = StreamQueue<int>(createErrorStream());
+ expect(events.skip(4), throwsA('To err is divine!'));
+ expect(events.skip(2), completion(1));
+ await events.cancel();
+ });
+ test('multiple skips at same time complete in order.', () async {
+ var events = StreamQueue<int>(createStream());
+ var skip1 = events.skip(1);
+ var skip2 = events.skip(0);
+ var skip3 = events.skip(4);
+ var skip4 = events.skip(1);
+ var index = 0;
+ // Check that futures complete in order.
+ Func1Required<int?> sequence(int expectedValue, int sequenceIndex) =>
+ (value) {
+ expect(value, expectedValue);
+ expect(index, sequenceIndex);
+ index++;
+ return null;
+ };
+ await Future.wait([
+ skip1.then(sequence(0, 0)),
+ skip2.then(sequence(0, 1)),
+ skip3.then(sequence(1, 2)),
+ skip4.then(sequence(1, 3))
+ ]);
+ await events.cancel();
+ });
+ });
+
+ group('take operation', () {
+ test('as simple take of events', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.take(2), [2, 3]);
+ expect(await events.next, 4);
+ await events.cancel();
+ });
+
+ test('of 0 events', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(events.take(0), completion([]));
+ expect(events.next, completion(1));
+ expect(events.take(0), completion([]));
+ expect(events.next, completion(2));
+ expect(events.take(0), completion([]));
+ expect(events.next, completion(3));
+ expect(events.take(0), completion([]));
+ expect(events.next, completion(4));
+ expect(events.take(0), completion([]));
+ expect(events.take(5), completion([]));
+ expect(events.next, throwsStateError);
+ await events.cancel();
+ });
+
+ test('with bad arguments throws', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(() => events.take(-1), throwsArgumentError);
+ expect(await events.next, 1); // Did not consume event.
+ expect(() => events.take(-1), throwsArgumentError);
+ expect(await events.next, 2); // Did not consume event.
+ await events.cancel();
+ });
+
+ test('of too many arguments', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.take(6), [1, 2, 3, 4]);
+ await events.cancel();
+ });
+
+ test('too large later', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.next, 2);
+ expect(await events.take(6), [3, 4]);
+ await events.cancel();
+ });
+
+ test('error', () async {
+ var events = StreamQueue<int>(createErrorStream());
+ expect(events.take(4), throwsA('To err is divine!'));
+ expect(await events.next, 4);
+ await events.cancel();
+ });
+ });
+
+ group('rest operation', () {
+ test('after single next', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.rest.toList(), [2, 3, 4]);
+ });
+
+ test('at start', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.rest.toList(), [1, 2, 3, 4]);
+ });
+
+ test('at end', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.next, 2);
+ expect(await events.next, 3);
+ expect(await events.next, 4);
+ expect(await events.rest.toList(), isEmpty);
+ });
+
+ test('after end', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.next, 2);
+ expect(await events.next, 3);
+ expect(await events.next, 4);
+ expect(events.next, throwsStateError);
+ expect(await events.rest.toList(), isEmpty);
+ });
+
+ test('after receiving done requested before', () async {
+ var events = StreamQueue<int>(createStream());
+ var next1 = events.next;
+ var next2 = events.next;
+ var next3 = events.next;
+ var rest = events.rest;
+ for (var i = 0; i < 10; i++) {
+ await flushMicrotasks();
+ }
+ expect(await next1, 1);
+ expect(await next2, 2);
+ expect(await next3, 3);
+ expect(await rest.toList(), [4]);
+ });
+
+ test('with an error event error', () async {
+ var events = StreamQueue<int>(createErrorStream());
+ expect(await events.next, 1);
+ var rest = events.rest;
+ var events2 = StreamQueue(rest);
+ expect(await events2.next, 2);
+ expect(events2.next, throwsA('To err is divine!'));
+ expect(await events2.next, 4);
+ });
+
+ test('closes the events, prevents other operations', () async {
+ var events = StreamQueue<int>(createStream());
+ var stream = events.rest;
+ expect(() => events.next, throwsStateError);
+ expect(() => events.skip(1), throwsStateError);
+ expect(() => events.take(1), throwsStateError);
+ expect(() => events.rest, throwsStateError);
+ expect(() => events.cancel(), throwsStateError);
+ expect(stream.toList(), completion([1, 2, 3, 4]));
+ });
+
+ test('forwards to underlying stream', () async {
+ var cancel = Completer<int>();
+ var controller = StreamController<int>(onCancel: () => cancel.future);
+ var events = StreamQueue<int>(controller.stream);
+ expect(controller.hasListener, isFalse);
+ var next = events.next;
+ expect(controller.hasListener, isTrue);
+ expect(controller.isPaused, isFalse);
+
+ controller.add(1);
+ expect(await next, 1);
+ expect(controller.isPaused, isTrue);
+
+ var rest = events.rest;
+ var subscription = rest.listen(null);
+ expect(controller.hasListener, isTrue);
+ expect(controller.isPaused, isFalse);
+
+ dynamic lastEvent;
+ subscription.onData((value) => lastEvent = value);
+
+ controller.add(2);
+
+ await flushMicrotasks();
+ expect(lastEvent, 2);
+ expect(controller.hasListener, isTrue);
+ expect(controller.isPaused, isFalse);
+
+ subscription.pause();
+ expect(controller.isPaused, isTrue);
+
+ controller.add(3);
+
+ await flushMicrotasks();
+ expect(lastEvent, 2);
+ subscription.resume();
+
+ await flushMicrotasks();
+ expect(lastEvent, 3);
+
+ var cancelFuture = subscription.cancel();
+ expect(controller.hasListener, isFalse);
+ cancel.complete(42);
+ expect(cancelFuture, completion(42));
+ });
+ });
+
+ group('peek operation', () {
+ test('peeks one event', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.peek, 1);
+ expect(await events.next, 1);
+ expect(await events.peek, 2);
+ expect(await events.take(2), [2, 3]);
+ expect(await events.peek, 4);
+ expect(await events.next, 4);
+ // Throws at end.
+ expect(events.peek, throwsA(anything));
+ await events.cancel();
+ });
+ test('multiple requests at the same time', () async {
+ var events = StreamQueue<int>(createStream());
+ var result = await Future.wait(
+ [events.peek, events.peek, events.next, events.peek, events.peek]);
+ expect(result, [1, 1, 1, 2, 2]);
+ await events.cancel();
+ });
+ test('sequence of requests with error', () async {
+ var events = StreamQueue<int>(createErrorStream());
+ expect(await events.next, 1);
+ expect(await events.next, 2);
+ expect(events.peek, throwsA('To err is divine!'));
+ // Error stays in queue.
+ expect(events.peek, throwsA('To err is divine!'));
+ expect(events.next, throwsA('To err is divine!'));
+ expect(await events.next, 4);
+ await events.cancel();
+ });
+ });
+
+ group('cancel operation', () {
+ test('closes the events, prevents any other operation', () async {
+ var events = StreamQueue<int>(createStream());
+ await events.cancel();
+ expect(() => events.lookAhead(1), throwsStateError);
+ expect(() => events.next, throwsStateError);
+ expect(() => events.peek, throwsStateError);
+ expect(() => events.skip(1), throwsStateError);
+ expect(() => events.take(1), throwsStateError);
+ expect(() => events.rest, throwsStateError);
+ expect(() => events.cancel(), throwsStateError);
+ });
+
+ test('cancels underlying subscription when called before any event',
+ () async {
+ var cancelFuture = Future.value(42);
+ var controller = StreamController<int>(onCancel: () => cancelFuture);
+ var events = StreamQueue<int>(controller.stream);
+ expect(await events.cancel(), 42);
+ });
+
+ test('cancels underlying subscription, returns result', () async {
+ var cancelFuture = Future.value(42);
+ var controller = StreamController<int>(onCancel: () => cancelFuture);
+ var events = StreamQueue<int>(controller.stream);
+ controller.add(1);
+ expect(await events.next, 1);
+ expect(await events.cancel(), 42);
+ });
+
+ group('with immediate: true', () {
+ test('closes the events, prevents any other operation', () async {
+ var events = StreamQueue<int>(createStream());
+ await events.cancel(immediate: true);
+ expect(() => events.next, throwsStateError);
+ expect(() => events.skip(1), throwsStateError);
+ expect(() => events.take(1), throwsStateError);
+ expect(() => events.rest, throwsStateError);
+ expect(() => events.cancel(), throwsStateError);
+ });
+
+ test('cancels the underlying subscription immediately', () async {
+ var controller = StreamController<int>();
+ controller.add(1);
+
+ var events = StreamQueue<int>(controller.stream);
+ expect(await events.next, 1);
+ expect(controller.hasListener, isTrue);
+
+ await events.cancel(immediate: true);
+ expect(controller.hasListener, isFalse);
+ });
+
+ test('cancels the underlying subscription when called before any event',
+ () async {
+ var cancelFuture = Future.value(42);
+ var controller = StreamController<int>(onCancel: () => cancelFuture);
+
+ var events = StreamQueue<int>(controller.stream);
+ expect(await events.cancel(immediate: true), 42);
+ });
+
+ test('closes pending requests', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(events.next, throwsStateError);
+ expect(events.hasNext, completion(isFalse));
+
+ await events.cancel(immediate: true);
+ });
+
+ test('returns the result of closing the underlying subscription',
+ () async {
+ var controller =
+ StreamController<int>(onCancel: () => Future<int>.value(42));
+ var events = StreamQueue<int>(controller.stream);
+ expect(await events.cancel(immediate: true), 42);
+ });
+
+ test("listens and then cancels a stream that hasn't been listened to yet",
+ () async {
+ var wasListened = false;
+ var controller =
+ StreamController<int>(onListen: () => wasListened = true);
+ var events = StreamQueue<int>(controller.stream);
+ expect(wasListened, isFalse);
+ expect(controller.hasListener, isFalse);
+
+ await events.cancel(immediate: true);
+ expect(wasListened, isTrue);
+ expect(controller.hasListener, isFalse);
+ });
+ });
+ });
+
+ group('hasNext operation', () {
+ test('true at start', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.hasNext, isTrue);
+ });
+
+ test('true after start', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.hasNext, isTrue);
+ });
+
+ test('true at end', () async {
+ var events = StreamQueue<int>(createStream());
+ for (var i = 1; i <= 4; i++) {
+ expect(await events.next, i);
+ }
+ expect(await events.hasNext, isFalse);
+ });
+
+ test('true when enqueued', () async {
+ var events = StreamQueue<int>(createStream());
+ var values = <int>[];
+ for (var i = 1; i <= 3; i++) {
+ events.next.then(values.add);
+ }
+ expect(values, isEmpty);
+ expect(await events.hasNext, isTrue);
+ expect(values, [1, 2, 3]);
+ });
+
+ test('false when enqueued', () async {
+ var events = StreamQueue<int>(createStream());
+ var values = <int>[];
+ for (var i = 1; i <= 4; i++) {
+ events.next.then(values.add);
+ }
+ expect(values, isEmpty);
+ expect(await events.hasNext, isFalse);
+ expect(values, [1, 2, 3, 4]);
+ });
+
+ test('true when data event', () async {
+ var controller = StreamController<int>();
+ var events = StreamQueue<int>(controller.stream);
+
+ bool? hasNext;
+ events.hasNext.then((result) {
+ hasNext = result;
+ });
+ await flushMicrotasks();
+ expect(hasNext, isNull);
+ controller.add(42);
+ expect(hasNext, isNull);
+ await flushMicrotasks();
+ expect(hasNext, isTrue);
+ });
+
+ test('true when error event', () async {
+ var controller = StreamController<int>();
+ var events = StreamQueue<int>(controller.stream);
+
+ bool? hasNext;
+ events.hasNext.then((result) {
+ hasNext = result;
+ });
+ await flushMicrotasks();
+ expect(hasNext, isNull);
+ controller.addError('BAD');
+ expect(hasNext, isNull);
+ await flushMicrotasks();
+ expect(hasNext, isTrue);
+ expect(events.next, throwsA('BAD'));
+ });
+
+ test('- hasNext after hasNext', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.hasNext, true);
+ expect(await events.hasNext, true);
+ expect(await events.next, 1);
+ expect(await events.hasNext, true);
+ expect(await events.hasNext, true);
+ expect(await events.next, 2);
+ expect(await events.hasNext, true);
+ expect(await events.hasNext, true);
+ expect(await events.next, 3);
+ expect(await events.hasNext, true);
+ expect(await events.hasNext, true);
+ expect(await events.next, 4);
+ expect(await events.hasNext, false);
+ expect(await events.hasNext, false);
+ });
+
+ test('- next after true', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.hasNext, true);
+ expect(await events.next, 2);
+ expect(await events.next, 3);
+ });
+
+ test('- next after true, enqueued', () async {
+ var events = StreamQueue<int>(createStream());
+ var responses = <Object>[];
+ events.next.then(responses.add);
+ events.hasNext.then(responses.add);
+ events.next.then(responses.add);
+ do {
+ await flushMicrotasks();
+ } while (responses.length < 3);
+ expect(responses, [1, true, 2]);
+ });
+
+ test('- skip 0 after true', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.hasNext, true);
+ expect(await events.skip(0), 0);
+ expect(await events.next, 2);
+ });
+
+ test('- skip 1 after true', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.hasNext, true);
+ expect(await events.skip(1), 0);
+ expect(await events.next, 3);
+ });
+
+ test('- skip 2 after true', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.hasNext, true);
+ expect(await events.skip(2), 0);
+ expect(await events.next, 4);
+ });
+
+ test('- take 0 after true', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.hasNext, true);
+ expect(await events.take(0), isEmpty);
+ expect(await events.next, 2);
+ });
+
+ test('- take 1 after true', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.hasNext, true);
+ expect(await events.take(1), [2]);
+ expect(await events.next, 3);
+ });
+
+ test('- take 2 after true', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.hasNext, true);
+ expect(await events.take(2), [2, 3]);
+ expect(await events.next, 4);
+ });
+
+ test('- rest after true', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.hasNext, true);
+ var stream = events.rest;
+ expect(await stream.toList(), [2, 3, 4]);
+ });
+
+ test('- rest after true, at last', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.next, 2);
+ expect(await events.next, 3);
+ expect(await events.hasNext, true);
+ var stream = events.rest;
+ expect(await stream.toList(), [4]);
+ });
+
+ test('- rest after false', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.next, 2);
+ expect(await events.next, 3);
+ expect(await events.next, 4);
+ expect(await events.hasNext, false);
+ var stream = events.rest;
+ expect(await stream.toList(), isEmpty);
+ });
+
+ test('- cancel after true on data', () async {
+ var events = StreamQueue<int>(createStream());
+ expect(await events.next, 1);
+ expect(await events.next, 2);
+ expect(await events.hasNext, true);
+ expect(await events.cancel(), null);
+ });
+
+ test('- cancel after true on error', () async {
+ var events = StreamQueue<int>(createErrorStream());
+ expect(await events.next, 1);
+ expect(await events.next, 2);
+ expect(await events.hasNext, true);
+ expect(await events.cancel(), null);
+ });
+ });
+
+ group('startTransaction operation produces a transaction that', () {
+ late StreamQueue<int> events;
+ late StreamQueueTransaction<int> transaction;
+ late StreamQueue<int> queue1;
+ late StreamQueue<int> queue2;
+ setUp(() async {
+ events = StreamQueue(createStream());
+ expect(await events.next, 1);
+ transaction = events.startTransaction();
+ queue1 = transaction.newQueue();
+ queue2 = transaction.newQueue();
+ });
+
+ group('emits queues that', () {
+ test('independently emit events', () async {
+ expect(await queue1.next, 2);
+ expect(await queue2.next, 2);
+ expect(await queue2.next, 3);
+ expect(await queue1.next, 3);
+ expect(await queue1.next, 4);
+ expect(await queue2.next, 4);
+ expect(await queue1.hasNext, isFalse);
+ expect(await queue2.hasNext, isFalse);
+ });
+
+ test('queue requests for events', () async {
+ expect(queue1.next, completion(2));
+ expect(queue2.next, completion(2));
+ expect(queue2.next, completion(3));
+ expect(queue1.next, completion(3));
+ expect(queue1.next, completion(4));
+ expect(queue2.next, completion(4));
+ expect(queue1.hasNext, completion(isFalse));
+ expect(queue2.hasNext, completion(isFalse));
+ });
+
+ test('independently emit errors', () async {
+ events = StreamQueue(createErrorStream());
+ expect(await events.next, 1);
+ transaction = events.startTransaction();
+ queue1 = transaction.newQueue();
+ queue2 = transaction.newQueue();
+
+ expect(queue1.next, completion(2));
+ expect(queue2.next, completion(2));
+ expect(queue2.next, throwsA('To err is divine!'));
+ expect(queue1.next, throwsA('To err is divine!'));
+ expect(queue1.next, completion(4));
+ expect(queue2.next, completion(4));
+ expect(queue1.hasNext, completion(isFalse));
+ expect(queue2.hasNext, completion(isFalse));
+ });
+ });
+
+ group('when rejected', () {
+ test('further original requests use the previous state', () async {
+ expect(await queue1.next, 2);
+ expect(await queue2.next, 2);
+ expect(await queue2.next, 3);
+
+ await flushMicrotasks();
+ transaction.reject();
+
+ expect(await events.next, 2);
+ expect(await events.next, 3);
+ expect(await events.next, 4);
+ expect(await events.hasNext, isFalse);
+ });
+
+ test('pending original requests use the previous state', () async {
+ expect(await queue1.next, 2);
+ expect(await queue2.next, 2);
+ expect(await queue2.next, 3);
+ expect(events.next, completion(2));
+ expect(events.next, completion(3));
+ expect(events.next, completion(4));
+ expect(events.hasNext, completion(isFalse));
+
+ await flushMicrotasks();
+ transaction.reject();
+ });
+
+ test('further child requests act as though the stream was closed',
+ () async {
+ expect(await queue1.next, 2);
+ transaction.reject();
+
+ expect(await queue1.hasNext, isFalse);
+ expect(queue1.next, throwsStateError);
+ });
+
+ test('pending child requests act as though the stream was closed',
+ () async {
+ expect(await queue1.next, 2);
+ expect(queue1.hasNext, completion(isFalse));
+ expect(queue1.next, throwsStateError);
+ transaction.reject();
+ });
+
+ // Regression test.
+ test('pending child rest requests emit no more events', () async {
+ var controller = StreamController<int>();
+ var events = StreamQueue(controller.stream);
+ var transaction = events.startTransaction();
+ var queue = transaction.newQueue();
+
+ // This should emit no more events after the transaction is rejected.
+ queue.rest.listen(expectAsync1((_) {}, count: 3),
+ onDone: expectAsync0(() {}, count: 0));
+
+ controller.add(1);
+ controller.add(2);
+ controller.add(3);
+ await flushMicrotasks();
+
+ transaction.reject();
+ await flushMicrotasks();
+
+ // These shouldn't affect the result of `queue.rest.toList()`.
+ controller.add(4);
+ controller.add(5);
+ });
+
+ test("child requests' cancel() may still be called explicitly", () async {
+ transaction.reject();
+ await queue1.cancel();
+ });
+
+ test('calls to commit() or reject() fail', () async {
+ transaction.reject();
+ expect(transaction.reject, throwsStateError);
+ expect(() => transaction.commit(queue1), throwsStateError);
+ });
+
+ test('before the transaction emits any events, does nothing', () async {
+ var controller = StreamController<int>();
+ var events = StreamQueue(controller.stream);
+
+ // Queue a request before the transaction, but don't let it complete
+ // until we're done with the transaction.
+ expect(events.next, completion(equals(1)));
+ events.startTransaction().reject();
+ expect(events.next, completion(equals(2)));
+
+ await flushMicrotasks();
+ controller.add(1);
+ await flushMicrotasks();
+ controller.add(2);
+ await flushMicrotasks();
+ controller.close();
+ });
+
+ test(
+ 'can reject a transaction where one copy is fully consumed '
+ 'in a transaction and a second copy is made', () async {
+ // Regression test for https://github.com/dart-lang/async/issues/229
+ final queue = StreamQueue(Stream.fromIterable([0]));
+ final transaction = queue.startTransaction();
+
+ final copy1 = transaction.newQueue();
+ final inner1 = copy1.startTransaction();
+ final innerCopy1 = inner1.newQueue();
+ await innerCopy1.next;
+
+ transaction.newQueue();
+
+ transaction.reject();
+ expect(await queue.next, 0);
+ expect(await queue.hasNext, isFalse);
+ }, skip: 'https://github.com/dart-lang/async/issues/229');
+ });
+
+ group('when committed', () {
+ test('further original requests use the committed state', () async {
+ expect(await queue1.next, 2);
+ await flushMicrotasks();
+ transaction.commit(queue1);
+ expect(await events.next, 3);
+ });
+
+ test('pending original requests use the committed state', () async {
+ expect(await queue1.next, 2);
+ expect(events.next, completion(3));
+ await flushMicrotasks();
+ transaction.commit(queue1);
+ });
+
+ test('further child requests act as though the stream was closed',
+ () async {
+ expect(await queue2.next, 2);
+ transaction.commit(queue2);
+
+ expect(await queue1.hasNext, isFalse);
+ expect(queue1.next, throwsStateError);
+ });
+
+ test('pending child requests act as though the stream was closed',
+ () async {
+ expect(await queue2.next, 2);
+ expect(queue1.hasNext, completion(isFalse));
+ expect(queue1.next, throwsStateError);
+ transaction.commit(queue2);
+ });
+
+ test('further requests act as though the stream was closed', () async {
+ expect(await queue1.next, 2);
+ transaction.commit(queue1);
+
+ expect(await queue1.hasNext, isFalse);
+ expect(queue1.next, throwsStateError);
+ });
+
+ test('cancel() may still be called explicitly', () async {
+ expect(await queue1.next, 2);
+ transaction.commit(queue1);
+ await queue1.cancel();
+ });
+
+ test('throws if there are pending requests', () async {
+ expect(await queue1.next, 2);
+ expect(queue1.hasNext, completion(isTrue));
+ expect(() => transaction.commit(queue1), throwsStateError);
+ });
+
+ test('calls to commit() or reject() fail', () async {
+ transaction.commit(queue1);
+ expect(transaction.reject, throwsStateError);
+ expect(() => transaction.commit(queue1), throwsStateError);
+ });
+
+ test('before the transaction emits any events, does nothing', () async {
+ var controller = StreamController<int>();
+ var events = StreamQueue(controller.stream);
+
+ // Queue a request before the transaction, but don't let it complete
+ // until we're done with the transaction.
+ expect(events.next, completion(equals(1)));
+ var transaction = events.startTransaction();
+ transaction.commit(transaction.newQueue());
+ expect(events.next, completion(equals(2)));
+
+ await flushMicrotasks();
+ controller.add(1);
+ await flushMicrotasks();
+ controller.add(2);
+ await flushMicrotasks();
+ controller.close();
+ });
+ });
+ });
+
+ group('withTransaction operation', () {
+ late StreamQueue<int> events;
+ setUp(() async {
+ events = StreamQueue(createStream());
+ expect(await events.next, 1);
+ });
+
+ test('passes a copy of the parent queue', () async {
+ await events.withTransaction(expectAsync1((queue) async {
+ expect(await queue.next, 2);
+ expect(await queue.next, 3);
+ expect(await queue.next, 4);
+ expect(await queue.hasNext, isFalse);
+ return true;
+ }));
+ });
+
+ test(
+ 'the parent queue continues from the child position if it returns '
+ 'true', () async {
+ await events.withTransaction(expectAsync1((queue) async {
+ expect(await queue.next, 2);
+ return true;
+ }));
+
+ expect(await events.next, 3);
+ });
+
+ test(
+ 'the parent queue continues from its original position if it returns '
+ 'false', () async {
+ await events.withTransaction(expectAsync1((queue) async {
+ expect(await queue.next, 2);
+ return false;
+ }));
+
+ expect(await events.next, 2);
+ });
+
+ test('the parent queue continues from the child position if it throws', () {
+ expect(events.withTransaction(expectAsync1((queue) async {
+ expect(await queue.next, 2);
+ throw 'oh no';
+ })), throwsA('oh no'));
+
+ expect(events.next, completion(3));
+ });
+
+ test('returns whether the transaction succeeded', () {
+ expect(events.withTransaction((_) async => true), completion(isTrue));
+ expect(events.withTransaction((_) async => false), completion(isFalse));
+ });
+ });
+
+ group('cancelable operation', () {
+ late StreamQueue<int> events;
+ setUp(() async {
+ events = StreamQueue(createStream());
+ expect(await events.next, 1);
+ });
+
+ test('passes a copy of the parent queue', () async {
+ await events.cancelable(expectAsync1((queue) async {
+ expect(await queue.next, 2);
+ expect(await queue.next, 3);
+ expect(await queue.next, 4);
+ expect(await queue.hasNext, isFalse);
+ })).value;
+ });
+
+ test('the parent queue continues from the child position by default',
+ () async {
+ await events.cancelable(expectAsync1((queue) async {
+ expect(await queue.next, 2);
+ })).value;
+
+ expect(await events.next, 3);
+ });
+
+ test(
+ 'the parent queue continues from the child position if an error is '
+ 'thrown', () async {
+ expect(
+ events.cancelable(expectAsync1((queue) async {
+ expect(await queue.next, 2);
+ throw 'oh no';
+ })).value,
+ throwsA('oh no'));
+
+ expect(events.next, completion(3));
+ });
+
+ test('the parent queue continues from the original position if canceled',
+ () async {
+ var operation = events.cancelable(expectAsync1((queue) async {
+ expect(await queue.next, 2);
+ }));
+ operation.cancel();
+
+ expect(await events.next, 2);
+ });
+
+ test('forwards the value from the callback', () async {
+ expect(
+ await events.cancelable(expectAsync1((queue) async {
+ expect(await queue.next, 2);
+ return 'value';
+ })).value,
+ 'value');
+ });
+ });
+
+ test('all combinations sequential skip/next/take operations', () async {
+ // Takes all combinations of two of next, skip and take, then ends with
+ // doing rest. Each of the first rounds do 10 events of each type,
+ // the rest does 20 elements.
+ var eventCount = 20 * (3 * 3 + 1);
+ var events = StreamQueue<int>(createLongStream(eventCount));
+
+ // Test expecting [startIndex .. startIndex + 9] as events using
+ // `next`.
+ void nextTest(int startIndex) {
+ for (var i = 0; i < 10; i++) {
+ expect(events.next, completion(startIndex + i));
+ }
+ }
+
+ // Test expecting 10 events to be skipped.
+ void skipTest(startIndex) {
+ expect(events.skip(10), completion(0));
+ }
+
+ // Test expecting [startIndex .. startIndex + 9] as events using
+ // `take(10)`.
+ void takeTest(int startIndex) {
+ expect(events.take(10),
+ completion(List.generate(10, (i) => startIndex + i)));
+ }
+
+ var tests = [nextTest, skipTest, takeTest];
+
+ var counter = 0;
+ // Run through all pairs of two tests and run them.
+ for (var i = 0; i < tests.length; i++) {
+ for (var j = 0; j < tests.length; j++) {
+ tests[i](counter);
+ tests[j](counter + 10);
+ counter += 20;
+ }
+ }
+ // Then expect 20 more events as a `rest` call.
+ expect(events.rest.toList(),
+ completion(List.generate(20, (i) => counter + i)));
+ });
+}
+
+typedef Func1Required<T> = T Function(T value);
+
+Stream<int> createStream() async* {
+ yield 1;
+ await flushMicrotasks();
+ yield 2;
+ await flushMicrotasks();
+ yield 3;
+ await flushMicrotasks();
+ yield 4;
+}
+
+Stream<int> createErrorStream() {
+ var controller = StreamController<int>();
+ () async {
+ controller.add(1);
+ await flushMicrotasks();
+ controller.add(2);
+ await flushMicrotasks();
+ controller.addError('To err is divine!');
+ await flushMicrotasks();
+ controller.add(4);
+ await flushMicrotasks();
+ controller.close();
+ }();
+ return controller.stream;
+}
+
+Stream<int> createLongStream(int eventCount) async* {
+ for (var i = 0; i < eventCount; i++) {
+ yield i;
+ }
+}
diff --git a/pkgs/async/test/stream_sink_completer_test.dart b/pkgs/async/test/stream_sink_completer_test.dart
new file mode 100644
index 0000000..3a6f25b
--- /dev/null
+++ b/pkgs/async/test/stream_sink_completer_test.dart
@@ -0,0 +1,307 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ late StreamSinkCompleter completer;
+ setUp(() {
+ completer = StreamSinkCompleter<void>();
+ });
+
+ group('when a stream is linked before events are added', () {
+ test('data events are forwarded', () {
+ var sink = TestSink();
+ completer.setDestinationSink(sink);
+ completer.sink
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..add(4);
+
+ expect(sink.results[0].asValue!.value, equals(1));
+ expect(sink.results[1].asValue!.value, equals(2));
+ expect(sink.results[2].asValue!.value, equals(3));
+ expect(sink.results[3].asValue!.value, equals(4));
+ });
+
+ test('error events are forwarded', () {
+ var sink = TestSink();
+ completer.setDestinationSink(sink);
+ completer.sink
+ ..addError('oh no')
+ ..addError("that's bad");
+
+ expect(sink.results[0].asError!.error, equals('oh no'));
+ expect(sink.results[1].asError!.error, equals("that's bad"));
+ });
+
+ test('addStream is forwarded', () async {
+ var sink = TestSink();
+ completer.setDestinationSink(sink);
+
+ var controller = StreamController<int>();
+ completer.sink.addStream(controller.stream);
+
+ controller.add(1);
+ controller.addError('oh no');
+ controller.add(2);
+ controller.addError("that's bad");
+ await flushMicrotasks();
+
+ expect(sink.results[0].asValue!.value, equals(1));
+ expect(sink.results[1].asError!.error, equals('oh no'));
+ expect(sink.results[2].asValue!.value, equals(2));
+ expect(sink.results[3].asError!.error, equals("that's bad"));
+ expect(sink.isClosed, isFalse);
+
+ controller.close();
+ await flushMicrotasks();
+ expect(sink.isClosed, isFalse);
+ });
+
+ test('close() is forwarded', () {
+ var sink = TestSink();
+ completer.setDestinationSink(sink);
+ completer.sink.close();
+ expect(sink.isClosed, isTrue);
+ });
+
+ test('the future from the inner close() is returned', () async {
+ var closeCompleter = Completer<void>();
+ var sink = TestSink(onDone: () => closeCompleter.future);
+ completer.setDestinationSink(sink);
+
+ var closeCompleted = false;
+ completer.sink.close().then(expectAsync1((_) {
+ closeCompleted = true;
+ }));
+
+ await flushMicrotasks();
+ expect(closeCompleted, isFalse);
+
+ closeCompleter.complete();
+ await flushMicrotasks();
+ expect(closeCompleted, isTrue);
+ });
+
+ test('errors are forwarded from the inner close()', () {
+ var sink = TestSink(onDone: () => throw 'oh no');
+ completer.setDestinationSink(sink);
+ expect(completer.sink.done, throwsA('oh no'));
+ expect(completer.sink.close(), throwsA('oh no'));
+ });
+
+ test("errors aren't top-leveled if only close() is listened to", () async {
+ var sink = TestSink(onDone: () => throw 'oh no');
+ completer.setDestinationSink(sink);
+ expect(completer.sink.close(), throwsA('oh no'));
+
+ // Give the event loop a chance to top-level errors if it's going to.
+ await flushMicrotasks();
+ });
+
+ test("errors aren't top-leveled if only done is listened to", () async {
+ var sink = TestSink(onDone: () => throw 'oh no');
+ completer.setDestinationSink(sink);
+ completer.sink.close();
+ expect(completer.sink.done, throwsA('oh no'));
+
+ // Give the event loop a chance to top-level errors if it's going to.
+ await flushMicrotasks();
+ });
+ });
+
+ group('when a stream is linked after events are added', () {
+ test('data events are forwarded', () async {
+ completer.sink
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..add(4);
+ await flushMicrotasks();
+
+ var sink = TestSink();
+ completer.setDestinationSink(sink);
+ await flushMicrotasks();
+
+ expect(sink.results[0].asValue!.value, equals(1));
+ expect(sink.results[1].asValue!.value, equals(2));
+ expect(sink.results[2].asValue!.value, equals(3));
+ expect(sink.results[3].asValue!.value, equals(4));
+ });
+
+ test('error events are forwarded', () async {
+ completer.sink
+ ..addError('oh no')
+ ..addError("that's bad");
+ await flushMicrotasks();
+
+ var sink = TestSink();
+ completer.setDestinationSink(sink);
+ await flushMicrotasks();
+
+ expect(sink.results[0].asError!.error, equals('oh no'));
+ expect(sink.results[1].asError!.error, equals("that's bad"));
+ });
+
+ test('addStream is forwarded', () async {
+ var controller = StreamController<int>();
+ completer.sink.addStream(controller.stream);
+
+ controller.add(1);
+ controller.addError('oh no');
+ controller.add(2);
+ controller.addError("that's bad");
+ controller.close();
+ await flushMicrotasks();
+
+ var sink = TestSink();
+ completer.setDestinationSink(sink);
+ await flushMicrotasks();
+
+ expect(sink.results[0].asValue!.value, equals(1));
+ expect(sink.results[1].asError!.error, equals('oh no'));
+ expect(sink.results[2].asValue!.value, equals(2));
+ expect(sink.results[3].asError!.error, equals("that's bad"));
+ expect(sink.isClosed, isFalse);
+ });
+
+ test('close() is forwarded', () async {
+ completer.sink.close();
+ await flushMicrotasks();
+
+ var sink = TestSink();
+ completer.setDestinationSink(sink);
+ await flushMicrotasks();
+
+ expect(sink.isClosed, isTrue);
+ });
+
+ test('the future from the inner close() is returned', () async {
+ var closeCompleted = false;
+ completer.sink.close().then(expectAsync1((_) {
+ closeCompleted = true;
+ }));
+ await flushMicrotasks();
+
+ var closeCompleter = Completer<void>();
+ var sink = TestSink(onDone: () => closeCompleter.future);
+ completer.setDestinationSink(sink);
+ await flushMicrotasks();
+ expect(closeCompleted, isFalse);
+
+ closeCompleter.complete();
+ await flushMicrotasks();
+ expect(closeCompleted, isTrue);
+ });
+
+ test('errors are forwarded from the inner close()', () async {
+ expect(completer.sink.done, throwsA('oh no'));
+ expect(completer.sink.close(), throwsA('oh no'));
+ await flushMicrotasks();
+
+ var sink = TestSink(onDone: () => throw 'oh no');
+ completer.setDestinationSink(sink);
+ });
+
+ test("errors aren't top-leveled if only close() is listened to", () async {
+ expect(completer.sink.close(), throwsA('oh no'));
+ await flushMicrotasks();
+
+ var sink = TestSink(onDone: () => throw 'oh no');
+ completer.setDestinationSink(sink);
+
+ // Give the event loop a chance to top-level errors if it's going to.
+ await flushMicrotasks();
+ });
+
+ test("errors aren't top-leveled if only done is listened to", () async {
+ completer.sink.close();
+ expect(completer.sink.done, throwsA('oh no'));
+ await flushMicrotasks();
+
+ var sink = TestSink(onDone: () => throw 'oh no');
+ completer.setDestinationSink(sink);
+
+ // Give the event loop a chance to top-level errors if it's going to.
+ await flushMicrotasks();
+ });
+ });
+
+ test('the sink is closed, the destination is set, then done is read',
+ () async {
+ expect(completer.sink.close(), completes);
+ await flushMicrotasks();
+
+ completer.setDestinationSink(TestSink());
+ await flushMicrotasks();
+
+ expect(completer.sink.done, completes);
+ });
+
+ test('done is read, the destination is set, then the sink is closed',
+ () async {
+ expect(completer.sink.done, completes);
+ await flushMicrotasks();
+
+ completer.setDestinationSink(TestSink());
+ await flushMicrotasks();
+
+ expect(completer.sink.close(), completes);
+ });
+
+ group('fromFuture()', () {
+ test('with a successful completion', () async {
+ var futureCompleter = Completer<StreamSink>();
+ var sink = StreamSinkCompleter.fromFuture(futureCompleter.future);
+ sink.add(1);
+ sink.add(2);
+ sink.add(3);
+ sink.close();
+
+ var testSink = TestSink();
+ futureCompleter.complete(testSink);
+ await testSink.done;
+
+ expect(testSink.results[0].asValue!.value, equals(1));
+ expect(testSink.results[1].asValue!.value, equals(2));
+ expect(testSink.results[2].asValue!.value, equals(3));
+ });
+
+ test('with an error', () async {
+ var futureCompleter = Completer<StreamSink>();
+ var sink = StreamSinkCompleter.fromFuture(futureCompleter.future);
+ expect(sink.done, throwsA('oh no'));
+ futureCompleter.completeError('oh no');
+ });
+ });
+
+ group('setError()', () {
+ test('produces a closed sink with the error', () {
+ completer.setError('oh no');
+ expect(completer.sink.done, throwsA('oh no'));
+ expect(completer.sink.close(), throwsA('oh no'));
+ });
+
+ test('produces an error even if done was accessed earlier', () async {
+ expect(completer.sink.done, throwsA('oh no'));
+ expect(completer.sink.close(), throwsA('oh no'));
+ await flushMicrotasks();
+
+ completer.setError('oh no');
+ });
+ });
+
+ test("doesn't allow the destination sink to be set multiple times", () {
+ completer.setDestinationSink(TestSink());
+ expect(() => completer.setDestinationSink(TestSink()), throwsStateError);
+ expect(() => completer.setDestinationSink(TestSink()), throwsStateError);
+ });
+}
diff --git a/pkgs/async/test/stream_sink_transformer_test.dart b/pkgs/async/test/stream_sink_transformer_test.dart
new file mode 100644
index 0000000..caea349
--- /dev/null
+++ b/pkgs/async/test/stream_sink_transformer_test.dart
@@ -0,0 +1,213 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE filevents.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ late StreamController controller;
+ setUp(() {
+ controller = StreamController<void>();
+ });
+
+ group('fromStreamTransformer', () {
+ test('transforms data events', () {
+ var transformer = StreamSinkTransformer.fromStreamTransformer(
+ StreamTransformer.fromHandlers(handleData: (int i, sink) {
+ sink.add(i * 2);
+ }));
+ var sink = transformer.bind(controller.sink);
+
+ var results = [];
+ controller.stream.listen(results.add, onDone: expectAsync0(() {
+ expect(results, equals([2, 4, 6]));
+ }));
+
+ sink.add(1);
+ sink.add(2);
+ sink.add(3);
+ sink.close();
+ });
+
+ test('transforms error events', () {
+ var transformer = StreamSinkTransformer.fromStreamTransformer(
+ StreamTransformer.fromHandlers(handleError: (i, stackTrace, sink) {
+ sink.addError((i as num) * 2, stackTrace);
+ }));
+ var sink = transformer.bind(controller.sink);
+
+ var results = [];
+ controller.stream.listen(expectAsync1((_) {}, count: 0),
+ onError: (Object error, stackTrace) {
+ results.add(error);
+ }, onDone: expectAsync0(() {
+ expect(results, equals([2, 4, 6]));
+ }));
+
+ sink.addError(1);
+ sink.addError(2);
+ sink.addError(3);
+ sink.close();
+ });
+
+ test('transforms done events', () {
+ var transformer = StreamSinkTransformer.fromStreamTransformer(
+ StreamTransformer.fromHandlers(handleDone: (sink) {
+ sink.add(1);
+ sink.close();
+ }));
+ var sink = transformer.bind(controller.sink);
+
+ var results = [];
+ controller.stream.listen(results.add, onDone: expectAsync0(() {
+ expect(results, equals([1]));
+ }));
+
+ sink.close();
+ });
+
+ test('forwards the future from inner.close', () async {
+ var transformer = StreamSinkTransformer.fromStreamTransformer(
+ StreamTransformer.fromHandlers());
+ var innerSink = CompleterStreamSink();
+ var sink = transformer.bind(innerSink);
+
+ // The futures shouldn't complete until the inner sink's close future
+ // completes.
+ var doneResult = ResultFuture(sink.done);
+ doneResult.catchError((_) {});
+ var closeResult = ResultFuture(sink.close());
+ closeResult.catchError((_) {});
+ await flushMicrotasks();
+ expect(doneResult.isComplete, isFalse);
+ expect(closeResult.isComplete, isFalse);
+
+ // Once the inner sink is completed, the futures should fire.
+ innerSink.completer.complete();
+ await flushMicrotasks();
+ expect(doneResult.isComplete, isTrue);
+ expect(closeResult.isComplete, isTrue);
+ });
+
+ test("doesn't top-level the future from inner.close", () async {
+ var transformer = StreamSinkTransformer.fromStreamTransformer(
+ StreamTransformer.fromHandlers(handleData: (_, sink) {
+ sink.close();
+ }));
+ var innerSink = CompleterStreamSink();
+ var sink = transformer.bind(innerSink);
+
+ // This will close the inner sink, but it shouldn't top-level the error.
+ sink.add(1);
+ innerSink.completer.completeError('oh no');
+ await flushMicrotasks();
+
+ // The error should be piped through done and close even if they're called
+ // after the underlying sink is closed.
+ expect(sink.done, throwsA('oh no'));
+ expect(sink.close(), throwsA('oh no'));
+ });
+ });
+
+ group('fromHandlers', () {
+ test('transforms data events', () {
+ var transformer =
+ StreamSinkTransformer.fromHandlers(handleData: (int i, sink) {
+ sink.add(i * 2);
+ });
+ var sink = transformer.bind(controller.sink);
+
+ var results = [];
+ controller.stream.listen(results.add, onDone: expectAsync0(() {
+ expect(results, equals([2, 4, 6]));
+ }));
+
+ sink.add(1);
+ sink.add(2);
+ sink.add(3);
+ sink.close();
+ });
+
+ test('transforms error events', () {
+ var transformer = StreamSinkTransformer.fromHandlers(
+ handleError: (i, stackTrace, sink) {
+ sink.addError((i as num) * 2, stackTrace);
+ });
+ var sink = transformer.bind(controller.sink);
+
+ var results = [];
+ controller.stream.listen(expectAsync1((_) {}, count: 0),
+ onError: (Object error, stackTrace) {
+ results.add(error);
+ }, onDone: expectAsync0(() {
+ expect(results, equals([2, 4, 6]));
+ }));
+
+ sink.addError(1);
+ sink.addError(2);
+ sink.addError(3);
+ sink.close();
+ });
+
+ test('transforms done events', () {
+ var transformer = StreamSinkTransformer.fromHandlers(handleDone: (sink) {
+ sink.add(1);
+ sink.close();
+ });
+ var sink = transformer.bind(controller.sink);
+
+ var results = [];
+ controller.stream.listen(results.add, onDone: expectAsync0(() {
+ expect(results, equals([1]));
+ }));
+
+ sink.close();
+ });
+
+ test('forwards the future from inner.close', () async {
+ var transformer = StreamSinkTransformer.fromHandlers();
+ var innerSink = CompleterStreamSink();
+ var sink = transformer.bind(innerSink);
+
+ // The futures shouldn't complete until the inner sink's close future
+ // completes.
+ var doneResult = ResultFuture(sink.done);
+ doneResult.catchError((_) {});
+ var closeResult = ResultFuture(sink.close());
+ closeResult.catchError((_) {});
+ await flushMicrotasks();
+ expect(doneResult.isComplete, isFalse);
+ expect(closeResult.isComplete, isFalse);
+
+ // Once the inner sink is completed, the futures should fire.
+ innerSink.completer.complete();
+ await flushMicrotasks();
+ expect(doneResult.isComplete, isTrue);
+ expect(closeResult.isComplete, isTrue);
+ });
+
+ test("doesn't top-level the future from inner.close", () async {
+ var transformer =
+ StreamSinkTransformer.fromHandlers(handleData: (_, sink) {
+ sink.close();
+ });
+ var innerSink = CompleterStreamSink();
+ var sink = transformer.bind(innerSink);
+
+ // This will close the inner sink, but it shouldn't top-level the error.
+ sink.add(1);
+ innerSink.completer.completeError('oh no');
+ await flushMicrotasks();
+
+ // The error should be piped through done and close even if they're called
+ // after the underlying sink is closed.
+ expect(sink.done, throwsA('oh no'));
+ expect(sink.close(), throwsA('oh no'));
+ });
+ });
+}
diff --git a/pkgs/async/test/stream_splitter_test.dart b/pkgs/async/test/stream_splitter_test.dart
new file mode 100644
index 0000000..27921db
--- /dev/null
+++ b/pkgs/async/test/stream_splitter_test.dart
@@ -0,0 +1,290 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: unawaited_futures
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late StreamController<int> controller;
+ late StreamSplitter splitter;
+ setUp(() {
+ controller = StreamController<int>();
+ splitter = StreamSplitter<int>(controller.stream);
+ });
+
+ test("a branch that's created before the stream starts to replay it",
+ () async {
+ var events = [];
+ var branch = splitter.split();
+ splitter.close();
+ branch.listen(events.add);
+
+ controller.add(1);
+ await flushMicrotasks();
+ expect(events, equals([1]));
+
+ controller.add(2);
+ await flushMicrotasks();
+ expect(events, equals([1, 2]));
+
+ controller.add(3);
+ await flushMicrotasks();
+ expect(events, equals([1, 2, 3]));
+
+ controller.close();
+ });
+
+ test('a branch replays error events as well as data events', () {
+ var branch = splitter.split();
+ splitter.close();
+
+ controller.add(1);
+ controller.addError('error');
+ controller.add(3);
+ controller.close();
+
+ var count = 0;
+ branch.listen(
+ expectAsync1((value) {
+ expect(count, anyOf(0, 2));
+ expect(value, equals(count + 1));
+ count++;
+ }, count: 2), onError: expectAsync1((error) {
+ expect(count, equals(1));
+ expect(error, equals('error'));
+ count++;
+ }), onDone: expectAsync0(() {
+ expect(count, equals(3));
+ }));
+ });
+
+ test("a branch that's created in the middle of a stream replays it",
+ () async {
+ controller.add(1);
+ controller.add(2);
+ await flushMicrotasks();
+
+ var branch = splitter.split();
+ splitter.close();
+
+ controller.add(3);
+ controller.add(4);
+ controller.close();
+
+ expect(branch.toList(), completion(equals([1, 2, 3, 4])));
+ });
+
+ test("a branch that's created after the stream is finished replays it",
+ () async {
+ controller.add(1);
+ controller.add(2);
+ controller.add(3);
+ controller.close();
+ await flushMicrotasks();
+
+ expect(splitter.split().toList(), completion(equals([1, 2, 3])));
+ splitter.close();
+ });
+
+ test('creates single-subscription branches', () async {
+ var branch = splitter.split();
+ expect(branch.isBroadcast, isFalse);
+ branch.listen(null);
+ expect(() => branch.listen(null), throwsStateError);
+ expect(() => branch.listen(null), throwsStateError);
+ });
+
+ test('multiple branches each replay the stream', () async {
+ var branch1 = splitter.split();
+ controller.add(1);
+ controller.add(2);
+ await flushMicrotasks();
+
+ var branch2 = splitter.split();
+ controller.add(3);
+ controller.close();
+ await flushMicrotasks();
+
+ var branch3 = splitter.split();
+ splitter.close();
+
+ expect(branch1.toList(), completion(equals([1, 2, 3])));
+ expect(branch2.toList(), completion(equals([1, 2, 3])));
+ expect(branch3.toList(), completion(equals([1, 2, 3])));
+ });
+
+ test("a branch doesn't close until the source stream closes", () async {
+ var branch = splitter.split();
+ splitter.close();
+
+ var closed = false;
+ branch.last.then((_) => closed = true);
+
+ controller.add(1);
+ controller.add(2);
+ controller.add(3);
+ await flushMicrotasks();
+ expect(closed, isFalse);
+
+ controller.close();
+ await flushMicrotasks();
+ expect(closed, isTrue);
+ });
+
+ test("the source stream isn't listened to until a branch is", () async {
+ expect(controller.hasListener, isFalse);
+
+ var branch = splitter.split();
+ splitter.close();
+ await flushMicrotasks();
+ expect(controller.hasListener, isFalse);
+
+ branch.listen(null);
+ await flushMicrotasks();
+ expect(controller.hasListener, isTrue);
+ });
+
+ test('the source stream is paused when all branches are paused', () async {
+ var branch1 = splitter.split();
+ var branch2 = splitter.split();
+ var branch3 = splitter.split();
+ splitter.close();
+
+ var subscription1 = branch1.listen(null);
+ var subscription2 = branch2.listen(null);
+ var subscription3 = branch3.listen(null);
+
+ subscription1.pause();
+ await flushMicrotasks();
+ expect(controller.isPaused, isFalse);
+
+ subscription2.pause();
+ await flushMicrotasks();
+ expect(controller.isPaused, isFalse);
+
+ subscription3.pause();
+ await flushMicrotasks();
+ expect(controller.isPaused, isTrue);
+
+ subscription2.resume();
+ await flushMicrotasks();
+ expect(controller.isPaused, isFalse);
+ });
+
+ test('the source stream is paused when all branches are canceled', () async {
+ var branch1 = splitter.split();
+ var branch2 = splitter.split();
+ var branch3 = splitter.split();
+
+ var subscription1 = branch1.listen(null);
+ var subscription2 = branch2.listen(null);
+ var subscription3 = branch3.listen(null);
+
+ subscription1.cancel();
+ await flushMicrotasks();
+ expect(controller.isPaused, isFalse);
+
+ subscription2.cancel();
+ await flushMicrotasks();
+ expect(controller.isPaused, isFalse);
+
+ subscription3.cancel();
+ await flushMicrotasks();
+ expect(controller.isPaused, isTrue);
+
+ var branch4 = splitter.split();
+ splitter.close();
+ await flushMicrotasks();
+ expect(controller.isPaused, isTrue);
+
+ branch4.listen(null);
+ await flushMicrotasks();
+ expect(controller.isPaused, isFalse);
+ });
+
+ test(
+ "the source stream is canceled when it's closed after all branches have "
+ 'been canceled', () async {
+ var branch1 = splitter.split();
+ var branch2 = splitter.split();
+ var branch3 = splitter.split();
+
+ var subscription1 = branch1.listen(null);
+ var subscription2 = branch2.listen(null);
+ var subscription3 = branch3.listen(null);
+
+ subscription1.cancel();
+ await flushMicrotasks();
+ expect(controller.hasListener, isTrue);
+
+ subscription2.cancel();
+ await flushMicrotasks();
+ expect(controller.hasListener, isTrue);
+
+ subscription3.cancel();
+ await flushMicrotasks();
+ expect(controller.hasListener, isTrue);
+
+ splitter.close();
+ expect(controller.hasListener, isFalse);
+ });
+
+ test(
+ 'the source stream is canceled when all branches are canceled after it '
+ 'has been closed', () async {
+ var branch1 = splitter.split();
+ var branch2 = splitter.split();
+ var branch3 = splitter.split();
+ splitter.close();
+
+ var subscription1 = branch1.listen(null);
+ var subscription2 = branch2.listen(null);
+ var subscription3 = branch3.listen(null);
+
+ subscription1.cancel();
+ await flushMicrotasks();
+ expect(controller.hasListener, isTrue);
+
+ subscription2.cancel();
+ await flushMicrotasks();
+ expect(controller.hasListener, isTrue);
+
+ subscription3.cancel();
+ await flushMicrotasks();
+ expect(controller.hasListener, isFalse);
+ });
+
+ test(
+ "a splitter that's closed before any branches are added never listens "
+ 'to the source stream', () {
+ splitter.close();
+
+ // This would throw an error if the stream had already been listened to.
+ controller.stream.listen(null);
+ });
+
+ test(
+ 'splitFrom splits a source stream into the designated number of '
+ 'branches', () {
+ var branches = StreamSplitter.splitFrom(controller.stream, 5);
+
+ controller.add(1);
+ controller.add(2);
+ controller.add(3);
+ controller.close();
+
+ expect(branches[0].toList(), completion(equals([1, 2, 3])));
+ expect(branches[1].toList(), completion(equals([1, 2, 3])));
+ expect(branches[2].toList(), completion(equals([1, 2, 3])));
+ expect(branches[3].toList(), completion(equals([1, 2, 3])));
+ expect(branches[4].toList(), completion(equals([1, 2, 3])));
+ });
+}
+
+/// Wait for all microtasks to complete.
+Future flushMicrotasks() => Future<void>.delayed(Duration.zero);
diff --git a/pkgs/async/test/stream_zip_test.dart b/pkgs/async/test/stream_zip_test.dart
new file mode 100644
index 0000000..147d419
--- /dev/null
+++ b/pkgs/async/test/stream_zip_test.dart
@@ -0,0 +1,336 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+/// Create an error with the same values as [base], except that it throwsA
+/// when seeing the value [errorValue].
+Stream streamError(Stream base, int errorValue, Object error) {
+ return base.map((x) => (x == errorValue) ? throw error : x);
+}
+
+/// Make a [Stream] from an [Iterable] by adding events to a stream controller
+/// at periodic intervals.
+Stream<T> mks<T>(Iterable<T> iterable) {
+ var iterator = iterable.iterator;
+ var controller = StreamController<T>();
+ // Some varying time between 3 and 10 ms.
+ var ms = ((++ctr) * 5) % 7 + 3;
+ Timer.periodic(Duration(milliseconds: ms), (Timer timer) {
+ if (iterator.moveNext()) {
+ controller.add(iterator.current);
+ } else {
+ controller.close();
+ timer.cancel();
+ }
+ });
+ return controller.stream;
+}
+
+/// Counter used to give varying delays for streams.
+int ctr = 0;
+
+void main() {
+ // Test that zipping [streams] gives the results iterated by [expectedData].
+ void testZip(Iterable<Stream> streams, Iterable expectedData) {
+ var data = [];
+ Stream zip = StreamZip(streams);
+ zip.listen(data.add, onDone: expectAsync0(() {
+ expect(data, equals(expectedData));
+ }));
+ }
+
+ test('Basic', () {
+ testZip([
+ mks([1, 2, 3]),
+ mks([4, 5, 6]),
+ mks([7, 8, 9])
+ ], [
+ [1, 4, 7],
+ [2, 5, 8],
+ [3, 6, 9]
+ ]);
+ });
+
+ test('Uneven length 1', () {
+ testZip([
+ mks([1, 2, 3, 99, 100]),
+ mks([4, 5, 6]),
+ mks([7, 8, 9])
+ ], [
+ [1, 4, 7],
+ [2, 5, 8],
+ [3, 6, 9]
+ ]);
+ });
+
+ test('Uneven length 2', () {
+ testZip([
+ mks([1, 2, 3]),
+ mks([4, 5, 6, 99, 100]),
+ mks([7, 8, 9])
+ ], [
+ [1, 4, 7],
+ [2, 5, 8],
+ [3, 6, 9]
+ ]);
+ });
+
+ test('Uneven length 3', () {
+ testZip([
+ mks([1, 2, 3]),
+ mks([4, 5, 6]),
+ mks([7, 8, 9, 99, 100])
+ ], [
+ [1, 4, 7],
+ [2, 5, 8],
+ [3, 6, 9]
+ ]);
+ });
+
+ test('Uneven length 4', () {
+ testZip([
+ mks([1, 2, 3, 98]),
+ mks([4, 5, 6]),
+ mks([7, 8, 9, 99, 100])
+ ], [
+ [1, 4, 7],
+ [2, 5, 8],
+ [3, 6, 9]
+ ]);
+ });
+
+ test('Empty 1', () {
+ testZip([
+ mks([]),
+ mks([4, 5, 6]),
+ mks([7, 8, 9])
+ ], []);
+ });
+
+ test('Empty 2', () {
+ testZip([
+ mks([1, 2, 3]),
+ mks([]),
+ mks([7, 8, 9])
+ ], []);
+ });
+
+ test('Empty 3', () {
+ testZip([
+ mks([1, 2, 3]),
+ mks([4, 5, 6]),
+ mks([])
+ ], []);
+ });
+
+ test('Empty source', () {
+ testZip([], []);
+ });
+
+ test('Single Source', () {
+ testZip([
+ mks([1, 2, 3])
+ ], [
+ [1],
+ [2],
+ [3]
+ ]);
+ });
+
+ test('Other-streams', () {
+ var st1 = mks([1, 2, 3, 4, 5, 6]).where((x) => x < 4);
+ Stream st2 =
+ Stream.periodic(const Duration(milliseconds: 5), (x) => x + 4).take(3);
+ var c = StreamController.broadcast();
+ var st3 = c.stream;
+ testZip([
+ st1,
+ st2,
+ st3
+ ], [
+ [1, 4, 7],
+ [2, 5, 8],
+ [3, 6, 9]
+ ]);
+ c
+ ..add(7)
+ ..add(8)
+ ..add(9)
+ ..close();
+ });
+
+ test('Error 1', () {
+ expect(
+ StreamZip([
+ streamError(mks([1, 2, 3]), 2, 'BAD-1'),
+ mks([4, 5, 6]),
+ mks([7, 8, 9])
+ ]).toList(),
+ throwsA(equals('BAD-1')));
+ });
+
+ test('Error 2', () {
+ expect(
+ StreamZip([
+ mks([1, 2, 3]),
+ streamError(mks([4, 5, 6]), 5, 'BAD-2'),
+ mks([7, 8, 9])
+ ]).toList(),
+ throwsA(equals('BAD-2')));
+ });
+
+ test('Error 3', () {
+ expect(
+ StreamZip([
+ mks([1, 2, 3]),
+ mks([4, 5, 6]),
+ streamError(mks([7, 8, 9]), 8, 'BAD-3')
+ ]).toList(),
+ throwsA(equals('BAD-3')));
+ });
+
+ test('Error at end', () {
+ expect(
+ StreamZip([
+ mks([1, 2, 3]),
+ streamError(mks([4, 5, 6]), 6, 'BAD-4'),
+ mks([7, 8, 9])
+ ]).toList(),
+ throwsA(equals('BAD-4')));
+ });
+
+ test('Error before first end', () {
+ // StreamControllers' streams with no "close" called will never be done,
+ // so the fourth event of the first stream is guaranteed to come first.
+ expect(
+ StreamZip([
+ streamError(mks([1, 2, 3, 4]), 4, 'BAD-5'),
+ (StreamController()
+ ..add(4)
+ ..add(5)
+ ..add(6))
+ .stream,
+ (StreamController()
+ ..add(7)
+ ..add(8)
+ ..add(9))
+ .stream
+ ]).toList(),
+ throwsA(equals('BAD-5')));
+ });
+
+ test('Error after first end', () {
+ var controller = StreamController<int>();
+ controller
+ ..add(7)
+ ..add(8)
+ ..add(9);
+ // Transformer that puts error into controller when one of the first two
+ // streams have sent a done event.
+ var trans =
+ StreamTransformer<int, int>.fromHandlers(handleDone: (EventSink s) {
+ Timer.run(() {
+ controller.addError('BAD-6');
+ });
+ s.close();
+ });
+ testZip([
+ mks([1, 2, 3]).transform(trans),
+ mks([4, 5, 6]).transform(trans),
+ controller.stream
+ ], [
+ [1, 4, 7],
+ [2, 5, 8],
+ [3, 6, 9]
+ ]);
+ });
+
+ test('Pause/Resume', () {
+ var sc1p = 0;
+ var c1 = StreamController(onPause: () {
+ sc1p++;
+ }, onResume: () {
+ sc1p--;
+ });
+
+ var sc2p = 0;
+ var c2 = StreamController(onPause: () {
+ sc2p++;
+ }, onResume: () {
+ sc2p--;
+ });
+
+ var done = expectAsync0(() {
+ expect(sc1p, equals(1));
+ expect(sc2p, equals(0));
+ }); // Call to complete test.
+
+ Stream zip = StreamZip([c1.stream, c2.stream]);
+
+ const ms25 = Duration(milliseconds: 25);
+
+ // StreamIterator uses pause and resume to control flow.
+ var it = StreamIterator(zip);
+
+ it.moveNext().then((hasMore) {
+ expect(hasMore, isTrue);
+ expect(it.current, equals([1, 2]));
+ return it.moveNext();
+ }).then((hasMore) {
+ expect(hasMore, isTrue);
+ expect(it.current, equals([3, 4]));
+ c2.add(6);
+ return it.moveNext();
+ }).then((hasMore) {
+ expect(hasMore, isTrue);
+ expect(it.current, equals([5, 6]));
+ Future<void>.delayed(ms25).then((_) {
+ c2.add(8);
+ });
+ return it.moveNext();
+ }).then((hasMore) {
+ expect(hasMore, isTrue);
+ expect(it.current, equals([7, 8]));
+ c2.add(9);
+ return it.moveNext();
+ }).then((hasMore) {
+ expect(hasMore, isFalse);
+ done();
+ });
+
+ c1
+ ..add(1)
+ ..add(3)
+ ..add(5)
+ ..add(7)
+ ..close();
+ c2
+ ..add(2)
+ ..add(4);
+ });
+
+ test('pause-resume2', () {
+ var s1 = Stream.fromIterable([0, 2, 4, 6, 8]);
+ var s2 = Stream.fromIterable([1, 3, 5, 7]);
+ var sz = StreamZip([s1, s2]);
+ var ctr = 0;
+ late StreamSubscription sub;
+ sub = sz.listen(expectAsync1((v) {
+ expect(v, equals([ctr * 2, ctr * 2 + 1]));
+ if (ctr == 1) {
+ sub.pause(Future<void>.delayed(const Duration(milliseconds: 25)));
+ } else if (ctr == 2) {
+ sub.pause();
+ Future<void>.delayed(const Duration(milliseconds: 25)).then((_) {
+ sub.resume();
+ });
+ }
+ ctr++;
+ }, count: 4));
+ });
+}
diff --git a/pkgs/async/test/stream_zip_zone_test.dart b/pkgs/async/test/stream_zip_zone_test.dart
new file mode 100644
index 0000000..a18c776
--- /dev/null
+++ b/pkgs/async/test/stream_zip_zone_test.dart
@@ -0,0 +1,66 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+// Test that stream listener callbacks all happen in the zone where the
+// listen occurred.
+
+void main() {
+ StreamController controller;
+ controller = StreamController<void>();
+ testStream('singlesub-async', controller, controller.stream);
+ controller = StreamController.broadcast();
+ testStream('broadcast-async', controller, controller.stream);
+ controller = StreamController<void>();
+ testStream(
+ 'asbroadcast-async', controller, controller.stream.asBroadcastStream());
+
+ controller = StreamController(sync: true);
+ testStream('singlesub-sync', controller, controller.stream);
+ controller = StreamController.broadcast(sync: true);
+ testStream('broadcast-sync', controller, controller.stream);
+ controller = StreamController(sync: true);
+ testStream(
+ 'asbroadcast-sync', controller, controller.stream.asBroadcastStream());
+}
+
+void testStream(String name, StreamController controller, Stream stream) {
+ test(name, () {
+ var outer = Zone.current;
+ runZoned(() {
+ var newZone1 = Zone.current;
+ late StreamSubscription sub;
+ sub = stream.listen(expectAsync1((v) {
+ expect(v, 42);
+ expect(Zone.current, newZone1);
+ outer.run(() {
+ sub.onData(expectAsync1((v) {
+ expect(v, 37);
+ expect(Zone.current, newZone1);
+ runZoned(() {
+ sub.onData(expectAsync1((v) {
+ expect(v, 87);
+ expect(Zone.current, newZone1);
+ }));
+ });
+ if (controller is SynchronousStreamController) {
+ scheduleMicrotask(() => controller.add(87));
+ } else {
+ controller.add(87);
+ }
+ }));
+ });
+ if (controller is SynchronousStreamController) {
+ scheduleMicrotask(() => controller.add(37));
+ } else {
+ controller.add(37);
+ }
+ }));
+ });
+ controller.add(42);
+ });
+}
diff --git a/pkgs/async/test/subscription_stream_test.dart b/pkgs/async/test/subscription_stream_test.dart
new file mode 100644
index 0000000..0d245f3
--- /dev/null
+++ b/pkgs/async/test/subscription_stream_test.dart
@@ -0,0 +1,182 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart' show SubscriptionStream;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ test('subscription stream of an entire subscription', () async {
+ var stream = createStream();
+ var subscription = stream.listen(null);
+ var subscriptionStream = SubscriptionStream<int>(subscription);
+ await flushMicrotasks();
+ expect(subscriptionStream.toList(), completion([1, 2, 3, 4]));
+ });
+
+ test('subscription stream after two events', () async {
+ var stream = createStream();
+ var skips = 0;
+ var completer = Completer<SubscriptionStream<int>>();
+ late StreamSubscription<int> subscription;
+ subscription = stream.listen((value) {
+ ++skips;
+ expect(value, skips);
+ if (skips == 2) {
+ completer.complete(SubscriptionStream<int>(subscription));
+ }
+ });
+ var subscriptionStream = await completer.future;
+ await flushMicrotasks();
+ expect(subscriptionStream.toList(), completion([3, 4]));
+ });
+
+ test('listening twice fails', () async {
+ var stream = createStream();
+ var sourceSubscription = stream.listen(null);
+ var subscriptionStream = SubscriptionStream<int>(sourceSubscription);
+ var subscription = subscriptionStream.listen(null);
+ expect(() => subscriptionStream.listen(null), throwsA(anything));
+ await subscription.cancel();
+ });
+
+ test('pause and cancel passed through to original stream', () async {
+ var controller = StreamController(onCancel: () async => 42);
+ var sourceSubscription = controller.stream.listen(null);
+ var subscriptionStream = SubscriptionStream(sourceSubscription);
+ expect(controller.isPaused, isTrue);
+ dynamic lastEvent;
+ var subscription = subscriptionStream.listen((value) {
+ lastEvent = value;
+ });
+ controller.add(1);
+
+ await flushMicrotasks();
+ expect(lastEvent, 1);
+ expect(controller.isPaused, isFalse);
+
+ subscription.pause();
+ expect(controller.isPaused, isTrue);
+
+ subscription.resume();
+ expect(controller.isPaused, isFalse);
+
+ expect(await subscription.cancel() as dynamic, 42);
+ expect(controller.hasListener, isFalse);
+ });
+
+ group('cancelOnError source:', () {
+ for (var sourceCancels in [false, true]) {
+ group('${sourceCancels ? "yes" : "no"}:', () {
+ late SubscriptionStream subscriptionStream;
+ late Future
+ onCancel; // Completes if source stream is canceled before done.
+ setUp(() {
+ var cancelCompleter = Completer<void>();
+ var source = createErrorStream(cancelCompleter);
+ onCancel = cancelCompleter.future;
+ var sourceSubscription =
+ source.listen(null, cancelOnError: sourceCancels);
+ subscriptionStream = SubscriptionStream<int>(sourceSubscription);
+ });
+
+ test('- subscriptionStream: no', () async {
+ var done = Completer<void>();
+ var events = [];
+ subscriptionStream.listen(events.add,
+ onError: events.add, onDone: done.complete, cancelOnError: false);
+ var expected = [1, 2, 'To err is divine!'];
+ if (sourceCancels) {
+ await onCancel;
+ // And [done] won't complete at all.
+ var isDone = false;
+ done.future.then((_) {
+ isDone = true;
+ });
+ await Future<void>.delayed(const Duration(milliseconds: 5));
+ expect(isDone, false);
+ } else {
+ expected.add(4);
+ await done.future;
+ }
+ expect(events, expected);
+ });
+
+ test('- subscriptionStream: yes', () async {
+ var completer = Completer<void>();
+ var events = <Object?>[];
+ subscriptionStream.listen(events.add,
+ onError: (Object? value) {
+ events.add(value);
+ completer.complete();
+ },
+ onDone: () => throw 'should not happen',
+ cancelOnError: true);
+ await completer.future;
+ await flushMicrotasks();
+ expect(events, [1, 2, 'To err is divine!']);
+ });
+ });
+ }
+
+ for (var cancelOnError in [false, true]) {
+ group(cancelOnError ? 'yes' : 'no', () {
+ test('- no error, value goes to asFuture', () async {
+ var stream = createStream();
+ var sourceSubscription =
+ stream.listen(null, cancelOnError: cancelOnError);
+ var subscriptionStream = SubscriptionStream(sourceSubscription);
+ var subscription =
+ subscriptionStream.listen(null, cancelOnError: cancelOnError);
+ expect(subscription.asFuture(42), completion(42));
+ });
+
+ test('- error goes to asFuture', () async {
+ var stream = createErrorStream();
+ var sourceSubscription =
+ stream.listen(null, cancelOnError: cancelOnError);
+ var subscriptionStream = SubscriptionStream(sourceSubscription);
+
+ var subscription =
+ subscriptionStream.listen(null, cancelOnError: cancelOnError);
+ expect(subscription.asFuture(), throwsA(anything));
+ });
+ });
+ }
+ });
+}
+
+Stream<int> createStream() async* {
+ yield 1;
+ await flushMicrotasks();
+ yield 2;
+ await flushMicrotasks();
+ yield 3;
+ await flushMicrotasks();
+ yield 4;
+}
+
+Stream<int> createErrorStream([Completer? onCancel]) async* {
+ var canceled = true;
+ try {
+ yield 1;
+ await flushMicrotasks();
+ yield 2;
+ await flushMicrotasks();
+ yield* Future<int>.error('To err is divine!').asStream();
+ await flushMicrotasks();
+ yield 4;
+ await flushMicrotasks();
+ canceled = false;
+ } finally {
+ // Completes before the "done", but should be after all events.
+ if (canceled && onCancel != null) {
+ await flushMicrotasks();
+ onCancel.complete();
+ }
+ }
+}
diff --git a/pkgs/async/test/subscription_transformer_test.dart b/pkgs/async/test/subscription_transformer_test.dart
new file mode 100644
index 0000000..53610e1
--- /dev/null
+++ b/pkgs/async/test/subscription_transformer_test.dart
@@ -0,0 +1,291 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('with no callbacks', () {
+ test('forwards cancellation', () async {
+ var isCanceled = false;
+ var cancelCompleter = Completer<void>();
+ var controller =
+ StreamController(onCancel: expectAsync0<Future<void>>(() {
+ isCanceled = true;
+ return cancelCompleter.future;
+ }));
+ var subscription = controller.stream
+ .transform(subscriptionTransformer())
+ .listen(expectAsync1((_) {}, count: 0));
+
+ var cancelFired = false;
+ subscription.cancel().then(expectAsync1((_) {
+ cancelFired = true;
+ }));
+
+ await flushMicrotasks();
+ expect(isCanceled, isTrue);
+ expect(cancelFired, isFalse);
+
+ cancelCompleter.complete();
+ await flushMicrotasks();
+ expect(cancelFired, isTrue);
+
+ // This shouldn't call the onCancel callback again.
+ expect(subscription.cancel(), completes);
+ });
+
+ test('forwards pausing and resuming', () async {
+ var controller = StreamController<void>();
+ var subscription = controller.stream
+ .transform(subscriptionTransformer())
+ .listen(expectAsync1((_) {}, count: 0));
+
+ subscription.pause();
+ await flushMicrotasks();
+ expect(controller.isPaused, isTrue);
+
+ subscription.pause();
+ await flushMicrotasks();
+ expect(controller.isPaused, isTrue);
+
+ subscription.resume();
+ await flushMicrotasks();
+ expect(controller.isPaused, isTrue);
+
+ subscription.resume();
+ await flushMicrotasks();
+ expect(controller.isPaused, isFalse);
+ });
+
+ test('forwards pausing with a resume future', () async {
+ var controller = StreamController<void>();
+ var subscription = controller.stream
+ .transform(subscriptionTransformer())
+ .listen(expectAsync1((_) {}, count: 0));
+
+ var completer = Completer<void>();
+ subscription.pause(completer.future);
+ await flushMicrotasks();
+ expect(controller.isPaused, isTrue);
+
+ completer.complete();
+ await flushMicrotasks();
+ expect(controller.isPaused, isFalse);
+ });
+ });
+
+ group('with a cancel callback', () {
+ test('invokes the callback when the subscription is canceled', () async {
+ var isCanceled = false;
+ var callbackInvoked = false;
+ var controller = StreamController(onCancel: expectAsync0(() {
+ isCanceled = true;
+ }));
+ var subscription = controller.stream.transform(
+ subscriptionTransformer(handleCancel: expectAsync1((inner) {
+ callbackInvoked = true;
+ inner.cancel();
+ return Future.value();
+ }))).listen(expectAsync1((_) {}, count: 0));
+
+ await flushMicrotasks();
+ expect(callbackInvoked, isFalse);
+ expect(isCanceled, isFalse);
+
+ subscription.cancel();
+ await flushMicrotasks();
+ expect(callbackInvoked, isTrue);
+ expect(isCanceled, isTrue);
+ });
+
+ test('invokes the callback once and caches its result', () async {
+ var completer = Completer<void>();
+ var controller = StreamController<void>();
+ var subscription = controller.stream
+ .transform(subscriptionTransformer(
+ handleCancel: expectAsync1((inner) => completer.future)))
+ .listen(expectAsync1((_) {}, count: 0));
+
+ var cancelFired1 = false;
+ subscription.cancel().then(expectAsync1((_) {
+ cancelFired1 = true;
+ }));
+
+ var cancelFired2 = false;
+ subscription.cancel().then(expectAsync1((_) {
+ cancelFired2 = true;
+ }));
+
+ await flushMicrotasks();
+ expect(cancelFired1, isFalse);
+ expect(cancelFired2, isFalse);
+
+ completer.complete();
+ await flushMicrotasks();
+ expect(cancelFired1, isTrue);
+ expect(cancelFired2, isTrue);
+ });
+ });
+
+ group('with a pause callback', () {
+ test('invokes the callback when pause is called', () async {
+ var pauseCount = 0;
+ var controller = StreamController<void>();
+ var subscription = controller.stream
+ .transform(subscriptionTransformer(
+ handlePause: expectAsync1((inner) {
+ pauseCount++;
+ inner.pause();
+ }, count: 3)))
+ .listen(expectAsync1((_) {}, count: 0));
+
+ await flushMicrotasks();
+ expect(pauseCount, equals(0));
+
+ subscription.pause();
+ await flushMicrotasks();
+ expect(pauseCount, equals(1));
+
+ subscription.pause();
+ await flushMicrotasks();
+ expect(pauseCount, equals(2));
+
+ subscription.resume();
+ subscription.resume();
+ await flushMicrotasks();
+ expect(pauseCount, equals(2));
+
+ subscription.pause();
+ await flushMicrotasks();
+ expect(pauseCount, equals(3));
+ });
+
+ test("doesn't invoke the callback when the subscription has been canceled",
+ () async {
+ var controller = StreamController<void>();
+ var subscription = controller.stream
+ .transform(subscriptionTransformer(
+ handlePause: expectAsync1((_) {}, count: 0)))
+ .listen(expectAsync1((_) {}, count: 0));
+
+ subscription.cancel();
+ subscription.pause();
+ subscription.pause();
+ subscription.pause();
+ });
+ });
+
+ group('with a resume callback', () {
+ test('invokes the callback when resume is called', () async {
+ var resumeCount = 0;
+ var controller = StreamController<void>();
+ var subscription = controller.stream
+ .transform(subscriptionTransformer(
+ handleResume: expectAsync1((inner) {
+ resumeCount++;
+ inner.resume();
+ }, count: 3)))
+ .listen(expectAsync1((_) {}, count: 0));
+
+ await flushMicrotasks();
+ expect(resumeCount, equals(0));
+
+ subscription.resume();
+ await flushMicrotasks();
+ expect(resumeCount, equals(1));
+
+ subscription.pause();
+ subscription.pause();
+ await flushMicrotasks();
+ expect(resumeCount, equals(1));
+
+ subscription.resume();
+ await flushMicrotasks();
+ expect(resumeCount, equals(2));
+
+ subscription.resume();
+ await flushMicrotasks();
+ expect(resumeCount, equals(3));
+ });
+
+ test('invokes the callback when a resume future completes', () async {
+ var resumed = false;
+ var controller = StreamController<void>();
+ var subscription = controller.stream.transform(
+ subscriptionTransformer(handleResume: expectAsync1((inner) {
+ resumed = true;
+ inner.resume();
+ }))).listen(expectAsync1((_) {}, count: 0));
+
+ var completer = Completer<void>();
+ subscription.pause(completer.future);
+ await flushMicrotasks();
+ expect(resumed, isFalse);
+
+ completer.complete();
+ await flushMicrotasks();
+ expect(resumed, isTrue);
+ });
+
+ test("doesn't invoke the callback when the subscription has been canceled",
+ () async {
+ var controller = StreamController<void>();
+ var subscription = controller.stream
+ .transform(subscriptionTransformer(
+ handlePause: expectAsync1((_) {}, count: 0)))
+ .listen(expectAsync1((_) {}, count: 0));
+
+ subscription.cancel();
+ subscription.resume();
+ subscription.resume();
+ subscription.resume();
+ });
+ });
+
+ group('when the outer subscription is canceled but the inner is not', () {
+ late StreamSubscription subscription;
+ setUp(() {
+ var controller = StreamController<int>();
+ subscription = controller.stream
+ .transform(
+ subscriptionTransformer(handleCancel: (_) => Future.value()))
+ .listen(expectAsync1((_) {}, count: 0),
+ onError: expectAsync2((_, __) {}, count: 0),
+ onDone: expectAsync0(() {}, count: 0));
+ subscription.cancel();
+ controller.add(1);
+ controller.addError('oh no!');
+ controller.close();
+ });
+
+ test("doesn't call a new onData", () async {
+ subscription.onData(expectAsync1((_) {}, count: 0));
+ await flushMicrotasks();
+ });
+
+ test("doesn't call a new onError", () async {
+ subscription.onError(expectAsync2((_, __) {}, count: 0));
+ await flushMicrotasks();
+ });
+
+ test("doesn't call a new onDone", () async {
+ subscription.onDone(expectAsync0(() {}, count: 0));
+ await flushMicrotasks();
+ });
+
+ test('isPaused returns false', () {
+ expect(subscription.isPaused, isFalse);
+ });
+
+ test('asFuture never completes', () async {
+ subscription.asFuture().then(expectAsync1((_) {}, count: 0));
+ await flushMicrotasks();
+ });
+ });
+}
diff --git a/pkgs/async/test/typed_wrapper/stream_subscription_test.dart b/pkgs/async/test/typed_wrapper/stream_subscription_test.dart
new file mode 100644
index 0000000..74195ba
--- /dev/null
+++ b/pkgs/async/test/typed_wrapper/stream_subscription_test.dart
@@ -0,0 +1,143 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/src/typed/stream_subscription.dart';
+import 'package:test/test.dart';
+
+import '../utils.dart';
+
+void main() {
+ group('with valid types, forwards', () {
+ late StreamController controller;
+ late StreamSubscription wrapper;
+ late bool isCanceled;
+ setUp(() {
+ isCanceled = false;
+ controller = StreamController<Object>(onCancel: () {
+ isCanceled = true;
+ });
+ wrapper = TypeSafeStreamSubscription<int>(controller.stream.listen(null));
+ });
+
+ test('onData()', () {
+ wrapper.onData(expectAsync1((data) {
+ expect(data, equals(1));
+ }));
+ controller.add(1);
+ });
+
+ test('onError()', () {
+ wrapper.onError(expectAsync1((error) {
+ expect(error, equals('oh no'));
+ }));
+ controller.addError('oh no');
+ });
+
+ test('onDone()', () {
+ wrapper.onDone(expectAsync0(() {}));
+ controller.close();
+ });
+
+ test('pause(), resume(), and isPaused', () async {
+ expect(wrapper.isPaused, isFalse);
+
+ wrapper.pause();
+ await flushMicrotasks();
+ expect(controller.isPaused, isTrue);
+ expect(wrapper.isPaused, isTrue);
+
+ wrapper.resume();
+ await flushMicrotasks();
+ expect(controller.isPaused, isFalse);
+ expect(wrapper.isPaused, isFalse);
+ });
+
+ test('cancel()', () async {
+ wrapper.cancel();
+ await flushMicrotasks();
+ expect(isCanceled, isTrue);
+ });
+
+ test('asFuture()', () {
+ expect(wrapper.asFuture(12), completion(equals(12)));
+ controller.close();
+ });
+ });
+
+ group('with invalid types,', () {
+ late StreamController controller;
+ late StreamSubscription wrapper;
+ late bool isCanceled;
+ setUp(() {
+ isCanceled = false;
+ controller = StreamController<Object>(onCancel: () {
+ isCanceled = true;
+ });
+ wrapper = TypeSafeStreamSubscription<int>(controller.stream.listen(null));
+ });
+
+ group('throws a TypeError for', () {
+ test('onData()', () {
+ expect(() {
+ // TODO(nweiz): Use the wrapper declared in setUp when sdk#26226 is
+ // fixed.
+ controller = StreamController<Object>();
+ wrapper =
+ TypeSafeStreamSubscription<int>(controller.stream.listen(null));
+
+ wrapper.onData(expectAsync1((_) {}, count: 0));
+ controller.add('foo');
+ }, throwsZonedTypeError);
+ });
+ });
+
+ group("doesn't throw a TypeError for", () {
+ test('onError()', () {
+ wrapper.onError(expectAsync1((error) {
+ expect(error, equals('oh no'));
+ }));
+ controller.add('foo');
+ controller.addError('oh no');
+ });
+
+ test('onDone()', () {
+ wrapper.onDone(expectAsync0(() {}));
+ controller.add('foo');
+ controller.close();
+ });
+
+ test('pause(), resume(), and isPaused', () async {
+ controller.add('foo');
+
+ expect(wrapper.isPaused, isFalse);
+
+ wrapper.pause();
+ await flushMicrotasks();
+ expect(controller.isPaused, isTrue);
+ expect(wrapper.isPaused, isTrue);
+
+ wrapper.resume();
+ await flushMicrotasks();
+ expect(controller.isPaused, isFalse);
+ expect(wrapper.isPaused, isFalse);
+ });
+
+ test('cancel()', () async {
+ controller.add('foo');
+
+ wrapper.cancel();
+ await flushMicrotasks();
+ expect(isCanceled, isTrue);
+ });
+
+ test('asFuture()', () {
+ expect(wrapper.asFuture(12), completion(equals(12)));
+ controller.add('foo');
+ controller.close();
+ });
+ });
+ });
+}
diff --git a/pkgs/async/test/utils.dart b/pkgs/async/test/utils.dart
new file mode 100644
index 0000000..0a6b339
--- /dev/null
+++ b/pkgs/async/test/utils.dart
@@ -0,0 +1,126 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Helper utilities for testing.
+library;
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+/// A zero-millisecond timer should wait until after all microtasks.
+Future flushMicrotasks() => Future<void>.delayed(Duration.zero);
+
+typedef OptionalArgAction = void Function([dynamic a, dynamic b]);
+
+/// A generic unreachable callback function.
+///
+/// Returns a function that fails the test if it is ever called.
+OptionalArgAction unreachable(String name) =>
+ ([a, b]) => fail('Unreachable: $name');
+
+/// A matcher that runs a callback in its own zone and asserts that that zone
+/// emits an error that matches [matcher].
+Matcher throwsZoned(Matcher matcher) => predicate((void Function() callback) {
+ var firstError = true;
+ runZonedGuarded(
+ callback,
+ expectAsync2((error, stackTrace) {
+ if (firstError) {
+ expect(error, matcher);
+ firstError = false;
+ } else {
+ registerException(error, stackTrace);
+ }
+ }, max: -1));
+ return true;
+ });
+
+/// A matcher that runs a callback in its own zone and asserts that that zone
+/// emits a [TypeError].
+final throwsZonedTypeError = throwsZoned(isA<TypeError>());
+
+/// A matcher that matches a callback or future that throws a [TypeError].
+final throwsTypeError = throwsA(isA<TypeError>());
+
+/// A badly behaved stream which throws if it's ever listened to.
+///
+/// Can be used to test cases where a stream should not be used.
+class UnusableStream<T> extends Stream<T> {
+ @override
+ StreamSubscription<T> listen(void Function(T event)? onData,
+ {Function? onError, void Function()? onDone, bool? cancelOnError}) {
+ throw UnimplementedError('Gotcha!');
+ }
+}
+
+/// A dummy [StreamSink] for testing the routing of the [done] and [close]
+/// futures.
+///
+/// The [completer] field allows the user to control the future returned by
+/// [done] and [close].
+class CompleterStreamSink<T> implements StreamSink<T> {
+ final completer = Completer<void>();
+
+ @override
+ Future get done => completer.future;
+
+ @override
+ void add(T event) {}
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {}
+ @override
+ Future addStream(Stream<T> stream) async {}
+ @override
+ Future close() => completer.future;
+}
+
+/// A [StreamSink] that collects all events added to it as results.
+///
+/// This is used for testing code that interacts with sinks.
+class TestSink<T> implements StreamSink<T> {
+ /// The results corresponding to events that have been added to the sink.
+ final results = <Result<T>>[];
+
+ /// Whether [close] has been called.
+ bool get isClosed => _isClosed;
+ var _isClosed = false;
+
+ @override
+ Future get done => _doneCompleter.future;
+ final _doneCompleter = Completer<void>();
+
+ final void Function() _onDone;
+
+ /// Creates a new sink.
+ ///
+ /// If [onDone] is passed, it's called when the user calls [close]. Its result
+ /// is piped to the [done] future.
+ TestSink({void Function()? onDone}) : _onDone = onDone ?? (() {});
+
+ @override
+ void add(T event) {
+ results.add(Result<T>.value(event));
+ }
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ results.add(Result<T>.error(error, stackTrace));
+ }
+
+ @override
+ Future addStream(Stream<T> stream) {
+ var completer = Completer.sync();
+ stream.listen(add, onError: addError, onDone: completer.complete);
+ return completer.future;
+ }
+
+ @override
+ Future close() {
+ _isClosed = true;
+ _doneCompleter.complete(Future.microtask(_onDone));
+ return done;
+ }
+}
diff --git a/pkgs/characters/.gitignore b/pkgs/characters/.gitignore
new file mode 100644
index 0000000..2d72f2b
--- /dev/null
+++ b/pkgs/characters/.gitignore
@@ -0,0 +1,4 @@
+.dart_tool/
+.packages
+pubspec.lock
+doc/api/
diff --git a/pkgs/characters/AUTHORS b/pkgs/characters/AUTHORS
new file mode 100644
index 0000000..846e4a1
--- /dev/null
+++ b/pkgs/characters/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the Dart project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google LLC
diff --git a/pkgs/characters/CHANGELOG.md b/pkgs/characters/CHANGELOG.md
new file mode 100644
index 0000000..29f9c4d
--- /dev/null
+++ b/pkgs/characters/CHANGELOG.md
@@ -0,0 +1,71 @@
+## 1.4.0
+
+* Updated to use Unicode 16.0.0.
+
+## 1.3.1
+
+* Fixed README rendering on pub.dev and API docs.
+* Require Dart `^3.4.0`.
+* Move to `dart-lang/core` monorepo.
+
+## 1.3.0
+
+* Updated to use Unicode 15.0.0.
+
+## 1.2.1
+
+* Update the value of the pubspec `repository` field.
+
+## 1.2.0
+
+* Fix `Characters.where` which unnecessarily did the iteration and test twice.
+* Adds `Characters.empty` constant and makes `Characters("")` return it.
+* Changes the argument type of `Characters.contains` to (covariant) `String`.
+ The implementation still accepts `Object?`, so it can be cast to
+ `Iterable<Object?>`, but you get warned if you try to call directly with a
+ non-`String`.
+
+## 1.1.0
+
+* Stable release for null safety.
+* Added `stringBeforeLength` and `stringAfterLength` to `CharacterRange`.
+* Added `CharacterRange.at` constructor.
+* Added `getRange(start, end)` and `characterAt(pos)` to `Characters`
+ as alternative to `.take(end).skip(start)` and `getRange(pos, pos + 1)`.
+* Change some positional parameter names from `other` to `characters`.
+
+## 1.0.0
+
+* Core APIs deemed stable; package version set to 1.0.0.
+* Added `split` methods on `Characters` and `CharacterRange`.
+
+## 0.5.0
+
+* Change [codeUnits] getter to [utf16CodeUnits] which returns an iterable.
+ This avoids leaking that the underlying string has efficient UTF-16
+ code unit access in the API, and allows the same interface to be
+ just as efficiently implemented on top of UTF-8.
+
+## 0.4.0
+
+* Added an extension method on `String` to allow easy access to the `Characters`
+ of the string:
+
+ ```dart
+ print('The first character is: ' + myString.characters.first)
+ ```
+
+* Updated Dart SDK dependency to Dart 2.6.0
+
+## 0.3.1
+
+* Added small example in `example/main.dart`
+* Enabled pedantic lints and updated code to resolve issues.
+
+## 0.3.0
+
+* Updated API which does not expose the underlying string indices.
+
+## 0.1.0
+
+* Initial release
diff --git a/pkgs/characters/LICENSE b/pkgs/characters/LICENSE
new file mode 100644
index 0000000..7670007
--- /dev/null
+++ b/pkgs/characters/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2019, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/characters/README.md b/pkgs/characters/README.md
new file mode 100644
index 0000000..2bb1f5d
--- /dev/null
+++ b/pkgs/characters/README.md
@@ -0,0 +1,135 @@
+[](https://github.com/dart-lang/core/actions/workflows/characters.yaml)
+[](https://pub.dev/packages/characters)
+[](https://pub.dev/packages/characters/publisher)
+
+[`Characters`][Characters] are strings viewed as
+sequences of **user-perceived character**s,
+also known as [Unicode (extended) grapheme clusters][Grapheme Clusters].
+
+The [`Characters`][Characters] class allows access to
+the individual characters of a string,
+and a way to navigate back and forth between them
+using a [`CharacterRange`][CharacterRange].
+
+Based on Unicode <!-- unicode-version -->version 16.0.0<!-- /unicode-version -->.
+
+## Unicode characters and representations
+
+There is no such thing as plain text.
+
+Computers only know numbers,
+so any "text" on a computer is represented by numbers,
+which are again stored as bytes in memory.
+
+The meaning of those bytes are provided by layers of interpretation,
+building up to the *glyph*s that the computer displays on the screen.
+
+| Abstraction | Dart Type | Usage | Example |
+| --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| Bytes | [`ByteBuffer`][ByteBuffer],<br />[`Uint8List`][Uint8List] | Physical layout: Memory or network communication. | `file.readAsBytesSync()` |
+| [Code units][] | [`Uint8List`][Uint8List] (UTF‑8)<br />[`Uint16List`][Uint16List], [`String`][String] (UTF‑16) | Standard formats for<br /> encoding code points in memory.<br />Stored in memory using one (UTF‑8) or more (UTF‑16) bytes. One or more code units encode a code point. | `string.codeUnits`<br />`string.codeUnitAt(index)`<br />`utf8.encode(string)` |
+| [Code points][] | [`Runes`][Runes] | The Unicode unit of meaning. | `string.runes` |
+| [Grapheme Clusters][] | [`Characters`][Characters] | Human perceived character. One or more code points. | `string.characters` |
+| [Glyphs][] | | Visual rendering of grapheme clusters. | `print(string)` |
+
+A Dart `String` is a sequence of UTF-16 code units,
+just like strings in JavaScript and Java.
+The runtime system decides on the underlying physical representation.
+
+That makes plain strings inadequate
+when needing to manipulate the text that a user is viewing, or entering,
+because string operations are not working at the grapheme cluster level.
+
+For example, to abbreviate a text to, say, the 15 first characters or glyphs,
+a string like "A 🇬🇧 text in English"
+should abbreviate to "A 🇬🇧 text in Eng… when counting characters,
+but will become "A 🇬🇧 text in …"
+if counting code units using [`String`][String] operations.
+
+Whenever you need to manipulate strings at the character level,
+you should be using the [`Characters`][Characters] type,
+not the methods of the [`String`][String] class.
+
+## The Characters class
+
+The [`Characters`][Characters] class exposes a string
+as a sequence of grapheme clusters.
+All operations on [`Characters`][Characters] operate
+on entire grapheme clusters,
+so it removes the risk of splitting combined characters or emojis
+that are inherent in the code-unit based [`String`][String] operations.
+
+You can get a [`Characters`][Characters] object for a string using either
+the constructor [`Characters(string)`][Characters constructor]
+or the extension getter `string.characters`.
+
+At its core, the class is an [`Iterable<String>`][Iterable]
+where the element strings are single grapheme clusters.
+This allows sequential access to the individual grapheme clusters
+of the original string.
+
+On top of that, there are operations mirroring the operations
+of [`String`][String] that are not index, code-unit or code-point based,
+like [`startsWith`][Characters.startsWith]
+or [`replaceAll`][Characters.replaceAll].
+There are some differences between these and the [`String`][String] operations.
+For example the replace methods only accept characters as pattern.
+Regular expressions are not grapheme cluster aware,
+so they cannot be used safely on a sequence of characters.
+
+Grapheme clusters have varying length in the underlying representation,
+so operations on a [`Characters`][Characters] sequence cannot be index based.
+Instead, the [`CharacterRange`][CharacterRange] *iterator*
+provided by [`Characters.iterator`][Characters.iterator]
+has been greatly enhanced.
+It can move both forwards and backwards,
+and it can span a *range* of grapheme cluster.
+Most operations that can be performed on a full [`Characters`][Characters]
+can also be performed on the grapheme clusters
+in the range of a [`CharacterRange`][CharacterRange].
+The range can be contracted, expanded or moved in various ways,
+not restricted to using [`moveNext`][CharacterRange.moveNext],
+to move to the next grapheme cluster.
+
+Example:
+
+```dart
+// Using String indices.
+String? firstTagString(String source) {
+ var start = source.indexOf('<') + 1;
+ if (start > 0) {
+ var end = source.indexOf('>', start);
+ if (end >= 0) {
+ return source.substring(start, end);
+ }
+ }
+ return null;
+}
+
+// Using CharacterRange operations.
+Characters? firstTagCharacters(Characters source) {
+ var range = source.findFirst('<'.characters);
+ if (range != null && range.moveUntil('>'.characters)) {
+ return range.currentCharacters;
+ }
+ return null;
+}
+```
+
+[ByteBuffer]: https://api.dart.dev/dart-typed_data/ByteBuffer-class.html "ByteBuffer class"
+[CharacterRange.moveNext]: https://pub.dev/documentation/characters/latest/characters/CharacterRange/moveNext.html "CharacterRange.moveNext"
+[CharacterRange]: https://pub.dev/documentation/characters/latest/characters/CharacterRange-class.html "CharacterRange class"
+[Characters constructor]: https://pub.dev/documentation/characters/latest/characters/Characters/Characters.html "Characters constructor"
+[Characters.iterator]: https://pub.dev/documentation/characters/latest/characters/Characters/iterator.html "CharactersRange get iterator"
+[Characters.replaceAll]: https://pub.dev/documentation/characters/latest/characters/Characters/replaceAll.html "Characters.replaceAlle"
+[Characters.startsWith]: https://pub.dev/documentation/characters/latest/characters/Characters/startsWith.html "Characters.startsWith"
+[Characters]: https://pub.dev/documentation/characters/latest/characters/Characters-class.html "Characters class"
+[Code Points]: https://unicode.org/glossary/#code_point "Unicode Code Point"
+[Code Units]: https://unicode.org/glossary/#code_unit "Unicode Code Units"
+[Glyphs]: https://unicode.org/glossary/#glyph "Unicode Glyphs"
+[Grapheme Clusters]: https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries "Unicode (Extended) Grapheme Cluster"
+[Iterable]: https://api.dart.dev/dart-core/Iterable-class.html "Iterable class"
+[Runes]: https://api.dart.dev/dart-core/Runes-class.html "Runes class"
+[String]: https://api.dart.dev/dart-core/String-class.html "String class"
+[Uint16List]: https://api.dart.dev/dart-typed_data/Uint16List-class.html "Uint16List class"
+[Uint8List]: https://api.dart.dev/dart-typed_data/Uint8List-class.html "Uint8List class"
diff --git a/pkgs/characters/analysis_options.yaml b/pkgs/characters/analysis_options.yaml
new file mode 100644
index 0000000..d978f81
--- /dev/null
+++ b/pkgs/characters/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
diff --git a/pkgs/characters/benchmark/benchmark.dart b/pkgs/characters/benchmark/benchmark.dart
new file mode 100644
index 0000000..d1f08a3
--- /dev/null
+++ b/pkgs/characters/benchmark/benchmark.dart
@@ -0,0 +1,137 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Benchmark of efficiency of grapheme cluster operations.
+
+import 'package:characters/characters.dart';
+
+import '../test/src/text_samples.dart';
+
+double bench(int Function() action, int ms) {
+ var elapsed = 0;
+ var count = 0;
+ var stopwatch = Stopwatch()..start();
+ do {
+ count += action();
+ elapsed = stopwatch.elapsedMilliseconds;
+ } while (elapsed < ms);
+ return count / elapsed;
+}
+
+int iterateIndicesOnly() {
+ var graphemeClusters = 0;
+ var char = Characters(hangul).iterator;
+ while (char.moveNext()) {
+ graphemeClusters++;
+ }
+ char = Characters(genesis).iterator;
+ while (char.moveNext()) {
+ graphemeClusters++;
+ }
+ return graphemeClusters;
+}
+
+int iterateStrings() {
+ var codeUnits = 0;
+ var char = Characters(hangul).iterator;
+ while (char.moveNext()) {
+ codeUnits += char.current.length;
+ }
+ char = Characters(genesis).iterator;
+ while (char.moveNext()) {
+ codeUnits += char.current.length;
+ }
+ return codeUnits;
+}
+
+int reverseStrings() {
+ var revHangul = reverse(hangul);
+ var rev2Hangul = reverse(revHangul);
+ if (hangul != rev2Hangul || hangul == revHangul) {
+ throw AssertionError('Bad reverse');
+ }
+ var revGenesis = reverse(genesis);
+ var rev2Genesis = reverse(revGenesis);
+ if (genesis != rev2Genesis || genesis == revGenesis) {
+ throw AssertionError('Bad reverse');
+ }
+
+ return (hangul.length + genesis.length) * 2;
+}
+
+int replaceStrings() {
+ var count = 0;
+ {
+ const language = '한글';
+ assert(language.length == 6);
+ var chars = Characters(hangul);
+ var replaced =
+ chars.replaceAll(Characters(language), Characters('Hangul!'));
+ count += replaced.string.length - hangul.length;
+ }
+ {
+ var chars = Characters(genesis);
+ var replaced = chars.replaceAll(Characters('And'), Characters('Also'));
+ count += replaced.string.length - genesis.length;
+ }
+ return count;
+}
+
+String reverse(String input) {
+ var chars = Characters(input);
+ var buffer = StringBuffer();
+ for (var it = chars.iteratorAtEnd; it.moveBack();) {
+ buffer.write(it.current);
+ }
+ return buffer.toString();
+}
+
+void main(List<String> args) {
+ var count = 1;
+ if (args.isNotEmpty) count = int.tryParse(args[0]) ?? 1;
+
+ // Warmup.
+ bench(iterateIndicesOnly, 250);
+ bench(iterateStrings, 250);
+ bench(reverseStrings, 250);
+ bench(replaceStrings, 250);
+
+ var bestIterateIndices = 0.0;
+ var bestIterateStrings = 0.0;
+ var bestReverseStrings = 0.0;
+ var bestReplaceStrings = 0.0;
+
+ String toDigits(double d) {
+ const n = 5;
+ var s = d.round().toString();
+ if (s.length >= n) return s;
+ return d.toStringAsFixed(n - s.length);
+ }
+
+ for (var i = 0; i < count; i++) {
+ var performance = bench(iterateIndicesOnly, 2000);
+ print('Index Iteration: ${toDigits(performance)} gc/ms');
+ if (performance > bestIterateIndices) bestIterateIndices = performance;
+
+ performance = bench(iterateStrings, 2000);
+ print('String Iteration: ${toDigits(performance)} cu/ms');
+ if (performance > bestIterateStrings) bestIterateStrings = performance;
+
+ performance = bench(reverseStrings, 2000);
+ print('String Reversing: ${toDigits(performance)} cu/ms');
+ if (performance > bestReverseStrings) bestReverseStrings = performance;
+
+ performance = bench(replaceStrings, 2000);
+ print('String Replacing: ${toDigits(performance)} changes/ms');
+ if (performance > bestReplaceStrings) bestReplaceStrings = performance;
+ }
+
+ if (count > 1) {
+ print('Best: ');
+ print('Index Iteration: ${toDigits(bestIterateIndices)} gc/ms');
+ print('String Iteration: ${toDigits(bestIterateStrings)} cu/ms');
+ print('String Reversing: ${toDigits(bestReverseStrings)} cu/ms');
+ print('String Replacing: ${toDigits(bestReplaceStrings)} changes/ms');
+ }
+}
diff --git a/pkgs/characters/example/main.dart b/pkgs/characters/example/main.dart
new file mode 100644
index 0000000..b9d5bed
--- /dev/null
+++ b/pkgs/characters/example/main.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:characters/characters.dart';
+
+// Small API examples. For full API docs see:
+// https://pub.dev/documentation/characters/latest/characters/characters-library.html
+void main() {
+ var hi = 'Hi 🇩🇰';
+ print('String is "$hi"\n');
+
+ // Length.
+ print('String.length: ${hi.length}');
+ print('Characters.length: ${hi.characters.length}\n');
+
+ // Last character.
+ print('The string ends with: ${hi.substring(hi.length - 1)}');
+ print('The last character: ${hi.characters.last}\n');
+
+ // Skip last character.
+ print('Substring -1: "${hi.substring(0, hi.length - 1)}"');
+ print('Skipping last character: "${hi.characters.skipLast(1)}"\n');
+
+ // Replace characters.
+ var newHi = hi.characters.replaceAll('🇩🇰'.characters, '🇺🇸'.characters);
+ print('Change flag: "$newHi"');
+}
diff --git a/pkgs/characters/lib/characters.dart b/pkgs/characters/lib/characters.dart
new file mode 100644
index 0000000..f2fd1b1
--- /dev/null
+++ b/pkgs/characters/lib/characters.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// String operations based on characters (Unicode grapheme clusters).
+library;
+
+export 'src/characters.dart';
+export 'src/extensions.dart';
diff --git a/pkgs/characters/lib/src/characters.dart b/pkgs/characters/lib/src/characters.dart
new file mode 100644
index 0000000..fe36863
--- /dev/null
+++ b/pkgs/characters/lib/src/characters.dart
@@ -0,0 +1,846 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'characters_impl.dart';
+
+/// The characters of a string.
+///
+/// A character is a Unicode Grapheme cluster represented
+/// by a substring of the original string.
+/// The `Characters` class is an [Iterable] of those strings.
+/// However, unlike most iterables, many of the operations are
+/// *eager*. Since the underlying string is known in its entirety,
+/// and is known not to change, operations which select a subset of
+/// the elements can be computed eagerly, and in that case the
+/// operation returns a new `Characters` object.
+///
+/// The [iterator] provided by [Characters] is a [CharacterRange]
+/// which allows iterating the independent characters in both directions,
+/// but which also provides ways to select other ranges of characters
+/// in different ways.
+abstract class Characters implements Iterable<String> {
+ /// An empty [Characters] containing no characters.
+ static const Characters empty = StringCharacters('');
+
+ /// Creates a [Characters] allowing iteration of
+ /// the characters of [string].
+ ///
+ /// Returns [empty] if [string] is empty.
+ factory Characters(String string) =>
+ string.isEmpty ? empty : StringCharacters(string);
+
+ /// The string to iterate over.
+ String get string;
+
+ /// Iterator over the characters of this string.
+ ///
+ /// Returns [CharacterRange] positioned before the first character
+ /// of this [Characters].
+ ///
+ /// Allows iterating the characters of [string] as a plain iterator,
+ /// using [CharacterRange.moveNext],
+ /// as well as controlling the iteration in more detail.
+ @override
+ CharacterRange get iterator;
+
+ /// Iterator over the characters of this string.
+ ///
+ /// Returns [CharacterRange] positioned after the last character
+ /// of this [Characters].
+ ///
+ /// Allows iterating the characters of [string] backwards using
+ /// [CharacterRange.moveBack],
+ /// as well as controlling the iteration in more detail.
+ CharacterRange get iteratorAtEnd;
+
+ /// Whether [singleCharacterString] occurs in this
+ /// sequence of characters.
+ ///
+ /// Returns true only if [singleCharacterString] is
+ /// a string containing a *single* character
+ /// and that character is one of the characters
+ /// in this character sequence, and false otherwise.
+ /// This behavior is inherited from `Iterable<String>`,
+ /// which is why it is not [Characters]-based.
+ /// Use [containsAll] for a method which acts like
+ /// [String.contains] for characters.
+ @override
+ bool contains(covariant String singleCharacterString);
+
+ /// Whether this sequence of characters contains [other]
+ /// as a subsequence.
+ bool containsAll(Characters other);
+
+ /// Whether this string starts with the characters of [other].
+ ///
+ /// Returns `true` if [other] the characters of [other]
+ /// are also the first characters of this string,
+ /// and `false` otherwise.
+ bool startsWith(Characters other);
+
+ /// Whether this string ends with the characters of [other].
+ ///
+ /// Returns `true` if [other] the characters of [other]
+ /// are also the last characters of this string,
+ /// and `false` otherwise.
+ bool endsWith(Characters other);
+
+ /// Finds the first occurrence of [characters] in this string.
+ ///
+ /// Returns a [CharacterRange] containing the first occurrence of
+ /// [characters] in this string.
+ /// Returns `null` if there is no such occurrence.
+ CharacterRange? findFirst(Characters characters);
+
+ /// Finds the last occurrence of [characters].
+ ///
+ /// Returns a [CharacterRange] containing the last occurrence of
+ /// [characters].
+ /// Returns `null` if there is no such occurrence,
+ CharacterRange? findLast(Characters characters);
+
+ /// Eagerly selects a subset of the characters.
+ ///
+ /// Tests each character against [test], and returns the
+ /// characters of the concatenation of those character strings.
+ @override
+ Characters where(bool Function(String) test);
+
+ /// Eagerly selects all but the first [count] characters.
+ ///
+ /// If [count] is greater than [length], the count of character
+ /// available, then the empty sequence of characters
+ /// is returned.
+ @override
+ Characters skip(int count);
+
+ /// Eagerly selects the first [count] characters.
+ ///
+ /// If [count] is greater than [length], the count of character
+ /// available, then the entire sequence of characters
+ /// is returned.
+ @override
+ Characters take(int count);
+
+ /// Eagerly selects the range of characters from [start] to [end].
+ ///
+ /// The [start] value must be non-negative,
+ /// and [end], if supplied, must be greater than or equal to [start].
+ ///
+ /// A `characters.getRange(start, end)` is equivalent to
+ /// ```dart
+ /// (end != null ? characters.take(end) : characters).skip(start)
+ /// ```
+ /// if [end] is omitted, the call is equivalent to `characters.skip(start)`.
+ ///
+ /// If [start] is greater than or equal to [length]
+ /// the returned characters is empty.
+ /// Otherwise, if [end] is greater than [length], or omitted,
+ /// [end] is equivalent to [length].
+ Characters getRange(int start, [int? end]);
+
+ /// Returns the single-character sequence of the [position]th character.
+ ///
+ /// The [position] must be non-negative and less than [length].
+ ///
+ /// This operation must iterate characters up to [position] to find
+ /// the result, just like [elementAt].
+ /// It is not an efficient way to iterate over the individual characters
+ /// of a `Characters`.
+ ///
+ /// An call to `chars.characterAt(n)`
+ /// is equivalent to `chars.elementAt(n).characters`,
+ /// or to `chars.getRange(n, n + 1)`
+ /// (except that [getRange] allows `n` to be larger than [length]).
+ Characters characterAt(int position);
+
+ /// Eagerly selects all but the last [count] characters.
+ ///
+ /// If [count] is greater than [length], the count of character
+ /// available, then the empty sequence of characters
+ /// is returned.
+ Characters skipLast(int count);
+
+ /// Eagerly selects the last [count] characters.
+ ///
+ /// If [count] is greater than [length], the count of character
+ /// available, then the entire sequence of characters
+ /// is returned.
+ Characters takeLast(int count);
+
+ /// Eagerly selects a trailing sequence of characters.
+ ///
+ /// Checks each character, from first to last, against [test],
+ /// until one is found where [test] returns `false`.
+ /// The characters starting with the first one
+ /// where [test] returns `false`, are included in the result.
+ ///
+ /// If no characters test `false`, the result is an empty sequence
+ /// of characters.
+ @override
+ Characters skipWhile(bool Function(String) test);
+
+ /// Eagerly selects a leading sequence of characters.
+ ///
+ /// Checks each character, from first to last, against [test],
+ /// until one is found where [test] returns `false`.
+ /// The characters up to, but not including, the first one
+ /// where [test] returns `false` are included in the result.
+ ///
+ /// If no characters test `false`, the entire sequence of character
+ /// is returned.
+ @override
+ Characters takeWhile(bool Function(String) test);
+
+ /// Eagerly selects a leading sequence of characters.
+ ///
+ /// Checks each character, from last to first, against [test],
+ /// until one is found where [test] returns `false`.
+ /// The characters up to and including the one with the latest index
+ /// where [test] returns `false` are included in the result.
+ ///
+ /// If no characters test `false`, the empty sequence of character
+ /// is returned.
+ Characters skipLastWhile(bool Function(String) test);
+
+ /// Eagerly selects a trailing sequence of characters.
+ ///
+ /// Checks each character, from last to first, against [test],
+ /// until one is found where [test] returns `false`.
+ /// The characters after the one with the latest index where
+ /// [test] returns `false` are included in the result.
+ ///
+ /// If no characters test `false`, the entire sequence of character
+ /// is returned.
+ Characters takeLastWhile(bool Function(String) test);
+
+ /// The characters of the concatenation of this and [other].
+ ///
+ /// This is the characters of the concatenation of the underlying
+ /// strings. If there is no character break at the concatenation
+ /// point in the resulting string, then the result is not the concatenation
+ /// of the two character sequences.
+ ///
+ /// This differs from [followedBy] which provides the lazy concatenation
+ /// of this sequence of strings with any other sequence of strings.
+ Characters operator +(Characters other);
+
+ /// Replaces [pattern] with [replacement].
+ ///
+ /// Returns a new [Characters] sequence where all occurrences of the
+ /// [pattern] characters are replaced by [replacement],
+ /// unless the occurrence overlaps a prior
+ /// replaced occurrence of [pattern].
+ ///
+ /// Returns the current characters if there is no occurrence of [pattern].
+ Characters replaceAll(Characters pattern, Characters replacement);
+
+ /// Splits this sequence of characters at each occurrence of [pattern].
+ ///
+ /// Returns a lazy iterable of characters that were separated by [pattern].
+ /// The iterable has *at most* [maxParts] elements if a positive [maxParts]
+ /// is supplied.
+ ///
+ /// Finds each occurrence of [pattern], which does not overlap with
+ /// a previously found occurrence, then the non-matched characters
+ /// before, after, and between the matches are provided in first-to-last
+ /// position order.
+
+ /// If [pattern] is empty, the character sequence is split into separate
+ /// characters, and no leading or trailing empty ranges are provided
+ /// unless the range itself is empty,
+ /// in which case a single empty range is the only result range.
+ /// Otherwise a range starting or ending with [pattern] will cause
+ /// an empty character sequence to be emitted at the start or end.
+ ///
+ /// If [maxParts] is provided and greater than zero,
+ /// only the first `maxParts - 1` occurrences of [pattern] are found
+ /// and split at.
+ /// Any further occurrences will be included in the last part.
+ /// Example:
+ /// ```dart
+ /// var c = 'abracadabra'.characters;
+ /// var parts = c.split('a'.characters, 4).toList();
+ /// print(parts); // Prints is ['', 'br', 'c', 'dabra']
+ /// ```
+ /// If there are fewer than `maxParts - 1` occurrences of [pattern],
+ /// then the characters are split at all occurrences.
+ /// If [maxParts] is zero or negative, it is ignored and the result
+ /// is split at all occurrences of [pattern].
+ Iterable<Characters> split(Characters pattern, [int maxParts = 0]);
+
+ /// Replaces the first occurrence of [pattern] with [replacement].
+ ///
+ /// Returns a new [Characters] where the first occurrence of the
+ /// [pattern] character sequence, if any, is replaced by [replacement].
+ ///
+ /// Returns the current characters if there is no occurrence of [pattern].
+ Characters replaceFirst(Characters pattern, Characters replacement);
+
+ /// The characters of the lower-case version of [string].
+ Characters toLowerCase();
+
+ /// The characters of the upper-case version of [string].
+ Characters toUpperCase();
+
+ /// The hash code of [string].
+ @override
+ int get hashCode;
+
+ /// Whether [other] to another [Characters] with the same [string].
+ @override
+ bool operator ==(Object other);
+
+ /// The [string] content of these characters.
+ @override
+ String toString();
+}
+
+/// A range of characters of a [Characters].
+///
+/// A range of consecutive characters in [source],
+/// corresponding to a start and end position in the source sequence.
+/// The range may even be empty, but that will still correspond to a position
+/// where both start and end happen to be the same position.
+///
+/// The source sequence can be separated into the *preceding* characters,
+/// those before the range, the range itself, and the *following* characters,
+/// those after the range.
+///
+/// Some operations inspect or act on the characters of the current range,
+/// and other operations modify the range by moving the start and/or end
+/// position.
+///
+/// In general, an operation with a name starting with `move` will move
+/// both start and end positions, selecting an entirely new range
+/// which does not overlap the current range.
+/// Operations starting with `collapse` reduces the current range to
+/// a sub-range of itself.
+/// Operations starting with `expand` increase the current range
+/// by moving the end position to a later position
+/// or the start position to an earlier position,
+/// and operations starting with `drop` reduce the current range
+/// by moving the start to a later position or the end to an earlier position,
+/// thereby dropping characters from one or both ends from the current range.
+///
+///
+/// The character range implements [Iterator]
+/// The [moveNext] operation, when called with no argument,
+/// iterates the *next* single characters of the [source] sequence.
+abstract class CharacterRange implements Iterator<String> {
+ /// Creates a new character iterator iterating the character
+ /// of [string].
+ factory CharacterRange(String string) = StringCharacterRange;
+
+ /// Creates a new character iterator on [string].
+ ///
+ /// The iterator starts with a current range containing the
+ /// substring from [startIndex] to [endIndex] of [string].
+ /// If [startIndex] is not a character boundary,
+ /// the range starts at the beginning of the character
+ /// that [startIndex] is pointing into.
+ /// If [endIndex] is not a character boundary,
+ /// the range end at then end of the character
+ /// that [endIndex] is pointing into.
+ /// If [endIndex] is not provided,
+ /// it defaults to the same index as [startIndex].
+ ///
+ /// So, if no [endIndex] is provided,
+ /// and [startIndex] is at a character boundary,
+ /// the resulting iterator's current range is empty.
+ /// Otherwise, the range will contain the characters
+ /// of the substring from [startIndex] to [endIndex],
+ /// extended so that it contains entire characters of the original [string].
+ factory CharacterRange.at(String string, int startIndex, [int? endIndex]) =
+ StringCharacterRange.at;
+
+ /// The character sequence that this range is a sub-sequence of.
+ Characters get source;
+
+ /// The code units of the current character range.
+ Iterable<int> get utf16CodeUnits;
+
+ /// The code points of the current character range.
+ Runes get runes;
+
+ /// The characters of this range.
+ Characters get currentCharacters;
+
+ /// The characters before the current range.
+ Characters get charactersBefore;
+
+ /// The characters after the current range.
+ Characters get charactersAfter;
+
+ /// The string of the characters before the current range.
+ String get stringBefore;
+
+ /// The length, in code units, of [stringBefore].
+ int get stringBeforeLength;
+
+ /// The string of the characters after the current range.
+ String get stringAfter;
+
+ /// The length, in code units, of [stringAfter].
+ int get stringAfterLength;
+
+ /// Creates a copy of this [CharacterRange].
+ ///
+ /// The copy is in the exact same state as this iterator.
+ /// Can be used to iterate the following characters more than once
+ /// at the same time. To simply rewind an iterator, remember the
+ /// start or end position and use reset the iterator to that position.
+ CharacterRange copy();
+
+ /// Whether the current range is empty.
+ ///
+ /// An empty range has no characters, but still has a position as
+ /// a sub-sequence of the source character sequence.
+ bool get isEmpty;
+
+ /// Whether the current range is not empty.
+ ///
+ /// A non-empty range contains at least one character.
+ bool get isNotEmpty;
+
+ /// Moves the range to be the next [count] characters after the current range.
+ ///
+ /// The new range starts and the end of the current range and includes
+ /// the next [count] characters, or as many as available if there
+ /// are fewer than [count] characters following the current range.
+ ///
+ /// The [count] must not be negative.
+ /// If it is zero, the call has the same effect as [collapseToEnd].
+ ///
+ /// Returns `true` if there were [count] following characters
+ /// and `false` if not.
+ @override
+ bool moveNext([int count = 1]);
+
+ /// Moves the range to be everything after the current range.
+ void moveNextAll();
+
+ /// Moves the range to the next occurrence of [target]
+ /// after the current range.
+ ///
+ /// If there is an occurrence of [target] in the characters following
+ /// the current range,
+ /// then the new range contains exactly the first such occurrence of [target].
+ ///
+ /// If there is no occurrence of [target] after the current range,
+ /// the range is not modified.
+ ///
+ /// Returns `true` if the range is modified and `false` if not.
+ bool moveTo(Characters target);
+
+ /// Moves to the range until the next occurrence of [target].
+ ///
+ /// If there is an occurrence of [target] in the characters following
+ /// the current range,
+ /// then the new range contains the characters from the end
+ /// of the current range until, but no including the first such
+ /// occurrence of [target].
+ ///
+ /// If there is no occurrence of [target] after the current range,
+ /// the new range contains all the characters following the current range,
+ /// from the end of the current range until the end of the string.
+ ///
+ /// Returns `true` if there was an occurrence of [target].
+ bool moveUntil(Characters target);
+
+ /// Moves the range to be the last [count] characters before the current
+ /// range.
+ ///
+ /// The new range ends at the start of the current range and includes
+ /// the previous [count] characters, or as many as available if there
+ /// are fewer than [count] characters preceding the current range.
+ ///
+ /// The [count] must not be negative.
+ /// If it is zero, the call has the same effect as [collapseToStart].
+ ///
+ /// Returns `true` if there were [count] preceding characters
+ /// and `false` if not.
+ bool moveBack([int count = 1]);
+
+ /// Moves the range to be everything before the current range.
+ void moveBackAll();
+
+ /// Moves the range to the last occurrence of [target]
+ /// before the current range.
+ ///
+ /// If there is an occurrence of [target] in the characters preceding
+ /// the current range,
+ /// then the new range contains exactly the last such occurrence of [target].
+ ///
+ /// If there is no occurrence of [target] after the current range,
+ /// the range is not modified.
+ ///
+ /// Returns `true` if the range is modified and `false` if not.
+ bool moveBackTo(Characters target);
+
+ /// Moves to the range after the previous occurrence of [target].
+ ///
+ /// If there is an occurrence of [target] in the characters preceding
+ /// the current range,
+ /// then the new range contains the characters after
+ /// the last such occurrence, and up to the start of the current range.
+ ///
+ /// If there is no occurrence of [target] after the current range,
+ /// the new range contains all the characters preceding the current range,
+ /// from the start of the string to the start of the current range.
+ ///
+ /// Returns `true` if there was an occurrence of [target].
+ bool moveBackUntil(Characters target);
+
+ /// Expands the current range with the next [count] characters.
+ ///
+ /// Expands the current range to include the first [count] characters
+ /// following the current range, or as many as are available if
+ /// there are fewer than [count] characters following the current range.
+ ///
+ /// The [count] must not be negative.
+ /// If it is zero, the range does not change.
+ ///
+ /// Returns `true` if there are at least [count] characters following
+ /// the current range, and `false` if not.
+ bool expandNext([int count = 1]);
+
+ /// Expands the range to include the next occurrence of [target].
+ ///
+ /// If there is an occurrence of [target] in the characters following
+ /// the current range, the end of the the range is moved to just after
+ /// the first such occurrence.
+ ///
+ /// If there is no such occurrence of [target], the range is not modified.
+ ///
+ /// Returns `true` if there is an occurrence of [target] and `false` if not.
+ /// Notice that if [target] is empty,
+ /// the result is `true` even though the range is not modified.
+ bool expandTo(Characters target);
+
+ /// Expands the range to include characters until the next [target].
+ ///
+ /// If there is an occurrence of [target] in the characters following
+ /// the current range, the end of the the range is moved to just before
+ /// the first such occurrence.
+ ///
+ /// If there is no such occurrence of [target], the end of the range is
+ /// moved to the end of [source].
+ ///
+ /// Returns `true` if there is an occurrence of [target] and `false` if not.
+ bool expandUntil(Characters target);
+
+ /// Expands the range with the following characters satisfying [test].
+ ///
+ /// Iterates through the characters following the current range
+ /// and includes them into the range until finding a character that
+ /// [test] returns `false` for.
+ void expandWhile(bool Function(String) test);
+
+ /// Expands the range to the end of [source].
+ void expandAll();
+
+ /// Expands the current range with the preceding [count] characters.
+ ///
+ /// Expands the current range to include the last [count] characters
+ /// preceding the current range, or as many as are available if
+ /// there are fewer than [count] characters preceding the current range.
+ ///
+ /// The [count] must not be negative.
+ /// If it is zero, the range does not change.
+ ///
+ /// Returns `true` if there are at least [count] characters preceding
+ /// the current range, and `false` if not.
+ bool expandBack([int count = 1]);
+
+ /// Expands the range to include the previous occurrence of [target].
+ ///
+ /// If there is an occurrence of [target] in the characters preceding
+ /// the current range, the stat of the the range is moved to just before
+ /// the last such occurrence.
+ ///
+ /// If there is no such occurrence of [target], the range is not modified.
+ ///
+ /// Returns `true` if there is an occurrence of [target] and `false` if not.
+ /// Notice that if [target] is empty,
+ /// the result is `true` even though the range is not modified.
+ bool expandBackTo(Characters target);
+
+ /// Expands the range to include characters back until the previous [target].
+ ///
+ /// If there is an occurrence of [target] in the characters preceding
+ /// the current range, the start of the the range is moved to just after
+ /// the last such occurrence.
+ ///
+ /// If there is no such occurrence of [target], the end of the range is
+ /// moved to the end of [source].
+ ///
+ /// Returns `true` if there is an occurrence of [target] and `false` if not.
+ bool expandBackUntil(Characters target);
+
+ /// Expands the range with the preceding characters satisfying [test].
+ ///
+ /// Iterates back through the characters preceding the current range
+ /// and includes them into the range until finding a character that
+ /// [test] returns `false` for.
+ void expandBackWhile(bool Function(String) test);
+
+ /// Expands the range back to the start of [source].
+ void expandBackAll();
+
+ /// Collapses the range to its start.
+ ///
+ /// Sets the end of the range to be the same position as the start.
+ /// The new range is empty and positioned at the start of the current range.
+ void collapseToStart();
+
+ /// Collapses to the first occurrence of [target] in the current range.
+ ///
+ /// If there is an occurrence of [target] in the characters of the current
+ /// range, then the new range contains exactly the characters of the
+ /// first such occurrence.
+ ///
+ /// If there is no such occurrence, the range is not changed.
+ ///
+ /// Returns `true` if there is an occurrence of [target] and `false` if not.
+ bool collapseToFirst(Characters target);
+
+ /// Collapses to the last occurrence of [target] in the current range.
+ ///
+ /// If there is an occurrence of [target] in the characters of the current
+ /// range, then the new range contains exactly the characters of the
+ /// last such occurrence.
+ ///
+ /// If there is no such occurrence, the range is not changed.
+ ///
+ /// Returns `true` if there is an occurrence of [target] and `false` if not.
+ bool collapseToLast(Characters target);
+
+ /// Collapses the range to its end.
+ ///
+ /// Sets the start of the range to be the same as its end.
+ /// The new range is an empty range positioned at the end
+ /// of the current range.
+ void collapseToEnd();
+
+ /// Drop the first [count] characters from the range.
+ ///
+ /// Advances the start of the range to after the [count] first characters
+ /// of the range, or as many as are available if
+ /// there are fewer than [count] characters in the current range.
+ ///
+ /// The [count] must not be negative.
+ /// If it is zero, the range is not changed.
+ ///
+ /// Returns `true` if there are [count] characters in the range,
+ /// and `false` if there are fewer.
+ bool dropFirst([int count = 1]);
+
+ /// Drops the first occurrence of [target] in the range.
+ ///
+ /// If the range contains any occurrences of [target],
+ /// then all characters before the end of the first such occurrence
+ /// is removed from the range.
+ /// This advances the start of the range to the end of the
+ /// first occurrence of [target].
+ ///
+ /// If there are no occurrences of [target] in the range,
+ /// the range is not changed.
+ ///
+ /// Returns `true` if there is an occurrence of [target] and `false` if not.
+ bool dropTo(Characters target);
+
+ /// Drops characters from the start of the range until before
+ /// the first occurrence of [target].
+ ///
+ /// If the range contains any occurrences of [target],
+ /// then all characters before the start of the first such occurrence
+ /// is removed from the range.
+ /// This advances the start of the range to the start of the
+ /// first occurrence of [target].
+ ///
+ /// If there are no occurrences of [target] in the range,
+ /// all characters in the range are removed,
+ /// which gives the same effect as [collapseToEnd].
+ ///
+ /// Returns `true` if there is an occurrence of [target] and `false` if not.
+ bool dropUntil(Characters target);
+
+ /// Drops characters from the start of the range while they satisfy [test].
+ ///
+ /// Iterates the characters of the current range from the start
+ /// and removes all the iterated characters until one is
+ /// reached for which [test] returns `false`.
+ /// If on such character is found, all characters are removed,
+ /// which gives the same effect as [collapseToEnd].
+ void dropWhile(bool Function(String) test);
+
+ /// Drop the last [count] characters from the range.
+ ///
+ /// Retracts the end of the range to before the [count] last characters
+ /// of the range, or as many as are available if
+ /// there are fewer than [count] characters in the current range.
+ ///
+ /// The [count] must not be negative.
+ /// If it is zero, the range is not changed.
+ ///
+ /// Returns `true` if there are [count] characters in the range,
+ /// and `false` if there are fewer.
+ bool dropLast([int count = 1]);
+
+ /// Drops the last occurrence of [target] in the range.
+ ///
+ /// If the range contains any occurrences of [target],
+ /// then all characters after the start of the first such occurrence
+ /// is removed from the range.
+ /// This retracts the end of the range to the start of the
+ /// last occurrence of [target].
+ ///
+ /// If there are no occurrences of [target] in the range,
+ /// the range is not changed.
+ ///
+ /// Returns `true` if there is an occurrence of [target] and `false` if not.
+ bool dropBackTo(Characters target);
+
+ /// Drops characters from the end of the range until after
+ /// the last occurrence of [target].
+ ///
+ /// If the range contains any occurrences of [target],
+ /// then all characters after the end of the last such occurrence
+ /// is removed from the range.
+ /// This retracts the end of the range to the end of the
+ /// last occurrence of [target].
+ ///
+ /// If there are no occurrences of [target] in the range,
+ /// all characters in the range are removed,
+ /// which gives the same effect as [collapseToStart].
+ ///
+ /// Returns `true` if there is an occurrence of [target] and `false` if not.
+ bool dropBackUntil(Characters target);
+
+ /// Drops characters from the end of the range while they satisfy [test].
+ ///
+ /// Iterates the characters of the current range backwards from the end
+ /// and removes all the iterated characters until one is
+ /// reached for which [test] returns `false`.
+ /// If on such character is found, all characters are removed,
+ /// which gives the same effect as [collapseToStart].
+ void dropBackWhile(bool Function(String) test);
+
+ /// Replaces the current range with [replacement] and returns the result.
+ ///
+ /// Replaces the current range in [source] with [replacement]
+ /// and returns a range of the resulting characters
+ /// which contains the replacement characters.
+ ///
+ /// The inserted characters may combine with
+ /// the preceding or following code points,
+ /// so that the start and end of the original range
+ /// are no longer grapheme cluster boundaries.
+ /// In that case, the returned range may extend into into the code points
+ /// before and after the original range.
+ CharacterRange replaceRange(Characters replacement);
+
+ /// Replaces [pattern] in the current range with [replacement].
+ ///
+ /// Replaces all occurrences of [pattern] in the current range with
+ /// [replacement], unless they overlap with an earlier occurrence of
+ /// [pattern] which was replaced.
+ /// Then returns a range on the resulting characters
+ /// which contains all inserted replacement characters
+ /// and any remaining characters of the original range.
+ ///
+ /// The inserted characters may combine with
+ /// the preceding or following code points,
+ /// so that the start and end of the original range
+ /// are no longer grapheme cluster boundaries.
+ /// In that case, the returned range may extend into into the code points
+ /// before and after the original range.
+ ///
+ /// Returns `null` if there are no occurrences of [pattern]
+ /// in the current range.
+ CharacterRange? replaceAll(Characters pattern, Characters replacement);
+
+ /// Splits the current range of characters at each occurrence of [pattern].
+ ///
+ /// Returns a lazy iterable of character ranges that were separated by
+ /// [pattern].
+ /// Each provided character range object is new
+ /// and unrelated to this character range
+ /// The iterable has *at most* [maxParts] elements if a positive [maxParts]
+ /// is supplied.
+ ///
+ /// Finds each occurrence of [pattern] in the range, which does not overlap
+ /// with a previously found occurrence, then the non-matched characters
+ /// of the range before, after and between the matches are provided
+ /// in first-to-last position order.
+ ///
+ /// If [pattern] is empty, the range is split into separate characters,
+ /// and no leading or trailing empty ranges are provided unless the
+ /// range itself is empty, in which case a single empty range is the
+ /// only result range.
+ /// Otherwise a range starting or ending with [pattern] will cause
+ /// an empty range to be emitted at the start or end.
+ ///
+ /// If [maxParts] is provided and greater than zero,
+ /// only the first `maxParts - 1` occurrences of [pattern] are found
+ /// and split at.
+ /// Any further occurrences will be included in the last part.
+ ///
+ /// Example:
+ /// ```dart
+ /// var c = 'abracadabra'.characters.dropFirst().dropLast();
+ /// // c is 'bracadabr'.
+ /// var parts = c.split('a'.characters, 3).toList();
+ /// print(parts); // [br, c, dabr]
+ /// ```
+ /// If there are fewer than `maxParts - 1` occurrences of [pattern],
+ /// then the characters are split at all occurrences.
+ /// If [maxParts] is zero or negative, it is ignored and the result
+ /// is split at all occurrences of [pattern].
+ Iterable<CharacterRange> split(Characters pattern, [int maxParts = 0]);
+
+ /// Replaces the first occurrence of [pattern] with [replacement].
+ ///
+ /// Finds the first occurrence of [pattern] in the current range,
+ /// then replaces that occurrence with [replacement].
+ /// Then returns a range on the resulting characters
+ /// which contains the inserted replacement characters
+ /// and any remaining characters of the original range.
+ ///
+ /// The inserted characters may combine with
+ /// the preceding or following code points,
+ /// so that the start and end of the original range
+ /// are no longer grapheme cluster boundaries.
+ /// In that case, the returned range may extend into into the code points
+ /// before and after the original range.
+ ///
+ /// Returns `null` if there are no occurrences of [pattern]
+ /// in the current range.
+ CharacterRange? replaceFirst(Characters pattern, Characters replacement);
+
+ /// Whether the current range starts with [characters].
+ ///
+ /// Returns `true` if the characters of the current range starts with
+ /// [characters], `false` if not.
+ bool startsWith(Characters characters);
+
+ /// Whether the current range ends with [characters].
+ ///
+ /// Returns `true` if the characters of the current range ends with
+ /// [characters], `false` if not.
+ bool endsWith(Characters characters);
+
+ /// Whether the current range is preceded by [characters].
+ ///
+ /// Returns `true` if the characters immediately preceding the current
+ /// range are [characters], and `false` if not.
+ bool isPrecededBy(Characters characters);
+
+ /// Whether the current range is followed by [characters].
+ ///
+ /// Returns `true` if the characters immediately following the current
+ /// range are [characters], and `false` if not.
+ bool isFollowedBy(Characters characters);
+}
diff --git a/pkgs/characters/lib/src/characters_impl.dart b/pkgs/characters/lib/src/characters_impl.dart
new file mode 100644
index 0000000..39a3b5d
--- /dev/null
+++ b/pkgs/characters/lib/src/characters_impl.dart
@@ -0,0 +1,1119 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'characters.dart';
+import 'grapheme_clusters/breaks.dart';
+import 'grapheme_clusters/constants.dart';
+import 'grapheme_clusters/table.dart';
+
+/// The grapheme clusters of a string.
+///
+/// Backed by a single string.
+final class StringCharacters extends Iterable<String> implements Characters {
+ @override
+ final String string;
+
+ const StringCharacters(this.string);
+
+ @override
+ CharacterRange get iterator => StringCharacterRange._(string, 0, 0);
+
+ @override
+ CharacterRange get iteratorAtEnd =>
+ StringCharacterRange._(string, string.length, string.length);
+
+ StringCharacterRange get _rangeAll =>
+ StringCharacterRange._(string, 0, string.length);
+
+ @override
+ String get first => string.isEmpty
+ ? throw StateError('No element')
+ : string.substring(
+ 0, Breaks(string, 0, string.length, stateSoTNoBreak).nextBreak());
+
+ @override
+ String get last => string.isEmpty
+ ? throw StateError('No element')
+ : string.substring(
+ BackBreaks(string, string.length, 0, stateEoTNoBreak).nextBreak());
+
+ @override
+ String get single {
+ if (string.isEmpty) throw StateError('No element');
+ var firstEnd =
+ Breaks(string, 0, string.length, stateSoTNoBreak).nextBreak();
+ if (firstEnd == string.length) return string;
+ throw StateError('Too many elements');
+ }
+
+ @override
+ bool get isEmpty => string.isEmpty;
+
+ @override
+ bool get isNotEmpty => string.isNotEmpty;
+
+ @override
+ int get length {
+ if (string.isEmpty) return 0;
+ var brk = Breaks(string, 0, string.length, stateSoTNoBreak);
+ var length = 0;
+ while (brk.nextBreak() >= 0) {
+ length++;
+ }
+ return length;
+ }
+
+ @override
+ Iterable<T> whereType<T>() {
+ Iterable<Object?> self = this;
+ if (self is Iterable<T>) {
+ return self.map<T>((x) => x);
+ }
+ return Iterable<T>.empty();
+ }
+
+ @override
+ String join([String separator = '']) {
+ if (separator == '') return string;
+ return _explodeReplace(string, 0, string.length, separator, '');
+ }
+
+ @override
+ String lastWhere(bool Function(String element) test,
+ {String Function()? orElse}) {
+ var cursor = string.length;
+ var brk = BackBreaks(string, cursor, 0, stateEoTNoBreak);
+ var next = 0;
+ while ((next = brk.nextBreak()) >= 0) {
+ var current = string.substring(next, cursor);
+ if (test(current)) return current;
+ cursor = next;
+ }
+ if (orElse != null) return orElse();
+ throw StateError('No element');
+ }
+
+ @override
+ String elementAt(int index) {
+ RangeError.checkNotNegative(index, 'index');
+ var count = 0;
+ if (string.isNotEmpty) {
+ var breaks = Breaks(string, 0, string.length, stateSoTNoBreak);
+ var start = 0;
+ var end = 0;
+ while ((end = breaks.nextBreak()) >= 0) {
+ if (count == index) return string.substring(start, end);
+ count++;
+ start = end;
+ }
+ }
+ throw RangeError.index(index, this, 'index', null, count);
+ }
+
+ @override
+ // ignore: avoid_renaming_method_parameters
+ bool contains(Object? singleCharacterString) {
+ if (singleCharacterString is! String) return false;
+ if (singleCharacterString.isEmpty) return false;
+ var next = Breaks(singleCharacterString, 0, singleCharacterString.length,
+ stateSoTNoBreak)
+ .nextBreak();
+ if (next != singleCharacterString.length) return false;
+ // [singleCharacterString] is single grapheme cluster.
+ return _indexOf(string, singleCharacterString, 0, string.length) >= 0;
+ }
+
+ @override
+ bool startsWith(Characters characters) {
+ var length = string.length;
+ var otherString = characters.string;
+ if (otherString.isEmpty) return true;
+ return string.startsWith(otherString) &&
+ isGraphemeClusterBoundary(string, 0, length, otherString.length);
+ }
+
+ @override
+ bool endsWith(Characters characters) {
+ var length = string.length;
+ var otherString = characters.string;
+ if (otherString.isEmpty) return true;
+ var otherLength = otherString.length;
+ var start = string.length - otherLength;
+ return start >= 0 &&
+ string.startsWith(otherString, start) &&
+ isGraphemeClusterBoundary(string, 0, length, start);
+ }
+
+ @override
+ Characters replaceAll(Characters pattern, Characters replacement) =>
+ _rangeAll.replaceAll(pattern, replacement)?.source ?? this;
+
+ @override
+ Characters replaceFirst(Characters pattern, Characters replacement) =>
+ _rangeAll.replaceFirst(pattern, replacement)?.source ?? this;
+
+ @override
+ Iterable<Characters> split(Characters pattern, [int maxParts = 0]) sync* {
+ if (maxParts == 1 || string.isEmpty) {
+ yield this;
+ return;
+ }
+ var patternString = pattern.string;
+ var start = 0;
+ if (patternString.isNotEmpty) {
+ do {
+ var match = _indexOf(string, patternString, start, string.length);
+ if (match < 0) break;
+ yield StringCharacters(string.substring(start, match));
+ start = match + patternString.length;
+ maxParts--;
+ } while (maxParts != 1);
+ } else {
+ // Empty pattern. Split on internal boundaries only.
+ var breaks = Breaks(string, 0, string.length, stateSoTNoBreak);
+ do {
+ var match = breaks.nextBreak();
+ if (match < 0) return;
+ yield StringCharacters(string.substring(start, match));
+ start = match;
+ maxParts--;
+ } while (maxParts != 1);
+ if (start == string.length) return;
+ }
+ yield StringCharacters(string.substring(start));
+ }
+
+ @override
+ bool containsAll(Characters characters) =>
+ _indexOf(string, characters.string, 0, string.length) >= 0;
+
+ /// Returns the break position of the [count]'th break.
+ ///
+ /// Starts from the index [cursor] in [string].
+ /// Use [breaks], which is assumed to be at [cursor],
+ /// if available.
+ ///
+ /// Returns `string.length` if there are less than [count]
+ /// characters left.
+ int _skipIndices(int count, int cursor, Breaks? breaks) {
+ if (count == 0 || cursor == string.length) return cursor;
+ breaks ??= Breaks(string, cursor, string.length, stateSoTNoBreak);
+ do {
+ var nextBreak = breaks.nextBreak();
+ if (nextBreak < 0) break;
+ cursor = nextBreak;
+ } while (--count > 0);
+ return cursor;
+ }
+
+ @override
+ Characters skip(int count) {
+ RangeError.checkNotNegative(count, 'count');
+ return _skip(count);
+ }
+
+ Characters _skip(int count) {
+ var start = _skipIndices(count, 0, null);
+ if (start == string.length) return Characters.empty;
+ return StringCharacters(string.substring(start));
+ }
+
+ @override
+ Characters take(int count) {
+ RangeError.checkNotNegative(count, 'count');
+ return _take(count);
+ }
+
+ Characters _take(int count) {
+ var end = _skipIndices(count, 0, null);
+ if (end == string.length) return this;
+ return StringCharacters(string.substring(0, end));
+ }
+
+ @override
+ Characters getRange(int start, [int? end]) {
+ RangeError.checkNotNegative(start, 'start');
+ if (end == null) return _skip(start);
+ if (end < start) throw RangeError.range(end, start, null, 'end');
+ if (end == start) return Characters.empty;
+ if (start == 0) return _take(end);
+ if (string.isEmpty) return this;
+ var breaks = Breaks(string, 0, string.length, stateSoTNoBreak);
+ var startIndex = _skipIndices(start, 0, breaks);
+ if (startIndex == string.length) return Characters.empty;
+ var endIndex = _skipIndices(end - start, start, breaks);
+ return StringCharacters(string.substring(startIndex, endIndex));
+ }
+
+ @override
+ Characters characterAt(int position) {
+ var breaks = Breaks(string, 0, string.length, stateSoTNoBreak);
+ var start = 0;
+
+ while (position > 0) {
+ position--;
+ start = breaks.nextBreak();
+ if (start < 0) throw StateError('No element');
+ }
+ var end = breaks.nextBreak();
+ if (end < 0) throw StateError('No element');
+ if (start == 0 && end == string.length) return this;
+ return StringCharacters(string.substring(start, end));
+ }
+
+ @override
+ Characters skipWhile(bool Function(String) test) {
+ if (string.isNotEmpty) {
+ var stringLength = string.length;
+ var breaks = Breaks(string, 0, stringLength, stateSoTNoBreak);
+ var index = 0;
+ var startIndex = 0;
+ while ((index = breaks.nextBreak()) >= 0) {
+ if (!test(string.substring(startIndex, index))) {
+ if (startIndex == 0) return this;
+ if (startIndex == stringLength) return Characters.empty;
+ return StringCharacters(string.substring(startIndex));
+ }
+ startIndex = index;
+ }
+ }
+ return Characters.empty;
+ }
+
+ @override
+ Characters takeWhile(bool Function(String) test) {
+ if (string.isNotEmpty) {
+ var breaks = Breaks(string, 0, string.length, stateSoTNoBreak);
+ var index = 0;
+ var endIndex = 0;
+ while ((index = breaks.nextBreak()) >= 0) {
+ if (!test(string.substring(endIndex, index))) {
+ if (endIndex == 0) return Characters.empty;
+ return StringCharacters(string.substring(0, endIndex));
+ }
+ endIndex = index;
+ }
+ }
+ return this;
+ }
+
+ @override
+ Characters where(bool Function(String) test) {
+ var string = super.where(test).join();
+ if (string.isEmpty) return Characters.empty;
+ return StringCharacters(string);
+ }
+
+ @override
+ Characters operator +(Characters characters) =>
+ StringCharacters(string + characters.string);
+
+ @override
+ Characters skipLast(int count) {
+ RangeError.checkNotNegative(count, 'count');
+ if (count == 0) return this;
+ if (string.isNotEmpty) {
+ var breaks = BackBreaks(string, string.length, 0, stateEoTNoBreak);
+ var endIndex = string.length;
+ while (count > 0) {
+ var index = breaks.nextBreak();
+ if (index >= 0) {
+ endIndex = index;
+ count--;
+ } else {
+ return Characters.empty;
+ }
+ }
+ if (endIndex > 0) return StringCharacters(string.substring(0, endIndex));
+ }
+ return Characters.empty;
+ }
+
+ @override
+ Characters skipLastWhile(bool Function(String) test) {
+ if (string.isNotEmpty) {
+ var breaks = BackBreaks(string, string.length, 0, stateEoTNoBreak);
+ var index = 0;
+ var end = string.length;
+ while ((index = breaks.nextBreak()) >= 0) {
+ if (!test(string.substring(index, end))) {
+ if (end == string.length) return this;
+ return end == 0
+ ? Characters.empty
+ : StringCharacters(string.substring(0, end));
+ }
+ end = index;
+ }
+ }
+ return Characters.empty;
+ }
+
+ @override
+ Characters takeLast(int count) {
+ RangeError.checkNotNegative(count, 'count');
+ if (count == 0) return Characters.empty;
+ if (string.isNotEmpty) {
+ var breaks = BackBreaks(string, string.length, 0, stateEoTNoBreak);
+ var startIndex = string.length;
+ while (count > 0) {
+ var index = breaks.nextBreak();
+ if (index >= 0) {
+ startIndex = index;
+ count--;
+ } else {
+ return this;
+ }
+ }
+ if (startIndex > 0) {
+ return StringCharacters(string.substring(startIndex));
+ }
+ }
+ return this;
+ }
+
+ @override
+ Characters takeLastWhile(bool Function(String) test) {
+ if (string.isNotEmpty) {
+ var breaks = BackBreaks(string, string.length, 0, stateEoTNoBreak);
+ var index = 0;
+ var start = string.length;
+ while ((index = breaks.nextBreak()) >= 0) {
+ if (!test(string.substring(index, start))) {
+ if (start == string.length) return Characters.empty;
+ return StringCharacters(string.substring(start));
+ }
+ start = index;
+ }
+ }
+ return this;
+ }
+
+ @override
+ Characters toLowerCase() => StringCharacters(string.toLowerCase());
+
+ @override
+ Characters toUpperCase() => StringCharacters(string.toUpperCase());
+
+ @override
+ bool operator ==(Object other) =>
+ other is Characters && string == other.string;
+
+ @override
+ int get hashCode => string.hashCode;
+
+ @override
+ String toString() => string;
+
+ @override
+ CharacterRange? findFirst(Characters characters) {
+ var range = _rangeAll;
+ if (range.collapseToFirst(characters)) return range;
+ return null;
+ }
+
+ @override
+ CharacterRange? findLast(Characters characters) {
+ var range = _rangeAll;
+ if (range.collapseToLast(characters)) return range;
+ return null;
+ }
+}
+
+/// A [CharacterRange] on a single string.
+class StringCharacterRange implements CharacterRange {
+ /// The source string.
+ final String _string;
+
+ /// Start index of range in string.
+ ///
+ /// The index is a code unit index in the [String].
+ /// It is always at a grapheme cluster boundary.
+ int _start;
+
+ /// End index of range in string.
+ ///
+ /// The index is a code unit index in the [String].
+ /// It is always at a grapheme cluster boundary.
+ int _end;
+
+ /// The [current] value is created lazily and cached to avoid repeated
+ /// or unnecessary string allocation.
+ String? _currentCache;
+
+ StringCharacterRange(String string) : this._(string, 0, 0);
+
+ factory StringCharacterRange.at(String string, int startIndex,
+ [int? endIndex]) {
+ RangeError.checkValidRange(
+ startIndex, endIndex, string.length, 'startIndex', 'endIndex');
+ return _expandRange(string, startIndex, endIndex ?? startIndex);
+ }
+
+ StringCharacterRange._(this._string, this._start, this._end);
+
+ /// Changes the current range.
+ ///
+ /// Resets all cached state.
+ void _move(int start, int end) {
+ _start = start;
+ _end = end;
+ _currentCache = null;
+ }
+
+ /// Creates a [Breaks] from [_end] to `_string.length`.
+ ///
+ /// Uses information stored in the state for cases where the next character
+ /// has already been seen.
+ Breaks _breaksFromEnd() {
+ return Breaks(_string, _end, _string.length, stateSoTNoBreak);
+ }
+
+ /// Creates a [Breaks] from string start to [_start].
+ ///
+ /// Uses information stored in the state for cases where the previous
+ /// character has already been seen.
+ BackBreaks _backBreaksFromStart() {
+ return BackBreaks(_string, _start, 0, stateEoTNoBreak);
+ }
+
+ @override
+ String get current => _currentCache ??= _string.substring(_start, _end);
+
+ @override
+ bool moveNext([int count = 1]) => _advanceEnd(count, _end);
+
+ bool _advanceEnd(int count, int newStart) {
+ if (count > 0) {
+ var state = stateSoTNoBreak;
+ var index = _end;
+ while (index < _string.length) {
+ var char = _string.codeUnitAt(index);
+ var category = categoryControl;
+ var nextIndex = index + 1;
+ if (char & 0xFC00 != 0xD800) {
+ category = low(char);
+ } else if (nextIndex < _string.length) {
+ var nextChar = _string.codeUnitAt(nextIndex);
+ if (nextChar & 0xFC00 == 0xDC00) {
+ nextIndex += 1;
+ category = high(char, nextChar);
+ }
+ }
+ state = move(state, category);
+ if (state & maskBreak != flagNoBreak && --count == 0) {
+ _move(newStart, index);
+ return true;
+ }
+ index = nextIndex;
+ }
+ _move(newStart, _string.length);
+ return count == 1 && state != stateSoTNoBreak;
+ } else if (count == 0) {
+ _move(newStart, _end);
+ return true;
+ } else {
+ throw RangeError.range(count, 0, null, 'count');
+ }
+ }
+
+ bool _moveNextPattern(String patternString, int start, int end) {
+ var offset = _indexOf(_string, patternString, start, end);
+ if (offset >= 0) {
+ _move(offset, offset + patternString.length);
+ return true;
+ }
+ return false;
+ }
+
+ @override
+ bool moveBack([int count = 1]) => _retractStart(count, _start);
+
+ bool _retractStart(int count, int newEnd) {
+ RangeError.checkNotNegative(count, 'count');
+ var breaks = _backBreaksFromStart();
+ var start = _start;
+ while (count > 0) {
+ var nextBreak = breaks.nextBreak();
+ if (nextBreak >= 0) {
+ start = nextBreak;
+ } else {
+ break;
+ }
+ count--;
+ }
+ _move(start, newEnd);
+ return count == 0;
+ }
+
+ bool _movePreviousPattern(String patternString, int start, int end) {
+ var offset = _lastIndexOf(_string, patternString, start, end);
+ if (offset >= 0) {
+ _move(offset, offset + patternString.length);
+ return true;
+ }
+ return false;
+ }
+
+ @override
+ Iterable<int> get utf16CodeUnits => _string.codeUnits.getRange(_start, _end);
+
+ @override
+ Runes get runes => Runes(current);
+
+ @override
+ CharacterRange copy() {
+ return StringCharacterRange._(_string, _start, _end);
+ }
+
+ @override
+ void collapseToEnd() {
+ _move(_end, _end);
+ }
+
+ @override
+ void collapseToStart() {
+ _move(_start, _start);
+ }
+
+ @override
+ bool dropFirst([int count = 1]) {
+ RangeError.checkNotNegative(count, 'count');
+ if (_start == _end) return count == 0;
+ var breaks = Breaks(_string, _start, _end, stateSoTNoBreak);
+ while (count > 0) {
+ var nextBreak = breaks.nextBreak();
+ if (nextBreak >= 0) {
+ _start = nextBreak;
+ _currentCache = null;
+ count--;
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @override
+ bool dropTo(Characters target) {
+ if (_start == _end) return target.isEmpty;
+ var targetString = target.string;
+ var index = _indexOf(_string, targetString, _start, _end);
+ if (index >= 0) {
+ _move(index + targetString.length, _end);
+ return true;
+ }
+ return false;
+ }
+
+ @override
+ bool dropUntil(Characters target) {
+ if (_start == _end) return target.isEmpty;
+ var targetString = target.string;
+ var index = _indexOf(_string, targetString, _start, _end);
+ if (index >= 0) {
+ _move(index, _end);
+ return true;
+ }
+ _move(_end, _end);
+ return false;
+ }
+
+ @override
+ void dropWhile(bool Function(String) test) {
+ if (_start == _end) return;
+ var breaks = Breaks(_string, _start, _end, stateSoTNoBreak);
+ var cursor = _start;
+ var next = 0;
+ while ((next = breaks.nextBreak()) >= 0) {
+ if (!test(_string.substring(cursor, next))) {
+ break;
+ }
+ cursor = next;
+ }
+ _move(cursor, _end);
+ }
+
+ @override
+ bool dropLast([int count = 1]) {
+ RangeError.checkNotNegative(count, 'count');
+ var breaks = BackBreaks(_string, _end, _start, stateEoTNoBreak);
+ while (count > 0) {
+ var nextBreak = breaks.nextBreak();
+ if (nextBreak >= 0) {
+ _end = nextBreak;
+ _currentCache = null;
+ count--;
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @override
+ bool dropBackTo(Characters target) {
+ if (_start == _end) return target.isEmpty;
+ var targetString = target.string;
+ var index = _lastIndexOf(_string, targetString, _start, _end);
+ if (index >= 0) {
+ _move(_start, index);
+ return true;
+ }
+ return false;
+ }
+
+ @override
+ bool dropBackUntil(Characters target) {
+ if (_start == _end) return target.isEmpty;
+ var targetString = target.string;
+ var index = _lastIndexOf(_string, targetString, _start, _end);
+ if (index >= 0) {
+ _move(_start, index + targetString.length);
+ return true;
+ }
+ _move(_start, _start);
+ return false;
+ }
+
+ @override
+ void dropBackWhile(bool Function(String) test) {
+ if (_start == _end) return;
+ var breaks = BackBreaks(_string, _end, _start, stateEoTNoBreak);
+ var cursor = _end;
+ var next = 0;
+ while ((next = breaks.nextBreak()) >= 0) {
+ if (!test(_string.substring(next, cursor))) {
+ break;
+ }
+ cursor = next;
+ }
+ _move(_start, cursor);
+ }
+
+ @override
+ bool expandNext([int count = 1]) => _advanceEnd(count, _start);
+
+ @override
+ bool expandTo(Characters target) {
+ var targetString = target.string;
+ var index = _indexOf(_string, targetString, _end, _string.length);
+ if (index >= 0) {
+ _move(_start, index + targetString.length);
+ return true;
+ }
+ return false;
+ }
+
+ @override
+ void expandWhile(bool Function(String character) test) {
+ var breaks = _breaksFromEnd();
+ var cursor = _end;
+ var next = 0;
+ while ((next = breaks.nextBreak()) >= 0) {
+ if (!test(_string.substring(cursor, next))) {
+ break;
+ }
+ cursor = next;
+ }
+ _move(_start, cursor);
+ }
+
+ @override
+ void expandAll() {
+ _move(_start, _string.length);
+ }
+
+ @override
+ bool expandBack([int count = 1]) => _retractStart(count, _end);
+
+ @override
+ bool expandBackTo(Characters target) {
+ var targetString = target.string;
+ var index = _lastIndexOf(_string, targetString, 0, _start);
+ if (index >= 0) {
+ _move(index, _end);
+ return true;
+ }
+ return false;
+ }
+
+ @override
+ void expandBackWhile(bool Function(String character) test) {
+ var breaks = _backBreaksFromStart();
+ var cursor = _start;
+ var next = 0;
+ while ((next = breaks.nextBreak()) >= 0) {
+ if (!test(_string.substring(next, cursor))) {
+ _move(cursor, _end);
+ return;
+ }
+ cursor = next;
+ }
+ _move(0, _end);
+ }
+
+ @override
+ bool expandBackUntil(Characters target) {
+ return _retractStartUntil(target.string, _end);
+ }
+
+ @override
+ void expandBackAll() {
+ _move(0, _end);
+ }
+
+ @override
+ bool expandUntil(Characters target) {
+ return _advanceEndUntil(target.string, _start);
+ }
+
+ @override
+ bool get isEmpty => _start == _end;
+
+ @override
+ bool get isNotEmpty => _start != _end;
+
+ @override
+ bool moveBackUntil(Characters target) {
+ var targetString = target.string;
+ return _retractStartUntil(targetString, _start);
+ }
+
+ bool _retractStartUntil(String targetString, int newEnd) {
+ var index = _lastIndexOf(_string, targetString, 0, _start);
+ if (index >= 0) {
+ _move(index + targetString.length, newEnd);
+ return true;
+ }
+ _move(0, newEnd);
+ return false;
+ }
+
+ @override
+ bool collapseToFirst(Characters target) {
+ return _moveNextPattern(target.string, _start, _end);
+ }
+
+ @override
+ bool collapseToLast(Characters target) {
+ return _movePreviousPattern(target.string, _start, _end);
+ }
+
+ @override
+ bool moveUntil(Characters target) {
+ var targetString = target.string;
+ return _advanceEndUntil(targetString, _end);
+ }
+
+ bool _advanceEndUntil(String targetString, int newStart) {
+ var index = _indexOf(_string, targetString, _end, _string.length);
+ if (index >= 0) {
+ _move(newStart, index);
+ return true;
+ }
+ _move(newStart, _string.length);
+ return false;
+ }
+
+ @override
+ CharacterRange? replaceFirst(Characters pattern, Characters replacement) {
+ var patternString = pattern.string;
+ var replacementString = replacement.string;
+ String replaced;
+ if (patternString.isEmpty) {
+ replaced = _string.replaceRange(_start, _start, replacementString);
+ } else {
+ var index = _indexOf(_string, patternString, _start, _end);
+ if (index >= 0) {
+ replaced = _string.replaceRange(
+ index, index + patternString.length, replacementString);
+ } else {
+ return null;
+ }
+ }
+ var newEnd = replaced.length - _string.length + _end;
+ return _expandRange(replaced, _start, newEnd);
+ }
+
+ @override
+ CharacterRange? replaceAll(Characters pattern, Characters replacement) {
+ var patternString = pattern.string;
+ var replacementString = replacement.string;
+ if (patternString.isEmpty) {
+ var replaced = _explodeReplace(
+ _string, _start, _end, replacementString, replacementString);
+ var newEnd = replaced.length - (_string.length - _end);
+ return _expandRange(replaced, _start, newEnd);
+ }
+ if (_start == _end) return null;
+ var start = 0;
+ var cursor = _start;
+ StringBuffer? buffer;
+ while ((cursor = _indexOf(_string, patternString, cursor, _end)) >= 0) {
+ (buffer ??= StringBuffer())
+ ..write(_string.substring(start, cursor))
+ ..write(replacementString);
+ cursor += patternString.length;
+ start = cursor;
+ }
+ if (buffer == null) return null;
+ buffer.write(_string.substring(start));
+ var replaced = buffer.toString();
+ var newEnd = replaced.length - (_string.length - _end);
+ return _expandRange(replaced, _start, newEnd);
+ }
+
+ @override
+ CharacterRange replaceRange(Characters replacement) {
+ var replacementString = replacement.string;
+ var resultString = _string.replaceRange(_start, _end, replacementString);
+ return _expandRange(
+ resultString, _start, _start + replacementString.length);
+ }
+
+ /// Expands a range if its start or end are not grapheme cluster boundaries.
+ ///
+ /// Low-level function which does not validate its input. Assume that
+ /// 0 <= [start] <= [end] <= `string.length`.
+ static StringCharacterRange _expandRange(String string, int start, int end) {
+ start = previousBreak(string, 0, string.length, start);
+ if (end != start) {
+ end = nextBreak(string, 0, string.length, end);
+ }
+ return StringCharacterRange._(string, start, end);
+ }
+
+ @override
+ Characters get source => Characters(_string);
+
+ @override
+ bool startsWith(Characters characters) {
+ return _startsWith(_start, _end, characters.string);
+ }
+
+ @override
+ bool endsWith(Characters characters) {
+ return _endsWith(_start, _end, characters.string);
+ }
+
+ @override
+ bool isFollowedBy(Characters characters) {
+ return _startsWith(_end, _string.length, characters.string);
+ }
+
+ @override
+ bool isPrecededBy(Characters characters) {
+ return _endsWith(0, _start, characters.string);
+ }
+
+ bool _endsWith(int start, int end, String string) {
+ var length = string.length;
+ var stringStart = end - length;
+ return stringStart >= start &&
+ _string.startsWith(string, stringStart) &&
+ isGraphemeClusterBoundary(_string, start, end, stringStart);
+ }
+
+ bool _startsWith(int start, int end, String string) {
+ var length = string.length;
+ var stringEnd = start + length;
+ return stringEnd <= end &&
+ _string.startsWith(string, start) &&
+ isGraphemeClusterBoundary(_string, start, end, stringEnd);
+ }
+
+ @override
+ bool moveBackTo(Characters target) {
+ var targetString = target.string;
+ var index = _lastIndexOf(_string, targetString, 0, _start);
+ if (index >= 0) {
+ _move(index, index + targetString.length);
+ return true;
+ }
+ return false;
+ }
+
+ @override
+ bool moveTo(Characters target) {
+ var targetString = target.string;
+ var index = _indexOf(_string, targetString, _end, _string.length);
+ if (index >= 0) {
+ _move(index, index + targetString.length);
+ return true;
+ }
+ return false;
+ }
+
+ @override
+ Characters get charactersAfter => StringCharacters(_string.substring(_end));
+
+ @override
+ Characters get charactersBefore =>
+ StringCharacters(_string.substring(0, _start));
+
+ @override
+ Characters get currentCharacters => StringCharacters(current);
+
+ @override
+ void moveBackAll() {
+ _move(0, _start);
+ }
+
+ @override
+ void moveNextAll() {
+ _move(_end, _string.length);
+ }
+
+ @override
+ String get stringAfter => _string.substring(_end);
+
+ @override
+ int get stringAfterLength => _string.length - _end;
+
+ @override
+ String get stringBefore => _string.substring(0, _start);
+
+ @override
+ int get stringBeforeLength => _start;
+
+ @override
+ Iterable<CharacterRange> split(Characters pattern, [int maxParts = 0]) sync* {
+ if (maxParts == 1 || _start == _end) {
+ yield this;
+ return;
+ }
+ var patternString = pattern.string;
+ var start = _start;
+ if (patternString.isNotEmpty) {
+ do {
+ var match = _indexOf(_string, patternString, start, _end);
+ if (match < 0) break;
+ yield StringCharacterRange._(_string, start, match);
+ start = match + patternString.length;
+ maxParts--;
+ } while (maxParts != 1);
+ yield StringCharacterRange._(_string, start, _end);
+ } else {
+ // Empty pattern. Split on internal boundaries only.
+ var breaks = Breaks(_string, _start, _end, stateSoTNoBreak);
+ do {
+ var match = breaks.nextBreak();
+ if (match < 0) return;
+ yield StringCharacterRange._(_string, start, match);
+ start = match;
+ maxParts--;
+ } while (maxParts != 1);
+ if (start < _end) {
+ yield StringCharacterRange._(_string, start, _end);
+ }
+ }
+ }
+}
+
+String _explodeReplace(String string, int start, int end,
+ String internalReplacement, String outerReplacement) {
+ if (start == end) {
+ return string.replaceRange(start, start, outerReplacement);
+ }
+ var buffer = StringBuffer(string.substring(0, start));
+ var breaks = Breaks(string, start, end, stateSoTNoBreak);
+ var index = 0;
+ var replacement = outerReplacement;
+ while ((index = breaks.nextBreak()) >= 0) {
+ buffer
+ ..write(replacement)
+ ..write(string.substring(start, index));
+ start = index;
+ replacement = internalReplacement;
+ }
+ buffer
+ ..write(outerReplacement)
+ ..write(string.substring(end));
+ return buffer.toString();
+}
+
+/// Finds [pattern] in the range from [start] to [end].
+///
+/// Both [start] and [end] are grapheme cluster boundaries in the
+/// [source] string.
+int _indexOf(String source, String pattern, int start, int end) {
+ var patternLength = pattern.length;
+ if (patternLength == 0) return start;
+ // Any start position after realEnd won't fit the pattern before end.
+ var realEnd = end - patternLength;
+ if (realEnd < start) return -1;
+ // Use indexOf if what we can overshoot is
+ // less than twice as much as what we have left to search.
+ var rest = source.length - realEnd;
+ if (rest <= (realEnd - start) * 2) {
+ var index = 0;
+ while (start < realEnd && (index = source.indexOf(pattern, start)) >= 0) {
+ if (index > realEnd) return -1;
+ if (isGraphemeClusterBoundary(source, start, end, index) &&
+ isGraphemeClusterBoundary(
+ source, start, end, index + patternLength)) {
+ return index;
+ }
+ start = index + 1;
+ }
+ return -1;
+ }
+ return _gcIndexOf(source, pattern, start, end);
+}
+
+int _gcIndexOf(String source, String pattern, int start, int end) {
+ var breaks = Breaks(source, start, end, stateSoT);
+ var index = 0;
+ while ((index = breaks.nextBreak()) >= 0) {
+ var endIndex = index + pattern.length;
+ if (endIndex > end) break;
+ if (source.startsWith(pattern, index) &&
+ isGraphemeClusterBoundary(source, start, end, endIndex)) {
+ return index;
+ }
+ }
+ return -1;
+}
+
+/// Finds pattern in the range from [start] to [end].
+/// Both [start] and [end] are grapheme cluster boundaries in the
+/// [source] string.
+int _lastIndexOf(String source, String pattern, int start, int end) {
+ var patternLength = pattern.length;
+ if (patternLength == 0) return end;
+ // Start of pattern must be in range [start .. end - patternLength].
+ var realEnd = end - patternLength;
+ if (realEnd < start) return -1;
+ // If the range from 0 to start is no more than double the range from
+ // start to end, use lastIndexOf.
+ if (realEnd * 2 > start) {
+ var index = 0;
+ while (realEnd >= start &&
+ (index = source.lastIndexOf(pattern, realEnd)) >= 0) {
+ if (index < start) return -1;
+ if (isGraphemeClusterBoundary(source, start, end, index) &&
+ isGraphemeClusterBoundary(
+ source, start, end, index + patternLength)) {
+ return index;
+ }
+ realEnd = index - 1;
+ }
+ return -1;
+ }
+ return _gcLastIndexOf(source, pattern, start, end);
+}
+
+int _gcLastIndexOf(String source, String pattern, int start, int end) {
+ var breaks = BackBreaks(source, end, start, stateEoT);
+ var index = 0;
+ while ((index = breaks.nextBreak()) >= 0) {
+ var startIndex = index - pattern.length;
+ if (startIndex < start) break;
+ if (source.startsWith(pattern, startIndex) &&
+ isGraphemeClusterBoundary(source, start, end, startIndex)) {
+ return startIndex;
+ }
+ }
+ return -1;
+}
diff --git a/pkgs/characters/lib/src/extensions.dart b/pkgs/characters/lib/src/extensions.dart
new file mode 100644
index 0000000..b661daf
--- /dev/null
+++ b/pkgs/characters/lib/src/extensions.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'characters.dart';
+
+extension StringCharacters on String {
+ /// The [Characters] of this string.
+ Characters get characters => Characters(this);
+}
diff --git a/pkgs/characters/lib/src/grapheme_clusters/breaks.dart b/pkgs/characters/lib/src/grapheme_clusters/breaks.dart
new file mode 100644
index 0000000..0e69b6a
--- /dev/null
+++ b/pkgs/characters/lib/src/grapheme_clusters/breaks.dart
@@ -0,0 +1,421 @@
+// BSD-style license that can be found in the LICENSE file.
+
+/// API for accessing the generated state and category tables.
+///
+/// Use only [Breaks], [BackBreaks] and [nextBreak] from files other than this.
+/// Do not use any function from `table.dart` directly.
+/// (In both cases this package's own testing is exempted.)
+
+library;
+
+import 'constants.dart';
+import 'table.dart';
+
+/// Iterates grapheme cluster breaks of a string.
+///
+/// Iterates the grapheme cluster breaks of the substring of
+/// [base] from [cursor] to [end].
+///
+/// To iterate a substring, use:
+/// ```dart
+/// var breaks = Breaks(string, start, end, stateSoT);
+/// int brk = 0;
+/// while((brk = breaks.nextBreak) >= 0) {
+/// print('Break at index $brk');
+/// }
+/// ```
+/// If you use [stateSoTNoBreak] instead of [stateSoT], the
+/// initial break between the start-of-text and the first grapheme
+/// is suppressed.
+class Breaks {
+ /// Text being iterated.
+ final String base;
+
+ /// end of substring of [base] being iterated.
+ final int end;
+
+ /// Position of the first yet-unprocessed code point.
+ int cursor;
+
+ /// Current state based on code points processed so far.
+ ///
+ /// A state value is a multiple of [automatonRowLength] plus possibly
+ /// a few bits of flags.
+ int state;
+
+ Breaks(this.base, this.cursor, this.end, this.state);
+
+ /// Creates a copy of the current iteration, at the exact same state.
+ Breaks copy() => Breaks(base, cursor, end, state);
+
+ /// The index of the next grapheme cluster break in last-to-first index order.
+ ///
+ /// Returns a negative number if there are no further breaks,
+ /// which means that [cursor] has reached [end].
+ ///
+ /// Also stops if reaching a state with the `flagLookahead` bit set,
+ /// with the returned position being before the character which triggered
+ /// that look-behind.
+ /// If the state is not one which can trigger a look-behind, the exit position
+ /// is always the next break (if any, or -1 if none, which only happens on
+ /// empty strings.)
+ int nextBreak() {
+ while (cursor < end) {
+ var breakAt = cursor;
+ step();
+ if (state & maskFlags != flagNoBreak) {
+ return breakAt;
+ }
+ }
+ state = move(state, categoryEoT);
+ if (state & maskFlags != flagNoBreak) return cursor;
+ return -1;
+ }
+
+ /// Takes one step forward in the state machine.
+ void step() {
+ assert(cursor < end);
+ var char = base.codeUnitAt(cursor++);
+ if (char & 0xFC00 != 0xD800) {
+ state = move(state, low(char));
+ return;
+ }
+ // The category of an unpaired lead surrogate is Control.
+ int category;
+ int nextChar;
+ if (cursor < end &&
+ (nextChar = base.codeUnitAt(cursor)) & 0xFC00 == 0xDC00) {
+ category = high(char, nextChar);
+ cursor++;
+ } else {
+ category = categoryControl;
+ }
+ state = move(state, category);
+ }
+
+ /// Start with no knowledge about the position at [cursor].
+ ///
+ /// Starts from state `CAny` and takes one step based on the
+ /// latest character starting earlier than [cursor].
+ ///
+ /// Can be used if the [cursor] isn't even known to be at
+ /// a grapheme cluster boundary.
+ ///
+ /// Returns the start of that prior character, which is
+ /// one of `cursor` (if cursor is at start of input) or
+ /// `cursor - 1`or `cursor - 2` depending whether
+ /// it is a surrogate pair, and if so, where it ends.
+ int _unknownPositionFirstStep(int start) {
+ if (cursor == start) {
+ state = stateSoTNoBreak;
+ return cursor;
+ }
+ var cursorBefore = cursor - 1;
+ var prevChar = base.codeUnitAt(cursorBefore);
+ int prevCategory;
+ if (prevChar & 0xF800 != 0xD800) {
+ // Not surrogate.
+ prevCategory = low(prevChar);
+ } else if (prevChar & 0xFC00 == 0xD800) {
+ // Lead surrogate. Check for a following tail surrogate.
+ int tailChar;
+ if (cursor < end &&
+ (tailChar = base.codeUnitAt(cursor)) & 0xFC00 == 0xDC00) {
+ cursor += 1;
+ prevCategory = high(prevChar, tailChar);
+ } else {
+ prevCategory = categoryControl;
+ }
+ } else {
+ // Tail surrogate, check for prior lead surrogate.
+ int leadChar;
+ var leadIndex = cursorBefore - 1;
+ if (leadIndex >= start &&
+ (leadChar = base.codeUnitAt(leadIndex)) & 0xFC00 == 0xD800) {
+ prevCategory = high(leadChar, prevChar);
+ cursorBefore = leadIndex;
+ } else {
+ prevCategory = categoryControl;
+ }
+ }
+ state = move(stateCAny, prevCategory);
+ return cursorBefore;
+ }
+}
+
+/// Iterates grapheme cluster breaks backwards.
+///
+/// Given a substring of a [base] string from [start] to [cursor],
+/// iterates the grapheme cluster breaks from [cursor] to [start].
+///
+/// To iterate a substring, do
+/// ```dart
+/// var breaks = BackBreaks(string, start, end, idStateEoT);
+/// int brk = 0;
+/// while ((brk = breaks.nextBreak()) >= 0) {
+/// print('Break at index $brk');
+/// }
+/// ```
+/// If the initial [state] is [stateEoTNoBreak] instead of [stateEoT],
+/// the initial break between the last grapheme and the end-of-text
+/// is suppressed.
+class BackBreaks {
+ /// Text being iterated.
+ final String base;
+
+ /// Start of substring of [base] being iterated.
+ final int start;
+
+ /// Position after the last yet-unprocessed code point.
+ int cursor;
+
+ /// Current state based on code points processed so far.
+ int state;
+
+ BackBreaks(this.base, this.cursor, this.start, this.state);
+
+ BackBreaks copy() => BackBreaks(base, cursor, start, state);
+
+ /// The index of the next grapheme cluster break in first-to-last index order.
+ ///
+ /// Returns a negative number if there are no further breaks,
+ /// which means that [cursor] was already at [start].
+ int nextBreak() {
+ while (cursor > start) {
+ var breakAt = cursor;
+ step();
+ if (state & maskFlags == flagNoBreak) {
+ continue;
+ }
+ if (state & maskLookahead != 0) {
+ _lookaheadInNextBreak();
+ }
+ if (state & maskBreak != flagNoBreak) {
+ return breakAt;
+ }
+ }
+ state = moveBack(state, categoryEoT);
+ assert(state < stateLookaheadMin, state);
+ if (state & maskBreak != flagNoBreak) return cursor;
+ return -1;
+ }
+
+ /// Reads a single code point before [cursor] and transition on it.
+ ///
+ /// Puts cursor before the code point.
+ void step() {
+ assert(cursor > start);
+ var char = base.codeUnitAt(--cursor);
+ if (char & 0xFC00 != 0xDC00) {
+ var category = low(char);
+ state = moveBack(state, category);
+ return;
+ }
+ // Found tail surrogate, check for prior lead surrogate.
+ // The category of an unpaired tail surrogate is Control.
+ int category;
+ int prevChar;
+ if (cursor >= start &&
+ (prevChar = base.codeUnitAt(--cursor)) & 0xFC00 == 0xD800) {
+ category = high(prevChar, char);
+ } else {
+ category = categoryControl;
+ cursor++;
+ }
+ state = moveBack(state, category);
+ }
+
+ /// Steps back using lookahead states.
+ ///
+ /// Returns the position before the last scanned character.
+ /// (Because in some cases the next break will be at that point.)
+ int _lookahead() {
+ assert(state >= stateLookaheadMin);
+ while (cursor > start) {
+ var cursorBeforeLast = cursor;
+ step();
+ if (state < stateLookaheadMin) return cursorBeforeLast;
+ }
+ state = moveBack(state, categorySoT);
+ assert(state < stateLookaheadMin, state);
+ return start;
+ }
+
+ /// Called from [nextBreak] to perform a lookahead, and set the result state.
+ ///
+ /// After this call, the state has [flagBreak] set if it should break
+ /// between the two characters which triggered lookahead.
+ /// The state and cursor are set to a position prior to reaching the next
+ /// break.
+ void _lookaheadInNextBreak() {
+ assert(state >= stateLookaheadMin, state);
+ // To check if this was a regional lookahead afterwards.
+ var preState = state;
+ // Regional lookahead resets to this position.
+ var preCursor = cursor;
+ // Non-regional lookahead may reset to the position before the last seen,
+ // to avoid having to report two breaks.
+ var breakAt = _lookahead();
+
+ if (preState >= stateLookaheadRegionalEven) {
+ // Result is always one of one of flagBreak or flagNoBreak.
+ assert(
+ preState == (stateLookaheadRegionalOdd | flagLookahead) ||
+ preState == (stateLookaheadRegionalEven | flagLookahead),
+ preState);
+ assert(state == (stateRegionalEven | flagNoBreak) ||
+ state == (stateRegionalOdd | flagBreak));
+ // Always reset cursor for regional lookahead.
+ // (Could detect stateRegionalOdd, decrease cursor two positions and
+ // switch to stateRegionalEven. Not worth the extra code.)
+ cursor = preCursor;
+ } else {
+ // Flags mean:
+ // flagNoBreak: Do not break before position, or before cursor.
+ // flagBreak: break at position before lookahead, keep cursor.
+ // flagLookahead: Not used.
+ // flagBreak+flagLookahead: Break at position before lookahead,
+ // set cursor to reread the last character before cursor
+ // (because it'll break again there.)
+
+ // Keep cursor at or just before last read.
+ if (state & maskFlags == flagLookaheadBreakBoth) {
+ cursor = breakAt;
+ }
+ }
+ }
+}
+
+/// Whether there is a grapheme cluster boundary before [index] in [text].
+///
+/// This is a low-level function. There is no validation of the arguments.
+/// They should satisfy `0 <= start <= index <= end <= text.length`.
+///
+/// Allows [index] to not be at a grapheme cluster boundary
+/// (or even a code point boundary).
+bool isGraphemeClusterBoundary(String text, int start, int end, int index) {
+ assert(0 <= start);
+ assert(start <= index);
+ assert(index <= end);
+ assert(end <= text.length);
+ // Uses the backwards automaton because answering the question
+ // might be answered by looking only at the code points around the
+ // index, but it may also require looking further back. It never
+ // requires looking further ahead, though.
+ // The backwards automaton is built for this use case.
+ // Most of the apparent complication in this function is merely dealing with
+ // surrogates.
+ if (start < index && index < end) {
+ var breaks = Breaks(text, index, end, stateCAny);
+ var cursorBefore = breaks._unknownPositionFirstStep(start);
+ // If cursor moved, index is in the middle of a surrogate pair.
+ if (breaks.cursor != index) return false;
+ breaks.step();
+ if (breaks.state & maskBreak != flagNoBreak) {
+ return true;
+ }
+ if (breaks.state & maskLookahead == 0) return false;
+ assert(breaks.state >= stateLookaheadMin);
+
+ var backBreaks = BackBreaks(text, cursorBefore, start, breaks.state);
+ backBreaks._lookahead();
+ return (backBreaks.state & maskBreak != flagNoBreak);
+ }
+ return true;
+}
+
+/// The most recent break no later than [index] in
+/// `string.substring(start, end)`.
+///
+/// Allows [index] to not be at a grapheme cluster boundary
+/// (or even a code point boundary).
+int previousBreak(String text, int start, int end, int index) {
+ assert(0 <= start);
+ assert(start <= index);
+ assert(index <= end);
+ assert(end <= text.length);
+ // First character ending after `index`.
+ // Accounts for an `index` in the middle of a surrogate pair.
+ if (start < index && index < end) {
+ var cursorBefore = index;
+ var nextChar = text.codeUnitAt(index);
+ var category = categoryControl;
+ if (nextChar & 0xF800 != 0xD800) {
+ category = low(nextChar);
+ } else if (nextChar & 0xFC00 == 0xD800) {
+ var indexAfter = index + 1;
+ if (indexAfter < end) {
+ var secondChar = text.codeUnitAt(indexAfter);
+ if (secondChar & 0xFC00 == 0xDC00) {
+ category = high(nextChar, secondChar);
+ }
+ }
+ } else {
+ var prevChar = text.codeUnitAt(index - 1);
+ if (prevChar & 0xFC00 == 0xD800) {
+ category = high(prevChar, nextChar);
+ cursorBefore -= 1;
+ }
+ }
+ return BackBreaks(
+ text, cursorBefore, start, moveBack(stateEoTNoBreak, category))
+ .nextBreak();
+ }
+ return index;
+}
+
+/// The next break no earlier than [index] in `string.substring(start, end)`.
+///
+/// Allows [index] to not be at a grapheme cluster boundary
+/// (or even a code point boundary).
+int nextBreak(String text, int start, int end, int index) {
+ assert(0 <= start);
+ assert(start <= index);
+ assert(index <= end);
+ assert(end <= text.length);
+ // Always break at start or end (GB1).
+ if (index == start || index == end) return index;
+ var breaks = Breaks(text, index, end, stateCAny);
+ var cursorBefore = breaks._unknownPositionFirstStep(start);
+ var possibleBreak = breaks.nextBreak();
+ assert(breaks.state & maskFlags != 0);
+ if (breaks.state & maskFlags == flagBreak) return possibleBreak;
+ var lookbehindState = breaks.state;
+ assert(lookbehindState & maskFlags == flagLookahead);
+ assert(lookbehindState & maskState >= stateLookaheadMin);
+
+ var backBreaks = BackBreaks(text, cursorBefore, start, lookbehindState);
+ backBreaks._lookahead();
+ if (backBreaks.state & maskBreak != flagNoBreak) {
+ return possibleBreak;
+ }
+
+ // Find the correct forward category to continue with.
+ // There are only three possible character categories that can trigger
+ // a look-behind.
+ if (lookbehindState == stateLookaheadRegionalEven | flagLookahead) {
+ assert(backBreaks.state == stateRegionalEven);
+ // Started by RI+RI.
+ breaks.state = stateRegionalEven;
+ } else {
+ // Was triggered by ZWJ+Pic or InCB={Extend|Linked}+InCB=Consonant.
+ assert(lookbehindState == (stateLookaheadZWJPictographic | flagLookahead) ||
+ lookbehindState == (stateLookaheadInC | flagLookahead) ||
+ lookbehindState == (stateLookaheadInCL | flagLookahead));
+ // If starting in lookahead state ZWJ+Pic, and not breaking,
+ // final backwards state is Pic.
+ assert(lookbehindState != (stateLookaheadZWJPictographic | flagLookahead) ||
+ backBreaks.state == statePictographic);
+ // If starting in lookahead state InC or InCL, and not breaking,
+ // final backwards state is Inc.
+ assert(lookbehindState != (stateLookaheadInC | flagLookahead) &&
+ lookbehindState != (stateLookaheadInCL | flagLookahead) ||
+ backBreaks.state == stateInC);
+ // In both cases, that's the same as the forward state
+ // at the point that triggered the look-behind.
+ breaks.state = backBreaks.state;
+ }
+ var result = breaks.nextBreak();
+ assert(breaks.state & maskFlags == flagBreak);
+ return result;
+}
diff --git a/pkgs/characters/lib/src/grapheme_clusters/constants.dart b/pkgs/characters/lib/src/grapheme_clusters/constants.dart
new file mode 100644
index 0000000..7f42d92
--- /dev/null
+++ b/pkgs/characters/lib/src/grapheme_clusters/constants.dart
@@ -0,0 +1,243 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Unicode Grapheme Breaking Algorithm Character Categories.
+/// (Order is irrelevant to correctness, so it is chosen
+/// to minimize the size of the generated table strings
+/// by avoiding many bytes that need escapes).
+const int categoryCR = 0;
+const int categoryControl = 1;
+const int categoryOther = 2; // Any character not in any other category.
+const int categoryExtend = 3;
+const int categorySpacingMark = 4;
+const int categoryRegionalIndicator = 5;
+const int categoryPictographic = 6;
+const int categoryLF = 7;
+const int categoryPrepend = 8;
+const int categoryL = 9;
+const int categoryV = 10;
+const int categoryT = 11;
+const int categoryLV = 12;
+const int categoryLVT = 13;
+const int categoryOtherIndicConsonant = 14; // Other + InCB=Consonant.
+const int categoryZWJ = 15; // Is also InCB=Extend.
+const int categoryExtendIndicExtend = 16; // Extend + InCB=Extend.
+const int categoryExtendIndicLinked = 17; // Extend + InCB=Linked.
+const int categoryEoT = 18; // End of Text (synthetic input)
+
+const int categoryCount = categoryEoT + 1;
+const int inputCategoryCount = categoryEoT;
+
+const int regionalIndicatorStart = 0x1F1E6; // A
+const int regionalIndicatorEnd = 0x1F1FF; // Z
+
+// Automaton states for forwards automaton.
+
+/// Bit flag or'ed to the automaton output if there should not be a break
+/// before the most recent input character.
+const int flagNoBreak = 0;
+const int flagBreak = 1;
+const int maskBreak = 1;
+
+/// Extra bit used to trigger or modify the effect of lookahead/lookbehind.
+///
+/// Requires [automatonRowLength] to be a multiple of 4.
+/// It is currently 20.
+const int flagLookahead = 2;
+const int maskLookahead = 2;
+
+/// Mask of entry in automatons without low flag bits.
+const int maskFlags = maskLookahead | maskBreak;
+const int maskState = ~maskFlags;
+
+// For complex lookahead (Indic Ext/Lnk+Con, ZWJ+PIC), where to put the
+// breaks and cursor afterwards.
+const int flagLookaheadBreakNone = flagNoBreak;
+const int flagLookaheadBreakEarly = flagBreak;
+const int flagLookaheadBreakLate = flagLookahead | flagNoBreak; // Not used.
+const int flagLookaheadBreakBoth = flagLookahead | flagBreak;
+
+/// Automaton row length, number of input categories rounded up
+/// to a multiple of `maskFlags + 1`, so that the state value
+/// has room for flags in the low bits.
+/// (Rather than having to right-shift the state to find the
+/// table entry.)
+/// All state integers are multiples of this value.
+const automatonRowLength = (categoryCount + maskFlags) & maskState;
+
+// Let states be the position of their entries in the automaton data.
+
+// States of forwards automaton ---------------------------------------
+
+// For each state, also have a `automatonRowLength...` for the value of that
+// state that occurs in the automaton tables (and which is an index
+// into the automaton tables).
+
+/// Always break before next.
+const int stateBreak = 0x00 * automatonRowLength;
+
+/// Break unless next is LF.
+const int stateCR = 0x01 * automatonRowLength;
+
+/// Break unless next is Extend, ZWJ, SpacingMark.
+const int stateOther = 0x02 * automatonRowLength;
+
+/// Break only if next is Control/CR/LF/eot.
+const int statePrepend = 0x03 * automatonRowLength;
+
+/// As Other unless next is L, V, LV, LVT.
+///
+/// Seen `L+`
+const int stateL = 0x04 * automatonRowLength;
+
+/// As Other unless next is V, T.
+/// Seen: `L* (LV|V) V*`
+const int stateV = 0x05 * automatonRowLength;
+
+/// As Other unless next is T.
+///
+/// Seen `L*(LV?V*T|LVT)T*`.
+const int stateT = 0x06 * automatonRowLength;
+
+/// As Other unless followed by Ext* ZWJ Pic.
+const int statePictographic = 0x07 * automatonRowLength;
+
+/// As Other unless followed by Pic.
+const int statePictographicZWJ = 0x08 * automatonRowLength;
+
+/// As Other unless followed by RI.
+///
+/// Unknown whether there is an even or odd number of prior RIs.
+const int stateRegionalSingle = 0x09 * automatonRowLength;
+
+/// As Other unless next is InCB=Extend|Linked|.
+/// Has seen `{InCB=Consonant} {InCB=Extend}*`.
+const int stateInC = 0x0A * automatonRowLength;
+
+/// As Other unless InCB=Extend|Linked|Consonant.
+/// Seen `{InCB=Consonant} {InCB=Extend}* {InCB=Linked} {InCB=Extend|Linked}*`.
+/// Don't break before a following `{InCB=Consonant}`.
+/// (Not used in backwards automaton).
+const int stateInCL = 0x0B * automatonRowLength;
+
+/// As SoT, but never cause break before next character.
+///
+/// Not reachable in automaton, only used as start state.
+/// Used internally at start of inputs, which is automatically considered a
+/// break anyway.
+const int stateSoTNoBreak = 0x0C * automatonRowLength;
+
+/// Start of text (or known start of grapheme).
+///
+/// Not reachable in automaton, only used as start state.
+const int stateSoT = 0x0D * automatonRowLength;
+
+// Context-unaware states in forward automaton.
+// States that do not know what's behind the current sequence of Ext{InCB=?}+ZWJ
+// sequence, and which may need to trigger a look-behind in some cases.
+
+/// Start of context=unaware lookahead, no characters seen.
+const int stateCAny = 0x0E * automatonRowLength;
+
+/// Seen ZWJ only, as the first (prior) character.
+const int stateCZWJ = 0x0F * automatonRowLength;
+
+/// Seen Extend{InCB=Extend}+ only.
+const int stateCIE = 0x10 * automatonRowLength;
+
+/// Seen Extend{InCB=Extend|Lined}+, with at least one Linked
+const int stateCIL = 0x11 * automatonRowLength;
+
+/// Seen Extend{InCB=Extend}+ + ZWJ
+const int stateCIEZ = 0x12 * automatonRowLength;
+
+/// Seen Extend{InCB=Extend|Linked}+ + ZWJ with at least one Linked
+const int stateCILZ = 0x13 * automatonRowLength;
+
+/// Seen (Extend{InCB=Extend}|ZWJ)+ with at least one non-trailing ZWJ
+const int stateCZIE = 0x14 * automatonRowLength;
+
+/// Seen (Extend{InCB=Extend|Linked}|ZWJ)+
+/// with at least one non-trailing ZWJ and at least one Linked.
+const int stateCZIL = 0x15 * automatonRowLength;
+
+/// Seen Extend{InCB=?}+ with at least one Extend{InCB=None}
+const int stateCExt = 0x16 * automatonRowLength;
+
+/// Seen Extend{InCB=?}+ + ZWJ with at least one Extend{InCB=None}
+const int stateCExZ = 0x17 * automatonRowLength;
+
+/// Seen single RegionalIndicator only.
+const int stateCReg = 0x18 * automatonRowLength;
+
+// --------------------------------------------------------------------
+
+/// First state which might trigger look-behind.
+const int stateMinContextUnaware = stateCAny;
+
+/// Number of states in forward automaton.
+const int stateLimit = stateCReg + automatonRowLength;
+
+// ---------------------------------------------------------------------
+// Backwards Automaton extra/alternative states and categories.
+//
+// Reuses state positions that are not used in backwards search,
+// possibly because they are replaced by look-behind.
+
+const int categorySoT = categoryEoT; // Start of Text (synthetic input)
+
+/// Start of text (or grapheme).
+const int stateEoT = stateSoT;
+
+/// Break unless prev is CR.
+const int stateLF = stateCR;
+
+/// Only break if prev is Control/CR/LF/sot.
+const int stateExtend = statePrepend;
+
+/// As EoT but never cause break before.
+const int stateEoTNoBreak = stateSoTNoBreak;
+
+/// There is an even number of RIs before.
+const int stateRegionalEven = stateInCL;
+
+/// There is an odd (non-zero!) number of RIs before.
+const int stateRegionalOdd = statePictographicZWJ;
+
+// Backwards automaton sometimes needs to perform lookahead.
+// The rules for grapheme cluster breaking can depend on knowing
+// the categories of multiple *prior* code points. When getting to such a point
+// during backwards movement, the automaton breaks out and runs specialized
+// code that looks back on prior characters to decide whether the current
+// position should break.
+// (TODO: Also allow updating the position if it's known where the next break
+// is in the scanned characters.)
+
+// It triggers that by entering a synthetic state.
+// After doing the lookahead, that synthetic state is replaced by a
+// conventional state that allows it to proceed.
+// The extra states are not part of the state machine.
+
+/// Minimum state requesting a look-ahead.
+const int stateLookaheadMin = stateLookaheadZWJPictographic;
+
+/// State requesting a look-ahead for Pic Ext*.
+const int stateLookaheadZWJPictographic = 0x0E * automatonRowLength;
+
+/// State requesting a look-ahead for InCB consonant + InCB (Extend + inked)+
+/// with at least one inked.
+const int stateLookaheadInC = 0x0F * automatonRowLength;
+
+/// State requesting a look-ahead for InCB consonant + InCB (Extend + inked)+
+/// ending with a linked.
+const int stateLookaheadInCL = 0x10 * automatonRowLength;
+
+/// Look-ahead state for regional indicators, having seen an even number.
+const int stateLookaheadRegionalEven = 0x11 * automatonRowLength;
+
+/// Look-ahead state for regional indicators, having seen an odd number.
+const int stateLookaheadRegionalOdd = 0x12 * automatonRowLength;
+
+/// Limit on the entries of states in backwards automaton.
+const int backStateLimit = stateLookaheadRegionalOdd + automatonRowLength;
diff --git a/pkgs/characters/lib/src/grapheme_clusters/table.dart b/pkgs/characters/lib/src/grapheme_clusters/table.dart
new file mode 100644
index 0000000..fce9c85
--- /dev/null
+++ b/pkgs/characters/lib/src/grapheme_clusters/table.dart
@@ -0,0 +1,1178 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Generated code. Do not edit.
+// Generated from:
+// - [https://unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakTest.txt](../../third_party/Unicode_Consortium/GraphemeBreakTest.txt)
+// - [https://unicode.org/Public/emoji/latest/emoji-test.txt](../../third_party/Unicode_Consortium/emoji_test.txt)
+// - [https://unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakProperty.txt](../../third_party/Unicode_Consortium/GraphemeBreakProperty.txt)
+// Licensed under the Unicode Inc. License Agreement
+// (https://www.unicode.org/license.txt, ../../third_party/third_party/Unicode_Consortium/UNICODE_LICENSE.txt)
+
+const String _data = '\x10\x10\b\x04\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x04\x04\x10\x10\x10\x10\x10\x02\x02\x02\x04\x04\x10\x10\x10\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x02\x01\x01\x01\x01\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10'
+ '\x02\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04'
+ '\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04'
+ '\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04'
+ '\x04\x04\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x02\x0e\x0e\x0e\x0e\x0e\x0e'
+ '\x0e\x02\x0e\x02\x02\x02\x0e\x0e\x0e\x0e\x02\x02\x10\x02\x10\x04\x10\x04'
+ '\x04\x02\x10\x10\x10\x02\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x10'
+ '\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x04\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x10\x10\x06'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06\x02\x02\x06\x02\x02'
+ '\x02\x02\x06\x02\x06\x02\x02\x02\x02\x06\x06\x06\x02\x06\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x04\x10\x10\x10\x10\x02\x02\x04\x04\x02\x02\x04\x04\x11'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x02\x02\x02\x02\x0e\x0e\x02\x0e'
+ '\x10\x04\x04\x04\x04\x02\x10\x10\x10\x02\x10\x10\x10\x11\x02\x02\x02\x02'
+ '\x02\x02\x02\x10\x10\x02\x0e\x0e\x0e\x02\x02\x02\x02\x02\x10\x10\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x0e\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10'
+ '\x10\x04\x10\x10\x10\x10\x10\x10\x02\x10\x10\x04\x04\x10\x10\x02\x10\x02'
+ '\x02\x10\x10\x10\x10\x10\x10\x10\x10\x04\x04\x04\x04\x04\x04\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x02\x02\x10\x10\x02\x10\x10\x10\x10\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x10\x10\x04\x10\x10\x10\x10\x10\x10\x10\x04\x04\x04\x10\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x0e\x0e\x0e\x0e\x0e\x0e'
+ '\x0e\x0e\x0e\x0e\x0e\x02\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e'
+ '\x0e\x0e\x0e\x0e\x02\x02\x10\x02\x10\x10\x10\x02\x10\x10\x02\x02\x02\x02'
+ '\x02\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x02\x02\x02\x02\x10\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x04\x04\x10\x02\x02'
+ '\x02\x02\x04\x10\x10\x10\x10\x10\x10\x10\x10\x04\x04\x04\x04\x11\x04\x04'
+ '\x02\x10\x10\x10\x10\x10\x10\x10\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\r\r\r\r\r'
+ '\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\f\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r'
+ '\r\r\r\r\r\r\f\r\r\r\r\r\r\r\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\v\v\v'
+ '\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\x02\x02\x02\x02'
+ '\x04\x10\x10\x10\x10\x02\x04\x04\x04\x02\x04\x04\x04\x11\b\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x10\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x04\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x10\x10\x10\x01\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x10\x10\x10\x02\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x02\x10\x10\x02\x02\x02\x02\x02\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x02\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x02\x02\x10\x10\x10\x10\x10\x10\x10\x02\x10\x10\x02\x10'
+ '\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x10\x10\x10\x10\x10\x02\x02\x02\x10\x10\x10\x10\x10\x10\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x10\x10\x10\x10\x10\x10\x10\x10\x02\x02\x10\x10\x10'
+ '\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10'
+ '\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06\x06\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x06\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06\x02\x02\x02\x02\x02\x02'
+ '\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x0e\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x02\x02\x02\x02\x06\x06\x06\x02\x02\x02\x02\x02\x10'
+ '\x04\x04\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x02\x04\x10\x10\x10'
+ '\x10\x10\x10\x10\x02\x02\x02\x02\x02\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'
+ '\t\t\t\t\t\t\t\t\t\t\t\t\t\x02\x02\x02\x04\x04\x10\x04\x04\x10\x04\x04\x02'
+ '\x04\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x06\x02\x02\x02\x02\x06\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x06\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x06\x06\x06\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x06\x06\x02\x02\x02\x10\x04\x04\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x0e\x0e\x0e\x0e\x0e\x0e'
+ '\x0e\x0e\x0e\x0e\x0e\x02\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x02\x0e\x0e\x02\x0e'
+ '\x0e\x0e\x0e\x0e\x02\x02\x10\x02\x10\x10\x04\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e'
+ '\x0e\x0e\x0e\x02\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x02\x0e\x0e\x02\x0e\x0e\x0e'
+ '\x0e\x0e\x02\x02\x10\x02\x04\x04\x10\x10\x10\x10\x02\x02\x04\x04\x02\x02'
+ '\x04\x04\x11\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x02\x02\x02\x02\x0e'
+ '\x0e\x02\x0e\n\n\n\n\n\n\n\x02\x02\x02\x02\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v\v'
+ '\v\v\v\v\v\x10\x10\b\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x02\x02\x02\x02\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x10\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10'
+ '\x10\x10\x10\x02\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x04\x10\x10\x10\x10\x10\x10\x10\x02'
+ '\x10\x10\x10\x10\x10\x10\x04\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x02\x04\x10\x10\x10\x10\x10\x10\x10\x04\x10\x10\x04\x10'
+ '\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x10\x10\x10\x10\x10\x10\x02\x02\x02\x10\x02\x10\x10\x02\x10\x10'
+ '\x10\x10\x10\x10\x10\b\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x04\x04\x04\x04\x04\x02\x10\x10\x02\x04\x04\x10\x04\x10\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x04\x04\x04\x04\x04\x02\x04'
+ '\x04\x02\x02\x10\x10\x10\x10\b\x04\b\x04\x10\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x04\x04\x04\x10\x10\x10\x10\x02\x02\x10'
+ '\x10\x04\x04\x04\x04\x10\x02\x02\x02\x04\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x06\x06\x06\x06\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x06\x06\x06\x06\x06\x06\x06\x06\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x06\x06\x06\x06\x06\x06\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x02\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x02\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06'
+ '\x02\x02\x06\x06\x06\x06\x06\x06\x06\x06\x06\x02\x06\x06\x06\x06\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x02\x02\x02\x02\x02\x02\x02\x02\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x07'
+ '\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x02\x02\x02\x02\x04\x04\x10\x10\x04\x02\x02\x02\x02\x10\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x04'
+ '\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x10\x10\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x10\x10\x10\x10\x10\x10\x10\b\x02\x10\x10\x10\x10\x02\x10\x10'
+ '\x10\x02\x10\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x04\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x04\x10\x04\x10\x10'
+ '\x10\x10\x10\x10\x10\x02\x02\x02\x02\x10\x02\x02\x02\x02\x02\x02\x10\x02'
+ '\x02\x04\x10\x10\x02\x02\x02\x02\x02\x02\x10\x04\x10\x10\x04\x04\x04\x10'
+ '\x04\x10\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x01\x03\x0f\x01\x01\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x10\x10\x02\x02\x02\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x10\x10\x10\x10\x10\x10\x04\x04\x10\x10\x04\x04\x10\x10\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x01\x01\x01\x01\x01\x01\x01\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x06\x02\x02\x02\x01\x06\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x02\x10\x10\x10'
+ '\x02\x02\x10\x10\x02\x02\x02\x02\x02\x10\x10\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x0e\x0e\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x10\x02\x04\x10\x10\x02\x02\x02\x02\x02\x02\x10\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x02\x02\x02\x02\x10\x04\x04'
+ '\x10\x10\x10\x02\x10\x02\x04\x04\x04\x04\x04\x04\x04\x10\x04\x04\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x10\x02\x02\x04\x10\x10\x10\x10\x04\x04\x10\x10\x10\x10\x10\x10\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10'
+ '\x10\x10\x10\x04\x10\x10\x10\x10\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x04\x04\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x04'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x04\x04\x04\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x04\x10\x02\b\b\x02\x02\x02\x02\x02\x10\x10\x10'
+ '\x10\x02\x04\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x04\x04'
+ '\x04\x10\x10\x10\x10\x10\x10\x10\x10\x04\x04\x10\x04\x10\x10\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x04\x10\x04\x04'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x10\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x04\x04\x04\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10'
+ '\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x04\x04\x04\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x04\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10'
+ '\x10\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\b\b\x02\x02\x02\x02\x02\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x02\x02\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x10\x10\x04\x04\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e'
+ '\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x10'
+ '\x10\x02\x10\x04\x04\x02\x02\x02\x04\x04\x04\x02\x04\x04\x04\x10\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x10\x02\x02\x02\x02\x02\x02\x02\x02\x10\x04'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x10\x04\x04\x10\x10\x10\x10\x04\x04\x10\x10\x04\x04\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x02'
+ '\x10\x04\x10\x04\x04\x04\x04\x02\x02\x04\x04\x02\x02\x04\x04\x10\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x04\x04\x02\x02\x10\x10\x10\x10\x10\x10\x10\x02\x02\x02\x10\x10\x10\x10'
+ '\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x04\x04\x10'
+ '\x10\x10\x10\x10\x10\x02\x10\x02\x02\x10\x02\x10\x10\x10\x04\x02\x04\x04'
+ '\x10\x10\x10\b\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x04\x04\x10\x10\x02'
+ '\x02\x02\x02\x10\x10\x02\x02\x10\x10\x10\x02\x02\x02\x10\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\b\x02\x10\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x10\x10\x10\x10\x10\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x02\x02\x02\x10\x02\x02'
+ '\x02\x02\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x04\x04\x04'
+ '\x04\x10\x10\x04\x04\x04\x02\x02\x02\x02\x04\x04\x10\x04\x04\x04\x04\x04'
+ '\x04\x10\x10\x10\x02\x02\x02\x02\x10\x10\x10\x04\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x0e\x0e\x0e\x0e\x0e\x0e\x0e'
+ '\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e'
+ '\x0e\x10\x04\x10\x02\x04\x04\x10\x04\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x02\x02\x10\x10\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x04\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x04\x04\x04\x10\x10\x10\x10\x04\x04\x10\x10\x02\x02\b\x02\x02\x02'
+ '\x02\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\b\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
+ '\x01\x01\x01\x01\x01\x01\x10\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x10\x04\x04\x10\x10\x10\x10\x02\x02\x04\x04\x04\x04'
+ '\x10\x10\x04\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x04\x10\x02'
+ '\x02\x10\x10\x10\x10\x04\x10\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x04\x04\x04\x10\x10\x10'
+ '\x10\x10\x10\x10\x10\x04\x04\x10\x10\x10\x04\x10\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x04\x04\x10\x10\x10\x10\x10\x10'
+ '\x04\x10\x04\x04\x10\x04\x10\x10\x04\x10\x10\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x04'
+ '\x04\x04\x10\x10\x10\x04\x04\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x10'
+ '\x02\x02\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x04'
+ '\x04\x04\x10\x10\x10\x10\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06\x06\x06\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x06\x06\x06\x06\x06\x06\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x06\x06\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06\x02'
+ '\x02\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x05\x05\x05\x05'
+ '\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05'
+ '\x05\x05\x05\x05\b\b\b\b\b\b\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x02\x01\x02\x02\x02\x10\x10\x02'
+ '\x10\x10\x02\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06\x02\x06\x02\x02\x02\x02\x02'
+ '\x02\x06\x06\x06\x06\x06\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x02\x10'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x10'
+ '\x10\x04\b\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x10\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x10\x10\x10\x10\x10\x10\x04\x04\x10\x10\x10\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\b\b\b\b\b\b\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x04\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\n\x02\x02'
+ '\x02\n\n\n\n\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x02\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x02\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x02\x02\x06\x06'
+ '\x06\x06\x06\x06\x06\x06\x06\x06\x06\x02\x06\x02\x06\x02\x02\x02\x02\x02'
+ '\x02\x06\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x06\x06\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x10'
+ '\x02\x10\x02\x10\x02\x02\x02\x02\x04\x04\x04\x04\x04\x04\x04\x04\x10\x10'
+ '\x10\x10\x10\x10\x10\x10\x04\x04\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x10\x10\x10\x10\x10\x10\x10\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x02\x02\x02\x02\x02\x02\x02\x10\x02\x04\x10\x10\x10\x10\x10\x10\x10'
+ '\x10\x10\x02\x02\x02\x04\x10\x10\x10\x10\x10\x02\x10\x10\x04\x02\x04\x04'
+ '\x11\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'
+ '\x02\x04\x04\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x04\x10\x10'
+ '\x04\x04\x02\x02\x02\x02\x02\x04\x10\x02\x02\x02\x02\x02\x02\x02\x02\x02';
+const String _start = '\u1132\u166c\u166c\u206f\u11c0\u13fb\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u1bff\u1bff\u1bff\u1c36\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u1aee\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u1fb5'
+ '\u059c\u266d\u166c\u264e\u166c\u0a70\u175c\u166c\u166c\u1310\u033a\u1ebd'
+ '\u0a6b\u2302\u166c\u166c\u22fc\u166c\u1ef8\u269d\u132f\u03b8\u166c\u1be8'
+ '\u166c\u0a71\u0915\u1f5a\u1f6f\u04a2\u0202\u086b\u021a\u029a\u1427\u1518'
+ '\u0147\u1eab\u13b9\u089f\u08b6\u2a91\u02d8\u086b\u0882\u08d5\u0789\u176a'
+ '\u251c\u1d6c\u166c\u0365\u037c\u02ba\u22af\u07bf\u07c3\u0238\u024b\u1d39'
+ '\u1d4e\u054a\u22af\u07bf\u166c\u1456\u2a9f\u166c\u07ce\u2a61\u166c\u166c'
+ '\u2a71\u1ae9\u166c\u0466\u2a2e\u166c\u133e\u05b5\u0932\u1766\u166c\u166c'
+ '\u0304\u1e94\u1ece\u1443\u166c\u166c\u166c\u07ee\u07ee\u07ee\u0506\u0506'
+ '\u051e\u0526\u0526\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u196b\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u1798\u1657\u046c\u046c\u166c'
+ '\u0348\u146f\u166c\u0578\u166c\u166c\u166c\u22ac\u1763\u166c\u166c\u166c'
+ '\u1f3a\u166c\u166c\u166c\u166c\u166c\u166c\u0482\u166c\u1364\u0322\u166c'
+ '\u0a6b\u1fc6\u166c\u1359\u1f1f\u270e\u1ee3\u200e\u148e\u166c\u1394\u166c'
+ '\u2a48\u166c\u166c\u166c\u166c\u0588\u137a\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u1bff\u1bff\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u13a9\u13e8\u2574\u12b0\u166c'
+ '\u166c\u0a6b\u1c35\u166c\u076b\u166c\u166c\u25a6\u2a23\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u0747\u2575\u166c\u166c\u2575'
+ '\u166c\u256e\u07a0\u166c\u166c\u166c\u166c\u166c\u166c\u257b\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u0757\u255d\u0c6d\u0d76\u28f0\u28f0\u28f0\u29ea'
+ '\u28f0\u28f0\u28f0\u2a04\u2a19\u027a\u2693\u2546\u0832\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u074d\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u084c'
+ '\u166c\u081e\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u165a\u166c\u166c\u166c\u174d\u166c\u166c\u166c\u1bff\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u0261\u166c\u166c\u0465\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u2676'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u26a4\u196a\u166c\u166c\u046e\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u1f13\u12dd\u166c\u166c\u14de\u12ea\u1306\u02f2\u166c'
+ '\u2a62\u0563\u07f1\u200d\u1d8e\u198c\u1767\u166c\u13d0\u1d80\u1750\u166c'
+ '\u140b\u176b\u2ab4\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u080e\u04d6'
+ '\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce'
+ '\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6'
+ '\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da'
+ '\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2'
+ '\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca'
+ '\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2'
+ '\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6'
+ '\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce'
+ '\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6'
+ '\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da'
+ '\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2'
+ '\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca'
+ '\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2'
+ '\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6'
+ '\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce'
+ '\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6'
+ '\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da'
+ '\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2'
+ '\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca'
+ '\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2'
+ '\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6'
+ '\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce'
+ '\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6'
+ '\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da'
+ '\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2'
+ '\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca'
+ '\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2'
+ '\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6'
+ '\u04da\u04c2\u04c6\u04ca\u04ce\u04d2\u04d6\u04da\u04c2\u04c6\u04ca\u04ce'
+ '\u04f6\u08f5\u052a\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0'
+ '\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0'
+ '\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0'
+ '\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0'
+ '\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0'
+ '\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u174e\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u1c36\u1c36\u166c\u166c\u166c'
+ '\u166c\u166c\u206f\u166c\u166c\u166c\u166c\u196a\u166c\u166c\u12c0\u166c'
+ '\u166f\u168c\u1912\u166c\u166c\u166c\u166c\u166c\u166c\u0399\u166c\u166c'
+ '\u1786\u2206\u22bc\u1f8e\u1499\u245b\u1daa\u2387\u20b4\u1569\u2197\u19e6'
+ '\u0b88\u26b7\u166c\u09e9\u0ab8\u1c46\x00\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u205e\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u1868\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u1898\u1ac1\u166c'
+ '\u2754\u166c\u0114\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166cc\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u1bff\u166c\u0661\u1627\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u0918\u166c\u166c\u166c\u166c\u166c\u05c6\u1ac1\u16be\u166c\u1af8\u21c3'
+ '\u166c\u166c\u1a21\u1aad\u166c\u166c\u166c\u166c\u166c\u166c\u28f0\u254e'
+ '\u0d89\u0f41\u28f0\u0efb\u0e39\u27e0\u0c7c\u28a9\u28f0\u166c\u28f0\u28f0'
+ '\u28f0\u28f2\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u1140\u103c\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0'
+ '\u11c0\u11c0\u11c0\u11c0\u11c0\u11c0\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c\u166c'
+ '\u166c\u166c';
+@pragma('dart2js:prefer-inline')
+@pragma('vm:prefer-inline')
+@pragma('wasm:prefer-inline')
+int low(int codeUnit) {
+ var chunkStart = _start.codeUnitAt(codeUnit >> 5);
+ var index = chunkStart + (codeUnit & 31);
+ return _data.codeUnitAt(index);
+}
+
+@pragma('dart2js:prefer-inline')
+@pragma('vm:prefer-inline')
+@pragma('wasm:prefer-inline')
+int high(int lead, int tail) {
+ var offset = (((0x3ff & lead) << 10) + (0x3ff & tail)) + (2048 << 8);
+ var chunkStart = _start.codeUnitAt(offset >> 8);
+ var index = chunkStart + (tail & 255);
+ return _data.codeUnitAt(index);
+}
+
+const _stateMachine = '\x15\x01)))µ\x8d\x01=QeyeyÉ)))ñð\x15\x01)))µ\x8d\x00=Qey'
+ 'eyÉ)))ñð\x15\x01)((µ\x8d\x01=QeyeyÉ(((ñð\x15\x01(((´\x8c\x01<PdxdxÈ(((ñð'
+ '\x15\x01)((µ\x8d\x01=PdydxÉ(((ñð\x15\x01)((µ\x8d\x01=QdxeyÉ(((ñð\x15\x01)('
+ '(µ\x8d\x01=QexeyÉ(((ñð\x15\x01)\x8c(µ\x8d\x01=QeyeyÉ\xa0\x8c\x8cñð\x15\x01'
+ ')((µ\x8c\x01=QeyeyÉ(((ñð\x15\x01)(((\x8d\x01=QeyeyÉ(((ñð\x15\x01)((µ\x8d'
+ '\x01=QeyeyÉÈÈÜñð\x15\x01)((µ\x8d\x01=QeyeyÈÜÜÜñð\x14\x00(((´\x8c\x00<Pdxdx'
+ 'È(((ðð\x15\x01)))µ\x8d\x01=QeyeyÉ)))ðð\x15\x01(Ƹ(Ǡ\x8d\x01<PdxdxÈĬŀŔðð\x15'
+ '\x01)((µĚ\x01=QeyeyĮƐƐƤñð\x15\x01)Ƹ(µ\x8d\x01=QeyeyĮŨŀŔñð\x15\x01)Ƹ(µ\x8d'
+ '\x01=QeyeyłżŔŔñð\x15\x01)((µĚ\x01=QeyeyÉƐƐƤñð\x15\x01)((µĚ\x01=QeyeyłƤƤƤñð'
+ '\x15\x01)((µ\x8d\x01=QeyeyĮƐƐƤñð\x15\x01)((µ\x8d\x01=QeyeyłƤƤƤñð\x15\x01)Ƹ'
+ '(µ\x8d\x01=QeyeyÉnjƸƸñð\x15\x01)((µĚ\x01=QeyeyÉ(((ñð\x15\x01)((Ŗ\x8d\x01=Qe'
+ 'yeyÉ(((ñð';
+@pragma('dart2js:prefer-inline')
+@pragma('vm:prefer-inline')
+@pragma('wasm:prefer-inline')
+int move(int state, int inputCategory) =>
+ _stateMachine.codeUnitAt((state & -4) + inputCategory);
+
+const _backStateMachine = '\x01\x01)==µ\x8d\x15)QeyQQÉ===ñð\x00\x01)==µ\x8d\x15'
+ ')QeyQQÉ===ñð\x01\x01)==µ\x8d\x15(QeyQQÉ===ñð\x01\x01(<<´\x8c\x15(PdxPPÈ<<<'
+ 'ñð\x01\x01)==µ\x8d\x15(PeyQQÉ===ñð\x01\x01)==µ\x8d\x15(PdyPQÉ===ñð\x01\x01'
+ ')==µ\x8d\x15(QdxPPÉ===ñð\x01\x01)==µ\x8d\x15(QeyQQÉĚ==ñððððððÜðððððððððððð'
+ 'ðð\x01\x01)==Ŗ\x8d\x15(QeyQQÉ===ñð\x01\x01)==µ\x8d\x15(QeyQQÉĮĮłñð\x01\x01'
+ ')==¡\x8d\x15(QeyQQÉ===ñð\x00\x00(<<´\x8c\x14(PdxPPÈ<<<ðð\x01\x01)==µ\x8d'
+ '\x15)QeyQQÉ===ðð??)Ę=µ\x8c?)QeyQQÉ=ĘĘ?ð??)==µ\x8d?)QeyQQÉĬĬŀ?ð??)==µ\x8d?)'
+ 'QeyQQÈŀŀŀ?ðÜÜÜÜÜŨÜÜÜÜÜÜÜÜÜÜÜÜÜ\x00¡¡¡¡¡Ŕ¡¡¡¡¡¡¡¡¡¡¡¡¡\x00';
+@pragma('dart2js:prefer-inline')
+@pragma('vm:prefer-inline')
+@pragma('wasm:prefer-inline')
+int moveBack(int state, int inputCategory) =>
+ _backStateMachine.codeUnitAt((state & -4) + inputCategory);
diff --git a/pkgs/characters/pubspec.yaml b/pkgs/characters/pubspec.yaml
new file mode 100644
index 0000000..a3de2fe
--- /dev/null
+++ b/pkgs/characters/pubspec.yaml
@@ -0,0 +1,17 @@
+name: characters
+version: 1.4.0
+description: >-
+ String replacement with operations that are Unicode/grapheme cluster aware.
+repository: https://github.com/dart-lang/core/tree/main/pkgs/characters
+issue_tracker: https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acharacters
+
+topics:
+ - strings
+ - unicode
+
+environment:
+ sdk: ^3.4.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.1.0
+ test: ^1.16.6
diff --git a/pkgs/characters/test/breaks_test.dart b/pkgs/characters/test/breaks_test.dart
new file mode 100644
index 0000000..b48c8cb
--- /dev/null
+++ b/pkgs/characters/test/breaks_test.dart
@@ -0,0 +1,450 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Test the generated automatons directly.
+
+import 'package:characters/src/grapheme_clusters/breaks.dart';
+import 'package:characters/src/grapheme_clusters/constants.dart';
+import 'package:characters/src/grapheme_clusters/table.dart';
+import 'package:test/test.dart';
+
+import '../tool/src/debug_names.dart';
+import 'src/equiv.dart';
+import 'src/unicode_tests.dart';
+
+// Can be set to true while debugging.
+const verbose = false;
+
+void main() {
+ // Test [Breaks] on all the available Unicode tests.
+ group('forward automaton:', () {
+ for (var expectedParts in splitTests) {
+ for (var (variantParts, kind) in testVariants(expectedParts)) {
+ test(testDescription(variantParts) + kind, () {
+ var input = variantParts.join('');
+ var breaks = Breaks(input, 0, input.length, stateSoTNoBreak);
+ var parts = <String>[];
+ var start = 0;
+ while (start < input.length) {
+ var next = breaks.nextBreak();
+ expect(next, greaterThan(start));
+ parts.add(input.substring(start, next));
+ start = next;
+ }
+ expect(parts, variantParts, reason: partCategories(parts) + kind);
+ });
+ }
+ }
+ });
+
+ // Test [BackBreaks] directly on all the available Unicode tests.
+ group('backward automaton:', () {
+ for (var expectedParts in splitTests) {
+ for (var (variantParts, kind) in testVariants(expectedParts)) {
+ test(testDescription(variantParts) + kind, () {
+ var input = variantParts.join('');
+ var breaks = BackBreaks(input, input.length, 0, stateEoTNoBreak);
+ var parts = <String>[];
+ var start = input.length;
+ while (start > 0) {
+ var next = breaks.nextBreak();
+ expect(next, lessThan(start));
+ parts.add(input.substring(next, start));
+ start = next;
+ }
+ parts = [...parts.reversed];
+ expect(parts, variantParts, reason: partCategories(parts) + kind);
+ });
+ }
+ }
+ });
+
+ // Test the top-level [nextBreak] function on all positions of all
+ // the Unicode tests.
+ group('nextBreak', () {
+ // Should find the next break at any position.
+ for (var expectedParts in splitTests) {
+ for (var (variantParts, kind) in testVariants(expectedParts)) {
+ test(testDescription(variantParts) + kind, () {
+ var input = variantParts.join('');
+ var description = partCategories(expectedParts);
+ var partCursor = 0;
+ var nextExpectedBreak = 0;
+
+ for (var i = 0; i <= input.length; i++) {
+ var actualBreak = nextBreak(input, 0, input.length, i);
+ expect(actualBreak, nextExpectedBreak,
+ reason: 'at $i: $description$kind');
+ if (i == nextExpectedBreak && i < input.length) {
+ nextExpectedBreak += variantParts[partCursor].length;
+ partCursor++;
+ }
+ }
+ });
+ }
+ }
+ });
+
+ // Test the top-level [previousBreak] function on all positions of all
+ // the Unicode tests.
+ group('previousBreak', () {
+ // Should find the next break at any position.
+ for (var expectedParts in splitTests) {
+ for (var (variantParts, kind) in testVariants(expectedParts)) {
+ test(testDescription(variantParts) + kind, () {
+ var input = variantParts.join('');
+ var description = partCategories(expectedParts);
+ var partCursor = 0;
+ var nextBreak = 0;
+ var expectedBreak = 0;
+
+ for (var i = 0; i <= input.length; i++) {
+ if (i == nextBreak) {
+ expectedBreak = nextBreak;
+ if (i < input.length) {
+ nextBreak += variantParts[partCursor++].length;
+ }
+ }
+ var actualBreak = previousBreak(input, 0, input.length, i);
+ expect(actualBreak, expectedBreak,
+ reason: 'at $i: $description$kind');
+ }
+ });
+ }
+ }
+ });
+
+ // Test the top-level [previousBreak] function on all positions of all
+ // the Unicode tests.
+ group('isGraphemeClusterBreak', () {
+ // Should find the next break at any position.
+ for (var expectedParts in splitTests) {
+ for (var (variantParts, kind) in testVariants(expectedParts)) {
+ test(testDescription(variantParts) + kind, () {
+ var input = variantParts.join('');
+ var description = partCategories(expectedParts);
+ var partCursor = 0;
+ var nextBreak = 0;
+
+ for (var i = 0; i <= input.length; i++) {
+ expect(isGraphemeClusterBoundary(input, 0, input.length, i),
+ i == nextBreak,
+ reason: 'at $i: $description');
+
+ if (i == nextBreak && i < input.length) {
+ nextBreak += variantParts[partCursor++].length;
+ }
+ }
+ });
+ }
+ }
+ });
+
+ // Check that automatons are minimal.
+ //
+ // * All states are reachable from the start states.
+ // * No states are indistinguishable wrt. all inputs.
+ //
+ // That means that no state can be removed, because it is unique and
+ // used.
+ group('Minimal automaton:', () {
+ test('States reachable', () {
+ // Expected reachable states.
+ var states = {
+ stateBreak,
+ stateCR,
+ stateOther,
+ statePrepend,
+ stateL,
+ stateV,
+ stateT,
+ statePictographic,
+ statePictographicZWJ,
+ stateRegionalSingle,
+ stateInC,
+ stateInCL,
+ stateSoT, // Entry point.
+ stateSoTNoBreak, // Entry point.
+ stateCAny, // Entry point.
+ stateCZWJ,
+ stateCExZ,
+ stateCIE,
+ stateCIEZ,
+ stateCIL,
+ stateCILZ,
+ stateCZIE,
+ stateCZIL,
+ stateCReg,
+ stateCExt,
+ };
+ // Standard reachability algorithm.
+ // Fringe of reachable states. Will contain all reachable states once.
+
+ var entryStates = [stateSoTNoBreak, stateSoT, stateCAny];
+
+ // All reachable state will be removed from this set,
+ // and added to the worklist the first time they are seen.
+ var unreachableStates = {...states}..removeAll(entryStates);
+ // Start with entry points.
+ var workList = <int>[...entryStates];
+ var nextStepList = <int>[];
+
+ var step = 1;
+ // Continue until all states reachable, or no states left in fringe.
+ while ((workList.isNotEmpty || nextStepList.isNotEmpty) &&
+ unreachableStates.isNotEmpty) {
+ if (workList.isEmpty) {
+ workList = nextStepList;
+ nextStepList = [];
+ step++;
+ }
+ var state = workList.removeLast();
+ for (var c = 0; c < categoryCount; c++) {
+ var newState = move(state, c) & maskState;
+ if (newState & maskFlags == flagLookahead) {
+ // A lookahead in the forwards automaton uses the
+ // backwards automaton to determine whether to break.
+ // It should leave the context-unaware part of the states
+ // and reach a state that should otherwise be reachable too.
+ continue;
+ }
+ // No unexpected output states.
+ expect(states, contains(newState),
+ reason: '($state,$c): Unexpected output state');
+ // Add to fringe the first time a state is seen.
+ if (unreachableStates.remove(newState)) {
+ nextStepList.add(newState);
+ }
+ }
+ }
+ if (unreachableStates.isNotEmpty) {
+ expect(unreachableStates.map(stateShortName).toList(), isEmpty,
+ reason: 'Should be reachable');
+ }
+ if (verbose) print('Forward states reachable in $step steps');
+ });
+
+ test('States distinguishable', () {
+ // Classify states into equivalence categories based on whether they
+ // can be distinguished by *n* transitions. Start with all states
+ // indistinguishable, then create new equivalence classes by splitting
+ // existing equivalence classes by whether they transition differ in
+ // whether to break on any input category, or whether they transition
+ // to states that are distinguishable in the existing equivalence.
+ // Continue until no further equivalence classes are introduced,
+ // the equivalence classes are trivial (one element each),
+ // or (as sanity check) at most `idStateCount` rounds.
+
+ var states = [for (var i = 0; i < stateLimit; i += automatonRowLength) i];
+ var eqClasses = [states];
+ var eq = Equivalence(eqClasses);
+ var stateCount = stateLimit ~/ automatonRowLength;
+ for (var r = 0; r <= stateCount; r++) {
+ // Sanity limit.
+ var nextEq = Equivalence.distinct(states);
+ // Upper bound.
+ for (var eqClass in eqClasses) {
+ for (var i = 0; i < eqClass.length - 1; i++) {
+ var state1 = eqClass[i];
+ nextPair:
+ for (var j = i + 1; j < eqClass.length; j++) {
+ var state2 = eqClass[j];
+ for (var c = 0; c < categoryCount; c++) {
+ var newState1 = move(state1, c);
+ var newState2 = move(state2, c);
+
+ if ((newState1 ^ newState2) & maskFlags != 0 ||
+ !eq.eq(newState1 & maskState, newState2 & maskState)) {
+ continue nextPair; // Keep distinguishable.
+ }
+ }
+ nextEq.equate(state1, state2);
+ }
+ }
+ }
+ var prevEqClasses = eqClasses;
+ eqClasses = nextEq.classes;
+ eq = nextEq;
+ if (prevEqClasses.length == eqClasses.length) break; // No progress.
+ if (prevEqClasses.length == states.length) {
+ // Maximal progress achieved.
+ if (verbose) print('Forwards states distinguishable in $r steps');
+ break;
+ }
+ }
+ expect(eqClasses, everyElement(hasLength(1)),
+ reason: 'Not distinguishable in $stateCount steps');
+ });
+
+ test('States backward reachable', () {
+ var states = {
+ stateBreak,
+ stateLF,
+ stateOther,
+ stateExtend,
+ stateL,
+ stateV,
+ stateT,
+ statePictographic,
+ // -- Only reachable through lookahead.
+ stateRegionalOdd,
+ stateRegionalSingle,
+ stateInC,
+ // -- Only reachable through lookahead.
+ stateRegionalEven,
+ // -- Not reachable, only used as start state.
+ stateEoT,
+ // Used as filler, and state after EoT.
+ stateEoTNoBreak,
+ stateLookaheadZWJPictographic,
+ stateLookaheadInC,
+ stateLookaheadInCL,
+ stateLookaheadRegionalEven,
+ stateLookaheadRegionalOdd,
+ };
+ var entryStates = <int>[stateEoTNoBreak, stateEoT];
+ var unreachableStates = {...states}..removeAll(entryStates);
+ var workList = <int>[...entryStates];
+ var nextStepList = <int>[];
+ var step = 1;
+
+ while ((workList.isNotEmpty || nextStepList.isNotEmpty) &&
+ unreachableStates.isNotEmpty) {
+ if (workList.isEmpty) {
+ step++;
+ workList = nextStepList;
+ nextStepList = [];
+ }
+ var state = workList.removeLast();
+ for (var c = 0; c < categoryCount; c++) {
+ var newState = moveBack(state, c) & maskState;
+ expect(states, contains(newState), reason: 'Unexpected output state');
+ if (unreachableStates.remove(newState)) {
+ nextStepList.add(newState);
+ }
+ }
+ if (unreachableStates.isEmpty) {
+ if (verbose) print('Backward states reachable in $step steps');
+ return;
+ }
+ }
+ if (unreachableStates.isNotEmpty) {
+ expect(unreachableStates.map(stateShortName).toList(), isEmpty,
+ reason: 'Should be reachable, not reached in $step steps');
+ }
+ });
+
+ test('Backward states distinguishable', () {
+ // Classify states into equivalence categories based on whether they
+ // can be distinguished by *n* transitions. Start with all states
+ // indistinguishable, then create new equivalence classes by splitting
+ // existing equivalence classes by whether they transition differ in
+ // whether to break on any input category, or whether they transition
+ // to states that are distinguishable in the existing equivalence.
+ // Continue until no further equivalence classes are introduced,
+ // the equivalence classes are trivial (one element each),
+ // or (as sanity check) at most `idStateCount` rounds.
+ //
+ // Assume that any lookahead state can be distinguished from any other
+ // state.
+ var states = [
+ for (var i = 0; i < backStateLimit; i += automatonRowLength) i
+ ];
+ var eqClasses = [states];
+ var eq = Equivalence(eqClasses);
+
+ var stateCount = backStateLimit ~/ automatonRowLength;
+ for (var r = 0; r <= stateCount; r++) {
+ var nextEq = Equivalence<int>.distinct(states);
+ // Upper bound.
+ for (var eqClass in eqClasses) {
+ for (var i = 0; i < eqClass.length - 1; i++) {
+ var state1 = eqClass[i];
+ nextPair:
+ for (var j = i + 1; j < eqClass.length; j++) {
+ var state2 = eqClass[j];
+ for (var c = 0; c < categoryCount; c++) {
+ var backState1 = moveBack(state1, c);
+ var backState2 = moveBack(state2, c);
+ if ((backState1 ^ backState2) & maskFlags != 0 ||
+ backState1 >= stateLookaheadMin ||
+ backState2 >= stateLookaheadMin ||
+ !eq.eq(backState1 & maskState, backState2 & maskState)) {
+ continue nextPair; // Keep distinguishable.
+ }
+ }
+ nextEq.equate(state1, state2);
+ }
+ }
+ }
+ var prevEqClasses = eqClasses;
+ eqClasses = nextEq.classes;
+ eq = nextEq;
+ if (prevEqClasses.length == eqClasses.length) break; // No progress.
+ if (prevEqClasses.length == states.length) {
+ // Maximal progress achieved.
+ if (verbose) print('Backwards states distinguishable in $r steps');
+ break;
+ }
+ }
+ expect(eqClasses, everyElement(hasLength(1)));
+ });
+ });
+}
+
+List<(List<String> parts, String kind)> testVariants(List<String> parts) {
+ // Create three variants of the test by replacing a character with another
+ // character in the same category, but opposite BMP-ness, if possible.
+ // - One where all possible characters are BMP characters.
+ // - One where all possible characters are non-BMP characters.
+ // - One where the BMP/non-BMP is the opposite of the original where possible.
+
+ var flipped = <List<int>>[]; // Flipped BMP.
+ var upper = <List<int>>[]; // Upper-planes.
+ var lower = <List<int>>[]; // BMP only.
+ const hasNonBmp = 1; // Has character that is non-BMP where base was BMP.
+ const hasBmp = 2; // Has character that is BMP where base was non-BMP.
+ var changes = 0; // Or'ed with `hasNonBmp` and `hasBmp`.
+ for (var part in parts) {
+ flipped.add([]);
+ upper.add([]);
+ lower.add([]);
+ for (var rune in part.runes) {
+ int category, runeLC = rune, runeFC = rune, runeUC = rune;
+ category = categoryOf(rune);
+ if (rune < 0x10000) {
+ runeLC = rune;
+ var other = upperChars[category];
+ if (other >= 0) {
+ changes |= hasNonBmp;
+ runeUC = runeFC = other;
+ }
+ } else {
+ runeUC = rune;
+ var other = lowerChars[category];
+ if (other >= 0) {
+ changes |= hasBmp;
+ runeLC = runeFC = other;
+ }
+ }
+ flipped.last.add(runeFC);
+ upper.last.add(runeUC);
+ lower.last.add(runeLC);
+ }
+ }
+ var variants = [
+ (parts, ''),
+ if (changes == hasNonBmp | hasBmp)
+ // If it's only one or the other, then upperCase or lowerCase has the
+ // same content.
+ ([...flipped.map(String.fromCharCodes)], '(Flip)'),
+ if (changes & hasNonBmp != 0)
+ ([...upper.map(String.fromCharCodes)], '(non-BMP)'),
+ if (changes & hasBmp != 0) ([...lower.map(String.fromCharCodes)], '(BMP)'),
+ // Also include a version where the case is not at start/end of input.
+ // (Wrap in control characters to ensure the breaks are still correct.)
+ (['\x00', ...parts, '\x00'], '(Wrapped)'),
+ ];
+ return variants;
+}
diff --git a/pkgs/characters/test/characters_test.dart b/pkgs/characters/test/characters_test.dart
new file mode 100644
index 0000000..7dae8fa
--- /dev/null
+++ b/pkgs/characters/test/characters_test.dart
@@ -0,0 +1,765 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Not all categories are currently used in tests.
+// They're retained in case we add more tests.
+// ignore_for_file: unreachable_from_main
+
+import 'dart:math';
+
+import 'package:characters/characters.dart';
+import 'package:test/test.dart';
+
+import 'src/unicode_tests.dart';
+import 'src/various_tests.dart';
+
+late Random random;
+
+void main([List<String>? args]) {
+ // Ensure random seed is part of every test failure message,
+ // and that it can be reapplied for testing.
+ var seed = (args != null && args.isNotEmpty)
+ ? int.parse(args[0])
+ : Random().nextInt(0x3FFFFFFF);
+ random = Random(seed);
+ group('[Random Seed: $seed]', tests);
+
+ group('characters', () {
+ test('operations', () {
+ var flag = '\u{1F1E9}\u{1F1F0}'; // Regional Indicators "DK".
+ var string = 'Hi $flag!';
+ expect(string.length, 8);
+ var cs = gc(string);
+ expect(cs.length, 5);
+ expect(cs.toList(), ['H', 'i', ' ', flag, '!']);
+ expect(cs.skip(2).toString(), ' $flag!');
+ expect(cs.skipLast(2).toString(), 'Hi ');
+ expect(cs.take(2).toString(), 'Hi');
+ expect(cs.takeLast(2).toString(), '$flag!');
+ expect(cs.getRange(1, 4).toString(), 'i $flag');
+ expect(cs.characterAt(1).toString(), 'i');
+ expect(cs.characterAt(3).toString(), flag);
+
+ expect(cs.contains('\u{1F1E9}'), false);
+ expect(cs.contains(flag), true);
+ expect(cs.contains('$flag!'), false);
+ expect(cs.containsAll(gc('$flag!')), true);
+
+ expect(cs.takeWhile((x) => x != ' ').toString(), 'Hi');
+ expect(cs.takeLastWhile((x) => x != ' ').toString(), '$flag!');
+ expect(cs.skipWhile((x) => x != ' ').toString(), ' $flag!');
+ expect(cs.skipLastWhile((x) => x != ' ').toString(), 'Hi ');
+
+ expect(cs.findFirst(gc(''))!.moveBack(), false);
+ expect(cs.findFirst(gc(flag))!.current, flag);
+ expect(cs.findLast(gc(flag))!.current, flag);
+ expect(cs.iterator.moveNext(), true);
+ expect(cs.iterator.moveBack(), false);
+ expect((cs.iterator..moveNext()).current, 'H');
+ expect(cs.iteratorAtEnd.moveNext(), false);
+ expect(cs.iteratorAtEnd.moveBack(), true);
+ expect((cs.iteratorAtEnd..moveBack()).current, '!');
+ });
+
+ testParts(gc('a'), gc('b'), gc('c'), gc('d'), gc('e'));
+
+ // Composite pictogram example, from https://en.wikipedia.org/wiki/Zero-width_joiner.
+ var flag = '\u{1f3f3}'; // U+1F3F3, Flag, waving. Category Pictogram.
+ var white = '\ufe0f'; // U+FE0F, Variant selector 16. Category Extend.
+ var zwj = '\u200d'; // U+200D, ZWJ
+ var rainbow = '\u{1f308}'; // U+1F308, Rainbow. Category Pictogram
+
+ testParts(gc('$flag$white$zwj$rainbow'), gc('$flag$white'), gc(rainbow),
+ gc('$flag$zwj$rainbow'), gc('!'));
+ });
+
+ group('CharacterRange', () {
+ test('new', () {
+ var range = CharacterRange('abc');
+ expect(range.isEmpty, true);
+ expect(range.moveNext(), true);
+ expect(range.current, 'a');
+ });
+ group('new.at', () {
+ test('simple', () {
+ var range = CharacterRange.at('abc', 0);
+ expect(range.isEmpty, true);
+ expect(range.moveNext(), true);
+ expect(range.current, 'a');
+
+ range = CharacterRange.at('abc', 1);
+ expect(range.isEmpty, true);
+ expect(range.moveNext(), true);
+ expect(range.current, 'b');
+
+ range = CharacterRange.at('abc', 1, 2);
+ expect(range.isEmpty, false);
+ expect(range.current, 'b');
+ expect(range.moveNext(), true);
+
+ range = CharacterRange.at('abc', 0, 3);
+ expect(range.isEmpty, false);
+ expect(range.current, 'abc');
+ expect(range.moveNext(), false);
+ });
+ test('complicated', () {
+ // Composite pictogram example, from https://en.wikipedia.org/wiki/Zero-width_joiner.
+ var flag = '\u{1f3f3}'; // U+1F3F3, Flag, waving. Category Pictogram.
+ var white = '\ufe0f'; // U+FE0F, Variant selector 16. Category Extend.
+ var zwj = '\u200d'; // U+200D, ZWJ
+ var rainbow = '\u{1f308}'; // U+1F308, Rainbow. Category Pictogram
+
+ var rbflag = '$flag$white$zwj$rainbow';
+ var string = '-$rbflag-';
+ var range = CharacterRange.at(string, 1);
+ expect(range.isEmpty, true);
+ expect(range.moveNext(), true);
+ expect(range.current, rbflag);
+
+ range = range = CharacterRange.at(string, 2);
+ expect(range.isEmpty, false);
+ expect(range.current, rbflag);
+
+ range = range = CharacterRange.at(string, 0, 2);
+ expect(range.isEmpty, false);
+ expect(range.current, '-$rbflag');
+
+ range = range = CharacterRange.at(string, 0, 2);
+ expect(range.isEmpty, false);
+ expect(range.current, '-$rbflag');
+
+ range = range = CharacterRange.at(string, 2, '-$rbflag'.length - 1);
+ expect(range.isEmpty, false);
+ expect(range.current, rbflag);
+ expect(range.stringBeforeLength, 1);
+
+ range = range = CharacterRange.at(string, 0, string.length);
+ expect(range.isEmpty, false);
+ expect(range.current, string);
+ });
+ });
+ });
+}
+
+void tests() {
+ test('empty', () {
+ expectGC(gc(''), []);
+ });
+ group('gc-ASCII', () {
+ for (var text in [
+ '',
+ 'A',
+ '123456abcdefab',
+ ]) {
+ test('"$text"', () {
+ expectGC(gc(text), charsOf(text));
+ });
+ }
+ test('CR+NL', () {
+ expectGC(gc('a\r\nb'), ['a', '\r\n', 'b']);
+ expectGC(gc('a\n\rb'), ['a', '\n', '\r', 'b']);
+ });
+ });
+ group('Non-ASCII single-code point', () {
+ for (var text in [
+ 'à la mode',
+ 'rødgrød-æble-ål',
+ ]) {
+ test('"$text"', () {
+ expectGC(gc(text), charsOf(text));
+ });
+ }
+ });
+ group('Combining marks', () {
+ var text = 'a\u0300 la mode';
+ test('"$text"', () {
+ expectGC(gc(text), ['a\u0300', ' ', 'l', 'a', ' ', 'm', 'o', 'd', 'e']);
+ });
+ var text2 = 'æble-a\u030Al';
+ test('"$text2"', () {
+ expectGC(gc(text2), ['æ', 'b', 'l', 'e', '-', 'a\u030A', 'l']);
+ });
+ });
+
+ group('Regional Indicators', () {
+ test('"🇦🇩🇰🇾🇪🇸"', () {
+ // Andorra, Cayman Islands, Spain.
+ expectGC(gc('🇦🇩🇰🇾🇪🇸'), ['🇦🇩', '🇰🇾', '🇪🇸']);
+ });
+ test('"X🇦🇩🇰🇾🇪🇸"', () {
+ // Other, Andorra, Cayman Islands, Spain.
+ expectGC(gc('X🇦🇩🇰🇾🇪🇸'), ['X', '🇦🇩', '🇰🇾', '🇪🇸']);
+ });
+ test('"🇩🇰🇾🇪🇸"', () {
+ // Denmark, Yemen, unmatched S.
+ expectGC(gc('🇩🇰🇾🇪🇸'), ['🇩🇰', '🇾🇪', '🇸']);
+ });
+ test('"X🇩🇰🇾🇪🇸"', () {
+ // Other, Denmark, Yemen, unmatched S.
+ expectGC(gc('X🇩🇰🇾🇪🇸'), ['X', '🇩🇰', '🇾🇪', '🇸']);
+ });
+ });
+
+ group('Hangul', () {
+ // Individual characters found on Wikipedia. Not expected to make sense.
+ test('"읍쌍된밟"', () {
+ expectGC(gc('읍쌍된밟'), ['읍', '쌍', '된', '밟']);
+ });
+ });
+
+ group('Unicode test', () {
+ for (var gcs in splitTests) {
+ test('[${testDescription(gcs)}]', () {
+ expectGC(gc(gcs.join()), gcs);
+ });
+ }
+ });
+
+ group('Emoji test', () {
+ for (var gcs in emojis) {
+ test('[${testDescription(gcs)}]', () {
+ expectGC(gc(gcs.join()), gcs);
+ });
+ }
+ });
+
+ group('Zalgo test', () {
+ for (var gcs in zalgo) {
+ test('[${testDescription(gcs)}]', () {
+ expectGC(gc(gcs.join()), gcs);
+ });
+ }
+ });
+}
+
+// Converts text with no multi-code-point grapheme clusters into
+// list of grapheme clusters.
+List<String> charsOf(String text) =>
+ text.runes.map(String.fromCharCode).toList();
+
+void expectGC(Characters actual, List<String> expected) {
+ var text = expected.join();
+
+ // Iterable operations.
+ expect(actual.string, text);
+ expect(actual.toString(), text);
+ expect(actual.toList(), expected);
+ expect(actual.length, expected.length);
+ if (expected.isNotEmpty) {
+ expect(actual.first, expected.first);
+ expect(actual.last, expected.last);
+ } else {
+ expect(() => actual.first, throwsStateError);
+ expect(() => actual.last, throwsStateError);
+ }
+ if (expected.length == 1) {
+ expect(actual.single, expected.single);
+ } else {
+ expect(() => actual.single, throwsStateError);
+ }
+ expect(actual.isEmpty, expected.isEmpty);
+ expect(actual.isNotEmpty, expected.isNotEmpty);
+ expect(actual.contains(''), false);
+ for (var char in expected) {
+ expect(actual.contains(char), true);
+ }
+ for (var i = 1; i < expected.length; i++) {
+ expect(actual.contains(expected[i - 1] + expected[i]), false);
+ }
+ expect(actual.skip(1).toList(), expected.skip(1).toList());
+ expect(actual.take(1).toList(), expected.take(1).toList());
+ expect(actual.skip(1).toString(), expected.skip(1).join());
+ expect(actual.take(1).toString(), expected.take(1).join());
+ expect(actual.getRange(1, 2).toString(), expected.take(2).skip(1).join());
+
+ if (expected.isNotEmpty) {
+ expect(actual.skipLast(1).toList(),
+ expected.take(expected.length - 1).toList());
+ expect(actual.takeLast(1).toList(),
+ expected.skip(expected.length - 1).toList());
+ expect(actual.skipLast(1).toString(),
+ expected.take(expected.length - 1).join());
+ expect(actual.takeLast(1).toString(),
+ expected.skip(expected.length - 1).join());
+ }
+ bool isEven(String s) => s.length.isEven;
+
+ expect(
+ actual.skipWhile(isEven).toList(), expected.skipWhile(isEven).toList());
+ expect(
+ actual.takeWhile(isEven).toList(), expected.takeWhile(isEven).toList());
+ expect(
+ actual.skipWhile(isEven).toString(), expected.skipWhile(isEven).join());
+ expect(
+ actual.takeWhile(isEven).toString(), expected.takeWhile(isEven).join());
+
+ expect(actual.skipLastWhile(isEven).toString(),
+ expected.toList().reversed.skipWhile(isEven).toList().reversed.join());
+ expect(actual.takeLastWhile(isEven).toString(),
+ expected.toList().reversed.takeWhile(isEven).toList().reversed.join());
+
+ expect(actual.where(isEven).toString(), expected.where(isEven).join());
+
+ expect((actual + actual).toString(), actual.string + actual.string);
+
+ // Iteration.
+ var it = actual.iterator;
+ expect(it.isEmpty, true);
+ for (var i = 0; i < expected.length; i++) {
+ expect(it.moveNext(), true);
+ expect(it.current, expected[i]);
+
+ expect(actual.elementAt(i), expected[i]);
+ expect(actual.skip(i).first, expected[i]);
+ expect(actual.characterAt(i).toString(), expected[i]);
+ expect(actual.getRange(i, i + 1).toString(), expected[i]);
+ }
+ expect(it.moveNext(), false);
+ for (var i = expected.length - 1; i >= 0; i--) {
+ expect(it.moveBack(), true);
+ expect(it.current, expected[i]);
+ }
+ expect(it.moveBack(), false);
+ expect(it.isEmpty, true);
+
+ // GraphemeClusters operations.
+ expect(actual.toUpperCase().string, text.toUpperCase());
+ expect(actual.toLowerCase().string, text.toLowerCase());
+
+ expect(actual.string, text);
+
+ expect(actual.containsAll(gc('')), true);
+ expect(actual.containsAll(actual), true);
+ if (expected.isNotEmpty) {
+ var steps = min(5, expected.length);
+ for (var s = 0; s <= steps; s++) {
+ var i = expected.length * s ~/ steps;
+ expect(actual.startsWith(gc(expected.sublist(0, i).join())), true);
+ expect(actual.endsWith(gc(expected.sublist(i).join())), true);
+ for (var t = s + 1; t <= steps; t++) {
+ var j = expected.length * t ~/ steps;
+ var slice = expected.sublist(i, j).join();
+ var gcs = gc(slice);
+ expect(actual.containsAll(gcs), true);
+ }
+ }
+ }
+
+ {
+ // Random walk back and forth.
+ var it = actual.iterator;
+ var pos = -1;
+ if (random.nextBool()) {
+ pos = expected.length;
+ it = actual.iteratorAtEnd;
+ }
+ var steps = 5 + random.nextInt(expected.length * 2 + 1);
+ var lastMove = false;
+ while (true) {
+ var back = false;
+ if (pos < 0) {
+ expect(lastMove, false);
+ expect(it.isEmpty, true);
+ } else if (pos >= expected.length) {
+ expect(lastMove, false);
+ expect(it.isEmpty, true);
+ back = true;
+ } else {
+ expect(lastMove, true);
+ expect(it.current, expected[pos]);
+ back = random.nextBool();
+ }
+ if (--steps < 0) break;
+ if (back) {
+ lastMove = it.moveBack();
+ pos -= 1;
+ } else {
+ lastMove = it.moveNext();
+ pos += 1;
+ }
+ }
+ }
+}
+
+Characters gc(String string) => Characters(string);
+
+void testParts(
+ Characters a, Characters b, Characters c, Characters d, Characters e) {
+ var cs = gc('$a$b$c$d$e');
+ test('$cs', () {
+ var it = cs.iterator;
+ expect(it.isEmpty, true);
+ expect(it.isNotEmpty, false);
+ expect(it.current, '');
+
+ // moveNext().
+ expect(it.moveNext(), true);
+ expect(it.isEmpty, false);
+ expect(it.current, '$a');
+ expect(it.moveNext(), true);
+ expect(it.isEmpty, false);
+ expect(it.current, '$b');
+ expect(it.moveNext(), true);
+ expect(it.isEmpty, false);
+ expect(it.current, '$c');
+ expect(it.moveNext(), true);
+ expect(it.isEmpty, false);
+ expect(it.current, '$d');
+ expect(it.moveNext(), true);
+ expect(it.isEmpty, false);
+ expect(it.current, '$e');
+ expect(it.moveNext(), false);
+ expect(it.isEmpty, true);
+ expect(it.current, '');
+
+ // moveBack().
+ expect(it.moveBack(), true);
+ expect(it.isEmpty, false);
+ expect(it.current, '$e');
+ expect(it.moveBack(), true);
+ expect(it.isEmpty, false);
+ expect(it.current, '$d');
+ expect(it.moveBack(), true);
+ expect(it.isEmpty, false);
+ expect(it.current, '$c');
+ expect(it.moveBack(), true);
+ expect(it.isEmpty, false);
+ expect(it.current, '$b');
+ expect(it.moveBack(), true);
+ expect(it.isEmpty, false);
+ expect(it.current, '$a');
+ expect(it.moveBack(), false);
+ expect(it.isEmpty, true);
+ expect(it.current, '');
+
+ // moveNext(int).
+ expect(it.moveTo(c), true);
+ expect(it.current, '$c');
+ expect(it.moveTo(b), false);
+ expect(it.moveTo(c), false);
+ expect(it.current, '$c');
+ expect(it.moveTo(d), true);
+ expect(it.current, '$d');
+
+ // moveBack(c).
+ expect(it.moveBackTo(c), true);
+ expect(it.current, '$c');
+ expect(it.moveBackTo(d), false);
+ expect(it.moveBackTo(c), false);
+ expect(it.moveBackTo(a), true);
+ expect(it.current, '$a');
+
+ // moveNext(n)
+ expect(it.moveBack(), false);
+
+ expect(it.moveNext(2), true);
+ expect(it.current, '$a$b');
+ expect(it.moveNext(4), false);
+ expect(it.current, '$c$d$e');
+ expect(it.moveNext(0), true);
+ expect(it.current, '');
+ expect(it.moveNext(1), false);
+ expect(it.current, '');
+
+ // moveBack(n).
+ expect(it.moveBack(2), true);
+ expect(it.current, '$d$e');
+ expect(it.moveBack(1), true);
+ expect(it.current, '$c');
+ expect(it.moveBack(3), false);
+ expect(it.current, '$a$b');
+ expect(it.moveBack(), false);
+
+ // moveFirst.
+ it.expandAll();
+ expect(it.current, '$a$b$c$d$e');
+ expect(it.collapseToFirst(b), true);
+ expect(it.current, '$b');
+ it.expandAll();
+ expect(it.current, '$b$c$d$e');
+ expect(it.collapseToFirst(a), false);
+ expect(it.current, '$b$c$d$e');
+
+ // moveBackTo
+ it.expandBackAll();
+ expect(it.current, '$a$b$c$d$e');
+ expect(it.collapseToLast(c), true);
+ expect(it.current, '$c');
+
+ // includeNext/includePrevious
+ expect(it.expandTo(e), true);
+ expect(it.current, '$c$d$e');
+ expect(it.expandTo(e), false);
+ expect(it.expandBackTo(b), true);
+ expect(it.current, '$b$c$d$e');
+ expect(it.expandBackTo(b), false);
+ expect(it.current, '$b$c$d$e');
+ expect(it.collapseToFirst(c), true);
+ expect(it.current, '$c');
+
+ // includeUntilNext/expandBackUntil
+ expect(it.expandBackUntil(a), true);
+ expect(it.current, '$b$c');
+ expect(it.expandBackUntil(a), true);
+ expect(it.current, '$b$c');
+ expect(it.expandUntil(e), true);
+ expect(it.current, '$b$c$d');
+ expect(it.expandUntil(e), true);
+ expect(it.current, '$b$c$d');
+
+ // dropFirst/dropLast
+ expect(it.dropFirst(), true);
+ expect(it.current, '$c$d');
+ expect(it.dropLast(), true);
+ expect(it.current, '$c');
+ it.expandBackAll();
+ it.expandAll();
+ expect(it.current, '$a$b$c$d$e');
+ expect(it.dropTo(b), true);
+ expect(it.current, '$c$d$e');
+ expect(it.dropBackTo(d), true);
+ expect(it.current, '$c');
+
+ it.expandBackAll();
+ it.expandAll();
+ expect(it.current, '$a$b$c$d$e');
+
+ expect(it.dropUntil(b), true);
+ expect(it.current, '$b$c$d$e');
+ expect(it.dropBackUntil(d), true);
+ expect(it.current, '$b$c$d');
+
+ it.dropWhile((x) => x == b.string);
+ expect(it.current, '$c$d');
+ it.expandBackAll();
+ expect(it.current, '$a$b$c$d');
+ it.dropBackWhile((x) => x != b.string);
+ expect(it.current, '$a$b');
+ it.dropBackWhile((x) => false);
+ expect(it.current, '$a$b');
+
+ // include..While
+ it.expandWhile((x) => false);
+ expect(it.current, '$a$b');
+ it.expandWhile((x) => x != e.string);
+ expect(it.current, '$a$b$c$d');
+ expect(it.collapseToFirst(c), true);
+ expect(it.current, '$c');
+ it.expandBackWhile((x) => false);
+ expect(it.current, '$c');
+ it.expandBackWhile((x) => x != a.string);
+ expect(it.current, '$b$c');
+
+ var cs2 = cs.replaceAll(c, gc(''));
+ var cs3 = cs.replaceFirst(c, gc(''));
+ var cs4 = cs.findFirst(c)!.replaceRange(gc('')).source;
+ var cse = gc('$a$b$d$e');
+ expect(cs2, cse);
+ expect(cs3, cse);
+ expect(cs4, cse);
+ var cs5 = cs4.replaceAll(a, c);
+ expect(cs5, gc('$c$b$d$e'));
+ var cs6 = cs5.replaceAll(gc(''), a);
+ expect(cs6, gc('$a$c$a$b$a$d$a$e$a'));
+ var cs7 = cs6.replaceFirst(b, a);
+ expect(cs7, gc('$a$c$a$a$a$d$a$e$a'));
+ var cs8 = cs7.replaceFirst(e, a);
+ expect(cs8, gc('$a$c$a$a$a$d$a$a$a'));
+ var cs9 = cs8.replaceAll(a + a, b);
+ expect(cs9, gc('$a$c$b$a$d$b$a'));
+ it = cs9.iterator;
+ it.moveTo(b + a);
+ expect('$b$a', it.current);
+ it.expandTo(b + a);
+ expect('$b$a$d$b$a', it.current);
+ var cs10 = it.replaceAll(b + a, e + e)!;
+ expect(cs10.currentCharacters, e + e + d + e + e);
+ expect(cs10.source, gc('$a$c$e$e$d$e$e'));
+ var cs11 = it.replaceRange(e);
+ expect(cs11.currentCharacters, e);
+ expect(cs11.source, gc('$a$c$e'));
+
+ var cs12 = gc('$a$b$a');
+ expect(cs12.split(b), [a, a]);
+ expect(cs12.split(a), [gc(''), b, gc('')]);
+ expect(cs12.split(a, 2), [gc(''), gc('$b$a')]);
+
+ expect(cs12.split(gc('')), [a, b, a]);
+ expect(cs12.split(gc(''), 2), [a, gc('$b$a')]);
+
+ expect(gc('').split(gc('')), [gc('')]);
+
+ var cs13 = gc('$b$a$b$a$b$a');
+ expect(cs13.split(b), [gc(''), a, a, a]);
+ expect(cs13.split(b, 1), [cs13]);
+ expect(cs13.split(b, 2), [gc(''), gc('$a$b$a$b$a')]);
+ expect(cs13.split(b, 3), [gc(''), a, gc('$a$b$a')]);
+ expect(cs13.split(b, 4), [gc(''), a, a, a]);
+ expect(cs13.split(b, 5), [gc(''), a, a, a]);
+ expect(cs13.split(b, 9999), [gc(''), a, a, a]);
+ expect(cs13.split(b, 0), [gc(''), a, a, a]);
+ expect(cs13.split(b, -1), [gc(''), a, a, a]);
+ expect(cs13.split(b, -9999), [gc(''), a, a, a]);
+
+ it = cs13.iterator..expandAll();
+ expect(it.current, '$b$a$b$a$b$a');
+ it.dropFirst();
+ it.dropLast();
+ expect(it.current, '$a$b$a$b');
+ expect(it.split(a).map((range) => range.current), ['', '$b', '$b']);
+ expect(it.split(a, 2).map((range) => range.current), ['', '$b$a$b']);
+ // Each split is after an *a*.
+ var first = true;
+ for (var range in it.split(a)) {
+ if (range.isEmpty) {
+ // First range is empty.
+ expect(first, true);
+ first = false;
+ continue;
+ }
+ // Later ranges are "b" that come after "a".
+ expect(range.current, '$b');
+ range.moveBack();
+ expect(range.current, '$a');
+ }
+
+ expect(it.split(gc('')).map((range) => range.current),
+ ['$a', '$b', '$a', '$b']);
+
+ expect(gc('').iterator.split(gc('')).map((range) => range.current), ['']);
+
+ expect(cs.startsWith(gc('')), true);
+ expect(cs.startsWith(a), true);
+ expect(cs.startsWith(a + b), true);
+ expect(cs.startsWith(gc('$a$b$c')), true);
+ expect(cs.startsWith(gc('$a$b$c$d')), true);
+ expect(cs.startsWith(gc('$a$b$c$d$e')), true);
+ expect(cs.startsWith(b), false);
+ expect(cs.startsWith(c), false);
+ expect(cs.startsWith(d), false);
+ expect(cs.startsWith(e), false);
+
+ expect(cs.endsWith(gc('')), true);
+ expect(cs.endsWith(e), true);
+ expect(cs.endsWith(d + e), true);
+ expect(cs.endsWith(gc('$c$d$e')), true);
+ expect(cs.endsWith(gc('$b$c$d$e')), true);
+ expect(cs.endsWith(gc('$a$b$c$d$e')), true);
+ expect(cs.endsWith(d), false);
+ expect(cs.endsWith(c), false);
+ expect(cs.endsWith(b), false);
+ expect(cs.endsWith(a), false);
+
+ it = cs.findFirst(b + c)!;
+ expect(it.startsWith(gc('')), true);
+ expect(it.startsWith(b), true);
+ expect(it.startsWith(b + c), true);
+ expect(it.startsWith(a + b + c), false);
+ expect(it.startsWith(b + c + d), false);
+ expect(it.startsWith(a), false);
+
+ expect(it.endsWith(gc('')), true);
+ expect(it.endsWith(c), true);
+ expect(it.endsWith(b + c), true);
+ expect(it.endsWith(a + b + c), false);
+ expect(it.endsWith(b + c + d), false);
+ expect(it.endsWith(d), false);
+
+ it.collapseToFirst(c);
+ expect(it.isPrecededBy(gc('')), true);
+ expect(it.isPrecededBy(b), true);
+ expect(it.isPrecededBy(a + b), true);
+ expect(it.isPrecededBy(a + b + c), false);
+ expect(it.isPrecededBy(a), false);
+
+ expect(it.isFollowedBy(gc('')), true);
+ expect(it.isFollowedBy(d), true);
+ expect(it.isFollowedBy(d + e), true);
+ expect(it.isFollowedBy(c + d + e), false);
+ expect(it.isFollowedBy(e), false);
+ });
+ test('replace methods', () {
+ // Unicode grapheme breaking character classes,
+ // represented by their first value.
+
+ var pattern = gc('\t'); // A non-combining entry to be replaced.
+ var non = gc('');
+
+ var c = otr + cr + pattern + lf + pic + pattern + zwj + pic + otr;
+ var r = c.replaceAll(pattern, non);
+ expect(r, otr + cr + lf + pic + zwj + pic + otr);
+ var ci = c.iterator..moveNextAll();
+ var ri = ci.replaceAll(pattern, non)!;
+ expect(ri.currentCharacters, otr + cr + lf + pic + zwj + pic + otr);
+ ci.dropFirst();
+ ci.dropLast();
+ expect(ci.currentCharacters, cr + pattern + lf + pic + pattern + zwj + pic);
+ expect(ci.currentCharacters.length, 7);
+ ri = ci.replaceAll(pattern, non)!;
+ expect(ri.currentCharacters, cr + lf + pic + zwj + pic);
+ expect(ri.currentCharacters.length, 2);
+ ci.dropFirst();
+ ci.dropLast(5);
+ expect(ci.currentCharacters, pattern);
+ ri = ci.replaceAll(pattern, non)!;
+ expect(ri.currentCharacters, cr + lf);
+ ci.moveNext(2);
+ ci.moveNext(1);
+ expect(ci.currentCharacters, pattern);
+ ri = ci.replaceAll(pattern, non)!;
+ expect(ri.currentCharacters, pic + zwj + pic);
+
+ c = otr + pic + ext + pattern + pic + ext + otr;
+ expect(c.length, 5);
+ ci = c.iterator..moveTo(pattern);
+ expect(ci.currentCharacters, pattern);
+ ri = ci.replaceAll(pattern, zwj)!;
+ expect(ri.currentCharacters, pic + ext + zwj + pic + ext);
+
+ c = reg + pattern + reg + reg;
+ ci = c.iterator..moveTo(pattern);
+ ri = ci.replaceRange(non);
+ expect(ri.currentCharacters, reg + reg);
+ expect(ri.moveNext(), true);
+ expect(ri.currentCharacters, reg);
+
+ c = inc + ine + pattern + ine + zwj + inc + ext;
+ // Breaks before and after `pattern`, before second `inc`.
+ expect(c.length, 4);
+ ci = c.iterator..moveTo(pattern);
+ ri = ci.replaceRange(non);
+ // Still breaks before second `inc`.
+ expect(ri.currentCharacters, inc + ine + ine + zwj);
+ expect(ri.source.length, 2);
+
+ ci = c.iterator..moveTo(pattern);
+ ri = ci.replaceRange(inl);
+ expect(ri.currentCharacters, inc + ine + inl + ine + zwj + inc + ext);
+ expect(ri.source.length, 1);
+ });
+}
+
+// Sample characters from each breaking algorithm category.
+// TODO: Generate these.
+final Characters ctl = gc('\x00'); // Control, NUL.
+final Characters cr = gc('\r'); // Carriage Return, CR.
+final Characters lf = gc('\n'); // Newline, NL.
+final Characters otr = gc(' '); // Other, Space.
+final Characters ext = gc('\u200c'); // Extend, Combining Grave Accent.
+final Characters spc = gc('\u0903'); // Spacing Mark, Devanagari Sign Visarga.
+final Characters pre = gc('\u0600'); // Prepend, Arabic Number Sign.
+final Characters zwj = gc('\u200d'); // Zero-Width Joiner.
+final Characters pic = gc('\u00a9'); // Extended Pictographic, Copyright.
+final Characters reg = gc('\u{1f1e6}'); // Regional Identifier "a".
+final Characters hanl = gc('\u1100'); // Hangul L, Choseong Kiyeok.
+final Characters hanv = gc('\u1160'); // Hangul V, Jungseong Filler.
+final Characters hant = gc('\u11a8'); // Hangul T, Jongseong Kiyeok.
+final Characters hanlv = gc('\uac00'); // Hangul LV, Syllable Ga.
+final Characters hanlvt = gc('\uac01'); // Hangul LVT, Syllable Gag.
+final Characters inc =
+ gc('\u0915'); // Other{InCL=Consonant}, Devanagari letter Ka.
+final Characters ine =
+ gc('\u0300'); // Extend{InCL=Extend}, Combining Grave Accent.
+final Characters inl =
+ gc('\u094d'); // Extend{InCL=Linker}, Devanagari Sign Virama.
diff --git a/pkgs/characters/test/src/equiv.dart b/pkgs/characters/test/src/equiv.dart
new file mode 100644
index 0000000..705167e
--- /dev/null
+++ b/pkgs/characters/test/src/equiv.dart
@@ -0,0 +1,201 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Equivalence class and equivalence builder.
+///
+/// Allows incrementally building equivalence classes by equating elements.
+///
+/// One way to use this class is to start with an empty `Equivalence`
+class Equivalence<T> {
+ /// Equivalence class IDs.
+ ///
+ /// Each entry is an index into this list.
+ /// If the entry is the same as its index, it is the canonical
+ /// ID for an equivalence class.
+ /// If not, the entry points to an earlier entry which is in the same
+ /// equivalence class.
+ /// Every entry can be followed iteratively to a canonical equivalence
+ /// class ID, and any two entries represent the same equivalence class
+ /// if they point to the same canonical ID.
+ final List<int> _equivalences;
+
+ /// Mapping from element to equivalence class ID.
+ ///
+ /// Two elements are considered equivalent (by [eq]) if their
+ /// IDs point to the same canonical equivalence class ID.
+ ///
+ /// When doing lookups, the map is updated with the canonical
+ /// ID if it's not currently pointing directly to that.
+ final Map<T, int> _class;
+
+ /// Each element of [equivalenceClasses] is an equivalence by itself.
+ ///
+ /// If any element is in more than one equivalence class,
+ /// if [allowCollapse] is `false` an error is raised, and
+ /// If [allowCollapse] is `true` the equivalence classes are
+ /// collapsed.
+ Equivalence(Iterable<Iterable<T>> equivalenceClasses,
+ {bool allowCollapse = false})
+ : _equivalences = [],
+ _class = {} {
+ for (var eqClass in equivalenceClasses) {
+ var newClass = _equivalences.length;
+ _equivalences.add(newClass);
+ for (var element in eqClass) {
+ var existing = _class[element];
+ if (existing == null) {
+ _class[element] = newClass;
+ } else if (existing != newClass) {
+ if (!allowCollapse) {
+ // Wasn't in the *same* iterable.
+ throw ArgumentError.value(equivalenceClasses, 'equivalenceClasses',
+ "Contains element '$element' more than once");
+ }
+ var c1 = _canonicalizeId(existing);
+ var c2 = _canonicalizeId(newClass);
+ if (c1 != c2) {
+ c1 = _equate(c1, c2);
+ }
+ _class[element] = c1;
+ newClass = c1;
+ }
+ }
+ }
+ }
+
+ /// All [elements] as distinct equivalence classes.
+ ///
+ /// A starting point for building an equivalence from scratch.
+ Equivalence.distinct(Iterable<T> elements)
+ : _equivalences = [],
+ _class = {} {
+ for (var element in elements) {
+ _add(element);
+ }
+ }
+
+ List<List<T>> get classes {
+ _optimize();
+ var result = [for (var i = 0; i < _equivalences.length; i++) <T>[]];
+ for (var MapEntry(:key, :value) in _class.entries) {
+ result[value].add(key);
+ }
+ return result;
+ }
+
+ /// Makes all elements point directly to their canonical equivalence class
+ /// ID, makes all canonical IDs be consecutive small integers, and
+ /// truncates the `_equivalences` list to only those values that are needed.
+ void _optimize() {
+ // Ensure all elements point to canonical class.
+ _canonicalize();
+ var head = 0;
+ var tail = _equivalences.length - 1;
+ // Reuse unreachable early entries in `_equivalences` for later equivalence.
+ // Invariant: 0..head-1 are canonical, tail=1..length are not.
+ outer:
+ while (head <= tail) {
+ if (_equivalences[head] == head) {
+ head++;
+ } else {
+ // _equivalences[head] is known not canonical.
+ while (head < tail) {
+ if (_equivalences[tail] != tail) {
+ tail--;
+ } else {
+ _equivalences[head] = _equivalences[tail] = head;
+ head++;
+ tail--;
+ continue outer;
+ }
+ }
+ break;
+ }
+ }
+ _canonicalize();
+ _equivalences.length = head;
+ }
+
+ void _canonicalize() {
+ _class.updateAll((_, id) => _canonicalizeId(id));
+ }
+
+ /// Adds element.
+ ///
+ /// If the element is already in the equivalence, nothing changes.
+ /// If not, it is added in a new singleton equivalence class.
+ void add(T element) {
+ if (!_class.containsKey(element)) _add(element);
+ }
+
+ /// Adds new element to equivalence class, in an equivalence class of its own.
+ int _add(T element) {
+ assert(_class[element] == null);
+ var newClass = _equivalences.length;
+ _equivalences.add(newClass);
+ _class[element] = newClass;
+ return newClass;
+ }
+
+ /// The canonical equivalence class ID of the element.
+ ///
+ /// Is `null` if the element has not been added to the equivalence.
+ int? _classOf(T element) {
+ var c = _class[element];
+ if (c != null) {
+ var e = _equivalences[c];
+ if (e == c) return e;
+ return _equivalences[c] = _canonicalizeId(e);
+ }
+ return null;
+ }
+
+ int _canonicalizeId(int id) {
+ while (true) {
+ var nextId = _equivalences[id];
+ if (nextId == id) return id;
+ _equivalences[id] = nextId;
+ id = nextId;
+ }
+ }
+
+ bool eq(T v1, T v2) {
+ var c1 = _classOf(v1);
+ return c1 != null && c1 == _classOf(v2);
+ }
+
+ /// Make two elements equivalent.
+ ///
+ /// This merges the equivalence classes of the two elements.
+ ///
+ /// If either element is not already in the equivalence,
+ /// it is added, as by [add].
+ void equate(T v1, T v2) {
+ var c1 = _classOf(v1);
+ var c2 = _classOf(v2);
+ if (c1 == null) {
+ _class[v1] = c2 ?? _add(v2);
+ } else if (c2 == null) {
+ _class[v2] = c1;
+ } else {
+ var newId = _equate(c1, c2);
+ if (c1 != newId) _class[v1] = newId;
+ if (c2 != newId) _class[v2] = newId;
+ }
+ }
+
+ /// Merge two equivalence classes.
+ int _equate(int c1, int c2) {
+ // Both must be canonical.
+ assert(_equivalences[c1] == c1);
+ assert(_equivalences[c2] == c2);
+ if (c1 == c2) return c1;
+ if (c1 < c2) {
+ _equivalences[c2] = c1;
+ return c1;
+ }
+ _equivalences[c1] = c2;
+ return c2;
+ }
+}
diff --git a/pkgs/characters/test/src/text_samples.dart b/pkgs/characters/test/src/text_samples.dart
new file mode 100644
index 0000000..1d643ea
--- /dev/null
+++ b/pkgs/characters/test/src/text_samples.dart
@@ -0,0 +1,2201 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// From: https://ko.wikipedia.org/wiki/%ED%95%9C%EA%B8%80
+/// text converted to Unicode NFD format.
+/// Text is available under the [Creative Commons Attribution-ShareAlike License](https://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License).
+const hangul = '''
+한글
+위키백과, 우리 모두의 백과사전.
+둘러보기로 가기검색하러 가기
+ 이 문서에는 옛 한글이 포함되어 있습니다.
+관련 글꼴이 설치되지 않은 경우, 일부 문자가 깨진 글자로 표시될 수 있습니다. 옛 한글 도움말을 참고하여 볼 수 있습니다.
+ 다른 뜻에 대해서는 한글 (동음이의) 문서를 참조하십시오.
+ 서적에 대해서는 훈민정음 문서를, 언어에 대해서는 한국어 문서를 참조하십시오.
+한글 · 조선글
+
+
+한글의 구조
+한글의 구조
+원래 이름 훈민정음(訓民正音)
+유형 음소문자
+표기 언어 한국어, 제주어, 찌아찌아어
+사용 시기 1443년 ~ 현재
+창제자 세종
+ISO 15924 Hang
+한국어의 표기법
+문자
+한글
+한자
+한글 점자
+한글전용
+국한문혼용
+이두
+향찰
+구결
+로마자 표기법
+국어의 로마자 표기법
+매큔-라이샤워 표기법
+예일 로마자 표기법
+ISO/TR 11941
+김복문 로마자 표기법
+양병선 로마자 표기법
+21세기 로마자 표기법
+음소문자의 역사
+원시 시나이 문자 (기원전 18 ~ 15세기)
+
+우가리트 문자 (기원전 15세기)
+원시 가나안 문자 (기원전 14세기)
+페니키아 문자 (기원전 11세기)
+고대 히브리 문자 (기원전 10세기)
+사마리아 문자 (기원전 6세기)
+아람 문자 (기원전 8세기)
+브라흐미 문자와 인도 문자 (기원전 6세기)
+티베트 문자 (7세기)
+크메르 문자/자와 문자 (9세기)
+히브리 문자 (기원전 3세기)
+시리아 문자 (기원전 2세기)
+나바테아 문자 (기원전 2세기)
+아랍 문자 (4세기)
+타나 문자 (18세기)
+소그드 문자 (기원전 4세기)
+돌궐 문자 (5세기)
+로바쉬 문자 (12세기)
+위구르 문자 (8세기)
+몽골 문자 (13세기)
+만주 문자 (16세기)
+팔라비 문자 (기원전 3세기)
+아베스타 문자 (4세기)
+그리스 문자 (기원전 9세기)
+에트루리아 문자 (기원전 8세기)
+로마자 (기원전 7세기)
+룬 문자 (2세기)
+고트 문자 (3세기)
+콥트 문자 (300년)
+아르메니아 문자 (405년)
+조지아 문자 (5세기)
+글라골 문자 (862년)
+키릴 문자 (10세기)
+아부르 문자 (1372년)
+고대 히스파니아 문자 (기원전 7세기)
+남아랍 문자 금석문 (기원전 9세기)
+그으즈 문자 (기원전 5 ~ 6세기)
+메로이트 문자 (기원전 3세기)
+오검 문자 (4세기)
+한글 1443년
+캐나다 문자 1840년
+주음부호 1913년
+전체 분류
+v • d • e • h
+한글은 발음기관과 하늘, 땅, 사람을 본따 고안된 음소문자로, 닿소리 14자에 홀소리 10자 총 24자로 구성되어 있다. "나랏말이 중국과 달라" 문제를 느낀 조선 세종이 한국어를 표기하기 위하여 1443년 창제, 1446년 반포하였다. 낱자가 음가만 표기하기 때문에 갈래로는 음소문자에 속하나, 네모 칸에 초성, 중성, 종성을 이루는 자모음을 한데 모아 쓰는 방식 때문에 음절문자의 특성도 일부 지닌다. 원래 글자 수는 닿소리 17자에 홀소리 11자 총 28자였으나 이후 4자가 소실, 24자만 쓰이게 되었다. 대한민국과 조선민주주의인민공화국과 옌볜 조선족 자치주에서는 공용 문자로, 인도네시아 부톤 섬에서는 찌아찌아어의 보조 문자로 채택되었다. 북한에서는 조선글(朝鮮-)이라 한다.
+
+세계에서 유일하게 만든 사람,만든 이유,만든 날짜를 아는 글자이다.
+
+
+목차
+1 명칭
+2 역사
+2.1 창제
+2.2 창제 논란
+2.3 조선
+2.4 근대 이후
+2.5 현대 이후
+3 창제원리
+4 구조
+4.1 낱자
+4.2 모아쓰기
+4.3 표기 가능한 글자 수와 소리나는 음절 개수
+5 한글의 유래
+6 한글에 관한 여러 이설
+6.1 파스파 문자 기원설
+6.2 기타 한글과 유사하다고 주장하는 문자
+6.2.1 가림토와 신대 문자
+6.2.2 구자라트 문자
+6.3 다른 언어에서의 한글 사용
+7 오해와 사실
+8 한글 자모일람
+8.1 방언 한글 자모
+8.2 고문 한글 자모
+8.3 복합원음와 보음
+9 관련 항목
+10 각주
+11 참고 문헌
+12 읽을거리
+13 외부 링크
+명칭
+창제 때는 백성(民)을 가르치는(訓) 바른(正) 소리(音), 훈민정음(訓民正音)이라 하였고, 줄여서 정음(正音)이라고도 했다.
+
+'한글'이라는 이름은 주시경이 ‘큰’, ‘바른’, ‘하나’를 뜻하는 고유어 ‘한’을 차용하여 지었다. 하지만 주시경의 의도한 뜻이 무엇이었는지는 명확히 밝혀진 바가 없다.
+
+한글 창제 당시에는 백성을 가르치는 바른소리라는 뜻으로 훈민정음이라 하였고, 줄여서 정음(正音)이라고도 하였다. 조선시대에는 지식층으로부터 경시되며, 본래의 이름으로 쓰지 않고 막연히 언문(諺文)[1], 언서(諺書)[2], 반절(反切)[3] 로 불리거나, 혹은 암클(여성들이 배우는 글), 아햇글(어린이들이 배우는 글)이라고 불렀다고 알려져 있다. (단, 암클, 아햇글이라는 표현은 그 출처가 불분명하다.) 1894년 갑오개혁 이후 국서(國書), 국문(國文)이라고 불렀고 혹은 조선글로 부르기도 하였는데 이것은 한국의 글이라는 보통 이름일 뿐이며, 고유명사로 한글이라는 이름이 널리 쓰이기 전에는 가갸, 정음 등으로 불렀다.
+
+처음 한글이라는 이름이 사용된 것에대한 명확한 기록은 없다. 다만 1913년 3월 23일 주시경이 ‘배달말글몯음(조선어문회, 朝鮮言文會)[4]’를 ‘한글모’로 바꾼 바 있고[5], 같은 해 9월 최남선의 출판사 ‘신문관(新文館)’에서 창간한 어린이 잡지 《아이들 보이》의 끝에 가로글씨로 '한글풀이’라 한 것이 있고[6], 1914년 4월에 ‘조선어강습원(朝鮮語講習院)’이 ‘한글배곧’으로 이름을 바꾼 것 등으로 볼 때 1913년 무렵 주시경이 처음으로 사용한 것으로 보이며, 1927년에는 조선어학회 회원들이 《한글》이라는 잡지를 매달 발간하였다. 한글이라는 명칭이 일반화된 것은 1928년 11월 11일 조선어연구회에서 가갸날을 한글날로 고쳐 부른 때부터라고 한다.
+
+현재 한글의 명칭을 대한민국에서는 한글로, 조선민주주의인민공화국에서는 조선어자모로 부르는데[7], 2001년 2월 중국 옌지에서 열린 ‘제5차 코리안 컴퓨터 처리 국제 학술 대회(ICCKL 2001)’에서는 남과 북, 해외 동포 학자들이 국제 표준화 기구(ISO)에 등록하기 위한 명칭으로 ‘정음(Jeong'eum)’을 쓰기로 합의하였다.
+
+다른 나라에서는 한글(Hangul/Hangeul)이라는 이름을 많이 쓰지만, 중국에서는 조선 자모(중국어: 朝鮮字母, 병음: Cháoxiǎn zìmǔ 차오셴 쯔무[*])와 같은 이름을 쓴다. 일본에서는 한글은 물론 한국어를 ‘한구루(한글)(ハングル)’로 부르기도 하는데, 이는 NHK 방송에서 한국어 강좌를 설립시에 대한민국의 ‘한국어’와 조선민주주의인민공화국의 ‘조선어’ 사이에서 중립적인 위치를 지키기 위해 한국어 강좌 명칭으로 '한글강좌'를 사용하여 많은 일본인들이 이를 보고 한글의 뜻을 한국어로 오해한 것이다.
+
+한글이라는 이름은 본디 문자의 이름이지만, 관용적으로는 한국어를 한글로 적은 것이라는 의미로 책이나 소프트웨어, 게임 등의 한국어 번역 작업을 한글화라 하고 번역본을 한글판이라 부르기도 한다. 그리고 한글 이름, 한글 지명처럼 고유어라는 의미로 쓰이기도 한다. 하지만 표준국어대사전에서는 두 의미 모두 등재되지 않았으며, 한국어화, 한국어판이 맞는 표현이다.
+
+역사
+
+《훈민정음 언해》의 서두
+창제
+한국은 삼국시대부터 이두(吏讀)와 구결(口訣)을 써 왔는데, 구결은 본래 한문에 구두(句讀)를 떼는 데 쓰기 위한 일종의 보조적 편법에 지나지 않았고, 이두는 비록 한국어를 표시함에 틀림이 없었지만 한국어를 자유자재로 적을 수 없었으며, 그 표기법의 일원성(一元性)이 없어서 설사 이두로써 족하다 해도 한자교육이 선행되어야 했다. 이러한 문자생활의 불편은 한자를 쓰지 않고도, 배우기 쉽고 쓰기 쉬운 새로운 글자의 출현이 절실히 요구되었다.
+
+이러한 사조가 세종때에 특히 두드러져 드디어 1443년 음력 12월에 문자혁명의 결실을 보게 되었다. 훈민정음 창제의 취지에 관하여는 세종이 손수 저술한 《훈민정음》 예의편(例義篇) 첫머리에 잘 나타나 있는데, 첫째 한국어는 중국말과 다르므로 한자를 가지고는 잘 표기할 수 없으며, 둘째 우리의 고유한 글자가 없어서 문자생활의 불편이 매우 심하고, 셋째 이런 뜻에서 새로 글자를 만들었으니 일상생활에 편하게 쓰라는 것이다.
+
+‘훈민정음’은 “백성을 가르치는 바른소리”라는 뜻으로[8], 세종의 어제 서문과 정인지 서(序)에서 분명히 밝히고 있는바, 당시까지 한문 의존에 따른 어려움을 근본적으로 극복하기 위해 한국어의 고유문자로서 창제되었다.
+
+한편, 훈민정음 창제 후 5년 뒤에 《동국정운(東國正韻)》이 간행되는데, 당시 조선에서 통용되던 한자음을 중국어 원음으로 교정하기 위한 책으로서 이것의 발음 표기에 훈민정음이 사용되고 있다. 따라서, 세종의 훈민정음 창제가 한자 및 한문의 폐지를 목적한 것은 아니라고 보이며, 훈민정음의 활용 범위가 상당히 넓었음을 짐작할 수 있다. 훈민정음에 대하여 반대하는 신하들이 있었는데 대표적으로 최만리는 상소를 올려 반대하였다. 그러나 세종은 "경이 운서를 아는가? 사성칠음에 자모가 몇이나 있는가? 만일 짐이 운서를 바로잡지 아니하면 누가 이를 바로잡을 것인가?" 라고 말하였다.
+
+처음 만들었을 때는 낱자 28글자와 성조를 나타내는 기호(방점)가 따로 있었으나, 지금은 ㅿ, ㆁ, ㆆ, ㆍ 네 글자와 성조 기호(방점)가 사라져서 24글자가 되었다. (제주도를 비롯한 몇 곳에서는 아직도 ㆍ의 발음이 남아 있다.)
+
+그 뒤로 몇 백 년에 걸쳐, 식자층은 주로 한글보다는 한문 위주의 문자 생활을 했지만 한자를 배울 수 없었던 백성과 여자들은 서로 주고 받는 편지나 계약서 등에 한글을 썼고, 궁궐에서 여자끼리 주고 받는 문서에 한글을 쓰기도 하였다.
+
+창제 논란
+오늘날 한글이라 불리는 글이 창제되었다는 사실이 세상에 알려진 것은 세종대왕 25년인 1443년이다. 창제 당시에 한글은 '훈민정음'이라 불렸으며 1446년 음력 9월 초에는 《훈민정음》(통칭 '해례본')이 책으로 엮어졌다. 이 사실은 정인지(鄭麟趾)가 쓴 〈서(序)〉로 확인된다.[9]
+
+지금까지 논란이 되고 있는 부분은 세종대왕이 홀로 글을 창제했는지, 집현전 학자들의 도움을 받았는지, 아니면 세종대왕의 명을 받아 집현전 학자들이 글을 창제했는지가 문제이다. 세종실록(世宗實錄)은 훈민정음을 세종대왕이 친히 만들었다고 기록하고 있으며[10], 누구의 도움을 받았다는 기록은 없다.[11]
+
+다시 말하면 시월 상친제언문이십팔자(是月 上親制諺文二十八字, 세종 25년, 12월 30일)에서 ‘상친제(上親制)’란 세종이 직접 한글을 만들었다는 뜻인데 '세종실록' 안에는 다른 업적에 관해서는 "친제"라는 말이 없었지만, 훈민정음(한글)에 관해서는 이렇게 확실하게 적어 놓았다는 것이다. 또한, 집현전 학자였던 정인지가 집필한 훈민정음 해례본의 서문 중에도 세종대왕이 직접 한글을 창제했다는 내용이 있다.[12]
+
+그러나 성현(成俔, 1439년~1504년)의 《용재총화(慵齋叢話)》 제7권에서 세종이 언문청을 세워 신숙주, 성삼문 등에게 글을 짓도록 명을 내렸다는 주장이 나왔다. 주시경은 《대한국어문법》(1906년)에서 세종이 집현전 학자들의 도움을 받아 한글을 창제했다고 썼다. 그리하여 한글 창제에 집현전 학자들이 관여했다는 설이 우세하게 되었으나, 이기문을 비롯한 학자들은 기록에 나타난 당시 정황을 볼 때 세종이 한글을 홀로 창제한 것이 아니라고 볼 이유가 없다고 주장하고 있다. 한글 창제 후 세종은 표음주의 표기가 일반적인 당대의 표기법과는 달리 형태주의 표기를 주로 활용하고 동국정운 같은 책을 편찬한 예에서 보듯이 국어와 중국어의 전반에 걸쳐 음운학 및 언어학에 깊은 조예와 지식을 보여 주었다. 집현전 학자들은 한글 창제 후 정음청에서 한글을 사용한 편찬 사업에만 관여했다는 것이다.[13]
+
+조선
+처음에 ‘훈민정음’으로 반포된 한글은 조선시대에는 '언문'이라고 불렸다. 이것은 《세종실록》에서 '상친제언문이십팔자(上親製諺文二十八字)'라고 한 것에 연유하는데 한자를 제외한 문자는 ‘언문’이라고 불렀기 때문이다. 여성들이 많이 한글을 썼기 때문에 ‘암클’ 등으로 낮추어 불리기도 하였으나, 궁중과 일부양반층, 백성들 사이에서도 사용되었다.
+
+1445년(세종 27) 4월에 훈민정음을 처음으로 사용하여 악장(樂章)인 《용비어천가》를 편찬하고, 1447년(세종 29) 5월에 간행하였다. 목판본 10권 5책 모두 125장에 달하는 서사시로서, 한글로 엮어진 책으로는 한국 최초의 것이 된다. 세종은 “어리석은 남녀노소 모두가 쉽게 깨달을 수 있도록” 《(세종실록》, 세종 26년) 《삼강행실도》를 훈민정음으로 번역하도록 했으며, 훈민정음이 반포된 뒤에는 일부 관리를 뽑을 때 훈민정음을 시험하도록 했다. 이후로 민간과 조정의 일부 문서에서 훈민정음을 써 왔다.
+
+이러한 한글 보급 정책에 따라 한글은 빠르게 퍼져 반 세기 만인 1500년대 지방의 노비 수준의 신분인 도공에게까지 쓰이게 되었다.[14]
+
+연산군은 1504년(연산군 10년) 훈민정음을 쓰거나 가르치는 것을 금했지만, 조정안에서 훈민정음을 쓰는 것을 금하지는 않았으며, 훈민정음을 아는 사람을 일부러 궁궐에 등용하기도 했다고 전한다.
+
+율곡 이이가 《대학》에 구결을 달고 언해한 《대학율곡언해》는 1749년에 간행되었다.[15]
+
+조선 중기 이후로 가사 문학, 한글 소설 등 한글로 창작된 문학이 유행하였고, 서간에서도 한글/정음이 종종 사용되었다.
+
+근대 이후
+1894년(조선 고종 31년) 갑오개혁에서 마침내 한글을 ‘국문’(國文 나랏글)이라고 하여, 1894년 11월 21일 칙령 제1호 공문식(公文式) 제14조[16] 및 1895년 5월 8일 칙령 제86호 공문식 제9조[17] 에서 법령을 모두 국문을 바탕으로 삼고 한문 번역을 붙이거나 국한문을 섞어 쓰도록 하였다. 1905년 지석영이 상소한 6개 항목의 〈신정국문(新訂國文)〉이 광무황제의 재가를 얻어 한글 맞춤법으로서 공포되었으나, 그 내용의 결점이 지적되면서 1906년 5월에 이능화(李能和)가 〈국문일정의견(國文一定意見)〉을 제출하는 등 논란이 되자, 당시 학부대신 이재곤(李載崑)의 건의로 1907년 7월 8일 대한제국 학부에 통일된 문자 체계를 확립하기 위한 국어 연구 기관으로 '국문연구소(國文硏究所)'가 설치되었는데, 국문연구소의 연구 성과는 1909년 12월 28일 학부에 제출한 보고서로서 〈국문연구의정안(國文硏究議定案)〉 및 어윤적, 이종일(李鍾一), 이억(李億), 윤돈구(尹敦求), 송기용(宋綺用), 유필근(柳苾根), 지석영, 이민응(李敏應)의 8위원 연구안으로 완결되었다.
+
+
+한글과 한문이 혼용되어 쓰인 매일신보 1944년 기사
+한편, 민간에서는 1906년 주시경이 《대한국어문법(大韓國語文法)》을 저술하여 1908년에 《국어문전음학(國語文典音學)》으로 출판하였으며, 1908년 최광옥(崔光玉)의 《대한문전(大韓文典)》, 1909년 유길준(兪吉濬)의 《대한문전(大韓文典)》, 김희상(金熙祥)의 《초등국어어전(初等國語語典)》, 1910년 주시경의 《국어문법(國語文法)》등이 출간되고, 이후에도 1911년 김희상의 《조선어전(朝鮮語典)》, 1913년 남궁억(南宮檍)의 〈조선문법(朝鮮文法)〉, 이규영(李奎榮)의 〈말듬〉, 1925년 이상춘(李常春)의 《조선어문법(朝鮮語文法)》 등으로 이어지면서, 1937년 최현배의 《우리말본》으로 집대성된다.
+
+이와 함께, 조선어학회와 같은 모임에서 꾸준히 애쓴 덕에 조금씩 한국어의 표준 문자로 힘을 얻게 되어 누구나 쓸 수 있게끔, 널리 퍼지게 되었다. '한글'이라는 이름은 주시경이 지은 것이며 조선어학회가 이 이름을 널리 알리기 시작한 것도 이 즈음이다. 일제강점기를 거쳐 광복을 맞이한 다음에는 남북한 모두 공문서와 법전에 한글을 쓰게 되었고, 끝내 조선어를 받아적는 글자로 자리잡게 되었다.
+
+현대 이후
+한국에서는 한글전용법이 시행되어 한자의 사용이 줄어들면서 1990년대 그 사용이 절정을 이루었다.[18] 이후 정부차원에서의 영어우대정책으로 인해 한글의 사용이 점차 줄고 있다는 지적이 있다.[19]
+
+2009년에는 문자가 없어 의사 소통에 곤란을 겪었던 인도네시아의 소수 민족인 찌아찌아족이 자신들의 언어 찌아찌아어의 표기 문자로 시범적으로 한글을 채택, 도입하였다. 그러나 주 정부의 반대와 소수만 배우는 문제 등으로 인해서 이 방법은 사용되지 않고 있다. 그리고 2012년에 솔로몬 제도에 있는 일부 주가 모어 표기문자로 한글을 도입하였다.[20]
+
+창제원리
+『훈민정음 해례본(訓民正音 解例本)』을 바탕으로 한글과 음양오행의 관계를 기록하였다.
+
+가. 모음은 음양의 원리를 기본으로 만들어졌다.
+
+기본 모음'ㆍ, ㅡ, ㅣ'를 보면 'ㆍ'(아래아)는 양(陽)인 하늘(天)을 본 떠 만들고, 'ㅡ'는 음(陰)인 땅(地)을 본 떠 만들었으며 'ㅣ'는 음과 양의 중간자인 인간(人)의 형상을 본 떠 만들었다. 천지인(天地人)은 단군사상에서 유래한 것으로 우주를 구성하는 주요한 요소인 하늘(·)과 땅(ㅡ), 사람(ㅣ)을 나타낸다.[21]
+『훈민정음 해례본』에 따르면 'ㅏ,ㅑ, ㅗ, ㅛ'는 'ㆍ'(아래아) 계열의 글자이다.
+'ㆍ'(아래아)의 속성은 양이다. 양의 특성은 위로의 상승, 바깥으로의 확장이다. 따라서 점을 위, 바깥 쪽에다 찍은 것.
+
+'ㅓ, ㅕ, ㅜ, ㅠ'는 그 반대로 'ㅡ' 계열의 글자이기 때문에 음의 속성을 따라, 하강, 수축의 뜻으로 점을 안쪽, 아래로 찍은 것.
+나. 자음은 오행을 바탕으로 만들어졌다.
+
+『훈민정음 해례본』에선 각 방위와 발음기관을 연결시키고, 해당 발음기관에서 나는 소리 또한 방위와 연관시키고 있다. 방위는 또 계절과 연결이 되므로, 결국 소리는 계절과 연결된다.
+(소리=방위=계절, 소리=계절) 계절은 봄, 여름, 늦여름, 가을, 겨울 순이므로, 소리 역시 어금닛소리(ㄱ, 봄), 혓소리(ㄴ, 여름), 입술소리(ㅁ, 늦여름), 잇소리(ㅅ, 가을), 목소리(ㅇ,겨울) 순으로 배열한다.
+
+『훈민정음 해례본』에서 기본 자음을 ㄱ,ㄴ,ㅁ,ㅅ,ㅇ,ㄹ 순으로 배열한 것은 오행 원리와 연관이 있다.
+자음과 오행의 관계 정리표
+속성 계절 방위 음성 음계
+목(木, 나무) 춘(春, 봄) 동(東, 동녘) 어금닛소리(ㄱ,ㅋ,ㄲ) 각(角)
+화(火, 불) 하(夏, 여름) 남, (南, 남녘) 혓소리(ㄴ,ㄷ,ㅌ,ㄸ) 치(徵)
+토(土, 흙) 계하 (季夏, 늦여름) 중앙(中, 無定) 입술소리(ㅁ,ㅂ,ㅍ,ㅃ,) 궁(宮)
+금(金, 쇠) 추(秋, 가을) 서(西, 서녘) 잇소리(ㅅ,ㅆ,ㅈ,ㅊ,ㅉ) 상(商)
+수(水, 물) 동(冬, 겨울) 북(北, 북녘) 목소리(ㅇ, ㅎ) 우(羽)
+구조
+한글은 낱소리 문자에 속하며, 낱자하나는 낱소리하나를 나타낸다. 낱소리는 닿소리(자음)와 홀소리(모음)로 이루어진다.
+
+한 소리마디는 첫소리(초성), 가운뎃소리(중성), 끝소리(종성)의 낱소리 세 벌로 이루어지는데, 첫소리와 끝소리에는 닿소리를 쓰고 가운뎃소리에는 홀소리를 쓴다. 한글은 낱자를 하나씩 풀어쓰지 않고 하나의 글자 마디로 모아쓰는 특징을 가지고 있다.
+
+낱자
+<nowiki />이 부분의 본문은 한글 낱자입니다.
+처음 한글 낱자는 닿소리 17자와 홀소리 11자로 총 28가지였다. 오늘날 한글 낱자에 쓰이지 않는 없어진 글자를 소실자(消失字)라 하는데, 닿소리 ㅿ(반시옷), ㆁ(옛이응), ㆆ(여린히읗)과 홀소리 ㆍ(아래아)의 네 글자이다. 이로써 현대 한글은 모두 24자로서 닿소리 14자와 홀소리 10자로 되었다. 낱자의 이름과 순서는 다음과 같다.
+
+훈민정음 창제 당시에는 낱자 자체의 칭호법(稱號法)은 표시되어 있지 않았고, 중종 때 최세진의 《훈몽자회》에 이르러 각 낱자의 명칭이 붙게 되었다. 하지만 기역, 디귿, 시옷은 이두식 한자어 명칭을 그대로 사용하여 일제시대의 언문 철자법을 거쳐 지금까지 그대로 사용하게 되었다.[22]
+
+각 자모에 대한 소릿값을 살펴보면, 첫소리 아·설·순·치·후(牙舌脣齒喉)와 반설·반치(反舌半齒)의 7음으로 구별하였고, 모음은 따로 구별하지 않았다. 이러한 7음과 각 자모의 독특한 배열 순서는 중국 운서(韻書)를 그대로 모방한 것이라고 여겨진다. 그리고 실제로 쓸 적에는 각 낱자를 독립시켜 소리 나는 차례대로 적지 않고, 반드시 닿소리와 홀소리를 어울려 쓰기로 하였으니, 곧 <· ㅡ ㅗ ㅜ ㅛ ㅠ >는 자음 아래에 쓰고, <ㅏ ㅓ ㅑ ㅕ>는 자음 오른쪽에 붙여 쓰기로 하였다. 즉 음절문자(音節文字)로 하되, 그 모양이 네모꼴이 되도록 하였으니, 이는 한자의 꼴에 영향을 받았기 때문이라 여겨진다.
+
+닿소리
+ㄱ ㄴ ㄷ ㄹ ㅁ ㅂ ㅅ ㅇ ㅈ ㅊ ㅋ ㅌ ㅍ ㅎ
+기윽/기역 니은 디읃/디귿 리을 미음 비읍 시읏/시옷 이응 지읒 치읓 키읔 티읕 피읖 히읗
+홀소리
+ㅏ ㅑ ㅓ ㅕ ㅗ ㅛ ㅜ ㅠ ㅡ ㅣ
+아 야 어 여 오 요 우 유 으 이
+이 스물네 가지를 바탕으로 하는데 모두 홑소리(단음)이고, 홑소리로 나타낼 수 없는 겹소리(복음)는 두세 홑소리를 어울러서 적되, 그 이름과 순서는 다음과 같다.
+
+겹닿소리
+ㄲ ㄸ ㅃ ㅆ ㅉ
+된기윽/쌍기역 된디읃/쌍디귿 된비읍/쌍비읍 된시읏/쌍시옷 된지읒/쌍지읒
+겹홀소리
+ㅐ ㅒ ㅔ ㅖ ㅘ ㅙ ㅚ ㅝ ㅞ ㅟ ㅢ
+애 얘 에 예 와 왜 외 워 웨 위 의
+소실자 닿소리
+ㅿ ㆁ ㆆ
+반시옷 옛이응 여린히읗
+유성 치경 마찰음 연구개 비음 성문 파열음
+반시옷은 알파벳의 z에 해당하는 음가를 가진 것으로 추정되며 여린히읗은 1을 강하게 발음 시 혀로 목구멍을 막으며 발음된다.
+
+현대 한글에서는 끝소리가 없으면 받침을 쓰지 않고 끝소리가 있을 때에만 홑받침 또는 겹받침을 쓰는데, 홑받침에는 모든 닿소리가 쓰이며, 겹받침에는 홑홀소리 아래에만 놓이는 겹닿소리 ㄲ(쌍기역)과 ㅆ(쌍시옷)과 따로 이름이 없지만 모든 홀소리 아래에 놓일 수 있는 겹받침으로만 쓰이는 겹닿소리가 있다. 모든 받침의 소릿값은 끝소리 규칙에 따라 8갈래로 모인다.[23]
+
+겹받침
+ㄲ ㅆ ㄳ ㄵ ㄶ ㄺ ㄻ ㄼ ㄽ ㄾ ㄿ ㅀ ㅄ
+받침의 소릿값
+ㄱ ㄴ ㄷ ㄹ ㅁ ㅂ ㅇ
+사전에 올릴 때에는 첫소리 > 가운뎃소리 > 끝소리의 순으로 정렬하되, 그 정렬 순서는 다음과 같다.
+
+정렬 순서
+첫소리 ㄱ ㄲ ㄴ ㄷ ㄸ ㄹ ㅁ ㅂ ㅃ ㅅ ㅆ ㅇ ㅈ ㅉ ㅊ ㅋ ㅌ ㅍ ㅎ
+가운뎃소리 ㅏ ㅐ ㅑ ㅒ ㅓ ㅔ ㅕ ㅖ ㅗ ㅘ ㅙ ㅚ ㅛ ㅜ ㅝ ㅞ ㅟ ㅠ ㅡ ㅢ ㅣ
+끝소리 ( ) ㄱ ㄲ ㄳ ㄴ ㄵ ㄶ ㄷ ㄹ ㄺ ㄻ ㄼ ㄽ ㄾ ㄿ ㅀ ㅁ ㅂ ㅄ ㅅ ㅆ ㅇ ㅈ ㅊ ㅋ ㅌ ㅍ ㅎ
+모아쓰기
+한글의 모든 낱자는 한데 모아쓰도록 하고 있으며, 닿소리를 가장 먼저 쓰고 그 오른쪽이나 아래에 홀소리를 적으며, 모든 받침은 닿소리와 홀소리 밑에 놓인다. 따라서, 글자 마디로 모아쓸 때는 다음과 같은 틀에 맞추어 쓴다.
+
+중성이 ㅏ, ㅐ, ㅑ, ㅒ, ㅓ, ㅔ, ㅕ, ㅖ, ㅣ일 때는 중성을 초성의 오른쪽에 붙여 쓴다.
+초성 중성
+초성 중성
+종성
+중성이 ㅗ, ㅛ, ㅜ, ㅠ, ㅡ일 때는 중성을 아래쪽에 붙여 쓴다. 종성이 있으면 그 아래 붙여 쓴다.
+초성
+중성
+초성
+중성
+종성
+중성이 ㅘ, ㅙ, ㅚ, ㅝ, ㅞ, ㅟ, ㅢ와 같이 아래쪽에 붙이는 모음과 오른쪽에 붙이는 모음의 복합일 때는 다음과 같이 아래쪽에 먼저, 그 다음 오른쪽에 붙여 쓴다. 종성은 마찬가지로 아래쪽에 붙여 쓴다.
+초성 중성
+중성
+초성 중성
+중성
+종성
+표기 가능한 글자 수와 소리나는 음절 개수
+현대 한글은 낱자를 엮어 11,172(첫소리 19 × 가운뎃소리 21 × (끝소리 27 + 끝소리 없음 1))글자 마디를 쓸 수 있다. 11,172자 중 399자는 무받침 글자이며 10,773자는 받침 글자이다. 사용 빈도는 KS X 1001 완성형 한글 코드에 선별된 2,350글자가 상위 99.9%로 알려져 있다.[출처 필요]
+
+어문 규정에 의하여, 현대 한국어 표준어에서 실제 사용하는 음절은 이보다 적다. 한국어의 소리는 첫소리+가운뎃소리(+끝소리)로 이루어지는데, 표준어에서 첫소리에는 19가지 닿소리가 모두 쓰이되 첫소리에 놓인 ㅇ은 소리 나지 않는다. 끝소리는 7종성법에 따라 7갈래로 모이며 끝소리가 없는 것까지 더하여 모두 8갈래이므로 현대 한국어의 발음은 첫소리 19 × 가운뎃소리 21 × 끝소리 8 = 3,192가지 소리가 된다.
+
+그런데, 표준 발음법을 따르면 구개음 ㅈ, ㅉ, ㅊ 뒤의 이중 모음 ㅑ, ㅒ, ㅕ, ㅖ, ㅛ, ㅠ는 단모음 ㅏ, ㅐ, ㅓ, ㅔ, ㅗ, ㅜ로 소리나므로 첫소리 3 × 가운뎃소리 6 × 끝소리 8 = 144소리가 빠지고, 아울러 소리나는 첫소리 (ㅇ이 아닌 첫소리 뒤에 오는)를 얹은 가운뎃소리 [ㅢ]는 ㄴ을 제외하면(ㄴ의 경우는 구개음화에 따른 다른 음소로 인정하고 있다.) [ㅣ]로 소리나므로(한글 맞춤법 제9항 및 표준 발음법 제5항 단서 3) 첫소리 17 × 가운뎃소리 1 × 끝소리 8 = 136 소리가 다시 빠진다. 따라서, 현재 한국어 표준어에서 실제 사용하는 소리마디는 3192 − 144 − 136 = 2,912가지가 된다.
+
+옛 한글의 경우, 2009년 10월 1일 발표된 유니코드 5.2에 포함되어 있는 옛 한글 자모의 총 개수는 초성 124개, 중성 95개, 종성 137개와 채움 문자 2개(초성, 중성)이다. 방점 2개는 현재 유니코드에 등록돼 있다. 방점을 제외하고, 총 조합 가능한 글자 마디 개수를 구한다면 다음과 같다.
+
+조합 가능한 한글 코드(125×96×138): 1,656,000개
+완성된 한글(124×95×138): 1,625,640개
+조합 가능한 비표준 한글: 총 16,989개
+채움 문자로만 구성된 한글: 1개
+초성, 종성만 있는 비표준 한글(124×137): 16,988개
+∴ 표준 한글 총 개수(조합 가능한 한글 코드 − 비표준 한글): 1,639,011개
+한글의 유래
+《세종실록》에 최만리가 훈민정음이 “고전(古篆)을 본땄다(倣)”라고 말한 기록이 있는데,[24][25] 이 말이 모호하기 때문에 여러 가지 해석이 있다. ‘고전’의 해석에는 한자의 전자체(篆字體)라는 설과 당시에 ‘몽고전자’(蒙古篆字)로도 불렸던 파스파 문자를 말하는 것이라는 설이 있다. 《환단고기》를 인정하는 사람은 이것이 가림토를 일컫는 말이라고 주장한다. 또한 ‘본땄다’(倣)에 대해서도 그 생김새만이 닮았을 뿐이라는 풀이와 만드는 데에 참고를 했다, 또는 모두 본땄다 등의 여러 가지 해석이 있다.
+
+1940년 《훈민정음》(해례본)이 발견되기 이전에는 훈민정음의 창제 원리를 설명한 문헌이 존재하지 않아 그 유래에 대한 여러 이론이 제기되었다. 그 이전에 제기되었던 주요 학설은 다음과 같다.
+
+발음 기관 상형설: 발음 기관을 상형했다는 설. 신경준(申景濬), 홍양호(洪良浩), 최현배
+전자 기원설: 한문 비석 등에 쓰이는 전자체에서 유래되었다는 설. 황윤석(黃胤錫), 이능화
+몽골 문자 기원설: 몽골문자(파스파)에서 유래했다는 설. 이익(李翼), 유희(柳僖), 게리 레드야드(Gari Ledyard)
+범자(梵字) 기원설: 불경과 함께 고대 인도 문자가 전해져, 그것에서 유래했다는 설. 성현, 이수광(李晬光)
+고대 문자 전래설: 훈민정음 이전 민간에서 전해지던 고대문자로부터 유래했다는 설.
+창문 상형설: 한옥의 창살 모양에서 유래했다는 설. 에카르트(P. A. Eckardt)
+서장(西藏)글자·오행(五行)이론.[26]
+《훈민정음》(해례본)에는 자음과 모음 각각에 대한 창제 원리가 상세히 설명되어 기본 자음 5자는 발음 기관의 모양을 추상화하고, 기본 모음 3자는 천지인 3재를 상징하여 창제되었고 다른 글자들이 획을 덧붙이는 방식으로 만들어졌다고 분명히 밝힘으로써, 여러 이설들을 잠재우고 정설이 되었다.
+
+한글에 관한 여러 이설
+파스파 문자 기원설
+
+(위) ’파스파 문자 ꡂ [k], ꡊ [t], ꡎ [p], ꡛ [s], ꡙ [l]와 그에 대응하는 것으로 여겨지는 한글 문자 ㄱ [k], ㄷ [t], ㅂ [p], ㅈ [ts], ㄹ [l].
+(아래) 중국어를 표현하기 위한 파스파 문자 ꡯ w, ꡤ v, ꡰ f의 파생과 그의 변형 문자 ꡜ [h]
+(왼쪽) 밑에 기호를 덧붙인 ꡧ [w][27] 와 유사한 중국어 표기용 한글 ㅱ w/m, ㅸ v, ㆄ f. 이 한글들은 기본자 ㅁ과 ㅇ에서 유래했다.
+1966년 컬럼비아 대학의 게리 레드야드 교수는 그의 논문에서 훈민정음에서 언급한 고전(古篆)을 몽고전자(蒙古篆字)로 해석하며 한글이 파스파 문자에서 그 기하학적 모양을 차용했다고 주장했다.[28] 레드야드는 그 근거로 당시 조선의 궁에는 파스파 문자가 쓰이고 있었고, 집현전 학자 일부는 파스파 문자를 잘 알고 있었다는 점을 들며, 한글의 기본 자음은 ㄱ, ㄷ, ㅂ, ㅈ, ㄹ라고 제시했다.
+
+레드야드에 따르면 이 다섯개의 글자는 그 모양이 단순화되어 파열음을 위한 가획을 할 수 있는 여지(ㅋ, ㅌ, ㅍ, ㅊ)를 만들어 냈다고 한다. 그는 전통적인 설명과는 다르게 비파열음 ㄴ, ㅁ, ㅅ은 기본자 ㄷ, ㅂ, ㅈ의 윗부분이 지워진 형태라 주장했다. 그는 ㅁ이 ㅂ의 윗부분을 지워서 파생되기는 쉽지만, ㅁ에서 ㅂ의 모양을 이끌어 내는 것은 불분명하다고 주장했다. 즉 다른 파열음과 같은 방법으로 파생되었다면 ㅂ의 모양은 ㅁ위에 한 획이 더해진 형태(ㄱ-ㅋ, ㄷ-ㅌ, ㅈ-ㅊ의 관계처럼)여야 한다는 것이다.
+
+ㆁ자의 유래에 대한 설명도 기존과 다르다. 많은 중국 단어는 ng으로 시작하는데 세종대왕 집권 시기 즈음의 중국에서는 앞에 나오는 ng는 [ŋ]으로 발음하거나 발음하지 않았으며, 이런 단어가 한국어로 차용되었을 경우에도 이는 묵음이었다. 또한 논리적으로 추론 가능한 ng음의 모양은 ㄱ에서 가로 획을 제한 모양인데, 이는 모음 ㅣ과 구분하기 힘들었을 것이다. 따라서 세종대왕은 가로 획을 제한 ㄱ에 묵음이라는 뜻의 ㅇ을 더해 ㆁ을 만들었을 것이라 주장한다. 이는 단어 중간 혹은 끝에서의 [ŋ]의 발음과 단어 처음 부분에서의 묵음을 상징적으로 나타낼 수 있는 것이었다.
+
+중국어를 표기하기 위한 다른 글자는 ㅱ이었는데 훈민정음은 이를 微(미)의 초성이라 설명했다. 이는 중국 방언에 따라 m 혹은 w로 발음되는데 한글에서는 ㅁ([m])과 ㅇ의 조합(이에 대응되는 파스파 문자에서는 [w]로 발음한다)으로 만들어졌다. 파스파 문자에서 글자 밑에 환형의 모양을 그리는 것은 모음 뒤의 w를 의미했다. 레드야드는 ㅱ자의 'ㅇ'모양이 이 과정을 통해 만들어 졌다고 주장했다.
+
+마지막 증거로 레드야드는 ㄷ의 좌측 상단에 작게 삐져나온 형상(입술 모양으로)은 파스파 문자의 d와 유사하다는 점을 들었다. 이러한 입술 모양은 티베트 문자의 d인 ད에서도 찾아볼 수 있다.
+
+만약 레드야드의 이러한 기원설이 사실이라면 한글은 파스파 문자→티베트 문자→브라미 문자→아람 문자를 거쳐 결국 중동 페니키아 문자의 일족에 속하게 된다. 하지만 파스파문자는 세계의 다른 고대문자들처럼 상형문자일 뿐만 아니라 각 글자가 한가지의 음을 나타내지 않고, 그 문자를 사용하던 언어권에 따라 각기 다른 음을 가졌을 것이기 때문에 한글과 같이 소리를 표기하는 문자와의 상관관계는 레드야드 혼자만이 인정하고 있다.
+
+이에 대해 2009년 국어학자 정광(鄭光)은 훈민정음이 36개 중국어 초성을 기본으로 하는 등 파스파 문자로부터 일부 영향을 받았지만 글자를 만든 원리가 서로 다르며, 자음과 모음을 분리하여 독창적으로 만든 문자라고 반론하였다.[29]
+
+기타 한글과 유사하다고 주장하는 문자
+생김새가 한글과 유사한 문자가 있어서 한글 이전의 고대문자에 영향을 받았다는 주장이 있는데, 우연히 닮은 경우이거나 신뢰할 수 없는 출처를 근거로 하고 있다고 설명된다.
+
+가림토와 신대 문자
+송호수는 1984년 《광장(廣場)》 1월호 기고문에서 〈천부경〉과 《환단고기》〈태백일사〉를 참조하여 한글이 단군 시대부터 있었고, 단군조선의 가림다문(加臨多文)에서 한글과 일본의 아히루 문자가 기원했다고 주장하였다.[30] 이에 대하여 국어학자 이근수는 《광장(廣場)》 2월호의 기고문을 통하여 과학적 논증이 없는 이상 추론일 뿐이며, 참조한 고서의 대부분이 야사임을 지적하였다.[31] 또한 가림토 문자는 《환단고기》의 저자로 의심되고 있는 이유립이 한글의 모(母)문자로 창작한 가공의 문자일 가능성이 높아[32] 이러한 주장은 역사학계 및 언어학계에서 인정받지 못하고 있다.
+
+일본의 신대 문자 중에서도 모습이 한글과 비슷한 것이 있어 이를 가림토와 연관이 있다고 주장하기도 하나, 신대 문자가 새겨져 있는 비석마다 문자의 모습이 달라 일관성이 없고 언어학자들이 추정하는 고대 일본어의 음운 구조와도 맞지 않으며,[33] 신대 문자가 기록되었다고 하는 유물 거의 전부가 18~19세기의 것이고 에도 시대 전의 것을 찾을 수 없는바, 신대 문자라는 것은 고대 일본에 문자가 있었다고 주장하기 위한 에도 시대의 위작이며, 특히 그 중에 한글과 유사한 것들은 오히려 한글을 모방한 것임이 밝혀졌다.[34]
+
+구자라트 문자
+1983년 9월 KBS가 방영한 8부작 다큐멘터리 《신왕오천축국전》은[35] 구자라트 문자를 소개하면서 '자음은 ㄱ, ㄴ, ㄷ, ㄹ, ㅁ, ㅅ, ㅇ 등이고, 모음은 ㅏ, ㅑ, ㅓ, ㅕ, ㅗ, ㅛ, ㅜ, ㅠ, ㅡ, ㅣ의 열 자가 꼭 같았으며, 받침까지도 비슷하게 쓰고 있었다'고 주장하였다.
+
+또한, 개천학회 회장 송호수[36]는 1984년 이를 인용하면서 '자음에서는 상당수가 같고, 모음은 10자가 꼭 같다는 것이다'라고 썼다. 그는 구자라트 문자가 가림토에서 비롯되었다고 주장했다.[37][38]
+
+그러나 구자라트 문자는 문자 구성상 자모로 완전히 분리되는 한글과는 달리 모든 자음이 딸림 모음을 수반하는 아부기다이며, 데바나가리 문자에서 수직선을 제거한 데바나가리 파생문자로서 다른 인도계 여러 문자와 친족 관계가 명확하게 밝혀져 있기 때문에 이는 구자라트 문자의 특정 글자체와 한글 사이의 표면적 유사성에 대한 착오일 뿐이다.[39]
+
+다른 언어에서의 한글 사용
+한글은 2009년에 처음으로 인도네시아의 소수 민족인 찌아찌아족의 언어인 찌아찌아어를 표기하는데 사용되었다.
+
+이밖에도 한국에서는 한글을 표기 문자로 보급하기 위하여 노력하고 있으며 2012년 솔로몬제도의 토착어를 한글로 표기하여 교육하는 활동이 시작됐다. 2012년 10월부터 시행된 것은 2개 언어이며 결과에 따라 솔로몬제도 전역으로 확대할 예정이다.[40]
+
+간혹, 영어 발음을 정확하게 표기하기 위해 옛 한글 등을 부활시킨 표기법을 연구하는 경우도 있으나, 이 역시 개인 연구자에 의한 것이다. 그리고 한국인이 아닌 사람이 만든 인공어618-Vuro나 인공 문자 井卜文(Jingbu Script) 등에서 일부 한글 또는 한글을 모티브로 한 문자를 개인 수준에서 사용한 예를 볼 수 있다.
+
+오해와 사실
+<nowiki />이 부분의 본문은 한글에 대한 오해입니다.
+유네스코의 세계기록유산에 등재된 것은 한글이 아니라, 책《훈민정음》(해례본)이다.
+유네스코 세계기록유산은 기록물이 담고 있는 내용이 아니라 기록물 자체만을 등록 대상으로 한다.
+실제의 한글은 모든 언어의 발음을 표기할 수 있는 것이 아니다. 또한, 현재의 한글은 창제 당시의 훈민정음보다 표현할 수 있는 발음 수가 적다.
+'모든 소리를 표현할 수 있다는 것'은 원래 언어학적 명제가 아니고, 창제 당시에 '모든 소리는 기본 5음의 조화로 이루어진다'는 사상을 배경으로 한 철학적 표현이다.
+한글 낱자는 모두 소릿값이 확정되어 있고 실제 한글 쓰임에서는 모아쓰기의 규칙도 정해져 있으므로, 한글로 표현되는 소리의 숫자는 본래 유한하며, 한글은 기본적으로 한국어에 맞추어져 있다.
+현재 한글은 한국어 발음에만 사용하고 있으나, 원래의 훈민정음에서는 모아쓰기가 좀 더 다양하며, 아울러 《동국정운》에 따르면 실제의 한국어 발음뿐만 아니라, 이론적인 한자음도 훈민정음으로써 표현하고 있다.
+한글은 언어의 이름이 아니라 글자의 이름이다.
+창제 당시의 이름인 '훈민정음'과 그 약칭인 '정음'도 본래 글자의 이름이었다.
+찌아찌아족의 찌아찌아어의 표기에는 사용되나 공식은 아니다.
+한글 자모일람
+방언 한글 자모
+ㄱㄲㄳㄴㄵㄶㄷㄸㄹㄺㄻㄼㄽㄾㄿ
+
+ㅀㅁㅂㅃㅄㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎㅏ
+
+ㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟ
+
+ㅠㅡㅢㅣㅤㅥㅦㅧㅨㅩㅪㅫㅬㅭㅮㅯ
+
+ㅰㅱㅲㅳㅴㅵㅶㅷㅸㅹㅺㅻㅼㅽㅾㅿ
+
+ㆀㆁㆂㆃㆄㆅㆆㆇㆈㆉㆊㆋㆌㆍㆎ
+
+고문 한글 자모
+ᄀᄁᄂᄃᄄᄅᄆᄇᄈᄉᄊᄋᄌᄍᄎᄏ
+
+ᄐᄑᄒᄓᄔᄕᄖᄗᄘᄙᄚᄛᄜᄝᄞᄟ
+
+ᄠᄡᄢᄣᄤᄥᄦᄧᄨᄩᄪᄫᄬᄭᄮᄯ
+
+ᄰᄱᄲᄳᄴᄵᄶᄷᄸᄹᄺᄻᄼᄽᄾᄿ
+
+ᅐᅑᅒᅓᅔᅕᅖᅗᅘᅙᅚᅛᅜᅝᅞᅟ
+
+복합원음와 보음
+ᅠᅡᅢᅣᅤᅥᅦᅧᅨᅩᅪᅫᅬᅭᅮᅯ
+
+ᅰᅱᅲᅳᅴᅵᅶᅷᅸᅹᅺᅻᅼᅽᅾᅿ
+
+ᆀᆁᆂᆃᆄᆅᆆᆇᆈᆉᆊᆋᆌᆍᆎᆏ
+
+ᆐᆑᆒᆓᆔᆕᆖᆗᆘᆙᆚᆛᆜᆝᆞᆟ
+
+ᆠᆡᆢᆨᆩᆪᆫᆬᆭᆮᆯ
+
+ᆰᆱᆲᆳᆴᆵᆶᆷᆸᆹᆺᆻᆼᆽᆾᆿ
+
+ᇀᇁᇂᇃᇄᇅᇆᇇᇈᇉᇊᇋᇌᇍᇎᇏ
+
+ᇐᇑᇒᇓᇔᇕᇖᇗᇘᇙᇚᇛᇜᇝᇞᇟ
+
+ᇠᇡᇢᇣᇤᇥᇦᇧᇨᇩᇪᇫᇬᇭᇮᇯ
+
+ᇰᇱᇲᇳᇴᇵᇶᇷᇸᇹᇺᇻᇼᇽᇾᇿ
+
+관련 항목
+국어
+세종대왕
+주시경
+한글의 우수성에 관한 논란
+문자
+한글 맞춤법
+한글 낱자
+한글 낱자 목록
+한국어
+한국어 로마자 표기법
+한글전용과 국한문혼용
+조선어 신철자법
+옛 한글
+훈민정음
+한글날
+한글 위키백과
+한글의 모든 글자
+한글을 표기하는 언어 목록
+한국어
+카리어
+꽈라아에어[41]
+찌아찌아어
+각주
+ 이것은 훈민정음 창제 당시부터 보인다. 예컨대, 《세종실록》은 훈민정음 창제를 上親制諺文二十八字…是謂訓民正音(주상께서 친히 언문 28자를 만들어 … 이것을 훈민정음이라 이른다)이라고 기록하는데, 이것은 한글의 이름이거나 또는 굳이 한글만 지칭한 것은 아니고 한자 이외의 문자를 통칭하는 표현이다. 예컨대 《순조실록(純祖實錄)》 9년 12월 2일 기사에 역관 현의순(玄義洵)이 대마도의 사정을 보고한 글 가운데 敎之以諺文名之曰假名(언문을 가르치는데, 그 이름을 일러 가나라고 한다)과 같은 문장이 있어, 일본 문자에 대해서도 언문이라는 표현이 사용됨을 볼 수 있다.) 또한 《세종실록》 28년 11월 8일자에 언문청이라는 한글을 보급하는 구실을 하는 기관 이름이 나온다.
+ 한문을 지칭하는 ‘진서(眞書)’와 대비되는 표현이다.
+ 諺文字母俗所謂反切二十七字(세간에서 이른바 반절 27자라고 하는 언문 자모). 최세진(崔世珍), 〈범례(凡例)〉, 《훈몽자회(訓蒙字會)》. 1527. 반절은 본래 2개의 한자로 다른 한자음을 표기하는 방법을 말하며, 이렇게 소리의 표기에 사용된 글자를 반절자(反切字)라고 한다. 당시 훈민정음이 이와 비슷한 용법으로 한자음 표기에도 사용되었기 때문에 반절이라고 불렸던 것으로 보인다.
+ 1908년 설립한 ‘국어연구학회(國語硏究學會)’가 1911년 9월에 명칭을 바꾼 것으로, 공식적으로 한글과 한문 표기를 나란히 사용했다.
+ ‘本會의 名稱을 한글모라 改稱하고 이 몯음을 세움몯음으로…’, 〈한글모세움몯음적발〉, 《한글모 죽보기》. 이규영. 1917.
+ 한글풀이의 수록이 확인되는 것은 1914년 3월의 제7호부터 1914년 7월의 제11호까지
+ 〈맞춤법〉, 《조선말규범집》. 북한(조선민주주의인민공화국) 내각직속 국어사정위원회. 1987.
+ “훈민정음은 백성(百姓) 가르치시는 정(正)한 소리라”(현대어 표기로 옮김), 〈세종어제훈민정음〉, 《월인석보》. 1459년.
+ 癸亥冬我殿下創制正音二十八字略揭例義以示之名曰訓民正音 (계해년 겨울, 우리 전하께서 정음 28자를 창제하시어, 간략하게 예를 들어 보이시고 이름을 훈민정음이라 하셨다). 정인지,〈서(序)〉, 《훈민정음》. 1446년.
+ “上親制諺文二十八字…是謂訓民正音”, 《세종실록》 25년 12월.
+ 정인지는 훈민정음을 지은 세종이 집현전 학자들에게 ‘해설서’의 편찬을 명했다고 적고 있다. 遂命詳加解釋以喩諸人…謹作諸解及例以敍其梗槪 (마침내, 해석을 상세히 더하여 사람들을 깨우치라고 명하시어… 여러 풀이와 예를 지어 그 개요를 펴내니), 정인지, 〈서〉, 《훈민정음》. 1446년.
+ "錢下槍制', 《훈민정음 해례본》
+ 〈훈민정음 친제론〉, 이기문, 《한국문화》제13집. 서울대학교 규장각 한국학연구원, 1992년.
+ '라랴러려' 분청사기..."16세기 지방 하층민도 한글 사용".YTN.2011-09-08.
+ [1]
+ 第十四條 法律勅令總以國文爲本漢文附譯或用國漢文
+ 第九條 法律命令은 다 國文으로써 本을 삼꼬 漢譯을 附하며 或國漢文을 混用홈
+ 세계는 지금 '언어전쟁' 중
+ “한글 홀대하는 사회”. 2012년 11월 30일에 원본 문서에서 보존된 문서. 2010년 2월 2일에 확인함.
+ 솔로몬제도 일부 주(州)서 표기문자로 한글 채택
+ 최현철 기자 (2010년 10월 20일). “‘천지인’ 개발자 특허권 기부 … 표준 제정 임박”. 중앙일보. 2012년 11월 15일에 확인함.
+ [북녘말] 기윽 디읃 시읏 / 김태훈 : 칼럼 : 사설.칼럼 : 뉴스 : 한겨레
+ 然ㄱㆁㄷㄴㅂㅁㅅㄹ八字可足用也如ㅂ·ㅣㅅ곶爲梨花여ㅿ의갗爲狐皮而ㅅ字可以通用故只用ㅅ字 ,훈민정음 해례 종성해
+ 其字倣古篆分爲初中終聲合之然後乃成字 : (그 글자는 옛 전자(篆字)를 모방하고, 초·중·종성으로 나뉘어 그것을 합한 연후에 글자를 이룬다.) 《세종실록》 25년 12월 30일.
+ 象形而字倣古篆因聲而音叶七調 (물건의 형상을 본떠서 글자는 고전을 모방하고, 소리(聲)로 인(因)하여 음(音)은 칠조(七調)에 맞아). 《세종실록》28년 9월 29일. 이 기사는 《훈민정음》의 정인지 〈서(序)〉를 옮겨 놓은 것이다.
+ 글로벌세계대백과, 〈양반관료의 문화〉, 한글 창제.
+ 이 문자들은 확실히 증명되진 않았다. 어떤 필사본에는 ꡜ h에서 파생된 저 세 글자 모두 아래쪽이 환형으로 되어있기도 하다.
+ "The Korean Language Reform of 1446", Gari Ledyard. (1966)
+ [Why] 세종대왕 한글 창제가 '표절' 누명 쓰고 있다고?, 《조선닷컴》, 2009.10.10.
+ "한글은 檀君시대부터 있었다" 송호수 교수 주장에 學界관심, 《경향신문》, 1984.1.12.
+ 한글 世宗전 創製 "터무니없다" 李覲洙교수, 宋鎬洙교수 主張 반박, 《경향신문》, 1984.2.6.
+ 문명, 《만들어진 한국사》, 파란, 2010
+ "日 神代文字는 한글의 僞作이다", 《경향신문》, 1985.7.17.
+ MBC 다큐멘터리 “미스터리 한글, 해례6211의 비밀”, 2007. 10. 7. 방송
+ 《新往五天竺國傳(신왕오천축국전)》. 문순태, KBS 특별취재반. 한국방송사업단, 1983. 참조
+ 당시 보도에는 S베일러대 교수로 소개되었다.
+ 〈한글은 세종 이전에도 있었다〉, 송호수,《廣場(광장)》1984년 1월호. 세계평화교수협의회.
+ 일본 神代文字 논란[깨진 링크(과거 내용 찾기)], 충북대학 국어국문학과 국어학강의실
+ 〈한글과 비슷한(?) 구자라트 문자〉, 김광해, 《새국어소식》1999년 10월호. 한국어문진흥회
+ [2]
+ 섬나라 솔로몬제도 2개주도 한글 쓴다
+참고 문헌
+〈한글〉, 《한국민족문화대백과》. 한국학중앙연구원.
+읽을거리
+Portal icon 한국 포털
+Portal icon 문화 포털
+한글날에 생각해보는 훈민정음 미스터리. 김현미.《신동아》 2006년 10월호.
+히브리문자 기원설을 계기로 본 훈민정음. 이기혁. 《신동아》 1997년 5월호.
+어느 기하학자의 한글 창제. 《동아일보》, 2007-10-19. (영어의 v, f, θ, ð, l 등의 발음을 한글로 표기하기 - 고등과학원 최재경 교수의 제안)
+'한글 연구가' 최성철씨 "이젠 한글표기법 독립운동할 때". 《동아일보》, 2006-10-09.
+한글과 코드: 한글과 컴퓨터 코드에 관하여
+외부 링크
+ 위키미디어 공용에 관련된 미디어 자료와 분류가 있습니다.
+한글 (분류)
+ 위키낱말사전에
+관련된 항목이 있습니다.
+한글
+디지털한글박물관
+한글학회
+한글재단
+국립국어원
+한글 듣기 테스트
+"한글 자음 쓰기 영상" - 유튜브
+"한글 모음 쓰기 영상" - 유튜브
+Heckert GNU white.svgCc.logo.circle.svg 이 문서에는 다음커뮤니케이션(현 카카오)에서 GFDL 또는 CC-SA 라이선스로 배포한 글로벌 세계대백과사전의 "양반관료의 문화" 항목을 기초로 작성된 글이 포함되어 있습니다.
+''';
+
+/// Sample ASCII text: Genesis from the King James Bible (1604).
+const genesis = '''
+Genesis
+
+1.
+In the beginning God created the heaven and the earth.
+And the earth was without form, and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters.
+And God said, Let there be light: and there was light.
+And God saw the light, that it was good: and God divided the light from the darkness.
+And God called the light Day, and the darkness he called Night. And the evening and the morning were the first day.
+And God said, Let there be a firmament in the midst of the waters, and let it divide the waters from the waters.
+And God made the firmament, and divided the waters which were under the firmament from the waters which were above the firmament: and it was so.
+And God called the firmament Heaven. And the evening and the morning were the second day.
+And God said, Let the waters under the heaven be gathered together unto one place, and let the dry land appear: and it was so.
+And God called the dry land Earth; and the gathering together of the waters called he Seas: and God saw that it was good.
+And God said, Let the earth bring forth grass, the herb yielding seed, and the fruit tree yielding fruit after his kind, whose seed is in itself, upon the earth: and it was so.
+And the earth brought forth grass, and herb yielding seed after his kind, and the tree yielding fruit, whose seed was in itself, after his kind: and God saw that it was good.
+And the evening and the morning were the third day.
+And God said, Let there be lights in the firmament of the heaven to divide the day from the night; and let them be for signs, and for seasons, and for days, and years:
+And let them be for lights in the firmament of the heaven to give light upon the earth: and it was so.
+And God made two great lights; the greater light to rule the day, and the lesser light to rule the night: he made the stars also.
+And God set them in the firmament of the heaven to give light upon the earth,
+And to rule over the day and over the night, and to divide the light from the darkness: and God saw that it was good.
+And the evening and the morning were the fourth day.
+And God said, Let the waters bring forth abundantly the moving creature that hath life, and fowl that may fly above the earth in the open firmament of heaven.
+And God created great whales, and every living creature that moveth, which the waters brought forth abundantly, after their kind, and every winged fowl after his kind: and God saw that it was good.
+And God blessed them, saying, Be fruitful, and multiply, and fill the waters in the seas, and let fowl multiply in the earth.
+And the evening and the morning were the fifth day.
+And God said, Let the earth bring forth the living creature after his kind, cattle, and creeping thing, and beast of the earth after his kind: and it was so.
+And God made the beast of the earth after his kind, and cattle after their kind, and every thing that creepeth upon the earth after his kind: and God saw that it was good.
+And God said, Let us make man in our image, after our likeness: and let them have dominion over the fish of the sea, and over the fowl of the air, and over the cattle, and over all the earth, and over every creeping thing that creepeth upon the earth.
+So God created man in his own image, in the image of God created he him; male and female created he them.
+And God blessed them, and God said unto them, Be fruitful, and multiply, and replenish the earth, and subdue it: and have dominion over the fish of the sea, and over the fowl of the air, and over every living thing that moveth upon the earth.
+And God said, Behold, I have given you every herb bearing seed, which is upon the face of all the earth, and every tree, in the which is the fruit of a tree yielding seed; to you it shall be for meat.
+And to every beast of the earth, and to every fowl of the air, and to every thing that creepeth upon the earth, wherein there is life, I have given every green herb for meat: and it was so.
+And God saw every thing that he had made, and, behold, it was very good. And the evening and the morning were the sixth day.
+
+2.
+Thus the heavens and the earth were finished, and all the host of them.
+And on the seventh day God ended his work which he had made; and he rested on the seventh day from all his work which he had made.
+And God blessed the seventh day, and sanctified it: because that in it he had rested from all his work which God created and made.
+These are the generations of the heavens and of the earth when they were created, in the day that the LORD God made the earth and the heavens,
+And every plant of the field before it was in the earth, and every herb of the field before it grew: for the LORD God had not caused it to rain upon the earth, and there was not a man to till the ground.
+But there went up a mist from the earth, and watered the whole face of the ground.
+And the LORD God formed man of the dust of the ground, and breathed into his nostrils the breath of life; and man became a living soul.
+And the LORD God planted a garden eastward in Eden; and there he put the man whom he had formed.
+And out of the ground made the LORD God to grow every tree that is pleasant to the sight, and good for food; the tree of life also in the midst of the garden, and the tree of knowledge of good and evil.
+And a river went out of Eden to water the garden; and from thence it was parted, and became into four heads.
+The name of the first is Pison: that is it which compasseth the whole land of Havilah, where there is gold;
+And the gold of that land is good: there is bdellium and the onyx stone.
+And the name of the second river is Gihon: the same is it that compasseth the whole land of Ethiopia.
+And the name of the third river is Hiddekel: that is it which goeth toward the east of Assyria. And the fourth river is Euphrates.
+And the LORD God took the man, and put him into the garden of Eden to dress it and to keep it.
+And the LORD God commanded the man, saying, Of every tree of the garden thou mayest freely eat:
+But of the tree of the knowledge of good and evil, thou shalt not eat of it: for in the day that thou eatest thereof thou shalt surely die.
+And the LORD God said, It is not good that the man should be alone; I will make him an help meet for him.
+And out of the ground the LORD God formed every beast of the field, and every fowl of the air; and brought them unto Adam to see what he would call them: and whatsoever Adam called every living creature, that was the name thereof.
+And Adam gave names to all cattle, and to the fowl of the air, and to every beast of the field; but for Adam there was not found an help meet for him.
+And the LORD God caused a deep sleep to fall upon Adam and he slept: and he took one of his ribs, and closed up the flesh instead thereof;
+And the rib, which the LORD God had taken from man, made he a woman, and brought her unto the man.
+And Adam said, This is now bone of my bones, and flesh of my flesh: she shall be called Woman, because she was taken out of Man.
+Therefore shall a man leave his father and his mother, and shall cleave unto his wife: and they shall be one flesh.
+And they were both naked, the man and his wife, and were not ashamed.
+
+3.
+Now the serpent was more subtil than any beast of the field which the LORD God had made. And he said unto the woman, Yea, hath God said, Ye shall not eat of every tree of the garden?
+And the woman said unto the serpent, We may eat of the fruit of the trees of the garden:
+But of the fruit of the tree which is in the midst of the garden, God hath said, Ye shall not eat of it, neither shall ye touch it, lest ye die.
+And the serpent said unto the woman, Ye shall not surely die:
+For God doth know that in the day ye eat thereof, then your eyes shall be opened, and ye shall be as gods, knowing good and evil.
+And when the woman saw that the tree was good for food, and that it was pleasant to the eyes, and a tree to be desired to make one wise, she took of the fruit thereof, and did eat, and gave also unto her husband with her; and he did eat.
+And the eyes of them both were opened, and they knew that they were naked; and they sewed fig leaves together, and made themselves aprons.
+And they heard the voice of the LORD God walking in the garden in the cool of the day: and Adam and his wife hid themselves from the presence of the LORD God amongst the trees of the garden.
+And the LORD God called unto Adam, and said unto him, Where art thou?
+And he said, I heard thy voice in the garden, and I was afraid, because I was naked; and I hid myself.
+And he said, Who told thee that thou wast naked? Hast thou eaten of the tree, whereof I commanded thee that thou shouldest not eat?
+And the man said, The woman whom thou gavest to be with me, she gave me of the tree, and I did eat.
+And the LORD God said unto the woman, What is this that thou hast done? And the woman said, The serpent beguiled me, and I did eat.
+And the LORD God said unto the serpent, Because thou hast done this, thou art cursed above all cattle, and above every beast of the field; upon thy belly shalt thou go, and dust shalt thou eat all the days of thy life:
+And I will put enmity between thee and the woman, and between thy seed and her seed; it shall bruise thy head, and thou shalt bruise his heel.
+Unto the woman he said, I will greatly multiply thy sorrow and thy conception; in sorrow thou shalt bring forth children; and thy desire shall be to thy husband, and he shall rule over thee.
+And unto Adam he said, Because thou hast hearkened unto the voice of thy wife, and hast eaten of the tree, of which I commanded thee, saying, Thou shalt not eat of it: cursed is the ground for thy sake; in sorrow shalt thou eat of it all the days of thy life;
+Thorns also and thistles shall it bring forth to thee; and thou shalt eat the herb of the field;
+In the sweat of thy face shalt thou eat bread, till thou return unto the ground; for out of it wast thou taken: for dust thou art, and unto dust shalt thou return.
+And Adam called his wife's name Eve; because she was the mother of all living.
+Unto Adam also and to his wife did the LORD God make coats of skins, and clothed them.
+And the LORD God said, Behold, the man is become as one of us, to know good and evil: and now, lest he put forth his hand, and take also of the tree of life, and eat, and live for ever:
+Therefore the LORD God sent him forth from the garden of Eden, to till the ground from whence he was taken.
+So he drove out the man; and he placed at the east of the garden of Eden Cherubims, and a flaming sword which turned every way, to keep the way of the tree of life.
+
+4.
+And Adam knew Eve his wife; and she conceived, and bare Cain, and said, I have gotten a man from the LORD.
+And she again bare his brother Abel. And Abel was a keeper of sheep, but Cain was a tiller of the ground.
+And in process of time it came to pass, that Cain brought of the fruit of the ground an offering unto the LORD.
+And Abel, he also brought of the firstlings of his flock and of the fat thereof. And the LORD had respect unto Abel and to his offering:
+But unto Cain and to his offering he had not respect. And Cain was very wroth, and his countenance fell.
+And the LORD said unto Cain, Why art thou wroth? and why is thy countenance fallen?
+If thou doest well, shalt thou not be accepted? and if thou doest not well, sin lieth at the door. And unto thee shall be his desire, and thou shalt rule over him.
+And Cain talked with Abel his brother: and it came to pass, when they were in the field, that Cain rose up against Abel his brother, and slew him.
+And the LORD said unto Cain, Where is Abel thy brother? And he said, I know not: Am I my brother's keeper?
+And he said, What hast thou done? the voice of thy brother's blood crieth unto me from the ground.
+And now art thou cursed from the earth, which hath opened her mouth to receive thy brother's blood from thy hand;
+When thou tillest the ground, it shall not henceforth yield unto thee her strength; a fugitive and a vagabond shalt thou be in the earth.
+And Cain said unto the LORD, My punishment is greater than I can bear.
+Behold, thou hast driven me out this day from the face of the earth; and from thy face shall I be hid; and I shall be a fugitive and a vagabond in the earth; and it shall come to pass, that every one that findeth me shall slay me.
+And the LORD said unto him, Therefore whosoever slayeth Cain, vengeance shall be taken on him sevenfold. And the LORD set a mark upon Cain, lest any finding him should kill him.
+And Cain went out from the presence of the LORD, and dwelt in the land of Nod, on the east of Eden.
+And Cain knew his wife; and she conceived, and bare Enoch: and he builded a city, and called the name of the city, after the name of his son, Enoch.
+And unto Enoch was born Irad: and Irad begat Mehujael: and Mehujael begat Methusael: and Methusael begat Lamech.
+And Lamech took unto him two wives: the name of the one was Adah, and the name of the other Zillah.
+And Adah bare Jabal: he was the father of such as dwell in tents, and of such as have cattle.
+And his brother's name was Jubal: he was the father of all such as handle the harp and organ.
+And Zillah, she also bare Tubal-cain, an instructer of every artificer in brass and iron: and the sister of Tubal-cain was Naamah.
+And Lamech said unto his wives, Adah and Zillah, Hear my voice; ye wives of Lamech, hearken unto my speech: for I have slain a man to my wounding, and a young man to my hurt.
+If Cain shall be avenged sevenfold, truly Lamech seventy and sevenfold.
+And Adam knew his wife again; and she bare a son, and called his name Seth: For God, said she, hath appointed me another seed instead of Abel, whom Cain slew.
+And to Seth, to him also there was born a son; and he called his name Enos: then began men to call upon the name of the LORD.
+
+5.
+This is the book of the generations of Adam. In the day that God created man, in the likeness of God made he him;
+Male and female created he them; and blessed them, and called their name Adam, in the day when they were created.
+And Adam lived an hundred and thirty years, and begat a son in his own likeness, after his image; and called his name Seth:
+And the days of Adam after he had begotten Seth were eight hundred years: and he begat sons and daughters:
+And all the days that Adam lived were nine hundred and thirty years: and he died.
+And Seth lived an hundred and five years, and begat Enos:
+And Seth lived after he begat Enos eight hundred and seven years, and begat sons and daughters:
+And all the days of Seth were nine hundred and twelve years: and he died.
+And Enos lived ninety years, and begat Cainan:
+And Enos lived after he begat Cainan eight hundred and fifteen years, and begat sons and daughters:
+And all the days of Enos were nine hundred and five years: and he died.
+And Cainan lived seventy years, and begat Mahalaleel:
+And Cainan lived after he begat Mahalaleel eight hundred and forty years, and begat sons and daughters:
+And all the days of Cainan were nine hundred and ten years: and he died.
+And Mahalaleel lived sixty and five years, and begat Jared:
+And Mahalaleel lived after he begat Jared eight hundred and thirty years, and begat sons and daughters:
+And all the days of Mahalaleel were eight hundred ninety and five years: and he died.
+And Jared lived an hundred sixty and two years, and he begat Enoch:
+And Jared lived after he begat Enoch eight hundred years, and begat sons and daughters:
+And all the days of Jared were nine hundred sixty and two years: and he died.
+And Enoch lived sixty and five years, and begat Methuselah:
+And Enoch walked with God after he begat Methuselah three hundred years, and begat sons and daughters:
+And all the days of Enoch were three hundred sixty and five years:
+And Enoch walked with God: and he was not; for God took him.
+And Methuselah lived an hundred eighty and seven years, and begat Lamech:
+And Methuselah lived after he begat Lamech seven hundred eighty and two years, and begat sons and daughters:
+And all the days of Methuselah were nine hundred sixty and nine years: and he died.
+And Lamech lived an hundred eighty and two years, and begat a son:
+And he called his name Noah, saying, This same shall comfort us concerning our work and toil of our hands, because of the ground which the LORD hath cursed.
+And Lamech lived after he begat Noah five hundred ninety and five years, and begat sons and daughters:
+And all the days of Lamech were seven hundred seventy and seven years: and he died.
+And Noah was five hundred years old: and Noah begat Shem, Ham, and Japheth.
+
+6.
+And it came to pass, when men began to multiply on the face of the earth, and daughters were born unto them,
+That the sons of God saw the daughters of men that they were fair; and they took them wives of all which they chose.
+And the LORD said, My spirit shall not always strive with man, for that he also is flesh: yet his days shall be an hundred and twenty years.
+There were giants in the earth in those days; and also after that, when the sons of God came in unto the daughters of men, and they bare children to them, the same became mighty men which were of old, men of renown.
+And GOD saw that the wickedness of man was great in the earth, and that every imagination of the thoughts of his heart was only evil continually.
+And it repented the LORD that he had made man on the earth, and it grieved him at his heart.
+And the LORD said, I will destroy man whom I have created from the face of the earth; both man, and beast, and the creeping thing, and the fowls of the air; for it repenteth me that I have made them.
+But Noah found grace in the eyes of the LORD.
+These are the generations of Noah: Noah was a just man and perfect in his generations, and Noah walked with God.
+And Noah begat three sons, Shem, Ham, and Japheth.
+The earth also was corrupt before God, and the earth was filled with violence.
+And God looked upon the earth, and, behold, it was corrupt; for all flesh had corrupted his way upon the earth.
+And God said unto Noah, The end of all flesh is come before me; for the earth is filled with violence through them; and, behold, I will destroy them with the earth.
+Make thee an ark of gopher wood; rooms shalt thou make in the ark, and shalt pitch it within and without with pitch.
+And this is the fashion which thou shalt make it of: The length of the ark shall be three hundred cubits, the breadth of it fifty cubits, and the height of it thirty cubits.
+A window shalt thou make to the ark, and in a cubit shalt thou finish it above; and the door of the ark shalt thou set in the side thereof; with lower, second, and third stories shalt thou make it.
+And, behold, I, even I, do bring a flood of waters upon the earth, to destroy all flesh, wherein is the breath of life, from under heaven; and every thing that is in the earth shall die.
+But with thee will I establish my covenant; and thou shalt come into the ark, thou, and thy sons, and thy wife, and thy sons' wives with thee.
+And of every living thing of all flesh, two of every sort shalt thou bring into the ark, to keep them alive with thee; they shall be male and female.
+Of fowls after their kind, and of cattle after their kind, of every creeping thing of the earth after his kind, two of every sort shall come unto thee, to keep them alive.
+And take thou unto thee of all food that is eaten, and thou shalt gather it to thee; and it shall be for food for thee, and for them.
+Thus did Noah; according to all that God commanded him, so did he.
+
+7.
+And the LORD said unto Noah, Come thou and all thy house into the ark; for thee have I seen righteous before me in this generation.
+Of every clean beast thou shalt take to thee by sevens, the male and his female: and of beasts that are not clean by two, the male and his female.
+Of fowls also of the air by sevens, the male and the female; to keep seed alive upon the face of all the earth.
+For yet seven days, and I will cause it to rain upon the earth forty days and forty nights; and every living substance that I have made will I destroy from off the face of the earth.
+And Noah did according unto all that the LORD commanded him.
+And Noah was six hundred years old when the flood of waters was upon the earth.
+And Noah went in, and his sons, and his wife, and his sons' wives with him, into the ark, because of the waters of the flood.
+Of clean beasts, and of beasts that are not clean, and of fowls, and of every thing that creepeth upon the earth,
+There went in two and two unto Noah into the ark, the male and the female, as God had commanded Noah.
+And it came to pass after seven days, that the waters of the flood were upon the earth.
+In the six hundredth year of Noah's life, in the second month, the seventeenth day of the month, the same day were all the fountains of the great deep broken up, and the windows of heaven were opened.
+And the rain was upon the earth forty days and forty nights.
+In the selfsame day entered Noah, and Shem, and Ham, and Japheth, the sons of Noah, and Noah's wife, and the three wives of his sons with them, into the ark;
+They, and every beast after his kind, and all the cattle after their kind, and every creeping thing that creepeth upon the earth after his kind, and every fowl after his kind, every bird of every sort.
+And they went in unto Noah into the ark, two and two of all flesh, wherein is the breath of life.
+And they that went in, went in male and female of all flesh, as God had commanded him: and the LORD shut him in.
+And the flood was forty days upon the earth; and the waters increased, and bare up the ark, and it was lift up above the earth.
+And the waters prevailed, and were increased greatly upon the earth; and the ark went upon the face of the waters.
+And the waters prevailed exceedingly upon the earth; and all the high hills, that were under the whole heaven, were covered.
+Fifteen cubits upward did the waters prevail; and the mountains were covered.
+And all flesh died that moved upon the earth, both of fowl, and of cattle, and of beast, and of every creeping thing that creepeth upon the earth, and every man:
+All in whose nostrils was the breath of life, of all that was in the dry land, died.
+And every living substance was destroyed which was upon the face of the ground, both man, and cattle, and the creeping things, and the fowl of the heaven; and they were destroyed from the earth: and Noah only remained alive, and they that were with him in the ark.
+And the waters prevailed upon the earth an hundred and fifty days.
+
+8.
+And God remembered Noah, and every living thing, and all the cattle that was with him in the ark: and God made a wind to pass over the earth, and the waters asswaged;
+The fountains also of the deep and the windows of heaven were stopped, and the rain from heaven was restrained;
+And the waters returned from off the earth continually: and after the end of the hundred and fifty days the waters were abated.
+And the ark rested in the seventh month, on the seventeenth day of the month, upon the mountains of Ararat.
+And the waters decreased continually until the tenth month: in the tenth month, on the first day of the month, were the tops of the mountains seen.
+And it came to pass at the end of forty days, that Noah opened the window of the ark which he had made:
+And he sent forth a raven, which went forth to and fro, until the waters were dried up from off the earth.
+Also he sent forth a dove from him, to see if the waters were abated from off the face of the ground;
+But the dove found no rest for the sole of her foot, and she returned unto him into the ark, for the waters were on the face of the whole earth: then he put forth his hand, and took her, and pulled her in unto him into the ark.
+And he stayed yet other seven days; and again he sent forth the dove out of the ark;
+And the dove came in to him in the evening; and, lo, in her mouth was an olive leaf pluckt off: so Noah knew that the waters were abated from off the earth.
+And he stayed yet other seven days; and sent forth the dove; which returned not again unto him any more.
+And it came to pass in the six hundredth and first year, in the first month, the first day of the month, the waters were dried up from off the earth: and Noah removed the covering of the ark, and looked, and, behold, the face of the ground was dry.
+And in the second month, on the seven and twentieth day of the month, was the earth dried.
+And God spake unto Noah, saying,
+Go forth of the ark, thou, and thy wife, and thy sons, and thy sons' wives with thee.
+Bring forth with thee every living thing that is with thee, of all flesh, both of fowl, and of cattle, and of every creeping thing that creepeth upon the earth; that they may breed abundantly in the earth, and be fruitful, and multiply upon the earth.
+And Noah went forth, and his sons, and his wife, and his sons' wives with him:
+Every beast, every creeping thing, and every fowl, and whatsoever creepeth upon the earth, after their kinds, went forth out of the ark.
+And Noah builded an altar unto the LORD; and took of every clean beast, and of every clean fowl, and offered burnt offerings on the altar.
+And the LORD smelled a sweet savour; and the LORD said in his heart, I will not again curse the ground any more for man's sake; for the imagination of man's heart is evil from his youth; neither will I again smite any more every thing living, as I have done.
+While the earth remaineth, seedtime and harvest, and cold and heat, and summer and winter, and day and night shall not cease.
+
+9.
+And God blessed Noah and his sons, and said unto them, Be fruitful, and multiply, and replenish the earth.
+And the fear of you and the dread of you shall be upon every beast of the earth, and upon every fowl of the air, upon all that moveth upon the earth, and upon all the fishes of the sea; into your hand are they delivered.
+Every moving thing that liveth shall be meat for you; even as the green herb have I given you all things.
+But flesh with the life thereof, which is the blood thereof, shall ye not eat.
+And surely your blood of your lives will I require; at the hand of every beast will I require it, and at the hand of man; at the hand of every man's brother will I require the life of man.
+Whoso sheddeth man's blood, by man shall his blood be shed: for in the image of God made he man.
+And you, be ye fruitful, and multiply; bring forth abundantly in the earth, and multiply therein.
+And God spake unto Noah, and to his sons with him, saying,
+And I, behold, I establish my covenant with you, and with your seed after you;
+And with every living creature that is with you, of the fowl, of the cattle, and of every beast of the earth with you; from all that go out of the ark, to every beast of the earth.
+And I will establish my covenant with you; neither shall all flesh be cut off any more by the waters of a flood; neither shall there any more be a flood to destroy the earth.
+And God said, This is the token of the covenant which I make between me and you and every living creature that is with you, for perpetual generations:
+I do set my bow in the cloud, and it shall be for a token of a covenant between me and the earth.
+And it shall come to pass, when I bring a cloud over the earth, that the bow shall be seen in the cloud:
+And I will remember my covenant, which is between me and you and every living creature of all flesh; and the waters shall no more become a flood to destroy all flesh.
+And the bow shall be in the cloud; and I will look upon it, that I may remember the everlasting covenant between God and every living creature of all flesh that is upon the earth.
+And God said unto Noah, This is the token of the covenant, which I have established between me and all flesh that is upon the earth.
+And the sons of Noah, that went forth of the ark, were Shem, and Ham, and Japheth: and Ham is the father of Canaan.
+These are the three sons of Noah: and of them was the whole earth overspread.
+And Noah began to be an husbandman, and he planted a vineyard:
+And he drank of the wine, and was drunken; and he was uncovered within his tent.
+And Ham, the father of Canaan, saw the nakedness of his father, and told his two brethren without.
+And Shem and Japheth took a garment, and laid it upon both their shoulders, and went backward, and covered the nakedness of their father; and their faces were backward, and they saw not their father's nakedness.
+And Noah awoke from his wine, and knew what his younger son had done unto him.
+And he said, Cursed be Canaan; a servant of servants shall he be unto his brethren.
+And he said, Blessed be the LORD God of Shem; and Canaan shall be his servant.
+God shall enlarge Japheth, and he shall dwell in the tents of Shem; and Canaan shall be his servant.
+And Noah lived after the flood three hundred and fifty years.
+And all the days of Noah were nine hundred and fifty years: and he died.
+
+10.
+Now these are the generations of the sons of Noah, Shem, Ham, and Japheth: and unto them were sons born after the flood.
+The sons of Japheth; Gomer, and Magog, and Madai, and Javan, and Tubal, and Meshech, and Tiras.
+And the sons of Gomer; Ashkenaz, and Riphath, and Togarmah.
+And the sons of Javan; Elishah, and Tarshish, Kittim, and Dodanim.
+By these were the isles of the Gentiles divided in their lands; every one after his tongue, after their families, in their nations.
+And the sons of Ham; Cush, and Mizraim, and Phut, and Canaan.
+And the sons of Cush; Seba, and Havilah, and Sabtah, and Raamah, and Sabtecha: and the sons of Raamah; Sheba, and Dedan.
+And Cush begat Nimrod: he began to be a mighty one in the earth.
+He was a mighty hunter before the LORD: wherefore it is said, Even as Nimrod the mighty hunter before the LORD.
+And the beginning of his kingdom was Babel, and Erech, and Accad, and Calneh, in the land of Shinar.
+Out of that land went forth Asshur, and builded Nineveh, and the city Rehoboth, and Calah,
+And Resen between Nineveh and Calah: the same is a great city.
+And Mizraim begat Ludim, and Anamim, and Lehabim, and Naphtuhim,
+And Pathrusim, and Casluhim, (out of whom came Philistim,) and Caphtorim.
+And Canaan begat Sidon his firstborn, and Heth,
+And the Jebusite, and the Amorite, and the Girgasite,
+And the Hivite, and the Arkite, and the Sinite,
+And the Arvadite, and the Zemarite, and the Hamathite: and afterward were the families of the Canaanites spread abroad.
+And the border of the Canaanites was from Sidon, as thou comest to Gerar, unto Gaza; as thou goest, unto Sodom, and Gomorrah, and Admah, and Zeboim, even unto Lasha.
+These are the sons of Ham, after their families, after their tongues, in their countries, and in their nations.
+Unto Shem also, the father of all the children of Eber, the brother of Japheth the elder, even to him were children born.
+The children of Shem; Elam, and Asshur, and Arphaxad, and Lud, and Aram.
+And the children of Aram; Uz, and Hul, and Gether, and Mash.
+And Arphaxad begat Salah; and Salah begat Eber.
+And unto Eber were born two sons: the name of one was Peleg; for in his days was the earth divided; and his brother's name was Joktan.
+And Joktan begat Almodad, and Sheleph, and Hazar-maveth, and Jerah,
+And Hadoram, and Uzal, and Diklah,
+And Obal, and Abimael, and Sheba,
+And Ophir, and Havilah, and Jobab: all these were the sons of Joktan.
+And their dwelling was from Mesha, as thou goest unto Sephar a mount of the east.
+These are the sons of Shem, after their families, after their tongues, in their lands, after their nations.
+These are the families of the sons of Noah, after their generations, in their nations: and by these were the nations divided in the earth after the flood.
+
+11.
+And the whole earth was of one language, and of one speech.
+And it came to pass, as they journeyed from the east, that they found a plain in the land of Shinar; and they dwelt there.
+And they said one to another, Go to, let us make brick, and burn them throughly. And they had brick for stone, and slime had they for morter.
+And they said, Go to, let us build us a city and a tower, whose top may reach unto heaven; and let us make us a name, lest we be scattered abroad upon the face of the whole earth.
+And the LORD came down to see the city and the tower, which the children of men builded.
+And the LORD said, Behold, the people is one, and they have all one language; and this they begin to do: and now nothing will be restrained from them, which they have imagined to do.
+Go to, let us go down, and there confound their language, that they may not understand one another's speech.
+So the LORD scattered them abroad from thence upon the face of all the earth: and they left off to build the city.
+Therefore is the name of it called Babel; because the LORD did there confound the language of all the earth: and from thence did the LORD scatter them abroad upon the face of all the earth.
+These are the generations of Shem: Shem was an hundred years old, and begat Arphaxad two years after the flood:
+And Shem lived after he begat Arphaxad five hundred years, and begat sons and daughters.
+And Arphaxad lived five and thirty years, and begat Salah:
+And Arphaxad lived after he begat Salah four hundred and three years, and begat sons and daughters.
+And Salah lived thirty years, and begat Eber:
+And Salah lived after he begat Eber four hundred and three years, and begat sons and daughters.
+And Eber lived four and thirty years, and begat Peleg:
+And Eber lived after he begat Peleg four hundred and thirty years, and begat sons and daughters.
+And Peleg lived thirty years, and begat Reu:
+And Peleg lived after he begat Reu two hundred and nine years, and begat sons and daughters.
+And Reu lived two and thirty years, and begat Serug:
+And Reu lived after he begat Serug two hundred and seven years, and begat sons and daughters.
+And Serug lived thirty years, and begat Nahor:
+And Serug lived after he begat Nahor two hundred years, and begat sons and daughters.
+And Nahor lived nine and twenty years, and begat Terah:
+And Nahor lived after he begat Terah an hundred and nineteen years, and begat sons and daughters.
+And Terah lived seventy years, and begat Abram, Nahor, and Haran.
+Now these are the generations of Terah: Terah begat Abram, Nahor, and Haran; and Haran begat Lot.
+And Haran died before his father Terah in the land of his nativity, in Ur of the Chaldees.
+And Abram and Nahor took them wives: the name of Abram's wife was Sarai; and the name of Nahor's wife, Milcah, the daughter of Haran, the father of Milcah, and the father of Iscah.
+But Sarai was barren; she had no child.
+And Terah took Abram his son, and Lot the son of Haran his son's son, and Sarai his daughter in law, his son Abram's wife; and they went forth with them from Ur of the Chaldees, to go into the land of Canaan; and they came unto Haran, and dwelt there.
+And the days of Terah were two hundred and five years: and Terah died in Haran.
+
+12.
+Now the LORD had said unto Abram, Get thee out of thy country, and from thy kindred, and from thy father's house, unto a land that I will shew thee:
+And I will make of thee a great nation, and I will bless thee, and make thy name great; and thou shalt be a blessing:
+And I will bless them that bless thee, and curse him that curseth thee: and in thee shall all families of the earth be blessed.
+So Abram departed, as the LORD had spoken unto him; and Lot went with him: and Abram was seventy and five years old when he departed out of Haran.
+And Abram took Sarai his wife, and Lot his brother's son, and all their substance that they had gathered, and the souls that they had gotten in Haran; and they went forth to go into the land of Canaan; and into the land of Canaan they came.
+And Abram passed through the land unto the place of Sichem, unto the plain of Moreh. And the Canaanite was then in the land.
+And the LORD appeared unto Abram, and said, Unto thy seed will I give this land: and there builded he an altar unto the LORD, who appeared unto him.
+And he removed from thence unto a mountain on the east of Bethel, and pitched his tent, having Bethel on the west, and Hai on the east: and there he builded an altar unto the LORD, and called upon the name of the LORD.
+And Abram journeyed, going on still toward the south.
+And there was a famine in the land: and Abram went down into Egypt to sojourn there; for the famine was grievous in the land.
+And it came to pass, when he was come near to enter into Egypt, that he said unto Sarai his wife, Behold now, I know that thou art a fair woman to look upon:
+Therefore it shall come to pass, when the Egyptians shall see thee, that they shall say, This is his wife: and they will kill me, but they will save thee alive.
+Say, I pray thee, thou art my sister: that it may be well with me for thy sake; and my soul shall live because of thee.
+And it came to pass, that, when Abram was come into Egypt, the Egyptians beheld the woman that she was very fair.
+The princes also of Pharaoh saw her, and commended her before Pharaoh: and the woman was taken into Pharaoh's house.
+And he entreated Abram well for her sake: and he had sheep, and oxen, and he asses, and menservants, and maidservants, and she asses, and camels.
+And the LORD plagued Pharaoh and his house with great plagues because of Sarai Abram's wife.
+And Pharaoh called Abram, and said, What is this that thou hast done unto me? why didst thou not tell me that she was thy wife?
+Why saidst thou, She is my sister? so I might have taken her to me to wife: now therefore behold thy wife, take her, and go thy way.
+And Pharaoh commanded his men concerning him: and they sent him away, and his wife, and all that he had.
+
+13.
+And Abram went up out of Egypt, he, and his wife, and all that he had, and Lot with him, into the south.
+And Abram was very rich in cattle, in silver, and in gold.
+And he went on his journeys from the south even to Bethel, unto the place where his tent had been at the beginning, between Bethel and Hai;
+Unto the place of the altar, which he had made there at the first: and there Abram called on the name of the LORD.
+And Lot also, which went with Abram, had flocks, and herds, and tents.
+And the land was not able to bear them, that they might dwell together: for their substance was great, so that they could not dwell together.
+And there was a strife between the herdmen of Abram's cattle and the herdmen of Lot's cattle: and the Canaanite and the Perizzite dwelled then in the land.
+And Abram said unto Lot, Let there be no strife, I pray thee, between me and thee, and between my herdmen and thy herdmen; for we be brethren.
+Is not the whole land before thee? separate thyself, I pray thee, from me: if thou wilt take the left hand, then I will go to the right; or if thou depart to the right hand, then I will go to the left.
+And Lot lifted up his eyes, and beheld all the plain of Jordan, that it was well watered every where, before the LORD destroyed Sodom and Gomorrah, even as the garden of the LORD, like the land of Egypt, as thou comest unto Zoar.
+Then Lot chose him all the plain of Jordan; and Lot journeyed east: and they separated themselves the one from the other.
+Abram dwelled in the land of Canaan, and Lot dwelled in the cities of the plain, and pitched his tent toward Sodom.
+But the men of Sodom were wicked and sinners before the LORD exceedingly.
+And the LORD said unto Abram, after that Lot was separated from him, Lift up now thine eyes, and look from the place where thou art northward, and southward, and eastward, and westward:
+For all the land which thou seest, to thee will I give it, and to thy seed for ever.
+And I will make thy seed as the dust of the earth: so that if a man can number the dust of the earth, then shall thy seed also be numbered.
+Arise, walk through the land in the length of it and in the breadth of it; for I will give it unto thee.
+Then Abram removed his tent, and came and dwelt in the plain of Mamre, which is in Hebron, and built there an altar unto the LORD.
+
+14.
+And it came to pass in the days of Amraphel king of Shinar, Arioch king of Ellasar, Chedorlaomer king of Elam, and Tidal king of nations;
+That these made war with Bera king of Sodom, and with Birsha king of Gomorrah, Shinab king of Admah, and Shemeber king of Zeboiim, and the king of Bela, which is Zoar.
+All these were joined together in the vale of Siddim, which is the salt sea.
+Twelve years they served Chedorlaomer, and in the thirteenth year they rebelled.
+And in the fourteenth year came Chedorlaomer, and the kings that were with him, and smote the Rephaims in Ashteroth Karnaim, and the Zuzims in Ham, and the Emims in Shaveh Kiriathaim,
+And the Horites in their mount Seir, unto El-paran, which is by the wilderness.
+And they returned, and came to En-mishpat, which is Kadesh, and smote all the country of the Amalekites, and also the Amorites that dwelt in Hazezon-tamar.
+And there went out the king of Sodom, and the king of Gomorrah, and the king of Admah, and the king of Zeboiim, and the king of Bela (the same is Zoar;) and they joined battle with them in the vale of Siddim;
+With Chedorlaomer the king of Elam, and with Tidal king of nations, and Amraphel king of Shinar, and Arioch king of Ellasar; four kings with five.
+And the vale of Siddim was full of slimepits; and the kings of Sodom and Gomorrah fled, and fell there; and they that remained fled to the mountain.
+And they took all the goods of Sodom and Gomorrah, and all their victuals, and went their way.
+And they took Lot, Abram's brother's son, who dwelt in Sodom, and his goods, and departed.
+And there came one that had escaped, and told Abram the Hebrew; for he dwelt in the plain of Mamre the Amorite, brother of Eschol, and brother of Aner: and these were confederate with Abram.
+And when Abram heard that his brother was taken captive, he armed his trained servants, born in his own house, three hundred and eighteen, and pursued them unto Dan.
+And he divided himself against them, he and his servants, by night, and smote them, and pursued them unto Hobah, which is on the left hand of Damascus.
+And he brought back all the goods, and also brought again his brother Lot, and his goods, and the women also, and the people.
+And the king of Sodom went out to meet him after his return from the slaughter of Chedorlaomer, and of the kings that were with him, at the valley of Shaveh, which is the king's dale.
+And Melchizedek king of Salem brought forth bread and wine: and he was the priest of the most high God.
+And he blessed him, and said, Blessed be Abram of the most high God, possessor of heaven and earth:
+And blessed be the most high God, which hath delivered thine enemies into thy hand. And he gave him tithes of all.
+And the king of Sodom said unto Abram, Give me the persons, and take the goods to thyself.
+And Abram said to the king of Sodom, I have lift up mine hand unto the LORD, the most high God, the possessor of heaven and earth,
+That I will not take from a thread even to a shoelatchet, and that I will not take any thing that is thine, lest thou shouldest say, I have made Abram rich:
+Save only that which the young men have eaten, and the portion of the men which went with me, Aner, Eshcol, and Mamre; let them take their portion.
+
+15.
+After these things the word of the LORD came unto Abram in a vision, saying, Fear not, Abram: I am thy shield, and thy exceeding great reward.
+And Abram said, Lord GOD, what wilt thou give me, seeing I go childless, and the steward of my house is this Eliezer of Damascus?
+And Abram said, Behold, to me thou hast given no seed: and, lo, one born in my house is mine heir.
+And, behold, the word of the LORD came unto him, saying, This shall not be thine heir; but he that shall come forth out of thine own bowels shall be thine heir.
+And he brought him forth abroad, and said, Look now toward heaven, and tell the stars, if thou be able to number them: and he said unto him, So shall thy seed be.
+And he believed in the LORD; and he counted it to him for righteousness.
+And he said unto him, I am the LORD that brought thee out of Ur of the Chaldees, to give thee this land to inherit it.
+And he said, Lord GOD, whereby shall I know that I shall inherit it?
+And he said unto him, Take me an heifer of three years old, and a she goat of three years old, and a ram of three years old, and a turtledove, and a young pigeon.
+And he took unto him all these, and divided them in the midst, and laid each piece one against another: but the birds divided he not.
+And when the fowls came down upon the carcases, Abram drove them away.
+And when the sun was going down, a deep sleep fell upon Abram; and, lo, an horror of great darkness fell upon him.
+And he said unto Abram, Know of a surety that thy seed shall be a stranger in a land that is not theirs, and shall serve them; and they shall afflict them four hundred years;
+And also that nation, whom they shall serve, will I judge: and afterward shall they come out with great substance.
+And thou shalt go to thy fathers in peace; thou shalt be buried in a good old age.
+But in the fourth generation they shall come hither again: for the iniquity of the Amorites is not yet full.
+And it came to pass, that, when the sun went down, and it was dark, behold a smoking furnace, and a burning lamp that passed between those pieces.
+In the same day the LORD made a covenant with Abram, saying, Unto thy seed have I given this land, from the river of Egypt unto the great river, the river Euphrates:
+The Kenites, and the Kenizzites, and the Kadmonites,
+And the Hittites, and the Perizzites, and the Rephaims,
+And the Amorites, and the Canaanites, and the Girgashites, and the Jebusites.
+
+16.
+Now Sarai Abram's wife bare him no children: and she had an handmaid, an Egyptian, whose name was Hagar.
+And Sarai said unto Abram, Behold now, the LORD hath restrained me from bearing: I pray thee, go in unto my maid; it may be that I may obtain children by her. And Abram hearkened to the voice of Sarai.
+And Sarai Abram's wife took Hagar her maid the Egyptian, after Abram had dwelt ten years in the land of Canaan, and gave her to her husband Abram to be his wife.
+And he went in unto Hagar, and she conceived: and when she saw that she had conceived, her mistress was despised in her eyes.
+And Sarai said unto Abram, My wrong be upon thee: I have given my maid into thy bosom; and when she saw that she had conceived, I was despised in her eyes: the LORD judge between me and thee.
+But Abram said unto Sarai, Behold, thy maid is in thy hand; do to her as it pleaseth thee. And when Sarai dealt hardly with her, she fled from her face.
+And the angel of the LORD found her by a fountain of water in the wilderness, by the fountain in the way to Shur.
+And he said, Hagar, Sarai's maid, whence camest thou? and whither wilt thou go? And she said, I flee from the face of my mistress Sarai.
+And the angel of the LORD said unto her, Return to thy mistress, and submit thyself under her hands.
+And the angel of the LORD said unto her, I will multiply thy seed exceedingly, that it shall not be numbered for multitude.
+And the angel of the LORD said unto her, Behold, thou art with child, and shalt bear a son, and shalt call his name Ishmael; because the LORD hath heard thy affliction.
+And he will be a wild man; his hand will be against every man, and every man's hand against him; and he shall dwell in the presence of all his brethren.
+And she called the name of the LORD that spake unto her, Thou God seest me: for she said, Have I also here looked after him that seeth me?
+Wherefore the well was called Beer-lahai-roi; behold, it is between Kadesh and Bered.
+And Hagar bare Abram a son: and Abram called his son's name, which Hagar bare, Ishmael.
+And Abram was fourscore and six years old, when Hagar bare Ishmael to Abram.
+
+17.
+And when Abram was ninety years old and nine, the LORD appeared to Abram, and said unto him, I am the Almighty God; walk before me, and be thou perfect.
+And I will make my covenant between me and thee, and will multiply thee exceedingly.
+And Abram fell on his face: and God talked with him, saying,
+As for me, behold, my covenant is with thee, and thou shalt be a father of many nations.
+Neither shall thy name any more be called Abram, but thy name shall be Abraham; for a father of many nations have I made thee.
+And I will make thee exceeding fruitful, and I will make nations of thee, and kings shall come out of thee.
+And I will establish my covenant between me and thee and thy seed after thee in their generations for an everlasting covenant, to be a God unto thee, and to thy seed after thee.
+And I will give unto thee, and to thy seed after thee, the land wherein thou art a stranger, all the land of Canaan, for an everlasting possession; and I will be their God.
+And God said unto Abraham, Thou shalt keep my covenant therefore, thou, and thy seed after thee in their generations.
+This is my covenant, which ye shall keep, between me and you and thy seed after thee; Every man child among you shall be circumcised.
+And ye shall circumcise the flesh of your foreskin; and it shall be a token of the covenant betwixt me and you.
+And he that is eight days old shall be circumcised among you, every man child in your generations, he that is born in the house, or bought with money of any stranger, which is not of thy seed.
+He that is born in thy house, and he that is bought with thy money, must needs be circumcised: and my covenant shall be in your flesh for an everlasting covenant.
+And the uncircumcised man child whose flesh of his foreskin is not circumcised, that soul shall be cut off from his people; he hath broken my covenant.
+And God said unto Abraham, As for Sarai thy wife, thou shalt not call her name Sarai, but Sarah shall her name be.
+And I will bless her, and give thee a son also of her: yea, I will bless her, and she shall be a mother of nations; kings of people shall be of her.
+Then Abraham fell upon his face, and laughed, and said in his heart, Shall a child be born unto him that is an hundred years old? and shall Sarah, that is ninety years old, bear?
+And Abraham said unto God, O that Ishmael might live before thee!
+And God said, Sarah thy wife shall bear thee a son indeed; and thou shalt call his name Isaac: and I will establish my covenant with him for an everlasting covenant, and with his seed after him.
+And as for Ishmael, I have heard thee: Behold, I have blessed him, and will make him fruitful, and will multiply him exceedingly; twelve princes shall he beget, and I will make him a great nation.
+But my covenant will I establish with Isaac, which Sarah shall bear unto thee at this set time in the next year.
+And he left off talking with him, and God went up from Abraham.
+And Abraham took Ishmael his son, and all that were born in his house, and all that were bought with his money, every male among the men of Abraham's house; and circumcised the flesh of their foreskin in the selfsame day, as God had said unto him.
+And Abraham was ninety years old and nine, when he was circumcised in the flesh of his foreskin.
+And Ishmael his son was thirteen years old, when he was circumcised in the flesh of his foreskin.
+In the selfsame day was Abraham circumcised, and Ishmael his son.
+And all the men of his house, born in the house, and bought with money of the stranger, were circumcised with him.
+
+18.
+And the LORD appeared unto him in the plains of Mamre: and he sat in the tent door in the heat of the day;
+And he lift up his eyes and looked, and, lo, three men stood by him: and when he saw them, he ran to meet them from the tent door, and bowed himself toward the ground,
+And said, My Lord, if now I have found favour in thy sight, pass not away, I pray thee, from thy servant:
+Let a little water, I pray you, be fetched, and wash your feet, and rest yourselves under the tree:
+And I will fetch a morsel of bread, and comfort ye your hearts; after that ye shall pass on: for therefore are ye come to your servant. And they said, So do, as thou hast said.
+And Abraham hastened into the tent unto Sarah, and said, Make ready quickly three measures of fine meal, knead it, and make cakes upon the hearth.
+And Abraham ran unto the herd, and fetcht a calf tender and good, and gave it unto a young man; and he hasted to dress it.
+And he took butter, and milk, and the calf which he had dressed, and set it before them; and he stood by them under the tree, and they did eat.
+And they said unto him, Where is Sarah thy wife? And he said, Behold, in the tent.
+And he said, I will certainly return unto thee according to the time of life; and, lo, Sarah thy wife shall have a son. And Sarah heard it in the tent door, which was behind him.
+Now Abraham and Sarah were old and well stricken in age; and it ceased to be with Sarah after the manner of women.
+Therefore Sarah laughed within herself, saying, After I am waxed old shall I have pleasure, my lord being old also?
+And the LORD said unto Abraham, Wherefore did Sarah laugh, saying, Shall I of a surety bear a child, which am old?
+Is any thing too hard for the LORD? At the time appointed I will return unto thee, according to the time of life, and Sarah shall have a son.
+Then Sarah denied, saying, I laughed not; for she was afraid. And he said, Nay; but thou didst laugh.
+And the men rose up from thence, and looked toward Sodom: and Abraham went with them to bring them on the way.
+And the LORD said, Shall I hide from Abraham that thing which I do;
+Seeing that Abraham shall surely become a great and mighty nation, and all the nations of the earth shall be blessed in him?
+For I know him, that he will command his children and his household after him, and they shall keep the way of the LORD, to do justice and judgment; that the LORD may bring upon Abraham that which he hath spoken of him.
+And the LORD said, Because the cry of Sodom and Gomorrah is great, and because their sin is very grievous;
+I will go down now, and see whether they have done altogether according to the cry of it, which is come unto me; and if not, I will know.
+And the men turned their faces from thence, and went toward Sodom: but Abraham stood yet before the LORD.
+And Abraham drew near, and said, Wilt thou also destroy the righteous with the wicked?
+Peradventure there be fifty righteous within the city: wilt thou also destroy and not spare the place for the fifty righteous that are therein?
+That be far from thee to do after this manner, to slay the righteous with the wicked: and that the righteous should be as the wicked, that be far from thee: Shall not the Judge of all the earth do right?
+And the LORD said, If I find in Sodom fifty righteous within the city, then I will spare all the place for their sakes.
+And Abraham answered and said, Behold now, I have taken upon me to speak unto the Lord, which am but dust and ashes:
+Peradventure there shall lack five of the fifty righteous: wilt thou destroy all the city for lack of five? And he said, If I find there forty and five, I will not destroy it.
+And he spake unto him yet again, and said, Peradventure there shall be forty found there. And he said, I will not do it for forty's sake.
+And he said unto him, Oh let not the Lord be angry, and I will speak: Peradventure there shall thirty be found there. And he said, I will not do it, if I find thirty there.
+And he said, Behold now, I have taken upon me to speak unto the Lord: Peradventure there shall be twenty found there. And he said, I will not destroy it for twenty's sake.
+And he said, Oh let not the Lord be angry, and I will speak yet but this once: Peradventure ten shall be found there. And he said, I will not destroy it for ten's sake.
+And the LORD went his way, as soon as he had left communing with Abraham: and Abraham returned unto his place.
+
+19.
+And there came two angels to Sodom at even; and Lot sat in the gate of Sodom: and Lot seeing them rose up to meet them; and he bowed himself with his face toward the ground;
+And he said, Behold now, my lords, turn in, I pray you, into your servant's house, and tarry all night, and wash your feet, and ye shall rise up early, and go on your ways. And they said, Nay; but we will abide in the street all night.
+And he pressed upon them greatly; and they turned in unto him, and entered into his house; and he made them a feast, and did bake unleavened bread, and they did eat.
+But before they lay down, the men of the city, even the men of Sodom, compassed the house round, both old and young, all the people from every quarter:
+And they called unto Lot, and said unto him, Where are the men which came in to thee this night? bring them out unto us, that we may know them.
+And Lot went out at the door unto them, and shut the door after him,
+And said, I pray you, brethren, do not so wickedly.
+Behold now, I have two daughters which have not known man; let me, I pray you, bring them out unto you, and do ye to them as is good in your eyes: only unto these men do nothing; for therefore came they under the shadow of my roof.
+And they said, Stand back. And they said again, This one fellow came in to sojourn, and he will needs be a judge: now will we deal worse with thee, than with them. And they pressed sore upon the man, even Lot, and came near to break the door.
+But the men put forth their hand, and pulled Lot into the house to them, and shut to the door.
+And they smote the men that were at the door of the house with blindness, both small and great: so that they wearied themselves to find the door.
+And the men said unto Lot, Hast thou here any besides? son in law, and thy sons, and thy daughters, and whatsoever thou hast in the city, bring them out of this place:
+For we will destroy this place, because the cry of them is waxen great before the face of the LORD; and the LORD hath sent us to destroy it.
+And Lot went out, and spake unto his sons in law, which married his daughters, and said, Up, get you out of this place; for the LORD will destroy this city. But he seemed as one that mocked unto his sons in law.
+And when the morning arose, then the angels hastened Lot, saying, Arise, take thy wife, and thy two daughters, which are here; lest thou be consumed in the iniquity of the city.
+And while he lingered, the men laid hold upon his hand, and upon the hand of his wife, and upon the hand of his two daughters; the LORD being merciful unto him: and they brought him forth, and set him without the city.
+And it came to pass, when they had brought them forth abroad, that he said, Escape for thy life; look not behind thee, neither stay thou in all the plain; escape to the mountain, lest thou be consumed.
+And Lot said unto them, Oh, not so, my Lord:
+Behold now, thy servant hath found grace in thy sight, and thou hast magnified thy mercy, which thou hast shewed unto me in saving my life; and I cannot escape to the mountain, lest some evil take me, and I die:
+Behold now, this city is near to flee unto, and it is a little one: Oh, let me escape thither, (is it not a little one?) and my soul shall live.
+And he said unto him, See, I have accepted thee concerning this thing also, that I will not overthrow this city, for the which thou hast spoken.
+Haste thee, escape thither; for I cannot do any thing till thou be come thither. Therefore the name of the city was called Zoar.
+The sun was risen upon the earth when Lot entered into Zoar.
+Then the LORD rained upon Sodom and upon Gomorrah brimstone and fire from the LORD out of heaven;
+And he overthrew those cities, and all the plain, and all the inhabitants of the cities, and that which grew upon the ground.
+But his wife looked back from behind him, and she became a pillar of salt.
+And Abraham gat up early in the morning to the place where he stood before the LORD:
+And he looked toward Sodom and Gomorrah, and toward all the land of the plain, and beheld, and, lo, the smoke of the country went up as the smoke of a furnace.
+And it came to pass, when God destroyed the cities of the plain, that God remembered Abraham, and sent Lot out of the midst of the overthrow, when he overthrew the cities in the which Lot dwelt.
+And Lot went up out of Zoar, and dwelt in the mountain, and his two daughters with him; for he feared to dwell in Zoar: and he dwelt in a cave, he and his two daughters.
+And the firstborn said unto the younger, Our father is old, and there is not a man in the earth to come in unto us after the manner of all the earth:
+Come, let us make our father drink wine, and we will lie with him, that we may preserve seed of our father.
+And they made their father drink wine that night: and the firstborn went in, and lay with her father; and he perceived not when she lay down, nor when she arose.
+And it came to pass on the morrow, that the firstborn said unto the younger, Behold, I lay yesternight with my father: let us make him drink wine this night also; and go thou in, and lie with him, that we may preserve seed of our Father.
+And they made their father drink wine that night also: and the younger arose, and lay with him; and he perceived not when she lay down, nor when she arose.
+Thus were both the daughters of Lot with child by their father.
+And the firstborn bare a son, and called his name Moab: the same is the father of the Moabites unto this day.
+And the younger, she also bare a son, and called his name Benammi: the same is the father of the children of Ammon unto this day.
+
+20.
+And Abraham journeyed from thence toward the south country, and dwelled between Kadesh and Shur, and sojourned in Gerar.
+And Abraham said of Sarah his wife, She is my sister: and Abimelech king of Gerar sent, and took Sarah.
+But God came to Abimelech in a dream by night, and said to him, Behold, thou art but a dead man, for the woman which thou hast taken; for she is a man's wife.
+But Abimelech had not come near her: and he said, Lord, wilt thou slay also a righteous nation?
+Said he not unto me, She is my sister? and she, even she herself said, He is my brother: in the integrity of my heart and innocency of my hands have I done this.
+And God said unto him in a dream, Yea, I know that thou didst this in the integrity of thy heart; for I also withheld thee from sinning against me: therefore suffered I thee not to touch her.
+Now therefore restore the man his wife; for he is a prophet, and he shall pray for thee, and thou shalt live: and if thou restore her not, know thou that thou shalt surely die, thou, and all that are thine.
+Therefore Abimelech rose early in the morning, and called all his servants, and told all these things in their ears: and the men were sore afraid.
+Then Abimelech called Abraham, and said unto him, What hast thou done unto us? and what have I offended thee, that thou hast brought on me and on my kingdom a great sin? thou hast done deeds unto me that ought not to be done.
+And Abimelech said unto Abraham, What sawest thou, that thou hast done this thing?
+And Abraham said, Because I thought, Surely the fear of God is not in this place; and they will slay me for my wife's sake.
+And yet indeed she is my sister; she is the daughter of my father, but not the daughter of my mother; and she became my wife.
+And it came to pass, when God caused me to wander from my father's house, that I said unto her, This is thy kindness which thou shalt shew unto me; at every place whither we shall come, say of me, He is my brother.
+And Abimelech took sheep, and oxen, and menservants, and womenservants, and gave them unto Abraham, and restored him Sarah his wife.
+And Abimelech said, Behold, my land is before thee: dwell where it pleaseth thee.
+And unto Sarah he said, Behold, I have given thy brother a thousand pieces of silver: behold, he is to thee a covering of the eyes, unto all that are with thee, and with all other: thus she was reproved.
+So Abraham prayed unto God: and God healed Abimelech, and his wife, and his maidservants; and they bare children.
+For the LORD had fast closed up all the wombs of the house of Abimelech, because of Sarah Abraham's wife.
+
+21.
+And the LORD visited Sarah as he had said, and the LORD did unto Sarah as he had spoken.
+For Sarah conceived, and bare Abraham a son in his old age, at the set time of which God had spoken to him.
+And Abraham called the name of his son that was born unto him, whom Sarah bare to him, Isaac.
+And Abraham circumcised his son Isaac being eight days old, as God had commanded him.
+And Abraham was an hundred years old, when his son Isaac was born unto him.
+And Sarah said, God hath made me to laugh, so that all that hear will laugh with me.
+And she said, Who would have said unto Abraham, that Sarah should have given children suck? for I have born him a son in his old age.
+And the child grew, and was weaned: and Abraham made a great feast the same day that Isaac was weaned.
+And Sarah saw the son of Hagar the Egyptian, which she had born unto Abraham, mocking.
+Wherefore she said unto Abraham, Cast out this bondwoman and her son: for the son of this bondwoman shall not be heir with my son, even with Isaac.
+And the thing was very grievous in Abraham's sight because of his son.
+And God said unto Abraham, Let it not be grievous in thy sight because of the lad, and because of thy bondwoman; in all that Sarah hath said unto thee, hearken unto her voice; for in Isaac shall thy seed be called.
+And also of the son of the bondwoman will I make a nation, because he is thy seed.
+And Abraham rose up early in the morning, and took bread, and a bottle of water, and gave it unto Hagar, putting it on her shoulder, and the child, and sent her away: and she departed, and wandered in the wilderness of Beer-sheba.
+And the water was spent in the bottle, and she cast the child under one of the shrubs.
+And she went, and sat her down over against him a good way off, as it were a bowshot: for she said, Let me not see the death of the child. And she sat over against him, and lift up her voice, and wept.
+And God heard the voice of the lad; and the angel of God called Hagar out of heaven, and said unto her, What aileth thee, Hagar? fear not; for God hath heard the voice of the lad where he is.
+Arise, lift up the lad, and hold him in thine hand; for I will make him a great nation.
+And God opened her eyes, and she saw a well of water; and she went, and filled the bottle with water, and gave the lad drink.
+And God was with the lad; and he grew, and dwelt in the wilderness, and became an archer.
+And he dwelt in the wilderness of Paran: and his mother took him a wife out of the land of Egypt.
+And it came to pass at that time, that Abimelech and Phichol the chief captain of his host spake unto Abraham, saying, God is with thee in all that thou doest:
+Now therefore swear unto me here by God that thou wilt not deal falsely with me, nor with my son, nor with my son's son: but according to the kindness that I have done unto thee, thou shalt do unto me, and to the land wherein thou hast sojourned.
+And Abraham said, I will swear.
+And Abraham reproved Abimelech because of a well of water, which Abimelech's servants had violently taken away.
+And Abimelech said, I wot not who hath done this thing: neither didst thou tell me, neither yet heard I of it, but to day.
+And Abraham took sheep and oxen, and gave them unto Abimelech; and both of them made a covenant.
+And Abraham set seven ewe lambs of the flock by themselves.
+And Abimelech said unto Abraham, What mean these seven ewe lambs which thou hast set by themselves?
+And he said, For these seven ewe lambs shalt thou take of my hand, that they may be a witness unto me, that I have digged this well.
+Wherefore he called that place Beer-sheba; because there they sware both of them.
+Thus they made a covenant at Beer-sheba: then Abimelech rose up, and Phichol the chief captain of his host, and they returned into the land of the Philistines.
+And Abraham planted a grove in Beer-sheba, and called there on the name of the LORD, the everlasting God.
+And Abraham sojourned in the Philistines' land many days.
+
+22.
+And it came to pass after these things, that God did tempt Abraham, and said unto him, Abraham: and he said, Behold, here I am.
+And he said, Take now thy son, thine only son Isaac, whom thou lovest, and get thee into the land of Moriah; and offer him there for a burnt offering upon one of the mountains which I will tell thee of.
+And Abraham rose up early in the morning, and saddled his ass, and took two of his young men with him, and Isaac his son, and clave the wood for the burnt offering, and rose up, and went unto the place of which God had told him.
+Then on the third day Abraham lifted up his eyes, and saw the place afar off.
+And Abraham said unto his young men, Abide ye here with the ass; and I and the lad will go yonder and worship, and come again to you,
+And Abraham took the wood of the burnt offering, and laid it upon Isaac his son; and he took the fire in his hand, and a knife; and they went both of them together.
+And Isaac spake unto Abraham his father, and said, My father: and he said, Here am I, my son. And he said, Behold the fire and the wood: but where is the lamb for a burnt offering?
+And Abraham said, My son, God will provide himself a lamb for a burnt offering: so they went both of them together.
+And they came to the place which God had told him of; and Abraham built an altar there, and laid the wood in order, and bound Isaac his son, and laid him on the altar upon the wood.
+And Abraham stretched forth his hand, and took the knife to slay his son.
+And the angel of the LORD called unto him out of heaven, and said, Abraham, Abraham: and he said, Here am I.
+And he said, Lay not thine hand upon the lad, neither do thou any thing unto him: for now I know that thou fearest God, seeing thou hast not withheld thy son, thine only son from me.
+And Abraham lifted up his eyes, and looked, and behold behind him a ram caught in a thicket by his horns: and Abraham went and took the ram, and offered him up for a burnt offering in the stead of his son.
+And Abraham called the name of that place Jehovah-jireh: as it is said to this day, In the mount of the LORD it shall be seen.
+And the angel of the LORD called unto Abraham out of heaven the second time,
+And said, By myself have I sworn, saith the LORD, for because thou hast done this thing, and hast not withheld thy son, thine only son:
+That in blessing I will bless thee, and in multiplying I will multiply thy seed as the stars of the heaven, and as the sand which is upon the sea shore; and thy seed shall possess the gate of his enemies;
+And in thy seed shall all the nations of the earth be blessed; because thou hast obeyed my voice.
+So Abraham returned unto his young men, and they rose up and went together to Beer-sheba; and Abraham dwelt at Beer-sheba.
+And it came to pass after these things, that it was told Abraham, saying, Behold, Milcah, she hath also born children unto thy brother Nahor;
+Huz his firstborn, and Buz his brother, and Kemuel the father of Aram,
+And Chesed, and Hazo, and Pildash, and Jidlaph, and Bethuel.
+And Bethuel begat Rebekah: these eight Milcah did bear to Nahor, Abraham's brother.
+And his concubine, whose name was Reumah, she bare also Tebah, and Gaham, and Thahash, and Maachah.
+
+23.
+And Sarah was an hundred and seven and twenty years old: these were the years of the life of Sarah.
+And Sarah died in Kirjath-arba; the same is Hebron in the land of Canaan: and Abraham came to mourn for Sarah, and to weep for her.
+And Abraham stood up from before his dead, and spake unto the sons of Heth, saying,
+I am a stranger and a sojourner with you: give me a possession of a buryingplace with you, that I may bury my dead out of my sight.
+And the children of Heth answered Abraham, saying unto him,
+Hear us, my lord: thou art a mighty prince among us: in the choice of our sepulchres bury thy dead; none of us shall withhold from thee his sepulchre, but that thou mayest bury thy dead.
+And Abraham stood up, and bowed himself to the people of the land, even to the children of Heth.
+And he communed with them, saying, If it be your mind that I should bury my dead out of my sight; hear me, and intreat for me to Ephron the son of Zohar,
+That he may give me the cave of Machpelah, which he hath, which is in the end of his field; for as much money as it is worth he shall give it me for a possession of a buryingplace amongst you.
+And Ephron dwelt among the children of Heth: and Ephron the Hittite answered Abraham in the audience of the children of Heth, even of all that went in at the gate of his city, saying,
+Nay, my lord, hear me: the field give I thee, and the cave that is therein, I give it thee; in the presence of the sons of my people give I it thee: bury thy dead.
+And Abraham bowed down himself before the people of the land.
+And he spake unto Ephron in the audience of the people of the land, saying, But if thou wilt give it, I pray thee, hear me: I will give thee money for the field; take it of me, and I will bury my dead there.
+And Ephron answered Abraham, saying unto him,
+My lord, hearken unto me: the land is worth four hundred shekels of silver; what is that betwixt me and thee? bury therefore thy dead.
+And Abraham hearkened unto Ephron; and Abraham weighed to Ephron the silver, which he had named in the audience of the sons of Heth, four hundred shekels of silver, current money with the merchant.
+And the field of Ephron, which was in Machpelah, which was before Mamre, the field, and the cave which was therein, and all the trees that were in the field, that were in all the borders round about, were made sure
+Unto Abraham for a possession in the presence of the children of Heth, before all that went in at the gate of his city.
+And after this, Abraham buried Sarah his wife in the cave of the field of Machpelah before Mamre: the same is Hebron in the land of Canaan.
+And the field, and the cave that is therein, were made sure unto Abraham for a possession of a buryingplace by the sons of Heth.
+
+24.
+And Abraham was old, and well stricken in age: and the LORD had blessed Abraham in all things.
+And Abraham said unto his eldest servant of his house, that ruled over all that he had, Put, I pray thee, thy hand under my thigh:
+And I will make thee swear by the LORD, the God of heaven, and the God of the earth, that thou shalt not take a wife unto my son of the daughters of the Canaanites, among whom I dwell:
+But thou shalt go unto my country, and to my kindred, and take a wife unto my son Isaac.
+And the servant said unto him, Peradventure the woman will not be willing to follow me unto this land: must I needs bring thy son again unto the land from whence thou camest?
+And Abraham said unto him, Beware thou that thou bring not my son thither again.
+The LORD God of heaven, which took me from my father's house, and from the land of my kindred, and which spake unto me, and that sware unto me, saying, Unto thy seed will I give this land; he shall send his angel before thee, and thou shalt take a wife unto my son from thence.
+And if the woman will not be willing to follow thee, then thou shalt be clear from this my oath: only bring not my son thither again.
+And the servant put his hand under the thigh of Abraham his master, and sware to him concerning that matter.
+And the servant took ten camels of the camels of his master, and departed; for all the goods of his master were in his hand: and he arose, and went to Mesopotamia, unto the city of Nahor.
+And he made his camels to kneel down without the city by a well of water at the time of the evening, even the time that women go out to draw water.
+And he said, O LORD God of my master Abraham, I pray thee, send me good speed this day, and shew kindness unto my master Abraham.
+Behold, I stand here by the well of water; and the daughters of the men of the city come out to draw water:
+And let it come to pass, that the damsel to whom I shall say, Let down thy pitcher, I pray thee, that I may drink; and she shall say, Drink, and I will give thy camels drink also: let the same be she that thou hast appointed for thy servant Isaac; and thereby shall I know that thou hast shewed kindness unto my master.
+And it came to pass, before he had done speaking, that, behold, Rebekah came out, who was born to Bethuel, son of Milcah, the wife of Nahor, Abraham's brother, with her pitcher upon her shoulder.
+And the damsel was very fair to look upon, a virgin, neither had any man known her: and she went down to the well, and filled her pitcher, and came up.
+And the servant ran to meet her, and said, Let me, I pray thee, drink a little water of thy pitcher.
+And she said, Drink, my lord: and she hasted, and let down her pitcher upon her hand, and gave him drink.
+And when she had done giving him drink, she said, I will draw water for thy camels also, until they have done drinking.
+And she hasted, and emptied her pitcher into the trough, and ran again unto the well to draw water, and drew for all his camels.
+And the man wondering at her held his peace, to wit whether the LORD had made his journey prosperous or not.
+And it came to pass, as the camels had done drinking, that the man took a golden earring of half a shekel weight, and two bracelets for her hands of ten shekels weight of gold;
+And said, Whose daughter art thou? tell me, I pray thee: is there room in thy father's house for us to lodge in?
+And she said unto him, I am the daughter of Bethuel the son of Milcah, which she bare unto Nahor.
+She said moreover unto him, We have both straw and provender enough, and room to lodge in.
+And the man bowed down his head, and worshipped the LORD.
+And he said, Blessed be the LORD God of my master Abraham, who hath not left destitute my master of his mercy and his truth: I being in the way, the LORD led me to the house of my master's brethren.
+And the damsel ran, and told them of her mother's house these things.
+And Rebekah had a brother, and his name was Laban: and Laban ran out unto the man, unto the well.
+And it came to pass, when he saw the earring and bracelets upon his sister's hands, and when he heard the words of Rebekah his sister, saying, Thus spake the man unto me; that he came unto the man; and, behold, he stood by the camels at the well.
+And he said, Come in, thou blessed of the LORD; wherefore standest thou without? for I have prepared the house, and room for the camels.
+And the man came into the house: and he ungirded his camels, and gave straw and provender for the camels, and water to wash his feet, and the men's feet that were with him.
+And there was set meat before him to eat: but he said, I will not eat, until I have told mine errand. And he said, Speak on.
+And he said, I am Abraham's servant.
+And the LORD hath blessed my master greatly; and he is become great: and he hath given him flocks, and herds, and silver, and gold, and menservants, and maidservants, and camels, and asses.
+And Sarah my master's wife bare a son to my master when she was old: and unto him hath he given all that he hath.
+And my master made me swear, saying, Thou shalt not take a wife to my son of the daughters of the Canaanites, in whose land I dwell:
+But thou shalt go unto my father's house, and to my kindred, and take a wife unto my son.
+And I said unto my master, Peradventure the woman will not follow me.
+And he said unto me, The LORD, before whom I walk, will send his angel with thee, and prosper thy way; and thou shalt take a wife for my son of my kindred, and of my father's house:
+Then shalt thou be clear from this my oath, when thou comest to my kindred; and if they give not thee one, thou shalt be clear from my oath.
+And I came this day unto the well, and said, O LORD God of my master Abraham, if now thou do prosper my way which I go;
+Behold, I stand by the well of water; and it shall come to pass, that when the virgin cometh forth to draw water, and I say to her, Give me, I pray thee, a little water of thy pitcher to drink;
+And she say to me, Both drink thou, and I will also draw for thy camels: let the same be the woman whom the LORD hath appointed out for my master's son.
+And before I had done speaking in mine heart, behold, Rebekah came forth with her pitcher on her shoulder; and she went down unto the well, and drew water: and I said unto her, Let me drink, I pray thee.
+And she made haste, and let down her pitcher from her shoulder, and said, Drink, and I will give thy camels drink also: so I drank, and she made the camels drink also.
+And I asked her, and said, Whose daughter art thou? And she said, The daughter of Bethuel, Nahor's son, whom Milcah bare unto him: and I put the earring upon her face, and the bracelets upon her hands.
+And I bowed down my head, and worshipped the LORD, and blessed the LORD God of my master Abraham, which had led me in the right way to take my master's brother's daughter unto his son.
+And now if ye will deal kindly and truly with my master, tell me: and if not, tell me; that I may turn to the right hand, or to the left.
+Then Laban and Bethuel answered and said, The thing proceedeth from the LORD: we cannot speak unto thee bad or good.
+Behold, Rebekah is before thee, take her, and go, and let her be thy master's son's wife, as the LORD hath spoken.
+And it came to pass, that, when Abraham's servant heard their words, he worshipped the LORD, bowing himself to the earth.
+And the servant brought forth jewels of silver, and jewels of gold, and raiment, and gave them to Rebekah: he gave also to her brother and to her mother precious things.
+And they did eat and drink, he and the men that were with him, and tarried all night; and they rose up in the morning, and he said, Send me away unto my master.
+And her brother and her mother said, Let the damsel abide with us a few days, at the least ten; after that she shall go.
+And he said unto them, Hinder me not, seeing the LORD hath prospered my way; send me away that I may go to my master.
+And they said, We will call the damsel, and inquire at her mouth.
+And they called Rebekah, and said unto her, Wilt thou go with this man? And she said, I will go.
+And they sent away Rebekah their sister, and her nurse, and Abraham's servant, and his men.
+And they blessed Rebekah, and said unto her, Thou art our sister, be thou the mother of thousands of millions, and let thy seed possess the gate of those which hate them.
+And Rebekah arose, and her damsels, and they rode upon the camels, and followed the man: and the servant took Rebekah, and went his way.
+And Isaac came from the way of the well Lahai-roi; for he dwelt in the south country.
+And Isaac went out to meditate in the field at the eventide: and he lifted up his eyes, and saw, and, behold, the camels were coming.
+And Rebekah lifted up her eyes, and when she saw Isaac, she lighted off the camel.
+For she had said unto the servant, What man is this that walketh in the field to meet us? And the servant had said, It is my master: therefore she took a vail, and covered herself.
+And the servant told Isaac all things that he had done.
+And Isaac brought her into his mother Sarah's tent, and took Rebekah, and she became his wife; and he loved her: and Isaac was comforted after his mother's death.
+
+25.
+Then again Abraham took a wife, and her name was Keturah.
+And she bare him Zimran, and Jokshan, and Medan, and Midian, and Ishbak, and Shuah.
+And Jokshan begat Sheba, and Dedan. And the sons of Dedan were Asshurim, and Letushim, and Leummim.
+And the sons of Midian; Ephah, and Epher, and Hanoch, and Abida, and Eldaah. All these were the children of Keturah.
+And Abraham gave all that he had unto Isaac.
+But unto the sons of the concubines, which Abraham had, Abraham gave gifts, and sent them away from Isaac his son, while he yet lived, eastward, unto the east country.
+And these are the days of the years of Abraham's life which he lived, an hundred threescore and fifteen years.
+Then Abraham gave up the ghost, and died in a good old age, an old man, and full of years; and was gathered to his people.
+And his sons Isaac and Ishmael buried him in the cave of Machpelah, in the field of Ephron the son of Zohar the Hittite, which is before Mamre;
+The field which Abraham purchased of the sons of Heth: there was Abraham buried, and Sarah his wife.
+And it came to pass after the death of Abraham, that God blessed his son Isaac; and Isaac dwelt by the well Lahai-roi.
+Now these are the generations of Ishmael, Abraham's son, whom Hagar the Egyptian, Sarah's handmaid, bare unto Abraham:
+And these are the names of the sons of Ishmael, by their names, according to their generations: the firstborn of Ishmael, Nebajoth; and Kedar, and Adbeel, and Mibsam,
+And Mishma, and Dumah, and Massa,
+Hadar, and Tema, Jetur, Naphish, and Kedemah:
+These are the sons of Ishmael, and these are their names, by their towns, and by their castles; twelve princes according to their nations.
+And these are the years of the life of Ishmael, an hundred and thirty and seven years: and he gave up the ghost and died; and was gathered unto his people.
+And they dwelt from Havilah unto Shur, that is before Egypt, as thou goest toward Assyria: and he died in the presence of all his brethren.
+And these are the generations of Isaac, Abraham's son: Abraham begat Isaac:
+And Isaac was forty years old when he took Rebekah to wife, the daughter of Bethuel the Syrian of Padan-aram, the sister to Laban the Syrian.
+And Isaac intreated the LORD for his wife, because she was barren: and the LORD was intreated of him, and Rebekah his wife conceived.
+And the children struggled together within her; and she said, If it be so, why am I thus? And she went to inquire of the LORD.
+And the LORD said unto her, Two nations are in thy womb, and two manner of people shall be separated from thy bowels; and the one people shall be stronger than the other people; and the elder shall serve the younger.
+And when her days to be delivered were fulfilled, behold, there were twins in her womb.
+And the first came out red, all over like an hairy garment; and they called his name Esau.
+And after that came his brother out, and his hand took hold on Esau's heel; and his name was called Jacob: and Isaac was threescore years old when she bare them.
+And the boys grew: and Esau was a cunning hunter, a man of the field; and Jacob was a plain man, dwelling in tents.
+And Isaac loved Esau, because he did eat of his venison: but Rebekah loved Jacob.
+And Jacob sod pottage: and Esau came from the field, and he was faint:
+And Esau said to Jacob, Feed me, I pray thee, with that same red pottage; for I am faint: therefore was his name called Edom.
+And Jacob said, Sell me this day thy birthright.
+And Esau said, Behold, I am at the point to die: and what profit shall this birthright do to me?
+And Jacob said, Swear to me this day; and he sware unto him: and he sold his birthright unto Jacob.
+Then Jacob gave Esau bread and pottage of lentiles; and he did eat and drink, and rose up, and went his way: thus Esau despised his birthright.
+
+26.
+And there was a famine in the land, beside the first famine that was in the days of Abraham. And Isaac went unto Abimelech king of the Philistines unto Gerar.
+And the LORD appeared unto him, and said, Go not down into Egypt; dwell in the land which I shall tell thee of:
+Sojourn in this land, and I will be with thee, and will bless thee; for unto thee, and unto thy seed, I will give all these countries, and I will perform the oath which I sware unto Abraham thy father;
+And I will make thy seed to multiply as the stars of heaven, and will give unto thy seed all these countries; and in thy seed shall all the nations of the earth be blessed;
+Because that Abraham obeyed my voice, and kept my charge, my commandments, my statutes, and my laws.
+And Isaac dwelt in Gerar:
+And the men of the place asked him of his wife; and he said, She is my sister: for he feared to say, She is my wife; lest, said he, the men of the place should kill me for Rebekah; because she was fair to look upon.
+And it came to pass, when he had been there a long time, that Abimelech king of the Philistines looked out at a window, and saw, and, behold, Isaac was sporting with Rebekah his wife.
+And Abimelech called Isaac, and said, Behold, of a surety she is thy wife: and how saidst thou, She is my sister? And Isaac said unto him, Because I said, Lest I die for her.
+And Abimelech said, What is this thou hast done unto us? one of the people might lightly have lien with thy wife, and thou shouldest have brought guiltiness upon us.
+And Abimelech charged all his people, saying, He that toucheth this man or his wife shall surely be put to death.
+Then Isaac sowed in that land, and received in the same year an hundredfold: and the LORD blessed him.
+And the man waxed great, and went forward, and grew until he became very great:
+For he had possession of flocks, and possessions of herds, and great store of servants: and the Philistines envied him.
+For all the wells which his father's servants had digged in the days of Abraham his father, the Philistines had stopped them, and filled them with earth.
+And Abimelech said unto Isaac, Go from us; for thou art much mightier than we.
+And Isaac departed thence, and pitched his tent in the valley of Gerar, and dwelt there.
+And Isaac digged again the wells of water, which they had digged in the days of Abraham his father; for the philistines had stopped them after the death of Abraham: and he called their names after the names by which his father had called them.
+And Isaac's servants digged in the valley, and found there a well of springing water.
+And the herdmen of Gerar did strive with Isaac's herdmen, saying, The water is ours: and he called the name of the well Esek; because they strove with him.
+And they digged another well, and strove for that also:and he called the name of it Sitnah.
+And he removed from thence, and digged another well; and for that they strove not: and he called the name of it Rehoboth; and he said, For now the LORD hath made room for us, and we shall be fruitful in the land.
+And he went up from thence to Beer-sheba.
+And the LORD appeared unto him the same night, and said, I am the God of Abraham thy father: fear not, for I am with thee, and will bless thee, and multiply thy seed for my servant Abraham's sake.
+And he builded an altar there, and called upon the name of the LORD and pitched his tent there: and there Isaac's servants digged a well.
+Then Abimelech went to him from Gerar, and Ahuzzath one of his friends, and Phichol the chief captain of his army.
+And Isaac said unto them, Wherefore come ye to me, seeing ye hate me, and have sent me away from you?
+And they said, We saw certainly that the LORD was with thee: and we said, Let there be now an oath betwixt us, even betwixt us and thee, and let us make a covenant with thee;
+That thou wilt do us no hurt, as we have not touched thee, and as we have done unto thee nothing but good, and have sent thee away in peace: thou art now the blessed of the LORD.
+And he made them a feast, and they did eat and drink.
+And they rose up betimes in the morning, and sware one to another: and Isaac sent them away, and they departed from him in peace.
+And it came to pass the same day, that Isaac's servants came, and told him concerning the well which they had digged, and said unto him, We have found water.
+And he called it Shebah: therefore the name of the city is Beer-sheba unto this day.
+And Esau was forty years old when he took to wife Judith the daughter of Beeri the Hittite, and Bashemath the daughter of Elon the Hittite:
+Which were a grief of mind unto Isaac and to Rebekah.
+
+27.
+And it came to pass, that when Isaac was old, and his eyes were dim, so that he could not see, he called Esau his eldest son, and said unto him, My son: and he said unto him, Behold, here am I.
+And he said, Behold now, I am old, I know not the day of my death:
+Now therefore take, I pray thee, thy weapons, thy quiver and thy bow, and go out to the field, and take me some venison;
+And make me savoury meat, such as I love, and bring it to me, that I may eat; that my soul may bless thee before I die.
+And Rebekah heard when Isaac spake to Esau his son. And Esau went to the field to hunt for venison, and to bring it.
+And Rebekah spake unto Jacob her son, saying, Behold, I heard thy father speak unto Esau thy brother, saying,
+Bring me venison, and make me savoury meat, that I may eat, and bless thee before the LORD before my death.
+Now therefore, my son, obey my voice according to that which I command thee.
+Go now to the flock, and fetch me from thence two good kids of the goats; and I will make them savoury meat for thy father, such as he loveth:
+And thou shalt bring it to thy father, that he may eat, and that he may bless thee before his death.
+And Jacob said to Rebekah his mother, Behold, Esau my brother is a hairy man, and I am a smooth man:
+My father peradventure will feel me, and I shall seem to him as a deceiver; and I shall bring a curse upon me, and not a blessing.
+And his mother said unto him, Upon me be thy curse, my son: only obey my voice, and go fetch me them.
+And he went, and fetched, and brought them to his mother: and his mother made savoury meat, such as his father loved.
+And Rebekah took goodly raiment of her eldest son Esau, which were with her in the house, and put them upon Jacob her younger son:
+And she put the skins of the kids of the goats upon his hands, and upon the smooth of his neck:
+And she gave the savoury meat and the bread, which she had prepared, into the hand of her son Jacob.
+And he came unto his father, and said, My father: and he said, Here am I; who art thou, my son?
+And Jacob said unto his father, I am Esau thy firstborn; I have done according as thou badest me: arise, I pray thee, sit and eat of my venison, that thy soul may bless me.
+And Isaac said unto his son, How is it that thou hast found it so quickly, my son? And he said, Because the LORD thy God brought it to me.
+And Isaac said unto Jacob, Come near, I pray thee, that I may feel thee, my son, whether thou be my very son Esau or not.
+And Jacob went near unto Isaac his father; and he felt him, and said, The voice is Jacob's voice, but the hands are the hands of Esau.
+And he discerned him not, because his hands were hairy, as his brother Esau's hands: so he blessed him.
+And he said, Art thou my very son Esau? And he said, I am.
+And he said, Bring it near to me, and I will eat of my son's venison, that my soul may bless thee. And he brought it near to him, and he did eat: and he brought him wine, and he drank.
+And his father Isaac said unto him, Come near now, and kiss me, my son.
+And he came near, and kissed him: and he smelled the smell of his raiment, and blessed him, and said, See, the smell of my son is as the smell of a field which the LORD hath blessed:
+Therefore God give thee of the dew of heaven, and the fatness of the earth, and plenty of corn and wine:
+Let people serve thee, and nations bow down to thee: be lord over thy brethren, and let thy mother's sons bow down to thee: cursed be every one that curseth thee, and blessed be he that blesseth thee.
+And it came to pass, as soon as Isaac had made an end of blessing Jacob, and Jacob was yet scarce gone out from the presence of Isaac his father, that Esau his brother came in from his hunting.
+And he also had made savoury meat, and brought it unto his father, and said unto his father, Let my father arise, and eat of his son's venison, that thy soul may bless me.
+And Isaac his father said unto him, Who art thou? And he said, I am thy son, thy firstborn Esau.
+And Isaac trembled very exceedingly, and said, Who? where is he that hath taken venison, and brought it me, and I have eaten of all before thou camest, and have blessed him? yea, and he shall be blessed.
+And when Esau heard the words of his father, he cried with a great and exceeding bitter cry, and said unto his father, Bless me, even me also, O my father.
+And he said, Thy brother came with subtilty, and hath taken away thy blessing.
+And he said, Is not he rightly named Jacob? for he hath supplanted me these two times: he took away my birthright; and, behold, now he hath taken away my blessing. And he said, Hast thou not reserved a blessing for me?
+And Isaac answered and said unto Esau, Behold, I have made him thy lord, and all his brethren have I given to him for servants; and with corn and wine have I sustained him: and what shall I do now unto thee, my son?
+And Esau said unto his father, Hast thou but one blessing, my father? bless me, even me also, O my father. And Esau lifted up his voice, and wept.
+And Isaac his father answered and said unto him, Behold, thy dwelling shall be the fatness of the earth, and of the dew of heaven from above;
+And by thy sword shalt thou live, and shalt serve thy brother; and it shall come to pass when thou shalt have the dominion, that thou shalt break his yoke from off thy neck.
+And Esau hated Jacob because of the blessing wherewith his father blessed him: and Esau said in his heart, The days of mourning for my father are at hand; then will I slay my brother Jacob.
+And these words of Esau her elder son were told to Rebekah: and she sent and called Jacob her younger son, and said unto him, Behold, thy brother Esau, as touching thee, doth comfort himself, purposing to kill thee.
+Now therefore, my son, obey my voice; and arise, flee thou to Laban my brother to Haran;
+And tarry with him a few days, until thy brother's fury turn away;
+Until thy brother's anger turn away from thee, and he forget that which thou hast done to him: then I will send, and fetch thee from thence: why should I be deprived also of you both in one day?
+And Rebekah said to Isaac, I am weary of my life because of the daughters of Heth: if Jacob take a wife of the daughters of Heth, such as these which are of the daughters of the land, what good shall my life do me?
+
+28.
+And Isaac called Jacob, and blessed him, and charged him, and said unto him, Thou shalt not take a wife of the daughters of Canaan.
+Arise, go to Padan-aram, to the house of Bethuel thy mother's father; and take thee a wife from thence of the daughters of Laban thy mother's brother.
+And God Almighty bless thee, and make thee fruitful, and multiply thee, that thou mayest be a multitude of people;
+And give thee the blessing of Abraham, to thee, and to thy seed with thee; that thou mayest inherit the land wherein thou art a stranger, which God gave unto Abraham.
+And Isaac sent away Jacob: and he went to Padan-aram unto Laban, son of Bethuel the Syrian, the brother of Rebekah, Jacob's and Esau's mother.
+When Esau saw that Isaac had blessed Jacob, and sent him away to Padan-aram, to take him a wife from thence; and that as he blessed him he gave him a charge, saying, Thou shalt not take a wife of the daughters of Canaan;
+And that Jacob obeyed his father and his mother, and was gone to Padan-aram;
+And Esau seeing that the daughters of Canaan pleased not Isaac his father;
+Then went Esau unto Ishmael, and took unto the wives which he had Mahalath the daughter of Ishmael Abraham's son, the sister of Nebajoth, to be his wife.
+And Jacob went out from Beer-sheba, and went toward Haran.
+And he lighted upon a certain place, and tarried there all night, because the sun was set; and he took of the stones of that place, and put them for his pillows, and lay down in that place to sleep.
+And he dreamed, and behold a ladder set up on the earth, and the top of it reached to heaven: and behold the angels of God ascending and descending on it.
+And, behold, the LORD stood above it, and said, I am the LORD God of Abraham thy father, and the God of Isaac: the land whereon thou liest, to thee will I give it, and to thy seed;
+And thy seed shall be as the dust of the earth, and thou shalt spread abroad to the west, and to the east, and to the north, and to the south: and in thee and in thy seed shall all the families of the earth be blessed.
+And, behold, I am with thee, and will keep thee in all places whither thou goest, and will bring thee again into this land; for I will not leave thee, until I have done that which I have spoken to thee of.
+And Jacob awaked out of his sleep, and he said, Surely the LORD is in this place; and I knew it not.
+And he was afraid, and said, How dreadful is this place! this is none other but the house of God, and this is the gate of heaven.
+And Jacob rose up early in the morning, and took the stone that he had put for his pillows, and set it up for a pillar, and poured oil upon the top of it.
+And he called the name of that place Bethel: but the name of that city was called Luz at the first.
+And Jacob vowed a vow, saying, If God will be with me, and will keep me in this way that I go, and will give me bread to eat, and raiment to put on,
+So that I come again to my father's house in peace; then shall the LORD be my God:
+And this stone, which I have set for a pillar, shall be God's house: and of all that thou shalt give me I will surely give the tenth unto thee.
+
+29.
+Then Jacob went on his journey, and came into the land of the people of the east.
+And he looked, and behold a well in the field, and, lo, there were three flocks of sheep lying by it; for out of that well they watered the flocks: and a great stone was upon the well's mouth.
+And thither were all the flocks gathered: and they rolled the stone from the well's mouth, and watered the sheep, and put the stone again upon the well's mouth in his place.
+And Jacob said unto them, My brethren, whence be ye? And they said, Of Haran are we.
+And he said unto them, Know ye Laban the son of Nahor? And they said, We know him.
+And he said unto them, Is he well? And they said, He is well: and, behold, Rachel his daughter cometh with the sheep.
+And he said, Lo, it is yet high day, neither is it time that the cattle should be gathered together: water ye the sheep, and go and feed them.
+And they said, We cannot, until all the flocks be gathered together, and till they roll the stone from the well's mouth; then we water the sheep.
+And while he yet spake with them, Rachel came with her father's sheep: for she kept them.
+And it came to pass, when Jacob saw Rachel the daughter of Laban his mother's brother, and the sheep of Laban his mother's brother, that Jacob went near, and rolled the stone from the well's mouth, and watered the flock of Laban his mother's brother.
+And Jacob kissed Rachel, and lifted up his voice, and wept.
+And Jacob told Rachel that he was her father's brother, and that he was Rebekah's son: and she ran and told her father.
+And it came to pass, when Laban heard the tidings of Jacob his sister's son, that he ran to meet him, and embraced him, and kissed him, and brought him to his house. And he told Laban all these things.
+And Laban said to him, Surely thou art my bone and my flesh. And he abode with him the space of a month.
+And Laban said unto Jacob, Because thou art my brother, shouldest thou therefore serve me for nought? tell me, what shall thy wages be?
+And Laban had two daughters: the name of the elder was Leah, and the name of the younger was Rachel.
+Leah was tender eyed; but Rachel was beautiful and well favoured.
+And Jacob loved Rachel; and said, I will serve thee seven years for Rachel thy younger daughter.
+And Laban said, It is better that I give her to thee, than that I should give her to another man: abide with me.
+And Jacob served seven years for Rachel; and they seemed unto him but a few days, for the love he had to her.
+And Jacob said unto Laban, Give me my wife, for my days are fulfilled, that I may go in unto her.
+And Laban gathered together all the men of the place, and made a feast.
+And it came to pass in the evening, that he took Leah his daughter, and brought her to him; and he went in unto her.
+And Laban gave unto his daughter Leah Zilpah his maid for an handmaid.
+And it came to pass, that in the morning, behold, it was Leah: and he said to Laban, What is this thou hast done unto me? did not I serve with thee for Rachel? wherefore then hast thou beguiled me?
+And Laban said, It must not be so done in our country, to give the younger before the firstborn.
+Fulfil her week, and we will give thee this also for the service which thou shalt serve with me yet seven other years.
+And Jacob did so, and fulfilled her week: and he gave him Rachel his daughter to wife also.
+And Laban gave to Rachel his daughter Bilhah his handmaid to be her maid.
+And he went in also unto Rachel, and he loved also Rachel more than Leah, and served with him yet seven other years.
+And when the LORD saw that Leah was hated, he opened her womb: but Rachel was barren.
+And Leah conceived, and bare a son, and she called his name Reuben: for she said, Surely the LORD hath looked upon my affliction; now therefore my husband will love me.
+And she conceived again, and bare a son; and said, Because the LORD hath heard that I was hated, he hath therefore given me this son also: and she called his name Simeon.
+And she conceived again, and bare a son; and said, Now this time will my husband be joined unto me, because I have born him three sons: therefore was his name called Levi.
+And she conceived again, and bare a son: and she said, Now will I praise the LORD: therefore she called his name Judah; and left bearing.
+
+30.
+And when Rachel saw that she bare Jacob no children, Rachel envied her sister; and said unto Jacob, Give me children, or else I die.
+And Jacob's anger was kindled against Rachel: and he said, Am I in God's stead, who hath withheld from thee the fruit of the womb?
+And she said, Behold my maid Bilhah, go in unto her; and she shall bear upon my knees that I may also have children by her.
+And she gave him Bilhah her handmaid to wife: and Jacob went in unto her.
+And Bilhah conceived, and bare Jacob a son.
+And Rachel said, God hath judged me, and hath also heard my voice, and hath given me a son: therefore called she his name Dan.
+And Bilhah Rachel's maid conceived again, and bare Jacob a second son.
+And Rachel said, With great wrestlings have I wrestled with my sister, and I have prevailed: and she called his name Naphtali.
+When Leah saw that she had left bearing, she took Zilpah her maid, and gave her Jacob to wife.
+And Zilpah Leah's maid bare Jacob a son.
+And Leah said, A troop cometh: and she called his name Gad.
+And Zilpah Leah's maid bare Jacob a second son.
+And Leah said, Happy am I, for the daughters will call me blessed: and she called his name Asher.
+And Reuben went in the days of wheat harvest, and found mandrakes in the field, and brought them unto his mother Leah. Then Rachel said to Leah, Give me, I pray thee, of thy son's mandrakes.
+And she said unto her, Is it a small matter that thou hast taken my husband? and wouldest thou take away my son's mandrakes also? And Rachel said, Therefore he shall lie with thee to night for thy son's mandrakes.
+And Jacob came out of the field in the evening, and Leah went out to meet him, and said, Thou must come in unto me; for surely I have hired thee with my son's mandrakes. And he lay with her that night.
+And God hearkened unto Leah, and she conceived, and bare Jacob the fifth son.
+And Leah said, God hath given me my hire, because I have given my maiden to my husband: and she called his name Issachar.
+And Leah conceived again, and bare Jacob the sixth son.
+And Leah said, God hath endued me with a good dowry; now will my husband dwell with me, because I have born him six sons: and she called his name Zebulun.
+And afterwards she bare a daughter, and called her name Dinah.
+And God remembered Rachel, and God hearkened to her, and opened her womb.
+And she conceived, and bare a son; and said, God hath taken away my reproach:
+And she called his name Joseph; and said, The LORD shall add to me another son.
+And it came to pass, when Rachel had born Joseph, that Jacob said unto Laban, Send me away, that I may go unto mine own place, and to my country.
+Give me my wives and my children, for whom I have served thee, and let me go: for thou knowest my service which I have done thee.
+And Laban said unto him, I pray thee, if I have found favour in thine eyes, tarry: for I have learned by experience that the LORD hath blessed me for thy sake.
+And he said, Appoint me thy wages, and I will give it.
+And he said unto him, Thou knowest how I have served thee, and how thy cattle was with me.
+For it was little which thou hadst before I came, and it is now increased unto a multitude; and the LORD hath blessed thee since my coming: and now when shall I provide for mine own house also?
+And he said, What shall I give thee? And Jacob said, Thou shalt not give me any thing: if thou wilt do this thing for me, I will again feed and keep thy flock:
+I will pass through all thy flock to day, removing from thence all the speckled and spotted cattle, and all the brown cattle among the sheep, and the spotted and speckled among the goats: and of such shall be my hire.
+So shall my righteousness answer for me in time to come, when it shall come for my hire before thy face: every one that is not speckled and spotted among the goats, and brown among the sheep, that shall be counted stolen with me.
+And Laban said, Behold, I would it might be according to thy word.
+And he removed that day the he goats that were ringstraked and spotted, and all the she goats that were speckled and spotted, and every one that had some white in it, and all the brown among the sheep, and gave them into the hand of his sons.
+And he set three days' journey betwixt himself and Jacob: and Jacob fed the rest of Laban's flocks.
+And Jacob took him rods of green poplar, and of the hazel and chesnut tree; and pilled white strakes in them, and made the white appear which was in the rods.
+And he set the rods which he had pilled before the flocks in the gutters in the watering troughs when the flocks came to drink, that they should conceive when they came to drink.
+And the flocks conceived before the rods, and brought forth cattle ringstraked, speckled, and spotted.
+And Jacob did separate the lambs, and set the faces of the flocks toward the ringstraked, and all the brown in the flock of Laban; and he put his own flocks by themselves, and put them not unto Laban's cattle.
+And it came to pass, whensoever the stronger cattle did conceive, that Jacob laid the rods before the eyes of the cattle in the gutters, that they might conceive among the rods.
+But when the cattle were feeble, he put them not in: so the feebler were Laban's, and the stronger Jacob's.
+And the man increased exceedingly, and had much cattle, and maidservants, and menservants, and camels, and asses.
+
+31.
+And he heard the words of Laban's sons, saying, Jacob hath taken away all that was our father's; and of that which was our father's hath he gotten all this glory.
+And Jacob beheld the countenance of Laban, and, behold, it was not toward him as before.
+And the LORD said unto Jacob, Return unto the land of thy fathers, and to thy kindred; and I will be with thee.
+And Jacob sent and called Rachel and Leah to the field unto his flock,
+And said unto them, I see your father's countenance, that it is not toward me as before; but the God of my father hath been with me.
+And ye know that with all my power I have served your father.
+And your father hath deceived me, and changed my wages ten times; but God suffered him not to hurt me.
+If he said thus, The speckled shall be thy wages; then all the cattle bare speckled: and if he said thus, The ringstraked shall be thy hire; then bare all the cattle ringstraked.
+Thus God hath taken away the cattle of your father, and given them to me.
+And it came to pass at the time that the cattle conceived, that I lifted up mine eyes, and saw in a dream, and, behold, the rams which leaped upon the cattle were ringstraked, speckled, and grisled.
+And the angel of God spake unto me in a dream, saying, Jacob: And I said, Here am I.
+And he said, Lift up now thine eyes, and see, all the rams which leap upon the cattle are ringstraked, speckled, and grisled: for I have seen all that Laban doeth unto thee.
+I am the God of Bethel, where thou anointedst the pillar, and where thou vowedst a vow unto me: now arise, get thee out from this land, and return unto the land of thy kindred.
+And Rachel and Leah answered and said unto him, Is there yet any portion or inheritance for us in our father's house?
+Are we not counted of him strangers? for he hath sold us, and hath quite devoured also our money.
+For all the riches which God hath taken from our father, that is ours, and our children's: now then, whatsoever God hath said unto thee, do.
+Then Jacob rose up, and set his sons and his wives upon camels;
+And he carried away all his cattle, and all his goods which he had gotten, the cattle of his getting, which he had gotten in Padan-aram, for to go to Isaac his father in the land of Canaan.
+And Laban went to shear his sheep: and Rachel had stolen the images that were her father's.
+And Jacob stole away unawares to Laban the Syrian, in that he told him not that he fled.
+So he fled with all that he had; and he rose up, and passed over the river, and set his face toward the mount Gilead.
+And it was told Laban on the third day that Jacob was fled.
+And he took his brethren with him, and pursued after him seven days' journey; and they overtook him in the mount Gilead.
+And God came to Laban the Syrian in a dream by night, and said unto him, Take heed that thou speak not to Jacob either good or bad.
+Then Laban overtook Jacob. Now Jacob had pitched his tent in the mount: and Laban with his brethren pitched in the mount of Gilead.
+And Laban said to Jacob, What hast thou done, that thou hast stolen away unawares to me, and carried away my daughters, as captives taken with the sword?
+Wherefore didst thou flee away secretly, and steal away from me; and didst not tell me, that I might have sent thee away with mirth, and with songs, with tabret, and with harp?
+And hast not suffered me to kiss my sons and my daughters? thou hast now done foolishly in so doing.
+It is in the power of my hand to do you hurt: but the God of your father spake unto me yesternight, saying, Take thou heed that thou speak not to Jacob either good or bad.
+And now, though thou wouldest needs be gone, because thou sore longedst after thy father's house, yet wherefore hast thou stolen my gods?
+And Jacob answered and said to Laban, Because I was afraid: for I said, Peradventure thou wouldest take by force thy daughters from me.
+With whomsoever thou findest thy gods, let him not live: before our brethren discern thou what is thine with me, and take it to thee. For Jacob knew not that Rachel had stolen them.
+And Laban went into Jacob's tent, and into Leah's tent, and into the two maidservants' tents; but he found them not. Then went he out of Leah's tent, and entered into Rachel's tent.
+Now Rachel had taken the images, and put them in the camel's furniture, and sat upon them. And Laban searched all the tent, but found them not.
+And she said to her father, Let it not displease my lord that I cannot rise up before thee; for the custom of women is upon me. And he searched, but found not the images.
+And Jacob was wroth, and chode with Laban: and Jacob answered and said to Laban, What is my trespass? what is my sin, that thou hast so hotly pursued after me?
+Whereas thou hast searched all my stuff, what hast thou found of all thy household stuff? set it here before my brethren and thy brethren, that they may judge betwixt us both.
+This twenty years have I been with thee; thy ewes and thy she goats have not cast their young, and the rams of thy flock have I not eaten.
+That which was torn of beasts I brought not unto thee; I bare the loss of it; of my hand didst thou require it, whether stolen by day, or stolen by night.
+Thus I was; in the day the drought consumed me, and the frost by night; and my sleep departed from mine eyes.
+Thus have I been twenty years in thy house; I served thee fourteen years for thy two daughters, and six years for thy cattle: and thou hast changed my wages ten times.
+Except the God of my father, the God of Abraham, and the fear of Isaac, had been with me, surely thou hadst sent me away now empty. God hath seen mine affliction and the labour of my hands, and rebuked thee yesternight.
+And Laban answered and said unto Jacob, These daughters are my daughters, and these children are my children, and these cattle are my cattle, and all that thou seest is mine: and what can I do this day unto these my daughters, or unto their children which they have born?
+Now therefore come thou, let us make a covenant, I and thou; and let it be for a witness between me and thee.
+And Jacob took a stone, and set it up for a pillar.
+And Jacob said unto his brethren, Gather stones; and they took stones, and made an heap: and they did eat there upon the heap.
+And Laban called it Jegar-sahadutha: but Jacob called it Galeed.
+And Laban said, This heap is a witness between me and thee this day. Therefore was the name of it called Galeed;
+And Mizpah; for he said, The LORD watch between me and thee, when we are absent one from another.
+If thou shalt afflict my daughters, or if thou shalt take other wives beside my daughters, no man is with us; see, God is witness betwixt me and thee.
+And Laban said to Jacob, Behold this heap, and behold this pillar, which I have cast betwixt me and thee;
+This heap be witness, and this pillar be witness, that I will not pass over this heap to thee, and that thou shalt not pass over this heap and this pillar unto me, for harm.
+The God of Abraham, and the God of Nahor, the God of their father, judge betwixt us. And Jacob sware by the fear of his father Isaac.
+Then Jacob offered sacrifice upon the mount, and called his brethren to eat bread: and they did eat bread, and tarried all night in the mount.
+And early in the morning Laban rose up, and kissed his sons and his daughters, and blessed them: and Laban departed, and returned unto his place.
+
+32.
+And Jacob went on his way, and the angels of God met him.
+And when Jacob saw them, he said, This is God's host: and he called the name of that place Mahanaim.
+And Jacob sent messengers before him to Esau his brother unto the land of Seir, the country of Edom.
+And he commanded them, saying, Thus shall ye speak unto my lord Esau; Thy servant Jacob saith thus, I have sojourned with Laban, and stayed there until now:
+And I have oxen, and asses, flocks, and menservants, and womenservants: and I have sent to tell my lord, that I may find grace in thy sight.
+And the messengers returned to Jacob, saying, We came to thy brother Esau, and also he cometh to meet thee, and four hundred men with him.
+Then Jacob was greatly afraid and distressed: and he divided the people that was with him, and the flocks, and herds, and the camels, into two bands;
+And said, If Esau come to the one company, and smite it, then the other company which is left shall escape.
+And Jacob said, O God of my father Abraham, and God of my father Isaac, the LORD which saidst unto me, Return unto thy country, and to thy kindred, and I will deal well with thee:
+I am not worthy of the least of all the mercies, and of all the truth, which thou hast shewed unto thy servant; for with my staff I passed over this Jordan; and now I am become two bands.
+Deliver me, I pray thee, from the hand of my brother, from the hand of Esau: for I fear him, lest he will come and smite me, and the mother with the children.
+And thou saidst, I will surely do thee good, and make thy seed as the sand of the sea, which cannot be numbered for multitude.
+And he lodged there that same night; and took of that which came to his hand a present for Esau his brother;
+Two hundred she goats, and twenty he goats, two hundred ewes, and twenty rams,
+Thirty milch camels with their colts, forty kine, and ten bulls, twenty she asses, and ten foals.
+And he delivered them into the hand of his servants, every drove by themselves; and said unto his servants, Pass over before me, and put a space betwixt drove and drove.
+And he commanded the foremost, saying, When Esau my brother meeteth thee, and asketh thee, saying, Whose art thou? and whither goest thou? and whose are these before thee?
+Then thou shalt say, They be thy servant Jacob's; it is a present sent unto my lord Esau: and, behold, also he is behind us.
+And so commanded he the second, and the third, and all that followed the droves, saying, On this manner shall ye speak unto Esau, when ye find him.
+And say ye moreover, Behold, thy servant Jacob is behind us. For he said, I will appease him with the present that goeth before me, and afterward I will see his face; peradventure he will accept of me.
+So went the present over before him: and himself lodged that night in the company.
+And he rose up that night, and took his two wives, and his two womenservants, and his eleven sons, and passed over the ford Jabbok.
+And he took them, and sent them over the brook, and sent over that he had.
+And Jacob was left alone; and there wrestled a man with him until the breaking of the day.
+And when he saw that he prevailed not against him, he touched the hollow of his thigh; and the hollow of Jacob's thigh was out of joint, as he wrestled with him.
+And he said, Let me go, for the day breaketh. And he said, I will not let thee go, except thou bless me.
+And he said unto him, What is thy name? And he said, Jacob.
+And he said, Thy name shall be called no more Jacob, but Israel: for as a prince hast thou power with God and with men, and hast prevailed.
+And Jacob asked him, and said, Tell me, I pray thee, thy name. And he said, Wherefore is it that thou dost ask after my name? And he blessed him there.
+And Jacob called the name of the place Peniel: for I have seen God face to face, and my life is preserved.
+And as he passed over Penuel the sun rose upon him, and he halted upon his thigh.
+Therefore the children of Israel eat not of the sinew which shrank, which is upon the hollow of the thigh, unto this day: because he touched the hollow of Jacob's thigh in the sinew that shrank.
+
+33.
+And Jacob lifted up his eyes, and looked, and, behold, Esau came, and with him four hundred men. And he divided the children unto Leah, and unto Rachel, and unto the two handmaids.
+And he put the handmaids and their children foremost, and Leah and her children after, and Rachel and Joseph hindermost.
+And he passed over before them, and bowed himself to the ground seven times, until he came near to his brother.
+And Esau ran to meet him, and embraced him, and fell on his neck, and kissed him: and they wept.
+And he lifted up his eyes, and saw the women and the children; and said, Who are those with thee? And he said, The children which God hath graciously given thy servant.
+Then the handmaidens came near, they and their children, and they bowed themselves.
+And Leah also with her children came near, and bowed themselves: and after came Joseph near and Rachel, and they bowed themselves.
+And he said, What meanest thou by all this drove which I met? And he said, These are to find grace in the sight of my lord.
+And Esau said, I have enough, my brother; keep that thou hast unto thyself.
+And Jacob said, Nay, I pray thee, if now I have found grace in thy sight, then receive my present at my hand: for therefore I have seen thy face, as though I had seen the face of God, and thou wast pleased with me.
+Take, I pray thee, my blessing that is brought to thee; because God hath dealt graciously with me, and because I have enough. And he urged him, and he took it.
+And he said, Let us take our journey, and let us go, and I will go before thee.
+And he said unto him, My lord knoweth that the children are tender, and the flocks and herds with young are with me: and if men should overdrive them one day, all the flock will die.
+Let my lord, I pray thee, pass over before his servant: and I will lead on softly, according as the cattle that goeth before me and the children be able to endure, until I come unto my lord unto Seir.
+And Esau said, Let me now leave with thee some of the folk that are with me. And he said, What needeth it? let me find grace in the sight of my lord.
+So Esau returned that day on his way unto Seir.
+And Jacob journeyed to Succoth, and built him an house, and made booths for his cattle: therefore the name of the place is called Succoth.
+And Jacob came to Shalem, a city of Shechem, which is in the land of Canaan, when he came from Padan-aram; and pitched his tent before the city.
+And he bought a parcel of a field, where he had spread his tent, at the hand of the children of Hamor, Shechem's father, for an hundred pieces of money.
+And he erected there an altar, and called it El-elohe-Israel.
+
+34.
+And Dinah the daughter of Leah, which she bare unto Jacob, went out to see the daughters of the land.
+And when Shechem the son of Hamor the Hivite, prince of the country, saw her, he took her, and lay with her, and defiled her.
+And his soul clave unto Dinah the daughter of Jacob, and he loved the damsel, and spake kindly unto the damsel.
+And Shechem spake unto his father Hamor, saying, Get me this damsel to wife.
+And Jacob heard that he had defiled Dinah his daughter: now his sons were with his cattle in the field: and Jacob held his peace until they were come.
+And Hamor the father of Shechem went out unto Jacob to commune with him.
+And the sons of Jacob came out of the field when they heard it: and the men were grieved, and they were very wroth, because he had wrought folly in Israel in lying with Jacob's daughter; which thing ought not to be done.
+And Hamor communed with them, saying, The soul of my son Shechem longeth for your daughter: I pray you give her him to wife.
+And make ye marriages with us, and give your daughters unto us, and take our daughters unto you.
+And ye shall dwell with us: and the land shall be before you; dwell and trade ye therein, and get you possessions therein.
+And Shechem said unto her father and unto her brethren, Let me find grace in your eyes, and what ye shall say unto me I will give.
+Ask me never so much dowry and gift, and I will give according as ye shall say unto me: but give me the damsel to wife.
+And the sons of Jacob answered Shechem and Hamor his father deceitfully, and said, because he had defiled Dinah their sister:
+And they said unto them, We cannot do this thing, to give our sister to one that is uncircumcised; for that were a reproach unto us:
+But in this will we consent unto you: If ye will be as we be, that every male of you be circumcised;
+Then will we give our daughters unto you, and we will take your daughters to us, and we will dwell with you, and we will become one people.
+But if ye will not hearken unto us, to be circumcised; then will we take our daughter, and we will be gone.
+And their words pleased Hamor, and Shechem Hamor's son.
+And the young man deferred not to do the thing, because he had delight in Jacob's daughter: and he was more honourable than all the house of his father.
+And Hamor and Shechem his son came unto the gate of their city, and communed with the men of their city, saying,
+These men are peaceable with us; therefore let them dwell in the land, and trade therein; for the land, behold, it is large enough for them; let us take their daughters to us for wives, and let us give them our daughters.
+Only herein will the men consent unto us for to dwell with us, to be one people, if every male among us be circumcised, as they are circumcised.
+Shall not their cattle and their substance and every beast of theirs be ours? only let us consent unto them, and they will dwell with us.
+And unto Hamor and unto Shechem his son hearkened all that went out of the gate of his city; and every male was circumcised, all that went out of the gate of his city.
+And it came to pass on the third day, when they were sore, that two of the sons of Jacob, Simeon and Levi, Dinah's brethren, took each man his sword, and came upon the city boldly, and slew all the males.
+And they slew Hamor and Shechem his son with the edge of the sword, and took Dinah out of Shechem's house, and went out.
+The sons of Jacob came upon the slain, and spoiled the city, because they had defiled their sister.
+They took their sheep, and their oxen, and their asses, and that which was in the city, and that which was in the field,
+And all their wealth, and all their little ones, and their wives took they captive, and spoiled even all that was in the house.
+And Jacob said to Simeon and Levi, Ye have troubled me to make me to stink among the inhabitants of the land, among the Canaanites and the Perizzites: and I being few in number, they shall gather themselves together against me, and slay me; and I shall be destroyed, I and my house.
+And they said, Should he deal with our sister as with an harlot?
+
+35.
+And God said unto Jacob, Arise, go up to Bethel, and dwell there: and make there an altar unto God, that appeared unto thee when thou fleddest from the face of Esau thy brother.
+Then Jacob said unto his household, and to all that were with him, Put away the strange gods that are among you, and be clean, and change your garments:
+And let us arise, and go up to Bethel; and I will make there an altar unto God, who answered me in the day of my distress, and was with me in the way which I went.
+And they gave unto Jacob all the strange gods which were in their hand, and all their earrings which were in their ears; and Jacob hid them under the oak which was by Shechem.
+And they journeyed: and the terror of God was upon the cities that were round about them, and they did not pursue after the sons of Jacob.
+So Jacob came to Luz, which is in the land of Canaan, that is, Bethel, he and all the people that were with him.
+And he built there an altar, and called the place El-beth-el: because there God appeared unto him, when he fled from the face of his brother.
+But Deborah Rebekah's nurse died, and she was buried beneath Bethel under an oak: and the name of it was called Allon-bachuth.
+And God appeared unto Jacob again, when he came out of Padan-aram, and blessed him.
+And God said unto him, Thy name is Jacob: thy name shall not be called any more Jacob, but Israel shall be thy name: and he called his name Israel.
+And God said unto him, I am God Almighty: be fruitful and multiply; a nation and a company of nations shall be of thee, and kings shall come out of thy loins;
+And the land which I gave Abraham and Isaac, to thee I will give it, and to thy seed after thee will I give the land.
+And God went up from him in the place where he talked with him.
+And Jacob set up a pillar in the place where he talked with him, even a pillar of stone: and he poured a drink offering thereon, and he poured oil thereon.
+And Jacob called the name of the place where God spake with him, Bethel.
+And they journeyed from Bethel; and there was but a little way to come to Ephrath: and Rachel travailed, and she had hard labour.
+And it came to pass, when she was in hard labour, that the midwife said unto her, Fear not; thou shalt have this son also.
+And it came to pass, as her soul was in departing, (for she died) that she called his name Ben-oni: but his father called him Benjamin.
+And Rachel died, and was buried in the way to Ephrath, which is Bethlehem.
+And Jacob set a pillar upon her grave: that is the pillar of Rachel's grave unto this day.
+And Israel journeyed, and spread his tent beyond the tower of Edar.
+And it came to pass, when Israel dwelt in that land, that Reuben went and lay with Bilhah his father's concubine: and Israel heard it. Now the sons of Jacob were twelve:
+The sons of Leah; Reuben, Jacob's firstborn, and Simeon, and Levi, and Judah, and Issachar, and Zebulun:
+The sons of Rachel; Joseph, and Benjamin:
+And the sons of Bilhah, Rachel's handmaid; Dan, and Naphtali:
+And the sons of Zilpah, Leah's handmaid; Gad, and Asher: these are the sons of Jacob, which were born to him in Padan-aram.
+And Jacob came unto Isaac his father unto Mamre, unto the city of Arbah, which is Hebron, where Abraham and Isaac sojourned.
+And the days of Isaac were an hundred and fourscore years.
+And Isaac gave up the ghost, and died, and was gathered unto his people, being old and full of days: and his sons Esau and Jacob buried him.
+
+36.
+Now these are the generations of Esau, who is Edom.
+Esau took his wives of the daughters of Canaan; Adah the daughter of Elon the Hittite, and Aholibamah the daughter of Anah the daughter of Zibeon the Hivite;
+And Bashemath Ishmael's daughter, sister of Nebajoth.
+And Adah bare to Esau Eliphaz; and Bashemath bare Reuel;
+And Aholibamah bare Jeush, and Jaalam, and Korah: these are the sons of Esau, which were born unto him in the land of Canaan.
+And Esau took his wives, and his sons, and his daughters, and all the persons of his house, and his cattle, and all his beasts, and all his substance, which he had got in the land of Canaan; and went into the country from the face of his brother Jacob.
+For their riches were more than that they might dwell together; and the land wherein they were strangers could not bear them because of their cattle.
+Thus dwelt Esau in mount Seir: Esau is Edom.
+And these are the generations of Esau the father of the Edomites in mount Seir:
+These are the names of Esau's sons; Eliphaz the son of Adah the wife of Esau, Reuel the son of Bashemath the wife of Esau.
+And the sons of Eliphaz were Teman, Omar, Zepho, and Gatam, and Kenaz.
+And Timna was concubine to Eliphaz Esau's son; and she bare to Eliphaz Amalek: these were the sons of Adah Esau's wife.
+And these are the sons of Reuel; Nahath, and Zerah, Shammah, and Mizzah: these were the sons of Bashemath Esau's wife.
+And these were the sons of Aholibamah, the daughter of Anah the daughter of Zibeon, Esau's wife: and she bare to Esau Jeush, and Jaalam, and Korah.
+These were dukes of the sons of Esau: the sons of Eliphaz the firstborn son of Esau; duke Teman, duke Omar, duke Zepho, duke Kenaz,
+Duke Korah, duke Gatam, and duke Amalek: these are the dukes that came of Eliphaz in the land of Edom; these were the sons of Adah.
+And these are the sons of Reuel Esau's son; duke Nahath, duke Zerah, duke Shammah, duke Mizzah: these are the dukes that came of Reuel in the land of Edom; these are the sons of Bashemath Esau's wife.
+And these are the sons of Aholibamah Esau's wife; duke Jeush, duke Jaalam, duke Korah: these were the dukes that came of Aholibamah the daughter of Anah, Esau's wife.
+These are the sons of Esau, who is Edom, and these are their dukes.
+These are the sons of Seir the Horite, who inhabited the land; Lotan, and Shobal, and Zibeon, and Anah,
+And Dishon, and Ezer, and Dishan: these are the dukes of the Horites, the children of Seir in the land of Edom.
+And the children of Lotan were Hori and Hemam; and Lotan's sister was Timna.
+And the children of Shobal were these; Alvan, and Manahath, and Ebal, Shepho, and Onam.
+And these are the children of Zibeon; both Ajah, and Anah: this was that Anah that found the mules in the wilderness, as he fed the asses of Zibeon his father.
+And the children of Anah were these; Dishon, and Aholibamah the daughter of Anah.
+And these are the children of Dishon; Hemdan, and Eshban, and Ithran, and Cheran.
+The children of Ezer are these; Bilhan, and Zaavan, and Akan.
+The children of Dishan are these: Uz, and Aran.
+These are the dukes that came of the Horites; duke Lotan, duke Shobal, duke Zibeon, duke Anah,
+Duke Dishon, duke Ezer, duke Dishan: these are the dukes that came of Hori, among their dukes in the land of Seir.
+And these are the kings that reigned in the land of Edom, before there reigned any king over the children of Israel.
+And Bela the son of Beor reigned in Edom: and the name of his city was Dinhabah.
+And Bela died, and Jobab the son of Zerah of Bozrah reigned in his stead.
+And Jobab died, and Husham of the land of Temani reigned in his stead.
+And Husham died, and Hadad the son of Bedad, who smote Midian in the field of Moab, reigned in his stead: and the name of his city was Avith.
+And Hadad died, and Samlah of Masrekah reigned in his stead.
+And Samlah died, and Saul of Rehoboth by the river reigned in his stead.
+And Saul died, and Baal-hanan the son of Achbor reigned in his stead.
+And Baal-hanan the son of Achbor died, and Hadar reigned in his stead: and the name of his city was Pau; and his wife's name was Mehetabel, the daughter of Matred, the daughter of Mezahab.
+And these are the names of the dukes that came of Esau, according to their families, after their places, by their names; duke Timnah, duke Alvah, duke Jetheth,
+Duke Aholibamah, duke Elah, duke Pinon,
+Duke Kenaz, duke Teman, duke Mibzar,
+Duke Magdiel, duke Iram: these be the dukes of Edom, according to their habitations in the land of their possession: he is Esau the father of the Edomites.
+
+37.
+And Jacob dwelt in the land wherein his father was a stranger, in the land of Canaan.
+These are the generations of Jacob. Joseph, being seventeen years old, was feeding the flock with his brethren; and the lad was with the sons of Bilhah, and with the sons of Zilpah, his father's wives: and Joseph brought unto his father their evil report.
+Now Israel loved Joseph more than all his children, because he was the son of his old age: and he made him a coat of many colours.
+And when his brethren saw that their father loved him more than all his brethren, they hated him, and could not speak peaceably unto him.
+And Joseph dreamed a dream, and he told it his brethren: and they hated him yet the more.
+And he said unto them, Hear, I pray you, this dream which I have dreamed:
+For, behold, we were binding sheaves in the field, and, lo, my sheaf arose, and also stood upright; and, behold, your sheaves stood round about, and made obeisance to my sheaf.
+And his brethren said to him, Shalt thou indeed reign over us? or shalt thou indeed have dominion over us? And they hated him yet the more for his dreams, and for his words.
+And he dreamed yet another dream, and told it his brethren, and said, Behold, I have dreamed a dream more; and, behold, the sun and the moon and the eleven stars made obeisance to me.
+And he told it to his father, and to his brethren: and his father rebuked him, and said unto him, What is this dream that thou hast dreamed? Shall I and thy mother and thy brethren indeed come to bow down ourselves to thee to the earth?
+And his brethren envied him; but his father observed the saying.
+And his brethren went to feed their father's flock in Shechem.
+And Israel said unto Joseph, Do not thy brethren feed the flock in Shechem? come, and I will send thee unto them. And he said to him, Here am I.
+And he said to him, Go, I pray thee, see whether it be well with thy brethren, and well with the flocks; and bring me word again. So he sent him out of the vale of Hebron, and he came to Shechem.
+And a certain man found him, and, behold, he was wandering in the field: and the man asked him, saying, What seekest thou?
+And he said, I seek my brethren: tell me, I pray thee, where they feed their flocks.
+And the man said, They are departed hence; for I heard them say, Let us go to Dothan. And Joseph went after his brethren, and found them in Dothan.
+And when they saw him afar off, even before he came near unto them, they conspired against him to slay him.
+And they said one to another, Behold, this dreamer cometh.
+Come now therefore, and let us slay him, and cast him into some pit, and we will say, Some evil beast hath devoured him: and we shall see what will become of his dreams.
+And Reuben heard it, and he delivered him out of their hands; and said, Let us not kill him.
+And Reuben said unto them, Shed no blood, but cast him into this pit that is in the wilderness, and lay no hand upon him; that he might rid him out of their hands, to deliver him to his father again.
+And it came to pass, when Joseph was come unto his brethren, that they stript Joseph out of his coat, his coat of many colours that was on him;
+And they took him, and cast him into a pit: and the pit was empty, there was no water in it.
+And they sat down to eat bread: and they lifted up their eyes and looked, and, behold, a company of Ishmeelites came from Gilead with their camels bearing spicery and balm and myrrh, going to carry it down to Egypt.
+And Judah said unto his brethren, What profit is it if we slay our brother, and conceal his blood?
+Come, and let us sell him to the Ishmeelites, and let not our hand be upon him; for he is our brother and our flesh. And his brethren were content.
+Then there passed by Midianites merchantmen; and they drew and lifted up Joseph out of the pit, and sold Joseph to the Ishmeelites for twenty pieces of silver: and they brought Joseph into Egypt.
+And Reuben returned unto the pit; and, behold, Joseph was not in the pit; and he rent his clothes.
+And he returned unto his brethren, and said, The child is not; and I, whither shall I go?
+And they took Joseph's coat, and killed a kid of the goats, and dipped the coat in the blood;
+And they sent the coat of many colours, and they brought it to their father; and said, This have we found: know now whether it be thy son's coat or no.
+And he knew it, and said, It is my son's coat; an evil beast hath devoured him; Joseph is without doubt rent in pieces.
+And Jacob rent his clothes, and put sackcloth upon his loins, and mourned for his son many days.
+And all his sons and all his daughters rose up to comfort him; but he refused to be comforted; and he said, For I will go down into the grave unto my son mourning. Thus his father wept for him.
+And the Midianites sold him into Egypt unto Potiphar, an officer of Pharaoh's, and captain of the guard.
+
+38.
+And it came to pass at that time, that Judah went down from his brethren, and turned in to a certain Adullamite, whose name was Hirah.
+And Judah saw there a daughter of a certain Canaanite, whose name was Shuah; and he took her, and went in unto her.
+And she conceived, and bare a son; and he called his name Er.
+And she conceived again, and bare a son; and she called his name Onan.
+And she yet again conceived, and bare a son; and called his name Shelah: and he was at Chezib, when she bare him.
+And Judah took a wife for Er his firstborn, whose name was Tamar.
+And Er, Judah's firstborn, was wicked in the sight of the LORD; and the LORD slew him.
+And Judah said unto Onan, Go in unto thy brother's wife, and marry her, and raise up seed to thy brother.
+And Onan knew that the seed should not be his; and it came to pass, when he went in unto his brother's wife, that he spilled it on the ground, lest that he should give seed to his brother.
+And the thing which he did displeased the LORD: wherefore he slew him also.
+Then said Judah to Tamar his daughter in law, Remain a widow at thy father's house, till Shelah my son be grown: for he said, Lest peradventure he die also, as his brethren did. And Tamar went and dwelt in her father's house.
+And in process of time the daughter of Shuah Judah's wife died; and Judah was comforted, and went up unto his sheepshearers to Timnath, he and his friend Hirah the Adullamite.
+And it was told Tamar, saying, Behold thy father in law goeth up to Timnath to shear his sheep.
+And she put her widow's garments off from her, and covered her with a vail, and wrapped herself, and sat in an open place, which is by the way to Timnath; for she saw that Shelah was grown, and she was not given unto him to wife.
+When Judah saw her, he thought her to be an harlot; because she had covered her face.
+And he turned unto her by the way, and said, Go to, I pray thee, let me come in unto thee; (for he knew not that she was his daughter in law.) And she said, What wilt thou give me, that thou mayest come in unto me?
+And he said, I will send thee a kid from the flock. And she said, Wilt thou give me a pledge, till thou send it?
+And he said, What pledge shall I give thee? And she said, Thy signet, and thy bracelets, and thy staff that is in thine hand. And he gave it her, and came in unto her, and she conceived by him.
+And she arose, and went away, and laid by her vail from her, and put on the garments of her widowhood.
+And Judah sent the kid by the hand of his friend the Adullamite, to receive his pledge from the woman's hand: but he found her not.
+Then he asked the men of that place, saying, Where is the harlot, that was openly by the way side? And they said, There was no harlot in this place.
+And he returned to Judah, and said, I cannot find her; and also the men of the place said, that there was no harlot in this place.
+And Judah said, Let her take it to her, lest we be shamed: behold, I sent this kid, and thou hast not found her.
+And it came to pass about three months after, that it was told Judah, saying, Tamar thy daughter in law hath played the harlot; and also, behold, she is with child by whoredom. And Judah said, Bring her forth, and let her be burnt.
+When she was brought forth, she sent to her father in law, saying, By the man, whose these are, am I with child: and she said, Discern, I pray thee, whose are these, the signet, and bracelets, and staff.
+And Judah acknowledged them, and said, She hath been more righteous than I; because that I gave her not to Shelah my son. And he knew her again no more.
+And it came to pass in the time of her travail, that, behold, twins were in her womb.
+And it came to pass, when she travailed, that the one put out his hand: and the midwife took and bound upon his hand a scarlet thread, saying, This came out first,
+And it came to pass, as he drew back his hand, that, behold, his brother came out: and she said, How hast thou broken forth? this breach be upon thee: therefore his name was called Pharez.
+And afterward came out his brother, that had the scarlet thread upon his hand: and his name was called Zarah.
+
+39.
+And Joseph was brought down to Egypt; and Potiphar, an officer of Pharaoh, captain of the guard, an Egyptian, bought him of the hands of the Ishmeelites, which had brought him down thither.
+And the LORD was with Joseph, and he was a prosperous man; and he was in the house of his master the Egyptian.
+And his master saw that the LORD was with him, and that the LORD made all that he did to prosper in his hand.
+And Joseph found grace in his sight, and he served him: and he made him overseer over his house, and all that he had he put into his hand.
+And it came to pass from the time that he had made him overseer in his house, and over all that he had, that the LORD blessed the Egyptian's house for Joseph's sake; and the blessing of the LORD was upon all that he had in the house, and in the field.
+And he left all that he had in Joseph's hand; and he knew not ought he had, save the bread which he did eat. And Joseph was a goodly person, and well favoured.
+And it came to pass after these things, that his master's wife cast her eyes upon Joseph; and she said, Lie with me.
+But he refused, and said unto his master's wife, Behold, my master wotteth not what is with me in the house, and he hath committed all that he hath to my hand;
+There is none greater in this house than I; neither hath he kept back any thing from me but thee, because thou art his wife: how then can I do this great wickedness, and sin against God?
+And it came to pass, as she spake to Joseph day by day, that he hearkened not unto her, to lie by her, or to be with her.
+And it came to pass about this time, that Joseph went into the house to do his business; and there was none of the men of the house there within.
+And she caught him by his garment, saying, Lie with me: and he left his garment in her hand, and fled, and got him out.
+And it came to pass, when she saw that he had left his garment in her hand, and was fled forth,
+That she called unto the men of her house, and spake unto them, saying, See, he hath brought in an Hebrew unto us to mock us; he came in unto me to lie with me, and I cried with a loud voice:
+And it came to pass, when he heard that I lifted up my voice and cried, that he left his garment with me, and fled, and got him out.
+And she laid up his garment by her, until his lord came home.
+And she spake unto him according to these words, saying, The Hebrew servant, which thou hast brought unto us, came in unto me to mock me:
+And it came to pass, as I lifted up my voice and cried, that he left his garment with me, and fled out.
+And it came to pass, when his master heard the words of his wife, which she spake unto him, saying, After this manner did thy servant to me; that his wrath was kindled.
+And Joseph's master took him, and put him into the prison, a place where the king's prisoners were bound: and he was there in the prison.
+But the LORD was with Joseph, and shewed him mercy, and gave him favour in the sight of the keeper of the prison.
+And the keeper of the prison committed to Joseph's hand all the prisoners that were in the prison; and whatsoever they did there, he was the doer of it.
+The keeper of the prison looked not to any thing that was under his hand; because the LORD was with him, and that which he did, the LORD made it to prosper.
+
+40.
+And it came to pass after these things, that the butler of the king of Egypt and his baker had offended their lord the king of Egypt.
+And Pharaoh was wroth against two of his officers, against the chief of the butlers, and against the chief of the bakers.
+And he put them in ward in the house of the captain of the guard, into the prison, the place where Joseph was bound.
+And the captain of the guard charged Joseph with them, and he served them: and they continued a season in ward.
+And they dreamed a dream both of them, each man his dream in one night, each man according to the interpretation of his dream, the butler and the baker of the king of Egypt, which were bound in the prison.
+And Joseph came in unto them in the morning, and looked upon them, and, behold, they were sad.
+And he asked Pharaoh's officers that were with him in the ward of his lord's house, saying, Wherefore look ye so sadly to day?
+And they said unto him, We have dreamed a dream, and there is no interpreter of it. And Joseph said unto them, Do not interpretations belong to God? tell me them, I pray you.
+And the chief butler told his dream to Joseph, and said to him, In my dream, behold, a vine was before me;
+And in the vine were three branches: and it was as though it budded, and her blossoms shot forth; and the clusters thereof brought forth ripe grapes:
+And Pharaoh's cup was in my hand: and I took the grapes, and pressed them into Pharaoh's cup, and I gave the cup into Pharaoh's hand.
+And Joseph said unto him, This is the interpretation of it: The three branches are three days:
+Yet within three days shall Pharaoh lift up thine head, and restore thee unto thy place: and thou shalt deliver Pharaoh's cup into his hand, after the former manner when thou wast his butler.
+But think on me when it shall be well with thee, and shew kindness, I pray thee, unto me, and make mention of me unto Pharaoh, and bring me out of this house:
+For indeed I was stolen away out of the land of the Hebrews: and here also have I done nothing that they should put me into the dungeon.
+When the chief baker saw that the interpretation was good, he said unto Joseph, I also was in my dream, and, behold, I had three white baskets on my head:
+And in the uppermost basket there was of all manner of bakemeats for Pharaoh; and the birds did eat them out of the basket upon my head.
+And Joseph answered and said, This is the interpretation thereof: The three baskets are three days:
+Yet within three days shall Pharaoh lift up thy head from off thee, and shall hang thee on a tree; and the birds shall eat thy flesh from off thee.
+And it came to pass the third day, which was Pharaoh's birthday, that he made a feast unto all his servants: and he lifted up the head of the chief butler and of the chief baker among his servants.
+And he restored the chief butler unto his butlership again; and he gave the cup into Pharaoh's hand:
+But he hanged the chief baker: as Joseph had interpreted to them.
+Yet did not the chief butler remember Joseph, but forgat him.
+
+41.
+And it came to pass at the end of two full years, that Pharaoh dreamed: and, behold, he stood by the river.
+And, behold, there came up out of the river seven well favoured kine and fatfleshed; and they fed in a meadow.
+And, behold, seven other kine came up after them out of the river, ill favoured and leanfleshed; and stood by the other kine upon the brink of the river.
+And the ill favoured and leanfleshed kine did eat up the seven well favoured and fat kine. So Pharaoh awoke.
+And he slept and dreamed the second time: and, behold, seven ears of corn came up upon one stalk, rank and good.
+And, behold, seven thin ears and blasted with the east wind sprung up after them.
+And the seven thin ears devoured the seven rank and full ears. And Pharaoh awoke, and, behold, it was a dream.
+And it came to pass in the morning that his spirit was troubled; and he sent and called for all the magicians of Egypt, and all the wise men thereof: and Pharaoh told them his dream; but there was none that could interpret them unto Pharaoh.
+Then spake the chief butler unto Pharaoh, saying, I do remember my faults this day:
+Pharaoh was wroth with his servants, and put me in ward in the captain of the guard's house, both me and the chief baker:
+And we dreamed a dream in one night, I and he; we dreamed each man according to the interpretation of his dream.
+And there was there with us a young man, an Hebrew, servant to the captain of the guard; and we told him, and he interpreted to us our dreams; to each man according to his dream he did interpret.
+And it came to pass, as he interpreted to us, so it was; me he restored unto mine office, and him he hanged.
+Then Pharaoh sent and called Joseph, and they brought him hastily out of the dungeon: and he shaved himself, and changed his raiment, and came in unto Pharaoh.
+And Pharaoh said unto Joseph, I have dreamed a dream, and there is none that can interpret it: and I have heard say of thee, that thou canst understand a dream to interpret it.
+And Joseph answered Pharaoh, saying, It is not in me: God shall give Pharaoh an answer of peace.
+And Pharaoh said unto Joseph, In my dream, behold, I stood upon the bank of the river:
+And, behold, there came up out of the river seven kine, fatfleshed and well favoured; and they fed in a meadow:
+And, behold, seven other kine came up after them, poor and very ill favoured and leanfleshed, such as I never saw in all the land of Egypt for badness:
+And the lean and the ill favoured kine did eat up the first seven fat kine:
+And when they had eaten them up, it could not be known that they had eaten them; but they were still ill favoured, as at the beginning. So I awoke.
+And I saw in my dream, and, behold, seven ears came up in one stalk, full and good:
+And, behold, seven ears, withered, thin, and blasted with the east wind, sprung up after them:
+And the thin ears devoured the seven good ears: and I told this unto the magicians; but there was none that could declare it to me.
+And Joseph said unto Pharaoh, The dream of Pharaoh is one: God hath shewed Pharaoh what he is about to do.
+The seven good kine are seven years; and the seven good ears are seven years: the dream is one.
+And the seven thin and ill favoured kine that came up after them are seven years; and the seven empty ears blasted with the east wind shall be seven years of famine.
+This is the thing which I have spoken unto Pharaoh: What God is about to do he sheweth unto Pharaoh.
+Behold, there come seven years of great plenty throughout all the land of Egypt:
+And there shall arise after them seven years of famine; and all the plenty shall be forgotten in the land of Egypt; and the famine shall consume the land;
+And the plenty shall not be known in the land by reason of that famine following; for it shall be very grievous.
+And for that the dream was doubled unto Pharaoh twice; it is because the thing is established by God, and God will shortly bring it to pass.
+Now therefore let Pharaoh look out a man discreet and wise, and set him over the land of Egypt.
+Let Pharaoh do this, and let him appoint officers over the land, and take up the fifth part of the land of Egypt in the seven plenteous years.
+And let them gather all the food of those good years that come, and lay up corn under the hand of Pharaoh, and let them keep food in the cities.
+And that food shall be for store to the land against the seven years of famine, which shall be in the land of Egypt; that the land perish not through the famine.
+And the thing was good in the eyes of Pharaoh, and in the eyes of all his servants.
+And Pharaoh said unto his servants, Can we find such a one as this is, a man in whom the Spirit of God is?
+And Pharaoh said unto Joseph, Forasmuch as God hath shewed thee all this, there is none so discreet and wise as thou art:
+Thou shalt be over my house, and according unto thy word shall all my people be ruled: only in the throne will I be greater than thou.
+And Pharaoh said unto Joseph, See, I have set thee over all the land of Egypt.
+And Pharaoh took off his ring from his hand, and put it upon Joseph's hand, and arrayed him in vestures of fine linen, and put a gold chain about his neck;
+And he made him to ride in the second chariot which he had; and they cried before him, Bow the knee: and he made him ruler over all the land of Egypt.
+And Pharaoh said unto Joseph, I am Pharaoh, and without thee shall no man lift up his hand or foot in all the land of Egypt.
+And Pharaoh called Joseph's name Zaphnath-paaneah; and he gave him to wife Asenath the daughter of Poti-pherah priest of On. And Joseph went out over all the land of Egypt.
+And Joseph was thirty years old when he stood before Pharaoh king of Egypt. And Joseph went out from the presence of Pharaoh, and went throughout all the land of Egypt.
+And in the seven plenteous years the earth brought forth by handfuls.
+And he gathered up all the food of the seven years, which were in the land of Egypt, and laid up the food in the cities: the food of the field, which was round about every city, laid he up in the same.
+And Joseph gathered corn as the sand of the sea, very much, until he left numbering; for it was without number.
+And unto Joseph were born two sons before the years of famine came, which Asenath the daughter of Poti-pherah priest of On bare unto him.
+And Joseph called the name of the firstborn Manasseh: For God, said he, hath made me forget all my toil, and all my father's house.
+And the name of the second called he Ephraim: For God hath caused me to be fruitful in the land of my affliction.
+And the seven years of plenteousness, that was in the land of Egypt, were ended.
+And the seven years of dearth began to come, according as Joseph had said: and the dearth was in all lands; but in all the land of Egypt there was bread.
+And when all the land of Egypt was famished, the people cried to Pharaoh for bread: and Pharaoh said unto all the Egyptians, Go unto Joseph; what he saith to you, do.
+And the famine was over all the face of the earth: and Joseph opened all the storehouses, and sold unto the Egyptians; and the famine waxed sore in the land of Egypt.
+And all countries came into Egypt to Joseph for to buy corn; because that the famine was so sore in all lands.
+
+42.
+Now when Jacob saw that there was corn in Egypt, Jacob said unto his sons, Why do ye look one upon another?
+And he said, Behold, I have heard that there is corn in Egypt: get you down thither, and buy for us from thence; that we may live, and not die.
+And Joseph's ten brethren went down to buy corn in Egypt.
+But Benjamin, Joseph's brother, Jacob sent not with his brethren; for he said, Lest peradventure mischief befall him.
+And the sons of Israel came to buy corn among those that came: for the famine was in the land of Canaan.
+And Joseph was the governor over the land, and he it was that sold to all the people of the land: and Joseph's brethren came, and bowed down themselves before him with their faces to the earth.
+And Joseph saw his brethren, and he knew them, but made himself strange unto them, and spake roughly unto them; and he said unto them, Whence come ye? And they said, From the land of Canaan to buy food.
+And Joseph knew his brethren, but they knew not him.
+And Joseph remembered the dreams which he dreamed of them, and said unto them, Ye are spies; to see the nakedness of the land ye are come.
+And they said unto him, Nay, my lord, but to buy food are thy servants come.
+We are all one man's sons; we are true men, thy servants are no spies.
+And he said unto them, Nay, but to see the nakedness of the land ye are come.
+And they said, Thy servants are twelve brethren, the sons of one man in the land of Canaan; and, behold, the youngest is this day with our father, and one is not.
+And Joseph said unto them, That is it that I spake unto you, saying, Ye are spies:
+Hereby ye shall be proved: By the life of Pharaoh ye shall not go forth hence, except your youngest brother come hither.
+Send one of you, and let him fetch your brother, and ye shall be kept in prison, that your words may be proved, whether there be any truth in you: or else by the life of Pharaoh surely ye are spies.
+And he put them all together into ward three days.
+And Joseph said unto them the third day, This do, and live; for I fear God:
+If ye be true men, let one of your brethren be bound in the house of your prison: go ye, carry corn for the famine of your houses:
+But bring your youngest brother unto me; so shall your words be verified, and ye shall not die. And they did so.
+And they said one to another, We are verily guilty concerning our brother, in that we saw the anguish of his soul, when he besought us, and we would not hear; therefore is this distress come upon us.
+And Reuben answered them, saying, Spake I not unto you, saying, Do not sin against the child; and ye would not hear? therefore, behold, also his blood is required.
+And they knew not that Joseph understood them; for he spake unto them by an interpreter.
+And he turned himself about from them, and wept; and returned to them again, and communed with them, and took from them Simeon, and bound him before their eyes.
+Then Joseph commanded to fill their sacks with corn, and to restore every man's money into his sack, and to give them provision for the way: and thus did he unto them.
+And they laded their asses with the corn, and departed thence.
+And as one of them opened his sack to give his ass provender in the inn, he espied his money; for, behold, it was in his sack's mouth.
+And he said unto his brethren, My money is restored; and, lo, it is even in my sack: and their heart failed them, and they were afraid, saying one to another, What is this that God hath done unto us?
+And they came unto Jacob their father unto the land of Canaan, and told him all that befell unto them; saying,
+The man, who is the lord of the land, spake roughly to us, and took us for spies of the country.
+And we said unto him, We are true men; we are no spies:
+We be twelve brethren, sons of our father; one is not, and the youngest is this day with our father in the land of Canaan.
+And the man, the lord of the country, said unto us, Hereby shall I know that ye are true men; leave one of your brethren here with me, and take food for the famine of your households, and be gone:
+And bring your youngest brother unto me: then shall I know that ye are no spies, but that ye are true men: so will I deliver you your brother, and ye shall traffick in the land.
+And it came to pass as they emptied their sacks, that, behold, every man's bundle of money was in his sack: and when both they and their father saw the bundles of money, they were afraid.
+And Jacob their father said unto them, Me have ye bereaved of my children: Joseph is not, and Simeon is not, and ye will take Benjamin away: all these things are against me.
+And Reuben spake unto his father, saying, Slay my two sons, if I bring him not to thee: deliver him into my hand, and I will bring him to thee again.
+And he said, My son shall not go down with you; for his brother is dead, and he is left alone: if mischief befall him by the way in the which ye go, then shall ye bring down my gray hairs with sorrow to the grave.
+
+43.
+And the famine was sore in the land.
+And it came to pass, when they had eaten up the corn which they had brought out of Egypt, their father said unto them, Go again, buy us a little food.
+And Judah spake unto him, saying, The man did solemnly protest unto us, saying, Ye shall not see my face, except your brother be with you.
+If thou wilt send our brother with us, we will go down and buy thee food:
+But if thou wilt not send him, we will not go down: for the man said unto us, Ye shall not see my face, except your brother be with you.
+And Israel said, Wherefore dealt ye so ill with me, as to tell the man whether ye had yet a brother?
+And they said, The man asked us straitly of our state, and of our kindred, saying, Is your father yet alive? have ye another brother? and we told him according to the tenor of these words: could we certainly know that he would say, Bring your brother down?
+And Judah said unto Israel his father, Send the lad with me, and we will arise and go; that we may live, and not die, both we, and thou, and also our little ones.
+I will be surety for him; of my hand shalt thou require him: if I bring him not unto thee, and set him before thee, then let me bear the blame for ever:
+For except we had lingered, surely now we had returned this second time.
+And their father Israel said unto them, If it must be so now, do this; take of the best fruits in the land in your vessels, and carry down the man a present, a little balm, and a little honey, spices, and myrrh, nuts, and almonds:
+And take double money in your hand; and the money that was brought again in the mouth of your sacks, carry it again in your hand; peradventure it was an oversight:
+Take also your brother, and arise, go again unto the man:
+And God Almighty give you mercy before the man, that he may send away your other brother, and Benjamin. If I be bereaved of my children, I am bereaved.
+And the men took that present, and they took double money in their hand, and Benjamin; and rose up, and went down to Egypt, and stood before Joseph.
+And when Joseph saw Benjamin with them, he said to the ruler of his house, Bring these men home, and slay, and make ready; for these men shall dine with me at noon.
+And the man did as Joseph bade; and the man brought the men into Joseph's house.
+And the men were afraid, because they were brought into Joseph's house; and they said, Because of the money that was returned in our sacks at the first time are we brought in; that he may seek occasion against us, and fall upon us, and take us for bondmen, and our asses.
+And they came near to the steward of Joseph's house, and they communed with him at the door of the house,
+And said, O sir, we came indeed down at the first time to buy food:
+And it came to pass, when we came to the inn, that we opened our sacks, and, behold, every man's money was in the mouth of his sack, our money in full weight: and we have brought it again in our hand.
+And other money have we brought down in our hands to buy food: we cannot tell who put our money in our sacks.
+And he said, Peace be to you, fear not: your God, and the God of your father, hath given you treasure in your sacks: I had your money. And he brought Simeon out unto them.
+And the man brought the men into Joseph's house, and gave them water, and they washed their feet; and he gave their asses provender.
+And they made ready the present against Joseph came at noon: for they heard that they should eat bread there.
+And when Joseph came home, they brought him the present which was in their hand into the house, and bowed themselves to him to the earth.
+And he asked them of their welfare, and said, Is your father well, the old man of whom ye spake? Is he yet alive?
+And they answered, Thy servant our father is in good health, he is yet alive. And they bowed down their heads, and made obeisance.
+And he lifted up his eyes, and saw his brother Benjamin, his mother's son, and said, Is this your younger brother, of whom ye spake unto me? And he said, God be gracious unto thee, my son.
+And Joseph made haste; for his bowels did yearn upon his brother: and he sought where to weep; and he entered into his chamber, and wept there.
+And he washed his face, and went out, and refrained himself, and said, Set on bread.
+And they set on for him by himself, and for them by themselves, and for the Egyptians, which did eat with him, by themselves: because the Egyptians might not eat bread with the Hebrews; for that is an abomination unto the Egyptians.
+And they sat before him, the firstborn according to his birthright, and the youngest according to his youth: and the men marvelled one at another.
+And he took and sent messes unto them from before him: but Benjamin's mess was five times so much as any of theirs. And they drank, and were merry with him.
+
+44.
+And he commanded the steward of his house, saying, Fill the men's sacks with food, as much as they can carry, and put every man's money in his sack's mouth.
+And put my cup, the silver cup, in the sack's mouth of the youngest, and his corn money. And he did according to the word that Joseph had spoken.
+As soon as the morning was light, the men were sent away, they and their asses.
+And when they were gone out of the city, and not yet far off, Joseph said unto his steward, Up, follow after the men; and when thou dost overtake them, say unto them, Wherefore have ye rewarded evil for good?
+Is not this it in which my lord drinketh, and whereby indeed he divineth? ye have done evil in so doing.
+And he overtook them, and he spake unto them these same words.
+And they said unto him, Wherefore saith my lord these words? God forbid that thy servants should do according to this thing:
+Behold, the money, which we found in our sacks' mouths, we brought again unto thee out of the land of Canaan: how then should we steal out of thy lord's house silver or gold?
+With whomsoever of thy servants it be found, both let him die, and we also will be my lord's bondmen.
+And he said, Now also let it be according unto your words; he with whom it is found shall be my servant; and ye shall be blameless.
+Then they speedily took down every man his sack to the ground, and opened every man his sack.
+And he searched, and began at the eldest, and left at the youngest: and the cup was found in Benjamin's sack.
+Then they rent their clothes, and laded every man his ass, and returned to the city.
+And Judah and his brethren came to Joseph's house; for he was yet there: and they fell before him on the ground.
+And Joseph said unto them, What deed is this that ye have done? wot ye not that such a man as I can certainly divine?
+And Judah said, What shall we say unto my lord? what shall we speak? or how shall we clear ourselves? God hath found out the iniquity of thy servants: behold, we are my lord's servants, both we, and he also with whom the cup is found.
+And he said, God forbid that I should do so: but the man in whose hand the cup is found, he shall be my servant; and as for you, get you up in peace unto your father.
+Then Judah came near unto him, and said, Oh my lord, let thy servant, I pray thee, speak a word in my lord's ears, and let not thine anger burn against thy servant: for thou art even as Pharaoh.
+My lord asked his servants, saying, Have ye a father, or a brother?
+And we said unto my lord, We have a father, an old man, and a child of his old age, a little one; and his brother is dead, and he alone is left of his mother, and his father loveth him.
+And thou saidst unto thy servants, Bring him down unto me, that I may set mine eyes upon him.
+And we said unto my lord, The lad cannot leave his father: for if he should leave his father, his father would die.
+And thou saidst unto thy servants, Except your youngest brother come down with you, ye shall see my face no more.
+And it came to pass when we came up unto thy servant my father, we told him the words of my lord.
+And our father said, Go again, and buy us a little food.
+And we said, We cannot go down: if our youngest brother be with us, then will we go down: for we may not see the man's face, except our youngest brother be with us.
+And thy servant my father said unto us, Ye know that my wife bare me two sons:
+And the one went out from me, and I said, Surely he is torn in pieces; and I saw him not since:
+And if ye take this also from me, and mischief befall him, ye shall bring down my gray hairs with sorrow to the grave.
+Now therefore when I come to thy servant my father, and the lad be not with us; seeing that his life is bound up in the lad's life;
+It shall come to pass, when he seeth that the lad is not with us, that he will die: and thy servants shall bring down the gray hairs of thy servant our father with sorrow to the grave.
+For thy servant became surety for the lad unto my father, saying, If I bring him not unto thee, then I shall bear the blame to my father for ever.
+Now therefore, I pray thee, let thy servant abide instead of the lad a bondman to my lord; and let the lad go up with his brethren.
+For how shall I go up to my father, and the lad be not with me? lest peradventure I see the evil that shall come on my father.
+
+45.
+Then Joseph could not refrain himself before all them that stood by him; and he cried, Cause every man to go out from me. And there stood no man with him, while Joseph made himself known unto his brethren.
+And he wept aloud: and the Egyptians and the house of Pharaoh heard.
+And Joseph said unto his brethren, I am Joseph; doth my father yet live? And his brethren could not answer him; for they were troubled at his presence.
+And Joseph said unto his brethren, Come near to me, I pray you. And they came near. And he said, I am Joseph your brother, whom ye sold into Egypt.
+Now therefore be not grieved, nor angry with yourselves, that ye sold me hither: for God did send me before you to preserve life.
+For these two years hath the famine been in the land: and yet there are five years, in the which there shall neither be earing nor harvest.
+And God sent me before you to preserve you a posterity in the earth, and to save your lives by a great deliverance.
+So now it was not you that sent me hither, but God: and he hath made me a father to Pharaoh, and lord of all his house, and a ruler throughout all the land of Egypt.
+Haste ye, and go up to my father, and say unto him, Thus saith thy son Joseph, God hath made me lord of all Egypt: come down unto me, tarry not:
+And thou shalt dwell in the land of Goshen, and thou shalt be near unto me, thou, and thy children, and thy children's children, and thy flocks, and thy herds, and all that thou hast:
+And there will I nourish thee; for yet there are five years of famine; lest thou, and thy household, and all that thou hast, come to poverty.
+And, behold, your eyes see, and the eyes of my brother Benjamin, that it is my mouth that speaketh unto you.
+And ye shall tell my father of all my glory in Egypt, and of all that ye have seen; and ye shall haste and bring down my father hither.
+And he fell upon his brother Benjamin's neck, and wept; and Benjamin wept upon his neck.
+Moreover he kissed all his brethren, and wept upon them: and after that his brethren talked with him.
+And the fame thereof was heard in Pharaoh's house, saying, Joseph's brethren are come: and it pleased Pharaoh well, and his servants.
+And Pharaoh said unto Joseph, Say unto thy brethren, This do ye; lade your beasts, and go, get you unto the land of Canaan;
+And take your father and your households, and come unto me: and I will give you the good of the land of Egypt, and ye shall eat the fat of the land.
+Now thou art commanded, this do ye; take you wagons out of the land of Egypt for your little ones, and for your wives, and bring your father, and come.
+Also regard not your stuff; for the good of all the land of Egypt is yours.
+And the children of Israel did so: and Joseph gave them wagons, according to the commandment of Pharaoh, and gave them provision for the way.
+To all of them he gave each man changes of raiment; but to Benjamin he gave three hundred pieces of silver, and five changes of raiment.
+And to his father he sent after this manner; ten asses laden with the good things of Egypt, and ten she asses laden with corn and bread and meat for his father by the way.
+So he sent his brethren away, and they departed: and he said unto them, See that ye fall not out by the way.
+And they went up out of Egypt, and came into the land of Canaan unto Jacob their father,
+And told him, saying, Joseph is yet alive, and he is governor over all the land of Egypt. And Jacob's heart fainted, for he believed them not.
+And they told him all the words of Joseph, which he had said unto them: and when he saw the wagons which Joseph had sent to carry him, the spirit of Jacob their father revived:
+And Israel said, It is enough; Joseph my son is yet alive: I will go and see him before I die.
+
+46.
+And Israel took his journey with all that he had, and came to Beer-sheba, and offered sacrifices unto the God of his father Isaac.
+And God spake unto Israel in the visions of the night, and said, Jacob, Jacob. And he said, Here am I.
+And he said, I am God, the God of thy father: fear not to go down into Egypt; for I will there make of thee a great nation:
+I will go down with thee into Egypt; and I will also surely bring thee up again: and Joseph shall put his hand upon thine eyes.
+And Jacob rose up from Beer-sheba: and the sons of Israel carried Jacob their father, and their little ones, and their wives, in the wagons which Pharaoh had sent to carry him.
+And they took their cattle, and their goods, which they had gotten in the land of Canaan, and came into Egypt, Jacob, and all his seed with him:
+His sons, and his sons' sons with him, his daughters, and his sons' daughters, and all his seed brought he with him into Egypt.
+And these are the names of the children of Israel, which came into Egypt, Jacob and his sons: Reuben, Jacob's firstborn.
+And the sons of Reuben; Hanoch, and Phallu, and Hezron, and Carmi.
+And the sons of Simeon; Jemuel, and Jamin, and Ohad, and Jachin, and Zohar, and Shaul the son of a Canaanitish woman.
+And the sons of Levi; Gershon, Kohath, and Merari.
+And the sons of Judah; Er, and Onan, and Shelah, and Pharez, and Zerah: but Er and Onan died in the land of Canaan. And the sons of Pharez were Hezron and Hamul.
+And the sons of Issachar; Tola, and Phuvah, and Job, and Shimron.
+And the sons of Zebulun; Sered, and Elon, and Jahleel.
+These be the sons of Leah, which she bare unto Jacob in Padan-aram, with his daughter Dinah: all the souls of his sons and his daughters were thirty and three.
+And the sons of Gad; Ziphion, and Haggi, Shuni, and Ezbon, Eri, and Arodi, and Areli.
+And the sons of Asher; Jimnah, and Ishuah, and Isui, and Beriah, and Serah their sister: and the sons of Beriah; Heber, and Malchiel.
+These are the sons of Zilpah, whom Laban gave to Leah his daughter, and these she bare unto Jacob, even sixteen souls.
+The sons of Rachel Jacob's wife; Joseph, and Benjamin.
+And unto Joseph in the land of Egypt were born Manasseh and Ephraim, which Asenath the daughter of Poti-pherah priest of On bare unto him.
+And the sons of Benjamin were Belah, and Becher, and Ashbel, Gera, and Naaman, Ehi, and Rosh, Muppim, and Huppim, and Ard.
+These are the sons of Rachel, which were born to Jacob: all the souls were fourteen.
+And the sons of Dan; Hushim.
+And the sons of Naphtali; Jahzeel, and Guni, and Jezer, and Shillem.
+These are the sons of Bilhah, which Laban gave unto Rachel his daughter, and she bare these unto Jacob: all the souls were seven.
+All the souls that came with Jacob into Egypt, which came out of his loins, besides Jacob's sons' wives, all the souls were threescore and six;
+And the sons of Joseph, which were born him in Egypt, were two souls: all the souls of the house of Jacob, which came into Egypt, were threescore and ten.
+And he sent Judah before him unto Joseph, to direct his face unto Goshen; and they came into the land of Goshen.
+And Joseph made ready his chariot, and went up to meet Israel his father, to Goshen, and presented himself unto him; and he fell on his neck, and wept on his neck a good while.
+And Israel said unto Joseph, Now let me die, since I have seen thy face, because thou art yet alive.
+And Joseph said unto his brethren, and unto his father's house, I will go up, and shew Pharaoh, and say unto him, My brethren, and my father's house, which were in the land of Canaan, are come unto me;
+And the men are shepherds, for their trade hath been to feed cattle; and they have brought their flocks, and their herds, and all that they have.
+And it shall come to pass, when Pharaoh shall call you, and shall say, What is your occupation?
+That ye shall say, Thy servants' trade hath been about cattle from our youth even until now, both we, and also our fathers: that ye may dwell in the land of Goshen; for every shepherd is an abomination unto the Egyptians.
+
+47.
+Then Joseph came and told Pharaoh, and said, My father and my brethren, and their flocks, and their herds, and all that they have, are come out of the land of Canaan; and, behold, they are in the land of Goshen.
+And he took some of his brethren, even five men, and presented them unto Pharaoh.
+And Pharaoh said unto his brethren, What is your occupation? And they said unto Pharaoh, Thy servants are shepherds, both we, and also our fathers.
+They said moreover unto Pharaoh, For to sojourn in the land are we come; for thy servants have no pasture for their flocks; for the famine is sore in the land of Canaan: now therefore, we pray thee, let thy servants dwell in the land of Goshen.
+And Pharaoh spake unto Joseph, saying, Thy father and thy brethren are come unto thee:
+The land of Egypt is before thee; in the best of the land make thy father and brethren to dwell; in the land of Goshen let them dwell: and if thou knowest any men of activity among them, then make them rulers over my cattle.
+And Joseph brought in Jacob his father, and set him before Pharaoh: and Jacob blessed Pharaoh.
+And Pharaoh said unto Jacob, How old art thou?
+And Jacob said unto Pharaoh, The days of the years of my pilgrimage are an hundred and thirty years: few and evil have the days of the years of my life been, and have not attained unto the days of the years of the life of my fathers in the days of their pilgrimage.
+And Jacob blessed Pharaoh, and went out from before Pharaoh.
+And Joseph placed his father and his brethren, and gave them a possession in the land of Egypt, in the best of the land, in the land of Rameses, as Pharaoh had commanded.
+And Joseph nourished his father, and his brethren, and all his father's household, with bread, according to their families.
+And there was no bread in all the land; for the famine was very sore, so that the land of Egypt and all the land of Canaan fainted by reason of the famine.
+And Joseph gathered up all the money that was found in the land of Egypt, and in the land of Canaan, for the corn which they bought: and Joseph brought the money into Pharaoh's house.
+And when money failed in the land of Egypt, and in the land of Canaan, all the Egyptians came unto Joseph, and said, Give us bread: for why should we die in thy presence? for the money faileth.
+And Joseph said, Give your cattle; and I will give you for your cattle, if money fail.
+And they brought their cattle unto Joseph: and Joseph gave them bread in exchange for horses, and for the flocks, and for the cattle of the herds, and for the asses: and he fed them with bread for all their cattle for that year.
+When that year was ended, they came unto him the second year, and said unto him, We will not hide it from my lord, how that our money is spent; my lord also hath our herds of cattle; there is not ought left in the sight of my lord, but our bodies, and our lands:
+Wherefore shall we die before thine eyes, both we and our land? buy us and our land for bread, and we and our land will be servants unto Pharaoh: and give us seed, that we may live, and not die, that the land be not desolate.
+And Joseph bought all the land of Egypt for Pharaoh; for the Egyptians sold every man his field, because the famine prevailed over them: so the land became Pharaoh's.
+And as for the people, he removed them to cities from one end of the borders of Egypt even to the other end thereof.
+Only the land of the priests bought he not; for the priests had a portion assigned them of Pharaoh, and did eat their portion which Pharaoh gave them: wherefore they sold not their lands.
+Then Joseph said unto the people, Behold, I have bought you this day and your land for Pharaoh: lo, here is seed for you, and ye shall sow the land.
+And it shall come to pass in the increase, that ye shall give the fifth part unto Pharaoh, and four parts shall be your own, for seed of the field, and for your food, and for them of your households, and for food for your little ones.
+And they said, Thou hast saved our lives: let us find grace in the sight of my lord, and we will be Pharaoh's servants.
+And Joseph made it a law over the land of Egypt unto this day, that Pharaoh should have the fifth part; except the land of the priests only, which became not Pharaoh's.
+And Israel dwelt in the land of Egypt, in the country of Goshen; and they had possessions therein, and grew, and multiplied exceedingly.
+And Jacob lived in the land of Egypt seventeen years: so the whole age of Jacob was an hundred forty and seven years.
+And the time drew nigh that Israel must die: and he called his son Joseph, and said unto him, If now I have found grace in thy sight, put, I pray thee, thy hand under my thigh, and deal kindly and truly with me; bury me not, I pray thee, in Egypt:
+But I will lie with my fathers, and thou shalt carry me out of Egypt, and bury me in their buryingplace. And he said, I will do as thou hast said.
+And he said, Swear unto me. And he sware unto him. And Israel bowed himself upon the bed's head.
+
+48.
+And it came to pass after these things, that one told Joseph, Behold, thy father is sick: and he took with him his two sons, Manasseh and Ephraim.
+And one told Jacob, and said, Behold, thy son Joseph cometh unto thee: and Israel strengthened himself, and sat upon the bed.
+And Jacob said unto Joseph, God Almighty appeared unto me at Luz in the land of Canaan, and blessed me,
+And said unto me, Behold, I will make thee fruitful, and multiply thee, and I will make of thee a multitude of people; and will give this land to thy seed after thee for an everlasting possession.
+And now thy two sons, Ephraim and Manasseh, which were born unto thee in the land of Egypt before I came unto thee into Egypt, are mine; as Reuben and Simeon, they shall be mine.
+And thy issue, which thou begettest after them, shall be thine, and shall be called after the name of their brethren in their inheritance.
+And as for me, when I came from Padan, Rachel died by me in the land of Canaan in the way, when yet there was but a little way to come unto Ephrath: and I buried her there in the way of Ephrath; the same is Bethlehem.
+And Israel beheld Joseph's sons, and said, Who are these?
+And Joseph said unto his father, They are my sons, whom God hath given me in this place. And he said, Bring them, I pray thee, unto me, and I will bless them.
+Now the eyes of Israel were dim for age, so that he could not see. And he brought them near unto him; and he kissed them, and embraced them.
+And Israel said unto Joseph, I had not thought to see thy face: and, lo, God hath shewed me also thy seed.
+And Joseph brought them out from between his knees, and he bowed himself with his face to the earth.
+And Joseph took them both, Ephraim in his right hand toward Israel's left hand, and Manasseh in his left hand toward Israel's right hand, and brought them near unto him.
+And Israel stretched out his right hand, and laid it upon Ephraim's head, who was the younger, and his left hand upon Manasseh's head, guiding his hands wittingly; for Manasseh was the firstborn.
+And he blessed Joseph, and said, God, before whom my fathers Abraham and Isaac did walk, the God which fed me all my life long unto this day,
+The Angel which redeemed me from all evil, bless the lads; and let my name be named on them, and the name of my fathers Abraham and Isaac; and let them grow into a multitude in the midst of the earth.
+And when Joseph saw that his father laid his right hand upon the head of Ephraim, it displeased him: and he held up his father's hand, to remove it from Ephraim's head unto Manasseh's head.
+And Joseph said unto his father, Not so, my father: for this is the firstborn; put thy right hand upon his head.
+And his father refused, and said, I know it, my son, I know it: he also shall become a people, and he also shall be great: but truly his younger brother shall be greater than he, and his seed shall become a multitude of nations.
+And he blessed them that day, saying, In thee shall Israel bless, saying, God make thee as Ephraim and as Manasseh: and he set Ephraim before Manasseh.
+And Israel said unto Joseph, Behold, I die: but God shall be with you, and bring you again unto the land of your fathers.
+Moreover I have given to thee one portion above thy brethren, which I took out of the hand of the Amorite with my sword and with my bow.
+
+49.
+And Jacob called unto his sons, and said, Gather yourselves together, that I may tell you that which shall befall you in the last days.
+Gather yourselves together, and hear, ye sons of Jacob; and hearken unto Israel your father.
+Reuben, thou art my firstborn, my might, and the beginning of my strength, the excellency of dignity, and the excellency of power:
+Unstable as water, thou shalt not excel; because thou wentest up to thy father's bed; then defiledst thou it: he went up to my couch.
+Simeon and Levi are brethren; instruments of cruelty are in their habitations.
+O my soul, come not thou into their secret; unto their assembly, mine honour, be not thou united: for in their anger they slew a man, and in their selfwill they digged down a wall.
+Cursed be their anger, for it was fierce; and their wrath, for it was cruel: I will divide them in Jacob, and scatter them in Israel.
+Judah, thou art he whom thy brethren shall praise: thy hand shall be in the neck of thine enemies; thy father's children shall bow down before thee.
+Judah is a lion's whelp: from the prey, my son, thou art gone up: he stooped down, he couched as a lion, and as an old lion; who shall rouse him up?
+The sceptre shall not depart from Judah, nor a lawgiver from between his feet, until Shiloh come; and unto him shall the gathering of the people be.
+Binding his foal unto the vine, and his ass's colt unto the choice vine; he washed his garments in wine, and his clothes in the blood of grapes:
+His eyes shall be red with wine, and his teeth white with milk.
+Zebulun shall dwell at the haven of the sea; and he shall be for an haven of ships; and his border shall be unto Zidon.
+Issachar is a strong ass couching down between two burdens:
+And he saw that rest was good, and the land that it was pleasant; and bowed his shoulder to bear, and became a servant unto tribute.
+Dan shall judge his people, as one of the tribes of Israel.
+Dan shall be a serpent by the way, an adder in the path, that biteth the horse heels, so that his rider shall fall backward.
+I have waited for thy salvation, O LORD.
+Gad, a troop shall overcome him: but he shall overcome at the last.
+Out of Asher his bread shall be fat, and he shall yield royal dainties.
+Naphtali is a hind let loose: he giveth goodly words.
+Joseph is a fruitful bough, even a fruitful bough by a well; whose branches run over the wall:
+The archers have sorely grieved him, and shot at him, and hated him:
+But his bow abode in strength, and the arms of his hands were made strong by the hands of the mighty God of Jacob; (from thence is the shepherd, the stone of Israel:)
+Even by the God of thy father, who shall help thee; and by the Almighty, who shall bless thee with blessings of heaven above, blessings of the deep that lieth under, blessings of the breasts, and of the womb:
+The blessings of thy father have prevailed above the blessings of my progenitors unto the utmost bound of the everlasting hills: they shall be on the head of Joseph, and on the crown of the head of him that was separate from his brethren.
+Benjamin shall ravin as a wolf: in the morning he shall devour the prey, and at night he shall divide the spoil.
+All these are the twelve tribes of Israel: and this is it that their father spake unto them, and blessed them; every one according to his blessing he blessed them.
+And he charged them, and said unto them, I am to be gathered unto my people: bury me with my fathers in the cave that is in the field of Ephron the Hittite,
+In the cave that is in the field of Machpelah, which is before Mamre, in the land of Canaan, which Abraham bought with the field of Ephron the Hittite for a possession of a buryingplace.
+There they buried Abraham and Sarah his wife; there they buried Isaac and Rebekah his wife; and there I buried Leah.
+The purchase of the field and of the cave that is therein was from the children of Heth.
+And when Jacob had made an end of commanding his sons, he gathered up his feet into the bed, and yielded up the ghost, and was gathered unto his people.
+
+50.
+And Joseph fell upon his father's face, and wept upon him, and kissed him.
+And Joseph commanded his servants the physicians to embalm his father: and the physicians embalmed Israel.
+And forty days were fulfilled for him; for so are fulfilled the days of those which are embalmed: and the Egyptians mourned for him threescore and ten days.
+And when the days of his mourning were past, Joseph spake unto the house of Pharaoh, saying, If now I have found grace in your eyes, speak, I pray you, in the ears of Pharaoh, saying,
+My father made me swear, saying, Lo, I die: in my grave which I have digged for me in the land of Canaan, there shalt thou bury me. Now therefore let me go up, I pray thee, and bury my father, and I will come again.
+And Pharaoh said, Go up, and bury thy father, according as he made thee swear.
+And Joseph went up to bury his father: and with him went up all the servants of Pharaoh, the elders of his house, and all the elders of the land of Egypt,
+And all the house of Joseph, and his brethren, and his father's house: only their little ones, and their flocks, and their herds, they left in the land of Goshen.
+And there went up with him both chariots and horsemen: and it was a very great company.
+And they came to the threshingfloor of Atad, which is beyond Jordan, and there they mourned with a great and very sore lamentation: and he made a mourning for his father seven days.
+And when the inhabitants of the land, the Canaanites, saw the mourning in the floor of Atad, they said, This is a grievous mourning to the Egyptians: wherefore the name of it was called Abel-mizraim, which is beyond Jordan.
+And his sons did unto him according as he commanded them:
+For his sons carried him into the land of Canaan, and buried him in the cave of the field of Machpelah, which Abraham bought with the field for a possession of a buryingplace of Ephron the Hittite, before Mamre.
+And Joseph returned into Egypt, he, and his brethren, and all that went up with him to bury his father, after he had buried his father.
+And when Joseph's brethren saw that their father was dead, they said, Joseph will peradventure hate us, and will certainly requite us all the evil which we did unto him.
+And they sent a messenger unto Joseph, saying, Thy father did command before he died, saying,
+So shall ye say unto Joseph, Forgive, I pray thee now, the trespass of thy brethren, and their sin; for they did unto thee evil: and now, we pray thee, forgive the trespass of the servants of the God of thy father. And Joseph wept when they spake unto him.
+And his brethren also went and fell down before his face; and they said, Behold, we be thy servants.
+And Joseph said unto them, Fear not: for am I in the place of God?
+But as for you, ye thought evil against me; but God meant it unto good, to bring to pass, as it is this day, to save much people alive.
+Now therefore fear ye not: I will nourish you, and your little ones. And he comforted them, and spake kindly unto them.
+And Joseph dwelt in Egypt, he, and his father's house: and Joseph lived an hundred and ten years.
+And Joseph saw Ephraim's children of the third generation: the children also of Machir the son Manasseh were brought up upon Joseph's knees.
+And Joseph said unto his brethren, I die: and God will surely visit you, and bring you out of this land unto the land which he sware to Abraham, to Isaac, and to Jacob.
+And Joseph took an oath of the children of Israel, saying, God will surely visit you, and ye shall carry up my bones from hence.
+So Joseph died, being an hundred and ten years old: and they embalmed him, and he was put in a coffin in Egypt.
+''';
+
+/// From: https://en.wiktionary.org/wiki/Appendix:English_words_with_diacritics
+/// which no longer exists.
+/// Text is available under the [Creative Commons Attribution-ShareAlike License](https://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License).
+const diacretics = '''
+à bas, à la, à la carte, à la mode, à gogo, à propos, abacá, abaká,
+abbé, açaí, adiós, agèd, agrément, aikidō, Åland, ampère, Ancien
+Régime, André, ångström, animé (the oleo-resin), animēshon (usually
+anime), áo dài, aperçu, apéritif, appliqué, après-ski, arête, art
+décoratif, attaché, auto-da-fé bánh mì, barège, beau idéal, béchamel,
+belle époque, béguin, bentō, bête noire, bêtise, Beyoncé, Bézier curves,
+biały, Bichon Frisé, bíró, blasé, blessèd, bobèche, bodegón, boîte,
+Bokmål, bombé, Bön, bon appétit, Boötes, boutonnière, brassière,
+bric-à-brac, Brontë, bún café, cafetería, cafetière, caffè, caïque
+[-jee], calèche, canapé, cañón (usually canyon), cap-à-pie,
+Champs-Élysées, château, chargé d'affaires, cause célèbre, chacun à son
+goût, chaînés, chèvre, Chloë, cinéma, cinéma vérité, Citroën, cliché,
+cliché-verre, clientèle, comme ci comme ça, cloisonné, compère, consommé,
+communiqué, confrère, confronté, continuüm (rare), coöperate [-ion, -ive],
+coöpt, coördinate [-ed, -ing, -ion, -or, -ors], cortège, coup d'état, coup
+de grâce, coupé, coulée, crèche, crème [-brûlée, -caramel, -de cacao, -de
+la crème, -de menthe, -fraîche], Créole, crêpe [-paper, -Suzette], crétin
+[-ism], Creüsa, croûton, crudités, csárdás, Curaçao, cursèd (rare)
+Daimyō, daïs, dấu hỏi, débâcle, débris, début, décal [-comania],
+déclassé, décolletage, décolleté, décor, découpage, dégagé,
+dégustation, déjà vu, démarche, démodé, dénouement, dépôt, dérailleur,
+derrière, déshabillé, détente, diamanté, diddé, discothèque, divorcé,
+divorcée, dōjō, dōmoic acid, Doña, doppelgänger, Dvořák éclair, éclat,
+Éire, El Niño, élan, élite, Élysée, émigré, entrée, entrepôt,
+entrecôte, épaulette, épée, étouffée, étude, exposé façade, fête,
+faïence, fiancé, fiancée, filmjölk, fin de siècle, flambé, flèche, föhn,
+folie à deux, fouetté, frappé, Fräulein, frère, fricassée, Führer
+garçon, garçonnière, gâteau, Geiger–Müller counter, gemütlichkeit, genrō,
+genkō yōshi, Gewürztraminer, ginkyō (usually ginkgo), glacé, glögg,
+Götterdämmerung, Gruyère, gyōza habitué, háček, hajdúk, halászlé,
+hāngi, hapū, Hawaiʻi, hors d'œuvre, hôtel, humuhumunukunukuāpuaʻa ingénue,
+inrō jäger, jalapeño, jardinière, jūdō, jūjutsu kākā, kākāpō,
+kåldolmar, kamaʻāina, karōshi, kāwanatanga, kendō, kererū, kōan, kōhanga
+reo, kōji, kōkako, kōrero, króna (Icelandic with accent, Swedish without),
+kroužek, kūmara, kümmel, kyūdō lamé, lānai, ländler, langue d'oïl,
+Laocoön, La Niña, légionnaire, littérateur, lūʻau, lycée macédoine,
+macramé, mahātmā, maître d'hôtel, malagueña, Malmö, mañana, manège,
+manœuvre, manqué, Māori, maté, matériel, matinée, mélange, mêlée,
+ménage à trois, ménagerie, mésalliance, métier, Métis, México,
+minaudière, mise en scène, Monégasque, moiré, Montaño, Montréal naïf,
+naïve, naïveté, né, née, négligée, Neufchâtel, Nez Percé, Nō (usually
+Noh), Noël, noöne (rare), número uno (Spanish with accent, Italian without)
+objet trouvé, Öland, olé, ombré, omertà, oöcyte, oölogy (rare), opéra
+bouffe, opéra comique, opïum (rare), öre, øre, outré pączki, pāhoehoe,
+papier-mâché, páramo, passé, pâté, pāua, phở, pièce de résistance,
+pied-à-terre, plissé, piña colada, piñata, piñón, piraña (usually
+piranha), piqué, più, plié, précis, pōhutukawa, pölsa, preëminent [-ly]
+(rare), preëmpt [-ion, -ive] (rare), prélude, première, première danseuse,
+prêt-à-porter, protégé, protégée, purée Québec, Québécois,
+quinceañera ragoût, ragù, raison d'être, rāmen, rātā, recherché,
+réclame, reconnoître, reëlect [-ed, -ing] (rare), reënter [-ed, -ing]
+(rare), reëstablish [-ed, -ing] (rare), régime, rędzina (usually rendzina),
+résumé, residuüm (rare), retroussé, rincón, risqué, rôle, rivière, roman
+à clef, România, röntgen, rosé, roué sauté, sayōnara, séance, señor,
+señora, señorita, senryū, Shintō, shōgun, shōyu, Sinn Féin, Škoda,
+smörgåsbord, smörgåstårta, soigné, soirée, soufflé, soupçon, sūdoku,
+sumō, surströmming table d'hôte, takahē, télécommunication, tennō,
+tête-à-tête, Thaïs, tōfu, Tōkyō, tōtara, touché, toupée, tourtière
+über, Übermensch, ʻukulele vicuña, Việt Nam, vis-à-vis, voilà whekī
+Zaïre, Zoë, zoölogy, Zürich, zōri, złoty
+''';
diff --git a/pkgs/characters/test/src/unicode_grapheme_tests.dart b/pkgs/characters/test/src/unicode_grapheme_tests.dart
new file mode 100644
index 0000000..c4cec23
--- /dev/null
+++ b/pkgs/characters/test/src/unicode_grapheme_tests.dart
@@ -0,0 +1,6010 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Generated code. Do not edit.
+// Generated from:
+// - [https://unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakTest.txt](../../third_party/Unicode_Consortium/GraphemeBreakTest.txt)
+// - [https://unicode.org/Public/emoji/latest/emoji-test.txt](../../third_party/Unicode_Consortium/emoji_test.txt)
+// - [https://unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakProperty.txt](../../third_party/Unicode_Consortium/GraphemeBreakProperty.txt)
+// - [https://unicode.org/Public/UCD/latest/ucd/emoji/emoji-data.txt](../../third_party/Unicode_Consortium/emoji_data.txt)
+// - [https://unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt](../../third_party/Unicode_Consortium/DerivedCoreProperties.txt)
+// Licensed under the Unicode Inc. License Agreement
+// (https://www.unicode.org/license.txt, ../../third_party/third_party/Unicode_Consortium/UNICODE_LICENSE.txt)
+// ignore_for_file: lines_longer_than_80_chars
+// dart format off
+
+// Grapheme cluster tests.
+const List<List<String>> splitTests = [
+ [
+ ' ',
+ ' ',
+ ], // ÷ [0.2] SPACE (Other) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ ' \u0308',
+ ' ',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ ' ',
+ '\r',
+ ], // ÷ [0.2] SPACE (Other) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ ' \u0308',
+ '\r',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ ' ',
+ '\n',
+ ], // ÷ [0.2] SPACE (Other) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ ' \u0308',
+ '\n',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ ' ',
+ '\x01',
+ ], // ÷ [0.2] SPACE (Other) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ ' \u0308',
+ '\x01',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ ' \u200c',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ ' \u0308\u200c',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ ' ',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] SPACE (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ ' \u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ ' ',
+ '\u0600',
+ ], // ÷ [0.2] SPACE (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ ' \u0308',
+ '\u0600',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ ' \u0a03',
+ ], // ÷ [0.2] SPACE (Other) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ ' \u0308\u0a03',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ ' ',
+ '\u1100',
+ ], // ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ ' \u0308',
+ '\u1100',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ ' ',
+ '\u1160',
+ ], // ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ ' \u0308',
+ '\u1160',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ ' ',
+ '\u11a8',
+ ], // ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ ' \u0308',
+ '\u11a8',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ ' ',
+ '\uac00',
+ ], // ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ ' \u0308',
+ '\uac00',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ ' ',
+ '\uac01',
+ ], // ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ ' \u0308',
+ '\uac01',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ ' \u0903',
+ ], // ÷ [0.2] SPACE (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ ' \u0308\u0903',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ ' ',
+ '\u0904',
+ ], // ÷ [0.2] SPACE (Other) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ ' \u0308',
+ '\u0904',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ ' ',
+ '\u0d4e',
+ ], // ÷ [0.2] SPACE (Other) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ ' \u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ ' ',
+ '\u0915',
+ ], // ÷ [0.2] SPACE (Other) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ ' \u0308',
+ '\u0915',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ ' ',
+ '\u231a',
+ ], // ÷ [0.2] SPACE (Other) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ ' \u0308',
+ '\u231a',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ ' \u0300',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ ' \u0308\u0300',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ ' \u0900',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ ' \u0308\u0900',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ ' \u094d',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ ' \u0308\u094d',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ ' \u200d',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ ' \u0308\u200d',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ ' ',
+ '\u0378',
+ ], // ÷ [0.2] SPACE (Other) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ ' \u0308',
+ '\u0378',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\r',
+ ' ',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] SPACE (Other) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ ' ',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\r',
+ '\r',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\r',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\r\n',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) × [3.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\n',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\r',
+ '\x01',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\x01',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\r',
+ '\u200c',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\r',
+ '\u0308\u200c',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\r',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\r',
+ '\u0600',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\u0600',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\r',
+ '\u0a03',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\r',
+ '\u0308\u0a03',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\r',
+ '\u1100',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\u1100',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\r',
+ '\u1160',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\u1160',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\r',
+ '\u11a8',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\r',
+ '\uac00',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\uac00',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\r',
+ '\uac01',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\uac01',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\r',
+ '\u0903',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\r',
+ '\u0308\u0903',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\r',
+ '\u0904',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\u0904',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\r',
+ '\u0d4e',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\r',
+ '\u0915',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\u0915',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\r',
+ '\u231a',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\u231a',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\r',
+ '\u0300',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\r',
+ '\u0308\u0300',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\r',
+ '\u0900',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\r',
+ '\u0308\u0900',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\r',
+ '\u094d',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\r',
+ '\u0308\u094d',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\r',
+ '\u200d',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\r',
+ '\u0308\u200d',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\r',
+ '\u0378',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\r',
+ '\u0308',
+ '\u0378',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\n',
+ ' ',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] SPACE (Other) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ ' ',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\n',
+ '\r',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\r',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\n',
+ '\n',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\n',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\n',
+ '\x01',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\x01',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\n',
+ '\u200c',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\n',
+ '\u0308\u200c',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\n',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\n',
+ '\u0600',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\u0600',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\n',
+ '\u0a03',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\n',
+ '\u0308\u0a03',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\n',
+ '\u1100',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\u1100',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\n',
+ '\u1160',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\u1160',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\n',
+ '\u11a8',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\n',
+ '\uac00',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\uac00',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\n',
+ '\uac01',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\uac01',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\n',
+ '\u0903',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\n',
+ '\u0308\u0903',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\n',
+ '\u0904',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\u0904',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\n',
+ '\u0d4e',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\n',
+ '\u0915',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\u0915',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\n',
+ '\u231a',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\u231a',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\n',
+ '\u0300',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\n',
+ '\u0308\u0300',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\n',
+ '\u0900',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\n',
+ '\u0308\u0900',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\n',
+ '\u094d',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\n',
+ '\u0308\u094d',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\n',
+ '\u200d',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\n',
+ '\u0308\u200d',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\n',
+ '\u0378',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\n',
+ '\u0308',
+ '\u0378',
+ ], // ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\x01',
+ ' ',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] SPACE (Other) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ ' ',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\x01',
+ '\r',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\r',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\x01',
+ '\n',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\n',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\x01',
+ '\x01',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\x01',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\x01',
+ '\u200c',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308\u200c',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\x01',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\x01',
+ '\u0600',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\u0600',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\x01',
+ '\u0a03',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308\u0a03',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\x01',
+ '\u1100',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\u1100',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\x01',
+ '\u1160',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\u1160',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\x01',
+ '\u11a8',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\x01',
+ '\uac00',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\uac00',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\x01',
+ '\uac01',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\uac01',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\x01',
+ '\u0903',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308\u0903',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\x01',
+ '\u0904',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\u0904',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\x01',
+ '\u0d4e',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\x01',
+ '\u0915',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\u0915',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\x01',
+ '\u231a',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\u231a',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\x01',
+ '\u0300',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308\u0300',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\x01',
+ '\u0900',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308\u0900',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\x01',
+ '\u094d',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308\u094d',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\x01',
+ '\u200d',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308\u200d',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\x01',
+ '\u0378',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\x01',
+ '\u0308',
+ '\u0378',
+ ], // ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u200c',
+ ' ',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ ' ',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u200c',
+ '\r',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\r',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u200c',
+ '\n',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\n',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u200c',
+ '\x01',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\x01',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u200c\u200c',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u200c\u0308\u200c',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u200c',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u200c',
+ '\u0600',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\u0600',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u200c\u0a03',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u200c\u0308\u0a03',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u200c',
+ '\u1100',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\u1100',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u200c',
+ '\u1160',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\u1160',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u200c',
+ '\u11a8',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u200c',
+ '\uac00',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\uac00',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u200c',
+ '\uac01',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\uac01',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u200c\u0903',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u200c\u0308\u0903',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u200c',
+ '\u0904',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\u0904',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u200c',
+ '\u0d4e',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u200c',
+ '\u0915',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\u0915',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u200c',
+ '\u231a',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\u231a',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u200c\u0300',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200c\u0308\u0300',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200c\u0900',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200c\u0308\u0900',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200c\u094d',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200c\u0308\u094d',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200c\u200d',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200c\u0308\u200d',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200c',
+ '\u0378',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u200c\u0308',
+ '\u0378',
+ ], // ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ ' ',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ ' ',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ '\r',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\r',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ '\n',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\n',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ '\x01',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\x01',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u{1f1e6}\u200c',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308\u200c',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u{1f1e6}\u{1f1e6}',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ '\u0600',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\u0600',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0a03',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308\u0a03',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ '\u1100',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\u1100',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ '\u1160',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\u1160',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ '\u11a8',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ '\uac00',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\uac00',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ '\uac01',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\uac01',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0903',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308\u0903',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ '\u0904',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\u0904',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ '\u0d4e',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ '\u0915',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\u0915',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ '\u231a',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\u231a',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0300',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308\u0300',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0900',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308\u0900',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u{1f1e6}\u094d',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308\u094d',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u{1f1e6}\u200d',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308\u200d',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u{1f1e6}',
+ '\u0378',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u{1f1e6}\u0308',
+ '\u0378',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0600 ',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] SPACE (Other) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ ' ',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0600',
+ '\r',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\r',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0600',
+ '\n',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\n',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0600',
+ '\x01',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\x01',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0600\u200c',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0600\u0308\u200c',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0600\u{1f1e6}',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0600\u0600',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\u0600',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0600\u0a03',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0600\u0308\u0a03',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0600\u1100',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\u1100',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0600\u1160',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\u1160',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0600\u11a8',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0600\uac00',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\uac00',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0600\uac01',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\uac01',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0600\u0903',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0600\u0308\u0903',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0600\u0904',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\u0904',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0600\u0d4e',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0600\u0915',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\u0915',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0600\u231a',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\u231a',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0600\u0300',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0600\u0308\u0300',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0600\u0900',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0600\u0308\u0900',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0600\u094d',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0600\u0308\u094d',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0600\u200d',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0600\u0308\u200d',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0600\u0378',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0600\u0308',
+ '\u0378',
+ ], // ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0a03',
+ ' ',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ ' ',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0a03',
+ '\r',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\r',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0a03',
+ '\n',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\n',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0a03',
+ '\x01',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\x01',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0a03\u200c',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0a03\u0308\u200c',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0a03',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0a03',
+ '\u0600',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\u0600',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0a03\u0a03',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0a03\u0308\u0a03',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0a03',
+ '\u1100',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\u1100',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0a03',
+ '\u1160',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\u1160',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0a03',
+ '\u11a8',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0a03',
+ '\uac00',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\uac00',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0a03',
+ '\uac01',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\uac01',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0a03\u0903',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0a03\u0308\u0903',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0a03',
+ '\u0904',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\u0904',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0a03',
+ '\u0d4e',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0a03',
+ '\u0915',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\u0915',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0a03',
+ '\u231a',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\u231a',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0a03\u0300',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0a03\u0308\u0300',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0a03\u0900',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0a03\u0308\u0900',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0a03\u094d',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0a03\u0308\u094d',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0a03\u200d',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0a03\u0308\u200d',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0a03',
+ '\u0378',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0a03\u0308',
+ '\u0378',
+ ], // ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u1100',
+ ' ',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ ' ',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u1100',
+ '\r',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\r',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u1100',
+ '\n',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\n',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u1100',
+ '\x01',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\x01',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u1100\u200c',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u1100\u0308\u200c',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u1100',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u1100',
+ '\u0600',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\u0600',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u1100\u0a03',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u1100\u0308\u0a03',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u1100\u1100',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\u1100',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u1100\u1160',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\u1160',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u1100',
+ '\u11a8',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u1100\uac00',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\uac00',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u1100\uac01',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\uac01',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u1100\u0903',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u1100\u0308\u0903',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u1100',
+ '\u0904',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\u0904',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u1100',
+ '\u0d4e',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u1100',
+ '\u0915',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\u0915',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u1100',
+ '\u231a',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\u231a',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u1100\u0300',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1100\u0308\u0300',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1100\u0900',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1100\u0308\u0900',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1100\u094d',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1100\u0308\u094d',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1100\u200d',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1100\u0308\u200d',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1100',
+ '\u0378',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u1100\u0308',
+ '\u0378',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u1160',
+ ' ',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ ' ',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u1160',
+ '\r',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\r',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u1160',
+ '\n',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\n',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u1160',
+ '\x01',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\x01',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u1160\u200c',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u1160\u0308\u200c',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u1160',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u1160',
+ '\u0600',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\u0600',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u1160\u0a03',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u1160\u0308\u0a03',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u1160',
+ '\u1100',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\u1100',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u1160\u1160',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\u1160',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u1160\u11a8',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u1160',
+ '\uac00',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\uac00',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u1160',
+ '\uac01',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\uac01',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u1160\u0903',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u1160\u0308\u0903',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u1160',
+ '\u0904',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\u0904',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u1160',
+ '\u0d4e',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u1160',
+ '\u0915',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\u0915',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u1160',
+ '\u231a',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\u231a',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u1160\u0300',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1160\u0308\u0300',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1160\u0900',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1160\u0308\u0900',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1160\u094d',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1160\u0308\u094d',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1160\u200d',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1160\u0308\u200d',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u1160',
+ '\u0378',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u1160\u0308',
+ '\u0378',
+ ], // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u11a8',
+ ' ',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ ' ',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u11a8',
+ '\r',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\r',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u11a8',
+ '\n',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\n',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u11a8',
+ '\x01',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\x01',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u11a8\u200c',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u11a8\u0308\u200c',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u11a8',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u11a8',
+ '\u0600',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\u0600',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u11a8\u0a03',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u11a8\u0308\u0a03',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u11a8',
+ '\u1100',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\u1100',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u11a8',
+ '\u1160',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\u1160',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u11a8\u11a8',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u11a8',
+ '\uac00',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\uac00',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u11a8',
+ '\uac01',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\uac01',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u11a8\u0903',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u11a8\u0308\u0903',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u11a8',
+ '\u0904',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\u0904',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u11a8',
+ '\u0d4e',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u11a8',
+ '\u0915',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\u0915',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u11a8',
+ '\u231a',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\u231a',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u11a8\u0300',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u11a8\u0308\u0300',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u11a8\u0900',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u11a8\u0308\u0900',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u11a8\u094d',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u11a8\u0308\u094d',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u11a8\u200d',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u11a8\u0308\u200d',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u11a8',
+ '\u0378',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u11a8\u0308',
+ '\u0378',
+ ], // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\uac00',
+ ' ',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ ' ',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\uac00',
+ '\r',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\r',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\uac00',
+ '\n',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\n',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\uac00',
+ '\x01',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\x01',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\uac00\u200c',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\uac00\u0308\u200c',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\uac00',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\uac00',
+ '\u0600',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\u0600',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\uac00\u0a03',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\uac00\u0308\u0a03',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\uac00',
+ '\u1100',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\u1100',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\uac00\u1160',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\u1160',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\uac00\u11a8',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\uac00',
+ '\uac00',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\uac00',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\uac00',
+ '\uac01',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\uac01',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\uac00\u0903',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\uac00\u0308\u0903',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\uac00',
+ '\u0904',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\u0904',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\uac00',
+ '\u0d4e',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\uac00',
+ '\u0915',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\u0915',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\uac00',
+ '\u231a',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\u231a',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\uac00\u0300',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac00\u0308\u0300',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac00\u0900',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac00\u0308\u0900',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac00\u094d',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac00\u0308\u094d',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac00\u200d',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac00\u0308\u200d',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac00',
+ '\u0378',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\uac00\u0308',
+ '\u0378',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\uac01',
+ ' ',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ ' ',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\uac01',
+ '\r',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\r',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\uac01',
+ '\n',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\n',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\uac01',
+ '\x01',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\x01',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\uac01\u200c',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\uac01\u0308\u200c',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\uac01',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\uac01',
+ '\u0600',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\u0600',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\uac01\u0a03',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\uac01\u0308\u0a03',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\uac01',
+ '\u1100',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\u1100',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\uac01',
+ '\u1160',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\u1160',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\uac01\u11a8',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\uac01',
+ '\uac00',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\uac00',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\uac01',
+ '\uac01',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\uac01',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\uac01\u0903',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\uac01\u0308\u0903',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\uac01',
+ '\u0904',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\u0904',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\uac01',
+ '\u0d4e',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\uac01',
+ '\u0915',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\u0915',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\uac01',
+ '\u231a',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\u231a',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\uac01\u0300',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac01\u0308\u0300',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac01\u0900',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac01\u0308\u0900',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac01\u094d',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac01\u0308\u094d',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac01\u200d',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac01\u0308\u200d',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\uac01',
+ '\u0378',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\uac01\u0308',
+ '\u0378',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0903',
+ ' ',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ ' ',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0903',
+ '\r',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\r',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0903',
+ '\n',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\n',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0903',
+ '\x01',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\x01',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0903\u200c',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0903\u0308\u200c',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0903',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0903',
+ '\u0600',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\u0600',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0903\u0a03',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0903\u0308\u0a03',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0903',
+ '\u1100',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\u1100',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0903',
+ '\u1160',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\u1160',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0903',
+ '\u11a8',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0903',
+ '\uac00',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\uac00',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0903',
+ '\uac01',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\uac01',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0903\u0903',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0903\u0308\u0903',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0903',
+ '\u0904',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\u0904',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0903',
+ '\u0d4e',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0903',
+ '\u0915',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\u0915',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0903',
+ '\u231a',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\u231a',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0903\u0300',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0903\u0308\u0300',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0903\u0900',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0903\u0308\u0900',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0903\u094d',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0903\u0308\u094d',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0903\u200d',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0903\u0308\u200d',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0903',
+ '\u0378',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0903\u0308',
+ '\u0378',
+ ], // ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0904',
+ ' ',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ ' ',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0904',
+ '\r',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\r',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0904',
+ '\n',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\n',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0904',
+ '\x01',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\x01',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0904\u200c',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0904\u0308\u200c',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0904',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0904',
+ '\u0600',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\u0600',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0904\u0a03',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0904\u0308\u0a03',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0904',
+ '\u1100',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\u1100',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0904',
+ '\u1160',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\u1160',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0904',
+ '\u11a8',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0904',
+ '\uac00',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\uac00',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0904',
+ '\uac01',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\uac01',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0904\u0903',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0904\u0308\u0903',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0904',
+ '\u0904',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\u0904',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0904',
+ '\u0d4e',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0904',
+ '\u0915',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\u0915',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0904',
+ '\u231a',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\u231a',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0904\u0300',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0904\u0308\u0300',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0904\u0900',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0904\u0308\u0900',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0904\u094d',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0904\u0308\u094d',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0904\u200d',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0904\u0308\u200d',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0904',
+ '\u0378',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0904\u0308',
+ '\u0378',
+ ], // ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0d4e ',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] SPACE (Other) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ ' ',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0d4e',
+ '\r',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\r',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0d4e',
+ '\n',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\n',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0d4e',
+ '\x01',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\x01',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0d4e\u200c',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0d4e\u0308\u200c',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0d4e\u{1f1e6}',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0d4e\u0600',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\u0600',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0d4e\u0a03',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0d4e\u0308\u0a03',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0d4e\u1100',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\u1100',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0d4e\u1160',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\u1160',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0d4e\u11a8',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0d4e\uac00',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\uac00',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0d4e\uac01',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\uac01',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0d4e\u0903',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0d4e\u0308\u0903',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0d4e\u0904',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\u0904',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0d4e\u0d4e',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0d4e\u0915',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\u0915',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0d4e\u231a',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\u231a',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0d4e\u0300',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0d4e\u0308\u0300',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0d4e\u0900',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0d4e\u0308\u0900',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0d4e\u094d',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0d4e\u0308\u094d',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0d4e\u200d',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0d4e\u0308\u200d',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0d4e\u0378',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0d4e\u0308',
+ '\u0378',
+ ], // ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0915',
+ ' ',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ ' ',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0915',
+ '\r',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\r',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0915',
+ '\n',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\n',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0915',
+ '\x01',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\x01',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0915\u200c',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0915\u0308\u200c',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0915',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0915',
+ '\u0600',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\u0600',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0915\u0a03',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0915\u0308\u0a03',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0915',
+ '\u1100',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\u1100',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0915',
+ '\u1160',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\u1160',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0915',
+ '\u11a8',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0915',
+ '\uac00',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\uac00',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0915',
+ '\uac01',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\uac01',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0915\u0903',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0915\u0308\u0903',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0915',
+ '\u0904',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\u0904',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0915',
+ '\u0d4e',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0915',
+ '\u0915',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\u0915',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0915',
+ '\u231a',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\u231a',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0915\u0300',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0915\u0308\u0300',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0915\u0900',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0915\u0308\u0900',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0915\u094d',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0915\u0308\u094d',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0915\u200d',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0915\u0308\u200d',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0915',
+ '\u0378',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0915\u0308',
+ '\u0378',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u231a',
+ ' ',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ ' ',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u231a',
+ '\r',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\r',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u231a',
+ '\n',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\n',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u231a',
+ '\x01',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\x01',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u231a\u200c',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u231a\u0308\u200c',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u231a',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u231a',
+ '\u0600',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\u0600',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u231a\u0a03',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u231a\u0308\u0a03',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u231a',
+ '\u1100',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\u1100',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u231a',
+ '\u1160',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\u1160',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u231a',
+ '\u11a8',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u231a',
+ '\uac00',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\uac00',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u231a',
+ '\uac01',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\uac01',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u231a\u0903',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u231a\u0308\u0903',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u231a',
+ '\u0904',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\u0904',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u231a',
+ '\u0d4e',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u231a',
+ '\u0915',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\u0915',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u231a',
+ '\u231a',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\u231a',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u231a\u0300',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u231a\u0308\u0300',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u231a\u0900',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u231a\u0308\u0900',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u231a\u094d',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u231a\u0308\u094d',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u231a\u200d',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u231a\u0308\u200d',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u231a',
+ '\u0378',
+ ], // ÷ [0.2] WATCH (ExtPict) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u231a\u0308',
+ '\u0378',
+ ], // ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0300',
+ ' ',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ ' ',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0300',
+ '\r',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\r',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0300',
+ '\n',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\n',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0300',
+ '\x01',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\x01',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0300\u200c',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0300\u0308\u200c',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0300',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0300',
+ '\u0600',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\u0600',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0300\u0a03',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0300\u0308\u0a03',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0300',
+ '\u1100',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\u1100',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0300',
+ '\u1160',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\u1160',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0300',
+ '\u11a8',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0300',
+ '\uac00',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\uac00',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0300',
+ '\uac01',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\uac01',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0300\u0903',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0300\u0308\u0903',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0300',
+ '\u0904',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\u0904',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0300',
+ '\u0d4e',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0300',
+ '\u0915',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\u0915',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0300',
+ '\u231a',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\u231a',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0300\u0300',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0300\u0308\u0300',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0300\u0900',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0300\u0308\u0900',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0300\u094d',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0300\u0308\u094d',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0300\u200d',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0300\u0308\u200d',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0300',
+ '\u0378',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0300\u0308',
+ '\u0378',
+ ], // ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0900',
+ ' ',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ ' ',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0900',
+ '\r',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\r',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0900',
+ '\n',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\n',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0900',
+ '\x01',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\x01',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0900\u200c',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0900\u0308\u200c',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0900',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0900',
+ '\u0600',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\u0600',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0900\u0a03',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0900\u0308\u0a03',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0900',
+ '\u1100',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\u1100',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0900',
+ '\u1160',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\u1160',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0900',
+ '\u11a8',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0900',
+ '\uac00',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\uac00',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0900',
+ '\uac01',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\uac01',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0900\u0903',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0900\u0308\u0903',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0900',
+ '\u0904',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\u0904',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0900',
+ '\u0d4e',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0900',
+ '\u0915',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\u0915',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0900',
+ '\u231a',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\u231a',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0900\u0300',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0900\u0308\u0300',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0900\u0900',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0900\u0308\u0900',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0900\u094d',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0900\u0308\u094d',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0900\u200d',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0900\u0308\u200d',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0900',
+ '\u0378',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0900\u0308',
+ '\u0378',
+ ], // ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u094d',
+ ' ',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ ' ',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u094d',
+ '\r',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\r',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u094d',
+ '\n',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\n',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u094d',
+ '\x01',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\x01',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u094d\u200c',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u094d\u0308\u200c',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u094d',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u094d',
+ '\u0600',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\u0600',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u094d\u0a03',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u094d\u0308\u0a03',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u094d',
+ '\u1100',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\u1100',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u094d',
+ '\u1160',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\u1160',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u094d',
+ '\u11a8',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u094d',
+ '\uac00',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\uac00',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u094d',
+ '\uac01',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\uac01',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u094d\u0903',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u094d\u0308\u0903',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u094d',
+ '\u0904',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\u0904',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u094d',
+ '\u0d4e',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u094d',
+ '\u0915',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\u0915',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u094d',
+ '\u231a',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\u231a',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u094d\u0300',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u094d\u0308\u0300',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u094d\u0900',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u094d\u0308\u0900',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u094d\u094d',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u094d\u0308\u094d',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u094d\u200d',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u094d\u0308\u200d',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u094d',
+ '\u0378',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u094d\u0308',
+ '\u0378',
+ ], // ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u200d',
+ ' ',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ ' ',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u200d',
+ '\r',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\r',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u200d',
+ '\n',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\n',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u200d',
+ '\x01',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\x01',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u200d\u200c',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u200d\u0308\u200c',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u200d',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u200d',
+ '\u0600',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\u0600',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u200d\u0a03',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u200d\u0308\u0a03',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u200d',
+ '\u1100',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\u1100',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u200d',
+ '\u1160',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\u1160',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u200d',
+ '\u11a8',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u200d',
+ '\uac00',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\uac00',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u200d',
+ '\uac01',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\uac01',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u200d\u0903',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u200d\u0308\u0903',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u200d',
+ '\u0904',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\u0904',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u200d',
+ '\u0d4e',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u200d',
+ '\u0915',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\u0915',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u200d',
+ '\u231a',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\u231a',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u200d\u0300',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200d\u0308\u0300',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200d\u0900',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200d\u0308\u0900',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200d\u094d',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200d\u0308\u094d',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200d\u200d',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200d\u0308\u200d',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u200d',
+ '\u0378',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u200d\u0308',
+ '\u0378',
+ ], // ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0378',
+ ' ',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ ' ',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u0378',
+ '\r',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\r',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+ [
+ '\u0378',
+ '\n',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\n',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+ [
+ '\u0378',
+ '\x01',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\x01',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+ [
+ '\u0378\u200c',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0378\u0308\u200c',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ [
+ '\u0378',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\u{1f1e6}',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ [
+ '\u0378',
+ '\u0600',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\u0600',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ [
+ '\u0378\u0a03',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0378\u0308\u0a03',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ [
+ '\u0378',
+ '\u1100',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\u1100',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u0378',
+ '\u1160',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\u1160',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ [
+ '\u0378',
+ '\u11a8',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\u11a8',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ [
+ '\u0378',
+ '\uac00',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\uac00',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ [
+ '\u0378',
+ '\uac01',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\uac01',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ [
+ '\u0378\u0903',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0378\u0308\u0903',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0378',
+ '\u0904',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\u0904',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0378',
+ '\u0d4e',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\u0d4e',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ [
+ '\u0378',
+ '\u0915',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\u0915',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0378',
+ '\u231a',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\u231a',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ [
+ '\u0378\u0300',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0378\u0308\u0300',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0378\u0900',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0378\u0308\u0900',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0378\u094d',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0378\u0308\u094d',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0378\u200d',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0378\u0308\u200d',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ '\u0378',
+ '\u0378',
+ ], // ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\u0378\u0308',
+ '\u0378',
+ ], // ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+ [
+ '\r\n',
+ 'a',
+ '\n',
+ '\u0308',
+ ], // ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) × [3.0] <LINE FEED (LF)> (LF) ÷ [4.0] LATIN SMALL LETTER A (Other) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ 'a\u0308',
+ ], // ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ ' \u200d',
+ '\u0646',
+ ], // ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC LETTER NOON (Other) ÷ [0.3]
+ [
+ '\u0646\u200d',
+ ' ',
+ ], // ÷ [0.2] ARABIC LETTER NOON (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ [
+ '\u1100\u1100',
+ ], // ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\uac00\u11a8',
+ '\u1100',
+ ], // ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\uac01\u11a8',
+ '\u1100',
+ ], // ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ [
+ '\u{1f1e6}\u{1f1e7}',
+ '\u{1f1e8}',
+ 'b',
+ ], // ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+ [
+ 'a',
+ '\u{1f1e6}\u{1f1e7}',
+ '\u{1f1e8}',
+ 'b',
+ ], // ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+ [
+ 'a',
+ '\u{1f1e6}\u{1f1e7}\u200d',
+ '\u{1f1e8}',
+ 'b',
+ ], // ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+ [
+ 'a',
+ '\u{1f1e6}\u200d',
+ '\u{1f1e7}\u{1f1e8}',
+ 'b',
+ ], // ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+ [
+ 'a',
+ '\u{1f1e6}\u{1f1e7}',
+ '\u{1f1e8}\u{1f1e9}',
+ 'b',
+ ], // ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER D (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+ [
+ 'a\u200d',
+ ], // ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ [
+ 'a\u0308',
+ 'b',
+ ], // ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+ [
+ 'a\u0903',
+ 'b',
+ ], // ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+ [
+ 'a',
+ '\u0600b',
+ ], // ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) × [9.2] LATIN SMALL LETTER B (Other) ÷ [0.3]
+ [
+ '\u{1f476}\u{1f3ff}',
+ '\u{1f476}',
+ ], // ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) ÷ [0.3]
+ [
+ 'a\u{1f3ff}',
+ '\u{1f476}',
+ ], // ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) ÷ [0.3]
+ [
+ 'a\u{1f3ff}',
+ '\u{1f476}\u200d\u{1f6d1}',
+ ], // ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3]
+ [
+ '\u{1f476}\u{1f3ff}\u0308\u200d\u{1f476}\u{1f3ff}',
+ ], // ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [0.3]
+ [
+ '\u{1f6d1}\u200d\u{1f6d1}',
+ ], // ÷ [0.2] OCTAGONAL SIGN (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3]
+ [
+ 'a\u200d',
+ '\u{1f6d1}',
+ ], // ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3]
+ [
+ '\u2701\u200d\u2701',
+ ], // ÷ [0.2] UPPER BLADE SCISSORS (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] UPPER BLADE SCISSORS (Other) ÷ [0.3]
+ [
+ 'a\u200d',
+ '\u2701',
+ ], // ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] UPPER BLADE SCISSORS (Other) ÷ [0.3]
+ [
+ '\u0915',
+ '\u0924',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0915\u094d\u0924',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0915\u094d\u094d\u0924',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0915\u094d\u200d\u0924',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0915\u093c\u200d\u094d\u0924',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0915\u093c\u094d\u200d\u0924',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0915\u094d\u0924\u094d\u092f',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER YA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0915\u094d',
+ 'a',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] LATIN SMALL LETTER A (Other) ÷ [0.3]
+ [
+ 'a\u094d',
+ '\u0924',
+ ], // ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '?\u094d',
+ '\u0924',
+ ], // ÷ [0.2] QUESTION MARK (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ [
+ '\u0915\u094d\u094d\u0924',
+ ], // ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+];
+// Emoji tests.
+const List<List<String>> emojis = [
+ [
+ '#',
+ ], // E0.0 [1] (#️) hash sign
+ [
+ '*',
+ ], // E0.0 [1] (*️) asterisk
+ [
+ '©',
+ ], // E0.6 [1] (©️) copyright
+ [
+ '®',
+ ], // E0.6 [1] (®️) registered
+ [
+ '\u203c',
+ ], // E0.6 [1] (‼️) double exclamation mark
+ [
+ '\u2049',
+ ], // E0.6 [1] (⁉️) exclamation question mark
+ [
+ '\u2122',
+ ], // E0.6 [1] (™️) trade mark
+ [
+ '\u2139',
+ ], // E0.6 [1] (ℹ️) information
+ [
+ '\u2328',
+ ], // E1.0 [1] (⌨️) keyboard
+ [
+ '\u23cf',
+ ], // E1.0 [1] (⏏️) eject button
+ [
+ '\u23ef',
+ ], // E1.0 [1] (⏯️) play or pause button
+ [
+ '\u23f0',
+ ], // E0.6 [1] (⏰) alarm clock
+ [
+ '\u23f3',
+ ], // E0.6 [1] (⏳) hourglass not done
+ [
+ '\u24c2',
+ ], // E0.6 [1] (Ⓜ️) circled M
+ [
+ '\u25b6',
+ ], // E0.6 [1] (▶️) play button
+ [
+ '\u25c0',
+ ], // E0.6 [1] (◀️) reverse button
+ [
+ '\u2604',
+ ], // E1.0 [1] (☄️) comet
+ [
+ '\u260e',
+ ], // E0.6 [1] (☎️) telephone
+ [
+ '\u2611',
+ ], // E0.6 [1] (☑️) check box with check
+ [
+ '\u2618',
+ ], // E1.0 [1] (☘️) shamrock
+ [
+ '\u261d',
+ ], // E0.6 [1] (☝️) index pointing up
+ [
+ '\u2620',
+ ], // E1.0 [1] (☠️) skull and crossbones
+ [
+ '\u2626',
+ ], // E1.0 [1] (☦️) orthodox cross
+ [
+ '\u262a',
+ ], // E0.7 [1] (☪️) star and crescent
+ [
+ '\u262e',
+ ], // E1.0 [1] (☮️) peace symbol
+ [
+ '\u262f',
+ ], // E0.7 [1] (☯️) yin yang
+ [
+ '\u263a',
+ ], // E0.6 [1] (☺️) smiling face
+ [
+ '\u2640',
+ ], // E4.0 [1] (♀️) female sign
+ [
+ '\u2642',
+ ], // E4.0 [1] (♂️) male sign
+ [
+ '\u265f',
+ ], // E11.0 [1] (♟️) chess pawn
+ [
+ '\u2660',
+ ], // E0.6 [1] (♠️) spade suit
+ [
+ '\u2663',
+ ], // E0.6 [1] (♣️) club suit
+ [
+ '\u2668',
+ ], // E0.6 [1] (♨️) hot springs
+ [
+ '\u267b',
+ ], // E0.6 [1] (♻️) recycling symbol
+ [
+ '\u267e',
+ ], // E11.0 [1] (♾️) infinity
+ [
+ '\u267f',
+ ], // E0.6 [1] (♿) wheelchair symbol
+ [
+ '\u2692',
+ ], // E1.0 [1] (⚒️) hammer and pick
+ [
+ '\u2693',
+ ], // E0.6 [1] (⚓) anchor
+ [
+ '\u2694',
+ ], // E1.0 [1] (⚔️) crossed swords
+ [
+ '\u2695',
+ ], // E4.0 [1] (⚕️) medical symbol
+ [
+ '\u2699',
+ ], // E1.0 [1] (⚙️) gear
+ [
+ '\u26a7',
+ ], // E13.0 [1] (⚧️) transgender symbol
+ [
+ '\u26c8',
+ ], // E0.7 [1] (⛈️) cloud with lightning and rain
+ [
+ '\u26ce',
+ ], // E0.6 [1] (⛎) Ophiuchus
+ [
+ '\u26cf',
+ ], // E0.7 [1] (⛏️) pick
+ [
+ '\u26d1',
+ ], // E0.7 [1] (⛑️) rescue worker’s helmet
+ [
+ '\u26d3',
+ ], // E0.7 [1] (⛓️) chains
+ [
+ '\u26d4',
+ ], // E0.6 [1] (⛔) no entry
+ [
+ '\u26e9',
+ ], // E0.7 [1] (⛩️) shinto shrine
+ [
+ '\u26ea',
+ ], // E0.6 [1] (⛪) church
+ [
+ '\u26f4',
+ ], // E0.7 [1] (⛴️) ferry
+ [
+ '\u26f5',
+ ], // E0.6 [1] (⛵) sailboat
+ [
+ '\u26fa',
+ ], // E0.6 [1] (⛺) tent
+ [
+ '\u26fd',
+ ], // E0.6 [1] (⛽) fuel pump
+ [
+ '\u2702',
+ ], // E0.6 [1] (✂️) scissors
+ [
+ '\u2705',
+ ], // E0.6 [1] (✅) check mark button
+ [
+ '\u270d',
+ ], // E0.7 [1] (✍️) writing hand
+ [
+ '\u270f',
+ ], // E0.6 [1] (✏️) pencil
+ [
+ '\u2712',
+ ], // E0.6 [1] (✒️) black nib
+ [
+ '\u2714',
+ ], // E0.6 [1] (✔️) check mark
+ [
+ '\u2716',
+ ], // E0.6 [1] (✖️) multiply
+ [
+ '\u271d',
+ ], // E0.7 [1] (✝️) latin cross
+ [
+ '\u2721',
+ ], // E0.7 [1] (✡️) star of David
+ [
+ '\u2728',
+ ], // E0.6 [1] (✨) sparkles
+ [
+ '\u2744',
+ ], // E0.6 [1] (❄️) snowflake
+ [
+ '\u2747',
+ ], // E0.6 [1] (❇️) sparkle
+ [
+ '\u274c',
+ ], // E0.6 [1] (❌) cross mark
+ [
+ '\u274e',
+ ], // E0.6 [1] (❎) cross mark button
+ [
+ '\u2757',
+ ], // E0.6 [1] (❗) red exclamation mark
+ [
+ '\u2763',
+ ], // E1.0 [1] (❣️) heart exclamation
+ [
+ '\u2764',
+ ], // E0.6 [1] (❤️) red heart
+ [
+ '\u27a1',
+ ], // E0.6 [1] (➡️) right arrow
+ [
+ '\u27b0',
+ ], // E0.6 [1] (➰) curly loop
+ [
+ '\u27bf',
+ ], // E1.0 [1] (➿) double curly loop
+ [
+ '\u2b50',
+ ], // E0.6 [1] (⭐) star
+ [
+ '\u2b55',
+ ], // E0.6 [1] (⭕) hollow red circle
+ [
+ '\u3030',
+ ], // E0.6 [1] (〰️) wavy dash
+ [
+ '\u303d',
+ ], // E0.6 [1] (〽️) part alternation mark
+ [
+ '\u3297',
+ ], // E0.6 [1] (㊗️) Japanese “congratulations” button
+ [
+ '\u3299',
+ ], // E0.6 [1] (㊙️) Japanese “secret” button
+ [
+ '\u{1f004}',
+ ], // E0.6 [1] (🀄) mahjong red dragon
+ [
+ '\u{1f0cf}',
+ ], // E0.6 [1] (🃏) joker
+ [
+ '\u{1f18e}',
+ ], // E0.6 [1] (🆎) AB button (blood type)
+ [
+ '\u{1f21a}',
+ ], // E0.6 [1] (🈚) Japanese “free of charge” button
+ [
+ '\u{1f22f}',
+ ], // E0.6 [1] (🈯) Japanese “reserved” button
+ [
+ '\u{1f30f}',
+ ], // E0.6 [1] (🌏) globe showing Asia-Australia
+ [
+ '\u{1f310}',
+ ], // E1.0 [1] (🌐) globe with meridians
+ [
+ '\u{1f311}',
+ ], // E0.6 [1] (🌑) new moon
+ [
+ '\u{1f312}',
+ ], // E1.0 [1] (🌒) waxing crescent moon
+ [
+ '\u{1f319}',
+ ], // E0.6 [1] (🌙) crescent moon
+ [
+ '\u{1f31a}',
+ ], // E1.0 [1] (🌚) new moon face
+ [
+ '\u{1f31b}',
+ ], // E0.6 [1] (🌛) first quarter moon face
+ [
+ '\u{1f31c}',
+ ], // E0.7 [1] (🌜) last quarter moon face
+ [
+ '\u{1f321}',
+ ], // E0.7 [1] (🌡️) thermometer
+ [
+ '\u{1f336}',
+ ], // E0.7 [1] (🌶️) hot pepper
+ [
+ '\u{1f34b}',
+ ], // E1.0 [1] (🍋) lemon
+ [
+ '\u{1f350}',
+ ], // E1.0 [1] (🍐) pear
+ [
+ '\u{1f37c}',
+ ], // E1.0 [1] (🍼) baby bottle
+ [
+ '\u{1f37d}',
+ ], // E0.7 [1] (🍽️) fork and knife with plate
+ [
+ '\u{1f3c5}',
+ ], // E1.0 [1] (🏅) sports medal
+ [
+ '\u{1f3c6}',
+ ], // E0.6 [1] (🏆) trophy
+ [
+ '\u{1f3c7}',
+ ], // E1.0 [1] (🏇) horse racing
+ [
+ '\u{1f3c8}',
+ ], // E0.6 [1] (🏈) american football
+ [
+ '\u{1f3c9}',
+ ], // E1.0 [1] (🏉) rugby football
+ [
+ '\u{1f3ca}',
+ ], // E0.6 [1] (🏊) person swimming
+ [
+ '\u{1f3e4}',
+ ], // E1.0 [1] (🏤) post office
+ [
+ '\u{1f3f3}',
+ ], // E0.7 [1] (🏳️) white flag
+ [
+ '\u{1f3f4}',
+ ], // E1.0 [1] (🏴) black flag
+ [
+ '\u{1f3f5}',
+ ], // E0.7 [1] (🏵️) rosette
+ [
+ '\u{1f3f7}',
+ ], // E0.7 [1] (🏷️) label
+ [
+ '\u{1f408}',
+ ], // E0.7 [1] (🐈) cat
+ [
+ '\u{1f413}',
+ ], // E1.0 [1] (🐓) rooster
+ [
+ '\u{1f414}',
+ ], // E0.6 [1] (🐔) chicken
+ [
+ '\u{1f415}',
+ ], // E0.7 [1] (🐕) dog
+ [
+ '\u{1f416}',
+ ], // E1.0 [1] (🐖) pig
+ [
+ '\u{1f42a}',
+ ], // E1.0 [1] (🐪) camel
+ [
+ '\u{1f43f}',
+ ], // E0.7 [1] (🐿️) chipmunk
+ [
+ '\u{1f440}',
+ ], // E0.6 [1] (👀) eyes
+ [
+ '\u{1f441}',
+ ], // E0.7 [1] (👁️) eye
+ [
+ '\u{1f465}',
+ ], // E1.0 [1] (👥) busts in silhouette
+ [
+ '\u{1f4ad}',
+ ], // E1.0 [1] (💭) thought balloon
+ [
+ '\u{1f4ee}',
+ ], // E0.6 [1] (📮) postbox
+ [
+ '\u{1f4ef}',
+ ], // E1.0 [1] (📯) postal horn
+ [
+ '\u{1f4f5}',
+ ], // E1.0 [1] (📵) no mobile phones
+ [
+ '\u{1f4f8}',
+ ], // E1.0 [1] (📸) camera with flash
+ [
+ '\u{1f4fd}',
+ ], // E0.7 [1] (📽️) film projector
+ [
+ '\u{1f503}',
+ ], // E0.6 [1] (🔃) clockwise vertical arrows
+ [
+ '\u{1f508}',
+ ], // E0.7 [1] (🔈) speaker low volume
+ [
+ '\u{1f509}',
+ ], // E1.0 [1] (🔉) speaker medium volume
+ [
+ '\u{1f515}',
+ ], // E1.0 [1] (🔕) bell with slash
+ [
+ '\u{1f57a}',
+ ], // E3.0 [1] (🕺) man dancing
+ [
+ '\u{1f587}',
+ ], // E0.7 [1] (🖇️) linked paperclips
+ [
+ '\u{1f590}',
+ ], // E0.7 [1] (🖐️) hand with fingers splayed
+ [
+ '\u{1f5a4}',
+ ], // E3.0 [1] (🖤) black heart
+ [
+ '\u{1f5a5}',
+ ], // E0.7 [1] (🖥️) desktop computer
+ [
+ '\u{1f5a8}',
+ ], // E0.7 [1] (🖨️) printer
+ [
+ '\u{1f5bc}',
+ ], // E0.7 [1] (🖼️) framed picture
+ [
+ '\u{1f5e1}',
+ ], // E0.7 [1] (🗡️) dagger
+ [
+ '\u{1f5e3}',
+ ], // E0.7 [1] (🗣️) speaking head
+ [
+ '\u{1f5e8}',
+ ], // E2.0 [1] (🗨️) left speech bubble
+ [
+ '\u{1f5ef}',
+ ], // E0.7 [1] (🗯️) right anger bubble
+ [
+ '\u{1f5f3}',
+ ], // E0.7 [1] (🗳️) ballot box with ballot
+ [
+ '\u{1f5fa}',
+ ], // E0.7 [1] (🗺️) world map
+ [
+ '\u{1f600}',
+ ], // E1.0 [1] (😀) grinning face
+ [
+ '\u{1f60e}',
+ ], // E1.0 [1] (😎) smiling face with sunglasses
+ [
+ '\u{1f60f}',
+ ], // E0.6 [1] (😏) smirking face
+ [
+ '\u{1f610}',
+ ], // E0.7 [1] (😐) neutral face
+ [
+ '\u{1f611}',
+ ], // E1.0 [1] (😑) expressionless face
+ [
+ '\u{1f615}',
+ ], // E1.0 [1] (😕) confused face
+ [
+ '\u{1f616}',
+ ], // E0.6 [1] (😖) confounded face
+ [
+ '\u{1f617}',
+ ], // E1.0 [1] (😗) kissing face
+ [
+ '\u{1f618}',
+ ], // E0.6 [1] (😘) face blowing a kiss
+ [
+ '\u{1f619}',
+ ], // E1.0 [1] (😙) kissing face with smiling eyes
+ [
+ '\u{1f61a}',
+ ], // E0.6 [1] (😚) kissing face with closed eyes
+ [
+ '\u{1f61b}',
+ ], // E1.0 [1] (😛) face with tongue
+ [
+ '\u{1f61f}',
+ ], // E1.0 [1] (😟) worried face
+ [
+ '\u{1f62c}',
+ ], // E1.0 [1] (😬) grimacing face
+ [
+ '\u{1f62d}',
+ ], // E0.6 [1] (😭) loudly crying face
+ [
+ '\u{1f634}',
+ ], // E1.0 [1] (😴) sleeping face
+ [
+ '\u{1f635}',
+ ], // E0.6 [1] (😵) face with crossed-out eyes
+ [
+ '\u{1f636}',
+ ], // E1.0 [1] (😶) face without mouth
+ [
+ '\u{1f680}',
+ ], // E0.6 [1] (🚀) rocket
+ [
+ '\u{1f686}',
+ ], // E1.0 [1] (🚆) train
+ [
+ '\u{1f687}',
+ ], // E0.6 [1] (🚇) metro
+ [
+ '\u{1f688}',
+ ], // E1.0 [1] (🚈) light rail
+ [
+ '\u{1f689}',
+ ], // E0.6 [1] (🚉) station
+ [
+ '\u{1f68c}',
+ ], // E0.6 [1] (🚌) bus
+ [
+ '\u{1f68d}',
+ ], // E0.7 [1] (🚍) oncoming bus
+ [
+ '\u{1f68e}',
+ ], // E1.0 [1] (🚎) trolleybus
+ [
+ '\u{1f68f}',
+ ], // E0.6 [1] (🚏) bus stop
+ [
+ '\u{1f690}',
+ ], // E1.0 [1] (🚐) minibus
+ [
+ '\u{1f694}',
+ ], // E0.7 [1] (🚔) oncoming police car
+ [
+ '\u{1f695}',
+ ], // E0.6 [1] (🚕) taxi
+ [
+ '\u{1f696}',
+ ], // E1.0 [1] (🚖) oncoming taxi
+ [
+ '\u{1f697}',
+ ], // E0.6 [1] (🚗) automobile
+ [
+ '\u{1f698}',
+ ], // E0.7 [1] (🚘) oncoming automobile
+ [
+ '\u{1f6a2}',
+ ], // E0.6 [1] (🚢) ship
+ [
+ '\u{1f6a3}',
+ ], // E1.0 [1] (🚣) person rowing boat
+ [
+ '\u{1f6a6}',
+ ], // E1.0 [1] (🚦) vertical traffic light
+ [
+ '\u{1f6b2}',
+ ], // E0.6 [1] (🚲) bicycle
+ [
+ '\u{1f6b6}',
+ ], // E0.6 [1] (🚶) person walking
+ [
+ '\u{1f6bf}',
+ ], // E1.0 [1] (🚿) shower
+ [
+ '\u{1f6c0}',
+ ], // E0.6 [1] (🛀) person taking bath
+ [
+ '\u{1f6cb}',
+ ], // E0.7 [1] (🛋️) couch and lamp
+ [
+ '\u{1f6cc}',
+ ], // E1.0 [1] (🛌) person in bed
+ [
+ '\u{1f6d0}',
+ ], // E1.0 [1] (🛐) place of worship
+ [
+ '\u{1f6d5}',
+ ], // E12.0 [1] (🛕) hindu temple
+ [
+ '\u{1f6dc}',
+ ], // E15.0 [1] (🛜) wireless
+ [
+ '\u{1f6e9}',
+ ], // E0.7 [1] (🛩️) small airplane
+ [
+ '\u{1f6f0}',
+ ], // E0.7 [1] (🛰️) satellite
+ [
+ '\u{1f6f3}',
+ ], // E0.7 [1] (🛳️) passenger ship
+ [
+ '\u{1f6f9}',
+ ], // E11.0 [1] (🛹) skateboard
+ [
+ '\u{1f6fa}',
+ ], // E12.0 [1] (🛺) auto rickshaw
+ [
+ '\u{1f7f0}',
+ ], // E14.0 [1] (🟰) heavy equals sign
+ [
+ '\u{1f90c}',
+ ], // E13.0 [1] (🤌) pinched fingers
+ [
+ '\u{1f91f}',
+ ], // E5.0 [1] (🤟) love-you gesture
+ [
+ '\u{1f930}',
+ ], // E3.0 [1] (🤰) pregnant woman
+ [
+ '\u{1f93f}',
+ ], // E12.0 [1] (🤿) diving mask
+ [
+ '\u{1f94c}',
+ ], // E5.0 [1] (🥌) curling stone
+ [
+ '\u{1f971}',
+ ], // E12.0 [1] (🥱) yawning face
+ [
+ '\u{1f972}',
+ ], // E13.0 [1] (🥲) smiling face with tear
+ [
+ '\u{1f979}',
+ ], // E14.0 [1] (🥹) face holding back tears
+ [
+ '\u{1f97a}',
+ ], // E11.0 [1] (🥺) pleading face
+ [
+ '\u{1f97b}',
+ ], // E12.0 [1] (🥻) sari
+ [
+ '\u{1f9c0}',
+ ], // E1.0 [1] (🧀) cheese wedge
+ [
+ '\u{1f9cb}',
+ ], // E13.0 [1] (🧋) bubble tea
+ [
+ '\u{1f9cc}',
+ ], // E14.0 [1] (🧌) troll
+ [
+ '\u{1fa74}',
+ ], // E13.0 [1] (🩴) thong sandal
+ [
+ '\u{1fa89}',
+ ], // E16.0 [1] () harp
+ [
+ '\u{1fa8f}',
+ ], // E16.0 [1] () shovel
+ [
+ '\u{1fabe}',
+ ], // E16.0 [1] () leafless tree
+ [
+ '\u{1fabf}',
+ ], // E15.0 [1] (🪿) goose
+ [
+ '\u{1fac6}',
+ ], // E16.0 [1] () fingerprint
+ [
+ '\u{1fadc}',
+ ], // E16.0 [1] () root vegetable
+ [
+ '\u{1fadf}',
+ ], // E16.0 [1] () splatter
+ [
+ '\u{1fae8}',
+ ], // E15.0 [1] (🫨) shaking face
+ [
+ '\u{1fae9}',
+ ], // E16.0 [1] () face with bags under eyes
+ [
+ '\u23f0',
+ ], // E0.6 [1] (⏰) alarm clock
+ [
+ '\u23f3',
+ ], // E0.6 [1] (⏳) hourglass not done
+ [
+ '\u267f',
+ ], // E0.6 [1] (♿) wheelchair symbol
+ [
+ '\u2693',
+ ], // E0.6 [1] (⚓) anchor
+ [
+ '\u26a1',
+ ], // E0.6 [1] (⚡) high voltage
+ [
+ '\u26ce',
+ ], // E0.6 [1] (⛎) Ophiuchus
+ [
+ '\u26d4',
+ ], // E0.6 [1] (⛔) no entry
+ [
+ '\u26ea',
+ ], // E0.6 [1] (⛪) church
+ [
+ '\u26f5',
+ ], // E0.6 [1] (⛵) sailboat
+ [
+ '\u26fa',
+ ], // E0.6 [1] (⛺) tent
+ [
+ '\u26fd',
+ ], // E0.6 [1] (⛽) fuel pump
+ [
+ '\u2705',
+ ], // E0.6 [1] (✅) check mark button
+ [
+ '\u2728',
+ ], // E0.6 [1] (✨) sparkles
+ [
+ '\u274c',
+ ], // E0.6 [1] (❌) cross mark
+ [
+ '\u274e',
+ ], // E0.6 [1] (❎) cross mark button
+ [
+ '\u2757',
+ ], // E0.6 [1] (❗) red exclamation mark
+ [
+ '\u27b0',
+ ], // E0.6 [1] (➰) curly loop
+ [
+ '\u27bf',
+ ], // E1.0 [1] (➿) double curly loop
+ [
+ '\u2b50',
+ ], // E0.6 [1] (⭐) star
+ [
+ '\u2b55',
+ ], // E0.6 [1] (⭕) hollow red circle
+ [
+ '\u{1f004}',
+ ], // E0.6 [1] (🀄) mahjong red dragon
+ [
+ '\u{1f0cf}',
+ ], // E0.6 [1] (🃏) joker
+ [
+ '\u{1f18e}',
+ ], // E0.6 [1] (🆎) AB button (blood type)
+ [
+ '\u{1f201}',
+ ], // E0.6 [1] (🈁) Japanese “here” button
+ [
+ '\u{1f21a}',
+ ], // E0.6 [1] (🈚) Japanese “free of charge” button
+ [
+ '\u{1f22f}',
+ ], // E0.6 [1] (🈯) Japanese “reserved” button
+ [
+ '\u{1f30f}',
+ ], // E0.6 [1] (🌏) globe showing Asia-Australia
+ [
+ '\u{1f310}',
+ ], // E1.0 [1] (🌐) globe with meridians
+ [
+ '\u{1f311}',
+ ], // E0.6 [1] (🌑) new moon
+ [
+ '\u{1f312}',
+ ], // E1.0 [1] (🌒) waxing crescent moon
+ [
+ '\u{1f319}',
+ ], // E0.6 [1] (🌙) crescent moon
+ [
+ '\u{1f31a}',
+ ], // E1.0 [1] (🌚) new moon face
+ [
+ '\u{1f31b}',
+ ], // E0.6 [1] (🌛) first quarter moon face
+ [
+ '\u{1f31c}',
+ ], // E0.7 [1] (🌜) last quarter moon face
+ [
+ '\u{1f34b}',
+ ], // E1.0 [1] (🍋) lemon
+ [
+ '\u{1f350}',
+ ], // E1.0 [1] (🍐) pear
+ [
+ '\u{1f37c}',
+ ], // E1.0 [1] (🍼) baby bottle
+ [
+ '\u{1f3c5}',
+ ], // E1.0 [1] (🏅) sports medal
+ [
+ '\u{1f3c6}',
+ ], // E0.6 [1] (🏆) trophy
+ [
+ '\u{1f3c7}',
+ ], // E1.0 [1] (🏇) horse racing
+ [
+ '\u{1f3c8}',
+ ], // E0.6 [1] (🏈) american football
+ [
+ '\u{1f3c9}',
+ ], // E1.0 [1] (🏉) rugby football
+ [
+ '\u{1f3ca}',
+ ], // E0.6 [1] (🏊) person swimming
+ [
+ '\u{1f3e4}',
+ ], // E1.0 [1] (🏤) post office
+ [
+ '\u{1f3f4}',
+ ], // E1.0 [1] (🏴) black flag
+ [
+ '\u{1f408}',
+ ], // E0.7 [1] (🐈) cat
+ [
+ '\u{1f413}',
+ ], // E1.0 [1] (🐓) rooster
+ [
+ '\u{1f414}',
+ ], // E0.6 [1] (🐔) chicken
+ [
+ '\u{1f415}',
+ ], // E0.7 [1] (🐕) dog
+ [
+ '\u{1f416}',
+ ], // E1.0 [1] (🐖) pig
+ [
+ '\u{1f42a}',
+ ], // E1.0 [1] (🐪) camel
+ [
+ '\u{1f440}',
+ ], // E0.6 [1] (👀) eyes
+ [
+ '\u{1f465}',
+ ], // E1.0 [1] (👥) busts in silhouette
+ [
+ '\u{1f4ad}',
+ ], // E1.0 [1] (💭) thought balloon
+ [
+ '\u{1f4ee}',
+ ], // E0.6 [1] (📮) postbox
+ [
+ '\u{1f4ef}',
+ ], // E1.0 [1] (📯) postal horn
+ [
+ '\u{1f4f5}',
+ ], // E1.0 [1] (📵) no mobile phones
+ [
+ '\u{1f4f8}',
+ ], // E1.0 [1] (📸) camera with flash
+ [
+ '\u{1f503}',
+ ], // E0.6 [1] (🔃) clockwise vertical arrows
+ [
+ '\u{1f508}',
+ ], // E0.7 [1] (🔈) speaker low volume
+ [
+ '\u{1f509}',
+ ], // E1.0 [1] (🔉) speaker medium volume
+ [
+ '\u{1f515}',
+ ], // E1.0 [1] (🔕) bell with slash
+ [
+ '\u{1f57a}',
+ ], // E3.0 [1] (🕺) man dancing
+ [
+ '\u{1f5a4}',
+ ], // E3.0 [1] (🖤) black heart
+ [
+ '\u{1f600}',
+ ], // E1.0 [1] (😀) grinning face
+ [
+ '\u{1f60e}',
+ ], // E1.0 [1] (😎) smiling face with sunglasses
+ [
+ '\u{1f60f}',
+ ], // E0.6 [1] (😏) smirking face
+ [
+ '\u{1f610}',
+ ], // E0.7 [1] (😐) neutral face
+ [
+ '\u{1f611}',
+ ], // E1.0 [1] (😑) expressionless face
+ [
+ '\u{1f615}',
+ ], // E1.0 [1] (😕) confused face
+ [
+ '\u{1f616}',
+ ], // E0.6 [1] (😖) confounded face
+ [
+ '\u{1f617}',
+ ], // E1.0 [1] (😗) kissing face
+ [
+ '\u{1f618}',
+ ], // E0.6 [1] (😘) face blowing a kiss
+ [
+ '\u{1f619}',
+ ], // E1.0 [1] (😙) kissing face with smiling eyes
+ [
+ '\u{1f61a}',
+ ], // E0.6 [1] (😚) kissing face with closed eyes
+ [
+ '\u{1f61b}',
+ ], // E1.0 [1] (😛) face with tongue
+ [
+ '\u{1f61f}',
+ ], // E1.0 [1] (😟) worried face
+ [
+ '\u{1f62c}',
+ ], // E1.0 [1] (😬) grimacing face
+ [
+ '\u{1f62d}',
+ ], // E0.6 [1] (😭) loudly crying face
+ [
+ '\u{1f634}',
+ ], // E1.0 [1] (😴) sleeping face
+ [
+ '\u{1f635}',
+ ], // E0.6 [1] (😵) face with crossed-out eyes
+ [
+ '\u{1f636}',
+ ], // E1.0 [1] (😶) face without mouth
+ [
+ '\u{1f680}',
+ ], // E0.6 [1] (🚀) rocket
+ [
+ '\u{1f686}',
+ ], // E1.0 [1] (🚆) train
+ [
+ '\u{1f687}',
+ ], // E0.6 [1] (🚇) metro
+ [
+ '\u{1f688}',
+ ], // E1.0 [1] (🚈) light rail
+ [
+ '\u{1f689}',
+ ], // E0.6 [1] (🚉) station
+ [
+ '\u{1f68c}',
+ ], // E0.6 [1] (🚌) bus
+ [
+ '\u{1f68d}',
+ ], // E0.7 [1] (🚍) oncoming bus
+ [
+ '\u{1f68e}',
+ ], // E1.0 [1] (🚎) trolleybus
+ [
+ '\u{1f68f}',
+ ], // E0.6 [1] (🚏) bus stop
+ [
+ '\u{1f690}',
+ ], // E1.0 [1] (🚐) minibus
+ [
+ '\u{1f694}',
+ ], // E0.7 [1] (🚔) oncoming police car
+ [
+ '\u{1f695}',
+ ], // E0.6 [1] (🚕) taxi
+ [
+ '\u{1f696}',
+ ], // E1.0 [1] (🚖) oncoming taxi
+ [
+ '\u{1f697}',
+ ], // E0.6 [1] (🚗) automobile
+ [
+ '\u{1f698}',
+ ], // E0.7 [1] (🚘) oncoming automobile
+ [
+ '\u{1f6a2}',
+ ], // E0.6 [1] (🚢) ship
+ [
+ '\u{1f6a3}',
+ ], // E1.0 [1] (🚣) person rowing boat
+ [
+ '\u{1f6a6}',
+ ], // E1.0 [1] (🚦) vertical traffic light
+ [
+ '\u{1f6b2}',
+ ], // E0.6 [1] (🚲) bicycle
+ [
+ '\u{1f6b6}',
+ ], // E0.6 [1] (🚶) person walking
+ [
+ '\u{1f6bf}',
+ ], // E1.0 [1] (🚿) shower
+ [
+ '\u{1f6c0}',
+ ], // E0.6 [1] (🛀) person taking bath
+ [
+ '\u{1f6cc}',
+ ], // E1.0 [1] (🛌) person in bed
+ [
+ '\u{1f6d0}',
+ ], // E1.0 [1] (🛐) place of worship
+ [
+ '\u{1f6d5}',
+ ], // E12.0 [1] (🛕) hindu temple
+ [
+ '\u{1f6dc}',
+ ], // E15.0 [1] (🛜) wireless
+ [
+ '\u{1f6f9}',
+ ], // E11.0 [1] (🛹) skateboard
+ [
+ '\u{1f6fa}',
+ ], // E12.0 [1] (🛺) auto rickshaw
+ [
+ '\u{1f7f0}',
+ ], // E14.0 [1] (🟰) heavy equals sign
+ [
+ '\u{1f90c}',
+ ], // E13.0 [1] (🤌) pinched fingers
+ [
+ '\u{1f91f}',
+ ], // E5.0 [1] (🤟) love-you gesture
+ [
+ '\u{1f930}',
+ ], // E3.0 [1] (🤰) pregnant woman
+ [
+ '\u{1f93f}',
+ ], // E12.0 [1] (🤿) diving mask
+ [
+ '\u{1f94c}',
+ ], // E5.0 [1] (🥌) curling stone
+ [
+ '\u{1f971}',
+ ], // E12.0 [1] (🥱) yawning face
+ [
+ '\u{1f972}',
+ ], // E13.0 [1] (🥲) smiling face with tear
+ [
+ '\u{1f979}',
+ ], // E14.0 [1] (🥹) face holding back tears
+ [
+ '\u{1f97a}',
+ ], // E11.0 [1] (🥺) pleading face
+ [
+ '\u{1f97b}',
+ ], // E12.0 [1] (🥻) sari
+ [
+ '\u{1f9c0}',
+ ], // E1.0 [1] (🧀) cheese wedge
+ [
+ '\u{1f9cb}',
+ ], // E13.0 [1] (🧋) bubble tea
+ [
+ '\u{1f9cc}',
+ ], // E14.0 [1] (🧌) troll
+ [
+ '\u{1fa74}',
+ ], // E13.0 [1] (🩴) thong sandal
+ [
+ '\u{1fa89}',
+ ], // E16.0 [1] () harp
+ [
+ '\u{1fa8f}',
+ ], // E16.0 [1] () shovel
+ [
+ '\u{1fabe}',
+ ], // E16.0 [1] () leafless tree
+ [
+ '\u{1fabf}',
+ ], // E15.0 [1] (🪿) goose
+ [
+ '\u{1fac6}',
+ ], // E16.0 [1] () fingerprint
+ [
+ '\u{1fadc}',
+ ], // E16.0 [1] () root vegetable
+ [
+ '\u{1fadf}',
+ ], // E16.0 [1] () splatter
+ [
+ '\u{1fae8}',
+ ], // E15.0 [1] (🫨) shaking face
+ [
+ '\u{1fae9}',
+ ], // E16.0 [1] () face with bags under eyes
+ [
+ '\u261d',
+ ], // E0.6 [1] (☝️) index pointing up
+ [
+ '\u26f9',
+ ], // E0.7 [1] (⛹️) person bouncing ball
+ [
+ '\u270d',
+ ], // E0.7 [1] (✍️) writing hand
+ [
+ '\u{1f385}',
+ ], // E0.6 [1] (🎅) Santa Claus
+ [
+ '\u{1f3c7}',
+ ], // E1.0 [1] (🏇) horse racing
+ [
+ '\u{1f3ca}',
+ ], // E0.6 [1] (🏊) person swimming
+ [
+ '\u{1f47c}',
+ ], // E0.6 [1] (👼) baby angel
+ [
+ '\u{1f48f}',
+ ], // E0.6 [1] (💏) kiss
+ [
+ '\u{1f491}',
+ ], // E0.6 [1] (💑) couple with heart
+ [
+ '\u{1f4aa}',
+ ], // E0.6 [1] (💪) flexed biceps
+ [
+ '\u{1f57a}',
+ ], // E3.0 [1] (🕺) man dancing
+ [
+ '\u{1f590}',
+ ], // E0.7 [1] (🖐️) hand with fingers splayed
+ [
+ '\u{1f6a3}',
+ ], // E1.0 [1] (🚣) person rowing boat
+ [
+ '\u{1f6b6}',
+ ], // E0.6 [1] (🚶) person walking
+ [
+ '\u{1f6c0}',
+ ], // E0.6 [1] (🛀) person taking bath
+ [
+ '\u{1f6cc}',
+ ], // E1.0 [1] (🛌) person in bed
+ [
+ '\u{1f90c}',
+ ], // E13.0 [1] (🤌) pinched fingers
+ [
+ '\u{1f90f}',
+ ], // E12.0 [1] (🤏) pinching hand
+ [
+ '\u{1f918}',
+ ], // E1.0 [1] (🤘) sign of the horns
+ [
+ '\u{1f91f}',
+ ], // E5.0 [1] (🤟) love-you gesture
+ [
+ '\u{1f926}',
+ ], // E3.0 [1] (🤦) person facepalming
+ [
+ '\u{1f930}',
+ ], // E3.0 [1] (🤰) pregnant woman
+ [
+ '\u{1f977}',
+ ], // E13.0 [1] (🥷) ninja
+ [
+ '\u{1f9bb}',
+ ], // E12.0 [1] (🦻) ear with hearing aid
+ [
+ '#',
+ ], // E0.0 [1] (#️) hash sign
+ [
+ '*',
+ ], // E0.0 [1] (*️) asterisk
+ [
+ '\u200d',
+ ], // E0.0 [1] () zero width joiner
+ [
+ '\u20e3',
+ ], // E0.0 [1] (⃣) combining enclosing keycap
+ [
+ '\ufe0f',
+ ], // E0.0 [1] () VARIATION SELECTOR-16
+ [
+ '©',
+ ], // E0.6 [1] (©️) copyright
+ [
+ '®',
+ ], // E0.6 [1] (®️) registered
+ [
+ '\u203c',
+ ], // E0.6 [1] (‼️) double exclamation mark
+ [
+ '\u2049',
+ ], // E0.6 [1] (⁉️) exclamation question mark
+ [
+ '\u2122',
+ ], // E0.6 [1] (™️) trade mark
+ [
+ '\u2139',
+ ], // E0.6 [1] (ℹ️) information
+ [
+ '\u2328',
+ ], // E1.0 [1] (⌨️) keyboard
+ [
+ '\u2388',
+ ], // E0.0 [1] (⎈) HELM SYMBOL
+ [
+ '\u23cf',
+ ], // E1.0 [1] (⏏️) eject button
+ [
+ '\u23ef',
+ ], // E1.0 [1] (⏯️) play or pause button
+ [
+ '\u23f0',
+ ], // E0.6 [1] (⏰) alarm clock
+ [
+ '\u23f3',
+ ], // E0.6 [1] (⏳) hourglass not done
+ [
+ '\u24c2',
+ ], // E0.6 [1] (Ⓜ️) circled M
+ [
+ '\u25b6',
+ ], // E0.6 [1] (▶️) play button
+ [
+ '\u25c0',
+ ], // E0.6 [1] (◀️) reverse button
+ [
+ '\u2604',
+ ], // E1.0 [1] (☄️) comet
+ [
+ '\u2605',
+ ], // E0.0 [1] (★) BLACK STAR
+ [
+ '\u260e',
+ ], // E0.6 [1] (☎️) telephone
+ [
+ '\u2611',
+ ], // E0.6 [1] (☑️) check box with check
+ [
+ '\u2612',
+ ], // E0.0 [1] (☒) BALLOT BOX WITH X
+ [
+ '\u2618',
+ ], // E1.0 [1] (☘️) shamrock
+ [
+ '\u261d',
+ ], // E0.6 [1] (☝️) index pointing up
+ [
+ '\u2620',
+ ], // E1.0 [1] (☠️) skull and crossbones
+ [
+ '\u2621',
+ ], // E0.0 [1] (☡) CAUTION SIGN
+ [
+ '\u2626',
+ ], // E1.0 [1] (☦️) orthodox cross
+ [
+ '\u262a',
+ ], // E0.7 [1] (☪️) star and crescent
+ [
+ '\u262e',
+ ], // E1.0 [1] (☮️) peace symbol
+ [
+ '\u262f',
+ ], // E0.7 [1] (☯️) yin yang
+ [
+ '\u263a',
+ ], // E0.6 [1] (☺️) smiling face
+ [
+ '\u2640',
+ ], // E4.0 [1] (♀️) female sign
+ [
+ '\u2641',
+ ], // E0.0 [1] (♁) EARTH
+ [
+ '\u2642',
+ ], // E4.0 [1] (♂️) male sign
+ [
+ '\u265f',
+ ], // E11.0 [1] (♟️) chess pawn
+ [
+ '\u2660',
+ ], // E0.6 [1] (♠️) spade suit
+ [
+ '\u2663',
+ ], // E0.6 [1] (♣️) club suit
+ [
+ '\u2664',
+ ], // E0.0 [1] (♤) WHITE SPADE SUIT
+ [
+ '\u2667',
+ ], // E0.0 [1] (♧) WHITE CLUB SUIT
+ [
+ '\u2668',
+ ], // E0.6 [1] (♨️) hot springs
+ [
+ '\u267b',
+ ], // E0.6 [1] (♻️) recycling symbol
+ [
+ '\u267e',
+ ], // E11.0 [1] (♾️) infinity
+ [
+ '\u267f',
+ ], // E0.6 [1] (♿) wheelchair symbol
+ [
+ '\u2692',
+ ], // E1.0 [1] (⚒️) hammer and pick
+ [
+ '\u2693',
+ ], // E0.6 [1] (⚓) anchor
+ [
+ '\u2694',
+ ], // E1.0 [1] (⚔️) crossed swords
+ [
+ '\u2695',
+ ], // E4.0 [1] (⚕️) medical symbol
+ [
+ '\u2698',
+ ], // E0.0 [1] (⚘) FLOWER
+ [
+ '\u2699',
+ ], // E1.0 [1] (⚙️) gear
+ [
+ '\u269a',
+ ], // E0.0 [1] (⚚) STAFF OF HERMES
+ [
+ '\u26a7',
+ ], // E13.0 [1] (⚧️) transgender symbol
+ [
+ '\u26c8',
+ ], // E0.7 [1] (⛈️) cloud with lightning and rain
+ [
+ '\u26ce',
+ ], // E0.6 [1] (⛎) Ophiuchus
+ [
+ '\u26cf',
+ ], // E0.7 [1] (⛏️) pick
+ [
+ '\u26d0',
+ ], // E0.0 [1] (⛐) CAR SLIDING
+ [
+ '\u26d1',
+ ], // E0.7 [1] (⛑️) rescue worker’s helmet
+ [
+ '\u26d2',
+ ], // E0.0 [1] (⛒) CIRCLED CROSSING LANES
+ [
+ '\u26d3',
+ ], // E0.7 [1] (⛓️) chains
+ [
+ '\u26d4',
+ ], // E0.6 [1] (⛔) no entry
+ [
+ '\u26e9',
+ ], // E0.7 [1] (⛩️) shinto shrine
+ [
+ '\u26ea',
+ ], // E0.6 [1] (⛪) church
+ [
+ '\u26f4',
+ ], // E0.7 [1] (⛴️) ferry
+ [
+ '\u26f5',
+ ], // E0.6 [1] (⛵) sailboat
+ [
+ '\u26f6',
+ ], // E0.0 [1] (⛶) SQUARE FOUR CORNERS
+ [
+ '\u26fa',
+ ], // E0.6 [1] (⛺) tent
+ [
+ '\u26fd',
+ ], // E0.6 [1] (⛽) fuel pump
+ [
+ '\u2702',
+ ], // E0.6 [1] (✂️) scissors
+ [
+ '\u2705',
+ ], // E0.6 [1] (✅) check mark button
+ [
+ '\u270d',
+ ], // E0.7 [1] (✍️) writing hand
+ [
+ '\u270e',
+ ], // E0.0 [1] (✎) LOWER RIGHT PENCIL
+ [
+ '\u270f',
+ ], // E0.6 [1] (✏️) pencil
+ [
+ '\u2712',
+ ], // E0.6 [1] (✒️) black nib
+ [
+ '\u2714',
+ ], // E0.6 [1] (✔️) check mark
+ [
+ '\u2716',
+ ], // E0.6 [1] (✖️) multiply
+ [
+ '\u271d',
+ ], // E0.7 [1] (✝️) latin cross
+ [
+ '\u2721',
+ ], // E0.7 [1] (✡️) star of David
+ [
+ '\u2728',
+ ], // E0.6 [1] (✨) sparkles
+ [
+ '\u2744',
+ ], // E0.6 [1] (❄️) snowflake
+ [
+ '\u2747',
+ ], // E0.6 [1] (❇️) sparkle
+ [
+ '\u274c',
+ ], // E0.6 [1] (❌) cross mark
+ [
+ '\u274e',
+ ], // E0.6 [1] (❎) cross mark button
+ [
+ '\u2757',
+ ], // E0.6 [1] (❗) red exclamation mark
+ [
+ '\u2763',
+ ], // E1.0 [1] (❣️) heart exclamation
+ [
+ '\u2764',
+ ], // E0.6 [1] (❤️) red heart
+ [
+ '\u27a1',
+ ], // E0.6 [1] (➡️) right arrow
+ [
+ '\u27b0',
+ ], // E0.6 [1] (➰) curly loop
+ [
+ '\u27bf',
+ ], // E1.0 [1] (➿) double curly loop
+ [
+ '\u2b50',
+ ], // E0.6 [1] (⭐) star
+ [
+ '\u2b55',
+ ], // E0.6 [1] (⭕) hollow red circle
+ [
+ '\u3030',
+ ], // E0.6 [1] (〰️) wavy dash
+ [
+ '\u303d',
+ ], // E0.6 [1] (〽️) part alternation mark
+ [
+ '\u3297',
+ ], // E0.6 [1] (㊗️) Japanese “congratulations” button
+ [
+ '\u3299',
+ ], // E0.6 [1] (㊙️) Japanese “secret” button
+ [
+ '\u{1f004}',
+ ], // E0.6 [1] (🀄) mahjong red dragon
+ [
+ '\u{1f0cf}',
+ ], // E0.6 [1] (🃏) joker
+ [
+ '\u{1f12f}',
+ ], // E0.0 [1] (🄯) COPYLEFT SYMBOL
+ [
+ '\u{1f18e}',
+ ], // E0.6 [1] (🆎) AB button (blood type)
+ [
+ '\u{1f21a}',
+ ], // E0.6 [1] (🈚) Japanese “free of charge” button
+ [
+ '\u{1f22f}',
+ ], // E0.6 [1] (🈯) Japanese “reserved” button
+ [
+ '\u{1f30f}',
+ ], // E0.6 [1] (🌏) globe showing Asia-Australia
+ [
+ '\u{1f310}',
+ ], // E1.0 [1] (🌐) globe with meridians
+ [
+ '\u{1f311}',
+ ], // E0.6 [1] (🌑) new moon
+ [
+ '\u{1f312}',
+ ], // E1.0 [1] (🌒) waxing crescent moon
+ [
+ '\u{1f319}',
+ ], // E0.6 [1] (🌙) crescent moon
+ [
+ '\u{1f31a}',
+ ], // E1.0 [1] (🌚) new moon face
+ [
+ '\u{1f31b}',
+ ], // E0.6 [1] (🌛) first quarter moon face
+ [
+ '\u{1f31c}',
+ ], // E0.7 [1] (🌜) last quarter moon face
+ [
+ '\u{1f321}',
+ ], // E0.7 [1] (🌡️) thermometer
+ [
+ '\u{1f336}',
+ ], // E0.7 [1] (🌶️) hot pepper
+ [
+ '\u{1f34b}',
+ ], // E1.0 [1] (🍋) lemon
+ [
+ '\u{1f350}',
+ ], // E1.0 [1] (🍐) pear
+ [
+ '\u{1f37c}',
+ ], // E1.0 [1] (🍼) baby bottle
+ [
+ '\u{1f37d}',
+ ], // E0.7 [1] (🍽️) fork and knife with plate
+ [
+ '\u{1f398}',
+ ], // E0.0 [1] (🎘) MUSICAL KEYBOARD WITH JACKS
+ [
+ '\u{1f3c5}',
+ ], // E1.0 [1] (🏅) sports medal
+ [
+ '\u{1f3c6}',
+ ], // E0.6 [1] (🏆) trophy
+ [
+ '\u{1f3c7}',
+ ], // E1.0 [1] (🏇) horse racing
+ [
+ '\u{1f3c8}',
+ ], // E0.6 [1] (🏈) american football
+ [
+ '\u{1f3c9}',
+ ], // E1.0 [1] (🏉) rugby football
+ [
+ '\u{1f3ca}',
+ ], // E0.6 [1] (🏊) person swimming
+ [
+ '\u{1f3e4}',
+ ], // E1.0 [1] (🏤) post office
+ [
+ '\u{1f3f3}',
+ ], // E0.7 [1] (🏳️) white flag
+ [
+ '\u{1f3f4}',
+ ], // E1.0 [1] (🏴) black flag
+ [
+ '\u{1f3f5}',
+ ], // E0.7 [1] (🏵️) rosette
+ [
+ '\u{1f3f6}',
+ ], // E0.0 [1] (🏶) BLACK ROSETTE
+ [
+ '\u{1f3f7}',
+ ], // E0.7 [1] (🏷️) label
+ [
+ '\u{1f408}',
+ ], // E0.7 [1] (🐈) cat
+ [
+ '\u{1f413}',
+ ], // E1.0 [1] (🐓) rooster
+ [
+ '\u{1f414}',
+ ], // E0.6 [1] (🐔) chicken
+ [
+ '\u{1f415}',
+ ], // E0.7 [1] (🐕) dog
+ [
+ '\u{1f416}',
+ ], // E1.0 [1] (🐖) pig
+ [
+ '\u{1f42a}',
+ ], // E1.0 [1] (🐪) camel
+ [
+ '\u{1f43f}',
+ ], // E0.7 [1] (🐿️) chipmunk
+ [
+ '\u{1f440}',
+ ], // E0.6 [1] (👀) eyes
+ [
+ '\u{1f441}',
+ ], // E0.7 [1] (👁️) eye
+ [
+ '\u{1f465}',
+ ], // E1.0 [1] (👥) busts in silhouette
+ [
+ '\u{1f4ad}',
+ ], // E1.0 [1] (💭) thought balloon
+ [
+ '\u{1f4ee}',
+ ], // E0.6 [1] (📮) postbox
+ [
+ '\u{1f4ef}',
+ ], // E1.0 [1] (📯) postal horn
+ [
+ '\u{1f4f5}',
+ ], // E1.0 [1] (📵) no mobile phones
+ [
+ '\u{1f4f8}',
+ ], // E1.0 [1] (📸) camera with flash
+ [
+ '\u{1f4fd}',
+ ], // E0.7 [1] (📽️) film projector
+ [
+ '\u{1f4fe}',
+ ], // E0.0 [1] (📾) PORTABLE STEREO
+ [
+ '\u{1f503}',
+ ], // E0.6 [1] (🔃) clockwise vertical arrows
+ [
+ '\u{1f508}',
+ ], // E0.7 [1] (🔈) speaker low volume
+ [
+ '\u{1f509}',
+ ], // E1.0 [1] (🔉) speaker medium volume
+ [
+ '\u{1f515}',
+ ], // E1.0 [1] (🔕) bell with slash
+ [
+ '\u{1f54f}',
+ ], // E0.0 [1] (🕏) BOWL OF HYGIEIA
+ [
+ '\u{1f57a}',
+ ], // E3.0 [1] (🕺) man dancing
+ [
+ '\u{1f587}',
+ ], // E0.7 [1] (🖇️) linked paperclips
+ [
+ '\u{1f590}',
+ ], // E0.7 [1] (🖐️) hand with fingers splayed
+ [
+ '\u{1f5a4}',
+ ], // E3.0 [1] (🖤) black heart
+ [
+ '\u{1f5a5}',
+ ], // E0.7 [1] (🖥️) desktop computer
+ [
+ '\u{1f5a8}',
+ ], // E0.7 [1] (🖨️) printer
+ [
+ '\u{1f5bc}',
+ ], // E0.7 [1] (🖼️) framed picture
+ [
+ '\u{1f5e1}',
+ ], // E0.7 [1] (🗡️) dagger
+ [
+ '\u{1f5e2}',
+ ], // E0.0 [1] (🗢) LIPS
+ [
+ '\u{1f5e3}',
+ ], // E0.7 [1] (🗣️) speaking head
+ [
+ '\u{1f5e8}',
+ ], // E2.0 [1] (🗨️) left speech bubble
+ [
+ '\u{1f5ef}',
+ ], // E0.7 [1] (🗯️) right anger bubble
+ [
+ '\u{1f5f3}',
+ ], // E0.7 [1] (🗳️) ballot box with ballot
+ [
+ '\u{1f5fa}',
+ ], // E0.7 [1] (🗺️) world map
+ [
+ '\u{1f600}',
+ ], // E1.0 [1] (😀) grinning face
+ [
+ '\u{1f60e}',
+ ], // E1.0 [1] (😎) smiling face with sunglasses
+ [
+ '\u{1f60f}',
+ ], // E0.6 [1] (😏) smirking face
+ [
+ '\u{1f610}',
+ ], // E0.7 [1] (😐) neutral face
+ [
+ '\u{1f611}',
+ ], // E1.0 [1] (😑) expressionless face
+ [
+ '\u{1f615}',
+ ], // E1.0 [1] (😕) confused face
+ [
+ '\u{1f616}',
+ ], // E0.6 [1] (😖) confounded face
+ [
+ '\u{1f617}',
+ ], // E1.0 [1] (😗) kissing face
+ [
+ '\u{1f618}',
+ ], // E0.6 [1] (😘) face blowing a kiss
+ [
+ '\u{1f619}',
+ ], // E1.0 [1] (😙) kissing face with smiling eyes
+ [
+ '\u{1f61a}',
+ ], // E0.6 [1] (😚) kissing face with closed eyes
+ [
+ '\u{1f61b}',
+ ], // E1.0 [1] (😛) face with tongue
+ [
+ '\u{1f61f}',
+ ], // E1.0 [1] (😟) worried face
+ [
+ '\u{1f62c}',
+ ], // E1.0 [1] (😬) grimacing face
+ [
+ '\u{1f62d}',
+ ], // E0.6 [1] (😭) loudly crying face
+ [
+ '\u{1f634}',
+ ], // E1.0 [1] (😴) sleeping face
+ [
+ '\u{1f635}',
+ ], // E0.6 [1] (😵) face with crossed-out eyes
+ [
+ '\u{1f636}',
+ ], // E1.0 [1] (😶) face without mouth
+ [
+ '\u{1f680}',
+ ], // E0.6 [1] (🚀) rocket
+ [
+ '\u{1f686}',
+ ], // E1.0 [1] (🚆) train
+ [
+ '\u{1f687}',
+ ], // E0.6 [1] (🚇) metro
+ [
+ '\u{1f688}',
+ ], // E1.0 [1] (🚈) light rail
+ [
+ '\u{1f689}',
+ ], // E0.6 [1] (🚉) station
+ [
+ '\u{1f68c}',
+ ], // E0.6 [1] (🚌) bus
+ [
+ '\u{1f68d}',
+ ], // E0.7 [1] (🚍) oncoming bus
+ [
+ '\u{1f68e}',
+ ], // E1.0 [1] (🚎) trolleybus
+ [
+ '\u{1f68f}',
+ ], // E0.6 [1] (🚏) bus stop
+ [
+ '\u{1f690}',
+ ], // E1.0 [1] (🚐) minibus
+ [
+ '\u{1f694}',
+ ], // E0.7 [1] (🚔) oncoming police car
+ [
+ '\u{1f695}',
+ ], // E0.6 [1] (🚕) taxi
+ [
+ '\u{1f696}',
+ ], // E1.0 [1] (🚖) oncoming taxi
+ [
+ '\u{1f697}',
+ ], // E0.6 [1] (🚗) automobile
+ [
+ '\u{1f698}',
+ ], // E0.7 [1] (🚘) oncoming automobile
+ [
+ '\u{1f6a2}',
+ ], // E0.6 [1] (🚢) ship
+ [
+ '\u{1f6a3}',
+ ], // E1.0 [1] (🚣) person rowing boat
+ [
+ '\u{1f6a6}',
+ ], // E1.0 [1] (🚦) vertical traffic light
+ [
+ '\u{1f6b2}',
+ ], // E0.6 [1] (🚲) bicycle
+ [
+ '\u{1f6b6}',
+ ], // E0.6 [1] (🚶) person walking
+ [
+ '\u{1f6bf}',
+ ], // E1.0 [1] (🚿) shower
+ [
+ '\u{1f6c0}',
+ ], // E0.6 [1] (🛀) person taking bath
+ [
+ '\u{1f6cb}',
+ ], // E0.7 [1] (🛋️) couch and lamp
+ [
+ '\u{1f6cc}',
+ ], // E1.0 [1] (🛌) person in bed
+ [
+ '\u{1f6d0}',
+ ], // E1.0 [1] (🛐) place of worship
+ [
+ '\u{1f6d5}',
+ ], // E12.0 [1] (🛕) hindu temple
+ [
+ '\u{1f6dc}',
+ ], // E15.0 [1] (🛜) wireless
+ [
+ '\u{1f6e9}',
+ ], // E0.7 [1] (🛩️) small airplane
+ [
+ '\u{1f6ea}',
+ ], // E0.0 [1] (🛪) NORTHEAST-POINTING AIRPLANE
+ [
+ '\u{1f6f0}',
+ ], // E0.7 [1] (🛰️) satellite
+ [
+ '\u{1f6f3}',
+ ], // E0.7 [1] (🛳️) passenger ship
+ [
+ '\u{1f6f9}',
+ ], // E11.0 [1] (🛹) skateboard
+ [
+ '\u{1f6fa}',
+ ], // E12.0 [1] (🛺) auto rickshaw
+ [
+ '\u{1f7f0}',
+ ], // E14.0 [1] (🟰) heavy equals sign
+ [
+ '\u{1f90c}',
+ ], // E13.0 [1] (🤌) pinched fingers
+ [
+ '\u{1f91f}',
+ ], // E5.0 [1] (🤟) love-you gesture
+ [
+ '\u{1f930}',
+ ], // E3.0 [1] (🤰) pregnant woman
+ [
+ '\u{1f93f}',
+ ], // E12.0 [1] (🤿) diving mask
+ [
+ '\u{1f94c}',
+ ], // E5.0 [1] (🥌) curling stone
+ [
+ '\u{1f971}',
+ ], // E12.0 [1] (🥱) yawning face
+ [
+ '\u{1f972}',
+ ], // E13.0 [1] (🥲) smiling face with tear
+ [
+ '\u{1f979}',
+ ], // E14.0 [1] (🥹) face holding back tears
+ [
+ '\u{1f97a}',
+ ], // E11.0 [1] (🥺) pleading face
+ [
+ '\u{1f97b}',
+ ], // E12.0 [1] (🥻) sari
+ [
+ '\u{1f9c0}',
+ ], // E1.0 [1] (🧀) cheese wedge
+ [
+ '\u{1f9cb}',
+ ], // E13.0 [1] (🧋) bubble tea
+ [
+ '\u{1f9cc}',
+ ], // E14.0 [1] (🧌) troll
+ [
+ '\u{1fa74}',
+ ], // E13.0 [1] (🩴) thong sandal
+ [
+ '\u{1fa89}',
+ ], // E16.0 [1] () harp
+ [
+ '\u{1fa8f}',
+ ], // E16.0 [1] () shovel
+ [
+ '\u{1fabe}',
+ ], // E16.0 [1] () leafless tree
+ [
+ '\u{1fabf}',
+ ], // E15.0 [1] (🪿) goose
+ [
+ '\u{1fac6}',
+ ], // E16.0 [1] () fingerprint
+ [
+ '\u{1fadc}',
+ ], // E16.0 [1] () root vegetable
+ [
+ '\u{1fadf}',
+ ], // E16.0 [1] () splatter
+ [
+ '\u{1fae8}',
+ ], // E15.0 [1] (🫨) shaking face
+ [
+ '\u{1fae9}',
+ ], // E16.0 [1] () face with bags under eyes
+];
+// dart format on
+// BMP character in each category, if any, -1 if none.
+const lowerChars = <int>[
+ 0xd,
+ 0x1,
+ 0x2a,
+ 0x200c,
+ 0x903,
+ -0x1,
+ 0x3299,
+ 0xa,
+ 0x600,
+ 0x1100,
+ 0x1160,
+ 0x11a8,
+ 0xac00,
+ 0xac01,
+ 0x924,
+ 0x200d,
+ 0xfe0f,
+ 0x94d,
+];
+// Non-BMP character in each category, if any, -1 if none.
+const upperChars = <int>[
+ -0x1,
+ 0x13430,
+ 0x10000,
+ -0x1,
+ 0x11000,
+ 0x1f1e9,
+ 0x1fae9,
+ -0x1,
+ 0x110bd,
+ -0x1,
+ 0x16d63,
+ -0x1,
+ -0x1,
+ -0x1,
+ -0x1,
+ -0x1,
+ 0x1f3ff,
+ -0x1,
+];
diff --git a/pkgs/characters/test/src/unicode_tests.dart b/pkgs/characters/test/src/unicode_tests.dart
new file mode 100644
index 0000000..f88ba26
--- /dev/null
+++ b/pkgs/characters/test/src/unicode_tests.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:characters/src/grapheme_clusters/table.dart';
+
+import '../../tool/src/debug_names.dart';
+
+export 'unicode_grapheme_tests.dart';
+
+/// Readable description of the [expected] grapheme clusters.
+///
+/// The list of strings is the expected grapheme cluster separation
+/// of the concatenation of those strings.
+///
+/// The description converts each code unit to a 4-digit hex number,
+/// puts ` × ` between the code units of the same grapheme cluster
+/// and ` ÷ ` before, after and between the grapheme clusters.
+/// (This is the format of the original Unicode test data, so it
+/// can be compared to the original tests.)
+String testDescription(List<String> expected) {
+ var expectedString = expected
+ .map((s) =>
+ s.runes.map((x) => x.toRadixString(16).padLeft(4, '0')).join(' × '))
+ .join(' ÷ ');
+ return '÷ $expectedString ÷';
+}
+
+int categoryOf(int codePoint) {
+ if (codePoint < 0x10000) return low(codePoint);
+ var nonBmpOffset = codePoint - 0x10000;
+ return high(0xD800 + (nonBmpOffset >> 10), 0xDC00 + (nonBmpOffset & 0x3ff));
+}
+
+String partCategories(List<String> parts) {
+ var index = 0;
+ int posOf(int rune) {
+ var result = index;
+ index += rune >= 0xFFFF ? 2 : 1;
+ return result;
+ }
+
+ return parts.map((part) {
+ return part.runes
+ .map((n) => '#${posOf(n)}:${categoryLongNames[categoryOf(n)]}')
+ .join(' × ');
+ }).join(' ÷ ');
+}
diff --git a/pkgs/characters/test/src/various_tests.dart b/pkgs/characters/test/src/various_tests.dart
new file mode 100644
index 0000000..7409c5b
--- /dev/null
+++ b/pkgs/characters/test/src/various_tests.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+const zalgo = [
+ [
+ 'Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍',
+ 'A̴̵̜̰͔ͫ͗͢',
+ 'L̠ͨͧͩ͘',
+ 'G̴̻͈͍͔̹̑͗̎̅͛́',
+ 'Ǫ̵̹̻̝̳͂̌̌͘',
+ '!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞'
+ ],
+];
diff --git a/pkgs/characters/third_party/Unicode_Consortium/DerivedCoreProperties.txt b/pkgs/characters/third_party/Unicode_Consortium/DerivedCoreProperties.txt
new file mode 100644
index 0000000..1075638
--- /dev/null
+++ b/pkgs/characters/third_party/Unicode_Consortium/DerivedCoreProperties.txt
@@ -0,0 +1,13362 @@
+# DerivedCoreProperties-16.0.0.txt
+# Date: 2024-05-31, 18:09:32 GMT
+# © 2024 Unicode®, Inc.
+# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+# For terms of use and license, see https://www.unicode.org/terms_of_use.html
+#
+# Unicode Character Database
+# For documentation, see https://www.unicode.org/reports/tr44/
+
+# ================================================
+
+# Derived Property: Math
+# Generated from: Sm + Other_Math
+
+002B ; Math # Sm PLUS SIGN
+003C..003E ; Math # Sm [3] LESS-THAN SIGN..GREATER-THAN SIGN
+005E ; Math # Sk CIRCUMFLEX ACCENT
+007C ; Math # Sm VERTICAL LINE
+007E ; Math # Sm TILDE
+00AC ; Math # Sm NOT SIGN
+00B1 ; Math # Sm PLUS-MINUS SIGN
+00D7 ; Math # Sm MULTIPLICATION SIGN
+00F7 ; Math # Sm DIVISION SIGN
+03D0..03D2 ; Math # L& [3] GREEK BETA SYMBOL..GREEK UPSILON WITH HOOK SYMBOL
+03D5 ; Math # L& GREEK PHI SYMBOL
+03F0..03F1 ; Math # L& [2] GREEK KAPPA SYMBOL..GREEK RHO SYMBOL
+03F4..03F5 ; Math # L& [2] GREEK CAPITAL THETA SYMBOL..GREEK LUNATE EPSILON SYMBOL
+03F6 ; Math # Sm GREEK REVERSED LUNATE EPSILON SYMBOL
+0606..0608 ; Math # Sm [3] ARABIC-INDIC CUBE ROOT..ARABIC RAY
+2016 ; Math # Po DOUBLE VERTICAL LINE
+2032..2034 ; Math # Po [3] PRIME..TRIPLE PRIME
+2040 ; Math # Pc CHARACTER TIE
+2044 ; Math # Sm FRACTION SLASH
+2052 ; Math # Sm COMMERCIAL MINUS SIGN
+2061..2064 ; Math # Cf [4] FUNCTION APPLICATION..INVISIBLE PLUS
+207A..207C ; Math # Sm [3] SUPERSCRIPT PLUS SIGN..SUPERSCRIPT EQUALS SIGN
+207D ; Math # Ps SUPERSCRIPT LEFT PARENTHESIS
+207E ; Math # Pe SUPERSCRIPT RIGHT PARENTHESIS
+208A..208C ; Math # Sm [3] SUBSCRIPT PLUS SIGN..SUBSCRIPT EQUALS SIGN
+208D ; Math # Ps SUBSCRIPT LEFT PARENTHESIS
+208E ; Math # Pe SUBSCRIPT RIGHT PARENTHESIS
+20D0..20DC ; Math # Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE
+20E1 ; Math # Mn COMBINING LEFT RIGHT ARROW ABOVE
+20E5..20E6 ; Math # Mn [2] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING DOUBLE VERTICAL STROKE OVERLAY
+20EB..20EF ; Math # Mn [5] COMBINING LONG DOUBLE SOLIDUS OVERLAY..COMBINING RIGHT ARROW BELOW
+2102 ; Math # L& DOUBLE-STRUCK CAPITAL C
+2107 ; Math # L& EULER CONSTANT
+210A..2113 ; Math # L& [10] SCRIPT SMALL G..SCRIPT SMALL L
+2115 ; Math # L& DOUBLE-STRUCK CAPITAL N
+2118 ; Math # Sm SCRIPT CAPITAL P
+2119..211D ; Math # L& [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R
+2124 ; Math # L& DOUBLE-STRUCK CAPITAL Z
+2128 ; Math # L& BLACK-LETTER CAPITAL Z
+2129 ; Math # So TURNED GREEK SMALL LETTER IOTA
+212C..212D ; Math # L& [2] SCRIPT CAPITAL B..BLACK-LETTER CAPITAL C
+212F..2131 ; Math # L& [3] SCRIPT SMALL E..SCRIPT CAPITAL F
+2133..2134 ; Math # L& [2] SCRIPT CAPITAL M..SCRIPT SMALL O
+2135..2138 ; Math # Lo [4] ALEF SYMBOL..DALET SYMBOL
+213C..213F ; Math # L& [4] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK CAPITAL PI
+2140..2144 ; Math # Sm [5] DOUBLE-STRUCK N-ARY SUMMATION..TURNED SANS-SERIF CAPITAL Y
+2145..2149 ; Math # L& [5] DOUBLE-STRUCK ITALIC CAPITAL D..DOUBLE-STRUCK ITALIC SMALL J
+214B ; Math # Sm TURNED AMPERSAND
+2190..2194 ; Math # Sm [5] LEFTWARDS ARROW..LEFT RIGHT ARROW
+2195..2199 ; Math # So [5] UP DOWN ARROW..SOUTH WEST ARROW
+219A..219B ; Math # Sm [2] LEFTWARDS ARROW WITH STROKE..RIGHTWARDS ARROW WITH STROKE
+219C..219F ; Math # So [4] LEFTWARDS WAVE ARROW..UPWARDS TWO HEADED ARROW
+21A0 ; Math # Sm RIGHTWARDS TWO HEADED ARROW
+21A1..21A2 ; Math # So [2] DOWNWARDS TWO HEADED ARROW..LEFTWARDS ARROW WITH TAIL
+21A3 ; Math # Sm RIGHTWARDS ARROW WITH TAIL
+21A4..21A5 ; Math # So [2] LEFTWARDS ARROW FROM BAR..UPWARDS ARROW FROM BAR
+21A6 ; Math # Sm RIGHTWARDS ARROW FROM BAR
+21A7 ; Math # So DOWNWARDS ARROW FROM BAR
+21A9..21AD ; Math # So [5] LEFTWARDS ARROW WITH HOOK..LEFT RIGHT WAVE ARROW
+21AE ; Math # Sm LEFT RIGHT ARROW WITH STROKE
+21B0..21B1 ; Math # So [2] UPWARDS ARROW WITH TIP LEFTWARDS..UPWARDS ARROW WITH TIP RIGHTWARDS
+21B6..21B7 ; Math # So [2] ANTICLOCKWISE TOP SEMICIRCLE ARROW..CLOCKWISE TOP SEMICIRCLE ARROW
+21BC..21CD ; Math # So [18] LEFTWARDS HARPOON WITH BARB UPWARDS..LEFTWARDS DOUBLE ARROW WITH STROKE
+21CE..21CF ; Math # Sm [2] LEFT RIGHT DOUBLE ARROW WITH STROKE..RIGHTWARDS DOUBLE ARROW WITH STROKE
+21D0..21D1 ; Math # So [2] LEFTWARDS DOUBLE ARROW..UPWARDS DOUBLE ARROW
+21D2 ; Math # Sm RIGHTWARDS DOUBLE ARROW
+21D3 ; Math # So DOWNWARDS DOUBLE ARROW
+21D4 ; Math # Sm LEFT RIGHT DOUBLE ARROW
+21D5..21DB ; Math # So [7] UP DOWN DOUBLE ARROW..RIGHTWARDS TRIPLE ARROW
+21DD ; Math # So RIGHTWARDS SQUIGGLE ARROW
+21E4..21E5 ; Math # So [2] LEFTWARDS ARROW TO BAR..RIGHTWARDS ARROW TO BAR
+21F4..22FF ; Math # Sm [268] RIGHT ARROW WITH SMALL CIRCLE..Z NOTATION BAG MEMBERSHIP
+2308 ; Math # Ps LEFT CEILING
+2309 ; Math # Pe RIGHT CEILING
+230A ; Math # Ps LEFT FLOOR
+230B ; Math # Pe RIGHT FLOOR
+2320..2321 ; Math # Sm [2] TOP HALF INTEGRAL..BOTTOM HALF INTEGRAL
+237C ; Math # Sm RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW
+239B..23B3 ; Math # Sm [25] LEFT PARENTHESIS UPPER HOOK..SUMMATION BOTTOM
+23B4..23B5 ; Math # So [2] TOP SQUARE BRACKET..BOTTOM SQUARE BRACKET
+23B7 ; Math # So RADICAL SYMBOL BOTTOM
+23D0 ; Math # So VERTICAL LINE EXTENSION
+23DC..23E1 ; Math # Sm [6] TOP PARENTHESIS..BOTTOM TORTOISE SHELL BRACKET
+23E2 ; Math # So WHITE TRAPEZIUM
+25A0..25A1 ; Math # So [2] BLACK SQUARE..WHITE SQUARE
+25AE..25B6 ; Math # So [9] BLACK VERTICAL RECTANGLE..BLACK RIGHT-POINTING TRIANGLE
+25B7 ; Math # Sm WHITE RIGHT-POINTING TRIANGLE
+25BC..25C0 ; Math # So [5] BLACK DOWN-POINTING TRIANGLE..BLACK LEFT-POINTING TRIANGLE
+25C1 ; Math # Sm WHITE LEFT-POINTING TRIANGLE
+25C6..25C7 ; Math # So [2] BLACK DIAMOND..WHITE DIAMOND
+25CA..25CB ; Math # So [2] LOZENGE..WHITE CIRCLE
+25CF..25D3 ; Math # So [5] BLACK CIRCLE..CIRCLE WITH UPPER HALF BLACK
+25E2 ; Math # So BLACK LOWER RIGHT TRIANGLE
+25E4 ; Math # So BLACK UPPER LEFT TRIANGLE
+25E7..25EC ; Math # So [6] SQUARE WITH LEFT HALF BLACK..WHITE UP-POINTING TRIANGLE WITH DOT
+25F8..25FF ; Math # Sm [8] UPPER LEFT TRIANGLE..LOWER RIGHT TRIANGLE
+2605..2606 ; Math # So [2] BLACK STAR..WHITE STAR
+2640 ; Math # So FEMALE SIGN
+2642 ; Math # So MALE SIGN
+2660..2663 ; Math # So [4] BLACK SPADE SUIT..BLACK CLUB SUIT
+266D..266E ; Math # So [2] MUSIC FLAT SIGN..MUSIC NATURAL SIGN
+266F ; Math # Sm MUSIC SHARP SIGN
+27C0..27C4 ; Math # Sm [5] THREE DIMENSIONAL ANGLE..OPEN SUPERSET
+27C5 ; Math # Ps LEFT S-SHAPED BAG DELIMITER
+27C6 ; Math # Pe RIGHT S-SHAPED BAG DELIMITER
+27C7..27E5 ; Math # Sm [31] OR WITH DOT INSIDE..WHITE SQUARE WITH RIGHTWARDS TICK
+27E6 ; Math # Ps MATHEMATICAL LEFT WHITE SQUARE BRACKET
+27E7 ; Math # Pe MATHEMATICAL RIGHT WHITE SQUARE BRACKET
+27E8 ; Math # Ps MATHEMATICAL LEFT ANGLE BRACKET
+27E9 ; Math # Pe MATHEMATICAL RIGHT ANGLE BRACKET
+27EA ; Math # Ps MATHEMATICAL LEFT DOUBLE ANGLE BRACKET
+27EB ; Math # Pe MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET
+27EC ; Math # Ps MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET
+27ED ; Math # Pe MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET
+27EE ; Math # Ps MATHEMATICAL LEFT FLATTENED PARENTHESIS
+27EF ; Math # Pe MATHEMATICAL RIGHT FLATTENED PARENTHESIS
+27F0..27FF ; Math # Sm [16] UPWARDS QUADRUPLE ARROW..LONG RIGHTWARDS SQUIGGLE ARROW
+2900..2982 ; Math # Sm [131] RIGHTWARDS TWO-HEADED ARROW WITH VERTICAL STROKE..Z NOTATION TYPE COLON
+2983 ; Math # Ps LEFT WHITE CURLY BRACKET
+2984 ; Math # Pe RIGHT WHITE CURLY BRACKET
+2985 ; Math # Ps LEFT WHITE PARENTHESIS
+2986 ; Math # Pe RIGHT WHITE PARENTHESIS
+2987 ; Math # Ps Z NOTATION LEFT IMAGE BRACKET
+2988 ; Math # Pe Z NOTATION RIGHT IMAGE BRACKET
+2989 ; Math # Ps Z NOTATION LEFT BINDING BRACKET
+298A ; Math # Pe Z NOTATION RIGHT BINDING BRACKET
+298B ; Math # Ps LEFT SQUARE BRACKET WITH UNDERBAR
+298C ; Math # Pe RIGHT SQUARE BRACKET WITH UNDERBAR
+298D ; Math # Ps LEFT SQUARE BRACKET WITH TICK IN TOP CORNER
+298E ; Math # Pe RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER
+298F ; Math # Ps LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER
+2990 ; Math # Pe RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER
+2991 ; Math # Ps LEFT ANGLE BRACKET WITH DOT
+2992 ; Math # Pe RIGHT ANGLE BRACKET WITH DOT
+2993 ; Math # Ps LEFT ARC LESS-THAN BRACKET
+2994 ; Math # Pe RIGHT ARC GREATER-THAN BRACKET
+2995 ; Math # Ps DOUBLE LEFT ARC GREATER-THAN BRACKET
+2996 ; Math # Pe DOUBLE RIGHT ARC LESS-THAN BRACKET
+2997 ; Math # Ps LEFT BLACK TORTOISE SHELL BRACKET
+2998 ; Math # Pe RIGHT BLACK TORTOISE SHELL BRACKET
+2999..29D7 ; Math # Sm [63] DOTTED FENCE..BLACK HOURGLASS
+29D8 ; Math # Ps LEFT WIGGLY FENCE
+29D9 ; Math # Pe RIGHT WIGGLY FENCE
+29DA ; Math # Ps LEFT DOUBLE WIGGLY FENCE
+29DB ; Math # Pe RIGHT DOUBLE WIGGLY FENCE
+29DC..29FB ; Math # Sm [32] INCOMPLETE INFINITY..TRIPLE PLUS
+29FC ; Math # Ps LEFT-POINTING CURVED ANGLE BRACKET
+29FD ; Math # Pe RIGHT-POINTING CURVED ANGLE BRACKET
+29FE..2AFF ; Math # Sm [258] TINY..N-ARY WHITE VERTICAL BAR
+2B30..2B44 ; Math # Sm [21] LEFT ARROW WITH SMALL CIRCLE..RIGHTWARDS ARROW THROUGH SUPERSET
+2B47..2B4C ; Math # Sm [6] REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR
+FB29 ; Math # Sm HEBREW LETTER ALTERNATIVE PLUS SIGN
+FE61 ; Math # Po SMALL ASTERISK
+FE62 ; Math # Sm SMALL PLUS SIGN
+FE63 ; Math # Pd SMALL HYPHEN-MINUS
+FE64..FE66 ; Math # Sm [3] SMALL LESS-THAN SIGN..SMALL EQUALS SIGN
+FE68 ; Math # Po SMALL REVERSE SOLIDUS
+FF0B ; Math # Sm FULLWIDTH PLUS SIGN
+FF1C..FF1E ; Math # Sm [3] FULLWIDTH LESS-THAN SIGN..FULLWIDTH GREATER-THAN SIGN
+FF3C ; Math # Po FULLWIDTH REVERSE SOLIDUS
+FF3E ; Math # Sk FULLWIDTH CIRCUMFLEX ACCENT
+FF5C ; Math # Sm FULLWIDTH VERTICAL LINE
+FF5E ; Math # Sm FULLWIDTH TILDE
+FFE2 ; Math # Sm FULLWIDTH NOT SIGN
+FFE9..FFEC ; Math # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS ARROW
+10D8E..10D8F ; Math # Sm [2] GARAY PLUS SIGN..GARAY MINUS SIGN
+1D400..1D454 ; Math # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G
+1D456..1D49C ; Math # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A
+1D49E..1D49F ; Math # L& [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D
+1D4A2 ; Math # L& MATHEMATICAL SCRIPT CAPITAL G
+1D4A5..1D4A6 ; Math # L& [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K
+1D4A9..1D4AC ; Math # L& [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q
+1D4AE..1D4B9 ; Math # L& [12] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D
+1D4BB ; Math # L& MATHEMATICAL SCRIPT SMALL F
+1D4BD..1D4C3 ; Math # L& [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N
+1D4C5..1D505 ; Math # L& [65] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B
+1D507..1D50A ; Math # L& [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G
+1D50D..1D514 ; Math # L& [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q
+1D516..1D51C ; Math # L& [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y
+1D51E..1D539 ; Math # L& [28] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B
+1D53B..1D53E ; Math # L& [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G
+1D540..1D544 ; Math # L& [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M
+1D546 ; Math # L& MATHEMATICAL DOUBLE-STRUCK CAPITAL O
+1D54A..1D550 ; Math # L& [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y
+1D552..1D6A5 ; Math # L& [340] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J
+1D6A8..1D6C0 ; Math # L& [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA
+1D6C1 ; Math # Sm MATHEMATICAL BOLD NABLA
+1D6C2..1D6DA ; Math # L& [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA
+1D6DB ; Math # Sm MATHEMATICAL BOLD PARTIAL DIFFERENTIAL
+1D6DC..1D6FA ; Math # L& [31] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL ITALIC CAPITAL OMEGA
+1D6FB ; Math # Sm MATHEMATICAL ITALIC NABLA
+1D6FC..1D714 ; Math # L& [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA
+1D715 ; Math # Sm MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL
+1D716..1D734 ; Math # L& [31] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA
+1D735 ; Math # Sm MATHEMATICAL BOLD ITALIC NABLA
+1D736..1D74E ; Math # L& [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA
+1D74F ; Math # Sm MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL
+1D750..1D76E ; Math # L& [31] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA
+1D76F ; Math # Sm MATHEMATICAL SANS-SERIF BOLD NABLA
+1D770..1D788 ; Math # L& [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA
+1D789 ; Math # Sm MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL
+1D78A..1D7A8 ; Math # L& [31] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA
+1D7A9 ; Math # Sm MATHEMATICAL SANS-SERIF BOLD ITALIC NABLA
+1D7AA..1D7C2 ; Math # L& [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA
+1D7C3 ; Math # Sm MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL
+1D7C4..1D7CB ; Math # L& [8] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD SMALL DIGAMMA
+1D7CE..1D7FF ; Math # Nd [50] MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE
+1EE00..1EE03 ; Math # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL
+1EE05..1EE1F ; Math # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF
+1EE21..1EE22 ; Math # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM
+1EE24 ; Math # Lo ARABIC MATHEMATICAL INITIAL HEH
+1EE27 ; Math # Lo ARABIC MATHEMATICAL INITIAL HAH
+1EE29..1EE32 ; Math # Lo [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF
+1EE34..1EE37 ; Math # Lo [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH
+1EE39 ; Math # Lo ARABIC MATHEMATICAL INITIAL DAD
+1EE3B ; Math # Lo ARABIC MATHEMATICAL INITIAL GHAIN
+1EE42 ; Math # Lo ARABIC MATHEMATICAL TAILED JEEM
+1EE47 ; Math # Lo ARABIC MATHEMATICAL TAILED HAH
+1EE49 ; Math # Lo ARABIC MATHEMATICAL TAILED YEH
+1EE4B ; Math # Lo ARABIC MATHEMATICAL TAILED LAM
+1EE4D..1EE4F ; Math # Lo [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN
+1EE51..1EE52 ; Math # Lo [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF
+1EE54 ; Math # Lo ARABIC MATHEMATICAL TAILED SHEEN
+1EE57 ; Math # Lo ARABIC MATHEMATICAL TAILED KHAH
+1EE59 ; Math # Lo ARABIC MATHEMATICAL TAILED DAD
+1EE5B ; Math # Lo ARABIC MATHEMATICAL TAILED GHAIN
+1EE5D ; Math # Lo ARABIC MATHEMATICAL TAILED DOTLESS NOON
+1EE5F ; Math # Lo ARABIC MATHEMATICAL TAILED DOTLESS QAF
+1EE61..1EE62 ; Math # Lo [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM
+1EE64 ; Math # Lo ARABIC MATHEMATICAL STRETCHED HEH
+1EE67..1EE6A ; Math # Lo [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF
+1EE6C..1EE72 ; Math # Lo [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF
+1EE74..1EE77 ; Math # Lo [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH
+1EE79..1EE7C ; Math # Lo [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH
+1EE7E ; Math # Lo ARABIC MATHEMATICAL STRETCHED DOTLESS FEH
+1EE80..1EE89 ; Math # Lo [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH
+1EE8B..1EE9B ; Math # Lo [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN
+1EEA1..1EEA3 ; Math # Lo [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL
+1EEA5..1EEA9 ; Math # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH
+1EEAB..1EEBB ; Math # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN
+1EEF0..1EEF1 ; Math # Sm [2] ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL
+
+# Total code points: 2312
+
+# ================================================
+
+# Derived Property: Alphabetic
+# Generated from: Uppercase + Lowercase + Lt + Lm + Lo + Nl + Other_Alphabetic
+
+0041..005A ; Alphabetic # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+0061..007A ; Alphabetic # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+00AA ; Alphabetic # Lo FEMININE ORDINAL INDICATOR
+00B5 ; Alphabetic # L& MICRO SIGN
+00BA ; Alphabetic # Lo MASCULINE ORDINAL INDICATOR
+00C0..00D6 ; Alphabetic # L& [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS
+00D8..00F6 ; Alphabetic # L& [31] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER O WITH DIAERESIS
+00F8..01BA ; Alphabetic # L& [195] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL
+01BB ; Alphabetic # Lo LATIN LETTER TWO WITH STROKE
+01BC..01BF ; Alphabetic # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN
+01C0..01C3 ; Alphabetic # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK
+01C4..0293 ; Alphabetic # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL
+0294 ; Alphabetic # Lo LATIN LETTER GLOTTAL STOP
+0295..02AF ; Alphabetic # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL
+02B0..02C1 ; Alphabetic # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP
+02C6..02D1 ; Alphabetic # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON
+02E0..02E4 ; Alphabetic # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP
+02EC ; Alphabetic # Lm MODIFIER LETTER VOICING
+02EE ; Alphabetic # Lm MODIFIER LETTER DOUBLE APOSTROPHE
+0345 ; Alphabetic # Mn COMBINING GREEK YPOGEGRAMMENI
+0363..036F ; Alphabetic # Mn [13] COMBINING LATIN SMALL LETTER A..COMBINING LATIN SMALL LETTER X
+0370..0373 ; Alphabetic # L& [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI
+0374 ; Alphabetic # Lm GREEK NUMERAL SIGN
+0376..0377 ; Alphabetic # L& [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA
+037A ; Alphabetic # Lm GREEK YPOGEGRAMMENI
+037B..037D ; Alphabetic # L& [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL
+037F ; Alphabetic # L& GREEK CAPITAL LETTER YOT
+0386 ; Alphabetic # L& GREEK CAPITAL LETTER ALPHA WITH TONOS
+0388..038A ; Alphabetic # L& [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS
+038C ; Alphabetic # L& GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E..03A1 ; Alphabetic # L& [20] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER RHO
+03A3..03F5 ; Alphabetic # L& [83] GREEK CAPITAL LETTER SIGMA..GREEK LUNATE EPSILON SYMBOL
+03F7..0481 ; Alphabetic # L& [139] GREEK CAPITAL LETTER SHO..CYRILLIC SMALL LETTER KOPPA
+048A..052F ; Alphabetic # L& [166] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER EL WITH DESCENDER
+0531..0556 ; Alphabetic # L& [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH
+0559 ; Alphabetic # Lm ARMENIAN MODIFIER LETTER LEFT HALF RING
+0560..0588 ; Alphabetic # L& [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE
+05B0..05BD ; Alphabetic # Mn [14] HEBREW POINT SHEVA..HEBREW POINT METEG
+05BF ; Alphabetic # Mn HEBREW POINT RAFE
+05C1..05C2 ; Alphabetic # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT
+05C4..05C5 ; Alphabetic # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT
+05C7 ; Alphabetic # Mn HEBREW POINT QAMATS QATAN
+05D0..05EA ; Alphabetic # Lo [27] HEBREW LETTER ALEF..HEBREW LETTER TAV
+05EF..05F2 ; Alphabetic # Lo [4] HEBREW YOD TRIANGLE..HEBREW LIGATURE YIDDISH DOUBLE YOD
+0610..061A ; Alphabetic # Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA
+0620..063F ; Alphabetic # Lo [32] ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE
+0640 ; Alphabetic # Lm ARABIC TATWEEL
+0641..064A ; Alphabetic # Lo [10] ARABIC LETTER FEH..ARABIC LETTER YEH
+064B..0657 ; Alphabetic # Mn [13] ARABIC FATHATAN..ARABIC INVERTED DAMMA
+0659..065F ; Alphabetic # Mn [7] ARABIC ZWARAKAY..ARABIC WAVY HAMZA BELOW
+066E..066F ; Alphabetic # Lo [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF
+0670 ; Alphabetic # Mn ARABIC LETTER SUPERSCRIPT ALEF
+0671..06D3 ; Alphabetic # Lo [99] ARABIC LETTER ALEF WASLA..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE
+06D5 ; Alphabetic # Lo ARABIC LETTER AE
+06D6..06DC ; Alphabetic # Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN
+06E1..06E4 ; Alphabetic # Mn [4] ARABIC SMALL HIGH DOTLESS HEAD OF KHAH..ARABIC SMALL HIGH MADDA
+06E5..06E6 ; Alphabetic # Lm [2] ARABIC SMALL WAW..ARABIC SMALL YEH
+06E7..06E8 ; Alphabetic # Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON
+06ED ; Alphabetic # Mn ARABIC SMALL LOW MEEM
+06EE..06EF ; Alphabetic # Lo [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V
+06FA..06FC ; Alphabetic # Lo [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW
+06FF ; Alphabetic # Lo ARABIC LETTER HEH WITH INVERTED V
+0710 ; Alphabetic # Lo SYRIAC LETTER ALAPH
+0711 ; Alphabetic # Mn SYRIAC LETTER SUPERSCRIPT ALAPH
+0712..072F ; Alphabetic # Lo [30] SYRIAC LETTER BETH..SYRIAC LETTER PERSIAN DHALATH
+0730..073F ; Alphabetic # Mn [16] SYRIAC PTHAHA ABOVE..SYRIAC RWAHA
+074D..07A5 ; Alphabetic # Lo [89] SYRIAC LETTER SOGDIAN ZHAIN..THAANA LETTER WAAVU
+07A6..07B0 ; Alphabetic # Mn [11] THAANA ABAFILI..THAANA SUKUN
+07B1 ; Alphabetic # Lo THAANA LETTER NAA
+07CA..07EA ; Alphabetic # Lo [33] NKO LETTER A..NKO LETTER JONA RA
+07F4..07F5 ; Alphabetic # Lm [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE
+07FA ; Alphabetic # Lm NKO LAJANYALAN
+0800..0815 ; Alphabetic # Lo [22] SAMARITAN LETTER ALAF..SAMARITAN LETTER TAAF
+0816..0817 ; Alphabetic # Mn [2] SAMARITAN MARK IN..SAMARITAN MARK IN-ALAF
+081A ; Alphabetic # Lm SAMARITAN MODIFIER LETTER EPENTHETIC YUT
+081B..0823 ; Alphabetic # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A
+0824 ; Alphabetic # Lm SAMARITAN MODIFIER LETTER SHORT A
+0825..0827 ; Alphabetic # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U
+0828 ; Alphabetic # Lm SAMARITAN MODIFIER LETTER I
+0829..082C ; Alphabetic # Mn [4] SAMARITAN VOWEL SIGN LONG I..SAMARITAN VOWEL SIGN SUKUN
+0840..0858 ; Alphabetic # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN
+0860..086A ; Alphabetic # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA
+0870..0887 ; Alphabetic # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT
+0889..088E ; Alphabetic # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL
+0897 ; Alphabetic # Mn ARABIC PEPET
+08A0..08C8 ; Alphabetic # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF
+08C9 ; Alphabetic # Lm ARABIC SMALL FARSI YEH
+08D4..08DF ; Alphabetic # Mn [12] ARABIC SMALL HIGH WORD AR-RUB..ARABIC SMALL HIGH WORD WAQFA
+08E3..08E9 ; Alphabetic # Mn [7] ARABIC TURNED DAMMA BELOW..ARABIC CURLY KASRATAN
+08F0..0902 ; Alphabetic # Mn [19] ARABIC OPEN FATHATAN..DEVANAGARI SIGN ANUSVARA
+0903 ; Alphabetic # Mc DEVANAGARI SIGN VISARGA
+0904..0939 ; Alphabetic # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA
+093A ; Alphabetic # Mn DEVANAGARI VOWEL SIGN OE
+093B ; Alphabetic # Mc DEVANAGARI VOWEL SIGN OOE
+093D ; Alphabetic # Lo DEVANAGARI SIGN AVAGRAHA
+093E..0940 ; Alphabetic # Mc [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II
+0941..0948 ; Alphabetic # Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI
+0949..094C ; Alphabetic # Mc [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU
+094E..094F ; Alphabetic # Mc [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW
+0950 ; Alphabetic # Lo DEVANAGARI OM
+0955..0957 ; Alphabetic # Mn [3] DEVANAGARI VOWEL SIGN CANDRA LONG E..DEVANAGARI VOWEL SIGN UUE
+0958..0961 ; Alphabetic # Lo [10] DEVANAGARI LETTER QA..DEVANAGARI LETTER VOCALIC LL
+0962..0963 ; Alphabetic # Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL
+0971 ; Alphabetic # Lm DEVANAGARI SIGN HIGH SPACING DOT
+0972..0980 ; Alphabetic # Lo [15] DEVANAGARI LETTER CANDRA A..BENGALI ANJI
+0981 ; Alphabetic # Mn BENGALI SIGN CANDRABINDU
+0982..0983 ; Alphabetic # Mc [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA
+0985..098C ; Alphabetic # Lo [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L
+098F..0990 ; Alphabetic # Lo [2] BENGALI LETTER E..BENGALI LETTER AI
+0993..09A8 ; Alphabetic # Lo [22] BENGALI LETTER O..BENGALI LETTER NA
+09AA..09B0 ; Alphabetic # Lo [7] BENGALI LETTER PA..BENGALI LETTER RA
+09B2 ; Alphabetic # Lo BENGALI LETTER LA
+09B6..09B9 ; Alphabetic # Lo [4] BENGALI LETTER SHA..BENGALI LETTER HA
+09BD ; Alphabetic # Lo BENGALI SIGN AVAGRAHA
+09BE..09C0 ; Alphabetic # Mc [3] BENGALI VOWEL SIGN AA..BENGALI VOWEL SIGN II
+09C1..09C4 ; Alphabetic # Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR
+09C7..09C8 ; Alphabetic # Mc [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI
+09CB..09CC ; Alphabetic # Mc [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU
+09CE ; Alphabetic # Lo BENGALI LETTER KHANDA TA
+09D7 ; Alphabetic # Mc BENGALI AU LENGTH MARK
+09DC..09DD ; Alphabetic # Lo [2] BENGALI LETTER RRA..BENGALI LETTER RHA
+09DF..09E1 ; Alphabetic # Lo [3] BENGALI LETTER YYA..BENGALI LETTER VOCALIC LL
+09E2..09E3 ; Alphabetic # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL
+09F0..09F1 ; Alphabetic # Lo [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL
+09FC ; Alphabetic # Lo BENGALI LETTER VEDIC ANUSVARA
+0A01..0A02 ; Alphabetic # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI
+0A03 ; Alphabetic # Mc GURMUKHI SIGN VISARGA
+0A05..0A0A ; Alphabetic # Lo [6] GURMUKHI LETTER A..GURMUKHI LETTER UU
+0A0F..0A10 ; Alphabetic # Lo [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI
+0A13..0A28 ; Alphabetic # Lo [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA
+0A2A..0A30 ; Alphabetic # Lo [7] GURMUKHI LETTER PA..GURMUKHI LETTER RA
+0A32..0A33 ; Alphabetic # Lo [2] GURMUKHI LETTER LA..GURMUKHI LETTER LLA
+0A35..0A36 ; Alphabetic # Lo [2] GURMUKHI LETTER VA..GURMUKHI LETTER SHA
+0A38..0A39 ; Alphabetic # Lo [2] GURMUKHI LETTER SA..GURMUKHI LETTER HA
+0A3E..0A40 ; Alphabetic # Mc [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II
+0A41..0A42 ; Alphabetic # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU
+0A47..0A48 ; Alphabetic # Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI
+0A4B..0A4C ; Alphabetic # Mn [2] GURMUKHI VOWEL SIGN OO..GURMUKHI VOWEL SIGN AU
+0A51 ; Alphabetic # Mn GURMUKHI SIGN UDAAT
+0A59..0A5C ; Alphabetic # Lo [4] GURMUKHI LETTER KHHA..GURMUKHI LETTER RRA
+0A5E ; Alphabetic # Lo GURMUKHI LETTER FA
+0A70..0A71 ; Alphabetic # Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK
+0A72..0A74 ; Alphabetic # Lo [3] GURMUKHI IRI..GURMUKHI EK ONKAR
+0A75 ; Alphabetic # Mn GURMUKHI SIGN YAKASH
+0A81..0A82 ; Alphabetic # Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA
+0A83 ; Alphabetic # Mc GUJARATI SIGN VISARGA
+0A85..0A8D ; Alphabetic # Lo [9] GUJARATI LETTER A..GUJARATI VOWEL CANDRA E
+0A8F..0A91 ; Alphabetic # Lo [3] GUJARATI LETTER E..GUJARATI VOWEL CANDRA O
+0A93..0AA8 ; Alphabetic # Lo [22] GUJARATI LETTER O..GUJARATI LETTER NA
+0AAA..0AB0 ; Alphabetic # Lo [7] GUJARATI LETTER PA..GUJARATI LETTER RA
+0AB2..0AB3 ; Alphabetic # Lo [2] GUJARATI LETTER LA..GUJARATI LETTER LLA
+0AB5..0AB9 ; Alphabetic # Lo [5] GUJARATI LETTER VA..GUJARATI LETTER HA
+0ABD ; Alphabetic # Lo GUJARATI SIGN AVAGRAHA
+0ABE..0AC0 ; Alphabetic # Mc [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II
+0AC1..0AC5 ; Alphabetic # Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E
+0AC7..0AC8 ; Alphabetic # Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI
+0AC9 ; Alphabetic # Mc GUJARATI VOWEL SIGN CANDRA O
+0ACB..0ACC ; Alphabetic # Mc [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU
+0AD0 ; Alphabetic # Lo GUJARATI OM
+0AE0..0AE1 ; Alphabetic # Lo [2] GUJARATI LETTER VOCALIC RR..GUJARATI LETTER VOCALIC LL
+0AE2..0AE3 ; Alphabetic # Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL
+0AF9 ; Alphabetic # Lo GUJARATI LETTER ZHA
+0AFA..0AFC ; Alphabetic # Mn [3] GUJARATI SIGN SUKUN..GUJARATI SIGN MADDAH
+0B01 ; Alphabetic # Mn ORIYA SIGN CANDRABINDU
+0B02..0B03 ; Alphabetic # Mc [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA
+0B05..0B0C ; Alphabetic # Lo [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L
+0B0F..0B10 ; Alphabetic # Lo [2] ORIYA LETTER E..ORIYA LETTER AI
+0B13..0B28 ; Alphabetic # Lo [22] ORIYA LETTER O..ORIYA LETTER NA
+0B2A..0B30 ; Alphabetic # Lo [7] ORIYA LETTER PA..ORIYA LETTER RA
+0B32..0B33 ; Alphabetic # Lo [2] ORIYA LETTER LA..ORIYA LETTER LLA
+0B35..0B39 ; Alphabetic # Lo [5] ORIYA LETTER VA..ORIYA LETTER HA
+0B3D ; Alphabetic # Lo ORIYA SIGN AVAGRAHA
+0B3E ; Alphabetic # Mc ORIYA VOWEL SIGN AA
+0B3F ; Alphabetic # Mn ORIYA VOWEL SIGN I
+0B40 ; Alphabetic # Mc ORIYA VOWEL SIGN II
+0B41..0B44 ; Alphabetic # Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR
+0B47..0B48 ; Alphabetic # Mc [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI
+0B4B..0B4C ; Alphabetic # Mc [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU
+0B56 ; Alphabetic # Mn ORIYA AI LENGTH MARK
+0B57 ; Alphabetic # Mc ORIYA AU LENGTH MARK
+0B5C..0B5D ; Alphabetic # Lo [2] ORIYA LETTER RRA..ORIYA LETTER RHA
+0B5F..0B61 ; Alphabetic # Lo [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL
+0B62..0B63 ; Alphabetic # Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL
+0B71 ; Alphabetic # Lo ORIYA LETTER WA
+0B82 ; Alphabetic # Mn TAMIL SIGN ANUSVARA
+0B83 ; Alphabetic # Lo TAMIL SIGN VISARGA
+0B85..0B8A ; Alphabetic # Lo [6] TAMIL LETTER A..TAMIL LETTER UU
+0B8E..0B90 ; Alphabetic # Lo [3] TAMIL LETTER E..TAMIL LETTER AI
+0B92..0B95 ; Alphabetic # Lo [4] TAMIL LETTER O..TAMIL LETTER KA
+0B99..0B9A ; Alphabetic # Lo [2] TAMIL LETTER NGA..TAMIL LETTER CA
+0B9C ; Alphabetic # Lo TAMIL LETTER JA
+0B9E..0B9F ; Alphabetic # Lo [2] TAMIL LETTER NYA..TAMIL LETTER TTA
+0BA3..0BA4 ; Alphabetic # Lo [2] TAMIL LETTER NNA..TAMIL LETTER TA
+0BA8..0BAA ; Alphabetic # Lo [3] TAMIL LETTER NA..TAMIL LETTER PA
+0BAE..0BB9 ; Alphabetic # Lo [12] TAMIL LETTER MA..TAMIL LETTER HA
+0BBE..0BBF ; Alphabetic # Mc [2] TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN I
+0BC0 ; Alphabetic # Mn TAMIL VOWEL SIGN II
+0BC1..0BC2 ; Alphabetic # Mc [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU
+0BC6..0BC8 ; Alphabetic # Mc [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI
+0BCA..0BCC ; Alphabetic # Mc [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU
+0BD0 ; Alphabetic # Lo TAMIL OM
+0BD7 ; Alphabetic # Mc TAMIL AU LENGTH MARK
+0C00 ; Alphabetic # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE
+0C01..0C03 ; Alphabetic # Mc [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA
+0C04 ; Alphabetic # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE
+0C05..0C0C ; Alphabetic # Lo [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L
+0C0E..0C10 ; Alphabetic # Lo [3] TELUGU LETTER E..TELUGU LETTER AI
+0C12..0C28 ; Alphabetic # Lo [23] TELUGU LETTER O..TELUGU LETTER NA
+0C2A..0C39 ; Alphabetic # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA
+0C3D ; Alphabetic # Lo TELUGU SIGN AVAGRAHA
+0C3E..0C40 ; Alphabetic # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II
+0C41..0C44 ; Alphabetic # Mc [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR
+0C46..0C48 ; Alphabetic # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI
+0C4A..0C4C ; Alphabetic # Mn [3] TELUGU VOWEL SIGN O..TELUGU VOWEL SIGN AU
+0C55..0C56 ; Alphabetic # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK
+0C58..0C5A ; Alphabetic # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA
+0C5D ; Alphabetic # Lo TELUGU LETTER NAKAARA POLLU
+0C60..0C61 ; Alphabetic # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL
+0C62..0C63 ; Alphabetic # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL
+0C80 ; Alphabetic # Lo KANNADA SIGN SPACING CANDRABINDU
+0C81 ; Alphabetic # Mn KANNADA SIGN CANDRABINDU
+0C82..0C83 ; Alphabetic # Mc [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA
+0C85..0C8C ; Alphabetic # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L
+0C8E..0C90 ; Alphabetic # Lo [3] KANNADA LETTER E..KANNADA LETTER AI
+0C92..0CA8 ; Alphabetic # Lo [23] KANNADA LETTER O..KANNADA LETTER NA
+0CAA..0CB3 ; Alphabetic # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA
+0CB5..0CB9 ; Alphabetic # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA
+0CBD ; Alphabetic # Lo KANNADA SIGN AVAGRAHA
+0CBE ; Alphabetic # Mc KANNADA VOWEL SIGN AA
+0CBF ; Alphabetic # Mn KANNADA VOWEL SIGN I
+0CC0..0CC4 ; Alphabetic # Mc [5] KANNADA VOWEL SIGN II..KANNADA VOWEL SIGN VOCALIC RR
+0CC6 ; Alphabetic # Mn KANNADA VOWEL SIGN E
+0CC7..0CC8 ; Alphabetic # Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI
+0CCA..0CCB ; Alphabetic # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO
+0CCC ; Alphabetic # Mn KANNADA VOWEL SIGN AU
+0CD5..0CD6 ; Alphabetic # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK
+0CDD..0CDE ; Alphabetic # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA
+0CE0..0CE1 ; Alphabetic # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL
+0CE2..0CE3 ; Alphabetic # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL
+0CF1..0CF2 ; Alphabetic # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA
+0CF3 ; Alphabetic # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT
+0D00..0D01 ; Alphabetic # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU
+0D02..0D03 ; Alphabetic # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA
+0D04..0D0C ; Alphabetic # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L
+0D0E..0D10 ; Alphabetic # Lo [3] MALAYALAM LETTER E..MALAYALAM LETTER AI
+0D12..0D3A ; Alphabetic # Lo [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA
+0D3D ; Alphabetic # Lo MALAYALAM SIGN AVAGRAHA
+0D3E..0D40 ; Alphabetic # Mc [3] MALAYALAM VOWEL SIGN AA..MALAYALAM VOWEL SIGN II
+0D41..0D44 ; Alphabetic # Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR
+0D46..0D48 ; Alphabetic # Mc [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI
+0D4A..0D4C ; Alphabetic # Mc [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU
+0D4E ; Alphabetic # Lo MALAYALAM LETTER DOT REPH
+0D54..0D56 ; Alphabetic # Lo [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL
+0D57 ; Alphabetic # Mc MALAYALAM AU LENGTH MARK
+0D5F..0D61 ; Alphabetic # Lo [3] MALAYALAM LETTER ARCHAIC II..MALAYALAM LETTER VOCALIC LL
+0D62..0D63 ; Alphabetic # Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL
+0D7A..0D7F ; Alphabetic # Lo [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K
+0D81 ; Alphabetic # Mn SINHALA SIGN CANDRABINDU
+0D82..0D83 ; Alphabetic # Mc [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA
+0D85..0D96 ; Alphabetic # Lo [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA
+0D9A..0DB1 ; Alphabetic # Lo [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA
+0DB3..0DBB ; Alphabetic # Lo [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA
+0DBD ; Alphabetic # Lo SINHALA LETTER DANTAJA LAYANNA
+0DC0..0DC6 ; Alphabetic # Lo [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA
+0DCF..0DD1 ; Alphabetic # Mc [3] SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA
+0DD2..0DD4 ; Alphabetic # Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA
+0DD6 ; Alphabetic # Mn SINHALA VOWEL SIGN DIGA PAA-PILLA
+0DD8..0DDF ; Alphabetic # Mc [8] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN GAYANUKITTA
+0DF2..0DF3 ; Alphabetic # Mc [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA
+0E01..0E30 ; Alphabetic # Lo [48] THAI CHARACTER KO KAI..THAI CHARACTER SARA A
+0E31 ; Alphabetic # Mn THAI CHARACTER MAI HAN-AKAT
+0E32..0E33 ; Alphabetic # Lo [2] THAI CHARACTER SARA AA..THAI CHARACTER SARA AM
+0E34..0E3A ; Alphabetic # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU
+0E40..0E45 ; Alphabetic # Lo [6] THAI CHARACTER SARA E..THAI CHARACTER LAKKHANGYAO
+0E46 ; Alphabetic # Lm THAI CHARACTER MAIYAMOK
+0E4D ; Alphabetic # Mn THAI CHARACTER NIKHAHIT
+0E81..0E82 ; Alphabetic # Lo [2] LAO LETTER KO..LAO LETTER KHO SUNG
+0E84 ; Alphabetic # Lo LAO LETTER KHO TAM
+0E86..0E8A ; Alphabetic # Lo [5] LAO LETTER PALI GHA..LAO LETTER SO TAM
+0E8C..0EA3 ; Alphabetic # Lo [24] LAO LETTER PALI JHA..LAO LETTER LO LING
+0EA5 ; Alphabetic # Lo LAO LETTER LO LOOT
+0EA7..0EB0 ; Alphabetic # Lo [10] LAO LETTER WO..LAO VOWEL SIGN A
+0EB1 ; Alphabetic # Mn LAO VOWEL SIGN MAI KAN
+0EB2..0EB3 ; Alphabetic # Lo [2] LAO VOWEL SIGN AA..LAO VOWEL SIGN AM
+0EB4..0EB9 ; Alphabetic # Mn [6] LAO VOWEL SIGN I..LAO VOWEL SIGN UU
+0EBB..0EBC ; Alphabetic # Mn [2] LAO VOWEL SIGN MAI KON..LAO SEMIVOWEL SIGN LO
+0EBD ; Alphabetic # Lo LAO SEMIVOWEL SIGN NYO
+0EC0..0EC4 ; Alphabetic # Lo [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI
+0EC6 ; Alphabetic # Lm LAO KO LA
+0ECD ; Alphabetic # Mn LAO NIGGAHITA
+0EDC..0EDF ; Alphabetic # Lo [4] LAO HO NO..LAO LETTER KHMU NYO
+0F00 ; Alphabetic # Lo TIBETAN SYLLABLE OM
+0F40..0F47 ; Alphabetic # Lo [8] TIBETAN LETTER KA..TIBETAN LETTER JA
+0F49..0F6C ; Alphabetic # Lo [36] TIBETAN LETTER NYA..TIBETAN LETTER RRA
+0F71..0F7E ; Alphabetic # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO
+0F7F ; Alphabetic # Mc TIBETAN SIGN RNAM BCAD
+0F80..0F83 ; Alphabetic # Mn [4] TIBETAN VOWEL SIGN REVERSED I..TIBETAN SIGN SNA LDAN
+0F88..0F8C ; Alphabetic # Lo [5] TIBETAN SIGN LCE TSA CAN..TIBETAN SIGN INVERTED MCHU CAN
+0F8D..0F97 ; Alphabetic # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA
+0F99..0FBC ; Alphabetic # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA
+1000..102A ; Alphabetic # Lo [43] MYANMAR LETTER KA..MYANMAR LETTER AU
+102B..102C ; Alphabetic # Mc [2] MYANMAR VOWEL SIGN TALL AA..MYANMAR VOWEL SIGN AA
+102D..1030 ; Alphabetic # Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU
+1031 ; Alphabetic # Mc MYANMAR VOWEL SIGN E
+1032..1036 ; Alphabetic # Mn [5] MYANMAR VOWEL SIGN AI..MYANMAR SIGN ANUSVARA
+1038 ; Alphabetic # Mc MYANMAR SIGN VISARGA
+103B..103C ; Alphabetic # Mc [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA
+103D..103E ; Alphabetic # Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA
+103F ; Alphabetic # Lo MYANMAR LETTER GREAT SA
+1050..1055 ; Alphabetic # Lo [6] MYANMAR LETTER SHA..MYANMAR LETTER VOCALIC LL
+1056..1057 ; Alphabetic # Mc [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR
+1058..1059 ; Alphabetic # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL
+105A..105D ; Alphabetic # Lo [4] MYANMAR LETTER MON NGA..MYANMAR LETTER MON BBE
+105E..1060 ; Alphabetic # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA
+1061 ; Alphabetic # Lo MYANMAR LETTER SGAW KAREN SHA
+1062..1064 ; Alphabetic # Mc [3] MYANMAR VOWEL SIGN SGAW KAREN EU..MYANMAR TONE MARK SGAW KAREN KE PHO
+1065..1066 ; Alphabetic # Lo [2] MYANMAR LETTER WESTERN PWO KAREN THA..MYANMAR LETTER WESTERN PWO KAREN PWA
+1067..106D ; Alphabetic # Mc [7] MYANMAR VOWEL SIGN WESTERN PWO KAREN EU..MYANMAR SIGN WESTERN PWO KAREN TONE-5
+106E..1070 ; Alphabetic # Lo [3] MYANMAR LETTER EASTERN PWO KAREN NNA..MYANMAR LETTER EASTERN PWO KAREN GHWA
+1071..1074 ; Alphabetic # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE
+1075..1081 ; Alphabetic # Lo [13] MYANMAR LETTER SHAN KA..MYANMAR LETTER SHAN HA
+1082 ; Alphabetic # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA
+1083..1084 ; Alphabetic # Mc [2] MYANMAR VOWEL SIGN SHAN AA..MYANMAR VOWEL SIGN SHAN E
+1085..1086 ; Alphabetic # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y
+1087..108C ; Alphabetic # Mc [6] MYANMAR SIGN SHAN TONE-2..MYANMAR SIGN SHAN COUNCIL TONE-3
+108D ; Alphabetic # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE
+108E ; Alphabetic # Lo MYANMAR LETTER RUMAI PALAUNG FA
+108F ; Alphabetic # Mc MYANMAR SIGN RUMAI PALAUNG TONE-5
+109A..109C ; Alphabetic # Mc [3] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON A
+109D ; Alphabetic # Mn MYANMAR VOWEL SIGN AITON AI
+10A0..10C5 ; Alphabetic # L& [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE
+10C7 ; Alphabetic # L& GEORGIAN CAPITAL LETTER YN
+10CD ; Alphabetic # L& GEORGIAN CAPITAL LETTER AEN
+10D0..10FA ; Alphabetic # L& [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN
+10FC ; Alphabetic # Lm MODIFIER LETTER GEORGIAN NAR
+10FD..10FF ; Alphabetic # L& [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN
+1100..1248 ; Alphabetic # Lo [329] HANGUL CHOSEONG KIYEOK..ETHIOPIC SYLLABLE QWA
+124A..124D ; Alphabetic # Lo [4] ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE
+1250..1256 ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO
+1258 ; Alphabetic # Lo ETHIOPIC SYLLABLE QHWA
+125A..125D ; Alphabetic # Lo [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE
+1260..1288 ; Alphabetic # Lo [41] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA
+128A..128D ; Alphabetic # Lo [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE
+1290..12B0 ; Alphabetic # Lo [33] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA
+12B2..12B5 ; Alphabetic # Lo [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE
+12B8..12BE ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO
+12C0 ; Alphabetic # Lo ETHIOPIC SYLLABLE KXWA
+12C2..12C5 ; Alphabetic # Lo [4] ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE
+12C8..12D6 ; Alphabetic # Lo [15] ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O
+12D8..1310 ; Alphabetic # Lo [57] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA
+1312..1315 ; Alphabetic # Lo [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE
+1318..135A ; Alphabetic # Lo [67] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA
+1380..138F ; Alphabetic # Lo [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE
+13A0..13F5 ; Alphabetic # L& [86] CHEROKEE LETTER A..CHEROKEE LETTER MV
+13F8..13FD ; Alphabetic # L& [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV
+1401..166C ; Alphabetic # Lo [620] CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA
+166F..167F ; Alphabetic # Lo [17] CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W
+1681..169A ; Alphabetic # Lo [26] OGHAM LETTER BEITH..OGHAM LETTER PEITH
+16A0..16EA ; Alphabetic # Lo [75] RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X
+16EE..16F0 ; Alphabetic # Nl [3] RUNIC ARLAUG SYMBOL..RUNIC BELGTHOR SYMBOL
+16F1..16F8 ; Alphabetic # Lo [8] RUNIC LETTER K..RUNIC LETTER FRANKS CASKET AESC
+1700..1711 ; Alphabetic # Lo [18] TAGALOG LETTER A..TAGALOG LETTER HA
+1712..1713 ; Alphabetic # Mn [2] TAGALOG VOWEL SIGN I..TAGALOG VOWEL SIGN U
+171F..1731 ; Alphabetic # Lo [19] TAGALOG LETTER ARCHAIC RA..HANUNOO LETTER HA
+1732..1733 ; Alphabetic # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U
+1740..1751 ; Alphabetic # Lo [18] BUHID LETTER A..BUHID LETTER HA
+1752..1753 ; Alphabetic # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U
+1760..176C ; Alphabetic # Lo [13] TAGBANWA LETTER A..TAGBANWA LETTER YA
+176E..1770 ; Alphabetic # Lo [3] TAGBANWA LETTER LA..TAGBANWA LETTER SA
+1772..1773 ; Alphabetic # Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U
+1780..17B3 ; Alphabetic # Lo [52] KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU
+17B6 ; Alphabetic # Mc KHMER VOWEL SIGN AA
+17B7..17BD ; Alphabetic # Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA
+17BE..17C5 ; Alphabetic # Mc [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU
+17C6 ; Alphabetic # Mn KHMER SIGN NIKAHIT
+17C7..17C8 ; Alphabetic # Mc [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU
+17D7 ; Alphabetic # Lm KHMER SIGN LEK TOO
+17DC ; Alphabetic # Lo KHMER SIGN AVAKRAHASANYA
+1820..1842 ; Alphabetic # Lo [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI
+1843 ; Alphabetic # Lm MONGOLIAN LETTER TODO LONG VOWEL SIGN
+1844..1878 ; Alphabetic # Lo [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS
+1880..1884 ; Alphabetic # Lo [5] MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER ALI GALI INVERTED UBADAMA
+1885..1886 ; Alphabetic # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA
+1887..18A8 ; Alphabetic # Lo [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA
+18A9 ; Alphabetic # Mn MONGOLIAN LETTER ALI GALI DAGALGA
+18AA ; Alphabetic # Lo MONGOLIAN LETTER MANCHU ALI GALI LHA
+18B0..18F5 ; Alphabetic # Lo [70] CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S
+1900..191E ; Alphabetic # Lo [31] LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER TRA
+1920..1922 ; Alphabetic # Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U
+1923..1926 ; Alphabetic # Mc [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU
+1927..1928 ; Alphabetic # Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O
+1929..192B ; Alphabetic # Mc [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA
+1930..1931 ; Alphabetic # Mc [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA
+1932 ; Alphabetic # Mn LIMBU SMALL LETTER ANUSVARA
+1933..1938 ; Alphabetic # Mc [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA
+1950..196D ; Alphabetic # Lo [30] TAI LE LETTER KA..TAI LE LETTER AI
+1970..1974 ; Alphabetic # Lo [5] TAI LE LETTER TONE-2..TAI LE LETTER TONE-6
+1980..19AB ; Alphabetic # Lo [44] NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA
+19B0..19C9 ; Alphabetic # Lo [26] NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2
+1A00..1A16 ; Alphabetic # Lo [23] BUGINESE LETTER KA..BUGINESE LETTER HA
+1A17..1A18 ; Alphabetic # Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U
+1A19..1A1A ; Alphabetic # Mc [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O
+1A1B ; Alphabetic # Mn BUGINESE VOWEL SIGN AE
+1A20..1A54 ; Alphabetic # Lo [53] TAI THAM LETTER HIGH KA..TAI THAM LETTER GREAT SA
+1A55 ; Alphabetic # Mc TAI THAM CONSONANT SIGN MEDIAL RA
+1A56 ; Alphabetic # Mn TAI THAM CONSONANT SIGN MEDIAL LA
+1A57 ; Alphabetic # Mc TAI THAM CONSONANT SIGN LA TANG LAI
+1A58..1A5E ; Alphabetic # Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA
+1A61 ; Alphabetic # Mc TAI THAM VOWEL SIGN A
+1A62 ; Alphabetic # Mn TAI THAM VOWEL SIGN MAI SAT
+1A63..1A64 ; Alphabetic # Mc [2] TAI THAM VOWEL SIGN AA..TAI THAM VOWEL SIGN TALL AA
+1A65..1A6C ; Alphabetic # Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW
+1A6D..1A72 ; Alphabetic # Mc [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI
+1A73..1A74 ; Alphabetic # Mn [2] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN MAI KANG
+1AA7 ; Alphabetic # Lm TAI THAM SIGN MAI YAMOK
+1ABF..1AC0 ; Alphabetic # Mn [2] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER TURNED W BELOW
+1ACC..1ACE ; Alphabetic # Mn [3] COMBINING LATIN SMALL LETTER INSULAR G..COMBINING LATIN SMALL LETTER INSULAR T
+1B00..1B03 ; Alphabetic # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG
+1B04 ; Alphabetic # Mc BALINESE SIGN BISAH
+1B05..1B33 ; Alphabetic # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA
+1B35 ; Alphabetic # Mc BALINESE VOWEL SIGN TEDUNG
+1B36..1B3A ; Alphabetic # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA
+1B3B ; Alphabetic # Mc BALINESE VOWEL SIGN RA REPA TEDUNG
+1B3C ; Alphabetic # Mn BALINESE VOWEL SIGN LA LENGA
+1B3D..1B41 ; Alphabetic # Mc [5] BALINESE VOWEL SIGN LA LENGA TEDUNG..BALINESE VOWEL SIGN TALING REPA TEDUNG
+1B42 ; Alphabetic # Mn BALINESE VOWEL SIGN PEPET
+1B43 ; Alphabetic # Mc BALINESE VOWEL SIGN PEPET TEDUNG
+1B45..1B4C ; Alphabetic # Lo [8] BALINESE LETTER KAF SASAK..BALINESE LETTER ARCHAIC JNYA
+1B80..1B81 ; Alphabetic # Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR
+1B82 ; Alphabetic # Mc SUNDANESE SIGN PANGWISAD
+1B83..1BA0 ; Alphabetic # Lo [30] SUNDANESE LETTER A..SUNDANESE LETTER HA
+1BA1 ; Alphabetic # Mc SUNDANESE CONSONANT SIGN PAMINGKAL
+1BA2..1BA5 ; Alphabetic # Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU
+1BA6..1BA7 ; Alphabetic # Mc [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG
+1BA8..1BA9 ; Alphabetic # Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG
+1BAC..1BAD ; Alphabetic # Mn [2] SUNDANESE CONSONANT SIGN PASANGAN MA..SUNDANESE CONSONANT SIGN PASANGAN WA
+1BAE..1BAF ; Alphabetic # Lo [2] SUNDANESE LETTER KHA..SUNDANESE LETTER SYA
+1BBA..1BE5 ; Alphabetic # Lo [44] SUNDANESE AVAGRAHA..BATAK LETTER U
+1BE7 ; Alphabetic # Mc BATAK VOWEL SIGN E
+1BE8..1BE9 ; Alphabetic # Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE
+1BEA..1BEC ; Alphabetic # Mc [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O
+1BED ; Alphabetic # Mn BATAK VOWEL SIGN KARO O
+1BEE ; Alphabetic # Mc BATAK VOWEL SIGN U
+1BEF..1BF1 ; Alphabetic # Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H
+1C00..1C23 ; Alphabetic # Lo [36] LEPCHA LETTER KA..LEPCHA LETTER A
+1C24..1C2B ; Alphabetic # Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU
+1C2C..1C33 ; Alphabetic # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T
+1C34..1C35 ; Alphabetic # Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG
+1C36 ; Alphabetic # Mn LEPCHA SIGN RAN
+1C4D..1C4F ; Alphabetic # Lo [3] LEPCHA LETTER TTA..LEPCHA LETTER DDA
+1C5A..1C77 ; Alphabetic # Lo [30] OL CHIKI LETTER LA..OL CHIKI LETTER OH
+1C78..1C7D ; Alphabetic # Lm [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD
+1C80..1C8A ; Alphabetic # L& [11] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER TJE
+1C90..1CBA ; Alphabetic # L& [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN
+1CBD..1CBF ; Alphabetic # L& [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
+1CE9..1CEC ; Alphabetic # Lo [4] VEDIC SIGN ANUSVARA ANTARGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL
+1CEE..1CF3 ; Alphabetic # Lo [6] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ROTATED ARDHAVISARGA
+1CF5..1CF6 ; Alphabetic # Lo [2] VEDIC SIGN JIHVAMULIYA..VEDIC SIGN UPADHMANIYA
+1CFA ; Alphabetic # Lo VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA
+1D00..1D2B ; Alphabetic # L& [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL
+1D2C..1D6A ; Alphabetic # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI
+1D6B..1D77 ; Alphabetic # L& [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G
+1D78 ; Alphabetic # Lm MODIFIER LETTER CYRILLIC EN
+1D79..1D9A ; Alphabetic # L& [34] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK
+1D9B..1DBF ; Alphabetic # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA
+1DD3..1DF4 ; Alphabetic # Mn [34] COMBINING LATIN SMALL LETTER FLATTENED OPEN A ABOVE..COMBINING LATIN SMALL LETTER U WITH DIAERESIS
+1E00..1F15 ; Alphabetic # L& [278] LATIN CAPITAL LETTER A WITH RING BELOW..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA
+1F18..1F1D ; Alphabetic # L& [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F20..1F45 ; Alphabetic # L& [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA
+1F48..1F4D ; Alphabetic # L& [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F50..1F57 ; Alphabetic # L& [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F59 ; Alphabetic # L& GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B ; Alphabetic # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D ; Alphabetic # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F..1F7D ; Alphabetic # L& [31] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER OMEGA WITH OXIA
+1F80..1FB4 ; Alphabetic # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6..1FBC ; Alphabetic # L& [7] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBE ; Alphabetic # L& GREEK PROSGEGRAMMENI
+1FC2..1FC4 ; Alphabetic # L& [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6..1FCC ; Alphabetic # L& [7] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FD0..1FD3 ; Alphabetic # L& [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6..1FDB ; Alphabetic # L& [6] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH OXIA
+1FE0..1FEC ; Alphabetic # L& [13] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA
+1FF2..1FF4 ; Alphabetic # L& [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6..1FFC ; Alphabetic # L& [7] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+2071 ; Alphabetic # Lm SUPERSCRIPT LATIN SMALL LETTER I
+207F ; Alphabetic # Lm SUPERSCRIPT LATIN SMALL LETTER N
+2090..209C ; Alphabetic # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T
+2102 ; Alphabetic # L& DOUBLE-STRUCK CAPITAL C
+2107 ; Alphabetic # L& EULER CONSTANT
+210A..2113 ; Alphabetic # L& [10] SCRIPT SMALL G..SCRIPT SMALL L
+2115 ; Alphabetic # L& DOUBLE-STRUCK CAPITAL N
+2119..211D ; Alphabetic # L& [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R
+2124 ; Alphabetic # L& DOUBLE-STRUCK CAPITAL Z
+2126 ; Alphabetic # L& OHM SIGN
+2128 ; Alphabetic # L& BLACK-LETTER CAPITAL Z
+212A..212D ; Alphabetic # L& [4] KELVIN SIGN..BLACK-LETTER CAPITAL C
+212F..2134 ; Alphabetic # L& [6] SCRIPT SMALL E..SCRIPT SMALL O
+2135..2138 ; Alphabetic # Lo [4] ALEF SYMBOL..DALET SYMBOL
+2139 ; Alphabetic # L& INFORMATION SOURCE
+213C..213F ; Alphabetic # L& [4] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK CAPITAL PI
+2145..2149 ; Alphabetic # L& [5] DOUBLE-STRUCK ITALIC CAPITAL D..DOUBLE-STRUCK ITALIC SMALL J
+214E ; Alphabetic # L& TURNED SMALL F
+2160..2182 ; Alphabetic # Nl [35] ROMAN NUMERAL ONE..ROMAN NUMERAL TEN THOUSAND
+2183..2184 ; Alphabetic # L& [2] ROMAN NUMERAL REVERSED ONE HUNDRED..LATIN SMALL LETTER REVERSED C
+2185..2188 ; Alphabetic # Nl [4] ROMAN NUMERAL SIX LATE FORM..ROMAN NUMERAL ONE HUNDRED THOUSAND
+24B6..24E9 ; Alphabetic # So [52] CIRCLED LATIN CAPITAL LETTER A..CIRCLED LATIN SMALL LETTER Z
+2C00..2C7B ; Alphabetic # L& [124] GLAGOLITIC CAPITAL LETTER AZU..LATIN LETTER SMALL CAPITAL TURNED E
+2C7C..2C7D ; Alphabetic # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V
+2C7E..2CE4 ; Alphabetic # L& [103] LATIN CAPITAL LETTER S WITH SWASH TAIL..COPTIC SYMBOL KAI
+2CEB..2CEE ; Alphabetic # L& [4] COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI..COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA
+2CF2..2CF3 ; Alphabetic # L& [2] COPTIC CAPITAL LETTER BOHAIRIC KHEI..COPTIC SMALL LETTER BOHAIRIC KHEI
+2D00..2D25 ; Alphabetic # L& [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE
+2D27 ; Alphabetic # L& GEORGIAN SMALL LETTER YN
+2D2D ; Alphabetic # L& GEORGIAN SMALL LETTER AEN
+2D30..2D67 ; Alphabetic # Lo [56] TIFINAGH LETTER YA..TIFINAGH LETTER YO
+2D6F ; Alphabetic # Lm TIFINAGH MODIFIER LETTER LABIALIZATION MARK
+2D80..2D96 ; Alphabetic # Lo [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE
+2DA0..2DA6 ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO
+2DA8..2DAE ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO
+2DB0..2DB6 ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO
+2DB8..2DBE ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO
+2DC0..2DC6 ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO
+2DC8..2DCE ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO
+2DD0..2DD6 ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO
+2DD8..2DDE ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO
+2DE0..2DFF ; Alphabetic # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS
+2E2F ; Alphabetic # Lm VERTICAL TILDE
+3005 ; Alphabetic # Lm IDEOGRAPHIC ITERATION MARK
+3006 ; Alphabetic # Lo IDEOGRAPHIC CLOSING MARK
+3007 ; Alphabetic # Nl IDEOGRAPHIC NUMBER ZERO
+3021..3029 ; Alphabetic # Nl [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE
+3031..3035 ; Alphabetic # Lm [5] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT MARK LOWER HALF
+3038..303A ; Alphabetic # Nl [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY
+303B ; Alphabetic # Lm VERTICAL IDEOGRAPHIC ITERATION MARK
+303C ; Alphabetic # Lo MASU MARK
+3041..3096 ; Alphabetic # Lo [86] HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMALL KE
+309D..309E ; Alphabetic # Lm [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK
+309F ; Alphabetic # Lo HIRAGANA DIGRAPH YORI
+30A1..30FA ; Alphabetic # Lo [90] KATAKANA LETTER SMALL A..KATAKANA LETTER VO
+30FC..30FE ; Alphabetic # Lm [3] KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK
+30FF ; Alphabetic # Lo KATAKANA DIGRAPH KOTO
+3105..312F ; Alphabetic # Lo [43] BOPOMOFO LETTER B..BOPOMOFO LETTER NN
+3131..318E ; Alphabetic # Lo [94] HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE
+31A0..31BF ; Alphabetic # Lo [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH
+31F0..31FF ; Alphabetic # Lo [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO
+3400..4DBF ; Alphabetic # Lo [6592] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DBF
+4E00..A014 ; Alphabetic # Lo [21013] CJK UNIFIED IDEOGRAPH-4E00..YI SYLLABLE E
+A015 ; Alphabetic # Lm YI SYLLABLE WU
+A016..A48C ; Alphabetic # Lo [1143] YI SYLLABLE BIT..YI SYLLABLE YYR
+A4D0..A4F7 ; Alphabetic # Lo [40] LISU LETTER BA..LISU LETTER OE
+A4F8..A4FD ; Alphabetic # Lm [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU
+A500..A60B ; Alphabetic # Lo [268] VAI SYLLABLE EE..VAI SYLLABLE NG
+A60C ; Alphabetic # Lm VAI SYLLABLE LENGTHENER
+A610..A61F ; Alphabetic # Lo [16] VAI SYLLABLE NDOLE FA..VAI SYMBOL JONG
+A62A..A62B ; Alphabetic # Lo [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO
+A640..A66D ; Alphabetic # L& [46] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER DOUBLE MONOCULAR O
+A66E ; Alphabetic # Lo CYRILLIC LETTER MULTIOCULAR O
+A674..A67B ; Alphabetic # Mn [8] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC LETTER OMEGA
+A67F ; Alphabetic # Lm CYRILLIC PAYEROK
+A680..A69B ; Alphabetic # L& [28] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER CROSSED O
+A69C..A69D ; Alphabetic # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN
+A69E..A69F ; Alphabetic # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E
+A6A0..A6E5 ; Alphabetic # Lo [70] BAMUM LETTER A..BAMUM LETTER KI
+A6E6..A6EF ; Alphabetic # Nl [10] BAMUM LETTER MO..BAMUM LETTER KOGHOM
+A717..A71F ; Alphabetic # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK
+A722..A76F ; Alphabetic # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CON
+A770 ; Alphabetic # Lm MODIFIER LETTER US
+A771..A787 ; Alphabetic # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T
+A788 ; Alphabetic # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT
+A78B..A78E ; Alphabetic # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT
+A78F ; Alphabetic # Lo LATIN LETTER SINOLOGICAL DOT
+A790..A7CD ; Alphabetic # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE
+A7D0..A7D1 ; Alphabetic # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G
+A7D3 ; Alphabetic # L& LATIN SMALL LETTER DOUBLE THORN
+A7D5..A7DC ; Alphabetic # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE
+A7F2..A7F4 ; Alphabetic # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q
+A7F5..A7F6 ; Alphabetic # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H
+A7F7 ; Alphabetic # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I
+A7F8..A7F9 ; Alphabetic # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE
+A7FA ; Alphabetic # L& LATIN LETTER SMALL CAPITAL TURNED M
+A7FB..A801 ; Alphabetic # Lo [7] LATIN EPIGRAPHIC LETTER REVERSED F..SYLOTI NAGRI LETTER I
+A802 ; Alphabetic # Mn SYLOTI NAGRI SIGN DVISVARA
+A803..A805 ; Alphabetic # Lo [3] SYLOTI NAGRI LETTER U..SYLOTI NAGRI LETTER O
+A807..A80A ; Alphabetic # Lo [4] SYLOTI NAGRI LETTER KO..SYLOTI NAGRI LETTER GHO
+A80B ; Alphabetic # Mn SYLOTI NAGRI SIGN ANUSVARA
+A80C..A822 ; Alphabetic # Lo [23] SYLOTI NAGRI LETTER CO..SYLOTI NAGRI LETTER HO
+A823..A824 ; Alphabetic # Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I
+A825..A826 ; Alphabetic # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E
+A827 ; Alphabetic # Mc SYLOTI NAGRI VOWEL SIGN OO
+A840..A873 ; Alphabetic # Lo [52] PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU
+A880..A881 ; Alphabetic # Mc [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA
+A882..A8B3 ; Alphabetic # Lo [50] SAURASHTRA LETTER A..SAURASHTRA LETTER LLA
+A8B4..A8C3 ; Alphabetic # Mc [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU
+A8C5 ; Alphabetic # Mn SAURASHTRA SIGN CANDRABINDU
+A8F2..A8F7 ; Alphabetic # Lo [6] DEVANAGARI SIGN SPACING CANDRABINDU..DEVANAGARI SIGN CANDRABINDU AVAGRAHA
+A8FB ; Alphabetic # Lo DEVANAGARI HEADSTROKE
+A8FD..A8FE ; Alphabetic # Lo [2] DEVANAGARI JAIN OM..DEVANAGARI LETTER AY
+A8FF ; Alphabetic # Mn DEVANAGARI VOWEL SIGN AY
+A90A..A925 ; Alphabetic # Lo [28] KAYAH LI LETTER KA..KAYAH LI LETTER OO
+A926..A92A ; Alphabetic # Mn [5] KAYAH LI VOWEL UE..KAYAH LI VOWEL O
+A930..A946 ; Alphabetic # Lo [23] REJANG LETTER KA..REJANG LETTER A
+A947..A951 ; Alphabetic # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R
+A952 ; Alphabetic # Mc REJANG CONSONANT SIGN H
+A960..A97C ; Alphabetic # Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH
+A980..A982 ; Alphabetic # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR
+A983 ; Alphabetic # Mc JAVANESE SIGN WIGNYAN
+A984..A9B2 ; Alphabetic # Lo [47] JAVANESE LETTER A..JAVANESE LETTER HA
+A9B4..A9B5 ; Alphabetic # Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG
+A9B6..A9B9 ; Alphabetic # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT
+A9BA..A9BB ; Alphabetic # Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE
+A9BC..A9BD ; Alphabetic # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET
+A9BE..A9BF ; Alphabetic # Mc [2] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE CONSONANT SIGN CAKRA
+A9CF ; Alphabetic # Lm JAVANESE PANGRANGKEP
+A9E0..A9E4 ; Alphabetic # Lo [5] MYANMAR LETTER SHAN GHA..MYANMAR LETTER SHAN BHA
+A9E5 ; Alphabetic # Mn MYANMAR SIGN SHAN SAW
+A9E6 ; Alphabetic # Lm MYANMAR MODIFIER LETTER SHAN REDUPLICATION
+A9E7..A9EF ; Alphabetic # Lo [9] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING NNA
+A9FA..A9FE ; Alphabetic # Lo [5] MYANMAR LETTER TAI LAING LLA..MYANMAR LETTER TAI LAING BHA
+AA00..AA28 ; Alphabetic # Lo [41] CHAM LETTER A..CHAM LETTER HA
+AA29..AA2E ; Alphabetic # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE
+AA2F..AA30 ; Alphabetic # Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI
+AA31..AA32 ; Alphabetic # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE
+AA33..AA34 ; Alphabetic # Mc [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA
+AA35..AA36 ; Alphabetic # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA
+AA40..AA42 ; Alphabetic # Lo [3] CHAM LETTER FINAL K..CHAM LETTER FINAL NG
+AA43 ; Alphabetic # Mn CHAM CONSONANT SIGN FINAL NG
+AA44..AA4B ; Alphabetic # Lo [8] CHAM LETTER FINAL CH..CHAM LETTER FINAL SS
+AA4C ; Alphabetic # Mn CHAM CONSONANT SIGN FINAL M
+AA4D ; Alphabetic # Mc CHAM CONSONANT SIGN FINAL H
+AA60..AA6F ; Alphabetic # Lo [16] MYANMAR LETTER KHAMTI GA..MYANMAR LETTER KHAMTI FA
+AA70 ; Alphabetic # Lm MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION
+AA71..AA76 ; Alphabetic # Lo [6] MYANMAR LETTER KHAMTI XA..MYANMAR LOGOGRAM KHAMTI HM
+AA7A ; Alphabetic # Lo MYANMAR LETTER AITON RA
+AA7B ; Alphabetic # Mc MYANMAR SIGN PAO KAREN TONE
+AA7C ; Alphabetic # Mn MYANMAR SIGN TAI LAING TONE-2
+AA7D ; Alphabetic # Mc MYANMAR SIGN TAI LAING TONE-5
+AA7E..AAAF ; Alphabetic # Lo [50] MYANMAR LETTER SHWE PALAUNG CHA..TAI VIET LETTER HIGH O
+AAB0 ; Alphabetic # Mn TAI VIET MAI KANG
+AAB1 ; Alphabetic # Lo TAI VIET VOWEL AA
+AAB2..AAB4 ; Alphabetic # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U
+AAB5..AAB6 ; Alphabetic # Lo [2] TAI VIET VOWEL E..TAI VIET VOWEL O
+AAB7..AAB8 ; Alphabetic # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA
+AAB9..AABD ; Alphabetic # Lo [5] TAI VIET VOWEL UEA..TAI VIET VOWEL AN
+AABE ; Alphabetic # Mn TAI VIET VOWEL AM
+AAC0 ; Alphabetic # Lo TAI VIET TONE MAI NUENG
+AAC2 ; Alphabetic # Lo TAI VIET TONE MAI SONG
+AADB..AADC ; Alphabetic # Lo [2] TAI VIET SYMBOL KON..TAI VIET SYMBOL NUENG
+AADD ; Alphabetic # Lm TAI VIET SYMBOL SAM
+AAE0..AAEA ; Alphabetic # Lo [11] MEETEI MAYEK LETTER E..MEETEI MAYEK LETTER SSA
+AAEB ; Alphabetic # Mc MEETEI MAYEK VOWEL SIGN II
+AAEC..AAED ; Alphabetic # Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI
+AAEE..AAEF ; Alphabetic # Mc [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU
+AAF2 ; Alphabetic # Lo MEETEI MAYEK ANJI
+AAF3..AAF4 ; Alphabetic # Lm [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK
+AAF5 ; Alphabetic # Mc MEETEI MAYEK VOWEL SIGN VISARGA
+AB01..AB06 ; Alphabetic # Lo [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO
+AB09..AB0E ; Alphabetic # Lo [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO
+AB11..AB16 ; Alphabetic # Lo [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO
+AB20..AB26 ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO
+AB28..AB2E ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO
+AB30..AB5A ; Alphabetic # L& [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG
+AB5C..AB5F ; Alphabetic # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK
+AB60..AB68 ; Alphabetic # L& [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE
+AB69 ; Alphabetic # Lm MODIFIER LETTER SMALL TURNED W
+AB70..ABBF ; Alphabetic # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA
+ABC0..ABE2 ; Alphabetic # Lo [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM
+ABE3..ABE4 ; Alphabetic # Mc [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP
+ABE5 ; Alphabetic # Mn MEETEI MAYEK VOWEL SIGN ANAP
+ABE6..ABE7 ; Alphabetic # Mc [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP
+ABE8 ; Alphabetic # Mn MEETEI MAYEK VOWEL SIGN UNAP
+ABE9..ABEA ; Alphabetic # Mc [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG
+AC00..D7A3 ; Alphabetic # Lo [11172] HANGUL SYLLABLE GA..HANGUL SYLLABLE HIH
+D7B0..D7C6 ; Alphabetic # Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E
+D7CB..D7FB ; Alphabetic # Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH
+F900..FA6D ; Alphabetic # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D
+FA70..FAD9 ; Alphabetic # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9
+FB00..FB06 ; Alphabetic # L& [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST
+FB13..FB17 ; Alphabetic # L& [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH
+FB1D ; Alphabetic # Lo HEBREW LETTER YOD WITH HIRIQ
+FB1E ; Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
+FB1F..FB28 ; Alphabetic # Lo [10] HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER WIDE TAV
+FB2A..FB36 ; Alphabetic # Lo [13] HEBREW LETTER SHIN WITH SHIN DOT..HEBREW LETTER ZAYIN WITH DAGESH
+FB38..FB3C ; Alphabetic # Lo [5] HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH
+FB3E ; Alphabetic # Lo HEBREW LETTER MEM WITH DAGESH
+FB40..FB41 ; Alphabetic # Lo [2] HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH
+FB43..FB44 ; Alphabetic # Lo [2] HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH
+FB46..FBB1 ; Alphabetic # Lo [108] HEBREW LETTER TSADI WITH DAGESH..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM
+FBD3..FD3D ; Alphabetic # Lo [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM
+FD50..FD8F ; Alphabetic # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM
+FD92..FDC7 ; Alphabetic # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM
+FDF0..FDFB ; Alphabetic # Lo [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU
+FE70..FE74 ; Alphabetic # Lo [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN ISOLATED FORM
+FE76..FEFC ; Alphabetic # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM
+FF21..FF3A ; Alphabetic # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z
+FF41..FF5A ; Alphabetic # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z
+FF66..FF6F ; Alphabetic # Lo [10] HALFWIDTH KATAKANA LETTER WO..HALFWIDTH KATAKANA LETTER SMALL TU
+FF70 ; Alphabetic # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK
+FF71..FF9D ; Alphabetic # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAKANA LETTER N
+FF9E..FF9F ; Alphabetic # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
+FFA0..FFBE ; Alphabetic # Lo [31] HALFWIDTH HANGUL FILLER..HALFWIDTH HANGUL LETTER HIEUH
+FFC2..FFC7 ; Alphabetic # Lo [6] HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E
+FFCA..FFCF ; Alphabetic # Lo [6] HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE
+FFD2..FFD7 ; Alphabetic # Lo [6] HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU
+FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I
+10000..1000B ; Alphabetic # Lo [12] LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE
+1000D..10026 ; Alphabetic # Lo [26] LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO
+10028..1003A ; Alphabetic # Lo [19] LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO
+1003C..1003D ; Alphabetic # Lo [2] LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE
+1003F..1004D ; Alphabetic # Lo [15] LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO
+10050..1005D ; Alphabetic # Lo [14] LINEAR B SYMBOL B018..LINEAR B SYMBOL B089
+10080..100FA ; Alphabetic # Lo [123] LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305
+10140..10174 ; Alphabetic # Nl [53] GREEK ACROPHONIC ATTIC ONE QUARTER..GREEK ACROPHONIC STRATIAN FIFTY MNAS
+10280..1029C ; Alphabetic # Lo [29] LYCIAN LETTER A..LYCIAN LETTER X
+102A0..102D0 ; Alphabetic # Lo [49] CARIAN LETTER A..CARIAN LETTER UUU3
+10300..1031F ; Alphabetic # Lo [32] OLD ITALIC LETTER A..OLD ITALIC LETTER ESS
+1032D..10340 ; Alphabetic # Lo [20] OLD ITALIC LETTER YE..GOTHIC LETTER PAIRTHRA
+10341 ; Alphabetic # Nl GOTHIC LETTER NINETY
+10342..10349 ; Alphabetic # Lo [8] GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL
+1034A ; Alphabetic # Nl GOTHIC LETTER NINE HUNDRED
+10350..10375 ; Alphabetic # Lo [38] OLD PERMIC LETTER AN..OLD PERMIC LETTER IA
+10376..1037A ; Alphabetic # Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII
+10380..1039D ; Alphabetic # Lo [30] UGARITIC LETTER ALPA..UGARITIC LETTER SSU
+103A0..103C3 ; Alphabetic # Lo [36] OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA
+103C8..103CF ; Alphabetic # Lo [8] OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH
+103D1..103D5 ; Alphabetic # Nl [5] OLD PERSIAN NUMBER ONE..OLD PERSIAN NUMBER HUNDRED
+10400..1044F ; Alphabetic # L& [80] DESERET CAPITAL LETTER LONG I..DESERET SMALL LETTER EW
+10450..1049D ; Alphabetic # Lo [78] SHAVIAN LETTER PEEP..OSMANYA LETTER OO
+104B0..104D3 ; Alphabetic # L& [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA
+104D8..104FB ; Alphabetic # L& [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA
+10500..10527 ; Alphabetic # Lo [40] ELBASAN LETTER A..ELBASAN LETTER KHE
+10530..10563 ; Alphabetic # Lo [52] CAUCASIAN ALBANIAN LETTER ALT..CAUCASIAN ALBANIAN LETTER KIW
+10570..1057A ; Alphabetic # L& [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA
+1057C..1058A ; Alphabetic # L& [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE
+1058C..10592 ; Alphabetic # L& [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE
+10594..10595 ; Alphabetic # L& [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE
+10597..105A1 ; Alphabetic # L& [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA
+105A3..105B1 ; Alphabetic # L& [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE
+105B3..105B9 ; Alphabetic # L& [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE
+105BB..105BC ; Alphabetic # L& [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE
+105C0..105F3 ; Alphabetic # Lo [52] TODHRI LETTER A..TODHRI LETTER OO
+10600..10736 ; Alphabetic # Lo [311] LINEAR A SIGN AB001..LINEAR A SIGN A664
+10740..10755 ; Alphabetic # Lo [22] LINEAR A SIGN A701 A..LINEAR A SIGN A732 JE
+10760..10767 ; Alphabetic # Lo [8] LINEAR A SIGN A800..LINEAR A SIGN A807
+10780..10785 ; Alphabetic # Lm [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK
+10787..107B0 ; Alphabetic # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK
+107B2..107BA ; Alphabetic # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL
+10800..10805 ; Alphabetic # Lo [6] CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA
+10808 ; Alphabetic # Lo CYPRIOT SYLLABLE JO
+1080A..10835 ; Alphabetic # Lo [44] CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO
+10837..10838 ; Alphabetic # Lo [2] CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE
+1083C ; Alphabetic # Lo CYPRIOT SYLLABLE ZA
+1083F..10855 ; Alphabetic # Lo [23] CYPRIOT SYLLABLE ZO..IMPERIAL ARAMAIC LETTER TAW
+10860..10876 ; Alphabetic # Lo [23] PALMYRENE LETTER ALEPH..PALMYRENE LETTER TAW
+10880..1089E ; Alphabetic # Lo [31] NABATAEAN LETTER FINAL ALEPH..NABATAEAN LETTER TAW
+108E0..108F2 ; Alphabetic # Lo [19] HATRAN LETTER ALEPH..HATRAN LETTER QOPH
+108F4..108F5 ; Alphabetic # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW
+10900..10915 ; Alphabetic # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU
+10920..10939 ; Alphabetic # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C
+10980..109B7 ; Alphabetic # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA
+109BE..109BF ; Alphabetic # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN
+10A00 ; Alphabetic # Lo KHAROSHTHI LETTER A
+10A01..10A03 ; Alphabetic # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R
+10A05..10A06 ; Alphabetic # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O
+10A0C..10A0F ; Alphabetic # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA
+10A10..10A13 ; Alphabetic # Lo [4] KHAROSHTHI LETTER KA..KHAROSHTHI LETTER GHA
+10A15..10A17 ; Alphabetic # Lo [3] KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA
+10A19..10A35 ; Alphabetic # Lo [29] KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER VHA
+10A60..10A7C ; Alphabetic # Lo [29] OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH
+10A80..10A9C ; Alphabetic # Lo [29] OLD NORTH ARABIAN LETTER HEH..OLD NORTH ARABIAN LETTER ZAH
+10AC0..10AC7 ; Alphabetic # Lo [8] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER WAW
+10AC9..10AE4 ; Alphabetic # Lo [28] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER TAW
+10B00..10B35 ; Alphabetic # Lo [54] AVESTAN LETTER A..AVESTAN LETTER HE
+10B40..10B55 ; Alphabetic # Lo [22] INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW
+10B60..10B72 ; Alphabetic # Lo [19] INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW
+10B80..10B91 ; Alphabetic # Lo [18] PSALTER PAHLAVI LETTER ALEPH..PSALTER PAHLAVI LETTER TAW
+10C00..10C48 ; Alphabetic # Lo [73] OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH
+10C80..10CB2 ; Alphabetic # L& [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US
+10CC0..10CF2 ; Alphabetic # L& [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US
+10D00..10D23 ; Alphabetic # Lo [36] HANIFI ROHINGYA LETTER A..HANIFI ROHINGYA MARK NA KHONNA
+10D24..10D27 ; Alphabetic # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI
+10D4A..10D4D ; Alphabetic # Lo [4] GARAY VOWEL SIGN A..GARAY VOWEL SIGN EE
+10D4E ; Alphabetic # Lm GARAY VOWEL LENGTH MARK
+10D4F ; Alphabetic # Lo GARAY SUKUN
+10D50..10D65 ; Alphabetic # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA
+10D69 ; Alphabetic # Mn GARAY VOWEL SIGN E
+10D6F ; Alphabetic # Lm GARAY REDUPLICATION MARK
+10D70..10D85 ; Alphabetic # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA
+10E80..10EA9 ; Alphabetic # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET
+10EAB..10EAC ; Alphabetic # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK
+10EB0..10EB1 ; Alphabetic # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE
+10EC2..10EC4 ; Alphabetic # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW
+10EFC ; Alphabetic # Mn ARABIC COMBINING ALEF OVERLAY
+10F00..10F1C ; Alphabetic # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL
+10F27 ; Alphabetic # Lo OLD SOGDIAN LIGATURE AYIN-DALETH
+10F30..10F45 ; Alphabetic # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN
+10F70..10F81 ; Alphabetic # Lo [18] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER LESH
+10FB0..10FC4 ; Alphabetic # Lo [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW
+10FE0..10FF6 ; Alphabetic # Lo [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH
+11000 ; Alphabetic # Mc BRAHMI SIGN CANDRABINDU
+11001 ; Alphabetic # Mn BRAHMI SIGN ANUSVARA
+11002 ; Alphabetic # Mc BRAHMI SIGN VISARGA
+11003..11037 ; Alphabetic # Lo [53] BRAHMI SIGN JIHVAMULIYA..BRAHMI LETTER OLD TAMIL NNNA
+11038..11045 ; Alphabetic # Mn [14] BRAHMI VOWEL SIGN AA..BRAHMI VOWEL SIGN AU
+11071..11072 ; Alphabetic # Lo [2] BRAHMI LETTER OLD TAMIL SHORT E..BRAHMI LETTER OLD TAMIL SHORT O
+11073..11074 ; Alphabetic # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O
+11075 ; Alphabetic # Lo BRAHMI LETTER OLD TAMIL LLA
+11080..11081 ; Alphabetic # Mn [2] KAITHI SIGN CANDRABINDU..KAITHI SIGN ANUSVARA
+11082 ; Alphabetic # Mc KAITHI SIGN VISARGA
+11083..110AF ; Alphabetic # Lo [45] KAITHI LETTER A..KAITHI LETTER HA
+110B0..110B2 ; Alphabetic # Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II
+110B3..110B6 ; Alphabetic # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI
+110B7..110B8 ; Alphabetic # Mc [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU
+110C2 ; Alphabetic # Mn KAITHI VOWEL SIGN VOCALIC R
+110D0..110E8 ; Alphabetic # Lo [25] SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE
+11100..11102 ; Alphabetic # Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA
+11103..11126 ; Alphabetic # Lo [36] CHAKMA LETTER AA..CHAKMA LETTER HAA
+11127..1112B ; Alphabetic # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU
+1112C ; Alphabetic # Mc CHAKMA VOWEL SIGN E
+1112D..11132 ; Alphabetic # Mn [6] CHAKMA VOWEL SIGN AI..CHAKMA AU MARK
+11144 ; Alphabetic # Lo CHAKMA LETTER LHAA
+11145..11146 ; Alphabetic # Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI
+11147 ; Alphabetic # Lo CHAKMA LETTER VAA
+11150..11172 ; Alphabetic # Lo [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA
+11176 ; Alphabetic # Lo MAHAJANI LIGATURE SHRI
+11180..11181 ; Alphabetic # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA
+11182 ; Alphabetic # Mc SHARADA SIGN VISARGA
+11183..111B2 ; Alphabetic # Lo [48] SHARADA LETTER A..SHARADA LETTER HA
+111B3..111B5 ; Alphabetic # Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II
+111B6..111BE ; Alphabetic # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O
+111BF ; Alphabetic # Mc SHARADA VOWEL SIGN AU
+111C1..111C4 ; Alphabetic # Lo [4] SHARADA SIGN AVAGRAHA..SHARADA OM
+111CE ; Alphabetic # Mc SHARADA VOWEL SIGN PRISHTHAMATRA E
+111CF ; Alphabetic # Mn SHARADA SIGN INVERTED CANDRABINDU
+111DA ; Alphabetic # Lo SHARADA EKAM
+111DC ; Alphabetic # Lo SHARADA HEADSTROKE
+11200..11211 ; Alphabetic # Lo [18] KHOJKI LETTER A..KHOJKI LETTER JJA
+11213..1122B ; Alphabetic # Lo [25] KHOJKI LETTER NYA..KHOJKI LETTER LLA
+1122C..1122E ; Alphabetic # Mc [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II
+1122F..11231 ; Alphabetic # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI
+11232..11233 ; Alphabetic # Mc [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU
+11234 ; Alphabetic # Mn KHOJKI SIGN ANUSVARA
+11237 ; Alphabetic # Mn KHOJKI SIGN SHADDA
+1123E ; Alphabetic # Mn KHOJKI SIGN SUKUN
+1123F..11240 ; Alphabetic # Lo [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I
+11241 ; Alphabetic # Mn KHOJKI VOWEL SIGN VOCALIC R
+11280..11286 ; Alphabetic # Lo [7] MULTANI LETTER A..MULTANI LETTER GA
+11288 ; Alphabetic # Lo MULTANI LETTER GHA
+1128A..1128D ; Alphabetic # Lo [4] MULTANI LETTER CA..MULTANI LETTER JJA
+1128F..1129D ; Alphabetic # Lo [15] MULTANI LETTER NYA..MULTANI LETTER BA
+1129F..112A8 ; Alphabetic # Lo [10] MULTANI LETTER BHA..MULTANI LETTER RHA
+112B0..112DE ; Alphabetic # Lo [47] KHUDAWADI LETTER A..KHUDAWADI LETTER HA
+112DF ; Alphabetic # Mn KHUDAWADI SIGN ANUSVARA
+112E0..112E2 ; Alphabetic # Mc [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II
+112E3..112E8 ; Alphabetic # Mn [6] KHUDAWADI VOWEL SIGN U..KHUDAWADI VOWEL SIGN AU
+11300..11301 ; Alphabetic # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU
+11302..11303 ; Alphabetic # Mc [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA
+11305..1130C ; Alphabetic # Lo [8] GRANTHA LETTER A..GRANTHA LETTER VOCALIC L
+1130F..11310 ; Alphabetic # Lo [2] GRANTHA LETTER EE..GRANTHA LETTER AI
+11313..11328 ; Alphabetic # Lo [22] GRANTHA LETTER OO..GRANTHA LETTER NA
+1132A..11330 ; Alphabetic # Lo [7] GRANTHA LETTER PA..GRANTHA LETTER RA
+11332..11333 ; Alphabetic # Lo [2] GRANTHA LETTER LA..GRANTHA LETTER LLA
+11335..11339 ; Alphabetic # Lo [5] GRANTHA LETTER VA..GRANTHA LETTER HA
+1133D ; Alphabetic # Lo GRANTHA SIGN AVAGRAHA
+1133E..1133F ; Alphabetic # Mc [2] GRANTHA VOWEL SIGN AA..GRANTHA VOWEL SIGN I
+11340 ; Alphabetic # Mn GRANTHA VOWEL SIGN II
+11341..11344 ; Alphabetic # Mc [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR
+11347..11348 ; Alphabetic # Mc [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI
+1134B..1134C ; Alphabetic # Mc [2] GRANTHA VOWEL SIGN OO..GRANTHA VOWEL SIGN AU
+11350 ; Alphabetic # Lo GRANTHA OM
+11357 ; Alphabetic # Mc GRANTHA AU LENGTH MARK
+1135D..11361 ; Alphabetic # Lo [5] GRANTHA SIGN PLUTA..GRANTHA LETTER VOCALIC LL
+11362..11363 ; Alphabetic # Mc [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL
+11380..11389 ; Alphabetic # Lo [10] TULU-TIGALARI LETTER A..TULU-TIGALARI LETTER VOCALIC LL
+1138B ; Alphabetic # Lo TULU-TIGALARI LETTER EE
+1138E ; Alphabetic # Lo TULU-TIGALARI LETTER AI
+11390..113B5 ; Alphabetic # Lo [38] TULU-TIGALARI LETTER OO..TULU-TIGALARI LETTER LLLA
+113B7 ; Alphabetic # Lo TULU-TIGALARI SIGN AVAGRAHA
+113B8..113BA ; Alphabetic # Mc [3] TULU-TIGALARI VOWEL SIGN AA..TULU-TIGALARI VOWEL SIGN II
+113BB..113C0 ; Alphabetic # Mn [6] TULU-TIGALARI VOWEL SIGN U..TULU-TIGALARI VOWEL SIGN VOCALIC LL
+113C2 ; Alphabetic # Mc TULU-TIGALARI VOWEL SIGN EE
+113C5 ; Alphabetic # Mc TULU-TIGALARI VOWEL SIGN AI
+113C7..113CA ; Alphabetic # Mc [4] TULU-TIGALARI VOWEL SIGN OO..TULU-TIGALARI SIGN CANDRA ANUNASIKA
+113CC..113CD ; Alphabetic # Mc [2] TULU-TIGALARI SIGN ANUSVARA..TULU-TIGALARI SIGN VISARGA
+113D1 ; Alphabetic # Lo TULU-TIGALARI REPHA
+113D3 ; Alphabetic # Lo TULU-TIGALARI SIGN PLUTA
+11400..11434 ; Alphabetic # Lo [53] NEWA LETTER A..NEWA LETTER HA
+11435..11437 ; Alphabetic # Mc [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II
+11438..1143F ; Alphabetic # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI
+11440..11441 ; Alphabetic # Mc [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU
+11443..11444 ; Alphabetic # Mn [2] NEWA SIGN CANDRABINDU..NEWA SIGN ANUSVARA
+11445 ; Alphabetic # Mc NEWA SIGN VISARGA
+11447..1144A ; Alphabetic # Lo [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI
+1145F..11461 ; Alphabetic # Lo [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA
+11480..114AF ; Alphabetic # Lo [48] TIRHUTA ANJI..TIRHUTA LETTER HA
+114B0..114B2 ; Alphabetic # Mc [3] TIRHUTA VOWEL SIGN AA..TIRHUTA VOWEL SIGN II
+114B3..114B8 ; Alphabetic # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL
+114B9 ; Alphabetic # Mc TIRHUTA VOWEL SIGN E
+114BA ; Alphabetic # Mn TIRHUTA VOWEL SIGN SHORT E
+114BB..114BE ; Alphabetic # Mc [4] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN AU
+114BF..114C0 ; Alphabetic # Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA
+114C1 ; Alphabetic # Mc TIRHUTA SIGN VISARGA
+114C4..114C5 ; Alphabetic # Lo [2] TIRHUTA SIGN AVAGRAHA..TIRHUTA GVANG
+114C7 ; Alphabetic # Lo TIRHUTA OM
+11580..115AE ; Alphabetic # Lo [47] SIDDHAM LETTER A..SIDDHAM LETTER HA
+115AF..115B1 ; Alphabetic # Mc [3] SIDDHAM VOWEL SIGN AA..SIDDHAM VOWEL SIGN II
+115B2..115B5 ; Alphabetic # Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR
+115B8..115BB ; Alphabetic # Mc [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU
+115BC..115BD ; Alphabetic # Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA
+115BE ; Alphabetic # Mc SIDDHAM SIGN VISARGA
+115D8..115DB ; Alphabetic # Lo [4] SIDDHAM LETTER THREE-CIRCLE ALTERNATE I..SIDDHAM LETTER ALTERNATE U
+115DC..115DD ; Alphabetic # Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU
+11600..1162F ; Alphabetic # Lo [48] MODI LETTER A..MODI LETTER LLA
+11630..11632 ; Alphabetic # Mc [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II
+11633..1163A ; Alphabetic # Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI
+1163B..1163C ; Alphabetic # Mc [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU
+1163D ; Alphabetic # Mn MODI SIGN ANUSVARA
+1163E ; Alphabetic # Mc MODI SIGN VISARGA
+11640 ; Alphabetic # Mn MODI SIGN ARDHACANDRA
+11644 ; Alphabetic # Lo MODI SIGN HUVA
+11680..116AA ; Alphabetic # Lo [43] TAKRI LETTER A..TAKRI LETTER RRA
+116AB ; Alphabetic # Mn TAKRI SIGN ANUSVARA
+116AC ; Alphabetic # Mc TAKRI SIGN VISARGA
+116AD ; Alphabetic # Mn TAKRI VOWEL SIGN AA
+116AE..116AF ; Alphabetic # Mc [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II
+116B0..116B5 ; Alphabetic # Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU
+116B8 ; Alphabetic # Lo TAKRI LETTER ARCHAIC KHA
+11700..1171A ; Alphabetic # Lo [27] AHOM LETTER KA..AHOM LETTER ALTERNATE BA
+1171D ; Alphabetic # Mn AHOM CONSONANT SIGN MEDIAL LA
+1171E ; Alphabetic # Mc AHOM CONSONANT SIGN MEDIAL RA
+1171F ; Alphabetic # Mn AHOM CONSONANT SIGN MEDIAL LIGATING RA
+11720..11721 ; Alphabetic # Mc [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA
+11722..11725 ; Alphabetic # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU
+11726 ; Alphabetic # Mc AHOM VOWEL SIGN E
+11727..1172A ; Alphabetic # Mn [4] AHOM VOWEL SIGN AW..AHOM VOWEL SIGN AM
+11740..11746 ; Alphabetic # Lo [7] AHOM LETTER CA..AHOM LETTER LLA
+11800..1182B ; Alphabetic # Lo [44] DOGRA LETTER A..DOGRA LETTER RRA
+1182C..1182E ; Alphabetic # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II
+1182F..11837 ; Alphabetic # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA
+11838 ; Alphabetic # Mc DOGRA SIGN VISARGA
+118A0..118DF ; Alphabetic # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO
+118FF..11906 ; Alphabetic # Lo [8] WARANG CITI OM..DIVES AKURU LETTER E
+11909 ; Alphabetic # Lo DIVES AKURU LETTER O
+1190C..11913 ; Alphabetic # Lo [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA
+11915..11916 ; Alphabetic # Lo [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA
+11918..1192F ; Alphabetic # Lo [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA
+11930..11935 ; Alphabetic # Mc [6] DIVES AKURU VOWEL SIGN AA..DIVES AKURU VOWEL SIGN E
+11937..11938 ; Alphabetic # Mc [2] DIVES AKURU VOWEL SIGN AI..DIVES AKURU VOWEL SIGN O
+1193B..1193C ; Alphabetic # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU
+1193F ; Alphabetic # Lo DIVES AKURU PREFIXED NASAL SIGN
+11940 ; Alphabetic # Mc DIVES AKURU MEDIAL YA
+11941 ; Alphabetic # Lo DIVES AKURU INITIAL RA
+11942 ; Alphabetic # Mc DIVES AKURU MEDIAL RA
+119A0..119A7 ; Alphabetic # Lo [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR
+119AA..119D0 ; Alphabetic # Lo [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA
+119D1..119D3 ; Alphabetic # Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II
+119D4..119D7 ; Alphabetic # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR
+119DA..119DB ; Alphabetic # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI
+119DC..119DF ; Alphabetic # Mc [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA
+119E1 ; Alphabetic # Lo NANDINAGARI SIGN AVAGRAHA
+119E3 ; Alphabetic # Lo NANDINAGARI HEADSTROKE
+119E4 ; Alphabetic # Mc NANDINAGARI VOWEL SIGN PRISHTHAMATRA E
+11A00 ; Alphabetic # Lo ZANABAZAR SQUARE LETTER A
+11A01..11A0A ; Alphabetic # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK
+11A0B..11A32 ; Alphabetic # Lo [40] ZANABAZAR SQUARE LETTER KA..ZANABAZAR SQUARE LETTER KSSA
+11A35..11A38 ; Alphabetic # Mn [4] ZANABAZAR SQUARE SIGN CANDRABINDU..ZANABAZAR SQUARE SIGN ANUSVARA
+11A39 ; Alphabetic # Mc ZANABAZAR SQUARE SIGN VISARGA
+11A3A ; Alphabetic # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA
+11A3B..11A3E ; Alphabetic # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA
+11A50 ; Alphabetic # Lo SOYOMBO LETTER A
+11A51..11A56 ; Alphabetic # Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE
+11A57..11A58 ; Alphabetic # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU
+11A59..11A5B ; Alphabetic # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK
+11A5C..11A89 ; Alphabetic # Lo [46] SOYOMBO LETTER KA..SOYOMBO CLUSTER-INITIAL LETTER SA
+11A8A..11A96 ; Alphabetic # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA
+11A97 ; Alphabetic # Mc SOYOMBO SIGN VISARGA
+11A9D ; Alphabetic # Lo SOYOMBO MARK PLUTA
+11AB0..11AF8 ; Alphabetic # Lo [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL
+11BC0..11BE0 ; Alphabetic # Lo [33] SUNUWAR LETTER DEVI..SUNUWAR LETTER KLOKO
+11C00..11C08 ; Alphabetic # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L
+11C0A..11C2E ; Alphabetic # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA
+11C2F ; Alphabetic # Mc BHAIKSUKI VOWEL SIGN AA
+11C30..11C36 ; Alphabetic # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L
+11C38..11C3D ; Alphabetic # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA
+11C3E ; Alphabetic # Mc BHAIKSUKI SIGN VISARGA
+11C40 ; Alphabetic # Lo BHAIKSUKI SIGN AVAGRAHA
+11C72..11C8F ; Alphabetic # Lo [30] MARCHEN LETTER KA..MARCHEN LETTER A
+11C92..11CA7 ; Alphabetic # Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA
+11CA9 ; Alphabetic # Mc MARCHEN SUBJOINED LETTER YA
+11CAA..11CB0 ; Alphabetic # Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA
+11CB1 ; Alphabetic # Mc MARCHEN VOWEL SIGN I
+11CB2..11CB3 ; Alphabetic # Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E
+11CB4 ; Alphabetic # Mc MARCHEN VOWEL SIGN O
+11CB5..11CB6 ; Alphabetic # Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU
+11D00..11D06 ; Alphabetic # Lo [7] MASARAM GONDI LETTER A..MASARAM GONDI LETTER E
+11D08..11D09 ; Alphabetic # Lo [2] MASARAM GONDI LETTER AI..MASARAM GONDI LETTER O
+11D0B..11D30 ; Alphabetic # Lo [38] MASARAM GONDI LETTER AU..MASARAM GONDI LETTER TRA
+11D31..11D36 ; Alphabetic # Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R
+11D3A ; Alphabetic # Mn MASARAM GONDI VOWEL SIGN E
+11D3C..11D3D ; Alphabetic # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O
+11D3F..11D41 ; Alphabetic # Mn [3] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI SIGN VISARGA
+11D43 ; Alphabetic # Mn MASARAM GONDI SIGN CANDRA
+11D46 ; Alphabetic # Lo MASARAM GONDI REPHA
+11D47 ; Alphabetic # Mn MASARAM GONDI RA-KARA
+11D60..11D65 ; Alphabetic # Lo [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU
+11D67..11D68 ; Alphabetic # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI
+11D6A..11D89 ; Alphabetic # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA
+11D8A..11D8E ; Alphabetic # Mc [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU
+11D90..11D91 ; Alphabetic # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI
+11D93..11D94 ; Alphabetic # Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU
+11D95 ; Alphabetic # Mn GUNJALA GONDI SIGN ANUSVARA
+11D96 ; Alphabetic # Mc GUNJALA GONDI SIGN VISARGA
+11D98 ; Alphabetic # Lo GUNJALA GONDI OM
+11EE0..11EF2 ; Alphabetic # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA
+11EF3..11EF4 ; Alphabetic # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U
+11EF5..11EF6 ; Alphabetic # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O
+11F00..11F01 ; Alphabetic # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA
+11F02 ; Alphabetic # Lo KAWI SIGN REPHA
+11F03 ; Alphabetic # Mc KAWI SIGN VISARGA
+11F04..11F10 ; Alphabetic # Lo [13] KAWI LETTER A..KAWI LETTER O
+11F12..11F33 ; Alphabetic # Lo [34] KAWI LETTER KA..KAWI LETTER JNYA
+11F34..11F35 ; Alphabetic # Mc [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA
+11F36..11F3A ; Alphabetic # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R
+11F3E..11F3F ; Alphabetic # Mc [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI
+11F40 ; Alphabetic # Mn KAWI VOWEL SIGN EU
+11FB0 ; Alphabetic # Lo LISU LETTER YHA
+12000..12399 ; Alphabetic # Lo [922] CUNEIFORM SIGN A..CUNEIFORM SIGN U U
+12400..1246E ; Alphabetic # Nl [111] CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM
+12480..12543 ; Alphabetic # Lo [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU
+12F90..12FF0 ; Alphabetic # Lo [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114
+13000..1342F ; Alphabetic # Lo [1072] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH V011D
+13441..13446 ; Alphabetic # Lo [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN
+13460..143FA ; Alphabetic # Lo [3995] EGYPTIAN HIEROGLYPH-13460..EGYPTIAN HIEROGLYPH-143FA
+14400..14646 ; Alphabetic # Lo [583] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A530
+16100..1611D ; Alphabetic # Lo [30] GURUNG KHEMA LETTER A..GURUNG KHEMA LETTER SA
+1611E..16129 ; Alphabetic # Mn [12] GURUNG KHEMA VOWEL SIGN AA..GURUNG KHEMA VOWEL LENGTH MARK
+1612A..1612C ; Alphabetic # Mc [3] GURUNG KHEMA CONSONANT SIGN MEDIAL YA..GURUNG KHEMA CONSONANT SIGN MEDIAL HA
+1612D..1612E ; Alphabetic # Mn [2] GURUNG KHEMA SIGN ANUSVARA..GURUNG KHEMA CONSONANT SIGN MEDIAL RA
+16800..16A38 ; Alphabetic # Lo [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ
+16A40..16A5E ; Alphabetic # Lo [31] MRO LETTER TA..MRO LETTER TEK
+16A70..16ABE ; Alphabetic # Lo [79] TANGSA LETTER OZ..TANGSA LETTER ZA
+16AD0..16AED ; Alphabetic # Lo [30] BASSA VAH LETTER ENNI..BASSA VAH LETTER I
+16B00..16B2F ; Alphabetic # Lo [48] PAHAWH HMONG VOWEL KEEB..PAHAWH HMONG CONSONANT CAU
+16B40..16B43 ; Alphabetic # Lm [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM
+16B63..16B77 ; Alphabetic # Lo [21] PAHAWH HMONG SIGN VOS LUB..PAHAWH HMONG SIGN CIM NRES TOS
+16B7D..16B8F ; Alphabetic # Lo [19] PAHAWH HMONG CLAN SIGN TSHEEJ..PAHAWH HMONG CLAN SIGN VWJ
+16D40..16D42 ; Alphabetic # Lm [3] KIRAT RAI SIGN ANUSVARA..KIRAT RAI SIGN VISARGA
+16D43..16D6A ; Alphabetic # Lo [40] KIRAT RAI LETTER A..KIRAT RAI VOWEL SIGN AU
+16D6B..16D6C ; Alphabetic # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT
+16E40..16E7F ; Alphabetic # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y
+16F00..16F4A ; Alphabetic # Lo [75] MIAO LETTER PA..MIAO LETTER RTE
+16F4F ; Alphabetic # Mn MIAO SIGN CONSONANT MODIFIER BAR
+16F50 ; Alphabetic # Lo MIAO LETTER NASALIZATION
+16F51..16F87 ; Alphabetic # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI
+16F8F..16F92 ; Alphabetic # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
+16F93..16F9F ; Alphabetic # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8
+16FE0..16FE1 ; Alphabetic # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK
+16FE3 ; Alphabetic # Lm OLD CHINESE ITERATION MARK
+16FF0..16FF1 ; Alphabetic # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY
+17000..187F7 ; Alphabetic # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7
+18800..18CD5 ; Alphabetic # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5
+18CFF..18D08 ; Alphabetic # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08
+1AFF0..1AFF3 ; Alphabetic # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5
+1AFF5..1AFFB ; Alphabetic # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5
+1AFFD..1AFFE ; Alphabetic # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8
+1B000..1B122 ; Alphabetic # Lo [291] KATAKANA LETTER ARCHAIC E..KATAKANA LETTER ARCHAIC WU
+1B132 ; Alphabetic # Lo HIRAGANA LETTER SMALL KO
+1B150..1B152 ; Alphabetic # Lo [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO
+1B155 ; Alphabetic # Lo KATAKANA LETTER SMALL KO
+1B164..1B167 ; Alphabetic # Lo [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N
+1B170..1B2FB ; Alphabetic # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB
+1BC00..1BC6A ; Alphabetic # Lo [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M
+1BC70..1BC7C ; Alphabetic # Lo [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK
+1BC80..1BC88 ; Alphabetic # Lo [9] DUPLOYAN AFFIX HIGH ACUTE..DUPLOYAN AFFIX HIGH VERTICAL
+1BC90..1BC99 ; Alphabetic # Lo [10] DUPLOYAN AFFIX LOW ACUTE..DUPLOYAN AFFIX LOW ARROW
+1BC9E ; Alphabetic # Mn DUPLOYAN DOUBLE MARK
+1D400..1D454 ; Alphabetic # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G
+1D456..1D49C ; Alphabetic # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A
+1D49E..1D49F ; Alphabetic # L& [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D
+1D4A2 ; Alphabetic # L& MATHEMATICAL SCRIPT CAPITAL G
+1D4A5..1D4A6 ; Alphabetic # L& [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K
+1D4A9..1D4AC ; Alphabetic # L& [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q
+1D4AE..1D4B9 ; Alphabetic # L& [12] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D
+1D4BB ; Alphabetic # L& MATHEMATICAL SCRIPT SMALL F
+1D4BD..1D4C3 ; Alphabetic # L& [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N
+1D4C5..1D505 ; Alphabetic # L& [65] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B
+1D507..1D50A ; Alphabetic # L& [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G
+1D50D..1D514 ; Alphabetic # L& [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q
+1D516..1D51C ; Alphabetic # L& [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y
+1D51E..1D539 ; Alphabetic # L& [28] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B
+1D53B..1D53E ; Alphabetic # L& [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G
+1D540..1D544 ; Alphabetic # L& [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M
+1D546 ; Alphabetic # L& MATHEMATICAL DOUBLE-STRUCK CAPITAL O
+1D54A..1D550 ; Alphabetic # L& [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y
+1D552..1D6A5 ; Alphabetic # L& [340] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J
+1D6A8..1D6C0 ; Alphabetic # L& [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA
+1D6C2..1D6DA ; Alphabetic # L& [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA
+1D6DC..1D6FA ; Alphabetic # L& [31] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL ITALIC CAPITAL OMEGA
+1D6FC..1D714 ; Alphabetic # L& [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA
+1D716..1D734 ; Alphabetic # L& [31] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA
+1D736..1D74E ; Alphabetic # L& [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA
+1D750..1D76E ; Alphabetic # L& [31] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA
+1D770..1D788 ; Alphabetic # L& [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA
+1D78A..1D7A8 ; Alphabetic # L& [31] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA
+1D7AA..1D7C2 ; Alphabetic # L& [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA
+1D7C4..1D7CB ; Alphabetic # L& [8] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD SMALL DIGAMMA
+1DF00..1DF09 ; Alphabetic # L& [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK
+1DF0A ; Alphabetic # Lo LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK
+1DF0B..1DF1E ; Alphabetic # L& [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL
+1DF25..1DF2A ; Alphabetic # L& [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK
+1E000..1E006 ; Alphabetic # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE
+1E008..1E018 ; Alphabetic # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU
+1E01B..1E021 ; Alphabetic # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI
+1E023..1E024 ; Alphabetic # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS
+1E026..1E02A ; Alphabetic # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA
+1E030..1E06D ; Alphabetic # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE
+1E08F ; Alphabetic # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+1E100..1E12C ; Alphabetic # Lo [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W
+1E137..1E13D ; Alphabetic # Lm [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER
+1E14E ; Alphabetic # Lo NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ
+1E290..1E2AD ; Alphabetic # Lo [30] TOTO LETTER PA..TOTO LETTER A
+1E2C0..1E2EB ; Alphabetic # Lo [44] WANCHO LETTER AA..WANCHO LETTER YIH
+1E4D0..1E4EA ; Alphabetic # Lo [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL
+1E4EB ; Alphabetic # Lm NAG MUNDARI SIGN OJOD
+1E5D0..1E5ED ; Alphabetic # Lo [30] OL ONAL LETTER O..OL ONAL LETTER EG
+1E5F0 ; Alphabetic # Lo OL ONAL SIGN HODDOND
+1E7E0..1E7E6 ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO
+1E7E8..1E7EB ; Alphabetic # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE
+1E7ED..1E7EE ; Alphabetic # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE
+1E7F0..1E7FE ; Alphabetic # Lo [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE
+1E800..1E8C4 ; Alphabetic # Lo [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON
+1E900..1E943 ; Alphabetic # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA
+1E947 ; Alphabetic # Mn ADLAM HAMZA
+1E94B ; Alphabetic # Lm ADLAM NASALIZATION MARK
+1EE00..1EE03 ; Alphabetic # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL
+1EE05..1EE1F ; Alphabetic # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF
+1EE21..1EE22 ; Alphabetic # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM
+1EE24 ; Alphabetic # Lo ARABIC MATHEMATICAL INITIAL HEH
+1EE27 ; Alphabetic # Lo ARABIC MATHEMATICAL INITIAL HAH
+1EE29..1EE32 ; Alphabetic # Lo [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF
+1EE34..1EE37 ; Alphabetic # Lo [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH
+1EE39 ; Alphabetic # Lo ARABIC MATHEMATICAL INITIAL DAD
+1EE3B ; Alphabetic # Lo ARABIC MATHEMATICAL INITIAL GHAIN
+1EE42 ; Alphabetic # Lo ARABIC MATHEMATICAL TAILED JEEM
+1EE47 ; Alphabetic # Lo ARABIC MATHEMATICAL TAILED HAH
+1EE49 ; Alphabetic # Lo ARABIC MATHEMATICAL TAILED YEH
+1EE4B ; Alphabetic # Lo ARABIC MATHEMATICAL TAILED LAM
+1EE4D..1EE4F ; Alphabetic # Lo [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN
+1EE51..1EE52 ; Alphabetic # Lo [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF
+1EE54 ; Alphabetic # Lo ARABIC MATHEMATICAL TAILED SHEEN
+1EE57 ; Alphabetic # Lo ARABIC MATHEMATICAL TAILED KHAH
+1EE59 ; Alphabetic # Lo ARABIC MATHEMATICAL TAILED DAD
+1EE5B ; Alphabetic # Lo ARABIC MATHEMATICAL TAILED GHAIN
+1EE5D ; Alphabetic # Lo ARABIC MATHEMATICAL TAILED DOTLESS NOON
+1EE5F ; Alphabetic # Lo ARABIC MATHEMATICAL TAILED DOTLESS QAF
+1EE61..1EE62 ; Alphabetic # Lo [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM
+1EE64 ; Alphabetic # Lo ARABIC MATHEMATICAL STRETCHED HEH
+1EE67..1EE6A ; Alphabetic # Lo [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF
+1EE6C..1EE72 ; Alphabetic # Lo [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF
+1EE74..1EE77 ; Alphabetic # Lo [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH
+1EE79..1EE7C ; Alphabetic # Lo [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH
+1EE7E ; Alphabetic # Lo ARABIC MATHEMATICAL STRETCHED DOTLESS FEH
+1EE80..1EE89 ; Alphabetic # Lo [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH
+1EE8B..1EE9B ; Alphabetic # Lo [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN
+1EEA1..1EEA3 ; Alphabetic # Lo [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL
+1EEA5..1EEA9 ; Alphabetic # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH
+1EEAB..1EEBB ; Alphabetic # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN
+1F130..1F149 ; Alphabetic # So [26] SQUARED LATIN CAPITAL LETTER A..SQUARED LATIN CAPITAL LETTER Z
+1F150..1F169 ; Alphabetic # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z
+1F170..1F189 ; Alphabetic # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z
+20000..2A6DF ; Alphabetic # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF
+2A700..2B739 ; Alphabetic # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739
+2B740..2B81D ; Alphabetic # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D
+2B820..2CEA1 ; Alphabetic # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1
+2CEB0..2EBE0 ; Alphabetic # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0
+2EBF0..2EE5D ; Alphabetic # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D
+2F800..2FA1D ; Alphabetic # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D
+30000..3134A ; Alphabetic # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A
+31350..323AF ; Alphabetic # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF
+
+# Total code points: 142759
+
+# ================================================
+
+# Derived Property: Lowercase
+# Generated from: Ll + Other_Lowercase
+
+0061..007A ; Lowercase # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+00AA ; Lowercase # Lo FEMININE ORDINAL INDICATOR
+00B5 ; Lowercase # L& MICRO SIGN
+00BA ; Lowercase # Lo MASCULINE ORDINAL INDICATOR
+00DF..00F6 ; Lowercase # L& [24] LATIN SMALL LETTER SHARP S..LATIN SMALL LETTER O WITH DIAERESIS
+00F8..00FF ; Lowercase # L& [8] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER Y WITH DIAERESIS
+0101 ; Lowercase # L& LATIN SMALL LETTER A WITH MACRON
+0103 ; Lowercase # L& LATIN SMALL LETTER A WITH BREVE
+0105 ; Lowercase # L& LATIN SMALL LETTER A WITH OGONEK
+0107 ; Lowercase # L& LATIN SMALL LETTER C WITH ACUTE
+0109 ; Lowercase # L& LATIN SMALL LETTER C WITH CIRCUMFLEX
+010B ; Lowercase # L& LATIN SMALL LETTER C WITH DOT ABOVE
+010D ; Lowercase # L& LATIN SMALL LETTER C WITH CARON
+010F ; Lowercase # L& LATIN SMALL LETTER D WITH CARON
+0111 ; Lowercase # L& LATIN SMALL LETTER D WITH STROKE
+0113 ; Lowercase # L& LATIN SMALL LETTER E WITH MACRON
+0115 ; Lowercase # L& LATIN SMALL LETTER E WITH BREVE
+0117 ; Lowercase # L& LATIN SMALL LETTER E WITH DOT ABOVE
+0119 ; Lowercase # L& LATIN SMALL LETTER E WITH OGONEK
+011B ; Lowercase # L& LATIN SMALL LETTER E WITH CARON
+011D ; Lowercase # L& LATIN SMALL LETTER G WITH CIRCUMFLEX
+011F ; Lowercase # L& LATIN SMALL LETTER G WITH BREVE
+0121 ; Lowercase # L& LATIN SMALL LETTER G WITH DOT ABOVE
+0123 ; Lowercase # L& LATIN SMALL LETTER G WITH CEDILLA
+0125 ; Lowercase # L& LATIN SMALL LETTER H WITH CIRCUMFLEX
+0127 ; Lowercase # L& LATIN SMALL LETTER H WITH STROKE
+0129 ; Lowercase # L& LATIN SMALL LETTER I WITH TILDE
+012B ; Lowercase # L& LATIN SMALL LETTER I WITH MACRON
+012D ; Lowercase # L& LATIN SMALL LETTER I WITH BREVE
+012F ; Lowercase # L& LATIN SMALL LETTER I WITH OGONEK
+0131 ; Lowercase # L& LATIN SMALL LETTER DOTLESS I
+0133 ; Lowercase # L& LATIN SMALL LIGATURE IJ
+0135 ; Lowercase # L& LATIN SMALL LETTER J WITH CIRCUMFLEX
+0137..0138 ; Lowercase # L& [2] LATIN SMALL LETTER K WITH CEDILLA..LATIN SMALL LETTER KRA
+013A ; Lowercase # L& LATIN SMALL LETTER L WITH ACUTE
+013C ; Lowercase # L& LATIN SMALL LETTER L WITH CEDILLA
+013E ; Lowercase # L& LATIN SMALL LETTER L WITH CARON
+0140 ; Lowercase # L& LATIN SMALL LETTER L WITH MIDDLE DOT
+0142 ; Lowercase # L& LATIN SMALL LETTER L WITH STROKE
+0144 ; Lowercase # L& LATIN SMALL LETTER N WITH ACUTE
+0146 ; Lowercase # L& LATIN SMALL LETTER N WITH CEDILLA
+0148..0149 ; Lowercase # L& [2] LATIN SMALL LETTER N WITH CARON..LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+014B ; Lowercase # L& LATIN SMALL LETTER ENG
+014D ; Lowercase # L& LATIN SMALL LETTER O WITH MACRON
+014F ; Lowercase # L& LATIN SMALL LETTER O WITH BREVE
+0151 ; Lowercase # L& LATIN SMALL LETTER O WITH DOUBLE ACUTE
+0153 ; Lowercase # L& LATIN SMALL LIGATURE OE
+0155 ; Lowercase # L& LATIN SMALL LETTER R WITH ACUTE
+0157 ; Lowercase # L& LATIN SMALL LETTER R WITH CEDILLA
+0159 ; Lowercase # L& LATIN SMALL LETTER R WITH CARON
+015B ; Lowercase # L& LATIN SMALL LETTER S WITH ACUTE
+015D ; Lowercase # L& LATIN SMALL LETTER S WITH CIRCUMFLEX
+015F ; Lowercase # L& LATIN SMALL LETTER S WITH CEDILLA
+0161 ; Lowercase # L& LATIN SMALL LETTER S WITH CARON
+0163 ; Lowercase # L& LATIN SMALL LETTER T WITH CEDILLA
+0165 ; Lowercase # L& LATIN SMALL LETTER T WITH CARON
+0167 ; Lowercase # L& LATIN SMALL LETTER T WITH STROKE
+0169 ; Lowercase # L& LATIN SMALL LETTER U WITH TILDE
+016B ; Lowercase # L& LATIN SMALL LETTER U WITH MACRON
+016D ; Lowercase # L& LATIN SMALL LETTER U WITH BREVE
+016F ; Lowercase # L& LATIN SMALL LETTER U WITH RING ABOVE
+0171 ; Lowercase # L& LATIN SMALL LETTER U WITH DOUBLE ACUTE
+0173 ; Lowercase # L& LATIN SMALL LETTER U WITH OGONEK
+0175 ; Lowercase # L& LATIN SMALL LETTER W WITH CIRCUMFLEX
+0177 ; Lowercase # L& LATIN SMALL LETTER Y WITH CIRCUMFLEX
+017A ; Lowercase # L& LATIN SMALL LETTER Z WITH ACUTE
+017C ; Lowercase # L& LATIN SMALL LETTER Z WITH DOT ABOVE
+017E..0180 ; Lowercase # L& [3] LATIN SMALL LETTER Z WITH CARON..LATIN SMALL LETTER B WITH STROKE
+0183 ; Lowercase # L& LATIN SMALL LETTER B WITH TOPBAR
+0185 ; Lowercase # L& LATIN SMALL LETTER TONE SIX
+0188 ; Lowercase # L& LATIN SMALL LETTER C WITH HOOK
+018C..018D ; Lowercase # L& [2] LATIN SMALL LETTER D WITH TOPBAR..LATIN SMALL LETTER TURNED DELTA
+0192 ; Lowercase # L& LATIN SMALL LETTER F WITH HOOK
+0195 ; Lowercase # L& LATIN SMALL LETTER HV
+0199..019B ; Lowercase # L& [3] LATIN SMALL LETTER K WITH HOOK..LATIN SMALL LETTER LAMBDA WITH STROKE
+019E ; Lowercase # L& LATIN SMALL LETTER N WITH LONG RIGHT LEG
+01A1 ; Lowercase # L& LATIN SMALL LETTER O WITH HORN
+01A3 ; Lowercase # L& LATIN SMALL LETTER OI
+01A5 ; Lowercase # L& LATIN SMALL LETTER P WITH HOOK
+01A8 ; Lowercase # L& LATIN SMALL LETTER TONE TWO
+01AA..01AB ; Lowercase # L& [2] LATIN LETTER REVERSED ESH LOOP..LATIN SMALL LETTER T WITH PALATAL HOOK
+01AD ; Lowercase # L& LATIN SMALL LETTER T WITH HOOK
+01B0 ; Lowercase # L& LATIN SMALL LETTER U WITH HORN
+01B4 ; Lowercase # L& LATIN SMALL LETTER Y WITH HOOK
+01B6 ; Lowercase # L& LATIN SMALL LETTER Z WITH STROKE
+01B9..01BA ; Lowercase # L& [2] LATIN SMALL LETTER EZH REVERSED..LATIN SMALL LETTER EZH WITH TAIL
+01BD..01BF ; Lowercase # L& [3] LATIN SMALL LETTER TONE FIVE..LATIN LETTER WYNN
+01C6 ; Lowercase # L& LATIN SMALL LETTER DZ WITH CARON
+01C9 ; Lowercase # L& LATIN SMALL LETTER LJ
+01CC ; Lowercase # L& LATIN SMALL LETTER NJ
+01CE ; Lowercase # L& LATIN SMALL LETTER A WITH CARON
+01D0 ; Lowercase # L& LATIN SMALL LETTER I WITH CARON
+01D2 ; Lowercase # L& LATIN SMALL LETTER O WITH CARON
+01D4 ; Lowercase # L& LATIN SMALL LETTER U WITH CARON
+01D6 ; Lowercase # L& LATIN SMALL LETTER U WITH DIAERESIS AND MACRON
+01D8 ; Lowercase # L& LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE
+01DA ; Lowercase # L& LATIN SMALL LETTER U WITH DIAERESIS AND CARON
+01DC..01DD ; Lowercase # L& [2] LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE..LATIN SMALL LETTER TURNED E
+01DF ; Lowercase # L& LATIN SMALL LETTER A WITH DIAERESIS AND MACRON
+01E1 ; Lowercase # L& LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON
+01E3 ; Lowercase # L& LATIN SMALL LETTER AE WITH MACRON
+01E5 ; Lowercase # L& LATIN SMALL LETTER G WITH STROKE
+01E7 ; Lowercase # L& LATIN SMALL LETTER G WITH CARON
+01E9 ; Lowercase # L& LATIN SMALL LETTER K WITH CARON
+01EB ; Lowercase # L& LATIN SMALL LETTER O WITH OGONEK
+01ED ; Lowercase # L& LATIN SMALL LETTER O WITH OGONEK AND MACRON
+01EF..01F0 ; Lowercase # L& [2] LATIN SMALL LETTER EZH WITH CARON..LATIN SMALL LETTER J WITH CARON
+01F3 ; Lowercase # L& LATIN SMALL LETTER DZ
+01F5 ; Lowercase # L& LATIN SMALL LETTER G WITH ACUTE
+01F9 ; Lowercase # L& LATIN SMALL LETTER N WITH GRAVE
+01FB ; Lowercase # L& LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE
+01FD ; Lowercase # L& LATIN SMALL LETTER AE WITH ACUTE
+01FF ; Lowercase # L& LATIN SMALL LETTER O WITH STROKE AND ACUTE
+0201 ; Lowercase # L& LATIN SMALL LETTER A WITH DOUBLE GRAVE
+0203 ; Lowercase # L& LATIN SMALL LETTER A WITH INVERTED BREVE
+0205 ; Lowercase # L& LATIN SMALL LETTER E WITH DOUBLE GRAVE
+0207 ; Lowercase # L& LATIN SMALL LETTER E WITH INVERTED BREVE
+0209 ; Lowercase # L& LATIN SMALL LETTER I WITH DOUBLE GRAVE
+020B ; Lowercase # L& LATIN SMALL LETTER I WITH INVERTED BREVE
+020D ; Lowercase # L& LATIN SMALL LETTER O WITH DOUBLE GRAVE
+020F ; Lowercase # L& LATIN SMALL LETTER O WITH INVERTED BREVE
+0211 ; Lowercase # L& LATIN SMALL LETTER R WITH DOUBLE GRAVE
+0213 ; Lowercase # L& LATIN SMALL LETTER R WITH INVERTED BREVE
+0215 ; Lowercase # L& LATIN SMALL LETTER U WITH DOUBLE GRAVE
+0217 ; Lowercase # L& LATIN SMALL LETTER U WITH INVERTED BREVE
+0219 ; Lowercase # L& LATIN SMALL LETTER S WITH COMMA BELOW
+021B ; Lowercase # L& LATIN SMALL LETTER T WITH COMMA BELOW
+021D ; Lowercase # L& LATIN SMALL LETTER YOGH
+021F ; Lowercase # L& LATIN SMALL LETTER H WITH CARON
+0221 ; Lowercase # L& LATIN SMALL LETTER D WITH CURL
+0223 ; Lowercase # L& LATIN SMALL LETTER OU
+0225 ; Lowercase # L& LATIN SMALL LETTER Z WITH HOOK
+0227 ; Lowercase # L& LATIN SMALL LETTER A WITH DOT ABOVE
+0229 ; Lowercase # L& LATIN SMALL LETTER E WITH CEDILLA
+022B ; Lowercase # L& LATIN SMALL LETTER O WITH DIAERESIS AND MACRON
+022D ; Lowercase # L& LATIN SMALL LETTER O WITH TILDE AND MACRON
+022F ; Lowercase # L& LATIN SMALL LETTER O WITH DOT ABOVE
+0231 ; Lowercase # L& LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON
+0233..0239 ; Lowercase # L& [7] LATIN SMALL LETTER Y WITH MACRON..LATIN SMALL LETTER QP DIGRAPH
+023C ; Lowercase # L& LATIN SMALL LETTER C WITH STROKE
+023F..0240 ; Lowercase # L& [2] LATIN SMALL LETTER S WITH SWASH TAIL..LATIN SMALL LETTER Z WITH SWASH TAIL
+0242 ; Lowercase # L& LATIN SMALL LETTER GLOTTAL STOP
+0247 ; Lowercase # L& LATIN SMALL LETTER E WITH STROKE
+0249 ; Lowercase # L& LATIN SMALL LETTER J WITH STROKE
+024B ; Lowercase # L& LATIN SMALL LETTER Q WITH HOOK TAIL
+024D ; Lowercase # L& LATIN SMALL LETTER R WITH STROKE
+024F..0293 ; Lowercase # L& [69] LATIN SMALL LETTER Y WITH STROKE..LATIN SMALL LETTER EZH WITH CURL
+0295..02AF ; Lowercase # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL
+02B0..02B8 ; Lowercase # Lm [9] MODIFIER LETTER SMALL H..MODIFIER LETTER SMALL Y
+02C0..02C1 ; Lowercase # Lm [2] MODIFIER LETTER GLOTTAL STOP..MODIFIER LETTER REVERSED GLOTTAL STOP
+02E0..02E4 ; Lowercase # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP
+0345 ; Lowercase # Mn COMBINING GREEK YPOGEGRAMMENI
+0371 ; Lowercase # L& GREEK SMALL LETTER HETA
+0373 ; Lowercase # L& GREEK SMALL LETTER ARCHAIC SAMPI
+0377 ; Lowercase # L& GREEK SMALL LETTER PAMPHYLIAN DIGAMMA
+037A ; Lowercase # Lm GREEK YPOGEGRAMMENI
+037B..037D ; Lowercase # L& [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL
+0390 ; Lowercase # L& GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+03AC..03CE ; Lowercase # L& [35] GREEK SMALL LETTER ALPHA WITH TONOS..GREEK SMALL LETTER OMEGA WITH TONOS
+03D0..03D1 ; Lowercase # L& [2] GREEK BETA SYMBOL..GREEK THETA SYMBOL
+03D5..03D7 ; Lowercase # L& [3] GREEK PHI SYMBOL..GREEK KAI SYMBOL
+03D9 ; Lowercase # L& GREEK SMALL LETTER ARCHAIC KOPPA
+03DB ; Lowercase # L& GREEK SMALL LETTER STIGMA
+03DD ; Lowercase # L& GREEK SMALL LETTER DIGAMMA
+03DF ; Lowercase # L& GREEK SMALL LETTER KOPPA
+03E1 ; Lowercase # L& GREEK SMALL LETTER SAMPI
+03E3 ; Lowercase # L& COPTIC SMALL LETTER SHEI
+03E5 ; Lowercase # L& COPTIC SMALL LETTER FEI
+03E7 ; Lowercase # L& COPTIC SMALL LETTER KHEI
+03E9 ; Lowercase # L& COPTIC SMALL LETTER HORI
+03EB ; Lowercase # L& COPTIC SMALL LETTER GANGIA
+03ED ; Lowercase # L& COPTIC SMALL LETTER SHIMA
+03EF..03F3 ; Lowercase # L& [5] COPTIC SMALL LETTER DEI..GREEK LETTER YOT
+03F5 ; Lowercase # L& GREEK LUNATE EPSILON SYMBOL
+03F8 ; Lowercase # L& GREEK SMALL LETTER SHO
+03FB..03FC ; Lowercase # L& [2] GREEK SMALL LETTER SAN..GREEK RHO WITH STROKE SYMBOL
+0430..045F ; Lowercase # L& [48] CYRILLIC SMALL LETTER A..CYRILLIC SMALL LETTER DZHE
+0461 ; Lowercase # L& CYRILLIC SMALL LETTER OMEGA
+0463 ; Lowercase # L& CYRILLIC SMALL LETTER YAT
+0465 ; Lowercase # L& CYRILLIC SMALL LETTER IOTIFIED E
+0467 ; Lowercase # L& CYRILLIC SMALL LETTER LITTLE YUS
+0469 ; Lowercase # L& CYRILLIC SMALL LETTER IOTIFIED LITTLE YUS
+046B ; Lowercase # L& CYRILLIC SMALL LETTER BIG YUS
+046D ; Lowercase # L& CYRILLIC SMALL LETTER IOTIFIED BIG YUS
+046F ; Lowercase # L& CYRILLIC SMALL LETTER KSI
+0471 ; Lowercase # L& CYRILLIC SMALL LETTER PSI
+0473 ; Lowercase # L& CYRILLIC SMALL LETTER FITA
+0475 ; Lowercase # L& CYRILLIC SMALL LETTER IZHITSA
+0477 ; Lowercase # L& CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT
+0479 ; Lowercase # L& CYRILLIC SMALL LETTER UK
+047B ; Lowercase # L& CYRILLIC SMALL LETTER ROUND OMEGA
+047D ; Lowercase # L& CYRILLIC SMALL LETTER OMEGA WITH TITLO
+047F ; Lowercase # L& CYRILLIC SMALL LETTER OT
+0481 ; Lowercase # L& CYRILLIC SMALL LETTER KOPPA
+048B ; Lowercase # L& CYRILLIC SMALL LETTER SHORT I WITH TAIL
+048D ; Lowercase # L& CYRILLIC SMALL LETTER SEMISOFT SIGN
+048F ; Lowercase # L& CYRILLIC SMALL LETTER ER WITH TICK
+0491 ; Lowercase # L& CYRILLIC SMALL LETTER GHE WITH UPTURN
+0493 ; Lowercase # L& CYRILLIC SMALL LETTER GHE WITH STROKE
+0495 ; Lowercase # L& CYRILLIC SMALL LETTER GHE WITH MIDDLE HOOK
+0497 ; Lowercase # L& CYRILLIC SMALL LETTER ZHE WITH DESCENDER
+0499 ; Lowercase # L& CYRILLIC SMALL LETTER ZE WITH DESCENDER
+049B ; Lowercase # L& CYRILLIC SMALL LETTER KA WITH DESCENDER
+049D ; Lowercase # L& CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE
+049F ; Lowercase # L& CYRILLIC SMALL LETTER KA WITH STROKE
+04A1 ; Lowercase # L& CYRILLIC SMALL LETTER BASHKIR KA
+04A3 ; Lowercase # L& CYRILLIC SMALL LETTER EN WITH DESCENDER
+04A5 ; Lowercase # L& CYRILLIC SMALL LIGATURE EN GHE
+04A7 ; Lowercase # L& CYRILLIC SMALL LETTER PE WITH MIDDLE HOOK
+04A9 ; Lowercase # L& CYRILLIC SMALL LETTER ABKHASIAN HA
+04AB ; Lowercase # L& CYRILLIC SMALL LETTER ES WITH DESCENDER
+04AD ; Lowercase # L& CYRILLIC SMALL LETTER TE WITH DESCENDER
+04AF ; Lowercase # L& CYRILLIC SMALL LETTER STRAIGHT U
+04B1 ; Lowercase # L& CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE
+04B3 ; Lowercase # L& CYRILLIC SMALL LETTER HA WITH DESCENDER
+04B5 ; Lowercase # L& CYRILLIC SMALL LIGATURE TE TSE
+04B7 ; Lowercase # L& CYRILLIC SMALL LETTER CHE WITH DESCENDER
+04B9 ; Lowercase # L& CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE
+04BB ; Lowercase # L& CYRILLIC SMALL LETTER SHHA
+04BD ; Lowercase # L& CYRILLIC SMALL LETTER ABKHASIAN CHE
+04BF ; Lowercase # L& CYRILLIC SMALL LETTER ABKHASIAN CHE WITH DESCENDER
+04C2 ; Lowercase # L& CYRILLIC SMALL LETTER ZHE WITH BREVE
+04C4 ; Lowercase # L& CYRILLIC SMALL LETTER KA WITH HOOK
+04C6 ; Lowercase # L& CYRILLIC SMALL LETTER EL WITH TAIL
+04C8 ; Lowercase # L& CYRILLIC SMALL LETTER EN WITH HOOK
+04CA ; Lowercase # L& CYRILLIC SMALL LETTER EN WITH TAIL
+04CC ; Lowercase # L& CYRILLIC SMALL LETTER KHAKASSIAN CHE
+04CE..04CF ; Lowercase # L& [2] CYRILLIC SMALL LETTER EM WITH TAIL..CYRILLIC SMALL LETTER PALOCHKA
+04D1 ; Lowercase # L& CYRILLIC SMALL LETTER A WITH BREVE
+04D3 ; Lowercase # L& CYRILLIC SMALL LETTER A WITH DIAERESIS
+04D5 ; Lowercase # L& CYRILLIC SMALL LIGATURE A IE
+04D7 ; Lowercase # L& CYRILLIC SMALL LETTER IE WITH BREVE
+04D9 ; Lowercase # L& CYRILLIC SMALL LETTER SCHWA
+04DB ; Lowercase # L& CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS
+04DD ; Lowercase # L& CYRILLIC SMALL LETTER ZHE WITH DIAERESIS
+04DF ; Lowercase # L& CYRILLIC SMALL LETTER ZE WITH DIAERESIS
+04E1 ; Lowercase # L& CYRILLIC SMALL LETTER ABKHASIAN DZE
+04E3 ; Lowercase # L& CYRILLIC SMALL LETTER I WITH MACRON
+04E5 ; Lowercase # L& CYRILLIC SMALL LETTER I WITH DIAERESIS
+04E7 ; Lowercase # L& CYRILLIC SMALL LETTER O WITH DIAERESIS
+04E9 ; Lowercase # L& CYRILLIC SMALL LETTER BARRED O
+04EB ; Lowercase # L& CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS
+04ED ; Lowercase # L& CYRILLIC SMALL LETTER E WITH DIAERESIS
+04EF ; Lowercase # L& CYRILLIC SMALL LETTER U WITH MACRON
+04F1 ; Lowercase # L& CYRILLIC SMALL LETTER U WITH DIAERESIS
+04F3 ; Lowercase # L& CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE
+04F5 ; Lowercase # L& CYRILLIC SMALL LETTER CHE WITH DIAERESIS
+04F7 ; Lowercase # L& CYRILLIC SMALL LETTER GHE WITH DESCENDER
+04F9 ; Lowercase # L& CYRILLIC SMALL LETTER YERU WITH DIAERESIS
+04FB ; Lowercase # L& CYRILLIC SMALL LETTER GHE WITH STROKE AND HOOK
+04FD ; Lowercase # L& CYRILLIC SMALL LETTER HA WITH HOOK
+04FF ; Lowercase # L& CYRILLIC SMALL LETTER HA WITH STROKE
+0501 ; Lowercase # L& CYRILLIC SMALL LETTER KOMI DE
+0503 ; Lowercase # L& CYRILLIC SMALL LETTER KOMI DJE
+0505 ; Lowercase # L& CYRILLIC SMALL LETTER KOMI ZJE
+0507 ; Lowercase # L& CYRILLIC SMALL LETTER KOMI DZJE
+0509 ; Lowercase # L& CYRILLIC SMALL LETTER KOMI LJE
+050B ; Lowercase # L& CYRILLIC SMALL LETTER KOMI NJE
+050D ; Lowercase # L& CYRILLIC SMALL LETTER KOMI SJE
+050F ; Lowercase # L& CYRILLIC SMALL LETTER KOMI TJE
+0511 ; Lowercase # L& CYRILLIC SMALL LETTER REVERSED ZE
+0513 ; Lowercase # L& CYRILLIC SMALL LETTER EL WITH HOOK
+0515 ; Lowercase # L& CYRILLIC SMALL LETTER LHA
+0517 ; Lowercase # L& CYRILLIC SMALL LETTER RHA
+0519 ; Lowercase # L& CYRILLIC SMALL LETTER YAE
+051B ; Lowercase # L& CYRILLIC SMALL LETTER QA
+051D ; Lowercase # L& CYRILLIC SMALL LETTER WE
+051F ; Lowercase # L& CYRILLIC SMALL LETTER ALEUT KA
+0521 ; Lowercase # L& CYRILLIC SMALL LETTER EL WITH MIDDLE HOOK
+0523 ; Lowercase # L& CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK
+0525 ; Lowercase # L& CYRILLIC SMALL LETTER PE WITH DESCENDER
+0527 ; Lowercase # L& CYRILLIC SMALL LETTER SHHA WITH DESCENDER
+0529 ; Lowercase # L& CYRILLIC SMALL LETTER EN WITH LEFT HOOK
+052B ; Lowercase # L& CYRILLIC SMALL LETTER DZZHE
+052D ; Lowercase # L& CYRILLIC SMALL LETTER DCHE
+052F ; Lowercase # L& CYRILLIC SMALL LETTER EL WITH DESCENDER
+0560..0588 ; Lowercase # L& [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE
+10D0..10FA ; Lowercase # L& [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN
+10FC ; Lowercase # Lm MODIFIER LETTER GEORGIAN NAR
+10FD..10FF ; Lowercase # L& [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN
+13F8..13FD ; Lowercase # L& [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV
+1C80..1C88 ; Lowercase # L& [9] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER UNBLENDED UK
+1C8A ; Lowercase # L& CYRILLIC SMALL LETTER TJE
+1D00..1D2B ; Lowercase # L& [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL
+1D2C..1D6A ; Lowercase # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI
+1D6B..1D77 ; Lowercase # L& [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G
+1D78 ; Lowercase # Lm MODIFIER LETTER CYRILLIC EN
+1D79..1D9A ; Lowercase # L& [34] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK
+1D9B..1DBF ; Lowercase # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA
+1E01 ; Lowercase # L& LATIN SMALL LETTER A WITH RING BELOW
+1E03 ; Lowercase # L& LATIN SMALL LETTER B WITH DOT ABOVE
+1E05 ; Lowercase # L& LATIN SMALL LETTER B WITH DOT BELOW
+1E07 ; Lowercase # L& LATIN SMALL LETTER B WITH LINE BELOW
+1E09 ; Lowercase # L& LATIN SMALL LETTER C WITH CEDILLA AND ACUTE
+1E0B ; Lowercase # L& LATIN SMALL LETTER D WITH DOT ABOVE
+1E0D ; Lowercase # L& LATIN SMALL LETTER D WITH DOT BELOW
+1E0F ; Lowercase # L& LATIN SMALL LETTER D WITH LINE BELOW
+1E11 ; Lowercase # L& LATIN SMALL LETTER D WITH CEDILLA
+1E13 ; Lowercase # L& LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW
+1E15 ; Lowercase # L& LATIN SMALL LETTER E WITH MACRON AND GRAVE
+1E17 ; Lowercase # L& LATIN SMALL LETTER E WITH MACRON AND ACUTE
+1E19 ; Lowercase # L& LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW
+1E1B ; Lowercase # L& LATIN SMALL LETTER E WITH TILDE BELOW
+1E1D ; Lowercase # L& LATIN SMALL LETTER E WITH CEDILLA AND BREVE
+1E1F ; Lowercase # L& LATIN SMALL LETTER F WITH DOT ABOVE
+1E21 ; Lowercase # L& LATIN SMALL LETTER G WITH MACRON
+1E23 ; Lowercase # L& LATIN SMALL LETTER H WITH DOT ABOVE
+1E25 ; Lowercase # L& LATIN SMALL LETTER H WITH DOT BELOW
+1E27 ; Lowercase # L& LATIN SMALL LETTER H WITH DIAERESIS
+1E29 ; Lowercase # L& LATIN SMALL LETTER H WITH CEDILLA
+1E2B ; Lowercase # L& LATIN SMALL LETTER H WITH BREVE BELOW
+1E2D ; Lowercase # L& LATIN SMALL LETTER I WITH TILDE BELOW
+1E2F ; Lowercase # L& LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE
+1E31 ; Lowercase # L& LATIN SMALL LETTER K WITH ACUTE
+1E33 ; Lowercase # L& LATIN SMALL LETTER K WITH DOT BELOW
+1E35 ; Lowercase # L& LATIN SMALL LETTER K WITH LINE BELOW
+1E37 ; Lowercase # L& LATIN SMALL LETTER L WITH DOT BELOW
+1E39 ; Lowercase # L& LATIN SMALL LETTER L WITH DOT BELOW AND MACRON
+1E3B ; Lowercase # L& LATIN SMALL LETTER L WITH LINE BELOW
+1E3D ; Lowercase # L& LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW
+1E3F ; Lowercase # L& LATIN SMALL LETTER M WITH ACUTE
+1E41 ; Lowercase # L& LATIN SMALL LETTER M WITH DOT ABOVE
+1E43 ; Lowercase # L& LATIN SMALL LETTER M WITH DOT BELOW
+1E45 ; Lowercase # L& LATIN SMALL LETTER N WITH DOT ABOVE
+1E47 ; Lowercase # L& LATIN SMALL LETTER N WITH DOT BELOW
+1E49 ; Lowercase # L& LATIN SMALL LETTER N WITH LINE BELOW
+1E4B ; Lowercase # L& LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW
+1E4D ; Lowercase # L& LATIN SMALL LETTER O WITH TILDE AND ACUTE
+1E4F ; Lowercase # L& LATIN SMALL LETTER O WITH TILDE AND DIAERESIS
+1E51 ; Lowercase # L& LATIN SMALL LETTER O WITH MACRON AND GRAVE
+1E53 ; Lowercase # L& LATIN SMALL LETTER O WITH MACRON AND ACUTE
+1E55 ; Lowercase # L& LATIN SMALL LETTER P WITH ACUTE
+1E57 ; Lowercase # L& LATIN SMALL LETTER P WITH DOT ABOVE
+1E59 ; Lowercase # L& LATIN SMALL LETTER R WITH DOT ABOVE
+1E5B ; Lowercase # L& LATIN SMALL LETTER R WITH DOT BELOW
+1E5D ; Lowercase # L& LATIN SMALL LETTER R WITH DOT BELOW AND MACRON
+1E5F ; Lowercase # L& LATIN SMALL LETTER R WITH LINE BELOW
+1E61 ; Lowercase # L& LATIN SMALL LETTER S WITH DOT ABOVE
+1E63 ; Lowercase # L& LATIN SMALL LETTER S WITH DOT BELOW
+1E65 ; Lowercase # L& LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE
+1E67 ; Lowercase # L& LATIN SMALL LETTER S WITH CARON AND DOT ABOVE
+1E69 ; Lowercase # L& LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE
+1E6B ; Lowercase # L& LATIN SMALL LETTER T WITH DOT ABOVE
+1E6D ; Lowercase # L& LATIN SMALL LETTER T WITH DOT BELOW
+1E6F ; Lowercase # L& LATIN SMALL LETTER T WITH LINE BELOW
+1E71 ; Lowercase # L& LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW
+1E73 ; Lowercase # L& LATIN SMALL LETTER U WITH DIAERESIS BELOW
+1E75 ; Lowercase # L& LATIN SMALL LETTER U WITH TILDE BELOW
+1E77 ; Lowercase # L& LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW
+1E79 ; Lowercase # L& LATIN SMALL LETTER U WITH TILDE AND ACUTE
+1E7B ; Lowercase # L& LATIN SMALL LETTER U WITH MACRON AND DIAERESIS
+1E7D ; Lowercase # L& LATIN SMALL LETTER V WITH TILDE
+1E7F ; Lowercase # L& LATIN SMALL LETTER V WITH DOT BELOW
+1E81 ; Lowercase # L& LATIN SMALL LETTER W WITH GRAVE
+1E83 ; Lowercase # L& LATIN SMALL LETTER W WITH ACUTE
+1E85 ; Lowercase # L& LATIN SMALL LETTER W WITH DIAERESIS
+1E87 ; Lowercase # L& LATIN SMALL LETTER W WITH DOT ABOVE
+1E89 ; Lowercase # L& LATIN SMALL LETTER W WITH DOT BELOW
+1E8B ; Lowercase # L& LATIN SMALL LETTER X WITH DOT ABOVE
+1E8D ; Lowercase # L& LATIN SMALL LETTER X WITH DIAERESIS
+1E8F ; Lowercase # L& LATIN SMALL LETTER Y WITH DOT ABOVE
+1E91 ; Lowercase # L& LATIN SMALL LETTER Z WITH CIRCUMFLEX
+1E93 ; Lowercase # L& LATIN SMALL LETTER Z WITH DOT BELOW
+1E95..1E9D ; Lowercase # L& [9] LATIN SMALL LETTER Z WITH LINE BELOW..LATIN SMALL LETTER LONG S WITH HIGH STROKE
+1E9F ; Lowercase # L& LATIN SMALL LETTER DELTA
+1EA1 ; Lowercase # L& LATIN SMALL LETTER A WITH DOT BELOW
+1EA3 ; Lowercase # L& LATIN SMALL LETTER A WITH HOOK ABOVE
+1EA5 ; Lowercase # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE
+1EA7 ; Lowercase # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE
+1EA9 ; Lowercase # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
+1EAB ; Lowercase # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE
+1EAD ; Lowercase # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW
+1EAF ; Lowercase # L& LATIN SMALL LETTER A WITH BREVE AND ACUTE
+1EB1 ; Lowercase # L& LATIN SMALL LETTER A WITH BREVE AND GRAVE
+1EB3 ; Lowercase # L& LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE
+1EB5 ; Lowercase # L& LATIN SMALL LETTER A WITH BREVE AND TILDE
+1EB7 ; Lowercase # L& LATIN SMALL LETTER A WITH BREVE AND DOT BELOW
+1EB9 ; Lowercase # L& LATIN SMALL LETTER E WITH DOT BELOW
+1EBB ; Lowercase # L& LATIN SMALL LETTER E WITH HOOK ABOVE
+1EBD ; Lowercase # L& LATIN SMALL LETTER E WITH TILDE
+1EBF ; Lowercase # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE
+1EC1 ; Lowercase # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE
+1EC3 ; Lowercase # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+1EC5 ; Lowercase # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE
+1EC7 ; Lowercase # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW
+1EC9 ; Lowercase # L& LATIN SMALL LETTER I WITH HOOK ABOVE
+1ECB ; Lowercase # L& LATIN SMALL LETTER I WITH DOT BELOW
+1ECD ; Lowercase # L& LATIN SMALL LETTER O WITH DOT BELOW
+1ECF ; Lowercase # L& LATIN SMALL LETTER O WITH HOOK ABOVE
+1ED1 ; Lowercase # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE
+1ED3 ; Lowercase # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE
+1ED5 ; Lowercase # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
+1ED7 ; Lowercase # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE
+1ED9 ; Lowercase # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW
+1EDB ; Lowercase # L& LATIN SMALL LETTER O WITH HORN AND ACUTE
+1EDD ; Lowercase # L& LATIN SMALL LETTER O WITH HORN AND GRAVE
+1EDF ; Lowercase # L& LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE
+1EE1 ; Lowercase # L& LATIN SMALL LETTER O WITH HORN AND TILDE
+1EE3 ; Lowercase # L& LATIN SMALL LETTER O WITH HORN AND DOT BELOW
+1EE5 ; Lowercase # L& LATIN SMALL LETTER U WITH DOT BELOW
+1EE7 ; Lowercase # L& LATIN SMALL LETTER U WITH HOOK ABOVE
+1EE9 ; Lowercase # L& LATIN SMALL LETTER U WITH HORN AND ACUTE
+1EEB ; Lowercase # L& LATIN SMALL LETTER U WITH HORN AND GRAVE
+1EED ; Lowercase # L& LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE
+1EEF ; Lowercase # L& LATIN SMALL LETTER U WITH HORN AND TILDE
+1EF1 ; Lowercase # L& LATIN SMALL LETTER U WITH HORN AND DOT BELOW
+1EF3 ; Lowercase # L& LATIN SMALL LETTER Y WITH GRAVE
+1EF5 ; Lowercase # L& LATIN SMALL LETTER Y WITH DOT BELOW
+1EF7 ; Lowercase # L& LATIN SMALL LETTER Y WITH HOOK ABOVE
+1EF9 ; Lowercase # L& LATIN SMALL LETTER Y WITH TILDE
+1EFB ; Lowercase # L& LATIN SMALL LETTER MIDDLE-WELSH LL
+1EFD ; Lowercase # L& LATIN SMALL LETTER MIDDLE-WELSH V
+1EFF..1F07 ; Lowercase # L& [9] LATIN SMALL LETTER Y WITH LOOP..GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI
+1F10..1F15 ; Lowercase # L& [6] GREEK SMALL LETTER EPSILON WITH PSILI..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA
+1F20..1F27 ; Lowercase # L& [8] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI
+1F30..1F37 ; Lowercase # L& [8] GREEK SMALL LETTER IOTA WITH PSILI..GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI
+1F40..1F45 ; Lowercase # L& [6] GREEK SMALL LETTER OMICRON WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA
+1F50..1F57 ; Lowercase # L& [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F60..1F67 ; Lowercase # L& [8] GREEK SMALL LETTER OMEGA WITH PSILI..GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI
+1F70..1F7D ; Lowercase # L& [14] GREEK SMALL LETTER ALPHA WITH VARIA..GREEK SMALL LETTER OMEGA WITH OXIA
+1F80..1F87 ; Lowercase # L& [8] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F90..1F97 ; Lowercase # L& [8] GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1FA0..1FA7 ; Lowercase # L& [8] GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1FB0..1FB4 ; Lowercase # L& [5] GREEK SMALL LETTER ALPHA WITH VRACHY..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6..1FB7 ; Lowercase # L& [2] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FBE ; Lowercase # L& GREEK PROSGEGRAMMENI
+1FC2..1FC4 ; Lowercase # L& [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6..1FC7 ; Lowercase # L& [2] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FD0..1FD3 ; Lowercase # L& [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6..1FD7 ; Lowercase # L& [2] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI
+1FE0..1FE7 ; Lowercase # L& [8] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI
+1FF2..1FF4 ; Lowercase # L& [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6..1FF7 ; Lowercase # L& [2] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
+2071 ; Lowercase # Lm SUPERSCRIPT LATIN SMALL LETTER I
+207F ; Lowercase # Lm SUPERSCRIPT LATIN SMALL LETTER N
+2090..209C ; Lowercase # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T
+210A ; Lowercase # L& SCRIPT SMALL G
+210E..210F ; Lowercase # L& [2] PLANCK CONSTANT..PLANCK CONSTANT OVER TWO PI
+2113 ; Lowercase # L& SCRIPT SMALL L
+212F ; Lowercase # L& SCRIPT SMALL E
+2134 ; Lowercase # L& SCRIPT SMALL O
+2139 ; Lowercase # L& INFORMATION SOURCE
+213C..213D ; Lowercase # L& [2] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK SMALL GAMMA
+2146..2149 ; Lowercase # L& [4] DOUBLE-STRUCK ITALIC SMALL D..DOUBLE-STRUCK ITALIC SMALL J
+214E ; Lowercase # L& TURNED SMALL F
+2170..217F ; Lowercase # Nl [16] SMALL ROMAN NUMERAL ONE..SMALL ROMAN NUMERAL ONE THOUSAND
+2184 ; Lowercase # L& LATIN SMALL LETTER REVERSED C
+24D0..24E9 ; Lowercase # So [26] CIRCLED LATIN SMALL LETTER A..CIRCLED LATIN SMALL LETTER Z
+2C30..2C5F ; Lowercase # L& [48] GLAGOLITIC SMALL LETTER AZU..GLAGOLITIC SMALL LETTER CAUDATE CHRIVI
+2C61 ; Lowercase # L& LATIN SMALL LETTER L WITH DOUBLE BAR
+2C65..2C66 ; Lowercase # L& [2] LATIN SMALL LETTER A WITH STROKE..LATIN SMALL LETTER T WITH DIAGONAL STROKE
+2C68 ; Lowercase # L& LATIN SMALL LETTER H WITH DESCENDER
+2C6A ; Lowercase # L& LATIN SMALL LETTER K WITH DESCENDER
+2C6C ; Lowercase # L& LATIN SMALL LETTER Z WITH DESCENDER
+2C71 ; Lowercase # L& LATIN SMALL LETTER V WITH RIGHT HOOK
+2C73..2C74 ; Lowercase # L& [2] LATIN SMALL LETTER W WITH HOOK..LATIN SMALL LETTER V WITH CURL
+2C76..2C7B ; Lowercase # L& [6] LATIN SMALL LETTER HALF H..LATIN LETTER SMALL CAPITAL TURNED E
+2C7C..2C7D ; Lowercase # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V
+2C81 ; Lowercase # L& COPTIC SMALL LETTER ALFA
+2C83 ; Lowercase # L& COPTIC SMALL LETTER VIDA
+2C85 ; Lowercase # L& COPTIC SMALL LETTER GAMMA
+2C87 ; Lowercase # L& COPTIC SMALL LETTER DALDA
+2C89 ; Lowercase # L& COPTIC SMALL LETTER EIE
+2C8B ; Lowercase # L& COPTIC SMALL LETTER SOU
+2C8D ; Lowercase # L& COPTIC SMALL LETTER ZATA
+2C8F ; Lowercase # L& COPTIC SMALL LETTER HATE
+2C91 ; Lowercase # L& COPTIC SMALL LETTER THETHE
+2C93 ; Lowercase # L& COPTIC SMALL LETTER IAUDA
+2C95 ; Lowercase # L& COPTIC SMALL LETTER KAPA
+2C97 ; Lowercase # L& COPTIC SMALL LETTER LAULA
+2C99 ; Lowercase # L& COPTIC SMALL LETTER MI
+2C9B ; Lowercase # L& COPTIC SMALL LETTER NI
+2C9D ; Lowercase # L& COPTIC SMALL LETTER KSI
+2C9F ; Lowercase # L& COPTIC SMALL LETTER O
+2CA1 ; Lowercase # L& COPTIC SMALL LETTER PI
+2CA3 ; Lowercase # L& COPTIC SMALL LETTER RO
+2CA5 ; Lowercase # L& COPTIC SMALL LETTER SIMA
+2CA7 ; Lowercase # L& COPTIC SMALL LETTER TAU
+2CA9 ; Lowercase # L& COPTIC SMALL LETTER UA
+2CAB ; Lowercase # L& COPTIC SMALL LETTER FI
+2CAD ; Lowercase # L& COPTIC SMALL LETTER KHI
+2CAF ; Lowercase # L& COPTIC SMALL LETTER PSI
+2CB1 ; Lowercase # L& COPTIC SMALL LETTER OOU
+2CB3 ; Lowercase # L& COPTIC SMALL LETTER DIALECT-P ALEF
+2CB5 ; Lowercase # L& COPTIC SMALL LETTER OLD COPTIC AIN
+2CB7 ; Lowercase # L& COPTIC SMALL LETTER CRYPTOGRAMMIC EIE
+2CB9 ; Lowercase # L& COPTIC SMALL LETTER DIALECT-P KAPA
+2CBB ; Lowercase # L& COPTIC SMALL LETTER DIALECT-P NI
+2CBD ; Lowercase # L& COPTIC SMALL LETTER CRYPTOGRAMMIC NI
+2CBF ; Lowercase # L& COPTIC SMALL LETTER OLD COPTIC OOU
+2CC1 ; Lowercase # L& COPTIC SMALL LETTER SAMPI
+2CC3 ; Lowercase # L& COPTIC SMALL LETTER CROSSED SHEI
+2CC5 ; Lowercase # L& COPTIC SMALL LETTER OLD COPTIC SHEI
+2CC7 ; Lowercase # L& COPTIC SMALL LETTER OLD COPTIC ESH
+2CC9 ; Lowercase # L& COPTIC SMALL LETTER AKHMIMIC KHEI
+2CCB ; Lowercase # L& COPTIC SMALL LETTER DIALECT-P HORI
+2CCD ; Lowercase # L& COPTIC SMALL LETTER OLD COPTIC HORI
+2CCF ; Lowercase # L& COPTIC SMALL LETTER OLD COPTIC HA
+2CD1 ; Lowercase # L& COPTIC SMALL LETTER L-SHAPED HA
+2CD3 ; Lowercase # L& COPTIC SMALL LETTER OLD COPTIC HEI
+2CD5 ; Lowercase # L& COPTIC SMALL LETTER OLD COPTIC HAT
+2CD7 ; Lowercase # L& COPTIC SMALL LETTER OLD COPTIC GANGIA
+2CD9 ; Lowercase # L& COPTIC SMALL LETTER OLD COPTIC DJA
+2CDB ; Lowercase # L& COPTIC SMALL LETTER OLD COPTIC SHIMA
+2CDD ; Lowercase # L& COPTIC SMALL LETTER OLD NUBIAN SHIMA
+2CDF ; Lowercase # L& COPTIC SMALL LETTER OLD NUBIAN NGI
+2CE1 ; Lowercase # L& COPTIC SMALL LETTER OLD NUBIAN NYI
+2CE3..2CE4 ; Lowercase # L& [2] COPTIC SMALL LETTER OLD NUBIAN WAU..COPTIC SYMBOL KAI
+2CEC ; Lowercase # L& COPTIC SMALL LETTER CRYPTOGRAMMIC SHEI
+2CEE ; Lowercase # L& COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA
+2CF3 ; Lowercase # L& COPTIC SMALL LETTER BOHAIRIC KHEI
+2D00..2D25 ; Lowercase # L& [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE
+2D27 ; Lowercase # L& GEORGIAN SMALL LETTER YN
+2D2D ; Lowercase # L& GEORGIAN SMALL LETTER AEN
+A641 ; Lowercase # L& CYRILLIC SMALL LETTER ZEMLYA
+A643 ; Lowercase # L& CYRILLIC SMALL LETTER DZELO
+A645 ; Lowercase # L& CYRILLIC SMALL LETTER REVERSED DZE
+A647 ; Lowercase # L& CYRILLIC SMALL LETTER IOTA
+A649 ; Lowercase # L& CYRILLIC SMALL LETTER DJERV
+A64B ; Lowercase # L& CYRILLIC SMALL LETTER MONOGRAPH UK
+A64D ; Lowercase # L& CYRILLIC SMALL LETTER BROAD OMEGA
+A64F ; Lowercase # L& CYRILLIC SMALL LETTER NEUTRAL YER
+A651 ; Lowercase # L& CYRILLIC SMALL LETTER YERU WITH BACK YER
+A653 ; Lowercase # L& CYRILLIC SMALL LETTER IOTIFIED YAT
+A655 ; Lowercase # L& CYRILLIC SMALL LETTER REVERSED YU
+A657 ; Lowercase # L& CYRILLIC SMALL LETTER IOTIFIED A
+A659 ; Lowercase # L& CYRILLIC SMALL LETTER CLOSED LITTLE YUS
+A65B ; Lowercase # L& CYRILLIC SMALL LETTER BLENDED YUS
+A65D ; Lowercase # L& CYRILLIC SMALL LETTER IOTIFIED CLOSED LITTLE YUS
+A65F ; Lowercase # L& CYRILLIC SMALL LETTER YN
+A661 ; Lowercase # L& CYRILLIC SMALL LETTER REVERSED TSE
+A663 ; Lowercase # L& CYRILLIC SMALL LETTER SOFT DE
+A665 ; Lowercase # L& CYRILLIC SMALL LETTER SOFT EL
+A667 ; Lowercase # L& CYRILLIC SMALL LETTER SOFT EM
+A669 ; Lowercase # L& CYRILLIC SMALL LETTER MONOCULAR O
+A66B ; Lowercase # L& CYRILLIC SMALL LETTER BINOCULAR O
+A66D ; Lowercase # L& CYRILLIC SMALL LETTER DOUBLE MONOCULAR O
+A681 ; Lowercase # L& CYRILLIC SMALL LETTER DWE
+A683 ; Lowercase # L& CYRILLIC SMALL LETTER DZWE
+A685 ; Lowercase # L& CYRILLIC SMALL LETTER ZHWE
+A687 ; Lowercase # L& CYRILLIC SMALL LETTER CCHE
+A689 ; Lowercase # L& CYRILLIC SMALL LETTER DZZE
+A68B ; Lowercase # L& CYRILLIC SMALL LETTER TE WITH MIDDLE HOOK
+A68D ; Lowercase # L& CYRILLIC SMALL LETTER TWE
+A68F ; Lowercase # L& CYRILLIC SMALL LETTER TSWE
+A691 ; Lowercase # L& CYRILLIC SMALL LETTER TSSE
+A693 ; Lowercase # L& CYRILLIC SMALL LETTER TCHE
+A695 ; Lowercase # L& CYRILLIC SMALL LETTER HWE
+A697 ; Lowercase # L& CYRILLIC SMALL LETTER SHWE
+A699 ; Lowercase # L& CYRILLIC SMALL LETTER DOUBLE O
+A69B ; Lowercase # L& CYRILLIC SMALL LETTER CROSSED O
+A69C..A69D ; Lowercase # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN
+A723 ; Lowercase # L& LATIN SMALL LETTER EGYPTOLOGICAL ALEF
+A725 ; Lowercase # L& LATIN SMALL LETTER EGYPTOLOGICAL AIN
+A727 ; Lowercase # L& LATIN SMALL LETTER HENG
+A729 ; Lowercase # L& LATIN SMALL LETTER TZ
+A72B ; Lowercase # L& LATIN SMALL LETTER TRESILLO
+A72D ; Lowercase # L& LATIN SMALL LETTER CUATRILLO
+A72F..A731 ; Lowercase # L& [3] LATIN SMALL LETTER CUATRILLO WITH COMMA..LATIN LETTER SMALL CAPITAL S
+A733 ; Lowercase # L& LATIN SMALL LETTER AA
+A735 ; Lowercase # L& LATIN SMALL LETTER AO
+A737 ; Lowercase # L& LATIN SMALL LETTER AU
+A739 ; Lowercase # L& LATIN SMALL LETTER AV
+A73B ; Lowercase # L& LATIN SMALL LETTER AV WITH HORIZONTAL BAR
+A73D ; Lowercase # L& LATIN SMALL LETTER AY
+A73F ; Lowercase # L& LATIN SMALL LETTER REVERSED C WITH DOT
+A741 ; Lowercase # L& LATIN SMALL LETTER K WITH STROKE
+A743 ; Lowercase # L& LATIN SMALL LETTER K WITH DIAGONAL STROKE
+A745 ; Lowercase # L& LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE
+A747 ; Lowercase # L& LATIN SMALL LETTER BROKEN L
+A749 ; Lowercase # L& LATIN SMALL LETTER L WITH HIGH STROKE
+A74B ; Lowercase # L& LATIN SMALL LETTER O WITH LONG STROKE OVERLAY
+A74D ; Lowercase # L& LATIN SMALL LETTER O WITH LOOP
+A74F ; Lowercase # L& LATIN SMALL LETTER OO
+A751 ; Lowercase # L& LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER
+A753 ; Lowercase # L& LATIN SMALL LETTER P WITH FLOURISH
+A755 ; Lowercase # L& LATIN SMALL LETTER P WITH SQUIRREL TAIL
+A757 ; Lowercase # L& LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER
+A759 ; Lowercase # L& LATIN SMALL LETTER Q WITH DIAGONAL STROKE
+A75B ; Lowercase # L& LATIN SMALL LETTER R ROTUNDA
+A75D ; Lowercase # L& LATIN SMALL LETTER RUM ROTUNDA
+A75F ; Lowercase # L& LATIN SMALL LETTER V WITH DIAGONAL STROKE
+A761 ; Lowercase # L& LATIN SMALL LETTER VY
+A763 ; Lowercase # L& LATIN SMALL LETTER VISIGOTHIC Z
+A765 ; Lowercase # L& LATIN SMALL LETTER THORN WITH STROKE
+A767 ; Lowercase # L& LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER
+A769 ; Lowercase # L& LATIN SMALL LETTER VEND
+A76B ; Lowercase # L& LATIN SMALL LETTER ET
+A76D ; Lowercase # L& LATIN SMALL LETTER IS
+A76F ; Lowercase # L& LATIN SMALL LETTER CON
+A770 ; Lowercase # Lm MODIFIER LETTER US
+A771..A778 ; Lowercase # L& [8] LATIN SMALL LETTER DUM..LATIN SMALL LETTER UM
+A77A ; Lowercase # L& LATIN SMALL LETTER INSULAR D
+A77C ; Lowercase # L& LATIN SMALL LETTER INSULAR F
+A77F ; Lowercase # L& LATIN SMALL LETTER TURNED INSULAR G
+A781 ; Lowercase # L& LATIN SMALL LETTER TURNED L
+A783 ; Lowercase # L& LATIN SMALL LETTER INSULAR R
+A785 ; Lowercase # L& LATIN SMALL LETTER INSULAR S
+A787 ; Lowercase # L& LATIN SMALL LETTER INSULAR T
+A78C ; Lowercase # L& LATIN SMALL LETTER SALTILLO
+A78E ; Lowercase # L& LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT
+A791 ; Lowercase # L& LATIN SMALL LETTER N WITH DESCENDER
+A793..A795 ; Lowercase # L& [3] LATIN SMALL LETTER C WITH BAR..LATIN SMALL LETTER H WITH PALATAL HOOK
+A797 ; Lowercase # L& LATIN SMALL LETTER B WITH FLOURISH
+A799 ; Lowercase # L& LATIN SMALL LETTER F WITH STROKE
+A79B ; Lowercase # L& LATIN SMALL LETTER VOLAPUK AE
+A79D ; Lowercase # L& LATIN SMALL LETTER VOLAPUK OE
+A79F ; Lowercase # L& LATIN SMALL LETTER VOLAPUK UE
+A7A1 ; Lowercase # L& LATIN SMALL LETTER G WITH OBLIQUE STROKE
+A7A3 ; Lowercase # L& LATIN SMALL LETTER K WITH OBLIQUE STROKE
+A7A5 ; Lowercase # L& LATIN SMALL LETTER N WITH OBLIQUE STROKE
+A7A7 ; Lowercase # L& LATIN SMALL LETTER R WITH OBLIQUE STROKE
+A7A9 ; Lowercase # L& LATIN SMALL LETTER S WITH OBLIQUE STROKE
+A7AF ; Lowercase # L& LATIN LETTER SMALL CAPITAL Q
+A7B5 ; Lowercase # L& LATIN SMALL LETTER BETA
+A7B7 ; Lowercase # L& LATIN SMALL LETTER OMEGA
+A7B9 ; Lowercase # L& LATIN SMALL LETTER U WITH STROKE
+A7BB ; Lowercase # L& LATIN SMALL LETTER GLOTTAL A
+A7BD ; Lowercase # L& LATIN SMALL LETTER GLOTTAL I
+A7BF ; Lowercase # L& LATIN SMALL LETTER GLOTTAL U
+A7C1 ; Lowercase # L& LATIN SMALL LETTER OLD POLISH O
+A7C3 ; Lowercase # L& LATIN SMALL LETTER ANGLICANA W
+A7C8 ; Lowercase # L& LATIN SMALL LETTER D WITH SHORT STROKE OVERLAY
+A7CA ; Lowercase # L& LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY
+A7CD ; Lowercase # L& LATIN SMALL LETTER S WITH DIAGONAL STROKE
+A7D1 ; Lowercase # L& LATIN SMALL LETTER CLOSED INSULAR G
+A7D3 ; Lowercase # L& LATIN SMALL LETTER DOUBLE THORN
+A7D5 ; Lowercase # L& LATIN SMALL LETTER DOUBLE WYNN
+A7D7 ; Lowercase # L& LATIN SMALL LETTER MIDDLE SCOTS S
+A7D9 ; Lowercase # L& LATIN SMALL LETTER SIGMOID S
+A7DB ; Lowercase # L& LATIN SMALL LETTER LAMBDA
+A7F2..A7F4 ; Lowercase # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q
+A7F6 ; Lowercase # L& LATIN SMALL LETTER REVERSED HALF H
+A7F8..A7F9 ; Lowercase # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE
+A7FA ; Lowercase # L& LATIN LETTER SMALL CAPITAL TURNED M
+AB30..AB5A ; Lowercase # L& [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG
+AB5C..AB5F ; Lowercase # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK
+AB60..AB68 ; Lowercase # L& [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE
+AB69 ; Lowercase # Lm MODIFIER LETTER SMALL TURNED W
+AB70..ABBF ; Lowercase # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA
+FB00..FB06 ; Lowercase # L& [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST
+FB13..FB17 ; Lowercase # L& [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH
+FF41..FF5A ; Lowercase # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z
+10428..1044F ; Lowercase # L& [40] DESERET SMALL LETTER LONG I..DESERET SMALL LETTER EW
+104D8..104FB ; Lowercase # L& [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA
+10597..105A1 ; Lowercase # L& [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA
+105A3..105B1 ; Lowercase # L& [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE
+105B3..105B9 ; Lowercase # L& [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE
+105BB..105BC ; Lowercase # L& [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE
+10780 ; Lowercase # Lm MODIFIER LETTER SMALL CAPITAL AA
+10783..10785 ; Lowercase # Lm [3] MODIFIER LETTER SMALL AE..MODIFIER LETTER SMALL B WITH HOOK
+10787..107B0 ; Lowercase # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK
+107B2..107BA ; Lowercase # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL
+10CC0..10CF2 ; Lowercase # L& [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US
+10D70..10D85 ; Lowercase # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA
+118C0..118DF ; Lowercase # L& [32] WARANG CITI SMALL LETTER NGAA..WARANG CITI SMALL LETTER VIYO
+16E60..16E7F ; Lowercase # L& [32] MEDEFAIDRIN SMALL LETTER M..MEDEFAIDRIN SMALL LETTER Y
+1D41A..1D433 ; Lowercase # L& [26] MATHEMATICAL BOLD SMALL A..MATHEMATICAL BOLD SMALL Z
+1D44E..1D454 ; Lowercase # L& [7] MATHEMATICAL ITALIC SMALL A..MATHEMATICAL ITALIC SMALL G
+1D456..1D467 ; Lowercase # L& [18] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL ITALIC SMALL Z
+1D482..1D49B ; Lowercase # L& [26] MATHEMATICAL BOLD ITALIC SMALL A..MATHEMATICAL BOLD ITALIC SMALL Z
+1D4B6..1D4B9 ; Lowercase # L& [4] MATHEMATICAL SCRIPT SMALL A..MATHEMATICAL SCRIPT SMALL D
+1D4BB ; Lowercase # L& MATHEMATICAL SCRIPT SMALL F
+1D4BD..1D4C3 ; Lowercase # L& [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N
+1D4C5..1D4CF ; Lowercase # L& [11] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL SCRIPT SMALL Z
+1D4EA..1D503 ; Lowercase # L& [26] MATHEMATICAL BOLD SCRIPT SMALL A..MATHEMATICAL BOLD SCRIPT SMALL Z
+1D51E..1D537 ; Lowercase # L& [26] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL FRAKTUR SMALL Z
+1D552..1D56B ; Lowercase # L& [26] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL DOUBLE-STRUCK SMALL Z
+1D586..1D59F ; Lowercase # L& [26] MATHEMATICAL BOLD FRAKTUR SMALL A..MATHEMATICAL BOLD FRAKTUR SMALL Z
+1D5BA..1D5D3 ; Lowercase # L& [26] MATHEMATICAL SANS-SERIF SMALL A..MATHEMATICAL SANS-SERIF SMALL Z
+1D5EE..1D607 ; Lowercase # L& [26] MATHEMATICAL SANS-SERIF BOLD SMALL A..MATHEMATICAL SANS-SERIF BOLD SMALL Z
+1D622..1D63B ; Lowercase # L& [26] MATHEMATICAL SANS-SERIF ITALIC SMALL A..MATHEMATICAL SANS-SERIF ITALIC SMALL Z
+1D656..1D66F ; Lowercase # L& [26] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL A..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Z
+1D68A..1D6A5 ; Lowercase # L& [28] MATHEMATICAL MONOSPACE SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J
+1D6C2..1D6DA ; Lowercase # L& [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA
+1D6DC..1D6E1 ; Lowercase # L& [6] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL BOLD PI SYMBOL
+1D6FC..1D714 ; Lowercase # L& [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA
+1D716..1D71B ; Lowercase # L& [6] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL ITALIC PI SYMBOL
+1D736..1D74E ; Lowercase # L& [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA
+1D750..1D755 ; Lowercase # L& [6] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC PI SYMBOL
+1D770..1D788 ; Lowercase # L& [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA
+1D78A..1D78F ; Lowercase # L& [6] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD PI SYMBOL
+1D7AA..1D7C2 ; Lowercase # L& [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA
+1D7C4..1D7C9 ; Lowercase # L& [6] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC PI SYMBOL
+1D7CB ; Lowercase # L& MATHEMATICAL BOLD SMALL DIGAMMA
+1DF00..1DF09 ; Lowercase # L& [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK
+1DF0B..1DF1E ; Lowercase # L& [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL
+1DF25..1DF2A ; Lowercase # L& [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK
+1E030..1E06D ; Lowercase # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE
+1E922..1E943 ; Lowercase # L& [34] ADLAM SMALL LETTER ALIF..ADLAM SMALL LETTER SHA
+
+# Total code points: 2569
+
+# ================================================
+
+# Derived Property: Uppercase
+# Generated from: Lu + Other_Uppercase
+
+0041..005A ; Uppercase # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+00C0..00D6 ; Uppercase # L& [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS
+00D8..00DE ; Uppercase # L& [7] LATIN CAPITAL LETTER O WITH STROKE..LATIN CAPITAL LETTER THORN
+0100 ; Uppercase # L& LATIN CAPITAL LETTER A WITH MACRON
+0102 ; Uppercase # L& LATIN CAPITAL LETTER A WITH BREVE
+0104 ; Uppercase # L& LATIN CAPITAL LETTER A WITH OGONEK
+0106 ; Uppercase # L& LATIN CAPITAL LETTER C WITH ACUTE
+0108 ; Uppercase # L& LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+010A ; Uppercase # L& LATIN CAPITAL LETTER C WITH DOT ABOVE
+010C ; Uppercase # L& LATIN CAPITAL LETTER C WITH CARON
+010E ; Uppercase # L& LATIN CAPITAL LETTER D WITH CARON
+0110 ; Uppercase # L& LATIN CAPITAL LETTER D WITH STROKE
+0112 ; Uppercase # L& LATIN CAPITAL LETTER E WITH MACRON
+0114 ; Uppercase # L& LATIN CAPITAL LETTER E WITH BREVE
+0116 ; Uppercase # L& LATIN CAPITAL LETTER E WITH DOT ABOVE
+0118 ; Uppercase # L& LATIN CAPITAL LETTER E WITH OGONEK
+011A ; Uppercase # L& LATIN CAPITAL LETTER E WITH CARON
+011C ; Uppercase # L& LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+011E ; Uppercase # L& LATIN CAPITAL LETTER G WITH BREVE
+0120 ; Uppercase # L& LATIN CAPITAL LETTER G WITH DOT ABOVE
+0122 ; Uppercase # L& LATIN CAPITAL LETTER G WITH CEDILLA
+0124 ; Uppercase # L& LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+0126 ; Uppercase # L& LATIN CAPITAL LETTER H WITH STROKE
+0128 ; Uppercase # L& LATIN CAPITAL LETTER I WITH TILDE
+012A ; Uppercase # L& LATIN CAPITAL LETTER I WITH MACRON
+012C ; Uppercase # L& LATIN CAPITAL LETTER I WITH BREVE
+012E ; Uppercase # L& LATIN CAPITAL LETTER I WITH OGONEK
+0130 ; Uppercase # L& LATIN CAPITAL LETTER I WITH DOT ABOVE
+0132 ; Uppercase # L& LATIN CAPITAL LIGATURE IJ
+0134 ; Uppercase # L& LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+0136 ; Uppercase # L& LATIN CAPITAL LETTER K WITH CEDILLA
+0139 ; Uppercase # L& LATIN CAPITAL LETTER L WITH ACUTE
+013B ; Uppercase # L& LATIN CAPITAL LETTER L WITH CEDILLA
+013D ; Uppercase # L& LATIN CAPITAL LETTER L WITH CARON
+013F ; Uppercase # L& LATIN CAPITAL LETTER L WITH MIDDLE DOT
+0141 ; Uppercase # L& LATIN CAPITAL LETTER L WITH STROKE
+0143 ; Uppercase # L& LATIN CAPITAL LETTER N WITH ACUTE
+0145 ; Uppercase # L& LATIN CAPITAL LETTER N WITH CEDILLA
+0147 ; Uppercase # L& LATIN CAPITAL LETTER N WITH CARON
+014A ; Uppercase # L& LATIN CAPITAL LETTER ENG
+014C ; Uppercase # L& LATIN CAPITAL LETTER O WITH MACRON
+014E ; Uppercase # L& LATIN CAPITAL LETTER O WITH BREVE
+0150 ; Uppercase # L& LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+0152 ; Uppercase # L& LATIN CAPITAL LIGATURE OE
+0154 ; Uppercase # L& LATIN CAPITAL LETTER R WITH ACUTE
+0156 ; Uppercase # L& LATIN CAPITAL LETTER R WITH CEDILLA
+0158 ; Uppercase # L& LATIN CAPITAL LETTER R WITH CARON
+015A ; Uppercase # L& LATIN CAPITAL LETTER S WITH ACUTE
+015C ; Uppercase # L& LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+015E ; Uppercase # L& LATIN CAPITAL LETTER S WITH CEDILLA
+0160 ; Uppercase # L& LATIN CAPITAL LETTER S WITH CARON
+0162 ; Uppercase # L& LATIN CAPITAL LETTER T WITH CEDILLA
+0164 ; Uppercase # L& LATIN CAPITAL LETTER T WITH CARON
+0166 ; Uppercase # L& LATIN CAPITAL LETTER T WITH STROKE
+0168 ; Uppercase # L& LATIN CAPITAL LETTER U WITH TILDE
+016A ; Uppercase # L& LATIN CAPITAL LETTER U WITH MACRON
+016C ; Uppercase # L& LATIN CAPITAL LETTER U WITH BREVE
+016E ; Uppercase # L& LATIN CAPITAL LETTER U WITH RING ABOVE
+0170 ; Uppercase # L& LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+0172 ; Uppercase # L& LATIN CAPITAL LETTER U WITH OGONEK
+0174 ; Uppercase # L& LATIN CAPITAL LETTER W WITH CIRCUMFLEX
+0176 ; Uppercase # L& LATIN CAPITAL LETTER Y WITH CIRCUMFLEX
+0178..0179 ; Uppercase # L& [2] LATIN CAPITAL LETTER Y WITH DIAERESIS..LATIN CAPITAL LETTER Z WITH ACUTE
+017B ; Uppercase # L& LATIN CAPITAL LETTER Z WITH DOT ABOVE
+017D ; Uppercase # L& LATIN CAPITAL LETTER Z WITH CARON
+0181..0182 ; Uppercase # L& [2] LATIN CAPITAL LETTER B WITH HOOK..LATIN CAPITAL LETTER B WITH TOPBAR
+0184 ; Uppercase # L& LATIN CAPITAL LETTER TONE SIX
+0186..0187 ; Uppercase # L& [2] LATIN CAPITAL LETTER OPEN O..LATIN CAPITAL LETTER C WITH HOOK
+0189..018B ; Uppercase # L& [3] LATIN CAPITAL LETTER AFRICAN D..LATIN CAPITAL LETTER D WITH TOPBAR
+018E..0191 ; Uppercase # L& [4] LATIN CAPITAL LETTER REVERSED E..LATIN CAPITAL LETTER F WITH HOOK
+0193..0194 ; Uppercase # L& [2] LATIN CAPITAL LETTER G WITH HOOK..LATIN CAPITAL LETTER GAMMA
+0196..0198 ; Uppercase # L& [3] LATIN CAPITAL LETTER IOTA..LATIN CAPITAL LETTER K WITH HOOK
+019C..019D ; Uppercase # L& [2] LATIN CAPITAL LETTER TURNED M..LATIN CAPITAL LETTER N WITH LEFT HOOK
+019F..01A0 ; Uppercase # L& [2] LATIN CAPITAL LETTER O WITH MIDDLE TILDE..LATIN CAPITAL LETTER O WITH HORN
+01A2 ; Uppercase # L& LATIN CAPITAL LETTER OI
+01A4 ; Uppercase # L& LATIN CAPITAL LETTER P WITH HOOK
+01A6..01A7 ; Uppercase # L& [2] LATIN LETTER YR..LATIN CAPITAL LETTER TONE TWO
+01A9 ; Uppercase # L& LATIN CAPITAL LETTER ESH
+01AC ; Uppercase # L& LATIN CAPITAL LETTER T WITH HOOK
+01AE..01AF ; Uppercase # L& [2] LATIN CAPITAL LETTER T WITH RETROFLEX HOOK..LATIN CAPITAL LETTER U WITH HORN
+01B1..01B3 ; Uppercase # L& [3] LATIN CAPITAL LETTER UPSILON..LATIN CAPITAL LETTER Y WITH HOOK
+01B5 ; Uppercase # L& LATIN CAPITAL LETTER Z WITH STROKE
+01B7..01B8 ; Uppercase # L& [2] LATIN CAPITAL LETTER EZH..LATIN CAPITAL LETTER EZH REVERSED
+01BC ; Uppercase # L& LATIN CAPITAL LETTER TONE FIVE
+01C4 ; Uppercase # L& LATIN CAPITAL LETTER DZ WITH CARON
+01C7 ; Uppercase # L& LATIN CAPITAL LETTER LJ
+01CA ; Uppercase # L& LATIN CAPITAL LETTER NJ
+01CD ; Uppercase # L& LATIN CAPITAL LETTER A WITH CARON
+01CF ; Uppercase # L& LATIN CAPITAL LETTER I WITH CARON
+01D1 ; Uppercase # L& LATIN CAPITAL LETTER O WITH CARON
+01D3 ; Uppercase # L& LATIN CAPITAL LETTER U WITH CARON
+01D5 ; Uppercase # L& LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON
+01D7 ; Uppercase # L& LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE
+01D9 ; Uppercase # L& LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON
+01DB ; Uppercase # L& LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE
+01DE ; Uppercase # L& LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON
+01E0 ; Uppercase # L& LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON
+01E2 ; Uppercase # L& LATIN CAPITAL LETTER AE WITH MACRON
+01E4 ; Uppercase # L& LATIN CAPITAL LETTER G WITH STROKE
+01E6 ; Uppercase # L& LATIN CAPITAL LETTER G WITH CARON
+01E8 ; Uppercase # L& LATIN CAPITAL LETTER K WITH CARON
+01EA ; Uppercase # L& LATIN CAPITAL LETTER O WITH OGONEK
+01EC ; Uppercase # L& LATIN CAPITAL LETTER O WITH OGONEK AND MACRON
+01EE ; Uppercase # L& LATIN CAPITAL LETTER EZH WITH CARON
+01F1 ; Uppercase # L& LATIN CAPITAL LETTER DZ
+01F4 ; Uppercase # L& LATIN CAPITAL LETTER G WITH ACUTE
+01F6..01F8 ; Uppercase # L& [3] LATIN CAPITAL LETTER HWAIR..LATIN CAPITAL LETTER N WITH GRAVE
+01FA ; Uppercase # L& LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE
+01FC ; Uppercase # L& LATIN CAPITAL LETTER AE WITH ACUTE
+01FE ; Uppercase # L& LATIN CAPITAL LETTER O WITH STROKE AND ACUTE
+0200 ; Uppercase # L& LATIN CAPITAL LETTER A WITH DOUBLE GRAVE
+0202 ; Uppercase # L& LATIN CAPITAL LETTER A WITH INVERTED BREVE
+0204 ; Uppercase # L& LATIN CAPITAL LETTER E WITH DOUBLE GRAVE
+0206 ; Uppercase # L& LATIN CAPITAL LETTER E WITH INVERTED BREVE
+0208 ; Uppercase # L& LATIN CAPITAL LETTER I WITH DOUBLE GRAVE
+020A ; Uppercase # L& LATIN CAPITAL LETTER I WITH INVERTED BREVE
+020C ; Uppercase # L& LATIN CAPITAL LETTER O WITH DOUBLE GRAVE
+020E ; Uppercase # L& LATIN CAPITAL LETTER O WITH INVERTED BREVE
+0210 ; Uppercase # L& LATIN CAPITAL LETTER R WITH DOUBLE GRAVE
+0212 ; Uppercase # L& LATIN CAPITAL LETTER R WITH INVERTED BREVE
+0214 ; Uppercase # L& LATIN CAPITAL LETTER U WITH DOUBLE GRAVE
+0216 ; Uppercase # L& LATIN CAPITAL LETTER U WITH INVERTED BREVE
+0218 ; Uppercase # L& LATIN CAPITAL LETTER S WITH COMMA BELOW
+021A ; Uppercase # L& LATIN CAPITAL LETTER T WITH COMMA BELOW
+021C ; Uppercase # L& LATIN CAPITAL LETTER YOGH
+021E ; Uppercase # L& LATIN CAPITAL LETTER H WITH CARON
+0220 ; Uppercase # L& LATIN CAPITAL LETTER N WITH LONG RIGHT LEG
+0222 ; Uppercase # L& LATIN CAPITAL LETTER OU
+0224 ; Uppercase # L& LATIN CAPITAL LETTER Z WITH HOOK
+0226 ; Uppercase # L& LATIN CAPITAL LETTER A WITH DOT ABOVE
+0228 ; Uppercase # L& LATIN CAPITAL LETTER E WITH CEDILLA
+022A ; Uppercase # L& LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON
+022C ; Uppercase # L& LATIN CAPITAL LETTER O WITH TILDE AND MACRON
+022E ; Uppercase # L& LATIN CAPITAL LETTER O WITH DOT ABOVE
+0230 ; Uppercase # L& LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON
+0232 ; Uppercase # L& LATIN CAPITAL LETTER Y WITH MACRON
+023A..023B ; Uppercase # L& [2] LATIN CAPITAL LETTER A WITH STROKE..LATIN CAPITAL LETTER C WITH STROKE
+023D..023E ; Uppercase # L& [2] LATIN CAPITAL LETTER L WITH BAR..LATIN CAPITAL LETTER T WITH DIAGONAL STROKE
+0241 ; Uppercase # L& LATIN CAPITAL LETTER GLOTTAL STOP
+0243..0246 ; Uppercase # L& [4] LATIN CAPITAL LETTER B WITH STROKE..LATIN CAPITAL LETTER E WITH STROKE
+0248 ; Uppercase # L& LATIN CAPITAL LETTER J WITH STROKE
+024A ; Uppercase # L& LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL
+024C ; Uppercase # L& LATIN CAPITAL LETTER R WITH STROKE
+024E ; Uppercase # L& LATIN CAPITAL LETTER Y WITH STROKE
+0370 ; Uppercase # L& GREEK CAPITAL LETTER HETA
+0372 ; Uppercase # L& GREEK CAPITAL LETTER ARCHAIC SAMPI
+0376 ; Uppercase # L& GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA
+037F ; Uppercase # L& GREEK CAPITAL LETTER YOT
+0386 ; Uppercase # L& GREEK CAPITAL LETTER ALPHA WITH TONOS
+0388..038A ; Uppercase # L& [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS
+038C ; Uppercase # L& GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E..038F ; Uppercase # L& [2] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER OMEGA WITH TONOS
+0391..03A1 ; Uppercase # L& [17] GREEK CAPITAL LETTER ALPHA..GREEK CAPITAL LETTER RHO
+03A3..03AB ; Uppercase # L& [9] GREEK CAPITAL LETTER SIGMA..GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+03CF ; Uppercase # L& GREEK CAPITAL KAI SYMBOL
+03D2..03D4 ; Uppercase # L& [3] GREEK UPSILON WITH HOOK SYMBOL..GREEK UPSILON WITH DIAERESIS AND HOOK SYMBOL
+03D8 ; Uppercase # L& GREEK LETTER ARCHAIC KOPPA
+03DA ; Uppercase # L& GREEK LETTER STIGMA
+03DC ; Uppercase # L& GREEK LETTER DIGAMMA
+03DE ; Uppercase # L& GREEK LETTER KOPPA
+03E0 ; Uppercase # L& GREEK LETTER SAMPI
+03E2 ; Uppercase # L& COPTIC CAPITAL LETTER SHEI
+03E4 ; Uppercase # L& COPTIC CAPITAL LETTER FEI
+03E6 ; Uppercase # L& COPTIC CAPITAL LETTER KHEI
+03E8 ; Uppercase # L& COPTIC CAPITAL LETTER HORI
+03EA ; Uppercase # L& COPTIC CAPITAL LETTER GANGIA
+03EC ; Uppercase # L& COPTIC CAPITAL LETTER SHIMA
+03EE ; Uppercase # L& COPTIC CAPITAL LETTER DEI
+03F4 ; Uppercase # L& GREEK CAPITAL THETA SYMBOL
+03F7 ; Uppercase # L& GREEK CAPITAL LETTER SHO
+03F9..03FA ; Uppercase # L& [2] GREEK CAPITAL LUNATE SIGMA SYMBOL..GREEK CAPITAL LETTER SAN
+03FD..042F ; Uppercase # L& [51] GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL..CYRILLIC CAPITAL LETTER YA
+0460 ; Uppercase # L& CYRILLIC CAPITAL LETTER OMEGA
+0462 ; Uppercase # L& CYRILLIC CAPITAL LETTER YAT
+0464 ; Uppercase # L& CYRILLIC CAPITAL LETTER IOTIFIED E
+0466 ; Uppercase # L& CYRILLIC CAPITAL LETTER LITTLE YUS
+0468 ; Uppercase # L& CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS
+046A ; Uppercase # L& CYRILLIC CAPITAL LETTER BIG YUS
+046C ; Uppercase # L& CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS
+046E ; Uppercase # L& CYRILLIC CAPITAL LETTER KSI
+0470 ; Uppercase # L& CYRILLIC CAPITAL LETTER PSI
+0472 ; Uppercase # L& CYRILLIC CAPITAL LETTER FITA
+0474 ; Uppercase # L& CYRILLIC CAPITAL LETTER IZHITSA
+0476 ; Uppercase # L& CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT
+0478 ; Uppercase # L& CYRILLIC CAPITAL LETTER UK
+047A ; Uppercase # L& CYRILLIC CAPITAL LETTER ROUND OMEGA
+047C ; Uppercase # L& CYRILLIC CAPITAL LETTER OMEGA WITH TITLO
+047E ; Uppercase # L& CYRILLIC CAPITAL LETTER OT
+0480 ; Uppercase # L& CYRILLIC CAPITAL LETTER KOPPA
+048A ; Uppercase # L& CYRILLIC CAPITAL LETTER SHORT I WITH TAIL
+048C ; Uppercase # L& CYRILLIC CAPITAL LETTER SEMISOFT SIGN
+048E ; Uppercase # L& CYRILLIC CAPITAL LETTER ER WITH TICK
+0490 ; Uppercase # L& CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+0492 ; Uppercase # L& CYRILLIC CAPITAL LETTER GHE WITH STROKE
+0494 ; Uppercase # L& CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK
+0496 ; Uppercase # L& CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER
+0498 ; Uppercase # L& CYRILLIC CAPITAL LETTER ZE WITH DESCENDER
+049A ; Uppercase # L& CYRILLIC CAPITAL LETTER KA WITH DESCENDER
+049C ; Uppercase # L& CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE
+049E ; Uppercase # L& CYRILLIC CAPITAL LETTER KA WITH STROKE
+04A0 ; Uppercase # L& CYRILLIC CAPITAL LETTER BASHKIR KA
+04A2 ; Uppercase # L& CYRILLIC CAPITAL LETTER EN WITH DESCENDER
+04A4 ; Uppercase # L& CYRILLIC CAPITAL LIGATURE EN GHE
+04A6 ; Uppercase # L& CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK
+04A8 ; Uppercase # L& CYRILLIC CAPITAL LETTER ABKHASIAN HA
+04AA ; Uppercase # L& CYRILLIC CAPITAL LETTER ES WITH DESCENDER
+04AC ; Uppercase # L& CYRILLIC CAPITAL LETTER TE WITH DESCENDER
+04AE ; Uppercase # L& CYRILLIC CAPITAL LETTER STRAIGHT U
+04B0 ; Uppercase # L& CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE
+04B2 ; Uppercase # L& CYRILLIC CAPITAL LETTER HA WITH DESCENDER
+04B4 ; Uppercase # L& CYRILLIC CAPITAL LIGATURE TE TSE
+04B6 ; Uppercase # L& CYRILLIC CAPITAL LETTER CHE WITH DESCENDER
+04B8 ; Uppercase # L& CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE
+04BA ; Uppercase # L& CYRILLIC CAPITAL LETTER SHHA
+04BC ; Uppercase # L& CYRILLIC CAPITAL LETTER ABKHASIAN CHE
+04BE ; Uppercase # L& CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER
+04C0..04C1 ; Uppercase # L& [2] CYRILLIC LETTER PALOCHKA..CYRILLIC CAPITAL LETTER ZHE WITH BREVE
+04C3 ; Uppercase # L& CYRILLIC CAPITAL LETTER KA WITH HOOK
+04C5 ; Uppercase # L& CYRILLIC CAPITAL LETTER EL WITH TAIL
+04C7 ; Uppercase # L& CYRILLIC CAPITAL LETTER EN WITH HOOK
+04C9 ; Uppercase # L& CYRILLIC CAPITAL LETTER EN WITH TAIL
+04CB ; Uppercase # L& CYRILLIC CAPITAL LETTER KHAKASSIAN CHE
+04CD ; Uppercase # L& CYRILLIC CAPITAL LETTER EM WITH TAIL
+04D0 ; Uppercase # L& CYRILLIC CAPITAL LETTER A WITH BREVE
+04D2 ; Uppercase # L& CYRILLIC CAPITAL LETTER A WITH DIAERESIS
+04D4 ; Uppercase # L& CYRILLIC CAPITAL LIGATURE A IE
+04D6 ; Uppercase # L& CYRILLIC CAPITAL LETTER IE WITH BREVE
+04D8 ; Uppercase # L& CYRILLIC CAPITAL LETTER SCHWA
+04DA ; Uppercase # L& CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS
+04DC ; Uppercase # L& CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS
+04DE ; Uppercase # L& CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS
+04E0 ; Uppercase # L& CYRILLIC CAPITAL LETTER ABKHASIAN DZE
+04E2 ; Uppercase # L& CYRILLIC CAPITAL LETTER I WITH MACRON
+04E4 ; Uppercase # L& CYRILLIC CAPITAL LETTER I WITH DIAERESIS
+04E6 ; Uppercase # L& CYRILLIC CAPITAL LETTER O WITH DIAERESIS
+04E8 ; Uppercase # L& CYRILLIC CAPITAL LETTER BARRED O
+04EA ; Uppercase # L& CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS
+04EC ; Uppercase # L& CYRILLIC CAPITAL LETTER E WITH DIAERESIS
+04EE ; Uppercase # L& CYRILLIC CAPITAL LETTER U WITH MACRON
+04F0 ; Uppercase # L& CYRILLIC CAPITAL LETTER U WITH DIAERESIS
+04F2 ; Uppercase # L& CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE
+04F4 ; Uppercase # L& CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS
+04F6 ; Uppercase # L& CYRILLIC CAPITAL LETTER GHE WITH DESCENDER
+04F8 ; Uppercase # L& CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS
+04FA ; Uppercase # L& CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK
+04FC ; Uppercase # L& CYRILLIC CAPITAL LETTER HA WITH HOOK
+04FE ; Uppercase # L& CYRILLIC CAPITAL LETTER HA WITH STROKE
+0500 ; Uppercase # L& CYRILLIC CAPITAL LETTER KOMI DE
+0502 ; Uppercase # L& CYRILLIC CAPITAL LETTER KOMI DJE
+0504 ; Uppercase # L& CYRILLIC CAPITAL LETTER KOMI ZJE
+0506 ; Uppercase # L& CYRILLIC CAPITAL LETTER KOMI DZJE
+0508 ; Uppercase # L& CYRILLIC CAPITAL LETTER KOMI LJE
+050A ; Uppercase # L& CYRILLIC CAPITAL LETTER KOMI NJE
+050C ; Uppercase # L& CYRILLIC CAPITAL LETTER KOMI SJE
+050E ; Uppercase # L& CYRILLIC CAPITAL LETTER KOMI TJE
+0510 ; Uppercase # L& CYRILLIC CAPITAL LETTER REVERSED ZE
+0512 ; Uppercase # L& CYRILLIC CAPITAL LETTER EL WITH HOOK
+0514 ; Uppercase # L& CYRILLIC CAPITAL LETTER LHA
+0516 ; Uppercase # L& CYRILLIC CAPITAL LETTER RHA
+0518 ; Uppercase # L& CYRILLIC CAPITAL LETTER YAE
+051A ; Uppercase # L& CYRILLIC CAPITAL LETTER QA
+051C ; Uppercase # L& CYRILLIC CAPITAL LETTER WE
+051E ; Uppercase # L& CYRILLIC CAPITAL LETTER ALEUT KA
+0520 ; Uppercase # L& CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK
+0522 ; Uppercase # L& CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK
+0524 ; Uppercase # L& CYRILLIC CAPITAL LETTER PE WITH DESCENDER
+0526 ; Uppercase # L& CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER
+0528 ; Uppercase # L& CYRILLIC CAPITAL LETTER EN WITH LEFT HOOK
+052A ; Uppercase # L& CYRILLIC CAPITAL LETTER DZZHE
+052C ; Uppercase # L& CYRILLIC CAPITAL LETTER DCHE
+052E ; Uppercase # L& CYRILLIC CAPITAL LETTER EL WITH DESCENDER
+0531..0556 ; Uppercase # L& [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH
+10A0..10C5 ; Uppercase # L& [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE
+10C7 ; Uppercase # L& GEORGIAN CAPITAL LETTER YN
+10CD ; Uppercase # L& GEORGIAN CAPITAL LETTER AEN
+13A0..13F5 ; Uppercase # L& [86] CHEROKEE LETTER A..CHEROKEE LETTER MV
+1C89 ; Uppercase # L& CYRILLIC CAPITAL LETTER TJE
+1C90..1CBA ; Uppercase # L& [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN
+1CBD..1CBF ; Uppercase # L& [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
+1E00 ; Uppercase # L& LATIN CAPITAL LETTER A WITH RING BELOW
+1E02 ; Uppercase # L& LATIN CAPITAL LETTER B WITH DOT ABOVE
+1E04 ; Uppercase # L& LATIN CAPITAL LETTER B WITH DOT BELOW
+1E06 ; Uppercase # L& LATIN CAPITAL LETTER B WITH LINE BELOW
+1E08 ; Uppercase # L& LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE
+1E0A ; Uppercase # L& LATIN CAPITAL LETTER D WITH DOT ABOVE
+1E0C ; Uppercase # L& LATIN CAPITAL LETTER D WITH DOT BELOW
+1E0E ; Uppercase # L& LATIN CAPITAL LETTER D WITH LINE BELOW
+1E10 ; Uppercase # L& LATIN CAPITAL LETTER D WITH CEDILLA
+1E12 ; Uppercase # L& LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW
+1E14 ; Uppercase # L& LATIN CAPITAL LETTER E WITH MACRON AND GRAVE
+1E16 ; Uppercase # L& LATIN CAPITAL LETTER E WITH MACRON AND ACUTE
+1E18 ; Uppercase # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW
+1E1A ; Uppercase # L& LATIN CAPITAL LETTER E WITH TILDE BELOW
+1E1C ; Uppercase # L& LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE
+1E1E ; Uppercase # L& LATIN CAPITAL LETTER F WITH DOT ABOVE
+1E20 ; Uppercase # L& LATIN CAPITAL LETTER G WITH MACRON
+1E22 ; Uppercase # L& LATIN CAPITAL LETTER H WITH DOT ABOVE
+1E24 ; Uppercase # L& LATIN CAPITAL LETTER H WITH DOT BELOW
+1E26 ; Uppercase # L& LATIN CAPITAL LETTER H WITH DIAERESIS
+1E28 ; Uppercase # L& LATIN CAPITAL LETTER H WITH CEDILLA
+1E2A ; Uppercase # L& LATIN CAPITAL LETTER H WITH BREVE BELOW
+1E2C ; Uppercase # L& LATIN CAPITAL LETTER I WITH TILDE BELOW
+1E2E ; Uppercase # L& LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE
+1E30 ; Uppercase # L& LATIN CAPITAL LETTER K WITH ACUTE
+1E32 ; Uppercase # L& LATIN CAPITAL LETTER K WITH DOT BELOW
+1E34 ; Uppercase # L& LATIN CAPITAL LETTER K WITH LINE BELOW
+1E36 ; Uppercase # L& LATIN CAPITAL LETTER L WITH DOT BELOW
+1E38 ; Uppercase # L& LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON
+1E3A ; Uppercase # L& LATIN CAPITAL LETTER L WITH LINE BELOW
+1E3C ; Uppercase # L& LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW
+1E3E ; Uppercase # L& LATIN CAPITAL LETTER M WITH ACUTE
+1E40 ; Uppercase # L& LATIN CAPITAL LETTER M WITH DOT ABOVE
+1E42 ; Uppercase # L& LATIN CAPITAL LETTER M WITH DOT BELOW
+1E44 ; Uppercase # L& LATIN CAPITAL LETTER N WITH DOT ABOVE
+1E46 ; Uppercase # L& LATIN CAPITAL LETTER N WITH DOT BELOW
+1E48 ; Uppercase # L& LATIN CAPITAL LETTER N WITH LINE BELOW
+1E4A ; Uppercase # L& LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW
+1E4C ; Uppercase # L& LATIN CAPITAL LETTER O WITH TILDE AND ACUTE
+1E4E ; Uppercase # L& LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS
+1E50 ; Uppercase # L& LATIN CAPITAL LETTER O WITH MACRON AND GRAVE
+1E52 ; Uppercase # L& LATIN CAPITAL LETTER O WITH MACRON AND ACUTE
+1E54 ; Uppercase # L& LATIN CAPITAL LETTER P WITH ACUTE
+1E56 ; Uppercase # L& LATIN CAPITAL LETTER P WITH DOT ABOVE
+1E58 ; Uppercase # L& LATIN CAPITAL LETTER R WITH DOT ABOVE
+1E5A ; Uppercase # L& LATIN CAPITAL LETTER R WITH DOT BELOW
+1E5C ; Uppercase # L& LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON
+1E5E ; Uppercase # L& LATIN CAPITAL LETTER R WITH LINE BELOW
+1E60 ; Uppercase # L& LATIN CAPITAL LETTER S WITH DOT ABOVE
+1E62 ; Uppercase # L& LATIN CAPITAL LETTER S WITH DOT BELOW
+1E64 ; Uppercase # L& LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE
+1E66 ; Uppercase # L& LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE
+1E68 ; Uppercase # L& LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE
+1E6A ; Uppercase # L& LATIN CAPITAL LETTER T WITH DOT ABOVE
+1E6C ; Uppercase # L& LATIN CAPITAL LETTER T WITH DOT BELOW
+1E6E ; Uppercase # L& LATIN CAPITAL LETTER T WITH LINE BELOW
+1E70 ; Uppercase # L& LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW
+1E72 ; Uppercase # L& LATIN CAPITAL LETTER U WITH DIAERESIS BELOW
+1E74 ; Uppercase # L& LATIN CAPITAL LETTER U WITH TILDE BELOW
+1E76 ; Uppercase # L& LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW
+1E78 ; Uppercase # L& LATIN CAPITAL LETTER U WITH TILDE AND ACUTE
+1E7A ; Uppercase # L& LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS
+1E7C ; Uppercase # L& LATIN CAPITAL LETTER V WITH TILDE
+1E7E ; Uppercase # L& LATIN CAPITAL LETTER V WITH DOT BELOW
+1E80 ; Uppercase # L& LATIN CAPITAL LETTER W WITH GRAVE
+1E82 ; Uppercase # L& LATIN CAPITAL LETTER W WITH ACUTE
+1E84 ; Uppercase # L& LATIN CAPITAL LETTER W WITH DIAERESIS
+1E86 ; Uppercase # L& LATIN CAPITAL LETTER W WITH DOT ABOVE
+1E88 ; Uppercase # L& LATIN CAPITAL LETTER W WITH DOT BELOW
+1E8A ; Uppercase # L& LATIN CAPITAL LETTER X WITH DOT ABOVE
+1E8C ; Uppercase # L& LATIN CAPITAL LETTER X WITH DIAERESIS
+1E8E ; Uppercase # L& LATIN CAPITAL LETTER Y WITH DOT ABOVE
+1E90 ; Uppercase # L& LATIN CAPITAL LETTER Z WITH CIRCUMFLEX
+1E92 ; Uppercase # L& LATIN CAPITAL LETTER Z WITH DOT BELOW
+1E94 ; Uppercase # L& LATIN CAPITAL LETTER Z WITH LINE BELOW
+1E9E ; Uppercase # L& LATIN CAPITAL LETTER SHARP S
+1EA0 ; Uppercase # L& LATIN CAPITAL LETTER A WITH DOT BELOW
+1EA2 ; Uppercase # L& LATIN CAPITAL LETTER A WITH HOOK ABOVE
+1EA4 ; Uppercase # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE
+1EA6 ; Uppercase # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE
+1EA8 ; Uppercase # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
+1EAA ; Uppercase # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE
+1EAC ; Uppercase # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW
+1EAE ; Uppercase # L& LATIN CAPITAL LETTER A WITH BREVE AND ACUTE
+1EB0 ; Uppercase # L& LATIN CAPITAL LETTER A WITH BREVE AND GRAVE
+1EB2 ; Uppercase # L& LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE
+1EB4 ; Uppercase # L& LATIN CAPITAL LETTER A WITH BREVE AND TILDE
+1EB6 ; Uppercase # L& LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW
+1EB8 ; Uppercase # L& LATIN CAPITAL LETTER E WITH DOT BELOW
+1EBA ; Uppercase # L& LATIN CAPITAL LETTER E WITH HOOK ABOVE
+1EBC ; Uppercase # L& LATIN CAPITAL LETTER E WITH TILDE
+1EBE ; Uppercase # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE
+1EC0 ; Uppercase # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE
+1EC2 ; Uppercase # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+1EC4 ; Uppercase # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE
+1EC6 ; Uppercase # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW
+1EC8 ; Uppercase # L& LATIN CAPITAL LETTER I WITH HOOK ABOVE
+1ECA ; Uppercase # L& LATIN CAPITAL LETTER I WITH DOT BELOW
+1ECC ; Uppercase # L& LATIN CAPITAL LETTER O WITH DOT BELOW
+1ECE ; Uppercase # L& LATIN CAPITAL LETTER O WITH HOOK ABOVE
+1ED0 ; Uppercase # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE
+1ED2 ; Uppercase # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE
+1ED4 ; Uppercase # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
+1ED6 ; Uppercase # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE
+1ED8 ; Uppercase # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW
+1EDA ; Uppercase # L& LATIN CAPITAL LETTER O WITH HORN AND ACUTE
+1EDC ; Uppercase # L& LATIN CAPITAL LETTER O WITH HORN AND GRAVE
+1EDE ; Uppercase # L& LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE
+1EE0 ; Uppercase # L& LATIN CAPITAL LETTER O WITH HORN AND TILDE
+1EE2 ; Uppercase # L& LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW
+1EE4 ; Uppercase # L& LATIN CAPITAL LETTER U WITH DOT BELOW
+1EE6 ; Uppercase # L& LATIN CAPITAL LETTER U WITH HOOK ABOVE
+1EE8 ; Uppercase # L& LATIN CAPITAL LETTER U WITH HORN AND ACUTE
+1EEA ; Uppercase # L& LATIN CAPITAL LETTER U WITH HORN AND GRAVE
+1EEC ; Uppercase # L& LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE
+1EEE ; Uppercase # L& LATIN CAPITAL LETTER U WITH HORN AND TILDE
+1EF0 ; Uppercase # L& LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW
+1EF2 ; Uppercase # L& LATIN CAPITAL LETTER Y WITH GRAVE
+1EF4 ; Uppercase # L& LATIN CAPITAL LETTER Y WITH DOT BELOW
+1EF6 ; Uppercase # L& LATIN CAPITAL LETTER Y WITH HOOK ABOVE
+1EF8 ; Uppercase # L& LATIN CAPITAL LETTER Y WITH TILDE
+1EFA ; Uppercase # L& LATIN CAPITAL LETTER MIDDLE-WELSH LL
+1EFC ; Uppercase # L& LATIN CAPITAL LETTER MIDDLE-WELSH V
+1EFE ; Uppercase # L& LATIN CAPITAL LETTER Y WITH LOOP
+1F08..1F0F ; Uppercase # L& [8] GREEK CAPITAL LETTER ALPHA WITH PSILI..GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI
+1F18..1F1D ; Uppercase # L& [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F28..1F2F ; Uppercase # L& [8] GREEK CAPITAL LETTER ETA WITH PSILI..GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI
+1F38..1F3F ; Uppercase # L& [8] GREEK CAPITAL LETTER IOTA WITH PSILI..GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI
+1F48..1F4D ; Uppercase # L& [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F59 ; Uppercase # L& GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B ; Uppercase # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D ; Uppercase # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F ; Uppercase # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F68..1F6F ; Uppercase # L& [8] GREEK CAPITAL LETTER OMEGA WITH PSILI..GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI
+1FB8..1FBB ; Uppercase # L& [4] GREEK CAPITAL LETTER ALPHA WITH VRACHY..GREEK CAPITAL LETTER ALPHA WITH OXIA
+1FC8..1FCB ; Uppercase # L& [4] GREEK CAPITAL LETTER EPSILON WITH VARIA..GREEK CAPITAL LETTER ETA WITH OXIA
+1FD8..1FDB ; Uppercase # L& [4] GREEK CAPITAL LETTER IOTA WITH VRACHY..GREEK CAPITAL LETTER IOTA WITH OXIA
+1FE8..1FEC ; Uppercase # L& [5] GREEK CAPITAL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA
+1FF8..1FFB ; Uppercase # L& [4] GREEK CAPITAL LETTER OMICRON WITH VARIA..GREEK CAPITAL LETTER OMEGA WITH OXIA
+2102 ; Uppercase # L& DOUBLE-STRUCK CAPITAL C
+2107 ; Uppercase # L& EULER CONSTANT
+210B..210D ; Uppercase # L& [3] SCRIPT CAPITAL H..DOUBLE-STRUCK CAPITAL H
+2110..2112 ; Uppercase # L& [3] SCRIPT CAPITAL I..SCRIPT CAPITAL L
+2115 ; Uppercase # L& DOUBLE-STRUCK CAPITAL N
+2119..211D ; Uppercase # L& [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R
+2124 ; Uppercase # L& DOUBLE-STRUCK CAPITAL Z
+2126 ; Uppercase # L& OHM SIGN
+2128 ; Uppercase # L& BLACK-LETTER CAPITAL Z
+212A..212D ; Uppercase # L& [4] KELVIN SIGN..BLACK-LETTER CAPITAL C
+2130..2133 ; Uppercase # L& [4] SCRIPT CAPITAL E..SCRIPT CAPITAL M
+213E..213F ; Uppercase # L& [2] DOUBLE-STRUCK CAPITAL GAMMA..DOUBLE-STRUCK CAPITAL PI
+2145 ; Uppercase # L& DOUBLE-STRUCK ITALIC CAPITAL D
+2160..216F ; Uppercase # Nl [16] ROMAN NUMERAL ONE..ROMAN NUMERAL ONE THOUSAND
+2183 ; Uppercase # L& ROMAN NUMERAL REVERSED ONE HUNDRED
+24B6..24CF ; Uppercase # So [26] CIRCLED LATIN CAPITAL LETTER A..CIRCLED LATIN CAPITAL LETTER Z
+2C00..2C2F ; Uppercase # L& [48] GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC CAPITAL LETTER CAUDATE CHRIVI
+2C60 ; Uppercase # L& LATIN CAPITAL LETTER L WITH DOUBLE BAR
+2C62..2C64 ; Uppercase # L& [3] LATIN CAPITAL LETTER L WITH MIDDLE TILDE..LATIN CAPITAL LETTER R WITH TAIL
+2C67 ; Uppercase # L& LATIN CAPITAL LETTER H WITH DESCENDER
+2C69 ; Uppercase # L& LATIN CAPITAL LETTER K WITH DESCENDER
+2C6B ; Uppercase # L& LATIN CAPITAL LETTER Z WITH DESCENDER
+2C6D..2C70 ; Uppercase # L& [4] LATIN CAPITAL LETTER ALPHA..LATIN CAPITAL LETTER TURNED ALPHA
+2C72 ; Uppercase # L& LATIN CAPITAL LETTER W WITH HOOK
+2C75 ; Uppercase # L& LATIN CAPITAL LETTER HALF H
+2C7E..2C80 ; Uppercase # L& [3] LATIN CAPITAL LETTER S WITH SWASH TAIL..COPTIC CAPITAL LETTER ALFA
+2C82 ; Uppercase # L& COPTIC CAPITAL LETTER VIDA
+2C84 ; Uppercase # L& COPTIC CAPITAL LETTER GAMMA
+2C86 ; Uppercase # L& COPTIC CAPITAL LETTER DALDA
+2C88 ; Uppercase # L& COPTIC CAPITAL LETTER EIE
+2C8A ; Uppercase # L& COPTIC CAPITAL LETTER SOU
+2C8C ; Uppercase # L& COPTIC CAPITAL LETTER ZATA
+2C8E ; Uppercase # L& COPTIC CAPITAL LETTER HATE
+2C90 ; Uppercase # L& COPTIC CAPITAL LETTER THETHE
+2C92 ; Uppercase # L& COPTIC CAPITAL LETTER IAUDA
+2C94 ; Uppercase # L& COPTIC CAPITAL LETTER KAPA
+2C96 ; Uppercase # L& COPTIC CAPITAL LETTER LAULA
+2C98 ; Uppercase # L& COPTIC CAPITAL LETTER MI
+2C9A ; Uppercase # L& COPTIC CAPITAL LETTER NI
+2C9C ; Uppercase # L& COPTIC CAPITAL LETTER KSI
+2C9E ; Uppercase # L& COPTIC CAPITAL LETTER O
+2CA0 ; Uppercase # L& COPTIC CAPITAL LETTER PI
+2CA2 ; Uppercase # L& COPTIC CAPITAL LETTER RO
+2CA4 ; Uppercase # L& COPTIC CAPITAL LETTER SIMA
+2CA6 ; Uppercase # L& COPTIC CAPITAL LETTER TAU
+2CA8 ; Uppercase # L& COPTIC CAPITAL LETTER UA
+2CAA ; Uppercase # L& COPTIC CAPITAL LETTER FI
+2CAC ; Uppercase # L& COPTIC CAPITAL LETTER KHI
+2CAE ; Uppercase # L& COPTIC CAPITAL LETTER PSI
+2CB0 ; Uppercase # L& COPTIC CAPITAL LETTER OOU
+2CB2 ; Uppercase # L& COPTIC CAPITAL LETTER DIALECT-P ALEF
+2CB4 ; Uppercase # L& COPTIC CAPITAL LETTER OLD COPTIC AIN
+2CB6 ; Uppercase # L& COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE
+2CB8 ; Uppercase # L& COPTIC CAPITAL LETTER DIALECT-P KAPA
+2CBA ; Uppercase # L& COPTIC CAPITAL LETTER DIALECT-P NI
+2CBC ; Uppercase # L& COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI
+2CBE ; Uppercase # L& COPTIC CAPITAL LETTER OLD COPTIC OOU
+2CC0 ; Uppercase # L& COPTIC CAPITAL LETTER SAMPI
+2CC2 ; Uppercase # L& COPTIC CAPITAL LETTER CROSSED SHEI
+2CC4 ; Uppercase # L& COPTIC CAPITAL LETTER OLD COPTIC SHEI
+2CC6 ; Uppercase # L& COPTIC CAPITAL LETTER OLD COPTIC ESH
+2CC8 ; Uppercase # L& COPTIC CAPITAL LETTER AKHMIMIC KHEI
+2CCA ; Uppercase # L& COPTIC CAPITAL LETTER DIALECT-P HORI
+2CCC ; Uppercase # L& COPTIC CAPITAL LETTER OLD COPTIC HORI
+2CCE ; Uppercase # L& COPTIC CAPITAL LETTER OLD COPTIC HA
+2CD0 ; Uppercase # L& COPTIC CAPITAL LETTER L-SHAPED HA
+2CD2 ; Uppercase # L& COPTIC CAPITAL LETTER OLD COPTIC HEI
+2CD4 ; Uppercase # L& COPTIC CAPITAL LETTER OLD COPTIC HAT
+2CD6 ; Uppercase # L& COPTIC CAPITAL LETTER OLD COPTIC GANGIA
+2CD8 ; Uppercase # L& COPTIC CAPITAL LETTER OLD COPTIC DJA
+2CDA ; Uppercase # L& COPTIC CAPITAL LETTER OLD COPTIC SHIMA
+2CDC ; Uppercase # L& COPTIC CAPITAL LETTER OLD NUBIAN SHIMA
+2CDE ; Uppercase # L& COPTIC CAPITAL LETTER OLD NUBIAN NGI
+2CE0 ; Uppercase # L& COPTIC CAPITAL LETTER OLD NUBIAN NYI
+2CE2 ; Uppercase # L& COPTIC CAPITAL LETTER OLD NUBIAN WAU
+2CEB ; Uppercase # L& COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI
+2CED ; Uppercase # L& COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA
+2CF2 ; Uppercase # L& COPTIC CAPITAL LETTER BOHAIRIC KHEI
+A640 ; Uppercase # L& CYRILLIC CAPITAL LETTER ZEMLYA
+A642 ; Uppercase # L& CYRILLIC CAPITAL LETTER DZELO
+A644 ; Uppercase # L& CYRILLIC CAPITAL LETTER REVERSED DZE
+A646 ; Uppercase # L& CYRILLIC CAPITAL LETTER IOTA
+A648 ; Uppercase # L& CYRILLIC CAPITAL LETTER DJERV
+A64A ; Uppercase # L& CYRILLIC CAPITAL LETTER MONOGRAPH UK
+A64C ; Uppercase # L& CYRILLIC CAPITAL LETTER BROAD OMEGA
+A64E ; Uppercase # L& CYRILLIC CAPITAL LETTER NEUTRAL YER
+A650 ; Uppercase # L& CYRILLIC CAPITAL LETTER YERU WITH BACK YER
+A652 ; Uppercase # L& CYRILLIC CAPITAL LETTER IOTIFIED YAT
+A654 ; Uppercase # L& CYRILLIC CAPITAL LETTER REVERSED YU
+A656 ; Uppercase # L& CYRILLIC CAPITAL LETTER IOTIFIED A
+A658 ; Uppercase # L& CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS
+A65A ; Uppercase # L& CYRILLIC CAPITAL LETTER BLENDED YUS
+A65C ; Uppercase # L& CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS
+A65E ; Uppercase # L& CYRILLIC CAPITAL LETTER YN
+A660 ; Uppercase # L& CYRILLIC CAPITAL LETTER REVERSED TSE
+A662 ; Uppercase # L& CYRILLIC CAPITAL LETTER SOFT DE
+A664 ; Uppercase # L& CYRILLIC CAPITAL LETTER SOFT EL
+A666 ; Uppercase # L& CYRILLIC CAPITAL LETTER SOFT EM
+A668 ; Uppercase # L& CYRILLIC CAPITAL LETTER MONOCULAR O
+A66A ; Uppercase # L& CYRILLIC CAPITAL LETTER BINOCULAR O
+A66C ; Uppercase # L& CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O
+A680 ; Uppercase # L& CYRILLIC CAPITAL LETTER DWE
+A682 ; Uppercase # L& CYRILLIC CAPITAL LETTER DZWE
+A684 ; Uppercase # L& CYRILLIC CAPITAL LETTER ZHWE
+A686 ; Uppercase # L& CYRILLIC CAPITAL LETTER CCHE
+A688 ; Uppercase # L& CYRILLIC CAPITAL LETTER DZZE
+A68A ; Uppercase # L& CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK
+A68C ; Uppercase # L& CYRILLIC CAPITAL LETTER TWE
+A68E ; Uppercase # L& CYRILLIC CAPITAL LETTER TSWE
+A690 ; Uppercase # L& CYRILLIC CAPITAL LETTER TSSE
+A692 ; Uppercase # L& CYRILLIC CAPITAL LETTER TCHE
+A694 ; Uppercase # L& CYRILLIC CAPITAL LETTER HWE
+A696 ; Uppercase # L& CYRILLIC CAPITAL LETTER SHWE
+A698 ; Uppercase # L& CYRILLIC CAPITAL LETTER DOUBLE O
+A69A ; Uppercase # L& CYRILLIC CAPITAL LETTER CROSSED O
+A722 ; Uppercase # L& LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF
+A724 ; Uppercase # L& LATIN CAPITAL LETTER EGYPTOLOGICAL AIN
+A726 ; Uppercase # L& LATIN CAPITAL LETTER HENG
+A728 ; Uppercase # L& LATIN CAPITAL LETTER TZ
+A72A ; Uppercase # L& LATIN CAPITAL LETTER TRESILLO
+A72C ; Uppercase # L& LATIN CAPITAL LETTER CUATRILLO
+A72E ; Uppercase # L& LATIN CAPITAL LETTER CUATRILLO WITH COMMA
+A732 ; Uppercase # L& LATIN CAPITAL LETTER AA
+A734 ; Uppercase # L& LATIN CAPITAL LETTER AO
+A736 ; Uppercase # L& LATIN CAPITAL LETTER AU
+A738 ; Uppercase # L& LATIN CAPITAL LETTER AV
+A73A ; Uppercase # L& LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR
+A73C ; Uppercase # L& LATIN CAPITAL LETTER AY
+A73E ; Uppercase # L& LATIN CAPITAL LETTER REVERSED C WITH DOT
+A740 ; Uppercase # L& LATIN CAPITAL LETTER K WITH STROKE
+A742 ; Uppercase # L& LATIN CAPITAL LETTER K WITH DIAGONAL STROKE
+A744 ; Uppercase # L& LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE
+A746 ; Uppercase # L& LATIN CAPITAL LETTER BROKEN L
+A748 ; Uppercase # L& LATIN CAPITAL LETTER L WITH HIGH STROKE
+A74A ; Uppercase # L& LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY
+A74C ; Uppercase # L& LATIN CAPITAL LETTER O WITH LOOP
+A74E ; Uppercase # L& LATIN CAPITAL LETTER OO
+A750 ; Uppercase # L& LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER
+A752 ; Uppercase # L& LATIN CAPITAL LETTER P WITH FLOURISH
+A754 ; Uppercase # L& LATIN CAPITAL LETTER P WITH SQUIRREL TAIL
+A756 ; Uppercase # L& LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER
+A758 ; Uppercase # L& LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE
+A75A ; Uppercase # L& LATIN CAPITAL LETTER R ROTUNDA
+A75C ; Uppercase # L& LATIN CAPITAL LETTER RUM ROTUNDA
+A75E ; Uppercase # L& LATIN CAPITAL LETTER V WITH DIAGONAL STROKE
+A760 ; Uppercase # L& LATIN CAPITAL LETTER VY
+A762 ; Uppercase # L& LATIN CAPITAL LETTER VISIGOTHIC Z
+A764 ; Uppercase # L& LATIN CAPITAL LETTER THORN WITH STROKE
+A766 ; Uppercase # L& LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER
+A768 ; Uppercase # L& LATIN CAPITAL LETTER VEND
+A76A ; Uppercase # L& LATIN CAPITAL LETTER ET
+A76C ; Uppercase # L& LATIN CAPITAL LETTER IS
+A76E ; Uppercase # L& LATIN CAPITAL LETTER CON
+A779 ; Uppercase # L& LATIN CAPITAL LETTER INSULAR D
+A77B ; Uppercase # L& LATIN CAPITAL LETTER INSULAR F
+A77D..A77E ; Uppercase # L& [2] LATIN CAPITAL LETTER INSULAR G..LATIN CAPITAL LETTER TURNED INSULAR G
+A780 ; Uppercase # L& LATIN CAPITAL LETTER TURNED L
+A782 ; Uppercase # L& LATIN CAPITAL LETTER INSULAR R
+A784 ; Uppercase # L& LATIN CAPITAL LETTER INSULAR S
+A786 ; Uppercase # L& LATIN CAPITAL LETTER INSULAR T
+A78B ; Uppercase # L& LATIN CAPITAL LETTER SALTILLO
+A78D ; Uppercase # L& LATIN CAPITAL LETTER TURNED H
+A790 ; Uppercase # L& LATIN CAPITAL LETTER N WITH DESCENDER
+A792 ; Uppercase # L& LATIN CAPITAL LETTER C WITH BAR
+A796 ; Uppercase # L& LATIN CAPITAL LETTER B WITH FLOURISH
+A798 ; Uppercase # L& LATIN CAPITAL LETTER F WITH STROKE
+A79A ; Uppercase # L& LATIN CAPITAL LETTER VOLAPUK AE
+A79C ; Uppercase # L& LATIN CAPITAL LETTER VOLAPUK OE
+A79E ; Uppercase # L& LATIN CAPITAL LETTER VOLAPUK UE
+A7A0 ; Uppercase # L& LATIN CAPITAL LETTER G WITH OBLIQUE STROKE
+A7A2 ; Uppercase # L& LATIN CAPITAL LETTER K WITH OBLIQUE STROKE
+A7A4 ; Uppercase # L& LATIN CAPITAL LETTER N WITH OBLIQUE STROKE
+A7A6 ; Uppercase # L& LATIN CAPITAL LETTER R WITH OBLIQUE STROKE
+A7A8 ; Uppercase # L& LATIN CAPITAL LETTER S WITH OBLIQUE STROKE
+A7AA..A7AE ; Uppercase # L& [5] LATIN CAPITAL LETTER H WITH HOOK..LATIN CAPITAL LETTER SMALL CAPITAL I
+A7B0..A7B4 ; Uppercase # L& [5] LATIN CAPITAL LETTER TURNED K..LATIN CAPITAL LETTER BETA
+A7B6 ; Uppercase # L& LATIN CAPITAL LETTER OMEGA
+A7B8 ; Uppercase # L& LATIN CAPITAL LETTER U WITH STROKE
+A7BA ; Uppercase # L& LATIN CAPITAL LETTER GLOTTAL A
+A7BC ; Uppercase # L& LATIN CAPITAL LETTER GLOTTAL I
+A7BE ; Uppercase # L& LATIN CAPITAL LETTER GLOTTAL U
+A7C0 ; Uppercase # L& LATIN CAPITAL LETTER OLD POLISH O
+A7C2 ; Uppercase # L& LATIN CAPITAL LETTER ANGLICANA W
+A7C4..A7C7 ; Uppercase # L& [4] LATIN CAPITAL LETTER C WITH PALATAL HOOK..LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY
+A7C9 ; Uppercase # L& LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY
+A7CB..A7CC ; Uppercase # L& [2] LATIN CAPITAL LETTER RAMS HORN..LATIN CAPITAL LETTER S WITH DIAGONAL STROKE
+A7D0 ; Uppercase # L& LATIN CAPITAL LETTER CLOSED INSULAR G
+A7D6 ; Uppercase # L& LATIN CAPITAL LETTER MIDDLE SCOTS S
+A7D8 ; Uppercase # L& LATIN CAPITAL LETTER SIGMOID S
+A7DA ; Uppercase # L& LATIN CAPITAL LETTER LAMBDA
+A7DC ; Uppercase # L& LATIN CAPITAL LETTER LAMBDA WITH STROKE
+A7F5 ; Uppercase # L& LATIN CAPITAL LETTER REVERSED HALF H
+FF21..FF3A ; Uppercase # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z
+10400..10427 ; Uppercase # L& [40] DESERET CAPITAL LETTER LONG I..DESERET CAPITAL LETTER EW
+104B0..104D3 ; Uppercase # L& [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA
+10570..1057A ; Uppercase # L& [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA
+1057C..1058A ; Uppercase # L& [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE
+1058C..10592 ; Uppercase # L& [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE
+10594..10595 ; Uppercase # L& [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE
+10C80..10CB2 ; Uppercase # L& [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US
+10D50..10D65 ; Uppercase # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA
+118A0..118BF ; Uppercase # L& [32] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI CAPITAL LETTER VIYO
+16E40..16E5F ; Uppercase # L& [32] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN CAPITAL LETTER Y
+1D400..1D419 ; Uppercase # L& [26] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL BOLD CAPITAL Z
+1D434..1D44D ; Uppercase # L& [26] MATHEMATICAL ITALIC CAPITAL A..MATHEMATICAL ITALIC CAPITAL Z
+1D468..1D481 ; Uppercase # L& [26] MATHEMATICAL BOLD ITALIC CAPITAL A..MATHEMATICAL BOLD ITALIC CAPITAL Z
+1D49C ; Uppercase # L& MATHEMATICAL SCRIPT CAPITAL A
+1D49E..1D49F ; Uppercase # L& [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D
+1D4A2 ; Uppercase # L& MATHEMATICAL SCRIPT CAPITAL G
+1D4A5..1D4A6 ; Uppercase # L& [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K
+1D4A9..1D4AC ; Uppercase # L& [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q
+1D4AE..1D4B5 ; Uppercase # L& [8] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT CAPITAL Z
+1D4D0..1D4E9 ; Uppercase # L& [26] MATHEMATICAL BOLD SCRIPT CAPITAL A..MATHEMATICAL BOLD SCRIPT CAPITAL Z
+1D504..1D505 ; Uppercase # L& [2] MATHEMATICAL FRAKTUR CAPITAL A..MATHEMATICAL FRAKTUR CAPITAL B
+1D507..1D50A ; Uppercase # L& [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G
+1D50D..1D514 ; Uppercase # L& [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q
+1D516..1D51C ; Uppercase # L& [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y
+1D538..1D539 ; Uppercase # L& [2] MATHEMATICAL DOUBLE-STRUCK CAPITAL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B
+1D53B..1D53E ; Uppercase # L& [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G
+1D540..1D544 ; Uppercase # L& [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M
+1D546 ; Uppercase # L& MATHEMATICAL DOUBLE-STRUCK CAPITAL O
+1D54A..1D550 ; Uppercase # L& [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y
+1D56C..1D585 ; Uppercase # L& [26] MATHEMATICAL BOLD FRAKTUR CAPITAL A..MATHEMATICAL BOLD FRAKTUR CAPITAL Z
+1D5A0..1D5B9 ; Uppercase # L& [26] MATHEMATICAL SANS-SERIF CAPITAL A..MATHEMATICAL SANS-SERIF CAPITAL Z
+1D5D4..1D5ED ; Uppercase # L& [26] MATHEMATICAL SANS-SERIF BOLD CAPITAL A..MATHEMATICAL SANS-SERIF BOLD CAPITAL Z
+1D608..1D621 ; Uppercase # L& [26] MATHEMATICAL SANS-SERIF ITALIC CAPITAL A..MATHEMATICAL SANS-SERIF ITALIC CAPITAL Z
+1D63C..1D655 ; Uppercase # L& [26] MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL A..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Z
+1D670..1D689 ; Uppercase # L& [26] MATHEMATICAL MONOSPACE CAPITAL A..MATHEMATICAL MONOSPACE CAPITAL Z
+1D6A8..1D6C0 ; Uppercase # L& [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA
+1D6E2..1D6FA ; Uppercase # L& [25] MATHEMATICAL ITALIC CAPITAL ALPHA..MATHEMATICAL ITALIC CAPITAL OMEGA
+1D71C..1D734 ; Uppercase # L& [25] MATHEMATICAL BOLD ITALIC CAPITAL ALPHA..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA
+1D756..1D76E ; Uppercase # L& [25] MATHEMATICAL SANS-SERIF BOLD CAPITAL ALPHA..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA
+1D790..1D7A8 ; Uppercase # L& [25] MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA
+1D7CA ; Uppercase # L& MATHEMATICAL BOLD CAPITAL DIGAMMA
+1E900..1E921 ; Uppercase # L& [34] ADLAM CAPITAL LETTER ALIF..ADLAM CAPITAL LETTER SHA
+1F130..1F149 ; Uppercase # So [26] SQUARED LATIN CAPITAL LETTER A..SQUARED LATIN CAPITAL LETTER Z
+1F150..1F169 ; Uppercase # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z
+1F170..1F189 ; Uppercase # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z
+
+# Total code points: 1978
+
+# ================================================
+
+# Derived Property: Cased (Cased)
+# As defined by Unicode Standard Definition D135
+# C has the Lowercase or Uppercase property or has a General_Category value of Titlecase_Letter.
+
+0041..005A ; Cased # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+0061..007A ; Cased # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+00AA ; Cased # Lo FEMININE ORDINAL INDICATOR
+00B5 ; Cased # L& MICRO SIGN
+00BA ; Cased # Lo MASCULINE ORDINAL INDICATOR
+00C0..00D6 ; Cased # L& [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS
+00D8..00F6 ; Cased # L& [31] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER O WITH DIAERESIS
+00F8..01BA ; Cased # L& [195] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL
+01BC..01BF ; Cased # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN
+01C4..0293 ; Cased # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL
+0295..02AF ; Cased # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL
+02B0..02B8 ; Cased # Lm [9] MODIFIER LETTER SMALL H..MODIFIER LETTER SMALL Y
+02C0..02C1 ; Cased # Lm [2] MODIFIER LETTER GLOTTAL STOP..MODIFIER LETTER REVERSED GLOTTAL STOP
+02E0..02E4 ; Cased # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP
+0345 ; Cased # Mn COMBINING GREEK YPOGEGRAMMENI
+0370..0373 ; Cased # L& [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI
+0376..0377 ; Cased # L& [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA
+037A ; Cased # Lm GREEK YPOGEGRAMMENI
+037B..037D ; Cased # L& [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL
+037F ; Cased # L& GREEK CAPITAL LETTER YOT
+0386 ; Cased # L& GREEK CAPITAL LETTER ALPHA WITH TONOS
+0388..038A ; Cased # L& [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS
+038C ; Cased # L& GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E..03A1 ; Cased # L& [20] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER RHO
+03A3..03F5 ; Cased # L& [83] GREEK CAPITAL LETTER SIGMA..GREEK LUNATE EPSILON SYMBOL
+03F7..0481 ; Cased # L& [139] GREEK CAPITAL LETTER SHO..CYRILLIC SMALL LETTER KOPPA
+048A..052F ; Cased # L& [166] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER EL WITH DESCENDER
+0531..0556 ; Cased # L& [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH
+0560..0588 ; Cased # L& [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE
+10A0..10C5 ; Cased # L& [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE
+10C7 ; Cased # L& GEORGIAN CAPITAL LETTER YN
+10CD ; Cased # L& GEORGIAN CAPITAL LETTER AEN
+10D0..10FA ; Cased # L& [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN
+10FC ; Cased # Lm MODIFIER LETTER GEORGIAN NAR
+10FD..10FF ; Cased # L& [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN
+13A0..13F5 ; Cased # L& [86] CHEROKEE LETTER A..CHEROKEE LETTER MV
+13F8..13FD ; Cased # L& [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV
+1C80..1C8A ; Cased # L& [11] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER TJE
+1C90..1CBA ; Cased # L& [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN
+1CBD..1CBF ; Cased # L& [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
+1D00..1D2B ; Cased # L& [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL
+1D2C..1D6A ; Cased # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI
+1D6B..1D77 ; Cased # L& [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G
+1D78 ; Cased # Lm MODIFIER LETTER CYRILLIC EN
+1D79..1D9A ; Cased # L& [34] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK
+1D9B..1DBF ; Cased # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA
+1E00..1F15 ; Cased # L& [278] LATIN CAPITAL LETTER A WITH RING BELOW..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA
+1F18..1F1D ; Cased # L& [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F20..1F45 ; Cased # L& [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA
+1F48..1F4D ; Cased # L& [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F50..1F57 ; Cased # L& [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F59 ; Cased # L& GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B ; Cased # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D ; Cased # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F..1F7D ; Cased # L& [31] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER OMEGA WITH OXIA
+1F80..1FB4 ; Cased # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6..1FBC ; Cased # L& [7] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBE ; Cased # L& GREEK PROSGEGRAMMENI
+1FC2..1FC4 ; Cased # L& [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6..1FCC ; Cased # L& [7] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FD0..1FD3 ; Cased # L& [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6..1FDB ; Cased # L& [6] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH OXIA
+1FE0..1FEC ; Cased # L& [13] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA
+1FF2..1FF4 ; Cased # L& [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6..1FFC ; Cased # L& [7] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+2071 ; Cased # Lm SUPERSCRIPT LATIN SMALL LETTER I
+207F ; Cased # Lm SUPERSCRIPT LATIN SMALL LETTER N
+2090..209C ; Cased # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T
+2102 ; Cased # L& DOUBLE-STRUCK CAPITAL C
+2107 ; Cased # L& EULER CONSTANT
+210A..2113 ; Cased # L& [10] SCRIPT SMALL G..SCRIPT SMALL L
+2115 ; Cased # L& DOUBLE-STRUCK CAPITAL N
+2119..211D ; Cased # L& [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R
+2124 ; Cased # L& DOUBLE-STRUCK CAPITAL Z
+2126 ; Cased # L& OHM SIGN
+2128 ; Cased # L& BLACK-LETTER CAPITAL Z
+212A..212D ; Cased # L& [4] KELVIN SIGN..BLACK-LETTER CAPITAL C
+212F..2134 ; Cased # L& [6] SCRIPT SMALL E..SCRIPT SMALL O
+2139 ; Cased # L& INFORMATION SOURCE
+213C..213F ; Cased # L& [4] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK CAPITAL PI
+2145..2149 ; Cased # L& [5] DOUBLE-STRUCK ITALIC CAPITAL D..DOUBLE-STRUCK ITALIC SMALL J
+214E ; Cased # L& TURNED SMALL F
+2160..217F ; Cased # Nl [32] ROMAN NUMERAL ONE..SMALL ROMAN NUMERAL ONE THOUSAND
+2183..2184 ; Cased # L& [2] ROMAN NUMERAL REVERSED ONE HUNDRED..LATIN SMALL LETTER REVERSED C
+24B6..24E9 ; Cased # So [52] CIRCLED LATIN CAPITAL LETTER A..CIRCLED LATIN SMALL LETTER Z
+2C00..2C7B ; Cased # L& [124] GLAGOLITIC CAPITAL LETTER AZU..LATIN LETTER SMALL CAPITAL TURNED E
+2C7C..2C7D ; Cased # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V
+2C7E..2CE4 ; Cased # L& [103] LATIN CAPITAL LETTER S WITH SWASH TAIL..COPTIC SYMBOL KAI
+2CEB..2CEE ; Cased # L& [4] COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI..COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA
+2CF2..2CF3 ; Cased # L& [2] COPTIC CAPITAL LETTER BOHAIRIC KHEI..COPTIC SMALL LETTER BOHAIRIC KHEI
+2D00..2D25 ; Cased # L& [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE
+2D27 ; Cased # L& GEORGIAN SMALL LETTER YN
+2D2D ; Cased # L& GEORGIAN SMALL LETTER AEN
+A640..A66D ; Cased # L& [46] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER DOUBLE MONOCULAR O
+A680..A69B ; Cased # L& [28] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER CROSSED O
+A69C..A69D ; Cased # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN
+A722..A76F ; Cased # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CON
+A770 ; Cased # Lm MODIFIER LETTER US
+A771..A787 ; Cased # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T
+A78B..A78E ; Cased # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT
+A790..A7CD ; Cased # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE
+A7D0..A7D1 ; Cased # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G
+A7D3 ; Cased # L& LATIN SMALL LETTER DOUBLE THORN
+A7D5..A7DC ; Cased # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE
+A7F2..A7F4 ; Cased # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q
+A7F5..A7F6 ; Cased # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H
+A7F8..A7F9 ; Cased # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE
+A7FA ; Cased # L& LATIN LETTER SMALL CAPITAL TURNED M
+AB30..AB5A ; Cased # L& [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG
+AB5C..AB5F ; Cased # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK
+AB60..AB68 ; Cased # L& [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE
+AB69 ; Cased # Lm MODIFIER LETTER SMALL TURNED W
+AB70..ABBF ; Cased # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA
+FB00..FB06 ; Cased # L& [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST
+FB13..FB17 ; Cased # L& [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH
+FF21..FF3A ; Cased # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z
+FF41..FF5A ; Cased # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z
+10400..1044F ; Cased # L& [80] DESERET CAPITAL LETTER LONG I..DESERET SMALL LETTER EW
+104B0..104D3 ; Cased # L& [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA
+104D8..104FB ; Cased # L& [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA
+10570..1057A ; Cased # L& [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA
+1057C..1058A ; Cased # L& [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE
+1058C..10592 ; Cased # L& [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE
+10594..10595 ; Cased # L& [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE
+10597..105A1 ; Cased # L& [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA
+105A3..105B1 ; Cased # L& [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE
+105B3..105B9 ; Cased # L& [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE
+105BB..105BC ; Cased # L& [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE
+10780 ; Cased # Lm MODIFIER LETTER SMALL CAPITAL AA
+10783..10785 ; Cased # Lm [3] MODIFIER LETTER SMALL AE..MODIFIER LETTER SMALL B WITH HOOK
+10787..107B0 ; Cased # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK
+107B2..107BA ; Cased # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL
+10C80..10CB2 ; Cased # L& [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US
+10CC0..10CF2 ; Cased # L& [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US
+10D50..10D65 ; Cased # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA
+10D70..10D85 ; Cased # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA
+118A0..118DF ; Cased # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO
+16E40..16E7F ; Cased # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y
+1D400..1D454 ; Cased # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G
+1D456..1D49C ; Cased # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A
+1D49E..1D49F ; Cased # L& [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D
+1D4A2 ; Cased # L& MATHEMATICAL SCRIPT CAPITAL G
+1D4A5..1D4A6 ; Cased # L& [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K
+1D4A9..1D4AC ; Cased # L& [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q
+1D4AE..1D4B9 ; Cased # L& [12] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D
+1D4BB ; Cased # L& MATHEMATICAL SCRIPT SMALL F
+1D4BD..1D4C3 ; Cased # L& [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N
+1D4C5..1D505 ; Cased # L& [65] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B
+1D507..1D50A ; Cased # L& [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G
+1D50D..1D514 ; Cased # L& [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q
+1D516..1D51C ; Cased # L& [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y
+1D51E..1D539 ; Cased # L& [28] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B
+1D53B..1D53E ; Cased # L& [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G
+1D540..1D544 ; Cased # L& [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M
+1D546 ; Cased # L& MATHEMATICAL DOUBLE-STRUCK CAPITAL O
+1D54A..1D550 ; Cased # L& [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y
+1D552..1D6A5 ; Cased # L& [340] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J
+1D6A8..1D6C0 ; Cased # L& [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA
+1D6C2..1D6DA ; Cased # L& [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA
+1D6DC..1D6FA ; Cased # L& [31] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL ITALIC CAPITAL OMEGA
+1D6FC..1D714 ; Cased # L& [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA
+1D716..1D734 ; Cased # L& [31] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA
+1D736..1D74E ; Cased # L& [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA
+1D750..1D76E ; Cased # L& [31] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA
+1D770..1D788 ; Cased # L& [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA
+1D78A..1D7A8 ; Cased # L& [31] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA
+1D7AA..1D7C2 ; Cased # L& [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA
+1D7C4..1D7CB ; Cased # L& [8] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD SMALL DIGAMMA
+1DF00..1DF09 ; Cased # L& [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK
+1DF0B..1DF1E ; Cased # L& [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL
+1DF25..1DF2A ; Cased # L& [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK
+1E030..1E06D ; Cased # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE
+1E900..1E943 ; Cased # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA
+1F130..1F149 ; Cased # So [26] SQUARED LATIN CAPITAL LETTER A..SQUARED LATIN CAPITAL LETTER Z
+1F150..1F169 ; Cased # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z
+1F170..1F189 ; Cased # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z
+
+# Total code points: 4578
+
+# ================================================
+
+# Derived Property: Case_Ignorable (CI)
+# As defined by Unicode Standard Definition D136
+# C is defined to be case-ignorable if
+# Word_Break(C) = MidLetter or MidNumLet or Single_Quote, or
+# General_Category(C) = Nonspacing_Mark (Mn), Enclosing_Mark (Me), Format (Cf), Modifier_Letter (Lm), or Modifier_Symbol (Sk).
+
+0027 ; Case_Ignorable # Po APOSTROPHE
+002E ; Case_Ignorable # Po FULL STOP
+003A ; Case_Ignorable # Po COLON
+005E ; Case_Ignorable # Sk CIRCUMFLEX ACCENT
+0060 ; Case_Ignorable # Sk GRAVE ACCENT
+00A8 ; Case_Ignorable # Sk DIAERESIS
+00AD ; Case_Ignorable # Cf SOFT HYPHEN
+00AF ; Case_Ignorable # Sk MACRON
+00B4 ; Case_Ignorable # Sk ACUTE ACCENT
+00B7 ; Case_Ignorable # Po MIDDLE DOT
+00B8 ; Case_Ignorable # Sk CEDILLA
+02B0..02C1 ; Case_Ignorable # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP
+02C2..02C5 ; Case_Ignorable # Sk [4] MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LETTER DOWN ARROWHEAD
+02C6..02D1 ; Case_Ignorable # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON
+02D2..02DF ; Case_Ignorable # Sk [14] MODIFIER LETTER CENTRED RIGHT HALF RING..MODIFIER LETTER CROSS ACCENT
+02E0..02E4 ; Case_Ignorable # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP
+02E5..02EB ; Case_Ignorable # Sk [7] MODIFIER LETTER EXTRA-HIGH TONE BAR..MODIFIER LETTER YANG DEPARTING TONE MARK
+02EC ; Case_Ignorable # Lm MODIFIER LETTER VOICING
+02ED ; Case_Ignorable # Sk MODIFIER LETTER UNASPIRATED
+02EE ; Case_Ignorable # Lm MODIFIER LETTER DOUBLE APOSTROPHE
+02EF..02FF ; Case_Ignorable # Sk [17] MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER LETTER LOW LEFT ARROW
+0300..036F ; Case_Ignorable # Mn [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X
+0374 ; Case_Ignorable # Lm GREEK NUMERAL SIGN
+0375 ; Case_Ignorable # Sk GREEK LOWER NUMERAL SIGN
+037A ; Case_Ignorable # Lm GREEK YPOGEGRAMMENI
+0384..0385 ; Case_Ignorable # Sk [2] GREEK TONOS..GREEK DIALYTIKA TONOS
+0387 ; Case_Ignorable # Po GREEK ANO TELEIA
+0483..0487 ; Case_Ignorable # Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE
+0488..0489 ; Case_Ignorable # Me [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN
+0559 ; Case_Ignorable # Lm ARMENIAN MODIFIER LETTER LEFT HALF RING
+055F ; Case_Ignorable # Po ARMENIAN ABBREVIATION MARK
+0591..05BD ; Case_Ignorable # Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG
+05BF ; Case_Ignorable # Mn HEBREW POINT RAFE
+05C1..05C2 ; Case_Ignorable # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT
+05C4..05C5 ; Case_Ignorable # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT
+05C7 ; Case_Ignorable # Mn HEBREW POINT QAMATS QATAN
+05F4 ; Case_Ignorable # Po HEBREW PUNCTUATION GERSHAYIM
+0600..0605 ; Case_Ignorable # Cf [6] ARABIC NUMBER SIGN..ARABIC NUMBER MARK ABOVE
+0610..061A ; Case_Ignorable # Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA
+061C ; Case_Ignorable # Cf ARABIC LETTER MARK
+0640 ; Case_Ignorable # Lm ARABIC TATWEEL
+064B..065F ; Case_Ignorable # Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW
+0670 ; Case_Ignorable # Mn ARABIC LETTER SUPERSCRIPT ALEF
+06D6..06DC ; Case_Ignorable # Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN
+06DD ; Case_Ignorable # Cf ARABIC END OF AYAH
+06DF..06E4 ; Case_Ignorable # Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA
+06E5..06E6 ; Case_Ignorable # Lm [2] ARABIC SMALL WAW..ARABIC SMALL YEH
+06E7..06E8 ; Case_Ignorable # Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON
+06EA..06ED ; Case_Ignorable # Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM
+070F ; Case_Ignorable # Cf SYRIAC ABBREVIATION MARK
+0711 ; Case_Ignorable # Mn SYRIAC LETTER SUPERSCRIPT ALAPH
+0730..074A ; Case_Ignorable # Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH
+07A6..07B0 ; Case_Ignorable # Mn [11] THAANA ABAFILI..THAANA SUKUN
+07EB..07F3 ; Case_Ignorable # Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE
+07F4..07F5 ; Case_Ignorable # Lm [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE
+07FA ; Case_Ignorable # Lm NKO LAJANYALAN
+07FD ; Case_Ignorable # Mn NKO DANTAYALAN
+0816..0819 ; Case_Ignorable # Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH
+081A ; Case_Ignorable # Lm SAMARITAN MODIFIER LETTER EPENTHETIC YUT
+081B..0823 ; Case_Ignorable # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A
+0824 ; Case_Ignorable # Lm SAMARITAN MODIFIER LETTER SHORT A
+0825..0827 ; Case_Ignorable # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U
+0828 ; Case_Ignorable # Lm SAMARITAN MODIFIER LETTER I
+0829..082D ; Case_Ignorable # Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA
+0859..085B ; Case_Ignorable # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK
+0888 ; Case_Ignorable # Sk ARABIC RAISED ROUND DOT
+0890..0891 ; Case_Ignorable # Cf [2] ARABIC POUND MARK ABOVE..ARABIC PIASTRE MARK ABOVE
+0897..089F ; Case_Ignorable # Mn [9] ARABIC PEPET..ARABIC HALF MADDA OVER MADDA
+08C9 ; Case_Ignorable # Lm ARABIC SMALL FARSI YEH
+08CA..08E1 ; Case_Ignorable # Mn [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA
+08E2 ; Case_Ignorable # Cf ARABIC DISPUTED END OF AYAH
+08E3..0902 ; Case_Ignorable # Mn [32] ARABIC TURNED DAMMA BELOW..DEVANAGARI SIGN ANUSVARA
+093A ; Case_Ignorable # Mn DEVANAGARI VOWEL SIGN OE
+093C ; Case_Ignorable # Mn DEVANAGARI SIGN NUKTA
+0941..0948 ; Case_Ignorable # Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI
+094D ; Case_Ignorable # Mn DEVANAGARI SIGN VIRAMA
+0951..0957 ; Case_Ignorable # Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE
+0962..0963 ; Case_Ignorable # Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL
+0971 ; Case_Ignorable # Lm DEVANAGARI SIGN HIGH SPACING DOT
+0981 ; Case_Ignorable # Mn BENGALI SIGN CANDRABINDU
+09BC ; Case_Ignorable # Mn BENGALI SIGN NUKTA
+09C1..09C4 ; Case_Ignorable # Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR
+09CD ; Case_Ignorable # Mn BENGALI SIGN VIRAMA
+09E2..09E3 ; Case_Ignorable # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL
+09FE ; Case_Ignorable # Mn BENGALI SANDHI MARK
+0A01..0A02 ; Case_Ignorable # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI
+0A3C ; Case_Ignorable # Mn GURMUKHI SIGN NUKTA
+0A41..0A42 ; Case_Ignorable # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU
+0A47..0A48 ; Case_Ignorable # Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI
+0A4B..0A4D ; Case_Ignorable # Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA
+0A51 ; Case_Ignorable # Mn GURMUKHI SIGN UDAAT
+0A70..0A71 ; Case_Ignorable # Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK
+0A75 ; Case_Ignorable # Mn GURMUKHI SIGN YAKASH
+0A81..0A82 ; Case_Ignorable # Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA
+0ABC ; Case_Ignorable # Mn GUJARATI SIGN NUKTA
+0AC1..0AC5 ; Case_Ignorable # Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E
+0AC7..0AC8 ; Case_Ignorable # Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI
+0ACD ; Case_Ignorable # Mn GUJARATI SIGN VIRAMA
+0AE2..0AE3 ; Case_Ignorable # Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL
+0AFA..0AFF ; Case_Ignorable # Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE
+0B01 ; Case_Ignorable # Mn ORIYA SIGN CANDRABINDU
+0B3C ; Case_Ignorable # Mn ORIYA SIGN NUKTA
+0B3F ; Case_Ignorable # Mn ORIYA VOWEL SIGN I
+0B41..0B44 ; Case_Ignorable # Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR
+0B4D ; Case_Ignorable # Mn ORIYA SIGN VIRAMA
+0B55..0B56 ; Case_Ignorable # Mn [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK
+0B62..0B63 ; Case_Ignorable # Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL
+0B82 ; Case_Ignorable # Mn TAMIL SIGN ANUSVARA
+0BC0 ; Case_Ignorable # Mn TAMIL VOWEL SIGN II
+0BCD ; Case_Ignorable # Mn TAMIL SIGN VIRAMA
+0C00 ; Case_Ignorable # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE
+0C04 ; Case_Ignorable # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE
+0C3C ; Case_Ignorable # Mn TELUGU SIGN NUKTA
+0C3E..0C40 ; Case_Ignorable # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II
+0C46..0C48 ; Case_Ignorable # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI
+0C4A..0C4D ; Case_Ignorable # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA
+0C55..0C56 ; Case_Ignorable # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK
+0C62..0C63 ; Case_Ignorable # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL
+0C81 ; Case_Ignorable # Mn KANNADA SIGN CANDRABINDU
+0CBC ; Case_Ignorable # Mn KANNADA SIGN NUKTA
+0CBF ; Case_Ignorable # Mn KANNADA VOWEL SIGN I
+0CC6 ; Case_Ignorable # Mn KANNADA VOWEL SIGN E
+0CCC..0CCD ; Case_Ignorable # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA
+0CE2..0CE3 ; Case_Ignorable # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL
+0D00..0D01 ; Case_Ignorable # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU
+0D3B..0D3C ; Case_Ignorable # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA
+0D41..0D44 ; Case_Ignorable # Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR
+0D4D ; Case_Ignorable # Mn MALAYALAM SIGN VIRAMA
+0D62..0D63 ; Case_Ignorable # Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL
+0D81 ; Case_Ignorable # Mn SINHALA SIGN CANDRABINDU
+0DCA ; Case_Ignorable # Mn SINHALA SIGN AL-LAKUNA
+0DD2..0DD4 ; Case_Ignorable # Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA
+0DD6 ; Case_Ignorable # Mn SINHALA VOWEL SIGN DIGA PAA-PILLA
+0E31 ; Case_Ignorable # Mn THAI CHARACTER MAI HAN-AKAT
+0E34..0E3A ; Case_Ignorable # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU
+0E46 ; Case_Ignorable # Lm THAI CHARACTER MAIYAMOK
+0E47..0E4E ; Case_Ignorable # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN
+0EB1 ; Case_Ignorable # Mn LAO VOWEL SIGN MAI KAN
+0EB4..0EBC ; Case_Ignorable # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO
+0EC6 ; Case_Ignorable # Lm LAO KO LA
+0EC8..0ECE ; Case_Ignorable # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN
+0F18..0F19 ; Case_Ignorable # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS
+0F35 ; Case_Ignorable # Mn TIBETAN MARK NGAS BZUNG NYI ZLA
+0F37 ; Case_Ignorable # Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS
+0F39 ; Case_Ignorable # Mn TIBETAN MARK TSA -PHRU
+0F71..0F7E ; Case_Ignorable # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO
+0F80..0F84 ; Case_Ignorable # Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA
+0F86..0F87 ; Case_Ignorable # Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS
+0F8D..0F97 ; Case_Ignorable # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA
+0F99..0FBC ; Case_Ignorable # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA
+0FC6 ; Case_Ignorable # Mn TIBETAN SYMBOL PADMA GDAN
+102D..1030 ; Case_Ignorable # Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU
+1032..1037 ; Case_Ignorable # Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW
+1039..103A ; Case_Ignorable # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT
+103D..103E ; Case_Ignorable # Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA
+1058..1059 ; Case_Ignorable # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL
+105E..1060 ; Case_Ignorable # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA
+1071..1074 ; Case_Ignorable # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE
+1082 ; Case_Ignorable # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA
+1085..1086 ; Case_Ignorable # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y
+108D ; Case_Ignorable # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE
+109D ; Case_Ignorable # Mn MYANMAR VOWEL SIGN AITON AI
+10FC ; Case_Ignorable # Lm MODIFIER LETTER GEORGIAN NAR
+135D..135F ; Case_Ignorable # Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK
+1712..1714 ; Case_Ignorable # Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA
+1732..1733 ; Case_Ignorable # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U
+1752..1753 ; Case_Ignorable # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U
+1772..1773 ; Case_Ignorable # Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U
+17B4..17B5 ; Case_Ignorable # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
+17B7..17BD ; Case_Ignorable # Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA
+17C6 ; Case_Ignorable # Mn KHMER SIGN NIKAHIT
+17C9..17D3 ; Case_Ignorable # Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT
+17D7 ; Case_Ignorable # Lm KHMER SIGN LEK TOO
+17DD ; Case_Ignorable # Mn KHMER SIGN ATTHACAN
+180B..180D ; Case_Ignorable # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
+180E ; Case_Ignorable # Cf MONGOLIAN VOWEL SEPARATOR
+180F ; Case_Ignorable # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR
+1843 ; Case_Ignorable # Lm MONGOLIAN LETTER TODO LONG VOWEL SIGN
+1885..1886 ; Case_Ignorable # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA
+18A9 ; Case_Ignorable # Mn MONGOLIAN LETTER ALI GALI DAGALGA
+1920..1922 ; Case_Ignorable # Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U
+1927..1928 ; Case_Ignorable # Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O
+1932 ; Case_Ignorable # Mn LIMBU SMALL LETTER ANUSVARA
+1939..193B ; Case_Ignorable # Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I
+1A17..1A18 ; Case_Ignorable # Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U
+1A1B ; Case_Ignorable # Mn BUGINESE VOWEL SIGN AE
+1A56 ; Case_Ignorable # Mn TAI THAM CONSONANT SIGN MEDIAL LA
+1A58..1A5E ; Case_Ignorable # Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA
+1A60 ; Case_Ignorable # Mn TAI THAM SIGN SAKOT
+1A62 ; Case_Ignorable # Mn TAI THAM VOWEL SIGN MAI SAT
+1A65..1A6C ; Case_Ignorable # Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW
+1A73..1A7C ; Case_Ignorable # Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN
+1A7F ; Case_Ignorable # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT
+1AA7 ; Case_Ignorable # Lm TAI THAM SIGN MAI YAMOK
+1AB0..1ABD ; Case_Ignorable # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW
+1ABE ; Case_Ignorable # Me COMBINING PARENTHESES OVERLAY
+1ABF..1ACE ; Case_Ignorable # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T
+1B00..1B03 ; Case_Ignorable # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG
+1B34 ; Case_Ignorable # Mn BALINESE SIGN REREKAN
+1B36..1B3A ; Case_Ignorable # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA
+1B3C ; Case_Ignorable # Mn BALINESE VOWEL SIGN LA LENGA
+1B42 ; Case_Ignorable # Mn BALINESE VOWEL SIGN PEPET
+1B6B..1B73 ; Case_Ignorable # Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG
+1B80..1B81 ; Case_Ignorable # Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR
+1BA2..1BA5 ; Case_Ignorable # Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU
+1BA8..1BA9 ; Case_Ignorable # Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG
+1BAB..1BAD ; Case_Ignorable # Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA
+1BE6 ; Case_Ignorable # Mn BATAK SIGN TOMPI
+1BE8..1BE9 ; Case_Ignorable # Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE
+1BED ; Case_Ignorable # Mn BATAK VOWEL SIGN KARO O
+1BEF..1BF1 ; Case_Ignorable # Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H
+1C2C..1C33 ; Case_Ignorable # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T
+1C36..1C37 ; Case_Ignorable # Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA
+1C78..1C7D ; Case_Ignorable # Lm [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD
+1CD0..1CD2 ; Case_Ignorable # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA
+1CD4..1CE0 ; Case_Ignorable # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA
+1CE2..1CE8 ; Case_Ignorable # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL
+1CED ; Case_Ignorable # Mn VEDIC SIGN TIRYAK
+1CF4 ; Case_Ignorable # Mn VEDIC TONE CANDRA ABOVE
+1CF8..1CF9 ; Case_Ignorable # Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE
+1D2C..1D6A ; Case_Ignorable # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI
+1D78 ; Case_Ignorable # Lm MODIFIER LETTER CYRILLIC EN
+1D9B..1DBF ; Case_Ignorable # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA
+1DC0..1DFF ; Case_Ignorable # Mn [64] COMBINING DOTTED GRAVE ACCENT..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW
+1FBD ; Case_Ignorable # Sk GREEK KORONIS
+1FBF..1FC1 ; Case_Ignorable # Sk [3] GREEK PSILI..GREEK DIALYTIKA AND PERISPOMENI
+1FCD..1FCF ; Case_Ignorable # Sk [3] GREEK PSILI AND VARIA..GREEK PSILI AND PERISPOMENI
+1FDD..1FDF ; Case_Ignorable # Sk [3] GREEK DASIA AND VARIA..GREEK DASIA AND PERISPOMENI
+1FED..1FEF ; Case_Ignorable # Sk [3] GREEK DIALYTIKA AND VARIA..GREEK VARIA
+1FFD..1FFE ; Case_Ignorable # Sk [2] GREEK OXIA..GREEK DASIA
+200B..200F ; Case_Ignorable # Cf [5] ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK
+2018 ; Case_Ignorable # Pi LEFT SINGLE QUOTATION MARK
+2019 ; Case_Ignorable # Pf RIGHT SINGLE QUOTATION MARK
+2024 ; Case_Ignorable # Po ONE DOT LEADER
+2027 ; Case_Ignorable # Po HYPHENATION POINT
+202A..202E ; Case_Ignorable # Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
+2060..2064 ; Case_Ignorable # Cf [5] WORD JOINER..INVISIBLE PLUS
+2066..206F ; Case_Ignorable # Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES
+2071 ; Case_Ignorable # Lm SUPERSCRIPT LATIN SMALL LETTER I
+207F ; Case_Ignorable # Lm SUPERSCRIPT LATIN SMALL LETTER N
+2090..209C ; Case_Ignorable # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T
+20D0..20DC ; Case_Ignorable # Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE
+20DD..20E0 ; Case_Ignorable # Me [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH
+20E1 ; Case_Ignorable # Mn COMBINING LEFT RIGHT ARROW ABOVE
+20E2..20E4 ; Case_Ignorable # Me [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE
+20E5..20F0 ; Case_Ignorable # Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE
+2C7C..2C7D ; Case_Ignorable # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V
+2CEF..2CF1 ; Case_Ignorable # Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS
+2D6F ; Case_Ignorable # Lm TIFINAGH MODIFIER LETTER LABIALIZATION MARK
+2D7F ; Case_Ignorable # Mn TIFINAGH CONSONANT JOINER
+2DE0..2DFF ; Case_Ignorable # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS
+2E2F ; Case_Ignorable # Lm VERTICAL TILDE
+3005 ; Case_Ignorable # Lm IDEOGRAPHIC ITERATION MARK
+302A..302D ; Case_Ignorable # Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK
+3031..3035 ; Case_Ignorable # Lm [5] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT MARK LOWER HALF
+303B ; Case_Ignorable # Lm VERTICAL IDEOGRAPHIC ITERATION MARK
+3099..309A ; Case_Ignorable # Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+309B..309C ; Case_Ignorable # Sk [2] KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+309D..309E ; Case_Ignorable # Lm [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK
+30FC..30FE ; Case_Ignorable # Lm [3] KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK
+A015 ; Case_Ignorable # Lm YI SYLLABLE WU
+A4F8..A4FD ; Case_Ignorable # Lm [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU
+A60C ; Case_Ignorable # Lm VAI SYLLABLE LENGTHENER
+A66F ; Case_Ignorable # Mn COMBINING CYRILLIC VZMET
+A670..A672 ; Case_Ignorable # Me [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN
+A674..A67D ; Case_Ignorable # Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK
+A67F ; Case_Ignorable # Lm CYRILLIC PAYEROK
+A69C..A69D ; Case_Ignorable # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN
+A69E..A69F ; Case_Ignorable # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E
+A6F0..A6F1 ; Case_Ignorable # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS
+A700..A716 ; Case_Ignorable # Sk [23] MODIFIER LETTER CHINESE TONE YIN PING..MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR
+A717..A71F ; Case_Ignorable # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK
+A720..A721 ; Case_Ignorable # Sk [2] MODIFIER LETTER STRESS AND HIGH TONE..MODIFIER LETTER STRESS AND LOW TONE
+A770 ; Case_Ignorable # Lm MODIFIER LETTER US
+A788 ; Case_Ignorable # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT
+A789..A78A ; Case_Ignorable # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN
+A7F2..A7F4 ; Case_Ignorable # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q
+A7F8..A7F9 ; Case_Ignorable # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE
+A802 ; Case_Ignorable # Mn SYLOTI NAGRI SIGN DVISVARA
+A806 ; Case_Ignorable # Mn SYLOTI NAGRI SIGN HASANTA
+A80B ; Case_Ignorable # Mn SYLOTI NAGRI SIGN ANUSVARA
+A825..A826 ; Case_Ignorable # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E
+A82C ; Case_Ignorable # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA
+A8C4..A8C5 ; Case_Ignorable # Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU
+A8E0..A8F1 ; Case_Ignorable # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA
+A8FF ; Case_Ignorable # Mn DEVANAGARI VOWEL SIGN AY
+A926..A92D ; Case_Ignorable # Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU
+A947..A951 ; Case_Ignorable # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R
+A980..A982 ; Case_Ignorable # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR
+A9B3 ; Case_Ignorable # Mn JAVANESE SIGN CECAK TELU
+A9B6..A9B9 ; Case_Ignorable # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT
+A9BC..A9BD ; Case_Ignorable # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET
+A9CF ; Case_Ignorable # Lm JAVANESE PANGRANGKEP
+A9E5 ; Case_Ignorable # Mn MYANMAR SIGN SHAN SAW
+A9E6 ; Case_Ignorable # Lm MYANMAR MODIFIER LETTER SHAN REDUPLICATION
+AA29..AA2E ; Case_Ignorable # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE
+AA31..AA32 ; Case_Ignorable # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE
+AA35..AA36 ; Case_Ignorable # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA
+AA43 ; Case_Ignorable # Mn CHAM CONSONANT SIGN FINAL NG
+AA4C ; Case_Ignorable # Mn CHAM CONSONANT SIGN FINAL M
+AA70 ; Case_Ignorable # Lm MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION
+AA7C ; Case_Ignorable # Mn MYANMAR SIGN TAI LAING TONE-2
+AAB0 ; Case_Ignorable # Mn TAI VIET MAI KANG
+AAB2..AAB4 ; Case_Ignorable # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U
+AAB7..AAB8 ; Case_Ignorable # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA
+AABE..AABF ; Case_Ignorable # Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK
+AAC1 ; Case_Ignorable # Mn TAI VIET TONE MAI THO
+AADD ; Case_Ignorable # Lm TAI VIET SYMBOL SAM
+AAEC..AAED ; Case_Ignorable # Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI
+AAF3..AAF4 ; Case_Ignorable # Lm [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK
+AAF6 ; Case_Ignorable # Mn MEETEI MAYEK VIRAMA
+AB5B ; Case_Ignorable # Sk MODIFIER BREVE WITH INVERTED BREVE
+AB5C..AB5F ; Case_Ignorable # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK
+AB69 ; Case_Ignorable # Lm MODIFIER LETTER SMALL TURNED W
+AB6A..AB6B ; Case_Ignorable # Sk [2] MODIFIER LETTER LEFT TACK..MODIFIER LETTER RIGHT TACK
+ABE5 ; Case_Ignorable # Mn MEETEI MAYEK VOWEL SIGN ANAP
+ABE8 ; Case_Ignorable # Mn MEETEI MAYEK VOWEL SIGN UNAP
+ABED ; Case_Ignorable # Mn MEETEI MAYEK APUN IYEK
+FB1E ; Case_Ignorable # Mn HEBREW POINT JUDEO-SPANISH VARIKA
+FBB2..FBC2 ; Case_Ignorable # Sk [17] ARABIC SYMBOL DOT ABOVE..ARABIC SYMBOL WASLA ABOVE
+FE00..FE0F ; Case_Ignorable # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
+FE13 ; Case_Ignorable # Po PRESENTATION FORM FOR VERTICAL COLON
+FE20..FE2F ; Case_Ignorable # Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF
+FE52 ; Case_Ignorable # Po SMALL FULL STOP
+FE55 ; Case_Ignorable # Po SMALL COLON
+FEFF ; Case_Ignorable # Cf ZERO WIDTH NO-BREAK SPACE
+FF07 ; Case_Ignorable # Po FULLWIDTH APOSTROPHE
+FF0E ; Case_Ignorable # Po FULLWIDTH FULL STOP
+FF1A ; Case_Ignorable # Po FULLWIDTH COLON
+FF3E ; Case_Ignorable # Sk FULLWIDTH CIRCUMFLEX ACCENT
+FF40 ; Case_Ignorable # Sk FULLWIDTH GRAVE ACCENT
+FF70 ; Case_Ignorable # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK
+FF9E..FF9F ; Case_Ignorable # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
+FFE3 ; Case_Ignorable # Sk FULLWIDTH MACRON
+FFF9..FFFB ; Case_Ignorable # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR
+101FD ; Case_Ignorable # Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE
+102E0 ; Case_Ignorable # Mn COPTIC EPACT THOUSANDS MARK
+10376..1037A ; Case_Ignorable # Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII
+10780..10785 ; Case_Ignorable # Lm [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK
+10787..107B0 ; Case_Ignorable # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK
+107B2..107BA ; Case_Ignorable # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL
+10A01..10A03 ; Case_Ignorable # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R
+10A05..10A06 ; Case_Ignorable # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O
+10A0C..10A0F ; Case_Ignorable # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA
+10A38..10A3A ; Case_Ignorable # Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW
+10A3F ; Case_Ignorable # Mn KHAROSHTHI VIRAMA
+10AE5..10AE6 ; Case_Ignorable # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW
+10D24..10D27 ; Case_Ignorable # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI
+10D4E ; Case_Ignorable # Lm GARAY VOWEL LENGTH MARK
+10D69..10D6D ; Case_Ignorable # Mn [5] GARAY VOWEL SIGN E..GARAY CONSONANT NASALIZATION MARK
+10D6F ; Case_Ignorable # Lm GARAY REDUPLICATION MARK
+10EAB..10EAC ; Case_Ignorable # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK
+10EFC..10EFF ; Case_Ignorable # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA
+10F46..10F50 ; Case_Ignorable # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW
+10F82..10F85 ; Case_Ignorable # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW
+11001 ; Case_Ignorable # Mn BRAHMI SIGN ANUSVARA
+11038..11046 ; Case_Ignorable # Mn [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA
+11070 ; Case_Ignorable # Mn BRAHMI SIGN OLD TAMIL VIRAMA
+11073..11074 ; Case_Ignorable # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O
+1107F..11081 ; Case_Ignorable # Mn [3] BRAHMI NUMBER JOINER..KAITHI SIGN ANUSVARA
+110B3..110B6 ; Case_Ignorable # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI
+110B9..110BA ; Case_Ignorable # Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA
+110BD ; Case_Ignorable # Cf KAITHI NUMBER SIGN
+110C2 ; Case_Ignorable # Mn KAITHI VOWEL SIGN VOCALIC R
+110CD ; Case_Ignorable # Cf KAITHI NUMBER SIGN ABOVE
+11100..11102 ; Case_Ignorable # Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA
+11127..1112B ; Case_Ignorable # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU
+1112D..11134 ; Case_Ignorable # Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA
+11173 ; Case_Ignorable # Mn MAHAJANI SIGN NUKTA
+11180..11181 ; Case_Ignorable # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA
+111B6..111BE ; Case_Ignorable # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O
+111C9..111CC ; Case_Ignorable # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK
+111CF ; Case_Ignorable # Mn SHARADA SIGN INVERTED CANDRABINDU
+1122F..11231 ; Case_Ignorable # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI
+11234 ; Case_Ignorable # Mn KHOJKI SIGN ANUSVARA
+11236..11237 ; Case_Ignorable # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA
+1123E ; Case_Ignorable # Mn KHOJKI SIGN SUKUN
+11241 ; Case_Ignorable # Mn KHOJKI VOWEL SIGN VOCALIC R
+112DF ; Case_Ignorable # Mn KHUDAWADI SIGN ANUSVARA
+112E3..112EA ; Case_Ignorable # Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA
+11300..11301 ; Case_Ignorable # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU
+1133B..1133C ; Case_Ignorable # Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA
+11340 ; Case_Ignorable # Mn GRANTHA VOWEL SIGN II
+11366..1136C ; Case_Ignorable # Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX
+11370..11374 ; Case_Ignorable # Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA
+113BB..113C0 ; Case_Ignorable # Mn [6] TULU-TIGALARI VOWEL SIGN U..TULU-TIGALARI VOWEL SIGN VOCALIC LL
+113CE ; Case_Ignorable # Mn TULU-TIGALARI SIGN VIRAMA
+113D0 ; Case_Ignorable # Mn TULU-TIGALARI CONJOINER
+113D2 ; Case_Ignorable # Mn TULU-TIGALARI GEMINATION MARK
+113E1..113E2 ; Case_Ignorable # Mn [2] TULU-TIGALARI VEDIC TONE SVARITA..TULU-TIGALARI VEDIC TONE ANUDATTA
+11438..1143F ; Case_Ignorable # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI
+11442..11444 ; Case_Ignorable # Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA
+11446 ; Case_Ignorable # Mn NEWA SIGN NUKTA
+1145E ; Case_Ignorable # Mn NEWA SANDHI MARK
+114B3..114B8 ; Case_Ignorable # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL
+114BA ; Case_Ignorable # Mn TIRHUTA VOWEL SIGN SHORT E
+114BF..114C0 ; Case_Ignorable # Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA
+114C2..114C3 ; Case_Ignorable # Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA
+115B2..115B5 ; Case_Ignorable # Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR
+115BC..115BD ; Case_Ignorable # Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA
+115BF..115C0 ; Case_Ignorable # Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA
+115DC..115DD ; Case_Ignorable # Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU
+11633..1163A ; Case_Ignorable # Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI
+1163D ; Case_Ignorable # Mn MODI SIGN ANUSVARA
+1163F..11640 ; Case_Ignorable # Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA
+116AB ; Case_Ignorable # Mn TAKRI SIGN ANUSVARA
+116AD ; Case_Ignorable # Mn TAKRI VOWEL SIGN AA
+116B0..116B5 ; Case_Ignorable # Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU
+116B7 ; Case_Ignorable # Mn TAKRI SIGN NUKTA
+1171D ; Case_Ignorable # Mn AHOM CONSONANT SIGN MEDIAL LA
+1171F ; Case_Ignorable # Mn AHOM CONSONANT SIGN MEDIAL LIGATING RA
+11722..11725 ; Case_Ignorable # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU
+11727..1172B ; Case_Ignorable # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER
+1182F..11837 ; Case_Ignorable # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA
+11839..1183A ; Case_Ignorable # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA
+1193B..1193C ; Case_Ignorable # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU
+1193E ; Case_Ignorable # Mn DIVES AKURU VIRAMA
+11943 ; Case_Ignorable # Mn DIVES AKURU SIGN NUKTA
+119D4..119D7 ; Case_Ignorable # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR
+119DA..119DB ; Case_Ignorable # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI
+119E0 ; Case_Ignorable # Mn NANDINAGARI SIGN VIRAMA
+11A01..11A0A ; Case_Ignorable # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK
+11A33..11A38 ; Case_Ignorable # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA
+11A3B..11A3E ; Case_Ignorable # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA
+11A47 ; Case_Ignorable # Mn ZANABAZAR SQUARE SUBJOINER
+11A51..11A56 ; Case_Ignorable # Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE
+11A59..11A5B ; Case_Ignorable # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK
+11A8A..11A96 ; Case_Ignorable # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA
+11A98..11A99 ; Case_Ignorable # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER
+11C30..11C36 ; Case_Ignorable # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L
+11C38..11C3D ; Case_Ignorable # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA
+11C3F ; Case_Ignorable # Mn BHAIKSUKI SIGN VIRAMA
+11C92..11CA7 ; Case_Ignorable # Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA
+11CAA..11CB0 ; Case_Ignorable # Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA
+11CB2..11CB3 ; Case_Ignorable # Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E
+11CB5..11CB6 ; Case_Ignorable # Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU
+11D31..11D36 ; Case_Ignorable # Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R
+11D3A ; Case_Ignorable # Mn MASARAM GONDI VOWEL SIGN E
+11D3C..11D3D ; Case_Ignorable # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O
+11D3F..11D45 ; Case_Ignorable # Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA
+11D47 ; Case_Ignorable # Mn MASARAM GONDI RA-KARA
+11D90..11D91 ; Case_Ignorable # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI
+11D95 ; Case_Ignorable # Mn GUNJALA GONDI SIGN ANUSVARA
+11D97 ; Case_Ignorable # Mn GUNJALA GONDI VIRAMA
+11EF3..11EF4 ; Case_Ignorable # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U
+11F00..11F01 ; Case_Ignorable # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA
+11F36..11F3A ; Case_Ignorable # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R
+11F40 ; Case_Ignorable # Mn KAWI VOWEL SIGN EU
+11F42 ; Case_Ignorable # Mn KAWI CONJOINER
+11F5A ; Case_Ignorable # Mn KAWI SIGN NUKTA
+13430..1343F ; Case_Ignorable # Cf [16] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE
+13440 ; Case_Ignorable # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY
+13447..13455 ; Case_Ignorable # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED
+1611E..16129 ; Case_Ignorable # Mn [12] GURUNG KHEMA VOWEL SIGN AA..GURUNG KHEMA VOWEL LENGTH MARK
+1612D..1612F ; Case_Ignorable # Mn [3] GURUNG KHEMA SIGN ANUSVARA..GURUNG KHEMA SIGN THOLHOMA
+16AF0..16AF4 ; Case_Ignorable # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE
+16B30..16B36 ; Case_Ignorable # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
+16B40..16B43 ; Case_Ignorable # Lm [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM
+16D40..16D42 ; Case_Ignorable # Lm [3] KIRAT RAI SIGN ANUSVARA..KIRAT RAI SIGN VISARGA
+16D6B..16D6C ; Case_Ignorable # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT
+16F4F ; Case_Ignorable # Mn MIAO SIGN CONSONANT MODIFIER BAR
+16F8F..16F92 ; Case_Ignorable # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
+16F93..16F9F ; Case_Ignorable # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8
+16FE0..16FE1 ; Case_Ignorable # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK
+16FE3 ; Case_Ignorable # Lm OLD CHINESE ITERATION MARK
+16FE4 ; Case_Ignorable # Mn KHITAN SMALL SCRIPT FILLER
+1AFF0..1AFF3 ; Case_Ignorable # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5
+1AFF5..1AFFB ; Case_Ignorable # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5
+1AFFD..1AFFE ; Case_Ignorable # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8
+1BC9D..1BC9E ; Case_Ignorable # Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK
+1BCA0..1BCA3 ; Case_Ignorable # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP
+1CF00..1CF2D ; Case_Ignorable # Mn [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT
+1CF30..1CF46 ; Case_Ignorable # Mn [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG
+1D167..1D169 ; Case_Ignorable # Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3
+1D173..1D17A ; Case_Ignorable # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE
+1D17B..1D182 ; Case_Ignorable # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE
+1D185..1D18B ; Case_Ignorable # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE
+1D1AA..1D1AD ; Case_Ignorable # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO
+1D242..1D244 ; Case_Ignorable # Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME
+1DA00..1DA36 ; Case_Ignorable # Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN
+1DA3B..1DA6C ; Case_Ignorable # Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT
+1DA75 ; Case_Ignorable # Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS
+1DA84 ; Case_Ignorable # Mn SIGNWRITING LOCATION HEAD NECK
+1DA9B..1DA9F ; Case_Ignorable # Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6
+1DAA1..1DAAF ; Case_Ignorable # Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16
+1E000..1E006 ; Case_Ignorable # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE
+1E008..1E018 ; Case_Ignorable # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU
+1E01B..1E021 ; Case_Ignorable # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI
+1E023..1E024 ; Case_Ignorable # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS
+1E026..1E02A ; Case_Ignorable # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA
+1E030..1E06D ; Case_Ignorable # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE
+1E08F ; Case_Ignorable # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+1E130..1E136 ; Case_Ignorable # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D
+1E137..1E13D ; Case_Ignorable # Lm [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER
+1E2AE ; Case_Ignorable # Mn TOTO SIGN RISING TONE
+1E2EC..1E2EF ; Case_Ignorable # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI
+1E4EB ; Case_Ignorable # Lm NAG MUNDARI SIGN OJOD
+1E4EC..1E4EF ; Case_Ignorable # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH
+1E5EE..1E5EF ; Case_Ignorable # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR
+1E8D0..1E8D6 ; Case_Ignorable # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS
+1E944..1E94A ; Case_Ignorable # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA
+1E94B ; Case_Ignorable # Lm ADLAM NASALIZATION MARK
+1F3FB..1F3FF ; Case_Ignorable # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6
+E0001 ; Case_Ignorable # Cf LANGUAGE TAG
+E0020..E007F ; Case_Ignorable # Cf [96] TAG SPACE..CANCEL TAG
+E0100..E01EF ; Case_Ignorable # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
+
+# Total code points: 2749
+
+# ================================================
+
+# Derived Property: Changes_When_Lowercased (CWL)
+# Characters whose normalized forms are not stable under a toLowercase mapping.
+# For more information, see D139 in Section 3.13, "Default Case Algorithms".
+# Changes_When_Lowercased(X) is true when toLowercase(toNFD(X)) != toNFD(X)
+
+0041..005A ; Changes_When_Lowercased # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+00C0..00D6 ; Changes_When_Lowercased # L& [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS
+00D8..00DE ; Changes_When_Lowercased # L& [7] LATIN CAPITAL LETTER O WITH STROKE..LATIN CAPITAL LETTER THORN
+0100 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH MACRON
+0102 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH BREVE
+0104 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH OGONEK
+0106 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER C WITH ACUTE
+0108 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+010A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER C WITH DOT ABOVE
+010C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER C WITH CARON
+010E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER D WITH CARON
+0110 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER D WITH STROKE
+0112 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH MACRON
+0114 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH BREVE
+0116 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH DOT ABOVE
+0118 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH OGONEK
+011A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH CARON
+011C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+011E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER G WITH BREVE
+0120 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER G WITH DOT ABOVE
+0122 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER G WITH CEDILLA
+0124 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+0126 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER H WITH STROKE
+0128 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER I WITH TILDE
+012A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER I WITH MACRON
+012C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER I WITH BREVE
+012E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER I WITH OGONEK
+0130 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER I WITH DOT ABOVE
+0132 ; Changes_When_Lowercased # L& LATIN CAPITAL LIGATURE IJ
+0134 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+0136 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER K WITH CEDILLA
+0139 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER L WITH ACUTE
+013B ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER L WITH CEDILLA
+013D ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER L WITH CARON
+013F ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER L WITH MIDDLE DOT
+0141 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER L WITH STROKE
+0143 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER N WITH ACUTE
+0145 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER N WITH CEDILLA
+0147 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER N WITH CARON
+014A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER ENG
+014C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH MACRON
+014E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH BREVE
+0150 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+0152 ; Changes_When_Lowercased # L& LATIN CAPITAL LIGATURE OE
+0154 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER R WITH ACUTE
+0156 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER R WITH CEDILLA
+0158 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER R WITH CARON
+015A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER S WITH ACUTE
+015C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+015E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER S WITH CEDILLA
+0160 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER S WITH CARON
+0162 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER T WITH CEDILLA
+0164 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER T WITH CARON
+0166 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER T WITH STROKE
+0168 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH TILDE
+016A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH MACRON
+016C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH BREVE
+016E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH RING ABOVE
+0170 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+0172 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH OGONEK
+0174 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER W WITH CIRCUMFLEX
+0176 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Y WITH CIRCUMFLEX
+0178..0179 ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER Y WITH DIAERESIS..LATIN CAPITAL LETTER Z WITH ACUTE
+017B ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Z WITH DOT ABOVE
+017D ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Z WITH CARON
+0181..0182 ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER B WITH HOOK..LATIN CAPITAL LETTER B WITH TOPBAR
+0184 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER TONE SIX
+0186..0187 ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER OPEN O..LATIN CAPITAL LETTER C WITH HOOK
+0189..018B ; Changes_When_Lowercased # L& [3] LATIN CAPITAL LETTER AFRICAN D..LATIN CAPITAL LETTER D WITH TOPBAR
+018E..0191 ; Changes_When_Lowercased # L& [4] LATIN CAPITAL LETTER REVERSED E..LATIN CAPITAL LETTER F WITH HOOK
+0193..0194 ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER G WITH HOOK..LATIN CAPITAL LETTER GAMMA
+0196..0198 ; Changes_When_Lowercased # L& [3] LATIN CAPITAL LETTER IOTA..LATIN CAPITAL LETTER K WITH HOOK
+019C..019D ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER TURNED M..LATIN CAPITAL LETTER N WITH LEFT HOOK
+019F..01A0 ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER O WITH MIDDLE TILDE..LATIN CAPITAL LETTER O WITH HORN
+01A2 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER OI
+01A4 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER P WITH HOOK
+01A6..01A7 ; Changes_When_Lowercased # L& [2] LATIN LETTER YR..LATIN CAPITAL LETTER TONE TWO
+01A9 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER ESH
+01AC ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER T WITH HOOK
+01AE..01AF ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER T WITH RETROFLEX HOOK..LATIN CAPITAL LETTER U WITH HORN
+01B1..01B3 ; Changes_When_Lowercased # L& [3] LATIN CAPITAL LETTER UPSILON..LATIN CAPITAL LETTER Y WITH HOOK
+01B5 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Z WITH STROKE
+01B7..01B8 ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER EZH..LATIN CAPITAL LETTER EZH REVERSED
+01BC ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER TONE FIVE
+01C4..01C5 ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER DZ WITH CARON..LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON
+01C7..01C8 ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER LJ..LATIN CAPITAL LETTER L WITH SMALL LETTER J
+01CA..01CB ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER NJ..LATIN CAPITAL LETTER N WITH SMALL LETTER J
+01CD ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH CARON
+01CF ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER I WITH CARON
+01D1 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH CARON
+01D3 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH CARON
+01D5 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON
+01D7 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE
+01D9 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON
+01DB ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE
+01DE ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON
+01E0 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON
+01E2 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER AE WITH MACRON
+01E4 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER G WITH STROKE
+01E6 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER G WITH CARON
+01E8 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER K WITH CARON
+01EA ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH OGONEK
+01EC ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH OGONEK AND MACRON
+01EE ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER EZH WITH CARON
+01F1..01F2 ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER DZ..LATIN CAPITAL LETTER D WITH SMALL LETTER Z
+01F4 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER G WITH ACUTE
+01F6..01F8 ; Changes_When_Lowercased # L& [3] LATIN CAPITAL LETTER HWAIR..LATIN CAPITAL LETTER N WITH GRAVE
+01FA ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE
+01FC ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER AE WITH ACUTE
+01FE ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH STROKE AND ACUTE
+0200 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH DOUBLE GRAVE
+0202 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH INVERTED BREVE
+0204 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH DOUBLE GRAVE
+0206 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH INVERTED BREVE
+0208 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER I WITH DOUBLE GRAVE
+020A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER I WITH INVERTED BREVE
+020C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH DOUBLE GRAVE
+020E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH INVERTED BREVE
+0210 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER R WITH DOUBLE GRAVE
+0212 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER R WITH INVERTED BREVE
+0214 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH DOUBLE GRAVE
+0216 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH INVERTED BREVE
+0218 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER S WITH COMMA BELOW
+021A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER T WITH COMMA BELOW
+021C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER YOGH
+021E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER H WITH CARON
+0220 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER N WITH LONG RIGHT LEG
+0222 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER OU
+0224 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Z WITH HOOK
+0226 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH DOT ABOVE
+0228 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH CEDILLA
+022A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON
+022C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH TILDE AND MACRON
+022E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH DOT ABOVE
+0230 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON
+0232 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Y WITH MACRON
+023A..023B ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER A WITH STROKE..LATIN CAPITAL LETTER C WITH STROKE
+023D..023E ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER L WITH BAR..LATIN CAPITAL LETTER T WITH DIAGONAL STROKE
+0241 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER GLOTTAL STOP
+0243..0246 ; Changes_When_Lowercased # L& [4] LATIN CAPITAL LETTER B WITH STROKE..LATIN CAPITAL LETTER E WITH STROKE
+0248 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER J WITH STROKE
+024A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL
+024C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER R WITH STROKE
+024E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Y WITH STROKE
+0370 ; Changes_When_Lowercased # L& GREEK CAPITAL LETTER HETA
+0372 ; Changes_When_Lowercased # L& GREEK CAPITAL LETTER ARCHAIC SAMPI
+0376 ; Changes_When_Lowercased # L& GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA
+037F ; Changes_When_Lowercased # L& GREEK CAPITAL LETTER YOT
+0386 ; Changes_When_Lowercased # L& GREEK CAPITAL LETTER ALPHA WITH TONOS
+0388..038A ; Changes_When_Lowercased # L& [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS
+038C ; Changes_When_Lowercased # L& GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E..038F ; Changes_When_Lowercased # L& [2] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER OMEGA WITH TONOS
+0391..03A1 ; Changes_When_Lowercased # L& [17] GREEK CAPITAL LETTER ALPHA..GREEK CAPITAL LETTER RHO
+03A3..03AB ; Changes_When_Lowercased # L& [9] GREEK CAPITAL LETTER SIGMA..GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+03CF ; Changes_When_Lowercased # L& GREEK CAPITAL KAI SYMBOL
+03D8 ; Changes_When_Lowercased # L& GREEK LETTER ARCHAIC KOPPA
+03DA ; Changes_When_Lowercased # L& GREEK LETTER STIGMA
+03DC ; Changes_When_Lowercased # L& GREEK LETTER DIGAMMA
+03DE ; Changes_When_Lowercased # L& GREEK LETTER KOPPA
+03E0 ; Changes_When_Lowercased # L& GREEK LETTER SAMPI
+03E2 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER SHEI
+03E4 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER FEI
+03E6 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER KHEI
+03E8 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER HORI
+03EA ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER GANGIA
+03EC ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER SHIMA
+03EE ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER DEI
+03F4 ; Changes_When_Lowercased # L& GREEK CAPITAL THETA SYMBOL
+03F7 ; Changes_When_Lowercased # L& GREEK CAPITAL LETTER SHO
+03F9..03FA ; Changes_When_Lowercased # L& [2] GREEK CAPITAL LUNATE SIGMA SYMBOL..GREEK CAPITAL LETTER SAN
+03FD..042F ; Changes_When_Lowercased # L& [51] GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL..CYRILLIC CAPITAL LETTER YA
+0460 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER OMEGA
+0462 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER YAT
+0464 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER IOTIFIED E
+0466 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER LITTLE YUS
+0468 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS
+046A ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER BIG YUS
+046C ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS
+046E ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KSI
+0470 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER PSI
+0472 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER FITA
+0474 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER IZHITSA
+0476 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT
+0478 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER UK
+047A ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER ROUND OMEGA
+047C ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER OMEGA WITH TITLO
+047E ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER OT
+0480 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KOPPA
+048A ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER SHORT I WITH TAIL
+048C ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER SEMISOFT SIGN
+048E ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER ER WITH TICK
+0490 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+0492 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER GHE WITH STROKE
+0494 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK
+0496 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER
+0498 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER ZE WITH DESCENDER
+049A ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KA WITH DESCENDER
+049C ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE
+049E ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KA WITH STROKE
+04A0 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER BASHKIR KA
+04A2 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER EN WITH DESCENDER
+04A4 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LIGATURE EN GHE
+04A6 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK
+04A8 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER ABKHASIAN HA
+04AA ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER ES WITH DESCENDER
+04AC ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER TE WITH DESCENDER
+04AE ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER STRAIGHT U
+04B0 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE
+04B2 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER HA WITH DESCENDER
+04B4 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LIGATURE TE TSE
+04B6 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER CHE WITH DESCENDER
+04B8 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE
+04BA ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER SHHA
+04BC ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER ABKHASIAN CHE
+04BE ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER
+04C0..04C1 ; Changes_When_Lowercased # L& [2] CYRILLIC LETTER PALOCHKA..CYRILLIC CAPITAL LETTER ZHE WITH BREVE
+04C3 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KA WITH HOOK
+04C5 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER EL WITH TAIL
+04C7 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER EN WITH HOOK
+04C9 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER EN WITH TAIL
+04CB ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KHAKASSIAN CHE
+04CD ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER EM WITH TAIL
+04D0 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER A WITH BREVE
+04D2 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER A WITH DIAERESIS
+04D4 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LIGATURE A IE
+04D6 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER IE WITH BREVE
+04D8 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER SCHWA
+04DA ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS
+04DC ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS
+04DE ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS
+04E0 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER ABKHASIAN DZE
+04E2 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER I WITH MACRON
+04E4 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER I WITH DIAERESIS
+04E6 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER O WITH DIAERESIS
+04E8 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER BARRED O
+04EA ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS
+04EC ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER E WITH DIAERESIS
+04EE ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER U WITH MACRON
+04F0 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER U WITH DIAERESIS
+04F2 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE
+04F4 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS
+04F6 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER GHE WITH DESCENDER
+04F8 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS
+04FA ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK
+04FC ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER HA WITH HOOK
+04FE ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER HA WITH STROKE
+0500 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KOMI DE
+0502 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KOMI DJE
+0504 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KOMI ZJE
+0506 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KOMI DZJE
+0508 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KOMI LJE
+050A ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KOMI NJE
+050C ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KOMI SJE
+050E ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER KOMI TJE
+0510 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER REVERSED ZE
+0512 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER EL WITH HOOK
+0514 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER LHA
+0516 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER RHA
+0518 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER YAE
+051A ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER QA
+051C ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER WE
+051E ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER ALEUT KA
+0520 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK
+0522 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK
+0524 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER PE WITH DESCENDER
+0526 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER
+0528 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER EN WITH LEFT HOOK
+052A ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER DZZHE
+052C ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER DCHE
+052E ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER EL WITH DESCENDER
+0531..0556 ; Changes_When_Lowercased # L& [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH
+10A0..10C5 ; Changes_When_Lowercased # L& [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE
+10C7 ; Changes_When_Lowercased # L& GEORGIAN CAPITAL LETTER YN
+10CD ; Changes_When_Lowercased # L& GEORGIAN CAPITAL LETTER AEN
+13A0..13F5 ; Changes_When_Lowercased # L& [86] CHEROKEE LETTER A..CHEROKEE LETTER MV
+1C89 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER TJE
+1C90..1CBA ; Changes_When_Lowercased # L& [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN
+1CBD..1CBF ; Changes_When_Lowercased # L& [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
+1E00 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH RING BELOW
+1E02 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER B WITH DOT ABOVE
+1E04 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER B WITH DOT BELOW
+1E06 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER B WITH LINE BELOW
+1E08 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE
+1E0A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER D WITH DOT ABOVE
+1E0C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER D WITH DOT BELOW
+1E0E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER D WITH LINE BELOW
+1E10 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER D WITH CEDILLA
+1E12 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW
+1E14 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH MACRON AND GRAVE
+1E16 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH MACRON AND ACUTE
+1E18 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW
+1E1A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH TILDE BELOW
+1E1C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE
+1E1E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER F WITH DOT ABOVE
+1E20 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER G WITH MACRON
+1E22 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER H WITH DOT ABOVE
+1E24 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER H WITH DOT BELOW
+1E26 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER H WITH DIAERESIS
+1E28 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER H WITH CEDILLA
+1E2A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER H WITH BREVE BELOW
+1E2C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER I WITH TILDE BELOW
+1E2E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE
+1E30 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER K WITH ACUTE
+1E32 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER K WITH DOT BELOW
+1E34 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER K WITH LINE BELOW
+1E36 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER L WITH DOT BELOW
+1E38 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON
+1E3A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER L WITH LINE BELOW
+1E3C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW
+1E3E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER M WITH ACUTE
+1E40 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER M WITH DOT ABOVE
+1E42 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER M WITH DOT BELOW
+1E44 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER N WITH DOT ABOVE
+1E46 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER N WITH DOT BELOW
+1E48 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER N WITH LINE BELOW
+1E4A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW
+1E4C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH TILDE AND ACUTE
+1E4E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS
+1E50 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH MACRON AND GRAVE
+1E52 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH MACRON AND ACUTE
+1E54 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER P WITH ACUTE
+1E56 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER P WITH DOT ABOVE
+1E58 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER R WITH DOT ABOVE
+1E5A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER R WITH DOT BELOW
+1E5C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON
+1E5E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER R WITH LINE BELOW
+1E60 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER S WITH DOT ABOVE
+1E62 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER S WITH DOT BELOW
+1E64 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE
+1E66 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE
+1E68 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE
+1E6A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER T WITH DOT ABOVE
+1E6C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER T WITH DOT BELOW
+1E6E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER T WITH LINE BELOW
+1E70 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW
+1E72 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH DIAERESIS BELOW
+1E74 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH TILDE BELOW
+1E76 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW
+1E78 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH TILDE AND ACUTE
+1E7A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS
+1E7C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER V WITH TILDE
+1E7E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER V WITH DOT BELOW
+1E80 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER W WITH GRAVE
+1E82 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER W WITH ACUTE
+1E84 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER W WITH DIAERESIS
+1E86 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER W WITH DOT ABOVE
+1E88 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER W WITH DOT BELOW
+1E8A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER X WITH DOT ABOVE
+1E8C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER X WITH DIAERESIS
+1E8E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Y WITH DOT ABOVE
+1E90 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Z WITH CIRCUMFLEX
+1E92 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Z WITH DOT BELOW
+1E94 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Z WITH LINE BELOW
+1E9E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER SHARP S
+1EA0 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH DOT BELOW
+1EA2 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH HOOK ABOVE
+1EA4 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE
+1EA6 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE
+1EA8 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
+1EAA ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE
+1EAC ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW
+1EAE ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH BREVE AND ACUTE
+1EB0 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH BREVE AND GRAVE
+1EB2 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE
+1EB4 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH BREVE AND TILDE
+1EB6 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW
+1EB8 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH DOT BELOW
+1EBA ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH HOOK ABOVE
+1EBC ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH TILDE
+1EBE ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE
+1EC0 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE
+1EC2 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+1EC4 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE
+1EC6 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW
+1EC8 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER I WITH HOOK ABOVE
+1ECA ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER I WITH DOT BELOW
+1ECC ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH DOT BELOW
+1ECE ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH HOOK ABOVE
+1ED0 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE
+1ED2 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE
+1ED4 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
+1ED6 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE
+1ED8 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW
+1EDA ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH HORN AND ACUTE
+1EDC ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH HORN AND GRAVE
+1EDE ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE
+1EE0 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH HORN AND TILDE
+1EE2 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW
+1EE4 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH DOT BELOW
+1EE6 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH HOOK ABOVE
+1EE8 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH HORN AND ACUTE
+1EEA ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH HORN AND GRAVE
+1EEC ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE
+1EEE ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH HORN AND TILDE
+1EF0 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW
+1EF2 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Y WITH GRAVE
+1EF4 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Y WITH DOT BELOW
+1EF6 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Y WITH HOOK ABOVE
+1EF8 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Y WITH TILDE
+1EFA ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER MIDDLE-WELSH LL
+1EFC ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER MIDDLE-WELSH V
+1EFE ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Y WITH LOOP
+1F08..1F0F ; Changes_When_Lowercased # L& [8] GREEK CAPITAL LETTER ALPHA WITH PSILI..GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI
+1F18..1F1D ; Changes_When_Lowercased # L& [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F28..1F2F ; Changes_When_Lowercased # L& [8] GREEK CAPITAL LETTER ETA WITH PSILI..GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI
+1F38..1F3F ; Changes_When_Lowercased # L& [8] GREEK CAPITAL LETTER IOTA WITH PSILI..GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI
+1F48..1F4D ; Changes_When_Lowercased # L& [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F59 ; Changes_When_Lowercased # L& GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B ; Changes_When_Lowercased # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D ; Changes_When_Lowercased # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F ; Changes_When_Lowercased # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F68..1F6F ; Changes_When_Lowercased # L& [8] GREEK CAPITAL LETTER OMEGA WITH PSILI..GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI
+1F88..1F8F ; Changes_When_Lowercased # L& [8] GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI..GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1F98..1F9F ; Changes_When_Lowercased # L& [8] GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI..GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FA8..1FAF ; Changes_When_Lowercased # L& [8] GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI..GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FB8..1FBC ; Changes_When_Lowercased # L& [5] GREEK CAPITAL LETTER ALPHA WITH VRACHY..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FC8..1FCC ; Changes_When_Lowercased # L& [5] GREEK CAPITAL LETTER EPSILON WITH VARIA..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FD8..1FDB ; Changes_When_Lowercased # L& [4] GREEK CAPITAL LETTER IOTA WITH VRACHY..GREEK CAPITAL LETTER IOTA WITH OXIA
+1FE8..1FEC ; Changes_When_Lowercased # L& [5] GREEK CAPITAL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA
+1FF8..1FFC ; Changes_When_Lowercased # L& [5] GREEK CAPITAL LETTER OMICRON WITH VARIA..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+2126 ; Changes_When_Lowercased # L& OHM SIGN
+212A..212B ; Changes_When_Lowercased # L& [2] KELVIN SIGN..ANGSTROM SIGN
+2132 ; Changes_When_Lowercased # L& TURNED CAPITAL F
+2160..216F ; Changes_When_Lowercased # Nl [16] ROMAN NUMERAL ONE..ROMAN NUMERAL ONE THOUSAND
+2183 ; Changes_When_Lowercased # L& ROMAN NUMERAL REVERSED ONE HUNDRED
+24B6..24CF ; Changes_When_Lowercased # So [26] CIRCLED LATIN CAPITAL LETTER A..CIRCLED LATIN CAPITAL LETTER Z
+2C00..2C2F ; Changes_When_Lowercased # L& [48] GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC CAPITAL LETTER CAUDATE CHRIVI
+2C60 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER L WITH DOUBLE BAR
+2C62..2C64 ; Changes_When_Lowercased # L& [3] LATIN CAPITAL LETTER L WITH MIDDLE TILDE..LATIN CAPITAL LETTER R WITH TAIL
+2C67 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER H WITH DESCENDER
+2C69 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER K WITH DESCENDER
+2C6B ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Z WITH DESCENDER
+2C6D..2C70 ; Changes_When_Lowercased # L& [4] LATIN CAPITAL LETTER ALPHA..LATIN CAPITAL LETTER TURNED ALPHA
+2C72 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER W WITH HOOK
+2C75 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER HALF H
+2C7E..2C80 ; Changes_When_Lowercased # L& [3] LATIN CAPITAL LETTER S WITH SWASH TAIL..COPTIC CAPITAL LETTER ALFA
+2C82 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER VIDA
+2C84 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER GAMMA
+2C86 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER DALDA
+2C88 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER EIE
+2C8A ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER SOU
+2C8C ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER ZATA
+2C8E ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER HATE
+2C90 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER THETHE
+2C92 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER IAUDA
+2C94 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER KAPA
+2C96 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER LAULA
+2C98 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER MI
+2C9A ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER NI
+2C9C ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER KSI
+2C9E ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER O
+2CA0 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER PI
+2CA2 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER RO
+2CA4 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER SIMA
+2CA6 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER TAU
+2CA8 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER UA
+2CAA ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER FI
+2CAC ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER KHI
+2CAE ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER PSI
+2CB0 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OOU
+2CB2 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER DIALECT-P ALEF
+2CB4 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD COPTIC AIN
+2CB6 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE
+2CB8 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER DIALECT-P KAPA
+2CBA ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER DIALECT-P NI
+2CBC ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI
+2CBE ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD COPTIC OOU
+2CC0 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER SAMPI
+2CC2 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER CROSSED SHEI
+2CC4 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD COPTIC SHEI
+2CC6 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD COPTIC ESH
+2CC8 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER AKHMIMIC KHEI
+2CCA ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER DIALECT-P HORI
+2CCC ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD COPTIC HORI
+2CCE ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD COPTIC HA
+2CD0 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER L-SHAPED HA
+2CD2 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD COPTIC HEI
+2CD4 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD COPTIC HAT
+2CD6 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD COPTIC GANGIA
+2CD8 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD COPTIC DJA
+2CDA ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD COPTIC SHIMA
+2CDC ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD NUBIAN SHIMA
+2CDE ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD NUBIAN NGI
+2CE0 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD NUBIAN NYI
+2CE2 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER OLD NUBIAN WAU
+2CEB ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI
+2CED ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA
+2CF2 ; Changes_When_Lowercased # L& COPTIC CAPITAL LETTER BOHAIRIC KHEI
+A640 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER ZEMLYA
+A642 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER DZELO
+A644 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER REVERSED DZE
+A646 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER IOTA
+A648 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER DJERV
+A64A ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER MONOGRAPH UK
+A64C ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER BROAD OMEGA
+A64E ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER NEUTRAL YER
+A650 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER YERU WITH BACK YER
+A652 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER IOTIFIED YAT
+A654 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER REVERSED YU
+A656 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER IOTIFIED A
+A658 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS
+A65A ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER BLENDED YUS
+A65C ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS
+A65E ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER YN
+A660 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER REVERSED TSE
+A662 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER SOFT DE
+A664 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER SOFT EL
+A666 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER SOFT EM
+A668 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER MONOCULAR O
+A66A ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER BINOCULAR O
+A66C ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O
+A680 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER DWE
+A682 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER DZWE
+A684 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER ZHWE
+A686 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER CCHE
+A688 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER DZZE
+A68A ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK
+A68C ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER TWE
+A68E ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER TSWE
+A690 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER TSSE
+A692 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER TCHE
+A694 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER HWE
+A696 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER SHWE
+A698 ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER DOUBLE O
+A69A ; Changes_When_Lowercased # L& CYRILLIC CAPITAL LETTER CROSSED O
+A722 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF
+A724 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER EGYPTOLOGICAL AIN
+A726 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER HENG
+A728 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER TZ
+A72A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER TRESILLO
+A72C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER CUATRILLO
+A72E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER CUATRILLO WITH COMMA
+A732 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER AA
+A734 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER AO
+A736 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER AU
+A738 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER AV
+A73A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR
+A73C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER AY
+A73E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER REVERSED C WITH DOT
+A740 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER K WITH STROKE
+A742 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER K WITH DIAGONAL STROKE
+A744 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE
+A746 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER BROKEN L
+A748 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER L WITH HIGH STROKE
+A74A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY
+A74C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER O WITH LOOP
+A74E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER OO
+A750 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER
+A752 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER P WITH FLOURISH
+A754 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER P WITH SQUIRREL TAIL
+A756 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER
+A758 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE
+A75A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER R ROTUNDA
+A75C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER RUM ROTUNDA
+A75E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER V WITH DIAGONAL STROKE
+A760 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER VY
+A762 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER VISIGOTHIC Z
+A764 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER THORN WITH STROKE
+A766 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER
+A768 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER VEND
+A76A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER ET
+A76C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER IS
+A76E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER CON
+A779 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER INSULAR D
+A77B ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER INSULAR F
+A77D..A77E ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER INSULAR G..LATIN CAPITAL LETTER TURNED INSULAR G
+A780 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER TURNED L
+A782 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER INSULAR R
+A784 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER INSULAR S
+A786 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER INSULAR T
+A78B ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER SALTILLO
+A78D ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER TURNED H
+A790 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER N WITH DESCENDER
+A792 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER C WITH BAR
+A796 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER B WITH FLOURISH
+A798 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER F WITH STROKE
+A79A ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER VOLAPUK AE
+A79C ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER VOLAPUK OE
+A79E ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER VOLAPUK UE
+A7A0 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER G WITH OBLIQUE STROKE
+A7A2 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER K WITH OBLIQUE STROKE
+A7A4 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER N WITH OBLIQUE STROKE
+A7A6 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER R WITH OBLIQUE STROKE
+A7A8 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER S WITH OBLIQUE STROKE
+A7AA..A7AE ; Changes_When_Lowercased # L& [5] LATIN CAPITAL LETTER H WITH HOOK..LATIN CAPITAL LETTER SMALL CAPITAL I
+A7B0..A7B4 ; Changes_When_Lowercased # L& [5] LATIN CAPITAL LETTER TURNED K..LATIN CAPITAL LETTER BETA
+A7B6 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER OMEGA
+A7B8 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER U WITH STROKE
+A7BA ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER GLOTTAL A
+A7BC ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER GLOTTAL I
+A7BE ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER GLOTTAL U
+A7C0 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER OLD POLISH O
+A7C2 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER ANGLICANA W
+A7C4..A7C7 ; Changes_When_Lowercased # L& [4] LATIN CAPITAL LETTER C WITH PALATAL HOOK..LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY
+A7C9 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY
+A7CB..A7CC ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER RAMS HORN..LATIN CAPITAL LETTER S WITH DIAGONAL STROKE
+A7D0 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER CLOSED INSULAR G
+A7D6 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER MIDDLE SCOTS S
+A7D8 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER SIGMOID S
+A7DA ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER LAMBDA
+A7DC ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER LAMBDA WITH STROKE
+A7F5 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER REVERSED HALF H
+FF21..FF3A ; Changes_When_Lowercased # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z
+10400..10427 ; Changes_When_Lowercased # L& [40] DESERET CAPITAL LETTER LONG I..DESERET CAPITAL LETTER EW
+104B0..104D3 ; Changes_When_Lowercased # L& [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA
+10570..1057A ; Changes_When_Lowercased # L& [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA
+1057C..1058A ; Changes_When_Lowercased # L& [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE
+1058C..10592 ; Changes_When_Lowercased # L& [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE
+10594..10595 ; Changes_When_Lowercased # L& [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE
+10C80..10CB2 ; Changes_When_Lowercased # L& [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US
+10D50..10D65 ; Changes_When_Lowercased # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA
+118A0..118BF ; Changes_When_Lowercased # L& [32] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI CAPITAL LETTER VIYO
+16E40..16E5F ; Changes_When_Lowercased # L& [32] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN CAPITAL LETTER Y
+1E900..1E921 ; Changes_When_Lowercased # L& [34] ADLAM CAPITAL LETTER ALIF..ADLAM CAPITAL LETTER SHA
+
+# Total code points: 1460
+
+# ================================================
+
+# Derived Property: Changes_When_Uppercased (CWU)
+# Characters whose normalized forms are not stable under a toUppercase mapping.
+# For more information, see D140 in Section 3.13, "Default Case Algorithms".
+# Changes_When_Uppercased(X) is true when toUppercase(toNFD(X)) != toNFD(X)
+
+0061..007A ; Changes_When_Uppercased # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+00B5 ; Changes_When_Uppercased # L& MICRO SIGN
+00DF..00F6 ; Changes_When_Uppercased # L& [24] LATIN SMALL LETTER SHARP S..LATIN SMALL LETTER O WITH DIAERESIS
+00F8..00FF ; Changes_When_Uppercased # L& [8] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER Y WITH DIAERESIS
+0101 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH MACRON
+0103 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH BREVE
+0105 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH OGONEK
+0107 ; Changes_When_Uppercased # L& LATIN SMALL LETTER C WITH ACUTE
+0109 ; Changes_When_Uppercased # L& LATIN SMALL LETTER C WITH CIRCUMFLEX
+010B ; Changes_When_Uppercased # L& LATIN SMALL LETTER C WITH DOT ABOVE
+010D ; Changes_When_Uppercased # L& LATIN SMALL LETTER C WITH CARON
+010F ; Changes_When_Uppercased # L& LATIN SMALL LETTER D WITH CARON
+0111 ; Changes_When_Uppercased # L& LATIN SMALL LETTER D WITH STROKE
+0113 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH MACRON
+0115 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH BREVE
+0117 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH DOT ABOVE
+0119 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH OGONEK
+011B ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH CARON
+011D ; Changes_When_Uppercased # L& LATIN SMALL LETTER G WITH CIRCUMFLEX
+011F ; Changes_When_Uppercased # L& LATIN SMALL LETTER G WITH BREVE
+0121 ; Changes_When_Uppercased # L& LATIN SMALL LETTER G WITH DOT ABOVE
+0123 ; Changes_When_Uppercased # L& LATIN SMALL LETTER G WITH CEDILLA
+0125 ; Changes_When_Uppercased # L& LATIN SMALL LETTER H WITH CIRCUMFLEX
+0127 ; Changes_When_Uppercased # L& LATIN SMALL LETTER H WITH STROKE
+0129 ; Changes_When_Uppercased # L& LATIN SMALL LETTER I WITH TILDE
+012B ; Changes_When_Uppercased # L& LATIN SMALL LETTER I WITH MACRON
+012D ; Changes_When_Uppercased # L& LATIN SMALL LETTER I WITH BREVE
+012F ; Changes_When_Uppercased # L& LATIN SMALL LETTER I WITH OGONEK
+0131 ; Changes_When_Uppercased # L& LATIN SMALL LETTER DOTLESS I
+0133 ; Changes_When_Uppercased # L& LATIN SMALL LIGATURE IJ
+0135 ; Changes_When_Uppercased # L& LATIN SMALL LETTER J WITH CIRCUMFLEX
+0137 ; Changes_When_Uppercased # L& LATIN SMALL LETTER K WITH CEDILLA
+013A ; Changes_When_Uppercased # L& LATIN SMALL LETTER L WITH ACUTE
+013C ; Changes_When_Uppercased # L& LATIN SMALL LETTER L WITH CEDILLA
+013E ; Changes_When_Uppercased # L& LATIN SMALL LETTER L WITH CARON
+0140 ; Changes_When_Uppercased # L& LATIN SMALL LETTER L WITH MIDDLE DOT
+0142 ; Changes_When_Uppercased # L& LATIN SMALL LETTER L WITH STROKE
+0144 ; Changes_When_Uppercased # L& LATIN SMALL LETTER N WITH ACUTE
+0146 ; Changes_When_Uppercased # L& LATIN SMALL LETTER N WITH CEDILLA
+0148..0149 ; Changes_When_Uppercased # L& [2] LATIN SMALL LETTER N WITH CARON..LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+014B ; Changes_When_Uppercased # L& LATIN SMALL LETTER ENG
+014D ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH MACRON
+014F ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH BREVE
+0151 ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH DOUBLE ACUTE
+0153 ; Changes_When_Uppercased # L& LATIN SMALL LIGATURE OE
+0155 ; Changes_When_Uppercased # L& LATIN SMALL LETTER R WITH ACUTE
+0157 ; Changes_When_Uppercased # L& LATIN SMALL LETTER R WITH CEDILLA
+0159 ; Changes_When_Uppercased # L& LATIN SMALL LETTER R WITH CARON
+015B ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH ACUTE
+015D ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH CIRCUMFLEX
+015F ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH CEDILLA
+0161 ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH CARON
+0163 ; Changes_When_Uppercased # L& LATIN SMALL LETTER T WITH CEDILLA
+0165 ; Changes_When_Uppercased # L& LATIN SMALL LETTER T WITH CARON
+0167 ; Changes_When_Uppercased # L& LATIN SMALL LETTER T WITH STROKE
+0169 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH TILDE
+016B ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH MACRON
+016D ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH BREVE
+016F ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH RING ABOVE
+0171 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH DOUBLE ACUTE
+0173 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH OGONEK
+0175 ; Changes_When_Uppercased # L& LATIN SMALL LETTER W WITH CIRCUMFLEX
+0177 ; Changes_When_Uppercased # L& LATIN SMALL LETTER Y WITH CIRCUMFLEX
+017A ; Changes_When_Uppercased # L& LATIN SMALL LETTER Z WITH ACUTE
+017C ; Changes_When_Uppercased # L& LATIN SMALL LETTER Z WITH DOT ABOVE
+017E..0180 ; Changes_When_Uppercased # L& [3] LATIN SMALL LETTER Z WITH CARON..LATIN SMALL LETTER B WITH STROKE
+0183 ; Changes_When_Uppercased # L& LATIN SMALL LETTER B WITH TOPBAR
+0185 ; Changes_When_Uppercased # L& LATIN SMALL LETTER TONE SIX
+0188 ; Changes_When_Uppercased # L& LATIN SMALL LETTER C WITH HOOK
+018C ; Changes_When_Uppercased # L& LATIN SMALL LETTER D WITH TOPBAR
+0192 ; Changes_When_Uppercased # L& LATIN SMALL LETTER F WITH HOOK
+0195 ; Changes_When_Uppercased # L& LATIN SMALL LETTER HV
+0199..019B ; Changes_When_Uppercased # L& [3] LATIN SMALL LETTER K WITH HOOK..LATIN SMALL LETTER LAMBDA WITH STROKE
+019E ; Changes_When_Uppercased # L& LATIN SMALL LETTER N WITH LONG RIGHT LEG
+01A1 ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH HORN
+01A3 ; Changes_When_Uppercased # L& LATIN SMALL LETTER OI
+01A5 ; Changes_When_Uppercased # L& LATIN SMALL LETTER P WITH HOOK
+01A8 ; Changes_When_Uppercased # L& LATIN SMALL LETTER TONE TWO
+01AD ; Changes_When_Uppercased # L& LATIN SMALL LETTER T WITH HOOK
+01B0 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH HORN
+01B4 ; Changes_When_Uppercased # L& LATIN SMALL LETTER Y WITH HOOK
+01B6 ; Changes_When_Uppercased # L& LATIN SMALL LETTER Z WITH STROKE
+01B9 ; Changes_When_Uppercased # L& LATIN SMALL LETTER EZH REVERSED
+01BD ; Changes_When_Uppercased # L& LATIN SMALL LETTER TONE FIVE
+01BF ; Changes_When_Uppercased # L& LATIN LETTER WYNN
+01C5..01C6 ; Changes_When_Uppercased # L& [2] LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON..LATIN SMALL LETTER DZ WITH CARON
+01C8..01C9 ; Changes_When_Uppercased # L& [2] LATIN CAPITAL LETTER L WITH SMALL LETTER J..LATIN SMALL LETTER LJ
+01CB..01CC ; Changes_When_Uppercased # L& [2] LATIN CAPITAL LETTER N WITH SMALL LETTER J..LATIN SMALL LETTER NJ
+01CE ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH CARON
+01D0 ; Changes_When_Uppercased # L& LATIN SMALL LETTER I WITH CARON
+01D2 ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH CARON
+01D4 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH CARON
+01D6 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH DIAERESIS AND MACRON
+01D8 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE
+01DA ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH DIAERESIS AND CARON
+01DC..01DD ; Changes_When_Uppercased # L& [2] LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE..LATIN SMALL LETTER TURNED E
+01DF ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH DIAERESIS AND MACRON
+01E1 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON
+01E3 ; Changes_When_Uppercased # L& LATIN SMALL LETTER AE WITH MACRON
+01E5 ; Changes_When_Uppercased # L& LATIN SMALL LETTER G WITH STROKE
+01E7 ; Changes_When_Uppercased # L& LATIN SMALL LETTER G WITH CARON
+01E9 ; Changes_When_Uppercased # L& LATIN SMALL LETTER K WITH CARON
+01EB ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH OGONEK
+01ED ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH OGONEK AND MACRON
+01EF..01F0 ; Changes_When_Uppercased # L& [2] LATIN SMALL LETTER EZH WITH CARON..LATIN SMALL LETTER J WITH CARON
+01F2..01F3 ; Changes_When_Uppercased # L& [2] LATIN CAPITAL LETTER D WITH SMALL LETTER Z..LATIN SMALL LETTER DZ
+01F5 ; Changes_When_Uppercased # L& LATIN SMALL LETTER G WITH ACUTE
+01F9 ; Changes_When_Uppercased # L& LATIN SMALL LETTER N WITH GRAVE
+01FB ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE
+01FD ; Changes_When_Uppercased # L& LATIN SMALL LETTER AE WITH ACUTE
+01FF ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH STROKE AND ACUTE
+0201 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH DOUBLE GRAVE
+0203 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH INVERTED BREVE
+0205 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH DOUBLE GRAVE
+0207 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH INVERTED BREVE
+0209 ; Changes_When_Uppercased # L& LATIN SMALL LETTER I WITH DOUBLE GRAVE
+020B ; Changes_When_Uppercased # L& LATIN SMALL LETTER I WITH INVERTED BREVE
+020D ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH DOUBLE GRAVE
+020F ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH INVERTED BREVE
+0211 ; Changes_When_Uppercased # L& LATIN SMALL LETTER R WITH DOUBLE GRAVE
+0213 ; Changes_When_Uppercased # L& LATIN SMALL LETTER R WITH INVERTED BREVE
+0215 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH DOUBLE GRAVE
+0217 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH INVERTED BREVE
+0219 ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH COMMA BELOW
+021B ; Changes_When_Uppercased # L& LATIN SMALL LETTER T WITH COMMA BELOW
+021D ; Changes_When_Uppercased # L& LATIN SMALL LETTER YOGH
+021F ; Changes_When_Uppercased # L& LATIN SMALL LETTER H WITH CARON
+0223 ; Changes_When_Uppercased # L& LATIN SMALL LETTER OU
+0225 ; Changes_When_Uppercased # L& LATIN SMALL LETTER Z WITH HOOK
+0227 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH DOT ABOVE
+0229 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH CEDILLA
+022B ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH DIAERESIS AND MACRON
+022D ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH TILDE AND MACRON
+022F ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH DOT ABOVE
+0231 ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON
+0233 ; Changes_When_Uppercased # L& LATIN SMALL LETTER Y WITH MACRON
+023C ; Changes_When_Uppercased # L& LATIN SMALL LETTER C WITH STROKE
+023F..0240 ; Changes_When_Uppercased # L& [2] LATIN SMALL LETTER S WITH SWASH TAIL..LATIN SMALL LETTER Z WITH SWASH TAIL
+0242 ; Changes_When_Uppercased # L& LATIN SMALL LETTER GLOTTAL STOP
+0247 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH STROKE
+0249 ; Changes_When_Uppercased # L& LATIN SMALL LETTER J WITH STROKE
+024B ; Changes_When_Uppercased # L& LATIN SMALL LETTER Q WITH HOOK TAIL
+024D ; Changes_When_Uppercased # L& LATIN SMALL LETTER R WITH STROKE
+024F..0254 ; Changes_When_Uppercased # L& [6] LATIN SMALL LETTER Y WITH STROKE..LATIN SMALL LETTER OPEN O
+0256..0257 ; Changes_When_Uppercased # L& [2] LATIN SMALL LETTER D WITH TAIL..LATIN SMALL LETTER D WITH HOOK
+0259 ; Changes_When_Uppercased # L& LATIN SMALL LETTER SCHWA
+025B..025C ; Changes_When_Uppercased # L& [2] LATIN SMALL LETTER OPEN E..LATIN SMALL LETTER REVERSED OPEN E
+0260..0261 ; Changes_When_Uppercased # L& [2] LATIN SMALL LETTER G WITH HOOK..LATIN SMALL LETTER SCRIPT G
+0263..0266 ; Changes_When_Uppercased # L& [4] LATIN SMALL LETTER GAMMA..LATIN SMALL LETTER H WITH HOOK
+0268..026C ; Changes_When_Uppercased # L& [5] LATIN SMALL LETTER I WITH STROKE..LATIN SMALL LETTER L WITH BELT
+026F ; Changes_When_Uppercased # L& LATIN SMALL LETTER TURNED M
+0271..0272 ; Changes_When_Uppercased # L& [2] LATIN SMALL LETTER M WITH HOOK..LATIN SMALL LETTER N WITH LEFT HOOK
+0275 ; Changes_When_Uppercased # L& LATIN SMALL LETTER BARRED O
+027D ; Changes_When_Uppercased # L& LATIN SMALL LETTER R WITH TAIL
+0280 ; Changes_When_Uppercased # L& LATIN LETTER SMALL CAPITAL R
+0282..0283 ; Changes_When_Uppercased # L& [2] LATIN SMALL LETTER S WITH HOOK..LATIN SMALL LETTER ESH
+0287..028C ; Changes_When_Uppercased # L& [6] LATIN SMALL LETTER TURNED T..LATIN SMALL LETTER TURNED V
+0292 ; Changes_When_Uppercased # L& LATIN SMALL LETTER EZH
+029D..029E ; Changes_When_Uppercased # L& [2] LATIN SMALL LETTER J WITH CROSSED-TAIL..LATIN SMALL LETTER TURNED K
+0345 ; Changes_When_Uppercased # Mn COMBINING GREEK YPOGEGRAMMENI
+0371 ; Changes_When_Uppercased # L& GREEK SMALL LETTER HETA
+0373 ; Changes_When_Uppercased # L& GREEK SMALL LETTER ARCHAIC SAMPI
+0377 ; Changes_When_Uppercased # L& GREEK SMALL LETTER PAMPHYLIAN DIGAMMA
+037B..037D ; Changes_When_Uppercased # L& [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL
+0390 ; Changes_When_Uppercased # L& GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+03AC..03CE ; Changes_When_Uppercased # L& [35] GREEK SMALL LETTER ALPHA WITH TONOS..GREEK SMALL LETTER OMEGA WITH TONOS
+03D0..03D1 ; Changes_When_Uppercased # L& [2] GREEK BETA SYMBOL..GREEK THETA SYMBOL
+03D5..03D7 ; Changes_When_Uppercased # L& [3] GREEK PHI SYMBOL..GREEK KAI SYMBOL
+03D9 ; Changes_When_Uppercased # L& GREEK SMALL LETTER ARCHAIC KOPPA
+03DB ; Changes_When_Uppercased # L& GREEK SMALL LETTER STIGMA
+03DD ; Changes_When_Uppercased # L& GREEK SMALL LETTER DIGAMMA
+03DF ; Changes_When_Uppercased # L& GREEK SMALL LETTER KOPPA
+03E1 ; Changes_When_Uppercased # L& GREEK SMALL LETTER SAMPI
+03E3 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER SHEI
+03E5 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER FEI
+03E7 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER KHEI
+03E9 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER HORI
+03EB ; Changes_When_Uppercased # L& COPTIC SMALL LETTER GANGIA
+03ED ; Changes_When_Uppercased # L& COPTIC SMALL LETTER SHIMA
+03EF..03F3 ; Changes_When_Uppercased # L& [5] COPTIC SMALL LETTER DEI..GREEK LETTER YOT
+03F5 ; Changes_When_Uppercased # L& GREEK LUNATE EPSILON SYMBOL
+03F8 ; Changes_When_Uppercased # L& GREEK SMALL LETTER SHO
+03FB ; Changes_When_Uppercased # L& GREEK SMALL LETTER SAN
+0430..045F ; Changes_When_Uppercased # L& [48] CYRILLIC SMALL LETTER A..CYRILLIC SMALL LETTER DZHE
+0461 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER OMEGA
+0463 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER YAT
+0465 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER IOTIFIED E
+0467 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER LITTLE YUS
+0469 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER IOTIFIED LITTLE YUS
+046B ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER BIG YUS
+046D ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER IOTIFIED BIG YUS
+046F ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KSI
+0471 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER PSI
+0473 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER FITA
+0475 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER IZHITSA
+0477 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT
+0479 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER UK
+047B ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ROUND OMEGA
+047D ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER OMEGA WITH TITLO
+047F ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER OT
+0481 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KOPPA
+048B ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER SHORT I WITH TAIL
+048D ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER SEMISOFT SIGN
+048F ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ER WITH TICK
+0491 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER GHE WITH UPTURN
+0493 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER GHE WITH STROKE
+0495 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER GHE WITH MIDDLE HOOK
+0497 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ZHE WITH DESCENDER
+0499 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ZE WITH DESCENDER
+049B ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KA WITH DESCENDER
+049D ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE
+049F ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KA WITH STROKE
+04A1 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER BASHKIR KA
+04A3 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER EN WITH DESCENDER
+04A5 ; Changes_When_Uppercased # L& CYRILLIC SMALL LIGATURE EN GHE
+04A7 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER PE WITH MIDDLE HOOK
+04A9 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ABKHASIAN HA
+04AB ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ES WITH DESCENDER
+04AD ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER TE WITH DESCENDER
+04AF ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER STRAIGHT U
+04B1 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE
+04B3 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER HA WITH DESCENDER
+04B5 ; Changes_When_Uppercased # L& CYRILLIC SMALL LIGATURE TE TSE
+04B7 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER CHE WITH DESCENDER
+04B9 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE
+04BB ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER SHHA
+04BD ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ABKHASIAN CHE
+04BF ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ABKHASIAN CHE WITH DESCENDER
+04C2 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ZHE WITH BREVE
+04C4 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KA WITH HOOK
+04C6 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER EL WITH TAIL
+04C8 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER EN WITH HOOK
+04CA ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER EN WITH TAIL
+04CC ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KHAKASSIAN CHE
+04CE..04CF ; Changes_When_Uppercased # L& [2] CYRILLIC SMALL LETTER EM WITH TAIL..CYRILLIC SMALL LETTER PALOCHKA
+04D1 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER A WITH BREVE
+04D3 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER A WITH DIAERESIS
+04D5 ; Changes_When_Uppercased # L& CYRILLIC SMALL LIGATURE A IE
+04D7 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER IE WITH BREVE
+04D9 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER SCHWA
+04DB ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS
+04DD ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ZHE WITH DIAERESIS
+04DF ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ZE WITH DIAERESIS
+04E1 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ABKHASIAN DZE
+04E3 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER I WITH MACRON
+04E5 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER I WITH DIAERESIS
+04E7 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER O WITH DIAERESIS
+04E9 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER BARRED O
+04EB ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS
+04ED ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER E WITH DIAERESIS
+04EF ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER U WITH MACRON
+04F1 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER U WITH DIAERESIS
+04F3 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE
+04F5 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER CHE WITH DIAERESIS
+04F7 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER GHE WITH DESCENDER
+04F9 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER YERU WITH DIAERESIS
+04FB ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER GHE WITH STROKE AND HOOK
+04FD ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER HA WITH HOOK
+04FF ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER HA WITH STROKE
+0501 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KOMI DE
+0503 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KOMI DJE
+0505 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KOMI ZJE
+0507 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KOMI DZJE
+0509 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KOMI LJE
+050B ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KOMI NJE
+050D ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KOMI SJE
+050F ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER KOMI TJE
+0511 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER REVERSED ZE
+0513 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER EL WITH HOOK
+0515 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER LHA
+0517 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER RHA
+0519 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER YAE
+051B ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER QA
+051D ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER WE
+051F ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ALEUT KA
+0521 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER EL WITH MIDDLE HOOK
+0523 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK
+0525 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER PE WITH DESCENDER
+0527 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER SHHA WITH DESCENDER
+0529 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER EN WITH LEFT HOOK
+052B ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER DZZHE
+052D ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER DCHE
+052F ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER EL WITH DESCENDER
+0561..0587 ; Changes_When_Uppercased # L& [39] ARMENIAN SMALL LETTER AYB..ARMENIAN SMALL LIGATURE ECH YIWN
+10D0..10FA ; Changes_When_Uppercased # L& [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN
+10FD..10FF ; Changes_When_Uppercased # L& [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN
+13F8..13FD ; Changes_When_Uppercased # L& [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV
+1C80..1C88 ; Changes_When_Uppercased # L& [9] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER UNBLENDED UK
+1C8A ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER TJE
+1D79 ; Changes_When_Uppercased # L& LATIN SMALL LETTER INSULAR G
+1D7D ; Changes_When_Uppercased # L& LATIN SMALL LETTER P WITH STROKE
+1D8E ; Changes_When_Uppercased # L& LATIN SMALL LETTER Z WITH PALATAL HOOK
+1E01 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH RING BELOW
+1E03 ; Changes_When_Uppercased # L& LATIN SMALL LETTER B WITH DOT ABOVE
+1E05 ; Changes_When_Uppercased # L& LATIN SMALL LETTER B WITH DOT BELOW
+1E07 ; Changes_When_Uppercased # L& LATIN SMALL LETTER B WITH LINE BELOW
+1E09 ; Changes_When_Uppercased # L& LATIN SMALL LETTER C WITH CEDILLA AND ACUTE
+1E0B ; Changes_When_Uppercased # L& LATIN SMALL LETTER D WITH DOT ABOVE
+1E0D ; Changes_When_Uppercased # L& LATIN SMALL LETTER D WITH DOT BELOW
+1E0F ; Changes_When_Uppercased # L& LATIN SMALL LETTER D WITH LINE BELOW
+1E11 ; Changes_When_Uppercased # L& LATIN SMALL LETTER D WITH CEDILLA
+1E13 ; Changes_When_Uppercased # L& LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW
+1E15 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH MACRON AND GRAVE
+1E17 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH MACRON AND ACUTE
+1E19 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW
+1E1B ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH TILDE BELOW
+1E1D ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH CEDILLA AND BREVE
+1E1F ; Changes_When_Uppercased # L& LATIN SMALL LETTER F WITH DOT ABOVE
+1E21 ; Changes_When_Uppercased # L& LATIN SMALL LETTER G WITH MACRON
+1E23 ; Changes_When_Uppercased # L& LATIN SMALL LETTER H WITH DOT ABOVE
+1E25 ; Changes_When_Uppercased # L& LATIN SMALL LETTER H WITH DOT BELOW
+1E27 ; Changes_When_Uppercased # L& LATIN SMALL LETTER H WITH DIAERESIS
+1E29 ; Changes_When_Uppercased # L& LATIN SMALL LETTER H WITH CEDILLA
+1E2B ; Changes_When_Uppercased # L& LATIN SMALL LETTER H WITH BREVE BELOW
+1E2D ; Changes_When_Uppercased # L& LATIN SMALL LETTER I WITH TILDE BELOW
+1E2F ; Changes_When_Uppercased # L& LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE
+1E31 ; Changes_When_Uppercased # L& LATIN SMALL LETTER K WITH ACUTE
+1E33 ; Changes_When_Uppercased # L& LATIN SMALL LETTER K WITH DOT BELOW
+1E35 ; Changes_When_Uppercased # L& LATIN SMALL LETTER K WITH LINE BELOW
+1E37 ; Changes_When_Uppercased # L& LATIN SMALL LETTER L WITH DOT BELOW
+1E39 ; Changes_When_Uppercased # L& LATIN SMALL LETTER L WITH DOT BELOW AND MACRON
+1E3B ; Changes_When_Uppercased # L& LATIN SMALL LETTER L WITH LINE BELOW
+1E3D ; Changes_When_Uppercased # L& LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW
+1E3F ; Changes_When_Uppercased # L& LATIN SMALL LETTER M WITH ACUTE
+1E41 ; Changes_When_Uppercased # L& LATIN SMALL LETTER M WITH DOT ABOVE
+1E43 ; Changes_When_Uppercased # L& LATIN SMALL LETTER M WITH DOT BELOW
+1E45 ; Changes_When_Uppercased # L& LATIN SMALL LETTER N WITH DOT ABOVE
+1E47 ; Changes_When_Uppercased # L& LATIN SMALL LETTER N WITH DOT BELOW
+1E49 ; Changes_When_Uppercased # L& LATIN SMALL LETTER N WITH LINE BELOW
+1E4B ; Changes_When_Uppercased # L& LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW
+1E4D ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH TILDE AND ACUTE
+1E4F ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH TILDE AND DIAERESIS
+1E51 ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH MACRON AND GRAVE
+1E53 ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH MACRON AND ACUTE
+1E55 ; Changes_When_Uppercased # L& LATIN SMALL LETTER P WITH ACUTE
+1E57 ; Changes_When_Uppercased # L& LATIN SMALL LETTER P WITH DOT ABOVE
+1E59 ; Changes_When_Uppercased # L& LATIN SMALL LETTER R WITH DOT ABOVE
+1E5B ; Changes_When_Uppercased # L& LATIN SMALL LETTER R WITH DOT BELOW
+1E5D ; Changes_When_Uppercased # L& LATIN SMALL LETTER R WITH DOT BELOW AND MACRON
+1E5F ; Changes_When_Uppercased # L& LATIN SMALL LETTER R WITH LINE BELOW
+1E61 ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH DOT ABOVE
+1E63 ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH DOT BELOW
+1E65 ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE
+1E67 ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH CARON AND DOT ABOVE
+1E69 ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE
+1E6B ; Changes_When_Uppercased # L& LATIN SMALL LETTER T WITH DOT ABOVE
+1E6D ; Changes_When_Uppercased # L& LATIN SMALL LETTER T WITH DOT BELOW
+1E6F ; Changes_When_Uppercased # L& LATIN SMALL LETTER T WITH LINE BELOW
+1E71 ; Changes_When_Uppercased # L& LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW
+1E73 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH DIAERESIS BELOW
+1E75 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH TILDE BELOW
+1E77 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW
+1E79 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH TILDE AND ACUTE
+1E7B ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH MACRON AND DIAERESIS
+1E7D ; Changes_When_Uppercased # L& LATIN SMALL LETTER V WITH TILDE
+1E7F ; Changes_When_Uppercased # L& LATIN SMALL LETTER V WITH DOT BELOW
+1E81 ; Changes_When_Uppercased # L& LATIN SMALL LETTER W WITH GRAVE
+1E83 ; Changes_When_Uppercased # L& LATIN SMALL LETTER W WITH ACUTE
+1E85 ; Changes_When_Uppercased # L& LATIN SMALL LETTER W WITH DIAERESIS
+1E87 ; Changes_When_Uppercased # L& LATIN SMALL LETTER W WITH DOT ABOVE
+1E89 ; Changes_When_Uppercased # L& LATIN SMALL LETTER W WITH DOT BELOW
+1E8B ; Changes_When_Uppercased # L& LATIN SMALL LETTER X WITH DOT ABOVE
+1E8D ; Changes_When_Uppercased # L& LATIN SMALL LETTER X WITH DIAERESIS
+1E8F ; Changes_When_Uppercased # L& LATIN SMALL LETTER Y WITH DOT ABOVE
+1E91 ; Changes_When_Uppercased # L& LATIN SMALL LETTER Z WITH CIRCUMFLEX
+1E93 ; Changes_When_Uppercased # L& LATIN SMALL LETTER Z WITH DOT BELOW
+1E95..1E9B ; Changes_When_Uppercased # L& [7] LATIN SMALL LETTER Z WITH LINE BELOW..LATIN SMALL LETTER LONG S WITH DOT ABOVE
+1EA1 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH DOT BELOW
+1EA3 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH HOOK ABOVE
+1EA5 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE
+1EA7 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE
+1EA9 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
+1EAB ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE
+1EAD ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW
+1EAF ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH BREVE AND ACUTE
+1EB1 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH BREVE AND GRAVE
+1EB3 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE
+1EB5 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH BREVE AND TILDE
+1EB7 ; Changes_When_Uppercased # L& LATIN SMALL LETTER A WITH BREVE AND DOT BELOW
+1EB9 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH DOT BELOW
+1EBB ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH HOOK ABOVE
+1EBD ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH TILDE
+1EBF ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE
+1EC1 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE
+1EC3 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+1EC5 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE
+1EC7 ; Changes_When_Uppercased # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW
+1EC9 ; Changes_When_Uppercased # L& LATIN SMALL LETTER I WITH HOOK ABOVE
+1ECB ; Changes_When_Uppercased # L& LATIN SMALL LETTER I WITH DOT BELOW
+1ECD ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH DOT BELOW
+1ECF ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH HOOK ABOVE
+1ED1 ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE
+1ED3 ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE
+1ED5 ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
+1ED7 ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE
+1ED9 ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW
+1EDB ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH HORN AND ACUTE
+1EDD ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH HORN AND GRAVE
+1EDF ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE
+1EE1 ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH HORN AND TILDE
+1EE3 ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH HORN AND DOT BELOW
+1EE5 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH DOT BELOW
+1EE7 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH HOOK ABOVE
+1EE9 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH HORN AND ACUTE
+1EEB ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH HORN AND GRAVE
+1EED ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE
+1EEF ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH HORN AND TILDE
+1EF1 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH HORN AND DOT BELOW
+1EF3 ; Changes_When_Uppercased # L& LATIN SMALL LETTER Y WITH GRAVE
+1EF5 ; Changes_When_Uppercased # L& LATIN SMALL LETTER Y WITH DOT BELOW
+1EF7 ; Changes_When_Uppercased # L& LATIN SMALL LETTER Y WITH HOOK ABOVE
+1EF9 ; Changes_When_Uppercased # L& LATIN SMALL LETTER Y WITH TILDE
+1EFB ; Changes_When_Uppercased # L& LATIN SMALL LETTER MIDDLE-WELSH LL
+1EFD ; Changes_When_Uppercased # L& LATIN SMALL LETTER MIDDLE-WELSH V
+1EFF..1F07 ; Changes_When_Uppercased # L& [9] LATIN SMALL LETTER Y WITH LOOP..GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI
+1F10..1F15 ; Changes_When_Uppercased # L& [6] GREEK SMALL LETTER EPSILON WITH PSILI..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA
+1F20..1F27 ; Changes_When_Uppercased # L& [8] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI
+1F30..1F37 ; Changes_When_Uppercased # L& [8] GREEK SMALL LETTER IOTA WITH PSILI..GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI
+1F40..1F45 ; Changes_When_Uppercased # L& [6] GREEK SMALL LETTER OMICRON WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA
+1F50..1F57 ; Changes_When_Uppercased # L& [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F60..1F67 ; Changes_When_Uppercased # L& [8] GREEK SMALL LETTER OMEGA WITH PSILI..GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI
+1F70..1F7D ; Changes_When_Uppercased # L& [14] GREEK SMALL LETTER ALPHA WITH VARIA..GREEK SMALL LETTER OMEGA WITH OXIA
+1F80..1FB4 ; Changes_When_Uppercased # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6..1FB7 ; Changes_When_Uppercased # L& [2] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FBC ; Changes_When_Uppercased # L& GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBE ; Changes_When_Uppercased # L& GREEK PROSGEGRAMMENI
+1FC2..1FC4 ; Changes_When_Uppercased # L& [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6..1FC7 ; Changes_When_Uppercased # L& [2] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FCC ; Changes_When_Uppercased # L& GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FD0..1FD3 ; Changes_When_Uppercased # L& [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6..1FD7 ; Changes_When_Uppercased # L& [2] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI
+1FE0..1FE7 ; Changes_When_Uppercased # L& [8] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI
+1FF2..1FF4 ; Changes_When_Uppercased # L& [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6..1FF7 ; Changes_When_Uppercased # L& [2] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FFC ; Changes_When_Uppercased # L& GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+214E ; Changes_When_Uppercased # L& TURNED SMALL F
+2170..217F ; Changes_When_Uppercased # Nl [16] SMALL ROMAN NUMERAL ONE..SMALL ROMAN NUMERAL ONE THOUSAND
+2184 ; Changes_When_Uppercased # L& LATIN SMALL LETTER REVERSED C
+24D0..24E9 ; Changes_When_Uppercased # So [26] CIRCLED LATIN SMALL LETTER A..CIRCLED LATIN SMALL LETTER Z
+2C30..2C5F ; Changes_When_Uppercased # L& [48] GLAGOLITIC SMALL LETTER AZU..GLAGOLITIC SMALL LETTER CAUDATE CHRIVI
+2C61 ; Changes_When_Uppercased # L& LATIN SMALL LETTER L WITH DOUBLE BAR
+2C65..2C66 ; Changes_When_Uppercased # L& [2] LATIN SMALL LETTER A WITH STROKE..LATIN SMALL LETTER T WITH DIAGONAL STROKE
+2C68 ; Changes_When_Uppercased # L& LATIN SMALL LETTER H WITH DESCENDER
+2C6A ; Changes_When_Uppercased # L& LATIN SMALL LETTER K WITH DESCENDER
+2C6C ; Changes_When_Uppercased # L& LATIN SMALL LETTER Z WITH DESCENDER
+2C73 ; Changes_When_Uppercased # L& LATIN SMALL LETTER W WITH HOOK
+2C76 ; Changes_When_Uppercased # L& LATIN SMALL LETTER HALF H
+2C81 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER ALFA
+2C83 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER VIDA
+2C85 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER GAMMA
+2C87 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER DALDA
+2C89 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER EIE
+2C8B ; Changes_When_Uppercased # L& COPTIC SMALL LETTER SOU
+2C8D ; Changes_When_Uppercased # L& COPTIC SMALL LETTER ZATA
+2C8F ; Changes_When_Uppercased # L& COPTIC SMALL LETTER HATE
+2C91 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER THETHE
+2C93 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER IAUDA
+2C95 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER KAPA
+2C97 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER LAULA
+2C99 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER MI
+2C9B ; Changes_When_Uppercased # L& COPTIC SMALL LETTER NI
+2C9D ; Changes_When_Uppercased # L& COPTIC SMALL LETTER KSI
+2C9F ; Changes_When_Uppercased # L& COPTIC SMALL LETTER O
+2CA1 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER PI
+2CA3 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER RO
+2CA5 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER SIMA
+2CA7 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER TAU
+2CA9 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER UA
+2CAB ; Changes_When_Uppercased # L& COPTIC SMALL LETTER FI
+2CAD ; Changes_When_Uppercased # L& COPTIC SMALL LETTER KHI
+2CAF ; Changes_When_Uppercased # L& COPTIC SMALL LETTER PSI
+2CB1 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OOU
+2CB3 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER DIALECT-P ALEF
+2CB5 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD COPTIC AIN
+2CB7 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER CRYPTOGRAMMIC EIE
+2CB9 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER DIALECT-P KAPA
+2CBB ; Changes_When_Uppercased # L& COPTIC SMALL LETTER DIALECT-P NI
+2CBD ; Changes_When_Uppercased # L& COPTIC SMALL LETTER CRYPTOGRAMMIC NI
+2CBF ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD COPTIC OOU
+2CC1 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER SAMPI
+2CC3 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER CROSSED SHEI
+2CC5 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD COPTIC SHEI
+2CC7 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD COPTIC ESH
+2CC9 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER AKHMIMIC KHEI
+2CCB ; Changes_When_Uppercased # L& COPTIC SMALL LETTER DIALECT-P HORI
+2CCD ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD COPTIC HORI
+2CCF ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD COPTIC HA
+2CD1 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER L-SHAPED HA
+2CD3 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD COPTIC HEI
+2CD5 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD COPTIC HAT
+2CD7 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD COPTIC GANGIA
+2CD9 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD COPTIC DJA
+2CDB ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD COPTIC SHIMA
+2CDD ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD NUBIAN SHIMA
+2CDF ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD NUBIAN NGI
+2CE1 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD NUBIAN NYI
+2CE3 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER OLD NUBIAN WAU
+2CEC ; Changes_When_Uppercased # L& COPTIC SMALL LETTER CRYPTOGRAMMIC SHEI
+2CEE ; Changes_When_Uppercased # L& COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA
+2CF3 ; Changes_When_Uppercased # L& COPTIC SMALL LETTER BOHAIRIC KHEI
+2D00..2D25 ; Changes_When_Uppercased # L& [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE
+2D27 ; Changes_When_Uppercased # L& GEORGIAN SMALL LETTER YN
+2D2D ; Changes_When_Uppercased # L& GEORGIAN SMALL LETTER AEN
+A641 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ZEMLYA
+A643 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER DZELO
+A645 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER REVERSED DZE
+A647 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER IOTA
+A649 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER DJERV
+A64B ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER MONOGRAPH UK
+A64D ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER BROAD OMEGA
+A64F ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER NEUTRAL YER
+A651 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER YERU WITH BACK YER
+A653 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER IOTIFIED YAT
+A655 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER REVERSED YU
+A657 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER IOTIFIED A
+A659 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER CLOSED LITTLE YUS
+A65B ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER BLENDED YUS
+A65D ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER IOTIFIED CLOSED LITTLE YUS
+A65F ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER YN
+A661 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER REVERSED TSE
+A663 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER SOFT DE
+A665 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER SOFT EL
+A667 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER SOFT EM
+A669 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER MONOCULAR O
+A66B ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER BINOCULAR O
+A66D ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER DOUBLE MONOCULAR O
+A681 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER DWE
+A683 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER DZWE
+A685 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER ZHWE
+A687 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER CCHE
+A689 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER DZZE
+A68B ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER TE WITH MIDDLE HOOK
+A68D ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER TWE
+A68F ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER TSWE
+A691 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER TSSE
+A693 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER TCHE
+A695 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER HWE
+A697 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER SHWE
+A699 ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER DOUBLE O
+A69B ; Changes_When_Uppercased # L& CYRILLIC SMALL LETTER CROSSED O
+A723 ; Changes_When_Uppercased # L& LATIN SMALL LETTER EGYPTOLOGICAL ALEF
+A725 ; Changes_When_Uppercased # L& LATIN SMALL LETTER EGYPTOLOGICAL AIN
+A727 ; Changes_When_Uppercased # L& LATIN SMALL LETTER HENG
+A729 ; Changes_When_Uppercased # L& LATIN SMALL LETTER TZ
+A72B ; Changes_When_Uppercased # L& LATIN SMALL LETTER TRESILLO
+A72D ; Changes_When_Uppercased # L& LATIN SMALL LETTER CUATRILLO
+A72F ; Changes_When_Uppercased # L& LATIN SMALL LETTER CUATRILLO WITH COMMA
+A733 ; Changes_When_Uppercased # L& LATIN SMALL LETTER AA
+A735 ; Changes_When_Uppercased # L& LATIN SMALL LETTER AO
+A737 ; Changes_When_Uppercased # L& LATIN SMALL LETTER AU
+A739 ; Changes_When_Uppercased # L& LATIN SMALL LETTER AV
+A73B ; Changes_When_Uppercased # L& LATIN SMALL LETTER AV WITH HORIZONTAL BAR
+A73D ; Changes_When_Uppercased # L& LATIN SMALL LETTER AY
+A73F ; Changes_When_Uppercased # L& LATIN SMALL LETTER REVERSED C WITH DOT
+A741 ; Changes_When_Uppercased # L& LATIN SMALL LETTER K WITH STROKE
+A743 ; Changes_When_Uppercased # L& LATIN SMALL LETTER K WITH DIAGONAL STROKE
+A745 ; Changes_When_Uppercased # L& LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE
+A747 ; Changes_When_Uppercased # L& LATIN SMALL LETTER BROKEN L
+A749 ; Changes_When_Uppercased # L& LATIN SMALL LETTER L WITH HIGH STROKE
+A74B ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH LONG STROKE OVERLAY
+A74D ; Changes_When_Uppercased # L& LATIN SMALL LETTER O WITH LOOP
+A74F ; Changes_When_Uppercased # L& LATIN SMALL LETTER OO
+A751 ; Changes_When_Uppercased # L& LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER
+A753 ; Changes_When_Uppercased # L& LATIN SMALL LETTER P WITH FLOURISH
+A755 ; Changes_When_Uppercased # L& LATIN SMALL LETTER P WITH SQUIRREL TAIL
+A757 ; Changes_When_Uppercased # L& LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER
+A759 ; Changes_When_Uppercased # L& LATIN SMALL LETTER Q WITH DIAGONAL STROKE
+A75B ; Changes_When_Uppercased # L& LATIN SMALL LETTER R ROTUNDA
+A75D ; Changes_When_Uppercased # L& LATIN SMALL LETTER RUM ROTUNDA
+A75F ; Changes_When_Uppercased # L& LATIN SMALL LETTER V WITH DIAGONAL STROKE
+A761 ; Changes_When_Uppercased # L& LATIN SMALL LETTER VY
+A763 ; Changes_When_Uppercased # L& LATIN SMALL LETTER VISIGOTHIC Z
+A765 ; Changes_When_Uppercased # L& LATIN SMALL LETTER THORN WITH STROKE
+A767 ; Changes_When_Uppercased # L& LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER
+A769 ; Changes_When_Uppercased # L& LATIN SMALL LETTER VEND
+A76B ; Changes_When_Uppercased # L& LATIN SMALL LETTER ET
+A76D ; Changes_When_Uppercased # L& LATIN SMALL LETTER IS
+A76F ; Changes_When_Uppercased # L& LATIN SMALL LETTER CON
+A77A ; Changes_When_Uppercased # L& LATIN SMALL LETTER INSULAR D
+A77C ; Changes_When_Uppercased # L& LATIN SMALL LETTER INSULAR F
+A77F ; Changes_When_Uppercased # L& LATIN SMALL LETTER TURNED INSULAR G
+A781 ; Changes_When_Uppercased # L& LATIN SMALL LETTER TURNED L
+A783 ; Changes_When_Uppercased # L& LATIN SMALL LETTER INSULAR R
+A785 ; Changes_When_Uppercased # L& LATIN SMALL LETTER INSULAR S
+A787 ; Changes_When_Uppercased # L& LATIN SMALL LETTER INSULAR T
+A78C ; Changes_When_Uppercased # L& LATIN SMALL LETTER SALTILLO
+A791 ; Changes_When_Uppercased # L& LATIN SMALL LETTER N WITH DESCENDER
+A793..A794 ; Changes_When_Uppercased # L& [2] LATIN SMALL LETTER C WITH BAR..LATIN SMALL LETTER C WITH PALATAL HOOK
+A797 ; Changes_When_Uppercased # L& LATIN SMALL LETTER B WITH FLOURISH
+A799 ; Changes_When_Uppercased # L& LATIN SMALL LETTER F WITH STROKE
+A79B ; Changes_When_Uppercased # L& LATIN SMALL LETTER VOLAPUK AE
+A79D ; Changes_When_Uppercased # L& LATIN SMALL LETTER VOLAPUK OE
+A79F ; Changes_When_Uppercased # L& LATIN SMALL LETTER VOLAPUK UE
+A7A1 ; Changes_When_Uppercased # L& LATIN SMALL LETTER G WITH OBLIQUE STROKE
+A7A3 ; Changes_When_Uppercased # L& LATIN SMALL LETTER K WITH OBLIQUE STROKE
+A7A5 ; Changes_When_Uppercased # L& LATIN SMALL LETTER N WITH OBLIQUE STROKE
+A7A7 ; Changes_When_Uppercased # L& LATIN SMALL LETTER R WITH OBLIQUE STROKE
+A7A9 ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH OBLIQUE STROKE
+A7B5 ; Changes_When_Uppercased # L& LATIN SMALL LETTER BETA
+A7B7 ; Changes_When_Uppercased # L& LATIN SMALL LETTER OMEGA
+A7B9 ; Changes_When_Uppercased # L& LATIN SMALL LETTER U WITH STROKE
+A7BB ; Changes_When_Uppercased # L& LATIN SMALL LETTER GLOTTAL A
+A7BD ; Changes_When_Uppercased # L& LATIN SMALL LETTER GLOTTAL I
+A7BF ; Changes_When_Uppercased # L& LATIN SMALL LETTER GLOTTAL U
+A7C1 ; Changes_When_Uppercased # L& LATIN SMALL LETTER OLD POLISH O
+A7C3 ; Changes_When_Uppercased # L& LATIN SMALL LETTER ANGLICANA W
+A7C8 ; Changes_When_Uppercased # L& LATIN SMALL LETTER D WITH SHORT STROKE OVERLAY
+A7CA ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY
+A7CD ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH DIAGONAL STROKE
+A7D1 ; Changes_When_Uppercased # L& LATIN SMALL LETTER CLOSED INSULAR G
+A7D7 ; Changes_When_Uppercased # L& LATIN SMALL LETTER MIDDLE SCOTS S
+A7D9 ; Changes_When_Uppercased # L& LATIN SMALL LETTER SIGMOID S
+A7DB ; Changes_When_Uppercased # L& LATIN SMALL LETTER LAMBDA
+A7F6 ; Changes_When_Uppercased # L& LATIN SMALL LETTER REVERSED HALF H
+AB53 ; Changes_When_Uppercased # L& LATIN SMALL LETTER CHI
+AB70..ABBF ; Changes_When_Uppercased # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA
+FB00..FB06 ; Changes_When_Uppercased # L& [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST
+FB13..FB17 ; Changes_When_Uppercased # L& [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH
+FF41..FF5A ; Changes_When_Uppercased # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z
+10428..1044F ; Changes_When_Uppercased # L& [40] DESERET SMALL LETTER LONG I..DESERET SMALL LETTER EW
+104D8..104FB ; Changes_When_Uppercased # L& [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA
+10597..105A1 ; Changes_When_Uppercased # L& [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA
+105A3..105B1 ; Changes_When_Uppercased # L& [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE
+105B3..105B9 ; Changes_When_Uppercased # L& [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE
+105BB..105BC ; Changes_When_Uppercased # L& [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE
+10CC0..10CF2 ; Changes_When_Uppercased # L& [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US
+10D70..10D85 ; Changes_When_Uppercased # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA
+118C0..118DF ; Changes_When_Uppercased # L& [32] WARANG CITI SMALL LETTER NGAA..WARANG CITI SMALL LETTER VIYO
+16E60..16E7F ; Changes_When_Uppercased # L& [32] MEDEFAIDRIN SMALL LETTER M..MEDEFAIDRIN SMALL LETTER Y
+1E922..1E943 ; Changes_When_Uppercased # L& [34] ADLAM SMALL LETTER ALIF..ADLAM SMALL LETTER SHA
+
+# Total code points: 1552
+
+# ================================================
+
+# Derived Property: Changes_When_Titlecased (CWT)
+# Characters whose normalized forms are not stable under a toTitlecase mapping.
+# For more information, see D141 in Section 3.13, "Default Case Algorithms".
+# Changes_When_Titlecased(X) is true when toTitlecase(toNFD(X)) != toNFD(X)
+
+0061..007A ; Changes_When_Titlecased # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+00B5 ; Changes_When_Titlecased # L& MICRO SIGN
+00DF..00F6 ; Changes_When_Titlecased # L& [24] LATIN SMALL LETTER SHARP S..LATIN SMALL LETTER O WITH DIAERESIS
+00F8..00FF ; Changes_When_Titlecased # L& [8] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER Y WITH DIAERESIS
+0101 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH MACRON
+0103 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH BREVE
+0105 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH OGONEK
+0107 ; Changes_When_Titlecased # L& LATIN SMALL LETTER C WITH ACUTE
+0109 ; Changes_When_Titlecased # L& LATIN SMALL LETTER C WITH CIRCUMFLEX
+010B ; Changes_When_Titlecased # L& LATIN SMALL LETTER C WITH DOT ABOVE
+010D ; Changes_When_Titlecased # L& LATIN SMALL LETTER C WITH CARON
+010F ; Changes_When_Titlecased # L& LATIN SMALL LETTER D WITH CARON
+0111 ; Changes_When_Titlecased # L& LATIN SMALL LETTER D WITH STROKE
+0113 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH MACRON
+0115 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH BREVE
+0117 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH DOT ABOVE
+0119 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH OGONEK
+011B ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH CARON
+011D ; Changes_When_Titlecased # L& LATIN SMALL LETTER G WITH CIRCUMFLEX
+011F ; Changes_When_Titlecased # L& LATIN SMALL LETTER G WITH BREVE
+0121 ; Changes_When_Titlecased # L& LATIN SMALL LETTER G WITH DOT ABOVE
+0123 ; Changes_When_Titlecased # L& LATIN SMALL LETTER G WITH CEDILLA
+0125 ; Changes_When_Titlecased # L& LATIN SMALL LETTER H WITH CIRCUMFLEX
+0127 ; Changes_When_Titlecased # L& LATIN SMALL LETTER H WITH STROKE
+0129 ; Changes_When_Titlecased # L& LATIN SMALL LETTER I WITH TILDE
+012B ; Changes_When_Titlecased # L& LATIN SMALL LETTER I WITH MACRON
+012D ; Changes_When_Titlecased # L& LATIN SMALL LETTER I WITH BREVE
+012F ; Changes_When_Titlecased # L& LATIN SMALL LETTER I WITH OGONEK
+0131 ; Changes_When_Titlecased # L& LATIN SMALL LETTER DOTLESS I
+0133 ; Changes_When_Titlecased # L& LATIN SMALL LIGATURE IJ
+0135 ; Changes_When_Titlecased # L& LATIN SMALL LETTER J WITH CIRCUMFLEX
+0137 ; Changes_When_Titlecased # L& LATIN SMALL LETTER K WITH CEDILLA
+013A ; Changes_When_Titlecased # L& LATIN SMALL LETTER L WITH ACUTE
+013C ; Changes_When_Titlecased # L& LATIN SMALL LETTER L WITH CEDILLA
+013E ; Changes_When_Titlecased # L& LATIN SMALL LETTER L WITH CARON
+0140 ; Changes_When_Titlecased # L& LATIN SMALL LETTER L WITH MIDDLE DOT
+0142 ; Changes_When_Titlecased # L& LATIN SMALL LETTER L WITH STROKE
+0144 ; Changes_When_Titlecased # L& LATIN SMALL LETTER N WITH ACUTE
+0146 ; Changes_When_Titlecased # L& LATIN SMALL LETTER N WITH CEDILLA
+0148..0149 ; Changes_When_Titlecased # L& [2] LATIN SMALL LETTER N WITH CARON..LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+014B ; Changes_When_Titlecased # L& LATIN SMALL LETTER ENG
+014D ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH MACRON
+014F ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH BREVE
+0151 ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH DOUBLE ACUTE
+0153 ; Changes_When_Titlecased # L& LATIN SMALL LIGATURE OE
+0155 ; Changes_When_Titlecased # L& LATIN SMALL LETTER R WITH ACUTE
+0157 ; Changes_When_Titlecased # L& LATIN SMALL LETTER R WITH CEDILLA
+0159 ; Changes_When_Titlecased # L& LATIN SMALL LETTER R WITH CARON
+015B ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH ACUTE
+015D ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH CIRCUMFLEX
+015F ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH CEDILLA
+0161 ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH CARON
+0163 ; Changes_When_Titlecased # L& LATIN SMALL LETTER T WITH CEDILLA
+0165 ; Changes_When_Titlecased # L& LATIN SMALL LETTER T WITH CARON
+0167 ; Changes_When_Titlecased # L& LATIN SMALL LETTER T WITH STROKE
+0169 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH TILDE
+016B ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH MACRON
+016D ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH BREVE
+016F ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH RING ABOVE
+0171 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH DOUBLE ACUTE
+0173 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH OGONEK
+0175 ; Changes_When_Titlecased # L& LATIN SMALL LETTER W WITH CIRCUMFLEX
+0177 ; Changes_When_Titlecased # L& LATIN SMALL LETTER Y WITH CIRCUMFLEX
+017A ; Changes_When_Titlecased # L& LATIN SMALL LETTER Z WITH ACUTE
+017C ; Changes_When_Titlecased # L& LATIN SMALL LETTER Z WITH DOT ABOVE
+017E..0180 ; Changes_When_Titlecased # L& [3] LATIN SMALL LETTER Z WITH CARON..LATIN SMALL LETTER B WITH STROKE
+0183 ; Changes_When_Titlecased # L& LATIN SMALL LETTER B WITH TOPBAR
+0185 ; Changes_When_Titlecased # L& LATIN SMALL LETTER TONE SIX
+0188 ; Changes_When_Titlecased # L& LATIN SMALL LETTER C WITH HOOK
+018C ; Changes_When_Titlecased # L& LATIN SMALL LETTER D WITH TOPBAR
+0192 ; Changes_When_Titlecased # L& LATIN SMALL LETTER F WITH HOOK
+0195 ; Changes_When_Titlecased # L& LATIN SMALL LETTER HV
+0199..019B ; Changes_When_Titlecased # L& [3] LATIN SMALL LETTER K WITH HOOK..LATIN SMALL LETTER LAMBDA WITH STROKE
+019E ; Changes_When_Titlecased # L& LATIN SMALL LETTER N WITH LONG RIGHT LEG
+01A1 ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH HORN
+01A3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER OI
+01A5 ; Changes_When_Titlecased # L& LATIN SMALL LETTER P WITH HOOK
+01A8 ; Changes_When_Titlecased # L& LATIN SMALL LETTER TONE TWO
+01AD ; Changes_When_Titlecased # L& LATIN SMALL LETTER T WITH HOOK
+01B0 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH HORN
+01B4 ; Changes_When_Titlecased # L& LATIN SMALL LETTER Y WITH HOOK
+01B6 ; Changes_When_Titlecased # L& LATIN SMALL LETTER Z WITH STROKE
+01B9 ; Changes_When_Titlecased # L& LATIN SMALL LETTER EZH REVERSED
+01BD ; Changes_When_Titlecased # L& LATIN SMALL LETTER TONE FIVE
+01BF ; Changes_When_Titlecased # L& LATIN LETTER WYNN
+01C4 ; Changes_When_Titlecased # L& LATIN CAPITAL LETTER DZ WITH CARON
+01C6..01C7 ; Changes_When_Titlecased # L& [2] LATIN SMALL LETTER DZ WITH CARON..LATIN CAPITAL LETTER LJ
+01C9..01CA ; Changes_When_Titlecased # L& [2] LATIN SMALL LETTER LJ..LATIN CAPITAL LETTER NJ
+01CC ; Changes_When_Titlecased # L& LATIN SMALL LETTER NJ
+01CE ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH CARON
+01D0 ; Changes_When_Titlecased # L& LATIN SMALL LETTER I WITH CARON
+01D2 ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH CARON
+01D4 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH CARON
+01D6 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH DIAERESIS AND MACRON
+01D8 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE
+01DA ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH DIAERESIS AND CARON
+01DC..01DD ; Changes_When_Titlecased # L& [2] LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE..LATIN SMALL LETTER TURNED E
+01DF ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH DIAERESIS AND MACRON
+01E1 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON
+01E3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER AE WITH MACRON
+01E5 ; Changes_When_Titlecased # L& LATIN SMALL LETTER G WITH STROKE
+01E7 ; Changes_When_Titlecased # L& LATIN SMALL LETTER G WITH CARON
+01E9 ; Changes_When_Titlecased # L& LATIN SMALL LETTER K WITH CARON
+01EB ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH OGONEK
+01ED ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH OGONEK AND MACRON
+01EF..01F1 ; Changes_When_Titlecased # L& [3] LATIN SMALL LETTER EZH WITH CARON..LATIN CAPITAL LETTER DZ
+01F3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER DZ
+01F5 ; Changes_When_Titlecased # L& LATIN SMALL LETTER G WITH ACUTE
+01F9 ; Changes_When_Titlecased # L& LATIN SMALL LETTER N WITH GRAVE
+01FB ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE
+01FD ; Changes_When_Titlecased # L& LATIN SMALL LETTER AE WITH ACUTE
+01FF ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH STROKE AND ACUTE
+0201 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH DOUBLE GRAVE
+0203 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH INVERTED BREVE
+0205 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH DOUBLE GRAVE
+0207 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH INVERTED BREVE
+0209 ; Changes_When_Titlecased # L& LATIN SMALL LETTER I WITH DOUBLE GRAVE
+020B ; Changes_When_Titlecased # L& LATIN SMALL LETTER I WITH INVERTED BREVE
+020D ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH DOUBLE GRAVE
+020F ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH INVERTED BREVE
+0211 ; Changes_When_Titlecased # L& LATIN SMALL LETTER R WITH DOUBLE GRAVE
+0213 ; Changes_When_Titlecased # L& LATIN SMALL LETTER R WITH INVERTED BREVE
+0215 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH DOUBLE GRAVE
+0217 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH INVERTED BREVE
+0219 ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH COMMA BELOW
+021B ; Changes_When_Titlecased # L& LATIN SMALL LETTER T WITH COMMA BELOW
+021D ; Changes_When_Titlecased # L& LATIN SMALL LETTER YOGH
+021F ; Changes_When_Titlecased # L& LATIN SMALL LETTER H WITH CARON
+0223 ; Changes_When_Titlecased # L& LATIN SMALL LETTER OU
+0225 ; Changes_When_Titlecased # L& LATIN SMALL LETTER Z WITH HOOK
+0227 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH DOT ABOVE
+0229 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH CEDILLA
+022B ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH DIAERESIS AND MACRON
+022D ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH TILDE AND MACRON
+022F ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH DOT ABOVE
+0231 ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON
+0233 ; Changes_When_Titlecased # L& LATIN SMALL LETTER Y WITH MACRON
+023C ; Changes_When_Titlecased # L& LATIN SMALL LETTER C WITH STROKE
+023F..0240 ; Changes_When_Titlecased # L& [2] LATIN SMALL LETTER S WITH SWASH TAIL..LATIN SMALL LETTER Z WITH SWASH TAIL
+0242 ; Changes_When_Titlecased # L& LATIN SMALL LETTER GLOTTAL STOP
+0247 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH STROKE
+0249 ; Changes_When_Titlecased # L& LATIN SMALL LETTER J WITH STROKE
+024B ; Changes_When_Titlecased # L& LATIN SMALL LETTER Q WITH HOOK TAIL
+024D ; Changes_When_Titlecased # L& LATIN SMALL LETTER R WITH STROKE
+024F..0254 ; Changes_When_Titlecased # L& [6] LATIN SMALL LETTER Y WITH STROKE..LATIN SMALL LETTER OPEN O
+0256..0257 ; Changes_When_Titlecased # L& [2] LATIN SMALL LETTER D WITH TAIL..LATIN SMALL LETTER D WITH HOOK
+0259 ; Changes_When_Titlecased # L& LATIN SMALL LETTER SCHWA
+025B..025C ; Changes_When_Titlecased # L& [2] LATIN SMALL LETTER OPEN E..LATIN SMALL LETTER REVERSED OPEN E
+0260..0261 ; Changes_When_Titlecased # L& [2] LATIN SMALL LETTER G WITH HOOK..LATIN SMALL LETTER SCRIPT G
+0263..0266 ; Changes_When_Titlecased # L& [4] LATIN SMALL LETTER GAMMA..LATIN SMALL LETTER H WITH HOOK
+0268..026C ; Changes_When_Titlecased # L& [5] LATIN SMALL LETTER I WITH STROKE..LATIN SMALL LETTER L WITH BELT
+026F ; Changes_When_Titlecased # L& LATIN SMALL LETTER TURNED M
+0271..0272 ; Changes_When_Titlecased # L& [2] LATIN SMALL LETTER M WITH HOOK..LATIN SMALL LETTER N WITH LEFT HOOK
+0275 ; Changes_When_Titlecased # L& LATIN SMALL LETTER BARRED O
+027D ; Changes_When_Titlecased # L& LATIN SMALL LETTER R WITH TAIL
+0280 ; Changes_When_Titlecased # L& LATIN LETTER SMALL CAPITAL R
+0282..0283 ; Changes_When_Titlecased # L& [2] LATIN SMALL LETTER S WITH HOOK..LATIN SMALL LETTER ESH
+0287..028C ; Changes_When_Titlecased # L& [6] LATIN SMALL LETTER TURNED T..LATIN SMALL LETTER TURNED V
+0292 ; Changes_When_Titlecased # L& LATIN SMALL LETTER EZH
+029D..029E ; Changes_When_Titlecased # L& [2] LATIN SMALL LETTER J WITH CROSSED-TAIL..LATIN SMALL LETTER TURNED K
+0345 ; Changes_When_Titlecased # Mn COMBINING GREEK YPOGEGRAMMENI
+0371 ; Changes_When_Titlecased # L& GREEK SMALL LETTER HETA
+0373 ; Changes_When_Titlecased # L& GREEK SMALL LETTER ARCHAIC SAMPI
+0377 ; Changes_When_Titlecased # L& GREEK SMALL LETTER PAMPHYLIAN DIGAMMA
+037B..037D ; Changes_When_Titlecased # L& [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL
+0390 ; Changes_When_Titlecased # L& GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+03AC..03CE ; Changes_When_Titlecased # L& [35] GREEK SMALL LETTER ALPHA WITH TONOS..GREEK SMALL LETTER OMEGA WITH TONOS
+03D0..03D1 ; Changes_When_Titlecased # L& [2] GREEK BETA SYMBOL..GREEK THETA SYMBOL
+03D5..03D7 ; Changes_When_Titlecased # L& [3] GREEK PHI SYMBOL..GREEK KAI SYMBOL
+03D9 ; Changes_When_Titlecased # L& GREEK SMALL LETTER ARCHAIC KOPPA
+03DB ; Changes_When_Titlecased # L& GREEK SMALL LETTER STIGMA
+03DD ; Changes_When_Titlecased # L& GREEK SMALL LETTER DIGAMMA
+03DF ; Changes_When_Titlecased # L& GREEK SMALL LETTER KOPPA
+03E1 ; Changes_When_Titlecased # L& GREEK SMALL LETTER SAMPI
+03E3 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER SHEI
+03E5 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER FEI
+03E7 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER KHEI
+03E9 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER HORI
+03EB ; Changes_When_Titlecased # L& COPTIC SMALL LETTER GANGIA
+03ED ; Changes_When_Titlecased # L& COPTIC SMALL LETTER SHIMA
+03EF..03F3 ; Changes_When_Titlecased # L& [5] COPTIC SMALL LETTER DEI..GREEK LETTER YOT
+03F5 ; Changes_When_Titlecased # L& GREEK LUNATE EPSILON SYMBOL
+03F8 ; Changes_When_Titlecased # L& GREEK SMALL LETTER SHO
+03FB ; Changes_When_Titlecased # L& GREEK SMALL LETTER SAN
+0430..045F ; Changes_When_Titlecased # L& [48] CYRILLIC SMALL LETTER A..CYRILLIC SMALL LETTER DZHE
+0461 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER OMEGA
+0463 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER YAT
+0465 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER IOTIFIED E
+0467 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER LITTLE YUS
+0469 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER IOTIFIED LITTLE YUS
+046B ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER BIG YUS
+046D ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER IOTIFIED BIG YUS
+046F ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KSI
+0471 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER PSI
+0473 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER FITA
+0475 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER IZHITSA
+0477 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT
+0479 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER UK
+047B ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ROUND OMEGA
+047D ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER OMEGA WITH TITLO
+047F ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER OT
+0481 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KOPPA
+048B ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER SHORT I WITH TAIL
+048D ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER SEMISOFT SIGN
+048F ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ER WITH TICK
+0491 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER GHE WITH UPTURN
+0493 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER GHE WITH STROKE
+0495 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER GHE WITH MIDDLE HOOK
+0497 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ZHE WITH DESCENDER
+0499 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ZE WITH DESCENDER
+049B ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KA WITH DESCENDER
+049D ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE
+049F ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KA WITH STROKE
+04A1 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER BASHKIR KA
+04A3 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER EN WITH DESCENDER
+04A5 ; Changes_When_Titlecased # L& CYRILLIC SMALL LIGATURE EN GHE
+04A7 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER PE WITH MIDDLE HOOK
+04A9 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ABKHASIAN HA
+04AB ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ES WITH DESCENDER
+04AD ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER TE WITH DESCENDER
+04AF ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER STRAIGHT U
+04B1 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE
+04B3 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER HA WITH DESCENDER
+04B5 ; Changes_When_Titlecased # L& CYRILLIC SMALL LIGATURE TE TSE
+04B7 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER CHE WITH DESCENDER
+04B9 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE
+04BB ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER SHHA
+04BD ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ABKHASIAN CHE
+04BF ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ABKHASIAN CHE WITH DESCENDER
+04C2 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ZHE WITH BREVE
+04C4 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KA WITH HOOK
+04C6 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER EL WITH TAIL
+04C8 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER EN WITH HOOK
+04CA ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER EN WITH TAIL
+04CC ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KHAKASSIAN CHE
+04CE..04CF ; Changes_When_Titlecased # L& [2] CYRILLIC SMALL LETTER EM WITH TAIL..CYRILLIC SMALL LETTER PALOCHKA
+04D1 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER A WITH BREVE
+04D3 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER A WITH DIAERESIS
+04D5 ; Changes_When_Titlecased # L& CYRILLIC SMALL LIGATURE A IE
+04D7 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER IE WITH BREVE
+04D9 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER SCHWA
+04DB ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS
+04DD ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ZHE WITH DIAERESIS
+04DF ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ZE WITH DIAERESIS
+04E1 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ABKHASIAN DZE
+04E3 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER I WITH MACRON
+04E5 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER I WITH DIAERESIS
+04E7 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER O WITH DIAERESIS
+04E9 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER BARRED O
+04EB ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS
+04ED ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER E WITH DIAERESIS
+04EF ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER U WITH MACRON
+04F1 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER U WITH DIAERESIS
+04F3 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE
+04F5 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER CHE WITH DIAERESIS
+04F7 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER GHE WITH DESCENDER
+04F9 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER YERU WITH DIAERESIS
+04FB ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER GHE WITH STROKE AND HOOK
+04FD ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER HA WITH HOOK
+04FF ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER HA WITH STROKE
+0501 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KOMI DE
+0503 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KOMI DJE
+0505 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KOMI ZJE
+0507 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KOMI DZJE
+0509 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KOMI LJE
+050B ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KOMI NJE
+050D ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KOMI SJE
+050F ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER KOMI TJE
+0511 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER REVERSED ZE
+0513 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER EL WITH HOOK
+0515 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER LHA
+0517 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER RHA
+0519 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER YAE
+051B ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER QA
+051D ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER WE
+051F ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ALEUT KA
+0521 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER EL WITH MIDDLE HOOK
+0523 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK
+0525 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER PE WITH DESCENDER
+0527 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER SHHA WITH DESCENDER
+0529 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER EN WITH LEFT HOOK
+052B ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER DZZHE
+052D ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER DCHE
+052F ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER EL WITH DESCENDER
+0561..0587 ; Changes_When_Titlecased # L& [39] ARMENIAN SMALL LETTER AYB..ARMENIAN SMALL LIGATURE ECH YIWN
+13F8..13FD ; Changes_When_Titlecased # L& [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV
+1C80..1C88 ; Changes_When_Titlecased # L& [9] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER UNBLENDED UK
+1C8A ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER TJE
+1D79 ; Changes_When_Titlecased # L& LATIN SMALL LETTER INSULAR G
+1D7D ; Changes_When_Titlecased # L& LATIN SMALL LETTER P WITH STROKE
+1D8E ; Changes_When_Titlecased # L& LATIN SMALL LETTER Z WITH PALATAL HOOK
+1E01 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH RING BELOW
+1E03 ; Changes_When_Titlecased # L& LATIN SMALL LETTER B WITH DOT ABOVE
+1E05 ; Changes_When_Titlecased # L& LATIN SMALL LETTER B WITH DOT BELOW
+1E07 ; Changes_When_Titlecased # L& LATIN SMALL LETTER B WITH LINE BELOW
+1E09 ; Changes_When_Titlecased # L& LATIN SMALL LETTER C WITH CEDILLA AND ACUTE
+1E0B ; Changes_When_Titlecased # L& LATIN SMALL LETTER D WITH DOT ABOVE
+1E0D ; Changes_When_Titlecased # L& LATIN SMALL LETTER D WITH DOT BELOW
+1E0F ; Changes_When_Titlecased # L& LATIN SMALL LETTER D WITH LINE BELOW
+1E11 ; Changes_When_Titlecased # L& LATIN SMALL LETTER D WITH CEDILLA
+1E13 ; Changes_When_Titlecased # L& LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW
+1E15 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH MACRON AND GRAVE
+1E17 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH MACRON AND ACUTE
+1E19 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW
+1E1B ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH TILDE BELOW
+1E1D ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH CEDILLA AND BREVE
+1E1F ; Changes_When_Titlecased # L& LATIN SMALL LETTER F WITH DOT ABOVE
+1E21 ; Changes_When_Titlecased # L& LATIN SMALL LETTER G WITH MACRON
+1E23 ; Changes_When_Titlecased # L& LATIN SMALL LETTER H WITH DOT ABOVE
+1E25 ; Changes_When_Titlecased # L& LATIN SMALL LETTER H WITH DOT BELOW
+1E27 ; Changes_When_Titlecased # L& LATIN SMALL LETTER H WITH DIAERESIS
+1E29 ; Changes_When_Titlecased # L& LATIN SMALL LETTER H WITH CEDILLA
+1E2B ; Changes_When_Titlecased # L& LATIN SMALL LETTER H WITH BREVE BELOW
+1E2D ; Changes_When_Titlecased # L& LATIN SMALL LETTER I WITH TILDE BELOW
+1E2F ; Changes_When_Titlecased # L& LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE
+1E31 ; Changes_When_Titlecased # L& LATIN SMALL LETTER K WITH ACUTE
+1E33 ; Changes_When_Titlecased # L& LATIN SMALL LETTER K WITH DOT BELOW
+1E35 ; Changes_When_Titlecased # L& LATIN SMALL LETTER K WITH LINE BELOW
+1E37 ; Changes_When_Titlecased # L& LATIN SMALL LETTER L WITH DOT BELOW
+1E39 ; Changes_When_Titlecased # L& LATIN SMALL LETTER L WITH DOT BELOW AND MACRON
+1E3B ; Changes_When_Titlecased # L& LATIN SMALL LETTER L WITH LINE BELOW
+1E3D ; Changes_When_Titlecased # L& LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW
+1E3F ; Changes_When_Titlecased # L& LATIN SMALL LETTER M WITH ACUTE
+1E41 ; Changes_When_Titlecased # L& LATIN SMALL LETTER M WITH DOT ABOVE
+1E43 ; Changes_When_Titlecased # L& LATIN SMALL LETTER M WITH DOT BELOW
+1E45 ; Changes_When_Titlecased # L& LATIN SMALL LETTER N WITH DOT ABOVE
+1E47 ; Changes_When_Titlecased # L& LATIN SMALL LETTER N WITH DOT BELOW
+1E49 ; Changes_When_Titlecased # L& LATIN SMALL LETTER N WITH LINE BELOW
+1E4B ; Changes_When_Titlecased # L& LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW
+1E4D ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH TILDE AND ACUTE
+1E4F ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH TILDE AND DIAERESIS
+1E51 ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH MACRON AND GRAVE
+1E53 ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH MACRON AND ACUTE
+1E55 ; Changes_When_Titlecased # L& LATIN SMALL LETTER P WITH ACUTE
+1E57 ; Changes_When_Titlecased # L& LATIN SMALL LETTER P WITH DOT ABOVE
+1E59 ; Changes_When_Titlecased # L& LATIN SMALL LETTER R WITH DOT ABOVE
+1E5B ; Changes_When_Titlecased # L& LATIN SMALL LETTER R WITH DOT BELOW
+1E5D ; Changes_When_Titlecased # L& LATIN SMALL LETTER R WITH DOT BELOW AND MACRON
+1E5F ; Changes_When_Titlecased # L& LATIN SMALL LETTER R WITH LINE BELOW
+1E61 ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH DOT ABOVE
+1E63 ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH DOT BELOW
+1E65 ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE
+1E67 ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH CARON AND DOT ABOVE
+1E69 ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE
+1E6B ; Changes_When_Titlecased # L& LATIN SMALL LETTER T WITH DOT ABOVE
+1E6D ; Changes_When_Titlecased # L& LATIN SMALL LETTER T WITH DOT BELOW
+1E6F ; Changes_When_Titlecased # L& LATIN SMALL LETTER T WITH LINE BELOW
+1E71 ; Changes_When_Titlecased # L& LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW
+1E73 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH DIAERESIS BELOW
+1E75 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH TILDE BELOW
+1E77 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW
+1E79 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH TILDE AND ACUTE
+1E7B ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH MACRON AND DIAERESIS
+1E7D ; Changes_When_Titlecased # L& LATIN SMALL LETTER V WITH TILDE
+1E7F ; Changes_When_Titlecased # L& LATIN SMALL LETTER V WITH DOT BELOW
+1E81 ; Changes_When_Titlecased # L& LATIN SMALL LETTER W WITH GRAVE
+1E83 ; Changes_When_Titlecased # L& LATIN SMALL LETTER W WITH ACUTE
+1E85 ; Changes_When_Titlecased # L& LATIN SMALL LETTER W WITH DIAERESIS
+1E87 ; Changes_When_Titlecased # L& LATIN SMALL LETTER W WITH DOT ABOVE
+1E89 ; Changes_When_Titlecased # L& LATIN SMALL LETTER W WITH DOT BELOW
+1E8B ; Changes_When_Titlecased # L& LATIN SMALL LETTER X WITH DOT ABOVE
+1E8D ; Changes_When_Titlecased # L& LATIN SMALL LETTER X WITH DIAERESIS
+1E8F ; Changes_When_Titlecased # L& LATIN SMALL LETTER Y WITH DOT ABOVE
+1E91 ; Changes_When_Titlecased # L& LATIN SMALL LETTER Z WITH CIRCUMFLEX
+1E93 ; Changes_When_Titlecased # L& LATIN SMALL LETTER Z WITH DOT BELOW
+1E95..1E9B ; Changes_When_Titlecased # L& [7] LATIN SMALL LETTER Z WITH LINE BELOW..LATIN SMALL LETTER LONG S WITH DOT ABOVE
+1EA1 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH DOT BELOW
+1EA3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH HOOK ABOVE
+1EA5 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE
+1EA7 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE
+1EA9 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
+1EAB ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE
+1EAD ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW
+1EAF ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH BREVE AND ACUTE
+1EB1 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH BREVE AND GRAVE
+1EB3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE
+1EB5 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH BREVE AND TILDE
+1EB7 ; Changes_When_Titlecased # L& LATIN SMALL LETTER A WITH BREVE AND DOT BELOW
+1EB9 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH DOT BELOW
+1EBB ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH HOOK ABOVE
+1EBD ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH TILDE
+1EBF ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE
+1EC1 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE
+1EC3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+1EC5 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE
+1EC7 ; Changes_When_Titlecased # L& LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW
+1EC9 ; Changes_When_Titlecased # L& LATIN SMALL LETTER I WITH HOOK ABOVE
+1ECB ; Changes_When_Titlecased # L& LATIN SMALL LETTER I WITH DOT BELOW
+1ECD ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH DOT BELOW
+1ECF ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH HOOK ABOVE
+1ED1 ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE
+1ED3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE
+1ED5 ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
+1ED7 ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE
+1ED9 ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW
+1EDB ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH HORN AND ACUTE
+1EDD ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH HORN AND GRAVE
+1EDF ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE
+1EE1 ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH HORN AND TILDE
+1EE3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH HORN AND DOT BELOW
+1EE5 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH DOT BELOW
+1EE7 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH HOOK ABOVE
+1EE9 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH HORN AND ACUTE
+1EEB ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH HORN AND GRAVE
+1EED ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE
+1EEF ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH HORN AND TILDE
+1EF1 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH HORN AND DOT BELOW
+1EF3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER Y WITH GRAVE
+1EF5 ; Changes_When_Titlecased # L& LATIN SMALL LETTER Y WITH DOT BELOW
+1EF7 ; Changes_When_Titlecased # L& LATIN SMALL LETTER Y WITH HOOK ABOVE
+1EF9 ; Changes_When_Titlecased # L& LATIN SMALL LETTER Y WITH TILDE
+1EFB ; Changes_When_Titlecased # L& LATIN SMALL LETTER MIDDLE-WELSH LL
+1EFD ; Changes_When_Titlecased # L& LATIN SMALL LETTER MIDDLE-WELSH V
+1EFF..1F07 ; Changes_When_Titlecased # L& [9] LATIN SMALL LETTER Y WITH LOOP..GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI
+1F10..1F15 ; Changes_When_Titlecased # L& [6] GREEK SMALL LETTER EPSILON WITH PSILI..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA
+1F20..1F27 ; Changes_When_Titlecased # L& [8] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI
+1F30..1F37 ; Changes_When_Titlecased # L& [8] GREEK SMALL LETTER IOTA WITH PSILI..GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI
+1F40..1F45 ; Changes_When_Titlecased # L& [6] GREEK SMALL LETTER OMICRON WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA
+1F50..1F57 ; Changes_When_Titlecased # L& [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F60..1F67 ; Changes_When_Titlecased # L& [8] GREEK SMALL LETTER OMEGA WITH PSILI..GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI
+1F70..1F7D ; Changes_When_Titlecased # L& [14] GREEK SMALL LETTER ALPHA WITH VARIA..GREEK SMALL LETTER OMEGA WITH OXIA
+1F80..1F87 ; Changes_When_Titlecased # L& [8] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F90..1F97 ; Changes_When_Titlecased # L& [8] GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1FA0..1FA7 ; Changes_When_Titlecased # L& [8] GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1FB0..1FB4 ; Changes_When_Titlecased # L& [5] GREEK SMALL LETTER ALPHA WITH VRACHY..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6..1FB7 ; Changes_When_Titlecased # L& [2] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FBE ; Changes_When_Titlecased # L& GREEK PROSGEGRAMMENI
+1FC2..1FC4 ; Changes_When_Titlecased # L& [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6..1FC7 ; Changes_When_Titlecased # L& [2] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FD0..1FD3 ; Changes_When_Titlecased # L& [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6..1FD7 ; Changes_When_Titlecased # L& [2] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI
+1FE0..1FE7 ; Changes_When_Titlecased # L& [8] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI
+1FF2..1FF4 ; Changes_When_Titlecased # L& [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6..1FF7 ; Changes_When_Titlecased # L& [2] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
+214E ; Changes_When_Titlecased # L& TURNED SMALL F
+2170..217F ; Changes_When_Titlecased # Nl [16] SMALL ROMAN NUMERAL ONE..SMALL ROMAN NUMERAL ONE THOUSAND
+2184 ; Changes_When_Titlecased # L& LATIN SMALL LETTER REVERSED C
+24D0..24E9 ; Changes_When_Titlecased # So [26] CIRCLED LATIN SMALL LETTER A..CIRCLED LATIN SMALL LETTER Z
+2C30..2C5F ; Changes_When_Titlecased # L& [48] GLAGOLITIC SMALL LETTER AZU..GLAGOLITIC SMALL LETTER CAUDATE CHRIVI
+2C61 ; Changes_When_Titlecased # L& LATIN SMALL LETTER L WITH DOUBLE BAR
+2C65..2C66 ; Changes_When_Titlecased # L& [2] LATIN SMALL LETTER A WITH STROKE..LATIN SMALL LETTER T WITH DIAGONAL STROKE
+2C68 ; Changes_When_Titlecased # L& LATIN SMALL LETTER H WITH DESCENDER
+2C6A ; Changes_When_Titlecased # L& LATIN SMALL LETTER K WITH DESCENDER
+2C6C ; Changes_When_Titlecased # L& LATIN SMALL LETTER Z WITH DESCENDER
+2C73 ; Changes_When_Titlecased # L& LATIN SMALL LETTER W WITH HOOK
+2C76 ; Changes_When_Titlecased # L& LATIN SMALL LETTER HALF H
+2C81 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER ALFA
+2C83 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER VIDA
+2C85 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER GAMMA
+2C87 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER DALDA
+2C89 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER EIE
+2C8B ; Changes_When_Titlecased # L& COPTIC SMALL LETTER SOU
+2C8D ; Changes_When_Titlecased # L& COPTIC SMALL LETTER ZATA
+2C8F ; Changes_When_Titlecased # L& COPTIC SMALL LETTER HATE
+2C91 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER THETHE
+2C93 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER IAUDA
+2C95 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER KAPA
+2C97 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER LAULA
+2C99 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER MI
+2C9B ; Changes_When_Titlecased # L& COPTIC SMALL LETTER NI
+2C9D ; Changes_When_Titlecased # L& COPTIC SMALL LETTER KSI
+2C9F ; Changes_When_Titlecased # L& COPTIC SMALL LETTER O
+2CA1 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER PI
+2CA3 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER RO
+2CA5 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER SIMA
+2CA7 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER TAU
+2CA9 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER UA
+2CAB ; Changes_When_Titlecased # L& COPTIC SMALL LETTER FI
+2CAD ; Changes_When_Titlecased # L& COPTIC SMALL LETTER KHI
+2CAF ; Changes_When_Titlecased # L& COPTIC SMALL LETTER PSI
+2CB1 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OOU
+2CB3 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER DIALECT-P ALEF
+2CB5 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD COPTIC AIN
+2CB7 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER CRYPTOGRAMMIC EIE
+2CB9 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER DIALECT-P KAPA
+2CBB ; Changes_When_Titlecased # L& COPTIC SMALL LETTER DIALECT-P NI
+2CBD ; Changes_When_Titlecased # L& COPTIC SMALL LETTER CRYPTOGRAMMIC NI
+2CBF ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD COPTIC OOU
+2CC1 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER SAMPI
+2CC3 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER CROSSED SHEI
+2CC5 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD COPTIC SHEI
+2CC7 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD COPTIC ESH
+2CC9 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER AKHMIMIC KHEI
+2CCB ; Changes_When_Titlecased # L& COPTIC SMALL LETTER DIALECT-P HORI
+2CCD ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD COPTIC HORI
+2CCF ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD COPTIC HA
+2CD1 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER L-SHAPED HA
+2CD3 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD COPTIC HEI
+2CD5 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD COPTIC HAT
+2CD7 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD COPTIC GANGIA
+2CD9 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD COPTIC DJA
+2CDB ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD COPTIC SHIMA
+2CDD ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD NUBIAN SHIMA
+2CDF ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD NUBIAN NGI
+2CE1 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD NUBIAN NYI
+2CE3 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER OLD NUBIAN WAU
+2CEC ; Changes_When_Titlecased # L& COPTIC SMALL LETTER CRYPTOGRAMMIC SHEI
+2CEE ; Changes_When_Titlecased # L& COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA
+2CF3 ; Changes_When_Titlecased # L& COPTIC SMALL LETTER BOHAIRIC KHEI
+2D00..2D25 ; Changes_When_Titlecased # L& [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE
+2D27 ; Changes_When_Titlecased # L& GEORGIAN SMALL LETTER YN
+2D2D ; Changes_When_Titlecased # L& GEORGIAN SMALL LETTER AEN
+A641 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ZEMLYA
+A643 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER DZELO
+A645 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER REVERSED DZE
+A647 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER IOTA
+A649 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER DJERV
+A64B ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER MONOGRAPH UK
+A64D ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER BROAD OMEGA
+A64F ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER NEUTRAL YER
+A651 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER YERU WITH BACK YER
+A653 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER IOTIFIED YAT
+A655 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER REVERSED YU
+A657 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER IOTIFIED A
+A659 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER CLOSED LITTLE YUS
+A65B ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER BLENDED YUS
+A65D ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER IOTIFIED CLOSED LITTLE YUS
+A65F ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER YN
+A661 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER REVERSED TSE
+A663 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER SOFT DE
+A665 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER SOFT EL
+A667 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER SOFT EM
+A669 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER MONOCULAR O
+A66B ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER BINOCULAR O
+A66D ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER DOUBLE MONOCULAR O
+A681 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER DWE
+A683 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER DZWE
+A685 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER ZHWE
+A687 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER CCHE
+A689 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER DZZE
+A68B ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER TE WITH MIDDLE HOOK
+A68D ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER TWE
+A68F ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER TSWE
+A691 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER TSSE
+A693 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER TCHE
+A695 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER HWE
+A697 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER SHWE
+A699 ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER DOUBLE O
+A69B ; Changes_When_Titlecased # L& CYRILLIC SMALL LETTER CROSSED O
+A723 ; Changes_When_Titlecased # L& LATIN SMALL LETTER EGYPTOLOGICAL ALEF
+A725 ; Changes_When_Titlecased # L& LATIN SMALL LETTER EGYPTOLOGICAL AIN
+A727 ; Changes_When_Titlecased # L& LATIN SMALL LETTER HENG
+A729 ; Changes_When_Titlecased # L& LATIN SMALL LETTER TZ
+A72B ; Changes_When_Titlecased # L& LATIN SMALL LETTER TRESILLO
+A72D ; Changes_When_Titlecased # L& LATIN SMALL LETTER CUATRILLO
+A72F ; Changes_When_Titlecased # L& LATIN SMALL LETTER CUATRILLO WITH COMMA
+A733 ; Changes_When_Titlecased # L& LATIN SMALL LETTER AA
+A735 ; Changes_When_Titlecased # L& LATIN SMALL LETTER AO
+A737 ; Changes_When_Titlecased # L& LATIN SMALL LETTER AU
+A739 ; Changes_When_Titlecased # L& LATIN SMALL LETTER AV
+A73B ; Changes_When_Titlecased # L& LATIN SMALL LETTER AV WITH HORIZONTAL BAR
+A73D ; Changes_When_Titlecased # L& LATIN SMALL LETTER AY
+A73F ; Changes_When_Titlecased # L& LATIN SMALL LETTER REVERSED C WITH DOT
+A741 ; Changes_When_Titlecased # L& LATIN SMALL LETTER K WITH STROKE
+A743 ; Changes_When_Titlecased # L& LATIN SMALL LETTER K WITH DIAGONAL STROKE
+A745 ; Changes_When_Titlecased # L& LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE
+A747 ; Changes_When_Titlecased # L& LATIN SMALL LETTER BROKEN L
+A749 ; Changes_When_Titlecased # L& LATIN SMALL LETTER L WITH HIGH STROKE
+A74B ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH LONG STROKE OVERLAY
+A74D ; Changes_When_Titlecased # L& LATIN SMALL LETTER O WITH LOOP
+A74F ; Changes_When_Titlecased # L& LATIN SMALL LETTER OO
+A751 ; Changes_When_Titlecased # L& LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER
+A753 ; Changes_When_Titlecased # L& LATIN SMALL LETTER P WITH FLOURISH
+A755 ; Changes_When_Titlecased # L& LATIN SMALL LETTER P WITH SQUIRREL TAIL
+A757 ; Changes_When_Titlecased # L& LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER
+A759 ; Changes_When_Titlecased # L& LATIN SMALL LETTER Q WITH DIAGONAL STROKE
+A75B ; Changes_When_Titlecased # L& LATIN SMALL LETTER R ROTUNDA
+A75D ; Changes_When_Titlecased # L& LATIN SMALL LETTER RUM ROTUNDA
+A75F ; Changes_When_Titlecased # L& LATIN SMALL LETTER V WITH DIAGONAL STROKE
+A761 ; Changes_When_Titlecased # L& LATIN SMALL LETTER VY
+A763 ; Changes_When_Titlecased # L& LATIN SMALL LETTER VISIGOTHIC Z
+A765 ; Changes_When_Titlecased # L& LATIN SMALL LETTER THORN WITH STROKE
+A767 ; Changes_When_Titlecased # L& LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER
+A769 ; Changes_When_Titlecased # L& LATIN SMALL LETTER VEND
+A76B ; Changes_When_Titlecased # L& LATIN SMALL LETTER ET
+A76D ; Changes_When_Titlecased # L& LATIN SMALL LETTER IS
+A76F ; Changes_When_Titlecased # L& LATIN SMALL LETTER CON
+A77A ; Changes_When_Titlecased # L& LATIN SMALL LETTER INSULAR D
+A77C ; Changes_When_Titlecased # L& LATIN SMALL LETTER INSULAR F
+A77F ; Changes_When_Titlecased # L& LATIN SMALL LETTER TURNED INSULAR G
+A781 ; Changes_When_Titlecased # L& LATIN SMALL LETTER TURNED L
+A783 ; Changes_When_Titlecased # L& LATIN SMALL LETTER INSULAR R
+A785 ; Changes_When_Titlecased # L& LATIN SMALL LETTER INSULAR S
+A787 ; Changes_When_Titlecased # L& LATIN SMALL LETTER INSULAR T
+A78C ; Changes_When_Titlecased # L& LATIN SMALL LETTER SALTILLO
+A791 ; Changes_When_Titlecased # L& LATIN SMALL LETTER N WITH DESCENDER
+A793..A794 ; Changes_When_Titlecased # L& [2] LATIN SMALL LETTER C WITH BAR..LATIN SMALL LETTER C WITH PALATAL HOOK
+A797 ; Changes_When_Titlecased # L& LATIN SMALL LETTER B WITH FLOURISH
+A799 ; Changes_When_Titlecased # L& LATIN SMALL LETTER F WITH STROKE
+A79B ; Changes_When_Titlecased # L& LATIN SMALL LETTER VOLAPUK AE
+A79D ; Changes_When_Titlecased # L& LATIN SMALL LETTER VOLAPUK OE
+A79F ; Changes_When_Titlecased # L& LATIN SMALL LETTER VOLAPUK UE
+A7A1 ; Changes_When_Titlecased # L& LATIN SMALL LETTER G WITH OBLIQUE STROKE
+A7A3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER K WITH OBLIQUE STROKE
+A7A5 ; Changes_When_Titlecased # L& LATIN SMALL LETTER N WITH OBLIQUE STROKE
+A7A7 ; Changes_When_Titlecased # L& LATIN SMALL LETTER R WITH OBLIQUE STROKE
+A7A9 ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH OBLIQUE STROKE
+A7B5 ; Changes_When_Titlecased # L& LATIN SMALL LETTER BETA
+A7B7 ; Changes_When_Titlecased # L& LATIN SMALL LETTER OMEGA
+A7B9 ; Changes_When_Titlecased # L& LATIN SMALL LETTER U WITH STROKE
+A7BB ; Changes_When_Titlecased # L& LATIN SMALL LETTER GLOTTAL A
+A7BD ; Changes_When_Titlecased # L& LATIN SMALL LETTER GLOTTAL I
+A7BF ; Changes_When_Titlecased # L& LATIN SMALL LETTER GLOTTAL U
+A7C1 ; Changes_When_Titlecased # L& LATIN SMALL LETTER OLD POLISH O
+A7C3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER ANGLICANA W
+A7C8 ; Changes_When_Titlecased # L& LATIN SMALL LETTER D WITH SHORT STROKE OVERLAY
+A7CA ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY
+A7CD ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH DIAGONAL STROKE
+A7D1 ; Changes_When_Titlecased # L& LATIN SMALL LETTER CLOSED INSULAR G
+A7D7 ; Changes_When_Titlecased # L& LATIN SMALL LETTER MIDDLE SCOTS S
+A7D9 ; Changes_When_Titlecased # L& LATIN SMALL LETTER SIGMOID S
+A7DB ; Changes_When_Titlecased # L& LATIN SMALL LETTER LAMBDA
+A7F6 ; Changes_When_Titlecased # L& LATIN SMALL LETTER REVERSED HALF H
+AB53 ; Changes_When_Titlecased # L& LATIN SMALL LETTER CHI
+AB70..ABBF ; Changes_When_Titlecased # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA
+FB00..FB06 ; Changes_When_Titlecased # L& [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST
+FB13..FB17 ; Changes_When_Titlecased # L& [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH
+FF41..FF5A ; Changes_When_Titlecased # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z
+10428..1044F ; Changes_When_Titlecased # L& [40] DESERET SMALL LETTER LONG I..DESERET SMALL LETTER EW
+104D8..104FB ; Changes_When_Titlecased # L& [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA
+10597..105A1 ; Changes_When_Titlecased # L& [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA
+105A3..105B1 ; Changes_When_Titlecased # L& [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE
+105B3..105B9 ; Changes_When_Titlecased # L& [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE
+105BB..105BC ; Changes_When_Titlecased # L& [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE
+10CC0..10CF2 ; Changes_When_Titlecased # L& [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US
+10D70..10D85 ; Changes_When_Titlecased # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA
+118C0..118DF ; Changes_When_Titlecased # L& [32] WARANG CITI SMALL LETTER NGAA..WARANG CITI SMALL LETTER VIYO
+16E60..16E7F ; Changes_When_Titlecased # L& [32] MEDEFAIDRIN SMALL LETTER M..MEDEFAIDRIN SMALL LETTER Y
+1E922..1E943 ; Changes_When_Titlecased # L& [34] ADLAM SMALL LETTER ALIF..ADLAM SMALL LETTER SHA
+
+# Total code points: 1479
+
+# ================================================
+
+# Derived Property: Changes_When_Casefolded (CWCF)
+# Characters whose normalized forms are not stable under case folding.
+# For more information, see D142 in Section 3.13, "Default Case Algorithms".
+# Changes_When_Casefolded(X) is true when toCasefold(toNFD(X)) != toNFD(X)
+
+0041..005A ; Changes_When_Casefolded # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+00B5 ; Changes_When_Casefolded # L& MICRO SIGN
+00C0..00D6 ; Changes_When_Casefolded # L& [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS
+00D8..00DF ; Changes_When_Casefolded # L& [8] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER SHARP S
+0100 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH MACRON
+0102 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH BREVE
+0104 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH OGONEK
+0106 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER C WITH ACUTE
+0108 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+010A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER C WITH DOT ABOVE
+010C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER C WITH CARON
+010E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER D WITH CARON
+0110 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER D WITH STROKE
+0112 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH MACRON
+0114 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH BREVE
+0116 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH DOT ABOVE
+0118 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH OGONEK
+011A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH CARON
+011C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+011E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER G WITH BREVE
+0120 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER G WITH DOT ABOVE
+0122 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER G WITH CEDILLA
+0124 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+0126 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER H WITH STROKE
+0128 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER I WITH TILDE
+012A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER I WITH MACRON
+012C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER I WITH BREVE
+012E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER I WITH OGONEK
+0130 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER I WITH DOT ABOVE
+0132 ; Changes_When_Casefolded # L& LATIN CAPITAL LIGATURE IJ
+0134 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+0136 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER K WITH CEDILLA
+0139 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER L WITH ACUTE
+013B ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER L WITH CEDILLA
+013D ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER L WITH CARON
+013F ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER L WITH MIDDLE DOT
+0141 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER L WITH STROKE
+0143 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER N WITH ACUTE
+0145 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER N WITH CEDILLA
+0147 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER N WITH CARON
+0149..014A ; Changes_When_Casefolded # L& [2] LATIN SMALL LETTER N PRECEDED BY APOSTROPHE..LATIN CAPITAL LETTER ENG
+014C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH MACRON
+014E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH BREVE
+0150 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+0152 ; Changes_When_Casefolded # L& LATIN CAPITAL LIGATURE OE
+0154 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER R WITH ACUTE
+0156 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER R WITH CEDILLA
+0158 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER R WITH CARON
+015A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER S WITH ACUTE
+015C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+015E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER S WITH CEDILLA
+0160 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER S WITH CARON
+0162 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER T WITH CEDILLA
+0164 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER T WITH CARON
+0166 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER T WITH STROKE
+0168 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH TILDE
+016A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH MACRON
+016C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH BREVE
+016E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH RING ABOVE
+0170 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+0172 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH OGONEK
+0174 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER W WITH CIRCUMFLEX
+0176 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Y WITH CIRCUMFLEX
+0178..0179 ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER Y WITH DIAERESIS..LATIN CAPITAL LETTER Z WITH ACUTE
+017B ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Z WITH DOT ABOVE
+017D ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Z WITH CARON
+017F ; Changes_When_Casefolded # L& LATIN SMALL LETTER LONG S
+0181..0182 ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER B WITH HOOK..LATIN CAPITAL LETTER B WITH TOPBAR
+0184 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER TONE SIX
+0186..0187 ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER OPEN O..LATIN CAPITAL LETTER C WITH HOOK
+0189..018B ; Changes_When_Casefolded # L& [3] LATIN CAPITAL LETTER AFRICAN D..LATIN CAPITAL LETTER D WITH TOPBAR
+018E..0191 ; Changes_When_Casefolded # L& [4] LATIN CAPITAL LETTER REVERSED E..LATIN CAPITAL LETTER F WITH HOOK
+0193..0194 ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER G WITH HOOK..LATIN CAPITAL LETTER GAMMA
+0196..0198 ; Changes_When_Casefolded # L& [3] LATIN CAPITAL LETTER IOTA..LATIN CAPITAL LETTER K WITH HOOK
+019C..019D ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER TURNED M..LATIN CAPITAL LETTER N WITH LEFT HOOK
+019F..01A0 ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER O WITH MIDDLE TILDE..LATIN CAPITAL LETTER O WITH HORN
+01A2 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER OI
+01A4 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER P WITH HOOK
+01A6..01A7 ; Changes_When_Casefolded # L& [2] LATIN LETTER YR..LATIN CAPITAL LETTER TONE TWO
+01A9 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER ESH
+01AC ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER T WITH HOOK
+01AE..01AF ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER T WITH RETROFLEX HOOK..LATIN CAPITAL LETTER U WITH HORN
+01B1..01B3 ; Changes_When_Casefolded # L& [3] LATIN CAPITAL LETTER UPSILON..LATIN CAPITAL LETTER Y WITH HOOK
+01B5 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Z WITH STROKE
+01B7..01B8 ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER EZH..LATIN CAPITAL LETTER EZH REVERSED
+01BC ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER TONE FIVE
+01C4..01C5 ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER DZ WITH CARON..LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON
+01C7..01C8 ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER LJ..LATIN CAPITAL LETTER L WITH SMALL LETTER J
+01CA..01CB ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER NJ..LATIN CAPITAL LETTER N WITH SMALL LETTER J
+01CD ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH CARON
+01CF ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER I WITH CARON
+01D1 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH CARON
+01D3 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH CARON
+01D5 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON
+01D7 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE
+01D9 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON
+01DB ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE
+01DE ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON
+01E0 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON
+01E2 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER AE WITH MACRON
+01E4 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER G WITH STROKE
+01E6 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER G WITH CARON
+01E8 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER K WITH CARON
+01EA ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH OGONEK
+01EC ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH OGONEK AND MACRON
+01EE ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER EZH WITH CARON
+01F1..01F2 ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER DZ..LATIN CAPITAL LETTER D WITH SMALL LETTER Z
+01F4 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER G WITH ACUTE
+01F6..01F8 ; Changes_When_Casefolded # L& [3] LATIN CAPITAL LETTER HWAIR..LATIN CAPITAL LETTER N WITH GRAVE
+01FA ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE
+01FC ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER AE WITH ACUTE
+01FE ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH STROKE AND ACUTE
+0200 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH DOUBLE GRAVE
+0202 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH INVERTED BREVE
+0204 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH DOUBLE GRAVE
+0206 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH INVERTED BREVE
+0208 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER I WITH DOUBLE GRAVE
+020A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER I WITH INVERTED BREVE
+020C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH DOUBLE GRAVE
+020E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH INVERTED BREVE
+0210 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER R WITH DOUBLE GRAVE
+0212 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER R WITH INVERTED BREVE
+0214 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH DOUBLE GRAVE
+0216 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH INVERTED BREVE
+0218 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER S WITH COMMA BELOW
+021A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER T WITH COMMA BELOW
+021C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER YOGH
+021E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER H WITH CARON
+0220 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER N WITH LONG RIGHT LEG
+0222 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER OU
+0224 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Z WITH HOOK
+0226 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH DOT ABOVE
+0228 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH CEDILLA
+022A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON
+022C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH TILDE AND MACRON
+022E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH DOT ABOVE
+0230 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON
+0232 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Y WITH MACRON
+023A..023B ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER A WITH STROKE..LATIN CAPITAL LETTER C WITH STROKE
+023D..023E ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER L WITH BAR..LATIN CAPITAL LETTER T WITH DIAGONAL STROKE
+0241 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER GLOTTAL STOP
+0243..0246 ; Changes_When_Casefolded # L& [4] LATIN CAPITAL LETTER B WITH STROKE..LATIN CAPITAL LETTER E WITH STROKE
+0248 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER J WITH STROKE
+024A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL
+024C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER R WITH STROKE
+024E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Y WITH STROKE
+0345 ; Changes_When_Casefolded # Mn COMBINING GREEK YPOGEGRAMMENI
+0370 ; Changes_When_Casefolded # L& GREEK CAPITAL LETTER HETA
+0372 ; Changes_When_Casefolded # L& GREEK CAPITAL LETTER ARCHAIC SAMPI
+0376 ; Changes_When_Casefolded # L& GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA
+037F ; Changes_When_Casefolded # L& GREEK CAPITAL LETTER YOT
+0386 ; Changes_When_Casefolded # L& GREEK CAPITAL LETTER ALPHA WITH TONOS
+0388..038A ; Changes_When_Casefolded # L& [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS
+038C ; Changes_When_Casefolded # L& GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E..038F ; Changes_When_Casefolded # L& [2] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER OMEGA WITH TONOS
+0391..03A1 ; Changes_When_Casefolded # L& [17] GREEK CAPITAL LETTER ALPHA..GREEK CAPITAL LETTER RHO
+03A3..03AB ; Changes_When_Casefolded # L& [9] GREEK CAPITAL LETTER SIGMA..GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+03C2 ; Changes_When_Casefolded # L& GREEK SMALL LETTER FINAL SIGMA
+03CF..03D1 ; Changes_When_Casefolded # L& [3] GREEK CAPITAL KAI SYMBOL..GREEK THETA SYMBOL
+03D5..03D6 ; Changes_When_Casefolded # L& [2] GREEK PHI SYMBOL..GREEK PI SYMBOL
+03D8 ; Changes_When_Casefolded # L& GREEK LETTER ARCHAIC KOPPA
+03DA ; Changes_When_Casefolded # L& GREEK LETTER STIGMA
+03DC ; Changes_When_Casefolded # L& GREEK LETTER DIGAMMA
+03DE ; Changes_When_Casefolded # L& GREEK LETTER KOPPA
+03E0 ; Changes_When_Casefolded # L& GREEK LETTER SAMPI
+03E2 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER SHEI
+03E4 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER FEI
+03E6 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER KHEI
+03E8 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER HORI
+03EA ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER GANGIA
+03EC ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER SHIMA
+03EE ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER DEI
+03F0..03F1 ; Changes_When_Casefolded # L& [2] GREEK KAPPA SYMBOL..GREEK RHO SYMBOL
+03F4..03F5 ; Changes_When_Casefolded # L& [2] GREEK CAPITAL THETA SYMBOL..GREEK LUNATE EPSILON SYMBOL
+03F7 ; Changes_When_Casefolded # L& GREEK CAPITAL LETTER SHO
+03F9..03FA ; Changes_When_Casefolded # L& [2] GREEK CAPITAL LUNATE SIGMA SYMBOL..GREEK CAPITAL LETTER SAN
+03FD..042F ; Changes_When_Casefolded # L& [51] GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL..CYRILLIC CAPITAL LETTER YA
+0460 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER OMEGA
+0462 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER YAT
+0464 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER IOTIFIED E
+0466 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER LITTLE YUS
+0468 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS
+046A ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER BIG YUS
+046C ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS
+046E ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KSI
+0470 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER PSI
+0472 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER FITA
+0474 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER IZHITSA
+0476 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT
+0478 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER UK
+047A ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER ROUND OMEGA
+047C ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER OMEGA WITH TITLO
+047E ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER OT
+0480 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KOPPA
+048A ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER SHORT I WITH TAIL
+048C ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER SEMISOFT SIGN
+048E ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER ER WITH TICK
+0490 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+0492 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER GHE WITH STROKE
+0494 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK
+0496 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER
+0498 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER ZE WITH DESCENDER
+049A ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KA WITH DESCENDER
+049C ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE
+049E ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KA WITH STROKE
+04A0 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER BASHKIR KA
+04A2 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER EN WITH DESCENDER
+04A4 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LIGATURE EN GHE
+04A6 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK
+04A8 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER ABKHASIAN HA
+04AA ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER ES WITH DESCENDER
+04AC ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER TE WITH DESCENDER
+04AE ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER STRAIGHT U
+04B0 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE
+04B2 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER HA WITH DESCENDER
+04B4 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LIGATURE TE TSE
+04B6 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER CHE WITH DESCENDER
+04B8 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE
+04BA ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER SHHA
+04BC ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER ABKHASIAN CHE
+04BE ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER
+04C0..04C1 ; Changes_When_Casefolded # L& [2] CYRILLIC LETTER PALOCHKA..CYRILLIC CAPITAL LETTER ZHE WITH BREVE
+04C3 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KA WITH HOOK
+04C5 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER EL WITH TAIL
+04C7 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER EN WITH HOOK
+04C9 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER EN WITH TAIL
+04CB ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KHAKASSIAN CHE
+04CD ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER EM WITH TAIL
+04D0 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER A WITH BREVE
+04D2 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER A WITH DIAERESIS
+04D4 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LIGATURE A IE
+04D6 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER IE WITH BREVE
+04D8 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER SCHWA
+04DA ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS
+04DC ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS
+04DE ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS
+04E0 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER ABKHASIAN DZE
+04E2 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER I WITH MACRON
+04E4 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER I WITH DIAERESIS
+04E6 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER O WITH DIAERESIS
+04E8 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER BARRED O
+04EA ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS
+04EC ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER E WITH DIAERESIS
+04EE ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER U WITH MACRON
+04F0 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER U WITH DIAERESIS
+04F2 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE
+04F4 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS
+04F6 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER GHE WITH DESCENDER
+04F8 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS
+04FA ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK
+04FC ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER HA WITH HOOK
+04FE ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER HA WITH STROKE
+0500 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KOMI DE
+0502 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KOMI DJE
+0504 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KOMI ZJE
+0506 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KOMI DZJE
+0508 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KOMI LJE
+050A ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KOMI NJE
+050C ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KOMI SJE
+050E ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER KOMI TJE
+0510 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER REVERSED ZE
+0512 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER EL WITH HOOK
+0514 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER LHA
+0516 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER RHA
+0518 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER YAE
+051A ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER QA
+051C ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER WE
+051E ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER ALEUT KA
+0520 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK
+0522 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK
+0524 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER PE WITH DESCENDER
+0526 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER
+0528 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER EN WITH LEFT HOOK
+052A ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER DZZHE
+052C ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER DCHE
+052E ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER EL WITH DESCENDER
+0531..0556 ; Changes_When_Casefolded # L& [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH
+0587 ; Changes_When_Casefolded # L& ARMENIAN SMALL LIGATURE ECH YIWN
+10A0..10C5 ; Changes_When_Casefolded # L& [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE
+10C7 ; Changes_When_Casefolded # L& GEORGIAN CAPITAL LETTER YN
+10CD ; Changes_When_Casefolded # L& GEORGIAN CAPITAL LETTER AEN
+13F8..13FD ; Changes_When_Casefolded # L& [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV
+1C80..1C89 ; Changes_When_Casefolded # L& [10] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC CAPITAL LETTER TJE
+1C90..1CBA ; Changes_When_Casefolded # L& [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN
+1CBD..1CBF ; Changes_When_Casefolded # L& [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
+1E00 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH RING BELOW
+1E02 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER B WITH DOT ABOVE
+1E04 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER B WITH DOT BELOW
+1E06 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER B WITH LINE BELOW
+1E08 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE
+1E0A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER D WITH DOT ABOVE
+1E0C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER D WITH DOT BELOW
+1E0E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER D WITH LINE BELOW
+1E10 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER D WITH CEDILLA
+1E12 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW
+1E14 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH MACRON AND GRAVE
+1E16 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH MACRON AND ACUTE
+1E18 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW
+1E1A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH TILDE BELOW
+1E1C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE
+1E1E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER F WITH DOT ABOVE
+1E20 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER G WITH MACRON
+1E22 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER H WITH DOT ABOVE
+1E24 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER H WITH DOT BELOW
+1E26 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER H WITH DIAERESIS
+1E28 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER H WITH CEDILLA
+1E2A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER H WITH BREVE BELOW
+1E2C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER I WITH TILDE BELOW
+1E2E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE
+1E30 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER K WITH ACUTE
+1E32 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER K WITH DOT BELOW
+1E34 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER K WITH LINE BELOW
+1E36 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER L WITH DOT BELOW
+1E38 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON
+1E3A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER L WITH LINE BELOW
+1E3C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW
+1E3E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER M WITH ACUTE
+1E40 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER M WITH DOT ABOVE
+1E42 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER M WITH DOT BELOW
+1E44 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER N WITH DOT ABOVE
+1E46 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER N WITH DOT BELOW
+1E48 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER N WITH LINE BELOW
+1E4A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW
+1E4C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH TILDE AND ACUTE
+1E4E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS
+1E50 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH MACRON AND GRAVE
+1E52 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH MACRON AND ACUTE
+1E54 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER P WITH ACUTE
+1E56 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER P WITH DOT ABOVE
+1E58 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER R WITH DOT ABOVE
+1E5A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER R WITH DOT BELOW
+1E5C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON
+1E5E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER R WITH LINE BELOW
+1E60 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER S WITH DOT ABOVE
+1E62 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER S WITH DOT BELOW
+1E64 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE
+1E66 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE
+1E68 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE
+1E6A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER T WITH DOT ABOVE
+1E6C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER T WITH DOT BELOW
+1E6E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER T WITH LINE BELOW
+1E70 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW
+1E72 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH DIAERESIS BELOW
+1E74 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH TILDE BELOW
+1E76 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW
+1E78 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH TILDE AND ACUTE
+1E7A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS
+1E7C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER V WITH TILDE
+1E7E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER V WITH DOT BELOW
+1E80 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER W WITH GRAVE
+1E82 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER W WITH ACUTE
+1E84 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER W WITH DIAERESIS
+1E86 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER W WITH DOT ABOVE
+1E88 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER W WITH DOT BELOW
+1E8A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER X WITH DOT ABOVE
+1E8C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER X WITH DIAERESIS
+1E8E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Y WITH DOT ABOVE
+1E90 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Z WITH CIRCUMFLEX
+1E92 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Z WITH DOT BELOW
+1E94 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Z WITH LINE BELOW
+1E9A..1E9B ; Changes_When_Casefolded # L& [2] LATIN SMALL LETTER A WITH RIGHT HALF RING..LATIN SMALL LETTER LONG S WITH DOT ABOVE
+1E9E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER SHARP S
+1EA0 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH DOT BELOW
+1EA2 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH HOOK ABOVE
+1EA4 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE
+1EA6 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE
+1EA8 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
+1EAA ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE
+1EAC ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW
+1EAE ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH BREVE AND ACUTE
+1EB0 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH BREVE AND GRAVE
+1EB2 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE
+1EB4 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH BREVE AND TILDE
+1EB6 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW
+1EB8 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH DOT BELOW
+1EBA ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH HOOK ABOVE
+1EBC ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH TILDE
+1EBE ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE
+1EC0 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE
+1EC2 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+1EC4 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE
+1EC6 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW
+1EC8 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER I WITH HOOK ABOVE
+1ECA ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER I WITH DOT BELOW
+1ECC ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH DOT BELOW
+1ECE ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH HOOK ABOVE
+1ED0 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE
+1ED2 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE
+1ED4 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
+1ED6 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE
+1ED8 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW
+1EDA ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH HORN AND ACUTE
+1EDC ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH HORN AND GRAVE
+1EDE ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE
+1EE0 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH HORN AND TILDE
+1EE2 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW
+1EE4 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH DOT BELOW
+1EE6 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH HOOK ABOVE
+1EE8 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH HORN AND ACUTE
+1EEA ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH HORN AND GRAVE
+1EEC ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE
+1EEE ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH HORN AND TILDE
+1EF0 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW
+1EF2 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Y WITH GRAVE
+1EF4 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Y WITH DOT BELOW
+1EF6 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Y WITH HOOK ABOVE
+1EF8 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Y WITH TILDE
+1EFA ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER MIDDLE-WELSH LL
+1EFC ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER MIDDLE-WELSH V
+1EFE ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Y WITH LOOP
+1F08..1F0F ; Changes_When_Casefolded # L& [8] GREEK CAPITAL LETTER ALPHA WITH PSILI..GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI
+1F18..1F1D ; Changes_When_Casefolded # L& [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F28..1F2F ; Changes_When_Casefolded # L& [8] GREEK CAPITAL LETTER ETA WITH PSILI..GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI
+1F38..1F3F ; Changes_When_Casefolded # L& [8] GREEK CAPITAL LETTER IOTA WITH PSILI..GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI
+1F48..1F4D ; Changes_When_Casefolded # L& [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F59 ; Changes_When_Casefolded # L& GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B ; Changes_When_Casefolded # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D ; Changes_When_Casefolded # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F ; Changes_When_Casefolded # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F68..1F6F ; Changes_When_Casefolded # L& [8] GREEK CAPITAL LETTER OMEGA WITH PSILI..GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI
+1F80..1FAF ; Changes_When_Casefolded # L& [48] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FB2..1FB4 ; Changes_When_Casefolded # L& [3] GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB7..1FBC ; Changes_When_Casefolded # L& [6] GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FC2..1FC4 ; Changes_When_Casefolded # L& [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC7..1FCC ; Changes_When_Casefolded # L& [6] GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FD8..1FDB ; Changes_When_Casefolded # L& [4] GREEK CAPITAL LETTER IOTA WITH VRACHY..GREEK CAPITAL LETTER IOTA WITH OXIA
+1FE8..1FEC ; Changes_When_Casefolded # L& [5] GREEK CAPITAL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA
+1FF2..1FF4 ; Changes_When_Casefolded # L& [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF7..1FFC ; Changes_When_Casefolded # L& [6] GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+2126 ; Changes_When_Casefolded # L& OHM SIGN
+212A..212B ; Changes_When_Casefolded # L& [2] KELVIN SIGN..ANGSTROM SIGN
+2132 ; Changes_When_Casefolded # L& TURNED CAPITAL F
+2160..216F ; Changes_When_Casefolded # Nl [16] ROMAN NUMERAL ONE..ROMAN NUMERAL ONE THOUSAND
+2183 ; Changes_When_Casefolded # L& ROMAN NUMERAL REVERSED ONE HUNDRED
+24B6..24CF ; Changes_When_Casefolded # So [26] CIRCLED LATIN CAPITAL LETTER A..CIRCLED LATIN CAPITAL LETTER Z
+2C00..2C2F ; Changes_When_Casefolded # L& [48] GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC CAPITAL LETTER CAUDATE CHRIVI
+2C60 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER L WITH DOUBLE BAR
+2C62..2C64 ; Changes_When_Casefolded # L& [3] LATIN CAPITAL LETTER L WITH MIDDLE TILDE..LATIN CAPITAL LETTER R WITH TAIL
+2C67 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER H WITH DESCENDER
+2C69 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER K WITH DESCENDER
+2C6B ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Z WITH DESCENDER
+2C6D..2C70 ; Changes_When_Casefolded # L& [4] LATIN CAPITAL LETTER ALPHA..LATIN CAPITAL LETTER TURNED ALPHA
+2C72 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER W WITH HOOK
+2C75 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER HALF H
+2C7E..2C80 ; Changes_When_Casefolded # L& [3] LATIN CAPITAL LETTER S WITH SWASH TAIL..COPTIC CAPITAL LETTER ALFA
+2C82 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER VIDA
+2C84 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER GAMMA
+2C86 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER DALDA
+2C88 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER EIE
+2C8A ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER SOU
+2C8C ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER ZATA
+2C8E ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER HATE
+2C90 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER THETHE
+2C92 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER IAUDA
+2C94 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER KAPA
+2C96 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER LAULA
+2C98 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER MI
+2C9A ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER NI
+2C9C ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER KSI
+2C9E ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER O
+2CA0 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER PI
+2CA2 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER RO
+2CA4 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER SIMA
+2CA6 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER TAU
+2CA8 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER UA
+2CAA ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER FI
+2CAC ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER KHI
+2CAE ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER PSI
+2CB0 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OOU
+2CB2 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER DIALECT-P ALEF
+2CB4 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD COPTIC AIN
+2CB6 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE
+2CB8 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER DIALECT-P KAPA
+2CBA ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER DIALECT-P NI
+2CBC ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI
+2CBE ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD COPTIC OOU
+2CC0 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER SAMPI
+2CC2 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER CROSSED SHEI
+2CC4 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD COPTIC SHEI
+2CC6 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD COPTIC ESH
+2CC8 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER AKHMIMIC KHEI
+2CCA ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER DIALECT-P HORI
+2CCC ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD COPTIC HORI
+2CCE ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD COPTIC HA
+2CD0 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER L-SHAPED HA
+2CD2 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD COPTIC HEI
+2CD4 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD COPTIC HAT
+2CD6 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD COPTIC GANGIA
+2CD8 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD COPTIC DJA
+2CDA ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD COPTIC SHIMA
+2CDC ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD NUBIAN SHIMA
+2CDE ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD NUBIAN NGI
+2CE0 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD NUBIAN NYI
+2CE2 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER OLD NUBIAN WAU
+2CEB ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI
+2CED ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA
+2CF2 ; Changes_When_Casefolded # L& COPTIC CAPITAL LETTER BOHAIRIC KHEI
+A640 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER ZEMLYA
+A642 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER DZELO
+A644 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER REVERSED DZE
+A646 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER IOTA
+A648 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER DJERV
+A64A ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER MONOGRAPH UK
+A64C ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER BROAD OMEGA
+A64E ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER NEUTRAL YER
+A650 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER YERU WITH BACK YER
+A652 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER IOTIFIED YAT
+A654 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER REVERSED YU
+A656 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER IOTIFIED A
+A658 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS
+A65A ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER BLENDED YUS
+A65C ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS
+A65E ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER YN
+A660 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER REVERSED TSE
+A662 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER SOFT DE
+A664 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER SOFT EL
+A666 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER SOFT EM
+A668 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER MONOCULAR O
+A66A ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER BINOCULAR O
+A66C ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O
+A680 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER DWE
+A682 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER DZWE
+A684 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER ZHWE
+A686 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER CCHE
+A688 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER DZZE
+A68A ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK
+A68C ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER TWE
+A68E ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER TSWE
+A690 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER TSSE
+A692 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER TCHE
+A694 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER HWE
+A696 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER SHWE
+A698 ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER DOUBLE O
+A69A ; Changes_When_Casefolded # L& CYRILLIC CAPITAL LETTER CROSSED O
+A722 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF
+A724 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER EGYPTOLOGICAL AIN
+A726 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER HENG
+A728 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER TZ
+A72A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER TRESILLO
+A72C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER CUATRILLO
+A72E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER CUATRILLO WITH COMMA
+A732 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER AA
+A734 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER AO
+A736 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER AU
+A738 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER AV
+A73A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR
+A73C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER AY
+A73E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER REVERSED C WITH DOT
+A740 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER K WITH STROKE
+A742 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER K WITH DIAGONAL STROKE
+A744 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE
+A746 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER BROKEN L
+A748 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER L WITH HIGH STROKE
+A74A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY
+A74C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER O WITH LOOP
+A74E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER OO
+A750 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER
+A752 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER P WITH FLOURISH
+A754 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER P WITH SQUIRREL TAIL
+A756 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER
+A758 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE
+A75A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER R ROTUNDA
+A75C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER RUM ROTUNDA
+A75E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER V WITH DIAGONAL STROKE
+A760 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER VY
+A762 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER VISIGOTHIC Z
+A764 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER THORN WITH STROKE
+A766 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER
+A768 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER VEND
+A76A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER ET
+A76C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER IS
+A76E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER CON
+A779 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER INSULAR D
+A77B ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER INSULAR F
+A77D..A77E ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER INSULAR G..LATIN CAPITAL LETTER TURNED INSULAR G
+A780 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER TURNED L
+A782 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER INSULAR R
+A784 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER INSULAR S
+A786 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER INSULAR T
+A78B ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER SALTILLO
+A78D ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER TURNED H
+A790 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER N WITH DESCENDER
+A792 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER C WITH BAR
+A796 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER B WITH FLOURISH
+A798 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER F WITH STROKE
+A79A ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER VOLAPUK AE
+A79C ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER VOLAPUK OE
+A79E ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER VOLAPUK UE
+A7A0 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER G WITH OBLIQUE STROKE
+A7A2 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER K WITH OBLIQUE STROKE
+A7A4 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER N WITH OBLIQUE STROKE
+A7A6 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER R WITH OBLIQUE STROKE
+A7A8 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER S WITH OBLIQUE STROKE
+A7AA..A7AE ; Changes_When_Casefolded # L& [5] LATIN CAPITAL LETTER H WITH HOOK..LATIN CAPITAL LETTER SMALL CAPITAL I
+A7B0..A7B4 ; Changes_When_Casefolded # L& [5] LATIN CAPITAL LETTER TURNED K..LATIN CAPITAL LETTER BETA
+A7B6 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER OMEGA
+A7B8 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER U WITH STROKE
+A7BA ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER GLOTTAL A
+A7BC ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER GLOTTAL I
+A7BE ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER GLOTTAL U
+A7C0 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER OLD POLISH O
+A7C2 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER ANGLICANA W
+A7C4..A7C7 ; Changes_When_Casefolded # L& [4] LATIN CAPITAL LETTER C WITH PALATAL HOOK..LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY
+A7C9 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY
+A7CB..A7CC ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER RAMS HORN..LATIN CAPITAL LETTER S WITH DIAGONAL STROKE
+A7D0 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER CLOSED INSULAR G
+A7D6 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER MIDDLE SCOTS S
+A7D8 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER SIGMOID S
+A7DA ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER LAMBDA
+A7DC ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER LAMBDA WITH STROKE
+A7F5 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER REVERSED HALF H
+AB70..ABBF ; Changes_When_Casefolded # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA
+FB00..FB06 ; Changes_When_Casefolded # L& [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST
+FB13..FB17 ; Changes_When_Casefolded # L& [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH
+FF21..FF3A ; Changes_When_Casefolded # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z
+10400..10427 ; Changes_When_Casefolded # L& [40] DESERET CAPITAL LETTER LONG I..DESERET CAPITAL LETTER EW
+104B0..104D3 ; Changes_When_Casefolded # L& [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA
+10570..1057A ; Changes_When_Casefolded # L& [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA
+1057C..1058A ; Changes_When_Casefolded # L& [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE
+1058C..10592 ; Changes_When_Casefolded # L& [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE
+10594..10595 ; Changes_When_Casefolded # L& [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE
+10C80..10CB2 ; Changes_When_Casefolded # L& [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US
+10D50..10D65 ; Changes_When_Casefolded # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA
+118A0..118BF ; Changes_When_Casefolded # L& [32] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI CAPITAL LETTER VIYO
+16E40..16E5F ; Changes_When_Casefolded # L& [32] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN CAPITAL LETTER Y
+1E900..1E921 ; Changes_When_Casefolded # L& [34] ADLAM CAPITAL LETTER ALIF..ADLAM CAPITAL LETTER SHA
+
+# Total code points: 1533
+
+# ================================================
+
+# Derived Property: Changes_When_Casemapped (CWCM)
+# Characters whose normalized forms are not stable under case mapping.
+# For more information, see D143 in Section 3.13, "Default Case Algorithms".
+# Changes_When_Casemapped(X) is true when CWL(X), or CWT(X), or CWU(X)
+
+0041..005A ; Changes_When_Casemapped # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+0061..007A ; Changes_When_Casemapped # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+00B5 ; Changes_When_Casemapped # L& MICRO SIGN
+00C0..00D6 ; Changes_When_Casemapped # L& [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS
+00D8..00F6 ; Changes_When_Casemapped # L& [31] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER O WITH DIAERESIS
+00F8..0137 ; Changes_When_Casemapped # L& [64] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER K WITH CEDILLA
+0139..018C ; Changes_When_Casemapped # L& [84] LATIN CAPITAL LETTER L WITH ACUTE..LATIN SMALL LETTER D WITH TOPBAR
+018E..01A9 ; Changes_When_Casemapped # L& [28] LATIN CAPITAL LETTER REVERSED E..LATIN CAPITAL LETTER ESH
+01AC..01B9 ; Changes_When_Casemapped # L& [14] LATIN CAPITAL LETTER T WITH HOOK..LATIN SMALL LETTER EZH REVERSED
+01BC..01BD ; Changes_When_Casemapped # L& [2] LATIN CAPITAL LETTER TONE FIVE..LATIN SMALL LETTER TONE FIVE
+01BF ; Changes_When_Casemapped # L& LATIN LETTER WYNN
+01C4..0220 ; Changes_When_Casemapped # L& [93] LATIN CAPITAL LETTER DZ WITH CARON..LATIN CAPITAL LETTER N WITH LONG RIGHT LEG
+0222..0233 ; Changes_When_Casemapped # L& [18] LATIN CAPITAL LETTER OU..LATIN SMALL LETTER Y WITH MACRON
+023A..0254 ; Changes_When_Casemapped # L& [27] LATIN CAPITAL LETTER A WITH STROKE..LATIN SMALL LETTER OPEN O
+0256..0257 ; Changes_When_Casemapped # L& [2] LATIN SMALL LETTER D WITH TAIL..LATIN SMALL LETTER D WITH HOOK
+0259 ; Changes_When_Casemapped # L& LATIN SMALL LETTER SCHWA
+025B..025C ; Changes_When_Casemapped # L& [2] LATIN SMALL LETTER OPEN E..LATIN SMALL LETTER REVERSED OPEN E
+0260..0261 ; Changes_When_Casemapped # L& [2] LATIN SMALL LETTER G WITH HOOK..LATIN SMALL LETTER SCRIPT G
+0263..0266 ; Changes_When_Casemapped # L& [4] LATIN SMALL LETTER GAMMA..LATIN SMALL LETTER H WITH HOOK
+0268..026C ; Changes_When_Casemapped # L& [5] LATIN SMALL LETTER I WITH STROKE..LATIN SMALL LETTER L WITH BELT
+026F ; Changes_When_Casemapped # L& LATIN SMALL LETTER TURNED M
+0271..0272 ; Changes_When_Casemapped # L& [2] LATIN SMALL LETTER M WITH HOOK..LATIN SMALL LETTER N WITH LEFT HOOK
+0275 ; Changes_When_Casemapped # L& LATIN SMALL LETTER BARRED O
+027D ; Changes_When_Casemapped # L& LATIN SMALL LETTER R WITH TAIL
+0280 ; Changes_When_Casemapped # L& LATIN LETTER SMALL CAPITAL R
+0282..0283 ; Changes_When_Casemapped # L& [2] LATIN SMALL LETTER S WITH HOOK..LATIN SMALL LETTER ESH
+0287..028C ; Changes_When_Casemapped # L& [6] LATIN SMALL LETTER TURNED T..LATIN SMALL LETTER TURNED V
+0292 ; Changes_When_Casemapped # L& LATIN SMALL LETTER EZH
+029D..029E ; Changes_When_Casemapped # L& [2] LATIN SMALL LETTER J WITH CROSSED-TAIL..LATIN SMALL LETTER TURNED K
+0345 ; Changes_When_Casemapped # Mn COMBINING GREEK YPOGEGRAMMENI
+0370..0373 ; Changes_When_Casemapped # L& [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI
+0376..0377 ; Changes_When_Casemapped # L& [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA
+037B..037D ; Changes_When_Casemapped # L& [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL
+037F ; Changes_When_Casemapped # L& GREEK CAPITAL LETTER YOT
+0386 ; Changes_When_Casemapped # L& GREEK CAPITAL LETTER ALPHA WITH TONOS
+0388..038A ; Changes_When_Casemapped # L& [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS
+038C ; Changes_When_Casemapped # L& GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E..03A1 ; Changes_When_Casemapped # L& [20] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER RHO
+03A3..03D1 ; Changes_When_Casemapped # L& [47] GREEK CAPITAL LETTER SIGMA..GREEK THETA SYMBOL
+03D5..03F5 ; Changes_When_Casemapped # L& [33] GREEK PHI SYMBOL..GREEK LUNATE EPSILON SYMBOL
+03F7..03FB ; Changes_When_Casemapped # L& [5] GREEK CAPITAL LETTER SHO..GREEK SMALL LETTER SAN
+03FD..0481 ; Changes_When_Casemapped # L& [133] GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL..CYRILLIC SMALL LETTER KOPPA
+048A..052F ; Changes_When_Casemapped # L& [166] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER EL WITH DESCENDER
+0531..0556 ; Changes_When_Casemapped # L& [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH
+0561..0587 ; Changes_When_Casemapped # L& [39] ARMENIAN SMALL LETTER AYB..ARMENIAN SMALL LIGATURE ECH YIWN
+10A0..10C5 ; Changes_When_Casemapped # L& [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE
+10C7 ; Changes_When_Casemapped # L& GEORGIAN CAPITAL LETTER YN
+10CD ; Changes_When_Casemapped # L& GEORGIAN CAPITAL LETTER AEN
+10D0..10FA ; Changes_When_Casemapped # L& [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN
+10FD..10FF ; Changes_When_Casemapped # L& [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN
+13A0..13F5 ; Changes_When_Casemapped # L& [86] CHEROKEE LETTER A..CHEROKEE LETTER MV
+13F8..13FD ; Changes_When_Casemapped # L& [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV
+1C80..1C8A ; Changes_When_Casemapped # L& [11] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER TJE
+1C90..1CBA ; Changes_When_Casemapped # L& [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN
+1CBD..1CBF ; Changes_When_Casemapped # L& [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
+1D79 ; Changes_When_Casemapped # L& LATIN SMALL LETTER INSULAR G
+1D7D ; Changes_When_Casemapped # L& LATIN SMALL LETTER P WITH STROKE
+1D8E ; Changes_When_Casemapped # L& LATIN SMALL LETTER Z WITH PALATAL HOOK
+1E00..1E9B ; Changes_When_Casemapped # L& [156] LATIN CAPITAL LETTER A WITH RING BELOW..LATIN SMALL LETTER LONG S WITH DOT ABOVE
+1E9E ; Changes_When_Casemapped # L& LATIN CAPITAL LETTER SHARP S
+1EA0..1F15 ; Changes_When_Casemapped # L& [118] LATIN CAPITAL LETTER A WITH DOT BELOW..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA
+1F18..1F1D ; Changes_When_Casemapped # L& [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F20..1F45 ; Changes_When_Casemapped # L& [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA
+1F48..1F4D ; Changes_When_Casemapped # L& [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F50..1F57 ; Changes_When_Casemapped # L& [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F59 ; Changes_When_Casemapped # L& GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B ; Changes_When_Casemapped # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D ; Changes_When_Casemapped # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F..1F7D ; Changes_When_Casemapped # L& [31] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER OMEGA WITH OXIA
+1F80..1FB4 ; Changes_When_Casemapped # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6..1FBC ; Changes_When_Casemapped # L& [7] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBE ; Changes_When_Casemapped # L& GREEK PROSGEGRAMMENI
+1FC2..1FC4 ; Changes_When_Casemapped # L& [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6..1FCC ; Changes_When_Casemapped # L& [7] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FD0..1FD3 ; Changes_When_Casemapped # L& [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6..1FDB ; Changes_When_Casemapped # L& [6] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH OXIA
+1FE0..1FEC ; Changes_When_Casemapped # L& [13] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA
+1FF2..1FF4 ; Changes_When_Casemapped # L& [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6..1FFC ; Changes_When_Casemapped # L& [7] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+2126 ; Changes_When_Casemapped # L& OHM SIGN
+212A..212B ; Changes_When_Casemapped # L& [2] KELVIN SIGN..ANGSTROM SIGN
+2132 ; Changes_When_Casemapped # L& TURNED CAPITAL F
+214E ; Changes_When_Casemapped # L& TURNED SMALL F
+2160..217F ; Changes_When_Casemapped # Nl [32] ROMAN NUMERAL ONE..SMALL ROMAN NUMERAL ONE THOUSAND
+2183..2184 ; Changes_When_Casemapped # L& [2] ROMAN NUMERAL REVERSED ONE HUNDRED..LATIN SMALL LETTER REVERSED C
+24B6..24E9 ; Changes_When_Casemapped # So [52] CIRCLED LATIN CAPITAL LETTER A..CIRCLED LATIN SMALL LETTER Z
+2C00..2C70 ; Changes_When_Casemapped # L& [113] GLAGOLITIC CAPITAL LETTER AZU..LATIN CAPITAL LETTER TURNED ALPHA
+2C72..2C73 ; Changes_When_Casemapped # L& [2] LATIN CAPITAL LETTER W WITH HOOK..LATIN SMALL LETTER W WITH HOOK
+2C75..2C76 ; Changes_When_Casemapped # L& [2] LATIN CAPITAL LETTER HALF H..LATIN SMALL LETTER HALF H
+2C7E..2CE3 ; Changes_When_Casemapped # L& [102] LATIN CAPITAL LETTER S WITH SWASH TAIL..COPTIC SMALL LETTER OLD NUBIAN WAU
+2CEB..2CEE ; Changes_When_Casemapped # L& [4] COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI..COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA
+2CF2..2CF3 ; Changes_When_Casemapped # L& [2] COPTIC CAPITAL LETTER BOHAIRIC KHEI..COPTIC SMALL LETTER BOHAIRIC KHEI
+2D00..2D25 ; Changes_When_Casemapped # L& [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE
+2D27 ; Changes_When_Casemapped # L& GEORGIAN SMALL LETTER YN
+2D2D ; Changes_When_Casemapped # L& GEORGIAN SMALL LETTER AEN
+A640..A66D ; Changes_When_Casemapped # L& [46] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER DOUBLE MONOCULAR O
+A680..A69B ; Changes_When_Casemapped # L& [28] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER CROSSED O
+A722..A72F ; Changes_When_Casemapped # L& [14] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CUATRILLO WITH COMMA
+A732..A76F ; Changes_When_Casemapped # L& [62] LATIN CAPITAL LETTER AA..LATIN SMALL LETTER CON
+A779..A787 ; Changes_When_Casemapped # L& [15] LATIN CAPITAL LETTER INSULAR D..LATIN SMALL LETTER INSULAR T
+A78B..A78D ; Changes_When_Casemapped # L& [3] LATIN CAPITAL LETTER SALTILLO..LATIN CAPITAL LETTER TURNED H
+A790..A794 ; Changes_When_Casemapped # L& [5] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER C WITH PALATAL HOOK
+A796..A7AE ; Changes_When_Casemapped # L& [25] LATIN CAPITAL LETTER B WITH FLOURISH..LATIN CAPITAL LETTER SMALL CAPITAL I
+A7B0..A7CD ; Changes_When_Casemapped # L& [30] LATIN CAPITAL LETTER TURNED K..LATIN SMALL LETTER S WITH DIAGONAL STROKE
+A7D0..A7D1 ; Changes_When_Casemapped # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G
+A7D6..A7DC ; Changes_When_Casemapped # L& [7] LATIN CAPITAL LETTER MIDDLE SCOTS S..LATIN CAPITAL LETTER LAMBDA WITH STROKE
+A7F5..A7F6 ; Changes_When_Casemapped # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H
+AB53 ; Changes_When_Casemapped # L& LATIN SMALL LETTER CHI
+AB70..ABBF ; Changes_When_Casemapped # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA
+FB00..FB06 ; Changes_When_Casemapped # L& [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST
+FB13..FB17 ; Changes_When_Casemapped # L& [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH
+FF21..FF3A ; Changes_When_Casemapped # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z
+FF41..FF5A ; Changes_When_Casemapped # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z
+10400..1044F ; Changes_When_Casemapped # L& [80] DESERET CAPITAL LETTER LONG I..DESERET SMALL LETTER EW
+104B0..104D3 ; Changes_When_Casemapped # L& [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA
+104D8..104FB ; Changes_When_Casemapped # L& [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA
+10570..1057A ; Changes_When_Casemapped # L& [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA
+1057C..1058A ; Changes_When_Casemapped # L& [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE
+1058C..10592 ; Changes_When_Casemapped # L& [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE
+10594..10595 ; Changes_When_Casemapped # L& [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE
+10597..105A1 ; Changes_When_Casemapped # L& [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA
+105A3..105B1 ; Changes_When_Casemapped # L& [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE
+105B3..105B9 ; Changes_When_Casemapped # L& [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE
+105BB..105BC ; Changes_When_Casemapped # L& [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE
+10C80..10CB2 ; Changes_When_Casemapped # L& [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US
+10CC0..10CF2 ; Changes_When_Casemapped # L& [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US
+10D50..10D65 ; Changes_When_Casemapped # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA
+10D70..10D85 ; Changes_When_Casemapped # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA
+118A0..118DF ; Changes_When_Casemapped # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO
+16E40..16E7F ; Changes_When_Casemapped # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y
+1E900..1E943 ; Changes_When_Casemapped # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA
+
+# Total code points: 2981
+
+# ================================================
+
+# Derived Property: ID_Start
+# Characters that can start an identifier.
+# Generated from:
+# Lu + Ll + Lt + Lm + Lo + Nl
+# + Other_ID_Start
+# - Pattern_Syntax
+# - Pattern_White_Space
+# NOTE: See UAX #31 for more information
+
+0041..005A ; ID_Start # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+0061..007A ; ID_Start # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+00AA ; ID_Start # Lo FEMININE ORDINAL INDICATOR
+00B5 ; ID_Start # L& MICRO SIGN
+00BA ; ID_Start # Lo MASCULINE ORDINAL INDICATOR
+00C0..00D6 ; ID_Start # L& [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS
+00D8..00F6 ; ID_Start # L& [31] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER O WITH DIAERESIS
+00F8..01BA ; ID_Start # L& [195] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL
+01BB ; ID_Start # Lo LATIN LETTER TWO WITH STROKE
+01BC..01BF ; ID_Start # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN
+01C0..01C3 ; ID_Start # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK
+01C4..0293 ; ID_Start # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL
+0294 ; ID_Start # Lo LATIN LETTER GLOTTAL STOP
+0295..02AF ; ID_Start # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL
+02B0..02C1 ; ID_Start # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP
+02C6..02D1 ; ID_Start # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON
+02E0..02E4 ; ID_Start # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP
+02EC ; ID_Start # Lm MODIFIER LETTER VOICING
+02EE ; ID_Start # Lm MODIFIER LETTER DOUBLE APOSTROPHE
+0370..0373 ; ID_Start # L& [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI
+0374 ; ID_Start # Lm GREEK NUMERAL SIGN
+0376..0377 ; ID_Start # L& [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA
+037A ; ID_Start # Lm GREEK YPOGEGRAMMENI
+037B..037D ; ID_Start # L& [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL
+037F ; ID_Start # L& GREEK CAPITAL LETTER YOT
+0386 ; ID_Start # L& GREEK CAPITAL LETTER ALPHA WITH TONOS
+0388..038A ; ID_Start # L& [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS
+038C ; ID_Start # L& GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E..03A1 ; ID_Start # L& [20] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER RHO
+03A3..03F5 ; ID_Start # L& [83] GREEK CAPITAL LETTER SIGMA..GREEK LUNATE EPSILON SYMBOL
+03F7..0481 ; ID_Start # L& [139] GREEK CAPITAL LETTER SHO..CYRILLIC SMALL LETTER KOPPA
+048A..052F ; ID_Start # L& [166] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER EL WITH DESCENDER
+0531..0556 ; ID_Start # L& [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH
+0559 ; ID_Start # Lm ARMENIAN MODIFIER LETTER LEFT HALF RING
+0560..0588 ; ID_Start # L& [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE
+05D0..05EA ; ID_Start # Lo [27] HEBREW LETTER ALEF..HEBREW LETTER TAV
+05EF..05F2 ; ID_Start # Lo [4] HEBREW YOD TRIANGLE..HEBREW LIGATURE YIDDISH DOUBLE YOD
+0620..063F ; ID_Start # Lo [32] ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE
+0640 ; ID_Start # Lm ARABIC TATWEEL
+0641..064A ; ID_Start # Lo [10] ARABIC LETTER FEH..ARABIC LETTER YEH
+066E..066F ; ID_Start # Lo [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF
+0671..06D3 ; ID_Start # Lo [99] ARABIC LETTER ALEF WASLA..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE
+06D5 ; ID_Start # Lo ARABIC LETTER AE
+06E5..06E6 ; ID_Start # Lm [2] ARABIC SMALL WAW..ARABIC SMALL YEH
+06EE..06EF ; ID_Start # Lo [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V
+06FA..06FC ; ID_Start # Lo [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW
+06FF ; ID_Start # Lo ARABIC LETTER HEH WITH INVERTED V
+0710 ; ID_Start # Lo SYRIAC LETTER ALAPH
+0712..072F ; ID_Start # Lo [30] SYRIAC LETTER BETH..SYRIAC LETTER PERSIAN DHALATH
+074D..07A5 ; ID_Start # Lo [89] SYRIAC LETTER SOGDIAN ZHAIN..THAANA LETTER WAAVU
+07B1 ; ID_Start # Lo THAANA LETTER NAA
+07CA..07EA ; ID_Start # Lo [33] NKO LETTER A..NKO LETTER JONA RA
+07F4..07F5 ; ID_Start # Lm [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE
+07FA ; ID_Start # Lm NKO LAJANYALAN
+0800..0815 ; ID_Start # Lo [22] SAMARITAN LETTER ALAF..SAMARITAN LETTER TAAF
+081A ; ID_Start # Lm SAMARITAN MODIFIER LETTER EPENTHETIC YUT
+0824 ; ID_Start # Lm SAMARITAN MODIFIER LETTER SHORT A
+0828 ; ID_Start # Lm SAMARITAN MODIFIER LETTER I
+0840..0858 ; ID_Start # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN
+0860..086A ; ID_Start # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA
+0870..0887 ; ID_Start # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT
+0889..088E ; ID_Start # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL
+08A0..08C8 ; ID_Start # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF
+08C9 ; ID_Start # Lm ARABIC SMALL FARSI YEH
+0904..0939 ; ID_Start # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA
+093D ; ID_Start # Lo DEVANAGARI SIGN AVAGRAHA
+0950 ; ID_Start # Lo DEVANAGARI OM
+0958..0961 ; ID_Start # Lo [10] DEVANAGARI LETTER QA..DEVANAGARI LETTER VOCALIC LL
+0971 ; ID_Start # Lm DEVANAGARI SIGN HIGH SPACING DOT
+0972..0980 ; ID_Start # Lo [15] DEVANAGARI LETTER CANDRA A..BENGALI ANJI
+0985..098C ; ID_Start # Lo [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L
+098F..0990 ; ID_Start # Lo [2] BENGALI LETTER E..BENGALI LETTER AI
+0993..09A8 ; ID_Start # Lo [22] BENGALI LETTER O..BENGALI LETTER NA
+09AA..09B0 ; ID_Start # Lo [7] BENGALI LETTER PA..BENGALI LETTER RA
+09B2 ; ID_Start # Lo BENGALI LETTER LA
+09B6..09B9 ; ID_Start # Lo [4] BENGALI LETTER SHA..BENGALI LETTER HA
+09BD ; ID_Start # Lo BENGALI SIGN AVAGRAHA
+09CE ; ID_Start # Lo BENGALI LETTER KHANDA TA
+09DC..09DD ; ID_Start # Lo [2] BENGALI LETTER RRA..BENGALI LETTER RHA
+09DF..09E1 ; ID_Start # Lo [3] BENGALI LETTER YYA..BENGALI LETTER VOCALIC LL
+09F0..09F1 ; ID_Start # Lo [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL
+09FC ; ID_Start # Lo BENGALI LETTER VEDIC ANUSVARA
+0A05..0A0A ; ID_Start # Lo [6] GURMUKHI LETTER A..GURMUKHI LETTER UU
+0A0F..0A10 ; ID_Start # Lo [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI
+0A13..0A28 ; ID_Start # Lo [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA
+0A2A..0A30 ; ID_Start # Lo [7] GURMUKHI LETTER PA..GURMUKHI LETTER RA
+0A32..0A33 ; ID_Start # Lo [2] GURMUKHI LETTER LA..GURMUKHI LETTER LLA
+0A35..0A36 ; ID_Start # Lo [2] GURMUKHI LETTER VA..GURMUKHI LETTER SHA
+0A38..0A39 ; ID_Start # Lo [2] GURMUKHI LETTER SA..GURMUKHI LETTER HA
+0A59..0A5C ; ID_Start # Lo [4] GURMUKHI LETTER KHHA..GURMUKHI LETTER RRA
+0A5E ; ID_Start # Lo GURMUKHI LETTER FA
+0A72..0A74 ; ID_Start # Lo [3] GURMUKHI IRI..GURMUKHI EK ONKAR
+0A85..0A8D ; ID_Start # Lo [9] GUJARATI LETTER A..GUJARATI VOWEL CANDRA E
+0A8F..0A91 ; ID_Start # Lo [3] GUJARATI LETTER E..GUJARATI VOWEL CANDRA O
+0A93..0AA8 ; ID_Start # Lo [22] GUJARATI LETTER O..GUJARATI LETTER NA
+0AAA..0AB0 ; ID_Start # Lo [7] GUJARATI LETTER PA..GUJARATI LETTER RA
+0AB2..0AB3 ; ID_Start # Lo [2] GUJARATI LETTER LA..GUJARATI LETTER LLA
+0AB5..0AB9 ; ID_Start # Lo [5] GUJARATI LETTER VA..GUJARATI LETTER HA
+0ABD ; ID_Start # Lo GUJARATI SIGN AVAGRAHA
+0AD0 ; ID_Start # Lo GUJARATI OM
+0AE0..0AE1 ; ID_Start # Lo [2] GUJARATI LETTER VOCALIC RR..GUJARATI LETTER VOCALIC LL
+0AF9 ; ID_Start # Lo GUJARATI LETTER ZHA
+0B05..0B0C ; ID_Start # Lo [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L
+0B0F..0B10 ; ID_Start # Lo [2] ORIYA LETTER E..ORIYA LETTER AI
+0B13..0B28 ; ID_Start # Lo [22] ORIYA LETTER O..ORIYA LETTER NA
+0B2A..0B30 ; ID_Start # Lo [7] ORIYA LETTER PA..ORIYA LETTER RA
+0B32..0B33 ; ID_Start # Lo [2] ORIYA LETTER LA..ORIYA LETTER LLA
+0B35..0B39 ; ID_Start # Lo [5] ORIYA LETTER VA..ORIYA LETTER HA
+0B3D ; ID_Start # Lo ORIYA SIGN AVAGRAHA
+0B5C..0B5D ; ID_Start # Lo [2] ORIYA LETTER RRA..ORIYA LETTER RHA
+0B5F..0B61 ; ID_Start # Lo [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL
+0B71 ; ID_Start # Lo ORIYA LETTER WA
+0B83 ; ID_Start # Lo TAMIL SIGN VISARGA
+0B85..0B8A ; ID_Start # Lo [6] TAMIL LETTER A..TAMIL LETTER UU
+0B8E..0B90 ; ID_Start # Lo [3] TAMIL LETTER E..TAMIL LETTER AI
+0B92..0B95 ; ID_Start # Lo [4] TAMIL LETTER O..TAMIL LETTER KA
+0B99..0B9A ; ID_Start # Lo [2] TAMIL LETTER NGA..TAMIL LETTER CA
+0B9C ; ID_Start # Lo TAMIL LETTER JA
+0B9E..0B9F ; ID_Start # Lo [2] TAMIL LETTER NYA..TAMIL LETTER TTA
+0BA3..0BA4 ; ID_Start # Lo [2] TAMIL LETTER NNA..TAMIL LETTER TA
+0BA8..0BAA ; ID_Start # Lo [3] TAMIL LETTER NA..TAMIL LETTER PA
+0BAE..0BB9 ; ID_Start # Lo [12] TAMIL LETTER MA..TAMIL LETTER HA
+0BD0 ; ID_Start # Lo TAMIL OM
+0C05..0C0C ; ID_Start # Lo [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L
+0C0E..0C10 ; ID_Start # Lo [3] TELUGU LETTER E..TELUGU LETTER AI
+0C12..0C28 ; ID_Start # Lo [23] TELUGU LETTER O..TELUGU LETTER NA
+0C2A..0C39 ; ID_Start # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA
+0C3D ; ID_Start # Lo TELUGU SIGN AVAGRAHA
+0C58..0C5A ; ID_Start # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA
+0C5D ; ID_Start # Lo TELUGU LETTER NAKAARA POLLU
+0C60..0C61 ; ID_Start # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL
+0C80 ; ID_Start # Lo KANNADA SIGN SPACING CANDRABINDU
+0C85..0C8C ; ID_Start # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L
+0C8E..0C90 ; ID_Start # Lo [3] KANNADA LETTER E..KANNADA LETTER AI
+0C92..0CA8 ; ID_Start # Lo [23] KANNADA LETTER O..KANNADA LETTER NA
+0CAA..0CB3 ; ID_Start # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA
+0CB5..0CB9 ; ID_Start # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA
+0CBD ; ID_Start # Lo KANNADA SIGN AVAGRAHA
+0CDD..0CDE ; ID_Start # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA
+0CE0..0CE1 ; ID_Start # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL
+0CF1..0CF2 ; ID_Start # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA
+0D04..0D0C ; ID_Start # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L
+0D0E..0D10 ; ID_Start # Lo [3] MALAYALAM LETTER E..MALAYALAM LETTER AI
+0D12..0D3A ; ID_Start # Lo [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA
+0D3D ; ID_Start # Lo MALAYALAM SIGN AVAGRAHA
+0D4E ; ID_Start # Lo MALAYALAM LETTER DOT REPH
+0D54..0D56 ; ID_Start # Lo [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL
+0D5F..0D61 ; ID_Start # Lo [3] MALAYALAM LETTER ARCHAIC II..MALAYALAM LETTER VOCALIC LL
+0D7A..0D7F ; ID_Start # Lo [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K
+0D85..0D96 ; ID_Start # Lo [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA
+0D9A..0DB1 ; ID_Start # Lo [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA
+0DB3..0DBB ; ID_Start # Lo [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA
+0DBD ; ID_Start # Lo SINHALA LETTER DANTAJA LAYANNA
+0DC0..0DC6 ; ID_Start # Lo [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA
+0E01..0E30 ; ID_Start # Lo [48] THAI CHARACTER KO KAI..THAI CHARACTER SARA A
+0E32..0E33 ; ID_Start # Lo [2] THAI CHARACTER SARA AA..THAI CHARACTER SARA AM
+0E40..0E45 ; ID_Start # Lo [6] THAI CHARACTER SARA E..THAI CHARACTER LAKKHANGYAO
+0E46 ; ID_Start # Lm THAI CHARACTER MAIYAMOK
+0E81..0E82 ; ID_Start # Lo [2] LAO LETTER KO..LAO LETTER KHO SUNG
+0E84 ; ID_Start # Lo LAO LETTER KHO TAM
+0E86..0E8A ; ID_Start # Lo [5] LAO LETTER PALI GHA..LAO LETTER SO TAM
+0E8C..0EA3 ; ID_Start # Lo [24] LAO LETTER PALI JHA..LAO LETTER LO LING
+0EA5 ; ID_Start # Lo LAO LETTER LO LOOT
+0EA7..0EB0 ; ID_Start # Lo [10] LAO LETTER WO..LAO VOWEL SIGN A
+0EB2..0EB3 ; ID_Start # Lo [2] LAO VOWEL SIGN AA..LAO VOWEL SIGN AM
+0EBD ; ID_Start # Lo LAO SEMIVOWEL SIGN NYO
+0EC0..0EC4 ; ID_Start # Lo [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI
+0EC6 ; ID_Start # Lm LAO KO LA
+0EDC..0EDF ; ID_Start # Lo [4] LAO HO NO..LAO LETTER KHMU NYO
+0F00 ; ID_Start # Lo TIBETAN SYLLABLE OM
+0F40..0F47 ; ID_Start # Lo [8] TIBETAN LETTER KA..TIBETAN LETTER JA
+0F49..0F6C ; ID_Start # Lo [36] TIBETAN LETTER NYA..TIBETAN LETTER RRA
+0F88..0F8C ; ID_Start # Lo [5] TIBETAN SIGN LCE TSA CAN..TIBETAN SIGN INVERTED MCHU CAN
+1000..102A ; ID_Start # Lo [43] MYANMAR LETTER KA..MYANMAR LETTER AU
+103F ; ID_Start # Lo MYANMAR LETTER GREAT SA
+1050..1055 ; ID_Start # Lo [6] MYANMAR LETTER SHA..MYANMAR LETTER VOCALIC LL
+105A..105D ; ID_Start # Lo [4] MYANMAR LETTER MON NGA..MYANMAR LETTER MON BBE
+1061 ; ID_Start # Lo MYANMAR LETTER SGAW KAREN SHA
+1065..1066 ; ID_Start # Lo [2] MYANMAR LETTER WESTERN PWO KAREN THA..MYANMAR LETTER WESTERN PWO KAREN PWA
+106E..1070 ; ID_Start # Lo [3] MYANMAR LETTER EASTERN PWO KAREN NNA..MYANMAR LETTER EASTERN PWO KAREN GHWA
+1075..1081 ; ID_Start # Lo [13] MYANMAR LETTER SHAN KA..MYANMAR LETTER SHAN HA
+108E ; ID_Start # Lo MYANMAR LETTER RUMAI PALAUNG FA
+10A0..10C5 ; ID_Start # L& [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE
+10C7 ; ID_Start # L& GEORGIAN CAPITAL LETTER YN
+10CD ; ID_Start # L& GEORGIAN CAPITAL LETTER AEN
+10D0..10FA ; ID_Start # L& [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN
+10FC ; ID_Start # Lm MODIFIER LETTER GEORGIAN NAR
+10FD..10FF ; ID_Start # L& [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN
+1100..1248 ; ID_Start # Lo [329] HANGUL CHOSEONG KIYEOK..ETHIOPIC SYLLABLE QWA
+124A..124D ; ID_Start # Lo [4] ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE
+1250..1256 ; ID_Start # Lo [7] ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO
+1258 ; ID_Start # Lo ETHIOPIC SYLLABLE QHWA
+125A..125D ; ID_Start # Lo [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE
+1260..1288 ; ID_Start # Lo [41] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA
+128A..128D ; ID_Start # Lo [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE
+1290..12B0 ; ID_Start # Lo [33] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA
+12B2..12B5 ; ID_Start # Lo [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE
+12B8..12BE ; ID_Start # Lo [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO
+12C0 ; ID_Start # Lo ETHIOPIC SYLLABLE KXWA
+12C2..12C5 ; ID_Start # Lo [4] ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE
+12C8..12D6 ; ID_Start # Lo [15] ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O
+12D8..1310 ; ID_Start # Lo [57] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA
+1312..1315 ; ID_Start # Lo [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE
+1318..135A ; ID_Start # Lo [67] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA
+1380..138F ; ID_Start # Lo [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE
+13A0..13F5 ; ID_Start # L& [86] CHEROKEE LETTER A..CHEROKEE LETTER MV
+13F8..13FD ; ID_Start # L& [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV
+1401..166C ; ID_Start # Lo [620] CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA
+166F..167F ; ID_Start # Lo [17] CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W
+1681..169A ; ID_Start # Lo [26] OGHAM LETTER BEITH..OGHAM LETTER PEITH
+16A0..16EA ; ID_Start # Lo [75] RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X
+16EE..16F0 ; ID_Start # Nl [3] RUNIC ARLAUG SYMBOL..RUNIC BELGTHOR SYMBOL
+16F1..16F8 ; ID_Start # Lo [8] RUNIC LETTER K..RUNIC LETTER FRANKS CASKET AESC
+1700..1711 ; ID_Start # Lo [18] TAGALOG LETTER A..TAGALOG LETTER HA
+171F..1731 ; ID_Start # Lo [19] TAGALOG LETTER ARCHAIC RA..HANUNOO LETTER HA
+1740..1751 ; ID_Start # Lo [18] BUHID LETTER A..BUHID LETTER HA
+1760..176C ; ID_Start # Lo [13] TAGBANWA LETTER A..TAGBANWA LETTER YA
+176E..1770 ; ID_Start # Lo [3] TAGBANWA LETTER LA..TAGBANWA LETTER SA
+1780..17B3 ; ID_Start # Lo [52] KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU
+17D7 ; ID_Start # Lm KHMER SIGN LEK TOO
+17DC ; ID_Start # Lo KHMER SIGN AVAKRAHASANYA
+1820..1842 ; ID_Start # Lo [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI
+1843 ; ID_Start # Lm MONGOLIAN LETTER TODO LONG VOWEL SIGN
+1844..1878 ; ID_Start # Lo [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS
+1880..1884 ; ID_Start # Lo [5] MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER ALI GALI INVERTED UBADAMA
+1885..1886 ; ID_Start # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA
+1887..18A8 ; ID_Start # Lo [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA
+18AA ; ID_Start # Lo MONGOLIAN LETTER MANCHU ALI GALI LHA
+18B0..18F5 ; ID_Start # Lo [70] CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S
+1900..191E ; ID_Start # Lo [31] LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER TRA
+1950..196D ; ID_Start # Lo [30] TAI LE LETTER KA..TAI LE LETTER AI
+1970..1974 ; ID_Start # Lo [5] TAI LE LETTER TONE-2..TAI LE LETTER TONE-6
+1980..19AB ; ID_Start # Lo [44] NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA
+19B0..19C9 ; ID_Start # Lo [26] NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2
+1A00..1A16 ; ID_Start # Lo [23] BUGINESE LETTER KA..BUGINESE LETTER HA
+1A20..1A54 ; ID_Start # Lo [53] TAI THAM LETTER HIGH KA..TAI THAM LETTER GREAT SA
+1AA7 ; ID_Start # Lm TAI THAM SIGN MAI YAMOK
+1B05..1B33 ; ID_Start # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA
+1B45..1B4C ; ID_Start # Lo [8] BALINESE LETTER KAF SASAK..BALINESE LETTER ARCHAIC JNYA
+1B83..1BA0 ; ID_Start # Lo [30] SUNDANESE LETTER A..SUNDANESE LETTER HA
+1BAE..1BAF ; ID_Start # Lo [2] SUNDANESE LETTER KHA..SUNDANESE LETTER SYA
+1BBA..1BE5 ; ID_Start # Lo [44] SUNDANESE AVAGRAHA..BATAK LETTER U
+1C00..1C23 ; ID_Start # Lo [36] LEPCHA LETTER KA..LEPCHA LETTER A
+1C4D..1C4F ; ID_Start # Lo [3] LEPCHA LETTER TTA..LEPCHA LETTER DDA
+1C5A..1C77 ; ID_Start # Lo [30] OL CHIKI LETTER LA..OL CHIKI LETTER OH
+1C78..1C7D ; ID_Start # Lm [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD
+1C80..1C8A ; ID_Start # L& [11] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER TJE
+1C90..1CBA ; ID_Start # L& [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN
+1CBD..1CBF ; ID_Start # L& [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
+1CE9..1CEC ; ID_Start # Lo [4] VEDIC SIGN ANUSVARA ANTARGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL
+1CEE..1CF3 ; ID_Start # Lo [6] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ROTATED ARDHAVISARGA
+1CF5..1CF6 ; ID_Start # Lo [2] VEDIC SIGN JIHVAMULIYA..VEDIC SIGN UPADHMANIYA
+1CFA ; ID_Start # Lo VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA
+1D00..1D2B ; ID_Start # L& [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL
+1D2C..1D6A ; ID_Start # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI
+1D6B..1D77 ; ID_Start # L& [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G
+1D78 ; ID_Start # Lm MODIFIER LETTER CYRILLIC EN
+1D79..1D9A ; ID_Start # L& [34] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK
+1D9B..1DBF ; ID_Start # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA
+1E00..1F15 ; ID_Start # L& [278] LATIN CAPITAL LETTER A WITH RING BELOW..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA
+1F18..1F1D ; ID_Start # L& [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F20..1F45 ; ID_Start # L& [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA
+1F48..1F4D ; ID_Start # L& [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F50..1F57 ; ID_Start # L& [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F59 ; ID_Start # L& GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B ; ID_Start # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D ; ID_Start # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F..1F7D ; ID_Start # L& [31] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER OMEGA WITH OXIA
+1F80..1FB4 ; ID_Start # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6..1FBC ; ID_Start # L& [7] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBE ; ID_Start # L& GREEK PROSGEGRAMMENI
+1FC2..1FC4 ; ID_Start # L& [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6..1FCC ; ID_Start # L& [7] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FD0..1FD3 ; ID_Start # L& [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6..1FDB ; ID_Start # L& [6] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH OXIA
+1FE0..1FEC ; ID_Start # L& [13] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA
+1FF2..1FF4 ; ID_Start # L& [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6..1FFC ; ID_Start # L& [7] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+2071 ; ID_Start # Lm SUPERSCRIPT LATIN SMALL LETTER I
+207F ; ID_Start # Lm SUPERSCRIPT LATIN SMALL LETTER N
+2090..209C ; ID_Start # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T
+2102 ; ID_Start # L& DOUBLE-STRUCK CAPITAL C
+2107 ; ID_Start # L& EULER CONSTANT
+210A..2113 ; ID_Start # L& [10] SCRIPT SMALL G..SCRIPT SMALL L
+2115 ; ID_Start # L& DOUBLE-STRUCK CAPITAL N
+2118 ; ID_Start # Sm SCRIPT CAPITAL P
+2119..211D ; ID_Start # L& [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R
+2124 ; ID_Start # L& DOUBLE-STRUCK CAPITAL Z
+2126 ; ID_Start # L& OHM SIGN
+2128 ; ID_Start # L& BLACK-LETTER CAPITAL Z
+212A..212D ; ID_Start # L& [4] KELVIN SIGN..BLACK-LETTER CAPITAL C
+212E ; ID_Start # So ESTIMATED SYMBOL
+212F..2134 ; ID_Start # L& [6] SCRIPT SMALL E..SCRIPT SMALL O
+2135..2138 ; ID_Start # Lo [4] ALEF SYMBOL..DALET SYMBOL
+2139 ; ID_Start # L& INFORMATION SOURCE
+213C..213F ; ID_Start # L& [4] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK CAPITAL PI
+2145..2149 ; ID_Start # L& [5] DOUBLE-STRUCK ITALIC CAPITAL D..DOUBLE-STRUCK ITALIC SMALL J
+214E ; ID_Start # L& TURNED SMALL F
+2160..2182 ; ID_Start # Nl [35] ROMAN NUMERAL ONE..ROMAN NUMERAL TEN THOUSAND
+2183..2184 ; ID_Start # L& [2] ROMAN NUMERAL REVERSED ONE HUNDRED..LATIN SMALL LETTER REVERSED C
+2185..2188 ; ID_Start # Nl [4] ROMAN NUMERAL SIX LATE FORM..ROMAN NUMERAL ONE HUNDRED THOUSAND
+2C00..2C7B ; ID_Start # L& [124] GLAGOLITIC CAPITAL LETTER AZU..LATIN LETTER SMALL CAPITAL TURNED E
+2C7C..2C7D ; ID_Start # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V
+2C7E..2CE4 ; ID_Start # L& [103] LATIN CAPITAL LETTER S WITH SWASH TAIL..COPTIC SYMBOL KAI
+2CEB..2CEE ; ID_Start # L& [4] COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI..COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA
+2CF2..2CF3 ; ID_Start # L& [2] COPTIC CAPITAL LETTER BOHAIRIC KHEI..COPTIC SMALL LETTER BOHAIRIC KHEI
+2D00..2D25 ; ID_Start # L& [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE
+2D27 ; ID_Start # L& GEORGIAN SMALL LETTER YN
+2D2D ; ID_Start # L& GEORGIAN SMALL LETTER AEN
+2D30..2D67 ; ID_Start # Lo [56] TIFINAGH LETTER YA..TIFINAGH LETTER YO
+2D6F ; ID_Start # Lm TIFINAGH MODIFIER LETTER LABIALIZATION MARK
+2D80..2D96 ; ID_Start # Lo [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE
+2DA0..2DA6 ; ID_Start # Lo [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO
+2DA8..2DAE ; ID_Start # Lo [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO
+2DB0..2DB6 ; ID_Start # Lo [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO
+2DB8..2DBE ; ID_Start # Lo [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO
+2DC0..2DC6 ; ID_Start # Lo [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO
+2DC8..2DCE ; ID_Start # Lo [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO
+2DD0..2DD6 ; ID_Start # Lo [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO
+2DD8..2DDE ; ID_Start # Lo [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO
+3005 ; ID_Start # Lm IDEOGRAPHIC ITERATION MARK
+3006 ; ID_Start # Lo IDEOGRAPHIC CLOSING MARK
+3007 ; ID_Start # Nl IDEOGRAPHIC NUMBER ZERO
+3021..3029 ; ID_Start # Nl [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE
+3031..3035 ; ID_Start # Lm [5] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT MARK LOWER HALF
+3038..303A ; ID_Start # Nl [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY
+303B ; ID_Start # Lm VERTICAL IDEOGRAPHIC ITERATION MARK
+303C ; ID_Start # Lo MASU MARK
+3041..3096 ; ID_Start # Lo [86] HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMALL KE
+309B..309C ; ID_Start # Sk [2] KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+309D..309E ; ID_Start # Lm [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK
+309F ; ID_Start # Lo HIRAGANA DIGRAPH YORI
+30A1..30FA ; ID_Start # Lo [90] KATAKANA LETTER SMALL A..KATAKANA LETTER VO
+30FC..30FE ; ID_Start # Lm [3] KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK
+30FF ; ID_Start # Lo KATAKANA DIGRAPH KOTO
+3105..312F ; ID_Start # Lo [43] BOPOMOFO LETTER B..BOPOMOFO LETTER NN
+3131..318E ; ID_Start # Lo [94] HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE
+31A0..31BF ; ID_Start # Lo [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH
+31F0..31FF ; ID_Start # Lo [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO
+3400..4DBF ; ID_Start # Lo [6592] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DBF
+4E00..A014 ; ID_Start # Lo [21013] CJK UNIFIED IDEOGRAPH-4E00..YI SYLLABLE E
+A015 ; ID_Start # Lm YI SYLLABLE WU
+A016..A48C ; ID_Start # Lo [1143] YI SYLLABLE BIT..YI SYLLABLE YYR
+A4D0..A4F7 ; ID_Start # Lo [40] LISU LETTER BA..LISU LETTER OE
+A4F8..A4FD ; ID_Start # Lm [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU
+A500..A60B ; ID_Start # Lo [268] VAI SYLLABLE EE..VAI SYLLABLE NG
+A60C ; ID_Start # Lm VAI SYLLABLE LENGTHENER
+A610..A61F ; ID_Start # Lo [16] VAI SYLLABLE NDOLE FA..VAI SYMBOL JONG
+A62A..A62B ; ID_Start # Lo [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO
+A640..A66D ; ID_Start # L& [46] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER DOUBLE MONOCULAR O
+A66E ; ID_Start # Lo CYRILLIC LETTER MULTIOCULAR O
+A67F ; ID_Start # Lm CYRILLIC PAYEROK
+A680..A69B ; ID_Start # L& [28] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER CROSSED O
+A69C..A69D ; ID_Start # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN
+A6A0..A6E5 ; ID_Start # Lo [70] BAMUM LETTER A..BAMUM LETTER KI
+A6E6..A6EF ; ID_Start # Nl [10] BAMUM LETTER MO..BAMUM LETTER KOGHOM
+A717..A71F ; ID_Start # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK
+A722..A76F ; ID_Start # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CON
+A770 ; ID_Start # Lm MODIFIER LETTER US
+A771..A787 ; ID_Start # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T
+A788 ; ID_Start # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT
+A78B..A78E ; ID_Start # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT
+A78F ; ID_Start # Lo LATIN LETTER SINOLOGICAL DOT
+A790..A7CD ; ID_Start # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE
+A7D0..A7D1 ; ID_Start # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G
+A7D3 ; ID_Start # L& LATIN SMALL LETTER DOUBLE THORN
+A7D5..A7DC ; ID_Start # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE
+A7F2..A7F4 ; ID_Start # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q
+A7F5..A7F6 ; ID_Start # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H
+A7F7 ; ID_Start # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I
+A7F8..A7F9 ; ID_Start # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE
+A7FA ; ID_Start # L& LATIN LETTER SMALL CAPITAL TURNED M
+A7FB..A801 ; ID_Start # Lo [7] LATIN EPIGRAPHIC LETTER REVERSED F..SYLOTI NAGRI LETTER I
+A803..A805 ; ID_Start # Lo [3] SYLOTI NAGRI LETTER U..SYLOTI NAGRI LETTER O
+A807..A80A ; ID_Start # Lo [4] SYLOTI NAGRI LETTER KO..SYLOTI NAGRI LETTER GHO
+A80C..A822 ; ID_Start # Lo [23] SYLOTI NAGRI LETTER CO..SYLOTI NAGRI LETTER HO
+A840..A873 ; ID_Start # Lo [52] PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU
+A882..A8B3 ; ID_Start # Lo [50] SAURASHTRA LETTER A..SAURASHTRA LETTER LLA
+A8F2..A8F7 ; ID_Start # Lo [6] DEVANAGARI SIGN SPACING CANDRABINDU..DEVANAGARI SIGN CANDRABINDU AVAGRAHA
+A8FB ; ID_Start # Lo DEVANAGARI HEADSTROKE
+A8FD..A8FE ; ID_Start # Lo [2] DEVANAGARI JAIN OM..DEVANAGARI LETTER AY
+A90A..A925 ; ID_Start # Lo [28] KAYAH LI LETTER KA..KAYAH LI LETTER OO
+A930..A946 ; ID_Start # Lo [23] REJANG LETTER KA..REJANG LETTER A
+A960..A97C ; ID_Start # Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH
+A984..A9B2 ; ID_Start # Lo [47] JAVANESE LETTER A..JAVANESE LETTER HA
+A9CF ; ID_Start # Lm JAVANESE PANGRANGKEP
+A9E0..A9E4 ; ID_Start # Lo [5] MYANMAR LETTER SHAN GHA..MYANMAR LETTER SHAN BHA
+A9E6 ; ID_Start # Lm MYANMAR MODIFIER LETTER SHAN REDUPLICATION
+A9E7..A9EF ; ID_Start # Lo [9] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING NNA
+A9FA..A9FE ; ID_Start # Lo [5] MYANMAR LETTER TAI LAING LLA..MYANMAR LETTER TAI LAING BHA
+AA00..AA28 ; ID_Start # Lo [41] CHAM LETTER A..CHAM LETTER HA
+AA40..AA42 ; ID_Start # Lo [3] CHAM LETTER FINAL K..CHAM LETTER FINAL NG
+AA44..AA4B ; ID_Start # Lo [8] CHAM LETTER FINAL CH..CHAM LETTER FINAL SS
+AA60..AA6F ; ID_Start # Lo [16] MYANMAR LETTER KHAMTI GA..MYANMAR LETTER KHAMTI FA
+AA70 ; ID_Start # Lm MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION
+AA71..AA76 ; ID_Start # Lo [6] MYANMAR LETTER KHAMTI XA..MYANMAR LOGOGRAM KHAMTI HM
+AA7A ; ID_Start # Lo MYANMAR LETTER AITON RA
+AA7E..AAAF ; ID_Start # Lo [50] MYANMAR LETTER SHWE PALAUNG CHA..TAI VIET LETTER HIGH O
+AAB1 ; ID_Start # Lo TAI VIET VOWEL AA
+AAB5..AAB6 ; ID_Start # Lo [2] TAI VIET VOWEL E..TAI VIET VOWEL O
+AAB9..AABD ; ID_Start # Lo [5] TAI VIET VOWEL UEA..TAI VIET VOWEL AN
+AAC0 ; ID_Start # Lo TAI VIET TONE MAI NUENG
+AAC2 ; ID_Start # Lo TAI VIET TONE MAI SONG
+AADB..AADC ; ID_Start # Lo [2] TAI VIET SYMBOL KON..TAI VIET SYMBOL NUENG
+AADD ; ID_Start # Lm TAI VIET SYMBOL SAM
+AAE0..AAEA ; ID_Start # Lo [11] MEETEI MAYEK LETTER E..MEETEI MAYEK LETTER SSA
+AAF2 ; ID_Start # Lo MEETEI MAYEK ANJI
+AAF3..AAF4 ; ID_Start # Lm [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK
+AB01..AB06 ; ID_Start # Lo [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO
+AB09..AB0E ; ID_Start # Lo [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO
+AB11..AB16 ; ID_Start # Lo [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO
+AB20..AB26 ; ID_Start # Lo [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO
+AB28..AB2E ; ID_Start # Lo [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO
+AB30..AB5A ; ID_Start # L& [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG
+AB5C..AB5F ; ID_Start # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK
+AB60..AB68 ; ID_Start # L& [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE
+AB69 ; ID_Start # Lm MODIFIER LETTER SMALL TURNED W
+AB70..ABBF ; ID_Start # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA
+ABC0..ABE2 ; ID_Start # Lo [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM
+AC00..D7A3 ; ID_Start # Lo [11172] HANGUL SYLLABLE GA..HANGUL SYLLABLE HIH
+D7B0..D7C6 ; ID_Start # Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E
+D7CB..D7FB ; ID_Start # Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH
+F900..FA6D ; ID_Start # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D
+FA70..FAD9 ; ID_Start # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9
+FB00..FB06 ; ID_Start # L& [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST
+FB13..FB17 ; ID_Start # L& [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH
+FB1D ; ID_Start # Lo HEBREW LETTER YOD WITH HIRIQ
+FB1F..FB28 ; ID_Start # Lo [10] HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER WIDE TAV
+FB2A..FB36 ; ID_Start # Lo [13] HEBREW LETTER SHIN WITH SHIN DOT..HEBREW LETTER ZAYIN WITH DAGESH
+FB38..FB3C ; ID_Start # Lo [5] HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH
+FB3E ; ID_Start # Lo HEBREW LETTER MEM WITH DAGESH
+FB40..FB41 ; ID_Start # Lo [2] HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH
+FB43..FB44 ; ID_Start # Lo [2] HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH
+FB46..FBB1 ; ID_Start # Lo [108] HEBREW LETTER TSADI WITH DAGESH..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM
+FBD3..FD3D ; ID_Start # Lo [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM
+FD50..FD8F ; ID_Start # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM
+FD92..FDC7 ; ID_Start # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM
+FDF0..FDFB ; ID_Start # Lo [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU
+FE70..FE74 ; ID_Start # Lo [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN ISOLATED FORM
+FE76..FEFC ; ID_Start # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM
+FF21..FF3A ; ID_Start # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z
+FF41..FF5A ; ID_Start # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z
+FF66..FF6F ; ID_Start # Lo [10] HALFWIDTH KATAKANA LETTER WO..HALFWIDTH KATAKANA LETTER SMALL TU
+FF70 ; ID_Start # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK
+FF71..FF9D ; ID_Start # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAKANA LETTER N
+FF9E..FF9F ; ID_Start # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
+FFA0..FFBE ; ID_Start # Lo [31] HALFWIDTH HANGUL FILLER..HALFWIDTH HANGUL LETTER HIEUH
+FFC2..FFC7 ; ID_Start # Lo [6] HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E
+FFCA..FFCF ; ID_Start # Lo [6] HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE
+FFD2..FFD7 ; ID_Start # Lo [6] HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU
+FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I
+10000..1000B ; ID_Start # Lo [12] LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE
+1000D..10026 ; ID_Start # Lo [26] LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO
+10028..1003A ; ID_Start # Lo [19] LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO
+1003C..1003D ; ID_Start # Lo [2] LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE
+1003F..1004D ; ID_Start # Lo [15] LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO
+10050..1005D ; ID_Start # Lo [14] LINEAR B SYMBOL B018..LINEAR B SYMBOL B089
+10080..100FA ; ID_Start # Lo [123] LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305
+10140..10174 ; ID_Start # Nl [53] GREEK ACROPHONIC ATTIC ONE QUARTER..GREEK ACROPHONIC STRATIAN FIFTY MNAS
+10280..1029C ; ID_Start # Lo [29] LYCIAN LETTER A..LYCIAN LETTER X
+102A0..102D0 ; ID_Start # Lo [49] CARIAN LETTER A..CARIAN LETTER UUU3
+10300..1031F ; ID_Start # Lo [32] OLD ITALIC LETTER A..OLD ITALIC LETTER ESS
+1032D..10340 ; ID_Start # Lo [20] OLD ITALIC LETTER YE..GOTHIC LETTER PAIRTHRA
+10341 ; ID_Start # Nl GOTHIC LETTER NINETY
+10342..10349 ; ID_Start # Lo [8] GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL
+1034A ; ID_Start # Nl GOTHIC LETTER NINE HUNDRED
+10350..10375 ; ID_Start # Lo [38] OLD PERMIC LETTER AN..OLD PERMIC LETTER IA
+10380..1039D ; ID_Start # Lo [30] UGARITIC LETTER ALPA..UGARITIC LETTER SSU
+103A0..103C3 ; ID_Start # Lo [36] OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA
+103C8..103CF ; ID_Start # Lo [8] OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH
+103D1..103D5 ; ID_Start # Nl [5] OLD PERSIAN NUMBER ONE..OLD PERSIAN NUMBER HUNDRED
+10400..1044F ; ID_Start # L& [80] DESERET CAPITAL LETTER LONG I..DESERET SMALL LETTER EW
+10450..1049D ; ID_Start # Lo [78] SHAVIAN LETTER PEEP..OSMANYA LETTER OO
+104B0..104D3 ; ID_Start # L& [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA
+104D8..104FB ; ID_Start # L& [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA
+10500..10527 ; ID_Start # Lo [40] ELBASAN LETTER A..ELBASAN LETTER KHE
+10530..10563 ; ID_Start # Lo [52] CAUCASIAN ALBANIAN LETTER ALT..CAUCASIAN ALBANIAN LETTER KIW
+10570..1057A ; ID_Start # L& [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA
+1057C..1058A ; ID_Start # L& [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE
+1058C..10592 ; ID_Start # L& [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE
+10594..10595 ; ID_Start # L& [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE
+10597..105A1 ; ID_Start # L& [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA
+105A3..105B1 ; ID_Start # L& [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE
+105B3..105B9 ; ID_Start # L& [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE
+105BB..105BC ; ID_Start # L& [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE
+105C0..105F3 ; ID_Start # Lo [52] TODHRI LETTER A..TODHRI LETTER OO
+10600..10736 ; ID_Start # Lo [311] LINEAR A SIGN AB001..LINEAR A SIGN A664
+10740..10755 ; ID_Start # Lo [22] LINEAR A SIGN A701 A..LINEAR A SIGN A732 JE
+10760..10767 ; ID_Start # Lo [8] LINEAR A SIGN A800..LINEAR A SIGN A807
+10780..10785 ; ID_Start # Lm [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK
+10787..107B0 ; ID_Start # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK
+107B2..107BA ; ID_Start # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL
+10800..10805 ; ID_Start # Lo [6] CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA
+10808 ; ID_Start # Lo CYPRIOT SYLLABLE JO
+1080A..10835 ; ID_Start # Lo [44] CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO
+10837..10838 ; ID_Start # Lo [2] CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE
+1083C ; ID_Start # Lo CYPRIOT SYLLABLE ZA
+1083F..10855 ; ID_Start # Lo [23] CYPRIOT SYLLABLE ZO..IMPERIAL ARAMAIC LETTER TAW
+10860..10876 ; ID_Start # Lo [23] PALMYRENE LETTER ALEPH..PALMYRENE LETTER TAW
+10880..1089E ; ID_Start # Lo [31] NABATAEAN LETTER FINAL ALEPH..NABATAEAN LETTER TAW
+108E0..108F2 ; ID_Start # Lo [19] HATRAN LETTER ALEPH..HATRAN LETTER QOPH
+108F4..108F5 ; ID_Start # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW
+10900..10915 ; ID_Start # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU
+10920..10939 ; ID_Start # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C
+10980..109B7 ; ID_Start # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA
+109BE..109BF ; ID_Start # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN
+10A00 ; ID_Start # Lo KHAROSHTHI LETTER A
+10A10..10A13 ; ID_Start # Lo [4] KHAROSHTHI LETTER KA..KHAROSHTHI LETTER GHA
+10A15..10A17 ; ID_Start # Lo [3] KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA
+10A19..10A35 ; ID_Start # Lo [29] KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER VHA
+10A60..10A7C ; ID_Start # Lo [29] OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH
+10A80..10A9C ; ID_Start # Lo [29] OLD NORTH ARABIAN LETTER HEH..OLD NORTH ARABIAN LETTER ZAH
+10AC0..10AC7 ; ID_Start # Lo [8] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER WAW
+10AC9..10AE4 ; ID_Start # Lo [28] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER TAW
+10B00..10B35 ; ID_Start # Lo [54] AVESTAN LETTER A..AVESTAN LETTER HE
+10B40..10B55 ; ID_Start # Lo [22] INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW
+10B60..10B72 ; ID_Start # Lo [19] INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW
+10B80..10B91 ; ID_Start # Lo [18] PSALTER PAHLAVI LETTER ALEPH..PSALTER PAHLAVI LETTER TAW
+10C00..10C48 ; ID_Start # Lo [73] OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH
+10C80..10CB2 ; ID_Start # L& [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US
+10CC0..10CF2 ; ID_Start # L& [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US
+10D00..10D23 ; ID_Start # Lo [36] HANIFI ROHINGYA LETTER A..HANIFI ROHINGYA MARK NA KHONNA
+10D4A..10D4D ; ID_Start # Lo [4] GARAY VOWEL SIGN A..GARAY VOWEL SIGN EE
+10D4E ; ID_Start # Lm GARAY VOWEL LENGTH MARK
+10D4F ; ID_Start # Lo GARAY SUKUN
+10D50..10D65 ; ID_Start # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA
+10D6F ; ID_Start # Lm GARAY REDUPLICATION MARK
+10D70..10D85 ; ID_Start # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA
+10E80..10EA9 ; ID_Start # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET
+10EB0..10EB1 ; ID_Start # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE
+10EC2..10EC4 ; ID_Start # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW
+10F00..10F1C ; ID_Start # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL
+10F27 ; ID_Start # Lo OLD SOGDIAN LIGATURE AYIN-DALETH
+10F30..10F45 ; ID_Start # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN
+10F70..10F81 ; ID_Start # Lo [18] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER LESH
+10FB0..10FC4 ; ID_Start # Lo [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW
+10FE0..10FF6 ; ID_Start # Lo [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH
+11003..11037 ; ID_Start # Lo [53] BRAHMI SIGN JIHVAMULIYA..BRAHMI LETTER OLD TAMIL NNNA
+11071..11072 ; ID_Start # Lo [2] BRAHMI LETTER OLD TAMIL SHORT E..BRAHMI LETTER OLD TAMIL SHORT O
+11075 ; ID_Start # Lo BRAHMI LETTER OLD TAMIL LLA
+11083..110AF ; ID_Start # Lo [45] KAITHI LETTER A..KAITHI LETTER HA
+110D0..110E8 ; ID_Start # Lo [25] SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE
+11103..11126 ; ID_Start # Lo [36] CHAKMA LETTER AA..CHAKMA LETTER HAA
+11144 ; ID_Start # Lo CHAKMA LETTER LHAA
+11147 ; ID_Start # Lo CHAKMA LETTER VAA
+11150..11172 ; ID_Start # Lo [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA
+11176 ; ID_Start # Lo MAHAJANI LIGATURE SHRI
+11183..111B2 ; ID_Start # Lo [48] SHARADA LETTER A..SHARADA LETTER HA
+111C1..111C4 ; ID_Start # Lo [4] SHARADA SIGN AVAGRAHA..SHARADA OM
+111DA ; ID_Start # Lo SHARADA EKAM
+111DC ; ID_Start # Lo SHARADA HEADSTROKE
+11200..11211 ; ID_Start # Lo [18] KHOJKI LETTER A..KHOJKI LETTER JJA
+11213..1122B ; ID_Start # Lo [25] KHOJKI LETTER NYA..KHOJKI LETTER LLA
+1123F..11240 ; ID_Start # Lo [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I
+11280..11286 ; ID_Start # Lo [7] MULTANI LETTER A..MULTANI LETTER GA
+11288 ; ID_Start # Lo MULTANI LETTER GHA
+1128A..1128D ; ID_Start # Lo [4] MULTANI LETTER CA..MULTANI LETTER JJA
+1128F..1129D ; ID_Start # Lo [15] MULTANI LETTER NYA..MULTANI LETTER BA
+1129F..112A8 ; ID_Start # Lo [10] MULTANI LETTER BHA..MULTANI LETTER RHA
+112B0..112DE ; ID_Start # Lo [47] KHUDAWADI LETTER A..KHUDAWADI LETTER HA
+11305..1130C ; ID_Start # Lo [8] GRANTHA LETTER A..GRANTHA LETTER VOCALIC L
+1130F..11310 ; ID_Start # Lo [2] GRANTHA LETTER EE..GRANTHA LETTER AI
+11313..11328 ; ID_Start # Lo [22] GRANTHA LETTER OO..GRANTHA LETTER NA
+1132A..11330 ; ID_Start # Lo [7] GRANTHA LETTER PA..GRANTHA LETTER RA
+11332..11333 ; ID_Start # Lo [2] GRANTHA LETTER LA..GRANTHA LETTER LLA
+11335..11339 ; ID_Start # Lo [5] GRANTHA LETTER VA..GRANTHA LETTER HA
+1133D ; ID_Start # Lo GRANTHA SIGN AVAGRAHA
+11350 ; ID_Start # Lo GRANTHA OM
+1135D..11361 ; ID_Start # Lo [5] GRANTHA SIGN PLUTA..GRANTHA LETTER VOCALIC LL
+11380..11389 ; ID_Start # Lo [10] TULU-TIGALARI LETTER A..TULU-TIGALARI LETTER VOCALIC LL
+1138B ; ID_Start # Lo TULU-TIGALARI LETTER EE
+1138E ; ID_Start # Lo TULU-TIGALARI LETTER AI
+11390..113B5 ; ID_Start # Lo [38] TULU-TIGALARI LETTER OO..TULU-TIGALARI LETTER LLLA
+113B7 ; ID_Start # Lo TULU-TIGALARI SIGN AVAGRAHA
+113D1 ; ID_Start # Lo TULU-TIGALARI REPHA
+113D3 ; ID_Start # Lo TULU-TIGALARI SIGN PLUTA
+11400..11434 ; ID_Start # Lo [53] NEWA LETTER A..NEWA LETTER HA
+11447..1144A ; ID_Start # Lo [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI
+1145F..11461 ; ID_Start # Lo [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA
+11480..114AF ; ID_Start # Lo [48] TIRHUTA ANJI..TIRHUTA LETTER HA
+114C4..114C5 ; ID_Start # Lo [2] TIRHUTA SIGN AVAGRAHA..TIRHUTA GVANG
+114C7 ; ID_Start # Lo TIRHUTA OM
+11580..115AE ; ID_Start # Lo [47] SIDDHAM LETTER A..SIDDHAM LETTER HA
+115D8..115DB ; ID_Start # Lo [4] SIDDHAM LETTER THREE-CIRCLE ALTERNATE I..SIDDHAM LETTER ALTERNATE U
+11600..1162F ; ID_Start # Lo [48] MODI LETTER A..MODI LETTER LLA
+11644 ; ID_Start # Lo MODI SIGN HUVA
+11680..116AA ; ID_Start # Lo [43] TAKRI LETTER A..TAKRI LETTER RRA
+116B8 ; ID_Start # Lo TAKRI LETTER ARCHAIC KHA
+11700..1171A ; ID_Start # Lo [27] AHOM LETTER KA..AHOM LETTER ALTERNATE BA
+11740..11746 ; ID_Start # Lo [7] AHOM LETTER CA..AHOM LETTER LLA
+11800..1182B ; ID_Start # Lo [44] DOGRA LETTER A..DOGRA LETTER RRA
+118A0..118DF ; ID_Start # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO
+118FF..11906 ; ID_Start # Lo [8] WARANG CITI OM..DIVES AKURU LETTER E
+11909 ; ID_Start # Lo DIVES AKURU LETTER O
+1190C..11913 ; ID_Start # Lo [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA
+11915..11916 ; ID_Start # Lo [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA
+11918..1192F ; ID_Start # Lo [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA
+1193F ; ID_Start # Lo DIVES AKURU PREFIXED NASAL SIGN
+11941 ; ID_Start # Lo DIVES AKURU INITIAL RA
+119A0..119A7 ; ID_Start # Lo [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR
+119AA..119D0 ; ID_Start # Lo [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA
+119E1 ; ID_Start # Lo NANDINAGARI SIGN AVAGRAHA
+119E3 ; ID_Start # Lo NANDINAGARI HEADSTROKE
+11A00 ; ID_Start # Lo ZANABAZAR SQUARE LETTER A
+11A0B..11A32 ; ID_Start # Lo [40] ZANABAZAR SQUARE LETTER KA..ZANABAZAR SQUARE LETTER KSSA
+11A3A ; ID_Start # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA
+11A50 ; ID_Start # Lo SOYOMBO LETTER A
+11A5C..11A89 ; ID_Start # Lo [46] SOYOMBO LETTER KA..SOYOMBO CLUSTER-INITIAL LETTER SA
+11A9D ; ID_Start # Lo SOYOMBO MARK PLUTA
+11AB0..11AF8 ; ID_Start # Lo [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL
+11BC0..11BE0 ; ID_Start # Lo [33] SUNUWAR LETTER DEVI..SUNUWAR LETTER KLOKO
+11C00..11C08 ; ID_Start # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L
+11C0A..11C2E ; ID_Start # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA
+11C40 ; ID_Start # Lo BHAIKSUKI SIGN AVAGRAHA
+11C72..11C8F ; ID_Start # Lo [30] MARCHEN LETTER KA..MARCHEN LETTER A
+11D00..11D06 ; ID_Start # Lo [7] MASARAM GONDI LETTER A..MASARAM GONDI LETTER E
+11D08..11D09 ; ID_Start # Lo [2] MASARAM GONDI LETTER AI..MASARAM GONDI LETTER O
+11D0B..11D30 ; ID_Start # Lo [38] MASARAM GONDI LETTER AU..MASARAM GONDI LETTER TRA
+11D46 ; ID_Start # Lo MASARAM GONDI REPHA
+11D60..11D65 ; ID_Start # Lo [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU
+11D67..11D68 ; ID_Start # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI
+11D6A..11D89 ; ID_Start # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA
+11D98 ; ID_Start # Lo GUNJALA GONDI OM
+11EE0..11EF2 ; ID_Start # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA
+11F02 ; ID_Start # Lo KAWI SIGN REPHA
+11F04..11F10 ; ID_Start # Lo [13] KAWI LETTER A..KAWI LETTER O
+11F12..11F33 ; ID_Start # Lo [34] KAWI LETTER KA..KAWI LETTER JNYA
+11FB0 ; ID_Start # Lo LISU LETTER YHA
+12000..12399 ; ID_Start # Lo [922] CUNEIFORM SIGN A..CUNEIFORM SIGN U U
+12400..1246E ; ID_Start # Nl [111] CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM
+12480..12543 ; ID_Start # Lo [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU
+12F90..12FF0 ; ID_Start # Lo [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114
+13000..1342F ; ID_Start # Lo [1072] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH V011D
+13441..13446 ; ID_Start # Lo [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN
+13460..143FA ; ID_Start # Lo [3995] EGYPTIAN HIEROGLYPH-13460..EGYPTIAN HIEROGLYPH-143FA
+14400..14646 ; ID_Start # Lo [583] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A530
+16100..1611D ; ID_Start # Lo [30] GURUNG KHEMA LETTER A..GURUNG KHEMA LETTER SA
+16800..16A38 ; ID_Start # Lo [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ
+16A40..16A5E ; ID_Start # Lo [31] MRO LETTER TA..MRO LETTER TEK
+16A70..16ABE ; ID_Start # Lo [79] TANGSA LETTER OZ..TANGSA LETTER ZA
+16AD0..16AED ; ID_Start # Lo [30] BASSA VAH LETTER ENNI..BASSA VAH LETTER I
+16B00..16B2F ; ID_Start # Lo [48] PAHAWH HMONG VOWEL KEEB..PAHAWH HMONG CONSONANT CAU
+16B40..16B43 ; ID_Start # Lm [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM
+16B63..16B77 ; ID_Start # Lo [21] PAHAWH HMONG SIGN VOS LUB..PAHAWH HMONG SIGN CIM NRES TOS
+16B7D..16B8F ; ID_Start # Lo [19] PAHAWH HMONG CLAN SIGN TSHEEJ..PAHAWH HMONG CLAN SIGN VWJ
+16D40..16D42 ; ID_Start # Lm [3] KIRAT RAI SIGN ANUSVARA..KIRAT RAI SIGN VISARGA
+16D43..16D6A ; ID_Start # Lo [40] KIRAT RAI LETTER A..KIRAT RAI VOWEL SIGN AU
+16D6B..16D6C ; ID_Start # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT
+16E40..16E7F ; ID_Start # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y
+16F00..16F4A ; ID_Start # Lo [75] MIAO LETTER PA..MIAO LETTER RTE
+16F50 ; ID_Start # Lo MIAO LETTER NASALIZATION
+16F93..16F9F ; ID_Start # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8
+16FE0..16FE1 ; ID_Start # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK
+16FE3 ; ID_Start # Lm OLD CHINESE ITERATION MARK
+17000..187F7 ; ID_Start # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7
+18800..18CD5 ; ID_Start # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5
+18CFF..18D08 ; ID_Start # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08
+1AFF0..1AFF3 ; ID_Start # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5
+1AFF5..1AFFB ; ID_Start # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5
+1AFFD..1AFFE ; ID_Start # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8
+1B000..1B122 ; ID_Start # Lo [291] KATAKANA LETTER ARCHAIC E..KATAKANA LETTER ARCHAIC WU
+1B132 ; ID_Start # Lo HIRAGANA LETTER SMALL KO
+1B150..1B152 ; ID_Start # Lo [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO
+1B155 ; ID_Start # Lo KATAKANA LETTER SMALL KO
+1B164..1B167 ; ID_Start # Lo [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N
+1B170..1B2FB ; ID_Start # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB
+1BC00..1BC6A ; ID_Start # Lo [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M
+1BC70..1BC7C ; ID_Start # Lo [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK
+1BC80..1BC88 ; ID_Start # Lo [9] DUPLOYAN AFFIX HIGH ACUTE..DUPLOYAN AFFIX HIGH VERTICAL
+1BC90..1BC99 ; ID_Start # Lo [10] DUPLOYAN AFFIX LOW ACUTE..DUPLOYAN AFFIX LOW ARROW
+1D400..1D454 ; ID_Start # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G
+1D456..1D49C ; ID_Start # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A
+1D49E..1D49F ; ID_Start # L& [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D
+1D4A2 ; ID_Start # L& MATHEMATICAL SCRIPT CAPITAL G
+1D4A5..1D4A6 ; ID_Start # L& [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K
+1D4A9..1D4AC ; ID_Start # L& [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q
+1D4AE..1D4B9 ; ID_Start # L& [12] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D
+1D4BB ; ID_Start # L& MATHEMATICAL SCRIPT SMALL F
+1D4BD..1D4C3 ; ID_Start # L& [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N
+1D4C5..1D505 ; ID_Start # L& [65] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B
+1D507..1D50A ; ID_Start # L& [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G
+1D50D..1D514 ; ID_Start # L& [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q
+1D516..1D51C ; ID_Start # L& [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y
+1D51E..1D539 ; ID_Start # L& [28] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B
+1D53B..1D53E ; ID_Start # L& [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G
+1D540..1D544 ; ID_Start # L& [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M
+1D546 ; ID_Start # L& MATHEMATICAL DOUBLE-STRUCK CAPITAL O
+1D54A..1D550 ; ID_Start # L& [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y
+1D552..1D6A5 ; ID_Start # L& [340] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J
+1D6A8..1D6C0 ; ID_Start # L& [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA
+1D6C2..1D6DA ; ID_Start # L& [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA
+1D6DC..1D6FA ; ID_Start # L& [31] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL ITALIC CAPITAL OMEGA
+1D6FC..1D714 ; ID_Start # L& [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA
+1D716..1D734 ; ID_Start # L& [31] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA
+1D736..1D74E ; ID_Start # L& [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA
+1D750..1D76E ; ID_Start # L& [31] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA
+1D770..1D788 ; ID_Start # L& [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA
+1D78A..1D7A8 ; ID_Start # L& [31] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA
+1D7AA..1D7C2 ; ID_Start # L& [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA
+1D7C4..1D7CB ; ID_Start # L& [8] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD SMALL DIGAMMA
+1DF00..1DF09 ; ID_Start # L& [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK
+1DF0A ; ID_Start # Lo LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK
+1DF0B..1DF1E ; ID_Start # L& [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL
+1DF25..1DF2A ; ID_Start # L& [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK
+1E030..1E06D ; ID_Start # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE
+1E100..1E12C ; ID_Start # Lo [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W
+1E137..1E13D ; ID_Start # Lm [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER
+1E14E ; ID_Start # Lo NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ
+1E290..1E2AD ; ID_Start # Lo [30] TOTO LETTER PA..TOTO LETTER A
+1E2C0..1E2EB ; ID_Start # Lo [44] WANCHO LETTER AA..WANCHO LETTER YIH
+1E4D0..1E4EA ; ID_Start # Lo [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL
+1E4EB ; ID_Start # Lm NAG MUNDARI SIGN OJOD
+1E5D0..1E5ED ; ID_Start # Lo [30] OL ONAL LETTER O..OL ONAL LETTER EG
+1E5F0 ; ID_Start # Lo OL ONAL SIGN HODDOND
+1E7E0..1E7E6 ; ID_Start # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO
+1E7E8..1E7EB ; ID_Start # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE
+1E7ED..1E7EE ; ID_Start # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE
+1E7F0..1E7FE ; ID_Start # Lo [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE
+1E800..1E8C4 ; ID_Start # Lo [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON
+1E900..1E943 ; ID_Start # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA
+1E94B ; ID_Start # Lm ADLAM NASALIZATION MARK
+1EE00..1EE03 ; ID_Start # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL
+1EE05..1EE1F ; ID_Start # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF
+1EE21..1EE22 ; ID_Start # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM
+1EE24 ; ID_Start # Lo ARABIC MATHEMATICAL INITIAL HEH
+1EE27 ; ID_Start # Lo ARABIC MATHEMATICAL INITIAL HAH
+1EE29..1EE32 ; ID_Start # Lo [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF
+1EE34..1EE37 ; ID_Start # Lo [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH
+1EE39 ; ID_Start # Lo ARABIC MATHEMATICAL INITIAL DAD
+1EE3B ; ID_Start # Lo ARABIC MATHEMATICAL INITIAL GHAIN
+1EE42 ; ID_Start # Lo ARABIC MATHEMATICAL TAILED JEEM
+1EE47 ; ID_Start # Lo ARABIC MATHEMATICAL TAILED HAH
+1EE49 ; ID_Start # Lo ARABIC MATHEMATICAL TAILED YEH
+1EE4B ; ID_Start # Lo ARABIC MATHEMATICAL TAILED LAM
+1EE4D..1EE4F ; ID_Start # Lo [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN
+1EE51..1EE52 ; ID_Start # Lo [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF
+1EE54 ; ID_Start # Lo ARABIC MATHEMATICAL TAILED SHEEN
+1EE57 ; ID_Start # Lo ARABIC MATHEMATICAL TAILED KHAH
+1EE59 ; ID_Start # Lo ARABIC MATHEMATICAL TAILED DAD
+1EE5B ; ID_Start # Lo ARABIC MATHEMATICAL TAILED GHAIN
+1EE5D ; ID_Start # Lo ARABIC MATHEMATICAL TAILED DOTLESS NOON
+1EE5F ; ID_Start # Lo ARABIC MATHEMATICAL TAILED DOTLESS QAF
+1EE61..1EE62 ; ID_Start # Lo [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM
+1EE64 ; ID_Start # Lo ARABIC MATHEMATICAL STRETCHED HEH
+1EE67..1EE6A ; ID_Start # Lo [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF
+1EE6C..1EE72 ; ID_Start # Lo [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF
+1EE74..1EE77 ; ID_Start # Lo [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH
+1EE79..1EE7C ; ID_Start # Lo [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH
+1EE7E ; ID_Start # Lo ARABIC MATHEMATICAL STRETCHED DOTLESS FEH
+1EE80..1EE89 ; ID_Start # Lo [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH
+1EE8B..1EE9B ; ID_Start # Lo [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN
+1EEA1..1EEA3 ; ID_Start # Lo [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL
+1EEA5..1EEA9 ; ID_Start # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH
+1EEAB..1EEBB ; ID_Start # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN
+20000..2A6DF ; ID_Start # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF
+2A700..2B739 ; ID_Start # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739
+2B740..2B81D ; ID_Start # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D
+2B820..2CEA1 ; ID_Start # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1
+2CEB0..2EBE0 ; ID_Start # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0
+2EBF0..2EE5D ; ID_Start # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D
+2F800..2FA1D ; ID_Start # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D
+30000..3134A ; ID_Start # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A
+31350..323AF ; ID_Start # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF
+
+# Total code points: 141269
+
+# ================================================
+
+# Derived Property: ID_Continue
+# Characters that can continue an identifier.
+# Generated from:
+# ID_Start
+# + Mn + Mc + Nd + Pc
+# + Other_ID_Continue
+# - Pattern_Syntax
+# - Pattern_White_Space
+# NOTE: See UAX #31 for more information
+
+0030..0039 ; ID_Continue # Nd [10] DIGIT ZERO..DIGIT NINE
+0041..005A ; ID_Continue # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+005F ; ID_Continue # Pc LOW LINE
+0061..007A ; ID_Continue # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+00AA ; ID_Continue # Lo FEMININE ORDINAL INDICATOR
+00B5 ; ID_Continue # L& MICRO SIGN
+00B7 ; ID_Continue # Po MIDDLE DOT
+00BA ; ID_Continue # Lo MASCULINE ORDINAL INDICATOR
+00C0..00D6 ; ID_Continue # L& [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS
+00D8..00F6 ; ID_Continue # L& [31] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER O WITH DIAERESIS
+00F8..01BA ; ID_Continue # L& [195] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL
+01BB ; ID_Continue # Lo LATIN LETTER TWO WITH STROKE
+01BC..01BF ; ID_Continue # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN
+01C0..01C3 ; ID_Continue # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK
+01C4..0293 ; ID_Continue # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL
+0294 ; ID_Continue # Lo LATIN LETTER GLOTTAL STOP
+0295..02AF ; ID_Continue # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL
+02B0..02C1 ; ID_Continue # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP
+02C6..02D1 ; ID_Continue # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON
+02E0..02E4 ; ID_Continue # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP
+02EC ; ID_Continue # Lm MODIFIER LETTER VOICING
+02EE ; ID_Continue # Lm MODIFIER LETTER DOUBLE APOSTROPHE
+0300..036F ; ID_Continue # Mn [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X
+0370..0373 ; ID_Continue # L& [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI
+0374 ; ID_Continue # Lm GREEK NUMERAL SIGN
+0376..0377 ; ID_Continue # L& [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA
+037A ; ID_Continue # Lm GREEK YPOGEGRAMMENI
+037B..037D ; ID_Continue # L& [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL
+037F ; ID_Continue # L& GREEK CAPITAL LETTER YOT
+0386 ; ID_Continue # L& GREEK CAPITAL LETTER ALPHA WITH TONOS
+0387 ; ID_Continue # Po GREEK ANO TELEIA
+0388..038A ; ID_Continue # L& [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS
+038C ; ID_Continue # L& GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E..03A1 ; ID_Continue # L& [20] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER RHO
+03A3..03F5 ; ID_Continue # L& [83] GREEK CAPITAL LETTER SIGMA..GREEK LUNATE EPSILON SYMBOL
+03F7..0481 ; ID_Continue # L& [139] GREEK CAPITAL LETTER SHO..CYRILLIC SMALL LETTER KOPPA
+0483..0487 ; ID_Continue # Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE
+048A..052F ; ID_Continue # L& [166] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER EL WITH DESCENDER
+0531..0556 ; ID_Continue # L& [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH
+0559 ; ID_Continue # Lm ARMENIAN MODIFIER LETTER LEFT HALF RING
+0560..0588 ; ID_Continue # L& [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE
+0591..05BD ; ID_Continue # Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG
+05BF ; ID_Continue # Mn HEBREW POINT RAFE
+05C1..05C2 ; ID_Continue # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT
+05C4..05C5 ; ID_Continue # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT
+05C7 ; ID_Continue # Mn HEBREW POINT QAMATS QATAN
+05D0..05EA ; ID_Continue # Lo [27] HEBREW LETTER ALEF..HEBREW LETTER TAV
+05EF..05F2 ; ID_Continue # Lo [4] HEBREW YOD TRIANGLE..HEBREW LIGATURE YIDDISH DOUBLE YOD
+0610..061A ; ID_Continue # Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA
+0620..063F ; ID_Continue # Lo [32] ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE
+0640 ; ID_Continue # Lm ARABIC TATWEEL
+0641..064A ; ID_Continue # Lo [10] ARABIC LETTER FEH..ARABIC LETTER YEH
+064B..065F ; ID_Continue # Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW
+0660..0669 ; ID_Continue # Nd [10] ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE
+066E..066F ; ID_Continue # Lo [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF
+0670 ; ID_Continue # Mn ARABIC LETTER SUPERSCRIPT ALEF
+0671..06D3 ; ID_Continue # Lo [99] ARABIC LETTER ALEF WASLA..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE
+06D5 ; ID_Continue # Lo ARABIC LETTER AE
+06D6..06DC ; ID_Continue # Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN
+06DF..06E4 ; ID_Continue # Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA
+06E5..06E6 ; ID_Continue # Lm [2] ARABIC SMALL WAW..ARABIC SMALL YEH
+06E7..06E8 ; ID_Continue # Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON
+06EA..06ED ; ID_Continue # Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM
+06EE..06EF ; ID_Continue # Lo [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V
+06F0..06F9 ; ID_Continue # Nd [10] EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE
+06FA..06FC ; ID_Continue # Lo [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW
+06FF ; ID_Continue # Lo ARABIC LETTER HEH WITH INVERTED V
+0710 ; ID_Continue # Lo SYRIAC LETTER ALAPH
+0711 ; ID_Continue # Mn SYRIAC LETTER SUPERSCRIPT ALAPH
+0712..072F ; ID_Continue # Lo [30] SYRIAC LETTER BETH..SYRIAC LETTER PERSIAN DHALATH
+0730..074A ; ID_Continue # Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH
+074D..07A5 ; ID_Continue # Lo [89] SYRIAC LETTER SOGDIAN ZHAIN..THAANA LETTER WAAVU
+07A6..07B0 ; ID_Continue # Mn [11] THAANA ABAFILI..THAANA SUKUN
+07B1 ; ID_Continue # Lo THAANA LETTER NAA
+07C0..07C9 ; ID_Continue # Nd [10] NKO DIGIT ZERO..NKO DIGIT NINE
+07CA..07EA ; ID_Continue # Lo [33] NKO LETTER A..NKO LETTER JONA RA
+07EB..07F3 ; ID_Continue # Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE
+07F4..07F5 ; ID_Continue # Lm [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE
+07FA ; ID_Continue # Lm NKO LAJANYALAN
+07FD ; ID_Continue # Mn NKO DANTAYALAN
+0800..0815 ; ID_Continue # Lo [22] SAMARITAN LETTER ALAF..SAMARITAN LETTER TAAF
+0816..0819 ; ID_Continue # Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH
+081A ; ID_Continue # Lm SAMARITAN MODIFIER LETTER EPENTHETIC YUT
+081B..0823 ; ID_Continue # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A
+0824 ; ID_Continue # Lm SAMARITAN MODIFIER LETTER SHORT A
+0825..0827 ; ID_Continue # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U
+0828 ; ID_Continue # Lm SAMARITAN MODIFIER LETTER I
+0829..082D ; ID_Continue # Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA
+0840..0858 ; ID_Continue # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN
+0859..085B ; ID_Continue # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK
+0860..086A ; ID_Continue # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA
+0870..0887 ; ID_Continue # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT
+0889..088E ; ID_Continue # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL
+0897..089F ; ID_Continue # Mn [9] ARABIC PEPET..ARABIC HALF MADDA OVER MADDA
+08A0..08C8 ; ID_Continue # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF
+08C9 ; ID_Continue # Lm ARABIC SMALL FARSI YEH
+08CA..08E1 ; ID_Continue # Mn [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA
+08E3..0902 ; ID_Continue # Mn [32] ARABIC TURNED DAMMA BELOW..DEVANAGARI SIGN ANUSVARA
+0903 ; ID_Continue # Mc DEVANAGARI SIGN VISARGA
+0904..0939 ; ID_Continue # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA
+093A ; ID_Continue # Mn DEVANAGARI VOWEL SIGN OE
+093B ; ID_Continue # Mc DEVANAGARI VOWEL SIGN OOE
+093C ; ID_Continue # Mn DEVANAGARI SIGN NUKTA
+093D ; ID_Continue # Lo DEVANAGARI SIGN AVAGRAHA
+093E..0940 ; ID_Continue # Mc [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II
+0941..0948 ; ID_Continue # Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI
+0949..094C ; ID_Continue # Mc [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU
+094D ; ID_Continue # Mn DEVANAGARI SIGN VIRAMA
+094E..094F ; ID_Continue # Mc [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW
+0950 ; ID_Continue # Lo DEVANAGARI OM
+0951..0957 ; ID_Continue # Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE
+0958..0961 ; ID_Continue # Lo [10] DEVANAGARI LETTER QA..DEVANAGARI LETTER VOCALIC LL
+0962..0963 ; ID_Continue # Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL
+0966..096F ; ID_Continue # Nd [10] DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE
+0971 ; ID_Continue # Lm DEVANAGARI SIGN HIGH SPACING DOT
+0972..0980 ; ID_Continue # Lo [15] DEVANAGARI LETTER CANDRA A..BENGALI ANJI
+0981 ; ID_Continue # Mn BENGALI SIGN CANDRABINDU
+0982..0983 ; ID_Continue # Mc [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA
+0985..098C ; ID_Continue # Lo [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L
+098F..0990 ; ID_Continue # Lo [2] BENGALI LETTER E..BENGALI LETTER AI
+0993..09A8 ; ID_Continue # Lo [22] BENGALI LETTER O..BENGALI LETTER NA
+09AA..09B0 ; ID_Continue # Lo [7] BENGALI LETTER PA..BENGALI LETTER RA
+09B2 ; ID_Continue # Lo BENGALI LETTER LA
+09B6..09B9 ; ID_Continue # Lo [4] BENGALI LETTER SHA..BENGALI LETTER HA
+09BC ; ID_Continue # Mn BENGALI SIGN NUKTA
+09BD ; ID_Continue # Lo BENGALI SIGN AVAGRAHA
+09BE..09C0 ; ID_Continue # Mc [3] BENGALI VOWEL SIGN AA..BENGALI VOWEL SIGN II
+09C1..09C4 ; ID_Continue # Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR
+09C7..09C8 ; ID_Continue # Mc [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI
+09CB..09CC ; ID_Continue # Mc [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU
+09CD ; ID_Continue # Mn BENGALI SIGN VIRAMA
+09CE ; ID_Continue # Lo BENGALI LETTER KHANDA TA
+09D7 ; ID_Continue # Mc BENGALI AU LENGTH MARK
+09DC..09DD ; ID_Continue # Lo [2] BENGALI LETTER RRA..BENGALI LETTER RHA
+09DF..09E1 ; ID_Continue # Lo [3] BENGALI LETTER YYA..BENGALI LETTER VOCALIC LL
+09E2..09E3 ; ID_Continue # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL
+09E6..09EF ; ID_Continue # Nd [10] BENGALI DIGIT ZERO..BENGALI DIGIT NINE
+09F0..09F1 ; ID_Continue # Lo [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL
+09FC ; ID_Continue # Lo BENGALI LETTER VEDIC ANUSVARA
+09FE ; ID_Continue # Mn BENGALI SANDHI MARK
+0A01..0A02 ; ID_Continue # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI
+0A03 ; ID_Continue # Mc GURMUKHI SIGN VISARGA
+0A05..0A0A ; ID_Continue # Lo [6] GURMUKHI LETTER A..GURMUKHI LETTER UU
+0A0F..0A10 ; ID_Continue # Lo [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI
+0A13..0A28 ; ID_Continue # Lo [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA
+0A2A..0A30 ; ID_Continue # Lo [7] GURMUKHI LETTER PA..GURMUKHI LETTER RA
+0A32..0A33 ; ID_Continue # Lo [2] GURMUKHI LETTER LA..GURMUKHI LETTER LLA
+0A35..0A36 ; ID_Continue # Lo [2] GURMUKHI LETTER VA..GURMUKHI LETTER SHA
+0A38..0A39 ; ID_Continue # Lo [2] GURMUKHI LETTER SA..GURMUKHI LETTER HA
+0A3C ; ID_Continue # Mn GURMUKHI SIGN NUKTA
+0A3E..0A40 ; ID_Continue # Mc [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II
+0A41..0A42 ; ID_Continue # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU
+0A47..0A48 ; ID_Continue # Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI
+0A4B..0A4D ; ID_Continue # Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA
+0A51 ; ID_Continue # Mn GURMUKHI SIGN UDAAT
+0A59..0A5C ; ID_Continue # Lo [4] GURMUKHI LETTER KHHA..GURMUKHI LETTER RRA
+0A5E ; ID_Continue # Lo GURMUKHI LETTER FA
+0A66..0A6F ; ID_Continue # Nd [10] GURMUKHI DIGIT ZERO..GURMUKHI DIGIT NINE
+0A70..0A71 ; ID_Continue # Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK
+0A72..0A74 ; ID_Continue # Lo [3] GURMUKHI IRI..GURMUKHI EK ONKAR
+0A75 ; ID_Continue # Mn GURMUKHI SIGN YAKASH
+0A81..0A82 ; ID_Continue # Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA
+0A83 ; ID_Continue # Mc GUJARATI SIGN VISARGA
+0A85..0A8D ; ID_Continue # Lo [9] GUJARATI LETTER A..GUJARATI VOWEL CANDRA E
+0A8F..0A91 ; ID_Continue # Lo [3] GUJARATI LETTER E..GUJARATI VOWEL CANDRA O
+0A93..0AA8 ; ID_Continue # Lo [22] GUJARATI LETTER O..GUJARATI LETTER NA
+0AAA..0AB0 ; ID_Continue # Lo [7] GUJARATI LETTER PA..GUJARATI LETTER RA
+0AB2..0AB3 ; ID_Continue # Lo [2] GUJARATI LETTER LA..GUJARATI LETTER LLA
+0AB5..0AB9 ; ID_Continue # Lo [5] GUJARATI LETTER VA..GUJARATI LETTER HA
+0ABC ; ID_Continue # Mn GUJARATI SIGN NUKTA
+0ABD ; ID_Continue # Lo GUJARATI SIGN AVAGRAHA
+0ABE..0AC0 ; ID_Continue # Mc [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II
+0AC1..0AC5 ; ID_Continue # Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E
+0AC7..0AC8 ; ID_Continue # Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI
+0AC9 ; ID_Continue # Mc GUJARATI VOWEL SIGN CANDRA O
+0ACB..0ACC ; ID_Continue # Mc [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU
+0ACD ; ID_Continue # Mn GUJARATI SIGN VIRAMA
+0AD0 ; ID_Continue # Lo GUJARATI OM
+0AE0..0AE1 ; ID_Continue # Lo [2] GUJARATI LETTER VOCALIC RR..GUJARATI LETTER VOCALIC LL
+0AE2..0AE3 ; ID_Continue # Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL
+0AE6..0AEF ; ID_Continue # Nd [10] GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE
+0AF9 ; ID_Continue # Lo GUJARATI LETTER ZHA
+0AFA..0AFF ; ID_Continue # Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE
+0B01 ; ID_Continue # Mn ORIYA SIGN CANDRABINDU
+0B02..0B03 ; ID_Continue # Mc [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA
+0B05..0B0C ; ID_Continue # Lo [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L
+0B0F..0B10 ; ID_Continue # Lo [2] ORIYA LETTER E..ORIYA LETTER AI
+0B13..0B28 ; ID_Continue # Lo [22] ORIYA LETTER O..ORIYA LETTER NA
+0B2A..0B30 ; ID_Continue # Lo [7] ORIYA LETTER PA..ORIYA LETTER RA
+0B32..0B33 ; ID_Continue # Lo [2] ORIYA LETTER LA..ORIYA LETTER LLA
+0B35..0B39 ; ID_Continue # Lo [5] ORIYA LETTER VA..ORIYA LETTER HA
+0B3C ; ID_Continue # Mn ORIYA SIGN NUKTA
+0B3D ; ID_Continue # Lo ORIYA SIGN AVAGRAHA
+0B3E ; ID_Continue # Mc ORIYA VOWEL SIGN AA
+0B3F ; ID_Continue # Mn ORIYA VOWEL SIGN I
+0B40 ; ID_Continue # Mc ORIYA VOWEL SIGN II
+0B41..0B44 ; ID_Continue # Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR
+0B47..0B48 ; ID_Continue # Mc [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI
+0B4B..0B4C ; ID_Continue # Mc [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU
+0B4D ; ID_Continue # Mn ORIYA SIGN VIRAMA
+0B55..0B56 ; ID_Continue # Mn [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK
+0B57 ; ID_Continue # Mc ORIYA AU LENGTH MARK
+0B5C..0B5D ; ID_Continue # Lo [2] ORIYA LETTER RRA..ORIYA LETTER RHA
+0B5F..0B61 ; ID_Continue # Lo [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL
+0B62..0B63 ; ID_Continue # Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL
+0B66..0B6F ; ID_Continue # Nd [10] ORIYA DIGIT ZERO..ORIYA DIGIT NINE
+0B71 ; ID_Continue # Lo ORIYA LETTER WA
+0B82 ; ID_Continue # Mn TAMIL SIGN ANUSVARA
+0B83 ; ID_Continue # Lo TAMIL SIGN VISARGA
+0B85..0B8A ; ID_Continue # Lo [6] TAMIL LETTER A..TAMIL LETTER UU
+0B8E..0B90 ; ID_Continue # Lo [3] TAMIL LETTER E..TAMIL LETTER AI
+0B92..0B95 ; ID_Continue # Lo [4] TAMIL LETTER O..TAMIL LETTER KA
+0B99..0B9A ; ID_Continue # Lo [2] TAMIL LETTER NGA..TAMIL LETTER CA
+0B9C ; ID_Continue # Lo TAMIL LETTER JA
+0B9E..0B9F ; ID_Continue # Lo [2] TAMIL LETTER NYA..TAMIL LETTER TTA
+0BA3..0BA4 ; ID_Continue # Lo [2] TAMIL LETTER NNA..TAMIL LETTER TA
+0BA8..0BAA ; ID_Continue # Lo [3] TAMIL LETTER NA..TAMIL LETTER PA
+0BAE..0BB9 ; ID_Continue # Lo [12] TAMIL LETTER MA..TAMIL LETTER HA
+0BBE..0BBF ; ID_Continue # Mc [2] TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN I
+0BC0 ; ID_Continue # Mn TAMIL VOWEL SIGN II
+0BC1..0BC2 ; ID_Continue # Mc [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU
+0BC6..0BC8 ; ID_Continue # Mc [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI
+0BCA..0BCC ; ID_Continue # Mc [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU
+0BCD ; ID_Continue # Mn TAMIL SIGN VIRAMA
+0BD0 ; ID_Continue # Lo TAMIL OM
+0BD7 ; ID_Continue # Mc TAMIL AU LENGTH MARK
+0BE6..0BEF ; ID_Continue # Nd [10] TAMIL DIGIT ZERO..TAMIL DIGIT NINE
+0C00 ; ID_Continue # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE
+0C01..0C03 ; ID_Continue # Mc [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA
+0C04 ; ID_Continue # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE
+0C05..0C0C ; ID_Continue # Lo [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L
+0C0E..0C10 ; ID_Continue # Lo [3] TELUGU LETTER E..TELUGU LETTER AI
+0C12..0C28 ; ID_Continue # Lo [23] TELUGU LETTER O..TELUGU LETTER NA
+0C2A..0C39 ; ID_Continue # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA
+0C3C ; ID_Continue # Mn TELUGU SIGN NUKTA
+0C3D ; ID_Continue # Lo TELUGU SIGN AVAGRAHA
+0C3E..0C40 ; ID_Continue # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II
+0C41..0C44 ; ID_Continue # Mc [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR
+0C46..0C48 ; ID_Continue # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI
+0C4A..0C4D ; ID_Continue # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA
+0C55..0C56 ; ID_Continue # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK
+0C58..0C5A ; ID_Continue # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA
+0C5D ; ID_Continue # Lo TELUGU LETTER NAKAARA POLLU
+0C60..0C61 ; ID_Continue # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL
+0C62..0C63 ; ID_Continue # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL
+0C66..0C6F ; ID_Continue # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE
+0C80 ; ID_Continue # Lo KANNADA SIGN SPACING CANDRABINDU
+0C81 ; ID_Continue # Mn KANNADA SIGN CANDRABINDU
+0C82..0C83 ; ID_Continue # Mc [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA
+0C85..0C8C ; ID_Continue # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L
+0C8E..0C90 ; ID_Continue # Lo [3] KANNADA LETTER E..KANNADA LETTER AI
+0C92..0CA8 ; ID_Continue # Lo [23] KANNADA LETTER O..KANNADA LETTER NA
+0CAA..0CB3 ; ID_Continue # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA
+0CB5..0CB9 ; ID_Continue # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA
+0CBC ; ID_Continue # Mn KANNADA SIGN NUKTA
+0CBD ; ID_Continue # Lo KANNADA SIGN AVAGRAHA
+0CBE ; ID_Continue # Mc KANNADA VOWEL SIGN AA
+0CBF ; ID_Continue # Mn KANNADA VOWEL SIGN I
+0CC0..0CC4 ; ID_Continue # Mc [5] KANNADA VOWEL SIGN II..KANNADA VOWEL SIGN VOCALIC RR
+0CC6 ; ID_Continue # Mn KANNADA VOWEL SIGN E
+0CC7..0CC8 ; ID_Continue # Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI
+0CCA..0CCB ; ID_Continue # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO
+0CCC..0CCD ; ID_Continue # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA
+0CD5..0CD6 ; ID_Continue # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK
+0CDD..0CDE ; ID_Continue # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA
+0CE0..0CE1 ; ID_Continue # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL
+0CE2..0CE3 ; ID_Continue # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL
+0CE6..0CEF ; ID_Continue # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE
+0CF1..0CF2 ; ID_Continue # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA
+0CF3 ; ID_Continue # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT
+0D00..0D01 ; ID_Continue # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU
+0D02..0D03 ; ID_Continue # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA
+0D04..0D0C ; ID_Continue # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L
+0D0E..0D10 ; ID_Continue # Lo [3] MALAYALAM LETTER E..MALAYALAM LETTER AI
+0D12..0D3A ; ID_Continue # Lo [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA
+0D3B..0D3C ; ID_Continue # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA
+0D3D ; ID_Continue # Lo MALAYALAM SIGN AVAGRAHA
+0D3E..0D40 ; ID_Continue # Mc [3] MALAYALAM VOWEL SIGN AA..MALAYALAM VOWEL SIGN II
+0D41..0D44 ; ID_Continue # Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR
+0D46..0D48 ; ID_Continue # Mc [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI
+0D4A..0D4C ; ID_Continue # Mc [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU
+0D4D ; ID_Continue # Mn MALAYALAM SIGN VIRAMA
+0D4E ; ID_Continue # Lo MALAYALAM LETTER DOT REPH
+0D54..0D56 ; ID_Continue # Lo [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL
+0D57 ; ID_Continue # Mc MALAYALAM AU LENGTH MARK
+0D5F..0D61 ; ID_Continue # Lo [3] MALAYALAM LETTER ARCHAIC II..MALAYALAM LETTER VOCALIC LL
+0D62..0D63 ; ID_Continue # Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL
+0D66..0D6F ; ID_Continue # Nd [10] MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE
+0D7A..0D7F ; ID_Continue # Lo [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K
+0D81 ; ID_Continue # Mn SINHALA SIGN CANDRABINDU
+0D82..0D83 ; ID_Continue # Mc [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA
+0D85..0D96 ; ID_Continue # Lo [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA
+0D9A..0DB1 ; ID_Continue # Lo [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA
+0DB3..0DBB ; ID_Continue # Lo [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA
+0DBD ; ID_Continue # Lo SINHALA LETTER DANTAJA LAYANNA
+0DC0..0DC6 ; ID_Continue # Lo [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA
+0DCA ; ID_Continue # Mn SINHALA SIGN AL-LAKUNA
+0DCF..0DD1 ; ID_Continue # Mc [3] SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA
+0DD2..0DD4 ; ID_Continue # Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA
+0DD6 ; ID_Continue # Mn SINHALA VOWEL SIGN DIGA PAA-PILLA
+0DD8..0DDF ; ID_Continue # Mc [8] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN GAYANUKITTA
+0DE6..0DEF ; ID_Continue # Nd [10] SINHALA LITH DIGIT ZERO..SINHALA LITH DIGIT NINE
+0DF2..0DF3 ; ID_Continue # Mc [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA
+0E01..0E30 ; ID_Continue # Lo [48] THAI CHARACTER KO KAI..THAI CHARACTER SARA A
+0E31 ; ID_Continue # Mn THAI CHARACTER MAI HAN-AKAT
+0E32..0E33 ; ID_Continue # Lo [2] THAI CHARACTER SARA AA..THAI CHARACTER SARA AM
+0E34..0E3A ; ID_Continue # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU
+0E40..0E45 ; ID_Continue # Lo [6] THAI CHARACTER SARA E..THAI CHARACTER LAKKHANGYAO
+0E46 ; ID_Continue # Lm THAI CHARACTER MAIYAMOK
+0E47..0E4E ; ID_Continue # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN
+0E50..0E59 ; ID_Continue # Nd [10] THAI DIGIT ZERO..THAI DIGIT NINE
+0E81..0E82 ; ID_Continue # Lo [2] LAO LETTER KO..LAO LETTER KHO SUNG
+0E84 ; ID_Continue # Lo LAO LETTER KHO TAM
+0E86..0E8A ; ID_Continue # Lo [5] LAO LETTER PALI GHA..LAO LETTER SO TAM
+0E8C..0EA3 ; ID_Continue # Lo [24] LAO LETTER PALI JHA..LAO LETTER LO LING
+0EA5 ; ID_Continue # Lo LAO LETTER LO LOOT
+0EA7..0EB0 ; ID_Continue # Lo [10] LAO LETTER WO..LAO VOWEL SIGN A
+0EB1 ; ID_Continue # Mn LAO VOWEL SIGN MAI KAN
+0EB2..0EB3 ; ID_Continue # Lo [2] LAO VOWEL SIGN AA..LAO VOWEL SIGN AM
+0EB4..0EBC ; ID_Continue # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO
+0EBD ; ID_Continue # Lo LAO SEMIVOWEL SIGN NYO
+0EC0..0EC4 ; ID_Continue # Lo [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI
+0EC6 ; ID_Continue # Lm LAO KO LA
+0EC8..0ECE ; ID_Continue # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN
+0ED0..0ED9 ; ID_Continue # Nd [10] LAO DIGIT ZERO..LAO DIGIT NINE
+0EDC..0EDF ; ID_Continue # Lo [4] LAO HO NO..LAO LETTER KHMU NYO
+0F00 ; ID_Continue # Lo TIBETAN SYLLABLE OM
+0F18..0F19 ; ID_Continue # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS
+0F20..0F29 ; ID_Continue # Nd [10] TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE
+0F35 ; ID_Continue # Mn TIBETAN MARK NGAS BZUNG NYI ZLA
+0F37 ; ID_Continue # Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS
+0F39 ; ID_Continue # Mn TIBETAN MARK TSA -PHRU
+0F3E..0F3F ; ID_Continue # Mc [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES
+0F40..0F47 ; ID_Continue # Lo [8] TIBETAN LETTER KA..TIBETAN LETTER JA
+0F49..0F6C ; ID_Continue # Lo [36] TIBETAN LETTER NYA..TIBETAN LETTER RRA
+0F71..0F7E ; ID_Continue # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO
+0F7F ; ID_Continue # Mc TIBETAN SIGN RNAM BCAD
+0F80..0F84 ; ID_Continue # Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA
+0F86..0F87 ; ID_Continue # Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS
+0F88..0F8C ; ID_Continue # Lo [5] TIBETAN SIGN LCE TSA CAN..TIBETAN SIGN INVERTED MCHU CAN
+0F8D..0F97 ; ID_Continue # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA
+0F99..0FBC ; ID_Continue # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA
+0FC6 ; ID_Continue # Mn TIBETAN SYMBOL PADMA GDAN
+1000..102A ; ID_Continue # Lo [43] MYANMAR LETTER KA..MYANMAR LETTER AU
+102B..102C ; ID_Continue # Mc [2] MYANMAR VOWEL SIGN TALL AA..MYANMAR VOWEL SIGN AA
+102D..1030 ; ID_Continue # Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU
+1031 ; ID_Continue # Mc MYANMAR VOWEL SIGN E
+1032..1037 ; ID_Continue # Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW
+1038 ; ID_Continue # Mc MYANMAR SIGN VISARGA
+1039..103A ; ID_Continue # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT
+103B..103C ; ID_Continue # Mc [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA
+103D..103E ; ID_Continue # Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA
+103F ; ID_Continue # Lo MYANMAR LETTER GREAT SA
+1040..1049 ; ID_Continue # Nd [10] MYANMAR DIGIT ZERO..MYANMAR DIGIT NINE
+1050..1055 ; ID_Continue # Lo [6] MYANMAR LETTER SHA..MYANMAR LETTER VOCALIC LL
+1056..1057 ; ID_Continue # Mc [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR
+1058..1059 ; ID_Continue # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL
+105A..105D ; ID_Continue # Lo [4] MYANMAR LETTER MON NGA..MYANMAR LETTER MON BBE
+105E..1060 ; ID_Continue # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA
+1061 ; ID_Continue # Lo MYANMAR LETTER SGAW KAREN SHA
+1062..1064 ; ID_Continue # Mc [3] MYANMAR VOWEL SIGN SGAW KAREN EU..MYANMAR TONE MARK SGAW KAREN KE PHO
+1065..1066 ; ID_Continue # Lo [2] MYANMAR LETTER WESTERN PWO KAREN THA..MYANMAR LETTER WESTERN PWO KAREN PWA
+1067..106D ; ID_Continue # Mc [7] MYANMAR VOWEL SIGN WESTERN PWO KAREN EU..MYANMAR SIGN WESTERN PWO KAREN TONE-5
+106E..1070 ; ID_Continue # Lo [3] MYANMAR LETTER EASTERN PWO KAREN NNA..MYANMAR LETTER EASTERN PWO KAREN GHWA
+1071..1074 ; ID_Continue # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE
+1075..1081 ; ID_Continue # Lo [13] MYANMAR LETTER SHAN KA..MYANMAR LETTER SHAN HA
+1082 ; ID_Continue # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA
+1083..1084 ; ID_Continue # Mc [2] MYANMAR VOWEL SIGN SHAN AA..MYANMAR VOWEL SIGN SHAN E
+1085..1086 ; ID_Continue # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y
+1087..108C ; ID_Continue # Mc [6] MYANMAR SIGN SHAN TONE-2..MYANMAR SIGN SHAN COUNCIL TONE-3
+108D ; ID_Continue # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE
+108E ; ID_Continue # Lo MYANMAR LETTER RUMAI PALAUNG FA
+108F ; ID_Continue # Mc MYANMAR SIGN RUMAI PALAUNG TONE-5
+1090..1099 ; ID_Continue # Nd [10] MYANMAR SHAN DIGIT ZERO..MYANMAR SHAN DIGIT NINE
+109A..109C ; ID_Continue # Mc [3] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON A
+109D ; ID_Continue # Mn MYANMAR VOWEL SIGN AITON AI
+10A0..10C5 ; ID_Continue # L& [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE
+10C7 ; ID_Continue # L& GEORGIAN CAPITAL LETTER YN
+10CD ; ID_Continue # L& GEORGIAN CAPITAL LETTER AEN
+10D0..10FA ; ID_Continue # L& [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN
+10FC ; ID_Continue # Lm MODIFIER LETTER GEORGIAN NAR
+10FD..10FF ; ID_Continue # L& [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN
+1100..1248 ; ID_Continue # Lo [329] HANGUL CHOSEONG KIYEOK..ETHIOPIC SYLLABLE QWA
+124A..124D ; ID_Continue # Lo [4] ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE
+1250..1256 ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO
+1258 ; ID_Continue # Lo ETHIOPIC SYLLABLE QHWA
+125A..125D ; ID_Continue # Lo [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE
+1260..1288 ; ID_Continue # Lo [41] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA
+128A..128D ; ID_Continue # Lo [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE
+1290..12B0 ; ID_Continue # Lo [33] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA
+12B2..12B5 ; ID_Continue # Lo [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE
+12B8..12BE ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO
+12C0 ; ID_Continue # Lo ETHIOPIC SYLLABLE KXWA
+12C2..12C5 ; ID_Continue # Lo [4] ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE
+12C8..12D6 ; ID_Continue # Lo [15] ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O
+12D8..1310 ; ID_Continue # Lo [57] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA
+1312..1315 ; ID_Continue # Lo [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE
+1318..135A ; ID_Continue # Lo [67] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA
+135D..135F ; ID_Continue # Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK
+1369..1371 ; ID_Continue # No [9] ETHIOPIC DIGIT ONE..ETHIOPIC DIGIT NINE
+1380..138F ; ID_Continue # Lo [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE
+13A0..13F5 ; ID_Continue # L& [86] CHEROKEE LETTER A..CHEROKEE LETTER MV
+13F8..13FD ; ID_Continue # L& [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV
+1401..166C ; ID_Continue # Lo [620] CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA
+166F..167F ; ID_Continue # Lo [17] CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W
+1681..169A ; ID_Continue # Lo [26] OGHAM LETTER BEITH..OGHAM LETTER PEITH
+16A0..16EA ; ID_Continue # Lo [75] RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X
+16EE..16F0 ; ID_Continue # Nl [3] RUNIC ARLAUG SYMBOL..RUNIC BELGTHOR SYMBOL
+16F1..16F8 ; ID_Continue # Lo [8] RUNIC LETTER K..RUNIC LETTER FRANKS CASKET AESC
+1700..1711 ; ID_Continue # Lo [18] TAGALOG LETTER A..TAGALOG LETTER HA
+1712..1714 ; ID_Continue # Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA
+1715 ; ID_Continue # Mc TAGALOG SIGN PAMUDPOD
+171F..1731 ; ID_Continue # Lo [19] TAGALOG LETTER ARCHAIC RA..HANUNOO LETTER HA
+1732..1733 ; ID_Continue # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U
+1734 ; ID_Continue # Mc HANUNOO SIGN PAMUDPOD
+1740..1751 ; ID_Continue # Lo [18] BUHID LETTER A..BUHID LETTER HA
+1752..1753 ; ID_Continue # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U
+1760..176C ; ID_Continue # Lo [13] TAGBANWA LETTER A..TAGBANWA LETTER YA
+176E..1770 ; ID_Continue # Lo [3] TAGBANWA LETTER LA..TAGBANWA LETTER SA
+1772..1773 ; ID_Continue # Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U
+1780..17B3 ; ID_Continue # Lo [52] KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU
+17B4..17B5 ; ID_Continue # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
+17B6 ; ID_Continue # Mc KHMER VOWEL SIGN AA
+17B7..17BD ; ID_Continue # Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA
+17BE..17C5 ; ID_Continue # Mc [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU
+17C6 ; ID_Continue # Mn KHMER SIGN NIKAHIT
+17C7..17C8 ; ID_Continue # Mc [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU
+17C9..17D3 ; ID_Continue # Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT
+17D7 ; ID_Continue # Lm KHMER SIGN LEK TOO
+17DC ; ID_Continue # Lo KHMER SIGN AVAKRAHASANYA
+17DD ; ID_Continue # Mn KHMER SIGN ATTHACAN
+17E0..17E9 ; ID_Continue # Nd [10] KHMER DIGIT ZERO..KHMER DIGIT NINE
+180B..180D ; ID_Continue # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
+180F ; ID_Continue # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR
+1810..1819 ; ID_Continue # Nd [10] MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE
+1820..1842 ; ID_Continue # Lo [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI
+1843 ; ID_Continue # Lm MONGOLIAN LETTER TODO LONG VOWEL SIGN
+1844..1878 ; ID_Continue # Lo [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS
+1880..1884 ; ID_Continue # Lo [5] MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER ALI GALI INVERTED UBADAMA
+1885..1886 ; ID_Continue # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA
+1887..18A8 ; ID_Continue # Lo [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA
+18A9 ; ID_Continue # Mn MONGOLIAN LETTER ALI GALI DAGALGA
+18AA ; ID_Continue # Lo MONGOLIAN LETTER MANCHU ALI GALI LHA
+18B0..18F5 ; ID_Continue # Lo [70] CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S
+1900..191E ; ID_Continue # Lo [31] LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER TRA
+1920..1922 ; ID_Continue # Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U
+1923..1926 ; ID_Continue # Mc [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU
+1927..1928 ; ID_Continue # Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O
+1929..192B ; ID_Continue # Mc [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA
+1930..1931 ; ID_Continue # Mc [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA
+1932 ; ID_Continue # Mn LIMBU SMALL LETTER ANUSVARA
+1933..1938 ; ID_Continue # Mc [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA
+1939..193B ; ID_Continue # Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I
+1946..194F ; ID_Continue # Nd [10] LIMBU DIGIT ZERO..LIMBU DIGIT NINE
+1950..196D ; ID_Continue # Lo [30] TAI LE LETTER KA..TAI LE LETTER AI
+1970..1974 ; ID_Continue # Lo [5] TAI LE LETTER TONE-2..TAI LE LETTER TONE-6
+1980..19AB ; ID_Continue # Lo [44] NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA
+19B0..19C9 ; ID_Continue # Lo [26] NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2
+19D0..19D9 ; ID_Continue # Nd [10] NEW TAI LUE DIGIT ZERO..NEW TAI LUE DIGIT NINE
+19DA ; ID_Continue # No NEW TAI LUE THAM DIGIT ONE
+1A00..1A16 ; ID_Continue # Lo [23] BUGINESE LETTER KA..BUGINESE LETTER HA
+1A17..1A18 ; ID_Continue # Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U
+1A19..1A1A ; ID_Continue # Mc [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O
+1A1B ; ID_Continue # Mn BUGINESE VOWEL SIGN AE
+1A20..1A54 ; ID_Continue # Lo [53] TAI THAM LETTER HIGH KA..TAI THAM LETTER GREAT SA
+1A55 ; ID_Continue # Mc TAI THAM CONSONANT SIGN MEDIAL RA
+1A56 ; ID_Continue # Mn TAI THAM CONSONANT SIGN MEDIAL LA
+1A57 ; ID_Continue # Mc TAI THAM CONSONANT SIGN LA TANG LAI
+1A58..1A5E ; ID_Continue # Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA
+1A60 ; ID_Continue # Mn TAI THAM SIGN SAKOT
+1A61 ; ID_Continue # Mc TAI THAM VOWEL SIGN A
+1A62 ; ID_Continue # Mn TAI THAM VOWEL SIGN MAI SAT
+1A63..1A64 ; ID_Continue # Mc [2] TAI THAM VOWEL SIGN AA..TAI THAM VOWEL SIGN TALL AA
+1A65..1A6C ; ID_Continue # Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW
+1A6D..1A72 ; ID_Continue # Mc [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI
+1A73..1A7C ; ID_Continue # Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN
+1A7F ; ID_Continue # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT
+1A80..1A89 ; ID_Continue # Nd [10] TAI THAM HORA DIGIT ZERO..TAI THAM HORA DIGIT NINE
+1A90..1A99 ; ID_Continue # Nd [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE
+1AA7 ; ID_Continue # Lm TAI THAM SIGN MAI YAMOK
+1AB0..1ABD ; ID_Continue # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW
+1ABF..1ACE ; ID_Continue # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T
+1B00..1B03 ; ID_Continue # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG
+1B04 ; ID_Continue # Mc BALINESE SIGN BISAH
+1B05..1B33 ; ID_Continue # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA
+1B34 ; ID_Continue # Mn BALINESE SIGN REREKAN
+1B35 ; ID_Continue # Mc BALINESE VOWEL SIGN TEDUNG
+1B36..1B3A ; ID_Continue # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA
+1B3B ; ID_Continue # Mc BALINESE VOWEL SIGN RA REPA TEDUNG
+1B3C ; ID_Continue # Mn BALINESE VOWEL SIGN LA LENGA
+1B3D..1B41 ; ID_Continue # Mc [5] BALINESE VOWEL SIGN LA LENGA TEDUNG..BALINESE VOWEL SIGN TALING REPA TEDUNG
+1B42 ; ID_Continue # Mn BALINESE VOWEL SIGN PEPET
+1B43..1B44 ; ID_Continue # Mc [2] BALINESE VOWEL SIGN PEPET TEDUNG..BALINESE ADEG ADEG
+1B45..1B4C ; ID_Continue # Lo [8] BALINESE LETTER KAF SASAK..BALINESE LETTER ARCHAIC JNYA
+1B50..1B59 ; ID_Continue # Nd [10] BALINESE DIGIT ZERO..BALINESE DIGIT NINE
+1B6B..1B73 ; ID_Continue # Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG
+1B80..1B81 ; ID_Continue # Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR
+1B82 ; ID_Continue # Mc SUNDANESE SIGN PANGWISAD
+1B83..1BA0 ; ID_Continue # Lo [30] SUNDANESE LETTER A..SUNDANESE LETTER HA
+1BA1 ; ID_Continue # Mc SUNDANESE CONSONANT SIGN PAMINGKAL
+1BA2..1BA5 ; ID_Continue # Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU
+1BA6..1BA7 ; ID_Continue # Mc [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG
+1BA8..1BA9 ; ID_Continue # Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG
+1BAA ; ID_Continue # Mc SUNDANESE SIGN PAMAAEH
+1BAB..1BAD ; ID_Continue # Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA
+1BAE..1BAF ; ID_Continue # Lo [2] SUNDANESE LETTER KHA..SUNDANESE LETTER SYA
+1BB0..1BB9 ; ID_Continue # Nd [10] SUNDANESE DIGIT ZERO..SUNDANESE DIGIT NINE
+1BBA..1BE5 ; ID_Continue # Lo [44] SUNDANESE AVAGRAHA..BATAK LETTER U
+1BE6 ; ID_Continue # Mn BATAK SIGN TOMPI
+1BE7 ; ID_Continue # Mc BATAK VOWEL SIGN E
+1BE8..1BE9 ; ID_Continue # Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE
+1BEA..1BEC ; ID_Continue # Mc [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O
+1BED ; ID_Continue # Mn BATAK VOWEL SIGN KARO O
+1BEE ; ID_Continue # Mc BATAK VOWEL SIGN U
+1BEF..1BF1 ; ID_Continue # Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H
+1BF2..1BF3 ; ID_Continue # Mc [2] BATAK PANGOLAT..BATAK PANONGONAN
+1C00..1C23 ; ID_Continue # Lo [36] LEPCHA LETTER KA..LEPCHA LETTER A
+1C24..1C2B ; ID_Continue # Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU
+1C2C..1C33 ; ID_Continue # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T
+1C34..1C35 ; ID_Continue # Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG
+1C36..1C37 ; ID_Continue # Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA
+1C40..1C49 ; ID_Continue # Nd [10] LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE
+1C4D..1C4F ; ID_Continue # Lo [3] LEPCHA LETTER TTA..LEPCHA LETTER DDA
+1C50..1C59 ; ID_Continue # Nd [10] OL CHIKI DIGIT ZERO..OL CHIKI DIGIT NINE
+1C5A..1C77 ; ID_Continue # Lo [30] OL CHIKI LETTER LA..OL CHIKI LETTER OH
+1C78..1C7D ; ID_Continue # Lm [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD
+1C80..1C8A ; ID_Continue # L& [11] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER TJE
+1C90..1CBA ; ID_Continue # L& [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN
+1CBD..1CBF ; ID_Continue # L& [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
+1CD0..1CD2 ; ID_Continue # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA
+1CD4..1CE0 ; ID_Continue # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA
+1CE1 ; ID_Continue # Mc VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA
+1CE2..1CE8 ; ID_Continue # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL
+1CE9..1CEC ; ID_Continue # Lo [4] VEDIC SIGN ANUSVARA ANTARGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL
+1CED ; ID_Continue # Mn VEDIC SIGN TIRYAK
+1CEE..1CF3 ; ID_Continue # Lo [6] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ROTATED ARDHAVISARGA
+1CF4 ; ID_Continue # Mn VEDIC TONE CANDRA ABOVE
+1CF5..1CF6 ; ID_Continue # Lo [2] VEDIC SIGN JIHVAMULIYA..VEDIC SIGN UPADHMANIYA
+1CF7 ; ID_Continue # Mc VEDIC SIGN ATIKRAMA
+1CF8..1CF9 ; ID_Continue # Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE
+1CFA ; ID_Continue # Lo VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA
+1D00..1D2B ; ID_Continue # L& [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL
+1D2C..1D6A ; ID_Continue # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI
+1D6B..1D77 ; ID_Continue # L& [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G
+1D78 ; ID_Continue # Lm MODIFIER LETTER CYRILLIC EN
+1D79..1D9A ; ID_Continue # L& [34] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK
+1D9B..1DBF ; ID_Continue # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA
+1DC0..1DFF ; ID_Continue # Mn [64] COMBINING DOTTED GRAVE ACCENT..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW
+1E00..1F15 ; ID_Continue # L& [278] LATIN CAPITAL LETTER A WITH RING BELOW..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA
+1F18..1F1D ; ID_Continue # L& [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F20..1F45 ; ID_Continue # L& [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA
+1F48..1F4D ; ID_Continue # L& [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F50..1F57 ; ID_Continue # L& [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F59 ; ID_Continue # L& GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B ; ID_Continue # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D ; ID_Continue # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F..1F7D ; ID_Continue # L& [31] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER OMEGA WITH OXIA
+1F80..1FB4 ; ID_Continue # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6..1FBC ; ID_Continue # L& [7] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBE ; ID_Continue # L& GREEK PROSGEGRAMMENI
+1FC2..1FC4 ; ID_Continue # L& [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6..1FCC ; ID_Continue # L& [7] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FD0..1FD3 ; ID_Continue # L& [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6..1FDB ; ID_Continue # L& [6] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH OXIA
+1FE0..1FEC ; ID_Continue # L& [13] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA
+1FF2..1FF4 ; ID_Continue # L& [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6..1FFC ; ID_Continue # L& [7] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+200C..200D ; ID_Continue # Cf [2] ZERO WIDTH NON-JOINER..ZERO WIDTH JOINER
+203F..2040 ; ID_Continue # Pc [2] UNDERTIE..CHARACTER TIE
+2054 ; ID_Continue # Pc INVERTED UNDERTIE
+2071 ; ID_Continue # Lm SUPERSCRIPT LATIN SMALL LETTER I
+207F ; ID_Continue # Lm SUPERSCRIPT LATIN SMALL LETTER N
+2090..209C ; ID_Continue # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T
+20D0..20DC ; ID_Continue # Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE
+20E1 ; ID_Continue # Mn COMBINING LEFT RIGHT ARROW ABOVE
+20E5..20F0 ; ID_Continue # Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE
+2102 ; ID_Continue # L& DOUBLE-STRUCK CAPITAL C
+2107 ; ID_Continue # L& EULER CONSTANT
+210A..2113 ; ID_Continue # L& [10] SCRIPT SMALL G..SCRIPT SMALL L
+2115 ; ID_Continue # L& DOUBLE-STRUCK CAPITAL N
+2118 ; ID_Continue # Sm SCRIPT CAPITAL P
+2119..211D ; ID_Continue # L& [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R
+2124 ; ID_Continue # L& DOUBLE-STRUCK CAPITAL Z
+2126 ; ID_Continue # L& OHM SIGN
+2128 ; ID_Continue # L& BLACK-LETTER CAPITAL Z
+212A..212D ; ID_Continue # L& [4] KELVIN SIGN..BLACK-LETTER CAPITAL C
+212E ; ID_Continue # So ESTIMATED SYMBOL
+212F..2134 ; ID_Continue # L& [6] SCRIPT SMALL E..SCRIPT SMALL O
+2135..2138 ; ID_Continue # Lo [4] ALEF SYMBOL..DALET SYMBOL
+2139 ; ID_Continue # L& INFORMATION SOURCE
+213C..213F ; ID_Continue # L& [4] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK CAPITAL PI
+2145..2149 ; ID_Continue # L& [5] DOUBLE-STRUCK ITALIC CAPITAL D..DOUBLE-STRUCK ITALIC SMALL J
+214E ; ID_Continue # L& TURNED SMALL F
+2160..2182 ; ID_Continue # Nl [35] ROMAN NUMERAL ONE..ROMAN NUMERAL TEN THOUSAND
+2183..2184 ; ID_Continue # L& [2] ROMAN NUMERAL REVERSED ONE HUNDRED..LATIN SMALL LETTER REVERSED C
+2185..2188 ; ID_Continue # Nl [4] ROMAN NUMERAL SIX LATE FORM..ROMAN NUMERAL ONE HUNDRED THOUSAND
+2C00..2C7B ; ID_Continue # L& [124] GLAGOLITIC CAPITAL LETTER AZU..LATIN LETTER SMALL CAPITAL TURNED E
+2C7C..2C7D ; ID_Continue # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V
+2C7E..2CE4 ; ID_Continue # L& [103] LATIN CAPITAL LETTER S WITH SWASH TAIL..COPTIC SYMBOL KAI
+2CEB..2CEE ; ID_Continue # L& [4] COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI..COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA
+2CEF..2CF1 ; ID_Continue # Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS
+2CF2..2CF3 ; ID_Continue # L& [2] COPTIC CAPITAL LETTER BOHAIRIC KHEI..COPTIC SMALL LETTER BOHAIRIC KHEI
+2D00..2D25 ; ID_Continue # L& [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE
+2D27 ; ID_Continue # L& GEORGIAN SMALL LETTER YN
+2D2D ; ID_Continue # L& GEORGIAN SMALL LETTER AEN
+2D30..2D67 ; ID_Continue # Lo [56] TIFINAGH LETTER YA..TIFINAGH LETTER YO
+2D6F ; ID_Continue # Lm TIFINAGH MODIFIER LETTER LABIALIZATION MARK
+2D7F ; ID_Continue # Mn TIFINAGH CONSONANT JOINER
+2D80..2D96 ; ID_Continue # Lo [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE
+2DA0..2DA6 ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO
+2DA8..2DAE ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO
+2DB0..2DB6 ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO
+2DB8..2DBE ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO
+2DC0..2DC6 ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO
+2DC8..2DCE ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO
+2DD0..2DD6 ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO
+2DD8..2DDE ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO
+2DE0..2DFF ; ID_Continue # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS
+3005 ; ID_Continue # Lm IDEOGRAPHIC ITERATION MARK
+3006 ; ID_Continue # Lo IDEOGRAPHIC CLOSING MARK
+3007 ; ID_Continue # Nl IDEOGRAPHIC NUMBER ZERO
+3021..3029 ; ID_Continue # Nl [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE
+302A..302D ; ID_Continue # Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK
+302E..302F ; ID_Continue # Mc [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK
+3031..3035 ; ID_Continue # Lm [5] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT MARK LOWER HALF
+3038..303A ; ID_Continue # Nl [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY
+303B ; ID_Continue # Lm VERTICAL IDEOGRAPHIC ITERATION MARK
+303C ; ID_Continue # Lo MASU MARK
+3041..3096 ; ID_Continue # Lo [86] HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMALL KE
+3099..309A ; ID_Continue # Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+309B..309C ; ID_Continue # Sk [2] KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+309D..309E ; ID_Continue # Lm [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK
+309F ; ID_Continue # Lo HIRAGANA DIGRAPH YORI
+30A1..30FA ; ID_Continue # Lo [90] KATAKANA LETTER SMALL A..KATAKANA LETTER VO
+30FB ; ID_Continue # Po KATAKANA MIDDLE DOT
+30FC..30FE ; ID_Continue # Lm [3] KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK
+30FF ; ID_Continue # Lo KATAKANA DIGRAPH KOTO
+3105..312F ; ID_Continue # Lo [43] BOPOMOFO LETTER B..BOPOMOFO LETTER NN
+3131..318E ; ID_Continue # Lo [94] HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE
+31A0..31BF ; ID_Continue # Lo [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH
+31F0..31FF ; ID_Continue # Lo [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO
+3400..4DBF ; ID_Continue # Lo [6592] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DBF
+4E00..A014 ; ID_Continue # Lo [21013] CJK UNIFIED IDEOGRAPH-4E00..YI SYLLABLE E
+A015 ; ID_Continue # Lm YI SYLLABLE WU
+A016..A48C ; ID_Continue # Lo [1143] YI SYLLABLE BIT..YI SYLLABLE YYR
+A4D0..A4F7 ; ID_Continue # Lo [40] LISU LETTER BA..LISU LETTER OE
+A4F8..A4FD ; ID_Continue # Lm [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU
+A500..A60B ; ID_Continue # Lo [268] VAI SYLLABLE EE..VAI SYLLABLE NG
+A60C ; ID_Continue # Lm VAI SYLLABLE LENGTHENER
+A610..A61F ; ID_Continue # Lo [16] VAI SYLLABLE NDOLE FA..VAI SYMBOL JONG
+A620..A629 ; ID_Continue # Nd [10] VAI DIGIT ZERO..VAI DIGIT NINE
+A62A..A62B ; ID_Continue # Lo [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO
+A640..A66D ; ID_Continue # L& [46] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER DOUBLE MONOCULAR O
+A66E ; ID_Continue # Lo CYRILLIC LETTER MULTIOCULAR O
+A66F ; ID_Continue # Mn COMBINING CYRILLIC VZMET
+A674..A67D ; ID_Continue # Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK
+A67F ; ID_Continue # Lm CYRILLIC PAYEROK
+A680..A69B ; ID_Continue # L& [28] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER CROSSED O
+A69C..A69D ; ID_Continue # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN
+A69E..A69F ; ID_Continue # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E
+A6A0..A6E5 ; ID_Continue # Lo [70] BAMUM LETTER A..BAMUM LETTER KI
+A6E6..A6EF ; ID_Continue # Nl [10] BAMUM LETTER MO..BAMUM LETTER KOGHOM
+A6F0..A6F1 ; ID_Continue # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS
+A717..A71F ; ID_Continue # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK
+A722..A76F ; ID_Continue # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CON
+A770 ; ID_Continue # Lm MODIFIER LETTER US
+A771..A787 ; ID_Continue # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T
+A788 ; ID_Continue # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT
+A78B..A78E ; ID_Continue # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT
+A78F ; ID_Continue # Lo LATIN LETTER SINOLOGICAL DOT
+A790..A7CD ; ID_Continue # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE
+A7D0..A7D1 ; ID_Continue # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G
+A7D3 ; ID_Continue # L& LATIN SMALL LETTER DOUBLE THORN
+A7D5..A7DC ; ID_Continue # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE
+A7F2..A7F4 ; ID_Continue # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q
+A7F5..A7F6 ; ID_Continue # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H
+A7F7 ; ID_Continue # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I
+A7F8..A7F9 ; ID_Continue # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE
+A7FA ; ID_Continue # L& LATIN LETTER SMALL CAPITAL TURNED M
+A7FB..A801 ; ID_Continue # Lo [7] LATIN EPIGRAPHIC LETTER REVERSED F..SYLOTI NAGRI LETTER I
+A802 ; ID_Continue # Mn SYLOTI NAGRI SIGN DVISVARA
+A803..A805 ; ID_Continue # Lo [3] SYLOTI NAGRI LETTER U..SYLOTI NAGRI LETTER O
+A806 ; ID_Continue # Mn SYLOTI NAGRI SIGN HASANTA
+A807..A80A ; ID_Continue # Lo [4] SYLOTI NAGRI LETTER KO..SYLOTI NAGRI LETTER GHO
+A80B ; ID_Continue # Mn SYLOTI NAGRI SIGN ANUSVARA
+A80C..A822 ; ID_Continue # Lo [23] SYLOTI NAGRI LETTER CO..SYLOTI NAGRI LETTER HO
+A823..A824 ; ID_Continue # Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I
+A825..A826 ; ID_Continue # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E
+A827 ; ID_Continue # Mc SYLOTI NAGRI VOWEL SIGN OO
+A82C ; ID_Continue # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA
+A840..A873 ; ID_Continue # Lo [52] PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU
+A880..A881 ; ID_Continue # Mc [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA
+A882..A8B3 ; ID_Continue # Lo [50] SAURASHTRA LETTER A..SAURASHTRA LETTER LLA
+A8B4..A8C3 ; ID_Continue # Mc [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU
+A8C4..A8C5 ; ID_Continue # Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU
+A8D0..A8D9 ; ID_Continue # Nd [10] SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE
+A8E0..A8F1 ; ID_Continue # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA
+A8F2..A8F7 ; ID_Continue # Lo [6] DEVANAGARI SIGN SPACING CANDRABINDU..DEVANAGARI SIGN CANDRABINDU AVAGRAHA
+A8FB ; ID_Continue # Lo DEVANAGARI HEADSTROKE
+A8FD..A8FE ; ID_Continue # Lo [2] DEVANAGARI JAIN OM..DEVANAGARI LETTER AY
+A8FF ; ID_Continue # Mn DEVANAGARI VOWEL SIGN AY
+A900..A909 ; ID_Continue # Nd [10] KAYAH LI DIGIT ZERO..KAYAH LI DIGIT NINE
+A90A..A925 ; ID_Continue # Lo [28] KAYAH LI LETTER KA..KAYAH LI LETTER OO
+A926..A92D ; ID_Continue # Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU
+A930..A946 ; ID_Continue # Lo [23] REJANG LETTER KA..REJANG LETTER A
+A947..A951 ; ID_Continue # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R
+A952..A953 ; ID_Continue # Mc [2] REJANG CONSONANT SIGN H..REJANG VIRAMA
+A960..A97C ; ID_Continue # Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH
+A980..A982 ; ID_Continue # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR
+A983 ; ID_Continue # Mc JAVANESE SIGN WIGNYAN
+A984..A9B2 ; ID_Continue # Lo [47] JAVANESE LETTER A..JAVANESE LETTER HA
+A9B3 ; ID_Continue # Mn JAVANESE SIGN CECAK TELU
+A9B4..A9B5 ; ID_Continue # Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG
+A9B6..A9B9 ; ID_Continue # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT
+A9BA..A9BB ; ID_Continue # Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE
+A9BC..A9BD ; ID_Continue # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET
+A9BE..A9C0 ; ID_Continue # Mc [3] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE PANGKON
+A9CF ; ID_Continue # Lm JAVANESE PANGRANGKEP
+A9D0..A9D9 ; ID_Continue # Nd [10] JAVANESE DIGIT ZERO..JAVANESE DIGIT NINE
+A9E0..A9E4 ; ID_Continue # Lo [5] MYANMAR LETTER SHAN GHA..MYANMAR LETTER SHAN BHA
+A9E5 ; ID_Continue # Mn MYANMAR SIGN SHAN SAW
+A9E6 ; ID_Continue # Lm MYANMAR MODIFIER LETTER SHAN REDUPLICATION
+A9E7..A9EF ; ID_Continue # Lo [9] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING NNA
+A9F0..A9F9 ; ID_Continue # Nd [10] MYANMAR TAI LAING DIGIT ZERO..MYANMAR TAI LAING DIGIT NINE
+A9FA..A9FE ; ID_Continue # Lo [5] MYANMAR LETTER TAI LAING LLA..MYANMAR LETTER TAI LAING BHA
+AA00..AA28 ; ID_Continue # Lo [41] CHAM LETTER A..CHAM LETTER HA
+AA29..AA2E ; ID_Continue # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE
+AA2F..AA30 ; ID_Continue # Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI
+AA31..AA32 ; ID_Continue # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE
+AA33..AA34 ; ID_Continue # Mc [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA
+AA35..AA36 ; ID_Continue # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA
+AA40..AA42 ; ID_Continue # Lo [3] CHAM LETTER FINAL K..CHAM LETTER FINAL NG
+AA43 ; ID_Continue # Mn CHAM CONSONANT SIGN FINAL NG
+AA44..AA4B ; ID_Continue # Lo [8] CHAM LETTER FINAL CH..CHAM LETTER FINAL SS
+AA4C ; ID_Continue # Mn CHAM CONSONANT SIGN FINAL M
+AA4D ; ID_Continue # Mc CHAM CONSONANT SIGN FINAL H
+AA50..AA59 ; ID_Continue # Nd [10] CHAM DIGIT ZERO..CHAM DIGIT NINE
+AA60..AA6F ; ID_Continue # Lo [16] MYANMAR LETTER KHAMTI GA..MYANMAR LETTER KHAMTI FA
+AA70 ; ID_Continue # Lm MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION
+AA71..AA76 ; ID_Continue # Lo [6] MYANMAR LETTER KHAMTI XA..MYANMAR LOGOGRAM KHAMTI HM
+AA7A ; ID_Continue # Lo MYANMAR LETTER AITON RA
+AA7B ; ID_Continue # Mc MYANMAR SIGN PAO KAREN TONE
+AA7C ; ID_Continue # Mn MYANMAR SIGN TAI LAING TONE-2
+AA7D ; ID_Continue # Mc MYANMAR SIGN TAI LAING TONE-5
+AA7E..AAAF ; ID_Continue # Lo [50] MYANMAR LETTER SHWE PALAUNG CHA..TAI VIET LETTER HIGH O
+AAB0 ; ID_Continue # Mn TAI VIET MAI KANG
+AAB1 ; ID_Continue # Lo TAI VIET VOWEL AA
+AAB2..AAB4 ; ID_Continue # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U
+AAB5..AAB6 ; ID_Continue # Lo [2] TAI VIET VOWEL E..TAI VIET VOWEL O
+AAB7..AAB8 ; ID_Continue # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA
+AAB9..AABD ; ID_Continue # Lo [5] TAI VIET VOWEL UEA..TAI VIET VOWEL AN
+AABE..AABF ; ID_Continue # Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK
+AAC0 ; ID_Continue # Lo TAI VIET TONE MAI NUENG
+AAC1 ; ID_Continue # Mn TAI VIET TONE MAI THO
+AAC2 ; ID_Continue # Lo TAI VIET TONE MAI SONG
+AADB..AADC ; ID_Continue # Lo [2] TAI VIET SYMBOL KON..TAI VIET SYMBOL NUENG
+AADD ; ID_Continue # Lm TAI VIET SYMBOL SAM
+AAE0..AAEA ; ID_Continue # Lo [11] MEETEI MAYEK LETTER E..MEETEI MAYEK LETTER SSA
+AAEB ; ID_Continue # Mc MEETEI MAYEK VOWEL SIGN II
+AAEC..AAED ; ID_Continue # Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI
+AAEE..AAEF ; ID_Continue # Mc [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU
+AAF2 ; ID_Continue # Lo MEETEI MAYEK ANJI
+AAF3..AAF4 ; ID_Continue # Lm [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK
+AAF5 ; ID_Continue # Mc MEETEI MAYEK VOWEL SIGN VISARGA
+AAF6 ; ID_Continue # Mn MEETEI MAYEK VIRAMA
+AB01..AB06 ; ID_Continue # Lo [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO
+AB09..AB0E ; ID_Continue # Lo [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO
+AB11..AB16 ; ID_Continue # Lo [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO
+AB20..AB26 ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO
+AB28..AB2E ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO
+AB30..AB5A ; ID_Continue # L& [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG
+AB5C..AB5F ; ID_Continue # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK
+AB60..AB68 ; ID_Continue # L& [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE
+AB69 ; ID_Continue # Lm MODIFIER LETTER SMALL TURNED W
+AB70..ABBF ; ID_Continue # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA
+ABC0..ABE2 ; ID_Continue # Lo [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM
+ABE3..ABE4 ; ID_Continue # Mc [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP
+ABE5 ; ID_Continue # Mn MEETEI MAYEK VOWEL SIGN ANAP
+ABE6..ABE7 ; ID_Continue # Mc [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP
+ABE8 ; ID_Continue # Mn MEETEI MAYEK VOWEL SIGN UNAP
+ABE9..ABEA ; ID_Continue # Mc [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG
+ABEC ; ID_Continue # Mc MEETEI MAYEK LUM IYEK
+ABED ; ID_Continue # Mn MEETEI MAYEK APUN IYEK
+ABF0..ABF9 ; ID_Continue # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT NINE
+AC00..D7A3 ; ID_Continue # Lo [11172] HANGUL SYLLABLE GA..HANGUL SYLLABLE HIH
+D7B0..D7C6 ; ID_Continue # Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E
+D7CB..D7FB ; ID_Continue # Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH
+F900..FA6D ; ID_Continue # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D
+FA70..FAD9 ; ID_Continue # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9
+FB00..FB06 ; ID_Continue # L& [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST
+FB13..FB17 ; ID_Continue # L& [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH
+FB1D ; ID_Continue # Lo HEBREW LETTER YOD WITH HIRIQ
+FB1E ; ID_Continue # Mn HEBREW POINT JUDEO-SPANISH VARIKA
+FB1F..FB28 ; ID_Continue # Lo [10] HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER WIDE TAV
+FB2A..FB36 ; ID_Continue # Lo [13] HEBREW LETTER SHIN WITH SHIN DOT..HEBREW LETTER ZAYIN WITH DAGESH
+FB38..FB3C ; ID_Continue # Lo [5] HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH
+FB3E ; ID_Continue # Lo HEBREW LETTER MEM WITH DAGESH
+FB40..FB41 ; ID_Continue # Lo [2] HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH
+FB43..FB44 ; ID_Continue # Lo [2] HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH
+FB46..FBB1 ; ID_Continue # Lo [108] HEBREW LETTER TSADI WITH DAGESH..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM
+FBD3..FD3D ; ID_Continue # Lo [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM
+FD50..FD8F ; ID_Continue # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM
+FD92..FDC7 ; ID_Continue # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM
+FDF0..FDFB ; ID_Continue # Lo [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU
+FE00..FE0F ; ID_Continue # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
+FE20..FE2F ; ID_Continue # Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF
+FE33..FE34 ; ID_Continue # Pc [2] PRESENTATION FORM FOR VERTICAL LOW LINE..PRESENTATION FORM FOR VERTICAL WAVY LOW LINE
+FE4D..FE4F ; ID_Continue # Pc [3] DASHED LOW LINE..WAVY LOW LINE
+FE70..FE74 ; ID_Continue # Lo [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN ISOLATED FORM
+FE76..FEFC ; ID_Continue # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM
+FF10..FF19 ; ID_Continue # Nd [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE
+FF21..FF3A ; ID_Continue # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z
+FF3F ; ID_Continue # Pc FULLWIDTH LOW LINE
+FF41..FF5A ; ID_Continue # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z
+FF65 ; ID_Continue # Po HALFWIDTH KATAKANA MIDDLE DOT
+FF66..FF6F ; ID_Continue # Lo [10] HALFWIDTH KATAKANA LETTER WO..HALFWIDTH KATAKANA LETTER SMALL TU
+FF70 ; ID_Continue # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK
+FF71..FF9D ; ID_Continue # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAKANA LETTER N
+FF9E..FF9F ; ID_Continue # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
+FFA0..FFBE ; ID_Continue # Lo [31] HALFWIDTH HANGUL FILLER..HALFWIDTH HANGUL LETTER HIEUH
+FFC2..FFC7 ; ID_Continue # Lo [6] HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E
+FFCA..FFCF ; ID_Continue # Lo [6] HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE
+FFD2..FFD7 ; ID_Continue # Lo [6] HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU
+FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I
+10000..1000B ; ID_Continue # Lo [12] LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE
+1000D..10026 ; ID_Continue # Lo [26] LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO
+10028..1003A ; ID_Continue # Lo [19] LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO
+1003C..1003D ; ID_Continue # Lo [2] LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE
+1003F..1004D ; ID_Continue # Lo [15] LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO
+10050..1005D ; ID_Continue # Lo [14] LINEAR B SYMBOL B018..LINEAR B SYMBOL B089
+10080..100FA ; ID_Continue # Lo [123] LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305
+10140..10174 ; ID_Continue # Nl [53] GREEK ACROPHONIC ATTIC ONE QUARTER..GREEK ACROPHONIC STRATIAN FIFTY MNAS
+101FD ; ID_Continue # Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE
+10280..1029C ; ID_Continue # Lo [29] LYCIAN LETTER A..LYCIAN LETTER X
+102A0..102D0 ; ID_Continue # Lo [49] CARIAN LETTER A..CARIAN LETTER UUU3
+102E0 ; ID_Continue # Mn COPTIC EPACT THOUSANDS MARK
+10300..1031F ; ID_Continue # Lo [32] OLD ITALIC LETTER A..OLD ITALIC LETTER ESS
+1032D..10340 ; ID_Continue # Lo [20] OLD ITALIC LETTER YE..GOTHIC LETTER PAIRTHRA
+10341 ; ID_Continue # Nl GOTHIC LETTER NINETY
+10342..10349 ; ID_Continue # Lo [8] GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL
+1034A ; ID_Continue # Nl GOTHIC LETTER NINE HUNDRED
+10350..10375 ; ID_Continue # Lo [38] OLD PERMIC LETTER AN..OLD PERMIC LETTER IA
+10376..1037A ; ID_Continue # Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII
+10380..1039D ; ID_Continue # Lo [30] UGARITIC LETTER ALPA..UGARITIC LETTER SSU
+103A0..103C3 ; ID_Continue # Lo [36] OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA
+103C8..103CF ; ID_Continue # Lo [8] OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH
+103D1..103D5 ; ID_Continue # Nl [5] OLD PERSIAN NUMBER ONE..OLD PERSIAN NUMBER HUNDRED
+10400..1044F ; ID_Continue # L& [80] DESERET CAPITAL LETTER LONG I..DESERET SMALL LETTER EW
+10450..1049D ; ID_Continue # Lo [78] SHAVIAN LETTER PEEP..OSMANYA LETTER OO
+104A0..104A9 ; ID_Continue # Nd [10] OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE
+104B0..104D3 ; ID_Continue # L& [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA
+104D8..104FB ; ID_Continue # L& [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA
+10500..10527 ; ID_Continue # Lo [40] ELBASAN LETTER A..ELBASAN LETTER KHE
+10530..10563 ; ID_Continue # Lo [52] CAUCASIAN ALBANIAN LETTER ALT..CAUCASIAN ALBANIAN LETTER KIW
+10570..1057A ; ID_Continue # L& [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA
+1057C..1058A ; ID_Continue # L& [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE
+1058C..10592 ; ID_Continue # L& [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE
+10594..10595 ; ID_Continue # L& [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE
+10597..105A1 ; ID_Continue # L& [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA
+105A3..105B1 ; ID_Continue # L& [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE
+105B3..105B9 ; ID_Continue # L& [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE
+105BB..105BC ; ID_Continue # L& [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE
+105C0..105F3 ; ID_Continue # Lo [52] TODHRI LETTER A..TODHRI LETTER OO
+10600..10736 ; ID_Continue # Lo [311] LINEAR A SIGN AB001..LINEAR A SIGN A664
+10740..10755 ; ID_Continue # Lo [22] LINEAR A SIGN A701 A..LINEAR A SIGN A732 JE
+10760..10767 ; ID_Continue # Lo [8] LINEAR A SIGN A800..LINEAR A SIGN A807
+10780..10785 ; ID_Continue # Lm [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK
+10787..107B0 ; ID_Continue # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK
+107B2..107BA ; ID_Continue # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL
+10800..10805 ; ID_Continue # Lo [6] CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA
+10808 ; ID_Continue # Lo CYPRIOT SYLLABLE JO
+1080A..10835 ; ID_Continue # Lo [44] CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO
+10837..10838 ; ID_Continue # Lo [2] CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE
+1083C ; ID_Continue # Lo CYPRIOT SYLLABLE ZA
+1083F..10855 ; ID_Continue # Lo [23] CYPRIOT SYLLABLE ZO..IMPERIAL ARAMAIC LETTER TAW
+10860..10876 ; ID_Continue # Lo [23] PALMYRENE LETTER ALEPH..PALMYRENE LETTER TAW
+10880..1089E ; ID_Continue # Lo [31] NABATAEAN LETTER FINAL ALEPH..NABATAEAN LETTER TAW
+108E0..108F2 ; ID_Continue # Lo [19] HATRAN LETTER ALEPH..HATRAN LETTER QOPH
+108F4..108F5 ; ID_Continue # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW
+10900..10915 ; ID_Continue # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU
+10920..10939 ; ID_Continue # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C
+10980..109B7 ; ID_Continue # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA
+109BE..109BF ; ID_Continue # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN
+10A00 ; ID_Continue # Lo KHAROSHTHI LETTER A
+10A01..10A03 ; ID_Continue # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R
+10A05..10A06 ; ID_Continue # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O
+10A0C..10A0F ; ID_Continue # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA
+10A10..10A13 ; ID_Continue # Lo [4] KHAROSHTHI LETTER KA..KHAROSHTHI LETTER GHA
+10A15..10A17 ; ID_Continue # Lo [3] KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA
+10A19..10A35 ; ID_Continue # Lo [29] KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER VHA
+10A38..10A3A ; ID_Continue # Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW
+10A3F ; ID_Continue # Mn KHAROSHTHI VIRAMA
+10A60..10A7C ; ID_Continue # Lo [29] OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH
+10A80..10A9C ; ID_Continue # Lo [29] OLD NORTH ARABIAN LETTER HEH..OLD NORTH ARABIAN LETTER ZAH
+10AC0..10AC7 ; ID_Continue # Lo [8] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER WAW
+10AC9..10AE4 ; ID_Continue # Lo [28] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER TAW
+10AE5..10AE6 ; ID_Continue # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW
+10B00..10B35 ; ID_Continue # Lo [54] AVESTAN LETTER A..AVESTAN LETTER HE
+10B40..10B55 ; ID_Continue # Lo [22] INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW
+10B60..10B72 ; ID_Continue # Lo [19] INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW
+10B80..10B91 ; ID_Continue # Lo [18] PSALTER PAHLAVI LETTER ALEPH..PSALTER PAHLAVI LETTER TAW
+10C00..10C48 ; ID_Continue # Lo [73] OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH
+10C80..10CB2 ; ID_Continue # L& [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US
+10CC0..10CF2 ; ID_Continue # L& [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US
+10D00..10D23 ; ID_Continue # Lo [36] HANIFI ROHINGYA LETTER A..HANIFI ROHINGYA MARK NA KHONNA
+10D24..10D27 ; ID_Continue # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI
+10D30..10D39 ; ID_Continue # Nd [10] HANIFI ROHINGYA DIGIT ZERO..HANIFI ROHINGYA DIGIT NINE
+10D40..10D49 ; ID_Continue # Nd [10] GARAY DIGIT ZERO..GARAY DIGIT NINE
+10D4A..10D4D ; ID_Continue # Lo [4] GARAY VOWEL SIGN A..GARAY VOWEL SIGN EE
+10D4E ; ID_Continue # Lm GARAY VOWEL LENGTH MARK
+10D4F ; ID_Continue # Lo GARAY SUKUN
+10D50..10D65 ; ID_Continue # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA
+10D69..10D6D ; ID_Continue # Mn [5] GARAY VOWEL SIGN E..GARAY CONSONANT NASALIZATION MARK
+10D6F ; ID_Continue # Lm GARAY REDUPLICATION MARK
+10D70..10D85 ; ID_Continue # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA
+10E80..10EA9 ; ID_Continue # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET
+10EAB..10EAC ; ID_Continue # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK
+10EB0..10EB1 ; ID_Continue # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE
+10EC2..10EC4 ; ID_Continue # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW
+10EFC..10EFF ; ID_Continue # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA
+10F00..10F1C ; ID_Continue # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL
+10F27 ; ID_Continue # Lo OLD SOGDIAN LIGATURE AYIN-DALETH
+10F30..10F45 ; ID_Continue # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN
+10F46..10F50 ; ID_Continue # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW
+10F70..10F81 ; ID_Continue # Lo [18] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER LESH
+10F82..10F85 ; ID_Continue # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW
+10FB0..10FC4 ; ID_Continue # Lo [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW
+10FE0..10FF6 ; ID_Continue # Lo [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH
+11000 ; ID_Continue # Mc BRAHMI SIGN CANDRABINDU
+11001 ; ID_Continue # Mn BRAHMI SIGN ANUSVARA
+11002 ; ID_Continue # Mc BRAHMI SIGN VISARGA
+11003..11037 ; ID_Continue # Lo [53] BRAHMI SIGN JIHVAMULIYA..BRAHMI LETTER OLD TAMIL NNNA
+11038..11046 ; ID_Continue # Mn [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA
+11066..1106F ; ID_Continue # Nd [10] BRAHMI DIGIT ZERO..BRAHMI DIGIT NINE
+11070 ; ID_Continue # Mn BRAHMI SIGN OLD TAMIL VIRAMA
+11071..11072 ; ID_Continue # Lo [2] BRAHMI LETTER OLD TAMIL SHORT E..BRAHMI LETTER OLD TAMIL SHORT O
+11073..11074 ; ID_Continue # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O
+11075 ; ID_Continue # Lo BRAHMI LETTER OLD TAMIL LLA
+1107F..11081 ; ID_Continue # Mn [3] BRAHMI NUMBER JOINER..KAITHI SIGN ANUSVARA
+11082 ; ID_Continue # Mc KAITHI SIGN VISARGA
+11083..110AF ; ID_Continue # Lo [45] KAITHI LETTER A..KAITHI LETTER HA
+110B0..110B2 ; ID_Continue # Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II
+110B3..110B6 ; ID_Continue # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI
+110B7..110B8 ; ID_Continue # Mc [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU
+110B9..110BA ; ID_Continue # Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA
+110C2 ; ID_Continue # Mn KAITHI VOWEL SIGN VOCALIC R
+110D0..110E8 ; ID_Continue # Lo [25] SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE
+110F0..110F9 ; ID_Continue # Nd [10] SORA SOMPENG DIGIT ZERO..SORA SOMPENG DIGIT NINE
+11100..11102 ; ID_Continue # Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA
+11103..11126 ; ID_Continue # Lo [36] CHAKMA LETTER AA..CHAKMA LETTER HAA
+11127..1112B ; ID_Continue # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU
+1112C ; ID_Continue # Mc CHAKMA VOWEL SIGN E
+1112D..11134 ; ID_Continue # Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA
+11136..1113F ; ID_Continue # Nd [10] CHAKMA DIGIT ZERO..CHAKMA DIGIT NINE
+11144 ; ID_Continue # Lo CHAKMA LETTER LHAA
+11145..11146 ; ID_Continue # Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI
+11147 ; ID_Continue # Lo CHAKMA LETTER VAA
+11150..11172 ; ID_Continue # Lo [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA
+11173 ; ID_Continue # Mn MAHAJANI SIGN NUKTA
+11176 ; ID_Continue # Lo MAHAJANI LIGATURE SHRI
+11180..11181 ; ID_Continue # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA
+11182 ; ID_Continue # Mc SHARADA SIGN VISARGA
+11183..111B2 ; ID_Continue # Lo [48] SHARADA LETTER A..SHARADA LETTER HA
+111B3..111B5 ; ID_Continue # Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II
+111B6..111BE ; ID_Continue # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O
+111BF..111C0 ; ID_Continue # Mc [2] SHARADA VOWEL SIGN AU..SHARADA SIGN VIRAMA
+111C1..111C4 ; ID_Continue # Lo [4] SHARADA SIGN AVAGRAHA..SHARADA OM
+111C9..111CC ; ID_Continue # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK
+111CE ; ID_Continue # Mc SHARADA VOWEL SIGN PRISHTHAMATRA E
+111CF ; ID_Continue # Mn SHARADA SIGN INVERTED CANDRABINDU
+111D0..111D9 ; ID_Continue # Nd [10] SHARADA DIGIT ZERO..SHARADA DIGIT NINE
+111DA ; ID_Continue # Lo SHARADA EKAM
+111DC ; ID_Continue # Lo SHARADA HEADSTROKE
+11200..11211 ; ID_Continue # Lo [18] KHOJKI LETTER A..KHOJKI LETTER JJA
+11213..1122B ; ID_Continue # Lo [25] KHOJKI LETTER NYA..KHOJKI LETTER LLA
+1122C..1122E ; ID_Continue # Mc [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II
+1122F..11231 ; ID_Continue # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI
+11232..11233 ; ID_Continue # Mc [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU
+11234 ; ID_Continue # Mn KHOJKI SIGN ANUSVARA
+11235 ; ID_Continue # Mc KHOJKI SIGN VIRAMA
+11236..11237 ; ID_Continue # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA
+1123E ; ID_Continue # Mn KHOJKI SIGN SUKUN
+1123F..11240 ; ID_Continue # Lo [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I
+11241 ; ID_Continue # Mn KHOJKI VOWEL SIGN VOCALIC R
+11280..11286 ; ID_Continue # Lo [7] MULTANI LETTER A..MULTANI LETTER GA
+11288 ; ID_Continue # Lo MULTANI LETTER GHA
+1128A..1128D ; ID_Continue # Lo [4] MULTANI LETTER CA..MULTANI LETTER JJA
+1128F..1129D ; ID_Continue # Lo [15] MULTANI LETTER NYA..MULTANI LETTER BA
+1129F..112A8 ; ID_Continue # Lo [10] MULTANI LETTER BHA..MULTANI LETTER RHA
+112B0..112DE ; ID_Continue # Lo [47] KHUDAWADI LETTER A..KHUDAWADI LETTER HA
+112DF ; ID_Continue # Mn KHUDAWADI SIGN ANUSVARA
+112E0..112E2 ; ID_Continue # Mc [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II
+112E3..112EA ; ID_Continue # Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA
+112F0..112F9 ; ID_Continue # Nd [10] KHUDAWADI DIGIT ZERO..KHUDAWADI DIGIT NINE
+11300..11301 ; ID_Continue # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU
+11302..11303 ; ID_Continue # Mc [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA
+11305..1130C ; ID_Continue # Lo [8] GRANTHA LETTER A..GRANTHA LETTER VOCALIC L
+1130F..11310 ; ID_Continue # Lo [2] GRANTHA LETTER EE..GRANTHA LETTER AI
+11313..11328 ; ID_Continue # Lo [22] GRANTHA LETTER OO..GRANTHA LETTER NA
+1132A..11330 ; ID_Continue # Lo [7] GRANTHA LETTER PA..GRANTHA LETTER RA
+11332..11333 ; ID_Continue # Lo [2] GRANTHA LETTER LA..GRANTHA LETTER LLA
+11335..11339 ; ID_Continue # Lo [5] GRANTHA LETTER VA..GRANTHA LETTER HA
+1133B..1133C ; ID_Continue # Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA
+1133D ; ID_Continue # Lo GRANTHA SIGN AVAGRAHA
+1133E..1133F ; ID_Continue # Mc [2] GRANTHA VOWEL SIGN AA..GRANTHA VOWEL SIGN I
+11340 ; ID_Continue # Mn GRANTHA VOWEL SIGN II
+11341..11344 ; ID_Continue # Mc [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR
+11347..11348 ; ID_Continue # Mc [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI
+1134B..1134D ; ID_Continue # Mc [3] GRANTHA VOWEL SIGN OO..GRANTHA SIGN VIRAMA
+11350 ; ID_Continue # Lo GRANTHA OM
+11357 ; ID_Continue # Mc GRANTHA AU LENGTH MARK
+1135D..11361 ; ID_Continue # Lo [5] GRANTHA SIGN PLUTA..GRANTHA LETTER VOCALIC LL
+11362..11363 ; ID_Continue # Mc [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL
+11366..1136C ; ID_Continue # Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX
+11370..11374 ; ID_Continue # Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA
+11380..11389 ; ID_Continue # Lo [10] TULU-TIGALARI LETTER A..TULU-TIGALARI LETTER VOCALIC LL
+1138B ; ID_Continue # Lo TULU-TIGALARI LETTER EE
+1138E ; ID_Continue # Lo TULU-TIGALARI LETTER AI
+11390..113B5 ; ID_Continue # Lo [38] TULU-TIGALARI LETTER OO..TULU-TIGALARI LETTER LLLA
+113B7 ; ID_Continue # Lo TULU-TIGALARI SIGN AVAGRAHA
+113B8..113BA ; ID_Continue # Mc [3] TULU-TIGALARI VOWEL SIGN AA..TULU-TIGALARI VOWEL SIGN II
+113BB..113C0 ; ID_Continue # Mn [6] TULU-TIGALARI VOWEL SIGN U..TULU-TIGALARI VOWEL SIGN VOCALIC LL
+113C2 ; ID_Continue # Mc TULU-TIGALARI VOWEL SIGN EE
+113C5 ; ID_Continue # Mc TULU-TIGALARI VOWEL SIGN AI
+113C7..113CA ; ID_Continue # Mc [4] TULU-TIGALARI VOWEL SIGN OO..TULU-TIGALARI SIGN CANDRA ANUNASIKA
+113CC..113CD ; ID_Continue # Mc [2] TULU-TIGALARI SIGN ANUSVARA..TULU-TIGALARI SIGN VISARGA
+113CE ; ID_Continue # Mn TULU-TIGALARI SIGN VIRAMA
+113CF ; ID_Continue # Mc TULU-TIGALARI SIGN LOOPED VIRAMA
+113D0 ; ID_Continue # Mn TULU-TIGALARI CONJOINER
+113D1 ; ID_Continue # Lo TULU-TIGALARI REPHA
+113D2 ; ID_Continue # Mn TULU-TIGALARI GEMINATION MARK
+113D3 ; ID_Continue # Lo TULU-TIGALARI SIGN PLUTA
+113E1..113E2 ; ID_Continue # Mn [2] TULU-TIGALARI VEDIC TONE SVARITA..TULU-TIGALARI VEDIC TONE ANUDATTA
+11400..11434 ; ID_Continue # Lo [53] NEWA LETTER A..NEWA LETTER HA
+11435..11437 ; ID_Continue # Mc [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II
+11438..1143F ; ID_Continue # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI
+11440..11441 ; ID_Continue # Mc [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU
+11442..11444 ; ID_Continue # Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA
+11445 ; ID_Continue # Mc NEWA SIGN VISARGA
+11446 ; ID_Continue # Mn NEWA SIGN NUKTA
+11447..1144A ; ID_Continue # Lo [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI
+11450..11459 ; ID_Continue # Nd [10] NEWA DIGIT ZERO..NEWA DIGIT NINE
+1145E ; ID_Continue # Mn NEWA SANDHI MARK
+1145F..11461 ; ID_Continue # Lo [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA
+11480..114AF ; ID_Continue # Lo [48] TIRHUTA ANJI..TIRHUTA LETTER HA
+114B0..114B2 ; ID_Continue # Mc [3] TIRHUTA VOWEL SIGN AA..TIRHUTA VOWEL SIGN II
+114B3..114B8 ; ID_Continue # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL
+114B9 ; ID_Continue # Mc TIRHUTA VOWEL SIGN E
+114BA ; ID_Continue # Mn TIRHUTA VOWEL SIGN SHORT E
+114BB..114BE ; ID_Continue # Mc [4] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN AU
+114BF..114C0 ; ID_Continue # Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA
+114C1 ; ID_Continue # Mc TIRHUTA SIGN VISARGA
+114C2..114C3 ; ID_Continue # Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA
+114C4..114C5 ; ID_Continue # Lo [2] TIRHUTA SIGN AVAGRAHA..TIRHUTA GVANG
+114C7 ; ID_Continue # Lo TIRHUTA OM
+114D0..114D9 ; ID_Continue # Nd [10] TIRHUTA DIGIT ZERO..TIRHUTA DIGIT NINE
+11580..115AE ; ID_Continue # Lo [47] SIDDHAM LETTER A..SIDDHAM LETTER HA
+115AF..115B1 ; ID_Continue # Mc [3] SIDDHAM VOWEL SIGN AA..SIDDHAM VOWEL SIGN II
+115B2..115B5 ; ID_Continue # Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR
+115B8..115BB ; ID_Continue # Mc [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU
+115BC..115BD ; ID_Continue # Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA
+115BE ; ID_Continue # Mc SIDDHAM SIGN VISARGA
+115BF..115C0 ; ID_Continue # Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA
+115D8..115DB ; ID_Continue # Lo [4] SIDDHAM LETTER THREE-CIRCLE ALTERNATE I..SIDDHAM LETTER ALTERNATE U
+115DC..115DD ; ID_Continue # Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU
+11600..1162F ; ID_Continue # Lo [48] MODI LETTER A..MODI LETTER LLA
+11630..11632 ; ID_Continue # Mc [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II
+11633..1163A ; ID_Continue # Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI
+1163B..1163C ; ID_Continue # Mc [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU
+1163D ; ID_Continue # Mn MODI SIGN ANUSVARA
+1163E ; ID_Continue # Mc MODI SIGN VISARGA
+1163F..11640 ; ID_Continue # Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA
+11644 ; ID_Continue # Lo MODI SIGN HUVA
+11650..11659 ; ID_Continue # Nd [10] MODI DIGIT ZERO..MODI DIGIT NINE
+11680..116AA ; ID_Continue # Lo [43] TAKRI LETTER A..TAKRI LETTER RRA
+116AB ; ID_Continue # Mn TAKRI SIGN ANUSVARA
+116AC ; ID_Continue # Mc TAKRI SIGN VISARGA
+116AD ; ID_Continue # Mn TAKRI VOWEL SIGN AA
+116AE..116AF ; ID_Continue # Mc [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II
+116B0..116B5 ; ID_Continue # Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU
+116B6 ; ID_Continue # Mc TAKRI SIGN VIRAMA
+116B7 ; ID_Continue # Mn TAKRI SIGN NUKTA
+116B8 ; ID_Continue # Lo TAKRI LETTER ARCHAIC KHA
+116C0..116C9 ; ID_Continue # Nd [10] TAKRI DIGIT ZERO..TAKRI DIGIT NINE
+116D0..116E3 ; ID_Continue # Nd [20] MYANMAR PAO DIGIT ZERO..MYANMAR EASTERN PWO KAREN DIGIT NINE
+11700..1171A ; ID_Continue # Lo [27] AHOM LETTER KA..AHOM LETTER ALTERNATE BA
+1171D ; ID_Continue # Mn AHOM CONSONANT SIGN MEDIAL LA
+1171E ; ID_Continue # Mc AHOM CONSONANT SIGN MEDIAL RA
+1171F ; ID_Continue # Mn AHOM CONSONANT SIGN MEDIAL LIGATING RA
+11720..11721 ; ID_Continue # Mc [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA
+11722..11725 ; ID_Continue # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU
+11726 ; ID_Continue # Mc AHOM VOWEL SIGN E
+11727..1172B ; ID_Continue # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER
+11730..11739 ; ID_Continue # Nd [10] AHOM DIGIT ZERO..AHOM DIGIT NINE
+11740..11746 ; ID_Continue # Lo [7] AHOM LETTER CA..AHOM LETTER LLA
+11800..1182B ; ID_Continue # Lo [44] DOGRA LETTER A..DOGRA LETTER RRA
+1182C..1182E ; ID_Continue # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II
+1182F..11837 ; ID_Continue # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA
+11838 ; ID_Continue # Mc DOGRA SIGN VISARGA
+11839..1183A ; ID_Continue # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA
+118A0..118DF ; ID_Continue # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO
+118E0..118E9 ; ID_Continue # Nd [10] WARANG CITI DIGIT ZERO..WARANG CITI DIGIT NINE
+118FF..11906 ; ID_Continue # Lo [8] WARANG CITI OM..DIVES AKURU LETTER E
+11909 ; ID_Continue # Lo DIVES AKURU LETTER O
+1190C..11913 ; ID_Continue # Lo [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA
+11915..11916 ; ID_Continue # Lo [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA
+11918..1192F ; ID_Continue # Lo [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA
+11930..11935 ; ID_Continue # Mc [6] DIVES AKURU VOWEL SIGN AA..DIVES AKURU VOWEL SIGN E
+11937..11938 ; ID_Continue # Mc [2] DIVES AKURU VOWEL SIGN AI..DIVES AKURU VOWEL SIGN O
+1193B..1193C ; ID_Continue # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU
+1193D ; ID_Continue # Mc DIVES AKURU SIGN HALANTA
+1193E ; ID_Continue # Mn DIVES AKURU VIRAMA
+1193F ; ID_Continue # Lo DIVES AKURU PREFIXED NASAL SIGN
+11940 ; ID_Continue # Mc DIVES AKURU MEDIAL YA
+11941 ; ID_Continue # Lo DIVES AKURU INITIAL RA
+11942 ; ID_Continue # Mc DIVES AKURU MEDIAL RA
+11943 ; ID_Continue # Mn DIVES AKURU SIGN NUKTA
+11950..11959 ; ID_Continue # Nd [10] DIVES AKURU DIGIT ZERO..DIVES AKURU DIGIT NINE
+119A0..119A7 ; ID_Continue # Lo [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR
+119AA..119D0 ; ID_Continue # Lo [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA
+119D1..119D3 ; ID_Continue # Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II
+119D4..119D7 ; ID_Continue # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR
+119DA..119DB ; ID_Continue # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI
+119DC..119DF ; ID_Continue # Mc [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA
+119E0 ; ID_Continue # Mn NANDINAGARI SIGN VIRAMA
+119E1 ; ID_Continue # Lo NANDINAGARI SIGN AVAGRAHA
+119E3 ; ID_Continue # Lo NANDINAGARI HEADSTROKE
+119E4 ; ID_Continue # Mc NANDINAGARI VOWEL SIGN PRISHTHAMATRA E
+11A00 ; ID_Continue # Lo ZANABAZAR SQUARE LETTER A
+11A01..11A0A ; ID_Continue # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK
+11A0B..11A32 ; ID_Continue # Lo [40] ZANABAZAR SQUARE LETTER KA..ZANABAZAR SQUARE LETTER KSSA
+11A33..11A38 ; ID_Continue # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA
+11A39 ; ID_Continue # Mc ZANABAZAR SQUARE SIGN VISARGA
+11A3A ; ID_Continue # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA
+11A3B..11A3E ; ID_Continue # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA
+11A47 ; ID_Continue # Mn ZANABAZAR SQUARE SUBJOINER
+11A50 ; ID_Continue # Lo SOYOMBO LETTER A
+11A51..11A56 ; ID_Continue # Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE
+11A57..11A58 ; ID_Continue # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU
+11A59..11A5B ; ID_Continue # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK
+11A5C..11A89 ; ID_Continue # Lo [46] SOYOMBO LETTER KA..SOYOMBO CLUSTER-INITIAL LETTER SA
+11A8A..11A96 ; ID_Continue # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA
+11A97 ; ID_Continue # Mc SOYOMBO SIGN VISARGA
+11A98..11A99 ; ID_Continue # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER
+11A9D ; ID_Continue # Lo SOYOMBO MARK PLUTA
+11AB0..11AF8 ; ID_Continue # Lo [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL
+11BC0..11BE0 ; ID_Continue # Lo [33] SUNUWAR LETTER DEVI..SUNUWAR LETTER KLOKO
+11BF0..11BF9 ; ID_Continue # Nd [10] SUNUWAR DIGIT ZERO..SUNUWAR DIGIT NINE
+11C00..11C08 ; ID_Continue # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L
+11C0A..11C2E ; ID_Continue # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA
+11C2F ; ID_Continue # Mc BHAIKSUKI VOWEL SIGN AA
+11C30..11C36 ; ID_Continue # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L
+11C38..11C3D ; ID_Continue # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA
+11C3E ; ID_Continue # Mc BHAIKSUKI SIGN VISARGA
+11C3F ; ID_Continue # Mn BHAIKSUKI SIGN VIRAMA
+11C40 ; ID_Continue # Lo BHAIKSUKI SIGN AVAGRAHA
+11C50..11C59 ; ID_Continue # Nd [10] BHAIKSUKI DIGIT ZERO..BHAIKSUKI DIGIT NINE
+11C72..11C8F ; ID_Continue # Lo [30] MARCHEN LETTER KA..MARCHEN LETTER A
+11C92..11CA7 ; ID_Continue # Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA
+11CA9 ; ID_Continue # Mc MARCHEN SUBJOINED LETTER YA
+11CAA..11CB0 ; ID_Continue # Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA
+11CB1 ; ID_Continue # Mc MARCHEN VOWEL SIGN I
+11CB2..11CB3 ; ID_Continue # Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E
+11CB4 ; ID_Continue # Mc MARCHEN VOWEL SIGN O
+11CB5..11CB6 ; ID_Continue # Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU
+11D00..11D06 ; ID_Continue # Lo [7] MASARAM GONDI LETTER A..MASARAM GONDI LETTER E
+11D08..11D09 ; ID_Continue # Lo [2] MASARAM GONDI LETTER AI..MASARAM GONDI LETTER O
+11D0B..11D30 ; ID_Continue # Lo [38] MASARAM GONDI LETTER AU..MASARAM GONDI LETTER TRA
+11D31..11D36 ; ID_Continue # Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R
+11D3A ; ID_Continue # Mn MASARAM GONDI VOWEL SIGN E
+11D3C..11D3D ; ID_Continue # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O
+11D3F..11D45 ; ID_Continue # Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA
+11D46 ; ID_Continue # Lo MASARAM GONDI REPHA
+11D47 ; ID_Continue # Mn MASARAM GONDI RA-KARA
+11D50..11D59 ; ID_Continue # Nd [10] MASARAM GONDI DIGIT ZERO..MASARAM GONDI DIGIT NINE
+11D60..11D65 ; ID_Continue # Lo [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU
+11D67..11D68 ; ID_Continue # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI
+11D6A..11D89 ; ID_Continue # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA
+11D8A..11D8E ; ID_Continue # Mc [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU
+11D90..11D91 ; ID_Continue # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI
+11D93..11D94 ; ID_Continue # Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU
+11D95 ; ID_Continue # Mn GUNJALA GONDI SIGN ANUSVARA
+11D96 ; ID_Continue # Mc GUNJALA GONDI SIGN VISARGA
+11D97 ; ID_Continue # Mn GUNJALA GONDI VIRAMA
+11D98 ; ID_Continue # Lo GUNJALA GONDI OM
+11DA0..11DA9 ; ID_Continue # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE
+11EE0..11EF2 ; ID_Continue # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA
+11EF3..11EF4 ; ID_Continue # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U
+11EF5..11EF6 ; ID_Continue # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O
+11F00..11F01 ; ID_Continue # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA
+11F02 ; ID_Continue # Lo KAWI SIGN REPHA
+11F03 ; ID_Continue # Mc KAWI SIGN VISARGA
+11F04..11F10 ; ID_Continue # Lo [13] KAWI LETTER A..KAWI LETTER O
+11F12..11F33 ; ID_Continue # Lo [34] KAWI LETTER KA..KAWI LETTER JNYA
+11F34..11F35 ; ID_Continue # Mc [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA
+11F36..11F3A ; ID_Continue # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R
+11F3E..11F3F ; ID_Continue # Mc [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI
+11F40 ; ID_Continue # Mn KAWI VOWEL SIGN EU
+11F41 ; ID_Continue # Mc KAWI SIGN KILLER
+11F42 ; ID_Continue # Mn KAWI CONJOINER
+11F50..11F59 ; ID_Continue # Nd [10] KAWI DIGIT ZERO..KAWI DIGIT NINE
+11F5A ; ID_Continue # Mn KAWI SIGN NUKTA
+11FB0 ; ID_Continue # Lo LISU LETTER YHA
+12000..12399 ; ID_Continue # Lo [922] CUNEIFORM SIGN A..CUNEIFORM SIGN U U
+12400..1246E ; ID_Continue # Nl [111] CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM
+12480..12543 ; ID_Continue # Lo [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU
+12F90..12FF0 ; ID_Continue # Lo [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114
+13000..1342F ; ID_Continue # Lo [1072] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH V011D
+13440 ; ID_Continue # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY
+13441..13446 ; ID_Continue # Lo [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN
+13447..13455 ; ID_Continue # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED
+13460..143FA ; ID_Continue # Lo [3995] EGYPTIAN HIEROGLYPH-13460..EGYPTIAN HIEROGLYPH-143FA
+14400..14646 ; ID_Continue # Lo [583] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A530
+16100..1611D ; ID_Continue # Lo [30] GURUNG KHEMA LETTER A..GURUNG KHEMA LETTER SA
+1611E..16129 ; ID_Continue # Mn [12] GURUNG KHEMA VOWEL SIGN AA..GURUNG KHEMA VOWEL LENGTH MARK
+1612A..1612C ; ID_Continue # Mc [3] GURUNG KHEMA CONSONANT SIGN MEDIAL YA..GURUNG KHEMA CONSONANT SIGN MEDIAL HA
+1612D..1612F ; ID_Continue # Mn [3] GURUNG KHEMA SIGN ANUSVARA..GURUNG KHEMA SIGN THOLHOMA
+16130..16139 ; ID_Continue # Nd [10] GURUNG KHEMA DIGIT ZERO..GURUNG KHEMA DIGIT NINE
+16800..16A38 ; ID_Continue # Lo [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ
+16A40..16A5E ; ID_Continue # Lo [31] MRO LETTER TA..MRO LETTER TEK
+16A60..16A69 ; ID_Continue # Nd [10] MRO DIGIT ZERO..MRO DIGIT NINE
+16A70..16ABE ; ID_Continue # Lo [79] TANGSA LETTER OZ..TANGSA LETTER ZA
+16AC0..16AC9 ; ID_Continue # Nd [10] TANGSA DIGIT ZERO..TANGSA DIGIT NINE
+16AD0..16AED ; ID_Continue # Lo [30] BASSA VAH LETTER ENNI..BASSA VAH LETTER I
+16AF0..16AF4 ; ID_Continue # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE
+16B00..16B2F ; ID_Continue # Lo [48] PAHAWH HMONG VOWEL KEEB..PAHAWH HMONG CONSONANT CAU
+16B30..16B36 ; ID_Continue # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
+16B40..16B43 ; ID_Continue # Lm [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM
+16B50..16B59 ; ID_Continue # Nd [10] PAHAWH HMONG DIGIT ZERO..PAHAWH HMONG DIGIT NINE
+16B63..16B77 ; ID_Continue # Lo [21] PAHAWH HMONG SIGN VOS LUB..PAHAWH HMONG SIGN CIM NRES TOS
+16B7D..16B8F ; ID_Continue # Lo [19] PAHAWH HMONG CLAN SIGN TSHEEJ..PAHAWH HMONG CLAN SIGN VWJ
+16D40..16D42 ; ID_Continue # Lm [3] KIRAT RAI SIGN ANUSVARA..KIRAT RAI SIGN VISARGA
+16D43..16D6A ; ID_Continue # Lo [40] KIRAT RAI LETTER A..KIRAT RAI VOWEL SIGN AU
+16D6B..16D6C ; ID_Continue # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT
+16D70..16D79 ; ID_Continue # Nd [10] KIRAT RAI DIGIT ZERO..KIRAT RAI DIGIT NINE
+16E40..16E7F ; ID_Continue # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y
+16F00..16F4A ; ID_Continue # Lo [75] MIAO LETTER PA..MIAO LETTER RTE
+16F4F ; ID_Continue # Mn MIAO SIGN CONSONANT MODIFIER BAR
+16F50 ; ID_Continue # Lo MIAO LETTER NASALIZATION
+16F51..16F87 ; ID_Continue # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI
+16F8F..16F92 ; ID_Continue # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
+16F93..16F9F ; ID_Continue # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8
+16FE0..16FE1 ; ID_Continue # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK
+16FE3 ; ID_Continue # Lm OLD CHINESE ITERATION MARK
+16FE4 ; ID_Continue # Mn KHITAN SMALL SCRIPT FILLER
+16FF0..16FF1 ; ID_Continue # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY
+17000..187F7 ; ID_Continue # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7
+18800..18CD5 ; ID_Continue # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5
+18CFF..18D08 ; ID_Continue # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08
+1AFF0..1AFF3 ; ID_Continue # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5
+1AFF5..1AFFB ; ID_Continue # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5
+1AFFD..1AFFE ; ID_Continue # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8
+1B000..1B122 ; ID_Continue # Lo [291] KATAKANA LETTER ARCHAIC E..KATAKANA LETTER ARCHAIC WU
+1B132 ; ID_Continue # Lo HIRAGANA LETTER SMALL KO
+1B150..1B152 ; ID_Continue # Lo [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO
+1B155 ; ID_Continue # Lo KATAKANA LETTER SMALL KO
+1B164..1B167 ; ID_Continue # Lo [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N
+1B170..1B2FB ; ID_Continue # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB
+1BC00..1BC6A ; ID_Continue # Lo [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M
+1BC70..1BC7C ; ID_Continue # Lo [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK
+1BC80..1BC88 ; ID_Continue # Lo [9] DUPLOYAN AFFIX HIGH ACUTE..DUPLOYAN AFFIX HIGH VERTICAL
+1BC90..1BC99 ; ID_Continue # Lo [10] DUPLOYAN AFFIX LOW ACUTE..DUPLOYAN AFFIX LOW ARROW
+1BC9D..1BC9E ; ID_Continue # Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK
+1CCF0..1CCF9 ; ID_Continue # Nd [10] OUTLINED DIGIT ZERO..OUTLINED DIGIT NINE
+1CF00..1CF2D ; ID_Continue # Mn [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT
+1CF30..1CF46 ; ID_Continue # Mn [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG
+1D165..1D166 ; ID_Continue # Mc [2] MUSICAL SYMBOL COMBINING STEM..MUSICAL SYMBOL COMBINING SPRECHGESANG STEM
+1D167..1D169 ; ID_Continue # Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3
+1D16D..1D172 ; ID_Continue # Mc [6] MUSICAL SYMBOL COMBINING AUGMENTATION DOT..MUSICAL SYMBOL COMBINING FLAG-5
+1D17B..1D182 ; ID_Continue # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE
+1D185..1D18B ; ID_Continue # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE
+1D1AA..1D1AD ; ID_Continue # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO
+1D242..1D244 ; ID_Continue # Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME
+1D400..1D454 ; ID_Continue # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G
+1D456..1D49C ; ID_Continue # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A
+1D49E..1D49F ; ID_Continue # L& [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D
+1D4A2 ; ID_Continue # L& MATHEMATICAL SCRIPT CAPITAL G
+1D4A5..1D4A6 ; ID_Continue # L& [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K
+1D4A9..1D4AC ; ID_Continue # L& [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q
+1D4AE..1D4B9 ; ID_Continue # L& [12] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D
+1D4BB ; ID_Continue # L& MATHEMATICAL SCRIPT SMALL F
+1D4BD..1D4C3 ; ID_Continue # L& [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N
+1D4C5..1D505 ; ID_Continue # L& [65] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B
+1D507..1D50A ; ID_Continue # L& [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G
+1D50D..1D514 ; ID_Continue # L& [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q
+1D516..1D51C ; ID_Continue # L& [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y
+1D51E..1D539 ; ID_Continue # L& [28] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B
+1D53B..1D53E ; ID_Continue # L& [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G
+1D540..1D544 ; ID_Continue # L& [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M
+1D546 ; ID_Continue # L& MATHEMATICAL DOUBLE-STRUCK CAPITAL O
+1D54A..1D550 ; ID_Continue # L& [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y
+1D552..1D6A5 ; ID_Continue # L& [340] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J
+1D6A8..1D6C0 ; ID_Continue # L& [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA
+1D6C2..1D6DA ; ID_Continue # L& [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA
+1D6DC..1D6FA ; ID_Continue # L& [31] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL ITALIC CAPITAL OMEGA
+1D6FC..1D714 ; ID_Continue # L& [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA
+1D716..1D734 ; ID_Continue # L& [31] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA
+1D736..1D74E ; ID_Continue # L& [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA
+1D750..1D76E ; ID_Continue # L& [31] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA
+1D770..1D788 ; ID_Continue # L& [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA
+1D78A..1D7A8 ; ID_Continue # L& [31] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA
+1D7AA..1D7C2 ; ID_Continue # L& [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA
+1D7C4..1D7CB ; ID_Continue # L& [8] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD SMALL DIGAMMA
+1D7CE..1D7FF ; ID_Continue # Nd [50] MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE
+1DA00..1DA36 ; ID_Continue # Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN
+1DA3B..1DA6C ; ID_Continue # Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT
+1DA75 ; ID_Continue # Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS
+1DA84 ; ID_Continue # Mn SIGNWRITING LOCATION HEAD NECK
+1DA9B..1DA9F ; ID_Continue # Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6
+1DAA1..1DAAF ; ID_Continue # Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16
+1DF00..1DF09 ; ID_Continue # L& [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK
+1DF0A ; ID_Continue # Lo LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK
+1DF0B..1DF1E ; ID_Continue # L& [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL
+1DF25..1DF2A ; ID_Continue # L& [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK
+1E000..1E006 ; ID_Continue # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE
+1E008..1E018 ; ID_Continue # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU
+1E01B..1E021 ; ID_Continue # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI
+1E023..1E024 ; ID_Continue # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS
+1E026..1E02A ; ID_Continue # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA
+1E030..1E06D ; ID_Continue # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE
+1E08F ; ID_Continue # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+1E100..1E12C ; ID_Continue # Lo [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W
+1E130..1E136 ; ID_Continue # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D
+1E137..1E13D ; ID_Continue # Lm [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER
+1E140..1E149 ; ID_Continue # Nd [10] NYIAKENG PUACHUE HMONG DIGIT ZERO..NYIAKENG PUACHUE HMONG DIGIT NINE
+1E14E ; ID_Continue # Lo NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ
+1E290..1E2AD ; ID_Continue # Lo [30] TOTO LETTER PA..TOTO LETTER A
+1E2AE ; ID_Continue # Mn TOTO SIGN RISING TONE
+1E2C0..1E2EB ; ID_Continue # Lo [44] WANCHO LETTER AA..WANCHO LETTER YIH
+1E2EC..1E2EF ; ID_Continue # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI
+1E2F0..1E2F9 ; ID_Continue # Nd [10] WANCHO DIGIT ZERO..WANCHO DIGIT NINE
+1E4D0..1E4EA ; ID_Continue # Lo [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL
+1E4EB ; ID_Continue # Lm NAG MUNDARI SIGN OJOD
+1E4EC..1E4EF ; ID_Continue # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH
+1E4F0..1E4F9 ; ID_Continue # Nd [10] NAG MUNDARI DIGIT ZERO..NAG MUNDARI DIGIT NINE
+1E5D0..1E5ED ; ID_Continue # Lo [30] OL ONAL LETTER O..OL ONAL LETTER EG
+1E5EE..1E5EF ; ID_Continue # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR
+1E5F0 ; ID_Continue # Lo OL ONAL SIGN HODDOND
+1E5F1..1E5FA ; ID_Continue # Nd [10] OL ONAL DIGIT ZERO..OL ONAL DIGIT NINE
+1E7E0..1E7E6 ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO
+1E7E8..1E7EB ; ID_Continue # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE
+1E7ED..1E7EE ; ID_Continue # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE
+1E7F0..1E7FE ; ID_Continue # Lo [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE
+1E800..1E8C4 ; ID_Continue # Lo [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON
+1E8D0..1E8D6 ; ID_Continue # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS
+1E900..1E943 ; ID_Continue # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA
+1E944..1E94A ; ID_Continue # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA
+1E94B ; ID_Continue # Lm ADLAM NASALIZATION MARK
+1E950..1E959 ; ID_Continue # Nd [10] ADLAM DIGIT ZERO..ADLAM DIGIT NINE
+1EE00..1EE03 ; ID_Continue # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL
+1EE05..1EE1F ; ID_Continue # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF
+1EE21..1EE22 ; ID_Continue # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM
+1EE24 ; ID_Continue # Lo ARABIC MATHEMATICAL INITIAL HEH
+1EE27 ; ID_Continue # Lo ARABIC MATHEMATICAL INITIAL HAH
+1EE29..1EE32 ; ID_Continue # Lo [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF
+1EE34..1EE37 ; ID_Continue # Lo [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH
+1EE39 ; ID_Continue # Lo ARABIC MATHEMATICAL INITIAL DAD
+1EE3B ; ID_Continue # Lo ARABIC MATHEMATICAL INITIAL GHAIN
+1EE42 ; ID_Continue # Lo ARABIC MATHEMATICAL TAILED JEEM
+1EE47 ; ID_Continue # Lo ARABIC MATHEMATICAL TAILED HAH
+1EE49 ; ID_Continue # Lo ARABIC MATHEMATICAL TAILED YEH
+1EE4B ; ID_Continue # Lo ARABIC MATHEMATICAL TAILED LAM
+1EE4D..1EE4F ; ID_Continue # Lo [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN
+1EE51..1EE52 ; ID_Continue # Lo [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF
+1EE54 ; ID_Continue # Lo ARABIC MATHEMATICAL TAILED SHEEN
+1EE57 ; ID_Continue # Lo ARABIC MATHEMATICAL TAILED KHAH
+1EE59 ; ID_Continue # Lo ARABIC MATHEMATICAL TAILED DAD
+1EE5B ; ID_Continue # Lo ARABIC MATHEMATICAL TAILED GHAIN
+1EE5D ; ID_Continue # Lo ARABIC MATHEMATICAL TAILED DOTLESS NOON
+1EE5F ; ID_Continue # Lo ARABIC MATHEMATICAL TAILED DOTLESS QAF
+1EE61..1EE62 ; ID_Continue # Lo [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM
+1EE64 ; ID_Continue # Lo ARABIC MATHEMATICAL STRETCHED HEH
+1EE67..1EE6A ; ID_Continue # Lo [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF
+1EE6C..1EE72 ; ID_Continue # Lo [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF
+1EE74..1EE77 ; ID_Continue # Lo [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH
+1EE79..1EE7C ; ID_Continue # Lo [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH
+1EE7E ; ID_Continue # Lo ARABIC MATHEMATICAL STRETCHED DOTLESS FEH
+1EE80..1EE89 ; ID_Continue # Lo [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH
+1EE8B..1EE9B ; ID_Continue # Lo [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN
+1EEA1..1EEA3 ; ID_Continue # Lo [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL
+1EEA5..1EEA9 ; ID_Continue # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH
+1EEAB..1EEBB ; ID_Continue # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN
+1FBF0..1FBF9 ; ID_Continue # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE
+20000..2A6DF ; ID_Continue # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF
+2A700..2B739 ; ID_Continue # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739
+2B740..2B81D ; ID_Continue # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D
+2B820..2CEA1 ; ID_Continue # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1
+2CEB0..2EBE0 ; ID_Continue # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0
+2EBF0..2EE5D ; ID_Continue # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D
+2F800..2FA1D ; ID_Continue # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D
+30000..3134A ; ID_Continue # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A
+31350..323AF ; ID_Continue # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF
+E0100..E01EF ; ID_Continue # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
+
+# Total code points: 144541
+
+# ================================================
+
+# Derived Property: XID_Start
+# ID_Start modified for closure under NFKx
+# Modified as described in UAX #15
+# NOTE: Does NOT remove the non-NFKx characters.
+# Merely ensures that if isIdentifer(string) then isIdentifier(NFKx(string))
+# NOTE: See UAX #31 for more information
+
+0041..005A ; XID_Start # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+0061..007A ; XID_Start # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+00AA ; XID_Start # Lo FEMININE ORDINAL INDICATOR
+00B5 ; XID_Start # L& MICRO SIGN
+00BA ; XID_Start # Lo MASCULINE ORDINAL INDICATOR
+00C0..00D6 ; XID_Start # L& [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS
+00D8..00F6 ; XID_Start # L& [31] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER O WITH DIAERESIS
+00F8..01BA ; XID_Start # L& [195] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL
+01BB ; XID_Start # Lo LATIN LETTER TWO WITH STROKE
+01BC..01BF ; XID_Start # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN
+01C0..01C3 ; XID_Start # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK
+01C4..0293 ; XID_Start # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL
+0294 ; XID_Start # Lo LATIN LETTER GLOTTAL STOP
+0295..02AF ; XID_Start # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL
+02B0..02C1 ; XID_Start # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP
+02C6..02D1 ; XID_Start # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON
+02E0..02E4 ; XID_Start # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP
+02EC ; XID_Start # Lm MODIFIER LETTER VOICING
+02EE ; XID_Start # Lm MODIFIER LETTER DOUBLE APOSTROPHE
+0370..0373 ; XID_Start # L& [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI
+0374 ; XID_Start # Lm GREEK NUMERAL SIGN
+0376..0377 ; XID_Start # L& [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA
+037B..037D ; XID_Start # L& [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL
+037F ; XID_Start # L& GREEK CAPITAL LETTER YOT
+0386 ; XID_Start # L& GREEK CAPITAL LETTER ALPHA WITH TONOS
+0388..038A ; XID_Start # L& [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS
+038C ; XID_Start # L& GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E..03A1 ; XID_Start # L& [20] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER RHO
+03A3..03F5 ; XID_Start # L& [83] GREEK CAPITAL LETTER SIGMA..GREEK LUNATE EPSILON SYMBOL
+03F7..0481 ; XID_Start # L& [139] GREEK CAPITAL LETTER SHO..CYRILLIC SMALL LETTER KOPPA
+048A..052F ; XID_Start # L& [166] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER EL WITH DESCENDER
+0531..0556 ; XID_Start # L& [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH
+0559 ; XID_Start # Lm ARMENIAN MODIFIER LETTER LEFT HALF RING
+0560..0588 ; XID_Start # L& [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE
+05D0..05EA ; XID_Start # Lo [27] HEBREW LETTER ALEF..HEBREW LETTER TAV
+05EF..05F2 ; XID_Start # Lo [4] HEBREW YOD TRIANGLE..HEBREW LIGATURE YIDDISH DOUBLE YOD
+0620..063F ; XID_Start # Lo [32] ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE
+0640 ; XID_Start # Lm ARABIC TATWEEL
+0641..064A ; XID_Start # Lo [10] ARABIC LETTER FEH..ARABIC LETTER YEH
+066E..066F ; XID_Start # Lo [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF
+0671..06D3 ; XID_Start # Lo [99] ARABIC LETTER ALEF WASLA..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE
+06D5 ; XID_Start # Lo ARABIC LETTER AE
+06E5..06E6 ; XID_Start # Lm [2] ARABIC SMALL WAW..ARABIC SMALL YEH
+06EE..06EF ; XID_Start # Lo [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V
+06FA..06FC ; XID_Start # Lo [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW
+06FF ; XID_Start # Lo ARABIC LETTER HEH WITH INVERTED V
+0710 ; XID_Start # Lo SYRIAC LETTER ALAPH
+0712..072F ; XID_Start # Lo [30] SYRIAC LETTER BETH..SYRIAC LETTER PERSIAN DHALATH
+074D..07A5 ; XID_Start # Lo [89] SYRIAC LETTER SOGDIAN ZHAIN..THAANA LETTER WAAVU
+07B1 ; XID_Start # Lo THAANA LETTER NAA
+07CA..07EA ; XID_Start # Lo [33] NKO LETTER A..NKO LETTER JONA RA
+07F4..07F5 ; XID_Start # Lm [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE
+07FA ; XID_Start # Lm NKO LAJANYALAN
+0800..0815 ; XID_Start # Lo [22] SAMARITAN LETTER ALAF..SAMARITAN LETTER TAAF
+081A ; XID_Start # Lm SAMARITAN MODIFIER LETTER EPENTHETIC YUT
+0824 ; XID_Start # Lm SAMARITAN MODIFIER LETTER SHORT A
+0828 ; XID_Start # Lm SAMARITAN MODIFIER LETTER I
+0840..0858 ; XID_Start # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN
+0860..086A ; XID_Start # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA
+0870..0887 ; XID_Start # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT
+0889..088E ; XID_Start # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL
+08A0..08C8 ; XID_Start # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF
+08C9 ; XID_Start # Lm ARABIC SMALL FARSI YEH
+0904..0939 ; XID_Start # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA
+093D ; XID_Start # Lo DEVANAGARI SIGN AVAGRAHA
+0950 ; XID_Start # Lo DEVANAGARI OM
+0958..0961 ; XID_Start # Lo [10] DEVANAGARI LETTER QA..DEVANAGARI LETTER VOCALIC LL
+0971 ; XID_Start # Lm DEVANAGARI SIGN HIGH SPACING DOT
+0972..0980 ; XID_Start # Lo [15] DEVANAGARI LETTER CANDRA A..BENGALI ANJI
+0985..098C ; XID_Start # Lo [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L
+098F..0990 ; XID_Start # Lo [2] BENGALI LETTER E..BENGALI LETTER AI
+0993..09A8 ; XID_Start # Lo [22] BENGALI LETTER O..BENGALI LETTER NA
+09AA..09B0 ; XID_Start # Lo [7] BENGALI LETTER PA..BENGALI LETTER RA
+09B2 ; XID_Start # Lo BENGALI LETTER LA
+09B6..09B9 ; XID_Start # Lo [4] BENGALI LETTER SHA..BENGALI LETTER HA
+09BD ; XID_Start # Lo BENGALI SIGN AVAGRAHA
+09CE ; XID_Start # Lo BENGALI LETTER KHANDA TA
+09DC..09DD ; XID_Start # Lo [2] BENGALI LETTER RRA..BENGALI LETTER RHA
+09DF..09E1 ; XID_Start # Lo [3] BENGALI LETTER YYA..BENGALI LETTER VOCALIC LL
+09F0..09F1 ; XID_Start # Lo [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL
+09FC ; XID_Start # Lo BENGALI LETTER VEDIC ANUSVARA
+0A05..0A0A ; XID_Start # Lo [6] GURMUKHI LETTER A..GURMUKHI LETTER UU
+0A0F..0A10 ; XID_Start # Lo [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI
+0A13..0A28 ; XID_Start # Lo [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA
+0A2A..0A30 ; XID_Start # Lo [7] GURMUKHI LETTER PA..GURMUKHI LETTER RA
+0A32..0A33 ; XID_Start # Lo [2] GURMUKHI LETTER LA..GURMUKHI LETTER LLA
+0A35..0A36 ; XID_Start # Lo [2] GURMUKHI LETTER VA..GURMUKHI LETTER SHA
+0A38..0A39 ; XID_Start # Lo [2] GURMUKHI LETTER SA..GURMUKHI LETTER HA
+0A59..0A5C ; XID_Start # Lo [4] GURMUKHI LETTER KHHA..GURMUKHI LETTER RRA
+0A5E ; XID_Start # Lo GURMUKHI LETTER FA
+0A72..0A74 ; XID_Start # Lo [3] GURMUKHI IRI..GURMUKHI EK ONKAR
+0A85..0A8D ; XID_Start # Lo [9] GUJARATI LETTER A..GUJARATI VOWEL CANDRA E
+0A8F..0A91 ; XID_Start # Lo [3] GUJARATI LETTER E..GUJARATI VOWEL CANDRA O
+0A93..0AA8 ; XID_Start # Lo [22] GUJARATI LETTER O..GUJARATI LETTER NA
+0AAA..0AB0 ; XID_Start # Lo [7] GUJARATI LETTER PA..GUJARATI LETTER RA
+0AB2..0AB3 ; XID_Start # Lo [2] GUJARATI LETTER LA..GUJARATI LETTER LLA
+0AB5..0AB9 ; XID_Start # Lo [5] GUJARATI LETTER VA..GUJARATI LETTER HA
+0ABD ; XID_Start # Lo GUJARATI SIGN AVAGRAHA
+0AD0 ; XID_Start # Lo GUJARATI OM
+0AE0..0AE1 ; XID_Start # Lo [2] GUJARATI LETTER VOCALIC RR..GUJARATI LETTER VOCALIC LL
+0AF9 ; XID_Start # Lo GUJARATI LETTER ZHA
+0B05..0B0C ; XID_Start # Lo [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L
+0B0F..0B10 ; XID_Start # Lo [2] ORIYA LETTER E..ORIYA LETTER AI
+0B13..0B28 ; XID_Start # Lo [22] ORIYA LETTER O..ORIYA LETTER NA
+0B2A..0B30 ; XID_Start # Lo [7] ORIYA LETTER PA..ORIYA LETTER RA
+0B32..0B33 ; XID_Start # Lo [2] ORIYA LETTER LA..ORIYA LETTER LLA
+0B35..0B39 ; XID_Start # Lo [5] ORIYA LETTER VA..ORIYA LETTER HA
+0B3D ; XID_Start # Lo ORIYA SIGN AVAGRAHA
+0B5C..0B5D ; XID_Start # Lo [2] ORIYA LETTER RRA..ORIYA LETTER RHA
+0B5F..0B61 ; XID_Start # Lo [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL
+0B71 ; XID_Start # Lo ORIYA LETTER WA
+0B83 ; XID_Start # Lo TAMIL SIGN VISARGA
+0B85..0B8A ; XID_Start # Lo [6] TAMIL LETTER A..TAMIL LETTER UU
+0B8E..0B90 ; XID_Start # Lo [3] TAMIL LETTER E..TAMIL LETTER AI
+0B92..0B95 ; XID_Start # Lo [4] TAMIL LETTER O..TAMIL LETTER KA
+0B99..0B9A ; XID_Start # Lo [2] TAMIL LETTER NGA..TAMIL LETTER CA
+0B9C ; XID_Start # Lo TAMIL LETTER JA
+0B9E..0B9F ; XID_Start # Lo [2] TAMIL LETTER NYA..TAMIL LETTER TTA
+0BA3..0BA4 ; XID_Start # Lo [2] TAMIL LETTER NNA..TAMIL LETTER TA
+0BA8..0BAA ; XID_Start # Lo [3] TAMIL LETTER NA..TAMIL LETTER PA
+0BAE..0BB9 ; XID_Start # Lo [12] TAMIL LETTER MA..TAMIL LETTER HA
+0BD0 ; XID_Start # Lo TAMIL OM
+0C05..0C0C ; XID_Start # Lo [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L
+0C0E..0C10 ; XID_Start # Lo [3] TELUGU LETTER E..TELUGU LETTER AI
+0C12..0C28 ; XID_Start # Lo [23] TELUGU LETTER O..TELUGU LETTER NA
+0C2A..0C39 ; XID_Start # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA
+0C3D ; XID_Start # Lo TELUGU SIGN AVAGRAHA
+0C58..0C5A ; XID_Start # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA
+0C5D ; XID_Start # Lo TELUGU LETTER NAKAARA POLLU
+0C60..0C61 ; XID_Start # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL
+0C80 ; XID_Start # Lo KANNADA SIGN SPACING CANDRABINDU
+0C85..0C8C ; XID_Start # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L
+0C8E..0C90 ; XID_Start # Lo [3] KANNADA LETTER E..KANNADA LETTER AI
+0C92..0CA8 ; XID_Start # Lo [23] KANNADA LETTER O..KANNADA LETTER NA
+0CAA..0CB3 ; XID_Start # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA
+0CB5..0CB9 ; XID_Start # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA
+0CBD ; XID_Start # Lo KANNADA SIGN AVAGRAHA
+0CDD..0CDE ; XID_Start # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA
+0CE0..0CE1 ; XID_Start # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL
+0CF1..0CF2 ; XID_Start # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA
+0D04..0D0C ; XID_Start # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L
+0D0E..0D10 ; XID_Start # Lo [3] MALAYALAM LETTER E..MALAYALAM LETTER AI
+0D12..0D3A ; XID_Start # Lo [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA
+0D3D ; XID_Start # Lo MALAYALAM SIGN AVAGRAHA
+0D4E ; XID_Start # Lo MALAYALAM LETTER DOT REPH
+0D54..0D56 ; XID_Start # Lo [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL
+0D5F..0D61 ; XID_Start # Lo [3] MALAYALAM LETTER ARCHAIC II..MALAYALAM LETTER VOCALIC LL
+0D7A..0D7F ; XID_Start # Lo [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K
+0D85..0D96 ; XID_Start # Lo [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA
+0D9A..0DB1 ; XID_Start # Lo [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA
+0DB3..0DBB ; XID_Start # Lo [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA
+0DBD ; XID_Start # Lo SINHALA LETTER DANTAJA LAYANNA
+0DC0..0DC6 ; XID_Start # Lo [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA
+0E01..0E30 ; XID_Start # Lo [48] THAI CHARACTER KO KAI..THAI CHARACTER SARA A
+0E32 ; XID_Start # Lo THAI CHARACTER SARA AA
+0E40..0E45 ; XID_Start # Lo [6] THAI CHARACTER SARA E..THAI CHARACTER LAKKHANGYAO
+0E46 ; XID_Start # Lm THAI CHARACTER MAIYAMOK
+0E81..0E82 ; XID_Start # Lo [2] LAO LETTER KO..LAO LETTER KHO SUNG
+0E84 ; XID_Start # Lo LAO LETTER KHO TAM
+0E86..0E8A ; XID_Start # Lo [5] LAO LETTER PALI GHA..LAO LETTER SO TAM
+0E8C..0EA3 ; XID_Start # Lo [24] LAO LETTER PALI JHA..LAO LETTER LO LING
+0EA5 ; XID_Start # Lo LAO LETTER LO LOOT
+0EA7..0EB0 ; XID_Start # Lo [10] LAO LETTER WO..LAO VOWEL SIGN A
+0EB2 ; XID_Start # Lo LAO VOWEL SIGN AA
+0EBD ; XID_Start # Lo LAO SEMIVOWEL SIGN NYO
+0EC0..0EC4 ; XID_Start # Lo [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI
+0EC6 ; XID_Start # Lm LAO KO LA
+0EDC..0EDF ; XID_Start # Lo [4] LAO HO NO..LAO LETTER KHMU NYO
+0F00 ; XID_Start # Lo TIBETAN SYLLABLE OM
+0F40..0F47 ; XID_Start # Lo [8] TIBETAN LETTER KA..TIBETAN LETTER JA
+0F49..0F6C ; XID_Start # Lo [36] TIBETAN LETTER NYA..TIBETAN LETTER RRA
+0F88..0F8C ; XID_Start # Lo [5] TIBETAN SIGN LCE TSA CAN..TIBETAN SIGN INVERTED MCHU CAN
+1000..102A ; XID_Start # Lo [43] MYANMAR LETTER KA..MYANMAR LETTER AU
+103F ; XID_Start # Lo MYANMAR LETTER GREAT SA
+1050..1055 ; XID_Start # Lo [6] MYANMAR LETTER SHA..MYANMAR LETTER VOCALIC LL
+105A..105D ; XID_Start # Lo [4] MYANMAR LETTER MON NGA..MYANMAR LETTER MON BBE
+1061 ; XID_Start # Lo MYANMAR LETTER SGAW KAREN SHA
+1065..1066 ; XID_Start # Lo [2] MYANMAR LETTER WESTERN PWO KAREN THA..MYANMAR LETTER WESTERN PWO KAREN PWA
+106E..1070 ; XID_Start # Lo [3] MYANMAR LETTER EASTERN PWO KAREN NNA..MYANMAR LETTER EASTERN PWO KAREN GHWA
+1075..1081 ; XID_Start # Lo [13] MYANMAR LETTER SHAN KA..MYANMAR LETTER SHAN HA
+108E ; XID_Start # Lo MYANMAR LETTER RUMAI PALAUNG FA
+10A0..10C5 ; XID_Start # L& [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE
+10C7 ; XID_Start # L& GEORGIAN CAPITAL LETTER YN
+10CD ; XID_Start # L& GEORGIAN CAPITAL LETTER AEN
+10D0..10FA ; XID_Start # L& [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN
+10FC ; XID_Start # Lm MODIFIER LETTER GEORGIAN NAR
+10FD..10FF ; XID_Start # L& [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN
+1100..1248 ; XID_Start # Lo [329] HANGUL CHOSEONG KIYEOK..ETHIOPIC SYLLABLE QWA
+124A..124D ; XID_Start # Lo [4] ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE
+1250..1256 ; XID_Start # Lo [7] ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO
+1258 ; XID_Start # Lo ETHIOPIC SYLLABLE QHWA
+125A..125D ; XID_Start # Lo [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE
+1260..1288 ; XID_Start # Lo [41] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA
+128A..128D ; XID_Start # Lo [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE
+1290..12B0 ; XID_Start # Lo [33] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA
+12B2..12B5 ; XID_Start # Lo [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE
+12B8..12BE ; XID_Start # Lo [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO
+12C0 ; XID_Start # Lo ETHIOPIC SYLLABLE KXWA
+12C2..12C5 ; XID_Start # Lo [4] ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE
+12C8..12D6 ; XID_Start # Lo [15] ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O
+12D8..1310 ; XID_Start # Lo [57] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA
+1312..1315 ; XID_Start # Lo [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE
+1318..135A ; XID_Start # Lo [67] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA
+1380..138F ; XID_Start # Lo [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE
+13A0..13F5 ; XID_Start # L& [86] CHEROKEE LETTER A..CHEROKEE LETTER MV
+13F8..13FD ; XID_Start # L& [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV
+1401..166C ; XID_Start # Lo [620] CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA
+166F..167F ; XID_Start # Lo [17] CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W
+1681..169A ; XID_Start # Lo [26] OGHAM LETTER BEITH..OGHAM LETTER PEITH
+16A0..16EA ; XID_Start # Lo [75] RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X
+16EE..16F0 ; XID_Start # Nl [3] RUNIC ARLAUG SYMBOL..RUNIC BELGTHOR SYMBOL
+16F1..16F8 ; XID_Start # Lo [8] RUNIC LETTER K..RUNIC LETTER FRANKS CASKET AESC
+1700..1711 ; XID_Start # Lo [18] TAGALOG LETTER A..TAGALOG LETTER HA
+171F..1731 ; XID_Start # Lo [19] TAGALOG LETTER ARCHAIC RA..HANUNOO LETTER HA
+1740..1751 ; XID_Start # Lo [18] BUHID LETTER A..BUHID LETTER HA
+1760..176C ; XID_Start # Lo [13] TAGBANWA LETTER A..TAGBANWA LETTER YA
+176E..1770 ; XID_Start # Lo [3] TAGBANWA LETTER LA..TAGBANWA LETTER SA
+1780..17B3 ; XID_Start # Lo [52] KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU
+17D7 ; XID_Start # Lm KHMER SIGN LEK TOO
+17DC ; XID_Start # Lo KHMER SIGN AVAKRAHASANYA
+1820..1842 ; XID_Start # Lo [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI
+1843 ; XID_Start # Lm MONGOLIAN LETTER TODO LONG VOWEL SIGN
+1844..1878 ; XID_Start # Lo [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS
+1880..1884 ; XID_Start # Lo [5] MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER ALI GALI INVERTED UBADAMA
+1885..1886 ; XID_Start # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA
+1887..18A8 ; XID_Start # Lo [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA
+18AA ; XID_Start # Lo MONGOLIAN LETTER MANCHU ALI GALI LHA
+18B0..18F5 ; XID_Start # Lo [70] CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S
+1900..191E ; XID_Start # Lo [31] LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER TRA
+1950..196D ; XID_Start # Lo [30] TAI LE LETTER KA..TAI LE LETTER AI
+1970..1974 ; XID_Start # Lo [5] TAI LE LETTER TONE-2..TAI LE LETTER TONE-6
+1980..19AB ; XID_Start # Lo [44] NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA
+19B0..19C9 ; XID_Start # Lo [26] NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2
+1A00..1A16 ; XID_Start # Lo [23] BUGINESE LETTER KA..BUGINESE LETTER HA
+1A20..1A54 ; XID_Start # Lo [53] TAI THAM LETTER HIGH KA..TAI THAM LETTER GREAT SA
+1AA7 ; XID_Start # Lm TAI THAM SIGN MAI YAMOK
+1B05..1B33 ; XID_Start # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA
+1B45..1B4C ; XID_Start # Lo [8] BALINESE LETTER KAF SASAK..BALINESE LETTER ARCHAIC JNYA
+1B83..1BA0 ; XID_Start # Lo [30] SUNDANESE LETTER A..SUNDANESE LETTER HA
+1BAE..1BAF ; XID_Start # Lo [2] SUNDANESE LETTER KHA..SUNDANESE LETTER SYA
+1BBA..1BE5 ; XID_Start # Lo [44] SUNDANESE AVAGRAHA..BATAK LETTER U
+1C00..1C23 ; XID_Start # Lo [36] LEPCHA LETTER KA..LEPCHA LETTER A
+1C4D..1C4F ; XID_Start # Lo [3] LEPCHA LETTER TTA..LEPCHA LETTER DDA
+1C5A..1C77 ; XID_Start # Lo [30] OL CHIKI LETTER LA..OL CHIKI LETTER OH
+1C78..1C7D ; XID_Start # Lm [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD
+1C80..1C8A ; XID_Start # L& [11] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER TJE
+1C90..1CBA ; XID_Start # L& [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN
+1CBD..1CBF ; XID_Start # L& [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
+1CE9..1CEC ; XID_Start # Lo [4] VEDIC SIGN ANUSVARA ANTARGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL
+1CEE..1CF3 ; XID_Start # Lo [6] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ROTATED ARDHAVISARGA
+1CF5..1CF6 ; XID_Start # Lo [2] VEDIC SIGN JIHVAMULIYA..VEDIC SIGN UPADHMANIYA
+1CFA ; XID_Start # Lo VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA
+1D00..1D2B ; XID_Start # L& [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL
+1D2C..1D6A ; XID_Start # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI
+1D6B..1D77 ; XID_Start # L& [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G
+1D78 ; XID_Start # Lm MODIFIER LETTER CYRILLIC EN
+1D79..1D9A ; XID_Start # L& [34] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK
+1D9B..1DBF ; XID_Start # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA
+1E00..1F15 ; XID_Start # L& [278] LATIN CAPITAL LETTER A WITH RING BELOW..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA
+1F18..1F1D ; XID_Start # L& [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F20..1F45 ; XID_Start # L& [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA
+1F48..1F4D ; XID_Start # L& [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F50..1F57 ; XID_Start # L& [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F59 ; XID_Start # L& GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B ; XID_Start # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D ; XID_Start # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F..1F7D ; XID_Start # L& [31] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER OMEGA WITH OXIA
+1F80..1FB4 ; XID_Start # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6..1FBC ; XID_Start # L& [7] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBE ; XID_Start # L& GREEK PROSGEGRAMMENI
+1FC2..1FC4 ; XID_Start # L& [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6..1FCC ; XID_Start # L& [7] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FD0..1FD3 ; XID_Start # L& [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6..1FDB ; XID_Start # L& [6] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH OXIA
+1FE0..1FEC ; XID_Start # L& [13] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA
+1FF2..1FF4 ; XID_Start # L& [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6..1FFC ; XID_Start # L& [7] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+2071 ; XID_Start # Lm SUPERSCRIPT LATIN SMALL LETTER I
+207F ; XID_Start # Lm SUPERSCRIPT LATIN SMALL LETTER N
+2090..209C ; XID_Start # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T
+2102 ; XID_Start # L& DOUBLE-STRUCK CAPITAL C
+2107 ; XID_Start # L& EULER CONSTANT
+210A..2113 ; XID_Start # L& [10] SCRIPT SMALL G..SCRIPT SMALL L
+2115 ; XID_Start # L& DOUBLE-STRUCK CAPITAL N
+2118 ; XID_Start # Sm SCRIPT CAPITAL P
+2119..211D ; XID_Start # L& [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R
+2124 ; XID_Start # L& DOUBLE-STRUCK CAPITAL Z
+2126 ; XID_Start # L& OHM SIGN
+2128 ; XID_Start # L& BLACK-LETTER CAPITAL Z
+212A..212D ; XID_Start # L& [4] KELVIN SIGN..BLACK-LETTER CAPITAL C
+212E ; XID_Start # So ESTIMATED SYMBOL
+212F..2134 ; XID_Start # L& [6] SCRIPT SMALL E..SCRIPT SMALL O
+2135..2138 ; XID_Start # Lo [4] ALEF SYMBOL..DALET SYMBOL
+2139 ; XID_Start # L& INFORMATION SOURCE
+213C..213F ; XID_Start # L& [4] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK CAPITAL PI
+2145..2149 ; XID_Start # L& [5] DOUBLE-STRUCK ITALIC CAPITAL D..DOUBLE-STRUCK ITALIC SMALL J
+214E ; XID_Start # L& TURNED SMALL F
+2160..2182 ; XID_Start # Nl [35] ROMAN NUMERAL ONE..ROMAN NUMERAL TEN THOUSAND
+2183..2184 ; XID_Start # L& [2] ROMAN NUMERAL REVERSED ONE HUNDRED..LATIN SMALL LETTER REVERSED C
+2185..2188 ; XID_Start # Nl [4] ROMAN NUMERAL SIX LATE FORM..ROMAN NUMERAL ONE HUNDRED THOUSAND
+2C00..2C7B ; XID_Start # L& [124] GLAGOLITIC CAPITAL LETTER AZU..LATIN LETTER SMALL CAPITAL TURNED E
+2C7C..2C7D ; XID_Start # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V
+2C7E..2CE4 ; XID_Start # L& [103] LATIN CAPITAL LETTER S WITH SWASH TAIL..COPTIC SYMBOL KAI
+2CEB..2CEE ; XID_Start # L& [4] COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI..COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA
+2CF2..2CF3 ; XID_Start # L& [2] COPTIC CAPITAL LETTER BOHAIRIC KHEI..COPTIC SMALL LETTER BOHAIRIC KHEI
+2D00..2D25 ; XID_Start # L& [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE
+2D27 ; XID_Start # L& GEORGIAN SMALL LETTER YN
+2D2D ; XID_Start # L& GEORGIAN SMALL LETTER AEN
+2D30..2D67 ; XID_Start # Lo [56] TIFINAGH LETTER YA..TIFINAGH LETTER YO
+2D6F ; XID_Start # Lm TIFINAGH MODIFIER LETTER LABIALIZATION MARK
+2D80..2D96 ; XID_Start # Lo [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE
+2DA0..2DA6 ; XID_Start # Lo [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO
+2DA8..2DAE ; XID_Start # Lo [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO
+2DB0..2DB6 ; XID_Start # Lo [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO
+2DB8..2DBE ; XID_Start # Lo [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO
+2DC0..2DC6 ; XID_Start # Lo [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO
+2DC8..2DCE ; XID_Start # Lo [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO
+2DD0..2DD6 ; XID_Start # Lo [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO
+2DD8..2DDE ; XID_Start # Lo [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO
+3005 ; XID_Start # Lm IDEOGRAPHIC ITERATION MARK
+3006 ; XID_Start # Lo IDEOGRAPHIC CLOSING MARK
+3007 ; XID_Start # Nl IDEOGRAPHIC NUMBER ZERO
+3021..3029 ; XID_Start # Nl [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE
+3031..3035 ; XID_Start # Lm [5] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT MARK LOWER HALF
+3038..303A ; XID_Start # Nl [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY
+303B ; XID_Start # Lm VERTICAL IDEOGRAPHIC ITERATION MARK
+303C ; XID_Start # Lo MASU MARK
+3041..3096 ; XID_Start # Lo [86] HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMALL KE
+309D..309E ; XID_Start # Lm [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK
+309F ; XID_Start # Lo HIRAGANA DIGRAPH YORI
+30A1..30FA ; XID_Start # Lo [90] KATAKANA LETTER SMALL A..KATAKANA LETTER VO
+30FC..30FE ; XID_Start # Lm [3] KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK
+30FF ; XID_Start # Lo KATAKANA DIGRAPH KOTO
+3105..312F ; XID_Start # Lo [43] BOPOMOFO LETTER B..BOPOMOFO LETTER NN
+3131..318E ; XID_Start # Lo [94] HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE
+31A0..31BF ; XID_Start # Lo [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH
+31F0..31FF ; XID_Start # Lo [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO
+3400..4DBF ; XID_Start # Lo [6592] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DBF
+4E00..A014 ; XID_Start # Lo [21013] CJK UNIFIED IDEOGRAPH-4E00..YI SYLLABLE E
+A015 ; XID_Start # Lm YI SYLLABLE WU
+A016..A48C ; XID_Start # Lo [1143] YI SYLLABLE BIT..YI SYLLABLE YYR
+A4D0..A4F7 ; XID_Start # Lo [40] LISU LETTER BA..LISU LETTER OE
+A4F8..A4FD ; XID_Start # Lm [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU
+A500..A60B ; XID_Start # Lo [268] VAI SYLLABLE EE..VAI SYLLABLE NG
+A60C ; XID_Start # Lm VAI SYLLABLE LENGTHENER
+A610..A61F ; XID_Start # Lo [16] VAI SYLLABLE NDOLE FA..VAI SYMBOL JONG
+A62A..A62B ; XID_Start # Lo [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO
+A640..A66D ; XID_Start # L& [46] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER DOUBLE MONOCULAR O
+A66E ; XID_Start # Lo CYRILLIC LETTER MULTIOCULAR O
+A67F ; XID_Start # Lm CYRILLIC PAYEROK
+A680..A69B ; XID_Start # L& [28] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER CROSSED O
+A69C..A69D ; XID_Start # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN
+A6A0..A6E5 ; XID_Start # Lo [70] BAMUM LETTER A..BAMUM LETTER KI
+A6E6..A6EF ; XID_Start # Nl [10] BAMUM LETTER MO..BAMUM LETTER KOGHOM
+A717..A71F ; XID_Start # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK
+A722..A76F ; XID_Start # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CON
+A770 ; XID_Start # Lm MODIFIER LETTER US
+A771..A787 ; XID_Start # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T
+A788 ; XID_Start # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT
+A78B..A78E ; XID_Start # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT
+A78F ; XID_Start # Lo LATIN LETTER SINOLOGICAL DOT
+A790..A7CD ; XID_Start # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE
+A7D0..A7D1 ; XID_Start # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G
+A7D3 ; XID_Start # L& LATIN SMALL LETTER DOUBLE THORN
+A7D5..A7DC ; XID_Start # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE
+A7F2..A7F4 ; XID_Start # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q
+A7F5..A7F6 ; XID_Start # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H
+A7F7 ; XID_Start # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I
+A7F8..A7F9 ; XID_Start # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE
+A7FA ; XID_Start # L& LATIN LETTER SMALL CAPITAL TURNED M
+A7FB..A801 ; XID_Start # Lo [7] LATIN EPIGRAPHIC LETTER REVERSED F..SYLOTI NAGRI LETTER I
+A803..A805 ; XID_Start # Lo [3] SYLOTI NAGRI LETTER U..SYLOTI NAGRI LETTER O
+A807..A80A ; XID_Start # Lo [4] SYLOTI NAGRI LETTER KO..SYLOTI NAGRI LETTER GHO
+A80C..A822 ; XID_Start # Lo [23] SYLOTI NAGRI LETTER CO..SYLOTI NAGRI LETTER HO
+A840..A873 ; XID_Start # Lo [52] PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU
+A882..A8B3 ; XID_Start # Lo [50] SAURASHTRA LETTER A..SAURASHTRA LETTER LLA
+A8F2..A8F7 ; XID_Start # Lo [6] DEVANAGARI SIGN SPACING CANDRABINDU..DEVANAGARI SIGN CANDRABINDU AVAGRAHA
+A8FB ; XID_Start # Lo DEVANAGARI HEADSTROKE
+A8FD..A8FE ; XID_Start # Lo [2] DEVANAGARI JAIN OM..DEVANAGARI LETTER AY
+A90A..A925 ; XID_Start # Lo [28] KAYAH LI LETTER KA..KAYAH LI LETTER OO
+A930..A946 ; XID_Start # Lo [23] REJANG LETTER KA..REJANG LETTER A
+A960..A97C ; XID_Start # Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH
+A984..A9B2 ; XID_Start # Lo [47] JAVANESE LETTER A..JAVANESE LETTER HA
+A9CF ; XID_Start # Lm JAVANESE PANGRANGKEP
+A9E0..A9E4 ; XID_Start # Lo [5] MYANMAR LETTER SHAN GHA..MYANMAR LETTER SHAN BHA
+A9E6 ; XID_Start # Lm MYANMAR MODIFIER LETTER SHAN REDUPLICATION
+A9E7..A9EF ; XID_Start # Lo [9] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING NNA
+A9FA..A9FE ; XID_Start # Lo [5] MYANMAR LETTER TAI LAING LLA..MYANMAR LETTER TAI LAING BHA
+AA00..AA28 ; XID_Start # Lo [41] CHAM LETTER A..CHAM LETTER HA
+AA40..AA42 ; XID_Start # Lo [3] CHAM LETTER FINAL K..CHAM LETTER FINAL NG
+AA44..AA4B ; XID_Start # Lo [8] CHAM LETTER FINAL CH..CHAM LETTER FINAL SS
+AA60..AA6F ; XID_Start # Lo [16] MYANMAR LETTER KHAMTI GA..MYANMAR LETTER KHAMTI FA
+AA70 ; XID_Start # Lm MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION
+AA71..AA76 ; XID_Start # Lo [6] MYANMAR LETTER KHAMTI XA..MYANMAR LOGOGRAM KHAMTI HM
+AA7A ; XID_Start # Lo MYANMAR LETTER AITON RA
+AA7E..AAAF ; XID_Start # Lo [50] MYANMAR LETTER SHWE PALAUNG CHA..TAI VIET LETTER HIGH O
+AAB1 ; XID_Start # Lo TAI VIET VOWEL AA
+AAB5..AAB6 ; XID_Start # Lo [2] TAI VIET VOWEL E..TAI VIET VOWEL O
+AAB9..AABD ; XID_Start # Lo [5] TAI VIET VOWEL UEA..TAI VIET VOWEL AN
+AAC0 ; XID_Start # Lo TAI VIET TONE MAI NUENG
+AAC2 ; XID_Start # Lo TAI VIET TONE MAI SONG
+AADB..AADC ; XID_Start # Lo [2] TAI VIET SYMBOL KON..TAI VIET SYMBOL NUENG
+AADD ; XID_Start # Lm TAI VIET SYMBOL SAM
+AAE0..AAEA ; XID_Start # Lo [11] MEETEI MAYEK LETTER E..MEETEI MAYEK LETTER SSA
+AAF2 ; XID_Start # Lo MEETEI MAYEK ANJI
+AAF3..AAF4 ; XID_Start # Lm [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK
+AB01..AB06 ; XID_Start # Lo [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO
+AB09..AB0E ; XID_Start # Lo [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO
+AB11..AB16 ; XID_Start # Lo [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO
+AB20..AB26 ; XID_Start # Lo [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO
+AB28..AB2E ; XID_Start # Lo [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO
+AB30..AB5A ; XID_Start # L& [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG
+AB5C..AB5F ; XID_Start # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK
+AB60..AB68 ; XID_Start # L& [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE
+AB69 ; XID_Start # Lm MODIFIER LETTER SMALL TURNED W
+AB70..ABBF ; XID_Start # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA
+ABC0..ABE2 ; XID_Start # Lo [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM
+AC00..D7A3 ; XID_Start # Lo [11172] HANGUL SYLLABLE GA..HANGUL SYLLABLE HIH
+D7B0..D7C6 ; XID_Start # Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E
+D7CB..D7FB ; XID_Start # Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH
+F900..FA6D ; XID_Start # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D
+FA70..FAD9 ; XID_Start # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9
+FB00..FB06 ; XID_Start # L& [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST
+FB13..FB17 ; XID_Start # L& [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH
+FB1D ; XID_Start # Lo HEBREW LETTER YOD WITH HIRIQ
+FB1F..FB28 ; XID_Start # Lo [10] HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER WIDE TAV
+FB2A..FB36 ; XID_Start # Lo [13] HEBREW LETTER SHIN WITH SHIN DOT..HEBREW LETTER ZAYIN WITH DAGESH
+FB38..FB3C ; XID_Start # Lo [5] HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH
+FB3E ; XID_Start # Lo HEBREW LETTER MEM WITH DAGESH
+FB40..FB41 ; XID_Start # Lo [2] HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH
+FB43..FB44 ; XID_Start # Lo [2] HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH
+FB46..FBB1 ; XID_Start # Lo [108] HEBREW LETTER TSADI WITH DAGESH..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM
+FBD3..FC5D ; XID_Start # Lo [139] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF MAKSURA WITH SUPERSCRIPT ALEF ISOLATED FORM
+FC64..FD3D ; XID_Start # Lo [218] ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH REH FINAL FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM
+FD50..FD8F ; XID_Start # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM
+FD92..FDC7 ; XID_Start # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM
+FDF0..FDF9 ; XID_Start # Lo [10] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE SALLA ISOLATED FORM
+FE71 ; XID_Start # Lo ARABIC TATWEEL WITH FATHATAN ABOVE
+FE73 ; XID_Start # Lo ARABIC TAIL FRAGMENT
+FE77 ; XID_Start # Lo ARABIC FATHA MEDIAL FORM
+FE79 ; XID_Start # Lo ARABIC DAMMA MEDIAL FORM
+FE7B ; XID_Start # Lo ARABIC KASRA MEDIAL FORM
+FE7D ; XID_Start # Lo ARABIC SHADDA MEDIAL FORM
+FE7F..FEFC ; XID_Start # Lo [126] ARABIC SUKUN MEDIAL FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM
+FF21..FF3A ; XID_Start # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z
+FF41..FF5A ; XID_Start # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z
+FF66..FF6F ; XID_Start # Lo [10] HALFWIDTH KATAKANA LETTER WO..HALFWIDTH KATAKANA LETTER SMALL TU
+FF70 ; XID_Start # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK
+FF71..FF9D ; XID_Start # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAKANA LETTER N
+FFA0..FFBE ; XID_Start # Lo [31] HALFWIDTH HANGUL FILLER..HALFWIDTH HANGUL LETTER HIEUH
+FFC2..FFC7 ; XID_Start # Lo [6] HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E
+FFCA..FFCF ; XID_Start # Lo [6] HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE
+FFD2..FFD7 ; XID_Start # Lo [6] HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU
+FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I
+10000..1000B ; XID_Start # Lo [12] LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE
+1000D..10026 ; XID_Start # Lo [26] LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO
+10028..1003A ; XID_Start # Lo [19] LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO
+1003C..1003D ; XID_Start # Lo [2] LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE
+1003F..1004D ; XID_Start # Lo [15] LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO
+10050..1005D ; XID_Start # Lo [14] LINEAR B SYMBOL B018..LINEAR B SYMBOL B089
+10080..100FA ; XID_Start # Lo [123] LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305
+10140..10174 ; XID_Start # Nl [53] GREEK ACROPHONIC ATTIC ONE QUARTER..GREEK ACROPHONIC STRATIAN FIFTY MNAS
+10280..1029C ; XID_Start # Lo [29] LYCIAN LETTER A..LYCIAN LETTER X
+102A0..102D0 ; XID_Start # Lo [49] CARIAN LETTER A..CARIAN LETTER UUU3
+10300..1031F ; XID_Start # Lo [32] OLD ITALIC LETTER A..OLD ITALIC LETTER ESS
+1032D..10340 ; XID_Start # Lo [20] OLD ITALIC LETTER YE..GOTHIC LETTER PAIRTHRA
+10341 ; XID_Start # Nl GOTHIC LETTER NINETY
+10342..10349 ; XID_Start # Lo [8] GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL
+1034A ; XID_Start # Nl GOTHIC LETTER NINE HUNDRED
+10350..10375 ; XID_Start # Lo [38] OLD PERMIC LETTER AN..OLD PERMIC LETTER IA
+10380..1039D ; XID_Start # Lo [30] UGARITIC LETTER ALPA..UGARITIC LETTER SSU
+103A0..103C3 ; XID_Start # Lo [36] OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA
+103C8..103CF ; XID_Start # Lo [8] OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH
+103D1..103D5 ; XID_Start # Nl [5] OLD PERSIAN NUMBER ONE..OLD PERSIAN NUMBER HUNDRED
+10400..1044F ; XID_Start # L& [80] DESERET CAPITAL LETTER LONG I..DESERET SMALL LETTER EW
+10450..1049D ; XID_Start # Lo [78] SHAVIAN LETTER PEEP..OSMANYA LETTER OO
+104B0..104D3 ; XID_Start # L& [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA
+104D8..104FB ; XID_Start # L& [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA
+10500..10527 ; XID_Start # Lo [40] ELBASAN LETTER A..ELBASAN LETTER KHE
+10530..10563 ; XID_Start # Lo [52] CAUCASIAN ALBANIAN LETTER ALT..CAUCASIAN ALBANIAN LETTER KIW
+10570..1057A ; XID_Start # L& [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA
+1057C..1058A ; XID_Start # L& [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE
+1058C..10592 ; XID_Start # L& [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE
+10594..10595 ; XID_Start # L& [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE
+10597..105A1 ; XID_Start # L& [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA
+105A3..105B1 ; XID_Start # L& [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE
+105B3..105B9 ; XID_Start # L& [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE
+105BB..105BC ; XID_Start # L& [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE
+105C0..105F3 ; XID_Start # Lo [52] TODHRI LETTER A..TODHRI LETTER OO
+10600..10736 ; XID_Start # Lo [311] LINEAR A SIGN AB001..LINEAR A SIGN A664
+10740..10755 ; XID_Start # Lo [22] LINEAR A SIGN A701 A..LINEAR A SIGN A732 JE
+10760..10767 ; XID_Start # Lo [8] LINEAR A SIGN A800..LINEAR A SIGN A807
+10780..10785 ; XID_Start # Lm [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK
+10787..107B0 ; XID_Start # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK
+107B2..107BA ; XID_Start # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL
+10800..10805 ; XID_Start # Lo [6] CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA
+10808 ; XID_Start # Lo CYPRIOT SYLLABLE JO
+1080A..10835 ; XID_Start # Lo [44] CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO
+10837..10838 ; XID_Start # Lo [2] CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE
+1083C ; XID_Start # Lo CYPRIOT SYLLABLE ZA
+1083F..10855 ; XID_Start # Lo [23] CYPRIOT SYLLABLE ZO..IMPERIAL ARAMAIC LETTER TAW
+10860..10876 ; XID_Start # Lo [23] PALMYRENE LETTER ALEPH..PALMYRENE LETTER TAW
+10880..1089E ; XID_Start # Lo [31] NABATAEAN LETTER FINAL ALEPH..NABATAEAN LETTER TAW
+108E0..108F2 ; XID_Start # Lo [19] HATRAN LETTER ALEPH..HATRAN LETTER QOPH
+108F4..108F5 ; XID_Start # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW
+10900..10915 ; XID_Start # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU
+10920..10939 ; XID_Start # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C
+10980..109B7 ; XID_Start # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA
+109BE..109BF ; XID_Start # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN
+10A00 ; XID_Start # Lo KHAROSHTHI LETTER A
+10A10..10A13 ; XID_Start # Lo [4] KHAROSHTHI LETTER KA..KHAROSHTHI LETTER GHA
+10A15..10A17 ; XID_Start # Lo [3] KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA
+10A19..10A35 ; XID_Start # Lo [29] KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER VHA
+10A60..10A7C ; XID_Start # Lo [29] OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH
+10A80..10A9C ; XID_Start # Lo [29] OLD NORTH ARABIAN LETTER HEH..OLD NORTH ARABIAN LETTER ZAH
+10AC0..10AC7 ; XID_Start # Lo [8] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER WAW
+10AC9..10AE4 ; XID_Start # Lo [28] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER TAW
+10B00..10B35 ; XID_Start # Lo [54] AVESTAN LETTER A..AVESTAN LETTER HE
+10B40..10B55 ; XID_Start # Lo [22] INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW
+10B60..10B72 ; XID_Start # Lo [19] INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW
+10B80..10B91 ; XID_Start # Lo [18] PSALTER PAHLAVI LETTER ALEPH..PSALTER PAHLAVI LETTER TAW
+10C00..10C48 ; XID_Start # Lo [73] OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH
+10C80..10CB2 ; XID_Start # L& [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US
+10CC0..10CF2 ; XID_Start # L& [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US
+10D00..10D23 ; XID_Start # Lo [36] HANIFI ROHINGYA LETTER A..HANIFI ROHINGYA MARK NA KHONNA
+10D4A..10D4D ; XID_Start # Lo [4] GARAY VOWEL SIGN A..GARAY VOWEL SIGN EE
+10D4E ; XID_Start # Lm GARAY VOWEL LENGTH MARK
+10D4F ; XID_Start # Lo GARAY SUKUN
+10D50..10D65 ; XID_Start # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA
+10D6F ; XID_Start # Lm GARAY REDUPLICATION MARK
+10D70..10D85 ; XID_Start # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA
+10E80..10EA9 ; XID_Start # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET
+10EB0..10EB1 ; XID_Start # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE
+10EC2..10EC4 ; XID_Start # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW
+10F00..10F1C ; XID_Start # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL
+10F27 ; XID_Start # Lo OLD SOGDIAN LIGATURE AYIN-DALETH
+10F30..10F45 ; XID_Start # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN
+10F70..10F81 ; XID_Start # Lo [18] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER LESH
+10FB0..10FC4 ; XID_Start # Lo [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW
+10FE0..10FF6 ; XID_Start # Lo [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH
+11003..11037 ; XID_Start # Lo [53] BRAHMI SIGN JIHVAMULIYA..BRAHMI LETTER OLD TAMIL NNNA
+11071..11072 ; XID_Start # Lo [2] BRAHMI LETTER OLD TAMIL SHORT E..BRAHMI LETTER OLD TAMIL SHORT O
+11075 ; XID_Start # Lo BRAHMI LETTER OLD TAMIL LLA
+11083..110AF ; XID_Start # Lo [45] KAITHI LETTER A..KAITHI LETTER HA
+110D0..110E8 ; XID_Start # Lo [25] SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE
+11103..11126 ; XID_Start # Lo [36] CHAKMA LETTER AA..CHAKMA LETTER HAA
+11144 ; XID_Start # Lo CHAKMA LETTER LHAA
+11147 ; XID_Start # Lo CHAKMA LETTER VAA
+11150..11172 ; XID_Start # Lo [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA
+11176 ; XID_Start # Lo MAHAJANI LIGATURE SHRI
+11183..111B2 ; XID_Start # Lo [48] SHARADA LETTER A..SHARADA LETTER HA
+111C1..111C4 ; XID_Start # Lo [4] SHARADA SIGN AVAGRAHA..SHARADA OM
+111DA ; XID_Start # Lo SHARADA EKAM
+111DC ; XID_Start # Lo SHARADA HEADSTROKE
+11200..11211 ; XID_Start # Lo [18] KHOJKI LETTER A..KHOJKI LETTER JJA
+11213..1122B ; XID_Start # Lo [25] KHOJKI LETTER NYA..KHOJKI LETTER LLA
+1123F..11240 ; XID_Start # Lo [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I
+11280..11286 ; XID_Start # Lo [7] MULTANI LETTER A..MULTANI LETTER GA
+11288 ; XID_Start # Lo MULTANI LETTER GHA
+1128A..1128D ; XID_Start # Lo [4] MULTANI LETTER CA..MULTANI LETTER JJA
+1128F..1129D ; XID_Start # Lo [15] MULTANI LETTER NYA..MULTANI LETTER BA
+1129F..112A8 ; XID_Start # Lo [10] MULTANI LETTER BHA..MULTANI LETTER RHA
+112B0..112DE ; XID_Start # Lo [47] KHUDAWADI LETTER A..KHUDAWADI LETTER HA
+11305..1130C ; XID_Start # Lo [8] GRANTHA LETTER A..GRANTHA LETTER VOCALIC L
+1130F..11310 ; XID_Start # Lo [2] GRANTHA LETTER EE..GRANTHA LETTER AI
+11313..11328 ; XID_Start # Lo [22] GRANTHA LETTER OO..GRANTHA LETTER NA
+1132A..11330 ; XID_Start # Lo [7] GRANTHA LETTER PA..GRANTHA LETTER RA
+11332..11333 ; XID_Start # Lo [2] GRANTHA LETTER LA..GRANTHA LETTER LLA
+11335..11339 ; XID_Start # Lo [5] GRANTHA LETTER VA..GRANTHA LETTER HA
+1133D ; XID_Start # Lo GRANTHA SIGN AVAGRAHA
+11350 ; XID_Start # Lo GRANTHA OM
+1135D..11361 ; XID_Start # Lo [5] GRANTHA SIGN PLUTA..GRANTHA LETTER VOCALIC LL
+11380..11389 ; XID_Start # Lo [10] TULU-TIGALARI LETTER A..TULU-TIGALARI LETTER VOCALIC LL
+1138B ; XID_Start # Lo TULU-TIGALARI LETTER EE
+1138E ; XID_Start # Lo TULU-TIGALARI LETTER AI
+11390..113B5 ; XID_Start # Lo [38] TULU-TIGALARI LETTER OO..TULU-TIGALARI LETTER LLLA
+113B7 ; XID_Start # Lo TULU-TIGALARI SIGN AVAGRAHA
+113D1 ; XID_Start # Lo TULU-TIGALARI REPHA
+113D3 ; XID_Start # Lo TULU-TIGALARI SIGN PLUTA
+11400..11434 ; XID_Start # Lo [53] NEWA LETTER A..NEWA LETTER HA
+11447..1144A ; XID_Start # Lo [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI
+1145F..11461 ; XID_Start # Lo [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA
+11480..114AF ; XID_Start # Lo [48] TIRHUTA ANJI..TIRHUTA LETTER HA
+114C4..114C5 ; XID_Start # Lo [2] TIRHUTA SIGN AVAGRAHA..TIRHUTA GVANG
+114C7 ; XID_Start # Lo TIRHUTA OM
+11580..115AE ; XID_Start # Lo [47] SIDDHAM LETTER A..SIDDHAM LETTER HA
+115D8..115DB ; XID_Start # Lo [4] SIDDHAM LETTER THREE-CIRCLE ALTERNATE I..SIDDHAM LETTER ALTERNATE U
+11600..1162F ; XID_Start # Lo [48] MODI LETTER A..MODI LETTER LLA
+11644 ; XID_Start # Lo MODI SIGN HUVA
+11680..116AA ; XID_Start # Lo [43] TAKRI LETTER A..TAKRI LETTER RRA
+116B8 ; XID_Start # Lo TAKRI LETTER ARCHAIC KHA
+11700..1171A ; XID_Start # Lo [27] AHOM LETTER KA..AHOM LETTER ALTERNATE BA
+11740..11746 ; XID_Start # Lo [7] AHOM LETTER CA..AHOM LETTER LLA
+11800..1182B ; XID_Start # Lo [44] DOGRA LETTER A..DOGRA LETTER RRA
+118A0..118DF ; XID_Start # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO
+118FF..11906 ; XID_Start # Lo [8] WARANG CITI OM..DIVES AKURU LETTER E
+11909 ; XID_Start # Lo DIVES AKURU LETTER O
+1190C..11913 ; XID_Start # Lo [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA
+11915..11916 ; XID_Start # Lo [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA
+11918..1192F ; XID_Start # Lo [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA
+1193F ; XID_Start # Lo DIVES AKURU PREFIXED NASAL SIGN
+11941 ; XID_Start # Lo DIVES AKURU INITIAL RA
+119A0..119A7 ; XID_Start # Lo [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR
+119AA..119D0 ; XID_Start # Lo [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA
+119E1 ; XID_Start # Lo NANDINAGARI SIGN AVAGRAHA
+119E3 ; XID_Start # Lo NANDINAGARI HEADSTROKE
+11A00 ; XID_Start # Lo ZANABAZAR SQUARE LETTER A
+11A0B..11A32 ; XID_Start # Lo [40] ZANABAZAR SQUARE LETTER KA..ZANABAZAR SQUARE LETTER KSSA
+11A3A ; XID_Start # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA
+11A50 ; XID_Start # Lo SOYOMBO LETTER A
+11A5C..11A89 ; XID_Start # Lo [46] SOYOMBO LETTER KA..SOYOMBO CLUSTER-INITIAL LETTER SA
+11A9D ; XID_Start # Lo SOYOMBO MARK PLUTA
+11AB0..11AF8 ; XID_Start # Lo [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL
+11BC0..11BE0 ; XID_Start # Lo [33] SUNUWAR LETTER DEVI..SUNUWAR LETTER KLOKO
+11C00..11C08 ; XID_Start # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L
+11C0A..11C2E ; XID_Start # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA
+11C40 ; XID_Start # Lo BHAIKSUKI SIGN AVAGRAHA
+11C72..11C8F ; XID_Start # Lo [30] MARCHEN LETTER KA..MARCHEN LETTER A
+11D00..11D06 ; XID_Start # Lo [7] MASARAM GONDI LETTER A..MASARAM GONDI LETTER E
+11D08..11D09 ; XID_Start # Lo [2] MASARAM GONDI LETTER AI..MASARAM GONDI LETTER O
+11D0B..11D30 ; XID_Start # Lo [38] MASARAM GONDI LETTER AU..MASARAM GONDI LETTER TRA
+11D46 ; XID_Start # Lo MASARAM GONDI REPHA
+11D60..11D65 ; XID_Start # Lo [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU
+11D67..11D68 ; XID_Start # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI
+11D6A..11D89 ; XID_Start # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA
+11D98 ; XID_Start # Lo GUNJALA GONDI OM
+11EE0..11EF2 ; XID_Start # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA
+11F02 ; XID_Start # Lo KAWI SIGN REPHA
+11F04..11F10 ; XID_Start # Lo [13] KAWI LETTER A..KAWI LETTER O
+11F12..11F33 ; XID_Start # Lo [34] KAWI LETTER KA..KAWI LETTER JNYA
+11FB0 ; XID_Start # Lo LISU LETTER YHA
+12000..12399 ; XID_Start # Lo [922] CUNEIFORM SIGN A..CUNEIFORM SIGN U U
+12400..1246E ; XID_Start # Nl [111] CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM
+12480..12543 ; XID_Start # Lo [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU
+12F90..12FF0 ; XID_Start # Lo [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114
+13000..1342F ; XID_Start # Lo [1072] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH V011D
+13441..13446 ; XID_Start # Lo [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN
+13460..143FA ; XID_Start # Lo [3995] EGYPTIAN HIEROGLYPH-13460..EGYPTIAN HIEROGLYPH-143FA
+14400..14646 ; XID_Start # Lo [583] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A530
+16100..1611D ; XID_Start # Lo [30] GURUNG KHEMA LETTER A..GURUNG KHEMA LETTER SA
+16800..16A38 ; XID_Start # Lo [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ
+16A40..16A5E ; XID_Start # Lo [31] MRO LETTER TA..MRO LETTER TEK
+16A70..16ABE ; XID_Start # Lo [79] TANGSA LETTER OZ..TANGSA LETTER ZA
+16AD0..16AED ; XID_Start # Lo [30] BASSA VAH LETTER ENNI..BASSA VAH LETTER I
+16B00..16B2F ; XID_Start # Lo [48] PAHAWH HMONG VOWEL KEEB..PAHAWH HMONG CONSONANT CAU
+16B40..16B43 ; XID_Start # Lm [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM
+16B63..16B77 ; XID_Start # Lo [21] PAHAWH HMONG SIGN VOS LUB..PAHAWH HMONG SIGN CIM NRES TOS
+16B7D..16B8F ; XID_Start # Lo [19] PAHAWH HMONG CLAN SIGN TSHEEJ..PAHAWH HMONG CLAN SIGN VWJ
+16D40..16D42 ; XID_Start # Lm [3] KIRAT RAI SIGN ANUSVARA..KIRAT RAI SIGN VISARGA
+16D43..16D6A ; XID_Start # Lo [40] KIRAT RAI LETTER A..KIRAT RAI VOWEL SIGN AU
+16D6B..16D6C ; XID_Start # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT
+16E40..16E7F ; XID_Start # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y
+16F00..16F4A ; XID_Start # Lo [75] MIAO LETTER PA..MIAO LETTER RTE
+16F50 ; XID_Start # Lo MIAO LETTER NASALIZATION
+16F93..16F9F ; XID_Start # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8
+16FE0..16FE1 ; XID_Start # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK
+16FE3 ; XID_Start # Lm OLD CHINESE ITERATION MARK
+17000..187F7 ; XID_Start # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7
+18800..18CD5 ; XID_Start # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5
+18CFF..18D08 ; XID_Start # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08
+1AFF0..1AFF3 ; XID_Start # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5
+1AFF5..1AFFB ; XID_Start # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5
+1AFFD..1AFFE ; XID_Start # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8
+1B000..1B122 ; XID_Start # Lo [291] KATAKANA LETTER ARCHAIC E..KATAKANA LETTER ARCHAIC WU
+1B132 ; XID_Start # Lo HIRAGANA LETTER SMALL KO
+1B150..1B152 ; XID_Start # Lo [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO
+1B155 ; XID_Start # Lo KATAKANA LETTER SMALL KO
+1B164..1B167 ; XID_Start # Lo [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N
+1B170..1B2FB ; XID_Start # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB
+1BC00..1BC6A ; XID_Start # Lo [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M
+1BC70..1BC7C ; XID_Start # Lo [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK
+1BC80..1BC88 ; XID_Start # Lo [9] DUPLOYAN AFFIX HIGH ACUTE..DUPLOYAN AFFIX HIGH VERTICAL
+1BC90..1BC99 ; XID_Start # Lo [10] DUPLOYAN AFFIX LOW ACUTE..DUPLOYAN AFFIX LOW ARROW
+1D400..1D454 ; XID_Start # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G
+1D456..1D49C ; XID_Start # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A
+1D49E..1D49F ; XID_Start # L& [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D
+1D4A2 ; XID_Start # L& MATHEMATICAL SCRIPT CAPITAL G
+1D4A5..1D4A6 ; XID_Start # L& [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K
+1D4A9..1D4AC ; XID_Start # L& [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q
+1D4AE..1D4B9 ; XID_Start # L& [12] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D
+1D4BB ; XID_Start # L& MATHEMATICAL SCRIPT SMALL F
+1D4BD..1D4C3 ; XID_Start # L& [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N
+1D4C5..1D505 ; XID_Start # L& [65] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B
+1D507..1D50A ; XID_Start # L& [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G
+1D50D..1D514 ; XID_Start # L& [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q
+1D516..1D51C ; XID_Start # L& [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y
+1D51E..1D539 ; XID_Start # L& [28] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B
+1D53B..1D53E ; XID_Start # L& [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G
+1D540..1D544 ; XID_Start # L& [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M
+1D546 ; XID_Start # L& MATHEMATICAL DOUBLE-STRUCK CAPITAL O
+1D54A..1D550 ; XID_Start # L& [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y
+1D552..1D6A5 ; XID_Start # L& [340] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J
+1D6A8..1D6C0 ; XID_Start # L& [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA
+1D6C2..1D6DA ; XID_Start # L& [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA
+1D6DC..1D6FA ; XID_Start # L& [31] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL ITALIC CAPITAL OMEGA
+1D6FC..1D714 ; XID_Start # L& [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA
+1D716..1D734 ; XID_Start # L& [31] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA
+1D736..1D74E ; XID_Start # L& [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA
+1D750..1D76E ; XID_Start # L& [31] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA
+1D770..1D788 ; XID_Start # L& [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA
+1D78A..1D7A8 ; XID_Start # L& [31] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA
+1D7AA..1D7C2 ; XID_Start # L& [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA
+1D7C4..1D7CB ; XID_Start # L& [8] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD SMALL DIGAMMA
+1DF00..1DF09 ; XID_Start # L& [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK
+1DF0A ; XID_Start # Lo LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK
+1DF0B..1DF1E ; XID_Start # L& [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL
+1DF25..1DF2A ; XID_Start # L& [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK
+1E030..1E06D ; XID_Start # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE
+1E100..1E12C ; XID_Start # Lo [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W
+1E137..1E13D ; XID_Start # Lm [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER
+1E14E ; XID_Start # Lo NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ
+1E290..1E2AD ; XID_Start # Lo [30] TOTO LETTER PA..TOTO LETTER A
+1E2C0..1E2EB ; XID_Start # Lo [44] WANCHO LETTER AA..WANCHO LETTER YIH
+1E4D0..1E4EA ; XID_Start # Lo [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL
+1E4EB ; XID_Start # Lm NAG MUNDARI SIGN OJOD
+1E5D0..1E5ED ; XID_Start # Lo [30] OL ONAL LETTER O..OL ONAL LETTER EG
+1E5F0 ; XID_Start # Lo OL ONAL SIGN HODDOND
+1E7E0..1E7E6 ; XID_Start # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO
+1E7E8..1E7EB ; XID_Start # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE
+1E7ED..1E7EE ; XID_Start # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE
+1E7F0..1E7FE ; XID_Start # Lo [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE
+1E800..1E8C4 ; XID_Start # Lo [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON
+1E900..1E943 ; XID_Start # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA
+1E94B ; XID_Start # Lm ADLAM NASALIZATION MARK
+1EE00..1EE03 ; XID_Start # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL
+1EE05..1EE1F ; XID_Start # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF
+1EE21..1EE22 ; XID_Start # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM
+1EE24 ; XID_Start # Lo ARABIC MATHEMATICAL INITIAL HEH
+1EE27 ; XID_Start # Lo ARABIC MATHEMATICAL INITIAL HAH
+1EE29..1EE32 ; XID_Start # Lo [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF
+1EE34..1EE37 ; XID_Start # Lo [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH
+1EE39 ; XID_Start # Lo ARABIC MATHEMATICAL INITIAL DAD
+1EE3B ; XID_Start # Lo ARABIC MATHEMATICAL INITIAL GHAIN
+1EE42 ; XID_Start # Lo ARABIC MATHEMATICAL TAILED JEEM
+1EE47 ; XID_Start # Lo ARABIC MATHEMATICAL TAILED HAH
+1EE49 ; XID_Start # Lo ARABIC MATHEMATICAL TAILED YEH
+1EE4B ; XID_Start # Lo ARABIC MATHEMATICAL TAILED LAM
+1EE4D..1EE4F ; XID_Start # Lo [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN
+1EE51..1EE52 ; XID_Start # Lo [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF
+1EE54 ; XID_Start # Lo ARABIC MATHEMATICAL TAILED SHEEN
+1EE57 ; XID_Start # Lo ARABIC MATHEMATICAL TAILED KHAH
+1EE59 ; XID_Start # Lo ARABIC MATHEMATICAL TAILED DAD
+1EE5B ; XID_Start # Lo ARABIC MATHEMATICAL TAILED GHAIN
+1EE5D ; XID_Start # Lo ARABIC MATHEMATICAL TAILED DOTLESS NOON
+1EE5F ; XID_Start # Lo ARABIC MATHEMATICAL TAILED DOTLESS QAF
+1EE61..1EE62 ; XID_Start # Lo [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM
+1EE64 ; XID_Start # Lo ARABIC MATHEMATICAL STRETCHED HEH
+1EE67..1EE6A ; XID_Start # Lo [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF
+1EE6C..1EE72 ; XID_Start # Lo [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF
+1EE74..1EE77 ; XID_Start # Lo [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH
+1EE79..1EE7C ; XID_Start # Lo [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH
+1EE7E ; XID_Start # Lo ARABIC MATHEMATICAL STRETCHED DOTLESS FEH
+1EE80..1EE89 ; XID_Start # Lo [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH
+1EE8B..1EE9B ; XID_Start # Lo [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN
+1EEA1..1EEA3 ; XID_Start # Lo [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL
+1EEA5..1EEA9 ; XID_Start # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH
+1EEAB..1EEBB ; XID_Start # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN
+20000..2A6DF ; XID_Start # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF
+2A700..2B739 ; XID_Start # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739
+2B740..2B81D ; XID_Start # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D
+2B820..2CEA1 ; XID_Start # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1
+2CEB0..2EBE0 ; XID_Start # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0
+2EBF0..2EE5D ; XID_Start # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D
+2F800..2FA1D ; XID_Start # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D
+30000..3134A ; XID_Start # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A
+31350..323AF ; XID_Start # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF
+
+# Total code points: 141246
+
+# ================================================
+
+# Derived Property: XID_Continue
+# Mod_ID_Continue modified for closure under NFKx
+# Modified as described in UAX #15
+# NOTE: Does NOT remove the non-NFKx characters.
+# Merely ensures that if isIdentifer(string) then isIdentifier(NFKx(string))
+# NOTE: See UAX #31 for more information
+
+0030..0039 ; XID_Continue # Nd [10] DIGIT ZERO..DIGIT NINE
+0041..005A ; XID_Continue # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+005F ; XID_Continue # Pc LOW LINE
+0061..007A ; XID_Continue # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+00AA ; XID_Continue # Lo FEMININE ORDINAL INDICATOR
+00B5 ; XID_Continue # L& MICRO SIGN
+00B7 ; XID_Continue # Po MIDDLE DOT
+00BA ; XID_Continue # Lo MASCULINE ORDINAL INDICATOR
+00C0..00D6 ; XID_Continue # L& [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS
+00D8..00F6 ; XID_Continue # L& [31] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER O WITH DIAERESIS
+00F8..01BA ; XID_Continue # L& [195] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL
+01BB ; XID_Continue # Lo LATIN LETTER TWO WITH STROKE
+01BC..01BF ; XID_Continue # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN
+01C0..01C3 ; XID_Continue # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK
+01C4..0293 ; XID_Continue # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL
+0294 ; XID_Continue # Lo LATIN LETTER GLOTTAL STOP
+0295..02AF ; XID_Continue # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL
+02B0..02C1 ; XID_Continue # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP
+02C6..02D1 ; XID_Continue # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON
+02E0..02E4 ; XID_Continue # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP
+02EC ; XID_Continue # Lm MODIFIER LETTER VOICING
+02EE ; XID_Continue # Lm MODIFIER LETTER DOUBLE APOSTROPHE
+0300..036F ; XID_Continue # Mn [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X
+0370..0373 ; XID_Continue # L& [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI
+0374 ; XID_Continue # Lm GREEK NUMERAL SIGN
+0376..0377 ; XID_Continue # L& [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA
+037B..037D ; XID_Continue # L& [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL
+037F ; XID_Continue # L& GREEK CAPITAL LETTER YOT
+0386 ; XID_Continue # L& GREEK CAPITAL LETTER ALPHA WITH TONOS
+0387 ; XID_Continue # Po GREEK ANO TELEIA
+0388..038A ; XID_Continue # L& [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS
+038C ; XID_Continue # L& GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E..03A1 ; XID_Continue # L& [20] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER RHO
+03A3..03F5 ; XID_Continue # L& [83] GREEK CAPITAL LETTER SIGMA..GREEK LUNATE EPSILON SYMBOL
+03F7..0481 ; XID_Continue # L& [139] GREEK CAPITAL LETTER SHO..CYRILLIC SMALL LETTER KOPPA
+0483..0487 ; XID_Continue # Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE
+048A..052F ; XID_Continue # L& [166] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER EL WITH DESCENDER
+0531..0556 ; XID_Continue # L& [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH
+0559 ; XID_Continue # Lm ARMENIAN MODIFIER LETTER LEFT HALF RING
+0560..0588 ; XID_Continue # L& [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE
+0591..05BD ; XID_Continue # Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG
+05BF ; XID_Continue # Mn HEBREW POINT RAFE
+05C1..05C2 ; XID_Continue # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT
+05C4..05C5 ; XID_Continue # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT
+05C7 ; XID_Continue # Mn HEBREW POINT QAMATS QATAN
+05D0..05EA ; XID_Continue # Lo [27] HEBREW LETTER ALEF..HEBREW LETTER TAV
+05EF..05F2 ; XID_Continue # Lo [4] HEBREW YOD TRIANGLE..HEBREW LIGATURE YIDDISH DOUBLE YOD
+0610..061A ; XID_Continue # Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA
+0620..063F ; XID_Continue # Lo [32] ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE
+0640 ; XID_Continue # Lm ARABIC TATWEEL
+0641..064A ; XID_Continue # Lo [10] ARABIC LETTER FEH..ARABIC LETTER YEH
+064B..065F ; XID_Continue # Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW
+0660..0669 ; XID_Continue # Nd [10] ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE
+066E..066F ; XID_Continue # Lo [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF
+0670 ; XID_Continue # Mn ARABIC LETTER SUPERSCRIPT ALEF
+0671..06D3 ; XID_Continue # Lo [99] ARABIC LETTER ALEF WASLA..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE
+06D5 ; XID_Continue # Lo ARABIC LETTER AE
+06D6..06DC ; XID_Continue # Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN
+06DF..06E4 ; XID_Continue # Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA
+06E5..06E6 ; XID_Continue # Lm [2] ARABIC SMALL WAW..ARABIC SMALL YEH
+06E7..06E8 ; XID_Continue # Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON
+06EA..06ED ; XID_Continue # Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM
+06EE..06EF ; XID_Continue # Lo [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V
+06F0..06F9 ; XID_Continue # Nd [10] EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE
+06FA..06FC ; XID_Continue # Lo [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW
+06FF ; XID_Continue # Lo ARABIC LETTER HEH WITH INVERTED V
+0710 ; XID_Continue # Lo SYRIAC LETTER ALAPH
+0711 ; XID_Continue # Mn SYRIAC LETTER SUPERSCRIPT ALAPH
+0712..072F ; XID_Continue # Lo [30] SYRIAC LETTER BETH..SYRIAC LETTER PERSIAN DHALATH
+0730..074A ; XID_Continue # Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH
+074D..07A5 ; XID_Continue # Lo [89] SYRIAC LETTER SOGDIAN ZHAIN..THAANA LETTER WAAVU
+07A6..07B0 ; XID_Continue # Mn [11] THAANA ABAFILI..THAANA SUKUN
+07B1 ; XID_Continue # Lo THAANA LETTER NAA
+07C0..07C9 ; XID_Continue # Nd [10] NKO DIGIT ZERO..NKO DIGIT NINE
+07CA..07EA ; XID_Continue # Lo [33] NKO LETTER A..NKO LETTER JONA RA
+07EB..07F3 ; XID_Continue # Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE
+07F4..07F5 ; XID_Continue # Lm [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE
+07FA ; XID_Continue # Lm NKO LAJANYALAN
+07FD ; XID_Continue # Mn NKO DANTAYALAN
+0800..0815 ; XID_Continue # Lo [22] SAMARITAN LETTER ALAF..SAMARITAN LETTER TAAF
+0816..0819 ; XID_Continue # Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH
+081A ; XID_Continue # Lm SAMARITAN MODIFIER LETTER EPENTHETIC YUT
+081B..0823 ; XID_Continue # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A
+0824 ; XID_Continue # Lm SAMARITAN MODIFIER LETTER SHORT A
+0825..0827 ; XID_Continue # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U
+0828 ; XID_Continue # Lm SAMARITAN MODIFIER LETTER I
+0829..082D ; XID_Continue # Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA
+0840..0858 ; XID_Continue # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN
+0859..085B ; XID_Continue # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK
+0860..086A ; XID_Continue # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA
+0870..0887 ; XID_Continue # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT
+0889..088E ; XID_Continue # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL
+0897..089F ; XID_Continue # Mn [9] ARABIC PEPET..ARABIC HALF MADDA OVER MADDA
+08A0..08C8 ; XID_Continue # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF
+08C9 ; XID_Continue # Lm ARABIC SMALL FARSI YEH
+08CA..08E1 ; XID_Continue # Mn [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA
+08E3..0902 ; XID_Continue # Mn [32] ARABIC TURNED DAMMA BELOW..DEVANAGARI SIGN ANUSVARA
+0903 ; XID_Continue # Mc DEVANAGARI SIGN VISARGA
+0904..0939 ; XID_Continue # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA
+093A ; XID_Continue # Mn DEVANAGARI VOWEL SIGN OE
+093B ; XID_Continue # Mc DEVANAGARI VOWEL SIGN OOE
+093C ; XID_Continue # Mn DEVANAGARI SIGN NUKTA
+093D ; XID_Continue # Lo DEVANAGARI SIGN AVAGRAHA
+093E..0940 ; XID_Continue # Mc [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II
+0941..0948 ; XID_Continue # Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI
+0949..094C ; XID_Continue # Mc [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU
+094D ; XID_Continue # Mn DEVANAGARI SIGN VIRAMA
+094E..094F ; XID_Continue # Mc [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW
+0950 ; XID_Continue # Lo DEVANAGARI OM
+0951..0957 ; XID_Continue # Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE
+0958..0961 ; XID_Continue # Lo [10] DEVANAGARI LETTER QA..DEVANAGARI LETTER VOCALIC LL
+0962..0963 ; XID_Continue # Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL
+0966..096F ; XID_Continue # Nd [10] DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE
+0971 ; XID_Continue # Lm DEVANAGARI SIGN HIGH SPACING DOT
+0972..0980 ; XID_Continue # Lo [15] DEVANAGARI LETTER CANDRA A..BENGALI ANJI
+0981 ; XID_Continue # Mn BENGALI SIGN CANDRABINDU
+0982..0983 ; XID_Continue # Mc [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA
+0985..098C ; XID_Continue # Lo [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L
+098F..0990 ; XID_Continue # Lo [2] BENGALI LETTER E..BENGALI LETTER AI
+0993..09A8 ; XID_Continue # Lo [22] BENGALI LETTER O..BENGALI LETTER NA
+09AA..09B0 ; XID_Continue # Lo [7] BENGALI LETTER PA..BENGALI LETTER RA
+09B2 ; XID_Continue # Lo BENGALI LETTER LA
+09B6..09B9 ; XID_Continue # Lo [4] BENGALI LETTER SHA..BENGALI LETTER HA
+09BC ; XID_Continue # Mn BENGALI SIGN NUKTA
+09BD ; XID_Continue # Lo BENGALI SIGN AVAGRAHA
+09BE..09C0 ; XID_Continue # Mc [3] BENGALI VOWEL SIGN AA..BENGALI VOWEL SIGN II
+09C1..09C4 ; XID_Continue # Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR
+09C7..09C8 ; XID_Continue # Mc [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI
+09CB..09CC ; XID_Continue # Mc [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU
+09CD ; XID_Continue # Mn BENGALI SIGN VIRAMA
+09CE ; XID_Continue # Lo BENGALI LETTER KHANDA TA
+09D7 ; XID_Continue # Mc BENGALI AU LENGTH MARK
+09DC..09DD ; XID_Continue # Lo [2] BENGALI LETTER RRA..BENGALI LETTER RHA
+09DF..09E1 ; XID_Continue # Lo [3] BENGALI LETTER YYA..BENGALI LETTER VOCALIC LL
+09E2..09E3 ; XID_Continue # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL
+09E6..09EF ; XID_Continue # Nd [10] BENGALI DIGIT ZERO..BENGALI DIGIT NINE
+09F0..09F1 ; XID_Continue # Lo [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL
+09FC ; XID_Continue # Lo BENGALI LETTER VEDIC ANUSVARA
+09FE ; XID_Continue # Mn BENGALI SANDHI MARK
+0A01..0A02 ; XID_Continue # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI
+0A03 ; XID_Continue # Mc GURMUKHI SIGN VISARGA
+0A05..0A0A ; XID_Continue # Lo [6] GURMUKHI LETTER A..GURMUKHI LETTER UU
+0A0F..0A10 ; XID_Continue # Lo [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI
+0A13..0A28 ; XID_Continue # Lo [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA
+0A2A..0A30 ; XID_Continue # Lo [7] GURMUKHI LETTER PA..GURMUKHI LETTER RA
+0A32..0A33 ; XID_Continue # Lo [2] GURMUKHI LETTER LA..GURMUKHI LETTER LLA
+0A35..0A36 ; XID_Continue # Lo [2] GURMUKHI LETTER VA..GURMUKHI LETTER SHA
+0A38..0A39 ; XID_Continue # Lo [2] GURMUKHI LETTER SA..GURMUKHI LETTER HA
+0A3C ; XID_Continue # Mn GURMUKHI SIGN NUKTA
+0A3E..0A40 ; XID_Continue # Mc [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II
+0A41..0A42 ; XID_Continue # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU
+0A47..0A48 ; XID_Continue # Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI
+0A4B..0A4D ; XID_Continue # Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA
+0A51 ; XID_Continue # Mn GURMUKHI SIGN UDAAT
+0A59..0A5C ; XID_Continue # Lo [4] GURMUKHI LETTER KHHA..GURMUKHI LETTER RRA
+0A5E ; XID_Continue # Lo GURMUKHI LETTER FA
+0A66..0A6F ; XID_Continue # Nd [10] GURMUKHI DIGIT ZERO..GURMUKHI DIGIT NINE
+0A70..0A71 ; XID_Continue # Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK
+0A72..0A74 ; XID_Continue # Lo [3] GURMUKHI IRI..GURMUKHI EK ONKAR
+0A75 ; XID_Continue # Mn GURMUKHI SIGN YAKASH
+0A81..0A82 ; XID_Continue # Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA
+0A83 ; XID_Continue # Mc GUJARATI SIGN VISARGA
+0A85..0A8D ; XID_Continue # Lo [9] GUJARATI LETTER A..GUJARATI VOWEL CANDRA E
+0A8F..0A91 ; XID_Continue # Lo [3] GUJARATI LETTER E..GUJARATI VOWEL CANDRA O
+0A93..0AA8 ; XID_Continue # Lo [22] GUJARATI LETTER O..GUJARATI LETTER NA
+0AAA..0AB0 ; XID_Continue # Lo [7] GUJARATI LETTER PA..GUJARATI LETTER RA
+0AB2..0AB3 ; XID_Continue # Lo [2] GUJARATI LETTER LA..GUJARATI LETTER LLA
+0AB5..0AB9 ; XID_Continue # Lo [5] GUJARATI LETTER VA..GUJARATI LETTER HA
+0ABC ; XID_Continue # Mn GUJARATI SIGN NUKTA
+0ABD ; XID_Continue # Lo GUJARATI SIGN AVAGRAHA
+0ABE..0AC0 ; XID_Continue # Mc [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II
+0AC1..0AC5 ; XID_Continue # Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E
+0AC7..0AC8 ; XID_Continue # Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI
+0AC9 ; XID_Continue # Mc GUJARATI VOWEL SIGN CANDRA O
+0ACB..0ACC ; XID_Continue # Mc [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU
+0ACD ; XID_Continue # Mn GUJARATI SIGN VIRAMA
+0AD0 ; XID_Continue # Lo GUJARATI OM
+0AE0..0AE1 ; XID_Continue # Lo [2] GUJARATI LETTER VOCALIC RR..GUJARATI LETTER VOCALIC LL
+0AE2..0AE3 ; XID_Continue # Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL
+0AE6..0AEF ; XID_Continue # Nd [10] GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE
+0AF9 ; XID_Continue # Lo GUJARATI LETTER ZHA
+0AFA..0AFF ; XID_Continue # Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE
+0B01 ; XID_Continue # Mn ORIYA SIGN CANDRABINDU
+0B02..0B03 ; XID_Continue # Mc [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA
+0B05..0B0C ; XID_Continue # Lo [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L
+0B0F..0B10 ; XID_Continue # Lo [2] ORIYA LETTER E..ORIYA LETTER AI
+0B13..0B28 ; XID_Continue # Lo [22] ORIYA LETTER O..ORIYA LETTER NA
+0B2A..0B30 ; XID_Continue # Lo [7] ORIYA LETTER PA..ORIYA LETTER RA
+0B32..0B33 ; XID_Continue # Lo [2] ORIYA LETTER LA..ORIYA LETTER LLA
+0B35..0B39 ; XID_Continue # Lo [5] ORIYA LETTER VA..ORIYA LETTER HA
+0B3C ; XID_Continue # Mn ORIYA SIGN NUKTA
+0B3D ; XID_Continue # Lo ORIYA SIGN AVAGRAHA
+0B3E ; XID_Continue # Mc ORIYA VOWEL SIGN AA
+0B3F ; XID_Continue # Mn ORIYA VOWEL SIGN I
+0B40 ; XID_Continue # Mc ORIYA VOWEL SIGN II
+0B41..0B44 ; XID_Continue # Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR
+0B47..0B48 ; XID_Continue # Mc [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI
+0B4B..0B4C ; XID_Continue # Mc [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU
+0B4D ; XID_Continue # Mn ORIYA SIGN VIRAMA
+0B55..0B56 ; XID_Continue # Mn [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK
+0B57 ; XID_Continue # Mc ORIYA AU LENGTH MARK
+0B5C..0B5D ; XID_Continue # Lo [2] ORIYA LETTER RRA..ORIYA LETTER RHA
+0B5F..0B61 ; XID_Continue # Lo [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL
+0B62..0B63 ; XID_Continue # Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL
+0B66..0B6F ; XID_Continue # Nd [10] ORIYA DIGIT ZERO..ORIYA DIGIT NINE
+0B71 ; XID_Continue # Lo ORIYA LETTER WA
+0B82 ; XID_Continue # Mn TAMIL SIGN ANUSVARA
+0B83 ; XID_Continue # Lo TAMIL SIGN VISARGA
+0B85..0B8A ; XID_Continue # Lo [6] TAMIL LETTER A..TAMIL LETTER UU
+0B8E..0B90 ; XID_Continue # Lo [3] TAMIL LETTER E..TAMIL LETTER AI
+0B92..0B95 ; XID_Continue # Lo [4] TAMIL LETTER O..TAMIL LETTER KA
+0B99..0B9A ; XID_Continue # Lo [2] TAMIL LETTER NGA..TAMIL LETTER CA
+0B9C ; XID_Continue # Lo TAMIL LETTER JA
+0B9E..0B9F ; XID_Continue # Lo [2] TAMIL LETTER NYA..TAMIL LETTER TTA
+0BA3..0BA4 ; XID_Continue # Lo [2] TAMIL LETTER NNA..TAMIL LETTER TA
+0BA8..0BAA ; XID_Continue # Lo [3] TAMIL LETTER NA..TAMIL LETTER PA
+0BAE..0BB9 ; XID_Continue # Lo [12] TAMIL LETTER MA..TAMIL LETTER HA
+0BBE..0BBF ; XID_Continue # Mc [2] TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN I
+0BC0 ; XID_Continue # Mn TAMIL VOWEL SIGN II
+0BC1..0BC2 ; XID_Continue # Mc [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU
+0BC6..0BC8 ; XID_Continue # Mc [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI
+0BCA..0BCC ; XID_Continue # Mc [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU
+0BCD ; XID_Continue # Mn TAMIL SIGN VIRAMA
+0BD0 ; XID_Continue # Lo TAMIL OM
+0BD7 ; XID_Continue # Mc TAMIL AU LENGTH MARK
+0BE6..0BEF ; XID_Continue # Nd [10] TAMIL DIGIT ZERO..TAMIL DIGIT NINE
+0C00 ; XID_Continue # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE
+0C01..0C03 ; XID_Continue # Mc [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA
+0C04 ; XID_Continue # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE
+0C05..0C0C ; XID_Continue # Lo [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L
+0C0E..0C10 ; XID_Continue # Lo [3] TELUGU LETTER E..TELUGU LETTER AI
+0C12..0C28 ; XID_Continue # Lo [23] TELUGU LETTER O..TELUGU LETTER NA
+0C2A..0C39 ; XID_Continue # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA
+0C3C ; XID_Continue # Mn TELUGU SIGN NUKTA
+0C3D ; XID_Continue # Lo TELUGU SIGN AVAGRAHA
+0C3E..0C40 ; XID_Continue # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II
+0C41..0C44 ; XID_Continue # Mc [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR
+0C46..0C48 ; XID_Continue # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI
+0C4A..0C4D ; XID_Continue # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA
+0C55..0C56 ; XID_Continue # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK
+0C58..0C5A ; XID_Continue # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA
+0C5D ; XID_Continue # Lo TELUGU LETTER NAKAARA POLLU
+0C60..0C61 ; XID_Continue # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL
+0C62..0C63 ; XID_Continue # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL
+0C66..0C6F ; XID_Continue # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE
+0C80 ; XID_Continue # Lo KANNADA SIGN SPACING CANDRABINDU
+0C81 ; XID_Continue # Mn KANNADA SIGN CANDRABINDU
+0C82..0C83 ; XID_Continue # Mc [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA
+0C85..0C8C ; XID_Continue # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L
+0C8E..0C90 ; XID_Continue # Lo [3] KANNADA LETTER E..KANNADA LETTER AI
+0C92..0CA8 ; XID_Continue # Lo [23] KANNADA LETTER O..KANNADA LETTER NA
+0CAA..0CB3 ; XID_Continue # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA
+0CB5..0CB9 ; XID_Continue # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA
+0CBC ; XID_Continue # Mn KANNADA SIGN NUKTA
+0CBD ; XID_Continue # Lo KANNADA SIGN AVAGRAHA
+0CBE ; XID_Continue # Mc KANNADA VOWEL SIGN AA
+0CBF ; XID_Continue # Mn KANNADA VOWEL SIGN I
+0CC0..0CC4 ; XID_Continue # Mc [5] KANNADA VOWEL SIGN II..KANNADA VOWEL SIGN VOCALIC RR
+0CC6 ; XID_Continue # Mn KANNADA VOWEL SIGN E
+0CC7..0CC8 ; XID_Continue # Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI
+0CCA..0CCB ; XID_Continue # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO
+0CCC..0CCD ; XID_Continue # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA
+0CD5..0CD6 ; XID_Continue # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK
+0CDD..0CDE ; XID_Continue # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA
+0CE0..0CE1 ; XID_Continue # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL
+0CE2..0CE3 ; XID_Continue # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL
+0CE6..0CEF ; XID_Continue # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE
+0CF1..0CF2 ; XID_Continue # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA
+0CF3 ; XID_Continue # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT
+0D00..0D01 ; XID_Continue # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU
+0D02..0D03 ; XID_Continue # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA
+0D04..0D0C ; XID_Continue # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L
+0D0E..0D10 ; XID_Continue # Lo [3] MALAYALAM LETTER E..MALAYALAM LETTER AI
+0D12..0D3A ; XID_Continue # Lo [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA
+0D3B..0D3C ; XID_Continue # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA
+0D3D ; XID_Continue # Lo MALAYALAM SIGN AVAGRAHA
+0D3E..0D40 ; XID_Continue # Mc [3] MALAYALAM VOWEL SIGN AA..MALAYALAM VOWEL SIGN II
+0D41..0D44 ; XID_Continue # Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR
+0D46..0D48 ; XID_Continue # Mc [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI
+0D4A..0D4C ; XID_Continue # Mc [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU
+0D4D ; XID_Continue # Mn MALAYALAM SIGN VIRAMA
+0D4E ; XID_Continue # Lo MALAYALAM LETTER DOT REPH
+0D54..0D56 ; XID_Continue # Lo [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL
+0D57 ; XID_Continue # Mc MALAYALAM AU LENGTH MARK
+0D5F..0D61 ; XID_Continue # Lo [3] MALAYALAM LETTER ARCHAIC II..MALAYALAM LETTER VOCALIC LL
+0D62..0D63 ; XID_Continue # Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL
+0D66..0D6F ; XID_Continue # Nd [10] MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE
+0D7A..0D7F ; XID_Continue # Lo [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K
+0D81 ; XID_Continue # Mn SINHALA SIGN CANDRABINDU
+0D82..0D83 ; XID_Continue # Mc [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA
+0D85..0D96 ; XID_Continue # Lo [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA
+0D9A..0DB1 ; XID_Continue # Lo [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA
+0DB3..0DBB ; XID_Continue # Lo [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA
+0DBD ; XID_Continue # Lo SINHALA LETTER DANTAJA LAYANNA
+0DC0..0DC6 ; XID_Continue # Lo [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA
+0DCA ; XID_Continue # Mn SINHALA SIGN AL-LAKUNA
+0DCF..0DD1 ; XID_Continue # Mc [3] SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA
+0DD2..0DD4 ; XID_Continue # Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA
+0DD6 ; XID_Continue # Mn SINHALA VOWEL SIGN DIGA PAA-PILLA
+0DD8..0DDF ; XID_Continue # Mc [8] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN GAYANUKITTA
+0DE6..0DEF ; XID_Continue # Nd [10] SINHALA LITH DIGIT ZERO..SINHALA LITH DIGIT NINE
+0DF2..0DF3 ; XID_Continue # Mc [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA
+0E01..0E30 ; XID_Continue # Lo [48] THAI CHARACTER KO KAI..THAI CHARACTER SARA A
+0E31 ; XID_Continue # Mn THAI CHARACTER MAI HAN-AKAT
+0E32..0E33 ; XID_Continue # Lo [2] THAI CHARACTER SARA AA..THAI CHARACTER SARA AM
+0E34..0E3A ; XID_Continue # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU
+0E40..0E45 ; XID_Continue # Lo [6] THAI CHARACTER SARA E..THAI CHARACTER LAKKHANGYAO
+0E46 ; XID_Continue # Lm THAI CHARACTER MAIYAMOK
+0E47..0E4E ; XID_Continue # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN
+0E50..0E59 ; XID_Continue # Nd [10] THAI DIGIT ZERO..THAI DIGIT NINE
+0E81..0E82 ; XID_Continue # Lo [2] LAO LETTER KO..LAO LETTER KHO SUNG
+0E84 ; XID_Continue # Lo LAO LETTER KHO TAM
+0E86..0E8A ; XID_Continue # Lo [5] LAO LETTER PALI GHA..LAO LETTER SO TAM
+0E8C..0EA3 ; XID_Continue # Lo [24] LAO LETTER PALI JHA..LAO LETTER LO LING
+0EA5 ; XID_Continue # Lo LAO LETTER LO LOOT
+0EA7..0EB0 ; XID_Continue # Lo [10] LAO LETTER WO..LAO VOWEL SIGN A
+0EB1 ; XID_Continue # Mn LAO VOWEL SIGN MAI KAN
+0EB2..0EB3 ; XID_Continue # Lo [2] LAO VOWEL SIGN AA..LAO VOWEL SIGN AM
+0EB4..0EBC ; XID_Continue # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO
+0EBD ; XID_Continue # Lo LAO SEMIVOWEL SIGN NYO
+0EC0..0EC4 ; XID_Continue # Lo [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI
+0EC6 ; XID_Continue # Lm LAO KO LA
+0EC8..0ECE ; XID_Continue # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN
+0ED0..0ED9 ; XID_Continue # Nd [10] LAO DIGIT ZERO..LAO DIGIT NINE
+0EDC..0EDF ; XID_Continue # Lo [4] LAO HO NO..LAO LETTER KHMU NYO
+0F00 ; XID_Continue # Lo TIBETAN SYLLABLE OM
+0F18..0F19 ; XID_Continue # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS
+0F20..0F29 ; XID_Continue # Nd [10] TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE
+0F35 ; XID_Continue # Mn TIBETAN MARK NGAS BZUNG NYI ZLA
+0F37 ; XID_Continue # Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS
+0F39 ; XID_Continue # Mn TIBETAN MARK TSA -PHRU
+0F3E..0F3F ; XID_Continue # Mc [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES
+0F40..0F47 ; XID_Continue # Lo [8] TIBETAN LETTER KA..TIBETAN LETTER JA
+0F49..0F6C ; XID_Continue # Lo [36] TIBETAN LETTER NYA..TIBETAN LETTER RRA
+0F71..0F7E ; XID_Continue # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO
+0F7F ; XID_Continue # Mc TIBETAN SIGN RNAM BCAD
+0F80..0F84 ; XID_Continue # Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA
+0F86..0F87 ; XID_Continue # Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS
+0F88..0F8C ; XID_Continue # Lo [5] TIBETAN SIGN LCE TSA CAN..TIBETAN SIGN INVERTED MCHU CAN
+0F8D..0F97 ; XID_Continue # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA
+0F99..0FBC ; XID_Continue # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA
+0FC6 ; XID_Continue # Mn TIBETAN SYMBOL PADMA GDAN
+1000..102A ; XID_Continue # Lo [43] MYANMAR LETTER KA..MYANMAR LETTER AU
+102B..102C ; XID_Continue # Mc [2] MYANMAR VOWEL SIGN TALL AA..MYANMAR VOWEL SIGN AA
+102D..1030 ; XID_Continue # Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU
+1031 ; XID_Continue # Mc MYANMAR VOWEL SIGN E
+1032..1037 ; XID_Continue # Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW
+1038 ; XID_Continue # Mc MYANMAR SIGN VISARGA
+1039..103A ; XID_Continue # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT
+103B..103C ; XID_Continue # Mc [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA
+103D..103E ; XID_Continue # Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA
+103F ; XID_Continue # Lo MYANMAR LETTER GREAT SA
+1040..1049 ; XID_Continue # Nd [10] MYANMAR DIGIT ZERO..MYANMAR DIGIT NINE
+1050..1055 ; XID_Continue # Lo [6] MYANMAR LETTER SHA..MYANMAR LETTER VOCALIC LL
+1056..1057 ; XID_Continue # Mc [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR
+1058..1059 ; XID_Continue # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL
+105A..105D ; XID_Continue # Lo [4] MYANMAR LETTER MON NGA..MYANMAR LETTER MON BBE
+105E..1060 ; XID_Continue # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA
+1061 ; XID_Continue # Lo MYANMAR LETTER SGAW KAREN SHA
+1062..1064 ; XID_Continue # Mc [3] MYANMAR VOWEL SIGN SGAW KAREN EU..MYANMAR TONE MARK SGAW KAREN KE PHO
+1065..1066 ; XID_Continue # Lo [2] MYANMAR LETTER WESTERN PWO KAREN THA..MYANMAR LETTER WESTERN PWO KAREN PWA
+1067..106D ; XID_Continue # Mc [7] MYANMAR VOWEL SIGN WESTERN PWO KAREN EU..MYANMAR SIGN WESTERN PWO KAREN TONE-5
+106E..1070 ; XID_Continue # Lo [3] MYANMAR LETTER EASTERN PWO KAREN NNA..MYANMAR LETTER EASTERN PWO KAREN GHWA
+1071..1074 ; XID_Continue # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE
+1075..1081 ; XID_Continue # Lo [13] MYANMAR LETTER SHAN KA..MYANMAR LETTER SHAN HA
+1082 ; XID_Continue # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA
+1083..1084 ; XID_Continue # Mc [2] MYANMAR VOWEL SIGN SHAN AA..MYANMAR VOWEL SIGN SHAN E
+1085..1086 ; XID_Continue # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y
+1087..108C ; XID_Continue # Mc [6] MYANMAR SIGN SHAN TONE-2..MYANMAR SIGN SHAN COUNCIL TONE-3
+108D ; XID_Continue # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE
+108E ; XID_Continue # Lo MYANMAR LETTER RUMAI PALAUNG FA
+108F ; XID_Continue # Mc MYANMAR SIGN RUMAI PALAUNG TONE-5
+1090..1099 ; XID_Continue # Nd [10] MYANMAR SHAN DIGIT ZERO..MYANMAR SHAN DIGIT NINE
+109A..109C ; XID_Continue # Mc [3] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON A
+109D ; XID_Continue # Mn MYANMAR VOWEL SIGN AITON AI
+10A0..10C5 ; XID_Continue # L& [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE
+10C7 ; XID_Continue # L& GEORGIAN CAPITAL LETTER YN
+10CD ; XID_Continue # L& GEORGIAN CAPITAL LETTER AEN
+10D0..10FA ; XID_Continue # L& [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN
+10FC ; XID_Continue # Lm MODIFIER LETTER GEORGIAN NAR
+10FD..10FF ; XID_Continue # L& [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN
+1100..1248 ; XID_Continue # Lo [329] HANGUL CHOSEONG KIYEOK..ETHIOPIC SYLLABLE QWA
+124A..124D ; XID_Continue # Lo [4] ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE
+1250..1256 ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO
+1258 ; XID_Continue # Lo ETHIOPIC SYLLABLE QHWA
+125A..125D ; XID_Continue # Lo [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE
+1260..1288 ; XID_Continue # Lo [41] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA
+128A..128D ; XID_Continue # Lo [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE
+1290..12B0 ; XID_Continue # Lo [33] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA
+12B2..12B5 ; XID_Continue # Lo [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE
+12B8..12BE ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO
+12C0 ; XID_Continue # Lo ETHIOPIC SYLLABLE KXWA
+12C2..12C5 ; XID_Continue # Lo [4] ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE
+12C8..12D6 ; XID_Continue # Lo [15] ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O
+12D8..1310 ; XID_Continue # Lo [57] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA
+1312..1315 ; XID_Continue # Lo [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE
+1318..135A ; XID_Continue # Lo [67] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA
+135D..135F ; XID_Continue # Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK
+1369..1371 ; XID_Continue # No [9] ETHIOPIC DIGIT ONE..ETHIOPIC DIGIT NINE
+1380..138F ; XID_Continue # Lo [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE
+13A0..13F5 ; XID_Continue # L& [86] CHEROKEE LETTER A..CHEROKEE LETTER MV
+13F8..13FD ; XID_Continue # L& [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV
+1401..166C ; XID_Continue # Lo [620] CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA
+166F..167F ; XID_Continue # Lo [17] CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W
+1681..169A ; XID_Continue # Lo [26] OGHAM LETTER BEITH..OGHAM LETTER PEITH
+16A0..16EA ; XID_Continue # Lo [75] RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X
+16EE..16F0 ; XID_Continue # Nl [3] RUNIC ARLAUG SYMBOL..RUNIC BELGTHOR SYMBOL
+16F1..16F8 ; XID_Continue # Lo [8] RUNIC LETTER K..RUNIC LETTER FRANKS CASKET AESC
+1700..1711 ; XID_Continue # Lo [18] TAGALOG LETTER A..TAGALOG LETTER HA
+1712..1714 ; XID_Continue # Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA
+1715 ; XID_Continue # Mc TAGALOG SIGN PAMUDPOD
+171F..1731 ; XID_Continue # Lo [19] TAGALOG LETTER ARCHAIC RA..HANUNOO LETTER HA
+1732..1733 ; XID_Continue # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U
+1734 ; XID_Continue # Mc HANUNOO SIGN PAMUDPOD
+1740..1751 ; XID_Continue # Lo [18] BUHID LETTER A..BUHID LETTER HA
+1752..1753 ; XID_Continue # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U
+1760..176C ; XID_Continue # Lo [13] TAGBANWA LETTER A..TAGBANWA LETTER YA
+176E..1770 ; XID_Continue # Lo [3] TAGBANWA LETTER LA..TAGBANWA LETTER SA
+1772..1773 ; XID_Continue # Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U
+1780..17B3 ; XID_Continue # Lo [52] KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU
+17B4..17B5 ; XID_Continue # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
+17B6 ; XID_Continue # Mc KHMER VOWEL SIGN AA
+17B7..17BD ; XID_Continue # Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA
+17BE..17C5 ; XID_Continue # Mc [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU
+17C6 ; XID_Continue # Mn KHMER SIGN NIKAHIT
+17C7..17C8 ; XID_Continue # Mc [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU
+17C9..17D3 ; XID_Continue # Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT
+17D7 ; XID_Continue # Lm KHMER SIGN LEK TOO
+17DC ; XID_Continue # Lo KHMER SIGN AVAKRAHASANYA
+17DD ; XID_Continue # Mn KHMER SIGN ATTHACAN
+17E0..17E9 ; XID_Continue # Nd [10] KHMER DIGIT ZERO..KHMER DIGIT NINE
+180B..180D ; XID_Continue # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
+180F ; XID_Continue # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR
+1810..1819 ; XID_Continue # Nd [10] MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE
+1820..1842 ; XID_Continue # Lo [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI
+1843 ; XID_Continue # Lm MONGOLIAN LETTER TODO LONG VOWEL SIGN
+1844..1878 ; XID_Continue # Lo [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS
+1880..1884 ; XID_Continue # Lo [5] MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER ALI GALI INVERTED UBADAMA
+1885..1886 ; XID_Continue # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA
+1887..18A8 ; XID_Continue # Lo [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA
+18A9 ; XID_Continue # Mn MONGOLIAN LETTER ALI GALI DAGALGA
+18AA ; XID_Continue # Lo MONGOLIAN LETTER MANCHU ALI GALI LHA
+18B0..18F5 ; XID_Continue # Lo [70] CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S
+1900..191E ; XID_Continue # Lo [31] LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER TRA
+1920..1922 ; XID_Continue # Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U
+1923..1926 ; XID_Continue # Mc [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU
+1927..1928 ; XID_Continue # Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O
+1929..192B ; XID_Continue # Mc [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA
+1930..1931 ; XID_Continue # Mc [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA
+1932 ; XID_Continue # Mn LIMBU SMALL LETTER ANUSVARA
+1933..1938 ; XID_Continue # Mc [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA
+1939..193B ; XID_Continue # Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I
+1946..194F ; XID_Continue # Nd [10] LIMBU DIGIT ZERO..LIMBU DIGIT NINE
+1950..196D ; XID_Continue # Lo [30] TAI LE LETTER KA..TAI LE LETTER AI
+1970..1974 ; XID_Continue # Lo [5] TAI LE LETTER TONE-2..TAI LE LETTER TONE-6
+1980..19AB ; XID_Continue # Lo [44] NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA
+19B0..19C9 ; XID_Continue # Lo [26] NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2
+19D0..19D9 ; XID_Continue # Nd [10] NEW TAI LUE DIGIT ZERO..NEW TAI LUE DIGIT NINE
+19DA ; XID_Continue # No NEW TAI LUE THAM DIGIT ONE
+1A00..1A16 ; XID_Continue # Lo [23] BUGINESE LETTER KA..BUGINESE LETTER HA
+1A17..1A18 ; XID_Continue # Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U
+1A19..1A1A ; XID_Continue # Mc [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O
+1A1B ; XID_Continue # Mn BUGINESE VOWEL SIGN AE
+1A20..1A54 ; XID_Continue # Lo [53] TAI THAM LETTER HIGH KA..TAI THAM LETTER GREAT SA
+1A55 ; XID_Continue # Mc TAI THAM CONSONANT SIGN MEDIAL RA
+1A56 ; XID_Continue # Mn TAI THAM CONSONANT SIGN MEDIAL LA
+1A57 ; XID_Continue # Mc TAI THAM CONSONANT SIGN LA TANG LAI
+1A58..1A5E ; XID_Continue # Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA
+1A60 ; XID_Continue # Mn TAI THAM SIGN SAKOT
+1A61 ; XID_Continue # Mc TAI THAM VOWEL SIGN A
+1A62 ; XID_Continue # Mn TAI THAM VOWEL SIGN MAI SAT
+1A63..1A64 ; XID_Continue # Mc [2] TAI THAM VOWEL SIGN AA..TAI THAM VOWEL SIGN TALL AA
+1A65..1A6C ; XID_Continue # Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW
+1A6D..1A72 ; XID_Continue # Mc [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI
+1A73..1A7C ; XID_Continue # Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN
+1A7F ; XID_Continue # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT
+1A80..1A89 ; XID_Continue # Nd [10] TAI THAM HORA DIGIT ZERO..TAI THAM HORA DIGIT NINE
+1A90..1A99 ; XID_Continue # Nd [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE
+1AA7 ; XID_Continue # Lm TAI THAM SIGN MAI YAMOK
+1AB0..1ABD ; XID_Continue # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW
+1ABF..1ACE ; XID_Continue # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T
+1B00..1B03 ; XID_Continue # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG
+1B04 ; XID_Continue # Mc BALINESE SIGN BISAH
+1B05..1B33 ; XID_Continue # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA
+1B34 ; XID_Continue # Mn BALINESE SIGN REREKAN
+1B35 ; XID_Continue # Mc BALINESE VOWEL SIGN TEDUNG
+1B36..1B3A ; XID_Continue # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA
+1B3B ; XID_Continue # Mc BALINESE VOWEL SIGN RA REPA TEDUNG
+1B3C ; XID_Continue # Mn BALINESE VOWEL SIGN LA LENGA
+1B3D..1B41 ; XID_Continue # Mc [5] BALINESE VOWEL SIGN LA LENGA TEDUNG..BALINESE VOWEL SIGN TALING REPA TEDUNG
+1B42 ; XID_Continue # Mn BALINESE VOWEL SIGN PEPET
+1B43..1B44 ; XID_Continue # Mc [2] BALINESE VOWEL SIGN PEPET TEDUNG..BALINESE ADEG ADEG
+1B45..1B4C ; XID_Continue # Lo [8] BALINESE LETTER KAF SASAK..BALINESE LETTER ARCHAIC JNYA
+1B50..1B59 ; XID_Continue # Nd [10] BALINESE DIGIT ZERO..BALINESE DIGIT NINE
+1B6B..1B73 ; XID_Continue # Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG
+1B80..1B81 ; XID_Continue # Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR
+1B82 ; XID_Continue # Mc SUNDANESE SIGN PANGWISAD
+1B83..1BA0 ; XID_Continue # Lo [30] SUNDANESE LETTER A..SUNDANESE LETTER HA
+1BA1 ; XID_Continue # Mc SUNDANESE CONSONANT SIGN PAMINGKAL
+1BA2..1BA5 ; XID_Continue # Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU
+1BA6..1BA7 ; XID_Continue # Mc [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG
+1BA8..1BA9 ; XID_Continue # Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG
+1BAA ; XID_Continue # Mc SUNDANESE SIGN PAMAAEH
+1BAB..1BAD ; XID_Continue # Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA
+1BAE..1BAF ; XID_Continue # Lo [2] SUNDANESE LETTER KHA..SUNDANESE LETTER SYA
+1BB0..1BB9 ; XID_Continue # Nd [10] SUNDANESE DIGIT ZERO..SUNDANESE DIGIT NINE
+1BBA..1BE5 ; XID_Continue # Lo [44] SUNDANESE AVAGRAHA..BATAK LETTER U
+1BE6 ; XID_Continue # Mn BATAK SIGN TOMPI
+1BE7 ; XID_Continue # Mc BATAK VOWEL SIGN E
+1BE8..1BE9 ; XID_Continue # Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE
+1BEA..1BEC ; XID_Continue # Mc [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O
+1BED ; XID_Continue # Mn BATAK VOWEL SIGN KARO O
+1BEE ; XID_Continue # Mc BATAK VOWEL SIGN U
+1BEF..1BF1 ; XID_Continue # Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H
+1BF2..1BF3 ; XID_Continue # Mc [2] BATAK PANGOLAT..BATAK PANONGONAN
+1C00..1C23 ; XID_Continue # Lo [36] LEPCHA LETTER KA..LEPCHA LETTER A
+1C24..1C2B ; XID_Continue # Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU
+1C2C..1C33 ; XID_Continue # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T
+1C34..1C35 ; XID_Continue # Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG
+1C36..1C37 ; XID_Continue # Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA
+1C40..1C49 ; XID_Continue # Nd [10] LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE
+1C4D..1C4F ; XID_Continue # Lo [3] LEPCHA LETTER TTA..LEPCHA LETTER DDA
+1C50..1C59 ; XID_Continue # Nd [10] OL CHIKI DIGIT ZERO..OL CHIKI DIGIT NINE
+1C5A..1C77 ; XID_Continue # Lo [30] OL CHIKI LETTER LA..OL CHIKI LETTER OH
+1C78..1C7D ; XID_Continue # Lm [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD
+1C80..1C8A ; XID_Continue # L& [11] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER TJE
+1C90..1CBA ; XID_Continue # L& [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN
+1CBD..1CBF ; XID_Continue # L& [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
+1CD0..1CD2 ; XID_Continue # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA
+1CD4..1CE0 ; XID_Continue # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA
+1CE1 ; XID_Continue # Mc VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA
+1CE2..1CE8 ; XID_Continue # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL
+1CE9..1CEC ; XID_Continue # Lo [4] VEDIC SIGN ANUSVARA ANTARGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL
+1CED ; XID_Continue # Mn VEDIC SIGN TIRYAK
+1CEE..1CF3 ; XID_Continue # Lo [6] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ROTATED ARDHAVISARGA
+1CF4 ; XID_Continue # Mn VEDIC TONE CANDRA ABOVE
+1CF5..1CF6 ; XID_Continue # Lo [2] VEDIC SIGN JIHVAMULIYA..VEDIC SIGN UPADHMANIYA
+1CF7 ; XID_Continue # Mc VEDIC SIGN ATIKRAMA
+1CF8..1CF9 ; XID_Continue # Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE
+1CFA ; XID_Continue # Lo VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA
+1D00..1D2B ; XID_Continue # L& [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL
+1D2C..1D6A ; XID_Continue # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI
+1D6B..1D77 ; XID_Continue # L& [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G
+1D78 ; XID_Continue # Lm MODIFIER LETTER CYRILLIC EN
+1D79..1D9A ; XID_Continue # L& [34] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK
+1D9B..1DBF ; XID_Continue # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA
+1DC0..1DFF ; XID_Continue # Mn [64] COMBINING DOTTED GRAVE ACCENT..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW
+1E00..1F15 ; XID_Continue # L& [278] LATIN CAPITAL LETTER A WITH RING BELOW..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA
+1F18..1F1D ; XID_Continue # L& [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F20..1F45 ; XID_Continue # L& [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA
+1F48..1F4D ; XID_Continue # L& [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F50..1F57 ; XID_Continue # L& [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F59 ; XID_Continue # L& GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B ; XID_Continue # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D ; XID_Continue # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F..1F7D ; XID_Continue # L& [31] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER OMEGA WITH OXIA
+1F80..1FB4 ; XID_Continue # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6..1FBC ; XID_Continue # L& [7] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBE ; XID_Continue # L& GREEK PROSGEGRAMMENI
+1FC2..1FC4 ; XID_Continue # L& [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6..1FCC ; XID_Continue # L& [7] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FD0..1FD3 ; XID_Continue # L& [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6..1FDB ; XID_Continue # L& [6] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH OXIA
+1FE0..1FEC ; XID_Continue # L& [13] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA
+1FF2..1FF4 ; XID_Continue # L& [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6..1FFC ; XID_Continue # L& [7] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+200C..200D ; XID_Continue # Cf [2] ZERO WIDTH NON-JOINER..ZERO WIDTH JOINER
+203F..2040 ; XID_Continue # Pc [2] UNDERTIE..CHARACTER TIE
+2054 ; XID_Continue # Pc INVERTED UNDERTIE
+2071 ; XID_Continue # Lm SUPERSCRIPT LATIN SMALL LETTER I
+207F ; XID_Continue # Lm SUPERSCRIPT LATIN SMALL LETTER N
+2090..209C ; XID_Continue # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T
+20D0..20DC ; XID_Continue # Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE
+20E1 ; XID_Continue # Mn COMBINING LEFT RIGHT ARROW ABOVE
+20E5..20F0 ; XID_Continue # Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE
+2102 ; XID_Continue # L& DOUBLE-STRUCK CAPITAL C
+2107 ; XID_Continue # L& EULER CONSTANT
+210A..2113 ; XID_Continue # L& [10] SCRIPT SMALL G..SCRIPT SMALL L
+2115 ; XID_Continue # L& DOUBLE-STRUCK CAPITAL N
+2118 ; XID_Continue # Sm SCRIPT CAPITAL P
+2119..211D ; XID_Continue # L& [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R
+2124 ; XID_Continue # L& DOUBLE-STRUCK CAPITAL Z
+2126 ; XID_Continue # L& OHM SIGN
+2128 ; XID_Continue # L& BLACK-LETTER CAPITAL Z
+212A..212D ; XID_Continue # L& [4] KELVIN SIGN..BLACK-LETTER CAPITAL C
+212E ; XID_Continue # So ESTIMATED SYMBOL
+212F..2134 ; XID_Continue # L& [6] SCRIPT SMALL E..SCRIPT SMALL O
+2135..2138 ; XID_Continue # Lo [4] ALEF SYMBOL..DALET SYMBOL
+2139 ; XID_Continue # L& INFORMATION SOURCE
+213C..213F ; XID_Continue # L& [4] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK CAPITAL PI
+2145..2149 ; XID_Continue # L& [5] DOUBLE-STRUCK ITALIC CAPITAL D..DOUBLE-STRUCK ITALIC SMALL J
+214E ; XID_Continue # L& TURNED SMALL F
+2160..2182 ; XID_Continue # Nl [35] ROMAN NUMERAL ONE..ROMAN NUMERAL TEN THOUSAND
+2183..2184 ; XID_Continue # L& [2] ROMAN NUMERAL REVERSED ONE HUNDRED..LATIN SMALL LETTER REVERSED C
+2185..2188 ; XID_Continue # Nl [4] ROMAN NUMERAL SIX LATE FORM..ROMAN NUMERAL ONE HUNDRED THOUSAND
+2C00..2C7B ; XID_Continue # L& [124] GLAGOLITIC CAPITAL LETTER AZU..LATIN LETTER SMALL CAPITAL TURNED E
+2C7C..2C7D ; XID_Continue # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V
+2C7E..2CE4 ; XID_Continue # L& [103] LATIN CAPITAL LETTER S WITH SWASH TAIL..COPTIC SYMBOL KAI
+2CEB..2CEE ; XID_Continue # L& [4] COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI..COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA
+2CEF..2CF1 ; XID_Continue # Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS
+2CF2..2CF3 ; XID_Continue # L& [2] COPTIC CAPITAL LETTER BOHAIRIC KHEI..COPTIC SMALL LETTER BOHAIRIC KHEI
+2D00..2D25 ; XID_Continue # L& [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE
+2D27 ; XID_Continue # L& GEORGIAN SMALL LETTER YN
+2D2D ; XID_Continue # L& GEORGIAN SMALL LETTER AEN
+2D30..2D67 ; XID_Continue # Lo [56] TIFINAGH LETTER YA..TIFINAGH LETTER YO
+2D6F ; XID_Continue # Lm TIFINAGH MODIFIER LETTER LABIALIZATION MARK
+2D7F ; XID_Continue # Mn TIFINAGH CONSONANT JOINER
+2D80..2D96 ; XID_Continue # Lo [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE
+2DA0..2DA6 ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO
+2DA8..2DAE ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO
+2DB0..2DB6 ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO
+2DB8..2DBE ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO
+2DC0..2DC6 ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO
+2DC8..2DCE ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO
+2DD0..2DD6 ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO
+2DD8..2DDE ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO
+2DE0..2DFF ; XID_Continue # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS
+3005 ; XID_Continue # Lm IDEOGRAPHIC ITERATION MARK
+3006 ; XID_Continue # Lo IDEOGRAPHIC CLOSING MARK
+3007 ; XID_Continue # Nl IDEOGRAPHIC NUMBER ZERO
+3021..3029 ; XID_Continue # Nl [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE
+302A..302D ; XID_Continue # Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK
+302E..302F ; XID_Continue # Mc [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK
+3031..3035 ; XID_Continue # Lm [5] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT MARK LOWER HALF
+3038..303A ; XID_Continue # Nl [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY
+303B ; XID_Continue # Lm VERTICAL IDEOGRAPHIC ITERATION MARK
+303C ; XID_Continue # Lo MASU MARK
+3041..3096 ; XID_Continue # Lo [86] HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMALL KE
+3099..309A ; XID_Continue # Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+309D..309E ; XID_Continue # Lm [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK
+309F ; XID_Continue # Lo HIRAGANA DIGRAPH YORI
+30A1..30FA ; XID_Continue # Lo [90] KATAKANA LETTER SMALL A..KATAKANA LETTER VO
+30FB ; XID_Continue # Po KATAKANA MIDDLE DOT
+30FC..30FE ; XID_Continue # Lm [3] KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK
+30FF ; XID_Continue # Lo KATAKANA DIGRAPH KOTO
+3105..312F ; XID_Continue # Lo [43] BOPOMOFO LETTER B..BOPOMOFO LETTER NN
+3131..318E ; XID_Continue # Lo [94] HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE
+31A0..31BF ; XID_Continue # Lo [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH
+31F0..31FF ; XID_Continue # Lo [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO
+3400..4DBF ; XID_Continue # Lo [6592] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DBF
+4E00..A014 ; XID_Continue # Lo [21013] CJK UNIFIED IDEOGRAPH-4E00..YI SYLLABLE E
+A015 ; XID_Continue # Lm YI SYLLABLE WU
+A016..A48C ; XID_Continue # Lo [1143] YI SYLLABLE BIT..YI SYLLABLE YYR
+A4D0..A4F7 ; XID_Continue # Lo [40] LISU LETTER BA..LISU LETTER OE
+A4F8..A4FD ; XID_Continue # Lm [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU
+A500..A60B ; XID_Continue # Lo [268] VAI SYLLABLE EE..VAI SYLLABLE NG
+A60C ; XID_Continue # Lm VAI SYLLABLE LENGTHENER
+A610..A61F ; XID_Continue # Lo [16] VAI SYLLABLE NDOLE FA..VAI SYMBOL JONG
+A620..A629 ; XID_Continue # Nd [10] VAI DIGIT ZERO..VAI DIGIT NINE
+A62A..A62B ; XID_Continue # Lo [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO
+A640..A66D ; XID_Continue # L& [46] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER DOUBLE MONOCULAR O
+A66E ; XID_Continue # Lo CYRILLIC LETTER MULTIOCULAR O
+A66F ; XID_Continue # Mn COMBINING CYRILLIC VZMET
+A674..A67D ; XID_Continue # Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK
+A67F ; XID_Continue # Lm CYRILLIC PAYEROK
+A680..A69B ; XID_Continue # L& [28] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER CROSSED O
+A69C..A69D ; XID_Continue # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN
+A69E..A69F ; XID_Continue # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E
+A6A0..A6E5 ; XID_Continue # Lo [70] BAMUM LETTER A..BAMUM LETTER KI
+A6E6..A6EF ; XID_Continue # Nl [10] BAMUM LETTER MO..BAMUM LETTER KOGHOM
+A6F0..A6F1 ; XID_Continue # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS
+A717..A71F ; XID_Continue # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK
+A722..A76F ; XID_Continue # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CON
+A770 ; XID_Continue # Lm MODIFIER LETTER US
+A771..A787 ; XID_Continue # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T
+A788 ; XID_Continue # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT
+A78B..A78E ; XID_Continue # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT
+A78F ; XID_Continue # Lo LATIN LETTER SINOLOGICAL DOT
+A790..A7CD ; XID_Continue # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE
+A7D0..A7D1 ; XID_Continue # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G
+A7D3 ; XID_Continue # L& LATIN SMALL LETTER DOUBLE THORN
+A7D5..A7DC ; XID_Continue # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE
+A7F2..A7F4 ; XID_Continue # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q
+A7F5..A7F6 ; XID_Continue # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H
+A7F7 ; XID_Continue # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I
+A7F8..A7F9 ; XID_Continue # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE
+A7FA ; XID_Continue # L& LATIN LETTER SMALL CAPITAL TURNED M
+A7FB..A801 ; XID_Continue # Lo [7] LATIN EPIGRAPHIC LETTER REVERSED F..SYLOTI NAGRI LETTER I
+A802 ; XID_Continue # Mn SYLOTI NAGRI SIGN DVISVARA
+A803..A805 ; XID_Continue # Lo [3] SYLOTI NAGRI LETTER U..SYLOTI NAGRI LETTER O
+A806 ; XID_Continue # Mn SYLOTI NAGRI SIGN HASANTA
+A807..A80A ; XID_Continue # Lo [4] SYLOTI NAGRI LETTER KO..SYLOTI NAGRI LETTER GHO
+A80B ; XID_Continue # Mn SYLOTI NAGRI SIGN ANUSVARA
+A80C..A822 ; XID_Continue # Lo [23] SYLOTI NAGRI LETTER CO..SYLOTI NAGRI LETTER HO
+A823..A824 ; XID_Continue # Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I
+A825..A826 ; XID_Continue # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E
+A827 ; XID_Continue # Mc SYLOTI NAGRI VOWEL SIGN OO
+A82C ; XID_Continue # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA
+A840..A873 ; XID_Continue # Lo [52] PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU
+A880..A881 ; XID_Continue # Mc [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA
+A882..A8B3 ; XID_Continue # Lo [50] SAURASHTRA LETTER A..SAURASHTRA LETTER LLA
+A8B4..A8C3 ; XID_Continue # Mc [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU
+A8C4..A8C5 ; XID_Continue # Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU
+A8D0..A8D9 ; XID_Continue # Nd [10] SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE
+A8E0..A8F1 ; XID_Continue # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA
+A8F2..A8F7 ; XID_Continue # Lo [6] DEVANAGARI SIGN SPACING CANDRABINDU..DEVANAGARI SIGN CANDRABINDU AVAGRAHA
+A8FB ; XID_Continue # Lo DEVANAGARI HEADSTROKE
+A8FD..A8FE ; XID_Continue # Lo [2] DEVANAGARI JAIN OM..DEVANAGARI LETTER AY
+A8FF ; XID_Continue # Mn DEVANAGARI VOWEL SIGN AY
+A900..A909 ; XID_Continue # Nd [10] KAYAH LI DIGIT ZERO..KAYAH LI DIGIT NINE
+A90A..A925 ; XID_Continue # Lo [28] KAYAH LI LETTER KA..KAYAH LI LETTER OO
+A926..A92D ; XID_Continue # Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU
+A930..A946 ; XID_Continue # Lo [23] REJANG LETTER KA..REJANG LETTER A
+A947..A951 ; XID_Continue # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R
+A952..A953 ; XID_Continue # Mc [2] REJANG CONSONANT SIGN H..REJANG VIRAMA
+A960..A97C ; XID_Continue # Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH
+A980..A982 ; XID_Continue # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR
+A983 ; XID_Continue # Mc JAVANESE SIGN WIGNYAN
+A984..A9B2 ; XID_Continue # Lo [47] JAVANESE LETTER A..JAVANESE LETTER HA
+A9B3 ; XID_Continue # Mn JAVANESE SIGN CECAK TELU
+A9B4..A9B5 ; XID_Continue # Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG
+A9B6..A9B9 ; XID_Continue # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT
+A9BA..A9BB ; XID_Continue # Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE
+A9BC..A9BD ; XID_Continue # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET
+A9BE..A9C0 ; XID_Continue # Mc [3] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE PANGKON
+A9CF ; XID_Continue # Lm JAVANESE PANGRANGKEP
+A9D0..A9D9 ; XID_Continue # Nd [10] JAVANESE DIGIT ZERO..JAVANESE DIGIT NINE
+A9E0..A9E4 ; XID_Continue # Lo [5] MYANMAR LETTER SHAN GHA..MYANMAR LETTER SHAN BHA
+A9E5 ; XID_Continue # Mn MYANMAR SIGN SHAN SAW
+A9E6 ; XID_Continue # Lm MYANMAR MODIFIER LETTER SHAN REDUPLICATION
+A9E7..A9EF ; XID_Continue # Lo [9] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING NNA
+A9F0..A9F9 ; XID_Continue # Nd [10] MYANMAR TAI LAING DIGIT ZERO..MYANMAR TAI LAING DIGIT NINE
+A9FA..A9FE ; XID_Continue # Lo [5] MYANMAR LETTER TAI LAING LLA..MYANMAR LETTER TAI LAING BHA
+AA00..AA28 ; XID_Continue # Lo [41] CHAM LETTER A..CHAM LETTER HA
+AA29..AA2E ; XID_Continue # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE
+AA2F..AA30 ; XID_Continue # Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI
+AA31..AA32 ; XID_Continue # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE
+AA33..AA34 ; XID_Continue # Mc [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA
+AA35..AA36 ; XID_Continue # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA
+AA40..AA42 ; XID_Continue # Lo [3] CHAM LETTER FINAL K..CHAM LETTER FINAL NG
+AA43 ; XID_Continue # Mn CHAM CONSONANT SIGN FINAL NG
+AA44..AA4B ; XID_Continue # Lo [8] CHAM LETTER FINAL CH..CHAM LETTER FINAL SS
+AA4C ; XID_Continue # Mn CHAM CONSONANT SIGN FINAL M
+AA4D ; XID_Continue # Mc CHAM CONSONANT SIGN FINAL H
+AA50..AA59 ; XID_Continue # Nd [10] CHAM DIGIT ZERO..CHAM DIGIT NINE
+AA60..AA6F ; XID_Continue # Lo [16] MYANMAR LETTER KHAMTI GA..MYANMAR LETTER KHAMTI FA
+AA70 ; XID_Continue # Lm MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION
+AA71..AA76 ; XID_Continue # Lo [6] MYANMAR LETTER KHAMTI XA..MYANMAR LOGOGRAM KHAMTI HM
+AA7A ; XID_Continue # Lo MYANMAR LETTER AITON RA
+AA7B ; XID_Continue # Mc MYANMAR SIGN PAO KAREN TONE
+AA7C ; XID_Continue # Mn MYANMAR SIGN TAI LAING TONE-2
+AA7D ; XID_Continue # Mc MYANMAR SIGN TAI LAING TONE-5
+AA7E..AAAF ; XID_Continue # Lo [50] MYANMAR LETTER SHWE PALAUNG CHA..TAI VIET LETTER HIGH O
+AAB0 ; XID_Continue # Mn TAI VIET MAI KANG
+AAB1 ; XID_Continue # Lo TAI VIET VOWEL AA
+AAB2..AAB4 ; XID_Continue # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U
+AAB5..AAB6 ; XID_Continue # Lo [2] TAI VIET VOWEL E..TAI VIET VOWEL O
+AAB7..AAB8 ; XID_Continue # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA
+AAB9..AABD ; XID_Continue # Lo [5] TAI VIET VOWEL UEA..TAI VIET VOWEL AN
+AABE..AABF ; XID_Continue # Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK
+AAC0 ; XID_Continue # Lo TAI VIET TONE MAI NUENG
+AAC1 ; XID_Continue # Mn TAI VIET TONE MAI THO
+AAC2 ; XID_Continue # Lo TAI VIET TONE MAI SONG
+AADB..AADC ; XID_Continue # Lo [2] TAI VIET SYMBOL KON..TAI VIET SYMBOL NUENG
+AADD ; XID_Continue # Lm TAI VIET SYMBOL SAM
+AAE0..AAEA ; XID_Continue # Lo [11] MEETEI MAYEK LETTER E..MEETEI MAYEK LETTER SSA
+AAEB ; XID_Continue # Mc MEETEI MAYEK VOWEL SIGN II
+AAEC..AAED ; XID_Continue # Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI
+AAEE..AAEF ; XID_Continue # Mc [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU
+AAF2 ; XID_Continue # Lo MEETEI MAYEK ANJI
+AAF3..AAF4 ; XID_Continue # Lm [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK
+AAF5 ; XID_Continue # Mc MEETEI MAYEK VOWEL SIGN VISARGA
+AAF6 ; XID_Continue # Mn MEETEI MAYEK VIRAMA
+AB01..AB06 ; XID_Continue # Lo [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO
+AB09..AB0E ; XID_Continue # Lo [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO
+AB11..AB16 ; XID_Continue # Lo [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO
+AB20..AB26 ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO
+AB28..AB2E ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO
+AB30..AB5A ; XID_Continue # L& [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG
+AB5C..AB5F ; XID_Continue # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK
+AB60..AB68 ; XID_Continue # L& [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE
+AB69 ; XID_Continue # Lm MODIFIER LETTER SMALL TURNED W
+AB70..ABBF ; XID_Continue # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA
+ABC0..ABE2 ; XID_Continue # Lo [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM
+ABE3..ABE4 ; XID_Continue # Mc [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP
+ABE5 ; XID_Continue # Mn MEETEI MAYEK VOWEL SIGN ANAP
+ABE6..ABE7 ; XID_Continue # Mc [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP
+ABE8 ; XID_Continue # Mn MEETEI MAYEK VOWEL SIGN UNAP
+ABE9..ABEA ; XID_Continue # Mc [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG
+ABEC ; XID_Continue # Mc MEETEI MAYEK LUM IYEK
+ABED ; XID_Continue # Mn MEETEI MAYEK APUN IYEK
+ABF0..ABF9 ; XID_Continue # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT NINE
+AC00..D7A3 ; XID_Continue # Lo [11172] HANGUL SYLLABLE GA..HANGUL SYLLABLE HIH
+D7B0..D7C6 ; XID_Continue # Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E
+D7CB..D7FB ; XID_Continue # Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH
+F900..FA6D ; XID_Continue # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D
+FA70..FAD9 ; XID_Continue # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9
+FB00..FB06 ; XID_Continue # L& [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST
+FB13..FB17 ; XID_Continue # L& [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH
+FB1D ; XID_Continue # Lo HEBREW LETTER YOD WITH HIRIQ
+FB1E ; XID_Continue # Mn HEBREW POINT JUDEO-SPANISH VARIKA
+FB1F..FB28 ; XID_Continue # Lo [10] HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER WIDE TAV
+FB2A..FB36 ; XID_Continue # Lo [13] HEBREW LETTER SHIN WITH SHIN DOT..HEBREW LETTER ZAYIN WITH DAGESH
+FB38..FB3C ; XID_Continue # Lo [5] HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH
+FB3E ; XID_Continue # Lo HEBREW LETTER MEM WITH DAGESH
+FB40..FB41 ; XID_Continue # Lo [2] HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH
+FB43..FB44 ; XID_Continue # Lo [2] HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH
+FB46..FBB1 ; XID_Continue # Lo [108] HEBREW LETTER TSADI WITH DAGESH..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM
+FBD3..FC5D ; XID_Continue # Lo [139] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF MAKSURA WITH SUPERSCRIPT ALEF ISOLATED FORM
+FC64..FD3D ; XID_Continue # Lo [218] ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH REH FINAL FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM
+FD50..FD8F ; XID_Continue # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM
+FD92..FDC7 ; XID_Continue # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM
+FDF0..FDF9 ; XID_Continue # Lo [10] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE SALLA ISOLATED FORM
+FE00..FE0F ; XID_Continue # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
+FE20..FE2F ; XID_Continue # Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF
+FE33..FE34 ; XID_Continue # Pc [2] PRESENTATION FORM FOR VERTICAL LOW LINE..PRESENTATION FORM FOR VERTICAL WAVY LOW LINE
+FE4D..FE4F ; XID_Continue # Pc [3] DASHED LOW LINE..WAVY LOW LINE
+FE71 ; XID_Continue # Lo ARABIC TATWEEL WITH FATHATAN ABOVE
+FE73 ; XID_Continue # Lo ARABIC TAIL FRAGMENT
+FE77 ; XID_Continue # Lo ARABIC FATHA MEDIAL FORM
+FE79 ; XID_Continue # Lo ARABIC DAMMA MEDIAL FORM
+FE7B ; XID_Continue # Lo ARABIC KASRA MEDIAL FORM
+FE7D ; XID_Continue # Lo ARABIC SHADDA MEDIAL FORM
+FE7F..FEFC ; XID_Continue # Lo [126] ARABIC SUKUN MEDIAL FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM
+FF10..FF19 ; XID_Continue # Nd [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE
+FF21..FF3A ; XID_Continue # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z
+FF3F ; XID_Continue # Pc FULLWIDTH LOW LINE
+FF41..FF5A ; XID_Continue # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z
+FF65 ; XID_Continue # Po HALFWIDTH KATAKANA MIDDLE DOT
+FF66..FF6F ; XID_Continue # Lo [10] HALFWIDTH KATAKANA LETTER WO..HALFWIDTH KATAKANA LETTER SMALL TU
+FF70 ; XID_Continue # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK
+FF71..FF9D ; XID_Continue # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAKANA LETTER N
+FF9E..FF9F ; XID_Continue # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
+FFA0..FFBE ; XID_Continue # Lo [31] HALFWIDTH HANGUL FILLER..HALFWIDTH HANGUL LETTER HIEUH
+FFC2..FFC7 ; XID_Continue # Lo [6] HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E
+FFCA..FFCF ; XID_Continue # Lo [6] HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE
+FFD2..FFD7 ; XID_Continue # Lo [6] HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU
+FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I
+10000..1000B ; XID_Continue # Lo [12] LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE
+1000D..10026 ; XID_Continue # Lo [26] LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO
+10028..1003A ; XID_Continue # Lo [19] LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO
+1003C..1003D ; XID_Continue # Lo [2] LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE
+1003F..1004D ; XID_Continue # Lo [15] LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO
+10050..1005D ; XID_Continue # Lo [14] LINEAR B SYMBOL B018..LINEAR B SYMBOL B089
+10080..100FA ; XID_Continue # Lo [123] LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305
+10140..10174 ; XID_Continue # Nl [53] GREEK ACROPHONIC ATTIC ONE QUARTER..GREEK ACROPHONIC STRATIAN FIFTY MNAS
+101FD ; XID_Continue # Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE
+10280..1029C ; XID_Continue # Lo [29] LYCIAN LETTER A..LYCIAN LETTER X
+102A0..102D0 ; XID_Continue # Lo [49] CARIAN LETTER A..CARIAN LETTER UUU3
+102E0 ; XID_Continue # Mn COPTIC EPACT THOUSANDS MARK
+10300..1031F ; XID_Continue # Lo [32] OLD ITALIC LETTER A..OLD ITALIC LETTER ESS
+1032D..10340 ; XID_Continue # Lo [20] OLD ITALIC LETTER YE..GOTHIC LETTER PAIRTHRA
+10341 ; XID_Continue # Nl GOTHIC LETTER NINETY
+10342..10349 ; XID_Continue # Lo [8] GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL
+1034A ; XID_Continue # Nl GOTHIC LETTER NINE HUNDRED
+10350..10375 ; XID_Continue # Lo [38] OLD PERMIC LETTER AN..OLD PERMIC LETTER IA
+10376..1037A ; XID_Continue # Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII
+10380..1039D ; XID_Continue # Lo [30] UGARITIC LETTER ALPA..UGARITIC LETTER SSU
+103A0..103C3 ; XID_Continue # Lo [36] OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA
+103C8..103CF ; XID_Continue # Lo [8] OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH
+103D1..103D5 ; XID_Continue # Nl [5] OLD PERSIAN NUMBER ONE..OLD PERSIAN NUMBER HUNDRED
+10400..1044F ; XID_Continue # L& [80] DESERET CAPITAL LETTER LONG I..DESERET SMALL LETTER EW
+10450..1049D ; XID_Continue # Lo [78] SHAVIAN LETTER PEEP..OSMANYA LETTER OO
+104A0..104A9 ; XID_Continue # Nd [10] OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE
+104B0..104D3 ; XID_Continue # L& [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA
+104D8..104FB ; XID_Continue # L& [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA
+10500..10527 ; XID_Continue # Lo [40] ELBASAN LETTER A..ELBASAN LETTER KHE
+10530..10563 ; XID_Continue # Lo [52] CAUCASIAN ALBANIAN LETTER ALT..CAUCASIAN ALBANIAN LETTER KIW
+10570..1057A ; XID_Continue # L& [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA
+1057C..1058A ; XID_Continue # L& [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE
+1058C..10592 ; XID_Continue # L& [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE
+10594..10595 ; XID_Continue # L& [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE
+10597..105A1 ; XID_Continue # L& [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA
+105A3..105B1 ; XID_Continue # L& [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE
+105B3..105B9 ; XID_Continue # L& [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE
+105BB..105BC ; XID_Continue # L& [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE
+105C0..105F3 ; XID_Continue # Lo [52] TODHRI LETTER A..TODHRI LETTER OO
+10600..10736 ; XID_Continue # Lo [311] LINEAR A SIGN AB001..LINEAR A SIGN A664
+10740..10755 ; XID_Continue # Lo [22] LINEAR A SIGN A701 A..LINEAR A SIGN A732 JE
+10760..10767 ; XID_Continue # Lo [8] LINEAR A SIGN A800..LINEAR A SIGN A807
+10780..10785 ; XID_Continue # Lm [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK
+10787..107B0 ; XID_Continue # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK
+107B2..107BA ; XID_Continue # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL
+10800..10805 ; XID_Continue # Lo [6] CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA
+10808 ; XID_Continue # Lo CYPRIOT SYLLABLE JO
+1080A..10835 ; XID_Continue # Lo [44] CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO
+10837..10838 ; XID_Continue # Lo [2] CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE
+1083C ; XID_Continue # Lo CYPRIOT SYLLABLE ZA
+1083F..10855 ; XID_Continue # Lo [23] CYPRIOT SYLLABLE ZO..IMPERIAL ARAMAIC LETTER TAW
+10860..10876 ; XID_Continue # Lo [23] PALMYRENE LETTER ALEPH..PALMYRENE LETTER TAW
+10880..1089E ; XID_Continue # Lo [31] NABATAEAN LETTER FINAL ALEPH..NABATAEAN LETTER TAW
+108E0..108F2 ; XID_Continue # Lo [19] HATRAN LETTER ALEPH..HATRAN LETTER QOPH
+108F4..108F5 ; XID_Continue # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW
+10900..10915 ; XID_Continue # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU
+10920..10939 ; XID_Continue # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C
+10980..109B7 ; XID_Continue # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA
+109BE..109BF ; XID_Continue # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN
+10A00 ; XID_Continue # Lo KHAROSHTHI LETTER A
+10A01..10A03 ; XID_Continue # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R
+10A05..10A06 ; XID_Continue # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O
+10A0C..10A0F ; XID_Continue # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA
+10A10..10A13 ; XID_Continue # Lo [4] KHAROSHTHI LETTER KA..KHAROSHTHI LETTER GHA
+10A15..10A17 ; XID_Continue # Lo [3] KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA
+10A19..10A35 ; XID_Continue # Lo [29] KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER VHA
+10A38..10A3A ; XID_Continue # Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW
+10A3F ; XID_Continue # Mn KHAROSHTHI VIRAMA
+10A60..10A7C ; XID_Continue # Lo [29] OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH
+10A80..10A9C ; XID_Continue # Lo [29] OLD NORTH ARABIAN LETTER HEH..OLD NORTH ARABIAN LETTER ZAH
+10AC0..10AC7 ; XID_Continue # Lo [8] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER WAW
+10AC9..10AE4 ; XID_Continue # Lo [28] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER TAW
+10AE5..10AE6 ; XID_Continue # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW
+10B00..10B35 ; XID_Continue # Lo [54] AVESTAN LETTER A..AVESTAN LETTER HE
+10B40..10B55 ; XID_Continue # Lo [22] INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW
+10B60..10B72 ; XID_Continue # Lo [19] INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW
+10B80..10B91 ; XID_Continue # Lo [18] PSALTER PAHLAVI LETTER ALEPH..PSALTER PAHLAVI LETTER TAW
+10C00..10C48 ; XID_Continue # Lo [73] OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH
+10C80..10CB2 ; XID_Continue # L& [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US
+10CC0..10CF2 ; XID_Continue # L& [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US
+10D00..10D23 ; XID_Continue # Lo [36] HANIFI ROHINGYA LETTER A..HANIFI ROHINGYA MARK NA KHONNA
+10D24..10D27 ; XID_Continue # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI
+10D30..10D39 ; XID_Continue # Nd [10] HANIFI ROHINGYA DIGIT ZERO..HANIFI ROHINGYA DIGIT NINE
+10D40..10D49 ; XID_Continue # Nd [10] GARAY DIGIT ZERO..GARAY DIGIT NINE
+10D4A..10D4D ; XID_Continue # Lo [4] GARAY VOWEL SIGN A..GARAY VOWEL SIGN EE
+10D4E ; XID_Continue # Lm GARAY VOWEL LENGTH MARK
+10D4F ; XID_Continue # Lo GARAY SUKUN
+10D50..10D65 ; XID_Continue # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA
+10D69..10D6D ; XID_Continue # Mn [5] GARAY VOWEL SIGN E..GARAY CONSONANT NASALIZATION MARK
+10D6F ; XID_Continue # Lm GARAY REDUPLICATION MARK
+10D70..10D85 ; XID_Continue # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA
+10E80..10EA9 ; XID_Continue # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET
+10EAB..10EAC ; XID_Continue # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK
+10EB0..10EB1 ; XID_Continue # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE
+10EC2..10EC4 ; XID_Continue # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW
+10EFC..10EFF ; XID_Continue # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA
+10F00..10F1C ; XID_Continue # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL
+10F27 ; XID_Continue # Lo OLD SOGDIAN LIGATURE AYIN-DALETH
+10F30..10F45 ; XID_Continue # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN
+10F46..10F50 ; XID_Continue # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW
+10F70..10F81 ; XID_Continue # Lo [18] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER LESH
+10F82..10F85 ; XID_Continue # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW
+10FB0..10FC4 ; XID_Continue # Lo [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW
+10FE0..10FF6 ; XID_Continue # Lo [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH
+11000 ; XID_Continue # Mc BRAHMI SIGN CANDRABINDU
+11001 ; XID_Continue # Mn BRAHMI SIGN ANUSVARA
+11002 ; XID_Continue # Mc BRAHMI SIGN VISARGA
+11003..11037 ; XID_Continue # Lo [53] BRAHMI SIGN JIHVAMULIYA..BRAHMI LETTER OLD TAMIL NNNA
+11038..11046 ; XID_Continue # Mn [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA
+11066..1106F ; XID_Continue # Nd [10] BRAHMI DIGIT ZERO..BRAHMI DIGIT NINE
+11070 ; XID_Continue # Mn BRAHMI SIGN OLD TAMIL VIRAMA
+11071..11072 ; XID_Continue # Lo [2] BRAHMI LETTER OLD TAMIL SHORT E..BRAHMI LETTER OLD TAMIL SHORT O
+11073..11074 ; XID_Continue # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O
+11075 ; XID_Continue # Lo BRAHMI LETTER OLD TAMIL LLA
+1107F..11081 ; XID_Continue # Mn [3] BRAHMI NUMBER JOINER..KAITHI SIGN ANUSVARA
+11082 ; XID_Continue # Mc KAITHI SIGN VISARGA
+11083..110AF ; XID_Continue # Lo [45] KAITHI LETTER A..KAITHI LETTER HA
+110B0..110B2 ; XID_Continue # Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II
+110B3..110B6 ; XID_Continue # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI
+110B7..110B8 ; XID_Continue # Mc [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU
+110B9..110BA ; XID_Continue # Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA
+110C2 ; XID_Continue # Mn KAITHI VOWEL SIGN VOCALIC R
+110D0..110E8 ; XID_Continue # Lo [25] SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE
+110F0..110F9 ; XID_Continue # Nd [10] SORA SOMPENG DIGIT ZERO..SORA SOMPENG DIGIT NINE
+11100..11102 ; XID_Continue # Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA
+11103..11126 ; XID_Continue # Lo [36] CHAKMA LETTER AA..CHAKMA LETTER HAA
+11127..1112B ; XID_Continue # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU
+1112C ; XID_Continue # Mc CHAKMA VOWEL SIGN E
+1112D..11134 ; XID_Continue # Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA
+11136..1113F ; XID_Continue # Nd [10] CHAKMA DIGIT ZERO..CHAKMA DIGIT NINE
+11144 ; XID_Continue # Lo CHAKMA LETTER LHAA
+11145..11146 ; XID_Continue # Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI
+11147 ; XID_Continue # Lo CHAKMA LETTER VAA
+11150..11172 ; XID_Continue # Lo [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA
+11173 ; XID_Continue # Mn MAHAJANI SIGN NUKTA
+11176 ; XID_Continue # Lo MAHAJANI LIGATURE SHRI
+11180..11181 ; XID_Continue # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA
+11182 ; XID_Continue # Mc SHARADA SIGN VISARGA
+11183..111B2 ; XID_Continue # Lo [48] SHARADA LETTER A..SHARADA LETTER HA
+111B3..111B5 ; XID_Continue # Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II
+111B6..111BE ; XID_Continue # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O
+111BF..111C0 ; XID_Continue # Mc [2] SHARADA VOWEL SIGN AU..SHARADA SIGN VIRAMA
+111C1..111C4 ; XID_Continue # Lo [4] SHARADA SIGN AVAGRAHA..SHARADA OM
+111C9..111CC ; XID_Continue # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK
+111CE ; XID_Continue # Mc SHARADA VOWEL SIGN PRISHTHAMATRA E
+111CF ; XID_Continue # Mn SHARADA SIGN INVERTED CANDRABINDU
+111D0..111D9 ; XID_Continue # Nd [10] SHARADA DIGIT ZERO..SHARADA DIGIT NINE
+111DA ; XID_Continue # Lo SHARADA EKAM
+111DC ; XID_Continue # Lo SHARADA HEADSTROKE
+11200..11211 ; XID_Continue # Lo [18] KHOJKI LETTER A..KHOJKI LETTER JJA
+11213..1122B ; XID_Continue # Lo [25] KHOJKI LETTER NYA..KHOJKI LETTER LLA
+1122C..1122E ; XID_Continue # Mc [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II
+1122F..11231 ; XID_Continue # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI
+11232..11233 ; XID_Continue # Mc [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU
+11234 ; XID_Continue # Mn KHOJKI SIGN ANUSVARA
+11235 ; XID_Continue # Mc KHOJKI SIGN VIRAMA
+11236..11237 ; XID_Continue # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA
+1123E ; XID_Continue # Mn KHOJKI SIGN SUKUN
+1123F..11240 ; XID_Continue # Lo [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I
+11241 ; XID_Continue # Mn KHOJKI VOWEL SIGN VOCALIC R
+11280..11286 ; XID_Continue # Lo [7] MULTANI LETTER A..MULTANI LETTER GA
+11288 ; XID_Continue # Lo MULTANI LETTER GHA
+1128A..1128D ; XID_Continue # Lo [4] MULTANI LETTER CA..MULTANI LETTER JJA
+1128F..1129D ; XID_Continue # Lo [15] MULTANI LETTER NYA..MULTANI LETTER BA
+1129F..112A8 ; XID_Continue # Lo [10] MULTANI LETTER BHA..MULTANI LETTER RHA
+112B0..112DE ; XID_Continue # Lo [47] KHUDAWADI LETTER A..KHUDAWADI LETTER HA
+112DF ; XID_Continue # Mn KHUDAWADI SIGN ANUSVARA
+112E0..112E2 ; XID_Continue # Mc [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II
+112E3..112EA ; XID_Continue # Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA
+112F0..112F9 ; XID_Continue # Nd [10] KHUDAWADI DIGIT ZERO..KHUDAWADI DIGIT NINE
+11300..11301 ; XID_Continue # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU
+11302..11303 ; XID_Continue # Mc [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA
+11305..1130C ; XID_Continue # Lo [8] GRANTHA LETTER A..GRANTHA LETTER VOCALIC L
+1130F..11310 ; XID_Continue # Lo [2] GRANTHA LETTER EE..GRANTHA LETTER AI
+11313..11328 ; XID_Continue # Lo [22] GRANTHA LETTER OO..GRANTHA LETTER NA
+1132A..11330 ; XID_Continue # Lo [7] GRANTHA LETTER PA..GRANTHA LETTER RA
+11332..11333 ; XID_Continue # Lo [2] GRANTHA LETTER LA..GRANTHA LETTER LLA
+11335..11339 ; XID_Continue # Lo [5] GRANTHA LETTER VA..GRANTHA LETTER HA
+1133B..1133C ; XID_Continue # Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA
+1133D ; XID_Continue # Lo GRANTHA SIGN AVAGRAHA
+1133E..1133F ; XID_Continue # Mc [2] GRANTHA VOWEL SIGN AA..GRANTHA VOWEL SIGN I
+11340 ; XID_Continue # Mn GRANTHA VOWEL SIGN II
+11341..11344 ; XID_Continue # Mc [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR
+11347..11348 ; XID_Continue # Mc [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI
+1134B..1134D ; XID_Continue # Mc [3] GRANTHA VOWEL SIGN OO..GRANTHA SIGN VIRAMA
+11350 ; XID_Continue # Lo GRANTHA OM
+11357 ; XID_Continue # Mc GRANTHA AU LENGTH MARK
+1135D..11361 ; XID_Continue # Lo [5] GRANTHA SIGN PLUTA..GRANTHA LETTER VOCALIC LL
+11362..11363 ; XID_Continue # Mc [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL
+11366..1136C ; XID_Continue # Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX
+11370..11374 ; XID_Continue # Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA
+11380..11389 ; XID_Continue # Lo [10] TULU-TIGALARI LETTER A..TULU-TIGALARI LETTER VOCALIC LL
+1138B ; XID_Continue # Lo TULU-TIGALARI LETTER EE
+1138E ; XID_Continue # Lo TULU-TIGALARI LETTER AI
+11390..113B5 ; XID_Continue # Lo [38] TULU-TIGALARI LETTER OO..TULU-TIGALARI LETTER LLLA
+113B7 ; XID_Continue # Lo TULU-TIGALARI SIGN AVAGRAHA
+113B8..113BA ; XID_Continue # Mc [3] TULU-TIGALARI VOWEL SIGN AA..TULU-TIGALARI VOWEL SIGN II
+113BB..113C0 ; XID_Continue # Mn [6] TULU-TIGALARI VOWEL SIGN U..TULU-TIGALARI VOWEL SIGN VOCALIC LL
+113C2 ; XID_Continue # Mc TULU-TIGALARI VOWEL SIGN EE
+113C5 ; XID_Continue # Mc TULU-TIGALARI VOWEL SIGN AI
+113C7..113CA ; XID_Continue # Mc [4] TULU-TIGALARI VOWEL SIGN OO..TULU-TIGALARI SIGN CANDRA ANUNASIKA
+113CC..113CD ; XID_Continue # Mc [2] TULU-TIGALARI SIGN ANUSVARA..TULU-TIGALARI SIGN VISARGA
+113CE ; XID_Continue # Mn TULU-TIGALARI SIGN VIRAMA
+113CF ; XID_Continue # Mc TULU-TIGALARI SIGN LOOPED VIRAMA
+113D0 ; XID_Continue # Mn TULU-TIGALARI CONJOINER
+113D1 ; XID_Continue # Lo TULU-TIGALARI REPHA
+113D2 ; XID_Continue # Mn TULU-TIGALARI GEMINATION MARK
+113D3 ; XID_Continue # Lo TULU-TIGALARI SIGN PLUTA
+113E1..113E2 ; XID_Continue # Mn [2] TULU-TIGALARI VEDIC TONE SVARITA..TULU-TIGALARI VEDIC TONE ANUDATTA
+11400..11434 ; XID_Continue # Lo [53] NEWA LETTER A..NEWA LETTER HA
+11435..11437 ; XID_Continue # Mc [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II
+11438..1143F ; XID_Continue # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI
+11440..11441 ; XID_Continue # Mc [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU
+11442..11444 ; XID_Continue # Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA
+11445 ; XID_Continue # Mc NEWA SIGN VISARGA
+11446 ; XID_Continue # Mn NEWA SIGN NUKTA
+11447..1144A ; XID_Continue # Lo [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI
+11450..11459 ; XID_Continue # Nd [10] NEWA DIGIT ZERO..NEWA DIGIT NINE
+1145E ; XID_Continue # Mn NEWA SANDHI MARK
+1145F..11461 ; XID_Continue # Lo [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA
+11480..114AF ; XID_Continue # Lo [48] TIRHUTA ANJI..TIRHUTA LETTER HA
+114B0..114B2 ; XID_Continue # Mc [3] TIRHUTA VOWEL SIGN AA..TIRHUTA VOWEL SIGN II
+114B3..114B8 ; XID_Continue # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL
+114B9 ; XID_Continue # Mc TIRHUTA VOWEL SIGN E
+114BA ; XID_Continue # Mn TIRHUTA VOWEL SIGN SHORT E
+114BB..114BE ; XID_Continue # Mc [4] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN AU
+114BF..114C0 ; XID_Continue # Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA
+114C1 ; XID_Continue # Mc TIRHUTA SIGN VISARGA
+114C2..114C3 ; XID_Continue # Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA
+114C4..114C5 ; XID_Continue # Lo [2] TIRHUTA SIGN AVAGRAHA..TIRHUTA GVANG
+114C7 ; XID_Continue # Lo TIRHUTA OM
+114D0..114D9 ; XID_Continue # Nd [10] TIRHUTA DIGIT ZERO..TIRHUTA DIGIT NINE
+11580..115AE ; XID_Continue # Lo [47] SIDDHAM LETTER A..SIDDHAM LETTER HA
+115AF..115B1 ; XID_Continue # Mc [3] SIDDHAM VOWEL SIGN AA..SIDDHAM VOWEL SIGN II
+115B2..115B5 ; XID_Continue # Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR
+115B8..115BB ; XID_Continue # Mc [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU
+115BC..115BD ; XID_Continue # Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA
+115BE ; XID_Continue # Mc SIDDHAM SIGN VISARGA
+115BF..115C0 ; XID_Continue # Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA
+115D8..115DB ; XID_Continue # Lo [4] SIDDHAM LETTER THREE-CIRCLE ALTERNATE I..SIDDHAM LETTER ALTERNATE U
+115DC..115DD ; XID_Continue # Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU
+11600..1162F ; XID_Continue # Lo [48] MODI LETTER A..MODI LETTER LLA
+11630..11632 ; XID_Continue # Mc [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II
+11633..1163A ; XID_Continue # Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI
+1163B..1163C ; XID_Continue # Mc [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU
+1163D ; XID_Continue # Mn MODI SIGN ANUSVARA
+1163E ; XID_Continue # Mc MODI SIGN VISARGA
+1163F..11640 ; XID_Continue # Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA
+11644 ; XID_Continue # Lo MODI SIGN HUVA
+11650..11659 ; XID_Continue # Nd [10] MODI DIGIT ZERO..MODI DIGIT NINE
+11680..116AA ; XID_Continue # Lo [43] TAKRI LETTER A..TAKRI LETTER RRA
+116AB ; XID_Continue # Mn TAKRI SIGN ANUSVARA
+116AC ; XID_Continue # Mc TAKRI SIGN VISARGA
+116AD ; XID_Continue # Mn TAKRI VOWEL SIGN AA
+116AE..116AF ; XID_Continue # Mc [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II
+116B0..116B5 ; XID_Continue # Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU
+116B6 ; XID_Continue # Mc TAKRI SIGN VIRAMA
+116B7 ; XID_Continue # Mn TAKRI SIGN NUKTA
+116B8 ; XID_Continue # Lo TAKRI LETTER ARCHAIC KHA
+116C0..116C9 ; XID_Continue # Nd [10] TAKRI DIGIT ZERO..TAKRI DIGIT NINE
+116D0..116E3 ; XID_Continue # Nd [20] MYANMAR PAO DIGIT ZERO..MYANMAR EASTERN PWO KAREN DIGIT NINE
+11700..1171A ; XID_Continue # Lo [27] AHOM LETTER KA..AHOM LETTER ALTERNATE BA
+1171D ; XID_Continue # Mn AHOM CONSONANT SIGN MEDIAL LA
+1171E ; XID_Continue # Mc AHOM CONSONANT SIGN MEDIAL RA
+1171F ; XID_Continue # Mn AHOM CONSONANT SIGN MEDIAL LIGATING RA
+11720..11721 ; XID_Continue # Mc [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA
+11722..11725 ; XID_Continue # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU
+11726 ; XID_Continue # Mc AHOM VOWEL SIGN E
+11727..1172B ; XID_Continue # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER
+11730..11739 ; XID_Continue # Nd [10] AHOM DIGIT ZERO..AHOM DIGIT NINE
+11740..11746 ; XID_Continue # Lo [7] AHOM LETTER CA..AHOM LETTER LLA
+11800..1182B ; XID_Continue # Lo [44] DOGRA LETTER A..DOGRA LETTER RRA
+1182C..1182E ; XID_Continue # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II
+1182F..11837 ; XID_Continue # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA
+11838 ; XID_Continue # Mc DOGRA SIGN VISARGA
+11839..1183A ; XID_Continue # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA
+118A0..118DF ; XID_Continue # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO
+118E0..118E9 ; XID_Continue # Nd [10] WARANG CITI DIGIT ZERO..WARANG CITI DIGIT NINE
+118FF..11906 ; XID_Continue # Lo [8] WARANG CITI OM..DIVES AKURU LETTER E
+11909 ; XID_Continue # Lo DIVES AKURU LETTER O
+1190C..11913 ; XID_Continue # Lo [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA
+11915..11916 ; XID_Continue # Lo [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA
+11918..1192F ; XID_Continue # Lo [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA
+11930..11935 ; XID_Continue # Mc [6] DIVES AKURU VOWEL SIGN AA..DIVES AKURU VOWEL SIGN E
+11937..11938 ; XID_Continue # Mc [2] DIVES AKURU VOWEL SIGN AI..DIVES AKURU VOWEL SIGN O
+1193B..1193C ; XID_Continue # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU
+1193D ; XID_Continue # Mc DIVES AKURU SIGN HALANTA
+1193E ; XID_Continue # Mn DIVES AKURU VIRAMA
+1193F ; XID_Continue # Lo DIVES AKURU PREFIXED NASAL SIGN
+11940 ; XID_Continue # Mc DIVES AKURU MEDIAL YA
+11941 ; XID_Continue # Lo DIVES AKURU INITIAL RA
+11942 ; XID_Continue # Mc DIVES AKURU MEDIAL RA
+11943 ; XID_Continue # Mn DIVES AKURU SIGN NUKTA
+11950..11959 ; XID_Continue # Nd [10] DIVES AKURU DIGIT ZERO..DIVES AKURU DIGIT NINE
+119A0..119A7 ; XID_Continue # Lo [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR
+119AA..119D0 ; XID_Continue # Lo [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA
+119D1..119D3 ; XID_Continue # Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II
+119D4..119D7 ; XID_Continue # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR
+119DA..119DB ; XID_Continue # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI
+119DC..119DF ; XID_Continue # Mc [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA
+119E0 ; XID_Continue # Mn NANDINAGARI SIGN VIRAMA
+119E1 ; XID_Continue # Lo NANDINAGARI SIGN AVAGRAHA
+119E3 ; XID_Continue # Lo NANDINAGARI HEADSTROKE
+119E4 ; XID_Continue # Mc NANDINAGARI VOWEL SIGN PRISHTHAMATRA E
+11A00 ; XID_Continue # Lo ZANABAZAR SQUARE LETTER A
+11A01..11A0A ; XID_Continue # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK
+11A0B..11A32 ; XID_Continue # Lo [40] ZANABAZAR SQUARE LETTER KA..ZANABAZAR SQUARE LETTER KSSA
+11A33..11A38 ; XID_Continue # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA
+11A39 ; XID_Continue # Mc ZANABAZAR SQUARE SIGN VISARGA
+11A3A ; XID_Continue # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA
+11A3B..11A3E ; XID_Continue # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA
+11A47 ; XID_Continue # Mn ZANABAZAR SQUARE SUBJOINER
+11A50 ; XID_Continue # Lo SOYOMBO LETTER A
+11A51..11A56 ; XID_Continue # Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE
+11A57..11A58 ; XID_Continue # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU
+11A59..11A5B ; XID_Continue # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK
+11A5C..11A89 ; XID_Continue # Lo [46] SOYOMBO LETTER KA..SOYOMBO CLUSTER-INITIAL LETTER SA
+11A8A..11A96 ; XID_Continue # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA
+11A97 ; XID_Continue # Mc SOYOMBO SIGN VISARGA
+11A98..11A99 ; XID_Continue # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER
+11A9D ; XID_Continue # Lo SOYOMBO MARK PLUTA
+11AB0..11AF8 ; XID_Continue # Lo [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL
+11BC0..11BE0 ; XID_Continue # Lo [33] SUNUWAR LETTER DEVI..SUNUWAR LETTER KLOKO
+11BF0..11BF9 ; XID_Continue # Nd [10] SUNUWAR DIGIT ZERO..SUNUWAR DIGIT NINE
+11C00..11C08 ; XID_Continue # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L
+11C0A..11C2E ; XID_Continue # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA
+11C2F ; XID_Continue # Mc BHAIKSUKI VOWEL SIGN AA
+11C30..11C36 ; XID_Continue # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L
+11C38..11C3D ; XID_Continue # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA
+11C3E ; XID_Continue # Mc BHAIKSUKI SIGN VISARGA
+11C3F ; XID_Continue # Mn BHAIKSUKI SIGN VIRAMA
+11C40 ; XID_Continue # Lo BHAIKSUKI SIGN AVAGRAHA
+11C50..11C59 ; XID_Continue # Nd [10] BHAIKSUKI DIGIT ZERO..BHAIKSUKI DIGIT NINE
+11C72..11C8F ; XID_Continue # Lo [30] MARCHEN LETTER KA..MARCHEN LETTER A
+11C92..11CA7 ; XID_Continue # Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA
+11CA9 ; XID_Continue # Mc MARCHEN SUBJOINED LETTER YA
+11CAA..11CB0 ; XID_Continue # Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA
+11CB1 ; XID_Continue # Mc MARCHEN VOWEL SIGN I
+11CB2..11CB3 ; XID_Continue # Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E
+11CB4 ; XID_Continue # Mc MARCHEN VOWEL SIGN O
+11CB5..11CB6 ; XID_Continue # Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU
+11D00..11D06 ; XID_Continue # Lo [7] MASARAM GONDI LETTER A..MASARAM GONDI LETTER E
+11D08..11D09 ; XID_Continue # Lo [2] MASARAM GONDI LETTER AI..MASARAM GONDI LETTER O
+11D0B..11D30 ; XID_Continue # Lo [38] MASARAM GONDI LETTER AU..MASARAM GONDI LETTER TRA
+11D31..11D36 ; XID_Continue # Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R
+11D3A ; XID_Continue # Mn MASARAM GONDI VOWEL SIGN E
+11D3C..11D3D ; XID_Continue # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O
+11D3F..11D45 ; XID_Continue # Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA
+11D46 ; XID_Continue # Lo MASARAM GONDI REPHA
+11D47 ; XID_Continue # Mn MASARAM GONDI RA-KARA
+11D50..11D59 ; XID_Continue # Nd [10] MASARAM GONDI DIGIT ZERO..MASARAM GONDI DIGIT NINE
+11D60..11D65 ; XID_Continue # Lo [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU
+11D67..11D68 ; XID_Continue # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI
+11D6A..11D89 ; XID_Continue # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA
+11D8A..11D8E ; XID_Continue # Mc [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU
+11D90..11D91 ; XID_Continue # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI
+11D93..11D94 ; XID_Continue # Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU
+11D95 ; XID_Continue # Mn GUNJALA GONDI SIGN ANUSVARA
+11D96 ; XID_Continue # Mc GUNJALA GONDI SIGN VISARGA
+11D97 ; XID_Continue # Mn GUNJALA GONDI VIRAMA
+11D98 ; XID_Continue # Lo GUNJALA GONDI OM
+11DA0..11DA9 ; XID_Continue # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE
+11EE0..11EF2 ; XID_Continue # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA
+11EF3..11EF4 ; XID_Continue # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U
+11EF5..11EF6 ; XID_Continue # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O
+11F00..11F01 ; XID_Continue # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA
+11F02 ; XID_Continue # Lo KAWI SIGN REPHA
+11F03 ; XID_Continue # Mc KAWI SIGN VISARGA
+11F04..11F10 ; XID_Continue # Lo [13] KAWI LETTER A..KAWI LETTER O
+11F12..11F33 ; XID_Continue # Lo [34] KAWI LETTER KA..KAWI LETTER JNYA
+11F34..11F35 ; XID_Continue # Mc [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA
+11F36..11F3A ; XID_Continue # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R
+11F3E..11F3F ; XID_Continue # Mc [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI
+11F40 ; XID_Continue # Mn KAWI VOWEL SIGN EU
+11F41 ; XID_Continue # Mc KAWI SIGN KILLER
+11F42 ; XID_Continue # Mn KAWI CONJOINER
+11F50..11F59 ; XID_Continue # Nd [10] KAWI DIGIT ZERO..KAWI DIGIT NINE
+11F5A ; XID_Continue # Mn KAWI SIGN NUKTA
+11FB0 ; XID_Continue # Lo LISU LETTER YHA
+12000..12399 ; XID_Continue # Lo [922] CUNEIFORM SIGN A..CUNEIFORM SIGN U U
+12400..1246E ; XID_Continue # Nl [111] CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM
+12480..12543 ; XID_Continue # Lo [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU
+12F90..12FF0 ; XID_Continue # Lo [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114
+13000..1342F ; XID_Continue # Lo [1072] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH V011D
+13440 ; XID_Continue # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY
+13441..13446 ; XID_Continue # Lo [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN
+13447..13455 ; XID_Continue # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED
+13460..143FA ; XID_Continue # Lo [3995] EGYPTIAN HIEROGLYPH-13460..EGYPTIAN HIEROGLYPH-143FA
+14400..14646 ; XID_Continue # Lo [583] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A530
+16100..1611D ; XID_Continue # Lo [30] GURUNG KHEMA LETTER A..GURUNG KHEMA LETTER SA
+1611E..16129 ; XID_Continue # Mn [12] GURUNG KHEMA VOWEL SIGN AA..GURUNG KHEMA VOWEL LENGTH MARK
+1612A..1612C ; XID_Continue # Mc [3] GURUNG KHEMA CONSONANT SIGN MEDIAL YA..GURUNG KHEMA CONSONANT SIGN MEDIAL HA
+1612D..1612F ; XID_Continue # Mn [3] GURUNG KHEMA SIGN ANUSVARA..GURUNG KHEMA SIGN THOLHOMA
+16130..16139 ; XID_Continue # Nd [10] GURUNG KHEMA DIGIT ZERO..GURUNG KHEMA DIGIT NINE
+16800..16A38 ; XID_Continue # Lo [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ
+16A40..16A5E ; XID_Continue # Lo [31] MRO LETTER TA..MRO LETTER TEK
+16A60..16A69 ; XID_Continue # Nd [10] MRO DIGIT ZERO..MRO DIGIT NINE
+16A70..16ABE ; XID_Continue # Lo [79] TANGSA LETTER OZ..TANGSA LETTER ZA
+16AC0..16AC9 ; XID_Continue # Nd [10] TANGSA DIGIT ZERO..TANGSA DIGIT NINE
+16AD0..16AED ; XID_Continue # Lo [30] BASSA VAH LETTER ENNI..BASSA VAH LETTER I
+16AF0..16AF4 ; XID_Continue # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE
+16B00..16B2F ; XID_Continue # Lo [48] PAHAWH HMONG VOWEL KEEB..PAHAWH HMONG CONSONANT CAU
+16B30..16B36 ; XID_Continue # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
+16B40..16B43 ; XID_Continue # Lm [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM
+16B50..16B59 ; XID_Continue # Nd [10] PAHAWH HMONG DIGIT ZERO..PAHAWH HMONG DIGIT NINE
+16B63..16B77 ; XID_Continue # Lo [21] PAHAWH HMONG SIGN VOS LUB..PAHAWH HMONG SIGN CIM NRES TOS
+16B7D..16B8F ; XID_Continue # Lo [19] PAHAWH HMONG CLAN SIGN TSHEEJ..PAHAWH HMONG CLAN SIGN VWJ
+16D40..16D42 ; XID_Continue # Lm [3] KIRAT RAI SIGN ANUSVARA..KIRAT RAI SIGN VISARGA
+16D43..16D6A ; XID_Continue # Lo [40] KIRAT RAI LETTER A..KIRAT RAI VOWEL SIGN AU
+16D6B..16D6C ; XID_Continue # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT
+16D70..16D79 ; XID_Continue # Nd [10] KIRAT RAI DIGIT ZERO..KIRAT RAI DIGIT NINE
+16E40..16E7F ; XID_Continue # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y
+16F00..16F4A ; XID_Continue # Lo [75] MIAO LETTER PA..MIAO LETTER RTE
+16F4F ; XID_Continue # Mn MIAO SIGN CONSONANT MODIFIER BAR
+16F50 ; XID_Continue # Lo MIAO LETTER NASALIZATION
+16F51..16F87 ; XID_Continue # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI
+16F8F..16F92 ; XID_Continue # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
+16F93..16F9F ; XID_Continue # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8
+16FE0..16FE1 ; XID_Continue # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK
+16FE3 ; XID_Continue # Lm OLD CHINESE ITERATION MARK
+16FE4 ; XID_Continue # Mn KHITAN SMALL SCRIPT FILLER
+16FF0..16FF1 ; XID_Continue # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY
+17000..187F7 ; XID_Continue # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7
+18800..18CD5 ; XID_Continue # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5
+18CFF..18D08 ; XID_Continue # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08
+1AFF0..1AFF3 ; XID_Continue # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5
+1AFF5..1AFFB ; XID_Continue # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5
+1AFFD..1AFFE ; XID_Continue # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8
+1B000..1B122 ; XID_Continue # Lo [291] KATAKANA LETTER ARCHAIC E..KATAKANA LETTER ARCHAIC WU
+1B132 ; XID_Continue # Lo HIRAGANA LETTER SMALL KO
+1B150..1B152 ; XID_Continue # Lo [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO
+1B155 ; XID_Continue # Lo KATAKANA LETTER SMALL KO
+1B164..1B167 ; XID_Continue # Lo [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N
+1B170..1B2FB ; XID_Continue # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB
+1BC00..1BC6A ; XID_Continue # Lo [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M
+1BC70..1BC7C ; XID_Continue # Lo [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK
+1BC80..1BC88 ; XID_Continue # Lo [9] DUPLOYAN AFFIX HIGH ACUTE..DUPLOYAN AFFIX HIGH VERTICAL
+1BC90..1BC99 ; XID_Continue # Lo [10] DUPLOYAN AFFIX LOW ACUTE..DUPLOYAN AFFIX LOW ARROW
+1BC9D..1BC9E ; XID_Continue # Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK
+1CCF0..1CCF9 ; XID_Continue # Nd [10] OUTLINED DIGIT ZERO..OUTLINED DIGIT NINE
+1CF00..1CF2D ; XID_Continue # Mn [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT
+1CF30..1CF46 ; XID_Continue # Mn [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG
+1D165..1D166 ; XID_Continue # Mc [2] MUSICAL SYMBOL COMBINING STEM..MUSICAL SYMBOL COMBINING SPRECHGESANG STEM
+1D167..1D169 ; XID_Continue # Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3
+1D16D..1D172 ; XID_Continue # Mc [6] MUSICAL SYMBOL COMBINING AUGMENTATION DOT..MUSICAL SYMBOL COMBINING FLAG-5
+1D17B..1D182 ; XID_Continue # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE
+1D185..1D18B ; XID_Continue # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE
+1D1AA..1D1AD ; XID_Continue # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO
+1D242..1D244 ; XID_Continue # Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME
+1D400..1D454 ; XID_Continue # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G
+1D456..1D49C ; XID_Continue # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A
+1D49E..1D49F ; XID_Continue # L& [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D
+1D4A2 ; XID_Continue # L& MATHEMATICAL SCRIPT CAPITAL G
+1D4A5..1D4A6 ; XID_Continue # L& [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K
+1D4A9..1D4AC ; XID_Continue # L& [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q
+1D4AE..1D4B9 ; XID_Continue # L& [12] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D
+1D4BB ; XID_Continue # L& MATHEMATICAL SCRIPT SMALL F
+1D4BD..1D4C3 ; XID_Continue # L& [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N
+1D4C5..1D505 ; XID_Continue # L& [65] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B
+1D507..1D50A ; XID_Continue # L& [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G
+1D50D..1D514 ; XID_Continue # L& [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q
+1D516..1D51C ; XID_Continue # L& [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y
+1D51E..1D539 ; XID_Continue # L& [28] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B
+1D53B..1D53E ; XID_Continue # L& [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G
+1D540..1D544 ; XID_Continue # L& [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M
+1D546 ; XID_Continue # L& MATHEMATICAL DOUBLE-STRUCK CAPITAL O
+1D54A..1D550 ; XID_Continue # L& [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y
+1D552..1D6A5 ; XID_Continue # L& [340] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J
+1D6A8..1D6C0 ; XID_Continue # L& [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA
+1D6C2..1D6DA ; XID_Continue # L& [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA
+1D6DC..1D6FA ; XID_Continue # L& [31] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL ITALIC CAPITAL OMEGA
+1D6FC..1D714 ; XID_Continue # L& [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA
+1D716..1D734 ; XID_Continue # L& [31] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA
+1D736..1D74E ; XID_Continue # L& [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA
+1D750..1D76E ; XID_Continue # L& [31] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA
+1D770..1D788 ; XID_Continue # L& [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA
+1D78A..1D7A8 ; XID_Continue # L& [31] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA
+1D7AA..1D7C2 ; XID_Continue # L& [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA
+1D7C4..1D7CB ; XID_Continue # L& [8] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD SMALL DIGAMMA
+1D7CE..1D7FF ; XID_Continue # Nd [50] MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE
+1DA00..1DA36 ; XID_Continue # Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN
+1DA3B..1DA6C ; XID_Continue # Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT
+1DA75 ; XID_Continue # Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS
+1DA84 ; XID_Continue # Mn SIGNWRITING LOCATION HEAD NECK
+1DA9B..1DA9F ; XID_Continue # Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6
+1DAA1..1DAAF ; XID_Continue # Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16
+1DF00..1DF09 ; XID_Continue # L& [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK
+1DF0A ; XID_Continue # Lo LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK
+1DF0B..1DF1E ; XID_Continue # L& [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL
+1DF25..1DF2A ; XID_Continue # L& [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK
+1E000..1E006 ; XID_Continue # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE
+1E008..1E018 ; XID_Continue # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU
+1E01B..1E021 ; XID_Continue # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI
+1E023..1E024 ; XID_Continue # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS
+1E026..1E02A ; XID_Continue # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA
+1E030..1E06D ; XID_Continue # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE
+1E08F ; XID_Continue # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+1E100..1E12C ; XID_Continue # Lo [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W
+1E130..1E136 ; XID_Continue # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D
+1E137..1E13D ; XID_Continue # Lm [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER
+1E140..1E149 ; XID_Continue # Nd [10] NYIAKENG PUACHUE HMONG DIGIT ZERO..NYIAKENG PUACHUE HMONG DIGIT NINE
+1E14E ; XID_Continue # Lo NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ
+1E290..1E2AD ; XID_Continue # Lo [30] TOTO LETTER PA..TOTO LETTER A
+1E2AE ; XID_Continue # Mn TOTO SIGN RISING TONE
+1E2C0..1E2EB ; XID_Continue # Lo [44] WANCHO LETTER AA..WANCHO LETTER YIH
+1E2EC..1E2EF ; XID_Continue # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI
+1E2F0..1E2F9 ; XID_Continue # Nd [10] WANCHO DIGIT ZERO..WANCHO DIGIT NINE
+1E4D0..1E4EA ; XID_Continue # Lo [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL
+1E4EB ; XID_Continue # Lm NAG MUNDARI SIGN OJOD
+1E4EC..1E4EF ; XID_Continue # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH
+1E4F0..1E4F9 ; XID_Continue # Nd [10] NAG MUNDARI DIGIT ZERO..NAG MUNDARI DIGIT NINE
+1E5D0..1E5ED ; XID_Continue # Lo [30] OL ONAL LETTER O..OL ONAL LETTER EG
+1E5EE..1E5EF ; XID_Continue # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR
+1E5F0 ; XID_Continue # Lo OL ONAL SIGN HODDOND
+1E5F1..1E5FA ; XID_Continue # Nd [10] OL ONAL DIGIT ZERO..OL ONAL DIGIT NINE
+1E7E0..1E7E6 ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO
+1E7E8..1E7EB ; XID_Continue # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE
+1E7ED..1E7EE ; XID_Continue # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE
+1E7F0..1E7FE ; XID_Continue # Lo [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE
+1E800..1E8C4 ; XID_Continue # Lo [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON
+1E8D0..1E8D6 ; XID_Continue # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS
+1E900..1E943 ; XID_Continue # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA
+1E944..1E94A ; XID_Continue # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA
+1E94B ; XID_Continue # Lm ADLAM NASALIZATION MARK
+1E950..1E959 ; XID_Continue # Nd [10] ADLAM DIGIT ZERO..ADLAM DIGIT NINE
+1EE00..1EE03 ; XID_Continue # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL
+1EE05..1EE1F ; XID_Continue # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF
+1EE21..1EE22 ; XID_Continue # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM
+1EE24 ; XID_Continue # Lo ARABIC MATHEMATICAL INITIAL HEH
+1EE27 ; XID_Continue # Lo ARABIC MATHEMATICAL INITIAL HAH
+1EE29..1EE32 ; XID_Continue # Lo [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF
+1EE34..1EE37 ; XID_Continue # Lo [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH
+1EE39 ; XID_Continue # Lo ARABIC MATHEMATICAL INITIAL DAD
+1EE3B ; XID_Continue # Lo ARABIC MATHEMATICAL INITIAL GHAIN
+1EE42 ; XID_Continue # Lo ARABIC MATHEMATICAL TAILED JEEM
+1EE47 ; XID_Continue # Lo ARABIC MATHEMATICAL TAILED HAH
+1EE49 ; XID_Continue # Lo ARABIC MATHEMATICAL TAILED YEH
+1EE4B ; XID_Continue # Lo ARABIC MATHEMATICAL TAILED LAM
+1EE4D..1EE4F ; XID_Continue # Lo [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN
+1EE51..1EE52 ; XID_Continue # Lo [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF
+1EE54 ; XID_Continue # Lo ARABIC MATHEMATICAL TAILED SHEEN
+1EE57 ; XID_Continue # Lo ARABIC MATHEMATICAL TAILED KHAH
+1EE59 ; XID_Continue # Lo ARABIC MATHEMATICAL TAILED DAD
+1EE5B ; XID_Continue # Lo ARABIC MATHEMATICAL TAILED GHAIN
+1EE5D ; XID_Continue # Lo ARABIC MATHEMATICAL TAILED DOTLESS NOON
+1EE5F ; XID_Continue # Lo ARABIC MATHEMATICAL TAILED DOTLESS QAF
+1EE61..1EE62 ; XID_Continue # Lo [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM
+1EE64 ; XID_Continue # Lo ARABIC MATHEMATICAL STRETCHED HEH
+1EE67..1EE6A ; XID_Continue # Lo [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF
+1EE6C..1EE72 ; XID_Continue # Lo [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF
+1EE74..1EE77 ; XID_Continue # Lo [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH
+1EE79..1EE7C ; XID_Continue # Lo [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH
+1EE7E ; XID_Continue # Lo ARABIC MATHEMATICAL STRETCHED DOTLESS FEH
+1EE80..1EE89 ; XID_Continue # Lo [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH
+1EE8B..1EE9B ; XID_Continue # Lo [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN
+1EEA1..1EEA3 ; XID_Continue # Lo [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL
+1EEA5..1EEA9 ; XID_Continue # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH
+1EEAB..1EEBB ; XID_Continue # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN
+1FBF0..1FBF9 ; XID_Continue # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE
+20000..2A6DF ; XID_Continue # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF
+2A700..2B739 ; XID_Continue # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739
+2B740..2B81D ; XID_Continue # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D
+2B820..2CEA1 ; XID_Continue # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1
+2CEB0..2EBE0 ; XID_Continue # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0
+2EBF0..2EE5D ; XID_Continue # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D
+2F800..2FA1D ; XID_Continue # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D
+30000..3134A ; XID_Continue # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A
+31350..323AF ; XID_Continue # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF
+E0100..E01EF ; XID_Continue # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
+
+# Total code points: 144522
+
+# ================================================
+
+# Derived Property: Default_Ignorable_Code_Point
+# Generated from
+# Other_Default_Ignorable_Code_Point
+# + Cf (Format characters)
+# + Variation_Selector
+# - White_Space
+# - FFF9..FFFB (Interlinear annotation format characters)
+# - 13430..13440 (Egyptian hieroglyph format characters)
+# - Prepended_Concatenation_Mark (Exceptional format characters that should be visible)
+#
+# There are currently no stability guarantees for DICP. However, the
+# values of DICP interact with the derivation of XID_Continue
+# and NFKC_CF, for which there are stability guarantees.
+# Maintainers of this property should note that in the
+# unlikely case that the DICP value changes for an existing character
+# which is also XID_Continue=Yes, then exceptions must be put
+# in place to ensure that the NFKC_CF mapping value for that
+# existing character does not change.
+
+00AD ; Default_Ignorable_Code_Point # Cf SOFT HYPHEN
+034F ; Default_Ignorable_Code_Point # Mn COMBINING GRAPHEME JOINER
+061C ; Default_Ignorable_Code_Point # Cf ARABIC LETTER MARK
+115F..1160 ; Default_Ignorable_Code_Point # Lo [2] HANGUL CHOSEONG FILLER..HANGUL JUNGSEONG FILLER
+17B4..17B5 ; Default_Ignorable_Code_Point # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
+180B..180D ; Default_Ignorable_Code_Point # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
+180E ; Default_Ignorable_Code_Point # Cf MONGOLIAN VOWEL SEPARATOR
+180F ; Default_Ignorable_Code_Point # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR
+200B..200F ; Default_Ignorable_Code_Point # Cf [5] ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK
+202A..202E ; Default_Ignorable_Code_Point # Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
+2060..2064 ; Default_Ignorable_Code_Point # Cf [5] WORD JOINER..INVISIBLE PLUS
+2065 ; Default_Ignorable_Code_Point # Cn <reserved-2065>
+2066..206F ; Default_Ignorable_Code_Point # Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES
+3164 ; Default_Ignorable_Code_Point # Lo HANGUL FILLER
+FE00..FE0F ; Default_Ignorable_Code_Point # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
+FEFF ; Default_Ignorable_Code_Point # Cf ZERO WIDTH NO-BREAK SPACE
+FFA0 ; Default_Ignorable_Code_Point # Lo HALFWIDTH HANGUL FILLER
+FFF0..FFF8 ; Default_Ignorable_Code_Point # Cn [9] <reserved-FFF0>..<reserved-FFF8>
+1BCA0..1BCA3 ; Default_Ignorable_Code_Point # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP
+1D173..1D17A ; Default_Ignorable_Code_Point # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE
+E0000 ; Default_Ignorable_Code_Point # Cn <reserved-E0000>
+E0001 ; Default_Ignorable_Code_Point # Cf LANGUAGE TAG
+E0002..E001F ; Default_Ignorable_Code_Point # Cn [30] <reserved-E0002>..<reserved-E001F>
+E0020..E007F ; Default_Ignorable_Code_Point # Cf [96] TAG SPACE..CANCEL TAG
+E0080..E00FF ; Default_Ignorable_Code_Point # Cn [128] <reserved-E0080>..<reserved-E00FF>
+E0100..E01EF ; Default_Ignorable_Code_Point # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
+E01F0..E0FFF ; Default_Ignorable_Code_Point # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
+
+# Total code points: 4174
+
+# ================================================
+
+# Derived Property: Grapheme_Extend
+# Generated from: Me + Mn + Other_Grapheme_Extend
+# Note: depending on an application's interpretation of Co (private use),
+# they may be either in Grapheme_Base, or in Grapheme_Extend, or in neither.
+
+0300..036F ; Grapheme_Extend # Mn [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X
+0483..0487 ; Grapheme_Extend # Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE
+0488..0489 ; Grapheme_Extend # Me [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN
+0591..05BD ; Grapheme_Extend # Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG
+05BF ; Grapheme_Extend # Mn HEBREW POINT RAFE
+05C1..05C2 ; Grapheme_Extend # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT
+05C4..05C5 ; Grapheme_Extend # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT
+05C7 ; Grapheme_Extend # Mn HEBREW POINT QAMATS QATAN
+0610..061A ; Grapheme_Extend # Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA
+064B..065F ; Grapheme_Extend # Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW
+0670 ; Grapheme_Extend # Mn ARABIC LETTER SUPERSCRIPT ALEF
+06D6..06DC ; Grapheme_Extend # Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN
+06DF..06E4 ; Grapheme_Extend # Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA
+06E7..06E8 ; Grapheme_Extend # Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON
+06EA..06ED ; Grapheme_Extend # Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM
+0711 ; Grapheme_Extend # Mn SYRIAC LETTER SUPERSCRIPT ALAPH
+0730..074A ; Grapheme_Extend # Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH
+07A6..07B0 ; Grapheme_Extend # Mn [11] THAANA ABAFILI..THAANA SUKUN
+07EB..07F3 ; Grapheme_Extend # Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE
+07FD ; Grapheme_Extend # Mn NKO DANTAYALAN
+0816..0819 ; Grapheme_Extend # Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH
+081B..0823 ; Grapheme_Extend # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A
+0825..0827 ; Grapheme_Extend # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U
+0829..082D ; Grapheme_Extend # Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA
+0859..085B ; Grapheme_Extend # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK
+0897..089F ; Grapheme_Extend # Mn [9] ARABIC PEPET..ARABIC HALF MADDA OVER MADDA
+08CA..08E1 ; Grapheme_Extend # Mn [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA
+08E3..0902 ; Grapheme_Extend # Mn [32] ARABIC TURNED DAMMA BELOW..DEVANAGARI SIGN ANUSVARA
+093A ; Grapheme_Extend # Mn DEVANAGARI VOWEL SIGN OE
+093C ; Grapheme_Extend # Mn DEVANAGARI SIGN NUKTA
+0941..0948 ; Grapheme_Extend # Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI
+094D ; Grapheme_Extend # Mn DEVANAGARI SIGN VIRAMA
+0951..0957 ; Grapheme_Extend # Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE
+0962..0963 ; Grapheme_Extend # Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL
+0981 ; Grapheme_Extend # Mn BENGALI SIGN CANDRABINDU
+09BC ; Grapheme_Extend # Mn BENGALI SIGN NUKTA
+09BE ; Grapheme_Extend # Mc BENGALI VOWEL SIGN AA
+09C1..09C4 ; Grapheme_Extend # Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR
+09CD ; Grapheme_Extend # Mn BENGALI SIGN VIRAMA
+09D7 ; Grapheme_Extend # Mc BENGALI AU LENGTH MARK
+09E2..09E3 ; Grapheme_Extend # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL
+09FE ; Grapheme_Extend # Mn BENGALI SANDHI MARK
+0A01..0A02 ; Grapheme_Extend # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI
+0A3C ; Grapheme_Extend # Mn GURMUKHI SIGN NUKTA
+0A41..0A42 ; Grapheme_Extend # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU
+0A47..0A48 ; Grapheme_Extend # Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI
+0A4B..0A4D ; Grapheme_Extend # Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA
+0A51 ; Grapheme_Extend # Mn GURMUKHI SIGN UDAAT
+0A70..0A71 ; Grapheme_Extend # Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK
+0A75 ; Grapheme_Extend # Mn GURMUKHI SIGN YAKASH
+0A81..0A82 ; Grapheme_Extend # Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA
+0ABC ; Grapheme_Extend # Mn GUJARATI SIGN NUKTA
+0AC1..0AC5 ; Grapheme_Extend # Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E
+0AC7..0AC8 ; Grapheme_Extend # Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI
+0ACD ; Grapheme_Extend # Mn GUJARATI SIGN VIRAMA
+0AE2..0AE3 ; Grapheme_Extend # Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL
+0AFA..0AFF ; Grapheme_Extend # Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE
+0B01 ; Grapheme_Extend # Mn ORIYA SIGN CANDRABINDU
+0B3C ; Grapheme_Extend # Mn ORIYA SIGN NUKTA
+0B3E ; Grapheme_Extend # Mc ORIYA VOWEL SIGN AA
+0B3F ; Grapheme_Extend # Mn ORIYA VOWEL SIGN I
+0B41..0B44 ; Grapheme_Extend # Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR
+0B4D ; Grapheme_Extend # Mn ORIYA SIGN VIRAMA
+0B55..0B56 ; Grapheme_Extend # Mn [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK
+0B57 ; Grapheme_Extend # Mc ORIYA AU LENGTH MARK
+0B62..0B63 ; Grapheme_Extend # Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL
+0B82 ; Grapheme_Extend # Mn TAMIL SIGN ANUSVARA
+0BBE ; Grapheme_Extend # Mc TAMIL VOWEL SIGN AA
+0BC0 ; Grapheme_Extend # Mn TAMIL VOWEL SIGN II
+0BCD ; Grapheme_Extend # Mn TAMIL SIGN VIRAMA
+0BD7 ; Grapheme_Extend # Mc TAMIL AU LENGTH MARK
+0C00 ; Grapheme_Extend # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE
+0C04 ; Grapheme_Extend # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE
+0C3C ; Grapheme_Extend # Mn TELUGU SIGN NUKTA
+0C3E..0C40 ; Grapheme_Extend # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II
+0C46..0C48 ; Grapheme_Extend # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI
+0C4A..0C4D ; Grapheme_Extend # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA
+0C55..0C56 ; Grapheme_Extend # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK
+0C62..0C63 ; Grapheme_Extend # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL
+0C81 ; Grapheme_Extend # Mn KANNADA SIGN CANDRABINDU
+0CBC ; Grapheme_Extend # Mn KANNADA SIGN NUKTA
+0CBF ; Grapheme_Extend # Mn KANNADA VOWEL SIGN I
+0CC0 ; Grapheme_Extend # Mc KANNADA VOWEL SIGN II
+0CC2 ; Grapheme_Extend # Mc KANNADA VOWEL SIGN UU
+0CC6 ; Grapheme_Extend # Mn KANNADA VOWEL SIGN E
+0CC7..0CC8 ; Grapheme_Extend # Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI
+0CCA..0CCB ; Grapheme_Extend # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO
+0CCC..0CCD ; Grapheme_Extend # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA
+0CD5..0CD6 ; Grapheme_Extend # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK
+0CE2..0CE3 ; Grapheme_Extend # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL
+0D00..0D01 ; Grapheme_Extend # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU
+0D3B..0D3C ; Grapheme_Extend # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA
+0D3E ; Grapheme_Extend # Mc MALAYALAM VOWEL SIGN AA
+0D41..0D44 ; Grapheme_Extend # Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR
+0D4D ; Grapheme_Extend # Mn MALAYALAM SIGN VIRAMA
+0D57 ; Grapheme_Extend # Mc MALAYALAM AU LENGTH MARK
+0D62..0D63 ; Grapheme_Extend # Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL
+0D81 ; Grapheme_Extend # Mn SINHALA SIGN CANDRABINDU
+0DCA ; Grapheme_Extend # Mn SINHALA SIGN AL-LAKUNA
+0DCF ; Grapheme_Extend # Mc SINHALA VOWEL SIGN AELA-PILLA
+0DD2..0DD4 ; Grapheme_Extend # Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA
+0DD6 ; Grapheme_Extend # Mn SINHALA VOWEL SIGN DIGA PAA-PILLA
+0DDF ; Grapheme_Extend # Mc SINHALA VOWEL SIGN GAYANUKITTA
+0E31 ; Grapheme_Extend # Mn THAI CHARACTER MAI HAN-AKAT
+0E34..0E3A ; Grapheme_Extend # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU
+0E47..0E4E ; Grapheme_Extend # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN
+0EB1 ; Grapheme_Extend # Mn LAO VOWEL SIGN MAI KAN
+0EB4..0EBC ; Grapheme_Extend # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO
+0EC8..0ECE ; Grapheme_Extend # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN
+0F18..0F19 ; Grapheme_Extend # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS
+0F35 ; Grapheme_Extend # Mn TIBETAN MARK NGAS BZUNG NYI ZLA
+0F37 ; Grapheme_Extend # Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS
+0F39 ; Grapheme_Extend # Mn TIBETAN MARK TSA -PHRU
+0F71..0F7E ; Grapheme_Extend # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO
+0F80..0F84 ; Grapheme_Extend # Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA
+0F86..0F87 ; Grapheme_Extend # Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS
+0F8D..0F97 ; Grapheme_Extend # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA
+0F99..0FBC ; Grapheme_Extend # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA
+0FC6 ; Grapheme_Extend # Mn TIBETAN SYMBOL PADMA GDAN
+102D..1030 ; Grapheme_Extend # Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU
+1032..1037 ; Grapheme_Extend # Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW
+1039..103A ; Grapheme_Extend # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT
+103D..103E ; Grapheme_Extend # Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA
+1058..1059 ; Grapheme_Extend # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL
+105E..1060 ; Grapheme_Extend # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA
+1071..1074 ; Grapheme_Extend # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE
+1082 ; Grapheme_Extend # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA
+1085..1086 ; Grapheme_Extend # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y
+108D ; Grapheme_Extend # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE
+109D ; Grapheme_Extend # Mn MYANMAR VOWEL SIGN AITON AI
+135D..135F ; Grapheme_Extend # Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK
+1712..1714 ; Grapheme_Extend # Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA
+1715 ; Grapheme_Extend # Mc TAGALOG SIGN PAMUDPOD
+1732..1733 ; Grapheme_Extend # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U
+1734 ; Grapheme_Extend # Mc HANUNOO SIGN PAMUDPOD
+1752..1753 ; Grapheme_Extend # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U
+1772..1773 ; Grapheme_Extend # Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U
+17B4..17B5 ; Grapheme_Extend # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
+17B7..17BD ; Grapheme_Extend # Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA
+17C6 ; Grapheme_Extend # Mn KHMER SIGN NIKAHIT
+17C9..17D3 ; Grapheme_Extend # Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT
+17DD ; Grapheme_Extend # Mn KHMER SIGN ATTHACAN
+180B..180D ; Grapheme_Extend # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
+180F ; Grapheme_Extend # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR
+1885..1886 ; Grapheme_Extend # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA
+18A9 ; Grapheme_Extend # Mn MONGOLIAN LETTER ALI GALI DAGALGA
+1920..1922 ; Grapheme_Extend # Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U
+1927..1928 ; Grapheme_Extend # Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O
+1932 ; Grapheme_Extend # Mn LIMBU SMALL LETTER ANUSVARA
+1939..193B ; Grapheme_Extend # Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I
+1A17..1A18 ; Grapheme_Extend # Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U
+1A1B ; Grapheme_Extend # Mn BUGINESE VOWEL SIGN AE
+1A56 ; Grapheme_Extend # Mn TAI THAM CONSONANT SIGN MEDIAL LA
+1A58..1A5E ; Grapheme_Extend # Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA
+1A60 ; Grapheme_Extend # Mn TAI THAM SIGN SAKOT
+1A62 ; Grapheme_Extend # Mn TAI THAM VOWEL SIGN MAI SAT
+1A65..1A6C ; Grapheme_Extend # Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW
+1A73..1A7C ; Grapheme_Extend # Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN
+1A7F ; Grapheme_Extend # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT
+1AB0..1ABD ; Grapheme_Extend # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW
+1ABE ; Grapheme_Extend # Me COMBINING PARENTHESES OVERLAY
+1ABF..1ACE ; Grapheme_Extend # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T
+1B00..1B03 ; Grapheme_Extend # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG
+1B34 ; Grapheme_Extend # Mn BALINESE SIGN REREKAN
+1B35 ; Grapheme_Extend # Mc BALINESE VOWEL SIGN TEDUNG
+1B36..1B3A ; Grapheme_Extend # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA
+1B3B ; Grapheme_Extend # Mc BALINESE VOWEL SIGN RA REPA TEDUNG
+1B3C ; Grapheme_Extend # Mn BALINESE VOWEL SIGN LA LENGA
+1B3D ; Grapheme_Extend # Mc BALINESE VOWEL SIGN LA LENGA TEDUNG
+1B42 ; Grapheme_Extend # Mn BALINESE VOWEL SIGN PEPET
+1B43..1B44 ; Grapheme_Extend # Mc [2] BALINESE VOWEL SIGN PEPET TEDUNG..BALINESE ADEG ADEG
+1B6B..1B73 ; Grapheme_Extend # Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG
+1B80..1B81 ; Grapheme_Extend # Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR
+1BA2..1BA5 ; Grapheme_Extend # Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU
+1BA8..1BA9 ; Grapheme_Extend # Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG
+1BAA ; Grapheme_Extend # Mc SUNDANESE SIGN PAMAAEH
+1BAB..1BAD ; Grapheme_Extend # Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA
+1BE6 ; Grapheme_Extend # Mn BATAK SIGN TOMPI
+1BE8..1BE9 ; Grapheme_Extend # Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE
+1BED ; Grapheme_Extend # Mn BATAK VOWEL SIGN KARO O
+1BEF..1BF1 ; Grapheme_Extend # Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H
+1BF2..1BF3 ; Grapheme_Extend # Mc [2] BATAK PANGOLAT..BATAK PANONGONAN
+1C2C..1C33 ; Grapheme_Extend # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T
+1C36..1C37 ; Grapheme_Extend # Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA
+1CD0..1CD2 ; Grapheme_Extend # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA
+1CD4..1CE0 ; Grapheme_Extend # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA
+1CE2..1CE8 ; Grapheme_Extend # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL
+1CED ; Grapheme_Extend # Mn VEDIC SIGN TIRYAK
+1CF4 ; Grapheme_Extend # Mn VEDIC TONE CANDRA ABOVE
+1CF8..1CF9 ; Grapheme_Extend # Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE
+1DC0..1DFF ; Grapheme_Extend # Mn [64] COMBINING DOTTED GRAVE ACCENT..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW
+200C ; Grapheme_Extend # Cf ZERO WIDTH NON-JOINER
+20D0..20DC ; Grapheme_Extend # Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE
+20DD..20E0 ; Grapheme_Extend # Me [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH
+20E1 ; Grapheme_Extend # Mn COMBINING LEFT RIGHT ARROW ABOVE
+20E2..20E4 ; Grapheme_Extend # Me [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE
+20E5..20F0 ; Grapheme_Extend # Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE
+2CEF..2CF1 ; Grapheme_Extend # Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS
+2D7F ; Grapheme_Extend # Mn TIFINAGH CONSONANT JOINER
+2DE0..2DFF ; Grapheme_Extend # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS
+302A..302D ; Grapheme_Extend # Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK
+302E..302F ; Grapheme_Extend # Mc [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK
+3099..309A ; Grapheme_Extend # Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+A66F ; Grapheme_Extend # Mn COMBINING CYRILLIC VZMET
+A670..A672 ; Grapheme_Extend # Me [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN
+A674..A67D ; Grapheme_Extend # Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK
+A69E..A69F ; Grapheme_Extend # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E
+A6F0..A6F1 ; Grapheme_Extend # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS
+A802 ; Grapheme_Extend # Mn SYLOTI NAGRI SIGN DVISVARA
+A806 ; Grapheme_Extend # Mn SYLOTI NAGRI SIGN HASANTA
+A80B ; Grapheme_Extend # Mn SYLOTI NAGRI SIGN ANUSVARA
+A825..A826 ; Grapheme_Extend # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E
+A82C ; Grapheme_Extend # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA
+A8C4..A8C5 ; Grapheme_Extend # Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU
+A8E0..A8F1 ; Grapheme_Extend # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA
+A8FF ; Grapheme_Extend # Mn DEVANAGARI VOWEL SIGN AY
+A926..A92D ; Grapheme_Extend # Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU
+A947..A951 ; Grapheme_Extend # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R
+A953 ; Grapheme_Extend # Mc REJANG VIRAMA
+A980..A982 ; Grapheme_Extend # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR
+A9B3 ; Grapheme_Extend # Mn JAVANESE SIGN CECAK TELU
+A9B6..A9B9 ; Grapheme_Extend # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT
+A9BC..A9BD ; Grapheme_Extend # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET
+A9C0 ; Grapheme_Extend # Mc JAVANESE PANGKON
+A9E5 ; Grapheme_Extend # Mn MYANMAR SIGN SHAN SAW
+AA29..AA2E ; Grapheme_Extend # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE
+AA31..AA32 ; Grapheme_Extend # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE
+AA35..AA36 ; Grapheme_Extend # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA
+AA43 ; Grapheme_Extend # Mn CHAM CONSONANT SIGN FINAL NG
+AA4C ; Grapheme_Extend # Mn CHAM CONSONANT SIGN FINAL M
+AA7C ; Grapheme_Extend # Mn MYANMAR SIGN TAI LAING TONE-2
+AAB0 ; Grapheme_Extend # Mn TAI VIET MAI KANG
+AAB2..AAB4 ; Grapheme_Extend # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U
+AAB7..AAB8 ; Grapheme_Extend # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA
+AABE..AABF ; Grapheme_Extend # Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK
+AAC1 ; Grapheme_Extend # Mn TAI VIET TONE MAI THO
+AAEC..AAED ; Grapheme_Extend # Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI
+AAF6 ; Grapheme_Extend # Mn MEETEI MAYEK VIRAMA
+ABE5 ; Grapheme_Extend # Mn MEETEI MAYEK VOWEL SIGN ANAP
+ABE8 ; Grapheme_Extend # Mn MEETEI MAYEK VOWEL SIGN UNAP
+ABED ; Grapheme_Extend # Mn MEETEI MAYEK APUN IYEK
+FB1E ; Grapheme_Extend # Mn HEBREW POINT JUDEO-SPANISH VARIKA
+FE00..FE0F ; Grapheme_Extend # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
+FE20..FE2F ; Grapheme_Extend # Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF
+FF9E..FF9F ; Grapheme_Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
+101FD ; Grapheme_Extend # Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE
+102E0 ; Grapheme_Extend # Mn COPTIC EPACT THOUSANDS MARK
+10376..1037A ; Grapheme_Extend # Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII
+10A01..10A03 ; Grapheme_Extend # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R
+10A05..10A06 ; Grapheme_Extend # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O
+10A0C..10A0F ; Grapheme_Extend # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA
+10A38..10A3A ; Grapheme_Extend # Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW
+10A3F ; Grapheme_Extend # Mn KHAROSHTHI VIRAMA
+10AE5..10AE6 ; Grapheme_Extend # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW
+10D24..10D27 ; Grapheme_Extend # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI
+10D69..10D6D ; Grapheme_Extend # Mn [5] GARAY VOWEL SIGN E..GARAY CONSONANT NASALIZATION MARK
+10EAB..10EAC ; Grapheme_Extend # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK
+10EFC..10EFF ; Grapheme_Extend # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA
+10F46..10F50 ; Grapheme_Extend # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW
+10F82..10F85 ; Grapheme_Extend # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW
+11001 ; Grapheme_Extend # Mn BRAHMI SIGN ANUSVARA
+11038..11046 ; Grapheme_Extend # Mn [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA
+11070 ; Grapheme_Extend # Mn BRAHMI SIGN OLD TAMIL VIRAMA
+11073..11074 ; Grapheme_Extend # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O
+1107F..11081 ; Grapheme_Extend # Mn [3] BRAHMI NUMBER JOINER..KAITHI SIGN ANUSVARA
+110B3..110B6 ; Grapheme_Extend # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI
+110B9..110BA ; Grapheme_Extend # Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA
+110C2 ; Grapheme_Extend # Mn KAITHI VOWEL SIGN VOCALIC R
+11100..11102 ; Grapheme_Extend # Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA
+11127..1112B ; Grapheme_Extend # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU
+1112D..11134 ; Grapheme_Extend # Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA
+11173 ; Grapheme_Extend # Mn MAHAJANI SIGN NUKTA
+11180..11181 ; Grapheme_Extend # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA
+111B6..111BE ; Grapheme_Extend # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O
+111C0 ; Grapheme_Extend # Mc SHARADA SIGN VIRAMA
+111C9..111CC ; Grapheme_Extend # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK
+111CF ; Grapheme_Extend # Mn SHARADA SIGN INVERTED CANDRABINDU
+1122F..11231 ; Grapheme_Extend # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI
+11234 ; Grapheme_Extend # Mn KHOJKI SIGN ANUSVARA
+11235 ; Grapheme_Extend # Mc KHOJKI SIGN VIRAMA
+11236..11237 ; Grapheme_Extend # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA
+1123E ; Grapheme_Extend # Mn KHOJKI SIGN SUKUN
+11241 ; Grapheme_Extend # Mn KHOJKI VOWEL SIGN VOCALIC R
+112DF ; Grapheme_Extend # Mn KHUDAWADI SIGN ANUSVARA
+112E3..112EA ; Grapheme_Extend # Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA
+11300..11301 ; Grapheme_Extend # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU
+1133B..1133C ; Grapheme_Extend # Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA
+1133E ; Grapheme_Extend # Mc GRANTHA VOWEL SIGN AA
+11340 ; Grapheme_Extend # Mn GRANTHA VOWEL SIGN II
+1134D ; Grapheme_Extend # Mc GRANTHA SIGN VIRAMA
+11357 ; Grapheme_Extend # Mc GRANTHA AU LENGTH MARK
+11366..1136C ; Grapheme_Extend # Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX
+11370..11374 ; Grapheme_Extend # Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA
+113B8 ; Grapheme_Extend # Mc TULU-TIGALARI VOWEL SIGN AA
+113BB..113C0 ; Grapheme_Extend # Mn [6] TULU-TIGALARI VOWEL SIGN U..TULU-TIGALARI VOWEL SIGN VOCALIC LL
+113C2 ; Grapheme_Extend # Mc TULU-TIGALARI VOWEL SIGN EE
+113C5 ; Grapheme_Extend # Mc TULU-TIGALARI VOWEL SIGN AI
+113C7..113C9 ; Grapheme_Extend # Mc [3] TULU-TIGALARI VOWEL SIGN OO..TULU-TIGALARI AU LENGTH MARK
+113CE ; Grapheme_Extend # Mn TULU-TIGALARI SIGN VIRAMA
+113CF ; Grapheme_Extend # Mc TULU-TIGALARI SIGN LOOPED VIRAMA
+113D0 ; Grapheme_Extend # Mn TULU-TIGALARI CONJOINER
+113D2 ; Grapheme_Extend # Mn TULU-TIGALARI GEMINATION MARK
+113E1..113E2 ; Grapheme_Extend # Mn [2] TULU-TIGALARI VEDIC TONE SVARITA..TULU-TIGALARI VEDIC TONE ANUDATTA
+11438..1143F ; Grapheme_Extend # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI
+11442..11444 ; Grapheme_Extend # Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA
+11446 ; Grapheme_Extend # Mn NEWA SIGN NUKTA
+1145E ; Grapheme_Extend # Mn NEWA SANDHI MARK
+114B0 ; Grapheme_Extend # Mc TIRHUTA VOWEL SIGN AA
+114B3..114B8 ; Grapheme_Extend # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL
+114BA ; Grapheme_Extend # Mn TIRHUTA VOWEL SIGN SHORT E
+114BD ; Grapheme_Extend # Mc TIRHUTA VOWEL SIGN SHORT O
+114BF..114C0 ; Grapheme_Extend # Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA
+114C2..114C3 ; Grapheme_Extend # Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA
+115AF ; Grapheme_Extend # Mc SIDDHAM VOWEL SIGN AA
+115B2..115B5 ; Grapheme_Extend # Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR
+115BC..115BD ; Grapheme_Extend # Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA
+115BF..115C0 ; Grapheme_Extend # Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA
+115DC..115DD ; Grapheme_Extend # Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU
+11633..1163A ; Grapheme_Extend # Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI
+1163D ; Grapheme_Extend # Mn MODI SIGN ANUSVARA
+1163F..11640 ; Grapheme_Extend # Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA
+116AB ; Grapheme_Extend # Mn TAKRI SIGN ANUSVARA
+116AD ; Grapheme_Extend # Mn TAKRI VOWEL SIGN AA
+116B0..116B5 ; Grapheme_Extend # Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU
+116B6 ; Grapheme_Extend # Mc TAKRI SIGN VIRAMA
+116B7 ; Grapheme_Extend # Mn TAKRI SIGN NUKTA
+1171D ; Grapheme_Extend # Mn AHOM CONSONANT SIGN MEDIAL LA
+1171F ; Grapheme_Extend # Mn AHOM CONSONANT SIGN MEDIAL LIGATING RA
+11722..11725 ; Grapheme_Extend # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU
+11727..1172B ; Grapheme_Extend # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER
+1182F..11837 ; Grapheme_Extend # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA
+11839..1183A ; Grapheme_Extend # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA
+11930 ; Grapheme_Extend # Mc DIVES AKURU VOWEL SIGN AA
+1193B..1193C ; Grapheme_Extend # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU
+1193D ; Grapheme_Extend # Mc DIVES AKURU SIGN HALANTA
+1193E ; Grapheme_Extend # Mn DIVES AKURU VIRAMA
+11943 ; Grapheme_Extend # Mn DIVES AKURU SIGN NUKTA
+119D4..119D7 ; Grapheme_Extend # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR
+119DA..119DB ; Grapheme_Extend # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI
+119E0 ; Grapheme_Extend # Mn NANDINAGARI SIGN VIRAMA
+11A01..11A0A ; Grapheme_Extend # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK
+11A33..11A38 ; Grapheme_Extend # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA
+11A3B..11A3E ; Grapheme_Extend # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA
+11A47 ; Grapheme_Extend # Mn ZANABAZAR SQUARE SUBJOINER
+11A51..11A56 ; Grapheme_Extend # Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE
+11A59..11A5B ; Grapheme_Extend # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK
+11A8A..11A96 ; Grapheme_Extend # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA
+11A98..11A99 ; Grapheme_Extend # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER
+11C30..11C36 ; Grapheme_Extend # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L
+11C38..11C3D ; Grapheme_Extend # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA
+11C3F ; Grapheme_Extend # Mn BHAIKSUKI SIGN VIRAMA
+11C92..11CA7 ; Grapheme_Extend # Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA
+11CAA..11CB0 ; Grapheme_Extend # Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA
+11CB2..11CB3 ; Grapheme_Extend # Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E
+11CB5..11CB6 ; Grapheme_Extend # Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU
+11D31..11D36 ; Grapheme_Extend # Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R
+11D3A ; Grapheme_Extend # Mn MASARAM GONDI VOWEL SIGN E
+11D3C..11D3D ; Grapheme_Extend # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O
+11D3F..11D45 ; Grapheme_Extend # Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA
+11D47 ; Grapheme_Extend # Mn MASARAM GONDI RA-KARA
+11D90..11D91 ; Grapheme_Extend # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI
+11D95 ; Grapheme_Extend # Mn GUNJALA GONDI SIGN ANUSVARA
+11D97 ; Grapheme_Extend # Mn GUNJALA GONDI VIRAMA
+11EF3..11EF4 ; Grapheme_Extend # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U
+11F00..11F01 ; Grapheme_Extend # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA
+11F36..11F3A ; Grapheme_Extend # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R
+11F40 ; Grapheme_Extend # Mn KAWI VOWEL SIGN EU
+11F41 ; Grapheme_Extend # Mc KAWI SIGN KILLER
+11F42 ; Grapheme_Extend # Mn KAWI CONJOINER
+11F5A ; Grapheme_Extend # Mn KAWI SIGN NUKTA
+13440 ; Grapheme_Extend # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY
+13447..13455 ; Grapheme_Extend # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED
+1611E..16129 ; Grapheme_Extend # Mn [12] GURUNG KHEMA VOWEL SIGN AA..GURUNG KHEMA VOWEL LENGTH MARK
+1612D..1612F ; Grapheme_Extend # Mn [3] GURUNG KHEMA SIGN ANUSVARA..GURUNG KHEMA SIGN THOLHOMA
+16AF0..16AF4 ; Grapheme_Extend # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE
+16B30..16B36 ; Grapheme_Extend # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
+16F4F ; Grapheme_Extend # Mn MIAO SIGN CONSONANT MODIFIER BAR
+16F8F..16F92 ; Grapheme_Extend # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
+16FE4 ; Grapheme_Extend # Mn KHITAN SMALL SCRIPT FILLER
+16FF0..16FF1 ; Grapheme_Extend # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY
+1BC9D..1BC9E ; Grapheme_Extend # Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK
+1CF00..1CF2D ; Grapheme_Extend # Mn [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT
+1CF30..1CF46 ; Grapheme_Extend # Mn [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG
+1D165..1D166 ; Grapheme_Extend # Mc [2] MUSICAL SYMBOL COMBINING STEM..MUSICAL SYMBOL COMBINING SPRECHGESANG STEM
+1D167..1D169 ; Grapheme_Extend # Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3
+1D16D..1D172 ; Grapheme_Extend # Mc [6] MUSICAL SYMBOL COMBINING AUGMENTATION DOT..MUSICAL SYMBOL COMBINING FLAG-5
+1D17B..1D182 ; Grapheme_Extend # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE
+1D185..1D18B ; Grapheme_Extend # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE
+1D1AA..1D1AD ; Grapheme_Extend # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO
+1D242..1D244 ; Grapheme_Extend # Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME
+1DA00..1DA36 ; Grapheme_Extend # Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN
+1DA3B..1DA6C ; Grapheme_Extend # Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT
+1DA75 ; Grapheme_Extend # Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS
+1DA84 ; Grapheme_Extend # Mn SIGNWRITING LOCATION HEAD NECK
+1DA9B..1DA9F ; Grapheme_Extend # Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6
+1DAA1..1DAAF ; Grapheme_Extend # Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16
+1E000..1E006 ; Grapheme_Extend # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE
+1E008..1E018 ; Grapheme_Extend # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU
+1E01B..1E021 ; Grapheme_Extend # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI
+1E023..1E024 ; Grapheme_Extend # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS
+1E026..1E02A ; Grapheme_Extend # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA
+1E08F ; Grapheme_Extend # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+1E130..1E136 ; Grapheme_Extend # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D
+1E2AE ; Grapheme_Extend # Mn TOTO SIGN RISING TONE
+1E2EC..1E2EF ; Grapheme_Extend # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI
+1E4EC..1E4EF ; Grapheme_Extend # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH
+1E5EE..1E5EF ; Grapheme_Extend # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR
+1E8D0..1E8D6 ; Grapheme_Extend # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS
+1E944..1E94A ; Grapheme_Extend # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA
+E0020..E007F ; Grapheme_Extend # Cf [96] TAG SPACE..CANCEL TAG
+E0100..E01EF ; Grapheme_Extend # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
+
+# Total code points: 2193
+
+# ================================================
+
+# Derived Property: Grapheme_Base
+# Generated from: [0..10FFFF] - Cc - Cf - Cs - Co - Cn - Zl - Zp - Grapheme_Extend
+# Note: depending on an application's interpretation of Co (private use),
+# they may be either in Grapheme_Base, or in Grapheme_Extend, or in neither.
+
+0020 ; Grapheme_Base # Zs SPACE
+0021..0023 ; Grapheme_Base # Po [3] EXCLAMATION MARK..NUMBER SIGN
+0024 ; Grapheme_Base # Sc DOLLAR SIGN
+0025..0027 ; Grapheme_Base # Po [3] PERCENT SIGN..APOSTROPHE
+0028 ; Grapheme_Base # Ps LEFT PARENTHESIS
+0029 ; Grapheme_Base # Pe RIGHT PARENTHESIS
+002A ; Grapheme_Base # Po ASTERISK
+002B ; Grapheme_Base # Sm PLUS SIGN
+002C ; Grapheme_Base # Po COMMA
+002D ; Grapheme_Base # Pd HYPHEN-MINUS
+002E..002F ; Grapheme_Base # Po [2] FULL STOP..SOLIDUS
+0030..0039 ; Grapheme_Base # Nd [10] DIGIT ZERO..DIGIT NINE
+003A..003B ; Grapheme_Base # Po [2] COLON..SEMICOLON
+003C..003E ; Grapheme_Base # Sm [3] LESS-THAN SIGN..GREATER-THAN SIGN
+003F..0040 ; Grapheme_Base # Po [2] QUESTION MARK..COMMERCIAL AT
+0041..005A ; Grapheme_Base # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+005B ; Grapheme_Base # Ps LEFT SQUARE BRACKET
+005C ; Grapheme_Base # Po REVERSE SOLIDUS
+005D ; Grapheme_Base # Pe RIGHT SQUARE BRACKET
+005E ; Grapheme_Base # Sk CIRCUMFLEX ACCENT
+005F ; Grapheme_Base # Pc LOW LINE
+0060 ; Grapheme_Base # Sk GRAVE ACCENT
+0061..007A ; Grapheme_Base # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z
+007B ; Grapheme_Base # Ps LEFT CURLY BRACKET
+007C ; Grapheme_Base # Sm VERTICAL LINE
+007D ; Grapheme_Base # Pe RIGHT CURLY BRACKET
+007E ; Grapheme_Base # Sm TILDE
+00A0 ; Grapheme_Base # Zs NO-BREAK SPACE
+00A1 ; Grapheme_Base # Po INVERTED EXCLAMATION MARK
+00A2..00A5 ; Grapheme_Base # Sc [4] CENT SIGN..YEN SIGN
+00A6 ; Grapheme_Base # So BROKEN BAR
+00A7 ; Grapheme_Base # Po SECTION SIGN
+00A8 ; Grapheme_Base # Sk DIAERESIS
+00A9 ; Grapheme_Base # So COPYRIGHT SIGN
+00AA ; Grapheme_Base # Lo FEMININE ORDINAL INDICATOR
+00AB ; Grapheme_Base # Pi LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+00AC ; Grapheme_Base # Sm NOT SIGN
+00AE ; Grapheme_Base # So REGISTERED SIGN
+00AF ; Grapheme_Base # Sk MACRON
+00B0 ; Grapheme_Base # So DEGREE SIGN
+00B1 ; Grapheme_Base # Sm PLUS-MINUS SIGN
+00B2..00B3 ; Grapheme_Base # No [2] SUPERSCRIPT TWO..SUPERSCRIPT THREE
+00B4 ; Grapheme_Base # Sk ACUTE ACCENT
+00B5 ; Grapheme_Base # L& MICRO SIGN
+00B6..00B7 ; Grapheme_Base # Po [2] PILCROW SIGN..MIDDLE DOT
+00B8 ; Grapheme_Base # Sk CEDILLA
+00B9 ; Grapheme_Base # No SUPERSCRIPT ONE
+00BA ; Grapheme_Base # Lo MASCULINE ORDINAL INDICATOR
+00BB ; Grapheme_Base # Pf RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+00BC..00BE ; Grapheme_Base # No [3] VULGAR FRACTION ONE QUARTER..VULGAR FRACTION THREE QUARTERS
+00BF ; Grapheme_Base # Po INVERTED QUESTION MARK
+00C0..00D6 ; Grapheme_Base # L& [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS
+00D7 ; Grapheme_Base # Sm MULTIPLICATION SIGN
+00D8..00F6 ; Grapheme_Base # L& [31] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER O WITH DIAERESIS
+00F7 ; Grapheme_Base # Sm DIVISION SIGN
+00F8..01BA ; Grapheme_Base # L& [195] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL
+01BB ; Grapheme_Base # Lo LATIN LETTER TWO WITH STROKE
+01BC..01BF ; Grapheme_Base # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN
+01C0..01C3 ; Grapheme_Base # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK
+01C4..0293 ; Grapheme_Base # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL
+0294 ; Grapheme_Base # Lo LATIN LETTER GLOTTAL STOP
+0295..02AF ; Grapheme_Base # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL
+02B0..02C1 ; Grapheme_Base # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP
+02C2..02C5 ; Grapheme_Base # Sk [4] MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LETTER DOWN ARROWHEAD
+02C6..02D1 ; Grapheme_Base # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON
+02D2..02DF ; Grapheme_Base # Sk [14] MODIFIER LETTER CENTRED RIGHT HALF RING..MODIFIER LETTER CROSS ACCENT
+02E0..02E4 ; Grapheme_Base # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP
+02E5..02EB ; Grapheme_Base # Sk [7] MODIFIER LETTER EXTRA-HIGH TONE BAR..MODIFIER LETTER YANG DEPARTING TONE MARK
+02EC ; Grapheme_Base # Lm MODIFIER LETTER VOICING
+02ED ; Grapheme_Base # Sk MODIFIER LETTER UNASPIRATED
+02EE ; Grapheme_Base # Lm MODIFIER LETTER DOUBLE APOSTROPHE
+02EF..02FF ; Grapheme_Base # Sk [17] MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER LETTER LOW LEFT ARROW
+0370..0373 ; Grapheme_Base # L& [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI
+0374 ; Grapheme_Base # Lm GREEK NUMERAL SIGN
+0375 ; Grapheme_Base # Sk GREEK LOWER NUMERAL SIGN
+0376..0377 ; Grapheme_Base # L& [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA
+037A ; Grapheme_Base # Lm GREEK YPOGEGRAMMENI
+037B..037D ; Grapheme_Base # L& [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL
+037E ; Grapheme_Base # Po GREEK QUESTION MARK
+037F ; Grapheme_Base # L& GREEK CAPITAL LETTER YOT
+0384..0385 ; Grapheme_Base # Sk [2] GREEK TONOS..GREEK DIALYTIKA TONOS
+0386 ; Grapheme_Base # L& GREEK CAPITAL LETTER ALPHA WITH TONOS
+0387 ; Grapheme_Base # Po GREEK ANO TELEIA
+0388..038A ; Grapheme_Base # L& [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS
+038C ; Grapheme_Base # L& GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E..03A1 ; Grapheme_Base # L& [20] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER RHO
+03A3..03F5 ; Grapheme_Base # L& [83] GREEK CAPITAL LETTER SIGMA..GREEK LUNATE EPSILON SYMBOL
+03F6 ; Grapheme_Base # Sm GREEK REVERSED LUNATE EPSILON SYMBOL
+03F7..0481 ; Grapheme_Base # L& [139] GREEK CAPITAL LETTER SHO..CYRILLIC SMALL LETTER KOPPA
+0482 ; Grapheme_Base # So CYRILLIC THOUSANDS SIGN
+048A..052F ; Grapheme_Base # L& [166] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER EL WITH DESCENDER
+0531..0556 ; Grapheme_Base # L& [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH
+0559 ; Grapheme_Base # Lm ARMENIAN MODIFIER LETTER LEFT HALF RING
+055A..055F ; Grapheme_Base # Po [6] ARMENIAN APOSTROPHE..ARMENIAN ABBREVIATION MARK
+0560..0588 ; Grapheme_Base # L& [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE
+0589 ; Grapheme_Base # Po ARMENIAN FULL STOP
+058A ; Grapheme_Base # Pd ARMENIAN HYPHEN
+058D..058E ; Grapheme_Base # So [2] RIGHT-FACING ARMENIAN ETERNITY SIGN..LEFT-FACING ARMENIAN ETERNITY SIGN
+058F ; Grapheme_Base # Sc ARMENIAN DRAM SIGN
+05BE ; Grapheme_Base # Pd HEBREW PUNCTUATION MAQAF
+05C0 ; Grapheme_Base # Po HEBREW PUNCTUATION PASEQ
+05C3 ; Grapheme_Base # Po HEBREW PUNCTUATION SOF PASUQ
+05C6 ; Grapheme_Base # Po HEBREW PUNCTUATION NUN HAFUKHA
+05D0..05EA ; Grapheme_Base # Lo [27] HEBREW LETTER ALEF..HEBREW LETTER TAV
+05EF..05F2 ; Grapheme_Base # Lo [4] HEBREW YOD TRIANGLE..HEBREW LIGATURE YIDDISH DOUBLE YOD
+05F3..05F4 ; Grapheme_Base # Po [2] HEBREW PUNCTUATION GERESH..HEBREW PUNCTUATION GERSHAYIM
+0606..0608 ; Grapheme_Base # Sm [3] ARABIC-INDIC CUBE ROOT..ARABIC RAY
+0609..060A ; Grapheme_Base # Po [2] ARABIC-INDIC PER MILLE SIGN..ARABIC-INDIC PER TEN THOUSAND SIGN
+060B ; Grapheme_Base # Sc AFGHANI SIGN
+060C..060D ; Grapheme_Base # Po [2] ARABIC COMMA..ARABIC DATE SEPARATOR
+060E..060F ; Grapheme_Base # So [2] ARABIC POETIC VERSE SIGN..ARABIC SIGN MISRA
+061B ; Grapheme_Base # Po ARABIC SEMICOLON
+061D..061F ; Grapheme_Base # Po [3] ARABIC END OF TEXT MARK..ARABIC QUESTION MARK
+0620..063F ; Grapheme_Base # Lo [32] ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE
+0640 ; Grapheme_Base # Lm ARABIC TATWEEL
+0641..064A ; Grapheme_Base # Lo [10] ARABIC LETTER FEH..ARABIC LETTER YEH
+0660..0669 ; Grapheme_Base # Nd [10] ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE
+066A..066D ; Grapheme_Base # Po [4] ARABIC PERCENT SIGN..ARABIC FIVE POINTED STAR
+066E..066F ; Grapheme_Base # Lo [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF
+0671..06D3 ; Grapheme_Base # Lo [99] ARABIC LETTER ALEF WASLA..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE
+06D4 ; Grapheme_Base # Po ARABIC FULL STOP
+06D5 ; Grapheme_Base # Lo ARABIC LETTER AE
+06DE ; Grapheme_Base # So ARABIC START OF RUB EL HIZB
+06E5..06E6 ; Grapheme_Base # Lm [2] ARABIC SMALL WAW..ARABIC SMALL YEH
+06E9 ; Grapheme_Base # So ARABIC PLACE OF SAJDAH
+06EE..06EF ; Grapheme_Base # Lo [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V
+06F0..06F9 ; Grapheme_Base # Nd [10] EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE
+06FA..06FC ; Grapheme_Base # Lo [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW
+06FD..06FE ; Grapheme_Base # So [2] ARABIC SIGN SINDHI AMPERSAND..ARABIC SIGN SINDHI POSTPOSITION MEN
+06FF ; Grapheme_Base # Lo ARABIC LETTER HEH WITH INVERTED V
+0700..070D ; Grapheme_Base # Po [14] SYRIAC END OF PARAGRAPH..SYRIAC HARKLEAN ASTERISCUS
+0710 ; Grapheme_Base # Lo SYRIAC LETTER ALAPH
+0712..072F ; Grapheme_Base # Lo [30] SYRIAC LETTER BETH..SYRIAC LETTER PERSIAN DHALATH
+074D..07A5 ; Grapheme_Base # Lo [89] SYRIAC LETTER SOGDIAN ZHAIN..THAANA LETTER WAAVU
+07B1 ; Grapheme_Base # Lo THAANA LETTER NAA
+07C0..07C9 ; Grapheme_Base # Nd [10] NKO DIGIT ZERO..NKO DIGIT NINE
+07CA..07EA ; Grapheme_Base # Lo [33] NKO LETTER A..NKO LETTER JONA RA
+07F4..07F5 ; Grapheme_Base # Lm [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE
+07F6 ; Grapheme_Base # So NKO SYMBOL OO DENNEN
+07F7..07F9 ; Grapheme_Base # Po [3] NKO SYMBOL GBAKURUNEN..NKO EXCLAMATION MARK
+07FA ; Grapheme_Base # Lm NKO LAJANYALAN
+07FE..07FF ; Grapheme_Base # Sc [2] NKO DOROME SIGN..NKO TAMAN SIGN
+0800..0815 ; Grapheme_Base # Lo [22] SAMARITAN LETTER ALAF..SAMARITAN LETTER TAAF
+081A ; Grapheme_Base # Lm SAMARITAN MODIFIER LETTER EPENTHETIC YUT
+0824 ; Grapheme_Base # Lm SAMARITAN MODIFIER LETTER SHORT A
+0828 ; Grapheme_Base # Lm SAMARITAN MODIFIER LETTER I
+0830..083E ; Grapheme_Base # Po [15] SAMARITAN PUNCTUATION NEQUDAA..SAMARITAN PUNCTUATION ANNAAU
+0840..0858 ; Grapheme_Base # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN
+085E ; Grapheme_Base # Po MANDAIC PUNCTUATION
+0860..086A ; Grapheme_Base # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA
+0870..0887 ; Grapheme_Base # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT
+0888 ; Grapheme_Base # Sk ARABIC RAISED ROUND DOT
+0889..088E ; Grapheme_Base # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL
+08A0..08C8 ; Grapheme_Base # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF
+08C9 ; Grapheme_Base # Lm ARABIC SMALL FARSI YEH
+0903 ; Grapheme_Base # Mc DEVANAGARI SIGN VISARGA
+0904..0939 ; Grapheme_Base # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA
+093B ; Grapheme_Base # Mc DEVANAGARI VOWEL SIGN OOE
+093D ; Grapheme_Base # Lo DEVANAGARI SIGN AVAGRAHA
+093E..0940 ; Grapheme_Base # Mc [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II
+0949..094C ; Grapheme_Base # Mc [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU
+094E..094F ; Grapheme_Base # Mc [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW
+0950 ; Grapheme_Base # Lo DEVANAGARI OM
+0958..0961 ; Grapheme_Base # Lo [10] DEVANAGARI LETTER QA..DEVANAGARI LETTER VOCALIC LL
+0964..0965 ; Grapheme_Base # Po [2] DEVANAGARI DANDA..DEVANAGARI DOUBLE DANDA
+0966..096F ; Grapheme_Base # Nd [10] DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE
+0970 ; Grapheme_Base # Po DEVANAGARI ABBREVIATION SIGN
+0971 ; Grapheme_Base # Lm DEVANAGARI SIGN HIGH SPACING DOT
+0972..0980 ; Grapheme_Base # Lo [15] DEVANAGARI LETTER CANDRA A..BENGALI ANJI
+0982..0983 ; Grapheme_Base # Mc [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA
+0985..098C ; Grapheme_Base # Lo [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L
+098F..0990 ; Grapheme_Base # Lo [2] BENGALI LETTER E..BENGALI LETTER AI
+0993..09A8 ; Grapheme_Base # Lo [22] BENGALI LETTER O..BENGALI LETTER NA
+09AA..09B0 ; Grapheme_Base # Lo [7] BENGALI LETTER PA..BENGALI LETTER RA
+09B2 ; Grapheme_Base # Lo BENGALI LETTER LA
+09B6..09B9 ; Grapheme_Base # Lo [4] BENGALI LETTER SHA..BENGALI LETTER HA
+09BD ; Grapheme_Base # Lo BENGALI SIGN AVAGRAHA
+09BF..09C0 ; Grapheme_Base # Mc [2] BENGALI VOWEL SIGN I..BENGALI VOWEL SIGN II
+09C7..09C8 ; Grapheme_Base # Mc [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI
+09CB..09CC ; Grapheme_Base # Mc [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU
+09CE ; Grapheme_Base # Lo BENGALI LETTER KHANDA TA
+09DC..09DD ; Grapheme_Base # Lo [2] BENGALI LETTER RRA..BENGALI LETTER RHA
+09DF..09E1 ; Grapheme_Base # Lo [3] BENGALI LETTER YYA..BENGALI LETTER VOCALIC LL
+09E6..09EF ; Grapheme_Base # Nd [10] BENGALI DIGIT ZERO..BENGALI DIGIT NINE
+09F0..09F1 ; Grapheme_Base # Lo [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL
+09F2..09F3 ; Grapheme_Base # Sc [2] BENGALI RUPEE MARK..BENGALI RUPEE SIGN
+09F4..09F9 ; Grapheme_Base # No [6] BENGALI CURRENCY NUMERATOR ONE..BENGALI CURRENCY DENOMINATOR SIXTEEN
+09FA ; Grapheme_Base # So BENGALI ISSHAR
+09FB ; Grapheme_Base # Sc BENGALI GANDA MARK
+09FC ; Grapheme_Base # Lo BENGALI LETTER VEDIC ANUSVARA
+09FD ; Grapheme_Base # Po BENGALI ABBREVIATION SIGN
+0A03 ; Grapheme_Base # Mc GURMUKHI SIGN VISARGA
+0A05..0A0A ; Grapheme_Base # Lo [6] GURMUKHI LETTER A..GURMUKHI LETTER UU
+0A0F..0A10 ; Grapheme_Base # Lo [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI
+0A13..0A28 ; Grapheme_Base # Lo [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA
+0A2A..0A30 ; Grapheme_Base # Lo [7] GURMUKHI LETTER PA..GURMUKHI LETTER RA
+0A32..0A33 ; Grapheme_Base # Lo [2] GURMUKHI LETTER LA..GURMUKHI LETTER LLA
+0A35..0A36 ; Grapheme_Base # Lo [2] GURMUKHI LETTER VA..GURMUKHI LETTER SHA
+0A38..0A39 ; Grapheme_Base # Lo [2] GURMUKHI LETTER SA..GURMUKHI LETTER HA
+0A3E..0A40 ; Grapheme_Base # Mc [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II
+0A59..0A5C ; Grapheme_Base # Lo [4] GURMUKHI LETTER KHHA..GURMUKHI LETTER RRA
+0A5E ; Grapheme_Base # Lo GURMUKHI LETTER FA
+0A66..0A6F ; Grapheme_Base # Nd [10] GURMUKHI DIGIT ZERO..GURMUKHI DIGIT NINE
+0A72..0A74 ; Grapheme_Base # Lo [3] GURMUKHI IRI..GURMUKHI EK ONKAR
+0A76 ; Grapheme_Base # Po GURMUKHI ABBREVIATION SIGN
+0A83 ; Grapheme_Base # Mc GUJARATI SIGN VISARGA
+0A85..0A8D ; Grapheme_Base # Lo [9] GUJARATI LETTER A..GUJARATI VOWEL CANDRA E
+0A8F..0A91 ; Grapheme_Base # Lo [3] GUJARATI LETTER E..GUJARATI VOWEL CANDRA O
+0A93..0AA8 ; Grapheme_Base # Lo [22] GUJARATI LETTER O..GUJARATI LETTER NA
+0AAA..0AB0 ; Grapheme_Base # Lo [7] GUJARATI LETTER PA..GUJARATI LETTER RA
+0AB2..0AB3 ; Grapheme_Base # Lo [2] GUJARATI LETTER LA..GUJARATI LETTER LLA
+0AB5..0AB9 ; Grapheme_Base # Lo [5] GUJARATI LETTER VA..GUJARATI LETTER HA
+0ABD ; Grapheme_Base # Lo GUJARATI SIGN AVAGRAHA
+0ABE..0AC0 ; Grapheme_Base # Mc [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II
+0AC9 ; Grapheme_Base # Mc GUJARATI VOWEL SIGN CANDRA O
+0ACB..0ACC ; Grapheme_Base # Mc [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU
+0AD0 ; Grapheme_Base # Lo GUJARATI OM
+0AE0..0AE1 ; Grapheme_Base # Lo [2] GUJARATI LETTER VOCALIC RR..GUJARATI LETTER VOCALIC LL
+0AE6..0AEF ; Grapheme_Base # Nd [10] GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE
+0AF0 ; Grapheme_Base # Po GUJARATI ABBREVIATION SIGN
+0AF1 ; Grapheme_Base # Sc GUJARATI RUPEE SIGN
+0AF9 ; Grapheme_Base # Lo GUJARATI LETTER ZHA
+0B02..0B03 ; Grapheme_Base # Mc [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA
+0B05..0B0C ; Grapheme_Base # Lo [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L
+0B0F..0B10 ; Grapheme_Base # Lo [2] ORIYA LETTER E..ORIYA LETTER AI
+0B13..0B28 ; Grapheme_Base # Lo [22] ORIYA LETTER O..ORIYA LETTER NA
+0B2A..0B30 ; Grapheme_Base # Lo [7] ORIYA LETTER PA..ORIYA LETTER RA
+0B32..0B33 ; Grapheme_Base # Lo [2] ORIYA LETTER LA..ORIYA LETTER LLA
+0B35..0B39 ; Grapheme_Base # Lo [5] ORIYA LETTER VA..ORIYA LETTER HA
+0B3D ; Grapheme_Base # Lo ORIYA SIGN AVAGRAHA
+0B40 ; Grapheme_Base # Mc ORIYA VOWEL SIGN II
+0B47..0B48 ; Grapheme_Base # Mc [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI
+0B4B..0B4C ; Grapheme_Base # Mc [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU
+0B5C..0B5D ; Grapheme_Base # Lo [2] ORIYA LETTER RRA..ORIYA LETTER RHA
+0B5F..0B61 ; Grapheme_Base # Lo [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL
+0B66..0B6F ; Grapheme_Base # Nd [10] ORIYA DIGIT ZERO..ORIYA DIGIT NINE
+0B70 ; Grapheme_Base # So ORIYA ISSHAR
+0B71 ; Grapheme_Base # Lo ORIYA LETTER WA
+0B72..0B77 ; Grapheme_Base # No [6] ORIYA FRACTION ONE QUARTER..ORIYA FRACTION THREE SIXTEENTHS
+0B83 ; Grapheme_Base # Lo TAMIL SIGN VISARGA
+0B85..0B8A ; Grapheme_Base # Lo [6] TAMIL LETTER A..TAMIL LETTER UU
+0B8E..0B90 ; Grapheme_Base # Lo [3] TAMIL LETTER E..TAMIL LETTER AI
+0B92..0B95 ; Grapheme_Base # Lo [4] TAMIL LETTER O..TAMIL LETTER KA
+0B99..0B9A ; Grapheme_Base # Lo [2] TAMIL LETTER NGA..TAMIL LETTER CA
+0B9C ; Grapheme_Base # Lo TAMIL LETTER JA
+0B9E..0B9F ; Grapheme_Base # Lo [2] TAMIL LETTER NYA..TAMIL LETTER TTA
+0BA3..0BA4 ; Grapheme_Base # Lo [2] TAMIL LETTER NNA..TAMIL LETTER TA
+0BA8..0BAA ; Grapheme_Base # Lo [3] TAMIL LETTER NA..TAMIL LETTER PA
+0BAE..0BB9 ; Grapheme_Base # Lo [12] TAMIL LETTER MA..TAMIL LETTER HA
+0BBF ; Grapheme_Base # Mc TAMIL VOWEL SIGN I
+0BC1..0BC2 ; Grapheme_Base # Mc [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU
+0BC6..0BC8 ; Grapheme_Base # Mc [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI
+0BCA..0BCC ; Grapheme_Base # Mc [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU
+0BD0 ; Grapheme_Base # Lo TAMIL OM
+0BE6..0BEF ; Grapheme_Base # Nd [10] TAMIL DIGIT ZERO..TAMIL DIGIT NINE
+0BF0..0BF2 ; Grapheme_Base # No [3] TAMIL NUMBER TEN..TAMIL NUMBER ONE THOUSAND
+0BF3..0BF8 ; Grapheme_Base # So [6] TAMIL DAY SIGN..TAMIL AS ABOVE SIGN
+0BF9 ; Grapheme_Base # Sc TAMIL RUPEE SIGN
+0BFA ; Grapheme_Base # So TAMIL NUMBER SIGN
+0C01..0C03 ; Grapheme_Base # Mc [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA
+0C05..0C0C ; Grapheme_Base # Lo [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L
+0C0E..0C10 ; Grapheme_Base # Lo [3] TELUGU LETTER E..TELUGU LETTER AI
+0C12..0C28 ; Grapheme_Base # Lo [23] TELUGU LETTER O..TELUGU LETTER NA
+0C2A..0C39 ; Grapheme_Base # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA
+0C3D ; Grapheme_Base # Lo TELUGU SIGN AVAGRAHA
+0C41..0C44 ; Grapheme_Base # Mc [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR
+0C58..0C5A ; Grapheme_Base # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA
+0C5D ; Grapheme_Base # Lo TELUGU LETTER NAKAARA POLLU
+0C60..0C61 ; Grapheme_Base # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL
+0C66..0C6F ; Grapheme_Base # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE
+0C77 ; Grapheme_Base # Po TELUGU SIGN SIDDHAM
+0C78..0C7E ; Grapheme_Base # No [7] TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR..TELUGU FRACTION DIGIT THREE FOR EVEN POWERS OF FOUR
+0C7F ; Grapheme_Base # So TELUGU SIGN TUUMU
+0C80 ; Grapheme_Base # Lo KANNADA SIGN SPACING CANDRABINDU
+0C82..0C83 ; Grapheme_Base # Mc [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA
+0C84 ; Grapheme_Base # Po KANNADA SIGN SIDDHAM
+0C85..0C8C ; Grapheme_Base # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L
+0C8E..0C90 ; Grapheme_Base # Lo [3] KANNADA LETTER E..KANNADA LETTER AI
+0C92..0CA8 ; Grapheme_Base # Lo [23] KANNADA LETTER O..KANNADA LETTER NA
+0CAA..0CB3 ; Grapheme_Base # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA
+0CB5..0CB9 ; Grapheme_Base # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA
+0CBD ; Grapheme_Base # Lo KANNADA SIGN AVAGRAHA
+0CBE ; Grapheme_Base # Mc KANNADA VOWEL SIGN AA
+0CC1 ; Grapheme_Base # Mc KANNADA VOWEL SIGN U
+0CC3..0CC4 ; Grapheme_Base # Mc [2] KANNADA VOWEL SIGN VOCALIC R..KANNADA VOWEL SIGN VOCALIC RR
+0CDD..0CDE ; Grapheme_Base # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA
+0CE0..0CE1 ; Grapheme_Base # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL
+0CE6..0CEF ; Grapheme_Base # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE
+0CF1..0CF2 ; Grapheme_Base # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA
+0CF3 ; Grapheme_Base # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT
+0D02..0D03 ; Grapheme_Base # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA
+0D04..0D0C ; Grapheme_Base # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L
+0D0E..0D10 ; Grapheme_Base # Lo [3] MALAYALAM LETTER E..MALAYALAM LETTER AI
+0D12..0D3A ; Grapheme_Base # Lo [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA
+0D3D ; Grapheme_Base # Lo MALAYALAM SIGN AVAGRAHA
+0D3F..0D40 ; Grapheme_Base # Mc [2] MALAYALAM VOWEL SIGN I..MALAYALAM VOWEL SIGN II
+0D46..0D48 ; Grapheme_Base # Mc [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI
+0D4A..0D4C ; Grapheme_Base # Mc [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU
+0D4E ; Grapheme_Base # Lo MALAYALAM LETTER DOT REPH
+0D4F ; Grapheme_Base # So MALAYALAM SIGN PARA
+0D54..0D56 ; Grapheme_Base # Lo [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL
+0D58..0D5E ; Grapheme_Base # No [7] MALAYALAM FRACTION ONE ONE-HUNDRED-AND-SIXTIETH..MALAYALAM FRACTION ONE FIFTH
+0D5F..0D61 ; Grapheme_Base # Lo [3] MALAYALAM LETTER ARCHAIC II..MALAYALAM LETTER VOCALIC LL
+0D66..0D6F ; Grapheme_Base # Nd [10] MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE
+0D70..0D78 ; Grapheme_Base # No [9] MALAYALAM NUMBER TEN..MALAYALAM FRACTION THREE SIXTEENTHS
+0D79 ; Grapheme_Base # So MALAYALAM DATE MARK
+0D7A..0D7F ; Grapheme_Base # Lo [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K
+0D82..0D83 ; Grapheme_Base # Mc [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA
+0D85..0D96 ; Grapheme_Base # Lo [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA
+0D9A..0DB1 ; Grapheme_Base # Lo [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA
+0DB3..0DBB ; Grapheme_Base # Lo [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA
+0DBD ; Grapheme_Base # Lo SINHALA LETTER DANTAJA LAYANNA
+0DC0..0DC6 ; Grapheme_Base # Lo [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA
+0DD0..0DD1 ; Grapheme_Base # Mc [2] SINHALA VOWEL SIGN KETTI AEDA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA
+0DD8..0DDE ; Grapheme_Base # Mc [7] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN KOMBUVA HAA GAYANUKITTA
+0DE6..0DEF ; Grapheme_Base # Nd [10] SINHALA LITH DIGIT ZERO..SINHALA LITH DIGIT NINE
+0DF2..0DF3 ; Grapheme_Base # Mc [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA
+0DF4 ; Grapheme_Base # Po SINHALA PUNCTUATION KUNDDALIYA
+0E01..0E30 ; Grapheme_Base # Lo [48] THAI CHARACTER KO KAI..THAI CHARACTER SARA A
+0E32..0E33 ; Grapheme_Base # Lo [2] THAI CHARACTER SARA AA..THAI CHARACTER SARA AM
+0E3F ; Grapheme_Base # Sc THAI CURRENCY SYMBOL BAHT
+0E40..0E45 ; Grapheme_Base # Lo [6] THAI CHARACTER SARA E..THAI CHARACTER LAKKHANGYAO
+0E46 ; Grapheme_Base # Lm THAI CHARACTER MAIYAMOK
+0E4F ; Grapheme_Base # Po THAI CHARACTER FONGMAN
+0E50..0E59 ; Grapheme_Base # Nd [10] THAI DIGIT ZERO..THAI DIGIT NINE
+0E5A..0E5B ; Grapheme_Base # Po [2] THAI CHARACTER ANGKHANKHU..THAI CHARACTER KHOMUT
+0E81..0E82 ; Grapheme_Base # Lo [2] LAO LETTER KO..LAO LETTER KHO SUNG
+0E84 ; Grapheme_Base # Lo LAO LETTER KHO TAM
+0E86..0E8A ; Grapheme_Base # Lo [5] LAO LETTER PALI GHA..LAO LETTER SO TAM
+0E8C..0EA3 ; Grapheme_Base # Lo [24] LAO LETTER PALI JHA..LAO LETTER LO LING
+0EA5 ; Grapheme_Base # Lo LAO LETTER LO LOOT
+0EA7..0EB0 ; Grapheme_Base # Lo [10] LAO LETTER WO..LAO VOWEL SIGN A
+0EB2..0EB3 ; Grapheme_Base # Lo [2] LAO VOWEL SIGN AA..LAO VOWEL SIGN AM
+0EBD ; Grapheme_Base # Lo LAO SEMIVOWEL SIGN NYO
+0EC0..0EC4 ; Grapheme_Base # Lo [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI
+0EC6 ; Grapheme_Base # Lm LAO KO LA
+0ED0..0ED9 ; Grapheme_Base # Nd [10] LAO DIGIT ZERO..LAO DIGIT NINE
+0EDC..0EDF ; Grapheme_Base # Lo [4] LAO HO NO..LAO LETTER KHMU NYO
+0F00 ; Grapheme_Base # Lo TIBETAN SYLLABLE OM
+0F01..0F03 ; Grapheme_Base # So [3] TIBETAN MARK GTER YIG MGO TRUNCATED A..TIBETAN MARK GTER YIG MGO -UM GTER TSHEG MA
+0F04..0F12 ; Grapheme_Base # Po [15] TIBETAN MARK INITIAL YIG MGO MDUN MA..TIBETAN MARK RGYA GRAM SHAD
+0F13 ; Grapheme_Base # So TIBETAN MARK CARET -DZUD RTAGS ME LONG CAN
+0F14 ; Grapheme_Base # Po TIBETAN MARK GTER TSHEG
+0F15..0F17 ; Grapheme_Base # So [3] TIBETAN LOGOTYPE SIGN CHAD RTAGS..TIBETAN ASTROLOGICAL SIGN SGRA GCAN -CHAR RTAGS
+0F1A..0F1F ; Grapheme_Base # So [6] TIBETAN SIGN RDEL DKAR GCIG..TIBETAN SIGN RDEL DKAR RDEL NAG
+0F20..0F29 ; Grapheme_Base # Nd [10] TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE
+0F2A..0F33 ; Grapheme_Base # No [10] TIBETAN DIGIT HALF ONE..TIBETAN DIGIT HALF ZERO
+0F34 ; Grapheme_Base # So TIBETAN MARK BSDUS RTAGS
+0F36 ; Grapheme_Base # So TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN
+0F38 ; Grapheme_Base # So TIBETAN MARK CHE MGO
+0F3A ; Grapheme_Base # Ps TIBETAN MARK GUG RTAGS GYON
+0F3B ; Grapheme_Base # Pe TIBETAN MARK GUG RTAGS GYAS
+0F3C ; Grapheme_Base # Ps TIBETAN MARK ANG KHANG GYON
+0F3D ; Grapheme_Base # Pe TIBETAN MARK ANG KHANG GYAS
+0F3E..0F3F ; Grapheme_Base # Mc [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES
+0F40..0F47 ; Grapheme_Base # Lo [8] TIBETAN LETTER KA..TIBETAN LETTER JA
+0F49..0F6C ; Grapheme_Base # Lo [36] TIBETAN LETTER NYA..TIBETAN LETTER RRA
+0F7F ; Grapheme_Base # Mc TIBETAN SIGN RNAM BCAD
+0F85 ; Grapheme_Base # Po TIBETAN MARK PALUTA
+0F88..0F8C ; Grapheme_Base # Lo [5] TIBETAN SIGN LCE TSA CAN..TIBETAN SIGN INVERTED MCHU CAN
+0FBE..0FC5 ; Grapheme_Base # So [8] TIBETAN KU RU KHA..TIBETAN SYMBOL RDO RJE
+0FC7..0FCC ; Grapheme_Base # So [6] TIBETAN SYMBOL RDO RJE RGYA GRAM..TIBETAN SYMBOL NOR BU BZHI -KHYIL
+0FCE..0FCF ; Grapheme_Base # So [2] TIBETAN SIGN RDEL NAG RDEL DKAR..TIBETAN SIGN RDEL NAG GSUM
+0FD0..0FD4 ; Grapheme_Base # Po [5] TIBETAN MARK BSKA- SHOG GI MGO RGYAN..TIBETAN MARK CLOSING BRDA RNYING YIG MGO SGAB MA
+0FD5..0FD8 ; Grapheme_Base # So [4] RIGHT-FACING SVASTI SIGN..LEFT-FACING SVASTI SIGN WITH DOTS
+0FD9..0FDA ; Grapheme_Base # Po [2] TIBETAN MARK LEADING MCHAN RTAGS..TIBETAN MARK TRAILING MCHAN RTAGS
+1000..102A ; Grapheme_Base # Lo [43] MYANMAR LETTER KA..MYANMAR LETTER AU
+102B..102C ; Grapheme_Base # Mc [2] MYANMAR VOWEL SIGN TALL AA..MYANMAR VOWEL SIGN AA
+1031 ; Grapheme_Base # Mc MYANMAR VOWEL SIGN E
+1038 ; Grapheme_Base # Mc MYANMAR SIGN VISARGA
+103B..103C ; Grapheme_Base # Mc [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA
+103F ; Grapheme_Base # Lo MYANMAR LETTER GREAT SA
+1040..1049 ; Grapheme_Base # Nd [10] MYANMAR DIGIT ZERO..MYANMAR DIGIT NINE
+104A..104F ; Grapheme_Base # Po [6] MYANMAR SIGN LITTLE SECTION..MYANMAR SYMBOL GENITIVE
+1050..1055 ; Grapheme_Base # Lo [6] MYANMAR LETTER SHA..MYANMAR LETTER VOCALIC LL
+1056..1057 ; Grapheme_Base # Mc [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR
+105A..105D ; Grapheme_Base # Lo [4] MYANMAR LETTER MON NGA..MYANMAR LETTER MON BBE
+1061 ; Grapheme_Base # Lo MYANMAR LETTER SGAW KAREN SHA
+1062..1064 ; Grapheme_Base # Mc [3] MYANMAR VOWEL SIGN SGAW KAREN EU..MYANMAR TONE MARK SGAW KAREN KE PHO
+1065..1066 ; Grapheme_Base # Lo [2] MYANMAR LETTER WESTERN PWO KAREN THA..MYANMAR LETTER WESTERN PWO KAREN PWA
+1067..106D ; Grapheme_Base # Mc [7] MYANMAR VOWEL SIGN WESTERN PWO KAREN EU..MYANMAR SIGN WESTERN PWO KAREN TONE-5
+106E..1070 ; Grapheme_Base # Lo [3] MYANMAR LETTER EASTERN PWO KAREN NNA..MYANMAR LETTER EASTERN PWO KAREN GHWA
+1075..1081 ; Grapheme_Base # Lo [13] MYANMAR LETTER SHAN KA..MYANMAR LETTER SHAN HA
+1083..1084 ; Grapheme_Base # Mc [2] MYANMAR VOWEL SIGN SHAN AA..MYANMAR VOWEL SIGN SHAN E
+1087..108C ; Grapheme_Base # Mc [6] MYANMAR SIGN SHAN TONE-2..MYANMAR SIGN SHAN COUNCIL TONE-3
+108E ; Grapheme_Base # Lo MYANMAR LETTER RUMAI PALAUNG FA
+108F ; Grapheme_Base # Mc MYANMAR SIGN RUMAI PALAUNG TONE-5
+1090..1099 ; Grapheme_Base # Nd [10] MYANMAR SHAN DIGIT ZERO..MYANMAR SHAN DIGIT NINE
+109A..109C ; Grapheme_Base # Mc [3] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON A
+109E..109F ; Grapheme_Base # So [2] MYANMAR SYMBOL SHAN ONE..MYANMAR SYMBOL SHAN EXCLAMATION
+10A0..10C5 ; Grapheme_Base # L& [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE
+10C7 ; Grapheme_Base # L& GEORGIAN CAPITAL LETTER YN
+10CD ; Grapheme_Base # L& GEORGIAN CAPITAL LETTER AEN
+10D0..10FA ; Grapheme_Base # L& [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN
+10FB ; Grapheme_Base # Po GEORGIAN PARAGRAPH SEPARATOR
+10FC ; Grapheme_Base # Lm MODIFIER LETTER GEORGIAN NAR
+10FD..10FF ; Grapheme_Base # L& [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN
+1100..1248 ; Grapheme_Base # Lo [329] HANGUL CHOSEONG KIYEOK..ETHIOPIC SYLLABLE QWA
+124A..124D ; Grapheme_Base # Lo [4] ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE
+1250..1256 ; Grapheme_Base # Lo [7] ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO
+1258 ; Grapheme_Base # Lo ETHIOPIC SYLLABLE QHWA
+125A..125D ; Grapheme_Base # Lo [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE
+1260..1288 ; Grapheme_Base # Lo [41] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA
+128A..128D ; Grapheme_Base # Lo [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE
+1290..12B0 ; Grapheme_Base # Lo [33] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA
+12B2..12B5 ; Grapheme_Base # Lo [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE
+12B8..12BE ; Grapheme_Base # Lo [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO
+12C0 ; Grapheme_Base # Lo ETHIOPIC SYLLABLE KXWA
+12C2..12C5 ; Grapheme_Base # Lo [4] ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE
+12C8..12D6 ; Grapheme_Base # Lo [15] ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O
+12D8..1310 ; Grapheme_Base # Lo [57] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA
+1312..1315 ; Grapheme_Base # Lo [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE
+1318..135A ; Grapheme_Base # Lo [67] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA
+1360..1368 ; Grapheme_Base # Po [9] ETHIOPIC SECTION MARK..ETHIOPIC PARAGRAPH SEPARATOR
+1369..137C ; Grapheme_Base # No [20] ETHIOPIC DIGIT ONE..ETHIOPIC NUMBER TEN THOUSAND
+1380..138F ; Grapheme_Base # Lo [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE
+1390..1399 ; Grapheme_Base # So [10] ETHIOPIC TONAL MARK YIZET..ETHIOPIC TONAL MARK KURT
+13A0..13F5 ; Grapheme_Base # L& [86] CHEROKEE LETTER A..CHEROKEE LETTER MV
+13F8..13FD ; Grapheme_Base # L& [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV
+1400 ; Grapheme_Base # Pd CANADIAN SYLLABICS HYPHEN
+1401..166C ; Grapheme_Base # Lo [620] CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA
+166D ; Grapheme_Base # So CANADIAN SYLLABICS CHI SIGN
+166E ; Grapheme_Base # Po CANADIAN SYLLABICS FULL STOP
+166F..167F ; Grapheme_Base # Lo [17] CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W
+1680 ; Grapheme_Base # Zs OGHAM SPACE MARK
+1681..169A ; Grapheme_Base # Lo [26] OGHAM LETTER BEITH..OGHAM LETTER PEITH
+169B ; Grapheme_Base # Ps OGHAM FEATHER MARK
+169C ; Grapheme_Base # Pe OGHAM REVERSED FEATHER MARK
+16A0..16EA ; Grapheme_Base # Lo [75] RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X
+16EB..16ED ; Grapheme_Base # Po [3] RUNIC SINGLE PUNCTUATION..RUNIC CROSS PUNCTUATION
+16EE..16F0 ; Grapheme_Base # Nl [3] RUNIC ARLAUG SYMBOL..RUNIC BELGTHOR SYMBOL
+16F1..16F8 ; Grapheme_Base # Lo [8] RUNIC LETTER K..RUNIC LETTER FRANKS CASKET AESC
+1700..1711 ; Grapheme_Base # Lo [18] TAGALOG LETTER A..TAGALOG LETTER HA
+171F..1731 ; Grapheme_Base # Lo [19] TAGALOG LETTER ARCHAIC RA..HANUNOO LETTER HA
+1735..1736 ; Grapheme_Base # Po [2] PHILIPPINE SINGLE PUNCTUATION..PHILIPPINE DOUBLE PUNCTUATION
+1740..1751 ; Grapheme_Base # Lo [18] BUHID LETTER A..BUHID LETTER HA
+1760..176C ; Grapheme_Base # Lo [13] TAGBANWA LETTER A..TAGBANWA LETTER YA
+176E..1770 ; Grapheme_Base # Lo [3] TAGBANWA LETTER LA..TAGBANWA LETTER SA
+1780..17B3 ; Grapheme_Base # Lo [52] KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU
+17B6 ; Grapheme_Base # Mc KHMER VOWEL SIGN AA
+17BE..17C5 ; Grapheme_Base # Mc [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU
+17C7..17C8 ; Grapheme_Base # Mc [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU
+17D4..17D6 ; Grapheme_Base # Po [3] KHMER SIGN KHAN..KHMER SIGN CAMNUC PII KUUH
+17D7 ; Grapheme_Base # Lm KHMER SIGN LEK TOO
+17D8..17DA ; Grapheme_Base # Po [3] KHMER SIGN BEYYAL..KHMER SIGN KOOMUUT
+17DB ; Grapheme_Base # Sc KHMER CURRENCY SYMBOL RIEL
+17DC ; Grapheme_Base # Lo KHMER SIGN AVAKRAHASANYA
+17E0..17E9 ; Grapheme_Base # Nd [10] KHMER DIGIT ZERO..KHMER DIGIT NINE
+17F0..17F9 ; Grapheme_Base # No [10] KHMER SYMBOL LEK ATTAK SON..KHMER SYMBOL LEK ATTAK PRAM-BUON
+1800..1805 ; Grapheme_Base # Po [6] MONGOLIAN BIRGA..MONGOLIAN FOUR DOTS
+1806 ; Grapheme_Base # Pd MONGOLIAN TODO SOFT HYPHEN
+1807..180A ; Grapheme_Base # Po [4] MONGOLIAN SIBE SYLLABLE BOUNDARY MARKER..MONGOLIAN NIRUGU
+1810..1819 ; Grapheme_Base # Nd [10] MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE
+1820..1842 ; Grapheme_Base # Lo [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI
+1843 ; Grapheme_Base # Lm MONGOLIAN LETTER TODO LONG VOWEL SIGN
+1844..1878 ; Grapheme_Base # Lo [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS
+1880..1884 ; Grapheme_Base # Lo [5] MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER ALI GALI INVERTED UBADAMA
+1887..18A8 ; Grapheme_Base # Lo [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA
+18AA ; Grapheme_Base # Lo MONGOLIAN LETTER MANCHU ALI GALI LHA
+18B0..18F5 ; Grapheme_Base # Lo [70] CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S
+1900..191E ; Grapheme_Base # Lo [31] LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER TRA
+1923..1926 ; Grapheme_Base # Mc [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU
+1929..192B ; Grapheme_Base # Mc [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA
+1930..1931 ; Grapheme_Base # Mc [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA
+1933..1938 ; Grapheme_Base # Mc [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA
+1940 ; Grapheme_Base # So LIMBU SIGN LOO
+1944..1945 ; Grapheme_Base # Po [2] LIMBU EXCLAMATION MARK..LIMBU QUESTION MARK
+1946..194F ; Grapheme_Base # Nd [10] LIMBU DIGIT ZERO..LIMBU DIGIT NINE
+1950..196D ; Grapheme_Base # Lo [30] TAI LE LETTER KA..TAI LE LETTER AI
+1970..1974 ; Grapheme_Base # Lo [5] TAI LE LETTER TONE-2..TAI LE LETTER TONE-6
+1980..19AB ; Grapheme_Base # Lo [44] NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA
+19B0..19C9 ; Grapheme_Base # Lo [26] NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2
+19D0..19D9 ; Grapheme_Base # Nd [10] NEW TAI LUE DIGIT ZERO..NEW TAI LUE DIGIT NINE
+19DA ; Grapheme_Base # No NEW TAI LUE THAM DIGIT ONE
+19DE..19FF ; Grapheme_Base # So [34] NEW TAI LUE SIGN LAE..KHMER SYMBOL DAP-PRAM ROC
+1A00..1A16 ; Grapheme_Base # Lo [23] BUGINESE LETTER KA..BUGINESE LETTER HA
+1A19..1A1A ; Grapheme_Base # Mc [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O
+1A1E..1A1F ; Grapheme_Base # Po [2] BUGINESE PALLAWA..BUGINESE END OF SECTION
+1A20..1A54 ; Grapheme_Base # Lo [53] TAI THAM LETTER HIGH KA..TAI THAM LETTER GREAT SA
+1A55 ; Grapheme_Base # Mc TAI THAM CONSONANT SIGN MEDIAL RA
+1A57 ; Grapheme_Base # Mc TAI THAM CONSONANT SIGN LA TANG LAI
+1A61 ; Grapheme_Base # Mc TAI THAM VOWEL SIGN A
+1A63..1A64 ; Grapheme_Base # Mc [2] TAI THAM VOWEL SIGN AA..TAI THAM VOWEL SIGN TALL AA
+1A6D..1A72 ; Grapheme_Base # Mc [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI
+1A80..1A89 ; Grapheme_Base # Nd [10] TAI THAM HORA DIGIT ZERO..TAI THAM HORA DIGIT NINE
+1A90..1A99 ; Grapheme_Base # Nd [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE
+1AA0..1AA6 ; Grapheme_Base # Po [7] TAI THAM SIGN WIANG..TAI THAM SIGN REVERSED ROTATED RANA
+1AA7 ; Grapheme_Base # Lm TAI THAM SIGN MAI YAMOK
+1AA8..1AAD ; Grapheme_Base # Po [6] TAI THAM SIGN KAAN..TAI THAM SIGN CAANG
+1B04 ; Grapheme_Base # Mc BALINESE SIGN BISAH
+1B05..1B33 ; Grapheme_Base # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA
+1B3E..1B41 ; Grapheme_Base # Mc [4] BALINESE VOWEL SIGN TALING..BALINESE VOWEL SIGN TALING REPA TEDUNG
+1B45..1B4C ; Grapheme_Base # Lo [8] BALINESE LETTER KAF SASAK..BALINESE LETTER ARCHAIC JNYA
+1B4E..1B4F ; Grapheme_Base # Po [2] BALINESE INVERTED CARIK SIKI..BALINESE INVERTED CARIK PAREREN
+1B50..1B59 ; Grapheme_Base # Nd [10] BALINESE DIGIT ZERO..BALINESE DIGIT NINE
+1B5A..1B60 ; Grapheme_Base # Po [7] BALINESE PANTI..BALINESE PAMENENG
+1B61..1B6A ; Grapheme_Base # So [10] BALINESE MUSICAL SYMBOL DONG..BALINESE MUSICAL SYMBOL DANG GEDE
+1B74..1B7C ; Grapheme_Base # So [9] BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DUG..BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PING
+1B7D..1B7F ; Grapheme_Base # Po [3] BALINESE PANTI LANTANG..BALINESE PANTI BAWAK
+1B82 ; Grapheme_Base # Mc SUNDANESE SIGN PANGWISAD
+1B83..1BA0 ; Grapheme_Base # Lo [30] SUNDANESE LETTER A..SUNDANESE LETTER HA
+1BA1 ; Grapheme_Base # Mc SUNDANESE CONSONANT SIGN PAMINGKAL
+1BA6..1BA7 ; Grapheme_Base # Mc [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG
+1BAE..1BAF ; Grapheme_Base # Lo [2] SUNDANESE LETTER KHA..SUNDANESE LETTER SYA
+1BB0..1BB9 ; Grapheme_Base # Nd [10] SUNDANESE DIGIT ZERO..SUNDANESE DIGIT NINE
+1BBA..1BE5 ; Grapheme_Base # Lo [44] SUNDANESE AVAGRAHA..BATAK LETTER U
+1BE7 ; Grapheme_Base # Mc BATAK VOWEL SIGN E
+1BEA..1BEC ; Grapheme_Base # Mc [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O
+1BEE ; Grapheme_Base # Mc BATAK VOWEL SIGN U
+1BFC..1BFF ; Grapheme_Base # Po [4] BATAK SYMBOL BINDU NA METEK..BATAK SYMBOL BINDU PANGOLAT
+1C00..1C23 ; Grapheme_Base # Lo [36] LEPCHA LETTER KA..LEPCHA LETTER A
+1C24..1C2B ; Grapheme_Base # Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU
+1C34..1C35 ; Grapheme_Base # Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG
+1C3B..1C3F ; Grapheme_Base # Po [5] LEPCHA PUNCTUATION TA-ROL..LEPCHA PUNCTUATION TSHOOK
+1C40..1C49 ; Grapheme_Base # Nd [10] LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE
+1C4D..1C4F ; Grapheme_Base # Lo [3] LEPCHA LETTER TTA..LEPCHA LETTER DDA
+1C50..1C59 ; Grapheme_Base # Nd [10] OL CHIKI DIGIT ZERO..OL CHIKI DIGIT NINE
+1C5A..1C77 ; Grapheme_Base # Lo [30] OL CHIKI LETTER LA..OL CHIKI LETTER OH
+1C78..1C7D ; Grapheme_Base # Lm [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD
+1C7E..1C7F ; Grapheme_Base # Po [2] OL CHIKI PUNCTUATION MUCAAD..OL CHIKI PUNCTUATION DOUBLE MUCAAD
+1C80..1C8A ; Grapheme_Base # L& [11] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER TJE
+1C90..1CBA ; Grapheme_Base # L& [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN
+1CBD..1CBF ; Grapheme_Base # L& [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
+1CC0..1CC7 ; Grapheme_Base # Po [8] SUNDANESE PUNCTUATION BINDU SURYA..SUNDANESE PUNCTUATION BINDU BA SATANGA
+1CD3 ; Grapheme_Base # Po VEDIC SIGN NIHSHVASA
+1CE1 ; Grapheme_Base # Mc VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA
+1CE9..1CEC ; Grapheme_Base # Lo [4] VEDIC SIGN ANUSVARA ANTARGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL
+1CEE..1CF3 ; Grapheme_Base # Lo [6] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ROTATED ARDHAVISARGA
+1CF5..1CF6 ; Grapheme_Base # Lo [2] VEDIC SIGN JIHVAMULIYA..VEDIC SIGN UPADHMANIYA
+1CF7 ; Grapheme_Base # Mc VEDIC SIGN ATIKRAMA
+1CFA ; Grapheme_Base # Lo VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA
+1D00..1D2B ; Grapheme_Base # L& [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL
+1D2C..1D6A ; Grapheme_Base # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI
+1D6B..1D77 ; Grapheme_Base # L& [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G
+1D78 ; Grapheme_Base # Lm MODIFIER LETTER CYRILLIC EN
+1D79..1D9A ; Grapheme_Base # L& [34] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK
+1D9B..1DBF ; Grapheme_Base # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA
+1E00..1F15 ; Grapheme_Base # L& [278] LATIN CAPITAL LETTER A WITH RING BELOW..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA
+1F18..1F1D ; Grapheme_Base # L& [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F20..1F45 ; Grapheme_Base # L& [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA
+1F48..1F4D ; Grapheme_Base # L& [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F50..1F57 ; Grapheme_Base # L& [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F59 ; Grapheme_Base # L& GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B ; Grapheme_Base # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D ; Grapheme_Base # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F..1F7D ; Grapheme_Base # L& [31] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER OMEGA WITH OXIA
+1F80..1FB4 ; Grapheme_Base # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6..1FBC ; Grapheme_Base # L& [7] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBD ; Grapheme_Base # Sk GREEK KORONIS
+1FBE ; Grapheme_Base # L& GREEK PROSGEGRAMMENI
+1FBF..1FC1 ; Grapheme_Base # Sk [3] GREEK PSILI..GREEK DIALYTIKA AND PERISPOMENI
+1FC2..1FC4 ; Grapheme_Base # L& [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6..1FCC ; Grapheme_Base # L& [7] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FCD..1FCF ; Grapheme_Base # Sk [3] GREEK PSILI AND VARIA..GREEK PSILI AND PERISPOMENI
+1FD0..1FD3 ; Grapheme_Base # L& [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6..1FDB ; Grapheme_Base # L& [6] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH OXIA
+1FDD..1FDF ; Grapheme_Base # Sk [3] GREEK DASIA AND VARIA..GREEK DASIA AND PERISPOMENI
+1FE0..1FEC ; Grapheme_Base # L& [13] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA
+1FED..1FEF ; Grapheme_Base # Sk [3] GREEK DIALYTIKA AND VARIA..GREEK VARIA
+1FF2..1FF4 ; Grapheme_Base # L& [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6..1FFC ; Grapheme_Base # L& [7] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+1FFD..1FFE ; Grapheme_Base # Sk [2] GREEK OXIA..GREEK DASIA
+2000..200A ; Grapheme_Base # Zs [11] EN QUAD..HAIR SPACE
+2010..2015 ; Grapheme_Base # Pd [6] HYPHEN..HORIZONTAL BAR
+2016..2017 ; Grapheme_Base # Po [2] DOUBLE VERTICAL LINE..DOUBLE LOW LINE
+2018 ; Grapheme_Base # Pi LEFT SINGLE QUOTATION MARK
+2019 ; Grapheme_Base # Pf RIGHT SINGLE QUOTATION MARK
+201A ; Grapheme_Base # Ps SINGLE LOW-9 QUOTATION MARK
+201B..201C ; Grapheme_Base # Pi [2] SINGLE HIGH-REVERSED-9 QUOTATION MARK..LEFT DOUBLE QUOTATION MARK
+201D ; Grapheme_Base # Pf RIGHT DOUBLE QUOTATION MARK
+201E ; Grapheme_Base # Ps DOUBLE LOW-9 QUOTATION MARK
+201F ; Grapheme_Base # Pi DOUBLE HIGH-REVERSED-9 QUOTATION MARK
+2020..2027 ; Grapheme_Base # Po [8] DAGGER..HYPHENATION POINT
+202F ; Grapheme_Base # Zs NARROW NO-BREAK SPACE
+2030..2038 ; Grapheme_Base # Po [9] PER MILLE SIGN..CARET
+2039 ; Grapheme_Base # Pi SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+203A ; Grapheme_Base # Pf SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+203B..203E ; Grapheme_Base # Po [4] REFERENCE MARK..OVERLINE
+203F..2040 ; Grapheme_Base # Pc [2] UNDERTIE..CHARACTER TIE
+2041..2043 ; Grapheme_Base # Po [3] CARET INSERTION POINT..HYPHEN BULLET
+2044 ; Grapheme_Base # Sm FRACTION SLASH
+2045 ; Grapheme_Base # Ps LEFT SQUARE BRACKET WITH QUILL
+2046 ; Grapheme_Base # Pe RIGHT SQUARE BRACKET WITH QUILL
+2047..2051 ; Grapheme_Base # Po [11] DOUBLE QUESTION MARK..TWO ASTERISKS ALIGNED VERTICALLY
+2052 ; Grapheme_Base # Sm COMMERCIAL MINUS SIGN
+2053 ; Grapheme_Base # Po SWUNG DASH
+2054 ; Grapheme_Base # Pc INVERTED UNDERTIE
+2055..205E ; Grapheme_Base # Po [10] FLOWER PUNCTUATION MARK..VERTICAL FOUR DOTS
+205F ; Grapheme_Base # Zs MEDIUM MATHEMATICAL SPACE
+2070 ; Grapheme_Base # No SUPERSCRIPT ZERO
+2071 ; Grapheme_Base # Lm SUPERSCRIPT LATIN SMALL LETTER I
+2074..2079 ; Grapheme_Base # No [6] SUPERSCRIPT FOUR..SUPERSCRIPT NINE
+207A..207C ; Grapheme_Base # Sm [3] SUPERSCRIPT PLUS SIGN..SUPERSCRIPT EQUALS SIGN
+207D ; Grapheme_Base # Ps SUPERSCRIPT LEFT PARENTHESIS
+207E ; Grapheme_Base # Pe SUPERSCRIPT RIGHT PARENTHESIS
+207F ; Grapheme_Base # Lm SUPERSCRIPT LATIN SMALL LETTER N
+2080..2089 ; Grapheme_Base # No [10] SUBSCRIPT ZERO..SUBSCRIPT NINE
+208A..208C ; Grapheme_Base # Sm [3] SUBSCRIPT PLUS SIGN..SUBSCRIPT EQUALS SIGN
+208D ; Grapheme_Base # Ps SUBSCRIPT LEFT PARENTHESIS
+208E ; Grapheme_Base # Pe SUBSCRIPT RIGHT PARENTHESIS
+2090..209C ; Grapheme_Base # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T
+20A0..20C0 ; Grapheme_Base # Sc [33] EURO-CURRENCY SIGN..SOM SIGN
+2100..2101 ; Grapheme_Base # So [2] ACCOUNT OF..ADDRESSED TO THE SUBJECT
+2102 ; Grapheme_Base # L& DOUBLE-STRUCK CAPITAL C
+2103..2106 ; Grapheme_Base # So [4] DEGREE CELSIUS..CADA UNA
+2107 ; Grapheme_Base # L& EULER CONSTANT
+2108..2109 ; Grapheme_Base # So [2] SCRUPLE..DEGREE FAHRENHEIT
+210A..2113 ; Grapheme_Base # L& [10] SCRIPT SMALL G..SCRIPT SMALL L
+2114 ; Grapheme_Base # So L B BAR SYMBOL
+2115 ; Grapheme_Base # L& DOUBLE-STRUCK CAPITAL N
+2116..2117 ; Grapheme_Base # So [2] NUMERO SIGN..SOUND RECORDING COPYRIGHT
+2118 ; Grapheme_Base # Sm SCRIPT CAPITAL P
+2119..211D ; Grapheme_Base # L& [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R
+211E..2123 ; Grapheme_Base # So [6] PRESCRIPTION TAKE..VERSICLE
+2124 ; Grapheme_Base # L& DOUBLE-STRUCK CAPITAL Z
+2125 ; Grapheme_Base # So OUNCE SIGN
+2126 ; Grapheme_Base # L& OHM SIGN
+2127 ; Grapheme_Base # So INVERTED OHM SIGN
+2128 ; Grapheme_Base # L& BLACK-LETTER CAPITAL Z
+2129 ; Grapheme_Base # So TURNED GREEK SMALL LETTER IOTA
+212A..212D ; Grapheme_Base # L& [4] KELVIN SIGN..BLACK-LETTER CAPITAL C
+212E ; Grapheme_Base # So ESTIMATED SYMBOL
+212F..2134 ; Grapheme_Base # L& [6] SCRIPT SMALL E..SCRIPT SMALL O
+2135..2138 ; Grapheme_Base # Lo [4] ALEF SYMBOL..DALET SYMBOL
+2139 ; Grapheme_Base # L& INFORMATION SOURCE
+213A..213B ; Grapheme_Base # So [2] ROTATED CAPITAL Q..FACSIMILE SIGN
+213C..213F ; Grapheme_Base # L& [4] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK CAPITAL PI
+2140..2144 ; Grapheme_Base # Sm [5] DOUBLE-STRUCK N-ARY SUMMATION..TURNED SANS-SERIF CAPITAL Y
+2145..2149 ; Grapheme_Base # L& [5] DOUBLE-STRUCK ITALIC CAPITAL D..DOUBLE-STRUCK ITALIC SMALL J
+214A ; Grapheme_Base # So PROPERTY LINE
+214B ; Grapheme_Base # Sm TURNED AMPERSAND
+214C..214D ; Grapheme_Base # So [2] PER SIGN..AKTIESELSKAB
+214E ; Grapheme_Base # L& TURNED SMALL F
+214F ; Grapheme_Base # So SYMBOL FOR SAMARITAN SOURCE
+2150..215F ; Grapheme_Base # No [16] VULGAR FRACTION ONE SEVENTH..FRACTION NUMERATOR ONE
+2160..2182 ; Grapheme_Base # Nl [35] ROMAN NUMERAL ONE..ROMAN NUMERAL TEN THOUSAND
+2183..2184 ; Grapheme_Base # L& [2] ROMAN NUMERAL REVERSED ONE HUNDRED..LATIN SMALL LETTER REVERSED C
+2185..2188 ; Grapheme_Base # Nl [4] ROMAN NUMERAL SIX LATE FORM..ROMAN NUMERAL ONE HUNDRED THOUSAND
+2189 ; Grapheme_Base # No VULGAR FRACTION ZERO THIRDS
+218A..218B ; Grapheme_Base # So [2] TURNED DIGIT TWO..TURNED DIGIT THREE
+2190..2194 ; Grapheme_Base # Sm [5] LEFTWARDS ARROW..LEFT RIGHT ARROW
+2195..2199 ; Grapheme_Base # So [5] UP DOWN ARROW..SOUTH WEST ARROW
+219A..219B ; Grapheme_Base # Sm [2] LEFTWARDS ARROW WITH STROKE..RIGHTWARDS ARROW WITH STROKE
+219C..219F ; Grapheme_Base # So [4] LEFTWARDS WAVE ARROW..UPWARDS TWO HEADED ARROW
+21A0 ; Grapheme_Base # Sm RIGHTWARDS TWO HEADED ARROW
+21A1..21A2 ; Grapheme_Base # So [2] DOWNWARDS TWO HEADED ARROW..LEFTWARDS ARROW WITH TAIL
+21A3 ; Grapheme_Base # Sm RIGHTWARDS ARROW WITH TAIL
+21A4..21A5 ; Grapheme_Base # So [2] LEFTWARDS ARROW FROM BAR..UPWARDS ARROW FROM BAR
+21A6 ; Grapheme_Base # Sm RIGHTWARDS ARROW FROM BAR
+21A7..21AD ; Grapheme_Base # So [7] DOWNWARDS ARROW FROM BAR..LEFT RIGHT WAVE ARROW
+21AE ; Grapheme_Base # Sm LEFT RIGHT ARROW WITH STROKE
+21AF..21CD ; Grapheme_Base # So [31] DOWNWARDS ZIGZAG ARROW..LEFTWARDS DOUBLE ARROW WITH STROKE
+21CE..21CF ; Grapheme_Base # Sm [2] LEFT RIGHT DOUBLE ARROW WITH STROKE..RIGHTWARDS DOUBLE ARROW WITH STROKE
+21D0..21D1 ; Grapheme_Base # So [2] LEFTWARDS DOUBLE ARROW..UPWARDS DOUBLE ARROW
+21D2 ; Grapheme_Base # Sm RIGHTWARDS DOUBLE ARROW
+21D3 ; Grapheme_Base # So DOWNWARDS DOUBLE ARROW
+21D4 ; Grapheme_Base # Sm LEFT RIGHT DOUBLE ARROW
+21D5..21F3 ; Grapheme_Base # So [31] UP DOWN DOUBLE ARROW..UP DOWN WHITE ARROW
+21F4..22FF ; Grapheme_Base # Sm [268] RIGHT ARROW WITH SMALL CIRCLE..Z NOTATION BAG MEMBERSHIP
+2300..2307 ; Grapheme_Base # So [8] DIAMETER SIGN..WAVY LINE
+2308 ; Grapheme_Base # Ps LEFT CEILING
+2309 ; Grapheme_Base # Pe RIGHT CEILING
+230A ; Grapheme_Base # Ps LEFT FLOOR
+230B ; Grapheme_Base # Pe RIGHT FLOOR
+230C..231F ; Grapheme_Base # So [20] BOTTOM RIGHT CROP..BOTTOM RIGHT CORNER
+2320..2321 ; Grapheme_Base # Sm [2] TOP HALF INTEGRAL..BOTTOM HALF INTEGRAL
+2322..2328 ; Grapheme_Base # So [7] FROWN..KEYBOARD
+2329 ; Grapheme_Base # Ps LEFT-POINTING ANGLE BRACKET
+232A ; Grapheme_Base # Pe RIGHT-POINTING ANGLE BRACKET
+232B..237B ; Grapheme_Base # So [81] ERASE TO THE LEFT..NOT CHECK MARK
+237C ; Grapheme_Base # Sm RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW
+237D..239A ; Grapheme_Base # So [30] SHOULDERED OPEN BOX..CLEAR SCREEN SYMBOL
+239B..23B3 ; Grapheme_Base # Sm [25] LEFT PARENTHESIS UPPER HOOK..SUMMATION BOTTOM
+23B4..23DB ; Grapheme_Base # So [40] TOP SQUARE BRACKET..FUSE
+23DC..23E1 ; Grapheme_Base # Sm [6] TOP PARENTHESIS..BOTTOM TORTOISE SHELL BRACKET
+23E2..2429 ; Grapheme_Base # So [72] WHITE TRAPEZIUM..SYMBOL FOR DELETE MEDIUM SHADE FORM
+2440..244A ; Grapheme_Base # So [11] OCR HOOK..OCR DOUBLE BACKSLASH
+2460..249B ; Grapheme_Base # No [60] CIRCLED DIGIT ONE..NUMBER TWENTY FULL STOP
+249C..24E9 ; Grapheme_Base # So [78] PARENTHESIZED LATIN SMALL LETTER A..CIRCLED LATIN SMALL LETTER Z
+24EA..24FF ; Grapheme_Base # No [22] CIRCLED DIGIT ZERO..NEGATIVE CIRCLED DIGIT ZERO
+2500..25B6 ; Grapheme_Base # So [183] BOX DRAWINGS LIGHT HORIZONTAL..BLACK RIGHT-POINTING TRIANGLE
+25B7 ; Grapheme_Base # Sm WHITE RIGHT-POINTING TRIANGLE
+25B8..25C0 ; Grapheme_Base # So [9] BLACK RIGHT-POINTING SMALL TRIANGLE..BLACK LEFT-POINTING TRIANGLE
+25C1 ; Grapheme_Base # Sm WHITE LEFT-POINTING TRIANGLE
+25C2..25F7 ; Grapheme_Base # So [54] BLACK LEFT-POINTING SMALL TRIANGLE..WHITE CIRCLE WITH UPPER RIGHT QUADRANT
+25F8..25FF ; Grapheme_Base # Sm [8] UPPER LEFT TRIANGLE..LOWER RIGHT TRIANGLE
+2600..266E ; Grapheme_Base # So [111] BLACK SUN WITH RAYS..MUSIC NATURAL SIGN
+266F ; Grapheme_Base # Sm MUSIC SHARP SIGN
+2670..2767 ; Grapheme_Base # So [248] WEST SYRIAC CROSS..ROTATED FLORAL HEART BULLET
+2768 ; Grapheme_Base # Ps MEDIUM LEFT PARENTHESIS ORNAMENT
+2769 ; Grapheme_Base # Pe MEDIUM RIGHT PARENTHESIS ORNAMENT
+276A ; Grapheme_Base # Ps MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT
+276B ; Grapheme_Base # Pe MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT
+276C ; Grapheme_Base # Ps MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT
+276D ; Grapheme_Base # Pe MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT
+276E ; Grapheme_Base # Ps HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT
+276F ; Grapheme_Base # Pe HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT
+2770 ; Grapheme_Base # Ps HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT
+2771 ; Grapheme_Base # Pe HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT
+2772 ; Grapheme_Base # Ps LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT
+2773 ; Grapheme_Base # Pe LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT
+2774 ; Grapheme_Base # Ps MEDIUM LEFT CURLY BRACKET ORNAMENT
+2775 ; Grapheme_Base # Pe MEDIUM RIGHT CURLY BRACKET ORNAMENT
+2776..2793 ; Grapheme_Base # No [30] DINGBAT NEGATIVE CIRCLED DIGIT ONE..DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN
+2794..27BF ; Grapheme_Base # So [44] HEAVY WIDE-HEADED RIGHTWARDS ARROW..DOUBLE CURLY LOOP
+27C0..27C4 ; Grapheme_Base # Sm [5] THREE DIMENSIONAL ANGLE..OPEN SUPERSET
+27C5 ; Grapheme_Base # Ps LEFT S-SHAPED BAG DELIMITER
+27C6 ; Grapheme_Base # Pe RIGHT S-SHAPED BAG DELIMITER
+27C7..27E5 ; Grapheme_Base # Sm [31] OR WITH DOT INSIDE..WHITE SQUARE WITH RIGHTWARDS TICK
+27E6 ; Grapheme_Base # Ps MATHEMATICAL LEFT WHITE SQUARE BRACKET
+27E7 ; Grapheme_Base # Pe MATHEMATICAL RIGHT WHITE SQUARE BRACKET
+27E8 ; Grapheme_Base # Ps MATHEMATICAL LEFT ANGLE BRACKET
+27E9 ; Grapheme_Base # Pe MATHEMATICAL RIGHT ANGLE BRACKET
+27EA ; Grapheme_Base # Ps MATHEMATICAL LEFT DOUBLE ANGLE BRACKET
+27EB ; Grapheme_Base # Pe MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET
+27EC ; Grapheme_Base # Ps MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET
+27ED ; Grapheme_Base # Pe MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET
+27EE ; Grapheme_Base # Ps MATHEMATICAL LEFT FLATTENED PARENTHESIS
+27EF ; Grapheme_Base # Pe MATHEMATICAL RIGHT FLATTENED PARENTHESIS
+27F0..27FF ; Grapheme_Base # Sm [16] UPWARDS QUADRUPLE ARROW..LONG RIGHTWARDS SQUIGGLE ARROW
+2800..28FF ; Grapheme_Base # So [256] BRAILLE PATTERN BLANK..BRAILLE PATTERN DOTS-12345678
+2900..2982 ; Grapheme_Base # Sm [131] RIGHTWARDS TWO-HEADED ARROW WITH VERTICAL STROKE..Z NOTATION TYPE COLON
+2983 ; Grapheme_Base # Ps LEFT WHITE CURLY BRACKET
+2984 ; Grapheme_Base # Pe RIGHT WHITE CURLY BRACKET
+2985 ; Grapheme_Base # Ps LEFT WHITE PARENTHESIS
+2986 ; Grapheme_Base # Pe RIGHT WHITE PARENTHESIS
+2987 ; Grapheme_Base # Ps Z NOTATION LEFT IMAGE BRACKET
+2988 ; Grapheme_Base # Pe Z NOTATION RIGHT IMAGE BRACKET
+2989 ; Grapheme_Base # Ps Z NOTATION LEFT BINDING BRACKET
+298A ; Grapheme_Base # Pe Z NOTATION RIGHT BINDING BRACKET
+298B ; Grapheme_Base # Ps LEFT SQUARE BRACKET WITH UNDERBAR
+298C ; Grapheme_Base # Pe RIGHT SQUARE BRACKET WITH UNDERBAR
+298D ; Grapheme_Base # Ps LEFT SQUARE BRACKET WITH TICK IN TOP CORNER
+298E ; Grapheme_Base # Pe RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER
+298F ; Grapheme_Base # Ps LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER
+2990 ; Grapheme_Base # Pe RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER
+2991 ; Grapheme_Base # Ps LEFT ANGLE BRACKET WITH DOT
+2992 ; Grapheme_Base # Pe RIGHT ANGLE BRACKET WITH DOT
+2993 ; Grapheme_Base # Ps LEFT ARC LESS-THAN BRACKET
+2994 ; Grapheme_Base # Pe RIGHT ARC GREATER-THAN BRACKET
+2995 ; Grapheme_Base # Ps DOUBLE LEFT ARC GREATER-THAN BRACKET
+2996 ; Grapheme_Base # Pe DOUBLE RIGHT ARC LESS-THAN BRACKET
+2997 ; Grapheme_Base # Ps LEFT BLACK TORTOISE SHELL BRACKET
+2998 ; Grapheme_Base # Pe RIGHT BLACK TORTOISE SHELL BRACKET
+2999..29D7 ; Grapheme_Base # Sm [63] DOTTED FENCE..BLACK HOURGLASS
+29D8 ; Grapheme_Base # Ps LEFT WIGGLY FENCE
+29D9 ; Grapheme_Base # Pe RIGHT WIGGLY FENCE
+29DA ; Grapheme_Base # Ps LEFT DOUBLE WIGGLY FENCE
+29DB ; Grapheme_Base # Pe RIGHT DOUBLE WIGGLY FENCE
+29DC..29FB ; Grapheme_Base # Sm [32] INCOMPLETE INFINITY..TRIPLE PLUS
+29FC ; Grapheme_Base # Ps LEFT-POINTING CURVED ANGLE BRACKET
+29FD ; Grapheme_Base # Pe RIGHT-POINTING CURVED ANGLE BRACKET
+29FE..2AFF ; Grapheme_Base # Sm [258] TINY..N-ARY WHITE VERTICAL BAR
+2B00..2B2F ; Grapheme_Base # So [48] NORTH EAST WHITE ARROW..WHITE VERTICAL ELLIPSE
+2B30..2B44 ; Grapheme_Base # Sm [21] LEFT ARROW WITH SMALL CIRCLE..RIGHTWARDS ARROW THROUGH SUPERSET
+2B45..2B46 ; Grapheme_Base # So [2] LEFTWARDS QUADRUPLE ARROW..RIGHTWARDS QUADRUPLE ARROW
+2B47..2B4C ; Grapheme_Base # Sm [6] REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR
+2B4D..2B73 ; Grapheme_Base # So [39] DOWNWARDS TRIANGLE-HEADED ZIGZAG ARROW..DOWNWARDS TRIANGLE-HEADED ARROW TO BAR
+2B76..2B95 ; Grapheme_Base # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW
+2B97..2BFF ; Grapheme_Base # So [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL
+2C00..2C7B ; Grapheme_Base # L& [124] GLAGOLITIC CAPITAL LETTER AZU..LATIN LETTER SMALL CAPITAL TURNED E
+2C7C..2C7D ; Grapheme_Base # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V
+2C7E..2CE4 ; Grapheme_Base # L& [103] LATIN CAPITAL LETTER S WITH SWASH TAIL..COPTIC SYMBOL KAI
+2CE5..2CEA ; Grapheme_Base # So [6] COPTIC SYMBOL MI RO..COPTIC SYMBOL SHIMA SIMA
+2CEB..2CEE ; Grapheme_Base # L& [4] COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI..COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA
+2CF2..2CF3 ; Grapheme_Base # L& [2] COPTIC CAPITAL LETTER BOHAIRIC KHEI..COPTIC SMALL LETTER BOHAIRIC KHEI
+2CF9..2CFC ; Grapheme_Base # Po [4] COPTIC OLD NUBIAN FULL STOP..COPTIC OLD NUBIAN VERSE DIVIDER
+2CFD ; Grapheme_Base # No COPTIC FRACTION ONE HALF
+2CFE..2CFF ; Grapheme_Base # Po [2] COPTIC FULL STOP..COPTIC MORPHOLOGICAL DIVIDER
+2D00..2D25 ; Grapheme_Base # L& [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE
+2D27 ; Grapheme_Base # L& GEORGIAN SMALL LETTER YN
+2D2D ; Grapheme_Base # L& GEORGIAN SMALL LETTER AEN
+2D30..2D67 ; Grapheme_Base # Lo [56] TIFINAGH LETTER YA..TIFINAGH LETTER YO
+2D6F ; Grapheme_Base # Lm TIFINAGH MODIFIER LETTER LABIALIZATION MARK
+2D70 ; Grapheme_Base # Po TIFINAGH SEPARATOR MARK
+2D80..2D96 ; Grapheme_Base # Lo [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE
+2DA0..2DA6 ; Grapheme_Base # Lo [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO
+2DA8..2DAE ; Grapheme_Base # Lo [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO
+2DB0..2DB6 ; Grapheme_Base # Lo [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO
+2DB8..2DBE ; Grapheme_Base # Lo [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO
+2DC0..2DC6 ; Grapheme_Base # Lo [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO
+2DC8..2DCE ; Grapheme_Base # Lo [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO
+2DD0..2DD6 ; Grapheme_Base # Lo [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO
+2DD8..2DDE ; Grapheme_Base # Lo [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO
+2E00..2E01 ; Grapheme_Base # Po [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER
+2E02 ; Grapheme_Base # Pi LEFT SUBSTITUTION BRACKET
+2E03 ; Grapheme_Base # Pf RIGHT SUBSTITUTION BRACKET
+2E04 ; Grapheme_Base # Pi LEFT DOTTED SUBSTITUTION BRACKET
+2E05 ; Grapheme_Base # Pf RIGHT DOTTED SUBSTITUTION BRACKET
+2E06..2E08 ; Grapheme_Base # Po [3] RAISED INTERPOLATION MARKER..DOTTED TRANSPOSITION MARKER
+2E09 ; Grapheme_Base # Pi LEFT TRANSPOSITION BRACKET
+2E0A ; Grapheme_Base # Pf RIGHT TRANSPOSITION BRACKET
+2E0B ; Grapheme_Base # Po RAISED SQUARE
+2E0C ; Grapheme_Base # Pi LEFT RAISED OMISSION BRACKET
+2E0D ; Grapheme_Base # Pf RIGHT RAISED OMISSION BRACKET
+2E0E..2E16 ; Grapheme_Base # Po [9] EDITORIAL CORONIS..DOTTED RIGHT-POINTING ANGLE
+2E17 ; Grapheme_Base # Pd DOUBLE OBLIQUE HYPHEN
+2E18..2E19 ; Grapheme_Base # Po [2] INVERTED INTERROBANG..PALM BRANCH
+2E1A ; Grapheme_Base # Pd HYPHEN WITH DIAERESIS
+2E1B ; Grapheme_Base # Po TILDE WITH RING ABOVE
+2E1C ; Grapheme_Base # Pi LEFT LOW PARAPHRASE BRACKET
+2E1D ; Grapheme_Base # Pf RIGHT LOW PARAPHRASE BRACKET
+2E1E..2E1F ; Grapheme_Base # Po [2] TILDE WITH DOT ABOVE..TILDE WITH DOT BELOW
+2E20 ; Grapheme_Base # Pi LEFT VERTICAL BAR WITH QUILL
+2E21 ; Grapheme_Base # Pf RIGHT VERTICAL BAR WITH QUILL
+2E22 ; Grapheme_Base # Ps TOP LEFT HALF BRACKET
+2E23 ; Grapheme_Base # Pe TOP RIGHT HALF BRACKET
+2E24 ; Grapheme_Base # Ps BOTTOM LEFT HALF BRACKET
+2E25 ; Grapheme_Base # Pe BOTTOM RIGHT HALF BRACKET
+2E26 ; Grapheme_Base # Ps LEFT SIDEWAYS U BRACKET
+2E27 ; Grapheme_Base # Pe RIGHT SIDEWAYS U BRACKET
+2E28 ; Grapheme_Base # Ps LEFT DOUBLE PARENTHESIS
+2E29 ; Grapheme_Base # Pe RIGHT DOUBLE PARENTHESIS
+2E2A..2E2E ; Grapheme_Base # Po [5] TWO DOTS OVER ONE DOT PUNCTUATION..REVERSED QUESTION MARK
+2E2F ; Grapheme_Base # Lm VERTICAL TILDE
+2E30..2E39 ; Grapheme_Base # Po [10] RING POINT..TOP HALF SECTION SIGN
+2E3A..2E3B ; Grapheme_Base # Pd [2] TWO-EM DASH..THREE-EM DASH
+2E3C..2E3F ; Grapheme_Base # Po [4] STENOGRAPHIC FULL STOP..CAPITULUM
+2E40 ; Grapheme_Base # Pd DOUBLE HYPHEN
+2E41 ; Grapheme_Base # Po REVERSED COMMA
+2E42 ; Grapheme_Base # Ps DOUBLE LOW-REVERSED-9 QUOTATION MARK
+2E43..2E4F ; Grapheme_Base # Po [13] DASH WITH LEFT UPTURN..CORNISH VERSE DIVIDER
+2E50..2E51 ; Grapheme_Base # So [2] CROSS PATTY WITH RIGHT CROSSBAR..CROSS PATTY WITH LEFT CROSSBAR
+2E52..2E54 ; Grapheme_Base # Po [3] TIRONIAN SIGN CAPITAL ET..MEDIEVAL QUESTION MARK
+2E55 ; Grapheme_Base # Ps LEFT SQUARE BRACKET WITH STROKE
+2E56 ; Grapheme_Base # Pe RIGHT SQUARE BRACKET WITH STROKE
+2E57 ; Grapheme_Base # Ps LEFT SQUARE BRACKET WITH DOUBLE STROKE
+2E58 ; Grapheme_Base # Pe RIGHT SQUARE BRACKET WITH DOUBLE STROKE
+2E59 ; Grapheme_Base # Ps TOP HALF LEFT PARENTHESIS
+2E5A ; Grapheme_Base # Pe TOP HALF RIGHT PARENTHESIS
+2E5B ; Grapheme_Base # Ps BOTTOM HALF LEFT PARENTHESIS
+2E5C ; Grapheme_Base # Pe BOTTOM HALF RIGHT PARENTHESIS
+2E5D ; Grapheme_Base # Pd OBLIQUE HYPHEN
+2E80..2E99 ; Grapheme_Base # So [26] CJK RADICAL REPEAT..CJK RADICAL RAP
+2E9B..2EF3 ; Grapheme_Base # So [89] CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED TURTLE
+2F00..2FD5 ; Grapheme_Base # So [214] KANGXI RADICAL ONE..KANGXI RADICAL FLUTE
+2FF0..2FFF ; Grapheme_Base # So [16] IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT..IDEOGRAPHIC DESCRIPTION CHARACTER ROTATION
+3000 ; Grapheme_Base # Zs IDEOGRAPHIC SPACE
+3001..3003 ; Grapheme_Base # Po [3] IDEOGRAPHIC COMMA..DITTO MARK
+3004 ; Grapheme_Base # So JAPANESE INDUSTRIAL STANDARD SYMBOL
+3005 ; Grapheme_Base # Lm IDEOGRAPHIC ITERATION MARK
+3006 ; Grapheme_Base # Lo IDEOGRAPHIC CLOSING MARK
+3007 ; Grapheme_Base # Nl IDEOGRAPHIC NUMBER ZERO
+3008 ; Grapheme_Base # Ps LEFT ANGLE BRACKET
+3009 ; Grapheme_Base # Pe RIGHT ANGLE BRACKET
+300A ; Grapheme_Base # Ps LEFT DOUBLE ANGLE BRACKET
+300B ; Grapheme_Base # Pe RIGHT DOUBLE ANGLE BRACKET
+300C ; Grapheme_Base # Ps LEFT CORNER BRACKET
+300D ; Grapheme_Base # Pe RIGHT CORNER BRACKET
+300E ; Grapheme_Base # Ps LEFT WHITE CORNER BRACKET
+300F ; Grapheme_Base # Pe RIGHT WHITE CORNER BRACKET
+3010 ; Grapheme_Base # Ps LEFT BLACK LENTICULAR BRACKET
+3011 ; Grapheme_Base # Pe RIGHT BLACK LENTICULAR BRACKET
+3012..3013 ; Grapheme_Base # So [2] POSTAL MARK..GETA MARK
+3014 ; Grapheme_Base # Ps LEFT TORTOISE SHELL BRACKET
+3015 ; Grapheme_Base # Pe RIGHT TORTOISE SHELL BRACKET
+3016 ; Grapheme_Base # Ps LEFT WHITE LENTICULAR BRACKET
+3017 ; Grapheme_Base # Pe RIGHT WHITE LENTICULAR BRACKET
+3018 ; Grapheme_Base # Ps LEFT WHITE TORTOISE SHELL BRACKET
+3019 ; Grapheme_Base # Pe RIGHT WHITE TORTOISE SHELL BRACKET
+301A ; Grapheme_Base # Ps LEFT WHITE SQUARE BRACKET
+301B ; Grapheme_Base # Pe RIGHT WHITE SQUARE BRACKET
+301C ; Grapheme_Base # Pd WAVE DASH
+301D ; Grapheme_Base # Ps REVERSED DOUBLE PRIME QUOTATION MARK
+301E..301F ; Grapheme_Base # Pe [2] DOUBLE PRIME QUOTATION MARK..LOW DOUBLE PRIME QUOTATION MARK
+3020 ; Grapheme_Base # So POSTAL MARK FACE
+3021..3029 ; Grapheme_Base # Nl [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE
+3030 ; Grapheme_Base # Pd WAVY DASH
+3031..3035 ; Grapheme_Base # Lm [5] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT MARK LOWER HALF
+3036..3037 ; Grapheme_Base # So [2] CIRCLED POSTAL MARK..IDEOGRAPHIC TELEGRAPH LINE FEED SEPARATOR SYMBOL
+3038..303A ; Grapheme_Base # Nl [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY
+303B ; Grapheme_Base # Lm VERTICAL IDEOGRAPHIC ITERATION MARK
+303C ; Grapheme_Base # Lo MASU MARK
+303D ; Grapheme_Base # Po PART ALTERNATION MARK
+303E..303F ; Grapheme_Base # So [2] IDEOGRAPHIC VARIATION INDICATOR..IDEOGRAPHIC HALF FILL SPACE
+3041..3096 ; Grapheme_Base # Lo [86] HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMALL KE
+309B..309C ; Grapheme_Base # Sk [2] KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+309D..309E ; Grapheme_Base # Lm [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK
+309F ; Grapheme_Base # Lo HIRAGANA DIGRAPH YORI
+30A0 ; Grapheme_Base # Pd KATAKANA-HIRAGANA DOUBLE HYPHEN
+30A1..30FA ; Grapheme_Base # Lo [90] KATAKANA LETTER SMALL A..KATAKANA LETTER VO
+30FB ; Grapheme_Base # Po KATAKANA MIDDLE DOT
+30FC..30FE ; Grapheme_Base # Lm [3] KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK
+30FF ; Grapheme_Base # Lo KATAKANA DIGRAPH KOTO
+3105..312F ; Grapheme_Base # Lo [43] BOPOMOFO LETTER B..BOPOMOFO LETTER NN
+3131..318E ; Grapheme_Base # Lo [94] HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE
+3190..3191 ; Grapheme_Base # So [2] IDEOGRAPHIC ANNOTATION LINKING MARK..IDEOGRAPHIC ANNOTATION REVERSE MARK
+3192..3195 ; Grapheme_Base # No [4] IDEOGRAPHIC ANNOTATION ONE MARK..IDEOGRAPHIC ANNOTATION FOUR MARK
+3196..319F ; Grapheme_Base # So [10] IDEOGRAPHIC ANNOTATION TOP MARK..IDEOGRAPHIC ANNOTATION MAN MARK
+31A0..31BF ; Grapheme_Base # Lo [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH
+31C0..31E5 ; Grapheme_Base # So [38] CJK STROKE T..CJK STROKE SZP
+31EF ; Grapheme_Base # So IDEOGRAPHIC DESCRIPTION CHARACTER SUBTRACTION
+31F0..31FF ; Grapheme_Base # Lo [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO
+3200..321E ; Grapheme_Base # So [31] PARENTHESIZED HANGUL KIYEOK..PARENTHESIZED KOREAN CHARACTER O HU
+3220..3229 ; Grapheme_Base # No [10] PARENTHESIZED IDEOGRAPH ONE..PARENTHESIZED IDEOGRAPH TEN
+322A..3247 ; Grapheme_Base # So [30] PARENTHESIZED IDEOGRAPH MOON..CIRCLED IDEOGRAPH KOTO
+3248..324F ; Grapheme_Base # No [8] CIRCLED NUMBER TEN ON BLACK SQUARE..CIRCLED NUMBER EIGHTY ON BLACK SQUARE
+3250 ; Grapheme_Base # So PARTNERSHIP SIGN
+3251..325F ; Grapheme_Base # No [15] CIRCLED NUMBER TWENTY ONE..CIRCLED NUMBER THIRTY FIVE
+3260..327F ; Grapheme_Base # So [32] CIRCLED HANGUL KIYEOK..KOREAN STANDARD SYMBOL
+3280..3289 ; Grapheme_Base # No [10] CIRCLED IDEOGRAPH ONE..CIRCLED IDEOGRAPH TEN
+328A..32B0 ; Grapheme_Base # So [39] CIRCLED IDEOGRAPH MOON..CIRCLED IDEOGRAPH NIGHT
+32B1..32BF ; Grapheme_Base # No [15] CIRCLED NUMBER THIRTY SIX..CIRCLED NUMBER FIFTY
+32C0..33FF ; Grapheme_Base # So [320] IDEOGRAPHIC TELEGRAPH SYMBOL FOR JANUARY..SQUARE GAL
+3400..4DBF ; Grapheme_Base # Lo [6592] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DBF
+4DC0..4DFF ; Grapheme_Base # So [64] HEXAGRAM FOR THE CREATIVE HEAVEN..HEXAGRAM FOR BEFORE COMPLETION
+4E00..A014 ; Grapheme_Base # Lo [21013] CJK UNIFIED IDEOGRAPH-4E00..YI SYLLABLE E
+A015 ; Grapheme_Base # Lm YI SYLLABLE WU
+A016..A48C ; Grapheme_Base # Lo [1143] YI SYLLABLE BIT..YI SYLLABLE YYR
+A490..A4C6 ; Grapheme_Base # So [55] YI RADICAL QOT..YI RADICAL KE
+A4D0..A4F7 ; Grapheme_Base # Lo [40] LISU LETTER BA..LISU LETTER OE
+A4F8..A4FD ; Grapheme_Base # Lm [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU
+A4FE..A4FF ; Grapheme_Base # Po [2] LISU PUNCTUATION COMMA..LISU PUNCTUATION FULL STOP
+A500..A60B ; Grapheme_Base # Lo [268] VAI SYLLABLE EE..VAI SYLLABLE NG
+A60C ; Grapheme_Base # Lm VAI SYLLABLE LENGTHENER
+A60D..A60F ; Grapheme_Base # Po [3] VAI COMMA..VAI QUESTION MARK
+A610..A61F ; Grapheme_Base # Lo [16] VAI SYLLABLE NDOLE FA..VAI SYMBOL JONG
+A620..A629 ; Grapheme_Base # Nd [10] VAI DIGIT ZERO..VAI DIGIT NINE
+A62A..A62B ; Grapheme_Base # Lo [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO
+A640..A66D ; Grapheme_Base # L& [46] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER DOUBLE MONOCULAR O
+A66E ; Grapheme_Base # Lo CYRILLIC LETTER MULTIOCULAR O
+A673 ; Grapheme_Base # Po SLAVONIC ASTERISK
+A67E ; Grapheme_Base # Po CYRILLIC KAVYKA
+A67F ; Grapheme_Base # Lm CYRILLIC PAYEROK
+A680..A69B ; Grapheme_Base # L& [28] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER CROSSED O
+A69C..A69D ; Grapheme_Base # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN
+A6A0..A6E5 ; Grapheme_Base # Lo [70] BAMUM LETTER A..BAMUM LETTER KI
+A6E6..A6EF ; Grapheme_Base # Nl [10] BAMUM LETTER MO..BAMUM LETTER KOGHOM
+A6F2..A6F7 ; Grapheme_Base # Po [6] BAMUM NJAEMLI..BAMUM QUESTION MARK
+A700..A716 ; Grapheme_Base # Sk [23] MODIFIER LETTER CHINESE TONE YIN PING..MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR
+A717..A71F ; Grapheme_Base # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK
+A720..A721 ; Grapheme_Base # Sk [2] MODIFIER LETTER STRESS AND HIGH TONE..MODIFIER LETTER STRESS AND LOW TONE
+A722..A76F ; Grapheme_Base # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CON
+A770 ; Grapheme_Base # Lm MODIFIER LETTER US
+A771..A787 ; Grapheme_Base # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T
+A788 ; Grapheme_Base # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT
+A789..A78A ; Grapheme_Base # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN
+A78B..A78E ; Grapheme_Base # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT
+A78F ; Grapheme_Base # Lo LATIN LETTER SINOLOGICAL DOT
+A790..A7CD ; Grapheme_Base # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE
+A7D0..A7D1 ; Grapheme_Base # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G
+A7D3 ; Grapheme_Base # L& LATIN SMALL LETTER DOUBLE THORN
+A7D5..A7DC ; Grapheme_Base # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE
+A7F2..A7F4 ; Grapheme_Base # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q
+A7F5..A7F6 ; Grapheme_Base # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H
+A7F7 ; Grapheme_Base # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I
+A7F8..A7F9 ; Grapheme_Base # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE
+A7FA ; Grapheme_Base # L& LATIN LETTER SMALL CAPITAL TURNED M
+A7FB..A801 ; Grapheme_Base # Lo [7] LATIN EPIGRAPHIC LETTER REVERSED F..SYLOTI NAGRI LETTER I
+A803..A805 ; Grapheme_Base # Lo [3] SYLOTI NAGRI LETTER U..SYLOTI NAGRI LETTER O
+A807..A80A ; Grapheme_Base # Lo [4] SYLOTI NAGRI LETTER KO..SYLOTI NAGRI LETTER GHO
+A80C..A822 ; Grapheme_Base # Lo [23] SYLOTI NAGRI LETTER CO..SYLOTI NAGRI LETTER HO
+A823..A824 ; Grapheme_Base # Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I
+A827 ; Grapheme_Base # Mc SYLOTI NAGRI VOWEL SIGN OO
+A828..A82B ; Grapheme_Base # So [4] SYLOTI NAGRI POETRY MARK-1..SYLOTI NAGRI POETRY MARK-4
+A830..A835 ; Grapheme_Base # No [6] NORTH INDIC FRACTION ONE QUARTER..NORTH INDIC FRACTION THREE SIXTEENTHS
+A836..A837 ; Grapheme_Base # So [2] NORTH INDIC QUARTER MARK..NORTH INDIC PLACEHOLDER MARK
+A838 ; Grapheme_Base # Sc NORTH INDIC RUPEE MARK
+A839 ; Grapheme_Base # So NORTH INDIC QUANTITY MARK
+A840..A873 ; Grapheme_Base # Lo [52] PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU
+A874..A877 ; Grapheme_Base # Po [4] PHAGS-PA SINGLE HEAD MARK..PHAGS-PA MARK DOUBLE SHAD
+A880..A881 ; Grapheme_Base # Mc [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA
+A882..A8B3 ; Grapheme_Base # Lo [50] SAURASHTRA LETTER A..SAURASHTRA LETTER LLA
+A8B4..A8C3 ; Grapheme_Base # Mc [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU
+A8CE..A8CF ; Grapheme_Base # Po [2] SAURASHTRA DANDA..SAURASHTRA DOUBLE DANDA
+A8D0..A8D9 ; Grapheme_Base # Nd [10] SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE
+A8F2..A8F7 ; Grapheme_Base # Lo [6] DEVANAGARI SIGN SPACING CANDRABINDU..DEVANAGARI SIGN CANDRABINDU AVAGRAHA
+A8F8..A8FA ; Grapheme_Base # Po [3] DEVANAGARI SIGN PUSHPIKA..DEVANAGARI CARET
+A8FB ; Grapheme_Base # Lo DEVANAGARI HEADSTROKE
+A8FC ; Grapheme_Base # Po DEVANAGARI SIGN SIDDHAM
+A8FD..A8FE ; Grapheme_Base # Lo [2] DEVANAGARI JAIN OM..DEVANAGARI LETTER AY
+A900..A909 ; Grapheme_Base # Nd [10] KAYAH LI DIGIT ZERO..KAYAH LI DIGIT NINE
+A90A..A925 ; Grapheme_Base # Lo [28] KAYAH LI LETTER KA..KAYAH LI LETTER OO
+A92E..A92F ; Grapheme_Base # Po [2] KAYAH LI SIGN CWI..KAYAH LI SIGN SHYA
+A930..A946 ; Grapheme_Base # Lo [23] REJANG LETTER KA..REJANG LETTER A
+A952 ; Grapheme_Base # Mc REJANG CONSONANT SIGN H
+A95F ; Grapheme_Base # Po REJANG SECTION MARK
+A960..A97C ; Grapheme_Base # Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH
+A983 ; Grapheme_Base # Mc JAVANESE SIGN WIGNYAN
+A984..A9B2 ; Grapheme_Base # Lo [47] JAVANESE LETTER A..JAVANESE LETTER HA
+A9B4..A9B5 ; Grapheme_Base # Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG
+A9BA..A9BB ; Grapheme_Base # Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE
+A9BE..A9BF ; Grapheme_Base # Mc [2] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE CONSONANT SIGN CAKRA
+A9C1..A9CD ; Grapheme_Base # Po [13] JAVANESE LEFT RERENGGAN..JAVANESE TURNED PADA PISELEH
+A9CF ; Grapheme_Base # Lm JAVANESE PANGRANGKEP
+A9D0..A9D9 ; Grapheme_Base # Nd [10] JAVANESE DIGIT ZERO..JAVANESE DIGIT NINE
+A9DE..A9DF ; Grapheme_Base # Po [2] JAVANESE PADA TIRTA TUMETES..JAVANESE PADA ISEN-ISEN
+A9E0..A9E4 ; Grapheme_Base # Lo [5] MYANMAR LETTER SHAN GHA..MYANMAR LETTER SHAN BHA
+A9E6 ; Grapheme_Base # Lm MYANMAR MODIFIER LETTER SHAN REDUPLICATION
+A9E7..A9EF ; Grapheme_Base # Lo [9] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING NNA
+A9F0..A9F9 ; Grapheme_Base # Nd [10] MYANMAR TAI LAING DIGIT ZERO..MYANMAR TAI LAING DIGIT NINE
+A9FA..A9FE ; Grapheme_Base # Lo [5] MYANMAR LETTER TAI LAING LLA..MYANMAR LETTER TAI LAING BHA
+AA00..AA28 ; Grapheme_Base # Lo [41] CHAM LETTER A..CHAM LETTER HA
+AA2F..AA30 ; Grapheme_Base # Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI
+AA33..AA34 ; Grapheme_Base # Mc [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA
+AA40..AA42 ; Grapheme_Base # Lo [3] CHAM LETTER FINAL K..CHAM LETTER FINAL NG
+AA44..AA4B ; Grapheme_Base # Lo [8] CHAM LETTER FINAL CH..CHAM LETTER FINAL SS
+AA4D ; Grapheme_Base # Mc CHAM CONSONANT SIGN FINAL H
+AA50..AA59 ; Grapheme_Base # Nd [10] CHAM DIGIT ZERO..CHAM DIGIT NINE
+AA5C..AA5F ; Grapheme_Base # Po [4] CHAM PUNCTUATION SPIRAL..CHAM PUNCTUATION TRIPLE DANDA
+AA60..AA6F ; Grapheme_Base # Lo [16] MYANMAR LETTER KHAMTI GA..MYANMAR LETTER KHAMTI FA
+AA70 ; Grapheme_Base # Lm MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION
+AA71..AA76 ; Grapheme_Base # Lo [6] MYANMAR LETTER KHAMTI XA..MYANMAR LOGOGRAM KHAMTI HM
+AA77..AA79 ; Grapheme_Base # So [3] MYANMAR SYMBOL AITON EXCLAMATION..MYANMAR SYMBOL AITON TWO
+AA7A ; Grapheme_Base # Lo MYANMAR LETTER AITON RA
+AA7B ; Grapheme_Base # Mc MYANMAR SIGN PAO KAREN TONE
+AA7D ; Grapheme_Base # Mc MYANMAR SIGN TAI LAING TONE-5
+AA7E..AAAF ; Grapheme_Base # Lo [50] MYANMAR LETTER SHWE PALAUNG CHA..TAI VIET LETTER HIGH O
+AAB1 ; Grapheme_Base # Lo TAI VIET VOWEL AA
+AAB5..AAB6 ; Grapheme_Base # Lo [2] TAI VIET VOWEL E..TAI VIET VOWEL O
+AAB9..AABD ; Grapheme_Base # Lo [5] TAI VIET VOWEL UEA..TAI VIET VOWEL AN
+AAC0 ; Grapheme_Base # Lo TAI VIET TONE MAI NUENG
+AAC2 ; Grapheme_Base # Lo TAI VIET TONE MAI SONG
+AADB..AADC ; Grapheme_Base # Lo [2] TAI VIET SYMBOL KON..TAI VIET SYMBOL NUENG
+AADD ; Grapheme_Base # Lm TAI VIET SYMBOL SAM
+AADE..AADF ; Grapheme_Base # Po [2] TAI VIET SYMBOL HO HOI..TAI VIET SYMBOL KOI KOI
+AAE0..AAEA ; Grapheme_Base # Lo [11] MEETEI MAYEK LETTER E..MEETEI MAYEK LETTER SSA
+AAEB ; Grapheme_Base # Mc MEETEI MAYEK VOWEL SIGN II
+AAEE..AAEF ; Grapheme_Base # Mc [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU
+AAF0..AAF1 ; Grapheme_Base # Po [2] MEETEI MAYEK CHEIKHAN..MEETEI MAYEK AHANG KHUDAM
+AAF2 ; Grapheme_Base # Lo MEETEI MAYEK ANJI
+AAF3..AAF4 ; Grapheme_Base # Lm [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK
+AAF5 ; Grapheme_Base # Mc MEETEI MAYEK VOWEL SIGN VISARGA
+AB01..AB06 ; Grapheme_Base # Lo [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO
+AB09..AB0E ; Grapheme_Base # Lo [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO
+AB11..AB16 ; Grapheme_Base # Lo [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO
+AB20..AB26 ; Grapheme_Base # Lo [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO
+AB28..AB2E ; Grapheme_Base # Lo [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO
+AB30..AB5A ; Grapheme_Base # L& [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG
+AB5B ; Grapheme_Base # Sk MODIFIER BREVE WITH INVERTED BREVE
+AB5C..AB5F ; Grapheme_Base # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK
+AB60..AB68 ; Grapheme_Base # L& [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE
+AB69 ; Grapheme_Base # Lm MODIFIER LETTER SMALL TURNED W
+AB6A..AB6B ; Grapheme_Base # Sk [2] MODIFIER LETTER LEFT TACK..MODIFIER LETTER RIGHT TACK
+AB70..ABBF ; Grapheme_Base # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA
+ABC0..ABE2 ; Grapheme_Base # Lo [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM
+ABE3..ABE4 ; Grapheme_Base # Mc [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP
+ABE6..ABE7 ; Grapheme_Base # Mc [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP
+ABE9..ABEA ; Grapheme_Base # Mc [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG
+ABEB ; Grapheme_Base # Po MEETEI MAYEK CHEIKHEI
+ABEC ; Grapheme_Base # Mc MEETEI MAYEK LUM IYEK
+ABF0..ABF9 ; Grapheme_Base # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT NINE
+AC00..D7A3 ; Grapheme_Base # Lo [11172] HANGUL SYLLABLE GA..HANGUL SYLLABLE HIH
+D7B0..D7C6 ; Grapheme_Base # Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E
+D7CB..D7FB ; Grapheme_Base # Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH
+F900..FA6D ; Grapheme_Base # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D
+FA70..FAD9 ; Grapheme_Base # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9
+FB00..FB06 ; Grapheme_Base # L& [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST
+FB13..FB17 ; Grapheme_Base # L& [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH
+FB1D ; Grapheme_Base # Lo HEBREW LETTER YOD WITH HIRIQ
+FB1F..FB28 ; Grapheme_Base # Lo [10] HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER WIDE TAV
+FB29 ; Grapheme_Base # Sm HEBREW LETTER ALTERNATIVE PLUS SIGN
+FB2A..FB36 ; Grapheme_Base # Lo [13] HEBREW LETTER SHIN WITH SHIN DOT..HEBREW LETTER ZAYIN WITH DAGESH
+FB38..FB3C ; Grapheme_Base # Lo [5] HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH
+FB3E ; Grapheme_Base # Lo HEBREW LETTER MEM WITH DAGESH
+FB40..FB41 ; Grapheme_Base # Lo [2] HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH
+FB43..FB44 ; Grapheme_Base # Lo [2] HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH
+FB46..FBB1 ; Grapheme_Base # Lo [108] HEBREW LETTER TSADI WITH DAGESH..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM
+FBB2..FBC2 ; Grapheme_Base # Sk [17] ARABIC SYMBOL DOT ABOVE..ARABIC SYMBOL WASLA ABOVE
+FBD3..FD3D ; Grapheme_Base # Lo [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM
+FD3E ; Grapheme_Base # Pe ORNATE LEFT PARENTHESIS
+FD3F ; Grapheme_Base # Ps ORNATE RIGHT PARENTHESIS
+FD40..FD4F ; Grapheme_Base # So [16] ARABIC LIGATURE RAHIMAHU ALLAAH..ARABIC LIGATURE RAHIMAHUM ALLAAH
+FD50..FD8F ; Grapheme_Base # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM
+FD92..FDC7 ; Grapheme_Base # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM
+FDCF ; Grapheme_Base # So ARABIC LIGATURE SALAAMUHU ALAYNAA
+FDF0..FDFB ; Grapheme_Base # Lo [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU
+FDFC ; Grapheme_Base # Sc RIAL SIGN
+FDFD..FDFF ; Grapheme_Base # So [3] ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM..ARABIC LIGATURE AZZA WA JALL
+FE10..FE16 ; Grapheme_Base # Po [7] PRESENTATION FORM FOR VERTICAL COMMA..PRESENTATION FORM FOR VERTICAL QUESTION MARK
+FE17 ; Grapheme_Base # Ps PRESENTATION FORM FOR VERTICAL LEFT WHITE LENTICULAR BRACKET
+FE18 ; Grapheme_Base # Pe PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRAKCET
+FE19 ; Grapheme_Base # Po PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS
+FE30 ; Grapheme_Base # Po PRESENTATION FORM FOR VERTICAL TWO DOT LEADER
+FE31..FE32 ; Grapheme_Base # Pd [2] PRESENTATION FORM FOR VERTICAL EM DASH..PRESENTATION FORM FOR VERTICAL EN DASH
+FE33..FE34 ; Grapheme_Base # Pc [2] PRESENTATION FORM FOR VERTICAL LOW LINE..PRESENTATION FORM FOR VERTICAL WAVY LOW LINE
+FE35 ; Grapheme_Base # Ps PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS
+FE36 ; Grapheme_Base # Pe PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS
+FE37 ; Grapheme_Base # Ps PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET
+FE38 ; Grapheme_Base # Pe PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET
+FE39 ; Grapheme_Base # Ps PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET
+FE3A ; Grapheme_Base # Pe PRESENTATION FORM FOR VERTICAL RIGHT TORTOISE SHELL BRACKET
+FE3B ; Grapheme_Base # Ps PRESENTATION FORM FOR VERTICAL LEFT BLACK LENTICULAR BRACKET
+FE3C ; Grapheme_Base # Pe PRESENTATION FORM FOR VERTICAL RIGHT BLACK LENTICULAR BRACKET
+FE3D ; Grapheme_Base # Ps PRESENTATION FORM FOR VERTICAL LEFT DOUBLE ANGLE BRACKET
+FE3E ; Grapheme_Base # Pe PRESENTATION FORM FOR VERTICAL RIGHT DOUBLE ANGLE BRACKET
+FE3F ; Grapheme_Base # Ps PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET
+FE40 ; Grapheme_Base # Pe PRESENTATION FORM FOR VERTICAL RIGHT ANGLE BRACKET
+FE41 ; Grapheme_Base # Ps PRESENTATION FORM FOR VERTICAL LEFT CORNER BRACKET
+FE42 ; Grapheme_Base # Pe PRESENTATION FORM FOR VERTICAL RIGHT CORNER BRACKET
+FE43 ; Grapheme_Base # Ps PRESENTATION FORM FOR VERTICAL LEFT WHITE CORNER BRACKET
+FE44 ; Grapheme_Base # Pe PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET
+FE45..FE46 ; Grapheme_Base # Po [2] SESAME DOT..WHITE SESAME DOT
+FE47 ; Grapheme_Base # Ps PRESENTATION FORM FOR VERTICAL LEFT SQUARE BRACKET
+FE48 ; Grapheme_Base # Pe PRESENTATION FORM FOR VERTICAL RIGHT SQUARE BRACKET
+FE49..FE4C ; Grapheme_Base # Po [4] DASHED OVERLINE..DOUBLE WAVY OVERLINE
+FE4D..FE4F ; Grapheme_Base # Pc [3] DASHED LOW LINE..WAVY LOW LINE
+FE50..FE52 ; Grapheme_Base # Po [3] SMALL COMMA..SMALL FULL STOP
+FE54..FE57 ; Grapheme_Base # Po [4] SMALL SEMICOLON..SMALL EXCLAMATION MARK
+FE58 ; Grapheme_Base # Pd SMALL EM DASH
+FE59 ; Grapheme_Base # Ps SMALL LEFT PARENTHESIS
+FE5A ; Grapheme_Base # Pe SMALL RIGHT PARENTHESIS
+FE5B ; Grapheme_Base # Ps SMALL LEFT CURLY BRACKET
+FE5C ; Grapheme_Base # Pe SMALL RIGHT CURLY BRACKET
+FE5D ; Grapheme_Base # Ps SMALL LEFT TORTOISE SHELL BRACKET
+FE5E ; Grapheme_Base # Pe SMALL RIGHT TORTOISE SHELL BRACKET
+FE5F..FE61 ; Grapheme_Base # Po [3] SMALL NUMBER SIGN..SMALL ASTERISK
+FE62 ; Grapheme_Base # Sm SMALL PLUS SIGN
+FE63 ; Grapheme_Base # Pd SMALL HYPHEN-MINUS
+FE64..FE66 ; Grapheme_Base # Sm [3] SMALL LESS-THAN SIGN..SMALL EQUALS SIGN
+FE68 ; Grapheme_Base # Po SMALL REVERSE SOLIDUS
+FE69 ; Grapheme_Base # Sc SMALL DOLLAR SIGN
+FE6A..FE6B ; Grapheme_Base # Po [2] SMALL PERCENT SIGN..SMALL COMMERCIAL AT
+FE70..FE74 ; Grapheme_Base # Lo [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN ISOLATED FORM
+FE76..FEFC ; Grapheme_Base # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM
+FF01..FF03 ; Grapheme_Base # Po [3] FULLWIDTH EXCLAMATION MARK..FULLWIDTH NUMBER SIGN
+FF04 ; Grapheme_Base # Sc FULLWIDTH DOLLAR SIGN
+FF05..FF07 ; Grapheme_Base # Po [3] FULLWIDTH PERCENT SIGN..FULLWIDTH APOSTROPHE
+FF08 ; Grapheme_Base # Ps FULLWIDTH LEFT PARENTHESIS
+FF09 ; Grapheme_Base # Pe FULLWIDTH RIGHT PARENTHESIS
+FF0A ; Grapheme_Base # Po FULLWIDTH ASTERISK
+FF0B ; Grapheme_Base # Sm FULLWIDTH PLUS SIGN
+FF0C ; Grapheme_Base # Po FULLWIDTH COMMA
+FF0D ; Grapheme_Base # Pd FULLWIDTH HYPHEN-MINUS
+FF0E..FF0F ; Grapheme_Base # Po [2] FULLWIDTH FULL STOP..FULLWIDTH SOLIDUS
+FF10..FF19 ; Grapheme_Base # Nd [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE
+FF1A..FF1B ; Grapheme_Base # Po [2] FULLWIDTH COLON..FULLWIDTH SEMICOLON
+FF1C..FF1E ; Grapheme_Base # Sm [3] FULLWIDTH LESS-THAN SIGN..FULLWIDTH GREATER-THAN SIGN
+FF1F..FF20 ; Grapheme_Base # Po [2] FULLWIDTH QUESTION MARK..FULLWIDTH COMMERCIAL AT
+FF21..FF3A ; Grapheme_Base # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z
+FF3B ; Grapheme_Base # Ps FULLWIDTH LEFT SQUARE BRACKET
+FF3C ; Grapheme_Base # Po FULLWIDTH REVERSE SOLIDUS
+FF3D ; Grapheme_Base # Pe FULLWIDTH RIGHT SQUARE BRACKET
+FF3E ; Grapheme_Base # Sk FULLWIDTH CIRCUMFLEX ACCENT
+FF3F ; Grapheme_Base # Pc FULLWIDTH LOW LINE
+FF40 ; Grapheme_Base # Sk FULLWIDTH GRAVE ACCENT
+FF41..FF5A ; Grapheme_Base # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z
+FF5B ; Grapheme_Base # Ps FULLWIDTH LEFT CURLY BRACKET
+FF5C ; Grapheme_Base # Sm FULLWIDTH VERTICAL LINE
+FF5D ; Grapheme_Base # Pe FULLWIDTH RIGHT CURLY BRACKET
+FF5E ; Grapheme_Base # Sm FULLWIDTH TILDE
+FF5F ; Grapheme_Base # Ps FULLWIDTH LEFT WHITE PARENTHESIS
+FF60 ; Grapheme_Base # Pe FULLWIDTH RIGHT WHITE PARENTHESIS
+FF61 ; Grapheme_Base # Po HALFWIDTH IDEOGRAPHIC FULL STOP
+FF62 ; Grapheme_Base # Ps HALFWIDTH LEFT CORNER BRACKET
+FF63 ; Grapheme_Base # Pe HALFWIDTH RIGHT CORNER BRACKET
+FF64..FF65 ; Grapheme_Base # Po [2] HALFWIDTH IDEOGRAPHIC COMMA..HALFWIDTH KATAKANA MIDDLE DOT
+FF66..FF6F ; Grapheme_Base # Lo [10] HALFWIDTH KATAKANA LETTER WO..HALFWIDTH KATAKANA LETTER SMALL TU
+FF70 ; Grapheme_Base # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK
+FF71..FF9D ; Grapheme_Base # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAKANA LETTER N
+FFA0..FFBE ; Grapheme_Base # Lo [31] HALFWIDTH HANGUL FILLER..HALFWIDTH HANGUL LETTER HIEUH
+FFC2..FFC7 ; Grapheme_Base # Lo [6] HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E
+FFCA..FFCF ; Grapheme_Base # Lo [6] HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE
+FFD2..FFD7 ; Grapheme_Base # Lo [6] HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU
+FFDA..FFDC ; Grapheme_Base # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I
+FFE0..FFE1 ; Grapheme_Base # Sc [2] FULLWIDTH CENT SIGN..FULLWIDTH POUND SIGN
+FFE2 ; Grapheme_Base # Sm FULLWIDTH NOT SIGN
+FFE3 ; Grapheme_Base # Sk FULLWIDTH MACRON
+FFE4 ; Grapheme_Base # So FULLWIDTH BROKEN BAR
+FFE5..FFE6 ; Grapheme_Base # Sc [2] FULLWIDTH YEN SIGN..FULLWIDTH WON SIGN
+FFE8 ; Grapheme_Base # So HALFWIDTH FORMS LIGHT VERTICAL
+FFE9..FFEC ; Grapheme_Base # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS ARROW
+FFED..FFEE ; Grapheme_Base # So [2] HALFWIDTH BLACK SQUARE..HALFWIDTH WHITE CIRCLE
+FFFC..FFFD ; Grapheme_Base # So [2] OBJECT REPLACEMENT CHARACTER..REPLACEMENT CHARACTER
+10000..1000B ; Grapheme_Base # Lo [12] LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE
+1000D..10026 ; Grapheme_Base # Lo [26] LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO
+10028..1003A ; Grapheme_Base # Lo [19] LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO
+1003C..1003D ; Grapheme_Base # Lo [2] LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE
+1003F..1004D ; Grapheme_Base # Lo [15] LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO
+10050..1005D ; Grapheme_Base # Lo [14] LINEAR B SYMBOL B018..LINEAR B SYMBOL B089
+10080..100FA ; Grapheme_Base # Lo [123] LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305
+10100..10102 ; Grapheme_Base # Po [3] AEGEAN WORD SEPARATOR LINE..AEGEAN CHECK MARK
+10107..10133 ; Grapheme_Base # No [45] AEGEAN NUMBER ONE..AEGEAN NUMBER NINETY THOUSAND
+10137..1013F ; Grapheme_Base # So [9] AEGEAN WEIGHT BASE UNIT..AEGEAN MEASURE THIRD SUBUNIT
+10140..10174 ; Grapheme_Base # Nl [53] GREEK ACROPHONIC ATTIC ONE QUARTER..GREEK ACROPHONIC STRATIAN FIFTY MNAS
+10175..10178 ; Grapheme_Base # No [4] GREEK ONE HALF SIGN..GREEK THREE QUARTERS SIGN
+10179..10189 ; Grapheme_Base # So [17] GREEK YEAR SIGN..GREEK TRYBLION BASE SIGN
+1018A..1018B ; Grapheme_Base # No [2] GREEK ZERO SIGN..GREEK ONE QUARTER SIGN
+1018C..1018E ; Grapheme_Base # So [3] GREEK SINUSOID SIGN..NOMISMA SIGN
+10190..1019C ; Grapheme_Base # So [13] ROMAN SEXTANS SIGN..ASCIA SYMBOL
+101A0 ; Grapheme_Base # So GREEK SYMBOL TAU RHO
+101D0..101FC ; Grapheme_Base # So [45] PHAISTOS DISC SIGN PEDESTRIAN..PHAISTOS DISC SIGN WAVY BAND
+10280..1029C ; Grapheme_Base # Lo [29] LYCIAN LETTER A..LYCIAN LETTER X
+102A0..102D0 ; Grapheme_Base # Lo [49] CARIAN LETTER A..CARIAN LETTER UUU3
+102E1..102FB ; Grapheme_Base # No [27] COPTIC EPACT DIGIT ONE..COPTIC EPACT NUMBER NINE HUNDRED
+10300..1031F ; Grapheme_Base # Lo [32] OLD ITALIC LETTER A..OLD ITALIC LETTER ESS
+10320..10323 ; Grapheme_Base # No [4] OLD ITALIC NUMERAL ONE..OLD ITALIC NUMERAL FIFTY
+1032D..10340 ; Grapheme_Base # Lo [20] OLD ITALIC LETTER YE..GOTHIC LETTER PAIRTHRA
+10341 ; Grapheme_Base # Nl GOTHIC LETTER NINETY
+10342..10349 ; Grapheme_Base # Lo [8] GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL
+1034A ; Grapheme_Base # Nl GOTHIC LETTER NINE HUNDRED
+10350..10375 ; Grapheme_Base # Lo [38] OLD PERMIC LETTER AN..OLD PERMIC LETTER IA
+10380..1039D ; Grapheme_Base # Lo [30] UGARITIC LETTER ALPA..UGARITIC LETTER SSU
+1039F ; Grapheme_Base # Po UGARITIC WORD DIVIDER
+103A0..103C3 ; Grapheme_Base # Lo [36] OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA
+103C8..103CF ; Grapheme_Base # Lo [8] OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH
+103D0 ; Grapheme_Base # Po OLD PERSIAN WORD DIVIDER
+103D1..103D5 ; Grapheme_Base # Nl [5] OLD PERSIAN NUMBER ONE..OLD PERSIAN NUMBER HUNDRED
+10400..1044F ; Grapheme_Base # L& [80] DESERET CAPITAL LETTER LONG I..DESERET SMALL LETTER EW
+10450..1049D ; Grapheme_Base # Lo [78] SHAVIAN LETTER PEEP..OSMANYA LETTER OO
+104A0..104A9 ; Grapheme_Base # Nd [10] OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE
+104B0..104D3 ; Grapheme_Base # L& [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA
+104D8..104FB ; Grapheme_Base # L& [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA
+10500..10527 ; Grapheme_Base # Lo [40] ELBASAN LETTER A..ELBASAN LETTER KHE
+10530..10563 ; Grapheme_Base # Lo [52] CAUCASIAN ALBANIAN LETTER ALT..CAUCASIAN ALBANIAN LETTER KIW
+1056F ; Grapheme_Base # Po CAUCASIAN ALBANIAN CITATION MARK
+10570..1057A ; Grapheme_Base # L& [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA
+1057C..1058A ; Grapheme_Base # L& [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE
+1058C..10592 ; Grapheme_Base # L& [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE
+10594..10595 ; Grapheme_Base # L& [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE
+10597..105A1 ; Grapheme_Base # L& [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA
+105A3..105B1 ; Grapheme_Base # L& [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE
+105B3..105B9 ; Grapheme_Base # L& [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE
+105BB..105BC ; Grapheme_Base # L& [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE
+105C0..105F3 ; Grapheme_Base # Lo [52] TODHRI LETTER A..TODHRI LETTER OO
+10600..10736 ; Grapheme_Base # Lo [311] LINEAR A SIGN AB001..LINEAR A SIGN A664
+10740..10755 ; Grapheme_Base # Lo [22] LINEAR A SIGN A701 A..LINEAR A SIGN A732 JE
+10760..10767 ; Grapheme_Base # Lo [8] LINEAR A SIGN A800..LINEAR A SIGN A807
+10780..10785 ; Grapheme_Base # Lm [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK
+10787..107B0 ; Grapheme_Base # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK
+107B2..107BA ; Grapheme_Base # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL
+10800..10805 ; Grapheme_Base # Lo [6] CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA
+10808 ; Grapheme_Base # Lo CYPRIOT SYLLABLE JO
+1080A..10835 ; Grapheme_Base # Lo [44] CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO
+10837..10838 ; Grapheme_Base # Lo [2] CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE
+1083C ; Grapheme_Base # Lo CYPRIOT SYLLABLE ZA
+1083F..10855 ; Grapheme_Base # Lo [23] CYPRIOT SYLLABLE ZO..IMPERIAL ARAMAIC LETTER TAW
+10857 ; Grapheme_Base # Po IMPERIAL ARAMAIC SECTION SIGN
+10858..1085F ; Grapheme_Base # No [8] IMPERIAL ARAMAIC NUMBER ONE..IMPERIAL ARAMAIC NUMBER TEN THOUSAND
+10860..10876 ; Grapheme_Base # Lo [23] PALMYRENE LETTER ALEPH..PALMYRENE LETTER TAW
+10877..10878 ; Grapheme_Base # So [2] PALMYRENE LEFT-POINTING FLEURON..PALMYRENE RIGHT-POINTING FLEURON
+10879..1087F ; Grapheme_Base # No [7] PALMYRENE NUMBER ONE..PALMYRENE NUMBER TWENTY
+10880..1089E ; Grapheme_Base # Lo [31] NABATAEAN LETTER FINAL ALEPH..NABATAEAN LETTER TAW
+108A7..108AF ; Grapheme_Base # No [9] NABATAEAN NUMBER ONE..NABATAEAN NUMBER ONE HUNDRED
+108E0..108F2 ; Grapheme_Base # Lo [19] HATRAN LETTER ALEPH..HATRAN LETTER QOPH
+108F4..108F5 ; Grapheme_Base # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW
+108FB..108FF ; Grapheme_Base # No [5] HATRAN NUMBER ONE..HATRAN NUMBER ONE HUNDRED
+10900..10915 ; Grapheme_Base # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU
+10916..1091B ; Grapheme_Base # No [6] PHOENICIAN NUMBER ONE..PHOENICIAN NUMBER THREE
+1091F ; Grapheme_Base # Po PHOENICIAN WORD SEPARATOR
+10920..10939 ; Grapheme_Base # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C
+1093F ; Grapheme_Base # Po LYDIAN TRIANGULAR MARK
+10980..109B7 ; Grapheme_Base # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA
+109BC..109BD ; Grapheme_Base # No [2] MEROITIC CURSIVE FRACTION ELEVEN TWELFTHS..MEROITIC CURSIVE FRACTION ONE HALF
+109BE..109BF ; Grapheme_Base # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN
+109C0..109CF ; Grapheme_Base # No [16] MEROITIC CURSIVE NUMBER ONE..MEROITIC CURSIVE NUMBER SEVENTY
+109D2..109FF ; Grapheme_Base # No [46] MEROITIC CURSIVE NUMBER ONE HUNDRED..MEROITIC CURSIVE FRACTION TEN TWELFTHS
+10A00 ; Grapheme_Base # Lo KHAROSHTHI LETTER A
+10A10..10A13 ; Grapheme_Base # Lo [4] KHAROSHTHI LETTER KA..KHAROSHTHI LETTER GHA
+10A15..10A17 ; Grapheme_Base # Lo [3] KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA
+10A19..10A35 ; Grapheme_Base # Lo [29] KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER VHA
+10A40..10A48 ; Grapheme_Base # No [9] KHAROSHTHI DIGIT ONE..KHAROSHTHI FRACTION ONE HALF
+10A50..10A58 ; Grapheme_Base # Po [9] KHAROSHTHI PUNCTUATION DOT..KHAROSHTHI PUNCTUATION LINES
+10A60..10A7C ; Grapheme_Base # Lo [29] OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH
+10A7D..10A7E ; Grapheme_Base # No [2] OLD SOUTH ARABIAN NUMBER ONE..OLD SOUTH ARABIAN NUMBER FIFTY
+10A7F ; Grapheme_Base # Po OLD SOUTH ARABIAN NUMERIC INDICATOR
+10A80..10A9C ; Grapheme_Base # Lo [29] OLD NORTH ARABIAN LETTER HEH..OLD NORTH ARABIAN LETTER ZAH
+10A9D..10A9F ; Grapheme_Base # No [3] OLD NORTH ARABIAN NUMBER ONE..OLD NORTH ARABIAN NUMBER TWENTY
+10AC0..10AC7 ; Grapheme_Base # Lo [8] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER WAW
+10AC8 ; Grapheme_Base # So MANICHAEAN SIGN UD
+10AC9..10AE4 ; Grapheme_Base # Lo [28] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER TAW
+10AEB..10AEF ; Grapheme_Base # No [5] MANICHAEAN NUMBER ONE..MANICHAEAN NUMBER ONE HUNDRED
+10AF0..10AF6 ; Grapheme_Base # Po [7] MANICHAEAN PUNCTUATION STAR..MANICHAEAN PUNCTUATION LINE FILLER
+10B00..10B35 ; Grapheme_Base # Lo [54] AVESTAN LETTER A..AVESTAN LETTER HE
+10B39..10B3F ; Grapheme_Base # Po [7] AVESTAN ABBREVIATION MARK..LARGE ONE RING OVER TWO RINGS PUNCTUATION
+10B40..10B55 ; Grapheme_Base # Lo [22] INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW
+10B58..10B5F ; Grapheme_Base # No [8] INSCRIPTIONAL PARTHIAN NUMBER ONE..INSCRIPTIONAL PARTHIAN NUMBER ONE THOUSAND
+10B60..10B72 ; Grapheme_Base # Lo [19] INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW
+10B78..10B7F ; Grapheme_Base # No [8] INSCRIPTIONAL PAHLAVI NUMBER ONE..INSCRIPTIONAL PAHLAVI NUMBER ONE THOUSAND
+10B80..10B91 ; Grapheme_Base # Lo [18] PSALTER PAHLAVI LETTER ALEPH..PSALTER PAHLAVI LETTER TAW
+10B99..10B9C ; Grapheme_Base # Po [4] PSALTER PAHLAVI SECTION MARK..PSALTER PAHLAVI FOUR DOTS WITH DOT
+10BA9..10BAF ; Grapheme_Base # No [7] PSALTER PAHLAVI NUMBER ONE..PSALTER PAHLAVI NUMBER ONE HUNDRED
+10C00..10C48 ; Grapheme_Base # Lo [73] OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH
+10C80..10CB2 ; Grapheme_Base # L& [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US
+10CC0..10CF2 ; Grapheme_Base # L& [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US
+10CFA..10CFF ; Grapheme_Base # No [6] OLD HUNGARIAN NUMBER ONE..OLD HUNGARIAN NUMBER ONE THOUSAND
+10D00..10D23 ; Grapheme_Base # Lo [36] HANIFI ROHINGYA LETTER A..HANIFI ROHINGYA MARK NA KHONNA
+10D30..10D39 ; Grapheme_Base # Nd [10] HANIFI ROHINGYA DIGIT ZERO..HANIFI ROHINGYA DIGIT NINE
+10D40..10D49 ; Grapheme_Base # Nd [10] GARAY DIGIT ZERO..GARAY DIGIT NINE
+10D4A..10D4D ; Grapheme_Base # Lo [4] GARAY VOWEL SIGN A..GARAY VOWEL SIGN EE
+10D4E ; Grapheme_Base # Lm GARAY VOWEL LENGTH MARK
+10D4F ; Grapheme_Base # Lo GARAY SUKUN
+10D50..10D65 ; Grapheme_Base # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA
+10D6E ; Grapheme_Base # Pd GARAY HYPHEN
+10D6F ; Grapheme_Base # Lm GARAY REDUPLICATION MARK
+10D70..10D85 ; Grapheme_Base # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA
+10D8E..10D8F ; Grapheme_Base # Sm [2] GARAY PLUS SIGN..GARAY MINUS SIGN
+10E60..10E7E ; Grapheme_Base # No [31] RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS
+10E80..10EA9 ; Grapheme_Base # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET
+10EAD ; Grapheme_Base # Pd YEZIDI HYPHENATION MARK
+10EB0..10EB1 ; Grapheme_Base # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE
+10EC2..10EC4 ; Grapheme_Base # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW
+10F00..10F1C ; Grapheme_Base # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL
+10F1D..10F26 ; Grapheme_Base # No [10] OLD SOGDIAN NUMBER ONE..OLD SOGDIAN FRACTION ONE HALF
+10F27 ; Grapheme_Base # Lo OLD SOGDIAN LIGATURE AYIN-DALETH
+10F30..10F45 ; Grapheme_Base # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN
+10F51..10F54 ; Grapheme_Base # No [4] SOGDIAN NUMBER ONE..SOGDIAN NUMBER ONE HUNDRED
+10F55..10F59 ; Grapheme_Base # Po [5] SOGDIAN PUNCTUATION TWO VERTICAL BARS..SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT
+10F70..10F81 ; Grapheme_Base # Lo [18] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER LESH
+10F86..10F89 ; Grapheme_Base # Po [4] OLD UYGHUR PUNCTUATION BAR..OLD UYGHUR PUNCTUATION FOUR DOTS
+10FB0..10FC4 ; Grapheme_Base # Lo [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW
+10FC5..10FCB ; Grapheme_Base # No [7] CHORASMIAN NUMBER ONE..CHORASMIAN NUMBER ONE HUNDRED
+10FE0..10FF6 ; Grapheme_Base # Lo [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH
+11000 ; Grapheme_Base # Mc BRAHMI SIGN CANDRABINDU
+11002 ; Grapheme_Base # Mc BRAHMI SIGN VISARGA
+11003..11037 ; Grapheme_Base # Lo [53] BRAHMI SIGN JIHVAMULIYA..BRAHMI LETTER OLD TAMIL NNNA
+11047..1104D ; Grapheme_Base # Po [7] BRAHMI DANDA..BRAHMI PUNCTUATION LOTUS
+11052..11065 ; Grapheme_Base # No [20] BRAHMI NUMBER ONE..BRAHMI NUMBER ONE THOUSAND
+11066..1106F ; Grapheme_Base # Nd [10] BRAHMI DIGIT ZERO..BRAHMI DIGIT NINE
+11071..11072 ; Grapheme_Base # Lo [2] BRAHMI LETTER OLD TAMIL SHORT E..BRAHMI LETTER OLD TAMIL SHORT O
+11075 ; Grapheme_Base # Lo BRAHMI LETTER OLD TAMIL LLA
+11082 ; Grapheme_Base # Mc KAITHI SIGN VISARGA
+11083..110AF ; Grapheme_Base # Lo [45] KAITHI LETTER A..KAITHI LETTER HA
+110B0..110B2 ; Grapheme_Base # Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II
+110B7..110B8 ; Grapheme_Base # Mc [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU
+110BB..110BC ; Grapheme_Base # Po [2] KAITHI ABBREVIATION SIGN..KAITHI ENUMERATION SIGN
+110BE..110C1 ; Grapheme_Base # Po [4] KAITHI SECTION MARK..KAITHI DOUBLE DANDA
+110D0..110E8 ; Grapheme_Base # Lo [25] SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE
+110F0..110F9 ; Grapheme_Base # Nd [10] SORA SOMPENG DIGIT ZERO..SORA SOMPENG DIGIT NINE
+11103..11126 ; Grapheme_Base # Lo [36] CHAKMA LETTER AA..CHAKMA LETTER HAA
+1112C ; Grapheme_Base # Mc CHAKMA VOWEL SIGN E
+11136..1113F ; Grapheme_Base # Nd [10] CHAKMA DIGIT ZERO..CHAKMA DIGIT NINE
+11140..11143 ; Grapheme_Base # Po [4] CHAKMA SECTION MARK..CHAKMA QUESTION MARK
+11144 ; Grapheme_Base # Lo CHAKMA LETTER LHAA
+11145..11146 ; Grapheme_Base # Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI
+11147 ; Grapheme_Base # Lo CHAKMA LETTER VAA
+11150..11172 ; Grapheme_Base # Lo [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA
+11174..11175 ; Grapheme_Base # Po [2] MAHAJANI ABBREVIATION SIGN..MAHAJANI SECTION MARK
+11176 ; Grapheme_Base # Lo MAHAJANI LIGATURE SHRI
+11182 ; Grapheme_Base # Mc SHARADA SIGN VISARGA
+11183..111B2 ; Grapheme_Base # Lo [48] SHARADA LETTER A..SHARADA LETTER HA
+111B3..111B5 ; Grapheme_Base # Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II
+111BF ; Grapheme_Base # Mc SHARADA VOWEL SIGN AU
+111C1..111C4 ; Grapheme_Base # Lo [4] SHARADA SIGN AVAGRAHA..SHARADA OM
+111C5..111C8 ; Grapheme_Base # Po [4] SHARADA DANDA..SHARADA SEPARATOR
+111CD ; Grapheme_Base # Po SHARADA SUTRA MARK
+111CE ; Grapheme_Base # Mc SHARADA VOWEL SIGN PRISHTHAMATRA E
+111D0..111D9 ; Grapheme_Base # Nd [10] SHARADA DIGIT ZERO..SHARADA DIGIT NINE
+111DA ; Grapheme_Base # Lo SHARADA EKAM
+111DB ; Grapheme_Base # Po SHARADA SIGN SIDDHAM
+111DC ; Grapheme_Base # Lo SHARADA HEADSTROKE
+111DD..111DF ; Grapheme_Base # Po [3] SHARADA CONTINUATION SIGN..SHARADA SECTION MARK-2
+111E1..111F4 ; Grapheme_Base # No [20] SINHALA ARCHAIC DIGIT ONE..SINHALA ARCHAIC NUMBER ONE THOUSAND
+11200..11211 ; Grapheme_Base # Lo [18] KHOJKI LETTER A..KHOJKI LETTER JJA
+11213..1122B ; Grapheme_Base # Lo [25] KHOJKI LETTER NYA..KHOJKI LETTER LLA
+1122C..1122E ; Grapheme_Base # Mc [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II
+11232..11233 ; Grapheme_Base # Mc [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU
+11238..1123D ; Grapheme_Base # Po [6] KHOJKI DANDA..KHOJKI ABBREVIATION SIGN
+1123F..11240 ; Grapheme_Base # Lo [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I
+11280..11286 ; Grapheme_Base # Lo [7] MULTANI LETTER A..MULTANI LETTER GA
+11288 ; Grapheme_Base # Lo MULTANI LETTER GHA
+1128A..1128D ; Grapheme_Base # Lo [4] MULTANI LETTER CA..MULTANI LETTER JJA
+1128F..1129D ; Grapheme_Base # Lo [15] MULTANI LETTER NYA..MULTANI LETTER BA
+1129F..112A8 ; Grapheme_Base # Lo [10] MULTANI LETTER BHA..MULTANI LETTER RHA
+112A9 ; Grapheme_Base # Po MULTANI SECTION MARK
+112B0..112DE ; Grapheme_Base # Lo [47] KHUDAWADI LETTER A..KHUDAWADI LETTER HA
+112E0..112E2 ; Grapheme_Base # Mc [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II
+112F0..112F9 ; Grapheme_Base # Nd [10] KHUDAWADI DIGIT ZERO..KHUDAWADI DIGIT NINE
+11302..11303 ; Grapheme_Base # Mc [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA
+11305..1130C ; Grapheme_Base # Lo [8] GRANTHA LETTER A..GRANTHA LETTER VOCALIC L
+1130F..11310 ; Grapheme_Base # Lo [2] GRANTHA LETTER EE..GRANTHA LETTER AI
+11313..11328 ; Grapheme_Base # Lo [22] GRANTHA LETTER OO..GRANTHA LETTER NA
+1132A..11330 ; Grapheme_Base # Lo [7] GRANTHA LETTER PA..GRANTHA LETTER RA
+11332..11333 ; Grapheme_Base # Lo [2] GRANTHA LETTER LA..GRANTHA LETTER LLA
+11335..11339 ; Grapheme_Base # Lo [5] GRANTHA LETTER VA..GRANTHA LETTER HA
+1133D ; Grapheme_Base # Lo GRANTHA SIGN AVAGRAHA
+1133F ; Grapheme_Base # Mc GRANTHA VOWEL SIGN I
+11341..11344 ; Grapheme_Base # Mc [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR
+11347..11348 ; Grapheme_Base # Mc [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI
+1134B..1134C ; Grapheme_Base # Mc [2] GRANTHA VOWEL SIGN OO..GRANTHA VOWEL SIGN AU
+11350 ; Grapheme_Base # Lo GRANTHA OM
+1135D..11361 ; Grapheme_Base # Lo [5] GRANTHA SIGN PLUTA..GRANTHA LETTER VOCALIC LL
+11362..11363 ; Grapheme_Base # Mc [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL
+11380..11389 ; Grapheme_Base # Lo [10] TULU-TIGALARI LETTER A..TULU-TIGALARI LETTER VOCALIC LL
+1138B ; Grapheme_Base # Lo TULU-TIGALARI LETTER EE
+1138E ; Grapheme_Base # Lo TULU-TIGALARI LETTER AI
+11390..113B5 ; Grapheme_Base # Lo [38] TULU-TIGALARI LETTER OO..TULU-TIGALARI LETTER LLLA
+113B7 ; Grapheme_Base # Lo TULU-TIGALARI SIGN AVAGRAHA
+113B9..113BA ; Grapheme_Base # Mc [2] TULU-TIGALARI VOWEL SIGN I..TULU-TIGALARI VOWEL SIGN II
+113CA ; Grapheme_Base # Mc TULU-TIGALARI SIGN CANDRA ANUNASIKA
+113CC..113CD ; Grapheme_Base # Mc [2] TULU-TIGALARI SIGN ANUSVARA..TULU-TIGALARI SIGN VISARGA
+113D1 ; Grapheme_Base # Lo TULU-TIGALARI REPHA
+113D3 ; Grapheme_Base # Lo TULU-TIGALARI SIGN PLUTA
+113D4..113D5 ; Grapheme_Base # Po [2] TULU-TIGALARI DANDA..TULU-TIGALARI DOUBLE DANDA
+113D7..113D8 ; Grapheme_Base # Po [2] TULU-TIGALARI SIGN OM PUSHPIKA..TULU-TIGALARI SIGN SHRII PUSHPIKA
+11400..11434 ; Grapheme_Base # Lo [53] NEWA LETTER A..NEWA LETTER HA
+11435..11437 ; Grapheme_Base # Mc [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II
+11440..11441 ; Grapheme_Base # Mc [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU
+11445 ; Grapheme_Base # Mc NEWA SIGN VISARGA
+11447..1144A ; Grapheme_Base # Lo [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI
+1144B..1144F ; Grapheme_Base # Po [5] NEWA DANDA..NEWA ABBREVIATION SIGN
+11450..11459 ; Grapheme_Base # Nd [10] NEWA DIGIT ZERO..NEWA DIGIT NINE
+1145A..1145B ; Grapheme_Base # Po [2] NEWA DOUBLE COMMA..NEWA PLACEHOLDER MARK
+1145D ; Grapheme_Base # Po NEWA INSERTION SIGN
+1145F..11461 ; Grapheme_Base # Lo [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA
+11480..114AF ; Grapheme_Base # Lo [48] TIRHUTA ANJI..TIRHUTA LETTER HA
+114B1..114B2 ; Grapheme_Base # Mc [2] TIRHUTA VOWEL SIGN I..TIRHUTA VOWEL SIGN II
+114B9 ; Grapheme_Base # Mc TIRHUTA VOWEL SIGN E
+114BB..114BC ; Grapheme_Base # Mc [2] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN O
+114BE ; Grapheme_Base # Mc TIRHUTA VOWEL SIGN AU
+114C1 ; Grapheme_Base # Mc TIRHUTA SIGN VISARGA
+114C4..114C5 ; Grapheme_Base # Lo [2] TIRHUTA SIGN AVAGRAHA..TIRHUTA GVANG
+114C6 ; Grapheme_Base # Po TIRHUTA ABBREVIATION SIGN
+114C7 ; Grapheme_Base # Lo TIRHUTA OM
+114D0..114D9 ; Grapheme_Base # Nd [10] TIRHUTA DIGIT ZERO..TIRHUTA DIGIT NINE
+11580..115AE ; Grapheme_Base # Lo [47] SIDDHAM LETTER A..SIDDHAM LETTER HA
+115B0..115B1 ; Grapheme_Base # Mc [2] SIDDHAM VOWEL SIGN I..SIDDHAM VOWEL SIGN II
+115B8..115BB ; Grapheme_Base # Mc [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU
+115BE ; Grapheme_Base # Mc SIDDHAM SIGN VISARGA
+115C1..115D7 ; Grapheme_Base # Po [23] SIDDHAM SIGN SIDDHAM..SIDDHAM SECTION MARK WITH CIRCLES AND FOUR ENCLOSURES
+115D8..115DB ; Grapheme_Base # Lo [4] SIDDHAM LETTER THREE-CIRCLE ALTERNATE I..SIDDHAM LETTER ALTERNATE U
+11600..1162F ; Grapheme_Base # Lo [48] MODI LETTER A..MODI LETTER LLA
+11630..11632 ; Grapheme_Base # Mc [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II
+1163B..1163C ; Grapheme_Base # Mc [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU
+1163E ; Grapheme_Base # Mc MODI SIGN VISARGA
+11641..11643 ; Grapheme_Base # Po [3] MODI DANDA..MODI ABBREVIATION SIGN
+11644 ; Grapheme_Base # Lo MODI SIGN HUVA
+11650..11659 ; Grapheme_Base # Nd [10] MODI DIGIT ZERO..MODI DIGIT NINE
+11660..1166C ; Grapheme_Base # Po [13] MONGOLIAN BIRGA WITH ORNAMENT..MONGOLIAN TURNED SWIRL BIRGA WITH DOUBLE ORNAMENT
+11680..116AA ; Grapheme_Base # Lo [43] TAKRI LETTER A..TAKRI LETTER RRA
+116AC ; Grapheme_Base # Mc TAKRI SIGN VISARGA
+116AE..116AF ; Grapheme_Base # Mc [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II
+116B8 ; Grapheme_Base # Lo TAKRI LETTER ARCHAIC KHA
+116B9 ; Grapheme_Base # Po TAKRI ABBREVIATION SIGN
+116C0..116C9 ; Grapheme_Base # Nd [10] TAKRI DIGIT ZERO..TAKRI DIGIT NINE
+116D0..116E3 ; Grapheme_Base # Nd [20] MYANMAR PAO DIGIT ZERO..MYANMAR EASTERN PWO KAREN DIGIT NINE
+11700..1171A ; Grapheme_Base # Lo [27] AHOM LETTER KA..AHOM LETTER ALTERNATE BA
+1171E ; Grapheme_Base # Mc AHOM CONSONANT SIGN MEDIAL RA
+11720..11721 ; Grapheme_Base # Mc [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA
+11726 ; Grapheme_Base # Mc AHOM VOWEL SIGN E
+11730..11739 ; Grapheme_Base # Nd [10] AHOM DIGIT ZERO..AHOM DIGIT NINE
+1173A..1173B ; Grapheme_Base # No [2] AHOM NUMBER TEN..AHOM NUMBER TWENTY
+1173C..1173E ; Grapheme_Base # Po [3] AHOM SIGN SMALL SECTION..AHOM SIGN RULAI
+1173F ; Grapheme_Base # So AHOM SYMBOL VI
+11740..11746 ; Grapheme_Base # Lo [7] AHOM LETTER CA..AHOM LETTER LLA
+11800..1182B ; Grapheme_Base # Lo [44] DOGRA LETTER A..DOGRA LETTER RRA
+1182C..1182E ; Grapheme_Base # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II
+11838 ; Grapheme_Base # Mc DOGRA SIGN VISARGA
+1183B ; Grapheme_Base # Po DOGRA ABBREVIATION SIGN
+118A0..118DF ; Grapheme_Base # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO
+118E0..118E9 ; Grapheme_Base # Nd [10] WARANG CITI DIGIT ZERO..WARANG CITI DIGIT NINE
+118EA..118F2 ; Grapheme_Base # No [9] WARANG CITI NUMBER TEN..WARANG CITI NUMBER NINETY
+118FF..11906 ; Grapheme_Base # Lo [8] WARANG CITI OM..DIVES AKURU LETTER E
+11909 ; Grapheme_Base # Lo DIVES AKURU LETTER O
+1190C..11913 ; Grapheme_Base # Lo [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA
+11915..11916 ; Grapheme_Base # Lo [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA
+11918..1192F ; Grapheme_Base # Lo [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA
+11931..11935 ; Grapheme_Base # Mc [5] DIVES AKURU VOWEL SIGN I..DIVES AKURU VOWEL SIGN E
+11937..11938 ; Grapheme_Base # Mc [2] DIVES AKURU VOWEL SIGN AI..DIVES AKURU VOWEL SIGN O
+1193F ; Grapheme_Base # Lo DIVES AKURU PREFIXED NASAL SIGN
+11940 ; Grapheme_Base # Mc DIVES AKURU MEDIAL YA
+11941 ; Grapheme_Base # Lo DIVES AKURU INITIAL RA
+11942 ; Grapheme_Base # Mc DIVES AKURU MEDIAL RA
+11944..11946 ; Grapheme_Base # Po [3] DIVES AKURU DOUBLE DANDA..DIVES AKURU END OF TEXT MARK
+11950..11959 ; Grapheme_Base # Nd [10] DIVES AKURU DIGIT ZERO..DIVES AKURU DIGIT NINE
+119A0..119A7 ; Grapheme_Base # Lo [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR
+119AA..119D0 ; Grapheme_Base # Lo [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA
+119D1..119D3 ; Grapheme_Base # Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II
+119DC..119DF ; Grapheme_Base # Mc [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA
+119E1 ; Grapheme_Base # Lo NANDINAGARI SIGN AVAGRAHA
+119E2 ; Grapheme_Base # Po NANDINAGARI SIGN SIDDHAM
+119E3 ; Grapheme_Base # Lo NANDINAGARI HEADSTROKE
+119E4 ; Grapheme_Base # Mc NANDINAGARI VOWEL SIGN PRISHTHAMATRA E
+11A00 ; Grapheme_Base # Lo ZANABAZAR SQUARE LETTER A
+11A0B..11A32 ; Grapheme_Base # Lo [40] ZANABAZAR SQUARE LETTER KA..ZANABAZAR SQUARE LETTER KSSA
+11A39 ; Grapheme_Base # Mc ZANABAZAR SQUARE SIGN VISARGA
+11A3A ; Grapheme_Base # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA
+11A3F..11A46 ; Grapheme_Base # Po [8] ZANABAZAR SQUARE INITIAL HEAD MARK..ZANABAZAR SQUARE CLOSING DOUBLE-LINED HEAD MARK
+11A50 ; Grapheme_Base # Lo SOYOMBO LETTER A
+11A57..11A58 ; Grapheme_Base # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU
+11A5C..11A89 ; Grapheme_Base # Lo [46] SOYOMBO LETTER KA..SOYOMBO CLUSTER-INITIAL LETTER SA
+11A97 ; Grapheme_Base # Mc SOYOMBO SIGN VISARGA
+11A9A..11A9C ; Grapheme_Base # Po [3] SOYOMBO MARK TSHEG..SOYOMBO MARK DOUBLE SHAD
+11A9D ; Grapheme_Base # Lo SOYOMBO MARK PLUTA
+11A9E..11AA2 ; Grapheme_Base # Po [5] SOYOMBO HEAD MARK WITH MOON AND SUN AND TRIPLE FLAME..SOYOMBO TERMINAL MARK-2
+11AB0..11AF8 ; Grapheme_Base # Lo [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL
+11B00..11B09 ; Grapheme_Base # Po [10] DEVANAGARI HEAD MARK..DEVANAGARI SIGN MINDU
+11BC0..11BE0 ; Grapheme_Base # Lo [33] SUNUWAR LETTER DEVI..SUNUWAR LETTER KLOKO
+11BE1 ; Grapheme_Base # Po SUNUWAR SIGN PVO
+11BF0..11BF9 ; Grapheme_Base # Nd [10] SUNUWAR DIGIT ZERO..SUNUWAR DIGIT NINE
+11C00..11C08 ; Grapheme_Base # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L
+11C0A..11C2E ; Grapheme_Base # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA
+11C2F ; Grapheme_Base # Mc BHAIKSUKI VOWEL SIGN AA
+11C3E ; Grapheme_Base # Mc BHAIKSUKI SIGN VISARGA
+11C40 ; Grapheme_Base # Lo BHAIKSUKI SIGN AVAGRAHA
+11C41..11C45 ; Grapheme_Base # Po [5] BHAIKSUKI DANDA..BHAIKSUKI GAP FILLER-2
+11C50..11C59 ; Grapheme_Base # Nd [10] BHAIKSUKI DIGIT ZERO..BHAIKSUKI DIGIT NINE
+11C5A..11C6C ; Grapheme_Base # No [19] BHAIKSUKI NUMBER ONE..BHAIKSUKI HUNDREDS UNIT MARK
+11C70..11C71 ; Grapheme_Base # Po [2] MARCHEN HEAD MARK..MARCHEN MARK SHAD
+11C72..11C8F ; Grapheme_Base # Lo [30] MARCHEN LETTER KA..MARCHEN LETTER A
+11CA9 ; Grapheme_Base # Mc MARCHEN SUBJOINED LETTER YA
+11CB1 ; Grapheme_Base # Mc MARCHEN VOWEL SIGN I
+11CB4 ; Grapheme_Base # Mc MARCHEN VOWEL SIGN O
+11D00..11D06 ; Grapheme_Base # Lo [7] MASARAM GONDI LETTER A..MASARAM GONDI LETTER E
+11D08..11D09 ; Grapheme_Base # Lo [2] MASARAM GONDI LETTER AI..MASARAM GONDI LETTER O
+11D0B..11D30 ; Grapheme_Base # Lo [38] MASARAM GONDI LETTER AU..MASARAM GONDI LETTER TRA
+11D46 ; Grapheme_Base # Lo MASARAM GONDI REPHA
+11D50..11D59 ; Grapheme_Base # Nd [10] MASARAM GONDI DIGIT ZERO..MASARAM GONDI DIGIT NINE
+11D60..11D65 ; Grapheme_Base # Lo [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU
+11D67..11D68 ; Grapheme_Base # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI
+11D6A..11D89 ; Grapheme_Base # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA
+11D8A..11D8E ; Grapheme_Base # Mc [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU
+11D93..11D94 ; Grapheme_Base # Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU
+11D96 ; Grapheme_Base # Mc GUNJALA GONDI SIGN VISARGA
+11D98 ; Grapheme_Base # Lo GUNJALA GONDI OM
+11DA0..11DA9 ; Grapheme_Base # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE
+11EE0..11EF2 ; Grapheme_Base # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA
+11EF5..11EF6 ; Grapheme_Base # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O
+11EF7..11EF8 ; Grapheme_Base # Po [2] MAKASAR PASSIMBANG..MAKASAR END OF SECTION
+11F02 ; Grapheme_Base # Lo KAWI SIGN REPHA
+11F03 ; Grapheme_Base # Mc KAWI SIGN VISARGA
+11F04..11F10 ; Grapheme_Base # Lo [13] KAWI LETTER A..KAWI LETTER O
+11F12..11F33 ; Grapheme_Base # Lo [34] KAWI LETTER KA..KAWI LETTER JNYA
+11F34..11F35 ; Grapheme_Base # Mc [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA
+11F3E..11F3F ; Grapheme_Base # Mc [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI
+11F43..11F4F ; Grapheme_Base # Po [13] KAWI DANDA..KAWI PUNCTUATION CLOSING SPIRAL
+11F50..11F59 ; Grapheme_Base # Nd [10] KAWI DIGIT ZERO..KAWI DIGIT NINE
+11FB0 ; Grapheme_Base # Lo LISU LETTER YHA
+11FC0..11FD4 ; Grapheme_Base # No [21] TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH..TAMIL FRACTION DOWNSCALING FACTOR KIIZH
+11FD5..11FDC ; Grapheme_Base # So [8] TAMIL SIGN NEL..TAMIL SIGN MUKKURUNI
+11FDD..11FE0 ; Grapheme_Base # Sc [4] TAMIL SIGN KAACU..TAMIL SIGN VARAAKAN
+11FE1..11FF1 ; Grapheme_Base # So [17] TAMIL SIGN PAARAM..TAMIL SIGN VAKAIYARAA
+11FFF ; Grapheme_Base # Po TAMIL PUNCTUATION END OF TEXT
+12000..12399 ; Grapheme_Base # Lo [922] CUNEIFORM SIGN A..CUNEIFORM SIGN U U
+12400..1246E ; Grapheme_Base # Nl [111] CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM
+12470..12474 ; Grapheme_Base # Po [5] CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER..CUNEIFORM PUNCTUATION SIGN DIAGONAL QUADCOLON
+12480..12543 ; Grapheme_Base # Lo [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU
+12F90..12FF0 ; Grapheme_Base # Lo [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114
+12FF1..12FF2 ; Grapheme_Base # Po [2] CYPRO-MINOAN SIGN CM301..CYPRO-MINOAN SIGN CM302
+13000..1342F ; Grapheme_Base # Lo [1072] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH V011D
+13441..13446 ; Grapheme_Base # Lo [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN
+13460..143FA ; Grapheme_Base # Lo [3995] EGYPTIAN HIEROGLYPH-13460..EGYPTIAN HIEROGLYPH-143FA
+14400..14646 ; Grapheme_Base # Lo [583] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A530
+16100..1611D ; Grapheme_Base # Lo [30] GURUNG KHEMA LETTER A..GURUNG KHEMA LETTER SA
+1612A..1612C ; Grapheme_Base # Mc [3] GURUNG KHEMA CONSONANT SIGN MEDIAL YA..GURUNG KHEMA CONSONANT SIGN MEDIAL HA
+16130..16139 ; Grapheme_Base # Nd [10] GURUNG KHEMA DIGIT ZERO..GURUNG KHEMA DIGIT NINE
+16800..16A38 ; Grapheme_Base # Lo [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ
+16A40..16A5E ; Grapheme_Base # Lo [31] MRO LETTER TA..MRO LETTER TEK
+16A60..16A69 ; Grapheme_Base # Nd [10] MRO DIGIT ZERO..MRO DIGIT NINE
+16A6E..16A6F ; Grapheme_Base # Po [2] MRO DANDA..MRO DOUBLE DANDA
+16A70..16ABE ; Grapheme_Base # Lo [79] TANGSA LETTER OZ..TANGSA LETTER ZA
+16AC0..16AC9 ; Grapheme_Base # Nd [10] TANGSA DIGIT ZERO..TANGSA DIGIT NINE
+16AD0..16AED ; Grapheme_Base # Lo [30] BASSA VAH LETTER ENNI..BASSA VAH LETTER I
+16AF5 ; Grapheme_Base # Po BASSA VAH FULL STOP
+16B00..16B2F ; Grapheme_Base # Lo [48] PAHAWH HMONG VOWEL KEEB..PAHAWH HMONG CONSONANT CAU
+16B37..16B3B ; Grapheme_Base # Po [5] PAHAWH HMONG SIGN VOS THOM..PAHAWH HMONG SIGN VOS FEEM
+16B3C..16B3F ; Grapheme_Base # So [4] PAHAWH HMONG SIGN XYEEM NTXIV..PAHAWH HMONG SIGN XYEEM FAIB
+16B40..16B43 ; Grapheme_Base # Lm [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM
+16B44 ; Grapheme_Base # Po PAHAWH HMONG SIGN XAUS
+16B45 ; Grapheme_Base # So PAHAWH HMONG SIGN CIM TSOV ROG
+16B50..16B59 ; Grapheme_Base # Nd [10] PAHAWH HMONG DIGIT ZERO..PAHAWH HMONG DIGIT NINE
+16B5B..16B61 ; Grapheme_Base # No [7] PAHAWH HMONG NUMBER TENS..PAHAWH HMONG NUMBER TRILLIONS
+16B63..16B77 ; Grapheme_Base # Lo [21] PAHAWH HMONG SIGN VOS LUB..PAHAWH HMONG SIGN CIM NRES TOS
+16B7D..16B8F ; Grapheme_Base # Lo [19] PAHAWH HMONG CLAN SIGN TSHEEJ..PAHAWH HMONG CLAN SIGN VWJ
+16D40..16D42 ; Grapheme_Base # Lm [3] KIRAT RAI SIGN ANUSVARA..KIRAT RAI SIGN VISARGA
+16D43..16D6A ; Grapheme_Base # Lo [40] KIRAT RAI LETTER A..KIRAT RAI VOWEL SIGN AU
+16D6B..16D6C ; Grapheme_Base # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT
+16D6D..16D6F ; Grapheme_Base # Po [3] KIRAT RAI SIGN YUPI..KIRAT RAI DOUBLE DANDA
+16D70..16D79 ; Grapheme_Base # Nd [10] KIRAT RAI DIGIT ZERO..KIRAT RAI DIGIT NINE
+16E40..16E7F ; Grapheme_Base # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y
+16E80..16E96 ; Grapheme_Base # No [23] MEDEFAIDRIN DIGIT ZERO..MEDEFAIDRIN DIGIT THREE ALTERNATE FORM
+16E97..16E9A ; Grapheme_Base # Po [4] MEDEFAIDRIN COMMA..MEDEFAIDRIN EXCLAMATION OH
+16F00..16F4A ; Grapheme_Base # Lo [75] MIAO LETTER PA..MIAO LETTER RTE
+16F50 ; Grapheme_Base # Lo MIAO LETTER NASALIZATION
+16F51..16F87 ; Grapheme_Base # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI
+16F93..16F9F ; Grapheme_Base # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8
+16FE0..16FE1 ; Grapheme_Base # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK
+16FE2 ; Grapheme_Base # Po OLD CHINESE HOOK MARK
+16FE3 ; Grapheme_Base # Lm OLD CHINESE ITERATION MARK
+17000..187F7 ; Grapheme_Base # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7
+18800..18CD5 ; Grapheme_Base # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5
+18CFF..18D08 ; Grapheme_Base # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08
+1AFF0..1AFF3 ; Grapheme_Base # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5
+1AFF5..1AFFB ; Grapheme_Base # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5
+1AFFD..1AFFE ; Grapheme_Base # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8
+1B000..1B122 ; Grapheme_Base # Lo [291] KATAKANA LETTER ARCHAIC E..KATAKANA LETTER ARCHAIC WU
+1B132 ; Grapheme_Base # Lo HIRAGANA LETTER SMALL KO
+1B150..1B152 ; Grapheme_Base # Lo [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO
+1B155 ; Grapheme_Base # Lo KATAKANA LETTER SMALL KO
+1B164..1B167 ; Grapheme_Base # Lo [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N
+1B170..1B2FB ; Grapheme_Base # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB
+1BC00..1BC6A ; Grapheme_Base # Lo [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M
+1BC70..1BC7C ; Grapheme_Base # Lo [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK
+1BC80..1BC88 ; Grapheme_Base # Lo [9] DUPLOYAN AFFIX HIGH ACUTE..DUPLOYAN AFFIX HIGH VERTICAL
+1BC90..1BC99 ; Grapheme_Base # Lo [10] DUPLOYAN AFFIX LOW ACUTE..DUPLOYAN AFFIX LOW ARROW
+1BC9C ; Grapheme_Base # So DUPLOYAN SIGN O WITH CROSS
+1BC9F ; Grapheme_Base # Po DUPLOYAN PUNCTUATION CHINOOK FULL STOP
+1CC00..1CCEF ; Grapheme_Base # So [240] UP-POINTING GO-KART..OUTLINED LATIN CAPITAL LETTER Z
+1CCF0..1CCF9 ; Grapheme_Base # Nd [10] OUTLINED DIGIT ZERO..OUTLINED DIGIT NINE
+1CD00..1CEB3 ; Grapheme_Base # So [436] BLOCK OCTANT-3..BLACK RIGHT TRIANGLE CARET
+1CF50..1CFC3 ; Grapheme_Base # So [116] ZNAMENNY NEUME KRYUK..ZNAMENNY NEUME PAUK
+1D000..1D0F5 ; Grapheme_Base # So [246] BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MUSICAL SYMBOL GORGON NEO KATO
+1D100..1D126 ; Grapheme_Base # So [39] MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBOL DRUM CLEF-2
+1D129..1D164 ; Grapheme_Base # So [60] MUSICAL SYMBOL MULTIPLE MEASURE REST..MUSICAL SYMBOL ONE HUNDRED TWENTY-EIGHTH NOTE
+1D16A..1D16C ; Grapheme_Base # So [3] MUSICAL SYMBOL FINGERED TREMOLO-1..MUSICAL SYMBOL FINGERED TREMOLO-3
+1D183..1D184 ; Grapheme_Base # So [2] MUSICAL SYMBOL ARPEGGIATO UP..MUSICAL SYMBOL ARPEGGIATO DOWN
+1D18C..1D1A9 ; Grapheme_Base # So [30] MUSICAL SYMBOL RINFORZANDO..MUSICAL SYMBOL DEGREE SLASH
+1D1AE..1D1EA ; Grapheme_Base # So [61] MUSICAL SYMBOL PEDAL MARK..MUSICAL SYMBOL KORON
+1D200..1D241 ; Grapheme_Base # So [66] GREEK VOCAL NOTATION SYMBOL-1..GREEK INSTRUMENTAL NOTATION SYMBOL-54
+1D245 ; Grapheme_Base # So GREEK MUSICAL LEIMMA
+1D2C0..1D2D3 ; Grapheme_Base # No [20] KAKTOVIK NUMERAL ZERO..KAKTOVIK NUMERAL NINETEEN
+1D2E0..1D2F3 ; Grapheme_Base # No [20] MAYAN NUMERAL ZERO..MAYAN NUMERAL NINETEEN
+1D300..1D356 ; Grapheme_Base # So [87] MONOGRAM FOR EARTH..TETRAGRAM FOR FOSTERING
+1D360..1D378 ; Grapheme_Base # No [25] COUNTING ROD UNIT DIGIT ONE..TALLY MARK FIVE
+1D400..1D454 ; Grapheme_Base # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G
+1D456..1D49C ; Grapheme_Base # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A
+1D49E..1D49F ; Grapheme_Base # L& [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D
+1D4A2 ; Grapheme_Base # L& MATHEMATICAL SCRIPT CAPITAL G
+1D4A5..1D4A6 ; Grapheme_Base # L& [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K
+1D4A9..1D4AC ; Grapheme_Base # L& [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q
+1D4AE..1D4B9 ; Grapheme_Base # L& [12] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D
+1D4BB ; Grapheme_Base # L& MATHEMATICAL SCRIPT SMALL F
+1D4BD..1D4C3 ; Grapheme_Base # L& [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N
+1D4C5..1D505 ; Grapheme_Base # L& [65] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B
+1D507..1D50A ; Grapheme_Base # L& [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G
+1D50D..1D514 ; Grapheme_Base # L& [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q
+1D516..1D51C ; Grapheme_Base # L& [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y
+1D51E..1D539 ; Grapheme_Base # L& [28] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B
+1D53B..1D53E ; Grapheme_Base # L& [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G
+1D540..1D544 ; Grapheme_Base # L& [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M
+1D546 ; Grapheme_Base # L& MATHEMATICAL DOUBLE-STRUCK CAPITAL O
+1D54A..1D550 ; Grapheme_Base # L& [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y
+1D552..1D6A5 ; Grapheme_Base # L& [340] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J
+1D6A8..1D6C0 ; Grapheme_Base # L& [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA
+1D6C1 ; Grapheme_Base # Sm MATHEMATICAL BOLD NABLA
+1D6C2..1D6DA ; Grapheme_Base # L& [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA
+1D6DB ; Grapheme_Base # Sm MATHEMATICAL BOLD PARTIAL DIFFERENTIAL
+1D6DC..1D6FA ; Grapheme_Base # L& [31] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL ITALIC CAPITAL OMEGA
+1D6FB ; Grapheme_Base # Sm MATHEMATICAL ITALIC NABLA
+1D6FC..1D714 ; Grapheme_Base # L& [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA
+1D715 ; Grapheme_Base # Sm MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL
+1D716..1D734 ; Grapheme_Base # L& [31] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA
+1D735 ; Grapheme_Base # Sm MATHEMATICAL BOLD ITALIC NABLA
+1D736..1D74E ; Grapheme_Base # L& [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA
+1D74F ; Grapheme_Base # Sm MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL
+1D750..1D76E ; Grapheme_Base # L& [31] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA
+1D76F ; Grapheme_Base # Sm MATHEMATICAL SANS-SERIF BOLD NABLA
+1D770..1D788 ; Grapheme_Base # L& [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA
+1D789 ; Grapheme_Base # Sm MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL
+1D78A..1D7A8 ; Grapheme_Base # L& [31] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA
+1D7A9 ; Grapheme_Base # Sm MATHEMATICAL SANS-SERIF BOLD ITALIC NABLA
+1D7AA..1D7C2 ; Grapheme_Base # L& [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA
+1D7C3 ; Grapheme_Base # Sm MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL
+1D7C4..1D7CB ; Grapheme_Base # L& [8] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD SMALL DIGAMMA
+1D7CE..1D7FF ; Grapheme_Base # Nd [50] MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE
+1D800..1D9FF ; Grapheme_Base # So [512] SIGNWRITING HAND-FIST INDEX..SIGNWRITING HEAD
+1DA37..1DA3A ; Grapheme_Base # So [4] SIGNWRITING AIR BLOW SMALL ROTATIONS..SIGNWRITING BREATH EXHALE
+1DA6D..1DA74 ; Grapheme_Base # So [8] SIGNWRITING SHOULDER HIP SPINE..SIGNWRITING TORSO-FLOORPLANE TWISTING
+1DA76..1DA83 ; Grapheme_Base # So [14] SIGNWRITING LIMB COMBINATION..SIGNWRITING LOCATION DEPTH
+1DA85..1DA86 ; Grapheme_Base # So [2] SIGNWRITING LOCATION TORSO..SIGNWRITING LOCATION LIMBS DIGITS
+1DA87..1DA8B ; Grapheme_Base # Po [5] SIGNWRITING COMMA..SIGNWRITING PARENTHESIS
+1DF00..1DF09 ; Grapheme_Base # L& [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK
+1DF0A ; Grapheme_Base # Lo LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK
+1DF0B..1DF1E ; Grapheme_Base # L& [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL
+1DF25..1DF2A ; Grapheme_Base # L& [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK
+1E030..1E06D ; Grapheme_Base # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE
+1E100..1E12C ; Grapheme_Base # Lo [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W
+1E137..1E13D ; Grapheme_Base # Lm [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER
+1E140..1E149 ; Grapheme_Base # Nd [10] NYIAKENG PUACHUE HMONG DIGIT ZERO..NYIAKENG PUACHUE HMONG DIGIT NINE
+1E14E ; Grapheme_Base # Lo NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ
+1E14F ; Grapheme_Base # So NYIAKENG PUACHUE HMONG CIRCLED CA
+1E290..1E2AD ; Grapheme_Base # Lo [30] TOTO LETTER PA..TOTO LETTER A
+1E2C0..1E2EB ; Grapheme_Base # Lo [44] WANCHO LETTER AA..WANCHO LETTER YIH
+1E2F0..1E2F9 ; Grapheme_Base # Nd [10] WANCHO DIGIT ZERO..WANCHO DIGIT NINE
+1E2FF ; Grapheme_Base # Sc WANCHO NGUN SIGN
+1E4D0..1E4EA ; Grapheme_Base # Lo [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL
+1E4EB ; Grapheme_Base # Lm NAG MUNDARI SIGN OJOD
+1E4F0..1E4F9 ; Grapheme_Base # Nd [10] NAG MUNDARI DIGIT ZERO..NAG MUNDARI DIGIT NINE
+1E5D0..1E5ED ; Grapheme_Base # Lo [30] OL ONAL LETTER O..OL ONAL LETTER EG
+1E5F0 ; Grapheme_Base # Lo OL ONAL SIGN HODDOND
+1E5F1..1E5FA ; Grapheme_Base # Nd [10] OL ONAL DIGIT ZERO..OL ONAL DIGIT NINE
+1E5FF ; Grapheme_Base # Po OL ONAL ABBREVIATION SIGN
+1E7E0..1E7E6 ; Grapheme_Base # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO
+1E7E8..1E7EB ; Grapheme_Base # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE
+1E7ED..1E7EE ; Grapheme_Base # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE
+1E7F0..1E7FE ; Grapheme_Base # Lo [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE
+1E800..1E8C4 ; Grapheme_Base # Lo [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON
+1E8C7..1E8CF ; Grapheme_Base # No [9] MENDE KIKAKUI DIGIT ONE..MENDE KIKAKUI DIGIT NINE
+1E900..1E943 ; Grapheme_Base # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA
+1E94B ; Grapheme_Base # Lm ADLAM NASALIZATION MARK
+1E950..1E959 ; Grapheme_Base # Nd [10] ADLAM DIGIT ZERO..ADLAM DIGIT NINE
+1E95E..1E95F ; Grapheme_Base # Po [2] ADLAM INITIAL EXCLAMATION MARK..ADLAM INITIAL QUESTION MARK
+1EC71..1ECAB ; Grapheme_Base # No [59] INDIC SIYAQ NUMBER ONE..INDIC SIYAQ NUMBER PREFIXED NINE
+1ECAC ; Grapheme_Base # So INDIC SIYAQ PLACEHOLDER
+1ECAD..1ECAF ; Grapheme_Base # No [3] INDIC SIYAQ FRACTION ONE QUARTER..INDIC SIYAQ FRACTION THREE QUARTERS
+1ECB0 ; Grapheme_Base # Sc INDIC SIYAQ RUPEE MARK
+1ECB1..1ECB4 ; Grapheme_Base # No [4] INDIC SIYAQ NUMBER ALTERNATE ONE..INDIC SIYAQ ALTERNATE LAKH MARK
+1ED01..1ED2D ; Grapheme_Base # No [45] OTTOMAN SIYAQ NUMBER ONE..OTTOMAN SIYAQ NUMBER NINETY THOUSAND
+1ED2E ; Grapheme_Base # So OTTOMAN SIYAQ MARRATAN
+1ED2F..1ED3D ; Grapheme_Base # No [15] OTTOMAN SIYAQ ALTERNATE NUMBER TWO..OTTOMAN SIYAQ FRACTION ONE SIXTH
+1EE00..1EE03 ; Grapheme_Base # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL
+1EE05..1EE1F ; Grapheme_Base # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF
+1EE21..1EE22 ; Grapheme_Base # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM
+1EE24 ; Grapheme_Base # Lo ARABIC MATHEMATICAL INITIAL HEH
+1EE27 ; Grapheme_Base # Lo ARABIC MATHEMATICAL INITIAL HAH
+1EE29..1EE32 ; Grapheme_Base # Lo [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF
+1EE34..1EE37 ; Grapheme_Base # Lo [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH
+1EE39 ; Grapheme_Base # Lo ARABIC MATHEMATICAL INITIAL DAD
+1EE3B ; Grapheme_Base # Lo ARABIC MATHEMATICAL INITIAL GHAIN
+1EE42 ; Grapheme_Base # Lo ARABIC MATHEMATICAL TAILED JEEM
+1EE47 ; Grapheme_Base # Lo ARABIC MATHEMATICAL TAILED HAH
+1EE49 ; Grapheme_Base # Lo ARABIC MATHEMATICAL TAILED YEH
+1EE4B ; Grapheme_Base # Lo ARABIC MATHEMATICAL TAILED LAM
+1EE4D..1EE4F ; Grapheme_Base # Lo [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN
+1EE51..1EE52 ; Grapheme_Base # Lo [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF
+1EE54 ; Grapheme_Base # Lo ARABIC MATHEMATICAL TAILED SHEEN
+1EE57 ; Grapheme_Base # Lo ARABIC MATHEMATICAL TAILED KHAH
+1EE59 ; Grapheme_Base # Lo ARABIC MATHEMATICAL TAILED DAD
+1EE5B ; Grapheme_Base # Lo ARABIC MATHEMATICAL TAILED GHAIN
+1EE5D ; Grapheme_Base # Lo ARABIC MATHEMATICAL TAILED DOTLESS NOON
+1EE5F ; Grapheme_Base # Lo ARABIC MATHEMATICAL TAILED DOTLESS QAF
+1EE61..1EE62 ; Grapheme_Base # Lo [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM
+1EE64 ; Grapheme_Base # Lo ARABIC MATHEMATICAL STRETCHED HEH
+1EE67..1EE6A ; Grapheme_Base # Lo [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF
+1EE6C..1EE72 ; Grapheme_Base # Lo [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF
+1EE74..1EE77 ; Grapheme_Base # Lo [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH
+1EE79..1EE7C ; Grapheme_Base # Lo [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH
+1EE7E ; Grapheme_Base # Lo ARABIC MATHEMATICAL STRETCHED DOTLESS FEH
+1EE80..1EE89 ; Grapheme_Base # Lo [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH
+1EE8B..1EE9B ; Grapheme_Base # Lo [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN
+1EEA1..1EEA3 ; Grapheme_Base # Lo [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL
+1EEA5..1EEA9 ; Grapheme_Base # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH
+1EEAB..1EEBB ; Grapheme_Base # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN
+1EEF0..1EEF1 ; Grapheme_Base # Sm [2] ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL
+1F000..1F02B ; Grapheme_Base # So [44] MAHJONG TILE EAST WIND..MAHJONG TILE BACK
+1F030..1F093 ; Grapheme_Base # So [100] DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06
+1F0A0..1F0AE ; Grapheme_Base # So [15] PLAYING CARD BACK..PLAYING CARD KING OF SPADES
+1F0B1..1F0BF ; Grapheme_Base # So [15] PLAYING CARD ACE OF HEARTS..PLAYING CARD RED JOKER
+1F0C1..1F0CF ; Grapheme_Base # So [15] PLAYING CARD ACE OF DIAMONDS..PLAYING CARD BLACK JOKER
+1F0D1..1F0F5 ; Grapheme_Base # So [37] PLAYING CARD ACE OF CLUBS..PLAYING CARD TRUMP-21
+1F100..1F10C ; Grapheme_Base # No [13] DIGIT ZERO FULL STOP..DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ZERO
+1F10D..1F1AD ; Grapheme_Base # So [161] CIRCLED ZERO WITH SLASH..MASK WORK SYMBOL
+1F1E6..1F202 ; Grapheme_Base # So [29] REGIONAL INDICATOR SYMBOL LETTER A..SQUARED KATAKANA SA
+1F210..1F23B ; Grapheme_Base # So [44] SQUARED CJK UNIFIED IDEOGRAPH-624B..SQUARED CJK UNIFIED IDEOGRAPH-914D
+1F240..1F248 ; Grapheme_Base # So [9] TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C..TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557
+1F250..1F251 ; Grapheme_Base # So [2] CIRCLED IDEOGRAPH ADVANTAGE..CIRCLED IDEOGRAPH ACCEPT
+1F260..1F265 ; Grapheme_Base # So [6] ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI
+1F300..1F3FA ; Grapheme_Base # So [251] CYCLONE..AMPHORA
+1F3FB..1F3FF ; Grapheme_Base # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6
+1F400..1F6D7 ; Grapheme_Base # So [728] RAT..ELEVATOR
+1F6DC..1F6EC ; Grapheme_Base # So [17] WIRELESS..AIRPLANE ARRIVING
+1F6F0..1F6FC ; Grapheme_Base # So [13] SATELLITE..ROLLER SKATE
+1F700..1F776 ; Grapheme_Base # So [119] ALCHEMICAL SYMBOL FOR QUINTESSENCE..LUNAR ECLIPSE
+1F77B..1F7D9 ; Grapheme_Base # So [95] HAUMEA..NINE POINTED WHITE STAR
+1F7E0..1F7EB ; Grapheme_Base # So [12] LARGE ORANGE CIRCLE..LARGE BROWN SQUARE
+1F7F0 ; Grapheme_Base # So HEAVY EQUALS SIGN
+1F800..1F80B ; Grapheme_Base # So [12] LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD..DOWNWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD
+1F810..1F847 ; Grapheme_Base # So [56] LEFTWARDS ARROW WITH SMALL EQUILATERAL ARROWHEAD..DOWNWARDS HEAVY ARROW
+1F850..1F859 ; Grapheme_Base # So [10] LEFTWARDS SANS-SERIF ARROW..UP DOWN SANS-SERIF ARROW
+1F860..1F887 ; Grapheme_Base # So [40] WIDE-HEADED LEFTWARDS LIGHT BARB ARROW..WIDE-HEADED SOUTH WEST VERY HEAVY BARB ARROW
+1F890..1F8AD ; Grapheme_Base # So [30] LEFTWARDS TRIANGLE ARROWHEAD..WHITE ARROW SHAFT WIDTH TWO THIRDS
+1F8B0..1F8BB ; Grapheme_Base # So [12] ARROW POINTING UPWARDS THEN NORTH WEST..SOUTH WEST ARROW FROM BAR
+1F8C0..1F8C1 ; Grapheme_Base # So [2] LEFTWARDS ARROW FROM DOWNWARDS ARROW..RIGHTWARDS ARROW FROM DOWNWARDS ARROW
+1F900..1FA53 ; Grapheme_Base # So [340] CIRCLED CROSS FORMEE WITH FOUR DOTS..BLACK CHESS KNIGHT-BISHOP
+1FA60..1FA6D ; Grapheme_Base # So [14] XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER
+1FA70..1FA7C ; Grapheme_Base # So [13] BALLET SHOES..CRUTCH
+1FA80..1FA89 ; Grapheme_Base # So [10] YO-YO..HARP
+1FA8F..1FAC6 ; Grapheme_Base # So [56] SHOVEL..FINGERPRINT
+1FACE..1FADC ; Grapheme_Base # So [15] MOOSE..ROOT VEGETABLE
+1FADF..1FAE9 ; Grapheme_Base # So [11] SPLATTER..FACE WITH BAGS UNDER EYES
+1FAF0..1FAF8 ; Grapheme_Base # So [9] HAND WITH INDEX FINGER AND THUMB CROSSED..RIGHTWARDS PUSHING HAND
+1FB00..1FB92 ; Grapheme_Base # So [147] BLOCK SEXTANT-1..UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK
+1FB94..1FBEF ; Grapheme_Base # So [92] LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK..TOP LEFT JUSTIFIED LOWER RIGHT QUARTER BLACK CIRCLE
+1FBF0..1FBF9 ; Grapheme_Base # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE
+20000..2A6DF ; Grapheme_Base # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF
+2A700..2B739 ; Grapheme_Base # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739
+2B740..2B81D ; Grapheme_Base # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D
+2B820..2CEA1 ; Grapheme_Base # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1
+2CEB0..2EBE0 ; Grapheme_Base # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0
+2EBF0..2EE5D ; Grapheme_Base # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D
+2F800..2FA1D ; Grapheme_Base # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D
+30000..3134A ; Grapheme_Base # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A
+31350..323AF ; Grapheme_Base # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF
+
+# Total code points: 152730
+
+# ================================================
+
+# Derived Property: Grapheme_Link (deprecated)
+# Generated from: Canonical_Combining_Class=Virama
+# Use Canonical_Combining_Class=Virama directly instead
+
+094D ; Grapheme_Link # Mn DEVANAGARI SIGN VIRAMA
+09CD ; Grapheme_Link # Mn BENGALI SIGN VIRAMA
+0A4D ; Grapheme_Link # Mn GURMUKHI SIGN VIRAMA
+0ACD ; Grapheme_Link # Mn GUJARATI SIGN VIRAMA
+0B4D ; Grapheme_Link # Mn ORIYA SIGN VIRAMA
+0BCD ; Grapheme_Link # Mn TAMIL SIGN VIRAMA
+0C4D ; Grapheme_Link # Mn TELUGU SIGN VIRAMA
+0CCD ; Grapheme_Link # Mn KANNADA SIGN VIRAMA
+0D3B..0D3C ; Grapheme_Link # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA
+0D4D ; Grapheme_Link # Mn MALAYALAM SIGN VIRAMA
+0DCA ; Grapheme_Link # Mn SINHALA SIGN AL-LAKUNA
+0E3A ; Grapheme_Link # Mn THAI CHARACTER PHINTHU
+0EBA ; Grapheme_Link # Mn LAO SIGN PALI VIRAMA
+0F84 ; Grapheme_Link # Mn TIBETAN MARK HALANTA
+1039..103A ; Grapheme_Link # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT
+1714 ; Grapheme_Link # Mn TAGALOG SIGN VIRAMA
+1715 ; Grapheme_Link # Mc TAGALOG SIGN PAMUDPOD
+1734 ; Grapheme_Link # Mc HANUNOO SIGN PAMUDPOD
+17D2 ; Grapheme_Link # Mn KHMER SIGN COENG
+1A60 ; Grapheme_Link # Mn TAI THAM SIGN SAKOT
+1B44 ; Grapheme_Link # Mc BALINESE ADEG ADEG
+1BAA ; Grapheme_Link # Mc SUNDANESE SIGN PAMAAEH
+1BAB ; Grapheme_Link # Mn SUNDANESE SIGN VIRAMA
+1BF2..1BF3 ; Grapheme_Link # Mc [2] BATAK PANGOLAT..BATAK PANONGONAN
+2D7F ; Grapheme_Link # Mn TIFINAGH CONSONANT JOINER
+A806 ; Grapheme_Link # Mn SYLOTI NAGRI SIGN HASANTA
+A82C ; Grapheme_Link # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA
+A8C4 ; Grapheme_Link # Mn SAURASHTRA SIGN VIRAMA
+A953 ; Grapheme_Link # Mc REJANG VIRAMA
+A9C0 ; Grapheme_Link # Mc JAVANESE PANGKON
+AAF6 ; Grapheme_Link # Mn MEETEI MAYEK VIRAMA
+ABED ; Grapheme_Link # Mn MEETEI MAYEK APUN IYEK
+10A3F ; Grapheme_Link # Mn KHAROSHTHI VIRAMA
+11046 ; Grapheme_Link # Mn BRAHMI VIRAMA
+11070 ; Grapheme_Link # Mn BRAHMI SIGN OLD TAMIL VIRAMA
+1107F ; Grapheme_Link # Mn BRAHMI NUMBER JOINER
+110B9 ; Grapheme_Link # Mn KAITHI SIGN VIRAMA
+11133..11134 ; Grapheme_Link # Mn [2] CHAKMA VIRAMA..CHAKMA MAAYYAA
+111C0 ; Grapheme_Link # Mc SHARADA SIGN VIRAMA
+11235 ; Grapheme_Link # Mc KHOJKI SIGN VIRAMA
+112EA ; Grapheme_Link # Mn KHUDAWADI SIGN VIRAMA
+1134D ; Grapheme_Link # Mc GRANTHA SIGN VIRAMA
+113CE ; Grapheme_Link # Mn TULU-TIGALARI SIGN VIRAMA
+113CF ; Grapheme_Link # Mc TULU-TIGALARI SIGN LOOPED VIRAMA
+113D0 ; Grapheme_Link # Mn TULU-TIGALARI CONJOINER
+11442 ; Grapheme_Link # Mn NEWA SIGN VIRAMA
+114C2 ; Grapheme_Link # Mn TIRHUTA SIGN VIRAMA
+115BF ; Grapheme_Link # Mn SIDDHAM SIGN VIRAMA
+1163F ; Grapheme_Link # Mn MODI SIGN VIRAMA
+116B6 ; Grapheme_Link # Mc TAKRI SIGN VIRAMA
+1172B ; Grapheme_Link # Mn AHOM SIGN KILLER
+11839 ; Grapheme_Link # Mn DOGRA SIGN VIRAMA
+1193D ; Grapheme_Link # Mc DIVES AKURU SIGN HALANTA
+1193E ; Grapheme_Link # Mn DIVES AKURU VIRAMA
+119E0 ; Grapheme_Link # Mn NANDINAGARI SIGN VIRAMA
+11A34 ; Grapheme_Link # Mn ZANABAZAR SQUARE SIGN VIRAMA
+11A47 ; Grapheme_Link # Mn ZANABAZAR SQUARE SUBJOINER
+11A99 ; Grapheme_Link # Mn SOYOMBO SUBJOINER
+11C3F ; Grapheme_Link # Mn BHAIKSUKI SIGN VIRAMA
+11D44..11D45 ; Grapheme_Link # Mn [2] MASARAM GONDI SIGN HALANTA..MASARAM GONDI VIRAMA
+11D97 ; Grapheme_Link # Mn GUNJALA GONDI VIRAMA
+11F41 ; Grapheme_Link # Mc KAWI SIGN KILLER
+11F42 ; Grapheme_Link # Mn KAWI CONJOINER
+1612F ; Grapheme_Link # Mn GURUNG KHEMA SIGN THOLHOMA
+
+# Total code points: 69
+
+# ================================================
+
+# Derived Property: Indic_Conjunct_Break
+# Generated from the Grapheme_Cluster_Break, Indic_Syllabic_Category,
+# Canonical_Combining_Class, and Script properties as described in UAX #44:
+# https://www.unicode.org/reports/tr44/.
+
+# All code points not explicitly listed for Indic_Conjunct_Break
+# have the value None.
+
+# @missing: 0000..10FFFF; InCB; None
+
+# ================================================
+
+# Indic_Conjunct_Break=Linker
+
+094D ; InCB; Linker # Mn DEVANAGARI SIGN VIRAMA
+09CD ; InCB; Linker # Mn BENGALI SIGN VIRAMA
+0ACD ; InCB; Linker # Mn GUJARATI SIGN VIRAMA
+0B4D ; InCB; Linker # Mn ORIYA SIGN VIRAMA
+0C4D ; InCB; Linker # Mn TELUGU SIGN VIRAMA
+0D4D ; InCB; Linker # Mn MALAYALAM SIGN VIRAMA
+
+# Total code points: 6
+
+# ================================================
+
+# Indic_Conjunct_Break=Consonant
+
+0915..0939 ; InCB; Consonant # Lo [37] DEVANAGARI LETTER KA..DEVANAGARI LETTER HA
+0958..095F ; InCB; Consonant # Lo [8] DEVANAGARI LETTER QA..DEVANAGARI LETTER YYA
+0978..097F ; InCB; Consonant # Lo [8] DEVANAGARI LETTER MARWARI DDA..DEVANAGARI LETTER BBA
+0995..09A8 ; InCB; Consonant # Lo [20] BENGALI LETTER KA..BENGALI LETTER NA
+09AA..09B0 ; InCB; Consonant # Lo [7] BENGALI LETTER PA..BENGALI LETTER RA
+09B2 ; InCB; Consonant # Lo BENGALI LETTER LA
+09B6..09B9 ; InCB; Consonant # Lo [4] BENGALI LETTER SHA..BENGALI LETTER HA
+09DC..09DD ; InCB; Consonant # Lo [2] BENGALI LETTER RRA..BENGALI LETTER RHA
+09DF ; InCB; Consonant # Lo BENGALI LETTER YYA
+09F0..09F1 ; InCB; Consonant # Lo [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL
+0A95..0AA8 ; InCB; Consonant # Lo [20] GUJARATI LETTER KA..GUJARATI LETTER NA
+0AAA..0AB0 ; InCB; Consonant # Lo [7] GUJARATI LETTER PA..GUJARATI LETTER RA
+0AB2..0AB3 ; InCB; Consonant # Lo [2] GUJARATI LETTER LA..GUJARATI LETTER LLA
+0AB5..0AB9 ; InCB; Consonant # Lo [5] GUJARATI LETTER VA..GUJARATI LETTER HA
+0AF9 ; InCB; Consonant # Lo GUJARATI LETTER ZHA
+0B15..0B28 ; InCB; Consonant # Lo [20] ORIYA LETTER KA..ORIYA LETTER NA
+0B2A..0B30 ; InCB; Consonant # Lo [7] ORIYA LETTER PA..ORIYA LETTER RA
+0B32..0B33 ; InCB; Consonant # Lo [2] ORIYA LETTER LA..ORIYA LETTER LLA
+0B35..0B39 ; InCB; Consonant # Lo [5] ORIYA LETTER VA..ORIYA LETTER HA
+0B5C..0B5D ; InCB; Consonant # Lo [2] ORIYA LETTER RRA..ORIYA LETTER RHA
+0B5F ; InCB; Consonant # Lo ORIYA LETTER YYA
+0B71 ; InCB; Consonant # Lo ORIYA LETTER WA
+0C15..0C28 ; InCB; Consonant # Lo [20] TELUGU LETTER KA..TELUGU LETTER NA
+0C2A..0C39 ; InCB; Consonant # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA
+0C58..0C5A ; InCB; Consonant # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA
+0D15..0D3A ; InCB; Consonant # Lo [38] MALAYALAM LETTER KA..MALAYALAM LETTER TTTA
+
+# Total code points: 240
+
+# ================================================
+
+# Indic_Conjunct_Break=Extend
+
+0300..036F ; InCB; Extend # Mn [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X
+0483..0487 ; InCB; Extend # Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE
+0488..0489 ; InCB; Extend # Me [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN
+0591..05BD ; InCB; Extend # Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG
+05BF ; InCB; Extend # Mn HEBREW POINT RAFE
+05C1..05C2 ; InCB; Extend # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT
+05C4..05C5 ; InCB; Extend # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT
+05C7 ; InCB; Extend # Mn HEBREW POINT QAMATS QATAN
+0610..061A ; InCB; Extend # Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA
+064B..065F ; InCB; Extend # Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW
+0670 ; InCB; Extend # Mn ARABIC LETTER SUPERSCRIPT ALEF
+06D6..06DC ; InCB; Extend # Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN
+06DF..06E4 ; InCB; Extend # Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA
+06E7..06E8 ; InCB; Extend # Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON
+06EA..06ED ; InCB; Extend # Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM
+0711 ; InCB; Extend # Mn SYRIAC LETTER SUPERSCRIPT ALAPH
+0730..074A ; InCB; Extend # Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH
+07A6..07B0 ; InCB; Extend # Mn [11] THAANA ABAFILI..THAANA SUKUN
+07EB..07F3 ; InCB; Extend # Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE
+07FD ; InCB; Extend # Mn NKO DANTAYALAN
+0816..0819 ; InCB; Extend # Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH
+081B..0823 ; InCB; Extend # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A
+0825..0827 ; InCB; Extend # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U
+0829..082D ; InCB; Extend # Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA
+0859..085B ; InCB; Extend # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK
+0897..089F ; InCB; Extend # Mn [9] ARABIC PEPET..ARABIC HALF MADDA OVER MADDA
+08CA..08E1 ; InCB; Extend # Mn [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA
+08E3..0902 ; InCB; Extend # Mn [32] ARABIC TURNED DAMMA BELOW..DEVANAGARI SIGN ANUSVARA
+093A ; InCB; Extend # Mn DEVANAGARI VOWEL SIGN OE
+093C ; InCB; Extend # Mn DEVANAGARI SIGN NUKTA
+0941..0948 ; InCB; Extend # Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI
+0951..0957 ; InCB; Extend # Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE
+0962..0963 ; InCB; Extend # Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL
+0981 ; InCB; Extend # Mn BENGALI SIGN CANDRABINDU
+09BC ; InCB; Extend # Mn BENGALI SIGN NUKTA
+09BE ; InCB; Extend # Mc BENGALI VOWEL SIGN AA
+09C1..09C4 ; InCB; Extend # Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR
+09D7 ; InCB; Extend # Mc BENGALI AU LENGTH MARK
+09E2..09E3 ; InCB; Extend # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL
+09FE ; InCB; Extend # Mn BENGALI SANDHI MARK
+0A01..0A02 ; InCB; Extend # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI
+0A3C ; InCB; Extend # Mn GURMUKHI SIGN NUKTA
+0A41..0A42 ; InCB; Extend # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU
+0A47..0A48 ; InCB; Extend # Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI
+0A4B..0A4D ; InCB; Extend # Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA
+0A51 ; InCB; Extend # Mn GURMUKHI SIGN UDAAT
+0A70..0A71 ; InCB; Extend # Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK
+0A75 ; InCB; Extend # Mn GURMUKHI SIGN YAKASH
+0A81..0A82 ; InCB; Extend # Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA
+0ABC ; InCB; Extend # Mn GUJARATI SIGN NUKTA
+0AC1..0AC5 ; InCB; Extend # Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E
+0AC7..0AC8 ; InCB; Extend # Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI
+0AE2..0AE3 ; InCB; Extend # Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL
+0AFA..0AFF ; InCB; Extend # Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE
+0B01 ; InCB; Extend # Mn ORIYA SIGN CANDRABINDU
+0B3C ; InCB; Extend # Mn ORIYA SIGN NUKTA
+0B3E ; InCB; Extend # Mc ORIYA VOWEL SIGN AA
+0B3F ; InCB; Extend # Mn ORIYA VOWEL SIGN I
+0B41..0B44 ; InCB; Extend # Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR
+0B55..0B56 ; InCB; Extend # Mn [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK
+0B57 ; InCB; Extend # Mc ORIYA AU LENGTH MARK
+0B62..0B63 ; InCB; Extend # Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL
+0B82 ; InCB; Extend # Mn TAMIL SIGN ANUSVARA
+0BBE ; InCB; Extend # Mc TAMIL VOWEL SIGN AA
+0BC0 ; InCB; Extend # Mn TAMIL VOWEL SIGN II
+0BCD ; InCB; Extend # Mn TAMIL SIGN VIRAMA
+0BD7 ; InCB; Extend # Mc TAMIL AU LENGTH MARK
+0C00 ; InCB; Extend # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE
+0C04 ; InCB; Extend # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE
+0C3C ; InCB; Extend # Mn TELUGU SIGN NUKTA
+0C3E..0C40 ; InCB; Extend # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II
+0C46..0C48 ; InCB; Extend # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI
+0C4A..0C4C ; InCB; Extend # Mn [3] TELUGU VOWEL SIGN O..TELUGU VOWEL SIGN AU
+0C55..0C56 ; InCB; Extend # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK
+0C62..0C63 ; InCB; Extend # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL
+0C81 ; InCB; Extend # Mn KANNADA SIGN CANDRABINDU
+0CBC ; InCB; Extend # Mn KANNADA SIGN NUKTA
+0CBF ; InCB; Extend # Mn KANNADA VOWEL SIGN I
+0CC0 ; InCB; Extend # Mc KANNADA VOWEL SIGN II
+0CC2 ; InCB; Extend # Mc KANNADA VOWEL SIGN UU
+0CC6 ; InCB; Extend # Mn KANNADA VOWEL SIGN E
+0CC7..0CC8 ; InCB; Extend # Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI
+0CCA..0CCB ; InCB; Extend # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO
+0CCC..0CCD ; InCB; Extend # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA
+0CD5..0CD6 ; InCB; Extend # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK
+0CE2..0CE3 ; InCB; Extend # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL
+0D00..0D01 ; InCB; Extend # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU
+0D3B..0D3C ; InCB; Extend # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA
+0D3E ; InCB; Extend # Mc MALAYALAM VOWEL SIGN AA
+0D41..0D44 ; InCB; Extend # Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR
+0D57 ; InCB; Extend # Mc MALAYALAM AU LENGTH MARK
+0D62..0D63 ; InCB; Extend # Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL
+0D81 ; InCB; Extend # Mn SINHALA SIGN CANDRABINDU
+0DCA ; InCB; Extend # Mn SINHALA SIGN AL-LAKUNA
+0DCF ; InCB; Extend # Mc SINHALA VOWEL SIGN AELA-PILLA
+0DD2..0DD4 ; InCB; Extend # Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA
+0DD6 ; InCB; Extend # Mn SINHALA VOWEL SIGN DIGA PAA-PILLA
+0DDF ; InCB; Extend # Mc SINHALA VOWEL SIGN GAYANUKITTA
+0E31 ; InCB; Extend # Mn THAI CHARACTER MAI HAN-AKAT
+0E34..0E3A ; InCB; Extend # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU
+0E47..0E4E ; InCB; Extend # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN
+0EB1 ; InCB; Extend # Mn LAO VOWEL SIGN MAI KAN
+0EB4..0EBC ; InCB; Extend # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO
+0EC8..0ECE ; InCB; Extend # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN
+0F18..0F19 ; InCB; Extend # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS
+0F35 ; InCB; Extend # Mn TIBETAN MARK NGAS BZUNG NYI ZLA
+0F37 ; InCB; Extend # Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS
+0F39 ; InCB; Extend # Mn TIBETAN MARK TSA -PHRU
+0F71..0F7E ; InCB; Extend # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO
+0F80..0F84 ; InCB; Extend # Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA
+0F86..0F87 ; InCB; Extend # Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS
+0F8D..0F97 ; InCB; Extend # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA
+0F99..0FBC ; InCB; Extend # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA
+0FC6 ; InCB; Extend # Mn TIBETAN SYMBOL PADMA GDAN
+102D..1030 ; InCB; Extend # Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU
+1032..1037 ; InCB; Extend # Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW
+1039..103A ; InCB; Extend # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT
+103D..103E ; InCB; Extend # Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA
+1058..1059 ; InCB; Extend # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL
+105E..1060 ; InCB; Extend # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA
+1071..1074 ; InCB; Extend # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE
+1082 ; InCB; Extend # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA
+1085..1086 ; InCB; Extend # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y
+108D ; InCB; Extend # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE
+109D ; InCB; Extend # Mn MYANMAR VOWEL SIGN AITON AI
+135D..135F ; InCB; Extend # Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK
+1712..1714 ; InCB; Extend # Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA
+1715 ; InCB; Extend # Mc TAGALOG SIGN PAMUDPOD
+1732..1733 ; InCB; Extend # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U
+1734 ; InCB; Extend # Mc HANUNOO SIGN PAMUDPOD
+1752..1753 ; InCB; Extend # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U
+1772..1773 ; InCB; Extend # Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U
+17B4..17B5 ; InCB; Extend # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
+17B7..17BD ; InCB; Extend # Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA
+17C6 ; InCB; Extend # Mn KHMER SIGN NIKAHIT
+17C9..17D3 ; InCB; Extend # Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT
+17DD ; InCB; Extend # Mn KHMER SIGN ATTHACAN
+180B..180D ; InCB; Extend # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
+180F ; InCB; Extend # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR
+1885..1886 ; InCB; Extend # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA
+18A9 ; InCB; Extend # Mn MONGOLIAN LETTER ALI GALI DAGALGA
+1920..1922 ; InCB; Extend # Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U
+1927..1928 ; InCB; Extend # Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O
+1932 ; InCB; Extend # Mn LIMBU SMALL LETTER ANUSVARA
+1939..193B ; InCB; Extend # Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I
+1A17..1A18 ; InCB; Extend # Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U
+1A1B ; InCB; Extend # Mn BUGINESE VOWEL SIGN AE
+1A56 ; InCB; Extend # Mn TAI THAM CONSONANT SIGN MEDIAL LA
+1A58..1A5E ; InCB; Extend # Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA
+1A60 ; InCB; Extend # Mn TAI THAM SIGN SAKOT
+1A62 ; InCB; Extend # Mn TAI THAM VOWEL SIGN MAI SAT
+1A65..1A6C ; InCB; Extend # Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW
+1A73..1A7C ; InCB; Extend # Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN
+1A7F ; InCB; Extend # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT
+1AB0..1ABD ; InCB; Extend # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW
+1ABE ; InCB; Extend # Me COMBINING PARENTHESES OVERLAY
+1ABF..1ACE ; InCB; Extend # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T
+1B00..1B03 ; InCB; Extend # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG
+1B34 ; InCB; Extend # Mn BALINESE SIGN REREKAN
+1B35 ; InCB; Extend # Mc BALINESE VOWEL SIGN TEDUNG
+1B36..1B3A ; InCB; Extend # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA
+1B3B ; InCB; Extend # Mc BALINESE VOWEL SIGN RA REPA TEDUNG
+1B3C ; InCB; Extend # Mn BALINESE VOWEL SIGN LA LENGA
+1B3D ; InCB; Extend # Mc BALINESE VOWEL SIGN LA LENGA TEDUNG
+1B42 ; InCB; Extend # Mn BALINESE VOWEL SIGN PEPET
+1B43..1B44 ; InCB; Extend # Mc [2] BALINESE VOWEL SIGN PEPET TEDUNG..BALINESE ADEG ADEG
+1B6B..1B73 ; InCB; Extend # Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG
+1B80..1B81 ; InCB; Extend # Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR
+1BA2..1BA5 ; InCB; Extend # Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU
+1BA8..1BA9 ; InCB; Extend # Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG
+1BAA ; InCB; Extend # Mc SUNDANESE SIGN PAMAAEH
+1BAB..1BAD ; InCB; Extend # Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA
+1BE6 ; InCB; Extend # Mn BATAK SIGN TOMPI
+1BE8..1BE9 ; InCB; Extend # Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE
+1BED ; InCB; Extend # Mn BATAK VOWEL SIGN KARO O
+1BEF..1BF1 ; InCB; Extend # Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H
+1BF2..1BF3 ; InCB; Extend # Mc [2] BATAK PANGOLAT..BATAK PANONGONAN
+1C2C..1C33 ; InCB; Extend # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T
+1C36..1C37 ; InCB; Extend # Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA
+1CD0..1CD2 ; InCB; Extend # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA
+1CD4..1CE0 ; InCB; Extend # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA
+1CE2..1CE8 ; InCB; Extend # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL
+1CED ; InCB; Extend # Mn VEDIC SIGN TIRYAK
+1CF4 ; InCB; Extend # Mn VEDIC TONE CANDRA ABOVE
+1CF8..1CF9 ; InCB; Extend # Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE
+1DC0..1DFF ; InCB; Extend # Mn [64] COMBINING DOTTED GRAVE ACCENT..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW
+200D ; InCB; Extend # Cf ZERO WIDTH JOINER
+20D0..20DC ; InCB; Extend # Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE
+20DD..20E0 ; InCB; Extend # Me [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH
+20E1 ; InCB; Extend # Mn COMBINING LEFT RIGHT ARROW ABOVE
+20E2..20E4 ; InCB; Extend # Me [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE
+20E5..20F0 ; InCB; Extend # Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE
+2CEF..2CF1 ; InCB; Extend # Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS
+2D7F ; InCB; Extend # Mn TIFINAGH CONSONANT JOINER
+2DE0..2DFF ; InCB; Extend # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS
+302A..302D ; InCB; Extend # Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK
+302E..302F ; InCB; Extend # Mc [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK
+3099..309A ; InCB; Extend # Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+A66F ; InCB; Extend # Mn COMBINING CYRILLIC VZMET
+A670..A672 ; InCB; Extend # Me [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN
+A674..A67D ; InCB; Extend # Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK
+A69E..A69F ; InCB; Extend # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E
+A6F0..A6F1 ; InCB; Extend # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS
+A802 ; InCB; Extend # Mn SYLOTI NAGRI SIGN DVISVARA
+A806 ; InCB; Extend # Mn SYLOTI NAGRI SIGN HASANTA
+A80B ; InCB; Extend # Mn SYLOTI NAGRI SIGN ANUSVARA
+A825..A826 ; InCB; Extend # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E
+A82C ; InCB; Extend # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA
+A8C4..A8C5 ; InCB; Extend # Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU
+A8E0..A8F1 ; InCB; Extend # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA
+A8FF ; InCB; Extend # Mn DEVANAGARI VOWEL SIGN AY
+A926..A92D ; InCB; Extend # Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU
+A947..A951 ; InCB; Extend # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R
+A953 ; InCB; Extend # Mc REJANG VIRAMA
+A980..A982 ; InCB; Extend # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR
+A9B3 ; InCB; Extend # Mn JAVANESE SIGN CECAK TELU
+A9B6..A9B9 ; InCB; Extend # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT
+A9BC..A9BD ; InCB; Extend # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET
+A9C0 ; InCB; Extend # Mc JAVANESE PANGKON
+A9E5 ; InCB; Extend # Mn MYANMAR SIGN SHAN SAW
+AA29..AA2E ; InCB; Extend # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE
+AA31..AA32 ; InCB; Extend # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE
+AA35..AA36 ; InCB; Extend # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA
+AA43 ; InCB; Extend # Mn CHAM CONSONANT SIGN FINAL NG
+AA4C ; InCB; Extend # Mn CHAM CONSONANT SIGN FINAL M
+AA7C ; InCB; Extend # Mn MYANMAR SIGN TAI LAING TONE-2
+AAB0 ; InCB; Extend # Mn TAI VIET MAI KANG
+AAB2..AAB4 ; InCB; Extend # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U
+AAB7..AAB8 ; InCB; Extend # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA
+AABE..AABF ; InCB; Extend # Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK
+AAC1 ; InCB; Extend # Mn TAI VIET TONE MAI THO
+AAEC..AAED ; InCB; Extend # Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI
+AAF6 ; InCB; Extend # Mn MEETEI MAYEK VIRAMA
+ABE5 ; InCB; Extend # Mn MEETEI MAYEK VOWEL SIGN ANAP
+ABE8 ; InCB; Extend # Mn MEETEI MAYEK VOWEL SIGN UNAP
+ABED ; InCB; Extend # Mn MEETEI MAYEK APUN IYEK
+FB1E ; InCB; Extend # Mn HEBREW POINT JUDEO-SPANISH VARIKA
+FE00..FE0F ; InCB; Extend # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
+FE20..FE2F ; InCB; Extend # Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF
+FF9E..FF9F ; InCB; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
+101FD ; InCB; Extend # Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE
+102E0 ; InCB; Extend # Mn COPTIC EPACT THOUSANDS MARK
+10376..1037A ; InCB; Extend # Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII
+10A01..10A03 ; InCB; Extend # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R
+10A05..10A06 ; InCB; Extend # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O
+10A0C..10A0F ; InCB; Extend # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA
+10A38..10A3A ; InCB; Extend # Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW
+10A3F ; InCB; Extend # Mn KHAROSHTHI VIRAMA
+10AE5..10AE6 ; InCB; Extend # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW
+10D24..10D27 ; InCB; Extend # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI
+10D69..10D6D ; InCB; Extend # Mn [5] GARAY VOWEL SIGN E..GARAY CONSONANT NASALIZATION MARK
+10EAB..10EAC ; InCB; Extend # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK
+10EFC..10EFF ; InCB; Extend # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA
+10F46..10F50 ; InCB; Extend # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW
+10F82..10F85 ; InCB; Extend # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW
+11001 ; InCB; Extend # Mn BRAHMI SIGN ANUSVARA
+11038..11046 ; InCB; Extend # Mn [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA
+11070 ; InCB; Extend # Mn BRAHMI SIGN OLD TAMIL VIRAMA
+11073..11074 ; InCB; Extend # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O
+1107F..11081 ; InCB; Extend # Mn [3] BRAHMI NUMBER JOINER..KAITHI SIGN ANUSVARA
+110B3..110B6 ; InCB; Extend # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI
+110B9..110BA ; InCB; Extend # Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA
+110C2 ; InCB; Extend # Mn KAITHI VOWEL SIGN VOCALIC R
+11100..11102 ; InCB; Extend # Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA
+11127..1112B ; InCB; Extend # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU
+1112D..11134 ; InCB; Extend # Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA
+11173 ; InCB; Extend # Mn MAHAJANI SIGN NUKTA
+11180..11181 ; InCB; Extend # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA
+111B6..111BE ; InCB; Extend # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O
+111C0 ; InCB; Extend # Mc SHARADA SIGN VIRAMA
+111C9..111CC ; InCB; Extend # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK
+111CF ; InCB; Extend # Mn SHARADA SIGN INVERTED CANDRABINDU
+1122F..11231 ; InCB; Extend # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI
+11234 ; InCB; Extend # Mn KHOJKI SIGN ANUSVARA
+11235 ; InCB; Extend # Mc KHOJKI SIGN VIRAMA
+11236..11237 ; InCB; Extend # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA
+1123E ; InCB; Extend # Mn KHOJKI SIGN SUKUN
+11241 ; InCB; Extend # Mn KHOJKI VOWEL SIGN VOCALIC R
+112DF ; InCB; Extend # Mn KHUDAWADI SIGN ANUSVARA
+112E3..112EA ; InCB; Extend # Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA
+11300..11301 ; InCB; Extend # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU
+1133B..1133C ; InCB; Extend # Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA
+1133E ; InCB; Extend # Mc GRANTHA VOWEL SIGN AA
+11340 ; InCB; Extend # Mn GRANTHA VOWEL SIGN II
+1134D ; InCB; Extend # Mc GRANTHA SIGN VIRAMA
+11357 ; InCB; Extend # Mc GRANTHA AU LENGTH MARK
+11366..1136C ; InCB; Extend # Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX
+11370..11374 ; InCB; Extend # Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA
+113B8 ; InCB; Extend # Mc TULU-TIGALARI VOWEL SIGN AA
+113BB..113C0 ; InCB; Extend # Mn [6] TULU-TIGALARI VOWEL SIGN U..TULU-TIGALARI VOWEL SIGN VOCALIC LL
+113C2 ; InCB; Extend # Mc TULU-TIGALARI VOWEL SIGN EE
+113C5 ; InCB; Extend # Mc TULU-TIGALARI VOWEL SIGN AI
+113C7..113C9 ; InCB; Extend # Mc [3] TULU-TIGALARI VOWEL SIGN OO..TULU-TIGALARI AU LENGTH MARK
+113CE ; InCB; Extend # Mn TULU-TIGALARI SIGN VIRAMA
+113CF ; InCB; Extend # Mc TULU-TIGALARI SIGN LOOPED VIRAMA
+113D0 ; InCB; Extend # Mn TULU-TIGALARI CONJOINER
+113D2 ; InCB; Extend # Mn TULU-TIGALARI GEMINATION MARK
+113E1..113E2 ; InCB; Extend # Mn [2] TULU-TIGALARI VEDIC TONE SVARITA..TULU-TIGALARI VEDIC TONE ANUDATTA
+11438..1143F ; InCB; Extend # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI
+11442..11444 ; InCB; Extend # Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA
+11446 ; InCB; Extend # Mn NEWA SIGN NUKTA
+1145E ; InCB; Extend # Mn NEWA SANDHI MARK
+114B0 ; InCB; Extend # Mc TIRHUTA VOWEL SIGN AA
+114B3..114B8 ; InCB; Extend # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL
+114BA ; InCB; Extend # Mn TIRHUTA VOWEL SIGN SHORT E
+114BD ; InCB; Extend # Mc TIRHUTA VOWEL SIGN SHORT O
+114BF..114C0 ; InCB; Extend # Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA
+114C2..114C3 ; InCB; Extend # Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA
+115AF ; InCB; Extend # Mc SIDDHAM VOWEL SIGN AA
+115B2..115B5 ; InCB; Extend # Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR
+115BC..115BD ; InCB; Extend # Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA
+115BF..115C0 ; InCB; Extend # Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA
+115DC..115DD ; InCB; Extend # Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU
+11633..1163A ; InCB; Extend # Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI
+1163D ; InCB; Extend # Mn MODI SIGN ANUSVARA
+1163F..11640 ; InCB; Extend # Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA
+116AB ; InCB; Extend # Mn TAKRI SIGN ANUSVARA
+116AD ; InCB; Extend # Mn TAKRI VOWEL SIGN AA
+116B0..116B5 ; InCB; Extend # Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU
+116B6 ; InCB; Extend # Mc TAKRI SIGN VIRAMA
+116B7 ; InCB; Extend # Mn TAKRI SIGN NUKTA
+1171D ; InCB; Extend # Mn AHOM CONSONANT SIGN MEDIAL LA
+1171F ; InCB; Extend # Mn AHOM CONSONANT SIGN MEDIAL LIGATING RA
+11722..11725 ; InCB; Extend # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU
+11727..1172B ; InCB; Extend # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER
+1182F..11837 ; InCB; Extend # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA
+11839..1183A ; InCB; Extend # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA
+11930 ; InCB; Extend # Mc DIVES AKURU VOWEL SIGN AA
+1193B..1193C ; InCB; Extend # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU
+1193D ; InCB; Extend # Mc DIVES AKURU SIGN HALANTA
+1193E ; InCB; Extend # Mn DIVES AKURU VIRAMA
+11943 ; InCB; Extend # Mn DIVES AKURU SIGN NUKTA
+119D4..119D7 ; InCB; Extend # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR
+119DA..119DB ; InCB; Extend # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI
+119E0 ; InCB; Extend # Mn NANDINAGARI SIGN VIRAMA
+11A01..11A0A ; InCB; Extend # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK
+11A33..11A38 ; InCB; Extend # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA
+11A3B..11A3E ; InCB; Extend # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA
+11A47 ; InCB; Extend # Mn ZANABAZAR SQUARE SUBJOINER
+11A51..11A56 ; InCB; Extend # Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE
+11A59..11A5B ; InCB; Extend # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK
+11A8A..11A96 ; InCB; Extend # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA
+11A98..11A99 ; InCB; Extend # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER
+11C30..11C36 ; InCB; Extend # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L
+11C38..11C3D ; InCB; Extend # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA
+11C3F ; InCB; Extend # Mn BHAIKSUKI SIGN VIRAMA
+11C92..11CA7 ; InCB; Extend # Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA
+11CAA..11CB0 ; InCB; Extend # Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA
+11CB2..11CB3 ; InCB; Extend # Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E
+11CB5..11CB6 ; InCB; Extend # Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU
+11D31..11D36 ; InCB; Extend # Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R
+11D3A ; InCB; Extend # Mn MASARAM GONDI VOWEL SIGN E
+11D3C..11D3D ; InCB; Extend # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O
+11D3F..11D45 ; InCB; Extend # Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA
+11D47 ; InCB; Extend # Mn MASARAM GONDI RA-KARA
+11D90..11D91 ; InCB; Extend # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI
+11D95 ; InCB; Extend # Mn GUNJALA GONDI SIGN ANUSVARA
+11D97 ; InCB; Extend # Mn GUNJALA GONDI VIRAMA
+11EF3..11EF4 ; InCB; Extend # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U
+11F00..11F01 ; InCB; Extend # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA
+11F36..11F3A ; InCB; Extend # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R
+11F40 ; InCB; Extend # Mn KAWI VOWEL SIGN EU
+11F41 ; InCB; Extend # Mc KAWI SIGN KILLER
+11F42 ; InCB; Extend # Mn KAWI CONJOINER
+11F5A ; InCB; Extend # Mn KAWI SIGN NUKTA
+13440 ; InCB; Extend # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY
+13447..13455 ; InCB; Extend # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED
+1611E..16129 ; InCB; Extend # Mn [12] GURUNG KHEMA VOWEL SIGN AA..GURUNG KHEMA VOWEL LENGTH MARK
+1612D..1612F ; InCB; Extend # Mn [3] GURUNG KHEMA SIGN ANUSVARA..GURUNG KHEMA SIGN THOLHOMA
+16AF0..16AF4 ; InCB; Extend # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE
+16B30..16B36 ; InCB; Extend # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
+16F4F ; InCB; Extend # Mn MIAO SIGN CONSONANT MODIFIER BAR
+16F8F..16F92 ; InCB; Extend # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
+16FE4 ; InCB; Extend # Mn KHITAN SMALL SCRIPT FILLER
+16FF0..16FF1 ; InCB; Extend # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY
+1BC9D..1BC9E ; InCB; Extend # Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK
+1CF00..1CF2D ; InCB; Extend # Mn [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT
+1CF30..1CF46 ; InCB; Extend # Mn [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG
+1D165..1D166 ; InCB; Extend # Mc [2] MUSICAL SYMBOL COMBINING STEM..MUSICAL SYMBOL COMBINING SPRECHGESANG STEM
+1D167..1D169 ; InCB; Extend # Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3
+1D16D..1D172 ; InCB; Extend # Mc [6] MUSICAL SYMBOL COMBINING AUGMENTATION DOT..MUSICAL SYMBOL COMBINING FLAG-5
+1D17B..1D182 ; InCB; Extend # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE
+1D185..1D18B ; InCB; Extend # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE
+1D1AA..1D1AD ; InCB; Extend # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO
+1D242..1D244 ; InCB; Extend # Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME
+1DA00..1DA36 ; InCB; Extend # Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN
+1DA3B..1DA6C ; InCB; Extend # Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT
+1DA75 ; InCB; Extend # Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS
+1DA84 ; InCB; Extend # Mn SIGNWRITING LOCATION HEAD NECK
+1DA9B..1DA9F ; InCB; Extend # Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6
+1DAA1..1DAAF ; InCB; Extend # Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16
+1E000..1E006 ; InCB; Extend # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE
+1E008..1E018 ; InCB; Extend # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU
+1E01B..1E021 ; InCB; Extend # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI
+1E023..1E024 ; InCB; Extend # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS
+1E026..1E02A ; InCB; Extend # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA
+1E08F ; InCB; Extend # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+1E130..1E136 ; InCB; Extend # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D
+1E2AE ; InCB; Extend # Mn TOTO SIGN RISING TONE
+1E2EC..1E2EF ; InCB; Extend # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI
+1E4EC..1E4EF ; InCB; Extend # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH
+1E5EE..1E5EF ; InCB; Extend # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR
+1E8D0..1E8D6 ; InCB; Extend # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS
+1E944..1E94A ; InCB; Extend # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA
+1F3FB..1F3FF ; InCB; Extend # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6
+E0020..E007F ; InCB; Extend # Cf [96] TAG SPACE..CANCEL TAG
+E0100..E01EF ; InCB; Extend # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
+
+# Total code points: 2192
+
+# EOF
diff --git a/pkgs/characters/third_party/Unicode_Consortium/GraphemeBreakProperty.txt b/pkgs/characters/third_party/Unicode_Consortium/GraphemeBreakProperty.txt
new file mode 100644
index 0000000..a863397
--- /dev/null
+++ b/pkgs/characters/third_party/Unicode_Consortium/GraphemeBreakProperty.txt
@@ -0,0 +1,1503 @@
+# GraphemeBreakProperty-16.0.0.txt
+# Date: 2024-05-31, 18:09:38 GMT
+# © 2024 Unicode®, Inc.
+# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+# For terms of use and license, see https://www.unicode.org/terms_of_use.html
+#
+# Unicode Character Database
+# For documentation, see https://www.unicode.org/reports/tr44/
+
+# ================================================
+
+# Property: Grapheme_Cluster_Break
+
+# All code points not explicitly listed for Grapheme_Cluster_Break
+# have the value Other (XX).
+
+# @missing: 0000..10FFFF; Other
+
+# ================================================
+
+0600..0605 ; Prepend # Cf [6] ARABIC NUMBER SIGN..ARABIC NUMBER MARK ABOVE
+06DD ; Prepend # Cf ARABIC END OF AYAH
+070F ; Prepend # Cf SYRIAC ABBREVIATION MARK
+0890..0891 ; Prepend # Cf [2] ARABIC POUND MARK ABOVE..ARABIC PIASTRE MARK ABOVE
+08E2 ; Prepend # Cf ARABIC DISPUTED END OF AYAH
+0D4E ; Prepend # Lo MALAYALAM LETTER DOT REPH
+110BD ; Prepend # Cf KAITHI NUMBER SIGN
+110CD ; Prepend # Cf KAITHI NUMBER SIGN ABOVE
+111C2..111C3 ; Prepend # Lo [2] SHARADA SIGN JIHVAMULIYA..SHARADA SIGN UPADHMANIYA
+113D1 ; Prepend # Lo TULU-TIGALARI REPHA
+1193F ; Prepend # Lo DIVES AKURU PREFIXED NASAL SIGN
+11941 ; Prepend # Lo DIVES AKURU INITIAL RA
+11A3A ; Prepend # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA
+11A84..11A89 ; Prepend # Lo [6] SOYOMBO SIGN JIHVAMULIYA..SOYOMBO CLUSTER-INITIAL LETTER SA
+11D46 ; Prepend # Lo MASARAM GONDI REPHA
+11F02 ; Prepend # Lo KAWI SIGN REPHA
+
+# Total code points: 28
+
+# ================================================
+
+000D ; CR # Cc <control-000D>
+
+# Total code points: 1
+
+# ================================================
+
+000A ; LF # Cc <control-000A>
+
+# Total code points: 1
+
+# ================================================
+
+0000..0009 ; Control # Cc [10] <control-0000>..<control-0009>
+000B..000C ; Control # Cc [2] <control-000B>..<control-000C>
+000E..001F ; Control # Cc [18] <control-000E>..<control-001F>
+007F..009F ; Control # Cc [33] <control-007F>..<control-009F>
+00AD ; Control # Cf SOFT HYPHEN
+061C ; Control # Cf ARABIC LETTER MARK
+180E ; Control # Cf MONGOLIAN VOWEL SEPARATOR
+200B ; Control # Cf ZERO WIDTH SPACE
+200E..200F ; Control # Cf [2] LEFT-TO-RIGHT MARK..RIGHT-TO-LEFT MARK
+2028 ; Control # Zl LINE SEPARATOR
+2029 ; Control # Zp PARAGRAPH SEPARATOR
+202A..202E ; Control # Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
+2060..2064 ; Control # Cf [5] WORD JOINER..INVISIBLE PLUS
+2065 ; Control # Cn <reserved-2065>
+2066..206F ; Control # Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES
+FEFF ; Control # Cf ZERO WIDTH NO-BREAK SPACE
+FFF0..FFF8 ; Control # Cn [9] <reserved-FFF0>..<reserved-FFF8>
+FFF9..FFFB ; Control # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR
+13430..1343F ; Control # Cf [16] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE
+1BCA0..1BCA3 ; Control # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP
+1D173..1D17A ; Control # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE
+E0000 ; Control # Cn <reserved-E0000>
+E0001 ; Control # Cf LANGUAGE TAG
+E0002..E001F ; Control # Cn [30] <reserved-E0002>..<reserved-E001F>
+E0080..E00FF ; Control # Cn [128] <reserved-E0080>..<reserved-E00FF>
+E01F0..E0FFF ; Control # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
+
+# Total code points: 3893
+
+# ================================================
+
+0300..036F ; Extend # Mn [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X
+0483..0487 ; Extend # Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE
+0488..0489 ; Extend # Me [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN
+0591..05BD ; Extend # Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG
+05BF ; Extend # Mn HEBREW POINT RAFE
+05C1..05C2 ; Extend # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT
+05C4..05C5 ; Extend # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT
+05C7 ; Extend # Mn HEBREW POINT QAMATS QATAN
+0610..061A ; Extend # Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA
+064B..065F ; Extend # Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW
+0670 ; Extend # Mn ARABIC LETTER SUPERSCRIPT ALEF
+06D6..06DC ; Extend # Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN
+06DF..06E4 ; Extend # Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA
+06E7..06E8 ; Extend # Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON
+06EA..06ED ; Extend # Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM
+0711 ; Extend # Mn SYRIAC LETTER SUPERSCRIPT ALAPH
+0730..074A ; Extend # Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH
+07A6..07B0 ; Extend # Mn [11] THAANA ABAFILI..THAANA SUKUN
+07EB..07F3 ; Extend # Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE
+07FD ; Extend # Mn NKO DANTAYALAN
+0816..0819 ; Extend # Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH
+081B..0823 ; Extend # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A
+0825..0827 ; Extend # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U
+0829..082D ; Extend # Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA
+0859..085B ; Extend # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK
+0897..089F ; Extend # Mn [9] ARABIC PEPET..ARABIC HALF MADDA OVER MADDA
+08CA..08E1 ; Extend # Mn [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA
+08E3..0902 ; Extend # Mn [32] ARABIC TURNED DAMMA BELOW..DEVANAGARI SIGN ANUSVARA
+093A ; Extend # Mn DEVANAGARI VOWEL SIGN OE
+093C ; Extend # Mn DEVANAGARI SIGN NUKTA
+0941..0948 ; Extend # Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI
+094D ; Extend # Mn DEVANAGARI SIGN VIRAMA
+0951..0957 ; Extend # Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE
+0962..0963 ; Extend # Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL
+0981 ; Extend # Mn BENGALI SIGN CANDRABINDU
+09BC ; Extend # Mn BENGALI SIGN NUKTA
+09BE ; Extend # Mc BENGALI VOWEL SIGN AA
+09C1..09C4 ; Extend # Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR
+09CD ; Extend # Mn BENGALI SIGN VIRAMA
+09D7 ; Extend # Mc BENGALI AU LENGTH MARK
+09E2..09E3 ; Extend # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL
+09FE ; Extend # Mn BENGALI SANDHI MARK
+0A01..0A02 ; Extend # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI
+0A3C ; Extend # Mn GURMUKHI SIGN NUKTA
+0A41..0A42 ; Extend # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU
+0A47..0A48 ; Extend # Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI
+0A4B..0A4D ; Extend # Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA
+0A51 ; Extend # Mn GURMUKHI SIGN UDAAT
+0A70..0A71 ; Extend # Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK
+0A75 ; Extend # Mn GURMUKHI SIGN YAKASH
+0A81..0A82 ; Extend # Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA
+0ABC ; Extend # Mn GUJARATI SIGN NUKTA
+0AC1..0AC5 ; Extend # Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E
+0AC7..0AC8 ; Extend # Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI
+0ACD ; Extend # Mn GUJARATI SIGN VIRAMA
+0AE2..0AE3 ; Extend # Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL
+0AFA..0AFF ; Extend # Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE
+0B01 ; Extend # Mn ORIYA SIGN CANDRABINDU
+0B3C ; Extend # Mn ORIYA SIGN NUKTA
+0B3E ; Extend # Mc ORIYA VOWEL SIGN AA
+0B3F ; Extend # Mn ORIYA VOWEL SIGN I
+0B41..0B44 ; Extend # Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR
+0B4D ; Extend # Mn ORIYA SIGN VIRAMA
+0B55..0B56 ; Extend # Mn [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK
+0B57 ; Extend # Mc ORIYA AU LENGTH MARK
+0B62..0B63 ; Extend # Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL
+0B82 ; Extend # Mn TAMIL SIGN ANUSVARA
+0BBE ; Extend # Mc TAMIL VOWEL SIGN AA
+0BC0 ; Extend # Mn TAMIL VOWEL SIGN II
+0BCD ; Extend # Mn TAMIL SIGN VIRAMA
+0BD7 ; Extend # Mc TAMIL AU LENGTH MARK
+0C00 ; Extend # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE
+0C04 ; Extend # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE
+0C3C ; Extend # Mn TELUGU SIGN NUKTA
+0C3E..0C40 ; Extend # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II
+0C46..0C48 ; Extend # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI
+0C4A..0C4D ; Extend # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA
+0C55..0C56 ; Extend # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK
+0C62..0C63 ; Extend # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL
+0C81 ; Extend # Mn KANNADA SIGN CANDRABINDU
+0CBC ; Extend # Mn KANNADA SIGN NUKTA
+0CBF ; Extend # Mn KANNADA VOWEL SIGN I
+0CC0 ; Extend # Mc KANNADA VOWEL SIGN II
+0CC2 ; Extend # Mc KANNADA VOWEL SIGN UU
+0CC6 ; Extend # Mn KANNADA VOWEL SIGN E
+0CC7..0CC8 ; Extend # Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI
+0CCA..0CCB ; Extend # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO
+0CCC..0CCD ; Extend # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA
+0CD5..0CD6 ; Extend # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK
+0CE2..0CE3 ; Extend # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL
+0D00..0D01 ; Extend # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU
+0D3B..0D3C ; Extend # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA
+0D3E ; Extend # Mc MALAYALAM VOWEL SIGN AA
+0D41..0D44 ; Extend # Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR
+0D4D ; Extend # Mn MALAYALAM SIGN VIRAMA
+0D57 ; Extend # Mc MALAYALAM AU LENGTH MARK
+0D62..0D63 ; Extend # Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL
+0D81 ; Extend # Mn SINHALA SIGN CANDRABINDU
+0DCA ; Extend # Mn SINHALA SIGN AL-LAKUNA
+0DCF ; Extend # Mc SINHALA VOWEL SIGN AELA-PILLA
+0DD2..0DD4 ; Extend # Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA
+0DD6 ; Extend # Mn SINHALA VOWEL SIGN DIGA PAA-PILLA
+0DDF ; Extend # Mc SINHALA VOWEL SIGN GAYANUKITTA
+0E31 ; Extend # Mn THAI CHARACTER MAI HAN-AKAT
+0E34..0E3A ; Extend # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU
+0E47..0E4E ; Extend # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN
+0EB1 ; Extend # Mn LAO VOWEL SIGN MAI KAN
+0EB4..0EBC ; Extend # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO
+0EC8..0ECE ; Extend # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN
+0F18..0F19 ; Extend # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS
+0F35 ; Extend # Mn TIBETAN MARK NGAS BZUNG NYI ZLA
+0F37 ; Extend # Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS
+0F39 ; Extend # Mn TIBETAN MARK TSA -PHRU
+0F71..0F7E ; Extend # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO
+0F80..0F84 ; Extend # Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA
+0F86..0F87 ; Extend # Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS
+0F8D..0F97 ; Extend # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA
+0F99..0FBC ; Extend # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA
+0FC6 ; Extend # Mn TIBETAN SYMBOL PADMA GDAN
+102D..1030 ; Extend # Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU
+1032..1037 ; Extend # Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW
+1039..103A ; Extend # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT
+103D..103E ; Extend # Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA
+1058..1059 ; Extend # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL
+105E..1060 ; Extend # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA
+1071..1074 ; Extend # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE
+1082 ; Extend # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA
+1085..1086 ; Extend # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y
+108D ; Extend # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE
+109D ; Extend # Mn MYANMAR VOWEL SIGN AITON AI
+135D..135F ; Extend # Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK
+1712..1714 ; Extend # Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA
+1715 ; Extend # Mc TAGALOG SIGN PAMUDPOD
+1732..1733 ; Extend # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U
+1734 ; Extend # Mc HANUNOO SIGN PAMUDPOD
+1752..1753 ; Extend # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U
+1772..1773 ; Extend # Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U
+17B4..17B5 ; Extend # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
+17B7..17BD ; Extend # Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA
+17C6 ; Extend # Mn KHMER SIGN NIKAHIT
+17C9..17D3 ; Extend # Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT
+17DD ; Extend # Mn KHMER SIGN ATTHACAN
+180B..180D ; Extend # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
+180F ; Extend # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR
+1885..1886 ; Extend # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA
+18A9 ; Extend # Mn MONGOLIAN LETTER ALI GALI DAGALGA
+1920..1922 ; Extend # Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U
+1927..1928 ; Extend # Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O
+1932 ; Extend # Mn LIMBU SMALL LETTER ANUSVARA
+1939..193B ; Extend # Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I
+1A17..1A18 ; Extend # Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U
+1A1B ; Extend # Mn BUGINESE VOWEL SIGN AE
+1A56 ; Extend # Mn TAI THAM CONSONANT SIGN MEDIAL LA
+1A58..1A5E ; Extend # Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA
+1A60 ; Extend # Mn TAI THAM SIGN SAKOT
+1A62 ; Extend # Mn TAI THAM VOWEL SIGN MAI SAT
+1A65..1A6C ; Extend # Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW
+1A73..1A7C ; Extend # Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN
+1A7F ; Extend # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT
+1AB0..1ABD ; Extend # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW
+1ABE ; Extend # Me COMBINING PARENTHESES OVERLAY
+1ABF..1ACE ; Extend # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T
+1B00..1B03 ; Extend # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG
+1B34 ; Extend # Mn BALINESE SIGN REREKAN
+1B35 ; Extend # Mc BALINESE VOWEL SIGN TEDUNG
+1B36..1B3A ; Extend # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA
+1B3B ; Extend # Mc BALINESE VOWEL SIGN RA REPA TEDUNG
+1B3C ; Extend # Mn BALINESE VOWEL SIGN LA LENGA
+1B3D ; Extend # Mc BALINESE VOWEL SIGN LA LENGA TEDUNG
+1B42 ; Extend # Mn BALINESE VOWEL SIGN PEPET
+1B43..1B44 ; Extend # Mc [2] BALINESE VOWEL SIGN PEPET TEDUNG..BALINESE ADEG ADEG
+1B6B..1B73 ; Extend # Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG
+1B80..1B81 ; Extend # Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR
+1BA2..1BA5 ; Extend # Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU
+1BA8..1BA9 ; Extend # Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG
+1BAA ; Extend # Mc SUNDANESE SIGN PAMAAEH
+1BAB..1BAD ; Extend # Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA
+1BE6 ; Extend # Mn BATAK SIGN TOMPI
+1BE8..1BE9 ; Extend # Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE
+1BED ; Extend # Mn BATAK VOWEL SIGN KARO O
+1BEF..1BF1 ; Extend # Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H
+1BF2..1BF3 ; Extend # Mc [2] BATAK PANGOLAT..BATAK PANONGONAN
+1C2C..1C33 ; Extend # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T
+1C36..1C37 ; Extend # Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA
+1CD0..1CD2 ; Extend # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA
+1CD4..1CE0 ; Extend # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA
+1CE2..1CE8 ; Extend # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL
+1CED ; Extend # Mn VEDIC SIGN TIRYAK
+1CF4 ; Extend # Mn VEDIC TONE CANDRA ABOVE
+1CF8..1CF9 ; Extend # Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE
+1DC0..1DFF ; Extend # Mn [64] COMBINING DOTTED GRAVE ACCENT..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW
+200C ; Extend # Cf ZERO WIDTH NON-JOINER
+20D0..20DC ; Extend # Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE
+20DD..20E0 ; Extend # Me [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH
+20E1 ; Extend # Mn COMBINING LEFT RIGHT ARROW ABOVE
+20E2..20E4 ; Extend # Me [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE
+20E5..20F0 ; Extend # Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE
+2CEF..2CF1 ; Extend # Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS
+2D7F ; Extend # Mn TIFINAGH CONSONANT JOINER
+2DE0..2DFF ; Extend # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS
+302A..302D ; Extend # Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK
+302E..302F ; Extend # Mc [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK
+3099..309A ; Extend # Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+A66F ; Extend # Mn COMBINING CYRILLIC VZMET
+A670..A672 ; Extend # Me [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN
+A674..A67D ; Extend # Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK
+A69E..A69F ; Extend # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E
+A6F0..A6F1 ; Extend # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS
+A802 ; Extend # Mn SYLOTI NAGRI SIGN DVISVARA
+A806 ; Extend # Mn SYLOTI NAGRI SIGN HASANTA
+A80B ; Extend # Mn SYLOTI NAGRI SIGN ANUSVARA
+A825..A826 ; Extend # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E
+A82C ; Extend # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA
+A8C4..A8C5 ; Extend # Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU
+A8E0..A8F1 ; Extend # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA
+A8FF ; Extend # Mn DEVANAGARI VOWEL SIGN AY
+A926..A92D ; Extend # Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU
+A947..A951 ; Extend # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R
+A953 ; Extend # Mc REJANG VIRAMA
+A980..A982 ; Extend # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR
+A9B3 ; Extend # Mn JAVANESE SIGN CECAK TELU
+A9B6..A9B9 ; Extend # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT
+A9BC..A9BD ; Extend # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET
+A9C0 ; Extend # Mc JAVANESE PANGKON
+A9E5 ; Extend # Mn MYANMAR SIGN SHAN SAW
+AA29..AA2E ; Extend # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE
+AA31..AA32 ; Extend # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE
+AA35..AA36 ; Extend # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA
+AA43 ; Extend # Mn CHAM CONSONANT SIGN FINAL NG
+AA4C ; Extend # Mn CHAM CONSONANT SIGN FINAL M
+AA7C ; Extend # Mn MYANMAR SIGN TAI LAING TONE-2
+AAB0 ; Extend # Mn TAI VIET MAI KANG
+AAB2..AAB4 ; Extend # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U
+AAB7..AAB8 ; Extend # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA
+AABE..AABF ; Extend # Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK
+AAC1 ; Extend # Mn TAI VIET TONE MAI THO
+AAEC..AAED ; Extend # Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI
+AAF6 ; Extend # Mn MEETEI MAYEK VIRAMA
+ABE5 ; Extend # Mn MEETEI MAYEK VOWEL SIGN ANAP
+ABE8 ; Extend # Mn MEETEI MAYEK VOWEL SIGN UNAP
+ABED ; Extend # Mn MEETEI MAYEK APUN IYEK
+FB1E ; Extend # Mn HEBREW POINT JUDEO-SPANISH VARIKA
+FE00..FE0F ; Extend # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
+FE20..FE2F ; Extend # Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF
+FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
+101FD ; Extend # Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE
+102E0 ; Extend # Mn COPTIC EPACT THOUSANDS MARK
+10376..1037A ; Extend # Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII
+10A01..10A03 ; Extend # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R
+10A05..10A06 ; Extend # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O
+10A0C..10A0F ; Extend # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA
+10A38..10A3A ; Extend # Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW
+10A3F ; Extend # Mn KHAROSHTHI VIRAMA
+10AE5..10AE6 ; Extend # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW
+10D24..10D27 ; Extend # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI
+10D69..10D6D ; Extend # Mn [5] GARAY VOWEL SIGN E..GARAY CONSONANT NASALIZATION MARK
+10EAB..10EAC ; Extend # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK
+10EFC..10EFF ; Extend # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA
+10F46..10F50 ; Extend # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW
+10F82..10F85 ; Extend # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW
+11001 ; Extend # Mn BRAHMI SIGN ANUSVARA
+11038..11046 ; Extend # Mn [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA
+11070 ; Extend # Mn BRAHMI SIGN OLD TAMIL VIRAMA
+11073..11074 ; Extend # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O
+1107F..11081 ; Extend # Mn [3] BRAHMI NUMBER JOINER..KAITHI SIGN ANUSVARA
+110B3..110B6 ; Extend # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI
+110B9..110BA ; Extend # Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA
+110C2 ; Extend # Mn KAITHI VOWEL SIGN VOCALIC R
+11100..11102 ; Extend # Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA
+11127..1112B ; Extend # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU
+1112D..11134 ; Extend # Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA
+11173 ; Extend # Mn MAHAJANI SIGN NUKTA
+11180..11181 ; Extend # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA
+111B6..111BE ; Extend # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O
+111C0 ; Extend # Mc SHARADA SIGN VIRAMA
+111C9..111CC ; Extend # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK
+111CF ; Extend # Mn SHARADA SIGN INVERTED CANDRABINDU
+1122F..11231 ; Extend # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI
+11234 ; Extend # Mn KHOJKI SIGN ANUSVARA
+11235 ; Extend # Mc KHOJKI SIGN VIRAMA
+11236..11237 ; Extend # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA
+1123E ; Extend # Mn KHOJKI SIGN SUKUN
+11241 ; Extend # Mn KHOJKI VOWEL SIGN VOCALIC R
+112DF ; Extend # Mn KHUDAWADI SIGN ANUSVARA
+112E3..112EA ; Extend # Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA
+11300..11301 ; Extend # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU
+1133B..1133C ; Extend # Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA
+1133E ; Extend # Mc GRANTHA VOWEL SIGN AA
+11340 ; Extend # Mn GRANTHA VOWEL SIGN II
+1134D ; Extend # Mc GRANTHA SIGN VIRAMA
+11357 ; Extend # Mc GRANTHA AU LENGTH MARK
+11366..1136C ; Extend # Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX
+11370..11374 ; Extend # Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA
+113B8 ; Extend # Mc TULU-TIGALARI VOWEL SIGN AA
+113BB..113C0 ; Extend # Mn [6] TULU-TIGALARI VOWEL SIGN U..TULU-TIGALARI VOWEL SIGN VOCALIC LL
+113C2 ; Extend # Mc TULU-TIGALARI VOWEL SIGN EE
+113C5 ; Extend # Mc TULU-TIGALARI VOWEL SIGN AI
+113C7..113C9 ; Extend # Mc [3] TULU-TIGALARI VOWEL SIGN OO..TULU-TIGALARI AU LENGTH MARK
+113CE ; Extend # Mn TULU-TIGALARI SIGN VIRAMA
+113CF ; Extend # Mc TULU-TIGALARI SIGN LOOPED VIRAMA
+113D0 ; Extend # Mn TULU-TIGALARI CONJOINER
+113D2 ; Extend # Mn TULU-TIGALARI GEMINATION MARK
+113E1..113E2 ; Extend # Mn [2] TULU-TIGALARI VEDIC TONE SVARITA..TULU-TIGALARI VEDIC TONE ANUDATTA
+11438..1143F ; Extend # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI
+11442..11444 ; Extend # Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA
+11446 ; Extend # Mn NEWA SIGN NUKTA
+1145E ; Extend # Mn NEWA SANDHI MARK
+114B0 ; Extend # Mc TIRHUTA VOWEL SIGN AA
+114B3..114B8 ; Extend # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL
+114BA ; Extend # Mn TIRHUTA VOWEL SIGN SHORT E
+114BD ; Extend # Mc TIRHUTA VOWEL SIGN SHORT O
+114BF..114C0 ; Extend # Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA
+114C2..114C3 ; Extend # Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA
+115AF ; Extend # Mc SIDDHAM VOWEL SIGN AA
+115B2..115B5 ; Extend # Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR
+115BC..115BD ; Extend # Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA
+115BF..115C0 ; Extend # Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA
+115DC..115DD ; Extend # Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU
+11633..1163A ; Extend # Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI
+1163D ; Extend # Mn MODI SIGN ANUSVARA
+1163F..11640 ; Extend # Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA
+116AB ; Extend # Mn TAKRI SIGN ANUSVARA
+116AD ; Extend # Mn TAKRI VOWEL SIGN AA
+116B0..116B5 ; Extend # Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU
+116B6 ; Extend # Mc TAKRI SIGN VIRAMA
+116B7 ; Extend # Mn TAKRI SIGN NUKTA
+1171D ; Extend # Mn AHOM CONSONANT SIGN MEDIAL LA
+1171F ; Extend # Mn AHOM CONSONANT SIGN MEDIAL LIGATING RA
+11722..11725 ; Extend # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU
+11727..1172B ; Extend # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER
+1182F..11837 ; Extend # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA
+11839..1183A ; Extend # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA
+11930 ; Extend # Mc DIVES AKURU VOWEL SIGN AA
+1193B..1193C ; Extend # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU
+1193D ; Extend # Mc DIVES AKURU SIGN HALANTA
+1193E ; Extend # Mn DIVES AKURU VIRAMA
+11943 ; Extend # Mn DIVES AKURU SIGN NUKTA
+119D4..119D7 ; Extend # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR
+119DA..119DB ; Extend # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI
+119E0 ; Extend # Mn NANDINAGARI SIGN VIRAMA
+11A01..11A0A ; Extend # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK
+11A33..11A38 ; Extend # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA
+11A3B..11A3E ; Extend # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA
+11A47 ; Extend # Mn ZANABAZAR SQUARE SUBJOINER
+11A51..11A56 ; Extend # Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE
+11A59..11A5B ; Extend # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK
+11A8A..11A96 ; Extend # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA
+11A98..11A99 ; Extend # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER
+11C30..11C36 ; Extend # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L
+11C38..11C3D ; Extend # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA
+11C3F ; Extend # Mn BHAIKSUKI SIGN VIRAMA
+11C92..11CA7 ; Extend # Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA
+11CAA..11CB0 ; Extend # Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA
+11CB2..11CB3 ; Extend # Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E
+11CB5..11CB6 ; Extend # Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU
+11D31..11D36 ; Extend # Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R
+11D3A ; Extend # Mn MASARAM GONDI VOWEL SIGN E
+11D3C..11D3D ; Extend # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O
+11D3F..11D45 ; Extend # Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA
+11D47 ; Extend # Mn MASARAM GONDI RA-KARA
+11D90..11D91 ; Extend # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI
+11D95 ; Extend # Mn GUNJALA GONDI SIGN ANUSVARA
+11D97 ; Extend # Mn GUNJALA GONDI VIRAMA
+11EF3..11EF4 ; Extend # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U
+11F00..11F01 ; Extend # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA
+11F36..11F3A ; Extend # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R
+11F40 ; Extend # Mn KAWI VOWEL SIGN EU
+11F41 ; Extend # Mc KAWI SIGN KILLER
+11F42 ; Extend # Mn KAWI CONJOINER
+11F5A ; Extend # Mn KAWI SIGN NUKTA
+13440 ; Extend # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY
+13447..13455 ; Extend # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED
+1611E..16129 ; Extend # Mn [12] GURUNG KHEMA VOWEL SIGN AA..GURUNG KHEMA VOWEL LENGTH MARK
+1612D..1612F ; Extend # Mn [3] GURUNG KHEMA SIGN ANUSVARA..GURUNG KHEMA SIGN THOLHOMA
+16AF0..16AF4 ; Extend # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE
+16B30..16B36 ; Extend # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
+16F4F ; Extend # Mn MIAO SIGN CONSONANT MODIFIER BAR
+16F8F..16F92 ; Extend # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
+16FE4 ; Extend # Mn KHITAN SMALL SCRIPT FILLER
+16FF0..16FF1 ; Extend # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY
+1BC9D..1BC9E ; Extend # Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK
+1CF00..1CF2D ; Extend # Mn [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT
+1CF30..1CF46 ; Extend # Mn [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG
+1D165..1D166 ; Extend # Mc [2] MUSICAL SYMBOL COMBINING STEM..MUSICAL SYMBOL COMBINING SPRECHGESANG STEM
+1D167..1D169 ; Extend # Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3
+1D16D..1D172 ; Extend # Mc [6] MUSICAL SYMBOL COMBINING AUGMENTATION DOT..MUSICAL SYMBOL COMBINING FLAG-5
+1D17B..1D182 ; Extend # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE
+1D185..1D18B ; Extend # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE
+1D1AA..1D1AD ; Extend # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO
+1D242..1D244 ; Extend # Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME
+1DA00..1DA36 ; Extend # Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN
+1DA3B..1DA6C ; Extend # Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT
+1DA75 ; Extend # Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS
+1DA84 ; Extend # Mn SIGNWRITING LOCATION HEAD NECK
+1DA9B..1DA9F ; Extend # Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6
+1DAA1..1DAAF ; Extend # Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16
+1E000..1E006 ; Extend # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE
+1E008..1E018 ; Extend # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU
+1E01B..1E021 ; Extend # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI
+1E023..1E024 ; Extend # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS
+1E026..1E02A ; Extend # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA
+1E08F ; Extend # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+1E130..1E136 ; Extend # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D
+1E2AE ; Extend # Mn TOTO SIGN RISING TONE
+1E2EC..1E2EF ; Extend # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI
+1E4EC..1E4EF ; Extend # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH
+1E5EE..1E5EF ; Extend # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR
+1E8D0..1E8D6 ; Extend # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS
+1E944..1E94A ; Extend # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA
+1F3FB..1F3FF ; Extend # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6
+E0020..E007F ; Extend # Cf [96] TAG SPACE..CANCEL TAG
+E0100..E01EF ; Extend # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
+
+# Total code points: 2198
+
+# ================================================
+
+1F1E6..1F1FF ; Regional_Indicator # So [26] REGIONAL INDICATOR SYMBOL LETTER A..REGIONAL INDICATOR SYMBOL LETTER Z
+
+# Total code points: 26
+
+# ================================================
+
+0903 ; SpacingMark # Mc DEVANAGARI SIGN VISARGA
+093B ; SpacingMark # Mc DEVANAGARI VOWEL SIGN OOE
+093E..0940 ; SpacingMark # Mc [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II
+0949..094C ; SpacingMark # Mc [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU
+094E..094F ; SpacingMark # Mc [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW
+0982..0983 ; SpacingMark # Mc [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA
+09BF..09C0 ; SpacingMark # Mc [2] BENGALI VOWEL SIGN I..BENGALI VOWEL SIGN II
+09C7..09C8 ; SpacingMark # Mc [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI
+09CB..09CC ; SpacingMark # Mc [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU
+0A03 ; SpacingMark # Mc GURMUKHI SIGN VISARGA
+0A3E..0A40 ; SpacingMark # Mc [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II
+0A83 ; SpacingMark # Mc GUJARATI SIGN VISARGA
+0ABE..0AC0 ; SpacingMark # Mc [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II
+0AC9 ; SpacingMark # Mc GUJARATI VOWEL SIGN CANDRA O
+0ACB..0ACC ; SpacingMark # Mc [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU
+0B02..0B03 ; SpacingMark # Mc [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA
+0B40 ; SpacingMark # Mc ORIYA VOWEL SIGN II
+0B47..0B48 ; SpacingMark # Mc [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI
+0B4B..0B4C ; SpacingMark # Mc [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU
+0BBF ; SpacingMark # Mc TAMIL VOWEL SIGN I
+0BC1..0BC2 ; SpacingMark # Mc [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU
+0BC6..0BC8 ; SpacingMark # Mc [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI
+0BCA..0BCC ; SpacingMark # Mc [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU
+0C01..0C03 ; SpacingMark # Mc [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA
+0C41..0C44 ; SpacingMark # Mc [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR
+0C82..0C83 ; SpacingMark # Mc [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA
+0CBE ; SpacingMark # Mc KANNADA VOWEL SIGN AA
+0CC1 ; SpacingMark # Mc KANNADA VOWEL SIGN U
+0CC3..0CC4 ; SpacingMark # Mc [2] KANNADA VOWEL SIGN VOCALIC R..KANNADA VOWEL SIGN VOCALIC RR
+0CF3 ; SpacingMark # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT
+0D02..0D03 ; SpacingMark # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA
+0D3F..0D40 ; SpacingMark # Mc [2] MALAYALAM VOWEL SIGN I..MALAYALAM VOWEL SIGN II
+0D46..0D48 ; SpacingMark # Mc [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI
+0D4A..0D4C ; SpacingMark # Mc [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU
+0D82..0D83 ; SpacingMark # Mc [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA
+0DD0..0DD1 ; SpacingMark # Mc [2] SINHALA VOWEL SIGN KETTI AEDA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA
+0DD8..0DDE ; SpacingMark # Mc [7] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN KOMBUVA HAA GAYANUKITTA
+0DF2..0DF3 ; SpacingMark # Mc [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA
+0E33 ; SpacingMark # Lo THAI CHARACTER SARA AM
+0EB3 ; SpacingMark # Lo LAO VOWEL SIGN AM
+0F3E..0F3F ; SpacingMark # Mc [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES
+0F7F ; SpacingMark # Mc TIBETAN SIGN RNAM BCAD
+1031 ; SpacingMark # Mc MYANMAR VOWEL SIGN E
+103B..103C ; SpacingMark # Mc [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA
+1056..1057 ; SpacingMark # Mc [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR
+1084 ; SpacingMark # Mc MYANMAR VOWEL SIGN SHAN E
+17B6 ; SpacingMark # Mc KHMER VOWEL SIGN AA
+17BE..17C5 ; SpacingMark # Mc [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU
+17C7..17C8 ; SpacingMark # Mc [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU
+1923..1926 ; SpacingMark # Mc [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU
+1929..192B ; SpacingMark # Mc [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA
+1930..1931 ; SpacingMark # Mc [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA
+1933..1938 ; SpacingMark # Mc [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA
+1A19..1A1A ; SpacingMark # Mc [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O
+1A55 ; SpacingMark # Mc TAI THAM CONSONANT SIGN MEDIAL RA
+1A57 ; SpacingMark # Mc TAI THAM CONSONANT SIGN LA TANG LAI
+1A6D..1A72 ; SpacingMark # Mc [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI
+1B04 ; SpacingMark # Mc BALINESE SIGN BISAH
+1B3E..1B41 ; SpacingMark # Mc [4] BALINESE VOWEL SIGN TALING..BALINESE VOWEL SIGN TALING REPA TEDUNG
+1B82 ; SpacingMark # Mc SUNDANESE SIGN PANGWISAD
+1BA1 ; SpacingMark # Mc SUNDANESE CONSONANT SIGN PAMINGKAL
+1BA6..1BA7 ; SpacingMark # Mc [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG
+1BE7 ; SpacingMark # Mc BATAK VOWEL SIGN E
+1BEA..1BEC ; SpacingMark # Mc [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O
+1BEE ; SpacingMark # Mc BATAK VOWEL SIGN U
+1C24..1C2B ; SpacingMark # Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU
+1C34..1C35 ; SpacingMark # Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG
+1CE1 ; SpacingMark # Mc VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA
+1CF7 ; SpacingMark # Mc VEDIC SIGN ATIKRAMA
+A823..A824 ; SpacingMark # Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I
+A827 ; SpacingMark # Mc SYLOTI NAGRI VOWEL SIGN OO
+A880..A881 ; SpacingMark # Mc [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA
+A8B4..A8C3 ; SpacingMark # Mc [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU
+A952 ; SpacingMark # Mc REJANG CONSONANT SIGN H
+A983 ; SpacingMark # Mc JAVANESE SIGN WIGNYAN
+A9B4..A9B5 ; SpacingMark # Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG
+A9BA..A9BB ; SpacingMark # Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE
+A9BE..A9BF ; SpacingMark # Mc [2] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE CONSONANT SIGN CAKRA
+AA2F..AA30 ; SpacingMark # Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI
+AA33..AA34 ; SpacingMark # Mc [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA
+AA4D ; SpacingMark # Mc CHAM CONSONANT SIGN FINAL H
+AAEB ; SpacingMark # Mc MEETEI MAYEK VOWEL SIGN II
+AAEE..AAEF ; SpacingMark # Mc [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU
+AAF5 ; SpacingMark # Mc MEETEI MAYEK VOWEL SIGN VISARGA
+ABE3..ABE4 ; SpacingMark # Mc [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP
+ABE6..ABE7 ; SpacingMark # Mc [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP
+ABE9..ABEA ; SpacingMark # Mc [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG
+ABEC ; SpacingMark # Mc MEETEI MAYEK LUM IYEK
+11000 ; SpacingMark # Mc BRAHMI SIGN CANDRABINDU
+11002 ; SpacingMark # Mc BRAHMI SIGN VISARGA
+11082 ; SpacingMark # Mc KAITHI SIGN VISARGA
+110B0..110B2 ; SpacingMark # Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II
+110B7..110B8 ; SpacingMark # Mc [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU
+1112C ; SpacingMark # Mc CHAKMA VOWEL SIGN E
+11145..11146 ; SpacingMark # Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI
+11182 ; SpacingMark # Mc SHARADA SIGN VISARGA
+111B3..111B5 ; SpacingMark # Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II
+111BF ; SpacingMark # Mc SHARADA VOWEL SIGN AU
+111CE ; SpacingMark # Mc SHARADA VOWEL SIGN PRISHTHAMATRA E
+1122C..1122E ; SpacingMark # Mc [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II
+11232..11233 ; SpacingMark # Mc [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU
+112E0..112E2 ; SpacingMark # Mc [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II
+11302..11303 ; SpacingMark # Mc [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA
+1133F ; SpacingMark # Mc GRANTHA VOWEL SIGN I
+11341..11344 ; SpacingMark # Mc [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR
+11347..11348 ; SpacingMark # Mc [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI
+1134B..1134C ; SpacingMark # Mc [2] GRANTHA VOWEL SIGN OO..GRANTHA VOWEL SIGN AU
+11362..11363 ; SpacingMark # Mc [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL
+113B9..113BA ; SpacingMark # Mc [2] TULU-TIGALARI VOWEL SIGN I..TULU-TIGALARI VOWEL SIGN II
+113CA ; SpacingMark # Mc TULU-TIGALARI SIGN CANDRA ANUNASIKA
+113CC..113CD ; SpacingMark # Mc [2] TULU-TIGALARI SIGN ANUSVARA..TULU-TIGALARI SIGN VISARGA
+11435..11437 ; SpacingMark # Mc [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II
+11440..11441 ; SpacingMark # Mc [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU
+11445 ; SpacingMark # Mc NEWA SIGN VISARGA
+114B1..114B2 ; SpacingMark # Mc [2] TIRHUTA VOWEL SIGN I..TIRHUTA VOWEL SIGN II
+114B9 ; SpacingMark # Mc TIRHUTA VOWEL SIGN E
+114BB..114BC ; SpacingMark # Mc [2] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN O
+114BE ; SpacingMark # Mc TIRHUTA VOWEL SIGN AU
+114C1 ; SpacingMark # Mc TIRHUTA SIGN VISARGA
+115B0..115B1 ; SpacingMark # Mc [2] SIDDHAM VOWEL SIGN I..SIDDHAM VOWEL SIGN II
+115B8..115BB ; SpacingMark # Mc [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU
+115BE ; SpacingMark # Mc SIDDHAM SIGN VISARGA
+11630..11632 ; SpacingMark # Mc [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II
+1163B..1163C ; SpacingMark # Mc [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU
+1163E ; SpacingMark # Mc MODI SIGN VISARGA
+116AC ; SpacingMark # Mc TAKRI SIGN VISARGA
+116AE..116AF ; SpacingMark # Mc [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II
+1171E ; SpacingMark # Mc AHOM CONSONANT SIGN MEDIAL RA
+11726 ; SpacingMark # Mc AHOM VOWEL SIGN E
+1182C..1182E ; SpacingMark # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II
+11838 ; SpacingMark # Mc DOGRA SIGN VISARGA
+11931..11935 ; SpacingMark # Mc [5] DIVES AKURU VOWEL SIGN I..DIVES AKURU VOWEL SIGN E
+11937..11938 ; SpacingMark # Mc [2] DIVES AKURU VOWEL SIGN AI..DIVES AKURU VOWEL SIGN O
+11940 ; SpacingMark # Mc DIVES AKURU MEDIAL YA
+11942 ; SpacingMark # Mc DIVES AKURU MEDIAL RA
+119D1..119D3 ; SpacingMark # Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II
+119DC..119DF ; SpacingMark # Mc [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA
+119E4 ; SpacingMark # Mc NANDINAGARI VOWEL SIGN PRISHTHAMATRA E
+11A39 ; SpacingMark # Mc ZANABAZAR SQUARE SIGN VISARGA
+11A57..11A58 ; SpacingMark # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU
+11A97 ; SpacingMark # Mc SOYOMBO SIGN VISARGA
+11C2F ; SpacingMark # Mc BHAIKSUKI VOWEL SIGN AA
+11C3E ; SpacingMark # Mc BHAIKSUKI SIGN VISARGA
+11CA9 ; SpacingMark # Mc MARCHEN SUBJOINED LETTER YA
+11CB1 ; SpacingMark # Mc MARCHEN VOWEL SIGN I
+11CB4 ; SpacingMark # Mc MARCHEN VOWEL SIGN O
+11D8A..11D8E ; SpacingMark # Mc [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU
+11D93..11D94 ; SpacingMark # Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU
+11D96 ; SpacingMark # Mc GUNJALA GONDI SIGN VISARGA
+11EF5..11EF6 ; SpacingMark # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O
+11F03 ; SpacingMark # Mc KAWI SIGN VISARGA
+11F34..11F35 ; SpacingMark # Mc [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA
+11F3E..11F3F ; SpacingMark # Mc [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI
+1612A..1612C ; SpacingMark # Mc [3] GURUNG KHEMA CONSONANT SIGN MEDIAL YA..GURUNG KHEMA CONSONANT SIGN MEDIAL HA
+16F51..16F87 ; SpacingMark # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI
+
+# Total code points: 378
+
+# ================================================
+
+1100..115F ; L # Lo [96] HANGUL CHOSEONG KIYEOK..HANGUL CHOSEONG FILLER
+A960..A97C ; L # Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH
+
+# Total code points: 125
+
+# ================================================
+
+1160..11A7 ; V # Lo [72] HANGUL JUNGSEONG FILLER..HANGUL JUNGSEONG O-YAE
+D7B0..D7C6 ; V # Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E
+16D63 ; V # Lo KIRAT RAI VOWEL SIGN AA
+16D67..16D6A ; V # Lo [4] KIRAT RAI VOWEL SIGN E..KIRAT RAI VOWEL SIGN AU
+
+# Total code points: 100
+
+# ================================================
+
+11A8..11FF ; T # Lo [88] HANGUL JONGSEONG KIYEOK..HANGUL JONGSEONG SSANGNIEUN
+D7CB..D7FB ; T # Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH
+
+# Total code points: 137
+
+# ================================================
+
+AC00 ; LV # Lo HANGUL SYLLABLE GA
+AC1C ; LV # Lo HANGUL SYLLABLE GAE
+AC38 ; LV # Lo HANGUL SYLLABLE GYA
+AC54 ; LV # Lo HANGUL SYLLABLE GYAE
+AC70 ; LV # Lo HANGUL SYLLABLE GEO
+AC8C ; LV # Lo HANGUL SYLLABLE GE
+ACA8 ; LV # Lo HANGUL SYLLABLE GYEO
+ACC4 ; LV # Lo HANGUL SYLLABLE GYE
+ACE0 ; LV # Lo HANGUL SYLLABLE GO
+ACFC ; LV # Lo HANGUL SYLLABLE GWA
+AD18 ; LV # Lo HANGUL SYLLABLE GWAE
+AD34 ; LV # Lo HANGUL SYLLABLE GOE
+AD50 ; LV # Lo HANGUL SYLLABLE GYO
+AD6C ; LV # Lo HANGUL SYLLABLE GU
+AD88 ; LV # Lo HANGUL SYLLABLE GWEO
+ADA4 ; LV # Lo HANGUL SYLLABLE GWE
+ADC0 ; LV # Lo HANGUL SYLLABLE GWI
+ADDC ; LV # Lo HANGUL SYLLABLE GYU
+ADF8 ; LV # Lo HANGUL SYLLABLE GEU
+AE14 ; LV # Lo HANGUL SYLLABLE GYI
+AE30 ; LV # Lo HANGUL SYLLABLE GI
+AE4C ; LV # Lo HANGUL SYLLABLE GGA
+AE68 ; LV # Lo HANGUL SYLLABLE GGAE
+AE84 ; LV # Lo HANGUL SYLLABLE GGYA
+AEA0 ; LV # Lo HANGUL SYLLABLE GGYAE
+AEBC ; LV # Lo HANGUL SYLLABLE GGEO
+AED8 ; LV # Lo HANGUL SYLLABLE GGE
+AEF4 ; LV # Lo HANGUL SYLLABLE GGYEO
+AF10 ; LV # Lo HANGUL SYLLABLE GGYE
+AF2C ; LV # Lo HANGUL SYLLABLE GGO
+AF48 ; LV # Lo HANGUL SYLLABLE GGWA
+AF64 ; LV # Lo HANGUL SYLLABLE GGWAE
+AF80 ; LV # Lo HANGUL SYLLABLE GGOE
+AF9C ; LV # Lo HANGUL SYLLABLE GGYO
+AFB8 ; LV # Lo HANGUL SYLLABLE GGU
+AFD4 ; LV # Lo HANGUL SYLLABLE GGWEO
+AFF0 ; LV # Lo HANGUL SYLLABLE GGWE
+B00C ; LV # Lo HANGUL SYLLABLE GGWI
+B028 ; LV # Lo HANGUL SYLLABLE GGYU
+B044 ; LV # Lo HANGUL SYLLABLE GGEU
+B060 ; LV # Lo HANGUL SYLLABLE GGYI
+B07C ; LV # Lo HANGUL SYLLABLE GGI
+B098 ; LV # Lo HANGUL SYLLABLE NA
+B0B4 ; LV # Lo HANGUL SYLLABLE NAE
+B0D0 ; LV # Lo HANGUL SYLLABLE NYA
+B0EC ; LV # Lo HANGUL SYLLABLE NYAE
+B108 ; LV # Lo HANGUL SYLLABLE NEO
+B124 ; LV # Lo HANGUL SYLLABLE NE
+B140 ; LV # Lo HANGUL SYLLABLE NYEO
+B15C ; LV # Lo HANGUL SYLLABLE NYE
+B178 ; LV # Lo HANGUL SYLLABLE NO
+B194 ; LV # Lo HANGUL SYLLABLE NWA
+B1B0 ; LV # Lo HANGUL SYLLABLE NWAE
+B1CC ; LV # Lo HANGUL SYLLABLE NOE
+B1E8 ; LV # Lo HANGUL SYLLABLE NYO
+B204 ; LV # Lo HANGUL SYLLABLE NU
+B220 ; LV # Lo HANGUL SYLLABLE NWEO
+B23C ; LV # Lo HANGUL SYLLABLE NWE
+B258 ; LV # Lo HANGUL SYLLABLE NWI
+B274 ; LV # Lo HANGUL SYLLABLE NYU
+B290 ; LV # Lo HANGUL SYLLABLE NEU
+B2AC ; LV # Lo HANGUL SYLLABLE NYI
+B2C8 ; LV # Lo HANGUL SYLLABLE NI
+B2E4 ; LV # Lo HANGUL SYLLABLE DA
+B300 ; LV # Lo HANGUL SYLLABLE DAE
+B31C ; LV # Lo HANGUL SYLLABLE DYA
+B338 ; LV # Lo HANGUL SYLLABLE DYAE
+B354 ; LV # Lo HANGUL SYLLABLE DEO
+B370 ; LV # Lo HANGUL SYLLABLE DE
+B38C ; LV # Lo HANGUL SYLLABLE DYEO
+B3A8 ; LV # Lo HANGUL SYLLABLE DYE
+B3C4 ; LV # Lo HANGUL SYLLABLE DO
+B3E0 ; LV # Lo HANGUL SYLLABLE DWA
+B3FC ; LV # Lo HANGUL SYLLABLE DWAE
+B418 ; LV # Lo HANGUL SYLLABLE DOE
+B434 ; LV # Lo HANGUL SYLLABLE DYO
+B450 ; LV # Lo HANGUL SYLLABLE DU
+B46C ; LV # Lo HANGUL SYLLABLE DWEO
+B488 ; LV # Lo HANGUL SYLLABLE DWE
+B4A4 ; LV # Lo HANGUL SYLLABLE DWI
+B4C0 ; LV # Lo HANGUL SYLLABLE DYU
+B4DC ; LV # Lo HANGUL SYLLABLE DEU
+B4F8 ; LV # Lo HANGUL SYLLABLE DYI
+B514 ; LV # Lo HANGUL SYLLABLE DI
+B530 ; LV # Lo HANGUL SYLLABLE DDA
+B54C ; LV # Lo HANGUL SYLLABLE DDAE
+B568 ; LV # Lo HANGUL SYLLABLE DDYA
+B584 ; LV # Lo HANGUL SYLLABLE DDYAE
+B5A0 ; LV # Lo HANGUL SYLLABLE DDEO
+B5BC ; LV # Lo HANGUL SYLLABLE DDE
+B5D8 ; LV # Lo HANGUL SYLLABLE DDYEO
+B5F4 ; LV # Lo HANGUL SYLLABLE DDYE
+B610 ; LV # Lo HANGUL SYLLABLE DDO
+B62C ; LV # Lo HANGUL SYLLABLE DDWA
+B648 ; LV # Lo HANGUL SYLLABLE DDWAE
+B664 ; LV # Lo HANGUL SYLLABLE DDOE
+B680 ; LV # Lo HANGUL SYLLABLE DDYO
+B69C ; LV # Lo HANGUL SYLLABLE DDU
+B6B8 ; LV # Lo HANGUL SYLLABLE DDWEO
+B6D4 ; LV # Lo HANGUL SYLLABLE DDWE
+B6F0 ; LV # Lo HANGUL SYLLABLE DDWI
+B70C ; LV # Lo HANGUL SYLLABLE DDYU
+B728 ; LV # Lo HANGUL SYLLABLE DDEU
+B744 ; LV # Lo HANGUL SYLLABLE DDYI
+B760 ; LV # Lo HANGUL SYLLABLE DDI
+B77C ; LV # Lo HANGUL SYLLABLE RA
+B798 ; LV # Lo HANGUL SYLLABLE RAE
+B7B4 ; LV # Lo HANGUL SYLLABLE RYA
+B7D0 ; LV # Lo HANGUL SYLLABLE RYAE
+B7EC ; LV # Lo HANGUL SYLLABLE REO
+B808 ; LV # Lo HANGUL SYLLABLE RE
+B824 ; LV # Lo HANGUL SYLLABLE RYEO
+B840 ; LV # Lo HANGUL SYLLABLE RYE
+B85C ; LV # Lo HANGUL SYLLABLE RO
+B878 ; LV # Lo HANGUL SYLLABLE RWA
+B894 ; LV # Lo HANGUL SYLLABLE RWAE
+B8B0 ; LV # Lo HANGUL SYLLABLE ROE
+B8CC ; LV # Lo HANGUL SYLLABLE RYO
+B8E8 ; LV # Lo HANGUL SYLLABLE RU
+B904 ; LV # Lo HANGUL SYLLABLE RWEO
+B920 ; LV # Lo HANGUL SYLLABLE RWE
+B93C ; LV # Lo HANGUL SYLLABLE RWI
+B958 ; LV # Lo HANGUL SYLLABLE RYU
+B974 ; LV # Lo HANGUL SYLLABLE REU
+B990 ; LV # Lo HANGUL SYLLABLE RYI
+B9AC ; LV # Lo HANGUL SYLLABLE RI
+B9C8 ; LV # Lo HANGUL SYLLABLE MA
+B9E4 ; LV # Lo HANGUL SYLLABLE MAE
+BA00 ; LV # Lo HANGUL SYLLABLE MYA
+BA1C ; LV # Lo HANGUL SYLLABLE MYAE
+BA38 ; LV # Lo HANGUL SYLLABLE MEO
+BA54 ; LV # Lo HANGUL SYLLABLE ME
+BA70 ; LV # Lo HANGUL SYLLABLE MYEO
+BA8C ; LV # Lo HANGUL SYLLABLE MYE
+BAA8 ; LV # Lo HANGUL SYLLABLE MO
+BAC4 ; LV # Lo HANGUL SYLLABLE MWA
+BAE0 ; LV # Lo HANGUL SYLLABLE MWAE
+BAFC ; LV # Lo HANGUL SYLLABLE MOE
+BB18 ; LV # Lo HANGUL SYLLABLE MYO
+BB34 ; LV # Lo HANGUL SYLLABLE MU
+BB50 ; LV # Lo HANGUL SYLLABLE MWEO
+BB6C ; LV # Lo HANGUL SYLLABLE MWE
+BB88 ; LV # Lo HANGUL SYLLABLE MWI
+BBA4 ; LV # Lo HANGUL SYLLABLE MYU
+BBC0 ; LV # Lo HANGUL SYLLABLE MEU
+BBDC ; LV # Lo HANGUL SYLLABLE MYI
+BBF8 ; LV # Lo HANGUL SYLLABLE MI
+BC14 ; LV # Lo HANGUL SYLLABLE BA
+BC30 ; LV # Lo HANGUL SYLLABLE BAE
+BC4C ; LV # Lo HANGUL SYLLABLE BYA
+BC68 ; LV # Lo HANGUL SYLLABLE BYAE
+BC84 ; LV # Lo HANGUL SYLLABLE BEO
+BCA0 ; LV # Lo HANGUL SYLLABLE BE
+BCBC ; LV # Lo HANGUL SYLLABLE BYEO
+BCD8 ; LV # Lo HANGUL SYLLABLE BYE
+BCF4 ; LV # Lo HANGUL SYLLABLE BO
+BD10 ; LV # Lo HANGUL SYLLABLE BWA
+BD2C ; LV # Lo HANGUL SYLLABLE BWAE
+BD48 ; LV # Lo HANGUL SYLLABLE BOE
+BD64 ; LV # Lo HANGUL SYLLABLE BYO
+BD80 ; LV # Lo HANGUL SYLLABLE BU
+BD9C ; LV # Lo HANGUL SYLLABLE BWEO
+BDB8 ; LV # Lo HANGUL SYLLABLE BWE
+BDD4 ; LV # Lo HANGUL SYLLABLE BWI
+BDF0 ; LV # Lo HANGUL SYLLABLE BYU
+BE0C ; LV # Lo HANGUL SYLLABLE BEU
+BE28 ; LV # Lo HANGUL SYLLABLE BYI
+BE44 ; LV # Lo HANGUL SYLLABLE BI
+BE60 ; LV # Lo HANGUL SYLLABLE BBA
+BE7C ; LV # Lo HANGUL SYLLABLE BBAE
+BE98 ; LV # Lo HANGUL SYLLABLE BBYA
+BEB4 ; LV # Lo HANGUL SYLLABLE BBYAE
+BED0 ; LV # Lo HANGUL SYLLABLE BBEO
+BEEC ; LV # Lo HANGUL SYLLABLE BBE
+BF08 ; LV # Lo HANGUL SYLLABLE BBYEO
+BF24 ; LV # Lo HANGUL SYLLABLE BBYE
+BF40 ; LV # Lo HANGUL SYLLABLE BBO
+BF5C ; LV # Lo HANGUL SYLLABLE BBWA
+BF78 ; LV # Lo HANGUL SYLLABLE BBWAE
+BF94 ; LV # Lo HANGUL SYLLABLE BBOE
+BFB0 ; LV # Lo HANGUL SYLLABLE BBYO
+BFCC ; LV # Lo HANGUL SYLLABLE BBU
+BFE8 ; LV # Lo HANGUL SYLLABLE BBWEO
+C004 ; LV # Lo HANGUL SYLLABLE BBWE
+C020 ; LV # Lo HANGUL SYLLABLE BBWI
+C03C ; LV # Lo HANGUL SYLLABLE BBYU
+C058 ; LV # Lo HANGUL SYLLABLE BBEU
+C074 ; LV # Lo HANGUL SYLLABLE BBYI
+C090 ; LV # Lo HANGUL SYLLABLE BBI
+C0AC ; LV # Lo HANGUL SYLLABLE SA
+C0C8 ; LV # Lo HANGUL SYLLABLE SAE
+C0E4 ; LV # Lo HANGUL SYLLABLE SYA
+C100 ; LV # Lo HANGUL SYLLABLE SYAE
+C11C ; LV # Lo HANGUL SYLLABLE SEO
+C138 ; LV # Lo HANGUL SYLLABLE SE
+C154 ; LV # Lo HANGUL SYLLABLE SYEO
+C170 ; LV # Lo HANGUL SYLLABLE SYE
+C18C ; LV # Lo HANGUL SYLLABLE SO
+C1A8 ; LV # Lo HANGUL SYLLABLE SWA
+C1C4 ; LV # Lo HANGUL SYLLABLE SWAE
+C1E0 ; LV # Lo HANGUL SYLLABLE SOE
+C1FC ; LV # Lo HANGUL SYLLABLE SYO
+C218 ; LV # Lo HANGUL SYLLABLE SU
+C234 ; LV # Lo HANGUL SYLLABLE SWEO
+C250 ; LV # Lo HANGUL SYLLABLE SWE
+C26C ; LV # Lo HANGUL SYLLABLE SWI
+C288 ; LV # Lo HANGUL SYLLABLE SYU
+C2A4 ; LV # Lo HANGUL SYLLABLE SEU
+C2C0 ; LV # Lo HANGUL SYLLABLE SYI
+C2DC ; LV # Lo HANGUL SYLLABLE SI
+C2F8 ; LV # Lo HANGUL SYLLABLE SSA
+C314 ; LV # Lo HANGUL SYLLABLE SSAE
+C330 ; LV # Lo HANGUL SYLLABLE SSYA
+C34C ; LV # Lo HANGUL SYLLABLE SSYAE
+C368 ; LV # Lo HANGUL SYLLABLE SSEO
+C384 ; LV # Lo HANGUL SYLLABLE SSE
+C3A0 ; LV # Lo HANGUL SYLLABLE SSYEO
+C3BC ; LV # Lo HANGUL SYLLABLE SSYE
+C3D8 ; LV # Lo HANGUL SYLLABLE SSO
+C3F4 ; LV # Lo HANGUL SYLLABLE SSWA
+C410 ; LV # Lo HANGUL SYLLABLE SSWAE
+C42C ; LV # Lo HANGUL SYLLABLE SSOE
+C448 ; LV # Lo HANGUL SYLLABLE SSYO
+C464 ; LV # Lo HANGUL SYLLABLE SSU
+C480 ; LV # Lo HANGUL SYLLABLE SSWEO
+C49C ; LV # Lo HANGUL SYLLABLE SSWE
+C4B8 ; LV # Lo HANGUL SYLLABLE SSWI
+C4D4 ; LV # Lo HANGUL SYLLABLE SSYU
+C4F0 ; LV # Lo HANGUL SYLLABLE SSEU
+C50C ; LV # Lo HANGUL SYLLABLE SSYI
+C528 ; LV # Lo HANGUL SYLLABLE SSI
+C544 ; LV # Lo HANGUL SYLLABLE A
+C560 ; LV # Lo HANGUL SYLLABLE AE
+C57C ; LV # Lo HANGUL SYLLABLE YA
+C598 ; LV # Lo HANGUL SYLLABLE YAE
+C5B4 ; LV # Lo HANGUL SYLLABLE EO
+C5D0 ; LV # Lo HANGUL SYLLABLE E
+C5EC ; LV # Lo HANGUL SYLLABLE YEO
+C608 ; LV # Lo HANGUL SYLLABLE YE
+C624 ; LV # Lo HANGUL SYLLABLE O
+C640 ; LV # Lo HANGUL SYLLABLE WA
+C65C ; LV # Lo HANGUL SYLLABLE WAE
+C678 ; LV # Lo HANGUL SYLLABLE OE
+C694 ; LV # Lo HANGUL SYLLABLE YO
+C6B0 ; LV # Lo HANGUL SYLLABLE U
+C6CC ; LV # Lo HANGUL SYLLABLE WEO
+C6E8 ; LV # Lo HANGUL SYLLABLE WE
+C704 ; LV # Lo HANGUL SYLLABLE WI
+C720 ; LV # Lo HANGUL SYLLABLE YU
+C73C ; LV # Lo HANGUL SYLLABLE EU
+C758 ; LV # Lo HANGUL SYLLABLE YI
+C774 ; LV # Lo HANGUL SYLLABLE I
+C790 ; LV # Lo HANGUL SYLLABLE JA
+C7AC ; LV # Lo HANGUL SYLLABLE JAE
+C7C8 ; LV # Lo HANGUL SYLLABLE JYA
+C7E4 ; LV # Lo HANGUL SYLLABLE JYAE
+C800 ; LV # Lo HANGUL SYLLABLE JEO
+C81C ; LV # Lo HANGUL SYLLABLE JE
+C838 ; LV # Lo HANGUL SYLLABLE JYEO
+C854 ; LV # Lo HANGUL SYLLABLE JYE
+C870 ; LV # Lo HANGUL SYLLABLE JO
+C88C ; LV # Lo HANGUL SYLLABLE JWA
+C8A8 ; LV # Lo HANGUL SYLLABLE JWAE
+C8C4 ; LV # Lo HANGUL SYLLABLE JOE
+C8E0 ; LV # Lo HANGUL SYLLABLE JYO
+C8FC ; LV # Lo HANGUL SYLLABLE JU
+C918 ; LV # Lo HANGUL SYLLABLE JWEO
+C934 ; LV # Lo HANGUL SYLLABLE JWE
+C950 ; LV # Lo HANGUL SYLLABLE JWI
+C96C ; LV # Lo HANGUL SYLLABLE JYU
+C988 ; LV # Lo HANGUL SYLLABLE JEU
+C9A4 ; LV # Lo HANGUL SYLLABLE JYI
+C9C0 ; LV # Lo HANGUL SYLLABLE JI
+C9DC ; LV # Lo HANGUL SYLLABLE JJA
+C9F8 ; LV # Lo HANGUL SYLLABLE JJAE
+CA14 ; LV # Lo HANGUL SYLLABLE JJYA
+CA30 ; LV # Lo HANGUL SYLLABLE JJYAE
+CA4C ; LV # Lo HANGUL SYLLABLE JJEO
+CA68 ; LV # Lo HANGUL SYLLABLE JJE
+CA84 ; LV # Lo HANGUL SYLLABLE JJYEO
+CAA0 ; LV # Lo HANGUL SYLLABLE JJYE
+CABC ; LV # Lo HANGUL SYLLABLE JJO
+CAD8 ; LV # Lo HANGUL SYLLABLE JJWA
+CAF4 ; LV # Lo HANGUL SYLLABLE JJWAE
+CB10 ; LV # Lo HANGUL SYLLABLE JJOE
+CB2C ; LV # Lo HANGUL SYLLABLE JJYO
+CB48 ; LV # Lo HANGUL SYLLABLE JJU
+CB64 ; LV # Lo HANGUL SYLLABLE JJWEO
+CB80 ; LV # Lo HANGUL SYLLABLE JJWE
+CB9C ; LV # Lo HANGUL SYLLABLE JJWI
+CBB8 ; LV # Lo HANGUL SYLLABLE JJYU
+CBD4 ; LV # Lo HANGUL SYLLABLE JJEU
+CBF0 ; LV # Lo HANGUL SYLLABLE JJYI
+CC0C ; LV # Lo HANGUL SYLLABLE JJI
+CC28 ; LV # Lo HANGUL SYLLABLE CA
+CC44 ; LV # Lo HANGUL SYLLABLE CAE
+CC60 ; LV # Lo HANGUL SYLLABLE CYA
+CC7C ; LV # Lo HANGUL SYLLABLE CYAE
+CC98 ; LV # Lo HANGUL SYLLABLE CEO
+CCB4 ; LV # Lo HANGUL SYLLABLE CE
+CCD0 ; LV # Lo HANGUL SYLLABLE CYEO
+CCEC ; LV # Lo HANGUL SYLLABLE CYE
+CD08 ; LV # Lo HANGUL SYLLABLE CO
+CD24 ; LV # Lo HANGUL SYLLABLE CWA
+CD40 ; LV # Lo HANGUL SYLLABLE CWAE
+CD5C ; LV # Lo HANGUL SYLLABLE COE
+CD78 ; LV # Lo HANGUL SYLLABLE CYO
+CD94 ; LV # Lo HANGUL SYLLABLE CU
+CDB0 ; LV # Lo HANGUL SYLLABLE CWEO
+CDCC ; LV # Lo HANGUL SYLLABLE CWE
+CDE8 ; LV # Lo HANGUL SYLLABLE CWI
+CE04 ; LV # Lo HANGUL SYLLABLE CYU
+CE20 ; LV # Lo HANGUL SYLLABLE CEU
+CE3C ; LV # Lo HANGUL SYLLABLE CYI
+CE58 ; LV # Lo HANGUL SYLLABLE CI
+CE74 ; LV # Lo HANGUL SYLLABLE KA
+CE90 ; LV # Lo HANGUL SYLLABLE KAE
+CEAC ; LV # Lo HANGUL SYLLABLE KYA
+CEC8 ; LV # Lo HANGUL SYLLABLE KYAE
+CEE4 ; LV # Lo HANGUL SYLLABLE KEO
+CF00 ; LV # Lo HANGUL SYLLABLE KE
+CF1C ; LV # Lo HANGUL SYLLABLE KYEO
+CF38 ; LV # Lo HANGUL SYLLABLE KYE
+CF54 ; LV # Lo HANGUL SYLLABLE KO
+CF70 ; LV # Lo HANGUL SYLLABLE KWA
+CF8C ; LV # Lo HANGUL SYLLABLE KWAE
+CFA8 ; LV # Lo HANGUL SYLLABLE KOE
+CFC4 ; LV # Lo HANGUL SYLLABLE KYO
+CFE0 ; LV # Lo HANGUL SYLLABLE KU
+CFFC ; LV # Lo HANGUL SYLLABLE KWEO
+D018 ; LV # Lo HANGUL SYLLABLE KWE
+D034 ; LV # Lo HANGUL SYLLABLE KWI
+D050 ; LV # Lo HANGUL SYLLABLE KYU
+D06C ; LV # Lo HANGUL SYLLABLE KEU
+D088 ; LV # Lo HANGUL SYLLABLE KYI
+D0A4 ; LV # Lo HANGUL SYLLABLE KI
+D0C0 ; LV # Lo HANGUL SYLLABLE TA
+D0DC ; LV # Lo HANGUL SYLLABLE TAE
+D0F8 ; LV # Lo HANGUL SYLLABLE TYA
+D114 ; LV # Lo HANGUL SYLLABLE TYAE
+D130 ; LV # Lo HANGUL SYLLABLE TEO
+D14C ; LV # Lo HANGUL SYLLABLE TE
+D168 ; LV # Lo HANGUL SYLLABLE TYEO
+D184 ; LV # Lo HANGUL SYLLABLE TYE
+D1A0 ; LV # Lo HANGUL SYLLABLE TO
+D1BC ; LV # Lo HANGUL SYLLABLE TWA
+D1D8 ; LV # Lo HANGUL SYLLABLE TWAE
+D1F4 ; LV # Lo HANGUL SYLLABLE TOE
+D210 ; LV # Lo HANGUL SYLLABLE TYO
+D22C ; LV # Lo HANGUL SYLLABLE TU
+D248 ; LV # Lo HANGUL SYLLABLE TWEO
+D264 ; LV # Lo HANGUL SYLLABLE TWE
+D280 ; LV # Lo HANGUL SYLLABLE TWI
+D29C ; LV # Lo HANGUL SYLLABLE TYU
+D2B8 ; LV # Lo HANGUL SYLLABLE TEU
+D2D4 ; LV # Lo HANGUL SYLLABLE TYI
+D2F0 ; LV # Lo HANGUL SYLLABLE TI
+D30C ; LV # Lo HANGUL SYLLABLE PA
+D328 ; LV # Lo HANGUL SYLLABLE PAE
+D344 ; LV # Lo HANGUL SYLLABLE PYA
+D360 ; LV # Lo HANGUL SYLLABLE PYAE
+D37C ; LV # Lo HANGUL SYLLABLE PEO
+D398 ; LV # Lo HANGUL SYLLABLE PE
+D3B4 ; LV # Lo HANGUL SYLLABLE PYEO
+D3D0 ; LV # Lo HANGUL SYLLABLE PYE
+D3EC ; LV # Lo HANGUL SYLLABLE PO
+D408 ; LV # Lo HANGUL SYLLABLE PWA
+D424 ; LV # Lo HANGUL SYLLABLE PWAE
+D440 ; LV # Lo HANGUL SYLLABLE POE
+D45C ; LV # Lo HANGUL SYLLABLE PYO
+D478 ; LV # Lo HANGUL SYLLABLE PU
+D494 ; LV # Lo HANGUL SYLLABLE PWEO
+D4B0 ; LV # Lo HANGUL SYLLABLE PWE
+D4CC ; LV # Lo HANGUL SYLLABLE PWI
+D4E8 ; LV # Lo HANGUL SYLLABLE PYU
+D504 ; LV # Lo HANGUL SYLLABLE PEU
+D520 ; LV # Lo HANGUL SYLLABLE PYI
+D53C ; LV # Lo HANGUL SYLLABLE PI
+D558 ; LV # Lo HANGUL SYLLABLE HA
+D574 ; LV # Lo HANGUL SYLLABLE HAE
+D590 ; LV # Lo HANGUL SYLLABLE HYA
+D5AC ; LV # Lo HANGUL SYLLABLE HYAE
+D5C8 ; LV # Lo HANGUL SYLLABLE HEO
+D5E4 ; LV # Lo HANGUL SYLLABLE HE
+D600 ; LV # Lo HANGUL SYLLABLE HYEO
+D61C ; LV # Lo HANGUL SYLLABLE HYE
+D638 ; LV # Lo HANGUL SYLLABLE HO
+D654 ; LV # Lo HANGUL SYLLABLE HWA
+D670 ; LV # Lo HANGUL SYLLABLE HWAE
+D68C ; LV # Lo HANGUL SYLLABLE HOE
+D6A8 ; LV # Lo HANGUL SYLLABLE HYO
+D6C4 ; LV # Lo HANGUL SYLLABLE HU
+D6E0 ; LV # Lo HANGUL SYLLABLE HWEO
+D6FC ; LV # Lo HANGUL SYLLABLE HWE
+D718 ; LV # Lo HANGUL SYLLABLE HWI
+D734 ; LV # Lo HANGUL SYLLABLE HYU
+D750 ; LV # Lo HANGUL SYLLABLE HEU
+D76C ; LV # Lo HANGUL SYLLABLE HYI
+D788 ; LV # Lo HANGUL SYLLABLE HI
+
+# Total code points: 399
+
+# ================================================
+
+AC01..AC1B ; LVT # Lo [27] HANGUL SYLLABLE GAG..HANGUL SYLLABLE GAH
+AC1D..AC37 ; LVT # Lo [27] HANGUL SYLLABLE GAEG..HANGUL SYLLABLE GAEH
+AC39..AC53 ; LVT # Lo [27] HANGUL SYLLABLE GYAG..HANGUL SYLLABLE GYAH
+AC55..AC6F ; LVT # Lo [27] HANGUL SYLLABLE GYAEG..HANGUL SYLLABLE GYAEH
+AC71..AC8B ; LVT # Lo [27] HANGUL SYLLABLE GEOG..HANGUL SYLLABLE GEOH
+AC8D..ACA7 ; LVT # Lo [27] HANGUL SYLLABLE GEG..HANGUL SYLLABLE GEH
+ACA9..ACC3 ; LVT # Lo [27] HANGUL SYLLABLE GYEOG..HANGUL SYLLABLE GYEOH
+ACC5..ACDF ; LVT # Lo [27] HANGUL SYLLABLE GYEG..HANGUL SYLLABLE GYEH
+ACE1..ACFB ; LVT # Lo [27] HANGUL SYLLABLE GOG..HANGUL SYLLABLE GOH
+ACFD..AD17 ; LVT # Lo [27] HANGUL SYLLABLE GWAG..HANGUL SYLLABLE GWAH
+AD19..AD33 ; LVT # Lo [27] HANGUL SYLLABLE GWAEG..HANGUL SYLLABLE GWAEH
+AD35..AD4F ; LVT # Lo [27] HANGUL SYLLABLE GOEG..HANGUL SYLLABLE GOEH
+AD51..AD6B ; LVT # Lo [27] HANGUL SYLLABLE GYOG..HANGUL SYLLABLE GYOH
+AD6D..AD87 ; LVT # Lo [27] HANGUL SYLLABLE GUG..HANGUL SYLLABLE GUH
+AD89..ADA3 ; LVT # Lo [27] HANGUL SYLLABLE GWEOG..HANGUL SYLLABLE GWEOH
+ADA5..ADBF ; LVT # Lo [27] HANGUL SYLLABLE GWEG..HANGUL SYLLABLE GWEH
+ADC1..ADDB ; LVT # Lo [27] HANGUL SYLLABLE GWIG..HANGUL SYLLABLE GWIH
+ADDD..ADF7 ; LVT # Lo [27] HANGUL SYLLABLE GYUG..HANGUL SYLLABLE GYUH
+ADF9..AE13 ; LVT # Lo [27] HANGUL SYLLABLE GEUG..HANGUL SYLLABLE GEUH
+AE15..AE2F ; LVT # Lo [27] HANGUL SYLLABLE GYIG..HANGUL SYLLABLE GYIH
+AE31..AE4B ; LVT # Lo [27] HANGUL SYLLABLE GIG..HANGUL SYLLABLE GIH
+AE4D..AE67 ; LVT # Lo [27] HANGUL SYLLABLE GGAG..HANGUL SYLLABLE GGAH
+AE69..AE83 ; LVT # Lo [27] HANGUL SYLLABLE GGAEG..HANGUL SYLLABLE GGAEH
+AE85..AE9F ; LVT # Lo [27] HANGUL SYLLABLE GGYAG..HANGUL SYLLABLE GGYAH
+AEA1..AEBB ; LVT # Lo [27] HANGUL SYLLABLE GGYAEG..HANGUL SYLLABLE GGYAEH
+AEBD..AED7 ; LVT # Lo [27] HANGUL SYLLABLE GGEOG..HANGUL SYLLABLE GGEOH
+AED9..AEF3 ; LVT # Lo [27] HANGUL SYLLABLE GGEG..HANGUL SYLLABLE GGEH
+AEF5..AF0F ; LVT # Lo [27] HANGUL SYLLABLE GGYEOG..HANGUL SYLLABLE GGYEOH
+AF11..AF2B ; LVT # Lo [27] HANGUL SYLLABLE GGYEG..HANGUL SYLLABLE GGYEH
+AF2D..AF47 ; LVT # Lo [27] HANGUL SYLLABLE GGOG..HANGUL SYLLABLE GGOH
+AF49..AF63 ; LVT # Lo [27] HANGUL SYLLABLE GGWAG..HANGUL SYLLABLE GGWAH
+AF65..AF7F ; LVT # Lo [27] HANGUL SYLLABLE GGWAEG..HANGUL SYLLABLE GGWAEH
+AF81..AF9B ; LVT # Lo [27] HANGUL SYLLABLE GGOEG..HANGUL SYLLABLE GGOEH
+AF9D..AFB7 ; LVT # Lo [27] HANGUL SYLLABLE GGYOG..HANGUL SYLLABLE GGYOH
+AFB9..AFD3 ; LVT # Lo [27] HANGUL SYLLABLE GGUG..HANGUL SYLLABLE GGUH
+AFD5..AFEF ; LVT # Lo [27] HANGUL SYLLABLE GGWEOG..HANGUL SYLLABLE GGWEOH
+AFF1..B00B ; LVT # Lo [27] HANGUL SYLLABLE GGWEG..HANGUL SYLLABLE GGWEH
+B00D..B027 ; LVT # Lo [27] HANGUL SYLLABLE GGWIG..HANGUL SYLLABLE GGWIH
+B029..B043 ; LVT # Lo [27] HANGUL SYLLABLE GGYUG..HANGUL SYLLABLE GGYUH
+B045..B05F ; LVT # Lo [27] HANGUL SYLLABLE GGEUG..HANGUL SYLLABLE GGEUH
+B061..B07B ; LVT # Lo [27] HANGUL SYLLABLE GGYIG..HANGUL SYLLABLE GGYIH
+B07D..B097 ; LVT # Lo [27] HANGUL SYLLABLE GGIG..HANGUL SYLLABLE GGIH
+B099..B0B3 ; LVT # Lo [27] HANGUL SYLLABLE NAG..HANGUL SYLLABLE NAH
+B0B5..B0CF ; LVT # Lo [27] HANGUL SYLLABLE NAEG..HANGUL SYLLABLE NAEH
+B0D1..B0EB ; LVT # Lo [27] HANGUL SYLLABLE NYAG..HANGUL SYLLABLE NYAH
+B0ED..B107 ; LVT # Lo [27] HANGUL SYLLABLE NYAEG..HANGUL SYLLABLE NYAEH
+B109..B123 ; LVT # Lo [27] HANGUL SYLLABLE NEOG..HANGUL SYLLABLE NEOH
+B125..B13F ; LVT # Lo [27] HANGUL SYLLABLE NEG..HANGUL SYLLABLE NEH
+B141..B15B ; LVT # Lo [27] HANGUL SYLLABLE NYEOG..HANGUL SYLLABLE NYEOH
+B15D..B177 ; LVT # Lo [27] HANGUL SYLLABLE NYEG..HANGUL SYLLABLE NYEH
+B179..B193 ; LVT # Lo [27] HANGUL SYLLABLE NOG..HANGUL SYLLABLE NOH
+B195..B1AF ; LVT # Lo [27] HANGUL SYLLABLE NWAG..HANGUL SYLLABLE NWAH
+B1B1..B1CB ; LVT # Lo [27] HANGUL SYLLABLE NWAEG..HANGUL SYLLABLE NWAEH
+B1CD..B1E7 ; LVT # Lo [27] HANGUL SYLLABLE NOEG..HANGUL SYLLABLE NOEH
+B1E9..B203 ; LVT # Lo [27] HANGUL SYLLABLE NYOG..HANGUL SYLLABLE NYOH
+B205..B21F ; LVT # Lo [27] HANGUL SYLLABLE NUG..HANGUL SYLLABLE NUH
+B221..B23B ; LVT # Lo [27] HANGUL SYLLABLE NWEOG..HANGUL SYLLABLE NWEOH
+B23D..B257 ; LVT # Lo [27] HANGUL SYLLABLE NWEG..HANGUL SYLLABLE NWEH
+B259..B273 ; LVT # Lo [27] HANGUL SYLLABLE NWIG..HANGUL SYLLABLE NWIH
+B275..B28F ; LVT # Lo [27] HANGUL SYLLABLE NYUG..HANGUL SYLLABLE NYUH
+B291..B2AB ; LVT # Lo [27] HANGUL SYLLABLE NEUG..HANGUL SYLLABLE NEUH
+B2AD..B2C7 ; LVT # Lo [27] HANGUL SYLLABLE NYIG..HANGUL SYLLABLE NYIH
+B2C9..B2E3 ; LVT # Lo [27] HANGUL SYLLABLE NIG..HANGUL SYLLABLE NIH
+B2E5..B2FF ; LVT # Lo [27] HANGUL SYLLABLE DAG..HANGUL SYLLABLE DAH
+B301..B31B ; LVT # Lo [27] HANGUL SYLLABLE DAEG..HANGUL SYLLABLE DAEH
+B31D..B337 ; LVT # Lo [27] HANGUL SYLLABLE DYAG..HANGUL SYLLABLE DYAH
+B339..B353 ; LVT # Lo [27] HANGUL SYLLABLE DYAEG..HANGUL SYLLABLE DYAEH
+B355..B36F ; LVT # Lo [27] HANGUL SYLLABLE DEOG..HANGUL SYLLABLE DEOH
+B371..B38B ; LVT # Lo [27] HANGUL SYLLABLE DEG..HANGUL SYLLABLE DEH
+B38D..B3A7 ; LVT # Lo [27] HANGUL SYLLABLE DYEOG..HANGUL SYLLABLE DYEOH
+B3A9..B3C3 ; LVT # Lo [27] HANGUL SYLLABLE DYEG..HANGUL SYLLABLE DYEH
+B3C5..B3DF ; LVT # Lo [27] HANGUL SYLLABLE DOG..HANGUL SYLLABLE DOH
+B3E1..B3FB ; LVT # Lo [27] HANGUL SYLLABLE DWAG..HANGUL SYLLABLE DWAH
+B3FD..B417 ; LVT # Lo [27] HANGUL SYLLABLE DWAEG..HANGUL SYLLABLE DWAEH
+B419..B433 ; LVT # Lo [27] HANGUL SYLLABLE DOEG..HANGUL SYLLABLE DOEH
+B435..B44F ; LVT # Lo [27] HANGUL SYLLABLE DYOG..HANGUL SYLLABLE DYOH
+B451..B46B ; LVT # Lo [27] HANGUL SYLLABLE DUG..HANGUL SYLLABLE DUH
+B46D..B487 ; LVT # Lo [27] HANGUL SYLLABLE DWEOG..HANGUL SYLLABLE DWEOH
+B489..B4A3 ; LVT # Lo [27] HANGUL SYLLABLE DWEG..HANGUL SYLLABLE DWEH
+B4A5..B4BF ; LVT # Lo [27] HANGUL SYLLABLE DWIG..HANGUL SYLLABLE DWIH
+B4C1..B4DB ; LVT # Lo [27] HANGUL SYLLABLE DYUG..HANGUL SYLLABLE DYUH
+B4DD..B4F7 ; LVT # Lo [27] HANGUL SYLLABLE DEUG..HANGUL SYLLABLE DEUH
+B4F9..B513 ; LVT # Lo [27] HANGUL SYLLABLE DYIG..HANGUL SYLLABLE DYIH
+B515..B52F ; LVT # Lo [27] HANGUL SYLLABLE DIG..HANGUL SYLLABLE DIH
+B531..B54B ; LVT # Lo [27] HANGUL SYLLABLE DDAG..HANGUL SYLLABLE DDAH
+B54D..B567 ; LVT # Lo [27] HANGUL SYLLABLE DDAEG..HANGUL SYLLABLE DDAEH
+B569..B583 ; LVT # Lo [27] HANGUL SYLLABLE DDYAG..HANGUL SYLLABLE DDYAH
+B585..B59F ; LVT # Lo [27] HANGUL SYLLABLE DDYAEG..HANGUL SYLLABLE DDYAEH
+B5A1..B5BB ; LVT # Lo [27] HANGUL SYLLABLE DDEOG..HANGUL SYLLABLE DDEOH
+B5BD..B5D7 ; LVT # Lo [27] HANGUL SYLLABLE DDEG..HANGUL SYLLABLE DDEH
+B5D9..B5F3 ; LVT # Lo [27] HANGUL SYLLABLE DDYEOG..HANGUL SYLLABLE DDYEOH
+B5F5..B60F ; LVT # Lo [27] HANGUL SYLLABLE DDYEG..HANGUL SYLLABLE DDYEH
+B611..B62B ; LVT # Lo [27] HANGUL SYLLABLE DDOG..HANGUL SYLLABLE DDOH
+B62D..B647 ; LVT # Lo [27] HANGUL SYLLABLE DDWAG..HANGUL SYLLABLE DDWAH
+B649..B663 ; LVT # Lo [27] HANGUL SYLLABLE DDWAEG..HANGUL SYLLABLE DDWAEH
+B665..B67F ; LVT # Lo [27] HANGUL SYLLABLE DDOEG..HANGUL SYLLABLE DDOEH
+B681..B69B ; LVT # Lo [27] HANGUL SYLLABLE DDYOG..HANGUL SYLLABLE DDYOH
+B69D..B6B7 ; LVT # Lo [27] HANGUL SYLLABLE DDUG..HANGUL SYLLABLE DDUH
+B6B9..B6D3 ; LVT # Lo [27] HANGUL SYLLABLE DDWEOG..HANGUL SYLLABLE DDWEOH
+B6D5..B6EF ; LVT # Lo [27] HANGUL SYLLABLE DDWEG..HANGUL SYLLABLE DDWEH
+B6F1..B70B ; LVT # Lo [27] HANGUL SYLLABLE DDWIG..HANGUL SYLLABLE DDWIH
+B70D..B727 ; LVT # Lo [27] HANGUL SYLLABLE DDYUG..HANGUL SYLLABLE DDYUH
+B729..B743 ; LVT # Lo [27] HANGUL SYLLABLE DDEUG..HANGUL SYLLABLE DDEUH
+B745..B75F ; LVT # Lo [27] HANGUL SYLLABLE DDYIG..HANGUL SYLLABLE DDYIH
+B761..B77B ; LVT # Lo [27] HANGUL SYLLABLE DDIG..HANGUL SYLLABLE DDIH
+B77D..B797 ; LVT # Lo [27] HANGUL SYLLABLE RAG..HANGUL SYLLABLE RAH
+B799..B7B3 ; LVT # Lo [27] HANGUL SYLLABLE RAEG..HANGUL SYLLABLE RAEH
+B7B5..B7CF ; LVT # Lo [27] HANGUL SYLLABLE RYAG..HANGUL SYLLABLE RYAH
+B7D1..B7EB ; LVT # Lo [27] HANGUL SYLLABLE RYAEG..HANGUL SYLLABLE RYAEH
+B7ED..B807 ; LVT # Lo [27] HANGUL SYLLABLE REOG..HANGUL SYLLABLE REOH
+B809..B823 ; LVT # Lo [27] HANGUL SYLLABLE REG..HANGUL SYLLABLE REH
+B825..B83F ; LVT # Lo [27] HANGUL SYLLABLE RYEOG..HANGUL SYLLABLE RYEOH
+B841..B85B ; LVT # Lo [27] HANGUL SYLLABLE RYEG..HANGUL SYLLABLE RYEH
+B85D..B877 ; LVT # Lo [27] HANGUL SYLLABLE ROG..HANGUL SYLLABLE ROH
+B879..B893 ; LVT # Lo [27] HANGUL SYLLABLE RWAG..HANGUL SYLLABLE RWAH
+B895..B8AF ; LVT # Lo [27] HANGUL SYLLABLE RWAEG..HANGUL SYLLABLE RWAEH
+B8B1..B8CB ; LVT # Lo [27] HANGUL SYLLABLE ROEG..HANGUL SYLLABLE ROEH
+B8CD..B8E7 ; LVT # Lo [27] HANGUL SYLLABLE RYOG..HANGUL SYLLABLE RYOH
+B8E9..B903 ; LVT # Lo [27] HANGUL SYLLABLE RUG..HANGUL SYLLABLE RUH
+B905..B91F ; LVT # Lo [27] HANGUL SYLLABLE RWEOG..HANGUL SYLLABLE RWEOH
+B921..B93B ; LVT # Lo [27] HANGUL SYLLABLE RWEG..HANGUL SYLLABLE RWEH
+B93D..B957 ; LVT # Lo [27] HANGUL SYLLABLE RWIG..HANGUL SYLLABLE RWIH
+B959..B973 ; LVT # Lo [27] HANGUL SYLLABLE RYUG..HANGUL SYLLABLE RYUH
+B975..B98F ; LVT # Lo [27] HANGUL SYLLABLE REUG..HANGUL SYLLABLE REUH
+B991..B9AB ; LVT # Lo [27] HANGUL SYLLABLE RYIG..HANGUL SYLLABLE RYIH
+B9AD..B9C7 ; LVT # Lo [27] HANGUL SYLLABLE RIG..HANGUL SYLLABLE RIH
+B9C9..B9E3 ; LVT # Lo [27] HANGUL SYLLABLE MAG..HANGUL SYLLABLE MAH
+B9E5..B9FF ; LVT # Lo [27] HANGUL SYLLABLE MAEG..HANGUL SYLLABLE MAEH
+BA01..BA1B ; LVT # Lo [27] HANGUL SYLLABLE MYAG..HANGUL SYLLABLE MYAH
+BA1D..BA37 ; LVT # Lo [27] HANGUL SYLLABLE MYAEG..HANGUL SYLLABLE MYAEH
+BA39..BA53 ; LVT # Lo [27] HANGUL SYLLABLE MEOG..HANGUL SYLLABLE MEOH
+BA55..BA6F ; LVT # Lo [27] HANGUL SYLLABLE MEG..HANGUL SYLLABLE MEH
+BA71..BA8B ; LVT # Lo [27] HANGUL SYLLABLE MYEOG..HANGUL SYLLABLE MYEOH
+BA8D..BAA7 ; LVT # Lo [27] HANGUL SYLLABLE MYEG..HANGUL SYLLABLE MYEH
+BAA9..BAC3 ; LVT # Lo [27] HANGUL SYLLABLE MOG..HANGUL SYLLABLE MOH
+BAC5..BADF ; LVT # Lo [27] HANGUL SYLLABLE MWAG..HANGUL SYLLABLE MWAH
+BAE1..BAFB ; LVT # Lo [27] HANGUL SYLLABLE MWAEG..HANGUL SYLLABLE MWAEH
+BAFD..BB17 ; LVT # Lo [27] HANGUL SYLLABLE MOEG..HANGUL SYLLABLE MOEH
+BB19..BB33 ; LVT # Lo [27] HANGUL SYLLABLE MYOG..HANGUL SYLLABLE MYOH
+BB35..BB4F ; LVT # Lo [27] HANGUL SYLLABLE MUG..HANGUL SYLLABLE MUH
+BB51..BB6B ; LVT # Lo [27] HANGUL SYLLABLE MWEOG..HANGUL SYLLABLE MWEOH
+BB6D..BB87 ; LVT # Lo [27] HANGUL SYLLABLE MWEG..HANGUL SYLLABLE MWEH
+BB89..BBA3 ; LVT # Lo [27] HANGUL SYLLABLE MWIG..HANGUL SYLLABLE MWIH
+BBA5..BBBF ; LVT # Lo [27] HANGUL SYLLABLE MYUG..HANGUL SYLLABLE MYUH
+BBC1..BBDB ; LVT # Lo [27] HANGUL SYLLABLE MEUG..HANGUL SYLLABLE MEUH
+BBDD..BBF7 ; LVT # Lo [27] HANGUL SYLLABLE MYIG..HANGUL SYLLABLE MYIH
+BBF9..BC13 ; LVT # Lo [27] HANGUL SYLLABLE MIG..HANGUL SYLLABLE MIH
+BC15..BC2F ; LVT # Lo [27] HANGUL SYLLABLE BAG..HANGUL SYLLABLE BAH
+BC31..BC4B ; LVT # Lo [27] HANGUL SYLLABLE BAEG..HANGUL SYLLABLE BAEH
+BC4D..BC67 ; LVT # Lo [27] HANGUL SYLLABLE BYAG..HANGUL SYLLABLE BYAH
+BC69..BC83 ; LVT # Lo [27] HANGUL SYLLABLE BYAEG..HANGUL SYLLABLE BYAEH
+BC85..BC9F ; LVT # Lo [27] HANGUL SYLLABLE BEOG..HANGUL SYLLABLE BEOH
+BCA1..BCBB ; LVT # Lo [27] HANGUL SYLLABLE BEG..HANGUL SYLLABLE BEH
+BCBD..BCD7 ; LVT # Lo [27] HANGUL SYLLABLE BYEOG..HANGUL SYLLABLE BYEOH
+BCD9..BCF3 ; LVT # Lo [27] HANGUL SYLLABLE BYEG..HANGUL SYLLABLE BYEH
+BCF5..BD0F ; LVT # Lo [27] HANGUL SYLLABLE BOG..HANGUL SYLLABLE BOH
+BD11..BD2B ; LVT # Lo [27] HANGUL SYLLABLE BWAG..HANGUL SYLLABLE BWAH
+BD2D..BD47 ; LVT # Lo [27] HANGUL SYLLABLE BWAEG..HANGUL SYLLABLE BWAEH
+BD49..BD63 ; LVT # Lo [27] HANGUL SYLLABLE BOEG..HANGUL SYLLABLE BOEH
+BD65..BD7F ; LVT # Lo [27] HANGUL SYLLABLE BYOG..HANGUL SYLLABLE BYOH
+BD81..BD9B ; LVT # Lo [27] HANGUL SYLLABLE BUG..HANGUL SYLLABLE BUH
+BD9D..BDB7 ; LVT # Lo [27] HANGUL SYLLABLE BWEOG..HANGUL SYLLABLE BWEOH
+BDB9..BDD3 ; LVT # Lo [27] HANGUL SYLLABLE BWEG..HANGUL SYLLABLE BWEH
+BDD5..BDEF ; LVT # Lo [27] HANGUL SYLLABLE BWIG..HANGUL SYLLABLE BWIH
+BDF1..BE0B ; LVT # Lo [27] HANGUL SYLLABLE BYUG..HANGUL SYLLABLE BYUH
+BE0D..BE27 ; LVT # Lo [27] HANGUL SYLLABLE BEUG..HANGUL SYLLABLE BEUH
+BE29..BE43 ; LVT # Lo [27] HANGUL SYLLABLE BYIG..HANGUL SYLLABLE BYIH
+BE45..BE5F ; LVT # Lo [27] HANGUL SYLLABLE BIG..HANGUL SYLLABLE BIH
+BE61..BE7B ; LVT # Lo [27] HANGUL SYLLABLE BBAG..HANGUL SYLLABLE BBAH
+BE7D..BE97 ; LVT # Lo [27] HANGUL SYLLABLE BBAEG..HANGUL SYLLABLE BBAEH
+BE99..BEB3 ; LVT # Lo [27] HANGUL SYLLABLE BBYAG..HANGUL SYLLABLE BBYAH
+BEB5..BECF ; LVT # Lo [27] HANGUL SYLLABLE BBYAEG..HANGUL SYLLABLE BBYAEH
+BED1..BEEB ; LVT # Lo [27] HANGUL SYLLABLE BBEOG..HANGUL SYLLABLE BBEOH
+BEED..BF07 ; LVT # Lo [27] HANGUL SYLLABLE BBEG..HANGUL SYLLABLE BBEH
+BF09..BF23 ; LVT # Lo [27] HANGUL SYLLABLE BBYEOG..HANGUL SYLLABLE BBYEOH
+BF25..BF3F ; LVT # Lo [27] HANGUL SYLLABLE BBYEG..HANGUL SYLLABLE BBYEH
+BF41..BF5B ; LVT # Lo [27] HANGUL SYLLABLE BBOG..HANGUL SYLLABLE BBOH
+BF5D..BF77 ; LVT # Lo [27] HANGUL SYLLABLE BBWAG..HANGUL SYLLABLE BBWAH
+BF79..BF93 ; LVT # Lo [27] HANGUL SYLLABLE BBWAEG..HANGUL SYLLABLE BBWAEH
+BF95..BFAF ; LVT # Lo [27] HANGUL SYLLABLE BBOEG..HANGUL SYLLABLE BBOEH
+BFB1..BFCB ; LVT # Lo [27] HANGUL SYLLABLE BBYOG..HANGUL SYLLABLE BBYOH
+BFCD..BFE7 ; LVT # Lo [27] HANGUL SYLLABLE BBUG..HANGUL SYLLABLE BBUH
+BFE9..C003 ; LVT # Lo [27] HANGUL SYLLABLE BBWEOG..HANGUL SYLLABLE BBWEOH
+C005..C01F ; LVT # Lo [27] HANGUL SYLLABLE BBWEG..HANGUL SYLLABLE BBWEH
+C021..C03B ; LVT # Lo [27] HANGUL SYLLABLE BBWIG..HANGUL SYLLABLE BBWIH
+C03D..C057 ; LVT # Lo [27] HANGUL SYLLABLE BBYUG..HANGUL SYLLABLE BBYUH
+C059..C073 ; LVT # Lo [27] HANGUL SYLLABLE BBEUG..HANGUL SYLLABLE BBEUH
+C075..C08F ; LVT # Lo [27] HANGUL SYLLABLE BBYIG..HANGUL SYLLABLE BBYIH
+C091..C0AB ; LVT # Lo [27] HANGUL SYLLABLE BBIG..HANGUL SYLLABLE BBIH
+C0AD..C0C7 ; LVT # Lo [27] HANGUL SYLLABLE SAG..HANGUL SYLLABLE SAH
+C0C9..C0E3 ; LVT # Lo [27] HANGUL SYLLABLE SAEG..HANGUL SYLLABLE SAEH
+C0E5..C0FF ; LVT # Lo [27] HANGUL SYLLABLE SYAG..HANGUL SYLLABLE SYAH
+C101..C11B ; LVT # Lo [27] HANGUL SYLLABLE SYAEG..HANGUL SYLLABLE SYAEH
+C11D..C137 ; LVT # Lo [27] HANGUL SYLLABLE SEOG..HANGUL SYLLABLE SEOH
+C139..C153 ; LVT # Lo [27] HANGUL SYLLABLE SEG..HANGUL SYLLABLE SEH
+C155..C16F ; LVT # Lo [27] HANGUL SYLLABLE SYEOG..HANGUL SYLLABLE SYEOH
+C171..C18B ; LVT # Lo [27] HANGUL SYLLABLE SYEG..HANGUL SYLLABLE SYEH
+C18D..C1A7 ; LVT # Lo [27] HANGUL SYLLABLE SOG..HANGUL SYLLABLE SOH
+C1A9..C1C3 ; LVT # Lo [27] HANGUL SYLLABLE SWAG..HANGUL SYLLABLE SWAH
+C1C5..C1DF ; LVT # Lo [27] HANGUL SYLLABLE SWAEG..HANGUL SYLLABLE SWAEH
+C1E1..C1FB ; LVT # Lo [27] HANGUL SYLLABLE SOEG..HANGUL SYLLABLE SOEH
+C1FD..C217 ; LVT # Lo [27] HANGUL SYLLABLE SYOG..HANGUL SYLLABLE SYOH
+C219..C233 ; LVT # Lo [27] HANGUL SYLLABLE SUG..HANGUL SYLLABLE SUH
+C235..C24F ; LVT # Lo [27] HANGUL SYLLABLE SWEOG..HANGUL SYLLABLE SWEOH
+C251..C26B ; LVT # Lo [27] HANGUL SYLLABLE SWEG..HANGUL SYLLABLE SWEH
+C26D..C287 ; LVT # Lo [27] HANGUL SYLLABLE SWIG..HANGUL SYLLABLE SWIH
+C289..C2A3 ; LVT # Lo [27] HANGUL SYLLABLE SYUG..HANGUL SYLLABLE SYUH
+C2A5..C2BF ; LVT # Lo [27] HANGUL SYLLABLE SEUG..HANGUL SYLLABLE SEUH
+C2C1..C2DB ; LVT # Lo [27] HANGUL SYLLABLE SYIG..HANGUL SYLLABLE SYIH
+C2DD..C2F7 ; LVT # Lo [27] HANGUL SYLLABLE SIG..HANGUL SYLLABLE SIH
+C2F9..C313 ; LVT # Lo [27] HANGUL SYLLABLE SSAG..HANGUL SYLLABLE SSAH
+C315..C32F ; LVT # Lo [27] HANGUL SYLLABLE SSAEG..HANGUL SYLLABLE SSAEH
+C331..C34B ; LVT # Lo [27] HANGUL SYLLABLE SSYAG..HANGUL SYLLABLE SSYAH
+C34D..C367 ; LVT # Lo [27] HANGUL SYLLABLE SSYAEG..HANGUL SYLLABLE SSYAEH
+C369..C383 ; LVT # Lo [27] HANGUL SYLLABLE SSEOG..HANGUL SYLLABLE SSEOH
+C385..C39F ; LVT # Lo [27] HANGUL SYLLABLE SSEG..HANGUL SYLLABLE SSEH
+C3A1..C3BB ; LVT # Lo [27] HANGUL SYLLABLE SSYEOG..HANGUL SYLLABLE SSYEOH
+C3BD..C3D7 ; LVT # Lo [27] HANGUL SYLLABLE SSYEG..HANGUL SYLLABLE SSYEH
+C3D9..C3F3 ; LVT # Lo [27] HANGUL SYLLABLE SSOG..HANGUL SYLLABLE SSOH
+C3F5..C40F ; LVT # Lo [27] HANGUL SYLLABLE SSWAG..HANGUL SYLLABLE SSWAH
+C411..C42B ; LVT # Lo [27] HANGUL SYLLABLE SSWAEG..HANGUL SYLLABLE SSWAEH
+C42D..C447 ; LVT # Lo [27] HANGUL SYLLABLE SSOEG..HANGUL SYLLABLE SSOEH
+C449..C463 ; LVT # Lo [27] HANGUL SYLLABLE SSYOG..HANGUL SYLLABLE SSYOH
+C465..C47F ; LVT # Lo [27] HANGUL SYLLABLE SSUG..HANGUL SYLLABLE SSUH
+C481..C49B ; LVT # Lo [27] HANGUL SYLLABLE SSWEOG..HANGUL SYLLABLE SSWEOH
+C49D..C4B7 ; LVT # Lo [27] HANGUL SYLLABLE SSWEG..HANGUL SYLLABLE SSWEH
+C4B9..C4D3 ; LVT # Lo [27] HANGUL SYLLABLE SSWIG..HANGUL SYLLABLE SSWIH
+C4D5..C4EF ; LVT # Lo [27] HANGUL SYLLABLE SSYUG..HANGUL SYLLABLE SSYUH
+C4F1..C50B ; LVT # Lo [27] HANGUL SYLLABLE SSEUG..HANGUL SYLLABLE SSEUH
+C50D..C527 ; LVT # Lo [27] HANGUL SYLLABLE SSYIG..HANGUL SYLLABLE SSYIH
+C529..C543 ; LVT # Lo [27] HANGUL SYLLABLE SSIG..HANGUL SYLLABLE SSIH
+C545..C55F ; LVT # Lo [27] HANGUL SYLLABLE AG..HANGUL SYLLABLE AH
+C561..C57B ; LVT # Lo [27] HANGUL SYLLABLE AEG..HANGUL SYLLABLE AEH
+C57D..C597 ; LVT # Lo [27] HANGUL SYLLABLE YAG..HANGUL SYLLABLE YAH
+C599..C5B3 ; LVT # Lo [27] HANGUL SYLLABLE YAEG..HANGUL SYLLABLE YAEH
+C5B5..C5CF ; LVT # Lo [27] HANGUL SYLLABLE EOG..HANGUL SYLLABLE EOH
+C5D1..C5EB ; LVT # Lo [27] HANGUL SYLLABLE EG..HANGUL SYLLABLE EH
+C5ED..C607 ; LVT # Lo [27] HANGUL SYLLABLE YEOG..HANGUL SYLLABLE YEOH
+C609..C623 ; LVT # Lo [27] HANGUL SYLLABLE YEG..HANGUL SYLLABLE YEH
+C625..C63F ; LVT # Lo [27] HANGUL SYLLABLE OG..HANGUL SYLLABLE OH
+C641..C65B ; LVT # Lo [27] HANGUL SYLLABLE WAG..HANGUL SYLLABLE WAH
+C65D..C677 ; LVT # Lo [27] HANGUL SYLLABLE WAEG..HANGUL SYLLABLE WAEH
+C679..C693 ; LVT # Lo [27] HANGUL SYLLABLE OEG..HANGUL SYLLABLE OEH
+C695..C6AF ; LVT # Lo [27] HANGUL SYLLABLE YOG..HANGUL SYLLABLE YOH
+C6B1..C6CB ; LVT # Lo [27] HANGUL SYLLABLE UG..HANGUL SYLLABLE UH
+C6CD..C6E7 ; LVT # Lo [27] HANGUL SYLLABLE WEOG..HANGUL SYLLABLE WEOH
+C6E9..C703 ; LVT # Lo [27] HANGUL SYLLABLE WEG..HANGUL SYLLABLE WEH
+C705..C71F ; LVT # Lo [27] HANGUL SYLLABLE WIG..HANGUL SYLLABLE WIH
+C721..C73B ; LVT # Lo [27] HANGUL SYLLABLE YUG..HANGUL SYLLABLE YUH
+C73D..C757 ; LVT # Lo [27] HANGUL SYLLABLE EUG..HANGUL SYLLABLE EUH
+C759..C773 ; LVT # Lo [27] HANGUL SYLLABLE YIG..HANGUL SYLLABLE YIH
+C775..C78F ; LVT # Lo [27] HANGUL SYLLABLE IG..HANGUL SYLLABLE IH
+C791..C7AB ; LVT # Lo [27] HANGUL SYLLABLE JAG..HANGUL SYLLABLE JAH
+C7AD..C7C7 ; LVT # Lo [27] HANGUL SYLLABLE JAEG..HANGUL SYLLABLE JAEH
+C7C9..C7E3 ; LVT # Lo [27] HANGUL SYLLABLE JYAG..HANGUL SYLLABLE JYAH
+C7E5..C7FF ; LVT # Lo [27] HANGUL SYLLABLE JYAEG..HANGUL SYLLABLE JYAEH
+C801..C81B ; LVT # Lo [27] HANGUL SYLLABLE JEOG..HANGUL SYLLABLE JEOH
+C81D..C837 ; LVT # Lo [27] HANGUL SYLLABLE JEG..HANGUL SYLLABLE JEH
+C839..C853 ; LVT # Lo [27] HANGUL SYLLABLE JYEOG..HANGUL SYLLABLE JYEOH
+C855..C86F ; LVT # Lo [27] HANGUL SYLLABLE JYEG..HANGUL SYLLABLE JYEH
+C871..C88B ; LVT # Lo [27] HANGUL SYLLABLE JOG..HANGUL SYLLABLE JOH
+C88D..C8A7 ; LVT # Lo [27] HANGUL SYLLABLE JWAG..HANGUL SYLLABLE JWAH
+C8A9..C8C3 ; LVT # Lo [27] HANGUL SYLLABLE JWAEG..HANGUL SYLLABLE JWAEH
+C8C5..C8DF ; LVT # Lo [27] HANGUL SYLLABLE JOEG..HANGUL SYLLABLE JOEH
+C8E1..C8FB ; LVT # Lo [27] HANGUL SYLLABLE JYOG..HANGUL SYLLABLE JYOH
+C8FD..C917 ; LVT # Lo [27] HANGUL SYLLABLE JUG..HANGUL SYLLABLE JUH
+C919..C933 ; LVT # Lo [27] HANGUL SYLLABLE JWEOG..HANGUL SYLLABLE JWEOH
+C935..C94F ; LVT # Lo [27] HANGUL SYLLABLE JWEG..HANGUL SYLLABLE JWEH
+C951..C96B ; LVT # Lo [27] HANGUL SYLLABLE JWIG..HANGUL SYLLABLE JWIH
+C96D..C987 ; LVT # Lo [27] HANGUL SYLLABLE JYUG..HANGUL SYLLABLE JYUH
+C989..C9A3 ; LVT # Lo [27] HANGUL SYLLABLE JEUG..HANGUL SYLLABLE JEUH
+C9A5..C9BF ; LVT # Lo [27] HANGUL SYLLABLE JYIG..HANGUL SYLLABLE JYIH
+C9C1..C9DB ; LVT # Lo [27] HANGUL SYLLABLE JIG..HANGUL SYLLABLE JIH
+C9DD..C9F7 ; LVT # Lo [27] HANGUL SYLLABLE JJAG..HANGUL SYLLABLE JJAH
+C9F9..CA13 ; LVT # Lo [27] HANGUL SYLLABLE JJAEG..HANGUL SYLLABLE JJAEH
+CA15..CA2F ; LVT # Lo [27] HANGUL SYLLABLE JJYAG..HANGUL SYLLABLE JJYAH
+CA31..CA4B ; LVT # Lo [27] HANGUL SYLLABLE JJYAEG..HANGUL SYLLABLE JJYAEH
+CA4D..CA67 ; LVT # Lo [27] HANGUL SYLLABLE JJEOG..HANGUL SYLLABLE JJEOH
+CA69..CA83 ; LVT # Lo [27] HANGUL SYLLABLE JJEG..HANGUL SYLLABLE JJEH
+CA85..CA9F ; LVT # Lo [27] HANGUL SYLLABLE JJYEOG..HANGUL SYLLABLE JJYEOH
+CAA1..CABB ; LVT # Lo [27] HANGUL SYLLABLE JJYEG..HANGUL SYLLABLE JJYEH
+CABD..CAD7 ; LVT # Lo [27] HANGUL SYLLABLE JJOG..HANGUL SYLLABLE JJOH
+CAD9..CAF3 ; LVT # Lo [27] HANGUL SYLLABLE JJWAG..HANGUL SYLLABLE JJWAH
+CAF5..CB0F ; LVT # Lo [27] HANGUL SYLLABLE JJWAEG..HANGUL SYLLABLE JJWAEH
+CB11..CB2B ; LVT # Lo [27] HANGUL SYLLABLE JJOEG..HANGUL SYLLABLE JJOEH
+CB2D..CB47 ; LVT # Lo [27] HANGUL SYLLABLE JJYOG..HANGUL SYLLABLE JJYOH
+CB49..CB63 ; LVT # Lo [27] HANGUL SYLLABLE JJUG..HANGUL SYLLABLE JJUH
+CB65..CB7F ; LVT # Lo [27] HANGUL SYLLABLE JJWEOG..HANGUL SYLLABLE JJWEOH
+CB81..CB9B ; LVT # Lo [27] HANGUL SYLLABLE JJWEG..HANGUL SYLLABLE JJWEH
+CB9D..CBB7 ; LVT # Lo [27] HANGUL SYLLABLE JJWIG..HANGUL SYLLABLE JJWIH
+CBB9..CBD3 ; LVT # Lo [27] HANGUL SYLLABLE JJYUG..HANGUL SYLLABLE JJYUH
+CBD5..CBEF ; LVT # Lo [27] HANGUL SYLLABLE JJEUG..HANGUL SYLLABLE JJEUH
+CBF1..CC0B ; LVT # Lo [27] HANGUL SYLLABLE JJYIG..HANGUL SYLLABLE JJYIH
+CC0D..CC27 ; LVT # Lo [27] HANGUL SYLLABLE JJIG..HANGUL SYLLABLE JJIH
+CC29..CC43 ; LVT # Lo [27] HANGUL SYLLABLE CAG..HANGUL SYLLABLE CAH
+CC45..CC5F ; LVT # Lo [27] HANGUL SYLLABLE CAEG..HANGUL SYLLABLE CAEH
+CC61..CC7B ; LVT # Lo [27] HANGUL SYLLABLE CYAG..HANGUL SYLLABLE CYAH
+CC7D..CC97 ; LVT # Lo [27] HANGUL SYLLABLE CYAEG..HANGUL SYLLABLE CYAEH
+CC99..CCB3 ; LVT # Lo [27] HANGUL SYLLABLE CEOG..HANGUL SYLLABLE CEOH
+CCB5..CCCF ; LVT # Lo [27] HANGUL SYLLABLE CEG..HANGUL SYLLABLE CEH
+CCD1..CCEB ; LVT # Lo [27] HANGUL SYLLABLE CYEOG..HANGUL SYLLABLE CYEOH
+CCED..CD07 ; LVT # Lo [27] HANGUL SYLLABLE CYEG..HANGUL SYLLABLE CYEH
+CD09..CD23 ; LVT # Lo [27] HANGUL SYLLABLE COG..HANGUL SYLLABLE COH
+CD25..CD3F ; LVT # Lo [27] HANGUL SYLLABLE CWAG..HANGUL SYLLABLE CWAH
+CD41..CD5B ; LVT # Lo [27] HANGUL SYLLABLE CWAEG..HANGUL SYLLABLE CWAEH
+CD5D..CD77 ; LVT # Lo [27] HANGUL SYLLABLE COEG..HANGUL SYLLABLE COEH
+CD79..CD93 ; LVT # Lo [27] HANGUL SYLLABLE CYOG..HANGUL SYLLABLE CYOH
+CD95..CDAF ; LVT # Lo [27] HANGUL SYLLABLE CUG..HANGUL SYLLABLE CUH
+CDB1..CDCB ; LVT # Lo [27] HANGUL SYLLABLE CWEOG..HANGUL SYLLABLE CWEOH
+CDCD..CDE7 ; LVT # Lo [27] HANGUL SYLLABLE CWEG..HANGUL SYLLABLE CWEH
+CDE9..CE03 ; LVT # Lo [27] HANGUL SYLLABLE CWIG..HANGUL SYLLABLE CWIH
+CE05..CE1F ; LVT # Lo [27] HANGUL SYLLABLE CYUG..HANGUL SYLLABLE CYUH
+CE21..CE3B ; LVT # Lo [27] HANGUL SYLLABLE CEUG..HANGUL SYLLABLE CEUH
+CE3D..CE57 ; LVT # Lo [27] HANGUL SYLLABLE CYIG..HANGUL SYLLABLE CYIH
+CE59..CE73 ; LVT # Lo [27] HANGUL SYLLABLE CIG..HANGUL SYLLABLE CIH
+CE75..CE8F ; LVT # Lo [27] HANGUL SYLLABLE KAG..HANGUL SYLLABLE KAH
+CE91..CEAB ; LVT # Lo [27] HANGUL SYLLABLE KAEG..HANGUL SYLLABLE KAEH
+CEAD..CEC7 ; LVT # Lo [27] HANGUL SYLLABLE KYAG..HANGUL SYLLABLE KYAH
+CEC9..CEE3 ; LVT # Lo [27] HANGUL SYLLABLE KYAEG..HANGUL SYLLABLE KYAEH
+CEE5..CEFF ; LVT # Lo [27] HANGUL SYLLABLE KEOG..HANGUL SYLLABLE KEOH
+CF01..CF1B ; LVT # Lo [27] HANGUL SYLLABLE KEG..HANGUL SYLLABLE KEH
+CF1D..CF37 ; LVT # Lo [27] HANGUL SYLLABLE KYEOG..HANGUL SYLLABLE KYEOH
+CF39..CF53 ; LVT # Lo [27] HANGUL SYLLABLE KYEG..HANGUL SYLLABLE KYEH
+CF55..CF6F ; LVT # Lo [27] HANGUL SYLLABLE KOG..HANGUL SYLLABLE KOH
+CF71..CF8B ; LVT # Lo [27] HANGUL SYLLABLE KWAG..HANGUL SYLLABLE KWAH
+CF8D..CFA7 ; LVT # Lo [27] HANGUL SYLLABLE KWAEG..HANGUL SYLLABLE KWAEH
+CFA9..CFC3 ; LVT # Lo [27] HANGUL SYLLABLE KOEG..HANGUL SYLLABLE KOEH
+CFC5..CFDF ; LVT # Lo [27] HANGUL SYLLABLE KYOG..HANGUL SYLLABLE KYOH
+CFE1..CFFB ; LVT # Lo [27] HANGUL SYLLABLE KUG..HANGUL SYLLABLE KUH
+CFFD..D017 ; LVT # Lo [27] HANGUL SYLLABLE KWEOG..HANGUL SYLLABLE KWEOH
+D019..D033 ; LVT # Lo [27] HANGUL SYLLABLE KWEG..HANGUL SYLLABLE KWEH
+D035..D04F ; LVT # Lo [27] HANGUL SYLLABLE KWIG..HANGUL SYLLABLE KWIH
+D051..D06B ; LVT # Lo [27] HANGUL SYLLABLE KYUG..HANGUL SYLLABLE KYUH
+D06D..D087 ; LVT # Lo [27] HANGUL SYLLABLE KEUG..HANGUL SYLLABLE KEUH
+D089..D0A3 ; LVT # Lo [27] HANGUL SYLLABLE KYIG..HANGUL SYLLABLE KYIH
+D0A5..D0BF ; LVT # Lo [27] HANGUL SYLLABLE KIG..HANGUL SYLLABLE KIH
+D0C1..D0DB ; LVT # Lo [27] HANGUL SYLLABLE TAG..HANGUL SYLLABLE TAH
+D0DD..D0F7 ; LVT # Lo [27] HANGUL SYLLABLE TAEG..HANGUL SYLLABLE TAEH
+D0F9..D113 ; LVT # Lo [27] HANGUL SYLLABLE TYAG..HANGUL SYLLABLE TYAH
+D115..D12F ; LVT # Lo [27] HANGUL SYLLABLE TYAEG..HANGUL SYLLABLE TYAEH
+D131..D14B ; LVT # Lo [27] HANGUL SYLLABLE TEOG..HANGUL SYLLABLE TEOH
+D14D..D167 ; LVT # Lo [27] HANGUL SYLLABLE TEG..HANGUL SYLLABLE TEH
+D169..D183 ; LVT # Lo [27] HANGUL SYLLABLE TYEOG..HANGUL SYLLABLE TYEOH
+D185..D19F ; LVT # Lo [27] HANGUL SYLLABLE TYEG..HANGUL SYLLABLE TYEH
+D1A1..D1BB ; LVT # Lo [27] HANGUL SYLLABLE TOG..HANGUL SYLLABLE TOH
+D1BD..D1D7 ; LVT # Lo [27] HANGUL SYLLABLE TWAG..HANGUL SYLLABLE TWAH
+D1D9..D1F3 ; LVT # Lo [27] HANGUL SYLLABLE TWAEG..HANGUL SYLLABLE TWAEH
+D1F5..D20F ; LVT # Lo [27] HANGUL SYLLABLE TOEG..HANGUL SYLLABLE TOEH
+D211..D22B ; LVT # Lo [27] HANGUL SYLLABLE TYOG..HANGUL SYLLABLE TYOH
+D22D..D247 ; LVT # Lo [27] HANGUL SYLLABLE TUG..HANGUL SYLLABLE TUH
+D249..D263 ; LVT # Lo [27] HANGUL SYLLABLE TWEOG..HANGUL SYLLABLE TWEOH
+D265..D27F ; LVT # Lo [27] HANGUL SYLLABLE TWEG..HANGUL SYLLABLE TWEH
+D281..D29B ; LVT # Lo [27] HANGUL SYLLABLE TWIG..HANGUL SYLLABLE TWIH
+D29D..D2B7 ; LVT # Lo [27] HANGUL SYLLABLE TYUG..HANGUL SYLLABLE TYUH
+D2B9..D2D3 ; LVT # Lo [27] HANGUL SYLLABLE TEUG..HANGUL SYLLABLE TEUH
+D2D5..D2EF ; LVT # Lo [27] HANGUL SYLLABLE TYIG..HANGUL SYLLABLE TYIH
+D2F1..D30B ; LVT # Lo [27] HANGUL SYLLABLE TIG..HANGUL SYLLABLE TIH
+D30D..D327 ; LVT # Lo [27] HANGUL SYLLABLE PAG..HANGUL SYLLABLE PAH
+D329..D343 ; LVT # Lo [27] HANGUL SYLLABLE PAEG..HANGUL SYLLABLE PAEH
+D345..D35F ; LVT # Lo [27] HANGUL SYLLABLE PYAG..HANGUL SYLLABLE PYAH
+D361..D37B ; LVT # Lo [27] HANGUL SYLLABLE PYAEG..HANGUL SYLLABLE PYAEH
+D37D..D397 ; LVT # Lo [27] HANGUL SYLLABLE PEOG..HANGUL SYLLABLE PEOH
+D399..D3B3 ; LVT # Lo [27] HANGUL SYLLABLE PEG..HANGUL SYLLABLE PEH
+D3B5..D3CF ; LVT # Lo [27] HANGUL SYLLABLE PYEOG..HANGUL SYLLABLE PYEOH
+D3D1..D3EB ; LVT # Lo [27] HANGUL SYLLABLE PYEG..HANGUL SYLLABLE PYEH
+D3ED..D407 ; LVT # Lo [27] HANGUL SYLLABLE POG..HANGUL SYLLABLE POH
+D409..D423 ; LVT # Lo [27] HANGUL SYLLABLE PWAG..HANGUL SYLLABLE PWAH
+D425..D43F ; LVT # Lo [27] HANGUL SYLLABLE PWAEG..HANGUL SYLLABLE PWAEH
+D441..D45B ; LVT # Lo [27] HANGUL SYLLABLE POEG..HANGUL SYLLABLE POEH
+D45D..D477 ; LVT # Lo [27] HANGUL SYLLABLE PYOG..HANGUL SYLLABLE PYOH
+D479..D493 ; LVT # Lo [27] HANGUL SYLLABLE PUG..HANGUL SYLLABLE PUH
+D495..D4AF ; LVT # Lo [27] HANGUL SYLLABLE PWEOG..HANGUL SYLLABLE PWEOH
+D4B1..D4CB ; LVT # Lo [27] HANGUL SYLLABLE PWEG..HANGUL SYLLABLE PWEH
+D4CD..D4E7 ; LVT # Lo [27] HANGUL SYLLABLE PWIG..HANGUL SYLLABLE PWIH
+D4E9..D503 ; LVT # Lo [27] HANGUL SYLLABLE PYUG..HANGUL SYLLABLE PYUH
+D505..D51F ; LVT # Lo [27] HANGUL SYLLABLE PEUG..HANGUL SYLLABLE PEUH
+D521..D53B ; LVT # Lo [27] HANGUL SYLLABLE PYIG..HANGUL SYLLABLE PYIH
+D53D..D557 ; LVT # Lo [27] HANGUL SYLLABLE PIG..HANGUL SYLLABLE PIH
+D559..D573 ; LVT # Lo [27] HANGUL SYLLABLE HAG..HANGUL SYLLABLE HAH
+D575..D58F ; LVT # Lo [27] HANGUL SYLLABLE HAEG..HANGUL SYLLABLE HAEH
+D591..D5AB ; LVT # Lo [27] HANGUL SYLLABLE HYAG..HANGUL SYLLABLE HYAH
+D5AD..D5C7 ; LVT # Lo [27] HANGUL SYLLABLE HYAEG..HANGUL SYLLABLE HYAEH
+D5C9..D5E3 ; LVT # Lo [27] HANGUL SYLLABLE HEOG..HANGUL SYLLABLE HEOH
+D5E5..D5FF ; LVT # Lo [27] HANGUL SYLLABLE HEG..HANGUL SYLLABLE HEH
+D601..D61B ; LVT # Lo [27] HANGUL SYLLABLE HYEOG..HANGUL SYLLABLE HYEOH
+D61D..D637 ; LVT # Lo [27] HANGUL SYLLABLE HYEG..HANGUL SYLLABLE HYEH
+D639..D653 ; LVT # Lo [27] HANGUL SYLLABLE HOG..HANGUL SYLLABLE HOH
+D655..D66F ; LVT # Lo [27] HANGUL SYLLABLE HWAG..HANGUL SYLLABLE HWAH
+D671..D68B ; LVT # Lo [27] HANGUL SYLLABLE HWAEG..HANGUL SYLLABLE HWAEH
+D68D..D6A7 ; LVT # Lo [27] HANGUL SYLLABLE HOEG..HANGUL SYLLABLE HOEH
+D6A9..D6C3 ; LVT # Lo [27] HANGUL SYLLABLE HYOG..HANGUL SYLLABLE HYOH
+D6C5..D6DF ; LVT # Lo [27] HANGUL SYLLABLE HUG..HANGUL SYLLABLE HUH
+D6E1..D6FB ; LVT # Lo [27] HANGUL SYLLABLE HWEOG..HANGUL SYLLABLE HWEOH
+D6FD..D717 ; LVT # Lo [27] HANGUL SYLLABLE HWEG..HANGUL SYLLABLE HWEH
+D719..D733 ; LVT # Lo [27] HANGUL SYLLABLE HWIG..HANGUL SYLLABLE HWIH
+D735..D74F ; LVT # Lo [27] HANGUL SYLLABLE HYUG..HANGUL SYLLABLE HYUH
+D751..D76B ; LVT # Lo [27] HANGUL SYLLABLE HEUG..HANGUL SYLLABLE HEUH
+D76D..D787 ; LVT # Lo [27] HANGUL SYLLABLE HYIG..HANGUL SYLLABLE HYIH
+D789..D7A3 ; LVT # Lo [27] HANGUL SYLLABLE HIG..HANGUL SYLLABLE HIH
+
+# Total code points: 10773
+
+# ================================================
+
+200D ; ZWJ # Cf ZERO WIDTH JOINER
+
+# Total code points: 1
+
+# EOF
diff --git a/pkgs/characters/third_party/Unicode_Consortium/GraphemeBreakTest.txt b/pkgs/characters/third_party/Unicode_Consortium/GraphemeBreakTest.txt
new file mode 100644
index 0000000..d10c174
--- /dev/null
+++ b/pkgs/characters/third_party/Unicode_Consortium/GraphemeBreakTest.txt
@@ -0,0 +1,1121 @@
+# GraphemeBreakTest-16.0.0.txt
+# Date: 2024-05-02, 15:02:48 GMT
+# © 2024 Unicode®, Inc.
+# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+# For terms of use and license, see https://www.unicode.org/terms_of_use.html
+#
+# Unicode Character Database
+# For documentation, see https://www.unicode.org/reports/tr44/
+#
+# Default Grapheme_Cluster_Break Test
+#
+# Format:
+# <string> (# <comment>)?
+# <string> contains hex Unicode code points, with
+# ÷ wherever there is a break opportunity, and
+# × wherever there is not.
+# <comment> the format can change, but currently it shows:
+# - the sample character name
+# - (x) the Grapheme_Cluster_Break property value for the sample character
+# - [x] the rule that determines whether there is a break or not,
+# as listed in the Rules section of GraphemeBreakTest.html
+#
+# These samples may be extended or changed in the future.
+#
+÷ 0020 ÷ 0020 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0020 × 0308 ÷ 0020 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0020 ÷ 000D ÷ # ÷ [0.2] SPACE (Other) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0020 × 0308 ÷ 000D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0020 ÷ 000A ÷ # ÷ [0.2] SPACE (Other) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0020 × 0308 ÷ 000A ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0020 ÷ 0001 ÷ # ÷ [0.2] SPACE (Other) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0020 × 0308 ÷ 0001 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0020 × 200C ÷ # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0020 × 0308 × 200C ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0020 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0020 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0020 ÷ 0600 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0020 × 0308 ÷ 0600 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0020 × 0A03 ÷ # ÷ [0.2] SPACE (Other) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0020 × 0308 × 0A03 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0020 ÷ 1100 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0020 × 0308 ÷ 1100 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0020 ÷ 1160 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0020 × 0308 ÷ 1160 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0020 ÷ 11A8 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0020 × 0308 ÷ 11A8 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0020 ÷ AC00 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0020 × 0308 ÷ AC00 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0020 ÷ AC01 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0020 × 0308 ÷ AC01 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0020 × 0903 ÷ # ÷ [0.2] SPACE (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0020 × 0308 × 0903 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0020 ÷ 0904 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0020 × 0308 ÷ 0904 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0020 ÷ 0D4E ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0020 × 0308 ÷ 0D4E ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0020 ÷ 0915 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0020 × 0308 ÷ 0915 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0020 ÷ 231A ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0020 × 0308 ÷ 231A ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0020 × 0300 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0020 × 0308 × 0300 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0020 × 0900 ÷ # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0020 × 0308 × 0900 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0020 × 094D ÷ # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0020 × 0308 × 094D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0020 × 200D ÷ # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0020 × 0308 × 200D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0020 ÷ 0378 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0020 × 0308 ÷ 0378 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 000D ÷ 0020 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] SPACE (Other) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 000D ÷ 000D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 000D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 000D × 000A ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) × [3.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 000A ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 000D ÷ 0001 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 000D ÷ 200C ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 000D ÷ 0308 × 200C ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 000D ÷ 1F1E6 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 000D ÷ 0600 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 000D ÷ 0A03 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 000D ÷ 0308 × 0A03 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 000D ÷ 1100 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 000D ÷ 1160 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 000D ÷ 11A8 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 000D ÷ AC00 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 000D ÷ AC01 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 000D ÷ 0903 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 000D ÷ 0308 × 0903 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 000D ÷ 0904 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 0904 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 000D ÷ 0D4E ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 0D4E ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 000D ÷ 0915 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 000D ÷ 231A ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] WATCH (ExtPict) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 231A ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 000D ÷ 0300 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 000D ÷ 0308 × 0300 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 000D ÷ 0900 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 000D ÷ 0308 × 0900 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 000D ÷ 094D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 000D ÷ 0308 × 094D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 000D ÷ 200D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 000D ÷ 0308 × 200D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 000D ÷ 0378 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 000A ÷ 0020 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] SPACE (Other) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 000A ÷ 000D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 000D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 000A ÷ 000A ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 000A ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 000A ÷ 0001 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 000A ÷ 200C ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 000A ÷ 0308 × 200C ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 000A ÷ 1F1E6 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 000A ÷ 0600 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 000A ÷ 0A03 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 000A ÷ 0308 × 0A03 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 000A ÷ 1100 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 000A ÷ 1160 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 000A ÷ 11A8 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 000A ÷ AC00 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 000A ÷ AC01 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 000A ÷ 0903 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 000A ÷ 0308 × 0903 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 000A ÷ 0904 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 0904 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 000A ÷ 0D4E ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 0D4E ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 000A ÷ 0915 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 000A ÷ 231A ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] WATCH (ExtPict) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 231A ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 000A ÷ 0300 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 000A ÷ 0308 × 0300 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 000A ÷ 0900 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 000A ÷ 0308 × 0900 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 000A ÷ 094D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 000A ÷ 0308 × 094D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 000A ÷ 200D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 000A ÷ 0308 × 200D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 000A ÷ 0378 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0001 ÷ 0020 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] SPACE (Other) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0001 ÷ 000D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 000D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0001 ÷ 000A ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 000A ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0001 ÷ 0001 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0001 ÷ 200C ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0001 ÷ 0308 × 200C ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0001 ÷ 1F1E6 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0001 ÷ 0600 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0001 ÷ 0A03 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0001 ÷ 0308 × 0A03 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0001 ÷ 1100 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0001 ÷ 1160 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0001 ÷ 11A8 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0001 ÷ AC00 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0001 ÷ AC01 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0001 ÷ 0903 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0001 ÷ 0308 × 0903 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0001 ÷ 0904 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 0904 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0001 ÷ 0D4E ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 0D4E ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0001 ÷ 0915 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0001 ÷ 231A ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 231A ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0001 ÷ 0300 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0001 ÷ 0308 × 0300 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0001 ÷ 0900 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0001 ÷ 0308 × 0900 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0001 ÷ 094D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0001 ÷ 0308 × 094D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0001 ÷ 200D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0001 ÷ 0308 × 200D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0001 ÷ 0378 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 200C ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 200C × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 200C ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 200C × 0308 ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 200C ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 200C × 0308 ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 200C ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 200C × 0308 ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 200C × 200C ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 200C × 0308 × 200C ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 200C ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 200C × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 200C ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 200C × 0308 ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 200C × 0A03 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 200C × 0308 × 0A03 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 200C ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 200C × 0308 ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 200C ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 200C × 0308 ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 200C ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 200C × 0308 ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 200C ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 200C × 0308 ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 200C ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 200C × 0308 ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 200C × 0903 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 200C × 0308 × 0903 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 200C ÷ 0904 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 200C × 0308 ÷ 0904 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 200C ÷ 0D4E ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 200C × 0308 ÷ 0D4E ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 200C ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 200C × 0308 ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 200C ÷ 231A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 200C × 0308 ÷ 231A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 200C × 0300 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 200C × 0308 × 0300 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 200C × 0900 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 200C × 0308 × 0900 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 200C × 094D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 200C × 0308 × 094D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 200C × 200D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 200C × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 200C ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 200C × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 1F1E6 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 1F1E6 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 1F1E6 ÷ 000A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 000A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 1F1E6 ÷ 0001 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 0001 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 1F1E6 × 200C ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 1F1E6 × 0308 × 200C ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 1F1E6 × 1F1E6 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 1F1E6 ÷ 0600 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 0600 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 1F1E6 × 0A03 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 1F1E6 × 0308 × 0A03 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 1F1E6 ÷ 1100 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 1100 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 1F1E6 ÷ 1160 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 1160 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 1F1E6 ÷ 11A8 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 11A8 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 1F1E6 ÷ AC00 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ AC00 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 1F1E6 ÷ AC01 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ AC01 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 1F1E6 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 1F1E6 × 0308 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 1F1E6 ÷ 0904 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 0904 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 1F1E6 ÷ 0D4E ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 0D4E ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 1F1E6 ÷ 0915 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 0915 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 1F1E6 ÷ 231A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 231A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 1F1E6 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 1F1E6 × 0308 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 1F1E6 × 0900 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 1F1E6 × 0308 × 0900 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 1F1E6 × 094D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 1F1E6 × 0308 × 094D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 1F1E6 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 1F1E6 × 0308 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 1F1E6 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0600 × 0020 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] SPACE (Other) ÷ [0.3]
+÷ 0600 × 0308 ÷ 0020 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0600 ÷ 000D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0600 × 0308 ÷ 000D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0600 ÷ 000A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0600 × 0308 ÷ 000A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0600 ÷ 0001 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0600 × 0308 ÷ 0001 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0600 × 200C ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0600 × 0308 × 200C ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0600 × 1F1E6 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0600 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0600 × 0600 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0600 × 0308 ÷ 0600 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0600 × 0A03 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0600 × 0308 × 0A03 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0600 × 1100 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0600 × 0308 ÷ 1100 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0600 × 1160 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0600 × 0308 ÷ 1160 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0600 × 11A8 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0600 × 0308 ÷ 11A8 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0600 × AC00 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0600 × 0308 ÷ AC00 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0600 × AC01 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0600 × 0308 ÷ AC01 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0600 × 0903 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0600 × 0308 × 0903 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0600 × 0904 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0600 × 0308 ÷ 0904 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0600 × 0D4E ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0600 × 0308 ÷ 0D4E ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0600 × 0915 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0600 × 0308 ÷ 0915 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0600 × 231A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] WATCH (ExtPict) ÷ [0.3]
+÷ 0600 × 0308 ÷ 231A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0600 × 0300 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0600 × 0308 × 0300 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0600 × 0900 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0600 × 0308 × 0900 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0600 × 094D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0600 × 0308 × 094D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0600 × 200D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0600 × 0308 × 200D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0600 × 0378 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] <reserved-0378> (Other) ÷ [0.3]
+÷ 0600 × 0308 ÷ 0378 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0A03 ÷ 0020 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0A03 × 0308 ÷ 0020 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0A03 ÷ 000D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0A03 × 0308 ÷ 000D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0A03 ÷ 000A ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0A03 × 0308 ÷ 000A ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0A03 ÷ 0001 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0A03 × 0308 ÷ 0001 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0A03 × 200C ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0A03 × 0308 × 200C ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0A03 ÷ 1F1E6 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0A03 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0A03 ÷ 0600 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0A03 × 0308 ÷ 0600 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0A03 × 0A03 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0A03 × 0308 × 0A03 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0A03 ÷ 1100 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0A03 × 0308 ÷ 1100 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0A03 ÷ 1160 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0A03 × 0308 ÷ 1160 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0A03 ÷ 11A8 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0A03 × 0308 ÷ 11A8 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0A03 ÷ AC00 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0A03 × 0308 ÷ AC00 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0A03 ÷ AC01 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0A03 × 0308 ÷ AC01 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0A03 × 0903 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0A03 × 0308 × 0903 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0A03 ÷ 0904 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0A03 × 0308 ÷ 0904 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0A03 ÷ 0D4E ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0A03 × 0308 ÷ 0D4E ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0A03 ÷ 0915 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0A03 × 0308 ÷ 0915 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0A03 ÷ 231A ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0A03 × 0308 ÷ 231A ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0A03 × 0300 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0A03 × 0308 × 0300 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0A03 × 0900 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0A03 × 0308 × 0900 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0A03 × 094D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0A03 × 0308 × 094D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0A03 × 200D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0A03 × 0308 × 200D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0A03 ÷ 0378 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0A03 × 0308 ÷ 0378 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 1100 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 1100 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 1100 ÷ 000D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 1100 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 1100 ÷ 000A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 1100 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 1100 ÷ 0001 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 1100 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 1100 × 200C ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 1100 × 0308 × 200C ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 1100 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 1100 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 1100 ÷ 0600 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 1100 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 1100 × 0A03 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 1100 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 1100 × 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 1100 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 1100 × 1160 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 1100 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 1100 ÷ 11A8 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 1100 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 1100 × AC00 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 1100 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 1100 × AC01 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 1100 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 1100 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 1100 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 1100 ÷ 0904 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 1100 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 1100 ÷ 0D4E ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 1100 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 1100 ÷ 0915 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 1100 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 1100 ÷ 231A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 1100 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 1100 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 1100 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 1100 × 0900 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 1100 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 1100 × 094D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 1100 × 0308 × 094D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 1100 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 1100 × 0308 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 1100 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 1100 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 1160 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 1160 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 1160 ÷ 000D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 1160 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 1160 ÷ 000A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 1160 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 1160 ÷ 0001 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 1160 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 1160 × 200C ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 1160 × 0308 × 200C ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 1160 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 1160 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 1160 ÷ 0600 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 1160 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 1160 × 0A03 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 1160 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 1160 ÷ 1100 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 1160 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 1160 × 1160 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 1160 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 1160 × 11A8 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 1160 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 1160 ÷ AC00 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 1160 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 1160 ÷ AC01 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 1160 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 1160 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 1160 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 1160 ÷ 0904 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 1160 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 1160 ÷ 0D4E ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 1160 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 1160 ÷ 0915 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 1160 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 1160 ÷ 231A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 1160 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 1160 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 1160 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 1160 × 0900 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 1160 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 1160 × 094D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 1160 × 0308 × 094D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 1160 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 1160 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 1160 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 1160 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 11A8 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 11A8 ÷ 000D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 11A8 ÷ 000A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 11A8 ÷ 0001 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 11A8 × 200C ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 11A8 × 0308 × 200C ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 11A8 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 11A8 ÷ 0600 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 11A8 × 0A03 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 11A8 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 11A8 ÷ 1160 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 11A8 × 11A8 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 11A8 ÷ AC00 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 11A8 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 11A8 ÷ AC01 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 11A8 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 11A8 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 11A8 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 11A8 ÷ 0904 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 11A8 ÷ 0D4E ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 11A8 ÷ 0915 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 11A8 ÷ 231A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 11A8 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 11A8 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 11A8 × 0900 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 11A8 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 11A8 × 094D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 11A8 × 0308 × 094D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 11A8 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 11A8 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 11A8 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ AC00 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ AC00 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ AC00 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ AC00 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ AC00 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ AC00 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ AC00 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ AC00 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ AC00 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ AC00 × 0308 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ AC00 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ AC00 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ AC00 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ AC00 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ AC00 × 0A03 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ AC00 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ AC00 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ AC00 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ AC00 × 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ AC00 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ AC00 × 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ AC00 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ AC00 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ AC00 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ AC00 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ AC00 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ AC00 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ AC00 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ AC00 ÷ 0904 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ AC00 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ AC00 ÷ 0D4E ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ AC00 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ AC00 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ AC00 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ AC00 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ AC00 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ AC00 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ AC00 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ AC00 × 0900 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ AC00 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ AC00 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ AC00 × 0308 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ AC00 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ AC00 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ AC00 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ AC00 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ AC01 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ AC01 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ AC01 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ AC01 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ AC01 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ AC01 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ AC01 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ AC01 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ AC01 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ AC01 × 0308 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ AC01 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ AC01 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ AC01 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ AC01 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ AC01 × 0A03 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ AC01 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ AC01 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ AC01 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ AC01 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ AC01 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ AC01 × 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ AC01 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ AC01 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ AC01 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ AC01 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ AC01 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ AC01 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ AC01 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ AC01 ÷ 0904 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ AC01 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ AC01 ÷ 0D4E ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ AC01 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ AC01 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ AC01 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ AC01 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ AC01 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ AC01 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ AC01 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ AC01 × 0900 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ AC01 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ AC01 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ AC01 × 0308 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ AC01 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ AC01 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ AC01 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ AC01 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0903 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0903 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0903 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0903 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0903 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0903 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0903 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0903 × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0903 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0903 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0903 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0903 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0903 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0903 × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0903 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0903 × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0903 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0903 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0903 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0903 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0903 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0903 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0903 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0903 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0903 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0903 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0903 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0903 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0903 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0903 × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0903 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0903 × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0903 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0903 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0903 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0903 × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0903 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0903 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0903 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0903 × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0903 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0903 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0903 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0903 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0903 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0903 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0904 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0904 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0904 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0904 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0904 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0904 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0904 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0904 × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0904 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0904 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0904 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0904 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0904 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0904 × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0904 × 0A03 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0904 × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0904 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0904 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0904 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0904 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0904 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0904 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0904 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0904 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0904 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0904 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0904 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0904 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0904 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0904 × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0904 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0904 × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0904 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0904 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0904 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0904 × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0904 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0904 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0904 × 0900 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0904 × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0904 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0904 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0904 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0904 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0904 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0904 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0D4E × 0020 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] SPACE (Other) ÷ [0.3]
+÷ 0D4E × 0308 ÷ 0020 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0D4E ÷ 000D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0D4E × 0308 ÷ 000D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0D4E ÷ 000A ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0D4E × 0308 ÷ 000A ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0D4E ÷ 0001 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0D4E × 0308 ÷ 0001 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0D4E × 200C ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0D4E × 0308 × 200C ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0D4E × 1F1E6 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0D4E × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0D4E × 0600 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0D4E × 0308 ÷ 0600 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0D4E × 0A03 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0D4E × 0308 × 0A03 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0D4E × 1100 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0D4E × 0308 ÷ 1100 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0D4E × 1160 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0D4E × 0308 ÷ 1160 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0D4E × 11A8 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0D4E × 0308 ÷ 11A8 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0D4E × AC00 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0D4E × 0308 ÷ AC00 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0D4E × AC01 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0D4E × 0308 ÷ AC01 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0D4E × 0903 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0D4E × 0308 × 0903 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0D4E × 0904 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0D4E × 0308 ÷ 0904 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0D4E × 0D4E ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0D4E × 0308 ÷ 0D4E ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0D4E × 0915 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0D4E × 0308 ÷ 0915 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0D4E × 231A ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] WATCH (ExtPict) ÷ [0.3]
+÷ 0D4E × 0308 ÷ 231A ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0D4E × 0300 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0D4E × 0308 × 0300 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0D4E × 0900 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0D4E × 0308 × 0900 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0D4E × 094D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0D4E × 0308 × 094D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0D4E × 200D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0D4E × 0308 × 200D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0D4E × 0378 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] <reserved-0378> (Other) ÷ [0.3]
+÷ 0D4E × 0308 ÷ 0378 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0915 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0915 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0915 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0915 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0915 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0915 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0915 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0915 × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0915 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0915 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0915 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0915 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0915 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0915 × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0915 × 0A03 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0915 × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0915 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0915 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0915 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0915 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0915 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0915 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0915 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0915 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0915 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0915 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0915 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0915 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0915 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0915 × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0915 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0915 × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0915 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0915 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0915 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0915 × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0915 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0915 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0915 × 0900 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0915 × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0915 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0915 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0915 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0915 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0915 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0915 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 231A ÷ 0020 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 231A × 0308 ÷ 0020 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 231A ÷ 000D ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 231A × 0308 ÷ 000D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 231A ÷ 000A ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 231A × 0308 ÷ 000A ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 231A ÷ 0001 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 231A × 0308 ÷ 0001 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 231A × 200C ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 231A × 0308 × 200C ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 231A ÷ 1F1E6 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 231A × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 231A ÷ 0600 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 231A × 0308 ÷ 0600 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 231A × 0A03 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 231A × 0308 × 0A03 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 231A ÷ 1100 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 231A × 0308 ÷ 1100 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 231A ÷ 1160 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 231A × 0308 ÷ 1160 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 231A ÷ 11A8 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 231A × 0308 ÷ 11A8 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 231A ÷ AC00 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 231A × 0308 ÷ AC00 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 231A ÷ AC01 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 231A × 0308 ÷ AC01 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 231A × 0903 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 231A × 0308 × 0903 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 231A ÷ 0904 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 231A × 0308 ÷ 0904 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 231A ÷ 0D4E ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 231A × 0308 ÷ 0D4E ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 231A ÷ 0915 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 231A × 0308 ÷ 0915 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 231A ÷ 231A ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 231A × 0308 ÷ 231A ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 231A × 0300 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 231A × 0308 × 0300 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 231A × 0900 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 231A × 0308 × 0900 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 231A × 094D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 231A × 0308 × 094D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 231A × 200D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 231A × 0308 × 200D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 231A ÷ 0378 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 231A × 0308 ÷ 0378 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0300 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0300 × 0308 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0300 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0300 × 0308 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0300 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0300 × 0308 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0300 ÷ 0001 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0300 × 0308 ÷ 0001 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0300 × 200C ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0300 × 0308 × 200C ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0300 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0300 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0300 ÷ 0600 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0300 × 0308 ÷ 0600 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0300 × 0A03 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0300 × 0308 × 0A03 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0300 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0300 × 0308 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0300 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0300 × 0308 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0300 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0300 × 0308 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0300 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0300 × 0308 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0300 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0300 × 0308 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0300 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0300 × 0308 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0300 ÷ 0904 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0300 × 0308 ÷ 0904 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0300 ÷ 0D4E ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0300 × 0308 ÷ 0D4E ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0300 ÷ 0915 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0300 × 0308 ÷ 0915 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0300 ÷ 231A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0300 × 0308 ÷ 231A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0300 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0300 × 0308 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0300 × 0900 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0300 × 0308 × 0900 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0300 × 094D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0300 × 0308 × 094D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0300 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0300 × 0308 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0300 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0300 × 0308 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0900 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0900 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0900 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0900 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0900 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0900 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0900 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0900 × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0900 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0900 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0900 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0900 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0900 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0900 × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0900 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0900 × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0900 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0900 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0900 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0900 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0900 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0900 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0900 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0900 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0900 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0900 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0900 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0900 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0900 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0900 × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0900 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0900 × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0900 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0900 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0900 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0900 × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0900 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0900 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0900 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0900 × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0900 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0900 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0900 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0900 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0900 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0900 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 094D ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 094D × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 094D ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 094D × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 094D ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 094D × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 094D ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 094D × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 094D × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 094D × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 094D ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 094D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 094D ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 094D × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 094D × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 094D × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 094D ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 094D × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 094D ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 094D × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 094D ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 094D × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 094D ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 094D × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 094D ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 094D × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 094D × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 094D × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 094D ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 094D × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 094D ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 094D × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 094D ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 094D × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 094D ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 094D × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 094D × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 094D × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 094D × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 094D × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 094D × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 094D × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 094D × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 094D × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 094D ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 094D × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 200D ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 200D × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 200D ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 200D × 0308 ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 200D ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 200D × 0308 ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 200D ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 200D × 0308 ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 200D × 200C ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 200D × 0308 × 200C ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 200D ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 200D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 200D ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 200D × 0308 ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 200D × 0A03 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 200D × 0308 × 0A03 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 200D ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 200D × 0308 ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 200D ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 200D × 0308 ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 200D ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 200D × 0308 ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 200D ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 200D × 0308 ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 200D ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 200D × 0308 ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 200D × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 200D × 0308 × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 200D ÷ 0904 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 200D × 0308 ÷ 0904 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 200D ÷ 0D4E ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 200D × 0308 ÷ 0D4E ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 200D ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 200D × 0308 ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 200D ÷ 231A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 200D × 0308 ÷ 231A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 200D × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 200D × 0308 × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 200D × 0900 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 200D × 0308 × 0900 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 200D × 094D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 200D × 0308 × 094D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 200D × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 200D × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 200D ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 200D × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0378 ÷ 0020 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0378 × 0308 ÷ 0020 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0378 ÷ 000D ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0378 × 0308 ÷ 000D ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0378 ÷ 000A ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0378 × 0308 ÷ 000A ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0378 ÷ 0001 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0378 × 0308 ÷ 0001 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0378 × 200C ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0378 × 0308 × 200C ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+÷ 0378 ÷ 1F1E6 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0378 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0378 ÷ 0600 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0378 × 0308 ÷ 0600 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0378 × 0A03 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0378 × 0308 × 0A03 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0378 ÷ 1100 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0378 × 0308 ÷ 1100 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0378 ÷ 1160 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0378 × 0308 ÷ 1160 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0378 ÷ 11A8 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0378 × 0308 ÷ 11A8 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0378 ÷ AC00 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0378 × 0308 ÷ AC00 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0378 ÷ AC01 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0378 × 0308 ÷ AC01 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0378 × 0903 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0378 × 0308 × 0903 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0378 ÷ 0904 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0378 × 0308 ÷ 0904 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+÷ 0378 ÷ 0D4E ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0378 × 0308 ÷ 0D4E ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+÷ 0378 ÷ 0915 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0378 × 0308 ÷ 0915 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0378 ÷ 231A ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0378 × 0308 ÷ 231A ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0378 × 0300 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0378 × 0308 × 0300 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0378 × 0900 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0378 × 0308 × 0900 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+÷ 0378 × 094D ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0378 × 0308 × 094D ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+÷ 0378 × 200D ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0378 × 0308 × 200D ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0378 ÷ 0378 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0378 × 0308 ÷ 0378 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 000D × 000A ÷ 0061 ÷ 000A ÷ 0308 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) × [3.0] <LINE FEED (LF)> (LF) ÷ [4.0] LATIN SMALL LETTER A (Other) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0061 × 0308 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0020 × 200D ÷ 0646 ÷ # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC LETTER NOON (Other) ÷ [0.3]
+÷ 0646 × 200D ÷ 0020 ÷ # ÷ [0.2] ARABIC LETTER NOON (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 1100 × 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ AC00 × 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ AC01 × 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 1F1E6 × 1F1E7 ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+÷ 0061 ÷ 1F1E6 × 1F1E7 ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+÷ 0061 ÷ 1F1E6 × 1F1E7 × 200D ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+÷ 0061 ÷ 1F1E6 × 200D ÷ 1F1E7 × 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+÷ 0061 ÷ 1F1E6 × 1F1E7 ÷ 1F1E8 × 1F1E9 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER D (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+÷ 0061 × 200D ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0061 × 0308 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+÷ 0061 × 0903 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+÷ 0061 ÷ 0600 × 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) × [9.2] LATIN SMALL LETTER B (Other) ÷ [0.3]
+÷ 1F476 × 1F3FF ÷ 1F476 ÷ # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) ÷ [0.3]
+÷ 0061 × 1F3FF ÷ 1F476 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) ÷ [0.3]
+÷ 0061 × 1F3FF ÷ 1F476 × 200D × 1F6D1 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3]
+÷ 1F476 × 1F3FF × 0308 × 200D × 1F476 × 1F3FF ÷ # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [0.3]
+÷ 1F6D1 × 200D × 1F6D1 ÷ # ÷ [0.2] OCTAGONAL SIGN (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3]
+÷ 0061 × 200D ÷ 1F6D1 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3]
+÷ 2701 × 200D × 2701 ÷ # ÷ [0.2] UPPER BLADE SCISSORS (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] UPPER BLADE SCISSORS (Other) ÷ [0.3]
+÷ 0061 × 200D ÷ 2701 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] UPPER BLADE SCISSORS (Other) ÷ [0.3]
+÷ 0915 ÷ 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0915 × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0915 × 094D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0915 × 094D × 200D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0915 × 093C × 200D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0915 × 093C × 094D × 200D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0915 × 094D × 0924 × 094D × 092F ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER YA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0915 × 094D ÷ 0061 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] LATIN SMALL LETTER A (Other) ÷ [0.3]
+÷ 0061 × 094D ÷ 0924 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 003F × 094D ÷ 0924 ÷ # ÷ [0.2] QUESTION MARK (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+÷ 0915 × 094D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+#
+# Lines: 1093
+#
+# EOF
diff --git a/pkgs/characters/third_party/Unicode_Consortium/UNICODE_LICENSE.txt b/pkgs/characters/third_party/Unicode_Consortium/UNICODE_LICENSE.txt
new file mode 100644
index 0000000..ee8e69b
--- /dev/null
+++ b/pkgs/characters/third_party/Unicode_Consortium/UNICODE_LICENSE.txt
@@ -0,0 +1,39 @@
+UNICODE LICENSE V3
+
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright © 1991-2024 Unicode, Inc.
+
+NOTICE TO USER: Carefully read the following legal agreement. BY
+DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
+SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
+TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
+DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of data files and any associated documentation (the "Data Files") or
+software and any associated documentation (the "Software") to deal in the
+Data Files or Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, and/or sell
+copies of the Data Files or Software, and to permit persons to whom the
+Data Files or Software are furnished to do so, provided that either (a)
+this copyright and permission notice appear with all copies of the Data
+Files or Software, or (b) this copyright and permission notice appear in
+associated Documentation.
+
+THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
+THIRD PARTY RIGHTS.
+
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
+BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
+OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
+FILES OR SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder shall
+not be used in advertising or otherwise to promote the sale, use or other
+dealings in these Data Files or Software without prior written
+authorization of the copyright holder.
diff --git a/pkgs/characters/third_party/Unicode_Consortium/emoji_data.txt b/pkgs/characters/third_party/Unicode_Consortium/emoji_data.txt
new file mode 100644
index 0000000..ff99028
--- /dev/null
+++ b/pkgs/characters/third_party/Unicode_Consortium/emoji_data.txt
@@ -0,0 +1,1340 @@
+# emoji-data.txt
+# Date: 2024-05-01, 21:25:24 GMT
+# © 2024 Unicode®, Inc.
+# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+# For terms of use and license, see https://www.unicode.org/terms_of_use.html
+#
+# Emoji Data for UTS #51
+# Used with Emoji Version 16.0 and subsequent minor revisions (if any)
+#
+# For documentation and usage, see https://www.unicode.org/reports/tr51
+#
+# Format:
+# <codepoint(s)> ; <property> # <comments>
+# Note: there is no guarantee as to the structure of whitespace or comments
+#
+# Characters and sequences are listed in code point order. Users should be shown a more natural order.
+# See the CLDR collation order for Emoji.
+
+
+# ================================================
+
+# All omitted code points have Emoji=No
+
+0023 ; Emoji # E0.0 [1] (#️) hash sign
+002A ; Emoji # E0.0 [1] (*️) asterisk
+0030..0039 ; Emoji # E0.0 [10] (0️..9️) digit zero..digit nine
+00A9 ; Emoji # E0.6 [1] (©️) copyright
+00AE ; Emoji # E0.6 [1] (®️) registered
+203C ; Emoji # E0.6 [1] (‼️) double exclamation mark
+2049 ; Emoji # E0.6 [1] (⁉️) exclamation question mark
+2122 ; Emoji # E0.6 [1] (™️) trade mark
+2139 ; Emoji # E0.6 [1] (ℹ️) information
+2194..2199 ; Emoji # E0.6 [6] (↔️..↙️) left-right arrow..down-left arrow
+21A9..21AA ; Emoji # E0.6 [2] (↩️..↪️) right arrow curving left..left arrow curving right
+231A..231B ; Emoji # E0.6 [2] (⌚..⌛) watch..hourglass done
+2328 ; Emoji # E1.0 [1] (⌨️) keyboard
+23CF ; Emoji # E1.0 [1] (⏏️) eject button
+23E9..23EC ; Emoji # E0.6 [4] (⏩..⏬) fast-forward button..fast down button
+23ED..23EE ; Emoji # E0.7 [2] (⏭️..⏮️) next track button..last track button
+23EF ; Emoji # E1.0 [1] (⏯️) play or pause button
+23F0 ; Emoji # E0.6 [1] (⏰) alarm clock
+23F1..23F2 ; Emoji # E1.0 [2] (⏱️..⏲️) stopwatch..timer clock
+23F3 ; Emoji # E0.6 [1] (⏳) hourglass not done
+23F8..23FA ; Emoji # E0.7 [3] (⏸️..⏺️) pause button..record button
+24C2 ; Emoji # E0.6 [1] (Ⓜ️) circled M
+25AA..25AB ; Emoji # E0.6 [2] (▪️..▫️) black small square..white small square
+25B6 ; Emoji # E0.6 [1] (▶️) play button
+25C0 ; Emoji # E0.6 [1] (◀️) reverse button
+25FB..25FE ; Emoji # E0.6 [4] (◻️..◾) white medium square..black medium-small square
+2600..2601 ; Emoji # E0.6 [2] (☀️..☁️) sun..cloud
+2602..2603 ; Emoji # E0.7 [2] (☂️..☃️) umbrella..snowman
+2604 ; Emoji # E1.0 [1] (☄️) comet
+260E ; Emoji # E0.6 [1] (☎️) telephone
+2611 ; Emoji # E0.6 [1] (☑️) check box with check
+2614..2615 ; Emoji # E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage
+2618 ; Emoji # E1.0 [1] (☘️) shamrock
+261D ; Emoji # E0.6 [1] (☝️) index pointing up
+2620 ; Emoji # E1.0 [1] (☠️) skull and crossbones
+2622..2623 ; Emoji # E1.0 [2] (☢️..☣️) radioactive..biohazard
+2626 ; Emoji # E1.0 [1] (☦️) orthodox cross
+262A ; Emoji # E0.7 [1] (☪️) star and crescent
+262E ; Emoji # E1.0 [1] (☮️) peace symbol
+262F ; Emoji # E0.7 [1] (☯️) yin yang
+2638..2639 ; Emoji # E0.7 [2] (☸️..☹️) wheel of dharma..frowning face
+263A ; Emoji # E0.6 [1] (☺️) smiling face
+2640 ; Emoji # E4.0 [1] (♀️) female sign
+2642 ; Emoji # E4.0 [1] (♂️) male sign
+2648..2653 ; Emoji # E0.6 [12] (♈..♓) Aries..Pisces
+265F ; Emoji # E11.0 [1] (♟️) chess pawn
+2660 ; Emoji # E0.6 [1] (♠️) spade suit
+2663 ; Emoji # E0.6 [1] (♣️) club suit
+2665..2666 ; Emoji # E0.6 [2] (♥️..♦️) heart suit..diamond suit
+2668 ; Emoji # E0.6 [1] (♨️) hot springs
+267B ; Emoji # E0.6 [1] (♻️) recycling symbol
+267E ; Emoji # E11.0 [1] (♾️) infinity
+267F ; Emoji # E0.6 [1] (♿) wheelchair symbol
+2692 ; Emoji # E1.0 [1] (⚒️) hammer and pick
+2693 ; Emoji # E0.6 [1] (⚓) anchor
+2694 ; Emoji # E1.0 [1] (⚔️) crossed swords
+2695 ; Emoji # E4.0 [1] (⚕️) medical symbol
+2696..2697 ; Emoji # E1.0 [2] (⚖️..⚗️) balance scale..alembic
+2699 ; Emoji # E1.0 [1] (⚙️) gear
+269B..269C ; Emoji # E1.0 [2] (⚛️..⚜️) atom symbol..fleur-de-lis
+26A0..26A1 ; Emoji # E0.6 [2] (⚠️..⚡) warning..high voltage
+26A7 ; Emoji # E13.0 [1] (⚧️) transgender symbol
+26AA..26AB ; Emoji # E0.6 [2] (⚪..⚫) white circle..black circle
+26B0..26B1 ; Emoji # E1.0 [2] (⚰️..⚱️) coffin..funeral urn
+26BD..26BE ; Emoji # E0.6 [2] (⚽..⚾) soccer ball..baseball
+26C4..26C5 ; Emoji # E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud
+26C8 ; Emoji # E0.7 [1] (⛈️) cloud with lightning and rain
+26CE ; Emoji # E0.6 [1] (⛎) Ophiuchus
+26CF ; Emoji # E0.7 [1] (⛏️) pick
+26D1 ; Emoji # E0.7 [1] (⛑️) rescue worker’s helmet
+26D3 ; Emoji # E0.7 [1] (⛓️) chains
+26D4 ; Emoji # E0.6 [1] (⛔) no entry
+26E9 ; Emoji # E0.7 [1] (⛩️) shinto shrine
+26EA ; Emoji # E0.6 [1] (⛪) church
+26F0..26F1 ; Emoji # E0.7 [2] (⛰️..⛱️) mountain..umbrella on ground
+26F2..26F3 ; Emoji # E0.6 [2] (⛲..⛳) fountain..flag in hole
+26F4 ; Emoji # E0.7 [1] (⛴️) ferry
+26F5 ; Emoji # E0.6 [1] (⛵) sailboat
+26F7..26F9 ; Emoji # E0.7 [3] (⛷️..⛹️) skier..person bouncing ball
+26FA ; Emoji # E0.6 [1] (⛺) tent
+26FD ; Emoji # E0.6 [1] (⛽) fuel pump
+2702 ; Emoji # E0.6 [1] (✂️) scissors
+2705 ; Emoji # E0.6 [1] (✅) check mark button
+2708..270C ; Emoji # E0.6 [5] (✈️..✌️) airplane..victory hand
+270D ; Emoji # E0.7 [1] (✍️) writing hand
+270F ; Emoji # E0.6 [1] (✏️) pencil
+2712 ; Emoji # E0.6 [1] (✒️) black nib
+2714 ; Emoji # E0.6 [1] (✔️) check mark
+2716 ; Emoji # E0.6 [1] (✖️) multiply
+271D ; Emoji # E0.7 [1] (✝️) latin cross
+2721 ; Emoji # E0.7 [1] (✡️) star of David
+2728 ; Emoji # E0.6 [1] (✨) sparkles
+2733..2734 ; Emoji # E0.6 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star
+2744 ; Emoji # E0.6 [1] (❄️) snowflake
+2747 ; Emoji # E0.6 [1] (❇️) sparkle
+274C ; Emoji # E0.6 [1] (❌) cross mark
+274E ; Emoji # E0.6 [1] (❎) cross mark button
+2753..2755 ; Emoji # E0.6 [3] (❓..❕) red question mark..white exclamation mark
+2757 ; Emoji # E0.6 [1] (❗) red exclamation mark
+2763 ; Emoji # E1.0 [1] (❣️) heart exclamation
+2764 ; Emoji # E0.6 [1] (❤️) red heart
+2795..2797 ; Emoji # E0.6 [3] (➕..➗) plus..divide
+27A1 ; Emoji # E0.6 [1] (➡️) right arrow
+27B0 ; Emoji # E0.6 [1] (➰) curly loop
+27BF ; Emoji # E1.0 [1] (➿) double curly loop
+2934..2935 ; Emoji # E0.6 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down
+2B05..2B07 ; Emoji # E0.6 [3] (⬅️..⬇️) left arrow..down arrow
+2B1B..2B1C ; Emoji # E0.6 [2] (⬛..⬜) black large square..white large square
+2B50 ; Emoji # E0.6 [1] (⭐) star
+2B55 ; Emoji # E0.6 [1] (⭕) hollow red circle
+3030 ; Emoji # E0.6 [1] (〰️) wavy dash
+303D ; Emoji # E0.6 [1] (〽️) part alternation mark
+3297 ; Emoji # E0.6 [1] (㊗️) Japanese “congratulations” button
+3299 ; Emoji # E0.6 [1] (㊙️) Japanese “secret” button
+1F004 ; Emoji # E0.6 [1] (🀄) mahjong red dragon
+1F0CF ; Emoji # E0.6 [1] (🃏) joker
+1F170..1F171 ; Emoji # E0.6 [2] (🅰️..🅱️) A button (blood type)..B button (blood type)
+1F17E..1F17F ; Emoji # E0.6 [2] (🅾️..🅿️) O button (blood type)..P button
+1F18E ; Emoji # E0.6 [1] (🆎) AB button (blood type)
+1F191..1F19A ; Emoji # E0.6 [10] (🆑..🆚) CL button..VS button
+1F1E6..1F1FF ; Emoji # E0.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
+1F201..1F202 ; Emoji # E0.6 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button
+1F21A ; Emoji # E0.6 [1] (🈚) Japanese “free of charge” button
+1F22F ; Emoji # E0.6 [1] (🈯) Japanese “reserved” button
+1F232..1F23A ; Emoji # E0.6 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button
+1F250..1F251 ; Emoji # E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
+1F300..1F30C ; Emoji # E0.6 [13] (🌀..🌌) cyclone..milky way
+1F30D..1F30E ; Emoji # E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas
+1F30F ; Emoji # E0.6 [1] (🌏) globe showing Asia-Australia
+1F310 ; Emoji # E1.0 [1] (🌐) globe with meridians
+1F311 ; Emoji # E0.6 [1] (🌑) new moon
+1F312 ; Emoji # E1.0 [1] (🌒) waxing crescent moon
+1F313..1F315 ; Emoji # E0.6 [3] (🌓..🌕) first quarter moon..full moon
+1F316..1F318 ; Emoji # E1.0 [3] (🌖..🌘) waning gibbous moon..waning crescent moon
+1F319 ; Emoji # E0.6 [1] (🌙) crescent moon
+1F31A ; Emoji # E1.0 [1] (🌚) new moon face
+1F31B ; Emoji # E0.6 [1] (🌛) first quarter moon face
+1F31C ; Emoji # E0.7 [1] (🌜) last quarter moon face
+1F31D..1F31E ; Emoji # E1.0 [2] (🌝..🌞) full moon face..sun with face
+1F31F..1F320 ; Emoji # E0.6 [2] (🌟..🌠) glowing star..shooting star
+1F321 ; Emoji # E0.7 [1] (🌡️) thermometer
+1F324..1F32C ; Emoji # E0.7 [9] (🌤️..🌬️) sun behind small cloud..wind face
+1F32D..1F32F ; Emoji # E1.0 [3] (🌭..🌯) hot dog..burrito
+1F330..1F331 ; Emoji # E0.6 [2] (🌰..🌱) chestnut..seedling
+1F332..1F333 ; Emoji # E1.0 [2] (🌲..🌳) evergreen tree..deciduous tree
+1F334..1F335 ; Emoji # E0.6 [2] (🌴..🌵) palm tree..cactus
+1F336 ; Emoji # E0.7 [1] (🌶️) hot pepper
+1F337..1F34A ; Emoji # E0.6 [20] (🌷..🍊) tulip..tangerine
+1F34B ; Emoji # E1.0 [1] (🍋) lemon
+1F34C..1F34F ; Emoji # E0.6 [4] (🍌..🍏) banana..green apple
+1F350 ; Emoji # E1.0 [1] (🍐) pear
+1F351..1F37B ; Emoji # E0.6 [43] (🍑..🍻) peach..clinking beer mugs
+1F37C ; Emoji # E1.0 [1] (🍼) baby bottle
+1F37D ; Emoji # E0.7 [1] (🍽️) fork and knife with plate
+1F37E..1F37F ; Emoji # E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn
+1F380..1F393 ; Emoji # E0.6 [20] (🎀..🎓) ribbon..graduation cap
+1F396..1F397 ; Emoji # E0.7 [2] (🎖️..🎗️) military medal..reminder ribbon
+1F399..1F39B ; Emoji # E0.7 [3] (🎙️..🎛️) studio microphone..control knobs
+1F39E..1F39F ; Emoji # E0.7 [2] (🎞️..🎟️) film frames..admission tickets
+1F3A0..1F3C4 ; Emoji # E0.6 [37] (🎠..🏄) carousel horse..person surfing
+1F3C5 ; Emoji # E1.0 [1] (🏅) sports medal
+1F3C6 ; Emoji # E0.6 [1] (🏆) trophy
+1F3C7 ; Emoji # E1.0 [1] (🏇) horse racing
+1F3C8 ; Emoji # E0.6 [1] (🏈) american football
+1F3C9 ; Emoji # E1.0 [1] (🏉) rugby football
+1F3CA ; Emoji # E0.6 [1] (🏊) person swimming
+1F3CB..1F3CE ; Emoji # E0.7 [4] (🏋️..🏎️) person lifting weights..racing car
+1F3CF..1F3D3 ; Emoji # E1.0 [5] (🏏..🏓) cricket game..ping pong
+1F3D4..1F3DF ; Emoji # E0.7 [12] (🏔️..🏟️) snow-capped mountain..stadium
+1F3E0..1F3E3 ; Emoji # E0.6 [4] (🏠..🏣) house..Japanese post office
+1F3E4 ; Emoji # E1.0 [1] (🏤) post office
+1F3E5..1F3F0 ; Emoji # E0.6 [12] (🏥..🏰) hospital..castle
+1F3F3 ; Emoji # E0.7 [1] (🏳️) white flag
+1F3F4 ; Emoji # E1.0 [1] (🏴) black flag
+1F3F5 ; Emoji # E0.7 [1] (🏵️) rosette
+1F3F7 ; Emoji # E0.7 [1] (🏷️) label
+1F3F8..1F407 ; Emoji # E1.0 [16] (🏸..🐇) badminton..rabbit
+1F408 ; Emoji # E0.7 [1] (🐈) cat
+1F409..1F40B ; Emoji # E1.0 [3] (🐉..🐋) dragon..whale
+1F40C..1F40E ; Emoji # E0.6 [3] (🐌..🐎) snail..horse
+1F40F..1F410 ; Emoji # E1.0 [2] (🐏..🐐) ram..goat
+1F411..1F412 ; Emoji # E0.6 [2] (🐑..🐒) ewe..monkey
+1F413 ; Emoji # E1.0 [1] (🐓) rooster
+1F414 ; Emoji # E0.6 [1] (🐔) chicken
+1F415 ; Emoji # E0.7 [1] (🐕) dog
+1F416 ; Emoji # E1.0 [1] (🐖) pig
+1F417..1F429 ; Emoji # E0.6 [19] (🐗..🐩) boar..poodle
+1F42A ; Emoji # E1.0 [1] (🐪) camel
+1F42B..1F43E ; Emoji # E0.6 [20] (🐫..🐾) two-hump camel..paw prints
+1F43F ; Emoji # E0.7 [1] (🐿️) chipmunk
+1F440 ; Emoji # E0.6 [1] (👀) eyes
+1F441 ; Emoji # E0.7 [1] (👁️) eye
+1F442..1F464 ; Emoji # E0.6 [35] (👂..👤) ear..bust in silhouette
+1F465 ; Emoji # E1.0 [1] (👥) busts in silhouette
+1F466..1F46B ; Emoji # E0.6 [6] (👦..👫) boy..woman and man holding hands
+1F46C..1F46D ; Emoji # E1.0 [2] (👬..👭) men holding hands..women holding hands
+1F46E..1F4AC ; Emoji # E0.6 [63] (👮..💬) police officer..speech balloon
+1F4AD ; Emoji # E1.0 [1] (💭) thought balloon
+1F4AE..1F4B5 ; Emoji # E0.6 [8] (💮..💵) white flower..dollar banknote
+1F4B6..1F4B7 ; Emoji # E1.0 [2] (💶..💷) euro banknote..pound banknote
+1F4B8..1F4EB ; Emoji # E0.6 [52] (💸..📫) money with wings..closed mailbox with raised flag
+1F4EC..1F4ED ; Emoji # E0.7 [2] (📬..📭) open mailbox with raised flag..open mailbox with lowered flag
+1F4EE ; Emoji # E0.6 [1] (📮) postbox
+1F4EF ; Emoji # E1.0 [1] (📯) postal horn
+1F4F0..1F4F4 ; Emoji # E0.6 [5] (📰..📴) newspaper..mobile phone off
+1F4F5 ; Emoji # E1.0 [1] (📵) no mobile phones
+1F4F6..1F4F7 ; Emoji # E0.6 [2] (📶..📷) antenna bars..camera
+1F4F8 ; Emoji # E1.0 [1] (📸) camera with flash
+1F4F9..1F4FC ; Emoji # E0.6 [4] (📹..📼) video camera..videocassette
+1F4FD ; Emoji # E0.7 [1] (📽️) film projector
+1F4FF..1F502 ; Emoji # E1.0 [4] (📿..🔂) prayer beads..repeat single button
+1F503 ; Emoji # E0.6 [1] (🔃) clockwise vertical arrows
+1F504..1F507 ; Emoji # E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker
+1F508 ; Emoji # E0.7 [1] (🔈) speaker low volume
+1F509 ; Emoji # E1.0 [1] (🔉) speaker medium volume
+1F50A..1F514 ; Emoji # E0.6 [11] (🔊..🔔) speaker high volume..bell
+1F515 ; Emoji # E1.0 [1] (🔕) bell with slash
+1F516..1F52B ; Emoji # E0.6 [22] (🔖..🔫) bookmark..water pistol
+1F52C..1F52D ; Emoji # E1.0 [2] (🔬..🔭) microscope..telescope
+1F52E..1F53D ; Emoji # E0.6 [16] (🔮..🔽) crystal ball..downwards button
+1F549..1F54A ; Emoji # E0.7 [2] (🕉️..🕊️) om..dove
+1F54B..1F54E ; Emoji # E1.0 [4] (🕋..🕎) kaaba..menorah
+1F550..1F55B ; Emoji # E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock
+1F55C..1F567 ; Emoji # E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty
+1F56F..1F570 ; Emoji # E0.7 [2] (🕯️..🕰️) candle..mantelpiece clock
+1F573..1F579 ; Emoji # E0.7 [7] (🕳️..🕹️) hole..joystick
+1F57A ; Emoji # E3.0 [1] (🕺) man dancing
+1F587 ; Emoji # E0.7 [1] (🖇️) linked paperclips
+1F58A..1F58D ; Emoji # E0.7 [4] (🖊️..🖍️) pen..crayon
+1F590 ; Emoji # E0.7 [1] (🖐️) hand with fingers splayed
+1F595..1F596 ; Emoji # E1.0 [2] (🖕..🖖) middle finger..vulcan salute
+1F5A4 ; Emoji # E3.0 [1] (🖤) black heart
+1F5A5 ; Emoji # E0.7 [1] (🖥️) desktop computer
+1F5A8 ; Emoji # E0.7 [1] (🖨️) printer
+1F5B1..1F5B2 ; Emoji # E0.7 [2] (🖱️..🖲️) computer mouse..trackball
+1F5BC ; Emoji # E0.7 [1] (🖼️) framed picture
+1F5C2..1F5C4 ; Emoji # E0.7 [3] (🗂️..🗄️) card index dividers..file cabinet
+1F5D1..1F5D3 ; Emoji # E0.7 [3] (🗑️..🗓️) wastebasket..spiral calendar
+1F5DC..1F5DE ; Emoji # E0.7 [3] (🗜️..🗞️) clamp..rolled-up newspaper
+1F5E1 ; Emoji # E0.7 [1] (🗡️) dagger
+1F5E3 ; Emoji # E0.7 [1] (🗣️) speaking head
+1F5E8 ; Emoji # E2.0 [1] (🗨️) left speech bubble
+1F5EF ; Emoji # E0.7 [1] (🗯️) right anger bubble
+1F5F3 ; Emoji # E0.7 [1] (🗳️) ballot box with ballot
+1F5FA ; Emoji # E0.7 [1] (🗺️) world map
+1F5FB..1F5FF ; Emoji # E0.6 [5] (🗻..🗿) mount fuji..moai
+1F600 ; Emoji # E1.0 [1] (😀) grinning face
+1F601..1F606 ; Emoji # E0.6 [6] (😁..😆) beaming face with smiling eyes..grinning squinting face
+1F607..1F608 ; Emoji # E1.0 [2] (😇..😈) smiling face with halo..smiling face with horns
+1F609..1F60D ; Emoji # E0.6 [5] (😉..😍) winking face..smiling face with heart-eyes
+1F60E ; Emoji # E1.0 [1] (😎) smiling face with sunglasses
+1F60F ; Emoji # E0.6 [1] (😏) smirking face
+1F610 ; Emoji # E0.7 [1] (😐) neutral face
+1F611 ; Emoji # E1.0 [1] (😑) expressionless face
+1F612..1F614 ; Emoji # E0.6 [3] (😒..😔) unamused face..pensive face
+1F615 ; Emoji # E1.0 [1] (😕) confused face
+1F616 ; Emoji # E0.6 [1] (😖) confounded face
+1F617 ; Emoji # E1.0 [1] (😗) kissing face
+1F618 ; Emoji # E0.6 [1] (😘) face blowing a kiss
+1F619 ; Emoji # E1.0 [1] (😙) kissing face with smiling eyes
+1F61A ; Emoji # E0.6 [1] (😚) kissing face with closed eyes
+1F61B ; Emoji # E1.0 [1] (😛) face with tongue
+1F61C..1F61E ; Emoji # E0.6 [3] (😜..😞) winking face with tongue..disappointed face
+1F61F ; Emoji # E1.0 [1] (😟) worried face
+1F620..1F625 ; Emoji # E0.6 [6] (😠..😥) angry face..sad but relieved face
+1F626..1F627 ; Emoji # E1.0 [2] (😦..😧) frowning face with open mouth..anguished face
+1F628..1F62B ; Emoji # E0.6 [4] (😨..😫) fearful face..tired face
+1F62C ; Emoji # E1.0 [1] (😬) grimacing face
+1F62D ; Emoji # E0.6 [1] (😭) loudly crying face
+1F62E..1F62F ; Emoji # E1.0 [2] (😮..😯) face with open mouth..hushed face
+1F630..1F633 ; Emoji # E0.6 [4] (😰..😳) anxious face with sweat..flushed face
+1F634 ; Emoji # E1.0 [1] (😴) sleeping face
+1F635 ; Emoji # E0.6 [1] (😵) face with crossed-out eyes
+1F636 ; Emoji # E1.0 [1] (😶) face without mouth
+1F637..1F640 ; Emoji # E0.6 [10] (😷..🙀) face with medical mask..weary cat
+1F641..1F644 ; Emoji # E1.0 [4] (🙁..🙄) slightly frowning face..face with rolling eyes
+1F645..1F64F ; Emoji # E0.6 [11] (🙅..🙏) person gesturing NO..folded hands
+1F680 ; Emoji # E0.6 [1] (🚀) rocket
+1F681..1F682 ; Emoji # E1.0 [2] (🚁..🚂) helicopter..locomotive
+1F683..1F685 ; Emoji # E0.6 [3] (🚃..🚅) railway car..bullet train
+1F686 ; Emoji # E1.0 [1] (🚆) train
+1F687 ; Emoji # E0.6 [1] (🚇) metro
+1F688 ; Emoji # E1.0 [1] (🚈) light rail
+1F689 ; Emoji # E0.6 [1] (🚉) station
+1F68A..1F68B ; Emoji # E1.0 [2] (🚊..🚋) tram..tram car
+1F68C ; Emoji # E0.6 [1] (🚌) bus
+1F68D ; Emoji # E0.7 [1] (🚍) oncoming bus
+1F68E ; Emoji # E1.0 [1] (🚎) trolleybus
+1F68F ; Emoji # E0.6 [1] (🚏) bus stop
+1F690 ; Emoji # E1.0 [1] (🚐) minibus
+1F691..1F693 ; Emoji # E0.6 [3] (🚑..🚓) ambulance..police car
+1F694 ; Emoji # E0.7 [1] (🚔) oncoming police car
+1F695 ; Emoji # E0.6 [1] (🚕) taxi
+1F696 ; Emoji # E1.0 [1] (🚖) oncoming taxi
+1F697 ; Emoji # E0.6 [1] (🚗) automobile
+1F698 ; Emoji # E0.7 [1] (🚘) oncoming automobile
+1F699..1F69A ; Emoji # E0.6 [2] (🚙..🚚) sport utility vehicle..delivery truck
+1F69B..1F6A1 ; Emoji # E1.0 [7] (🚛..🚡) articulated lorry..aerial tramway
+1F6A2 ; Emoji # E0.6 [1] (🚢) ship
+1F6A3 ; Emoji # E1.0 [1] (🚣) person rowing boat
+1F6A4..1F6A5 ; Emoji # E0.6 [2] (🚤..🚥) speedboat..horizontal traffic light
+1F6A6 ; Emoji # E1.0 [1] (🚦) vertical traffic light
+1F6A7..1F6AD ; Emoji # E0.6 [7] (🚧..🚭) construction..no smoking
+1F6AE..1F6B1 ; Emoji # E1.0 [4] (🚮..🚱) litter in bin sign..non-potable water
+1F6B2 ; Emoji # E0.6 [1] (🚲) bicycle
+1F6B3..1F6B5 ; Emoji # E1.0 [3] (🚳..🚵) no bicycles..person mountain biking
+1F6B6 ; Emoji # E0.6 [1] (🚶) person walking
+1F6B7..1F6B8 ; Emoji # E1.0 [2] (🚷..🚸) no pedestrians..children crossing
+1F6B9..1F6BE ; Emoji # E0.6 [6] (🚹..🚾) men’s room..water closet
+1F6BF ; Emoji # E1.0 [1] (🚿) shower
+1F6C0 ; Emoji # E0.6 [1] (🛀) person taking bath
+1F6C1..1F6C5 ; Emoji # E1.0 [5] (🛁..🛅) bathtub..left luggage
+1F6CB ; Emoji # E0.7 [1] (🛋️) couch and lamp
+1F6CC ; Emoji # E1.0 [1] (🛌) person in bed
+1F6CD..1F6CF ; Emoji # E0.7 [3] (🛍️..🛏️) shopping bags..bed
+1F6D0 ; Emoji # E1.0 [1] (🛐) place of worship
+1F6D1..1F6D2 ; Emoji # E3.0 [2] (🛑..🛒) stop sign..shopping cart
+1F6D5 ; Emoji # E12.0 [1] (🛕) hindu temple
+1F6D6..1F6D7 ; Emoji # E13.0 [2] (🛖..🛗) hut..elevator
+1F6DC ; Emoji # E15.0 [1] (🛜) wireless
+1F6DD..1F6DF ; Emoji # E14.0 [3] (🛝..🛟) playground slide..ring buoy
+1F6E0..1F6E5 ; Emoji # E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat
+1F6E9 ; Emoji # E0.7 [1] (🛩️) small airplane
+1F6EB..1F6EC ; Emoji # E1.0 [2] (🛫..🛬) airplane departure..airplane arrival
+1F6F0 ; Emoji # E0.7 [1] (🛰️) satellite
+1F6F3 ; Emoji # E0.7 [1] (🛳️) passenger ship
+1F6F4..1F6F6 ; Emoji # E3.0 [3] (🛴..🛶) kick scooter..canoe
+1F6F7..1F6F8 ; Emoji # E5.0 [2] (🛷..🛸) sled..flying saucer
+1F6F9 ; Emoji # E11.0 [1] (🛹) skateboard
+1F6FA ; Emoji # E12.0 [1] (🛺) auto rickshaw
+1F6FB..1F6FC ; Emoji # E13.0 [2] (🛻..🛼) pickup truck..roller skate
+1F7E0..1F7EB ; Emoji # E12.0 [12] (🟠..🟫) orange circle..brown square
+1F7F0 ; Emoji # E14.0 [1] (🟰) heavy equals sign
+1F90C ; Emoji # E13.0 [1] (🤌) pinched fingers
+1F90D..1F90F ; Emoji # E12.0 [3] (🤍..🤏) white heart..pinching hand
+1F910..1F918 ; Emoji # E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
+1F919..1F91E ; Emoji # E3.0 [6] (🤙..🤞) call me hand..crossed fingers
+1F91F ; Emoji # E5.0 [1] (🤟) love-you gesture
+1F920..1F927 ; Emoji # E3.0 [8] (🤠..🤧) cowboy hat face..sneezing face
+1F928..1F92F ; Emoji # E5.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
+1F930 ; Emoji # E3.0 [1] (🤰) pregnant woman
+1F931..1F932 ; Emoji # E5.0 [2] (🤱..🤲) breast-feeding..palms up together
+1F933..1F93A ; Emoji # E3.0 [8] (🤳..🤺) selfie..person fencing
+1F93C..1F93E ; Emoji # E3.0 [3] (🤼..🤾) people wrestling..person playing handball
+1F93F ; Emoji # E12.0 [1] (🤿) diving mask
+1F940..1F945 ; Emoji # E3.0 [6] (🥀..🥅) wilted flower..goal net
+1F947..1F94B ; Emoji # E3.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
+1F94C ; Emoji # E5.0 [1] (🥌) curling stone
+1F94D..1F94F ; Emoji # E11.0 [3] (🥍..🥏) lacrosse..flying disc
+1F950..1F95E ; Emoji # E3.0 [15] (🥐..🥞) croissant..pancakes
+1F95F..1F96B ; Emoji # E5.0 [13] (🥟..🥫) dumpling..canned food
+1F96C..1F970 ; Emoji # E11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
+1F971 ; Emoji # E12.0 [1] (🥱) yawning face
+1F972 ; Emoji # E13.0 [1] (🥲) smiling face with tear
+1F973..1F976 ; Emoji # E11.0 [4] (🥳..🥶) partying face..cold face
+1F977..1F978 ; Emoji # E13.0 [2] (🥷..🥸) ninja..disguised face
+1F979 ; Emoji # E14.0 [1] (🥹) face holding back tears
+1F97A ; Emoji # E11.0 [1] (🥺) pleading face
+1F97B ; Emoji # E12.0 [1] (🥻) sari
+1F97C..1F97F ; Emoji # E11.0 [4] (🥼..🥿) lab coat..flat shoe
+1F980..1F984 ; Emoji # E1.0 [5] (🦀..🦄) crab..unicorn
+1F985..1F991 ; Emoji # E3.0 [13] (🦅..🦑) eagle..squid
+1F992..1F997 ; Emoji # E5.0 [6] (🦒..🦗) giraffe..cricket
+1F998..1F9A2 ; Emoji # E11.0 [11] (🦘..🦢) kangaroo..swan
+1F9A3..1F9A4 ; Emoji # E13.0 [2] (🦣..🦤) mammoth..dodo
+1F9A5..1F9AA ; Emoji # E12.0 [6] (🦥..🦪) sloth..oyster
+1F9AB..1F9AD ; Emoji # E13.0 [3] (🦫..🦭) beaver..seal
+1F9AE..1F9AF ; Emoji # E12.0 [2] (🦮..🦯) guide dog..white cane
+1F9B0..1F9B9 ; Emoji # E11.0 [10] (🦰..🦹) red hair..supervillain
+1F9BA..1F9BF ; Emoji # E12.0 [6] (🦺..🦿) safety vest..mechanical leg
+1F9C0 ; Emoji # E1.0 [1] (🧀) cheese wedge
+1F9C1..1F9C2 ; Emoji # E11.0 [2] (🧁..🧂) cupcake..salt
+1F9C3..1F9CA ; Emoji # E12.0 [8] (🧃..🧊) beverage box..ice
+1F9CB ; Emoji # E13.0 [1] (🧋) bubble tea
+1F9CC ; Emoji # E14.0 [1] (🧌) troll
+1F9CD..1F9CF ; Emoji # E12.0 [3] (🧍..🧏) person standing..deaf person
+1F9D0..1F9E6 ; Emoji # E5.0 [23] (🧐..🧦) face with monocle..socks
+1F9E7..1F9FF ; Emoji # E11.0 [25] (🧧..🧿) red envelope..nazar amulet
+1FA70..1FA73 ; Emoji # E12.0 [4] (🩰..🩳) ballet shoes..shorts
+1FA74 ; Emoji # E13.0 [1] (🩴) thong sandal
+1FA75..1FA77 ; Emoji # E15.0 [3] (🩵..🩷) light blue heart..pink heart
+1FA78..1FA7A ; Emoji # E12.0 [3] (🩸..🩺) drop of blood..stethoscope
+1FA7B..1FA7C ; Emoji # E14.0 [2] (🩻..🩼) x-ray..crutch
+1FA80..1FA82 ; Emoji # E12.0 [3] (🪀..🪂) yo-yo..parachute
+1FA83..1FA86 ; Emoji # E13.0 [4] (🪃..🪆) boomerang..nesting dolls
+1FA87..1FA88 ; Emoji # E15.0 [2] (🪇..🪈) maracas..flute
+1FA89 ; Emoji # E16.0 [1] () harp
+1FA8F ; Emoji # E16.0 [1] () shovel
+1FA90..1FA95 ; Emoji # E12.0 [6] (🪐..🪕) ringed planet..banjo
+1FA96..1FAA8 ; Emoji # E13.0 [19] (🪖..🪨) military helmet..rock
+1FAA9..1FAAC ; Emoji # E14.0 [4] (🪩..🪬) mirror ball..hamsa
+1FAAD..1FAAF ; Emoji # E15.0 [3] (🪭..🪯) folding hand fan..khanda
+1FAB0..1FAB6 ; Emoji # E13.0 [7] (🪰..🪶) fly..feather
+1FAB7..1FABA ; Emoji # E14.0 [4] (🪷..🪺) lotus..nest with eggs
+1FABB..1FABD ; Emoji # E15.0 [3] (🪻..🪽) hyacinth..wing
+1FABE ; Emoji # E16.0 [1] () leafless tree
+1FABF ; Emoji # E15.0 [1] (🪿) goose
+1FAC0..1FAC2 ; Emoji # E13.0 [3] (🫀..🫂) anatomical heart..people hugging
+1FAC3..1FAC5 ; Emoji # E14.0 [3] (🫃..🫅) pregnant man..person with crown
+1FAC6 ; Emoji # E16.0 [1] () fingerprint
+1FACE..1FACF ; Emoji # E15.0 [2] (🫎..🫏) moose..donkey
+1FAD0..1FAD6 ; Emoji # E13.0 [7] (🫐..🫖) blueberries..teapot
+1FAD7..1FAD9 ; Emoji # E14.0 [3] (🫗..🫙) pouring liquid..jar
+1FADA..1FADB ; Emoji # E15.0 [2] (🫚..🫛) ginger root..pea pod
+1FADC ; Emoji # E16.0 [1] () root vegetable
+1FADF ; Emoji # E16.0 [1] () splatter
+1FAE0..1FAE7 ; Emoji # E14.0 [8] (🫠..🫧) melting face..bubbles
+1FAE8 ; Emoji # E15.0 [1] (🫨) shaking face
+1FAE9 ; Emoji # E16.0 [1] () face with bags under eyes
+1FAF0..1FAF6 ; Emoji # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands
+1FAF7..1FAF8 ; Emoji # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand
+
+# Total elements: 1431
+
+# ================================================
+
+# All omitted code points have Emoji_Presentation=No
+
+231A..231B ; Emoji_Presentation # E0.6 [2] (⌚..⌛) watch..hourglass done
+23E9..23EC ; Emoji_Presentation # E0.6 [4] (⏩..⏬) fast-forward button..fast down button
+23F0 ; Emoji_Presentation # E0.6 [1] (⏰) alarm clock
+23F3 ; Emoji_Presentation # E0.6 [1] (⏳) hourglass not done
+25FD..25FE ; Emoji_Presentation # E0.6 [2] (◽..◾) white medium-small square..black medium-small square
+2614..2615 ; Emoji_Presentation # E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage
+2648..2653 ; Emoji_Presentation # E0.6 [12] (♈..♓) Aries..Pisces
+267F ; Emoji_Presentation # E0.6 [1] (♿) wheelchair symbol
+2693 ; Emoji_Presentation # E0.6 [1] (⚓) anchor
+26A1 ; Emoji_Presentation # E0.6 [1] (⚡) high voltage
+26AA..26AB ; Emoji_Presentation # E0.6 [2] (⚪..⚫) white circle..black circle
+26BD..26BE ; Emoji_Presentation # E0.6 [2] (⚽..⚾) soccer ball..baseball
+26C4..26C5 ; Emoji_Presentation # E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud
+26CE ; Emoji_Presentation # E0.6 [1] (⛎) Ophiuchus
+26D4 ; Emoji_Presentation # E0.6 [1] (⛔) no entry
+26EA ; Emoji_Presentation # E0.6 [1] (⛪) church
+26F2..26F3 ; Emoji_Presentation # E0.6 [2] (⛲..⛳) fountain..flag in hole
+26F5 ; Emoji_Presentation # E0.6 [1] (⛵) sailboat
+26FA ; Emoji_Presentation # E0.6 [1] (⛺) tent
+26FD ; Emoji_Presentation # E0.6 [1] (⛽) fuel pump
+2705 ; Emoji_Presentation # E0.6 [1] (✅) check mark button
+270A..270B ; Emoji_Presentation # E0.6 [2] (✊..✋) raised fist..raised hand
+2728 ; Emoji_Presentation # E0.6 [1] (✨) sparkles
+274C ; Emoji_Presentation # E0.6 [1] (❌) cross mark
+274E ; Emoji_Presentation # E0.6 [1] (❎) cross mark button
+2753..2755 ; Emoji_Presentation # E0.6 [3] (❓..❕) red question mark..white exclamation mark
+2757 ; Emoji_Presentation # E0.6 [1] (❗) red exclamation mark
+2795..2797 ; Emoji_Presentation # E0.6 [3] (➕..➗) plus..divide
+27B0 ; Emoji_Presentation # E0.6 [1] (➰) curly loop
+27BF ; Emoji_Presentation # E1.0 [1] (➿) double curly loop
+2B1B..2B1C ; Emoji_Presentation # E0.6 [2] (⬛..⬜) black large square..white large square
+2B50 ; Emoji_Presentation # E0.6 [1] (⭐) star
+2B55 ; Emoji_Presentation # E0.6 [1] (⭕) hollow red circle
+1F004 ; Emoji_Presentation # E0.6 [1] (🀄) mahjong red dragon
+1F0CF ; Emoji_Presentation # E0.6 [1] (🃏) joker
+1F18E ; Emoji_Presentation # E0.6 [1] (🆎) AB button (blood type)
+1F191..1F19A ; Emoji_Presentation # E0.6 [10] (🆑..🆚) CL button..VS button
+1F1E6..1F1FF ; Emoji_Presentation # E0.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
+1F201 ; Emoji_Presentation # E0.6 [1] (🈁) Japanese “here” button
+1F21A ; Emoji_Presentation # E0.6 [1] (🈚) Japanese “free of charge” button
+1F22F ; Emoji_Presentation # E0.6 [1] (🈯) Japanese “reserved” button
+1F232..1F236 ; Emoji_Presentation # E0.6 [5] (🈲..🈶) Japanese “prohibited” button..Japanese “not free of charge” button
+1F238..1F23A ; Emoji_Presentation # E0.6 [3] (🈸..🈺) Japanese “application” button..Japanese “open for business” button
+1F250..1F251 ; Emoji_Presentation # E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
+1F300..1F30C ; Emoji_Presentation # E0.6 [13] (🌀..🌌) cyclone..milky way
+1F30D..1F30E ; Emoji_Presentation # E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas
+1F30F ; Emoji_Presentation # E0.6 [1] (🌏) globe showing Asia-Australia
+1F310 ; Emoji_Presentation # E1.0 [1] (🌐) globe with meridians
+1F311 ; Emoji_Presentation # E0.6 [1] (🌑) new moon
+1F312 ; Emoji_Presentation # E1.0 [1] (🌒) waxing crescent moon
+1F313..1F315 ; Emoji_Presentation # E0.6 [3] (🌓..🌕) first quarter moon..full moon
+1F316..1F318 ; Emoji_Presentation # E1.0 [3] (🌖..🌘) waning gibbous moon..waning crescent moon
+1F319 ; Emoji_Presentation # E0.6 [1] (🌙) crescent moon
+1F31A ; Emoji_Presentation # E1.0 [1] (🌚) new moon face
+1F31B ; Emoji_Presentation # E0.6 [1] (🌛) first quarter moon face
+1F31C ; Emoji_Presentation # E0.7 [1] (🌜) last quarter moon face
+1F31D..1F31E ; Emoji_Presentation # E1.0 [2] (🌝..🌞) full moon face..sun with face
+1F31F..1F320 ; Emoji_Presentation # E0.6 [2] (🌟..🌠) glowing star..shooting star
+1F32D..1F32F ; Emoji_Presentation # E1.0 [3] (🌭..🌯) hot dog..burrito
+1F330..1F331 ; Emoji_Presentation # E0.6 [2] (🌰..🌱) chestnut..seedling
+1F332..1F333 ; Emoji_Presentation # E1.0 [2] (🌲..🌳) evergreen tree..deciduous tree
+1F334..1F335 ; Emoji_Presentation # E0.6 [2] (🌴..🌵) palm tree..cactus
+1F337..1F34A ; Emoji_Presentation # E0.6 [20] (🌷..🍊) tulip..tangerine
+1F34B ; Emoji_Presentation # E1.0 [1] (🍋) lemon
+1F34C..1F34F ; Emoji_Presentation # E0.6 [4] (🍌..🍏) banana..green apple
+1F350 ; Emoji_Presentation # E1.0 [1] (🍐) pear
+1F351..1F37B ; Emoji_Presentation # E0.6 [43] (🍑..🍻) peach..clinking beer mugs
+1F37C ; Emoji_Presentation # E1.0 [1] (🍼) baby bottle
+1F37E..1F37F ; Emoji_Presentation # E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn
+1F380..1F393 ; Emoji_Presentation # E0.6 [20] (🎀..🎓) ribbon..graduation cap
+1F3A0..1F3C4 ; Emoji_Presentation # E0.6 [37] (🎠..🏄) carousel horse..person surfing
+1F3C5 ; Emoji_Presentation # E1.0 [1] (🏅) sports medal
+1F3C6 ; Emoji_Presentation # E0.6 [1] (🏆) trophy
+1F3C7 ; Emoji_Presentation # E1.0 [1] (🏇) horse racing
+1F3C8 ; Emoji_Presentation # E0.6 [1] (🏈) american football
+1F3C9 ; Emoji_Presentation # E1.0 [1] (🏉) rugby football
+1F3CA ; Emoji_Presentation # E0.6 [1] (🏊) person swimming
+1F3CF..1F3D3 ; Emoji_Presentation # E1.0 [5] (🏏..🏓) cricket game..ping pong
+1F3E0..1F3E3 ; Emoji_Presentation # E0.6 [4] (🏠..🏣) house..Japanese post office
+1F3E4 ; Emoji_Presentation # E1.0 [1] (🏤) post office
+1F3E5..1F3F0 ; Emoji_Presentation # E0.6 [12] (🏥..🏰) hospital..castle
+1F3F4 ; Emoji_Presentation # E1.0 [1] (🏴) black flag
+1F3F8..1F407 ; Emoji_Presentation # E1.0 [16] (🏸..🐇) badminton..rabbit
+1F408 ; Emoji_Presentation # E0.7 [1] (🐈) cat
+1F409..1F40B ; Emoji_Presentation # E1.0 [3] (🐉..🐋) dragon..whale
+1F40C..1F40E ; Emoji_Presentation # E0.6 [3] (🐌..🐎) snail..horse
+1F40F..1F410 ; Emoji_Presentation # E1.0 [2] (🐏..🐐) ram..goat
+1F411..1F412 ; Emoji_Presentation # E0.6 [2] (🐑..🐒) ewe..monkey
+1F413 ; Emoji_Presentation # E1.0 [1] (🐓) rooster
+1F414 ; Emoji_Presentation # E0.6 [1] (🐔) chicken
+1F415 ; Emoji_Presentation # E0.7 [1] (🐕) dog
+1F416 ; Emoji_Presentation # E1.0 [1] (🐖) pig
+1F417..1F429 ; Emoji_Presentation # E0.6 [19] (🐗..🐩) boar..poodle
+1F42A ; Emoji_Presentation # E1.0 [1] (🐪) camel
+1F42B..1F43E ; Emoji_Presentation # E0.6 [20] (🐫..🐾) two-hump camel..paw prints
+1F440 ; Emoji_Presentation # E0.6 [1] (👀) eyes
+1F442..1F464 ; Emoji_Presentation # E0.6 [35] (👂..👤) ear..bust in silhouette
+1F465 ; Emoji_Presentation # E1.0 [1] (👥) busts in silhouette
+1F466..1F46B ; Emoji_Presentation # E0.6 [6] (👦..👫) boy..woman and man holding hands
+1F46C..1F46D ; Emoji_Presentation # E1.0 [2] (👬..👭) men holding hands..women holding hands
+1F46E..1F4AC ; Emoji_Presentation # E0.6 [63] (👮..💬) police officer..speech balloon
+1F4AD ; Emoji_Presentation # E1.0 [1] (💭) thought balloon
+1F4AE..1F4B5 ; Emoji_Presentation # E0.6 [8] (💮..💵) white flower..dollar banknote
+1F4B6..1F4B7 ; Emoji_Presentation # E1.0 [2] (💶..💷) euro banknote..pound banknote
+1F4B8..1F4EB ; Emoji_Presentation # E0.6 [52] (💸..📫) money with wings..closed mailbox with raised flag
+1F4EC..1F4ED ; Emoji_Presentation # E0.7 [2] (📬..📭) open mailbox with raised flag..open mailbox with lowered flag
+1F4EE ; Emoji_Presentation # E0.6 [1] (📮) postbox
+1F4EF ; Emoji_Presentation # E1.0 [1] (📯) postal horn
+1F4F0..1F4F4 ; Emoji_Presentation # E0.6 [5] (📰..📴) newspaper..mobile phone off
+1F4F5 ; Emoji_Presentation # E1.0 [1] (📵) no mobile phones
+1F4F6..1F4F7 ; Emoji_Presentation # E0.6 [2] (📶..📷) antenna bars..camera
+1F4F8 ; Emoji_Presentation # E1.0 [1] (📸) camera with flash
+1F4F9..1F4FC ; Emoji_Presentation # E0.6 [4] (📹..📼) video camera..videocassette
+1F4FF..1F502 ; Emoji_Presentation # E1.0 [4] (📿..🔂) prayer beads..repeat single button
+1F503 ; Emoji_Presentation # E0.6 [1] (🔃) clockwise vertical arrows
+1F504..1F507 ; Emoji_Presentation # E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker
+1F508 ; Emoji_Presentation # E0.7 [1] (🔈) speaker low volume
+1F509 ; Emoji_Presentation # E1.0 [1] (🔉) speaker medium volume
+1F50A..1F514 ; Emoji_Presentation # E0.6 [11] (🔊..🔔) speaker high volume..bell
+1F515 ; Emoji_Presentation # E1.0 [1] (🔕) bell with slash
+1F516..1F52B ; Emoji_Presentation # E0.6 [22] (🔖..🔫) bookmark..water pistol
+1F52C..1F52D ; Emoji_Presentation # E1.0 [2] (🔬..🔭) microscope..telescope
+1F52E..1F53D ; Emoji_Presentation # E0.6 [16] (🔮..🔽) crystal ball..downwards button
+1F54B..1F54E ; Emoji_Presentation # E1.0 [4] (🕋..🕎) kaaba..menorah
+1F550..1F55B ; Emoji_Presentation # E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock
+1F55C..1F567 ; Emoji_Presentation # E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty
+1F57A ; Emoji_Presentation # E3.0 [1] (🕺) man dancing
+1F595..1F596 ; Emoji_Presentation # E1.0 [2] (🖕..🖖) middle finger..vulcan salute
+1F5A4 ; Emoji_Presentation # E3.0 [1] (🖤) black heart
+1F5FB..1F5FF ; Emoji_Presentation # E0.6 [5] (🗻..🗿) mount fuji..moai
+1F600 ; Emoji_Presentation # E1.0 [1] (😀) grinning face
+1F601..1F606 ; Emoji_Presentation # E0.6 [6] (😁..😆) beaming face with smiling eyes..grinning squinting face
+1F607..1F608 ; Emoji_Presentation # E1.0 [2] (😇..😈) smiling face with halo..smiling face with horns
+1F609..1F60D ; Emoji_Presentation # E0.6 [5] (😉..😍) winking face..smiling face with heart-eyes
+1F60E ; Emoji_Presentation # E1.0 [1] (😎) smiling face with sunglasses
+1F60F ; Emoji_Presentation # E0.6 [1] (😏) smirking face
+1F610 ; Emoji_Presentation # E0.7 [1] (😐) neutral face
+1F611 ; Emoji_Presentation # E1.0 [1] (😑) expressionless face
+1F612..1F614 ; Emoji_Presentation # E0.6 [3] (😒..😔) unamused face..pensive face
+1F615 ; Emoji_Presentation # E1.0 [1] (😕) confused face
+1F616 ; Emoji_Presentation # E0.6 [1] (😖) confounded face
+1F617 ; Emoji_Presentation # E1.0 [1] (😗) kissing face
+1F618 ; Emoji_Presentation # E0.6 [1] (😘) face blowing a kiss
+1F619 ; Emoji_Presentation # E1.0 [1] (😙) kissing face with smiling eyes
+1F61A ; Emoji_Presentation # E0.6 [1] (😚) kissing face with closed eyes
+1F61B ; Emoji_Presentation # E1.0 [1] (😛) face with tongue
+1F61C..1F61E ; Emoji_Presentation # E0.6 [3] (😜..😞) winking face with tongue..disappointed face
+1F61F ; Emoji_Presentation # E1.0 [1] (😟) worried face
+1F620..1F625 ; Emoji_Presentation # E0.6 [6] (😠..😥) angry face..sad but relieved face
+1F626..1F627 ; Emoji_Presentation # E1.0 [2] (😦..😧) frowning face with open mouth..anguished face
+1F628..1F62B ; Emoji_Presentation # E0.6 [4] (😨..😫) fearful face..tired face
+1F62C ; Emoji_Presentation # E1.0 [1] (😬) grimacing face
+1F62D ; Emoji_Presentation # E0.6 [1] (😭) loudly crying face
+1F62E..1F62F ; Emoji_Presentation # E1.0 [2] (😮..😯) face with open mouth..hushed face
+1F630..1F633 ; Emoji_Presentation # E0.6 [4] (😰..😳) anxious face with sweat..flushed face
+1F634 ; Emoji_Presentation # E1.0 [1] (😴) sleeping face
+1F635 ; Emoji_Presentation # E0.6 [1] (😵) face with crossed-out eyes
+1F636 ; Emoji_Presentation # E1.0 [1] (😶) face without mouth
+1F637..1F640 ; Emoji_Presentation # E0.6 [10] (😷..🙀) face with medical mask..weary cat
+1F641..1F644 ; Emoji_Presentation # E1.0 [4] (🙁..🙄) slightly frowning face..face with rolling eyes
+1F645..1F64F ; Emoji_Presentation # E0.6 [11] (🙅..🙏) person gesturing NO..folded hands
+1F680 ; Emoji_Presentation # E0.6 [1] (🚀) rocket
+1F681..1F682 ; Emoji_Presentation # E1.0 [2] (🚁..🚂) helicopter..locomotive
+1F683..1F685 ; Emoji_Presentation # E0.6 [3] (🚃..🚅) railway car..bullet train
+1F686 ; Emoji_Presentation # E1.0 [1] (🚆) train
+1F687 ; Emoji_Presentation # E0.6 [1] (🚇) metro
+1F688 ; Emoji_Presentation # E1.0 [1] (🚈) light rail
+1F689 ; Emoji_Presentation # E0.6 [1] (🚉) station
+1F68A..1F68B ; Emoji_Presentation # E1.0 [2] (🚊..🚋) tram..tram car
+1F68C ; Emoji_Presentation # E0.6 [1] (🚌) bus
+1F68D ; Emoji_Presentation # E0.7 [1] (🚍) oncoming bus
+1F68E ; Emoji_Presentation # E1.0 [1] (🚎) trolleybus
+1F68F ; Emoji_Presentation # E0.6 [1] (🚏) bus stop
+1F690 ; Emoji_Presentation # E1.0 [1] (🚐) minibus
+1F691..1F693 ; Emoji_Presentation # E0.6 [3] (🚑..🚓) ambulance..police car
+1F694 ; Emoji_Presentation # E0.7 [1] (🚔) oncoming police car
+1F695 ; Emoji_Presentation # E0.6 [1] (🚕) taxi
+1F696 ; Emoji_Presentation # E1.0 [1] (🚖) oncoming taxi
+1F697 ; Emoji_Presentation # E0.6 [1] (🚗) automobile
+1F698 ; Emoji_Presentation # E0.7 [1] (🚘) oncoming automobile
+1F699..1F69A ; Emoji_Presentation # E0.6 [2] (🚙..🚚) sport utility vehicle..delivery truck
+1F69B..1F6A1 ; Emoji_Presentation # E1.0 [7] (🚛..🚡) articulated lorry..aerial tramway
+1F6A2 ; Emoji_Presentation # E0.6 [1] (🚢) ship
+1F6A3 ; Emoji_Presentation # E1.0 [1] (🚣) person rowing boat
+1F6A4..1F6A5 ; Emoji_Presentation # E0.6 [2] (🚤..🚥) speedboat..horizontal traffic light
+1F6A6 ; Emoji_Presentation # E1.0 [1] (🚦) vertical traffic light
+1F6A7..1F6AD ; Emoji_Presentation # E0.6 [7] (🚧..🚭) construction..no smoking
+1F6AE..1F6B1 ; Emoji_Presentation # E1.0 [4] (🚮..🚱) litter in bin sign..non-potable water
+1F6B2 ; Emoji_Presentation # E0.6 [1] (🚲) bicycle
+1F6B3..1F6B5 ; Emoji_Presentation # E1.0 [3] (🚳..🚵) no bicycles..person mountain biking
+1F6B6 ; Emoji_Presentation # E0.6 [1] (🚶) person walking
+1F6B7..1F6B8 ; Emoji_Presentation # E1.0 [2] (🚷..🚸) no pedestrians..children crossing
+1F6B9..1F6BE ; Emoji_Presentation # E0.6 [6] (🚹..🚾) men’s room..water closet
+1F6BF ; Emoji_Presentation # E1.0 [1] (🚿) shower
+1F6C0 ; Emoji_Presentation # E0.6 [1] (🛀) person taking bath
+1F6C1..1F6C5 ; Emoji_Presentation # E1.0 [5] (🛁..🛅) bathtub..left luggage
+1F6CC ; Emoji_Presentation # E1.0 [1] (🛌) person in bed
+1F6D0 ; Emoji_Presentation # E1.0 [1] (🛐) place of worship
+1F6D1..1F6D2 ; Emoji_Presentation # E3.0 [2] (🛑..🛒) stop sign..shopping cart
+1F6D5 ; Emoji_Presentation # E12.0 [1] (🛕) hindu temple
+1F6D6..1F6D7 ; Emoji_Presentation # E13.0 [2] (🛖..🛗) hut..elevator
+1F6DC ; Emoji_Presentation # E15.0 [1] (🛜) wireless
+1F6DD..1F6DF ; Emoji_Presentation # E14.0 [3] (🛝..🛟) playground slide..ring buoy
+1F6EB..1F6EC ; Emoji_Presentation # E1.0 [2] (🛫..🛬) airplane departure..airplane arrival
+1F6F4..1F6F6 ; Emoji_Presentation # E3.0 [3] (🛴..🛶) kick scooter..canoe
+1F6F7..1F6F8 ; Emoji_Presentation # E5.0 [2] (🛷..🛸) sled..flying saucer
+1F6F9 ; Emoji_Presentation # E11.0 [1] (🛹) skateboard
+1F6FA ; Emoji_Presentation # E12.0 [1] (🛺) auto rickshaw
+1F6FB..1F6FC ; Emoji_Presentation # E13.0 [2] (🛻..🛼) pickup truck..roller skate
+1F7E0..1F7EB ; Emoji_Presentation # E12.0 [12] (🟠..🟫) orange circle..brown square
+1F7F0 ; Emoji_Presentation # E14.0 [1] (🟰) heavy equals sign
+1F90C ; Emoji_Presentation # E13.0 [1] (🤌) pinched fingers
+1F90D..1F90F ; Emoji_Presentation # E12.0 [3] (🤍..🤏) white heart..pinching hand
+1F910..1F918 ; Emoji_Presentation # E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
+1F919..1F91E ; Emoji_Presentation # E3.0 [6] (🤙..🤞) call me hand..crossed fingers
+1F91F ; Emoji_Presentation # E5.0 [1] (🤟) love-you gesture
+1F920..1F927 ; Emoji_Presentation # E3.0 [8] (🤠..🤧) cowboy hat face..sneezing face
+1F928..1F92F ; Emoji_Presentation # E5.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
+1F930 ; Emoji_Presentation # E3.0 [1] (🤰) pregnant woman
+1F931..1F932 ; Emoji_Presentation # E5.0 [2] (🤱..🤲) breast-feeding..palms up together
+1F933..1F93A ; Emoji_Presentation # E3.0 [8] (🤳..🤺) selfie..person fencing
+1F93C..1F93E ; Emoji_Presentation # E3.0 [3] (🤼..🤾) people wrestling..person playing handball
+1F93F ; Emoji_Presentation # E12.0 [1] (🤿) diving mask
+1F940..1F945 ; Emoji_Presentation # E3.0 [6] (🥀..🥅) wilted flower..goal net
+1F947..1F94B ; Emoji_Presentation # E3.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
+1F94C ; Emoji_Presentation # E5.0 [1] (🥌) curling stone
+1F94D..1F94F ; Emoji_Presentation # E11.0 [3] (🥍..🥏) lacrosse..flying disc
+1F950..1F95E ; Emoji_Presentation # E3.0 [15] (🥐..🥞) croissant..pancakes
+1F95F..1F96B ; Emoji_Presentation # E5.0 [13] (🥟..🥫) dumpling..canned food
+1F96C..1F970 ; Emoji_Presentation # E11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
+1F971 ; Emoji_Presentation # E12.0 [1] (🥱) yawning face
+1F972 ; Emoji_Presentation # E13.0 [1] (🥲) smiling face with tear
+1F973..1F976 ; Emoji_Presentation # E11.0 [4] (🥳..🥶) partying face..cold face
+1F977..1F978 ; Emoji_Presentation # E13.0 [2] (🥷..🥸) ninja..disguised face
+1F979 ; Emoji_Presentation # E14.0 [1] (🥹) face holding back tears
+1F97A ; Emoji_Presentation # E11.0 [1] (🥺) pleading face
+1F97B ; Emoji_Presentation # E12.0 [1] (🥻) sari
+1F97C..1F97F ; Emoji_Presentation # E11.0 [4] (🥼..🥿) lab coat..flat shoe
+1F980..1F984 ; Emoji_Presentation # E1.0 [5] (🦀..🦄) crab..unicorn
+1F985..1F991 ; Emoji_Presentation # E3.0 [13] (🦅..🦑) eagle..squid
+1F992..1F997 ; Emoji_Presentation # E5.0 [6] (🦒..🦗) giraffe..cricket
+1F998..1F9A2 ; Emoji_Presentation # E11.0 [11] (🦘..🦢) kangaroo..swan
+1F9A3..1F9A4 ; Emoji_Presentation # E13.0 [2] (🦣..🦤) mammoth..dodo
+1F9A5..1F9AA ; Emoji_Presentation # E12.0 [6] (🦥..🦪) sloth..oyster
+1F9AB..1F9AD ; Emoji_Presentation # E13.0 [3] (🦫..🦭) beaver..seal
+1F9AE..1F9AF ; Emoji_Presentation # E12.0 [2] (🦮..🦯) guide dog..white cane
+1F9B0..1F9B9 ; Emoji_Presentation # E11.0 [10] (🦰..🦹) red hair..supervillain
+1F9BA..1F9BF ; Emoji_Presentation # E12.0 [6] (🦺..🦿) safety vest..mechanical leg
+1F9C0 ; Emoji_Presentation # E1.0 [1] (🧀) cheese wedge
+1F9C1..1F9C2 ; Emoji_Presentation # E11.0 [2] (🧁..🧂) cupcake..salt
+1F9C3..1F9CA ; Emoji_Presentation # E12.0 [8] (🧃..🧊) beverage box..ice
+1F9CB ; Emoji_Presentation # E13.0 [1] (🧋) bubble tea
+1F9CC ; Emoji_Presentation # E14.0 [1] (🧌) troll
+1F9CD..1F9CF ; Emoji_Presentation # E12.0 [3] (🧍..🧏) person standing..deaf person
+1F9D0..1F9E6 ; Emoji_Presentation # E5.0 [23] (🧐..🧦) face with monocle..socks
+1F9E7..1F9FF ; Emoji_Presentation # E11.0 [25] (🧧..🧿) red envelope..nazar amulet
+1FA70..1FA73 ; Emoji_Presentation # E12.0 [4] (🩰..🩳) ballet shoes..shorts
+1FA74 ; Emoji_Presentation # E13.0 [1] (🩴) thong sandal
+1FA75..1FA77 ; Emoji_Presentation # E15.0 [3] (🩵..🩷) light blue heart..pink heart
+1FA78..1FA7A ; Emoji_Presentation # E12.0 [3] (🩸..🩺) drop of blood..stethoscope
+1FA7B..1FA7C ; Emoji_Presentation # E14.0 [2] (🩻..🩼) x-ray..crutch
+1FA80..1FA82 ; Emoji_Presentation # E12.0 [3] (🪀..🪂) yo-yo..parachute
+1FA83..1FA86 ; Emoji_Presentation # E13.0 [4] (🪃..🪆) boomerang..nesting dolls
+1FA87..1FA88 ; Emoji_Presentation # E15.0 [2] (🪇..🪈) maracas..flute
+1FA89 ; Emoji_Presentation # E16.0 [1] () harp
+1FA8F ; Emoji_Presentation # E16.0 [1] () shovel
+1FA90..1FA95 ; Emoji_Presentation # E12.0 [6] (🪐..🪕) ringed planet..banjo
+1FA96..1FAA8 ; Emoji_Presentation # E13.0 [19] (🪖..🪨) military helmet..rock
+1FAA9..1FAAC ; Emoji_Presentation # E14.0 [4] (🪩..🪬) mirror ball..hamsa
+1FAAD..1FAAF ; Emoji_Presentation # E15.0 [3] (🪭..🪯) folding hand fan..khanda
+1FAB0..1FAB6 ; Emoji_Presentation # E13.0 [7] (🪰..🪶) fly..feather
+1FAB7..1FABA ; Emoji_Presentation # E14.0 [4] (🪷..🪺) lotus..nest with eggs
+1FABB..1FABD ; Emoji_Presentation # E15.0 [3] (🪻..🪽) hyacinth..wing
+1FABE ; Emoji_Presentation # E16.0 [1] () leafless tree
+1FABF ; Emoji_Presentation # E15.0 [1] (🪿) goose
+1FAC0..1FAC2 ; Emoji_Presentation # E13.0 [3] (🫀..🫂) anatomical heart..people hugging
+1FAC3..1FAC5 ; Emoji_Presentation # E14.0 [3] (🫃..🫅) pregnant man..person with crown
+1FAC6 ; Emoji_Presentation # E16.0 [1] () fingerprint
+1FACE..1FACF ; Emoji_Presentation # E15.0 [2] (🫎..🫏) moose..donkey
+1FAD0..1FAD6 ; Emoji_Presentation # E13.0 [7] (🫐..🫖) blueberries..teapot
+1FAD7..1FAD9 ; Emoji_Presentation # E14.0 [3] (🫗..🫙) pouring liquid..jar
+1FADA..1FADB ; Emoji_Presentation # E15.0 [2] (🫚..🫛) ginger root..pea pod
+1FADC ; Emoji_Presentation # E16.0 [1] () root vegetable
+1FADF ; Emoji_Presentation # E16.0 [1] () splatter
+1FAE0..1FAE7 ; Emoji_Presentation # E14.0 [8] (🫠..🫧) melting face..bubbles
+1FAE8 ; Emoji_Presentation # E15.0 [1] (🫨) shaking face
+1FAE9 ; Emoji_Presentation # E16.0 [1] () face with bags under eyes
+1FAF0..1FAF6 ; Emoji_Presentation # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands
+1FAF7..1FAF8 ; Emoji_Presentation # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand
+
+# Total elements: 1212
+
+# ================================================
+
+# All omitted code points have Emoji_Modifier=No
+
+1F3FB..1F3FF ; Emoji_Modifier # E1.0 [5] (🏻..🏿) light skin tone..dark skin tone
+
+# Total elements: 5
+
+# ================================================
+
+# All omitted code points have Emoji_Modifier_Base=No
+
+261D ; Emoji_Modifier_Base # E0.6 [1] (☝️) index pointing up
+26F9 ; Emoji_Modifier_Base # E0.7 [1] (⛹️) person bouncing ball
+270A..270C ; Emoji_Modifier_Base # E0.6 [3] (✊..✌️) raised fist..victory hand
+270D ; Emoji_Modifier_Base # E0.7 [1] (✍️) writing hand
+1F385 ; Emoji_Modifier_Base # E0.6 [1] (🎅) Santa Claus
+1F3C2..1F3C4 ; Emoji_Modifier_Base # E0.6 [3] (🏂..🏄) snowboarder..person surfing
+1F3C7 ; Emoji_Modifier_Base # E1.0 [1] (🏇) horse racing
+1F3CA ; Emoji_Modifier_Base # E0.6 [1] (🏊) person swimming
+1F3CB..1F3CC ; Emoji_Modifier_Base # E0.7 [2] (🏋️..🏌️) person lifting weights..person golfing
+1F442..1F443 ; Emoji_Modifier_Base # E0.6 [2] (👂..👃) ear..nose
+1F446..1F450 ; Emoji_Modifier_Base # E0.6 [11] (👆..👐) backhand index pointing up..open hands
+1F466..1F46B ; Emoji_Modifier_Base # E0.6 [6] (👦..👫) boy..woman and man holding hands
+1F46C..1F46D ; Emoji_Modifier_Base # E1.0 [2] (👬..👭) men holding hands..women holding hands
+1F46E..1F478 ; Emoji_Modifier_Base # E0.6 [11] (👮..👸) police officer..princess
+1F47C ; Emoji_Modifier_Base # E0.6 [1] (👼) baby angel
+1F481..1F483 ; Emoji_Modifier_Base # E0.6 [3] (💁..💃) person tipping hand..woman dancing
+1F485..1F487 ; Emoji_Modifier_Base # E0.6 [3] (💅..💇) nail polish..person getting haircut
+1F48F ; Emoji_Modifier_Base # E0.6 [1] (💏) kiss
+1F491 ; Emoji_Modifier_Base # E0.6 [1] (💑) couple with heart
+1F4AA ; Emoji_Modifier_Base # E0.6 [1] (💪) flexed biceps
+1F574..1F575 ; Emoji_Modifier_Base # E0.7 [2] (🕴️..🕵️) person in suit levitating..detective
+1F57A ; Emoji_Modifier_Base # E3.0 [1] (🕺) man dancing
+1F590 ; Emoji_Modifier_Base # E0.7 [1] (🖐️) hand with fingers splayed
+1F595..1F596 ; Emoji_Modifier_Base # E1.0 [2] (🖕..🖖) middle finger..vulcan salute
+1F645..1F647 ; Emoji_Modifier_Base # E0.6 [3] (🙅..🙇) person gesturing NO..person bowing
+1F64B..1F64F ; Emoji_Modifier_Base # E0.6 [5] (🙋..🙏) person raising hand..folded hands
+1F6A3 ; Emoji_Modifier_Base # E1.0 [1] (🚣) person rowing boat
+1F6B4..1F6B5 ; Emoji_Modifier_Base # E1.0 [2] (🚴..🚵) person biking..person mountain biking
+1F6B6 ; Emoji_Modifier_Base # E0.6 [1] (🚶) person walking
+1F6C0 ; Emoji_Modifier_Base # E0.6 [1] (🛀) person taking bath
+1F6CC ; Emoji_Modifier_Base # E1.0 [1] (🛌) person in bed
+1F90C ; Emoji_Modifier_Base # E13.0 [1] (🤌) pinched fingers
+1F90F ; Emoji_Modifier_Base # E12.0 [1] (🤏) pinching hand
+1F918 ; Emoji_Modifier_Base # E1.0 [1] (🤘) sign of the horns
+1F919..1F91E ; Emoji_Modifier_Base # E3.0 [6] (🤙..🤞) call me hand..crossed fingers
+1F91F ; Emoji_Modifier_Base # E5.0 [1] (🤟) love-you gesture
+1F926 ; Emoji_Modifier_Base # E3.0 [1] (🤦) person facepalming
+1F930 ; Emoji_Modifier_Base # E3.0 [1] (🤰) pregnant woman
+1F931..1F932 ; Emoji_Modifier_Base # E5.0 [2] (🤱..🤲) breast-feeding..palms up together
+1F933..1F939 ; Emoji_Modifier_Base # E3.0 [7] (🤳..🤹) selfie..person juggling
+1F93C..1F93E ; Emoji_Modifier_Base # E3.0 [3] (🤼..🤾) people wrestling..person playing handball
+1F977 ; Emoji_Modifier_Base # E13.0 [1] (🥷) ninja
+1F9B5..1F9B6 ; Emoji_Modifier_Base # E11.0 [2] (🦵..🦶) leg..foot
+1F9B8..1F9B9 ; Emoji_Modifier_Base # E11.0 [2] (🦸..🦹) superhero..supervillain
+1F9BB ; Emoji_Modifier_Base # E12.0 [1] (🦻) ear with hearing aid
+1F9CD..1F9CF ; Emoji_Modifier_Base # E12.0 [3] (🧍..🧏) person standing..deaf person
+1F9D1..1F9DD ; Emoji_Modifier_Base # E5.0 [13] (🧑..🧝) person..elf
+1FAC3..1FAC5 ; Emoji_Modifier_Base # E14.0 [3] (🫃..🫅) pregnant man..person with crown
+1FAF0..1FAF6 ; Emoji_Modifier_Base # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands
+1FAF7..1FAF8 ; Emoji_Modifier_Base # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand
+
+# Total elements: 134
+
+# ================================================
+
+# All omitted code points have Emoji_Component=No
+
+0023 ; Emoji_Component # E0.0 [1] (#️) hash sign
+002A ; Emoji_Component # E0.0 [1] (*️) asterisk
+0030..0039 ; Emoji_Component # E0.0 [10] (0️..9️) digit zero..digit nine
+200D ; Emoji_Component # E0.0 [1] () zero width joiner
+20E3 ; Emoji_Component # E0.0 [1] (⃣) combining enclosing keycap
+FE0F ; Emoji_Component # E0.0 [1] () VARIATION SELECTOR-16
+1F1E6..1F1FF ; Emoji_Component # E0.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
+1F3FB..1F3FF ; Emoji_Component # E1.0 [5] (🏻..🏿) light skin tone..dark skin tone
+1F9B0..1F9B3 ; Emoji_Component # E11.0 [4] (🦰..🦳) red hair..white hair
+E0020..E007F ; Emoji_Component # E0.0 [96] (..) tag space..cancel tag
+
+# Total elements: 146
+
+# ================================================
+
+# All omitted code points have Extended_Pictographic=No
+
+00A9 ; Extended_Pictographic# E0.6 [1] (©️) copyright
+00AE ; Extended_Pictographic# E0.6 [1] (®️) registered
+203C ; Extended_Pictographic# E0.6 [1] (‼️) double exclamation mark
+2049 ; Extended_Pictographic# E0.6 [1] (⁉️) exclamation question mark
+2122 ; Extended_Pictographic# E0.6 [1] (™️) trade mark
+2139 ; Extended_Pictographic# E0.6 [1] (ℹ️) information
+2194..2199 ; Extended_Pictographic# E0.6 [6] (↔️..↙️) left-right arrow..down-left arrow
+21A9..21AA ; Extended_Pictographic# E0.6 [2] (↩️..↪️) right arrow curving left..left arrow curving right
+231A..231B ; Extended_Pictographic# E0.6 [2] (⌚..⌛) watch..hourglass done
+2328 ; Extended_Pictographic# E1.0 [1] (⌨️) keyboard
+2388 ; Extended_Pictographic# E0.0 [1] (⎈) HELM SYMBOL
+23CF ; Extended_Pictographic# E1.0 [1] (⏏️) eject button
+23E9..23EC ; Extended_Pictographic# E0.6 [4] (⏩..⏬) fast-forward button..fast down button
+23ED..23EE ; Extended_Pictographic# E0.7 [2] (⏭️..⏮️) next track button..last track button
+23EF ; Extended_Pictographic# E1.0 [1] (⏯️) play or pause button
+23F0 ; Extended_Pictographic# E0.6 [1] (⏰) alarm clock
+23F1..23F2 ; Extended_Pictographic# E1.0 [2] (⏱️..⏲️) stopwatch..timer clock
+23F3 ; Extended_Pictographic# E0.6 [1] (⏳) hourglass not done
+23F8..23FA ; Extended_Pictographic# E0.7 [3] (⏸️..⏺️) pause button..record button
+24C2 ; Extended_Pictographic# E0.6 [1] (Ⓜ️) circled M
+25AA..25AB ; Extended_Pictographic# E0.6 [2] (▪️..▫️) black small square..white small square
+25B6 ; Extended_Pictographic# E0.6 [1] (▶️) play button
+25C0 ; Extended_Pictographic# E0.6 [1] (◀️) reverse button
+25FB..25FE ; Extended_Pictographic# E0.6 [4] (◻️..◾) white medium square..black medium-small square
+2600..2601 ; Extended_Pictographic# E0.6 [2] (☀️..☁️) sun..cloud
+2602..2603 ; Extended_Pictographic# E0.7 [2] (☂️..☃️) umbrella..snowman
+2604 ; Extended_Pictographic# E1.0 [1] (☄️) comet
+2605 ; Extended_Pictographic# E0.0 [1] (★) BLACK STAR
+2607..260D ; Extended_Pictographic# E0.0 [7] (☇..☍) LIGHTNING..OPPOSITION
+260E ; Extended_Pictographic# E0.6 [1] (☎️) telephone
+260F..2610 ; Extended_Pictographic# E0.0 [2] (☏..☐) WHITE TELEPHONE..BALLOT BOX
+2611 ; Extended_Pictographic# E0.6 [1] (☑️) check box with check
+2612 ; Extended_Pictographic# E0.0 [1] (☒) BALLOT BOX WITH X
+2614..2615 ; Extended_Pictographic# E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage
+2616..2617 ; Extended_Pictographic# E0.0 [2] (☖..☗) WHITE SHOGI PIECE..BLACK SHOGI PIECE
+2618 ; Extended_Pictographic# E1.0 [1] (☘️) shamrock
+2619..261C ; Extended_Pictographic# E0.0 [4] (☙..☜) REVERSED ROTATED FLORAL HEART BULLET..WHITE LEFT POINTING INDEX
+261D ; Extended_Pictographic# E0.6 [1] (☝️) index pointing up
+261E..261F ; Extended_Pictographic# E0.0 [2] (☞..☟) WHITE RIGHT POINTING INDEX..WHITE DOWN POINTING INDEX
+2620 ; Extended_Pictographic# E1.0 [1] (☠️) skull and crossbones
+2621 ; Extended_Pictographic# E0.0 [1] (☡) CAUTION SIGN
+2622..2623 ; Extended_Pictographic# E1.0 [2] (☢️..☣️) radioactive..biohazard
+2624..2625 ; Extended_Pictographic# E0.0 [2] (☤..☥) CADUCEUS..ANKH
+2626 ; Extended_Pictographic# E1.0 [1] (☦️) orthodox cross
+2627..2629 ; Extended_Pictographic# E0.0 [3] (☧..☩) CHI RHO..CROSS OF JERUSALEM
+262A ; Extended_Pictographic# E0.7 [1] (☪️) star and crescent
+262B..262D ; Extended_Pictographic# E0.0 [3] (☫..☭) FARSI SYMBOL..HAMMER AND SICKLE
+262E ; Extended_Pictographic# E1.0 [1] (☮️) peace symbol
+262F ; Extended_Pictographic# E0.7 [1] (☯️) yin yang
+2630..2637 ; Extended_Pictographic# E0.0 [8] (☰..☷) TRIGRAM FOR HEAVEN..TRIGRAM FOR EARTH
+2638..2639 ; Extended_Pictographic# E0.7 [2] (☸️..☹️) wheel of dharma..frowning face
+263A ; Extended_Pictographic# E0.6 [1] (☺️) smiling face
+263B..263F ; Extended_Pictographic# E0.0 [5] (☻..☿) BLACK SMILING FACE..MERCURY
+2640 ; Extended_Pictographic# E4.0 [1] (♀️) female sign
+2641 ; Extended_Pictographic# E0.0 [1] (♁) EARTH
+2642 ; Extended_Pictographic# E4.0 [1] (♂️) male sign
+2643..2647 ; Extended_Pictographic# E0.0 [5] (♃..♇) JUPITER..PLUTO
+2648..2653 ; Extended_Pictographic# E0.6 [12] (♈..♓) Aries..Pisces
+2654..265E ; Extended_Pictographic# E0.0 [11] (♔..♞) WHITE CHESS KING..BLACK CHESS KNIGHT
+265F ; Extended_Pictographic# E11.0 [1] (♟️) chess pawn
+2660 ; Extended_Pictographic# E0.6 [1] (♠️) spade suit
+2661..2662 ; Extended_Pictographic# E0.0 [2] (♡..♢) WHITE HEART SUIT..WHITE DIAMOND SUIT
+2663 ; Extended_Pictographic# E0.6 [1] (♣️) club suit
+2664 ; Extended_Pictographic# E0.0 [1] (♤) WHITE SPADE SUIT
+2665..2666 ; Extended_Pictographic# E0.6 [2] (♥️..♦️) heart suit..diamond suit
+2667 ; Extended_Pictographic# E0.0 [1] (♧) WHITE CLUB SUIT
+2668 ; Extended_Pictographic# E0.6 [1] (♨️) hot springs
+2669..267A ; Extended_Pictographic# E0.0 [18] (♩..♺) QUARTER NOTE..RECYCLING SYMBOL FOR GENERIC MATERIALS
+267B ; Extended_Pictographic# E0.6 [1] (♻️) recycling symbol
+267C..267D ; Extended_Pictographic# E0.0 [2] (♼..♽) RECYCLED PAPER SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL
+267E ; Extended_Pictographic# E11.0 [1] (♾️) infinity
+267F ; Extended_Pictographic# E0.6 [1] (♿) wheelchair symbol
+2680..2685 ; Extended_Pictographic# E0.0 [6] (⚀..⚅) DIE FACE-1..DIE FACE-6
+2690..2691 ; Extended_Pictographic# E0.0 [2] (⚐..⚑) WHITE FLAG..BLACK FLAG
+2692 ; Extended_Pictographic# E1.0 [1] (⚒️) hammer and pick
+2693 ; Extended_Pictographic# E0.6 [1] (⚓) anchor
+2694 ; Extended_Pictographic# E1.0 [1] (⚔️) crossed swords
+2695 ; Extended_Pictographic# E4.0 [1] (⚕️) medical symbol
+2696..2697 ; Extended_Pictographic# E1.0 [2] (⚖️..⚗️) balance scale..alembic
+2698 ; Extended_Pictographic# E0.0 [1] (⚘) FLOWER
+2699 ; Extended_Pictographic# E1.0 [1] (⚙️) gear
+269A ; Extended_Pictographic# E0.0 [1] (⚚) STAFF OF HERMES
+269B..269C ; Extended_Pictographic# E1.0 [2] (⚛️..⚜️) atom symbol..fleur-de-lis
+269D..269F ; Extended_Pictographic# E0.0 [3] (⚝..⚟) OUTLINED WHITE STAR..THREE LINES CONVERGING LEFT
+26A0..26A1 ; Extended_Pictographic# E0.6 [2] (⚠️..⚡) warning..high voltage
+26A2..26A6 ; Extended_Pictographic# E0.0 [5] (⚢..⚦) DOUBLED FEMALE SIGN..MALE WITH STROKE SIGN
+26A7 ; Extended_Pictographic# E13.0 [1] (⚧️) transgender symbol
+26A8..26A9 ; Extended_Pictographic# E0.0 [2] (⚨..⚩) VERTICAL MALE WITH STROKE SIGN..HORIZONTAL MALE WITH STROKE SIGN
+26AA..26AB ; Extended_Pictographic# E0.6 [2] (⚪..⚫) white circle..black circle
+26AC..26AF ; Extended_Pictographic# E0.0 [4] (⚬..⚯) MEDIUM SMALL WHITE CIRCLE..UNMARRIED PARTNERSHIP SYMBOL
+26B0..26B1 ; Extended_Pictographic# E1.0 [2] (⚰️..⚱️) coffin..funeral urn
+26B2..26BC ; Extended_Pictographic# E0.0 [11] (⚲..⚼) NEUTER..SESQUIQUADRATE
+26BD..26BE ; Extended_Pictographic# E0.6 [2] (⚽..⚾) soccer ball..baseball
+26BF..26C3 ; Extended_Pictographic# E0.0 [5] (⚿..⛃) SQUARED KEY..BLACK DRAUGHTS KING
+26C4..26C5 ; Extended_Pictographic# E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud
+26C6..26C7 ; Extended_Pictographic# E0.0 [2] (⛆..⛇) RAIN..BLACK SNOWMAN
+26C8 ; Extended_Pictographic# E0.7 [1] (⛈️) cloud with lightning and rain
+26C9..26CD ; Extended_Pictographic# E0.0 [5] (⛉..⛍) TURNED WHITE SHOGI PIECE..DISABLED CAR
+26CE ; Extended_Pictographic# E0.6 [1] (⛎) Ophiuchus
+26CF ; Extended_Pictographic# E0.7 [1] (⛏️) pick
+26D0 ; Extended_Pictographic# E0.0 [1] (⛐) CAR SLIDING
+26D1 ; Extended_Pictographic# E0.7 [1] (⛑️) rescue worker’s helmet
+26D2 ; Extended_Pictographic# E0.0 [1] (⛒) CIRCLED CROSSING LANES
+26D3 ; Extended_Pictographic# E0.7 [1] (⛓️) chains
+26D4 ; Extended_Pictographic# E0.6 [1] (⛔) no entry
+26D5..26E8 ; Extended_Pictographic# E0.0 [20] (⛕..⛨) ALTERNATE ONE-WAY LEFT WAY TRAFFIC..BLACK CROSS ON SHIELD
+26E9 ; Extended_Pictographic# E0.7 [1] (⛩️) shinto shrine
+26EA ; Extended_Pictographic# E0.6 [1] (⛪) church
+26EB..26EF ; Extended_Pictographic# E0.0 [5] (⛫..⛯) CASTLE..MAP SYMBOL FOR LIGHTHOUSE
+26F0..26F1 ; Extended_Pictographic# E0.7 [2] (⛰️..⛱️) mountain..umbrella on ground
+26F2..26F3 ; Extended_Pictographic# E0.6 [2] (⛲..⛳) fountain..flag in hole
+26F4 ; Extended_Pictographic# E0.7 [1] (⛴️) ferry
+26F5 ; Extended_Pictographic# E0.6 [1] (⛵) sailboat
+26F6 ; Extended_Pictographic# E0.0 [1] (⛶) SQUARE FOUR CORNERS
+26F7..26F9 ; Extended_Pictographic# E0.7 [3] (⛷️..⛹️) skier..person bouncing ball
+26FA ; Extended_Pictographic# E0.6 [1] (⛺) tent
+26FB..26FC ; Extended_Pictographic# E0.0 [2] (⛻..⛼) JAPANESE BANK SYMBOL..HEADSTONE GRAVEYARD SYMBOL
+26FD ; Extended_Pictographic# E0.6 [1] (⛽) fuel pump
+26FE..2701 ; Extended_Pictographic# E0.0 [4] (⛾..✁) CUP ON BLACK SQUARE..UPPER BLADE SCISSORS
+2702 ; Extended_Pictographic# E0.6 [1] (✂️) scissors
+2703..2704 ; Extended_Pictographic# E0.0 [2] (✃..✄) LOWER BLADE SCISSORS..WHITE SCISSORS
+2705 ; Extended_Pictographic# E0.6 [1] (✅) check mark button
+2708..270C ; Extended_Pictographic# E0.6 [5] (✈️..✌️) airplane..victory hand
+270D ; Extended_Pictographic# E0.7 [1] (✍️) writing hand
+270E ; Extended_Pictographic# E0.0 [1] (✎) LOWER RIGHT PENCIL
+270F ; Extended_Pictographic# E0.6 [1] (✏️) pencil
+2710..2711 ; Extended_Pictographic# E0.0 [2] (✐..✑) UPPER RIGHT PENCIL..WHITE NIB
+2712 ; Extended_Pictographic# E0.6 [1] (✒️) black nib
+2714 ; Extended_Pictographic# E0.6 [1] (✔️) check mark
+2716 ; Extended_Pictographic# E0.6 [1] (✖️) multiply
+271D ; Extended_Pictographic# E0.7 [1] (✝️) latin cross
+2721 ; Extended_Pictographic# E0.7 [1] (✡️) star of David
+2728 ; Extended_Pictographic# E0.6 [1] (✨) sparkles
+2733..2734 ; Extended_Pictographic# E0.6 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star
+2744 ; Extended_Pictographic# E0.6 [1] (❄️) snowflake
+2747 ; Extended_Pictographic# E0.6 [1] (❇️) sparkle
+274C ; Extended_Pictographic# E0.6 [1] (❌) cross mark
+274E ; Extended_Pictographic# E0.6 [1] (❎) cross mark button
+2753..2755 ; Extended_Pictographic# E0.6 [3] (❓..❕) red question mark..white exclamation mark
+2757 ; Extended_Pictographic# E0.6 [1] (❗) red exclamation mark
+2763 ; Extended_Pictographic# E1.0 [1] (❣️) heart exclamation
+2764 ; Extended_Pictographic# E0.6 [1] (❤️) red heart
+2765..2767 ; Extended_Pictographic# E0.0 [3] (❥..❧) ROTATED HEAVY BLACK HEART BULLET..ROTATED FLORAL HEART BULLET
+2795..2797 ; Extended_Pictographic# E0.6 [3] (➕..➗) plus..divide
+27A1 ; Extended_Pictographic# E0.6 [1] (➡️) right arrow
+27B0 ; Extended_Pictographic# E0.6 [1] (➰) curly loop
+27BF ; Extended_Pictographic# E1.0 [1] (➿) double curly loop
+2934..2935 ; Extended_Pictographic# E0.6 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down
+2B05..2B07 ; Extended_Pictographic# E0.6 [3] (⬅️..⬇️) left arrow..down arrow
+2B1B..2B1C ; Extended_Pictographic# E0.6 [2] (⬛..⬜) black large square..white large square
+2B50 ; Extended_Pictographic# E0.6 [1] (⭐) star
+2B55 ; Extended_Pictographic# E0.6 [1] (⭕) hollow red circle
+3030 ; Extended_Pictographic# E0.6 [1] (〰️) wavy dash
+303D ; Extended_Pictographic# E0.6 [1] (〽️) part alternation mark
+3297 ; Extended_Pictographic# E0.6 [1] (㊗️) Japanese “congratulations” button
+3299 ; Extended_Pictographic# E0.6 [1] (㊙️) Japanese “secret” button
+1F000..1F003 ; Extended_Pictographic# E0.0 [4] (🀀..🀃) MAHJONG TILE EAST WIND..MAHJONG TILE NORTH WIND
+1F004 ; Extended_Pictographic# E0.6 [1] (🀄) mahjong red dragon
+1F005..1F0CE ; Extended_Pictographic# E0.0 [202] (🀅..🃎) MAHJONG TILE GREEN DRAGON..PLAYING CARD KING OF DIAMONDS
+1F0CF ; Extended_Pictographic# E0.6 [1] (🃏) joker
+1F0D0..1F0FF ; Extended_Pictographic# E0.0 [48] (..) <reserved-1F0D0>..<reserved-1F0FF>
+1F10D..1F10F ; Extended_Pictographic# E0.0 [3] (🄍..🄏) CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH
+1F12F ; Extended_Pictographic# E0.0 [1] (🄯) COPYLEFT SYMBOL
+1F16C..1F16F ; Extended_Pictographic# E0.0 [4] (🅬..🅯) RAISED MR SIGN..CIRCLED HUMAN FIGURE
+1F170..1F171 ; Extended_Pictographic# E0.6 [2] (🅰️..🅱️) A button (blood type)..B button (blood type)
+1F17E..1F17F ; Extended_Pictographic# E0.6 [2] (🅾️..🅿️) O button (blood type)..P button
+1F18E ; Extended_Pictographic# E0.6 [1] (🆎) AB button (blood type)
+1F191..1F19A ; Extended_Pictographic# E0.6 [10] (🆑..🆚) CL button..VS button
+1F1AD..1F1E5 ; Extended_Pictographic# E0.0 [57] (🆭..) MASK WORK SYMBOL..<reserved-1F1E5>
+1F201..1F202 ; Extended_Pictographic# E0.6 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button
+1F203..1F20F ; Extended_Pictographic# E0.0 [13] (..) <reserved-1F203>..<reserved-1F20F>
+1F21A ; Extended_Pictographic# E0.6 [1] (🈚) Japanese “free of charge” button
+1F22F ; Extended_Pictographic# E0.6 [1] (🈯) Japanese “reserved” button
+1F232..1F23A ; Extended_Pictographic# E0.6 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button
+1F23C..1F23F ; Extended_Pictographic# E0.0 [4] (..) <reserved-1F23C>..<reserved-1F23F>
+1F249..1F24F ; Extended_Pictographic# E0.0 [7] (..) <reserved-1F249>..<reserved-1F24F>
+1F250..1F251 ; Extended_Pictographic# E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
+1F252..1F2FF ; Extended_Pictographic# E0.0 [174] (..) <reserved-1F252>..<reserved-1F2FF>
+1F300..1F30C ; Extended_Pictographic# E0.6 [13] (🌀..🌌) cyclone..milky way
+1F30D..1F30E ; Extended_Pictographic# E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas
+1F30F ; Extended_Pictographic# E0.6 [1] (🌏) globe showing Asia-Australia
+1F310 ; Extended_Pictographic# E1.0 [1] (🌐) globe with meridians
+1F311 ; Extended_Pictographic# E0.6 [1] (🌑) new moon
+1F312 ; Extended_Pictographic# E1.0 [1] (🌒) waxing crescent moon
+1F313..1F315 ; Extended_Pictographic# E0.6 [3] (🌓..🌕) first quarter moon..full moon
+1F316..1F318 ; Extended_Pictographic# E1.0 [3] (🌖..🌘) waning gibbous moon..waning crescent moon
+1F319 ; Extended_Pictographic# E0.6 [1] (🌙) crescent moon
+1F31A ; Extended_Pictographic# E1.0 [1] (🌚) new moon face
+1F31B ; Extended_Pictographic# E0.6 [1] (🌛) first quarter moon face
+1F31C ; Extended_Pictographic# E0.7 [1] (🌜) last quarter moon face
+1F31D..1F31E ; Extended_Pictographic# E1.0 [2] (🌝..🌞) full moon face..sun with face
+1F31F..1F320 ; Extended_Pictographic# E0.6 [2] (🌟..🌠) glowing star..shooting star
+1F321 ; Extended_Pictographic# E0.7 [1] (🌡️) thermometer
+1F322..1F323 ; Extended_Pictographic# E0.0 [2] (🌢..🌣) BLACK DROPLET..WHITE SUN
+1F324..1F32C ; Extended_Pictographic# E0.7 [9] (🌤️..🌬️) sun behind small cloud..wind face
+1F32D..1F32F ; Extended_Pictographic# E1.0 [3] (🌭..🌯) hot dog..burrito
+1F330..1F331 ; Extended_Pictographic# E0.6 [2] (🌰..🌱) chestnut..seedling
+1F332..1F333 ; Extended_Pictographic# E1.0 [2] (🌲..🌳) evergreen tree..deciduous tree
+1F334..1F335 ; Extended_Pictographic# E0.6 [2] (🌴..🌵) palm tree..cactus
+1F336 ; Extended_Pictographic# E0.7 [1] (🌶️) hot pepper
+1F337..1F34A ; Extended_Pictographic# E0.6 [20] (🌷..🍊) tulip..tangerine
+1F34B ; Extended_Pictographic# E1.0 [1] (🍋) lemon
+1F34C..1F34F ; Extended_Pictographic# E0.6 [4] (🍌..🍏) banana..green apple
+1F350 ; Extended_Pictographic# E1.0 [1] (🍐) pear
+1F351..1F37B ; Extended_Pictographic# E0.6 [43] (🍑..🍻) peach..clinking beer mugs
+1F37C ; Extended_Pictographic# E1.0 [1] (🍼) baby bottle
+1F37D ; Extended_Pictographic# E0.7 [1] (🍽️) fork and knife with plate
+1F37E..1F37F ; Extended_Pictographic# E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn
+1F380..1F393 ; Extended_Pictographic# E0.6 [20] (🎀..🎓) ribbon..graduation cap
+1F394..1F395 ; Extended_Pictographic# E0.0 [2] (🎔..🎕) HEART WITH TIP ON THE LEFT..BOUQUET OF FLOWERS
+1F396..1F397 ; Extended_Pictographic# E0.7 [2] (🎖️..🎗️) military medal..reminder ribbon
+1F398 ; Extended_Pictographic# E0.0 [1] (🎘) MUSICAL KEYBOARD WITH JACKS
+1F399..1F39B ; Extended_Pictographic# E0.7 [3] (🎙️..🎛️) studio microphone..control knobs
+1F39C..1F39D ; Extended_Pictographic# E0.0 [2] (🎜..🎝) BEAMED ASCENDING MUSICAL NOTES..BEAMED DESCENDING MUSICAL NOTES
+1F39E..1F39F ; Extended_Pictographic# E0.7 [2] (🎞️..🎟️) film frames..admission tickets
+1F3A0..1F3C4 ; Extended_Pictographic# E0.6 [37] (🎠..🏄) carousel horse..person surfing
+1F3C5 ; Extended_Pictographic# E1.0 [1] (🏅) sports medal
+1F3C6 ; Extended_Pictographic# E0.6 [1] (🏆) trophy
+1F3C7 ; Extended_Pictographic# E1.0 [1] (🏇) horse racing
+1F3C8 ; Extended_Pictographic# E0.6 [1] (🏈) american football
+1F3C9 ; Extended_Pictographic# E1.0 [1] (🏉) rugby football
+1F3CA ; Extended_Pictographic# E0.6 [1] (🏊) person swimming
+1F3CB..1F3CE ; Extended_Pictographic# E0.7 [4] (🏋️..🏎️) person lifting weights..racing car
+1F3CF..1F3D3 ; Extended_Pictographic# E1.0 [5] (🏏..🏓) cricket game..ping pong
+1F3D4..1F3DF ; Extended_Pictographic# E0.7 [12] (🏔️..🏟️) snow-capped mountain..stadium
+1F3E0..1F3E3 ; Extended_Pictographic# E0.6 [4] (🏠..🏣) house..Japanese post office
+1F3E4 ; Extended_Pictographic# E1.0 [1] (🏤) post office
+1F3E5..1F3F0 ; Extended_Pictographic# E0.6 [12] (🏥..🏰) hospital..castle
+1F3F1..1F3F2 ; Extended_Pictographic# E0.0 [2] (🏱..🏲) WHITE PENNANT..BLACK PENNANT
+1F3F3 ; Extended_Pictographic# E0.7 [1] (🏳️) white flag
+1F3F4 ; Extended_Pictographic# E1.0 [1] (🏴) black flag
+1F3F5 ; Extended_Pictographic# E0.7 [1] (🏵️) rosette
+1F3F6 ; Extended_Pictographic# E0.0 [1] (🏶) BLACK ROSETTE
+1F3F7 ; Extended_Pictographic# E0.7 [1] (🏷️) label
+1F3F8..1F3FA ; Extended_Pictographic# E1.0 [3] (🏸..🏺) badminton..amphora
+1F400..1F407 ; Extended_Pictographic# E1.0 [8] (🐀..🐇) rat..rabbit
+1F408 ; Extended_Pictographic# E0.7 [1] (🐈) cat
+1F409..1F40B ; Extended_Pictographic# E1.0 [3] (🐉..🐋) dragon..whale
+1F40C..1F40E ; Extended_Pictographic# E0.6 [3] (🐌..🐎) snail..horse
+1F40F..1F410 ; Extended_Pictographic# E1.0 [2] (🐏..🐐) ram..goat
+1F411..1F412 ; Extended_Pictographic# E0.6 [2] (🐑..🐒) ewe..monkey
+1F413 ; Extended_Pictographic# E1.0 [1] (🐓) rooster
+1F414 ; Extended_Pictographic# E0.6 [1] (🐔) chicken
+1F415 ; Extended_Pictographic# E0.7 [1] (🐕) dog
+1F416 ; Extended_Pictographic# E1.0 [1] (🐖) pig
+1F417..1F429 ; Extended_Pictographic# E0.6 [19] (🐗..🐩) boar..poodle
+1F42A ; Extended_Pictographic# E1.0 [1] (🐪) camel
+1F42B..1F43E ; Extended_Pictographic# E0.6 [20] (🐫..🐾) two-hump camel..paw prints
+1F43F ; Extended_Pictographic# E0.7 [1] (🐿️) chipmunk
+1F440 ; Extended_Pictographic# E0.6 [1] (👀) eyes
+1F441 ; Extended_Pictographic# E0.7 [1] (👁️) eye
+1F442..1F464 ; Extended_Pictographic# E0.6 [35] (👂..👤) ear..bust in silhouette
+1F465 ; Extended_Pictographic# E1.0 [1] (👥) busts in silhouette
+1F466..1F46B ; Extended_Pictographic# E0.6 [6] (👦..👫) boy..woman and man holding hands
+1F46C..1F46D ; Extended_Pictographic# E1.0 [2] (👬..👭) men holding hands..women holding hands
+1F46E..1F4AC ; Extended_Pictographic# E0.6 [63] (👮..💬) police officer..speech balloon
+1F4AD ; Extended_Pictographic# E1.0 [1] (💭) thought balloon
+1F4AE..1F4B5 ; Extended_Pictographic# E0.6 [8] (💮..💵) white flower..dollar banknote
+1F4B6..1F4B7 ; Extended_Pictographic# E1.0 [2] (💶..💷) euro banknote..pound banknote
+1F4B8..1F4EB ; Extended_Pictographic# E0.6 [52] (💸..📫) money with wings..closed mailbox with raised flag
+1F4EC..1F4ED ; Extended_Pictographic# E0.7 [2] (📬..📭) open mailbox with raised flag..open mailbox with lowered flag
+1F4EE ; Extended_Pictographic# E0.6 [1] (📮) postbox
+1F4EF ; Extended_Pictographic# E1.0 [1] (📯) postal horn
+1F4F0..1F4F4 ; Extended_Pictographic# E0.6 [5] (📰..📴) newspaper..mobile phone off
+1F4F5 ; Extended_Pictographic# E1.0 [1] (📵) no mobile phones
+1F4F6..1F4F7 ; Extended_Pictographic# E0.6 [2] (📶..📷) antenna bars..camera
+1F4F8 ; Extended_Pictographic# E1.0 [1] (📸) camera with flash
+1F4F9..1F4FC ; Extended_Pictographic# E0.6 [4] (📹..📼) video camera..videocassette
+1F4FD ; Extended_Pictographic# E0.7 [1] (📽️) film projector
+1F4FE ; Extended_Pictographic# E0.0 [1] (📾) PORTABLE STEREO
+1F4FF..1F502 ; Extended_Pictographic# E1.0 [4] (📿..🔂) prayer beads..repeat single button
+1F503 ; Extended_Pictographic# E0.6 [1] (🔃) clockwise vertical arrows
+1F504..1F507 ; Extended_Pictographic# E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker
+1F508 ; Extended_Pictographic# E0.7 [1] (🔈) speaker low volume
+1F509 ; Extended_Pictographic# E1.0 [1] (🔉) speaker medium volume
+1F50A..1F514 ; Extended_Pictographic# E0.6 [11] (🔊..🔔) speaker high volume..bell
+1F515 ; Extended_Pictographic# E1.0 [1] (🔕) bell with slash
+1F516..1F52B ; Extended_Pictographic# E0.6 [22] (🔖..🔫) bookmark..water pistol
+1F52C..1F52D ; Extended_Pictographic# E1.0 [2] (🔬..🔭) microscope..telescope
+1F52E..1F53D ; Extended_Pictographic# E0.6 [16] (🔮..🔽) crystal ball..downwards button
+1F546..1F548 ; Extended_Pictographic# E0.0 [3] (🕆..🕈) WHITE LATIN CROSS..CELTIC CROSS
+1F549..1F54A ; Extended_Pictographic# E0.7 [2] (🕉️..🕊️) om..dove
+1F54B..1F54E ; Extended_Pictographic# E1.0 [4] (🕋..🕎) kaaba..menorah
+1F54F ; Extended_Pictographic# E0.0 [1] (🕏) BOWL OF HYGIEIA
+1F550..1F55B ; Extended_Pictographic# E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock
+1F55C..1F567 ; Extended_Pictographic# E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty
+1F568..1F56E ; Extended_Pictographic# E0.0 [7] (🕨..🕮) RIGHT SPEAKER..BOOK
+1F56F..1F570 ; Extended_Pictographic# E0.7 [2] (🕯️..🕰️) candle..mantelpiece clock
+1F571..1F572 ; Extended_Pictographic# E0.0 [2] (🕱..🕲) BLACK SKULL AND CROSSBONES..NO PIRACY
+1F573..1F579 ; Extended_Pictographic# E0.7 [7] (🕳️..🕹️) hole..joystick
+1F57A ; Extended_Pictographic# E3.0 [1] (🕺) man dancing
+1F57B..1F586 ; Extended_Pictographic# E0.0 [12] (🕻..🖆) LEFT HAND TELEPHONE RECEIVER..PEN OVER STAMPED ENVELOPE
+1F587 ; Extended_Pictographic# E0.7 [1] (🖇️) linked paperclips
+1F588..1F589 ; Extended_Pictographic# E0.0 [2] (🖈..🖉) BLACK PUSHPIN..LOWER LEFT PENCIL
+1F58A..1F58D ; Extended_Pictographic# E0.7 [4] (🖊️..🖍️) pen..crayon
+1F58E..1F58F ; Extended_Pictographic# E0.0 [2] (🖎..🖏) LEFT WRITING HAND..TURNED OK HAND SIGN
+1F590 ; Extended_Pictographic# E0.7 [1] (🖐️) hand with fingers splayed
+1F591..1F594 ; Extended_Pictographic# E0.0 [4] (🖑..🖔) REVERSED RAISED HAND WITH FINGERS SPLAYED..REVERSED VICTORY HAND
+1F595..1F596 ; Extended_Pictographic# E1.0 [2] (🖕..🖖) middle finger..vulcan salute
+1F597..1F5A3 ; Extended_Pictographic# E0.0 [13] (🖗..🖣) WHITE DOWN POINTING LEFT HAND INDEX..BLACK DOWN POINTING BACKHAND INDEX
+1F5A4 ; Extended_Pictographic# E3.0 [1] (🖤) black heart
+1F5A5 ; Extended_Pictographic# E0.7 [1] (🖥️) desktop computer
+1F5A6..1F5A7 ; Extended_Pictographic# E0.0 [2] (🖦..🖧) KEYBOARD AND MOUSE..THREE NETWORKED COMPUTERS
+1F5A8 ; Extended_Pictographic# E0.7 [1] (🖨️) printer
+1F5A9..1F5B0 ; Extended_Pictographic# E0.0 [8] (🖩..🖰) POCKET CALCULATOR..TWO BUTTON MOUSE
+1F5B1..1F5B2 ; Extended_Pictographic# E0.7 [2] (🖱️..🖲️) computer mouse..trackball
+1F5B3..1F5BB ; Extended_Pictographic# E0.0 [9] (🖳..🖻) OLD PERSONAL COMPUTER..DOCUMENT WITH PICTURE
+1F5BC ; Extended_Pictographic# E0.7 [1] (🖼️) framed picture
+1F5BD..1F5C1 ; Extended_Pictographic# E0.0 [5] (🖽..🗁) FRAME WITH TILES..OPEN FOLDER
+1F5C2..1F5C4 ; Extended_Pictographic# E0.7 [3] (🗂️..🗄️) card index dividers..file cabinet
+1F5C5..1F5D0 ; Extended_Pictographic# E0.0 [12] (🗅..🗐) EMPTY NOTE..PAGES
+1F5D1..1F5D3 ; Extended_Pictographic# E0.7 [3] (🗑️..🗓️) wastebasket..spiral calendar
+1F5D4..1F5DB ; Extended_Pictographic# E0.0 [8] (🗔..🗛) DESKTOP WINDOW..DECREASE FONT SIZE SYMBOL
+1F5DC..1F5DE ; Extended_Pictographic# E0.7 [3] (🗜️..🗞️) clamp..rolled-up newspaper
+1F5DF..1F5E0 ; Extended_Pictographic# E0.0 [2] (🗟..🗠) PAGE WITH CIRCLED TEXT..STOCK CHART
+1F5E1 ; Extended_Pictographic# E0.7 [1] (🗡️) dagger
+1F5E2 ; Extended_Pictographic# E0.0 [1] (🗢) LIPS
+1F5E3 ; Extended_Pictographic# E0.7 [1] (🗣️) speaking head
+1F5E4..1F5E7 ; Extended_Pictographic# E0.0 [4] (🗤..🗧) THREE RAYS ABOVE..THREE RAYS RIGHT
+1F5E8 ; Extended_Pictographic# E2.0 [1] (🗨️) left speech bubble
+1F5E9..1F5EE ; Extended_Pictographic# E0.0 [6] (🗩..🗮) RIGHT SPEECH BUBBLE..LEFT ANGER BUBBLE
+1F5EF ; Extended_Pictographic# E0.7 [1] (🗯️) right anger bubble
+1F5F0..1F5F2 ; Extended_Pictographic# E0.0 [3] (🗰..🗲) MOOD BUBBLE..LIGHTNING MOOD
+1F5F3 ; Extended_Pictographic# E0.7 [1] (🗳️) ballot box with ballot
+1F5F4..1F5F9 ; Extended_Pictographic# E0.0 [6] (🗴..🗹) BALLOT SCRIPT X..BALLOT BOX WITH BOLD CHECK
+1F5FA ; Extended_Pictographic# E0.7 [1] (🗺️) world map
+1F5FB..1F5FF ; Extended_Pictographic# E0.6 [5] (🗻..🗿) mount fuji..moai
+1F600 ; Extended_Pictographic# E1.0 [1] (😀) grinning face
+1F601..1F606 ; Extended_Pictographic# E0.6 [6] (😁..😆) beaming face with smiling eyes..grinning squinting face
+1F607..1F608 ; Extended_Pictographic# E1.0 [2] (😇..😈) smiling face with halo..smiling face with horns
+1F609..1F60D ; Extended_Pictographic# E0.6 [5] (😉..😍) winking face..smiling face with heart-eyes
+1F60E ; Extended_Pictographic# E1.0 [1] (😎) smiling face with sunglasses
+1F60F ; Extended_Pictographic# E0.6 [1] (😏) smirking face
+1F610 ; Extended_Pictographic# E0.7 [1] (😐) neutral face
+1F611 ; Extended_Pictographic# E1.0 [1] (😑) expressionless face
+1F612..1F614 ; Extended_Pictographic# E0.6 [3] (😒..😔) unamused face..pensive face
+1F615 ; Extended_Pictographic# E1.0 [1] (😕) confused face
+1F616 ; Extended_Pictographic# E0.6 [1] (😖) confounded face
+1F617 ; Extended_Pictographic# E1.0 [1] (😗) kissing face
+1F618 ; Extended_Pictographic# E0.6 [1] (😘) face blowing a kiss
+1F619 ; Extended_Pictographic# E1.0 [1] (😙) kissing face with smiling eyes
+1F61A ; Extended_Pictographic# E0.6 [1] (😚) kissing face with closed eyes
+1F61B ; Extended_Pictographic# E1.0 [1] (😛) face with tongue
+1F61C..1F61E ; Extended_Pictographic# E0.6 [3] (😜..😞) winking face with tongue..disappointed face
+1F61F ; Extended_Pictographic# E1.0 [1] (😟) worried face
+1F620..1F625 ; Extended_Pictographic# E0.6 [6] (😠..😥) angry face..sad but relieved face
+1F626..1F627 ; Extended_Pictographic# E1.0 [2] (😦..😧) frowning face with open mouth..anguished face
+1F628..1F62B ; Extended_Pictographic# E0.6 [4] (😨..😫) fearful face..tired face
+1F62C ; Extended_Pictographic# E1.0 [1] (😬) grimacing face
+1F62D ; Extended_Pictographic# E0.6 [1] (😭) loudly crying face
+1F62E..1F62F ; Extended_Pictographic# E1.0 [2] (😮..😯) face with open mouth..hushed face
+1F630..1F633 ; Extended_Pictographic# E0.6 [4] (😰..😳) anxious face with sweat..flushed face
+1F634 ; Extended_Pictographic# E1.0 [1] (😴) sleeping face
+1F635 ; Extended_Pictographic# E0.6 [1] (😵) face with crossed-out eyes
+1F636 ; Extended_Pictographic# E1.0 [1] (😶) face without mouth
+1F637..1F640 ; Extended_Pictographic# E0.6 [10] (😷..🙀) face with medical mask..weary cat
+1F641..1F644 ; Extended_Pictographic# E1.0 [4] (🙁..🙄) slightly frowning face..face with rolling eyes
+1F645..1F64F ; Extended_Pictographic# E0.6 [11] (🙅..🙏) person gesturing NO..folded hands
+1F680 ; Extended_Pictographic# E0.6 [1] (🚀) rocket
+1F681..1F682 ; Extended_Pictographic# E1.0 [2] (🚁..🚂) helicopter..locomotive
+1F683..1F685 ; Extended_Pictographic# E0.6 [3] (🚃..🚅) railway car..bullet train
+1F686 ; Extended_Pictographic# E1.0 [1] (🚆) train
+1F687 ; Extended_Pictographic# E0.6 [1] (🚇) metro
+1F688 ; Extended_Pictographic# E1.0 [1] (🚈) light rail
+1F689 ; Extended_Pictographic# E0.6 [1] (🚉) station
+1F68A..1F68B ; Extended_Pictographic# E1.0 [2] (🚊..🚋) tram..tram car
+1F68C ; Extended_Pictographic# E0.6 [1] (🚌) bus
+1F68D ; Extended_Pictographic# E0.7 [1] (🚍) oncoming bus
+1F68E ; Extended_Pictographic# E1.0 [1] (🚎) trolleybus
+1F68F ; Extended_Pictographic# E0.6 [1] (🚏) bus stop
+1F690 ; Extended_Pictographic# E1.0 [1] (🚐) minibus
+1F691..1F693 ; Extended_Pictographic# E0.6 [3] (🚑..🚓) ambulance..police car
+1F694 ; Extended_Pictographic# E0.7 [1] (🚔) oncoming police car
+1F695 ; Extended_Pictographic# E0.6 [1] (🚕) taxi
+1F696 ; Extended_Pictographic# E1.0 [1] (🚖) oncoming taxi
+1F697 ; Extended_Pictographic# E0.6 [1] (🚗) automobile
+1F698 ; Extended_Pictographic# E0.7 [1] (🚘) oncoming automobile
+1F699..1F69A ; Extended_Pictographic# E0.6 [2] (🚙..🚚) sport utility vehicle..delivery truck
+1F69B..1F6A1 ; Extended_Pictographic# E1.0 [7] (🚛..🚡) articulated lorry..aerial tramway
+1F6A2 ; Extended_Pictographic# E0.6 [1] (🚢) ship
+1F6A3 ; Extended_Pictographic# E1.0 [1] (🚣) person rowing boat
+1F6A4..1F6A5 ; Extended_Pictographic# E0.6 [2] (🚤..🚥) speedboat..horizontal traffic light
+1F6A6 ; Extended_Pictographic# E1.0 [1] (🚦) vertical traffic light
+1F6A7..1F6AD ; Extended_Pictographic# E0.6 [7] (🚧..🚭) construction..no smoking
+1F6AE..1F6B1 ; Extended_Pictographic# E1.0 [4] (🚮..🚱) litter in bin sign..non-potable water
+1F6B2 ; Extended_Pictographic# E0.6 [1] (🚲) bicycle
+1F6B3..1F6B5 ; Extended_Pictographic# E1.0 [3] (🚳..🚵) no bicycles..person mountain biking
+1F6B6 ; Extended_Pictographic# E0.6 [1] (🚶) person walking
+1F6B7..1F6B8 ; Extended_Pictographic# E1.0 [2] (🚷..🚸) no pedestrians..children crossing
+1F6B9..1F6BE ; Extended_Pictographic# E0.6 [6] (🚹..🚾) men’s room..water closet
+1F6BF ; Extended_Pictographic# E1.0 [1] (🚿) shower
+1F6C0 ; Extended_Pictographic# E0.6 [1] (🛀) person taking bath
+1F6C1..1F6C5 ; Extended_Pictographic# E1.0 [5] (🛁..🛅) bathtub..left luggage
+1F6C6..1F6CA ; Extended_Pictographic# E0.0 [5] (🛆..🛊) TRIANGLE WITH ROUNDED CORNERS..GIRLS SYMBOL
+1F6CB ; Extended_Pictographic# E0.7 [1] (🛋️) couch and lamp
+1F6CC ; Extended_Pictographic# E1.0 [1] (🛌) person in bed
+1F6CD..1F6CF ; Extended_Pictographic# E0.7 [3] (🛍️..🛏️) shopping bags..bed
+1F6D0 ; Extended_Pictographic# E1.0 [1] (🛐) place of worship
+1F6D1..1F6D2 ; Extended_Pictographic# E3.0 [2] (🛑..🛒) stop sign..shopping cart
+1F6D3..1F6D4 ; Extended_Pictographic# E0.0 [2] (🛓..🛔) STUPA..PAGODA
+1F6D5 ; Extended_Pictographic# E12.0 [1] (🛕) hindu temple
+1F6D6..1F6D7 ; Extended_Pictographic# E13.0 [2] (🛖..🛗) hut..elevator
+1F6D8..1F6DB ; Extended_Pictographic# E0.0 [4] (..) <reserved-1F6D8>..<reserved-1F6DB>
+1F6DC ; Extended_Pictographic# E15.0 [1] (🛜) wireless
+1F6DD..1F6DF ; Extended_Pictographic# E14.0 [3] (🛝..🛟) playground slide..ring buoy
+1F6E0..1F6E5 ; Extended_Pictographic# E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat
+1F6E6..1F6E8 ; Extended_Pictographic# E0.0 [3] (🛦..🛨) UP-POINTING MILITARY AIRPLANE..UP-POINTING SMALL AIRPLANE
+1F6E9 ; Extended_Pictographic# E0.7 [1] (🛩️) small airplane
+1F6EA ; Extended_Pictographic# E0.0 [1] (🛪) NORTHEAST-POINTING AIRPLANE
+1F6EB..1F6EC ; Extended_Pictographic# E1.0 [2] (🛫..🛬) airplane departure..airplane arrival
+1F6ED..1F6EF ; Extended_Pictographic# E0.0 [3] (..) <reserved-1F6ED>..<reserved-1F6EF>
+1F6F0 ; Extended_Pictographic# E0.7 [1] (🛰️) satellite
+1F6F1..1F6F2 ; Extended_Pictographic# E0.0 [2] (🛱..🛲) ONCOMING FIRE ENGINE..DIESEL LOCOMOTIVE
+1F6F3 ; Extended_Pictographic# E0.7 [1] (🛳️) passenger ship
+1F6F4..1F6F6 ; Extended_Pictographic# E3.0 [3] (🛴..🛶) kick scooter..canoe
+1F6F7..1F6F8 ; Extended_Pictographic# E5.0 [2] (🛷..🛸) sled..flying saucer
+1F6F9 ; Extended_Pictographic# E11.0 [1] (🛹) skateboard
+1F6FA ; Extended_Pictographic# E12.0 [1] (🛺) auto rickshaw
+1F6FB..1F6FC ; Extended_Pictographic# E13.0 [2] (🛻..🛼) pickup truck..roller skate
+1F6FD..1F6FF ; Extended_Pictographic# E0.0 [3] (..) <reserved-1F6FD>..<reserved-1F6FF>
+1F774..1F77F ; Extended_Pictographic# E0.0 [12] (🝴..🝿) LOT OF FORTUNE..ORCUS
+1F7D5..1F7DF ; Extended_Pictographic# E0.0 [11] (🟕..) CIRCLED TRIANGLE..<reserved-1F7DF>
+1F7E0..1F7EB ; Extended_Pictographic# E12.0 [12] (🟠..🟫) orange circle..brown square
+1F7EC..1F7EF ; Extended_Pictographic# E0.0 [4] (..) <reserved-1F7EC>..<reserved-1F7EF>
+1F7F0 ; Extended_Pictographic# E14.0 [1] (🟰) heavy equals sign
+1F7F1..1F7FF ; Extended_Pictographic# E0.0 [15] (..) <reserved-1F7F1>..<reserved-1F7FF>
+1F80C..1F80F ; Extended_Pictographic# E0.0 [4] (..) <reserved-1F80C>..<reserved-1F80F>
+1F848..1F84F ; Extended_Pictographic# E0.0 [8] (..) <reserved-1F848>..<reserved-1F84F>
+1F85A..1F85F ; Extended_Pictographic# E0.0 [6] (..) <reserved-1F85A>..<reserved-1F85F>
+1F888..1F88F ; Extended_Pictographic# E0.0 [8] (..) <reserved-1F888>..<reserved-1F88F>
+1F8AE..1F8FF ; Extended_Pictographic# E0.0 [82] (..) <reserved-1F8AE>..<reserved-1F8FF>
+1F90C ; Extended_Pictographic# E13.0 [1] (🤌) pinched fingers
+1F90D..1F90F ; Extended_Pictographic# E12.0 [3] (🤍..🤏) white heart..pinching hand
+1F910..1F918 ; Extended_Pictographic# E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
+1F919..1F91E ; Extended_Pictographic# E3.0 [6] (🤙..🤞) call me hand..crossed fingers
+1F91F ; Extended_Pictographic# E5.0 [1] (🤟) love-you gesture
+1F920..1F927 ; Extended_Pictographic# E3.0 [8] (🤠..🤧) cowboy hat face..sneezing face
+1F928..1F92F ; Extended_Pictographic# E5.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
+1F930 ; Extended_Pictographic# E3.0 [1] (🤰) pregnant woman
+1F931..1F932 ; Extended_Pictographic# E5.0 [2] (🤱..🤲) breast-feeding..palms up together
+1F933..1F93A ; Extended_Pictographic# E3.0 [8] (🤳..🤺) selfie..person fencing
+1F93C..1F93E ; Extended_Pictographic# E3.0 [3] (🤼..🤾) people wrestling..person playing handball
+1F93F ; Extended_Pictographic# E12.0 [1] (🤿) diving mask
+1F940..1F945 ; Extended_Pictographic# E3.0 [6] (🥀..🥅) wilted flower..goal net
+1F947..1F94B ; Extended_Pictographic# E3.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
+1F94C ; Extended_Pictographic# E5.0 [1] (🥌) curling stone
+1F94D..1F94F ; Extended_Pictographic# E11.0 [3] (🥍..🥏) lacrosse..flying disc
+1F950..1F95E ; Extended_Pictographic# E3.0 [15] (🥐..🥞) croissant..pancakes
+1F95F..1F96B ; Extended_Pictographic# E5.0 [13] (🥟..🥫) dumpling..canned food
+1F96C..1F970 ; Extended_Pictographic# E11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
+1F971 ; Extended_Pictographic# E12.0 [1] (🥱) yawning face
+1F972 ; Extended_Pictographic# E13.0 [1] (🥲) smiling face with tear
+1F973..1F976 ; Extended_Pictographic# E11.0 [4] (🥳..🥶) partying face..cold face
+1F977..1F978 ; Extended_Pictographic# E13.0 [2] (🥷..🥸) ninja..disguised face
+1F979 ; Extended_Pictographic# E14.0 [1] (🥹) face holding back tears
+1F97A ; Extended_Pictographic# E11.0 [1] (🥺) pleading face
+1F97B ; Extended_Pictographic# E12.0 [1] (🥻) sari
+1F97C..1F97F ; Extended_Pictographic# E11.0 [4] (🥼..🥿) lab coat..flat shoe
+1F980..1F984 ; Extended_Pictographic# E1.0 [5] (🦀..🦄) crab..unicorn
+1F985..1F991 ; Extended_Pictographic# E3.0 [13] (🦅..🦑) eagle..squid
+1F992..1F997 ; Extended_Pictographic# E5.0 [6] (🦒..🦗) giraffe..cricket
+1F998..1F9A2 ; Extended_Pictographic# E11.0 [11] (🦘..🦢) kangaroo..swan
+1F9A3..1F9A4 ; Extended_Pictographic# E13.0 [2] (🦣..🦤) mammoth..dodo
+1F9A5..1F9AA ; Extended_Pictographic# E12.0 [6] (🦥..🦪) sloth..oyster
+1F9AB..1F9AD ; Extended_Pictographic# E13.0 [3] (🦫..🦭) beaver..seal
+1F9AE..1F9AF ; Extended_Pictographic# E12.0 [2] (🦮..🦯) guide dog..white cane
+1F9B0..1F9B9 ; Extended_Pictographic# E11.0 [10] (🦰..🦹) red hair..supervillain
+1F9BA..1F9BF ; Extended_Pictographic# E12.0 [6] (🦺..🦿) safety vest..mechanical leg
+1F9C0 ; Extended_Pictographic# E1.0 [1] (🧀) cheese wedge
+1F9C1..1F9C2 ; Extended_Pictographic# E11.0 [2] (🧁..🧂) cupcake..salt
+1F9C3..1F9CA ; Extended_Pictographic# E12.0 [8] (🧃..🧊) beverage box..ice
+1F9CB ; Extended_Pictographic# E13.0 [1] (🧋) bubble tea
+1F9CC ; Extended_Pictographic# E14.0 [1] (🧌) troll
+1F9CD..1F9CF ; Extended_Pictographic# E12.0 [3] (🧍..🧏) person standing..deaf person
+1F9D0..1F9E6 ; Extended_Pictographic# E5.0 [23] (🧐..🧦) face with monocle..socks
+1F9E7..1F9FF ; Extended_Pictographic# E11.0 [25] (🧧..🧿) red envelope..nazar amulet
+1FA00..1FA6F ; Extended_Pictographic# E0.0 [112] (🨀..) NEUTRAL CHESS KING..<reserved-1FA6F>
+1FA70..1FA73 ; Extended_Pictographic# E12.0 [4] (🩰..🩳) ballet shoes..shorts
+1FA74 ; Extended_Pictographic# E13.0 [1] (🩴) thong sandal
+1FA75..1FA77 ; Extended_Pictographic# E15.0 [3] (🩵..🩷) light blue heart..pink heart
+1FA78..1FA7A ; Extended_Pictographic# E12.0 [3] (🩸..🩺) drop of blood..stethoscope
+1FA7B..1FA7C ; Extended_Pictographic# E14.0 [2] (🩻..🩼) x-ray..crutch
+1FA7D..1FA7F ; Extended_Pictographic# E0.0 [3] (..) <reserved-1FA7D>..<reserved-1FA7F>
+1FA80..1FA82 ; Extended_Pictographic# E12.0 [3] (🪀..🪂) yo-yo..parachute
+1FA83..1FA86 ; Extended_Pictographic# E13.0 [4] (🪃..🪆) boomerang..nesting dolls
+1FA87..1FA88 ; Extended_Pictographic# E15.0 [2] (🪇..🪈) maracas..flute
+1FA89 ; Extended_Pictographic# E16.0 [1] () harp
+1FA8A..1FA8E ; Extended_Pictographic# E0.0 [5] (..) <reserved-1FA8A>..<reserved-1FA8E>
+1FA8F ; Extended_Pictographic# E16.0 [1] () shovel
+1FA90..1FA95 ; Extended_Pictographic# E12.0 [6] (🪐..🪕) ringed planet..banjo
+1FA96..1FAA8 ; Extended_Pictographic# E13.0 [19] (🪖..🪨) military helmet..rock
+1FAA9..1FAAC ; Extended_Pictographic# E14.0 [4] (🪩..🪬) mirror ball..hamsa
+1FAAD..1FAAF ; Extended_Pictographic# E15.0 [3] (🪭..🪯) folding hand fan..khanda
+1FAB0..1FAB6 ; Extended_Pictographic# E13.0 [7] (🪰..🪶) fly..feather
+1FAB7..1FABA ; Extended_Pictographic# E14.0 [4] (🪷..🪺) lotus..nest with eggs
+1FABB..1FABD ; Extended_Pictographic# E15.0 [3] (🪻..🪽) hyacinth..wing
+1FABE ; Extended_Pictographic# E16.0 [1] () leafless tree
+1FABF ; Extended_Pictographic# E15.0 [1] (🪿) goose
+1FAC0..1FAC2 ; Extended_Pictographic# E13.0 [3] (🫀..🫂) anatomical heart..people hugging
+1FAC3..1FAC5 ; Extended_Pictographic# E14.0 [3] (🫃..🫅) pregnant man..person with crown
+1FAC6 ; Extended_Pictographic# E16.0 [1] () fingerprint
+1FAC7..1FACD ; Extended_Pictographic# E0.0 [7] (..) <reserved-1FAC7>..<reserved-1FACD>
+1FACE..1FACF ; Extended_Pictographic# E15.0 [2] (🫎..🫏) moose..donkey
+1FAD0..1FAD6 ; Extended_Pictographic# E13.0 [7] (🫐..🫖) blueberries..teapot
+1FAD7..1FAD9 ; Extended_Pictographic# E14.0 [3] (🫗..🫙) pouring liquid..jar
+1FADA..1FADB ; Extended_Pictographic# E15.0 [2] (🫚..🫛) ginger root..pea pod
+1FADC ; Extended_Pictographic# E16.0 [1] () root vegetable
+1FADD..1FADE ; Extended_Pictographic# E0.0 [2] (..) <reserved-1FADD>..<reserved-1FADE>
+1FADF ; Extended_Pictographic# E16.0 [1] () splatter
+1FAE0..1FAE7 ; Extended_Pictographic# E14.0 [8] (🫠..🫧) melting face..bubbles
+1FAE8 ; Extended_Pictographic# E15.0 [1] (🫨) shaking face
+1FAE9 ; Extended_Pictographic# E16.0 [1] () face with bags under eyes
+1FAEA..1FAEF ; Extended_Pictographic# E0.0 [6] (..) <reserved-1FAEA>..<reserved-1FAEF>
+1FAF0..1FAF6 ; Extended_Pictographic# E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands
+1FAF7..1FAF8 ; Extended_Pictographic# E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand
+1FAF9..1FAFF ; Extended_Pictographic# E0.0 [7] (..) <reserved-1FAF9>..<reserved-1FAFF>
+1FC00..1FFFD ; Extended_Pictographic# E0.0[1022] (..) <reserved-1FC00>..<reserved-1FFFD>
+
+# Total elements: 3537
+
+#EOF
diff --git a/pkgs/characters/third_party/Unicode_Consortium/emoji_test.txt b/pkgs/characters/third_party/Unicode_Consortium/emoji_test.txt
new file mode 100644
index 0000000..d77b118
--- /dev/null
+++ b/pkgs/characters/third_party/Unicode_Consortium/emoji_test.txt
@@ -0,0 +1,5331 @@
+# emoji-test.txt
+# Date: 2024-08-14, 23:51:54 GMT
+# © 2024 Unicode®, Inc.
+# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+# For terms of use and license, see https://www.unicode.org/terms_of_use.html
+#
+# Emoji Keyboard/Display Test Data for UTS #51
+# Version: 16.0
+#
+# For documentation and usage, see https://www.unicode.org/reports/tr51
+#
+# This file provides data for testing which emoji forms should be in keyboards and which should also be displayed/processed.
+# Format: code points; status # emoji name
+# Code points — list of one or more hex code points, separated by spaces
+# Status
+# component — an Emoji_Component,
+# excluding Regional_Indicators, ASCII, and non-Emoji.
+# fully-qualified — a fully-qualified emoji (see ED-18 in UTS #51),
+# excluding Emoji_Component
+# minimally-qualified — a minimally-qualified emoji (see ED-18a in UTS #51)
+# unqualified — a unqualified emoji (See ED-19 in UTS #51)
+# Notes:
+# • This includes the emoji components that need emoji presentation (skin tone and hair)
+# when isolated, but omits the components that need not have an emoji
+# presentation when isolated.
+# • The RGI set is covered by the listed fully-qualified emoji.
+# • The listed minimally-qualified and unqualified cover all cases where an
+# element of the RGI set is missing one or more emoji presentation selectors.
+# • The file is in CLDR order, not codepoint order. This is recommended (but not required!) for keyboard palettes.
+# • The groups and subgroups are illustrative. See the Emoji Order chart for more information.
+
+
+# group: Smileys & Emotion
+
+# subgroup: face-smiling
+1F600 ; fully-qualified # 😀 E1.0 grinning face
+1F603 ; fully-qualified # 😃 E0.6 grinning face with big eyes
+1F604 ; fully-qualified # 😄 E0.6 grinning face with smiling eyes
+1F601 ; fully-qualified # 😁 E0.6 beaming face with smiling eyes
+1F606 ; fully-qualified # 😆 E0.6 grinning squinting face
+1F605 ; fully-qualified # 😅 E0.6 grinning face with sweat
+1F923 ; fully-qualified # 🤣 E3.0 rolling on the floor laughing
+1F602 ; fully-qualified # 😂 E0.6 face with tears of joy
+1F642 ; fully-qualified # 🙂 E1.0 slightly smiling face
+1F643 ; fully-qualified # 🙃 E1.0 upside-down face
+1FAE0 ; fully-qualified # 🫠 E14.0 melting face
+1F609 ; fully-qualified # 😉 E0.6 winking face
+1F60A ; fully-qualified # 😊 E0.6 smiling face with smiling eyes
+1F607 ; fully-qualified # 😇 E1.0 smiling face with halo
+
+# subgroup: face-affection
+1F970 ; fully-qualified # 🥰 E11.0 smiling face with hearts
+1F60D ; fully-qualified # 😍 E0.6 smiling face with heart-eyes
+1F929 ; fully-qualified # 🤩 E5.0 star-struck
+1F618 ; fully-qualified # 😘 E0.6 face blowing a kiss
+1F617 ; fully-qualified # 😗 E1.0 kissing face
+263A FE0F ; fully-qualified # ☺️ E0.6 smiling face
+263A ; unqualified # ☺ E0.6 smiling face
+1F61A ; fully-qualified # 😚 E0.6 kissing face with closed eyes
+1F619 ; fully-qualified # 😙 E1.0 kissing face with smiling eyes
+1F972 ; fully-qualified # 🥲 E13.0 smiling face with tear
+
+# subgroup: face-tongue
+1F60B ; fully-qualified # 😋 E0.6 face savoring food
+1F61B ; fully-qualified # 😛 E1.0 face with tongue
+1F61C ; fully-qualified # 😜 E0.6 winking face with tongue
+1F92A ; fully-qualified # 🤪 E5.0 zany face
+1F61D ; fully-qualified # 😝 E0.6 squinting face with tongue
+1F911 ; fully-qualified # 🤑 E1.0 money-mouth face
+
+# subgroup: face-hand
+1F917 ; fully-qualified # 🤗 E1.0 smiling face with open hands
+1F92D ; fully-qualified # 🤭 E5.0 face with hand over mouth
+1FAE2 ; fully-qualified # 🫢 E14.0 face with open eyes and hand over mouth
+1FAE3 ; fully-qualified # 🫣 E14.0 face with peeking eye
+1F92B ; fully-qualified # 🤫 E5.0 shushing face
+1F914 ; fully-qualified # 🤔 E1.0 thinking face
+1FAE1 ; fully-qualified # 🫡 E14.0 saluting face
+
+# subgroup: face-neutral-skeptical
+1F910 ; fully-qualified # 🤐 E1.0 zipper-mouth face
+1F928 ; fully-qualified # 🤨 E5.0 face with raised eyebrow
+1F610 ; fully-qualified # 😐 E0.7 neutral face
+1F611 ; fully-qualified # 😑 E1.0 expressionless face
+1F636 ; fully-qualified # 😶 E1.0 face without mouth
+1FAE5 ; fully-qualified # 🫥 E14.0 dotted line face
+1F636 200D 1F32B FE0F ; fully-qualified # 😶🌫️ E13.1 face in clouds
+1F636 200D 1F32B ; minimally-qualified # 😶🌫 E13.1 face in clouds
+1F60F ; fully-qualified # 😏 E0.6 smirking face
+1F612 ; fully-qualified # 😒 E0.6 unamused face
+1F644 ; fully-qualified # 🙄 E1.0 face with rolling eyes
+1F62C ; fully-qualified # 😬 E1.0 grimacing face
+1F62E 200D 1F4A8 ; fully-qualified # 😮💨 E13.1 face exhaling
+1F925 ; fully-qualified # 🤥 E3.0 lying face
+1FAE8 ; fully-qualified # 🫨 E15.0 shaking face
+1F642 200D 2194 FE0F ; fully-qualified # 🙂↔️ E15.1 head shaking horizontally
+1F642 200D 2194 ; minimally-qualified # 🙂↔ E15.1 head shaking horizontally
+1F642 200D 2195 FE0F ; fully-qualified # 🙂↕️ E15.1 head shaking vertically
+1F642 200D 2195 ; minimally-qualified # 🙂↕ E15.1 head shaking vertically
+
+# subgroup: face-sleepy
+1F60C ; fully-qualified # 😌 E0.6 relieved face
+1F614 ; fully-qualified # 😔 E0.6 pensive face
+1F62A ; fully-qualified # 😪 E0.6 sleepy face
+1F924 ; fully-qualified # 🤤 E3.0 drooling face
+1F634 ; fully-qualified # 😴 E1.0 sleeping face
+1FAE9 ; fully-qualified # E16.0 face with bags under eyes
+
+# subgroup: face-unwell
+1F637 ; fully-qualified # 😷 E0.6 face with medical mask
+1F912 ; fully-qualified # 🤒 E1.0 face with thermometer
+1F915 ; fully-qualified # 🤕 E1.0 face with head-bandage
+1F922 ; fully-qualified # 🤢 E3.0 nauseated face
+1F92E ; fully-qualified # 🤮 E5.0 face vomiting
+1F927 ; fully-qualified # 🤧 E3.0 sneezing face
+1F975 ; fully-qualified # 🥵 E11.0 hot face
+1F976 ; fully-qualified # 🥶 E11.0 cold face
+1F974 ; fully-qualified # 🥴 E11.0 woozy face
+1F635 ; fully-qualified # 😵 E0.6 face with crossed-out eyes
+1F635 200D 1F4AB ; fully-qualified # 😵💫 E13.1 face with spiral eyes
+1F92F ; fully-qualified # 🤯 E5.0 exploding head
+
+# subgroup: face-hat
+1F920 ; fully-qualified # 🤠 E3.0 cowboy hat face
+1F973 ; fully-qualified # 🥳 E11.0 partying face
+1F978 ; fully-qualified # 🥸 E13.0 disguised face
+
+# subgroup: face-glasses
+1F60E ; fully-qualified # 😎 E1.0 smiling face with sunglasses
+1F913 ; fully-qualified # 🤓 E1.0 nerd face
+1F9D0 ; fully-qualified # 🧐 E5.0 face with monocle
+
+# subgroup: face-concerned
+1F615 ; fully-qualified # 😕 E1.0 confused face
+1FAE4 ; fully-qualified # 🫤 E14.0 face with diagonal mouth
+1F61F ; fully-qualified # 😟 E1.0 worried face
+1F641 ; fully-qualified # 🙁 E1.0 slightly frowning face
+2639 FE0F ; fully-qualified # ☹️ E0.7 frowning face
+2639 ; unqualified # ☹ E0.7 frowning face
+1F62E ; fully-qualified # 😮 E1.0 face with open mouth
+1F62F ; fully-qualified # 😯 E1.0 hushed face
+1F632 ; fully-qualified # 😲 E0.6 astonished face
+1F633 ; fully-qualified # 😳 E0.6 flushed face
+1F97A ; fully-qualified # 🥺 E11.0 pleading face
+1F979 ; fully-qualified # 🥹 E14.0 face holding back tears
+1F626 ; fully-qualified # 😦 E1.0 frowning face with open mouth
+1F627 ; fully-qualified # 😧 E1.0 anguished face
+1F628 ; fully-qualified # 😨 E0.6 fearful face
+1F630 ; fully-qualified # 😰 E0.6 anxious face with sweat
+1F625 ; fully-qualified # 😥 E0.6 sad but relieved face
+1F622 ; fully-qualified # 😢 E0.6 crying face
+1F62D ; fully-qualified # 😭 E0.6 loudly crying face
+1F631 ; fully-qualified # 😱 E0.6 face screaming in fear
+1F616 ; fully-qualified # 😖 E0.6 confounded face
+1F623 ; fully-qualified # 😣 E0.6 persevering face
+1F61E ; fully-qualified # 😞 E0.6 disappointed face
+1F613 ; fully-qualified # 😓 E0.6 downcast face with sweat
+1F629 ; fully-qualified # 😩 E0.6 weary face
+1F62B ; fully-qualified # 😫 E0.6 tired face
+1F971 ; fully-qualified # 🥱 E12.0 yawning face
+
+# subgroup: face-negative
+1F624 ; fully-qualified # 😤 E0.6 face with steam from nose
+1F621 ; fully-qualified # 😡 E0.6 enraged face
+1F620 ; fully-qualified # 😠 E0.6 angry face
+1F92C ; fully-qualified # 🤬 E5.0 face with symbols on mouth
+1F608 ; fully-qualified # 😈 E1.0 smiling face with horns
+1F47F ; fully-qualified # 👿 E0.6 angry face with horns
+1F480 ; fully-qualified # 💀 E0.6 skull
+2620 FE0F ; fully-qualified # ☠️ E1.0 skull and crossbones
+2620 ; unqualified # ☠ E1.0 skull and crossbones
+
+# subgroup: face-costume
+1F4A9 ; fully-qualified # 💩 E0.6 pile of poo
+1F921 ; fully-qualified # 🤡 E3.0 clown face
+1F479 ; fully-qualified # 👹 E0.6 ogre
+1F47A ; fully-qualified # 👺 E0.6 goblin
+1F47B ; fully-qualified # 👻 E0.6 ghost
+1F47D ; fully-qualified # 👽 E0.6 alien
+1F47E ; fully-qualified # 👾 E0.6 alien monster
+1F916 ; fully-qualified # 🤖 E1.0 robot
+
+# subgroup: cat-face
+1F63A ; fully-qualified # 😺 E0.6 grinning cat
+1F638 ; fully-qualified # 😸 E0.6 grinning cat with smiling eyes
+1F639 ; fully-qualified # 😹 E0.6 cat with tears of joy
+1F63B ; fully-qualified # 😻 E0.6 smiling cat with heart-eyes
+1F63C ; fully-qualified # 😼 E0.6 cat with wry smile
+1F63D ; fully-qualified # 😽 E0.6 kissing cat
+1F640 ; fully-qualified # 🙀 E0.6 weary cat
+1F63F ; fully-qualified # 😿 E0.6 crying cat
+1F63E ; fully-qualified # 😾 E0.6 pouting cat
+
+# subgroup: monkey-face
+1F648 ; fully-qualified # 🙈 E0.6 see-no-evil monkey
+1F649 ; fully-qualified # 🙉 E0.6 hear-no-evil monkey
+1F64A ; fully-qualified # 🙊 E0.6 speak-no-evil monkey
+
+# subgroup: heart
+1F48C ; fully-qualified # 💌 E0.6 love letter
+1F498 ; fully-qualified # 💘 E0.6 heart with arrow
+1F49D ; fully-qualified # 💝 E0.6 heart with ribbon
+1F496 ; fully-qualified # 💖 E0.6 sparkling heart
+1F497 ; fully-qualified # 💗 E0.6 growing heart
+1F493 ; fully-qualified # 💓 E0.6 beating heart
+1F49E ; fully-qualified # 💞 E0.6 revolving hearts
+1F495 ; fully-qualified # 💕 E0.6 two hearts
+1F49F ; fully-qualified # 💟 E0.6 heart decoration
+2763 FE0F ; fully-qualified # ❣️ E1.0 heart exclamation
+2763 ; unqualified # ❣ E1.0 heart exclamation
+1F494 ; fully-qualified # 💔 E0.6 broken heart
+2764 FE0F 200D 1F525 ; fully-qualified # ❤️🔥 E13.1 heart on fire
+2764 200D 1F525 ; unqualified # ❤🔥 E13.1 heart on fire
+2764 FE0F 200D 1FA79 ; fully-qualified # ❤️🩹 E13.1 mending heart
+2764 200D 1FA79 ; unqualified # ❤🩹 E13.1 mending heart
+2764 FE0F ; fully-qualified # ❤️ E0.6 red heart
+2764 ; unqualified # ❤ E0.6 red heart
+1FA77 ; fully-qualified # 🩷 E15.0 pink heart
+1F9E1 ; fully-qualified # 🧡 E5.0 orange heart
+1F49B ; fully-qualified # 💛 E0.6 yellow heart
+1F49A ; fully-qualified # 💚 E0.6 green heart
+1F499 ; fully-qualified # 💙 E0.6 blue heart
+1FA75 ; fully-qualified # 🩵 E15.0 light blue heart
+1F49C ; fully-qualified # 💜 E0.6 purple heart
+1F90E ; fully-qualified # 🤎 E12.0 brown heart
+1F5A4 ; fully-qualified # 🖤 E3.0 black heart
+1FA76 ; fully-qualified # 🩶 E15.0 grey heart
+1F90D ; fully-qualified # 🤍 E12.0 white heart
+
+# subgroup: emotion
+1F48B ; fully-qualified # 💋 E0.6 kiss mark
+1F4AF ; fully-qualified # 💯 E0.6 hundred points
+1F4A2 ; fully-qualified # 💢 E0.6 anger symbol
+1F4A5 ; fully-qualified # 💥 E0.6 collision
+1F4AB ; fully-qualified # 💫 E0.6 dizzy
+1F4A6 ; fully-qualified # 💦 E0.6 sweat droplets
+1F4A8 ; fully-qualified # 💨 E0.6 dashing away
+1F573 FE0F ; fully-qualified # 🕳️ E0.7 hole
+1F573 ; unqualified # 🕳 E0.7 hole
+1F4AC ; fully-qualified # 💬 E0.6 speech balloon
+1F441 FE0F 200D 1F5E8 FE0F ; fully-qualified # 👁️🗨️ E2.0 eye in speech bubble
+1F441 200D 1F5E8 FE0F ; unqualified # 👁🗨️ E2.0 eye in speech bubble
+1F441 FE0F 200D 1F5E8 ; minimally-qualified # 👁️🗨 E2.0 eye in speech bubble
+1F441 200D 1F5E8 ; unqualified # 👁🗨 E2.0 eye in speech bubble
+1F5E8 FE0F ; fully-qualified # 🗨️ E2.0 left speech bubble
+1F5E8 ; unqualified # 🗨 E2.0 left speech bubble
+1F5EF FE0F ; fully-qualified # 🗯️ E0.7 right anger bubble
+1F5EF ; unqualified # 🗯 E0.7 right anger bubble
+1F4AD ; fully-qualified # 💭 E1.0 thought balloon
+1F4A4 ; fully-qualified # 💤 E0.6 ZZZ
+
+# Smileys & Emotion subtotal: 185
+# Smileys & Emotion subtotal: 185 w/o modifiers
+
+# group: People & Body
+
+# subgroup: hand-fingers-open
+1F44B ; fully-qualified # 👋 E0.6 waving hand
+1F44B 1F3FB ; fully-qualified # 👋🏻 E1.0 waving hand: light skin tone
+1F44B 1F3FC ; fully-qualified # 👋🏼 E1.0 waving hand: medium-light skin tone
+1F44B 1F3FD ; fully-qualified # 👋🏽 E1.0 waving hand: medium skin tone
+1F44B 1F3FE ; fully-qualified # 👋🏾 E1.0 waving hand: medium-dark skin tone
+1F44B 1F3FF ; fully-qualified # 👋🏿 E1.0 waving hand: dark skin tone
+1F91A ; fully-qualified # 🤚 E3.0 raised back of hand
+1F91A 1F3FB ; fully-qualified # 🤚🏻 E3.0 raised back of hand: light skin tone
+1F91A 1F3FC ; fully-qualified # 🤚🏼 E3.0 raised back of hand: medium-light skin tone
+1F91A 1F3FD ; fully-qualified # 🤚🏽 E3.0 raised back of hand: medium skin tone
+1F91A 1F3FE ; fully-qualified # 🤚🏾 E3.0 raised back of hand: medium-dark skin tone
+1F91A 1F3FF ; fully-qualified # 🤚🏿 E3.0 raised back of hand: dark skin tone
+1F590 FE0F ; fully-qualified # 🖐️ E0.7 hand with fingers splayed
+1F590 ; unqualified # 🖐 E0.7 hand with fingers splayed
+1F590 1F3FB ; fully-qualified # 🖐🏻 E1.0 hand with fingers splayed: light skin tone
+1F590 1F3FC ; fully-qualified # 🖐🏼 E1.0 hand with fingers splayed: medium-light skin tone
+1F590 1F3FD ; fully-qualified # 🖐🏽 E1.0 hand with fingers splayed: medium skin tone
+1F590 1F3FE ; fully-qualified # 🖐🏾 E1.0 hand with fingers splayed: medium-dark skin tone
+1F590 1F3FF ; fully-qualified # 🖐🏿 E1.0 hand with fingers splayed: dark skin tone
+270B ; fully-qualified # ✋ E0.6 raised hand
+270B 1F3FB ; fully-qualified # ✋🏻 E1.0 raised hand: light skin tone
+270B 1F3FC ; fully-qualified # ✋🏼 E1.0 raised hand: medium-light skin tone
+270B 1F3FD ; fully-qualified # ✋🏽 E1.0 raised hand: medium skin tone
+270B 1F3FE ; fully-qualified # ✋🏾 E1.0 raised hand: medium-dark skin tone
+270B 1F3FF ; fully-qualified # ✋🏿 E1.0 raised hand: dark skin tone
+1F596 ; fully-qualified # 🖖 E1.0 vulcan salute
+1F596 1F3FB ; fully-qualified # 🖖🏻 E1.0 vulcan salute: light skin tone
+1F596 1F3FC ; fully-qualified # 🖖🏼 E1.0 vulcan salute: medium-light skin tone
+1F596 1F3FD ; fully-qualified # 🖖🏽 E1.0 vulcan salute: medium skin tone
+1F596 1F3FE ; fully-qualified # 🖖🏾 E1.0 vulcan salute: medium-dark skin tone
+1F596 1F3FF ; fully-qualified # 🖖🏿 E1.0 vulcan salute: dark skin tone
+1FAF1 ; fully-qualified # 🫱 E14.0 rightwards hand
+1FAF1 1F3FB ; fully-qualified # 🫱🏻 E14.0 rightwards hand: light skin tone
+1FAF1 1F3FC ; fully-qualified # 🫱🏼 E14.0 rightwards hand: medium-light skin tone
+1FAF1 1F3FD ; fully-qualified # 🫱🏽 E14.0 rightwards hand: medium skin tone
+1FAF1 1F3FE ; fully-qualified # 🫱🏾 E14.0 rightwards hand: medium-dark skin tone
+1FAF1 1F3FF ; fully-qualified # 🫱🏿 E14.0 rightwards hand: dark skin tone
+1FAF2 ; fully-qualified # 🫲 E14.0 leftwards hand
+1FAF2 1F3FB ; fully-qualified # 🫲🏻 E14.0 leftwards hand: light skin tone
+1FAF2 1F3FC ; fully-qualified # 🫲🏼 E14.0 leftwards hand: medium-light skin tone
+1FAF2 1F3FD ; fully-qualified # 🫲🏽 E14.0 leftwards hand: medium skin tone
+1FAF2 1F3FE ; fully-qualified # 🫲🏾 E14.0 leftwards hand: medium-dark skin tone
+1FAF2 1F3FF ; fully-qualified # 🫲🏿 E14.0 leftwards hand: dark skin tone
+1FAF3 ; fully-qualified # 🫳 E14.0 palm down hand
+1FAF3 1F3FB ; fully-qualified # 🫳🏻 E14.0 palm down hand: light skin tone
+1FAF3 1F3FC ; fully-qualified # 🫳🏼 E14.0 palm down hand: medium-light skin tone
+1FAF3 1F3FD ; fully-qualified # 🫳🏽 E14.0 palm down hand: medium skin tone
+1FAF3 1F3FE ; fully-qualified # 🫳🏾 E14.0 palm down hand: medium-dark skin tone
+1FAF3 1F3FF ; fully-qualified # 🫳🏿 E14.0 palm down hand: dark skin tone
+1FAF4 ; fully-qualified # 🫴 E14.0 palm up hand
+1FAF4 1F3FB ; fully-qualified # 🫴🏻 E14.0 palm up hand: light skin tone
+1FAF4 1F3FC ; fully-qualified # 🫴🏼 E14.0 palm up hand: medium-light skin tone
+1FAF4 1F3FD ; fully-qualified # 🫴🏽 E14.0 palm up hand: medium skin tone
+1FAF4 1F3FE ; fully-qualified # 🫴🏾 E14.0 palm up hand: medium-dark skin tone
+1FAF4 1F3FF ; fully-qualified # 🫴🏿 E14.0 palm up hand: dark skin tone
+1FAF7 ; fully-qualified # 🫷 E15.0 leftwards pushing hand
+1FAF7 1F3FB ; fully-qualified # 🫷🏻 E15.0 leftwards pushing hand: light skin tone
+1FAF7 1F3FC ; fully-qualified # 🫷🏼 E15.0 leftwards pushing hand: medium-light skin tone
+1FAF7 1F3FD ; fully-qualified # 🫷🏽 E15.0 leftwards pushing hand: medium skin tone
+1FAF7 1F3FE ; fully-qualified # 🫷🏾 E15.0 leftwards pushing hand: medium-dark skin tone
+1FAF7 1F3FF ; fully-qualified # 🫷🏿 E15.0 leftwards pushing hand: dark skin tone
+1FAF8 ; fully-qualified # 🫸 E15.0 rightwards pushing hand
+1FAF8 1F3FB ; fully-qualified # 🫸🏻 E15.0 rightwards pushing hand: light skin tone
+1FAF8 1F3FC ; fully-qualified # 🫸🏼 E15.0 rightwards pushing hand: medium-light skin tone
+1FAF8 1F3FD ; fully-qualified # 🫸🏽 E15.0 rightwards pushing hand: medium skin tone
+1FAF8 1F3FE ; fully-qualified # 🫸🏾 E15.0 rightwards pushing hand: medium-dark skin tone
+1FAF8 1F3FF ; fully-qualified # 🫸🏿 E15.0 rightwards pushing hand: dark skin tone
+
+# subgroup: hand-fingers-partial
+1F44C ; fully-qualified # 👌 E0.6 OK hand
+1F44C 1F3FB ; fully-qualified # 👌🏻 E1.0 OK hand: light skin tone
+1F44C 1F3FC ; fully-qualified # 👌🏼 E1.0 OK hand: medium-light skin tone
+1F44C 1F3FD ; fully-qualified # 👌🏽 E1.0 OK hand: medium skin tone
+1F44C 1F3FE ; fully-qualified # 👌🏾 E1.0 OK hand: medium-dark skin tone
+1F44C 1F3FF ; fully-qualified # 👌🏿 E1.0 OK hand: dark skin tone
+1F90C ; fully-qualified # 🤌 E13.0 pinched fingers
+1F90C 1F3FB ; fully-qualified # 🤌🏻 E13.0 pinched fingers: light skin tone
+1F90C 1F3FC ; fully-qualified # 🤌🏼 E13.0 pinched fingers: medium-light skin tone
+1F90C 1F3FD ; fully-qualified # 🤌🏽 E13.0 pinched fingers: medium skin tone
+1F90C 1F3FE ; fully-qualified # 🤌🏾 E13.0 pinched fingers: medium-dark skin tone
+1F90C 1F3FF ; fully-qualified # 🤌🏿 E13.0 pinched fingers: dark skin tone
+1F90F ; fully-qualified # 🤏 E12.0 pinching hand
+1F90F 1F3FB ; fully-qualified # 🤏🏻 E12.0 pinching hand: light skin tone
+1F90F 1F3FC ; fully-qualified # 🤏🏼 E12.0 pinching hand: medium-light skin tone
+1F90F 1F3FD ; fully-qualified # 🤏🏽 E12.0 pinching hand: medium skin tone
+1F90F 1F3FE ; fully-qualified # 🤏🏾 E12.0 pinching hand: medium-dark skin tone
+1F90F 1F3FF ; fully-qualified # 🤏🏿 E12.0 pinching hand: dark skin tone
+270C FE0F ; fully-qualified # ✌️ E0.6 victory hand
+270C ; unqualified # ✌ E0.6 victory hand
+270C 1F3FB ; fully-qualified # ✌🏻 E1.0 victory hand: light skin tone
+270C 1F3FC ; fully-qualified # ✌🏼 E1.0 victory hand: medium-light skin tone
+270C 1F3FD ; fully-qualified # ✌🏽 E1.0 victory hand: medium skin tone
+270C 1F3FE ; fully-qualified # ✌🏾 E1.0 victory hand: medium-dark skin tone
+270C 1F3FF ; fully-qualified # ✌🏿 E1.0 victory hand: dark skin tone
+1F91E ; fully-qualified # 🤞 E3.0 crossed fingers
+1F91E 1F3FB ; fully-qualified # 🤞🏻 E3.0 crossed fingers: light skin tone
+1F91E 1F3FC ; fully-qualified # 🤞🏼 E3.0 crossed fingers: medium-light skin tone
+1F91E 1F3FD ; fully-qualified # 🤞🏽 E3.0 crossed fingers: medium skin tone
+1F91E 1F3FE ; fully-qualified # 🤞🏾 E3.0 crossed fingers: medium-dark skin tone
+1F91E 1F3FF ; fully-qualified # 🤞🏿 E3.0 crossed fingers: dark skin tone
+1FAF0 ; fully-qualified # 🫰 E14.0 hand with index finger and thumb crossed
+1FAF0 1F3FB ; fully-qualified # 🫰🏻 E14.0 hand with index finger and thumb crossed: light skin tone
+1FAF0 1F3FC ; fully-qualified # 🫰🏼 E14.0 hand with index finger and thumb crossed: medium-light skin tone
+1FAF0 1F3FD ; fully-qualified # 🫰🏽 E14.0 hand with index finger and thumb crossed: medium skin tone
+1FAF0 1F3FE ; fully-qualified # 🫰🏾 E14.0 hand with index finger and thumb crossed: medium-dark skin tone
+1FAF0 1F3FF ; fully-qualified # 🫰🏿 E14.0 hand with index finger and thumb crossed: dark skin tone
+1F91F ; fully-qualified # 🤟 E5.0 love-you gesture
+1F91F 1F3FB ; fully-qualified # 🤟🏻 E5.0 love-you gesture: light skin tone
+1F91F 1F3FC ; fully-qualified # 🤟🏼 E5.0 love-you gesture: medium-light skin tone
+1F91F 1F3FD ; fully-qualified # 🤟🏽 E5.0 love-you gesture: medium skin tone
+1F91F 1F3FE ; fully-qualified # 🤟🏾 E5.0 love-you gesture: medium-dark skin tone
+1F91F 1F3FF ; fully-qualified # 🤟🏿 E5.0 love-you gesture: dark skin tone
+1F918 ; fully-qualified # 🤘 E1.0 sign of the horns
+1F918 1F3FB ; fully-qualified # 🤘🏻 E1.0 sign of the horns: light skin tone
+1F918 1F3FC ; fully-qualified # 🤘🏼 E1.0 sign of the horns: medium-light skin tone
+1F918 1F3FD ; fully-qualified # 🤘🏽 E1.0 sign of the horns: medium skin tone
+1F918 1F3FE ; fully-qualified # 🤘🏾 E1.0 sign of the horns: medium-dark skin tone
+1F918 1F3FF ; fully-qualified # 🤘🏿 E1.0 sign of the horns: dark skin tone
+1F919 ; fully-qualified # 🤙 E3.0 call me hand
+1F919 1F3FB ; fully-qualified # 🤙🏻 E3.0 call me hand: light skin tone
+1F919 1F3FC ; fully-qualified # 🤙🏼 E3.0 call me hand: medium-light skin tone
+1F919 1F3FD ; fully-qualified # 🤙🏽 E3.0 call me hand: medium skin tone
+1F919 1F3FE ; fully-qualified # 🤙🏾 E3.0 call me hand: medium-dark skin tone
+1F919 1F3FF ; fully-qualified # 🤙🏿 E3.0 call me hand: dark skin tone
+
+# subgroup: hand-single-finger
+1F448 ; fully-qualified # 👈 E0.6 backhand index pointing left
+1F448 1F3FB ; fully-qualified # 👈🏻 E1.0 backhand index pointing left: light skin tone
+1F448 1F3FC ; fully-qualified # 👈🏼 E1.0 backhand index pointing left: medium-light skin tone
+1F448 1F3FD ; fully-qualified # 👈🏽 E1.0 backhand index pointing left: medium skin tone
+1F448 1F3FE ; fully-qualified # 👈🏾 E1.0 backhand index pointing left: medium-dark skin tone
+1F448 1F3FF ; fully-qualified # 👈🏿 E1.0 backhand index pointing left: dark skin tone
+1F449 ; fully-qualified # 👉 E0.6 backhand index pointing right
+1F449 1F3FB ; fully-qualified # 👉🏻 E1.0 backhand index pointing right: light skin tone
+1F449 1F3FC ; fully-qualified # 👉🏼 E1.0 backhand index pointing right: medium-light skin tone
+1F449 1F3FD ; fully-qualified # 👉🏽 E1.0 backhand index pointing right: medium skin tone
+1F449 1F3FE ; fully-qualified # 👉🏾 E1.0 backhand index pointing right: medium-dark skin tone
+1F449 1F3FF ; fully-qualified # 👉🏿 E1.0 backhand index pointing right: dark skin tone
+1F446 ; fully-qualified # 👆 E0.6 backhand index pointing up
+1F446 1F3FB ; fully-qualified # 👆🏻 E1.0 backhand index pointing up: light skin tone
+1F446 1F3FC ; fully-qualified # 👆🏼 E1.0 backhand index pointing up: medium-light skin tone
+1F446 1F3FD ; fully-qualified # 👆🏽 E1.0 backhand index pointing up: medium skin tone
+1F446 1F3FE ; fully-qualified # 👆🏾 E1.0 backhand index pointing up: medium-dark skin tone
+1F446 1F3FF ; fully-qualified # 👆🏿 E1.0 backhand index pointing up: dark skin tone
+1F595 ; fully-qualified # 🖕 E1.0 middle finger
+1F595 1F3FB ; fully-qualified # 🖕🏻 E1.0 middle finger: light skin tone
+1F595 1F3FC ; fully-qualified # 🖕🏼 E1.0 middle finger: medium-light skin tone
+1F595 1F3FD ; fully-qualified # 🖕🏽 E1.0 middle finger: medium skin tone
+1F595 1F3FE ; fully-qualified # 🖕🏾 E1.0 middle finger: medium-dark skin tone
+1F595 1F3FF ; fully-qualified # 🖕🏿 E1.0 middle finger: dark skin tone
+1F447 ; fully-qualified # 👇 E0.6 backhand index pointing down
+1F447 1F3FB ; fully-qualified # 👇🏻 E1.0 backhand index pointing down: light skin tone
+1F447 1F3FC ; fully-qualified # 👇🏼 E1.0 backhand index pointing down: medium-light skin tone
+1F447 1F3FD ; fully-qualified # 👇🏽 E1.0 backhand index pointing down: medium skin tone
+1F447 1F3FE ; fully-qualified # 👇🏾 E1.0 backhand index pointing down: medium-dark skin tone
+1F447 1F3FF ; fully-qualified # 👇🏿 E1.0 backhand index pointing down: dark skin tone
+261D FE0F ; fully-qualified # ☝️ E0.6 index pointing up
+261D ; unqualified # ☝ E0.6 index pointing up
+261D 1F3FB ; fully-qualified # ☝🏻 E1.0 index pointing up: light skin tone
+261D 1F3FC ; fully-qualified # ☝🏼 E1.0 index pointing up: medium-light skin tone
+261D 1F3FD ; fully-qualified # ☝🏽 E1.0 index pointing up: medium skin tone
+261D 1F3FE ; fully-qualified # ☝🏾 E1.0 index pointing up: medium-dark skin tone
+261D 1F3FF ; fully-qualified # ☝🏿 E1.0 index pointing up: dark skin tone
+1FAF5 ; fully-qualified # 🫵 E14.0 index pointing at the viewer
+1FAF5 1F3FB ; fully-qualified # 🫵🏻 E14.0 index pointing at the viewer: light skin tone
+1FAF5 1F3FC ; fully-qualified # 🫵🏼 E14.0 index pointing at the viewer: medium-light skin tone
+1FAF5 1F3FD ; fully-qualified # 🫵🏽 E14.0 index pointing at the viewer: medium skin tone
+1FAF5 1F3FE ; fully-qualified # 🫵🏾 E14.0 index pointing at the viewer: medium-dark skin tone
+1FAF5 1F3FF ; fully-qualified # 🫵🏿 E14.0 index pointing at the viewer: dark skin tone
+
+# subgroup: hand-fingers-closed
+1F44D ; fully-qualified # 👍 E0.6 thumbs up
+1F44D 1F3FB ; fully-qualified # 👍🏻 E1.0 thumbs up: light skin tone
+1F44D 1F3FC ; fully-qualified # 👍🏼 E1.0 thumbs up: medium-light skin tone
+1F44D 1F3FD ; fully-qualified # 👍🏽 E1.0 thumbs up: medium skin tone
+1F44D 1F3FE ; fully-qualified # 👍🏾 E1.0 thumbs up: medium-dark skin tone
+1F44D 1F3FF ; fully-qualified # 👍🏿 E1.0 thumbs up: dark skin tone
+1F44E ; fully-qualified # 👎 E0.6 thumbs down
+1F44E 1F3FB ; fully-qualified # 👎🏻 E1.0 thumbs down: light skin tone
+1F44E 1F3FC ; fully-qualified # 👎🏼 E1.0 thumbs down: medium-light skin tone
+1F44E 1F3FD ; fully-qualified # 👎🏽 E1.0 thumbs down: medium skin tone
+1F44E 1F3FE ; fully-qualified # 👎🏾 E1.0 thumbs down: medium-dark skin tone
+1F44E 1F3FF ; fully-qualified # 👎🏿 E1.0 thumbs down: dark skin tone
+270A ; fully-qualified # ✊ E0.6 raised fist
+270A 1F3FB ; fully-qualified # ✊🏻 E1.0 raised fist: light skin tone
+270A 1F3FC ; fully-qualified # ✊🏼 E1.0 raised fist: medium-light skin tone
+270A 1F3FD ; fully-qualified # ✊🏽 E1.0 raised fist: medium skin tone
+270A 1F3FE ; fully-qualified # ✊🏾 E1.0 raised fist: medium-dark skin tone
+270A 1F3FF ; fully-qualified # ✊🏿 E1.0 raised fist: dark skin tone
+1F44A ; fully-qualified # 👊 E0.6 oncoming fist
+1F44A 1F3FB ; fully-qualified # 👊🏻 E1.0 oncoming fist: light skin tone
+1F44A 1F3FC ; fully-qualified # 👊🏼 E1.0 oncoming fist: medium-light skin tone
+1F44A 1F3FD ; fully-qualified # 👊🏽 E1.0 oncoming fist: medium skin tone
+1F44A 1F3FE ; fully-qualified # 👊🏾 E1.0 oncoming fist: medium-dark skin tone
+1F44A 1F3FF ; fully-qualified # 👊🏿 E1.0 oncoming fist: dark skin tone
+1F91B ; fully-qualified # 🤛 E3.0 left-facing fist
+1F91B 1F3FB ; fully-qualified # 🤛🏻 E3.0 left-facing fist: light skin tone
+1F91B 1F3FC ; fully-qualified # 🤛🏼 E3.0 left-facing fist: medium-light skin tone
+1F91B 1F3FD ; fully-qualified # 🤛🏽 E3.0 left-facing fist: medium skin tone
+1F91B 1F3FE ; fully-qualified # 🤛🏾 E3.0 left-facing fist: medium-dark skin tone
+1F91B 1F3FF ; fully-qualified # 🤛🏿 E3.0 left-facing fist: dark skin tone
+1F91C ; fully-qualified # 🤜 E3.0 right-facing fist
+1F91C 1F3FB ; fully-qualified # 🤜🏻 E3.0 right-facing fist: light skin tone
+1F91C 1F3FC ; fully-qualified # 🤜🏼 E3.0 right-facing fist: medium-light skin tone
+1F91C 1F3FD ; fully-qualified # 🤜🏽 E3.0 right-facing fist: medium skin tone
+1F91C 1F3FE ; fully-qualified # 🤜🏾 E3.0 right-facing fist: medium-dark skin tone
+1F91C 1F3FF ; fully-qualified # 🤜🏿 E3.0 right-facing fist: dark skin tone
+
+# subgroup: hands
+1F44F ; fully-qualified # 👏 E0.6 clapping hands
+1F44F 1F3FB ; fully-qualified # 👏🏻 E1.0 clapping hands: light skin tone
+1F44F 1F3FC ; fully-qualified # 👏🏼 E1.0 clapping hands: medium-light skin tone
+1F44F 1F3FD ; fully-qualified # 👏🏽 E1.0 clapping hands: medium skin tone
+1F44F 1F3FE ; fully-qualified # 👏🏾 E1.0 clapping hands: medium-dark skin tone
+1F44F 1F3FF ; fully-qualified # 👏🏿 E1.0 clapping hands: dark skin tone
+1F64C ; fully-qualified # 🙌 E0.6 raising hands
+1F64C 1F3FB ; fully-qualified # 🙌🏻 E1.0 raising hands: light skin tone
+1F64C 1F3FC ; fully-qualified # 🙌🏼 E1.0 raising hands: medium-light skin tone
+1F64C 1F3FD ; fully-qualified # 🙌🏽 E1.0 raising hands: medium skin tone
+1F64C 1F3FE ; fully-qualified # 🙌🏾 E1.0 raising hands: medium-dark skin tone
+1F64C 1F3FF ; fully-qualified # 🙌🏿 E1.0 raising hands: dark skin tone
+1FAF6 ; fully-qualified # 🫶 E14.0 heart hands
+1FAF6 1F3FB ; fully-qualified # 🫶🏻 E14.0 heart hands: light skin tone
+1FAF6 1F3FC ; fully-qualified # 🫶🏼 E14.0 heart hands: medium-light skin tone
+1FAF6 1F3FD ; fully-qualified # 🫶🏽 E14.0 heart hands: medium skin tone
+1FAF6 1F3FE ; fully-qualified # 🫶🏾 E14.0 heart hands: medium-dark skin tone
+1FAF6 1F3FF ; fully-qualified # 🫶🏿 E14.0 heart hands: dark skin tone
+1F450 ; fully-qualified # 👐 E0.6 open hands
+1F450 1F3FB ; fully-qualified # 👐🏻 E1.0 open hands: light skin tone
+1F450 1F3FC ; fully-qualified # 👐🏼 E1.0 open hands: medium-light skin tone
+1F450 1F3FD ; fully-qualified # 👐🏽 E1.0 open hands: medium skin tone
+1F450 1F3FE ; fully-qualified # 👐🏾 E1.0 open hands: medium-dark skin tone
+1F450 1F3FF ; fully-qualified # 👐🏿 E1.0 open hands: dark skin tone
+1F932 ; fully-qualified # 🤲 E5.0 palms up together
+1F932 1F3FB ; fully-qualified # 🤲🏻 E5.0 palms up together: light skin tone
+1F932 1F3FC ; fully-qualified # 🤲🏼 E5.0 palms up together: medium-light skin tone
+1F932 1F3FD ; fully-qualified # 🤲🏽 E5.0 palms up together: medium skin tone
+1F932 1F3FE ; fully-qualified # 🤲🏾 E5.0 palms up together: medium-dark skin tone
+1F932 1F3FF ; fully-qualified # 🤲🏿 E5.0 palms up together: dark skin tone
+1F91D ; fully-qualified # 🤝 E3.0 handshake
+1F91D 1F3FB ; fully-qualified # 🤝🏻 E14.0 handshake: light skin tone
+1F91D 1F3FC ; fully-qualified # 🤝🏼 E14.0 handshake: medium-light skin tone
+1F91D 1F3FD ; fully-qualified # 🤝🏽 E14.0 handshake: medium skin tone
+1F91D 1F3FE ; fully-qualified # 🤝🏾 E14.0 handshake: medium-dark skin tone
+1F91D 1F3FF ; fully-qualified # 🤝🏿 E14.0 handshake: dark skin tone
+1FAF1 1F3FB 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏻🫲🏼 E14.0 handshake: light skin tone, medium-light skin tone
+1FAF1 1F3FB 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏻🫲🏽 E14.0 handshake: light skin tone, medium skin tone
+1FAF1 1F3FB 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏻🫲🏾 E14.0 handshake: light skin tone, medium-dark skin tone
+1FAF1 1F3FB 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏻🫲🏿 E14.0 handshake: light skin tone, dark skin tone
+1FAF1 1F3FC 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏼🫲🏻 E14.0 handshake: medium-light skin tone, light skin tone
+1FAF1 1F3FC 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏼🫲🏽 E14.0 handshake: medium-light skin tone, medium skin tone
+1FAF1 1F3FC 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏼🫲🏾 E14.0 handshake: medium-light skin tone, medium-dark skin tone
+1FAF1 1F3FC 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏼🫲🏿 E14.0 handshake: medium-light skin tone, dark skin tone
+1FAF1 1F3FD 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏽🫲🏻 E14.0 handshake: medium skin tone, light skin tone
+1FAF1 1F3FD 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏽🫲🏼 E14.0 handshake: medium skin tone, medium-light skin tone
+1FAF1 1F3FD 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏽🫲🏾 E14.0 handshake: medium skin tone, medium-dark skin tone
+1FAF1 1F3FD 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏽🫲🏿 E14.0 handshake: medium skin tone, dark skin tone
+1FAF1 1F3FE 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏾🫲🏻 E14.0 handshake: medium-dark skin tone, light skin tone
+1FAF1 1F3FE 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏾🫲🏼 E14.0 handshake: medium-dark skin tone, medium-light skin tone
+1FAF1 1F3FE 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏾🫲🏽 E14.0 handshake: medium-dark skin tone, medium skin tone
+1FAF1 1F3FE 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏾🫲🏿 E14.0 handshake: medium-dark skin tone, dark skin tone
+1FAF1 1F3FF 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏿🫲🏻 E14.0 handshake: dark skin tone, light skin tone
+1FAF1 1F3FF 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏿🫲🏼 E14.0 handshake: dark skin tone, medium-light skin tone
+1FAF1 1F3FF 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏿🫲🏽 E14.0 handshake: dark skin tone, medium skin tone
+1FAF1 1F3FF 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏿🫲🏾 E14.0 handshake: dark skin tone, medium-dark skin tone
+1F64F ; fully-qualified # 🙏 E0.6 folded hands
+1F64F 1F3FB ; fully-qualified # 🙏🏻 E1.0 folded hands: light skin tone
+1F64F 1F3FC ; fully-qualified # 🙏🏼 E1.0 folded hands: medium-light skin tone
+1F64F 1F3FD ; fully-qualified # 🙏🏽 E1.0 folded hands: medium skin tone
+1F64F 1F3FE ; fully-qualified # 🙏🏾 E1.0 folded hands: medium-dark skin tone
+1F64F 1F3FF ; fully-qualified # 🙏🏿 E1.0 folded hands: dark skin tone
+
+# subgroup: hand-prop
+270D FE0F ; fully-qualified # ✍️ E0.7 writing hand
+270D ; unqualified # ✍ E0.7 writing hand
+270D 1F3FB ; fully-qualified # ✍🏻 E1.0 writing hand: light skin tone
+270D 1F3FC ; fully-qualified # ✍🏼 E1.0 writing hand: medium-light skin tone
+270D 1F3FD ; fully-qualified # ✍🏽 E1.0 writing hand: medium skin tone
+270D 1F3FE ; fully-qualified # ✍🏾 E1.0 writing hand: medium-dark skin tone
+270D 1F3FF ; fully-qualified # ✍🏿 E1.0 writing hand: dark skin tone
+1F485 ; fully-qualified # 💅 E0.6 nail polish
+1F485 1F3FB ; fully-qualified # 💅🏻 E1.0 nail polish: light skin tone
+1F485 1F3FC ; fully-qualified # 💅🏼 E1.0 nail polish: medium-light skin tone
+1F485 1F3FD ; fully-qualified # 💅🏽 E1.0 nail polish: medium skin tone
+1F485 1F3FE ; fully-qualified # 💅🏾 E1.0 nail polish: medium-dark skin tone
+1F485 1F3FF ; fully-qualified # 💅🏿 E1.0 nail polish: dark skin tone
+1F933 ; fully-qualified # 🤳 E3.0 selfie
+1F933 1F3FB ; fully-qualified # 🤳🏻 E3.0 selfie: light skin tone
+1F933 1F3FC ; fully-qualified # 🤳🏼 E3.0 selfie: medium-light skin tone
+1F933 1F3FD ; fully-qualified # 🤳🏽 E3.0 selfie: medium skin tone
+1F933 1F3FE ; fully-qualified # 🤳🏾 E3.0 selfie: medium-dark skin tone
+1F933 1F3FF ; fully-qualified # 🤳🏿 E3.0 selfie: dark skin tone
+
+# subgroup: body-parts
+1F4AA ; fully-qualified # 💪 E0.6 flexed biceps
+1F4AA 1F3FB ; fully-qualified # 💪🏻 E1.0 flexed biceps: light skin tone
+1F4AA 1F3FC ; fully-qualified # 💪🏼 E1.0 flexed biceps: medium-light skin tone
+1F4AA 1F3FD ; fully-qualified # 💪🏽 E1.0 flexed biceps: medium skin tone
+1F4AA 1F3FE ; fully-qualified # 💪🏾 E1.0 flexed biceps: medium-dark skin tone
+1F4AA 1F3FF ; fully-qualified # 💪🏿 E1.0 flexed biceps: dark skin tone
+1F9BE ; fully-qualified # 🦾 E12.0 mechanical arm
+1F9BF ; fully-qualified # 🦿 E12.0 mechanical leg
+1F9B5 ; fully-qualified # 🦵 E11.0 leg
+1F9B5 1F3FB ; fully-qualified # 🦵🏻 E11.0 leg: light skin tone
+1F9B5 1F3FC ; fully-qualified # 🦵🏼 E11.0 leg: medium-light skin tone
+1F9B5 1F3FD ; fully-qualified # 🦵🏽 E11.0 leg: medium skin tone
+1F9B5 1F3FE ; fully-qualified # 🦵🏾 E11.0 leg: medium-dark skin tone
+1F9B5 1F3FF ; fully-qualified # 🦵🏿 E11.0 leg: dark skin tone
+1F9B6 ; fully-qualified # 🦶 E11.0 foot
+1F9B6 1F3FB ; fully-qualified # 🦶🏻 E11.0 foot: light skin tone
+1F9B6 1F3FC ; fully-qualified # 🦶🏼 E11.0 foot: medium-light skin tone
+1F9B6 1F3FD ; fully-qualified # 🦶🏽 E11.0 foot: medium skin tone
+1F9B6 1F3FE ; fully-qualified # 🦶🏾 E11.0 foot: medium-dark skin tone
+1F9B6 1F3FF ; fully-qualified # 🦶🏿 E11.0 foot: dark skin tone
+1F442 ; fully-qualified # 👂 E0.6 ear
+1F442 1F3FB ; fully-qualified # 👂🏻 E1.0 ear: light skin tone
+1F442 1F3FC ; fully-qualified # 👂🏼 E1.0 ear: medium-light skin tone
+1F442 1F3FD ; fully-qualified # 👂🏽 E1.0 ear: medium skin tone
+1F442 1F3FE ; fully-qualified # 👂🏾 E1.0 ear: medium-dark skin tone
+1F442 1F3FF ; fully-qualified # 👂🏿 E1.0 ear: dark skin tone
+1F9BB ; fully-qualified # 🦻 E12.0 ear with hearing aid
+1F9BB 1F3FB ; fully-qualified # 🦻🏻 E12.0 ear with hearing aid: light skin tone
+1F9BB 1F3FC ; fully-qualified # 🦻🏼 E12.0 ear with hearing aid: medium-light skin tone
+1F9BB 1F3FD ; fully-qualified # 🦻🏽 E12.0 ear with hearing aid: medium skin tone
+1F9BB 1F3FE ; fully-qualified # 🦻🏾 E12.0 ear with hearing aid: medium-dark skin tone
+1F9BB 1F3FF ; fully-qualified # 🦻🏿 E12.0 ear with hearing aid: dark skin tone
+1F443 ; fully-qualified # 👃 E0.6 nose
+1F443 1F3FB ; fully-qualified # 👃🏻 E1.0 nose: light skin tone
+1F443 1F3FC ; fully-qualified # 👃🏼 E1.0 nose: medium-light skin tone
+1F443 1F3FD ; fully-qualified # 👃🏽 E1.0 nose: medium skin tone
+1F443 1F3FE ; fully-qualified # 👃🏾 E1.0 nose: medium-dark skin tone
+1F443 1F3FF ; fully-qualified # 👃🏿 E1.0 nose: dark skin tone
+1F9E0 ; fully-qualified # 🧠 E5.0 brain
+1FAC0 ; fully-qualified # 🫀 E13.0 anatomical heart
+1FAC1 ; fully-qualified # 🫁 E13.0 lungs
+1F9B7 ; fully-qualified # 🦷 E11.0 tooth
+1F9B4 ; fully-qualified # 🦴 E11.0 bone
+1F440 ; fully-qualified # 👀 E0.6 eyes
+1F441 FE0F ; fully-qualified # 👁️ E0.7 eye
+1F441 ; unqualified # 👁 E0.7 eye
+1F445 ; fully-qualified # 👅 E0.6 tongue
+1F444 ; fully-qualified # 👄 E0.6 mouth
+1FAE6 ; fully-qualified # 🫦 E14.0 biting lip
+
+# subgroup: person
+1F476 ; fully-qualified # 👶 E0.6 baby
+1F476 1F3FB ; fully-qualified # 👶🏻 E1.0 baby: light skin tone
+1F476 1F3FC ; fully-qualified # 👶🏼 E1.0 baby: medium-light skin tone
+1F476 1F3FD ; fully-qualified # 👶🏽 E1.0 baby: medium skin tone
+1F476 1F3FE ; fully-qualified # 👶🏾 E1.0 baby: medium-dark skin tone
+1F476 1F3FF ; fully-qualified # 👶🏿 E1.0 baby: dark skin tone
+1F9D2 ; fully-qualified # 🧒 E5.0 child
+1F9D2 1F3FB ; fully-qualified # 🧒🏻 E5.0 child: light skin tone
+1F9D2 1F3FC ; fully-qualified # 🧒🏼 E5.0 child: medium-light skin tone
+1F9D2 1F3FD ; fully-qualified # 🧒🏽 E5.0 child: medium skin tone
+1F9D2 1F3FE ; fully-qualified # 🧒🏾 E5.0 child: medium-dark skin tone
+1F9D2 1F3FF ; fully-qualified # 🧒🏿 E5.0 child: dark skin tone
+1F466 ; fully-qualified # 👦 E0.6 boy
+1F466 1F3FB ; fully-qualified # 👦🏻 E1.0 boy: light skin tone
+1F466 1F3FC ; fully-qualified # 👦🏼 E1.0 boy: medium-light skin tone
+1F466 1F3FD ; fully-qualified # 👦🏽 E1.0 boy: medium skin tone
+1F466 1F3FE ; fully-qualified # 👦🏾 E1.0 boy: medium-dark skin tone
+1F466 1F3FF ; fully-qualified # 👦🏿 E1.0 boy: dark skin tone
+1F467 ; fully-qualified # 👧 E0.6 girl
+1F467 1F3FB ; fully-qualified # 👧🏻 E1.0 girl: light skin tone
+1F467 1F3FC ; fully-qualified # 👧🏼 E1.0 girl: medium-light skin tone
+1F467 1F3FD ; fully-qualified # 👧🏽 E1.0 girl: medium skin tone
+1F467 1F3FE ; fully-qualified # 👧🏾 E1.0 girl: medium-dark skin tone
+1F467 1F3FF ; fully-qualified # 👧🏿 E1.0 girl: dark skin tone
+1F9D1 ; fully-qualified # 🧑 E5.0 person
+1F9D1 1F3FB ; fully-qualified # 🧑🏻 E5.0 person: light skin tone
+1F9D1 1F3FC ; fully-qualified # 🧑🏼 E5.0 person: medium-light skin tone
+1F9D1 1F3FD ; fully-qualified # 🧑🏽 E5.0 person: medium skin tone
+1F9D1 1F3FE ; fully-qualified # 🧑🏾 E5.0 person: medium-dark skin tone
+1F9D1 1F3FF ; fully-qualified # 🧑🏿 E5.0 person: dark skin tone
+1F471 ; fully-qualified # 👱 E0.6 person: blond hair
+1F471 1F3FB ; fully-qualified # 👱🏻 E1.0 person: light skin tone, blond hair
+1F471 1F3FC ; fully-qualified # 👱🏼 E1.0 person: medium-light skin tone, blond hair
+1F471 1F3FD ; fully-qualified # 👱🏽 E1.0 person: medium skin tone, blond hair
+1F471 1F3FE ; fully-qualified # 👱🏾 E1.0 person: medium-dark skin tone, blond hair
+1F471 1F3FF ; fully-qualified # 👱🏿 E1.0 person: dark skin tone, blond hair
+1F468 ; fully-qualified # 👨 E0.6 man
+1F468 1F3FB ; fully-qualified # 👨🏻 E1.0 man: light skin tone
+1F468 1F3FC ; fully-qualified # 👨🏼 E1.0 man: medium-light skin tone
+1F468 1F3FD ; fully-qualified # 👨🏽 E1.0 man: medium skin tone
+1F468 1F3FE ; fully-qualified # 👨🏾 E1.0 man: medium-dark skin tone
+1F468 1F3FF ; fully-qualified # 👨🏿 E1.0 man: dark skin tone
+1F9D4 ; fully-qualified # 🧔 E5.0 person: beard
+1F9D4 1F3FB ; fully-qualified # 🧔🏻 E5.0 person: light skin tone, beard
+1F9D4 1F3FC ; fully-qualified # 🧔🏼 E5.0 person: medium-light skin tone, beard
+1F9D4 1F3FD ; fully-qualified # 🧔🏽 E5.0 person: medium skin tone, beard
+1F9D4 1F3FE ; fully-qualified # 🧔🏾 E5.0 person: medium-dark skin tone, beard
+1F9D4 1F3FF ; fully-qualified # 🧔🏿 E5.0 person: dark skin tone, beard
+1F9D4 200D 2642 FE0F ; fully-qualified # 🧔♂️ E13.1 man: beard
+1F9D4 200D 2642 ; minimally-qualified # 🧔♂ E13.1 man: beard
+1F9D4 1F3FB 200D 2642 FE0F ; fully-qualified # 🧔🏻♂️ E13.1 man: light skin tone, beard
+1F9D4 1F3FB 200D 2642 ; minimally-qualified # 🧔🏻♂ E13.1 man: light skin tone, beard
+1F9D4 1F3FC 200D 2642 FE0F ; fully-qualified # 🧔🏼♂️ E13.1 man: medium-light skin tone, beard
+1F9D4 1F3FC 200D 2642 ; minimally-qualified # 🧔🏼♂ E13.1 man: medium-light skin tone, beard
+1F9D4 1F3FD 200D 2642 FE0F ; fully-qualified # 🧔🏽♂️ E13.1 man: medium skin tone, beard
+1F9D4 1F3FD 200D 2642 ; minimally-qualified # 🧔🏽♂ E13.1 man: medium skin tone, beard
+1F9D4 1F3FE 200D 2642 FE0F ; fully-qualified # 🧔🏾♂️ E13.1 man: medium-dark skin tone, beard
+1F9D4 1F3FE 200D 2642 ; minimally-qualified # 🧔🏾♂ E13.1 man: medium-dark skin tone, beard
+1F9D4 1F3FF 200D 2642 FE0F ; fully-qualified # 🧔🏿♂️ E13.1 man: dark skin tone, beard
+1F9D4 1F3FF 200D 2642 ; minimally-qualified # 🧔🏿♂ E13.1 man: dark skin tone, beard
+1F9D4 200D 2640 FE0F ; fully-qualified # 🧔♀️ E13.1 woman: beard
+1F9D4 200D 2640 ; minimally-qualified # 🧔♀ E13.1 woman: beard
+1F9D4 1F3FB 200D 2640 FE0F ; fully-qualified # 🧔🏻♀️ E13.1 woman: light skin tone, beard
+1F9D4 1F3FB 200D 2640 ; minimally-qualified # 🧔🏻♀ E13.1 woman: light skin tone, beard
+1F9D4 1F3FC 200D 2640 FE0F ; fully-qualified # 🧔🏼♀️ E13.1 woman: medium-light skin tone, beard
+1F9D4 1F3FC 200D 2640 ; minimally-qualified # 🧔🏼♀ E13.1 woman: medium-light skin tone, beard
+1F9D4 1F3FD 200D 2640 FE0F ; fully-qualified # 🧔🏽♀️ E13.1 woman: medium skin tone, beard
+1F9D4 1F3FD 200D 2640 ; minimally-qualified # 🧔🏽♀ E13.1 woman: medium skin tone, beard
+1F9D4 1F3FE 200D 2640 FE0F ; fully-qualified # 🧔🏾♀️ E13.1 woman: medium-dark skin tone, beard
+1F9D4 1F3FE 200D 2640 ; minimally-qualified # 🧔🏾♀ E13.1 woman: medium-dark skin tone, beard
+1F9D4 1F3FF 200D 2640 FE0F ; fully-qualified # 🧔🏿♀️ E13.1 woman: dark skin tone, beard
+1F9D4 1F3FF 200D 2640 ; minimally-qualified # 🧔🏿♀ E13.1 woman: dark skin tone, beard
+1F468 200D 1F9B0 ; fully-qualified # 👨🦰 E11.0 man: red hair
+1F468 1F3FB 200D 1F9B0 ; fully-qualified # 👨🏻🦰 E11.0 man: light skin tone, red hair
+1F468 1F3FC 200D 1F9B0 ; fully-qualified # 👨🏼🦰 E11.0 man: medium-light skin tone, red hair
+1F468 1F3FD 200D 1F9B0 ; fully-qualified # 👨🏽🦰 E11.0 man: medium skin tone, red hair
+1F468 1F3FE 200D 1F9B0 ; fully-qualified # 👨🏾🦰 E11.0 man: medium-dark skin tone, red hair
+1F468 1F3FF 200D 1F9B0 ; fully-qualified # 👨🏿🦰 E11.0 man: dark skin tone, red hair
+1F468 200D 1F9B1 ; fully-qualified # 👨🦱 E11.0 man: curly hair
+1F468 1F3FB 200D 1F9B1 ; fully-qualified # 👨🏻🦱 E11.0 man: light skin tone, curly hair
+1F468 1F3FC 200D 1F9B1 ; fully-qualified # 👨🏼🦱 E11.0 man: medium-light skin tone, curly hair
+1F468 1F3FD 200D 1F9B1 ; fully-qualified # 👨🏽🦱 E11.0 man: medium skin tone, curly hair
+1F468 1F3FE 200D 1F9B1 ; fully-qualified # 👨🏾🦱 E11.0 man: medium-dark skin tone, curly hair
+1F468 1F3FF 200D 1F9B1 ; fully-qualified # 👨🏿🦱 E11.0 man: dark skin tone, curly hair
+1F468 200D 1F9B3 ; fully-qualified # 👨🦳 E11.0 man: white hair
+1F468 1F3FB 200D 1F9B3 ; fully-qualified # 👨🏻🦳 E11.0 man: light skin tone, white hair
+1F468 1F3FC 200D 1F9B3 ; fully-qualified # 👨🏼🦳 E11.0 man: medium-light skin tone, white hair
+1F468 1F3FD 200D 1F9B3 ; fully-qualified # 👨🏽🦳 E11.0 man: medium skin tone, white hair
+1F468 1F3FE 200D 1F9B3 ; fully-qualified # 👨🏾🦳 E11.0 man: medium-dark skin tone, white hair
+1F468 1F3FF 200D 1F9B3 ; fully-qualified # 👨🏿🦳 E11.0 man: dark skin tone, white hair
+1F468 200D 1F9B2 ; fully-qualified # 👨🦲 E11.0 man: bald
+1F468 1F3FB 200D 1F9B2 ; fully-qualified # 👨🏻🦲 E11.0 man: light skin tone, bald
+1F468 1F3FC 200D 1F9B2 ; fully-qualified # 👨🏼🦲 E11.0 man: medium-light skin tone, bald
+1F468 1F3FD 200D 1F9B2 ; fully-qualified # 👨🏽🦲 E11.0 man: medium skin tone, bald
+1F468 1F3FE 200D 1F9B2 ; fully-qualified # 👨🏾🦲 E11.0 man: medium-dark skin tone, bald
+1F468 1F3FF 200D 1F9B2 ; fully-qualified # 👨🏿🦲 E11.0 man: dark skin tone, bald
+1F469 ; fully-qualified # 👩 E0.6 woman
+1F469 1F3FB ; fully-qualified # 👩🏻 E1.0 woman: light skin tone
+1F469 1F3FC ; fully-qualified # 👩🏼 E1.0 woman: medium-light skin tone
+1F469 1F3FD ; fully-qualified # 👩🏽 E1.0 woman: medium skin tone
+1F469 1F3FE ; fully-qualified # 👩🏾 E1.0 woman: medium-dark skin tone
+1F469 1F3FF ; fully-qualified # 👩🏿 E1.0 woman: dark skin tone
+1F469 200D 1F9B0 ; fully-qualified # 👩🦰 E11.0 woman: red hair
+1F469 1F3FB 200D 1F9B0 ; fully-qualified # 👩🏻🦰 E11.0 woman: light skin tone, red hair
+1F469 1F3FC 200D 1F9B0 ; fully-qualified # 👩🏼🦰 E11.0 woman: medium-light skin tone, red hair
+1F469 1F3FD 200D 1F9B0 ; fully-qualified # 👩🏽🦰 E11.0 woman: medium skin tone, red hair
+1F469 1F3FE 200D 1F9B0 ; fully-qualified # 👩🏾🦰 E11.0 woman: medium-dark skin tone, red hair
+1F469 1F3FF 200D 1F9B0 ; fully-qualified # 👩🏿🦰 E11.0 woman: dark skin tone, red hair
+1F9D1 200D 1F9B0 ; fully-qualified # 🧑🦰 E12.1 person: red hair
+1F9D1 1F3FB 200D 1F9B0 ; fully-qualified # 🧑🏻🦰 E12.1 person: light skin tone, red hair
+1F9D1 1F3FC 200D 1F9B0 ; fully-qualified # 🧑🏼🦰 E12.1 person: medium-light skin tone, red hair
+1F9D1 1F3FD 200D 1F9B0 ; fully-qualified # 🧑🏽🦰 E12.1 person: medium skin tone, red hair
+1F9D1 1F3FE 200D 1F9B0 ; fully-qualified # 🧑🏾🦰 E12.1 person: medium-dark skin tone, red hair
+1F9D1 1F3FF 200D 1F9B0 ; fully-qualified # 🧑🏿🦰 E12.1 person: dark skin tone, red hair
+1F469 200D 1F9B1 ; fully-qualified # 👩🦱 E11.0 woman: curly hair
+1F469 1F3FB 200D 1F9B1 ; fully-qualified # 👩🏻🦱 E11.0 woman: light skin tone, curly hair
+1F469 1F3FC 200D 1F9B1 ; fully-qualified # 👩🏼🦱 E11.0 woman: medium-light skin tone, curly hair
+1F469 1F3FD 200D 1F9B1 ; fully-qualified # 👩🏽🦱 E11.0 woman: medium skin tone, curly hair
+1F469 1F3FE 200D 1F9B1 ; fully-qualified # 👩🏾🦱 E11.0 woman: medium-dark skin tone, curly hair
+1F469 1F3FF 200D 1F9B1 ; fully-qualified # 👩🏿🦱 E11.0 woman: dark skin tone, curly hair
+1F9D1 200D 1F9B1 ; fully-qualified # 🧑🦱 E12.1 person: curly hair
+1F9D1 1F3FB 200D 1F9B1 ; fully-qualified # 🧑🏻🦱 E12.1 person: light skin tone, curly hair
+1F9D1 1F3FC 200D 1F9B1 ; fully-qualified # 🧑🏼🦱 E12.1 person: medium-light skin tone, curly hair
+1F9D1 1F3FD 200D 1F9B1 ; fully-qualified # 🧑🏽🦱 E12.1 person: medium skin tone, curly hair
+1F9D1 1F3FE 200D 1F9B1 ; fully-qualified # 🧑🏾🦱 E12.1 person: medium-dark skin tone, curly hair
+1F9D1 1F3FF 200D 1F9B1 ; fully-qualified # 🧑🏿🦱 E12.1 person: dark skin tone, curly hair
+1F469 200D 1F9B3 ; fully-qualified # 👩🦳 E11.0 woman: white hair
+1F469 1F3FB 200D 1F9B3 ; fully-qualified # 👩🏻🦳 E11.0 woman: light skin tone, white hair
+1F469 1F3FC 200D 1F9B3 ; fully-qualified # 👩🏼🦳 E11.0 woman: medium-light skin tone, white hair
+1F469 1F3FD 200D 1F9B3 ; fully-qualified # 👩🏽🦳 E11.0 woman: medium skin tone, white hair
+1F469 1F3FE 200D 1F9B3 ; fully-qualified # 👩🏾🦳 E11.0 woman: medium-dark skin tone, white hair
+1F469 1F3FF 200D 1F9B3 ; fully-qualified # 👩🏿🦳 E11.0 woman: dark skin tone, white hair
+1F9D1 200D 1F9B3 ; fully-qualified # 🧑🦳 E12.1 person: white hair
+1F9D1 1F3FB 200D 1F9B3 ; fully-qualified # 🧑🏻🦳 E12.1 person: light skin tone, white hair
+1F9D1 1F3FC 200D 1F9B3 ; fully-qualified # 🧑🏼🦳 E12.1 person: medium-light skin tone, white hair
+1F9D1 1F3FD 200D 1F9B3 ; fully-qualified # 🧑🏽🦳 E12.1 person: medium skin tone, white hair
+1F9D1 1F3FE 200D 1F9B3 ; fully-qualified # 🧑🏾🦳 E12.1 person: medium-dark skin tone, white hair
+1F9D1 1F3FF 200D 1F9B3 ; fully-qualified # 🧑🏿🦳 E12.1 person: dark skin tone, white hair
+1F469 200D 1F9B2 ; fully-qualified # 👩🦲 E11.0 woman: bald
+1F469 1F3FB 200D 1F9B2 ; fully-qualified # 👩🏻🦲 E11.0 woman: light skin tone, bald
+1F469 1F3FC 200D 1F9B2 ; fully-qualified # 👩🏼🦲 E11.0 woman: medium-light skin tone, bald
+1F469 1F3FD 200D 1F9B2 ; fully-qualified # 👩🏽🦲 E11.0 woman: medium skin tone, bald
+1F469 1F3FE 200D 1F9B2 ; fully-qualified # 👩🏾🦲 E11.0 woman: medium-dark skin tone, bald
+1F469 1F3FF 200D 1F9B2 ; fully-qualified # 👩🏿🦲 E11.0 woman: dark skin tone, bald
+1F9D1 200D 1F9B2 ; fully-qualified # 🧑🦲 E12.1 person: bald
+1F9D1 1F3FB 200D 1F9B2 ; fully-qualified # 🧑🏻🦲 E12.1 person: light skin tone, bald
+1F9D1 1F3FC 200D 1F9B2 ; fully-qualified # 🧑🏼🦲 E12.1 person: medium-light skin tone, bald
+1F9D1 1F3FD 200D 1F9B2 ; fully-qualified # 🧑🏽🦲 E12.1 person: medium skin tone, bald
+1F9D1 1F3FE 200D 1F9B2 ; fully-qualified # 🧑🏾🦲 E12.1 person: medium-dark skin tone, bald
+1F9D1 1F3FF 200D 1F9B2 ; fully-qualified # 🧑🏿🦲 E12.1 person: dark skin tone, bald
+1F471 200D 2640 FE0F ; fully-qualified # 👱♀️ E4.0 woman: blond hair
+1F471 200D 2640 ; minimally-qualified # 👱♀ E4.0 woman: blond hair
+1F471 1F3FB 200D 2640 FE0F ; fully-qualified # 👱🏻♀️ E4.0 woman: light skin tone, blond hair
+1F471 1F3FB 200D 2640 ; minimally-qualified # 👱🏻♀ E4.0 woman: light skin tone, blond hair
+1F471 1F3FC 200D 2640 FE0F ; fully-qualified # 👱🏼♀️ E4.0 woman: medium-light skin tone, blond hair
+1F471 1F3FC 200D 2640 ; minimally-qualified # 👱🏼♀ E4.0 woman: medium-light skin tone, blond hair
+1F471 1F3FD 200D 2640 FE0F ; fully-qualified # 👱🏽♀️ E4.0 woman: medium skin tone, blond hair
+1F471 1F3FD 200D 2640 ; minimally-qualified # 👱🏽♀ E4.0 woman: medium skin tone, blond hair
+1F471 1F3FE 200D 2640 FE0F ; fully-qualified # 👱🏾♀️ E4.0 woman: medium-dark skin tone, blond hair
+1F471 1F3FE 200D 2640 ; minimally-qualified # 👱🏾♀ E4.0 woman: medium-dark skin tone, blond hair
+1F471 1F3FF 200D 2640 FE0F ; fully-qualified # 👱🏿♀️ E4.0 woman: dark skin tone, blond hair
+1F471 1F3FF 200D 2640 ; minimally-qualified # 👱🏿♀ E4.0 woman: dark skin tone, blond hair
+1F471 200D 2642 FE0F ; fully-qualified # 👱♂️ E4.0 man: blond hair
+1F471 200D 2642 ; minimally-qualified # 👱♂ E4.0 man: blond hair
+1F471 1F3FB 200D 2642 FE0F ; fully-qualified # 👱🏻♂️ E4.0 man: light skin tone, blond hair
+1F471 1F3FB 200D 2642 ; minimally-qualified # 👱🏻♂ E4.0 man: light skin tone, blond hair
+1F471 1F3FC 200D 2642 FE0F ; fully-qualified # 👱🏼♂️ E4.0 man: medium-light skin tone, blond hair
+1F471 1F3FC 200D 2642 ; minimally-qualified # 👱🏼♂ E4.0 man: medium-light skin tone, blond hair
+1F471 1F3FD 200D 2642 FE0F ; fully-qualified # 👱🏽♂️ E4.0 man: medium skin tone, blond hair
+1F471 1F3FD 200D 2642 ; minimally-qualified # 👱🏽♂ E4.0 man: medium skin tone, blond hair
+1F471 1F3FE 200D 2642 FE0F ; fully-qualified # 👱🏾♂️ E4.0 man: medium-dark skin tone, blond hair
+1F471 1F3FE 200D 2642 ; minimally-qualified # 👱🏾♂ E4.0 man: medium-dark skin tone, blond hair
+1F471 1F3FF 200D 2642 FE0F ; fully-qualified # 👱🏿♂️ E4.0 man: dark skin tone, blond hair
+1F471 1F3FF 200D 2642 ; minimally-qualified # 👱🏿♂ E4.0 man: dark skin tone, blond hair
+1F9D3 ; fully-qualified # 🧓 E5.0 older person
+1F9D3 1F3FB ; fully-qualified # 🧓🏻 E5.0 older person: light skin tone
+1F9D3 1F3FC ; fully-qualified # 🧓🏼 E5.0 older person: medium-light skin tone
+1F9D3 1F3FD ; fully-qualified # 🧓🏽 E5.0 older person: medium skin tone
+1F9D3 1F3FE ; fully-qualified # 🧓🏾 E5.0 older person: medium-dark skin tone
+1F9D3 1F3FF ; fully-qualified # 🧓🏿 E5.0 older person: dark skin tone
+1F474 ; fully-qualified # 👴 E0.6 old man
+1F474 1F3FB ; fully-qualified # 👴🏻 E1.0 old man: light skin tone
+1F474 1F3FC ; fully-qualified # 👴🏼 E1.0 old man: medium-light skin tone
+1F474 1F3FD ; fully-qualified # 👴🏽 E1.0 old man: medium skin tone
+1F474 1F3FE ; fully-qualified # 👴🏾 E1.0 old man: medium-dark skin tone
+1F474 1F3FF ; fully-qualified # 👴🏿 E1.0 old man: dark skin tone
+1F475 ; fully-qualified # 👵 E0.6 old woman
+1F475 1F3FB ; fully-qualified # 👵🏻 E1.0 old woman: light skin tone
+1F475 1F3FC ; fully-qualified # 👵🏼 E1.0 old woman: medium-light skin tone
+1F475 1F3FD ; fully-qualified # 👵🏽 E1.0 old woman: medium skin tone
+1F475 1F3FE ; fully-qualified # 👵🏾 E1.0 old woman: medium-dark skin tone
+1F475 1F3FF ; fully-qualified # 👵🏿 E1.0 old woman: dark skin tone
+
+# subgroup: person-gesture
+1F64D ; fully-qualified # 🙍 E0.6 person frowning
+1F64D 1F3FB ; fully-qualified # 🙍🏻 E1.0 person frowning: light skin tone
+1F64D 1F3FC ; fully-qualified # 🙍🏼 E1.0 person frowning: medium-light skin tone
+1F64D 1F3FD ; fully-qualified # 🙍🏽 E1.0 person frowning: medium skin tone
+1F64D 1F3FE ; fully-qualified # 🙍🏾 E1.0 person frowning: medium-dark skin tone
+1F64D 1F3FF ; fully-qualified # 🙍🏿 E1.0 person frowning: dark skin tone
+1F64D 200D 2642 FE0F ; fully-qualified # 🙍♂️ E4.0 man frowning
+1F64D 200D 2642 ; minimally-qualified # 🙍♂ E4.0 man frowning
+1F64D 1F3FB 200D 2642 FE0F ; fully-qualified # 🙍🏻♂️ E4.0 man frowning: light skin tone
+1F64D 1F3FB 200D 2642 ; minimally-qualified # 🙍🏻♂ E4.0 man frowning: light skin tone
+1F64D 1F3FC 200D 2642 FE0F ; fully-qualified # 🙍🏼♂️ E4.0 man frowning: medium-light skin tone
+1F64D 1F3FC 200D 2642 ; minimally-qualified # 🙍🏼♂ E4.0 man frowning: medium-light skin tone
+1F64D 1F3FD 200D 2642 FE0F ; fully-qualified # 🙍🏽♂️ E4.0 man frowning: medium skin tone
+1F64D 1F3FD 200D 2642 ; minimally-qualified # 🙍🏽♂ E4.0 man frowning: medium skin tone
+1F64D 1F3FE 200D 2642 FE0F ; fully-qualified # 🙍🏾♂️ E4.0 man frowning: medium-dark skin tone
+1F64D 1F3FE 200D 2642 ; minimally-qualified # 🙍🏾♂ E4.0 man frowning: medium-dark skin tone
+1F64D 1F3FF 200D 2642 FE0F ; fully-qualified # 🙍🏿♂️ E4.0 man frowning: dark skin tone
+1F64D 1F3FF 200D 2642 ; minimally-qualified # 🙍🏿♂ E4.0 man frowning: dark skin tone
+1F64D 200D 2640 FE0F ; fully-qualified # 🙍♀️ E4.0 woman frowning
+1F64D 200D 2640 ; minimally-qualified # 🙍♀ E4.0 woman frowning
+1F64D 1F3FB 200D 2640 FE0F ; fully-qualified # 🙍🏻♀️ E4.0 woman frowning: light skin tone
+1F64D 1F3FB 200D 2640 ; minimally-qualified # 🙍🏻♀ E4.0 woman frowning: light skin tone
+1F64D 1F3FC 200D 2640 FE0F ; fully-qualified # 🙍🏼♀️ E4.0 woman frowning: medium-light skin tone
+1F64D 1F3FC 200D 2640 ; minimally-qualified # 🙍🏼♀ E4.0 woman frowning: medium-light skin tone
+1F64D 1F3FD 200D 2640 FE0F ; fully-qualified # 🙍🏽♀️ E4.0 woman frowning: medium skin tone
+1F64D 1F3FD 200D 2640 ; minimally-qualified # 🙍🏽♀ E4.0 woman frowning: medium skin tone
+1F64D 1F3FE 200D 2640 FE0F ; fully-qualified # 🙍🏾♀️ E4.0 woman frowning: medium-dark skin tone
+1F64D 1F3FE 200D 2640 ; minimally-qualified # 🙍🏾♀ E4.0 woman frowning: medium-dark skin tone
+1F64D 1F3FF 200D 2640 FE0F ; fully-qualified # 🙍🏿♀️ E4.0 woman frowning: dark skin tone
+1F64D 1F3FF 200D 2640 ; minimally-qualified # 🙍🏿♀ E4.0 woman frowning: dark skin tone
+1F64E ; fully-qualified # 🙎 E0.6 person pouting
+1F64E 1F3FB ; fully-qualified # 🙎🏻 E1.0 person pouting: light skin tone
+1F64E 1F3FC ; fully-qualified # 🙎🏼 E1.0 person pouting: medium-light skin tone
+1F64E 1F3FD ; fully-qualified # 🙎🏽 E1.0 person pouting: medium skin tone
+1F64E 1F3FE ; fully-qualified # 🙎🏾 E1.0 person pouting: medium-dark skin tone
+1F64E 1F3FF ; fully-qualified # 🙎🏿 E1.0 person pouting: dark skin tone
+1F64E 200D 2642 FE0F ; fully-qualified # 🙎♂️ E4.0 man pouting
+1F64E 200D 2642 ; minimally-qualified # 🙎♂ E4.0 man pouting
+1F64E 1F3FB 200D 2642 FE0F ; fully-qualified # 🙎🏻♂️ E4.0 man pouting: light skin tone
+1F64E 1F3FB 200D 2642 ; minimally-qualified # 🙎🏻♂ E4.0 man pouting: light skin tone
+1F64E 1F3FC 200D 2642 FE0F ; fully-qualified # 🙎🏼♂️ E4.0 man pouting: medium-light skin tone
+1F64E 1F3FC 200D 2642 ; minimally-qualified # 🙎🏼♂ E4.0 man pouting: medium-light skin tone
+1F64E 1F3FD 200D 2642 FE0F ; fully-qualified # 🙎🏽♂️ E4.0 man pouting: medium skin tone
+1F64E 1F3FD 200D 2642 ; minimally-qualified # 🙎🏽♂ E4.0 man pouting: medium skin tone
+1F64E 1F3FE 200D 2642 FE0F ; fully-qualified # 🙎🏾♂️ E4.0 man pouting: medium-dark skin tone
+1F64E 1F3FE 200D 2642 ; minimally-qualified # 🙎🏾♂ E4.0 man pouting: medium-dark skin tone
+1F64E 1F3FF 200D 2642 FE0F ; fully-qualified # 🙎🏿♂️ E4.0 man pouting: dark skin tone
+1F64E 1F3FF 200D 2642 ; minimally-qualified # 🙎🏿♂ E4.0 man pouting: dark skin tone
+1F64E 200D 2640 FE0F ; fully-qualified # 🙎♀️ E4.0 woman pouting
+1F64E 200D 2640 ; minimally-qualified # 🙎♀ E4.0 woman pouting
+1F64E 1F3FB 200D 2640 FE0F ; fully-qualified # 🙎🏻♀️ E4.0 woman pouting: light skin tone
+1F64E 1F3FB 200D 2640 ; minimally-qualified # 🙎🏻♀ E4.0 woman pouting: light skin tone
+1F64E 1F3FC 200D 2640 FE0F ; fully-qualified # 🙎🏼♀️ E4.0 woman pouting: medium-light skin tone
+1F64E 1F3FC 200D 2640 ; minimally-qualified # 🙎🏼♀ E4.0 woman pouting: medium-light skin tone
+1F64E 1F3FD 200D 2640 FE0F ; fully-qualified # 🙎🏽♀️ E4.0 woman pouting: medium skin tone
+1F64E 1F3FD 200D 2640 ; minimally-qualified # 🙎🏽♀ E4.0 woman pouting: medium skin tone
+1F64E 1F3FE 200D 2640 FE0F ; fully-qualified # 🙎🏾♀️ E4.0 woman pouting: medium-dark skin tone
+1F64E 1F3FE 200D 2640 ; minimally-qualified # 🙎🏾♀ E4.0 woman pouting: medium-dark skin tone
+1F64E 1F3FF 200D 2640 FE0F ; fully-qualified # 🙎🏿♀️ E4.0 woman pouting: dark skin tone
+1F64E 1F3FF 200D 2640 ; minimally-qualified # 🙎🏿♀ E4.0 woman pouting: dark skin tone
+1F645 ; fully-qualified # 🙅 E0.6 person gesturing NO
+1F645 1F3FB ; fully-qualified # 🙅🏻 E1.0 person gesturing NO: light skin tone
+1F645 1F3FC ; fully-qualified # 🙅🏼 E1.0 person gesturing NO: medium-light skin tone
+1F645 1F3FD ; fully-qualified # 🙅🏽 E1.0 person gesturing NO: medium skin tone
+1F645 1F3FE ; fully-qualified # 🙅🏾 E1.0 person gesturing NO: medium-dark skin tone
+1F645 1F3FF ; fully-qualified # 🙅🏿 E1.0 person gesturing NO: dark skin tone
+1F645 200D 2642 FE0F ; fully-qualified # 🙅♂️ E4.0 man gesturing NO
+1F645 200D 2642 ; minimally-qualified # 🙅♂ E4.0 man gesturing NO
+1F645 1F3FB 200D 2642 FE0F ; fully-qualified # 🙅🏻♂️ E4.0 man gesturing NO: light skin tone
+1F645 1F3FB 200D 2642 ; minimally-qualified # 🙅🏻♂ E4.0 man gesturing NO: light skin tone
+1F645 1F3FC 200D 2642 FE0F ; fully-qualified # 🙅🏼♂️ E4.0 man gesturing NO: medium-light skin tone
+1F645 1F3FC 200D 2642 ; minimally-qualified # 🙅🏼♂ E4.0 man gesturing NO: medium-light skin tone
+1F645 1F3FD 200D 2642 FE0F ; fully-qualified # 🙅🏽♂️ E4.0 man gesturing NO: medium skin tone
+1F645 1F3FD 200D 2642 ; minimally-qualified # 🙅🏽♂ E4.0 man gesturing NO: medium skin tone
+1F645 1F3FE 200D 2642 FE0F ; fully-qualified # 🙅🏾♂️ E4.0 man gesturing NO: medium-dark skin tone
+1F645 1F3FE 200D 2642 ; minimally-qualified # 🙅🏾♂ E4.0 man gesturing NO: medium-dark skin tone
+1F645 1F3FF 200D 2642 FE0F ; fully-qualified # 🙅🏿♂️ E4.0 man gesturing NO: dark skin tone
+1F645 1F3FF 200D 2642 ; minimally-qualified # 🙅🏿♂ E4.0 man gesturing NO: dark skin tone
+1F645 200D 2640 FE0F ; fully-qualified # 🙅♀️ E4.0 woman gesturing NO
+1F645 200D 2640 ; minimally-qualified # 🙅♀ E4.0 woman gesturing NO
+1F645 1F3FB 200D 2640 FE0F ; fully-qualified # 🙅🏻♀️ E4.0 woman gesturing NO: light skin tone
+1F645 1F3FB 200D 2640 ; minimally-qualified # 🙅🏻♀ E4.0 woman gesturing NO: light skin tone
+1F645 1F3FC 200D 2640 FE0F ; fully-qualified # 🙅🏼♀️ E4.0 woman gesturing NO: medium-light skin tone
+1F645 1F3FC 200D 2640 ; minimally-qualified # 🙅🏼♀ E4.0 woman gesturing NO: medium-light skin tone
+1F645 1F3FD 200D 2640 FE0F ; fully-qualified # 🙅🏽♀️ E4.0 woman gesturing NO: medium skin tone
+1F645 1F3FD 200D 2640 ; minimally-qualified # 🙅🏽♀ E4.0 woman gesturing NO: medium skin tone
+1F645 1F3FE 200D 2640 FE0F ; fully-qualified # 🙅🏾♀️ E4.0 woman gesturing NO: medium-dark skin tone
+1F645 1F3FE 200D 2640 ; minimally-qualified # 🙅🏾♀ E4.0 woman gesturing NO: medium-dark skin tone
+1F645 1F3FF 200D 2640 FE0F ; fully-qualified # 🙅🏿♀️ E4.0 woman gesturing NO: dark skin tone
+1F645 1F3FF 200D 2640 ; minimally-qualified # 🙅🏿♀ E4.0 woman gesturing NO: dark skin tone
+1F646 ; fully-qualified # 🙆 E0.6 person gesturing OK
+1F646 1F3FB ; fully-qualified # 🙆🏻 E1.0 person gesturing OK: light skin tone
+1F646 1F3FC ; fully-qualified # 🙆🏼 E1.0 person gesturing OK: medium-light skin tone
+1F646 1F3FD ; fully-qualified # 🙆🏽 E1.0 person gesturing OK: medium skin tone
+1F646 1F3FE ; fully-qualified # 🙆🏾 E1.0 person gesturing OK: medium-dark skin tone
+1F646 1F3FF ; fully-qualified # 🙆🏿 E1.0 person gesturing OK: dark skin tone
+1F646 200D 2642 FE0F ; fully-qualified # 🙆♂️ E4.0 man gesturing OK
+1F646 200D 2642 ; minimally-qualified # 🙆♂ E4.0 man gesturing OK
+1F646 1F3FB 200D 2642 FE0F ; fully-qualified # 🙆🏻♂️ E4.0 man gesturing OK: light skin tone
+1F646 1F3FB 200D 2642 ; minimally-qualified # 🙆🏻♂ E4.0 man gesturing OK: light skin tone
+1F646 1F3FC 200D 2642 FE0F ; fully-qualified # 🙆🏼♂️ E4.0 man gesturing OK: medium-light skin tone
+1F646 1F3FC 200D 2642 ; minimally-qualified # 🙆🏼♂ E4.0 man gesturing OK: medium-light skin tone
+1F646 1F3FD 200D 2642 FE0F ; fully-qualified # 🙆🏽♂️ E4.0 man gesturing OK: medium skin tone
+1F646 1F3FD 200D 2642 ; minimally-qualified # 🙆🏽♂ E4.0 man gesturing OK: medium skin tone
+1F646 1F3FE 200D 2642 FE0F ; fully-qualified # 🙆🏾♂️ E4.0 man gesturing OK: medium-dark skin tone
+1F646 1F3FE 200D 2642 ; minimally-qualified # 🙆🏾♂ E4.0 man gesturing OK: medium-dark skin tone
+1F646 1F3FF 200D 2642 FE0F ; fully-qualified # 🙆🏿♂️ E4.0 man gesturing OK: dark skin tone
+1F646 1F3FF 200D 2642 ; minimally-qualified # 🙆🏿♂ E4.0 man gesturing OK: dark skin tone
+1F646 200D 2640 FE0F ; fully-qualified # 🙆♀️ E4.0 woman gesturing OK
+1F646 200D 2640 ; minimally-qualified # 🙆♀ E4.0 woman gesturing OK
+1F646 1F3FB 200D 2640 FE0F ; fully-qualified # 🙆🏻♀️ E4.0 woman gesturing OK: light skin tone
+1F646 1F3FB 200D 2640 ; minimally-qualified # 🙆🏻♀ E4.0 woman gesturing OK: light skin tone
+1F646 1F3FC 200D 2640 FE0F ; fully-qualified # 🙆🏼♀️ E4.0 woman gesturing OK: medium-light skin tone
+1F646 1F3FC 200D 2640 ; minimally-qualified # 🙆🏼♀ E4.0 woman gesturing OK: medium-light skin tone
+1F646 1F3FD 200D 2640 FE0F ; fully-qualified # 🙆🏽♀️ E4.0 woman gesturing OK: medium skin tone
+1F646 1F3FD 200D 2640 ; minimally-qualified # 🙆🏽♀ E4.0 woman gesturing OK: medium skin tone
+1F646 1F3FE 200D 2640 FE0F ; fully-qualified # 🙆🏾♀️ E4.0 woman gesturing OK: medium-dark skin tone
+1F646 1F3FE 200D 2640 ; minimally-qualified # 🙆🏾♀ E4.0 woman gesturing OK: medium-dark skin tone
+1F646 1F3FF 200D 2640 FE0F ; fully-qualified # 🙆🏿♀️ E4.0 woman gesturing OK: dark skin tone
+1F646 1F3FF 200D 2640 ; minimally-qualified # 🙆🏿♀ E4.0 woman gesturing OK: dark skin tone
+1F481 ; fully-qualified # 💁 E0.6 person tipping hand
+1F481 1F3FB ; fully-qualified # 💁🏻 E1.0 person tipping hand: light skin tone
+1F481 1F3FC ; fully-qualified # 💁🏼 E1.0 person tipping hand: medium-light skin tone
+1F481 1F3FD ; fully-qualified # 💁🏽 E1.0 person tipping hand: medium skin tone
+1F481 1F3FE ; fully-qualified # 💁🏾 E1.0 person tipping hand: medium-dark skin tone
+1F481 1F3FF ; fully-qualified # 💁🏿 E1.0 person tipping hand: dark skin tone
+1F481 200D 2642 FE0F ; fully-qualified # 💁♂️ E4.0 man tipping hand
+1F481 200D 2642 ; minimally-qualified # 💁♂ E4.0 man tipping hand
+1F481 1F3FB 200D 2642 FE0F ; fully-qualified # 💁🏻♂️ E4.0 man tipping hand: light skin tone
+1F481 1F3FB 200D 2642 ; minimally-qualified # 💁🏻♂ E4.0 man tipping hand: light skin tone
+1F481 1F3FC 200D 2642 FE0F ; fully-qualified # 💁🏼♂️ E4.0 man tipping hand: medium-light skin tone
+1F481 1F3FC 200D 2642 ; minimally-qualified # 💁🏼♂ E4.0 man tipping hand: medium-light skin tone
+1F481 1F3FD 200D 2642 FE0F ; fully-qualified # 💁🏽♂️ E4.0 man tipping hand: medium skin tone
+1F481 1F3FD 200D 2642 ; minimally-qualified # 💁🏽♂ E4.0 man tipping hand: medium skin tone
+1F481 1F3FE 200D 2642 FE0F ; fully-qualified # 💁🏾♂️ E4.0 man tipping hand: medium-dark skin tone
+1F481 1F3FE 200D 2642 ; minimally-qualified # 💁🏾♂ E4.0 man tipping hand: medium-dark skin tone
+1F481 1F3FF 200D 2642 FE0F ; fully-qualified # 💁🏿♂️ E4.0 man tipping hand: dark skin tone
+1F481 1F3FF 200D 2642 ; minimally-qualified # 💁🏿♂ E4.0 man tipping hand: dark skin tone
+1F481 200D 2640 FE0F ; fully-qualified # 💁♀️ E4.0 woman tipping hand
+1F481 200D 2640 ; minimally-qualified # 💁♀ E4.0 woman tipping hand
+1F481 1F3FB 200D 2640 FE0F ; fully-qualified # 💁🏻♀️ E4.0 woman tipping hand: light skin tone
+1F481 1F3FB 200D 2640 ; minimally-qualified # 💁🏻♀ E4.0 woman tipping hand: light skin tone
+1F481 1F3FC 200D 2640 FE0F ; fully-qualified # 💁🏼♀️ E4.0 woman tipping hand: medium-light skin tone
+1F481 1F3FC 200D 2640 ; minimally-qualified # 💁🏼♀ E4.0 woman tipping hand: medium-light skin tone
+1F481 1F3FD 200D 2640 FE0F ; fully-qualified # 💁🏽♀️ E4.0 woman tipping hand: medium skin tone
+1F481 1F3FD 200D 2640 ; minimally-qualified # 💁🏽♀ E4.0 woman tipping hand: medium skin tone
+1F481 1F3FE 200D 2640 FE0F ; fully-qualified # 💁🏾♀️ E4.0 woman tipping hand: medium-dark skin tone
+1F481 1F3FE 200D 2640 ; minimally-qualified # 💁🏾♀ E4.0 woman tipping hand: medium-dark skin tone
+1F481 1F3FF 200D 2640 FE0F ; fully-qualified # 💁🏿♀️ E4.0 woman tipping hand: dark skin tone
+1F481 1F3FF 200D 2640 ; minimally-qualified # 💁🏿♀ E4.0 woman tipping hand: dark skin tone
+1F64B ; fully-qualified # 🙋 E0.6 person raising hand
+1F64B 1F3FB ; fully-qualified # 🙋🏻 E1.0 person raising hand: light skin tone
+1F64B 1F3FC ; fully-qualified # 🙋🏼 E1.0 person raising hand: medium-light skin tone
+1F64B 1F3FD ; fully-qualified # 🙋🏽 E1.0 person raising hand: medium skin tone
+1F64B 1F3FE ; fully-qualified # 🙋🏾 E1.0 person raising hand: medium-dark skin tone
+1F64B 1F3FF ; fully-qualified # 🙋🏿 E1.0 person raising hand: dark skin tone
+1F64B 200D 2642 FE0F ; fully-qualified # 🙋♂️ E4.0 man raising hand
+1F64B 200D 2642 ; minimally-qualified # 🙋♂ E4.0 man raising hand
+1F64B 1F3FB 200D 2642 FE0F ; fully-qualified # 🙋🏻♂️ E4.0 man raising hand: light skin tone
+1F64B 1F3FB 200D 2642 ; minimally-qualified # 🙋🏻♂ E4.0 man raising hand: light skin tone
+1F64B 1F3FC 200D 2642 FE0F ; fully-qualified # 🙋🏼♂️ E4.0 man raising hand: medium-light skin tone
+1F64B 1F3FC 200D 2642 ; minimally-qualified # 🙋🏼♂ E4.0 man raising hand: medium-light skin tone
+1F64B 1F3FD 200D 2642 FE0F ; fully-qualified # 🙋🏽♂️ E4.0 man raising hand: medium skin tone
+1F64B 1F3FD 200D 2642 ; minimally-qualified # 🙋🏽♂ E4.0 man raising hand: medium skin tone
+1F64B 1F3FE 200D 2642 FE0F ; fully-qualified # 🙋🏾♂️ E4.0 man raising hand: medium-dark skin tone
+1F64B 1F3FE 200D 2642 ; minimally-qualified # 🙋🏾♂ E4.0 man raising hand: medium-dark skin tone
+1F64B 1F3FF 200D 2642 FE0F ; fully-qualified # 🙋🏿♂️ E4.0 man raising hand: dark skin tone
+1F64B 1F3FF 200D 2642 ; minimally-qualified # 🙋🏿♂ E4.0 man raising hand: dark skin tone
+1F64B 200D 2640 FE0F ; fully-qualified # 🙋♀️ E4.0 woman raising hand
+1F64B 200D 2640 ; minimally-qualified # 🙋♀ E4.0 woman raising hand
+1F64B 1F3FB 200D 2640 FE0F ; fully-qualified # 🙋🏻♀️ E4.0 woman raising hand: light skin tone
+1F64B 1F3FB 200D 2640 ; minimally-qualified # 🙋🏻♀ E4.0 woman raising hand: light skin tone
+1F64B 1F3FC 200D 2640 FE0F ; fully-qualified # 🙋🏼♀️ E4.0 woman raising hand: medium-light skin tone
+1F64B 1F3FC 200D 2640 ; minimally-qualified # 🙋🏼♀ E4.0 woman raising hand: medium-light skin tone
+1F64B 1F3FD 200D 2640 FE0F ; fully-qualified # 🙋🏽♀️ E4.0 woman raising hand: medium skin tone
+1F64B 1F3FD 200D 2640 ; minimally-qualified # 🙋🏽♀ E4.0 woman raising hand: medium skin tone
+1F64B 1F3FE 200D 2640 FE0F ; fully-qualified # 🙋🏾♀️ E4.0 woman raising hand: medium-dark skin tone
+1F64B 1F3FE 200D 2640 ; minimally-qualified # 🙋🏾♀ E4.0 woman raising hand: medium-dark skin tone
+1F64B 1F3FF 200D 2640 FE0F ; fully-qualified # 🙋🏿♀️ E4.0 woman raising hand: dark skin tone
+1F64B 1F3FF 200D 2640 ; minimally-qualified # 🙋🏿♀ E4.0 woman raising hand: dark skin tone
+1F9CF ; fully-qualified # 🧏 E12.0 deaf person
+1F9CF 1F3FB ; fully-qualified # 🧏🏻 E12.0 deaf person: light skin tone
+1F9CF 1F3FC ; fully-qualified # 🧏🏼 E12.0 deaf person: medium-light skin tone
+1F9CF 1F3FD ; fully-qualified # 🧏🏽 E12.0 deaf person: medium skin tone
+1F9CF 1F3FE ; fully-qualified # 🧏🏾 E12.0 deaf person: medium-dark skin tone
+1F9CF 1F3FF ; fully-qualified # 🧏🏿 E12.0 deaf person: dark skin tone
+1F9CF 200D 2642 FE0F ; fully-qualified # 🧏♂️ E12.0 deaf man
+1F9CF 200D 2642 ; minimally-qualified # 🧏♂ E12.0 deaf man
+1F9CF 1F3FB 200D 2642 FE0F ; fully-qualified # 🧏🏻♂️ E12.0 deaf man: light skin tone
+1F9CF 1F3FB 200D 2642 ; minimally-qualified # 🧏🏻♂ E12.0 deaf man: light skin tone
+1F9CF 1F3FC 200D 2642 FE0F ; fully-qualified # 🧏🏼♂️ E12.0 deaf man: medium-light skin tone
+1F9CF 1F3FC 200D 2642 ; minimally-qualified # 🧏🏼♂ E12.0 deaf man: medium-light skin tone
+1F9CF 1F3FD 200D 2642 FE0F ; fully-qualified # 🧏🏽♂️ E12.0 deaf man: medium skin tone
+1F9CF 1F3FD 200D 2642 ; minimally-qualified # 🧏🏽♂ E12.0 deaf man: medium skin tone
+1F9CF 1F3FE 200D 2642 FE0F ; fully-qualified # 🧏🏾♂️ E12.0 deaf man: medium-dark skin tone
+1F9CF 1F3FE 200D 2642 ; minimally-qualified # 🧏🏾♂ E12.0 deaf man: medium-dark skin tone
+1F9CF 1F3FF 200D 2642 FE0F ; fully-qualified # 🧏🏿♂️ E12.0 deaf man: dark skin tone
+1F9CF 1F3FF 200D 2642 ; minimally-qualified # 🧏🏿♂ E12.0 deaf man: dark skin tone
+1F9CF 200D 2640 FE0F ; fully-qualified # 🧏♀️ E12.0 deaf woman
+1F9CF 200D 2640 ; minimally-qualified # 🧏♀ E12.0 deaf woman
+1F9CF 1F3FB 200D 2640 FE0F ; fully-qualified # 🧏🏻♀️ E12.0 deaf woman: light skin tone
+1F9CF 1F3FB 200D 2640 ; minimally-qualified # 🧏🏻♀ E12.0 deaf woman: light skin tone
+1F9CF 1F3FC 200D 2640 FE0F ; fully-qualified # 🧏🏼♀️ E12.0 deaf woman: medium-light skin tone
+1F9CF 1F3FC 200D 2640 ; minimally-qualified # 🧏🏼♀ E12.0 deaf woman: medium-light skin tone
+1F9CF 1F3FD 200D 2640 FE0F ; fully-qualified # 🧏🏽♀️ E12.0 deaf woman: medium skin tone
+1F9CF 1F3FD 200D 2640 ; minimally-qualified # 🧏🏽♀ E12.0 deaf woman: medium skin tone
+1F9CF 1F3FE 200D 2640 FE0F ; fully-qualified # 🧏🏾♀️ E12.0 deaf woman: medium-dark skin tone
+1F9CF 1F3FE 200D 2640 ; minimally-qualified # 🧏🏾♀ E12.0 deaf woman: medium-dark skin tone
+1F9CF 1F3FF 200D 2640 FE0F ; fully-qualified # 🧏🏿♀️ E12.0 deaf woman: dark skin tone
+1F9CF 1F3FF 200D 2640 ; minimally-qualified # 🧏🏿♀ E12.0 deaf woman: dark skin tone
+1F647 ; fully-qualified # 🙇 E0.6 person bowing
+1F647 1F3FB ; fully-qualified # 🙇🏻 E1.0 person bowing: light skin tone
+1F647 1F3FC ; fully-qualified # 🙇🏼 E1.0 person bowing: medium-light skin tone
+1F647 1F3FD ; fully-qualified # 🙇🏽 E1.0 person bowing: medium skin tone
+1F647 1F3FE ; fully-qualified # 🙇🏾 E1.0 person bowing: medium-dark skin tone
+1F647 1F3FF ; fully-qualified # 🙇🏿 E1.0 person bowing: dark skin tone
+1F647 200D 2642 FE0F ; fully-qualified # 🙇♂️ E4.0 man bowing
+1F647 200D 2642 ; minimally-qualified # 🙇♂ E4.0 man bowing
+1F647 1F3FB 200D 2642 FE0F ; fully-qualified # 🙇🏻♂️ E4.0 man bowing: light skin tone
+1F647 1F3FB 200D 2642 ; minimally-qualified # 🙇🏻♂ E4.0 man bowing: light skin tone
+1F647 1F3FC 200D 2642 FE0F ; fully-qualified # 🙇🏼♂️ E4.0 man bowing: medium-light skin tone
+1F647 1F3FC 200D 2642 ; minimally-qualified # 🙇🏼♂ E4.0 man bowing: medium-light skin tone
+1F647 1F3FD 200D 2642 FE0F ; fully-qualified # 🙇🏽♂️ E4.0 man bowing: medium skin tone
+1F647 1F3FD 200D 2642 ; minimally-qualified # 🙇🏽♂ E4.0 man bowing: medium skin tone
+1F647 1F3FE 200D 2642 FE0F ; fully-qualified # 🙇🏾♂️ E4.0 man bowing: medium-dark skin tone
+1F647 1F3FE 200D 2642 ; minimally-qualified # 🙇🏾♂ E4.0 man bowing: medium-dark skin tone
+1F647 1F3FF 200D 2642 FE0F ; fully-qualified # 🙇🏿♂️ E4.0 man bowing: dark skin tone
+1F647 1F3FF 200D 2642 ; minimally-qualified # 🙇🏿♂ E4.0 man bowing: dark skin tone
+1F647 200D 2640 FE0F ; fully-qualified # 🙇♀️ E4.0 woman bowing
+1F647 200D 2640 ; minimally-qualified # 🙇♀ E4.0 woman bowing
+1F647 1F3FB 200D 2640 FE0F ; fully-qualified # 🙇🏻♀️ E4.0 woman bowing: light skin tone
+1F647 1F3FB 200D 2640 ; minimally-qualified # 🙇🏻♀ E4.0 woman bowing: light skin tone
+1F647 1F3FC 200D 2640 FE0F ; fully-qualified # 🙇🏼♀️ E4.0 woman bowing: medium-light skin tone
+1F647 1F3FC 200D 2640 ; minimally-qualified # 🙇🏼♀ E4.0 woman bowing: medium-light skin tone
+1F647 1F3FD 200D 2640 FE0F ; fully-qualified # 🙇🏽♀️ E4.0 woman bowing: medium skin tone
+1F647 1F3FD 200D 2640 ; minimally-qualified # 🙇🏽♀ E4.0 woman bowing: medium skin tone
+1F647 1F3FE 200D 2640 FE0F ; fully-qualified # 🙇🏾♀️ E4.0 woman bowing: medium-dark skin tone
+1F647 1F3FE 200D 2640 ; minimally-qualified # 🙇🏾♀ E4.0 woman bowing: medium-dark skin tone
+1F647 1F3FF 200D 2640 FE0F ; fully-qualified # 🙇🏿♀️ E4.0 woman bowing: dark skin tone
+1F647 1F3FF 200D 2640 ; minimally-qualified # 🙇🏿♀ E4.0 woman bowing: dark skin tone
+1F926 ; fully-qualified # 🤦 E3.0 person facepalming
+1F926 1F3FB ; fully-qualified # 🤦🏻 E3.0 person facepalming: light skin tone
+1F926 1F3FC ; fully-qualified # 🤦🏼 E3.0 person facepalming: medium-light skin tone
+1F926 1F3FD ; fully-qualified # 🤦🏽 E3.0 person facepalming: medium skin tone
+1F926 1F3FE ; fully-qualified # 🤦🏾 E3.0 person facepalming: medium-dark skin tone
+1F926 1F3FF ; fully-qualified # 🤦🏿 E3.0 person facepalming: dark skin tone
+1F926 200D 2642 FE0F ; fully-qualified # 🤦♂️ E4.0 man facepalming
+1F926 200D 2642 ; minimally-qualified # 🤦♂ E4.0 man facepalming
+1F926 1F3FB 200D 2642 FE0F ; fully-qualified # 🤦🏻♂️ E4.0 man facepalming: light skin tone
+1F926 1F3FB 200D 2642 ; minimally-qualified # 🤦🏻♂ E4.0 man facepalming: light skin tone
+1F926 1F3FC 200D 2642 FE0F ; fully-qualified # 🤦🏼♂️ E4.0 man facepalming: medium-light skin tone
+1F926 1F3FC 200D 2642 ; minimally-qualified # 🤦🏼♂ E4.0 man facepalming: medium-light skin tone
+1F926 1F3FD 200D 2642 FE0F ; fully-qualified # 🤦🏽♂️ E4.0 man facepalming: medium skin tone
+1F926 1F3FD 200D 2642 ; minimally-qualified # 🤦🏽♂ E4.0 man facepalming: medium skin tone
+1F926 1F3FE 200D 2642 FE0F ; fully-qualified # 🤦🏾♂️ E4.0 man facepalming: medium-dark skin tone
+1F926 1F3FE 200D 2642 ; minimally-qualified # 🤦🏾♂ E4.0 man facepalming: medium-dark skin tone
+1F926 1F3FF 200D 2642 FE0F ; fully-qualified # 🤦🏿♂️ E4.0 man facepalming: dark skin tone
+1F926 1F3FF 200D 2642 ; minimally-qualified # 🤦🏿♂ E4.0 man facepalming: dark skin tone
+1F926 200D 2640 FE0F ; fully-qualified # 🤦♀️ E4.0 woman facepalming
+1F926 200D 2640 ; minimally-qualified # 🤦♀ E4.0 woman facepalming
+1F926 1F3FB 200D 2640 FE0F ; fully-qualified # 🤦🏻♀️ E4.0 woman facepalming: light skin tone
+1F926 1F3FB 200D 2640 ; minimally-qualified # 🤦🏻♀ E4.0 woman facepalming: light skin tone
+1F926 1F3FC 200D 2640 FE0F ; fully-qualified # 🤦🏼♀️ E4.0 woman facepalming: medium-light skin tone
+1F926 1F3FC 200D 2640 ; minimally-qualified # 🤦🏼♀ E4.0 woman facepalming: medium-light skin tone
+1F926 1F3FD 200D 2640 FE0F ; fully-qualified # 🤦🏽♀️ E4.0 woman facepalming: medium skin tone
+1F926 1F3FD 200D 2640 ; minimally-qualified # 🤦🏽♀ E4.0 woman facepalming: medium skin tone
+1F926 1F3FE 200D 2640 FE0F ; fully-qualified # 🤦🏾♀️ E4.0 woman facepalming: medium-dark skin tone
+1F926 1F3FE 200D 2640 ; minimally-qualified # 🤦🏾♀ E4.0 woman facepalming: medium-dark skin tone
+1F926 1F3FF 200D 2640 FE0F ; fully-qualified # 🤦🏿♀️ E4.0 woman facepalming: dark skin tone
+1F926 1F3FF 200D 2640 ; minimally-qualified # 🤦🏿♀ E4.0 woman facepalming: dark skin tone
+1F937 ; fully-qualified # 🤷 E3.0 person shrugging
+1F937 1F3FB ; fully-qualified # 🤷🏻 E3.0 person shrugging: light skin tone
+1F937 1F3FC ; fully-qualified # 🤷🏼 E3.0 person shrugging: medium-light skin tone
+1F937 1F3FD ; fully-qualified # 🤷🏽 E3.0 person shrugging: medium skin tone
+1F937 1F3FE ; fully-qualified # 🤷🏾 E3.0 person shrugging: medium-dark skin tone
+1F937 1F3FF ; fully-qualified # 🤷🏿 E3.0 person shrugging: dark skin tone
+1F937 200D 2642 FE0F ; fully-qualified # 🤷♂️ E4.0 man shrugging
+1F937 200D 2642 ; minimally-qualified # 🤷♂ E4.0 man shrugging
+1F937 1F3FB 200D 2642 FE0F ; fully-qualified # 🤷🏻♂️ E4.0 man shrugging: light skin tone
+1F937 1F3FB 200D 2642 ; minimally-qualified # 🤷🏻♂ E4.0 man shrugging: light skin tone
+1F937 1F3FC 200D 2642 FE0F ; fully-qualified # 🤷🏼♂️ E4.0 man shrugging: medium-light skin tone
+1F937 1F3FC 200D 2642 ; minimally-qualified # 🤷🏼♂ E4.0 man shrugging: medium-light skin tone
+1F937 1F3FD 200D 2642 FE0F ; fully-qualified # 🤷🏽♂️ E4.0 man shrugging: medium skin tone
+1F937 1F3FD 200D 2642 ; minimally-qualified # 🤷🏽♂ E4.0 man shrugging: medium skin tone
+1F937 1F3FE 200D 2642 FE0F ; fully-qualified # 🤷🏾♂️ E4.0 man shrugging: medium-dark skin tone
+1F937 1F3FE 200D 2642 ; minimally-qualified # 🤷🏾♂ E4.0 man shrugging: medium-dark skin tone
+1F937 1F3FF 200D 2642 FE0F ; fully-qualified # 🤷🏿♂️ E4.0 man shrugging: dark skin tone
+1F937 1F3FF 200D 2642 ; minimally-qualified # 🤷🏿♂ E4.0 man shrugging: dark skin tone
+1F937 200D 2640 FE0F ; fully-qualified # 🤷♀️ E4.0 woman shrugging
+1F937 200D 2640 ; minimally-qualified # 🤷♀ E4.0 woman shrugging
+1F937 1F3FB 200D 2640 FE0F ; fully-qualified # 🤷🏻♀️ E4.0 woman shrugging: light skin tone
+1F937 1F3FB 200D 2640 ; minimally-qualified # 🤷🏻♀ E4.0 woman shrugging: light skin tone
+1F937 1F3FC 200D 2640 FE0F ; fully-qualified # 🤷🏼♀️ E4.0 woman shrugging: medium-light skin tone
+1F937 1F3FC 200D 2640 ; minimally-qualified # 🤷🏼♀ E4.0 woman shrugging: medium-light skin tone
+1F937 1F3FD 200D 2640 FE0F ; fully-qualified # 🤷🏽♀️ E4.0 woman shrugging: medium skin tone
+1F937 1F3FD 200D 2640 ; minimally-qualified # 🤷🏽♀ E4.0 woman shrugging: medium skin tone
+1F937 1F3FE 200D 2640 FE0F ; fully-qualified # 🤷🏾♀️ E4.0 woman shrugging: medium-dark skin tone
+1F937 1F3FE 200D 2640 ; minimally-qualified # 🤷🏾♀ E4.0 woman shrugging: medium-dark skin tone
+1F937 1F3FF 200D 2640 FE0F ; fully-qualified # 🤷🏿♀️ E4.0 woman shrugging: dark skin tone
+1F937 1F3FF 200D 2640 ; minimally-qualified # 🤷🏿♀ E4.0 woman shrugging: dark skin tone
+
+# subgroup: person-role
+1F9D1 200D 2695 FE0F ; fully-qualified # 🧑⚕️ E12.1 health worker
+1F9D1 200D 2695 ; minimally-qualified # 🧑⚕ E12.1 health worker
+1F9D1 1F3FB 200D 2695 FE0F ; fully-qualified # 🧑🏻⚕️ E12.1 health worker: light skin tone
+1F9D1 1F3FB 200D 2695 ; minimally-qualified # 🧑🏻⚕ E12.1 health worker: light skin tone
+1F9D1 1F3FC 200D 2695 FE0F ; fully-qualified # 🧑🏼⚕️ E12.1 health worker: medium-light skin tone
+1F9D1 1F3FC 200D 2695 ; minimally-qualified # 🧑🏼⚕ E12.1 health worker: medium-light skin tone
+1F9D1 1F3FD 200D 2695 FE0F ; fully-qualified # 🧑🏽⚕️ E12.1 health worker: medium skin tone
+1F9D1 1F3FD 200D 2695 ; minimally-qualified # 🧑🏽⚕ E12.1 health worker: medium skin tone
+1F9D1 1F3FE 200D 2695 FE0F ; fully-qualified # 🧑🏾⚕️ E12.1 health worker: medium-dark skin tone
+1F9D1 1F3FE 200D 2695 ; minimally-qualified # 🧑🏾⚕ E12.1 health worker: medium-dark skin tone
+1F9D1 1F3FF 200D 2695 FE0F ; fully-qualified # 🧑🏿⚕️ E12.1 health worker: dark skin tone
+1F9D1 1F3FF 200D 2695 ; minimally-qualified # 🧑🏿⚕ E12.1 health worker: dark skin tone
+1F468 200D 2695 FE0F ; fully-qualified # 👨⚕️ E4.0 man health worker
+1F468 200D 2695 ; minimally-qualified # 👨⚕ E4.0 man health worker
+1F468 1F3FB 200D 2695 FE0F ; fully-qualified # 👨🏻⚕️ E4.0 man health worker: light skin tone
+1F468 1F3FB 200D 2695 ; minimally-qualified # 👨🏻⚕ E4.0 man health worker: light skin tone
+1F468 1F3FC 200D 2695 FE0F ; fully-qualified # 👨🏼⚕️ E4.0 man health worker: medium-light skin tone
+1F468 1F3FC 200D 2695 ; minimally-qualified # 👨🏼⚕ E4.0 man health worker: medium-light skin tone
+1F468 1F3FD 200D 2695 FE0F ; fully-qualified # 👨🏽⚕️ E4.0 man health worker: medium skin tone
+1F468 1F3FD 200D 2695 ; minimally-qualified # 👨🏽⚕ E4.0 man health worker: medium skin tone
+1F468 1F3FE 200D 2695 FE0F ; fully-qualified # 👨🏾⚕️ E4.0 man health worker: medium-dark skin tone
+1F468 1F3FE 200D 2695 ; minimally-qualified # 👨🏾⚕ E4.0 man health worker: medium-dark skin tone
+1F468 1F3FF 200D 2695 FE0F ; fully-qualified # 👨🏿⚕️ E4.0 man health worker: dark skin tone
+1F468 1F3FF 200D 2695 ; minimally-qualified # 👨🏿⚕ E4.0 man health worker: dark skin tone
+1F469 200D 2695 FE0F ; fully-qualified # 👩⚕️ E4.0 woman health worker
+1F469 200D 2695 ; minimally-qualified # 👩⚕ E4.0 woman health worker
+1F469 1F3FB 200D 2695 FE0F ; fully-qualified # 👩🏻⚕️ E4.0 woman health worker: light skin tone
+1F469 1F3FB 200D 2695 ; minimally-qualified # 👩🏻⚕ E4.0 woman health worker: light skin tone
+1F469 1F3FC 200D 2695 FE0F ; fully-qualified # 👩🏼⚕️ E4.0 woman health worker: medium-light skin tone
+1F469 1F3FC 200D 2695 ; minimally-qualified # 👩🏼⚕ E4.0 woman health worker: medium-light skin tone
+1F469 1F3FD 200D 2695 FE0F ; fully-qualified # 👩🏽⚕️ E4.0 woman health worker: medium skin tone
+1F469 1F3FD 200D 2695 ; minimally-qualified # 👩🏽⚕ E4.0 woman health worker: medium skin tone
+1F469 1F3FE 200D 2695 FE0F ; fully-qualified # 👩🏾⚕️ E4.0 woman health worker: medium-dark skin tone
+1F469 1F3FE 200D 2695 ; minimally-qualified # 👩🏾⚕ E4.0 woman health worker: medium-dark skin tone
+1F469 1F3FF 200D 2695 FE0F ; fully-qualified # 👩🏿⚕️ E4.0 woman health worker: dark skin tone
+1F469 1F3FF 200D 2695 ; minimally-qualified # 👩🏿⚕ E4.0 woman health worker: dark skin tone
+1F9D1 200D 1F393 ; fully-qualified # 🧑🎓 E12.1 student
+1F9D1 1F3FB 200D 1F393 ; fully-qualified # 🧑🏻🎓 E12.1 student: light skin tone
+1F9D1 1F3FC 200D 1F393 ; fully-qualified # 🧑🏼🎓 E12.1 student: medium-light skin tone
+1F9D1 1F3FD 200D 1F393 ; fully-qualified # 🧑🏽🎓 E12.1 student: medium skin tone
+1F9D1 1F3FE 200D 1F393 ; fully-qualified # 🧑🏾🎓 E12.1 student: medium-dark skin tone
+1F9D1 1F3FF 200D 1F393 ; fully-qualified # 🧑🏿🎓 E12.1 student: dark skin tone
+1F468 200D 1F393 ; fully-qualified # 👨🎓 E4.0 man student
+1F468 1F3FB 200D 1F393 ; fully-qualified # 👨🏻🎓 E4.0 man student: light skin tone
+1F468 1F3FC 200D 1F393 ; fully-qualified # 👨🏼🎓 E4.0 man student: medium-light skin tone
+1F468 1F3FD 200D 1F393 ; fully-qualified # 👨🏽🎓 E4.0 man student: medium skin tone
+1F468 1F3FE 200D 1F393 ; fully-qualified # 👨🏾🎓 E4.0 man student: medium-dark skin tone
+1F468 1F3FF 200D 1F393 ; fully-qualified # 👨🏿🎓 E4.0 man student: dark skin tone
+1F469 200D 1F393 ; fully-qualified # 👩🎓 E4.0 woman student
+1F469 1F3FB 200D 1F393 ; fully-qualified # 👩🏻🎓 E4.0 woman student: light skin tone
+1F469 1F3FC 200D 1F393 ; fully-qualified # 👩🏼🎓 E4.0 woman student: medium-light skin tone
+1F469 1F3FD 200D 1F393 ; fully-qualified # 👩🏽🎓 E4.0 woman student: medium skin tone
+1F469 1F3FE 200D 1F393 ; fully-qualified # 👩🏾🎓 E4.0 woman student: medium-dark skin tone
+1F469 1F3FF 200D 1F393 ; fully-qualified # 👩🏿🎓 E4.0 woman student: dark skin tone
+1F9D1 200D 1F3EB ; fully-qualified # 🧑🏫 E12.1 teacher
+1F9D1 1F3FB 200D 1F3EB ; fully-qualified # 🧑🏻🏫 E12.1 teacher: light skin tone
+1F9D1 1F3FC 200D 1F3EB ; fully-qualified # 🧑🏼🏫 E12.1 teacher: medium-light skin tone
+1F9D1 1F3FD 200D 1F3EB ; fully-qualified # 🧑🏽🏫 E12.1 teacher: medium skin tone
+1F9D1 1F3FE 200D 1F3EB ; fully-qualified # 🧑🏾🏫 E12.1 teacher: medium-dark skin tone
+1F9D1 1F3FF 200D 1F3EB ; fully-qualified # 🧑🏿🏫 E12.1 teacher: dark skin tone
+1F468 200D 1F3EB ; fully-qualified # 👨🏫 E4.0 man teacher
+1F468 1F3FB 200D 1F3EB ; fully-qualified # 👨🏻🏫 E4.0 man teacher: light skin tone
+1F468 1F3FC 200D 1F3EB ; fully-qualified # 👨🏼🏫 E4.0 man teacher: medium-light skin tone
+1F468 1F3FD 200D 1F3EB ; fully-qualified # 👨🏽🏫 E4.0 man teacher: medium skin tone
+1F468 1F3FE 200D 1F3EB ; fully-qualified # 👨🏾🏫 E4.0 man teacher: medium-dark skin tone
+1F468 1F3FF 200D 1F3EB ; fully-qualified # 👨🏿🏫 E4.0 man teacher: dark skin tone
+1F469 200D 1F3EB ; fully-qualified # 👩🏫 E4.0 woman teacher
+1F469 1F3FB 200D 1F3EB ; fully-qualified # 👩🏻🏫 E4.0 woman teacher: light skin tone
+1F469 1F3FC 200D 1F3EB ; fully-qualified # 👩🏼🏫 E4.0 woman teacher: medium-light skin tone
+1F469 1F3FD 200D 1F3EB ; fully-qualified # 👩🏽🏫 E4.0 woman teacher: medium skin tone
+1F469 1F3FE 200D 1F3EB ; fully-qualified # 👩🏾🏫 E4.0 woman teacher: medium-dark skin tone
+1F469 1F3FF 200D 1F3EB ; fully-qualified # 👩🏿🏫 E4.0 woman teacher: dark skin tone
+1F9D1 200D 2696 FE0F ; fully-qualified # 🧑⚖️ E12.1 judge
+1F9D1 200D 2696 ; minimally-qualified # 🧑⚖ E12.1 judge
+1F9D1 1F3FB 200D 2696 FE0F ; fully-qualified # 🧑🏻⚖️ E12.1 judge: light skin tone
+1F9D1 1F3FB 200D 2696 ; minimally-qualified # 🧑🏻⚖ E12.1 judge: light skin tone
+1F9D1 1F3FC 200D 2696 FE0F ; fully-qualified # 🧑🏼⚖️ E12.1 judge: medium-light skin tone
+1F9D1 1F3FC 200D 2696 ; minimally-qualified # 🧑🏼⚖ E12.1 judge: medium-light skin tone
+1F9D1 1F3FD 200D 2696 FE0F ; fully-qualified # 🧑🏽⚖️ E12.1 judge: medium skin tone
+1F9D1 1F3FD 200D 2696 ; minimally-qualified # 🧑🏽⚖ E12.1 judge: medium skin tone
+1F9D1 1F3FE 200D 2696 FE0F ; fully-qualified # 🧑🏾⚖️ E12.1 judge: medium-dark skin tone
+1F9D1 1F3FE 200D 2696 ; minimally-qualified # 🧑🏾⚖ E12.1 judge: medium-dark skin tone
+1F9D1 1F3FF 200D 2696 FE0F ; fully-qualified # 🧑🏿⚖️ E12.1 judge: dark skin tone
+1F9D1 1F3FF 200D 2696 ; minimally-qualified # 🧑🏿⚖ E12.1 judge: dark skin tone
+1F468 200D 2696 FE0F ; fully-qualified # 👨⚖️ E4.0 man judge
+1F468 200D 2696 ; minimally-qualified # 👨⚖ E4.0 man judge
+1F468 1F3FB 200D 2696 FE0F ; fully-qualified # 👨🏻⚖️ E4.0 man judge: light skin tone
+1F468 1F3FB 200D 2696 ; minimally-qualified # 👨🏻⚖ E4.0 man judge: light skin tone
+1F468 1F3FC 200D 2696 FE0F ; fully-qualified # 👨🏼⚖️ E4.0 man judge: medium-light skin tone
+1F468 1F3FC 200D 2696 ; minimally-qualified # 👨🏼⚖ E4.0 man judge: medium-light skin tone
+1F468 1F3FD 200D 2696 FE0F ; fully-qualified # 👨🏽⚖️ E4.0 man judge: medium skin tone
+1F468 1F3FD 200D 2696 ; minimally-qualified # 👨🏽⚖ E4.0 man judge: medium skin tone
+1F468 1F3FE 200D 2696 FE0F ; fully-qualified # 👨🏾⚖️ E4.0 man judge: medium-dark skin tone
+1F468 1F3FE 200D 2696 ; minimally-qualified # 👨🏾⚖ E4.0 man judge: medium-dark skin tone
+1F468 1F3FF 200D 2696 FE0F ; fully-qualified # 👨🏿⚖️ E4.0 man judge: dark skin tone
+1F468 1F3FF 200D 2696 ; minimally-qualified # 👨🏿⚖ E4.0 man judge: dark skin tone
+1F469 200D 2696 FE0F ; fully-qualified # 👩⚖️ E4.0 woman judge
+1F469 200D 2696 ; minimally-qualified # 👩⚖ E4.0 woman judge
+1F469 1F3FB 200D 2696 FE0F ; fully-qualified # 👩🏻⚖️ E4.0 woman judge: light skin tone
+1F469 1F3FB 200D 2696 ; minimally-qualified # 👩🏻⚖ E4.0 woman judge: light skin tone
+1F469 1F3FC 200D 2696 FE0F ; fully-qualified # 👩🏼⚖️ E4.0 woman judge: medium-light skin tone
+1F469 1F3FC 200D 2696 ; minimally-qualified # 👩🏼⚖ E4.0 woman judge: medium-light skin tone
+1F469 1F3FD 200D 2696 FE0F ; fully-qualified # 👩🏽⚖️ E4.0 woman judge: medium skin tone
+1F469 1F3FD 200D 2696 ; minimally-qualified # 👩🏽⚖ E4.0 woman judge: medium skin tone
+1F469 1F3FE 200D 2696 FE0F ; fully-qualified # 👩🏾⚖️ E4.0 woman judge: medium-dark skin tone
+1F469 1F3FE 200D 2696 ; minimally-qualified # 👩🏾⚖ E4.0 woman judge: medium-dark skin tone
+1F469 1F3FF 200D 2696 FE0F ; fully-qualified # 👩🏿⚖️ E4.0 woman judge: dark skin tone
+1F469 1F3FF 200D 2696 ; minimally-qualified # 👩🏿⚖ E4.0 woman judge: dark skin tone
+1F9D1 200D 1F33E ; fully-qualified # 🧑🌾 E12.1 farmer
+1F9D1 1F3FB 200D 1F33E ; fully-qualified # 🧑🏻🌾 E12.1 farmer: light skin tone
+1F9D1 1F3FC 200D 1F33E ; fully-qualified # 🧑🏼🌾 E12.1 farmer: medium-light skin tone
+1F9D1 1F3FD 200D 1F33E ; fully-qualified # 🧑🏽🌾 E12.1 farmer: medium skin tone
+1F9D1 1F3FE 200D 1F33E ; fully-qualified # 🧑🏾🌾 E12.1 farmer: medium-dark skin tone
+1F9D1 1F3FF 200D 1F33E ; fully-qualified # 🧑🏿🌾 E12.1 farmer: dark skin tone
+1F468 200D 1F33E ; fully-qualified # 👨🌾 E4.0 man farmer
+1F468 1F3FB 200D 1F33E ; fully-qualified # 👨🏻🌾 E4.0 man farmer: light skin tone
+1F468 1F3FC 200D 1F33E ; fully-qualified # 👨🏼🌾 E4.0 man farmer: medium-light skin tone
+1F468 1F3FD 200D 1F33E ; fully-qualified # 👨🏽🌾 E4.0 man farmer: medium skin tone
+1F468 1F3FE 200D 1F33E ; fully-qualified # 👨🏾🌾 E4.0 man farmer: medium-dark skin tone
+1F468 1F3FF 200D 1F33E ; fully-qualified # 👨🏿🌾 E4.0 man farmer: dark skin tone
+1F469 200D 1F33E ; fully-qualified # 👩🌾 E4.0 woman farmer
+1F469 1F3FB 200D 1F33E ; fully-qualified # 👩🏻🌾 E4.0 woman farmer: light skin tone
+1F469 1F3FC 200D 1F33E ; fully-qualified # 👩🏼🌾 E4.0 woman farmer: medium-light skin tone
+1F469 1F3FD 200D 1F33E ; fully-qualified # 👩🏽🌾 E4.0 woman farmer: medium skin tone
+1F469 1F3FE 200D 1F33E ; fully-qualified # 👩🏾🌾 E4.0 woman farmer: medium-dark skin tone
+1F469 1F3FF 200D 1F33E ; fully-qualified # 👩🏿🌾 E4.0 woman farmer: dark skin tone
+1F9D1 200D 1F373 ; fully-qualified # 🧑🍳 E12.1 cook
+1F9D1 1F3FB 200D 1F373 ; fully-qualified # 🧑🏻🍳 E12.1 cook: light skin tone
+1F9D1 1F3FC 200D 1F373 ; fully-qualified # 🧑🏼🍳 E12.1 cook: medium-light skin tone
+1F9D1 1F3FD 200D 1F373 ; fully-qualified # 🧑🏽🍳 E12.1 cook: medium skin tone
+1F9D1 1F3FE 200D 1F373 ; fully-qualified # 🧑🏾🍳 E12.1 cook: medium-dark skin tone
+1F9D1 1F3FF 200D 1F373 ; fully-qualified # 🧑🏿🍳 E12.1 cook: dark skin tone
+1F468 200D 1F373 ; fully-qualified # 👨🍳 E4.0 man cook
+1F468 1F3FB 200D 1F373 ; fully-qualified # 👨🏻🍳 E4.0 man cook: light skin tone
+1F468 1F3FC 200D 1F373 ; fully-qualified # 👨🏼🍳 E4.0 man cook: medium-light skin tone
+1F468 1F3FD 200D 1F373 ; fully-qualified # 👨🏽🍳 E4.0 man cook: medium skin tone
+1F468 1F3FE 200D 1F373 ; fully-qualified # 👨🏾🍳 E4.0 man cook: medium-dark skin tone
+1F468 1F3FF 200D 1F373 ; fully-qualified # 👨🏿🍳 E4.0 man cook: dark skin tone
+1F469 200D 1F373 ; fully-qualified # 👩🍳 E4.0 woman cook
+1F469 1F3FB 200D 1F373 ; fully-qualified # 👩🏻🍳 E4.0 woman cook: light skin tone
+1F469 1F3FC 200D 1F373 ; fully-qualified # 👩🏼🍳 E4.0 woman cook: medium-light skin tone
+1F469 1F3FD 200D 1F373 ; fully-qualified # 👩🏽🍳 E4.0 woman cook: medium skin tone
+1F469 1F3FE 200D 1F373 ; fully-qualified # 👩🏾🍳 E4.0 woman cook: medium-dark skin tone
+1F469 1F3FF 200D 1F373 ; fully-qualified # 👩🏿🍳 E4.0 woman cook: dark skin tone
+1F9D1 200D 1F527 ; fully-qualified # 🧑🔧 E12.1 mechanic
+1F9D1 1F3FB 200D 1F527 ; fully-qualified # 🧑🏻🔧 E12.1 mechanic: light skin tone
+1F9D1 1F3FC 200D 1F527 ; fully-qualified # 🧑🏼🔧 E12.1 mechanic: medium-light skin tone
+1F9D1 1F3FD 200D 1F527 ; fully-qualified # 🧑🏽🔧 E12.1 mechanic: medium skin tone
+1F9D1 1F3FE 200D 1F527 ; fully-qualified # 🧑🏾🔧 E12.1 mechanic: medium-dark skin tone
+1F9D1 1F3FF 200D 1F527 ; fully-qualified # 🧑🏿🔧 E12.1 mechanic: dark skin tone
+1F468 200D 1F527 ; fully-qualified # 👨🔧 E4.0 man mechanic
+1F468 1F3FB 200D 1F527 ; fully-qualified # 👨🏻🔧 E4.0 man mechanic: light skin tone
+1F468 1F3FC 200D 1F527 ; fully-qualified # 👨🏼🔧 E4.0 man mechanic: medium-light skin tone
+1F468 1F3FD 200D 1F527 ; fully-qualified # 👨🏽🔧 E4.0 man mechanic: medium skin tone
+1F468 1F3FE 200D 1F527 ; fully-qualified # 👨🏾🔧 E4.0 man mechanic: medium-dark skin tone
+1F468 1F3FF 200D 1F527 ; fully-qualified # 👨🏿🔧 E4.0 man mechanic: dark skin tone
+1F469 200D 1F527 ; fully-qualified # 👩🔧 E4.0 woman mechanic
+1F469 1F3FB 200D 1F527 ; fully-qualified # 👩🏻🔧 E4.0 woman mechanic: light skin tone
+1F469 1F3FC 200D 1F527 ; fully-qualified # 👩🏼🔧 E4.0 woman mechanic: medium-light skin tone
+1F469 1F3FD 200D 1F527 ; fully-qualified # 👩🏽🔧 E4.0 woman mechanic: medium skin tone
+1F469 1F3FE 200D 1F527 ; fully-qualified # 👩🏾🔧 E4.0 woman mechanic: medium-dark skin tone
+1F469 1F3FF 200D 1F527 ; fully-qualified # 👩🏿🔧 E4.0 woman mechanic: dark skin tone
+1F9D1 200D 1F3ED ; fully-qualified # 🧑🏭 E12.1 factory worker
+1F9D1 1F3FB 200D 1F3ED ; fully-qualified # 🧑🏻🏭 E12.1 factory worker: light skin tone
+1F9D1 1F3FC 200D 1F3ED ; fully-qualified # 🧑🏼🏭 E12.1 factory worker: medium-light skin tone
+1F9D1 1F3FD 200D 1F3ED ; fully-qualified # 🧑🏽🏭 E12.1 factory worker: medium skin tone
+1F9D1 1F3FE 200D 1F3ED ; fully-qualified # 🧑🏾🏭 E12.1 factory worker: medium-dark skin tone
+1F9D1 1F3FF 200D 1F3ED ; fully-qualified # 🧑🏿🏭 E12.1 factory worker: dark skin tone
+1F468 200D 1F3ED ; fully-qualified # 👨🏭 E4.0 man factory worker
+1F468 1F3FB 200D 1F3ED ; fully-qualified # 👨🏻🏭 E4.0 man factory worker: light skin tone
+1F468 1F3FC 200D 1F3ED ; fully-qualified # 👨🏼🏭 E4.0 man factory worker: medium-light skin tone
+1F468 1F3FD 200D 1F3ED ; fully-qualified # 👨🏽🏭 E4.0 man factory worker: medium skin tone
+1F468 1F3FE 200D 1F3ED ; fully-qualified # 👨🏾🏭 E4.0 man factory worker: medium-dark skin tone
+1F468 1F3FF 200D 1F3ED ; fully-qualified # 👨🏿🏭 E4.0 man factory worker: dark skin tone
+1F469 200D 1F3ED ; fully-qualified # 👩🏭 E4.0 woman factory worker
+1F469 1F3FB 200D 1F3ED ; fully-qualified # 👩🏻🏭 E4.0 woman factory worker: light skin tone
+1F469 1F3FC 200D 1F3ED ; fully-qualified # 👩🏼🏭 E4.0 woman factory worker: medium-light skin tone
+1F469 1F3FD 200D 1F3ED ; fully-qualified # 👩🏽🏭 E4.0 woman factory worker: medium skin tone
+1F469 1F3FE 200D 1F3ED ; fully-qualified # 👩🏾🏭 E4.0 woman factory worker: medium-dark skin tone
+1F469 1F3FF 200D 1F3ED ; fully-qualified # 👩🏿🏭 E4.0 woman factory worker: dark skin tone
+1F9D1 200D 1F4BC ; fully-qualified # 🧑💼 E12.1 office worker
+1F9D1 1F3FB 200D 1F4BC ; fully-qualified # 🧑🏻💼 E12.1 office worker: light skin tone
+1F9D1 1F3FC 200D 1F4BC ; fully-qualified # 🧑🏼💼 E12.1 office worker: medium-light skin tone
+1F9D1 1F3FD 200D 1F4BC ; fully-qualified # 🧑🏽💼 E12.1 office worker: medium skin tone
+1F9D1 1F3FE 200D 1F4BC ; fully-qualified # 🧑🏾💼 E12.1 office worker: medium-dark skin tone
+1F9D1 1F3FF 200D 1F4BC ; fully-qualified # 🧑🏿💼 E12.1 office worker: dark skin tone
+1F468 200D 1F4BC ; fully-qualified # 👨💼 E4.0 man office worker
+1F468 1F3FB 200D 1F4BC ; fully-qualified # 👨🏻💼 E4.0 man office worker: light skin tone
+1F468 1F3FC 200D 1F4BC ; fully-qualified # 👨🏼💼 E4.0 man office worker: medium-light skin tone
+1F468 1F3FD 200D 1F4BC ; fully-qualified # 👨🏽💼 E4.0 man office worker: medium skin tone
+1F468 1F3FE 200D 1F4BC ; fully-qualified # 👨🏾💼 E4.0 man office worker: medium-dark skin tone
+1F468 1F3FF 200D 1F4BC ; fully-qualified # 👨🏿💼 E4.0 man office worker: dark skin tone
+1F469 200D 1F4BC ; fully-qualified # 👩💼 E4.0 woman office worker
+1F469 1F3FB 200D 1F4BC ; fully-qualified # 👩🏻💼 E4.0 woman office worker: light skin tone
+1F469 1F3FC 200D 1F4BC ; fully-qualified # 👩🏼💼 E4.0 woman office worker: medium-light skin tone
+1F469 1F3FD 200D 1F4BC ; fully-qualified # 👩🏽💼 E4.0 woman office worker: medium skin tone
+1F469 1F3FE 200D 1F4BC ; fully-qualified # 👩🏾💼 E4.0 woman office worker: medium-dark skin tone
+1F469 1F3FF 200D 1F4BC ; fully-qualified # 👩🏿💼 E4.0 woman office worker: dark skin tone
+1F9D1 200D 1F52C ; fully-qualified # 🧑🔬 E12.1 scientist
+1F9D1 1F3FB 200D 1F52C ; fully-qualified # 🧑🏻🔬 E12.1 scientist: light skin tone
+1F9D1 1F3FC 200D 1F52C ; fully-qualified # 🧑🏼🔬 E12.1 scientist: medium-light skin tone
+1F9D1 1F3FD 200D 1F52C ; fully-qualified # 🧑🏽🔬 E12.1 scientist: medium skin tone
+1F9D1 1F3FE 200D 1F52C ; fully-qualified # 🧑🏾🔬 E12.1 scientist: medium-dark skin tone
+1F9D1 1F3FF 200D 1F52C ; fully-qualified # 🧑🏿🔬 E12.1 scientist: dark skin tone
+1F468 200D 1F52C ; fully-qualified # 👨🔬 E4.0 man scientist
+1F468 1F3FB 200D 1F52C ; fully-qualified # 👨🏻🔬 E4.0 man scientist: light skin tone
+1F468 1F3FC 200D 1F52C ; fully-qualified # 👨🏼🔬 E4.0 man scientist: medium-light skin tone
+1F468 1F3FD 200D 1F52C ; fully-qualified # 👨🏽🔬 E4.0 man scientist: medium skin tone
+1F468 1F3FE 200D 1F52C ; fully-qualified # 👨🏾🔬 E4.0 man scientist: medium-dark skin tone
+1F468 1F3FF 200D 1F52C ; fully-qualified # 👨🏿🔬 E4.0 man scientist: dark skin tone
+1F469 200D 1F52C ; fully-qualified # 👩🔬 E4.0 woman scientist
+1F469 1F3FB 200D 1F52C ; fully-qualified # 👩🏻🔬 E4.0 woman scientist: light skin tone
+1F469 1F3FC 200D 1F52C ; fully-qualified # 👩🏼🔬 E4.0 woman scientist: medium-light skin tone
+1F469 1F3FD 200D 1F52C ; fully-qualified # 👩🏽🔬 E4.0 woman scientist: medium skin tone
+1F469 1F3FE 200D 1F52C ; fully-qualified # 👩🏾🔬 E4.0 woman scientist: medium-dark skin tone
+1F469 1F3FF 200D 1F52C ; fully-qualified # 👩🏿🔬 E4.0 woman scientist: dark skin tone
+1F9D1 200D 1F4BB ; fully-qualified # 🧑💻 E12.1 technologist
+1F9D1 1F3FB 200D 1F4BB ; fully-qualified # 🧑🏻💻 E12.1 technologist: light skin tone
+1F9D1 1F3FC 200D 1F4BB ; fully-qualified # 🧑🏼💻 E12.1 technologist: medium-light skin tone
+1F9D1 1F3FD 200D 1F4BB ; fully-qualified # 🧑🏽💻 E12.1 technologist: medium skin tone
+1F9D1 1F3FE 200D 1F4BB ; fully-qualified # 🧑🏾💻 E12.1 technologist: medium-dark skin tone
+1F9D1 1F3FF 200D 1F4BB ; fully-qualified # 🧑🏿💻 E12.1 technologist: dark skin tone
+1F468 200D 1F4BB ; fully-qualified # 👨💻 E4.0 man technologist
+1F468 1F3FB 200D 1F4BB ; fully-qualified # 👨🏻💻 E4.0 man technologist: light skin tone
+1F468 1F3FC 200D 1F4BB ; fully-qualified # 👨🏼💻 E4.0 man technologist: medium-light skin tone
+1F468 1F3FD 200D 1F4BB ; fully-qualified # 👨🏽💻 E4.0 man technologist: medium skin tone
+1F468 1F3FE 200D 1F4BB ; fully-qualified # 👨🏾💻 E4.0 man technologist: medium-dark skin tone
+1F468 1F3FF 200D 1F4BB ; fully-qualified # 👨🏿💻 E4.0 man technologist: dark skin tone
+1F469 200D 1F4BB ; fully-qualified # 👩💻 E4.0 woman technologist
+1F469 1F3FB 200D 1F4BB ; fully-qualified # 👩🏻💻 E4.0 woman technologist: light skin tone
+1F469 1F3FC 200D 1F4BB ; fully-qualified # 👩🏼💻 E4.0 woman technologist: medium-light skin tone
+1F469 1F3FD 200D 1F4BB ; fully-qualified # 👩🏽💻 E4.0 woman technologist: medium skin tone
+1F469 1F3FE 200D 1F4BB ; fully-qualified # 👩🏾💻 E4.0 woman technologist: medium-dark skin tone
+1F469 1F3FF 200D 1F4BB ; fully-qualified # 👩🏿💻 E4.0 woman technologist: dark skin tone
+1F9D1 200D 1F3A4 ; fully-qualified # 🧑🎤 E12.1 singer
+1F9D1 1F3FB 200D 1F3A4 ; fully-qualified # 🧑🏻🎤 E12.1 singer: light skin tone
+1F9D1 1F3FC 200D 1F3A4 ; fully-qualified # 🧑🏼🎤 E12.1 singer: medium-light skin tone
+1F9D1 1F3FD 200D 1F3A4 ; fully-qualified # 🧑🏽🎤 E12.1 singer: medium skin tone
+1F9D1 1F3FE 200D 1F3A4 ; fully-qualified # 🧑🏾🎤 E12.1 singer: medium-dark skin tone
+1F9D1 1F3FF 200D 1F3A4 ; fully-qualified # 🧑🏿🎤 E12.1 singer: dark skin tone
+1F468 200D 1F3A4 ; fully-qualified # 👨🎤 E4.0 man singer
+1F468 1F3FB 200D 1F3A4 ; fully-qualified # 👨🏻🎤 E4.0 man singer: light skin tone
+1F468 1F3FC 200D 1F3A4 ; fully-qualified # 👨🏼🎤 E4.0 man singer: medium-light skin tone
+1F468 1F3FD 200D 1F3A4 ; fully-qualified # 👨🏽🎤 E4.0 man singer: medium skin tone
+1F468 1F3FE 200D 1F3A4 ; fully-qualified # 👨🏾🎤 E4.0 man singer: medium-dark skin tone
+1F468 1F3FF 200D 1F3A4 ; fully-qualified # 👨🏿🎤 E4.0 man singer: dark skin tone
+1F469 200D 1F3A4 ; fully-qualified # 👩🎤 E4.0 woman singer
+1F469 1F3FB 200D 1F3A4 ; fully-qualified # 👩🏻🎤 E4.0 woman singer: light skin tone
+1F469 1F3FC 200D 1F3A4 ; fully-qualified # 👩🏼🎤 E4.0 woman singer: medium-light skin tone
+1F469 1F3FD 200D 1F3A4 ; fully-qualified # 👩🏽🎤 E4.0 woman singer: medium skin tone
+1F469 1F3FE 200D 1F3A4 ; fully-qualified # 👩🏾🎤 E4.0 woman singer: medium-dark skin tone
+1F469 1F3FF 200D 1F3A4 ; fully-qualified # 👩🏿🎤 E4.0 woman singer: dark skin tone
+1F9D1 200D 1F3A8 ; fully-qualified # 🧑🎨 E12.1 artist
+1F9D1 1F3FB 200D 1F3A8 ; fully-qualified # 🧑🏻🎨 E12.1 artist: light skin tone
+1F9D1 1F3FC 200D 1F3A8 ; fully-qualified # 🧑🏼🎨 E12.1 artist: medium-light skin tone
+1F9D1 1F3FD 200D 1F3A8 ; fully-qualified # 🧑🏽🎨 E12.1 artist: medium skin tone
+1F9D1 1F3FE 200D 1F3A8 ; fully-qualified # 🧑🏾🎨 E12.1 artist: medium-dark skin tone
+1F9D1 1F3FF 200D 1F3A8 ; fully-qualified # 🧑🏿🎨 E12.1 artist: dark skin tone
+1F468 200D 1F3A8 ; fully-qualified # 👨🎨 E4.0 man artist
+1F468 1F3FB 200D 1F3A8 ; fully-qualified # 👨🏻🎨 E4.0 man artist: light skin tone
+1F468 1F3FC 200D 1F3A8 ; fully-qualified # 👨🏼🎨 E4.0 man artist: medium-light skin tone
+1F468 1F3FD 200D 1F3A8 ; fully-qualified # 👨🏽🎨 E4.0 man artist: medium skin tone
+1F468 1F3FE 200D 1F3A8 ; fully-qualified # 👨🏾🎨 E4.0 man artist: medium-dark skin tone
+1F468 1F3FF 200D 1F3A8 ; fully-qualified # 👨🏿🎨 E4.0 man artist: dark skin tone
+1F469 200D 1F3A8 ; fully-qualified # 👩🎨 E4.0 woman artist
+1F469 1F3FB 200D 1F3A8 ; fully-qualified # 👩🏻🎨 E4.0 woman artist: light skin tone
+1F469 1F3FC 200D 1F3A8 ; fully-qualified # 👩🏼🎨 E4.0 woman artist: medium-light skin tone
+1F469 1F3FD 200D 1F3A8 ; fully-qualified # 👩🏽🎨 E4.0 woman artist: medium skin tone
+1F469 1F3FE 200D 1F3A8 ; fully-qualified # 👩🏾🎨 E4.0 woman artist: medium-dark skin tone
+1F469 1F3FF 200D 1F3A8 ; fully-qualified # 👩🏿🎨 E4.0 woman artist: dark skin tone
+1F9D1 200D 2708 FE0F ; fully-qualified # 🧑✈️ E12.1 pilot
+1F9D1 200D 2708 ; minimally-qualified # 🧑✈ E12.1 pilot
+1F9D1 1F3FB 200D 2708 FE0F ; fully-qualified # 🧑🏻✈️ E12.1 pilot: light skin tone
+1F9D1 1F3FB 200D 2708 ; minimally-qualified # 🧑🏻✈ E12.1 pilot: light skin tone
+1F9D1 1F3FC 200D 2708 FE0F ; fully-qualified # 🧑🏼✈️ E12.1 pilot: medium-light skin tone
+1F9D1 1F3FC 200D 2708 ; minimally-qualified # 🧑🏼✈ E12.1 pilot: medium-light skin tone
+1F9D1 1F3FD 200D 2708 FE0F ; fully-qualified # 🧑🏽✈️ E12.1 pilot: medium skin tone
+1F9D1 1F3FD 200D 2708 ; minimally-qualified # 🧑🏽✈ E12.1 pilot: medium skin tone
+1F9D1 1F3FE 200D 2708 FE0F ; fully-qualified # 🧑🏾✈️ E12.1 pilot: medium-dark skin tone
+1F9D1 1F3FE 200D 2708 ; minimally-qualified # 🧑🏾✈ E12.1 pilot: medium-dark skin tone
+1F9D1 1F3FF 200D 2708 FE0F ; fully-qualified # 🧑🏿✈️ E12.1 pilot: dark skin tone
+1F9D1 1F3FF 200D 2708 ; minimally-qualified # 🧑🏿✈ E12.1 pilot: dark skin tone
+1F468 200D 2708 FE0F ; fully-qualified # 👨✈️ E4.0 man pilot
+1F468 200D 2708 ; minimally-qualified # 👨✈ E4.0 man pilot
+1F468 1F3FB 200D 2708 FE0F ; fully-qualified # 👨🏻✈️ E4.0 man pilot: light skin tone
+1F468 1F3FB 200D 2708 ; minimally-qualified # 👨🏻✈ E4.0 man pilot: light skin tone
+1F468 1F3FC 200D 2708 FE0F ; fully-qualified # 👨🏼✈️ E4.0 man pilot: medium-light skin tone
+1F468 1F3FC 200D 2708 ; minimally-qualified # 👨🏼✈ E4.0 man pilot: medium-light skin tone
+1F468 1F3FD 200D 2708 FE0F ; fully-qualified # 👨🏽✈️ E4.0 man pilot: medium skin tone
+1F468 1F3FD 200D 2708 ; minimally-qualified # 👨🏽✈ E4.0 man pilot: medium skin tone
+1F468 1F3FE 200D 2708 FE0F ; fully-qualified # 👨🏾✈️ E4.0 man pilot: medium-dark skin tone
+1F468 1F3FE 200D 2708 ; minimally-qualified # 👨🏾✈ E4.0 man pilot: medium-dark skin tone
+1F468 1F3FF 200D 2708 FE0F ; fully-qualified # 👨🏿✈️ E4.0 man pilot: dark skin tone
+1F468 1F3FF 200D 2708 ; minimally-qualified # 👨🏿✈ E4.0 man pilot: dark skin tone
+1F469 200D 2708 FE0F ; fully-qualified # 👩✈️ E4.0 woman pilot
+1F469 200D 2708 ; minimally-qualified # 👩✈ E4.0 woman pilot
+1F469 1F3FB 200D 2708 FE0F ; fully-qualified # 👩🏻✈️ E4.0 woman pilot: light skin tone
+1F469 1F3FB 200D 2708 ; minimally-qualified # 👩🏻✈ E4.0 woman pilot: light skin tone
+1F469 1F3FC 200D 2708 FE0F ; fully-qualified # 👩🏼✈️ E4.0 woman pilot: medium-light skin tone
+1F469 1F3FC 200D 2708 ; minimally-qualified # 👩🏼✈ E4.0 woman pilot: medium-light skin tone
+1F469 1F3FD 200D 2708 FE0F ; fully-qualified # 👩🏽✈️ E4.0 woman pilot: medium skin tone
+1F469 1F3FD 200D 2708 ; minimally-qualified # 👩🏽✈ E4.0 woman pilot: medium skin tone
+1F469 1F3FE 200D 2708 FE0F ; fully-qualified # 👩🏾✈️ E4.0 woman pilot: medium-dark skin tone
+1F469 1F3FE 200D 2708 ; minimally-qualified # 👩🏾✈ E4.0 woman pilot: medium-dark skin tone
+1F469 1F3FF 200D 2708 FE0F ; fully-qualified # 👩🏿✈️ E4.0 woman pilot: dark skin tone
+1F469 1F3FF 200D 2708 ; minimally-qualified # 👩🏿✈ E4.0 woman pilot: dark skin tone
+1F9D1 200D 1F680 ; fully-qualified # 🧑🚀 E12.1 astronaut
+1F9D1 1F3FB 200D 1F680 ; fully-qualified # 🧑🏻🚀 E12.1 astronaut: light skin tone
+1F9D1 1F3FC 200D 1F680 ; fully-qualified # 🧑🏼🚀 E12.1 astronaut: medium-light skin tone
+1F9D1 1F3FD 200D 1F680 ; fully-qualified # 🧑🏽🚀 E12.1 astronaut: medium skin tone
+1F9D1 1F3FE 200D 1F680 ; fully-qualified # 🧑🏾🚀 E12.1 astronaut: medium-dark skin tone
+1F9D1 1F3FF 200D 1F680 ; fully-qualified # 🧑🏿🚀 E12.1 astronaut: dark skin tone
+1F468 200D 1F680 ; fully-qualified # 👨🚀 E4.0 man astronaut
+1F468 1F3FB 200D 1F680 ; fully-qualified # 👨🏻🚀 E4.0 man astronaut: light skin tone
+1F468 1F3FC 200D 1F680 ; fully-qualified # 👨🏼🚀 E4.0 man astronaut: medium-light skin tone
+1F468 1F3FD 200D 1F680 ; fully-qualified # 👨🏽🚀 E4.0 man astronaut: medium skin tone
+1F468 1F3FE 200D 1F680 ; fully-qualified # 👨🏾🚀 E4.0 man astronaut: medium-dark skin tone
+1F468 1F3FF 200D 1F680 ; fully-qualified # 👨🏿🚀 E4.0 man astronaut: dark skin tone
+1F469 200D 1F680 ; fully-qualified # 👩🚀 E4.0 woman astronaut
+1F469 1F3FB 200D 1F680 ; fully-qualified # 👩🏻🚀 E4.0 woman astronaut: light skin tone
+1F469 1F3FC 200D 1F680 ; fully-qualified # 👩🏼🚀 E4.0 woman astronaut: medium-light skin tone
+1F469 1F3FD 200D 1F680 ; fully-qualified # 👩🏽🚀 E4.0 woman astronaut: medium skin tone
+1F469 1F3FE 200D 1F680 ; fully-qualified # 👩🏾🚀 E4.0 woman astronaut: medium-dark skin tone
+1F469 1F3FF 200D 1F680 ; fully-qualified # 👩🏿🚀 E4.0 woman astronaut: dark skin tone
+1F9D1 200D 1F692 ; fully-qualified # 🧑🚒 E12.1 firefighter
+1F9D1 1F3FB 200D 1F692 ; fully-qualified # 🧑🏻🚒 E12.1 firefighter: light skin tone
+1F9D1 1F3FC 200D 1F692 ; fully-qualified # 🧑🏼🚒 E12.1 firefighter: medium-light skin tone
+1F9D1 1F3FD 200D 1F692 ; fully-qualified # 🧑🏽🚒 E12.1 firefighter: medium skin tone
+1F9D1 1F3FE 200D 1F692 ; fully-qualified # 🧑🏾🚒 E12.1 firefighter: medium-dark skin tone
+1F9D1 1F3FF 200D 1F692 ; fully-qualified # 🧑🏿🚒 E12.1 firefighter: dark skin tone
+1F468 200D 1F692 ; fully-qualified # 👨🚒 E4.0 man firefighter
+1F468 1F3FB 200D 1F692 ; fully-qualified # 👨🏻🚒 E4.0 man firefighter: light skin tone
+1F468 1F3FC 200D 1F692 ; fully-qualified # 👨🏼🚒 E4.0 man firefighter: medium-light skin tone
+1F468 1F3FD 200D 1F692 ; fully-qualified # 👨🏽🚒 E4.0 man firefighter: medium skin tone
+1F468 1F3FE 200D 1F692 ; fully-qualified # 👨🏾🚒 E4.0 man firefighter: medium-dark skin tone
+1F468 1F3FF 200D 1F692 ; fully-qualified # 👨🏿🚒 E4.0 man firefighter: dark skin tone
+1F469 200D 1F692 ; fully-qualified # 👩🚒 E4.0 woman firefighter
+1F469 1F3FB 200D 1F692 ; fully-qualified # 👩🏻🚒 E4.0 woman firefighter: light skin tone
+1F469 1F3FC 200D 1F692 ; fully-qualified # 👩🏼🚒 E4.0 woman firefighter: medium-light skin tone
+1F469 1F3FD 200D 1F692 ; fully-qualified # 👩🏽🚒 E4.0 woman firefighter: medium skin tone
+1F469 1F3FE 200D 1F692 ; fully-qualified # 👩🏾🚒 E4.0 woman firefighter: medium-dark skin tone
+1F469 1F3FF 200D 1F692 ; fully-qualified # 👩🏿🚒 E4.0 woman firefighter: dark skin tone
+1F46E ; fully-qualified # 👮 E0.6 police officer
+1F46E 1F3FB ; fully-qualified # 👮🏻 E1.0 police officer: light skin tone
+1F46E 1F3FC ; fully-qualified # 👮🏼 E1.0 police officer: medium-light skin tone
+1F46E 1F3FD ; fully-qualified # 👮🏽 E1.0 police officer: medium skin tone
+1F46E 1F3FE ; fully-qualified # 👮🏾 E1.0 police officer: medium-dark skin tone
+1F46E 1F3FF ; fully-qualified # 👮🏿 E1.0 police officer: dark skin tone
+1F46E 200D 2642 FE0F ; fully-qualified # 👮♂️ E4.0 man police officer
+1F46E 200D 2642 ; minimally-qualified # 👮♂ E4.0 man police officer
+1F46E 1F3FB 200D 2642 FE0F ; fully-qualified # 👮🏻♂️ E4.0 man police officer: light skin tone
+1F46E 1F3FB 200D 2642 ; minimally-qualified # 👮🏻♂ E4.0 man police officer: light skin tone
+1F46E 1F3FC 200D 2642 FE0F ; fully-qualified # 👮🏼♂️ E4.0 man police officer: medium-light skin tone
+1F46E 1F3FC 200D 2642 ; minimally-qualified # 👮🏼♂ E4.0 man police officer: medium-light skin tone
+1F46E 1F3FD 200D 2642 FE0F ; fully-qualified # 👮🏽♂️ E4.0 man police officer: medium skin tone
+1F46E 1F3FD 200D 2642 ; minimally-qualified # 👮🏽♂ E4.0 man police officer: medium skin tone
+1F46E 1F3FE 200D 2642 FE0F ; fully-qualified # 👮🏾♂️ E4.0 man police officer: medium-dark skin tone
+1F46E 1F3FE 200D 2642 ; minimally-qualified # 👮🏾♂ E4.0 man police officer: medium-dark skin tone
+1F46E 1F3FF 200D 2642 FE0F ; fully-qualified # 👮🏿♂️ E4.0 man police officer: dark skin tone
+1F46E 1F3FF 200D 2642 ; minimally-qualified # 👮🏿♂ E4.0 man police officer: dark skin tone
+1F46E 200D 2640 FE0F ; fully-qualified # 👮♀️ E4.0 woman police officer
+1F46E 200D 2640 ; minimally-qualified # 👮♀ E4.0 woman police officer
+1F46E 1F3FB 200D 2640 FE0F ; fully-qualified # 👮🏻♀️ E4.0 woman police officer: light skin tone
+1F46E 1F3FB 200D 2640 ; minimally-qualified # 👮🏻♀ E4.0 woman police officer: light skin tone
+1F46E 1F3FC 200D 2640 FE0F ; fully-qualified # 👮🏼♀️ E4.0 woman police officer: medium-light skin tone
+1F46E 1F3FC 200D 2640 ; minimally-qualified # 👮🏼♀ E4.0 woman police officer: medium-light skin tone
+1F46E 1F3FD 200D 2640 FE0F ; fully-qualified # 👮🏽♀️ E4.0 woman police officer: medium skin tone
+1F46E 1F3FD 200D 2640 ; minimally-qualified # 👮🏽♀ E4.0 woman police officer: medium skin tone
+1F46E 1F3FE 200D 2640 FE0F ; fully-qualified # 👮🏾♀️ E4.0 woman police officer: medium-dark skin tone
+1F46E 1F3FE 200D 2640 ; minimally-qualified # 👮🏾♀ E4.0 woman police officer: medium-dark skin tone
+1F46E 1F3FF 200D 2640 FE0F ; fully-qualified # 👮🏿♀️ E4.0 woman police officer: dark skin tone
+1F46E 1F3FF 200D 2640 ; minimally-qualified # 👮🏿♀ E4.0 woman police officer: dark skin tone
+1F575 FE0F ; fully-qualified # 🕵️ E0.7 detective
+1F575 ; unqualified # 🕵 E0.7 detective
+1F575 1F3FB ; fully-qualified # 🕵🏻 E2.0 detective: light skin tone
+1F575 1F3FC ; fully-qualified # 🕵🏼 E2.0 detective: medium-light skin tone
+1F575 1F3FD ; fully-qualified # 🕵🏽 E2.0 detective: medium skin tone
+1F575 1F3FE ; fully-qualified # 🕵🏾 E2.0 detective: medium-dark skin tone
+1F575 1F3FF ; fully-qualified # 🕵🏿 E2.0 detective: dark skin tone
+1F575 FE0F 200D 2642 FE0F ; fully-qualified # 🕵️♂️ E4.0 man detective
+1F575 200D 2642 FE0F ; unqualified # 🕵♂️ E4.0 man detective
+1F575 FE0F 200D 2642 ; minimally-qualified # 🕵️♂ E4.0 man detective
+1F575 200D 2642 ; unqualified # 🕵♂ E4.0 man detective
+1F575 1F3FB 200D 2642 FE0F ; fully-qualified # 🕵🏻♂️ E4.0 man detective: light skin tone
+1F575 1F3FB 200D 2642 ; minimally-qualified # 🕵🏻♂ E4.0 man detective: light skin tone
+1F575 1F3FC 200D 2642 FE0F ; fully-qualified # 🕵🏼♂️ E4.0 man detective: medium-light skin tone
+1F575 1F3FC 200D 2642 ; minimally-qualified # 🕵🏼♂ E4.0 man detective: medium-light skin tone
+1F575 1F3FD 200D 2642 FE0F ; fully-qualified # 🕵🏽♂️ E4.0 man detective: medium skin tone
+1F575 1F3FD 200D 2642 ; minimally-qualified # 🕵🏽♂ E4.0 man detective: medium skin tone
+1F575 1F3FE 200D 2642 FE0F ; fully-qualified # 🕵🏾♂️ E4.0 man detective: medium-dark skin tone
+1F575 1F3FE 200D 2642 ; minimally-qualified # 🕵🏾♂ E4.0 man detective: medium-dark skin tone
+1F575 1F3FF 200D 2642 FE0F ; fully-qualified # 🕵🏿♂️ E4.0 man detective: dark skin tone
+1F575 1F3FF 200D 2642 ; minimally-qualified # 🕵🏿♂ E4.0 man detective: dark skin tone
+1F575 FE0F 200D 2640 FE0F ; fully-qualified # 🕵️♀️ E4.0 woman detective
+1F575 200D 2640 FE0F ; unqualified # 🕵♀️ E4.0 woman detective
+1F575 FE0F 200D 2640 ; minimally-qualified # 🕵️♀ E4.0 woman detective
+1F575 200D 2640 ; unqualified # 🕵♀ E4.0 woman detective
+1F575 1F3FB 200D 2640 FE0F ; fully-qualified # 🕵🏻♀️ E4.0 woman detective: light skin tone
+1F575 1F3FB 200D 2640 ; minimally-qualified # 🕵🏻♀ E4.0 woman detective: light skin tone
+1F575 1F3FC 200D 2640 FE0F ; fully-qualified # 🕵🏼♀️ E4.0 woman detective: medium-light skin tone
+1F575 1F3FC 200D 2640 ; minimally-qualified # 🕵🏼♀ E4.0 woman detective: medium-light skin tone
+1F575 1F3FD 200D 2640 FE0F ; fully-qualified # 🕵🏽♀️ E4.0 woman detective: medium skin tone
+1F575 1F3FD 200D 2640 ; minimally-qualified # 🕵🏽♀ E4.0 woman detective: medium skin tone
+1F575 1F3FE 200D 2640 FE0F ; fully-qualified # 🕵🏾♀️ E4.0 woman detective: medium-dark skin tone
+1F575 1F3FE 200D 2640 ; minimally-qualified # 🕵🏾♀ E4.0 woman detective: medium-dark skin tone
+1F575 1F3FF 200D 2640 FE0F ; fully-qualified # 🕵🏿♀️ E4.0 woman detective: dark skin tone
+1F575 1F3FF 200D 2640 ; minimally-qualified # 🕵🏿♀ E4.0 woman detective: dark skin tone
+1F482 ; fully-qualified # 💂 E0.6 guard
+1F482 1F3FB ; fully-qualified # 💂🏻 E1.0 guard: light skin tone
+1F482 1F3FC ; fully-qualified # 💂🏼 E1.0 guard: medium-light skin tone
+1F482 1F3FD ; fully-qualified # 💂🏽 E1.0 guard: medium skin tone
+1F482 1F3FE ; fully-qualified # 💂🏾 E1.0 guard: medium-dark skin tone
+1F482 1F3FF ; fully-qualified # 💂🏿 E1.0 guard: dark skin tone
+1F482 200D 2642 FE0F ; fully-qualified # 💂♂️ E4.0 man guard
+1F482 200D 2642 ; minimally-qualified # 💂♂ E4.0 man guard
+1F482 1F3FB 200D 2642 FE0F ; fully-qualified # 💂🏻♂️ E4.0 man guard: light skin tone
+1F482 1F3FB 200D 2642 ; minimally-qualified # 💂🏻♂ E4.0 man guard: light skin tone
+1F482 1F3FC 200D 2642 FE0F ; fully-qualified # 💂🏼♂️ E4.0 man guard: medium-light skin tone
+1F482 1F3FC 200D 2642 ; minimally-qualified # 💂🏼♂ E4.0 man guard: medium-light skin tone
+1F482 1F3FD 200D 2642 FE0F ; fully-qualified # 💂🏽♂️ E4.0 man guard: medium skin tone
+1F482 1F3FD 200D 2642 ; minimally-qualified # 💂🏽♂ E4.0 man guard: medium skin tone
+1F482 1F3FE 200D 2642 FE0F ; fully-qualified # 💂🏾♂️ E4.0 man guard: medium-dark skin tone
+1F482 1F3FE 200D 2642 ; minimally-qualified # 💂🏾♂ E4.0 man guard: medium-dark skin tone
+1F482 1F3FF 200D 2642 FE0F ; fully-qualified # 💂🏿♂️ E4.0 man guard: dark skin tone
+1F482 1F3FF 200D 2642 ; minimally-qualified # 💂🏿♂ E4.0 man guard: dark skin tone
+1F482 200D 2640 FE0F ; fully-qualified # 💂♀️ E4.0 woman guard
+1F482 200D 2640 ; minimally-qualified # 💂♀ E4.0 woman guard
+1F482 1F3FB 200D 2640 FE0F ; fully-qualified # 💂🏻♀️ E4.0 woman guard: light skin tone
+1F482 1F3FB 200D 2640 ; minimally-qualified # 💂🏻♀ E4.0 woman guard: light skin tone
+1F482 1F3FC 200D 2640 FE0F ; fully-qualified # 💂🏼♀️ E4.0 woman guard: medium-light skin tone
+1F482 1F3FC 200D 2640 ; minimally-qualified # 💂🏼♀ E4.0 woman guard: medium-light skin tone
+1F482 1F3FD 200D 2640 FE0F ; fully-qualified # 💂🏽♀️ E4.0 woman guard: medium skin tone
+1F482 1F3FD 200D 2640 ; minimally-qualified # 💂🏽♀ E4.0 woman guard: medium skin tone
+1F482 1F3FE 200D 2640 FE0F ; fully-qualified # 💂🏾♀️ E4.0 woman guard: medium-dark skin tone
+1F482 1F3FE 200D 2640 ; minimally-qualified # 💂🏾♀ E4.0 woman guard: medium-dark skin tone
+1F482 1F3FF 200D 2640 FE0F ; fully-qualified # 💂🏿♀️ E4.0 woman guard: dark skin tone
+1F482 1F3FF 200D 2640 ; minimally-qualified # 💂🏿♀ E4.0 woman guard: dark skin tone
+1F977 ; fully-qualified # 🥷 E13.0 ninja
+1F977 1F3FB ; fully-qualified # 🥷🏻 E13.0 ninja: light skin tone
+1F977 1F3FC ; fully-qualified # 🥷🏼 E13.0 ninja: medium-light skin tone
+1F977 1F3FD ; fully-qualified # 🥷🏽 E13.0 ninja: medium skin tone
+1F977 1F3FE ; fully-qualified # 🥷🏾 E13.0 ninja: medium-dark skin tone
+1F977 1F3FF ; fully-qualified # 🥷🏿 E13.0 ninja: dark skin tone
+1F477 ; fully-qualified # 👷 E0.6 construction worker
+1F477 1F3FB ; fully-qualified # 👷🏻 E1.0 construction worker: light skin tone
+1F477 1F3FC ; fully-qualified # 👷🏼 E1.0 construction worker: medium-light skin tone
+1F477 1F3FD ; fully-qualified # 👷🏽 E1.0 construction worker: medium skin tone
+1F477 1F3FE ; fully-qualified # 👷🏾 E1.0 construction worker: medium-dark skin tone
+1F477 1F3FF ; fully-qualified # 👷🏿 E1.0 construction worker: dark skin tone
+1F477 200D 2642 FE0F ; fully-qualified # 👷♂️ E4.0 man construction worker
+1F477 200D 2642 ; minimally-qualified # 👷♂ E4.0 man construction worker
+1F477 1F3FB 200D 2642 FE0F ; fully-qualified # 👷🏻♂️ E4.0 man construction worker: light skin tone
+1F477 1F3FB 200D 2642 ; minimally-qualified # 👷🏻♂ E4.0 man construction worker: light skin tone
+1F477 1F3FC 200D 2642 FE0F ; fully-qualified # 👷🏼♂️ E4.0 man construction worker: medium-light skin tone
+1F477 1F3FC 200D 2642 ; minimally-qualified # 👷🏼♂ E4.0 man construction worker: medium-light skin tone
+1F477 1F3FD 200D 2642 FE0F ; fully-qualified # 👷🏽♂️ E4.0 man construction worker: medium skin tone
+1F477 1F3FD 200D 2642 ; minimally-qualified # 👷🏽♂ E4.0 man construction worker: medium skin tone
+1F477 1F3FE 200D 2642 FE0F ; fully-qualified # 👷🏾♂️ E4.0 man construction worker: medium-dark skin tone
+1F477 1F3FE 200D 2642 ; minimally-qualified # 👷🏾♂ E4.0 man construction worker: medium-dark skin tone
+1F477 1F3FF 200D 2642 FE0F ; fully-qualified # 👷🏿♂️ E4.0 man construction worker: dark skin tone
+1F477 1F3FF 200D 2642 ; minimally-qualified # 👷🏿♂ E4.0 man construction worker: dark skin tone
+1F477 200D 2640 FE0F ; fully-qualified # 👷♀️ E4.0 woman construction worker
+1F477 200D 2640 ; minimally-qualified # 👷♀ E4.0 woman construction worker
+1F477 1F3FB 200D 2640 FE0F ; fully-qualified # 👷🏻♀️ E4.0 woman construction worker: light skin tone
+1F477 1F3FB 200D 2640 ; minimally-qualified # 👷🏻♀ E4.0 woman construction worker: light skin tone
+1F477 1F3FC 200D 2640 FE0F ; fully-qualified # 👷🏼♀️ E4.0 woman construction worker: medium-light skin tone
+1F477 1F3FC 200D 2640 ; minimally-qualified # 👷🏼♀ E4.0 woman construction worker: medium-light skin tone
+1F477 1F3FD 200D 2640 FE0F ; fully-qualified # 👷🏽♀️ E4.0 woman construction worker: medium skin tone
+1F477 1F3FD 200D 2640 ; minimally-qualified # 👷🏽♀ E4.0 woman construction worker: medium skin tone
+1F477 1F3FE 200D 2640 FE0F ; fully-qualified # 👷🏾♀️ E4.0 woman construction worker: medium-dark skin tone
+1F477 1F3FE 200D 2640 ; minimally-qualified # 👷🏾♀ E4.0 woman construction worker: medium-dark skin tone
+1F477 1F3FF 200D 2640 FE0F ; fully-qualified # 👷🏿♀️ E4.0 woman construction worker: dark skin tone
+1F477 1F3FF 200D 2640 ; minimally-qualified # 👷🏿♀ E4.0 woman construction worker: dark skin tone
+1FAC5 ; fully-qualified # 🫅 E14.0 person with crown
+1FAC5 1F3FB ; fully-qualified # 🫅🏻 E14.0 person with crown: light skin tone
+1FAC5 1F3FC ; fully-qualified # 🫅🏼 E14.0 person with crown: medium-light skin tone
+1FAC5 1F3FD ; fully-qualified # 🫅🏽 E14.0 person with crown: medium skin tone
+1FAC5 1F3FE ; fully-qualified # 🫅🏾 E14.0 person with crown: medium-dark skin tone
+1FAC5 1F3FF ; fully-qualified # 🫅🏿 E14.0 person with crown: dark skin tone
+1F934 ; fully-qualified # 🤴 E3.0 prince
+1F934 1F3FB ; fully-qualified # 🤴🏻 E3.0 prince: light skin tone
+1F934 1F3FC ; fully-qualified # 🤴🏼 E3.0 prince: medium-light skin tone
+1F934 1F3FD ; fully-qualified # 🤴🏽 E3.0 prince: medium skin tone
+1F934 1F3FE ; fully-qualified # 🤴🏾 E3.0 prince: medium-dark skin tone
+1F934 1F3FF ; fully-qualified # 🤴🏿 E3.0 prince: dark skin tone
+1F478 ; fully-qualified # 👸 E0.6 princess
+1F478 1F3FB ; fully-qualified # 👸🏻 E1.0 princess: light skin tone
+1F478 1F3FC ; fully-qualified # 👸🏼 E1.0 princess: medium-light skin tone
+1F478 1F3FD ; fully-qualified # 👸🏽 E1.0 princess: medium skin tone
+1F478 1F3FE ; fully-qualified # 👸🏾 E1.0 princess: medium-dark skin tone
+1F478 1F3FF ; fully-qualified # 👸🏿 E1.0 princess: dark skin tone
+1F473 ; fully-qualified # 👳 E0.6 person wearing turban
+1F473 1F3FB ; fully-qualified # 👳🏻 E1.0 person wearing turban: light skin tone
+1F473 1F3FC ; fully-qualified # 👳🏼 E1.0 person wearing turban: medium-light skin tone
+1F473 1F3FD ; fully-qualified # 👳🏽 E1.0 person wearing turban: medium skin tone
+1F473 1F3FE ; fully-qualified # 👳🏾 E1.0 person wearing turban: medium-dark skin tone
+1F473 1F3FF ; fully-qualified # 👳🏿 E1.0 person wearing turban: dark skin tone
+1F473 200D 2642 FE0F ; fully-qualified # 👳♂️ E4.0 man wearing turban
+1F473 200D 2642 ; minimally-qualified # 👳♂ E4.0 man wearing turban
+1F473 1F3FB 200D 2642 FE0F ; fully-qualified # 👳🏻♂️ E4.0 man wearing turban: light skin tone
+1F473 1F3FB 200D 2642 ; minimally-qualified # 👳🏻♂ E4.0 man wearing turban: light skin tone
+1F473 1F3FC 200D 2642 FE0F ; fully-qualified # 👳🏼♂️ E4.0 man wearing turban: medium-light skin tone
+1F473 1F3FC 200D 2642 ; minimally-qualified # 👳🏼♂ E4.0 man wearing turban: medium-light skin tone
+1F473 1F3FD 200D 2642 FE0F ; fully-qualified # 👳🏽♂️ E4.0 man wearing turban: medium skin tone
+1F473 1F3FD 200D 2642 ; minimally-qualified # 👳🏽♂ E4.0 man wearing turban: medium skin tone
+1F473 1F3FE 200D 2642 FE0F ; fully-qualified # 👳🏾♂️ E4.0 man wearing turban: medium-dark skin tone
+1F473 1F3FE 200D 2642 ; minimally-qualified # 👳🏾♂ E4.0 man wearing turban: medium-dark skin tone
+1F473 1F3FF 200D 2642 FE0F ; fully-qualified # 👳🏿♂️ E4.0 man wearing turban: dark skin tone
+1F473 1F3FF 200D 2642 ; minimally-qualified # 👳🏿♂ E4.0 man wearing turban: dark skin tone
+1F473 200D 2640 FE0F ; fully-qualified # 👳♀️ E4.0 woman wearing turban
+1F473 200D 2640 ; minimally-qualified # 👳♀ E4.0 woman wearing turban
+1F473 1F3FB 200D 2640 FE0F ; fully-qualified # 👳🏻♀️ E4.0 woman wearing turban: light skin tone
+1F473 1F3FB 200D 2640 ; minimally-qualified # 👳🏻♀ E4.0 woman wearing turban: light skin tone
+1F473 1F3FC 200D 2640 FE0F ; fully-qualified # 👳🏼♀️ E4.0 woman wearing turban: medium-light skin tone
+1F473 1F3FC 200D 2640 ; minimally-qualified # 👳🏼♀ E4.0 woman wearing turban: medium-light skin tone
+1F473 1F3FD 200D 2640 FE0F ; fully-qualified # 👳🏽♀️ E4.0 woman wearing turban: medium skin tone
+1F473 1F3FD 200D 2640 ; minimally-qualified # 👳🏽♀ E4.0 woman wearing turban: medium skin tone
+1F473 1F3FE 200D 2640 FE0F ; fully-qualified # 👳🏾♀️ E4.0 woman wearing turban: medium-dark skin tone
+1F473 1F3FE 200D 2640 ; minimally-qualified # 👳🏾♀ E4.0 woman wearing turban: medium-dark skin tone
+1F473 1F3FF 200D 2640 FE0F ; fully-qualified # 👳🏿♀️ E4.0 woman wearing turban: dark skin tone
+1F473 1F3FF 200D 2640 ; minimally-qualified # 👳🏿♀ E4.0 woman wearing turban: dark skin tone
+1F472 ; fully-qualified # 👲 E0.6 person with skullcap
+1F472 1F3FB ; fully-qualified # 👲🏻 E1.0 person with skullcap: light skin tone
+1F472 1F3FC ; fully-qualified # 👲🏼 E1.0 person with skullcap: medium-light skin tone
+1F472 1F3FD ; fully-qualified # 👲🏽 E1.0 person with skullcap: medium skin tone
+1F472 1F3FE ; fully-qualified # 👲🏾 E1.0 person with skullcap: medium-dark skin tone
+1F472 1F3FF ; fully-qualified # 👲🏿 E1.0 person with skullcap: dark skin tone
+1F9D5 ; fully-qualified # 🧕 E5.0 woman with headscarf
+1F9D5 1F3FB ; fully-qualified # 🧕🏻 E5.0 woman with headscarf: light skin tone
+1F9D5 1F3FC ; fully-qualified # 🧕🏼 E5.0 woman with headscarf: medium-light skin tone
+1F9D5 1F3FD ; fully-qualified # 🧕🏽 E5.0 woman with headscarf: medium skin tone
+1F9D5 1F3FE ; fully-qualified # 🧕🏾 E5.0 woman with headscarf: medium-dark skin tone
+1F9D5 1F3FF ; fully-qualified # 🧕🏿 E5.0 woman with headscarf: dark skin tone
+1F935 ; fully-qualified # 🤵 E3.0 person in tuxedo
+1F935 1F3FB ; fully-qualified # 🤵🏻 E3.0 person in tuxedo: light skin tone
+1F935 1F3FC ; fully-qualified # 🤵🏼 E3.0 person in tuxedo: medium-light skin tone
+1F935 1F3FD ; fully-qualified # 🤵🏽 E3.0 person in tuxedo: medium skin tone
+1F935 1F3FE ; fully-qualified # 🤵🏾 E3.0 person in tuxedo: medium-dark skin tone
+1F935 1F3FF ; fully-qualified # 🤵🏿 E3.0 person in tuxedo: dark skin tone
+1F935 200D 2642 FE0F ; fully-qualified # 🤵♂️ E13.0 man in tuxedo
+1F935 200D 2642 ; minimally-qualified # 🤵♂ E13.0 man in tuxedo
+1F935 1F3FB 200D 2642 FE0F ; fully-qualified # 🤵🏻♂️ E13.0 man in tuxedo: light skin tone
+1F935 1F3FB 200D 2642 ; minimally-qualified # 🤵🏻♂ E13.0 man in tuxedo: light skin tone
+1F935 1F3FC 200D 2642 FE0F ; fully-qualified # 🤵🏼♂️ E13.0 man in tuxedo: medium-light skin tone
+1F935 1F3FC 200D 2642 ; minimally-qualified # 🤵🏼♂ E13.0 man in tuxedo: medium-light skin tone
+1F935 1F3FD 200D 2642 FE0F ; fully-qualified # 🤵🏽♂️ E13.0 man in tuxedo: medium skin tone
+1F935 1F3FD 200D 2642 ; minimally-qualified # 🤵🏽♂ E13.0 man in tuxedo: medium skin tone
+1F935 1F3FE 200D 2642 FE0F ; fully-qualified # 🤵🏾♂️ E13.0 man in tuxedo: medium-dark skin tone
+1F935 1F3FE 200D 2642 ; minimally-qualified # 🤵🏾♂ E13.0 man in tuxedo: medium-dark skin tone
+1F935 1F3FF 200D 2642 FE0F ; fully-qualified # 🤵🏿♂️ E13.0 man in tuxedo: dark skin tone
+1F935 1F3FF 200D 2642 ; minimally-qualified # 🤵🏿♂ E13.0 man in tuxedo: dark skin tone
+1F935 200D 2640 FE0F ; fully-qualified # 🤵♀️ E13.0 woman in tuxedo
+1F935 200D 2640 ; minimally-qualified # 🤵♀ E13.0 woman in tuxedo
+1F935 1F3FB 200D 2640 FE0F ; fully-qualified # 🤵🏻♀️ E13.0 woman in tuxedo: light skin tone
+1F935 1F3FB 200D 2640 ; minimally-qualified # 🤵🏻♀ E13.0 woman in tuxedo: light skin tone
+1F935 1F3FC 200D 2640 FE0F ; fully-qualified # 🤵🏼♀️ E13.0 woman in tuxedo: medium-light skin tone
+1F935 1F3FC 200D 2640 ; minimally-qualified # 🤵🏼♀ E13.0 woman in tuxedo: medium-light skin tone
+1F935 1F3FD 200D 2640 FE0F ; fully-qualified # 🤵🏽♀️ E13.0 woman in tuxedo: medium skin tone
+1F935 1F3FD 200D 2640 ; minimally-qualified # 🤵🏽♀ E13.0 woman in tuxedo: medium skin tone
+1F935 1F3FE 200D 2640 FE0F ; fully-qualified # 🤵🏾♀️ E13.0 woman in tuxedo: medium-dark skin tone
+1F935 1F3FE 200D 2640 ; minimally-qualified # 🤵🏾♀ E13.0 woman in tuxedo: medium-dark skin tone
+1F935 1F3FF 200D 2640 FE0F ; fully-qualified # 🤵🏿♀️ E13.0 woman in tuxedo: dark skin tone
+1F935 1F3FF 200D 2640 ; minimally-qualified # 🤵🏿♀ E13.0 woman in tuxedo: dark skin tone
+1F470 ; fully-qualified # 👰 E0.6 person with veil
+1F470 1F3FB ; fully-qualified # 👰🏻 E1.0 person with veil: light skin tone
+1F470 1F3FC ; fully-qualified # 👰🏼 E1.0 person with veil: medium-light skin tone
+1F470 1F3FD ; fully-qualified # 👰🏽 E1.0 person with veil: medium skin tone
+1F470 1F3FE ; fully-qualified # 👰🏾 E1.0 person with veil: medium-dark skin tone
+1F470 1F3FF ; fully-qualified # 👰🏿 E1.0 person with veil: dark skin tone
+1F470 200D 2642 FE0F ; fully-qualified # 👰♂️ E13.0 man with veil
+1F470 200D 2642 ; minimally-qualified # 👰♂ E13.0 man with veil
+1F470 1F3FB 200D 2642 FE0F ; fully-qualified # 👰🏻♂️ E13.0 man with veil: light skin tone
+1F470 1F3FB 200D 2642 ; minimally-qualified # 👰🏻♂ E13.0 man with veil: light skin tone
+1F470 1F3FC 200D 2642 FE0F ; fully-qualified # 👰🏼♂️ E13.0 man with veil: medium-light skin tone
+1F470 1F3FC 200D 2642 ; minimally-qualified # 👰🏼♂ E13.0 man with veil: medium-light skin tone
+1F470 1F3FD 200D 2642 FE0F ; fully-qualified # 👰🏽♂️ E13.0 man with veil: medium skin tone
+1F470 1F3FD 200D 2642 ; minimally-qualified # 👰🏽♂ E13.0 man with veil: medium skin tone
+1F470 1F3FE 200D 2642 FE0F ; fully-qualified # 👰🏾♂️ E13.0 man with veil: medium-dark skin tone
+1F470 1F3FE 200D 2642 ; minimally-qualified # 👰🏾♂ E13.0 man with veil: medium-dark skin tone
+1F470 1F3FF 200D 2642 FE0F ; fully-qualified # 👰🏿♂️ E13.0 man with veil: dark skin tone
+1F470 1F3FF 200D 2642 ; minimally-qualified # 👰🏿♂ E13.0 man with veil: dark skin tone
+1F470 200D 2640 FE0F ; fully-qualified # 👰♀️ E13.0 woman with veil
+1F470 200D 2640 ; minimally-qualified # 👰♀ E13.0 woman with veil
+1F470 1F3FB 200D 2640 FE0F ; fully-qualified # 👰🏻♀️ E13.0 woman with veil: light skin tone
+1F470 1F3FB 200D 2640 ; minimally-qualified # 👰🏻♀ E13.0 woman with veil: light skin tone
+1F470 1F3FC 200D 2640 FE0F ; fully-qualified # 👰🏼♀️ E13.0 woman with veil: medium-light skin tone
+1F470 1F3FC 200D 2640 ; minimally-qualified # 👰🏼♀ E13.0 woman with veil: medium-light skin tone
+1F470 1F3FD 200D 2640 FE0F ; fully-qualified # 👰🏽♀️ E13.0 woman with veil: medium skin tone
+1F470 1F3FD 200D 2640 ; minimally-qualified # 👰🏽♀ E13.0 woman with veil: medium skin tone
+1F470 1F3FE 200D 2640 FE0F ; fully-qualified # 👰🏾♀️ E13.0 woman with veil: medium-dark skin tone
+1F470 1F3FE 200D 2640 ; minimally-qualified # 👰🏾♀ E13.0 woman with veil: medium-dark skin tone
+1F470 1F3FF 200D 2640 FE0F ; fully-qualified # 👰🏿♀️ E13.0 woman with veil: dark skin tone
+1F470 1F3FF 200D 2640 ; minimally-qualified # 👰🏿♀ E13.0 woman with veil: dark skin tone
+1F930 ; fully-qualified # 🤰 E3.0 pregnant woman
+1F930 1F3FB ; fully-qualified # 🤰🏻 E3.0 pregnant woman: light skin tone
+1F930 1F3FC ; fully-qualified # 🤰🏼 E3.0 pregnant woman: medium-light skin tone
+1F930 1F3FD ; fully-qualified # 🤰🏽 E3.0 pregnant woman: medium skin tone
+1F930 1F3FE ; fully-qualified # 🤰🏾 E3.0 pregnant woman: medium-dark skin tone
+1F930 1F3FF ; fully-qualified # 🤰🏿 E3.0 pregnant woman: dark skin tone
+1FAC3 ; fully-qualified # 🫃 E14.0 pregnant man
+1FAC3 1F3FB ; fully-qualified # 🫃🏻 E14.0 pregnant man: light skin tone
+1FAC3 1F3FC ; fully-qualified # 🫃🏼 E14.0 pregnant man: medium-light skin tone
+1FAC3 1F3FD ; fully-qualified # 🫃🏽 E14.0 pregnant man: medium skin tone
+1FAC3 1F3FE ; fully-qualified # 🫃🏾 E14.0 pregnant man: medium-dark skin tone
+1FAC3 1F3FF ; fully-qualified # 🫃🏿 E14.0 pregnant man: dark skin tone
+1FAC4 ; fully-qualified # 🫄 E14.0 pregnant person
+1FAC4 1F3FB ; fully-qualified # 🫄🏻 E14.0 pregnant person: light skin tone
+1FAC4 1F3FC ; fully-qualified # 🫄🏼 E14.0 pregnant person: medium-light skin tone
+1FAC4 1F3FD ; fully-qualified # 🫄🏽 E14.0 pregnant person: medium skin tone
+1FAC4 1F3FE ; fully-qualified # 🫄🏾 E14.0 pregnant person: medium-dark skin tone
+1FAC4 1F3FF ; fully-qualified # 🫄🏿 E14.0 pregnant person: dark skin tone
+1F931 ; fully-qualified # 🤱 E5.0 breast-feeding
+1F931 1F3FB ; fully-qualified # 🤱🏻 E5.0 breast-feeding: light skin tone
+1F931 1F3FC ; fully-qualified # 🤱🏼 E5.0 breast-feeding: medium-light skin tone
+1F931 1F3FD ; fully-qualified # 🤱🏽 E5.0 breast-feeding: medium skin tone
+1F931 1F3FE ; fully-qualified # 🤱🏾 E5.0 breast-feeding: medium-dark skin tone
+1F931 1F3FF ; fully-qualified # 🤱🏿 E5.0 breast-feeding: dark skin tone
+1F469 200D 1F37C ; fully-qualified # 👩🍼 E13.0 woman feeding baby
+1F469 1F3FB 200D 1F37C ; fully-qualified # 👩🏻🍼 E13.0 woman feeding baby: light skin tone
+1F469 1F3FC 200D 1F37C ; fully-qualified # 👩🏼🍼 E13.0 woman feeding baby: medium-light skin tone
+1F469 1F3FD 200D 1F37C ; fully-qualified # 👩🏽🍼 E13.0 woman feeding baby: medium skin tone
+1F469 1F3FE 200D 1F37C ; fully-qualified # 👩🏾🍼 E13.0 woman feeding baby: medium-dark skin tone
+1F469 1F3FF 200D 1F37C ; fully-qualified # 👩🏿🍼 E13.0 woman feeding baby: dark skin tone
+1F468 200D 1F37C ; fully-qualified # 👨🍼 E13.0 man feeding baby
+1F468 1F3FB 200D 1F37C ; fully-qualified # 👨🏻🍼 E13.0 man feeding baby: light skin tone
+1F468 1F3FC 200D 1F37C ; fully-qualified # 👨🏼🍼 E13.0 man feeding baby: medium-light skin tone
+1F468 1F3FD 200D 1F37C ; fully-qualified # 👨🏽🍼 E13.0 man feeding baby: medium skin tone
+1F468 1F3FE 200D 1F37C ; fully-qualified # 👨🏾🍼 E13.0 man feeding baby: medium-dark skin tone
+1F468 1F3FF 200D 1F37C ; fully-qualified # 👨🏿🍼 E13.0 man feeding baby: dark skin tone
+1F9D1 200D 1F37C ; fully-qualified # 🧑🍼 E13.0 person feeding baby
+1F9D1 1F3FB 200D 1F37C ; fully-qualified # 🧑🏻🍼 E13.0 person feeding baby: light skin tone
+1F9D1 1F3FC 200D 1F37C ; fully-qualified # 🧑🏼🍼 E13.0 person feeding baby: medium-light skin tone
+1F9D1 1F3FD 200D 1F37C ; fully-qualified # 🧑🏽🍼 E13.0 person feeding baby: medium skin tone
+1F9D1 1F3FE 200D 1F37C ; fully-qualified # 🧑🏾🍼 E13.0 person feeding baby: medium-dark skin tone
+1F9D1 1F3FF 200D 1F37C ; fully-qualified # 🧑🏿🍼 E13.0 person feeding baby: dark skin tone
+
+# subgroup: person-fantasy
+1F47C ; fully-qualified # 👼 E0.6 baby angel
+1F47C 1F3FB ; fully-qualified # 👼🏻 E1.0 baby angel: light skin tone
+1F47C 1F3FC ; fully-qualified # 👼🏼 E1.0 baby angel: medium-light skin tone
+1F47C 1F3FD ; fully-qualified # 👼🏽 E1.0 baby angel: medium skin tone
+1F47C 1F3FE ; fully-qualified # 👼🏾 E1.0 baby angel: medium-dark skin tone
+1F47C 1F3FF ; fully-qualified # 👼🏿 E1.0 baby angel: dark skin tone
+1F385 ; fully-qualified # 🎅 E0.6 Santa Claus
+1F385 1F3FB ; fully-qualified # 🎅🏻 E1.0 Santa Claus: light skin tone
+1F385 1F3FC ; fully-qualified # 🎅🏼 E1.0 Santa Claus: medium-light skin tone
+1F385 1F3FD ; fully-qualified # 🎅🏽 E1.0 Santa Claus: medium skin tone
+1F385 1F3FE ; fully-qualified # 🎅🏾 E1.0 Santa Claus: medium-dark skin tone
+1F385 1F3FF ; fully-qualified # 🎅🏿 E1.0 Santa Claus: dark skin tone
+1F936 ; fully-qualified # 🤶 E3.0 Mrs. Claus
+1F936 1F3FB ; fully-qualified # 🤶🏻 E3.0 Mrs. Claus: light skin tone
+1F936 1F3FC ; fully-qualified # 🤶🏼 E3.0 Mrs. Claus: medium-light skin tone
+1F936 1F3FD ; fully-qualified # 🤶🏽 E3.0 Mrs. Claus: medium skin tone
+1F936 1F3FE ; fully-qualified # 🤶🏾 E3.0 Mrs. Claus: medium-dark skin tone
+1F936 1F3FF ; fully-qualified # 🤶🏿 E3.0 Mrs. Claus: dark skin tone
+1F9D1 200D 1F384 ; fully-qualified # 🧑🎄 E13.0 Mx Claus
+1F9D1 1F3FB 200D 1F384 ; fully-qualified # 🧑🏻🎄 E13.0 Mx Claus: light skin tone
+1F9D1 1F3FC 200D 1F384 ; fully-qualified # 🧑🏼🎄 E13.0 Mx Claus: medium-light skin tone
+1F9D1 1F3FD 200D 1F384 ; fully-qualified # 🧑🏽🎄 E13.0 Mx Claus: medium skin tone
+1F9D1 1F3FE 200D 1F384 ; fully-qualified # 🧑🏾🎄 E13.0 Mx Claus: medium-dark skin tone
+1F9D1 1F3FF 200D 1F384 ; fully-qualified # 🧑🏿🎄 E13.0 Mx Claus: dark skin tone
+1F9B8 ; fully-qualified # 🦸 E11.0 superhero
+1F9B8 1F3FB ; fully-qualified # 🦸🏻 E11.0 superhero: light skin tone
+1F9B8 1F3FC ; fully-qualified # 🦸🏼 E11.0 superhero: medium-light skin tone
+1F9B8 1F3FD ; fully-qualified # 🦸🏽 E11.0 superhero: medium skin tone
+1F9B8 1F3FE ; fully-qualified # 🦸🏾 E11.0 superhero: medium-dark skin tone
+1F9B8 1F3FF ; fully-qualified # 🦸🏿 E11.0 superhero: dark skin tone
+1F9B8 200D 2642 FE0F ; fully-qualified # 🦸♂️ E11.0 man superhero
+1F9B8 200D 2642 ; minimally-qualified # 🦸♂ E11.0 man superhero
+1F9B8 1F3FB 200D 2642 FE0F ; fully-qualified # 🦸🏻♂️ E11.0 man superhero: light skin tone
+1F9B8 1F3FB 200D 2642 ; minimally-qualified # 🦸🏻♂ E11.0 man superhero: light skin tone
+1F9B8 1F3FC 200D 2642 FE0F ; fully-qualified # 🦸🏼♂️ E11.0 man superhero: medium-light skin tone
+1F9B8 1F3FC 200D 2642 ; minimally-qualified # 🦸🏼♂ E11.0 man superhero: medium-light skin tone
+1F9B8 1F3FD 200D 2642 FE0F ; fully-qualified # 🦸🏽♂️ E11.0 man superhero: medium skin tone
+1F9B8 1F3FD 200D 2642 ; minimally-qualified # 🦸🏽♂ E11.0 man superhero: medium skin tone
+1F9B8 1F3FE 200D 2642 FE0F ; fully-qualified # 🦸🏾♂️ E11.0 man superhero: medium-dark skin tone
+1F9B8 1F3FE 200D 2642 ; minimally-qualified # 🦸🏾♂ E11.0 man superhero: medium-dark skin tone
+1F9B8 1F3FF 200D 2642 FE0F ; fully-qualified # 🦸🏿♂️ E11.0 man superhero: dark skin tone
+1F9B8 1F3FF 200D 2642 ; minimally-qualified # 🦸🏿♂ E11.0 man superhero: dark skin tone
+1F9B8 200D 2640 FE0F ; fully-qualified # 🦸♀️ E11.0 woman superhero
+1F9B8 200D 2640 ; minimally-qualified # 🦸♀ E11.0 woman superhero
+1F9B8 1F3FB 200D 2640 FE0F ; fully-qualified # 🦸🏻♀️ E11.0 woman superhero: light skin tone
+1F9B8 1F3FB 200D 2640 ; minimally-qualified # 🦸🏻♀ E11.0 woman superhero: light skin tone
+1F9B8 1F3FC 200D 2640 FE0F ; fully-qualified # 🦸🏼♀️ E11.0 woman superhero: medium-light skin tone
+1F9B8 1F3FC 200D 2640 ; minimally-qualified # 🦸🏼♀ E11.0 woman superhero: medium-light skin tone
+1F9B8 1F3FD 200D 2640 FE0F ; fully-qualified # 🦸🏽♀️ E11.0 woman superhero: medium skin tone
+1F9B8 1F3FD 200D 2640 ; minimally-qualified # 🦸🏽♀ E11.0 woman superhero: medium skin tone
+1F9B8 1F3FE 200D 2640 FE0F ; fully-qualified # 🦸🏾♀️ E11.0 woman superhero: medium-dark skin tone
+1F9B8 1F3FE 200D 2640 ; minimally-qualified # 🦸🏾♀ E11.0 woman superhero: medium-dark skin tone
+1F9B8 1F3FF 200D 2640 FE0F ; fully-qualified # 🦸🏿♀️ E11.0 woman superhero: dark skin tone
+1F9B8 1F3FF 200D 2640 ; minimally-qualified # 🦸🏿♀ E11.0 woman superhero: dark skin tone
+1F9B9 ; fully-qualified # 🦹 E11.0 supervillain
+1F9B9 1F3FB ; fully-qualified # 🦹🏻 E11.0 supervillain: light skin tone
+1F9B9 1F3FC ; fully-qualified # 🦹🏼 E11.0 supervillain: medium-light skin tone
+1F9B9 1F3FD ; fully-qualified # 🦹🏽 E11.0 supervillain: medium skin tone
+1F9B9 1F3FE ; fully-qualified # 🦹🏾 E11.0 supervillain: medium-dark skin tone
+1F9B9 1F3FF ; fully-qualified # 🦹🏿 E11.0 supervillain: dark skin tone
+1F9B9 200D 2642 FE0F ; fully-qualified # 🦹♂️ E11.0 man supervillain
+1F9B9 200D 2642 ; minimally-qualified # 🦹♂ E11.0 man supervillain
+1F9B9 1F3FB 200D 2642 FE0F ; fully-qualified # 🦹🏻♂️ E11.0 man supervillain: light skin tone
+1F9B9 1F3FB 200D 2642 ; minimally-qualified # 🦹🏻♂ E11.0 man supervillain: light skin tone
+1F9B9 1F3FC 200D 2642 FE0F ; fully-qualified # 🦹🏼♂️ E11.0 man supervillain: medium-light skin tone
+1F9B9 1F3FC 200D 2642 ; minimally-qualified # 🦹🏼♂ E11.0 man supervillain: medium-light skin tone
+1F9B9 1F3FD 200D 2642 FE0F ; fully-qualified # 🦹🏽♂️ E11.0 man supervillain: medium skin tone
+1F9B9 1F3FD 200D 2642 ; minimally-qualified # 🦹🏽♂ E11.0 man supervillain: medium skin tone
+1F9B9 1F3FE 200D 2642 FE0F ; fully-qualified # 🦹🏾♂️ E11.0 man supervillain: medium-dark skin tone
+1F9B9 1F3FE 200D 2642 ; minimally-qualified # 🦹🏾♂ E11.0 man supervillain: medium-dark skin tone
+1F9B9 1F3FF 200D 2642 FE0F ; fully-qualified # 🦹🏿♂️ E11.0 man supervillain: dark skin tone
+1F9B9 1F3FF 200D 2642 ; minimally-qualified # 🦹🏿♂ E11.0 man supervillain: dark skin tone
+1F9B9 200D 2640 FE0F ; fully-qualified # 🦹♀️ E11.0 woman supervillain
+1F9B9 200D 2640 ; minimally-qualified # 🦹♀ E11.0 woman supervillain
+1F9B9 1F3FB 200D 2640 FE0F ; fully-qualified # 🦹🏻♀️ E11.0 woman supervillain: light skin tone
+1F9B9 1F3FB 200D 2640 ; minimally-qualified # 🦹🏻♀ E11.0 woman supervillain: light skin tone
+1F9B9 1F3FC 200D 2640 FE0F ; fully-qualified # 🦹🏼♀️ E11.0 woman supervillain: medium-light skin tone
+1F9B9 1F3FC 200D 2640 ; minimally-qualified # 🦹🏼♀ E11.0 woman supervillain: medium-light skin tone
+1F9B9 1F3FD 200D 2640 FE0F ; fully-qualified # 🦹🏽♀️ E11.0 woman supervillain: medium skin tone
+1F9B9 1F3FD 200D 2640 ; minimally-qualified # 🦹🏽♀ E11.0 woman supervillain: medium skin tone
+1F9B9 1F3FE 200D 2640 FE0F ; fully-qualified # 🦹🏾♀️ E11.0 woman supervillain: medium-dark skin tone
+1F9B9 1F3FE 200D 2640 ; minimally-qualified # 🦹🏾♀ E11.0 woman supervillain: medium-dark skin tone
+1F9B9 1F3FF 200D 2640 FE0F ; fully-qualified # 🦹🏿♀️ E11.0 woman supervillain: dark skin tone
+1F9B9 1F3FF 200D 2640 ; minimally-qualified # 🦹🏿♀ E11.0 woman supervillain: dark skin tone
+1F9D9 ; fully-qualified # 🧙 E5.0 mage
+1F9D9 1F3FB ; fully-qualified # 🧙🏻 E5.0 mage: light skin tone
+1F9D9 1F3FC ; fully-qualified # 🧙🏼 E5.0 mage: medium-light skin tone
+1F9D9 1F3FD ; fully-qualified # 🧙🏽 E5.0 mage: medium skin tone
+1F9D9 1F3FE ; fully-qualified # 🧙🏾 E5.0 mage: medium-dark skin tone
+1F9D9 1F3FF ; fully-qualified # 🧙🏿 E5.0 mage: dark skin tone
+1F9D9 200D 2642 FE0F ; fully-qualified # 🧙♂️ E5.0 man mage
+1F9D9 200D 2642 ; minimally-qualified # 🧙♂ E5.0 man mage
+1F9D9 1F3FB 200D 2642 FE0F ; fully-qualified # 🧙🏻♂️ E5.0 man mage: light skin tone
+1F9D9 1F3FB 200D 2642 ; minimally-qualified # 🧙🏻♂ E5.0 man mage: light skin tone
+1F9D9 1F3FC 200D 2642 FE0F ; fully-qualified # 🧙🏼♂️ E5.0 man mage: medium-light skin tone
+1F9D9 1F3FC 200D 2642 ; minimally-qualified # 🧙🏼♂ E5.0 man mage: medium-light skin tone
+1F9D9 1F3FD 200D 2642 FE0F ; fully-qualified # 🧙🏽♂️ E5.0 man mage: medium skin tone
+1F9D9 1F3FD 200D 2642 ; minimally-qualified # 🧙🏽♂ E5.0 man mage: medium skin tone
+1F9D9 1F3FE 200D 2642 FE0F ; fully-qualified # 🧙🏾♂️ E5.0 man mage: medium-dark skin tone
+1F9D9 1F3FE 200D 2642 ; minimally-qualified # 🧙🏾♂ E5.0 man mage: medium-dark skin tone
+1F9D9 1F3FF 200D 2642 FE0F ; fully-qualified # 🧙🏿♂️ E5.0 man mage: dark skin tone
+1F9D9 1F3FF 200D 2642 ; minimally-qualified # 🧙🏿♂ E5.0 man mage: dark skin tone
+1F9D9 200D 2640 FE0F ; fully-qualified # 🧙♀️ E5.0 woman mage
+1F9D9 200D 2640 ; minimally-qualified # 🧙♀ E5.0 woman mage
+1F9D9 1F3FB 200D 2640 FE0F ; fully-qualified # 🧙🏻♀️ E5.0 woman mage: light skin tone
+1F9D9 1F3FB 200D 2640 ; minimally-qualified # 🧙🏻♀ E5.0 woman mage: light skin tone
+1F9D9 1F3FC 200D 2640 FE0F ; fully-qualified # 🧙🏼♀️ E5.0 woman mage: medium-light skin tone
+1F9D9 1F3FC 200D 2640 ; minimally-qualified # 🧙🏼♀ E5.0 woman mage: medium-light skin tone
+1F9D9 1F3FD 200D 2640 FE0F ; fully-qualified # 🧙🏽♀️ E5.0 woman mage: medium skin tone
+1F9D9 1F3FD 200D 2640 ; minimally-qualified # 🧙🏽♀ E5.0 woman mage: medium skin tone
+1F9D9 1F3FE 200D 2640 FE0F ; fully-qualified # 🧙🏾♀️ E5.0 woman mage: medium-dark skin tone
+1F9D9 1F3FE 200D 2640 ; minimally-qualified # 🧙🏾♀ E5.0 woman mage: medium-dark skin tone
+1F9D9 1F3FF 200D 2640 FE0F ; fully-qualified # 🧙🏿♀️ E5.0 woman mage: dark skin tone
+1F9D9 1F3FF 200D 2640 ; minimally-qualified # 🧙🏿♀ E5.0 woman mage: dark skin tone
+1F9DA ; fully-qualified # 🧚 E5.0 fairy
+1F9DA 1F3FB ; fully-qualified # 🧚🏻 E5.0 fairy: light skin tone
+1F9DA 1F3FC ; fully-qualified # 🧚🏼 E5.0 fairy: medium-light skin tone
+1F9DA 1F3FD ; fully-qualified # 🧚🏽 E5.0 fairy: medium skin tone
+1F9DA 1F3FE ; fully-qualified # 🧚🏾 E5.0 fairy: medium-dark skin tone
+1F9DA 1F3FF ; fully-qualified # 🧚🏿 E5.0 fairy: dark skin tone
+1F9DA 200D 2642 FE0F ; fully-qualified # 🧚♂️ E5.0 man fairy
+1F9DA 200D 2642 ; minimally-qualified # 🧚♂ E5.0 man fairy
+1F9DA 1F3FB 200D 2642 FE0F ; fully-qualified # 🧚🏻♂️ E5.0 man fairy: light skin tone
+1F9DA 1F3FB 200D 2642 ; minimally-qualified # 🧚🏻♂ E5.0 man fairy: light skin tone
+1F9DA 1F3FC 200D 2642 FE0F ; fully-qualified # 🧚🏼♂️ E5.0 man fairy: medium-light skin tone
+1F9DA 1F3FC 200D 2642 ; minimally-qualified # 🧚🏼♂ E5.0 man fairy: medium-light skin tone
+1F9DA 1F3FD 200D 2642 FE0F ; fully-qualified # 🧚🏽♂️ E5.0 man fairy: medium skin tone
+1F9DA 1F3FD 200D 2642 ; minimally-qualified # 🧚🏽♂ E5.0 man fairy: medium skin tone
+1F9DA 1F3FE 200D 2642 FE0F ; fully-qualified # 🧚🏾♂️ E5.0 man fairy: medium-dark skin tone
+1F9DA 1F3FE 200D 2642 ; minimally-qualified # 🧚🏾♂ E5.0 man fairy: medium-dark skin tone
+1F9DA 1F3FF 200D 2642 FE0F ; fully-qualified # 🧚🏿♂️ E5.0 man fairy: dark skin tone
+1F9DA 1F3FF 200D 2642 ; minimally-qualified # 🧚🏿♂ E5.0 man fairy: dark skin tone
+1F9DA 200D 2640 FE0F ; fully-qualified # 🧚♀️ E5.0 woman fairy
+1F9DA 200D 2640 ; minimally-qualified # 🧚♀ E5.0 woman fairy
+1F9DA 1F3FB 200D 2640 FE0F ; fully-qualified # 🧚🏻♀️ E5.0 woman fairy: light skin tone
+1F9DA 1F3FB 200D 2640 ; minimally-qualified # 🧚🏻♀ E5.0 woman fairy: light skin tone
+1F9DA 1F3FC 200D 2640 FE0F ; fully-qualified # 🧚🏼♀️ E5.0 woman fairy: medium-light skin tone
+1F9DA 1F3FC 200D 2640 ; minimally-qualified # 🧚🏼♀ E5.0 woman fairy: medium-light skin tone
+1F9DA 1F3FD 200D 2640 FE0F ; fully-qualified # 🧚🏽♀️ E5.0 woman fairy: medium skin tone
+1F9DA 1F3FD 200D 2640 ; minimally-qualified # 🧚🏽♀ E5.0 woman fairy: medium skin tone
+1F9DA 1F3FE 200D 2640 FE0F ; fully-qualified # 🧚🏾♀️ E5.0 woman fairy: medium-dark skin tone
+1F9DA 1F3FE 200D 2640 ; minimally-qualified # 🧚🏾♀ E5.0 woman fairy: medium-dark skin tone
+1F9DA 1F3FF 200D 2640 FE0F ; fully-qualified # 🧚🏿♀️ E5.0 woman fairy: dark skin tone
+1F9DA 1F3FF 200D 2640 ; minimally-qualified # 🧚🏿♀ E5.0 woman fairy: dark skin tone
+1F9DB ; fully-qualified # 🧛 E5.0 vampire
+1F9DB 1F3FB ; fully-qualified # 🧛🏻 E5.0 vampire: light skin tone
+1F9DB 1F3FC ; fully-qualified # 🧛🏼 E5.0 vampire: medium-light skin tone
+1F9DB 1F3FD ; fully-qualified # 🧛🏽 E5.0 vampire: medium skin tone
+1F9DB 1F3FE ; fully-qualified # 🧛🏾 E5.0 vampire: medium-dark skin tone
+1F9DB 1F3FF ; fully-qualified # 🧛🏿 E5.0 vampire: dark skin tone
+1F9DB 200D 2642 FE0F ; fully-qualified # 🧛♂️ E5.0 man vampire
+1F9DB 200D 2642 ; minimally-qualified # 🧛♂ E5.0 man vampire
+1F9DB 1F3FB 200D 2642 FE0F ; fully-qualified # 🧛🏻♂️ E5.0 man vampire: light skin tone
+1F9DB 1F3FB 200D 2642 ; minimally-qualified # 🧛🏻♂ E5.0 man vampire: light skin tone
+1F9DB 1F3FC 200D 2642 FE0F ; fully-qualified # 🧛🏼♂️ E5.0 man vampire: medium-light skin tone
+1F9DB 1F3FC 200D 2642 ; minimally-qualified # 🧛🏼♂ E5.0 man vampire: medium-light skin tone
+1F9DB 1F3FD 200D 2642 FE0F ; fully-qualified # 🧛🏽♂️ E5.0 man vampire: medium skin tone
+1F9DB 1F3FD 200D 2642 ; minimally-qualified # 🧛🏽♂ E5.0 man vampire: medium skin tone
+1F9DB 1F3FE 200D 2642 FE0F ; fully-qualified # 🧛🏾♂️ E5.0 man vampire: medium-dark skin tone
+1F9DB 1F3FE 200D 2642 ; minimally-qualified # 🧛🏾♂ E5.0 man vampire: medium-dark skin tone
+1F9DB 1F3FF 200D 2642 FE0F ; fully-qualified # 🧛🏿♂️ E5.0 man vampire: dark skin tone
+1F9DB 1F3FF 200D 2642 ; minimally-qualified # 🧛🏿♂ E5.0 man vampire: dark skin tone
+1F9DB 200D 2640 FE0F ; fully-qualified # 🧛♀️ E5.0 woman vampire
+1F9DB 200D 2640 ; minimally-qualified # 🧛♀ E5.0 woman vampire
+1F9DB 1F3FB 200D 2640 FE0F ; fully-qualified # 🧛🏻♀️ E5.0 woman vampire: light skin tone
+1F9DB 1F3FB 200D 2640 ; minimally-qualified # 🧛🏻♀ E5.0 woman vampire: light skin tone
+1F9DB 1F3FC 200D 2640 FE0F ; fully-qualified # 🧛🏼♀️ E5.0 woman vampire: medium-light skin tone
+1F9DB 1F3FC 200D 2640 ; minimally-qualified # 🧛🏼♀ E5.0 woman vampire: medium-light skin tone
+1F9DB 1F3FD 200D 2640 FE0F ; fully-qualified # 🧛🏽♀️ E5.0 woman vampire: medium skin tone
+1F9DB 1F3FD 200D 2640 ; minimally-qualified # 🧛🏽♀ E5.0 woman vampire: medium skin tone
+1F9DB 1F3FE 200D 2640 FE0F ; fully-qualified # 🧛🏾♀️ E5.0 woman vampire: medium-dark skin tone
+1F9DB 1F3FE 200D 2640 ; minimally-qualified # 🧛🏾♀ E5.0 woman vampire: medium-dark skin tone
+1F9DB 1F3FF 200D 2640 FE0F ; fully-qualified # 🧛🏿♀️ E5.0 woman vampire: dark skin tone
+1F9DB 1F3FF 200D 2640 ; minimally-qualified # 🧛🏿♀ E5.0 woman vampire: dark skin tone
+1F9DC ; fully-qualified # 🧜 E5.0 merperson
+1F9DC 1F3FB ; fully-qualified # 🧜🏻 E5.0 merperson: light skin tone
+1F9DC 1F3FC ; fully-qualified # 🧜🏼 E5.0 merperson: medium-light skin tone
+1F9DC 1F3FD ; fully-qualified # 🧜🏽 E5.0 merperson: medium skin tone
+1F9DC 1F3FE ; fully-qualified # 🧜🏾 E5.0 merperson: medium-dark skin tone
+1F9DC 1F3FF ; fully-qualified # 🧜🏿 E5.0 merperson: dark skin tone
+1F9DC 200D 2642 FE0F ; fully-qualified # 🧜♂️ E5.0 merman
+1F9DC 200D 2642 ; minimally-qualified # 🧜♂ E5.0 merman
+1F9DC 1F3FB 200D 2642 FE0F ; fully-qualified # 🧜🏻♂️ E5.0 merman: light skin tone
+1F9DC 1F3FB 200D 2642 ; minimally-qualified # 🧜🏻♂ E5.0 merman: light skin tone
+1F9DC 1F3FC 200D 2642 FE0F ; fully-qualified # 🧜🏼♂️ E5.0 merman: medium-light skin tone
+1F9DC 1F3FC 200D 2642 ; minimally-qualified # 🧜🏼♂ E5.0 merman: medium-light skin tone
+1F9DC 1F3FD 200D 2642 FE0F ; fully-qualified # 🧜🏽♂️ E5.0 merman: medium skin tone
+1F9DC 1F3FD 200D 2642 ; minimally-qualified # 🧜🏽♂ E5.0 merman: medium skin tone
+1F9DC 1F3FE 200D 2642 FE0F ; fully-qualified # 🧜🏾♂️ E5.0 merman: medium-dark skin tone
+1F9DC 1F3FE 200D 2642 ; minimally-qualified # 🧜🏾♂ E5.0 merman: medium-dark skin tone
+1F9DC 1F3FF 200D 2642 FE0F ; fully-qualified # 🧜🏿♂️ E5.0 merman: dark skin tone
+1F9DC 1F3FF 200D 2642 ; minimally-qualified # 🧜🏿♂ E5.0 merman: dark skin tone
+1F9DC 200D 2640 FE0F ; fully-qualified # 🧜♀️ E5.0 mermaid
+1F9DC 200D 2640 ; minimally-qualified # 🧜♀ E5.0 mermaid
+1F9DC 1F3FB 200D 2640 FE0F ; fully-qualified # 🧜🏻♀️ E5.0 mermaid: light skin tone
+1F9DC 1F3FB 200D 2640 ; minimally-qualified # 🧜🏻♀ E5.0 mermaid: light skin tone
+1F9DC 1F3FC 200D 2640 FE0F ; fully-qualified # 🧜🏼♀️ E5.0 mermaid: medium-light skin tone
+1F9DC 1F3FC 200D 2640 ; minimally-qualified # 🧜🏼♀ E5.0 mermaid: medium-light skin tone
+1F9DC 1F3FD 200D 2640 FE0F ; fully-qualified # 🧜🏽♀️ E5.0 mermaid: medium skin tone
+1F9DC 1F3FD 200D 2640 ; minimally-qualified # 🧜🏽♀ E5.0 mermaid: medium skin tone
+1F9DC 1F3FE 200D 2640 FE0F ; fully-qualified # 🧜🏾♀️ E5.0 mermaid: medium-dark skin tone
+1F9DC 1F3FE 200D 2640 ; minimally-qualified # 🧜🏾♀ E5.0 mermaid: medium-dark skin tone
+1F9DC 1F3FF 200D 2640 FE0F ; fully-qualified # 🧜🏿♀️ E5.0 mermaid: dark skin tone
+1F9DC 1F3FF 200D 2640 ; minimally-qualified # 🧜🏿♀ E5.0 mermaid: dark skin tone
+1F9DD ; fully-qualified # 🧝 E5.0 elf
+1F9DD 1F3FB ; fully-qualified # 🧝🏻 E5.0 elf: light skin tone
+1F9DD 1F3FC ; fully-qualified # 🧝🏼 E5.0 elf: medium-light skin tone
+1F9DD 1F3FD ; fully-qualified # 🧝🏽 E5.0 elf: medium skin tone
+1F9DD 1F3FE ; fully-qualified # 🧝🏾 E5.0 elf: medium-dark skin tone
+1F9DD 1F3FF ; fully-qualified # 🧝🏿 E5.0 elf: dark skin tone
+1F9DD 200D 2642 FE0F ; fully-qualified # 🧝♂️ E5.0 man elf
+1F9DD 200D 2642 ; minimally-qualified # 🧝♂ E5.0 man elf
+1F9DD 1F3FB 200D 2642 FE0F ; fully-qualified # 🧝🏻♂️ E5.0 man elf: light skin tone
+1F9DD 1F3FB 200D 2642 ; minimally-qualified # 🧝🏻♂ E5.0 man elf: light skin tone
+1F9DD 1F3FC 200D 2642 FE0F ; fully-qualified # 🧝🏼♂️ E5.0 man elf: medium-light skin tone
+1F9DD 1F3FC 200D 2642 ; minimally-qualified # 🧝🏼♂ E5.0 man elf: medium-light skin tone
+1F9DD 1F3FD 200D 2642 FE0F ; fully-qualified # 🧝🏽♂️ E5.0 man elf: medium skin tone
+1F9DD 1F3FD 200D 2642 ; minimally-qualified # 🧝🏽♂ E5.0 man elf: medium skin tone
+1F9DD 1F3FE 200D 2642 FE0F ; fully-qualified # 🧝🏾♂️ E5.0 man elf: medium-dark skin tone
+1F9DD 1F3FE 200D 2642 ; minimally-qualified # 🧝🏾♂ E5.0 man elf: medium-dark skin tone
+1F9DD 1F3FF 200D 2642 FE0F ; fully-qualified # 🧝🏿♂️ E5.0 man elf: dark skin tone
+1F9DD 1F3FF 200D 2642 ; minimally-qualified # 🧝🏿♂ E5.0 man elf: dark skin tone
+1F9DD 200D 2640 FE0F ; fully-qualified # 🧝♀️ E5.0 woman elf
+1F9DD 200D 2640 ; minimally-qualified # 🧝♀ E5.0 woman elf
+1F9DD 1F3FB 200D 2640 FE0F ; fully-qualified # 🧝🏻♀️ E5.0 woman elf: light skin tone
+1F9DD 1F3FB 200D 2640 ; minimally-qualified # 🧝🏻♀ E5.0 woman elf: light skin tone
+1F9DD 1F3FC 200D 2640 FE0F ; fully-qualified # 🧝🏼♀️ E5.0 woman elf: medium-light skin tone
+1F9DD 1F3FC 200D 2640 ; minimally-qualified # 🧝🏼♀ E5.0 woman elf: medium-light skin tone
+1F9DD 1F3FD 200D 2640 FE0F ; fully-qualified # 🧝🏽♀️ E5.0 woman elf: medium skin tone
+1F9DD 1F3FD 200D 2640 ; minimally-qualified # 🧝🏽♀ E5.0 woman elf: medium skin tone
+1F9DD 1F3FE 200D 2640 FE0F ; fully-qualified # 🧝🏾♀️ E5.0 woman elf: medium-dark skin tone
+1F9DD 1F3FE 200D 2640 ; minimally-qualified # 🧝🏾♀ E5.0 woman elf: medium-dark skin tone
+1F9DD 1F3FF 200D 2640 FE0F ; fully-qualified # 🧝🏿♀️ E5.0 woman elf: dark skin tone
+1F9DD 1F3FF 200D 2640 ; minimally-qualified # 🧝🏿♀ E5.0 woman elf: dark skin tone
+1F9DE ; fully-qualified # 🧞 E5.0 genie
+1F9DE 200D 2642 FE0F ; fully-qualified # 🧞♂️ E5.0 man genie
+1F9DE 200D 2642 ; minimally-qualified # 🧞♂ E5.0 man genie
+1F9DE 200D 2640 FE0F ; fully-qualified # 🧞♀️ E5.0 woman genie
+1F9DE 200D 2640 ; minimally-qualified # 🧞♀ E5.0 woman genie
+1F9DF ; fully-qualified # 🧟 E5.0 zombie
+1F9DF 200D 2642 FE0F ; fully-qualified # 🧟♂️ E5.0 man zombie
+1F9DF 200D 2642 ; minimally-qualified # 🧟♂ E5.0 man zombie
+1F9DF 200D 2640 FE0F ; fully-qualified # 🧟♀️ E5.0 woman zombie
+1F9DF 200D 2640 ; minimally-qualified # 🧟♀ E5.0 woman zombie
+1F9CC ; fully-qualified # 🧌 E14.0 troll
+
+# subgroup: person-activity
+1F486 ; fully-qualified # 💆 E0.6 person getting massage
+1F486 1F3FB ; fully-qualified # 💆🏻 E1.0 person getting massage: light skin tone
+1F486 1F3FC ; fully-qualified # 💆🏼 E1.0 person getting massage: medium-light skin tone
+1F486 1F3FD ; fully-qualified # 💆🏽 E1.0 person getting massage: medium skin tone
+1F486 1F3FE ; fully-qualified # 💆🏾 E1.0 person getting massage: medium-dark skin tone
+1F486 1F3FF ; fully-qualified # 💆🏿 E1.0 person getting massage: dark skin tone
+1F486 200D 2642 FE0F ; fully-qualified # 💆♂️ E4.0 man getting massage
+1F486 200D 2642 ; minimally-qualified # 💆♂ E4.0 man getting massage
+1F486 1F3FB 200D 2642 FE0F ; fully-qualified # 💆🏻♂️ E4.0 man getting massage: light skin tone
+1F486 1F3FB 200D 2642 ; minimally-qualified # 💆🏻♂ E4.0 man getting massage: light skin tone
+1F486 1F3FC 200D 2642 FE0F ; fully-qualified # 💆🏼♂️ E4.0 man getting massage: medium-light skin tone
+1F486 1F3FC 200D 2642 ; minimally-qualified # 💆🏼♂ E4.0 man getting massage: medium-light skin tone
+1F486 1F3FD 200D 2642 FE0F ; fully-qualified # 💆🏽♂️ E4.0 man getting massage: medium skin tone
+1F486 1F3FD 200D 2642 ; minimally-qualified # 💆🏽♂ E4.0 man getting massage: medium skin tone
+1F486 1F3FE 200D 2642 FE0F ; fully-qualified # 💆🏾♂️ E4.0 man getting massage: medium-dark skin tone
+1F486 1F3FE 200D 2642 ; minimally-qualified # 💆🏾♂ E4.0 man getting massage: medium-dark skin tone
+1F486 1F3FF 200D 2642 FE0F ; fully-qualified # 💆🏿♂️ E4.0 man getting massage: dark skin tone
+1F486 1F3FF 200D 2642 ; minimally-qualified # 💆🏿♂ E4.0 man getting massage: dark skin tone
+1F486 200D 2640 FE0F ; fully-qualified # 💆♀️ E4.0 woman getting massage
+1F486 200D 2640 ; minimally-qualified # 💆♀ E4.0 woman getting massage
+1F486 1F3FB 200D 2640 FE0F ; fully-qualified # 💆🏻♀️ E4.0 woman getting massage: light skin tone
+1F486 1F3FB 200D 2640 ; minimally-qualified # 💆🏻♀ E4.0 woman getting massage: light skin tone
+1F486 1F3FC 200D 2640 FE0F ; fully-qualified # 💆🏼♀️ E4.0 woman getting massage: medium-light skin tone
+1F486 1F3FC 200D 2640 ; minimally-qualified # 💆🏼♀ E4.0 woman getting massage: medium-light skin tone
+1F486 1F3FD 200D 2640 FE0F ; fully-qualified # 💆🏽♀️ E4.0 woman getting massage: medium skin tone
+1F486 1F3FD 200D 2640 ; minimally-qualified # 💆🏽♀ E4.0 woman getting massage: medium skin tone
+1F486 1F3FE 200D 2640 FE0F ; fully-qualified # 💆🏾♀️ E4.0 woman getting massage: medium-dark skin tone
+1F486 1F3FE 200D 2640 ; minimally-qualified # 💆🏾♀ E4.0 woman getting massage: medium-dark skin tone
+1F486 1F3FF 200D 2640 FE0F ; fully-qualified # 💆🏿♀️ E4.0 woman getting massage: dark skin tone
+1F486 1F3FF 200D 2640 ; minimally-qualified # 💆🏿♀ E4.0 woman getting massage: dark skin tone
+1F487 ; fully-qualified # 💇 E0.6 person getting haircut
+1F487 1F3FB ; fully-qualified # 💇🏻 E1.0 person getting haircut: light skin tone
+1F487 1F3FC ; fully-qualified # 💇🏼 E1.0 person getting haircut: medium-light skin tone
+1F487 1F3FD ; fully-qualified # 💇🏽 E1.0 person getting haircut: medium skin tone
+1F487 1F3FE ; fully-qualified # 💇🏾 E1.0 person getting haircut: medium-dark skin tone
+1F487 1F3FF ; fully-qualified # 💇🏿 E1.0 person getting haircut: dark skin tone
+1F487 200D 2642 FE0F ; fully-qualified # 💇♂️ E4.0 man getting haircut
+1F487 200D 2642 ; minimally-qualified # 💇♂ E4.0 man getting haircut
+1F487 1F3FB 200D 2642 FE0F ; fully-qualified # 💇🏻♂️ E4.0 man getting haircut: light skin tone
+1F487 1F3FB 200D 2642 ; minimally-qualified # 💇🏻♂ E4.0 man getting haircut: light skin tone
+1F487 1F3FC 200D 2642 FE0F ; fully-qualified # 💇🏼♂️ E4.0 man getting haircut: medium-light skin tone
+1F487 1F3FC 200D 2642 ; minimally-qualified # 💇🏼♂ E4.0 man getting haircut: medium-light skin tone
+1F487 1F3FD 200D 2642 FE0F ; fully-qualified # 💇🏽♂️ E4.0 man getting haircut: medium skin tone
+1F487 1F3FD 200D 2642 ; minimally-qualified # 💇🏽♂ E4.0 man getting haircut: medium skin tone
+1F487 1F3FE 200D 2642 FE0F ; fully-qualified # 💇🏾♂️ E4.0 man getting haircut: medium-dark skin tone
+1F487 1F3FE 200D 2642 ; minimally-qualified # 💇🏾♂ E4.0 man getting haircut: medium-dark skin tone
+1F487 1F3FF 200D 2642 FE0F ; fully-qualified # 💇🏿♂️ E4.0 man getting haircut: dark skin tone
+1F487 1F3FF 200D 2642 ; minimally-qualified # 💇🏿♂ E4.0 man getting haircut: dark skin tone
+1F487 200D 2640 FE0F ; fully-qualified # 💇♀️ E4.0 woman getting haircut
+1F487 200D 2640 ; minimally-qualified # 💇♀ E4.0 woman getting haircut
+1F487 1F3FB 200D 2640 FE0F ; fully-qualified # 💇🏻♀️ E4.0 woman getting haircut: light skin tone
+1F487 1F3FB 200D 2640 ; minimally-qualified # 💇🏻♀ E4.0 woman getting haircut: light skin tone
+1F487 1F3FC 200D 2640 FE0F ; fully-qualified # 💇🏼♀️ E4.0 woman getting haircut: medium-light skin tone
+1F487 1F3FC 200D 2640 ; minimally-qualified # 💇🏼♀ E4.0 woman getting haircut: medium-light skin tone
+1F487 1F3FD 200D 2640 FE0F ; fully-qualified # 💇🏽♀️ E4.0 woman getting haircut: medium skin tone
+1F487 1F3FD 200D 2640 ; minimally-qualified # 💇🏽♀ E4.0 woman getting haircut: medium skin tone
+1F487 1F3FE 200D 2640 FE0F ; fully-qualified # 💇🏾♀️ E4.0 woman getting haircut: medium-dark skin tone
+1F487 1F3FE 200D 2640 ; minimally-qualified # 💇🏾♀ E4.0 woman getting haircut: medium-dark skin tone
+1F487 1F3FF 200D 2640 FE0F ; fully-qualified # 💇🏿♀️ E4.0 woman getting haircut: dark skin tone
+1F487 1F3FF 200D 2640 ; minimally-qualified # 💇🏿♀ E4.0 woman getting haircut: dark skin tone
+1F6B6 ; fully-qualified # 🚶 E0.6 person walking
+1F6B6 1F3FB ; fully-qualified # 🚶🏻 E1.0 person walking: light skin tone
+1F6B6 1F3FC ; fully-qualified # 🚶🏼 E1.0 person walking: medium-light skin tone
+1F6B6 1F3FD ; fully-qualified # 🚶🏽 E1.0 person walking: medium skin tone
+1F6B6 1F3FE ; fully-qualified # 🚶🏾 E1.0 person walking: medium-dark skin tone
+1F6B6 1F3FF ; fully-qualified # 🚶🏿 E1.0 person walking: dark skin tone
+1F6B6 200D 2642 FE0F ; fully-qualified # 🚶♂️ E4.0 man walking
+1F6B6 200D 2642 ; minimally-qualified # 🚶♂ E4.0 man walking
+1F6B6 1F3FB 200D 2642 FE0F ; fully-qualified # 🚶🏻♂️ E4.0 man walking: light skin tone
+1F6B6 1F3FB 200D 2642 ; minimally-qualified # 🚶🏻♂ E4.0 man walking: light skin tone
+1F6B6 1F3FC 200D 2642 FE0F ; fully-qualified # 🚶🏼♂️ E4.0 man walking: medium-light skin tone
+1F6B6 1F3FC 200D 2642 ; minimally-qualified # 🚶🏼♂ E4.0 man walking: medium-light skin tone
+1F6B6 1F3FD 200D 2642 FE0F ; fully-qualified # 🚶🏽♂️ E4.0 man walking: medium skin tone
+1F6B6 1F3FD 200D 2642 ; minimally-qualified # 🚶🏽♂ E4.0 man walking: medium skin tone
+1F6B6 1F3FE 200D 2642 FE0F ; fully-qualified # 🚶🏾♂️ E4.0 man walking: medium-dark skin tone
+1F6B6 1F3FE 200D 2642 ; minimally-qualified # 🚶🏾♂ E4.0 man walking: medium-dark skin tone
+1F6B6 1F3FF 200D 2642 FE0F ; fully-qualified # 🚶🏿♂️ E4.0 man walking: dark skin tone
+1F6B6 1F3FF 200D 2642 ; minimally-qualified # 🚶🏿♂ E4.0 man walking: dark skin tone
+1F6B6 200D 2640 FE0F ; fully-qualified # 🚶♀️ E4.0 woman walking
+1F6B6 200D 2640 ; minimally-qualified # 🚶♀ E4.0 woman walking
+1F6B6 1F3FB 200D 2640 FE0F ; fully-qualified # 🚶🏻♀️ E4.0 woman walking: light skin tone
+1F6B6 1F3FB 200D 2640 ; minimally-qualified # 🚶🏻♀ E4.0 woman walking: light skin tone
+1F6B6 1F3FC 200D 2640 FE0F ; fully-qualified # 🚶🏼♀️ E4.0 woman walking: medium-light skin tone
+1F6B6 1F3FC 200D 2640 ; minimally-qualified # 🚶🏼♀ E4.0 woman walking: medium-light skin tone
+1F6B6 1F3FD 200D 2640 FE0F ; fully-qualified # 🚶🏽♀️ E4.0 woman walking: medium skin tone
+1F6B6 1F3FD 200D 2640 ; minimally-qualified # 🚶🏽♀ E4.0 woman walking: medium skin tone
+1F6B6 1F3FE 200D 2640 FE0F ; fully-qualified # 🚶🏾♀️ E4.0 woman walking: medium-dark skin tone
+1F6B6 1F3FE 200D 2640 ; minimally-qualified # 🚶🏾♀ E4.0 woman walking: medium-dark skin tone
+1F6B6 1F3FF 200D 2640 FE0F ; fully-qualified # 🚶🏿♀️ E4.0 woman walking: dark skin tone
+1F6B6 1F3FF 200D 2640 ; minimally-qualified # 🚶🏿♀ E4.0 woman walking: dark skin tone
+1F6B6 200D 27A1 FE0F ; fully-qualified # 🚶➡️ E15.1 person walking facing right
+1F6B6 200D 27A1 ; minimally-qualified # 🚶➡ E15.1 person walking facing right
+1F6B6 1F3FB 200D 27A1 FE0F ; fully-qualified # 🚶🏻➡️ E15.1 person walking facing right: light skin tone
+1F6B6 1F3FB 200D 27A1 ; minimally-qualified # 🚶🏻➡ E15.1 person walking facing right: light skin tone
+1F6B6 1F3FC 200D 27A1 FE0F ; fully-qualified # 🚶🏼➡️ E15.1 person walking facing right: medium-light skin tone
+1F6B6 1F3FC 200D 27A1 ; minimally-qualified # 🚶🏼➡ E15.1 person walking facing right: medium-light skin tone
+1F6B6 1F3FD 200D 27A1 FE0F ; fully-qualified # 🚶🏽➡️ E15.1 person walking facing right: medium skin tone
+1F6B6 1F3FD 200D 27A1 ; minimally-qualified # 🚶🏽➡ E15.1 person walking facing right: medium skin tone
+1F6B6 1F3FE 200D 27A1 FE0F ; fully-qualified # 🚶🏾➡️ E15.1 person walking facing right: medium-dark skin tone
+1F6B6 1F3FE 200D 27A1 ; minimally-qualified # 🚶🏾➡ E15.1 person walking facing right: medium-dark skin tone
+1F6B6 1F3FF 200D 27A1 FE0F ; fully-qualified # 🚶🏿➡️ E15.1 person walking facing right: dark skin tone
+1F6B6 1F3FF 200D 27A1 ; minimally-qualified # 🚶🏿➡ E15.1 person walking facing right: dark skin tone
+1F6B6 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🚶♀️➡️ E15.1 woman walking facing right
+1F6B6 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🚶♀➡️ E15.1 woman walking facing right
+1F6B6 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🚶♀️➡ E15.1 woman walking facing right
+1F6B6 200D 2640 200D 27A1 ; minimally-qualified # 🚶♀➡ E15.1 woman walking facing right
+1F6B6 1F3FB 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🚶🏻♀️➡️ E15.1 woman walking facing right: light skin tone
+1F6B6 1F3FB 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🚶🏻♀➡️ E15.1 woman walking facing right: light skin tone
+1F6B6 1F3FB 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🚶🏻♀️➡ E15.1 woman walking facing right: light skin tone
+1F6B6 1F3FB 200D 2640 200D 27A1 ; minimally-qualified # 🚶🏻♀➡ E15.1 woman walking facing right: light skin tone
+1F6B6 1F3FC 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🚶🏼♀️➡️ E15.1 woman walking facing right: medium-light skin tone
+1F6B6 1F3FC 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🚶🏼♀➡️ E15.1 woman walking facing right: medium-light skin tone
+1F6B6 1F3FC 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🚶🏼♀️➡ E15.1 woman walking facing right: medium-light skin tone
+1F6B6 1F3FC 200D 2640 200D 27A1 ; minimally-qualified # 🚶🏼♀➡ E15.1 woman walking facing right: medium-light skin tone
+1F6B6 1F3FD 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🚶🏽♀️➡️ E15.1 woman walking facing right: medium skin tone
+1F6B6 1F3FD 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🚶🏽♀➡️ E15.1 woman walking facing right: medium skin tone
+1F6B6 1F3FD 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🚶🏽♀️➡ E15.1 woman walking facing right: medium skin tone
+1F6B6 1F3FD 200D 2640 200D 27A1 ; minimally-qualified # 🚶🏽♀➡ E15.1 woman walking facing right: medium skin tone
+1F6B6 1F3FE 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🚶🏾♀️➡️ E15.1 woman walking facing right: medium-dark skin tone
+1F6B6 1F3FE 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🚶🏾♀➡️ E15.1 woman walking facing right: medium-dark skin tone
+1F6B6 1F3FE 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🚶🏾♀️➡ E15.1 woman walking facing right: medium-dark skin tone
+1F6B6 1F3FE 200D 2640 200D 27A1 ; minimally-qualified # 🚶🏾♀➡ E15.1 woman walking facing right: medium-dark skin tone
+1F6B6 1F3FF 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🚶🏿♀️➡️ E15.1 woman walking facing right: dark skin tone
+1F6B6 1F3FF 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🚶🏿♀➡️ E15.1 woman walking facing right: dark skin tone
+1F6B6 1F3FF 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🚶🏿♀️➡ E15.1 woman walking facing right: dark skin tone
+1F6B6 1F3FF 200D 2640 200D 27A1 ; minimally-qualified # 🚶🏿♀➡ E15.1 woman walking facing right: dark skin tone
+1F6B6 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🚶♂️➡️ E15.1 man walking facing right
+1F6B6 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🚶♂➡️ E15.1 man walking facing right
+1F6B6 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🚶♂️➡ E15.1 man walking facing right
+1F6B6 200D 2642 200D 27A1 ; minimally-qualified # 🚶♂➡ E15.1 man walking facing right
+1F6B6 1F3FB 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🚶🏻♂️➡️ E15.1 man walking facing right: light skin tone
+1F6B6 1F3FB 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🚶🏻♂➡️ E15.1 man walking facing right: light skin tone
+1F6B6 1F3FB 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🚶🏻♂️➡ E15.1 man walking facing right: light skin tone
+1F6B6 1F3FB 200D 2642 200D 27A1 ; minimally-qualified # 🚶🏻♂➡ E15.1 man walking facing right: light skin tone
+1F6B6 1F3FC 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🚶🏼♂️➡️ E15.1 man walking facing right: medium-light skin tone
+1F6B6 1F3FC 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🚶🏼♂➡️ E15.1 man walking facing right: medium-light skin tone
+1F6B6 1F3FC 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🚶🏼♂️➡ E15.1 man walking facing right: medium-light skin tone
+1F6B6 1F3FC 200D 2642 200D 27A1 ; minimally-qualified # 🚶🏼♂➡ E15.1 man walking facing right: medium-light skin tone
+1F6B6 1F3FD 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🚶🏽♂️➡️ E15.1 man walking facing right: medium skin tone
+1F6B6 1F3FD 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🚶🏽♂➡️ E15.1 man walking facing right: medium skin tone
+1F6B6 1F3FD 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🚶🏽♂️➡ E15.1 man walking facing right: medium skin tone
+1F6B6 1F3FD 200D 2642 200D 27A1 ; minimally-qualified # 🚶🏽♂➡ E15.1 man walking facing right: medium skin tone
+1F6B6 1F3FE 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🚶🏾♂️➡️ E15.1 man walking facing right: medium-dark skin tone
+1F6B6 1F3FE 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🚶🏾♂➡️ E15.1 man walking facing right: medium-dark skin tone
+1F6B6 1F3FE 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🚶🏾♂️➡ E15.1 man walking facing right: medium-dark skin tone
+1F6B6 1F3FE 200D 2642 200D 27A1 ; minimally-qualified # 🚶🏾♂➡ E15.1 man walking facing right: medium-dark skin tone
+1F6B6 1F3FF 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🚶🏿♂️➡️ E15.1 man walking facing right: dark skin tone
+1F6B6 1F3FF 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🚶🏿♂➡️ E15.1 man walking facing right: dark skin tone
+1F6B6 1F3FF 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🚶🏿♂️➡ E15.1 man walking facing right: dark skin tone
+1F6B6 1F3FF 200D 2642 200D 27A1 ; minimally-qualified # 🚶🏿♂➡ E15.1 man walking facing right: dark skin tone
+1F9CD ; fully-qualified # 🧍 E12.0 person standing
+1F9CD 1F3FB ; fully-qualified # 🧍🏻 E12.0 person standing: light skin tone
+1F9CD 1F3FC ; fully-qualified # 🧍🏼 E12.0 person standing: medium-light skin tone
+1F9CD 1F3FD ; fully-qualified # 🧍🏽 E12.0 person standing: medium skin tone
+1F9CD 1F3FE ; fully-qualified # 🧍🏾 E12.0 person standing: medium-dark skin tone
+1F9CD 1F3FF ; fully-qualified # 🧍🏿 E12.0 person standing: dark skin tone
+1F9CD 200D 2642 FE0F ; fully-qualified # 🧍♂️ E12.0 man standing
+1F9CD 200D 2642 ; minimally-qualified # 🧍♂ E12.0 man standing
+1F9CD 1F3FB 200D 2642 FE0F ; fully-qualified # 🧍🏻♂️ E12.0 man standing: light skin tone
+1F9CD 1F3FB 200D 2642 ; minimally-qualified # 🧍🏻♂ E12.0 man standing: light skin tone
+1F9CD 1F3FC 200D 2642 FE0F ; fully-qualified # 🧍🏼♂️ E12.0 man standing: medium-light skin tone
+1F9CD 1F3FC 200D 2642 ; minimally-qualified # 🧍🏼♂ E12.0 man standing: medium-light skin tone
+1F9CD 1F3FD 200D 2642 FE0F ; fully-qualified # 🧍🏽♂️ E12.0 man standing: medium skin tone
+1F9CD 1F3FD 200D 2642 ; minimally-qualified # 🧍🏽♂ E12.0 man standing: medium skin tone
+1F9CD 1F3FE 200D 2642 FE0F ; fully-qualified # 🧍🏾♂️ E12.0 man standing: medium-dark skin tone
+1F9CD 1F3FE 200D 2642 ; minimally-qualified # 🧍🏾♂ E12.0 man standing: medium-dark skin tone
+1F9CD 1F3FF 200D 2642 FE0F ; fully-qualified # 🧍🏿♂️ E12.0 man standing: dark skin tone
+1F9CD 1F3FF 200D 2642 ; minimally-qualified # 🧍🏿♂ E12.0 man standing: dark skin tone
+1F9CD 200D 2640 FE0F ; fully-qualified # 🧍♀️ E12.0 woman standing
+1F9CD 200D 2640 ; minimally-qualified # 🧍♀ E12.0 woman standing
+1F9CD 1F3FB 200D 2640 FE0F ; fully-qualified # 🧍🏻♀️ E12.0 woman standing: light skin tone
+1F9CD 1F3FB 200D 2640 ; minimally-qualified # 🧍🏻♀ E12.0 woman standing: light skin tone
+1F9CD 1F3FC 200D 2640 FE0F ; fully-qualified # 🧍🏼♀️ E12.0 woman standing: medium-light skin tone
+1F9CD 1F3FC 200D 2640 ; minimally-qualified # 🧍🏼♀ E12.0 woman standing: medium-light skin tone
+1F9CD 1F3FD 200D 2640 FE0F ; fully-qualified # 🧍🏽♀️ E12.0 woman standing: medium skin tone
+1F9CD 1F3FD 200D 2640 ; minimally-qualified # 🧍🏽♀ E12.0 woman standing: medium skin tone
+1F9CD 1F3FE 200D 2640 FE0F ; fully-qualified # 🧍🏾♀️ E12.0 woman standing: medium-dark skin tone
+1F9CD 1F3FE 200D 2640 ; minimally-qualified # 🧍🏾♀ E12.0 woman standing: medium-dark skin tone
+1F9CD 1F3FF 200D 2640 FE0F ; fully-qualified # 🧍🏿♀️ E12.0 woman standing: dark skin tone
+1F9CD 1F3FF 200D 2640 ; minimally-qualified # 🧍🏿♀ E12.0 woman standing: dark skin tone
+1F9CE ; fully-qualified # 🧎 E12.0 person kneeling
+1F9CE 1F3FB ; fully-qualified # 🧎🏻 E12.0 person kneeling: light skin tone
+1F9CE 1F3FC ; fully-qualified # 🧎🏼 E12.0 person kneeling: medium-light skin tone
+1F9CE 1F3FD ; fully-qualified # 🧎🏽 E12.0 person kneeling: medium skin tone
+1F9CE 1F3FE ; fully-qualified # 🧎🏾 E12.0 person kneeling: medium-dark skin tone
+1F9CE 1F3FF ; fully-qualified # 🧎🏿 E12.0 person kneeling: dark skin tone
+1F9CE 200D 2642 FE0F ; fully-qualified # 🧎♂️ E12.0 man kneeling
+1F9CE 200D 2642 ; minimally-qualified # 🧎♂ E12.0 man kneeling
+1F9CE 1F3FB 200D 2642 FE0F ; fully-qualified # 🧎🏻♂️ E12.0 man kneeling: light skin tone
+1F9CE 1F3FB 200D 2642 ; minimally-qualified # 🧎🏻♂ E12.0 man kneeling: light skin tone
+1F9CE 1F3FC 200D 2642 FE0F ; fully-qualified # 🧎🏼♂️ E12.0 man kneeling: medium-light skin tone
+1F9CE 1F3FC 200D 2642 ; minimally-qualified # 🧎🏼♂ E12.0 man kneeling: medium-light skin tone
+1F9CE 1F3FD 200D 2642 FE0F ; fully-qualified # 🧎🏽♂️ E12.0 man kneeling: medium skin tone
+1F9CE 1F3FD 200D 2642 ; minimally-qualified # 🧎🏽♂ E12.0 man kneeling: medium skin tone
+1F9CE 1F3FE 200D 2642 FE0F ; fully-qualified # 🧎🏾♂️ E12.0 man kneeling: medium-dark skin tone
+1F9CE 1F3FE 200D 2642 ; minimally-qualified # 🧎🏾♂ E12.0 man kneeling: medium-dark skin tone
+1F9CE 1F3FF 200D 2642 FE0F ; fully-qualified # 🧎🏿♂️ E12.0 man kneeling: dark skin tone
+1F9CE 1F3FF 200D 2642 ; minimally-qualified # 🧎🏿♂ E12.0 man kneeling: dark skin tone
+1F9CE 200D 2640 FE0F ; fully-qualified # 🧎♀️ E12.0 woman kneeling
+1F9CE 200D 2640 ; minimally-qualified # 🧎♀ E12.0 woman kneeling
+1F9CE 1F3FB 200D 2640 FE0F ; fully-qualified # 🧎🏻♀️ E12.0 woman kneeling: light skin tone
+1F9CE 1F3FB 200D 2640 ; minimally-qualified # 🧎🏻♀ E12.0 woman kneeling: light skin tone
+1F9CE 1F3FC 200D 2640 FE0F ; fully-qualified # 🧎🏼♀️ E12.0 woman kneeling: medium-light skin tone
+1F9CE 1F3FC 200D 2640 ; minimally-qualified # 🧎🏼♀ E12.0 woman kneeling: medium-light skin tone
+1F9CE 1F3FD 200D 2640 FE0F ; fully-qualified # 🧎🏽♀️ E12.0 woman kneeling: medium skin tone
+1F9CE 1F3FD 200D 2640 ; minimally-qualified # 🧎🏽♀ E12.0 woman kneeling: medium skin tone
+1F9CE 1F3FE 200D 2640 FE0F ; fully-qualified # 🧎🏾♀️ E12.0 woman kneeling: medium-dark skin tone
+1F9CE 1F3FE 200D 2640 ; minimally-qualified # 🧎🏾♀ E12.0 woman kneeling: medium-dark skin tone
+1F9CE 1F3FF 200D 2640 FE0F ; fully-qualified # 🧎🏿♀️ E12.0 woman kneeling: dark skin tone
+1F9CE 1F3FF 200D 2640 ; minimally-qualified # 🧎🏿♀ E12.0 woman kneeling: dark skin tone
+1F9CE 200D 27A1 FE0F ; fully-qualified # 🧎➡️ E15.1 person kneeling facing right
+1F9CE 200D 27A1 ; minimally-qualified # 🧎➡ E15.1 person kneeling facing right
+1F9CE 1F3FB 200D 27A1 FE0F ; fully-qualified # 🧎🏻➡️ E15.1 person kneeling facing right: light skin tone
+1F9CE 1F3FB 200D 27A1 ; minimally-qualified # 🧎🏻➡ E15.1 person kneeling facing right: light skin tone
+1F9CE 1F3FC 200D 27A1 FE0F ; fully-qualified # 🧎🏼➡️ E15.1 person kneeling facing right: medium-light skin tone
+1F9CE 1F3FC 200D 27A1 ; minimally-qualified # 🧎🏼➡ E15.1 person kneeling facing right: medium-light skin tone
+1F9CE 1F3FD 200D 27A1 FE0F ; fully-qualified # 🧎🏽➡️ E15.1 person kneeling facing right: medium skin tone
+1F9CE 1F3FD 200D 27A1 ; minimally-qualified # 🧎🏽➡ E15.1 person kneeling facing right: medium skin tone
+1F9CE 1F3FE 200D 27A1 FE0F ; fully-qualified # 🧎🏾➡️ E15.1 person kneeling facing right: medium-dark skin tone
+1F9CE 1F3FE 200D 27A1 ; minimally-qualified # 🧎🏾➡ E15.1 person kneeling facing right: medium-dark skin tone
+1F9CE 1F3FF 200D 27A1 FE0F ; fully-qualified # 🧎🏿➡️ E15.1 person kneeling facing right: dark skin tone
+1F9CE 1F3FF 200D 27A1 ; minimally-qualified # 🧎🏿➡ E15.1 person kneeling facing right: dark skin tone
+1F9CE 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🧎♀️➡️ E15.1 woman kneeling facing right
+1F9CE 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🧎♀➡️ E15.1 woman kneeling facing right
+1F9CE 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🧎♀️➡ E15.1 woman kneeling facing right
+1F9CE 200D 2640 200D 27A1 ; minimally-qualified # 🧎♀➡ E15.1 woman kneeling facing right
+1F9CE 1F3FB 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🧎🏻♀️➡️ E15.1 woman kneeling facing right: light skin tone
+1F9CE 1F3FB 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🧎🏻♀➡️ E15.1 woman kneeling facing right: light skin tone
+1F9CE 1F3FB 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🧎🏻♀️➡ E15.1 woman kneeling facing right: light skin tone
+1F9CE 1F3FB 200D 2640 200D 27A1 ; minimally-qualified # 🧎🏻♀➡ E15.1 woman kneeling facing right: light skin tone
+1F9CE 1F3FC 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🧎🏼♀️➡️ E15.1 woman kneeling facing right: medium-light skin tone
+1F9CE 1F3FC 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🧎🏼♀➡️ E15.1 woman kneeling facing right: medium-light skin tone
+1F9CE 1F3FC 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🧎🏼♀️➡ E15.1 woman kneeling facing right: medium-light skin tone
+1F9CE 1F3FC 200D 2640 200D 27A1 ; minimally-qualified # 🧎🏼♀➡ E15.1 woman kneeling facing right: medium-light skin tone
+1F9CE 1F3FD 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🧎🏽♀️➡️ E15.1 woman kneeling facing right: medium skin tone
+1F9CE 1F3FD 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🧎🏽♀➡️ E15.1 woman kneeling facing right: medium skin tone
+1F9CE 1F3FD 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🧎🏽♀️➡ E15.1 woman kneeling facing right: medium skin tone
+1F9CE 1F3FD 200D 2640 200D 27A1 ; minimally-qualified # 🧎🏽♀➡ E15.1 woman kneeling facing right: medium skin tone
+1F9CE 1F3FE 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🧎🏾♀️➡️ E15.1 woman kneeling facing right: medium-dark skin tone
+1F9CE 1F3FE 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🧎🏾♀➡️ E15.1 woman kneeling facing right: medium-dark skin tone
+1F9CE 1F3FE 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🧎🏾♀️➡ E15.1 woman kneeling facing right: medium-dark skin tone
+1F9CE 1F3FE 200D 2640 200D 27A1 ; minimally-qualified # 🧎🏾♀➡ E15.1 woman kneeling facing right: medium-dark skin tone
+1F9CE 1F3FF 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🧎🏿♀️➡️ E15.1 woman kneeling facing right: dark skin tone
+1F9CE 1F3FF 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🧎🏿♀➡️ E15.1 woman kneeling facing right: dark skin tone
+1F9CE 1F3FF 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🧎🏿♀️➡ E15.1 woman kneeling facing right: dark skin tone
+1F9CE 1F3FF 200D 2640 200D 27A1 ; minimally-qualified # 🧎🏿♀➡ E15.1 woman kneeling facing right: dark skin tone
+1F9CE 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🧎♂️➡️ E15.1 man kneeling facing right
+1F9CE 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🧎♂➡️ E15.1 man kneeling facing right
+1F9CE 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🧎♂️➡ E15.1 man kneeling facing right
+1F9CE 200D 2642 200D 27A1 ; minimally-qualified # 🧎♂➡ E15.1 man kneeling facing right
+1F9CE 1F3FB 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🧎🏻♂️➡️ E15.1 man kneeling facing right: light skin tone
+1F9CE 1F3FB 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🧎🏻♂➡️ E15.1 man kneeling facing right: light skin tone
+1F9CE 1F3FB 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🧎🏻♂️➡ E15.1 man kneeling facing right: light skin tone
+1F9CE 1F3FB 200D 2642 200D 27A1 ; minimally-qualified # 🧎🏻♂➡ E15.1 man kneeling facing right: light skin tone
+1F9CE 1F3FC 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🧎🏼♂️➡️ E15.1 man kneeling facing right: medium-light skin tone
+1F9CE 1F3FC 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🧎🏼♂➡️ E15.1 man kneeling facing right: medium-light skin tone
+1F9CE 1F3FC 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🧎🏼♂️➡ E15.1 man kneeling facing right: medium-light skin tone
+1F9CE 1F3FC 200D 2642 200D 27A1 ; minimally-qualified # 🧎🏼♂➡ E15.1 man kneeling facing right: medium-light skin tone
+1F9CE 1F3FD 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🧎🏽♂️➡️ E15.1 man kneeling facing right: medium skin tone
+1F9CE 1F3FD 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🧎🏽♂➡️ E15.1 man kneeling facing right: medium skin tone
+1F9CE 1F3FD 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🧎🏽♂️➡ E15.1 man kneeling facing right: medium skin tone
+1F9CE 1F3FD 200D 2642 200D 27A1 ; minimally-qualified # 🧎🏽♂➡ E15.1 man kneeling facing right: medium skin tone
+1F9CE 1F3FE 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🧎🏾♂️➡️ E15.1 man kneeling facing right: medium-dark skin tone
+1F9CE 1F3FE 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🧎🏾♂➡️ E15.1 man kneeling facing right: medium-dark skin tone
+1F9CE 1F3FE 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🧎🏾♂️➡ E15.1 man kneeling facing right: medium-dark skin tone
+1F9CE 1F3FE 200D 2642 200D 27A1 ; minimally-qualified # 🧎🏾♂➡ E15.1 man kneeling facing right: medium-dark skin tone
+1F9CE 1F3FF 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🧎🏿♂️➡️ E15.1 man kneeling facing right: dark skin tone
+1F9CE 1F3FF 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🧎🏿♂➡️ E15.1 man kneeling facing right: dark skin tone
+1F9CE 1F3FF 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🧎🏿♂️➡ E15.1 man kneeling facing right: dark skin tone
+1F9CE 1F3FF 200D 2642 200D 27A1 ; minimally-qualified # 🧎🏿♂➡ E15.1 man kneeling facing right: dark skin tone
+1F9D1 200D 1F9AF ; fully-qualified # 🧑🦯 E12.1 person with white cane
+1F9D1 1F3FB 200D 1F9AF ; fully-qualified # 🧑🏻🦯 E12.1 person with white cane: light skin tone
+1F9D1 1F3FC 200D 1F9AF ; fully-qualified # 🧑🏼🦯 E12.1 person with white cane: medium-light skin tone
+1F9D1 1F3FD 200D 1F9AF ; fully-qualified # 🧑🏽🦯 E12.1 person with white cane: medium skin tone
+1F9D1 1F3FE 200D 1F9AF ; fully-qualified # 🧑🏾🦯 E12.1 person with white cane: medium-dark skin tone
+1F9D1 1F3FF 200D 1F9AF ; fully-qualified # 🧑🏿🦯 E12.1 person with white cane: dark skin tone
+1F9D1 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 🧑🦯➡️ E15.1 person with white cane facing right
+1F9D1 200D 1F9AF 200D 27A1 ; minimally-qualified # 🧑🦯➡ E15.1 person with white cane facing right
+1F9D1 1F3FB 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 🧑🏻🦯➡️ E15.1 person with white cane facing right: light skin tone
+1F9D1 1F3FB 200D 1F9AF 200D 27A1 ; minimally-qualified # 🧑🏻🦯➡ E15.1 person with white cane facing right: light skin tone
+1F9D1 1F3FC 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 🧑🏼🦯➡️ E15.1 person with white cane facing right: medium-light skin tone
+1F9D1 1F3FC 200D 1F9AF 200D 27A1 ; minimally-qualified # 🧑🏼🦯➡ E15.1 person with white cane facing right: medium-light skin tone
+1F9D1 1F3FD 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 🧑🏽🦯➡️ E15.1 person with white cane facing right: medium skin tone
+1F9D1 1F3FD 200D 1F9AF 200D 27A1 ; minimally-qualified # 🧑🏽🦯➡ E15.1 person with white cane facing right: medium skin tone
+1F9D1 1F3FE 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 🧑🏾🦯➡️ E15.1 person with white cane facing right: medium-dark skin tone
+1F9D1 1F3FE 200D 1F9AF 200D 27A1 ; minimally-qualified # 🧑🏾🦯➡ E15.1 person with white cane facing right: medium-dark skin tone
+1F9D1 1F3FF 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 🧑🏿🦯➡️ E15.1 person with white cane facing right: dark skin tone
+1F9D1 1F3FF 200D 1F9AF 200D 27A1 ; minimally-qualified # 🧑🏿🦯➡ E15.1 person with white cane facing right: dark skin tone
+1F468 200D 1F9AF ; fully-qualified # 👨🦯 E12.0 man with white cane
+1F468 1F3FB 200D 1F9AF ; fully-qualified # 👨🏻🦯 E12.0 man with white cane: light skin tone
+1F468 1F3FC 200D 1F9AF ; fully-qualified # 👨🏼🦯 E12.0 man with white cane: medium-light skin tone
+1F468 1F3FD 200D 1F9AF ; fully-qualified # 👨🏽🦯 E12.0 man with white cane: medium skin tone
+1F468 1F3FE 200D 1F9AF ; fully-qualified # 👨🏾🦯 E12.0 man with white cane: medium-dark skin tone
+1F468 1F3FF 200D 1F9AF ; fully-qualified # 👨🏿🦯 E12.0 man with white cane: dark skin tone
+1F468 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 👨🦯➡️ E15.1 man with white cane facing right
+1F468 200D 1F9AF 200D 27A1 ; minimally-qualified # 👨🦯➡ E15.1 man with white cane facing right
+1F468 1F3FB 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 👨🏻🦯➡️ E15.1 man with white cane facing right: light skin tone
+1F468 1F3FB 200D 1F9AF 200D 27A1 ; minimally-qualified # 👨🏻🦯➡ E15.1 man with white cane facing right: light skin tone
+1F468 1F3FC 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 👨🏼🦯➡️ E15.1 man with white cane facing right: medium-light skin tone
+1F468 1F3FC 200D 1F9AF 200D 27A1 ; minimally-qualified # 👨🏼🦯➡ E15.1 man with white cane facing right: medium-light skin tone
+1F468 1F3FD 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 👨🏽🦯➡️ E15.1 man with white cane facing right: medium skin tone
+1F468 1F3FD 200D 1F9AF 200D 27A1 ; minimally-qualified # 👨🏽🦯➡ E15.1 man with white cane facing right: medium skin tone
+1F468 1F3FE 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 👨🏾🦯➡️ E15.1 man with white cane facing right: medium-dark skin tone
+1F468 1F3FE 200D 1F9AF 200D 27A1 ; minimally-qualified # 👨🏾🦯➡ E15.1 man with white cane facing right: medium-dark skin tone
+1F468 1F3FF 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 👨🏿🦯➡️ E15.1 man with white cane facing right: dark skin tone
+1F468 1F3FF 200D 1F9AF 200D 27A1 ; minimally-qualified # 👨🏿🦯➡ E15.1 man with white cane facing right: dark skin tone
+1F469 200D 1F9AF ; fully-qualified # 👩🦯 E12.0 woman with white cane
+1F469 1F3FB 200D 1F9AF ; fully-qualified # 👩🏻🦯 E12.0 woman with white cane: light skin tone
+1F469 1F3FC 200D 1F9AF ; fully-qualified # 👩🏼🦯 E12.0 woman with white cane: medium-light skin tone
+1F469 1F3FD 200D 1F9AF ; fully-qualified # 👩🏽🦯 E12.0 woman with white cane: medium skin tone
+1F469 1F3FE 200D 1F9AF ; fully-qualified # 👩🏾🦯 E12.0 woman with white cane: medium-dark skin tone
+1F469 1F3FF 200D 1F9AF ; fully-qualified # 👩🏿🦯 E12.0 woman with white cane: dark skin tone
+1F469 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 👩🦯➡️ E15.1 woman with white cane facing right
+1F469 200D 1F9AF 200D 27A1 ; minimally-qualified # 👩🦯➡ E15.1 woman with white cane facing right
+1F469 1F3FB 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 👩🏻🦯➡️ E15.1 woman with white cane facing right: light skin tone
+1F469 1F3FB 200D 1F9AF 200D 27A1 ; minimally-qualified # 👩🏻🦯➡ E15.1 woman with white cane facing right: light skin tone
+1F469 1F3FC 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 👩🏼🦯➡️ E15.1 woman with white cane facing right: medium-light skin tone
+1F469 1F3FC 200D 1F9AF 200D 27A1 ; minimally-qualified # 👩🏼🦯➡ E15.1 woman with white cane facing right: medium-light skin tone
+1F469 1F3FD 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 👩🏽🦯➡️ E15.1 woman with white cane facing right: medium skin tone
+1F469 1F3FD 200D 1F9AF 200D 27A1 ; minimally-qualified # 👩🏽🦯➡ E15.1 woman with white cane facing right: medium skin tone
+1F469 1F3FE 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 👩🏾🦯➡️ E15.1 woman with white cane facing right: medium-dark skin tone
+1F469 1F3FE 200D 1F9AF 200D 27A1 ; minimally-qualified # 👩🏾🦯➡ E15.1 woman with white cane facing right: medium-dark skin tone
+1F469 1F3FF 200D 1F9AF 200D 27A1 FE0F ; fully-qualified # 👩🏿🦯➡️ E15.1 woman with white cane facing right: dark skin tone
+1F469 1F3FF 200D 1F9AF 200D 27A1 ; minimally-qualified # 👩🏿🦯➡ E15.1 woman with white cane facing right: dark skin tone
+1F9D1 200D 1F9BC ; fully-qualified # 🧑🦼 E12.1 person in motorized wheelchair
+1F9D1 1F3FB 200D 1F9BC ; fully-qualified # 🧑🏻🦼 E12.1 person in motorized wheelchair: light skin tone
+1F9D1 1F3FC 200D 1F9BC ; fully-qualified # 🧑🏼🦼 E12.1 person in motorized wheelchair: medium-light skin tone
+1F9D1 1F3FD 200D 1F9BC ; fully-qualified # 🧑🏽🦼 E12.1 person in motorized wheelchair: medium skin tone
+1F9D1 1F3FE 200D 1F9BC ; fully-qualified # 🧑🏾🦼 E12.1 person in motorized wheelchair: medium-dark skin tone
+1F9D1 1F3FF 200D 1F9BC ; fully-qualified # 🧑🏿🦼 E12.1 person in motorized wheelchair: dark skin tone
+1F9D1 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 🧑🦼➡️ E15.1 person in motorized wheelchair facing right
+1F9D1 200D 1F9BC 200D 27A1 ; minimally-qualified # 🧑🦼➡ E15.1 person in motorized wheelchair facing right
+1F9D1 1F3FB 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 🧑🏻🦼➡️ E15.1 person in motorized wheelchair facing right: light skin tone
+1F9D1 1F3FB 200D 1F9BC 200D 27A1 ; minimally-qualified # 🧑🏻🦼➡ E15.1 person in motorized wheelchair facing right: light skin tone
+1F9D1 1F3FC 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 🧑🏼🦼➡️ E15.1 person in motorized wheelchair facing right: medium-light skin tone
+1F9D1 1F3FC 200D 1F9BC 200D 27A1 ; minimally-qualified # 🧑🏼🦼➡ E15.1 person in motorized wheelchair facing right: medium-light skin tone
+1F9D1 1F3FD 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 🧑🏽🦼➡️ E15.1 person in motorized wheelchair facing right: medium skin tone
+1F9D1 1F3FD 200D 1F9BC 200D 27A1 ; minimally-qualified # 🧑🏽🦼➡ E15.1 person in motorized wheelchair facing right: medium skin tone
+1F9D1 1F3FE 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 🧑🏾🦼➡️ E15.1 person in motorized wheelchair facing right: medium-dark skin tone
+1F9D1 1F3FE 200D 1F9BC 200D 27A1 ; minimally-qualified # 🧑🏾🦼➡ E15.1 person in motorized wheelchair facing right: medium-dark skin tone
+1F9D1 1F3FF 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 🧑🏿🦼➡️ E15.1 person in motorized wheelchair facing right: dark skin tone
+1F9D1 1F3FF 200D 1F9BC 200D 27A1 ; minimally-qualified # 🧑🏿🦼➡ E15.1 person in motorized wheelchair facing right: dark skin tone
+1F468 200D 1F9BC ; fully-qualified # 👨🦼 E12.0 man in motorized wheelchair
+1F468 1F3FB 200D 1F9BC ; fully-qualified # 👨🏻🦼 E12.0 man in motorized wheelchair: light skin tone
+1F468 1F3FC 200D 1F9BC ; fully-qualified # 👨🏼🦼 E12.0 man in motorized wheelchair: medium-light skin tone
+1F468 1F3FD 200D 1F9BC ; fully-qualified # 👨🏽🦼 E12.0 man in motorized wheelchair: medium skin tone
+1F468 1F3FE 200D 1F9BC ; fully-qualified # 👨🏾🦼 E12.0 man in motorized wheelchair: medium-dark skin tone
+1F468 1F3FF 200D 1F9BC ; fully-qualified # 👨🏿🦼 E12.0 man in motorized wheelchair: dark skin tone
+1F468 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 👨🦼➡️ E15.1 man in motorized wheelchair facing right
+1F468 200D 1F9BC 200D 27A1 ; minimally-qualified # 👨🦼➡ E15.1 man in motorized wheelchair facing right
+1F468 1F3FB 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 👨🏻🦼➡️ E15.1 man in motorized wheelchair facing right: light skin tone
+1F468 1F3FB 200D 1F9BC 200D 27A1 ; minimally-qualified # 👨🏻🦼➡ E15.1 man in motorized wheelchair facing right: light skin tone
+1F468 1F3FC 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 👨🏼🦼➡️ E15.1 man in motorized wheelchair facing right: medium-light skin tone
+1F468 1F3FC 200D 1F9BC 200D 27A1 ; minimally-qualified # 👨🏼🦼➡ E15.1 man in motorized wheelchair facing right: medium-light skin tone
+1F468 1F3FD 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 👨🏽🦼➡️ E15.1 man in motorized wheelchair facing right: medium skin tone
+1F468 1F3FD 200D 1F9BC 200D 27A1 ; minimally-qualified # 👨🏽🦼➡ E15.1 man in motorized wheelchair facing right: medium skin tone
+1F468 1F3FE 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 👨🏾🦼➡️ E15.1 man in motorized wheelchair facing right: medium-dark skin tone
+1F468 1F3FE 200D 1F9BC 200D 27A1 ; minimally-qualified # 👨🏾🦼➡ E15.1 man in motorized wheelchair facing right: medium-dark skin tone
+1F468 1F3FF 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 👨🏿🦼➡️ E15.1 man in motorized wheelchair facing right: dark skin tone
+1F468 1F3FF 200D 1F9BC 200D 27A1 ; minimally-qualified # 👨🏿🦼➡ E15.1 man in motorized wheelchair facing right: dark skin tone
+1F469 200D 1F9BC ; fully-qualified # 👩🦼 E12.0 woman in motorized wheelchair
+1F469 1F3FB 200D 1F9BC ; fully-qualified # 👩🏻🦼 E12.0 woman in motorized wheelchair: light skin tone
+1F469 1F3FC 200D 1F9BC ; fully-qualified # 👩🏼🦼 E12.0 woman in motorized wheelchair: medium-light skin tone
+1F469 1F3FD 200D 1F9BC ; fully-qualified # 👩🏽🦼 E12.0 woman in motorized wheelchair: medium skin tone
+1F469 1F3FE 200D 1F9BC ; fully-qualified # 👩🏾🦼 E12.0 woman in motorized wheelchair: medium-dark skin tone
+1F469 1F3FF 200D 1F9BC ; fully-qualified # 👩🏿🦼 E12.0 woman in motorized wheelchair: dark skin tone
+1F469 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 👩🦼➡️ E15.1 woman in motorized wheelchair facing right
+1F469 200D 1F9BC 200D 27A1 ; minimally-qualified # 👩🦼➡ E15.1 woman in motorized wheelchair facing right
+1F469 1F3FB 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 👩🏻🦼➡️ E15.1 woman in motorized wheelchair facing right: light skin tone
+1F469 1F3FB 200D 1F9BC 200D 27A1 ; minimally-qualified # 👩🏻🦼➡ E15.1 woman in motorized wheelchair facing right: light skin tone
+1F469 1F3FC 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 👩🏼🦼➡️ E15.1 woman in motorized wheelchair facing right: medium-light skin tone
+1F469 1F3FC 200D 1F9BC 200D 27A1 ; minimally-qualified # 👩🏼🦼➡ E15.1 woman in motorized wheelchair facing right: medium-light skin tone
+1F469 1F3FD 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 👩🏽🦼➡️ E15.1 woman in motorized wheelchair facing right: medium skin tone
+1F469 1F3FD 200D 1F9BC 200D 27A1 ; minimally-qualified # 👩🏽🦼➡ E15.1 woman in motorized wheelchair facing right: medium skin tone
+1F469 1F3FE 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 👩🏾🦼➡️ E15.1 woman in motorized wheelchair facing right: medium-dark skin tone
+1F469 1F3FE 200D 1F9BC 200D 27A1 ; minimally-qualified # 👩🏾🦼➡ E15.1 woman in motorized wheelchair facing right: medium-dark skin tone
+1F469 1F3FF 200D 1F9BC 200D 27A1 FE0F ; fully-qualified # 👩🏿🦼➡️ E15.1 woman in motorized wheelchair facing right: dark skin tone
+1F469 1F3FF 200D 1F9BC 200D 27A1 ; minimally-qualified # 👩🏿🦼➡ E15.1 woman in motorized wheelchair facing right: dark skin tone
+1F9D1 200D 1F9BD ; fully-qualified # 🧑🦽 E12.1 person in manual wheelchair
+1F9D1 1F3FB 200D 1F9BD ; fully-qualified # 🧑🏻🦽 E12.1 person in manual wheelchair: light skin tone
+1F9D1 1F3FC 200D 1F9BD ; fully-qualified # 🧑🏼🦽 E12.1 person in manual wheelchair: medium-light skin tone
+1F9D1 1F3FD 200D 1F9BD ; fully-qualified # 🧑🏽🦽 E12.1 person in manual wheelchair: medium skin tone
+1F9D1 1F3FE 200D 1F9BD ; fully-qualified # 🧑🏾🦽 E12.1 person in manual wheelchair: medium-dark skin tone
+1F9D1 1F3FF 200D 1F9BD ; fully-qualified # 🧑🏿🦽 E12.1 person in manual wheelchair: dark skin tone
+1F9D1 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 🧑🦽➡️ E15.1 person in manual wheelchair facing right
+1F9D1 200D 1F9BD 200D 27A1 ; minimally-qualified # 🧑🦽➡ E15.1 person in manual wheelchair facing right
+1F9D1 1F3FB 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 🧑🏻🦽➡️ E15.1 person in manual wheelchair facing right: light skin tone
+1F9D1 1F3FB 200D 1F9BD 200D 27A1 ; minimally-qualified # 🧑🏻🦽➡ E15.1 person in manual wheelchair facing right: light skin tone
+1F9D1 1F3FC 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 🧑🏼🦽➡️ E15.1 person in manual wheelchair facing right: medium-light skin tone
+1F9D1 1F3FC 200D 1F9BD 200D 27A1 ; minimally-qualified # 🧑🏼🦽➡ E15.1 person in manual wheelchair facing right: medium-light skin tone
+1F9D1 1F3FD 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 🧑🏽🦽➡️ E15.1 person in manual wheelchair facing right: medium skin tone
+1F9D1 1F3FD 200D 1F9BD 200D 27A1 ; minimally-qualified # 🧑🏽🦽➡ E15.1 person in manual wheelchair facing right: medium skin tone
+1F9D1 1F3FE 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 🧑🏾🦽➡️ E15.1 person in manual wheelchair facing right: medium-dark skin tone
+1F9D1 1F3FE 200D 1F9BD 200D 27A1 ; minimally-qualified # 🧑🏾🦽➡ E15.1 person in manual wheelchair facing right: medium-dark skin tone
+1F9D1 1F3FF 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 🧑🏿🦽➡️ E15.1 person in manual wheelchair facing right: dark skin tone
+1F9D1 1F3FF 200D 1F9BD 200D 27A1 ; minimally-qualified # 🧑🏿🦽➡ E15.1 person in manual wheelchair facing right: dark skin tone
+1F468 200D 1F9BD ; fully-qualified # 👨🦽 E12.0 man in manual wheelchair
+1F468 1F3FB 200D 1F9BD ; fully-qualified # 👨🏻🦽 E12.0 man in manual wheelchair: light skin tone
+1F468 1F3FC 200D 1F9BD ; fully-qualified # 👨🏼🦽 E12.0 man in manual wheelchair: medium-light skin tone
+1F468 1F3FD 200D 1F9BD ; fully-qualified # 👨🏽🦽 E12.0 man in manual wheelchair: medium skin tone
+1F468 1F3FE 200D 1F9BD ; fully-qualified # 👨🏾🦽 E12.0 man in manual wheelchair: medium-dark skin tone
+1F468 1F3FF 200D 1F9BD ; fully-qualified # 👨🏿🦽 E12.0 man in manual wheelchair: dark skin tone
+1F468 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 👨🦽➡️ E15.1 man in manual wheelchair facing right
+1F468 200D 1F9BD 200D 27A1 ; minimally-qualified # 👨🦽➡ E15.1 man in manual wheelchair facing right
+1F468 1F3FB 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 👨🏻🦽➡️ E15.1 man in manual wheelchair facing right: light skin tone
+1F468 1F3FB 200D 1F9BD 200D 27A1 ; minimally-qualified # 👨🏻🦽➡ E15.1 man in manual wheelchair facing right: light skin tone
+1F468 1F3FC 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 👨🏼🦽➡️ E15.1 man in manual wheelchair facing right: medium-light skin tone
+1F468 1F3FC 200D 1F9BD 200D 27A1 ; minimally-qualified # 👨🏼🦽➡ E15.1 man in manual wheelchair facing right: medium-light skin tone
+1F468 1F3FD 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 👨🏽🦽➡️ E15.1 man in manual wheelchair facing right: medium skin tone
+1F468 1F3FD 200D 1F9BD 200D 27A1 ; minimally-qualified # 👨🏽🦽➡ E15.1 man in manual wheelchair facing right: medium skin tone
+1F468 1F3FE 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 👨🏾🦽➡️ E15.1 man in manual wheelchair facing right: medium-dark skin tone
+1F468 1F3FE 200D 1F9BD 200D 27A1 ; minimally-qualified # 👨🏾🦽➡ E15.1 man in manual wheelchair facing right: medium-dark skin tone
+1F468 1F3FF 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 👨🏿🦽➡️ E15.1 man in manual wheelchair facing right: dark skin tone
+1F468 1F3FF 200D 1F9BD 200D 27A1 ; minimally-qualified # 👨🏿🦽➡ E15.1 man in manual wheelchair facing right: dark skin tone
+1F469 200D 1F9BD ; fully-qualified # 👩🦽 E12.0 woman in manual wheelchair
+1F469 1F3FB 200D 1F9BD ; fully-qualified # 👩🏻🦽 E12.0 woman in manual wheelchair: light skin tone
+1F469 1F3FC 200D 1F9BD ; fully-qualified # 👩🏼🦽 E12.0 woman in manual wheelchair: medium-light skin tone
+1F469 1F3FD 200D 1F9BD ; fully-qualified # 👩🏽🦽 E12.0 woman in manual wheelchair: medium skin tone
+1F469 1F3FE 200D 1F9BD ; fully-qualified # 👩🏾🦽 E12.0 woman in manual wheelchair: medium-dark skin tone
+1F469 1F3FF 200D 1F9BD ; fully-qualified # 👩🏿🦽 E12.0 woman in manual wheelchair: dark skin tone
+1F469 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 👩🦽➡️ E15.1 woman in manual wheelchair facing right
+1F469 200D 1F9BD 200D 27A1 ; minimally-qualified # 👩🦽➡ E15.1 woman in manual wheelchair facing right
+1F469 1F3FB 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 👩🏻🦽➡️ E15.1 woman in manual wheelchair facing right: light skin tone
+1F469 1F3FB 200D 1F9BD 200D 27A1 ; minimally-qualified # 👩🏻🦽➡ E15.1 woman in manual wheelchair facing right: light skin tone
+1F469 1F3FC 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 👩🏼🦽➡️ E15.1 woman in manual wheelchair facing right: medium-light skin tone
+1F469 1F3FC 200D 1F9BD 200D 27A1 ; minimally-qualified # 👩🏼🦽➡ E15.1 woman in manual wheelchair facing right: medium-light skin tone
+1F469 1F3FD 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 👩🏽🦽➡️ E15.1 woman in manual wheelchair facing right: medium skin tone
+1F469 1F3FD 200D 1F9BD 200D 27A1 ; minimally-qualified # 👩🏽🦽➡ E15.1 woman in manual wheelchair facing right: medium skin tone
+1F469 1F3FE 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 👩🏾🦽➡️ E15.1 woman in manual wheelchair facing right: medium-dark skin tone
+1F469 1F3FE 200D 1F9BD 200D 27A1 ; minimally-qualified # 👩🏾🦽➡ E15.1 woman in manual wheelchair facing right: medium-dark skin tone
+1F469 1F3FF 200D 1F9BD 200D 27A1 FE0F ; fully-qualified # 👩🏿🦽➡️ E15.1 woman in manual wheelchair facing right: dark skin tone
+1F469 1F3FF 200D 1F9BD 200D 27A1 ; minimally-qualified # 👩🏿🦽➡ E15.1 woman in manual wheelchair facing right: dark skin tone
+1F3C3 ; fully-qualified # 🏃 E0.6 person running
+1F3C3 1F3FB ; fully-qualified # 🏃🏻 E1.0 person running: light skin tone
+1F3C3 1F3FC ; fully-qualified # 🏃🏼 E1.0 person running: medium-light skin tone
+1F3C3 1F3FD ; fully-qualified # 🏃🏽 E1.0 person running: medium skin tone
+1F3C3 1F3FE ; fully-qualified # 🏃🏾 E1.0 person running: medium-dark skin tone
+1F3C3 1F3FF ; fully-qualified # 🏃🏿 E1.0 person running: dark skin tone
+1F3C3 200D 2642 FE0F ; fully-qualified # 🏃♂️ E4.0 man running
+1F3C3 200D 2642 ; minimally-qualified # 🏃♂ E4.0 man running
+1F3C3 1F3FB 200D 2642 FE0F ; fully-qualified # 🏃🏻♂️ E4.0 man running: light skin tone
+1F3C3 1F3FB 200D 2642 ; minimally-qualified # 🏃🏻♂ E4.0 man running: light skin tone
+1F3C3 1F3FC 200D 2642 FE0F ; fully-qualified # 🏃🏼♂️ E4.0 man running: medium-light skin tone
+1F3C3 1F3FC 200D 2642 ; minimally-qualified # 🏃🏼♂ E4.0 man running: medium-light skin tone
+1F3C3 1F3FD 200D 2642 FE0F ; fully-qualified # 🏃🏽♂️ E4.0 man running: medium skin tone
+1F3C3 1F3FD 200D 2642 ; minimally-qualified # 🏃🏽♂ E4.0 man running: medium skin tone
+1F3C3 1F3FE 200D 2642 FE0F ; fully-qualified # 🏃🏾♂️ E4.0 man running: medium-dark skin tone
+1F3C3 1F3FE 200D 2642 ; minimally-qualified # 🏃🏾♂ E4.0 man running: medium-dark skin tone
+1F3C3 1F3FF 200D 2642 FE0F ; fully-qualified # 🏃🏿♂️ E4.0 man running: dark skin tone
+1F3C3 1F3FF 200D 2642 ; minimally-qualified # 🏃🏿♂ E4.0 man running: dark skin tone
+1F3C3 200D 2640 FE0F ; fully-qualified # 🏃♀️ E4.0 woman running
+1F3C3 200D 2640 ; minimally-qualified # 🏃♀ E4.0 woman running
+1F3C3 1F3FB 200D 2640 FE0F ; fully-qualified # 🏃🏻♀️ E4.0 woman running: light skin tone
+1F3C3 1F3FB 200D 2640 ; minimally-qualified # 🏃🏻♀ E4.0 woman running: light skin tone
+1F3C3 1F3FC 200D 2640 FE0F ; fully-qualified # 🏃🏼♀️ E4.0 woman running: medium-light skin tone
+1F3C3 1F3FC 200D 2640 ; minimally-qualified # 🏃🏼♀ E4.0 woman running: medium-light skin tone
+1F3C3 1F3FD 200D 2640 FE0F ; fully-qualified # 🏃🏽♀️ E4.0 woman running: medium skin tone
+1F3C3 1F3FD 200D 2640 ; minimally-qualified # 🏃🏽♀ E4.0 woman running: medium skin tone
+1F3C3 1F3FE 200D 2640 FE0F ; fully-qualified # 🏃🏾♀️ E4.0 woman running: medium-dark skin tone
+1F3C3 1F3FE 200D 2640 ; minimally-qualified # 🏃🏾♀ E4.0 woman running: medium-dark skin tone
+1F3C3 1F3FF 200D 2640 FE0F ; fully-qualified # 🏃🏿♀️ E4.0 woman running: dark skin tone
+1F3C3 1F3FF 200D 2640 ; minimally-qualified # 🏃🏿♀ E4.0 woman running: dark skin tone
+1F3C3 200D 27A1 FE0F ; fully-qualified # 🏃➡️ E15.1 person running facing right
+1F3C3 200D 27A1 ; minimally-qualified # 🏃➡ E15.1 person running facing right
+1F3C3 1F3FB 200D 27A1 FE0F ; fully-qualified # 🏃🏻➡️ E15.1 person running facing right: light skin tone
+1F3C3 1F3FB 200D 27A1 ; minimally-qualified # 🏃🏻➡ E15.1 person running facing right: light skin tone
+1F3C3 1F3FC 200D 27A1 FE0F ; fully-qualified # 🏃🏼➡️ E15.1 person running facing right: medium-light skin tone
+1F3C3 1F3FC 200D 27A1 ; minimally-qualified # 🏃🏼➡ E15.1 person running facing right: medium-light skin tone
+1F3C3 1F3FD 200D 27A1 FE0F ; fully-qualified # 🏃🏽➡️ E15.1 person running facing right: medium skin tone
+1F3C3 1F3FD 200D 27A1 ; minimally-qualified # 🏃🏽➡ E15.1 person running facing right: medium skin tone
+1F3C3 1F3FE 200D 27A1 FE0F ; fully-qualified # 🏃🏾➡️ E15.1 person running facing right: medium-dark skin tone
+1F3C3 1F3FE 200D 27A1 ; minimally-qualified # 🏃🏾➡ E15.1 person running facing right: medium-dark skin tone
+1F3C3 1F3FF 200D 27A1 FE0F ; fully-qualified # 🏃🏿➡️ E15.1 person running facing right: dark skin tone
+1F3C3 1F3FF 200D 27A1 ; minimally-qualified # 🏃🏿➡ E15.1 person running facing right: dark skin tone
+1F3C3 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🏃♀️➡️ E15.1 woman running facing right
+1F3C3 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🏃♀➡️ E15.1 woman running facing right
+1F3C3 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🏃♀️➡ E15.1 woman running facing right
+1F3C3 200D 2640 200D 27A1 ; minimally-qualified # 🏃♀➡ E15.1 woman running facing right
+1F3C3 1F3FB 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🏃🏻♀️➡️ E15.1 woman running facing right: light skin tone
+1F3C3 1F3FB 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🏃🏻♀➡️ E15.1 woman running facing right: light skin tone
+1F3C3 1F3FB 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🏃🏻♀️➡ E15.1 woman running facing right: light skin tone
+1F3C3 1F3FB 200D 2640 200D 27A1 ; minimally-qualified # 🏃🏻♀➡ E15.1 woman running facing right: light skin tone
+1F3C3 1F3FC 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🏃🏼♀️➡️ E15.1 woman running facing right: medium-light skin tone
+1F3C3 1F3FC 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🏃🏼♀➡️ E15.1 woman running facing right: medium-light skin tone
+1F3C3 1F3FC 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🏃🏼♀️➡ E15.1 woman running facing right: medium-light skin tone
+1F3C3 1F3FC 200D 2640 200D 27A1 ; minimally-qualified # 🏃🏼♀➡ E15.1 woman running facing right: medium-light skin tone
+1F3C3 1F3FD 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🏃🏽♀️➡️ E15.1 woman running facing right: medium skin tone
+1F3C3 1F3FD 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🏃🏽♀➡️ E15.1 woman running facing right: medium skin tone
+1F3C3 1F3FD 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🏃🏽♀️➡ E15.1 woman running facing right: medium skin tone
+1F3C3 1F3FD 200D 2640 200D 27A1 ; minimally-qualified # 🏃🏽♀➡ E15.1 woman running facing right: medium skin tone
+1F3C3 1F3FE 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🏃🏾♀️➡️ E15.1 woman running facing right: medium-dark skin tone
+1F3C3 1F3FE 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🏃🏾♀➡️ E15.1 woman running facing right: medium-dark skin tone
+1F3C3 1F3FE 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🏃🏾♀️➡ E15.1 woman running facing right: medium-dark skin tone
+1F3C3 1F3FE 200D 2640 200D 27A1 ; minimally-qualified # 🏃🏾♀➡ E15.1 woman running facing right: medium-dark skin tone
+1F3C3 1F3FF 200D 2640 FE0F 200D 27A1 FE0F ; fully-qualified # 🏃🏿♀️➡️ E15.1 woman running facing right: dark skin tone
+1F3C3 1F3FF 200D 2640 200D 27A1 FE0F ; minimally-qualified # 🏃🏿♀➡️ E15.1 woman running facing right: dark skin tone
+1F3C3 1F3FF 200D 2640 FE0F 200D 27A1 ; minimally-qualified # 🏃🏿♀️➡ E15.1 woman running facing right: dark skin tone
+1F3C3 1F3FF 200D 2640 200D 27A1 ; minimally-qualified # 🏃🏿♀➡ E15.1 woman running facing right: dark skin tone
+1F3C3 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🏃♂️➡️ E15.1 man running facing right
+1F3C3 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🏃♂➡️ E15.1 man running facing right
+1F3C3 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🏃♂️➡ E15.1 man running facing right
+1F3C3 200D 2642 200D 27A1 ; minimally-qualified # 🏃♂➡ E15.1 man running facing right
+1F3C3 1F3FB 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🏃🏻♂️➡️ E15.1 man running facing right: light skin tone
+1F3C3 1F3FB 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🏃🏻♂➡️ E15.1 man running facing right: light skin tone
+1F3C3 1F3FB 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🏃🏻♂️➡ E15.1 man running facing right: light skin tone
+1F3C3 1F3FB 200D 2642 200D 27A1 ; minimally-qualified # 🏃🏻♂➡ E15.1 man running facing right: light skin tone
+1F3C3 1F3FC 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🏃🏼♂️➡️ E15.1 man running facing right: medium-light skin tone
+1F3C3 1F3FC 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🏃🏼♂➡️ E15.1 man running facing right: medium-light skin tone
+1F3C3 1F3FC 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🏃🏼♂️➡ E15.1 man running facing right: medium-light skin tone
+1F3C3 1F3FC 200D 2642 200D 27A1 ; minimally-qualified # 🏃🏼♂➡ E15.1 man running facing right: medium-light skin tone
+1F3C3 1F3FD 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🏃🏽♂️➡️ E15.1 man running facing right: medium skin tone
+1F3C3 1F3FD 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🏃🏽♂➡️ E15.1 man running facing right: medium skin tone
+1F3C3 1F3FD 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🏃🏽♂️➡ E15.1 man running facing right: medium skin tone
+1F3C3 1F3FD 200D 2642 200D 27A1 ; minimally-qualified # 🏃🏽♂➡ E15.1 man running facing right: medium skin tone
+1F3C3 1F3FE 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🏃🏾♂️➡️ E15.1 man running facing right: medium-dark skin tone
+1F3C3 1F3FE 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🏃🏾♂➡️ E15.1 man running facing right: medium-dark skin tone
+1F3C3 1F3FE 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🏃🏾♂️➡ E15.1 man running facing right: medium-dark skin tone
+1F3C3 1F3FE 200D 2642 200D 27A1 ; minimally-qualified # 🏃🏾♂➡ E15.1 man running facing right: medium-dark skin tone
+1F3C3 1F3FF 200D 2642 FE0F 200D 27A1 FE0F ; fully-qualified # 🏃🏿♂️➡️ E15.1 man running facing right: dark skin tone
+1F3C3 1F3FF 200D 2642 200D 27A1 FE0F ; minimally-qualified # 🏃🏿♂➡️ E15.1 man running facing right: dark skin tone
+1F3C3 1F3FF 200D 2642 FE0F 200D 27A1 ; minimally-qualified # 🏃🏿♂️➡ E15.1 man running facing right: dark skin tone
+1F3C3 1F3FF 200D 2642 200D 27A1 ; minimally-qualified # 🏃🏿♂➡ E15.1 man running facing right: dark skin tone
+1F483 ; fully-qualified # 💃 E0.6 woman dancing
+1F483 1F3FB ; fully-qualified # 💃🏻 E1.0 woman dancing: light skin tone
+1F483 1F3FC ; fully-qualified # 💃🏼 E1.0 woman dancing: medium-light skin tone
+1F483 1F3FD ; fully-qualified # 💃🏽 E1.0 woman dancing: medium skin tone
+1F483 1F3FE ; fully-qualified # 💃🏾 E1.0 woman dancing: medium-dark skin tone
+1F483 1F3FF ; fully-qualified # 💃🏿 E1.0 woman dancing: dark skin tone
+1F57A ; fully-qualified # 🕺 E3.0 man dancing
+1F57A 1F3FB ; fully-qualified # 🕺🏻 E3.0 man dancing: light skin tone
+1F57A 1F3FC ; fully-qualified # 🕺🏼 E3.0 man dancing: medium-light skin tone
+1F57A 1F3FD ; fully-qualified # 🕺🏽 E3.0 man dancing: medium skin tone
+1F57A 1F3FE ; fully-qualified # 🕺🏾 E3.0 man dancing: medium-dark skin tone
+1F57A 1F3FF ; fully-qualified # 🕺🏿 E3.0 man dancing: dark skin tone
+1F574 FE0F ; fully-qualified # 🕴️ E0.7 person in suit levitating
+1F574 ; unqualified # 🕴 E0.7 person in suit levitating
+1F574 1F3FB ; fully-qualified # 🕴🏻 E4.0 person in suit levitating: light skin tone
+1F574 1F3FC ; fully-qualified # 🕴🏼 E4.0 person in suit levitating: medium-light skin tone
+1F574 1F3FD ; fully-qualified # 🕴🏽 E4.0 person in suit levitating: medium skin tone
+1F574 1F3FE ; fully-qualified # 🕴🏾 E4.0 person in suit levitating: medium-dark skin tone
+1F574 1F3FF ; fully-qualified # 🕴🏿 E4.0 person in suit levitating: dark skin tone
+1F46F ; fully-qualified # 👯 E0.6 people with bunny ears
+1F46F 200D 2642 FE0F ; fully-qualified # 👯♂️ E4.0 men with bunny ears
+1F46F 200D 2642 ; minimally-qualified # 👯♂ E4.0 men with bunny ears
+1F46F 200D 2640 FE0F ; fully-qualified # 👯♀️ E4.0 women with bunny ears
+1F46F 200D 2640 ; minimally-qualified # 👯♀ E4.0 women with bunny ears
+1F9D6 ; fully-qualified # 🧖 E5.0 person in steamy room
+1F9D6 1F3FB ; fully-qualified # 🧖🏻 E5.0 person in steamy room: light skin tone
+1F9D6 1F3FC ; fully-qualified # 🧖🏼 E5.0 person in steamy room: medium-light skin tone
+1F9D6 1F3FD ; fully-qualified # 🧖🏽 E5.0 person in steamy room: medium skin tone
+1F9D6 1F3FE ; fully-qualified # 🧖🏾 E5.0 person in steamy room: medium-dark skin tone
+1F9D6 1F3FF ; fully-qualified # 🧖🏿 E5.0 person in steamy room: dark skin tone
+1F9D6 200D 2642 FE0F ; fully-qualified # 🧖♂️ E5.0 man in steamy room
+1F9D6 200D 2642 ; minimally-qualified # 🧖♂ E5.0 man in steamy room
+1F9D6 1F3FB 200D 2642 FE0F ; fully-qualified # 🧖🏻♂️ E5.0 man in steamy room: light skin tone
+1F9D6 1F3FB 200D 2642 ; minimally-qualified # 🧖🏻♂ E5.0 man in steamy room: light skin tone
+1F9D6 1F3FC 200D 2642 FE0F ; fully-qualified # 🧖🏼♂️ E5.0 man in steamy room: medium-light skin tone
+1F9D6 1F3FC 200D 2642 ; minimally-qualified # 🧖🏼♂ E5.0 man in steamy room: medium-light skin tone
+1F9D6 1F3FD 200D 2642 FE0F ; fully-qualified # 🧖🏽♂️ E5.0 man in steamy room: medium skin tone
+1F9D6 1F3FD 200D 2642 ; minimally-qualified # 🧖🏽♂ E5.0 man in steamy room: medium skin tone
+1F9D6 1F3FE 200D 2642 FE0F ; fully-qualified # 🧖🏾♂️ E5.0 man in steamy room: medium-dark skin tone
+1F9D6 1F3FE 200D 2642 ; minimally-qualified # 🧖🏾♂ E5.0 man in steamy room: medium-dark skin tone
+1F9D6 1F3FF 200D 2642 FE0F ; fully-qualified # 🧖🏿♂️ E5.0 man in steamy room: dark skin tone
+1F9D6 1F3FF 200D 2642 ; minimally-qualified # 🧖🏿♂ E5.0 man in steamy room: dark skin tone
+1F9D6 200D 2640 FE0F ; fully-qualified # 🧖♀️ E5.0 woman in steamy room
+1F9D6 200D 2640 ; minimally-qualified # 🧖♀ E5.0 woman in steamy room
+1F9D6 1F3FB 200D 2640 FE0F ; fully-qualified # 🧖🏻♀️ E5.0 woman in steamy room: light skin tone
+1F9D6 1F3FB 200D 2640 ; minimally-qualified # 🧖🏻♀ E5.0 woman in steamy room: light skin tone
+1F9D6 1F3FC 200D 2640 FE0F ; fully-qualified # 🧖🏼♀️ E5.0 woman in steamy room: medium-light skin tone
+1F9D6 1F3FC 200D 2640 ; minimally-qualified # 🧖🏼♀ E5.0 woman in steamy room: medium-light skin tone
+1F9D6 1F3FD 200D 2640 FE0F ; fully-qualified # 🧖🏽♀️ E5.0 woman in steamy room: medium skin tone
+1F9D6 1F3FD 200D 2640 ; minimally-qualified # 🧖🏽♀ E5.0 woman in steamy room: medium skin tone
+1F9D6 1F3FE 200D 2640 FE0F ; fully-qualified # 🧖🏾♀️ E5.0 woman in steamy room: medium-dark skin tone
+1F9D6 1F3FE 200D 2640 ; minimally-qualified # 🧖🏾♀ E5.0 woman in steamy room: medium-dark skin tone
+1F9D6 1F3FF 200D 2640 FE0F ; fully-qualified # 🧖🏿♀️ E5.0 woman in steamy room: dark skin tone
+1F9D6 1F3FF 200D 2640 ; minimally-qualified # 🧖🏿♀ E5.0 woman in steamy room: dark skin tone
+1F9D7 ; fully-qualified # 🧗 E5.0 person climbing
+1F9D7 1F3FB ; fully-qualified # 🧗🏻 E5.0 person climbing: light skin tone
+1F9D7 1F3FC ; fully-qualified # 🧗🏼 E5.0 person climbing: medium-light skin tone
+1F9D7 1F3FD ; fully-qualified # 🧗🏽 E5.0 person climbing: medium skin tone
+1F9D7 1F3FE ; fully-qualified # 🧗🏾 E5.0 person climbing: medium-dark skin tone
+1F9D7 1F3FF ; fully-qualified # 🧗🏿 E5.0 person climbing: dark skin tone
+1F9D7 200D 2642 FE0F ; fully-qualified # 🧗♂️ E5.0 man climbing
+1F9D7 200D 2642 ; minimally-qualified # 🧗♂ E5.0 man climbing
+1F9D7 1F3FB 200D 2642 FE0F ; fully-qualified # 🧗🏻♂️ E5.0 man climbing: light skin tone
+1F9D7 1F3FB 200D 2642 ; minimally-qualified # 🧗🏻♂ E5.0 man climbing: light skin tone
+1F9D7 1F3FC 200D 2642 FE0F ; fully-qualified # 🧗🏼♂️ E5.0 man climbing: medium-light skin tone
+1F9D7 1F3FC 200D 2642 ; minimally-qualified # 🧗🏼♂ E5.0 man climbing: medium-light skin tone
+1F9D7 1F3FD 200D 2642 FE0F ; fully-qualified # 🧗🏽♂️ E5.0 man climbing: medium skin tone
+1F9D7 1F3FD 200D 2642 ; minimally-qualified # 🧗🏽♂ E5.0 man climbing: medium skin tone
+1F9D7 1F3FE 200D 2642 FE0F ; fully-qualified # 🧗🏾♂️ E5.0 man climbing: medium-dark skin tone
+1F9D7 1F3FE 200D 2642 ; minimally-qualified # 🧗🏾♂ E5.0 man climbing: medium-dark skin tone
+1F9D7 1F3FF 200D 2642 FE0F ; fully-qualified # 🧗🏿♂️ E5.0 man climbing: dark skin tone
+1F9D7 1F3FF 200D 2642 ; minimally-qualified # 🧗🏿♂ E5.0 man climbing: dark skin tone
+1F9D7 200D 2640 FE0F ; fully-qualified # 🧗♀️ E5.0 woman climbing
+1F9D7 200D 2640 ; minimally-qualified # 🧗♀ E5.0 woman climbing
+1F9D7 1F3FB 200D 2640 FE0F ; fully-qualified # 🧗🏻♀️ E5.0 woman climbing: light skin tone
+1F9D7 1F3FB 200D 2640 ; minimally-qualified # 🧗🏻♀ E5.0 woman climbing: light skin tone
+1F9D7 1F3FC 200D 2640 FE0F ; fully-qualified # 🧗🏼♀️ E5.0 woman climbing: medium-light skin tone
+1F9D7 1F3FC 200D 2640 ; minimally-qualified # 🧗🏼♀ E5.0 woman climbing: medium-light skin tone
+1F9D7 1F3FD 200D 2640 FE0F ; fully-qualified # 🧗🏽♀️ E5.0 woman climbing: medium skin tone
+1F9D7 1F3FD 200D 2640 ; minimally-qualified # 🧗🏽♀ E5.0 woman climbing: medium skin tone
+1F9D7 1F3FE 200D 2640 FE0F ; fully-qualified # 🧗🏾♀️ E5.0 woman climbing: medium-dark skin tone
+1F9D7 1F3FE 200D 2640 ; minimally-qualified # 🧗🏾♀ E5.0 woman climbing: medium-dark skin tone
+1F9D7 1F3FF 200D 2640 FE0F ; fully-qualified # 🧗🏿♀️ E5.0 woman climbing: dark skin tone
+1F9D7 1F3FF 200D 2640 ; minimally-qualified # 🧗🏿♀ E5.0 woman climbing: dark skin tone
+
+# subgroup: person-sport
+1F93A ; fully-qualified # 🤺 E3.0 person fencing
+1F3C7 ; fully-qualified # 🏇 E1.0 horse racing
+1F3C7 1F3FB ; fully-qualified # 🏇🏻 E1.0 horse racing: light skin tone
+1F3C7 1F3FC ; fully-qualified # 🏇🏼 E1.0 horse racing: medium-light skin tone
+1F3C7 1F3FD ; fully-qualified # 🏇🏽 E1.0 horse racing: medium skin tone
+1F3C7 1F3FE ; fully-qualified # 🏇🏾 E1.0 horse racing: medium-dark skin tone
+1F3C7 1F3FF ; fully-qualified # 🏇🏿 E1.0 horse racing: dark skin tone
+26F7 FE0F ; fully-qualified # ⛷️ E0.7 skier
+26F7 ; unqualified # ⛷ E0.7 skier
+1F3C2 ; fully-qualified # 🏂 E0.6 snowboarder
+1F3C2 1F3FB ; fully-qualified # 🏂🏻 E1.0 snowboarder: light skin tone
+1F3C2 1F3FC ; fully-qualified # 🏂🏼 E1.0 snowboarder: medium-light skin tone
+1F3C2 1F3FD ; fully-qualified # 🏂🏽 E1.0 snowboarder: medium skin tone
+1F3C2 1F3FE ; fully-qualified # 🏂🏾 E1.0 snowboarder: medium-dark skin tone
+1F3C2 1F3FF ; fully-qualified # 🏂🏿 E1.0 snowboarder: dark skin tone
+1F3CC FE0F ; fully-qualified # 🏌️ E0.7 person golfing
+1F3CC ; unqualified # 🏌 E0.7 person golfing
+1F3CC 1F3FB ; fully-qualified # 🏌🏻 E4.0 person golfing: light skin tone
+1F3CC 1F3FC ; fully-qualified # 🏌🏼 E4.0 person golfing: medium-light skin tone
+1F3CC 1F3FD ; fully-qualified # 🏌🏽 E4.0 person golfing: medium skin tone
+1F3CC 1F3FE ; fully-qualified # 🏌🏾 E4.0 person golfing: medium-dark skin tone
+1F3CC 1F3FF ; fully-qualified # 🏌🏿 E4.0 person golfing: dark skin tone
+1F3CC FE0F 200D 2642 FE0F ; fully-qualified # 🏌️♂️ E4.0 man golfing
+1F3CC 200D 2642 FE0F ; unqualified # 🏌♂️ E4.0 man golfing
+1F3CC FE0F 200D 2642 ; minimally-qualified # 🏌️♂ E4.0 man golfing
+1F3CC 200D 2642 ; unqualified # 🏌♂ E4.0 man golfing
+1F3CC 1F3FB 200D 2642 FE0F ; fully-qualified # 🏌🏻♂️ E4.0 man golfing: light skin tone
+1F3CC 1F3FB 200D 2642 ; minimally-qualified # 🏌🏻♂ E4.0 man golfing: light skin tone
+1F3CC 1F3FC 200D 2642 FE0F ; fully-qualified # 🏌🏼♂️ E4.0 man golfing: medium-light skin tone
+1F3CC 1F3FC 200D 2642 ; minimally-qualified # 🏌🏼♂ E4.0 man golfing: medium-light skin tone
+1F3CC 1F3FD 200D 2642 FE0F ; fully-qualified # 🏌🏽♂️ E4.0 man golfing: medium skin tone
+1F3CC 1F3FD 200D 2642 ; minimally-qualified # 🏌🏽♂ E4.0 man golfing: medium skin tone
+1F3CC 1F3FE 200D 2642 FE0F ; fully-qualified # 🏌🏾♂️ E4.0 man golfing: medium-dark skin tone
+1F3CC 1F3FE 200D 2642 ; minimally-qualified # 🏌🏾♂ E4.0 man golfing: medium-dark skin tone
+1F3CC 1F3FF 200D 2642 FE0F ; fully-qualified # 🏌🏿♂️ E4.0 man golfing: dark skin tone
+1F3CC 1F3FF 200D 2642 ; minimally-qualified # 🏌🏿♂ E4.0 man golfing: dark skin tone
+1F3CC FE0F 200D 2640 FE0F ; fully-qualified # 🏌️♀️ E4.0 woman golfing
+1F3CC 200D 2640 FE0F ; unqualified # 🏌♀️ E4.0 woman golfing
+1F3CC FE0F 200D 2640 ; minimally-qualified # 🏌️♀ E4.0 woman golfing
+1F3CC 200D 2640 ; unqualified # 🏌♀ E4.0 woman golfing
+1F3CC 1F3FB 200D 2640 FE0F ; fully-qualified # 🏌🏻♀️ E4.0 woman golfing: light skin tone
+1F3CC 1F3FB 200D 2640 ; minimally-qualified # 🏌🏻♀ E4.0 woman golfing: light skin tone
+1F3CC 1F3FC 200D 2640 FE0F ; fully-qualified # 🏌🏼♀️ E4.0 woman golfing: medium-light skin tone
+1F3CC 1F3FC 200D 2640 ; minimally-qualified # 🏌🏼♀ E4.0 woman golfing: medium-light skin tone
+1F3CC 1F3FD 200D 2640 FE0F ; fully-qualified # 🏌🏽♀️ E4.0 woman golfing: medium skin tone
+1F3CC 1F3FD 200D 2640 ; minimally-qualified # 🏌🏽♀ E4.0 woman golfing: medium skin tone
+1F3CC 1F3FE 200D 2640 FE0F ; fully-qualified # 🏌🏾♀️ E4.0 woman golfing: medium-dark skin tone
+1F3CC 1F3FE 200D 2640 ; minimally-qualified # 🏌🏾♀ E4.0 woman golfing: medium-dark skin tone
+1F3CC 1F3FF 200D 2640 FE0F ; fully-qualified # 🏌🏿♀️ E4.0 woman golfing: dark skin tone
+1F3CC 1F3FF 200D 2640 ; minimally-qualified # 🏌🏿♀ E4.0 woman golfing: dark skin tone
+1F3C4 ; fully-qualified # 🏄 E0.6 person surfing
+1F3C4 1F3FB ; fully-qualified # 🏄🏻 E1.0 person surfing: light skin tone
+1F3C4 1F3FC ; fully-qualified # 🏄🏼 E1.0 person surfing: medium-light skin tone
+1F3C4 1F3FD ; fully-qualified # 🏄🏽 E1.0 person surfing: medium skin tone
+1F3C4 1F3FE ; fully-qualified # 🏄🏾 E1.0 person surfing: medium-dark skin tone
+1F3C4 1F3FF ; fully-qualified # 🏄🏿 E1.0 person surfing: dark skin tone
+1F3C4 200D 2642 FE0F ; fully-qualified # 🏄♂️ E4.0 man surfing
+1F3C4 200D 2642 ; minimally-qualified # 🏄♂ E4.0 man surfing
+1F3C4 1F3FB 200D 2642 FE0F ; fully-qualified # 🏄🏻♂️ E4.0 man surfing: light skin tone
+1F3C4 1F3FB 200D 2642 ; minimally-qualified # 🏄🏻♂ E4.0 man surfing: light skin tone
+1F3C4 1F3FC 200D 2642 FE0F ; fully-qualified # 🏄🏼♂️ E4.0 man surfing: medium-light skin tone
+1F3C4 1F3FC 200D 2642 ; minimally-qualified # 🏄🏼♂ E4.0 man surfing: medium-light skin tone
+1F3C4 1F3FD 200D 2642 FE0F ; fully-qualified # 🏄🏽♂️ E4.0 man surfing: medium skin tone
+1F3C4 1F3FD 200D 2642 ; minimally-qualified # 🏄🏽♂ E4.0 man surfing: medium skin tone
+1F3C4 1F3FE 200D 2642 FE0F ; fully-qualified # 🏄🏾♂️ E4.0 man surfing: medium-dark skin tone
+1F3C4 1F3FE 200D 2642 ; minimally-qualified # 🏄🏾♂ E4.0 man surfing: medium-dark skin tone
+1F3C4 1F3FF 200D 2642 FE0F ; fully-qualified # 🏄🏿♂️ E4.0 man surfing: dark skin tone
+1F3C4 1F3FF 200D 2642 ; minimally-qualified # 🏄🏿♂ E4.0 man surfing: dark skin tone
+1F3C4 200D 2640 FE0F ; fully-qualified # 🏄♀️ E4.0 woman surfing
+1F3C4 200D 2640 ; minimally-qualified # 🏄♀ E4.0 woman surfing
+1F3C4 1F3FB 200D 2640 FE0F ; fully-qualified # 🏄🏻♀️ E4.0 woman surfing: light skin tone
+1F3C4 1F3FB 200D 2640 ; minimally-qualified # 🏄🏻♀ E4.0 woman surfing: light skin tone
+1F3C4 1F3FC 200D 2640 FE0F ; fully-qualified # 🏄🏼♀️ E4.0 woman surfing: medium-light skin tone
+1F3C4 1F3FC 200D 2640 ; minimally-qualified # 🏄🏼♀ E4.0 woman surfing: medium-light skin tone
+1F3C4 1F3FD 200D 2640 FE0F ; fully-qualified # 🏄🏽♀️ E4.0 woman surfing: medium skin tone
+1F3C4 1F3FD 200D 2640 ; minimally-qualified # 🏄🏽♀ E4.0 woman surfing: medium skin tone
+1F3C4 1F3FE 200D 2640 FE0F ; fully-qualified # 🏄🏾♀️ E4.0 woman surfing: medium-dark skin tone
+1F3C4 1F3FE 200D 2640 ; minimally-qualified # 🏄🏾♀ E4.0 woman surfing: medium-dark skin tone
+1F3C4 1F3FF 200D 2640 FE0F ; fully-qualified # 🏄🏿♀️ E4.0 woman surfing: dark skin tone
+1F3C4 1F3FF 200D 2640 ; minimally-qualified # 🏄🏿♀ E4.0 woman surfing: dark skin tone
+1F6A3 ; fully-qualified # 🚣 E1.0 person rowing boat
+1F6A3 1F3FB ; fully-qualified # 🚣🏻 E1.0 person rowing boat: light skin tone
+1F6A3 1F3FC ; fully-qualified # 🚣🏼 E1.0 person rowing boat: medium-light skin tone
+1F6A3 1F3FD ; fully-qualified # 🚣🏽 E1.0 person rowing boat: medium skin tone
+1F6A3 1F3FE ; fully-qualified # 🚣🏾 E1.0 person rowing boat: medium-dark skin tone
+1F6A3 1F3FF ; fully-qualified # 🚣🏿 E1.0 person rowing boat: dark skin tone
+1F6A3 200D 2642 FE0F ; fully-qualified # 🚣♂️ E4.0 man rowing boat
+1F6A3 200D 2642 ; minimally-qualified # 🚣♂ E4.0 man rowing boat
+1F6A3 1F3FB 200D 2642 FE0F ; fully-qualified # 🚣🏻♂️ E4.0 man rowing boat: light skin tone
+1F6A3 1F3FB 200D 2642 ; minimally-qualified # 🚣🏻♂ E4.0 man rowing boat: light skin tone
+1F6A3 1F3FC 200D 2642 FE0F ; fully-qualified # 🚣🏼♂️ E4.0 man rowing boat: medium-light skin tone
+1F6A3 1F3FC 200D 2642 ; minimally-qualified # 🚣🏼♂ E4.0 man rowing boat: medium-light skin tone
+1F6A3 1F3FD 200D 2642 FE0F ; fully-qualified # 🚣🏽♂️ E4.0 man rowing boat: medium skin tone
+1F6A3 1F3FD 200D 2642 ; minimally-qualified # 🚣🏽♂ E4.0 man rowing boat: medium skin tone
+1F6A3 1F3FE 200D 2642 FE0F ; fully-qualified # 🚣🏾♂️ E4.0 man rowing boat: medium-dark skin tone
+1F6A3 1F3FE 200D 2642 ; minimally-qualified # 🚣🏾♂ E4.0 man rowing boat: medium-dark skin tone
+1F6A3 1F3FF 200D 2642 FE0F ; fully-qualified # 🚣🏿♂️ E4.0 man rowing boat: dark skin tone
+1F6A3 1F3FF 200D 2642 ; minimally-qualified # 🚣🏿♂ E4.0 man rowing boat: dark skin tone
+1F6A3 200D 2640 FE0F ; fully-qualified # 🚣♀️ E4.0 woman rowing boat
+1F6A3 200D 2640 ; minimally-qualified # 🚣♀ E4.0 woman rowing boat
+1F6A3 1F3FB 200D 2640 FE0F ; fully-qualified # 🚣🏻♀️ E4.0 woman rowing boat: light skin tone
+1F6A3 1F3FB 200D 2640 ; minimally-qualified # 🚣🏻♀ E4.0 woman rowing boat: light skin tone
+1F6A3 1F3FC 200D 2640 FE0F ; fully-qualified # 🚣🏼♀️ E4.0 woman rowing boat: medium-light skin tone
+1F6A3 1F3FC 200D 2640 ; minimally-qualified # 🚣🏼♀ E4.0 woman rowing boat: medium-light skin tone
+1F6A3 1F3FD 200D 2640 FE0F ; fully-qualified # 🚣🏽♀️ E4.0 woman rowing boat: medium skin tone
+1F6A3 1F3FD 200D 2640 ; minimally-qualified # 🚣🏽♀ E4.0 woman rowing boat: medium skin tone
+1F6A3 1F3FE 200D 2640 FE0F ; fully-qualified # 🚣🏾♀️ E4.0 woman rowing boat: medium-dark skin tone
+1F6A3 1F3FE 200D 2640 ; minimally-qualified # 🚣🏾♀ E4.0 woman rowing boat: medium-dark skin tone
+1F6A3 1F3FF 200D 2640 FE0F ; fully-qualified # 🚣🏿♀️ E4.0 woman rowing boat: dark skin tone
+1F6A3 1F3FF 200D 2640 ; minimally-qualified # 🚣🏿♀ E4.0 woman rowing boat: dark skin tone
+1F3CA ; fully-qualified # 🏊 E0.6 person swimming
+1F3CA 1F3FB ; fully-qualified # 🏊🏻 E1.0 person swimming: light skin tone
+1F3CA 1F3FC ; fully-qualified # 🏊🏼 E1.0 person swimming: medium-light skin tone
+1F3CA 1F3FD ; fully-qualified # 🏊🏽 E1.0 person swimming: medium skin tone
+1F3CA 1F3FE ; fully-qualified # 🏊🏾 E1.0 person swimming: medium-dark skin tone
+1F3CA 1F3FF ; fully-qualified # 🏊🏿 E1.0 person swimming: dark skin tone
+1F3CA 200D 2642 FE0F ; fully-qualified # 🏊♂️ E4.0 man swimming
+1F3CA 200D 2642 ; minimally-qualified # 🏊♂ E4.0 man swimming
+1F3CA 1F3FB 200D 2642 FE0F ; fully-qualified # 🏊🏻♂️ E4.0 man swimming: light skin tone
+1F3CA 1F3FB 200D 2642 ; minimally-qualified # 🏊🏻♂ E4.0 man swimming: light skin tone
+1F3CA 1F3FC 200D 2642 FE0F ; fully-qualified # 🏊🏼♂️ E4.0 man swimming: medium-light skin tone
+1F3CA 1F3FC 200D 2642 ; minimally-qualified # 🏊🏼♂ E4.0 man swimming: medium-light skin tone
+1F3CA 1F3FD 200D 2642 FE0F ; fully-qualified # 🏊🏽♂️ E4.0 man swimming: medium skin tone
+1F3CA 1F3FD 200D 2642 ; minimally-qualified # 🏊🏽♂ E4.0 man swimming: medium skin tone
+1F3CA 1F3FE 200D 2642 FE0F ; fully-qualified # 🏊🏾♂️ E4.0 man swimming: medium-dark skin tone
+1F3CA 1F3FE 200D 2642 ; minimally-qualified # 🏊🏾♂ E4.0 man swimming: medium-dark skin tone
+1F3CA 1F3FF 200D 2642 FE0F ; fully-qualified # 🏊🏿♂️ E4.0 man swimming: dark skin tone
+1F3CA 1F3FF 200D 2642 ; minimally-qualified # 🏊🏿♂ E4.0 man swimming: dark skin tone
+1F3CA 200D 2640 FE0F ; fully-qualified # 🏊♀️ E4.0 woman swimming
+1F3CA 200D 2640 ; minimally-qualified # 🏊♀ E4.0 woman swimming
+1F3CA 1F3FB 200D 2640 FE0F ; fully-qualified # 🏊🏻♀️ E4.0 woman swimming: light skin tone
+1F3CA 1F3FB 200D 2640 ; minimally-qualified # 🏊🏻♀ E4.0 woman swimming: light skin tone
+1F3CA 1F3FC 200D 2640 FE0F ; fully-qualified # 🏊🏼♀️ E4.0 woman swimming: medium-light skin tone
+1F3CA 1F3FC 200D 2640 ; minimally-qualified # 🏊🏼♀ E4.0 woman swimming: medium-light skin tone
+1F3CA 1F3FD 200D 2640 FE0F ; fully-qualified # 🏊🏽♀️ E4.0 woman swimming: medium skin tone
+1F3CA 1F3FD 200D 2640 ; minimally-qualified # 🏊🏽♀ E4.0 woman swimming: medium skin tone
+1F3CA 1F3FE 200D 2640 FE0F ; fully-qualified # 🏊🏾♀️ E4.0 woman swimming: medium-dark skin tone
+1F3CA 1F3FE 200D 2640 ; minimally-qualified # 🏊🏾♀ E4.0 woman swimming: medium-dark skin tone
+1F3CA 1F3FF 200D 2640 FE0F ; fully-qualified # 🏊🏿♀️ E4.0 woman swimming: dark skin tone
+1F3CA 1F3FF 200D 2640 ; minimally-qualified # 🏊🏿♀ E4.0 woman swimming: dark skin tone
+26F9 FE0F ; fully-qualified # ⛹️ E0.7 person bouncing ball
+26F9 ; unqualified # ⛹ E0.7 person bouncing ball
+26F9 1F3FB ; fully-qualified # ⛹🏻 E2.0 person bouncing ball: light skin tone
+26F9 1F3FC ; fully-qualified # ⛹🏼 E2.0 person bouncing ball: medium-light skin tone
+26F9 1F3FD ; fully-qualified # ⛹🏽 E2.0 person bouncing ball: medium skin tone
+26F9 1F3FE ; fully-qualified # ⛹🏾 E2.0 person bouncing ball: medium-dark skin tone
+26F9 1F3FF ; fully-qualified # ⛹🏿 E2.0 person bouncing ball: dark skin tone
+26F9 FE0F 200D 2642 FE0F ; fully-qualified # ⛹️♂️ E4.0 man bouncing ball
+26F9 200D 2642 FE0F ; unqualified # ⛹♂️ E4.0 man bouncing ball
+26F9 FE0F 200D 2642 ; minimally-qualified # ⛹️♂ E4.0 man bouncing ball
+26F9 200D 2642 ; unqualified # ⛹♂ E4.0 man bouncing ball
+26F9 1F3FB 200D 2642 FE0F ; fully-qualified # ⛹🏻♂️ E4.0 man bouncing ball: light skin tone
+26F9 1F3FB 200D 2642 ; minimally-qualified # ⛹🏻♂ E4.0 man bouncing ball: light skin tone
+26F9 1F3FC 200D 2642 FE0F ; fully-qualified # ⛹🏼♂️ E4.0 man bouncing ball: medium-light skin tone
+26F9 1F3FC 200D 2642 ; minimally-qualified # ⛹🏼♂ E4.0 man bouncing ball: medium-light skin tone
+26F9 1F3FD 200D 2642 FE0F ; fully-qualified # ⛹🏽♂️ E4.0 man bouncing ball: medium skin tone
+26F9 1F3FD 200D 2642 ; minimally-qualified # ⛹🏽♂ E4.0 man bouncing ball: medium skin tone
+26F9 1F3FE 200D 2642 FE0F ; fully-qualified # ⛹🏾♂️ E4.0 man bouncing ball: medium-dark skin tone
+26F9 1F3FE 200D 2642 ; minimally-qualified # ⛹🏾♂ E4.0 man bouncing ball: medium-dark skin tone
+26F9 1F3FF 200D 2642 FE0F ; fully-qualified # ⛹🏿♂️ E4.0 man bouncing ball: dark skin tone
+26F9 1F3FF 200D 2642 ; minimally-qualified # ⛹🏿♂ E4.0 man bouncing ball: dark skin tone
+26F9 FE0F 200D 2640 FE0F ; fully-qualified # ⛹️♀️ E4.0 woman bouncing ball
+26F9 200D 2640 FE0F ; unqualified # ⛹♀️ E4.0 woman bouncing ball
+26F9 FE0F 200D 2640 ; minimally-qualified # ⛹️♀ E4.0 woman bouncing ball
+26F9 200D 2640 ; unqualified # ⛹♀ E4.0 woman bouncing ball
+26F9 1F3FB 200D 2640 FE0F ; fully-qualified # ⛹🏻♀️ E4.0 woman bouncing ball: light skin tone
+26F9 1F3FB 200D 2640 ; minimally-qualified # ⛹🏻♀ E4.0 woman bouncing ball: light skin tone
+26F9 1F3FC 200D 2640 FE0F ; fully-qualified # ⛹🏼♀️ E4.0 woman bouncing ball: medium-light skin tone
+26F9 1F3FC 200D 2640 ; minimally-qualified # ⛹🏼♀ E4.0 woman bouncing ball: medium-light skin tone
+26F9 1F3FD 200D 2640 FE0F ; fully-qualified # ⛹🏽♀️ E4.0 woman bouncing ball: medium skin tone
+26F9 1F3FD 200D 2640 ; minimally-qualified # ⛹🏽♀ E4.0 woman bouncing ball: medium skin tone
+26F9 1F3FE 200D 2640 FE0F ; fully-qualified # ⛹🏾♀️ E4.0 woman bouncing ball: medium-dark skin tone
+26F9 1F3FE 200D 2640 ; minimally-qualified # ⛹🏾♀ E4.0 woman bouncing ball: medium-dark skin tone
+26F9 1F3FF 200D 2640 FE0F ; fully-qualified # ⛹🏿♀️ E4.0 woman bouncing ball: dark skin tone
+26F9 1F3FF 200D 2640 ; minimally-qualified # ⛹🏿♀ E4.0 woman bouncing ball: dark skin tone
+1F3CB FE0F ; fully-qualified # 🏋️ E0.7 person lifting weights
+1F3CB ; unqualified # 🏋 E0.7 person lifting weights
+1F3CB 1F3FB ; fully-qualified # 🏋🏻 E2.0 person lifting weights: light skin tone
+1F3CB 1F3FC ; fully-qualified # 🏋🏼 E2.0 person lifting weights: medium-light skin tone
+1F3CB 1F3FD ; fully-qualified # 🏋🏽 E2.0 person lifting weights: medium skin tone
+1F3CB 1F3FE ; fully-qualified # 🏋🏾 E2.0 person lifting weights: medium-dark skin tone
+1F3CB 1F3FF ; fully-qualified # 🏋🏿 E2.0 person lifting weights: dark skin tone
+1F3CB FE0F 200D 2642 FE0F ; fully-qualified # 🏋️♂️ E4.0 man lifting weights
+1F3CB 200D 2642 FE0F ; unqualified # 🏋♂️ E4.0 man lifting weights
+1F3CB FE0F 200D 2642 ; minimally-qualified # 🏋️♂ E4.0 man lifting weights
+1F3CB 200D 2642 ; unqualified # 🏋♂ E4.0 man lifting weights
+1F3CB 1F3FB 200D 2642 FE0F ; fully-qualified # 🏋🏻♂️ E4.0 man lifting weights: light skin tone
+1F3CB 1F3FB 200D 2642 ; minimally-qualified # 🏋🏻♂ E4.0 man lifting weights: light skin tone
+1F3CB 1F3FC 200D 2642 FE0F ; fully-qualified # 🏋🏼♂️ E4.0 man lifting weights: medium-light skin tone
+1F3CB 1F3FC 200D 2642 ; minimally-qualified # 🏋🏼♂ E4.0 man lifting weights: medium-light skin tone
+1F3CB 1F3FD 200D 2642 FE0F ; fully-qualified # 🏋🏽♂️ E4.0 man lifting weights: medium skin tone
+1F3CB 1F3FD 200D 2642 ; minimally-qualified # 🏋🏽♂ E4.0 man lifting weights: medium skin tone
+1F3CB 1F3FE 200D 2642 FE0F ; fully-qualified # 🏋🏾♂️ E4.0 man lifting weights: medium-dark skin tone
+1F3CB 1F3FE 200D 2642 ; minimally-qualified # 🏋🏾♂ E4.0 man lifting weights: medium-dark skin tone
+1F3CB 1F3FF 200D 2642 FE0F ; fully-qualified # 🏋🏿♂️ E4.0 man lifting weights: dark skin tone
+1F3CB 1F3FF 200D 2642 ; minimally-qualified # 🏋🏿♂ E4.0 man lifting weights: dark skin tone
+1F3CB FE0F 200D 2640 FE0F ; fully-qualified # 🏋️♀️ E4.0 woman lifting weights
+1F3CB 200D 2640 FE0F ; unqualified # 🏋♀️ E4.0 woman lifting weights
+1F3CB FE0F 200D 2640 ; minimally-qualified # 🏋️♀ E4.0 woman lifting weights
+1F3CB 200D 2640 ; unqualified # 🏋♀ E4.0 woman lifting weights
+1F3CB 1F3FB 200D 2640 FE0F ; fully-qualified # 🏋🏻♀️ E4.0 woman lifting weights: light skin tone
+1F3CB 1F3FB 200D 2640 ; minimally-qualified # 🏋🏻♀ E4.0 woman lifting weights: light skin tone
+1F3CB 1F3FC 200D 2640 FE0F ; fully-qualified # 🏋🏼♀️ E4.0 woman lifting weights: medium-light skin tone
+1F3CB 1F3FC 200D 2640 ; minimally-qualified # 🏋🏼♀ E4.0 woman lifting weights: medium-light skin tone
+1F3CB 1F3FD 200D 2640 FE0F ; fully-qualified # 🏋🏽♀️ E4.0 woman lifting weights: medium skin tone
+1F3CB 1F3FD 200D 2640 ; minimally-qualified # 🏋🏽♀ E4.0 woman lifting weights: medium skin tone
+1F3CB 1F3FE 200D 2640 FE0F ; fully-qualified # 🏋🏾♀️ E4.0 woman lifting weights: medium-dark skin tone
+1F3CB 1F3FE 200D 2640 ; minimally-qualified # 🏋🏾♀ E4.0 woman lifting weights: medium-dark skin tone
+1F3CB 1F3FF 200D 2640 FE0F ; fully-qualified # 🏋🏿♀️ E4.0 woman lifting weights: dark skin tone
+1F3CB 1F3FF 200D 2640 ; minimally-qualified # 🏋🏿♀ E4.0 woman lifting weights: dark skin tone
+1F6B4 ; fully-qualified # 🚴 E1.0 person biking
+1F6B4 1F3FB ; fully-qualified # 🚴🏻 E1.0 person biking: light skin tone
+1F6B4 1F3FC ; fully-qualified # 🚴🏼 E1.0 person biking: medium-light skin tone
+1F6B4 1F3FD ; fully-qualified # 🚴🏽 E1.0 person biking: medium skin tone
+1F6B4 1F3FE ; fully-qualified # 🚴🏾 E1.0 person biking: medium-dark skin tone
+1F6B4 1F3FF ; fully-qualified # 🚴🏿 E1.0 person biking: dark skin tone
+1F6B4 200D 2642 FE0F ; fully-qualified # 🚴♂️ E4.0 man biking
+1F6B4 200D 2642 ; minimally-qualified # 🚴♂ E4.0 man biking
+1F6B4 1F3FB 200D 2642 FE0F ; fully-qualified # 🚴🏻♂️ E4.0 man biking: light skin tone
+1F6B4 1F3FB 200D 2642 ; minimally-qualified # 🚴🏻♂ E4.0 man biking: light skin tone
+1F6B4 1F3FC 200D 2642 FE0F ; fully-qualified # 🚴🏼♂️ E4.0 man biking: medium-light skin tone
+1F6B4 1F3FC 200D 2642 ; minimally-qualified # 🚴🏼♂ E4.0 man biking: medium-light skin tone
+1F6B4 1F3FD 200D 2642 FE0F ; fully-qualified # 🚴🏽♂️ E4.0 man biking: medium skin tone
+1F6B4 1F3FD 200D 2642 ; minimally-qualified # 🚴🏽♂ E4.0 man biking: medium skin tone
+1F6B4 1F3FE 200D 2642 FE0F ; fully-qualified # 🚴🏾♂️ E4.0 man biking: medium-dark skin tone
+1F6B4 1F3FE 200D 2642 ; minimally-qualified # 🚴🏾♂ E4.0 man biking: medium-dark skin tone
+1F6B4 1F3FF 200D 2642 FE0F ; fully-qualified # 🚴🏿♂️ E4.0 man biking: dark skin tone
+1F6B4 1F3FF 200D 2642 ; minimally-qualified # 🚴🏿♂ E4.0 man biking: dark skin tone
+1F6B4 200D 2640 FE0F ; fully-qualified # 🚴♀️ E4.0 woman biking
+1F6B4 200D 2640 ; minimally-qualified # 🚴♀ E4.0 woman biking
+1F6B4 1F3FB 200D 2640 FE0F ; fully-qualified # 🚴🏻♀️ E4.0 woman biking: light skin tone
+1F6B4 1F3FB 200D 2640 ; minimally-qualified # 🚴🏻♀ E4.0 woman biking: light skin tone
+1F6B4 1F3FC 200D 2640 FE0F ; fully-qualified # 🚴🏼♀️ E4.0 woman biking: medium-light skin tone
+1F6B4 1F3FC 200D 2640 ; minimally-qualified # 🚴🏼♀ E4.0 woman biking: medium-light skin tone
+1F6B4 1F3FD 200D 2640 FE0F ; fully-qualified # 🚴🏽♀️ E4.0 woman biking: medium skin tone
+1F6B4 1F3FD 200D 2640 ; minimally-qualified # 🚴🏽♀ E4.0 woman biking: medium skin tone
+1F6B4 1F3FE 200D 2640 FE0F ; fully-qualified # 🚴🏾♀️ E4.0 woman biking: medium-dark skin tone
+1F6B4 1F3FE 200D 2640 ; minimally-qualified # 🚴🏾♀ E4.0 woman biking: medium-dark skin tone
+1F6B4 1F3FF 200D 2640 FE0F ; fully-qualified # 🚴🏿♀️ E4.0 woman biking: dark skin tone
+1F6B4 1F3FF 200D 2640 ; minimally-qualified # 🚴🏿♀ E4.0 woman biking: dark skin tone
+1F6B5 ; fully-qualified # 🚵 E1.0 person mountain biking
+1F6B5 1F3FB ; fully-qualified # 🚵🏻 E1.0 person mountain biking: light skin tone
+1F6B5 1F3FC ; fully-qualified # 🚵🏼 E1.0 person mountain biking: medium-light skin tone
+1F6B5 1F3FD ; fully-qualified # 🚵🏽 E1.0 person mountain biking: medium skin tone
+1F6B5 1F3FE ; fully-qualified # 🚵🏾 E1.0 person mountain biking: medium-dark skin tone
+1F6B5 1F3FF ; fully-qualified # 🚵🏿 E1.0 person mountain biking: dark skin tone
+1F6B5 200D 2642 FE0F ; fully-qualified # 🚵♂️ E4.0 man mountain biking
+1F6B5 200D 2642 ; minimally-qualified # 🚵♂ E4.0 man mountain biking
+1F6B5 1F3FB 200D 2642 FE0F ; fully-qualified # 🚵🏻♂️ E4.0 man mountain biking: light skin tone
+1F6B5 1F3FB 200D 2642 ; minimally-qualified # 🚵🏻♂ E4.0 man mountain biking: light skin tone
+1F6B5 1F3FC 200D 2642 FE0F ; fully-qualified # 🚵🏼♂️ E4.0 man mountain biking: medium-light skin tone
+1F6B5 1F3FC 200D 2642 ; minimally-qualified # 🚵🏼♂ E4.0 man mountain biking: medium-light skin tone
+1F6B5 1F3FD 200D 2642 FE0F ; fully-qualified # 🚵🏽♂️ E4.0 man mountain biking: medium skin tone
+1F6B5 1F3FD 200D 2642 ; minimally-qualified # 🚵🏽♂ E4.0 man mountain biking: medium skin tone
+1F6B5 1F3FE 200D 2642 FE0F ; fully-qualified # 🚵🏾♂️ E4.0 man mountain biking: medium-dark skin tone
+1F6B5 1F3FE 200D 2642 ; minimally-qualified # 🚵🏾♂ E4.0 man mountain biking: medium-dark skin tone
+1F6B5 1F3FF 200D 2642 FE0F ; fully-qualified # 🚵🏿♂️ E4.0 man mountain biking: dark skin tone
+1F6B5 1F3FF 200D 2642 ; minimally-qualified # 🚵🏿♂ E4.0 man mountain biking: dark skin tone
+1F6B5 200D 2640 FE0F ; fully-qualified # 🚵♀️ E4.0 woman mountain biking
+1F6B5 200D 2640 ; minimally-qualified # 🚵♀ E4.0 woman mountain biking
+1F6B5 1F3FB 200D 2640 FE0F ; fully-qualified # 🚵🏻♀️ E4.0 woman mountain biking: light skin tone
+1F6B5 1F3FB 200D 2640 ; minimally-qualified # 🚵🏻♀ E4.0 woman mountain biking: light skin tone
+1F6B5 1F3FC 200D 2640 FE0F ; fully-qualified # 🚵🏼♀️ E4.0 woman mountain biking: medium-light skin tone
+1F6B5 1F3FC 200D 2640 ; minimally-qualified # 🚵🏼♀ E4.0 woman mountain biking: medium-light skin tone
+1F6B5 1F3FD 200D 2640 FE0F ; fully-qualified # 🚵🏽♀️ E4.0 woman mountain biking: medium skin tone
+1F6B5 1F3FD 200D 2640 ; minimally-qualified # 🚵🏽♀ E4.0 woman mountain biking: medium skin tone
+1F6B5 1F3FE 200D 2640 FE0F ; fully-qualified # 🚵🏾♀️ E4.0 woman mountain biking: medium-dark skin tone
+1F6B5 1F3FE 200D 2640 ; minimally-qualified # 🚵🏾♀ E4.0 woman mountain biking: medium-dark skin tone
+1F6B5 1F3FF 200D 2640 FE0F ; fully-qualified # 🚵🏿♀️ E4.0 woman mountain biking: dark skin tone
+1F6B5 1F3FF 200D 2640 ; minimally-qualified # 🚵🏿♀ E4.0 woman mountain biking: dark skin tone
+1F938 ; fully-qualified # 🤸 E3.0 person cartwheeling
+1F938 1F3FB ; fully-qualified # 🤸🏻 E3.0 person cartwheeling: light skin tone
+1F938 1F3FC ; fully-qualified # 🤸🏼 E3.0 person cartwheeling: medium-light skin tone
+1F938 1F3FD ; fully-qualified # 🤸🏽 E3.0 person cartwheeling: medium skin tone
+1F938 1F3FE ; fully-qualified # 🤸🏾 E3.0 person cartwheeling: medium-dark skin tone
+1F938 1F3FF ; fully-qualified # 🤸🏿 E3.0 person cartwheeling: dark skin tone
+1F938 200D 2642 FE0F ; fully-qualified # 🤸♂️ E4.0 man cartwheeling
+1F938 200D 2642 ; minimally-qualified # 🤸♂ E4.0 man cartwheeling
+1F938 1F3FB 200D 2642 FE0F ; fully-qualified # 🤸🏻♂️ E4.0 man cartwheeling: light skin tone
+1F938 1F3FB 200D 2642 ; minimally-qualified # 🤸🏻♂ E4.0 man cartwheeling: light skin tone
+1F938 1F3FC 200D 2642 FE0F ; fully-qualified # 🤸🏼♂️ E4.0 man cartwheeling: medium-light skin tone
+1F938 1F3FC 200D 2642 ; minimally-qualified # 🤸🏼♂ E4.0 man cartwheeling: medium-light skin tone
+1F938 1F3FD 200D 2642 FE0F ; fully-qualified # 🤸🏽♂️ E4.0 man cartwheeling: medium skin tone
+1F938 1F3FD 200D 2642 ; minimally-qualified # 🤸🏽♂ E4.0 man cartwheeling: medium skin tone
+1F938 1F3FE 200D 2642 FE0F ; fully-qualified # 🤸🏾♂️ E4.0 man cartwheeling: medium-dark skin tone
+1F938 1F3FE 200D 2642 ; minimally-qualified # 🤸🏾♂ E4.0 man cartwheeling: medium-dark skin tone
+1F938 1F3FF 200D 2642 FE0F ; fully-qualified # 🤸🏿♂️ E4.0 man cartwheeling: dark skin tone
+1F938 1F3FF 200D 2642 ; minimally-qualified # 🤸🏿♂ E4.0 man cartwheeling: dark skin tone
+1F938 200D 2640 FE0F ; fully-qualified # 🤸♀️ E4.0 woman cartwheeling
+1F938 200D 2640 ; minimally-qualified # 🤸♀ E4.0 woman cartwheeling
+1F938 1F3FB 200D 2640 FE0F ; fully-qualified # 🤸🏻♀️ E4.0 woman cartwheeling: light skin tone
+1F938 1F3FB 200D 2640 ; minimally-qualified # 🤸🏻♀ E4.0 woman cartwheeling: light skin tone
+1F938 1F3FC 200D 2640 FE0F ; fully-qualified # 🤸🏼♀️ E4.0 woman cartwheeling: medium-light skin tone
+1F938 1F3FC 200D 2640 ; minimally-qualified # 🤸🏼♀ E4.0 woman cartwheeling: medium-light skin tone
+1F938 1F3FD 200D 2640 FE0F ; fully-qualified # 🤸🏽♀️ E4.0 woman cartwheeling: medium skin tone
+1F938 1F3FD 200D 2640 ; minimally-qualified # 🤸🏽♀ E4.0 woman cartwheeling: medium skin tone
+1F938 1F3FE 200D 2640 FE0F ; fully-qualified # 🤸🏾♀️ E4.0 woman cartwheeling: medium-dark skin tone
+1F938 1F3FE 200D 2640 ; minimally-qualified # 🤸🏾♀ E4.0 woman cartwheeling: medium-dark skin tone
+1F938 1F3FF 200D 2640 FE0F ; fully-qualified # 🤸🏿♀️ E4.0 woman cartwheeling: dark skin tone
+1F938 1F3FF 200D 2640 ; minimally-qualified # 🤸🏿♀ E4.0 woman cartwheeling: dark skin tone
+1F93C ; fully-qualified # 🤼 E3.0 people wrestling
+1F93C 200D 2642 FE0F ; fully-qualified # 🤼♂️ E4.0 men wrestling
+1F93C 200D 2642 ; minimally-qualified # 🤼♂ E4.0 men wrestling
+1F93C 200D 2640 FE0F ; fully-qualified # 🤼♀️ E4.0 women wrestling
+1F93C 200D 2640 ; minimally-qualified # 🤼♀ E4.0 women wrestling
+1F93D ; fully-qualified # 🤽 E3.0 person playing water polo
+1F93D 1F3FB ; fully-qualified # 🤽🏻 E3.0 person playing water polo: light skin tone
+1F93D 1F3FC ; fully-qualified # 🤽🏼 E3.0 person playing water polo: medium-light skin tone
+1F93D 1F3FD ; fully-qualified # 🤽🏽 E3.0 person playing water polo: medium skin tone
+1F93D 1F3FE ; fully-qualified # 🤽🏾 E3.0 person playing water polo: medium-dark skin tone
+1F93D 1F3FF ; fully-qualified # 🤽🏿 E3.0 person playing water polo: dark skin tone
+1F93D 200D 2642 FE0F ; fully-qualified # 🤽♂️ E4.0 man playing water polo
+1F93D 200D 2642 ; minimally-qualified # 🤽♂ E4.0 man playing water polo
+1F93D 1F3FB 200D 2642 FE0F ; fully-qualified # 🤽🏻♂️ E4.0 man playing water polo: light skin tone
+1F93D 1F3FB 200D 2642 ; minimally-qualified # 🤽🏻♂ E4.0 man playing water polo: light skin tone
+1F93D 1F3FC 200D 2642 FE0F ; fully-qualified # 🤽🏼♂️ E4.0 man playing water polo: medium-light skin tone
+1F93D 1F3FC 200D 2642 ; minimally-qualified # 🤽🏼♂ E4.0 man playing water polo: medium-light skin tone
+1F93D 1F3FD 200D 2642 FE0F ; fully-qualified # 🤽🏽♂️ E4.0 man playing water polo: medium skin tone
+1F93D 1F3FD 200D 2642 ; minimally-qualified # 🤽🏽♂ E4.0 man playing water polo: medium skin tone
+1F93D 1F3FE 200D 2642 FE0F ; fully-qualified # 🤽🏾♂️ E4.0 man playing water polo: medium-dark skin tone
+1F93D 1F3FE 200D 2642 ; minimally-qualified # 🤽🏾♂ E4.0 man playing water polo: medium-dark skin tone
+1F93D 1F3FF 200D 2642 FE0F ; fully-qualified # 🤽🏿♂️ E4.0 man playing water polo: dark skin tone
+1F93D 1F3FF 200D 2642 ; minimally-qualified # 🤽🏿♂ E4.0 man playing water polo: dark skin tone
+1F93D 200D 2640 FE0F ; fully-qualified # 🤽♀️ E4.0 woman playing water polo
+1F93D 200D 2640 ; minimally-qualified # 🤽♀ E4.0 woman playing water polo
+1F93D 1F3FB 200D 2640 FE0F ; fully-qualified # 🤽🏻♀️ E4.0 woman playing water polo: light skin tone
+1F93D 1F3FB 200D 2640 ; minimally-qualified # 🤽🏻♀ E4.0 woman playing water polo: light skin tone
+1F93D 1F3FC 200D 2640 FE0F ; fully-qualified # 🤽🏼♀️ E4.0 woman playing water polo: medium-light skin tone
+1F93D 1F3FC 200D 2640 ; minimally-qualified # 🤽🏼♀ E4.0 woman playing water polo: medium-light skin tone
+1F93D 1F3FD 200D 2640 FE0F ; fully-qualified # 🤽🏽♀️ E4.0 woman playing water polo: medium skin tone
+1F93D 1F3FD 200D 2640 ; minimally-qualified # 🤽🏽♀ E4.0 woman playing water polo: medium skin tone
+1F93D 1F3FE 200D 2640 FE0F ; fully-qualified # 🤽🏾♀️ E4.0 woman playing water polo: medium-dark skin tone
+1F93D 1F3FE 200D 2640 ; minimally-qualified # 🤽🏾♀ E4.0 woman playing water polo: medium-dark skin tone
+1F93D 1F3FF 200D 2640 FE0F ; fully-qualified # 🤽🏿♀️ E4.0 woman playing water polo: dark skin tone
+1F93D 1F3FF 200D 2640 ; minimally-qualified # 🤽🏿♀ E4.0 woman playing water polo: dark skin tone
+1F93E ; fully-qualified # 🤾 E3.0 person playing handball
+1F93E 1F3FB ; fully-qualified # 🤾🏻 E3.0 person playing handball: light skin tone
+1F93E 1F3FC ; fully-qualified # 🤾🏼 E3.0 person playing handball: medium-light skin tone
+1F93E 1F3FD ; fully-qualified # 🤾🏽 E3.0 person playing handball: medium skin tone
+1F93E 1F3FE ; fully-qualified # 🤾🏾 E3.0 person playing handball: medium-dark skin tone
+1F93E 1F3FF ; fully-qualified # 🤾🏿 E3.0 person playing handball: dark skin tone
+1F93E 200D 2642 FE0F ; fully-qualified # 🤾♂️ E4.0 man playing handball
+1F93E 200D 2642 ; minimally-qualified # 🤾♂ E4.0 man playing handball
+1F93E 1F3FB 200D 2642 FE0F ; fully-qualified # 🤾🏻♂️ E4.0 man playing handball: light skin tone
+1F93E 1F3FB 200D 2642 ; minimally-qualified # 🤾🏻♂ E4.0 man playing handball: light skin tone
+1F93E 1F3FC 200D 2642 FE0F ; fully-qualified # 🤾🏼♂️ E4.0 man playing handball: medium-light skin tone
+1F93E 1F3FC 200D 2642 ; minimally-qualified # 🤾🏼♂ E4.0 man playing handball: medium-light skin tone
+1F93E 1F3FD 200D 2642 FE0F ; fully-qualified # 🤾🏽♂️ E4.0 man playing handball: medium skin tone
+1F93E 1F3FD 200D 2642 ; minimally-qualified # 🤾🏽♂ E4.0 man playing handball: medium skin tone
+1F93E 1F3FE 200D 2642 FE0F ; fully-qualified # 🤾🏾♂️ E4.0 man playing handball: medium-dark skin tone
+1F93E 1F3FE 200D 2642 ; minimally-qualified # 🤾🏾♂ E4.0 man playing handball: medium-dark skin tone
+1F93E 1F3FF 200D 2642 FE0F ; fully-qualified # 🤾🏿♂️ E4.0 man playing handball: dark skin tone
+1F93E 1F3FF 200D 2642 ; minimally-qualified # 🤾🏿♂ E4.0 man playing handball: dark skin tone
+1F93E 200D 2640 FE0F ; fully-qualified # 🤾♀️ E4.0 woman playing handball
+1F93E 200D 2640 ; minimally-qualified # 🤾♀ E4.0 woman playing handball
+1F93E 1F3FB 200D 2640 FE0F ; fully-qualified # 🤾🏻♀️ E4.0 woman playing handball: light skin tone
+1F93E 1F3FB 200D 2640 ; minimally-qualified # 🤾🏻♀ E4.0 woman playing handball: light skin tone
+1F93E 1F3FC 200D 2640 FE0F ; fully-qualified # 🤾🏼♀️ E4.0 woman playing handball: medium-light skin tone
+1F93E 1F3FC 200D 2640 ; minimally-qualified # 🤾🏼♀ E4.0 woman playing handball: medium-light skin tone
+1F93E 1F3FD 200D 2640 FE0F ; fully-qualified # 🤾🏽♀️ E4.0 woman playing handball: medium skin tone
+1F93E 1F3FD 200D 2640 ; minimally-qualified # 🤾🏽♀ E4.0 woman playing handball: medium skin tone
+1F93E 1F3FE 200D 2640 FE0F ; fully-qualified # 🤾🏾♀️ E4.0 woman playing handball: medium-dark skin tone
+1F93E 1F3FE 200D 2640 ; minimally-qualified # 🤾🏾♀ E4.0 woman playing handball: medium-dark skin tone
+1F93E 1F3FF 200D 2640 FE0F ; fully-qualified # 🤾🏿♀️ E4.0 woman playing handball: dark skin tone
+1F93E 1F3FF 200D 2640 ; minimally-qualified # 🤾🏿♀ E4.0 woman playing handball: dark skin tone
+1F939 ; fully-qualified # 🤹 E3.0 person juggling
+1F939 1F3FB ; fully-qualified # 🤹🏻 E3.0 person juggling: light skin tone
+1F939 1F3FC ; fully-qualified # 🤹🏼 E3.0 person juggling: medium-light skin tone
+1F939 1F3FD ; fully-qualified # 🤹🏽 E3.0 person juggling: medium skin tone
+1F939 1F3FE ; fully-qualified # 🤹🏾 E3.0 person juggling: medium-dark skin tone
+1F939 1F3FF ; fully-qualified # 🤹🏿 E3.0 person juggling: dark skin tone
+1F939 200D 2642 FE0F ; fully-qualified # 🤹♂️ E4.0 man juggling
+1F939 200D 2642 ; minimally-qualified # 🤹♂ E4.0 man juggling
+1F939 1F3FB 200D 2642 FE0F ; fully-qualified # 🤹🏻♂️ E4.0 man juggling: light skin tone
+1F939 1F3FB 200D 2642 ; minimally-qualified # 🤹🏻♂ E4.0 man juggling: light skin tone
+1F939 1F3FC 200D 2642 FE0F ; fully-qualified # 🤹🏼♂️ E4.0 man juggling: medium-light skin tone
+1F939 1F3FC 200D 2642 ; minimally-qualified # 🤹🏼♂ E4.0 man juggling: medium-light skin tone
+1F939 1F3FD 200D 2642 FE0F ; fully-qualified # 🤹🏽♂️ E4.0 man juggling: medium skin tone
+1F939 1F3FD 200D 2642 ; minimally-qualified # 🤹🏽♂ E4.0 man juggling: medium skin tone
+1F939 1F3FE 200D 2642 FE0F ; fully-qualified # 🤹🏾♂️ E4.0 man juggling: medium-dark skin tone
+1F939 1F3FE 200D 2642 ; minimally-qualified # 🤹🏾♂ E4.0 man juggling: medium-dark skin tone
+1F939 1F3FF 200D 2642 FE0F ; fully-qualified # 🤹🏿♂️ E4.0 man juggling: dark skin tone
+1F939 1F3FF 200D 2642 ; minimally-qualified # 🤹🏿♂ E4.0 man juggling: dark skin tone
+1F939 200D 2640 FE0F ; fully-qualified # 🤹♀️ E4.0 woman juggling
+1F939 200D 2640 ; minimally-qualified # 🤹♀ E4.0 woman juggling
+1F939 1F3FB 200D 2640 FE0F ; fully-qualified # 🤹🏻♀️ E4.0 woman juggling: light skin tone
+1F939 1F3FB 200D 2640 ; minimally-qualified # 🤹🏻♀ E4.0 woman juggling: light skin tone
+1F939 1F3FC 200D 2640 FE0F ; fully-qualified # 🤹🏼♀️ E4.0 woman juggling: medium-light skin tone
+1F939 1F3FC 200D 2640 ; minimally-qualified # 🤹🏼♀ E4.0 woman juggling: medium-light skin tone
+1F939 1F3FD 200D 2640 FE0F ; fully-qualified # 🤹🏽♀️ E4.0 woman juggling: medium skin tone
+1F939 1F3FD 200D 2640 ; minimally-qualified # 🤹🏽♀ E4.0 woman juggling: medium skin tone
+1F939 1F3FE 200D 2640 FE0F ; fully-qualified # 🤹🏾♀️ E4.0 woman juggling: medium-dark skin tone
+1F939 1F3FE 200D 2640 ; minimally-qualified # 🤹🏾♀ E4.0 woman juggling: medium-dark skin tone
+1F939 1F3FF 200D 2640 FE0F ; fully-qualified # 🤹🏿♀️ E4.0 woman juggling: dark skin tone
+1F939 1F3FF 200D 2640 ; minimally-qualified # 🤹🏿♀ E4.0 woman juggling: dark skin tone
+
+# subgroup: person-resting
+1F9D8 ; fully-qualified # 🧘 E5.0 person in lotus position
+1F9D8 1F3FB ; fully-qualified # 🧘🏻 E5.0 person in lotus position: light skin tone
+1F9D8 1F3FC ; fully-qualified # 🧘🏼 E5.0 person in lotus position: medium-light skin tone
+1F9D8 1F3FD ; fully-qualified # 🧘🏽 E5.0 person in lotus position: medium skin tone
+1F9D8 1F3FE ; fully-qualified # 🧘🏾 E5.0 person in lotus position: medium-dark skin tone
+1F9D8 1F3FF ; fully-qualified # 🧘🏿 E5.0 person in lotus position: dark skin tone
+1F9D8 200D 2642 FE0F ; fully-qualified # 🧘♂️ E5.0 man in lotus position
+1F9D8 200D 2642 ; minimally-qualified # 🧘♂ E5.0 man in lotus position
+1F9D8 1F3FB 200D 2642 FE0F ; fully-qualified # 🧘🏻♂️ E5.0 man in lotus position: light skin tone
+1F9D8 1F3FB 200D 2642 ; minimally-qualified # 🧘🏻♂ E5.0 man in lotus position: light skin tone
+1F9D8 1F3FC 200D 2642 FE0F ; fully-qualified # 🧘🏼♂️ E5.0 man in lotus position: medium-light skin tone
+1F9D8 1F3FC 200D 2642 ; minimally-qualified # 🧘🏼♂ E5.0 man in lotus position: medium-light skin tone
+1F9D8 1F3FD 200D 2642 FE0F ; fully-qualified # 🧘🏽♂️ E5.0 man in lotus position: medium skin tone
+1F9D8 1F3FD 200D 2642 ; minimally-qualified # 🧘🏽♂ E5.0 man in lotus position: medium skin tone
+1F9D8 1F3FE 200D 2642 FE0F ; fully-qualified # 🧘🏾♂️ E5.0 man in lotus position: medium-dark skin tone
+1F9D8 1F3FE 200D 2642 ; minimally-qualified # 🧘🏾♂ E5.0 man in lotus position: medium-dark skin tone
+1F9D8 1F3FF 200D 2642 FE0F ; fully-qualified # 🧘🏿♂️ E5.0 man in lotus position: dark skin tone
+1F9D8 1F3FF 200D 2642 ; minimally-qualified # 🧘🏿♂ E5.0 man in lotus position: dark skin tone
+1F9D8 200D 2640 FE0F ; fully-qualified # 🧘♀️ E5.0 woman in lotus position
+1F9D8 200D 2640 ; minimally-qualified # 🧘♀ E5.0 woman in lotus position
+1F9D8 1F3FB 200D 2640 FE0F ; fully-qualified # 🧘🏻♀️ E5.0 woman in lotus position: light skin tone
+1F9D8 1F3FB 200D 2640 ; minimally-qualified # 🧘🏻♀ E5.0 woman in lotus position: light skin tone
+1F9D8 1F3FC 200D 2640 FE0F ; fully-qualified # 🧘🏼♀️ E5.0 woman in lotus position: medium-light skin tone
+1F9D8 1F3FC 200D 2640 ; minimally-qualified # 🧘🏼♀ E5.0 woman in lotus position: medium-light skin tone
+1F9D8 1F3FD 200D 2640 FE0F ; fully-qualified # 🧘🏽♀️ E5.0 woman in lotus position: medium skin tone
+1F9D8 1F3FD 200D 2640 ; minimally-qualified # 🧘🏽♀ E5.0 woman in lotus position: medium skin tone
+1F9D8 1F3FE 200D 2640 FE0F ; fully-qualified # 🧘🏾♀️ E5.0 woman in lotus position: medium-dark skin tone
+1F9D8 1F3FE 200D 2640 ; minimally-qualified # 🧘🏾♀ E5.0 woman in lotus position: medium-dark skin tone
+1F9D8 1F3FF 200D 2640 FE0F ; fully-qualified # 🧘🏿♀️ E5.0 woman in lotus position: dark skin tone
+1F9D8 1F3FF 200D 2640 ; minimally-qualified # 🧘🏿♀ E5.0 woman in lotus position: dark skin tone
+1F6C0 ; fully-qualified # 🛀 E0.6 person taking bath
+1F6C0 1F3FB ; fully-qualified # 🛀🏻 E1.0 person taking bath: light skin tone
+1F6C0 1F3FC ; fully-qualified # 🛀🏼 E1.0 person taking bath: medium-light skin tone
+1F6C0 1F3FD ; fully-qualified # 🛀🏽 E1.0 person taking bath: medium skin tone
+1F6C0 1F3FE ; fully-qualified # 🛀🏾 E1.0 person taking bath: medium-dark skin tone
+1F6C0 1F3FF ; fully-qualified # 🛀🏿 E1.0 person taking bath: dark skin tone
+1F6CC ; fully-qualified # 🛌 E1.0 person in bed
+1F6CC 1F3FB ; fully-qualified # 🛌🏻 E4.0 person in bed: light skin tone
+1F6CC 1F3FC ; fully-qualified # 🛌🏼 E4.0 person in bed: medium-light skin tone
+1F6CC 1F3FD ; fully-qualified # 🛌🏽 E4.0 person in bed: medium skin tone
+1F6CC 1F3FE ; fully-qualified # 🛌🏾 E4.0 person in bed: medium-dark skin tone
+1F6CC 1F3FF ; fully-qualified # 🛌🏿 E4.0 person in bed: dark skin tone
+
+# subgroup: family
+1F9D1 200D 1F91D 200D 1F9D1 ; fully-qualified # 🧑🤝🧑 E12.0 people holding hands
+1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏻🤝🧑🏻 E12.0 people holding hands: light skin tone
+1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏻🤝🧑🏼 E12.1 people holding hands: light skin tone, medium-light skin tone
+1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏻🤝🧑🏽 E12.1 people holding hands: light skin tone, medium skin tone
+1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏻🤝🧑🏾 E12.1 people holding hands: light skin tone, medium-dark skin tone
+1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏻🤝🧑🏿 E12.1 people holding hands: light skin tone, dark skin tone
+1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏼🤝🧑🏻 E12.0 people holding hands: medium-light skin tone, light skin tone
+1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏼🤝🧑🏼 E12.0 people holding hands: medium-light skin tone
+1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏼🤝🧑🏽 E12.1 people holding hands: medium-light skin tone, medium skin tone
+1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏼🤝🧑🏾 E12.1 people holding hands: medium-light skin tone, medium-dark skin tone
+1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏼🤝🧑🏿 E12.1 people holding hands: medium-light skin tone, dark skin tone
+1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏽🤝🧑🏻 E12.0 people holding hands: medium skin tone, light skin tone
+1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏽🤝🧑🏼 E12.0 people holding hands: medium skin tone, medium-light skin tone
+1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏽🤝🧑🏽 E12.0 people holding hands: medium skin tone
+1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏽🤝🧑🏾 E12.1 people holding hands: medium skin tone, medium-dark skin tone
+1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏽🤝🧑🏿 E12.1 people holding hands: medium skin tone, dark skin tone
+1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏾🤝🧑🏻 E12.0 people holding hands: medium-dark skin tone, light skin tone
+1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏾🤝🧑🏼 E12.0 people holding hands: medium-dark skin tone, medium-light skin tone
+1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏾🤝🧑🏽 E12.0 people holding hands: medium-dark skin tone, medium skin tone
+1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏾🤝🧑🏾 E12.0 people holding hands: medium-dark skin tone
+1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏾🤝🧑🏿 E12.1 people holding hands: medium-dark skin tone, dark skin tone
+1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏿🤝🧑🏻 E12.0 people holding hands: dark skin tone, light skin tone
+1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏿🤝🧑🏼 E12.0 people holding hands: dark skin tone, medium-light skin tone
+1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏿🤝🧑🏽 E12.0 people holding hands: dark skin tone, medium skin tone
+1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏿🤝🧑🏾 E12.0 people holding hands: dark skin tone, medium-dark skin tone
+1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏿🤝🧑🏿 E12.0 people holding hands: dark skin tone
+1F46D ; fully-qualified # 👭 E1.0 women holding hands
+1F46D 1F3FB ; fully-qualified # 👭🏻 E12.0 women holding hands: light skin tone
+1F469 1F3FB 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏻🤝👩🏼 E12.1 women holding hands: light skin tone, medium-light skin tone
+1F469 1F3FB 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏻🤝👩🏽 E12.1 women holding hands: light skin tone, medium skin tone
+1F469 1F3FB 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏻🤝👩🏾 E12.1 women holding hands: light skin tone, medium-dark skin tone
+1F469 1F3FB 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏻🤝👩🏿 E12.1 women holding hands: light skin tone, dark skin tone
+1F469 1F3FC 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏼🤝👩🏻 E12.0 women holding hands: medium-light skin tone, light skin tone
+1F46D 1F3FC ; fully-qualified # 👭🏼 E12.0 women holding hands: medium-light skin tone
+1F469 1F3FC 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏼🤝👩🏽 E12.1 women holding hands: medium-light skin tone, medium skin tone
+1F469 1F3FC 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏼🤝👩🏾 E12.1 women holding hands: medium-light skin tone, medium-dark skin tone
+1F469 1F3FC 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏼🤝👩🏿 E12.1 women holding hands: medium-light skin tone, dark skin tone
+1F469 1F3FD 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏽🤝👩🏻 E12.0 women holding hands: medium skin tone, light skin tone
+1F469 1F3FD 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏽🤝👩🏼 E12.0 women holding hands: medium skin tone, medium-light skin tone
+1F46D 1F3FD ; fully-qualified # 👭🏽 E12.0 women holding hands: medium skin tone
+1F469 1F3FD 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏽🤝👩🏾 E12.1 women holding hands: medium skin tone, medium-dark skin tone
+1F469 1F3FD 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏽🤝👩🏿 E12.1 women holding hands: medium skin tone, dark skin tone
+1F469 1F3FE 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏾🤝👩🏻 E12.0 women holding hands: medium-dark skin tone, light skin tone
+1F469 1F3FE 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏾🤝👩🏼 E12.0 women holding hands: medium-dark skin tone, medium-light skin tone
+1F469 1F3FE 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏾🤝👩🏽 E12.0 women holding hands: medium-dark skin tone, medium skin tone
+1F46D 1F3FE ; fully-qualified # 👭🏾 E12.0 women holding hands: medium-dark skin tone
+1F469 1F3FE 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏾🤝👩🏿 E12.1 women holding hands: medium-dark skin tone, dark skin tone
+1F469 1F3FF 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏿🤝👩🏻 E12.0 women holding hands: dark skin tone, light skin tone
+1F469 1F3FF 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏿🤝👩🏼 E12.0 women holding hands: dark skin tone, medium-light skin tone
+1F469 1F3FF 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏿🤝👩🏽 E12.0 women holding hands: dark skin tone, medium skin tone
+1F469 1F3FF 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏿🤝👩🏾 E12.0 women holding hands: dark skin tone, medium-dark skin tone
+1F46D 1F3FF ; fully-qualified # 👭🏿 E12.0 women holding hands: dark skin tone
+1F46B ; fully-qualified # 👫 E0.6 woman and man holding hands
+1F46B 1F3FB ; fully-qualified # 👫🏻 E12.0 woman and man holding hands: light skin tone
+1F469 1F3FB 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏻🤝👨🏼 E12.0 woman and man holding hands: light skin tone, medium-light skin tone
+1F469 1F3FB 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏻🤝👨🏽 E12.0 woman and man holding hands: light skin tone, medium skin tone
+1F469 1F3FB 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏻🤝👨🏾 E12.0 woman and man holding hands: light skin tone, medium-dark skin tone
+1F469 1F3FB 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏻🤝👨🏿 E12.0 woman and man holding hands: light skin tone, dark skin tone
+1F469 1F3FC 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏼🤝👨🏻 E12.0 woman and man holding hands: medium-light skin tone, light skin tone
+1F46B 1F3FC ; fully-qualified # 👫🏼 E12.0 woman and man holding hands: medium-light skin tone
+1F469 1F3FC 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏼🤝👨🏽 E12.0 woman and man holding hands: medium-light skin tone, medium skin tone
+1F469 1F3FC 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏼🤝👨🏾 E12.0 woman and man holding hands: medium-light skin tone, medium-dark skin tone
+1F469 1F3FC 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏼🤝👨🏿 E12.0 woman and man holding hands: medium-light skin tone, dark skin tone
+1F469 1F3FD 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏽🤝👨🏻 E12.0 woman and man holding hands: medium skin tone, light skin tone
+1F469 1F3FD 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏽🤝👨🏼 E12.0 woman and man holding hands: medium skin tone, medium-light skin tone
+1F46B 1F3FD ; fully-qualified # 👫🏽 E12.0 woman and man holding hands: medium skin tone
+1F469 1F3FD 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏽🤝👨🏾 E12.0 woman and man holding hands: medium skin tone, medium-dark skin tone
+1F469 1F3FD 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏽🤝👨🏿 E12.0 woman and man holding hands: medium skin tone, dark skin tone
+1F469 1F3FE 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏾🤝👨🏻 E12.0 woman and man holding hands: medium-dark skin tone, light skin tone
+1F469 1F3FE 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏾🤝👨🏼 E12.0 woman and man holding hands: medium-dark skin tone, medium-light skin tone
+1F469 1F3FE 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏾🤝👨🏽 E12.0 woman and man holding hands: medium-dark skin tone, medium skin tone
+1F46B 1F3FE ; fully-qualified # 👫🏾 E12.0 woman and man holding hands: medium-dark skin tone
+1F469 1F3FE 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏾🤝👨🏿 E12.0 woman and man holding hands: medium-dark skin tone, dark skin tone
+1F469 1F3FF 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏿🤝👨🏻 E12.0 woman and man holding hands: dark skin tone, light skin tone
+1F469 1F3FF 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏿🤝👨🏼 E12.0 woman and man holding hands: dark skin tone, medium-light skin tone
+1F469 1F3FF 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏿🤝👨🏽 E12.0 woman and man holding hands: dark skin tone, medium skin tone
+1F469 1F3FF 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏿🤝👨🏾 E12.0 woman and man holding hands: dark skin tone, medium-dark skin tone
+1F46B 1F3FF ; fully-qualified # 👫🏿 E12.0 woman and man holding hands: dark skin tone
+1F46C ; fully-qualified # 👬 E1.0 men holding hands
+1F46C 1F3FB ; fully-qualified # 👬🏻 E12.0 men holding hands: light skin tone
+1F468 1F3FB 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏻🤝👨🏼 E12.1 men holding hands: light skin tone, medium-light skin tone
+1F468 1F3FB 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏻🤝👨🏽 E12.1 men holding hands: light skin tone, medium skin tone
+1F468 1F3FB 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏻🤝👨🏾 E12.1 men holding hands: light skin tone, medium-dark skin tone
+1F468 1F3FB 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏻🤝👨🏿 E12.1 men holding hands: light skin tone, dark skin tone
+1F468 1F3FC 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏼🤝👨🏻 E12.0 men holding hands: medium-light skin tone, light skin tone
+1F46C 1F3FC ; fully-qualified # 👬🏼 E12.0 men holding hands: medium-light skin tone
+1F468 1F3FC 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏼🤝👨🏽 E12.1 men holding hands: medium-light skin tone, medium skin tone
+1F468 1F3FC 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏼🤝👨🏾 E12.1 men holding hands: medium-light skin tone, medium-dark skin tone
+1F468 1F3FC 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏼🤝👨🏿 E12.1 men holding hands: medium-light skin tone, dark skin tone
+1F468 1F3FD 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏽🤝👨🏻 E12.0 men holding hands: medium skin tone, light skin tone
+1F468 1F3FD 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏽🤝👨🏼 E12.0 men holding hands: medium skin tone, medium-light skin tone
+1F46C 1F3FD ; fully-qualified # 👬🏽 E12.0 men holding hands: medium skin tone
+1F468 1F3FD 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏽🤝👨🏾 E12.1 men holding hands: medium skin tone, medium-dark skin tone
+1F468 1F3FD 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏽🤝👨🏿 E12.1 men holding hands: medium skin tone, dark skin tone
+1F468 1F3FE 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏾🤝👨🏻 E12.0 men holding hands: medium-dark skin tone, light skin tone
+1F468 1F3FE 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏾🤝👨🏼 E12.0 men holding hands: medium-dark skin tone, medium-light skin tone
+1F468 1F3FE 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏾🤝👨🏽 E12.0 men holding hands: medium-dark skin tone, medium skin tone
+1F46C 1F3FE ; fully-qualified # 👬🏾 E12.0 men holding hands: medium-dark skin tone
+1F468 1F3FE 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏾🤝👨🏿 E12.1 men holding hands: medium-dark skin tone, dark skin tone
+1F468 1F3FF 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏿🤝👨🏻 E12.0 men holding hands: dark skin tone, light skin tone
+1F468 1F3FF 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏿🤝👨🏼 E12.0 men holding hands: dark skin tone, medium-light skin tone
+1F468 1F3FF 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏿🤝👨🏽 E12.0 men holding hands: dark skin tone, medium skin tone
+1F468 1F3FF 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏿🤝👨🏾 E12.0 men holding hands: dark skin tone, medium-dark skin tone
+1F46C 1F3FF ; fully-qualified # 👬🏿 E12.0 men holding hands: dark skin tone
+1F48F ; fully-qualified # 💏 E0.6 kiss
+1F48F 1F3FB ; fully-qualified # 💏🏻 E13.1 kiss: light skin tone
+1F48F 1F3FC ; fully-qualified # 💏🏼 E13.1 kiss: medium-light skin tone
+1F48F 1F3FD ; fully-qualified # 💏🏽 E13.1 kiss: medium skin tone
+1F48F 1F3FE ; fully-qualified # 💏🏾 E13.1 kiss: medium-dark skin tone
+1F48F 1F3FF ; fully-qualified # 💏🏿 E13.1 kiss: dark skin tone
+1F9D1 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏻❤️💋🧑🏼 E13.1 kiss: person, person, light skin tone, medium-light skin tone
+1F9D1 1F3FB 200D 2764 200D 1F48B 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏻❤💋🧑🏼 E13.1 kiss: person, person, light skin tone, medium-light skin tone
+1F9D1 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏻❤️💋🧑🏽 E13.1 kiss: person, person, light skin tone, medium skin tone
+1F9D1 1F3FB 200D 2764 200D 1F48B 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏻❤💋🧑🏽 E13.1 kiss: person, person, light skin tone, medium skin tone
+1F9D1 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏻❤️💋🧑🏾 E13.1 kiss: person, person, light skin tone, medium-dark skin tone
+1F9D1 1F3FB 200D 2764 200D 1F48B 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏻❤💋🧑🏾 E13.1 kiss: person, person, light skin tone, medium-dark skin tone
+1F9D1 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏻❤️💋🧑🏿 E13.1 kiss: person, person, light skin tone, dark skin tone
+1F9D1 1F3FB 200D 2764 200D 1F48B 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏻❤💋🧑🏿 E13.1 kiss: person, person, light skin tone, dark skin tone
+1F9D1 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏼❤️💋🧑🏻 E13.1 kiss: person, person, medium-light skin tone, light skin tone
+1F9D1 1F3FC 200D 2764 200D 1F48B 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏼❤💋🧑🏻 E13.1 kiss: person, person, medium-light skin tone, light skin tone
+1F9D1 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏼❤️💋🧑🏽 E13.1 kiss: person, person, medium-light skin tone, medium skin tone
+1F9D1 1F3FC 200D 2764 200D 1F48B 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏼❤💋🧑🏽 E13.1 kiss: person, person, medium-light skin tone, medium skin tone
+1F9D1 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏼❤️💋🧑🏾 E13.1 kiss: person, person, medium-light skin tone, medium-dark skin tone
+1F9D1 1F3FC 200D 2764 200D 1F48B 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏼❤💋🧑🏾 E13.1 kiss: person, person, medium-light skin tone, medium-dark skin tone
+1F9D1 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏼❤️💋🧑🏿 E13.1 kiss: person, person, medium-light skin tone, dark skin tone
+1F9D1 1F3FC 200D 2764 200D 1F48B 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏼❤💋🧑🏿 E13.1 kiss: person, person, medium-light skin tone, dark skin tone
+1F9D1 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏽❤️💋🧑🏻 E13.1 kiss: person, person, medium skin tone, light skin tone
+1F9D1 1F3FD 200D 2764 200D 1F48B 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏽❤💋🧑🏻 E13.1 kiss: person, person, medium skin tone, light skin tone
+1F9D1 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏽❤️💋🧑🏼 E13.1 kiss: person, person, medium skin tone, medium-light skin tone
+1F9D1 1F3FD 200D 2764 200D 1F48B 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏽❤💋🧑🏼 E13.1 kiss: person, person, medium skin tone, medium-light skin tone
+1F9D1 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏽❤️💋🧑🏾 E13.1 kiss: person, person, medium skin tone, medium-dark skin tone
+1F9D1 1F3FD 200D 2764 200D 1F48B 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏽❤💋🧑🏾 E13.1 kiss: person, person, medium skin tone, medium-dark skin tone
+1F9D1 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏽❤️💋🧑🏿 E13.1 kiss: person, person, medium skin tone, dark skin tone
+1F9D1 1F3FD 200D 2764 200D 1F48B 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏽❤💋🧑🏿 E13.1 kiss: person, person, medium skin tone, dark skin tone
+1F9D1 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏾❤️💋🧑🏻 E13.1 kiss: person, person, medium-dark skin tone, light skin tone
+1F9D1 1F3FE 200D 2764 200D 1F48B 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏾❤💋🧑🏻 E13.1 kiss: person, person, medium-dark skin tone, light skin tone
+1F9D1 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏾❤️💋🧑🏼 E13.1 kiss: person, person, medium-dark skin tone, medium-light skin tone
+1F9D1 1F3FE 200D 2764 200D 1F48B 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏾❤💋🧑🏼 E13.1 kiss: person, person, medium-dark skin tone, medium-light skin tone
+1F9D1 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏾❤️💋🧑🏽 E13.1 kiss: person, person, medium-dark skin tone, medium skin tone
+1F9D1 1F3FE 200D 2764 200D 1F48B 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏾❤💋🧑🏽 E13.1 kiss: person, person, medium-dark skin tone, medium skin tone
+1F9D1 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏾❤️💋🧑🏿 E13.1 kiss: person, person, medium-dark skin tone, dark skin tone
+1F9D1 1F3FE 200D 2764 200D 1F48B 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏾❤💋🧑🏿 E13.1 kiss: person, person, medium-dark skin tone, dark skin tone
+1F9D1 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏿❤️💋🧑🏻 E13.1 kiss: person, person, dark skin tone, light skin tone
+1F9D1 1F3FF 200D 2764 200D 1F48B 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏿❤💋🧑🏻 E13.1 kiss: person, person, dark skin tone, light skin tone
+1F9D1 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏿❤️💋🧑🏼 E13.1 kiss: person, person, dark skin tone, medium-light skin tone
+1F9D1 1F3FF 200D 2764 200D 1F48B 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏿❤💋🧑🏼 E13.1 kiss: person, person, dark skin tone, medium-light skin tone
+1F9D1 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏿❤️💋🧑🏽 E13.1 kiss: person, person, dark skin tone, medium skin tone
+1F9D1 1F3FF 200D 2764 200D 1F48B 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏿❤💋🧑🏽 E13.1 kiss: person, person, dark skin tone, medium skin tone
+1F9D1 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏿❤️💋🧑🏾 E13.1 kiss: person, person, dark skin tone, medium-dark skin tone
+1F9D1 1F3FF 200D 2764 200D 1F48B 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏿❤💋🧑🏾 E13.1 kiss: person, person, dark skin tone, medium-dark skin tone
+1F469 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👩❤️💋👨 E2.0 kiss: woman, man
+1F469 200D 2764 200D 1F48B 200D 1F468 ; minimally-qualified # 👩❤💋👨 E2.0 kiss: woman, man
+1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏻❤️💋👨🏻 E13.1 kiss: woman, man, light skin tone
+1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏻❤💋👨🏻 E13.1 kiss: woman, man, light skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏻❤️💋👨🏼 E13.1 kiss: woman, man, light skin tone, medium-light skin tone
+1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏻❤💋👨🏼 E13.1 kiss: woman, man, light skin tone, medium-light skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏻❤️💋👨🏽 E13.1 kiss: woman, man, light skin tone, medium skin tone
+1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏻❤💋👨🏽 E13.1 kiss: woman, man, light skin tone, medium skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏻❤️💋👨🏾 E13.1 kiss: woman, man, light skin tone, medium-dark skin tone
+1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏻❤💋👨🏾 E13.1 kiss: woman, man, light skin tone, medium-dark skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏻❤️💋👨🏿 E13.1 kiss: woman, man, light skin tone, dark skin tone
+1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏻❤💋👨🏿 E13.1 kiss: woman, man, light skin tone, dark skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏼❤️💋👨🏻 E13.1 kiss: woman, man, medium-light skin tone, light skin tone
+1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏼❤💋👨🏻 E13.1 kiss: woman, man, medium-light skin tone, light skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏼❤️💋👨🏼 E13.1 kiss: woman, man, medium-light skin tone
+1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏼❤💋👨🏼 E13.1 kiss: woman, man, medium-light skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏼❤️💋👨🏽 E13.1 kiss: woman, man, medium-light skin tone, medium skin tone
+1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏼❤💋👨🏽 E13.1 kiss: woman, man, medium-light skin tone, medium skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏼❤️💋👨🏾 E13.1 kiss: woman, man, medium-light skin tone, medium-dark skin tone
+1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏼❤💋👨🏾 E13.1 kiss: woman, man, medium-light skin tone, medium-dark skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏼❤️💋👨🏿 E13.1 kiss: woman, man, medium-light skin tone, dark skin tone
+1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏼❤💋👨🏿 E13.1 kiss: woman, man, medium-light skin tone, dark skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏽❤️💋👨🏻 E13.1 kiss: woman, man, medium skin tone, light skin tone
+1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏽❤💋👨🏻 E13.1 kiss: woman, man, medium skin tone, light skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏽❤️💋👨🏼 E13.1 kiss: woman, man, medium skin tone, medium-light skin tone
+1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏽❤💋👨🏼 E13.1 kiss: woman, man, medium skin tone, medium-light skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏽❤️💋👨🏽 E13.1 kiss: woman, man, medium skin tone
+1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏽❤💋👨🏽 E13.1 kiss: woman, man, medium skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏽❤️💋👨🏾 E13.1 kiss: woman, man, medium skin tone, medium-dark skin tone
+1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏽❤💋👨🏾 E13.1 kiss: woman, man, medium skin tone, medium-dark skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏽❤️💋👨🏿 E13.1 kiss: woman, man, medium skin tone, dark skin tone
+1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏽❤💋👨🏿 E13.1 kiss: woman, man, medium skin tone, dark skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏾❤️💋👨🏻 E13.1 kiss: woman, man, medium-dark skin tone, light skin tone
+1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏾❤💋👨🏻 E13.1 kiss: woman, man, medium-dark skin tone, light skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏾❤️💋👨🏼 E13.1 kiss: woman, man, medium-dark skin tone, medium-light skin tone
+1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏾❤💋👨🏼 E13.1 kiss: woman, man, medium-dark skin tone, medium-light skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏾❤️💋👨🏽 E13.1 kiss: woman, man, medium-dark skin tone, medium skin tone
+1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏾❤💋👨🏽 E13.1 kiss: woman, man, medium-dark skin tone, medium skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏾❤️💋👨🏾 E13.1 kiss: woman, man, medium-dark skin tone
+1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏾❤💋👨🏾 E13.1 kiss: woman, man, medium-dark skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏾❤️💋👨🏿 E13.1 kiss: woman, man, medium-dark skin tone, dark skin tone
+1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏾❤💋👨🏿 E13.1 kiss: woman, man, medium-dark skin tone, dark skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏿❤️💋👨🏻 E13.1 kiss: woman, man, dark skin tone, light skin tone
+1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏿❤💋👨🏻 E13.1 kiss: woman, man, dark skin tone, light skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏿❤️💋👨🏼 E13.1 kiss: woman, man, dark skin tone, medium-light skin tone
+1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏿❤💋👨🏼 E13.1 kiss: woman, man, dark skin tone, medium-light skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏿❤️💋👨🏽 E13.1 kiss: woman, man, dark skin tone, medium skin tone
+1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏿❤💋👨🏽 E13.1 kiss: woman, man, dark skin tone, medium skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏿❤️💋👨🏾 E13.1 kiss: woman, man, dark skin tone, medium-dark skin tone
+1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏿❤💋👨🏾 E13.1 kiss: woman, man, dark skin tone, medium-dark skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏿❤️💋👨🏿 E13.1 kiss: woman, man, dark skin tone
+1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏿❤💋👨🏿 E13.1 kiss: woman, man, dark skin tone
+1F468 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👨❤️💋👨 E2.0 kiss: man, man
+1F468 200D 2764 200D 1F48B 200D 1F468 ; minimally-qualified # 👨❤💋👨 E2.0 kiss: man, man
+1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏻❤️💋👨🏻 E13.1 kiss: man, man, light skin tone
+1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏻❤💋👨🏻 E13.1 kiss: man, man, light skin tone
+1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏻❤️💋👨🏼 E13.1 kiss: man, man, light skin tone, medium-light skin tone
+1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏻❤💋👨🏼 E13.1 kiss: man, man, light skin tone, medium-light skin tone
+1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏻❤️💋👨🏽 E13.1 kiss: man, man, light skin tone, medium skin tone
+1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏻❤💋👨🏽 E13.1 kiss: man, man, light skin tone, medium skin tone
+1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏻❤️💋👨🏾 E13.1 kiss: man, man, light skin tone, medium-dark skin tone
+1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏻❤💋👨🏾 E13.1 kiss: man, man, light skin tone, medium-dark skin tone
+1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏻❤️💋👨🏿 E13.1 kiss: man, man, light skin tone, dark skin tone
+1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏻❤💋👨🏿 E13.1 kiss: man, man, light skin tone, dark skin tone
+1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏼❤️💋👨🏻 E13.1 kiss: man, man, medium-light skin tone, light skin tone
+1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏼❤💋👨🏻 E13.1 kiss: man, man, medium-light skin tone, light skin tone
+1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏼❤️💋👨🏼 E13.1 kiss: man, man, medium-light skin tone
+1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏼❤💋👨🏼 E13.1 kiss: man, man, medium-light skin tone
+1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏼❤️💋👨🏽 E13.1 kiss: man, man, medium-light skin tone, medium skin tone
+1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏼❤💋👨🏽 E13.1 kiss: man, man, medium-light skin tone, medium skin tone
+1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏼❤️💋👨🏾 E13.1 kiss: man, man, medium-light skin tone, medium-dark skin tone
+1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏼❤💋👨🏾 E13.1 kiss: man, man, medium-light skin tone, medium-dark skin tone
+1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏼❤️💋👨🏿 E13.1 kiss: man, man, medium-light skin tone, dark skin tone
+1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏼❤💋👨🏿 E13.1 kiss: man, man, medium-light skin tone, dark skin tone
+1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏽❤️💋👨🏻 E13.1 kiss: man, man, medium skin tone, light skin tone
+1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏽❤💋👨🏻 E13.1 kiss: man, man, medium skin tone, light skin tone
+1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏽❤️💋👨🏼 E13.1 kiss: man, man, medium skin tone, medium-light skin tone
+1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏽❤💋👨🏼 E13.1 kiss: man, man, medium skin tone, medium-light skin tone
+1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏽❤️💋👨🏽 E13.1 kiss: man, man, medium skin tone
+1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏽❤💋👨🏽 E13.1 kiss: man, man, medium skin tone
+1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏽❤️💋👨🏾 E13.1 kiss: man, man, medium skin tone, medium-dark skin tone
+1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏽❤💋👨🏾 E13.1 kiss: man, man, medium skin tone, medium-dark skin tone
+1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏽❤️💋👨🏿 E13.1 kiss: man, man, medium skin tone, dark skin tone
+1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏽❤💋👨🏿 E13.1 kiss: man, man, medium skin tone, dark skin tone
+1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏾❤️💋👨🏻 E13.1 kiss: man, man, medium-dark skin tone, light skin tone
+1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏾❤💋👨🏻 E13.1 kiss: man, man, medium-dark skin tone, light skin tone
+1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏾❤️💋👨🏼 E13.1 kiss: man, man, medium-dark skin tone, medium-light skin tone
+1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏾❤💋👨🏼 E13.1 kiss: man, man, medium-dark skin tone, medium-light skin tone
+1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏾❤️💋👨🏽 E13.1 kiss: man, man, medium-dark skin tone, medium skin tone
+1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏾❤💋👨🏽 E13.1 kiss: man, man, medium-dark skin tone, medium skin tone
+1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏾❤️💋👨🏾 E13.1 kiss: man, man, medium-dark skin tone
+1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏾❤💋👨🏾 E13.1 kiss: man, man, medium-dark skin tone
+1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏾❤️💋👨🏿 E13.1 kiss: man, man, medium-dark skin tone, dark skin tone
+1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏾❤💋👨🏿 E13.1 kiss: man, man, medium-dark skin tone, dark skin tone
+1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏿❤️💋👨🏻 E13.1 kiss: man, man, dark skin tone, light skin tone
+1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏿❤💋👨🏻 E13.1 kiss: man, man, dark skin tone, light skin tone
+1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏿❤️💋👨🏼 E13.1 kiss: man, man, dark skin tone, medium-light skin tone
+1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏿❤💋👨🏼 E13.1 kiss: man, man, dark skin tone, medium-light skin tone
+1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏿❤️💋👨🏽 E13.1 kiss: man, man, dark skin tone, medium skin tone
+1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏿❤💋👨🏽 E13.1 kiss: man, man, dark skin tone, medium skin tone
+1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏿❤️💋👨🏾 E13.1 kiss: man, man, dark skin tone, medium-dark skin tone
+1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏿❤💋👨🏾 E13.1 kiss: man, man, dark skin tone, medium-dark skin tone
+1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏿❤️💋👨🏿 E13.1 kiss: man, man, dark skin tone
+1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏿❤💋👨🏿 E13.1 kiss: man, man, dark skin tone
+1F469 200D 2764 FE0F 200D 1F48B 200D 1F469 ; fully-qualified # 👩❤️💋👩 E2.0 kiss: woman, woman
+1F469 200D 2764 200D 1F48B 200D 1F469 ; minimally-qualified # 👩❤💋👩 E2.0 kiss: woman, woman
+1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏻❤️💋👩🏻 E13.1 kiss: woman, woman, light skin tone
+1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏻❤💋👩🏻 E13.1 kiss: woman, woman, light skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏻❤️💋👩🏼 E13.1 kiss: woman, woman, light skin tone, medium-light skin tone
+1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏻❤💋👩🏼 E13.1 kiss: woman, woman, light skin tone, medium-light skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏻❤️💋👩🏽 E13.1 kiss: woman, woman, light skin tone, medium skin tone
+1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏻❤💋👩🏽 E13.1 kiss: woman, woman, light skin tone, medium skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏻❤️💋👩🏾 E13.1 kiss: woman, woman, light skin tone, medium-dark skin tone
+1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏻❤💋👩🏾 E13.1 kiss: woman, woman, light skin tone, medium-dark skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏻❤️💋👩🏿 E13.1 kiss: woman, woman, light skin tone, dark skin tone
+1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏻❤💋👩🏿 E13.1 kiss: woman, woman, light skin tone, dark skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏼❤️💋👩🏻 E13.1 kiss: woman, woman, medium-light skin tone, light skin tone
+1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏼❤💋👩🏻 E13.1 kiss: woman, woman, medium-light skin tone, light skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏼❤️💋👩🏼 E13.1 kiss: woman, woman, medium-light skin tone
+1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏼❤💋👩🏼 E13.1 kiss: woman, woman, medium-light skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏼❤️💋👩🏽 E13.1 kiss: woman, woman, medium-light skin tone, medium skin tone
+1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏼❤💋👩🏽 E13.1 kiss: woman, woman, medium-light skin tone, medium skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏼❤️💋👩🏾 E13.1 kiss: woman, woman, medium-light skin tone, medium-dark skin tone
+1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏼❤💋👩🏾 E13.1 kiss: woman, woman, medium-light skin tone, medium-dark skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏼❤️💋👩🏿 E13.1 kiss: woman, woman, medium-light skin tone, dark skin tone
+1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏼❤💋👩🏿 E13.1 kiss: woman, woman, medium-light skin tone, dark skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏽❤️💋👩🏻 E13.1 kiss: woman, woman, medium skin tone, light skin tone
+1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏽❤💋👩🏻 E13.1 kiss: woman, woman, medium skin tone, light skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏽❤️💋👩🏼 E13.1 kiss: woman, woman, medium skin tone, medium-light skin tone
+1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏽❤💋👩🏼 E13.1 kiss: woman, woman, medium skin tone, medium-light skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏽❤️💋👩🏽 E13.1 kiss: woman, woman, medium skin tone
+1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏽❤💋👩🏽 E13.1 kiss: woman, woman, medium skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏽❤️💋👩🏾 E13.1 kiss: woman, woman, medium skin tone, medium-dark skin tone
+1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏽❤💋👩🏾 E13.1 kiss: woman, woman, medium skin tone, medium-dark skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏽❤️💋👩🏿 E13.1 kiss: woman, woman, medium skin tone, dark skin tone
+1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏽❤💋👩🏿 E13.1 kiss: woman, woman, medium skin tone, dark skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏾❤️💋👩🏻 E13.1 kiss: woman, woman, medium-dark skin tone, light skin tone
+1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏾❤💋👩🏻 E13.1 kiss: woman, woman, medium-dark skin tone, light skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏾❤️💋👩🏼 E13.1 kiss: woman, woman, medium-dark skin tone, medium-light skin tone
+1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏾❤💋👩🏼 E13.1 kiss: woman, woman, medium-dark skin tone, medium-light skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏾❤️💋👩🏽 E13.1 kiss: woman, woman, medium-dark skin tone, medium skin tone
+1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏾❤💋👩🏽 E13.1 kiss: woman, woman, medium-dark skin tone, medium skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏾❤️💋👩🏾 E13.1 kiss: woman, woman, medium-dark skin tone
+1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏾❤💋👩🏾 E13.1 kiss: woman, woman, medium-dark skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏾❤️💋👩🏿 E13.1 kiss: woman, woman, medium-dark skin tone, dark skin tone
+1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏾❤💋👩🏿 E13.1 kiss: woman, woman, medium-dark skin tone, dark skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏿❤️💋👩🏻 E13.1 kiss: woman, woman, dark skin tone, light skin tone
+1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏿❤💋👩🏻 E13.1 kiss: woman, woman, dark skin tone, light skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏿❤️💋👩🏼 E13.1 kiss: woman, woman, dark skin tone, medium-light skin tone
+1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏿❤💋👩🏼 E13.1 kiss: woman, woman, dark skin tone, medium-light skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏿❤️💋👩🏽 E13.1 kiss: woman, woman, dark skin tone, medium skin tone
+1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏿❤💋👩🏽 E13.1 kiss: woman, woman, dark skin tone, medium skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏿❤️💋👩🏾 E13.1 kiss: woman, woman, dark skin tone, medium-dark skin tone
+1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏿❤💋👩🏾 E13.1 kiss: woman, woman, dark skin tone, medium-dark skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏿❤️💋👩🏿 E13.1 kiss: woman, woman, dark skin tone
+1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏿❤💋👩🏿 E13.1 kiss: woman, woman, dark skin tone
+1F491 ; fully-qualified # 💑 E0.6 couple with heart
+1F491 1F3FB ; fully-qualified # 💑🏻 E13.1 couple with heart: light skin tone
+1F491 1F3FC ; fully-qualified # 💑🏼 E13.1 couple with heart: medium-light skin tone
+1F491 1F3FD ; fully-qualified # 💑🏽 E13.1 couple with heart: medium skin tone
+1F491 1F3FE ; fully-qualified # 💑🏾 E13.1 couple with heart: medium-dark skin tone
+1F491 1F3FF ; fully-qualified # 💑🏿 E13.1 couple with heart: dark skin tone
+1F9D1 1F3FB 200D 2764 FE0F 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏻❤️🧑🏼 E13.1 couple with heart: person, person, light skin tone, medium-light skin tone
+1F9D1 1F3FB 200D 2764 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏻❤🧑🏼 E13.1 couple with heart: person, person, light skin tone, medium-light skin tone
+1F9D1 1F3FB 200D 2764 FE0F 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏻❤️🧑🏽 E13.1 couple with heart: person, person, light skin tone, medium skin tone
+1F9D1 1F3FB 200D 2764 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏻❤🧑🏽 E13.1 couple with heart: person, person, light skin tone, medium skin tone
+1F9D1 1F3FB 200D 2764 FE0F 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏻❤️🧑🏾 E13.1 couple with heart: person, person, light skin tone, medium-dark skin tone
+1F9D1 1F3FB 200D 2764 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏻❤🧑🏾 E13.1 couple with heart: person, person, light skin tone, medium-dark skin tone
+1F9D1 1F3FB 200D 2764 FE0F 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏻❤️🧑🏿 E13.1 couple with heart: person, person, light skin tone, dark skin tone
+1F9D1 1F3FB 200D 2764 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏻❤🧑🏿 E13.1 couple with heart: person, person, light skin tone, dark skin tone
+1F9D1 1F3FC 200D 2764 FE0F 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏼❤️🧑🏻 E13.1 couple with heart: person, person, medium-light skin tone, light skin tone
+1F9D1 1F3FC 200D 2764 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏼❤🧑🏻 E13.1 couple with heart: person, person, medium-light skin tone, light skin tone
+1F9D1 1F3FC 200D 2764 FE0F 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏼❤️🧑🏽 E13.1 couple with heart: person, person, medium-light skin tone, medium skin tone
+1F9D1 1F3FC 200D 2764 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏼❤🧑🏽 E13.1 couple with heart: person, person, medium-light skin tone, medium skin tone
+1F9D1 1F3FC 200D 2764 FE0F 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏼❤️🧑🏾 E13.1 couple with heart: person, person, medium-light skin tone, medium-dark skin tone
+1F9D1 1F3FC 200D 2764 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏼❤🧑🏾 E13.1 couple with heart: person, person, medium-light skin tone, medium-dark skin tone
+1F9D1 1F3FC 200D 2764 FE0F 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏼❤️🧑🏿 E13.1 couple with heart: person, person, medium-light skin tone, dark skin tone
+1F9D1 1F3FC 200D 2764 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏼❤🧑🏿 E13.1 couple with heart: person, person, medium-light skin tone, dark skin tone
+1F9D1 1F3FD 200D 2764 FE0F 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏽❤️🧑🏻 E13.1 couple with heart: person, person, medium skin tone, light skin tone
+1F9D1 1F3FD 200D 2764 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏽❤🧑🏻 E13.1 couple with heart: person, person, medium skin tone, light skin tone
+1F9D1 1F3FD 200D 2764 FE0F 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏽❤️🧑🏼 E13.1 couple with heart: person, person, medium skin tone, medium-light skin tone
+1F9D1 1F3FD 200D 2764 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏽❤🧑🏼 E13.1 couple with heart: person, person, medium skin tone, medium-light skin tone
+1F9D1 1F3FD 200D 2764 FE0F 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏽❤️🧑🏾 E13.1 couple with heart: person, person, medium skin tone, medium-dark skin tone
+1F9D1 1F3FD 200D 2764 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏽❤🧑🏾 E13.1 couple with heart: person, person, medium skin tone, medium-dark skin tone
+1F9D1 1F3FD 200D 2764 FE0F 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏽❤️🧑🏿 E13.1 couple with heart: person, person, medium skin tone, dark skin tone
+1F9D1 1F3FD 200D 2764 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏽❤🧑🏿 E13.1 couple with heart: person, person, medium skin tone, dark skin tone
+1F9D1 1F3FE 200D 2764 FE0F 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏾❤️🧑🏻 E13.1 couple with heart: person, person, medium-dark skin tone, light skin tone
+1F9D1 1F3FE 200D 2764 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏾❤🧑🏻 E13.1 couple with heart: person, person, medium-dark skin tone, light skin tone
+1F9D1 1F3FE 200D 2764 FE0F 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏾❤️🧑🏼 E13.1 couple with heart: person, person, medium-dark skin tone, medium-light skin tone
+1F9D1 1F3FE 200D 2764 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏾❤🧑🏼 E13.1 couple with heart: person, person, medium-dark skin tone, medium-light skin tone
+1F9D1 1F3FE 200D 2764 FE0F 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏾❤️🧑🏽 E13.1 couple with heart: person, person, medium-dark skin tone, medium skin tone
+1F9D1 1F3FE 200D 2764 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏾❤🧑🏽 E13.1 couple with heart: person, person, medium-dark skin tone, medium skin tone
+1F9D1 1F3FE 200D 2764 FE0F 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏾❤️🧑🏿 E13.1 couple with heart: person, person, medium-dark skin tone, dark skin tone
+1F9D1 1F3FE 200D 2764 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏾❤🧑🏿 E13.1 couple with heart: person, person, medium-dark skin tone, dark skin tone
+1F9D1 1F3FF 200D 2764 FE0F 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏿❤️🧑🏻 E13.1 couple with heart: person, person, dark skin tone, light skin tone
+1F9D1 1F3FF 200D 2764 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏿❤🧑🏻 E13.1 couple with heart: person, person, dark skin tone, light skin tone
+1F9D1 1F3FF 200D 2764 FE0F 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏿❤️🧑🏼 E13.1 couple with heart: person, person, dark skin tone, medium-light skin tone
+1F9D1 1F3FF 200D 2764 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏿❤🧑🏼 E13.1 couple with heart: person, person, dark skin tone, medium-light skin tone
+1F9D1 1F3FF 200D 2764 FE0F 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏿❤️🧑🏽 E13.1 couple with heart: person, person, dark skin tone, medium skin tone
+1F9D1 1F3FF 200D 2764 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏿❤🧑🏽 E13.1 couple with heart: person, person, dark skin tone, medium skin tone
+1F9D1 1F3FF 200D 2764 FE0F 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏿❤️🧑🏾 E13.1 couple with heart: person, person, dark skin tone, medium-dark skin tone
+1F9D1 1F3FF 200D 2764 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏿❤🧑🏾 E13.1 couple with heart: person, person, dark skin tone, medium-dark skin tone
+1F469 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👩❤️👨 E2.0 couple with heart: woman, man
+1F469 200D 2764 200D 1F468 ; minimally-qualified # 👩❤👨 E2.0 couple with heart: woman, man
+1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏻❤️👨🏻 E13.1 couple with heart: woman, man, light skin tone
+1F469 1F3FB 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏻❤👨🏻 E13.1 couple with heart: woman, man, light skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏻❤️👨🏼 E13.1 couple with heart: woman, man, light skin tone, medium-light skin tone
+1F469 1F3FB 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏻❤👨🏼 E13.1 couple with heart: woman, man, light skin tone, medium-light skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏻❤️👨🏽 E13.1 couple with heart: woman, man, light skin tone, medium skin tone
+1F469 1F3FB 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏻❤👨🏽 E13.1 couple with heart: woman, man, light skin tone, medium skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏻❤️👨🏾 E13.1 couple with heart: woman, man, light skin tone, medium-dark skin tone
+1F469 1F3FB 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏻❤👨🏾 E13.1 couple with heart: woman, man, light skin tone, medium-dark skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏻❤️👨🏿 E13.1 couple with heart: woman, man, light skin tone, dark skin tone
+1F469 1F3FB 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏻❤👨🏿 E13.1 couple with heart: woman, man, light skin tone, dark skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏼❤️👨🏻 E13.1 couple with heart: woman, man, medium-light skin tone, light skin tone
+1F469 1F3FC 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏼❤👨🏻 E13.1 couple with heart: woman, man, medium-light skin tone, light skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏼❤️👨🏼 E13.1 couple with heart: woman, man, medium-light skin tone
+1F469 1F3FC 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏼❤👨🏼 E13.1 couple with heart: woman, man, medium-light skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏼❤️👨🏽 E13.1 couple with heart: woman, man, medium-light skin tone, medium skin tone
+1F469 1F3FC 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏼❤👨🏽 E13.1 couple with heart: woman, man, medium-light skin tone, medium skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏼❤️👨🏾 E13.1 couple with heart: woman, man, medium-light skin tone, medium-dark skin tone
+1F469 1F3FC 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏼❤👨🏾 E13.1 couple with heart: woman, man, medium-light skin tone, medium-dark skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏼❤️👨🏿 E13.1 couple with heart: woman, man, medium-light skin tone, dark skin tone
+1F469 1F3FC 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏼❤👨🏿 E13.1 couple with heart: woman, man, medium-light skin tone, dark skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏽❤️👨🏻 E13.1 couple with heart: woman, man, medium skin tone, light skin tone
+1F469 1F3FD 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏽❤👨🏻 E13.1 couple with heart: woman, man, medium skin tone, light skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏽❤️👨🏼 E13.1 couple with heart: woman, man, medium skin tone, medium-light skin tone
+1F469 1F3FD 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏽❤👨🏼 E13.1 couple with heart: woman, man, medium skin tone, medium-light skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏽❤️👨🏽 E13.1 couple with heart: woman, man, medium skin tone
+1F469 1F3FD 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏽❤👨🏽 E13.1 couple with heart: woman, man, medium skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏽❤️👨🏾 E13.1 couple with heart: woman, man, medium skin tone, medium-dark skin tone
+1F469 1F3FD 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏽❤👨🏾 E13.1 couple with heart: woman, man, medium skin tone, medium-dark skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏽❤️👨🏿 E13.1 couple with heart: woman, man, medium skin tone, dark skin tone
+1F469 1F3FD 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏽❤👨🏿 E13.1 couple with heart: woman, man, medium skin tone, dark skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏾❤️👨🏻 E13.1 couple with heart: woman, man, medium-dark skin tone, light skin tone
+1F469 1F3FE 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏾❤👨🏻 E13.1 couple with heart: woman, man, medium-dark skin tone, light skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏾❤️👨🏼 E13.1 couple with heart: woman, man, medium-dark skin tone, medium-light skin tone
+1F469 1F3FE 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏾❤👨🏼 E13.1 couple with heart: woman, man, medium-dark skin tone, medium-light skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏾❤️👨🏽 E13.1 couple with heart: woman, man, medium-dark skin tone, medium skin tone
+1F469 1F3FE 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏾❤👨🏽 E13.1 couple with heart: woman, man, medium-dark skin tone, medium skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏾❤️👨🏾 E13.1 couple with heart: woman, man, medium-dark skin tone
+1F469 1F3FE 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏾❤👨🏾 E13.1 couple with heart: woman, man, medium-dark skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏾❤️👨🏿 E13.1 couple with heart: woman, man, medium-dark skin tone, dark skin tone
+1F469 1F3FE 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏾❤👨🏿 E13.1 couple with heart: woman, man, medium-dark skin tone, dark skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏿❤️👨🏻 E13.1 couple with heart: woman, man, dark skin tone, light skin tone
+1F469 1F3FF 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏿❤👨🏻 E13.1 couple with heart: woman, man, dark skin tone, light skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏿❤️👨🏼 E13.1 couple with heart: woman, man, dark skin tone, medium-light skin tone
+1F469 1F3FF 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏿❤👨🏼 E13.1 couple with heart: woman, man, dark skin tone, medium-light skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏿❤️👨🏽 E13.1 couple with heart: woman, man, dark skin tone, medium skin tone
+1F469 1F3FF 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏿❤👨🏽 E13.1 couple with heart: woman, man, dark skin tone, medium skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏿❤️👨🏾 E13.1 couple with heart: woman, man, dark skin tone, medium-dark skin tone
+1F469 1F3FF 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏿❤👨🏾 E13.1 couple with heart: woman, man, dark skin tone, medium-dark skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏿❤️👨🏿 E13.1 couple with heart: woman, man, dark skin tone
+1F469 1F3FF 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏿❤👨🏿 E13.1 couple with heart: woman, man, dark skin tone
+1F468 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👨❤️👨 E2.0 couple with heart: man, man
+1F468 200D 2764 200D 1F468 ; minimally-qualified # 👨❤👨 E2.0 couple with heart: man, man
+1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏻❤️👨🏻 E13.1 couple with heart: man, man, light skin tone
+1F468 1F3FB 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏻❤👨🏻 E13.1 couple with heart: man, man, light skin tone
+1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏻❤️👨🏼 E13.1 couple with heart: man, man, light skin tone, medium-light skin tone
+1F468 1F3FB 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏻❤👨🏼 E13.1 couple with heart: man, man, light skin tone, medium-light skin tone
+1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏻❤️👨🏽 E13.1 couple with heart: man, man, light skin tone, medium skin tone
+1F468 1F3FB 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏻❤👨🏽 E13.1 couple with heart: man, man, light skin tone, medium skin tone
+1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏻❤️👨🏾 E13.1 couple with heart: man, man, light skin tone, medium-dark skin tone
+1F468 1F3FB 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏻❤👨🏾 E13.1 couple with heart: man, man, light skin tone, medium-dark skin tone
+1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏻❤️👨🏿 E13.1 couple with heart: man, man, light skin tone, dark skin tone
+1F468 1F3FB 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏻❤👨🏿 E13.1 couple with heart: man, man, light skin tone, dark skin tone
+1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏼❤️👨🏻 E13.1 couple with heart: man, man, medium-light skin tone, light skin tone
+1F468 1F3FC 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏼❤👨🏻 E13.1 couple with heart: man, man, medium-light skin tone, light skin tone
+1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏼❤️👨🏼 E13.1 couple with heart: man, man, medium-light skin tone
+1F468 1F3FC 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏼❤👨🏼 E13.1 couple with heart: man, man, medium-light skin tone
+1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏼❤️👨🏽 E13.1 couple with heart: man, man, medium-light skin tone, medium skin tone
+1F468 1F3FC 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏼❤👨🏽 E13.1 couple with heart: man, man, medium-light skin tone, medium skin tone
+1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏼❤️👨🏾 E13.1 couple with heart: man, man, medium-light skin tone, medium-dark skin tone
+1F468 1F3FC 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏼❤👨🏾 E13.1 couple with heart: man, man, medium-light skin tone, medium-dark skin tone
+1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏼❤️👨🏿 E13.1 couple with heart: man, man, medium-light skin tone, dark skin tone
+1F468 1F3FC 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏼❤👨🏿 E13.1 couple with heart: man, man, medium-light skin tone, dark skin tone
+1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏽❤️👨🏻 E13.1 couple with heart: man, man, medium skin tone, light skin tone
+1F468 1F3FD 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏽❤👨🏻 E13.1 couple with heart: man, man, medium skin tone, light skin tone
+1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏽❤️👨🏼 E13.1 couple with heart: man, man, medium skin tone, medium-light skin tone
+1F468 1F3FD 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏽❤👨🏼 E13.1 couple with heart: man, man, medium skin tone, medium-light skin tone
+1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏽❤️👨🏽 E13.1 couple with heart: man, man, medium skin tone
+1F468 1F3FD 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏽❤👨🏽 E13.1 couple with heart: man, man, medium skin tone
+1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏽❤️👨🏾 E13.1 couple with heart: man, man, medium skin tone, medium-dark skin tone
+1F468 1F3FD 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏽❤👨🏾 E13.1 couple with heart: man, man, medium skin tone, medium-dark skin tone
+1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏽❤️👨🏿 E13.1 couple with heart: man, man, medium skin tone, dark skin tone
+1F468 1F3FD 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏽❤👨🏿 E13.1 couple with heart: man, man, medium skin tone, dark skin tone
+1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏾❤️👨🏻 E13.1 couple with heart: man, man, medium-dark skin tone, light skin tone
+1F468 1F3FE 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏾❤👨🏻 E13.1 couple with heart: man, man, medium-dark skin tone, light skin tone
+1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏾❤️👨🏼 E13.1 couple with heart: man, man, medium-dark skin tone, medium-light skin tone
+1F468 1F3FE 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏾❤👨🏼 E13.1 couple with heart: man, man, medium-dark skin tone, medium-light skin tone
+1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏾❤️👨🏽 E13.1 couple with heart: man, man, medium-dark skin tone, medium skin tone
+1F468 1F3FE 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏾❤👨🏽 E13.1 couple with heart: man, man, medium-dark skin tone, medium skin tone
+1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏾❤️👨🏾 E13.1 couple with heart: man, man, medium-dark skin tone
+1F468 1F3FE 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏾❤👨🏾 E13.1 couple with heart: man, man, medium-dark skin tone
+1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏾❤️👨🏿 E13.1 couple with heart: man, man, medium-dark skin tone, dark skin tone
+1F468 1F3FE 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏾❤👨🏿 E13.1 couple with heart: man, man, medium-dark skin tone, dark skin tone
+1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏿❤️👨🏻 E13.1 couple with heart: man, man, dark skin tone, light skin tone
+1F468 1F3FF 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏿❤👨🏻 E13.1 couple with heart: man, man, dark skin tone, light skin tone
+1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏿❤️👨🏼 E13.1 couple with heart: man, man, dark skin tone, medium-light skin tone
+1F468 1F3FF 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏿❤👨🏼 E13.1 couple with heart: man, man, dark skin tone, medium-light skin tone
+1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏿❤️👨🏽 E13.1 couple with heart: man, man, dark skin tone, medium skin tone
+1F468 1F3FF 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏿❤👨🏽 E13.1 couple with heart: man, man, dark skin tone, medium skin tone
+1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏿❤️👨🏾 E13.1 couple with heart: man, man, dark skin tone, medium-dark skin tone
+1F468 1F3FF 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏿❤👨🏾 E13.1 couple with heart: man, man, dark skin tone, medium-dark skin tone
+1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏿❤️👨🏿 E13.1 couple with heart: man, man, dark skin tone
+1F468 1F3FF 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏿❤👨🏿 E13.1 couple with heart: man, man, dark skin tone
+1F469 200D 2764 FE0F 200D 1F469 ; fully-qualified # 👩❤️👩 E2.0 couple with heart: woman, woman
+1F469 200D 2764 200D 1F469 ; minimally-qualified # 👩❤👩 E2.0 couple with heart: woman, woman
+1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏻❤️👩🏻 E13.1 couple with heart: woman, woman, light skin tone
+1F469 1F3FB 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏻❤👩🏻 E13.1 couple with heart: woman, woman, light skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏻❤️👩🏼 E13.1 couple with heart: woman, woman, light skin tone, medium-light skin tone
+1F469 1F3FB 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏻❤👩🏼 E13.1 couple with heart: woman, woman, light skin tone, medium-light skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏻❤️👩🏽 E13.1 couple with heart: woman, woman, light skin tone, medium skin tone
+1F469 1F3FB 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏻❤👩🏽 E13.1 couple with heart: woman, woman, light skin tone, medium skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏻❤️👩🏾 E13.1 couple with heart: woman, woman, light skin tone, medium-dark skin tone
+1F469 1F3FB 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏻❤👩🏾 E13.1 couple with heart: woman, woman, light skin tone, medium-dark skin tone
+1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏻❤️👩🏿 E13.1 couple with heart: woman, woman, light skin tone, dark skin tone
+1F469 1F3FB 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏻❤👩🏿 E13.1 couple with heart: woman, woman, light skin tone, dark skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏼❤️👩🏻 E13.1 couple with heart: woman, woman, medium-light skin tone, light skin tone
+1F469 1F3FC 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏼❤👩🏻 E13.1 couple with heart: woman, woman, medium-light skin tone, light skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏼❤️👩🏼 E13.1 couple with heart: woman, woman, medium-light skin tone
+1F469 1F3FC 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏼❤👩🏼 E13.1 couple with heart: woman, woman, medium-light skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏼❤️👩🏽 E13.1 couple with heart: woman, woman, medium-light skin tone, medium skin tone
+1F469 1F3FC 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏼❤👩🏽 E13.1 couple with heart: woman, woman, medium-light skin tone, medium skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏼❤️👩🏾 E13.1 couple with heart: woman, woman, medium-light skin tone, medium-dark skin tone
+1F469 1F3FC 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏼❤👩🏾 E13.1 couple with heart: woman, woman, medium-light skin tone, medium-dark skin tone
+1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏼❤️👩🏿 E13.1 couple with heart: woman, woman, medium-light skin tone, dark skin tone
+1F469 1F3FC 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏼❤👩🏿 E13.1 couple with heart: woman, woman, medium-light skin tone, dark skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏽❤️👩🏻 E13.1 couple with heart: woman, woman, medium skin tone, light skin tone
+1F469 1F3FD 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏽❤👩🏻 E13.1 couple with heart: woman, woman, medium skin tone, light skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏽❤️👩🏼 E13.1 couple with heart: woman, woman, medium skin tone, medium-light skin tone
+1F469 1F3FD 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏽❤👩🏼 E13.1 couple with heart: woman, woman, medium skin tone, medium-light skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏽❤️👩🏽 E13.1 couple with heart: woman, woman, medium skin tone
+1F469 1F3FD 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏽❤👩🏽 E13.1 couple with heart: woman, woman, medium skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏽❤️👩🏾 E13.1 couple with heart: woman, woman, medium skin tone, medium-dark skin tone
+1F469 1F3FD 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏽❤👩🏾 E13.1 couple with heart: woman, woman, medium skin tone, medium-dark skin tone
+1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏽❤️👩🏿 E13.1 couple with heart: woman, woman, medium skin tone, dark skin tone
+1F469 1F3FD 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏽❤👩🏿 E13.1 couple with heart: woman, woman, medium skin tone, dark skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏾❤️👩🏻 E13.1 couple with heart: woman, woman, medium-dark skin tone, light skin tone
+1F469 1F3FE 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏾❤👩🏻 E13.1 couple with heart: woman, woman, medium-dark skin tone, light skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏾❤️👩🏼 E13.1 couple with heart: woman, woman, medium-dark skin tone, medium-light skin tone
+1F469 1F3FE 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏾❤👩🏼 E13.1 couple with heart: woman, woman, medium-dark skin tone, medium-light skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏾❤️👩🏽 E13.1 couple with heart: woman, woman, medium-dark skin tone, medium skin tone
+1F469 1F3FE 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏾❤👩🏽 E13.1 couple with heart: woman, woman, medium-dark skin tone, medium skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏾❤️👩🏾 E13.1 couple with heart: woman, woman, medium-dark skin tone
+1F469 1F3FE 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏾❤👩🏾 E13.1 couple with heart: woman, woman, medium-dark skin tone
+1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏾❤️👩🏿 E13.1 couple with heart: woman, woman, medium-dark skin tone, dark skin tone
+1F469 1F3FE 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏾❤👩🏿 E13.1 couple with heart: woman, woman, medium-dark skin tone, dark skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏿❤️👩🏻 E13.1 couple with heart: woman, woman, dark skin tone, light skin tone
+1F469 1F3FF 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏿❤👩🏻 E13.1 couple with heart: woman, woman, dark skin tone, light skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏿❤️👩🏼 E13.1 couple with heart: woman, woman, dark skin tone, medium-light skin tone
+1F469 1F3FF 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏿❤👩🏼 E13.1 couple with heart: woman, woman, dark skin tone, medium-light skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏿❤️👩🏽 E13.1 couple with heart: woman, woman, dark skin tone, medium skin tone
+1F469 1F3FF 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏿❤👩🏽 E13.1 couple with heart: woman, woman, dark skin tone, medium skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏿❤️👩🏾 E13.1 couple with heart: woman, woman, dark skin tone, medium-dark skin tone
+1F469 1F3FF 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏿❤👩🏾 E13.1 couple with heart: woman, woman, dark skin tone, medium-dark skin tone
+1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏿❤️👩🏿 E13.1 couple with heart: woman, woman, dark skin tone
+1F469 1F3FF 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏿❤👩🏿 E13.1 couple with heart: woman, woman, dark skin tone
+1F468 200D 1F469 200D 1F466 ; fully-qualified # 👨👩👦 E2.0 family: man, woman, boy
+1F468 200D 1F469 200D 1F467 ; fully-qualified # 👨👩👧 E2.0 family: man, woman, girl
+1F468 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👨👩👧👦 E2.0 family: man, woman, girl, boy
+1F468 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👨👩👦👦 E2.0 family: man, woman, boy, boy
+1F468 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👨👩👧👧 E2.0 family: man, woman, girl, girl
+1F468 200D 1F468 200D 1F466 ; fully-qualified # 👨👨👦 E2.0 family: man, man, boy
+1F468 200D 1F468 200D 1F467 ; fully-qualified # 👨👨👧 E2.0 family: man, man, girl
+1F468 200D 1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨👨👧👦 E2.0 family: man, man, girl, boy
+1F468 200D 1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨👨👦👦 E2.0 family: man, man, boy, boy
+1F468 200D 1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨👨👧👧 E2.0 family: man, man, girl, girl
+1F469 200D 1F469 200D 1F466 ; fully-qualified # 👩👩👦 E2.0 family: woman, woman, boy
+1F469 200D 1F469 200D 1F467 ; fully-qualified # 👩👩👧 E2.0 family: woman, woman, girl
+1F469 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩👩👧👦 E2.0 family: woman, woman, girl, boy
+1F469 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩👩👦👦 E2.0 family: woman, woman, boy, boy
+1F469 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩👩👧👧 E2.0 family: woman, woman, girl, girl
+1F468 200D 1F466 ; fully-qualified # 👨👦 E4.0 family: man, boy
+1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨👦👦 E4.0 family: man, boy, boy
+1F468 200D 1F467 ; fully-qualified # 👨👧 E4.0 family: man, girl
+1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨👧👦 E4.0 family: man, girl, boy
+1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨👧👧 E4.0 family: man, girl, girl
+1F469 200D 1F466 ; fully-qualified # 👩👦 E4.0 family: woman, boy
+1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩👦👦 E4.0 family: woman, boy, boy
+1F469 200D 1F467 ; fully-qualified # 👩👧 E4.0 family: woman, girl
+1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩👧👦 E4.0 family: woman, girl, boy
+1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩👧👧 E4.0 family: woman, girl, girl
+
+# subgroup: person-symbol
+1F5E3 FE0F ; fully-qualified # 🗣️ E0.7 speaking head
+1F5E3 ; unqualified # 🗣 E0.7 speaking head
+1F464 ; fully-qualified # 👤 E0.6 bust in silhouette
+1F465 ; fully-qualified # 👥 E1.0 busts in silhouette
+1FAC2 ; fully-qualified # 🫂 E13.0 people hugging
+1F46A ; fully-qualified # 👪 E0.6 family
+1F9D1 200D 1F9D1 200D 1F9D2 ; fully-qualified # 🧑🧑🧒 E15.1 family: adult, adult, child
+1F9D1 200D 1F9D1 200D 1F9D2 200D 1F9D2 ; fully-qualified # 🧑🧑🧒🧒 E15.1 family: adult, adult, child, child
+1F9D1 200D 1F9D2 ; fully-qualified # 🧑🧒 E15.1 family: adult, child
+1F9D1 200D 1F9D2 200D 1F9D2 ; fully-qualified # 🧑🧒🧒 E15.1 family: adult, child, child
+1F463 ; fully-qualified # 👣 E0.6 footprints
+1FAC6 ; fully-qualified # E16.0 fingerprint
+
+# People & Body subtotal: 3291
+# People & Body subtotal: 561 w/o modifiers
+
+# group: Component
+
+# subgroup: skin-tone
+1F3FB ; component # 🏻 E1.0 light skin tone
+1F3FC ; component # 🏼 E1.0 medium-light skin tone
+1F3FD ; component # 🏽 E1.0 medium skin tone
+1F3FE ; component # 🏾 E1.0 medium-dark skin tone
+1F3FF ; component # 🏿 E1.0 dark skin tone
+
+# subgroup: hair-style
+1F9B0 ; component # 🦰 E11.0 red hair
+1F9B1 ; component # 🦱 E11.0 curly hair
+1F9B3 ; component # 🦳 E11.0 white hair
+1F9B2 ; component # 🦲 E11.0 bald
+
+# Component subtotal: 9
+# Component subtotal: 4 w/o modifiers
+
+# group: Animals & Nature
+
+# subgroup: animal-mammal
+1F435 ; fully-qualified # 🐵 E0.6 monkey face
+1F412 ; fully-qualified # 🐒 E0.6 monkey
+1F98D ; fully-qualified # 🦍 E3.0 gorilla
+1F9A7 ; fully-qualified # 🦧 E12.0 orangutan
+1F436 ; fully-qualified # 🐶 E0.6 dog face
+1F415 ; fully-qualified # 🐕 E0.7 dog
+1F9AE ; fully-qualified # 🦮 E12.0 guide dog
+1F415 200D 1F9BA ; fully-qualified # 🐕🦺 E12.0 service dog
+1F429 ; fully-qualified # 🐩 E0.6 poodle
+1F43A ; fully-qualified # 🐺 E0.6 wolf
+1F98A ; fully-qualified # 🦊 E3.0 fox
+1F99D ; fully-qualified # 🦝 E11.0 raccoon
+1F431 ; fully-qualified # 🐱 E0.6 cat face
+1F408 ; fully-qualified # 🐈 E0.7 cat
+1F408 200D 2B1B ; fully-qualified # 🐈⬛ E13.0 black cat
+1F981 ; fully-qualified # 🦁 E1.0 lion
+1F42F ; fully-qualified # 🐯 E0.6 tiger face
+1F405 ; fully-qualified # 🐅 E1.0 tiger
+1F406 ; fully-qualified # 🐆 E1.0 leopard
+1F434 ; fully-qualified # 🐴 E0.6 horse face
+1FACE ; fully-qualified # 🫎 E15.0 moose
+1FACF ; fully-qualified # 🫏 E15.0 donkey
+1F40E ; fully-qualified # 🐎 E0.6 horse
+1F984 ; fully-qualified # 🦄 E1.0 unicorn
+1F993 ; fully-qualified # 🦓 E5.0 zebra
+1F98C ; fully-qualified # 🦌 E3.0 deer
+1F9AC ; fully-qualified # 🦬 E13.0 bison
+1F42E ; fully-qualified # 🐮 E0.6 cow face
+1F402 ; fully-qualified # 🐂 E1.0 ox
+1F403 ; fully-qualified # 🐃 E1.0 water buffalo
+1F404 ; fully-qualified # 🐄 E1.0 cow
+1F437 ; fully-qualified # 🐷 E0.6 pig face
+1F416 ; fully-qualified # 🐖 E1.0 pig
+1F417 ; fully-qualified # 🐗 E0.6 boar
+1F43D ; fully-qualified # 🐽 E0.6 pig nose
+1F40F ; fully-qualified # 🐏 E1.0 ram
+1F411 ; fully-qualified # 🐑 E0.6 ewe
+1F410 ; fully-qualified # 🐐 E1.0 goat
+1F42A ; fully-qualified # 🐪 E1.0 camel
+1F42B ; fully-qualified # 🐫 E0.6 two-hump camel
+1F999 ; fully-qualified # 🦙 E11.0 llama
+1F992 ; fully-qualified # 🦒 E5.0 giraffe
+1F418 ; fully-qualified # 🐘 E0.6 elephant
+1F9A3 ; fully-qualified # 🦣 E13.0 mammoth
+1F98F ; fully-qualified # 🦏 E3.0 rhinoceros
+1F99B ; fully-qualified # 🦛 E11.0 hippopotamus
+1F42D ; fully-qualified # 🐭 E0.6 mouse face
+1F401 ; fully-qualified # 🐁 E1.0 mouse
+1F400 ; fully-qualified # 🐀 E1.0 rat
+1F439 ; fully-qualified # 🐹 E0.6 hamster
+1F430 ; fully-qualified # 🐰 E0.6 rabbit face
+1F407 ; fully-qualified # 🐇 E1.0 rabbit
+1F43F FE0F ; fully-qualified # 🐿️ E0.7 chipmunk
+1F43F ; unqualified # 🐿 E0.7 chipmunk
+1F9AB ; fully-qualified # 🦫 E13.0 beaver
+1F994 ; fully-qualified # 🦔 E5.0 hedgehog
+1F987 ; fully-qualified # 🦇 E3.0 bat
+1F43B ; fully-qualified # 🐻 E0.6 bear
+1F43B 200D 2744 FE0F ; fully-qualified # 🐻❄️ E13.0 polar bear
+1F43B 200D 2744 ; minimally-qualified # 🐻❄ E13.0 polar bear
+1F428 ; fully-qualified # 🐨 E0.6 koala
+1F43C ; fully-qualified # 🐼 E0.6 panda
+1F9A5 ; fully-qualified # 🦥 E12.0 sloth
+1F9A6 ; fully-qualified # 🦦 E12.0 otter
+1F9A8 ; fully-qualified # 🦨 E12.0 skunk
+1F998 ; fully-qualified # 🦘 E11.0 kangaroo
+1F9A1 ; fully-qualified # 🦡 E11.0 badger
+1F43E ; fully-qualified # 🐾 E0.6 paw prints
+
+# subgroup: animal-bird
+1F983 ; fully-qualified # 🦃 E1.0 turkey
+1F414 ; fully-qualified # 🐔 E0.6 chicken
+1F413 ; fully-qualified # 🐓 E1.0 rooster
+1F423 ; fully-qualified # 🐣 E0.6 hatching chick
+1F424 ; fully-qualified # 🐤 E0.6 baby chick
+1F425 ; fully-qualified # 🐥 E0.6 front-facing baby chick
+1F426 ; fully-qualified # 🐦 E0.6 bird
+1F427 ; fully-qualified # 🐧 E0.6 penguin
+1F54A FE0F ; fully-qualified # 🕊️ E0.7 dove
+1F54A ; unqualified # 🕊 E0.7 dove
+1F985 ; fully-qualified # 🦅 E3.0 eagle
+1F986 ; fully-qualified # 🦆 E3.0 duck
+1F9A2 ; fully-qualified # 🦢 E11.0 swan
+1F989 ; fully-qualified # 🦉 E3.0 owl
+1F9A4 ; fully-qualified # 🦤 E13.0 dodo
+1FAB6 ; fully-qualified # 🪶 E13.0 feather
+1F9A9 ; fully-qualified # 🦩 E12.0 flamingo
+1F99A ; fully-qualified # 🦚 E11.0 peacock
+1F99C ; fully-qualified # 🦜 E11.0 parrot
+1FABD ; fully-qualified # 🪽 E15.0 wing
+1F426 200D 2B1B ; fully-qualified # 🐦⬛ E15.0 black bird
+1FABF ; fully-qualified # 🪿 E15.0 goose
+1F426 200D 1F525 ; fully-qualified # 🐦🔥 E15.1 phoenix
+
+# subgroup: animal-amphibian
+1F438 ; fully-qualified # 🐸 E0.6 frog
+
+# subgroup: animal-reptile
+1F40A ; fully-qualified # 🐊 E1.0 crocodile
+1F422 ; fully-qualified # 🐢 E0.6 turtle
+1F98E ; fully-qualified # 🦎 E3.0 lizard
+1F40D ; fully-qualified # 🐍 E0.6 snake
+1F432 ; fully-qualified # 🐲 E0.6 dragon face
+1F409 ; fully-qualified # 🐉 E1.0 dragon
+1F995 ; fully-qualified # 🦕 E5.0 sauropod
+1F996 ; fully-qualified # 🦖 E5.0 T-Rex
+
+# subgroup: animal-marine
+1F433 ; fully-qualified # 🐳 E0.6 spouting whale
+1F40B ; fully-qualified # 🐋 E1.0 whale
+1F42C ; fully-qualified # 🐬 E0.6 dolphin
+1F9AD ; fully-qualified # 🦭 E13.0 seal
+1F41F ; fully-qualified # 🐟 E0.6 fish
+1F420 ; fully-qualified # 🐠 E0.6 tropical fish
+1F421 ; fully-qualified # 🐡 E0.6 blowfish
+1F988 ; fully-qualified # 🦈 E3.0 shark
+1F419 ; fully-qualified # 🐙 E0.6 octopus
+1F41A ; fully-qualified # 🐚 E0.6 spiral shell
+1FAB8 ; fully-qualified # 🪸 E14.0 coral
+1FABC ; fully-qualified # 🪼 E15.0 jellyfish
+1F980 ; fully-qualified # 🦀 E1.0 crab
+1F99E ; fully-qualified # 🦞 E11.0 lobster
+1F990 ; fully-qualified # 🦐 E3.0 shrimp
+1F991 ; fully-qualified # 🦑 E3.0 squid
+1F9AA ; fully-qualified # 🦪 E12.0 oyster
+
+# subgroup: animal-bug
+1F40C ; fully-qualified # 🐌 E0.6 snail
+1F98B ; fully-qualified # 🦋 E3.0 butterfly
+1F41B ; fully-qualified # 🐛 E0.6 bug
+1F41C ; fully-qualified # 🐜 E0.6 ant
+1F41D ; fully-qualified # 🐝 E0.6 honeybee
+1FAB2 ; fully-qualified # 🪲 E13.0 beetle
+1F41E ; fully-qualified # 🐞 E0.6 lady beetle
+1F997 ; fully-qualified # 🦗 E5.0 cricket
+1FAB3 ; fully-qualified # 🪳 E13.0 cockroach
+1F577 FE0F ; fully-qualified # 🕷️ E0.7 spider
+1F577 ; unqualified # 🕷 E0.7 spider
+1F578 FE0F ; fully-qualified # 🕸️ E0.7 spider web
+1F578 ; unqualified # 🕸 E0.7 spider web
+1F982 ; fully-qualified # 🦂 E1.0 scorpion
+1F99F ; fully-qualified # 🦟 E11.0 mosquito
+1FAB0 ; fully-qualified # 🪰 E13.0 fly
+1FAB1 ; fully-qualified # 🪱 E13.0 worm
+1F9A0 ; fully-qualified # 🦠 E11.0 microbe
+
+# subgroup: plant-flower
+1F490 ; fully-qualified # 💐 E0.6 bouquet
+1F338 ; fully-qualified # 🌸 E0.6 cherry blossom
+1F4AE ; fully-qualified # 💮 E0.6 white flower
+1FAB7 ; fully-qualified # 🪷 E14.0 lotus
+1F3F5 FE0F ; fully-qualified # 🏵️ E0.7 rosette
+1F3F5 ; unqualified # 🏵 E0.7 rosette
+1F339 ; fully-qualified # 🌹 E0.6 rose
+1F940 ; fully-qualified # 🥀 E3.0 wilted flower
+1F33A ; fully-qualified # 🌺 E0.6 hibiscus
+1F33B ; fully-qualified # 🌻 E0.6 sunflower
+1F33C ; fully-qualified # 🌼 E0.6 blossom
+1F337 ; fully-qualified # 🌷 E0.6 tulip
+1FABB ; fully-qualified # 🪻 E15.0 hyacinth
+
+# subgroup: plant-other
+1F331 ; fully-qualified # 🌱 E0.6 seedling
+1FAB4 ; fully-qualified # 🪴 E13.0 potted plant
+1F332 ; fully-qualified # 🌲 E1.0 evergreen tree
+1F333 ; fully-qualified # 🌳 E1.0 deciduous tree
+1F334 ; fully-qualified # 🌴 E0.6 palm tree
+1F335 ; fully-qualified # 🌵 E0.6 cactus
+1F33E ; fully-qualified # 🌾 E0.6 sheaf of rice
+1F33F ; fully-qualified # 🌿 E0.6 herb
+2618 FE0F ; fully-qualified # ☘️ E1.0 shamrock
+2618 ; unqualified # ☘ E1.0 shamrock
+1F340 ; fully-qualified # 🍀 E0.6 four leaf clover
+1F341 ; fully-qualified # 🍁 E0.6 maple leaf
+1F342 ; fully-qualified # 🍂 E0.6 fallen leaf
+1F343 ; fully-qualified # 🍃 E0.6 leaf fluttering in wind
+1FAB9 ; fully-qualified # 🪹 E14.0 empty nest
+1FABA ; fully-qualified # 🪺 E14.0 nest with eggs
+1F344 ; fully-qualified # 🍄 E0.6 mushroom
+1FABE ; fully-qualified # E16.0 leafless tree
+
+# Animals & Nature subtotal: 166
+# Animals & Nature subtotal: 166 w/o modifiers
+
+# group: Food & Drink
+
+# subgroup: food-fruit
+1F347 ; fully-qualified # 🍇 E0.6 grapes
+1F348 ; fully-qualified # 🍈 E0.6 melon
+1F349 ; fully-qualified # 🍉 E0.6 watermelon
+1F34A ; fully-qualified # 🍊 E0.6 tangerine
+1F34B ; fully-qualified # 🍋 E1.0 lemon
+1F34B 200D 1F7E9 ; fully-qualified # 🍋🟩 E15.1 lime
+1F34C ; fully-qualified # 🍌 E0.6 banana
+1F34D ; fully-qualified # 🍍 E0.6 pineapple
+1F96D ; fully-qualified # 🥭 E11.0 mango
+1F34E ; fully-qualified # 🍎 E0.6 red apple
+1F34F ; fully-qualified # 🍏 E0.6 green apple
+1F350 ; fully-qualified # 🍐 E1.0 pear
+1F351 ; fully-qualified # 🍑 E0.6 peach
+1F352 ; fully-qualified # 🍒 E0.6 cherries
+1F353 ; fully-qualified # 🍓 E0.6 strawberry
+1FAD0 ; fully-qualified # 🫐 E13.0 blueberries
+1F95D ; fully-qualified # 🥝 E3.0 kiwi fruit
+1F345 ; fully-qualified # 🍅 E0.6 tomato
+1FAD2 ; fully-qualified # 🫒 E13.0 olive
+1F965 ; fully-qualified # 🥥 E5.0 coconut
+
+# subgroup: food-vegetable
+1F951 ; fully-qualified # 🥑 E3.0 avocado
+1F346 ; fully-qualified # 🍆 E0.6 eggplant
+1F954 ; fully-qualified # 🥔 E3.0 potato
+1F955 ; fully-qualified # 🥕 E3.0 carrot
+1F33D ; fully-qualified # 🌽 E0.6 ear of corn
+1F336 FE0F ; fully-qualified # 🌶️ E0.7 hot pepper
+1F336 ; unqualified # 🌶 E0.7 hot pepper
+1FAD1 ; fully-qualified # 🫑 E13.0 bell pepper
+1F952 ; fully-qualified # 🥒 E3.0 cucumber
+1F96C ; fully-qualified # 🥬 E11.0 leafy green
+1F966 ; fully-qualified # 🥦 E5.0 broccoli
+1F9C4 ; fully-qualified # 🧄 E12.0 garlic
+1F9C5 ; fully-qualified # 🧅 E12.0 onion
+1F95C ; fully-qualified # 🥜 E3.0 peanuts
+1FAD8 ; fully-qualified # 🫘 E14.0 beans
+1F330 ; fully-qualified # 🌰 E0.6 chestnut
+1FADA ; fully-qualified # 🫚 E15.0 ginger root
+1FADB ; fully-qualified # 🫛 E15.0 pea pod
+1F344 200D 1F7EB ; fully-qualified # 🍄🟫 E15.1 brown mushroom
+1FADC ; fully-qualified # E16.0 root vegetable
+
+# subgroup: food-prepared
+1F35E ; fully-qualified # 🍞 E0.6 bread
+1F950 ; fully-qualified # 🥐 E3.0 croissant
+1F956 ; fully-qualified # 🥖 E3.0 baguette bread
+1FAD3 ; fully-qualified # 🫓 E13.0 flatbread
+1F968 ; fully-qualified # 🥨 E5.0 pretzel
+1F96F ; fully-qualified # 🥯 E11.0 bagel
+1F95E ; fully-qualified # 🥞 E3.0 pancakes
+1F9C7 ; fully-qualified # 🧇 E12.0 waffle
+1F9C0 ; fully-qualified # 🧀 E1.0 cheese wedge
+1F356 ; fully-qualified # 🍖 E0.6 meat on bone
+1F357 ; fully-qualified # 🍗 E0.6 poultry leg
+1F969 ; fully-qualified # 🥩 E5.0 cut of meat
+1F953 ; fully-qualified # 🥓 E3.0 bacon
+1F354 ; fully-qualified # 🍔 E0.6 hamburger
+1F35F ; fully-qualified # 🍟 E0.6 french fries
+1F355 ; fully-qualified # 🍕 E0.6 pizza
+1F32D ; fully-qualified # 🌭 E1.0 hot dog
+1F96A ; fully-qualified # 🥪 E5.0 sandwich
+1F32E ; fully-qualified # 🌮 E1.0 taco
+1F32F ; fully-qualified # 🌯 E1.0 burrito
+1FAD4 ; fully-qualified # 🫔 E13.0 tamale
+1F959 ; fully-qualified # 🥙 E3.0 stuffed flatbread
+1F9C6 ; fully-qualified # 🧆 E12.0 falafel
+1F95A ; fully-qualified # 🥚 E3.0 egg
+1F373 ; fully-qualified # 🍳 E0.6 cooking
+1F958 ; fully-qualified # 🥘 E3.0 shallow pan of food
+1F372 ; fully-qualified # 🍲 E0.6 pot of food
+1FAD5 ; fully-qualified # 🫕 E13.0 fondue
+1F963 ; fully-qualified # 🥣 E5.0 bowl with spoon
+1F957 ; fully-qualified # 🥗 E3.0 green salad
+1F37F ; fully-qualified # 🍿 E1.0 popcorn
+1F9C8 ; fully-qualified # 🧈 E12.0 butter
+1F9C2 ; fully-qualified # 🧂 E11.0 salt
+1F96B ; fully-qualified # 🥫 E5.0 canned food
+
+# subgroup: food-asian
+1F371 ; fully-qualified # 🍱 E0.6 bento box
+1F358 ; fully-qualified # 🍘 E0.6 rice cracker
+1F359 ; fully-qualified # 🍙 E0.6 rice ball
+1F35A ; fully-qualified # 🍚 E0.6 cooked rice
+1F35B ; fully-qualified # 🍛 E0.6 curry rice
+1F35C ; fully-qualified # 🍜 E0.6 steaming bowl
+1F35D ; fully-qualified # 🍝 E0.6 spaghetti
+1F360 ; fully-qualified # 🍠 E0.6 roasted sweet potato
+1F362 ; fully-qualified # 🍢 E0.6 oden
+1F363 ; fully-qualified # 🍣 E0.6 sushi
+1F364 ; fully-qualified # 🍤 E0.6 fried shrimp
+1F365 ; fully-qualified # 🍥 E0.6 fish cake with swirl
+1F96E ; fully-qualified # 🥮 E11.0 moon cake
+1F361 ; fully-qualified # 🍡 E0.6 dango
+1F95F ; fully-qualified # 🥟 E5.0 dumpling
+1F960 ; fully-qualified # 🥠 E5.0 fortune cookie
+1F961 ; fully-qualified # 🥡 E5.0 takeout box
+
+# subgroup: food-sweet
+1F366 ; fully-qualified # 🍦 E0.6 soft ice cream
+1F367 ; fully-qualified # 🍧 E0.6 shaved ice
+1F368 ; fully-qualified # 🍨 E0.6 ice cream
+1F369 ; fully-qualified # 🍩 E0.6 doughnut
+1F36A ; fully-qualified # 🍪 E0.6 cookie
+1F382 ; fully-qualified # 🎂 E0.6 birthday cake
+1F370 ; fully-qualified # 🍰 E0.6 shortcake
+1F9C1 ; fully-qualified # 🧁 E11.0 cupcake
+1F967 ; fully-qualified # 🥧 E5.0 pie
+1F36B ; fully-qualified # 🍫 E0.6 chocolate bar
+1F36C ; fully-qualified # 🍬 E0.6 candy
+1F36D ; fully-qualified # 🍭 E0.6 lollipop
+1F36E ; fully-qualified # 🍮 E0.6 custard
+1F36F ; fully-qualified # 🍯 E0.6 honey pot
+
+# subgroup: drink
+1F37C ; fully-qualified # 🍼 E1.0 baby bottle
+1F95B ; fully-qualified # 🥛 E3.0 glass of milk
+2615 ; fully-qualified # ☕ E0.6 hot beverage
+1FAD6 ; fully-qualified # 🫖 E13.0 teapot
+1F375 ; fully-qualified # 🍵 E0.6 teacup without handle
+1F376 ; fully-qualified # 🍶 E0.6 sake
+1F37E ; fully-qualified # 🍾 E1.0 bottle with popping cork
+1F377 ; fully-qualified # 🍷 E0.6 wine glass
+1F378 ; fully-qualified # 🍸 E0.6 cocktail glass
+1F379 ; fully-qualified # 🍹 E0.6 tropical drink
+1F37A ; fully-qualified # 🍺 E0.6 beer mug
+1F37B ; fully-qualified # 🍻 E0.6 clinking beer mugs
+1F942 ; fully-qualified # 🥂 E3.0 clinking glasses
+1F943 ; fully-qualified # 🥃 E3.0 tumbler glass
+1FAD7 ; fully-qualified # 🫗 E14.0 pouring liquid
+1F964 ; fully-qualified # 🥤 E5.0 cup with straw
+1F9CB ; fully-qualified # 🧋 E13.0 bubble tea
+1F9C3 ; fully-qualified # 🧃 E12.0 beverage box
+1F9C9 ; fully-qualified # 🧉 E12.0 mate
+1F9CA ; fully-qualified # 🧊 E12.0 ice
+
+# subgroup: dishware
+1F962 ; fully-qualified # 🥢 E5.0 chopsticks
+1F37D FE0F ; fully-qualified # 🍽️ E0.7 fork and knife with plate
+1F37D ; unqualified # 🍽 E0.7 fork and knife with plate
+1F374 ; fully-qualified # 🍴 E0.6 fork and knife
+1F944 ; fully-qualified # 🥄 E3.0 spoon
+1F52A ; fully-qualified # 🔪 E0.6 kitchen knife
+1FAD9 ; fully-qualified # 🫙 E14.0 jar
+1F3FA ; fully-qualified # 🏺 E1.0 amphora
+
+# Food & Drink subtotal: 133
+# Food & Drink subtotal: 133 w/o modifiers
+
+# group: Travel & Places
+
+# subgroup: place-map
+1F30D ; fully-qualified # 🌍 E0.7 globe showing Europe-Africa
+1F30E ; fully-qualified # 🌎 E0.7 globe showing Americas
+1F30F ; fully-qualified # 🌏 E0.6 globe showing Asia-Australia
+1F310 ; fully-qualified # 🌐 E1.0 globe with meridians
+1F5FA FE0F ; fully-qualified # 🗺️ E0.7 world map
+1F5FA ; unqualified # 🗺 E0.7 world map
+1F5FE ; fully-qualified # 🗾 E0.6 map of Japan
+1F9ED ; fully-qualified # 🧭 E11.0 compass
+
+# subgroup: place-geographic
+1F3D4 FE0F ; fully-qualified # 🏔️ E0.7 snow-capped mountain
+1F3D4 ; unqualified # 🏔 E0.7 snow-capped mountain
+26F0 FE0F ; fully-qualified # ⛰️ E0.7 mountain
+26F0 ; unqualified # ⛰ E0.7 mountain
+1F30B ; fully-qualified # 🌋 E0.6 volcano
+1F5FB ; fully-qualified # 🗻 E0.6 mount fuji
+1F3D5 FE0F ; fully-qualified # 🏕️ E0.7 camping
+1F3D5 ; unqualified # 🏕 E0.7 camping
+1F3D6 FE0F ; fully-qualified # 🏖️ E0.7 beach with umbrella
+1F3D6 ; unqualified # 🏖 E0.7 beach with umbrella
+1F3DC FE0F ; fully-qualified # 🏜️ E0.7 desert
+1F3DC ; unqualified # 🏜 E0.7 desert
+1F3DD FE0F ; fully-qualified # 🏝️ E0.7 desert island
+1F3DD ; unqualified # 🏝 E0.7 desert island
+1F3DE FE0F ; fully-qualified # 🏞️ E0.7 national park
+1F3DE ; unqualified # 🏞 E0.7 national park
+
+# subgroup: place-building
+1F3DF FE0F ; fully-qualified # 🏟️ E0.7 stadium
+1F3DF ; unqualified # 🏟 E0.7 stadium
+1F3DB FE0F ; fully-qualified # 🏛️ E0.7 classical building
+1F3DB ; unqualified # 🏛 E0.7 classical building
+1F3D7 FE0F ; fully-qualified # 🏗️ E0.7 building construction
+1F3D7 ; unqualified # 🏗 E0.7 building construction
+1F9F1 ; fully-qualified # 🧱 E11.0 brick
+1FAA8 ; fully-qualified # 🪨 E13.0 rock
+1FAB5 ; fully-qualified # 🪵 E13.0 wood
+1F6D6 ; fully-qualified # 🛖 E13.0 hut
+1F3D8 FE0F ; fully-qualified # 🏘️ E0.7 houses
+1F3D8 ; unqualified # 🏘 E0.7 houses
+1F3DA FE0F ; fully-qualified # 🏚️ E0.7 derelict house
+1F3DA ; unqualified # 🏚 E0.7 derelict house
+1F3E0 ; fully-qualified # 🏠 E0.6 house
+1F3E1 ; fully-qualified # 🏡 E0.6 house with garden
+1F3E2 ; fully-qualified # 🏢 E0.6 office building
+1F3E3 ; fully-qualified # 🏣 E0.6 Japanese post office
+1F3E4 ; fully-qualified # 🏤 E1.0 post office
+1F3E5 ; fully-qualified # 🏥 E0.6 hospital
+1F3E6 ; fully-qualified # 🏦 E0.6 bank
+1F3E8 ; fully-qualified # 🏨 E0.6 hotel
+1F3E9 ; fully-qualified # 🏩 E0.6 love hotel
+1F3EA ; fully-qualified # 🏪 E0.6 convenience store
+1F3EB ; fully-qualified # 🏫 E0.6 school
+1F3EC ; fully-qualified # 🏬 E0.6 department store
+1F3ED ; fully-qualified # 🏭 E0.6 factory
+1F3EF ; fully-qualified # 🏯 E0.6 Japanese castle
+1F3F0 ; fully-qualified # 🏰 E0.6 castle
+1F492 ; fully-qualified # 💒 E0.6 wedding
+1F5FC ; fully-qualified # 🗼 E0.6 Tokyo tower
+1F5FD ; fully-qualified # 🗽 E0.6 Statue of Liberty
+
+# subgroup: place-religious
+26EA ; fully-qualified # ⛪ E0.6 church
+1F54C ; fully-qualified # 🕌 E1.0 mosque
+1F6D5 ; fully-qualified # 🛕 E12.0 hindu temple
+1F54D ; fully-qualified # 🕍 E1.0 synagogue
+26E9 FE0F ; fully-qualified # ⛩️ E0.7 shinto shrine
+26E9 ; unqualified # ⛩ E0.7 shinto shrine
+1F54B ; fully-qualified # 🕋 E1.0 kaaba
+
+# subgroup: place-other
+26F2 ; fully-qualified # ⛲ E0.6 fountain
+26FA ; fully-qualified # ⛺ E0.6 tent
+1F301 ; fully-qualified # 🌁 E0.6 foggy
+1F303 ; fully-qualified # 🌃 E0.6 night with stars
+1F3D9 FE0F ; fully-qualified # 🏙️ E0.7 cityscape
+1F3D9 ; unqualified # 🏙 E0.7 cityscape
+1F304 ; fully-qualified # 🌄 E0.6 sunrise over mountains
+1F305 ; fully-qualified # 🌅 E0.6 sunrise
+1F306 ; fully-qualified # 🌆 E0.6 cityscape at dusk
+1F307 ; fully-qualified # 🌇 E0.6 sunset
+1F309 ; fully-qualified # 🌉 E0.6 bridge at night
+2668 FE0F ; fully-qualified # ♨️ E0.6 hot springs
+2668 ; unqualified # ♨ E0.6 hot springs
+1F3A0 ; fully-qualified # 🎠 E0.6 carousel horse
+1F6DD ; fully-qualified # 🛝 E14.0 playground slide
+1F3A1 ; fully-qualified # 🎡 E0.6 ferris wheel
+1F3A2 ; fully-qualified # 🎢 E0.6 roller coaster
+1F488 ; fully-qualified # 💈 E0.6 barber pole
+1F3AA ; fully-qualified # 🎪 E0.6 circus tent
+
+# subgroup: transport-ground
+1F682 ; fully-qualified # 🚂 E1.0 locomotive
+1F683 ; fully-qualified # 🚃 E0.6 railway car
+1F684 ; fully-qualified # 🚄 E0.6 high-speed train
+1F685 ; fully-qualified # 🚅 E0.6 bullet train
+1F686 ; fully-qualified # 🚆 E1.0 train
+1F687 ; fully-qualified # 🚇 E0.6 metro
+1F688 ; fully-qualified # 🚈 E1.0 light rail
+1F689 ; fully-qualified # 🚉 E0.6 station
+1F68A ; fully-qualified # 🚊 E1.0 tram
+1F69D ; fully-qualified # 🚝 E1.0 monorail
+1F69E ; fully-qualified # 🚞 E1.0 mountain railway
+1F68B ; fully-qualified # 🚋 E1.0 tram car
+1F68C ; fully-qualified # 🚌 E0.6 bus
+1F68D ; fully-qualified # 🚍 E0.7 oncoming bus
+1F68E ; fully-qualified # 🚎 E1.0 trolleybus
+1F690 ; fully-qualified # 🚐 E1.0 minibus
+1F691 ; fully-qualified # 🚑 E0.6 ambulance
+1F692 ; fully-qualified # 🚒 E0.6 fire engine
+1F693 ; fully-qualified # 🚓 E0.6 police car
+1F694 ; fully-qualified # 🚔 E0.7 oncoming police car
+1F695 ; fully-qualified # 🚕 E0.6 taxi
+1F696 ; fully-qualified # 🚖 E1.0 oncoming taxi
+1F697 ; fully-qualified # 🚗 E0.6 automobile
+1F698 ; fully-qualified # 🚘 E0.7 oncoming automobile
+1F699 ; fully-qualified # 🚙 E0.6 sport utility vehicle
+1F6FB ; fully-qualified # 🛻 E13.0 pickup truck
+1F69A ; fully-qualified # 🚚 E0.6 delivery truck
+1F69B ; fully-qualified # 🚛 E1.0 articulated lorry
+1F69C ; fully-qualified # 🚜 E1.0 tractor
+1F3CE FE0F ; fully-qualified # 🏎️ E0.7 racing car
+1F3CE ; unqualified # 🏎 E0.7 racing car
+1F3CD FE0F ; fully-qualified # 🏍️ E0.7 motorcycle
+1F3CD ; unqualified # 🏍 E0.7 motorcycle
+1F6F5 ; fully-qualified # 🛵 E3.0 motor scooter
+1F9BD ; fully-qualified # 🦽 E12.0 manual wheelchair
+1F9BC ; fully-qualified # 🦼 E12.0 motorized wheelchair
+1F6FA ; fully-qualified # 🛺 E12.0 auto rickshaw
+1F6B2 ; fully-qualified # 🚲 E0.6 bicycle
+1F6F4 ; fully-qualified # 🛴 E3.0 kick scooter
+1F6F9 ; fully-qualified # 🛹 E11.0 skateboard
+1F6FC ; fully-qualified # 🛼 E13.0 roller skate
+1F68F ; fully-qualified # 🚏 E0.6 bus stop
+1F6E3 FE0F ; fully-qualified # 🛣️ E0.7 motorway
+1F6E3 ; unqualified # 🛣 E0.7 motorway
+1F6E4 FE0F ; fully-qualified # 🛤️ E0.7 railway track
+1F6E4 ; unqualified # 🛤 E0.7 railway track
+1F6E2 FE0F ; fully-qualified # 🛢️ E0.7 oil drum
+1F6E2 ; unqualified # 🛢 E0.7 oil drum
+26FD ; fully-qualified # ⛽ E0.6 fuel pump
+1F6DE ; fully-qualified # 🛞 E14.0 wheel
+1F6A8 ; fully-qualified # 🚨 E0.6 police car light
+1F6A5 ; fully-qualified # 🚥 E0.6 horizontal traffic light
+1F6A6 ; fully-qualified # 🚦 E1.0 vertical traffic light
+1F6D1 ; fully-qualified # 🛑 E3.0 stop sign
+1F6A7 ; fully-qualified # 🚧 E0.6 construction
+
+# subgroup: transport-water
+2693 ; fully-qualified # ⚓ E0.6 anchor
+1F6DF ; fully-qualified # 🛟 E14.0 ring buoy
+26F5 ; fully-qualified # ⛵ E0.6 sailboat
+1F6F6 ; fully-qualified # 🛶 E3.0 canoe
+1F6A4 ; fully-qualified # 🚤 E0.6 speedboat
+1F6F3 FE0F ; fully-qualified # 🛳️ E0.7 passenger ship
+1F6F3 ; unqualified # 🛳 E0.7 passenger ship
+26F4 FE0F ; fully-qualified # ⛴️ E0.7 ferry
+26F4 ; unqualified # ⛴ E0.7 ferry
+1F6E5 FE0F ; fully-qualified # 🛥️ E0.7 motor boat
+1F6E5 ; unqualified # 🛥 E0.7 motor boat
+1F6A2 ; fully-qualified # 🚢 E0.6 ship
+
+# subgroup: transport-air
+2708 FE0F ; fully-qualified # ✈️ E0.6 airplane
+2708 ; unqualified # ✈ E0.6 airplane
+1F6E9 FE0F ; fully-qualified # 🛩️ E0.7 small airplane
+1F6E9 ; unqualified # 🛩 E0.7 small airplane
+1F6EB ; fully-qualified # 🛫 E1.0 airplane departure
+1F6EC ; fully-qualified # 🛬 E1.0 airplane arrival
+1FA82 ; fully-qualified # 🪂 E12.0 parachute
+1F4BA ; fully-qualified # 💺 E0.6 seat
+1F681 ; fully-qualified # 🚁 E1.0 helicopter
+1F69F ; fully-qualified # 🚟 E1.0 suspension railway
+1F6A0 ; fully-qualified # 🚠 E1.0 mountain cableway
+1F6A1 ; fully-qualified # 🚡 E1.0 aerial tramway
+1F6F0 FE0F ; fully-qualified # 🛰️ E0.7 satellite
+1F6F0 ; unqualified # 🛰 E0.7 satellite
+1F680 ; fully-qualified # 🚀 E0.6 rocket
+1F6F8 ; fully-qualified # 🛸 E5.0 flying saucer
+
+# subgroup: hotel
+1F6CE FE0F ; fully-qualified # 🛎️ E0.7 bellhop bell
+1F6CE ; unqualified # 🛎 E0.7 bellhop bell
+1F9F3 ; fully-qualified # 🧳 E11.0 luggage
+
+# subgroup: time
+231B ; fully-qualified # ⌛ E0.6 hourglass done
+23F3 ; fully-qualified # ⏳ E0.6 hourglass not done
+231A ; fully-qualified # ⌚ E0.6 watch
+23F0 ; fully-qualified # ⏰ E0.6 alarm clock
+23F1 FE0F ; fully-qualified # ⏱️ E1.0 stopwatch
+23F1 ; unqualified # ⏱ E1.0 stopwatch
+23F2 FE0F ; fully-qualified # ⏲️ E1.0 timer clock
+23F2 ; unqualified # ⏲ E1.0 timer clock
+1F570 FE0F ; fully-qualified # 🕰️ E0.7 mantelpiece clock
+1F570 ; unqualified # 🕰 E0.7 mantelpiece clock
+1F55B ; fully-qualified # 🕛 E0.6 twelve o’clock
+1F567 ; fully-qualified # 🕧 E0.7 twelve-thirty
+1F550 ; fully-qualified # 🕐 E0.6 one o’clock
+1F55C ; fully-qualified # 🕜 E0.7 one-thirty
+1F551 ; fully-qualified # 🕑 E0.6 two o’clock
+1F55D ; fully-qualified # 🕝 E0.7 two-thirty
+1F552 ; fully-qualified # 🕒 E0.6 three o’clock
+1F55E ; fully-qualified # 🕞 E0.7 three-thirty
+1F553 ; fully-qualified # 🕓 E0.6 four o’clock
+1F55F ; fully-qualified # 🕟 E0.7 four-thirty
+1F554 ; fully-qualified # 🕔 E0.6 five o’clock
+1F560 ; fully-qualified # 🕠 E0.7 five-thirty
+1F555 ; fully-qualified # 🕕 E0.6 six o’clock
+1F561 ; fully-qualified # 🕡 E0.7 six-thirty
+1F556 ; fully-qualified # 🕖 E0.6 seven o’clock
+1F562 ; fully-qualified # 🕢 E0.7 seven-thirty
+1F557 ; fully-qualified # 🕗 E0.6 eight o’clock
+1F563 ; fully-qualified # 🕣 E0.7 eight-thirty
+1F558 ; fully-qualified # 🕘 E0.6 nine o’clock
+1F564 ; fully-qualified # 🕤 E0.7 nine-thirty
+1F559 ; fully-qualified # 🕙 E0.6 ten o’clock
+1F565 ; fully-qualified # 🕥 E0.7 ten-thirty
+1F55A ; fully-qualified # 🕚 E0.6 eleven o’clock
+1F566 ; fully-qualified # 🕦 E0.7 eleven-thirty
+
+# subgroup: sky & weather
+1F311 ; fully-qualified # 🌑 E0.6 new moon
+1F312 ; fully-qualified # 🌒 E1.0 waxing crescent moon
+1F313 ; fully-qualified # 🌓 E0.6 first quarter moon
+1F314 ; fully-qualified # 🌔 E0.6 waxing gibbous moon
+1F315 ; fully-qualified # 🌕 E0.6 full moon
+1F316 ; fully-qualified # 🌖 E1.0 waning gibbous moon
+1F317 ; fully-qualified # 🌗 E1.0 last quarter moon
+1F318 ; fully-qualified # 🌘 E1.0 waning crescent moon
+1F319 ; fully-qualified # 🌙 E0.6 crescent moon
+1F31A ; fully-qualified # 🌚 E1.0 new moon face
+1F31B ; fully-qualified # 🌛 E0.6 first quarter moon face
+1F31C ; fully-qualified # 🌜 E0.7 last quarter moon face
+1F321 FE0F ; fully-qualified # 🌡️ E0.7 thermometer
+1F321 ; unqualified # 🌡 E0.7 thermometer
+2600 FE0F ; fully-qualified # ☀️ E0.6 sun
+2600 ; unqualified # ☀ E0.6 sun
+1F31D ; fully-qualified # 🌝 E1.0 full moon face
+1F31E ; fully-qualified # 🌞 E1.0 sun with face
+1FA90 ; fully-qualified # 🪐 E12.0 ringed planet
+2B50 ; fully-qualified # ⭐ E0.6 star
+1F31F ; fully-qualified # 🌟 E0.6 glowing star
+1F320 ; fully-qualified # 🌠 E0.6 shooting star
+1F30C ; fully-qualified # 🌌 E0.6 milky way
+2601 FE0F ; fully-qualified # ☁️ E0.6 cloud
+2601 ; unqualified # ☁ E0.6 cloud
+26C5 ; fully-qualified # ⛅ E0.6 sun behind cloud
+26C8 FE0F ; fully-qualified # ⛈️ E0.7 cloud with lightning and rain
+26C8 ; unqualified # ⛈ E0.7 cloud with lightning and rain
+1F324 FE0F ; fully-qualified # 🌤️ E0.7 sun behind small cloud
+1F324 ; unqualified # 🌤 E0.7 sun behind small cloud
+1F325 FE0F ; fully-qualified # 🌥️ E0.7 sun behind large cloud
+1F325 ; unqualified # 🌥 E0.7 sun behind large cloud
+1F326 FE0F ; fully-qualified # 🌦️ E0.7 sun behind rain cloud
+1F326 ; unqualified # 🌦 E0.7 sun behind rain cloud
+1F327 FE0F ; fully-qualified # 🌧️ E0.7 cloud with rain
+1F327 ; unqualified # 🌧 E0.7 cloud with rain
+1F328 FE0F ; fully-qualified # 🌨️ E0.7 cloud with snow
+1F328 ; unqualified # 🌨 E0.7 cloud with snow
+1F329 FE0F ; fully-qualified # 🌩️ E0.7 cloud with lightning
+1F329 ; unqualified # 🌩 E0.7 cloud with lightning
+1F32A FE0F ; fully-qualified # 🌪️ E0.7 tornado
+1F32A ; unqualified # 🌪 E0.7 tornado
+1F32B FE0F ; fully-qualified # 🌫️ E0.7 fog
+1F32B ; unqualified # 🌫 E0.7 fog
+1F32C FE0F ; fully-qualified # 🌬️ E0.7 wind face
+1F32C ; unqualified # 🌬 E0.7 wind face
+1F300 ; fully-qualified # 🌀 E0.6 cyclone
+1F308 ; fully-qualified # 🌈 E0.6 rainbow
+1F302 ; fully-qualified # 🌂 E0.6 closed umbrella
+2602 FE0F ; fully-qualified # ☂️ E0.7 umbrella
+2602 ; unqualified # ☂ E0.7 umbrella
+2614 ; fully-qualified # ☔ E0.6 umbrella with rain drops
+26F1 FE0F ; fully-qualified # ⛱️ E0.7 umbrella on ground
+26F1 ; unqualified # ⛱ E0.7 umbrella on ground
+26A1 ; fully-qualified # ⚡ E0.6 high voltage
+2744 FE0F ; fully-qualified # ❄️ E0.6 snowflake
+2744 ; unqualified # ❄ E0.6 snowflake
+2603 FE0F ; fully-qualified # ☃️ E0.7 snowman
+2603 ; unqualified # ☃ E0.7 snowman
+26C4 ; fully-qualified # ⛄ E0.6 snowman without snow
+2604 FE0F ; fully-qualified # ☄️ E1.0 comet
+2604 ; unqualified # ☄ E1.0 comet
+1F525 ; fully-qualified # 🔥 E0.6 fire
+1F4A7 ; fully-qualified # 💧 E0.6 droplet
+1F30A ; fully-qualified # 🌊 E0.6 water wave
+
+# Travel & Places subtotal: 267
+# Travel & Places subtotal: 267 w/o modifiers
+
+# group: Activities
+
+# subgroup: event
+1F383 ; fully-qualified # 🎃 E0.6 jack-o-lantern
+1F384 ; fully-qualified # 🎄 E0.6 Christmas tree
+1F386 ; fully-qualified # 🎆 E0.6 fireworks
+1F387 ; fully-qualified # 🎇 E0.6 sparkler
+1F9E8 ; fully-qualified # 🧨 E11.0 firecracker
+2728 ; fully-qualified # ✨ E0.6 sparkles
+1F388 ; fully-qualified # 🎈 E0.6 balloon
+1F389 ; fully-qualified # 🎉 E0.6 party popper
+1F38A ; fully-qualified # 🎊 E0.6 confetti ball
+1F38B ; fully-qualified # 🎋 E0.6 tanabata tree
+1F38D ; fully-qualified # 🎍 E0.6 pine decoration
+1F38E ; fully-qualified # 🎎 E0.6 Japanese dolls
+1F38F ; fully-qualified # 🎏 E0.6 carp streamer
+1F390 ; fully-qualified # 🎐 E0.6 wind chime
+1F391 ; fully-qualified # 🎑 E0.6 moon viewing ceremony
+1F9E7 ; fully-qualified # 🧧 E11.0 red envelope
+1F380 ; fully-qualified # 🎀 E0.6 ribbon
+1F381 ; fully-qualified # 🎁 E0.6 wrapped gift
+1F397 FE0F ; fully-qualified # 🎗️ E0.7 reminder ribbon
+1F397 ; unqualified # 🎗 E0.7 reminder ribbon
+1F39F FE0F ; fully-qualified # 🎟️ E0.7 admission tickets
+1F39F ; unqualified # 🎟 E0.7 admission tickets
+1F3AB ; fully-qualified # 🎫 E0.6 ticket
+
+# subgroup: award-medal
+1F396 FE0F ; fully-qualified # 🎖️ E0.7 military medal
+1F396 ; unqualified # 🎖 E0.7 military medal
+1F3C6 ; fully-qualified # 🏆 E0.6 trophy
+1F3C5 ; fully-qualified # 🏅 E1.0 sports medal
+1F947 ; fully-qualified # 🥇 E3.0 1st place medal
+1F948 ; fully-qualified # 🥈 E3.0 2nd place medal
+1F949 ; fully-qualified # 🥉 E3.0 3rd place medal
+
+# subgroup: sport
+26BD ; fully-qualified # ⚽ E0.6 soccer ball
+26BE ; fully-qualified # ⚾ E0.6 baseball
+1F94E ; fully-qualified # 🥎 E11.0 softball
+1F3C0 ; fully-qualified # 🏀 E0.6 basketball
+1F3D0 ; fully-qualified # 🏐 E1.0 volleyball
+1F3C8 ; fully-qualified # 🏈 E0.6 american football
+1F3C9 ; fully-qualified # 🏉 E1.0 rugby football
+1F3BE ; fully-qualified # 🎾 E0.6 tennis
+1F94F ; fully-qualified # 🥏 E11.0 flying disc
+1F3B3 ; fully-qualified # 🎳 E0.6 bowling
+1F3CF ; fully-qualified # 🏏 E1.0 cricket game
+1F3D1 ; fully-qualified # 🏑 E1.0 field hockey
+1F3D2 ; fully-qualified # 🏒 E1.0 ice hockey
+1F94D ; fully-qualified # 🥍 E11.0 lacrosse
+1F3D3 ; fully-qualified # 🏓 E1.0 ping pong
+1F3F8 ; fully-qualified # 🏸 E1.0 badminton
+1F94A ; fully-qualified # 🥊 E3.0 boxing glove
+1F94B ; fully-qualified # 🥋 E3.0 martial arts uniform
+1F945 ; fully-qualified # 🥅 E3.0 goal net
+26F3 ; fully-qualified # ⛳ E0.6 flag in hole
+26F8 FE0F ; fully-qualified # ⛸️ E0.7 ice skate
+26F8 ; unqualified # ⛸ E0.7 ice skate
+1F3A3 ; fully-qualified # 🎣 E0.6 fishing pole
+1F93F ; fully-qualified # 🤿 E12.0 diving mask
+1F3BD ; fully-qualified # 🎽 E0.6 running shirt
+1F3BF ; fully-qualified # 🎿 E0.6 skis
+1F6F7 ; fully-qualified # 🛷 E5.0 sled
+1F94C ; fully-qualified # 🥌 E5.0 curling stone
+
+# subgroup: game
+1F3AF ; fully-qualified # 🎯 E0.6 bullseye
+1FA80 ; fully-qualified # 🪀 E12.0 yo-yo
+1FA81 ; fully-qualified # 🪁 E12.0 kite
+1F52B ; fully-qualified # 🔫 E0.6 water pistol
+1F3B1 ; fully-qualified # 🎱 E0.6 pool 8 ball
+1F52E ; fully-qualified # 🔮 E0.6 crystal ball
+1FA84 ; fully-qualified # 🪄 E13.0 magic wand
+1F3AE ; fully-qualified # 🎮 E0.6 video game
+1F579 FE0F ; fully-qualified # 🕹️ E0.7 joystick
+1F579 ; unqualified # 🕹 E0.7 joystick
+1F3B0 ; fully-qualified # 🎰 E0.6 slot machine
+1F3B2 ; fully-qualified # 🎲 E0.6 game die
+1F9E9 ; fully-qualified # 🧩 E11.0 puzzle piece
+1F9F8 ; fully-qualified # 🧸 E11.0 teddy bear
+1FA85 ; fully-qualified # 🪅 E13.0 piñata
+1FAA9 ; fully-qualified # 🪩 E14.0 mirror ball
+1FA86 ; fully-qualified # 🪆 E13.0 nesting dolls
+2660 FE0F ; fully-qualified # ♠️ E0.6 spade suit
+2660 ; unqualified # ♠ E0.6 spade suit
+2665 FE0F ; fully-qualified # ♥️ E0.6 heart suit
+2665 ; unqualified # ♥ E0.6 heart suit
+2666 FE0F ; fully-qualified # ♦️ E0.6 diamond suit
+2666 ; unqualified # ♦ E0.6 diamond suit
+2663 FE0F ; fully-qualified # ♣️ E0.6 club suit
+2663 ; unqualified # ♣ E0.6 club suit
+265F FE0F ; fully-qualified # ♟️ E11.0 chess pawn
+265F ; unqualified # ♟ E11.0 chess pawn
+1F0CF ; fully-qualified # 🃏 E0.6 joker
+1F004 ; fully-qualified # 🀄 E0.6 mahjong red dragon
+1F3B4 ; fully-qualified # 🎴 E0.6 flower playing cards
+
+# subgroup: arts & crafts
+1F3AD ; fully-qualified # 🎭 E0.6 performing arts
+1F5BC FE0F ; fully-qualified # 🖼️ E0.7 framed picture
+1F5BC ; unqualified # 🖼 E0.7 framed picture
+1F3A8 ; fully-qualified # 🎨 E0.6 artist palette
+1F9F5 ; fully-qualified # 🧵 E11.0 thread
+1FAA1 ; fully-qualified # 🪡 E13.0 sewing needle
+1F9F6 ; fully-qualified # 🧶 E11.0 yarn
+1FAA2 ; fully-qualified # 🪢 E13.0 knot
+
+# Activities subtotal: 96
+# Activities subtotal: 96 w/o modifiers
+
+# group: Objects
+
+# subgroup: clothing
+1F453 ; fully-qualified # 👓 E0.6 glasses
+1F576 FE0F ; fully-qualified # 🕶️ E0.7 sunglasses
+1F576 ; unqualified # 🕶 E0.7 sunglasses
+1F97D ; fully-qualified # 🥽 E11.0 goggles
+1F97C ; fully-qualified # 🥼 E11.0 lab coat
+1F9BA ; fully-qualified # 🦺 E12.0 safety vest
+1F454 ; fully-qualified # 👔 E0.6 necktie
+1F455 ; fully-qualified # 👕 E0.6 t-shirt
+1F456 ; fully-qualified # 👖 E0.6 jeans
+1F9E3 ; fully-qualified # 🧣 E5.0 scarf
+1F9E4 ; fully-qualified # 🧤 E5.0 gloves
+1F9E5 ; fully-qualified # 🧥 E5.0 coat
+1F9E6 ; fully-qualified # 🧦 E5.0 socks
+1F457 ; fully-qualified # 👗 E0.6 dress
+1F458 ; fully-qualified # 👘 E0.6 kimono
+1F97B ; fully-qualified # 🥻 E12.0 sari
+1FA71 ; fully-qualified # 🩱 E12.0 one-piece swimsuit
+1FA72 ; fully-qualified # 🩲 E12.0 briefs
+1FA73 ; fully-qualified # 🩳 E12.0 shorts
+1F459 ; fully-qualified # 👙 E0.6 bikini
+1F45A ; fully-qualified # 👚 E0.6 woman’s clothes
+1FAAD ; fully-qualified # 🪭 E15.0 folding hand fan
+1F45B ; fully-qualified # 👛 E0.6 purse
+1F45C ; fully-qualified # 👜 E0.6 handbag
+1F45D ; fully-qualified # 👝 E0.6 clutch bag
+1F6CD FE0F ; fully-qualified # 🛍️ E0.7 shopping bags
+1F6CD ; unqualified # 🛍 E0.7 shopping bags
+1F392 ; fully-qualified # 🎒 E0.6 backpack
+1FA74 ; fully-qualified # 🩴 E13.0 thong sandal
+1F45E ; fully-qualified # 👞 E0.6 man’s shoe
+1F45F ; fully-qualified # 👟 E0.6 running shoe
+1F97E ; fully-qualified # 🥾 E11.0 hiking boot
+1F97F ; fully-qualified # 🥿 E11.0 flat shoe
+1F460 ; fully-qualified # 👠 E0.6 high-heeled shoe
+1F461 ; fully-qualified # 👡 E0.6 woman’s sandal
+1FA70 ; fully-qualified # 🩰 E12.0 ballet shoes
+1F462 ; fully-qualified # 👢 E0.6 woman’s boot
+1FAAE ; fully-qualified # 🪮 E15.0 hair pick
+1F451 ; fully-qualified # 👑 E0.6 crown
+1F452 ; fully-qualified # 👒 E0.6 woman’s hat
+1F3A9 ; fully-qualified # 🎩 E0.6 top hat
+1F393 ; fully-qualified # 🎓 E0.6 graduation cap
+1F9E2 ; fully-qualified # 🧢 E5.0 billed cap
+1FA96 ; fully-qualified # 🪖 E13.0 military helmet
+26D1 FE0F ; fully-qualified # ⛑️ E0.7 rescue worker’s helmet
+26D1 ; unqualified # ⛑ E0.7 rescue worker’s helmet
+1F4FF ; fully-qualified # 📿 E1.0 prayer beads
+1F484 ; fully-qualified # 💄 E0.6 lipstick
+1F48D ; fully-qualified # 💍 E0.6 ring
+1F48E ; fully-qualified # 💎 E0.6 gem stone
+
+# subgroup: sound
+1F507 ; fully-qualified # 🔇 E1.0 muted speaker
+1F508 ; fully-qualified # 🔈 E0.7 speaker low volume
+1F509 ; fully-qualified # 🔉 E1.0 speaker medium volume
+1F50A ; fully-qualified # 🔊 E0.6 speaker high volume
+1F4E2 ; fully-qualified # 📢 E0.6 loudspeaker
+1F4E3 ; fully-qualified # 📣 E0.6 megaphone
+1F4EF ; fully-qualified # 📯 E1.0 postal horn
+1F514 ; fully-qualified # 🔔 E0.6 bell
+1F515 ; fully-qualified # 🔕 E1.0 bell with slash
+
+# subgroup: music
+1F3BC ; fully-qualified # 🎼 E0.6 musical score
+1F3B5 ; fully-qualified # 🎵 E0.6 musical note
+1F3B6 ; fully-qualified # 🎶 E0.6 musical notes
+1F399 FE0F ; fully-qualified # 🎙️ E0.7 studio microphone
+1F399 ; unqualified # 🎙 E0.7 studio microphone
+1F39A FE0F ; fully-qualified # 🎚️ E0.7 level slider
+1F39A ; unqualified # 🎚 E0.7 level slider
+1F39B FE0F ; fully-qualified # 🎛️ E0.7 control knobs
+1F39B ; unqualified # 🎛 E0.7 control knobs
+1F3A4 ; fully-qualified # 🎤 E0.6 microphone
+1F3A7 ; fully-qualified # 🎧 E0.6 headphone
+1F4FB ; fully-qualified # 📻 E0.6 radio
+
+# subgroup: musical-instrument
+1F3B7 ; fully-qualified # 🎷 E0.6 saxophone
+1FA97 ; fully-qualified # 🪗 E13.0 accordion
+1F3B8 ; fully-qualified # 🎸 E0.6 guitar
+1F3B9 ; fully-qualified # 🎹 E0.6 musical keyboard
+1F3BA ; fully-qualified # 🎺 E0.6 trumpet
+1F3BB ; fully-qualified # 🎻 E0.6 violin
+1FA95 ; fully-qualified # 🪕 E12.0 banjo
+1F941 ; fully-qualified # 🥁 E3.0 drum
+1FA98 ; fully-qualified # 🪘 E13.0 long drum
+1FA87 ; fully-qualified # 🪇 E15.0 maracas
+1FA88 ; fully-qualified # 🪈 E15.0 flute
+1FA89 ; fully-qualified # E16.0 harp
+
+# subgroup: phone
+1F4F1 ; fully-qualified # 📱 E0.6 mobile phone
+1F4F2 ; fully-qualified # 📲 E0.6 mobile phone with arrow
+260E FE0F ; fully-qualified # ☎️ E0.6 telephone
+260E ; unqualified # ☎ E0.6 telephone
+1F4DE ; fully-qualified # 📞 E0.6 telephone receiver
+1F4DF ; fully-qualified # 📟 E0.6 pager
+1F4E0 ; fully-qualified # 📠 E0.6 fax machine
+
+# subgroup: computer
+1F50B ; fully-qualified # 🔋 E0.6 battery
+1FAAB ; fully-qualified # 🪫 E14.0 low battery
+1F50C ; fully-qualified # 🔌 E0.6 electric plug
+1F4BB ; fully-qualified # 💻 E0.6 laptop
+1F5A5 FE0F ; fully-qualified # 🖥️ E0.7 desktop computer
+1F5A5 ; unqualified # 🖥 E0.7 desktop computer
+1F5A8 FE0F ; fully-qualified # 🖨️ E0.7 printer
+1F5A8 ; unqualified # 🖨 E0.7 printer
+2328 FE0F ; fully-qualified # ⌨️ E1.0 keyboard
+2328 ; unqualified # ⌨ E1.0 keyboard
+1F5B1 FE0F ; fully-qualified # 🖱️ E0.7 computer mouse
+1F5B1 ; unqualified # 🖱 E0.7 computer mouse
+1F5B2 FE0F ; fully-qualified # 🖲️ E0.7 trackball
+1F5B2 ; unqualified # 🖲 E0.7 trackball
+1F4BD ; fully-qualified # 💽 E0.6 computer disk
+1F4BE ; fully-qualified # 💾 E0.6 floppy disk
+1F4BF ; fully-qualified # 💿 E0.6 optical disk
+1F4C0 ; fully-qualified # 📀 E0.6 dvd
+1F9EE ; fully-qualified # 🧮 E11.0 abacus
+
+# subgroup: light & video
+1F3A5 ; fully-qualified # 🎥 E0.6 movie camera
+1F39E FE0F ; fully-qualified # 🎞️ E0.7 film frames
+1F39E ; unqualified # 🎞 E0.7 film frames
+1F4FD FE0F ; fully-qualified # 📽️ E0.7 film projector
+1F4FD ; unqualified # 📽 E0.7 film projector
+1F3AC ; fully-qualified # 🎬 E0.6 clapper board
+1F4FA ; fully-qualified # 📺 E0.6 television
+1F4F7 ; fully-qualified # 📷 E0.6 camera
+1F4F8 ; fully-qualified # 📸 E1.0 camera with flash
+1F4F9 ; fully-qualified # 📹 E0.6 video camera
+1F4FC ; fully-qualified # 📼 E0.6 videocassette
+1F50D ; fully-qualified # 🔍 E0.6 magnifying glass tilted left
+1F50E ; fully-qualified # 🔎 E0.6 magnifying glass tilted right
+1F56F FE0F ; fully-qualified # 🕯️ E0.7 candle
+1F56F ; unqualified # 🕯 E0.7 candle
+1F4A1 ; fully-qualified # 💡 E0.6 light bulb
+1F526 ; fully-qualified # 🔦 E0.6 flashlight
+1F3EE ; fully-qualified # 🏮 E0.6 red paper lantern
+1FA94 ; fully-qualified # 🪔 E12.0 diya lamp
+
+# subgroup: book-paper
+1F4D4 ; fully-qualified # 📔 E0.6 notebook with decorative cover
+1F4D5 ; fully-qualified # 📕 E0.6 closed book
+1F4D6 ; fully-qualified # 📖 E0.6 open book
+1F4D7 ; fully-qualified # 📗 E0.6 green book
+1F4D8 ; fully-qualified # 📘 E0.6 blue book
+1F4D9 ; fully-qualified # 📙 E0.6 orange book
+1F4DA ; fully-qualified # 📚 E0.6 books
+1F4D3 ; fully-qualified # 📓 E0.6 notebook
+1F4D2 ; fully-qualified # 📒 E0.6 ledger
+1F4C3 ; fully-qualified # 📃 E0.6 page with curl
+1F4DC ; fully-qualified # 📜 E0.6 scroll
+1F4C4 ; fully-qualified # 📄 E0.6 page facing up
+1F4F0 ; fully-qualified # 📰 E0.6 newspaper
+1F5DE FE0F ; fully-qualified # 🗞️ E0.7 rolled-up newspaper
+1F5DE ; unqualified # 🗞 E0.7 rolled-up newspaper
+1F4D1 ; fully-qualified # 📑 E0.6 bookmark tabs
+1F516 ; fully-qualified # 🔖 E0.6 bookmark
+1F3F7 FE0F ; fully-qualified # 🏷️ E0.7 label
+1F3F7 ; unqualified # 🏷 E0.7 label
+
+# subgroup: money
+1F4B0 ; fully-qualified # 💰 E0.6 money bag
+1FA99 ; fully-qualified # 🪙 E13.0 coin
+1F4B4 ; fully-qualified # 💴 E0.6 yen banknote
+1F4B5 ; fully-qualified # 💵 E0.6 dollar banknote
+1F4B6 ; fully-qualified # 💶 E1.0 euro banknote
+1F4B7 ; fully-qualified # 💷 E1.0 pound banknote
+1F4B8 ; fully-qualified # 💸 E0.6 money with wings
+1F4B3 ; fully-qualified # 💳 E0.6 credit card
+1F9FE ; fully-qualified # 🧾 E11.0 receipt
+1F4B9 ; fully-qualified # 💹 E0.6 chart increasing with yen
+
+# subgroup: mail
+2709 FE0F ; fully-qualified # ✉️ E0.6 envelope
+2709 ; unqualified # ✉ E0.6 envelope
+1F4E7 ; fully-qualified # 📧 E0.6 e-mail
+1F4E8 ; fully-qualified # 📨 E0.6 incoming envelope
+1F4E9 ; fully-qualified # 📩 E0.6 envelope with arrow
+1F4E4 ; fully-qualified # 📤 E0.6 outbox tray
+1F4E5 ; fully-qualified # 📥 E0.6 inbox tray
+1F4E6 ; fully-qualified # 📦 E0.6 package
+1F4EB ; fully-qualified # 📫 E0.6 closed mailbox with raised flag
+1F4EA ; fully-qualified # 📪 E0.6 closed mailbox with lowered flag
+1F4EC ; fully-qualified # 📬 E0.7 open mailbox with raised flag
+1F4ED ; fully-qualified # 📭 E0.7 open mailbox with lowered flag
+1F4EE ; fully-qualified # 📮 E0.6 postbox
+1F5F3 FE0F ; fully-qualified # 🗳️ E0.7 ballot box with ballot
+1F5F3 ; unqualified # 🗳 E0.7 ballot box with ballot
+
+# subgroup: writing
+270F FE0F ; fully-qualified # ✏️ E0.6 pencil
+270F ; unqualified # ✏ E0.6 pencil
+2712 FE0F ; fully-qualified # ✒️ E0.6 black nib
+2712 ; unqualified # ✒ E0.6 black nib
+1F58B FE0F ; fully-qualified # 🖋️ E0.7 fountain pen
+1F58B ; unqualified # 🖋 E0.7 fountain pen
+1F58A FE0F ; fully-qualified # 🖊️ E0.7 pen
+1F58A ; unqualified # 🖊 E0.7 pen
+1F58C FE0F ; fully-qualified # 🖌️ E0.7 paintbrush
+1F58C ; unqualified # 🖌 E0.7 paintbrush
+1F58D FE0F ; fully-qualified # 🖍️ E0.7 crayon
+1F58D ; unqualified # 🖍 E0.7 crayon
+1F4DD ; fully-qualified # 📝 E0.6 memo
+
+# subgroup: office
+1F4BC ; fully-qualified # 💼 E0.6 briefcase
+1F4C1 ; fully-qualified # 📁 E0.6 file folder
+1F4C2 ; fully-qualified # 📂 E0.6 open file folder
+1F5C2 FE0F ; fully-qualified # 🗂️ E0.7 card index dividers
+1F5C2 ; unqualified # 🗂 E0.7 card index dividers
+1F4C5 ; fully-qualified # 📅 E0.6 calendar
+1F4C6 ; fully-qualified # 📆 E0.6 tear-off calendar
+1F5D2 FE0F ; fully-qualified # 🗒️ E0.7 spiral notepad
+1F5D2 ; unqualified # 🗒 E0.7 spiral notepad
+1F5D3 FE0F ; fully-qualified # 🗓️ E0.7 spiral calendar
+1F5D3 ; unqualified # 🗓 E0.7 spiral calendar
+1F4C7 ; fully-qualified # 📇 E0.6 card index
+1F4C8 ; fully-qualified # 📈 E0.6 chart increasing
+1F4C9 ; fully-qualified # 📉 E0.6 chart decreasing
+1F4CA ; fully-qualified # 📊 E0.6 bar chart
+1F4CB ; fully-qualified # 📋 E0.6 clipboard
+1F4CC ; fully-qualified # 📌 E0.6 pushpin
+1F4CD ; fully-qualified # 📍 E0.6 round pushpin
+1F4CE ; fully-qualified # 📎 E0.6 paperclip
+1F587 FE0F ; fully-qualified # 🖇️ E0.7 linked paperclips
+1F587 ; unqualified # 🖇 E0.7 linked paperclips
+1F4CF ; fully-qualified # 📏 E0.6 straight ruler
+1F4D0 ; fully-qualified # 📐 E0.6 triangular ruler
+2702 FE0F ; fully-qualified # ✂️ E0.6 scissors
+2702 ; unqualified # ✂ E0.6 scissors
+1F5C3 FE0F ; fully-qualified # 🗃️ E0.7 card file box
+1F5C3 ; unqualified # 🗃 E0.7 card file box
+1F5C4 FE0F ; fully-qualified # 🗄️ E0.7 file cabinet
+1F5C4 ; unqualified # 🗄 E0.7 file cabinet
+1F5D1 FE0F ; fully-qualified # 🗑️ E0.7 wastebasket
+1F5D1 ; unqualified # 🗑 E0.7 wastebasket
+
+# subgroup: lock
+1F512 ; fully-qualified # 🔒 E0.6 locked
+1F513 ; fully-qualified # 🔓 E0.6 unlocked
+1F50F ; fully-qualified # 🔏 E0.6 locked with pen
+1F510 ; fully-qualified # 🔐 E0.6 locked with key
+1F511 ; fully-qualified # 🔑 E0.6 key
+1F5DD FE0F ; fully-qualified # 🗝️ E0.7 old key
+1F5DD ; unqualified # 🗝 E0.7 old key
+
+# subgroup: tool
+1F528 ; fully-qualified # 🔨 E0.6 hammer
+1FA93 ; fully-qualified # 🪓 E12.0 axe
+26CF FE0F ; fully-qualified # ⛏️ E0.7 pick
+26CF ; unqualified # ⛏ E0.7 pick
+2692 FE0F ; fully-qualified # ⚒️ E1.0 hammer and pick
+2692 ; unqualified # ⚒ E1.0 hammer and pick
+1F6E0 FE0F ; fully-qualified # 🛠️ E0.7 hammer and wrench
+1F6E0 ; unqualified # 🛠 E0.7 hammer and wrench
+1F5E1 FE0F ; fully-qualified # 🗡️ E0.7 dagger
+1F5E1 ; unqualified # 🗡 E0.7 dagger
+2694 FE0F ; fully-qualified # ⚔️ E1.0 crossed swords
+2694 ; unqualified # ⚔ E1.0 crossed swords
+1F4A3 ; fully-qualified # 💣 E0.6 bomb
+1FA83 ; fully-qualified # 🪃 E13.0 boomerang
+1F3F9 ; fully-qualified # 🏹 E1.0 bow and arrow
+1F6E1 FE0F ; fully-qualified # 🛡️ E0.7 shield
+1F6E1 ; unqualified # 🛡 E0.7 shield
+1FA9A ; fully-qualified # 🪚 E13.0 carpentry saw
+1F527 ; fully-qualified # 🔧 E0.6 wrench
+1FA9B ; fully-qualified # 🪛 E13.0 screwdriver
+1F529 ; fully-qualified # 🔩 E0.6 nut and bolt
+2699 FE0F ; fully-qualified # ⚙️ E1.0 gear
+2699 ; unqualified # ⚙ E1.0 gear
+1F5DC FE0F ; fully-qualified # 🗜️ E0.7 clamp
+1F5DC ; unqualified # 🗜 E0.7 clamp
+2696 FE0F ; fully-qualified # ⚖️ E1.0 balance scale
+2696 ; unqualified # ⚖ E1.0 balance scale
+1F9AF ; fully-qualified # 🦯 E12.0 white cane
+1F517 ; fully-qualified # 🔗 E0.6 link
+26D3 FE0F 200D 1F4A5 ; fully-qualified # ⛓️💥 E15.1 broken chain
+26D3 200D 1F4A5 ; unqualified # ⛓💥 E15.1 broken chain
+26D3 FE0F ; fully-qualified # ⛓️ E0.7 chains
+26D3 ; unqualified # ⛓ E0.7 chains
+1FA9D ; fully-qualified # 🪝 E13.0 hook
+1F9F0 ; fully-qualified # 🧰 E11.0 toolbox
+1F9F2 ; fully-qualified # 🧲 E11.0 magnet
+1FA9C ; fully-qualified # 🪜 E13.0 ladder
+1FA8F ; fully-qualified # E16.0 shovel
+
+# subgroup: science
+2697 FE0F ; fully-qualified # ⚗️ E1.0 alembic
+2697 ; unqualified # ⚗ E1.0 alembic
+1F9EA ; fully-qualified # 🧪 E11.0 test tube
+1F9EB ; fully-qualified # 🧫 E11.0 petri dish
+1F9EC ; fully-qualified # 🧬 E11.0 dna
+1F52C ; fully-qualified # 🔬 E1.0 microscope
+1F52D ; fully-qualified # 🔭 E1.0 telescope
+1F4E1 ; fully-qualified # 📡 E0.6 satellite antenna
+
+# subgroup: medical
+1F489 ; fully-qualified # 💉 E0.6 syringe
+1FA78 ; fully-qualified # 🩸 E12.0 drop of blood
+1F48A ; fully-qualified # 💊 E0.6 pill
+1FA79 ; fully-qualified # 🩹 E12.0 adhesive bandage
+1FA7C ; fully-qualified # 🩼 E14.0 crutch
+1FA7A ; fully-qualified # 🩺 E12.0 stethoscope
+1FA7B ; fully-qualified # 🩻 E14.0 x-ray
+
+# subgroup: household
+1F6AA ; fully-qualified # 🚪 E0.6 door
+1F6D7 ; fully-qualified # 🛗 E13.0 elevator
+1FA9E ; fully-qualified # 🪞 E13.0 mirror
+1FA9F ; fully-qualified # 🪟 E13.0 window
+1F6CF FE0F ; fully-qualified # 🛏️ E0.7 bed
+1F6CF ; unqualified # 🛏 E0.7 bed
+1F6CB FE0F ; fully-qualified # 🛋️ E0.7 couch and lamp
+1F6CB ; unqualified # 🛋 E0.7 couch and lamp
+1FA91 ; fully-qualified # 🪑 E12.0 chair
+1F6BD ; fully-qualified # 🚽 E0.6 toilet
+1FAA0 ; fully-qualified # 🪠 E13.0 plunger
+1F6BF ; fully-qualified # 🚿 E1.0 shower
+1F6C1 ; fully-qualified # 🛁 E1.0 bathtub
+1FAA4 ; fully-qualified # 🪤 E13.0 mouse trap
+1FA92 ; fully-qualified # 🪒 E12.0 razor
+1F9F4 ; fully-qualified # 🧴 E11.0 lotion bottle
+1F9F7 ; fully-qualified # 🧷 E11.0 safety pin
+1F9F9 ; fully-qualified # 🧹 E11.0 broom
+1F9FA ; fully-qualified # 🧺 E11.0 basket
+1F9FB ; fully-qualified # 🧻 E11.0 roll of paper
+1FAA3 ; fully-qualified # 🪣 E13.0 bucket
+1F9FC ; fully-qualified # 🧼 E11.0 soap
+1FAE7 ; fully-qualified # 🫧 E14.0 bubbles
+1FAA5 ; fully-qualified # 🪥 E13.0 toothbrush
+1F9FD ; fully-qualified # 🧽 E11.0 sponge
+1F9EF ; fully-qualified # 🧯 E11.0 fire extinguisher
+1F6D2 ; fully-qualified # 🛒 E3.0 shopping cart
+
+# subgroup: other-object
+1F6AC ; fully-qualified # 🚬 E0.6 cigarette
+26B0 FE0F ; fully-qualified # ⚰️ E1.0 coffin
+26B0 ; unqualified # ⚰ E1.0 coffin
+1FAA6 ; fully-qualified # 🪦 E13.0 headstone
+26B1 FE0F ; fully-qualified # ⚱️ E1.0 funeral urn
+26B1 ; unqualified # ⚱ E1.0 funeral urn
+1F9FF ; fully-qualified # 🧿 E11.0 nazar amulet
+1FAAC ; fully-qualified # 🪬 E14.0 hamsa
+1F5FF ; fully-qualified # 🗿 E0.6 moai
+1FAA7 ; fully-qualified # 🪧 E13.0 placard
+1FAAA ; fully-qualified # 🪪 E14.0 identification card
+
+# Objects subtotal: 314
+# Objects subtotal: 314 w/o modifiers
+
+# group: Symbols
+
+# subgroup: transport-sign
+1F3E7 ; fully-qualified # 🏧 E0.6 ATM sign
+1F6AE ; fully-qualified # 🚮 E1.0 litter in bin sign
+1F6B0 ; fully-qualified # 🚰 E1.0 potable water
+267F ; fully-qualified # ♿ E0.6 wheelchair symbol
+1F6B9 ; fully-qualified # 🚹 E0.6 men’s room
+1F6BA ; fully-qualified # 🚺 E0.6 women’s room
+1F6BB ; fully-qualified # 🚻 E0.6 restroom
+1F6BC ; fully-qualified # 🚼 E0.6 baby symbol
+1F6BE ; fully-qualified # 🚾 E0.6 water closet
+1F6C2 ; fully-qualified # 🛂 E1.0 passport control
+1F6C3 ; fully-qualified # 🛃 E1.0 customs
+1F6C4 ; fully-qualified # 🛄 E1.0 baggage claim
+1F6C5 ; fully-qualified # 🛅 E1.0 left luggage
+
+# subgroup: warning
+26A0 FE0F ; fully-qualified # ⚠️ E0.6 warning
+26A0 ; unqualified # ⚠ E0.6 warning
+1F6B8 ; fully-qualified # 🚸 E1.0 children crossing
+26D4 ; fully-qualified # ⛔ E0.6 no entry
+1F6AB ; fully-qualified # 🚫 E0.6 prohibited
+1F6B3 ; fully-qualified # 🚳 E1.0 no bicycles
+1F6AD ; fully-qualified # 🚭 E0.6 no smoking
+1F6AF ; fully-qualified # 🚯 E1.0 no littering
+1F6B1 ; fully-qualified # 🚱 E1.0 non-potable water
+1F6B7 ; fully-qualified # 🚷 E1.0 no pedestrians
+1F4F5 ; fully-qualified # 📵 E1.0 no mobile phones
+1F51E ; fully-qualified # 🔞 E0.6 no one under eighteen
+2622 FE0F ; fully-qualified # ☢️ E1.0 radioactive
+2622 ; unqualified # ☢ E1.0 radioactive
+2623 FE0F ; fully-qualified # ☣️ E1.0 biohazard
+2623 ; unqualified # ☣ E1.0 biohazard
+
+# subgroup: arrow
+2B06 FE0F ; fully-qualified # ⬆️ E0.6 up arrow
+2B06 ; unqualified # ⬆ E0.6 up arrow
+2197 FE0F ; fully-qualified # ↗️ E0.6 up-right arrow
+2197 ; unqualified # ↗ E0.6 up-right arrow
+27A1 FE0F ; fully-qualified # ➡️ E0.6 right arrow
+27A1 ; unqualified # ➡ E0.6 right arrow
+2198 FE0F ; fully-qualified # ↘️ E0.6 down-right arrow
+2198 ; unqualified # ↘ E0.6 down-right arrow
+2B07 FE0F ; fully-qualified # ⬇️ E0.6 down arrow
+2B07 ; unqualified # ⬇ E0.6 down arrow
+2199 FE0F ; fully-qualified # ↙️ E0.6 down-left arrow
+2199 ; unqualified # ↙ E0.6 down-left arrow
+2B05 FE0F ; fully-qualified # ⬅️ E0.6 left arrow
+2B05 ; unqualified # ⬅ E0.6 left arrow
+2196 FE0F ; fully-qualified # ↖️ E0.6 up-left arrow
+2196 ; unqualified # ↖ E0.6 up-left arrow
+2195 FE0F ; fully-qualified # ↕️ E0.6 up-down arrow
+2195 ; unqualified # ↕ E0.6 up-down arrow
+2194 FE0F ; fully-qualified # ↔️ E0.6 left-right arrow
+2194 ; unqualified # ↔ E0.6 left-right arrow
+21A9 FE0F ; fully-qualified # ↩️ E0.6 right arrow curving left
+21A9 ; unqualified # ↩ E0.6 right arrow curving left
+21AA FE0F ; fully-qualified # ↪️ E0.6 left arrow curving right
+21AA ; unqualified # ↪ E0.6 left arrow curving right
+2934 FE0F ; fully-qualified # ⤴️ E0.6 right arrow curving up
+2934 ; unqualified # ⤴ E0.6 right arrow curving up
+2935 FE0F ; fully-qualified # ⤵️ E0.6 right arrow curving down
+2935 ; unqualified # ⤵ E0.6 right arrow curving down
+1F503 ; fully-qualified # 🔃 E0.6 clockwise vertical arrows
+1F504 ; fully-qualified # 🔄 E1.0 counterclockwise arrows button
+1F519 ; fully-qualified # 🔙 E0.6 BACK arrow
+1F51A ; fully-qualified # 🔚 E0.6 END arrow
+1F51B ; fully-qualified # 🔛 E0.6 ON! arrow
+1F51C ; fully-qualified # 🔜 E0.6 SOON arrow
+1F51D ; fully-qualified # 🔝 E0.6 TOP arrow
+
+# subgroup: religion
+1F6D0 ; fully-qualified # 🛐 E1.0 place of worship
+269B FE0F ; fully-qualified # ⚛️ E1.0 atom symbol
+269B ; unqualified # ⚛ E1.0 atom symbol
+1F549 FE0F ; fully-qualified # 🕉️ E0.7 om
+1F549 ; unqualified # 🕉 E0.7 om
+2721 FE0F ; fully-qualified # ✡️ E0.7 star of David
+2721 ; unqualified # ✡ E0.7 star of David
+2638 FE0F ; fully-qualified # ☸️ E0.7 wheel of dharma
+2638 ; unqualified # ☸ E0.7 wheel of dharma
+262F FE0F ; fully-qualified # ☯️ E0.7 yin yang
+262F ; unqualified # ☯ E0.7 yin yang
+271D FE0F ; fully-qualified # ✝️ E0.7 latin cross
+271D ; unqualified # ✝ E0.7 latin cross
+2626 FE0F ; fully-qualified # ☦️ E1.0 orthodox cross
+2626 ; unqualified # ☦ E1.0 orthodox cross
+262A FE0F ; fully-qualified # ☪️ E0.7 star and crescent
+262A ; unqualified # ☪ E0.7 star and crescent
+262E FE0F ; fully-qualified # ☮️ E1.0 peace symbol
+262E ; unqualified # ☮ E1.0 peace symbol
+1F54E ; fully-qualified # 🕎 E1.0 menorah
+1F52F ; fully-qualified # 🔯 E0.6 dotted six-pointed star
+1FAAF ; fully-qualified # 🪯 E15.0 khanda
+
+# subgroup: zodiac
+2648 ; fully-qualified # ♈ E0.6 Aries
+2649 ; fully-qualified # ♉ E0.6 Taurus
+264A ; fully-qualified # ♊ E0.6 Gemini
+264B ; fully-qualified # ♋ E0.6 Cancer
+264C ; fully-qualified # ♌ E0.6 Leo
+264D ; fully-qualified # ♍ E0.6 Virgo
+264E ; fully-qualified # ♎ E0.6 Libra
+264F ; fully-qualified # ♏ E0.6 Scorpio
+2650 ; fully-qualified # ♐ E0.6 Sagittarius
+2651 ; fully-qualified # ♑ E0.6 Capricorn
+2652 ; fully-qualified # ♒ E0.6 Aquarius
+2653 ; fully-qualified # ♓ E0.6 Pisces
+26CE ; fully-qualified # ⛎ E0.6 Ophiuchus
+
+# subgroup: av-symbol
+1F500 ; fully-qualified # 🔀 E1.0 shuffle tracks button
+1F501 ; fully-qualified # 🔁 E1.0 repeat button
+1F502 ; fully-qualified # 🔂 E1.0 repeat single button
+25B6 FE0F ; fully-qualified # ▶️ E0.6 play button
+25B6 ; unqualified # ▶ E0.6 play button
+23E9 ; fully-qualified # ⏩ E0.6 fast-forward button
+23ED FE0F ; fully-qualified # ⏭️ E0.7 next track button
+23ED ; unqualified # ⏭ E0.7 next track button
+23EF FE0F ; fully-qualified # ⏯️ E1.0 play or pause button
+23EF ; unqualified # ⏯ E1.0 play or pause button
+25C0 FE0F ; fully-qualified # ◀️ E0.6 reverse button
+25C0 ; unqualified # ◀ E0.6 reverse button
+23EA ; fully-qualified # ⏪ E0.6 fast reverse button
+23EE FE0F ; fully-qualified # ⏮️ E0.7 last track button
+23EE ; unqualified # ⏮ E0.7 last track button
+1F53C ; fully-qualified # 🔼 E0.6 upwards button
+23EB ; fully-qualified # ⏫ E0.6 fast up button
+1F53D ; fully-qualified # 🔽 E0.6 downwards button
+23EC ; fully-qualified # ⏬ E0.6 fast down button
+23F8 FE0F ; fully-qualified # ⏸️ E0.7 pause button
+23F8 ; unqualified # ⏸ E0.7 pause button
+23F9 FE0F ; fully-qualified # ⏹️ E0.7 stop button
+23F9 ; unqualified # ⏹ E0.7 stop button
+23FA FE0F ; fully-qualified # ⏺️ E0.7 record button
+23FA ; unqualified # ⏺ E0.7 record button
+23CF FE0F ; fully-qualified # ⏏️ E1.0 eject button
+23CF ; unqualified # ⏏ E1.0 eject button
+1F3A6 ; fully-qualified # 🎦 E0.6 cinema
+1F505 ; fully-qualified # 🔅 E1.0 dim button
+1F506 ; fully-qualified # 🔆 E1.0 bright button
+1F4F6 ; fully-qualified # 📶 E0.6 antenna bars
+1F6DC ; fully-qualified # 🛜 E15.0 wireless
+1F4F3 ; fully-qualified # 📳 E0.6 vibration mode
+1F4F4 ; fully-qualified # 📴 E0.6 mobile phone off
+
+# subgroup: gender
+2640 FE0F ; fully-qualified # ♀️ E4.0 female sign
+2640 ; unqualified # ♀ E4.0 female sign
+2642 FE0F ; fully-qualified # ♂️ E4.0 male sign
+2642 ; unqualified # ♂ E4.0 male sign
+26A7 FE0F ; fully-qualified # ⚧️ E13.0 transgender symbol
+26A7 ; unqualified # ⚧ E13.0 transgender symbol
+
+# subgroup: math
+2716 FE0F ; fully-qualified # ✖️ E0.6 multiply
+2716 ; unqualified # ✖ E0.6 multiply
+2795 ; fully-qualified # ➕ E0.6 plus
+2796 ; fully-qualified # ➖ E0.6 minus
+2797 ; fully-qualified # ➗ E0.6 divide
+1F7F0 ; fully-qualified # 🟰 E14.0 heavy equals sign
+267E FE0F ; fully-qualified # ♾️ E11.0 infinity
+267E ; unqualified # ♾ E11.0 infinity
+
+# subgroup: punctuation
+203C FE0F ; fully-qualified # ‼️ E0.6 double exclamation mark
+203C ; unqualified # ‼ E0.6 double exclamation mark
+2049 FE0F ; fully-qualified # ⁉️ E0.6 exclamation question mark
+2049 ; unqualified # ⁉ E0.6 exclamation question mark
+2753 ; fully-qualified # ❓ E0.6 red question mark
+2754 ; fully-qualified # ❔ E0.6 white question mark
+2755 ; fully-qualified # ❕ E0.6 white exclamation mark
+2757 ; fully-qualified # ❗ E0.6 red exclamation mark
+3030 FE0F ; fully-qualified # 〰️ E0.6 wavy dash
+3030 ; unqualified # 〰 E0.6 wavy dash
+
+# subgroup: currency
+1F4B1 ; fully-qualified # 💱 E0.6 currency exchange
+1F4B2 ; fully-qualified # 💲 E0.6 heavy dollar sign
+
+# subgroup: other-symbol
+2695 FE0F ; fully-qualified # ⚕️ E4.0 medical symbol
+2695 ; unqualified # ⚕ E4.0 medical symbol
+267B FE0F ; fully-qualified # ♻️ E0.6 recycling symbol
+267B ; unqualified # ♻ E0.6 recycling symbol
+269C FE0F ; fully-qualified # ⚜️ E1.0 fleur-de-lis
+269C ; unqualified # ⚜ E1.0 fleur-de-lis
+1F531 ; fully-qualified # 🔱 E0.6 trident emblem
+1F4DB ; fully-qualified # 📛 E0.6 name badge
+1F530 ; fully-qualified # 🔰 E0.6 Japanese symbol for beginner
+2B55 ; fully-qualified # ⭕ E0.6 hollow red circle
+2705 ; fully-qualified # ✅ E0.6 check mark button
+2611 FE0F ; fully-qualified # ☑️ E0.6 check box with check
+2611 ; unqualified # ☑ E0.6 check box with check
+2714 FE0F ; fully-qualified # ✔️ E0.6 check mark
+2714 ; unqualified # ✔ E0.6 check mark
+274C ; fully-qualified # ❌ E0.6 cross mark
+274E ; fully-qualified # ❎ E0.6 cross mark button
+27B0 ; fully-qualified # ➰ E0.6 curly loop
+27BF ; fully-qualified # ➿ E1.0 double curly loop
+303D FE0F ; fully-qualified # 〽️ E0.6 part alternation mark
+303D ; unqualified # 〽 E0.6 part alternation mark
+2733 FE0F ; fully-qualified # ✳️ E0.6 eight-spoked asterisk
+2733 ; unqualified # ✳ E0.6 eight-spoked asterisk
+2734 FE0F ; fully-qualified # ✴️ E0.6 eight-pointed star
+2734 ; unqualified # ✴ E0.6 eight-pointed star
+2747 FE0F ; fully-qualified # ❇️ E0.6 sparkle
+2747 ; unqualified # ❇ E0.6 sparkle
+00A9 FE0F ; fully-qualified # ©️ E0.6 copyright
+00A9 ; unqualified # © E0.6 copyright
+00AE FE0F ; fully-qualified # ®️ E0.6 registered
+00AE ; unqualified # ® E0.6 registered
+2122 FE0F ; fully-qualified # ™️ E0.6 trade mark
+2122 ; unqualified # ™ E0.6 trade mark
+1FADF ; fully-qualified # E16.0 splatter
+
+# subgroup: keycap
+0023 FE0F 20E3 ; fully-qualified # #️⃣ E0.6 keycap: #
+0023 20E3 ; unqualified # #⃣ E0.6 keycap: #
+002A FE0F 20E3 ; fully-qualified # *️⃣ E2.0 keycap: *
+002A 20E3 ; unqualified # *⃣ E2.0 keycap: *
+0030 FE0F 20E3 ; fully-qualified # 0️⃣ E0.6 keycap: 0
+0030 20E3 ; unqualified # 0⃣ E0.6 keycap: 0
+0031 FE0F 20E3 ; fully-qualified # 1️⃣ E0.6 keycap: 1
+0031 20E3 ; unqualified # 1⃣ E0.6 keycap: 1
+0032 FE0F 20E3 ; fully-qualified # 2️⃣ E0.6 keycap: 2
+0032 20E3 ; unqualified # 2⃣ E0.6 keycap: 2
+0033 FE0F 20E3 ; fully-qualified # 3️⃣ E0.6 keycap: 3
+0033 20E3 ; unqualified # 3⃣ E0.6 keycap: 3
+0034 FE0F 20E3 ; fully-qualified # 4️⃣ E0.6 keycap: 4
+0034 20E3 ; unqualified # 4⃣ E0.6 keycap: 4
+0035 FE0F 20E3 ; fully-qualified # 5️⃣ E0.6 keycap: 5
+0035 20E3 ; unqualified # 5⃣ E0.6 keycap: 5
+0036 FE0F 20E3 ; fully-qualified # 6️⃣ E0.6 keycap: 6
+0036 20E3 ; unqualified # 6⃣ E0.6 keycap: 6
+0037 FE0F 20E3 ; fully-qualified # 7️⃣ E0.6 keycap: 7
+0037 20E3 ; unqualified # 7⃣ E0.6 keycap: 7
+0038 FE0F 20E3 ; fully-qualified # 8️⃣ E0.6 keycap: 8
+0038 20E3 ; unqualified # 8⃣ E0.6 keycap: 8
+0039 FE0F 20E3 ; fully-qualified # 9️⃣ E0.6 keycap: 9
+0039 20E3 ; unqualified # 9⃣ E0.6 keycap: 9
+1F51F ; fully-qualified # 🔟 E0.6 keycap: 10
+
+# subgroup: alphanum
+1F520 ; fully-qualified # 🔠 E0.6 input latin uppercase
+1F521 ; fully-qualified # 🔡 E0.6 input latin lowercase
+1F522 ; fully-qualified # 🔢 E0.6 input numbers
+1F523 ; fully-qualified # 🔣 E0.6 input symbols
+1F524 ; fully-qualified # 🔤 E0.6 input latin letters
+1F170 FE0F ; fully-qualified # 🅰️ E0.6 A button (blood type)
+1F170 ; unqualified # 🅰 E0.6 A button (blood type)
+1F18E ; fully-qualified # 🆎 E0.6 AB button (blood type)
+1F171 FE0F ; fully-qualified # 🅱️ E0.6 B button (blood type)
+1F171 ; unqualified # 🅱 E0.6 B button (blood type)
+1F191 ; fully-qualified # 🆑 E0.6 CL button
+1F192 ; fully-qualified # 🆒 E0.6 COOL button
+1F193 ; fully-qualified # 🆓 E0.6 FREE button
+2139 FE0F ; fully-qualified # ℹ️ E0.6 information
+2139 ; unqualified # ℹ E0.6 information
+1F194 ; fully-qualified # 🆔 E0.6 ID button
+24C2 FE0F ; fully-qualified # Ⓜ️ E0.6 circled M
+24C2 ; unqualified # Ⓜ E0.6 circled M
+1F195 ; fully-qualified # 🆕 E0.6 NEW button
+1F196 ; fully-qualified # 🆖 E0.6 NG button
+1F17E FE0F ; fully-qualified # 🅾️ E0.6 O button (blood type)
+1F17E ; unqualified # 🅾 E0.6 O button (blood type)
+1F197 ; fully-qualified # 🆗 E0.6 OK button
+1F17F FE0F ; fully-qualified # 🅿️ E0.6 P button
+1F17F ; unqualified # 🅿 E0.6 P button
+1F198 ; fully-qualified # 🆘 E0.6 SOS button
+1F199 ; fully-qualified # 🆙 E0.6 UP! button
+1F19A ; fully-qualified # 🆚 E0.6 VS button
+1F201 ; fully-qualified # 🈁 E0.6 Japanese “here” button
+1F202 FE0F ; fully-qualified # 🈂️ E0.6 Japanese “service charge” button
+1F202 ; unqualified # 🈂 E0.6 Japanese “service charge” button
+1F237 FE0F ; fully-qualified # 🈷️ E0.6 Japanese “monthly amount” button
+1F237 ; unqualified # 🈷 E0.6 Japanese “monthly amount” button
+1F236 ; fully-qualified # 🈶 E0.6 Japanese “not free of charge” button
+1F22F ; fully-qualified # 🈯 E0.6 Japanese “reserved” button
+1F250 ; fully-qualified # 🉐 E0.6 Japanese “bargain” button
+1F239 ; fully-qualified # 🈹 E0.6 Japanese “discount” button
+1F21A ; fully-qualified # 🈚 E0.6 Japanese “free of charge” button
+1F232 ; fully-qualified # 🈲 E0.6 Japanese “prohibited” button
+1F251 ; fully-qualified # 🉑 E0.6 Japanese “acceptable” button
+1F238 ; fully-qualified # 🈸 E0.6 Japanese “application” button
+1F234 ; fully-qualified # 🈴 E0.6 Japanese “passing grade” button
+1F233 ; fully-qualified # 🈳 E0.6 Japanese “vacancy” button
+3297 FE0F ; fully-qualified # ㊗️ E0.6 Japanese “congratulations” button
+3297 ; unqualified # ㊗ E0.6 Japanese “congratulations” button
+3299 FE0F ; fully-qualified # ㊙️ E0.6 Japanese “secret” button
+3299 ; unqualified # ㊙ E0.6 Japanese “secret” button
+1F23A ; fully-qualified # 🈺 E0.6 Japanese “open for business” button
+1F235 ; fully-qualified # 🈵 E0.6 Japanese “no vacancy” button
+
+# subgroup: geometric
+1F534 ; fully-qualified # 🔴 E0.6 red circle
+1F7E0 ; fully-qualified # 🟠 E12.0 orange circle
+1F7E1 ; fully-qualified # 🟡 E12.0 yellow circle
+1F7E2 ; fully-qualified # 🟢 E12.0 green circle
+1F535 ; fully-qualified # 🔵 E0.6 blue circle
+1F7E3 ; fully-qualified # 🟣 E12.0 purple circle
+1F7E4 ; fully-qualified # 🟤 E12.0 brown circle
+26AB ; fully-qualified # ⚫ E0.6 black circle
+26AA ; fully-qualified # ⚪ E0.6 white circle
+1F7E5 ; fully-qualified # 🟥 E12.0 red square
+1F7E7 ; fully-qualified # 🟧 E12.0 orange square
+1F7E8 ; fully-qualified # 🟨 E12.0 yellow square
+1F7E9 ; fully-qualified # 🟩 E12.0 green square
+1F7E6 ; fully-qualified # 🟦 E12.0 blue square
+1F7EA ; fully-qualified # 🟪 E12.0 purple square
+1F7EB ; fully-qualified # 🟫 E12.0 brown square
+2B1B ; fully-qualified # ⬛ E0.6 black large square
+2B1C ; fully-qualified # ⬜ E0.6 white large square
+25FC FE0F ; fully-qualified # ◼️ E0.6 black medium square
+25FC ; unqualified # ◼ E0.6 black medium square
+25FB FE0F ; fully-qualified # ◻️ E0.6 white medium square
+25FB ; unqualified # ◻ E0.6 white medium square
+25FE ; fully-qualified # ◾ E0.6 black medium-small square
+25FD ; fully-qualified # ◽ E0.6 white medium-small square
+25AA FE0F ; fully-qualified # ▪️ E0.6 black small square
+25AA ; unqualified # ▪ E0.6 black small square
+25AB FE0F ; fully-qualified # ▫️ E0.6 white small square
+25AB ; unqualified # ▫ E0.6 white small square
+1F536 ; fully-qualified # 🔶 E0.6 large orange diamond
+1F537 ; fully-qualified # 🔷 E0.6 large blue diamond
+1F538 ; fully-qualified # 🔸 E0.6 small orange diamond
+1F539 ; fully-qualified # 🔹 E0.6 small blue diamond
+1F53A ; fully-qualified # 🔺 E0.6 red triangle pointed up
+1F53B ; fully-qualified # 🔻 E0.6 red triangle pointed down
+1F4A0 ; fully-qualified # 💠 E0.6 diamond with a dot
+1F518 ; fully-qualified # 🔘 E0.6 radio button
+1F533 ; fully-qualified # 🔳 E0.6 white square button
+1F532 ; fully-qualified # 🔲 E0.6 black square button
+
+# Symbols subtotal: 305
+# Symbols subtotal: 305 w/o modifiers
+
+# group: Flags
+
+# subgroup: flag
+1F3C1 ; fully-qualified # 🏁 E0.6 chequered flag
+1F6A9 ; fully-qualified # 🚩 E0.6 triangular flag
+1F38C ; fully-qualified # 🎌 E0.6 crossed flags
+1F3F4 ; fully-qualified # 🏴 E1.0 black flag
+1F3F3 FE0F ; fully-qualified # 🏳️ E0.7 white flag
+1F3F3 ; unqualified # 🏳 E0.7 white flag
+1F3F3 FE0F 200D 1F308 ; fully-qualified # 🏳️🌈 E4.0 rainbow flag
+1F3F3 200D 1F308 ; unqualified # 🏳🌈 E4.0 rainbow flag
+1F3F3 FE0F 200D 26A7 FE0F ; fully-qualified # 🏳️⚧️ E13.0 transgender flag
+1F3F3 200D 26A7 FE0F ; unqualified # 🏳⚧️ E13.0 transgender flag
+1F3F3 FE0F 200D 26A7 ; minimally-qualified # 🏳️⚧ E13.0 transgender flag
+1F3F3 200D 26A7 ; unqualified # 🏳⚧ E13.0 transgender flag
+1F3F4 200D 2620 FE0F ; fully-qualified # 🏴☠️ E11.0 pirate flag
+1F3F4 200D 2620 ; minimally-qualified # 🏴☠ E11.0 pirate flag
+
+# subgroup: country-flag
+1F1E6 1F1E8 ; fully-qualified # 🇦🇨 E2.0 flag: Ascension Island
+1F1E6 1F1E9 ; fully-qualified # 🇦🇩 E2.0 flag: Andorra
+1F1E6 1F1EA ; fully-qualified # 🇦🇪 E2.0 flag: United Arab Emirates
+1F1E6 1F1EB ; fully-qualified # 🇦🇫 E2.0 flag: Afghanistan
+1F1E6 1F1EC ; fully-qualified # 🇦🇬 E2.0 flag: Antigua & Barbuda
+1F1E6 1F1EE ; fully-qualified # 🇦🇮 E2.0 flag: Anguilla
+1F1E6 1F1F1 ; fully-qualified # 🇦🇱 E2.0 flag: Albania
+1F1E6 1F1F2 ; fully-qualified # 🇦🇲 E2.0 flag: Armenia
+1F1E6 1F1F4 ; fully-qualified # 🇦🇴 E2.0 flag: Angola
+1F1E6 1F1F6 ; fully-qualified # 🇦🇶 E2.0 flag: Antarctica
+1F1E6 1F1F7 ; fully-qualified # 🇦🇷 E2.0 flag: Argentina
+1F1E6 1F1F8 ; fully-qualified # 🇦🇸 E2.0 flag: American Samoa
+1F1E6 1F1F9 ; fully-qualified # 🇦🇹 E2.0 flag: Austria
+1F1E6 1F1FA ; fully-qualified # 🇦🇺 E2.0 flag: Australia
+1F1E6 1F1FC ; fully-qualified # 🇦🇼 E2.0 flag: Aruba
+1F1E6 1F1FD ; fully-qualified # 🇦🇽 E2.0 flag: Åland Islands
+1F1E6 1F1FF ; fully-qualified # 🇦🇿 E2.0 flag: Azerbaijan
+1F1E7 1F1E6 ; fully-qualified # 🇧🇦 E2.0 flag: Bosnia & Herzegovina
+1F1E7 1F1E7 ; fully-qualified # 🇧🇧 E2.0 flag: Barbados
+1F1E7 1F1E9 ; fully-qualified # 🇧🇩 E2.0 flag: Bangladesh
+1F1E7 1F1EA ; fully-qualified # 🇧🇪 E2.0 flag: Belgium
+1F1E7 1F1EB ; fully-qualified # 🇧🇫 E2.0 flag: Burkina Faso
+1F1E7 1F1EC ; fully-qualified # 🇧🇬 E2.0 flag: Bulgaria
+1F1E7 1F1ED ; fully-qualified # 🇧🇭 E2.0 flag: Bahrain
+1F1E7 1F1EE ; fully-qualified # 🇧🇮 E2.0 flag: Burundi
+1F1E7 1F1EF ; fully-qualified # 🇧🇯 E2.0 flag: Benin
+1F1E7 1F1F1 ; fully-qualified # 🇧🇱 E2.0 flag: St. Barthélemy
+1F1E7 1F1F2 ; fully-qualified # 🇧🇲 E2.0 flag: Bermuda
+1F1E7 1F1F3 ; fully-qualified # 🇧🇳 E2.0 flag: Brunei
+1F1E7 1F1F4 ; fully-qualified # 🇧🇴 E2.0 flag: Bolivia
+1F1E7 1F1F6 ; fully-qualified # 🇧🇶 E2.0 flag: Caribbean Netherlands
+1F1E7 1F1F7 ; fully-qualified # 🇧🇷 E2.0 flag: Brazil
+1F1E7 1F1F8 ; fully-qualified # 🇧🇸 E2.0 flag: Bahamas
+1F1E7 1F1F9 ; fully-qualified # 🇧🇹 E2.0 flag: Bhutan
+1F1E7 1F1FB ; fully-qualified # 🇧🇻 E2.0 flag: Bouvet Island
+1F1E7 1F1FC ; fully-qualified # 🇧🇼 E2.0 flag: Botswana
+1F1E7 1F1FE ; fully-qualified # 🇧🇾 E2.0 flag: Belarus
+1F1E7 1F1FF ; fully-qualified # 🇧🇿 E2.0 flag: Belize
+1F1E8 1F1E6 ; fully-qualified # 🇨🇦 E2.0 flag: Canada
+1F1E8 1F1E8 ; fully-qualified # 🇨🇨 E2.0 flag: Cocos (Keeling) Islands
+1F1E8 1F1E9 ; fully-qualified # 🇨🇩 E2.0 flag: Congo - Kinshasa
+1F1E8 1F1EB ; fully-qualified # 🇨🇫 E2.0 flag: Central African Republic
+1F1E8 1F1EC ; fully-qualified # 🇨🇬 E2.0 flag: Congo - Brazzaville
+1F1E8 1F1ED ; fully-qualified # 🇨🇭 E2.0 flag: Switzerland
+1F1E8 1F1EE ; fully-qualified # 🇨🇮 E2.0 flag: Côte d’Ivoire
+1F1E8 1F1F0 ; fully-qualified # 🇨🇰 E2.0 flag: Cook Islands
+1F1E8 1F1F1 ; fully-qualified # 🇨🇱 E2.0 flag: Chile
+1F1E8 1F1F2 ; fully-qualified # 🇨🇲 E2.0 flag: Cameroon
+1F1E8 1F1F3 ; fully-qualified # 🇨🇳 E0.6 flag: China
+1F1E8 1F1F4 ; fully-qualified # 🇨🇴 E2.0 flag: Colombia
+1F1E8 1F1F5 ; fully-qualified # 🇨🇵 E2.0 flag: Clipperton Island
+1F1E8 1F1F6 ; fully-qualified # 🇨🇶 E16.0 flag: Sark
+1F1E8 1F1F7 ; fully-qualified # 🇨🇷 E2.0 flag: Costa Rica
+1F1E8 1F1FA ; fully-qualified # 🇨🇺 E2.0 flag: Cuba
+1F1E8 1F1FB ; fully-qualified # 🇨🇻 E2.0 flag: Cape Verde
+1F1E8 1F1FC ; fully-qualified # 🇨🇼 E2.0 flag: Curaçao
+1F1E8 1F1FD ; fully-qualified # 🇨🇽 E2.0 flag: Christmas Island
+1F1E8 1F1FE ; fully-qualified # 🇨🇾 E2.0 flag: Cyprus
+1F1E8 1F1FF ; fully-qualified # 🇨🇿 E2.0 flag: Czechia
+1F1E9 1F1EA ; fully-qualified # 🇩🇪 E0.6 flag: Germany
+1F1E9 1F1EC ; fully-qualified # 🇩🇬 E2.0 flag: Diego Garcia
+1F1E9 1F1EF ; fully-qualified # 🇩🇯 E2.0 flag: Djibouti
+1F1E9 1F1F0 ; fully-qualified # 🇩🇰 E2.0 flag: Denmark
+1F1E9 1F1F2 ; fully-qualified # 🇩🇲 E2.0 flag: Dominica
+1F1E9 1F1F4 ; fully-qualified # 🇩🇴 E2.0 flag: Dominican Republic
+1F1E9 1F1FF ; fully-qualified # 🇩🇿 E2.0 flag: Algeria
+1F1EA 1F1E6 ; fully-qualified # 🇪🇦 E2.0 flag: Ceuta & Melilla
+1F1EA 1F1E8 ; fully-qualified # 🇪🇨 E2.0 flag: Ecuador
+1F1EA 1F1EA ; fully-qualified # 🇪🇪 E2.0 flag: Estonia
+1F1EA 1F1EC ; fully-qualified # 🇪🇬 E2.0 flag: Egypt
+1F1EA 1F1ED ; fully-qualified # 🇪🇭 E2.0 flag: Western Sahara
+1F1EA 1F1F7 ; fully-qualified # 🇪🇷 E2.0 flag: Eritrea
+1F1EA 1F1F8 ; fully-qualified # 🇪🇸 E0.6 flag: Spain
+1F1EA 1F1F9 ; fully-qualified # 🇪🇹 E2.0 flag: Ethiopia
+1F1EA 1F1FA ; fully-qualified # 🇪🇺 E2.0 flag: European Union
+1F1EB 1F1EE ; fully-qualified # 🇫🇮 E2.0 flag: Finland
+1F1EB 1F1EF ; fully-qualified # 🇫🇯 E2.0 flag: Fiji
+1F1EB 1F1F0 ; fully-qualified # 🇫🇰 E2.0 flag: Falkland Islands
+1F1EB 1F1F2 ; fully-qualified # 🇫🇲 E2.0 flag: Micronesia
+1F1EB 1F1F4 ; fully-qualified # 🇫🇴 E2.0 flag: Faroe Islands
+1F1EB 1F1F7 ; fully-qualified # 🇫🇷 E0.6 flag: France
+1F1EC 1F1E6 ; fully-qualified # 🇬🇦 E2.0 flag: Gabon
+1F1EC 1F1E7 ; fully-qualified # 🇬🇧 E0.6 flag: United Kingdom
+1F1EC 1F1E9 ; fully-qualified # 🇬🇩 E2.0 flag: Grenada
+1F1EC 1F1EA ; fully-qualified # 🇬🇪 E2.0 flag: Georgia
+1F1EC 1F1EB ; fully-qualified # 🇬🇫 E2.0 flag: French Guiana
+1F1EC 1F1EC ; fully-qualified # 🇬🇬 E2.0 flag: Guernsey
+1F1EC 1F1ED ; fully-qualified # 🇬🇭 E2.0 flag: Ghana
+1F1EC 1F1EE ; fully-qualified # 🇬🇮 E2.0 flag: Gibraltar
+1F1EC 1F1F1 ; fully-qualified # 🇬🇱 E2.0 flag: Greenland
+1F1EC 1F1F2 ; fully-qualified # 🇬🇲 E2.0 flag: Gambia
+1F1EC 1F1F3 ; fully-qualified # 🇬🇳 E2.0 flag: Guinea
+1F1EC 1F1F5 ; fully-qualified # 🇬🇵 E2.0 flag: Guadeloupe
+1F1EC 1F1F6 ; fully-qualified # 🇬🇶 E2.0 flag: Equatorial Guinea
+1F1EC 1F1F7 ; fully-qualified # 🇬🇷 E2.0 flag: Greece
+1F1EC 1F1F8 ; fully-qualified # 🇬🇸 E2.0 flag: South Georgia & South Sandwich Islands
+1F1EC 1F1F9 ; fully-qualified # 🇬🇹 E2.0 flag: Guatemala
+1F1EC 1F1FA ; fully-qualified # 🇬🇺 E2.0 flag: Guam
+1F1EC 1F1FC ; fully-qualified # 🇬🇼 E2.0 flag: Guinea-Bissau
+1F1EC 1F1FE ; fully-qualified # 🇬🇾 E2.0 flag: Guyana
+1F1ED 1F1F0 ; fully-qualified # 🇭🇰 E2.0 flag: Hong Kong SAR China
+1F1ED 1F1F2 ; fully-qualified # 🇭🇲 E2.0 flag: Heard & McDonald Islands
+1F1ED 1F1F3 ; fully-qualified # 🇭🇳 E2.0 flag: Honduras
+1F1ED 1F1F7 ; fully-qualified # 🇭🇷 E2.0 flag: Croatia
+1F1ED 1F1F9 ; fully-qualified # 🇭🇹 E2.0 flag: Haiti
+1F1ED 1F1FA ; fully-qualified # 🇭🇺 E2.0 flag: Hungary
+1F1EE 1F1E8 ; fully-qualified # 🇮🇨 E2.0 flag: Canary Islands
+1F1EE 1F1E9 ; fully-qualified # 🇮🇩 E2.0 flag: Indonesia
+1F1EE 1F1EA ; fully-qualified # 🇮🇪 E2.0 flag: Ireland
+1F1EE 1F1F1 ; fully-qualified # 🇮🇱 E2.0 flag: Israel
+1F1EE 1F1F2 ; fully-qualified # 🇮🇲 E2.0 flag: Isle of Man
+1F1EE 1F1F3 ; fully-qualified # 🇮🇳 E2.0 flag: India
+1F1EE 1F1F4 ; fully-qualified # 🇮🇴 E2.0 flag: British Indian Ocean Territory
+1F1EE 1F1F6 ; fully-qualified # 🇮🇶 E2.0 flag: Iraq
+1F1EE 1F1F7 ; fully-qualified # 🇮🇷 E2.0 flag: Iran
+1F1EE 1F1F8 ; fully-qualified # 🇮🇸 E2.0 flag: Iceland
+1F1EE 1F1F9 ; fully-qualified # 🇮🇹 E0.6 flag: Italy
+1F1EF 1F1EA ; fully-qualified # 🇯🇪 E2.0 flag: Jersey
+1F1EF 1F1F2 ; fully-qualified # 🇯🇲 E2.0 flag: Jamaica
+1F1EF 1F1F4 ; fully-qualified # 🇯🇴 E2.0 flag: Jordan
+1F1EF 1F1F5 ; fully-qualified # 🇯🇵 E0.6 flag: Japan
+1F1F0 1F1EA ; fully-qualified # 🇰🇪 E2.0 flag: Kenya
+1F1F0 1F1EC ; fully-qualified # 🇰🇬 E2.0 flag: Kyrgyzstan
+1F1F0 1F1ED ; fully-qualified # 🇰🇭 E2.0 flag: Cambodia
+1F1F0 1F1EE ; fully-qualified # 🇰🇮 E2.0 flag: Kiribati
+1F1F0 1F1F2 ; fully-qualified # 🇰🇲 E2.0 flag: Comoros
+1F1F0 1F1F3 ; fully-qualified # 🇰🇳 E2.0 flag: St. Kitts & Nevis
+1F1F0 1F1F5 ; fully-qualified # 🇰🇵 E2.0 flag: North Korea
+1F1F0 1F1F7 ; fully-qualified # 🇰🇷 E0.6 flag: South Korea
+1F1F0 1F1FC ; fully-qualified # 🇰🇼 E2.0 flag: Kuwait
+1F1F0 1F1FE ; fully-qualified # 🇰🇾 E2.0 flag: Cayman Islands
+1F1F0 1F1FF ; fully-qualified # 🇰🇿 E2.0 flag: Kazakhstan
+1F1F1 1F1E6 ; fully-qualified # 🇱🇦 E2.0 flag: Laos
+1F1F1 1F1E7 ; fully-qualified # 🇱🇧 E2.0 flag: Lebanon
+1F1F1 1F1E8 ; fully-qualified # 🇱🇨 E2.0 flag: St. Lucia
+1F1F1 1F1EE ; fully-qualified # 🇱🇮 E2.0 flag: Liechtenstein
+1F1F1 1F1F0 ; fully-qualified # 🇱🇰 E2.0 flag: Sri Lanka
+1F1F1 1F1F7 ; fully-qualified # 🇱🇷 E2.0 flag: Liberia
+1F1F1 1F1F8 ; fully-qualified # 🇱🇸 E2.0 flag: Lesotho
+1F1F1 1F1F9 ; fully-qualified # 🇱🇹 E2.0 flag: Lithuania
+1F1F1 1F1FA ; fully-qualified # 🇱🇺 E2.0 flag: Luxembourg
+1F1F1 1F1FB ; fully-qualified # 🇱🇻 E2.0 flag: Latvia
+1F1F1 1F1FE ; fully-qualified # 🇱🇾 E2.0 flag: Libya
+1F1F2 1F1E6 ; fully-qualified # 🇲🇦 E2.0 flag: Morocco
+1F1F2 1F1E8 ; fully-qualified # 🇲🇨 E2.0 flag: Monaco
+1F1F2 1F1E9 ; fully-qualified # 🇲🇩 E2.0 flag: Moldova
+1F1F2 1F1EA ; fully-qualified # 🇲🇪 E2.0 flag: Montenegro
+1F1F2 1F1EB ; fully-qualified # 🇲🇫 E2.0 flag: St. Martin
+1F1F2 1F1EC ; fully-qualified # 🇲🇬 E2.0 flag: Madagascar
+1F1F2 1F1ED ; fully-qualified # 🇲🇭 E2.0 flag: Marshall Islands
+1F1F2 1F1F0 ; fully-qualified # 🇲🇰 E2.0 flag: North Macedonia
+1F1F2 1F1F1 ; fully-qualified # 🇲🇱 E2.0 flag: Mali
+1F1F2 1F1F2 ; fully-qualified # 🇲🇲 E2.0 flag: Myanmar (Burma)
+1F1F2 1F1F3 ; fully-qualified # 🇲🇳 E2.0 flag: Mongolia
+1F1F2 1F1F4 ; fully-qualified # 🇲🇴 E2.0 flag: Macao SAR China
+1F1F2 1F1F5 ; fully-qualified # 🇲🇵 E2.0 flag: Northern Mariana Islands
+1F1F2 1F1F6 ; fully-qualified # 🇲🇶 E2.0 flag: Martinique
+1F1F2 1F1F7 ; fully-qualified # 🇲🇷 E2.0 flag: Mauritania
+1F1F2 1F1F8 ; fully-qualified # 🇲🇸 E2.0 flag: Montserrat
+1F1F2 1F1F9 ; fully-qualified # 🇲🇹 E2.0 flag: Malta
+1F1F2 1F1FA ; fully-qualified # 🇲🇺 E2.0 flag: Mauritius
+1F1F2 1F1FB ; fully-qualified # 🇲🇻 E2.0 flag: Maldives
+1F1F2 1F1FC ; fully-qualified # 🇲🇼 E2.0 flag: Malawi
+1F1F2 1F1FD ; fully-qualified # 🇲🇽 E2.0 flag: Mexico
+1F1F2 1F1FE ; fully-qualified # 🇲🇾 E2.0 flag: Malaysia
+1F1F2 1F1FF ; fully-qualified # 🇲🇿 E2.0 flag: Mozambique
+1F1F3 1F1E6 ; fully-qualified # 🇳🇦 E2.0 flag: Namibia
+1F1F3 1F1E8 ; fully-qualified # 🇳🇨 E2.0 flag: New Caledonia
+1F1F3 1F1EA ; fully-qualified # 🇳🇪 E2.0 flag: Niger
+1F1F3 1F1EB ; fully-qualified # 🇳🇫 E2.0 flag: Norfolk Island
+1F1F3 1F1EC ; fully-qualified # 🇳🇬 E2.0 flag: Nigeria
+1F1F3 1F1EE ; fully-qualified # 🇳🇮 E2.0 flag: Nicaragua
+1F1F3 1F1F1 ; fully-qualified # 🇳🇱 E2.0 flag: Netherlands
+1F1F3 1F1F4 ; fully-qualified # 🇳🇴 E2.0 flag: Norway
+1F1F3 1F1F5 ; fully-qualified # 🇳🇵 E2.0 flag: Nepal
+1F1F3 1F1F7 ; fully-qualified # 🇳🇷 E2.0 flag: Nauru
+1F1F3 1F1FA ; fully-qualified # 🇳🇺 E2.0 flag: Niue
+1F1F3 1F1FF ; fully-qualified # 🇳🇿 E2.0 flag: New Zealand
+1F1F4 1F1F2 ; fully-qualified # 🇴🇲 E2.0 flag: Oman
+1F1F5 1F1E6 ; fully-qualified # 🇵🇦 E2.0 flag: Panama
+1F1F5 1F1EA ; fully-qualified # 🇵🇪 E2.0 flag: Peru
+1F1F5 1F1EB ; fully-qualified # 🇵🇫 E2.0 flag: French Polynesia
+1F1F5 1F1EC ; fully-qualified # 🇵🇬 E2.0 flag: Papua New Guinea
+1F1F5 1F1ED ; fully-qualified # 🇵🇭 E2.0 flag: Philippines
+1F1F5 1F1F0 ; fully-qualified # 🇵🇰 E2.0 flag: Pakistan
+1F1F5 1F1F1 ; fully-qualified # 🇵🇱 E2.0 flag: Poland
+1F1F5 1F1F2 ; fully-qualified # 🇵🇲 E2.0 flag: St. Pierre & Miquelon
+1F1F5 1F1F3 ; fully-qualified # 🇵🇳 E2.0 flag: Pitcairn Islands
+1F1F5 1F1F7 ; fully-qualified # 🇵🇷 E2.0 flag: Puerto Rico
+1F1F5 1F1F8 ; fully-qualified # 🇵🇸 E2.0 flag: Palestinian Territories
+1F1F5 1F1F9 ; fully-qualified # 🇵🇹 E2.0 flag: Portugal
+1F1F5 1F1FC ; fully-qualified # 🇵🇼 E2.0 flag: Palau
+1F1F5 1F1FE ; fully-qualified # 🇵🇾 E2.0 flag: Paraguay
+1F1F6 1F1E6 ; fully-qualified # 🇶🇦 E2.0 flag: Qatar
+1F1F7 1F1EA ; fully-qualified # 🇷🇪 E2.0 flag: Réunion
+1F1F7 1F1F4 ; fully-qualified # 🇷🇴 E2.0 flag: Romania
+1F1F7 1F1F8 ; fully-qualified # 🇷🇸 E2.0 flag: Serbia
+1F1F7 1F1FA ; fully-qualified # 🇷🇺 E0.6 flag: Russia
+1F1F7 1F1FC ; fully-qualified # 🇷🇼 E2.0 flag: Rwanda
+1F1F8 1F1E6 ; fully-qualified # 🇸🇦 E2.0 flag: Saudi Arabia
+1F1F8 1F1E7 ; fully-qualified # 🇸🇧 E2.0 flag: Solomon Islands
+1F1F8 1F1E8 ; fully-qualified # 🇸🇨 E2.0 flag: Seychelles
+1F1F8 1F1E9 ; fully-qualified # 🇸🇩 E2.0 flag: Sudan
+1F1F8 1F1EA ; fully-qualified # 🇸🇪 E2.0 flag: Sweden
+1F1F8 1F1EC ; fully-qualified # 🇸🇬 E2.0 flag: Singapore
+1F1F8 1F1ED ; fully-qualified # 🇸🇭 E2.0 flag: St. Helena
+1F1F8 1F1EE ; fully-qualified # 🇸🇮 E2.0 flag: Slovenia
+1F1F8 1F1EF ; fully-qualified # 🇸🇯 E2.0 flag: Svalbard & Jan Mayen
+1F1F8 1F1F0 ; fully-qualified # 🇸🇰 E2.0 flag: Slovakia
+1F1F8 1F1F1 ; fully-qualified # 🇸🇱 E2.0 flag: Sierra Leone
+1F1F8 1F1F2 ; fully-qualified # 🇸🇲 E2.0 flag: San Marino
+1F1F8 1F1F3 ; fully-qualified # 🇸🇳 E2.0 flag: Senegal
+1F1F8 1F1F4 ; fully-qualified # 🇸🇴 E2.0 flag: Somalia
+1F1F8 1F1F7 ; fully-qualified # 🇸🇷 E2.0 flag: Suriname
+1F1F8 1F1F8 ; fully-qualified # 🇸🇸 E2.0 flag: South Sudan
+1F1F8 1F1F9 ; fully-qualified # 🇸🇹 E2.0 flag: São Tomé & Príncipe
+1F1F8 1F1FB ; fully-qualified # 🇸🇻 E2.0 flag: El Salvador
+1F1F8 1F1FD ; fully-qualified # 🇸🇽 E2.0 flag: Sint Maarten
+1F1F8 1F1FE ; fully-qualified # 🇸🇾 E2.0 flag: Syria
+1F1F8 1F1FF ; fully-qualified # 🇸🇿 E2.0 flag: Eswatini
+1F1F9 1F1E6 ; fully-qualified # 🇹🇦 E2.0 flag: Tristan da Cunha
+1F1F9 1F1E8 ; fully-qualified # 🇹🇨 E2.0 flag: Turks & Caicos Islands
+1F1F9 1F1E9 ; fully-qualified # 🇹🇩 E2.0 flag: Chad
+1F1F9 1F1EB ; fully-qualified # 🇹🇫 E2.0 flag: French Southern Territories
+1F1F9 1F1EC ; fully-qualified # 🇹🇬 E2.0 flag: Togo
+1F1F9 1F1ED ; fully-qualified # 🇹🇭 E2.0 flag: Thailand
+1F1F9 1F1EF ; fully-qualified # 🇹🇯 E2.0 flag: Tajikistan
+1F1F9 1F1F0 ; fully-qualified # 🇹🇰 E2.0 flag: Tokelau
+1F1F9 1F1F1 ; fully-qualified # 🇹🇱 E2.0 flag: Timor-Leste
+1F1F9 1F1F2 ; fully-qualified # 🇹🇲 E2.0 flag: Turkmenistan
+1F1F9 1F1F3 ; fully-qualified # 🇹🇳 E2.0 flag: Tunisia
+1F1F9 1F1F4 ; fully-qualified # 🇹🇴 E2.0 flag: Tonga
+1F1F9 1F1F7 ; fully-qualified # 🇹🇷 E2.0 flag: Türkiye
+1F1F9 1F1F9 ; fully-qualified # 🇹🇹 E2.0 flag: Trinidad & Tobago
+1F1F9 1F1FB ; fully-qualified # 🇹🇻 E2.0 flag: Tuvalu
+1F1F9 1F1FC ; fully-qualified # 🇹🇼 E2.0 flag: Taiwan
+1F1F9 1F1FF ; fully-qualified # 🇹🇿 E2.0 flag: Tanzania
+1F1FA 1F1E6 ; fully-qualified # 🇺🇦 E2.0 flag: Ukraine
+1F1FA 1F1EC ; fully-qualified # 🇺🇬 E2.0 flag: Uganda
+1F1FA 1F1F2 ; fully-qualified # 🇺🇲 E2.0 flag: U.S. Outlying Islands
+1F1FA 1F1F3 ; fully-qualified # 🇺🇳 E4.0 flag: United Nations
+1F1FA 1F1F8 ; fully-qualified # 🇺🇸 E0.6 flag: United States
+1F1FA 1F1FE ; fully-qualified # 🇺🇾 E2.0 flag: Uruguay
+1F1FA 1F1FF ; fully-qualified # 🇺🇿 E2.0 flag: Uzbekistan
+1F1FB 1F1E6 ; fully-qualified # 🇻🇦 E2.0 flag: Vatican City
+1F1FB 1F1E8 ; fully-qualified # 🇻🇨 E2.0 flag: St. Vincent & Grenadines
+1F1FB 1F1EA ; fully-qualified # 🇻🇪 E2.0 flag: Venezuela
+1F1FB 1F1EC ; fully-qualified # 🇻🇬 E2.0 flag: British Virgin Islands
+1F1FB 1F1EE ; fully-qualified # 🇻🇮 E2.0 flag: U.S. Virgin Islands
+1F1FB 1F1F3 ; fully-qualified # 🇻🇳 E2.0 flag: Vietnam
+1F1FB 1F1FA ; fully-qualified # 🇻🇺 E2.0 flag: Vanuatu
+1F1FC 1F1EB ; fully-qualified # 🇼🇫 E2.0 flag: Wallis & Futuna
+1F1FC 1F1F8 ; fully-qualified # 🇼🇸 E2.0 flag: Samoa
+1F1FD 1F1F0 ; fully-qualified # 🇽🇰 E2.0 flag: Kosovo
+1F1FE 1F1EA ; fully-qualified # 🇾🇪 E2.0 flag: Yemen
+1F1FE 1F1F9 ; fully-qualified # 🇾🇹 E2.0 flag: Mayotte
+1F1FF 1F1E6 ; fully-qualified # 🇿🇦 E2.0 flag: South Africa
+1F1FF 1F1F2 ; fully-qualified # 🇿🇲 E2.0 flag: Zambia
+1F1FF 1F1FC ; fully-qualified # 🇿🇼 E2.0 flag: Zimbabwe
+
+# subgroup: subdivision-flag
+1F3F4 E0067 E0062 E0065 E006E E0067 E007F ; fully-qualified # 🏴 E5.0 flag: England
+1F3F4 E0067 E0062 E0073 E0063 E0074 E007F ; fully-qualified # 🏴 E5.0 flag: Scotland
+1F3F4 E0067 E0062 E0077 E006C E0073 E007F ; fully-qualified # 🏴 E5.0 flag: Wales
+
+# Flags subtotal: 276
+# Flags subtotal: 276 w/o modifiers
+
+# Status Counts
+# fully-qualified : 3781
+# minimally-qualified : 1009
+# unqualified : 243
+# component : 9
+
+#EOF
diff --git a/pkgs/characters/third_party/Wikipedia/License CC BY-SA 3.0.txt b/pkgs/characters/third_party/Wikipedia/License CC BY-SA 3.0.txt
new file mode 100644
index 0000000..d6e56c1
--- /dev/null
+++ b/pkgs/characters/third_party/Wikipedia/License CC BY-SA 3.0.txt
@@ -0,0 +1,69 @@
+
+Creative Commons Attribution-ShareAlike 3.0 Unported License
+https://creativecommons.org/licenses/by-sa/3.0/
+
+License
+-------
+
+CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
+
+1. Definitions
+"Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License.
+"Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License.
+"Creative Commons Compatible License" means a license that is listed at https://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License.
+"Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership.
+"License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike.
+"Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License.
+"Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast.
+"Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work.
+"You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
+"Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images.
+"Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium.
+2. Fair Dealing Rights
+Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws.
+
+3. License Grant
+Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
+
+to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections;
+to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified.";
+to Distribute and Publicly Perform the Work including as incorporated in Collections; and,
+to Distribute and Publicly Perform Adaptations.
+For the avoidance of doubt:
+Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License;
+Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and,
+Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License.
+The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved.
+
+4. Restrictions
+The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
+
+You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested.
+You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License.
+If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties.
+Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise.
+5. Representations, Warranties and Disclaimer
+UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
+
+6. Limitation on Liability
+EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. Termination
+This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
+Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
+8. Miscellaneous
+Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
+Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
+If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
+No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
+This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
+The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law.
+Creative Commons Notice
+Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor.
+
+Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License.
+
+Creative Commons may be contacted at https://creativecommons.org/.
\ No newline at end of file
diff --git a/pkgs/characters/third_party/Wikipedia/hangul.txt b/pkgs/characters/third_party/Wikipedia/hangul.txt
new file mode 100644
index 0000000..5cb2225
--- /dev/null
+++ b/pkgs/characters/third_party/Wikipedia/hangul.txt
@@ -0,0 +1,488 @@
+한글
+위키백과, 우리 모두의 백과사전.
+둘러보기로 가기검색하러 가기
+ 이 문서에는 옛 한글이 포함되어 있습니다.
+관련 글꼴이 설치되지 않은 경우, 일부 문자가 깨진 글자로 표시될 수 있습니다. 옛 한글 도움말을 참고하여 볼 수 있습니다.
+ 다른 뜻에 대해서는 한글 (동음이의) 문서를 참조하십시오.
+ 서적에 대해서는 훈민정음 문서를, 언어에 대해서는 한국어 문서를 참조하십시오.
+한글 · 조선글
+
+
+한글의 구조
+한글의 구조
+원래 이름 훈민정음(訓民正音)
+유형 음소문자
+표기 언어 한국어, 제주어, 찌아찌아어
+사용 시기 1443년 ~ 현재
+창제자 세종
+ISO 15924 Hang
+한국어의 표기법
+문자
+한글
+한자
+한글 점자
+한글전용
+국한문혼용
+이두
+향찰
+구결
+로마자 표기법
+국어의 로마자 표기법
+매큔-라이샤워 표기법
+예일 로마자 표기법
+ISO/TR 11941
+김복문 로마자 표기법
+양병선 로마자 표기법
+21세기 로마자 표기법
+음소문자의 역사
+원시 시나이 문자 (기원전 18 ~ 15세기)
+
+우가리트 문자 (기원전 15세기)
+원시 가나안 문자 (기원전 14세기)
+페니키아 문자 (기원전 11세기)
+고대 히브리 문자 (기원전 10세기)
+사마리아 문자 (기원전 6세기)
+아람 문자 (기원전 8세기)
+브라흐미 문자와 인도 문자 (기원전 6세기)
+티베트 문자 (7세기)
+크메르 문자/자와 문자 (9세기)
+히브리 문자 (기원전 3세기)
+시리아 문자 (기원전 2세기)
+나바테아 문자 (기원전 2세기)
+아랍 문자 (4세기)
+타나 문자 (18세기)
+소그드 문자 (기원전 4세기)
+돌궐 문자 (5세기)
+로바쉬 문자 (12세기)
+위구르 문자 (8세기)
+몽골 문자 (13세기)
+만주 문자 (16세기)
+팔라비 문자 (기원전 3세기)
+아베스타 문자 (4세기)
+그리스 문자 (기원전 9세기)
+에트루리아 문자 (기원전 8세기)
+로마자 (기원전 7세기)
+룬 문자 (2세기)
+고트 문자 (3세기)
+콥트 문자 (300년)
+아르메니아 문자 (405년)
+조지아 문자 (5세기)
+글라골 문자 (862년)
+키릴 문자 (10세기)
+아부르 문자 (1372년)
+고대 히스파니아 문자 (기원전 7세기)
+남아랍 문자 금석문 (기원전 9세기)
+그으즈 문자 (기원전 5 ~ 6세기)
+메로이트 문자 (기원전 3세기)
+오검 문자 (4세기)
+한글 1443년
+캐나다 문자 1840년
+주음부호 1913년
+전체 분류
+v • d • e • h
+한글은 발음기관과 하늘, 땅, 사람을 본따 고안된 음소문자로, 닿소리 14자에 홀소리 10자 총 24자로 구성되어 있다. "나랏말이 중국과 달라" 문제를 느낀 조선 세종이 한국어를 표기하기 위하여 1443년 창제, 1446년 반포하였다. 낱자가 음가만 표기하기 때문에 갈래로는 음소문자에 속하나, 네모 칸에 초성, 중성, 종성을 이루는 자모음을 한데 모아 쓰는 방식 때문에 음절문자의 특성도 일부 지닌다. 원래 글자 수는 닿소리 17자에 홀소리 11자 총 28자였으나 이후 4자가 소실, 24자만 쓰이게 되었다. 대한민국과 조선민주주의인민공화국과 옌볜 조선족 자치주에서는 공용 문자로, 인도네시아 부톤 섬에서는 찌아찌아어의 보조 문자로 채택되었다. 북한에서는 조선글(朝鮮-)이라 한다.
+
+세계에서 유일하게 만든 사람,만든 이유,만든 날짜를 아는 글자이다.
+
+
+목차
+1 명칭
+2 역사
+2.1 창제
+2.2 창제 논란
+2.3 조선
+2.4 근대 이후
+2.5 현대 이후
+3 창제원리
+4 구조
+4.1 낱자
+4.2 모아쓰기
+4.3 표기 가능한 글자 수와 소리나는 음절 개수
+5 한글의 유래
+6 한글에 관한 여러 이설
+6.1 파스파 문자 기원설
+6.2 기타 한글과 유사하다고 주장하는 문자
+6.2.1 가림토와 신대 문자
+6.2.2 구자라트 문자
+6.3 다른 언어에서의 한글 사용
+7 오해와 사실
+8 한글 자모일람
+8.1 방언 한글 자모
+8.2 고문 한글 자모
+8.3 복합원음와 보음
+9 관련 항목
+10 각주
+11 참고 문헌
+12 읽을거리
+13 외부 링크
+명칭
+창제 때는 백성(民)을 가르치는(訓) 바른(正) 소리(音), 훈민정음(訓民正音)이라 하였고, 줄여서 정음(正音)이라고도 했다.
+
+'한글'이라는 이름은 주시경이 ‘큰’, ‘바른’, ‘하나’를 뜻하는 고유어 ‘한’을 차용하여 지었다. 하지만 주시경의 의도한 뜻이 무엇이었는지는 명확히 밝혀진 바가 없다.
+
+한글 창제 당시에는 백성을 가르치는 바른소리라는 뜻으로 훈민정음이라 하였고, 줄여서 정음(正音)이라고도 하였다. 조선시대에는 지식층으로부터 경시되며, 본래의 이름으로 쓰지 않고 막연히 언문(諺文)[1], 언서(諺書)[2], 반절(反切)[3] 로 불리거나, 혹은 암클(여성들이 배우는 글), 아햇글(어린이들이 배우는 글)이라고 불렀다고 알려져 있다. (단, 암클, 아햇글이라는 표현은 그 출처가 불분명하다.) 1894년 갑오개혁 이후 국서(國書), 국문(國文)이라고 불렀고 혹은 조선글로 부르기도 하였는데 이것은 한국의 글이라는 보통 이름일 뿐이며, 고유명사로 한글이라는 이름이 널리 쓰이기 전에는 가갸, 정음 등으로 불렀다.
+
+처음 한글이라는 이름이 사용된 것에대한 명확한 기록은 없다. 다만 1913년 3월 23일 주시경이 ‘배달말글몯음(조선어문회, 朝鮮言文會)[4]’를 ‘한글모’로 바꾼 바 있고[5], 같은 해 9월 최남선의 출판사 ‘신문관(新文館)’에서 창간한 어린이 잡지 《아이들 보이》의 끝에 가로글씨로 '한글풀이’라 한 것이 있고[6], 1914년 4월에 ‘조선어강습원(朝鮮語講習院)’이 ‘한글배곧’으로 이름을 바꾼 것 등으로 볼 때 1913년 무렵 주시경이 처음으로 사용한 것으로 보이며, 1927년에는 조선어학회 회원들이 《한글》이라는 잡지를 매달 발간하였다. 한글이라는 명칭이 일반화된 것은 1928년 11월 11일 조선어연구회에서 가갸날을 한글날로 고쳐 부른 때부터라고 한다.
+
+현재 한글의 명칭을 대한민국에서는 한글로, 조선민주주의인민공화국에서는 조선어자모로 부르는데[7], 2001년 2월 중국 옌지에서 열린 ‘제5차 코리안 컴퓨터 처리 국제 학술 대회(ICCKL 2001)’에서는 남과 북, 해외 동포 학자들이 국제 표준화 기구(ISO)에 등록하기 위한 명칭으로 ‘정음(Jeong'eum)’을 쓰기로 합의하였다.
+
+다른 나라에서는 한글(Hangul/Hangeul)이라는 이름을 많이 쓰지만, 중국에서는 조선 자모(중국어: 朝鮮字母, 병음: Cháoxiǎn zìmǔ 차오셴 쯔무[*])와 같은 이름을 쓴다. 일본에서는 한글은 물론 한국어를 ‘한구루(한글)(ハングル)’로 부르기도 하는데, 이는 NHK 방송에서 한국어 강좌를 설립시에 대한민국의 ‘한국어’와 조선민주주의인민공화국의 ‘조선어’ 사이에서 중립적인 위치를 지키기 위해 한국어 강좌 명칭으로 '한글강좌'를 사용하여 많은 일본인들이 이를 보고 한글의 뜻을 한국어로 오해한 것이다.
+
+한글이라는 이름은 본디 문자의 이름이지만, 관용적으로는 한국어를 한글로 적은 것이라는 의미로 책이나 소프트웨어, 게임 등의 한국어 번역 작업을 한글화라 하고 번역본을 한글판이라 부르기도 한다. 그리고 한글 이름, 한글 지명처럼 고유어라는 의미로 쓰이기도 한다. 하지만 표준국어대사전에서는 두 의미 모두 등재되지 않았으며, 한국어화, 한국어판이 맞는 표현이다.
+
+역사
+
+《훈민정음 언해》의 서두
+창제
+한국은 삼국시대부터 이두(吏讀)와 구결(口訣)을 써 왔는데, 구결은 본래 한문에 구두(句讀)를 떼는 데 쓰기 위한 일종의 보조적 편법에 지나지 않았고, 이두는 비록 한국어를 표시함에 틀림이 없었지만 한국어를 자유자재로 적을 수 없었으며, 그 표기법의 일원성(一元性)이 없어서 설사 이두로써 족하다 해도 한자교육이 선행되어야 했다. 이러한 문자생활의 불편은 한자를 쓰지 않고도, 배우기 쉽고 쓰기 쉬운 새로운 글자의 출현이 절실히 요구되었다.
+
+이러한 사조가 세종때에 특히 두드러져 드디어 1443년 음력 12월에 문자혁명의 결실을 보게 되었다. 훈민정음 창제의 취지에 관하여는 세종이 손수 저술한 《훈민정음》 예의편(例義篇) 첫머리에 잘 나타나 있는데, 첫째 한국어는 중국말과 다르므로 한자를 가지고는 잘 표기할 수 없으며, 둘째 우리의 고유한 글자가 없어서 문자생활의 불편이 매우 심하고, 셋째 이런 뜻에서 새로 글자를 만들었으니 일상생활에 편하게 쓰라는 것이다.
+
+‘훈민정음’은 “백성을 가르치는 바른소리”라는 뜻으로[8], 세종의 어제 서문과 정인지 서(序)에서 분명히 밝히고 있는바, 당시까지 한문 의존에 따른 어려움을 근본적으로 극복하기 위해 한국어의 고유문자로서 창제되었다.
+
+한편, 훈민정음 창제 후 5년 뒤에 《동국정운(東國正韻)》이 간행되는데, 당시 조선에서 통용되던 한자음을 중국어 원음으로 교정하기 위한 책으로서 이것의 발음 표기에 훈민정음이 사용되고 있다. 따라서, 세종의 훈민정음 창제가 한자 및 한문의 폐지를 목적한 것은 아니라고 보이며, 훈민정음의 활용 범위가 상당히 넓었음을 짐작할 수 있다. 훈민정음에 대하여 반대하는 신하들이 있었는데 대표적으로 최만리는 상소를 올려 반대하였다. 그러나 세종은 "경이 운서를 아는가? 사성칠음에 자모가 몇이나 있는가? 만일 짐이 운서를 바로잡지 아니하면 누가 이를 바로잡을 것인가?" 라고 말하였다.
+
+처음 만들었을 때는 낱자 28글자와 성조를 나타내는 기호(방점)가 따로 있었으나, 지금은 ㅿ, ㆁ, ㆆ, ㆍ 네 글자와 성조 기호(방점)가 사라져서 24글자가 되었다. (제주도를 비롯한 몇 곳에서는 아직도 ㆍ의 발음이 남아 있다.)
+
+그 뒤로 몇 백 년에 걸쳐, 식자층은 주로 한글보다는 한문 위주의 문자 생활을 했지만 한자를 배울 수 없었던 백성과 여자들은 서로 주고 받는 편지나 계약서 등에 한글을 썼고, 궁궐에서 여자끼리 주고 받는 문서에 한글을 쓰기도 하였다.
+
+창제 논란
+오늘날 한글이라 불리는 글이 창제되었다는 사실이 세상에 알려진 것은 세종대왕 25년인 1443년이다. 창제 당시에 한글은 '훈민정음'이라 불렸으며 1446년 음력 9월 초에는 《훈민정음》(통칭 '해례본')이 책으로 엮어졌다. 이 사실은 정인지(鄭麟趾)가 쓴 〈서(序)〉로 확인된다.[9]
+
+지금까지 논란이 되고 있는 부분은 세종대왕이 홀로 글을 창제했는지, 집현전 학자들의 도움을 받았는지, 아니면 세종대왕의 명을 받아 집현전 학자들이 글을 창제했는지가 문제이다. 세종실록(世宗實錄)은 훈민정음을 세종대왕이 친히 만들었다고 기록하고 있으며[10], 누구의 도움을 받았다는 기록은 없다.[11]
+
+다시 말하면 시월 상친제언문이십팔자(是月 上親制諺文二十八字, 세종 25년, 12월 30일)에서 ‘상친제(上親制)’란 세종이 직접 한글을 만들었다는 뜻인데 '세종실록' 안에는 다른 업적에 관해서는 "친제"라는 말이 없었지만, 훈민정음(한글)에 관해서는 이렇게 확실하게 적어 놓았다는 것이다. 또한, 집현전 학자였던 정인지가 집필한 훈민정음 해례본의 서문 중에도 세종대왕이 직접 한글을 창제했다는 내용이 있다.[12]
+
+그러나 성현(成俔, 1439년~1504년)의 《용재총화(慵齋叢話)》 제7권에서 세종이 언문청을 세워 신숙주, 성삼문 등에게 글을 짓도록 명을 내렸다는 주장이 나왔다. 주시경은 《대한국어문법》(1906년)에서 세종이 집현전 학자들의 도움을 받아 한글을 창제했다고 썼다. 그리하여 한글 창제에 집현전 학자들이 관여했다는 설이 우세하게 되었으나, 이기문을 비롯한 학자들은 기록에 나타난 당시 정황을 볼 때 세종이 한글을 홀로 창제한 것이 아니라고 볼 이유가 없다고 주장하고 있다. 한글 창제 후 세종은 표음주의 표기가 일반적인 당대의 표기법과는 달리 형태주의 표기를 주로 활용하고 동국정운 같은 책을 편찬한 예에서 보듯이 국어와 중국어의 전반에 걸쳐 음운학 및 언어학에 깊은 조예와 지식을 보여 주었다. 집현전 학자들은 한글 창제 후 정음청에서 한글을 사용한 편찬 사업에만 관여했다는 것이다.[13]
+
+조선
+처음에 ‘훈민정음’으로 반포된 한글은 조선시대에는 '언문'이라고 불렸다. 이것은 《세종실록》에서 '상친제언문이십팔자(上親製諺文二十八字)'라고 한 것에 연유하는데 한자를 제외한 문자는 ‘언문’이라고 불렀기 때문이다. 여성들이 많이 한글을 썼기 때문에 ‘암클’ 등으로 낮추어 불리기도 하였으나, 궁중과 일부양반층, 백성들 사이에서도 사용되었다.
+
+1445년(세종 27) 4월에 훈민정음을 처음으로 사용하여 악장(樂章)인 《용비어천가》를 편찬하고, 1447년(세종 29) 5월에 간행하였다. 목판본 10권 5책 모두 125장에 달하는 서사시로서, 한글로 엮어진 책으로는 한국 최초의 것이 된다. 세종은 “어리석은 남녀노소 모두가 쉽게 깨달을 수 있도록” 《(세종실록》, 세종 26년) 《삼강행실도》를 훈민정음으로 번역하도록 했으며, 훈민정음이 반포된 뒤에는 일부 관리를 뽑을 때 훈민정음을 시험하도록 했다. 이후로 민간과 조정의 일부 문서에서 훈민정음을 써 왔다.
+
+이러한 한글 보급 정책에 따라 한글은 빠르게 퍼져 반 세기 만인 1500년대 지방의 노비 수준의 신분인 도공에게까지 쓰이게 되었다.[14]
+
+연산군은 1504년(연산군 10년) 훈민정음을 쓰거나 가르치는 것을 금했지만, 조정안에서 훈민정음을 쓰는 것을 금하지는 않았으며, 훈민정음을 아는 사람을 일부러 궁궐에 등용하기도 했다고 전한다.
+
+율곡 이이가 《대학》에 구결을 달고 언해한 《대학율곡언해》는 1749년에 간행되었다.[15]
+
+조선 중기 이후로 가사 문학, 한글 소설 등 한글로 창작된 문학이 유행하였고, 서간에서도 한글/정음이 종종 사용되었다.
+
+근대 이후
+1894년(조선 고종 31년) 갑오개혁에서 마침내 한글을 ‘국문’(國文 나랏글)이라고 하여, 1894년 11월 21일 칙령 제1호 공문식(公文式) 제14조[16] 및 1895년 5월 8일 칙령 제86호 공문식 제9조[17] 에서 법령을 모두 국문을 바탕으로 삼고 한문 번역을 붙이거나 국한문을 섞어 쓰도록 하였다. 1905년 지석영이 상소한 6개 항목의 〈신정국문(新訂國文)〉이 광무황제의 재가를 얻어 한글 맞춤법으로서 공포되었으나, 그 내용의 결점이 지적되면서 1906년 5월에 이능화(李能和)가 〈국문일정의견(國文一定意見)〉을 제출하는 등 논란이 되자, 당시 학부대신 이재곤(李載崑)의 건의로 1907년 7월 8일 대한제국 학부에 통일된 문자 체계를 확립하기 위한 국어 연구 기관으로 '국문연구소(國文硏究所)'가 설치되었는데, 국문연구소의 연구 성과는 1909년 12월 28일 학부에 제출한 보고서로서 〈국문연구의정안(國文硏究議定案)〉 및 어윤적, 이종일(李鍾一), 이억(李億), 윤돈구(尹敦求), 송기용(宋綺用), 유필근(柳苾根), 지석영, 이민응(李敏應)의 8위원 연구안으로 완결되었다.
+
+
+한글과 한문이 혼용되어 쓰인 매일신보 1944년 기사
+한편, 민간에서는 1906년 주시경이 《대한국어문법(大韓國語文法)》을 저술하여 1908년에 《국어문전음학(國語文典音學)》으로 출판하였으며, 1908년 최광옥(崔光玉)의 《대한문전(大韓文典)》, 1909년 유길준(兪吉濬)의 《대한문전(大韓文典)》, 김희상(金熙祥)의 《초등국어어전(初等國語語典)》, 1910년 주시경의 《국어문법(國語文法)》등이 출간되고, 이후에도 1911년 김희상의 《조선어전(朝鮮語典)》, 1913년 남궁억(南宮檍)의 〈조선문법(朝鮮文法)〉, 이규영(李奎榮)의 〈말듬〉, 1925년 이상춘(李常春)의 《조선어문법(朝鮮語文法)》 등으로 이어지면서, 1937년 최현배의 《우리말본》으로 집대성된다.
+
+이와 함께, 조선어학회와 같은 모임에서 꾸준히 애쓴 덕에 조금씩 한국어의 표준 문자로 힘을 얻게 되어 누구나 쓸 수 있게끔, 널리 퍼지게 되었다. '한글'이라는 이름은 주시경이 지은 것이며 조선어학회가 이 이름을 널리 알리기 시작한 것도 이 즈음이다. 일제강점기를 거쳐 광복을 맞이한 다음에는 남북한 모두 공문서와 법전에 한글을 쓰게 되었고, 끝내 조선어를 받아적는 글자로 자리잡게 되었다.
+
+현대 이후
+한국에서는 한글전용법이 시행되어 한자의 사용이 줄어들면서 1990년대 그 사용이 절정을 이루었다.[18] 이후 정부차원에서의 영어우대정책으로 인해 한글의 사용이 점차 줄고 있다는 지적이 있다.[19]
+
+2009년에는 문자가 없어 의사 소통에 곤란을 겪었던 인도네시아의 소수 민족인 찌아찌아족이 자신들의 언어 찌아찌아어의 표기 문자로 시범적으로 한글을 채택, 도입하였다. 그러나 주 정부의 반대와 소수만 배우는 문제 등으로 인해서 이 방법은 사용되지 않고 있다. 그리고 2012년에 솔로몬 제도에 있는 일부 주가 모어 표기문자로 한글을 도입하였다.[20]
+
+창제원리
+『훈민정음 해례본(訓民正音 解例本)』을 바탕으로 한글과 음양오행의 관계를 기록하였다.
+
+가. 모음은 음양의 원리를 기본으로 만들어졌다.
+
+기본 모음'ㆍ, ㅡ, ㅣ'를 보면 'ㆍ'(아래아)는 양(陽)인 하늘(天)을 본 떠 만들고, 'ㅡ'는 음(陰)인 땅(地)을 본 떠 만들었으며 'ㅣ'는 음과 양의 중간자인 인간(人)의 형상을 본 떠 만들었다. 천지인(天地人)은 단군사상에서 유래한 것으로 우주를 구성하는 주요한 요소인 하늘(·)과 땅(ㅡ), 사람(ㅣ)을 나타낸다.[21]
+『훈민정음 해례본』에 따르면 'ㅏ,ㅑ, ㅗ, ㅛ'는 'ㆍ'(아래아) 계열의 글자이다.
+'ㆍ'(아래아)의 속성은 양이다. 양의 특성은 위로의 상승, 바깥으로의 확장이다. 따라서 점을 위, 바깥 쪽에다 찍은 것.
+
+'ㅓ, ㅕ, ㅜ, ㅠ'는 그 반대로 'ㅡ' 계열의 글자이기 때문에 음의 속성을 따라, 하강, 수축의 뜻으로 점을 안쪽, 아래로 찍은 것.
+나. 자음은 오행을 바탕으로 만들어졌다.
+
+『훈민정음 해례본』에선 각 방위와 발음기관을 연결시키고, 해당 발음기관에서 나는 소리 또한 방위와 연관시키고 있다. 방위는 또 계절과 연결이 되므로, 결국 소리는 계절과 연결된다.
+(소리=방위=계절, 소리=계절) 계절은 봄, 여름, 늦여름, 가을, 겨울 순이므로, 소리 역시 어금닛소리(ㄱ, 봄), 혓소리(ㄴ, 여름), 입술소리(ㅁ, 늦여름), 잇소리(ㅅ, 가을), 목소리(ㅇ,겨울) 순으로 배열한다.
+
+『훈민정음 해례본』에서 기본 자음을 ㄱ,ㄴ,ㅁ,ㅅ,ㅇ,ㄹ 순으로 배열한 것은 오행 원리와 연관이 있다.
+자음과 오행의 관계 정리표
+속성 계절 방위 음성 음계
+목(木, 나무) 춘(春, 봄) 동(東, 동녘) 어금닛소리(ㄱ,ㅋ,ㄲ) 각(角)
+화(火, 불) 하(夏, 여름) 남, (南, 남녘) 혓소리(ㄴ,ㄷ,ㅌ,ㄸ) 치(徵)
+토(土, 흙) 계하 (季夏, 늦여름) 중앙(中, 無定) 입술소리(ㅁ,ㅂ,ㅍ,ㅃ,) 궁(宮)
+금(金, 쇠) 추(秋, 가을) 서(西, 서녘) 잇소리(ㅅ,ㅆ,ㅈ,ㅊ,ㅉ) 상(商)
+수(水, 물) 동(冬, 겨울) 북(北, 북녘) 목소리(ㅇ, ㅎ) 우(羽)
+구조
+한글은 낱소리 문자에 속하며, 낱자하나는 낱소리하나를 나타낸다. 낱소리는 닿소리(자음)와 홀소리(모음)로 이루어진다.
+
+한 소리마디는 첫소리(초성), 가운뎃소리(중성), 끝소리(종성)의 낱소리 세 벌로 이루어지는데, 첫소리와 끝소리에는 닿소리를 쓰고 가운뎃소리에는 홀소리를 쓴다. 한글은 낱자를 하나씩 풀어쓰지 않고 하나의 글자 마디로 모아쓰는 특징을 가지고 있다.
+
+낱자
+<nowiki />이 부분의 본문은 한글 낱자입니다.
+처음 한글 낱자는 닿소리 17자와 홀소리 11자로 총 28가지였다. 오늘날 한글 낱자에 쓰이지 않는 없어진 글자를 소실자(消失字)라 하는데, 닿소리 ㅿ(반시옷), ㆁ(옛이응), ㆆ(여린히읗)과 홀소리 ㆍ(아래아)의 네 글자이다. 이로써 현대 한글은 모두 24자로서 닿소리 14자와 홀소리 10자로 되었다. 낱자의 이름과 순서는 다음과 같다.
+
+훈민정음 창제 당시에는 낱자 자체의 칭호법(稱號法)은 표시되어 있지 않았고, 중종 때 최세진의 《훈몽자회》에 이르러 각 낱자의 명칭이 붙게 되었다. 하지만 기역, 디귿, 시옷은 이두식 한자어 명칭을 그대로 사용하여 일제시대의 언문 철자법을 거쳐 지금까지 그대로 사용하게 되었다.[22]
+
+각 자모에 대한 소릿값을 살펴보면, 첫소리 아·설·순·치·후(牙舌脣齒喉)와 반설·반치(反舌半齒)의 7음으로 구별하였고, 모음은 따로 구별하지 않았다. 이러한 7음과 각 자모의 독특한 배열 순서는 중국 운서(韻書)를 그대로 모방한 것이라고 여겨진다. 그리고 실제로 쓸 적에는 각 낱자를 독립시켜 소리 나는 차례대로 적지 않고, 반드시 닿소리와 홀소리를 어울려 쓰기로 하였으니, 곧 <· ㅡ ㅗ ㅜ ㅛ ㅠ >는 자음 아래에 쓰고, <ㅏ ㅓ ㅑ ㅕ>는 자음 오른쪽에 붙여 쓰기로 하였다. 즉 음절문자(音節文字)로 하되, 그 모양이 네모꼴이 되도록 하였으니, 이는 한자의 꼴에 영향을 받았기 때문이라 여겨진다.
+
+닿소리
+ㄱ ㄴ ㄷ ㄹ ㅁ ㅂ ㅅ ㅇ ㅈ ㅊ ㅋ ㅌ ㅍ ㅎ
+기윽/기역 니은 디읃/디귿 리을 미음 비읍 시읏/시옷 이응 지읒 치읓 키읔 티읕 피읖 히읗
+홀소리
+ㅏ ㅑ ㅓ ㅕ ㅗ ㅛ ㅜ ㅠ ㅡ ㅣ
+아 야 어 여 오 요 우 유 으 이
+이 스물네 가지를 바탕으로 하는데 모두 홑소리(단음)이고, 홑소리로 나타낼 수 없는 겹소리(복음)는 두세 홑소리를 어울러서 적되, 그 이름과 순서는 다음과 같다.
+
+겹닿소리
+ㄲ ㄸ ㅃ ㅆ ㅉ
+된기윽/쌍기역 된디읃/쌍디귿 된비읍/쌍비읍 된시읏/쌍시옷 된지읒/쌍지읒
+겹홀소리
+ㅐ ㅒ ㅔ ㅖ ㅘ ㅙ ㅚ ㅝ ㅞ ㅟ ㅢ
+애 얘 에 예 와 왜 외 워 웨 위 의
+소실자 닿소리
+ㅿ ㆁ ㆆ
+반시옷 옛이응 여린히읗
+유성 치경 마찰음 연구개 비음 성문 파열음
+반시옷은 알파벳의 z에 해당하는 음가를 가진 것으로 추정되며 여린히읗은 1을 강하게 발음 시 혀로 목구멍을 막으며 발음된다.
+
+현대 한글에서는 끝소리가 없으면 받침을 쓰지 않고 끝소리가 있을 때에만 홑받침 또는 겹받침을 쓰는데, 홑받침에는 모든 닿소리가 쓰이며, 겹받침에는 홑홀소리 아래에만 놓이는 겹닿소리 ㄲ(쌍기역)과 ㅆ(쌍시옷)과 따로 이름이 없지만 모든 홀소리 아래에 놓일 수 있는 겹받침으로만 쓰이는 겹닿소리가 있다. 모든 받침의 소릿값은 끝소리 규칙에 따라 8갈래로 모인다.[23]
+
+겹받침
+ㄲ ㅆ ㄳ ㄵ ㄶ ㄺ ㄻ ㄼ ㄽ ㄾ ㄿ ㅀ ㅄ
+받침의 소릿값
+ㄱ ㄴ ㄷ ㄹ ㅁ ㅂ ㅇ
+사전에 올릴 때에는 첫소리 > 가운뎃소리 > 끝소리의 순으로 정렬하되, 그 정렬 순서는 다음과 같다.
+
+정렬 순서
+첫소리 ㄱ ㄲ ㄴ ㄷ ㄸ ㄹ ㅁ ㅂ ㅃ ㅅ ㅆ ㅇ ㅈ ㅉ ㅊ ㅋ ㅌ ㅍ ㅎ
+가운뎃소리 ㅏ ㅐ ㅑ ㅒ ㅓ ㅔ ㅕ ㅖ ㅗ ㅘ ㅙ ㅚ ㅛ ㅜ ㅝ ㅞ ㅟ ㅠ ㅡ ㅢ ㅣ
+끝소리 ( ) ㄱ ㄲ ㄳ ㄴ ㄵ ㄶ ㄷ ㄹ ㄺ ㄻ ㄼ ㄽ ㄾ ㄿ ㅀ ㅁ ㅂ ㅄ ㅅ ㅆ ㅇ ㅈ ㅊ ㅋ ㅌ ㅍ ㅎ
+모아쓰기
+한글의 모든 낱자는 한데 모아쓰도록 하고 있으며, 닿소리를 가장 먼저 쓰고 그 오른쪽이나 아래에 홀소리를 적으며, 모든 받침은 닿소리와 홀소리 밑에 놓인다. 따라서, 글자 마디로 모아쓸 때는 다음과 같은 틀에 맞추어 쓴다.
+
+중성이 ㅏ, ㅐ, ㅑ, ㅒ, ㅓ, ㅔ, ㅕ, ㅖ, ㅣ일 때는 중성을 초성의 오른쪽에 붙여 쓴다.
+초성 중성
+초성 중성
+종성
+중성이 ㅗ, ㅛ, ㅜ, ㅠ, ㅡ일 때는 중성을 아래쪽에 붙여 쓴다. 종성이 있으면 그 아래 붙여 쓴다.
+초성
+중성
+초성
+중성
+종성
+중성이 ㅘ, ㅙ, ㅚ, ㅝ, ㅞ, ㅟ, ㅢ와 같이 아래쪽에 붙이는 모음과 오른쪽에 붙이는 모음의 복합일 때는 다음과 같이 아래쪽에 먼저, 그 다음 오른쪽에 붙여 쓴다. 종성은 마찬가지로 아래쪽에 붙여 쓴다.
+초성 중성
+중성
+초성 중성
+중성
+종성
+표기 가능한 글자 수와 소리나는 음절 개수
+현대 한글은 낱자를 엮어 11,172(첫소리 19 × 가운뎃소리 21 × (끝소리 27 + 끝소리 없음 1))글자 마디를 쓸 수 있다. 11,172자 중 399자는 무받침 글자이며 10,773자는 받침 글자이다. 사용 빈도는 KS X 1001 완성형 한글 코드에 선별된 2,350글자가 상위 99.9%로 알려져 있다.[출처 필요]
+
+어문 규정에 의하여, 현대 한국어 표준어에서 실제 사용하는 음절은 이보다 적다. 한국어의 소리는 첫소리+가운뎃소리(+끝소리)로 이루어지는데, 표준어에서 첫소리에는 19가지 닿소리가 모두 쓰이되 첫소리에 놓인 ㅇ은 소리 나지 않는다. 끝소리는 7종성법에 따라 7갈래로 모이며 끝소리가 없는 것까지 더하여 모두 8갈래이므로 현대 한국어의 발음은 첫소리 19 × 가운뎃소리 21 × 끝소리 8 = 3,192가지 소리가 된다.
+
+그런데, 표준 발음법을 따르면 구개음 ㅈ, ㅉ, ㅊ 뒤의 이중 모음 ㅑ, ㅒ, ㅕ, ㅖ, ㅛ, ㅠ는 단모음 ㅏ, ㅐ, ㅓ, ㅔ, ㅗ, ㅜ로 소리나므로 첫소리 3 × 가운뎃소리 6 × 끝소리 8 = 144소리가 빠지고, 아울러 소리나는 첫소리 (ㅇ이 아닌 첫소리 뒤에 오는)를 얹은 가운뎃소리 [ㅢ]는 ㄴ을 제외하면(ㄴ의 경우는 구개음화에 따른 다른 음소로 인정하고 있다.) [ㅣ]로 소리나므로(한글 맞춤법 제9항 및 표준 발음법 제5항 단서 3) 첫소리 17 × 가운뎃소리 1 × 끝소리 8 = 136 소리가 다시 빠진다. 따라서, 현재 한국어 표준어에서 실제 사용하는 소리마디는 3192 − 144 − 136 = 2,912가지가 된다.
+
+옛 한글의 경우, 2009년 10월 1일 발표된 유니코드 5.2에 포함되어 있는 옛 한글 자모의 총 개수는 초성 124개, 중성 95개, 종성 137개와 채움 문자 2개(초성, 중성)이다. 방점 2개는 현재 유니코드에 등록돼 있다. 방점을 제외하고, 총 조합 가능한 글자 마디 개수를 구한다면 다음과 같다.
+
+조합 가능한 한글 코드(125×96×138): 1,656,000개
+완성된 한글(124×95×138): 1,625,640개
+조합 가능한 비표준 한글: 총 16,989개
+채움 문자로만 구성된 한글: 1개
+초성, 종성만 있는 비표준 한글(124×137): 16,988개
+∴ 표준 한글 총 개수(조합 가능한 한글 코드 − 비표준 한글): 1,639,011개
+한글의 유래
+《세종실록》에 최만리가 훈민정음이 “고전(古篆)을 본땄다(倣)”라고 말한 기록이 있는데,[24][25] 이 말이 모호하기 때문에 여러 가지 해석이 있다. ‘고전’의 해석에는 한자의 전자체(篆字體)라는 설과 당시에 ‘몽고전자’(蒙古篆字)로도 불렸던 파스파 문자를 말하는 것이라는 설이 있다. 《환단고기》를 인정하는 사람은 이것이 가림토를 일컫는 말이라고 주장한다. 또한 ‘본땄다’(倣)에 대해서도 그 생김새만이 닮았을 뿐이라는 풀이와 만드는 데에 참고를 했다, 또는 모두 본땄다 등의 여러 가지 해석이 있다.
+
+1940년 《훈민정음》(해례본)이 발견되기 이전에는 훈민정음의 창제 원리를 설명한 문헌이 존재하지 않아 그 유래에 대한 여러 이론이 제기되었다. 그 이전에 제기되었던 주요 학설은 다음과 같다.
+
+발음 기관 상형설: 발음 기관을 상형했다는 설. 신경준(申景濬), 홍양호(洪良浩), 최현배
+전자 기원설: 한문 비석 등에 쓰이는 전자체에서 유래되었다는 설. 황윤석(黃胤錫), 이능화
+몽골 문자 기원설: 몽골문자(파스파)에서 유래했다는 설. 이익(李翼), 유희(柳僖), 게리 레드야드(Gari Ledyard)
+범자(梵字) 기원설: 불경과 함께 고대 인도 문자가 전해져, 그것에서 유래했다는 설. 성현, 이수광(李晬光)
+고대 문자 전래설: 훈민정음 이전 민간에서 전해지던 고대문자로부터 유래했다는 설.
+창문 상형설: 한옥의 창살 모양에서 유래했다는 설. 에카르트(P. A. Eckardt)
+서장(西藏)글자·오행(五行)이론.[26]
+《훈민정음》(해례본)에는 자음과 모음 각각에 대한 창제 원리가 상세히 설명되어 기본 자음 5자는 발음 기관의 모양을 추상화하고, 기본 모음 3자는 천지인 3재를 상징하여 창제되었고 다른 글자들이 획을 덧붙이는 방식으로 만들어졌다고 분명히 밝힘으로써, 여러 이설들을 잠재우고 정설이 되었다.
+
+한글에 관한 여러 이설
+파스파 문자 기원설
+
+(위) ’파스파 문자 ꡂ [k], ꡊ [t], ꡎ [p], ꡛ [s], ꡙ [l]와 그에 대응하는 것으로 여겨지는 한글 문자 ㄱ [k], ㄷ [t], ㅂ [p], ㅈ [ts], ㄹ [l].
+(아래) 중국어를 표현하기 위한 파스파 문자 ꡯ w, ꡤ v, ꡰ f의 파생과 그의 변형 문자 ꡜ [h]
+(왼쪽) 밑에 기호를 덧붙인 ꡧ [w][27] 와 유사한 중국어 표기용 한글 ㅱ w/m, ㅸ v, ㆄ f. 이 한글들은 기본자 ㅁ과 ㅇ에서 유래했다.
+1966년 컬럼비아 대학의 게리 레드야드 교수는 그의 논문에서 훈민정음에서 언급한 고전(古篆)을 몽고전자(蒙古篆字)로 해석하며 한글이 파스파 문자에서 그 기하학적 모양을 차용했다고 주장했다.[28] 레드야드는 그 근거로 당시 조선의 궁에는 파스파 문자가 쓰이고 있었고, 집현전 학자 일부는 파스파 문자를 잘 알고 있었다는 점을 들며, 한글의 기본 자음은 ㄱ, ㄷ, ㅂ, ㅈ, ㄹ라고 제시했다.
+
+레드야드에 따르면 이 다섯개의 글자는 그 모양이 단순화되어 파열음을 위한 가획을 할 수 있는 여지(ㅋ, ㅌ, ㅍ, ㅊ)를 만들어 냈다고 한다. 그는 전통적인 설명과는 다르게 비파열음 ㄴ, ㅁ, ㅅ은 기본자 ㄷ, ㅂ, ㅈ의 윗부분이 지워진 형태라 주장했다. 그는 ㅁ이 ㅂ의 윗부분을 지워서 파생되기는 쉽지만, ㅁ에서 ㅂ의 모양을 이끌어 내는 것은 불분명하다고 주장했다. 즉 다른 파열음과 같은 방법으로 파생되었다면 ㅂ의 모양은 ㅁ위에 한 획이 더해진 형태(ㄱ-ㅋ, ㄷ-ㅌ, ㅈ-ㅊ의 관계처럼)여야 한다는 것이다.
+
+ㆁ자의 유래에 대한 설명도 기존과 다르다. 많은 중국 단어는 ng으로 시작하는데 세종대왕 집권 시기 즈음의 중국에서는 앞에 나오는 ng는 [ŋ]으로 발음하거나 발음하지 않았으며, 이런 단어가 한국어로 차용되었을 경우에도 이는 묵음이었다. 또한 논리적으로 추론 가능한 ng음의 모양은 ㄱ에서 가로 획을 제한 모양인데, 이는 모음 ㅣ과 구분하기 힘들었을 것이다. 따라서 세종대왕은 가로 획을 제한 ㄱ에 묵음이라는 뜻의 ㅇ을 더해 ㆁ을 만들었을 것이라 주장한다. 이는 단어 중간 혹은 끝에서의 [ŋ]의 발음과 단어 처음 부분에서의 묵음을 상징적으로 나타낼 수 있는 것이었다.
+
+중국어를 표기하기 위한 다른 글자는 ㅱ이었는데 훈민정음은 이를 微(미)의 초성이라 설명했다. 이는 중국 방언에 따라 m 혹은 w로 발음되는데 한글에서는 ㅁ([m])과 ㅇ의 조합(이에 대응되는 파스파 문자에서는 [w]로 발음한다)으로 만들어졌다. 파스파 문자에서 글자 밑에 환형의 모양을 그리는 것은 모음 뒤의 w를 의미했다. 레드야드는 ㅱ자의 'ㅇ'모양이 이 과정을 통해 만들어 졌다고 주장했다.
+
+마지막 증거로 레드야드는 ㄷ의 좌측 상단에 작게 삐져나온 형상(입술 모양으로)은 파스파 문자의 d와 유사하다는 점을 들었다. 이러한 입술 모양은 티베트 문자의 d인 ད에서도 찾아볼 수 있다.
+
+만약 레드야드의 이러한 기원설이 사실이라면 한글은 파스파 문자→티베트 문자→브라미 문자→아람 문자를 거쳐 결국 중동 페니키아 문자의 일족에 속하게 된다. 하지만 파스파문자는 세계의 다른 고대문자들처럼 상형문자일 뿐만 아니라 각 글자가 한가지의 음을 나타내지 않고, 그 문자를 사용하던 언어권에 따라 각기 다른 음을 가졌을 것이기 때문에 한글과 같이 소리를 표기하는 문자와의 상관관계는 레드야드 혼자만이 인정하고 있다.
+
+이에 대해 2009년 국어학자 정광(鄭光)은 훈민정음이 36개 중국어 초성을 기본으로 하는 등 파스파 문자로부터 일부 영향을 받았지만 글자를 만든 원리가 서로 다르며, 자음과 모음을 분리하여 독창적으로 만든 문자라고 반론하였다.[29]
+
+기타 한글과 유사하다고 주장하는 문자
+생김새가 한글과 유사한 문자가 있어서 한글 이전의 고대문자에 영향을 받았다는 주장이 있는데, 우연히 닮은 경우이거나 신뢰할 수 없는 출처를 근거로 하고 있다고 설명된다.
+
+가림토와 신대 문자
+송호수는 1984년 《광장(廣場)》 1월호 기고문에서 〈천부경〉과 《환단고기》〈태백일사〉를 참조하여 한글이 단군 시대부터 있었고, 단군조선의 가림다문(加臨多文)에서 한글과 일본의 아히루 문자가 기원했다고 주장하였다.[30] 이에 대하여 국어학자 이근수는 《광장(廣場)》 2월호의 기고문을 통하여 과학적 논증이 없는 이상 추론일 뿐이며, 참조한 고서의 대부분이 야사임을 지적하였다.[31] 또한 가림토 문자는 《환단고기》의 저자로 의심되고 있는 이유립이 한글의 모(母)문자로 창작한 가공의 문자일 가능성이 높아[32] 이러한 주장은 역사학계 및 언어학계에서 인정받지 못하고 있다.
+
+일본의 신대 문자 중에서도 모습이 한글과 비슷한 것이 있어 이를 가림토와 연관이 있다고 주장하기도 하나, 신대 문자가 새겨져 있는 비석마다 문자의 모습이 달라 일관성이 없고 언어학자들이 추정하는 고대 일본어의 음운 구조와도 맞지 않으며,[33] 신대 문자가 기록되었다고 하는 유물 거의 전부가 18~19세기의 것이고 에도 시대 전의 것을 찾을 수 없는바, 신대 문자라는 것은 고대 일본에 문자가 있었다고 주장하기 위한 에도 시대의 위작이며, 특히 그 중에 한글과 유사한 것들은 오히려 한글을 모방한 것임이 밝혀졌다.[34]
+
+구자라트 문자
+1983년 9월 KBS가 방영한 8부작 다큐멘터리 《신왕오천축국전》은[35] 구자라트 문자를 소개하면서 '자음은 ㄱ, ㄴ, ㄷ, ㄹ, ㅁ, ㅅ, ㅇ 등이고, 모음은 ㅏ, ㅑ, ㅓ, ㅕ, ㅗ, ㅛ, ㅜ, ㅠ, ㅡ, ㅣ의 열 자가 꼭 같았으며, 받침까지도 비슷하게 쓰고 있었다'고 주장하였다.
+
+또한, 개천학회 회장 송호수[36]는 1984년 이를 인용하면서 '자음에서는 상당수가 같고, 모음은 10자가 꼭 같다는 것이다'라고 썼다. 그는 구자라트 문자가 가림토에서 비롯되었다고 주장했다.[37][38]
+
+그러나 구자라트 문자는 문자 구성상 자모로 완전히 분리되는 한글과는 달리 모든 자음이 딸림 모음을 수반하는 아부기다이며, 데바나가리 문자에서 수직선을 제거한 데바나가리 파생문자로서 다른 인도계 여러 문자와 친족 관계가 명확하게 밝혀져 있기 때문에 이는 구자라트 문자의 특정 글자체와 한글 사이의 표면적 유사성에 대한 착오일 뿐이다.[39]
+
+다른 언어에서의 한글 사용
+한글은 2009년에 처음으로 인도네시아의 소수 민족인 찌아찌아족의 언어인 찌아찌아어를 표기하는데 사용되었다.
+
+이밖에도 한국에서는 한글을 표기 문자로 보급하기 위하여 노력하고 있으며 2012년 솔로몬제도의 토착어를 한글로 표기하여 교육하는 활동이 시작됐다. 2012년 10월부터 시행된 것은 2개 언어이며 결과에 따라 솔로몬제도 전역으로 확대할 예정이다.[40]
+
+간혹, 영어 발음을 정확하게 표기하기 위해 옛 한글 등을 부활시킨 표기법을 연구하는 경우도 있으나, 이 역시 개인 연구자에 의한 것이다. 그리고 한국인이 아닌 사람이 만든 인공어618-Vuro나 인공 문자 井卜文(Jingbu Script) 등에서 일부 한글 또는 한글을 모티브로 한 문자를 개인 수준에서 사용한 예를 볼 수 있다.
+
+오해와 사실
+<nowiki />이 부분의 본문은 한글에 대한 오해입니다.
+유네스코의 세계기록유산에 등재된 것은 한글이 아니라, 책《훈민정음》(해례본)이다.
+유네스코 세계기록유산은 기록물이 담고 있는 내용이 아니라 기록물 자체만을 등록 대상으로 한다.
+실제의 한글은 모든 언어의 발음을 표기할 수 있는 것이 아니다. 또한, 현재의 한글은 창제 당시의 훈민정음보다 표현할 수 있는 발음 수가 적다.
+'모든 소리를 표현할 수 있다는 것'은 원래 언어학적 명제가 아니고, 창제 당시에 '모든 소리는 기본 5음의 조화로 이루어진다'는 사상을 배경으로 한 철학적 표현이다.
+한글 낱자는 모두 소릿값이 확정되어 있고 실제 한글 쓰임에서는 모아쓰기의 규칙도 정해져 있으므로, 한글로 표현되는 소리의 숫자는 본래 유한하며, 한글은 기본적으로 한국어에 맞추어져 있다.
+현재 한글은 한국어 발음에만 사용하고 있으나, 원래의 훈민정음에서는 모아쓰기가 좀 더 다양하며, 아울러 《동국정운》에 따르면 실제의 한국어 발음뿐만 아니라, 이론적인 한자음도 훈민정음으로써 표현하고 있다.
+한글은 언어의 이름이 아니라 글자의 이름이다.
+창제 당시의 이름인 '훈민정음'과 그 약칭인 '정음'도 본래 글자의 이름이었다.
+찌아찌아족의 찌아찌아어의 표기에는 사용되나 공식은 아니다.
+한글 자모일람
+방언 한글 자모
+ㄱㄲㄳㄴㄵㄶㄷㄸㄹㄺㄻㄼㄽㄾㄿ
+
+ㅀㅁㅂㅃㅄㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎㅏ
+
+ㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟ
+
+ㅠㅡㅢㅣㅤㅥㅦㅧㅨㅩㅪㅫㅬㅭㅮㅯ
+
+ㅰㅱㅲㅳㅴㅵㅶㅷㅸㅹㅺㅻㅼㅽㅾㅿ
+
+ㆀㆁㆂㆃㆄㆅㆆㆇㆈㆉㆊㆋㆌㆍㆎ
+
+고문 한글 자모
+ᄀᄁᄂᄃᄄᄅᄆᄇᄈᄉᄊᄋᄌᄍᄎᄏ
+
+ᄐᄑᄒᄓᄔᄕᄖᄗᄘᄙᄚᄛᄜᄝᄞᄟ
+
+ᄠᄡᄢᄣᄤᄥᄦᄧᄨᄩᄪᄫᄬᄭᄮᄯ
+
+ᄰᄱᄲᄳᄴᄵᄶᄷᄸᄹᄺᄻᄼᄽᄾᄿ
+
+ᅐᅑᅒᅓᅔᅕᅖᅗᅘᅙᅚᅛᅜᅝᅞᅟ
+
+복합원음와 보음
+ᅠᅡᅢᅣᅤᅥᅦᅧᅨᅩᅪᅫᅬᅭᅮᅯ
+
+ᅰᅱᅲᅳᅴᅵᅶᅷᅸᅹᅺᅻᅼᅽᅾᅿ
+
+ᆀᆁᆂᆃᆄᆅᆆᆇᆈᆉᆊᆋᆌᆍᆎᆏ
+
+ᆐᆑᆒᆓᆔᆕᆖᆗᆘᆙᆚᆛᆜᆝᆞᆟ
+
+ᆠᆡᆢᆨᆩᆪᆫᆬᆭᆮᆯ
+
+ᆰᆱᆲᆳᆴᆵᆶᆷᆸᆹᆺᆻᆼᆽᆾᆿ
+
+ᇀᇁᇂᇃᇄᇅᇆᇇᇈᇉᇊᇋᇌᇍᇎᇏ
+
+ᇐᇑᇒᇓᇔᇕᇖᇗᇘᇙᇚᇛᇜᇝᇞᇟ
+
+ᇠᇡᇢᇣᇤᇥᇦᇧᇨᇩᇪᇫᇬᇭᇮᇯ
+
+ᇰᇱᇲᇳᇴᇵᇶᇷᇸᇹᇺᇻᇼᇽᇾᇿ
+
+관련 항목
+국어
+세종대왕
+주시경
+한글의 우수성에 관한 논란
+문자
+한글 맞춤법
+한글 낱자
+한글 낱자 목록
+한국어
+한국어 로마자 표기법
+한글전용과 국한문혼용
+조선어 신철자법
+옛 한글
+훈민정음
+한글날
+한글 위키백과
+한글의 모든 글자
+한글을 표기하는 언어 목록
+한국어
+카리어
+꽈라아에어[41]
+찌아찌아어
+각주
+ 이것은 훈민정음 창제 당시부터 보인다. 예컨대, 《세종실록》은 훈민정음 창제를 上親制諺文二十八字…是謂訓民正音(주상께서 친히 언문 28자를 만들어 … 이것을 훈민정음이라 이른다)이라고 기록하는데, 이것은 한글의 이름이거나 또는 굳이 한글만 지칭한 것은 아니고 한자 이외의 문자를 통칭하는 표현이다. 예컨대 《순조실록(純祖實錄)》 9년 12월 2일 기사에 역관 현의순(玄義洵)이 대마도의 사정을 보고한 글 가운데 敎之以諺文名之曰假名(언문을 가르치는데, 그 이름을 일러 가나라고 한다)과 같은 문장이 있어, 일본 문자에 대해서도 언문이라는 표현이 사용됨을 볼 수 있다.) 또한 《세종실록》 28년 11월 8일자에 언문청이라는 한글을 보급하는 구실을 하는 기관 이름이 나온다.
+ 한문을 지칭하는 ‘진서(眞書)’와 대비되는 표현이다.
+ 諺文字母俗所謂反切二十七字(세간에서 이른바 반절 27자라고 하는 언문 자모). 최세진(崔世珍), 〈범례(凡例)〉, 《훈몽자회(訓蒙字會)》. 1527. 반절은 본래 2개의 한자로 다른 한자음을 표기하는 방법을 말하며, 이렇게 소리의 표기에 사용된 글자를 반절자(反切字)라고 한다. 당시 훈민정음이 이와 비슷한 용법으로 한자음 표기에도 사용되었기 때문에 반절이라고 불렸던 것으로 보인다.
+ 1908년 설립한 ‘국어연구학회(國語硏究學會)’가 1911년 9월에 명칭을 바꾼 것으로, 공식적으로 한글과 한문 표기를 나란히 사용했다.
+ ‘本會의 名稱을 한글모라 改稱하고 이 몯음을 세움몯음으로…’, 〈한글모세움몯음적발〉, 《한글모 죽보기》. 이규영. 1917.
+ 한글풀이의 수록이 확인되는 것은 1914년 3월의 제7호부터 1914년 7월의 제11호까지
+ 〈맞춤법〉, 《조선말규범집》. 북한(조선민주주의인민공화국) 내각직속 국어사정위원회. 1987.
+ “훈민정음은 백성(百姓) 가르치시는 정(正)한 소리라”(현대어 표기로 옮김), 〈세종어제훈민정음〉, 《월인석보》. 1459년.
+ 癸亥冬我殿下創制正音二十八字略揭例義以示之名曰訓民正音 (계해년 겨울, 우리 전하께서 정음 28자를 창제하시어, 간략하게 예를 들어 보이시고 이름을 훈민정음이라 하셨다). 정인지,〈서(序)〉, 《훈민정음》. 1446년.
+ “上親制諺文二十八字…是謂訓民正音”, 《세종실록》 25년 12월.
+ 정인지는 훈민정음을 지은 세종이 집현전 학자들에게 ‘해설서’의 편찬을 명했다고 적고 있다. 遂命詳加解釋以喩諸人…謹作諸解及例以敍其梗槪 (마침내, 해석을 상세히 더하여 사람들을 깨우치라고 명하시어… 여러 풀이와 예를 지어 그 개요를 펴내니), 정인지, 〈서〉, 《훈민정음》. 1446년.
+ "錢下槍制', 《훈민정음 해례본》
+ 〈훈민정음 친제론〉, 이기문, 《한국문화》제13집. 서울대학교 규장각 한국학연구원, 1992년.
+ '라랴러려' 분청사기..."16세기 지방 하층민도 한글 사용".YTN.2011-09-08.
+ [1]
+ 第十四條 法律勅令總以國文爲本漢文附譯或用國漢文
+ 第九條 法律命令은 다 國文으로써 本을 삼꼬 漢譯을 附하며 或國漢文을 混用홈
+ 세계는 지금 '언어전쟁' 중
+ “한글 홀대하는 사회”. 2012년 11월 30일에 원본 문서에서 보존된 문서. 2010년 2월 2일에 확인함.
+ 솔로몬제도 일부 주(州)서 표기문자로 한글 채택
+ 최현철 기자 (2010년 10월 20일). “‘천지인’ 개발자 특허권 기부 … 표준 제정 임박”. 중앙일보. 2012년 11월 15일에 확인함.
+ [북녘말] 기윽 디읃 시읏 / 김태훈 : 칼럼 : 사설.칼럼 : 뉴스 : 한겨레
+ 然ㄱㆁㄷㄴㅂㅁㅅㄹ八字可足用也如ㅂ·ㅣㅅ곶爲梨花여ㅿ의갗爲狐皮而ㅅ字可以通用故只用ㅅ字 ,훈민정음 해례 종성해
+ 其字倣古篆分爲初中終聲合之然後乃成字 : (그 글자는 옛 전자(篆字)를 모방하고, 초·중·종성으로 나뉘어 그것을 합한 연후에 글자를 이룬다.) 《세종실록》 25년 12월 30일.
+ 象形而字倣古篆因聲而音叶七調 (물건의 형상을 본떠서 글자는 고전을 모방하고, 소리(聲)로 인(因)하여 음(音)은 칠조(七調)에 맞아). 《세종실록》28년 9월 29일. 이 기사는 《훈민정음》의 정인지 〈서(序)〉를 옮겨 놓은 것이다.
+ 글로벌세계대백과, 〈양반관료의 문화〉, 한글 창제.
+ 이 문자들은 확실히 증명되진 않았다. 어떤 필사본에는 ꡜ h에서 파생된 저 세 글자 모두 아래쪽이 환형으로 되어있기도 하다.
+ "The Korean Language Reform of 1446", Gari Ledyard. (1966)
+ [Why] 세종대왕 한글 창제가 '표절' 누명 쓰고 있다고?, 《조선닷컴》, 2009.10.10.
+ "한글은 檀君시대부터 있었다" 송호수 교수 주장에 學界관심, 《경향신문》, 1984.1.12.
+ 한글 世宗전 創製 "터무니없다" 李覲洙교수, 宋鎬洙교수 主張 반박, 《경향신문》, 1984.2.6.
+ 문명, 《만들어진 한국사》, 파란, 2010
+ "日 神代文字는 한글의 僞作이다", 《경향신문》, 1985.7.17.
+ MBC 다큐멘터리 “미스터리 한글, 해례6211의 비밀”, 2007. 10. 7. 방송
+ 《新往五天竺國傳(신왕오천축국전)》. 문순태, KBS 특별취재반. 한국방송사업단, 1983. 참조
+ 당시 보도에는 S베일러대 교수로 소개되었다.
+ 〈한글은 세종 이전에도 있었다〉, 송호수,《廣場(광장)》1984년 1월호. 세계평화교수협의회.
+ 일본 神代文字 논란[깨진 링크(과거 내용 찾기)], 충북대학 국어국문학과 국어학강의실
+ 〈한글과 비슷한(?) 구자라트 문자〉, 김광해, 《새국어소식》1999년 10월호. 한국어문진흥회
+ [2]
+ 섬나라 솔로몬제도 2개주도 한글 쓴다
+참고 문헌
+〈한글〉, 《한국민족문화대백과》. 한국학중앙연구원.
+읽을거리
+Portal icon 한국 포털
+Portal icon 문화 포털
+한글날에 생각해보는 훈민정음 미스터리. 김현미.《신동아》 2006년 10월호.
+히브리문자 기원설을 계기로 본 훈민정음. 이기혁. 《신동아》 1997년 5월호.
+어느 기하학자의 한글 창제. 《동아일보》, 2007-10-19. (영어의 v, f, θ, ð, l 등의 발음을 한글로 표기하기 - 고등과학원 최재경 교수의 제안)
+'한글 연구가' 최성철씨 "이젠 한글표기법 독립운동할 때". 《동아일보》, 2006-10-09.
+한글과 코드: 한글과 컴퓨터 코드에 관하여
+외부 링크
+ 위키미디어 공용에 관련된 미디어 자료와 분류가 있습니다.
+한글 (분류)
+ 위키낱말사전에
+관련된 항목이 있습니다.
+한글
+디지털한글박물관
+한글학회
+한글재단
+국립국어원
+한글 듣기 테스트
+"한글 자음 쓰기 영상" - 유튜브
+"한글 모음 쓰기 영상" - 유튜브
+Heckert GNU white.svgCc.logo.circle.svg 이 문서에는 다음커뮤니케이션(현 카카오)에서 GFDL 또는 CC-SA 라이선스로 배포한 글로벌 세계대백과사전의 "양반관료의 문화" 항목을 기초로 작성된 글이 포함되어 있습니다.
\ No newline at end of file
diff --git a/pkgs/characters/tool/README.txt b/pkgs/characters/tool/README.txt
new file mode 100644
index 0000000..80a27be
--- /dev/null
+++ b/pkgs/characters/tool/README.txt
@@ -0,0 +1,15 @@
+To re-generate generated files run:
+```
+ dart tool/generate.dart
+```
+
+To update to new data files, update the paths in `tool/src/data_files`
+if necessary, and run `dart tool.generate.dart -u -o`.
+The `-u` means read new files from `https://unicode.org/Public`,
+and `-o` means try to optimize table chunk sizes,
+which is most important when using new data files.
+
+When using `-u`, the update stops if the license file has changed.
+Check the differences, and decide whether to proceed, and if so
+run again adding the `--accept-license` command line argument.
+
diff --git a/pkgs/characters/tool/benchmark.dart b/pkgs/characters/tool/benchmark.dart
new file mode 100644
index 0000000..fd1170b
--- /dev/null
+++ b/pkgs/characters/tool/benchmark.dart
@@ -0,0 +1,87 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:characters/src/grapheme_clusters/breaks.dart';
+import 'package:characters/src/grapheme_clusters/constants.dart';
+
+import '../test/src/text_samples.dart';
+import '../test/src/unicode_grapheme_tests.dart';
+import '../test/src/various_tests.dart';
+
+// Low-level benchmark of the grapheme cluster step functions.
+
+void main(List<String> args) {
+ var count = 5;
+ if (args.isNotEmpty) {
+ count = int.parse(args[0]);
+ }
+ var gcsf = 0;
+ var gcsb = 0;
+
+ var text = genesis +
+ hangul +
+ genesis +
+ diacretics +
+ recJoin(splitTests) +
+ recJoin(emojis) +
+ recJoin(zalgo);
+ var codeUnits = text.length;
+ var codePoints = text.runes.length;
+ for (var i = 0; i < count; i++) {
+ gcsf = benchForward(text, i, codePoints, codeUnits);
+ gcsb = benchBackward(text, i, codePoints, codeUnits);
+ }
+ print('gc: Grapheme Clusters, cp: Code Points, cu: Code Units.');
+ if (gcsf != gcsb) {
+ print('ERROR: Did not count the same number of grapheme clusters: '
+ '$gcsf forward vs. $gcsb backward.');
+ } else {
+ print('Total: $gcsf gc, $codePoints cp, $codeUnits cu');
+ print('Avg ${(codePoints / gcsf).toStringAsFixed(3)} cp/gc');
+ print('Avg ${(codeUnits / gcsf).toStringAsFixed(3)} cu/gc');
+ }
+}
+
+String recJoin(Iterable<List<String>> texts) =>
+ texts.map((x) => x.join('')).join('\n');
+
+int benchForward(String text, int i, int cp, int cu) {
+ var n = 0;
+ var gc = 0;
+ var e = 0;
+ var sw = Stopwatch()..start();
+ do {
+ var breaks = Breaks(text, 0, text.length, stateSoTNoBreak);
+ while (breaks.nextBreak() >= 0) {
+ gc++;
+ }
+ e = sw.elapsedMilliseconds;
+ n++;
+ } while (e < 2000);
+ print('Forward #$i: ${(gc / e).round()} gc/ms, '
+ '${(n * cp / e).round()} cp/ms, '
+ '${(n * cu / e).round()} cu/ms, '
+ '$n rounds');
+ return gc ~/ n;
+}
+
+int benchBackward(String text, int i, int cp, int cu) {
+ var n = 0;
+ var gc = 0;
+ var e = 0;
+ var sw = Stopwatch()..start();
+ do {
+ var breaks = BackBreaks(text, text.length, 0, stateEoTNoBreak);
+ while (breaks.nextBreak() >= 0) {
+ gc++;
+ }
+ e = sw.elapsedMilliseconds;
+ n++;
+ } while (e < 2000);
+ print('Backward #$i: ${(gc / e).round()} gc/ms, '
+ '${(n * cp / e).round()} cp/ms, '
+ '${(n * cu / e).round()} cu/ms, '
+ '$n rounds');
+ return gc ~/ n;
+}
diff --git a/pkgs/characters/tool/bin/generate_tables.dart b/pkgs/characters/tool/bin/generate_tables.dart
new file mode 100644
index 0000000..709fd08
--- /dev/null
+++ b/pkgs/characters/tool/bin/generate_tables.dart
@@ -0,0 +1,327 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+import 'dart:typed_data';
+
+import '../src/args.dart';
+import '../src/automaton_builder.dart';
+import '../src/data_files.dart';
+import '../src/grapheme_category_loader.dart';
+import '../src/indirect_table.dart';
+import '../src/shared.dart';
+import '../src/string_literal_writer.dart';
+import '../src/table_builder.dart';
+
+// Generates tables used by the grapheme cluster breaking algorithm
+// and a state machine used to implement the algorithm.
+
+// The task of this tool is to take the complete table of
+// grapheme cluster categories for every code point (U+0000 ... U+10FFFF)
+// and build a smaller table with the same information.
+//
+// The approach taken is to split the large table into chunks,
+// smaller chunks for data in the BMP (U+0000 ... U+FFFF)
+// which have higher variation than later data,
+// and larger chunks for non-BMP ("astral" planes) code points.
+// This also corresponds to the split between one-UTF-16 code unit
+// character and surrogate pairs, which gives us a natural
+// branch in the string parsing code.
+//
+// Then a single table is built containing all these chunks, but where
+// the chunks are allowed to overlap.
+// An indirection table points to the start of each chunk
+// in the larger table.
+//
+// Having many small chunks increases the size of the indirection table,
+// and large chunks reduces the chance of chunks being completely
+// equivalent.
+//
+// The state machines are based on the extended Grapheme Cluster breaking
+// algorithm. The forward-scanning state machine is entirely regular.
+// The backwards-scanning state machine needs to call out to do look-ahead
+// in some cases (for example, how to combine a regional identifier
+// depends on whether there is an odd or even number of
+// previous regional identifiers.)
+
+/// Print more information to stderr while generating.
+///
+/// Set while developing if you don't want to pass `-v` on the command line
+/// every time.
+/// Only affects this file when it's run directly.
+const defaultVerbose = false;
+
+/// Default location for table file.
+const tableFile = 'lib/src/grapheme_clusters/table.dart';
+
+// Best values found for current tables.
+// Update if better value found when updating data files.
+// (May consider benchmark performance as well as size.)
+
+// TODO: Write out best sizes to a file after an update, and read them back
+// next time, instead of hardcoding in the source file.
+
+// Chunk sizes must be powers of 2.
+const int defaultLowChunkSize = 32;
+
+// Currently found best size.
+const int defaultHighChunkSize = 256;
+
+void main(List<String> args) async {
+ var flags = parseArgs(args, 'generate_tables', allowOptimize: true);
+ var output = flags.dryrun
+ ? null
+ : flags.targetFile ?? File(path(packageRoot, tableFile));
+
+ if (output != null && !output.existsSync()) {
+ try {
+ output.createSync(recursive: true);
+ } catch (e) {
+ stderr.writeln('Cannot find or create file: ${output.path}');
+ stderr.writeln('Writing to stdout');
+ output = null;
+ }
+ }
+
+ var categories =
+ await loadCategories(update: flags.update, verbose: flags.verbose);
+
+ generateTables(output, categories,
+ dryrun: flags.dryrun, verbose: flags.verbose, optimize: flags.optimize);
+}
+
+void generateTables(
+ File? output,
+ Uint8List table, {
+ bool dryrun = false,
+ bool optimize = false,
+ bool verbose = defaultVerbose,
+ bool acceptLicenseChange = false,
+}) async {
+ var lowChunkSize = defaultLowChunkSize;
+ var highChunkSize = defaultHighChunkSize;
+
+ int optimizeTable(
+ IndirectTable chunkTable, int lowChunkSize, int highChunkSize) {
+ var index = 0;
+ do {
+ chunkTable.entries.add(TableEntry(0, index, lowChunkSize));
+ index += lowChunkSize;
+ } while (index < 0x10000);
+ var lowChunkCount = chunkTable.entries.length;
+ do {
+ chunkTable.entries.add(TableEntry(0, index, highChunkSize));
+ index += highChunkSize;
+ } while (index < 0x110000);
+ var highChunkCount = chunkTable.entries.length - lowChunkCount;
+ assert(lowChunkCount * lowChunkSize + highChunkCount * highChunkSize ==
+ 0x110000);
+ assert(chunkTable.chunks.length == 1);
+ assert(_validate(table, chunkTable, lowChunkSize, highChunkSize,
+ verbose: false));
+
+ chunkifyTable(chunkTable);
+ assert(chunkTable.entries.length == lowChunkCount + highChunkCount);
+ assert(_validate(table, chunkTable, lowChunkSize, highChunkSize,
+ verbose: false));
+
+ combineChunkedTable(chunkTable);
+ assert(chunkTable.entries.length == lowChunkCount + highChunkCount);
+ assert(chunkTable.chunks.length == 1);
+ assert(_validate(table, chunkTable, lowChunkSize, highChunkSize,
+ verbose: false));
+
+ var size = chunkTable.chunks[0].length + chunkTable.entries.length * 2;
+ return size;
+ }
+
+ var chunkTable = IndirectTable([table.sublist(0, table.length)], []);
+ var size = optimizeTable(chunkTable, lowChunkSize, highChunkSize);
+ if (verbose) {
+ stderr.writeln('Default chunk size: $lowChunkSize/$highChunkSize: $size');
+ }
+ if (optimize) {
+ // Chunk sizes must be powers of 2.
+ // Smaller chunk sizes gives more smaller chunks,
+ // with more chance of overlap,
+ // but each chunks adds an entry to the index table.
+ for (var low in [64, 128, 32, 256]) {
+ for (var high in [512, 1024, 256, 2048]) {
+ if (low == lowChunkSize && high == highChunkSize) continue;
+ var newChunk = IndirectTable([table.sublist(0, table.length)], []);
+ var newSize = optimizeTable(newChunk, low, high);
+ if (verbose) {
+ var delta = newSize - size;
+ stderr.writeln("${size < newSize ? "Worse" : "Better"}"
+ ' chunk size: $low/$high: $newSize '
+ "(${delta > 0 ? "+$delta" : delta})");
+ }
+ if (newSize < size) {
+ lowChunkSize = low;
+ highChunkSize = high;
+ chunkTable = newChunk;
+ size = newSize;
+ }
+ }
+ }
+ if (verbose) {
+ stderr.writeln('Best low chunk size: $lowChunkSize');
+ stderr.writeln('Best high chunk size: $highChunkSize');
+ stderr.writeln('Best table size: $size');
+ }
+ }
+
+ var buffer = StringBuffer();
+ writeHeader(
+ buffer, [graphemeTestData, emojiTestData, graphemeBreakPropertyData]);
+ buffer.writeln();
+
+ writeTables(buffer, chunkTable, lowChunkSize, highChunkSize,
+ verbose: verbose);
+
+ writeForwardAutomaton(buffer, verbose: verbose);
+ buffer.writeln();
+
+ writeBackwardAutomaton(buffer, verbose: verbose);
+
+ if (output == null) {
+ stdout.write(buffer);
+ } else if (!dryrun) {
+ output.writeAsStringSync(buffer.toString());
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Combined table writing.
+void writeTables(
+ StringSink out, IndirectTable table, int lowChunkSize, int highChunkSize,
+ {required bool verbose}) {
+ assert(table.chunks.length == 1);
+ _writeStringLiteral(out, '_data', table.chunks[0], verbose: verbose);
+ _writeStringLiteral(out, '_start', table.entries.map((e) => e.start).toList(),
+ verbose: verbose);
+ _writeLookupFunction(out, '_data', '_start', lowChunkSize);
+ out.writeln();
+ _writeSurrogateLookupFunction(
+ out, '_data', '_start', 65536 ~/ lowChunkSize, highChunkSize);
+ out.writeln();
+}
+
+void _writeStringLiteral(StringSink out, String name, List<int> data,
+ {required bool verbose}) {
+ var prefix = 'const String $name = ';
+ out.write(prefix);
+ var writer = StringLiteralWriter(out, padding: 4, escape: _needsEscape);
+ writer.start(prefix.length);
+ var bytes = 0;
+ for (var i = 0; i < data.length; i++) {
+ var char = data[i];
+ writer.add(char);
+ bytes += char <= 0xFF ? 1 : 2;
+ }
+ writer.end();
+ out.write(';\n');
+ if (verbose) {
+ stderr.writeln('Writing $bytes bytes');
+ }
+}
+
+bool _needsEscape(int codeUnit) =>
+ codeUnit > 0xff || codeUnit == 0x7f || codeUnit & 0x60 == 0;
+
+void _writeLookupFunction(
+ StringSink out, String dataName, String startName, int chunkSize) {
+ out.write(_lookupMethod('low', dataName, startName, chunkSize));
+}
+
+void _writeSurrogateLookupFunction(StringSink out, String dataName,
+ String startName, int startOffset, int chunkSize) {
+ out.write(_lookupSurrogatesMethod(
+ 'high', dataName, startName, startOffset, chunkSize));
+}
+
+String _lookupMethod(
+ String name, String dataName, String startName, int chunkSize) =>
+ '''
+$preferInline
+int $name(int codeUnit) {
+ var chunkStart = $startName.codeUnitAt(codeUnit >> ${chunkSize.bitLength - 1});
+ var index = chunkStart + (codeUnit & ${chunkSize - 1});
+ return $dataName.codeUnitAt(index);
+}
+''';
+
+String _lookupSurrogatesMethod(String name, String dataName, String startName,
+ int startOffset, int chunkSize) {
+ if (chunkSize == 1024) {
+ return '''
+$preferInline
+int $name(int lead, int tail) {
+ var chunkStart = $startName.codeUnitAt($startOffset + (0x3ff & lead));
+ var index = chunkStart + (0x3ff & tail);
+ return $dataName.codeUnitAt(index);
+}
+''';
+ }
+ var shift = chunkSize.bitLength - 1;
+ var indexVar = chunkSize < 1024 ? 'tail' : 'offset';
+ return '''
+$preferInline
+int $name(int lead, int tail) {
+ var offset = (((0x3ff & lead) << 10) + (0x3ff & tail)) + ($startOffset << $shift);
+ var chunkStart = $startName.codeUnitAt(offset >> $shift);
+ var index = chunkStart + ($indexVar & ${chunkSize - 1});
+ return $dataName.codeUnitAt(index);
+}
+''';
+}
+
+// -----------------------------------------------------------------------------
+bool _validate(Uint8List table, IndirectTable indirectTable, int lowChunkSize,
+ int highChunkSize,
+ {required bool verbose}) {
+ var lowChunkCount = 65536 ~/ lowChunkSize;
+ var lowChunkShift = lowChunkSize.bitLength - 1;
+ var lowChunkMask = lowChunkSize - 1;
+ for (var i = 0; i < 65536; i++) {
+ var value = table[i];
+ var entryIndex = i >> lowChunkShift;
+ var entry = indirectTable.entries[entryIndex];
+ var indirectValue = indirectTable.chunks[entry.chunkNumber]
+ [entry.start + (i & lowChunkMask)];
+ if (value != indirectValue) {
+ stderr.writeln('$entryIndex: $entry');
+ stderr.writeln('Error: ${i.toRadixString(16)} -> Expected $value,'
+ ' was $indirectValue');
+ printIndirectTable(indirectTable);
+ return false;
+ }
+ }
+ var highChunkShift = highChunkSize.bitLength - 1;
+ var highChunkMask = highChunkSize - 1;
+ for (var i = 0x10000; i < 0x110000; i++) {
+ var j = i - 0x10000;
+ var value = table[i];
+ var entryIndex = lowChunkCount + (j >> highChunkShift);
+ var entry = indirectTable.entries[entryIndex];
+ var indirectValue = indirectTable.chunks[entry.chunkNumber]
+ [entry.start + (j & highChunkMask)];
+ if (value != indirectValue) {
+ stderr.writeln('$entryIndex: $entry');
+ stderr.writeln('Error: ${i.toRadixString(16)} -> Expected $value,'
+ ' was $indirectValue');
+ printIndirectTable(indirectTable);
+ return false;
+ }
+ }
+ if (verbose) {
+ stderr.writeln('Table validation success');
+ }
+ return true;
+}
+
+void printIndirectTable(IndirectTable table) {
+ stderr.writeln("IT(chunks: ${table.chunks.map((x) => "#${x.length}")},"
+ ' entries: ${table.entries}');
+}
diff --git a/pkgs/characters/tool/bin/generate_tests.dart b/pkgs/characters/tool/bin/generate_tests.dart
new file mode 100644
index 0000000..2f3b67b
--- /dev/null
+++ b/pkgs/characters/tool/bin/generate_tests.dart
@@ -0,0 +1,208 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:characters/src/grapheme_clusters/constants.dart';
+
+import '../src/args.dart';
+import '../src/data_files.dart';
+import '../src/grapheme_category_loader.dart';
+import '../src/shared.dart';
+import '../src/string_literal_writer.dart';
+
+// Generates tests for grapheme cluster splitting from the Unicode
+// GraphemeBreakTest.txt file.
+//
+// Fetches the data files from the Unicode web site.
+
+const defaultVerbose = false;
+
+const testFile = 'test/src/unicode_grapheme_tests.dart';
+
+void main(List<String> args) async {
+ var flags = parseArgs(args, 'generate_tests');
+
+ var output = flags.dryrun
+ ? null
+ : flags.targetFile ?? File(path(packageRoot, testFile));
+
+ if (output != null && !output.existsSync()) {
+ try {
+ output.createSync(recursive: true);
+ } catch (e) {
+ stderr.writeln('Cannot find or create file: ${output.path}');
+ stderr.writeln('Writing to stdout');
+ output = null;
+ }
+ }
+ if (flags.update) {
+ // Force license file update.
+ await licenseFile.load(checkForUpdate: true);
+ }
+
+ var (categories, graphemeTests, emojiTests) = await (
+ loadCategories(update: flags.update, verbose: flags.verbose),
+ graphemeTestData.load(checkForUpdate: flags.update),
+ emojiTestData.load(checkForUpdate: flags.update)
+ ).wait;
+
+ generateTests(output, [graphemeTests, emojiTests], categories,
+ verbose: flags.verbose, dryrun: flags.dryrun);
+}
+
+void generateTests(File? output, List<String> texts, Uint8List categories,
+ {bool verbose = false, bool dryrun = false}) {
+ var buffer = StringBuffer();
+ writeHeader(buffer, [
+ graphemeTestData,
+ emojiTestData,
+ graphemeBreakPropertyData,
+ emojiData,
+ derivedData
+ ]);
+ buffer.writeln('// ignore_for_file: lines_longer_than_80_chars');
+ buffer.writeln('// dart format off');
+ buffer.writeln();
+
+ // Example character of a category which is in the upper planes.
+ var upperChars = List<int>.filled(inputCategoryCount, -1);
+ // Example character of a category which is in the lower planes.
+ var lowerChars = List<int>.filled(inputCategoryCount, -1);
+
+ writeTests(buffer, texts, categories, lowerChars, upperChars,
+ verbose: verbose, dryrun: dryrun);
+
+ buffer.writeln('// dart format on');
+
+ writeOtherCategories(buffer, categories, lowerChars, upperChars);
+
+ if (output == null) {
+ stdout.write(buffer);
+ } else if (!dryrun) {
+ output.writeAsStringSync(buffer.toString());
+ }
+}
+
+void writeTests(StringSink buffer, List<String> texts, Uint8List categories,
+ List<int> lowerChars, List<int> upperChars,
+ {bool dryrun = false, bool verbose = defaultVerbose}) async {
+ var writer = StringLiteralWriter(buffer, lineLength: 9999, escape: _escape);
+ void writeParts(List<List<int>> parts, String description) {
+ buffer.writeln(' [');
+ const indent = ' ';
+ for (var i = 0; i < parts.length; i++) {
+ buffer.write(indent);
+ writer.start(indent.length);
+ for (var char in parts[i]) {
+ writer.add(char);
+ var c = categories[char];
+ ((char < 0x10000) ? lowerChars : upperChars)[c] = char;
+ }
+ writer.end();
+ buffer.writeln(',');
+ }
+ buffer
+ ..write(' ], // ')
+ ..writeln(description);
+ }
+
+ // Write grapheme cluster tests.
+ {
+ buffer.writeln('// Grapheme cluster tests.');
+ writeTestHeader(buffer, 'splitTests');
+ var test = texts[0];
+ var lineRE = RegExp(r'^(÷.*?)#[ \t]*(.*)', multiLine: true);
+ var tokensRE = RegExp(r'[÷×]|[\dA-F]+');
+
+ for (var line in lineRE.allMatches(test)) {
+ var description = line[2]!;
+ var tokens = tokensRE.allMatches(line[1]!).map((x) => x[0]!).toList();
+ assert(tokens.first == '÷');
+ assert(tokens.last == '÷');
+
+ var parts = <List<int>>[];
+ var chars = <int>[];
+ for (var i = 1; i < tokens.length; i += 2) {
+ var cp = int.parse(tokens[i], radix: 16);
+ chars.add(cp);
+ if (tokens[i + 1] == '÷') {
+ parts.add(chars);
+ chars = [];
+ }
+ }
+ writeParts(parts, description);
+ }
+ buffer.writeln('];');
+ }
+ // Write emoji cluster tests.
+ {
+ buffer.writeln('// Emoji tests.');
+ writeTestHeader(buffer, 'emojis');
+ // Emojis
+ var emojis = texts[1];
+ var lineRE = RegExp(r'^([ \dA-F]*?);[^#]*#[ \t]*(.*)', multiLine: true);
+ var tokensRE = RegExp(r'[\dA-F]+');
+ for (var line in lineRE.allMatches(emojis)) {
+ var description = line[2]!;
+ var part = <int>[];
+ for (var token in tokensRE.allMatches(line[1]!)) {
+ var value = int.parse(token[0]!, radix: 16);
+ part.add(value);
+ }
+ writeParts([part], description);
+ }
+ buffer.writeln('];');
+ }
+}
+
+bool _escape(int cp) => cp > 0xff || cp & 0x60 == 0 || cp == 0x7f;
+
+void writeTestHeader(StringSink buffer, String testName) {
+ buffer
+ ..write('const List<List<String>> ')
+ ..write(testName)
+ ..writeln(' = [');
+}
+
+void writeOtherCategories(StringSink output, Uint8List categories,
+ List<int> lowerChars, List<int> upperChars) {
+ var otherCategories = lowerChars;
+ for (var i = 0; i < 0x110000; i++) {
+ if (i == 0x10000) otherCategories = upperChars;
+ var category = categories[i];
+ if (otherCategories[category] < 0) otherCategories[category] = i;
+ }
+ // BMP characters.
+ output
+ ..writeln('// BMP character in each category, if any, -1 if none.')
+ ..writeln('const lowerChars = <int>[');
+ for (var char in lowerChars) {
+ output.write(' ');
+ writeHex(output, char);
+ output.writeln(',');
+ }
+ output.writeln('];');
+
+ output
+ ..writeln('// Non-BMP character in each category, if any, -1 if none.')
+ ..writeln('const upperChars = <int>[');
+ for (var char in upperChars) {
+ output.write(' ');
+ writeHex(output, char);
+ output.writeln(',');
+ }
+ output.writeln('];');
+}
+
+void writeHex(StringSink out, int value) {
+ if (value < 0) {
+ out.write('-');
+ value = -value;
+ }
+ out
+ ..write('0x')
+ ..write(value.toRadixString(16));
+}
diff --git a/pkgs/characters/tool/generate.dart b/pkgs/characters/tool/generate.dart
new file mode 100644
index 0000000..2eadd33
--- /dev/null
+++ b/pkgs/characters/tool/generate.dart
@@ -0,0 +1,77 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io' show File, exit, stderr;
+
+import 'bin/generate_tables.dart' show generateTables, tableFile;
+import 'bin/generate_tests.dart' show generateTests, testFile;
+import 'src/args.dart';
+import 'src/data_files.dart';
+import 'src/grapheme_category_loader.dart';
+import 'src/shared.dart';
+
+/// Generates both tests and tables.
+///
+/// Use this tool for updates, and only access `bin/generate_tables.dart` and
+/// `bin/generate_tests.dart` directly during development of those files.
+void main(List<String> args) async {
+ var flags =
+ parseArgs(args, 'generate', allowOptimize: true, allowFile: false);
+ if (flags.update && !await checkLicense(flags.acceptLicenseChange)) {
+ stderr.writeln('EXITING');
+ exit(1);
+ }
+
+ var (categories, graphemeTests, emojiTests) = await (
+ loadCategories(update: flags.update, verbose: flags.verbose),
+ graphemeTestData.load(checkForUpdate: flags.update),
+ emojiData.load(checkForUpdate: flags.update),
+ ).wait;
+
+ generateTables(File(path(packageRoot, tableFile)), categories,
+ optimize: flags.optimize, dryrun: flags.dryrun, verbose: flags.verbose);
+
+ generateTests(File(path(packageRoot, testFile)), [graphemeTests, emojiTests],
+ categories,
+ dryrun: flags.dryrun, verbose: flags.verbose);
+
+ if (flags.update && !flags.dryrun) {
+ var version = guessVersion(await graphemeBreakPropertyData.contents);
+ updateReadmeVersion(version);
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Unicode version number.
+
+String? guessVersion(String dataFile) {
+ // If first line has format:
+ //
+ // # GraphemeBreakProperty-16.0.0.txt
+ //
+ // Then use 16.0.0 as version number.
+ var match = RegExp(r'# \w+-(\d+\.\d+\.\d+)\.txt').matchAsPrefix(dataFile);
+ return match?[1];
+}
+
+void updateReadmeVersion(String? version) {
+ var readmeFile = File(packagePath('README.md'));
+ var contents = readmeFile.readAsStringSync();
+ String replacementText;
+ if (version != null) {
+ replacementText = 'version $version';
+ } else {
+ var now = DateTime.timestamp();
+ replacementText = 'of ${now.year}-${lz(now.month)}-${lz(now.day)}';
+ }
+ const startTag = '<!-- unicode-version -->';
+ const endTag = '<!-- /unicode-version -->';
+ var versionRE = RegExp('(?<=$startTag).*?(?=$endTag)');
+ var newContents = contents.replaceFirst(versionRE, replacementText);
+ if (contents != newContents) {
+ readmeFile.writeAsStringSync(newContents);
+ } else if (versionRE.firstMatch(contents) == null) {
+ stderr.writeln('MISSING VERSION TAGS IN README.md');
+ }
+}
diff --git a/pkgs/characters/tool/src/args.dart b/pkgs/characters/tool/src/args.dart
new file mode 100644
index 0000000..73b6c5e
--- /dev/null
+++ b/pkgs/characters/tool/src/args.dart
@@ -0,0 +1,76 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Very primitive arguments parser for the generator commands.
+
+import 'dart:io';
+
+typedef Flags = ({
+ bool verbose,
+ bool update,
+ bool dryrun,
+ bool optimize,
+ bool acceptLicenseChange,
+ File? targetFile,
+});
+
+Flags parseArgs(List<String> args, String toolName,
+ {bool allowOptimize = false, bool allowFile = true}) {
+ var update = false;
+ var dryrun = false;
+ var verbose = false;
+ var optimize = false;
+ var acceptLicenseChange = false;
+ File? output;
+ for (var arg in args) {
+ if (arg == '-h' || arg == '--help') {
+ stderr
+ ..writeln(
+ "Usage: $toolName.dart [-u] ${allowOptimize ? "[-i|-o] " : ""}[-n]"
+ "${allowFile ? " <targetFile>" : ""}")
+ ..writeln('-h | --help : Print this help and exit')
+ ..writeln('-u | --update : Fetch new data files')
+ ..writeln('--accept-license : Accept a changed license')
+ ..writeln(
+ '-n | --dryrun : Write to stdout instead of target file');
+ if (allowOptimize) {
+ stderr.writeln(
+ '-o | -i | --optimize : Optimize size parameters for tables');
+ }
+ stderr.writeln('-v | --verbose : Print more information');
+ if (allowFile) {
+ stderr.writeln('If no target file is given, writes to stdout.');
+ }
+ exit(0);
+ } else if (arg == '-u' || arg == '--update') {
+ update = true;
+ } else if (arg == '-n' || arg == '--dryrun') {
+ dryrun = true;
+ } else if (arg == '-v' || arg == '--verbose') {
+ verbose = true;
+ } else if (arg == '--accept-license') {
+ acceptLicenseChange = true;
+ } else if (allowOptimize && arg == '-o' ||
+ arg == '-i' ||
+ arg.startsWith('--opt')) {
+ // Try to find a better size for the table.
+ // No need to do this unless the representation changes or
+ // the input tables are updated.
+ // The current value is optimal for the data and representation used.
+ optimize = true;
+ } else if (arg.startsWith('-') || !allowFile) {
+ stderr.writeln('Unrecognized flag: $arg');
+ } else {
+ output = File(arg);
+ }
+ }
+ return (
+ acceptLicenseChange: acceptLicenseChange,
+ dryrun: dryrun,
+ optimize: optimize,
+ targetFile: output,
+ update: update,
+ verbose: verbose,
+ );
+}
diff --git a/pkgs/characters/tool/src/atsp.dart b/pkgs/characters/tool/src/atsp.dart
new file mode 100644
index 0000000..5141caf
--- /dev/null
+++ b/pkgs/characters/tool/src/atsp.dart
@@ -0,0 +1,196 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'graph.dart';
+
+// See: Asymmetric Traveling Salesman Problem.
+
+// Strategy for finding optimal overlapping of chunks of a larger table,
+// to save space.
+// Does so by solving traveling salesman/hamiltonian cycle in a graph
+// where the distance between chunks is how little they overlap
+// (chunk length minus overlap size).
+
+/// Optimize a cycle of a graph to minimize the edge weight.
+///
+/// The [cycle] must have the same node as first and last element.
+///
+/// This is an implementation of one step of 3-opt, a simple algorithm to
+/// approximate an asymmetric traveling salesman problem (ATSP).
+/// It splits the cycle into three parts and then find the best recombination
+/// of the parts, each potentially reversed.
+bool opt3(Graph graph, List<int> cycle) {
+ // Perhaps optimize the weight computations by creating
+ // a single array of cumulative weights, so any range can be computed
+ // as a difference between two points in that array.
+ for (var i = 1; i < cycle.length; i++) {
+ // Find three cut points in the cycle, A|B, C|D, and E|F,
+ // then find the cumulative weights of each section
+ // B-C, C-D, and E-A, in both directions, as well as the
+ // weight between the end-points.
+ //
+ // with Z being used to represent the start/end of the list
+ // representation (so the A-F/F-A ranges cross over the cycle
+ // representation edges)
+ // Find the weights
+ var nodeA = cycle[i - 1];
+ var nodeB = cycle[i];
+ // Weight of one-step transition from A to B.
+ var wAB = graph.weight(nodeA, nodeB);
+ var wBA = graph.weight(nodeB, nodeA);
+ // Weight of entire path for start to A.
+ var pZA = graph.pathWeight(cycle, 0, i - 1);
+ var pAZ = graph.pathWeight(cycle, i - 1, 0);
+ for (var j = i + 1; j < cycle.length; j++) {
+ var nodeC = cycle[j - 1];
+ var nodeD = cycle[j];
+ var wAC = graph.weight(nodeA, nodeC);
+ var wCA = graph.weight(nodeC, nodeA);
+ var wAD = graph.weight(nodeA, nodeD);
+ var wDA = graph.weight(nodeD, nodeA);
+ var wBD = graph.weight(nodeB, nodeD);
+ var wDB = graph.weight(nodeD, nodeB);
+ var wCD = graph.weight(nodeC, nodeD);
+ var wDC = graph.weight(nodeD, nodeC);
+ var pBC = graph.pathWeight(cycle, i, j - 1);
+ var pCB = graph.pathWeight(cycle, j - 1, i);
+ for (var k = j + 1; k < cycle.length; k++) {
+ var nodeE = cycle[k - 1];
+ var nodeF = cycle[k];
+ var wAE = graph.weight(nodeA, nodeE);
+ var wEA = graph.weight(nodeE, nodeA);
+ var wBE = graph.weight(nodeB, nodeE);
+ var wEB = graph.weight(nodeE, nodeB);
+ var wCE = graph.weight(nodeC, nodeE);
+ var wEC = graph.weight(nodeE, nodeC);
+ var wBF = graph.weight(nodeB, nodeF);
+ var wFB = graph.weight(nodeF, nodeB);
+ var wCF = graph.weight(nodeC, nodeF);
+ var wFC = graph.weight(nodeF, nodeC);
+ var wEF = graph.weight(nodeE, nodeF);
+ var wFE = graph.weight(nodeF, nodeE);
+ var wDF = graph.weight(nodeD, nodeF);
+ var wFD = graph.weight(nodeF, nodeD);
+ var pDE = graph.pathWeight(cycle, j, k - 1);
+ var pED = graph.pathWeight(cycle, k - 1, j);
+ var pFA = graph.pathWeight(cycle, k, cycle.length - 1) + pZA;
+ var pAF = graph.pathWeight(cycle, cycle.length - 1, k) + pAZ;
+
+ // Find best recombination of the three sections B-C, D-E, F-A,
+ // with each possibly reversed.
+ // Since there are only two ways to order three-element cycles,
+ // and three parts that can be reversed, this gives 16 combinations.
+ var wABCDEF = pFA + wAB + pBC + wCD + pDE + wEF;
+ var wACBDEF = pFA + wAC + pCB + wBD + pDE + wEF;
+ var wABCEDF = pFA + wAB + pBC + wCE + pED + wDF;
+ var wACBEDF = pFA + wAC + pCB + wBE + pED + wDF;
+ var wFBCDEA = pAF + wFB + pBC + wCD + pDE + wEA;
+ var wFCBDEA = pAF + wFC + pCB + wBD + pDE + wEA;
+ var wFBCEDA = pAF + wFB + pBC + wCE + pED + wDA;
+ var wFCBEDA = pAF + wFC + pCB + wBE + pED + wDA;
+ var wADEBCF = pFA + wAD + pDE + wEB + pBC + wCF;
+ var wADECBF = pFA + wAD + pDE + wEC + pCB + wBF;
+ var wAEDBCF = pFA + wAE + pED + wDB + pBC + wCF;
+ var wAEDCBF = pFA + wAE + pED + wDC + pCB + wBF;
+ var wFDEBCA = pAF + wFD + pDE + wEB + pBC + wCA;
+ var wFDECBA = pAF + wFD + pDE + wEC + pCB + wBA;
+ var wFEDBCA = pAF + wFE + pED + wDB + pBC + wCA;
+ var wFEDCBA = pAF + wFE + pED + wDC + pCB + wBA;
+ var best = min([
+ wABCDEF,
+ wACBDEF,
+ wABCEDF,
+ wACBEDF,
+ wFBCDEA,
+ wFCBDEA,
+ wFBCEDA,
+ wFCBEDA,
+ wADEBCF,
+ wADECBF,
+ wAEDBCF,
+ wAEDCBF,
+ wFDEBCA,
+ wFDECBA,
+ wFEDBCA,
+ wFEDCBA
+ ]);
+ if (best < wABCDEF) {
+ // Reorder and reverse to match the (or a) best solution.
+ if (best == wACBDEF) {
+ _reverse(cycle, i, j - 1);
+ } else if (best == wABCEDF) {
+ _reverse(cycle, j, k - 1);
+ } else if (best == wACBEDF) {
+ _reverse(cycle, i, j - 1);
+ _reverse(cycle, j, k - 1);
+ } else if (best == wFBCDEA) {
+ _reverse(cycle, i, k - 1);
+ _reverse(cycle, 0, cycle.length - 1);
+ } else if (best == wFCBDEA) {
+ _reverse(cycle, i, j - 1);
+ _reverse(cycle, i, k - 1);
+ _reverse(cycle, 0, cycle.length - 1);
+ } else if (best == wFBCEDA) {
+ _reverse(cycle, j, k - 1);
+ _reverse(cycle, i, k - 1);
+ _reverse(cycle, 0, cycle.length - 1);
+ } else if (best == wFCBEDA) {
+ _reverse(cycle, i, j - 1);
+ _reverse(cycle, j, k - 1);
+ _reverse(cycle, i, k - 1);
+ _reverse(cycle, 0, cycle.length - 1);
+ } else if (best == wADEBCF) {
+ _reverse(cycle, i, j - 1);
+ _reverse(cycle, j, k - 1);
+ _reverse(cycle, i, k - 1);
+ } else if (best == wADECBF) {
+ _reverse(cycle, j, k - 1);
+ _reverse(cycle, i, k - 1);
+ } else if (best == wAEDBCF) {
+ _reverse(cycle, i, j - 1);
+ _reverse(cycle, i, k - 1);
+ } else if (best == wAEDCBF) {
+ _reverse(cycle, i, k - 1);
+ } else if (best == wFDEBCA) {
+ _reverse(cycle, i, j - 1);
+ _reverse(cycle, j, k - 1);
+ _reverse(cycle, 0, cycle.length - 1);
+ } else if (best == wFDECBA) {
+ _reverse(cycle, j, k - 1);
+ _reverse(cycle, 0, cycle.length - 1);
+ } else if (best == wFEDBCA) {
+ _reverse(cycle, i, j - 1);
+ _reverse(cycle, 0, cycle.length - 1);
+ } else if (best == wFEDCBA) {
+ _reverse(cycle, 0, cycle.length - 1);
+ } else {
+ throw AssertionError('Unreachable');
+ }
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/// Reverses a slice of a list.
+void _reverse(List<int> list, int from, int to) {
+ while (from < to) {
+ var tmp = list[from];
+ list[from] = list[to];
+ list[to] = tmp;
+ from++;
+ to--;
+ }
+}
+
+int min(List<int> values) {
+ var result = values[0];
+ for (var i = 1; i < values.length; i++) {
+ var value = values[i];
+ if (value < result) result = value;
+ }
+ return result;
+}
diff --git a/pkgs/characters/tool/src/automaton_builder.dart b/pkgs/characters/tool/src/automaton_builder.dart
new file mode 100644
index 0000000..f8099e0
--- /dev/null
+++ b/pkgs/characters/tool/src/automaton_builder.dart
@@ -0,0 +1,730 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:characters/src/grapheme_clusters/constants.dart';
+
+import 'debug_names.dart';
+import 'string_literal_writer.dart';
+
+// Builder for state automata used to find
+// next/previous grapheme cluster break.
+
+// The automaton states are described below, and the code builds tables
+// for those automatons, then writes the table bytes as a string literal.
+
+//////////////////////////////////////////////////////////////////////////////
+// Transition table for grapheme cluster break automaton.
+// For each previous state and each input character category,
+// emit a new state and whether to break before that input character.
+// The table uses `!` to mark a break before the input character,
+// and then the output state.
+//
+// We do not care that there is no break between a start-of-text and
+// and end-of-text (and empty text). We could handle that with one extra
+// state, but it will never matter for the code using this table.
+//
+// Stored as string for comparison to actual generated automaton.
+const expectedAutomatonDescription = r'''
+Stat: Cat
+ : CR Ctl Otr Ext Spc Reg Pic LF Pre L V T LV LVT OInC ZWJ EInE EInL EoT :
+-----------------------------------------------------------------------------------------------------
+Brk :!CR !Brk !Otr !Otr !Otr !Reg !Pic !Brk !Pre !L !V !T !V !T !InC !Otr !Otr !Otr ! - :
+CR :!CR !Brk !Otr !Otr !Otr !Reg !Pic Brk !Pre !L !V !T !V !T !InC !Otr !Otr !Otr ! - :
+Otr :!CR !Brk !Otr Otr Otr !Reg !Pic !Brk !Pre !L !V !T !V !T !InC Otr Otr Otr ! - :
+Pre :!CR !Brk Otr Otr Otr Reg Pic !Brk Pre L V T V T InC Otr Otr Otr ! - :
+L :!CR !Brk !Otr Otr Otr !Reg !Pic !Brk !Pre L V !T V T !InC Otr Otr Otr ! - :
+V :!CR !Brk !Otr Otr Otr !Reg !Pic !Brk !Pre !L V T !V !T !InC Otr Otr Otr ! - :
+T :!CR !Brk !Otr Otr Otr !Reg !Pic !Brk !Pre !L !V T !V !T !InC Otr Otr Otr ! - :
+Pic :!CR !Brk !Otr Pic Otr !Reg !Pic !Brk !Pre !L !V !T !V !T !InC PicZ Pic Pic ! - :
+PicZ:!CR !Brk !Otr Otr Otr !Reg Pic !Brk !Pre !L !V !T !V !T !InC Otr Otr Otr ! - :
+Reg :!CR !Brk !Otr Otr Otr Otr !Pic !Brk !Pre !L !V !T !V !T !InC Otr Otr Otr ! - :
+InC :!CR !Brk !Otr Otr Otr !Reg !Pic !Brk !Pre !L !V !T !V !T !InC InC InC InCL! - :
+InCL:!CR !Brk !Otr Otr Otr !Reg !Pic !Brk !Pre !L !V !T !V !T InC InCL InCL InCL! - :
+SoTN: CR Brk Otr Otr Otr Reg Pic Brk Pre L V T V T InC Otr Otr Otr - :
+SoT :!CR !Brk !Otr !Otr !Otr !Reg !Pic !Brk !Pre !L !V !T !V !T !InC !Otr !Otr !Otr - :
+CAny:!CR !Brk Otr CExt Otr CReg!Pic !Brk Pre L V T V T InC CZWJ CIE CIL - :
+CZWJ:!CR !Brk !Otr Otr Otr !Reg $LAZP!Brk !Pre !L !V !T !V !T $LAIC CZIE CZIE CZIL! - :
+CIE :!CR !Brk !Otr CExt Otr !Reg !Pic !Brk !Pre !L !V !T !V !T $LAIC CIEZ CIE CIL ! - :
+CIL :!CR !Brk !Otr CExt Otr !Reg !Pic !Brk !Pre !L !V !T !V !T $LAIL CILZ CIL CIL ! - :
+CIEZ:!CR !Brk !Otr Otr Otr !Reg $LAZP!Brk !Pre !L !V !T !V !T !InC CZIE CZIE CZIL! - :
+CILZ:!CR !Brk !Otr Otr Otr !Reg $LAZP!Brk !Pre !L !V !T !V !T $LAIL CZIL CZIL CZIL! - :
+CZIE:!CR !Brk !Otr Otr Otr !Reg !Pic !Brk !Pre !L !V !T !V !T $LAIC CZIE CZIE CZIL! - :
+CZIL:!CR !Brk !Otr Otr Otr !Reg !Pic !Brk !Pre !L !V !T !V !T $LAIL CZIL CZIL CZIL! - :
+CExt:!CR !Brk !Otr CExt Otr !Reg !Pic !Brk !Pre !L !V !T !V !T !InC CExZ CExt CExt! - :
+CExZ:!CR !Brk !Otr Otr Otr !Reg $LAZP!Brk !Pre !L !V !T !V !T !InC Otr Otr Otr ! - :
+CReg:!CR !Brk !Otr Otr Otr $LARe!Pic !Brk !Pre !L !V !T !V !T !InC Otr Otr Otr ! - :
+''';
+
+void writeForwardAutomaton(StringSink buffer, {required bool verbose}) {
+ assert(categories.length == categoryCount);
+ assert(automatonRowLength & maskFlags == 0 &&
+ automatonRowLength >= categoryCount);
+ var table = Uint16List(stateLimit);
+ void transitionLA(int state, int category, int targetState, int flags) {
+ assert(flags <= maskFlags);
+ assert(
+ flags != flagLookahead || targetState >= stateLookaheadMin,
+ '${stateShortName(state)} x ${categoryNames[category]} -> '
+ '${_targetStateName(targetState, flags)} | $flags');
+ table[state + category] = targetState + flags;
+ }
+
+ void transition(int state, int category, int targetState, bool breakBefore) {
+ assert(targetState < stateLimit, '$state + $category -> $targetState');
+ transitionLA(
+ state, category, targetState, breakBefore ? flagBreak : flagNoBreak);
+ }
+
+ for (var state = 0; state < stateLimit; state += automatonRowLength) {
+ // States that should always be broken after, unless something specifically
+ // says otherwise. (And does so in GB1..G5).
+ var alwaysBreakBefore =
+ state == stateSoT || state == stateBreak || state == stateCR;
+
+ // States that should never be broken after, unless `alwaysBreakBefore`
+ // says otherwise (for example the rules in GB1..GB5).
+ var neverBreakBefore = state == stateSoTNoBreak ||
+ state == stateCAny || // Break in this state never matters.
+ state == statePrepend;
+
+ // Other with InCB=None.
+ // No rules apply specifically to Other, so break unless an
+ // Any rule applies.
+ transition(state, categoryOther, stateOther, !neverBreakBefore);
+ // Other with InCB=Consonant.
+ // GB9C. (Break unless Any rule applies, or preceded by indic sequence
+ // with at least one Linked, `stateInCL`).
+ // Remember having seen InCB=Consonant and no InCB=Linked yet.
+
+ if (state == stateCZWJ || state == stateCIE || state == stateCZIE) {
+ transitionLA(
+ state, categoryOtherIndicConsonant, stateLookaheadInC, flagLookahead);
+ } else if (state == stateCIL || state == stateCILZ || state == stateCZIL) {
+ transitionLA(state, categoryOtherIndicConsonant, stateLookaheadInCL,
+ flagLookahead);
+ } else {
+ transition(state, categoryOtherIndicConsonant, stateInC,
+ !(neverBreakBefore || state == stateInCL || state == stateCAny));
+ }
+ // CR.
+ // GB4 + GB5. Always break, after unless followed by LF, so remember
+ // having seen CR (`stateCR`).
+ transition(state, categoryCR, stateCR, state != stateSoTNoBreak);
+
+ // LF.
+ // GB3 + GB4 + GB5. Always break after. Break before unless following CR.
+ transition(state, categoryLF, stateBreak,
+ state != stateCR && state != stateSoTNoBreak);
+
+ // Control. (Like CR+LF, without their mutual exception.)
+ // GB4 + GB5. Always break before, even after Prepend,
+ // and always break after (`stateBreak`).
+ transition(state, categoryControl, stateBreak, state != stateSoTNoBreak);
+
+ // Ext + ZWJ (including InCB Extend and Linked).
+ // GB9 + GB9c + GB11. Never break before Ext or ZWJ,
+ // unless required by earlier rule (after Control, CR, LF, SoT).
+ // Remember whether after Pic+Ext* or InCB=Consonant(Extend|Linked)*
+ if (state == statePictographic) {
+ // GB9 + GB11, after Pic+Ext*.
+ // Extend with InCB=None.
+ transition(state, categoryExtend, statePictographic, false);
+ // Extend with InCB=Extend.
+ transition(state, categoryExtendIndicExtend, statePictographic, false);
+ // Extend with InCB=Linked.
+ transition(state, categoryExtendIndicLinked, statePictographic, false);
+ // ZWJ.
+ transition(state, categoryZWJ, statePictographicZWJ, false);
+ } else if (state == stateInC || state == stateInCL) {
+ // GB9 + GB9c, after InCB Consonant + (Extend|Linked)*.
+ // Extend with InCB=None.
+ transition(state, categoryExtend, stateOther, false);
+ // Extend with InCB=Extend.
+ transition(state, categoryExtendIndicExtend, state, false);
+ // ZWJ (which has InCB=Extend).
+ transition(state, categoryZWJ, state, false);
+ // Extend with InCB=Linked.
+ transition(state, categoryExtendIndicLinked, stateInCL, false);
+ } else if (state < stateMinContextUnaware || state == stateCReg) {
+ // GB9 alone.
+ // No special rules for breaking after,
+ // break before only if required by GB1-GB5.
+ transition(state, categoryExtend, stateOther, alwaysBreakBefore);
+ transition(
+ state, categoryExtendIndicExtend, stateOther, alwaysBreakBefore);
+ transition(
+ state, categoryExtendIndicLinked, stateOther, alwaysBreakBefore);
+ transition(state, categoryZWJ, stateOther, alwaysBreakBefore);
+ } else {
+ transition(
+ state,
+ categoryZWJ,
+ switch (state) {
+ stateCAny => stateCZWJ,
+ stateCZWJ => stateCZIE,
+ stateCIE => stateCIEZ,
+ stateCIL => stateCILZ,
+ stateCIEZ => stateCZIE,
+ stateCILZ => stateCZIL,
+ stateCZIE => stateCZIE,
+ stateCZIL => stateCZIL,
+ stateCExt => stateCExZ,
+ _ => stateOther,
+ },
+ false);
+ transition(
+ state,
+ categoryExtend,
+ (state == stateCAny ||
+ state == stateCIE ||
+ state == stateCIL ||
+ state == stateCExt)
+ ? stateCExt
+ : stateOther,
+ false);
+ transition(
+ state,
+ categoryExtendIndicExtend,
+ switch (state) {
+ stateCAny => stateCIE,
+ stateCZWJ => stateCZIE,
+ stateCIE => stateCIE,
+ stateCIL => stateCIL,
+ stateCIEZ => stateCZIE,
+ stateCILZ => stateCZIL,
+ stateCZIE => stateCZIE,
+ stateCZIL => stateCZIL,
+ stateCExt => stateCExt,
+ _ => stateOther,
+ },
+ false);
+ transition(
+ state,
+ categoryExtendIndicLinked,
+ switch (state) {
+ stateCAny => stateCIL,
+ stateCZWJ => stateCZIL,
+ stateCIE => stateCIL,
+ stateCIL => stateCIL,
+ stateCIEZ => stateCZIL,
+ stateCILZ => stateCZIL,
+ stateCZIE => stateCZIL,
+ stateCZIL => stateCZIL,
+ stateCExt => stateCExt,
+ _ => stateOther,
+ },
+ false);
+ }
+ // Regional indicator.
+ // GB12 + GB13: Don't break if after an odd number of Reg.
+ // Otherwise remember an odd number of Reg, and break before unless
+ // prior state says not to.
+ if (state == stateRegionalSingle) {
+ transition(state, categoryRegionalIndicator, stateOther, false);
+ } else if (state == stateCAny) {
+ transition(state, categoryRegionalIndicator, stateCReg, false);
+ } else if (state == stateCReg) {
+ transitionLA(state, categoryRegionalIndicator, stateLookaheadRegionalEven,
+ flagLookahead);
+ } else {
+ // Break unless prior state says not to.
+ transition(state, categoryRegionalIndicator, stateRegionalSingle,
+ !neverBreakBefore);
+ }
+
+ // Prepend.
+ // GB9b: Never break after Prepend (unless required by next character
+ // due to GB1..GB5).
+ // Break before unless prior state says not to.
+ transition(state, categoryPrepend, statePrepend, !neverBreakBefore);
+ // Spacing mark. (Like Extend but doesn't interact with emojis).
+ // GB9a. Don't break before, unless must always break after prior char.
+ transition(state, categorySpacingMark, stateOther, alwaysBreakBefore);
+ // Hangul.
+ // GB6+GB7+GB8.
+ // Don't break if T follows V and V follows L.
+ transition(
+ state, categoryL, stateL, !(neverBreakBefore || state == stateL));
+ transition(
+ state, categoryLV, stateV, !(neverBreakBefore || state == stateL));
+ transition(
+ state, categoryLVT, stateT, !(neverBreakBefore || state == stateL));
+ transition(state, categoryV, stateV,
+ !(neverBreakBefore || state == stateL || state == stateV));
+ transition(state, categoryT, stateT,
+ !(neverBreakBefore || state == stateV || state == stateT));
+ // Emoji
+ // GB11.
+ if (state == stateCZWJ ||
+ state == stateCExZ ||
+ state == stateCIEZ ||
+ state == stateCILZ) {
+ transitionLA(state, categoryPictographic, stateLookaheadZWJPictographic,
+ flagLookahead);
+ } else {
+ transition(
+ state,
+ categoryPictographic,
+ statePictographic,
+ state != statePrepend &&
+ state != statePictographicZWJ &&
+ state != stateSoTNoBreak);
+ }
+ // End of input.
+ // GB2.
+ transition(state, categoryEoT, stateSoTNoBreak,
+ state != stateSoT && state != stateSoTNoBreak && state != stateCAny);
+
+ // Pad table if necessary.
+ for (var c = categoryCount; c < automatonRowLength; c++) {
+ transition(state, c, stateSoTNoBreak, false);
+ }
+ }
+ const prefix = 'const _stateMachine = ';
+ buffer.write(prefix);
+ var stringWriter = StringLiteralWriter(buffer, padding: 4);
+ stringWriter.start(prefix.length);
+ for (var i = 0; i < table.length; i++) {
+ stringWriter.add(table[i]);
+ }
+ stringWriter.end();
+ buffer.write(';\n');
+ buffer.write(_moveMethod);
+
+ if (verbose) _writeForwardTable(table, automatonRowLength);
+}
+
+const String _moveMethod = '''
+$preferInline
+int move(int state, int inputCategory) =>
+ _stateMachine.codeUnitAt((state & $maskState) + inputCategory);
+''';
+
+const String _moveBackMethod = '''
+$preferInline
+int moveBack(int state, int inputCategory) =>
+ _backStateMachine.codeUnitAt((state & $maskState) + inputCategory);
+''';
+
+const categories = [
+ categoryOther,
+ categoryCR,
+ categoryLF,
+ categoryControl,
+ categoryExtend,
+ categoryRegionalIndicator,
+ categoryPrepend,
+ categorySpacingMark,
+ categoryL,
+ categoryV,
+ categoryT,
+ categoryLV,
+ categoryLVT,
+ categoryPictographic,
+ categoryOtherIndicConsonant,
+ categoryZWJ,
+ categoryExtendIndicExtend,
+ categoryExtendIndicLinked,
+ categoryEoT,
+];
+
+//////////////////////////////////////////////////////////////////////////////
+// Transition table for *reverse* grapheme cluster break automaton.
+// For each previous state and each previous input character category,
+// emit a new state and whether to break after that input character.
+// The table uses `!` to mark a break before the input character,
+// and then the output state.
+// Some breaks cannot be determined without look-ahead. Those return
+// specially marked states, with `$` in the name.
+// Those states will trigger a special code path which will then update
+// the state and/or index as necessary.
+//
+// Stored as string for comparison to actual generated automaton.
+const expectedBackAutomatonDescription = r'''
+Stat: Cat
+ : CR Ctl Otr Ext Spc Reg Pic LF Pre L V T LV LVT OInC ZWJ EInE EInL SoT :
+-----------------------------------------------------------------------------------------------------
+Brk :!Brk !Brk !Otr !Ext !Ext !Reg !Pic !LF !Otr !L !V !T !L !L !InC !Ext !Ext !Ext ! - :
+LF : Brk !Brk !Otr !Ext !Ext !Reg !Pic !LF !Otr !L !V !T !L !L !InC !Ext !Ext !Ext ! - :
+Otr :!Brk !Brk !Otr !Ext !Ext !Reg !Pic !LF Otr !L !V !T !L !L !InC !Ext !Ext !Ext ! - :
+Ext :!Brk !Brk Otr Ext Ext Reg Pic !LF Otr L V T L L InC Ext Ext Ext ! - :
+L :!Brk !Brk !Otr !Ext !Ext !Reg !Pic !LF Otr L !V !T !L !L !InC !Ext !Ext !Ext ! - :
+V :!Brk !Brk !Otr !Ext !Ext !Reg !Pic !LF Otr L V !T L !L !InC !Ext !Ext !Ext ! - :
+T :!Brk !Brk !Otr !Ext !Ext !Reg !Pic !LF Otr !L V T L L !InC !Ext !Ext !Ext ! - :
+Pic :!Brk !Brk !Otr !Ext !Ext !Reg !Pic !LF Otr !L !V !T !L !L !InC $LAZP!Ext !Ext ! - :
+RegO: - - - - - RegE - - - - - - - - - - - - - :
+Reg :!Brk !Brk !Otr !Ext !Ext $LARe!Pic !LF Otr !L !V !T !L !L !InC !Ext !Ext !Ext ! - :
+InC :!Brk !Brk !Otr !Ext !Ext !Reg !Pic !LF Otr !L !V !T !L !L !InC $LAIC$LAIC$LAIL! - :
+RegE:!Brk !Brk !Otr !Ext !Ext !RegO!Pic !LF Otr !L !V !T !L !L !InC !Ext !Ext !Ext ! - :
+EoTN: Brk Brk Otr Ext Ext Reg Pic LF Otr L V T L L InC Ext Ext Ext - :
+EoT :!Brk !Brk !Otr !Ext !Ext !Reg !Pic !LF !Otr !L !V !T !L !L !InC !Ext !Ext !Ext - :
+LAZP:#Ext #Ext !Otr LAZP!Ext !Reg Pic #Ext !Otr !L !V !T !L !L !InC !Ext LAZP LAZP#Ext :
+LAIC:#Ext #Ext !Otr !Ext !Ext !Reg !Pic #Ext !Otr !L !V !T !L !L !InC LAIC LAIC LAIL#Ext :
+LAIL:#Ext #Ext !Otr !Ext !Ext !Reg !Pic #Ext !Otr !L !V !T !L !L InC LAIL LAIL LAIL#Ext :
+LARe: RegE RegE RegE RegE RegE LARo RegE RegE RegE RegE RegE RegE RegE RegE RegE RegE RegE RegE RegE:
+LARo:!RegO!RegO!RegO!RegO!RegO LARe!RegO!RegO!RegO!RegO!RegO!RegO!RegO!RegO!RegO!RegO!RegO!RegO!RegO:
+''';
+
+// The look-ahead part of the state machine is triggered by the `$`-transitions
+// above.
+// It is really a combination of three state machines, one for RI, one
+// for ZWJ+Pic and one for InCB. The backwards automaton always knows
+// which one it starts in.
+// A state not in the LA-range means to end lookahead with that state.
+// If starting with `stateLookaheadRegional`,
+// the result always resets the position to before the lookahead,
+// and the output state only states whether to break before that position.
+// (The output states are always one of `stateRegionalEven` or
+// `stateRegionalOdd`+break-before.)
+// Represented by ` ` for not breaking and `!` for breaking.
+//
+// For the other lookaheads, the output flags represent one of:
+// The marker before the target state means one of four things:
+// - ' ': No break up to and including last seen character.
+// - '!': Break before char before lookahead, none up to last seen character.
+// - '#`: Break before char before lookahead and before last seen character.
+// In this case, the output state is the state before that character.
+// (So move character position to before last lookahead step.)
+//
+// Examples of '≮' the last would be ZWJ + EXT + ZWJ + PIC which does lookahead
+// after seeing ZWJ+PIC. Seeing the second ZWJ, it knows it's not
+// a PIC+EXT*+ZWJ+PIC sequence, so it must break before the second ZWJ.
+// It also knows that it doesn't need to break again up to the first ZWJ,
+// because it's all EXT characters. It's output state is `≮Ext`.
+// An example of `#` would be `CR + EXT + ZWJ + PIC` which knows when it's
+// seen the `CR` that it should break after CR and ZWJ.
+// (Since it can only return one break at a time, it'll keep the position after
+// CR with a state of Ext and return the position between ZWJ and PIC.)
+
+// The look-ahead states are recognized and calls out to code that looks
+// ahead (backwards in the string) to see what the state should really be after
+const backStates = <int>[
+ stateBreak,
+ stateLF,
+ stateOther,
+ stateExtend,
+ stateL,
+ stateV,
+ stateT,
+ statePictographic,
+ stateRegionalOdd, // Known disjoint look-ahead.
+ stateRegionalSingle,
+ stateInC,
+ stateRegionalEven,
+ stateEoTNoBreak,
+ stateEoT,
+ stateLookaheadRegionalEven,
+ stateLookaheadRegionalOdd,
+ stateLookaheadZWJPictographic,
+ stateLookaheadInC,
+ stateLookaheadInCL,
+];
+
+void writeBackwardAutomaton(StringSink buffer, {required bool verbose}) {
+ assert(categories.length <= automatonRowLength);
+ var table = Uint16List(backStateLimit);
+ void transitionLA(int state, int category, int targetState, int flags) {
+ assert(state < backStateLimit && targetState < backStateLimit,
+ '$state + $category -> $targetState');
+ assert(
+ switch ((state, targetState)) {
+ (< stateLookaheadMin, < stateLookaheadMin) => flags < flagLookahead,
+ // Entering lookahead. Always sets the flagLookahead bit.
+ (< stateLookaheadMin, _) => flags == flagLookahead,
+ // Exiting lookahead, can have any flag value.
+ (_, < stateLookaheadMin) => flags <= maskFlags,
+ // Inside lookahead, not done yet.
+ (_, _) => flags == 0,
+ },
+ '$state + $category => $targetState | $flags');
+ table[state + category] = targetState | flags;
+ }
+
+ void transition(int state, int category, int targetState, bool breakBefore) {
+ assert(state < stateLookaheadMin && targetState < stateLookaheadMin);
+ transitionLA(
+ state, category, targetState, (breakBefore ? flagBreak : flagNoBreak));
+ }
+
+ for (var state in backStates) {
+ if (state < stateLookaheadMin) {
+ if (state == stateRegionalOdd) {
+ // Special state where we know the previous character
+ // to some degree, due to having done look-ahead.
+ // Most inputs are unreachable. Use EoT-nobreak as unreachable marker.
+ for (var i = 0; i <= categoryCount; i++) {
+ transition(state, i, stateEoTNoBreak, false);
+ }
+ transition(state, categoryRegionalIndicator, stateRegionalEven, false);
+ // Remaining inputs are unreachable.
+ continue;
+ }
+ transition(state, categoryOther, stateOther,
+ state != stateExtend && state != stateEoTNoBreak);
+ transition(state, categoryOtherIndicConsonant, stateInC,
+ state != stateExtend && state != stateEoTNoBreak);
+ transition(state, categoryLF, stateLF, state != stateEoTNoBreak);
+ transition(state, categoryCR, stateBreak,
+ state != stateLF && state != stateEoTNoBreak);
+ transition(state, categoryControl, stateBreak, state != stateEoTNoBreak);
+
+ var breakBeforeExtend = state != stateExtend &&
+ state != stateRegionalOdd &&
+ state != stateEoTNoBreak;
+ transition(state, categoryExtend, stateExtend, breakBeforeExtend);
+ if (state != stateInC) {
+ transition(
+ state, categoryExtendIndicExtend, stateExtend, breakBeforeExtend);
+ transition(
+ state, categoryExtendIndicLinked, stateExtend, breakBeforeExtend);
+ } else {
+ // If these come just before an InCB Consonant, look ahead.
+ transitionLA(
+ state, categoryExtendIndicExtend, stateLookaheadInC, flagLookahead);
+ transitionLA(state, categoryExtendIndicLinked, stateLookaheadInCL,
+ flagLookahead);
+ }
+ transition(state, categorySpacingMark, stateExtend,
+ state != stateExtend && state != stateEoTNoBreak);
+ if (state == statePictographic) {
+ // Break-before value has no effect on lookahead states.
+ transitionLA(
+ state, categoryZWJ, stateLookaheadZWJPictographic, flagLookahead);
+ } else if (state == stateInC) {
+ transitionLA(state, categoryZWJ, stateLookaheadInC, flagLookahead);
+ } else {
+ transition(state, categoryZWJ, stateExtend,
+ state != stateExtend && state != stateEoTNoBreak);
+ }
+ if (state == stateRegionalEven) {
+ transition(state, categoryRegionalIndicator, stateRegionalOdd, true);
+ } else if (state == stateRegionalSingle) {
+ transitionLA(state, categoryRegionalIndicator,
+ stateLookaheadRegionalEven, flagLookahead);
+ } else {
+ transition(state, categoryRegionalIndicator, stateRegionalSingle,
+ state != stateExtend && state != stateEoTNoBreak);
+ }
+ transition(state, categoryPrepend, stateOther,
+ state == stateBreak || state == stateCR || state == stateEoT);
+ transition(
+ state,
+ categoryL,
+ stateL,
+ state != stateExtend &&
+ state != stateL &&
+ state != stateV &&
+ state != stateEoTNoBreak);
+ transition(
+ state,
+ categoryLV,
+ stateL,
+ state != stateExtend &&
+ state != stateV &&
+ state != stateT &&
+ state != stateEoTNoBreak);
+ transition(state, categoryLVT, stateL,
+ state != stateExtend && state != stateT && state != stateEoTNoBreak);
+ transition(
+ state,
+ categoryV,
+ stateV,
+ state != stateExtend &&
+ state != stateT &&
+ state != stateV &&
+ state != stateEoTNoBreak);
+ transition(state, categoryT, stateT,
+ state != stateExtend && state != stateT && state != stateEoTNoBreak);
+ transition(
+ state,
+ categoryPictographic,
+ statePictographic,
+ state != stateExtend &&
+ state != stateRegionalOdd &&
+ state != stateEoTNoBreak);
+ // Use EoT-NoBreak as marker for unreachable.
+ transition(state, categorySoT, stateEoTNoBreak,
+ state != stateEoT && state != stateEoTNoBreak);
+ } else {
+ if (state == stateLookaheadRegionalEven) {
+ transitionLA(
+ state, categoryRegionalIndicator, stateLookaheadRegionalOdd, 0);
+ for (var c = 0; c < categoryCount; c++) {
+ if (c != categoryRegionalIndicator) {
+ transitionLA(state, c, stateRegionalEven, 0);
+ }
+ }
+ continue;
+ }
+ if (state == stateLookaheadRegionalOdd) {
+ transitionLA(
+ state, categoryRegionalIndicator, stateLookaheadRegionalEven, 0);
+ for (var c = 0; c < categoryCount; c++) {
+ if (c != categoryRegionalIndicator) {
+ transitionLA(state, c, stateRegionalOdd, flagBreak);
+ }
+ }
+ continue;
+ }
+ transitionLA(state, categoryControl, stateExtend, flagLookaheadBreakBoth);
+ transitionLA(state, categoryCR, stateExtend, flagLookaheadBreakBoth);
+ transitionLA(state, categoryLF, stateExtend, flagLookaheadBreakBoth);
+ transitionLA(state, categoryOther, stateOther, flagLookaheadBreakEarly);
+ transitionLA(
+ state, categorySpacingMark, stateExtend, flagLookaheadBreakEarly);
+ transitionLA(state, categoryOther, stateOther, flagLookaheadBreakEarly);
+ transitionLA(state, categoryRegionalIndicator, stateRegionalSingle,
+ flagLookaheadBreakEarly);
+ transitionLA(
+ state,
+ categoryPictographic,
+ statePictographic,
+ state == stateLookaheadZWJPictographic
+ ? flagLookaheadBreakNone
+ : flagLookaheadBreakEarly);
+ transitionLA(state, categoryPrepend, stateOther, flagLookaheadBreakEarly);
+ transitionLA(state, categoryL, stateL, flagLookaheadBreakEarly);
+ transitionLA(state, categoryLV, stateL, flagLookaheadBreakEarly);
+ transitionLA(state, categoryLVT, stateL, flagLookaheadBreakEarly);
+ transitionLA(state, categoryV, stateV, flagLookaheadBreakEarly);
+ transitionLA(state, categoryT, stateT, flagLookaheadBreakEarly);
+ transitionLA(
+ state,
+ categoryOtherIndicConsonant,
+ stateInC,
+ state == stateLookaheadInCL
+ ? flagLookaheadBreakNone
+ : flagLookaheadBreakEarly);
+ if (state == stateLookaheadZWJPictographic) {
+ transitionLA(state, categoryExtend, state, 0);
+ transitionLA(state, categoryZWJ, stateExtend, flagLookaheadBreakEarly);
+ transitionLA(state, categoryExtendIndicLinked, state, 0);
+ } else {
+ transitionLA(
+ state, categoryExtend, stateExtend, flagLookaheadBreakEarly);
+ transitionLA(state, categoryZWJ, state, 0);
+ transitionLA(state, categoryExtendIndicLinked, stateLookaheadInCL, 0);
+ }
+ transitionLA(state, categoryExtendIndicExtend, state, 0);
+ transitionLA(state, categorySoT, stateExtend, flagLookaheadBreakBoth);
+ }
+ for (var i = categoryCount; i < automatonRowLength; i++) {
+ transitionLA(state, i, stateEoTNoBreak, 0);
+ }
+ }
+ var stringWriter = StringLiteralWriter(buffer, padding: 4);
+ buffer.write('const _backStateMachine = ');
+ stringWriter.start('const _backStateMachine = '.length);
+ for (var i = 0; i < table.length; i++) {
+ stringWriter.add(table[i]);
+ }
+ stringWriter.end();
+ buffer.write(';\n');
+ buffer.write(_moveBackMethod);
+ if (verbose) _writeBackTable(table, automatonRowLength);
+}
+
+void _writeForwardTable(Uint16List table, int automatonRowLength) {
+ var automaton = _generateTable(table, automatonRowLength, stateLimit,
+ stateShortName, backStateShortName, categoryShortNames, stateSoTNoBreak);
+ stdout.write(automaton);
+ if (automaton != expectedAutomatonDescription) {
+ stderr
+ ..writeln('DIFFERS FROM EXPECTATION:')
+ ..write(expectedAutomatonDescription);
+ }
+}
+
+void _writeBackTable(Uint16List table, int automatonRowLength) {
+ var backCategoryNames = [...categoryShortNames]..[categorySoT] = 'SoT';
+ var backAutomaton = _generateTable(
+ table,
+ automatonRowLength,
+ backStateLimit,
+ backStateShortName,
+ backStateShortName,
+ backCategoryNames,
+ stateEoTNoBreak,
+ );
+ stdout.write(backAutomaton);
+ if (backAutomaton != expectedBackAutomatonDescription) {
+ stderr
+ ..writeln('DIFFERS FROM EXPECTATION:')
+ ..write(expectedBackAutomatonDescription);
+ }
+}
+
+/// Writes an automaton table to string, for debugging.
+///
+/// The table has size `stateLimit`, which is a multiple of
+/// `automatonRowLength` and `automatonRowLength >= categoryCount`.
+/// The [stateNames] provide the names of the states for this particular
+/// automaton (differs between forward and backward automaton).
+/// It has a name for every target state that occurs in the *table*.
+/// The table contains states multiplied by `automatonRowLength`, possibly with
+/// the first bit set as a break-before/after flag.
+/// The [stateLimit] is an upper limit of "real" states that occur in the table,
+/// states above that, if any, are synthetic states that trigger non-
+/// automaton based scanning.
+/// The [ignoreState] is a single state that is not displayed.
+String _generateTable(
+ Uint16List table,
+ int automatonRowLength,
+ int stateLimit, // A multiple of automatonRowLength
+ String Function(int) stateNames,
+ String Function(int) lookaheadStateNames,
+ List<String> categoryNames,
+ int ignoreState) {
+ assert(automatonRowLength >= categoryCount);
+ assert(table.length == stateLimit);
+ var buf = StringBuffer();
+ buf.writeln('Stat: Cat');
+ var preHeaderLength = buf.length;
+ buf.write(' :');
+ for (var i = 0; i < categoryCount; i++) {
+ buf
+ ..write(' ')
+ ..write(categoryNames[i].padRight(4));
+ }
+ buf.writeln(':');
+ var lineLength = buf.length - preHeaderLength;
+ buf.writeln('-' * (lineLength - 1));
+ for (var si = 0; si < stateLimit; si += automatonRowLength) {
+ var stateName = stateNames(si);
+ buf
+ ..write(stateName.padRight(4))
+ ..write(':');
+ for (var ci = 0; ci < categoryCount; ci++) {
+ var value = table[si + ci];
+ var targetState = value & maskState;
+ var flags = value & maskFlags;
+ var prefix = r' !$#'[flags];
+
+ var targetStateName = (flags == flagLookahead)
+ ? lookaheadStateNames(targetState)
+ : stateNames(targetState);
+ // EoT is marker for unreachable states.
+ if (targetState == ignoreState) targetStateName = ' - ';
+ buf
+ ..write(prefix)
+ ..write(targetStateName.padRight(4));
+ }
+ buf.writeln(':');
+ }
+ return buf.toString();
+}
+
+/// Target state name for forward automaton.
+String _targetStateName(int state, int flags) {
+ if (flags == flagLookahead) return backStateShortName(state);
+ return stateShortName(state);
+}
+
+const preferInline = """
+@pragma('dart2js:prefer-inline')
+@pragma('vm:prefer-inline')
+@pragma('wasm:prefer-inline')""";
diff --git a/pkgs/characters/tool/src/data_files.dart b/pkgs/characters/tool/src/data_files.dart
new file mode 100644
index 0000000..090b6b8
--- /dev/null
+++ b/pkgs/characters/tool/src/data_files.dart
@@ -0,0 +1,83 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+import 'shared.dart' as util;
+
+// Abstraction over files fetched from the `unicode.org/Public` UCD repository.
+// If any of these URIs stop working, find out where they have moved to.
+
+final graphemeBreakPropertyData = DataFile(
+ 'https://unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakProperty.txt',
+ 'third_party/Unicode_Consortium/GraphemeBreakProperty.txt');
+
+final emojiData = DataFile(
+ 'https://unicode.org/Public/UCD/latest/ucd/emoji/emoji-data.txt',
+ 'third_party/Unicode_Consortium/emoji_data.txt');
+
+final graphemeTestData = DataFile(
+ 'https://unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakTest.txt',
+ 'third_party/Unicode_Consortium/GraphemeBreakTest.txt');
+
+final emojiTestData = DataFile(
+ 'https://unicode.org/Public/emoji/latest/emoji-test.txt',
+ 'third_party/Unicode_Consortium/emoji_test.txt');
+
+final licenseFile = DataFile('https://www.unicode.org/license.txt',
+ 'third_party/Unicode_Consortium/UNICODE_LICENSE.txt');
+
+final derivedData = DataFile(
+ 'https://unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt',
+ 'third_party/Unicode_Consortium/DerivedCoreProperties.txt');
+
+class DataFile {
+ /// Source URI.
+ final String sourceLocation;
+
+ /// Target file path relative to package root.
+ final String targetLocation;
+
+ /// Cached contents.
+ String? _contents;
+
+ DataFile(this.sourceLocation, this.targetLocation);
+
+ Future<String> get contents async => _contents ??= await load();
+
+ /// Loads file, fetching it from the source first if necessary.
+ ///
+ /// If [checkForUpdate] is `false`, the content of an existing file at
+ /// [targetLocation] is read. If there is no file or if [checkForUpdate]
+ /// is `true`, new content is fetched from the [sourceLocation] URI
+ /// and written to the [targetLocation] file.
+ Future<String> load({bool checkForUpdate = false}) async =>
+ (checkForUpdate ? null : _contents) ??
+ (_contents = await util.fetch(sourceLocation,
+ targetFile: _targetFile, forceLoad: checkForUpdate));
+
+ /// Fetches content, compares to existing content.
+ ///
+ /// Returns `null` if no change, a path to a temporary file containing the
+ /// new content if there are changes.
+ Future<String?> checkChange([bool Function(String, String)? equals]) async {
+ equals ??= _eq;
+ var contents = await this.contents;
+ var tmpFile = File(util.tmpPath(targetLocation));
+ var newContents =
+ await util.fetch(sourceLocation, targetFile: tmpFile, forceLoad: true);
+ if (equals(contents, newContents)) {
+ return null;
+ }
+ return tmpFile.path;
+ }
+
+ static bool _eq(String a, String b) => a == b;
+
+ void copyFrom(String contentPath) {
+ _contents = null;
+ File(contentPath).copySync(_targetFile.path);
+ }
+
+ File get _targetFile => File(util.packagePath(targetLocation));
+}
diff --git a/pkgs/characters/tool/src/debug_names.dart b/pkgs/characters/tool/src/debug_names.dart
new file mode 100644
index 0000000..3b8bf81
--- /dev/null
+++ b/pkgs/characters/tool/src/debug_names.dart
@@ -0,0 +1,182 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Helper for displaying names of categories.
+import 'package:characters/src/grapheme_clusters/constants.dart';
+
+final categoryNames = List.filled(categoryCount, '')
+ ..[categoryCR] = 'CR'
+ ..[categoryControl] = 'Control'
+ ..[categoryOther] = 'Other'
+ ..[categoryExtend] = 'Extend'
+ ..[categorySpacingMark] = 'SpacingMark'
+ ..[categoryRegionalIndicator] = 'RegionalIndicator'
+ ..[categoryPictographic] = 'Pictographic'
+ ..[categoryLF] = 'LF'
+ ..[categoryPrepend] = 'Prepend'
+ ..[categoryL] = 'L'
+ ..[categoryV] = 'V'
+ ..[categoryT] = 'T'
+ ..[categoryLV] = 'LV'
+ ..[categoryLVT] = 'LVT'
+ ..[categoryOtherIndicConsonant] = 'OtherIndicConsonant'
+ ..[categoryZWJ] = 'ZWJ'
+ ..[categoryExtendIndicExtend] = 'ExtendIndicExtend'
+ ..[categoryExtendIndicLinked] = 'ExtendIndicLinked'
+ ..[categoryEoT] = 'EoT'
+ ..forEach((text) {
+ if (text.isEmpty) throw StateError('Uninitialized category name');
+ });
+
+final categoryShortNames = List.filled(categoryCount, '')
+ ..[categoryCR] = 'CR'
+ ..[categoryControl] = 'Ctl'
+ ..[categoryOther] = 'Otr'
+ ..[categoryExtend] = 'Ext'
+ ..[categorySpacingMark] = 'Spc'
+ ..[categoryRegionalIndicator] = 'Reg'
+ ..[categoryPictographic] = 'Pic'
+ ..[categoryLF] = 'LF'
+ ..[categoryPrepend] = 'Pre'
+ ..[categoryL] = 'L'
+ ..[categoryV] = 'V'
+ ..[categoryT] = 'T'
+ ..[categoryLV] = 'LV'
+ ..[categoryLVT] = 'LVT'
+ ..[categoryOtherIndicConsonant] = 'OInC'
+ ..[categoryZWJ] = 'ZWJ'
+ ..[categoryExtendIndicExtend] = 'EInE'
+ ..[categoryExtendIndicLinked] = 'EInL'
+ ..[categoryEoT] = 'EoT'
+ ..forEach((text) {
+ if (text.isEmpty) throw StateError('Uninitialized category short name');
+ });
+
+final List<String> categoryLongNames = List<String>.filled(categoryCount, '')
+ ..[categoryOther] = 'Other'
+ ..[categoryCR] = 'CR'
+ ..[categoryLF] = 'LF'
+ ..[categoryControl] = 'Control'
+ ..[categoryExtend] = 'Extend'
+ ..[categoryZWJ] = 'ZWJ'
+ ..[categoryRegionalIndicator] = 'RI'
+ ..[categoryPrepend] = 'Prepend'
+ ..[categorySpacingMark] = 'SpacingMark'
+ ..[categoryL] = 'L'
+ ..[categoryV] = 'V'
+ ..[categoryT] = 'T'
+ ..[categoryLV] = 'LV'
+ ..[categoryLVT] = 'LVT'
+ ..[categoryPictographic] = 'Pictographic'
+ ..[categoryOtherIndicConsonant] = 'Other{InCB=Consonant}'
+ ..[categoryExtendIndicExtend] = 'Extend{InCB=Extend}'
+ ..[categoryExtendIndicLinked] = 'Extend{InCB=Linked}'
+ ..[categoryEoT] = 'EoT';
+
+String stateName(int state) => _stateNames[state ~/ automatonRowLength];
+final _stateNames = List<String>.filled(stateLimit ~/ automatonRowLength, '')
+ ..[stateSoT ~/ automatonRowLength] = 'SoT'
+ ..[stateBreak ~/ automatonRowLength] = 'Break'
+ ..[stateCR ~/ automatonRowLength] = 'CR'
+ ..[stateOther ~/ automatonRowLength] = 'Other'
+ ..[statePrepend ~/ automatonRowLength] = 'Prepend'
+ ..[stateL ~/ automatonRowLength] = 'L'
+ ..[stateV ~/ automatonRowLength] = 'V'
+ ..[stateT ~/ automatonRowLength] = 'T'
+ ..[statePictographic ~/ automatonRowLength] = 'Pictographic'
+ ..[statePictographicZWJ ~/ automatonRowLength] = 'PictographicZWJ'
+ ..[stateRegionalSingle ~/ automatonRowLength] = 'RegionalSingle'
+ ..[stateSoTNoBreak ~/ automatonRowLength] = 'SoTNoBreak'
+ ..[stateInC ~/ automatonRowLength] = 'InC'
+ ..[stateInCL ~/ automatonRowLength] = 'InCL'
+ ..[stateCAny ~/ automatonRowLength] = '?'
+ ..[stateCZWJ ~/ automatonRowLength] = '?+ZWJ'
+ ..[stateCIE ~/ automatonRowLength] = '?+IndicExtend'
+ ..[stateCIL ~/ automatonRowLength] = '?+IndicLinked'
+ ..[stateCIEZ ~/ automatonRowLength] = '?+IndicExtendZWJ'
+ ..[stateCILZ ~/ automatonRowLength] = '?+IndicLinkedZWJ'
+ ..[stateCZIE ~/ automatonRowLength] = '?+ZWJIndicExtend'
+ ..[stateCZIL ~/ automatonRowLength] = '?+ZWJIndicLinked'
+ ..[stateCExt ~/ automatonRowLength] = '?+Extend'
+ ..[stateCExZ ~/ automatonRowLength] = '?+ExtendZWJ'
+ ..[stateCReg ~/ automatonRowLength] = '?+Reg';
+
+String stateShortName(int state) =>
+ _stateShortNames[state ~/ automatonRowLength];
+final _stateShortNames =
+ List<String>.filled(stateLimit ~/ automatonRowLength, '')
+ ..[stateSoT ~/ automatonRowLength] = 'SoT'
+ ..[stateBreak ~/ automatonRowLength] = 'Brk'
+ ..[stateCR ~/ automatonRowLength] = 'CR'
+ ..[stateOther ~/ automatonRowLength] = 'Otr'
+ ..[statePrepend ~/ automatonRowLength] = 'Pre'
+ ..[stateL ~/ automatonRowLength] = 'L'
+ ..[stateV ~/ automatonRowLength] = 'V'
+ ..[stateT ~/ automatonRowLength] = 'T'
+ ..[statePictographic ~/ automatonRowLength] = 'Pic'
+ ..[statePictographicZWJ ~/ automatonRowLength] = 'PicZ'
+ ..[stateRegionalSingle ~/ automatonRowLength] = 'Reg'
+ ..[stateSoTNoBreak ~/ automatonRowLength] = 'SoTN'
+ ..[stateInC ~/ automatonRowLength] = 'InC'
+ ..[stateInCL ~/ automatonRowLength] = 'InCL'
+ ..[stateCAny ~/ automatonRowLength] = 'CAny'
+ ..[stateCZWJ ~/ automatonRowLength] = 'CZWJ'
+ ..[stateCIE ~/ automatonRowLength] = 'CIE'
+ ..[stateCIL ~/ automatonRowLength] = 'CIL'
+ ..[stateCIEZ ~/ automatonRowLength] = 'CIEZ'
+ ..[stateCILZ ~/ automatonRowLength] = 'CILZ'
+ ..[stateCZIE ~/ automatonRowLength] = 'CZIE'
+ ..[stateCZIL ~/ automatonRowLength] = 'CZIL'
+ ..[stateCExt ~/ automatonRowLength] = 'CExt'
+ ..[stateCExZ ~/ automatonRowLength] = 'CExZ'
+ ..[stateCReg ~/ automatonRowLength] = 'CReg';
+
+String backStateName(int state) => _backStateNames[state ~/ automatonRowLength];
+final _backStateNames = List<String>.filled(
+ backStateLimit ~/ automatonRowLength, '')
+ ..[stateEoT ~/ automatonRowLength] = 'EoT'
+ ..[stateBreak ~/ automatonRowLength] = 'Break'
+ ..[stateLF ~/ automatonRowLength] = 'LF'
+ ..[stateOther ~/ automatonRowLength] = 'Other'
+ ..[stateExtend ~/ automatonRowLength] = 'Extend'
+ ..[stateL ~/ automatonRowLength] = 'L'
+ ..[stateV ~/ automatonRowLength] = 'V'
+ ..[stateT ~/ automatonRowLength] = 'T'
+ ..[statePictographic ~/ automatonRowLength] = 'Pictographic'
+ ..[stateRegionalOdd ~/ automatonRowLength] =
+ 'RegionalOdd' // Known disjoint look-ahead
+ ..[stateRegionalSingle ~/ automatonRowLength] = 'RegionalSingle'
+ ..[stateEoTNoBreak ~/ automatonRowLength] = 'EoTNoBreak'
+ ..[stateInC ~/ automatonRowLength] = 'InC'
+ ..[stateRegionalEven ~/ automatonRowLength] = 'RegionalEven'
+ ..[stateLookaheadRegionalEven ~/ automatonRowLength] = 'RegionalLookaheadEven'
+ ..[stateLookaheadRegionalOdd ~/ automatonRowLength] = 'RegionalLookaheadOdd'
+ ..[stateLookaheadZWJPictographic ~/ automatonRowLength] =
+ 'ZWJPictographicLookahead'
+ ..[stateLookaheadInC ~/ automatonRowLength] = 'InCLookahead'
+ ..[stateLookaheadInCL ~/ automatonRowLength] = 'InCLLookahead';
+
+String backStateShortName(int state) =>
+ _backStateShortNames[state ~/ automatonRowLength];
+final _backStateShortNames =
+ List<String>.filled(backStateLimit ~/ automatonRowLength, '')
+ ..[stateEoT ~/ automatonRowLength] = 'EoT'
+ ..[stateBreak ~/ automatonRowLength] = 'Brk'
+ ..[stateLF ~/ automatonRowLength] = 'LF'
+ ..[stateOther ~/ automatonRowLength] = 'Otr'
+ ..[stateExtend ~/ automatonRowLength] = 'Ext'
+ ..[stateL ~/ automatonRowLength] = 'L'
+ ..[stateV ~/ automatonRowLength] = 'V'
+ ..[stateT ~/ automatonRowLength] = 'T'
+ ..[statePictographic ~/ automatonRowLength] = 'Pic'
+ ..[stateRegionalOdd ~/ automatonRowLength] = 'RegO'
+ ..[stateRegionalSingle ~/ automatonRowLength] = 'Reg'
+ ..[stateEoTNoBreak ~/ automatonRowLength] = 'EoTN'
+ ..[stateInC ~/ automatonRowLength] = 'InC'
+ ..[stateRegionalEven ~/ automatonRowLength] = 'RegE'
+ ..[stateLookaheadRegionalEven ~/ automatonRowLength] = 'LARe'
+ ..[stateLookaheadRegionalOdd ~/ automatonRowLength] = 'LARo'
+ ..[stateLookaheadZWJPictographic ~/ automatonRowLength] = 'LAZP'
+ ..[stateLookaheadInC ~/ automatonRowLength] = 'LAIC'
+ ..[stateLookaheadInCL ~/ automatonRowLength] = 'LAIL';
diff --git a/pkgs/characters/tool/src/graph.dart b/pkgs/characters/tool/src/graph.dart
new file mode 100644
index 0000000..d0722e4
--- /dev/null
+++ b/pkgs/characters/tool/src/graph.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// An asymmetric weighted complete graph.
+///
+/// The vertices are identified by numbers 0 through [vertexCount] - 1.
+/// Edges are pairs of vertices.
+class Graph {
+ /// Number of vertices.
+ final int vertexCount;
+
+ /// Table of weights, a list of length `vertexCount`*`vertexCount`.
+ final List<int> _table;
+
+ /// Creates a new complete graph with [vertexCount] vertices.
+ ///
+ /// The initial weights on all edges are [initialWeight].
+ Graph(this.vertexCount, [int initialWeight = 0])
+ : _table = List<int>.filled(vertexCount * vertexCount, initialWeight);
+
+ /// Update the weight on the edges from [fromVertex] to [toVertex].
+ void setWeight(int fromVertex, int toVertex, int newWeight) {
+ _table[fromVertex * vertexCount + toVertex] = newWeight;
+ }
+
+ /// The weight of the edge from [fromVertex] to [toVertex].
+ int weight(int fromVertex, int toVertex) =>
+ _table[fromVertex * vertexCount + toVertex];
+
+ /// The cumulative weight of the (sub-)path from `path[from]` to `path[to]`.
+ ///
+ /// If [to] is less than [from], the sub-path is traversed in reverse.
+ /// The values in `path` should be vertices in this graph.
+ int pathWeight(List<int> path, int from, int to) {
+ var weight = 0;
+ var cursor = path[from];
+ var step = from <= to ? 1 : -1;
+ for (var i = from; i != to;) {
+ i += step;
+ var next = path[i];
+ weight += this.weight(cursor, next);
+ cursor = next;
+ }
+ return weight;
+ }
+}
diff --git a/pkgs/characters/tool/src/grapheme_category_loader.dart b/pkgs/characters/tool/src/grapheme_category_loader.dart
new file mode 100644
index 0000000..0def73a
--- /dev/null
+++ b/pkgs/characters/tool/src/grapheme_category_loader.dart
@@ -0,0 +1,372 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io' show stderr;
+import 'dart:typed_data';
+
+import 'package:characters/src/grapheme_clusters/constants.dart';
+
+import 'data_files.dart';
+import 'debug_names.dart';
+
+// Loads the grapheme breaking categories from Unicode data files.
+
+/// Loads all categories by combining the categories of the
+/// grapheme break property with the categories of the InCB property.
+Future<Uint8List> loadCategories(
+ {bool update = false, bool verbose = false}) async {
+ var (graphemeTable, incbTable) = await (
+ loadGraphemeCategories(update: update, verbose: verbose),
+ loadInCBCategories(update: update, verbose: verbose)
+ ).wait;
+ if (verbose) {
+ _logIntersection(graphemeTable, incbTable);
+ }
+ for (var i = 0; i < graphemeTable.length; i++) {
+ var grapheme = graphemeTable[i];
+ var incb = incbTable[i];
+ if (incb != 0) {
+ if (grapheme == categoryZWJ) {
+ assert(incb == categoryExtendIndicExtend);
+ continue;
+ }
+ assert(incb == categoryOtherIndicConsonant && grapheme == categoryOther ||
+ (incb == categoryExtendIndicExtend ||
+ incb == categoryExtendIndicLinked) &&
+ grapheme == categoryExtend);
+ graphemeTable[i] = incb;
+ }
+ }
+ return graphemeTable;
+}
+
+/// Loads and parses the grapheme break categories from the grapheme break data,
+/// emoji data, and adds unpaired surrogates as controls.
+Future<Uint8List> loadGraphemeCategories(
+ {bool update = false, bool verbose = false}) async {
+ var dataFiles = await Future.wait([
+ graphemeBreakPropertyData.load(checkForUpdate: update),
+ emojiData.load(checkForUpdate: update),
+ // This data used to be in:
+ // https://www.unicode.org/Public/12.0.0/ucd/auxiliary/GraphemeBreakProperty-12.0.0d16.txt
+ // Make sure it's included.
+ Future.value(
+ 'D800..DFFF ; Control # Cc <control-D800>..<control-DFFF>\n'),
+ ]);
+ var table = _parseCategories(dataFiles, verbose: verbose);
+ return table;
+}
+
+/// Loads and parses the InCB categories from the derived properties data.
+Future<Uint8List> loadInCBCategories(
+ {bool update = false, bool verbose = false}) async {
+ var data = await derivedData.load(checkForUpdate: update);
+ var table = _parseInCBCategories(data, verbose: verbose);
+ return table;
+}
+
+/// Prints intersection between basic grapheme breaking properties
+/// and InCB categories.
+void _logIntersection(Uint8List table, Uint8List incbTable) {
+ const incbNames = ['None', 'Consonant', 'Extend', 'Linked'];
+ const incbOffsets = {
+ 0: 0,
+ categoryOtherIndicConsonant: 1,
+ categoryExtendIndicExtend: 2,
+ categoryExtendIndicLinked: 3,
+ };
+
+ var counts = List<int>.filled(categoryCount * incbNames.length, 0);
+ for (var i = 0; i < table.length; i++) {
+ var incbOffset = incbOffsets[incbTable[i]] ?? 0;
+ counts[table[i] * incbNames.length + incbOffset]++;
+ }
+ print(
+ "GC/InCB ${incbNames.map((s) => s.padLeft(10)).join(" ")}");
+ for (var i = 0; i < categoryCount; i++) {
+ if (i == categoryOtherIndicConsonant ||
+ i == categoryExtendIndicExtend ||
+ i == categoryExtendIndicLinked) {
+ assert(counts
+ .sublist(i * incbNames.length, (i + 1) * incbNames.length)
+ .every((c) => c == 0));
+ continue;
+ }
+ print("${categoryNames[i].padRight(20)}${[
+ for (var j = 0; j < incbNames.length; j++)
+ switch (counts[i * incbNames.length + j]) {
+ 0 => "",
+ var v => v.toString()
+ }
+ .padLeft(10)
+ ].join(" ")}");
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Unicode table parser.
+final _tableRE = RegExp(r'^([\dA-F]{4,5})(?:..([\dA-F]{4,5}))?\s*;\s*(\w+)\s*#',
+ multiLine: true);
+
+// The relevant names that occur in the Unicode tables.
+final categoryByName = {
+ 'CR': categoryCR,
+ 'LF': categoryLF,
+ 'Control': categoryControl,
+ 'Extend': categoryExtend,
+ 'ZWJ': categoryZWJ,
+ 'Regional_Indicator': categoryRegionalIndicator,
+ 'Prepend': categoryPrepend,
+ 'SpacingMark': categorySpacingMark,
+ 'L': categoryL,
+ 'V': categoryV,
+ 'T': categoryT,
+ 'LV': categoryLV,
+ 'LVT': categoryLVT,
+ 'Extended_Pictographic': categoryPictographic,
+};
+
+Uint8List _parseCategories(List<String> files, {required bool verbose}) {
+ var result = Uint8List(0x110000);
+ result.fillRange(0, result.length, categoryOther);
+ var count = 0;
+ var categoryCount = <String, int>{};
+ var categoryMin = <String, int>{
+ for (var category in categoryByName.keys) category: 0x10FFFF
+ };
+ int min(int a, int b) => a < b ? a : b;
+ for (var file in files) {
+ for (var match in _tableRE.allMatches(file)) {
+ var from = int.parse(match[1]!, radix: 16);
+ var endMatch = match[2];
+ var to = endMatch == null ? from : int.parse(endMatch, radix: 16);
+ var category = match[3]!;
+ assert(from <= to);
+ var categoryCode = categoryByName[category];
+ if (categoryCode != null) {
+ assert(result.getRange(from, to + 1).every((x) => x == categoryOther));
+ result.fillRange(from, to + 1, categoryCode);
+ count += to + 1 - from;
+ categoryMin[category] = min(categoryMin[category]!, from);
+ categoryCount[category] =
+ (categoryCount[category] ?? 0) + (to + 1 - from);
+ }
+ }
+ }
+ if (verbose) {
+ stderr.writeln('Loaded $count entries');
+ categoryCount.forEach((category, count) {
+ stderr.writeln(' $category: $count, min: U+'
+ "${categoryMin[category]!.toRadixString(16).padLeft(4, "0")}");
+ });
+ }
+ if (result[0xD800] != categoryControl) {
+ stderr.writeln('WARNING: Surrogates are not controls. Check inputs.');
+ }
+ if (categoryMin['Regional_Indicator']! < 0x10000) {
+ stderr.writeln('WARNING: Regional Indicator in BMP. '
+ 'Code assuming all RIs are non-BMP will fail');
+ }
+ return result;
+}
+
+// ---------------------------------------------------------------------------
+// Indic Conjunct Break property
+
+// Derived property. From definition:
+//
+// Define the set of applicable scripts. For Unicode 15.1, the set is defined as
+// S = [\p{sc=Beng}\p{sc=Deva}\p{sc=Gujr}\p{sc=Mlym}\p{sc=Orya}\p{sc=Telu}]
+// Then for any character C:
+// InCB = Linker iff C in [S &\p{Indic_Syllabic_Category=Virama}]
+// InCB = Consonant iff C in [S &\p{Indic_Syllabic_Category=Consonant}]
+// InCB = Extend iff C in
+// [\p{gcb=Extend}
+// \p{gcb=ZWJ}
+// -\p{InCB=Linker}
+// -\p{InCB=Consonant}
+// -[\u200C]]
+// Otherwise, InCB = None (the default value)
+//
+// Luckily it is precomputed in a file of its own.
+
+final _derivedPropertyTableRE = RegExp(
+ r'^([\dA-F]{4,5})(?:..([\dA-F]{4,5}))?\s*;\s*InCB\s*;\s*(\w+)\s*#'
+ r'|'
+ r'^# Total code points: (\d+)',
+ multiLine: true);
+
+Uint8List _parseInCBCategories(String file, {required bool verbose}) {
+ const categoryByName = {
+ 'Consonant': categoryOtherIndicConsonant,
+ 'Extend': categoryExtendIndicExtend,
+ 'Linker': categoryExtendIndicLinked
+ };
+ var result = Uint8List(0x110000);
+ var lines = 0;
+ var count = 0;
+ var counts = {for (var key in categoryByName.keys) key: 0};
+ var totalCounts = {for (var key in categoryByName.keys) key: 0};
+ var currentInCBCategory = '';
+ for (var match in _derivedPropertyTableRE.allMatches(file)) {
+ if (match[4] case var totalCountText?) {
+ if (currentInCBCategory.isNotEmpty) {
+ if (totalCounts[currentInCBCategory] != 0) {
+ throw FormatException(
+ 'More than one total count per category', match[0]!);
+ }
+ totalCounts[currentInCBCategory] = int.parse(totalCountText);
+ currentInCBCategory = '';
+ }
+ continue;
+ }
+ var start = int.parse(match[1]!, radix: 16);
+ // "None" should not occur in the table, since it's the default.
+ var name = match[3]!;
+ var incbCategory = categoryByName[name];
+ if (incbCategory == null) {
+ throw FormatException('Invalid InCB category', match[0]!);
+ }
+ currentInCBCategory = name;
+ var endMatch = match[2];
+ if (endMatch == null) {
+ assert(result[start] == 0);
+ result[start] = incbCategory;
+ lines += 1;
+ count += 1;
+ counts[name] = counts[name]! + 1;
+ } else {
+ var end = int.parse(endMatch, radix: 16);
+ assert(result.getRange(start, end + 1).every((x) => x == 0));
+ result.fillRange(start, end + 1, incbCategory);
+ var rangeLength = end - start + 1;
+ lines += 1;
+ count += rangeLength;
+ counts[name] = counts[name]! + rangeLength;
+ }
+ }
+ for (var name in categoryByName.keys) {
+ if (counts[name] != totalCounts[name]) {
+ stderr.writeln('${categoryByName[name]}: '
+ 'Parsed: ${counts[name]}, expected: ${totalCounts[name]}');
+ }
+ }
+ if (verbose) {
+ stderr.writeln('InCB categories: Loaded $count entries from $lines lines');
+ for (var name in categoryByName.keys) {
+ stderr.writeln(' ${name.padRight(9)}: '
+ '${counts[name].toString().padLeft(6)}');
+ }
+ }
+ return result;
+}
+
+// --------------------------------------------------------------------
+// TODO: Use a sparse table?
+// Likely not worth it.
+
+/// Fixed length table for Unicode properties.
+class UnicodePropertyTable {
+ static const int _unicodeCodePoints = 0x110000;
+ static const int _entrySize = 0x100;
+ static const int _entryMask = _entrySize - 1;
+ static const int _entryShift = 8;
+ static const int _entryCount = _unicodeCodePoints >> _entryShift;
+ final List<_TableEntry> _entries =
+ List.filled(_entryCount, const _ValueEntry(0));
+
+ int operator [](int index) =>
+ _entries[index >> _entryShift][index & _entryMask];
+
+ void operator []=(int index, int value) {
+ var entry = index >> _entryShift;
+ _entries[entry] = _entries[entry].set(index & _entryMask, value);
+ }
+
+ void fillRange(int start, int end, int value) {
+ RangeError.checkValidRange(start, end, _unicodeCodePoints);
+ var startEntry = start >>> _entryShift;
+ var endEntry = end >>> _entryShift;
+ var startOffset = start & _entryMask;
+
+ _ValueEntry? fullEntry;
+ if (startEntry - endEntry > 1) {
+ fullEntry = _ValueEntry(value); // TODO: Cache these per value.
+ }
+ while (startEntry < endEntry) {
+ _entries[startEntry] = _entries[startEntry]
+ .fillRange(startOffset, _entrySize, value, fullEntry);
+ startOffset = 0;
+ }
+ var endOffset = end & _entryMask;
+ if (endOffset > 0) {
+ _entries[endEntry] = _entries[endEntry]
+ .fillRange(startOffset, endOffset, value, fullEntry);
+ }
+ }
+}
+
+sealed class _TableEntry {
+ const _TableEntry();
+ int operator [](int index);
+ _TableEntry set(int index, int value);
+ _TableEntry fillRange(int start, int end, int value, _ValueEntry? fullEntry) {
+ RangeError.checkValidRange(start, end, UnicodePropertyTable._entrySize);
+ if (start == 0 && end == UnicodePropertyTable._entrySize) {
+ return fullEntry ?? _ValueEntry(value);
+ }
+ return _fillRange(start, end, value, fullEntry);
+ }
+
+ _TableEntry _fillRange(int start, int end, int value, _ValueEntry? fullEntry);
+}
+
+final class _ValueEntry extends _TableEntry {
+ final int value;
+ const _ValueEntry(this.value);
+
+ @override
+ int operator [](int index) => value;
+
+ @override
+ _TableEntry set(int index, int value) {
+ if (value == this.value) return this;
+ return _toListEntry()..set(index, value);
+ }
+
+ @override
+ _TableEntry _fillRange(
+ int start, int end, int value, _ValueEntry? fullEntry) {
+ if (value == this.value) return fullEntry ?? this;
+ return _toListEntry()._fillRange(start, end, value, fullEntry);
+ }
+
+ _ListEntry _toListEntry() => _ListEntry(value);
+}
+
+final class _ListEntry extends _TableEntry {
+ final Uint8List values = Uint8List(UnicodePropertyTable._entrySize);
+ _ListEntry([int value = 0]) {
+ if (value != 0) {
+ values.fillRange(0, UnicodePropertyTable._entrySize, value);
+ }
+ }
+
+ @override
+ int operator [](int index) => values[index];
+
+ @override
+ _TableEntry set(int index, int value) {
+ values[index] = value;
+ return this;
+ }
+
+ @override
+ _TableEntry _fillRange(
+ int start, int end, int value, _ValueEntry? fullEntry) {
+ values.fillRange(start, end, value);
+ return this;
+ }
+}
diff --git a/pkgs/characters/tool/src/indirect_table.dart b/pkgs/characters/tool/src/indirect_table.dart
new file mode 100644
index 0000000..7bae7e8
--- /dev/null
+++ b/pkgs/characters/tool/src/indirect_table.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:typed_data';
+
+/// A table with chunks and indirections.
+///
+/// Contains a number, one or more, of chunks,
+/// and a list of entries which point to entire chunks or parts of chunks.
+///
+/// The entries represent sequences of values.
+/// Each such sequence is stored in one of the chunks.
+///
+/// The main goal of these tools are to go from an initial complete
+/// table with one chunk and non-overlapping entries,
+/// to a smaller table with one chunk where the entry sequences may overlap.
+///
+/// Having multiple chunks is an intermediate step which allows the code
+/// to keep the entries consistent during the transformations.
+class IndirectTable {
+ /// Individual chunks.
+ List<Uint8List> chunks;
+
+ /// Position and length of each entry in one of the [chunks].
+ List<TableEntry> entries;
+ IndirectTable(this.chunks, this.entries);
+}
+
+class TableEntry {
+ int chunkNumber;
+ int start;
+ int length;
+ TableEntry(this.chunkNumber, this.start, this.length);
+ int get end => start + length;
+
+ void update(int chunkNumber, int start, int length) {
+ this.chunkNumber = chunkNumber;
+ this.start = start;
+ this.length = length;
+ }
+
+ TableEntry copy() => TableEntry(chunkNumber, start, length);
+
+ void copyFrom(TableEntry other) {
+ chunkNumber = other.chunkNumber;
+ start = other.start;
+ length = other.length;
+ }
+
+ @override
+ String toString() =>
+ '$chunkNumber[${start.toRadixString(16)}:${end.toRadixString(16)}]';
+}
diff --git a/pkgs/characters/tool/src/list_overlap.dart b/pkgs/characters/tool/src/list_overlap.dart
new file mode 100644
index 0000000..ddecf52
--- /dev/null
+++ b/pkgs/characters/tool/src/list_overlap.dart
@@ -0,0 +1,87 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Given a list of lists of integers, figure out a good way to overlap
+// these into a single list, with a list of indices telling where each
+// original list started.
+
+import 'dart:typed_data';
+
+import 'atsp.dart';
+import 'graph.dart';
+import 'indirect_table.dart';
+
+/// Takes a set of distinct chunks, and finds a semi-optimal overlapping.
+///
+/// The overlapping is a single chunk, of minimal length, containing all
+/// the original chunk's contents, and an indirection entry pointing
+/// to the position in the new table.
+IndirectTable combineLists(List<Uint8List> input) {
+ // See how much chunks are overlapping.
+ var chunkCount = input.length;
+ var graph = Graph(chunkCount + 1);
+ for (var i = 0; i < input.length; i++) {
+ var firstChunk = input[i];
+ for (var j = 0; j < input.length; j++) {
+ if (i == j) continue;
+ var secondChunk = input[j];
+ var overlap = _overlap(firstChunk, secondChunk);
+ graph.setWeight(i, j, secondChunk.length - overlap);
+ }
+ }
+
+ // Find an optimal(ish) path.
+
+ // First create a cycle through the one extra node (index `chunkCount`).
+ var path = List<int>.filled(chunkCount + 2, chunkCount);
+ for (var i = 0; i <= chunkCount; i++) {
+ path[i + 1] = i;
+ }
+
+ while (opt3(graph, path)) {}
+ // Then break the cycle at the extra node.
+ // The way we optimize, it's still first/last.
+ assert(path.last == chunkCount);
+ assert(path.first == chunkCount);
+
+ var chunkLength =
+ input[path[1]].length + graph.pathWeight(path, 1, path.length - 2);
+
+ var chunkData = Uint8List(chunkLength);
+ var entries = List<TableEntry>.filled(input.length, TableEntry(0, 0, 0));
+ {
+ // Handle path chunks.
+ var prevChunkNum = path[1];
+ var firstChunk = input[prevChunkNum];
+ chunkData.setRange(0, firstChunk.length, firstChunk);
+ entries[prevChunkNum] = TableEntry(0, 0, firstChunk.length);
+ var index = firstChunk.length;
+ for (var i = 2; i < path.length - 1; i++) {
+ var nextChunkNum = path[i];
+ var chunk = input[nextChunkNum];
+ var nonOverlap = graph.weight(prevChunkNum, nextChunkNum);
+ var overlap = chunk.length - nonOverlap;
+ entries[nextChunkNum] = TableEntry(0, index - overlap, chunk.length);
+ chunkData.setRange(index, index + nonOverlap, chunk, overlap);
+ index += nonOverlap;
+ prevChunkNum = nextChunkNum;
+ }
+ }
+ return IndirectTable([chunkData], entries);
+}
+
+/// Finds how much overlap there is between [first] and [second] in that order.
+int _overlap(Uint8List first, Uint8List second) {
+ var maxOverlap =
+ (first.length < second.length ? first.length : second.length) - 1;
+ outer:
+ for (var overlap = maxOverlap; overlap > 0; overlap--) {
+ var firstStart = first.length - overlap;
+ for (var j = 0; j < overlap; j++) {
+ if (first[firstStart + j] != second[j]) continue outer;
+ }
+ return overlap;
+ }
+ return 0;
+}
diff --git a/pkgs/characters/tool/src/shared.dart b/pkgs/characters/tool/src/shared.dart
new file mode 100644
index 0000000..69ccb97
--- /dev/null
+++ b/pkgs/characters/tool/src/shared.dart
@@ -0,0 +1,190 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'data_files.dart';
+
+// Shared tools used by other libraries.
+
+/// Quick and dirty caching URI loader.
+///
+/// Reads from [targetFile] if it exists and [forceLoad] is not `true`.
+/// Otherwise fetches from [location] URI and stores in [targetFile].
+///
+/// If [targetFile] is omitted, a file in the system temporary directory
+/// is used instead.
+Future<String> fetch(String location,
+ {File? targetFile, bool forceLoad = false}) async {
+ if (targetFile == null) {
+ var safeLocation = safePath(location);
+ targetFile = File(path(Directory.systemTemp.path, safeLocation));
+ }
+ if (!forceLoad && targetFile.existsSync()) {
+ return targetFile.readAsString();
+ }
+ var uri = Uri.parse(location);
+ String contents;
+ if (uri.isScheme('file')) {
+ contents = File.fromUri(uri).readAsStringSync();
+ } else {
+ var client = HttpClient();
+ var request = await client.getUrl(uri);
+ var response = await request.close();
+ if (response.statusCode != HttpStatus.ok) {
+ throw HttpException(response.reasonPhrase, uri: uri);
+ }
+ contents = await utf8.decoder.bind(response).join();
+ client.close();
+ }
+ writeToPath(targetFile, contents);
+ return contents;
+}
+
+/// Writes string to file.
+///
+/// Ensures directory of file exits.
+void writeToPath(File targetFile, String contents) {
+ var parentDir = Directory(parentPath(targetFile.path));
+ parentDir.createSync(recursive: true);
+ targetFile.writeAsStringSync(contents);
+}
+
+// Parent directory path of file or directory path.
+String parentPath(String path) {
+ var end = path.length;
+ if (path.endsWith('/')) end -= 1;
+ var lastSlash = path.lastIndexOf('/', end);
+ if (lastSlash >= 0) {
+ return path.substring(0, lastSlash + 1);
+ }
+ if (path == '/') return path;
+ return './'; // Empty relative path.
+}
+
+Future<bool> checkLicense(bool acceptLicenseChange) async {
+ if (await licenseFile.checkChange() case var changedLicensePath?) {
+ if (!acceptLicenseChange) {
+ stderr.writeln(
+ licenseChangeWarning(licenseFile.targetLocation, changedLicensePath));
+ return false;
+ }
+ stderr.writeln('LICENSE CHANGE ACCEPTED!');
+ licenseFile.copyFrom(changedLicensePath);
+ } else if (acceptLicenseChange) {
+ stderr.writeln('Accepting license change with no change.');
+ stderr.writeln('DO NOT AUTOMATE LICENSE ACCEPTANCE!');
+ return false;
+ }
+ return true;
+}
+
+/// Warning shown if the license has changed.
+String licenseChangeWarning(String originalPath, String newPath) => '''
+**NOTICE**
+The license file has changed. Check that it has not changed meaning.
+See changes using:
+ git diff ${_windowize(originalPath)} ${_windowize(newPath)}
+''';
+
+const copyright = '''
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+''';
+
+// Generated file header:
+void writeHeader(StringSink output, List<DataFile> dependencies) {
+ output
+ ..write(copyright)
+ ..writeln('// Generated code. Do not edit.')
+ ..writeln('// Generated from:');
+ for (var sourceFile in dependencies) {
+ output
+ ..write('// - [')
+ ..write(sourceFile.sourceLocation)
+ ..write('](../../')
+ ..write(sourceFile.targetLocation)
+ ..writeln(')');
+ }
+ output
+ ..writeln('// Licensed under the Unicode Inc. License Agreement')
+ ..writeln('// (${licenseFile.sourceLocation}, '
+ '../../third_party/${licenseFile.targetLocation})');
+}
+
+/// Temporary directory. Created once and for all.
+Directory get tmpDirectory => _tmpDirectory ??=
+ Directory.systemTemp.createTempSync('dart_pkg_characters');
+
+Directory? _tmpDirectory;
+
+/// Combines file paths into one path.
+///
+/// No fancy stuff, just adds path separator between parts,
+/// if previous part doesn't end with one.
+/// (Don't let later parts start with a path separator!)
+/// Converts forward slashes to backwards slashes in Windows.
+///
+/// Empty parts are ignored.
+String path(String path, [String path2 = '', String path3 = '']) {
+ var separator = Platform.pathSeparator;
+ path = _windowize(path);
+ if (path2.isEmpty && path3.isEmpty) return path;
+ var buffer = StringBuffer(path);
+ var prev = path;
+ for (var part in [path2, path3]) {
+ if (part.isEmpty) continue;
+ part = _windowize(part);
+ if (!prev.endsWith(separator)) {
+ buffer.write(separator);
+ }
+ buffer.write(part);
+ prev = part;
+ }
+ return buffer.toString();
+}
+
+/// Converts path to Windows path if on Windows (`/` to `\`).
+///
+/// Returns original path if not on Windows.
+String _windowize(String path) =>
+ Platform.isWindows ? path.replaceAll('/', r'') : path;
+
+/// Package root directory.
+String packageRoot = _findRootDir().path;
+
+/// A path relative to the [packageRoot].
+String packagePath(String path2, [String path3 = '']) =>
+ path(packageRoot, path2, path3);
+
+/// A path relative to a temporary directory.
+String tmpPath(String path2, [String path3 = '']) =>
+ path(tmpDirectory.path, path2, path3);
+
+/// Finds package root in the parent chain of the current directory.
+///
+/// Recognizes package root by `pubspec.yaml` file.
+Directory _findRootDir() {
+ var dir = Directory.current;
+ while (true) {
+ var pubspec = File('${dir.path}${Platform.pathSeparator}pubspec.yaml');
+ if (pubspec.existsSync()) return dir;
+ var parent = dir.parent;
+ if (dir.path == parent.path) {
+ throw UnsupportedError(
+ 'Cannot find package root directory. Run tools from inside package!');
+ }
+ }
+}
+
+/// Leading-zero padding.
+String lz(int n, [int length = 2]) => n.toString().padLeft(length, '0');
+
+final _unsafeCharsRE = RegExp(r'\W+');
+// Convert URI path to safe file path.
+String safePath(String uriPath) => uriPath.replaceAll(_unsafeCharsRE, '-');
diff --git a/pkgs/characters/tool/src/string_literal_writer.dart b/pkgs/characters/tool/src/string_literal_writer.dart
new file mode 100644
index 0000000..52a62d1
--- /dev/null
+++ b/pkgs/characters/tool/src/string_literal_writer.dart
@@ -0,0 +1,116 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Class to write string literals for bytes or words.
+///
+/// The string will be `'` delimited.
+/// Escapes as necessary, and performs line breaks to stay within 80
+/// characters.
+class StringLiteralWriter {
+ final StringSink buffer;
+ final String _padding;
+ final int _lineLength;
+ final bool Function(int) _escape;
+ int _currentLineLength = 0;
+
+ static final Map<int, String> _escapeCache = {};
+
+ StringLiteralWriter(this.buffer,
+ {int padding = 0, int lineLength = 80, bool Function(int)? escape})
+ : _padding = ' ' * padding,
+ _lineLength = lineLength,
+ _escape = escape ?? _defaultEscape;
+
+ static bool _defaultEscape(int codeUnit) {
+ return codeUnit < 0x20 || codeUnit >= 0x7f && codeUnit <= 0xa0;
+ }
+
+ void start([int initialOffset = 0]) {
+ if (initialOffset >= _lineLength - 2) {
+ buffer
+ ..write('\n')
+ ..write(_padding);
+ initialOffset = _padding.length;
+ }
+ buffer.write("'");
+ _currentLineLength = initialOffset + 1;
+ }
+
+ /// Adds a single UTF-16 code unit.
+ void add(int codeUnit) {
+ // Always escape: `\n`, `\r`, `'`, `$` and `\`, plus anything the user wants.
+ if (_escape(codeUnit) ||
+ codeUnit == 0x24 ||
+ codeUnit == 0x27 ||
+ codeUnit == 0x5c ||
+ codeUnit == 0x0a ||
+ codeUnit == 0x0d) {
+ _writeEscape(codeUnit);
+ return;
+ }
+ if (_currentLineLength >= _lineLength - 1) {
+ _wrap();
+ }
+ _currentLineLength++;
+ buffer.writeCharCode(codeUnit);
+ }
+
+ void _writeEscape(int codeUnit) {
+ var replacement = _escapeCache[codeUnit];
+ if (replacement == null) {
+ if (codeUnit < 0x10) {
+ if (codeUnit == '\b'.codeUnitAt(0)) {
+ replacement = r'\b';
+ } else if (codeUnit == '\t'.codeUnitAt(0)) {
+ replacement = r'\t';
+ } else if (codeUnit == '\n'.codeUnitAt(0)) {
+ replacement = r'\n';
+ } else if (codeUnit == '\v'.codeUnitAt(0)) {
+ replacement = r'\v';
+ } else if (codeUnit == '\f'.codeUnitAt(0)) {
+ replacement = r'\f';
+ } else if (codeUnit == '\r'.codeUnitAt(0)) {
+ replacement = r'\r';
+ } else {
+ replacement = r'\x0' + codeUnit.toRadixString(16);
+ }
+ } else if (codeUnit < 0x100) {
+ if (codeUnit == r'$'.codeUnitAt(0)) {
+ replacement = r'\$';
+ } else if (codeUnit == "'".codeUnitAt(0)) {
+ replacement = r"\'";
+ }
+ if (codeUnit == r''.codeUnitAt(0)) {
+ replacement = r'\';
+ } else {
+ replacement = r'\x' + codeUnit.toRadixString(16);
+ }
+ } else if (codeUnit < 0x1000) {
+ replacement = r'\u0' + codeUnit.toRadixString(16);
+ } else if (codeUnit < 0x10000) {
+ replacement = r'\u' + codeUnit.toRadixString(16);
+ } else {
+ replacement = '\\u{${codeUnit.toRadixString(16)}}';
+ }
+ _escapeCache[codeUnit] = replacement;
+ }
+ if (_currentLineLength + replacement.length + 1 > _lineLength) {
+ _wrap();
+ }
+ buffer.write(replacement);
+ _currentLineLength += replacement.length;
+ }
+
+ void _wrap() {
+ buffer
+ ..write("'\n")
+ ..write(_padding)
+ ..write("'");
+ _currentLineLength = _padding.length + 1;
+ }
+
+ void end() {
+ buffer.write("'");
+ }
+}
diff --git a/pkgs/characters/tool/src/table_builder.dart b/pkgs/characters/tool/src/table_builder.dart
new file mode 100644
index 0000000..d407f4a
--- /dev/null
+++ b/pkgs/characters/tool/src/table_builder.dart
@@ -0,0 +1,100 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+import 'dart:typed_data';
+
+import 'indirect_table.dart';
+import 'list_overlap.dart';
+
+/// Splits an indirect table with one large chunk into separate smaller chunks.
+///
+/// No new chunk is larger than the largest entry.
+///
+/// Preserves the entries, but they now point into the new chunks.
+/// All chunks are distinct, and no chunk is a sub-list of another chunk.
+void chunkifyTable(IndirectTable table) {
+ if (table.chunks.length != 1) {
+ throw ArgumentError('Single chunk table required');
+ }
+ var data = table.chunks[0];
+ var entries = table.entries.toList();
+ entries.sort((a, b) => b.length - a.length);
+ var uniqueChunks = <Uint8List>[];
+ var duplicateDetector =
+ HashMap<Uint8List, TableEntry>(equals: _equals, hashCode: _hash);
+ for (var entry in entries) {
+ var chunk = data.sublist(entry.start, entry.end);
+ var existingEntry = duplicateDetector[chunk];
+ if (existingEntry != null) {
+ entry.copyFrom(existingEntry);
+ } else {
+ // Check if chunk is a sublist of any existing chunk.
+ var chunkNum = 0;
+ var indexOf = 0;
+ for (; chunkNum < uniqueChunks.length; chunkNum++) {
+ var existingChunk = uniqueChunks[chunkNum];
+ if (existingChunk.length > chunk.length) {
+ var position = _indexOf(chunk, existingChunk);
+ if (position >= 0) {
+ indexOf = position;
+ break;
+ }
+ }
+ }
+ if (chunkNum == uniqueChunks.length) {
+ uniqueChunks.add(chunk);
+ }
+ entry.update(chunkNum, indexOf, entry.length);
+ duplicateDetector[chunk] = entry;
+ }
+ }
+ table.chunks = uniqueChunks;
+}
+
+int _indexOf(Uint8List short, Uint8List long) {
+ var length = short.length;
+ var range = long.length - length;
+ outer:
+ for (var i = 0; i < range; i++) {
+ for (var j = 0; j < short.length; j++) {
+ if (short[j] != long[i + j]) continue outer;
+ }
+ return i;
+ }
+ return -1;
+}
+
+/// Combines an indirect table with multiple chunks into only one chunk.
+void combineChunkedTable(IndirectTable table) {
+ var overlapped = combineLists(table.chunks);
+ for (var entry in table.entries) {
+ var chunkEntry = overlapped.entries[entry.chunkNumber];
+ entry.update(0, entry.start + chunkEntry.start, entry.length);
+ }
+ table.chunks = [overlapped.chunks[0]];
+}
+
+/// Hash on a list.
+int _hash(Uint8List list) {
+ var view = list.buffer.asUint32List();
+ var hash = 0;
+ for (var i = 0; i < view.length; i++) {
+ hash = (hash * 37 ^ view[i]) & 0xFFFFFFFF;
+ }
+ return hash;
+}
+
+/// Equality of lists of equal length.
+bool _equals(Uint8List a, Uint8List b) {
+ assert(a.length == b.length);
+ assert(a.length % 8 == 0);
+ // Compare 32 bits at a time.
+ var aView = a.buffer.asInt64List();
+ var bView = b.buffer.asInt64List();
+ for (var i = 0; i < aView.length; i++) {
+ if (aView[i] != bView[i]) return false;
+ }
+ return true;
+}
diff --git a/pkgs/collection/.gitignore b/pkgs/collection/.gitignore
new file mode 100644
index 0000000..3320291
--- /dev/null
+++ b/pkgs/collection/.gitignore
@@ -0,0 +1,11 @@
+.buildlog
+.DS_Store
+.idea
+.pub/
+.dart_tool/
+.settings/
+build/
+packages
+.packages
+pubspec.lock
+benchmark/*.exe
diff --git a/pkgs/collection/AUTHORS b/pkgs/collection/AUTHORS
new file mode 100644
index 0000000..80712fd
--- /dev/null
+++ b/pkgs/collection/AUTHORS
@@ -0,0 +1,8 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
+AAABramenko (https://github.com/AAAbramenko)
+TimWhiting tim@whitings.org
diff --git a/pkgs/collection/CHANGELOG.md b/pkgs/collection/CHANGELOG.md
new file mode 100644
index 0000000..2891b0d
--- /dev/null
+++ b/pkgs/collection/CHANGELOG.md
@@ -0,0 +1,333 @@
+## 1.20.0-wip
+
+- Add `IterableMapEntryExtension` for working on `Map` as a list of pairs, using
+ `Map.entries`.
+- Optimize equality and hash code for maps by using `update` and a `values`
+ iterator to avoid extra lookups.
+
+## 1.19.1
+
+- Move to `dart-lang/core` monorepo.
+
+## 1.19.0
+
+- Adds `shuffled` to `IterableExtension`.
+- Shuffle `IterableExtension.sample` results.
+- Fix `mergeSort` when the runtime iterable generic is a subtype of the static
+ generic.
+- `CanonicalizedMap`: added constructor `fromEntries`.
+- Mark "mixin" classes as `mixin`.
+- `extension IterableIterableExtension<T> on Iterable<Iterable<T>>`
+ - Add `flattenedToList` as a performance improvement over `flattened.`
+ - Add `flattenedToSet` as new behavior for flattening to unique elements.
+- Deprecate `transitiveClosure`. Consider using `package:graphs`.
+- Deprecate `whereNotNull()` from `IterableNullableExtension`. Use `nonNulls`
+ instead - this is an equivalent extension available in Dart core since
+ version 3.0.
+- Require Dart `^3.4.0`
+
+## 1.18.0
+
+- `CanonicalizedMap`:
+ - Added methods:
+ - `copy`: copies an instance without recalculating the canonical values of the keys.
+ - `toMap`: creates a `Map<K,V>` (with the original key values).
+ - `toMapOfCanonicalKeys`: creates a `Map<C,V>` (with the canonicalized keys).
+- Fixes bugs in `ListSlice.slice` and `ListExtensions.slice`.
+- Update to `package:lints` 2.0.1.
+
+## 1.17.2
+
+* Accept Dart SDK versions above 3.0.
+
+## 1.17.1
+
+* Require Dart 2.18.
+* Improve docs for `splitAfter` and `splitBefore`.
+
+## 1.17.0
+
+* Add `Iterable.elementAtOrNull` and `List.elementAtOrNull` extension methods.
+* Add a top-level `lastBy()` function that converts an `Iterable` to a `Map` by
+ grouping its elements using a function, keeping the last element for each
+ computed key. Also available as an extension method on `Iterable`.
+
+## 1.16.0
+
+* Add an `Iterable.slices` extension method.
+* Add `BoolList` class for space-efficient lists of boolean values.
+* Use a stable sort algorithm in the `IterableExtension.sortedBy` method.
+* Add `min`, `max`, `minOrNull` and `maxOrNull` getters to
+ `IterableDoubleExtension`, `IterableNumberExtension` and
+ `IterableIntegerExtension`
+* Change `UnorderedIterableEquality` and `SetEquality` to implement `Equality`
+ with a non-nullable generic to allows assignment to variables with that type.
+ Assignment to `Equality` with a nullable type is still allowed because of
+ covariance. The `equals` and `hash` methods continue to accept nullable
+ arguments.
+* Enable the `avoid_dynamic_calls` lint.
+
+## 1.15.0
+
+* Stable release for null safety.
+
+## 1.15.0-nullsafety.5
+
+* Fix typo in extension method `expandIndexed`.
+* Update sdk constraints to `>=2.12.0-0 <3.0.0` based on beta release
+ guidelines.
+
+## 1.15.0-nullsafety.4
+
+* Allow prerelease versions of the `2.12.x` sdk.
+
+* Remove the unusable setter `UnionSetController.set=`. This was mistakenly
+ added to the public API but could never be called.
+
+* Add extra optional `Random` argument to `shuffle`.
+
+* Add a large number of extension methods on `Iterable` and `List` types,
+ and on a few other types.
+ These either provide easy access to the operations from `algorithms.dart`,
+ or provide convenience variants of existing `Iterable` and `List` methods
+ like `singleWhereOrNull` or `forEachIndexed`.
+
+## 1.15.0-nullsafety.3
+
+* Allow 2.10 stable and 2.11.0 dev SDK versions.
+* Add `toUnorderedList` method on `PriorityQueue`.
+* Make `HeapPriorityQueue`'s `remove` and `contains` methods
+ use `==` for equality checks.
+ Previously used `comparison(a, b) == 0` as criteria, but it's possible
+ to have multiple elements with the same priority in a queue, so that
+ could remove the wrong element.
+ Still requires that objects that are `==` also have the same priority.
+
+## 1.15.0-nullsafety.2
+
+Update for the 2.10 dev sdk.
+
+## 1.15.0-nullsafety.1
+
+* Allow the <=2.9.10 stable sdks.
+
+## 1.15.0-nullsafety
+
+Pre-release for the null safety migration of this package.
+
+Note that `1.15.0` may not be the final stable null safety release version,
+we reserve the right to release it as a `2.0.0` breaking change.
+
+This release will be pinned to only allow pre-release sdk versions starting
+from `2.9.0-dev.18.0`, which is the first version where this package will
+appear in the null safety allow list.
+
+## 1.14.13
+
+* Deprecate `mapMap`. The Map interface has a `map` call and map literals can
+ use for-loop elements which supersede this method.
+
+## 1.14.12
+
+* Fix `CombinedMapView.keys`, `CombinedMapView.length`,
+ `CombinedMapView.forEach`, and `CombinedMapView.values` to work as specified
+ and not repeat duplicate items from the maps.
+ * As a result of this fix the `length` getter now must iterate all maps in
+ order to remove duplicates and return an accurate length, so it is no
+ longer `O(maps)`.
+
+## 1.14.11
+
+* Set max SDK version to `<3.0.0`.
+
+## 1.14.10
+
+* Fix the parameter names in overridden methods to match the source.
+* Make tests Dart 2 type-safe.
+* Stop depending on SDK `retype` and deprecate methods.
+
+## 1.14.9
+
+* Fixed bugs where `QueueList`, `MapKeySet`, and `MapValueSet` did not adhere to
+ the contract laid out by `List.cast`, `Set.cast` and `Map.cast` respectively.
+ The returned instances of these methods now correctly forward to the existing
+ instance instead of always creating a new copy.
+
+## 1.14.8
+
+* Deprecated `Delegating{Name}.typed` static methods in favor of the new Dart 2
+ `cast` methods. For example, `DelegatingList.typed<String>(list)` can now be
+ written as `list.cast<String>()`.
+
+## 1.14.7
+
+* Only the Dart 2 dev SDK (`>=2.0.0-dev.22.0`) is now supported.
+* Added support for all Dart 2 SDK methods that threw `UnimplementedError`.
+
+## 1.14.6
+
+* Make `DefaultEquality`'s `equals()` and `hash()` methods take any `Object`
+ rather than objects of type `E`. This makes `const DefaultEquality<Null>()`
+ usable as `Equality<E>` for any `E`, which means it can be used in a const
+ context which expects `Equality<E>`.
+
+ This makes the default arguments of various other const equality constructors
+ work in strong mode.
+
+## 1.14.5
+
+* Fix issue with `EmptyUnmodifiableSet`'s stubs that were introduced in 1.14.4.
+
+## 1.14.4
+
+* Add implementation stubs of upcoming Dart 2.0 core library methods, namely
+ new methods for classes that implement `Iterable`, `List`, `Map`, `Queue`,
+ and `Set`.
+
+## 1.14.3
+
+* Fix `MapKeySet.lookup` to be a valid override in strong mode.
+
+## 1.14.2
+
+* Add type arguments to `SyntheticInvocation`.
+
+## 1.14.1
+
+* Make `Equality` implementations accept `null` as argument to `hash`.
+
+## 1.14.0
+
+* Add `CombinedListView`, a view of several lists concatenated together.
+* Add `CombinedIterableView`, a view of several iterables concatenated together.
+* Add `CombinedMapView`, a view of several maps concatenated together.
+
+## 1.13.0
+
+* Add `EqualityBy`
+
+## 1.12.0
+
+* Add `CaseInsensitiveEquality`.
+
+* Fix bug in `equalsIgnoreAsciiCase`.
+
+## 1.11.0
+
+* Add `EqualityMap` and `EqualitySet` classes which use `Equality` objects for
+ key and element equality, respectively.
+
+## 1.10.1
+
+* `Set.difference` now takes a `Set<Object>` as argument.
+
+## 1.9.1
+
+* Fix some documentation bugs.
+
+## 1.9.0
+
+* Add a top-level `stronglyConnectedComponents()` function that returns the
+ strongly connected components in a directed graph.
+
+## 1.8.0
+
+* Add a top-level `mapMap()` function that works like `Iterable.map()` on a
+ `Map`.
+
+* Add a top-level `mergeMaps()` function that creates a new map with the
+ combined contents of two existing maps.
+
+* Add a top-level `groupBy()` function that converts an `Iterable` to a `Map` by
+ grouping its elements using a function.
+
+* Add top-level `minBy()` and `maxBy()` functions that return the minimum and
+ maximum values in an `Iterable`, respectively, ordered by a derived value.
+
+* Add a top-level `transitiveClosure()` function that returns the transitive
+ closure of a directed graph.
+
+## 1.7.0
+
+* Add a `const UnmodifiableSetView.empty()` constructor.
+
+## 1.6.0
+
+* Add a `UnionSet` class that provides a view of the union of a set of sets.
+
+* Add a `UnionSetController` class that provides a convenient way to manage the
+ contents of a `UnionSet`.
+
+* Fix another incorrectly-declared generic type.
+
+## 1.5.1
+
+* Fix an incorrectly-declared generic type.
+
+## 1.5.0
+
+* Add `DelegatingIterable.typed()`, `DelegatingList.typed()`,
+ `DelegatingSet.typed()`, `DelegatingMap.typed()`, and
+ `DelegatingQueue.typed()` static methods. These wrap untyped instances of
+ these classes with the correct type parameter, and assert the types of values
+ as they're accessed.
+
+* Fix the types for `binarySearch()` and `lowerBound()` so they no longer
+ require all arguments to be comparable.
+
+* Add generic annotations to `insertionSort()` and `mergeSort()`.
+
+## 1.4.1
+
+* Fix all strong mode warnings.
+
+## 1.4.0
+
+* Add a `new PriorityQueue()` constructor that forwards to `new
+ HeapPriorityQueue()`.
+
+* Deprecate top-level libraries other than `package:collection/collection.dart`,
+ which exports these libraries' interfaces.
+
+## 1.3.0
+
+* Add `lowerBound` to binary search for values that might not be present.
+
+* Verify that the is valid for `CanonicalMap.[]`.
+
+## 1.2.0
+
+* Add string comparators that ignore ASCII case and sort numbers numerically.
+
+## 1.1.3
+
+* Fix type inconsistencies with `Map` and `Set`.
+
+## 1.1.2
+
+* Export `UnmodifiableMapView` from the Dart core libraries.
+
+## 1.1.1
+
+* Bug-fix for signatures of `isValidKey` arguments of `CanonicalizedMap`.
+
+## 1.1.0
+
+* Add a `QueueList` class that implements both `Queue` and `List`.
+
+## 0.9.4
+
+* Add a `CanonicalizedMap` class that canonicalizes its keys to provide a custom
+ equality relation.
+
+## 0.9.3+1
+
+* Fix all analyzer hints.
+
+## 0.9.3
+
+* Add a `MapKeySet` class that exposes an unmodifiable `Set` view of a `Map`'s
+ keys.
+
+* Add a `MapValueSet` class that takes a function from values to keys and uses
+ it to expose a `Set` view of a `Map`'s values.
diff --git a/pkgs/collection/LICENSE b/pkgs/collection/LICENSE
new file mode 100644
index 0000000..dbd2843
--- /dev/null
+++ b/pkgs/collection/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2015, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/collection/README.md b/pkgs/collection/README.md
new file mode 100644
index 0000000..63f705c
--- /dev/null
+++ b/pkgs/collection/README.md
@@ -0,0 +1,59 @@
+[](https://github.com/dart-lang/core/actions/workflows/collection.yaml)
+[](https://pub.dev/packages/collection)
+[](https://pub.dev/packages/collection/publisher)
+
+Contains utility functions and classes in the style of `dart:collection` to make
+working with collections easier.
+
+## Algorithms
+
+The package contains functions that operate on lists.
+
+It contains ways to shuffle a `List`, do binary search on a sorted `List`, and
+various sorting algorithms.
+
+## Equality
+
+The package provides a way to specify the equality of elements and collections.
+
+Collections in Dart have no inherent equality. Two sets are not equal, even
+if they contain exactly the same objects as elements.
+
+The `Equality` interface provides a way to define such an equality. In this
+case, for example, `const SetEquality(IdentityEquality())` is an equality
+that considers two sets equal exactly if they contain identical elements.
+
+Equalities are provided for `Iterable`s, `List`s, `Set`s, and `Map`s, as well as
+combinations of these, such as:
+
+```dart
+const MapEquality(IdentityEquality(), ListEquality());
+```
+
+This equality considers maps equal if they have identical keys, and the
+corresponding values are lists with equal (`operator==`) values.
+
+## Iterable Zip
+
+Utilities for "zipping" a list of iterables into an iterable of lists.
+
+## Priority Queue
+
+An interface and implementation of a priority queue.
+
+## Wrappers
+
+The package contains classes that "wrap" a collection.
+
+A wrapper class contains an object of the same type, and it forwards all
+methods to the wrapped object.
+
+Wrapper classes can be used in various ways, for example to restrict the type
+of an object to that of a supertype, or to change the behavior of selected
+functions on an existing object.
+
+## Features and bugs
+
+Please file feature requests and bugs at the [issue tracker][tracker].
+
+[tracker]: https://github.com/dart-lang/collection/issues
diff --git a/pkgs/collection/analysis_options.yaml b/pkgs/collection/analysis_options.yaml
new file mode 100644
index 0000000..74c328a
--- /dev/null
+++ b/pkgs/collection/analysis_options.yaml
@@ -0,0 +1,15 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+
+linter:
+ rules:
+ - avoid_unused_constructor_parameters
+ - cancel_subscriptions
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - unnecessary_await_in_return
diff --git a/pkgs/collection/benchmark/deep_collection_equality.dart b/pkgs/collection/benchmark/deep_collection_equality.dart
new file mode 100644
index 0000000..182cf86
--- /dev/null
+++ b/pkgs/collection/benchmark/deep_collection_equality.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:benchmark_harness/benchmark_harness.dart';
+import 'package:collection/collection.dart';
+
+void main() {
+ for (var unordered in [true, false]) {
+ DeepCollectionEqualityEqualsBenchmark(unordered).report();
+ DeepCollectionEqualityHashBenchmark(unordered).report();
+ }
+}
+
+class DeepCollectionEqualityBase extends BenchmarkBase {
+ final DeepCollectionEquality equality;
+
+ DeepCollectionEqualityBase(bool unordered, String function)
+ : equality = unordered
+ ? const DeepCollectionEquality.unordered()
+ : const DeepCollectionEquality(),
+ super('DeepCollectionQuality${unordered ? 'Unordered' : ''}.$function');
+}
+
+class DeepCollectionEqualityHashBenchmark extends DeepCollectionEqualityBase {
+ DeepCollectionEqualityHashBenchmark(bool unordered)
+ : super(unordered, 'hash');
+
+ @override
+ void run() {
+ hash = equality.hash(mapA);
+ }
+
+ static int hash = 0;
+}
+
+class DeepCollectionEqualityEqualsBenchmark extends DeepCollectionEqualityBase {
+ DeepCollectionEqualityEqualsBenchmark(bool unordered)
+ : super(unordered, 'equals');
+
+ @override
+ void run() {
+ equals = equality.equals(mapA, mapB);
+ }
+
+ static bool equals = false;
+}
+
+final mapA = {
+ for (var i = 0; i < 100; i++)
+ {
+ [
+ for (var j = i; j < i + 10; j++) j,
+ ]: i.isEven ? i : '$i',
+ }
+};
+
+final mapB = {
+ for (var i = 0; i < 100; i++)
+ {
+ [
+ for (var j = i; j < i + 10; j++) j,
+ ]: i.isEven ? i : '$i',
+ }
+};
diff --git a/pkgs/collection/lib/algorithms.dart b/pkgs/collection/lib/algorithms.dart
new file mode 100644
index 0000000..ac43242
--- /dev/null
+++ b/pkgs/collection/lib/algorithms.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Import `collection.dart` instead.
+@Deprecated('Will be removed in collection 2.0.0.')
+library;
+
+export 'src/algorithms.dart'
+ show binarySearch, insertionSort, lowerBound, mergeSort, reverse, shuffle;
diff --git a/pkgs/collection/lib/collection.dart b/pkgs/collection/lib/collection.dart
new file mode 100644
index 0000000..73ec179
--- /dev/null
+++ b/pkgs/collection/lib/collection.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'src/algorithms.dart'
+ show binarySearch, insertionSort, lowerBound, mergeSort, reverse, shuffle;
+export 'src/boollist.dart';
+export 'src/canonicalized_map.dart';
+export 'src/combined_wrappers/combined_iterable.dart';
+export 'src/combined_wrappers/combined_list.dart';
+export 'src/combined_wrappers/combined_map.dart';
+export 'src/comparators.dart';
+export 'src/equality.dart';
+export 'src/equality_map.dart';
+export 'src/equality_set.dart';
+export 'src/functions.dart';
+export 'src/iterable_extensions.dart';
+export 'src/iterable_zip.dart';
+export 'src/list_extensions.dart';
+export 'src/priority_queue.dart';
+export 'src/queue_list.dart';
+export 'src/union_set.dart';
+export 'src/union_set_controller.dart';
+export 'src/unmodifiable_wrappers.dart';
+export 'src/wrappers.dart';
diff --git a/pkgs/collection/lib/equality.dart b/pkgs/collection/lib/equality.dart
new file mode 100644
index 0000000..5dc158c
--- /dev/null
+++ b/pkgs/collection/lib/equality.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Import `collection.dart` instead.
+@Deprecated('Will be removed in collection 2.0.0.')
+library;
+
+export 'src/equality.dart';
diff --git a/pkgs/collection/lib/iterable_zip.dart b/pkgs/collection/lib/iterable_zip.dart
new file mode 100644
index 0000000..bd0b1ef
--- /dev/null
+++ b/pkgs/collection/lib/iterable_zip.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Import `collection.dart` instead.
+@Deprecated('Will be removed in collection 2.0.0.')
+library;
+
+export 'src/iterable_zip.dart';
diff --git a/pkgs/collection/lib/priority_queue.dart b/pkgs/collection/lib/priority_queue.dart
new file mode 100644
index 0000000..7505ce4
--- /dev/null
+++ b/pkgs/collection/lib/priority_queue.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Import `collection.dart` instead.
+@Deprecated('Will be removed in collection 2.0.0.')
+library;
+
+export 'src/priority_queue.dart';
diff --git a/pkgs/collection/lib/src/algorithms.dart b/pkgs/collection/lib/src/algorithms.dart
new file mode 100644
index 0000000..bb5843c
--- /dev/null
+++ b/pkgs/collection/lib/src/algorithms.dart
@@ -0,0 +1,467 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A selection of data manipulation algorithms.
+library;
+
+import 'dart:math' show Random;
+
+import 'utils.dart';
+
+/// Returns a position of the [value] in [sortedList], if it is there.
+///
+/// If the list isn't sorted according to the [compare] function, the result
+/// is unpredictable.
+///
+/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
+/// the objects. In this case, the objects must be [Comparable].
+///
+/// Returns -1 if [value] is not in the list.
+int binarySearch<E>(List<E> sortedList, E value,
+ {int Function(E, E)? compare}) {
+ compare ??= defaultCompare;
+ return binarySearchBy<E, E>(sortedList, identity, compare, value);
+}
+
+/// Returns a position of the [value] in [sortedList], if it is there.
+///
+/// If the list isn't sorted according to the [compare] function on the [keyOf]
+/// property of the elements, the result is unpredictable.
+///
+/// Returns -1 if [value] is not in the list by default.
+///
+/// If [start] and [end] are supplied, only that range is searched,
+/// and only that range need to be sorted.
+int binarySearchBy<E, K>(List<E> sortedList, K Function(E element) keyOf,
+ int Function(K, K) compare, E value,
+ [int start = 0, int? end]) {
+ end = RangeError.checkValidRange(start, end, sortedList.length);
+ var min = start;
+ var max = end;
+ var key = keyOf(value);
+ while (min < max) {
+ var mid = min + ((max - min) >> 1);
+ var element = sortedList[mid];
+ var comp = compare(keyOf(element), key);
+ if (comp == 0) return mid;
+ if (comp < 0) {
+ min = mid + 1;
+ } else {
+ max = mid;
+ }
+ }
+ return -1;
+}
+
+/// Returns the first position in [sortedList] that does not compare less than
+/// [value].
+///
+/// Uses binary search to find the location of [value].
+/// This takes on the order of `log(n)` comparisons.
+/// If the list isn't sorted according to the [compare] function, the result
+/// is unpredictable.
+///
+/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
+/// the objects. In this case, the objects must be [Comparable].
+///
+/// Returns the length of [sortedList] if all the items in [sortedList] compare
+/// less than [value].
+int lowerBound<E>(List<E> sortedList, E value, {int Function(E, E)? compare}) {
+ compare ??= defaultCompare;
+ return lowerBoundBy<E, E>(sortedList, identity, compare, value);
+}
+
+/// Returns the first position in [sortedList] that is not before [value].
+///
+/// Uses binary search to find the location of [value].
+/// This takes on the order of `log(n)` comparisons.
+/// Elements are compared using the [compare] function of the [keyOf] property
+/// of the elements.
+/// If the list isn't sorted according to this order, the result is
+/// unpredictable.
+///
+/// Returns the length of [sortedList] if all the items in [sortedList] are
+/// before [value].
+///
+/// If [start] and [end] are supplied, only that range is searched,
+/// and only that range need to be sorted.
+int lowerBoundBy<E, K>(List<E> sortedList, K Function(E element) keyOf,
+ int Function(K, K) compare, E value,
+ [int start = 0, int? end]) {
+ end = RangeError.checkValidRange(start, end, sortedList.length);
+ var min = start;
+ var max = end;
+ var key = keyOf(value);
+ while (min < max) {
+ var mid = min + ((max - min) >> 1);
+ var element = sortedList[mid];
+ var comp = compare(keyOf(element), key);
+ if (comp < 0) {
+ min = mid + 1;
+ } else {
+ max = mid;
+ }
+ }
+ return min;
+}
+
+/// Shuffles a list randomly.
+///
+/// A sub-range of a list can be shuffled by providing [start] and [end].
+///
+/// If [start] or [end] are omitted,
+/// they default to the start and end of the list.
+///
+/// If [random] is omitted, it defaults to a new instance of [Random].
+void shuffle(List elements, [int start = 0, int? end, Random? random]) {
+ random ??= Random();
+ end ??= elements.length;
+ var length = end - start;
+ while (length > 1) {
+ var pos = random.nextInt(length);
+ length--;
+ var tmp1 = elements[start + pos];
+ elements[start + pos] = elements[start + length];
+ elements[start + length] = tmp1;
+ }
+}
+
+/// Reverses a list, or a part of a list, in-place.
+void reverse<E>(List<E> elements, [int start = 0, int? end]) {
+ end = RangeError.checkValidRange(start, end, elements.length);
+ _reverse<E>(elements, start, end);
+}
+
+/// Internal helper function that assumes valid arguments.
+void _reverse<E>(List<E> elements, int start, int end) {
+ for (var i = start, j = end - 1; i < j; i++, j--) {
+ var tmp = elements[i];
+ elements[i] = elements[j];
+ elements[j] = tmp;
+ }
+}
+
+/// Sort a list between [start] (inclusive) and [end] (exclusive) using
+/// insertion sort.
+///
+/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
+/// the objects. In this case, the objects must be [Comparable].
+///
+/// Insertion sort is a simple sorting algorithm. For `n` elements it does on
+/// the order of `n * log(n)` comparisons but up to `n` squared moves. The
+/// sorting is performed in-place, without using extra memory.
+///
+/// For short lists the many moves have less impact than the simple algorithm,
+/// and it is often the favored sorting algorithm for short lists.
+///
+/// This insertion sort is stable: Equal elements end up in the same order
+/// as they started in.
+void insertionSort<E>(List<E> elements,
+ {int Function(E, E)? compare, int start = 0, int? end}) {
+ // If the same method could have both positional and named optional
+ // parameters, this should be (list, [start, end], {compare}).
+ compare ??= defaultCompare;
+ end ??= elements.length;
+
+ for (var pos = start + 1; pos < end; pos++) {
+ var min = start;
+ var max = pos;
+ var element = elements[pos];
+ while (min < max) {
+ var mid = min + ((max - min) >> 1);
+ var comparison = compare(element, elements[mid]);
+ if (comparison < 0) {
+ max = mid;
+ } else {
+ min = mid + 1;
+ }
+ }
+ elements.setRange(min + 1, pos + 1, elements, min);
+ elements[min] = element;
+ }
+}
+
+/// Generalized insertion sort.
+///
+/// Performs insertion sort on the [elements] range from [start] to [end].
+/// Ordering is the [compare] of the [keyOf] of the elements.
+void insertionSortBy<E, K>(List<E> elements, K Function(E element) keyOf,
+ int Function(K a, K b) compare,
+ [int start = 0, int? end]) {
+ end = RangeError.checkValidRange(start, end, elements.length);
+ _movingInsertionSort(elements, keyOf, compare, start, end, elements, start);
+}
+
+/// Limit below which merge sort defaults to insertion sort.
+const int _mergeSortLimit = 32;
+
+/// Sorts a list between [start] (inclusive) and [end] (exclusive) using the
+/// merge sort algorithm.
+///
+/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
+/// the objects. If any object is not [Comparable], that throws a [TypeError].
+///
+/// Merge-sorting works by splitting the job into two parts, sorting each
+/// recursively, and then merging the two sorted parts.
+///
+/// This takes on the order of `n * log(n)` comparisons and moves to sort
+/// `n` elements, but requires extra space of about the same size as the list
+/// being sorted.
+///
+/// This merge sort is stable: Equal elements end up in the same order
+/// as they started in.
+void mergeSort<E>(List<E> elements,
+ {int start = 0, int? end, int Function(E, E)? compare}) {
+ end = RangeError.checkValidRange(start, end, elements.length);
+ compare ??= defaultCompare;
+
+ var length = end - start;
+ if (length < 2) return;
+ if (length < _mergeSortLimit) {
+ insertionSort(elements, compare: compare, start: start, end: end);
+ return;
+ }
+ // Special case the first split instead of directly calling
+ // _mergeSort, because the _mergeSort requires its target to
+ // be different from its source, and it requires extra space
+ // of the same size as the list to sort.
+ // This split allows us to have only half as much extra space,
+ // and allows the sorted elements to end up in the original list.
+ var firstLength = (end - start) >> 1;
+ var middle = start + firstLength;
+ var secondLength = end - middle;
+ // secondLength is always the same as firstLength, or one greater.
+ var scratchSpace = elements.sublist(0, secondLength);
+ _mergeSort(elements, identity<E>, compare, middle, end, scratchSpace, 0);
+ var firstTarget = end - firstLength;
+ _mergeSort(
+ elements, identity<E>, compare, start, middle, elements, firstTarget);
+ _merge(identity<E>, compare, elements, firstTarget, end, scratchSpace, 0,
+ secondLength, elements, start);
+}
+
+/// Sort [elements] using a merge-sort algorithm.
+///
+/// The elements are compared using [compare] on the value provided by [keyOf]
+/// on the element.
+/// If [start] and [end] are provided, only that range is sorted.
+///
+/// Uses insertion sort for smaller sublists.
+void mergeSortBy<E, K>(List<E> elements, K Function(E element) keyOf,
+ int Function(K a, K b) compare,
+ [int start = 0, int? end]) {
+ end = RangeError.checkValidRange(start, end, elements.length);
+ var length = end - start;
+ if (length < 2) return;
+ if (length < _mergeSortLimit) {
+ _movingInsertionSort(elements, keyOf, compare, start, end, elements, start);
+ return;
+ }
+ // Special case the first split instead of directly calling
+ // _mergeSort, because the _mergeSort requires its target to
+ // be different from its source, and it requires extra space
+ // of the same size as the list to sort.
+ // This split allows us to have only half as much extra space,
+ // and it ends up in the original place.
+ var middle = start + (length >> 1);
+ var firstLength = middle - start;
+ var secondLength = end - middle;
+ // secondLength is always the same as firstLength, or one greater.
+ var scratchSpace = elements.sublist(0, secondLength);
+ _mergeSort(elements, keyOf, compare, middle, end, scratchSpace, 0);
+ var firstTarget = end - firstLength;
+ _mergeSort(elements, keyOf, compare, start, middle, elements, firstTarget);
+ _merge(keyOf, compare, elements, firstTarget, end, scratchSpace, 0,
+ secondLength, elements, start);
+}
+
+/// Performs an insertion sort into a potentially different list than the
+/// one containing the original values.
+///
+/// It will work in-place as well.
+void _movingInsertionSort<E, K>(
+ List<E> list,
+ K Function(E element) keyOf,
+ int Function(K, K) compare,
+ int start,
+ int end,
+ List<E> target,
+ int targetOffset) {
+ var length = end - start;
+ if (length == 0) return;
+ target[targetOffset] = list[start];
+ for (var i = 1; i < length; i++) {
+ var element = list[start + i];
+ var elementKey = keyOf(element);
+ var min = targetOffset;
+ var max = targetOffset + i;
+ while (min < max) {
+ var mid = min + ((max - min) >> 1);
+ if (compare(elementKey, keyOf(target[mid])) < 0) {
+ max = mid;
+ } else {
+ min = mid + 1;
+ }
+ }
+ target.setRange(min + 1, targetOffset + i + 1, target, min);
+ target[min] = element;
+ }
+}
+
+/// Sorts [elements] from [start] to [end] into [target] at [targetOffset].
+///
+/// The `target` list must be able to contain the range from `start` to `end`
+/// after `targetOffset`.
+///
+/// Allows target to be the same list as [elements], as long as it's not
+/// overlapping the `start..end` range.
+void _mergeSort<E, K>(
+ List<E> elements,
+ K Function(E element) keyOf,
+ int Function(K, K) compare,
+ int start,
+ int end,
+ List<E> target,
+ int targetOffset) {
+ var length = end - start;
+ if (length < _mergeSortLimit) {
+ _movingInsertionSort<E, K>(
+ elements, keyOf, compare, start, end, target, targetOffset);
+ return;
+ }
+ var middle = start + (length >> 1);
+ var firstLength = middle - start;
+ var secondLength = end - middle;
+ // Here secondLength >= firstLength (differs by at most one).
+ var targetMiddle = targetOffset + firstLength;
+ // Sort the second half into the end of the target area.
+ _mergeSort(elements, keyOf, compare, middle, end, target, targetMiddle);
+ // Sort the first half into the end of the source area.
+ _mergeSort(elements, keyOf, compare, start, middle, elements, middle);
+ // Merge the two parts into the target area.
+ _merge(keyOf, compare, elements, middle, middle + firstLength, target,
+ targetMiddle, targetMiddle + secondLength, target, targetOffset);
+}
+
+/// Merges two lists into a target list.
+///
+/// One of the input lists may be positioned at the end of the target
+/// list.
+///
+/// For equal object, elements from [firstList] are always preferred.
+/// This allows the merge to be stable if the first list contains elements
+/// that started out earlier than the ones in [secondList]
+void _merge<E, K>(
+ K Function(E element) keyOf,
+ int Function(K, K) compare,
+ List<E> firstList,
+ int firstStart,
+ int firstEnd,
+ List<E> secondList,
+ int secondStart,
+ int secondEnd,
+ List<E> target,
+ int targetOffset) {
+ // No empty lists reaches here.
+ assert(firstStart < firstEnd);
+ assert(secondStart < secondEnd);
+ var cursor1 = firstStart;
+ var cursor2 = secondStart;
+ var firstElement = firstList[cursor1++];
+ var firstKey = keyOf(firstElement);
+ var secondElement = secondList[cursor2++];
+ var secondKey = keyOf(secondElement);
+ while (true) {
+ if (compare(firstKey, secondKey) <= 0) {
+ target[targetOffset++] = firstElement;
+ if (cursor1 == firstEnd) break; // Flushing second list after loop.
+ firstElement = firstList[cursor1++];
+ firstKey = keyOf(firstElement);
+ } else {
+ target[targetOffset++] = secondElement;
+ if (cursor2 != secondEnd) {
+ secondElement = secondList[cursor2++];
+ secondKey = keyOf(secondElement);
+ continue;
+ }
+ // Second list empties first. Flushing first list here.
+ target[targetOffset++] = firstElement;
+ target.setRange(targetOffset, targetOffset + (firstEnd - cursor1),
+ firstList, cursor1);
+ return;
+ }
+ }
+ // First list empties first. Reached by break above.
+ target[targetOffset++] = secondElement;
+ target.setRange(
+ targetOffset, targetOffset + (secondEnd - cursor2), secondList, cursor2);
+}
+
+/// Sort [elements] using a quick-sort algorithm.
+///
+/// The elements are compared using [compare] on the elements.
+/// If [start] and [end] are provided, only that range is sorted.
+///
+/// Uses insertion sort for smaller sublists.
+void quickSort<E>(List<E> elements, int Function(E a, E b) compare,
+ [int start = 0, int? end]) {
+ end = RangeError.checkValidRange(start, end, elements.length);
+ _quickSort<E, E>(elements, identity, compare, Random(), start, end);
+}
+
+/// Sort [list] using a quick-sort algorithm.
+///
+/// The elements are compared using [compare] on the value provided by [keyOf]
+/// on the element.
+/// If [start] and [end] are provided, only that range is sorted.
+///
+/// Uses insertion sort for smaller sublists.
+void quickSortBy<E, K>(
+ List<E> list, K Function(E element) keyOf, int Function(K a, K b) compare,
+ [int start = 0, int? end]) {
+ end = RangeError.checkValidRange(start, end, list.length);
+ _quickSort(list, keyOf, compare, Random(), start, end);
+}
+
+void _quickSort<E, K>(List<E> list, K Function(E element) keyOf,
+ int Function(K a, K b) compare, Random random, int start, int end) {
+ const minQuickSortLength = 24;
+ var length = end - start;
+ while (length >= minQuickSortLength) {
+ var pivotIndex = random.nextInt(length) + start;
+ var pivot = list[pivotIndex];
+ var pivotKey = keyOf(pivot);
+ var endSmaller = start;
+ var startGreater = end;
+ var startPivots = end - 1;
+ list[pivotIndex] = list[startPivots];
+ list[startPivots] = pivot;
+ while (endSmaller < startPivots) {
+ var current = list[endSmaller];
+ var relation = compare(keyOf(current), pivotKey);
+ if (relation < 0) {
+ endSmaller++;
+ } else {
+ startPivots--;
+ var currentTarget = startPivots;
+ list[endSmaller] = list[startPivots];
+ if (relation > 0) {
+ startGreater--;
+ currentTarget = startGreater;
+ list[startPivots] = list[startGreater];
+ }
+ list[currentTarget] = current;
+ }
+ }
+ if (endSmaller - start < end - startGreater) {
+ _quickSort(list, keyOf, compare, random, start, endSmaller);
+ start = startGreater;
+ } else {
+ _quickSort(list, keyOf, compare, random, startGreater, end);
+ end = endSmaller;
+ }
+ length = end - start;
+ }
+ _movingInsertionSort<E, K>(list, keyOf, compare, start, end, list, start);
+}
diff --git a/pkgs/collection/lib/src/boollist.dart b/pkgs/collection/lib/src/boollist.dart
new file mode 100644
index 0000000..b026d85
--- /dev/null
+++ b/pkgs/collection/lib/src/boollist.dart
@@ -0,0 +1,273 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection' show ListMixin;
+import 'dart:typed_data' show Uint32List;
+
+import 'unmodifiable_wrappers.dart' show NonGrowableListMixin;
+
+/// A space-efficient list of boolean values.
+///
+/// Uses list of integers as internal storage to reduce memory usage.
+abstract /*mixin*/ class BoolList with ListMixin<bool> {
+ static const int _entryShift = 5;
+
+ static const int _bitsPerEntry = 32;
+
+ static const int _entrySignBitIndex = 31;
+
+ /// The length of the list.
+ ///
+ /// Maybe be shorter than the capacity of the backing store.
+ int _length;
+
+ /// Backing store for bits.
+ Uint32List _data;
+
+ BoolList._(this._data, this._length);
+
+ factory BoolList._selectType(int length, bool growable) {
+ if (growable) {
+ return _GrowableBoolList(length);
+ } else {
+ return _NonGrowableBoolList(length);
+ }
+ }
+
+ /// Creates a list of booleans with the provided length.
+ ///
+ /// The list is initially filled with the [fill] value, and
+ /// the list is growable if [growable] is true.
+ factory BoolList(int length, {bool fill = false, bool growable = false}) {
+ RangeError.checkNotNegative(length, 'length');
+
+ BoolList boolList;
+ if (growable) {
+ boolList = _GrowableBoolList(length);
+ } else {
+ boolList = _NonGrowableBoolList(length);
+ }
+
+ if (fill) {
+ boolList.fillRange(0, length, true);
+ }
+
+ return boolList;
+ }
+
+ /// Creates an empty list of booleans.
+ ///
+ /// The list defaults to being growable unless [growable] is `false`.
+ /// If [capacity] is provided, and [growable] is not `false`,
+ /// the implementation will attempt to make space for that
+ /// many elements before needing to grow its internal storage.
+ factory BoolList.empty({bool growable = true, int capacity = 0}) {
+ RangeError.checkNotNegative(capacity, 'length');
+
+ if (growable) {
+ return _GrowableBoolList._withCapacity(0, capacity);
+ } else {
+ return _NonGrowableBoolList._withCapacity(0, capacity);
+ }
+ }
+
+ /// Generates a [BoolList] of values.
+ ///
+ /// Creates a [BoolList] with [length] positions and fills it with values
+ /// created by calling [generator] for each index in the range
+ /// `0` .. `length - 1` in increasing order.
+ ///
+ /// The created list is fixed-length unless [growable] is true.
+ factory BoolList.generate(
+ int length,
+ bool Function(int) generator, {
+ bool growable = true,
+ }) {
+ RangeError.checkNotNegative(length, 'length');
+
+ var instance = BoolList._selectType(length, growable);
+ for (var i = 0; i < length; i++) {
+ instance._setBit(i, generator(i));
+ }
+ return instance;
+ }
+
+ /// Creates a list containing all [elements].
+ ///
+ /// The [Iterator] of [elements] provides the order of the elements.
+ ///
+ /// This constructor creates a growable [BoolList] when [growable] is true;
+ /// otherwise, it returns a fixed-length list.
+ factory BoolList.of(Iterable<bool> elements, {bool growable = false}) {
+ return BoolList._selectType(elements.length, growable)..setAll(0, elements);
+ }
+
+ /// The number of boolean values in this list.
+ ///
+ /// The valid indices for a list are `0` through `length - 1`.
+ ///
+ /// If the list is growable, setting the length will change the
+ /// number of values.
+ /// Setting the length to a smaller number will remove all
+ /// values with indices greater than or equal to the new length.
+ /// Setting the length to a larger number will increase the number of
+ /// values, and all the new values will be `false`.
+ @override
+ int get length => _length;
+
+ @override
+ bool operator [](int index) {
+ RangeError.checkValidIndex(index, this, 'index', _length);
+ return (_data[index >> _entryShift] &
+ (1 << (index & _entrySignBitIndex))) !=
+ 0;
+ }
+
+ @override
+ void operator []=(int index, bool value) {
+ RangeError.checkValidIndex(index, this, 'index', _length);
+ _setBit(index, value);
+ }
+
+ @override
+ void fillRange(int start, int end, [bool? fill]) {
+ RangeError.checkValidRange(start, end, _length);
+ fill ??= false;
+
+ var startWord = start >> _entryShift;
+ var endWord = (end - 1) >> _entryShift;
+
+ var startBit = start & _entrySignBitIndex;
+ var endBit = (end - 1) & _entrySignBitIndex;
+
+ if (startWord < endWord) {
+ if (fill) {
+ _data[startWord] |= -1 << startBit;
+ _data.fillRange(startWord + 1, endWord, -1);
+ _data[endWord] |= (1 << (endBit + 1)) - 1;
+ } else {
+ _data[startWord] &= (1 << startBit) - 1;
+ _data.fillRange(startWord + 1, endWord, 0);
+ _data[endWord] &= -1 << (endBit + 1);
+ }
+ } else {
+ if (fill) {
+ _data[startWord] |= ((1 << (endBit - startBit + 1)) - 1) << startBit;
+ } else {
+ _data[startWord] &= ((1 << startBit) - 1) | (-1 << (endBit + 1));
+ }
+ }
+ }
+
+ /// Creates an iterator for the elements of this [BoolList].
+ ///
+ /// The [Iterator.current] getter of the returned iterator
+ /// is `false` when the iterator has no current element.
+ @override
+ Iterator<bool> get iterator => _BoolListIterator(this);
+
+ void _setBit(int index, bool value) {
+ if (value) {
+ _data[index >> _entryShift] |= 1 << (index & _entrySignBitIndex);
+ } else {
+ _data[index >> _entryShift] &= ~(1 << (index & _entrySignBitIndex));
+ }
+ }
+
+ static int _lengthInWords(int bitLength) {
+ return (bitLength + (_bitsPerEntry - 1)) >> _entryShift;
+ }
+}
+
+class _GrowableBoolList extends BoolList {
+ static const int _growthFactor = 2;
+
+ _GrowableBoolList._withCapacity(int length, int capacity)
+ : super._(
+ Uint32List(BoolList._lengthInWords(capacity)),
+ length,
+ );
+
+ _GrowableBoolList(int length)
+ : super._(
+ Uint32List(BoolList._lengthInWords(length * _growthFactor)),
+ length,
+ );
+
+ @override
+ set length(int length) {
+ RangeError.checkNotNegative(length, 'length');
+ if (length > _length) {
+ _expand(length);
+ } else if (length < _length) {
+ _shrink(length);
+ }
+ }
+
+ void _expand(int length) {
+ if (length > _data.length * BoolList._bitsPerEntry) {
+ _data = Uint32List(
+ BoolList._lengthInWords(length * _growthFactor),
+ )..setRange(0, _data.length, _data);
+ }
+ _length = length;
+ }
+
+ void _shrink(int length) {
+ if (length < _length ~/ _growthFactor) {
+ var newDataLength = BoolList._lengthInWords(length);
+ _data = Uint32List(newDataLength)..setRange(0, newDataLength, _data);
+ }
+
+ for (var i = length; i < _data.length * BoolList._bitsPerEntry; i++) {
+ _setBit(i, false);
+ }
+
+ _length = length;
+ }
+}
+
+class _NonGrowableBoolList extends BoolList with NonGrowableListMixin<bool> {
+ _NonGrowableBoolList._withCapacity(int length, int capacity)
+ : super._(
+ Uint32List(BoolList._lengthInWords(capacity)),
+ length,
+ );
+
+ _NonGrowableBoolList(int length)
+ : super._(
+ Uint32List(BoolList._lengthInWords(length)),
+ length,
+ );
+}
+
+class _BoolListIterator implements Iterator<bool> {
+ bool _current = false;
+ int _pos = 0;
+ final int _length;
+
+ final BoolList _boolList;
+
+ _BoolListIterator(this._boolList) : _length = _boolList._length;
+
+ @override
+ bool get current => _current;
+
+ @override
+ bool moveNext() {
+ if (_boolList._length != _length) {
+ throw ConcurrentModificationError(_boolList);
+ }
+
+ if (_pos < _boolList.length) {
+ var pos = _pos++;
+ _current = _boolList._data[pos >> BoolList._entryShift] &
+ (1 << (pos & BoolList._entrySignBitIndex)) !=
+ 0;
+ return true;
+ }
+ _current = false;
+ return false;
+ }
+}
diff --git a/pkgs/collection/lib/src/canonicalized_map.dart b/pkgs/collection/lib/src/canonicalized_map.dart
new file mode 100644
index 0000000..3dc6e37
--- /dev/null
+++ b/pkgs/collection/lib/src/canonicalized_map.dart
@@ -0,0 +1,200 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+/// A map whose keys are converted to canonical values of type `C`.
+///
+/// This is useful for using case-insensitive String keys, for example. It's
+/// more efficient than a [LinkedHashMap] with a custom equality operator
+/// because it only canonicalizes each key once, rather than doing so for each
+/// comparison.
+class CanonicalizedMap<C, K, V> implements Map<K, V> {
+ final C Function(K) _canonicalize;
+
+ final bool Function(K)? _isValidKeyFn;
+
+ final _base = <C, MapEntry<K, V>>{};
+
+ /// Creates an empty canonicalized map.
+ ///
+ /// The [canonicalize] function should return the canonical value for the
+ /// given key. Keys with the same canonical value are considered equivalent.
+ ///
+ /// The [isValidKey] function is called before calling [canonicalize] for
+ /// methods that take arbitrary objects. It can be used to filter out keys
+ /// that can't be canonicalized.
+ CanonicalizedMap(C Function(K key) canonicalize,
+ {bool Function(K key)? isValidKey})
+ : _canonicalize = canonicalize,
+ _isValidKeyFn = isValidKey;
+
+ /// Creates a canonicalized map that is initialized with the key/value pairs
+ /// of [other].
+ ///
+ /// The [canonicalize] function should return the canonical value for the
+ /// given key. Keys with the same canonical value are considered equivalent.
+ ///
+ /// The [isValidKey] function is called before calling [canonicalize] for
+ /// methods that take arbitrary objects. It can be used to filter out keys
+ /// that can't be canonicalized.
+ CanonicalizedMap.from(Map<K, V> other, C Function(K key) canonicalize,
+ {bool Function(K key)? isValidKey})
+ : _canonicalize = canonicalize,
+ _isValidKeyFn = isValidKey {
+ addAll(other);
+ }
+
+ /// Creates a canonicalized map that is initialized with the key/value pairs
+ /// of [entries].
+ ///
+ /// The [canonicalize] function should return the canonical value for the
+ /// given key. Keys with the same canonical value are considered equivalent.
+ ///
+ /// The [isValidKey] function is called before calling [canonicalize] for
+ /// methods that take arbitrary objects. It can be used to filter out keys
+ /// that can't be canonicalized.
+ CanonicalizedMap.fromEntries(
+ Iterable<MapEntry<K, V>> entries, C Function(K key) canonicalize,
+ {bool Function(K key)? isValidKey})
+ : _canonicalize = canonicalize,
+ _isValidKeyFn = isValidKey {
+ addEntries(entries);
+ }
+
+ CanonicalizedMap._(
+ this._canonicalize, this._isValidKeyFn, Map<C, MapEntry<K, V>> base) {
+ _base.addAll(base);
+ }
+
+ /// Copies this [CanonicalizedMap] instance without recalculating the
+ /// canonical values of the keys.
+ CanonicalizedMap<C, K, V> copy() =>
+ CanonicalizedMap._(_canonicalize, _isValidKeyFn, _base);
+
+ @override
+ V? operator [](Object? key) {
+ if (!_isValidKey(key)) return null;
+ var pair = _base[_canonicalize(key as K)];
+ return pair?.value;
+ }
+
+ @override
+ void operator []=(K key, V value) {
+ if (!_isValidKey(key)) return;
+ _base[_canonicalize(key)] = MapEntry(key, value);
+ }
+
+ @override
+ void addAll(Map<K, V> other) {
+ other.forEach((key, value) => this[key] = value);
+ }
+
+ @override
+ void addEntries(Iterable<MapEntry<K, V>> entries) => _base.addEntries(entries
+ .map((e) => MapEntry(_canonicalize(e.key), MapEntry(e.key, e.value))));
+
+ @override
+ Map<K2, V2> cast<K2, V2>() => _base.cast<K2, V2>();
+
+ @override
+ void clear() {
+ _base.clear();
+ }
+
+ @override
+ bool containsKey(Object? key) {
+ if (!_isValidKey(key)) return false;
+ return _base.containsKey(_canonicalize(key as K));
+ }
+
+ @override
+ bool containsValue(Object? value) =>
+ _base.values.any((pair) => pair.value == value);
+
+ @override
+ Iterable<MapEntry<K, V>> get entries =>
+ _base.entries.map((e) => MapEntry(e.value.key, e.value.value));
+
+ @override
+ void forEach(void Function(K, V) f) {
+ _base.forEach((key, pair) => f(pair.key, pair.value));
+ }
+
+ @override
+ bool get isEmpty => _base.isEmpty;
+
+ @override
+ bool get isNotEmpty => _base.isNotEmpty;
+
+ @override
+ Iterable<K> get keys => _base.values.map((pair) => pair.key);
+
+ @override
+ int get length => _base.length;
+
+ @override
+ Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> Function(K, V) transform) =>
+ _base.map((_, pair) => transform(pair.key, pair.value));
+
+ @override
+ V putIfAbsent(K key, V Function() ifAbsent) {
+ return _base
+ .putIfAbsent(_canonicalize(key), () => MapEntry(key, ifAbsent()))
+ .value;
+ }
+
+ @override
+ V? remove(Object? key) {
+ if (!_isValidKey(key)) return null;
+ var pair = _base.remove(_canonicalize(key as K));
+ return pair?.value;
+ }
+
+ @override
+ void removeWhere(bool Function(K key, V value) test) =>
+ _base.removeWhere((_, pair) => test(pair.key, pair.value));
+
+ @Deprecated('Use cast instead')
+ Map<K2, V2> retype<K2, V2>() => cast<K2, V2>();
+
+ @override
+ V update(K key, V Function(V) update, {V Function()? ifAbsent}) =>
+ _base.update(_canonicalize(key), (pair) {
+ var value = pair.value;
+ var newValue = update(value);
+ if (identical(newValue, value)) return pair;
+ return MapEntry(key, newValue);
+ },
+ ifAbsent:
+ ifAbsent == null ? null : () => MapEntry(key, ifAbsent())).value;
+
+ @override
+ void updateAll(V Function(K key, V value) update) =>
+ _base.updateAll((_, pair) {
+ var value = pair.value;
+ var key = pair.key;
+ var newValue = update(key, value);
+ if (identical(value, newValue)) return pair;
+ return MapEntry(key, newValue);
+ });
+
+ @override
+ Iterable<V> get values => _base.values.map((pair) => pair.value);
+
+ @override
+ String toString() => MapBase.mapToString(this);
+
+ bool _isValidKey(Object? key) =>
+ (key is K) && (_isValidKeyFn == null || _isValidKeyFn(key));
+
+ /// Creates a `Map<K,V>` (with the original key values).
+ /// See [toMapOfCanonicalKeys].
+ Map<K, V> toMap() => Map<K, V>.fromEntries(_base.values);
+
+ /// Creates a `Map<C,V>` (with the canonicalized keys).
+ /// See [toMap].
+ Map<C, V> toMapOfCanonicalKeys() => Map<C, V>.fromEntries(
+ _base.entries.map((e) => MapEntry<C, V>(e.key, e.value.value)));
+}
diff --git a/pkgs/collection/lib/src/combined_wrappers/combined_iterable.dart b/pkgs/collection/lib/src/combined_wrappers/combined_iterable.dart
new file mode 100644
index 0000000..281f8a2
--- /dev/null
+++ b/pkgs/collection/lib/src/combined_wrappers/combined_iterable.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'combined_iterator.dart';
+
+/// A view of several iterables combined sequentially into a single iterable.
+///
+/// All methods and accessors treat the [CombinedIterableView] as if it were a
+/// single concatenated iterable, but the underlying implementation is based on
+/// lazily accessing individual iterable instances. This means that if the
+/// underlying iterables change, the [CombinedIterableView] will reflect those
+/// changes.
+class CombinedIterableView<T> extends IterableBase<T> {
+ /// The iterables that this combines.
+ final Iterable<Iterable<T>> _iterables;
+
+ /// Creates a combined view of [_iterables].
+ const CombinedIterableView(this._iterables);
+
+ @override
+ Iterator<T> get iterator =>
+ CombinedIterator<T>(_iterables.map((i) => i.iterator).iterator);
+
+ // Special cased contains/isEmpty/length since many iterables have an
+ // efficient implementation instead of running through the entire iterator.
+
+ @override
+ bool contains(Object? element) => _iterables.any((i) => i.contains(element));
+
+ @override
+ bool get isEmpty => _iterables.every((i) => i.isEmpty);
+
+ @override
+ int get length => _iterables.fold(0, (length, i) => length + i.length);
+}
diff --git a/pkgs/collection/lib/src/combined_wrappers/combined_iterator.dart b/pkgs/collection/lib/src/combined_wrappers/combined_iterator.dart
new file mode 100644
index 0000000..0d6088a
--- /dev/null
+++ b/pkgs/collection/lib/src/combined_wrappers/combined_iterator.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// The iterator for `CombinedIterableView` and `CombinedListView`.
+///
+/// Moves through each iterable's iterator in sequence.
+class CombinedIterator<T> implements Iterator<T> {
+ /// The iterators that this combines, or `null` if done iterating.
+ ///
+ /// Because this comes from a call to [Iterable.map], it's lazy and will
+ /// avoid instantiating unnecessary iterators.
+ Iterator<Iterator<T>>? _iterators;
+
+ CombinedIterator(Iterator<Iterator<T>> iterators) : _iterators = iterators {
+ if (!iterators.moveNext()) _iterators = null;
+ }
+
+ @override
+ T get current {
+ var iterators = _iterators;
+ if (iterators != null) return iterators.current.current;
+ return null as T;
+ }
+
+ @override
+ bool moveNext() {
+ var iterators = _iterators;
+ if (iterators != null) {
+ do {
+ if (iterators.current.moveNext()) {
+ return true;
+ }
+ } while (iterators.moveNext());
+ _iterators = null;
+ }
+ return false;
+ }
+}
diff --git a/pkgs/collection/lib/src/combined_wrappers/combined_list.dart b/pkgs/collection/lib/src/combined_wrappers/combined_list.dart
new file mode 100644
index 0000000..f0cd447
--- /dev/null
+++ b/pkgs/collection/lib/src/combined_wrappers/combined_list.dart
@@ -0,0 +1,79 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'combined_iterator.dart';
+
+/// A view of several lists combined into a single list.
+///
+/// All methods and accessors treat the [CombinedListView] list as if it were a
+/// single concatenated list, but the underlying implementation is based on
+/// lazily accessing individual list instances. This means that if the
+/// underlying lists change, the [CombinedListView] will reflect those changes.
+///
+/// The index operator (`[]`) and [length] property of a [CombinedListView] are
+/// both `O(lists)` rather than `O(1)`. A [CombinedListView] is unmodifiable.
+class CombinedListView<T> extends ListBase<T>
+ implements UnmodifiableListView<T> {
+ static Never _throw() {
+ throw UnsupportedError('Cannot modify an unmodifiable List');
+ }
+
+ /// The lists that this combines.
+ final List<List<T>> _lists;
+
+ /// Creates a combined view of [_lists].
+ CombinedListView(this._lists);
+
+ @override
+ Iterator<T> get iterator =>
+ CombinedIterator<T>(_lists.map((i) => i.iterator).iterator);
+
+ @override
+ set length(int length) {
+ _throw();
+ }
+
+ @override
+ int get length => _lists.fold(0, (length, list) => length + list.length);
+
+ @override
+ T operator [](int index) {
+ var initialIndex = index;
+ for (var i = 0; i < _lists.length; i++) {
+ var list = _lists[i];
+ if (index < list.length) {
+ return list[index];
+ }
+ index -= list.length;
+ }
+ throw RangeError.index(initialIndex, this, 'index', null, length);
+ }
+
+ @override
+ void operator []=(int index, T value) {
+ _throw();
+ }
+
+ @override
+ void clear() {
+ _throw();
+ }
+
+ @override
+ bool remove(Object? element) {
+ _throw();
+ }
+
+ @override
+ void removeWhere(bool Function(T) test) {
+ _throw();
+ }
+
+ @override
+ void retainWhere(bool Function(T) test) {
+ _throw();
+ }
+}
diff --git a/pkgs/collection/lib/src/combined_wrappers/combined_map.dart b/pkgs/collection/lib/src/combined_wrappers/combined_map.dart
new file mode 100644
index 0000000..18db6e7
--- /dev/null
+++ b/pkgs/collection/lib/src/combined_wrappers/combined_map.dart
@@ -0,0 +1,104 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'combined_iterable.dart';
+
+/// Returns a new map that represents maps flattened into a single map.
+///
+/// All methods and accessors treat the new map as-if it were a single
+/// concatenated map, but the underlying implementation is based on lazily
+/// accessing individual map instances. In the occasion where a key occurs in
+/// multiple maps the first value is returned.
+///
+/// The resulting map has an index operator (`[]`) that is `O(maps)`, rather
+/// than `O(1)`, and the map is unmodifiable, but underlying changes to these
+/// maps are still accessible from the resulting map.
+///
+/// The `length` getter is `O(M)` where M is the total number of entries in
+/// all maps, since it has to remove duplicate entries.
+class CombinedMapView<K, V> extends UnmodifiableMapBase<K, V> {
+ final Iterable<Map<K, V>> _maps;
+
+ /// Create a new combined view of multiple maps.
+ ///
+ /// The iterable is accessed lazily so it should be collection type like
+ /// [List] or [Set] rather than a lazy iterable produced by `map()` et al.
+ CombinedMapView(this._maps);
+
+ @override
+ V? operator [](Object? key) {
+ for (var map in _maps) {
+ // Avoid two hash lookups on a positive hit.
+ var value = map[key];
+ if (value != null || map.containsKey(value)) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+ /// The keys of `this`.
+ ///
+ /// The returned iterable has efficient `contains` operations, assuming the
+ /// iterables returned by the wrapped maps have efficient `contains`
+ /// operations for their `keys` iterables.
+ ///
+ /// The `length` must do deduplication and thus is not optimized.
+ ///
+ /// The order of iteration is defined by the individual `Map` implementations,
+ /// but must be consistent between changes to the maps.
+ ///
+ /// Unlike most [Map] implementations, modifying an individual map while
+ /// iterating the keys will _sometimes_ throw. This behavior may change in
+ /// the future.
+ @override
+ Iterable<K> get keys => _DeduplicatingIterableView(
+ CombinedIterableView(_maps.map((m) => m.keys)));
+}
+
+/// A view of an iterable that skips any duplicate entries.
+class _DeduplicatingIterableView<T> extends IterableBase<T> {
+ final Iterable<T> _iterable;
+
+ const _DeduplicatingIterableView(this._iterable);
+
+ @override
+ Iterator<T> get iterator => _DeduplicatingIterator(_iterable.iterator);
+
+ // Special cased contains/isEmpty since many iterables have an efficient
+ // implementation instead of running through the entire iterator.
+ //
+ // Note: We do not do this for `length` because we have to remove the
+ // duplicates.
+
+ @override
+ bool contains(Object? element) => _iterable.contains(element);
+
+ @override
+ bool get isEmpty => _iterable.isEmpty;
+}
+
+/// An iterator that wraps another iterator and skips duplicate values.
+class _DeduplicatingIterator<T> implements Iterator<T> {
+ final Iterator<T> _iterator;
+
+ final _emitted = HashSet<T>();
+
+ _DeduplicatingIterator(this._iterator);
+
+ @override
+ T get current => _iterator.current;
+
+ @override
+ bool moveNext() {
+ while (_iterator.moveNext()) {
+ if (_emitted.add(current)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/pkgs/collection/lib/src/comparators.dart b/pkgs/collection/lib/src/comparators.dart
new file mode 100644
index 0000000..6e5d363
--- /dev/null
+++ b/pkgs/collection/lib/src/comparators.dart
@@ -0,0 +1,393 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Character constants.
+const int _zero = 0x30;
+const int _upperCaseA = 0x41;
+const int _upperCaseZ = 0x5a;
+const int _lowerCaseA = 0x61;
+const int _lowerCaseZ = 0x7a;
+const int _asciiCaseBit = 0x20;
+
+/// Checks if strings [a] and [b] differ only on the case of ASCII letters.
+///
+/// Strings are equal if they have the same length, and the characters at
+/// each index are the same, or they are ASCII letters where one is upper-case
+/// and the other is the lower-case version of the same letter.
+///
+/// The comparison does not ignore the case of non-ASCII letters, so
+/// an upper-case ae-ligature (Æ) is different from
+/// a lower case ae-ligature (æ).
+///
+/// Ignoring non-ASCII letters is not generally a good idea, but it makes sense
+/// for situations where the strings are known to be ASCII. Examples could
+/// be Dart identifiers, base-64 or hex encoded strings, GUIDs or similar
+/// strings with a known structure.
+bool equalsIgnoreAsciiCase(String a, String b) {
+ if (a.length != b.length) return false;
+ for (var i = 0; i < a.length; i++) {
+ var aChar = a.codeUnitAt(i);
+ var bChar = b.codeUnitAt(i);
+ if (aChar == bChar) continue;
+ // Quick-check for whether this may be different cases of the same letter.
+ if (aChar ^ bChar != _asciiCaseBit) return false;
+ // If it's possible, then check if either character is actually an ASCII
+ // letter.
+ var aCharLowerCase = aChar | _asciiCaseBit;
+ if (_lowerCaseA <= aCharLowerCase && aCharLowerCase <= _lowerCaseZ) {
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+/// Hash code for a string which is compatible with [equalsIgnoreAsciiCase].
+///
+/// The hash code is unaffected by changing the case of ASCII letters, but
+/// the case of non-ASCII letters do affect the result.
+int hashIgnoreAsciiCase(String string) {
+ // Jenkins hash code ( http://en.wikipedia.org/wiki/Jenkins_hash_function).
+ // adapted to smi values.
+ // Same hash used by dart2js for strings, modified to ignore ASCII letter
+ // case.
+ var hash = 0;
+ for (var i = 0; i < string.length; i++) {
+ var char = string.codeUnitAt(i);
+ // Convert lower-case ASCII letters to upper case.upper
+ // This ensures that strings that differ only in case will have the
+ // same hash code.
+ if (_lowerCaseA <= char && char <= _lowerCaseZ) char -= _asciiCaseBit;
+ hash = 0x1fffffff & (hash + char);
+ hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
+ hash >>= 6;
+ }
+ hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
+ hash >>= 11;
+ return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
+}
+
+/// Compares [a] and [b] lexically, converting ASCII letters to upper case.
+///
+/// Comparison treats all lower-case ASCII letters as upper-case letters,
+/// but does no case conversion for non-ASCII letters.
+///
+/// If two strings differ only on the case of ASCII letters, the one with the
+/// capital letter at the first difference will compare as less than the other
+/// string. This tie-breaking ensures that the comparison is a total ordering
+/// on strings and is compatible with equality.
+///
+/// Ignoring non-ASCII letters is not generally a good idea, but it makes sense
+/// for situations where the strings are known to be ASCII. Examples could
+/// be Dart identifiers, base-64 or hex encoded strings, GUIDs or similar
+/// strings with a known structure.
+int compareAsciiUpperCase(String a, String b) {
+ var defaultResult = 0; // Returned if no difference found.
+ for (var i = 0; i < a.length; i++) {
+ if (i >= b.length) return 1;
+ var aChar = a.codeUnitAt(i);
+ var bChar = b.codeUnitAt(i);
+ if (aChar == bChar) continue;
+ // Upper-case if letters.
+ var aUpperCase = aChar;
+ var bUpperCase = bChar;
+ if (_lowerCaseA <= aChar && aChar <= _lowerCaseZ) {
+ aUpperCase -= _asciiCaseBit;
+ }
+ if (_lowerCaseA <= bChar && bChar <= _lowerCaseZ) {
+ bUpperCase -= _asciiCaseBit;
+ }
+ if (aUpperCase != bUpperCase) return (aUpperCase - bUpperCase).sign;
+ if (defaultResult == 0) defaultResult = aChar - bChar;
+ }
+ if (b.length > a.length) return -1;
+ return defaultResult.sign;
+}
+
+/// Compares [a] and [b] lexically, converting ASCII letters to lower case.
+///
+/// Comparison treats all upper-case ASCII letters as lower-case letters,
+/// but does no case conversion for non-ASCII letters.
+///
+/// If two strings differ only on the case of ASCII letters, the one with the
+/// capital letter at the first difference will compare as less than the other
+/// string. This tie-breaking ensures that the comparison is a total ordering
+/// on strings.
+///
+/// Ignoring non-ASCII letters is not generally a good idea, but it makes sense
+/// for situations where the strings are known to be ASCII. Examples could
+/// be Dart identifiers, base-64 or hex encoded strings, GUIDs or similar
+/// strings with a known structure.
+int compareAsciiLowerCase(String a, String b) {
+ var defaultResult = 0;
+ for (var i = 0; i < a.length; i++) {
+ if (i >= b.length) return 1;
+ var aChar = a.codeUnitAt(i);
+ var bChar = b.codeUnitAt(i);
+ if (aChar == bChar) continue;
+ var aLowerCase = aChar;
+ var bLowerCase = bChar;
+ // Upper case if ASCII letters.
+ if (_upperCaseA <= bChar && bChar <= _upperCaseZ) {
+ bLowerCase += _asciiCaseBit;
+ }
+ if (_upperCaseA <= aChar && aChar <= _upperCaseZ) {
+ aLowerCase += _asciiCaseBit;
+ }
+ if (aLowerCase != bLowerCase) return (aLowerCase - bLowerCase).sign;
+ if (defaultResult == 0) defaultResult = aChar - bChar;
+ }
+ if (b.length > a.length) return -1;
+ return defaultResult.sign;
+}
+
+/// Compares strings [a] and [b] according to [natural sort ordering][].
+///
+/// A natural sort ordering is a lexical ordering where embedded
+/// numerals (digit sequences) are treated as a single unit and ordered by
+/// numerical value.
+/// This means that `"a10b"` will be ordered after `"a7b"` in natural
+/// ordering, where lexical ordering would put the `1` before the `7`, ignoring
+/// that the `1` is part of a larger number.
+///
+/// Example:
+/// The following strings are in the order they would be sorted by using this
+/// comparison function:
+///
+/// "a", "a0", "a0b", "a1", "a01", "a9", "a10", "a100", "a100b", "aa"
+///
+/// [natural sort ordering]: https://en.wikipedia.org/wiki/Natural_sort_order
+int compareNatural(String a, String b) {
+ for (var i = 0; i < a.length; i++) {
+ if (i >= b.length) return 1;
+ var aChar = a.codeUnitAt(i);
+ var bChar = b.codeUnitAt(i);
+ if (aChar != bChar) {
+ return _compareNaturally(a, b, i, aChar, bChar);
+ }
+ }
+ if (b.length > a.length) return -1;
+ return 0;
+}
+
+/// Compares strings [a] and [b] according to lower-case
+/// [natural sort ordering][].
+///
+/// ASCII letters are converted to lower case before being compared, like
+/// for [compareAsciiLowerCase], then the result is compared like for
+/// [compareNatural].
+///
+/// If two strings differ only on the case of ASCII letters, the one with the
+/// capital letter at the first difference will compare as less than the other
+/// string. This tie-breaking ensures that the comparison is a total ordering
+/// on strings.
+///
+/// [natural sort ordering]: https://en.wikipedia.org/wiki/Natural_sort_order
+int compareAsciiLowerCaseNatural(String a, String b) {
+ var defaultResult = 0; // Returned if no difference found.
+ for (var i = 0; i < a.length; i++) {
+ if (i >= b.length) return 1;
+ var aChar = a.codeUnitAt(i);
+ var bChar = b.codeUnitAt(i);
+ if (aChar == bChar) continue;
+ var aLowerCase = aChar;
+ var bLowerCase = bChar;
+ if (_upperCaseA <= aChar && aChar <= _upperCaseZ) {
+ aLowerCase += _asciiCaseBit;
+ }
+ if (_upperCaseA <= bChar && bChar <= _upperCaseZ) {
+ bLowerCase += _asciiCaseBit;
+ }
+ if (aLowerCase != bLowerCase) {
+ return _compareNaturally(a, b, i, aLowerCase, bLowerCase);
+ }
+ if (defaultResult == 0) defaultResult = aChar - bChar;
+ }
+ if (b.length > a.length) return -1;
+ return defaultResult.sign;
+}
+
+/// Compares strings [a] and [b] according to upper-case
+/// [natural sort ordering][].
+///
+/// ASCII letters are converted to upper case before being compared, like
+/// for [compareAsciiUpperCase], then the result is compared like for
+/// [compareNatural].
+///
+/// If two strings differ only on the case of ASCII letters, the one with the
+/// capital letter at the first difference will compare as less than the other
+/// string. This tie-breaking ensures that the comparison is a total ordering
+/// on strings
+///
+/// [natural sort ordering]: https://en.wikipedia.org/wiki/Natural_sort_order
+int compareAsciiUpperCaseNatural(String a, String b) {
+ var defaultResult = 0;
+ for (var i = 0; i < a.length; i++) {
+ if (i >= b.length) return 1;
+ var aChar = a.codeUnitAt(i);
+ var bChar = b.codeUnitAt(i);
+ if (aChar == bChar) continue;
+ var aUpperCase = aChar;
+ var bUpperCase = bChar;
+ if (_lowerCaseA <= aChar && aChar <= _lowerCaseZ) {
+ aUpperCase -= _asciiCaseBit;
+ }
+ if (_lowerCaseA <= bChar && bChar <= _lowerCaseZ) {
+ bUpperCase -= _asciiCaseBit;
+ }
+ if (aUpperCase != bUpperCase) {
+ return _compareNaturally(a, b, i, aUpperCase, bUpperCase);
+ }
+ if (defaultResult == 0) defaultResult = aChar - bChar;
+ }
+ if (b.length > a.length) return -1;
+ return defaultResult.sign;
+}
+
+/// Check for numbers overlapping the current mismatched characters.
+///
+/// If both [aChar] and [bChar] are digits, use numerical comparison.
+/// Check if the previous characters is a non-zero number, and if not,
+/// skip - but count - leading zeros before comparing numbers.
+///
+/// If one is a digit and the other isn't, check if the previous character
+/// is a digit, and if so, the the one with the digit is the greater number.
+///
+/// Otherwise just returns the difference between [aChar] and [bChar].
+int _compareNaturally(String a, String b, int index, int aChar, int bChar) {
+ assert(aChar != bChar);
+ var aIsDigit = _isDigit(aChar);
+ var bIsDigit = _isDigit(bChar);
+ if (aIsDigit) {
+ if (bIsDigit) {
+ return _compareNumerically(a, b, aChar, bChar, index);
+ } else if (index > 0 && _isDigit(a.codeUnitAt(index - 1))) {
+ // aChar is the continuation of a longer number.
+ return 1;
+ }
+ } else if (bIsDigit && index > 0 && _isDigit(b.codeUnitAt(index - 1))) {
+ // bChar is the continuation of a longer number.
+ return -1;
+ }
+ // Characters are both non-digits, or not continuation of earlier number.
+ return (aChar - bChar).sign;
+}
+
+/// Compare numbers overlapping [aChar] and [bChar] numerically.
+///
+/// If the numbers have the same numerical value, but one has more leading
+/// zeros, the longer number is considered greater than the shorter one.
+///
+/// This ensures a total ordering on strings compatible with equality.
+int _compareNumerically(String a, String b, int aChar, int bChar, int index) {
+ // Both are digits. Find the first significant different digit, then find
+ // the length of the numbers.
+ if (_isNonZeroNumberSuffix(a, index)) {
+ // Part of a longer number, differs at this index, just count the length.
+ var result = _compareDigitCount(a, b, index, index);
+ if (result != 0) return result;
+ // If same length, the current character is the most significant differing
+ // digit.
+ return (aChar - bChar).sign;
+ }
+ // Not part of larger (non-zero) number, so skip leading zeros before
+ // comparing numbers.
+ var aIndex = index;
+ var bIndex = index;
+ if (aChar == _zero) {
+ do {
+ aIndex++;
+ if (aIndex == a.length) return -1; // number in a is zero, b is not.
+ aChar = a.codeUnitAt(aIndex);
+ } while (aChar == _zero);
+ if (!_isDigit(aChar)) return -1;
+ } else if (bChar == _zero) {
+ do {
+ bIndex++;
+ if (bIndex == b.length) return 1; // number in b is zero, a is not.
+ bChar = b.codeUnitAt(bIndex);
+ } while (bChar == _zero);
+ if (!_isDigit(bChar)) return 1;
+ }
+ if (aChar != bChar) {
+ var result = _compareDigitCount(a, b, aIndex, bIndex);
+ if (result != 0) return result;
+ return (aChar - bChar).sign;
+ }
+ // Same leading digit, one had more leading zeros.
+ // Compare digits until reaching a difference.
+ while (true) {
+ var aIsDigit = false;
+ var bIsDigit = false;
+ aChar = 0;
+ bChar = 0;
+ if (++aIndex < a.length) {
+ aChar = a.codeUnitAt(aIndex);
+ aIsDigit = _isDigit(aChar);
+ }
+ if (++bIndex < b.length) {
+ bChar = b.codeUnitAt(bIndex);
+ bIsDigit = _isDigit(bChar);
+ }
+ if (aIsDigit) {
+ if (bIsDigit) {
+ if (aChar == bChar) continue;
+ // First different digit found.
+ break;
+ }
+ // bChar is non-digit, so a has longer number.
+ return 1;
+ } else if (bIsDigit) {
+ return -1; // b has longer number.
+ } else {
+ // Neither is digit, so numbers had same numerical value.
+ // Fall back on number of leading zeros
+ // (reflected by difference in indices).
+ return (aIndex - bIndex).sign;
+ }
+ }
+ // At first differing digits.
+ var result = _compareDigitCount(a, b, aIndex, bIndex);
+ if (result != 0) return result;
+ return (aChar - bChar).sign;
+}
+
+/// Checks which of [a] and [b] has the longest sequence of digits.
+///
+/// Starts counting from `i + 1` and `j + 1` (assumes that `a[i]` and `b[j]` are
+/// both already known to be digits).
+int _compareDigitCount(String a, String b, int i, int j) {
+ while (++i < a.length) {
+ var aIsDigit = _isDigit(a.codeUnitAt(i));
+ if (++j == b.length) return aIsDigit ? 1 : 0;
+ var bIsDigit = _isDigit(b.codeUnitAt(j));
+ if (aIsDigit) {
+ if (bIsDigit) continue;
+ return 1;
+ } else if (bIsDigit) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ if (++j < b.length && _isDigit(b.codeUnitAt(j))) {
+ return -1;
+ }
+ return 0;
+}
+
+bool _isDigit(int charCode) => (charCode ^ _zero) <= 9;
+
+/// Check if the digit at [index] is continuing a non-zero number.
+///
+/// If there is no non-zero digits before, then leading zeros at [index]
+/// are also ignored when comparing numerically. If there is a non-zero digit
+/// before, then zeros at [index] are significant.
+bool _isNonZeroNumberSuffix(String string, int index) {
+ while (--index >= 0) {
+ var char = string.codeUnitAt(index);
+ if (char != _zero) return _isDigit(char);
+ }
+ return false;
+}
diff --git a/pkgs/collection/lib/src/empty_unmodifiable_set.dart b/pkgs/collection/lib/src/empty_unmodifiable_set.dart
new file mode 100644
index 0000000..74fd39a
--- /dev/null
+++ b/pkgs/collection/lib/src/empty_unmodifiable_set.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'unmodifiable_wrappers.dart';
+import 'wrappers.dart';
+
+/// An unmodifiable, empty set which can be constant.
+class EmptyUnmodifiableSet<E> extends IterableBase<E>
+ with UnmodifiableSetMixin<E>
+ implements UnmodifiableSetView<E> {
+ const EmptyUnmodifiableSet();
+
+ @override
+ Iterator<E> get iterator => Iterable<E>.empty().iterator;
+ @override
+ int get length => 0;
+ @override
+ EmptyUnmodifiableSet<T> cast<T>() => EmptyUnmodifiableSet<T>();
+ @override
+ bool contains(Object? element) => false;
+ @override
+ bool containsAll(Iterable<Object?> other) => other.isEmpty;
+ @override
+ Iterable<E> followedBy(Iterable<E> other) => DelegatingIterable(other);
+ @override
+ E? lookup(Object? element) => null;
+ @Deprecated('Use cast instead')
+ @override
+ EmptyUnmodifiableSet<T> retype<T>() => EmptyUnmodifiableSet<T>();
+ @override
+ E singleWhere(bool Function(E) test, {E Function()? orElse}) =>
+ orElse != null ? orElse() : throw StateError('No element');
+ @override
+ Iterable<T> whereType<T>() => Iterable<T>.empty();
+ @override
+ Set<E> toSet() => {};
+ @override
+ Set<E> union(Set<E> other) => Set.of(other);
+ @override
+ Set<E> intersection(Set<Object?> other) => {};
+ @override
+ Set<E> difference(Set<Object?> other) => {};
+}
diff --git a/pkgs/collection/lib/src/equality.dart b/pkgs/collection/lib/src/equality.dart
new file mode 100644
index 0000000..1e2f02a
--- /dev/null
+++ b/pkgs/collection/lib/src/equality.dart
@@ -0,0 +1,501 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'comparators.dart';
+
+const int _hashMask = 0x7fffffff;
+
+/// A generic equality relation on objects.
+abstract class Equality<E> {
+ const factory Equality() = DefaultEquality<E>;
+
+ /// Compare two elements for being equal.
+ ///
+ /// This should be a proper equality relation.
+ bool equals(E e1, E e2);
+
+ /// Get a hashcode of an element.
+ ///
+ /// The hashcode should be compatible with [equals], so that if
+ /// `equals(a, b)` then `hash(a) == hash(b)`.
+ int hash(E e);
+
+ /// Test whether an object is a valid argument to [equals] and [hash].
+ ///
+ /// Some implementations may be restricted to only work on specific types
+ /// of objects.
+ bool isValidKey(Object? o);
+}
+
+/// Equality of objects based on derived values.
+///
+/// For example, given the class:
+/// ```dart
+/// abstract class Employee {
+/// int get employmentId;
+/// }
+/// ```
+///
+/// The following [Equality] considers employees with the same IDs to be equal:
+/// ```dart
+/// EqualityBy((Employee e) => e.employmentId);
+/// ```
+///
+/// It's also possible to pass an additional equality instance that should be
+/// used to compare the value itself.
+class EqualityBy<E, F> implements Equality<E> {
+ final F Function(E) _comparisonKey;
+
+ final Equality<F> _inner;
+
+ EqualityBy(F Function(E) comparisonKey,
+ [Equality<F> inner = const DefaultEquality<Never>()])
+ : _comparisonKey = comparisonKey,
+ _inner = inner;
+
+ @override
+ bool equals(E e1, E e2) =>
+ _inner.equals(_comparisonKey(e1), _comparisonKey(e2));
+
+ @override
+ int hash(E e) => _inner.hash(_comparisonKey(e));
+
+ @override
+ bool isValidKey(Object? o) {
+ if (o is E) {
+ final value = _comparisonKey(o);
+ return _inner.isValidKey(value);
+ }
+ return false;
+ }
+}
+
+/// Equality of objects that compares only the natural equality of the objects.
+///
+/// This equality uses the objects' own [Object.==] and [Object.hashCode] for
+/// the equality.
+///
+/// Note that [equals] and [hash] take `Object`s rather than `E`s. This allows
+/// `E` to be inferred as `Null` in const contexts where `E` wouldn't be a
+/// compile-time constant, while still allowing the class to be used at runtime.
+class DefaultEquality<E> implements Equality<E> {
+ const DefaultEquality();
+ @override
+ bool equals(Object? e1, Object? e2) => e1 == e2;
+ @override
+ int hash(Object? e) => e.hashCode;
+ @override
+ bool isValidKey(Object? o) => true;
+}
+
+/// Equality of objects that compares only the identity of the objects.
+class IdentityEquality<E> implements Equality<E> {
+ const IdentityEquality();
+ @override
+ bool equals(E e1, E e2) => identical(e1, e2);
+ @override
+ int hash(E e) => identityHashCode(e);
+ @override
+ bool isValidKey(Object? o) => true;
+}
+
+/// Equality on iterables.
+///
+/// Two iterables are equal if they have the same elements in the same order.
+///
+/// The [equals] and [hash] methods accepts `null` values,
+/// even if the [isValidKey] returns `false` for `null`.
+/// The [hash] of `null` is `null.hashCode`.
+class IterableEquality<E> implements Equality<Iterable<E>> {
+ final Equality<E?> _elementEquality;
+ const IterableEquality(
+ [Equality<E> elementEquality = const DefaultEquality<Never>()])
+ : _elementEquality = elementEquality;
+
+ @override
+ bool equals(Iterable<E>? elements1, Iterable<E>? elements2) {
+ if (identical(elements1, elements2)) return true;
+ if (elements1 == null || elements2 == null) return false;
+ var it1 = elements1.iterator;
+ var it2 = elements2.iterator;
+ while (true) {
+ var hasNext = it1.moveNext();
+ if (hasNext != it2.moveNext()) return false;
+ if (!hasNext) return true;
+ if (!_elementEquality.equals(it1.current, it2.current)) return false;
+ }
+ }
+
+ @override
+ int hash(Iterable<E>? elements) {
+ if (elements == null) return null.hashCode;
+ // Jenkins's one-at-a-time hash function.
+ var hash = 0;
+ for (var element in elements) {
+ var c = _elementEquality.hash(element);
+ hash = (hash + c) & _hashMask;
+ hash = (hash + (hash << 10)) & _hashMask;
+ hash ^= hash >> 6;
+ }
+ hash = (hash + (hash << 3)) & _hashMask;
+ hash ^= hash >> 11;
+ hash = (hash + (hash << 15)) & _hashMask;
+ return hash;
+ }
+
+ @override
+ bool isValidKey(Object? o) => o is Iterable<E>;
+}
+
+/// Equality on lists.
+///
+/// Two lists are equal if they have the same length and their elements
+/// at each index are equal.
+///
+/// This is effectively the same as [IterableEquality] except that it
+/// accesses elements by index instead of through iteration.
+///
+/// The [equals] and [hash] methods accepts `null` values,
+/// even if the [isValidKey] returns `false` for `null`.
+/// The [hash] of `null` is `null.hashCode`.
+class ListEquality<E> implements Equality<List<E>> {
+ final Equality<E> _elementEquality;
+ const ListEquality(
+ [Equality<E> elementEquality = const DefaultEquality<Never>()])
+ : _elementEquality = elementEquality;
+
+ @override
+ bool equals(List<E>? list1, List<E>? list2) {
+ if (identical(list1, list2)) return true;
+ if (list1 == null || list2 == null) return false;
+ var length = list1.length;
+ if (length != list2.length) return false;
+ for (var i = 0; i < length; i++) {
+ if (!_elementEquality.equals(list1[i], list2[i])) return false;
+ }
+ return true;
+ }
+
+ @override
+ int hash(List<E>? list) {
+ if (list == null) return null.hashCode;
+ // Jenkins's one-at-a-time hash function.
+ // This code is almost identical to the one in IterableEquality, except
+ // that it uses indexing instead of iterating to get the elements.
+ var hash = 0;
+ for (var i = 0; i < list.length; i++) {
+ var c = _elementEquality.hash(list[i]);
+ hash = (hash + c) & _hashMask;
+ hash = (hash + (hash << 10)) & _hashMask;
+ hash ^= hash >> 6;
+ }
+ hash = (hash + (hash << 3)) & _hashMask;
+ hash ^= hash >> 11;
+ hash = (hash + (hash << 15)) & _hashMask;
+ return hash;
+ }
+
+ @override
+ bool isValidKey(Object? o) => o is List<E>;
+}
+
+abstract class _UnorderedEquality<E, T extends Iterable<E>>
+ implements Equality<T> {
+ final Equality<E> _elementEquality;
+
+ const _UnorderedEquality(this._elementEquality);
+
+ @override
+ bool equals(T? elements1, T? elements2) {
+ if (identical(elements1, elements2)) return true;
+ if (elements1 == null || elements2 == null) return false;
+ var counts = HashMap<E, int>(
+ equals: _elementEquality.equals,
+ hashCode: _elementEquality.hash,
+ isValidKey: _elementEquality.isValidKey);
+ var length = 0;
+ for (var e in elements1) {
+ var count = counts[e] ?? 0;
+ counts[e] = count + 1;
+ length++;
+ }
+ for (var e in elements2) {
+ var count = counts[e];
+ if (count == null || count == 0) return false;
+ counts[e] = count - 1;
+ length--;
+ }
+ return length == 0;
+ }
+
+ @override
+ int hash(T? elements) {
+ if (elements == null) return null.hashCode;
+ var hash = 0;
+ for (E element in elements) {
+ var c = _elementEquality.hash(element);
+ hash = (hash + c) & _hashMask;
+ }
+ hash = (hash + (hash << 3)) & _hashMask;
+ hash ^= hash >> 11;
+ hash = (hash + (hash << 15)) & _hashMask;
+ return hash;
+ }
+}
+
+/// Equality of the elements of two iterables without considering order.
+///
+/// Two iterables are considered equal if they have the same number of elements,
+/// and the elements of one set can be paired with the elements
+/// of the other iterable, so that each pair are equal.
+class UnorderedIterableEquality<E> extends _UnorderedEquality<E, Iterable<E>> {
+ const UnorderedIterableEquality(
+ [super.elementEquality = const DefaultEquality<Never>()]);
+
+ @override
+ bool isValidKey(Object? o) => o is Iterable<E>;
+}
+
+/// Equality of sets.
+///
+/// Two sets are considered equal if they have the same number of elements,
+/// and the elements of one set can be paired with the elements
+/// of the other set, so that each pair are equal.
+///
+/// This equality behaves the same as [UnorderedIterableEquality] except that
+/// it expects sets instead of iterables as arguments.
+///
+/// The [equals] and [hash] methods accepts `null` values,
+/// even if the [isValidKey] returns `false` for `null`.
+/// The [hash] of `null` is `null.hashCode`.
+class SetEquality<E> extends _UnorderedEquality<E, Set<E>> {
+ const SetEquality([super.elementEquality = const DefaultEquality<Never>()]);
+
+ @override
+ bool isValidKey(Object? o) => o is Set<E>;
+}
+
+/// Internal class used by [MapEquality].
+///
+/// The class represents a map entry as a single object,
+/// using a combined hashCode and equality of the key and value.
+class _MapEntry {
+ final MapEquality equality;
+ final Object? key;
+ final Object? value;
+ _MapEntry(this.equality, this.key, this.value);
+
+ @override
+ int get hashCode =>
+ (3 * equality._keyEquality.hash(key) +
+ 7 * equality._valueEquality.hash(value)) &
+ _hashMask;
+
+ @override
+ bool operator ==(Object other) =>
+ other is _MapEntry &&
+ equality._keyEquality.equals(key, other.key) &&
+ equality._valueEquality.equals(value, other.value);
+}
+
+/// Equality on maps.
+///
+/// Two maps are equal if they have the same number of entries, and if the
+/// entries of the two maps are pairwise equal on both key and value.
+///
+/// The [equals] and [hash] methods accepts `null` values,
+/// even if the [isValidKey] returns `false` for `null`.
+/// The [hash] of `null` is `null.hashCode`.
+class MapEquality<K, V> implements Equality<Map<K, V>> {
+ final Equality<K> _keyEquality;
+ final Equality<V> _valueEquality;
+ const MapEquality(
+ {Equality<K> keys = const DefaultEquality<Never>(),
+ Equality<V> values = const DefaultEquality<Never>()})
+ : _keyEquality = keys,
+ _valueEquality = values;
+
+ @override
+ bool equals(Map<K, V>? map1, Map<K, V>? map2) {
+ if (identical(map1, map2)) return true;
+ if (map1 == null || map2 == null) return false;
+ var length = map1.length;
+ if (length != map2.length) return false;
+ Map<_MapEntry, int> equalElementCounts = HashMap();
+ var values1 = map1.values.iterator;
+ for (var key in map1.keys) {
+ var value = (values1..moveNext()).current;
+ var entry = _MapEntry(this, key, value);
+ equalElementCounts.update(entry, _addOne, ifAbsent: _one);
+ }
+ final values2 = map2.values.iterator;
+ for (var key in map2.keys) {
+ var value = (values2..moveNext()).current;
+ var entry = _MapEntry(this, key, value);
+ var count = equalElementCounts.update(entry, _subtractOne,
+ ifAbsent: _negativeOne);
+ if (count < 0) return false;
+ }
+ return true;
+ }
+
+ @override
+ int hash(Map<K, V>? map) {
+ if (map == null) return null.hashCode;
+ var hash = 0;
+ var values = map.values.iterator;
+ for (var key in map.keys) {
+ var value = (values..moveNext()).current;
+ var keyHash = _keyEquality.hash(key);
+ var valueHash = _valueEquality.hash(value);
+ hash = (hash + 3 * keyHash + 7 * valueHash) & _hashMask;
+ }
+ hash = (hash + (hash << 3)) & _hashMask;
+ hash ^= hash >> 11;
+ hash = (hash + (hash << 15)) & _hashMask;
+ return hash;
+ }
+
+ @override
+ bool isValidKey(Object? o) => o is Map<K, V>;
+}
+
+/// Combines several equalities into a single equality.
+///
+/// Tries each equality in order, using [Equality.isValidKey], and returns
+/// the result of the first equality that applies to the argument or arguments.
+///
+/// For `equals`, the first equality that matches the first argument is used,
+/// and if the second argument of `equals` is not valid for that equality,
+/// it returns false.
+///
+/// Because the equalities are tried in order, they should generally work on
+/// disjoint types. Otherwise the multi-equality may give inconsistent results
+/// for `equals(e1, e2)` and `equals(e2, e1)`. This can happen if one equality
+/// considers only `e1` a valid key, and not `e2`, but an equality which is
+/// checked later, allows both.
+class MultiEquality<E> implements Equality<E> {
+ final Iterable<Equality<E>> _equalities;
+
+ const MultiEquality(Iterable<Equality<E>> equalities)
+ : _equalities = equalities;
+
+ @override
+ bool equals(E e1, E e2) {
+ for (var eq in _equalities) {
+ if (eq.isValidKey(e1)) return eq.isValidKey(e2) && eq.equals(e1, e2);
+ }
+ return false;
+ }
+
+ @override
+ int hash(E e) {
+ for (var eq in _equalities) {
+ if (eq.isValidKey(e)) return eq.hash(e);
+ }
+ return 0;
+ }
+
+ @override
+ bool isValidKey(Object? o) {
+ for (var eq in _equalities) {
+ if (eq.isValidKey(o)) return true;
+ }
+ return false;
+ }
+}
+
+/// Deep equality on collections.
+///
+/// Recognizes lists, sets, iterables and maps and compares their elements using
+/// deep equality as well.
+///
+/// Non-iterable/map objects are compared using a configurable base equality.
+///
+/// Works in one of two modes: ordered or unordered.
+///
+/// In ordered mode, lists and iterables are required to have equal elements
+/// in the same order. In unordered mode, the order of elements in iterables
+/// and lists are not important.
+///
+/// A list is only equal to another list, likewise for sets and maps. All other
+/// iterables are compared as iterables only.
+class DeepCollectionEquality implements Equality {
+ final Equality _base;
+ final bool _unordered;
+ const DeepCollectionEquality([Equality base = const DefaultEquality<Never>()])
+ : _base = base,
+ _unordered = false;
+
+ /// Creates a deep equality on collections where the order of lists and
+ /// iterables are not considered important. That is, lists and iterables are
+ /// treated as unordered iterables.
+ const DeepCollectionEquality.unordered(
+ [Equality base = const DefaultEquality<Never>()])
+ : _base = base,
+ _unordered = true;
+
+ @override
+ bool equals(Object? e1, Object? e2) {
+ if (e1 is Set) {
+ return e2 is Set && SetEquality(this).equals(e1, e2);
+ }
+ if (e1 is Map) {
+ return e2 is Map && MapEquality(keys: this, values: this).equals(e1, e2);
+ }
+ if (!_unordered) {
+ if (e1 is List) {
+ return e2 is List && ListEquality(this).equals(e1, e2);
+ }
+ if (e1 is Iterable) {
+ return e2 is Iterable && IterableEquality(this).equals(e1, e2);
+ }
+ } else if (e1 is Iterable) {
+ if (e1 is List != e2 is List) return false;
+ return e2 is Iterable && UnorderedIterableEquality(this).equals(e1, e2);
+ }
+ return _base.equals(e1, e2);
+ }
+
+ @override
+ int hash(Object? o) {
+ if (o is Set) return SetEquality(this).hash(o);
+ if (o is Map) return MapEquality(keys: this, values: this).hash(o);
+ if (!_unordered) {
+ if (o is List) return ListEquality(this).hash(o);
+ if (o is Iterable) return IterableEquality(this).hash(o);
+ } else if (o is Iterable) {
+ return UnorderedIterableEquality(this).hash(o);
+ }
+ return _base.hash(o);
+ }
+
+ @override
+ bool isValidKey(Object? o) =>
+ o is Iterable || o is Map || _base.isValidKey(o);
+}
+
+/// String equality that's insensitive to differences in ASCII case.
+///
+/// Non-ASCII characters are compared as-is, with no conversion.
+class CaseInsensitiveEquality implements Equality<String> {
+ const CaseInsensitiveEquality();
+
+ @override
+ bool equals(String string1, String string2) =>
+ equalsIgnoreAsciiCase(string1, string2);
+
+ @override
+ int hash(String string) => hashIgnoreAsciiCase(string);
+
+ @override
+ bool isValidKey(Object? object) => object is String;
+}
+
+int _addOne(int i) => i + 1;
+int _subtractOne(int i) => i - 1;
+int _one() => 1;
+int _negativeOne() => -1;
diff --git a/pkgs/collection/lib/src/equality_map.dart b/pkgs/collection/lib/src/equality_map.dart
new file mode 100644
index 0000000..542977f
--- /dev/null
+++ b/pkgs/collection/lib/src/equality_map.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'equality.dart';
+import 'wrappers.dart';
+
+/// A [Map] whose key equality is determined by an [Equality] object.
+class EqualityMap<K, V> extends DelegatingMap<K, V> {
+ /// Creates a map with equality based on [equality].
+ EqualityMap(Equality<K> equality)
+ : super(LinkedHashMap(
+ equals: equality.equals,
+ hashCode: equality.hash,
+ isValidKey: equality.isValidKey));
+
+ /// Creates a map with equality based on [equality] that contains all
+ /// key-value pairs of [other].
+ ///
+ /// If [other] has multiple keys that are equivalent according to [equality],
+ /// the last one reached during iteration takes precedence.
+ EqualityMap.from(Equality<K> equality, Map<K, V> other)
+ : super(LinkedHashMap(
+ equals: equality.equals,
+ hashCode: equality.hash,
+ isValidKey: equality.isValidKey)) {
+ addAll(other);
+ }
+}
diff --git a/pkgs/collection/lib/src/equality_set.dart b/pkgs/collection/lib/src/equality_set.dart
new file mode 100644
index 0000000..8edbba5
--- /dev/null
+++ b/pkgs/collection/lib/src/equality_set.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'equality.dart';
+import 'wrappers.dart';
+
+/// A [Set] whose key equality is determined by an [Equality] object.
+class EqualitySet<E> extends DelegatingSet<E> {
+ /// Creates a set with equality based on [equality].
+ EqualitySet(Equality<E> equality)
+ : super(LinkedHashSet(
+ equals: equality.equals,
+ hashCode: equality.hash,
+ isValidKey: equality.isValidKey));
+
+ /// Creates a set with equality based on [equality] that contains all
+ /// elements in [other].
+ ///
+ /// If [other] has multiple values that are equivalent according to
+ /// [equality], the first one reached during iteration takes precedence.
+ EqualitySet.from(Equality<E> equality, Iterable<E> other)
+ : super(LinkedHashSet(
+ equals: equality.equals,
+ hashCode: equality.hash,
+ isValidKey: equality.isValidKey)) {
+ addAll(other);
+ }
+}
diff --git a/pkgs/collection/lib/src/functions.dart b/pkgs/collection/lib/src/functions.dart
new file mode 100644
index 0000000..db86574
--- /dev/null
+++ b/pkgs/collection/lib/src/functions.dart
@@ -0,0 +1,213 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+import 'dart:math' as math;
+
+import 'utils.dart';
+
+/// Creates a new map from [map] with new keys and values.
+///
+/// The return values of [key] are used as the keys and the return values of
+/// [value] are used as the values for the new map.
+@Deprecated('Use Map.map or a for loop in a Map literal.')
+Map<K2, V2> mapMap<K1, V1, K2, V2>(Map<K1, V1> map,
+ {K2 Function(K1, V1)? key, V2 Function(K1, V1)? value}) {
+ var keyFn = key ?? (mapKey, _) => mapKey as K2;
+ var valueFn = value ?? (_, mapValue) => mapValue as V2;
+
+ var result = <K2, V2>{};
+ map.forEach((mapKey, mapValue) {
+ result[keyFn(mapKey, mapValue)] = valueFn(mapKey, mapValue);
+ });
+ return result;
+}
+
+/// Returns a new map with all key/value pairs in both [map1] and [map2].
+///
+/// If there are keys that occur in both maps, the [value] function is used to
+/// select the value that goes into the resulting map based on the two original
+/// values. If [value] is omitted, the value from [map2] is used.
+Map<K, V> mergeMaps<K, V>(Map<K, V> map1, Map<K, V> map2,
+ {V Function(V, V)? value}) {
+ var result = Map<K, V>.of(map1);
+ if (value == null) return result..addAll(map2);
+
+ map2.forEach((key, mapValue) {
+ result[key] =
+ result.containsKey(key) ? value(result[key] as V, mapValue) : mapValue;
+ });
+ return result;
+}
+
+/// Associates the elements in [values] by the value returned by [key].
+///
+/// Returns a map from keys computed by [key] to the last value for which [key]
+/// returns that key.
+Map<T, S> lastBy<S, T>(Iterable<S> values, T Function(S) key) =>
+ {for (var element in values) key(element): element};
+
+/// Groups the elements in [values] by the value returned by [key].
+///
+/// Returns a map from keys computed by [key] to a list of all values for which
+/// [key] returns that key. The values appear in the list in the same relative
+/// order as in [values].
+Map<T, List<S>> groupBy<S, T>(Iterable<S> values, T Function(S) key) {
+ var map = <T, List<S>>{};
+ for (var element in values) {
+ (map[key(element)] ??= []).add(element);
+ }
+ return map;
+}
+
+/// Returns the element of [values] for which [orderBy] returns the minimum
+/// value.
+///
+/// The values returned by [orderBy] are compared using the [compare] function.
+/// If [compare] is omitted, values must implement [Comparable]`<T>` and they
+/// are compared using their [Comparable.compareTo].
+///
+/// Returns `null` if [values] is empty.
+S? minBy<S, T>(Iterable<S> values, T Function(S) orderBy,
+ {int Function(T, T)? compare}) {
+ compare ??= defaultCompare;
+
+ S? minValue;
+ T? minOrderBy;
+ for (var element in values) {
+ var elementOrderBy = orderBy(element);
+ if (minOrderBy == null || compare(elementOrderBy, minOrderBy) < 0) {
+ minValue = element;
+ minOrderBy = elementOrderBy;
+ }
+ }
+ return minValue;
+}
+
+/// Returns the element of [values] for which [orderBy] returns the maximum
+/// value.
+///
+/// The values returned by [orderBy] are compared using the [compare] function.
+/// If [compare] is omitted, values must implement [Comparable]`<T>` and they
+/// are compared using their [Comparable.compareTo].
+///
+/// Returns `null` if [values] is empty.
+S? maxBy<S, T>(Iterable<S> values, T Function(S) orderBy,
+ {int Function(T, T)? compare}) {
+ compare ??= defaultCompare;
+
+ S? maxValue;
+ T? maxOrderBy;
+ for (var element in values) {
+ var elementOrderBy = orderBy(element);
+ if (maxOrderBy == null || compare(elementOrderBy, maxOrderBy) > 0) {
+ maxValue = element;
+ maxOrderBy = elementOrderBy;
+ }
+ }
+ return maxValue;
+}
+
+/// Returns the [transitive closure][] of [graph].
+///
+/// [transitive closure]: https://en.wikipedia.org/wiki/Transitive_closure
+///
+/// Interprets [graph] as a directed graph with a vertex for each key and edges
+/// from each key to the values that the key maps to.
+///
+/// Assumes that every vertex in the graph has a key to represent it, even if
+/// that vertex has no outgoing edges. This isn't checked, but if it's not
+/// satisfied, the function may crash or provide unexpected output. For example,
+/// `{"a": ["b"]}` is not valid, but `{"a": ["b"], "b": []}` is.
+@Deprecated('This method will be removed. Consider using package:graphs.')
+Map<T, Set<T>> transitiveClosure<T>(Map<T, Iterable<T>> graph) {
+ // This uses [Warshall's algorithm][], modified not to add a vertex from each
+ // node to itself.
+ //
+ // [Warshall's algorithm]: https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm#Applications_and_generalizations.
+ var result = <T, Set<T>>{};
+ graph.forEach((vertex, edges) {
+ result[vertex] = Set<T>.from(edges);
+ });
+
+ // Lists are faster to iterate than maps, so we create a list since we're
+ // iterating repeatedly.
+ var keys = graph.keys.toList();
+ for (var vertex1 in keys) {
+ for (var vertex2 in keys) {
+ for (var vertex3 in keys) {
+ if (result[vertex2]!.contains(vertex1) &&
+ result[vertex1]!.contains(vertex3)) {
+ result[vertex2]!.add(vertex3);
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+/// Returns the [strongly connected components][] of [graph], in topological
+/// order.
+///
+/// [strongly connected components]: https://en.wikipedia.org/wiki/Strongly_connected_component
+///
+/// Interprets [graph] as a directed graph with a vertex for each key and edges
+/// from each key to the values that the key maps to.
+///
+/// Assumes that every vertex in the graph has a key to represent it, even if
+/// that vertex has no outgoing edges. This isn't checked, but if it's not
+/// satisfied, the function may crash or provide unexpected output. For example,
+/// `{"a": ["b"]}` is not valid, but `{"a": ["b"], "b": []}` is.
+List<Set<T>> stronglyConnectedComponents<T>(Map<T, Iterable<T>> graph) {
+ // This uses [Tarjan's algorithm][].
+ //
+ // [Tarjan's algorithm]: https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
+ var index = 0;
+ var stack = <T?>[];
+ var result = <Set<T>>[];
+
+ // The order of these doesn't matter, so we use un-linked implementations to
+ // avoid unnecessary overhead.
+ var indices = HashMap<T, int>();
+ var lowLinks = HashMap<T, int>();
+ var onStack = HashSet<T>();
+
+ void strongConnect(T vertex) {
+ indices[vertex] = index;
+ lowLinks[vertex] = index;
+ index++;
+
+ stack.add(vertex);
+ onStack.add(vertex);
+
+ for (var successor in graph[vertex]!) {
+ if (!indices.containsKey(successor)) {
+ strongConnect(successor);
+ lowLinks[vertex] = math.min(lowLinks[vertex]!, lowLinks[successor]!);
+ } else if (onStack.contains(successor)) {
+ lowLinks[vertex] = math.min(lowLinks[vertex]!, lowLinks[successor]!);
+ }
+ }
+
+ if (lowLinks[vertex] == indices[vertex]) {
+ var component = <T>{};
+ T? neighbor;
+ do {
+ neighbor = stack.removeLast();
+ onStack.remove(neighbor);
+ component.add(neighbor as T);
+ } while (neighbor != vertex);
+ result.add(component);
+ }
+ }
+
+ for (var vertex in graph.keys) {
+ if (!indices.containsKey(vertex)) strongConnect(vertex);
+ }
+
+ // Tarjan's algorithm produces a reverse-topological sort, so we reverse it to
+ // get a normal topological sort.
+ return result.reversed.toList();
+}
diff --git a/pkgs/collection/lib/src/iterable_extensions.dart b/pkgs/collection/lib/src/iterable_extensions.dart
new file mode 100644
index 0000000..10f4676
--- /dev/null
+++ b/pkgs/collection/lib/src/iterable_extensions.dart
@@ -0,0 +1,1050 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:math' show Random;
+
+import 'algorithms.dart';
+import 'functions.dart' as functions;
+import 'utils.dart';
+
+/// Extensions that apply to all iterables.
+///
+/// These extensions provide direct access to some of the
+/// algorithms expose by this package,
+/// as well as some generally useful convenience methods.
+///
+/// More specialized extension methods that only apply to
+/// iterables with specific element types include those of
+/// [IterableComparableExtension] and [IterableNullableExtension].
+extension IterableExtension<T> on Iterable<T> {
+ /// Selects [count] elements at random from this iterable.
+ ///
+ /// The returned list contains [count] different elements of the iterable.
+ /// If the iterable contains fewer that [count] elements,
+ /// the result will contain all of them, but will be shorter than [count].
+ /// If the same value occurs more than once in the iterable,
+ /// it can also occur more than once in the chosen elements.
+ ///
+ /// Each element of the iterable has the same chance of being chosen.
+ /// The chosen elements are not in any specific order.
+ List<T> sample(int count, [Random? random]) {
+ RangeError.checkNotNegative(count, 'count');
+ var iterator = this.iterator;
+ var chosen = <T>[];
+ random ??= Random();
+ while (chosen.length < count) {
+ if (iterator.moveNext()) {
+ var nextElement = iterator.current;
+ var position = random.nextInt(chosen.length + 1);
+ if (position == chosen.length) {
+ chosen.add(nextElement);
+ } else {
+ chosen.add(chosen[position]);
+ chosen[position] = nextElement;
+ }
+ } else {
+ return chosen;
+ }
+ }
+ var index = count;
+ while (iterator.moveNext()) {
+ index++;
+ var position = random.nextInt(index);
+ if (position < count) chosen[position] = iterator.current;
+ }
+ return chosen;
+ }
+
+ /// The elements that do not satisfy [test].
+ Iterable<T> whereNot(bool Function(T element) test) =>
+ where((element) => !test(element));
+
+ /// Creates a sorted list of the elements of the iterable.
+ ///
+ /// The elements are ordered by the [compare] [Comparator].
+ List<T> sorted(Comparator<T> compare) => [...this]..sort(compare);
+
+ /// Creates a shuffled list of the elements of the iterable.
+ List<T> shuffled([Random? random]) => [...this]..shuffle(random);
+
+ /// Creates a sorted list of the elements of the iterable.
+ ///
+ /// The elements are ordered by the natural ordering of the
+ /// property [keyOf] of the element.
+ List<T> sortedBy<K extends Comparable<K>>(K Function(T element) keyOf) {
+ var elements = [...this];
+ mergeSortBy<T, K>(elements, keyOf, compareComparable);
+ return elements;
+ }
+
+ /// Creates a sorted list of the elements of the iterable.
+ ///
+ /// The elements are ordered by the [compare] [Comparator] of the
+ /// property [keyOf] of the element.
+ List<T> sortedByCompare<K>(
+ K Function(T element) keyOf, Comparator<K> compare) {
+ var elements = [...this];
+ mergeSortBy<T, K>(elements, keyOf, compare);
+ return elements;
+ }
+
+ /// Whether the elements are sorted by the [compare] ordering.
+ ///
+ /// Compares pairs of elements using `compare` to check that
+ /// the elements of this iterable to check
+ /// that earlier elements always compare
+ /// smaller than or equal to later elements.
+ ///
+ /// An single-element or empty iterable is trivially in sorted order.
+ bool isSorted(Comparator<T> compare) {
+ var iterator = this.iterator;
+ if (!iterator.moveNext()) return true;
+ var previousElement = iterator.current;
+ while (iterator.moveNext()) {
+ var element = iterator.current;
+ if (compare(previousElement, element) > 0) return false;
+ previousElement = element;
+ }
+ return true;
+ }
+
+ /// Whether the elements are sorted by their [keyOf] property.
+ ///
+ /// Applies [keyOf] to each element in iteration order,
+ /// then checks whether the results are in non-decreasing [Comparable] order.
+ bool isSortedBy<K extends Comparable<K>>(K Function(T element) keyOf) {
+ var iterator = this.iterator;
+ if (!iterator.moveNext()) return true;
+ var previousKey = keyOf(iterator.current);
+ while (iterator.moveNext()) {
+ var key = keyOf(iterator.current);
+ if (previousKey.compareTo(key) > 0) return false;
+ previousKey = key;
+ }
+ return true;
+ }
+
+ /// Whether the elements are [compare]-sorted by their [keyOf] property.
+ ///
+ /// Applies [keyOf] to each element in iteration order,
+ /// then checks whether the results are in non-decreasing order
+ /// using the [compare] [Comparator]..
+ bool isSortedByCompare<K>(
+ K Function(T element) keyOf, Comparator<K> compare) {
+ var iterator = this.iterator;
+ if (!iterator.moveNext()) return true;
+ var previousKey = keyOf(iterator.current);
+ while (iterator.moveNext()) {
+ var key = keyOf(iterator.current);
+ if (compare(previousKey, key) > 0) return false;
+ previousKey = key;
+ }
+ return true;
+ }
+
+ /// Takes an action for each element.
+ ///
+ /// Calls [action] for each element along with the index in the
+ /// iteration order.
+ void forEachIndexed(void Function(int index, T element) action) {
+ var index = 0;
+ for (var element in this) {
+ action(index++, element);
+ }
+ }
+
+ /// Takes an action for each element as long as desired.
+ ///
+ /// Calls [action] for each element.
+ /// Stops iteration if [action] returns `false`.
+ void forEachWhile(bool Function(T element) action) {
+ for (var element in this) {
+ if (!action(element)) break;
+ }
+ }
+
+ /// Takes an action for each element and index as long as desired.
+ ///
+ /// Calls [action] for each element along with the index in the
+ /// iteration order.
+ /// Stops iteration if [action] returns `false`.
+ void forEachIndexedWhile(bool Function(int index, T element) action) {
+ var index = 0;
+ for (var element in this) {
+ if (!action(index++, element)) break;
+ }
+ }
+
+ /// Maps each element and its index to a new value.
+ Iterable<R> mapIndexed<R>(R Function(int index, T element) convert) sync* {
+ var index = 0;
+ for (var element in this) {
+ yield convert(index++, element);
+ }
+ }
+
+ /// The elements whose value and index satisfies [test].
+ Iterable<T> whereIndexed(bool Function(int index, T element) test) sync* {
+ var index = 0;
+ for (var element in this) {
+ if (test(index++, element)) yield element;
+ }
+ }
+
+ /// The elements whose value and index do not satisfy [test].
+ Iterable<T> whereNotIndexed(bool Function(int index, T element) test) sync* {
+ var index = 0;
+ for (var element in this) {
+ if (!test(index++, element)) yield element;
+ }
+ }
+
+ /// Expands each element and index to a number of elements in a new iterable.
+ Iterable<R> expandIndexed<R>(
+ Iterable<R> Function(int index, T element) expand) sync* {
+ var index = 0;
+ for (var element in this) {
+ yield* expand(index++, element);
+ }
+ }
+
+ /// Combine the elements with each other and the current index.
+ ///
+ /// Calls [combine] for each element except the first.
+ /// The call passes the index of the current element, the result of the
+ /// previous call, or the first element for the first call, and
+ /// the current element.
+ ///
+ /// Returns the result of the last call, or the first element if
+ /// there is only one element.
+ /// There must be at least one element.
+ T reduceIndexed(T Function(int index, T previous, T element) combine) {
+ var iterator = this.iterator;
+ if (!iterator.moveNext()) {
+ throw StateError('no elements');
+ }
+ var index = 1;
+ var result = iterator.current;
+ while (iterator.moveNext()) {
+ result = combine(index++, result, iterator.current);
+ }
+ return result;
+ }
+
+ /// Combine the elements with a value and the current index.
+ ///
+ /// Calls [combine] for each element with the current index,
+ /// the result of the previous call, or [initialValue] for the first element,
+ /// and the current element.
+ ///
+ /// Returns the result of the last call to [combine],
+ /// or [initialValue] if there are no elements.
+ R foldIndexed<R>(
+ R initialValue, R Function(int index, R previous, T element) combine) {
+ var result = initialValue;
+ var index = 0;
+ for (var element in this) {
+ result = combine(index++, result, element);
+ }
+ return result;
+ }
+
+ /// The first element satisfying [test], or `null` if there are none.
+ T? firstWhereOrNull(bool Function(T element) test) {
+ for (var element in this) {
+ if (test(element)) return element;
+ }
+ return null;
+ }
+
+ /// The first element whose value and index satisfies [test].
+ ///
+ /// Returns `null` if there are no element and index satisfying [test].
+ T? firstWhereIndexedOrNull(bool Function(int index, T element) test) {
+ var index = 0;
+ for (var element in this) {
+ if (test(index++, element)) return element;
+ }
+ return null;
+ }
+
+ /// The first element, or `null` if the iterable is empty.
+ T? get firstOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) return iterator.current;
+ return null;
+ }
+
+ /// The last element satisfying [test], or `null` if there are none.
+ T? lastWhereOrNull(bool Function(T element) test) {
+ T? result;
+ for (var element in this) {
+ if (test(element)) result = element;
+ }
+ return result;
+ }
+
+ /// The last element whose index and value satisfies [test].
+ ///
+ /// Returns `null` if no element and index satisfies [test].
+ T? lastWhereIndexedOrNull(bool Function(int index, T element) test) {
+ T? result;
+ var index = 0;
+ for (var element in this) {
+ if (test(index++, element)) result = element;
+ }
+ return result;
+ }
+
+ /// The last element, or `null` if the iterable is empty.
+ T? get lastOrNull {
+ if (isEmpty) return null;
+ return last;
+ }
+
+ /// The single element satisfying [test].
+ ///
+ /// Returns `null` if there are either no elements
+ /// or more than one element satisfying [test].
+ ///
+ /// **Notice**: This behavior differs from [Iterable.singleWhere]
+ /// which always throws if there are more than one match,
+ /// and only calls the `orElse` function on zero matches.
+ T? singleWhereOrNull(bool Function(T element) test) {
+ T? result;
+ var found = false;
+ for (var element in this) {
+ if (test(element)) {
+ if (!found) {
+ result = element;
+ found = true;
+ } else {
+ return null;
+ }
+ }
+ }
+ return result;
+ }
+
+ /// The single element satisfying [test].
+ ///
+ /// Returns `null` if there are either none
+ /// or more than one element and index satisfying [test].
+ T? singleWhereIndexedOrNull(bool Function(int index, T element) test) {
+ T? result;
+ var found = false;
+ var index = 0;
+ for (var element in this) {
+ if (test(index++, element)) {
+ if (!found) {
+ result = element;
+ found = true;
+ } else {
+ return null;
+ }
+ }
+ }
+ return result;
+ }
+
+ /// The single element of the iterable, or `null`.
+ ///
+ /// The value is `null` if the iterable is empty
+ /// or it contains more than one element.
+ T? get singleOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var result = iterator.current;
+ if (!iterator.moveNext()) {
+ return result;
+ }
+ }
+ return null;
+ }
+
+ /// The [index]th element, or `null` if there is no such element.
+ ///
+ /// Returns the element at position [index] of this iterable,
+ /// just like [elementAt], if this iterable has such an element.
+ /// If this iterable does not have enough elements to have one with the given
+ /// [index], the `null` value is returned, unlike [elementAt] which throws
+ /// instead.
+ ///
+ /// The [index] must not be negative.
+ T? elementAtOrNull(int index) => skip(index).firstOrNull;
+
+ /// Associates the elements in `this` by the value returned by [key].
+ ///
+ /// Returns a map from keys computed by [key] to the last value for which
+ /// [key] returns that key.
+ Map<K, T> lastBy<K>(K Function(T) key) => functions.lastBy(this, key);
+
+ /// Groups elements by [keyOf] then folds the elements in each group.
+ ///
+ /// A key is found for each element using [keyOf].
+ /// Then the elements with the same key are all folded using [combine].
+ /// The first call to [combine] for a particular key receives `null` as
+ /// the previous value, the remaining ones receive the result of the previous
+ /// call.
+ ///
+ /// Can be used to _group_ elements into arbitrary collections.
+ /// For example [groupSetsBy] could be written as:
+ /// ```dart
+ /// iterable.groupFoldBy(keyOf,
+ /// (Set<T>? previous, T element) => (previous ?? <T>{})..add(element));
+ /// ````
+ Map<K, G> groupFoldBy<K, G>(
+ K Function(T element) keyOf, G Function(G? previous, T element) combine) {
+ var result = <K, G>{};
+ for (var element in this) {
+ var key = keyOf(element);
+ result[key] = combine(result[key], element);
+ }
+ return result;
+ }
+
+ /// Groups elements into sets by [keyOf].
+ Map<K, Set<T>> groupSetsBy<K>(K Function(T element) keyOf) {
+ var result = <K, Set<T>>{};
+ for (var element in this) {
+ (result[keyOf(element)] ??= <T>{}).add(element);
+ }
+ return result;
+ }
+
+ /// Groups elements into lists by [keyOf].
+ Map<K, List<T>> groupListsBy<K>(K Function(T element) keyOf) {
+ var result = <K, List<T>>{};
+ for (var element in this) {
+ (result[keyOf(element)] ??= []).add(element);
+ }
+ return result;
+ }
+
+ /// Splits the elements into chunks before some elements.
+ ///
+ /// Each element except the first is checked using [test]
+ /// for whether it should be the first element in a new chunk.
+ /// If so, the elements since the previous chunk-starting element
+ /// are emitted as a list.
+ /// Any remaining elements are emitted at the end.
+ ///
+ /// Example:
+ /// Example:
+ /// ```dart
+ /// var parts = [1, 0, 2, 1, 5, 7, 6, 8, 9].splitBefore(isPrime);
+ /// print(parts); // ([1, 0], [2, 1], [5], [7, 6, 8, 9])
+ /// ```
+ Iterable<List<T>> splitBefore(bool Function(T element) test) =>
+ splitBeforeIndexed((_, element) => test(element));
+
+ /// Splits the elements into chunks after some elements.
+ ///
+ /// Each element is checked using [test] for whether it should end a chunk.
+ /// If so, the elements following the previous chunk-ending element,
+ /// including the element that satisfied [test],
+ /// are emitted as a list.
+ /// Any remaining elements are emitted at the end,
+ /// whether the last element should be split after or not.
+ ///
+ /// Example:
+ /// ```dart
+ /// var parts = [1, 0, 2, 1, 5, 7, 6, 8, 9].splitAfter(isPrime);
+ /// print(parts); // ([1, 0, 2], [1, 5], [7], [6, 8, 9])
+ /// ```
+ Iterable<List<T>> splitAfter(bool Function(T element) test) =>
+ splitAfterIndexed((_, element) => test(element));
+
+ /// Splits the elements into chunks between some elements.
+ ///
+ /// Each pair of adjacent elements are checked using [test]
+ /// for whether a chunk should end between them.
+ /// If so, the elements since the previous chunk-splitting elements
+ /// are emitted as a list.
+ /// Any remaining elements are emitted at the end.
+ ///
+ /// Example:
+ /// ```dart
+ /// var parts = [1, 0, 2, 1, 5, 7, 6, 8, 9].splitBetween((v1, v2) => v1 > v2);
+ /// print(parts); // ([1], [0, 2], [1, 5, 7], [6, 8, 9])
+ /// ```
+ Iterable<List<T>> splitBetween(bool Function(T first, T second) test) =>
+ splitBetweenIndexed((_, first, second) => test(first, second));
+
+ /// Splits the elements into chunks before some elements and indices.
+ ///
+ /// Each element and index except the first is checked using [test]
+ /// for whether it should start a new chunk.
+ /// If so, the elements since the previous chunk-starting element
+ /// are emitted as a list.
+ /// Any remaining elements are emitted at the end.
+ ///
+ /// Example:
+ /// ```dart
+ /// var parts = [1, 0, 2, 1, 5, 7, 6, 8, 9]
+ /// .splitBeforeIndexed((i, v) => i < v);
+ /// print(parts); // ([1], [0, 2], [1, 5, 7], [6, 8, 9])
+ /// ```
+ Iterable<List<T>> splitBeforeIndexed(
+ bool Function(int index, T element) test) sync* {
+ var iterator = this.iterator;
+ if (!iterator.moveNext()) {
+ return;
+ }
+ var index = 1;
+ var chunk = [iterator.current];
+ while (iterator.moveNext()) {
+ var element = iterator.current;
+ if (test(index++, element)) {
+ yield chunk;
+ chunk = [];
+ }
+ chunk.add(element);
+ }
+ yield chunk;
+ }
+
+ /// Splits the elements into chunks after some elements and indices.
+ ///
+ /// Each element and index is checked using [test]
+ /// for whether it should end the current chunk.
+ /// If so, the elements since the previous chunk-ending element,
+ /// including the element that satisfied [test],
+ /// are emitted as a list.
+ /// Any remaining elements are emitted at the end, whether the last
+ /// element should be split after or not.
+ ///
+ /// Example:
+ /// ```dart
+ /// var parts = [1, 0, 2, 1, 5, 7, 6, 8, 9]
+ /// .splitAfterIndexed((i, v) => i < v);
+ /// print(parts); // ([1, 0], [2, 1], [5, 7, 6], [8, 9])
+ /// ```
+ Iterable<List<T>> splitAfterIndexed(
+ bool Function(int index, T element) test) sync* {
+ var index = 0;
+ List<T>? chunk;
+ for (var element in this) {
+ (chunk ??= []).add(element);
+ if (test(index++, element)) {
+ yield chunk;
+ chunk = null;
+ }
+ }
+ if (chunk != null) yield chunk;
+ }
+
+ /// Splits the elements into chunks between some elements and indices.
+ ///
+ /// Each pair of adjacent elements and the index of the latter are
+ /// checked using [test] for whether a chunk should end between them.
+ /// If so, the elements since the previous chunk-splitting elements
+ /// are emitted as a list.
+ /// Any remaining elements are emitted at the end.
+ ///
+ /// Example:
+ /// ```dart
+ /// var parts = [1, 0, 2, 1, 5, 7, 6, 8, 9]
+ /// .splitBetweenIndexed((i, v1, v2) => v1 > v2);
+ /// print(parts); // ([1], [0, 2], [1, 5, 7], [6, 8, 9])
+ /// ```
+ Iterable<List<T>> splitBetweenIndexed(
+ bool Function(int index, T first, T second) test) sync* {
+ var iterator = this.iterator;
+ if (!iterator.moveNext()) return;
+ var previous = iterator.current;
+ var chunk = <T>[previous];
+ var index = 1;
+ while (iterator.moveNext()) {
+ var element = iterator.current;
+ if (test(index++, previous, element)) {
+ yield chunk;
+ chunk = [];
+ }
+ chunk.add(element);
+ previous = element;
+ }
+ yield chunk;
+ }
+
+ /// Whether no element satisfies [test].
+ ///
+ /// Returns true if no element satisfies [test],
+ /// and false if at least one does.
+ ///
+ /// Equivalent to `iterable.every((x) => !test(x))` or
+ /// `!iterable.any(test)`.
+ bool none(bool Function(T) test) {
+ for (var element in this) {
+ if (test(element)) return false;
+ }
+ return true;
+ }
+
+ /// Contiguous slices of `this` with the given [length].
+ ///
+ /// Each slice is [length] elements long, except for the last one which may be
+ /// shorter if `this` contains too few elements. Each slice begins after the
+ /// last one ends. The [length] must be greater than zero.
+ ///
+ /// For example, `{1, 2, 3, 4, 5}.slices(2)` returns `([1, 2], [3, 4], [5])`.
+ Iterable<List<T>> slices(int length) sync* {
+ if (length < 1) throw RangeError.range(length, 1, null, 'length');
+
+ var iterator = this.iterator;
+ while (iterator.moveNext()) {
+ var slice = [iterator.current];
+ for (var i = 1; i < length && iterator.moveNext(); i++) {
+ slice.add(iterator.current);
+ }
+ yield slice;
+ }
+ }
+}
+
+/// Extensions that apply to iterables with a nullable element type.
+extension IterableNullableExtension<T extends Object> on Iterable<T?> {
+ /// The non-`null` elements of this `Iterable`.
+ ///
+ /// Returns an iterable which emits all the non-`null` elements
+ /// of this iterable, in their original iteration order.
+ ///
+ /// For an `Iterable<X?>`, this method is equivalent to `.whereType<X>()`.
+ @Deprecated('Use .nonNulls instead.')
+ Iterable<T> whereNotNull() sync* {
+ for (var element in this) {
+ if (element != null) yield element;
+ }
+ }
+}
+
+/// Extensions that apply to iterables of numbers.
+///
+/// Specialized version of some extensions of [IterableComparableExtension]
+/// since doubles require special handling of [double.nan].
+extension IterableNumberExtension on Iterable<num> {
+ /// A minimal element of the iterable, or `null` it the iterable is empty.
+ ///
+ /// If any element is [NaN](double.nan), the result is NaN.
+ num? get minOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var value = iterator.current;
+ if (value.isNaN) {
+ return value;
+ }
+ while (iterator.moveNext()) {
+ var newValue = iterator.current;
+ if (newValue.isNaN) {
+ return newValue;
+ }
+ if (newValue < value) {
+ value = newValue;
+ }
+ }
+ return value;
+ }
+ return null;
+ }
+
+ /// A minimal element of the iterable.
+ ///
+ /// If any element is [NaN](double.nan), the result is NaN.
+ ///
+ /// The iterable must not be empty.
+ num get min => minOrNull ?? (throw StateError('No element'));
+
+ /// A maximal element of the iterable, or `null` if the iterable is empty.
+ ///
+ /// If any element is [NaN](double.nan), the result is NaN.
+ num? get maxOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var value = iterator.current;
+ if (value.isNaN) {
+ return value;
+ }
+ while (iterator.moveNext()) {
+ var newValue = iterator.current;
+ if (newValue.isNaN) {
+ return newValue;
+ }
+ if (newValue > value) {
+ value = newValue;
+ }
+ }
+ return value;
+ }
+ return null;
+ }
+
+ /// A maximal element of the iterable.
+ ///
+ /// If any element is [NaN](double.nan), the result is NaN.
+ ///
+ /// The iterable must not be empty.
+ num get max => maxOrNull ?? (throw StateError('No element'));
+
+ /// The sum of the elements.
+ ///
+ /// The sum is zero if the iterable is empty.
+ num get sum {
+ num result = 0;
+ for (var value in this) {
+ result += value;
+ }
+ return result;
+ }
+
+ /// The arithmetic mean of the elements of a non-empty iterable.
+ ///
+ /// The arithmetic mean is the sum of the elements
+ /// divided by the number of elements.
+ ///
+ /// The iterable must not be empty.
+ double get average {
+ var result = 0.0;
+ var count = 0;
+ for (var value in this) {
+ count += 1;
+ result += (value - result) / count;
+ }
+ if (count == 0) throw StateError('No elements');
+ return result;
+ }
+}
+
+/// Extension on iterables of integers.
+///
+/// Specialized version of some extensions of [IterableNumberExtension] or
+/// [IterableComparableExtension] since integers are only `Comparable<num>`.
+extension IterableIntegerExtension on Iterable<int> {
+ /// A minimal element of the iterable, or `null` it the iterable is empty.
+ int? get minOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var value = iterator.current;
+ while (iterator.moveNext()) {
+ var newValue = iterator.current;
+ if (newValue < value) {
+ value = newValue;
+ }
+ }
+ return value;
+ }
+ return null;
+ }
+
+ /// A minimal element of the iterable.
+ ///
+ /// The iterable must not be empty.
+ int get min => minOrNull ?? (throw StateError('No element'));
+
+ /// A maximal element of the iterable, or `null` if the iterable is empty.
+ int? get maxOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var value = iterator.current;
+ while (iterator.moveNext()) {
+ var newValue = iterator.current;
+ if (newValue > value) {
+ value = newValue;
+ }
+ }
+ return value;
+ }
+ return null;
+ }
+
+ /// A maximal element of the iterable.
+ ///
+ /// The iterable must not be empty.
+ int get max => maxOrNull ?? (throw StateError('No element'));
+
+ /// The sum of the elements.
+ ///
+ /// The sum is zero if the iterable is empty.
+ int get sum {
+ var result = 0;
+ for (var value in this) {
+ result += value;
+ }
+ return result;
+ }
+
+ /// The arithmetic mean of the elements of a non-empty iterable.
+ ///
+ /// The arithmetic mean is the sum of the elements
+ /// divided by the number of elements.
+ /// This method is specialized for integers,
+ /// and may give a different result than [IterableNumberExtension.average]
+ /// for the same values, because the the number algorithm
+ /// converts all numbers to doubles.
+ ///
+ /// The iterable must not be empty.
+ double get average {
+ var average = 0;
+ var remainder = 0;
+ var count = 0;
+ for (var value in this) {
+ // Invariant: Sum of values so far = average * count + remainder.
+ // (Unless overflow has occurred).
+ count += 1;
+ var delta = value - average + remainder;
+ average += delta ~/ count;
+ remainder = delta.remainder(count);
+ }
+ if (count == 0) throw StateError('No elements');
+ return average + remainder / count;
+ }
+}
+
+/// Extension on iterables of double.
+///
+/// Specialized version of some extensions of [IterableNumberExtension] or
+/// [IterableComparableExtension] since doubles are only `Comparable<num>`.
+extension IterableDoubleExtension on Iterable<double> {
+ /// A minimal element of the iterable, or `null` it the iterable is empty.
+ ///
+ /// If any element is [NaN](double.nan), the result is NaN.
+ double? get minOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var value = iterator.current;
+ if (value.isNaN) {
+ return value;
+ }
+ while (iterator.moveNext()) {
+ var newValue = iterator.current;
+ if (newValue.isNaN) {
+ return newValue;
+ }
+ if (newValue < value) {
+ value = newValue;
+ }
+ }
+ return value;
+ }
+ return null;
+ }
+
+ /// A minimal element of the iterable.
+ ///
+ /// If any element is [NaN](double.nan), the result is NaN.
+ ///
+ /// The iterable must not be empty.
+ double get min => minOrNull ?? (throw StateError('No element'));
+
+ /// A maximal element of the iterable, or `null` if the iterable is empty.
+ ///
+ /// If any element is [NaN](double.nan), the result is NaN.
+ double? get maxOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var value = iterator.current;
+ if (value.isNaN) {
+ return value;
+ }
+ while (iterator.moveNext()) {
+ var newValue = iterator.current;
+ if (newValue.isNaN) {
+ return newValue;
+ }
+ if (newValue > value) {
+ value = newValue;
+ }
+ }
+ return value;
+ }
+ return null;
+ }
+
+ /// A maximal element of the iterable.
+ ///
+ /// If any element is [NaN](double.nan), the result is NaN.
+ ///
+ /// The iterable must not be empty.
+ double get max => maxOrNull ?? (throw StateError('No element'));
+
+ /// The sum of the elements.
+ ///
+ /// The sum is zero if the iterable is empty.
+ double get sum {
+ var result = 0.0;
+ for (var value in this) {
+ result += value;
+ }
+ return result;
+ }
+}
+
+/// Extensions on iterables whose elements are also iterables.
+extension IterableIterableExtension<T> on Iterable<Iterable<T>> {
+ /// The sequential elements of each iterable in this iterable.
+ ///
+ /// Iterates the elements of this iterable.
+ /// For each one, which is itself an iterable,
+ /// all the elements of that are emitted
+ /// on the returned iterable, before moving on to the next element.
+ Iterable<T> get flattened sync* {
+ for (var elements in this) {
+ yield* elements;
+ }
+ }
+
+ /// The sequential elements of each iterable in this iterable.
+ ///
+ /// Iterates the elements of this iterable.
+ /// For each one, which is itself an iterable,
+ /// all the elements of that are added
+ /// to the returned list, before moving on to the next element.
+ List<T> get flattenedToList => [
+ for (final elements in this) ...elements,
+ ];
+
+ /// The unique sequential elements of each iterable in this iterable.
+ ///
+ /// Iterates the elements of this iterable.
+ /// For each one, which is itself an iterable,
+ /// all the elements of that are added
+ /// to the returned set, before moving on to the next element.
+ Set<T> get flattenedToSet => {
+ for (final elements in this) ...elements,
+ };
+}
+
+/// Extension on iterables of [MapEntry].
+///
+/// An [Iterable<MapEntry>] is obtained using [Map.entries]. These extensions
+/// facilitates working directly on the entries of a [Map].
+extension IterableMapEntryExtension<K, V> on Iterable<MapEntry<K, V>> {
+ /// The elements whose [MapEntry.key] values satisfy [test].
+ ///
+ /// The resulting iterable is lazily computing its elements
+ /// based on the elements this iterable.
+ Iterable<MapEntry<K, V>> whereKey(bool Function(K) test) =>
+ where((e) => test(e.key));
+
+ /// The elements whose [MapEntry.value] values satisfy [test].
+ ///
+ /// The resulting iterable is lazily computing its elements
+ /// based on the elements this iterable.
+ Iterable<MapEntry<K, V>> whereValue(bool Function(V) test) =>
+ where((e) => test(e.value));
+
+ /// A new lazy [Iterable] of the [MapEntry.key]s of these entries.
+ ///
+ /// Do not use this getter as `map.entries.keys`, just use `map.keys`
+ /// directly.
+ Iterable<K> get keys => map((e) => e.key);
+
+ /// A new lazy [Iterable] of the [MapEntry.value]s of these entries.
+ ///
+ /// Do not use this getter as `map.entries.values`, just use `map.values`
+ /// directly.
+ Iterable<V> get values => map((e) => e.value);
+
+ /// Create a [Map<K, V>] from all elements.
+ ///
+ /// This is a short-hand for [Map.fromEntries].
+ Map<K, V> toMap() => Map<K, V>.fromEntries(this);
+}
+
+/// Extensions that apply to iterables of [Comparable] elements.
+///
+/// These operations can assume that the elements have a natural ordering,
+/// and can therefore omit, or make it optional, for the user to provide
+/// a [Comparator].
+extension IterableComparableExtension<T extends Comparable<T>> on Iterable<T> {
+ /// A minimal element of the iterable, or `null` it the iterable is empty.
+ T? get minOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var value = iterator.current;
+ while (iterator.moveNext()) {
+ var newValue = iterator.current;
+ if (value.compareTo(newValue) > 0) {
+ value = newValue;
+ }
+ }
+ return value;
+ }
+ return null;
+ }
+
+ /// A minimal element of the iterable.
+ ///
+ /// The iterable must not be empty.
+ T get min => minOrNull ?? (throw StateError('No element'));
+
+ /// A maximal element of the iterable, or `null` if the iterable is empty.
+ T? get maxOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var value = iterator.current;
+ while (iterator.moveNext()) {
+ var newValue = iterator.current;
+ if (value.compareTo(newValue) < 0) {
+ value = newValue;
+ }
+ }
+ return value;
+ }
+ return null;
+ }
+
+ /// A maximal element of the iterable.
+ ///
+ /// The iterable must not be empty.
+ T get max => maxOrNull ?? (throw StateError('No element'));
+
+ /// Creates a sorted list of the elements of the iterable.
+ ///
+ /// If the [compare] function is not supplied, the sorting uses the
+ /// natural [Comparable] ordering of the elements.
+ List<T> sorted([Comparator<T>? compare]) => [...this]..sort(compare);
+
+ /// Whether the elements are sorted by the [compare] ordering.
+ ///
+ /// If [compare] is omitted, it defaults to comparing the
+ /// elements using their natural [Comparable] ordering.
+ bool isSorted([Comparator<T>? compare]) {
+ if (compare != null) {
+ return IterableExtension(this).isSorted(compare);
+ }
+ var iterator = this.iterator;
+ if (!iterator.moveNext()) return true;
+ var previousElement = iterator.current;
+ while (iterator.moveNext()) {
+ var element = iterator.current;
+ if (previousElement.compareTo(element) > 0) return false;
+ previousElement = element;
+ }
+ return true;
+ }
+}
+
+/// Extensions on comparator functions.
+extension ComparatorExtension<T> on Comparator<T> {
+ /// The inverse ordering of this comparator.
+ Comparator<T> get inverse => (T a, T b) => this(b, a);
+
+ /// Makes a comparator on [R] values using this comparator.
+ ///
+ /// Compares [R] values by comparing their [keyOf] value
+ /// using this comparator.
+ Comparator<R> compareBy<R>(T Function(R) keyOf) =>
+ (R a, R b) => this(keyOf(a), keyOf(b));
+
+ /// Combine comparators sequentially.
+ ///
+ /// Creates a comparator which orders elements the same way as
+ /// this comparator, except that when two elements are considered
+ /// equal, the [tieBreaker] comparator is used instead.
+ Comparator<T> then(Comparator<T> tieBreaker) => (T a, T b) {
+ var result = this(a, b);
+ if (result == 0) result = tieBreaker(a, b);
+ return result;
+ };
+}
diff --git a/pkgs/collection/lib/src/iterable_zip.dart b/pkgs/collection/lib/src/iterable_zip.dart
new file mode 100644
index 0000000..9671eaf
--- /dev/null
+++ b/pkgs/collection/lib/src/iterable_zip.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+/// Iterable that iterates over lists of values from other iterables.
+///
+/// When [iterator] is read, an [Iterator] is created for each [Iterable] in
+/// the [Iterable] passed to the constructor.
+///
+/// As long as all these iterators have a next value, those next values are
+/// combined into a single list, which becomes the next value of this
+/// [Iterable]'s [Iterator]. As soon as any of the iterators run out,
+/// the zipped iterator also stops.
+class IterableZip<T> extends IterableBase<List<T>> {
+ final Iterable<Iterable<T>> _iterables;
+
+ IterableZip(Iterable<Iterable<T>> iterables) : _iterables = iterables;
+
+ /// Returns an iterator that combines values of the iterables' iterators
+ /// as long as they all have values.
+ @override
+ Iterator<List<T>> get iterator {
+ var iterators = _iterables.map((x) => x.iterator).toList(growable: false);
+ return _IteratorZip<T>(iterators);
+ }
+}
+
+class _IteratorZip<T> implements Iterator<List<T>> {
+ final List<Iterator<T>> _iterators;
+ List<T>? _current;
+
+ _IteratorZip(List<Iterator<T>> iterators) : _iterators = iterators;
+
+ @override
+ bool moveNext() {
+ if (_iterators.isEmpty) return false;
+ for (var i = 0; i < _iterators.length; i++) {
+ if (!_iterators[i].moveNext()) {
+ _current = null;
+ return false;
+ }
+ }
+ _current = List.generate(_iterators.length, (i) => _iterators[i].current,
+ growable: false);
+ return true;
+ }
+
+ @override
+ List<T> get current => _current ?? (throw StateError('No element'));
+}
diff --git a/pkgs/collection/lib/src/list_extensions.dart b/pkgs/collection/lib/src/list_extensions.dart
new file mode 100644
index 0000000..40fa8af
--- /dev/null
+++ b/pkgs/collection/lib/src/list_extensions.dart
@@ -0,0 +1,518 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Extension methods on common collection types.
+import 'dart:collection';
+import 'dart:math';
+
+import 'algorithms.dart';
+import 'algorithms.dart' as algorithms;
+import 'equality.dart';
+import 'utils.dart';
+
+/// Various extensions on lists of arbitrary elements.
+extension ListExtensions<E> on List<E> {
+ /// Returns the index of [element] in this sorted list.
+ ///
+ /// Uses binary search to find the location of [element].
+ /// This takes on the order of `log(n)` comparisons.
+ /// The list *must* be sorted according to [compare],
+ /// otherwise the result is unspecified
+ ///
+ /// Returns -1 if [element] does not occur in this list.
+ int binarySearch(E element, int Function(E, E) compare) =>
+ algorithms.binarySearchBy<E, E>(this, identity, compare, element);
+
+ /// Returns the index of [element] in this sorted list.
+ ///
+ /// Uses binary search to find the location of [element].
+ /// This takes on the order of `log(n)` comparisons.
+ /// The list *must* be sorted according to [compare] on the [keyOf] of
+ /// elements, otherwise the result is unspecified.
+ ///
+ /// Returns -1 if [element] does not occur in this list.
+ ///
+ /// If [start] and [end] are supplied, only the list range from [start] to
+ /// [end] is searched, and only that range needs to be sorted.
+ int binarySearchByCompare<K>(
+ E element, K Function(E element) keyOf, int Function(K, K) compare,
+ [int start = 0, int? end]) =>
+ algorithms.binarySearchBy<E, K>(
+ this, keyOf, compare, element, start, end);
+
+ /// Returns the index of [element] in this sorted list.
+ ///
+ /// Uses binary search to find the location of [element].
+ /// This takes on the order of `log(n)` comparisons.
+ /// The list *must* be sorted according to the natural ordering of
+ /// the [keyOf] of elements, otherwise the result is unspecified.
+ ///
+ /// Returns -1 if [element] does not occur in this list.
+ ///
+ /// If [start] and [end] are supplied, only the list range from [start] to
+ /// [end] is searched, and only that range needs to be sorted.
+ int binarySearchBy<K extends Comparable<K>>(
+ E element, K Function(E element) keyOf, [int start = 0, int? end]) =>
+ algorithms.binarySearchBy<E, K>(
+ this, keyOf, (a, b) => a.compareTo(b), element, start, end);
+
+ /// Returns the index where [element] should be in this sorted list.
+ ///
+ /// Uses binary search to find the location of [element].
+ /// This takes on the order of `log(n)` comparisons.
+ /// The list *must* be sorted according to [compare],
+ /// otherwise the result is unspecified.
+ ///
+ /// If [element] is in the list, its index is returned,
+ /// otherwise returns the first position where adding [element]
+ /// would keep the list sorted. This may be the [length] of
+ /// the list if all elements of the list compare less than
+ /// [element].
+ int lowerBound(E element, int Function(E, E) compare) =>
+ algorithms.lowerBoundBy<E, E>(this, identity, compare, element);
+
+ /// Returns the index where [element] should be in this sorted list.
+ ///
+ /// Uses binary search to find the location of [element].
+ /// This takes on the order of `log(n)` comparisons.
+ /// The list *must* be sorted according to [compare] of
+ /// the [keyOf] of the elements, otherwise the result is unspecified.
+ ///
+ /// If [element] is in the list, its index is returned,
+ /// otherwise returns the first position where adding [element]
+ /// would keep the list sorted. This may be the [length] of
+ /// the list if all elements of the list compare less than
+ /// [element].
+ ///
+ /// If [start] and [end] are supplied, only that range is searched,
+ /// and only that range need to be sorted.
+ int lowerBoundByCompare<K>(
+ E element, K Function(E) keyOf, int Function(K, K) compare,
+ [int start = 0, int? end]) =>
+ algorithms.lowerBoundBy(this, keyOf, compare, element, start, end);
+
+ /// Returns the index where [element] should be in this sorted list.
+ ///
+ /// Uses binary search to find the location of [element].
+ /// This takes on the order of `log(n)` comparisons.
+ /// The list *must* be sorted according to the
+ /// natural ordering of the [keyOf] of the elements,
+ /// otherwise the result is unspecified.
+ ///
+ /// If [element] is in the list, its index is returned,
+ /// otherwise returns the first position where adding [element]
+ /// would keep the list sorted. This may be the [length] of
+ /// the list if all elements of the list compare less than
+ /// [element].
+ ///
+ /// If [start] and [end] are supplied, only that range is searched,
+ /// and only that range need to be sorted.
+ int lowerBoundBy<K extends Comparable<K>>(E element, K Function(E) keyOf,
+ [int start = 0, int? end]) =>
+ algorithms.lowerBoundBy<E, K>(
+ this, keyOf, compareComparable, element, start, end);
+
+ /// Takes an action for each element.
+ ///
+ /// Calls [action] for each element along with the index in the
+ /// iteration order.
+ void forEachIndexed(void Function(int index, E element) action) {
+ for (var index = 0; index < length; index++) {
+ action(index, this[index]);
+ }
+ }
+
+ /// Takes an action for each element as long as desired.
+ ///
+ /// Calls [action] for each element.
+ /// Stops iteration if [action] returns `false`.
+ void forEachWhile(bool Function(E element) action) {
+ for (var index = 0; index < length; index++) {
+ if (!action(this[index])) break;
+ }
+ }
+
+ /// Takes an action for each element and index as long as desired.
+ ///
+ /// Calls [action] for each element along with the index in the
+ /// iteration order.
+ /// Stops iteration if [action] returns `false`.
+ void forEachIndexedWhile(bool Function(int index, E element) action) {
+ for (var index = 0; index < length; index++) {
+ if (!action(index, this[index])) break;
+ }
+ }
+
+ /// Maps each element and its index to a new value.
+ Iterable<R> mapIndexed<R>(R Function(int index, E element) convert) sync* {
+ for (var index = 0; index < length; index++) {
+ yield convert(index, this[index]);
+ }
+ }
+
+ /// The elements whose value and index satisfies [test].
+ Iterable<E> whereIndexed(bool Function(int index, E element) test) sync* {
+ for (var index = 0; index < length; index++) {
+ var element = this[index];
+ if (test(index, element)) yield element;
+ }
+ }
+
+ /// The elements whose value and index do not satisfy [test].
+ Iterable<E> whereNotIndexed(bool Function(int index, E element) test) sync* {
+ for (var index = 0; index < length; index++) {
+ var element = this[index];
+ if (!test(index, element)) yield element;
+ }
+ }
+
+ /// Expands each element and index to a number of elements in a new iterable.
+ ///
+ /// Like [Iterable.expand] except that the callback function is supplied with
+ /// both the index and the element.
+ Iterable<R> expandIndexed<R>(
+ Iterable<R> Function(int index, E element) expand) sync* {
+ for (var index = 0; index < length; index++) {
+ yield* expand(index, this[index]);
+ }
+ }
+
+ /// Sort a range of elements by [compare].
+ void sortRange(int start, int end, int Function(E a, E b) compare) {
+ quickSortBy<E, E>(this, identity, compare, start, end);
+ }
+
+ /// Sorts elements by the [compare] of their [keyOf] property.
+ ///
+ /// Sorts elements from [start] to [end], defaulting to the entire list.
+ void sortByCompare<K>(
+ K Function(E element) keyOf, int Function(K a, K b) compare,
+ [int start = 0, int? end]) {
+ quickSortBy(this, keyOf, compare, start, end);
+ }
+
+ /// Sorts elements by the natural order of their [keyOf] property.
+ ///
+ /// Sorts elements from [start] to [end], defaulting to the entire list.
+ void sortBy<K extends Comparable<K>>(K Function(E element) keyOf,
+ [int start = 0, int? end]) {
+ quickSortBy<E, K>(this, keyOf, compareComparable, start, end);
+ }
+
+ /// Shuffle a range of elements.
+ void shuffleRange(int start, int end, [Random? random]) {
+ RangeError.checkValidRange(start, end, length);
+ shuffle(this, start, end, random);
+ }
+
+ /// Reverses the elements in a range of the list.
+ void reverseRange(int start, int end) {
+ RangeError.checkValidRange(start, end, length);
+ while (start < --end) {
+ var tmp = this[start];
+ this[start] = this[end];
+ this[end] = tmp;
+ start += 1;
+ }
+ }
+
+ /// Swaps two elements of this list.
+ void swap(int index1, int index2) {
+ RangeError.checkValidIndex(index1, this, 'index1');
+ RangeError.checkValidIndex(index2, this, 'index2');
+ var tmp = this[index1];
+ this[index1] = this[index2];
+ this[index2] = tmp;
+ }
+
+ /// A fixed length view of a range of this list.
+ ///
+ /// The view is backed by this list, which must not change its length while
+ /// the view is being used.
+ ///
+ /// The view can be used to perform specific whole-list
+ /// actions on a part of the list.
+ /// For example, to see if a list contains more than one
+ /// "marker" element, you can do:
+ /// ```dart
+ /// someList.slice(someList.indexOf(marker) + 1).contains(marker)
+ /// ```
+ ListSlice<E> slice(int start, [int? end]) {
+ end = RangeError.checkValidRange(start, end, length);
+ var self = this;
+ if (self is ListSlice<E>) return self.slice(start, end);
+ return ListSlice<E>(this, start, end);
+ }
+
+ /// Whether [other] has the same elements as this list.
+ ///
+ /// Returns true iff [other] has the same [length]
+ /// as this list, and the elements of this list and [other]
+ /// at the same indices are equal according to [equality],
+ /// which defaults to using `==`.
+ bool equals(List<E> other, [Equality<E> equality = const DefaultEquality()]) {
+ if (length != other.length) return false;
+ for (var i = 0; i < length; i++) {
+ if (!equality.equals(this[i], other[i])) return false;
+ }
+ return true;
+ }
+
+ /// The [index]th element, or `null` if there is no such element.
+ ///
+ /// Returns the element at position [index] of this list,
+ /// just like [elementAt], if this list has such an element.
+ /// If this list does not have enough elements to have one with the given
+ /// [index], the `null` value is returned, unlike [elementAt] which throws
+ /// instead.
+ ///
+ /// The [index] must not be negative.
+ E? elementAtOrNull(int index) => (index < length) ? this[index] : null;
+
+ /// Contiguous [slice]s of `this` with the given [length].
+ ///
+ /// Each slice is a view of this list [length] elements long, except for the
+ /// last one which may be shorter if `this` contains too few elements. Each
+ /// slice begins after the last one ends.
+ ///
+ /// As with [slice], these slices are backed by this list, which must not
+ /// change its length while the views are being used.
+ ///
+ /// For example, `[1, 2, 3, 4, 5].slices(2)` returns `[[1, 2], [3, 4], [5]]`.
+ Iterable<List<E>> slices(int length) sync* {
+ if (length < 1) throw RangeError.range(length, 1, null, 'length');
+ for (var i = 0; i < this.length; i += length) {
+ yield slice(i, min(i + length, this.length));
+ }
+ }
+}
+
+/// Various extensions on lists of comparable elements.
+extension ListComparableExtensions<E extends Comparable<E>> on List<E> {
+ /// Returns the index of [element] in this sorted list.
+ ///
+ /// Uses binary search to find the location of [element].
+ /// This takes on the order of `log(n)` comparisons.
+ /// The list *must* be sorted according to [compare],
+ /// otherwise the result is unspecified.
+ /// If [compare] is omitted, it uses the natural order of the elements.
+ ///
+ /// Returns -1 if [element] does not occur in this list.
+ int binarySearch(E element, [int Function(E, E)? compare]) =>
+ algorithms.binarySearchBy<E, E>(
+ this, identity, compare ?? compareComparable, element);
+
+ /// Returns the index where [element] should be in this sorted list.
+ ///
+ /// Uses binary search to find the location of where [element] should be.
+ /// This takes on the order of `log(n)` comparisons.
+ /// The list *must* be sorted according to [compare],
+ /// otherwise the result is unspecified.
+ /// If [compare] is omitted, it uses the natural order of the elements.
+ ///
+ /// If [element] does not occur in this list, the returned index is
+ /// the first index where inserting [element] would keep the list
+ /// sorted.
+ int lowerBound(E element, [int Function(E, E)? compare]) =>
+ algorithms.lowerBoundBy<E, E>(
+ this, identity, compare ?? compareComparable, element);
+
+ /// Sort a range of elements by [compare].
+ ///
+ /// If [compare] is omitted, the range is sorted according to the
+ /// natural ordering of the elements.
+ void sortRange(int start, int end, [int Function(E a, E b)? compare]) {
+ RangeError.checkValidRange(start, end, length);
+ algorithms.quickSortBy<E, E>(
+ this, identity, compare ?? compareComparable, start, end);
+ }
+}
+
+/// A list view of a range of another list.
+///
+/// Wraps the range of the [source] list from [start] to [end]
+/// and acts like a fixed-length list view of that range.
+/// The source list must not change length while a list slice is being used.
+class ListSlice<E> extends ListBase<E> {
+ /// Original length of [source].
+ ///
+ /// Used to detect modifications to [source] which may invalidate
+ /// the slice.
+ final int _initialSize;
+
+ /// The original list backing this slice.
+ final List<E> source;
+
+ /// The start index of the slice.
+ final int start;
+
+ @override
+ final int length;
+
+ /// Creates a slice of [source] from [start] to [end].
+ ListSlice(this.source, this.start, int end)
+ : length = end - start,
+ _initialSize = source.length {
+ RangeError.checkValidRange(start, end, source.length);
+ }
+
+ // No argument checking, for internal use.
+ ListSlice._(this._initialSize, this.source, this.start, this.length);
+
+ /// The end index of the slice.
+ int get end => start + length;
+
+ @override
+ E operator [](int index) {
+ if (source.length != _initialSize) {
+ throw ConcurrentModificationError(source);
+ }
+ RangeError.checkValidIndex(index, this, null, length);
+ return source[start + index];
+ }
+
+ @override
+ void operator []=(int index, E value) {
+ if (source.length != _initialSize) {
+ throw ConcurrentModificationError(source);
+ }
+ RangeError.checkValidIndex(index, this, null, length);
+ source[start + index] = value;
+ }
+
+ @override
+ void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
+ if (source.length != _initialSize) {
+ throw ConcurrentModificationError(source);
+ }
+ RangeError.checkValidRange(start, end, length);
+ source.setRange(start + start, start + end, iterable, skipCount);
+ }
+
+ /// A fixed length view of a range of this list.
+ ///
+ /// The view is backed by this list, which must not change its length while
+ /// the view is being used.
+ ///
+ /// The view can be used to perform specific whole-list
+ /// actions on a part of the list.
+ /// For example, to see if a list contains more than one
+ /// "marker" element, you can do:
+ /// ```dart
+ /// someList.slice(someList.indexOf(marker) + 1).contains(marker)
+ /// ```
+ ListSlice<E> slice(int start, [int? end]) {
+ end = RangeError.checkValidRange(start, end, length);
+ return ListSlice._(_initialSize, source, this.start + start, end - start);
+ }
+
+ @override
+ void shuffle([Random? random]) {
+ if (source.length != _initialSize) {
+ throw ConcurrentModificationError(source);
+ }
+ algorithms.shuffle(source, start, end, random);
+ }
+
+ @override
+ void sort([int Function(E a, E b)? compare]) {
+ if (source.length != _initialSize) {
+ throw ConcurrentModificationError(source);
+ }
+ compare ??= defaultCompare;
+ quickSort(source, compare, start, start + length);
+ }
+
+ /// Sort a range of elements by [compare].
+ void sortRange(int start, int end, int Function(E a, E b) compare) {
+ if (source.length != _initialSize) {
+ throw ConcurrentModificationError(source);
+ }
+ source.sortRange(start, end, compare);
+ }
+
+ /// Shuffles a range of elements.
+ ///
+ /// If [random] is omitted, a new instance of [Random] is used.
+ void shuffleRange(int start, int end, [Random? random]) {
+ if (source.length != _initialSize) {
+ throw ConcurrentModificationError(source);
+ }
+ RangeError.checkValidRange(start, end, length);
+ algorithms.shuffle(source, this.start + start, this.start + end, random);
+ }
+
+ /// Reverses a range of elements.
+ void reverseRange(int start, int end) {
+ RangeError.checkValidRange(start, end, length);
+ source.reverseRange(this.start + start, this.start + end);
+ }
+
+ // Act like a fixed-length list.
+
+ @override
+ set length(int newLength) {
+ throw UnsupportedError('Cannot change the length of a fixed-length list');
+ }
+
+ @override
+ void add(E element) {
+ throw UnsupportedError('Cannot add to a fixed-length list');
+ }
+
+ @override
+ void insert(int index, E element) {
+ throw UnsupportedError('Cannot add to a fixed-length list');
+ }
+
+ @override
+ void insertAll(int index, Iterable<E> iterable) {
+ throw UnsupportedError('Cannot add to a fixed-length list');
+ }
+
+ @override
+ void addAll(Iterable<E> iterable) {
+ throw UnsupportedError('Cannot add to a fixed-length list');
+ }
+
+ @override
+ bool remove(Object? element) {
+ throw UnsupportedError('Cannot remove from a fixed-length list');
+ }
+
+ @override
+ void removeWhere(bool Function(E element) test) {
+ throw UnsupportedError('Cannot remove from a fixed-length list');
+ }
+
+ @override
+ void retainWhere(bool Function(E element) test) {
+ throw UnsupportedError('Cannot remove from a fixed-length list');
+ }
+
+ @override
+ void clear() {
+ throw UnsupportedError('Cannot clear a fixed-length list');
+ }
+
+ @override
+ E removeAt(int index) {
+ throw UnsupportedError('Cannot remove from a fixed-length list');
+ }
+
+ @override
+ E removeLast() {
+ throw UnsupportedError('Cannot remove from a fixed-length list');
+ }
+
+ @override
+ void removeRange(int start, int end) {
+ throw UnsupportedError('Cannot remove from a fixed-length list');
+ }
+
+ @override
+ void replaceRange(int start, int end, Iterable<E> newContents) {
+ throw UnsupportedError('Cannot remove from a fixed-length list');
+ }
+}
diff --git a/pkgs/collection/lib/src/priority_queue.dart b/pkgs/collection/lib/src/priority_queue.dart
new file mode 100644
index 0000000..11b0348
--- /dev/null
+++ b/pkgs/collection/lib/src/priority_queue.dart
@@ -0,0 +1,497 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'utils.dart';
+
+/// A priority queue is a priority based work-list of elements.
+///
+/// The queue allows adding elements, and removing them again in priority order.
+/// The same object can be added to the queue more than once.
+/// There is no specified ordering for objects with the same priority
+/// (where the `comparison` function returns zero).
+///
+/// Operations which care about object equality, [contains] and [remove],
+/// use [Object.==] for testing equality.
+/// In most situations this will be the same as identity ([identical]),
+/// but there are types, like [String], where users can reasonably expect
+/// distinct objects to represent the same value.
+/// If elements override [Object.==], the `comparison` function must
+/// always give equal objects the same priority,
+/// otherwise [contains] or [remove] might not work correctly.
+abstract class PriorityQueue<E> {
+ /// Creates an empty [PriorityQueue].
+ ///
+ /// The created [PriorityQueue] is a plain [HeapPriorityQueue].
+ ///
+ /// The [comparison] is a [Comparator] used to compare the priority of
+ /// elements. An element that compares as less than another element has
+ /// a higher priority.
+ ///
+ /// If [comparison] is omitted, it defaults to [Comparable.compare]. If this
+ /// is the case, `E` must implement [Comparable], and this is checked at
+ /// runtime for every comparison.
+ factory PriorityQueue([int Function(E, E)? comparison]) =
+ HeapPriorityQueue<E>;
+
+ /// Number of elements in the queue.
+ int get length;
+
+ /// Whether the queue is empty.
+ bool get isEmpty;
+
+ /// Whether the queue has any elements.
+ bool get isNotEmpty;
+
+ /// Checks if [object] is in the queue.
+ ///
+ /// Returns true if the element is found.
+ ///
+ /// Uses the [Object.==] of elements in the queue to check
+ /// for whether they are equal to [object].
+ /// Equal objects objects must have the same priority
+ /// according to the comparison function.
+ /// That is, if `a == b` then `comparison(a, b) == 0`.
+ /// If that is not the case, this check might fail to find
+ /// an object.
+ bool contains(E object);
+
+ /// Provides efficient access to all the elements currently in the queue.
+ ///
+ /// The operation should be performed without copying or moving
+ /// the elements, if at all possible.
+ ///
+ /// The elements are iterated in no particular order.
+ /// The order is stable as long as the queue is not modified.
+ /// The queue must not be modified during an iteration.
+ Iterable<E> get unorderedElements;
+
+ /// Adds element to the queue.
+ ///
+ /// The element will become the next to be removed by [removeFirst]
+ /// when all elements with higher priority have been removed.
+ void add(E element);
+
+ /// Adds all [elements] to the queue.
+ void addAll(Iterable<E> elements);
+
+ /// Returns the next element that will be returned by [removeFirst].
+ ///
+ /// The element is not removed from the queue.
+ ///
+ /// The queue must not be empty when this method is called.
+ E get first;
+
+ /// Removes and returns the element with the highest priority.
+ ///
+ /// Repeatedly calling this method, without adding element in between,
+ /// is guaranteed to return elements in non-decreasing order as, specified by
+ /// the `comparison` constructor parameter.
+ ///
+ /// The queue must not be empty when this method is called.
+ E removeFirst();
+
+ /// Removes an element of the queue that compares equal to [element].
+ ///
+ /// Returns true if an element is found and removed,
+ /// and false if no equal element is found.
+ ///
+ /// If the queue contains more than one object equal to [element],
+ /// only one of them is removed.
+ ///
+ /// Uses the [Object.==] of elements in the queue to check
+ /// for whether they are equal to [element].
+ /// Equal objects objects must have the same priority
+ /// according to the `comparison` function.
+ /// That is, if `a == b` then `comparison(a, b) == 0`.
+ /// If that is not the case, this check might fail to find
+ /// an object.
+ bool remove(E element);
+
+ /// Removes all the elements from this queue and returns them.
+ ///
+ /// The returned iterable has no specified order.
+ Iterable<E> removeAll();
+
+ /// Removes all the elements from this queue.
+ void clear();
+
+ /// Returns a list of the elements of this queue in priority order.
+ ///
+ /// The queue is not modified.
+ ///
+ /// The order is the order that the elements would be in if they were
+ /// removed from this queue using [removeFirst].
+ List<E> toList();
+
+ /// Returns a list of the elements of this queue in no specific order.
+ ///
+ /// The queue is not modified.
+ ///
+ /// The order of the elements is implementation specific.
+ /// The order may differ between different calls on the same queue.
+ List<E> toUnorderedList();
+
+ /// Return a comparator based set using the comparator of this queue.
+ ///
+ /// The queue is not modified.
+ ///
+ /// The returned [Set] is currently a [SplayTreeSet],
+ /// but this may change as other ordered sets are implemented.
+ ///
+ /// The set contains all the elements of this queue.
+ /// If an element occurs more than once in the queue,
+ /// the set will contain it only once.
+ Set<E> toSet();
+}
+
+/// Heap based priority queue.
+///
+/// The elements are kept in a heap structure,
+/// where the element with the highest priority is immediately accessible,
+/// and modifying a single element takes
+/// logarithmic time in the number of elements on average.
+///
+/// * The [add] and [removeFirst] operations take amortized logarithmic time,
+/// O(log(n)), but may occasionally take linear time when growing the capacity
+/// of the heap.
+/// * The [addAll] operation works as doing repeated [add] operations.
+/// * The [first] getter takes constant time, O(1).
+/// * The [clear] and [removeAll] methods also take constant time, O(1).
+/// * The [contains] and [remove] operations may need to search the entire
+/// queue for the elements, taking O(n) time.
+/// * The [toList] operation effectively sorts the elements, taking O(n*log(n))
+/// time.
+/// * The [toUnorderedList] operation copies, but does not sort, the elements,
+/// and is linear, O(n).
+/// * The [toSet] operation effectively adds each element to the new set, taking
+/// an expected O(n*log(n)) time.
+class HeapPriorityQueue<E> implements PriorityQueue<E> {
+ /// Initial capacity of a queue when created, or when added to after a
+ /// [clear].
+ ///
+ /// Number can be any positive value. Picking a size that gives a whole
+ /// number of "tree levels" in the heap is only done for aesthetic reasons.
+ static const int _initialCapacity = 7;
+
+ /// The comparison being used to compare the priority of elements.
+ final Comparator<E> comparison;
+
+ /// List implementation of a heap.
+ List<E?> _queue = List<E?>.filled(_initialCapacity, null);
+
+ /// Number of elements in queue.
+ ///
+ /// The heap is implemented in the first [_length] entries of [_queue].
+ int _length = 0;
+
+ /// Modification count.
+ ///
+ /// Used to detect concurrent modifications during iteration.
+ int _modificationCount = 0;
+
+ /// Create a new priority queue.
+ ///
+ /// The [comparison] is a [Comparator] used to compare the priority of
+ /// elements. An element that compares as less than another element has
+ /// a higher priority.
+ ///
+ /// If [comparison] is omitted, it defaults to [Comparable.compare]. If this
+ /// is the case, `E` must implement [Comparable], and this is checked at
+ /// runtime for every comparison.
+ HeapPriorityQueue([int Function(E, E)? comparison])
+ : comparison = comparison ?? defaultCompare;
+
+ E _elementAt(int index) => _queue[index] ?? (null as E);
+
+ @override
+ void add(E element) {
+ _modificationCount++;
+ _add(element);
+ }
+
+ @override
+ void addAll(Iterable<E> elements) {
+ var modified = 0;
+ for (var element in elements) {
+ modified = 1;
+ _add(element);
+ }
+ _modificationCount += modified;
+ }
+
+ @override
+ void clear() {
+ _modificationCount++;
+ _queue = const [];
+ _length = 0;
+ }
+
+ @override
+ bool contains(E object) => _locate(object) >= 0;
+
+ /// Provides efficient access to all the elements currently in the queue.
+ ///
+ /// The operation is performed in the order they occur
+ /// in the underlying heap structure.
+ ///
+ /// The order is stable as long as the queue is not modified.
+ /// The queue must not be modified during an iteration.
+ @override
+ Iterable<E> get unorderedElements => _UnorderedElementsIterable<E>(this);
+
+ @override
+ E get first {
+ if (_length == 0) throw StateError('No element');
+ return _elementAt(0);
+ }
+
+ @override
+ bool get isEmpty => _length == 0;
+
+ @override
+ bool get isNotEmpty => _length != 0;
+
+ @override
+ int get length => _length;
+
+ @override
+ bool remove(E element) {
+ var index = _locate(element);
+ if (index < 0) return false;
+ _modificationCount++;
+ var last = _removeLast();
+ if (index < _length) {
+ var comp = comparison(last, element);
+ if (comp <= 0) {
+ _bubbleUp(last, index);
+ } else {
+ _bubbleDown(last, index);
+ }
+ }
+ return true;
+ }
+
+ /// Removes all the elements from this queue and returns them.
+ ///
+ /// The returned iterable has no specified order.
+ /// The operation does not copy the elements,
+ /// but instead keeps them in the existing heap structure,
+ /// and iterates over that directly.
+ @override
+ Iterable<E> removeAll() {
+ _modificationCount++;
+ var result = _queue;
+ var length = _length;
+ _queue = const [];
+ _length = 0;
+ return result.take(length).cast();
+ }
+
+ @override
+ E removeFirst() {
+ if (_length == 0) throw StateError('No element');
+ _modificationCount++;
+ var result = _elementAt(0);
+ var last = _removeLast();
+ if (_length > 0) {
+ _bubbleDown(last, 0);
+ }
+ return result;
+ }
+
+ @override
+ List<E> toList() => _toUnorderedList()..sort(comparison);
+
+ @override
+ Set<E> toSet() {
+ var set = SplayTreeSet<E>(comparison);
+ for (var i = 0; i < _length; i++) {
+ set.add(_elementAt(i));
+ }
+ return set;
+ }
+
+ @override
+ List<E> toUnorderedList() => _toUnorderedList();
+
+ List<E> _toUnorderedList() =>
+ [for (var i = 0; i < _length; i++) _elementAt(i)];
+
+ /// Returns some representation of the queue.
+ ///
+ /// The format isn't significant, and may change in the future.
+ @override
+ String toString() {
+ return _queue.take(_length).toString();
+ }
+
+ /// Add element to the queue.
+ ///
+ /// Grows the capacity if the backing list is full.
+ void _add(E element) {
+ if (_length == _queue.length) _grow();
+ _bubbleUp(element, _length++);
+ }
+
+ /// Find the index of an object in the heap.
+ ///
+ /// Returns -1 if the object is not found.
+ ///
+ /// A matching object, `o`, must satisfy that
+ /// `comparison(o, object) == 0 && o == object`.
+ int _locate(E object) {
+ if (_length == 0) return -1;
+ // Count positions from one instead of zero. This gives the numbers
+ // some nice properties. For example, all right children are odd,
+ // their left sibling is even, and the parent is found by shifting
+ // right by one.
+ // Valid range for position is [1.._length], inclusive.
+ var position = 1;
+ // Pre-order depth first search, omit child nodes if the current
+ // node has lower priority than [object], because all nodes lower
+ // in the heap will also have lower priority.
+ do {
+ var index = position - 1;
+ var element = _elementAt(index);
+ var comp = comparison(element, object);
+ if (comp <= 0) {
+ if (comp == 0 && element == object) return index;
+ // Element may be in subtree.
+ // Continue with the left child, if it is there.
+ var leftChildPosition = position * 2;
+ if (leftChildPosition <= _length) {
+ position = leftChildPosition;
+ continue;
+ }
+ }
+ // Find the next right sibling or right ancestor sibling.
+ do {
+ while (position.isOdd) {
+ // While position is a right child, go to the parent.
+ position >>= 1;
+ }
+ // Then go to the right sibling of the left-child.
+ position += 1;
+ } while (position > _length); // Happens if last element is a left child.
+ } while (position != 1); // At root again. Happens for right-most element.
+ return -1;
+ }
+
+ E _removeLast() {
+ var newLength = _length - 1;
+ var last = _elementAt(newLength);
+ _queue[newLength] = null;
+ _length = newLength;
+ return last;
+ }
+
+ /// Place [element] in heap at [index] or above.
+ ///
+ /// Put element into the empty cell at `index`.
+ /// While the `element` has higher priority than the
+ /// parent, swap it with the parent.
+ void _bubbleUp(E element, int index) {
+ while (index > 0) {
+ var parentIndex = (index - 1) ~/ 2;
+ var parent = _elementAt(parentIndex);
+ if (comparison(element, parent) > 0) break;
+ _queue[index] = parent;
+ index = parentIndex;
+ }
+ _queue[index] = element;
+ }
+
+ /// Place [element] in heap at [index] or above.
+ ///
+ /// Put element into the empty cell at `index`.
+ /// While the `element` has lower priority than either child,
+ /// swap it with the highest priority child.
+ void _bubbleDown(E element, int index) {
+ var rightChildIndex = index * 2 + 2;
+ while (rightChildIndex < _length) {
+ var leftChildIndex = rightChildIndex - 1;
+ var leftChild = _elementAt(leftChildIndex);
+ var rightChild = _elementAt(rightChildIndex);
+ var comp = comparison(leftChild, rightChild);
+ int minChildIndex;
+ E minChild;
+ if (comp < 0) {
+ minChild = leftChild;
+ minChildIndex = leftChildIndex;
+ } else {
+ minChild = rightChild;
+ minChildIndex = rightChildIndex;
+ }
+ comp = comparison(element, minChild);
+ if (comp <= 0) {
+ _queue[index] = element;
+ return;
+ }
+ _queue[index] = minChild;
+ index = minChildIndex;
+ rightChildIndex = index * 2 + 2;
+ }
+ var leftChildIndex = rightChildIndex - 1;
+ if (leftChildIndex < _length) {
+ var child = _elementAt(leftChildIndex);
+ var comp = comparison(element, child);
+ if (comp > 0) {
+ _queue[index] = child;
+ index = leftChildIndex;
+ }
+ }
+ _queue[index] = element;
+ }
+
+ /// Grows the capacity of the list holding the heap.
+ ///
+ /// Called when the list is full.
+ void _grow() {
+ var newCapacity = _queue.length * 2 + 1;
+ if (newCapacity < _initialCapacity) newCapacity = _initialCapacity;
+ var newQueue = List<E?>.filled(newCapacity, null);
+ newQueue.setRange(0, _length, _queue);
+ _queue = newQueue;
+ }
+}
+
+/// Implementation of [HeapPriorityQueue.unorderedElements].
+class _UnorderedElementsIterable<E> extends Iterable<E> {
+ final HeapPriorityQueue<E> _queue;
+ _UnorderedElementsIterable(this._queue);
+ @override
+ Iterator<E> get iterator => _UnorderedElementsIterator<E>(_queue);
+}
+
+class _UnorderedElementsIterator<E> implements Iterator<E> {
+ final HeapPriorityQueue<E> _queue;
+ final int _initialModificationCount;
+ E? _current;
+ int _index = -1;
+
+ _UnorderedElementsIterator(this._queue)
+ : _initialModificationCount = _queue._modificationCount;
+
+ @override
+ bool moveNext() {
+ if (_initialModificationCount != _queue._modificationCount) {
+ throw ConcurrentModificationError(_queue);
+ }
+ var nextIndex = _index + 1;
+ if (0 <= nextIndex && nextIndex < _queue.length) {
+ _current = _queue._queue[nextIndex];
+ _index = nextIndex;
+ return true;
+ }
+ _current = null;
+ _index = -2;
+ return false;
+ }
+
+ @override
+ E get current =>
+ _index < 0 ? throw StateError('No element') : (_current ?? null as E);
+}
diff --git a/pkgs/collection/lib/src/queue_list.dart b/pkgs/collection/lib/src/queue_list.dart
new file mode 100644
index 0000000..a3eaba1
--- /dev/null
+++ b/pkgs/collection/lib/src/queue_list.dart
@@ -0,0 +1,295 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+/// A class that efficiently implements both [Queue] and [List].
+// TODO(nweiz): Currently this code is copied almost verbatim from
+// dart:collection. The only changes are to implement List and to remove methods
+// that are redundant with ListMixin. Remove or simplify it when issue 21330 is
+// fixed.
+class QueueList<E> extends Object with ListMixin<E> implements Queue<E> {
+ /// Adapts [source] to be a `QueueList<T>`.
+ ///
+ /// Any time the class would produce an element that is not a [T], the element
+ /// access will throw.
+ ///
+ /// Any time a [T] value is attempted stored into the adapted class, the store
+ /// will throw unless the value is also an instance of [S].
+ ///
+ /// If all accessed elements of [source] are actually instances of [T] and if
+ /// all elements stored in the returned are actually instance of [S],
+ /// then the returned instance can be used as a `QueueList<T>`.
+ static QueueList<T> _castFrom<S, T>(QueueList<S> source) {
+ return _CastQueueList<S, T>(source);
+ }
+
+ /// Default and minimal initial capacity of the queue-list.
+ static const int _initialCapacity = 8;
+ List<E?> _table;
+ int _head;
+ int _tail;
+
+ /// Creates an empty queue.
+ ///
+ /// If [initialCapacity] is given, prepare the queue for at least that many
+ /// elements.
+ QueueList([int? initialCapacity])
+ : this._init(_computeInitialCapacity(initialCapacity));
+
+ /// Creates an empty queue with the specific initial capacity.
+ QueueList._init(int initialCapacity)
+ : assert(_isPowerOf2(initialCapacity)),
+ _table = List<E?>.filled(initialCapacity, null),
+ _head = 0,
+ _tail = 0;
+
+ /// An internal constructor for use by [_CastQueueList].
+ QueueList._(this._head, this._tail, this._table);
+
+ /// Create a queue initially containing the elements of [source].
+ factory QueueList.from(Iterable<E> source) {
+ if (source is List) {
+ var length = source.length;
+ var queue = QueueList<E>(length + 1);
+ assert(queue._table.length > length);
+ var sourceList = source;
+ queue._table.setRange(0, length, sourceList, 0);
+ queue._tail = length;
+ return queue;
+ } else {
+ return QueueList<E>()..addAll(source);
+ }
+ }
+
+ /// Computes the actual initial capacity based on the constructor parameter.
+ static int _computeInitialCapacity(int? initialCapacity) {
+ if (initialCapacity == null || initialCapacity < _initialCapacity) {
+ return _initialCapacity;
+ }
+ initialCapacity += 1;
+ if (_isPowerOf2(initialCapacity)) {
+ return initialCapacity;
+ }
+ return _nextPowerOf2(initialCapacity);
+ }
+
+ // Collection interface.
+
+ @override
+ void add(E element) {
+ _add(element);
+ }
+
+ @override
+ void addAll(Iterable<E> iterable) {
+ if (iterable is List) {
+ var list = iterable;
+ var addCount = list.length;
+ var length = this.length;
+ if (length + addCount >= _table.length) {
+ _preGrow(length + addCount);
+ // After preGrow, all elements are at the start of the list.
+ _table.setRange(length, length + addCount, list, 0);
+ _tail += addCount;
+ } else {
+ // Adding addCount elements won't reach _head.
+ var endSpace = _table.length - _tail;
+ if (addCount < endSpace) {
+ _table.setRange(_tail, _tail + addCount, list, 0);
+ _tail += addCount;
+ } else {
+ var preSpace = addCount - endSpace;
+ _table.setRange(_tail, _tail + endSpace, list, 0);
+ _table.setRange(0, preSpace, list, endSpace);
+ _tail = preSpace;
+ }
+ }
+ } else {
+ for (var element in iterable) {
+ _add(element);
+ }
+ }
+ }
+
+ QueueList<T> cast<T>() => QueueList._castFrom<E, T>(this);
+
+ @Deprecated('Use cast instead')
+ QueueList<T> retype<T>() => cast<T>();
+
+ @override
+ String toString() => IterableBase.iterableToFullString(this, '{', '}');
+
+ // Queue interface.
+
+ @override
+ void addLast(E element) {
+ _add(element);
+ }
+
+ @override
+ void addFirst(E element) {
+ _head = (_head - 1) & (_table.length - 1);
+ _table[_head] = element;
+ if (_head == _tail) _grow();
+ }
+
+ @override
+ E removeFirst() {
+ if (_head == _tail) throw StateError('No element');
+ var result = _table[_head] as E;
+ _table[_head] = null;
+ _head = (_head + 1) & (_table.length - 1);
+ return result;
+ }
+
+ @override
+ E removeLast() {
+ if (_head == _tail) throw StateError('No element');
+ _tail = (_tail - 1) & (_table.length - 1);
+ var result = _table[_tail] as E;
+ _table[_tail] = null;
+ return result;
+ }
+
+ // List interface.
+
+ @override
+ int get length => (_tail - _head) & (_table.length - 1);
+
+ @override
+ set length(int value) {
+ if (value < 0) throw RangeError('Length $value may not be negative.');
+ if (value > length && null is! E) {
+ throw UnsupportedError(
+ 'The length can only be increased when the element type is '
+ 'nullable, but the current element type is `$E`.');
+ }
+
+ var delta = value - length;
+ if (delta >= 0) {
+ if (_table.length <= value) {
+ _preGrow(value);
+ }
+ _tail = (_tail + delta) & (_table.length - 1);
+ return;
+ }
+
+ var newTail = _tail + delta; // [delta] is negative.
+ if (newTail >= 0) {
+ _table.fillRange(newTail, _tail, null);
+ } else {
+ newTail += _table.length;
+ _table.fillRange(0, _tail, null);
+ _table.fillRange(newTail, _table.length, null);
+ }
+ _tail = newTail;
+ }
+
+ @override
+ E operator [](int index) {
+ if (index < 0 || index >= length) {
+ throw RangeError('Index $index must be in the range [0..$length).');
+ }
+
+ return _table[(_head + index) & (_table.length - 1)] as E;
+ }
+
+ @override
+ void operator []=(int index, E value) {
+ if (index < 0 || index >= length) {
+ throw RangeError('Index $index must be in the range [0..$length).');
+ }
+
+ _table[(_head + index) & (_table.length - 1)] = value;
+ }
+
+ // Internal helper functions.
+
+ /// Whether [number] is a power of two.
+ ///
+ /// Only works for positive numbers.
+ static bool _isPowerOf2(int number) => (number & (number - 1)) == 0;
+
+ /// Rounds [number] up to the nearest power of 2.
+ ///
+ /// If [number] is a power of 2 already, it is returned.
+ ///
+ /// Only works for positive numbers.
+ static int _nextPowerOf2(int number) {
+ assert(number > 0);
+ number = (number << 1) - 1;
+ for (;;) {
+ var nextNumber = number & (number - 1);
+ if (nextNumber == 0) return number;
+ number = nextNumber;
+ }
+ }
+
+ /// Adds element at end of queue. Used by both [add] and [addAll].
+ void _add(E element) {
+ _table[_tail] = element;
+ _tail = (_tail + 1) & (_table.length - 1);
+ if (_head == _tail) _grow();
+ }
+
+ /// Grow the table when full.
+ void _grow() {
+ var newTable = List<E?>.filled(_table.length * 2, null);
+ var split = _table.length - _head;
+ newTable.setRange(0, split, _table, _head);
+ newTable.setRange(split, split + _head, _table, 0);
+ _head = 0;
+ _tail = _table.length;
+ _table = newTable;
+ }
+
+ int _writeToList(List<E?> target) {
+ assert(target.length >= length);
+ if (_head <= _tail) {
+ var length = _tail - _head;
+ target.setRange(0, length, _table, _head);
+ return length;
+ } else {
+ var firstPartSize = _table.length - _head;
+ target.setRange(0, firstPartSize, _table, _head);
+ target.setRange(firstPartSize, firstPartSize + _tail, _table, 0);
+ return _tail + firstPartSize;
+ }
+ }
+
+ /// Grows the table even if it is not full.
+ void _preGrow(int newElementCount) {
+ assert(newElementCount >= length);
+
+ // Add 1.5x extra room to ensure that there's room for more elements after
+ // expansion.
+ newElementCount += newElementCount >> 1;
+ var newCapacity = _nextPowerOf2(newElementCount);
+ var newTable = List<E?>.filled(newCapacity, null);
+ _tail = _writeToList(newTable);
+ _table = newTable;
+ _head = 0;
+ }
+}
+
+class _CastQueueList<S, T> extends QueueList<T> {
+ final QueueList<S> _delegate;
+
+ // Assigns invalid values for head/tail because it uses the delegate to hold
+ // the real values, but they are non-null fields.
+ _CastQueueList(this._delegate) : super._(-1, -1, _delegate._table.cast<T>());
+
+ @override
+ int get _head => _delegate._head;
+
+ @override
+ set _head(int value) => _delegate._head = value;
+
+ @override
+ int get _tail => _delegate._tail;
+
+ @override
+ set _tail(int value) => _delegate._tail = value;
+}
diff --git a/pkgs/collection/lib/src/union_set.dart b/pkgs/collection/lib/src/union_set.dart
new file mode 100644
index 0000000..a34d7ad
--- /dev/null
+++ b/pkgs/collection/lib/src/union_set.dart
@@ -0,0 +1,80 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'unmodifiable_wrappers.dart';
+
+/// A single set that provides a view of the union over a set of sets.
+///
+/// Since this is just a view, it reflects all changes in the underlying sets.
+///
+/// If an element is in multiple sets and the outer set is ordered, the version
+/// in the earliest inner set is preferred. Component sets are assumed to use
+/// `==` and `hashCode` for equality.
+class UnionSet<E> extends SetBase<E> with UnmodifiableSetMixin<E> {
+ /// The set of sets that this provides a view of.
+ final Set<Set<E>> _sets;
+
+ /// Whether the sets in [_sets] are guaranteed to be disjoint.
+ final bool _disjoint;
+
+ /// Creates a new set that's a view of the union of all sets in [sets].
+ ///
+ /// If any sets in [sets] change, this [UnionSet] reflects that change. If a
+ /// new set is added to [sets], this [UnionSet] reflects that as well.
+ ///
+ /// If [disjoint] is `true`, then all component sets must be disjoint. That
+ /// is, that they contain no elements in common. This makes many operations
+ /// including [length] more efficient. If the component sets turn out not to
+ /// be disjoint, some operations may behave inconsistently.
+ UnionSet(Set<Set<E>> sets, {bool disjoint = false})
+ : _sets = sets,
+ _disjoint = disjoint;
+
+ /// Creates a new set that's a view of the union of all sets in [sets].
+ ///
+ /// If any sets in [sets] change, this [UnionSet] reflects that change.
+ /// However, unlike [UnionSet.new], this creates a copy of its parameter, so
+ /// changes in [sets] aren't reflected in this [UnionSet].
+ ///
+ /// If [disjoint] is `true`, then all component sets must be disjoint. That
+ /// is, that they contain no elements in common. This makes many operations
+ /// including [length] more efficient. If the component sets turn out not to
+ /// be disjoint, some operations may behave inconsistently.
+ UnionSet.from(Iterable<Set<E>> sets, {bool disjoint = false})
+ : this(sets.toSet(), disjoint: disjoint);
+
+ @override
+ int get length => _disjoint
+ ? _sets.fold(0, (length, set) => length + set.length)
+ : _iterable.length;
+
+ @override
+ Iterator<E> get iterator => _iterable.iterator;
+
+ /// An iterable over the contents of all [_sets].
+ ///
+ /// If this is not a [_disjoint] union an extra set is used to deduplicate
+ /// values.
+ Iterable<E> get _iterable {
+ var allElements = _sets.expand((set) => set);
+ return _disjoint ? allElements : allElements.where(<E>{}.add);
+ }
+
+ @override
+ bool contains(Object? element) => _sets.any((set) => set.contains(element));
+
+ @override
+ E? lookup(Object? element) {
+ for (var set in _sets) {
+ var result = set.lookup(element);
+ if (result != null || set.contains(null)) return result;
+ }
+ return null;
+ }
+
+ @override
+ Set<E> toSet() => <E>{for (var set in _sets) ...set};
+}
diff --git a/pkgs/collection/lib/src/union_set_controller.dart b/pkgs/collection/lib/src/union_set_controller.dart
new file mode 100644
index 0000000..498528e
--- /dev/null
+++ b/pkgs/collection/lib/src/union_set_controller.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'union_set.dart';
+
+/// A controller that exposes a view of the union of a collection of sets.
+///
+/// This is a convenience class for creating a [UnionSet] whose contents change
+/// over the lifetime of a class. For example:
+///
+/// ```dart
+/// class Engine {
+/// Set<Test> get activeTests => _activeTestsGroup.set;
+/// final _activeTestsGroup = UnionSetController<Test>();
+///
+/// void addSuite(Suite suite) {
+/// _activeTestsGroup.add(suite.tests);
+/// _runSuite(suite);
+/// _activeTestsGroup.remove(suite.tests);
+/// }
+/// }
+/// ```
+class UnionSetController<E> {
+ /// The [UnionSet] that provides a view of the union of sets in `this`.
+ final UnionSet<E> set;
+
+ /// The sets whose union is exposed through [set].
+ final Set<Set<E>> _sets;
+
+ /// Creates a set of sets that provides a view of the union of those sets.
+ ///
+ /// If [disjoint] is `true`, this assumes that all component sets are
+ /// disjoint—that is, that they contain no elements in common. This makes
+ /// many operations including `length` more efficient.
+ UnionSetController({bool disjoint = false}) : this._(<Set<E>>{}, disjoint);
+
+ /// Creates a controller with the provided [_sets].
+ UnionSetController._(this._sets, bool disjoint)
+ : set = UnionSet<E>(_sets, disjoint: disjoint);
+
+ /// Adds the contents of [component] to [set].
+ ///
+ /// If the contents of [component] change over time, [set] will change
+ /// accordingly.
+ void add(Set<E> component) {
+ _sets.add(component);
+ }
+
+ /// Removes the contents of [component] to [set].
+ ///
+ /// If another set in `this` has overlapping elements with [component], those
+ /// elements will remain in [set].
+ bool remove(Set<E> component) => _sets.remove(component);
+}
diff --git a/pkgs/collection/lib/src/unmodifiable_wrappers.dart b/pkgs/collection/lib/src/unmodifiable_wrappers.dart
new file mode 100644
index 0000000..3b211c0
--- /dev/null
+++ b/pkgs/collection/lib/src/unmodifiable_wrappers.dart
@@ -0,0 +1,204 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'empty_unmodifiable_set.dart';
+import 'wrappers.dart';
+
+export 'dart:collection' show UnmodifiableListView, UnmodifiableMapView;
+
+/// A fixed-length list.
+///
+/// A `NonGrowableListView` contains a [List] object and ensures that
+/// its length does not change.
+/// Methods that would change the length of the list,
+/// such as [add] and [remove], throw an [UnsupportedError].
+/// All other methods work directly on the underlying list.
+///
+/// This class _does_ allow changes to the contents of the wrapped list.
+/// You can, for example, [sort] the list.
+/// Permitted operations defer to the wrapped list.
+class NonGrowableListView<E> extends DelegatingList<E>
+ with NonGrowableListMixin<E> {
+ NonGrowableListView(super.listBase);
+}
+
+/// Mixin class that implements a throwing version of all list operations that
+/// change the List's length.
+abstract mixin class NonGrowableListMixin<E> implements List<E> {
+ static Never _throw() {
+ throw UnsupportedError('Cannot change the length of a fixed-length list');
+ }
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the length of the list are disallowed.
+ @override
+ set length(int newLength) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the length of the list are disallowed.
+ @override
+ bool add(E value) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the length of the list are disallowed.
+ @override
+ void addAll(Iterable<E> iterable) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the length of the list are disallowed.
+ @override
+ void insert(int index, E element) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the length of the list are disallowed.
+ @override
+ void insertAll(int index, Iterable<E> iterable) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the length of the list are disallowed.
+ @override
+ bool remove(Object? value) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the length of the list are disallowed.
+ @override
+ E removeAt(int index) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the length of the list are disallowed.
+ @override
+ E removeLast() => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the length of the list are disallowed.
+ @override
+ void removeWhere(bool Function(E) test) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the length of the list are disallowed.
+ @override
+ void retainWhere(bool Function(E) test) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the length of the list are disallowed.
+ @override
+ void removeRange(int start, int end) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the length of the list are disallowed.
+ @override
+ void replaceRange(int start, int end, Iterable<E> iterable) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the length of the list are disallowed.
+ @override
+ void clear() => _throw();
+}
+
+/// An unmodifiable set.
+///
+/// An [UnmodifiableSetView] contains a [Set],
+/// and prevents that set from being changed through the view.
+/// Methods that could change the set,
+/// such as [add] and [remove], throw an [UnsupportedError].
+/// Permitted operations defer to the wrapped set.
+class UnmodifiableSetView<E> extends DelegatingSet<E>
+ with UnmodifiableSetMixin<E> {
+ UnmodifiableSetView(super.setBase);
+
+ /// An unmodifiable empty set.
+ ///
+ /// This is the same as `UnmodifiableSetView(Set())`, except that it
+ /// can be used in const contexts.
+ const factory UnmodifiableSetView.empty() = EmptyUnmodifiableSet<E>;
+}
+
+/// Mixin class that implements a throwing version of all set operations that
+/// change the Set.
+abstract mixin class UnmodifiableSetMixin<E> implements Set<E> {
+ static Never _throw() {
+ throw UnsupportedError('Cannot modify an unmodifiable Set');
+ }
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the set are disallowed.
+ @override
+ bool add(E value) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the set are disallowed.
+ @override
+ void addAll(Iterable<E> elements) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the set are disallowed.
+ @override
+ bool remove(Object? value) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the set are disallowed.
+ @override
+ void removeAll(Iterable elements) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the set are disallowed.
+ @override
+ void retainAll(Iterable elements) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the set are disallowed.
+ @override
+ void removeWhere(bool Function(E) test) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the set are disallowed.
+ @override
+ void retainWhere(bool Function(E) test) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the set are disallowed.
+ @override
+ void clear() => _throw();
+}
+
+/// Mixin class that implements a throwing version of all map operations that
+/// change the Map.
+abstract mixin class UnmodifiableMapMixin<K, V> implements Map<K, V> {
+ static Never _throw() {
+ throw UnsupportedError('Cannot modify an unmodifiable Map');
+ }
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the map are disallowed.
+ @override
+ void operator []=(K key, V value) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the map are disallowed.
+ @override
+ V putIfAbsent(K key, V Function() ifAbsent) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the map are disallowed.
+ @override
+ void addAll(Map<K, V> other) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the map are disallowed.
+ @override
+ V remove(Object? key) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the map are disallowed.
+ @override
+ void clear() => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the map are disallowed.
+ set first(_) => _throw();
+
+ /// Throws an [UnsupportedError];
+ /// operations that change the map are disallowed.
+ set last(_) => _throw();
+}
diff --git a/pkgs/collection/lib/src/utils.dart b/pkgs/collection/lib/src/utils.dart
new file mode 100644
index 0000000..64088f0
--- /dev/null
+++ b/pkgs/collection/lib/src/utils.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A [Comparator] that asserts that its first argument is comparable.
+///
+/// The function behaves just like [List.sort]'s
+/// default comparison function. It is entirely dynamic in its testing.
+///
+/// Should be used when optimistically comparing object that are assumed
+/// to be comparable.
+/// If the elements are known to be comparable, use [compareComparable].
+int defaultCompare(Object? value1, Object? value2) =>
+ (value1 as Comparable<Object?>).compareTo(value2);
+
+/// A reusable identity function at any type.
+T identity<T>(T value) => value;
+
+/// A reusable typed comparable comparator.
+int compareComparable<T extends Comparable<T>>(T a, T b) => a.compareTo(b);
diff --git a/pkgs/collection/lib/src/wrappers.dart b/pkgs/collection/lib/src/wrappers.dart
new file mode 100644
index 0000000..859d0bc
--- /dev/null
+++ b/pkgs/collection/lib/src/wrappers.dart
@@ -0,0 +1,838 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+import 'dart:math' as math;
+
+import 'unmodifiable_wrappers.dart';
+
+/// A base class for delegating iterables.
+///
+/// Subclasses can provide a [_base] that should be delegated to. Unlike
+/// [DelegatingIterable], this allows the base to be created on demand.
+abstract class _DelegatingIterableBase<E> implements Iterable<E> {
+ Iterable<E> get _base;
+
+ const _DelegatingIterableBase();
+
+ @override
+ bool any(bool Function(E) test) => _base.any(test);
+
+ @override
+ Iterable<T> cast<T>() => _base.cast<T>();
+
+ @override
+ bool contains(Object? element) => _base.contains(element);
+
+ @override
+ E elementAt(int index) => _base.elementAt(index);
+
+ @override
+ bool every(bool Function(E) test) => _base.every(test);
+
+ @override
+ Iterable<T> expand<T>(Iterable<T> Function(E) f) => _base.expand(f);
+
+ @override
+ E get first => _base.first;
+
+ @override
+ E firstWhere(bool Function(E) test, {E Function()? orElse}) =>
+ _base.firstWhere(test, orElse: orElse);
+
+ @override
+ T fold<T>(T initialValue, T Function(T previousValue, E element) combine) =>
+ _base.fold(initialValue, combine);
+
+ @override
+ Iterable<E> followedBy(Iterable<E> other) => _base.followedBy(other);
+
+ @override
+ void forEach(void Function(E) f) => _base.forEach(f);
+
+ @override
+ bool get isEmpty => _base.isEmpty;
+
+ @override
+ bool get isNotEmpty => _base.isNotEmpty;
+
+ @override
+ Iterator<E> get iterator => _base.iterator;
+
+ @override
+ String join([String separator = '']) => _base.join(separator);
+
+ @override
+ E get last => _base.last;
+
+ @override
+ E lastWhere(bool Function(E) test, {E Function()? orElse}) =>
+ _base.lastWhere(test, orElse: orElse);
+
+ @override
+ int get length => _base.length;
+
+ @override
+ Iterable<T> map<T>(T Function(E) f) => _base.map(f);
+
+ @override
+ E reduce(E Function(E value, E element) combine) => _base.reduce(combine);
+
+ @Deprecated('Use cast instead')
+ Iterable<T> retype<T>() => cast<T>();
+
+ @override
+ E get single => _base.single;
+
+ @override
+ E singleWhere(bool Function(E) test, {E Function()? orElse}) {
+ return _base.singleWhere(test, orElse: orElse);
+ }
+
+ @override
+ Iterable<E> skip(int n) => _base.skip(n);
+
+ @override
+ Iterable<E> skipWhile(bool Function(E) test) => _base.skipWhile(test);
+
+ @override
+ Iterable<E> take(int n) => _base.take(n);
+
+ @override
+ Iterable<E> takeWhile(bool Function(E) test) => _base.takeWhile(test);
+
+ @override
+ List<E> toList({bool growable = true}) => _base.toList(growable: growable);
+
+ @override
+ Set<E> toSet() => _base.toSet();
+
+ @override
+ Iterable<E> where(bool Function(E) test) => _base.where(test);
+
+ @override
+ Iterable<T> whereType<T>() => _base.whereType<T>();
+
+ @override
+ String toString() => _base.toString();
+}
+
+/// An [Iterable] that delegates all operations to a base iterable.
+///
+/// This class can be used to hide non-`Iterable` methods of an iterable object,
+/// or it can be extended to add extra functionality on top of an existing
+/// iterable object.
+class DelegatingIterable<E> extends _DelegatingIterableBase<E> {
+ @override
+ final Iterable<E> _base;
+
+ /// Creates a wrapper that forwards operations to [base].
+ const DelegatingIterable(Iterable<E> base) : _base = base;
+
+ /// Creates a wrapper that asserts the types of values in [base].
+ ///
+ /// This soundly converts an [Iterable] without a generic type to an
+ /// `Iterable<E>` by asserting that its elements are instances of `E` whenever
+ /// they're accessed. If they're not, it throws a [TypeError].
+ ///
+ /// This forwards all operations to [base], so any changes in [base] will be
+ /// reflected in `this`. If [base] is already an `Iterable<E>`, it's returned
+ /// unmodified.
+ @Deprecated('Use iterable.cast<E> instead.')
+ static Iterable<E> typed<E>(Iterable base) => base.cast<E>();
+}
+
+/// A [List] that delegates all operations to a base list.
+///
+/// This class can be used to hide non-`List` methods of a list object, or it
+/// can be extended to add extra functionality on top of an existing list
+/// object.
+class DelegatingList<E> extends _DelegatingIterableBase<E> implements List<E> {
+ @override
+ final List<E> _base;
+
+ const DelegatingList(List<E> base) : _base = base;
+
+ /// Creates a wrapper that asserts the types of values in [base].
+ ///
+ /// This soundly converts a [List] without a generic type to a `List<E>` by
+ /// asserting that its elements are instances of `E` whenever they're
+ /// accessed. If they're not, it throws a [TypeError]. Note that even if an
+ /// operation throws a [TypeError], it may still mutate the underlying
+ /// collection.
+ ///
+ /// This forwards all operations to [base], so any changes in [base] will be
+ /// reflected in `this`. If [base] is already a `List<E>`, it's returned
+ /// unmodified.
+ @Deprecated('Use list.cast<E> instead.')
+ static List<E> typed<E>(List base) => base.cast<E>();
+
+ @override
+ E operator [](int index) => _base[index];
+
+ @override
+ void operator []=(int index, E value) {
+ _base[index] = value;
+ }
+
+ @override
+ List<E> operator +(List<E> other) => _base + other;
+
+ @override
+ void add(E value) {
+ _base.add(value);
+ }
+
+ @override
+ void addAll(Iterable<E> iterable) {
+ _base.addAll(iterable);
+ }
+
+ @override
+ Map<int, E> asMap() => _base.asMap();
+
+ @override
+ List<T> cast<T>() => _base.cast<T>();
+
+ @override
+ void clear() {
+ _base.clear();
+ }
+
+ @override
+ void fillRange(int start, int end, [E? fillValue]) {
+ _base.fillRange(start, end, fillValue);
+ }
+
+ @override
+ set first(E value) {
+ if (isEmpty) throw RangeError.index(0, this);
+ this[0] = value;
+ }
+
+ @override
+ Iterable<E> getRange(int start, int end) => _base.getRange(start, end);
+
+ @override
+ int indexOf(E element, [int start = 0]) => _base.indexOf(element, start);
+
+ @override
+ int indexWhere(bool Function(E) test, [int start = 0]) =>
+ _base.indexWhere(test, start);
+
+ @override
+ void insert(int index, E element) {
+ _base.insert(index, element);
+ }
+
+ @override
+ void insertAll(int index, Iterable<E> iterable) {
+ _base.insertAll(index, iterable);
+ }
+
+ @override
+ set last(E value) {
+ if (isEmpty) throw RangeError.index(0, this);
+ this[length - 1] = value;
+ }
+
+ @override
+ int lastIndexOf(E element, [int? start]) => _base.lastIndexOf(element, start);
+
+ @override
+ int lastIndexWhere(bool Function(E) test, [int? start]) =>
+ _base.lastIndexWhere(test, start);
+
+ @override
+ set length(int newLength) {
+ _base.length = newLength;
+ }
+
+ @override
+ bool remove(Object? value) => _base.remove(value);
+
+ @override
+ E removeAt(int index) => _base.removeAt(index);
+
+ @override
+ E removeLast() => _base.removeLast();
+
+ @override
+ void removeRange(int start, int end) {
+ _base.removeRange(start, end);
+ }
+
+ @override
+ void removeWhere(bool Function(E) test) {
+ _base.removeWhere(test);
+ }
+
+ @override
+ void replaceRange(int start, int end, Iterable<E> iterable) {
+ _base.replaceRange(start, end, iterable);
+ }
+
+ @override
+ void retainWhere(bool Function(E) test) {
+ _base.retainWhere(test);
+ }
+
+ @Deprecated('Use cast instead')
+ @override
+ List<T> retype<T>() => cast<T>();
+
+ @override
+ Iterable<E> get reversed => _base.reversed;
+
+ @override
+ void setAll(int index, Iterable<E> iterable) {
+ _base.setAll(index, iterable);
+ }
+
+ @override
+ void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
+ _base.setRange(start, end, iterable, skipCount);
+ }
+
+ @override
+ void shuffle([math.Random? random]) {
+ _base.shuffle(random);
+ }
+
+ @override
+ void sort([int Function(E, E)? compare]) {
+ _base.sort(compare);
+ }
+
+ @override
+ List<E> sublist(int start, [int? end]) => _base.sublist(start, end);
+}
+
+/// A [Set] that delegates all operations to a base set.
+///
+/// This class can be used to hide non-`Set` methods of a set object, or it can
+/// be extended to add extra functionality on top of an existing set object.
+class DelegatingSet<E> extends _DelegatingIterableBase<E> implements Set<E> {
+ @override
+ final Set<E> _base;
+
+ const DelegatingSet(Set<E> base) : _base = base;
+
+ /// Creates a wrapper that asserts the types of values in [base].
+ ///
+ /// This soundly converts a [Set] without a generic type to a `Set<E>` by
+ /// asserting that its elements are instances of `E` whenever they're
+ /// accessed. If they're not, it throws a [TypeError]. Note that even if an
+ /// operation throws a [TypeError], it may still mutate the underlying
+ /// collection.
+ ///
+ /// This forwards all operations to [base], so any changes in [base] will be
+ /// reflected in `this`. If [base] is already a `Set<E>`, it's returned
+ /// unmodified.
+ @Deprecated('Use set.cast<E> instead.')
+ static Set<E> typed<E>(Set base) => base.cast<E>();
+
+ @override
+ bool add(E value) => _base.add(value);
+
+ @override
+ void addAll(Iterable<E> elements) {
+ _base.addAll(elements);
+ }
+
+ @override
+ Set<T> cast<T>() => _base.cast<T>();
+
+ @override
+ void clear() {
+ _base.clear();
+ }
+
+ @override
+ bool containsAll(Iterable<Object?> other) => _base.containsAll(other);
+
+ @override
+ Set<E> difference(Set<Object?> other) => _base.difference(other);
+
+ @override
+ Set<E> intersection(Set<Object?> other) => _base.intersection(other);
+
+ @override
+ E? lookup(Object? element) => _base.lookup(element);
+
+ @override
+ bool remove(Object? value) => _base.remove(value);
+
+ @override
+ void removeAll(Iterable<Object?> elements) {
+ _base.removeAll(elements);
+ }
+
+ @override
+ void removeWhere(bool Function(E) test) {
+ _base.removeWhere(test);
+ }
+
+ @override
+ void retainAll(Iterable<Object?> elements) {
+ _base.retainAll(elements);
+ }
+
+ @Deprecated('Use cast instead')
+ @override
+ Set<T> retype<T>() => cast<T>();
+
+ @override
+ void retainWhere(bool Function(E) test) {
+ _base.retainWhere(test);
+ }
+
+ @override
+ Set<E> union(Set<E> other) => _base.union(other);
+
+ @override
+ Set<E> toSet() => DelegatingSet<E>(_base.toSet());
+}
+
+/// A [Queue] that delegates all operations to a base queue.
+///
+/// This class can be used to hide non-`Queue` methods of a queue object, or it
+/// can be extended to add extra functionality on top of an existing queue
+/// object.
+class DelegatingQueue<E> extends _DelegatingIterableBase<E>
+ implements Queue<E> {
+ @override
+ final Queue<E> _base;
+
+ const DelegatingQueue(Queue<E> queue) : _base = queue;
+
+ /// Creates a wrapper that asserts the types of values in [base].
+ ///
+ /// This soundly converts a [Queue] without a generic type to a `Queue<E>` by
+ /// asserting that its elements are instances of `E` whenever they're
+ /// accessed. If they're not, it throws a [TypeError]. Note that even if an
+ /// operation throws a [TypeError], it may still mutate the underlying
+ /// collection.
+ ///
+ /// This forwards all operations to [base], so any changes in [base] will be
+ /// reflected in `this`. If [base] is already a `Queue<E>`, it's returned
+ /// unmodified.
+ @Deprecated('Use queue.cast<E> instead.')
+ static Queue<E> typed<E>(Queue base) => base.cast<E>();
+
+ @override
+ void add(E value) {
+ _base.add(value);
+ }
+
+ @override
+ void addAll(Iterable<E> iterable) {
+ _base.addAll(iterable);
+ }
+
+ @override
+ void addFirst(E value) {
+ _base.addFirst(value);
+ }
+
+ @override
+ void addLast(E value) {
+ _base.addLast(value);
+ }
+
+ @override
+ Queue<T> cast<T>() => _base.cast<T>();
+
+ @override
+ void clear() {
+ _base.clear();
+ }
+
+ @override
+ bool remove(Object? object) => _base.remove(object);
+
+ @override
+ void removeWhere(bool Function(E) test) {
+ _base.removeWhere(test);
+ }
+
+ @override
+ void retainWhere(bool Function(E) test) {
+ _base.retainWhere(test);
+ }
+
+ @Deprecated('Use cast instead')
+ @override
+ Queue<T> retype<T>() => cast<T>();
+
+ @override
+ E removeFirst() => _base.removeFirst();
+
+ @override
+ E removeLast() => _base.removeLast();
+}
+
+/// A [Map] that delegates all operations to a base map.
+///
+/// This class can be used to hide non-`Map` methods of an object that extends
+/// `Map`, or it can be extended to add extra functionality on top of an
+/// existing map object.
+class DelegatingMap<K, V> implements Map<K, V> {
+ final Map<K, V> _base;
+
+ const DelegatingMap(Map<K, V> base) : _base = base;
+
+ /// Creates a wrapper that asserts the types of keys and values in [base].
+ ///
+ /// This soundly converts a [Map] without generic types to a `Map<K, V>` by
+ /// asserting that its keys are instances of `E` and its values are instances
+ /// of `V` whenever they're accessed. If they're not, it throws a [TypeError].
+ /// Note that even if an operation throws a [TypeError], it may still mutate
+ /// the underlying collection.
+ ///
+ /// This forwards all operations to [base], so any changes in [base] will be
+ /// reflected in `this`. If [base] is already a `Map<K, V>`, it's returned
+ /// unmodified.
+ @Deprecated('Use map.cast<K, V> instead.')
+ static Map<K, V> typed<K, V>(Map base) => base.cast<K, V>();
+
+ @override
+ V? operator [](Object? key) => _base[key];
+
+ @override
+ void operator []=(K key, V value) {
+ _base[key] = value;
+ }
+
+ @override
+ void addAll(Map<K, V> other) {
+ _base.addAll(other);
+ }
+
+ @override
+ void addEntries(Iterable<MapEntry<K, V>> entries) {
+ _base.addEntries(entries);
+ }
+
+ @override
+ void clear() {
+ _base.clear();
+ }
+
+ @override
+ Map<K2, V2> cast<K2, V2>() => _base.cast<K2, V2>();
+
+ @override
+ bool containsKey(Object? key) => _base.containsKey(key);
+
+ @override
+ bool containsValue(Object? value) => _base.containsValue(value);
+
+ @override
+ Iterable<MapEntry<K, V>> get entries => _base.entries;
+
+ @override
+ void forEach(void Function(K, V) f) {
+ _base.forEach(f);
+ }
+
+ @override
+ bool get isEmpty => _base.isEmpty;
+
+ @override
+ bool get isNotEmpty => _base.isNotEmpty;
+
+ @override
+ Iterable<K> get keys => _base.keys;
+
+ @override
+ int get length => _base.length;
+
+ @override
+ Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> Function(K, V) transform) =>
+ _base.map(transform);
+
+ @override
+ V putIfAbsent(K key, V Function() ifAbsent) =>
+ _base.putIfAbsent(key, ifAbsent);
+
+ @override
+ V? remove(Object? key) => _base.remove(key);
+
+ @override
+ void removeWhere(bool Function(K, V) test) => _base.removeWhere(test);
+
+ @Deprecated('Use cast instead')
+ Map<K2, V2> retype<K2, V2>() => cast<K2, V2>();
+
+ @override
+ Iterable<V> get values => _base.values;
+
+ @override
+ String toString() => _base.toString();
+
+ @override
+ V update(K key, V Function(V) update, {V Function()? ifAbsent}) =>
+ _base.update(key, update, ifAbsent: ifAbsent);
+
+ @override
+ void updateAll(V Function(K, V) update) => _base.updateAll(update);
+}
+
+/// An unmodifiable [Set] view of the keys of a [Map].
+///
+/// The set delegates all operations to the underlying map.
+///
+/// A `Map` can only contain each key once, so its keys can always
+/// be viewed as a `Set` without any loss, even if the [Map.keys]
+/// getter only shows an [Iterable] view of the keys.
+///
+/// Note that [lookup] is not supported for this set.
+class MapKeySet<E> extends _DelegatingIterableBase<E>
+ with UnmodifiableSetMixin<E> {
+ final Map<E, dynamic> _baseMap;
+
+ MapKeySet(this._baseMap);
+
+ @override
+ Iterable<E> get _base => _baseMap.keys;
+
+ @override
+ Set<T> cast<T>() {
+ if (this is MapKeySet<T>) {
+ return this as MapKeySet<T>;
+ }
+ return Set.castFrom<E, T>(this);
+ }
+
+ @override
+ bool contains(Object? element) => _baseMap.containsKey(element);
+
+ @override
+ bool get isEmpty => _baseMap.isEmpty;
+
+ @override
+ bool get isNotEmpty => _baseMap.isNotEmpty;
+
+ @override
+ int get length => _baseMap.length;
+
+ @override
+ String toString() => SetBase.setToString(this);
+
+ @override
+ bool containsAll(Iterable<Object?> other) => other.every(contains);
+
+ /// Returns a new set with the the elements of `this` that are not in [other].
+ ///
+ /// That is, the returned set contains all the elements of this [Set] that are
+ /// not elements of [other] according to `other.contains`.
+ ///
+ /// Note that the returned set will use the default equality operation, which
+ /// may be different than the equality operation `this` uses.
+ @override
+ Set<E> difference(Set<Object?> other) =>
+ where((element) => !other.contains(element)).toSet();
+
+ /// Returns a new set which is the intersection between `this` and [other].
+ ///
+ /// That is, the returned set contains all the elements of this [Set] that are
+ /// also elements of [other] according to `other.contains`.
+ ///
+ /// Note that the returned set will use the default equality operation, which
+ /// may be different than the equality operation `this` uses.
+ @override
+ Set<E> intersection(Set<Object?> other) => where(other.contains).toSet();
+
+ /// Throws an [UnsupportedError] since there's no corresponding method for
+ /// [Map]s.
+ @override
+ E lookup(Object? element) =>
+ throw UnsupportedError("MapKeySet doesn't support lookup().");
+
+ @Deprecated('Use cast instead')
+ @override
+ Set<T> retype<T>() => Set.castFrom<E, T>(this);
+
+ /// Returns a new set which contains all the elements of `this` and [other].
+ ///
+ /// That is, the returned set contains all the elements of this [Set] and all
+ /// the elements of [other].
+ ///
+ /// Note that the returned set will use the default equality operation, which
+ /// may be different than the equality operation `this` uses.
+ @override
+ Set<E> union(Set<E> other) => toSet()..addAll(other);
+}
+
+/// Creates a modifiable [Set] view of the values of a [Map].
+///
+/// The `Set` view assumes that the keys of the `Map` can be uniquely determined
+/// from the values. The `keyForValue` function passed to the constructor finds
+/// the key for a single value. The `keyForValue` function should be consistent
+/// with equality. If `value1 == value2` then `keyForValue(value1)` and
+/// `keyForValue(value2)` should be considered equal keys by the underlying map,
+/// and vice versa.
+///
+/// Modifying the set will modify the underlying map based on the key returned
+/// by `keyForValue`.
+///
+/// If the `Map` contents are not compatible with the `keyForValue` function,
+/// the set will not work consistently, and may give meaningless responses or do
+/// inconsistent updates.
+///
+/// This set can, for example, be used on a map from database record IDs to the
+/// records. It exposes the records as a set, and allows for writing both
+/// `recordSet.add(databaseRecord)` and `recordMap[id]`.
+///
+/// Effectively, the map will act as a kind of index for the set.
+class MapValueSet<K, V> extends _DelegatingIterableBase<V> implements Set<V> {
+ final Map<K, V> _baseMap;
+ final K Function(V) _keyForValue;
+
+ /// Creates a new [MapValueSet] based on [_baseMap].
+ ///
+ /// [_keyForValue] returns the key in the map that should be associated with
+ /// the given value. The set's notion of equality is identical to the equality
+ /// of the return values of [_keyForValue].
+ MapValueSet(this._baseMap, this._keyForValue);
+
+ @override
+ Iterable<V> get _base => _baseMap.values;
+
+ @override
+ Set<T> cast<T>() {
+ if (this is Set<T>) {
+ return this as Set<T>;
+ }
+ return Set.castFrom<V, T>(this);
+ }
+
+ @override
+ bool contains(Object? element) {
+ if (element is! V) return false;
+ var key = _keyForValue(element);
+
+ return _baseMap.containsKey(key);
+ }
+
+ @override
+ bool get isEmpty => _baseMap.isEmpty;
+
+ @override
+ bool get isNotEmpty => _baseMap.isNotEmpty;
+
+ @override
+ int get length => _baseMap.length;
+
+ @override
+ String toString() => toSet().toString();
+
+ @override
+ bool add(V value) {
+ var key = _keyForValue(value);
+ var result = false;
+ _baseMap.putIfAbsent(key, () {
+ result = true;
+ return value;
+ });
+ return result;
+ }
+
+ @override
+ void addAll(Iterable<V> elements) => elements.forEach(add);
+
+ @override
+ void clear() => _baseMap.clear();
+
+ @override
+ bool containsAll(Iterable<Object?> other) => other.every(contains);
+
+ /// Returns a new set with the the elements of `this` that are not in [other].
+ ///
+ /// That is, the returned set contains all the elements of this [Set] that are
+ /// not elements of [other] according to `other.contains`.
+ ///
+ /// Note that the returned set will use the default equality operation, which
+ /// may be different than the equality operation `this` uses.
+ @override
+ Set<V> difference(Set<Object?> other) =>
+ where((element) => !other.contains(element)).toSet();
+
+ /// Returns a new set which is the intersection between `this` and [other].
+ ///
+ /// That is, the returned set contains all the elements of this [Set] that are
+ /// also elements of [other] according to `other.contains`.
+ ///
+ /// Note that the returned set will use the default equality operation, which
+ /// may be different than the equality operation `this` uses.
+ @override
+ Set<V> intersection(Set<Object?> other) => where(other.contains).toSet();
+
+ @override
+ V? lookup(Object? element) {
+ if (element is! V) return null;
+ var key = _keyForValue(element);
+
+ return _baseMap[key];
+ }
+
+ @override
+ bool remove(Object? element) {
+ if (element is! V) return false;
+ var key = _keyForValue(element);
+
+ if (!_baseMap.containsKey(key)) return false;
+ _baseMap.remove(key);
+ return true;
+ }
+
+ @override
+ void removeAll(Iterable<Object?> elements) => elements.forEach(remove);
+
+ @override
+ void removeWhere(bool Function(V) test) {
+ var toRemove = <K>[];
+ _baseMap.forEach((key, value) {
+ if (test(value)) toRemove.add(key);
+ });
+ toRemove.forEach(_baseMap.remove);
+ }
+
+ @override
+ void retainAll(Iterable<Object?> elements) {
+ var valuesToRetain = Set<V>.identity();
+ for (var element in elements) {
+ if (element is! V) continue;
+ var key = _keyForValue(element);
+
+ if (!_baseMap.containsKey(key)) continue;
+ valuesToRetain.add(_baseMap[key] ?? null as V);
+ }
+
+ var keysToRemove = <K>[];
+ _baseMap.forEach((k, v) {
+ if (!valuesToRetain.contains(v)) keysToRemove.add(k);
+ });
+ keysToRemove.forEach(_baseMap.remove);
+ }
+
+ @override
+ void retainWhere(bool Function(V) test) =>
+ removeWhere((element) => !test(element));
+
+ @Deprecated('Use cast instead')
+ @override
+ Set<T> retype<T>() => Set.castFrom<V, T>(this);
+
+ /// Returns a new set which contains all the elements of `this` and [other].
+ ///
+ /// That is, the returned set contains all the elements of this [Set] and all
+ /// the elements of [other].
+ ///
+ /// Note that the returned set will use the default equality operation, which
+ /// may be different than the equality operation `this` uses.
+ @override
+ Set<V> union(Set<V> other) => toSet()..addAll(other);
+}
diff --git a/pkgs/collection/lib/wrappers.dart b/pkgs/collection/lib/wrappers.dart
new file mode 100644
index 0000000..be529ca
--- /dev/null
+++ b/pkgs/collection/lib/wrappers.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Import `collection.dart` instead.
+@Deprecated('Will be removed in collection 2.0.0.')
+library;
+
+export 'src/canonicalized_map.dart';
+export 'src/unmodifiable_wrappers.dart';
+export 'src/wrappers.dart';
diff --git a/pkgs/collection/pubspec.yaml b/pkgs/collection/pubspec.yaml
new file mode 100644
index 0000000..a1a3a0b
--- /dev/null
+++ b/pkgs/collection/pubspec.yaml
@@ -0,0 +1,18 @@
+name: collection
+version: 1.20.0-wip
+description: >-
+ Collections and utilities functions and classes related to collections.
+repository: https://github.com/dart-lang/core/tree/main/pkgs/collection
+issue_tracker: https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acollection
+
+topics:
+ - collections
+ - data-structures
+
+environment:
+ sdk: ^3.4.0
+
+dev_dependencies:
+ benchmark_harness: ^2.3.1
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
diff --git a/pkgs/collection/test/algorithms_test.dart b/pkgs/collection/test/algorithms_test.dart
new file mode 100644
index 0000000..4bc1d54
--- /dev/null
+++ b/pkgs/collection/test/algorithms_test.dart
@@ -0,0 +1,422 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Tests algorithm utilities.
+library;
+
+import 'dart:math';
+
+import 'package:collection/collection.dart';
+import 'package:collection/src/algorithms.dart';
+import 'package:test/test.dart';
+
+void main() {
+ void testShuffle(List list) {
+ var copy = list.toList();
+ shuffle(list);
+ expect(const UnorderedIterableEquality().equals(list, copy), isTrue);
+ }
+
+ test('Shuffle 0', () {
+ testShuffle([]);
+ });
+ test('Shuffle 1', () {
+ testShuffle([1]);
+ });
+ test('Shuffle 3', () {
+ testShuffle([1, 2, 3]);
+ });
+ test('Shuffle 10', () {
+ testShuffle([1, 2, 3, 4, 5, 1, 3, 5, 7, 9]);
+ });
+ test('Shuffle shuffles', () {
+ var l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
+ var c = l.toList();
+ var count = 0;
+ for (;;) {
+ shuffle(l);
+ if (!const ListEquality().equals(c, l)) return;
+ // Odds of not changing the order should be one in ~ 16! ~= 2e+13.
+ // Repeat this 10 times, and the odds of accidentally shuffling to the
+ // same result every time is disappearingly tiny.
+ count++;
+ // If this happens even once, it's ok to report it.
+ print('Failed shuffle $count times');
+ if (count == 10) fail("Shuffle didn't change order.");
+ }
+ });
+ test('Shuffle sublist', () {
+ var l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
+ var c = l.toList();
+ shuffle(l, 4, 12);
+ expect(const IterableEquality().equals(l.getRange(0, 4), c.getRange(0, 4)),
+ isTrue);
+ expect(
+ const IterableEquality().equals(l.getRange(12, 16), c.getRange(12, 16)),
+ isTrue);
+ expect(
+ const UnorderedIterableEquality()
+ .equals(l.getRange(4, 12), c.getRange(4, 12)),
+ isTrue);
+ });
+
+ test('binsearch0', () {
+ expect(binarySearch([], 2), equals(-1));
+ });
+
+ test('binsearch1', () {
+ expect(binarySearch([5], 2), equals(-1));
+ expect(binarySearch([5], 5), equals(0));
+ expect(binarySearch([5], 7), equals(-1));
+ });
+
+ test('binsearch3', () {
+ expect(binarySearch([0, 5, 10], -1), equals(-1));
+ expect(binarySearch([0, 5, 10], 0), equals(0));
+ expect(binarySearch([0, 5, 10], 2), equals(-1));
+ expect(binarySearch([0, 5, 10], 5), equals(1));
+ expect(binarySearch([0, 5, 10], 7), equals(-1));
+ expect(binarySearch([0, 5, 10], 10), equals(2));
+ expect(binarySearch([0, 5, 10], 12), equals(-1));
+ });
+
+ test('binsearchCompare0', () {
+ expect(binarySearch(<C>[], C(2), compare: compareC), equals(-1));
+ });
+
+ test('binsearchCompare1', () {
+ var l1 = [C(5)];
+ expect(binarySearch(l1, C(2), compare: compareC), equals(-1));
+ expect(binarySearch(l1, C(5), compare: compareC), equals(0));
+ expect(binarySearch(l1, C(7), compare: compareC), equals(-1));
+ });
+
+ test('binsearchCompare3', () {
+ var l3 = [C(0), C(5), C(10)];
+ expect(binarySearch(l3, C(-1), compare: compareC), equals(-1));
+ expect(binarySearch(l3, C(0), compare: compareC), equals(0));
+ expect(binarySearch(l3, C(2), compare: compareC), equals(-1));
+ expect(binarySearch(l3, C(5), compare: compareC), equals(1));
+ expect(binarySearch(l3, C(7), compare: compareC), equals(-1));
+ expect(binarySearch(l3, C(10), compare: compareC), equals(2));
+ expect(binarySearch(l3, C(12), compare: compareC), equals(-1));
+ });
+
+ test('lowerbound0', () {
+ expect(lowerBound([], 2), equals(0));
+ });
+
+ test('lowerbound1', () {
+ expect(lowerBound([5], 2), equals(0));
+ expect(lowerBound([5], 5), equals(0));
+ expect(lowerBound([5], 7), equals(1));
+ });
+
+ test('lowerbound3', () {
+ expect(lowerBound([0, 5, 10], -1), equals(0));
+ expect(lowerBound([0, 5, 10], 0), equals(0));
+ expect(lowerBound([0, 5, 10], 2), equals(1));
+ expect(lowerBound([0, 5, 10], 5), equals(1));
+ expect(lowerBound([0, 5, 10], 7), equals(2));
+ expect(lowerBound([0, 5, 10], 10), equals(2));
+ expect(lowerBound([0, 5, 10], 12), equals(3));
+ });
+
+ test('lowerboundRepeat', () {
+ expect(lowerBound([5, 5, 5], 5), equals(0));
+ expect(lowerBound([0, 5, 5, 5, 10], 5), equals(1));
+ });
+
+ test('lowerboundCompare0', () {
+ expect(lowerBound(<C>[], C(2), compare: compareC), equals(0));
+ });
+
+ test('lowerboundCompare1', () {
+ var l1 = [C(5)];
+ expect(lowerBound(l1, C(2), compare: compareC), equals(0));
+ expect(lowerBound(l1, C(5), compare: compareC), equals(0));
+ expect(lowerBound(l1, C(7), compare: compareC), equals(1));
+ });
+
+ test('lowerboundCompare3', () {
+ var l3 = [C(0), C(5), C(10)];
+ expect(lowerBound(l3, C(-1), compare: compareC), equals(0));
+ expect(lowerBound(l3, C(0), compare: compareC), equals(0));
+ expect(lowerBound(l3, C(2), compare: compareC), equals(1));
+ expect(lowerBound(l3, C(5), compare: compareC), equals(1));
+ expect(lowerBound(l3, C(7), compare: compareC), equals(2));
+ expect(lowerBound(l3, C(10), compare: compareC), equals(2));
+ expect(lowerBound(l3, C(12), compare: compareC), equals(3));
+ });
+
+ test('lowerboundCompareRepeat', () {
+ var l1 = [C(5), C(5), C(5)];
+ var l2 = [C(0), C(5), C(5), C(5), C(10)];
+ expect(lowerBound(l1, C(5), compare: compareC), equals(0));
+ expect(lowerBound(l2, C(5), compare: compareC), equals(1));
+ });
+
+ void testSort(String name,
+ void Function(List<int> elements, [int? start, int? end]) sort) {
+ test('${name}Random', () {
+ var random = Random();
+ for (var i = 0; i < 250; i += 10) {
+ var list = [
+ for (var j = 0; j < i; j++)
+ random.nextInt(25) // Expect some equal elements.
+ ];
+ sort(list);
+ for (var j = 1; j < i; j++) {
+ expect(list[j - 1], lessThanOrEqualTo(list[j]));
+ }
+ }
+ });
+
+ test('${name}SubRanges', () {
+ var l = [6, 5, 4, 3, 2, 1];
+ sort(l, 2, 4);
+ expect(l, equals([6, 5, 3, 4, 2, 1]));
+ sort(l, 1, 1);
+ expect(l, equals([6, 5, 3, 4, 2, 1]));
+ sort(l, 4, 6);
+ expect(l, equals([6, 5, 3, 4, 1, 2]));
+ sort(l, 0, 2);
+ expect(l, equals([5, 6, 3, 4, 1, 2]));
+ sort(l, 0, 6);
+ expect(l, equals([1, 2, 3, 4, 5, 6]));
+ });
+
+ test('$name insertionSortSpecialCases', () {
+ var l = [6, 6, 6, 6, 6, 6];
+ sort(l);
+ expect(l, equals([6, 6, 6, 6, 6, 6]));
+
+ l = [6, 6, 3, 3, 0, 0];
+ sort(l);
+ expect(l, equals([0, 0, 3, 3, 6, 6]));
+ });
+ }
+
+ int intId(int x) => x;
+ int intCompare(int a, int b) => a - b;
+ testSort('insertionSort', (list, [start, end]) {
+ insertionSortBy(list, intId, intCompare, start ?? 0, end ?? list.length);
+ });
+ testSort('mergeSort compare', (list, [start, end]) {
+ mergeSort(list,
+ start: start ?? 0, end: end ?? list.length, compare: intCompare);
+ });
+ testSort('mergeSort comparable', (list, [start, end]) {
+ mergeSort(list, start: start ?? 0, end: end ?? list.length);
+ });
+ testSort('mergeSortBy', (list, [start, end]) {
+ mergeSortBy(list, intId, intCompare, start ?? 0, end ?? list.length);
+ });
+ testSort('quickSort', (list, [start, end]) {
+ quickSort(list, intCompare, start ?? 0, end ?? list.length);
+ });
+ testSort('quickSortBy', (list, [start, end]) {
+ quickSortBy(list, intId, intCompare, start ?? 0, end ?? list.length);
+ });
+ test('MergeSortSpecialCases', () {
+ for (var size in [511, 512, 513]) {
+ // All equal.
+ var list = List<OC>.generate(size, (i) => OC(0, i));
+ mergeSort(list);
+ for (var i = 0; i < size; i++) {
+ expect(list[i].order, equals(i));
+ }
+ // All but one equal, first.
+ list[0] = OC(1, 0);
+ for (var i = 1; i < size; i++) {
+ list[i] = OC(0, i);
+ }
+ mergeSort(list);
+ for (var i = 0; i < size - 1; i++) {
+ expect(list[i].order, equals(i + 1));
+ }
+ expect(list[size - 1].order, equals(0));
+
+ // All but one equal, last.
+ for (var i = 0; i < size - 1; i++) {
+ list[i] = OC(0, i);
+ }
+ list[size - 1] = OC(-1, size - 1);
+ mergeSort(list);
+ expect(list[0].order, equals(size - 1));
+ for (var i = 1; i < size; i++) {
+ expect(list[i].order, equals(i - 1));
+ }
+
+ // Reversed.
+ for (var i = 0; i < size; i++) {
+ list[i] = OC(size - 1 - i, i);
+ }
+ mergeSort(list);
+ for (var i = 0; i < size; i++) {
+ expect(list[i].id, equals(i));
+ expect(list[i].order, equals(size - 1 - i));
+ }
+ }
+ });
+
+ void testSortBy(
+ String name,
+ void Function<T, K>(List<T> elements, K Function(T element) keyOf,
+ int Function(K a, K b) compare,
+ [int start, int end])
+ sort) {
+ for (var n in [0, 1, 2, 10, 75, 250]) {
+ var name2 = name;
+ test('$name2: Same #$n', () {
+ var list = List<OC>.generate(n, (i) => OC(i, 0));
+ // Should succeed. Bad implementations of, e.g., quicksort can diverge.
+ sort(list, _ocOrder, _compareInt);
+ });
+ test('$name: Pre-sorted #$n', () {
+ var list = List<OC>.generate(n, (i) => OC(-i, i));
+ var expected = list.toList();
+ sort(list, _ocOrder, _compareInt);
+ // Elements have not moved.
+ expect(list, expected);
+ });
+ test('$name: Reverse-sorted #$n', () {
+ var list = List<OC>.generate(n, (i) => OC(i, -i));
+ sort(list, _ocOrder, _compareInt);
+ expectSorted(list, _ocOrder, _compareInt);
+ });
+ test('$name: Random #$n', () {
+ var random = Random();
+ var list = List<OC>.generate(n, (i) => OC(i, random.nextInt(n)));
+ sort(list, _ocOrder, _compareInt);
+ expectSorted(list, _ocOrder, _compareInt);
+ });
+ test('$name: Sublist #$n', () {
+ var random = Random();
+ var list = List<OC>.generate(n, (i) => OC(i, random.nextInt(n)));
+ var original = list.toList();
+ var start = n ~/ 4;
+ var end = start * 3;
+ sort(list, _ocOrder, _compareInt, start, end);
+ expectSorted(list, _ocOrder, _compareInt, start, end);
+ expect(list.sublist(0, start), original.sublist(0, start));
+ expect(list.sublist(end), original.sublist(end));
+ });
+ }
+ }
+
+ testSortBy('insertionSort', insertionSortBy);
+ testSortBy('mergeSort', mergeSortBy);
+ testSortBy('quickSortBy', quickSortBy);
+
+ test('MergeSortPreservesOrder', () {
+ var random = Random();
+ // Small case where only insertion call is called,
+ // larger case where the internal moving insertion sort is used
+ // larger cases with multiple splittings, numbers just around a power of 2.
+ for (var size in [8, 50, 511, 512, 513]) {
+ // Class OC compares using id.
+ // With size elements with id's in the range 0..size/4, a number of
+ // collisions are guaranteed. These should be sorted so that the 'order'
+ // part of the objects are still in order.
+ var list = [
+ for (var i = 0; i < size; i++) OC(random.nextInt(size >> 2), i)
+ ];
+ mergeSort(list);
+ var prev = list[0];
+ for (var i = 1; i < size; i++) {
+ var next = list[i];
+ expect(prev.id, lessThanOrEqualTo(next.id));
+ if (next.id == prev.id) {
+ expect(prev.order, lessThanOrEqualTo(next.order));
+ }
+ prev = next;
+ }
+ // Reverse compare on part of list.
+ List copy = list.toList();
+ var min = size >> 2;
+ var max = size - min;
+ mergeSort<OC>(list,
+ start: min, end: max, compare: (a, b) => b.compareTo(a));
+ prev = list[min];
+ for (var i = min + 1; i < max; i++) {
+ var next = list[i];
+ expect(prev.id, greaterThanOrEqualTo(next.id));
+ if (next.id == prev.id) {
+ expect(prev.order, lessThanOrEqualTo(next.order));
+ }
+ prev = next;
+ }
+ // Equals on OC objects is identity, so this means the parts before min,
+ // and the parts after max, didn't change at all.
+ expect(list.sublist(0, min), equals(copy.sublist(0, min)));
+ expect(list.sublist(max), equals(copy.sublist(max)));
+ }
+ });
+
+ test('Reverse', () {
+ var l = [6, 5, 4, 3, 2, 1];
+ reverse(l, 2, 4);
+ expect(l, equals([6, 5, 3, 4, 2, 1]));
+ reverse(l, 1, 1);
+ expect(l, equals([6, 5, 3, 4, 2, 1]));
+ reverse(l, 4, 6);
+ expect(l, equals([6, 5, 3, 4, 1, 2]));
+ reverse(l, 0, 2);
+ expect(l, equals([5, 6, 3, 4, 1, 2]));
+ reverse(l, 0, 6);
+ expect(l, equals([2, 1, 4, 3, 6, 5]));
+ });
+
+ test('mergeSort works when runtime generic is a subtype of the static type',
+ () {
+ // Regression test for https://github.com/dart-lang/collection/issues/317
+ final length = 1000; // Larger than _mergeSortLimit
+ // Out of order list, with first half guaranteed to empty first during
+ // merge.
+ final list = [
+ for (var i = 0; i < length / 2; i++) -i,
+ for (var i = 0; i < length / 2; i++) i + length,
+ ];
+ expect(() => mergeSort<num>(list), returnsNormally);
+ });
+}
+
+class C {
+ final int id;
+ C(this.id);
+}
+
+int compareC(C one, C other) => one.id - other.id;
+
+/// Class naturally ordered by its first constructor argument.
+class OC implements Comparable<OC> {
+ final int id;
+ final int order;
+ OC(this.id, this.order);
+
+ @override
+ int compareTo(OC other) => id - other.id;
+
+ @override
+ String toString() => 'OC[$id,$order]';
+}
+
+int _ocOrder(OC oc) => oc.order;
+
+int _compareInt(int a, int b) => a - b;
+
+/// Check that a list is sorted according to [compare] of [keyOf] of elements.
+void expectSorted<T, K>(
+ List<T> list, K Function(T element) keyOf, int Function(K a, K b) compare,
+ [int start = 0, int? end]) {
+ end ??= list.length;
+ if (start == end) return;
+ var prev = keyOf(list[start]);
+ for (var i = start + 1; i < end; i++) {
+ var next = keyOf(list[i]);
+ expect(compare(prev, next), isNonPositive);
+ prev = next;
+ }
+}
diff --git a/pkgs/collection/test/analysis_options.yaml b/pkgs/collection/test/analysis_options.yaml
new file mode 100644
index 0000000..899b0f3
--- /dev/null
+++ b/pkgs/collection/test/analysis_options.yaml
@@ -0,0 +1,8 @@
+include: ../analysis_options.yaml
+
+# Turn off the avoid_dynamic_calls lint for the test/ directory.
+analyzer:
+ errors:
+ avoid_dynamic_calls: ignore
+ inference_failure_on_collection_literal: ignore
+ inference_failure_on_instance_creation: ignore
diff --git a/pkgs/collection/test/boollist_test.dart b/pkgs/collection/test/boollist_test.dart
new file mode 100644
index 0000000..da7b736
--- /dev/null
+++ b/pkgs/collection/test/boollist_test.dart
@@ -0,0 +1,166 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Tests for BoolList.
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ bool generator(int index) {
+ if (index < 512) {
+ return index.isEven;
+ }
+ return false;
+ }
+
+ test('BoolList()', () {
+ expect(BoolList(1024, fill: false), List.filled(1024, false));
+
+ expect(BoolList(1024, fill: true), List.filled(1024, true));
+ });
+
+ test('BoolList.empty()', () {
+ expect(BoolList.empty(growable: true, capacity: 1024), []);
+
+ expect(BoolList.empty(growable: false, capacity: 1024), []);
+ });
+
+ test('BoolList.generate()', () {
+ expect(
+ BoolList.generate(1024, generator),
+ List.generate(1024, generator),
+ );
+ });
+
+ test('BoolList.of()', () {
+ var src = List.generate(1024, generator);
+ expect(BoolList.of(src), src);
+ });
+
+ group('[], []=', () {
+ test('RangeError', () {
+ var b = BoolList(1024, fill: false);
+
+ expect(() {
+ // ignore: unnecessary_statements
+ b[-1];
+ }, throwsRangeError);
+
+ expect(() {
+ // ignore: unnecessary_statements
+ b[1024];
+ }, throwsRangeError);
+ });
+
+ test('[], []=', () {
+ var b = BoolList(1024, fill: false);
+
+ bool posVal;
+ for (var pos = 0; pos < 1024; ++pos) {
+ posVal = generator(pos);
+ b[pos] = posVal;
+ expect(b[pos], posVal, reason: 'at pos $pos');
+ }
+ });
+ });
+
+ group('length', () {
+ test('shrink length', () {
+ var b = BoolList(1024, fill: true, growable: true);
+
+ b.length = 768;
+ expect(b, List.filled(768, true));
+
+ b.length = 128;
+ expect(b, List.filled(128, true));
+
+ b.length = 0;
+ expect(b, []);
+ });
+
+ test('expand from != 0', () {
+ var b = BoolList(256, fill: true, growable: true);
+
+ b.length = 384;
+ expect(b, List.filled(384, false)..fillRange(0, 256, true));
+
+ b.length = 2048;
+ expect(b, List.filled(2048, false)..fillRange(0, 256, true));
+ });
+
+ test('expand from = 0', () {
+ var b = BoolList(0, growable: true);
+ expect(b.length, 0);
+
+ b.length = 256;
+ expect(b, List.filled(256, false));
+ });
+
+ test('throw UnsupportedError', () {
+ expect(() {
+ BoolList(1024).length = 512;
+ }, throwsUnsupportedError);
+ });
+ });
+
+ group('fillRange', () {
+ test('In one word', () {
+ expect(
+ BoolList(1024)..fillRange(32, 64, true),
+ List.filled(1024, false)..fillRange(32, 64, true),
+ );
+
+ expect(
+ // BoolList.filled constructor isn't used due internal usage of
+ // fillRange
+ BoolList.generate(1024, (i) => true)..fillRange(32, 64, false),
+ List.filled(1024, true)..fillRange(32, 64, false),
+ );
+ });
+
+ test('In several words', () {
+ expect(
+ BoolList(1024)..fillRange(32, 128, true),
+ List.filled(1024, false)..fillRange(32, 128, true),
+ );
+
+ expect(
+ // BoolList.filled constructor isn't used due internal usage of
+ // fillRange
+ BoolList.generate(1024, (i) => true)..fillRange(32, 128, false),
+ List.filled(1024, true)..fillRange(32, 128, false),
+ );
+ });
+ });
+
+ group('Iterator', () {
+ test('Iterator', () {
+ var b = BoolList.generate(1024, generator);
+ var iter = b.iterator;
+
+ expect(iter.current, false);
+ for (var i = 0; i < 1024; i++) {
+ expect(iter.moveNext(), true);
+
+ expect(iter.current, generator(i), reason: 'at pos $i');
+ }
+
+ expect(iter.moveNext(), false);
+ expect(iter.current, false);
+ });
+
+ test('throw ConcurrentModificationError', () {
+ var b = BoolList(1024, fill: true, growable: true);
+
+ var iter = b.iterator;
+
+ iter.moveNext();
+ b.length = 512;
+ expect(() {
+ iter.moveNext();
+ }, throwsConcurrentModificationError);
+ });
+ });
+}
diff --git a/pkgs/collection/test/canonicalized_map_test.dart b/pkgs/collection/test/canonicalized_map_test.dart
new file mode 100644
index 0000000..aadb734
--- /dev/null
+++ b/pkgs/collection/test/canonicalized_map_test.dart
@@ -0,0 +1,282 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('with an empty canonicalized map', () {
+ late CanonicalizedMap<int, String, String> map;
+
+ setUp(() {
+ map = CanonicalizedMap(int.parse, isValidKey: RegExp(r'^\d+$').hasMatch);
+ });
+
+ test('canonicalizes keys on set and get', () {
+ map['1'] = 'value';
+ expect(map['01'], equals('value'));
+ });
+
+ test('get returns null for uncanonicalizable key', () {
+ expect(map['foo'], isNull);
+ });
+
+ test('set affects nothing for uncanonicalizable key', () {
+ map['foo'] = 'value';
+ expect(map['foo'], isNull);
+ expect(map.containsKey('foo'), isFalse);
+ expect(map.length, equals(0));
+ });
+
+ test('canonicalizes keys for addAll', () {
+ map.addAll({'1': 'value 1', '2': 'value 2', '3': 'value 3'});
+ expect(map['01'], equals('value 1'));
+ expect(map['02'], equals('value 2'));
+ expect(map['03'], equals('value 3'));
+ });
+
+ test('uses the final value for addAll collisions', () {
+ map.addAll({'1': 'value 1', '01': 'value 2', '001': 'value 3'});
+ expect(map.length, equals(1));
+ expect(map['0001'], equals('value 3'));
+ });
+
+ test('clear clears the map', () {
+ map.addAll({'1': 'value 1', '2': 'value 2', '3': 'value 3'});
+ expect(map, isNot(isEmpty));
+ map.clear();
+ expect(map, isEmpty);
+ });
+
+ test('canonicalizes keys for containsKey', () {
+ map['1'] = 'value';
+ expect(map.containsKey('01'), isTrue);
+ expect(map.containsKey('2'), isFalse);
+ });
+
+ test('containsKey returns false for uncanonicalizable key', () {
+ expect(map.containsKey('foo'), isFalse);
+ });
+
+ test('canonicalizes keys for putIfAbsent', () {
+ map['1'] = 'value';
+ expect(map.putIfAbsent('01', () => throw Exception("shouldn't run")),
+ equals('value'));
+ expect(map.putIfAbsent('2', () => 'new value'), equals('new value'));
+ });
+
+ test('canonicalizes keys for remove', () {
+ map['1'] = 'value';
+ expect(map.remove('2'), isNull);
+ expect(map.remove('01'), equals('value'));
+ expect(map, isEmpty);
+ });
+
+ test('remove returns null for uncanonicalizable key', () {
+ expect(map.remove('foo'), isNull);
+ });
+
+ test('containsValue returns whether a value is in the map', () {
+ map['1'] = 'value';
+ expect(map.containsValue('value'), isTrue);
+ expect(map.containsValue('not value'), isFalse);
+ });
+
+ test('isEmpty returns whether the map is empty', () {
+ expect(map.isEmpty, isTrue);
+ map['1'] = 'value';
+ expect(map.isEmpty, isFalse);
+ map.remove('01');
+ expect(map.isEmpty, isTrue);
+ });
+
+ test("isNotEmpty returns whether the map isn't empty", () {
+ expect(map.isNotEmpty, isFalse);
+ map['1'] = 'value';
+ expect(map.isNotEmpty, isTrue);
+ map.remove('01');
+ expect(map.isNotEmpty, isFalse);
+ });
+
+ test('length returns the number of pairs in the map', () {
+ expect(map.length, equals(0));
+ map['1'] = 'value 1';
+ expect(map.length, equals(1));
+ map['01'] = 'value 01';
+ expect(map.length, equals(1));
+ map['02'] = 'value 02';
+ expect(map.length, equals(2));
+ });
+
+ test('uses original keys for keys', () {
+ map['001'] = 'value 1';
+ map['02'] = 'value 2';
+ expect(map.keys, equals(['001', '02']));
+ });
+
+ test('uses original keys for forEach', () {
+ map['001'] = 'value 1';
+ map['02'] = 'value 2';
+
+ var keys = [];
+ map.forEach((key, value) => keys.add(key));
+ expect(keys, equals(['001', '02']));
+ });
+
+ test('values returns all values in the map', () {
+ map.addAll(
+ {'1': 'value 1', '01': 'value 01', '2': 'value 2', '03': 'value 03'});
+
+ expect(map.values, equals(['value 01', 'value 2', 'value 03']));
+ });
+
+ test('entries returns all key-value pairs in the map', () {
+ map.addAll({
+ '1': 'value 1',
+ '01': 'value 01',
+ '2': 'value 2',
+ });
+
+ var entries = map.entries.toList();
+ expect(entries[0].key, '01');
+ expect(entries[0].value, 'value 01');
+ expect(entries[1].key, '2');
+ expect(entries[1].value, 'value 2');
+ });
+
+ test('addEntries adds key-value pairs to the map', () {
+ map.addEntries([
+ const MapEntry('1', 'value 1'),
+ const MapEntry('01', 'value 01'),
+ const MapEntry('2', 'value 2'),
+ ]);
+ expect(map, {'01': 'value 01', '2': 'value 2'});
+ });
+
+ test('cast returns a new map instance', () {
+ expect(map.cast<Pattern, Pattern>(), isNot(same(map)));
+ });
+ });
+
+ group('CanonicalizedMap builds an informative string representation', () {
+ dynamic map;
+ setUp(() {
+ map = CanonicalizedMap<int, String, dynamic>(int.parse,
+ isValidKey: RegExp(r'^\d+$').hasMatch);
+ });
+
+ test('for an empty map', () {
+ expect(map.toString(), equals('{}'));
+ });
+
+ test('for a map with one value', () {
+ map.addAll({'1': 'value 1'});
+ expect(map.toString(), equals('{1: value 1}'));
+ });
+
+ test('for a map with multiple values', () {
+ map.addAll(
+ {'1': 'value 1', '01': 'value 01', '2': 'value 2', '03': 'value 03'});
+ expect(
+ map.toString(), equals('{01: value 01, 2: value 2, 03: value 03}'));
+ });
+
+ test('for a map with a loop', () {
+ map.addAll({'1': 'value 1', '2': map});
+ expect(map.toString(), equals('{1: value 1, 2: {...}}'));
+ });
+ });
+
+ group('CanonicalizedMap.from', () {
+ test('canonicalizes its keys', () {
+ var map = CanonicalizedMap.from(
+ {'1': 'value 1', '2': 'value 2', '3': 'value 3'}, int.parse);
+ expect(map['01'], equals('value 1'));
+ expect(map['02'], equals('value 2'));
+ expect(map['03'], equals('value 3'));
+ });
+
+ test('uses the final value for collisions', () {
+ var map = CanonicalizedMap.from(
+ {'1': 'value 1', '01': 'value 2', '001': 'value 3'}, int.parse);
+ expect(map.length, equals(1));
+ expect(map['0001'], equals('value 3'));
+ });
+ });
+
+ group('CanonicalizedMap.fromEntries', () {
+ test('canonicalizes its keys', () {
+ var map = CanonicalizedMap.fromEntries(
+ {'1': 'value 1', '2': 'value 2', '3': 'value 3'}.entries, int.parse);
+ expect(map['01'], equals('value 1'));
+ expect(map['02'], equals('value 2'));
+ expect(map['03'], equals('value 3'));
+ });
+
+ test('uses the final value for collisions', () {
+ var map = CanonicalizedMap.fromEntries(
+ {'1': 'value 1', '01': 'value 2', '001': 'value 3'}.entries,
+ int.parse);
+ expect(map.length, equals(1));
+ expect(map['0001'], equals('value 3'));
+ });
+ });
+
+ group('CanonicalizedMap.toMapOfCanonicalKeys', () {
+ test('convert to a `Map<C,V>`', () {
+ var map = CanonicalizedMap.from(
+ {'1': 'value 1', '2': 'value 2', '3': 'value 3'}, int.parse);
+
+ var map2 = map.toMapOfCanonicalKeys();
+
+ expect(map2, isNot(isA<CanonicalizedMap>()));
+
+ expect(map2[1], equals('value 1'));
+ expect(map2[2], equals('value 2'));
+ expect(map2[3], equals('value 3'));
+
+ expect(map2, equals({1: 'value 1', 2: 'value 2', 3: 'value 3'}));
+ });
+ });
+
+ group('CanonicalizedMap.toMap', () {
+ test('convert to a `Map<K,V>`', () {
+ var map = CanonicalizedMap.from(
+ {'1': 'value 1', '2': 'value 2', '3': 'value 3'}, int.parse);
+
+ var map2 = map.toMap();
+
+ expect(map2, isNot(isA<CanonicalizedMap>()));
+
+ expect(map2['1'], equals('value 1'));
+ expect(map2['2'], equals('value 2'));
+ expect(map2['3'], equals('value 3'));
+
+ expect(map2, equals({'1': 'value 1', '2': 'value 2', '3': 'value 3'}));
+ });
+ });
+
+ group('CanonicalizedMap.copy', () {
+ test('copy instance', () {
+ var map = CanonicalizedMap.from(
+ {'1': 'value 1', '2': 'value 2', '3': 'value 3'}, int.parse);
+
+ var map2 = map.copy();
+
+ expect(map2['01'], equals('value 1'));
+ expect(map2['02'], equals('value 2'));
+ expect(map2['03'], equals('value 3'));
+
+ expect(map2['1'], equals('value 1'));
+ expect(map2['2'], equals('value 2'));
+ expect(map2['3'], equals('value 3'));
+
+ expect(map2, equals({'1': 'value 1', '2': 'value 2', '3': 'value 3'}));
+
+ var map3 = Map.fromEntries(map2.entries);
+
+ expect(map3, equals({'1': 'value 1', '2': 'value 2', '3': 'value 3'}));
+ });
+ });
+}
diff --git a/pkgs/collection/test/combined_wrapper/iterable_test.dart b/pkgs/collection/test/combined_wrapper/iterable_test.dart
new file mode 100644
index 0000000..d5c90bf
--- /dev/null
+++ b/pkgs/collection/test/combined_wrapper/iterable_test.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ var iterable1 = Iterable.generate(3);
+ var iterable2 = Iterable.generate(3, (i) => i + 3);
+ var iterable3 = Iterable.generate(3, (i) => i + 6);
+
+ test('should combine multiple iterables when iterating', () {
+ var combined = CombinedIterableView([iterable1, iterable2, iterable3]);
+ expect(combined, [0, 1, 2, 3, 4, 5, 6, 7, 8]);
+ });
+
+ test('should combine multiple iterables with some empty ones', () {
+ var combined =
+ CombinedIterableView([iterable1, [], iterable2, [], iterable3, []]);
+ expect(combined, [0, 1, 2, 3, 4, 5, 6, 7, 8]);
+ });
+
+ test('should function as an empty iterable when no iterables are passed', () {
+ var empty = const CombinedIterableView([]);
+ expect(empty, isEmpty);
+ });
+
+ test('should function as an empty iterable with all empty iterables', () {
+ var empty = const CombinedIterableView([[], [], []]);
+ expect(empty, isEmpty);
+ });
+
+ test('should reflect changes from the underlying iterables', () {
+ var list1 = [];
+ var list2 = [];
+ var combined = CombinedIterableView([list1, list2]);
+ expect(combined, isEmpty);
+ list1.addAll([1, 2]);
+ list2.addAll([3, 4]);
+ expect(combined, [1, 2, 3, 4]);
+ expect(combined.last, 4);
+ expect(combined.first, 1);
+ });
+
+ test('should reflect changes from the iterable of iterables', () {
+ var iterables = <Iterable>[];
+ var combined = CombinedIterableView(iterables);
+ expect(combined, isEmpty);
+ expect(combined, hasLength(0));
+
+ iterables.add(iterable1);
+ expect(combined, isNotEmpty);
+ expect(combined, hasLength(3));
+
+ iterables.clear();
+ expect(combined, isEmpty);
+ expect(combined, hasLength(0));
+ });
+}
diff --git a/pkgs/collection/test/combined_wrapper/list_test.dart b/pkgs/collection/test/combined_wrapper/list_test.dart
new file mode 100644
index 0000000..2705e39
--- /dev/null
+++ b/pkgs/collection/test/combined_wrapper/list_test.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+import '../unmodifiable_collection_test.dart' as common;
+
+void main() {
+ var list1 = [1, 2, 3];
+ var list2 = [4, 5, 6];
+ var list3 = [7, 8, 9];
+ var concat = <int>[...list1, ...list2, ...list3];
+
+ // In every way possible this should test the same as an UnmodifiableListView.
+ common.testUnmodifiableList(
+ concat, CombinedListView([list1, list2, list3]), 'combineLists');
+
+ common.testUnmodifiableList(concat,
+ CombinedListView([list1, [], list2, [], list3, []]), 'combineLists');
+
+ test('should function as an empty list when no lists are passed', () {
+ var empty = CombinedListView([]);
+ expect(empty, isEmpty);
+ expect(empty.length, 0);
+ expect(() => empty[0], throwsRangeError);
+ });
+
+ test('should function as an empty list when only empty lists are passed', () {
+ var empty = CombinedListView([[], [], []]);
+ expect(empty, isEmpty);
+ expect(empty.length, 0);
+ expect(() => empty[0], throwsRangeError);
+ });
+
+ test('should reflect underlying changes back to the combined list', () {
+ var backing1 = <int>[];
+ var backing2 = <int>[];
+ var combined = CombinedListView([backing1, backing2]);
+ expect(combined, isEmpty);
+ backing1.addAll(list1);
+ expect(combined, list1);
+ backing2.addAll(list2);
+ expect(combined, backing1.toList()..addAll(backing2));
+ });
+
+ test('should reflect underlying changes from the list of lists', () {
+ var listOfLists = <List<int>>[];
+ var combined = CombinedListView(listOfLists);
+ expect(combined, isEmpty);
+ listOfLists.add(list1);
+ expect(combined, list1);
+ listOfLists.add(list2);
+ expect(combined, [...list1, ...list2]);
+ listOfLists.clear();
+ expect(combined, isEmpty);
+ });
+
+ test('should reflect underlying changes with a single list', () {
+ var backing1 = <int>[];
+ var combined = CombinedListView([backing1]);
+ expect(combined, isEmpty);
+ backing1.addAll(list1);
+ expect(combined, list1);
+ });
+}
diff --git a/pkgs/collection/test/combined_wrapper/map_test.dart b/pkgs/collection/test/combined_wrapper/map_test.dart
new file mode 100644
index 0000000..9b9a1a8
--- /dev/null
+++ b/pkgs/collection/test/combined_wrapper/map_test.dart
@@ -0,0 +1,118 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ var map1 = const {1: 1, 2: 2, 3: 3};
+ var map2 = const {4: 4, 5: 5, 6: 6};
+ var map3 = const {7: 7, 8: 8, 9: 9};
+ var map4 = const {1: -1, 2: -2, 3: -3};
+ var concat = SplayTreeMap<int, int>()
+ // The duplicates map appears first here but last in the CombinedMapView
+ // which has the opposite semantics of `concat`. Keys/values should be
+ // returned from the first map that contains them.
+ ..addAll(map4)
+ ..addAll(map1)
+ ..addAll(map2)
+ ..addAll(map3);
+
+ // In every way possible this should test the same as an UnmodifiableMapView.
+ _testReadMap(
+ concat, CombinedMapView([map1, map2, map3, map4]), 'CombinedMapView');
+
+ _testReadMap(
+ concat,
+ CombinedMapView([map1, {}, map2, {}, map3, {}, map4, {}]),
+ 'CombinedMapView (some empty)');
+
+ test('should function as an empty map when no maps are passed', () {
+ var empty = CombinedMapView([]);
+ expect(empty, isEmpty);
+ expect(empty.length, 0);
+ });
+
+ test('should function as an empty map when only empty maps are passed', () {
+ var empty = CombinedMapView([{}, {}, {}]);
+ expect(empty, isEmpty);
+ expect(empty.length, 0);
+ });
+
+ test('should reflect underlying changes back to the combined map', () {
+ var backing1 = <int, int>{};
+ var backing2 = <int, int>{};
+ var combined = CombinedMapView([backing1, backing2]);
+ expect(combined, isEmpty);
+ backing1.addAll(map1);
+ expect(combined, map1);
+ backing2.addAll(map2);
+ expect(combined, Map.from(backing1)..addAll(backing2));
+ });
+
+ test('should reflect underlying changes with a single map', () {
+ var backing1 = <int, int>{};
+ var combined = CombinedMapView([backing1]);
+ expect(combined, isEmpty);
+ backing1.addAll(map1);
+ expect(combined, map1);
+ });
+
+ test('re-iterating keys produces same result', () {
+ var combined = CombinedMapView([map1, map2, map3, map4]);
+ var keys = combined.keys;
+ expect(keys.toList(), keys.toList());
+ });
+}
+
+void _testReadMap(Map<int, int> original, Map<int, int> wrapped, String name) {
+ test('$name length', () {
+ expect(wrapped.length, equals(original.length));
+ });
+
+ test('$name isEmpty', () {
+ expect(wrapped.isEmpty, equals(original.isEmpty));
+ });
+
+ test('$name isNotEmpty', () {
+ expect(wrapped.isNotEmpty, equals(original.isNotEmpty));
+ });
+
+ test('$name operator[]', () {
+ expect(wrapped[0], equals(original[0]));
+ expect(wrapped[999], equals(original[999]));
+ });
+
+ test('$name containsKey', () {
+ expect(wrapped.containsKey(0), equals(original.containsKey(0)));
+ expect(wrapped.containsKey(999), equals(original.containsKey(999)));
+ });
+
+ test('$name containsValue', () {
+ expect(wrapped.containsValue(0), equals(original.containsValue(0)));
+ expect(wrapped.containsValue(999), equals(original.containsValue(999)));
+ });
+
+ test('$name forEach', () {
+ var origCnt = 0;
+ var wrapCnt = 0;
+ wrapped.forEach((k, v) {
+ wrapCnt += 1 << k + 3 * v;
+ });
+ original.forEach((k, v) {
+ origCnt += 1 << k + 3 * v;
+ });
+ expect(wrapCnt, equals(origCnt));
+ });
+
+ test('$name keys', () {
+ expect(wrapped.keys, orderedEquals(original.keys));
+ });
+
+ test('$name values', () {
+ expect(wrapped.values, orderedEquals(original.values));
+ });
+}
diff --git a/pkgs/collection/test/comparators_test.dart b/pkgs/collection/test/comparators_test.dart
new file mode 100644
index 0000000..ab48711
--- /dev/null
+++ b/pkgs/collection/test/comparators_test.dart
@@ -0,0 +1,121 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ var strings = [
+ '',
+ '\x00',
+ ' ',
+ '+',
+ '/',
+ '0',
+ '00',
+ '000',
+ '001',
+ '01',
+ '011',
+ '1',
+ '100',
+ '11',
+ '110',
+ '9',
+ ':',
+ '=',
+ '@',
+ 'A',
+ 'A0',
+ 'A000A',
+ 'A001A',
+ 'A00A',
+ 'A01A',
+ 'A0A',
+ 'A1A',
+ 'AA',
+ 'AAB',
+ 'AB',
+ 'Z',
+ '[',
+ '_',
+ '`',
+ 'a',
+ 'a0',
+ 'a000a',
+ 'a001a',
+ 'a00a',
+ 'a01a',
+ 'a0a',
+ 'a1a',
+ 'aa',
+ 'aab',
+ 'ab',
+ 'z',
+ '{',
+ '~'
+ ];
+
+ List<String> sortedBy(int Function(String, String)? compare) =>
+ strings.toList()
+ ..shuffle()
+ ..sort(compare);
+
+ test('String.compareTo', () {
+ expect(sortedBy(null), strings);
+ });
+
+ test('compareAsciiLowerCase', () {
+ expect(sortedBy(compareAsciiLowerCase), sortedBy((a, b) {
+ var delta = a.toLowerCase().compareTo(b.toLowerCase());
+ if (delta != 0) return delta;
+ if (a == b) return 0;
+ return a.compareTo(b);
+ }));
+ });
+
+ test('compareAsciiUpperCase', () {
+ expect(sortedBy(compareAsciiUpperCase), sortedBy((a, b) {
+ var delta = a.toUpperCase().compareTo(b.toUpperCase());
+ if (delta != 0) return delta;
+ if (a == b) return 0;
+ return a.compareTo(b);
+ }));
+ });
+
+ // Replace any digit sequence by ("0", value, length) as char codes.
+ // This will sort alphabetically (by charcode) the way digits sort
+ // numerically, and the leading 0 means it sorts like a digit
+ // compared to non-digits.
+ String replaceNumbers(String string) =>
+ string.replaceAllMapped(RegExp(r'\d+'), (m) {
+ var digits = m[0]!;
+ return String.fromCharCodes([0x30, int.parse(digits), digits.length]);
+ });
+
+ test('compareNatural', () {
+ expect(sortedBy(compareNatural),
+ sortedBy((a, b) => replaceNumbers(a).compareTo(replaceNumbers(b))));
+ });
+
+ test('compareAsciiLowerCaseNatural', () {
+ expect(sortedBy(compareAsciiLowerCaseNatural), sortedBy((a, b) {
+ var delta = replaceNumbers(a.toLowerCase())
+ .compareTo(replaceNumbers(b.toLowerCase()));
+ if (delta != 0) return delta;
+ if (a == b) return 0;
+ return a.compareTo(b);
+ }));
+ });
+
+ test('compareAsciiUpperCaseNatural', () {
+ expect(sortedBy(compareAsciiUpperCaseNatural), sortedBy((a, b) {
+ var delta = replaceNumbers(a.toUpperCase())
+ .compareTo(replaceNumbers(b.toUpperCase()));
+ if (delta != 0) return delta;
+ if (a == b) return 0;
+ return a.compareTo(b);
+ }));
+ });
+}
diff --git a/pkgs/collection/test/equality_map_test.dart b/pkgs/collection/test/equality_map_test.dart
new file mode 100644
index 0000000..d3aa9fc
--- /dev/null
+++ b/pkgs/collection/test/equality_map_test.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('uses the given equality', () {
+ var map = EqualityMap(const IterableEquality());
+ expect(map, isEmpty);
+
+ map[[1, 2, 3]] = 1;
+ expect(map, containsPair([1, 2, 3], 1));
+
+ map[[1, 2, 3]] = 2;
+ expect(map, containsPair([1, 2, 3], 2));
+
+ map[[2, 3, 4]] = 3;
+ expect(map, containsPair([1, 2, 3], 2));
+ expect(map, containsPair([2, 3, 4], 3));
+ });
+
+ test('EqualityMap.from() prefers the lattermost equivalent key', () {
+ var map = EqualityMap.from(const IterableEquality(), {
+ [1, 2, 3]: 1,
+ [2, 3, 4]: 2,
+ [1, 2, 3]: 3,
+ [2, 3, 4]: 4,
+ [1, 2, 3]: 5,
+ [1, 2, 3]: 6,
+ });
+
+ expect(map, containsPair([1, 2, 3], 6));
+ expect(map, containsPair([2, 3, 4], 4));
+ });
+}
diff --git a/pkgs/collection/test/equality_set_test.dart b/pkgs/collection/test/equality_set_test.dart
new file mode 100644
index 0000000..1022726
--- /dev/null
+++ b/pkgs/collection/test/equality_set_test.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('uses the given equality', () {
+ var set = EqualitySet(const IterableEquality());
+ expect(set, isEmpty);
+
+ var list1 = [1, 2, 3];
+ expect(set.add(list1), isTrue);
+ expect(set, contains([1, 2, 3]));
+ expect(set, contains(same(list1)));
+
+ var list2 = [1, 2, 3];
+ expect(set.add(list2), isFalse);
+ expect(set, contains([1, 2, 3]));
+ expect(set, contains(same(list1)));
+ expect(set, isNot(contains(same(list2))));
+
+ var list3 = [2, 3, 4];
+ expect(set.add(list3), isTrue);
+ expect(set, contains(same(list1)));
+ expect(set, contains(same(list3)));
+ });
+
+ test('EqualitySet.from() prefers the lattermost equivalent value', () {
+ var list1 = [1, 2, 3];
+ var list2 = [2, 3, 4];
+ var list3 = [1, 2, 3];
+ var list4 = [2, 3, 4];
+ var list5 = [1, 2, 3];
+ var list6 = [1, 2, 3];
+
+ var set = EqualitySet.from(
+ const IterableEquality(), [list1, list2, list3, list4, list5, list6]);
+
+ expect(set, contains(same(list1)));
+ expect(set, contains(same(list2)));
+ expect(set, isNot(contains(same(list3))));
+ expect(set, isNot(contains(same(list4))));
+ expect(set, isNot(contains(same(list5))));
+ expect(set, isNot(contains(same(list6))));
+ });
+}
diff --git a/pkgs/collection/test/equality_test.dart b/pkgs/collection/test/equality_test.dart
new file mode 100644
index 0000000..ce58df6
--- /dev/null
+++ b/pkgs/collection/test/equality_test.dart
@@ -0,0 +1,282 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Tests equality utilities.
+
+import 'dart:collection';
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ Element o(Comparable id) => Element(id);
+
+ // Lists that are point-wise equal, but not identical.
+ var list1 = [o(1), o(2), o(3), o(4), o(5)];
+ var list2 = [o(1), o(2), o(3), o(4), o(5)];
+ // Similar length list with equal elements in different order.
+ var list3 = [o(1), o(3), o(5), o(4), o(2)];
+
+ test('IterableEquality - List', () {
+ expect(const IterableEquality().equals(list1, list2), isTrue);
+ Equality iterId = const IterableEquality(IdentityEquality());
+ expect(iterId.equals(list1, list2), isFalse);
+ });
+
+ test('IterableEquality - LinkedSet', () {
+ var l1 = LinkedHashSet.from(list1);
+ var l2 = LinkedHashSet.from(list2);
+ expect(const IterableEquality().equals(l1, l2), isTrue);
+ Equality iterId = const IterableEquality(IdentityEquality());
+ expect(iterId.equals(l1, l2), isFalse);
+ });
+
+ test('ListEquality', () {
+ expect(const ListEquality().equals(list1, list2), isTrue);
+ Equality listId = const ListEquality(IdentityEquality());
+ expect(listId.equals(list1, list2), isFalse);
+ });
+
+ test('ListInequality length', () {
+ var list4 = [o(1), o(2), o(3), o(4), o(5), o(6)];
+ expect(const ListEquality().equals(list1, list4), isFalse);
+ expect(
+ const ListEquality(IdentityEquality()).equals(list1, list4), isFalse);
+ });
+
+ test('ListInequality value', () {
+ var list5 = [o(1), o(2), o(3), o(4), o(6)];
+ expect(const ListEquality().equals(list1, list5), isFalse);
+ expect(
+ const ListEquality(IdentityEquality()).equals(list1, list5), isFalse);
+ });
+
+ test('UnorderedIterableEquality', () {
+ expect(const UnorderedIterableEquality().equals(list1, list3), isTrue);
+ Equality uniterId = const UnorderedIterableEquality(IdentityEquality());
+ expect(uniterId.equals(list1, list3), isFalse);
+ });
+
+ test('UnorderedIterableInequality length', () {
+ var list6 = [o(1), o(3), o(5), o(4), o(2), o(1)];
+ expect(const UnorderedIterableEquality().equals(list1, list6), isFalse);
+ expect(
+ const UnorderedIterableEquality(IdentityEquality())
+ .equals(list1, list6),
+ isFalse);
+ });
+
+ test('UnorderedIterableInequality values', () {
+ var list7 = [o(1), o(3), o(5), o(4), o(6)];
+ expect(const UnorderedIterableEquality().equals(list1, list7), isFalse);
+ expect(
+ const UnorderedIterableEquality(IdentityEquality())
+ .equals(list1, list7),
+ isFalse);
+ });
+
+ test('SetEquality', () {
+ var set1 = HashSet.from(list1);
+ var set2 = LinkedHashSet.from(list3);
+ expect(const SetEquality().equals(set1, set2), isTrue);
+ Equality setId = const SetEquality(IdentityEquality());
+ expect(setId.equals(set1, set2), isFalse);
+ });
+
+ test('SetInequality length', () {
+ var list8 = [o(1), o(3), o(5), o(4), o(2), o(6)];
+ var set1 = HashSet.from(list1);
+ var set2 = LinkedHashSet.from(list8);
+ expect(const SetEquality().equals(set1, set2), isFalse);
+ expect(const SetEquality(IdentityEquality()).equals(set1, set2), isFalse);
+ });
+
+ test('SetInequality value', () {
+ var list7 = [o(1), o(3), o(5), o(4), o(6)];
+ var set1 = HashSet.from(list1);
+ var set2 = LinkedHashSet.from(list7);
+ expect(const SetEquality().equals(set1, set2), isFalse);
+ expect(const SetEquality(IdentityEquality()).equals(set1, set2), isFalse);
+ });
+
+ var map1a = {
+ 'x': [o(1), o(2), o(3)],
+ 'y': [true, false, null]
+ };
+ var map1b = {
+ 'x': [o(4), o(5), o(6)],
+ 'y': [false, true, null]
+ };
+ var map2a = {
+ 'x': [o(3), o(2), o(1)],
+ 'y': [false, true, null]
+ };
+ var map2b = {
+ 'x': [o(6), o(5), o(4)],
+ 'y': [null, false, true]
+ };
+ var l1 = [map1a, map1b];
+ var l2 = [map2a, map2b];
+ var s1 = {...l1};
+ var s2 = {map2b, map2a};
+
+ var i1 = Iterable.generate(l1.length, (i) => l1[i]);
+
+ test('RecursiveEquality', () {
+ const unordered = UnorderedIterableEquality();
+ expect(unordered.equals(map1a['x'], map2a['x']), isTrue);
+ expect(unordered.equals(map1a['y'], map2a['y']), isTrue);
+ expect(unordered.equals(map1b['x'], map2b['x']), isTrue);
+ expect(unordered.equals(map1b['y'], map2b['y']), isTrue);
+ const mapval = MapEquality(values: unordered);
+ expect(mapval.equals(map1a, map2a), isTrue);
+ expect(mapval.equals(map1b, map2b), isTrue);
+ const listmapval = ListEquality(mapval);
+ expect(listmapval.equals(l1, l2), isTrue);
+ const setmapval = SetEquality<Map>(mapval);
+ expect(setmapval.equals(s1, s2), isTrue);
+ });
+
+ group('DeepEquality', () {
+ group('unordered', () {
+ var colleq = const DeepCollectionEquality.unordered();
+
+ test('with identical collection types', () {
+ expect(colleq.equals(map1a['x'], map2a['x']), isTrue);
+ expect(colleq.equals(map1a['y'], map2a['y']), isTrue);
+ expect(colleq.equals(map1b['x'], map2b['x']), isTrue);
+ expect(colleq.equals(map1b['y'], map2b['y']), isTrue);
+ expect(colleq.equals(map1a, map2a), isTrue);
+ expect(colleq.equals(map1b, map2b), isTrue);
+ expect(colleq.equals(l1, l2), isTrue);
+ expect(colleq.equals(s1, s2), isTrue);
+ });
+
+ // TODO: https://github.com/dart-lang/collection/issues/208
+ test('comparing collections and iterables', () {
+ expect(colleq.equals(l1, i1), isFalse);
+ expect(colleq.equals(i1, l1), isFalse);
+ expect(colleq.equals(s1, i1), isFalse);
+ expect(colleq.equals(i1, s1), isTrue);
+ });
+ });
+
+ group('ordered', () {
+ var colleq = const DeepCollectionEquality();
+
+ test('with identical collection types', () {
+ expect(colleq.equals(l1, l1.toList()), isTrue);
+ expect(colleq.equals(s1, s1.toSet()), isTrue);
+ expect(colleq.equals(map1b, map1b.map(MapEntry.new)), isTrue);
+ expect(colleq.equals(i1, i1.map((i) => i)), isTrue);
+ expect(colleq.equals(map1a, map2a), isFalse);
+ expect(colleq.equals(map1b, map2b), isFalse);
+ expect(colleq.equals(l1, l2), isFalse);
+ expect(colleq.equals(s1, s2), isFalse);
+ });
+
+ // TODO: https://github.com/dart-lang/collection/issues/208
+ test('comparing collections and iterables', () {
+ expect(colleq.equals(l1, i1), isFalse);
+ expect(colleq.equals(i1, l1), isTrue);
+ expect(colleq.equals(s1, i1), isFalse);
+ expect(colleq.equals(i1, s1), isTrue);
+ });
+ });
+ });
+
+ test('CaseInsensitiveEquality', () {
+ var equality = const CaseInsensitiveEquality();
+ expect(equality.equals('foo', 'foo'), isTrue);
+ expect(equality.equals('fOo', 'FoO'), isTrue);
+ expect(equality.equals('FoO', 'fOo'), isTrue);
+ expect(equality.equals('foo', 'bar'), isFalse);
+ expect(equality.equals('fÕÕ', 'fõõ'), isFalse);
+
+ expect(equality.hash('foo'), equals(equality.hash('foo')));
+ expect(equality.hash('fOo'), equals(equality.hash('FoO')));
+ expect(equality.hash('FoO'), equals(equality.hash('fOo')));
+ expect(equality.hash('foo'), isNot(equals(equality.hash('bar'))));
+ expect(equality.hash('fÕÕ'), isNot(equals(equality.hash('fõõ'))));
+ });
+
+ group('EqualityBy should use a derived value for ', () {
+ var firstEquality = EqualityBy<List<String>, String>((e) => e.first);
+ var firstInsensitiveEquality = EqualityBy<List<String>, String>(
+ (e) => e.first, const CaseInsensitiveEquality());
+ var firstObjectEquality = EqualityBy<List<Object>, Object>(
+ (e) => e.first, const IterableEquality());
+
+ test('equality', () {
+ expect(firstEquality.equals(['foo', 'foo'], ['foo', 'bar']), isTrue);
+ expect(firstEquality.equals(['foo', 'foo'], ['bar', 'bar']), isFalse);
+ });
+
+ test('equality with an inner equality', () {
+ expect(firstInsensitiveEquality.equals(['fOo'], ['FoO']), isTrue);
+ expect(firstInsensitiveEquality.equals(['foo'], ['ffõõ']), isFalse);
+ });
+
+ test('hash', () {
+ expect(firstEquality.hash(['foo', 'bar']), 'foo'.hashCode);
+ });
+
+ test('hash with an inner equality', () {
+ expect(firstInsensitiveEquality.hash(['fOo']),
+ const CaseInsensitiveEquality().hash('foo'));
+ });
+
+ test('isValidKey', () {
+ expect(firstEquality.isValidKey(['foo']), isTrue);
+ expect(firstEquality.isValidKey('foo'), isFalse);
+ expect(firstEquality.isValidKey([1]), isFalse);
+ });
+
+ test('isValidKey with an inner equality', () {
+ expect(firstObjectEquality.isValidKey([[]]), isTrue);
+ expect(firstObjectEquality.isValidKey([{}]), isFalse);
+ });
+ });
+
+ test('Equality accepts null', () {
+ var ie = const IterableEquality();
+ var le = const ListEquality();
+ var se = const SetEquality();
+ var me = const MapEquality();
+ expect(ie.equals(null, null), true);
+ expect(ie.equals([], null), false);
+ expect(ie.equals(null, []), false);
+ expect(ie.hash(null), null.hashCode);
+
+ expect(le.equals(null, null), true);
+ expect(le.equals([], null), false);
+ expect(le.equals(null, []), false);
+ expect(le.hash(null), null.hashCode);
+
+ expect(se.equals(null, null), true);
+ expect(se.equals({}, null), false);
+ expect(se.equals(null, {}), false);
+ expect(se.hash(null), null.hashCode);
+
+ expect(me.equals(null, null), true);
+ expect(me.equals({}, null), false);
+ expect(me.equals(null, {}), false);
+ expect(me.hash(null), null.hashCode);
+ });
+}
+
+/// Wrapper objects for an `id` value.
+///
+/// Compares the `id` value by equality and for comparison.
+/// Allows creating simple objects that are equal without being identical.
+class Element implements Comparable<Element> {
+ final Comparable id;
+ const Element(this.id);
+ @override
+ int get hashCode => id.hashCode;
+ @override
+ bool operator ==(Object other) => other is Element && id == other.id;
+ @override
+ int compareTo(Element other) => id.compareTo(other.id);
+}
diff --git a/pkgs/collection/test/extensions_test.dart b/pkgs/collection/test/extensions_test.dart
new file mode 100644
index 0000000..6c5b45f
--- /dev/null
+++ b/pkgs/collection/test/extensions_test.dart
@@ -0,0 +1,2258 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:math' show Random, pow;
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('Iterable', () {
+ group('of any', () {
+ group('.whereNot', () {
+ test('empty', () {
+ expect(iterable([]).whereNot(unreachable), isEmpty);
+ });
+ test('none', () {
+ expect(iterable([1, 3, 5]).whereNot((e) => e.isOdd), isEmpty);
+ });
+ test('all', () {
+ expect(iterable([1, 3, 5]).whereNot((e) => e.isEven),
+ iterable([1, 3, 5]));
+ });
+ test('some', () {
+ expect(iterable([1, 2, 3, 4]).whereNot((e) => e.isEven),
+ iterable([1, 3]));
+ });
+ });
+ group('.sorted', () {
+ test('empty', () {
+ expect(iterable(<int>[]).sorted(unreachable), []);
+ });
+ test('singleton', () {
+ expect(iterable([1]).sorted(unreachable), [1]);
+ });
+ test('multiple', () {
+ expect(iterable([5, 2, 4, 3, 1]).sorted(cmpInt), [1, 2, 3, 4, 5]);
+ });
+ });
+ group('.shuffled', () {
+ test('empty', () {
+ expect(iterable(<int>[]).shuffled(), []);
+ });
+ test('singleton', () {
+ expect(iterable([1]).shuffled(), [1]);
+ });
+ test('multiple', () {
+ var input = iterable([1, 2, 3, 4, 5]);
+ var copy = [...input];
+ var shuffled = input.shuffled();
+ expect(const UnorderedIterableEquality().equals(input, shuffled),
+ isTrue);
+ // Check that the original list isn't touched
+ expect(input, copy);
+ });
+ });
+ group('.sortedBy', () {
+ test('empty', () {
+ expect(iterable(<int>[]).sortedBy(unreachable), []);
+ });
+ test('singleton', () {
+ expect(iterable(<int>[1]).sortedBy(unreachable), [1]);
+ });
+ test('multiple', () {
+ expect(iterable(<int>[3, 20, 100]).sortedBy(toString), [100, 20, 3]);
+ });
+ });
+ group('.sortedByCompare', () {
+ test('empty', () {
+ expect(
+ iterable(<int>[]).sortedByCompare(unreachable, unreachable), []);
+ });
+ test('singleton', () {
+ expect(iterable(<int>[2]).sortedByCompare(unreachable, unreachable),
+ [2]);
+ });
+ test('multiple', () {
+ expect(
+ iterable(<int>[30, 2, 100])
+ .sortedByCompare(toString, cmpParseInverse),
+ [100, 30, 2]);
+ });
+ });
+ group('isSorted', () {
+ test('empty', () {
+ expect(iterable(<int>[]).isSorted(unreachable), true);
+ });
+ test('single', () {
+ expect(iterable([1]).isSorted(unreachable), true);
+ });
+ test('same', () {
+ expect(iterable([1, 1, 1, 1]).isSorted(cmpInt), true);
+ expect(iterable([1, 0, 1, 0]).isSorted(cmpMod(2)), true);
+ });
+ test('multiple', () {
+ expect(iterable([1, 2, 3, 4]).isSorted(cmpInt), true);
+ expect(iterable([4, 3, 2, 1]).isSorted(cmpIntInverse), true);
+ expect(iterable([1, 2, 3, 0]).isSorted(cmpInt), false);
+ expect(iterable([4, 1, 2, 3]).isSorted(cmpInt), false);
+ expect(iterable([4, 3, 2, 1]).isSorted(cmpInt), false);
+ });
+ });
+ group('.isSortedBy', () {
+ test('empty', () {
+ expect(iterable(<int>[]).isSortedBy(unreachable), true);
+ });
+ test('single', () {
+ expect(iterable([1]).isSortedBy(toString), true);
+ });
+ test('same', () {
+ expect(iterable([1, 1, 1, 1]).isSortedBy(toString), true);
+ });
+ test('multiple', () {
+ expect(iterable([1, 2, 3, 4]).isSortedBy(toString), true);
+ expect(iterable([4, 3, 2, 1]).isSortedBy(toString), false);
+ expect(iterable([1000, 200, 30, 4]).isSortedBy(toString), true);
+ expect(iterable([1, 2, 3, 0]).isSortedBy(toString), false);
+ expect(iterable([4, 1, 2, 3]).isSortedBy(toString), false);
+ expect(iterable([4, 3, 2, 1]).isSortedBy(toString), false);
+ });
+ });
+ group('.isSortedByCompare', () {
+ test('empty', () {
+ expect(iterable(<int>[]).isSortedByCompare(unreachable, unreachable),
+ true);
+ });
+ test('single', () {
+ expect(iterable([1]).isSortedByCompare(toString, unreachable), true);
+ });
+ test('same', () {
+ expect(iterable([1, 1, 1, 1]).isSortedByCompare(toString, cmpParse),
+ true);
+ });
+ test('multiple', () {
+ expect(iterable([1, 2, 3, 4]).isSortedByCompare(toString, cmpParse),
+ true);
+ expect(
+ iterable([4, 3, 2, 1])
+ .isSortedByCompare(toString, cmpParseInverse),
+ true);
+ expect(
+ iterable([1000, 200, 30, 4])
+ .isSortedByCompare(toString, cmpString),
+ true);
+ expect(iterable([1, 2, 3, 0]).isSortedByCompare(toString, cmpParse),
+ false);
+ expect(iterable([4, 1, 2, 3]).isSortedByCompare(toString, cmpParse),
+ false);
+ expect(iterable([4, 3, 2, 1]).isSortedByCompare(toString, cmpParse),
+ false);
+ });
+ });
+ group('.forEachIndexed', () {
+ test('empty', () {
+ iterable([]).forEachIndexed(unreachable);
+ });
+ test('single', () {
+ var log = [];
+ iterable(['a']).forEachIndexed((i, s) {
+ log
+ ..add(i)
+ ..add(s);
+ });
+ expect(log, [0, 'a']);
+ });
+ test('multiple', () {
+ var log = [];
+ iterable(['a', 'b', 'c']).forEachIndexed((i, s) {
+ log
+ ..add(i)
+ ..add(s);
+ });
+ expect(log, [0, 'a', 1, 'b', 2, 'c']);
+ });
+ });
+ group('.forEachWhile', () {
+ test('empty', () {
+ iterable([]).forEachWhile(unreachable);
+ });
+ test('single true', () {
+ var log = [];
+ iterable(['a']).forEachWhile((s) {
+ log.add(s);
+ return true;
+ });
+ expect(log, ['a']);
+ });
+ test('single false', () {
+ var log = [];
+ iterable(['a']).forEachWhile((s) {
+ log.add(s);
+ return false;
+ });
+ expect(log, ['a']);
+ });
+ test('multiple one', () {
+ var log = [];
+ iterable(['a', 'b', 'c']).forEachWhile((s) {
+ log.add(s);
+ return false;
+ });
+ expect(log, ['a']);
+ });
+ test('multiple all', () {
+ var log = [];
+ iterable(['a', 'b', 'c']).forEachWhile((s) {
+ log.add(s);
+ return true;
+ });
+ expect(log, ['a', 'b', 'c']);
+ });
+ test('multiple some', () {
+ var log = [];
+ iterable(['a', 'b', 'c']).forEachWhile((s) {
+ log.add(s);
+ return s != 'b';
+ });
+ expect(log, ['a', 'b']);
+ });
+ });
+ group('.forEachIndexedWhile', () {
+ test('empty', () {
+ iterable([]).forEachIndexedWhile(unreachable);
+ });
+ test('single true', () {
+ var log = [];
+ iterable(['a']).forEachIndexedWhile((i, s) {
+ log
+ ..add(i)
+ ..add(s);
+ return true;
+ });
+ expect(log, [0, 'a']);
+ });
+ test('single false', () {
+ var log = [];
+ iterable(['a']).forEachIndexedWhile((i, s) {
+ log
+ ..add(i)
+ ..add(s);
+ return false;
+ });
+ expect(log, [0, 'a']);
+ });
+ test('multiple one', () {
+ var log = [];
+ iterable(['a', 'b', 'c']).forEachIndexedWhile((i, s) {
+ log
+ ..add(i)
+ ..add(s);
+ return false;
+ });
+ expect(log, [0, 'a']);
+ });
+ test('multiple all', () {
+ var log = [];
+ iterable(['a', 'b', 'c']).forEachIndexedWhile((i, s) {
+ log
+ ..add(i)
+ ..add(s);
+ return true;
+ });
+ expect(log, [0, 'a', 1, 'b', 2, 'c']);
+ });
+ test('multiple some', () {
+ var log = [];
+ iterable(['a', 'b', 'c']).forEachIndexedWhile((i, s) {
+ log
+ ..add(i)
+ ..add(s);
+ return s != 'b';
+ });
+ expect(log, [0, 'a', 1, 'b']);
+ });
+ });
+ group('.mapIndexed', () {
+ test('empty', () {
+ expect(iterable(<String>[]).mapIndexed(unreachable), isEmpty);
+ });
+ test('multiple', () {
+ expect(iterable(<String>['a', 'b']).mapIndexed((i, s) => [i, s]), [
+ [0, 'a'],
+ [1, 'b']
+ ]);
+ });
+ });
+ group('.whereIndexed', () {
+ test('empty', () {
+ expect(iterable(<String>[]).whereIndexed(unreachable), isEmpty);
+ });
+ test('none', () {
+ var trace = [];
+ int log(int a, int b) {
+ trace
+ ..add(a)
+ ..add(b);
+ return b;
+ }
+
+ expect(
+ iterable(<int>[1, 3, 5, 7])
+ .whereIndexed((i, x) => log(i, x).isEven),
+ isEmpty);
+ expect(trace, [0, 1, 1, 3, 2, 5, 3, 7]);
+ });
+ test('all', () {
+ expect(iterable(<int>[1, 3, 5, 7]).whereIndexed((i, x) => x.isOdd),
+ [1, 3, 5, 7]);
+ });
+ test('some', () {
+ expect(iterable(<int>[1, 3, 5, 7]).whereIndexed((i, x) => i.isOdd),
+ [3, 7]);
+ });
+ });
+ group('.whereNotIndexed', () {
+ test('empty', () {
+ expect(iterable(<int>[]).whereNotIndexed(unreachable), isEmpty);
+ });
+ test('none', () {
+ var trace = [];
+ int log(int a, int b) {
+ trace
+ ..add(a)
+ ..add(b);
+ return b;
+ }
+
+ expect(
+ iterable(<int>[1, 3, 5, 7])
+ .whereNotIndexed((i, x) => log(i, x).isOdd),
+ isEmpty);
+ expect(trace, [0, 1, 1, 3, 2, 5, 3, 7]);
+ });
+ test('all', () {
+ expect(
+ iterable(<int>[1, 3, 5, 7]).whereNotIndexed((i, x) => x.isEven),
+ [1, 3, 5, 7]);
+ });
+ test('some', () {
+ expect(iterable(<int>[1, 3, 5, 7]).whereNotIndexed((i, x) => i.isOdd),
+ [1, 5]);
+ });
+ });
+ group('.expandIndexed', () {
+ test('empty', () {
+ expect(iterable(<int>[]).expandIndexed<int>(unreachable), isEmpty);
+ });
+ test('empty result', () {
+ expect(iterable(['a', 'b']).expandIndexed((i, v) => []), isEmpty);
+ });
+ test('larger result', () {
+ expect(iterable(['a', 'b']).expandIndexed((i, v) => ['$i', v]),
+ ['0', 'a', '1', 'b']);
+ });
+ test('varying result', () {
+ expect(
+ iterable(['a', 'b'])
+ .expandIndexed((i, v) => i.isOdd ? ['$i', v] : []),
+ ['1', 'b']);
+ });
+ });
+ group('.reduceIndexed', () {
+ test('empty', () {
+ expect(() => iterable([]).reduceIndexed((i, a, b) => a),
+ throwsStateError);
+ });
+ test('single', () {
+ expect(iterable([1]).reduceIndexed(unreachable), 1);
+ });
+ test('multiple', () {
+ expect(
+ iterable([1, 4, 2])
+ .reduceIndexed((i, p, v) => p + (pow(i + 1, v) as int)),
+ 1 + 16 + 9);
+ });
+ });
+ group('.foldIndexed', () {
+ test('empty', () {
+ expect(iterable([]).foldIndexed(0, unreachable), 0);
+ });
+ test('single', () {
+ expect(
+ iterable([1]).foldIndexed('x', (i, a, b) => '$a:$i:$b'), 'x:0:1');
+ });
+ test('mulitple', () {
+ expect(iterable([1, 3, 9]).foldIndexed('x', (i, a, b) => '$a:$i:$b'),
+ 'x:0:1:1:3:2:9');
+ });
+ });
+ group('.firstWhereOrNull', () {
+ test('empty', () {
+ expect(iterable([]).firstWhereOrNull(unreachable), null);
+ });
+ test('none', () {
+ expect(iterable([1, 3, 7]).firstWhereOrNull(isEven), null);
+ });
+ test('single', () {
+ expect(iterable([0, 1, 2]).firstWhereOrNull(isOdd), 1);
+ });
+ test('first of multiple', () {
+ expect(iterable([0, 1, 3]).firstWhereOrNull(isOdd), 1);
+ });
+ });
+ group('.firstWhereIndexedOrNull', () {
+ test('empty', () {
+ expect(iterable([]).firstWhereIndexedOrNull(unreachable), null);
+ });
+ test('none', () {
+ expect(
+ iterable([1, 3, 7]).firstWhereIndexedOrNull((i, x) => x.isEven),
+ null);
+ expect(iterable([1, 3, 7]).firstWhereIndexedOrNull((i, x) => i < 0),
+ null);
+ });
+ test('single', () {
+ expect(iterable([0, 3, 6]).firstWhereIndexedOrNull((i, x) => x.isOdd),
+ 3);
+ expect(
+ iterable([0, 3, 6]).firstWhereIndexedOrNull((i, x) => i == 1), 3);
+ });
+ test('first of multiple', () {
+ expect(iterable([0, 3, 7]).firstWhereIndexedOrNull((i, x) => x.isOdd),
+ 3);
+ expect(
+ iterable([0, 3, 7]).firstWhereIndexedOrNull((i, x) => i.isEven),
+ 0);
+ });
+ });
+ group('.firstOrNull', () {
+ test('empty', () {
+ expect(iterable([]).firstOrNull, null);
+ });
+ test('single', () {
+ expect(iterable([1]).firstOrNull, 1);
+ });
+ test('first of multiple', () {
+ expect(iterable([1, 3, 5]).firstOrNull, 1);
+ });
+ });
+ group('.lastWhereOrNull', () {
+ test('empty', () {
+ expect(iterable([]).lastWhereOrNull(unreachable), null);
+ });
+ test('none', () {
+ expect(iterable([1, 3, 7]).lastWhereOrNull(isEven), null);
+ });
+ test('single', () {
+ expect(iterable([0, 1, 2]).lastWhereOrNull(isOdd), 1);
+ });
+ test('last of multiple', () {
+ expect(iterable([0, 1, 3]).lastWhereOrNull(isOdd), 3);
+ });
+ });
+ group('.lastWhereIndexedOrNull', () {
+ test('empty', () {
+ expect(iterable([]).lastWhereIndexedOrNull(unreachable), null);
+ });
+ test('none', () {
+ expect(iterable([1, 3, 7]).lastWhereIndexedOrNull((i, x) => x.isEven),
+ null);
+ expect(iterable([1, 3, 7]).lastWhereIndexedOrNull((i, x) => i < 0),
+ null);
+ });
+ test('single', () {
+ expect(
+ iterable([0, 3, 6]).lastWhereIndexedOrNull((i, x) => x.isOdd), 3);
+ expect(
+ iterable([0, 3, 6]).lastWhereIndexedOrNull((i, x) => i == 1), 3);
+ });
+ test('last of multiple', () {
+ expect(
+ iterable([0, 3, 7]).lastWhereIndexedOrNull((i, x) => x.isOdd), 7);
+ expect(iterable([0, 3, 7]).lastWhereIndexedOrNull((i, x) => i.isEven),
+ 7);
+ });
+ });
+ group('.lastOrNull', () {
+ test('empty', () {
+ expect(iterable([]).lastOrNull, null);
+ });
+ test('single', () {
+ expect(iterable([1]).lastOrNull, 1);
+ });
+ test('last of multiple', () {
+ expect(iterable([1, 3, 5]).lastOrNull, 5);
+ });
+ });
+ group('.singleWhereOrNull', () {
+ test('empty', () {
+ expect(iterable([]).singleWhereOrNull(unreachable), null);
+ });
+ test('none', () {
+ expect(iterable([1, 3, 7]).singleWhereOrNull(isEven), null);
+ });
+ test('single', () {
+ expect(iterable([0, 1, 2]).singleWhereOrNull(isOdd), 1);
+ });
+ test('multiple', () {
+ expect(iterable([0, 1, 3]).singleWhereOrNull(isOdd), null);
+ });
+ });
+ group('.singleWhereIndexedOrNull', () {
+ test('empty', () {
+ expect(iterable([]).singleWhereIndexedOrNull(unreachable), null);
+ });
+ test('none', () {
+ expect(
+ iterable([1, 3, 7]).singleWhereIndexedOrNull((i, x) => x.isEven),
+ null);
+ expect(iterable([1, 3, 7]).singleWhereIndexedOrNull((i, x) => i < 0),
+ null);
+ });
+ test('single', () {
+ expect(
+ iterable([0, 3, 6]).singleWhereIndexedOrNull((i, x) => x.isOdd),
+ 3);
+ expect(iterable([0, 3, 6]).singleWhereIndexedOrNull((i, x) => i == 1),
+ 3);
+ });
+ test('multiple', () {
+ expect(
+ iterable([0, 3, 7]).singleWhereIndexedOrNull((i, x) => x.isOdd),
+ null);
+ expect(
+ iterable([0, 3, 7]).singleWhereIndexedOrNull((i, x) => i.isEven),
+ null);
+ });
+ });
+ group('.singleOrNull', () {
+ test('empty', () {
+ expect(iterable([]).singleOrNull, null);
+ });
+ test('single', () {
+ expect(iterable([1]).singleOrNull, 1);
+ });
+ test('multiple', () {
+ expect(iterable([1, 3, 5]).singleOrNull, null);
+ });
+ });
+ group('.lastBy', () {
+ test('empty', () {
+ expect(iterable([]).lastBy((dynamic _) {}), {});
+ });
+ test('single', () {
+ expect(iterable([1]).lastBy(toString), {
+ '1': 1,
+ });
+ });
+ test('multiple', () {
+ expect(
+ iterable([1, 2, 3, 4, 5]).lastBy((x) => x.isEven),
+ {
+ false: 5,
+ true: 4,
+ },
+ );
+ });
+ });
+ group('.groupFoldBy', () {
+ test('empty', () {
+ expect(iterable([]).groupFoldBy(unreachable, unreachable), {});
+ });
+ test('single', () {
+ expect(iterable([1]).groupFoldBy(toString, (p, v) => [p, v]), {
+ '1': [null, 1]
+ });
+ });
+ test('multiple', () {
+ expect(
+ iterable([1, 2, 3, 4, 5]).groupFoldBy<bool, String>(
+ (x) => x.isEven, (p, v) => p == null ? '$v' : '$p:$v'),
+ {true: '2:4', false: '1:3:5'});
+ });
+ });
+ group('.groupSetsBy', () {
+ test('empty', () {
+ expect(iterable([]).groupSetsBy(unreachable), {});
+ });
+ test('multiple same', () {
+ expect(iterable([1, 1]).groupSetsBy(toString), {
+ '1': {1}
+ });
+ });
+ test('multiple', () {
+ expect(iterable([1, 2, 3, 4, 5, 1]).groupSetsBy((x) => x % 3), {
+ 1: {1, 4},
+ 2: {2, 5},
+ 0: {3}
+ });
+ });
+ });
+ group('.groupListsBy', () {
+ test('empty', () {
+ expect(iterable([]).groupListsBy(unreachable), {});
+ });
+ test('multiple saame', () {
+ expect(iterable([1, 1]).groupListsBy(toString), {
+ '1': [1, 1]
+ });
+ });
+ test('multiple', () {
+ expect(iterable([1, 2, 3, 4, 5, 1]).groupListsBy((x) => x % 3), {
+ 1: [1, 4, 1],
+ 2: [2, 5],
+ 0: [3]
+ });
+ });
+ });
+ group('.splitBefore', () {
+ test('empty', () {
+ expect(iterable([]).splitBefore(unreachable), []);
+ });
+ test('single', () {
+ expect(iterable([1]).splitBefore(unreachable), [
+ [1]
+ ]);
+ });
+ test('no split', () {
+ var trace = [];
+ bool log(int x) {
+ trace.add(x);
+ return false;
+ }
+
+ expect(iterable([1, 2, 3]).splitBefore(log), [
+ [1, 2, 3]
+ ]);
+ expect(trace, [2, 3]);
+ });
+ test('all splits', () {
+ expect(iterable([1, 2, 3]).splitBefore((x) => true), [
+ [1],
+ [2],
+ [3]
+ ]);
+ });
+ test('some splits', () {
+ expect(iterable([1, 2, 3]).splitBefore((x) => x.isEven), [
+ [1],
+ [2, 3]
+ ]);
+ });
+ });
+ group('.splitBeforeIndexed', () {
+ test('empty', () {
+ expect(iterable([]).splitBeforeIndexed(unreachable), []);
+ });
+ test('single', () {
+ expect(iterable([1]).splitBeforeIndexed(unreachable), [
+ [1]
+ ]);
+ });
+ test('no split', () {
+ var trace = [];
+ bool log(int i, int x) {
+ trace
+ ..add('$i')
+ ..add(x);
+ return false;
+ }
+
+ expect(iterable([1, 2, 3]).splitBeforeIndexed(log), [
+ [1, 2, 3]
+ ]);
+ expect(trace, ['1', 2, '2', 3]);
+ });
+ test('all splits', () {
+ expect(iterable([1, 2, 3]).splitBeforeIndexed((i, x) => true), [
+ [1],
+ [2],
+ [3]
+ ]);
+ });
+ test('some splits', () {
+ expect(iterable([1, 2, 3]).splitBeforeIndexed((i, x) => x.isEven), [
+ [1],
+ [2, 3]
+ ]);
+ expect(iterable([1, 2, 3]).splitBeforeIndexed((i, x) => i.isEven), [
+ [1, 2],
+ [3]
+ ]);
+ });
+ });
+ group('.splitAfter', () {
+ test('empty', () {
+ expect(iterable([]).splitAfter(unreachable), []);
+ });
+ test('single', () {
+ expect(iterable([1]).splitAfter((x) => false), [
+ [1]
+ ]);
+ expect(iterable([1]).splitAfter((x) => true), [
+ [1]
+ ]);
+ });
+ test('no split', () {
+ var trace = [];
+ bool log(int x) {
+ trace.add(x);
+ return false;
+ }
+
+ expect(iterable([1, 2, 3]).splitAfter(log), [
+ [1, 2, 3]
+ ]);
+ expect(trace, [1, 2, 3]);
+ });
+ test('all splits', () {
+ expect(iterable([1, 2, 3]).splitAfter((x) => true), [
+ [1],
+ [2],
+ [3]
+ ]);
+ });
+ test('some splits', () {
+ expect(iterable([1, 2, 3]).splitAfter((x) => x.isEven), [
+ [1, 2],
+ [3]
+ ]);
+ });
+ });
+ group('.splitAfterIndexed', () {
+ test('empty', () {
+ expect(iterable([]).splitAfterIndexed(unreachable), []);
+ });
+ test('single', () {
+ expect(iterable([1]).splitAfterIndexed((i, x) => true), [
+ [1]
+ ]);
+ expect(iterable([1]).splitAfterIndexed((i, x) => false), [
+ [1]
+ ]);
+ });
+ test('no split', () {
+ var trace = [];
+ bool log(int i, int x) {
+ trace
+ ..add('$i')
+ ..add(x);
+ return false;
+ }
+
+ expect(iterable([1, 2, 3]).splitAfterIndexed(log), [
+ [1, 2, 3]
+ ]);
+ expect(trace, ['0', 1, '1', 2, '2', 3]);
+ });
+ test('all splits', () {
+ expect(iterable([1, 2, 3]).splitAfterIndexed((i, x) => true), [
+ [1],
+ [2],
+ [3]
+ ]);
+ });
+ test('some splits', () {
+ expect(iterable([1, 2, 3]).splitAfterIndexed((i, x) => x.isEven), [
+ [1, 2],
+ [3]
+ ]);
+ expect(iterable([1, 2, 3]).splitAfterIndexed((i, x) => i.isEven), [
+ [1],
+ [2, 3]
+ ]);
+ });
+ });
+ group('.splitBetween', () {
+ test('empty', () {
+ expect(iterable([]).splitBetween(unreachable), []);
+ });
+ test('single', () {
+ expect(iterable([1]).splitBetween(unreachable), [
+ [1]
+ ]);
+ });
+ test('no split', () {
+ var trace = [];
+ bool log(int x, int y) {
+ trace.add([x, y]);
+ return false;
+ }
+
+ expect(iterable([1, 2, 3]).splitBetween(log), [
+ [1, 2, 3]
+ ]);
+ expect(trace, [
+ [1, 2],
+ [2, 3]
+ ]);
+ });
+ test('all splits', () {
+ expect(iterable([1, 2, 3]).splitBetween((x, y) => true), [
+ [1],
+ [2],
+ [3]
+ ]);
+ });
+ test('some splits', () {
+ expect(iterable([1, 2, 4]).splitBetween((x, y) => (x ^ y).isEven), [
+ [1, 2],
+ [4]
+ ]);
+ });
+ });
+ group('.splitBetweenIndexed', () {
+ test('empty', () {
+ expect(iterable([]).splitBetweenIndexed(unreachable), []);
+ });
+ test('single', () {
+ expect(iterable([1]).splitBetweenIndexed(unreachable), [
+ [1]
+ ]);
+ });
+ test('no split', () {
+ var trace = [];
+ bool log(int i, int x, int y) {
+ trace.add([i, x, y]);
+ return false;
+ }
+
+ expect(iterable([1, 2, 3]).splitBetweenIndexed(log), [
+ [1, 2, 3]
+ ]);
+ expect(trace, [
+ [1, 1, 2],
+ [2, 2, 3]
+ ]);
+ });
+ test('all splits', () {
+ expect(iterable([1, 2, 3]).splitBetweenIndexed((i, x, y) => true), [
+ [1],
+ [2],
+ [3]
+ ]);
+ });
+ test('some splits', () {
+ expect(
+ iterable([1, 2, 4])
+ .splitBetweenIndexed((i, x, y) => (x ^ y).isEven),
+ [
+ [1, 2],
+ [4]
+ ]);
+ expect(
+ iterable([1, 2, 4])
+ .splitBetweenIndexed((i, x, y) => (i ^ y).isEven),
+ [
+ [1, 2],
+ [4]
+ ]);
+ });
+ });
+ group('none', () {
+ test('empty', () {
+ expect(iterable([]).none(unreachable), true);
+ });
+ test('single', () {
+ expect(iterable([1]).none(isEven), true);
+ expect(iterable([1]).none(isOdd), false);
+ });
+ test('multiple', () {
+ expect(iterable([1, 3, 5, 7, 9, 11]).none(isEven), true);
+ expect(iterable([1, 3, 5, 7, 9, 10]).none(isEven), false);
+ expect(iterable([0, 3, 5, 7, 9, 11]).none(isEven), false);
+ expect(iterable([0, 2, 4, 6, 8, 10]).none(isEven), false);
+ });
+ });
+ });
+ group('of nullable', () {
+ group('.whereNotNull', () {
+ test('empty', () {
+ expect(
+ iterable(<int?>[])
+ .whereNotNull(), // ignore: deprecated_member_use_from_same_package
+ isEmpty);
+ });
+ test('single', () {
+ expect(
+ iterable(<int?>[
+ null
+ ]).whereNotNull(), // ignore: deprecated_member_use_from_same_package
+ isEmpty);
+ expect(
+ iterable(<int?>[
+ 1
+ ]).whereNotNull(), // ignore: deprecated_member_use_from_same_package
+ [1]);
+ });
+ test('multiple', () {
+ expect(
+ iterable(<int?>[
+ 1,
+ 3,
+ 5
+ ]).whereNotNull(), // ignore: deprecated_member_use_from_same_package
+ [1, 3, 5]);
+ expect(
+ iterable(<int?>[
+ null,
+ null,
+ null
+ ]).whereNotNull(), // ignore: deprecated_member_use_from_same_package
+ isEmpty);
+ expect(
+ iterable(<int?>[
+ 1,
+ null,
+ 3,
+ null,
+ 5
+ ]).whereNotNull(), // ignore: deprecated_member_use_from_same_package
+ [1, 3, 5]);
+ });
+ });
+ });
+ group('of number', () {
+ group('.sum', () {
+ test('empty', () {
+ expect(iterable(<int>[]).sum, same(0));
+ expect(iterable(<double>[]).sum, same(0.0));
+ expect(iterable(<num>[]).sum, same(0));
+ });
+ test('single', () {
+ expect(iterable(<int>[1]).sum, same(1));
+ expect(iterable(<double>[1.2]).sum, same(1.2));
+ expect(iterable(<num>[1]).sum, same(1));
+ expect(iterable(<num>[1.2]).sum, same(1.2));
+ });
+ test('multiple', () {
+ expect(iterable(<int>[1, 2, 4]).sum, 7);
+ expect(iterable(<double>[1.2, 3.5]).sum, 4.7);
+ expect(iterable(<num>[1, 3, 5]).sum, same(9));
+ expect(iterable(<num>[1.2, 3.5]).sum, 4.7);
+ expect(iterable(<num>[1.2, 2, 3.5]).sum, 6.7);
+ });
+ });
+ group('average', () {
+ test('empty', () {
+ expect(() => iterable(<int>[]).average, throwsStateError);
+ expect(() => iterable(<double>[]).average, throwsStateError);
+ expect(() => iterable(<num>[]).average, throwsStateError);
+ });
+ test('single', () {
+ expect(iterable(<int>[4]).average, same(4.0));
+ expect(iterable(<double>[3.5]).average, 3.5);
+ expect(iterable(<num>[4]).average, same(4.0));
+ expect(iterable(<num>[3.5]).average, 3.5);
+ });
+ test('multiple', () {
+ expect(iterable(<int>[1, 3, 5]).average, same(3.0));
+ expect(iterable(<int>[1, 3, 5, 9]).average, 4.5);
+ expect(iterable(<double>[1.0, 3.0, 5.0, 9.0]).average, 4.5);
+ expect(iterable(<num>[1, 3, 5, 9]).average, 4.5);
+ });
+ });
+ group('.min', () {
+ test('empty', () {
+ expect(() => iterable(<int>[]).min, throwsStateError);
+ expect(() => iterable(<double>[]).min, throwsStateError);
+ expect(() => iterable(<num>[]).min, throwsStateError);
+ });
+ test('single', () {
+ expect(iterable(<int>[1]).min, 1);
+ expect(iterable(<double>[1.0]).min, 1.0);
+ expect(iterable(<num>[1.0]).min, 1.0);
+ });
+ test('multiple', () {
+ expect(iterable(<int>[3, 1, 2]).min, 1);
+ expect(iterable(<double>[3.0, 1.0, 2.5]).min, 1.0);
+ expect(iterable(<num>[3, 1, 2.5]).min, 1.0);
+ });
+ test('nan', () {
+ expect(iterable(<double>[3.0, 1.0, double.nan]).min, isNaN);
+ expect(iterable(<num>[3.0, 1, double.nan]).min, isNaN);
+ });
+ });
+ group('.minOrNull', () {
+ test('empty', () {
+ expect(iterable(<int>[]).minOrNull, null);
+ expect(iterable(<double>[]).minOrNull, null);
+ expect(iterable(<num>[]).minOrNull, null);
+ });
+ test('single', () {
+ expect(iterable(<int>[1]).minOrNull, 1);
+ expect(iterable(<double>[1.0]).minOrNull, 1.0);
+ expect(iterable(<num>[1.0]).minOrNull, 1.0);
+ });
+ test('multiple', () {
+ expect(iterable(<int>[3, 1, 2]).minOrNull, 1);
+ expect(iterable(<double>[3.0, 1.0, 2.5]).minOrNull, 1.0);
+ expect(iterable(<num>[3, 1, 2.5]).minOrNull, 1.0);
+ });
+ test('nan', () {
+ expect(iterable(<double>[3.0, 1.0, double.nan]).minOrNull, isNaN);
+ expect(iterable(<num>[3.0, 1, double.nan]).minOrNull, isNaN);
+ });
+ });
+ group('.max', () {
+ test('empty', () {
+ expect(() => iterable(<int>[]).max, throwsStateError);
+ expect(() => iterable(<double>[]).max, throwsStateError);
+ expect(() => iterable(<num>[]).max, throwsStateError);
+ });
+ test('single', () {
+ expect(iterable(<int>[1]).max, 1);
+ expect(iterable(<double>[1.0]).max, 1.0);
+ expect(iterable(<num>[1.0]).max, 1.0);
+ });
+ test('multiple', () {
+ expect(iterable(<int>[3, 1, 2]).max, 3);
+ expect(iterable(<double>[3.0, 1.0, 2.5]).max, 3.0);
+ expect(iterable(<num>[3, 1, 2.5]).max, 3);
+ });
+ test('nan', () {
+ expect(iterable(<double>[3.0, 1.0, double.nan]).max, isNaN);
+ expect(iterable(<num>[3.0, 1, double.nan]).max, isNaN);
+ });
+ });
+ group('.maxOrNull', () {
+ test('empty', () {
+ expect(iterable(<int>[]).maxOrNull, null);
+ expect(iterable(<double>[]).maxOrNull, null);
+ expect(iterable(<num>[]).maxOrNull, null);
+ });
+ test('single', () {
+ expect(iterable(<int>[1]).maxOrNull, 1);
+ expect(iterable(<double>[1.0]).maxOrNull, 1.0);
+ expect(iterable(<num>[1.0]).maxOrNull, 1.0);
+ });
+ test('multiple', () {
+ expect(iterable(<int>[3, 1, 2]).maxOrNull, 3);
+ expect(iterable(<double>[3.0, 1.0, 2.5]).maxOrNull, 3.0);
+ expect(iterable(<num>[3, 1, 2.5]).maxOrNull, 3);
+ });
+ test('nan', () {
+ expect(iterable(<double>[3.0, 1.0, double.nan]).maxOrNull, isNaN);
+ expect(iterable(<num>[3.0, 1, double.nan]).maxOrNull, isNaN);
+ });
+ });
+ });
+ group('of iterable', () {
+ group('.flattened', () {
+ var empty = iterable(<int>[]);
+ test('empty', () {
+ expect(iterable(<Iterable<int>>[]).flattened, []);
+ });
+ test('multiple empty', () {
+ expect(iterable([empty, empty, empty]).flattened, []);
+ });
+ test('single value', () {
+ expect(
+ iterable(<Iterable>[
+ iterable([1])
+ ]).flattened,
+ [1]);
+ });
+ test('multiple', () {
+ expect(
+ iterable(<Iterable>[
+ iterable([1, 2]),
+ empty,
+ iterable([3, 4])
+ ]).flattened,
+ [1, 2, 3, 4]);
+ });
+ });
+ group('.flattenedToList', () {
+ var empty = iterable(<int>[]);
+ test('empty', () {
+ expect(iterable(<Iterable<int>>[]).flattenedToList, []);
+ });
+ test('multiple empty', () {
+ expect(iterable([empty, empty, empty]).flattenedToList, []);
+ });
+ test('single value', () {
+ expect(
+ iterable(<Iterable>[
+ iterable([1])
+ ]).flattenedToList,
+ [1]);
+ });
+ test('multiple', () {
+ expect(
+ iterable(<Iterable>[
+ iterable([1, 2]),
+ empty,
+ iterable([3, 4])
+ ]).flattenedToList,
+ [1, 2, 3, 4]);
+ });
+ });
+ group('.flattenedToSet', () {
+ var empty = iterable(<int>[]);
+ test('empty', () {
+ expect(iterable(<Iterable<int>>[]).flattenedToSet, <int>{});
+ });
+ test('multiple empty', () {
+ expect(iterable([empty, empty, empty]).flattenedToSet, <int>{});
+ });
+ test('single value', () {
+ expect(
+ iterable(<Iterable>[
+ iterable([1])
+ ]).flattenedToSet,
+ {1});
+ });
+ test('multiple', () {
+ expect(
+ iterable(<Iterable>[
+ iterable([1, 2]),
+ empty,
+ iterable([3, 4])
+ ]).flattenedToSet,
+ {1, 2, 3, 4});
+ expect(
+ iterable(<Iterable>[
+ iterable([1, 2, 3]),
+ empty,
+ iterable([2, 3, 4])
+ ]).flattenedToSet,
+ {1, 2, 3, 4});
+ });
+ });
+ });
+ group('of MapEntry', () {
+ group('.whereKey', () {
+ test('empty', () {
+ expect(
+ iterable(<MapEntry<String, int>>[]).whereKey(unreachable),
+ isEmpty,
+ );
+ });
+ test('single', () {
+ expect(
+ iterable([const MapEntry('a', 1)]).whereKey((k) => k == 'a'),
+ [const MapEntry('a', 1)],
+ );
+ expect(
+ iterable([const MapEntry('a', 1)]).whereKey((k) => k == 'b'),
+ isEmpty,
+ );
+ });
+ test('multiple', () {
+ expect(
+ iterable([
+ const MapEntry('a', 1),
+ const MapEntry('b', 2),
+ ]).whereKey((k) => k == 'a'),
+ [const MapEntry('a', 1)],
+ );
+ expect(
+ iterable([
+ const MapEntry('a', 1),
+ const MapEntry('b', 2),
+ ]).whereKey((k) => k == 'b'),
+ [const MapEntry('b', 2)],
+ );
+ expect(
+ iterable([
+ const MapEntry('a', 1),
+ const MapEntry('b', 2),
+ ]).whereKey((k) => k != 'c'),
+ [const MapEntry('a', 1), const MapEntry('b', 2)],
+ );
+ expect(
+ iterable([
+ const MapEntry('a', 1),
+ const MapEntry('b', 2),
+ const MapEntry('a', 3),
+ ]).whereKey((k) => k == 'a'),
+ [const MapEntry('a', 1), const MapEntry('a', 3)],
+ );
+ });
+ });
+ group('.whereValue', () {
+ test('empty', () {
+ expect(
+ iterable(<MapEntry<String, int>>[]).whereValue(unreachable),
+ isEmpty,
+ );
+ });
+ test('single', () {
+ expect(
+ iterable([const MapEntry('a', 1)]).whereValue((v) => v == 1),
+ [const MapEntry('a', 1)],
+ );
+ expect(
+ iterable([const MapEntry('a', 1)]).whereValue((v) => v == 2),
+ isEmpty,
+ );
+ });
+ test('multiple', () {
+ expect(
+ iterable([
+ const MapEntry('a', 1),
+ const MapEntry('b', 2),
+ ]).whereValue((v) => v == 1),
+ [const MapEntry('a', 1)],
+ );
+ expect(
+ iterable([
+ const MapEntry('a', 1),
+ const MapEntry('b', 2),
+ ]).whereValue((v) => v == 2),
+ [const MapEntry('b', 2)],
+ );
+ expect(
+ iterable([
+ const MapEntry('a', 1),
+ const MapEntry('b', 2),
+ ]).whereValue((v) => v != 3),
+ [const MapEntry('a', 1), const MapEntry('b', 2)],
+ );
+ expect(
+ iterable([
+ const MapEntry('a', 1),
+ const MapEntry('b', 2),
+ const MapEntry('c', 1),
+ ]).whereValue((v) => v == 1),
+ [const MapEntry('a', 1), const MapEntry('c', 1)],
+ );
+ expect(
+ iterable([
+ const MapEntry('a', 1),
+ const MapEntry('b', 2),
+ const MapEntry('a', 1),
+ ]).whereValue((v) => v == 1),
+ [const MapEntry('a', 1), const MapEntry('a', 1)],
+ );
+ });
+ });
+ group('.keys', () {
+ test('empty', () {
+ expect(iterable(<MapEntry<String, int>>[]).keys, isEmpty);
+ });
+ test('single', () {
+ expect(iterable([const MapEntry('a', 1)]).keys, ['a']);
+ });
+ test('multiple', () {
+ expect(
+ iterable([const MapEntry('a', 1), const MapEntry('b', 2)]).keys,
+ ['a', 'b'],
+ );
+ expect(
+ iterable([
+ const MapEntry('a', 1),
+ const MapEntry('b', 2),
+ const MapEntry('a', 3),
+ ]).keys,
+ ['a', 'b', 'a'],
+ );
+ });
+ });
+ group('.values', () {
+ test('empty', () {
+ expect(iterable(<MapEntry<String, int>>[]).values, isEmpty);
+ });
+ test('single', () {
+ expect(iterable([const MapEntry('a', 1)]).values, [1]);
+ });
+ test('multiple', () {
+ expect(
+ iterable([const MapEntry('a', 1), const MapEntry('b', 2)]).values,
+ [1, 2],
+ );
+ expect(
+ iterable([
+ const MapEntry('a', 1),
+ const MapEntry('b', 2),
+ const MapEntry('a', 3),
+ ]).values,
+ [1, 2, 3],
+ );
+ });
+ });
+ group('.toMap', () {
+ test('empty', () {
+ expect(iterable(<MapEntry<String, int>>[]).toMap(), <String, int>{});
+ });
+ test('single', () {
+ expect(iterable([const MapEntry('a', 1)]).toMap(), {'a': 1});
+ });
+ test('multiple', () {
+ expect(
+ iterable([const MapEntry('a', 1), const MapEntry('b', 2)]).toMap(),
+ {'a': 1, 'b': 2},
+ );
+ expect(
+ iterable([
+ const MapEntry('a', 1),
+ const MapEntry('b', 2),
+ const MapEntry('a', 3),
+ ]).toMap(),
+ {'b': 2, 'a': 3},
+ );
+ });
+ });
+ });
+ group('of comparable', () {
+ group('.min', () {
+ test('empty', () {
+ expect(() => iterable(<String>[]).min, throwsStateError);
+ });
+ test('single', () {
+ expect(iterable(<String>['a']).min, 'a');
+ });
+ test('multiple', () {
+ expect(iterable(<String>['c', 'a', 'b']).min, 'a');
+ });
+ });
+ group('.minOrNull', () {
+ test('empty', () {
+ expect(iterable(<String>[]).minOrNull, null);
+ });
+ test('single', () {
+ expect(iterable(<String>['a']).minOrNull, 'a');
+ });
+ test('multiple', () {
+ expect(iterable(<String>['c', 'a', 'b']).minOrNull, 'a');
+ });
+ });
+ group('.max', () {
+ test('empty', () {
+ expect(() => iterable(<String>[]).max, throwsStateError);
+ });
+ test('single', () {
+ expect(iterable(<String>['a']).max, 'a');
+ });
+ test('multiple', () {
+ expect(iterable(<String>['b', 'c', 'a']).max, 'c');
+ });
+ });
+ group('.maxOrNull', () {
+ test('empty', () {
+ expect(iterable(<String>[]).maxOrNull, null);
+ });
+ test('single', () {
+ expect(iterable(<String>['a']).maxOrNull, 'a');
+ });
+ test('multiple', () {
+ expect(iterable(<String>['b', 'c', 'a']).maxOrNull, 'c');
+ });
+ });
+ });
+ group('.sorted', () {
+ test('empty', () {
+ expect(iterable(<String>[]).sorted(unreachable), []);
+ expect(iterable(<String>[]).sorted(), []);
+ });
+ test('singleton', () {
+ expect(iterable(['a']).sorted(unreachable), ['a']);
+ expect(iterable(['a']).sorted(), ['a']);
+ });
+ test('multiple', () {
+ expect(iterable(<String>['5', '2', '4', '3', '1']).sorted(cmpParse),
+ ['1', '2', '3', '4', '5']);
+ expect(
+ iterable(<String>['5', '2', '4', '3', '1']).sorted(cmpParseInverse),
+ ['5', '4', '3', '2', '1']);
+ expect(iterable(<String>['5', '2', '4', '3', '1']).sorted(),
+ ['1', '2', '3', '4', '5']);
+ // Large enough to trigger quicksort.
+ var i256 = Iterable<int>.generate(256, (i) => i ^ 0x55);
+ var sorted256 = [...i256]..sort();
+ expect(i256.sorted(cmpInt), sorted256);
+ });
+ });
+ group('.isSorted', () {
+ test('empty', () {
+ expect(iterable(<String>[]).isSorted(unreachable), true);
+ expect(iterable(<String>[]).isSorted(), true);
+ });
+ test('single', () {
+ expect(iterable(['1']).isSorted(unreachable), true);
+ expect(iterable(['1']).isSorted(), true);
+ });
+ test('same', () {
+ expect(iterable(['1', '1', '1', '1']).isSorted(cmpParse), true);
+ expect(iterable(['1', '2', '0', '3']).isSorted(cmpStringLength), true);
+ expect(iterable(['1', '1', '1', '1']).isSorted(), true);
+ });
+ test('multiple', () {
+ expect(iterable(['1', '2', '3', '4']).isSorted(cmpParse), true);
+ expect(iterable(['1', '2', '3', '4']).isSorted(), true);
+ expect(iterable(['4', '3', '2', '1']).isSorted(cmpParseInverse), true);
+ expect(iterable(['1', '2', '3', '0']).isSorted(cmpParse), false);
+ expect(iterable(['1', '2', '3', '0']).isSorted(), false);
+ expect(iterable(['4', '1', '2', '3']).isSorted(cmpParse), false);
+ expect(iterable(['4', '1', '2', '3']).isSorted(), false);
+ expect(iterable(['4', '3', '2', '1']).isSorted(cmpParse), false);
+ expect(iterable(['4', '3', '2', '1']).isSorted(), false);
+ });
+ });
+ group('.sample', () {
+ test('errors', () {
+ expect(() => iterable([1]).sample(-1), throwsRangeError);
+ });
+ test('empty', () {
+ var empty = iterable(<int>[]);
+ expect(empty.sample(0), []);
+ expect(empty.sample(5), []);
+ });
+ test('single', () {
+ var single = iterable([1]);
+ expect(single.sample(0), []);
+ expect(single.sample(1), [1]);
+ expect(single.sample(5), [1]);
+ });
+ test('multiple', () {
+ var multiple = iterable([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
+ expect(multiple.sample(0), []);
+ var one = multiple.sample(1);
+ expect(one, hasLength(1));
+ expect(one.first, inInclusiveRange(1, 10));
+ var some = multiple.sample(3);
+ expect(some, hasLength(3));
+ expect(some[0], inInclusiveRange(1, 10));
+ expect(some[1], inInclusiveRange(1, 10));
+ expect(some[2], inInclusiveRange(1, 10));
+ expect(some[0], isNot(some[1]));
+ expect(some[0], isNot(some[2]));
+ expect(some[1], isNot(some[2]));
+
+ var seen = <int>{};
+ do {
+ seen.addAll(multiple.sample(3));
+ } while (seen.length < 10);
+ // Should eventually terminate.
+ });
+ test('random', () {
+ // Passing in a `Random` makes result deterministic.
+ var multiple = iterable([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
+ var seed = 12345;
+ var some = multiple.sample(5, Random(seed));
+ for (var i = 0; i < 10; i++) {
+ var other = multiple.sample(5, Random(seed));
+ expect(other, some);
+ }
+ });
+ group('shuffles results', () {
+ late Random random;
+ late Iterable<int> input;
+ setUp(() async {
+ input = iterable([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
+ random = Random(12345);
+ });
+ test('for partial samples of input', () {
+ var result = input.sample(9, random);
+ expect(result.length, 9);
+ expect(result.isSorted(cmpInt), isFalse);
+ });
+ test('for complete samples of input', () {
+ var result = input.sample(10, random);
+ expect(result.length, 10);
+ expect(result.isSorted(cmpInt), isFalse);
+ expect(
+ const UnorderedIterableEquality().equals(input, result), isTrue);
+ });
+ test('for overlengthed samples of input', () {
+ var result = input.sample(20, random);
+ expect(result.length, 10);
+ expect(result.isSorted(cmpInt), isFalse);
+ expect(
+ const UnorderedIterableEquality().equals(input, result), isTrue);
+ });
+ });
+ });
+ group('.elementAtOrNull', () {
+ test('empty', () async {
+ expect(iterable([]).elementAtOrNull(0), isNull);
+ });
+ test('negative index', () async {
+ expect(() => iterable([1]).elementAtOrNull(-1),
+ throwsA(isA<RangeError>()));
+ });
+ test('index within range', () async {
+ expect(iterable([1]).elementAtOrNull(0), 1);
+ });
+ test('index too high', () async {
+ expect(iterable([1]).elementAtOrNull(1), isNull);
+ });
+ });
+ group('.slices', () {
+ test('empty', () {
+ expect(iterable(<int>[]).slices(1), []);
+ });
+ test('with the same length as the iterable', () {
+ expect(iterable([1, 2, 3]).slices(3), [
+ [1, 2, 3]
+ ]);
+ });
+ test('with a longer length than the iterable', () {
+ expect(iterable([1, 2, 3]).slices(5), [
+ [1, 2, 3]
+ ]);
+ });
+ test('with a shorter length than the iterable', () {
+ expect(iterable([1, 2, 3]).slices(2), [
+ [1, 2],
+ [3]
+ ]);
+ });
+ test('with length divisible by the iterable\'s', () {
+ expect(iterable([1, 2, 3, 4]).slices(2), [
+ [1, 2],
+ [3, 4]
+ ]);
+ });
+ test('refuses negative length', () {
+ expect(() => iterable([1]).slices(-1), throwsRangeError);
+ });
+ test('refuses length 0', () {
+ expect(() => iterable([1]).slices(0), throwsRangeError);
+ });
+ test('regression #286, bug in slice', () {
+ var l1 = <int>[1, 2, 3, 4, 5, 6];
+ List<int> l2 = l1.slice(1, 5); // (2..5)
+ // This call would stack-overflow due to a lacking type promotion
+ // which caused the extension method to keep calling itself,
+ // instead of switching to the instance method on `ListSlice`.
+ //
+ // If successful, it would use the `2` argument as offset, instead
+ // of the `1` offset from the `slice` call above.
+ var l3 = l2.slice(2, 4); // (4..5)
+ expect(l3, [4, 5]);
+ expect(l3.toList(), [4, 5]);
+ });
+ });
+ });
+
+ group('Comparator', () {
+ test('.inverse', () {
+ var cmpStringInv = cmpString.inverse;
+ expect(cmpString('a', 'b'), isNegative);
+ expect(cmpStringInv('a', 'b'), isPositive);
+ expect(cmpString('aa', 'a'), isPositive);
+ expect(cmpStringInv('aa', 'a'), isNegative);
+ expect(cmpString('a', 'a'), isZero);
+ expect(cmpStringInv('a', 'a'), isZero);
+ });
+ test('.compareBy', () {
+ var cmpByLength = cmpInt.compareBy((String s) => s.length);
+ expect(cmpByLength('a', 'b'), 0);
+ expect(cmpByLength('aa', 'b'), isPositive);
+ expect(cmpByLength('b', 'aa'), isNegative);
+ var cmpByInverseLength = cmpIntInverse.compareBy((String s) => s.length);
+ expect(cmpByInverseLength('a', 'b'), 0);
+ expect(cmpByInverseLength('aa', 'b'), isNegative);
+ expect(cmpByInverseLength('b', 'aa'), isPositive);
+ });
+
+ test('.then', () {
+ var cmpLengthFirst = cmpStringLength.then(cmpString);
+ var strings = ['a', 'aa', 'ba', 'ab', 'b', 'aaa'];
+ strings.sort(cmpString);
+ expect(strings, ['a', 'aa', 'aaa', 'ab', 'b', 'ba']);
+ strings.sort(cmpLengthFirst);
+ expect(strings, ['a', 'b', 'aa', 'ab', 'ba', 'aaa']);
+
+ int cmpFirstLetter(String s1, String s2) =>
+ s1.runes.first - s2.runes.first;
+ var cmpLetterLength = cmpFirstLetter.then(cmpStringLength);
+ var cmpLengthLetter = cmpStringLength.then(cmpFirstLetter);
+ strings = ['a', 'ab', 'b', 'ba', 'aaa'];
+ strings.sort(cmpLetterLength);
+ expect(strings, ['a', 'ab', 'aaa', 'b', 'ba']);
+ strings.sort(cmpLengthLetter);
+ expect(strings, ['a', 'b', 'ab', 'ba', 'aaa']);
+ });
+ });
+
+ group('List', () {
+ group('of any', () {
+ group('.binarySearch', () {
+ test('empty', () {
+ expect(<int>[].binarySearch(1, unreachable), -1);
+ });
+ test('single', () {
+ expect([0].binarySearch(1, cmpInt), -1);
+ expect([1].binarySearch(1, cmpInt), 0);
+ expect([2].binarySearch(1, cmpInt), -1);
+ });
+ test('multiple', () {
+ expect([1, 2, 3, 4, 5, 6].binarySearch(3, cmpInt), 2);
+ expect([6, 5, 4, 3, 2, 1].binarySearch(3, cmpIntInverse), 3);
+ });
+ });
+ group('.binarySearchByCompare', () {
+ test('empty', () {
+ expect(<int>[].binarySearchByCompare(1, toString, cmpParse), -1);
+ });
+ test('single', () {
+ expect([0].binarySearchByCompare(1, toString, cmpParse), -1);
+ expect([1].binarySearchByCompare(1, toString, cmpParse), 0);
+ expect([2].binarySearchByCompare(1, toString, cmpParse), -1);
+ });
+ test('multiple', () {
+ expect(
+ [1, 2, 3, 4, 5, 6].binarySearchByCompare(3, toString, cmpParse),
+ 2);
+ expect(
+ [6, 5, 4, 3, 2, 1]
+ .binarySearchByCompare(3, toString, cmpParseInverse),
+ 3);
+ });
+ });
+ group('.binarySearchBy', () {
+ test('empty', () {
+ expect(<int>[].binarySearchBy(1, toString), -1);
+ });
+ test('single', () {
+ expect([0].binarySearchBy(1, toString), -1);
+ expect([1].binarySearchBy(1, toString), 0);
+ expect([2].binarySearchBy(1, toString), -1);
+ });
+ test('multiple', () {
+ expect([1, 2, 3, 4, 5, 6].binarySearchBy(3, toString), 2);
+ });
+ });
+
+ group('.lowerBound', () {
+ test('empty', () {
+ expect(<int>[].lowerBound(1, unreachable), 0);
+ });
+ test('single', () {
+ expect([0].lowerBound(1, cmpInt), 1);
+ expect([1].lowerBound(1, cmpInt), 0);
+ expect([2].lowerBound(1, cmpInt), 0);
+ });
+ test('multiple', () {
+ expect([1, 2, 3, 4, 5, 6].lowerBound(3, cmpInt), 2);
+ expect([6, 5, 4, 3, 2, 1].lowerBound(3, cmpIntInverse), 3);
+ expect([1, 2, 4, 5, 6].lowerBound(3, cmpInt), 2);
+ expect([6, 5, 4, 2, 1].lowerBound(3, cmpIntInverse), 3);
+ });
+ });
+ group('.lowerBoundByCompare', () {
+ test('empty', () {
+ expect(<int>[].lowerBoundByCompare(1, toString, cmpParse), 0);
+ });
+ test('single', () {
+ expect([0].lowerBoundByCompare(1, toString, cmpParse), 1);
+ expect([1].lowerBoundByCompare(1, toString, cmpParse), 0);
+ expect([2].lowerBoundByCompare(1, toString, cmpParse), 0);
+ });
+ test('multiple', () {
+ expect(
+ [1, 2, 3, 4, 5, 6].lowerBoundByCompare(3, toString, cmpParse), 2);
+ expect(
+ [6, 5, 4, 3, 2, 1]
+ .lowerBoundByCompare(3, toString, cmpParseInverse),
+ 3);
+ expect([1, 2, 4, 5, 6].lowerBoundByCompare(3, toString, cmpParse), 2);
+ expect(
+ [6, 5, 4, 2, 1].lowerBoundByCompare(3, toString, cmpParseInverse),
+ 3);
+ });
+ });
+ group('.lowerBoundBy', () {
+ test('empty', () {
+ expect(<int>[].lowerBoundBy(1, toString), 0);
+ });
+ test('single', () {
+ expect([0].lowerBoundBy(1, toString), 1);
+ expect([1].lowerBoundBy(1, toString), 0);
+ expect([2].lowerBoundBy(1, toString), 0);
+ });
+ test('multiple', () {
+ expect([1, 2, 3, 4, 5, 6].lowerBoundBy(3, toString), 2);
+ expect([1, 2, 4, 5, 6].lowerBoundBy(3, toString), 2);
+ });
+ });
+ group('sortRange', () {
+ test('errors', () {
+ expect(() => [1].sortRange(-1, 1, cmpInt), throwsArgumentError);
+ expect(() => [1].sortRange(0, 2, cmpInt), throwsArgumentError);
+ expect(() => [1].sortRange(1, 0, cmpInt), throwsArgumentError);
+ });
+ test('empty range', () {
+ <int>[].sortRange(0, 0, unreachable);
+ var list = [3, 2, 1];
+ list.sortRange(0, 0, unreachable);
+ list.sortRange(3, 3, unreachable);
+ expect(list, [3, 2, 1]);
+ });
+ test('single', () {
+ [1].sortRange(0, 1, unreachable);
+ var list = [3, 2, 1];
+ list.sortRange(0, 1, unreachable);
+ list.sortRange(1, 2, unreachable);
+ list.sortRange(2, 3, unreachable);
+ });
+ test('multiple', () {
+ var list = [9, 8, 7, 6, 5, 4, 3, 2, 1];
+ list.sortRange(2, 5, cmpInt);
+ expect(list, [9, 8, 5, 6, 7, 4, 3, 2, 1]);
+ list.sortRange(4, 8, cmpInt);
+ expect(list, [9, 8, 5, 6, 2, 3, 4, 7, 1]);
+ list.sortRange(3, 6, cmpIntInverse);
+ expect(list, [9, 8, 5, 6, 3, 2, 4, 7, 1]);
+ });
+ });
+ group('.sortBy', () {
+ test('empty', () {
+ expect(<int>[]..sortBy(unreachable), []);
+ });
+ test('singleton', () {
+ expect([1]..sortBy(unreachable), [1]);
+ });
+ test('multiple', () {
+ expect([3, 20, 100]..sortBy(toString), [100, 20, 3]);
+ });
+ group('range', () {
+ test('errors', () {
+ expect(() => [1].sortBy(toString, -1, 1), throwsArgumentError);
+ expect(() => [1].sortBy(toString, 0, 2), throwsArgumentError);
+ expect(() => [1].sortBy(toString, 1, 0), throwsArgumentError);
+ });
+ test('empty', () {
+ expect([5, 7, 4, 2, 3]..sortBy(unreachable, 2, 2), [5, 7, 4, 2, 3]);
+ });
+ test('singleton', () {
+ expect([5, 7, 4, 2, 3]..sortBy(unreachable, 2, 3), [5, 7, 4, 2, 3]);
+ });
+ test('multiple', () {
+ expect(
+ [5, 7, 40, 2, 3]..sortBy((a) => '$a', 1, 4), [5, 2, 40, 7, 3]);
+ });
+ });
+ });
+ group('.sortByCompare', () {
+ test('empty', () {
+ expect(<int>[]..sortByCompare(unreachable, unreachable), []);
+ });
+ test('singleton', () {
+ expect([2]..sortByCompare(unreachable, unreachable), [2]);
+ });
+ test('multiple', () {
+ expect([30, 2, 100]..sortByCompare(toString, cmpParseInverse),
+ [100, 30, 2]);
+ });
+ group('range', () {
+ test('errors', () {
+ expect(() => [1].sortByCompare(toString, cmpParse, -1, 1),
+ throwsArgumentError);
+ expect(() => [1].sortByCompare(toString, cmpParse, 0, 2),
+ throwsArgumentError);
+ expect(() => [1].sortByCompare(toString, cmpParse, 1, 0),
+ throwsArgumentError);
+ });
+ test('empty', () {
+ expect(
+ [3, 5, 7, 3, 1]..sortByCompare(unreachable, unreachable, 2, 2),
+ [3, 5, 7, 3, 1]);
+ });
+ test('singleton', () {
+ expect(
+ [3, 5, 7, 3, 1]..sortByCompare(unreachable, unreachable, 2, 3),
+ [3, 5, 7, 3, 1]);
+ });
+ test('multiple', () {
+ expect(
+ [3, 5, 7, 30, 1]
+ ..sortByCompare(toString, cmpParseInverse, 1, 4),
+ [3, 30, 7, 5, 1]);
+ });
+ });
+ });
+ group('.shuffleRange', () {
+ test('errors', () {
+ expect(() => [1].shuffleRange(-1, 1), throwsArgumentError);
+ expect(() => [1].shuffleRange(0, 2), throwsArgumentError);
+ expect(() => [1].shuffleRange(1, 0), throwsArgumentError);
+ });
+ test('empty range', () {
+ expect(<int>[]..shuffleRange(0, 0), []);
+ expect([1, 2, 3, 4]..shuffleRange(0, 0), [1, 2, 3, 4]);
+ expect([1, 2, 3, 4]..shuffleRange(4, 4), [1, 2, 3, 4]);
+ });
+ test('singleton range', () {
+ expect([1, 2, 3, 4]..shuffleRange(0, 1), [1, 2, 3, 4]);
+ expect([1, 2, 3, 4]..shuffleRange(3, 4), [1, 2, 3, 4]);
+ });
+ test('multiple', () {
+ var list = [1, 2, 3, 4, 5];
+ do {
+ list.shuffleRange(0, 3);
+ expect(list.getRange(3, 5), [4, 5]);
+ expect(list.getRange(0, 3), unorderedEquals([1, 2, 3]));
+ } while (const ListEquality().equals(list.sublist(0, 3), [1, 2, 3]));
+ // Won't terminate if shuffle *never* moves a value.
+ });
+ });
+ group('.reverseRange', () {
+ test('errors', () {
+ expect(() => [1].reverseRange(-1, 1), throwsArgumentError);
+ expect(() => [1].reverseRange(0, 2), throwsArgumentError);
+ expect(() => [1].reverseRange(1, 0), throwsArgumentError);
+ });
+ test('empty range', () {
+ expect(<int>[]..reverseRange(0, 0), []);
+ expect([1, 2, 3, 4]..reverseRange(0, 0), [1, 2, 3, 4]);
+ expect([1, 2, 3, 4]..reverseRange(4, 4), [1, 2, 3, 4]);
+ });
+ test('singleton range', () {
+ expect([1, 2, 3, 4]..reverseRange(0, 1), [1, 2, 3, 4]);
+ expect([1, 2, 3, 4]..reverseRange(3, 4), [1, 2, 3, 4]);
+ });
+ test('multiple', () {
+ var list = [1, 2, 3, 4, 5];
+ list.reverseRange(0, 3);
+ expect(list, [3, 2, 1, 4, 5]);
+ list.reverseRange(3, 5);
+ expect(list, [3, 2, 1, 5, 4]);
+ list.reverseRange(0, 5);
+ expect(list, [4, 5, 1, 2, 3]);
+ });
+ });
+ group('.swap', () {
+ test('errors', () {
+ expect(() => [1].swap(0, 1), throwsArgumentError);
+ expect(() => [1].swap(1, 1), throwsArgumentError);
+ expect(() => [1].swap(1, 0), throwsArgumentError);
+ expect(() => [1].swap(-1, 0), throwsArgumentError);
+ });
+ test('self swap', () {
+ expect([1]..swap(0, 0), [1]);
+ expect([1, 2, 3]..swap(1, 1), [1, 2, 3]);
+ });
+ test('actual swap', () {
+ expect([1, 2, 3]..swap(0, 2), [3, 2, 1]);
+ expect([1, 2, 3]..swap(2, 0), [3, 2, 1]);
+ expect([1, 2, 3]..swap(2, 1), [1, 3, 2]);
+ expect([1, 2, 3]..swap(1, 2), [1, 3, 2]);
+ expect([1, 2, 3]..swap(0, 1), [2, 1, 3]);
+ expect([1, 2, 3]..swap(1, 0), [2, 1, 3]);
+ });
+ });
+ group('.slice', () {
+ test('errors', () {
+ expect(() => [1].slice(-1, 1), throwsArgumentError);
+ expect(() => [1].slice(0, 2), throwsArgumentError);
+ expect(() => [1].slice(1, 0), throwsArgumentError);
+ var l = <int>[1];
+ var slice = l.slice(0, 1);
+ l.removeLast();
+ expect(() => slice.first, throwsConcurrentModificationError);
+ });
+ test('empty', () {
+ expect([].slice(0, 0), isEmpty);
+ });
+ test('modify', () {
+ var list = [1, 2, 3, 4, 5, 6, 7, 8, 9];
+ var slice = list.slice(2, 6);
+ expect(slice, [3, 4, 5, 6]);
+ slice.sort(cmpIntInverse);
+ expect(slice, [6, 5, 4, 3]);
+ expect(list, [1, 2, 6, 5, 4, 3, 7, 8, 9]);
+ });
+ });
+ group('equals', () {
+ test('empty', () {
+ expect(<Object>[].equals(<int>[]), true);
+ });
+ test('non-empty', () {
+ expect([1, 2.5, 'a'].equals([1.0, 2.5, 'a']), true);
+ expect([1, 2.5, 'a'].equals([1.0, 2.5, 'b']), false);
+ expect(
+ [
+ [1]
+ ].equals([
+ [1]
+ ]),
+ false);
+ expect(
+ [
+ [1]
+ ].equals([
+ [1]
+ ], const ListEquality()),
+ true);
+ });
+ });
+ group('.forEachIndexed', () {
+ test('empty', () {
+ [].forEachIndexed(unreachable);
+ });
+ test('single', () {
+ var log = [];
+ ['a'].forEachIndexed((i, s) {
+ log
+ ..add(i)
+ ..add(s);
+ });
+ expect(log, [0, 'a']);
+ });
+ test('multiple', () {
+ var log = [];
+ ['a', 'b', 'c'].forEachIndexed((i, s) {
+ log
+ ..add(i)
+ ..add(s);
+ });
+ expect(log, [0, 'a', 1, 'b', 2, 'c']);
+ });
+ });
+ group('.forEachWhile', () {
+ test('empty', () {
+ [].forEachWhile(unreachable);
+ });
+ test('single true', () {
+ var log = [];
+ ['a'].forEachWhile((s) {
+ log.add(s);
+ return true;
+ });
+ expect(log, ['a']);
+ });
+ test('single false', () {
+ var log = [];
+ ['a'].forEachWhile((s) {
+ log.add(s);
+ return false;
+ });
+ expect(log, ['a']);
+ });
+ test('multiple one', () {
+ var log = [];
+ ['a', 'b', 'c'].forEachWhile((s) {
+ log.add(s);
+ return false;
+ });
+ expect(log, ['a']);
+ });
+ test('multiple all', () {
+ var log = [];
+ ['a', 'b', 'c'].forEachWhile((s) {
+ log.add(s);
+ return true;
+ });
+ expect(log, ['a', 'b', 'c']);
+ });
+ test('multiple some', () {
+ var log = [];
+ ['a', 'b', 'c'].forEachWhile((s) {
+ log.add(s);
+ return s != 'b';
+ });
+ expect(log, ['a', 'b']);
+ });
+ });
+ group('.forEachIndexedWhile', () {
+ test('empty', () {
+ [].forEachIndexedWhile(unreachable);
+ });
+ test('single true', () {
+ var log = [];
+ ['a'].forEachIndexedWhile((i, s) {
+ log
+ ..add(i)
+ ..add(s);
+ return true;
+ });
+ expect(log, [0, 'a']);
+ });
+ test('single false', () {
+ var log = [];
+ ['a'].forEachIndexedWhile((i, s) {
+ log
+ ..add(i)
+ ..add(s);
+ return false;
+ });
+ expect(log, [0, 'a']);
+ });
+ test('multiple one', () {
+ var log = [];
+ ['a', 'b', 'c'].forEachIndexedWhile((i, s) {
+ log
+ ..add(i)
+ ..add(s);
+ return false;
+ });
+ expect(log, [0, 'a']);
+ });
+ test('multiple all', () {
+ var log = [];
+ ['a', 'b', 'c'].forEachIndexedWhile((i, s) {
+ log
+ ..add(i)
+ ..add(s);
+ return true;
+ });
+ expect(log, [0, 'a', 1, 'b', 2, 'c']);
+ });
+ test('multiple some', () {
+ var log = [];
+ ['a', 'b', 'c'].forEachIndexedWhile((i, s) {
+ log
+ ..add(i)
+ ..add(s);
+ return s != 'b';
+ });
+ expect(log, [0, 'a', 1, 'b']);
+ });
+ });
+ group('.mapIndexed', () {
+ test('empty', () {
+ expect(<String>[].mapIndexed(unreachable), isEmpty);
+ });
+ test('multiple', () {
+ expect(<String>['a', 'b'].mapIndexed((i, s) => [i, s]), [
+ [0, 'a'],
+ [1, 'b']
+ ]);
+ });
+ });
+ group('.whereIndexed', () {
+ test('empty', () {
+ expect(<String>[].whereIndexed(unreachable), isEmpty);
+ });
+ test('none', () {
+ var trace = [];
+ int log(int a, int b) {
+ trace
+ ..add(a)
+ ..add(b);
+ return b;
+ }
+
+ expect(<int>[1, 3, 5, 7].whereIndexed((i, x) => log(i, x).isEven),
+ isEmpty);
+ expect(trace, [0, 1, 1, 3, 2, 5, 3, 7]);
+ });
+ test('all', () {
+ expect(
+ <int>[1, 3, 5, 7].whereIndexed((i, x) => x.isOdd), [1, 3, 5, 7]);
+ });
+ test('some', () {
+ expect(<int>[1, 3, 5, 7].whereIndexed((i, x) => i.isOdd), [3, 7]);
+ });
+ });
+ group('.whereNotIndexed', () {
+ test('empty', () {
+ expect(<int>[].whereNotIndexed(unreachable), isEmpty);
+ });
+ test('none', () {
+ var trace = [];
+ int log(int a, int b) {
+ trace
+ ..add(a)
+ ..add(b);
+ return b;
+ }
+
+ expect(<int>[1, 3, 5, 7].whereNotIndexed((i, x) => log(i, x).isOdd),
+ isEmpty);
+ expect(trace, [0, 1, 1, 3, 2, 5, 3, 7]);
+ });
+ test('all', () {
+ expect(<int>[1, 3, 5, 7].whereNotIndexed((i, x) => x.isEven),
+ [1, 3, 5, 7]);
+ });
+ test('some', () {
+ expect(<int>[1, 3, 5, 7].whereNotIndexed((i, x) => i.isOdd), [1, 5]);
+ });
+ });
+ group('.expandIndexed', () {
+ test('empty', () {
+ expect(<int>[].expandIndexed<int>(unreachable), isEmpty);
+ });
+ test('empty result', () {
+ expect(['a', 'b'].expandIndexed((i, v) => []), isEmpty);
+ });
+ test('larger result', () {
+ expect(['a', 'b'].expandIndexed((i, v) => ['$i', v]),
+ ['0', 'a', '1', 'b']);
+ });
+ test('varying result', () {
+ expect(['a', 'b'].expandIndexed((i, v) => i.isOdd ? ['$i', v] : []),
+ ['1', 'b']);
+ });
+ });
+ group('.elementAtOrNull', () {
+ test('empty', () async {
+ expect([].elementAtOrNull(0), isNull);
+ });
+ test('negative index', () async {
+ expect(() => [1].elementAtOrNull(-1), throwsA(isA<RangeError>()));
+ });
+ test('index within range', () async {
+ expect([1].elementAtOrNull(0), 1);
+ });
+ test('index too high', () async {
+ expect([1].elementAtOrNull(1), isNull);
+ });
+ });
+ group('.slices', () {
+ test('empty', () {
+ expect(<int>[].slices(1), []);
+ });
+ test('with the same length as the iterable', () {
+ expect([1, 2, 3].slices(3), [
+ [1, 2, 3]
+ ]);
+ });
+ test('with a longer length than the iterable', () {
+ expect([1, 2, 3].slices(5), [
+ [1, 2, 3]
+ ]);
+ });
+ test('with a shorter length than the iterable', () {
+ expect([1, 2, 3].slices(2), [
+ [1, 2],
+ [3]
+ ]);
+ });
+ test('with length divisible by the iterable\'s', () {
+ expect([1, 2, 3, 4].slices(2), [
+ [1, 2],
+ [3, 4]
+ ]);
+ });
+ test('refuses negative length', () {
+ expect(() => [1].slices(-1), throwsRangeError);
+ });
+ test('refuses length 0', () {
+ expect(() => [1].slices(0), throwsRangeError);
+ });
+ });
+ });
+ group('on comparable', () {
+ group('.binarySearch', () {
+ test('empty', () {
+ expect(<String>[].binarySearch('1', unreachable), -1);
+ expect(<String>[].binarySearch('1'), -1);
+ });
+ test('single', () {
+ expect(['0'].binarySearch('1', cmpString), -1);
+ expect(['1'].binarySearch('1', cmpString), 0);
+ expect(['2'].binarySearch('1', cmpString), -1);
+ expect(
+ ['0'].binarySearch(
+ '1',
+ ),
+ -1);
+ expect(
+ ['1'].binarySearch(
+ '1',
+ ),
+ 0);
+ expect(
+ ['2'].binarySearch(
+ '1',
+ ),
+ -1);
+ });
+ test('multiple', () {
+ expect(
+ ['1', '2', '3', '4', '5', '6'].binarySearch('3', cmpString), 2);
+ expect(['1', '2', '3', '4', '5', '6'].binarySearch('3'), 2);
+ expect(
+ ['6', '5', '4', '3', '2', '1'].binarySearch('3', cmpParseInverse),
+ 3);
+ });
+ });
+ });
+ group('.lowerBound', () {
+ test('empty', () {
+ expect(<String>[].lowerBound('1', unreachable), 0);
+ });
+ test('single', () {
+ expect(['0'].lowerBound('1', cmpString), 1);
+ expect(['1'].lowerBound('1', cmpString), 0);
+ expect(['2'].lowerBound('1', cmpString), 0);
+ expect(['0'].lowerBound('1'), 1);
+ expect(['1'].lowerBound('1'), 0);
+ expect(['2'].lowerBound('1'), 0);
+ });
+ test('multiple', () {
+ expect(['1', '2', '3', '4', '5', '6'].lowerBound('3', cmpParse), 2);
+ expect(['1', '2', '3', '4', '5', '6'].lowerBound('3'), 2);
+ expect(
+ ['6', '5', '4', '3', '2', '1'].lowerBound('3', cmpParseInverse), 3);
+ expect(['1', '2', '4', '5', '6'].lowerBound('3', cmpParse), 2);
+ expect(['1', '2', '4', '5', '6'].lowerBound('3'), 2);
+ expect(['6', '5', '4', '2', '1'].lowerBound('3', cmpParseInverse), 3);
+ });
+ });
+ group('sortRange', () {
+ test('errors', () {
+ expect(() => [1].sortRange(-1, 1, cmpInt), throwsArgumentError);
+ expect(() => [1].sortRange(0, 2, cmpInt), throwsArgumentError);
+ expect(() => [1].sortRange(1, 0, cmpInt), throwsArgumentError);
+ });
+ test('empty range', () {
+ <int>[].sortRange(0, 0, unreachable);
+ var list = [3, 2, 1];
+ list.sortRange(0, 0, unreachable);
+ list.sortRange(3, 3, unreachable);
+ expect(list, [3, 2, 1]);
+ });
+ test('single', () {
+ [1].sortRange(0, 1, unreachable);
+ var list = [3, 2, 1];
+ list.sortRange(0, 1, unreachable);
+ list.sortRange(1, 2, unreachable);
+ list.sortRange(2, 3, unreachable);
+ });
+ test('multiple', () {
+ var list = [9, 8, 7, 6, 5, 4, 3, 2, 1];
+ list.sortRange(2, 5, cmpInt);
+ expect(list, [9, 8, 5, 6, 7, 4, 3, 2, 1]);
+ list.sortRange(4, 8, cmpInt);
+ expect(list, [9, 8, 5, 6, 2, 3, 4, 7, 1]);
+ list.sortRange(3, 6, cmpIntInverse);
+ expect(list, [9, 8, 5, 6, 3, 2, 4, 7, 1]);
+ });
+ });
+ });
+}
+
+/// Creates a plain iterable not implementing any other class.
+Iterable<T> iterable<T>(Iterable<T> values) sync* {
+ yield* values;
+}
+
+Never unreachable([_, __, ___]) => fail('Unreachable');
+
+String toString(Object? o) => '$o';
+
+/// Compares values equal if they have the same remainder mod [mod].
+int Function(int, int) cmpMod(int mod) => (a, b) => a ~/ mod - b ~/ mod;
+
+/// Compares strings lexically.
+int cmpString(String a, String b) => a.compareTo(b);
+
+/// Compares strings by length.
+int cmpStringLength(String a, String b) => a.length - b.length;
+
+/// Compares strings by their integer numeral content.
+int cmpParse(String s1, String s2) => cmpInt(int.parse(s1), int.parse(s2));
+
+/// Compares strings inversely by their integer numeral content.
+int cmpParseInverse(String s1, String s2) =>
+ cmpIntInverse(int.parse(s1), int.parse(s2));
+
+/// Compares integers by size.
+int cmpInt(int a, int b) => a - b;
+
+/// Compares integers by inverse size.
+int cmpIntInverse(int a, int b) => b - a;
+
+/// Tests an integer for being even.
+bool isEven(int x) => x.isEven;
+
+/// Tests an integer for being odd.
+bool isOdd(int x) => x.isOdd;
diff --git a/pkgs/collection/test/functions_test.dart b/pkgs/collection/test/functions_test.dart
new file mode 100644
index 0000000..f602303
--- /dev/null
+++ b/pkgs/collection/test/functions_test.dart
@@ -0,0 +1,363 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: deprecated_member_use_from_same_package
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('mapMap()', () {
+ test('with an empty map returns an empty map', () {
+ expect(
+ mapMap({},
+ key: expectAsync2((_, __) {}, count: 0),
+ value: expectAsync2((_, __) {}, count: 0)),
+ isEmpty);
+ });
+
+ test('with no callbacks, returns a copy of the map', () {
+ var map = {'foo': 1, 'bar': 2};
+ var result = mapMap<String, int, String, int>(map);
+ expect(result, equals({'foo': 1, 'bar': 2}));
+
+ // The resulting map should be a copy.
+ result['foo'] = 3;
+ expect(map, equals({'foo': 1, 'bar': 2}));
+ });
+
+ test("maps the map's keys", () {
+ expect(
+ mapMap<String, int, dynamic, int>({'foo': 1, 'bar': 2},
+ key: (dynamic key, dynamic value) => key[value]),
+ equals({'o': 1, 'r': 2}));
+ });
+
+ test("maps the map's values", () {
+ expect(
+ mapMap<String, int, String, dynamic>({'foo': 1, 'bar': 2},
+ value: (dynamic key, dynamic value) => key[value]),
+ equals({'foo': 'o', 'bar': 'r'}));
+ });
+
+ test("maps both the map's keys and values", () {
+ expect(
+ mapMap({'foo': 1, 'bar': 2},
+ key: (dynamic key, dynamic value) => '$key$value',
+ value: (dynamic key, dynamic value) => key[value]),
+ equals({'foo1': 'o', 'bar2': 'r'}));
+ });
+ });
+
+ group('mergeMaps()', () {
+ test('with empty maps returns an empty map', () {
+ expect(
+ mergeMaps({}, {},
+ value: expectAsync2((dynamic _, dynamic __) {}, count: 0)),
+ isEmpty);
+ });
+
+ test('returns a map with all values in both input maps', () {
+ expect(mergeMaps({'foo': 1, 'bar': 2}, {'baz': 3, 'qux': 4}),
+ equals({'foo': 1, 'bar': 2, 'baz': 3, 'qux': 4}));
+ });
+
+ test("the second map's values win by default", () {
+ expect(mergeMaps({'foo': 1, 'bar': 2}, {'bar': 3, 'baz': 4}),
+ equals({'foo': 1, 'bar': 3, 'baz': 4}));
+ });
+
+ test('uses the callback to merge values', () {
+ expect(
+ mergeMaps({'foo': 1, 'bar': 2}, {'bar': 3, 'baz': 4},
+ value: (dynamic value1, dynamic value2) => value1 + value2),
+ equals({'foo': 1, 'bar': 5, 'baz': 4}));
+ });
+ });
+
+ group('lastBy()', () {
+ test('returns an empty map for an empty iterable', () {
+ expect(
+ lastBy([], (_) => fail('Must not be called for empty input')),
+ isEmpty,
+ );
+ });
+
+ test("keeps the latest element for the function's return value", () {
+ expect(
+ lastBy(['foo', 'bar', 'baz', 'bop', 'qux'],
+ (String string) => string[1]),
+ equals({
+ 'o': 'bop',
+ 'a': 'baz',
+ 'u': 'qux',
+ }));
+ });
+ });
+
+ group('groupBy()', () {
+ test('returns an empty map for an empty iterable', () {
+ expect(groupBy([], expectAsync1((dynamic _) {}, count: 0)), isEmpty);
+ });
+
+ test("groups elements by the function's return value", () {
+ expect(
+ groupBy(['foo', 'bar', 'baz', 'bop', 'qux'],
+ (dynamic string) => string[1]),
+ equals({
+ 'o': ['foo', 'bop'],
+ 'a': ['bar', 'baz'],
+ 'u': ['qux']
+ }));
+ });
+ });
+
+ group('minBy()', () {
+ test('returns null for an empty iterable', () {
+ expect(
+ minBy([], expectAsync1((dynamic _) {}, count: 0),
+ compare: expectAsync2((dynamic _, dynamic __) => -1, count: 0)),
+ isNull);
+ });
+
+ test(
+ 'returns the element for which the ordering function returns the '
+ 'smallest value', () {
+ expect(
+ minBy([
+ {'foo': 3},
+ {'foo': 5},
+ {'foo': 4},
+ {'foo': 1},
+ {'foo': 2}
+ ], (dynamic map) => map['foo']),
+ equals({'foo': 1}));
+ });
+
+ test('uses a custom comparator if provided', () {
+ expect(
+ minBy<Map<String, int>, Map<String, int>>([
+ {'foo': 3},
+ {'foo': 5},
+ {'foo': 4},
+ {'foo': 1},
+ {'foo': 2}
+ ], (map) => map,
+ compare: (map1, map2) => map1['foo']!.compareTo(map2['foo']!)),
+ equals({'foo': 1}));
+ });
+ });
+
+ group('maxBy()', () {
+ test('returns null for an empty iterable', () {
+ expect(
+ maxBy([], expectAsync1((dynamic _) {}, count: 0),
+ compare: expectAsync2((dynamic _, dynamic __) => 0, count: 0)),
+ isNull);
+ });
+
+ test(
+ 'returns the element for which the ordering function returns the '
+ 'largest value', () {
+ expect(
+ maxBy([
+ {'foo': 3},
+ {'foo': 5},
+ {'foo': 4},
+ {'foo': 1},
+ {'foo': 2}
+ ], (dynamic map) => map['foo']),
+ equals({'foo': 5}));
+ });
+
+ test('uses a custom comparator if provided', () {
+ expect(
+ maxBy<Map<String, int>, Map<String, int>>([
+ {'foo': 3},
+ {'foo': 5},
+ {'foo': 4},
+ {'foo': 1},
+ {'foo': 2}
+ ], (map) => map,
+ compare: (map1, map2) => map1['foo']!.compareTo(map2['foo']!)),
+ equals({'foo': 5}));
+ });
+ });
+
+ group('transitiveClosure()', () {
+ test('returns an empty map for an empty graph', () {
+ expect(transitiveClosure({}), isEmpty);
+ });
+
+ test('returns the input when there are no transitive connections', () {
+ expect(
+ transitiveClosure({
+ 'foo': ['bar'],
+ 'bar': [],
+ 'bang': ['qux', 'zap'],
+ 'qux': [],
+ 'zap': []
+ }),
+ equals({
+ 'foo': ['bar'],
+ 'bar': [],
+ 'bang': ['qux', 'zap'],
+ 'qux': [],
+ 'zap': []
+ }));
+ });
+
+ test('flattens transitive connections', () {
+ expect(
+ transitiveClosure({
+ 'qux': [],
+ 'bar': ['baz'],
+ 'baz': ['qux'],
+ 'foo': ['bar']
+ }),
+ equals({
+ 'foo': ['bar', 'baz', 'qux'],
+ 'bar': ['baz', 'qux'],
+ 'baz': ['qux'],
+ 'qux': []
+ }));
+ });
+
+ test('handles loops', () {
+ expect(
+ transitiveClosure({
+ 'foo': ['bar'],
+ 'bar': ['baz'],
+ 'baz': ['foo']
+ }),
+ equals({
+ 'foo': ['bar', 'baz', 'foo'],
+ 'bar': ['baz', 'foo', 'bar'],
+ 'baz': ['foo', 'bar', 'baz']
+ }));
+ });
+ });
+
+ group('stronglyConnectedComponents()', () {
+ test('returns an empty list for an empty graph', () {
+ expect(stronglyConnectedComponents({}), isEmpty);
+ });
+
+ test('returns one set for a singleton graph', () {
+ expect(
+ stronglyConnectedComponents({'a': []}),
+ equals([
+ {'a'}
+ ]));
+ });
+
+ test('returns two sets for a two-element tree', () {
+ expect(
+ stronglyConnectedComponents({
+ 'a': ['b'],
+ 'b': []
+ }),
+ equals([
+ {'a'},
+ {'b'}
+ ]));
+ });
+
+ test('returns one set for a two-element loop', () {
+ expect(
+ stronglyConnectedComponents({
+ 'a': ['b'],
+ 'b': ['a']
+ }),
+ equals([
+ {'a', 'b'}
+ ]));
+ });
+
+ test('returns individual vertices for a tree', () {
+ expect(
+ stronglyConnectedComponents({
+ 'foo': ['bar'],
+ 'bar': ['baz', 'bang'],
+ 'baz': ['qux'],
+ 'bang': ['zap'],
+ 'qux': [],
+ 'zap': []
+ }),
+ equals([
+ // This is expected to return *a* topological ordering, but this isn't
+ // the only valid one. If the function implementation changes in the
+ // future, this test may need to be updated.
+ {'foo'},
+ {'bar'},
+ {'bang'},
+ {'zap'},
+ {'baz'},
+ {'qux'}
+ ]),
+ );
+ });
+
+ test('returns a single set for a fully cyclic graph', () {
+ expect(
+ stronglyConnectedComponents({
+ 'foo': ['bar'],
+ 'bar': ['baz'],
+ 'baz': ['bang'],
+ 'bang': ['foo']
+ }),
+ equals([
+ {'foo', 'bar', 'baz', 'bang'}
+ ]));
+ });
+
+ test('returns separate sets for each strongly connected component', () {
+ // https://en.wikipedia.org/wiki/Strongly_connected_component#/media/File:Scc.png
+ expect(
+ stronglyConnectedComponents({
+ 'a': ['b'],
+ 'b': ['c', 'e', 'f'],
+ 'c': ['d', 'g'],
+ 'd': ['c', 'h'],
+ 'e': ['a', 'f'],
+ 'f': ['g'],
+ 'g': ['f'],
+ 'h': ['g', 'd']
+ }),
+ equals([
+ // This is expected to return *a* topological ordering, but this isn't
+ // the only valid one. If the function implementation changes in the
+ // future, this test may need to be updated.
+ {'a', 'b', 'e'},
+ {'c', 'd', 'h'},
+ {'f', 'g'},
+ ]),
+ );
+ });
+
+ test('always returns components in topological order', () {
+ expect(
+ stronglyConnectedComponents({
+ 'bar': ['baz', 'bang'],
+ 'zap': [],
+ 'baz': ['qux'],
+ 'qux': [],
+ 'foo': ['bar'],
+ 'bang': ['zap']
+ }),
+ equals([
+ // This is expected to return *a* topological ordering, but this isn't
+ // the only valid one. If the function implementation changes in the
+ // future, this test may need to be updated.
+ {'foo'},
+ {'bar'},
+ {'bang'},
+ {'zap'},
+ {'baz'},
+ {'qux'}
+ ]),
+ );
+ });
+ });
+}
diff --git a/pkgs/collection/test/ignore_ascii_case_test.dart b/pkgs/collection/test/ignore_ascii_case_test.dart
new file mode 100644
index 0000000..78f54a2
--- /dev/null
+++ b/pkgs/collection/test/ignore_ascii_case_test.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Tests case-ignoring compare and equality.
+library;
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('equality ignore ASCII case', () {
+ var strings = [
+ '0@`aopz[{',
+ '0@`aopz[{',
+ '0@`Aopz[{',
+ '0@`aOpz[{',
+ '0@`AOpz[{',
+ '0@`aoPz[{',
+ '0@`AoPz[{',
+ '0@`aOPz[{',
+ '0@`AOPz[{',
+ '0@`aopZ[{',
+ '0@`AopZ[{',
+ '0@`aOpZ[{',
+ '0@`AOpZ[{',
+ '0@`aoPZ[{',
+ '0@`AoPZ[{',
+ '0@`aOPZ[{',
+ '0@`AOPZ[{',
+ ];
+
+ for (var s1 in strings) {
+ for (var s2 in strings) {
+ var reason = '$s1 =?= $s2';
+ expect(equalsIgnoreAsciiCase(s1, s2), true, reason: reason);
+ expect(hashIgnoreAsciiCase(s1), hashIgnoreAsciiCase(s2),
+ reason: reason);
+ }
+ }
+
+ var upperCaseLetters = '@`abcdefghijklmnopqrstuvwxyz[{åÅ';
+ var lowerCaseLetters = '@`ABCDEFGHIJKLMNOPQRSTUVWXYZ[{åÅ';
+ expect(equalsIgnoreAsciiCase(upperCaseLetters, lowerCaseLetters), true);
+
+ void testChars(String char1, String char2, bool areEqual) {
+ expect(equalsIgnoreAsciiCase(char1, char2), areEqual,
+ reason: "$char1 ${areEqual ? "=" : "!"}= $char2");
+ }
+
+ for (var i = 0; i < upperCaseLetters.length; i++) {
+ for (var j = 0; i < upperCaseLetters.length; i++) {
+ testChars(upperCaseLetters[i], upperCaseLetters[j], i == j);
+ testChars(lowerCaseLetters[i], upperCaseLetters[j], i == j);
+ testChars(upperCaseLetters[i], lowerCaseLetters[j], i == j);
+ testChars(lowerCaseLetters[i], lowerCaseLetters[j], i == j);
+ }
+ }
+ });
+}
diff --git a/pkgs/collection/test/iterable_zip_test.dart b/pkgs/collection/test/iterable_zip_test.dart
new file mode 100644
index 0000000..3881c6a
--- /dev/null
+++ b/pkgs/collection/test/iterable_zip_test.dart
@@ -0,0 +1,209 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+/// Iterable like [base] except that it throws when value equals [errorValue].
+Iterable iterError(Iterable base, int errorValue) {
+ // ignore: only_throw_errors
+ return base.map((x) => x == errorValue ? throw 'BAD' : x);
+}
+
+void main() {
+ test('Basic', () {
+ expect(
+ IterableZip([
+ [1, 2, 3],
+ [4, 5, 6],
+ [7, 8, 9]
+ ]),
+ equals([
+ [1, 4, 7],
+ [2, 5, 8],
+ [3, 6, 9]
+ ]));
+ });
+
+ test('Uneven length 1', () {
+ expect(
+ IterableZip([
+ [1, 2, 3, 99, 100],
+ [4, 5, 6],
+ [7, 8, 9]
+ ]),
+ equals([
+ [1, 4, 7],
+ [2, 5, 8],
+ [3, 6, 9]
+ ]));
+ });
+
+ test('Uneven length 2', () {
+ expect(
+ IterableZip([
+ [1, 2, 3],
+ [4, 5, 6, 99, 100],
+ [7, 8, 9]
+ ]),
+ equals([
+ [1, 4, 7],
+ [2, 5, 8],
+ [3, 6, 9]
+ ]));
+ });
+
+ test('Uneven length 3', () {
+ expect(
+ IterableZip([
+ [1, 2, 3],
+ [4, 5, 6],
+ [7, 8, 9, 99, 100]
+ ]),
+ equals([
+ [1, 4, 7],
+ [2, 5, 8],
+ [3, 6, 9]
+ ]));
+ });
+
+ test('Uneven length 3', () {
+ expect(
+ IterableZip([
+ [1, 2, 3, 98],
+ [4, 5, 6],
+ [7, 8, 9, 99, 100]
+ ]),
+ equals([
+ [1, 4, 7],
+ [2, 5, 8],
+ [3, 6, 9]
+ ]));
+ });
+
+ test('Empty 1', () {
+ expect(
+ IterableZip([
+ [],
+ [4, 5, 6],
+ [7, 8, 9]
+ ]),
+ equals([]));
+ });
+
+ test('Empty 2', () {
+ expect(
+ IterableZip([
+ [1, 2, 3],
+ [],
+ [7, 8, 9]
+ ]),
+ equals([]));
+ });
+
+ test('Empty 3', () {
+ expect(
+ IterableZip([
+ [1, 2, 3],
+ [4, 5, 6],
+ []
+ ]),
+ equals([]));
+ });
+
+ test('Empty source', () {
+ expect(IterableZip([]), equals([]));
+ });
+
+ test('Single Source', () {
+ expect(
+ IterableZip([
+ [1, 2, 3]
+ ]),
+ equals([
+ [1],
+ [2],
+ [3]
+ ]));
+ });
+
+ test('Not-lists', () {
+ // Use other iterables than list literals.
+ var it1 = [1, 2, 3, 4, 5, 6].where((x) => x < 4);
+ var it2 = {4, 5, 6};
+ var it3 = {7: 0, 8: 0, 9: 0}.keys;
+ var allIts = Iterable.generate(3, (i) => [it1, it2, it3][i]);
+ expect(
+ IterableZip(allIts),
+ equals([
+ [1, 4, 7],
+ [2, 5, 8],
+ [3, 6, 9]
+ ]));
+ });
+
+ test('Error 1', () {
+ expect(
+ () => IterableZip([
+ iterError([1, 2, 3], 2),
+ [4, 5, 6],
+ [7, 8, 9]
+ ]).toList(),
+ throwsA(equals('BAD')));
+ });
+
+ test('Error 2', () {
+ expect(
+ () => IterableZip([
+ [1, 2, 3],
+ iterError([4, 5, 6], 5),
+ [7, 8, 9]
+ ]).toList(),
+ throwsA(equals('BAD')));
+ });
+
+ test('Error 3', () {
+ expect(
+ () => IterableZip([
+ [1, 2, 3],
+ [4, 5, 6],
+ iterError([7, 8, 9], 8)
+ ]).toList(),
+ throwsA(equals('BAD')));
+ });
+
+ test('Error at end', () {
+ expect(
+ () => IterableZip([
+ [1, 2, 3],
+ iterError([4, 5, 6], 6),
+ [7, 8, 9]
+ ]).toList(),
+ throwsA(equals('BAD')));
+ });
+
+ test('Error before first end', () {
+ expect(
+ () => IterableZip([
+ iterError([1, 2, 3, 4], 4),
+ [4, 5, 6],
+ [7, 8, 9]
+ ]).toList(),
+ throwsA(equals('BAD')));
+ });
+
+ test('Error after first end', () {
+ expect(
+ IterableZip([
+ [1, 2, 3],
+ [4, 5, 6],
+ iterError([7, 8, 9, 10], 10)
+ ]),
+ equals([
+ [1, 4, 7],
+ [2, 5, 8],
+ [3, 6, 9]
+ ]));
+ });
+}
diff --git a/pkgs/collection/test/priority_queue_test.dart b/pkgs/collection/test/priority_queue_test.dart
new file mode 100644
index 0000000..f07a1a3
--- /dev/null
+++ b/pkgs/collection/test/priority_queue_test.dart
@@ -0,0 +1,383 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Tests priority queue implementations utilities.
+library;
+
+import 'package:collection/src/priority_queue.dart';
+import 'package:test/test.dart';
+
+void main() {
+ testDefault();
+ testInt(HeapPriorityQueue<int>.new);
+ testCustom(HeapPriorityQueue<C>.new);
+ testDuplicates();
+ testNullable();
+ testConcurrentModification();
+}
+
+void testDefault() {
+ test('PriorityQueue() returns a HeapPriorityQueue', () {
+ expect(PriorityQueue<int>(), const TypeMatcher<HeapPriorityQueue<int>>());
+ });
+ testInt(PriorityQueue<int>.new);
+ testCustom(PriorityQueue<C>.new);
+}
+
+void testInt(PriorityQueue<int> Function() create) {
+ for (var count in [1, 5, 127, 128]) {
+ testQueue('int:$count', create, List<int>.generate(count, (x) => x), count);
+ }
+}
+
+void testCustom(
+ PriorityQueue<C> Function(int Function(C, C)? comparator) create) {
+ for (var count in [1, 5, 127, 128]) {
+ testQueue('Custom:$count/null', () => create(null),
+ List<C>.generate(count, C.new), C(count));
+ testQueue('Custom:$count/compare', () => create(compare),
+ List<C>.generate(count, C.new), C(count));
+ testQueue('Custom:$count/compareNeg', () => create(compareNeg),
+ List<C>.generate(count, (x) => C(count - x)), const C(0));
+ }
+}
+
+/// Test that a queue behaves correctly.
+///
+/// The elements must be in priority order, from highest to lowest.
+void testQueue<T>(
+ String name,
+ PriorityQueue<T> Function() create,
+ List<T> elements,
+ T notElement,
+) {
+ test(name, () => testQueueBody(create, elements, notElement));
+}
+
+void testQueueBody<T>(
+ PriorityQueue<T> Function() create, List<T> elements, T notElement) {
+ var q = create();
+ expect(q.isEmpty, isTrue);
+ expect(q, hasLength(0));
+ expect(() {
+ q.first;
+ }, throwsStateError);
+ expect(() {
+ q.removeFirst();
+ }, throwsStateError);
+
+ // Tests removeFirst, first, contains, toList and toSet.
+ void testElements() {
+ expect(q.isNotEmpty, isTrue);
+ expect(q, hasLength(elements.length));
+
+ expect(q.toList(), equals(elements));
+ expect(q.toSet().toList(), equals(elements));
+ expect(q.toUnorderedList(), unorderedEquals(elements));
+ expect(q.unorderedElements, unorderedEquals(elements));
+
+ var allElements = q.removeAll();
+ q.addAll(allElements);
+
+ for (var i = 0; i < elements.length; i++) {
+ expect(q.contains(elements[i]), isTrue);
+ }
+ expect(q.contains(notElement), isFalse);
+
+ var all = [];
+ while (q.isNotEmpty) {
+ var expected = q.first;
+ var actual = q.removeFirst();
+ expect(actual, same(expected));
+ all.add(actual);
+ }
+
+ expect(all.length, elements.length);
+ for (var i = 0; i < all.length; i++) {
+ expect(all[i], same(elements[i]));
+ }
+
+ expect(q.isEmpty, isTrue);
+ }
+
+ q.addAll(elements);
+ testElements();
+
+ q.addAll(elements.reversed);
+ testElements();
+
+ // Add elements in a non-linear order (gray order).
+ for (var i = 0, j = 0; i < elements.length; i++) {
+ int gray;
+ do {
+ gray = j ^ (j >> 1);
+ j++;
+ } while (gray >= elements.length);
+ q.add(elements[gray]);
+ }
+ testElements();
+
+ // Add elements by picking the middle element first, and then recursing
+ // on each side.
+ void addRec(int min, int max) {
+ var mid = min + ((max - min) >> 1);
+ q.add(elements[mid]);
+ if (mid + 1 < max) addRec(mid + 1, max);
+ if (mid > min) addRec(min, mid);
+ }
+
+ addRec(0, elements.length);
+ testElements();
+
+ // Test removeAll.
+ q.addAll(elements);
+ expect(q, hasLength(elements.length));
+ var all = q.removeAll();
+ expect(q.isEmpty, isTrue);
+ expect(all, hasLength(elements.length));
+ for (var i = 0; i < elements.length; i++) {
+ expect(all, contains(elements[i]));
+ }
+
+ // Test the same element more than once in queue.
+ q.addAll(elements);
+ q.addAll(elements.reversed);
+ expect(q, hasLength(elements.length * 2));
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ expect(q.contains(element), isTrue);
+ expect(q.removeFirst(), element);
+ expect(q.removeFirst(), element);
+ }
+
+ // Test queue with all same element.
+ var a = elements[0];
+ for (var i = 0; i < elements.length; i++) {
+ q.add(a);
+ }
+ expect(q, hasLength(elements.length));
+ expect(q.contains(a), isTrue);
+ expect(q.contains(notElement), isFalse);
+ q.removeAll().forEach((x) => expect(x, same(a)));
+
+ // Test remove.
+ q.addAll(elements);
+ for (var element in elements.reversed) {
+ expect(q.remove(element), isTrue);
+ }
+ expect(q.isEmpty, isTrue);
+}
+
+void testDuplicates() {
+ // Check how the heap handles duplicate, or equal-but-not-identical, values.
+ test('duplicates', () {
+ var q = HeapPriorityQueue<C>(compare);
+ var c1 = const C(0);
+ // ignore: prefer_const_constructors
+ var c2 = C(0);
+
+ // Can contain the same element more than once.
+ expect(c1, equals(c2));
+ expect(c1, isNot(same(c2)));
+ q.add(c1);
+ q.add(c1);
+ expect(q.length, 2);
+ expect(q.contains(c1), true);
+ expect(q.contains(c2), true);
+ expect(q.remove(c2), true);
+ expect(q.length, 1);
+ expect(q.removeFirst(), same(c1));
+
+ // Can contain equal elements.
+ q.add(c1);
+ q.add(c2);
+ expect(q.length, 2);
+ expect(q.contains(c1), true);
+ expect(q.contains(c2), true);
+ expect(q.remove(c1), true);
+ expect(q.length, 1);
+ expect(q.first, anyOf(same(c1), same(c2)));
+ });
+}
+
+void testNullable() {
+ // Check that the queue works with a nullable type, and a comparator
+ // which accepts `null`.
+ // Compares `null` before instances of `C`.
+ int nullCompareFirst(C? a, C? b) => a == null
+ ? b == null
+ ? 0
+ : -1
+ : b == null
+ ? 1
+ : compare(a, b);
+
+ int nullCompareLast(C? a, C? b) => a == null
+ ? b == null
+ ? 0
+ : 1
+ : b == null
+ ? -1
+ : compare(a, b);
+
+ var c1 = const C(1);
+ var c2 = const C(2);
+ var c3 = const C(3);
+
+ test('nulls first', () {
+ var q = HeapPriorityQueue<C?>(nullCompareFirst);
+ q.add(c2);
+ q.add(c1);
+ q.add(null);
+ expect(q.length, 3);
+ expect(q.contains(null), true);
+ expect(q.contains(c1), true);
+ expect(q.contains(c3), false);
+
+ expect(q.removeFirst(), null);
+ expect(q.length, 2);
+ expect(q.contains(null), false);
+ q.add(null);
+ expect(q.length, 3);
+ expect(q.contains(null), true);
+ q.add(null);
+ expect(q.length, 4);
+ expect(q.contains(null), true);
+ expect(q.remove(null), true);
+ expect(q.length, 3);
+ expect(q.toList(), [null, c1, c2]);
+ });
+
+ test('nulls last', () {
+ var q = HeapPriorityQueue<C?>(nullCompareLast);
+ q.add(c2);
+ q.add(c1);
+ q.add(null);
+ expect(q.length, 3);
+ expect(q.contains(null), true);
+ expect(q.contains(c1), true);
+ expect(q.contains(c3), false);
+ expect(q.first, c1);
+
+ q.add(null);
+ expect(q.length, 4);
+ expect(q.contains(null), true);
+ q.add(null);
+ expect(q.length, 5);
+ expect(q.contains(null), true);
+ expect(q.remove(null), true);
+ expect(q.length, 4);
+ expect(q.toList(), [c1, c2, null, null]);
+ });
+}
+
+void testConcurrentModification() {
+ group('concurrent modification for', () {
+ test('add', () {
+ var q = HeapPriorityQueue<int>((a, b) => a - b)
+ ..addAll([6, 4, 2, 3, 5, 8]);
+ var e = q.unorderedElements;
+ q.add(12); // Modifiation before creating iterator is not a problem.
+ var it = e.iterator;
+ q.add(7); // Modification after creatig iterator is a problem.
+ expect(it.moveNext, throwsConcurrentModificationError);
+
+ it = e.iterator; // New iterator is not affected.
+ expect(it.moveNext(), true);
+ expect(it.moveNext(), true);
+ q.add(9); // Modification during iteration is a problem.
+ expect(it.moveNext, throwsConcurrentModificationError);
+ });
+
+ test('addAll', () {
+ var q = HeapPriorityQueue<int>((a, b) => a - b)
+ ..addAll([6, 4, 2, 3, 5, 8]);
+ var e = q.unorderedElements;
+ q.addAll([12]); // Modifiation before creating iterator is not a problem.
+ var it = e.iterator;
+ q.addAll([7]); // Modification after creatig iterator is a problem.
+ expect(it.moveNext, throwsConcurrentModificationError);
+ it = e.iterator; // New iterator is not affected.
+ expect(it.moveNext(), true);
+ q.addAll([]); // Adding nothing is not a modification.
+ expect(it.moveNext(), true);
+ q.addAll([9]); // Modification during iteration is a problem.
+ expect(it.moveNext, throwsConcurrentModificationError);
+ });
+
+ test('removeFirst', () {
+ var q = HeapPriorityQueue<int>((a, b) => a - b)
+ ..addAll([6, 4, 2, 3, 5, 8]);
+ var e = q.unorderedElements;
+ expect(q.removeFirst(),
+ 2); // Modifiation before creating iterator is not a problem.
+ var it = e.iterator;
+ expect(q.removeFirst(),
+ 3); // Modification after creatig iterator is a problem.
+ expect(it.moveNext, throwsConcurrentModificationError);
+
+ it = e.iterator; // New iterator is not affected.
+ expect(it.moveNext(), true);
+ expect(it.moveNext(), true);
+ expect(q.removeFirst(), 4); // Modification during iteration is a problem.
+ expect(it.moveNext, throwsConcurrentModificationError);
+ });
+
+ test('remove', () {
+ var q = HeapPriorityQueue<int>((a, b) => a - b)
+ ..addAll([6, 4, 2, 3, 5, 8]);
+ var e = q.unorderedElements;
+ expect(q.remove(3), true);
+ var it = e.iterator;
+ expect(q.remove(2), true);
+ expect(it.moveNext, throwsConcurrentModificationError);
+ it = e.iterator;
+ expect(q.remove(99), false);
+ expect(it.moveNext(), true);
+ expect(it.moveNext(), true);
+ expect(q.remove(5), true);
+ expect(it.moveNext, throwsConcurrentModificationError);
+ });
+
+ test('removeAll', () {
+ var q = HeapPriorityQueue<int>((a, b) => a - b)
+ ..addAll([6, 4, 2, 3, 5, 8]);
+ var e = q.unorderedElements;
+ var it = e.iterator;
+ expect(it.moveNext(), true);
+ expect(it.moveNext(), true);
+ expect(q.removeAll(), hasLength(6));
+ expect(it.moveNext, throwsConcurrentModificationError);
+ });
+
+ test('clear', () {
+ var q = HeapPriorityQueue<int>((a, b) => a - b)
+ ..addAll([6, 4, 2, 3, 5, 8]);
+ var e = q.unorderedElements;
+ var it = e.iterator;
+ expect(it.moveNext(), true);
+ expect(it.moveNext(), true);
+ q.clear();
+ expect(it.moveNext, throwsConcurrentModificationError);
+ });
+ });
+}
+
+// Custom class.
+// Class is comparable, comparators match normal and inverse order.
+int compare(C c1, C c2) => c1.value - c2.value;
+int compareNeg(C c1, C c2) => c2.value - c1.value;
+
+class C implements Comparable<C> {
+ final int value;
+ const C(this.value);
+ @override
+ int get hashCode => value;
+ @override
+ bool operator ==(Object other) => other is C && value == other.value;
+ @override
+ int compareTo(C other) => value - other.value;
+ @override
+ String toString() => 'C($value)';
+}
diff --git a/pkgs/collection/test/queue_list_test.dart b/pkgs/collection/test/queue_list_test.dart
new file mode 100644
index 0000000..1550f92
--- /dev/null
+++ b/pkgs/collection/test/queue_list_test.dart
@@ -0,0 +1,307 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('QueueList()', () {
+ test('creates an empty QueueList', () {
+ expect(QueueList(), isEmpty);
+ });
+
+ test('takes an initial capacity', () {
+ expect(QueueList(100), isEmpty);
+ });
+ });
+
+ test('QueueList.from() copies the contents of an iterable', () {
+ expect(QueueList.from([1, 2, 3].skip(1)), equals([2, 3]));
+ });
+
+ group('add()', () {
+ test('adds an element to the end of the queue', () {
+ var queue = QueueList.from([1, 2, 3]);
+ queue.add(4);
+ expect(queue, equals([1, 2, 3, 4]));
+ });
+
+ test('expands a full queue', () {
+ var queue = atCapacity();
+ queue.add(8);
+ expect(queue, equals([1, 2, 3, 4, 5, 6, 7, 8]));
+ });
+ });
+
+ group('addAll()', () {
+ test('adds elements to the end of the queue', () {
+ var queue = QueueList.from([1, 2, 3]);
+ queue.addAll([4, 5, 6]);
+ expect(queue, equals([1, 2, 3, 4, 5, 6]));
+ });
+
+ test('expands a full queue', () {
+ var queue = atCapacity();
+ queue.addAll([8, 9]);
+ expect(queue, equals([1, 2, 3, 4, 5, 6, 7, 8, 9]));
+ });
+ });
+
+ group('addFirst()', () {
+ test('adds an element to the beginning of the queue', () {
+ var queue = QueueList.from([1, 2, 3]);
+ queue.addFirst(0);
+ expect(queue, equals([0, 1, 2, 3]));
+ });
+
+ test('expands a full queue', () {
+ var queue = atCapacity();
+ queue.addFirst(0);
+ expect(queue, equals([0, 1, 2, 3, 4, 5, 6, 7]));
+ });
+ });
+
+ group('removeFirst()', () {
+ test('removes an element from the beginning of the queue', () {
+ var queue = QueueList.from([1, 2, 3]);
+ expect(queue.removeFirst(), equals(1));
+ expect(queue, equals([2, 3]));
+ });
+
+ test(
+ 'removes an element from the beginning of a queue with an internal '
+ 'gap', () {
+ var queue = withInternalGap();
+ expect(queue.removeFirst(), equals(1));
+ expect(queue, equals([2, 3, 4, 5, 6, 7]));
+ });
+
+ test('removes an element from the beginning of a queue at capacity', () {
+ var queue = atCapacity();
+ expect(queue.removeFirst(), equals(1));
+ expect(queue, equals([2, 3, 4, 5, 6, 7]));
+ });
+
+ test('throws a StateError for an empty queue', () {
+ expect(QueueList().removeFirst, throwsStateError);
+ });
+ });
+
+ group('removeLast()', () {
+ test('removes an element from the end of the queue', () {
+ var queue = QueueList.from([1, 2, 3]);
+ expect(queue.removeLast(), equals(3));
+ expect(queue, equals([1, 2]));
+ });
+
+ test('removes an element from the end of a queue with an internal gap', () {
+ var queue = withInternalGap();
+ expect(queue.removeLast(), equals(7));
+ expect(queue, equals([1, 2, 3, 4, 5, 6]));
+ });
+
+ test('removes an element from the end of a queue at capacity', () {
+ var queue = atCapacity();
+ expect(queue.removeLast(), equals(7));
+ expect(queue, equals([1, 2, 3, 4, 5, 6]));
+ });
+
+ test('throws a StateError for an empty queue', () {
+ expect(QueueList().removeLast, throwsStateError);
+ });
+ });
+
+ group('length', () {
+ test('returns the length of a queue', () {
+ expect(QueueList.from([1, 2, 3]).length, equals(3));
+ });
+
+ test('returns the length of a queue with an internal gap', () {
+ expect(withInternalGap().length, equals(7));
+ });
+
+ test('returns the length of a queue at capacity', () {
+ expect(atCapacity().length, equals(7));
+ });
+ });
+
+ group('length=', () {
+ test('shrinks a larger queue', () {
+ var queue = QueueList.from([1, 2, 3]);
+ queue.length = 1;
+ expect(queue, equals([1]));
+ });
+
+ test('grows a smaller queue', () {
+ var queue = QueueList<int?>.from([1, 2, 3]);
+ queue.length = 5;
+ expect(queue, equals([1, 2, 3, null, null]));
+ });
+
+ test('throws a RangeError if length is less than 0', () {
+ expect(() => QueueList().length = -1, throwsRangeError);
+ });
+
+ test('throws an UnsupportedError if element type is non-nullable', () {
+ expect(() => QueueList<int>().length = 1, throwsUnsupportedError);
+ });
+ });
+
+ group('[]', () {
+ test('returns individual entries in the queue', () {
+ var queue = QueueList.from([1, 2, 3]);
+ expect(queue[0], equals(1));
+ expect(queue[1], equals(2));
+ expect(queue[2], equals(3));
+ });
+
+ test('returns individual entries in a queue with an internal gap', () {
+ var queue = withInternalGap();
+ expect(queue[0], equals(1));
+ expect(queue[1], equals(2));
+ expect(queue[2], equals(3));
+ expect(queue[3], equals(4));
+ expect(queue[4], equals(5));
+ expect(queue[5], equals(6));
+ expect(queue[6], equals(7));
+ });
+
+ test('throws a RangeError if the index is less than 0', () {
+ var queue = QueueList.from([1, 2, 3]);
+ expect(() => queue[-1], throwsRangeError);
+ });
+
+ test(
+ 'throws a RangeError if the index is greater than or equal to the '
+ 'length', () {
+ var queue = QueueList.from([1, 2, 3]);
+ expect(() => queue[3], throwsRangeError);
+ });
+ });
+
+ group('[]=', () {
+ test('sets individual entries in the queue', () {
+ var queue = QueueList<dynamic>.from([1, 2, 3]);
+ queue[0] = 'a';
+ queue[1] = 'b';
+ queue[2] = 'c';
+ expect(queue, equals(['a', 'b', 'c']));
+ });
+
+ test('sets individual entries in a queue with an internal gap', () {
+ var queue = withInternalGap();
+ queue[0] = 'a';
+ queue[1] = 'b';
+ queue[2] = 'c';
+ queue[3] = 'd';
+ queue[4] = 'e';
+ queue[5] = 'f';
+ queue[6] = 'g';
+ expect(queue, equals(['a', 'b', 'c', 'd', 'e', 'f', 'g']));
+ });
+
+ test('throws a RangeError if the index is less than 0', () {
+ var queue = QueueList.from([1, 2, 3]);
+ expect(() {
+ queue[-1] = 0;
+ }, throwsRangeError);
+ });
+
+ test(
+ 'throws a RangeError if the index is greater than or equal to the '
+ 'length', () {
+ var queue = QueueList.from([1, 2, 3]);
+ expect(() {
+ queue[3] = 4;
+ }, throwsRangeError);
+ });
+ });
+
+ group('throws a modification error for', () {
+ dynamic queue;
+ setUp(() {
+ queue = QueueList.from([1, 2, 3]);
+ });
+
+ test('add', () {
+ expect(() => queue.forEach((_) => queue.add(4)),
+ throwsConcurrentModificationError);
+ });
+
+ test('addAll', () {
+ expect(() => queue.forEach((_) => queue.addAll([4, 5, 6])),
+ throwsConcurrentModificationError);
+ });
+
+ test('addFirst', () {
+ expect(() => queue.forEach((_) => queue.addFirst(0)),
+ throwsConcurrentModificationError);
+ });
+
+ test('removeFirst', () {
+ expect(() => queue.forEach((_) => queue.removeFirst()),
+ throwsConcurrentModificationError);
+ });
+
+ test('removeLast', () {
+ expect(() => queue.forEach((_) => queue.removeLast()),
+ throwsConcurrentModificationError);
+ });
+
+ test('length=', () {
+ expect(() => queue.forEach((_) => queue.length = 1),
+ throwsConcurrentModificationError);
+ });
+ });
+
+ test('cast does not throw on mutation when the type is valid', () {
+ var patternQueue = QueueList<Pattern>()..addAll(['a', 'b']);
+ var stringQueue = patternQueue.cast<String>();
+ stringQueue.addAll(['c', 'd']);
+ expect(stringQueue, const TypeMatcher<QueueList<String>>(),
+ reason: 'Expected QueueList<String>, got ${stringQueue.runtimeType}');
+
+ expect(stringQueue, ['a', 'b', 'c', 'd']);
+
+ expect(patternQueue, stringQueue, reason: 'Should forward to original');
+ });
+
+ test('cast throws on mutation when the type is not valid', () {
+ QueueList<Object> stringQueue = QueueList<String>();
+ var numQueue = stringQueue.cast<num>();
+ expect(numQueue, const TypeMatcher<QueueList<num>>(),
+ reason: 'Expected QueueList<num>, got ${numQueue.runtimeType}');
+ expect(() => numQueue.add(1), throwsA(isA<TypeError>()));
+ });
+
+ test('cast returns a new QueueList', () {
+ var queue = QueueList<String>();
+ expect(queue.cast<Pattern>(), isNot(same(queue)));
+ });
+}
+
+/// Returns a queue whose internal ring buffer is full enough that adding a new
+/// element will expand it.
+QueueList atCapacity() {
+ // Use addAll because `QueueList.from(list)` won't use the default initial
+ // capacity of 8.
+ return QueueList()..addAll([1, 2, 3, 4, 5, 6, 7]);
+}
+
+/// Returns a queue whose internal tail has a lower index than its head.
+QueueList withInternalGap() {
+ var queue = QueueList.from(<dynamic>[null, null, null, null, 1, 2, 3, 4]);
+ for (var i = 0; i < 4; i++) {
+ queue.removeFirst();
+ }
+ for (var i = 5; i < 8; i++) {
+ queue.addLast(i);
+ }
+ return queue;
+}
+
+/// Returns a matcher that expects that a closure throws a
+/// [ConcurrentModificationError].
+final throwsConcurrentModificationError =
+ throwsA(const TypeMatcher<ConcurrentModificationError>());
diff --git a/pkgs/collection/test/union_set_controller_test.dart b/pkgs/collection/test/union_set_controller_test.dart
new file mode 100644
index 0000000..5d94752
--- /dev/null
+++ b/pkgs/collection/test/union_set_controller_test.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late UnionSetController<int> controller;
+ late Set<int> innerSet;
+ setUp(() {
+ innerSet = {1, 2, 3};
+ controller = UnionSetController()..add(innerSet);
+ });
+
+ test('exposes a union set', () {
+ expect(controller.set, unorderedEquals([1, 2, 3]));
+
+ controller.add({3, 4, 5});
+ expect(controller.set, unorderedEquals([1, 2, 3, 4, 5]));
+
+ controller.remove(innerSet);
+ expect(controller.set, unorderedEquals([3, 4, 5]));
+ });
+
+ test('exposes a disjoint union set', () {
+ expect(controller.set, unorderedEquals([1, 2, 3]));
+
+ controller.add({4, 5, 6});
+ expect(controller.set, unorderedEquals([1, 2, 3, 4, 5, 6]));
+
+ controller.remove(innerSet);
+ expect(controller.set, unorderedEquals([4, 5, 6]));
+ });
+}
diff --git a/pkgs/collection/test/union_set_test.dart b/pkgs/collection/test/union_set_test.dart
new file mode 100644
index 0000000..d06faf3
--- /dev/null
+++ b/pkgs/collection/test/union_set_test.dart
@@ -0,0 +1,222 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('with an empty outer set', () {
+ dynamic set;
+ setUp(() {
+ set = UnionSet<int>({});
+ });
+
+ test('length returns 0', () {
+ expect(set.length, equals(0));
+ });
+
+ test('contains() returns false', () {
+ expect(set.contains(0), isFalse);
+ expect(set.contains(null), isFalse);
+ expect(set.contains('foo'), isFalse);
+ });
+
+ test('lookup() returns null', () {
+ expect(set.lookup(0), isNull);
+ expect(set.lookup(null), isNull);
+ expect(set.lookup('foo'), isNull);
+ });
+
+ test('toSet() returns an empty set', () {
+ expect(set.toSet(), isEmpty);
+ expect(set.toSet(), isNot(same(set)));
+ });
+
+ test("map() doesn't run on any elements", () {
+ expect(set.map(expectAsync1((dynamic _) {}, count: 0)), isEmpty);
+ });
+ });
+
+ group('with multiple disjoint sets', () {
+ late Set set;
+ setUp(() {
+ set = UnionSet.from([
+ {1, 2},
+ {3, 4},
+ {5},
+ <int>{},
+ ], disjoint: true);
+ });
+
+ test('length returns the total length', () {
+ expect(set.length, equals(5));
+ });
+
+ test('contains() returns whether any set contains the element', () {
+ expect(set.contains(1), isTrue);
+ expect(set.contains(4), isTrue);
+ expect(set.contains(5), isTrue);
+ expect(set.contains(6), isFalse);
+ });
+
+ test('lookup() returns elements that are in any set', () {
+ expect(set.lookup(1), equals(1));
+ expect(set.lookup(4), equals(4));
+ expect(set.lookup(5), equals(5));
+ expect(set.lookup(6), isNull);
+ });
+
+ test('toSet() returns the union of all the sets', () {
+ expect(set.toSet(), unorderedEquals([1, 2, 3, 4, 5]));
+ expect(set.toSet(), isNot(same(set)));
+ });
+
+ test('map() maps the elements', () {
+ expect(set.map((i) => i * 2), unorderedEquals([2, 4, 6, 8, 10]));
+ });
+ });
+
+ group('with multiple overlapping sets', () {
+ late Set set;
+ setUp(() {
+ set = UnionSet.from([
+ {1, 2, 3},
+ {3, 4},
+ {5, 1},
+ <int>{},
+ ]);
+ });
+
+ test('length returns the total length', () {
+ expect(set.length, equals(5));
+ });
+
+ test('contains() returns whether any set contains the element', () {
+ expect(set.contains(1), isTrue);
+ expect(set.contains(4), isTrue);
+ expect(set.contains(5), isTrue);
+ expect(set.contains(6), isFalse);
+ });
+
+ test('lookup() returns elements that are in any set', () {
+ expect(set.lookup(1), equals(1));
+ expect(set.lookup(4), equals(4));
+ expect(set.lookup(5), equals(5));
+ expect(set.lookup(6), isNull);
+ });
+
+ test('lookup() returns the first element in an ordered context', () {
+ var duration1 = const Duration(seconds: 0);
+ // ignore: prefer_const_constructors
+ var duration2 = Duration(seconds: 0);
+ expect(duration1, equals(duration2));
+ expect(duration1, isNot(same(duration2)));
+
+ var set = UnionSet.from([
+ {duration1},
+ {duration2}
+ ]);
+
+ expect(set.lookup(const Duration(seconds: 0)), same(duration1));
+ });
+
+ test('toSet() returns the union of all the sets', () {
+ expect(set.toSet(), unorderedEquals([1, 2, 3, 4, 5]));
+ expect(set.toSet(), isNot(same(set)));
+ });
+
+ test('map() maps the elements', () {
+ expect(set.map((i) => i * 2), unorderedEquals([2, 4, 6, 8, 10]));
+ });
+ });
+
+ group('after an inner set was modified', () {
+ late Set set;
+ setUp(() {
+ var innerSet = {3, 7};
+ set = UnionSet.from([
+ {1, 2},
+ {5},
+ innerSet
+ ]);
+
+ innerSet.add(4);
+ innerSet.remove(7);
+ });
+
+ test('length returns the total length', () {
+ expect(set.length, equals(5));
+ });
+
+ test('contains() returns true for a new element', () {
+ expect(set.contains(4), isTrue);
+ });
+
+ test('contains() returns false for a removed element', () {
+ expect(set.contains(7), isFalse);
+ });
+
+ test('lookup() returns a new element', () {
+ expect(set.lookup(4), equals(4));
+ });
+
+ test("lookup() doesn't returns a removed element", () {
+ expect(set.lookup(7), isNull);
+ });
+
+ test('toSet() returns the union of all the sets', () {
+ expect(set.toSet(), unorderedEquals([1, 2, 3, 4, 5]));
+ expect(set.toSet(), isNot(same(set)));
+ });
+
+ test('map() maps the elements', () {
+ expect(set.map((i) => i * 2), unorderedEquals([2, 4, 6, 8, 10]));
+ });
+ });
+
+ group('after the outer set was modified', () {
+ late Set set;
+ setUp(() {
+ var innerSet = {6};
+ var outerSet = {
+ {1, 2},
+ {5},
+ innerSet
+ };
+
+ set = UnionSet<int>(outerSet);
+ outerSet.remove(innerSet);
+ outerSet.add({3, 4});
+ });
+
+ test('length returns the total length', () {
+ expect(set.length, equals(5));
+ });
+
+ test('contains() returns true for a new element', () {
+ expect(set.contains(4), isTrue);
+ });
+
+ test('contains() returns false for a removed element', () {
+ expect(set.contains(6), isFalse);
+ });
+
+ test('lookup() returns a new element', () {
+ expect(set.lookup(4), equals(4));
+ });
+
+ test("lookup() doesn't returns a removed element", () {
+ expect(set.lookup(6), isNull);
+ });
+
+ test('toSet() returns the union of all the sets', () {
+ expect(set.toSet(), unorderedEquals([1, 2, 3, 4, 5]));
+ expect(set.toSet(), isNot(same(set)));
+ });
+
+ test('map() maps the elements', () {
+ expect(set.map((i) => i * 2), unorderedEquals([2, 4, 6, 8, 10]));
+ });
+ });
+}
diff --git a/pkgs/collection/test/unmodifiable_collection_test.dart b/pkgs/collection/test/unmodifiable_collection_test.dart
new file mode 100644
index 0000000..12a9a0a
--- /dev/null
+++ b/pkgs/collection/test/unmodifiable_collection_test.dart
@@ -0,0 +1,541 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+// Test unmodifiable collection views.
+// The collections should pass through the operations that are allowed,
+// an throw on the ones that aren't without affecting the original.
+
+void main() {
+ var list = <int>[];
+ testUnmodifiableList(list, UnmodifiableListView(list), 'empty');
+ list = [42];
+ testUnmodifiableList(list, UnmodifiableListView(list), 'single-42');
+ list = [7];
+ testUnmodifiableList(list, UnmodifiableListView(list), 'single!42');
+ list = [1, 42, 10];
+ testUnmodifiableList(list, UnmodifiableListView(list), 'three-42');
+ list = [1, 7, 10];
+ testUnmodifiableList(list, UnmodifiableListView(list), 'three!42');
+
+ list = [];
+ testNonGrowableList(list, NonGrowableListView(list), 'empty');
+ list = [42];
+ testNonGrowableList(list, NonGrowableListView(list), 'single-42');
+ list = [7];
+ testNonGrowableList(list, NonGrowableListView(list), 'single!42');
+ list = [1, 42, 10];
+ testNonGrowableList(list, NonGrowableListView(list), 'three-42');
+ list = [1, 7, 10];
+ testNonGrowableList(list, NonGrowableListView(list), 'three!42');
+
+ var aSet = <int>{};
+ testUnmodifiableSet(aSet, UnmodifiableSetView(aSet), 'empty');
+ aSet = {};
+ testUnmodifiableSet(aSet, const UnmodifiableSetView.empty(), 'const empty');
+ aSet = {42};
+ testUnmodifiableSet(aSet, UnmodifiableSetView(aSet), 'single-42');
+ aSet = {7};
+ testUnmodifiableSet(aSet, UnmodifiableSetView(aSet), 'single!42');
+ aSet = {1, 42, 10};
+ testUnmodifiableSet(aSet, UnmodifiableSetView(aSet), 'three-42');
+ aSet = {1, 7, 10};
+ testUnmodifiableSet(aSet, UnmodifiableSetView(aSet), 'three!42');
+}
+
+void testUnmodifiableList(List<int> original, List<int> wrapped, String name) {
+ name = 'unmodifiable-list-$name';
+ testIterable(original, wrapped, name);
+ testReadList(original, wrapped, name);
+ testNoWriteList(original, wrapped, name);
+ testNoChangeLengthList(original, wrapped, name);
+}
+
+void testNonGrowableList(List<int> original, List<int> wrapped, String name) {
+ name = 'nongrowable-list-$name';
+ testIterable(original, wrapped, name);
+ testReadList(original, wrapped, name);
+ testWriteList(original, wrapped, name);
+ testNoChangeLengthList(original, wrapped, name);
+}
+
+void testUnmodifiableSet(Set<int> original, Set<int> wrapped, String name) {
+ name = 'unmodifiable-set-$name';
+ testIterable(original, wrapped, name);
+ testReadSet(original, wrapped, name);
+ testNoChangeSet(original, wrapped, name);
+}
+
+void testIterable(Iterable<int> original, Iterable<int> wrapped, String name) {
+ test('$name - any', () {
+ expect(wrapped.any((x) => true), equals(original.any((x) => true)));
+ expect(wrapped.any((x) => false), equals(original.any((x) => false)));
+ });
+
+ test('$name - contains', () {
+ expect(wrapped.contains(0), equals(original.contains(0)));
+ });
+
+ test('$name - elementAt', () {
+ if (original.isEmpty) {
+ expect(() => wrapped.elementAt(0), throwsRangeError);
+ } else {
+ expect(wrapped.elementAt(0), equals(original.elementAt(0)));
+ }
+ });
+
+ test('$name - every', () {
+ expect(wrapped.every((x) => true), equals(original.every((x) => true)));
+ expect(wrapped.every((x) => false), equals(original.every((x) => false)));
+ });
+
+ test('$name - expand', () {
+ expect(
+ wrapped.expand((x) => [x, x]), equals(original.expand((x) => [x, x])));
+ });
+
+ test('$name - first', () {
+ if (original.isEmpty) {
+ expect(() => wrapped.first, throwsStateError);
+ } else {
+ expect(wrapped.first, equals(original.first));
+ }
+ });
+
+ test('$name - firstWhere', () {
+ if (original.isEmpty) {
+ expect(() => wrapped.firstWhere((_) => true), throwsStateError);
+ } else {
+ expect(wrapped.firstWhere((_) => true),
+ equals(original.firstWhere((_) => true)));
+ }
+ expect(() => wrapped.firstWhere((_) => false), throwsStateError);
+ });
+
+ test('$name - fold', () {
+ expect(wrapped.fold(0, (dynamic x, y) => x + y),
+ equals(original.fold(0, (dynamic x, y) => x + y)));
+ });
+
+ test('$name - forEach', () {
+ var wrapCtr = 0;
+ var origCtr = 0;
+ // ignore: avoid_function_literals_in_foreach_calls
+ wrapped.forEach((x) {
+ wrapCtr += x;
+ });
+ // ignore: avoid_function_literals_in_foreach_calls
+ original.forEach((x) {
+ origCtr += x;
+ });
+ expect(wrapCtr, equals(origCtr));
+ });
+
+ test('$name - isEmpty', () {
+ expect(wrapped.isEmpty, equals(original.isEmpty));
+ });
+
+ test('$name - isNotEmpty', () {
+ expect(wrapped.isNotEmpty, equals(original.isNotEmpty));
+ });
+
+ test('$name - iterator', () {
+ Iterator wrapIter = wrapped.iterator;
+ Iterator origIter = original.iterator;
+ while (origIter.moveNext()) {
+ expect(wrapIter.moveNext(), equals(true));
+ expect(wrapIter.current, equals(origIter.current));
+ }
+ expect(wrapIter.moveNext(), equals(false));
+ });
+
+ test('$name - join', () {
+ expect(wrapped.join(''), equals(original.join('')));
+ expect(wrapped.join('-'), equals(original.join('-')));
+ });
+
+ test('$name - last', () {
+ if (original.isEmpty) {
+ expect(() => wrapped.last, throwsStateError);
+ } else {
+ expect(wrapped.last, equals(original.last));
+ }
+ });
+
+ test('$name - lastWhere', () {
+ if (original.isEmpty) {
+ expect(() => wrapped.lastWhere((_) => true), throwsStateError);
+ } else {
+ expect(wrapped.lastWhere((_) => true),
+ equals(original.lastWhere((_) => true)));
+ }
+ expect(() => wrapped.lastWhere((_) => false), throwsStateError);
+ });
+
+ test('$name - length', () {
+ expect(wrapped.length, equals(original.length));
+ });
+
+ test('$name - map', () {
+ expect(wrapped.map((x) => '[$x]'), equals(original.map((x) => '[$x]')));
+ });
+
+ test('$name - reduce', () {
+ if (original.isEmpty) {
+ expect(() => wrapped.reduce((x, y) => x + y), throwsStateError);
+ } else {
+ expect(wrapped.reduce((x, y) => x + y),
+ equals(original.reduce((x, y) => x + y)));
+ }
+ });
+
+ test('$name - single', () {
+ if (original.length != 1) {
+ expect(() => wrapped.single, throwsStateError);
+ } else {
+ expect(wrapped.single, equals(original.single));
+ }
+ });
+
+ test('$name - singleWhere', () {
+ if (original.length != 1) {
+ expect(() => wrapped.singleWhere((_) => true), throwsStateError);
+ } else {
+ expect(wrapped.singleWhere((_) => true),
+ equals(original.singleWhere((_) => true)));
+ }
+ expect(() => wrapped.singleWhere((_) => false), throwsStateError);
+ });
+
+ test('$name - skip', () {
+ expect(wrapped.skip(0), orderedEquals(original.skip(0)));
+ expect(wrapped.skip(1), orderedEquals(original.skip(1)));
+ expect(wrapped.skip(5), orderedEquals(original.skip(5)));
+ });
+
+ test('$name - skipWhile', () {
+ expect(wrapped.skipWhile((x) => true),
+ orderedEquals(original.skipWhile((x) => true)));
+ expect(wrapped.skipWhile((x) => false),
+ orderedEquals(original.skipWhile((x) => false)));
+ expect(wrapped.skipWhile((x) => x != 42),
+ orderedEquals(original.skipWhile((x) => x != 42)));
+ });
+
+ test('$name - take', () {
+ expect(wrapped.take(0), orderedEquals(original.take(0)));
+ expect(wrapped.take(1), orderedEquals(original.take(1)));
+ expect(wrapped.take(5), orderedEquals(original.take(5)));
+ });
+
+ test('$name - takeWhile', () {
+ expect(wrapped.takeWhile((x) => true),
+ orderedEquals(original.takeWhile((x) => true)));
+ expect(wrapped.takeWhile((x) => false),
+ orderedEquals(original.takeWhile((x) => false)));
+ expect(wrapped.takeWhile((x) => x != 42),
+ orderedEquals(original.takeWhile((x) => x != 42)));
+ });
+
+ test('$name - toList', () {
+ expect(wrapped.toList(), orderedEquals(original.toList()));
+ expect(wrapped.toList(growable: false),
+ orderedEquals(original.toList(growable: false)));
+ });
+
+ test('$name - toSet', () {
+ expect(wrapped.toSet(), unorderedEquals(original.toSet()));
+ });
+
+ test('$name - where', () {
+ expect(
+ wrapped.where((x) => true), orderedEquals(original.where((x) => true)));
+ expect(wrapped.where((x) => false),
+ orderedEquals(original.where((x) => false)));
+ expect(wrapped.where((x) => x != 42),
+ orderedEquals(original.where((x) => x != 42)));
+ });
+}
+
+void testReadList(List original, List wrapped, String name) {
+ test('$name - length', () {
+ expect(wrapped.length, equals(original.length));
+ });
+
+ test('$name - isEmpty', () {
+ expect(wrapped.isEmpty, equals(original.isEmpty));
+ });
+
+ test('$name - isNotEmpty', () {
+ expect(wrapped.isNotEmpty, equals(original.isNotEmpty));
+ });
+
+ test('$name - []', () {
+ if (original.isEmpty) {
+ expect(() {
+ // ignore: unnecessary_statements
+ wrapped[0];
+ }, throwsRangeError);
+ } else {
+ expect(wrapped[0], equals(original[0]));
+ }
+ });
+
+ test('$name - indexOf', () {
+ expect(wrapped.indexOf(42), equals(original.indexOf(42)));
+ });
+
+ test('$name - lastIndexOf', () {
+ expect(wrapped.lastIndexOf(42), equals(original.lastIndexOf(42)));
+ });
+
+ test('$name - getRange', () {
+ var len = original.length;
+ expect(wrapped.getRange(0, len), equals(original.getRange(0, len)));
+ expect(wrapped.getRange(len ~/ 2, len),
+ equals(original.getRange(len ~/ 2, len)));
+ expect(
+ wrapped.getRange(0, len ~/ 2), equals(original.getRange(0, len ~/ 2)));
+ });
+
+ test('$name - sublist', () {
+ var len = original.length;
+ expect(wrapped.sublist(0), equals(original.sublist(0)));
+ expect(wrapped.sublist(len ~/ 2), equals(original.sublist(len ~/ 2)));
+ expect(wrapped.sublist(0, len ~/ 2), equals(original.sublist(0, len ~/ 2)));
+ });
+
+ test('$name - asMap', () {
+ expect(wrapped.asMap(), equals(original.asMap()));
+ });
+}
+
+void testNoWriteList(List<int> original, List<int> wrapped, String name) {
+ var copy = List.of(original);
+
+ void testThrows(String name, void Function() thunk) {
+ test(name, () {
+ expect(thunk, throwsUnsupportedError);
+ // No modifications happened.
+ expect(original, equals(copy));
+ });
+ }
+
+ testThrows('$name - []= throws', () {
+ wrapped[0] = 42;
+ });
+
+ testThrows('$name - sort throws', () {
+ wrapped.sort();
+ });
+
+ testThrows('$name - fillRange throws', () {
+ wrapped.fillRange(0, wrapped.length, 42);
+ });
+
+ testThrows('$name - setRange throws', () {
+ wrapped.setRange(
+ 0, wrapped.length, Iterable.generate(wrapped.length, (i) => i));
+ });
+
+ testThrows('$name - setAll throws', () {
+ wrapped.setAll(0, Iterable.generate(wrapped.length, (i) => i));
+ });
+}
+
+void testWriteList(List<int> original, List wrapped, String name) {
+ var copy = List.of(original);
+
+ test('$name - []=', () {
+ if (original.isNotEmpty) {
+ var originalFirst = original[0];
+ wrapped[0] = originalFirst + 1;
+ expect(original[0], equals(originalFirst + 1));
+ original[0] = originalFirst;
+ } else {
+ expect(() {
+ wrapped[0] = 42;
+ }, throwsRangeError);
+ }
+ });
+
+ test('$name - sort', () {
+ var sortCopy = List.of(original);
+ sortCopy.sort();
+ wrapped.sort();
+ expect(original, orderedEquals(sortCopy));
+ original.setAll(0, copy);
+ });
+
+ test('$name - fillRange', () {
+ wrapped.fillRange(0, wrapped.length, 37);
+ for (var i = 0; i < original.length; i++) {
+ expect(original[i], equals(37));
+ }
+ original.setAll(0, copy);
+ });
+
+ test('$name - setRange', () {
+ List reverseList = original.reversed.toList();
+ wrapped.setRange(0, wrapped.length, reverseList);
+ expect(original, equals(reverseList));
+ original.setAll(0, copy);
+ });
+
+ test('$name - setAll', () {
+ List reverseList = original.reversed.toList();
+ wrapped.setAll(0, reverseList);
+ expect(original, equals(reverseList));
+ original.setAll(0, copy);
+ });
+}
+
+void testNoChangeLengthList(
+ List<int> original, List<int> wrapped, String name) {
+ var copy = List.of(original);
+
+ void testThrows(String name, void Function() thunk) {
+ test(name, () {
+ expect(thunk, throwsUnsupportedError);
+ // No modifications happened.
+ expect(original, equals(copy));
+ });
+ }
+
+ testThrows('$name - length= throws', () {
+ wrapped.length = 100;
+ });
+
+ testThrows('$name - add throws', () {
+ wrapped.add(42);
+ });
+
+ testThrows('$name - addAll throws', () {
+ wrapped.addAll([42]);
+ });
+
+ testThrows('$name - insert throws', () {
+ wrapped.insert(0, 42);
+ });
+
+ testThrows('$name - insertAll throws', () {
+ wrapped.insertAll(0, [42]);
+ });
+
+ testThrows('$name - remove throws', () {
+ wrapped.remove(42);
+ });
+
+ testThrows('$name - removeAt throws', () {
+ wrapped.removeAt(0);
+ });
+
+ testThrows('$name - removeLast throws', () {
+ wrapped.removeLast();
+ });
+
+ testThrows('$name - removeWhere throws', () {
+ wrapped.removeWhere((element) => false);
+ });
+
+ testThrows('$name - retainWhere throws', () {
+ wrapped.retainWhere((element) => true);
+ });
+
+ testThrows('$name - removeRange throws', () {
+ wrapped.removeRange(0, wrapped.length);
+ });
+
+ testThrows('$name - replaceRange throws', () {
+ wrapped.replaceRange(0, wrapped.length, [42]);
+ });
+
+ testThrows('$name - clear throws', () {
+ wrapped.clear();
+ });
+}
+
+void testReadSet(Set<int> original, Set<int> wrapped, String name) {
+ var copy = Set.of(original);
+
+ test('$name - containsAll', () {
+ expect(wrapped.containsAll(copy), isTrue);
+ expect(wrapped.containsAll(copy.toList()), isTrue);
+ expect(wrapped.containsAll([]), isTrue);
+ expect(wrapped.containsAll([42]), equals(original.containsAll([42])));
+ });
+
+ test('$name - intersection', () {
+ expect(wrapped.intersection({}), isEmpty);
+ expect(wrapped.intersection(copy), unorderedEquals(original));
+ expect(
+ wrapped.intersection({42}), Set.of(original.contains(42) ? [42] : []));
+ });
+
+ test('$name - union', () {
+ expect(wrapped.union({}), unorderedEquals(original));
+ expect(wrapped.union(copy), unorderedEquals(original));
+ expect(wrapped.union({42}), equals(original.union({42})));
+ });
+
+ test('$name - difference', () {
+ expect(wrapped.difference({}), unorderedEquals(original));
+ expect(wrapped.difference(copy), isEmpty);
+ expect(wrapped.difference({42}), equals(original.difference({42})));
+ });
+}
+
+void testNoChangeSet(Set<int> original, Set<int> wrapped, String name) {
+ var originalElements = original.toList();
+
+ void testThrows(String name, void Function() thunk) {
+ test(name, () {
+ expect(thunk, throwsUnsupportedError);
+ // No modifications happened.
+ expect(original.toList(), equals(originalElements));
+ });
+ }
+
+ testThrows('$name - add throws', () {
+ wrapped.add(42);
+ });
+
+ testThrows('$name - addAll throws', () {
+ wrapped.addAll([42]);
+ });
+
+ testThrows('$name - addAll empty throws', () {
+ wrapped.addAll([]);
+ });
+
+ testThrows('$name - remove throws', () {
+ wrapped.remove(42);
+ });
+
+ testThrows('$name - removeAll throws', () {
+ wrapped.removeAll([42]);
+ });
+
+ testThrows('$name - removeAll empty throws', () {
+ wrapped.removeAll([]);
+ });
+
+ testThrows('$name - retainAll throws', () {
+ wrapped.retainAll([42]);
+ });
+
+ testThrows('$name - removeWhere throws', () {
+ wrapped.removeWhere((_) => false);
+ });
+
+ testThrows('$name - retainWhere throws', () {
+ wrapped.retainWhere((_) => true);
+ });
+
+ testThrows('$name - clear throws', () {
+ wrapped.clear();
+ });
+}
diff --git a/pkgs/collection/test/wrapper_test.dart b/pkgs/collection/test/wrapper_test.dart
new file mode 100644
index 0000000..65a693f
--- /dev/null
+++ b/pkgs/collection/test/wrapper_test.dart
@@ -0,0 +1,696 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: unnecessary_statements
+
+/// Tests wrapper utilities.
+@TestOn('vm')
+library;
+
+import 'dart:collection';
+import 'dart:mirrors';
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+// Test that any member access/call on the wrapper object is equal to
+// an expected access on the wrapped object.
+// This is implemented by capturing accesses using noSuchMethod and comparing
+// them to expected accesses captured previously.
+
+// Compare two Invocations for having equal type and arguments.
+void testInvocations(Invocation i1, Invocation i2) {
+ var name = '${i1.memberName}';
+ expect(i1.isGetter, equals(i2.isGetter), reason: name);
+ expect(i1.isSetter, equals(i2.isSetter), reason: name);
+ expect(i1.memberName, equals(i2.memberName), reason: name);
+ expect(i1.positionalArguments, equals(i2.positionalArguments), reason: name);
+ expect(i1.namedArguments, equals(i2.namedArguments), reason: name);
+}
+
+/// Utility class to record a member access and a member access on a wrapped
+/// object, and compare them for equality.
+///
+/// Use as `(expector..someAccess()).equals.someAccess();`.
+/// Alle the intercepted member accesses returns `null`.
+abstract class Expector {
+ dynamic wrappedChecker(Invocation i);
+ // After calling any member on the Expector, equals is an object that expects
+ // the *same* invocation on the wrapped object.
+ dynamic equals;
+
+ InstanceMirror get mirror;
+
+ @override
+ dynamic noSuchMethod(Invocation actual) {
+ equals = wrappedChecker(actual);
+ return mirror.delegate(actual);
+ }
+
+ @override
+ String toString() {
+ // Cannot return an _Equals object since toString must return a String.
+ // Just set equals and return a string.
+ equals = wrappedChecker(toStringInvocation);
+ return '';
+ }
+}
+
+// Parameterization of noSuchMethod. Calls [_action] on every
+// member invocation.
+class InvocationChecker {
+ final Invocation _expected;
+ final InstanceMirror _instanceMirror;
+
+ InvocationChecker(this._expected, this._instanceMirror);
+
+ @override
+ dynamic noSuchMethod(Invocation actual) {
+ testInvocations(_expected, actual);
+ return _instanceMirror.delegate(actual);
+ }
+
+ @override
+ String toString() {
+ testInvocations(_expected, toStringInvocation);
+ return '';
+ }
+ // Could also handle runtimeType, hashCode and == the same way as
+ // toString, but we are not testing them since collections generally
+ // don't override those and so the wrappers don't forward those.
+}
+
+final toStringInvocation = Invocation.method(#toString, const []);
+
+// InvocationCheckers with types Queue, Set, List or Iterable to allow them as
+// argument to DelegatingIterable/Set/List/Queue.
+class IterableInvocationChecker<T> extends InvocationChecker
+ implements Iterable<T> {
+ IterableInvocationChecker(super.expected, super.mirror);
+}
+
+class ListInvocationChecker<T> extends InvocationChecker implements List<T> {
+ ListInvocationChecker(super.expected, super.mirror);
+}
+
+class SetInvocationChecker<T> extends InvocationChecker implements Set<T> {
+ SetInvocationChecker(super.expected, super.mirror);
+}
+
+class QueueInvocationChecker<T> extends InvocationChecker implements Queue<T> {
+ QueueInvocationChecker(super.expected, super.mirror);
+}
+
+class MapInvocationChecker<K, V> extends InvocationChecker
+ implements Map<K, V> {
+ MapInvocationChecker(super.expected, super.mirror);
+}
+
+// Expector that wraps in DelegatingIterable.
+class IterableExpector<T> extends Expector implements Iterable<T> {
+ @override
+ final InstanceMirror mirror;
+
+ IterableExpector(Iterable<T> realInstance) : mirror = reflect(realInstance);
+
+ @override
+ dynamic wrappedChecker(Invocation i) =>
+ DelegatingIterable<T>(IterableInvocationChecker<T>(i, mirror));
+}
+
+// Expector that wraps in DelegatingList.
+class ListExpector<T> extends IterableExpector<T> implements List<T> {
+ ListExpector(List<T> super.realInstance);
+
+ @override
+ dynamic wrappedChecker(Invocation i) =>
+ DelegatingList<T>(ListInvocationChecker<T>(i, mirror));
+}
+
+// Expector that wraps in DelegatingSet.
+class SetExpector<T> extends IterableExpector<T> implements Set<T> {
+ SetExpector(Set<T> super.realInstance);
+
+ @override
+ dynamic wrappedChecker(Invocation i) =>
+ DelegatingSet<T>(SetInvocationChecker<T>(i, mirror));
+}
+
+// Expector that wraps in DelegatingSet.
+class QueueExpector<T> extends IterableExpector<T> implements Queue<T> {
+ QueueExpector(Queue<T> super.realInstance);
+
+ @override
+ dynamic wrappedChecker(Invocation i) =>
+ DelegatingQueue<T>(QueueInvocationChecker<T>(i, mirror));
+}
+
+// Expector that wraps in DelegatingMap.
+class MapExpector<K, V> extends Expector implements Map<K, V> {
+ @override
+ final InstanceMirror mirror;
+
+ MapExpector(Map<K, V> realInstance) : mirror = reflect(realInstance);
+
+ @override
+ dynamic wrappedChecker(Invocation i) =>
+ DelegatingMap<K, V>(MapInvocationChecker<K, V>(i, mirror));
+}
+
+// Utility values to use as arguments in calls.
+// ignore: prefer_void_to_null
+Null func0() => null;
+dynamic func1(dynamic x) => null;
+dynamic func2(dynamic x, dynamic y) => null;
+bool boolFunc(dynamic x) => true;
+Iterable<dynamic> expandFunc(dynamic x) => [x];
+dynamic foldFunc(dynamic previous, dynamic next) => previous;
+int compareFunc(dynamic x, dynamic y) => 0;
+int val = 10;
+
+void main() {
+ void testIterable(IterableExpector expect) {
+ (expect..any(boolFunc)).equals.any(boolFunc);
+ (expect..contains(val)).equals.contains(val);
+ (expect..elementAt(0)).equals.elementAt(0);
+ (expect..every(boolFunc)).equals.every(boolFunc);
+ (expect..expand(expandFunc)).equals.expand(expandFunc);
+ (expect..first).equals.first;
+ // Default values of the Iterable interface will be added in the
+ // second call to firstWhere, so we must record them in our
+ // expectation (which doesn't have the interface implemented or
+ // its default values).
+ (expect..firstWhere(boolFunc, orElse: null)).equals.firstWhere(boolFunc);
+ (expect..firstWhere(boolFunc, orElse: func0))
+ .equals
+ .firstWhere(boolFunc, orElse: func0);
+ (expect..fold(42, foldFunc)).equals.fold(42, foldFunc);
+ (expect..forEach(boolFunc)).equals.forEach(boolFunc);
+ (expect..isEmpty).equals.isEmpty;
+ (expect..isNotEmpty).equals.isNotEmpty;
+ (expect..iterator).equals.iterator;
+ (expect..join('')).equals.join();
+ (expect..join('X')).equals.join('X');
+ (expect..last).equals.last;
+ (expect..lastWhere(boolFunc, orElse: null)).equals.lastWhere(boolFunc);
+ (expect..lastWhere(boolFunc, orElse: func0))
+ .equals
+ .lastWhere(boolFunc, orElse: func0);
+ (expect..length).equals.length;
+ (expect..map(func1)).equals.map(func1);
+ (expect..reduce(func2)).equals.reduce(func2);
+ (expect..single).equals.single;
+ (expect..singleWhere(boolFunc, orElse: null)).equals.singleWhere(boolFunc);
+ (expect..skip(5)).equals.skip(5);
+ (expect..skipWhile(boolFunc)).equals.skipWhile(boolFunc);
+ (expect..take(5)).equals.take(5);
+ (expect..takeWhile(boolFunc)).equals.takeWhile(boolFunc);
+ (expect..toList(growable: true)).equals.toList();
+ (expect..toList(growable: true)).equals.toList(growable: true);
+ (expect..toList(growable: false)).equals.toList(growable: false);
+ (expect..toSet()).equals.toSet();
+ (expect..toString()).equals.toString();
+ (expect..where(boolFunc)).equals.where(boolFunc);
+ }
+
+ void testList(ListExpector expect) {
+ testIterable(expect);
+ // Later expects require at least 5 items
+ (expect..add(val)).equals.add(val);
+ (expect..addAll([val, val, val, val])).equals.addAll([val, val, val, val]);
+
+ (expect..[4]).equals[4];
+ (expect..[4] = 5).equals[4] = 5;
+
+ (expect..asMap()).equals.asMap();
+ (expect..fillRange(4, 5, null)).equals.fillRange(4, 5);
+ (expect..fillRange(4, 5, val)).equals.fillRange(4, 5, val);
+ (expect..getRange(4, 5)).equals.getRange(4, 5);
+ (expect..indexOf(val, 0)).equals.indexOf(val);
+ (expect..indexOf(val, 4)).equals.indexOf(val, 4);
+ (expect..insert(4, val)).equals.insert(4, val);
+ (expect..insertAll(4, [val])).equals.insertAll(4, [val]);
+ (expect..lastIndexOf(val, null)).equals.lastIndexOf(val);
+ (expect..lastIndexOf(val, 4)).equals.lastIndexOf(val, 4);
+ (expect..replaceRange(4, 5, [val])).equals.replaceRange(4, 5, [val]);
+ (expect..retainWhere(boolFunc)).equals.retainWhere(boolFunc);
+ (expect..reversed).equals.reversed;
+ (expect..setAll(4, [val])).equals.setAll(4, [val]);
+ (expect..setRange(4, 5, [val], 0)).equals.setRange(4, 5, [val]);
+ (expect..setRange(4, 5, [val, val], 1))
+ .equals
+ .setRange(4, 5, [val, val], 1);
+ (expect..sort()).equals.sort();
+ (expect..sort(compareFunc)).equals.sort(compareFunc);
+ (expect..sublist(4, null)).equals.sublist(4);
+ (expect..sublist(4, 5)).equals.sublist(4, 5);
+
+ // Do destructive apis last so other ones can work properly
+ (expect..removeAt(4)).equals.removeAt(4);
+ (expect..remove(val)).equals.remove(val);
+ (expect..removeLast()).equals.removeLast();
+ (expect..removeRange(4, 5)).equals.removeRange(4, 5);
+ (expect..removeWhere(boolFunc)).equals.removeWhere(boolFunc);
+ (expect..length = 5).equals.length = 5;
+ (expect..clear()).equals.clear();
+ }
+
+ void testSet(SetExpector expect) {
+ testIterable(expect);
+ var set = <dynamic>{};
+ (expect..add(val)).equals.add(val);
+ (expect..addAll([val])).equals.addAll([val]);
+ (expect..clear()).equals.clear();
+ (expect..containsAll([val])).equals.containsAll([val]);
+ (expect..difference(set)).equals.difference(set);
+ (expect..intersection(set)).equals.intersection(set);
+ (expect..remove(val)).equals.remove(val);
+ (expect..removeAll([val])).equals.removeAll([val]);
+ (expect..removeWhere(boolFunc)).equals.removeWhere(boolFunc);
+ (expect..retainAll([val])).equals.retainAll([val]);
+ (expect..retainWhere(boolFunc)).equals.retainWhere(boolFunc);
+ (expect..union(set)).equals.union(set);
+ }
+
+ void testQueue(QueueExpector expect) {
+ testIterable(expect);
+ (expect..add(val)).equals.add(val);
+ (expect..addAll([val])).equals.addAll([val]);
+ (expect..addFirst(val)).equals.addFirst(val);
+ (expect..addLast(val)).equals.addLast(val);
+ (expect..remove(val)).equals.remove(val);
+ (expect..removeFirst()).equals.removeFirst();
+ (expect..removeLast()).equals.removeLast();
+ (expect..clear()).equals.clear();
+ }
+
+ void testMap(MapExpector expect) {
+ var map = {};
+ (expect..[val]).equals[val];
+ (expect..[val] = val).equals[val] = val;
+ (expect..addAll(map)).equals.addAll(map);
+ (expect..clear()).equals.clear();
+ (expect..containsKey(val)).equals.containsKey(val);
+ (expect..containsValue(val)).equals.containsValue(val);
+ (expect..forEach(func2)).equals.forEach(func2);
+ (expect..isEmpty).equals.isEmpty;
+ (expect..isNotEmpty).equals.isNotEmpty;
+ (expect..keys).equals.keys;
+ (expect..length).equals.length;
+ (expect..putIfAbsent(val, func0)).equals.putIfAbsent(val, func0);
+ (expect..remove(val)).equals.remove(val);
+ (expect..values).equals.values;
+ (expect..toString()).equals.toString();
+ }
+
+ // Runs tests of Set behavior.
+ //
+ // [setUpSet] should return a set with two elements: "foo" and "bar".
+ void testTwoElementSet(Set<String> Function() setUpSet) {
+ group('with two elements', () {
+ late Set<String> set;
+ setUp(() => set = setUpSet());
+
+ test('.any', () {
+ expect(set.any((element) => element == 'foo'), isTrue);
+ expect(set.any((element) => element == 'baz'), isFalse);
+ });
+
+ test('.elementAt', () {
+ expect(set.elementAt(0), equals('foo'));
+ expect(set.elementAt(1), equals('bar'));
+ expect(() => set.elementAt(2), throwsRangeError);
+ });
+
+ test('.every', () {
+ expect(set.every((element) => element == 'foo'), isFalse);
+ expect(set.every((element) => true), isTrue);
+ });
+
+ test('.expand', () {
+ expect(set.expand((element) {
+ return [element.substring(0, 1), element.substring(1)];
+ }), equals(['f', 'oo', 'b', 'ar']));
+ });
+
+ test('.first', () {
+ expect(set.first, equals('foo'));
+ });
+
+ test('.firstWhere', () {
+ expect(set.firstWhere((element) => true), equals('foo'));
+ expect(set.firstWhere((element) => element.startsWith('b')),
+ equals('bar'));
+ expect(() => set.firstWhere((element) => element is int),
+ throwsStateError);
+ expect(set.firstWhere((element) => element is int, orElse: () => 'baz'),
+ equals('baz'));
+ });
+
+ test('.fold', () {
+ expect(
+ set.fold(
+ 'start', (dynamic previous, element) => previous + element),
+ equals('startfoobar'));
+ });
+
+ test('.forEach', () {
+ var values = [];
+ set.forEach(values.add);
+ expect(values, equals(['foo', 'bar']));
+ });
+
+ test('.iterator', () {
+ var values = [];
+ for (var element in set) {
+ values.add(element);
+ }
+ expect(values, equals(['foo', 'bar']));
+ });
+
+ test('.join', () {
+ expect(set.join(', '), equals('foo, bar'));
+ });
+
+ test('.last', () {
+ expect(set.last, equals('bar'));
+ });
+
+ test('.lastWhere', () {
+ expect(set.lastWhere((element) => true), equals('bar'));
+ expect(
+ set.lastWhere((element) => element.startsWith('f')), equals('foo'));
+ expect(
+ () => set.lastWhere((element) => element is int), throwsStateError);
+ expect(set.lastWhere((element) => element is int, orElse: () => 'baz'),
+ equals('baz'));
+ });
+
+ test('.map', () {
+ expect(
+ set.map((element) => element.substring(1)), equals(['oo', 'ar']));
+ });
+
+ test('.reduce', () {
+ expect(set.reduce((previous, element) => previous + element),
+ equals('foobar'));
+ });
+
+ test('.singleWhere', () {
+ expect(() => set.singleWhere((element) => element == 'baz'),
+ throwsStateError);
+ expect(set.singleWhere((element) => element == 'foo'), 'foo');
+ expect(() => set.singleWhere((element) => true), throwsStateError);
+ });
+
+ test('.skip', () {
+ expect(set.skip(0), equals(['foo', 'bar']));
+ expect(set.skip(1), equals(['bar']));
+ expect(set.skip(2), equals([]));
+ });
+
+ test('.skipWhile', () {
+ expect(set.skipWhile((element) => element.startsWith('f')),
+ equals(['bar']));
+ expect(set.skipWhile((element) => element.startsWith('z')),
+ equals(['foo', 'bar']));
+ expect(set.skipWhile((element) => true), equals([]));
+ });
+
+ test('.take', () {
+ expect(set.take(0), equals([]));
+ expect(set.take(1), equals(['foo']));
+ expect(set.take(2), equals(['foo', 'bar']));
+ });
+
+ test('.takeWhile', () {
+ expect(set.takeWhile((element) => element.startsWith('f')),
+ equals(['foo']));
+ expect(set.takeWhile((element) => element.startsWith('z')), equals([]));
+ expect(set.takeWhile((element) => true), equals(['foo', 'bar']));
+ });
+
+ test('.toList', () {
+ expect(set.toList(), equals(['foo', 'bar']));
+ expect(() => set.toList(growable: false).add('baz'),
+ throwsUnsupportedError);
+ expect(set.toList()..add('baz'), equals(['foo', 'bar', 'baz']));
+ });
+
+ test('.toSet', () {
+ expect(set.toSet(), equals({'foo', 'bar'}));
+ });
+
+ test('.where', () {
+ expect(
+ set.where((element) => element.startsWith('f')), equals(['foo']));
+ expect(set.where((element) => element.startsWith('z')), equals([]));
+ expect(set.whereType<String>(), equals(['foo', 'bar']));
+ });
+
+ test('.containsAll', () {
+ expect(set.containsAll(['foo', 'bar']), isTrue);
+ expect(set.containsAll(['foo']), isTrue);
+ expect(set.containsAll(['foo', 'bar', 'qux']), isFalse);
+ });
+
+ test('.difference', () {
+ expect(set.difference({'foo', 'baz'}), equals({'bar'}));
+ });
+
+ test('.intersection', () {
+ expect(set.intersection({'foo', 'baz'}), equals({'foo'}));
+ });
+
+ test('.union', () {
+ expect(set.union({'foo', 'baz'}), equals({'foo', 'bar', 'baz'}));
+ });
+ });
+ }
+
+ test('Iterable', () {
+ testIterable(IterableExpector([1]));
+ });
+
+ test('List', () {
+ testList(ListExpector([1]));
+ });
+
+ test('Set', () {
+ testSet(SetExpector({1}));
+ });
+
+ test('Queue', () {
+ testQueue(QueueExpector(Queue.of([1])));
+ });
+
+ test('Map', () {
+ testMap(MapExpector({'a': 'b'}));
+ });
+
+ group('MapKeySet', () {
+ late Map<String, dynamic> map;
+ late Set<String> set;
+
+ setUp(() {
+ map = <String, int>{};
+ set = MapKeySet<String>(map);
+ });
+
+ testTwoElementSet(() {
+ map['foo'] = 1;
+ map['bar'] = 2;
+ return set;
+ });
+
+ test('.single', () {
+ expect(() => set.single, throwsStateError);
+ map['foo'] = 1;
+ expect(set.single, equals('foo'));
+ map['bar'] = 1;
+ expect(() => set.single, throwsStateError);
+ });
+
+ test('.toString', () {
+ expect(set.toString(), equals('{}'));
+ map['foo'] = 1;
+ map['bar'] = 2;
+ expect(set.toString(), equals('{foo, bar}'));
+ });
+
+ test('.contains', () {
+ expect(set.contains('foo'), isFalse);
+ map['foo'] = 1;
+ expect(set.contains('foo'), isTrue);
+ });
+
+ test('.isEmpty', () {
+ expect(set.isEmpty, isTrue);
+ map['foo'] = 1;
+ expect(set.isEmpty, isFalse);
+ });
+
+ test('.isNotEmpty', () {
+ expect(set.isNotEmpty, isFalse);
+ map['foo'] = 1;
+ expect(set.isNotEmpty, isTrue);
+ });
+
+ test('.length', () {
+ expect(set, hasLength(0));
+ map['foo'] = 1;
+ expect(set, hasLength(1));
+ map['bar'] = 2;
+ expect(set, hasLength(2));
+ });
+
+ test('is unmodifiable', () {
+ expect(() => set.add('baz'), throwsUnsupportedError);
+ expect(() => set.addAll(['baz', 'bang']), throwsUnsupportedError);
+ expect(() => set.remove('foo'), throwsUnsupportedError);
+ expect(() => set.removeAll(['baz', 'bang']), throwsUnsupportedError);
+ expect(() => set.retainAll(['foo']), throwsUnsupportedError);
+ expect(() => set.removeWhere((_) => true), throwsUnsupportedError);
+ expect(() => set.retainWhere((_) => true), throwsUnsupportedError);
+ expect(() => set.clear(), throwsUnsupportedError);
+ });
+ });
+
+ group('MapValueSet', () {
+ late Map<String, String> map;
+ late Set<String> set;
+
+ setUp(() {
+ map = <String, String>{};
+ set =
+ MapValueSet<String, String>(map, (string) => string.substring(0, 1));
+ });
+
+ testTwoElementSet(() {
+ map['f'] = 'foo';
+ map['b'] = 'bar';
+ return set;
+ });
+
+ test('.single', () {
+ expect(() => set.single, throwsStateError);
+ map['f'] = 'foo';
+ expect(set.single, equals('foo'));
+ map['b'] = 'bar';
+ expect(() => set.single, throwsStateError);
+ });
+
+ test('.toString', () {
+ expect(set.toString(), equals('{}'));
+ map['f'] = 'foo';
+ map['b'] = 'bar';
+ expect(set.toString(), equals('{foo, bar}'));
+ });
+
+ test('.contains', () {
+ expect(set.contains('foo'), isFalse);
+ map['f'] = 'foo';
+ expect(set.contains('foo'), isTrue);
+ expect(set.contains('fblthp'), isTrue);
+ });
+
+ test('.isEmpty', () {
+ expect(set.isEmpty, isTrue);
+ map['f'] = 'foo';
+ expect(set.isEmpty, isFalse);
+ });
+
+ test('.isNotEmpty', () {
+ expect(set.isNotEmpty, isFalse);
+ map['f'] = 'foo';
+ expect(set.isNotEmpty, isTrue);
+ });
+
+ test('.length', () {
+ expect(set, hasLength(0));
+ map['f'] = 'foo';
+ expect(set, hasLength(1));
+ map['b'] = 'bar';
+ expect(set, hasLength(2));
+ });
+
+ test('.lookup', () {
+ map['f'] = 'foo';
+ expect(set.lookup('fblthp'), equals('foo'));
+ expect(set.lookup('bar'), isNull);
+ });
+
+ test('.add', () {
+ set.add('foo');
+ set.add('bar');
+ expect(map, equals({'f': 'foo', 'b': 'bar'}));
+ });
+
+ test('.addAll', () {
+ set.addAll(['foo', 'bar']);
+ expect(map, equals({'f': 'foo', 'b': 'bar'}));
+ });
+
+ test('.clear', () {
+ map['f'] = 'foo';
+ map['b'] = 'bar';
+ set.clear();
+ expect(map, isEmpty);
+ });
+
+ test('.remove', () {
+ map['f'] = 'foo';
+ map['b'] = 'bar';
+ set.remove('fblthp');
+ expect(map, equals({'b': 'bar'}));
+ });
+
+ test('.removeAll', () {
+ map['f'] = 'foo';
+ map['b'] = 'bar';
+ map['q'] = 'qux';
+ set.removeAll(['fblthp', 'qux']);
+ expect(map, equals({'b': 'bar'}));
+ });
+
+ test('.removeWhere', () {
+ map['f'] = 'foo';
+ map['b'] = 'bar';
+ map['q'] = 'qoo';
+ set.removeWhere((element) => element.endsWith('o'));
+ expect(map, equals({'b': 'bar'}));
+ });
+
+ test('.retainAll', () {
+ map['f'] = 'foo';
+ map['b'] = 'bar';
+ map['q'] = 'qux';
+ set.retainAll(['fblthp', 'qux']);
+ expect(map, equals({'f': 'foo', 'q': 'qux'}));
+ });
+
+ test('.retainAll respects an unusual notion of equality', () {
+ map = HashMap<String, String>(
+ equals: (value1, value2) =>
+ value1.toLowerCase() == value2.toLowerCase(),
+ hashCode: (value) => value.toLowerCase().hashCode);
+ set =
+ MapValueSet<String, String>(map, (string) => string.substring(0, 1));
+
+ map['f'] = 'foo';
+ map['B'] = 'bar';
+ map['Q'] = 'qux';
+ set.retainAll(['fblthp', 'qux']);
+ expect(map, equals({'f': 'foo', 'Q': 'qux'}));
+ });
+
+ test('.retainWhere', () {
+ map['f'] = 'foo';
+ map['b'] = 'bar';
+ map['q'] = 'qoo';
+ set.retainWhere((element) => element.endsWith('o'));
+ expect(map, equals({'f': 'foo', 'q': 'qoo'}));
+ });
+ });
+}
diff --git a/pkgs/convert/.gitignore b/pkgs/convert/.gitignore
new file mode 100644
index 0000000..79f51c3
--- /dev/null
+++ b/pkgs/convert/.gitignore
@@ -0,0 +1,3 @@
+.dart_tool
+.packages
+pubspec.lock
diff --git a/pkgs/convert/AUTHORS b/pkgs/convert/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/convert/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/convert/CHANGELOG.md b/pkgs/convert/CHANGELOG.md
new file mode 100644
index 0000000..8fc7ff5
--- /dev/null
+++ b/pkgs/convert/CHANGELOG.md
@@ -0,0 +1,75 @@
+## 3.1.2
+
+- Require Dart 3.4
+- Add chunked decoding support (`startChunkedConversion`) for `CodePage`
+ encodings.
+- Upper-cast the return type of the decoder from `List<int>` to `Uint8List`.
+- Move to `dart-lang/core` monorepo.
+
+## 3.1.1
+
+- Require Dart 2.18
+- Fix a number of comment references.
+
+## 3.1.0
+
+- Add a fixed-pattern DateTime formatter. See
+ [#210](https://github.com/dart-lang/intl/issues/210) in package:intl.
+
+## 3.0.2
+
+- Fix bug in `CodePage` class. See issue
+ [#47](https://github.com/dart-lang/convert/issues/47).
+
+## 3.0.1
+
+- Dependency clean-up.
+
+## 3.0.0
+
+- Stable null safety release.
+- Added `CodePage` class for single-byte `Encoding` implementations.
+
+## 2.1.1
+
+- Fixed a DDC compilation regression for consumers using the Dart 1.x SDK that
+ was introduced in `2.1.0`.
+
+## 2.1.0
+
+- Added an `IdentityCodec<T>` which implements `Codec<T,T>` for use as default
+ value for in functions accepting an optional `Codec` as parameter.
+
+## 2.0.2
+
+- Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 2.0.1
+
+- `PercentEncoder` no longer encodes digits. This follows the specified
+ behavior.
+
+## 2.0.0
+
+**Note**: No new APIs have been added in 2.0.0. Packages that would use 2.0.0 as
+a lower bound should use 1.0.0 instead—for example, `convert: ">=1.0.0 <3.0.0"`.
+
+- `HexDecoder`, `HexEncoder`, `PercentDecoder`, and `PercentEncoder` no longer
+ extend `ChunkedConverter`.
+
+## 1.1.1
+
+- Fix all strong-mode warnings.
+
+## 1.1.0
+
+- Add `AccumulatorSink`, `ByteAccumulatorSink`, and `StringAccumulatorSink`
+ classes for providing synchronous access to the output of chunked converters.
+
+## 1.0.1
+
+- Small improvement in percent decoder efficiency.
+
+## 1.0.0
+
+- Initial version
diff --git a/pkgs/convert/LICENSE b/pkgs/convert/LICENSE
new file mode 100644
index 0000000..633672a
--- /dev/null
+++ b/pkgs/convert/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2015, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/convert/README.md b/pkgs/convert/README.md
new file mode 100644
index 0000000..4efd4b0
--- /dev/null
+++ b/pkgs/convert/README.md
@@ -0,0 +1,9 @@
+[](https://github.com/dart-lang/core/actions/workflows/convert.yaml)
+[](https://pub.dev/packages/convert)
+[](https://pub.dev/packages/convert/publisher)
+
+Contains encoders and decoders for converting between different
+data representations. It's the external counterpart of the
+[`dart:convert`](https://api.dart.dev/dart-convert/dart-convert-library.html)
+SDK library, and contains less-central APIs and APIs that need more flexible
+versioning.
diff --git a/pkgs/convert/analysis_options.yaml b/pkgs/convert/analysis_options.yaml
new file mode 100644
index 0000000..3b001dd
--- /dev/null
+++ b/pkgs/convert/analysis_options.yaml
@@ -0,0 +1,23 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - unnecessary_await_in_return
+ - use_string_buffers
diff --git a/pkgs/convert/benchmark/fixed_datetime_formatter_benchmark.dart b/pkgs/convert/benchmark/fixed_datetime_formatter_benchmark.dart
new file mode 100644
index 0000000..6f35c01
--- /dev/null
+++ b/pkgs/convert/benchmark/fixed_datetime_formatter_benchmark.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:benchmark_harness/benchmark_harness.dart';
+import 'package:convert/convert.dart';
+
+/// Test the performance of [FixedDateTimeFormatter.decode].
+class DecodeBenchmark extends BenchmarkBase {
+ final fixedDateTimeFormatter = FixedDateTimeFormatter('YYYYMMDDhhmmss');
+ DecodeBenchmark() : super('Parse 10k strings to DateTime');
+
+ @override
+ void run() {
+ for (var i = 0; i < 10000; i++) {
+ fixedDateTimeFormatter.decode('19960425050322');
+ }
+ }
+}
+
+void main() {
+ DecodeBenchmark().report();
+}
diff --git a/pkgs/convert/example/example.dart b/pkgs/convert/example/example.dart
new file mode 100644
index 0000000..5038591
--- /dev/null
+++ b/pkgs/convert/example/example.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
+import 'package:convert/convert.dart';
+
+void main(List<String> args) {
+ // Creates a Codec that converts a UTF-8 strings to/from percent encoding
+ final fusedCodec = utf8.fuse(percent);
+
+ final input = args.isNotEmpty ? args.first : 'ABC 123 @!(';
+ print(input);
+ final encodedMessage = fusedCodec.encode(input);
+ print(encodedMessage);
+
+ final decodedMessage = fusedCodec.decode(encodedMessage);
+ assert(decodedMessage == input);
+}
diff --git a/pkgs/convert/lib/convert.dart b/pkgs/convert/lib/convert.dart
new file mode 100644
index 0000000..ec5c878
--- /dev/null
+++ b/pkgs/convert/lib/convert.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'src/accumulator_sink.dart';
+export 'src/byte_accumulator_sink.dart';
+export 'src/codepage.dart';
+export 'src/fixed_datetime_formatter.dart';
+export 'src/hex.dart';
+export 'src/identity_codec.dart';
+export 'src/percent.dart';
+export 'src/string_accumulator_sink.dart';
diff --git a/pkgs/convert/lib/src/accumulator_sink.dart b/pkgs/convert/lib/src/accumulator_sink.dart
new file mode 100644
index 0000000..af55b10
--- /dev/null
+++ b/pkgs/convert/lib/src/accumulator_sink.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+import 'dart:convert';
+
+/// A sink that provides access to all the [events] that have been passed to it.
+///
+/// See also [ChunkedConversionSink.withCallback].
+class AccumulatorSink<T> implements Sink<T> {
+ /// An unmodifiable list of events passed to this sink so far.
+ List<T> get events => UnmodifiableListView(_events);
+ final _events = <T>[];
+
+ /// Whether [close] has been called.
+ bool get isClosed => _isClosed;
+ var _isClosed = false;
+
+ /// Removes all events from [events].
+ ///
+ /// This can be used to avoid double-processing events.
+ void clear() {
+ _events.clear();
+ }
+
+ @override
+ void add(T event) {
+ if (_isClosed) {
+ throw StateError("Can't add to a closed sink.");
+ }
+
+ _events.add(event);
+ }
+
+ @override
+ void close() {
+ _isClosed = true;
+ }
+}
diff --git a/pkgs/convert/lib/src/byte_accumulator_sink.dart b/pkgs/convert/lib/src/byte_accumulator_sink.dart
new file mode 100644
index 0000000..44cf178
--- /dev/null
+++ b/pkgs/convert/lib/src/byte_accumulator_sink.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:typed_data/typed_data.dart';
+
+/// A sink that provides access to the concatenated bytes passed to it.
+///
+/// See also [ByteConversionSink.withCallback].
+class ByteAccumulatorSink extends ByteConversionSinkBase {
+ /// The bytes accumulated so far.
+ ///
+ /// The returned [Uint8List] is viewing a shared buffer, so it should not be
+ /// changed and any bytes outside the view should not be accessed.
+ Uint8List get bytes => Uint8List.view(_buffer.buffer, 0, _buffer.length);
+
+ final _buffer = Uint8Buffer();
+
+ /// Whether [close] has been called.
+ bool get isClosed => _isClosed;
+ var _isClosed = false;
+
+ /// Removes all bytes from [bytes].
+ ///
+ /// This can be used to avoid double-processing data.
+ void clear() {
+ _buffer.clear();
+ }
+
+ @override
+ void add(List<int> chunk) {
+ if (_isClosed) {
+ throw StateError("Can't add to a closed sink.");
+ }
+
+ _buffer.addAll(chunk);
+ }
+
+ @override
+ void addSlice(List<int> chunk, int start, int end, bool isLast) {
+ if (_isClosed) {
+ throw StateError("Can't add to a closed sink.");
+ }
+
+ _buffer.addAll(chunk, start, end);
+ if (isLast) _isClosed = true;
+ }
+
+ @override
+ void close() {
+ _isClosed = true;
+ }
+}
diff --git a/pkgs/convert/lib/src/charcodes.dart b/pkgs/convert/lib/src/charcodes.dart
new file mode 100644
index 0000000..fc94148
--- /dev/null
+++ b/pkgs/convert/lib/src/charcodes.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Character `%`.
+const int $percent = 0x25;
+
+/// Character `-`.
+const int $dash = 0x2d;
+
+/// Character `.`.
+const int $dot = 0x2e;
+
+/// Character `0`.
+const int $0 = 0x30;
+
+/// Character `9`.
+const int $9 = 0x39;
+
+/// Character `A`.
+const int $A = 0x41;
+
+/// Character `_`.
+const int $underscore = 0x5f;
+
+/// Character `a`.
+const int $a = 0x61;
+
+/// Character `f`.
+const int $f = 0x66;
+
+/// Character `z`.
+const int $z = 0x7a;
+
+/// Character `~`.
+const int $tilde = 0x7e;
diff --git a/pkgs/convert/lib/src/codepage.dart b/pkgs/convert/lib/src/codepage.dart
new file mode 100644
index 0000000..c298ff5
--- /dev/null
+++ b/pkgs/convert/lib/src/codepage.dart
@@ -0,0 +1,472 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:typed_data';
+
+/// The ISO-8859-2/Latin-2 (Eastern European) code page.
+final CodePage latin2 =
+ CodePage._bmp('latin-2', '$_ascii$_noControls$_top8859_2');
+
+/// The ISO-8859-3/Latin-3 (South European) code page.
+final CodePage latin3 =
+ CodePage._bmp('latin-3', '$_ascii$_noControls$_top8859_3');
+
+/// The ISO-8859-4/Latin-4 (North European) code page.
+final CodePage latin4 =
+ CodePage._bmp('latin-4', '$_ascii$_noControls$_top8859_4');
+
+/// The ISO-8859-5/Latin-Cyrillic code page.
+final CodePage latinCyrillic =
+ CodePage._bmp('cyrillic', '$_ascii$_noControls$_top8859_5');
+
+/// The ISO-8859-6/Latin-Arabic code page.
+final CodePage latinArabic =
+ CodePage._bmp('arabic', '$_ascii$_noControls$_top8859_6');
+
+/// The ISO-8859-7/Latin-Greek code page.
+final CodePage latinGreek =
+ CodePage._bmp('greek', '$_ascii$_noControls$_top8859_7');
+
+/// The ISO-8859-7/Latin-Hebrew code page.
+final CodePage latinHebrew =
+ CodePage._bmp('hebrew', '$_ascii$_noControls$_top8859_8');
+
+/// The ISO-8859-9/Latin-5 (Turkish) code page.
+final CodePage latin5 =
+ CodePage._bmp('latin-5', '$_ascii$_noControls$_top8859_9');
+
+/// The ISO-8859-10/Latin-6 (Nordic) code page.
+final CodePage latin6 =
+ CodePage._bmp('latin-6', '$_ascii$_noControls$_top8859_10');
+
+/// The ISO-8859-11/Latin-Thai code page.
+final CodePage latinThai =
+ CodePage._bmp('tis620', '$_ascii$_noControls$_top8859_11');
+
+/// The ISO-8859-13/Latin-6 (Baltic Rim) code page.
+final CodePage latin7 =
+ CodePage._bmp('latin-7', '$_ascii$_noControls$_top8859_13');
+
+/// The ISO-8859-14/Latin-8 (Celtic) code page.
+final CodePage latin8 =
+ CodePage._bmp('latin-8', '$_ascii$_noControls$_top8859_14');
+
+/// The ISO-8859-15/Latin-9 (Western European revised) code page.
+final CodePage latin9 =
+ CodePage._bmp('latin-9', '$_ascii$_noControls$_top8859_15');
+
+/// The ISO-8859-16/Latin-10 (South Eastern European) code page.
+final CodePage latin10 =
+ CodePage._bmp('latin-10', '$_ascii$_noControls$_top8859_16');
+
+/// Characters in ISO-8859-2 above the ASCII and top control characters.
+const _top8859_2 = '\xa0Ą˘Ł¤ĽŚ§¨ŠŞŤŹ\xadŽŻ°ą˛ł´ľśˇ¸šşťź˝žż'
+ 'ŔÁÂĂÄĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢß'
+ 'ŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ˙';
+
+/// Characters in ISO-8859-3 above the ASCII and top control characters.
+const _top8859_3 = '\xa0Ħ˘£\uFFFD¤Ĥ§¨İŞĞĴ\xad\uFFFDݰħ²³´µĥ·¸ışğĵ½\uFFFDż'
+ 'ÀÁÂ\uFFFDÄĊĈÇÈÉÊËÌÍÎÏ\uFFFDÑÒÓÔĠÖ×ĜÙÚÛÜŬŜß'
+ 'àáâ\uFFFDäċĉçèéêëìíîï\uFFFDñòóôġö÷ĝùúûüŭŝ˙';
+
+/// Characters in ISO-8859-4 above the ASCII and top control characters.
+const _top8859_4 = '\xa0ĄĸŖ¤Ĩϧ¨ŠĒĢŦ\xadޝ°ą˛ŗ´ĩšēģŧŊžŋ'
+ 'ĀÁÂÃÄÅÆĮČÉĘËĖÍÎĪĐŅŌĶÔÕÖרŲÚÛÜŨŪß'
+ 'āáâãäåæįčéęëėíîīđņōķôõö÷øųúûüũū˙';
+
+/// Characters in ISO-8859-5 above the ASCII and top control characters.
+const _top8859_5 = '\xa0ЁЂЃЄЅІЇЈЉЊЋЌ\xadЎЏАБВГДЕЖЗИЙКЛМНОП'
+ 'РСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмноп'
+ 'рстуфхцчшщъыьэюя№ёђѓєѕіїјљњћќ§ўџ';
+
+/// Characters in ISO-8859-6 above the ASCII and top control characters.
+const _top8859_6 = '\xa0\uFFFD\uFFFD\uFFFD¤\uFFFD\uFFFD\uFFFD'
+ '\uFFFD\uFFFD\uFFFD\uFFFD\u060c\xad\uFFFD\uFFFD'
+ '\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD'
+ '\uFFFD\uFFFD\uFFFD\u061b\uFFFD\uFFFD\uFFFD\u061f'
+ '\uFFFD\u0621\u0622\u0623\u0624\u0625\u0626\u0627'
+ '\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f'
+ '\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637'
+ '\u0638\u0639\u063a\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD'
+ '\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647'
+ '\u0648\u0649\u064a\u064b\u064c\u064d\u064e\u064f'
+ '\u0650\u0651\u0652\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD'
+ '\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD';
+
+/// Characters in ISO-8859-7 above the ASCII and top control characters.
+const _top8859_7 = '\xa0‘’£€₯¦§¨©ͺ«¬\xad\uFFFD―°±²³΄΅Ά·ΈΉΊ»Ό½ΎΏ'
+ 'ΐΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡ\uFFFDΣΤΥΦΧΨΩΪΫάέήί'
+ 'ΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώ\uFFFD';
+
+/// Characters in ISO-8859-8 above the ASCII and top control characters.
+const _top8859_8 = '\xa0\uFFFD¢£¤¥¦§¨©×«¬\xad®¯°±²³´µ¶·¸¹÷»¼½¾\uFFFD'
+ '\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD'
+ '\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD'
+ '\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD'
+ '\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD‗'
+ '\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7'
+ '\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df'
+ '\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6\u05e7'
+ '\u05e8\u05e9\u05ea\uFFFD\uFFFD\u200e\u200f\uFFFD';
+
+/// Characters in ISO-8859-9 above the ASCII and top control characters.
+const _top8859_9 = '\xa0¡¢£¤¥¦§¨©ª«¬\xad®¯°±²³´µ¶·¸¹º»¼½¾¿'
+ 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏĞÑÒÓÔÕÖרÙÚÛÜİŞß'
+ 'àáâãäåæçèéêëìíîïğñòóôõö÷øùúûüışÿ';
+
+/// Characters in ISO-8859-10 above the ASCII and top control characters.
+const _top8859_10 = '\xa0ĄĒĢĪĨͧĻĐŠŦŽ\xadŪŊ°ąēģīĩķ·ļđšŧž―ūŋ'
+ 'ĀÁÂÃÄÅÆĮČÉĘËĖÍÎÏÐŅŌÓÔÕÖŨØŲÚÛÜÝÞß'
+ 'āáâãäåæįčéęëėíîïðņōóôõöũøųúûüýþĸ';
+
+/// Characters in ISO-8859-11 above the ASCII and top control characters.
+const _top8859_11 = '\xa0กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟ'
+ 'ภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู\uFFFD\uFFFD\uFFFD\uFFFD฿'
+ 'เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛\uFFFD\uFFFD\uFFFD\uFFFD';
+
+/// Characters in ISO-8859-13 above the ASCII and top control characters.
+const _top8859_13 = '\xa0”¢£¤„¦§Ø©Ŗ«¬\xad®Æ°±²³“µ¶·ø¹ŗ»¼½¾æ'
+ 'ĄĮĀĆÄÅĘĒČÉŹĖĢĶĪĻŠŃŅÓŌÕÖ×ŲŁŚŪÜŻŽß'
+ 'ąįāćäåęēčéźėģķīļšńņóōõö÷ųłśūüżž’';
+
+/// Characters in ISO-8859-14 above the ASCII and top control characters.
+const _top8859_14 = '\xa0Ḃḃ£ĊċḊ§Ẁ©ẂḋỲ\xad®ŸḞḟĠġṀṁ¶ṖẁṗẃṠỳẄẅṡ'
+ 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏŴÑÒÓÔÕÖṪØÙÚÛÜÝŶß'
+ 'àáâãäåæçèéêëìíîïŵñòóôõöṫøùúûüýŷÿ';
+
+/// Characters in ISO-8859-15 above the ASCII and top control characters.
+const _top8859_15 = '\xa0¡¢£€¥Š§š©ª«¬\xad®¯°±²³Žµ¶·ž¹º»ŒœŸ¿'
+ 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß'
+ 'àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ';
+
+/// Characters in ISO-8859-16 above the ASCII and top control characters.
+const _top8859_16 = '\xa0ĄąŁ€„Чš©Ș«Ź\xadźŻ°±ČłŽ”¶·žčș»ŒœŸż'
+ 'ÀÁÂĂÄĆÆÇÈÉÊËÌÍÎÏĐŃÒÓÔŐÖŚŰÙÚÛÜĘȚß'
+ 'àáâăäćæçèéêëìíîïđńòóôőöśűùúûüęțÿ';
+
+const _noControls = '\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD'
+ '\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD'
+ '\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD'
+ '\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD';
+
+/// ASCII characters without control characters. Shared by many code pages.
+const _ascii = '$_noControls'
+ // ignore: missing_whitespace_between_adjacent_strings
+ r""" !"#$%&'()*+,-./0123456789:;<=>?"""
+ r'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_'
+ '`abcdefghijklmnopqrstuvwxyz{|}~\uFFFD';
+
+/// A mapping between bytes and characters.
+///
+/// A code page is a way to map bytes to character.
+/// As such, it can only represent 256 different characters.
+class CodePage extends Encoding {
+ @override
+ final CodePageDecoder decoder;
+ @override
+ final String name;
+ CodePageEncoder? _encoder;
+
+ /// Creates a code page with the given name and characters.
+ ///
+ /// The [characters] string must contain 256 code points (runes)
+ /// in the order of the bytes representing them.
+ ///
+ /// Any byte not defined by the code page should have a
+ /// U+FFFD (invalid character) code point at its place in
+ /// [characters].
+ ///
+ /// The name is used by [Encoding.name].
+ factory CodePage(String name, String characters) = CodePage._general;
+
+ /// Creates a code page with the characters of [characters].
+ ///
+ /// The [characters] must contain precisely 256 characters (code points).
+ ///
+ /// A U+FFFD (invalid character) entry in [characters] means that the
+ /// corresponding byte does not have a definition in this code page.
+ CodePage._general(this.name, String characters)
+ : decoder = _createDecoder(characters);
+
+ /// Creates a code page with characters from the basic multilingual plane.
+ ///
+ /// The basic multilingual plane (BMP) contains the first 65536 code points.
+ /// As such, each character can be represented by a single UTF-16 code unit,
+ /// which makes some operations more efficient.
+ ///
+ /// The [characters] must contain precisely 256 code points from the BMP
+ /// which means that it should have length 256 and not contain any surrogates.
+ ///
+ /// A U+FFFD (invalid character) entry in [characters] means that the
+ /// corresponding byte does not have a definition in this code page.
+ CodePage._bmp(this.name, String characters)
+ : decoder = _BmpCodePageDecoder(characters);
+
+ /// The character associated with a particular byte in this code page.
+ ///
+ /// The [byte] must be in the range 0..255.
+ /// The returned value should be a Unicode scalar value
+ /// (a non-surrogate code point).
+ ///
+ /// If a code page does not have a defined character for a particular
+ /// byte, it should return the Unicode invalid character (U+FFFD)
+ /// instad.
+ int operator [](int byte) => decoder._char(byte);
+
+ /// Encodes [input] using `encoder.convert`.
+ @override
+ Uint8List encode(String input, {int? invalidCharacter}) =>
+ encoder.convert(input, invalidCharacter: invalidCharacter);
+
+ /// Decodes [bytes] using `encoder.convert`.
+ @override
+ String decode(List<int> bytes, {bool allowInvalid = false}) =>
+ decoder.convert(bytes, allowInvalid: allowInvalid);
+
+ @override
+ CodePageEncoder get encoder => _encoder ??= decoder._createEncoder();
+}
+
+/// A code page decoder, converts from bytes to characters.
+///
+/// A code page assigns characters to a subset of byte values.
+/// The decoder converts those bytes back to their characters.
+abstract class CodePageDecoder implements Converter<List<int>, String> {
+ /// Decodes a sequence of bytes into a string using a code page.
+ ///
+ /// The code page assigns one character to each byte.
+ /// Values in [input] must be bytes (integers in the range 0..255).
+ ///
+ /// If [allowInvalid] is true, non-byte values in [input],
+ /// or byte values not defined as a character in the code page,
+ /// are emitted as U+FFFD (the Unicode invalid character).
+ /// If not true, the bytes must be calid and defined characters.
+ @override
+ String convert(List<int> input, {bool allowInvalid = false});
+
+ CodePageEncoder _createEncoder();
+ int _char(int byte);
+}
+
+/// Creates a decoder from [characters].
+///
+/// Recognizes if [characters] contains only characters in the BMP,
+/// and creates a [_BmpCodePageDecoder] in that case.
+CodePageDecoder _createDecoder(String characters) {
+ var result = Uint32List(256);
+ var i = 0;
+ var allChars = 0;
+ for (var char in characters.runes) {
+ if (i >= 256) {
+ throw ArgumentError.value(
+ characters, 'characters', 'Must contain 256 characters');
+ }
+ result[i++] = char;
+ allChars |= char;
+ }
+ if (i < 256) {
+ throw ArgumentError.value(
+ characters, 'characters', 'Must contain 256 characters');
+ }
+ if (allChars <= 0xFFFF) {
+ // It's in the BMP.
+ return _BmpCodePageDecoder(characters);
+ }
+ return _NonBmpCodePageDecoder._(result);
+}
+
+/// An input [ByteConversionSink] for decoders where each input byte can be be
+/// considered independantly.
+class _CodePageDecoderSink extends ByteConversionSink {
+ final Sink<String> _output;
+ final Converter<List<int>, String> _decoder;
+
+ _CodePageDecoderSink(this._output, this._decoder);
+
+ @override
+ void add(List<int> chunk) {
+ _output.add(_decoder.convert(chunk));
+ }
+
+ @override
+ void close() {
+ _output.close();
+ }
+}
+
+/// Code page with non-BMP characters.
+class _NonBmpCodePageDecoder extends Converter<List<int>, String>
+ implements CodePageDecoder {
+ final Uint32List _characters;
+ _NonBmpCodePageDecoder(String characters) : this._(_buildMapping(characters));
+ _NonBmpCodePageDecoder._(this._characters);
+
+ @override
+ int _char(int byte) => _characters[byte];
+
+ static Uint32List _buildMapping(String characters) {
+ var result = Uint32List(256);
+ var i = 0;
+ for (var char in characters.runes) {
+ if (i >= 256) {
+ throw ArgumentError.value(
+ characters, 'characters', 'Must contain 256 characters');
+ }
+ result[i++] = char;
+ }
+ if (i < 256) {
+ throw ArgumentError.value(
+ characters, 'characters', 'Must contain 256 characters');
+ }
+ return result;
+ }
+
+ @override
+ CodePageEncoder _createEncoder() {
+ var result = <int, int>{};
+ for (var i = 0; i < 256; i++) {
+ var char = _characters[i];
+ if (char != 0xFFFD) {
+ result[char] = i;
+ }
+ }
+ return CodePageEncoder._(result);
+ }
+
+ @override
+ String convert(List<int> input, {bool allowInvalid = false}) {
+ var buffer = Uint32List(input.length);
+ for (var i = 0; i < input.length; i++) {
+ var byte = input[i];
+ if (byte & 0xff != byte) throw FormatException('Not a byte', input, i);
+ buffer[i] = _characters[byte];
+ }
+ return String.fromCharCodes(buffer);
+ }
+
+ @override
+ Sink<List<int>> startChunkedConversion(Sink<String> sink) =>
+ _CodePageDecoderSink(sink, this);
+}
+
+class _BmpCodePageDecoder extends Converter<List<int>, String>
+ implements CodePageDecoder {
+ final String _characters;
+ _BmpCodePageDecoder(String characters) : _characters = characters {
+ if (characters.length != 256) {
+ throw ArgumentError.value(characters, 'characters',
+ 'Must contain 256 characters. Was ${characters.length}');
+ }
+ }
+
+ @override
+ int _char(int byte) => _characters.codeUnitAt(byte);
+
+ @override
+ String convert(List<int> bytes, {bool allowInvalid = false}) {
+ if (allowInvalid) return _convertAllowInvalid(bytes);
+ var count = bytes.length;
+ var codeUnits = Uint16List(count);
+ for (var i = 0; i < count; i++) {
+ var byte = bytes[i];
+ if (byte != byte & 0xff) {
+ throw FormatException('Not a byte value', bytes, i);
+ }
+ var character = _characters.codeUnitAt(byte);
+ if (character == 0xFFFD) {
+ throw FormatException('Not defined in this code page', bytes, i);
+ }
+ codeUnits[i] = character;
+ }
+ return String.fromCharCodes(codeUnits);
+ }
+
+ @override
+ Sink<List<int>> startChunkedConversion(Sink<String> sink) =>
+ _CodePageDecoderSink(sink, this);
+
+ String _convertAllowInvalid(List<int> bytes) {
+ var count = bytes.length;
+ var codeUnits = Uint16List(count);
+ for (var i = 0; i < count; i++) {
+ var byte = bytes[i];
+ int character;
+ if (byte == byte & 0xff) {
+ character = _characters.codeUnitAt(byte);
+ } else {
+ character = 0xFFFD;
+ }
+ codeUnits[i] = character;
+ }
+ return String.fromCharCodes(codeUnits);
+ }
+
+ @override
+ CodePageEncoder _createEncoder() => CodePageEncoder._bmp(_characters);
+}
+
+/// Encoder for a code page.
+///
+/// Converts a string into bytes where each byte represents that character
+/// according to the code page definition.
+class CodePageEncoder extends Converter<String, List<int>> {
+ final Map<int, int> _encoding;
+
+ CodePageEncoder._bmp(String characters)
+ : _encoding = _createBmpEncoding(characters);
+
+ CodePageEncoder._(this._encoding);
+
+ static Map<int, int> _createBmpEncoding(String characters) {
+ var encoding = <int, int>{};
+ for (var i = 0; i < characters.length; i++) {
+ var char = characters.codeUnitAt(i);
+ if (char != 0xFFFD) encoding[characters.codeUnitAt(i)] = i;
+ }
+ return encoding;
+ }
+
+ /// Converts input to the byte encoding in this code page.
+ ///
+ /// If [invalidCharacter] is supplied, it must be a byte value
+ /// (in the range 0..255).
+ ///
+ /// If [input] contains characters that are not available
+ /// in this code page, they are replaced by the [invalidCharacter] byte,
+ /// and then [invalidCharacter] must have been supplied.
+ @override
+ Uint8List convert(String input, {int? invalidCharacter}) {
+ if (invalidCharacter != null) {
+ RangeError.checkValueInInterval(
+ invalidCharacter, 0, 255, 'invalidCharacter');
+ }
+ var count = input.length;
+ var result = Uint8List(count);
+ var j = 0;
+ for (var i = 0; i < count; i++) {
+ var char = input.codeUnitAt(i);
+ var byte = _encoding[char];
+ nullCheck:
+ if (byte == null) {
+ // Check for surrogate.
+ var offset = i;
+ if (char & 0xFC00 == 0xD800 && i + 1 < count) {
+ var next = input.codeUnitAt(i + 1);
+ if ((next & 0xFC00) == 0xDC00) {
+ i = i + 1;
+ char = 0x10000 + ((char & 0x3ff) << 10) + (next & 0x3ff);
+ byte = _encoding[char];
+ if (byte != null) break nullCheck;
+ }
+ }
+ byte = invalidCharacter ??
+ (throw FormatException(
+ 'Not a character in this code page', input, offset));
+ }
+ result[j++] = byte;
+ }
+ return Uint8List.sublistView(result, 0, j);
+ }
+}
diff --git a/pkgs/convert/lib/src/fixed_datetime_formatter.dart b/pkgs/convert/lib/src/fixed_datetime_formatter.dart
new file mode 100644
index 0000000..fc0a58a
--- /dev/null
+++ b/pkgs/convert/lib/src/fixed_datetime_formatter.dart
@@ -0,0 +1,378 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A formatter and parser for [DateTime] in a fixed format [String] pattern.
+///
+/// For example, calling
+/// `FixedDateTimeCodec('YYYYMMDDhhmmss').decodeToLocal('19960425050322')` has
+/// the same result as calling `DateTime(1996, 4, 25, 5, 3, 22)`.
+///
+/// The allowed characters are
+/// * `Y` for “calendar year”
+/// * `M` for “calendar month”
+/// * `D` for “calendar day”
+/// * `E` for “decade”
+/// * `C` for “century”
+/// * `h` for “clock hour”
+/// * `m` for “clock minute”
+/// * `s` for “clock second”
+/// * `S` for “fractional clock second”
+///
+/// Note: Negative years are not supported.
+///
+/// Non-allowed characters in the format [pattern] are included when decoding a
+/// string, in this case `YYYY kiwi MM` is the same format string as
+/// `YYYY------MM`. When encoding a [DateTime], the non-format characters are in
+/// the output verbatim.
+///
+/// Note: this class differs from
+/// [DateFormat](https://pub.dev/documentation/intl/latest/intl/DateFormat-class.html)
+/// from [package:intl](https://pub.dev/packages/intl) in that here, the format
+/// character count is interpreted literally. For example, using the format
+/// string `YYY` to decode the string `996` would result in the same [DateTime]
+/// as calling `DateTime(996)`, and the same format string used to encode the
+/// `DateTime(1996)` would output only the three digits 996.
+class FixedDateTimeFormatter {
+ static const _powersOfTen = [1, 10, 100, 1000, 10000, 100000];
+ static const _validFormatCharacters = [
+ _yearCode,
+ _monthCode,
+ _dayCode,
+ _decadeCode,
+ _centuryCode,
+ _hourCode,
+ _minuteCode,
+ _secondCode,
+ _fractionSecondCode,
+ ];
+ static const _yearCode = 0x59; /*Y*/
+ static const _monthCode = 0x4D; /*M*/
+ static const _dayCode = 0x44; /*D*/
+ static const _decadeCode = 0x45; /*E*/
+ static const _centuryCode = 0x43; /*C*/
+ static const _hourCode = 0x68; /*H*/
+ static const _minuteCode = 0x6D; /*m*/
+ static const _secondCode = 0x73; /*s*/
+ static const _fractionSecondCode = 0x53; /*S*/
+
+ /// The format pattern string of this formatter.
+ final String pattern;
+
+ /// Whether to create UTC [DateTime] objects when parsing.
+ ///
+ /// If not, the created [DateTime] objects are in the local time zone.
+ final bool isUtc;
+
+ final _blocks = _ParsedFormatBlocks();
+
+ /// Creates a new [FixedDateTimeFormatter] with the provided [pattern].
+ ///
+ /// The [pattern] interprets the characters mentioned in
+ /// [FixedDateTimeFormatter] to represent fields of a `DateTime` value. Other
+ /// characters are not special. If [isUtc] is set to false, the DateTime is
+ /// constructed with respect to the local timezone.
+ ///
+ /// There must at most be one sequence of each special character to ensure a
+ /// single source of truth when constructing the [DateTime], so a pattern of
+ /// `"CCCC-MM-DD, CC"` is invalid, because it has two separate `C` sequences.
+ FixedDateTimeFormatter(this.pattern, {this.isUtc = true}) {
+ int? currentCharacter;
+ var start = 0;
+ for (var i = 0; i < pattern.length; i++) {
+ var formatCharacter = pattern.codeUnitAt(i);
+ if (currentCharacter != formatCharacter) {
+ _blocks.saveBlock(currentCharacter, start, i);
+ if (_validFormatCharacters.contains(formatCharacter)) {
+ var hasSeenBefore = _blocks.formatCharacters.indexOf(formatCharacter);
+ if (hasSeenBefore > -1) {
+ throw FormatException(
+ "Pattern contains more than one '$formatCharacter' block.\n"
+ 'Previous occurrence at index ${_blocks.starts[hasSeenBefore]}',
+ pattern,
+ i);
+ } else {
+ start = i;
+ currentCharacter = formatCharacter;
+ }
+ } else {
+ currentCharacter = null;
+ }
+ }
+ }
+ _blocks.saveBlock(currentCharacter, start, pattern.length);
+ }
+
+ /// Converts a [DateTime] to a [String] as specified by the [pattern].
+ ///
+ /// The [DateTime.year] must not be negative.
+ String encode(DateTime dateTime) {
+ if (dateTime.year < 0) {
+ throw ArgumentError.value(
+ dateTime,
+ 'dateTime',
+ 'Year must not be negative',
+ );
+ }
+ var buffer = StringBuffer();
+ for (var i = 0; i < _blocks.length; i++) {
+ var start = _blocks.starts[i];
+ var end = _blocks.ends[i];
+ var length = end - start;
+
+ var previousEnd = i > 0 ? _blocks.ends[i - 1] : 0;
+ if (previousEnd < start) {
+ buffer.write(pattern.substring(previousEnd, start));
+ }
+ var formatCharacter = _blocks.formatCharacters[i];
+ var number = _extractNumberFromDateTime(
+ formatCharacter,
+ dateTime,
+ length,
+ );
+ if (number.length > length) {
+ number = number.substring(number.length - length);
+ } else if (length > number.length) {
+ number = number.padLeft(length, '0');
+ }
+ buffer.write(number);
+ }
+ if (_blocks.length > 0) {
+ var lastEnd = _blocks.ends.last;
+ if (lastEnd < pattern.length) {
+ buffer.write(pattern.substring(lastEnd, pattern.length));
+ }
+ }
+ return buffer.toString();
+ }
+
+ String _extractNumberFromDateTime(
+ int? formatCharacter,
+ DateTime dateTime,
+ int length,
+ ) {
+ int value;
+ switch (formatCharacter) {
+ case _yearCode:
+ value = dateTime.year;
+ break;
+ case _centuryCode:
+ value = dateTime.year ~/ 100;
+ break;
+ case _decadeCode:
+ value = dateTime.year ~/ 10;
+ break;
+ case _monthCode:
+ value = dateTime.month;
+ break;
+ case _dayCode:
+ value = dateTime.day;
+ break;
+ case _hourCode:
+ value = dateTime.hour;
+ break;
+ case _minuteCode:
+ value = dateTime.minute;
+ break;
+ case _secondCode:
+ value = dateTime.second;
+ break;
+ case _fractionSecondCode:
+ value = dateTime.millisecond;
+ switch (length) {
+ case 1:
+ value ~/= 100;
+ break;
+ case 2:
+ value ~/= 10;
+ break;
+ case 3:
+ break;
+ case 4:
+ value = value * 10 + dateTime.microsecond ~/ 100;
+ break;
+ case 5:
+ value = value * 100 + dateTime.microsecond ~/ 10;
+ break;
+ case 6:
+ value = value * 1000 + dateTime.microsecond;
+ break;
+ default:
+ throw AssertionError(
+ 'Unreachable, length is restricted to 6 in the constructor');
+ }
+ break;
+ default:
+ throw AssertionError(
+ 'Unreachable, the key is checked in the constructor');
+ }
+ return value.toString().padLeft(length, '0');
+ }
+
+ /// Parses [formattedDateTime] to a [DateTime] as specified by the [pattern].
+ ///
+ /// Parts of a [DateTime] which are not mentioned in the pattern default to a
+ /// value of zero for time parts and year, and a value of 1 for day and month.
+ ///
+ /// Throws a [FormatException] if the [formattedDateTime] does not match the
+ /// [pattern].
+ DateTime decode(String formattedDateTime) =>
+ _decode(formattedDateTime, isUtc, true)!;
+
+ /// Parses [formattedDateTime] to a [DateTime] as specified by the [pattern].
+ ///
+ /// Parts of a [DateTime] which are not mentioned in the pattern default to a
+ /// value of zero for time parts and year, and a value of 1 for day and month.
+ ///
+ /// Returns the parsed value, or `null` if the [formattedDateTime] does not
+ /// match the [pattern].
+ DateTime? tryDecode(String formattedDateTime) =>
+ _decode(formattedDateTime, isUtc, false);
+
+ DateTime? _decode(
+ String formattedDateTime,
+ bool isUtc,
+ bool throwOnError,
+ ) {
+ var year = 0;
+ var month = 1;
+ var day = 1;
+ var hour = 0;
+ var minute = 0;
+ var second = 0;
+ var microsecond = 0;
+ for (var i = 0; i < _blocks.length; i++) {
+ var formatCharacter = _blocks.formatCharacters[i];
+ var number = _extractNumberFromString(formattedDateTime, i, throwOnError);
+ if (number != null) {
+ if (formatCharacter == _fractionSecondCode) {
+ // Special case, as we want fractional seconds to be the leading
+ // digits.
+ number *= _powersOfTen[6 - (_blocks.ends[i] - _blocks.starts[i])];
+ }
+ switch (formatCharacter) {
+ case _yearCode:
+ year += number;
+ break;
+ case _centuryCode:
+ year += number * 100;
+ break;
+ case _decadeCode:
+ year += number * 10;
+ break;
+ case _monthCode:
+ month = number;
+ break;
+ case _dayCode:
+ day = number;
+ break;
+ case _hourCode:
+ hour = number;
+ break;
+ case _minuteCode:
+ minute = number;
+ break;
+ case _secondCode:
+ second = number;
+ break;
+ case _fractionSecondCode:
+ microsecond = number;
+ break;
+ }
+ } else {
+ return null;
+ }
+ }
+ if (isUtc) {
+ return DateTime.utc(
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ 0,
+ microsecond,
+ );
+ } else {
+ return DateTime(
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ 0,
+ microsecond,
+ );
+ }
+ }
+
+ int? _extractNumberFromString(
+ String formattedDateTime,
+ int index,
+ bool throwOnError,
+ ) {
+ var parsed = tryParse(
+ formattedDateTime,
+ _blocks.starts[index],
+ _blocks.ends[index],
+ );
+ if (parsed == null && throwOnError) {
+ throw FormatException(
+ 'Expected digits at ${formattedDateTime.substring(
+ _blocks.starts[index],
+ _blocks.ends[index],
+ )}',
+ formattedDateTime,
+ _blocks.starts[index],
+ );
+ }
+ return parsed;
+ }
+
+ int? tryParse(String formattedDateTime, int start, int end) {
+ var result = 0;
+ for (var i = start; i < end; i++) {
+ var digit = formattedDateTime.codeUnitAt(i) ^ 0x30;
+ if (digit <= 9) {
+ result = result * 10 + digit;
+ } else {
+ return null;
+ }
+ }
+ return result;
+ }
+}
+
+class _ParsedFormatBlocks {
+ final formatCharacters = <int>[];
+ final starts = <int>[];
+ final ends = <int>[];
+
+ _ParsedFormatBlocks();
+
+ int get length => formatCharacters.length;
+
+ void saveBlock(int? char, int start, int end) {
+ if (char != null) {
+ if (char == FixedDateTimeFormatter._fractionSecondCode &&
+ end - start > 6) {
+ throw FormatException(
+ 'Fractional seconds can only be specified up to microseconds',
+ char,
+ start,
+ );
+ } else if (end - start > 9) {
+ throw FormatException(
+ 'Length of a format char block cannot be larger than 9',
+ char,
+ start,
+ );
+ }
+ formatCharacters.add(char);
+ starts.add(start);
+ ends.add(end);
+ }
+ }
+}
diff --git a/pkgs/convert/lib/src/hex.dart b/pkgs/convert/lib/src/hex.dart
new file mode 100644
index 0000000..f383240
--- /dev/null
+++ b/pkgs/convert/lib/src/hex.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
+import 'hex/decoder.dart';
+import 'hex/encoder.dart';
+
+export 'hex/decoder.dart' hide hexDecoder;
+export 'hex/encoder.dart' hide hexEncoder;
+
+/// The canonical instance of [HexCodec].
+const hex = HexCodec._();
+
+/// A codec that converts byte arrays to and from hexadecimal strings, following
+/// [the Base16 spec](https://tools.ietf.org/html/rfc4648#section-8).
+///
+/// This should be used via the [hex] field.
+class HexCodec extends Codec<List<int>, String> {
+ @override
+ HexEncoder get encoder => hexEncoder;
+ @override
+ HexDecoder get decoder => hexDecoder;
+
+ const HexCodec._();
+}
diff --git a/pkgs/convert/lib/src/hex/decoder.dart b/pkgs/convert/lib/src/hex/decoder.dart
new file mode 100644
index 0000000..3696d4d
--- /dev/null
+++ b/pkgs/convert/lib/src/hex/decoder.dart
@@ -0,0 +1,181 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:typed_data';
+
+import '../utils.dart';
+
+/// The canonical instance of [HexDecoder].
+const hexDecoder = HexDecoder._();
+
+/// A converter that decodes hexadecimal strings into byte arrays.
+///
+/// Because two hexadecimal digits correspond to a single byte, this will throw
+/// a [FormatException] if given an odd-length string. It will also throw a
+/// [FormatException] if given a string containing non-hexadecimal code units.
+class HexDecoder extends Converter<String, List<int>> {
+ const HexDecoder._();
+
+ @override
+ Uint8List convert(String input) {
+ if (!input.length.isEven) {
+ throw FormatException(
+ 'Invalid input length, must be even.',
+ input,
+ input.length,
+ );
+ }
+
+ var bytes = Uint8List(input.length ~/ 2);
+ _decode(input.codeUnits, 0, input.length, bytes, 0);
+ return bytes;
+ }
+
+ @override
+ StringConversionSink startChunkedConversion(Sink<List<int>> sink) =>
+ _HexDecoderSink(sink);
+}
+
+/// A conversion sink for chunked hexadecimal decoding.
+class _HexDecoderSink extends StringConversionSinkBase {
+ /// The underlying sink to which decoded byte arrays will be passed.
+ final Sink<List<int>> _sink;
+
+ /// The trailing digit from the previous string.
+ ///
+ /// This will be non-`null` if the most recent string had an odd number of
+ /// hexadecimal digits. Since it's the most significant digit, it's always a
+ /// multiple of 16.
+ int? _lastDigit;
+
+ _HexDecoderSink(this._sink);
+
+ @override
+ void addSlice(String string, int start, int end, bool isLast) {
+ RangeError.checkValidRange(start, end, string.length);
+
+ if (start == end) {
+ if (isLast) _close(string, end);
+ return;
+ }
+
+ var codeUnits = string.codeUnits;
+ Uint8List bytes;
+ int bytesStart;
+ if (_lastDigit == null) {
+ bytes = Uint8List((end - start) ~/ 2);
+ bytesStart = 0;
+ } else {
+ var hexPairs = (end - start - 1) ~/ 2;
+ bytes = Uint8List(1 + hexPairs);
+ bytes[0] = _lastDigit! + digitForCodeUnit(codeUnits, start);
+ start++;
+ bytesStart = 1;
+ }
+
+ _lastDigit = _decode(codeUnits, start, end, bytes, bytesStart);
+
+ _sink.add(bytes);
+ if (isLast) _close(string, end);
+ }
+
+ @override
+ ByteConversionSink asUtf8Sink(bool allowMalformed) =>
+ _HexDecoderByteSink(_sink);
+
+ @override
+ void close() => _close();
+
+ /// Like [close], but includes [string] and [index] in the [FormatException]
+ /// if one is thrown.
+ void _close([String? string, int? index]) {
+ if (_lastDigit != null) {
+ throw FormatException(
+ 'Input ended with incomplete encoded byte.', string, index);
+ }
+
+ _sink.close();
+ }
+}
+
+/// A conversion sink for chunked hexadecimal decoding from UTF-8 bytes.
+class _HexDecoderByteSink extends ByteConversionSinkBase {
+ /// The underlying sink to which decoded byte arrays will be passed.
+ final Sink<List<int>> _sink;
+
+ /// The trailing digit from the previous string.
+ ///
+ /// This will be non-`null` if the most recent string had an odd number of
+ /// hexadecimal digits. Since it's the most significant digit, it's always a
+ /// multiple of 16.
+ int? _lastDigit;
+
+ _HexDecoderByteSink(this._sink);
+
+ @override
+ void add(List<int> chunk) => addSlice(chunk, 0, chunk.length, false);
+
+ @override
+ void addSlice(List<int> chunk, int start, int end, bool isLast) {
+ RangeError.checkValidRange(start, end, chunk.length);
+
+ if (start == end) {
+ if (isLast) _close(chunk, end);
+ return;
+ }
+
+ Uint8List bytes;
+ int bytesStart;
+ if (_lastDigit == null) {
+ bytes = Uint8List((end - start) ~/ 2);
+ bytesStart = 0;
+ } else {
+ var hexPairs = (end - start - 1) ~/ 2;
+ bytes = Uint8List(1 + hexPairs);
+ bytes[0] = _lastDigit! + digitForCodeUnit(chunk, start);
+ start++;
+ bytesStart = 1;
+ }
+
+ _lastDigit = _decode(chunk, start, end, bytes, bytesStart);
+
+ _sink.add(bytes);
+ if (isLast) _close(chunk, end);
+ }
+
+ @override
+ void close() => _close();
+
+ /// Like [close], but includes [chunk] and [index] in the [FormatException]
+ /// if one is thrown.
+ void _close([List<int>? chunk, int? index]) {
+ if (_lastDigit != null) {
+ throw FormatException(
+ 'Input ended with incomplete encoded byte.', chunk, index);
+ }
+
+ _sink.close();
+ }
+}
+
+/// Decodes [codeUnits] and writes the result into [destination].
+///
+/// This reads from [codeUnits] between [sourceStart] and [sourceEnd]. It writes
+/// the result into [destination] starting at [destinationStart].
+///
+/// If there's a leftover digit at the end of the decoding, this returns that
+/// digit. Otherwise it returns `null`.
+int? _decode(List<int> codeUnits, int sourceStart, int sourceEnd,
+ List<int> destination, int destinationStart) {
+ var destinationIndex = destinationStart;
+ for (var i = sourceStart; i < sourceEnd - 1; i += 2) {
+ var firstDigit = digitForCodeUnit(codeUnits, i);
+ var secondDigit = digitForCodeUnit(codeUnits, i + 1);
+ destination[destinationIndex++] = 16 * firstDigit + secondDigit;
+ }
+
+ if ((sourceEnd - sourceStart).isEven) return null;
+ return 16 * digitForCodeUnit(codeUnits, sourceEnd - 1);
+}
diff --git a/pkgs/convert/lib/src/hex/encoder.dart b/pkgs/convert/lib/src/hex/encoder.dart
new file mode 100644
index 0000000..36d6c22
--- /dev/null
+++ b/pkgs/convert/lib/src/hex/encoder.dart
@@ -0,0 +1,92 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:typed_data';
+
+import '../charcodes.dart';
+
+/// The canonical instance of [HexEncoder].
+const hexEncoder = HexEncoder._();
+
+/// A converter that encodes byte arrays into hexadecimal strings.
+///
+/// This will throw a [RangeError] if the byte array has any digits that don't
+/// fit in the gamut of a byte.
+class HexEncoder extends Converter<List<int>, String> {
+ const HexEncoder._();
+
+ @override
+ String convert(List<int> input) => _convert(input, 0, input.length);
+
+ @override
+ ByteConversionSink startChunkedConversion(Sink<String> sink) =>
+ _HexEncoderSink(sink);
+}
+
+/// A conversion sink for chunked hexadecimal encoding.
+class _HexEncoderSink extends ByteConversionSinkBase {
+ /// The underlying sink to which decoded byte arrays will be passed.
+ final Sink<String> _sink;
+
+ _HexEncoderSink(this._sink);
+
+ @override
+ void add(List<int> chunk) {
+ _sink.add(_convert(chunk, 0, chunk.length));
+ }
+
+ @override
+ void addSlice(List<int> chunk, int start, int end, bool isLast) {
+ RangeError.checkValidRange(start, end, chunk.length);
+ _sink.add(_convert(chunk, start, end));
+ if (isLast) _sink.close();
+ }
+
+ @override
+ void close() {
+ _sink.close();
+ }
+}
+
+String _convert(List<int> bytes, int start, int end) {
+ // A Uint8List is more efficient than a StringBuffer given that we know that
+ // we're only emitting ASCII-compatible characters, and that we know the
+ // length ahead of time.
+ var buffer = Uint8List((end - start) * 2);
+ var bufferIndex = 0;
+
+ // A bitwise OR of all bytes in [bytes]. This allows us to check for
+ // out-of-range bytes without adding more branches than necessary to the
+ // core loop.
+ var byteOr = 0;
+ for (var i = start; i < end; i++) {
+ var byte = bytes[i];
+ byteOr |= byte;
+
+ // The bitwise arithmetic here is equivalent to `byte ~/ 16` and `byte % 16`
+ // for valid byte values, but is easier for dart2js to optimize given that
+ // it can't prove that [byte] will always be positive.
+ buffer[bufferIndex++] = _codeUnitForDigit((byte & 0xF0) >> 4);
+ buffer[bufferIndex++] = _codeUnitForDigit(byte & 0x0F);
+ }
+
+ if (byteOr >= 0 && byteOr <= 255) return String.fromCharCodes(buffer);
+
+ // If there was an invalid byte, find it and throw an exception.
+ for (var i = start; i < end; i++) {
+ var byte = bytes[i];
+ if (byte >= 0 && byte <= 0xff) continue;
+ throw FormatException(
+ "Invalid byte ${byte < 0 ? "-" : ""}0x${byte.abs().toRadixString(16)}.",
+ bytes,
+ i);
+ }
+
+ throw StateError('unreachable');
+}
+
+/// Returns the ASCII/Unicode code unit corresponding to the hexadecimal digit
+/// [digit].
+int _codeUnitForDigit(int digit) => digit < 10 ? digit + $0 : digit + $a - 10;
diff --git a/pkgs/convert/lib/src/identity_codec.dart b/pkgs/convert/lib/src/identity_codec.dart
new file mode 100644
index 0000000..0a2a0b5
--- /dev/null
+++ b/pkgs/convert/lib/src/identity_codec.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
+class _IdentityConverter<T> extends Converter<T, T> {
+ _IdentityConverter();
+ @override
+ T convert(T input) => input;
+}
+
+/// A [Codec] that performs the identity conversion (changing nothing) in both
+/// directions.
+///
+/// The identity codec passes input directly to output in both directions.
+/// This class can be used as a base when combining multiple codecs,
+/// because fusing the identity codec with any other codec gives the other
+/// codec back.
+///
+/// Note, that when fused with another [Codec] the identity codec disppears.
+class IdentityCodec<T> extends Codec<T, T> {
+ const IdentityCodec();
+
+ @override
+ Converter<T, T> get decoder => _IdentityConverter<T>();
+ @override
+ Converter<T, T> get encoder => _IdentityConverter<T>();
+
+ /// Fuse with an other codec.
+ ///
+ /// Fusing with the identify converter is a no-op, so this always return
+ /// [other].
+ @override
+ Codec<T, R> fuse<R>(Codec<T, R> other) => other;
+}
diff --git a/pkgs/convert/lib/src/percent.dart b/pkgs/convert/lib/src/percent.dart
new file mode 100644
index 0000000..ecf2867
--- /dev/null
+++ b/pkgs/convert/lib/src/percent.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
+import 'percent/decoder.dart';
+import 'percent/encoder.dart';
+
+export 'percent/decoder.dart' hide percentDecoder;
+export 'percent/encoder.dart' hide percentEncoder;
+
+/// The canonical instance of [PercentCodec].
+const percent = PercentCodec._();
+
+// TODO(nweiz): Add flags to support generating and interpreting "+" as a space
+// character. Also add an option for custom sets of unreserved characters.
+/// A codec that converts byte arrays to and from percent-encoded (also known as
+/// URL-encoded) strings according to
+/// [RFC 3986](https://tools.ietf.org/html/rfc3986#section-2.1).
+///
+/// [encoder] encodes all bytes other than ASCII letters, decimal digits, or one
+/// of `-._~`. This matches the behavior of [Uri.encodeQueryComponent] except
+/// that it doesn't encode `0x20` bytes to the `+` character.
+///
+/// To be maximally flexible, [decoder] will decode any percent-encoded byte and
+/// will allow any non-percent-encoded byte other than `%`. By default, it
+/// interprets `+` as `0x2B` rather than `0x20` as emitted by
+/// [Uri.encodeQueryComponent].
+class PercentCodec extends Codec<List<int>, String> {
+ @override
+ PercentEncoder get encoder => percentEncoder;
+ @override
+ PercentDecoder get decoder => percentDecoder;
+
+ const PercentCodec._();
+}
diff --git a/pkgs/convert/lib/src/percent/decoder.dart b/pkgs/convert/lib/src/percent/decoder.dart
new file mode 100644
index 0000000..ef9c8a8
--- /dev/null
+++ b/pkgs/convert/lib/src/percent/decoder.dart
@@ -0,0 +1,250 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
+import 'package:typed_data/typed_data.dart';
+
+import '../charcodes.dart';
+import '../utils.dart';
+
+/// The canonical instance of [PercentDecoder].
+const percentDecoder = PercentDecoder._();
+
+const _lastPercent = -1;
+
+/// A converter that decodes percent-encoded strings into byte arrays.
+///
+/// To be maximally flexible, this will decode any percent-encoded byte and
+/// will allow any non-percent-encoded byte other than `%`. By default, it
+/// interprets `+` as `0x2B` rather than `0x20` as emitted by
+/// [Uri.encodeQueryComponent].
+///
+/// This will throw a [FormatException] if the input string has an incomplete
+/// percent-encoding, or if it contains non-ASCII code units.
+class PercentDecoder extends Converter<String, List<int>> {
+ const PercentDecoder._();
+
+ @override
+ List<int> convert(String input) {
+ var buffer = Uint8Buffer();
+ var lastDigit = _decode(input.codeUnits, 0, input.length, buffer);
+
+ if (lastDigit != null) {
+ throw FormatException(
+ 'Input ended with incomplete encoded byte.', input, input.length);
+ }
+
+ return buffer.buffer.asUint8List(0, buffer.length);
+ }
+
+ @override
+ StringConversionSink startChunkedConversion(Sink<List<int>> sink) =>
+ _PercentDecoderSink(sink);
+}
+
+/// A conversion sink for chunked percent-encoded decoding.
+class _PercentDecoderSink extends StringConversionSinkBase {
+ /// The underlying sink to which decoded byte arrays will be passed.
+ final Sink<List<int>> _sink;
+
+ /// The trailing digit from the previous string.
+ ///
+ /// This is `null` if the previous string ended with a complete
+ /// percent-encoded byte or a literal character. It's [_lastPercent] if the
+ /// most recent string ended with `%`. Otherwise, the most recent string ended
+ /// with a `%` followed by a hexadecimal digit, and this is that digit. Since
+ /// it's the most significant digit, it's always a multiple of 16.
+ int? _lastDigit;
+
+ _PercentDecoderSink(this._sink);
+
+ @override
+ void addSlice(String string, int start, int end, bool isLast) {
+ RangeError.checkValidRange(start, end, string.length);
+
+ if (start == end) {
+ if (isLast) _close(string, end);
+ return;
+ }
+
+ var buffer = Uint8Buffer();
+ var codeUnits = string.codeUnits;
+ if (_lastDigit == _lastPercent) {
+ _lastDigit = 16 * digitForCodeUnit(codeUnits, start);
+ start++;
+
+ if (start == end) {
+ if (isLast) _close(string, end);
+ return;
+ }
+ }
+
+ if (_lastDigit != null) {
+ buffer.add(_lastDigit! + digitForCodeUnit(codeUnits, start));
+ start++;
+ }
+
+ _lastDigit = _decode(codeUnits, start, end, buffer);
+
+ _sink.add(buffer.buffer.asUint8List(0, buffer.length));
+ if (isLast) _close(string, end);
+ }
+
+ @override
+ ByteConversionSink asUtf8Sink(bool allowMalformed) =>
+ _PercentDecoderByteSink(_sink);
+
+ @override
+ void close() => _close();
+
+ /// Like [close], but includes [string] and [index] in the [FormatException]
+ /// if one is thrown.
+ void _close([String? string, int? index]) {
+ if (_lastDigit != null) {
+ throw FormatException(
+ 'Input ended with incomplete encoded byte.', string, index);
+ }
+
+ _sink.close();
+ }
+}
+
+/// A conversion sink for chunked percent-encoded decoding from UTF-8 bytes.
+class _PercentDecoderByteSink extends ByteConversionSinkBase {
+ /// The underlying sink to which decoded byte arrays will be passed.
+ final Sink<List<int>> _sink;
+
+ /// The trailing digit from the previous string.
+ ///
+ /// This is `null` if the previous string ended with a complete
+ /// percent-encoded byte or a literal character. It's [_lastPercent] if the
+ /// most recent string ended with `%`. Otherwise, the most recent string ended
+ /// with a `%` followed by a hexadecimal digit, and this is that digit. Since
+ /// it's the most significant digit, it's always a multiple of 16.
+ int? _lastDigit;
+
+ _PercentDecoderByteSink(this._sink);
+
+ @override
+ void add(List<int> chunk) => addSlice(chunk, 0, chunk.length, false);
+
+ @override
+ void addSlice(List<int> chunk, int start, int end, bool isLast) {
+ RangeError.checkValidRange(start, end, chunk.length);
+
+ if (start == end) {
+ if (isLast) _close(chunk, end);
+ return;
+ }
+
+ var buffer = Uint8Buffer();
+ if (_lastDigit == _lastPercent) {
+ _lastDigit = 16 * digitForCodeUnit(chunk, start);
+ start++;
+
+ if (start == end) {
+ if (isLast) _close(chunk, end);
+ return;
+ }
+ }
+
+ if (_lastDigit != null) {
+ buffer.add(_lastDigit! + digitForCodeUnit(chunk, start));
+ start++;
+ }
+
+ _lastDigit = _decode(chunk, start, end, buffer);
+
+ _sink.add(buffer.buffer.asUint8List(0, buffer.length));
+ if (isLast) _close(chunk, end);
+ }
+
+ @override
+ void close() => _close();
+
+ /// Like [close], but includes [chunk] and [index] in the [FormatException]
+ /// if one is thrown.
+ void _close([List<int>? chunk, int? index]) {
+ if (_lastDigit != null) {
+ throw FormatException(
+ 'Input ended with incomplete encoded byte.', chunk, index);
+ }
+
+ _sink.close();
+ }
+}
+
+/// Decodes [codeUnits] and writes the result into [buffer].
+///
+/// This reads from [codeUnits] between [start] and [end]. It writes
+/// the result into [buffer] starting at [end].
+///
+/// If there's a leftover digit at the end of the decoding, this returns that
+/// digit. Otherwise it returns `null`.
+int? _decode(List<int> codeUnits, int start, int end, Uint8Buffer buffer) {
+ // A bitwise OR of all code units in [codeUnits]. This allows us to check for
+ // out-of-range code units without adding more branches than necessary to the
+ // core loop.
+ var codeUnitOr = 0;
+
+ // The beginning of the current slice of adjacent non-% characters. We can add
+ // all of these to the buffer at once.
+ var sliceStart = start;
+ for (var i = start; i < end; i++) {
+ // First, loop through non-% characters.
+ var codeUnit = codeUnits[i];
+ if (codeUnits[i] != $percent) {
+ codeUnitOr |= codeUnit;
+ continue;
+ }
+
+ // We found a %. The slice from `sliceStart` to `i` represents characters
+ // than can be copied to the buffer as-is.
+ if (i > sliceStart) {
+ _checkForInvalidCodeUnit(codeUnitOr, codeUnits, sliceStart, i);
+ buffer.addAll(codeUnits, sliceStart, i);
+ }
+
+ // Now decode the percent-encoded byte and add it as well.
+ i++;
+ if (i >= end) return _lastPercent;
+
+ var firstDigit = digitForCodeUnit(codeUnits, i);
+ i++;
+ if (i >= end) return 16 * firstDigit;
+
+ var secondDigit = digitForCodeUnit(codeUnits, i);
+ buffer.add(16 * firstDigit + secondDigit);
+
+ // The next iteration will look for non-% characters again.
+ sliceStart = i + 1;
+ }
+
+ if (end > sliceStart) {
+ _checkForInvalidCodeUnit(codeUnitOr, codeUnits, sliceStart, end);
+ if (start == sliceStart) {
+ buffer.addAll(codeUnits);
+ } else {
+ buffer.addAll(codeUnits, sliceStart, end);
+ }
+ }
+
+ return null;
+}
+
+void _checkForInvalidCodeUnit(
+ int codeUnitOr, List<int> codeUnits, int start, int end) {
+ if (codeUnitOr >= 0 && codeUnitOr <= 0x7f) return;
+
+ for (var i = start; i < end; i++) {
+ var codeUnit = codeUnits[i];
+ if (codeUnit >= 0 && codeUnit <= 0x7f) continue;
+ throw FormatException(
+ 'Non-ASCII code unit '
+ "U+${codeUnit.toRadixString(16).padLeft(4, '0')}",
+ codeUnits,
+ i);
+ }
+}
diff --git a/pkgs/convert/lib/src/percent/encoder.dart b/pkgs/convert/lib/src/percent/encoder.dart
new file mode 100644
index 0000000..b087b7a
--- /dev/null
+++ b/pkgs/convert/lib/src/percent/encoder.dart
@@ -0,0 +1,109 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
+import '../charcodes.dart';
+
+/// The canonical instance of [PercentEncoder].
+const percentEncoder = PercentEncoder._();
+
+/// A converter that encodes byte arrays into percent-encoded strings.
+///
+/// Encodes all bytes other than ASCII letters, decimal digits, or one
+/// of `-._~`. This matches the behavior of [Uri.encodeQueryComponent] except
+/// that it doesn't encode `0x20` bytes to the `+` character.
+///
+/// This will throw a [RangeError] if the byte array has any digits that don't
+/// fit in the gamut of a byte.
+class PercentEncoder extends Converter<List<int>, String> {
+ const PercentEncoder._();
+
+ @override
+ String convert(List<int> input) => _convert(input, 0, input.length);
+
+ @override
+ ByteConversionSink startChunkedConversion(Sink<String> sink) =>
+ _PercentEncoderSink(sink);
+}
+
+/// A conversion sink for chunked percentadecimal encoding.
+class _PercentEncoderSink extends ByteConversionSinkBase {
+ /// The underlying sink to which decoded byte arrays will be passed.
+ final Sink<String> _sink;
+
+ _PercentEncoderSink(this._sink);
+
+ @override
+ void add(List<int> chunk) {
+ _sink.add(_convert(chunk, 0, chunk.length));
+ }
+
+ @override
+ void addSlice(List<int> chunk, int start, int end, bool isLast) {
+ RangeError.checkValidRange(start, end, chunk.length);
+ _sink.add(_convert(chunk, start, end));
+ if (isLast) _sink.close();
+ }
+
+ @override
+ void close() {
+ _sink.close();
+ }
+}
+
+String _convert(List<int> bytes, int start, int end) {
+ var buffer = StringBuffer();
+
+ // A bitwise OR of all bytes in [bytes]. This allows us to check for
+ // out-of-range bytes without adding more branches than necessary to the
+ // core loop.
+ var byteOr = 0;
+ for (var i = start; i < end; i++) {
+ var byte = bytes[i];
+ byteOr |= byte;
+
+ // If the byte is an uppercase letter, convert it to lowercase to check if
+ // it's unreserved. This works because uppercase letters in ASCII are
+ // exactly `0b100000 = 0x20` less than lowercase letters, so if we ensure
+ // that that bit is 1 we ensure that the letter is lowercase.
+ var letter = 0x20 | byte;
+ if ((letter >= $a && letter <= $z) ||
+ (byte >= $0 && byte <= $9) ||
+ byte == $dash ||
+ byte == $dot ||
+ byte == $underscore ||
+ byte == $tilde) {
+ // Unreserved characters are safe to write as-is.
+ buffer.writeCharCode(byte);
+ continue;
+ }
+
+ buffer.writeCharCode($percent);
+
+ // The bitwise arithmetic here is equivalent to `byte ~/ 16` and `byte % 16`
+ // for valid byte values, but is easier for dart2js to optimize given that
+ // it can't prove that [byte] will always be positive.
+ buffer.writeCharCode(_codeUnitForDigit((byte & 0xF0) >> 4));
+ buffer.writeCharCode(_codeUnitForDigit(byte & 0x0F));
+ }
+
+ if (byteOr >= 0 && byteOr <= 255) return buffer.toString();
+
+ // If there was an invalid byte, find it and throw an exception.
+ for (var i = start; i < end; i++) {
+ var byte = bytes[i];
+ if (byte >= 0 && byte <= 0xff) continue;
+ throw FormatException(
+ "Invalid byte ${byte < 0 ? "-" : ""}0x${byte.abs().toRadixString(16)}.",
+ bytes,
+ i);
+ }
+
+ throw StateError('unreachable');
+}
+
+/// Returns the ASCII/Unicode code unit corresponding to the hexadecimal digit
+/// [digit].
+int _codeUnitForDigit(int digit) => digit < 10 ? digit + $0 : digit + $A - 10;
diff --git a/pkgs/convert/lib/src/string_accumulator_sink.dart b/pkgs/convert/lib/src/string_accumulator_sink.dart
new file mode 100644
index 0000000..9b07593
--- /dev/null
+++ b/pkgs/convert/lib/src/string_accumulator_sink.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
+/// A sink that provides access to the concatenated strings passed to it.
+///
+/// See also [StringConversionSink.withCallback].
+class StringAccumulatorSink extends StringConversionSinkBase {
+ /// The string accumulated so far.
+ String get string => _buffer.toString();
+ final _buffer = StringBuffer();
+
+ /// Whether [close] has been called.
+ bool get isClosed => _isClosed;
+ var _isClosed = false;
+
+ /// Empties [string].
+ ///
+ /// This can be used to avoid double-processing data.
+ void clear() {
+ _buffer.clear();
+ }
+
+ @override
+ void add(String str) {
+ if (_isClosed) {
+ throw StateError("Can't add to a closed sink.");
+ }
+
+ _buffer.write(str);
+ }
+
+ @override
+ void addSlice(String chunk, int start, int end, bool isLast) {
+ if (_isClosed) {
+ throw StateError("Can't add to a closed sink.");
+ }
+
+ _buffer.write(chunk.substring(start, end));
+ if (isLast) _isClosed = true;
+ }
+
+ @override
+ void close() {
+ _isClosed = true;
+ }
+}
diff --git a/pkgs/convert/lib/src/utils.dart b/pkgs/convert/lib/src/utils.dart
new file mode 100644
index 0000000..cfcc127
--- /dev/null
+++ b/pkgs/convert/lib/src/utils.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'charcodes.dart';
+
+/// Returns the digit (0 through 15) corresponding to the hexadecimal code unit
+/// at index [index] in [codeUnits].
+///
+/// If the given code unit isn't valid hexadecimal, throws a [FormatException].
+int digitForCodeUnit(List<int> codeUnits, int index) {
+ // If the code unit is a numeral, get its value. XOR works because 0 in ASCII
+ // is `0b110000` and the other numerals come after it in ascending order and
+ // take up at most four bits.
+ //
+ // We check for digits first because it ensures there's only a single branch
+ // for 10 out of 16 of the expected cases. We don't count the `digit >= 0`
+ // check because branch prediction will always work on it for valid data.
+ var codeUnit = codeUnits[index];
+ var digit = $0 ^ codeUnit;
+ if (digit <= 9) {
+ if (digit >= 0) return digit;
+ } else {
+ // If the code unit is an uppercase letter, convert it to lowercase. This
+ // works because uppercase letters in ASCII are exactly `0b100000 = 0x20`
+ // less than lowercase letters, so if we ensure that that bit is 1 we ensure
+ // that the letter is lowercase.
+ var letter = 0x20 | codeUnit;
+ if ($a <= letter && letter <= $f) return letter - $a + 10;
+ }
+
+ throw FormatException(
+ 'Invalid hexadecimal code unit '
+ "U+${codeUnit.toRadixString(16).padLeft(4, '0')}.",
+ codeUnits,
+ index);
+}
diff --git a/pkgs/convert/pubspec.yaml b/pkgs/convert/pubspec.yaml
new file mode 100644
index 0000000..25c46ce
--- /dev/null
+++ b/pkgs/convert/pubspec.yaml
@@ -0,0 +1,18 @@
+name: convert
+version: 3.1.2
+description: >-
+ Utilities for converting between data representations.
+ Provides a number of Sink, Codec, Decoder, and Encoder types.
+repository: https://github.com/dart-lang/core/tree/main/pkgs/convert
+issue_tracker: https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aconvert
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ typed_data: ^1.3.0
+
+dev_dependencies:
+ benchmark_harness: ^2.2.0
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.17.0
diff --git a/pkgs/convert/test/accumulator_sink_test.dart b/pkgs/convert/test/accumulator_sink_test.dart
new file mode 100644
index 0000000..6842d1c
--- /dev/null
+++ b/pkgs/convert/test/accumulator_sink_test.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:convert/convert.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late AccumulatorSink<int> sink;
+ setUp(() {
+ sink = AccumulatorSink<int>();
+ });
+
+ test("provides access to events as they're added", () {
+ expect(sink.events, isEmpty);
+
+ sink.add(1);
+ expect(sink.events, equals([1]));
+
+ sink.add(2);
+ expect(sink.events, equals([1, 2]));
+
+ sink.add(3);
+ expect(sink.events, equals([1, 2, 3]));
+ });
+
+ test('clear() clears the events', () {
+ sink
+ ..add(1)
+ ..add(2)
+ ..add(3);
+ expect(sink.events, equals([1, 2, 3]));
+
+ sink.clear();
+ expect(sink.events, isEmpty);
+
+ sink
+ ..add(4)
+ ..add(5)
+ ..add(6);
+ expect(sink.events, equals([4, 5, 6]));
+ });
+
+ test('indicates whether the sink is closed', () {
+ expect(sink.isClosed, isFalse);
+ sink.close();
+ expect(sink.isClosed, isTrue);
+ });
+
+ test("doesn't allow add() to be called after close()", () {
+ sink.close();
+ expect(() => sink.add(1), throwsStateError);
+ });
+}
diff --git a/pkgs/convert/test/byte_accumulator_sink_test.dart b/pkgs/convert/test/byte_accumulator_sink_test.dart
new file mode 100644
index 0000000..8398ae2
--- /dev/null
+++ b/pkgs/convert/test/byte_accumulator_sink_test.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:convert/convert.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late ByteAccumulatorSink sink;
+ setUp(() {
+ sink = ByteAccumulatorSink();
+ });
+
+ test('provides access to the concatenated bytes', () {
+ expect(sink.bytes, isEmpty);
+
+ sink.add([1, 2, 3]);
+ expect(sink.bytes, equals([1, 2, 3]));
+
+ sink.addSlice([4, 5, 6, 7, 8], 1, 4, false);
+ expect(sink.bytes, equals([1, 2, 3, 5, 6, 7]));
+ });
+
+ test('clear() clears the bytes', () {
+ sink.add([1, 2, 3]);
+ expect(sink.bytes, equals([1, 2, 3]));
+
+ sink.clear();
+ expect(sink.bytes, isEmpty);
+
+ sink.add([4, 5, 6]);
+ expect(sink.bytes, equals([4, 5, 6]));
+ });
+
+ test('indicates whether the sink is closed', () {
+ expect(sink.isClosed, isFalse);
+ sink.close();
+ expect(sink.isClosed, isTrue);
+ });
+
+ test('indicates whether the sink is closed via addSlice', () {
+ expect(sink.isClosed, isFalse);
+ sink.addSlice([], 0, 0, true);
+ expect(sink.isClosed, isTrue);
+ });
+
+ test("doesn't allow add() to be called after close()", () {
+ sink.close();
+ expect(() => sink.add([1]), throwsStateError);
+ });
+
+ test("doesn't allow addSlice() to be called after close()", () {
+ sink.close();
+ expect(() => sink.addSlice([], 0, 0, false), throwsStateError);
+ });
+}
diff --git a/pkgs/convert/test/codepage_test.dart b/pkgs/convert/test/codepage_test.dart
new file mode 100644
index 0000000..cca75e7
--- /dev/null
+++ b/pkgs/convert/test/codepage_test.dart
@@ -0,0 +1,154 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:core';
+import 'dart:typed_data';
+
+import 'package:convert/convert.dart';
+import 'package:test/test.dart';
+
+void main() {
+ var bytes = Uint8List.fromList([for (var i = 0; i < 256; i++) i]);
+ for (var cp in [
+ latin2,
+ latin3,
+ latin4,
+ latin5,
+ latin6,
+ latin7,
+ latin8,
+ latin9,
+ latin10,
+ latinCyrillic,
+ latinGreek,
+ latinHebrew,
+ latinThai,
+ latinArabic
+ ]) {
+ group('${cp.name} codepage', () {
+ test('ascii compatible', () {
+ for (var byte = 0x20; byte < 0x7f; byte++) {
+ expect(cp[byte], byte);
+ }
+ });
+
+ test('bidirectional mapping', () {
+ // Maps both directions.
+ for (var byte = 0; byte < 256; byte++) {
+ var char = cp[byte];
+ if (char != 0xFFFD) {
+ var string = String.fromCharCode(char);
+ expect(cp.encode(string), [byte]);
+ expect(cp.decode([byte]), string);
+ }
+ }
+ });
+
+ test('decode invalid characters not allowed', () {
+ expect(() => cp.decode([0xfffd]), throwsA(isA<FormatException>()));
+ });
+
+ test('decode invalid characters allowed', () {
+ // Decode works like operator[].
+ expect(cp.decode(bytes, allowInvalid: true),
+ String.fromCharCodes([for (var i = 0; i < 256; i++) cp[i]]));
+ });
+
+ test('chunked conversion', () {
+ late final String decodedString;
+ final outputSink = StringConversionSink.withCallback(
+ (accumulated) => decodedString = accumulated);
+ final inputSink = cp.decoder.startChunkedConversion(outputSink);
+ final expected = StringBuffer();
+
+ for (var byte = 0; byte < 256; byte++) {
+ var char = cp[byte];
+ if (char != 0xFFFD) {
+ inputSink.add([byte]);
+ expected.writeCharCode(char);
+ }
+ }
+ inputSink.close();
+ expect(decodedString, expected.toString());
+ });
+ });
+ }
+ test('latin-2 roundtrip', () {
+ // Data from http://www.columbia.edu/kermit/latin2.html
+ var latin2text = '\xa0Ą˘Ł¤ĽŚ§¨ŠŞŤŹ\xadŽŻ°ą˛ł´ľśˇ¸šşťź˝žżŔÁÂĂÄĹĆÇČÉĘËĚÍÎĎĐŃŇ'
+ 'ÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ˙';
+ expect(latin2.decode(latin2.encode(latin2text)), latin2text);
+ });
+
+ test('latin-3 roundtrip', () {
+ // Data from http://www.columbia.edu/kermit/latin3.html
+ var latin2text = '\xa0Ħ˘£¤\u{FFFD}Ĥ§¨İŞĞĴ\xad\u{FFFD}ݰħ²³´µĥ·¸ışğĵ½'
+ '\u{FFFD}żÀÁÂ\u{FFFD}ÄĊĈÇÈÉÊËÌÍÎÏ\u{FFFD}ÑÒÓÔĠÖ×ĜÙÚÛÜŬŜßàáâ'
+ '\u{FFFD}äċĉçèéêëìíîï\u{FFFD}ñòóôġö÷ĝùúûüŭŝ˙';
+ var encoded = latin3.encode(latin2text, invalidCharacter: 0);
+ var decoded = latin3.decode(encoded, allowInvalid: true);
+ expect(decoded, latin2text);
+ });
+
+ group('Custom code page', () {
+ late final cp = CodePage('custom', "ABCDEF${"\uFFFD" * 250}");
+
+ test('simple encode', () {
+ var result = cp.encode('BADCAFE');
+ expect(result, [1, 0, 3, 2, 0, 5, 4]);
+ });
+
+ test('unencodable character', () {
+ expect(() => cp.encode('GAD'), throwsFormatException);
+ });
+
+ test('unencodable character with invalidCharacter', () {
+ expect(cp.encode('GAD', invalidCharacter: 0x3F), [0x3F, 0, 3]);
+ });
+
+ test('simple decode', () {
+ expect(cp.decode([1, 0, 3, 2, 0, 5, 4]), 'BADCAFE');
+ });
+
+ test('undecodable byte', () {
+ expect(() => cp.decode([6, 1, 255]), throwsFormatException);
+ });
+
+ test('undecodable byte with allowInvalid', () {
+ expect(cp.decode([6, 1, 255], allowInvalid: true), '\u{FFFD}B\u{FFFD}');
+ });
+
+ test('chunked conversion', () {
+ late final String decodedString;
+ final outputSink = StringConversionSink.withCallback(
+ (accumulated) => decodedString = accumulated);
+ final inputSink = cp.decoder.startChunkedConversion(outputSink);
+
+ inputSink
+ ..add([1])
+ ..add([0])
+ ..add([3])
+ ..close();
+ expect(decodedString, 'BAD');
+ });
+
+ test('chunked conversion - byte conversion sink', () {
+ late final String decodedString;
+ final outputSink = StringConversionSink.withCallback(
+ (accumulated) => decodedString = accumulated);
+ final bytes = [1, 0, 3, 2, 0, 5, 4];
+
+ final inputSink = cp.decoder.startChunkedConversion(outputSink);
+ expect(inputSink, isA<ByteConversionSink>());
+
+ (inputSink as ByteConversionSink)
+ ..addSlice(bytes, 1, 3, false)
+ ..addSlice(bytes, 4, 5, false)
+ ..addSlice(bytes, 6, 6, true);
+
+ expect(decodedString, 'ADA');
+ });
+ });
+}
diff --git a/pkgs/convert/test/fixed_datetime_formatter_test.dart b/pkgs/convert/test/fixed_datetime_formatter_test.dart
new file mode 100644
index 0000000..30e7ca9
--- /dev/null
+++ b/pkgs/convert/test/fixed_datetime_formatter_test.dart
@@ -0,0 +1,224 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:convert/src/fixed_datetime_formatter.dart';
+import 'package:test/test.dart';
+
+void main() {
+ var noFractionalSeconds = DateTime.utc(0);
+ var skipWeb = <String, Skip>{
+ 'js': const Skip(
+ 'Web does not support microseconds (see https://github.com/dart-lang/sdk/issues/44876)')
+ };
+ // Testing `decode`.
+ test('Parse only year', () {
+ var time = FixedDateTimeFormatter('YYYY').decode('1996');
+ expect(time, DateTime.utc(1996));
+ });
+ test('Escaped chars are ignored', () {
+ var time = FixedDateTimeFormatter('YYYY kiwi MM').decode('1996 rnad 01');
+ expect(time, DateTime.utc(1996));
+ });
+ test('Parse two years throws', () {
+ expect(() => FixedDateTimeFormatter('YYYY YYYY'), throwsException);
+ });
+ test('Parse year and century', () {
+ var time = FixedDateTimeFormatter('CCYY').decode('1996');
+ expect(time, DateTime.utc(1996));
+ });
+ test('Parse year, decade and century', () {
+ var time = FixedDateTimeFormatter('CCEY').decode('1996');
+ expect(time, DateTime.utc(1996));
+ });
+ test('Parse year, century, month', () {
+ var time = FixedDateTimeFormatter('CCYY MM').decode('1996 04');
+ expect(time, DateTime.utc(1996, 4));
+ });
+ test('Parse year, century, month, day', () {
+ var time = FixedDateTimeFormatter('CCYY MM-DD').decode('1996 04-25');
+ expect(time, DateTime.utc(1996, 4, 25));
+ });
+ test('Parse year, century, month, day, hour, minute, second', () {
+ var time = FixedDateTimeFormatter('CCYY MM-DD hh:mm:ss')
+ .decode('1996 04-25 05:03:22');
+ expect(time, DateTime.utc(1996, 4, 25, 5, 3, 22));
+ });
+ test('Parse YYYYMMDDhhmmssSSS', () {
+ var time =
+ FixedDateTimeFormatter('YYYYMMDDhhmmssSSS').decode('19960425050322533');
+ expect(time, DateTime.utc(1996, 4, 25, 5, 3, 22, 533));
+ });
+ test('Parse S 1/10 of a second', () {
+ var time = FixedDateTimeFormatter('S').decode('1');
+ expect(time, noFractionalSeconds.add(const Duration(milliseconds: 100)));
+ });
+ test('Parse SS 1/100 of a second', () {
+ var time = FixedDateTimeFormatter('SS').decode('01');
+ expect(time, noFractionalSeconds.add(const Duration(milliseconds: 10)));
+ });
+ test('Parse SSS a millisecond', () {
+ var time = FixedDateTimeFormatter('SSS').decode('001');
+ expect(time, noFractionalSeconds.add(const Duration(milliseconds: 1)));
+ });
+ test('Parse SSSSSS a microsecond', () {
+ var time = FixedDateTimeFormatter('SSSSSS').decode('000001');
+ expect(time, noFractionalSeconds.add(const Duration(microseconds: 1)));
+ }, onPlatform: skipWeb);
+ test('Parse SSSSSS a millisecond', () {
+ var time = FixedDateTimeFormatter('SSSSSS').decode('001000');
+ expect(time, noFractionalSeconds.add(const Duration(milliseconds: 1)));
+ });
+ test('Parse SSSSSS a millisecond and a microsecond', () {
+ var time = FixedDateTimeFormatter('SSSSSS').decode('001001');
+ expect(
+ time,
+ noFractionalSeconds.add(const Duration(
+ milliseconds: 1,
+ microseconds: 1,
+ )));
+ }, onPlatform: skipWeb);
+ test('Parse ssSSSSSS a second and a microsecond', () {
+ var time = FixedDateTimeFormatter('ssSSSSSS').decode('01000001');
+ expect(
+ time,
+ noFractionalSeconds.add(const Duration(
+ seconds: 1,
+ microseconds: 1,
+ )));
+ }, onPlatform: skipWeb);
+ test('7 S throws', () {
+ expect(
+ () => FixedDateTimeFormatter('S' * 7),
+ throwsFormatException,
+ );
+ });
+ test('10 Y throws', () {
+ expect(
+ () => FixedDateTimeFormatter('Y' * 10),
+ throwsFormatException,
+ );
+ });
+ test('Parse hex year throws', () {
+ expect(
+ () => FixedDateTimeFormatter('YYYY').decode('0xAB'),
+ throwsFormatException,
+ );
+ });
+ // Testing `tryDecode`.
+ test('Try parse year', () {
+ var time = FixedDateTimeFormatter('YYYY').tryDecode('1996');
+ expect(time, DateTime.utc(1996));
+ });
+ test('Try parse hex year returns null', () {
+ var time = FixedDateTimeFormatter('YYYY').tryDecode('0xAB');
+ expect(time, null);
+ });
+ test('Try parse invalid returns null', () {
+ var time = FixedDateTimeFormatter('YYYY').tryDecode('1x96');
+ expect(time, null);
+ });
+ // Testing `encode`.
+ test('Format simple', () {
+ var time = DateTime.utc(1996);
+ expect(FixedDateTimeFormatter('YYYY kiwi MM').encode(time), '1996 kiwi 01');
+ });
+ test('Format YYYYMMDDhhmmss', () {
+ var time = DateTime.utc(1996, 4, 25, 5, 3, 22);
+ expect(
+ FixedDateTimeFormatter('YYYYMMDDhhmmss').encode(time),
+ '19960425050322',
+ );
+ });
+ test('Format CCEY-MM', () {
+ var str = FixedDateTimeFormatter('CCEY-MM').encode(DateTime.utc(1996, 4));
+ expect(str, '1996-04');
+ });
+ test('Format XCCEY-MMX', () {
+ var str = FixedDateTimeFormatter('XCCEY-MMX').encode(DateTime.utc(1996, 4));
+ expect(str, 'X1996-04X');
+ });
+ test('Format S 1/10 of a second', () {
+ var str = FixedDateTimeFormatter('S')
+ .encode(noFractionalSeconds.add(const Duration(milliseconds: 100)));
+ expect(str, '1');
+ });
+ test('Format SS 1/100 of a second', () {
+ var str = FixedDateTimeFormatter('SS')
+ .encode(noFractionalSeconds.add(const Duration(milliseconds: 10)));
+ expect(str, '01');
+ });
+ test('Format SSS 1/100 of a second', () {
+ var str = FixedDateTimeFormatter('SSS')
+ .encode(noFractionalSeconds.add(const Duration(milliseconds: 10)));
+ expect(str, '010');
+ });
+ test('Format SSSS no fractions', () {
+ var str = FixedDateTimeFormatter('SSSS').encode(noFractionalSeconds);
+ expect(str, '0000');
+ });
+ test('Format SSSSSS no fractions', () {
+ var str = FixedDateTimeFormatter('SSSSSS').encode(noFractionalSeconds);
+ expect(str, '000000');
+ });
+ test('Format SSSS 1/10 of a second', () {
+ var str = FixedDateTimeFormatter('SSSS')
+ .encode(noFractionalSeconds.add(const Duration(milliseconds: 100)));
+ expect(str, '1000');
+ });
+ test('Format SSSS 1/100 of a second', () {
+ var str = FixedDateTimeFormatter('SSSS')
+ .encode(noFractionalSeconds.add(const Duration(milliseconds: 10)));
+ expect(str, '0100');
+ });
+ test('Format SSSS a millisecond', () {
+ var str = FixedDateTimeFormatter('SSSS')
+ .encode(noFractionalSeconds.add(const Duration(milliseconds: 1)));
+ expect(str, '0010');
+ });
+ test('Format SSSSSS a microsecond', () {
+ var str = FixedDateTimeFormatter('SSSSSS')
+ .encode(DateTime.utc(0, 1, 1, 0, 0, 0, 0, 1));
+ expect(str, '000001');
+ }, onPlatform: skipWeb);
+ test('Format SSSSSS a millisecond and a microsecond', () {
+ var dateTime = noFractionalSeconds.add(const Duration(
+ milliseconds: 1,
+ microseconds: 1,
+ ));
+ var str = FixedDateTimeFormatter('SSSSSS').encode(dateTime);
+ expect(str, '001001');
+ }, onPlatform: skipWeb);
+ test('Format SSSSSS0 a microsecond', () {
+ var str = FixedDateTimeFormatter('SSSSSS0')
+ .encode(noFractionalSeconds.add(const Duration(microseconds: 1)));
+ expect(str, '0000010');
+ }, onPlatform: skipWeb);
+ test('Format SSSSSS0 1/10 of a second', () {
+ var str = FixedDateTimeFormatter('SSSSSS0')
+ .encode(noFractionalSeconds.add(const Duration(milliseconds: 100)));
+ expect(str, '1000000');
+ });
+ test('Parse ssSSSSSS a second and a microsecond', () {
+ var dateTime = noFractionalSeconds.add(const Duration(
+ seconds: 1,
+ microseconds: 1,
+ ));
+ var str = FixedDateTimeFormatter('ssSSSSSS').encode(dateTime);
+ expect(str, '01000001');
+ }, onPlatform: skipWeb);
+ test('Parse ssSSSSSS0 a second and a microsecond', () {
+ var dateTime = noFractionalSeconds.add(const Duration(
+ seconds: 1,
+ microseconds: 1,
+ ));
+ var str = FixedDateTimeFormatter('ssSSSSSS0').encode(dateTime);
+ expect(str, '010000010');
+ }, onPlatform: skipWeb);
+ test('Parse negative year throws Error', () {
+ expect(
+ () => FixedDateTimeFormatter('YYYY').encode(DateTime(-1)),
+ throwsArgumentError,
+ );
+ });
+}
diff --git a/pkgs/convert/test/hex_test.dart b/pkgs/convert/test/hex_test.dart
new file mode 100644
index 0000000..abd940f
--- /dev/null
+++ b/pkgs/convert/test/hex_test.dart
@@ -0,0 +1,222 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:convert/convert.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('encoder', () {
+ test('converts byte arrays to hex', () {
+ expect(hex.encode([0x1a, 0xb2, 0x3c, 0xd4]), equals('1ab23cd4'));
+ expect(hex.encode([0x00, 0x01, 0xfe, 0xff]), equals('0001feff'));
+ });
+
+ group('with chunked conversion', () {
+ test('converts byte arrays to hex', () {
+ var results = <String>[];
+ var controller = StreamController<String>(sync: true);
+ controller.stream.listen(results.add);
+ var sink = hex.encoder.startChunkedConversion(controller.sink);
+
+ sink.add([0x1a, 0xb2, 0x3c, 0xd4]);
+ expect(results, equals(['1ab23cd4']));
+
+ sink.add([0x00, 0x01, 0xfe, 0xff]);
+ expect(results, equals(['1ab23cd4', '0001feff']));
+ });
+
+ test('handles empty and single-byte lists', () {
+ var results = <String>[];
+ var controller = StreamController<String>(sync: true);
+ controller.stream.listen(results.add);
+ var sink = hex.encoder.startChunkedConversion(controller.sink);
+
+ sink.add([]);
+ expect(results, equals(['']));
+
+ sink.add([0x00]);
+ expect(results, equals(['', '00']));
+
+ sink.add([]);
+ expect(results, equals(['', '00', '']));
+ });
+ });
+
+ test('rejects non-bytes', () {
+ expect(() => hex.encode([0x100]), throwsFormatException);
+
+ var sink =
+ hex.encoder.startChunkedConversion(StreamController(sync: true));
+ expect(() => sink.add([0x100]), throwsFormatException);
+ });
+ });
+
+ group('decoder', () {
+ test('converts hex to byte arrays', () {
+ expect(hex.decode('1ab23cd4'), equals([0x1a, 0xb2, 0x3c, 0xd4]));
+ expect(hex.decode('0001feff'), equals([0x00, 0x01, 0xfe, 0xff]));
+ });
+
+ test('supports uppercase letters', () {
+ expect(
+ hex.decode('0123456789ABCDEFabcdef'),
+ equals([
+ 0x01,
+ 0x23,
+ 0x45,
+ 0x67,
+ 0x89,
+ 0xab,
+ 0xcd,
+ 0xef,
+ 0xab,
+ 0xcd,
+ 0xef
+ ]));
+ });
+
+ group('with chunked conversion', () {
+ late List<List<int>> results;
+ late StringConversionSink sink;
+ setUp(() {
+ results = [];
+ var controller = StreamController<List<int>>(sync: true);
+ controller.stream.listen(results.add);
+ sink = hex.decoder.startChunkedConversion(controller.sink);
+ });
+
+ test('converts hex to byte arrays', () {
+ sink.add('1ab23cd4');
+ expect(
+ results,
+ equals([
+ [0x1a, 0xb2, 0x3c, 0xd4]
+ ]));
+
+ sink.add('0001feff');
+ expect(
+ results,
+ equals([
+ [0x1a, 0xb2, 0x3c, 0xd4],
+ [0x00, 0x01, 0xfe, 0xff]
+ ]));
+ });
+
+ test('supports trailing digits split across chunks', () {
+ sink.add('1ab23');
+ expect(
+ results,
+ equals([
+ [0x1a, 0xb2]
+ ]));
+
+ sink.add('cd');
+ expect(
+ results,
+ equals([
+ [0x1a, 0xb2],
+ [0x3c]
+ ]));
+
+ sink.add('40001');
+ expect(
+ results,
+ equals([
+ [0x1a, 0xb2],
+ [0x3c],
+ [0xd4, 0x00, 0x01]
+ ]));
+
+ sink.add('feff');
+ expect(
+ results,
+ equals([
+ [0x1a, 0xb2],
+ [0x3c],
+ [0xd4, 0x00, 0x01],
+ [0xfe, 0xff]
+ ]));
+ });
+
+ test('supports empty strings', () {
+ sink.add('');
+ expect(results, isEmpty);
+
+ sink.add('0');
+ expect(results, equals([<Never>[]]));
+
+ sink.add('');
+ expect(results, equals([<Never>[]]));
+
+ sink.add('0');
+ expect(
+ results,
+ equals([
+ <Never>[],
+ [0x00]
+ ]));
+
+ sink.add('');
+ expect(
+ results,
+ equals([
+ <Never>[],
+ [0x00]
+ ]));
+ });
+
+ test('rejects odd length detected in close()', () {
+ sink.add('1ab23');
+ expect(
+ results,
+ equals([
+ [0x1a, 0xb2]
+ ]));
+ expect(() => sink.close(), throwsFormatException);
+ });
+
+ test('rejects odd length detected in addSlice()', () {
+ sink.addSlice('1ab23cd', 0, 5, false);
+ expect(
+ results,
+ equals([
+ [0x1a, 0xb2]
+ ]));
+
+ expect(
+ () => sink.addSlice('1ab23cd', 5, 7, true), throwsFormatException);
+ });
+ });
+
+ group('rejects non-hex character', () {
+ for (var char in [
+ 'g',
+ 'G',
+ '/',
+ ':',
+ '@',
+ '`',
+ '\x00',
+ '\u0141',
+ '\u{10041}'
+ ]) {
+ test('"$char"', () {
+ expect(() => hex.decode('a$char'), throwsFormatException);
+ expect(() => hex.decode('${char}a'), throwsFormatException);
+
+ var sink =
+ hex.decoder.startChunkedConversion(StreamController(sync: true));
+ expect(() => sink.add(char), throwsFormatException);
+ });
+ }
+ });
+
+ test('rejects odd length detected in convert()', () {
+ expect(() => hex.decode('1ab23cd'), throwsFormatException);
+ });
+ });
+}
diff --git a/pkgs/convert/test/identity_codec_test.dart b/pkgs/convert/test/identity_codec_test.dart
new file mode 100644
index 0000000..1fb841f
--- /dev/null
+++ b/pkgs/convert/test/identity_codec_test.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'package:convert/convert.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('IdentityCodec', () {
+ test('encode', () {
+ const codec = IdentityCodec<String>();
+ expect(codec.encode('hello-world'), equals('hello-world'));
+ });
+
+ test('decode', () {
+ const codec = IdentityCodec<String>();
+ expect(codec.decode('hello-world'), equals('hello-world'));
+ });
+
+ test('fuse', () {
+ const stringCodec = IdentityCodec<String>();
+ final utf8Strings = stringCodec.fuse(utf8);
+ expect(utf8Strings, equals(utf8));
+ });
+ });
+}
diff --git a/pkgs/convert/test/percent_test.dart b/pkgs/convert/test/percent_test.dart
new file mode 100644
index 0000000..4e0f605
--- /dev/null
+++ b/pkgs/convert/test/percent_test.dart
@@ -0,0 +1,246 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:convert/convert.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('encoder', () {
+ test("doesn't percent-encode unreserved characters", () {
+ var safeChars = 'abcdefghijklmnopqrstuvwxyz'
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ '0123456789-._~';
+ expect(percent.encode([...safeChars.codeUnits]), equals(safeChars));
+ });
+
+ test('percent-encodes reserved ASCII characters', () {
+ expect(percent.encode([...' `{@[,/^}\x7f\x00%'.codeUnits]),
+ equals('%20%60%7B%40%5B%2C%2F%5E%7D%7F%00%25'));
+ });
+
+ test('percent-encodes non-ASCII characters', () {
+ expect(percent.encode([0x80, 0xFF]), equals('%80%FF'));
+ });
+
+ test('mixes encoded and unencoded characters', () {
+ expect(percent.encode([...'a+b=\x80'.codeUnits]), equals('a%2Bb%3D%80'));
+ });
+
+ group('with chunked conversion', () {
+ test('percent-encodes byte arrays', () {
+ var results = <String>[];
+ var controller = StreamController<String>(sync: true);
+ controller.stream.listen(results.add);
+ var sink = percent.encoder.startChunkedConversion(controller.sink);
+
+ sink.add([...'a+b=\x80'.codeUnits]);
+ expect(results, equals(['a%2Bb%3D%80']));
+
+ sink.add([0x00, 0x01, 0xfe, 0xff]);
+ expect(results, equals(['a%2Bb%3D%80', '%00%01%FE%FF']));
+ });
+
+ test('handles empty and single-byte lists', () {
+ var results = <String>[];
+ var controller = StreamController<String>(sync: true);
+ controller.stream.listen(results.add);
+ var sink = percent.encoder.startChunkedConversion(controller.sink);
+
+ sink.add([]);
+ expect(results, equals(['']));
+
+ sink.add([0x00]);
+ expect(results, equals(['', '%00']));
+
+ sink.add([]);
+ expect(results, equals(['', '%00', '']));
+ });
+ });
+
+ test('rejects non-bytes', () {
+ expect(() => percent.encode([0x100]), throwsFormatException);
+
+ var sink =
+ percent.encoder.startChunkedConversion(StreamController(sync: true));
+ expect(() => sink.add([0x100]), throwsFormatException);
+ });
+ });
+
+ group('decoder', () {
+ test('converts percent-encoded strings to byte arrays', () {
+ expect(
+ percent.decode('a%2Bb%3D%801'), equals([...'a+b=\x801'.codeUnits]));
+ });
+
+ test('supports lowercase letters', () {
+ expect(percent.decode('a%2bb%3d%80'), equals([...'a+b=\x80'.codeUnits]));
+ });
+
+ test('supports more aggressive encoding', () {
+ expect(percent.decode('%61%2E%5A'), equals([...'a.Z'.codeUnits]));
+ });
+
+ test('supports less aggressive encoding', () {
+ var chars = ' `{@[,/^}\x7F\x00';
+ expect(percent.decode(chars), equals([...chars.codeUnits]));
+ });
+
+ group('with chunked conversion', () {
+ late List<List<int>> results;
+ late StringConversionSink sink;
+ setUp(() {
+ results = [];
+ var controller = StreamController<List<int>>(sync: true);
+ controller.stream.listen(results.add);
+ sink = percent.decoder.startChunkedConversion(controller.sink);
+ });
+
+ test('converts percent to byte arrays', () {
+ sink.add('a%2Bb%3D%801');
+ expect(
+ results,
+ equals([
+ [...'a+b=\x801'.codeUnits]
+ ]));
+
+ sink.add('%00%01%FE%FF');
+ expect(
+ results,
+ equals([
+ [...'a+b=\x801'.codeUnits],
+ [0x00, 0x01, 0xfe, 0xff]
+ ]));
+ });
+
+ test('supports trailing percents and digits split across chunks', () {
+ sink.add('ab%');
+ expect(
+ results,
+ equals([
+ [...'ab'.codeUnits]
+ ]));
+
+ sink.add('2');
+ expect(
+ results,
+ equals([
+ [...'ab'.codeUnits]
+ ]));
+
+ sink.add('0cd%2');
+ expect(
+ results,
+ equals([
+ [...'ab'.codeUnits],
+ [...' cd'.codeUnits]
+ ]));
+
+ sink.add('0');
+ expect(
+ results,
+ equals([
+ [...'ab'.codeUnits],
+ [...' cd'.codeUnits],
+ [...' '.codeUnits]
+ ]));
+ });
+
+ test('supports empty strings', () {
+ sink.add('');
+ expect(results, isEmpty);
+
+ sink.add('%');
+ expect(results, equals([<Never>[]]));
+
+ sink.add('');
+ expect(results, equals([<Never>[]]));
+
+ sink.add('2');
+ expect(results, equals([<Never>[]]));
+
+ sink.add('');
+ expect(results, equals([<Never>[]]));
+
+ sink.add('0');
+ expect(
+ results,
+ equals([
+ <Never>[],
+ [0x20]
+ ]));
+ });
+
+ test('rejects dangling % detected in close()', () {
+ sink.add('ab%');
+ expect(
+ results,
+ equals([
+ [...'ab'.codeUnits]
+ ]));
+ expect(() => sink.close(), throwsFormatException);
+ });
+
+ test('rejects dangling digit detected in close()', () {
+ sink.add('ab%2');
+ expect(
+ results,
+ equals([
+ [...'ab'.codeUnits]
+ ]));
+ expect(() => sink.close(), throwsFormatException);
+ });
+
+ test('rejects danging % detected in addSlice()', () {
+ sink.addSlice('ab%', 0, 3, false);
+ expect(
+ results,
+ equals([
+ [...'ab'.codeUnits]
+ ]));
+
+ expect(() => sink.addSlice('ab%', 0, 3, true), throwsFormatException);
+ });
+
+ test('rejects danging digit detected in addSlice()', () {
+ sink.addSlice('ab%2', 0, 3, false);
+ expect(
+ results,
+ equals([
+ [...'ab'.codeUnits]
+ ]));
+
+ expect(() => sink.addSlice('ab%2', 0, 3, true), throwsFormatException);
+ });
+ });
+
+ group('rejects non-ASCII character', () {
+ for (var char in ['\u0141', '\u{10041}']) {
+ test('"$char"', () {
+ expect(() => percent.decode('a$char'), throwsFormatException);
+ expect(() => percent.decode('${char}a'), throwsFormatException);
+
+ var sink = percent.decoder
+ .startChunkedConversion(StreamController(sync: true));
+ expect(() => sink.add(char), throwsFormatException);
+ });
+ }
+ });
+
+ test('rejects % followed by non-hex', () {
+ expect(() => percent.decode('%z2'), throwsFormatException);
+ expect(() => percent.decode('%2z'), throwsFormatException);
+ });
+
+ test('rejects dangling % detected in convert()', () {
+ expect(() => percent.decode('ab%'), throwsFormatException);
+ });
+
+ test('rejects dangling digit detected in convert()', () {
+ expect(() => percent.decode('ab%2'), throwsFormatException);
+ });
+ });
+}
diff --git a/pkgs/convert/test/string_accumulator_sink_test.dart b/pkgs/convert/test/string_accumulator_sink_test.dart
new file mode 100644
index 0000000..8e3b7cb
--- /dev/null
+++ b/pkgs/convert/test/string_accumulator_sink_test.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:convert/convert.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late StringAccumulatorSink sink;
+ setUp(() {
+ sink = StringAccumulatorSink();
+ });
+
+ test('provides access to the concatenated string', () {
+ expect(sink.string, isEmpty);
+
+ sink.add('foo');
+ expect(sink.string, equals('foo'));
+
+ sink.addSlice(' bar baz', 1, 4, false);
+ expect(sink.string, equals('foobar'));
+ });
+
+ test('clear() clears the string', () {
+ sink.add('foo');
+ expect(sink.string, equals('foo'));
+
+ sink.clear();
+ expect(sink.string, isEmpty);
+
+ sink.add('bar');
+ expect(sink.string, equals('bar'));
+ });
+
+ test('indicates whether the sink is closed', () {
+ expect(sink.isClosed, isFalse);
+ sink.close();
+ expect(sink.isClosed, isTrue);
+ });
+
+ test('indicates whether the sink is closed via addSlice', () {
+ expect(sink.isClosed, isFalse);
+ sink.addSlice('', 0, 0, true);
+ expect(sink.isClosed, isTrue);
+ });
+
+ test("doesn't allow add() to be called after close()", () {
+ sink.close();
+ expect(() => sink.add('x'), throwsStateError);
+ });
+
+ test("doesn't allow addSlice() to be called after close()", () {
+ sink.close();
+ expect(() => sink.addSlice('', 0, 0, false), throwsStateError);
+ });
+}
diff --git a/pkgs/crypto/.gitignore b/pkgs/crypto/.gitignore
new file mode 100644
index 0000000..79f51c3
--- /dev/null
+++ b/pkgs/crypto/.gitignore
@@ -0,0 +1,3 @@
+.dart_tool
+.packages
+pubspec.lock
diff --git a/pkgs/crypto/.test_config b/pkgs/crypto/.test_config
new file mode 100644
index 0000000..531426a
--- /dev/null
+++ b/pkgs/crypto/.test_config
@@ -0,0 +1,5 @@
+{
+ "test_package": {
+ "platforms": ["vm"]
+ }
+}
\ No newline at end of file
diff --git a/pkgs/crypto/AUTHORS b/pkgs/crypto/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/crypto/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/crypto/CHANGELOG.md b/pkgs/crypto/CHANGELOG.md
new file mode 100644
index 0000000..f971b68
--- /dev/null
+++ b/pkgs/crypto/CHANGELOG.md
@@ -0,0 +1,169 @@
+## 3.0.6
+
+* Move to `dart-lang/core` monorepo.
+
+## 3.0.5
+
+* Revert switch to enable fast "sinks" on Wasm because it breaks `dart2js` with
+ server mode.
+
+## 3.0.4
+
+* Fix WebAssembly support.
+* Require Dart 3.4
+
+## 3.0.3
+
+* Require Dart 2.19.0.
+* Add topics to `pubspec.yaml`.
+
+## 3.0.2
+
+* Require Dart 2.14.0.
+* Fix bug calculating hashes for content larger than 512MB when compiled to JS.
+
+## 3.0.1
+
+* Fix doc links in README.
+
+## 3.0.0
+
+* Stable release for null safety.
+* Adds SHA-2 512/224 and SHA-2 512/256 from FIPS 180-4
+* Removes `newInstance` instance members on some classes
+ and updates documentation.
+
+## 2.1.5
+
+* Improve example and package description to address package site maintenance
+ suggestions.
+
+## 2.1.4
+ * BugFix: padding was incorrect for some SHA-512/328.
+
+## 2.1.3
+ * **Security vulnerability**: Fixed constant-time comparison in `Digest`.
+
+## 2.1.2
+ * Fix bug in SHA-2 384/512 blocksize.
+ * Added HMAC-SHA-2 test vectors
+
+## 2.1.1+1
+ * Bump version number for publish mishap (spare file uploaded with `pub
+ publish`).
+
+## 2.1.1
+ * Added a workaround for a bug in DDC (used in build_web_compilers 1.x).
+ This bug is not present in DDK (used in build_web_compilers 2.x).
+
+## 2.1.0
+ * Added SHA384, and SHA512
+ * Add Sha224 + Refactor
+ * Support 32bit and 64bit operations for SHA384/51
+ * Add conditional imports
+ * De-listify 32bit allocations
+ * Add sha monte tests for 224,256,384, and 512
+
+## 2.0.5
+
+* Changed the max message size instead to 0x3ffffffffffff, which is the largest
+ portable value for both JS and the Dart VM.
+
+## 2.0.4
+
+* Made max message size a BigNum instead of an int so that dart2js can compile
+ with crypto.
+
+## 2.0.3
+
+* Updated SDK version to 2.0.0-dev.17.0
+
+## 2.0.2+1
+
+* Fix SDK constraint.
+
+## 2.0.2
+
+* Prepare `HashSink` implementation for limiting integers to 64 bits in Dart
+ language.
+
+## 2.0.1
+
+* Support `convert` 2.0.0.
+
+## 2.0.0
+
+**Note**: There are no APIs in 2.0.0 that weren't also in 0.9.2. Packages that
+would use 2.0.0 as a lower bound should use 0.9.2 instead—for example, `crypto:
+">=0.9.2 <3.0.0"`.
+
+* `Hash` and `Hmac` no longer extend `ChunkedConverter`.
+
+## 1.1.1
+
+* Properly close sinks passed to `Hash.startChunkedConversion()` when
+ `ByteConversionSink.close()` is called.
+
+## 1.1.0
+
+* `Hmac` and `Hash` now extend the new `ChunkedConverter` class from
+ `dart:convert`.
+
+* Fix all strong mode warnings.
+
+## 1.0.0
+
+* All APIs that were deprecated in 0.9.2 have been removed. No new APIs have
+ been added. Packages that would use 1.0.0 as a lower bound should use 0.9.2
+ instead—for example, `crypto: ">=0.9.2 <2.0.0"`.
+
+## 0.9.2+1
+
+* Avoid core library methods that don't work on dart2js.
+
+## 0.9.2
+
+* `Hash`, `MD5`, `SHA1`, and `SHA256` now implement `Converter`. They convert
+ between `List<int>`s and the new `Digest` class, which represents a hash
+ digest. The `Converter` APIs—`Hash.convert()` and
+ `Hash.startChunkedConversion`—should be used in preference to the old APIs,
+ which are now deprecated.
+
+* `SHA1`, `SHA256`, and `HMAC` have been renamed to `Sha1`, `Sha256`, and
+ `Hmac`, respectively. The old names still work, but are deprecated.
+
+* Top-level `sha1`, `sha256`, and `md5` fields have been added to make it easier
+ to use those hash algorithms without having to instantiate new instances.
+
+* Hashing now works correctly for input sizes up to 2^64 bytes.
+
+### Deprecations
+
+* `Hash.add`, `Hash.close`, and `Hash.newInstance` are deprecated.
+ `Hash.convert` should be used for hashing single values, and
+ `Hash.startChunkedConversion` should be used for hashing streamed values.
+
+* `SHA1` and `SHA256` are deprecated. Use the top-level `sha1` and `sha256`
+ fields instead.
+
+* While the `MD5` class is not deprecated, the `new MD5()` constructor is. Use
+ the top-level `md5` field instead.
+
+* `HMAC` is deprecated. Use `Hmac` instead.
+
+* `Base64Codec`, `Base64Encoder`, `Base64Decoder`, `Base64EncoderSink`,
+ `Base64DecoderSink`, and `BASE64` are deprecated. Use the Base64 APIs in
+ `dart:convert` instead.
+
+* `CryptoUtils` is deprecated. Use the Base64 APIs in `dart:convert` and the hex
+ APIs in the `convert` package instead.
+
+## 0.9.1
+
+* Base64 convert returns an Uint8List
+* Base64 codec and encoder can now take an encodePaddingCharacter
+* Implement a Base64 codec similar to codecs in 'dart:convert'
+
+## 0.9.0
+
+* ChangeLog starts here.
diff --git a/pkgs/crypto/LICENSE b/pkgs/crypto/LICENSE
new file mode 100644
index 0000000..633672a
--- /dev/null
+++ b/pkgs/crypto/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2015, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/crypto/README.md b/pkgs/crypto/README.md
new file mode 100644
index 0000000..d2d0a02
--- /dev/null
+++ b/pkgs/crypto/README.md
@@ -0,0 +1,113 @@
+[](https://github.com/dart-lang/core/actions/workflows/crypto.yaml)
+[](https://pub.dev/packages/crypto)
+[](https://pub.dev/packages/crypto/publisher)
+
+A set of cryptographic hashing functions for Dart.
+
+The following hashing algorithms are supported:
+
+* SHA-1
+* SHA-224
+* SHA-256
+* SHA-384
+* SHA-512
+* SHA-512/224
+* SHA-512/256
+* MD5
+* HMAC (i.e. HMAC-MD5, HMAC-SHA1, HMAC-SHA256)
+
+## Usage
+
+### Digest on a single input
+
+To hash a list of bytes, invoke the [`convert`][convert] method on the
+[`sha1`][sha1-obj], [`sha256`][sha256-obj] or [`md5`][md5-obj]
+objects.
+
+```dart
+import 'package:crypto/crypto.dart';
+import 'dart:convert'; // for the utf8.encode method
+
+void main() {
+ var bytes = utf8.encode("foobar"); // data being hashed
+
+ var digest = sha1.convert(bytes);
+
+ print("Digest as bytes: ${digest.bytes}");
+ print("Digest as hex string: $digest");
+}
+```
+
+### Digest on chunked input
+
+If the input data is not available as a _single_ list of bytes, use
+the chunked conversion approach.
+
+Invoke the [`startChunkedConversion`][startChunkedConversion] method
+to create a sink for the input data. On the sink, invoke the `add`
+method for each chunk of input data, and invoke the `close` method
+when all the chunks have been added. The digest can then be retrieved
+from the `Sink<Digest>` used to create the input data sink.
+
+```dart
+import 'dart:convert';
+
+import 'package:convert/convert.dart';
+import 'package:crypto/crypto.dart';
+
+void main() {
+ var firstChunk = utf8.encode("foo");
+ var secondChunk = utf8.encode("bar");
+
+ var output = AccumulatorSink<Digest>();
+ var input = sha1.startChunkedConversion(output);
+ input.add(firstChunk);
+ input.add(secondChunk); // call `add` for every chunk of input data
+ input.close();
+ var digest = output.events.single;
+
+ print("Digest as bytes: ${digest.bytes}");
+ print("Digest as hex string: $digest");
+}
+```
+
+The above example uses the `AccumulatorSink` class that comes with the
+_convert_ package. It is capable of accumulating multiple events, but
+in this usage only a single `Digest` is added to it when the data sink's
+`close` method is invoked.
+
+### HMAC
+
+Create an instance of the [`Hmac`][Hmac] class with the hash function
+and secret key being used. The object can then be used like the other
+hash calculating objects.
+
+```dart
+import 'dart:convert';
+import 'package:crypto/crypto.dart';
+
+void main() {
+ var key = utf8.encode('p@ssw0rd');
+ var bytes = utf8.encode("foobar");
+
+ var hmacSha256 = Hmac(sha256, key); // HMAC-SHA256
+ var digest = hmacSha256.convert(bytes);
+
+ print("HMAC digest as bytes: ${digest.bytes}");
+ print("HMAC digest as hex string: $digest");
+}
+```
+
+## Disclaimer
+
+Support for this library is given as _best effort_.
+
+This library has not been reviewed or vetted by security professionals.
+
+[convert]: https://pub.dev/documentation/crypto/latest/crypto/Hash/convert.html
+[Digest]: https://pub.dev/documentation/crypto/latest/crypto/Digest-class.html
+[Hmac]: https://pub.dev/documentation/crypto/latest/crypto/Hmac-class.html
+[md5-obj]: https://pub.dev/documentation/crypto/latest/crypto/md5-constant.html
+[sha1-obj]: https://pub.dev/documentation/crypto/latest/crypto/sha1-constant.html
+[sha256-obj]: https://pub.dev/documentation/crypto/latest/crypto/sha256-constant.html
+[startChunkedConversion]: https://pub.dev/documentation/crypto/latest/crypto/Hash/startChunkedConversion.html
diff --git a/pkgs/crypto/analysis_options.yaml b/pkgs/crypto/analysis_options.yaml
new file mode 100644
index 0000000..897fc8a
--- /dev/null
+++ b/pkgs/crypto/analysis_options.yaml
@@ -0,0 +1,12 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_unused_constructor_parameters
+ - cancel_subscriptions
diff --git a/pkgs/crypto/example/example.dart b/pkgs/crypto/example/example.dart
new file mode 100644
index 0000000..61a4bf7
--- /dev/null
+++ b/pkgs/crypto/example/example.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:crypto/crypto.dart';
+
+final _usage = 'Usage: dart hash.dart <md5|sha1|sha256> <input_filename>';
+
+Future<void> main(List<String> args) async {
+ if (args.length != 2) {
+ print(_usage);
+ exitCode = 64; // Command was used incorrectly.
+ return;
+ }
+
+ Hash hasher;
+
+ switch (args[0]) {
+ case 'md5':
+ hasher = md5;
+ break;
+ case 'sha1':
+ hasher = sha1;
+ break;
+ case 'sha256':
+ hasher = sha256;
+ break;
+ default:
+ print(_usage);
+ exitCode = 64; // Command was used incorrectly.
+ return;
+ }
+
+ var filename = args[1];
+ var input = File(filename);
+
+ if (!input.existsSync()) {
+ print('File "$filename" does not exist.');
+ exitCode = 66; // An input file did not exist or was not readable.
+ return;
+ }
+
+ var value = await hasher.bind(input.openRead()).first;
+
+ print(value);
+}
diff --git a/pkgs/crypto/lib/crypto.dart b/pkgs/crypto/lib/crypto.dart
new file mode 100644
index 0000000..d0e1a76
--- /dev/null
+++ b/pkgs/crypto/lib/crypto.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'src/digest.dart';
+export 'src/hash.dart';
+export 'src/hmac.dart';
+export 'src/md5.dart';
+export 'src/sha1.dart';
+export 'src/sha256.dart';
+export 'src/sha512.dart';
diff --git a/pkgs/crypto/lib/src/digest.dart b/pkgs/crypto/lib/src/digest.dart
new file mode 100644
index 0000000..6503fed
--- /dev/null
+++ b/pkgs/crypto/lib/src/digest.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:typed_data';
+
+/// A message digest as computed by a `Hash` or `HMAC` function.
+class Digest {
+ /// The message digest as an array of bytes.
+ final List<int> bytes;
+
+ Digest(this.bytes);
+
+ /// Returns whether this is equal to another digest.
+ ///
+ /// This should be used instead of manual comparisons to avoid leaking
+ /// information via timing.
+ @override
+ bool operator ==(Object other) {
+ if (other is Digest) {
+ final a = bytes;
+ final b = other.bytes;
+ final n = a.length;
+ if (n != b.length) {
+ return false;
+ }
+ var mismatch = 0;
+ for (var i = 0; i < n; i++) {
+ mismatch |= a[i] ^ b[i];
+ }
+ return mismatch == 0;
+ }
+ return false;
+ }
+
+ @override
+ int get hashCode => Object.hashAll(bytes);
+
+ /// The message digest as a string of hexadecimal digits.
+ @override
+ String toString() => _hexEncode(bytes);
+}
+
+String _hexEncode(List<int> bytes) {
+ const hexDigits = '0123456789abcdef';
+ var charCodes = Uint8List(bytes.length * 2);
+ for (var i = 0, j = 0; i < bytes.length; i++) {
+ var byte = bytes[i];
+ charCodes[j++] = hexDigits.codeUnitAt((byte >> 4) & 0xF);
+ charCodes[j++] = hexDigits.codeUnitAt(byte & 0xF);
+ }
+ return String.fromCharCodes(charCodes);
+}
diff --git a/pkgs/crypto/lib/src/digest_sink.dart b/pkgs/crypto/lib/src/digest_sink.dart
new file mode 100644
index 0000000..092fc40
--- /dev/null
+++ b/pkgs/crypto/lib/src/digest_sink.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'digest.dart';
+
+/// A sink used to get a digest value out of `Hash.startChunkedConversion`.
+class DigestSink implements Sink<Digest> {
+ /// The value added to the sink.
+ ///
+ /// A value must have been added using [add] before reading the `value`.
+ Digest get value => _value!;
+
+ Digest? _value;
+
+ /// Adds [value] to the sink.
+ ///
+ /// Unlike most sinks, this may only be called once.
+ @override
+ void add(Digest value) {
+ if (_value != null) throw StateError('add may only be called once.');
+ _value = value;
+ }
+
+ @override
+ void close() {
+ if (_value == null) throw StateError('add must be called once.');
+ }
+}
diff --git a/pkgs/crypto/lib/src/hash.dart b/pkgs/crypto/lib/src/hash.dart
new file mode 100644
index 0000000..8e05160
--- /dev/null
+++ b/pkgs/crypto/lib/src/hash.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
+import 'digest.dart';
+import 'digest_sink.dart';
+
+/// An interface for cryptographic hash functions.
+///
+/// Every hash is a converter that takes a list of ints and returns a single
+/// digest. When used in chunked mode, it will only ever add one digest to the
+/// inner [Sink].
+abstract class Hash extends Converter<List<int>, Digest> {
+ /// The internal block size of the hash in bytes.
+ ///
+ /// This is exposed for use by the `Hmac` class,
+ /// which needs to know the block size for the [Hash] it uses.
+ int get blockSize;
+
+ const Hash();
+
+ @override
+ Digest convert(List<int> input) {
+ var innerSink = DigestSink();
+ var outerSink = startChunkedConversion(innerSink);
+ outerSink.add(input);
+ outerSink.close();
+ return innerSink.value;
+ }
+
+ @override
+ ByteConversionSink startChunkedConversion(Sink<Digest> sink);
+}
diff --git a/pkgs/crypto/lib/src/hash_sink.dart b/pkgs/crypto/lib/src/hash_sink.dart
new file mode 100644
index 0000000..cd23823
--- /dev/null
+++ b/pkgs/crypto/lib/src/hash_sink.dart
@@ -0,0 +1,178 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:typed_data';
+
+import 'package:typed_data/typed_data.dart';
+
+import 'digest.dart';
+import 'utils.dart';
+
+/// A base class for [Sink] implementations for hash algorithms.
+///
+/// Subclasses should override [updateHash] and [digest].
+abstract class HashSink implements Sink<List<int>> {
+ /// The inner sink that this should forward to.
+ final Sink<Digest> _sink;
+
+ /// Whether the hash function operates on big-endian words.
+ final Endian _endian;
+
+ /// The words in the current chunk.
+ ///
+ /// This is an instance variable to avoid re-allocating, but its data isn't
+ /// used across invocations of [_iterate].
+ final Uint32List _currentChunk;
+
+ /// Messages with more than 2^53-1 bits are not supported.
+ ///
+ /// This is the largest value that is precisely representable
+ /// on both JS and the Dart VM.
+ /// So the maximum length in bytes is (2^53-1)/8.
+ static const _maxMessageLengthInBytes = 0x0003ffffffffffff;
+
+ /// The length of the input data so far, in bytes.
+ int _lengthInBytes = 0;
+
+ /// Data that has yet to be processed by the hash function.
+ final _pendingData = Uint8Buffer();
+
+ /// Whether [close] has been called.
+ bool _isClosed = false;
+
+ /// The words in the current digest.
+ ///
+ /// This should be updated each time [updateHash] is called.
+ Uint32List get digest;
+
+ /// The number of signature bytes emitted at the end of the message.
+ ///
+ /// An encrypted message is followed by a signature which depends
+ /// on the encryption algorithm used. This value specifies the
+ /// number of bytes used by this signature. It must always be
+ /// a power of 2 and no less than 8.
+ final int _signatureBytes;
+
+ /// Creates a new hash.
+ ///
+ /// [chunkSizeInWords] represents the size of the input chunks processed by
+ /// the algorithm, in terms of 32-bit words.
+ HashSink(this._sink, int chunkSizeInWords,
+ {Endian endian = Endian.big, int signatureBytes = 8})
+ : _endian = endian,
+ assert(signatureBytes >= 8),
+ _signatureBytes = signatureBytes,
+ _currentChunk = Uint32List(chunkSizeInWords);
+
+ /// Runs a single iteration of the hash computation, updating [digest] with
+ /// the result.
+ ///
+ /// [chunk] is the current chunk, whose size is given by the
+ /// `chunkSizeInWords` parameter passed to the constructor.
+ void updateHash(Uint32List chunk);
+
+ @override
+ void add(List<int> data) {
+ if (_isClosed) throw StateError('Hash.add() called after close().');
+ _lengthInBytes += data.length;
+ _pendingData.addAll(data);
+ _iterate();
+ }
+
+ @override
+ void close() {
+ if (_isClosed) return;
+ _isClosed = true;
+
+ _finalizeData();
+ _iterate();
+ assert(_pendingData.isEmpty);
+ _sink.add(Digest(_byteDigest()));
+ _sink.close();
+ }
+
+ Uint8List _byteDigest() {
+ if (_endian == Endian.host) return digest.buffer.asUint8List();
+
+ // Cache the digest locally as `get` could be expensive.
+ final cachedDigest = digest;
+ final byteDigest = Uint8List(cachedDigest.lengthInBytes);
+ final byteData = byteDigest.buffer.asByteData();
+ for (var i = 0; i < cachedDigest.length; i++) {
+ byteData.setUint32(i * bytesPerWord, cachedDigest[i]);
+ }
+ return byteDigest;
+ }
+
+ /// Iterates through [_pendingData], updating the hash computation for each
+ /// chunk.
+ void _iterate() {
+ var pendingDataBytes = _pendingData.buffer.asByteData();
+ var pendingDataChunks = _pendingData.length ~/ _currentChunk.lengthInBytes;
+ for (var i = 0; i < pendingDataChunks; i++) {
+ // Copy words from the pending data buffer into the current chunk buffer.
+ for (var j = 0; j < _currentChunk.length; j++) {
+ _currentChunk[j] = pendingDataBytes.getUint32(
+ i * _currentChunk.lengthInBytes + j * bytesPerWord, _endian);
+ }
+
+ // Run the hash function on the current chunk.
+ updateHash(_currentChunk);
+ }
+
+ // Remove all pending data up to the last clean chunk break.
+ _pendingData.removeRange(
+ 0, pendingDataChunks * _currentChunk.lengthInBytes);
+ }
+
+ /// Finalizes [_pendingData].
+ ///
+ /// This adds a 1 bit to the end of the message, and expands it with 0 bits to
+ /// pad it out.
+ void _finalizeData() {
+ // Pad out the data with 0x80, eight or sixteen 0s, and as many more 0s
+ // as we need to land cleanly on a chunk boundary.
+ _pendingData.add(0x80);
+
+ final contentsLength = _lengthInBytes + 1 /* 0x80 */ + _signatureBytes;
+ final finalizedLength =
+ _roundUp(contentsLength, _currentChunk.lengthInBytes);
+
+ for (var i = 0; i < finalizedLength - contentsLength; i++) {
+ _pendingData.add(0);
+ }
+
+ if (_lengthInBytes > _maxMessageLengthInBytes) {
+ throw UnsupportedError(
+ 'Hashing is unsupported for messages with more than 2^53 bits.');
+ }
+
+ var lengthInBits = _lengthInBytes * bitsPerByte;
+
+ // Add the full length of the input data as a 64-bit value at the end of the
+ // hash. Note: we're only writing out 64 bits, so skip ahead 8 if the
+ // signature is 128-bit.
+ final offset = _pendingData.length + (_signatureBytes - 8);
+
+ _pendingData.addAll(Uint8List(_signatureBytes));
+ var byteData = _pendingData.buffer.asByteData();
+
+ // We're essentially doing byteData.setUint64(offset, lengthInBits, _endian)
+ // here, but that method isn't supported on dart2js so we implement it
+ // manually instead.
+ var highBits = lengthInBits ~/ 0x100000000; // >> 32
+ var lowBits = lengthInBits & mask32;
+ if (_endian == Endian.big) {
+ byteData.setUint32(offset, highBits, _endian);
+ byteData.setUint32(offset + bytesPerWord, lowBits, _endian);
+ } else {
+ byteData.setUint32(offset, lowBits, _endian);
+ byteData.setUint32(offset + bytesPerWord, highBits, _endian);
+ }
+ }
+
+ /// Rounds [val] up to the next multiple of [n], as long as [n] is a power of
+ /// two.
+ int _roundUp(int val, int n) => (val + n - 1) & -n;
+}
diff --git a/pkgs/crypto/lib/src/hmac.dart b/pkgs/crypto/lib/src/hmac.dart
new file mode 100644
index 0000000..f50ff89
--- /dev/null
+++ b/pkgs/crypto/lib/src/hmac.dart
@@ -0,0 +1,107 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'digest.dart';
+import 'digest_sink.dart';
+import 'hash.dart';
+
+/// An implementation of [keyed-hash method authentication codes][rfc].
+///
+/// [rfc]: https://tools.ietf.org/html/rfc2104
+///
+/// HMAC allows messages to be cryptographically authenticated using any
+/// iterated cryptographic hash function.
+class Hmac extends Converter<List<int>, Digest> {
+ /// The hash function used to compute the authentication digest.
+ final Hash _hash;
+
+ /// The secret key shared by the sender and the receiver.
+ final Uint8List _key;
+
+ /// Create an [Hmac] object from a [Hash] and a binary key.
+ ///
+ /// The key should be a secret shared between the sender and receiver of the
+ /// message.
+ Hmac(Hash hash, List<int> key)
+ : _hash = hash,
+ _key = Uint8List(hash.blockSize) {
+ // Hash the key if it's longer than the block size of the hash.
+ if (key.length > _hash.blockSize) key = _hash.convert(key).bytes;
+
+ // If [key] is shorter than the block size, the rest of [_key] will be
+ // 0-padded.
+ _key.setRange(0, key.length, key);
+ }
+
+ @override
+ Digest convert(List<int> input) {
+ var innerSink = DigestSink();
+ var outerSink = startChunkedConversion(innerSink);
+ outerSink.add(input);
+ outerSink.close();
+ return innerSink.value;
+ }
+
+ @override
+ ByteConversionSink startChunkedConversion(Sink<Digest> sink) =>
+ _HmacSink(sink, _hash, _key);
+}
+
+/// The concrete implementation of the HMAC algorithm.
+class _HmacSink extends ByteConversionSink {
+ /// The sink for the outer hash computation.
+ final ByteConversionSink _outerSink;
+
+ /// The sink that [_innerSink]'s result will be added to when it's available.
+ final _innerResultSink = DigestSink();
+
+ /// The sink for the inner hash computation.
+ late final ByteConversionSink _innerSink;
+
+ /// Whether [close] has been called.
+ bool _isClosed = false;
+
+ _HmacSink(Sink<Digest> sink, Hash hash, List<int> key)
+ : _outerSink = hash.startChunkedConversion(sink) {
+ _innerSink = hash.startChunkedConversion(_innerResultSink);
+
+ // Compute outer padding.
+ var padding = Uint8List(key.length);
+ for (var i = 0; i < padding.length; i++) {
+ padding[i] = 0x5c ^ key[i];
+ }
+ _outerSink.add(padding);
+
+ // Compute inner padding.
+ for (var i = 0; i < padding.length; i++) {
+ padding[i] = 0x36 ^ key[i];
+ }
+ _innerSink.add(padding);
+ }
+
+ @override
+ void add(List<int> data) {
+ if (_isClosed) throw StateError('HMAC is closed');
+ _innerSink.add(data);
+ }
+
+ @override
+ void addSlice(List<int> data, int start, int end, bool isLast) {
+ if (_isClosed) throw StateError('HMAC is closed');
+ _innerSink.addSlice(data, start, end, isLast);
+ }
+
+ @override
+ void close() {
+ if (_isClosed) return;
+ _isClosed = true;
+
+ _innerSink.close();
+ _outerSink.add(_innerResultSink.value.bytes);
+ _outerSink.close();
+ }
+}
diff --git a/pkgs/crypto/lib/src/md5.dart b/pkgs/crypto/lib/src/md5.dart
new file mode 100644
index 0000000..df30a3f
--- /dev/null
+++ b/pkgs/crypto/lib/src/md5.dart
@@ -0,0 +1,121 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'digest.dart';
+import 'hash.dart';
+import 'hash_sink.dart';
+import 'utils.dart';
+
+/// An implementation of the [MD5][rfc] hash function.
+///
+/// [rfc]: https://tools.ietf.org/html/rfc1321
+///
+/// **Warning**: MD5 has known collisions and should only be used when required
+/// for backwards compatibility.
+const Hash md5 = _MD5._();
+
+/// An implementation of the [MD5][rfc] hash function.
+///
+/// [rfc]: https://tools.ietf.org/html/rfc1321
+///
+/// **Warning**: MD5 has known collisions and should only be used when required
+/// for backwards compatibility.
+///
+/// Use the [md5] object to perform MD5 hashing.
+class _MD5 extends Hash {
+ @override
+ final int blockSize = 16 * bytesPerWord;
+
+ const _MD5._();
+
+ @override
+ ByteConversionSink startChunkedConversion(Sink<Digest> sink) =>
+ ByteConversionSink.from(_MD5Sink(sink));
+}
+
+/// Data from a non-linear mathematical function that functions as
+/// reproducible noise.
+const _noise = [
+ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, //
+ 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
+ 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,
+ 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
+ 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
+ 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
+ 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
+ 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+ 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
+ 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
+ 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
+];
+
+/// Per-round shift amounts.
+const _shiftAmounts = [
+ 07, 12, 17, 22, 07, 12, 17, 22, 07, 12, 17, 22, 07, 12, 17, 22, 05, 09, 14, //
+ 20, 05, 09, 14, 20, 05, 09, 14, 20, 05, 09, 14, 20, 04, 11, 16, 23, 04, 11,
+ 16, 23, 04, 11, 16, 23, 04, 11, 16, 23, 06, 10, 15, 21, 06, 10, 15, 21, 06,
+ 10, 15, 21, 06, 10, 15, 21
+];
+
+/// The concrete implementation of `MD5`.
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class _MD5Sink extends HashSink {
+ @override
+ final digest = Uint32List(4);
+
+ _MD5Sink(Sink<Digest> sink) : super(sink, 16, endian: Endian.little) {
+ digest[0] = 0x67452301;
+ digest[1] = 0xefcdab89;
+ digest[2] = 0x98badcfe;
+ digest[3] = 0x10325476;
+ }
+
+ @override
+ void updateHash(Uint32List chunk) {
+ assert(chunk.length == 16);
+
+ var a = digest[0];
+ var b = digest[1];
+ var c = digest[2];
+ var d = digest[3];
+
+ int e;
+ int f;
+
+ for (var i = 0; i < 64; i++) {
+ if (i < 16) {
+ e = (b & c) | ((~b & mask32) & d);
+ f = i;
+ } else if (i < 32) {
+ e = (d & b) | ((~d & mask32) & c);
+ f = ((5 * i) + 1) % 16;
+ } else if (i < 48) {
+ e = b ^ c ^ d;
+ f = ((3 * i) + 5) % 16;
+ } else {
+ e = c ^ (b | (~d & mask32));
+ f = (7 * i) % 16;
+ }
+
+ var temp = d;
+ d = c;
+ c = b;
+ b = add32(
+ b,
+ rotl32(add32(add32(a, e), add32(_noise[i], chunk[f])),
+ _shiftAmounts[i]));
+ a = temp;
+ }
+
+ digest[0] = add32(a, digest[0]);
+ digest[1] = add32(b, digest[1]);
+ digest[2] = add32(c, digest[2]);
+ digest[3] = add32(d, digest[3]);
+ }
+}
diff --git a/pkgs/crypto/lib/src/sha1.dart b/pkgs/crypto/lib/src/sha1.dart
new file mode 100644
index 0000000..5fc13a0
--- /dev/null
+++ b/pkgs/crypto/lib/src/sha1.dart
@@ -0,0 +1,102 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'digest.dart';
+import 'hash.dart';
+import 'hash_sink.dart';
+import 'utils.dart';
+
+/// An implementation of the [SHA-1][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc3174
+const Hash sha1 = _Sha1._();
+
+/// An implementation of the [SHA-1][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc3174
+class _Sha1 extends Hash {
+ @override
+ final int blockSize = 16 * bytesPerWord;
+
+ const _Sha1._();
+
+ @override
+ ByteConversionSink startChunkedConversion(Sink<Digest> sink) =>
+ ByteConversionSink.from(_Sha1Sink(sink));
+}
+
+/// The concrete implementation of `Sha1`.
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class _Sha1Sink extends HashSink {
+ @override
+ final digest = Uint32List(5);
+
+ /// The sixteen words from the original chunk, extended to 80 words.
+ ///
+ /// This is an instance variable to avoid re-allocating, but its data isn't
+ /// used across invocations of [updateHash].
+ final Uint32List _extended;
+
+ _Sha1Sink(Sink<Digest> sink)
+ : _extended = Uint32List(80),
+ super(sink, 16) {
+ digest[0] = 0x67452301;
+ digest[1] = 0xEFCDAB89;
+ digest[2] = 0x98BADCFE;
+ digest[3] = 0x10325476;
+ digest[4] = 0xC3D2E1F0;
+ }
+
+ @override
+ void updateHash(Uint32List chunk) {
+ assert(chunk.length == 16);
+
+ var a = digest[0];
+ var b = digest[1];
+ var c = digest[2];
+ var d = digest[3];
+ var e = digest[4];
+
+ for (var i = 0; i < 80; i++) {
+ if (i < 16) {
+ _extended[i] = chunk[i];
+ } else {
+ _extended[i] = rotl32(
+ _extended[i - 3] ^
+ _extended[i - 8] ^
+ _extended[i - 14] ^
+ _extended[i - 16],
+ 1);
+ }
+
+ var newA = add32(add32(rotl32(a, 5), e), _extended[i]);
+ if (i < 20) {
+ newA = add32(add32(newA, (b & c) | (~b & d)), 0x5A827999);
+ } else if (i < 40) {
+ newA = add32(add32(newA, b ^ c ^ d), 0x6ED9EBA1);
+ } else if (i < 60) {
+ newA = add32(add32(newA, (b & c) | (b & d) | (c & d)), 0x8F1BBCDC);
+ } else {
+ newA = add32(add32(newA, b ^ c ^ d), 0xCA62C1D6);
+ }
+
+ e = d;
+ d = c;
+ c = rotl32(b, 30);
+ b = a;
+ a = newA & mask32;
+ }
+
+ digest[0] = add32(a, digest[0]);
+ digest[1] = add32(b, digest[1]);
+ digest[2] = add32(c, digest[2]);
+ digest[3] = add32(d, digest[3]);
+ digest[4] = add32(e, digest[4]);
+ }
+}
diff --git a/pkgs/crypto/lib/src/sha256.dart b/pkgs/crypto/lib/src/sha256.dart
new file mode 100644
index 0000000..36808d3
--- /dev/null
+++ b/pkgs/crypto/lib/src/sha256.dart
@@ -0,0 +1,188 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'digest.dart';
+import 'hash.dart';
+import 'hash_sink.dart';
+import 'utils.dart';
+
+/// An implementation of the [SHA-256][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+const Hash sha256 = _Sha256._();
+
+/// An implementation of the [SHA-224][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+const Hash sha224 = _Sha224._();
+
+/// An implementation of the [SHA-256][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+///
+/// Use the [sha256] object to perform SHA-256 hashing.
+class _Sha256 extends Hash {
+ @override
+ final int blockSize = 16 * bytesPerWord;
+
+ const _Sha256._();
+
+ @override
+ ByteConversionSink startChunkedConversion(Sink<Digest> sink) =>
+ ByteConversionSink.from(_Sha256Sink(sink));
+}
+
+/// An implementation of the [SHA-224][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+///
+///
+/// Use the [sha224] object to perform SHA-224 hashing.
+class _Sha224 extends Hash {
+ @override
+ final int blockSize = 16 * bytesPerWord;
+
+ const _Sha224._();
+
+ @override
+ ByteConversionSink startChunkedConversion(Sink<Digest> sink) =>
+ ByteConversionSink.from(_Sha224Sink(sink));
+}
+
+/// Data from a non-linear function that functions as reproducible noise.
+const List<int> _noise = [
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, //
+ 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
+ 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
+ 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
+ 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
+ 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+];
+
+abstract class _Sha32BitSink extends HashSink {
+ final Uint32List _digest;
+
+ /// The sixteen words from the original chunk, extended to 64 words.
+ ///
+ /// This is an instance variable to avoid re-allocating, but its data isn't
+ /// used across invocations of [updateHash].
+ final _extended = Uint32List(64);
+
+ _Sha32BitSink(Sink<Digest> sink, this._digest) : super(sink, 16);
+
+ // The following helper functions are taken directly from
+ // http://tools.ietf.org/html/rfc6234.
+
+ int _rotr32(int n, int x) => (x >> n) | ((x << (32 - n)) & mask32);
+ int _ch(int x, int y, int z) => (x & y) ^ ((~x & mask32) & z);
+ int _maj(int x, int y, int z) => (x & y) ^ (x & z) ^ (y & z);
+ int _bsig0(int x) => _rotr32(2, x) ^ _rotr32(13, x) ^ _rotr32(22, x);
+ int _bsig1(int x) => _rotr32(6, x) ^ _rotr32(11, x) ^ _rotr32(25, x);
+ int _ssig0(int x) => _rotr32(7, x) ^ _rotr32(18, x) ^ (x >> 3);
+ int _ssig1(int x) => _rotr32(17, x) ^ _rotr32(19, x) ^ (x >> 10);
+
+ @override
+ void updateHash(Uint32List chunk) {
+ assert(chunk.length == 16);
+
+ // Prepare message schedule.
+ for (var i = 0; i < 16; i++) {
+ _extended[i] = chunk[i];
+ }
+ for (var i = 16; i < 64; i++) {
+ _extended[i] = add32(add32(_ssig1(_extended[i - 2]), _extended[i - 7]),
+ add32(_ssig0(_extended[i - 15]), _extended[i - 16]));
+ }
+
+ // Shuffle around the bits.
+ var a = _digest[0];
+ var b = _digest[1];
+ var c = _digest[2];
+ var d = _digest[3];
+ var e = _digest[4];
+ var f = _digest[5];
+ var g = _digest[6];
+ var h = _digest[7];
+
+ for (var i = 0; i < 64; i++) {
+ var temp1 = add32(add32(h, _bsig1(e)),
+ add32(_ch(e, f, g), add32(_noise[i], _extended[i])));
+ var temp2 = add32(_bsig0(a), _maj(a, b, c));
+ h = g;
+ g = f;
+ f = e;
+ e = add32(d, temp1);
+ d = c;
+ c = b;
+ b = a;
+ a = add32(temp1, temp2);
+ }
+
+ // Update hash values after iteration.
+ _digest[0] = add32(a, _digest[0]);
+ _digest[1] = add32(b, _digest[1]);
+ _digest[2] = add32(c, _digest[2]);
+ _digest[3] = add32(d, _digest[3]);
+ _digest[4] = add32(e, _digest[4]);
+ _digest[5] = add32(f, _digest[5]);
+ _digest[6] = add32(g, _digest[6]);
+ _digest[7] = add32(h, _digest[7]);
+ }
+}
+
+/// The concrete implementation of `Sha256`.
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class _Sha256Sink extends _Sha32BitSink {
+ @override
+ Uint32List get digest => _digest;
+
+ // Initial value of the hash parts. First 32 bits of the fractional parts
+ // of the square roots of the first 8 prime numbers.
+ _Sha256Sink(Sink<Digest> sink)
+ : super(
+ sink,
+ Uint32List.fromList([
+ 0x6a09e667,
+ 0xbb67ae85,
+ 0x3c6ef372,
+ 0xa54ff53a,
+ 0x510e527f,
+ 0x9b05688c,
+ 0x1f83d9ab,
+ 0x5be0cd19,
+ ]));
+}
+
+/// The concrete implementation of `Sha224`.
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class _Sha224Sink extends _Sha32BitSink {
+ @override
+ Uint32List get digest => _digest.buffer.asUint32List(0, 7);
+
+ _Sha224Sink(Sink<Digest> sink)
+ : super(
+ sink,
+ Uint32List.fromList([
+ 0xc1059ed8,
+ 0x367cd507,
+ 0x3070dd17,
+ 0xf70e5939,
+ 0xffc00b31,
+ 0x68581511,
+ 0x64f98fa7,
+ 0xbefa4fa4,
+ ]));
+}
diff --git a/pkgs/crypto/lib/src/sha512.dart b/pkgs/crypto/lib/src/sha512.dart
new file mode 100644
index 0000000..557954c
--- /dev/null
+++ b/pkgs/crypto/lib/src/sha512.dart
@@ -0,0 +1,95 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
+import 'digest.dart';
+import 'hash.dart';
+// ignore: uri_does_not_exist
+import 'sha512_fastsinks.dart' if (dart.library.js) 'sha512_slowsinks.dart';
+import 'utils.dart';
+
+/// An implementation of the [SHA-384][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+const Hash sha384 = _Sha384._();
+
+/// An implementation of the [SHA-512][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+const Hash sha512 = _Sha512._();
+
+/// An implementation of the [SHA-512/224][FIPS] hash function.
+///
+/// [FIPS]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
+const Hash sha512224 = _Sha512224();
+
+/// An implementation of the [SHA-512/256][FIPS] hash function.
+///
+/// [FIPS]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
+const Hash sha512256 = _Sha512256();
+
+/// An implementation of the [SHA-384][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+///
+/// Use the [sha384] object to perform SHA-384 hashing
+class _Sha384 extends Hash {
+ @override
+ final int blockSize = 32 * bytesPerWord;
+
+ const _Sha384._();
+
+ @override
+ ByteConversionSink startChunkedConversion(Sink<Digest> sink) =>
+ ByteConversionSink.from(Sha384Sink(sink));
+}
+
+/// An implementation of the [SHA-512][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+///
+/// Use the [sha512] object to perform SHA-512 hashing
+class _Sha512 extends Hash {
+ @override
+ final int blockSize = 32 * bytesPerWord;
+
+ const _Sha512._();
+
+ @override
+ ByteConversionSink startChunkedConversion(Sink<Digest> sink) =>
+ ByteConversionSink.from(Sha512Sink(sink));
+}
+
+/// An implementation of the [SHA-512/224][FIPS] hash function.
+///
+/// [FIPS]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
+///
+/// Use the [sha512224] object to perform SHA-512/224 hashing
+class _Sha512224 extends Hash {
+ @override
+ final int blockSize = 32 * bytesPerWord;
+
+ const _Sha512224();
+
+ @override
+ ByteConversionSink startChunkedConversion(Sink<Digest> sink) =>
+ ByteConversionSink.from(Sha512224Sink(sink));
+}
+
+/// An implementation of the [SHA-512/256][FIPS] hash function.
+///
+/// [FIPS]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
+///
+/// Use the [sha512256] object to perform SHA-512/256 hashing
+class _Sha512256 extends Hash {
+ @override
+ final int blockSize = 32 * bytesPerWord;
+
+ const _Sha512256();
+
+ @override
+ ByteConversionSink startChunkedConversion(Sink<Digest> sink) =>
+ ByteConversionSink.from(Sha512256Sink(sink));
+}
diff --git a/pkgs/crypto/lib/src/sha512_fastsinks.dart b/pkgs/crypto/lib/src/sha512_fastsinks.dart
new file mode 100644
index 0000000..c2a0f97
--- /dev/null
+++ b/pkgs/crypto/lib/src/sha512_fastsinks.dart
@@ -0,0 +1,277 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:typed_data';
+
+import 'digest.dart';
+import 'hash_sink.dart';
+
+abstract class _Sha64BitSink extends HashSink {
+ int get digestBytes;
+
+ @override
+ Uint32List get digest {
+ var unordered = _digest.buffer.asUint32List();
+ var ordered = Uint32List(digestBytes);
+ for (var i = 0; i < digestBytes; i++) {
+ ordered[i] = unordered[i + (i.isEven ? 1 : -1)];
+ }
+ return ordered;
+ }
+
+ // Initial value of the hash parts. First 64 bits of the fractional parts
+ // of the square roots of the ninth through sixteenth prime numbers.
+ final Uint64List _digest;
+
+ /// The sixteen words from the original chunk, extended to 64 words.
+ ///
+ /// This is an instance variable to avoid re-allocating, but its data isn't
+ /// used across invocations of [updateHash].
+ final _extended = Uint64List(80);
+
+ _Sha64BitSink(Sink<Digest> sink, this._digest)
+ : super(sink, 32, signatureBytes: 16);
+ // The following helper functions are taken directly from
+ // http://tools.ietf.org/html/rfc6234.
+
+ static int _rotr64(int n, int x) => _shr64(n, x) | (x << (64 - n));
+ static int _shr64(int n, int x) => (x >> n) & ~(-1 << (64 - n));
+
+ static int _ch(int x, int y, int z) => (x & y) ^ (~x & z);
+ static int _maj(int x, int y, int z) => (x & y) ^ (x & z) ^ (y & z);
+ static int _bsig0(int x) => _rotr64(28, x) ^ _rotr64(34, x) ^ _rotr64(39, x);
+ static int _bsig1(int x) => _rotr64(14, x) ^ _rotr64(18, x) ^ _rotr64(41, x);
+ static int _ssig0(int x) => _rotr64(1, x) ^ _rotr64(8, x) ^ _shr64(7, x);
+ static int _ssig1(int x) => _rotr64(19, x) ^ _rotr64(61, x) ^ _shr64(6, x);
+
+ @override
+ void updateHash(Uint32List chunk) {
+ assert(chunk.length == 32);
+
+ // Prepare message schedule.
+ for (var i = 0, x = 0; i < 32; i += 2, x++) {
+ _extended[x] = (chunk[i] << 32) | chunk[i + 1];
+ }
+
+ for (var t = 16; t < 80; t++) {
+ _extended[t] = _ssig1(_extended[t - 2]) +
+ _extended[t - 7] +
+ _ssig0(_extended[t - 15]) +
+ _extended[t - 16];
+ }
+
+ // Shuffle around the bits.
+ var a = _digest[0];
+ var b = _digest[1];
+ var c = _digest[2];
+ var d = _digest[3];
+ var e = _digest[4];
+ var f = _digest[5];
+ var g = _digest[6];
+ var h = _digest[7];
+
+ for (var i = 0; i < 80; i++) {
+ var temp1 = h + _bsig1(e) + _ch(e, f, g) + _noise64[i] + _extended[i];
+ var temp2 = _bsig0(a) + _maj(a, b, c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + temp1;
+ d = c;
+ c = b;
+ b = a;
+ a = temp1 + temp2;
+ }
+
+ // Update hash values after iteration.
+ _digest[0] += a;
+ _digest[1] += b;
+ _digest[2] += c;
+ _digest[3] += d;
+ _digest[4] += e;
+ _digest[5] += f;
+ _digest[6] += g;
+ _digest[7] += h;
+ }
+}
+
+/// The concrete implementation of `Sha384`.
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class Sha384Sink extends _Sha64BitSink {
+ @override
+ final digestBytes = 12;
+
+ Sha384Sink(Sink<Digest> sink)
+ : super(
+ sink,
+ Uint64List.fromList([
+ 0xcbbb9d5dc1059ed8,
+ 0x629a292a367cd507,
+ 0x9159015a3070dd17,
+ 0x152fecd8f70e5939,
+ 0x67332667ffc00b31,
+ 0x8eb44a8768581511,
+ 0xdb0c2e0d64f98fa7,
+ 0x47b5481dbefa4fa4,
+ ]));
+}
+
+/// The concrete implementation of `Sha512`.
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class Sha512Sink extends _Sha64BitSink {
+ @override
+ final digestBytes = 16;
+
+ Sha512Sink(Sink<Digest> sink)
+ : super(
+ sink,
+ Uint64List.fromList([
+ // Initial value of the hash parts. First 64 bits of the fractional
+ // parts of the square roots of the first eight prime numbers.
+ 0x6a09e667f3bcc908,
+ 0xbb67ae8584caa73b,
+ 0x3c6ef372fe94f82b,
+ 0xa54ff53a5f1d36f1,
+ 0x510e527fade682d1,
+ 0x9b05688c2b3e6c1f,
+ 0x1f83d9abfb41bd6b,
+ 0x5be0cd19137e2179,
+ ]),
+ );
+}
+
+/// The concrete implementation of [Sha512/224].
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class Sha512224Sink extends _Sha64BitSink {
+ @override
+ final digestBytes = 7;
+
+ Sha512224Sink(Sink<Digest> sink)
+ : super(
+ sink,
+ Uint64List.fromList([
+ // FIPS 180-4, Section 5.3.6.1
+ 0x8c3d37c819544da2,
+ 0x73e1996689dcd4d6,
+ 0x1dfab7ae32ff9c82,
+ 0x679dd514582f9fcf,
+ 0x0f6d2b697bd44da8,
+ 0x77e36f7304c48942,
+ 0x3f9d85a86a1d36c8,
+ 0x1112e6ad91d692a1,
+ ]));
+}
+
+/// The concrete implementation of [Sha512/256].
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class Sha512256Sink extends _Sha64BitSink {
+ @override
+ final digestBytes = 8;
+
+ Sha512256Sink(Sink<Digest> sink)
+ : super(
+ sink,
+ Uint64List.fromList([
+ // FIPS 180-4, Section 5.3.6.2
+ 0x22312194fc2bf72c,
+ 0x9f555fa3c84c64c2,
+ 0x2393b86b6f53b151,
+ 0x963877195940eabd,
+ 0x96283ee2a88effe3,
+ 0xbe5e1e2553863992,
+ 0x2b0199fc2c85b8aa,
+ 0x0eb72ddc81c52ca2,
+ ]));
+}
+
+final _noise64 = Uint64List.fromList([
+ 0x428a2f98d728ae22,
+ 0x7137449123ef65cd,
+ 0xb5c0fbcfec4d3b2f,
+ 0xe9b5dba58189dbbc,
+ 0x3956c25bf348b538,
+ 0x59f111f1b605d019,
+ 0x923f82a4af194f9b,
+ 0xab1c5ed5da6d8118,
+ 0xd807aa98a3030242,
+ 0x12835b0145706fbe,
+ 0x243185be4ee4b28c,
+ 0x550c7dc3d5ffb4e2,
+ 0x72be5d74f27b896f,
+ 0x80deb1fe3b1696b1,
+ 0x9bdc06a725c71235,
+ 0xc19bf174cf692694,
+ 0xe49b69c19ef14ad2,
+ 0xefbe4786384f25e3,
+ 0x0fc19dc68b8cd5b5,
+ 0x240ca1cc77ac9c65,
+ 0x2de92c6f592b0275,
+ 0x4a7484aa6ea6e483,
+ 0x5cb0a9dcbd41fbd4,
+ 0x76f988da831153b5,
+ 0x983e5152ee66dfab,
+ 0xa831c66d2db43210,
+ 0xb00327c898fb213f,
+ 0xbf597fc7beef0ee4,
+ 0xc6e00bf33da88fc2,
+ 0xd5a79147930aa725,
+ 0x06ca6351e003826f,
+ 0x142929670a0e6e70,
+ 0x27b70a8546d22ffc,
+ 0x2e1b21385c26c926,
+ 0x4d2c6dfc5ac42aed,
+ 0x53380d139d95b3df,
+ 0x650a73548baf63de,
+ 0x766a0abb3c77b2a8,
+ 0x81c2c92e47edaee6,
+ 0x92722c851482353b,
+ 0xa2bfe8a14cf10364,
+ 0xa81a664bbc423001,
+ 0xc24b8b70d0f89791,
+ 0xc76c51a30654be30,
+ 0xd192e819d6ef5218,
+ 0xd69906245565a910,
+ 0xf40e35855771202a,
+ 0x106aa07032bbd1b8,
+ 0x19a4c116b8d2d0c8,
+ 0x1e376c085141ab53,
+ 0x2748774cdf8eeb99,
+ 0x34b0bcb5e19b48a8,
+ 0x391c0cb3c5c95a63,
+ 0x4ed8aa4ae3418acb,
+ 0x5b9cca4f7763e373,
+ 0x682e6ff3d6b2b8a3,
+ 0x748f82ee5defb2fc,
+ 0x78a5636f43172f60,
+ 0x84c87814a1f0ab72,
+ 0x8cc702081a6439ec,
+ 0x90befffa23631e28,
+ 0xa4506cebde82bde9,
+ 0xbef9a3f7b2c67915,
+ 0xc67178f2e372532b,
+ 0xca273eceea26619c,
+ 0xd186b8c721c0c207,
+ 0xeada7dd6cde0eb1e,
+ 0xf57d4f7fee6ed178,
+ 0x06f067aa72176fba,
+ 0x0a637dc5a2c898a6,
+ 0x113f9804bef90dae,
+ 0x1b710b35131c471b,
+ 0x28db77f523047d84,
+ 0x32caab7b40c72493,
+ 0x3c9ebe0a15c9bebc,
+ 0x431d67c49c100d4c,
+ 0x4cc5d4becb3e42b6,
+ 0x597f299cfc657e2a,
+ 0x5fcb6fab3ad6faec,
+ 0x6c44198c4a475817,
+]);
diff --git a/pkgs/crypto/lib/src/sha512_slowsinks.dart b/pkgs/crypto/lib/src/sha512_slowsinks.dart
new file mode 100644
index 0000000..2dd64e0
--- /dev/null
+++ b/pkgs/crypto/lib/src/sha512_slowsinks.dart
@@ -0,0 +1,377 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:typed_data';
+
+import 'digest.dart';
+import 'hash_sink.dart';
+
+/// Data from a non-linear function that functions as reproducible noise.
+///
+/// [rfc]: https://tools.ietf.org/html/rfc6234#section-5.2
+final _noise32 = Uint32List.fromList([
+ 0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, //
+ 0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc,
+ 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019,
+ 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118,
+ 0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe,
+ 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2,
+ 0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1,
+ 0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694,
+ 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3,
+ 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65,
+ 0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483,
+ 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5,
+ 0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210,
+ 0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4,
+ 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725,
+ 0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70,
+ 0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926,
+ 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df,
+ 0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8,
+ 0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b,
+ 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001,
+ 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30,
+ 0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910,
+ 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8,
+ 0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53,
+ 0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8,
+ 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb,
+ 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3,
+ 0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60,
+ 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec,
+ 0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9,
+ 0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b,
+ 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207,
+ 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178,
+ 0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6,
+ 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b,
+ 0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493,
+ 0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c,
+ 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a,
+ 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817,
+]);
+
+abstract class _Sha64BitSink extends HashSink {
+ int get digestBytes;
+
+ @override
+ Uint32List get digest {
+ return Uint32List.view(_digest.buffer, 0, digestBytes);
+ }
+
+ // Initial value of the hash parts. First 64 bits of the fractional parts
+ // of the square roots of the ninth through sixteenth prime numbers.
+ final Uint32List _digest;
+
+ /// The sixteen words from the original chunk, extended to 64 words.
+ ///
+ /// This is an instance variable to avoid re-allocating, but its data isn't
+ /// used across invocations of [updateHash].
+ final _extended = Uint32List(160);
+
+ _Sha64BitSink(Sink<Digest> sink, this._digest)
+ : super(sink, 32, signatureBytes: 16);
+ // The following helper functions are taken directly from
+ // http://tools.ietf.org/html/rfc6234.
+
+ void _shr(
+ int bits, Uint32List word, int offset, Uint32List ret, int offsetR) {
+ ret[0 + offsetR] =
+ ((bits < 32) && (bits >= 0)) ? (word[0 + offset] >> bits) : 0;
+ ret[1 + offsetR] = (bits > 32)
+ ? (word[0 + offset] >> (bits - 32))
+ : (bits == 32)
+ ? word[0 + offset]
+ : (bits >= 0)
+ ? ((word[0 + offset] << (32 - bits)) |
+ (word[1 + offset] >> bits))
+ : 0;
+ }
+
+ void _shl(
+ int bits, Uint32List word, int offset, Uint32List ret, int offsetR) {
+ ret[0 + offsetR] = (bits > 32)
+ ? (word[1 + offset] << (bits - 32))
+ : (bits == 32)
+ ? word[1 + offset]
+ : (bits >= 0)
+ ? ((word[0 + offset] << bits) |
+ (word[1 + offset] >> (32 - bits)))
+ : 0;
+ ret[1 + offsetR] =
+ ((bits < 32) && (bits >= 0)) ? (word[1 + offset] << bits) : 0;
+ }
+
+ void _or(Uint32List word1, int offset1, Uint32List word2, int offset2,
+ Uint32List ret, int offsetR) {
+ ret[0 + offsetR] = word1[0 + offset1] | word2[0 + offset2];
+ ret[1 + offsetR] = word1[1 + offset1] | word2[1 + offset2];
+ }
+
+ void _xor(Uint32List word1, int offset1, Uint32List word2, int offset2,
+ Uint32List ret, int offsetR) {
+ ret[0 + offsetR] = word1[0 + offset1] ^ word2[0 + offset2];
+ ret[1 + offsetR] = word1[1 + offset1] ^ word2[1 + offset2];
+ }
+
+ void _add(Uint32List word1, int offset1, Uint32List word2, int offset2,
+ Uint32List ret, int offsetR) {
+ ret[1 + offsetR] = word1[1 + offset1] + word2[1 + offset2];
+ ret[0 + offsetR] = word1[0 + offset1] +
+ word2[0 + offset2] +
+ (ret[1 + offsetR] < word1[1 + offset1] ? 1 : 0);
+ }
+
+ void _addTo2(Uint32List word1, int offset1, Uint32List word2, int offset2) {
+ var addTemp = word1[1 + offset1];
+ word1[1 + offset1] += word2[1 + offset2];
+ word1[0 + offset1] +=
+ word2[0 + offset2] + (word1[1 + offset1] < addTemp ? 1 : 0);
+ }
+
+ static const _rotrIndex1 = 0;
+ static const _rotrIndex2 = _rotrIndex1 + 2;
+ static const _sigIndex1 = _rotrIndex2 + 2;
+ static const _sigIndex2 = _sigIndex1 + 2;
+ static const _sigIndex3 = _sigIndex2 + 2;
+ static const _sigIndex4 = _sigIndex3 + 2;
+ static const _aIndex = _sigIndex4 + 2;
+ static const _bIndex = _aIndex + 2;
+ static const _cIndex = _bIndex + 2;
+ static const _dIndex = _cIndex + 2;
+ static const _eIndex = _dIndex + 2;
+ static const _fIndex = _eIndex + 2;
+ static const _gIndex = _fIndex + 2;
+ static const _hIndex = _gIndex + 2;
+ static const _tmp1 = _hIndex + 2;
+ static const _tmp2 = _tmp1 + 2;
+ static const _tmp3 = _tmp2 + 2;
+ static const _tmp4 = _tmp3 + 2;
+ static const _tmp5 = _tmp4 + 2;
+ final _nums = Uint32List(12 + 16 + 10);
+
+ // SHA rotate ((word >> bits) | (word << (64-bits)))
+ void _rotr(
+ int bits, Uint32List word, int offset, Uint32List ret, int offsetR) {
+ _shr(bits, word, offset, _nums, _rotrIndex1);
+ _shl(64 - bits, word, offset, _nums, _rotrIndex2);
+ _or(_nums, _rotrIndex1, _nums, _rotrIndex2, ret, offsetR);
+ }
+
+ void _bsig0(Uint32List word, int offset, Uint32List ret, int offsetR) {
+ _rotr(28, word, offset, _nums, _sigIndex1);
+ _rotr(34, word, offset, _nums, _sigIndex2);
+ _rotr(39, word, offset, _nums, _sigIndex3);
+ _xor(_nums, _sigIndex2, _nums, _sigIndex3, _nums, _sigIndex4);
+ _xor(_nums, _sigIndex1, _nums, _sigIndex4, ret, offsetR);
+ }
+
+ void _bsig1(Uint32List word, int offset, Uint32List ret, int offsetR) {
+ _rotr(14, word, offset, _nums, _sigIndex1);
+ _rotr(18, word, offset, _nums, _sigIndex2);
+ _rotr(41, word, offset, _nums, _sigIndex3);
+ _xor(_nums, _sigIndex2, _nums, _sigIndex3, _nums, _sigIndex4);
+ _xor(_nums, _sigIndex1, _nums, _sigIndex4, ret, offsetR);
+ }
+
+ void _ssig0(Uint32List word, int offset, Uint32List ret, int offsetR) {
+ _rotr(1, word, offset, _nums, _sigIndex1);
+ _rotr(8, word, offset, _nums, _sigIndex2);
+ _shr(7, word, offset, _nums, _sigIndex3);
+ _xor(_nums, _sigIndex2, _nums, _sigIndex3, _nums, _sigIndex4);
+ _xor(_nums, _sigIndex1, _nums, _sigIndex4, ret, offsetR);
+ }
+
+ void _ssig1(Uint32List word, int offset, Uint32List ret, int offsetR) {
+ _rotr(19, word, offset, _nums, _sigIndex1);
+ _rotr(61, word, offset, _nums, _sigIndex2);
+ _shr(6, word, offset, _nums, _sigIndex3);
+ _xor(_nums, _sigIndex2, _nums, _sigIndex3, _nums, _sigIndex4);
+ _xor(_nums, _sigIndex1, _nums, _sigIndex4, ret, offsetR);
+ }
+
+ void _ch(Uint32List x, int offsetX, Uint32List y, int offsetY, Uint32List z,
+ int offsetZ, Uint32List ret, int offsetR) {
+ ret[0 + offsetR] =
+ (x[0 + offsetX] & (y[0 + offsetY] ^ z[0 + offsetZ])) ^ z[0 + offsetZ];
+ ret[1 + offsetR] =
+ (x[1 + offsetX] & (y[1 + offsetY] ^ z[1 + offsetZ])) ^ z[1 + offsetZ];
+ }
+
+ void _maj(Uint32List x, int offsetX, Uint32List y, int offsetY, Uint32List z,
+ int offsetZ, Uint32List ret, int offsetR) {
+ ret[0 + offsetR] = (x[0 + offsetX] & (y[0 + offsetY] | z[0 + offsetZ])) |
+ (y[0 + offsetY] & z[0 + offsetZ]);
+ ret[1 + offsetR] = (x[1 + offsetX] & (y[1 + offsetY] | z[1 + offsetZ])) |
+ (y[1 + offsetY] & z[1 + offsetZ]);
+ }
+
+ @override
+ void updateHash(Uint32List chunk) {
+ assert(chunk.length == 32);
+
+ // Prepare message schedule.
+ for (var i = 0; i < 32; i++) {
+ _extended[i] = chunk[i];
+ }
+
+ for (var i = 32; i < 160; i += 2) {
+ _ssig1(_extended, i - 2 * 2, _nums, _tmp1);
+ _add(_nums, _tmp1, _extended, i - 7 * 2, _nums, _tmp2);
+ _ssig0(_extended, i - 15 * 2, _nums, _tmp1);
+ _add(_nums, _tmp1, _extended, i - 16 * 2, _nums, _tmp3);
+ _add(_nums, _tmp2, _nums, _tmp3, _extended, i);
+ }
+
+ // Shuffle around the bits.
+ _nums.setRange(_aIndex, _hIndex + 2, _digest);
+
+ for (var i = 0; i < 160; i += 2) {
+ // temp1 = H + SHA512_SIGMA1(E) + SHA_Ch(E,F,G) + K[t] + W[t];
+ _bsig1(_nums, _eIndex, _nums, _tmp1);
+ _add(_nums, _hIndex, _nums, _tmp1, _nums, _tmp2);
+ _ch(_nums, _eIndex, _nums, _fIndex, _nums, _gIndex, _nums, _tmp3);
+ _add(_nums, _tmp2, _nums, _tmp3, _nums, _tmp4);
+ _add(_noise32, i, _extended, i, _nums, _tmp5);
+ _add(_nums, _tmp4, _nums, _tmp5, _nums, _tmp1);
+
+ // temp2 = SHA512_SIGMA0(A) + SHA_Maj(A,B,C);
+ _bsig0(_nums, _aIndex, _nums, _tmp3);
+ _maj(_nums, _aIndex, _nums, _bIndex, _nums, _cIndex, _nums, _tmp4);
+ _add(_nums, _tmp3, _nums, _tmp4, _nums, _tmp2);
+
+ _nums[_hIndex] = _nums[_gIndex];
+ _nums[_hIndex + 1] = _nums[_gIndex + 1];
+ _nums[_gIndex] = _nums[_fIndex];
+ _nums[_gIndex + 1] = _nums[_fIndex + 1];
+ _nums[_fIndex] = _nums[_eIndex];
+ _nums[_fIndex + 1] = _nums[_eIndex + 1];
+ _add(_nums, _dIndex, _nums, _tmp1, _nums, _eIndex);
+ _nums[_dIndex] = _nums[_cIndex];
+ _nums[_dIndex + 1] = _nums[_cIndex + 1];
+ _nums[_cIndex] = _nums[_bIndex];
+ _nums[_cIndex + 1] = _nums[_bIndex + 1];
+ _nums[_bIndex] = _nums[_aIndex];
+ _nums[_bIndex + 1] = _nums[_aIndex + 1];
+
+ _add(_nums, _tmp1, _nums, _tmp2, _nums, _aIndex);
+ }
+
+ // Update hash values after iteration.
+ _addTo2(_digest, 0, _nums, _aIndex);
+ _addTo2(_digest, 2, _nums, _bIndex);
+ _addTo2(_digest, 4, _nums, _cIndex);
+ _addTo2(_digest, 6, _nums, _dIndex);
+ _addTo2(_digest, 8, _nums, _eIndex);
+ _addTo2(_digest, 10, _nums, _fIndex);
+ _addTo2(_digest, 12, _nums, _gIndex);
+ _addTo2(_digest, 14, _nums, _hIndex);
+ }
+}
+
+/// The concrete implementation of `Sha384`.
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class Sha384Sink extends _Sha64BitSink {
+ @override
+ final digestBytes = 12;
+
+ Sha384Sink(Sink<Digest> sink)
+ : super(
+ sink,
+ Uint32List.fromList([
+ 0xcbbb9d5d,
+ 0xc1059ed8,
+ 0x629a292a,
+ 0x367cd507,
+ 0x9159015a,
+ 0x3070dd17,
+ 0x152fecd8,
+ 0xf70e5939,
+ 0x67332667,
+ 0xffc00b31,
+ 0x8eb44a87,
+ 0x68581511,
+ 0xdb0c2e0d,
+ 0x64f98fa7,
+ 0x47b5481d,
+ 0xbefa4fa4,
+ ]));
+}
+
+/// The concrete implementation of `Sha512`.
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class Sha512Sink extends _Sha64BitSink {
+ @override
+ final digestBytes = 16;
+
+ Sha512Sink(Sink<Digest> sink)
+ : super(
+ sink,
+ Uint32List.fromList([
+ // Initial value of the hash parts. First 64 bits of the fractional
+ // parts of the square roots of the first eight prime numbers.
+ 0x6a09e667, 0xf3bcc908,
+ 0xbb67ae85, 0x84caa73b,
+ 0x3c6ef372, 0xfe94f82b,
+ 0xa54ff53a, 0x5f1d36f1,
+ 0x510e527f, 0xade682d1,
+ 0x9b05688c, 0x2b3e6c1f,
+ 0x1f83d9ab, 0xfb41bd6b,
+ 0x5be0cd19, 0x137e2179,
+ ]),
+ );
+}
+
+/// The concrete implementation of [Sha512/224].
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class Sha512224Sink extends _Sha64BitSink {
+ @override
+ final digestBytes = 7;
+
+ Sha512224Sink(Sink<Digest> sink)
+ : super(
+ sink,
+ Uint32List.fromList([
+ // FIPS 180-4, Section 5.3.6.1
+ 0x8c3d37c8, 0x19544da2,
+ 0x73e19966, 0x89dcd4d6,
+ 0x1dfab7ae, 0x32ff9c82,
+ 0x679dd514, 0x582f9fcf,
+ 0x0f6d2b69, 0x7bd44da8,
+ 0x77e36f73, 0x04c48942,
+ 0x3f9d85a8, 0x6a1d36c8,
+ 0x1112e6ad, 0x91d692a1,
+ ]));
+}
+
+/// The concrete implementation of [Sha512/256].
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class Sha512256Sink extends _Sha64BitSink {
+ @override
+ final digestBytes = 8;
+
+ Sha512256Sink(Sink<Digest> sink)
+ : super(
+ sink,
+ Uint32List.fromList([
+ // FIPS 180-4, Section 5.3.6.2
+ 0x22312194, 0xfc2bf72c,
+ 0x9f555fa3, 0xc84c64c2,
+ 0x2393b86b, 0x6f53b151,
+ 0x96387719, 0x5940eabd,
+ 0x96283ee2, 0xa88effe3,
+ 0xbe5e1e25, 0x53863992,
+ 0x2b0199fc, 0x2c85b8aa,
+ 0x0eb72ddc, 0x81c52ca2,
+ ]));
+}
diff --git a/pkgs/crypto/lib/src/utils.dart b/pkgs/crypto/lib/src/utils.dart
new file mode 100644
index 0000000..9ac8efc
--- /dev/null
+++ b/pkgs/crypto/lib/src/utils.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A bitmask that limits an integer to 32 bits.
+const mask32 = 0xFFFFFFFF;
+
+/// The number of bits in a byte.
+const bitsPerByte = 8;
+
+/// The number of bytes in a 32-bit word.
+const bytesPerWord = 4;
+
+/// Adds [x] and [y] with 32-bit overflow semantics.
+int add32(int x, int y) => (x + y) & mask32;
+
+/// Bitwise rotates [val] to the left by [shift], obeying 32-bit overflow
+/// semantics.
+int rotl32(int val, int shift) {
+ var modShift = shift & 31;
+ return ((val << modShift) & mask32) | ((val & mask32) >> (32 - modShift));
+}
diff --git a/pkgs/crypto/pubspec.yaml b/pkgs/crypto/pubspec.yaml
new file mode 100644
index 0000000..7e74fa6
--- /dev/null
+++ b/pkgs/crypto/pubspec.yaml
@@ -0,0 +1,20 @@
+name: crypto
+version: 3.0.6
+description: Implementations of SHA, MD5, and HMAC cryptographic functions.
+repository: https://github.com/dart-lang/core/tree/main/pkgs/crypto
+issue_tracker: https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acrypto
+
+topics:
+ - crypto
+ - cryptography
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ typed_data: ^1.3.0
+
+dev_dependencies:
+ convert: ^3.0.0
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
diff --git a/pkgs/crypto/test/hmac_md5_test.dart b/pkgs/crypto/test/hmac_md5_test.dart
new file mode 100644
index 0000000..0cbcec0
--- /dev/null
+++ b/pkgs/crypto/test/hmac_md5_test.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:crypto/crypto.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('standard vector', () {
+ for (var i = 0; i < _inputs.length; i++) {
+ test(_macs[i], () {
+ expectHmacEquals(md5, bytesFromHexString(_inputs[i]),
+ bytesFromHexString(_keys[i]), _macs[i]);
+ });
+ }
+ });
+}
+
+// Data from http://tools.ietf.org/html/rfc2202.
+
+const List<String> _inputs = [
+ '4869205468657265',
+ '7768617420646f2079612077616e7420666f72206e6f7468696e673f',
+ 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
+ 'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd',
+ '546573742057697468205472756e636174696f6e',
+ '54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374',
+ '54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b657920616e64204c6172676572205468616e204f6e6520426c6f636b2d53697a652044617461',
+];
+
+const List<String> _keys = [
+ '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b',
+ '4a656665',
+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+ '0102030405060708090a0b0c0d0e0f10111213141516171819',
+ '0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c',
+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+];
+
+const List<String> _macs = [
+ '9294727a3638bb1c13f48ef8158bfc9d',
+ '750c783e6ab0b503eaa86e310a5db738',
+ '56be34521d144c88dbb8c733f0e8b3f6',
+ '697eaf0aca3a3aea3a75164746ffaa79',
+ '56461ef2342edc00f9bab995690efd4c',
+ '6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd',
+ '6f630fad67cda0ee1fb1f562db3aa53e',
+];
diff --git a/pkgs/crypto/test/hmac_sha1_test.dart b/pkgs/crypto/test/hmac_sha1_test.dart
new file mode 100644
index 0000000..c7dafa5
--- /dev/null
+++ b/pkgs/crypto/test/hmac_sha1_test.dart
@@ -0,0 +1,933 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:crypto/crypto.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('standard vector', () {
+ for (var i = 0; i < _inputs.length; i++) {
+ test(_macs[i], () {
+ expectHmacEquals(sha1, bytesFromHexString(_inputs[i]),
+ bytesFromHexString(_keys[i]), _macs[i]);
+ });
+ }
+ });
+}
+
+// Standard test vectors from:
+// http://csrc.nist.gov/groups/STM/cavp/documents/mac/hmactestvectors.zip
+
+const List<String> _inputs = [
+ 'fcd6d98bef45ed6850806e96f255fa0c8114b72873abe8f43c10bea7c1df706f10458e6d4e1c9201f057b8492fa10fe4b541d0fc9d41ef839acff1bc76e3fdfebf2235b5bd0347a9a6303e83152f9f8db941b1b94a8a1ce5c273b55dc94d99a171377969234134e7dad1ab4c8e46d18df4dc016764cf95a11ac4b491a2646be1',
+ 'd68b828a153f5198c005ee36c0af2ff92e84907517f01d9b7c7993469df5c21078fa356a8c9715ece2414be94e10e547f32cbb8d0582523ed3bb0066046e51722094aa44533d2c876e82db402fbb00a6c2f2cc3487973dfc1674463e81e42a39d9402941f39b5e126bafe864ea1648c0a5be0a912697a87e4f8eabf79cbf130e',
+ 'f84d0d813d2e9e779e8570bddbdf6fdc6baade5acb3c4cde1618c494d66d45d319e071fec88b89a8354699fbf325f05aea42d345aabc737d00ff1c69c746aeb9015f514927ae6548bd75b8992853fc79c40a78633285fd30ef191c832b0b9664d852142b019f18a05d9b3460246f7a83218a337b099ed43f0bec2daaa8c2e41d',
+ 'd6eb23c5ea87fd67b943928be0521823dc508acb2ad5f0fdac49e0844ffa4533eb6b5fd66bf00b692d774588aca9eb275c32c383d55cc05834e38155be051bcdc7d818afd3e0c0b8fae197e791f2263206d3fe770c80fbb5f806c67c6b969da232d857386a81a2bce8289090d85652aba3dc438f1769287bc25bb5e19ed6541a',
+ 'a64ec0d93360976b75f50ea532c3d501464a392c00aba572c9bd6977065ebb294007fbf282a43c3203a2ffec054941c0fd4cb919f49e5ba72d88201008f909e2261d62cdce30440f90955d2f2822f3eea5bf277bca2f77e6b42d87d7bdbb2180a1b77ad0dfafb7e962f6afd561f7f37484ca0cb948050316a4d52735ed4d0ae9',
+ '5f458657da5aec73d8aa5e348bedc6af487341593a0a741256222362912fff02514fc09e222d74d9ab251792e0a9636579e3e975a29b6169f45c3fb5a4d2871bfa77e171056ff0a48eafe0fd4a653ea353940d62d9ff16aa15497fdb7f5a9fbf41051158ebe707dd6892e1ff31ebff70c0d0d3a648fe3adda3320c5b8c8ff1f7',
+ '20100ed997ab74370607aeeb0bd2f64f6a56c7040d64fd8a498a380d638c8182531230f3c79f0c176bc2b52668903feb2a51201b677a4ce55ddc9eca5b1a7aaf8260b131cd52a4384f43adcfbca8ba332bcc3b291ac53f95b3a6d9494ef6c91b3661583ab0ae84c239f15d8d1002af4df42de1d72f2b1dc2d351b2314408b6ed',
+ '3223744302f481dd32a9d4d1ceaf72229b45f413a1e82d3ce70f0dde7e19c574c0842c8ada5f62d28802b37520fcbea7d24dd67e2ed6a804e60d1e8bd6f58440414eea035e08c97613fee95400e18105bf72a16f6af5cd0e5ee2ea473fdd5ff93de8745695d8fdf15a053d1775460563eb1d1c8d5e2ee383d7f639bbc2b99dc7',
+ 'fb091ddd95b100dfcf892d78e5e770d3a37b8c3885df803c1d6f0935b55b68f136fb65a84862942ebb35d76d26be2413cd3c8988c87d6d2362af189dc07476c6c33417762eb77bc70cf38d814c226dd6af187250e4d47007f1553617d4af5b516a5d3b3191d93c10896a569ba13dd2840fb851781f0b115090086c8b3a34a1fc',
+ '97f2769dc081f1fd7138ad61bd30743cd81a4565cf22a41a761a3544a2d489fc99cf384fc716303eb3664c09318f29aed81c35acb636080c43c6f8a294dae791d14a600de99be36584237c403a6e9a2602e11f43ed9db46814a75f53ce45573027ab17608ed6b178ceb9658d409772af3eb02cb3da1f4f36d00393debadd80e3',
+ '76a69cdd9ff87ee6b07ffe6d496c54560de1e9f64c061acbe059386a5445d3b84cf7385d206d3876cbcf2b8a040335c0aa7cc84f65526a358b98b92c40eaacdae2451b48a41b829578a702ec337fa8b3eb68f205a46d8f632c3367a64487db3800394e84712de4ab81af89791d0736979a4d6f02517f11bb8dd14ac1a844e93c',
+ '3658212a14b65ac3bd9e3d9039c631a94bb43c4e493877852a3abf05e1b5ae53ea04c92b225dfb21db9b43883040a99396ba76bab4e5a45f75d294b25bc7ffd216862f3555d26f49dc30c05bd6ebcdb96d5a2113996598273546139e588d7030e267ba0f551f9c83e7e51cd1d5cf8662f91da5219fc13925951fa6908111eab7',
+ 'fcd6d3ab67574d8f0bbf5ad14937966dbd4386a928e62a53ad0dd14a412b31405d20b7bdf55f1c67ae5039824cf31cb369c75b096deaa83dba81a639275afcd8b0d0a7ed6cef9486bfd96e72d068b5003d15100a0e19e432e8d2256c83676cbd5eaf4a42b24fdd73a423a0a9bee087dea0f74cb4f3bc03b99fc7f5ea3e9aab76',
+ 'c8f16efe636581b6ab7ab7f39426bd033ddccb8ec50d1b3160ef9f69aa7df3b33bbf91f17b4b4410b70cdfe875422e6305ca2de259a078dc17a203c8eb960b3e226f4c5975cc755f22c2d9a442db67ab565edc8f23d137a1c0bd6d53edb15f55a68909fdf8f0fcec14240eefa2fa50235721405dcaaa40c883c847d055d5d73f',
+ 'cad534c86629fc600b38138a7f3e1a701bc4bd1f865f96dac39a4eb46e31065e4280f53ddf3a52bfca5e74f0b667384802c4a3c78287c8458261ec0308cee9855a8dd0a4c053d2df8bc061f2569292aa8c19c6f72beb8943c7d8ba02d120ed8a19e40d2592db4665554621b8e926f13cc2ac6fd507f1a17c99e700da5090d915',
+ '96fa5619fac648843db788cb8e90dc6ffd6efe1332abf0815f0390ee73f56c7f916cd70cc09f3d23e436b350edaed29b4efec653b07ba20ae8f9f6e12733a406716def7a5157d518ca359fd3903db63f7940b8532e8dcb6d26133296d5c51e072043c6ed15b6b96ad9fb73dce1052f61657cfd9b12aa14b000986995e374818d',
+ '91f8ec848d6f811431cbdeee150b93af6f678be99c903f81fc38295503d57c228da212a672e7a6015b7b4361d487fcdea28cdea356a8234f2215a89becf2a23ca1468c0bcc42646367c616caf02739d4c030f945996654767e908afac777ce8074eb42fbc2062201fcb53f719473b0597258c4178c533bbeb7b4b5bbbced6ab8',
+ '5a529114ba6bdab69bada5e8916fb6eb222c71256f919dd117d369f65846ac95772c712762cab34795c265ab3a9cb65894a692169dfe6c22eeed3b24e076c260f12f1530695059b23d0acbbe331a041b479d7bf24d264b82d90e36165c0bea348f048418152453615c2ede09c410289a03ba329fc830c2599ede63b4132dad79',
+ 'f6d9565ef97ea11748689e263f52b4af880ff5c8ed1295226a34a1ec87b2edf4e5754f1016970abcb1228d04a61b5ea5d0bf516fc90cfded02837048132d22694fdc285e9cb3aaff82e897d181c9972aa8fd4296630d8f7a95238ff7e6115b115f944b1134da6827e04324547765498738523007621d33104a9a64c1a9668036',
+ '68de2a68bd4215ac21bfe2b6f0d26ffd90d4ffc9f972dd47745e43dda24479bbc10041b32b0e734a1f41e50fc4b88d2b6b0fea3a15d29f5935376280b70c141340ee31b3b8bc6b5a064b92a71a5bb77631ca91b45408207222cb8f37d0045f9b6e11c2116c3445055c44b227f9a23506696fbde0bffca5b8c48294aaf714a27c',
+ 'e1db8f7bcc0e5c22eea3e8dce39ac250c8681d3095f8c861adf0605cb435c4d4a1b1c99914542fbce958d4f40dca28409046e1cefc02f01ce60db35dc2d96c1efcf8f2294423a6a92980a990e9254c3687d8c8421f1830ce7762a3c6d6adc691193771f40383a933d5a2cf791eb31679d5a63b56a54570c08874996197b7ba77',
+ '285d7249ef30bf4b6e5f6bdc3cba5570c77f115de0d08aee7a63ecb2ae7cc11a03185a43ed6b7011938d0b7dd571a3308e1685501601799a0ceaa2b152b6a5b558a50e189ecdefad74c7c90205a8b0f09332ab70044c5ab09eb0db670fe4ed65b06b566e0a3c83489a736f13d147c6d95f3c4966b199745ab81d5e7cedeee251',
+ '2b7e03680c9ca6c759b6929383cadf567e4e38dd7216313cb477db12f4ad970eb87a27b209100b576b310a7213950f15558c36b95ce4273a1d0da3238d7b5c2c124c0a01382bbb45a6746ad75098d454eec487ddacbd3c1a230f667e88660bcd233cd3dc03b45f99f1c6db4aa29dd71a313d52d1cc6918e3adc44fac4b364cfa',
+ '5988c794c1f1e85d23d65be040c0129bb8a6bbccd86c3b1eb3a9588774adb571f2c3041885b37733198b77d6809f99970dcfcef05e08dae4790e07e51b781af64cfc860d37ece0bb3901930e3858d5b736bad96825204680fd76e9ea0da0a6428ebbb53a7ea50b3dacbf15520ff1ac425bef46fdd6bb693a686c665ef22d439f',
+ 'e8bfc5c09ec4807319d8f7369556e7654e981639e8c5dd3f0feae3085b4d2b2276fe514880ae10d6b2c4088042aebe428775e59a5e95dcf6cc0b7768e5af02a1ecc4831dbbce409b65a381d01bc5975c4cef1dfd10ee7e03c7b2b804fda55fd0923ce4a717cb17aa7a9deb90e644799ae52e48c9c879cc4e48082c426dd74997',
+ '7d70cff8df77770eaf0ce671b7a15daf5bdd75482ae15812b3cf30dc9a8de052ebc6f321ad32d15bbb18391ccf11eb6ee00ea56aae9c51a09b677db9bcfd0b5b30d52a4db09085dc687eba7d05640db3107d5e337abe5847785eec709196fd4ff4a65dc51018f95a5f4850db82242a47933186edb7cfd4cef2bd644840df1ff6',
+ 'f9598e9f4ece159beb897317f625a6a708e9aaeb8e9df706709c4c52f12bab53d709a4e9cb48d7c9025ab52d1d6f86cb4effb004bda2365f2a287f35d3e659ae984e3dec5dc3d585b0abbb37abc584d71cbcfd8be4fdb4399dc6ba3f8080a865854fe00fcbe715b83ba10e9b69cea6b3ba4b18e6cc56797e129f86d8bfa2a060',
+ '0f80ccfe5ade386b40e43f48136aedbe69849330274b761edee1c44a5bafcc1979f16d3b3a75cf8e169f524093b1c4351649d7a8f92cd214dd41865542e1840a554e8d3f08804a4968283df02ceff8d489fe8d094ec445052cf395bc55cc4d094a9d1350ed881062de85e9a004aaf1646aab9d9c4d9d38b873ffd7c7befa90dc',
+ '49867dfd015a50df8c676141eeef02fa2c347515bb25028d393d47555ba9d09b27a9e74e6338adde4def6a438c272240675e69e935dc776314957febde523d19590ccf66ae98c5ed1d8a7b6eee53a798abac2e888c383c8d3364932e9993236e4978db4eccc2c09464ff3ccbfdbab88b60e76dfaaa827693fc722a2675b3aa20',
+ '204cdf0f384280e3d55f8dd010e88666080d2d722a1ce7cfaff5647f65be82fab3d86fc6d7110e48731b9dda483d941e4148d091b3cdf063e38d0086c9315505133bb7976d3dc6740048966738a89d24cbcecfddf78e07100b8ba9a328ef8532495fffa8812e6d0c84d0c19e69926823ae89727d7dc8f27e2dd6a8fe0c60dd2b',
+ '44c7cc06ad290f3a54a970b640014cb5d1e6182352459901cdcd570c23ad4f995b9fe8c43b2528c9151228b2e44dc53398d299d2adf92a4a02fb6032e9b23dda7aa0c8762e334a7ea947bd54d6ed8228396b52198184779c5df93c22914fa2f549d35463addcdd1fb55019e43f69e95b5fb92b3ff66ceabf86ced124440de6b3',
+ '787fdaa90a2de3937e7942e6711f165a89b9e077fe322cab597d749a7c8741b5e36a930e29e3836ace0627983730b602f63eec824cfcb077ece0f51702f9de0774222529687bbdb5061ab68b7ffd62c74e43b696be9cf249acff85a88e9b2a89b40f58a1ceddd999af1cb864506e61d11832045c5afb3a4a2040ebf527556f64',
+ 'f9a9c16e3a4beff0d36430c0e7e1d6bd68349498d240d8dc19755a2cdf3cf5cceb95b764d7fe340008981f5ae4851b5c3e94cee1152037bc7f3542fbe0f59a6d5f3abf619b7d58b199f7caff0205093f8bd1af75b42f4bc0b5c5fb98b56f3d543ee202efee8f040b6fca5a36a92b496d35345ede1535b9f2a36dac8bc872858b',
+ 'b949df3b02871bea0976873a9c76942ac934ce63ac2956d2856492970d8a231e0b1b178b22f6605ced2085494ec1986f026f68ae79aff750e5b92feb927cd08875e2ad04075518b754829b544e5de910686513076029ffdb5c0b179e39443ef22028086e5aab2a4465252f2147526d55229d3834099e55bc12e1b178ace953a3',
+ '850d673723789c780040620ad945ece61850a94f41efc64c8c81f45bd48d6b64af582eecdfb6918be920f9a00307e4433368297bb6a180b19f834465c0a87820cd0609aabfc5527c774ee578a4a589d8e6f87f6534780ae97b672ee68772b78827427dd98c4ee734f3f3aefc84c6e38d79293473821c6bdb68563746f1952f85',
+ 'b4c30b451325a9621e258a5d91de6dcb421cfe7957c1a7f5b667aa50bd466d23345814d07fbc550a185988983dc3fe55e662947cfad18822c2848b049eae1783f76102ed74f754fe71b256a7ad9feb0d42c023d5db690e9f21ebced07670f095e626fd255aa04b460f791912473adbfb3f7dd30d6053e173b9e49c3dad55a160',
+ '487ee933a49275727c8e36588e4c68c295a5516ab441c85b18aef8a9dab0625e22d821b792587291e216731ec7ff2bdc1a9ecbc836ed33cfa26bb885f06e2519e4bbff89d9540e12619118eb2c72f0322b34b027f422429869ae259c94c06d84d64e0c0f412d51dd4227ae26834dbeac0f8e86eeb889fc9fb6a0c556904e4387',
+ '9c3a8524f8d6d9ec907be803baefee0aa08b74ad4ff60f860a334a3ee4dee1f68eb230e56d4fea42ef3a0e642026172878727493f7f237b875f211dc33787ed9b5ca3dc0d43003c20ffb705122c64282dafcc9b6279b9b79733788aa3241d0ddba8994fd55028b3695c5f611e859d6e16c325c5f0577a191ac0997f00ac040c9',
+ 'f1f9c895ab63fcdd69aed763d998a788e92ddb5294477313fc56b545ba5d22b9723da8f1aa3619cadcabdc5dc925e328119bdc6901f1accbacbe19443d52c63e8bf865f5ee78282052e078d38984eaa4e6446f0d070dcb11f2a34822649dab4365b1676a20311128f2d6148bc1bda6448faffa054ea5b72df68baaa7d645b70f',
+ '5b1a6754c3c30cc29d041779325922781454897c9c3f7cc69703521e3d49201863de8b96f15cda8e9507500eb9f5b87db37241233ca28cec2468046844876e17b307c0e43ddb37ef10c0a48fb96807984fd85ed9ee0fbfe967e8a524364188f0b55db0458f874a6c76f8bc0619fb3651504f89a79acd3d47ca4add58fdbf962b',
+ '434a42273f11fc06bc8eed402450f1915399d7e0a71c12205605b174053a929696e0d2794122872de62db204a17f6ff3a0626f3a31b3a8471fe84bd83f52f761469e2caddda202c7f8571b1b6321d6d99d57c59aeaff6246a4d9fd35d2a0f994fc8c380b3d1bd49c991110cf91bd8e0cf57fc248fa87a6e48cdfafd1e5ac00f9',
+ 'f753f3e9b4bd1895a259492ba160713f00ac8e24dbbfab0da7070e720b61b2b6f1dbf806debe99847eccdfa584c615d7b1313c68315affa32e98e93ca0d1d6ee623fa7628b743a53fb9c9af0340372816cd7c84ee02ee7bc6a4a9dba561ca75b72086ac464e8e4494053e1d35a1f728559249b9f8d434ca283a892b5d64b0f47',
+ 'c5ff34dd398c10fc020277ab85050c51a1c4d238887e9b34cd46c386be031dfff3ba2e6927109922470adb0ac918389f3f52f5672c01c88f16618dd1dca53a9b4a3c156deb5325821e9be6b46c4c419a196abaf3f947ec47854932cb2eeda886f20c52b22c5d9a65b03c007017a90d87589488a39958eda544851b3c5ce24d08',
+ '5e09b42139c3e0c709527f4f86d73697aabcdbec1d518accf1b7f6f08ffefe8af18a81cb12bb72a8a3cd2fde00fc0e3362ec39ff5649bdec6eaaddfa36bcacc6699cdb0b6584cf69ddaaf665ce655cb2b49279affd364e30be65b081a562e3a82f076aeb1a671e921eb37eeed85a469a07744301fa61652049ad168ec437cab9',
+ '6ed7bb6653ef66ce21b7ba0ee616d07114c64d9228642b158ac3bc94b486ebdc97eec65a3af039d0a58b1c4cfd58715bf063e67a5439a2cd0a423d14295110da587ab0ef7c24b519945ec007e077bc8649c863f8fdd504015a9584830d0da4cd7b24810f60b26111b5daac25d89a395be7a0cbf36c5fdc18406399cba9e12d1d',
+ 'a3ce8899df1022e8d2d539b47bf0e309c66f84095e21438ec355bf119ce5fdcb4e73a619cdf36f25b369d8c38ff419997f0c59830108223606e31223483fd39edeaa4d3f0d21198862d239c9fd26074130ff6c86493f5227ab895c8f244bd42c7afce5d147a20a590798c68e708e964902d124dadecdbda9dbd0051ed710e9bf',
+ '52b113614b80b970510f65a25d46edc023d9c7b8e7ca7c41923059c205366870ad669fb7572856dc4685ffe0833111a775c9455ab1590509132121950e99c5cd40b2a8d74a5f85d2de54cfb91a0da18a1413f4a8b67b147eccaf55665b7101c9341c9687ca2d2e9941033ff5c7e384b1273f3b6c9b3891eae2615bfe93c606ad',
+ '9f3360cf8f5465c7d24d7cbd7bef00315cd4f4ac29f245f6db714e8853baa14440d1056442e4bbb1502406f557d3eab2239e3314832eb925a8fae340cf5f6ac820f25f19d51570bf9ec867e744c2f3128dc1ab11611e502d2aa452a681a2965f063f77d78f0e0b5b86e2a77a8ce4a5ba62e264890aea91762918a5a1b0acaf70',
+ 'f5a07e3741f03174c6efcb1f9f186d1f233b367073c56e814f4204db2e203b048db6a0a387853fe4a6bd161ef903cab46671993942de90d71f60fef1e5102807250d3edaa9c48ed1506ef89c19d9a2177d6ced710266a78d0d6682a8f730c43d64ae4125d03586036b0a58df27255d110f341861dae31b6cc05b774a8c08786d',
+ 'da82641c0e59bfabc0618cd5cfcec107050ca4c1ed4b3b3fe93b04587f14e7a6f4da69e71cdf22a37089711061556e32ec1c20466f96f161bb1c5e556ab2f3d4734477d8fb3064416e059ac0cf8a53f54c035ad416af784d6f952f2c0581ab3e7e49f6b554546bcde35d6db0c07559974d47b8338aa0ba4b2e2fe0a6f789f82b',
+ '1a40e896d0c0c13e7824c3ef86e02355feb629ea887ce4d2c71f1d02e7e889a875fe42c7742d7822ade5645c46867e5d96daf0f838e34aca5ed87765686af0aeb64b2f83baf167a1519872c553860b1268923db31ee71bc13906b2674b0a3c4484309710ca96f5830c43d472d468313c1ce5f864630fc07f00b1b551b551d533',
+ '5935a870229c7251fcd0c5c6956144f251ab2a39d74de951d0dc119cebd872b525de854947200828b013e99b546765f9053c7175f293593a6d02a7baf1ad46426371e7d29862a42d1878e32c21857e57ef6a21b63b8bf3e502807867870eb63c9b5596b61c4a8e88bc687d2003a3d637989e01a6bc1dfe7b17bd4c4cb7e309cb',
+ 'eb5de69eb1371bfce00ab629a1362f0d4885af7a71f9c90f4ec9655d3fa6fc49a3420bb1ef13c153fd55fbeaa64e739992d5348d4f1552dfa18fd7b7195e00b7e9bfaa97f7d0070c309895ef1f48519bbec028978c55ae75dfd212f97cbc527e65dbab96f2f554f123dd6b8035ad30d9734f71de4f424599b19afd6b8f495866',
+ '10ca186baa79d9029eb618a2e5a636b9893b30e20b062258034c0ab1065bcfc9cc1e82fc92f0e398beae2791c210f8774239bea6798c1dbdd9c2be51f13953e2948fd50d387010049cac623cae8dc065ab67f99f88703feb91d2e3df50ff609fb0459b0862a2692e80d9520970c5956b0cee6b35ff5a90cb72a600c5e955fee8',
+ '5369745bbccbba88780ed2e2cc2d57e2591d02b5aa0cd59d0ae79995981e8b349dab53d31c5135f2ab218bd88243737ad2f3c59e58ca4840313f2535f06d9b0eee17f53fe1e9b981b000237486add1892676c01f7e5e77ec7e67829f2a5422c3eeb343e7321baefc2fb380fe01f3dbd7fdafdb804451cc6998669a1b6f5c881c',
+ 'a413ed98dd6e0901b1074381e1a90d59fbb60e2282bd6706494f3a2f200f6d80b209ab83ae45aca3259bb79c34c8652fe2c2a71a4b490a47ffbf3a44a539c5f3e4d622838350f29eced085e43c07a099507a7e9abd1d1496cd249a7a0316462d00235b7ea3b7625b744fb743438c48fd0c859a8b1e620d5a7c2760bb84cd7797',
+ '25aee305cda093a71094bc5ca6f570fbd67fcb4239f3d724c00fad64f8bddd638d8b10370e5becfcef5b386fd43841b90d8f7c885ca56c64ff57c641ea54d4505589171b76dd30d1901f01de2c3c0fbfa6b62a15ec5151f88310d08dcb5fabdb83923fda8f8e27cdf9c65dd2376aa1b8acda1f1071614c875420117321482bab',
+ '9d31b168ce6ec3184d7c36243acb4e1404d81dfd82f73f603f4fc84f15267bd1fd5f3d882540c9914379a4ac2a62549d9a85cdd25d5c2c458f5ca7a43e32c4b0334ccae30e9b75559997eee05684fa825af472045e8ef3d9140dd649b78c63cfe60041bfb206312bf6dffd08e7b8aa8deb2ff5dcaf14fee4736c3e86a9bcbef6',
+ 'a785aba75e6829f93f7a141c715763b64effeed00ce131899d394c0bd39c4fbfc8d1b5bd7de32e87c174a2f6555472744d53016cb95373ff85a1b4f99e85bc035617121a0a558f3f02736570987260d89df46b43f84f55d490e0d5fa6da2cca01afecba44de5d58bc91d667384d8b348058b343b11fd6070869fb8f7871b06fe',
+ 'edb2ba099961d38fd0a0a6a235d61271cb4d493b64d9de135cbb1fe086c4a4a767be280da2079817b47f6a35e1a4307f6efc6d3e11b4a7aea686bd0223e07ba9ce426cd0aee7ef283fa98de96a1f8a17b308ba04b5ec9616cb008fca114ba3f98b072d5aa34a0149d9e5b8c6b68c49c10138da9536cad5d234f13d3f364d431f',
+ '1948c7120a0618c544a39e5957408b89220ae398ec053039b00978adb70a6c2b6c9ce2846db58507deb5cba202a5284b0cbc829e3228e4c8040b76a3fcc3ad22566ebff021ad5a5497a99558aa54272adff2d6c25fd733c54c7285aa518a031b7dc8469e5176fd741786e3c176d6eeee44b2c94c9b9b85fa2f468c08dee8d6dc',
+ '44c9bf3ae8f14cc9d6935deda3c24de69c67f0885a87c89996c47c7b3e27850ac71c2bc8c6beb038ba55cb872c1d5871fb4a4d63f148f0dd9947471b55f7d0f4ab907302e016b503c8db2e7fdc453dac8dd1fa8ed8586c621b92fd3d27d82af1962e7f305f80c3f4a72c701ddac1665cfb06df51383fa6f0c2ab8429db51fbc8',
+ 'cb2a072d74a5749481030ee46edce28c471ef412c8a4814ac40b87cbc3c188a3ef5e8a4a313862d59731326cf9d431fedca1aa3396a448a3b34d9045987baf2a66da766b216fa36012716212695b13f3273f4ecd3b5d24f9ebf4a8d17658af67f845d3788d73be9bb96aa5be089812d3f1a1e7c700f6a0b435a9d857a7800ec4',
+ 'c7f4612dc47f7ce6b499af0a51e4a3ecb2ef40251cb420351c65436dd268040c90a04ba8a4ee05cf71f7d1efc528fc7366f8b02fee6d68fed9e2a7a9dd07ea0b7a29db73d1b4c74ab9f652f610256afd4fa4796e6182df7db6449f6d93e458b3ac197858f4d9ac9fb41c9be8dae4d3d4947a03aa1efa6cf9d911927f9c06374a',
+ '4c259ed53a1faa09d9cf2a1454cc2e5acfb3ab8893bfc3ca6b9a473f4d737baa3d51196a6fa798acac28addff6dc13686f74889777db18da150d9d31982c87e27ed1d96e94a074c35f1f98b3bbc8a8a5c25c2d8bef7b1e1483725f222854877ed54ce6cbf131c7b8bb5bf27ae9b5757a8f14a44a43c75fde7f7093f9471203e5',
+ '1b8747af6d82c61f98ccc3d79c7acebe18bd1fb5b0ba1f15b1952b58f8cf941610d3ea349acb7a58f2b8159f0fc21393abcc9857a44c1625a35a13fbfb072d90d4ef5b8d881275fa4ddff7f6159202acb2c0a3823e305893baedd060f599f3c2af042224fffec0eef269f1447592a1f175c1c99e440eed483f77eaf1ae30ee95',
+ '4617b323bc286d7680df7eddc101aecfa46c6dcc394367a1ae4b5ae8c29524ce7d5e21191e33b369565922bdb36ba73a5f45c3280a21d53e2500ec1f514cda2417bb8a5cd97693d1087b0c0d983fa3ddb198e955a8dbf0142d4118cac69026f77cf796f5d3393338000ee4d557c6c941032f865bf9b9dfad2fd886ef08aa30cd',
+ 'a0cfcc6559f2bdc8d0efe0519e8d311d3af585bfbf666d90ef2b5d4678ca0ec9777f20423be804744b02194faa5415c2596aa7d21e855be98491bd702357c19f21f46294f98a8aa37b3532ee1541ca35509adbef9d83eb99528ba14ef0bd2998a718da861c3f16fe6971725565ba171d276b693ec5c9e6496102500867650e5a',
+ '2fa33c03ada40c598f8800e017dc802a1c6a3ff0ff5ecb58e1a7637713a00815cef0d6b125af95c537ca8c4ca9a89580540d77e83a3f6f92bf68109e163c4efcf9dbd5759df99ff0e53cc5eed6e595584bb3e67ae904a84f563ebfffa66d12a6162ede57fdcb5161ffa754d084dda837682434adf5f69d160ef118a4ac7d7c9d',
+ '0f546834a313fe3981ef450f3e3b16bc184e3d6bdad57e65006ed63c1c72024978114659fda567a45340f9ff4a87e15279c4124b25369a5464ace2c381523151a3ca73ceaa7e39135a350037bbe5b606bfc87aae26b2a4bc9fa205473097706bd7a578fa72477c6ddcf7e12159fc9fc03484fffca6f2a384fa79c630efeac57f',
+ '6fb3ec66f9eb070a719bebbe708b93a65b201b78e2d26d8cccdf1c33f741904a9ade640fce000c334d04bb30795683dca09dbf3e7e32aea103d760e857a6d6211c47655df3665bbe4164e5d1334d301eff0bcffe6dd95dad97fa63a0ecaa7b197b55b6f86f073cd4d524324aa659e19501d2145fb8adc1d70eafec04bf36c959',
+ '1d7f6833333d6f99cc4de86dcb1a668af36966074c31d4adc9acd0ae27aeb19318364a77a1426d73c1e8ae5953a369a535eb07b0aa087c27fd2714bc68ae701b33cdcb202055834707ced464bec4e6943b610a73fd41408fa881fe1def192cebb66c7396781eb7fde726e2f5d324e43f4df4f8b70c8328cd10e113398498eeeb',
+ '3f5fe1a8a13c8357149f68bce47360bd6e73c98932ec4a7d2ac4c5495bbb864ea9f1c14befa93b394f4c4773c7b1f41a059b85b87d832123b898cca5ef059659d87212d8c0cd0a15da4a7186d7a89985b6b7a7f5de1743286a429400c4cc6b5575eabe973b3259b55ca1d03d3be2b8c429cd16887d2f1854e7c903a4019b6d0a',
+ 'a04d563eec5c909dee3f6fa8133c70f862d46333b9f5cade59718273a4afa5b426a1ae3ed3f5de618f90df2ff438a8d34f90a025eb4a067b939890c152e352cc7dc0e2ebf320babfa4c6dd4d50ffbe52918d5dd61ce4b30444995039c017435bad943a6cd743ea5f34cbb12ab1f97a1c31b1e271d32b9924745c0a0476b13e0a',
+ 'bec8d88f65e49567f23cc953d9ca9bad9a5ab34f38334c55edf98a251cd20ead87c8c9ecc26f0db4e8c7eaae8c63b79ef2cbefe87f203f546ffedc0ec6a61af1895d3b042d0f8445503897a6a705fc5638b60141c946c4da984e8e184c2762be2c4ed6e08f0d22a39358774412f6925cd2e19062fcee0471d0b0474b969a0f9f',
+ 'd199875bb7071c434ab236e6d10f8405978fca259f7c34939424eaa6ff3ae444bd7900a7af8a5161b328ba9ed382bcaabde18db3738a6acf44e62d41fbe022f8568f1758ba15b23d24c7083d638e6a2e858c82e88f03a04c71734e8638032a8e8622f5f53f6ee7de86d5454be8fa369ad6dad34f59af7d13011573fd1f6ba311',
+ 'ac76a7db964e9fad2f98c18c06f929f23b6217ee35ef4525920f771764e653a39aef73cdbce6b9c0dce5e20fc9cd5e4085e75f8bf9cb31dfe881c92622e7a0cafa52c278f9782124d48e304d9cadad82357abe250906406ffdf35cb4a5d95be8b3e7bb63b6ce82e101dad2cde862bebf33635c43cc681bdcbbad574854832b06',
+ 'bf465c887060c762cccd43e4a65c76e9fd685f44e7fdea03c83dc2f5c702676983c5803901bf7207ea4d31c7f399577d9c7773481d8da3a09db765dca6aaaaf7d6d72c93d792023e917371f59dfc06e6fd7de17a0b355493b0baad13d69b4f9d2043089fd8209e902905ab768ecdabac8a4254e29a3d2665680e42a1411d7fe4',
+ '635a508c6c44c1eb78e3dbf5961acab6ee7d9b92a8aa473609dcedcedfbd5f78207ce0f9ce202cb01d1cb9c8d8233db1013d70d0b81b13755da7310ef9e0a59bdae5dc627e4fdce4b3c4850ffbca17b535d8f53d7ab3a99946f82778d8f456bcdbbccc2e457ad9708006c834c8b661acd476b341b81b10880af4587243a27bc3',
+ '6349e3265d2630d1e14bea680d342ce9f76aefb789027f3d8f6630d50e584ce8d73351565d745918c47ada243a8a8f908a16b6fbee3f7c292598b6edc62dd14cd4c40cdf9262e4799911d00a27e12fc3ba2d7f7bde1fcf5243767794128706e081827c89a6f7ba3c889936e37c41f3caaf36b100ffab61010f89db919a6fd3eb',
+ '64f3d0ce82097d36385b6717fe155d0fc5ed85bf80a1fed9e3a1c37a6b08d3bb9ed18f839448639fb6bea814c681c9b3200ca5ef3f7a35ec82416fd8301c6a7ebb49c21841f53e6558f5b0fc0bb61de020771e549db586f18ae745f5f76c8dde41c2333892f857b3a7664778d69ba1bd4f97b897a23b391081fd0f7ac7e08303',
+ '9c84d18b6ec339247482cc3ee52a1bbd6bd4ae918216912d211c103a9dfbbe8dca43bc5763d3379cacf233e7559b873ba217294cc9d2acef9c6707d067fd98631cd6691dad25b1e3ba209ec36c5751e2a1442bb5492347740f0447cc3d1e54d5d96660431460aee0e635953af2078198af813a33c9b269a3c51b5898e506f9ca',
+ '8436228556a7569274bb14ad6271abfb82391e809363cb3877d84a63390898204e23753d1b8c0a4eb88bcffcf442aca099e25f11f11e1db988e07cef343b908153a2548f54574ca0792569efda522d06aed00f8ec6b321665ae8f0f20823acb61a19892308f064b03df3aa2d1e8b7654496af9a21a0a1f6574566f15bea734e7',
+ 'e01e4133819800b30445984a5f12d6e3e1e29e1bc6d428a209c569e37917cee70fb030767f4505800dd8d3bca27feb8f1f68532ff11a0408e6fd555f3e1db835062ba46ea1c5d232a8f6ac94f4010371f85a009b54f65d37a8c4d464a67cd81e6c978461109ed1917ca80b197c1f865315c28da819f09bf8f823ce3bd9bb9869',
+ '99d4482daecfeeb8d44226a39f85b42f9513fdc2d798c698044c3eb55a803f1e1e76d1483e76f0d1361e8f6e30fadc256f55c6bced4ebc71432eb8ebcaf87d7100421d5a2d44bdc4462f9c8911c0526f8a14569f86bec35996175ce52ed5cdcd06df3449c160dffbcd1a57dc8afe9e77aef9b655e81062b8c3af318cce3eb79a',
+ 'd83c04027297bacaa0ba8bedb834169fea05aef6c60e00fcfec5f6036e2ddc385906c27bf640216e2bb6c1cc9819d9fdd72a79e7022d2506769ac2bfd715b7f155a04cce2d1055e972bd158f0d7e5d5b03d5f405f6663b7befae11335af1f5bf52746aa21feda062fd3850de1f4be8e2f46ce8f9a9a28c82ef69ab06fea9dfc9',
+ '0e9b073a31c8fd215af1d8d0ce54ac9ae109036e1794250988b7966a898adf8688cd913e387c888eefa46d074c767e7f1c9992077ec5571d468edf23a07d5b10f665266613f405648889ad7c4e458507ae65ae385ecf414eedead70e60b34f711e0ecb9a0959fc0aee47a0171fec489a5e145fe9fdd968054475871413544311',
+ '86c7c82bba165b31ad74d92ba22a3bbff926807e5396f414f7b6b2c275e6680f89005aba41e8aaf26265d6c9092f82e78e49787bad90ed78e89506fd27a89a14a2353aa000546e91c09b425ad93601a59d3a4145e3371f6c650dcc1e670049e59a0e6ec73f7f31758fbf25c55b694162f0a4e3c23db2145938c60e0d7d16fce9',
+ 'a64ad96be224dceef6563f18c63fb7555ad926933f8e1cb02a4d9e2edfdc272e5170ed9c0b7b65a7cec509747cbe5913341320b2bf7ff8102be41035b59a2d61ed06ef42146f5669c90e84ffe564c5b4a3d1ccf90461406f71e9779fa25381ebc03668c4c6aab61e2d5a3821c8da0222ed3bb3d1d5ddfab4458559d46eaf29b6',
+ '22eeed3b24e076c260f12f1530695059b23d0acbbe331a041b479d7bf24d264b82d90e36165c0bea348f048418152453615c2ede09c410289a03ba329fc830c2599ede63b4132dad791a53c6c5af6f29bab9d5a67434a6aa3f8fa5c107534559100607c9e74f0292985bc3e4217e5864271ea82ce8cd061371b5052f10398d99',
+ '480be758a9b7ba9af001bf21db00c451cfd66f06c9d8d5d698ef47974a3d6f21e4049d5556c45b5fada447378b13226ed4af2427ab6692649ddb93831b0b40082e30fa9c66e60056148c403ab8ed6effbd1f541664ac69e7fff0a45e5fc292a68f57a734c362d2088b80532f4cd4d18df1eea7d9def280e925f62330fdab9085',
+ '220248f5e6d7a49335b3f91374f18bb8b0ff5e8b9a5853f3cfb293855d78301d837a0a2eb9e4f056f06c08361bd07180ee802651e69726c28910d2baef379606815dcbab01d0dc7acb0ba8e65a2928130da0522f2b2b3d05260885cf1c64f14ca3145313c685b0274bf6a1cb38e4f99895c6a8cc72fbe0e52c01766fede78a1a',
+ '6dcc3949424fefabd4b3b7b4cbd098a677878101640380ec2f3f34d699c8855ddac5926f3834ebafd776011ad30edbea8ca60aba4152deece119da481db266e5c28bc44d461045dca029bd695d043429f116decf4b5c4ef8ace7e6c7b89792ccce27b62b956964fad7d3d3ea933b0c2a4ddfe788a9a836da38b0409c920171da',
+ 'f53ee3e2ce4467de8b3b30aece9404dc90aed0675b3f8454baf62465ef5f1c29e306d53563df85b088e54b1577027b344b2f377a50dc3f737292098df5d7151f66527ba9d12fc65e34c504df34761e4a0fd76673d2116f71cc88215d42ba0c566469fdc880fccfee762384966cba9525c2f085da48a8bc57af1f935d3ecfacd7',
+ '538e379b06f1d89a9ea978a8f17ecd6f8a22d1d15a1418e4aac5603b54fa6a68337108bed8c7785c7e99f06740ea7a968ac402f4ce22ade1780e6d5a2307d37b0da52442c880ae96334d5c88a94a89d878dd12bb9577afdb8ebf83a0bfedf1aec973b2af40e32452a40de5939367a13e3cb328ae17dbc4dbd420c99491736d08',
+ '426090153dd06665123aa375cb992e221cdd03068b827aa7d367cced8bded3da03ff11756f43f407474e588aed0b4e5f91fe1c3f52d68574a5424a49fb06f0bf9e4ec481dc421d1a68dae166fdf44a4644a4ea98f8cbed6748eb9f5e7d392e83dcf4b022cef667063e8944ef437bab41ff7576fac7883ce68309d316589f138e',
+ '3c17d3274495dcc86f2722398db60237fc70fc0e63b30aa4a32c30b90b40556dccaa5103ac6647e4fece35e7d104c9cf688f7716ea49c8e95b78f573cb3bb45ecd2852972b330252d8d1754f265eaa5b39bc0819bc3eaa02d2c4faab5027814629d7fd6c2ac2b41ae77809f9f58d4de2593fd7a1415957f9f25867e902cb632e',
+ '4ae231eafe77a158c2472143faf169db29bf2b53c3288d8b3c9added65778095f85e2cb471ab58362041f0a27d874c42bbb06385a0403ca193cba67cf70029cdb7e73c7e2267b856fa0b8dd4c706b45e7174659b0ee2891df911724324f7ca5daf07c912b9b2abff762e62a1817688757492975db7185c4695f3a90895634b8d',
+ '00bf40f1efb6484fb6f9fcff80510bc8817959cde43a98ca04d5189bdea1e0fec7f5fd995a481a3fb597516fe508411d9ecc61b52f49935eb679fd7c908d147814d7f9c381e6091834f3b0021f7c7d9f762e7ca3ab08c09f9dbe3f840d5be363512bdd764cd83d649dd3bfc117f5e8d47167529e3fbf4517216b86bb3b537445',
+ 'fb9cfb8a89761e4c02117be850006b26aede2a205f342d459f9cb6a4da27a5681cfd919ec943173f8e42726a97c54cf102c2d417943d1198ab6a76ea7412b6c35e37dadbcffb90f315bec6169f87771f6da5c57bc59649302827a71e84dd6585ab94fdc80466307180ce9e74d00d94b8d6cd25d359057c16fc1c70c9715159b7',
+ 'e7462835e38509f5bee74c3133482ad4d7fb7ddcfb18c754d2177682d79e66616998a852b887820ee51bb6df65030710a703faa1f647da40a0f7fe75580b4f1dd9610419cc0cb047ecf07fb1688cbc058816974694cd26c0f28ba9418e9912867fc8c5f4e7bd9c891a8d2e11038a519dc45cdd319d53b3bd0ffbfe4e41f1b986',
+ '757d2b41484741e4f9a9fc4c30fc633d31be09c856362715bd5bed603ef31a42a0f8cb320c3f904bc15cc5500ac020ed6d24863f262b2397d442b97b71cb38ee877c90f2a101c34a00e93e8490bf69371b777d8abb0d96f59568094cc484f7f994d02288f1d5006a1f190ef2ab4367a4a17f95afff24a7b86a9583d920657eea',
+ '71db63e8b1392644e6fcf7c3d81a03a7518290f4d30048768a61d40580d7ad08109f2f389de0f0a784d74f004e3150102bb8a7859c3212f66f86ec24f02100805e989bed9c8fe5c629d9702352e11258a648f0bfabcfdcb8cf78e1eda1e81bdb4110cc8e150cadabbe4b82b44bf1f188ac799429699f4dc2947ddae9fcf4a921',
+ '179645a0885bf0f1deb9f6c105bdbf2bbdf728e6ed81786c3a3e955bd960781ba12ddec1650240338098068db186f8c42a07f58ae3fee7713437f652a3f0fcf0fb9839d99ed6498d1bcd52e2039f82a7f92fb988092c82313b4b48b767d3c7334a5fc0b0dadff147d7e14488a30f471c53f8dca9061332f67500f350cc12bf2c',
+ 'b20f96997b0603a0bb860070369885f3bb1908939f6195fd6b232124d2941c89e6d045bb8b79c2192ba170dfabea78619eeb2391b9d6efc78758e2c25ec11eea9265b6d7e842c0174ee3ab2cc984d3d5ae76538f15c51a5a8b1942c007da9d14209790f87ca924218c135a5f76adbfd7538241939b76413edd2ce928b426c091',
+ '883e6ca2b19ef54640bb8333f85a9380e17211f6ee3d1dc7dc8f0e7c5d67b73076c3eafc26b93bb248c406ceba5cb4a9bfc939f0a238e1559d0f4d84f87eb85975568050ec1fe13d3365033d405237ec92827dd8cd124b36a4fa89d4fb9de04f4d9f34864cf76f4ec8458168d265a5b02144e596b5f2e0d2b9f9cb54aeeeb67a',
+ 'e463626506144cece55dfb7aa22eb21ea3a4277d892c211762ea45cc205c2d9e4b3abbb8f2a1adb0e77171092cf43afca8c053771edeb467602bd333c0ffbc88c80d645c2b8a3a2dfa92008a1bc7d9d5f83ba34774908634235dcd91bad4f5b3c4a2045997171ded8787500759f0b633fbdcbef47289c2091348deeef62301a6',
+ '6cd70039a77e420d999b57caaeb53aceddbab11739447faac31adb3583fa22f3d796c9d00adc95ce287a0ea711a231b4cd0a650d1f38b0f25dfc2b697e3eb32975f9e2b7be883dcf3621af052f9f37acc484ddf76a3eea5ec8a95843c9d688d6ef0b3336ea0aa3d96996232d3034b47f6a2f011d41de95b7ad294c0b894a07c2',
+ '8a2db96a4df188ec323ef6eaa7d58b56216b0097beb5013929c231e3be8d6f89eed358e2e5220c1d6b3335d0087946316cfa01880d5e3ce41245e40d70de42bb53b67d05bfcd611c77ef5e391e41d4d49c1b8e17c3158c92336505307a68ac6a807e33ba231b0d531e1b790f2f56bca97975ad2c270477ab52c89b33245234fe',
+ '1e691365ad90646031e01e737cb3c65a665409621d05ad86bd47c9d721553121f8f235cb1b648bff1ec1890b24699707f8d4e5b85a8e59b5977fccc85d707597cccba584d0a2b5d1aff33d08de2b879a19e844c6b2037dbc2acecc03fe9acb18c37dcd587552cc1f0d00a33251007d5af0198e52ce6e01e39dbb314eaddc1bea',
+ '212a0448f4b39f0d22f9a0d5a42066167056368b9c668272c78a6bf8b58184f239e2d9cd58b030c8ab2e8e6005f5fd0c56438d2bcf96993b477a4b4bde9f62b3e02e3302ec5dee3855422336c8e485722f98edefd68ba26dcc9bd7dd8d6b7517ddb61bcff7e363c5e7da683d351785afc3fc5fbff86c256f1e951694090d4487',
+ '2d9313691868161ff609b6f0b094317198dd94cb41fb2e62930744b41e200683afb2c23621f8587d76c0ee34276fe48ab7440a628ee111f9050740c9bea168ae36041a489d7517a0e5eb080e1917705af0a2de21a2b6677afabf53daac731735ea10846632e43dd16a136e472e95bb2a697e77d12282172d99b8e6ad939efa60',
+ '81c94be426eaf01864e813a03e4674491b61516bc95d8a77c15f03d0adfc4adc27f27a5ac4165ff6518eda1a5c408708f78a9e26b834179804a312148d4f75f21a77d78387139da40c0a6293c2a59d0162437d68504f189ed970c5abb9ffc6d8e1be2b0877c7f24b1dc273b1765bfc5ce6f4b8d99a96d5b1c92ee53a39f685b3',
+ 'b34e5b0832128d3a8794c2ab447132857ac0a83475f6d96ea607f470e1ce7a8bc9af50e0887b1368c393ab37cc5123011aa3b7ddf7f92f4979626c6eb3f141a62c66843c910a6473a6dbfcc982e9297cfc00994e6187258568a8613767b271c4c6bb1ea4b48929631ab3dee9cd03edff081f760f1968632b5a23fa5163d7b2ee',
+ 'f184d3809b13c417e06c7ed51d89e79c026fbfbbf1022662a61d5e5a1de2d3f2b04f583d8112b47a179f5dd44c7f834c66eb50f384996f5c3cd6cb5182d599c5cb47980a732b97445ce8391ed999f5bbcaa860f0089eafb0033977c7a9c0b8cb8a931a503a06765cf76f981b8c7e44d375cd761944b8ee46446fec255b4939ee',
+ 'bc74041ea20c9b7489dce3ba9e279c00c124b6bf94b90cbfd2864f37e3254037adb02343ac8470404545cb955723368a145b86f30f00131395fbb4bb4151ebb2cba45c5921fd848fb9c8a7d325200aa8e84d633e888b8e4ee40d8146c84282a6bf5798aa28fd3f298c6c5fbd2fa87f24e50336e627e3e33866c59e219f826fdb',
+ '2f42a2ad39f842c355d46670455817e689ddd9e7e8d8e12b4d5b8302d4dfea3a25400b430109db911af2c04228a7460139cb142a483d1e2e129a1c3a25033a133a201145c464d67cc993d132f182118add1f5f7cb9b0703315605fb3f0f75abf16e99bfaad92994c0ac08087c972df4b1cdfa12763ba3f00fdb534b75e44b006',
+ 'eeb955b959c48f359e05da6fe4992c907c1c0134671c007818cedb547a00772c354f4da12e9a10ad4cb78fef8264de430a80b096ee7b08f9cd0b11f3dc20491c2b1be5e72a3a72c06b57b857a9d3e33b0acde5aaa19716a8376a1d4e4b5814655783e733558dfd95824f1b4e62ce859f046a6618875971addd54c90ccf901e2e',
+ '1552df9bae4fc97985bcf7d5fa01799332423bff194a2a61a7c298d263a7e24d26fb500922ba3c06220f77e613c8e8ffc40876aeea3b29ee674f8b29cc22554e1c364723d3ac58dd26700fee8db1311e7f949cdd7c2973d7519e7bca98b2c5947e6d8e91c90e6323194689926da39b17ea4f7533d8fa5145ee15305ccf417c4a',
+ '4d4481936f523035b921005101ba206b85f55e272ea49016160e32d0479f5043c6dda74ad09e07826378fb59007aac67b0190302456d0e0ce29ea510bd994d8d24075c92be7f5e8b14fab85b4f888bab4342db81ad80f114b94cfddfc81600f46fa9e993c35dfefbd48e7e80774e85de49572fcdf04300d5a4008464ef7e321e',
+ '7c881de00388a00f8ceea887b8e87ef7ceb23ea05dad950623b0caeb2ea2fb7d4149aacf795d788630e12fd522b306abce61212a203e585c4cb53921fdde506caf4fa6af5935879450a388ee6829c9ef5ca9789b7066967c545efe984cdaa3a08e43196aeb3757a1b2dcbbbcd2744e2c3e324ada964cd9d00352203663be7c81',
+ '837dc190bf0a96d9c7879d8d998c5c21a263475180bc9c700ca28cfc98ae9b75757b496fb959f2e73e46f3d3ee1a0efc3e011010f92eb0f33fcebb57cd3b6e8c7f73239912c8318b2fd90d0da5c0b539f78d4eae16f40be36f4252bb28951a59a74d983555be1a6fa127336447e81880d2ef4a535f7475e6a5e6984f32256783',
+ 'd60812433098c44623159153de7cd2721b349f685c43388a74c2a3d04a8e972ada4199177c61657369d78f907ba26a8934cc29d3029d4415c1101e3a8283e4c48bb2b8639fe60fc67f6a57b1b03fde507f10efcb43683e1ae223851b962370e1f144b74f1f9189e66cb831dc05bbf46e03e93877a50dec40dde5239a0fd5022a',
+ 'a16b3fdcaa7eb6a2135159aa6948c6a8dce747519f9f54cb92e759621f8fb97c615112cf8caac3d189e8ab70e0833404dbb09082e93443f24076e223c6d91a9d3248f3d76e1356aa40f9ce062a868be48f9fac7b165bbeb754147fe7a5bee8b65a786b5c1a617a1582ad48d20ff8d32f3ed922a6f1bbcb0215e8b91682e72cae',
+ '04e4798b90beaee2ecca6a4c1463ad9c1f9661e0718332e731059f00fe955105dd6bac9876e7a5ad8130d3497b1bc8889d4ea1e50ea5dcb658d46af6194e0547fb66c437e5b4edc373bb0a1aa4c83fa3d31dda40e94f2cd5d0ed98042b62e93b441de8f145ef2f2cacb43847f935b9f2a94d347a684bc94b839850b39c9aa4e8',
+ '2d201194f73a9ca6e44834d8a44aa948287d1536062c647020c9140d813c3a5e877bc622475b07f92da6721ce36d9f4a749f9406b2db46ffd5835dd0641238e959af31cd8002227f20462836dd9fa658ddae8da62a63dbb45713629d67cbcbf4eae3dafe69d6f41e0451de905a89c75aa9d28980366e2c78f0a2abdd500ffb68',
+ '1b3b012e5a3147207350e981c05f20f268b4792078f986a23630d325b2f51bc69d03bcbf5efa694663601fb2b5e55ae0d0eb88d5b145bea4303faa9290dfc979556bd96a552b92961270916f47d6950ac1c5edc8703e3135bed431301ff82b4dea7a4177674d29da298b27009eb83839e44b9041de6a471d88f6504687c7aa09',
+ 'f80c55de4b5ad74e4f8dc14b6a45c019e1826654ed66d9d5123dcddaacbaaf60cb8323d440f1b1ebf810bbcf89eeb37b0b128b68294a6c6977aaaad307d1f8e2376ed858cc03566745e9f6d16995eb4e2319892e8fedfd3f55f03cf136aa39b8e4d45bb2171a2e8add1f599c31c2d05ad0a04aee48d9f6215218697b61cddbab',
+ '9f65a426106db99dcb2130be14839241d4a92c8becc108d2c9521b8238c5c0df7c2365ec9f20848c0559d6e847dac3103ee31ce55dec0c3644e64c2993c497ddfc3a5e4d9dc4bc788cebacbfb3c47a8edeb9773e128bf13a219862617b5ae8ac4731f511b26248a7875f1c0a01499f01ddb3a55eb2a99e2685f0c5f298909b95',
+ '5f172973852b947ad8406fe004de6e94127c7fe2e9f3658c1433a21dc5359b7a1a31f7baa01048371624ede5731737e32a21ca50ac7e46602e2027afada1ead5307b723a4e7ba92cef736a2e57309f9360aba64c0683faff29ab0f598f607da4295f619c9754007eed95ae63b810efcc3c83db7e00ebc7908d3e21c2725c9c10',
+ 'e84dc3e5a3e9c59b8d4c80fee20b43f388c935d5fd5ce9b98f2b32f7cbda39e6372acce6441af9a47e53dc9906c2b5d442873dfad30e3b8bc77b5266104c1d9035397e31485f32df189ea91fa7401529dfdbc2ec8078a5525df437c5c8a784f24b447ecd990098d5c3f79099afcb8c7bc78e69b4eee25098b85e8a1bda349595',
+ 'aaa05c3e8c3337306abc752b9b044dd7349c9604da693749d461dfea648ff6ff585dd3d3dc122f8b929ad908e586ac0e9a53bfa5a7efdbbf4979321c51484d6bbe3047b2910039efdd4ff5001e79f7c0cbe498732f88856474ae70cc01f705f606a120a154063da6736530daeee51636f2d78b35173c1d7e7e8701c31ca405e9',
+ 'c8dc1345a06e53e6d7b7eef4519d82a43f1977cde9e8e242ac84a95e3e52e9e03a1d94f9d8c35fa4fb2edb367286e13677a5346e7ccc62422894eb419c27a5fafaaf5f11280fc592d1d28484ad60aec203785f066cdaa147d9448d45d7a0b362127cbcb318ba4e57608930078b94afefe97940bc3f7c66f7c87dd6917927dabf',
+ '77c192472253685d52a6fc393bb7a9d5bd73f5af2b6e742050d7eae9b4acb00f1b2a59ea4f8894781fe454f7a87e2fb2d324041b1fede11aa12a24a5499ae09166dd82a76c2bb4fbf546817907adbac195139935480fa54f7f15d53994a5f89761c254a702a68e8dddb4cae8e0ae12a90a28fc252d3d8769f28047cd1d35c2cc',
+ '2272579ca6eb22dc3f558314c47c2ef8ab4d678a7d8017e0877a1f28d371ece956d14b8c6bde7f1a809b92470febe8b0d1f71a612ecf019af75410d35755e7fd07f8260bc25c7fb1f97c106bc757efc2274e06cb65cd21f0d22d45f2bcd9442f9db08e2193ab4a2810c0a589d3066ab61719d4d00ac0a06a80cd6590e9452807',
+ 'f54c5e14a29abb699fea3504f4b9a077bd40a4dd72a61cb56c75bdf0a54bf848c0d221d449f1d0d93d4488e4cdca96155fde3cbed6690f2d13559ec5bb4554543b83a0a00a3952432ee549b902074bb8361c34bf17d053f211701125729ed337704822a16edb0a4e7bb3bfae1cd787064be3d30abf45afad6eac5d3851be3d99',
+ '8f636070d8c5c1f979734ae36acfe63f0c0817531a3f8de1dde9f7ada0751939642e1ed3d56230d17cc4471c350f3eebe4ec2cd16416f1fac0bc0fb2a627bc26189c356f658454cc58ca652faf8536fcced76d0db5141ef930279d964d3291bc13754a4c71715571754d4d26bf78f3f93490810ef7833c6695f449617fe0c182',
+ 'a89bbaa86a339951ddcd37799e21b5d1688e4abedbc72daf7cc9b5adfe10be34c00a504196cc7baccc0485b8682e48e9b00bd515ec4f5dbe6d9a529fceaac9857acf23606e9fec9a41ea03a761f1fbde9fd2c287ee4780356790c25691aed808e0d27b2e7b15b4c34269f96f10d098583dcc593b68165ebb73924ff9ce83b464',
+ '46252e54907ec102948e8233e7254a6ad0fe414250aa00025fcaf272798100ed59296db80545fe920ab75f8c0934c21b72f4c96c90aea6f7c6c3815718ba1959ececaf53128020b7039a51e766d0cf4bd9deb7a2ed9ad495722a0892f674edd788d6bbcdc2176d98069e1fec07e2bb228b22d48b7056d204ed6550ca1b98c290',
+ '8d5044a308c18e305d0a13bda0c69555bdfa93c9549bc053c751b37a917be035d973c75346136b1a1678062f6a05fbb6e4ab0cb97468cdce6f0e58f4e24643bf25d4cfb5b31d62f738e63824ec5e557a205fbe3e16f1e85e16107156beaf0e509afcc58ff5e65c0deedc1163ced88bea989d1120e23dfa4de4dd6466cfbc2931',
+ 'f39dcfd65ab7d025bbea7aa405f6d64a22aec28f7c64937fc0a2ff0de21b3ba961e06015ccd71374856a65a4c57cf8cde0a1643aca8ed868dace055dcfb7373b119dc5153945ac01d29c776f61a962b9a4c1befb18fa9724bde2954d1d70204a8b3ac77fa9e9e3f52dea77aee4675b35f7769a786d9018daf1447885d52c3cfd',
+ 'a18a27748ef39b49be984e8d18520110008bc8a1d5aeb424bedcaee5a7e1a62c8666ee12e367e09297e8c7e3d4e4fd056587509b379daaf81949f27cc0fa2d210e9be951940adbfb55ccc7e5ccffa044318ff18af9ad7b7f9c7d1f939a0fff72c091e1daa7c3d4a97fab153b0a8933f2eb0d721621c86de0cfe100d13e096548',
+ '9eeb079c552e421f703085b9b275d5b05c0c922efe14f2e78c7faefbb416fb1e6fbdbcf6d7f9f6c438af8447692f0cde5d7031ecf59d0a8018d1d3360620e358e9d6de49ae032c241237aaa0008a9f371adff187966a99f84b70549f0b4e9b6234bdd65d8254cd85274f5f8b1e8e7604bce13ac6888285954ce397ff6caa0c84',
+ 'b8ec3714f0f54c83d7e1e5e187b110d0abbaddf1ec4a71a9ac8e5625f7b3159bb64c07d326f468e78934ad471ca717ff485b893d1c7b970dfb2bdf6892b49c6d0de178ee8ba9a22ecf0d21e938446895f3162ae86f866f9a11b3e86c2a007f692673336c065b23e21036e8d1c4d1281a13b168fbccb222d757ee183aa5e0e718',
+ 'bdff024f5c8c625bf0e557c138e02f1fa7329bf70b846d616ccaa1fc37d09a2a9c15af7d34dde66ce782ff4b0d0bb57ad3ff40dce07c1e8a398313c962966f3ac7858f515a85a6087c82bed521b6f9d92f7b1d5a285d4f7309741f0a72f1c50306f6aab315ab2b98798e9947bd0a84a5854c395a29528983a444cca7ad0826ed',
+ '8d8094c0736564175a29e567309809ea14e090745e8e2904dfb9da996a7da14792ac5c89b6bfe6d93b13837e19527ea6992e10b45d5684dba0a299ecbf91286cf8f606ea72ee2c8f7e1515f71dfa683fc2d0d760596647bb875931f53488480447c85c8ab0d97e62ac996579447810e0172cad1f5aa6bacb1d446a5bd0484a37',
+ '69969242b77bb69e8d7d63bb08d63ebe8be96a460778f4447a176f0db6e1dbad6469cc7e48f4c8fac7e5f0cea678e22f14b3df71eb9a29d633a3afa4e869ec7afca40de3a059522cc04eb673ccc1d201be59ffda595dbb91ae244e61e5cdad7a3a309e9946131ddb80a2fed30319d5da92c413a6d929711ff584926d3773e356',
+ 'e68ccc21d4d7e9155773e9d612813f99baf6d72c3336562cf6e5a478b6f9a8e543145234ae12df41aedd587c42895c9d989d20942eaeb4bf3733886040942e4e138461ebdc9147558af9f3e178c02ec54dff7714217f48f0e1869bfbf4f1ad0e1e83022ea57da9bbb36fc1ebfc4d3c77a0c5e39453d09a25bb88e62f1939ac8d',
+ '657fcef962db04bd269ae5fef2cbd5e6558d072946d235e8706394d4cd250796769a926fbaaa121b6da42cfc82808474dd672f9362756af252bd8cded78d39b9ddf4d99e24824844934fcf25d03e54df0d83cdda2563fb2be73b54b8b1c4419d429589cfc9ea0dff41a3b7c20190adee8febca47b6264e5bd8e8d4aa8552850a',
+ '422e4cbdbcb7128f1966ef7432049d13a407cb27c8b4b7cbe686fff4a5d3b53fc6adb1ed12072b2b91188997fd05750176ba336e771831630956e06037a1c3aac106c64d1592d0627ab89b8e8ff2c4cbf4ab1e6b475d4c5a52f78fa38281dc359b0232e8aba22abb3d0cd05fce16b1fa85a435251ec92f362830b3c570bb2869',
+ 'a67b1dc3633d30c4ef2bf3185fd44865d2af5e72015cdf8c182e6b28c5e746c98ec24d2467b72f8284fad9676cc532714f570982993d4b22c7d07a1e79ff5a75c94eee75dc1fa222b630cad753664b30f3c99826b5cfe17c67dd875b9d0bd2390028e6ffe9fef36a2fd6adb13d3ffc69670cf4a67e9c0764a15e7925579315db',
+ 'a9174a67603a4d5fbaa8cfb562f07393abadbc80d1b57231829347a29c38ba6639ed3c3ce98c91e23ef07a2e8eaa915af4f574a098ed250630fbb17cc7941024bd234df11043e773d93276f11a8291b9b612f0b4c13dce3dfa5191339643ad4d40a1c6ae5dc715ba94560c278ee23d57faeb78e5d50f337ee87d2ff292ad598a',
+ '5c97f13331db20f6351f9aef4e0b7c9c92a2cabf476903a80ecbf8b65bbcdd1c289da1e1eb5f7b2bc5ecc6bcfcc20ebdabe16bbab8e80def077b19c2ede7b490e8095cac8d6c7fa5c1b146c82c34b2e6ebeceb588593d53f2107e310f6f1305102a4cc9dff4853ee9337c51cc7a791a0ba8af39e97b28023c43900ab5c207be6',
+ '179645a0885bf0f1deb9f6c105bdbf2bbdf728e6ed81786c3a3e955bd960781ba12ddec1650240338098068db186f8c42a07f58ae3fee7713437f652a3f0fcf0fb9839d99ed6498d1bcd52e2039f82a7f92fb988092c82313b4b48b767d3c7334a5fc0b0dadff147d7e14488a30f471c53f8dca9061332f67500f350cc12bf2c',
+ 'a782b87323a0ec6abd8f27e50e976184847e166a04a001f1d442289cb923184e5c5472b9f24aa6181c32ff210c84e035eadb4ddb7604ac6cee54cd10323f29e82627678d587225bae3dff445931aa454498ec3cda17a600ed34714dfd71944a4cda4a0d89b41efb6d8400f39e9803747693e8029cf2ba43f4ac105f2f0d6f1e9',
+ 'f7a519f3b5ae6fd988eae92a9bdfbecf81e7b405d73ee50e2559c32606795ab98981d5d3d60444d815a39c758b96ffd606883e1a7ca89d04effdd6f393f960143352f0d6d10d419e8ddc11bdc8a96c9f88732c441e59c1f407f42e2f11ea54e4bec073e3edf0ee93b73c4ee898418a90cf4f866d0778d94836e7d3c4c674bf90',
+ '2fb3b04e1f5e7fade5abfb52efe19edd2ebc80181a657b85f7a18d3957497fede1fac453500da4a6bfca9a8523d8fa0119f8d6f5e2f42396abd1184a124cd7bee7854f322ff561186fa541de27a220089cac0881da2e0733fa738fd5a1161d04c9ba1996c4fcfd2b7da6ba04022558193f3edc650cfc6e856bedbb810a8e99ea',
+ '7f7577736313f725fb872d0703a3759c422a55db25e34ae0a7ebc8e2734f7c654ddad4b1ae2cc182ae0cbc01270007f3181a35314714ec582ba0eac108f946b45cbef8d87a009cee759a73bf3fc0ab5312dbe0640f94e212262fb9d9351be6bf74c7ecd210b70fd116d65c2a930ee924fa165e5ec58bb4785f433d1042dee5f0',
+ 'cad04d5a15ec41e28c9944fd13bafcc52f54aa86c5420d17252a846b46af726353e8e6e667117c3496817e772cdc4f9c398a0a604d6866ae80bddd28b56f0d0420775e190692e539c43988c213d463708a2b6b75651d51cc8494aacab7b84cf63863fb1a79d5459a20aaaa05500900ea2b1d16ed95c998193a973278d2f2f8e1',
+ '55ceb7328ec045967807a80790b5f55b2a66aa1f6d2edc2c9fd0927ba3316c3bbf0c8820a3e6a5fda7458995551da1af278be86891c509cd4252c8a9a8769e9cb2f1a36dd9e9b2a16124c74ddc7aab28f18ad4e45bad86bf34283f5574a652b8b5e5d2c239afb1aa2d0c29d62fb65bf00fcd373cd2cc9b29fdbcbf2610a7d0b6',
+ 'de66e519983ba074220640d09848cf606f6f959c4e588de61f11156e67e3e953d290520b13d99b04ea43c58b861b7cee0eb849dd7b000816a82e9d42acd2e3196718e5cd5b4e51a6bda129e9cc27bcff6223d5d3c984327ccfae371c1d7de408c487052919a2a8a2c3a7d4b2127578dc9338a246e1ebf160bd1b4dc561eed566',
+ 'aca7f7f326453435b2ec9e17f0c8823f3cdab1cb8d4783429df61cca4b59ee9c3d8b7fb6c99c6dcf1629af907e2f1d01372033423337127b4409c715845ed02bf43edc3b634fd322925e1647953b08167ccacdb0335752e0a72a8d522a5b06ff19e896ecbc056e146db35ca2fd944a6453fe087d564e4b5a0e7ff5e705fb9602',
+ '13475d77c30210f6beedff5c38b926803e950da0a54f55a540bc90a8565b56b6523595d0bd0728366aa3abe6f0948e5f5d0169aa29d48f9b691ae65545adf60cac113f0f479dd005abdb1576d231f18eccc00c1eb28c6fe4dcdd4e0c53e624f689a5063a480a30eae95be517c6d77696f29aa00327c01a07ffcd6fd7674d0afd',
+ '3c5a85e4d4ccc1b8ff94c7c7af3031136b58e1c7452994790c83baacc2b086995046412f794ee3580da5e47e5fa3504ef8fb1abb8de2b2462f74d97dc253b5c2b091204edfd04676e0a76f2c694819c805604a090a3f2456cb39ba4a104c2270c303cc4bec99119ae0620fd9b467b50bf8501ab7a2881331499b041a94e3f62a',
+ '0e16a3bf115933403b178eb58a604ee203393afc54a61060b80882851ba97e2f7f96b2e69ead50a7d0f60ed930377282fac24cbb389284629e96150eb24d5a48309389f8acbb7d1d79ddb8c1ca71a82d171d2959c2cc4ca6fb0056cfe1690c1de9b62edb84ab420afc7492569f39784820f2d9bc3a7df09696ed4db1ef261d18',
+ '8c8387f4ae2ca1a6dd13d29e93580b1cdf6268da66cf589ca8b1ff0884f7d8b8fe299f8e41596e47e0562653612210e4fca6c446a0a54a6e37ef80d52bd7bb8729e6b17625d197159ea98622235223c316367fd5b03a3c8145f2f210c910d00094238757627e63379e75bbb3e0d08ce1b47961309d7876fc59211c60678c5f4c',
+ '50bcdf31389eadac5bb8197ee949f2864ede284c07d039a0b40eed7e6f1c43355d5cabc8828d7595da918a34a5735aa202a8159fbf951e547052bd39beae14360273540913eb30e75ba29266316e8d9a63ad947e11cee996c21357d3b19424b7688842b990c0c5eb08749ada344275b698740bb3a58282aed2d72514efd85d00',
+ '65bf93633e3a4cf878ddb21a5aa2672fbec644fc6bcc4ec59ec6e5b5ead03f8042dd154655b69cbb1a3fb785abfc6be556d5939af116d5026fbad483b1e9a7299ebf8b90764fd40563e82ae85297f15400ec09035801b86bfcb9e42d224686b0a1ee5b094b0edd1f7e5f710cf678e2c6e5940efe4696df486e4a7d7de4eec25d',
+ 'cf7210d4240cbba95a8635c1c37ef8bc4bbef2dbfdb32e16c922b0688416a16e301dac307eb3a73f91ff760005bd2c47307c7427a7093009042b5ffce790444c3b08c556bbf1119ab4f285120cedd1c3832e569139e9d35771e34137946ffb2f799c22ede3ad40e54bc92ba0e0f42d57cd3e61c0ba3a602895b21dc292990e3f',
+ '5d118ebeeb1a9774901045f4af19392c0a3f641b351618934b9e653ddf6aa2dd35024ad7b2870af39295175dd96dc5f08c5456b320360fa4338f92b57a8c6715fb6ddcb07c2d0ff93b6549e7df6e8d3dafc5710f02b42d82f62ff2d365fd7d9b1518eb512f55cf10f347829aa961ba9edb5c5e36c1d899b4fd462e9e89050bf7',
+ '155f60ad0a95bddede2a10f0c8447acd23a541f37b768062e8431db99a48fc9cb6eb72586189fdca1975327d4c3ef6122331f1e59f1f40ede8616ae4e21896a800b9fbe25dca97e509e624d9a007481822050cd8fe598f0b7027fc830d7cb95a9dd4e19128dff5f75484ce4cee27d6a7c6277815c0abd583289fb9de46f9cd78',
+ 'a5bddb41035156670818c030d2893f7eca39a429795de6a19e8aced57dc0f35379a7e9b0e518b62a18df858cbfc09f5278b8960e9c84c30a5b68f32f0f295e25ca5bd9bc31e34c8b8eb465d720dc8eb6b6c41d737cb3cb35149568dce8fbcd2cbf62112d8fb800d1921cc8d89ce6f6f1ace7a122c1f2e569ef9a94a4b13e27ae',
+ '3280224a9c75f01da9fd8bef8b925a1b7e901604ac8cd0064ee836ad15a41225c87713f22e1fd0e12ef50a3f35c43148d8db2ae2bb61508cb1e9b9912446ba81b8a1ade12bc9f12280c933d05cc0ec0cb0ed2b3c980a950183dbaa6a95064a67492577805b1a5cc6e5a28e0ac82e934e4deea1790c2ea74f0de5929f2e8bc9be',
+ '012870169ad72eb37a51b676597a2a8c0104464fb33fe6bdc632c82891ea922e8b1217ecb1c4d66f289fc36b241a4b30081792d9cfbcffc7aa7efa4eea7ef4ad2119a84484baa10194f3fd1cfecd7004bf5c8c998b963f9b70659d62b7fadfd00b65ac85dd6298510676ebefae3ba3f06df8bcf5b175ae21600e38cebe055c7f',
+ '4432f43f1b00d306dfab2c2a2409d049e1c30e897450d42ce62418657124766a3f5e1bcb75f7e1027064bb4b4edd54b6b10ff37abf12a28c6e9a8f70fe71b250c725b04b34fe000f10324caa005c1a9d512bab32f4572310c7daeb0d175c544362ef7d6661fc7655457da5ee426d69274a7dfe5a1b09a1e17b4af4e3c2cda36d',
+ '7ac33ace5b4a6a3292b72d0dd4bdf853509d9bdf87a5bc155ef684c6718b9853ab774b16146e12fde9873878f240d29610c3f66b166828b4d97a15be8b3e848344318916e292fb421320296eb025c9c44db331930e2ecaf1bc0ac1a417d6ff436e7a5c986ebd0f49380a69b7b673c4272ef6b62017ff8a132c2ff042c05cf3da',
+ 'f4d7a8f73898fe68c398588dfe2e019231131e194517908cce121bb2491ec781a1038634f9f3189da5782cbb79aac88f47a5ea2ca33a700ee9e535ac82ff7d5062359327d539b0947cb71fca928b9f9a74310989617d32267e8c139b1dfa27813e5515f956d28ff8503f7ae2d2394f5bc19fc15a0747a07e94effda6a2768fbc',
+ '504ccaaaf09c8e8a0c567ab7f1a1eca78ebfedced9e3b7126e43757e796f493ad7e193bb78d57137085b825cceaaf041d4b7ad9d4806fc3722c0349d0707c0196d866be1014cdb8e45da5acf7e7add5fcdd33e349cbbcdfa3b4c07bfcb3aa5f05c63d98452a8d4770dfc8b7ac9babbe9c23c2afd9ca93143030e774c8fb1ffa6',
+ '7416ef51d9ee9710b83b2f0bba9345aa7cb4f4ab8f7308bac4f66242a6239f824758f4e3405d5c89f397f628137ea819675109adca087ec1778aa3928320ecd3ab298cfd501095e7c07c6196b7c6325626b0150932540cc0805a6b88b06e838727f17e4712ef8a51a7523afeae55288a413be06ad040f9df68d085cc34f7acc5',
+ '0c6908b5053e858bd901c18bfe5f85e73328301465a5b6c2d42de91172f3f7028b22342bab2c1ab0bd5e8e6e70b96579dffd27c970061330fc5b638f3105d14a359d59f98ca941613c2957a22f6c7ab1d8285b091aca859e650b9b1322c4e12c5103fe86705e01869f87a18f0321c97868d2543d2a9a15f455631a030bd93191',
+ '07355ac818ce6b46d34163aeec45ab172d4b850b0dbb42e68381b67f1cc8e90a4c050f3d0138bab27e6f4f8d678bb65e184656493b7541649a8bab60315fa16c882ff85640e483f3eb9789c2215575ccd01fd0ced3356d9ac695e3bb19be405864b9fc5bfa5a2cd1c1c4f894412b4f28fadedae4fb842e52b0a545d8fc6d2f97',
+ '17925952af30959b1a5a136ff11b3de10db6e4cee19f31080dcbdeb43129a5f1ff71f9bb951cf50e09b3924e454d1ce61554e7307e873e9552459cf501081f48b23039869202a9c56cf0a9a17b1a69e17c16bd5806ec12081e65a78e0786faba5757807d50e998086c96c2323a8b0c1a6984ce0e22d797ac9cb46747eaab1f8d',
+ '00bd47d752532988758406e3cf718baf9bb9ed1be09a80fe9f59866351e4444591b75c9715fc5688e2f68004c09ff87eec9007ed0e22b0146ad389075aebcaebfc5fa4fd28f5d4d6a5a977ed9c4f205d4c7b28e8009e453c3e715e7642979ee5ab7ec8107386cafa246594a449ca2ad42340f8159e5567ff83fcadb8ef31e9bb',
+ 'ca7e275113faea9fa709a4ff193bb035ae1985a5c9c3d316a6d8cfb74b96ca5fbc4309196fcbd1e0ffaac1a7240c659de33307ae021ac84dbf58f071c24683dd4f6415a5c0f9deee33fa11f5802d6a536e8e067f26f27894e7ea1954fcea9f6debabf2fcf0cd3b50a9c13df013e6e8dfb5f22b1e1b940b738658f269e2ca4998',
+ 'f211cbcbf3f7a9c489ebe8f76922fad5cd3d0fa66b6e9fd0a4dd4256ff4ac89fd5f386794eb8ee5d8c7d63f525d04bdbd7cb65a4773c5c1d2b049dd4d9bd66dadfa020c805a5ef00afeb8735585b412e3b896ec653daeb3886ecf6991e323fa678df42c00006d5355dfffdc1e80c0655633cd316e89072a91f5df3aeb4f17b8a',
+ 'dc59a9d3b6d846f0c7b2ce52eba31d3bf192915e4c7260e70b662fbc0c28e0026cababe441ff708f8c764b8169056a0489ec1bf5e29929caa5ca69d471f390c0c6df4764bc9982b9f58d0d23d0eb67f9df4cd4419c98aebb5727fc22732646aed23da7dd8e6e2373ea413bbf881ebf21dcfae4c9e03696c109c30f2e7a8ba9d3',
+ '62e2a73bc77ac85b1aa812463dce29a097cf3c6973d98b76a28226226817f74196300255f388ec05e00cbaca3c32dcec868c6aad419dadc39debe10c5355397ed1a7245d976ccfb0e104ebf586f6b014208722926d8b9307f57b69d2edc8210b5c6f94b97cce794563b52c2fe2c1ae00aee5ec80bd4a4428f35945dafe16b6d0',
+ '34576ce2cbe2173bf40de23050851aed2fe7341f5678b34f00154d6e226d49b1f36d2b9facfc93688ce963782021204cc1269b845ebcd03a7ce60e937a1058931a8e0c363d45c2bceea87744a2e7eb9cbe6247585a640321450e0750499110bcb0a156cf06266ce0213467bc5f3d42862f8581c2d3d715ac647780ce165739d1',
+ 'c8ce9813cc18ff5ac309ea9e2a79e5091387a258d2814ae1fa0511d488660dc15d51485af2b1147b47cf9e671cbec65564f62e2bf73f918987d15709d5b966c5247e3a1aee0538acd7b23faadfd08154db3391ba261bbcc6945c9d7ca7bcec81069d97da2adc14f75bf8f5f0db77bd0e6185f28dc8df73a009ef0cb6673848fc',
+ 'c4c45cc235592317741f8ee232cffc52e9cdd87d6f66c9bacc56284b498eb740c93490975cea5ba81253c4c10dd32d0dda979fba02d6075adb569f8aa431aad2d1d964cda45a398afddf35317378bceaa31a7bfac8e89e2f8db0437f1fb92fec85bcc0ab34302384decac77c8c4512b2ec5f5287ec24f601876efe72dfadb054',
+ '2c869831696381346890bd7be46d798e15dd5c8879fa6b6dd4072abe76a5044bbc4aed49d9f046a4d60a0197d8bc0579a24bd4da5ad36bce90386a897c5e742c879dd9df0e6f7220626ccd5a13798aba6e3c053e44d3360fedc5d5108d38c1b79665a21c8e4acd4f139e69ef1c0ad0f8819638ddbe6293d7f496b47c309bb293',
+ '008cfd9f494b35d937ef3e1d8dbf95015f1284bdd206ff822375cd0deb25e87ba13f255f60031712eab9314aeeeb2cee86d1a829040d16beee99d59b47fd9bb010c517010f32d5facf306103e888af558057ba0c12bf6c7d6fdcbec902f920b357041baedf40353aed3a157105fee7dd568a028d8583c868ac27cec1a3833e2b',
+ '07128bc2e31dcb22aa5b9f3ed1b852041d36f022168f59cab91c95b26df56760385a25a43351c6663b913da1ea9f06b0c537fec9b7ed77c7bf148c2ce5dfb26672c69051602b11fe103eb7b33b1e32322b41313e2b15785c3ce732d7090589061d1f75d154f3d1728f2ab479ac7cfe13b61b318b584f8311985d31bbc2ae15c9',
+ '58dbed97e835ff418e9b06c0943d43e2e3727edf23504b8b24798cd07d37375c73cc59971c035bd8c40b84d88f85c06760dca05dfad5a1d46567b19494ccefcf44d8b30f278ace6c42e1130293f016a2f83533c84c27d2cdd30eea5ed817c42d94a802e652f1df65d1c4b826eaa6ccfd72264007626d66e035173e1692413dde',
+ '10ae29e78abbd1c4ba1a24bc417b6122f5e9b87628fdb0382e51c6fa193856b9c7acbf6d1f88c3df97f82cbbf92db5e6685527119ecac38f7789e063b3e7d59ef77f19e8166fa95c8fc4aa9957325015d809feb53964af9be0a39440351cfec2a90e7f7ff8d64ce2aa66e67de0f2fa584dec858983333b0570882ab628419bce',
+ '33fcb8eff417866344632d0f9e8198c4dbee1c139edafebdef37356b2610729f0b1c5eeb3b932261ce402d4a36d8311b6a8a6fa445d7358b28a4a5f9e78db793e37d82ac737bb7b889c76e04922625a59d7a05afc09568a7b74f993acfd6da2e0346ac9a647a4a52be2177a67814794cbce7669ad8bd9ef8e4619996a593e35a',
+ '90a02bc5f26d2ccc030b1503c6c712b8e6ef4b41ec33b887b45137c122f2dc8211ce88f68c17bd684115b008320ea0ecae68675480114f32661f26eac5b495569a25ad0db45bc3e521797eb6e6be2e61f3ae5f11556cafc1ae6bdcffe24521ef14ebc392d1ffe7488a7ea69448a263209b075c01d30c803b737c8188e36e2955',
+ 'ae3897b902c499faa6e54fcf8864ae65eff6e24903b5ef7e8fd198cd0683805cc4438f82973b97da7efb3796b06e0016e00dd7bac0529af4c47007a12841d99934803384bf3842f0f27c1fa14e59f228f0095db814691834d9aed88c4453764a86554d6882a3e4658ad0cd98690cccc3a7523ceb08e3af6756f2d53860a19f98',
+ '7ac33ace5b4a6a3292b72d0dd4bdf853509d9bdf87a5bc155ef684c6718b9853ab774b16146e12fde9873878f240d29610c3f66b166828b4d97a15be8b3e848344318916e292fb421320296eb025c9c44db331930e2ecaf1bc0ac1a417d6ff436e7a5c986ebd0f49380a69b7b673c4272ef6b62017ff8a132c2ff042c05cf3da',
+ '820037b251f283a52f6c19177dda02fe2416060fd593158e96dbe6647a3bde72afbc3325be56514a0f617d24ac4cb8bc4691e6797de82ff05cbca6fd23db28134a7187d0c237e8d57ee86ad432f509ea5b79c1307f6ff68db62313ce69e672f85a067cdce4fd11ed85e92a4f993cbc3068b5e05b638f320aabf876fcd3c482c8',
+ '097abbed69ebf2e5e87e4ed54fe38d10f32f4073962ed25088fac6ab11cc40a91413c745ecc349459af05f6c229bd3f232cc603105e1b8a18725cc06baa447e8583e5b44bafbc181f89efba5527dddc9ce8f4bcb23c74442d6a020b7a3fa15121e2400529a3a62814ab1a9e7a630b27f10a18ba7b8897d1bbd944a249575b30d',
+ '3f61d4e1b7b2014510544a12ed367d378f6204bcebc8a4a8003d6b2367c3e3d82c0b8c9ddc388956dfe69a16086b4a886b5c6a8e6f54bd2724f0f596d61edec1e298dad7c8ab8d35823dd98b140e0d3a653e59014d1086d9efede31d49ac83ee0910a5d6a29274aba061f1b738a82d15240fbb5eae8465860a3b1e00e8f33829',
+ '0f31992894b41db6dd3e8c807caca260b2ca46b5320e6bb5288734057a105b874ec9d373ccc8aca9250b3845d4b16c74246a8887f22dfb46b4298087bafd8effb42bef5775caae82f67c374f9ea0ba3ac0c9d088666e61934de3c5623087297c494035fe1624ecec5979d3c562e0555a90cd66df163a6743fb9d49bd6517f6a8',
+ '8eeabcffbbe968425ff795fabaa1a9c77a2ce9a931338fc205921c5eaa83ef308d0717de528866c181bcc6e67cccd058b5b69ba11df0d28ee04e0a334f25522f1db10b31cfb4fabb6e609b267f77b8e735b13b10e45e411ab94c6fe1a9eb89f0a7af40ff1ab64cba8eabbbc4a9ea89fc61e470ff6dc501eef955f4719e1cbdfb',
+ '07e23ba57979f53aad3bcd9341e6de6fc64ff3770c9cf019a0b36e9394f3a64e7e21906ec3a54ca716f6c0523b5383c011b4f9cecf00c0b98e804b340894cdb89fa4591ca15a4765ca0ed9df0a821f6d89d0171de9a019ffcb9e7238942c50527153ded69800af1dd16d606335dd791d368c958ce0e6c3935ff72bc6c023f5c3',
+ '8c79f911b301a8718cc4b19a81d5f0cb6312d87c5b4b079e23a61d247541cfc2c41a37f52b2c6e43a3db5dc47892d0e1feabcc5c808f2391791e45fb065159f99c1d8dd2f69baaf75267eb89dd460f1b6c0badb96cbbc8291cefa370fa7ad6997a4ca2b1fe968216032f02f29837d40215fa219c09161df074e1de8e37056e28',
+ '08dd4f5c7afbdb4363a7df60d247776d6c7c122eb155d44981c23858de4bfa3df30134b555b5c7318a69fce1c8046b11fe4a1cb8190aed4e809933dfe080a45e2f72753beb81bf37a3912778b90cbed866d72683fe85f7c176cb601023341276c4165915c3c58c00b806a84d2fc7386cab0d78b7eb2db9496b3f07142ed00a2e',
+ '7261818aa26ad3861426af03ae6ddcba10f19213d473def6143747de2db5b230c39183cc06cd05e1333e0c055d3cd9856d9e3df968e6021cf0b886db0e91a9ac2eb5e9216b69ccbd0d637f06507fbcdb68b3f008c1459e188b3bfe6b7614eb88bab5fcb35ba6f0c3ab7e4f2e109c4e660718f36869f97b91eea9f9b4efa63f6b',
+ '725400784625df22bbb897e7df2bdc801f8e8c1f724788f5d4b5c3f7f61498e234a1617cc7fe451d3cd7516f24c6ca720e74c2c3b202ea1d6fa7a720f89a68514a323663e14b8db52bed6a1b3d28a5e1c542810d3f1582e56cb27eb1004af7c29b4fa8b3fbd65eef70400973901913d62b40f0868248f754b31f703378edee3c',
+ 'abc9ccdfbd92b6919a5d6c6b5a765a39662ed90080d3549204dfaa5f6d70d48e1af8c84d53369d658765ef11d7b38510d9f431f99598f8cfd4da73d59b3b75a3f22fef7ae91610d5dd6db040f846ee6df7f51885300dccbcd38b5d28705078d3b9d5080f8a1a560926df75a1c417dd794a9a564c581a188288583001f4972545',
+ '7272eff0b28964a1aabfa08f37527a8607043fedf31ba6ee8fad05d8ff1ac4c10cda126f7779d8798cdfeba9fbd586a5e4c5f7ce31c1986928c701fd40447cfb34d6baa45756c4282716330b2467a4cde35f67ca5ed9775f8ebcaf4e3c813a6414ef4c59fb290ff7a2ebe17e5b11bc482c59f5a922692a19e814769598d9e642',
+ 'c2c1ad604e21c2c869193d6797ae657ee740649c7805eeb83cb6237dfc88b7e59d5e5009a13d2f38f1001346d94d5a2654c76abb8a854fec97c4a5f78ed8b907bd69eb0833db57ba800eb404bc487b8ccb6f4c84de7c8fc73d2c572445f88bf9ac4847040de48077a0abe74a488710d5d4a0d49e7ed0f470b858fead29d175e4',
+ 'a20f4cfde1c12ac3aa4d11b13dc4590ad9395f0ed28032d8e4368f87c701109c0319a0a30608321674aeb37ebe873cdbf6318d46e228b7d54fd518bfa7c78cc0c640e2bf0af38dafa90c9cb34871ed85c9479d1864b9c27cf9f45d03a4768aa29389fa99140aa356f26fb6970209d2d0f98577cc80b9bd968b9e469ae6987108',
+ '3b8bcf1cdcd4b5673d298f8df1e226c1a7ff4a2552bd15f588677402286fe26340bd77672e4722ce05e2333832571cdd5fba787f97f74c9dabae8dead541e3fd9c2bad4af7934551b52085151c108ad0d184b7e5f81efd169bce5af750e9a0a2167c78ad81dfa659178d8f0cf932f802c606103fbc5ab1c82070e312e090a2bb',
+ '5a84d46560d7ec2d1ab663c984022cb24393463581c5361af733b4844bc2a5189de249615d10b6735f9f85cf31b9cb87aca14ba3c93ae9c2b6cd620529073b28f541f7f2db058dd0a2cd19bd690dd2643d743c89e76f9fa507f0b7d0676dade4892b46e082bc5b8a0bc78959d60729911e9682b0826c3e0913221bafacfce394',
+ '9ead422c9e22b885a422c37ea49c271f9d65f28d297fae76519bdbafa5dc9d1c8ddeb1d1daf7a576a0bd49f048c8613ee1b99ca0b77acaff27c84989b1efc09c4fd510e5053a88c9ba3e59034624498fcc55abc74aa88ecd6ee03528ac77c7b28d9a48b14a74c84499afda01c73848dc0743054a0a9063a7cfec86d5bdfa1927',
+ '0f7251cc8687e3e02c363af2ed4551233cf2bfbb10e5ddbe2c622bc0a4c3f0f99d26219c54638465624115713ee9a953039ad164739f015a3c7ef21d7b7344d67f1c6848cf76bd636e08f9165d5ecb6662b9bfbd08056184e70ba5f325e886283dbeee77ffa9d602d9f5ae89548eff83e1b74f6dd6ff4562b4710decab0cfe1a',
+ 'e437f8b6ecad318267ddf85d7ee05b35382e3d6b40564129e9f3eaf66fdb0087809935d8fa1e087cf7b3ea3207329fb8bc76e8e46c105ff0323ba2163613b35c2e019fb2257a5e3a7be9fbe72ee9f54957b8e4a7f8e85f4ff4581e2a5f635c93f8577f69f429fb63fe6774a47b6d239012dc7add6c480bed3831a65b7335c1d4',
+ '5ad21401118c89f381a8343b12fd5a96d95d587dbc26e758d7149eef1f59b92145f018d8de2e8b3cc09a4c27affecdd939beb4eede69248d748e3fe1cad1e9cd8c3dcedb66dca6766c85b85abaf69c48572346fe60cd40666255370e07d3b9d8f5633df3f3bf64094d137eba7a0c504afd3215968979c24d68128e5c1e87b2aa',
+ 'e3a90651f7652c0c7dea981f8167c7e3879f81cdc249b1ef86b773c200b76f2225b7669ae82c0ae2b03413a609798f899959796a57458ee6f7675c1ea8889cba0230c12e3a0fd13b999b74b92cfb4b95bc2482160042a9641259bf4a202c903b645e429356d72a202069e4e152b3a20dd746c4572807a971bfd5c5cfcf6bf4ad',
+ 'b9b8f4c824377a6cd1a31b1f3a21b551dfc16baf8bb002f4d8b08b02f5c64331a732b7e78ea42c69aaad3df01e74c60033aa01f59fc0efdf0857fa8fc4f8d8f2e305b29e6fef86abf2aacac4395e527d586073e7ee606963aae4f6b30ef54c5773172d164e7f51dbb18108c21548207356c909affff93728c83ec8965d246707',
+ '84c514e4714119a9e4e47fccb9e82404dd5a785060d631decc92402cb69d036d9269bc2ecc88423914b3f6b9f910f9a0b9b59c4657681852efa880de47f2f3d6a63d16a1e9c7c104d313f943a5321f89ee436689a5368b6675d5c0d05804e97167470a87f18600d2ca0d70b0e5d7fe87250cbf6371c8f0e0071ee84b125d4b04',
+ '3d31cf76288ba777d0da29e9ce21d69dc6419c153e7a4d2eb02f5001dde9970c659fd08d9535e02f80428de851167a22dffc591982bc5c842664ec779d489e883a4863319b51ff75c627bcc678615f27b9b55b8eb475458cc65a882fd5815a28e3b3ee29e2e9eb91ca0f1e4bea096bf37bf40a3b7baef08eb9988af32c9ab133',
+ '4bbb7596f19aa5ded4017a81cac28e7d6a685253c01a5e0c45c2057a0d6e2dc043f65d15d3df18c4667f6a779362c0b653edfdabb641c928d5622ceb08995d205916d42738daa69870d41284594a57fe4f7bc9da648324b5527e2036b4f04692756501568854f861d9499b2f8443fc5e465be16a30a717bca35e09e3783d9121',
+ 'f4a65ebf30900ab9860490c7bd7c0ce4f46cb5bb38830f10522e625ce25f6ab7b28c50fb44fad927ad3bde01a6f6fc00e1e68c689925d5b76dab81406e114e16779b062bbd76b1b9a63e09e1dfc42e93a90d9bad739e5967aef672eedd5da94febdc6897c28dfa381915faaf8d6e0c64f4eacbd2ee7402e7bc191eae56c8e32b',
+ '61cb9e1f1e4b3a3b3bdff8cd5f24566b987f75c8a05377855f772b49b0e7ec1368b9c6cf9553db2803dc059e05f0bdd871983c3bed79dfbb694bd0f1ed8de36e9577be50da313d13124215a93a4bb7ccf4f57793cc28ed43bf7e9b68fef7d125efeecec9754b28a271fb6e16899d0bef287e6df7c5c867c569f6d4d66b8b7ee0',
+ '9ab4667b2df7eb4be8863aa53e9bf9af8bae0fc09de94f7373dc56fa4472b6b5c4235403a26c0e59557ca1911831ca843342acda7dbe72211fb5351d9a34205f0c77d219af5b0331a2126b94ec1adfcdbe70bed6f8018b2eef61db2b6dbf7292fa19a9655aac13fc57af5f57c14080b3b29f0c5b169ae2c16b4810cdc6faf475',
+ 'a1c7f3c9a79b071b49301aac754a2e89d971fd90a7a2dfc99544effa295d6975330657359b1d6d295c3931d0d1e35f0630038b1e54980830bfac09b4df880650902461efe3e14a131d7ae06c033898a95566e38e99050b4719c15efc2f238fa5c00759200751658094dc6ea994b3a31a52844d09fe51b1b5ae6938f8a297cd1b',
+ '8c5337d74388cbbfe0f400f403879687887b6b2f5cddefeb8f49d8e9abf517a745f00a58d1acf389bbbba904b3d68df44823c04bb8b89361065b3fdd4e8bd7d956c57a416500cd7c587aa84ff2b610fe74c566b46dc6dd24d4a932715438974be757f05ca68a41e2e0b9679d693007eb34eac532240fb67e20bb176b66013f46',
+ 'b9b50774715edeb6947842ae807d18bed911c4c9ce3491fd9ebb53f05b014befefda4a935cc81994487219e2b85127f21cadc2568cc8709151595d29a73b46fec16795d90e20ce48bb6d29aa79cc818680256c21d3fdac4fc6ecc689be51f040394430710eccc37af552bc2c4956ed210d610a4f2e3b0cde075dd4372aa9115e',
+ '15b186bce73456813d85a50e68c4e2a5fa4ec9a3288fe5f7731753d888efcab8642dd873bbc66ecd9ba49f1b4df8a5407cd225db98efb4bf7dd199a45015d41caa0260c8f95eb6cb2385927f6cbcf96799c27b6555a8b62dd5e31bfab8a0f5803157a62167a334631c5105a28db6e7029a4654a82763f32ac2736143863532cc',
+ '5967ebc2c80785c87cda84a888f4bab97312ff49e981819ab13b5c2adf546b374b945d8341660b557af008c04b847a271d3729011dcfd6da35e3ce9a3a3dbf0a6783c9940a17d84b7d3b322b58794ca1e542e24ed4d546083062f921926f78ec957c587e89e295b26c012870169ad72eb37a51b676597a2a8c0104464fb33fe6',
+ 'ecc714bd81aac0002a987a81d35d328872a23a2e8f63ec6e03a4937f0060896151c39cb7e399b6d48505be18ec76b97dfad7356d4006e7d7c1889381f87b2ca01dcb3da6a5a9875b0839eb2fc68b8bceaccd2df653bfe085eb67e1d73605bf4ed749be32cdc479bc3b9dcc6d6a85f1a410ece970d3751ea309a84628c2e88a96',
+ 'f753f3e9b4bd1895a259492ba160713f00ac8e24dbbfab0da7070e720b61b2b6f1dbf806debe99847eccdfa584c615d7b1313c68315affa32e98e93ca0d1d6ee623fa7628b743a53fb9c9af0340372816cd7c84ee02ee7bc6a4a9dba561ca75b72086ac464e8e4494053e1d35a1f728559249b9f8d434ca283a892b5d64b0f47',
+ '4e7c667a38bee08ac51afde3f22f2e38736a7f7d3f7b32f94e05a79ba19a809184e60217102abd8df3ed6fcd74ee26bbb15ca51e2b4909ae855dac6d89c74a3b6c7962a55395dfff1522f8b2430455d6662b7304870a4965f54b2c0f42c1f0928f9e50cd09e68f07b423603b685b04b2193fb2d75ba53b482438ee29d46eb9bd',
+ '7a000b03fce176de620f0df2d9d3886bee54014da45ea65bc361b13874bd9acc0b3c8ae924e0142ef1e0202cd2ed27c826b9a6e062bacc32602c7679f9555ed8d50c8f7c827c1d7ec42612062c25abb6ecb6c546eaf7926b13ef90fef2cfbc5a817703063f3cf99482e9cdc80f037dfde85246c5659c5fd086b4e60f88b41b18',
+ '9eeb079c552e421f703085b9b275d5b05c0c922efe14f2e78c7faefbb416fb1e6fbdbcf6d7f9f6c438af8447692f0cde5d7031ecf59d0a8018d1d3360620e358e9d6de49ae032c241237aaa0008a9f371adff187966a99f84b70549f0b4e9b6234bdd65d8254cd85274f5f8b1e8e7604bce13ac6888285954ce397ff6caa0c84',
+ '3af349f3647218e4be26fa863ac71381b64fccaa7e66761e121e308e2ae00ad9f8a76ae0ad6baf963ee115566861d87af2279d2932bf0d70d2bbc394d4a768a7d43f1c5a8ddf18129f3a923e904fe1e71099e28881869a21b62b1d87fb36aefe562427090db49c81689b3be5b87976f1980c657273a3655847d6060da8752405',
+ '13aaeb074c23597bf5557b221300ad3df211aedc75b198feaa8116f8a124d11b7fff2b91ce3c30881715c993b34f334cde04b03f0da67d03824103aa1d00515c75f3ca3e270f1b986e777138f4fae811e8dc462851d9e9b1a267fe748e3cf4761d1030d600a403f52203d9d97f07b3d43920d760e851c54e327b6e209ddea1b3',
+ '0136ea476e2e823f8e00bbcc7f9fc7272e951bc4caa67e1d78b060b248d66e4e67dd638b97d62198ddfe003a79e266111bc7981d5448cf814b418f86b1ec34e2f74ace3bbec52ee78f1341f6cc5d9d72e6a15ae5d155231cb54d8c2be7dea6b11744d25dcb41d2b10c0726065e5895d1f6ec0a242813a1781f9b02a9d0f4ee42',
+ '0c36ca43e7c113ed9fb71670b3ea73bfd6928c839f36db1a82d08ae0ff2c3dae199133a10aa38d1d3588ed115c4a437c137ce4307421ddd615c9863237fd5aa840dd05ff6c08bf66bfbcd9b43e3f95f45e7d3b21bdf2692e10caab495c474b616a646be675b850d0259c01e2c1901130a0dbb9dfe0722a2c5b1b20afd7d2bbe1',
+ 'ab5da4a64fbbf3c60f5ab1f7776ed6a55751e39a5ec81967ea88e9061ff9adbd373995451864e42c2c135c786d22f68dbfb7d751837f808d693b4597857c002ea6aa06a5e34b5a44768221ebced656f8df35bf6bbd39204869aaae3dea43c685a0b9df0cd6f9bed496b1e997c1135dae5fd68331337d616092db0d4176d7688b',
+ 'b7b3580daf783c070fa8fd143f5a65a18115ed1a26388c670299cdb71d6d247cab6882b63f2527753bc7b8998be191dd93935c1465f6e2b238ba228d160ea0e5d4c000a247a6d3deb53cb1a38a8e88f64c593314d16d4ffbb0554a2cf53abcb01905fb5931c4ea4a654f11b9a42bf3f496ae9ba2d264794c52b26c1c23b920e4',
+ '2ab533078b3314949c1f34c68bfdd76750f75105902c11e8c14ade47905f61bb7fece4f3d33c59aaadf39ed677eaff22813afd9fec974db6c8e0246279f3b29c5fc6ec16b6b48f2bba1462160f10bb6361b544a44846ff656ed68862f3159bf7106bd5d7fb43bf010baa08f01d181212368db17c6ae02fdcfc5493afc66d22b4',
+ 'd1a31b1f3a21b551dfc16baf8bb002f4d8b08b02f5c64331a732b7e78ea42c69aaad3df01e74c60033aa01f59fc0efdf0857fa8fc4f8d8f2e305b29e6fef86abf2aacac4395e527d586073e7ee606963aae4f6b30ef54c5773172d164e7f51dbb18108c21548207356c909affff93728c83ec8965d24670761527076b3bc54a0',
+ '4c76c4e416be43ac382abf32f44d9632a75c333740d8285ff66d7d5e3b1b48c5eb937e85cae409ae2d561b7df796c196c714bb8e70aa8bacaa7eccf10729c55528193e54303392a979bd065a867c59f439199d1846ca4536e82e7e99d378c3a469cfab5b30f50625842729cf894586d5643380ddab7f7d8519443c5e874e6938',
+ '34f6d2877d880c45408f53a1d8ff956146ec6b488e579f8e5e48ec8df11d04bd3321d8e22660138484bae7a0a6370d9da49a0781be39a965fa0bd7270f03905e829c2c930fb6e1ae4aa08cae8676ae9df6adb5c312ec7e1b3c1d1703a4c5c9376990560001317fa9da68c9334164814a844cfe77531926966ca6348b780ab831',
+ 'cf3fd262068f490c203d8ba57809e693ee284f4a3744536e77c55137114fe71abd8baaa6dc2b1aac0928d5a2f14e0a4964fb318eac24f9ae1d98829eed89cdaa4648715c9a508f9f378607241bbfec05098336a9dc11b7e71ca2516ecff2656491fd8e4de706902fd1de8bf39e63750f0447c6627013755f9b6b246e5e93988f',
+ 'f570273a4e5dbab38410e4af672995eb088408461e0e4730a8d7f15fd4693bc3205935bdbf1b4f8c3e1a1b08670854926673204b2a9a92840e7e7376b93c4233429979dd98df121622e84ab7a278a5c55fd032a1837f107ec27c31183c725ea4a55b7b02a3500d3a779ff926e01f8e6c3cc0c6b0f166c9070bf8b3ae27b397fc',
+ '2512718e7c139acdcd324303db3adb70348d09b058baf0e91d52b24952f832b0a3b81fa9bc9a2e9fb276a64e9e0922778b4992d892f6845b4372a28e47d27b53443586d9015463cacb5b65c617f84e1168b15988737a7eda8187f1f4165fecbdd032ae04916cc4b6e18a87558d2ce6a5946c65a9446f66cda139a76506c60d56',
+ 'b3fa42c51aabb708a64e4056402fc97bd8964820c09c4541523c99e2d9ad76feafefa7c1a2a519f79c229bc384c6e2945f8bd055bbdbf6e44da557c6d9af6e19522e73c94394db076da91ef7b1ddbca931dc824bb364099d465381a52705aca3e5dc2d47c42003225f0a515b921b60a397b2e66a6fde895384719fe68c563886',
+ '64971ce186ec2dbe037ca714f212f62fc863d080799e72dbe0442de3613a22c2cd1d4a1d85d5b946e36d23b4d5219fb1cbb9ab53d41670ad030b4846186e7ecb5c6e5500cd264bfc7b739e963203101b59afe7421a0b3961c43b66e06d08e6eedb334574a5086b47953721a251e0d1d33aed8d3495a4535de97c9098a730e296',
+ '33d8e9e9c066e53f1b7d689f82f33fb1ccd9872aa7ad15a125d1159f773cf0f5f87074526dac2f148a621b5fb9eb816c187a1724c04f6bee4d2d85c59b0dc88dcd141aa794c345c3ae6e9cf5acefe10cf99b661f187573682da2e855bf1d23ddbcac2411bd13eff38c87328ae46528367724bd423589f3b8cc1984796bd4c98c',
+ '6af0473b68f389d5b6f20efc60dddc2f3551e62170b0d5699877077ba4ccd8d7635721801b53ffb071e5d6ca88ac95906d993b96b3019af65af05a46f6c142c70cebb3dfc01e75caad8fb78c1590502a3a634b190b50a3f703f54b794fde71a52f5504419e7b748b3598b92a4db0966564571f93c2c579d25b2de1fcf84befd7',
+ '8e7aae5ed6832b58cf200019101822d0d54c4278fea6f5685b4c112626195a7dd14d5ecf03839dacdde4eda2819b1d57d588d9d68439cd2746160e2262dbb584714ccd4364246f1fc84e2b7a4957aa697524920bc3e0aa1ad4393fbff8ccc6abf4ddc263034ce8db1ac481477036112e3e8636c0c384d2698c1d6ca6f2d3d418',
+ 'c0b184c7b9e4cb8dd19af377306516c563b3b878baa250c1ee1605b90708b5527d213b8e9e87f2ef2ff7752e5614a930b8fefe35de27f153dd62d623363dd4bafb9131da3357cf6a80bdf724ff7a568e705e452b972d4ef2e1adebff4bfe9089802aec1441fd6de70a1702c1f33f24c8d4fa17c2ac5c6d87441fcdb60ff2f2a8',
+ '28aab2e4a0e55c11d5503c4dcab584545c4923a61b313c2c5a44d61d8213d523ac2629ba6e8945d9f488d2d553b6a5821b34ef9b2b2fb464caab7f8df37f535aefa1e4012aa407543f7f689f55907bd4aee1b5e57da9fb72f8165ba4af49fa591ca34d817b3f8cc7dcbf6475764ced913ed8db4cb8a6f89e0d0dd22a5f79b067',
+ 'fbdbc0f366d4678654544804b8d6fd6f171668f2832e4623cdff0785f7d2de51e83f1476634fa1de3addfdf3bf4234627c31391e24df7ca9c967be8f4e6e243320028bcd21c81cb4e55720d921df1594600e01a4f83406713da53793f45faa980becce02878aff90bd8a58bfc5f6c98f2c76698ae9740d03927f199cd0ed960b',
+ '39b971d28692e9a0b5781c9d4090e839a7ea7021b5b4791004ad14e8c3dd7e01b78444c18050aa6d1ed24e3eb33309b88a231637591376cbc3a49245215f239282a64f48f0ea147ff61feae25f6da4063f2998fa3803ff1ff6819f39fcaca7c7a309da905fcaef7f454638b0caa783cbcee23e91d9eddeb4a42c81ecdb2cd147',
+ '224e8d76f92822915a2fd36a510c398460090421d118ec654b17ebb9a452a96ef64a38a2f5b501687fc5fe2375ad2a33ca6236d4d99e7e42fc2b3b225a5efa1d00e24dce34b6c0de05790e6d27e695b4fe9b08e9f91e6463212125fff205b9c2699e35c05e36473c14d46b100fbe6250253ce12ad89f8610e3820f1a1350cea5',
+ 'f5c05a093ad994096deba25858e5c50168cff2f361b0280651b00039c37a863d34e44738cbd2abc3445785342e1ee92356093e27831793e1638b373cc64b83f20a86fb53d69996420c345980f8b82a2dcee4e48b53b1a706da7a72717260f3935eed9de2c5f8fc8eabc845c1207c3226b7a90ca83a46097c9cc5d9612f837c26',
+ '231b4a2a2e6a517a55f10aa8047cdf05941091df707f7eb077392096a265d703e730e8b65d65c5eaa03f8fcd777bd933b4b0af8c5ce3d613085656498ba236a2d505877e18fda45a2916b74828007f9c63e451e978f85d2cba523346d6fa86b0b7422f6aa65a7434b61f8b015f345aa9695481de0be69a6155d2bf75cb944d95',
+ 'ee59b47d837ce466a5c6361ac4f64365ce5007de53372d17e8fe8d16c9fcf409c2de23354f411a300281965025cbd863a17aa8a01ea09ade6ce29004218a80c184d7777daa97de8fdff8fdb0489cbdafc6ebb2671cad58ef55d89d1060a6a0fcfeebb93cdea6b9eb05d67322748f7bb3054c2d1a9787f1b06a87be22cc7add22',
+ '1dc026b6adffd69b6005aba5e5d179ec42620f8c75cc04565b8ab4c6d21685351ab76f50829abbc940250a4da0889ab56195c5805bd1ca8166cbd0d578ac28180d10d3d8cc14444a67b0663cc348e14b597d9a56dc4978331b4b6ea02a5fb67cbc725a37d495f9879d4fc85c9538d717f1c396f63e5c97d344b3950f2f57b6c9',
+ '8a7fdf734fe3e03017ce96e9a154d7e6a2a52578ba333b3aa713e697b9a6168c857835afde68b771010af3a010493130c251043a58acda45d3aad1c56407cce124c8c77905666768082ed506b1e8cdf1b9b7f20e024065cad00e95a6353559f2cd363cd8ac23179d9504e6246c78d4b4eea098faa03804520507db42147ae447',
+ '0e0e09152ca3b8f9e77d4f0781a0500ba7d8e5d202fd188e0976467b19fcd1c3c7a016a075109fc0231699ed886188ed618839a70a4cf8884b1e042574e14022acf02b528663121fd58e852dc2cb073a1b7a0949ee451aff57a9584d96b12a4f6405317488247be0a5eefa0e566535ba7cb43efed771e4bbd41f293aa6f7f713',
+ '35a2b369b9e1d7999354b2a6d3a2e301355f3d833ed2775588fc250d5bd5e7197cd9e1614ac36b280699093373e89d2e9f51db4b0044fe2cc20cb903600c71f87248a9cbc627bebab177d4a5a7b110700a7e08a9407b776a083936810e8967cfbdf6f3ee549238173cf6fb429984a48e1fefaae426fe4cd7018c82cf8cd43367',
+ '189aed1c0cf7700829333e5751bfd718a4450879e8836a3a2e5a2d61b222132e0441bf5165fc305b748d89730a75134a621384517d768229c470635af0eb374927800864674660a028e80c253dfb2047fc8e3bb99e020cfde91c151f0c58afa3ca804fbcda7e07bf8e6f50d6b4f806f9baddb41a15cf12a0e286cc17ce108526',
+ 'f08dac1d4d6a7ac4672b447a46cbeb3162f247ea09c6b4290004cda66d4f7746f4c8224921de4bc50668455325f13a0890526da74e87c11401bb7f0cc6a554145d1799af8ad4d7d4baa38b9feaa12647c5db58500c1c8e023b04ba196a5a52be71a39bb64ff427dacd049cc75e85b8d64ab5924f0b3023d9f70804352017792c',
+ '8c84810e4c90bf6e1e88c8b944398b35c422d48c6a7070680c2d913f11b474713468409086a532feb2f7f7be858a5984aee21e0ec2cc2db78395f34a61790514415e073d7ec3cc582df3be38a67e810540e9d3905ba5b7e4a43ed21e94d5157e3ad09cbd3bd0d6a117e3e7d0adfc4ae202a0bbb93ee15415f790f663b2afead6',
+ '8ecdcd8176d8a164f6259733bc77ef783b48d40cffc547353d195912afee9d399e31dd9e41160cb7455d7cddadd351f6dc1b3651f0ae4ed152216d4e8ba789385ad66b7d03aeaaade9d7da5d5f2a01c9bc734abdad75feb5d02faf437e5eb7b1e843e1e765a665900a1b1a797c84e73902d77a17de223d28decc86b82e1d0feb',
+ '1c4396f7b7f9228e832a13692002ba2aff439dcb7fddbfd456c022d133ee8903a2d482562fdaa493ce3916d77a0c51441dab26f6b0340238a36a71f87fc3e179cabca9482b704971ce69f3f20ab64b70413d6c2908532b2a888a9fc224cae1365da410b6f2e298904b63b4a41726321835a4774dd063c211cfc8b5166c2d11a2',
+ '7c287ca52d40f53f92b00432984595cd20e644494ac7c3a4f3e07cad7c9e785bcdd880629a048208e5ab3635c51a00ca655b19344f63ea41eb8db83242478611080b3745da92f463c444cd4706f2a36418c74558eb7cd9c372cc7e5a61282f3735abea73745012f73663138fe4354441401411dca57a59d39085154c60a73b75',
+ 'dd3e68b757ffe06068e52005889bfbc1b43bf0a11164f35cd38d713e5d998e66a9abb131eb3b42f6716ab2f4ce92bc883722eba42da95d7c5d30c682c4cdb795167521756112157bedd5cd8768cef0393fba12644f1c7abfbd8f29de225a1861ec45c06c01abdf57a5d17aa69d761e3b94ab6ccabfe5d58ebd51a13ac1673633',
+ '0a20bb48b5a3e4f47b2fe7312c223cec1271936281eb0a88afc2a2aac647f45238f5206b53b107a61550ba1d415a3137b20d41cbf0a5c88801db2b9482ac0273f65b112b5db97ba509a43257adceb220b7c0ef73df1e8bb8002c4def2791cf97ea5b76cefc44a7b9fe33382697062570c68f85a377dcbce155bcf105e07ec385',
+ '166cdbea93469428e66efe853b6c4df9fb13db05f4126deab4c5b81a355124ecc0efcf930b88d551a583cfe893db99523c7459b182afbc89323c832d9e2f3f77885658bc42ca54ff14c55665deb3e5e9fe8cef5174600e614434094e1c0c9e7637497f4d81359a9bfcdd9de5621fba280c03a8ce124feadab4555366f910ca4f',
+ 'c2412a6d1d52d12c0a54b8f5701ea58adaa11a767ad57a9e6ff46c1943e78441b8fd210ac4e39193dad17cfb6b017f76ad6517a09b99c1113d175f3129aade4d4a2516ebe054f15bc833d08ffe5e2a2d60c976e1b4b14cf8edd2c72baadb2db8001fd2b8798d39ac5ce27d592f1defd67b3301e3cf05637c078f6baece62baaa',
+ '77517dbfda50493a04445d72430ea3f6fd54bb31fc81f2920a0d72eabefeb61595af41dc44d0901a4dae4d1ed1b4c551a5329c18a85ebffc53999b0991f38d73d1f099805a8d5ea1df7e49e254ba0a85003944ead2fc89b3f84f8525ae4b79d0549eec72c48f9d19e23cbb88752658dc35f01c6f246436fd22b79805bc0e6472',
+ 'e88b88545af54f3559594239f0e4f0854770d576d3f02c2aca0f0543da1497e71a09d70b411c4af2164517f027296074be3fd24611317b0cb985dc13657c404cd03a4c95f028d63a7197fbbc61a66bd12d6508abcc3ab07d3a84563c287f58a3f2680c79d1e19c16529615240621baa37b2b9e2f6cd4728635559b4589e488f2',
+ '02140f7b50f2600961ced8b36dd48b8e3f70c2108c55ef2d83c4e6c0a50b492dd74c4444b57f7b692aba41f23db00bd12e792473c291a2e8db2298434b868d44ea072d34e7ea3f115badd7eb248ccd8ef04a6d61982d708eb04b2c635c0407f964d031138b3b93481d2d0265c86fb90dac6b06a2b533436929c508e87d8e9f93',
+ '7b7ba2a854840b24fd75ae12ebc2c6144bb2065c95abd31164b0b0f58528fa464ee1d5e2315466ae912b4337d300279ab968eba2eb30b131d7e663e1bb9b5cea00e86447ca2fe214cd234d3b628be44fda439fb81283651147637fce2c9f4d223a983720489ce7205b67b564bfea63fb574b0be6312c557a5d30ed0500bb35b4',
+ 'c1e969ae81507ce3dd94ef0a21da24935129daceca79f3a4270d7a856203e4a13b2a965bde13a8fac06be9a2ca872384b941a051c503ecf48021dd80026cd167430437eec86d51dd82e5377bf3f520b99247ddae71b7a6431dac1930c5a980279f1f534e8886fef3ebabe37ce34ca39ca4e299cd17bea8fac457377bf5e37947',
+ '09caedbd5568cc3ad0590b7d409fbc26547a2a20d9d0b22630d2d58500dd8b23289ed9c0f87aa57ca02dca99e8b1688322617d0d5d5ebafedc328fccc7b389a71f2addb9f7b545ade2ea0a6ea8bd62313da4fdb5f3f9dbc9ee9f6010d8e8aa01d7b62231bce151d57ed9f682e68d55388b8bd19f0168bd904e6270d79d449738',
+ '08df48713db1b8ab2b51e05cde25dc3dfbce1b12045bc181d8bc492479796fdd12a44d6a390cc43971b31d7df382f081ae3c453c8cb1fa27f734654b9c4e399e6eb4ae8fee77dce0aa7b68b4042a63e935696fa792cb24390d05b21cfea3c75624f9b309e65bca48df9109299a85fd1c9a3fe17b9e130762231979c029dedfae',
+ 'a204be1fc04372eed3c9e5ccd1435a02b357317e78960b6e6cac2f0eaada2dbee0a7c15852d2f9c0228a9abdcee1c107fa7fc6a170936568651020edfe15df8012acda8d32b8b82ce629f8f33a72910e793dd592395d9b0f97049d65c4361fd8c17dd26666dff757a90dc7171ddd1341b9fa28fcdbdaf58a8cf1701e062535ee',
+ '28be0d9e62dc89e2a913064c0d3dbfb35a0c7766f756741b0eafcc28ed3ddff6adc825b211112a45b065d6875771f2afa958e80f0803cafeb9b9961542efb99e1761d1497661b721906fbdbfe90b34bd01c7326e34a092ccdf8e3bb2c45aa64cb0b09acb5b753a5d8f5a425c8cb28ec5ac81dced43d5d26fc95943693b27aee8',
+ 'fb091ddd95b100dfcf892d78e5e770d3a37b8c3885df803c1d6f0935b55b68f136fb65a84862942ebb35d76d26be2413cd3c8988c87d6d2362af189dc07476c6c33417762eb77bc70cf38d814c226dd6af187250e4d47007f1553617d4af5b516a5d3b3191d93c10896a569ba13dd2840fb851781f0b115090086c8b3a34a1fc',
+ '9f63b0edfaf83bafce6c4e680bc075c7b3baf15733e5aea7f3d975a82cbc6356fa099a9ab290366f75bf8345051f6da2d821370f6b1b7032d98e2338acaa4f76f314964f95e63958e4f844ba755e06d83031c432a393af899bed1245f67bd013b30b0ed24b012db0449ffb9003832ab0e2710188825351f5637eab96b137d076',
+ 'b02dcae915a6a6be9d3c9bf3fc61a99ec3f181b4e3b0321f6cf304119b9da497144d82716cd67821eaf0ac428f2db71b532e0774b21681a8673f6bfc782c8a2f72bf8753f6ac98db742e5cf437f90619a26fbde1b916431ce34ad51fed2f535c53eaa136bb114d13c35f72b2fcaddcbf361d6ca4ff99bea3667c0a21058e4845',
+ 'ee880b8150bc9b86607012a9a3e737e2407598d659897ffc9beb22fe14411a6245d8166979a1d137557a4135afaf12b4a4c152d3e4666ea251d05d87c9321be13f8159ec117873e595dea26ef50b73333ea977ceb3b83ce867d47da10bbb9632040a3ad1c14768d64b249b1b1d0242a837b56f906e87d316067fea1482e3739e',
+ 'c280f5b782a0ba40a15699d680129b7207aa89c8ea94511c2b59aa57e146fb5a37657992b7ac90ccc973854b762c5918724ef09a5a9273663a62f258528e4ee31a4256a58335303f8022fb63c57cb22fce5e53b924c141ebdcf1e79160429fb072fed2196da3603fce4b4246f46c6e5c24c1fa4cd088855019eed32792c8b768',
+ 'b949df3b02871bea0976873a9c76942ac934ce63ac2956d2856492970d8a231e0b1b178b22f6605ced2085494ec1986f026f68ae79aff750e5b92feb927cd08875e2ad04075518b754829b544e5de910686513076029ffdb5c0b179e39443ef22028086e5aab2a4465252f2147526d55229d3834099e55bc12e1b178ace953a3',
+ 'd1d94bc59465657e9cf4020239e6164e00c707f8c4764d70c2873b871ce51c2d89bc827f4a96db0160c44527fcffa41b374ff1ba032cd5df61e376e5d53c9167175ac94a0ce23efef4606200e5e608a478f6be11c2a15d8d86f1defba8856fa1e57bc62fc293b6fdc2900095dce26b712c831706e91f0e0197771cd07e07e164',
+ 'a60c0e1ca329b27be58968171049a625d76154731e341b9e6066df854fee8afdbb6c0cc7b5bca0bff4cb505578a9bb416ce0167351057149598c3b0511e0097e43b493161b93ffeb88bf6352e5388581d91be58b7c2dfd92bbb8c737fd968056078bacf11cd85a69690ca9f4a11e8b4be5b9c9a3e6d747df4d918a045b3577ed',
+ '4996ec69eb2522599ccb47ed1dd6bb0f79b585be8b68f419c03585b91f9d0844868eff3f36da472491e8fab523aa938fe0ce5302ac39e42021b13d148cd9c5b63863bb5cf081d5f2bf9c274dfa4947bc8079afe041ef62befdf8d3134e5602e7e97de865210215eaad50985caa9d1fbde41c5f005174b61bde720f5d6efa0702',
+ '01069a2a048aac5791e0e922efcd5292d7af1e19c0b3156d60483a936fd4ac3caea5ce55282aa6dab76383ebcb96e321674493226c5b18731aad4e8ed4a14f3523289605fef3654e49e463229bc28aac443040c38fe0c4bf4404cc8c71056dfd6a783a620f4eb05c4d4ad2f0e8b910db775d6d25b0aae1f9e535fcb4cf69cd3c',
+ '6ef99052e93de72a0928886350c3a86b3e1b75c81beffc65f0ad4a29d79dd1ce745b0ef1c48a696515c75dcd56dcd86a9136e531b69a88219a13e9d33f2fb553566ac22e02ebf2ccdf6e59004382a2dec4f4aecdfa8b7fdd86f5555a520216a11b10f3322dc749076e06c5249e1ccc70dd3c1ac36e2ba940ba3cd4e5987ebc60',
+ 'e36b3b02b86b02996c1cc21fcb70b5b30327afada1f0afdebcd1b41970c8d2f18fb384c5926d44fad63a59880565f1b8d1276f2ce9cb061f251087ee04cf77d759dd650141337abd584c520c2dcf0a61f36e9ba8790e66865c2810e37b6f8fa6abb385bfac05cd6b5c1c54b32bf72b36cfc4da293901f69cc7e1f6ffbbf142e4',
+ 'dde1c090446d11f936517eac73d6776695c1ff3051850e32fab734cc46c280e355dca079ef3949810e7edaf19c783c187d0e0c32d074fc3a72a276ffc405837aaf74ec5fe5659ff26961531c51b56fbecb6b28455e78ea7f7237faad131659d9f290eb69ac5bd8f54fe233561bf5daff85bf9d9182f9a2a9015e07fcb95fcaa7',
+ '99958aa459604657c7bf6e4cdfcc8785f0abf06ffe636b5b64ecd931bd8a456305592421fc28dbcccb8a82acea2be8e54161d7a78e0399a6067ebaca3f2510274dc9f92f2c8ae4265eec13d7d42e9f8612d7bc258f913ecb5a3a5c610339b49fb90e9037b02d684fc60da835657cb24eab352750c8b463b1a8494660d36c3ab2',
+];
+
+const List<String> _keys = [
+ '82f3b69a1bff4de15c33',
+ '4766e6fe5dffc98a5c50',
+ '0f942d98a5c406155967',
+ '78cb194a958fc1b95e35',
+ '2baa6731c367e0f818ab',
+ 'c1f4f1ac1adf93df6e58',
+ '5de237ba1edadf54d566',
+ 'ed00f3c4c227d07cf2d1',
+ '3b6af34ae3ea52d3962d',
+ '6445f6d884fbd57a1eec',
+ 'b9ec31346806acaa9221',
+ '518a96ff0a44f95d97ee',
+ 'a79032a4f7f740f6d13e',
+ 'ab6b1fd8231147512309',
+ 'd7f2be75aaebb90d87a8',
+ '1379a7afcc0905a5fc81',
+ '80a0db49d039b316ae12',
+ '261812249e1338ac5a22',
+ '07a27c1b24094dd9a0b9',
+ 'aeb526731e1d0ca809f6',
+ 'bce413c5612019be937e',
+ '10fd56ddc8f64b9fd800',
+ '8b09ea6af3ed29288222',
+ '71ab12ca4795505deadd',
+ '5f24aa8bbc1eca3eab79',
+ 'be881a061074ed05e5ba',
+ '67f385228039427df681',
+ 'ed01edde5f8bee443346',
+ 'ab692b9e0d9cc9632754',
+ '2541c892495452ed89dc',
+ 'f5731a6e8925f74306fa',
+ '290566d777b0eee984fa',
+ 'a7e54ce234b0d5c839b8',
+ '2918c7779c43fdf21748',
+ '9e8c665ba53854f0fd27',
+ '41164988752465a8f929',
+ 'ea66bf3a628dd1a968c9',
+ '14f43e5424ac9aeb97e7',
+ '6251c2a2976b8757adca',
+ '036fc94fafab92ba5539',
+ 'c07d47559b6759f09651',
+ 'a32e28d4b458ceb7cb13',
+ '9fc05ef49579aaef45c0',
+ 'fe5df14e5888fad138ea',
+ '6c56890c603bd3833d21',
+ '59785928d72516e31272',
+ 'c52109c9d0da9258eb73',
+ 'aa6197d4afd5eef5187a',
+ '9e0be94ed707458d5cec',
+ '65e06954b0350fb3db19',
+ 'e89defd40777fe173167',
+ '1501b98cd2b030d62660',
+ 'bc28be9d8fbb1d766360',
+ 'aff7d836880232f8132d',
+ 'efe1c65a8a230e96cfa6',
+ '4fb2514d3d73b4770a69',
+ '1b6c5146ea28dca9f6a4',
+ '2d544e003b09cde4a4c7',
+ '1b5cddff531babb51b4c',
+ '8d8d15d8a9579adb2d62',
+ '191a700f3dc560a589f9c2ca784e970cb1e552a0e6b3df54fc1ce3c56cc446d2',
+ 'dcb463a13ae337414151a31aa0c3e8bab3ee781b9f3aaa869dc5b1b196abcf2b',
+ '93e7402cb2b1b594670e656a6ca4ef247231ac09b7cce194d76e3919e4b072aa',
+ 'ac286e206d88a3c00e6705df211b5ead6a693625445351874131790911037ec9',
+ 'd50ff2c5448b5c2b695f61dc55de55ee96f7bbe57067ae856a2d80e50d3ea0c5',
+ '607e645e1bd7fcefa0e34602d34471dd71173130ff1c59530017acd06b76f021',
+ 'ba60ee3734a54ae42cfeb678233ecafd8d55c783ca742865577279cd466f6c7a',
+ '861ae84f596bd23cd37970454e8908686022111154b546e1da84faaefdbcabcb',
+ '304e23c570eb7887270d73abba9c3268d0ae42aafb9e62c09a5e8954fe0e2aa1',
+ 'cb3c6fb3fcd464d5d2dcebac4fa41cba7a60706d9c888ba1af7e586714725b05',
+ 'd50df8aba7273e6427ea6bc0a4fdd4d5b0364f336cc696b906b1edae7f82050d',
+ '1daebe36007d26b988f8c4fcaa0b5a07658ef6ff528325927d98649673f4d7ec',
+ 'fdefd6dbd43cb817b132754633c0ce724be5572e4e732b7d4813ddef9489b20d',
+ 'e32e6acc16d4f6ed9cc3e23ac65a259c65704a3f8437c598576687a76e97d079',
+ '128ffb7d52b710de97ee921cc9d2bc5e0750d3a2e10dfc49c80550d6c27332f3',
+ 'a12794057de3b3ea426fbe0195ee17b4873ef7e6ba87b22bc6143c38da62ec98',
+ '2a432b462ebb78835008b4aa8a92b40f6fe9dc53a963352ea507c06c8da90a36',
+ '232eabc478501f246e73e76bf0227e0356a4161f97687540baa702fe8e442005',
+ 'aae20e01f6185d8073f40fd7648098fcfaf3dd8b6c7becb14a39ea480e8d4c43',
+ '58d259d3651b6533f98cd0f7da9cc4f3a251bc02cd063bed116bbe8feecdef37',
+ 'e0421039b649a0d72d2b5dba7aa02ef7f1f83303bd0110bdd32b89af29ea5091',
+ '59b818b12c95be441ff52d8bd19286300f8cb877e25ea4cfcb117fa74db07782',
+ '4def685532999b6352a6741ba47bd2aa393961e12ae4267ecfc558ad310c72ce',
+ 'a3e983e3e959ad38b9bc4b4516589b263ad2c141884e5c84c2d65dee7c001951',
+ 'b1b6d5e0b9b1efb608912da48d561f4489102abaa09f399631beb0fce340a202',
+ 'c913fe12cb76e574a23bf46c9032105848ce2c71f61e6d5880ff8cf20b917d76',
+ 'd3dded60911343bca3af35d2dccbca9d2344b60c74b4819e27a0e62f75f37a12',
+ '04d31106098fbda19af28e84339c736eec54e5859d9f288f4591ce64ade47ea3',
+ 'addde2c62bfa0722f73b99add65f2b3c9bfdc93c4b1839ec7ff380ca0a26a94a',
+ 'ab40bb089199ccc0ea49c6f5216280f5dd3eff7c771f8f7bb1121217a51999f5',
+ '58102423a4168fa60a5aa7f79092d52326c98e22ee5f3dffdb527d397dbb8c68',
+ '816aa4c3ee066310ac1e6666cf830c375355c3c8ba18cfe1f50a48c988b46272',
+ 'edbc48ed948cccc421efc7a6475a2dc2479dd9996f5e2f10e0c600c3957aad9d',
+ '420e70ecc3cdaffb726a183c793845315f730fa4dac9fe46e4180397107a6a05',
+ '78b8b8aa70fcb2b0cbe835941275a5405cef6d8013aae759f6f17c9d643f0cbc',
+ 'aa01f699da8d42261e3b04ba1389d2631e985fdba28a4c0a762e40cb96df3af3',
+ '6733498582e94a58cef983b1f52f215da1612e8e48f605814aa9095d398b965f',
+ '3a239ff156058ea4ff05e0f672b7ecb5d106fad5d31e9d6fb989430a84970a1a',
+ 'a3abb893aa5f82c4a8ef754460628af6b75af02168f45b72f8f09e45ed127c20',
+ 'c3070d79ebe3c6a98ac13e50ae4710e602485a68a04329fb272c31d30d6fc253',
+ 'a9d599a9d003686e2a3b2a27407644b73bc4d7c7ef3ee75d193cbdb0e5c8893b',
+ '8ef73e17f2dc9e063230a3352fe5c549c1fd526c43f90f57539522b0d3b22f97',
+ 'a535c38a4f69ccbc134306f5f158019b7c79992625e462e9bcba4a2f34b4798a',
+ '2b3a5890de01a30f88d4f7eaaf702f6129a5e7718dfe8f9ce7a4bfe8b080ca2a',
+ 'c05d6b83a27ef65cef5571222d24adbcc18958640548bc959a4baa2b00e7b0c6',
+ '895868f19695c1f5a26d8ae339c567e5ab43b0fcc8056050e9922ec53010f9ce',
+ '950fb0cde30f34f597af5caa2b16fc86a5c3ef065d36ffdd06ec048eec915039',
+ 'a31acd1af261a1e7f751140a580b91d476792a9f96e1dd013fba1645e2bf761b',
+ '8ddf3be2ab49f11f12f392a09f5b72fcddec1e186dd3e49aab0e95a08ec589b1',
+ '90aea6f7c6c3815718ba1959ececaf53128020b7039a51e766d0cf4bd9deb7a2',
+ '5e6a489725810a85fe4505fab03d3b3c78771075e913b759f701ea084e0ade36',
+ '618406f43dd79acd2cd384b3d12709e43d267d76febf63ed58afd60dd2f528ed',
+ 'ad445da48d46abfef103f9c6c5473444ffbbae90275cc4a8162bbec0fe26f6d9',
+ '05905a6ecb1679364090c9510f06fb3c0e09321b21fe0aad5cb9d980674e3561',
+ '3e9eebe9add8e8315892c6b3bbeb77abf60dcdae1961e2839fffb73538691b66',
+ 'c116c698b12c153b57c9d57d4eeb97f7dd8eff14cc2a2dbd767e7c35208c6f41',
+ 'ff73004a8aa629ca5c72414ea652a6533fd282e847a492650af12c5926ed80c4',
+ 'bedb392f8a77a470858a9c366b7255f3b25c9a5d10b76d793de9eef8fa407ec7',
+ '863bbe40cb6694f736b532b95e38fbabe0e49c15f7dc42c54def09ae1161b7d5',
+ 'b476d28aeb5fac74fcf4cdb1ab00a38571231db06624b4586588ac436a649749',
+ '268b0e1f110052aaa2eee327e34ab349029806daf702306867a7a03bc8351d8ac7ba50eee6b783166a77a8bd749e9dd96e05ae15a8c55c8243925c894f4be325',
+ '77c192472253685d52a6fc393bb7a9d5bd73f5af2b6e742050d7eae9b4acb00f1b2a59ea4f8894781fe454f7a87e2fb2d324041b1fede11aa12a24a5499ae091',
+ '79a557102517e406b26557d026cf06429a5be840ecc0f0c9b38399357860c3ba23ebbd35b377a3273237eafee8a33997d01d7a0048d532820cea0ddf65d2bed8',
+ '3a4182af8c3914d1df57b6321fa5dec68748ad746e0369bb64fc2d9b7dc3dfb3ed9063a7d5cc0ec45dd35ee703f9e89a33cb9181179701f5b02e55ee26e81426',
+ '3510c8f6da91371b5c81468b714d05284becdad01d5a2476dc481f784312082c19f181bcb6723635c426c1da439bcbbecf8c74922655f5bbe5a984a892877962',
+ '23904039640d48e163676d16198884a825604ba86329a1cdc0f0f6164d5100b19282af1c2493648a7af35e88fc3774e05d170abe2bb93e11a4336234cc4bafce',
+ 'd4471c7f6186e8c0ed3dfa2b0ef2cd184d6041c0921ea5fddc7c155135ae062ae62c1f64e7584b1099610c74b76812528ae20c6e5d3ebe4a31c75334b2cbf582',
+ 'de6cc5a186dc79b9e21b0578b5ac6e2440a115e713162d7522fe72ee1b221806f7660263d04e3547f2c28c6e340ead3a892d3b0dd2474ef6f678209135d30928',
+ '8989b2299f9db5a5df0253a97b775c94e8e9195ad698e1cd6576e71b96cf5698ff2fa0bec4811272c274ad890d23318b9df47ab744c00f47e335f9f5de79d1bd',
+ '8f55e53e046e6d6d64c4468d44aa49a4e07742dd04d8f4812c6b5e22ea893d1a8863d234ee50e5a8c7650a4de047230ad03d268dde8921401ff97b79dfb97cf2',
+ 'd5bbd2a2a536e6204259cbc2aa7e88452ffc2a5270485cb8876038fa84695d091b964252994dcafb1c85186a0473a408a5658e443eee33da2f43ff5566e582d2',
+ '4c34132786865ebba9bd1aa5d2d3675637744f7e5e619e8a8e16f36b84ab189a66f88f59fdfc6d3b1e806ce669f73b1837a918e8cd10a14fd682e7e61011c5f1',
+ 'd7931174ea188b2c8a1f045978346592014283a1d20f992c0e06f5959e39f11ec9a6255104b9db9f0b13c347308ae979f371e3bbd4194f8d65977d48a3c8684c',
+ '454262ab05cca57ff00f12d653f08a5e2e441e324493c6b86e1b56c93418af139e4332bc48997b48b55d4bbde560c5052a80de93376f0f4a7ab64c9aacf93aec',
+ '66ecea6ce6274578ae5283c8de9576f5865a38c321b9ca3d5f33fb0828a48bf1dd7391c8e10c1a71589013382eca69655b666e10665d7f3728b4e40ed366f796',
+ 'b244d305bfd534de7b05b66cda0b7bd3c2414956b5364611b0feffea53cdafc541c5bff7ca0b89fdc820616fc66fd62f682235e6073a4fb19bdf7c17def4e03f',
+ 'f3cb2cbaafe6281ebb546af88c052e6658a58407cd7ba30502918052ae159f3198ff29f94ef440151a6a8f50320e25502f62835fc0abf372a00a1c63c5e9d482',
+ '5ed96404ce1f0ae00c32ada5f605c10253d5de41135f211bd84fd0d1b6fb3c783751ec94a30ef7e97e32b28e51b08b43ae6935046e5b06df3d169d025970c718',
+ 'c92660b2f009f47d3589c74e22daca9f60d0147fcea28e7cd0eff0c5eafeec908d4aa8ba303e72ada33db087a0e51579a4951b6cfc2cadeb2314233d4b8074d1',
+ '2ab04d9a3af659171d80653a1f7ab9bc64863e6ccf0f882523d913fd68ddcdc09155d59d5b13831e7816a85eed5f1776b9016438b778eb20c53b14872695d61a',
+ '2c66bc60707a1da0c194e5422ba022acd049a0058a0fb2e9d2992e61e14cba12141c46b495a2dac6386f9280a3a1e70ab2b42feb1a9a67c44c0d313e9c241941',
+ '67856f8f84dba19cb38a23b0efad6eed229c536f45753f81c8fbbe1134a43e620fed160100f1c6fa333a804bffd7e899c6ae19221d14e8f32d9b6c5b592bbe9f',
+ 'cde363485e01d4d36242665f35a6e910b991fd9041211c05adbfdb40d6f46c372c7e68b69da4cb51b9c6419d1438a0a0ec51b5850cbe4394f01c49622ac78445',
+ '74c6bd81ed71bebacf5f7263cad715951c690afe4cd127e41b1e5468b813540833cde26834a60052ed5a8cfb4d68148876bbebd0728a7c64217ddfcd7611aa14',
+ '18f10073e71422a3d223c1a95fdfa6f3d5c27172f0e4ec9ed91f99bb55718d5b3da381252e2827d48148ba837e7ed927cc1e955d2c3ac96668c7aa6f85fc9e16',
+ 'fd4e7dfc0c21461f69fb237fa283378413f1e5d25db7e613146798f6b8d19977e76b9562d0f75c12eb5f387fe8e47d78e577612ce3670eef7b3df63bcde567f5',
+ '0293926e81c051a6c0945d2594644b824c100c368a85634751869c245ead7cd0bcac744393d9190e41ead93dabfce681d5db778fb17d30c335cfde09b0b568fd',
+ '75dfc0b734046aa2ef9d82f7596269e100793e5223f853a2c3a5e179fc00faee9683c0f0d828d5e59c2c1292a9127c3b3cec730be8d62db6a0c3635c137c4ab1',
+ '8af2e72ed2ad3be1e81a21e6fcbddff62d45385bf061ed60b6d58306c9cd47f8777190c173b9443d78839d4d2fe32dcf53ba20ce138ac2f5b888414a87f3b319',
+ '81b7e464796841368cda2cf7048055643e8d38dea614abb3e36db39f4eda9c93a96a49b40e1ec8a7254b290c9a3f9148ce278a88cd319d0381ed237f25f95816',
+ '8eccd467d875839cb4b0a0170a976f6056876859fb242f69d99dc6da2132028068f33b9cfbca48ff73bbaa73896b08562bdfdc88cf876b88077bfad955043fab',
+ 'b488332a10f2bc7d9042a1933da85dcc892504be3ea8d57bb5780f1648d1076309d276ffb5971790e3a2724e817ff2c381a73eced0a6c6ee88799cbd663a86bb',
+ '9dcb2ac482979d2b4f69b86154a66286c10a73dd5e8f0ecf7d9031332e2e8accb1f38d1331b5c337afbd65633c29293f6b8f5cb906e33105009b59e2ab10d320',
+ '5f360b2be1b1d9473ec74ffe0bca455c7150cfb2d33e0645b1250c43cdd24afb8c20fc4c9e11f05ee11d8a9183ca0cb3687d1476cb90672127a4ec855839fc33',
+ 'c05d6b83a27ef65cef5571222d24adbcc18958640548bc959a4baa2b00e7b0c66361926fb8b1f87e098565ba0d8968c3fce616ada108b7eeb1a5c07a5bfb022c',
+ '2af1053d2cca20406b7814ab9013677feeaeb773ade5fb2d27b50bb892916333e0b123c6e3ae5bdbb54c868a579654549831ad1538eaf2344e91861de70a8df1',
+ '9c9445d7df7eab77c9a5c7afbd2f38707d26efb89d1d415938173afce1a43565dc4da9f98f32467d33f24120cfcbecbc67038959708660f388d00f7d640d2225',
+ '64169fd4b7ba1e5a62412b8719a2b622d5031aa777cee7f5ae06e4471adc5465b27d791c632f57ebf99cbaff436d7a62721bfe6fc302ff895eb88e0c7d9c5984',
+ 'c49505be68196bf7b874b25353de09d677a847856a1477d5186a9464fd4891e7453a9c63328aa4a1bf5a19dc83eff3bcd750f5883b103397f668d207fd890fb2',
+ '5a905c63f9660429ac7b7be84766c71ba5a443458fea9fe3e0ba289fe73549c60d3052fcb889792f6fbb1fc93eb1542a5cd89c550b78f3e9c04410548430e743',
+ 'c9b74b2ba807d65ae62728882a32c4c0a0b2d9019fb50ced8a2477c5f451f29507cf91ac26866e4fd106a8afc91cab1875a3b26a859d8bcdd5839aa194d921b4',
+ '3af349f3647218e4be26fa863ac71381b64fccaa7e66761e121e308e2ae00ad9f8a76ae0ad6baf963ee115566861d87af2279d2932bf0d70d2bbc394d4a768a7',
+ '23d992873b968a5106f95b3693e230420ae819d993a80ba8735d29db78b2419098d49a8cd5caed2d6409b1a00d439b54d58166afdb71d0ff8001e5b3ca2c7fcb',
+ '2e4a7b49eb4ff970dc932c156e9a1a7be9616217009c6ff2a742f14f244b8e8e69b9d450a1d573dc09bba9c10118fdbd633330de132a71e7d77ed0f569d2f562',
+ 'bbfc60ad853142be6f602fd1eef95f882f478915aaad0ea0fa2f75e8ec33172ed6891b4f2aaaa5304a3d4b5e9ee0c9f6e524f5c3c8d9f5a7b58daf3cea4f81ba',
+ 'b9575f4d5ecc0f4f62e4a0556bb89464ba97d4570e55acd4c5e5177e452a3d6c9a0b3adb60c6211fe48640e08637a6826299e3e52f930f4f66cb0ea6a77311e3',
+ 'd291adbf05b06596c2f36f41a8cd8070370c42f687b8a6cc3a3e7b59afcd40f07801369b0fbfba17c460d21ffa1106ee937971ffa99d17177f017985b71067a8',
+ '902c2af0d13fb353f14a93eaba7e8a8f768eccacb264ef954114071b840e105ee9978ce2b27a6ce5f8fa34f0ef0c5bad6bc3f0f8a30c8438359b43f06b256491',
+ 'b9f4ccde4dbc27f1e6bb0fc9e854aa084249029cf32eaadacd1ea5d178ac83d8bb1ccd6af7d4a334f40da46be0ce0e63951b265e1b6adba26e56a6ce8197b46d',
+ 'a1aa034687ddffdd659326c6d11f58f1451f8524c4996da8c04aaa433c3af1662e9495a627b54c70358336f909001b75551ff58978d6ae025d742ac7a035880c',
+ '8fc7e719ff492846f151bdc5f6f6ed15a6452442ef42e806ac2a0f3479fb2f56c63657952be4fcdafbd736331c322d78162ccd2e6910c2ab2488a07bb31c6103',
+ 'cd7fd6beaf8ecdada5a4dfb800617e9b5b83bf23215a0340507cd65c7cb917eb16515a43ee658aaef7acd3be4a67bee16e979e35d76d2c9eac026e15ce48dd43',
+ '5657c22933cb8f8ee35b3ab821ab6b01ef8554252b1ee4a3639b3d66ead369a52b5748083eb0cd0cb9e76aa8c94bc931816ebd7b717178417b81fec6e2a2dabd',
+ '589e1c67214c34f4380e1bfa3629ce139b297b3fb8318bd9cc90e0ca6d945bfc29a3a2126e872056a70a4df2a8c32f644c2f212c5c04d3c7b3c192e1a08ac9c7',
+ '95ece1c8ae5e94d16ec9983b1089a37395ad5b1d660916c13c87e4c13dbecf8f68c6611c324a679471def5487a93aaec86c935025b4518962884ac2cb04e66f7',
+ '91650ed89aaa63a8fd43907daaf3985c6404ee02c23b92777a0b7de6de093faca7a0e7aff20623f1886ea8656280d4016d0692148ae87fdad95a4b4d3754613f',
+ 'caa2f077c0bde9e98c2f54a98caba4a9f95de80e742bfe92e23b03267ab50ddb1cca1d02e5f54f92008054cbbf4b2219eac9ea3b574b4ba4ba81c522bf3d70bd',
+ 'ac049e1a39d6039ce480416f058e06995b54a23c4d26696b76cc583c6130fc1f915a906ec59e66457a148893b0499e71f13412b3906c73bd2f98179983260546',
+ '82c16c68eca59a92986938366de60c16f60c98bd66d43e10d975a826dcdb67593055da9dcb8e521120be73d4a021de1a81a90d7fbef07d9b5f7013d6faf6b97d',
+ 'e262a7385aa3282c5d4298376acd1b7b6c978b029a0c75ac9c41656cefd064b48ae2be2ec28d09ad6b616263403dfa548567d20aeadcc28bb3e5c08816eb5fd7',
+ '150d3aa309a3669af99a70f2cec52d3da16b1c137ff7466269f268059f2f54981f45958b68425276839e75ac446e0b13cedaee3355d1a28c28fc7e2deef00c822fa7b26e1731',
+ 'c9c8b891b82567757dbf1a15b317628d98c486dbbe5ed4e6049a35bfc5b604264f182050973240e72ba8875367b55938eccb6c3f4e79221a0d9216c2c78cf403ab268f3b314d',
+ 'f3ca2dbf8a94697d351f5f18320749aeae13e6d57e15cd980f1201bda0a3c54aae9bb247b0ea06c405c23f1e2bf8e97f31acb4a46f2cc9e374165e6c40bd88cfb4ce51be4634',
+ 'e552f4fff6f6bbd14ec50aee19491452ac917aa36a835a1fe87488d34ff61b0d02f12c1581f6da188ecf91658e5b8ddc319999a255021d1a281c57118d4ce939c2eb94d93f9d',
+ '9d4219ed569eb35a9f5513eb1b938842371a995856da49b82bc299eb65d74f339283f67c3d2f268f5a140589e54d0e8bc53111b4f6e17b4ce71dd842215c96d92a1b0c9ea975',
+ '4e1acb25c41216f48b66627320abc5f5e0dd1a7427f548cbbab9c82562d861b6da3636a9eb850359d615a4c3f2edd73c961a425f3947b84ad88eb80a998e3653adbe9e747a00',
+ '4f047d37c653ac9434b9ac3e79628864179aee4f448ee0443d57adacdc3420726d17f7adbe64967f75f5fd3ca661f8cfa57e955a1924db1d5234b999ddd93df5550e07a07b61',
+ '22329812517b7a7a31d3cbbe04c3004e07e65a36a34abca4e71abaa4367af22f3db39f6428906b1516088585ca1cf470a3032b4cce85ddcfbaa512b1cc827bb3557f02e0c1a2',
+ '09e5e326d7c2b5b17381094933ea11a5030c36d9b8390d7ba15187045f44687af7d2fa4c2695027ef542f3058c2c62754b09bad917f931e2f2c4fa45cf63bc5ea4c34419c0c5',
+ '25ff10f4312ecc23b4af653fef943c7272f9847031d1f959dae5cfe16619e9aeeff14c02c155d399b39124d5b8a0e218b1aa257185cb277c74164083a8da14e90d230bc96384',
+ '81168b80d79f8ddecbd9e411cc41a22eb02b63b304be3bb5a140ed3b80945ee5d00049d1453433beb288a272da868a5a84a80871cd625262c263eff12e192397b173ae6c12ea',
+ 'c6c9fd575759c0f6010ecb932fb29559b5dc24c36e09d35423ee5289af0dee0c6187132aa2310f87d8e918108a2b9132c4df8949bd75855cb7347f0727cf2eb8163a881fc7bb',
+ '560d76c1bdde2e56ff54567df6713e4e243c1a42f7fe62fd4bb1786a31b68c0defc6bd95482b80b1fd30462593d6591d57c807c1a0910309540d08d3ad1dbf333d9fe30a309e',
+ 'a193b558891e947e0ee76f912ad51c607cdb59ffe033052143e790c9b696b022c07555aaf994e096d4638f73bd743c096482488458b3d2d6d71a2c57e5808fae9b640df5c240',
+ 'dc986d3d92368e2a19f49b6e537aaf845acbce31716c79c43ac8809d29d318ec38ee2dbbdc0bfa2f3811d60a91825175035b7ffd723b94dbc3c8b1784b4efe3087aaf9560e67',
+ '086d40b5bbe75dfa5905545f83bcd52d712f092fce2c0f5cc9faacb569523e7120abf258a4bb376dfa3a73cfd3e9f4e11cd329a9d1d212761256f5c678625366a9d71adb2af5',
+ '5744618fe8e5c1e4cad95cf43505cc032df1cfe50434ed13202d5bfefef420a377907660426b7306bb03e82fe2e18ad2a7cf4f1465461b61ac269cbc43a972536d9a94576cc2',
+ '6154b5d6d233c4e630b4b2094155954ee63f80cbf4ccfa3d4047afeef9f366dc3b4e3317e096ee6b9a8de33f3f7acbbd6370fc332cd2dcb962179b15c6cb22dba5d646d9ac01',
+ '1e8602e3f3a12b3f9ab21c3a7add7fa9a5381eff4f74f51385c08c231cea8418e7c76f0b2dd6e5095920d413f4621769d16e4a0987cfdd7224ac68ad20ef3e8e90a545389ca8',
+ 'caa2f077c0bde9e98c2f54a98caba4a9f95de80e742bfe92e23b03267ab50ddb1cca1d02e5f54f92008054cbbf4b2219eac9ea3b574b4ba4ba81c522bf3d70bd567beee24e9f',
+ '12145ff87225dabfb7c8dc370ec61b16e6219c14a4fb10f298b464bb3053944a6c27c00c92ae810723b57d1b0dc1398822ae2fb1c9962120f4f4acc952092093c57f8f14164d',
+ '495539a68141fc099393ad40555a70ebb45e3d37f9573fb14b5c7a5c759eb100ea5687c606fce40297ba9a509c2049e24d1980185b1e245178a916021aed10057cc4d033e6e9',
+ '387ca57d6cea7ece2adf507ee497bbc1cd043b32e3c04d6b2d45d4d34160bab80ae3da9ec89b1ed65881e452b634a7b7c0a7dbb43d1718931d417b0d02d14a63001dd6aaa113',
+ 'bf1512506858d2b38e387a1e65aa813bc1c1f6e6d96a6a864b59099e61430a9f934e4a014dc63391f211e30d20e58aee36b8148513780949217db17093bc7bbcea3d9f98becf',
+ '332c022cd7cdbb71fcc3eaf48635a8bb6e03e73f5c08a9cd799c702d7e5df58212301c7152822885b1d42bd20276c1d9d392feacfd6da55379ea9b6d75509b1aa74c2a19e23a',
+ 'ae1ba736e20691bcc3495be8e438d9cd5aa469de20ac7c5dbad753161960074cbfd1ccf423d3762157453dc0e88bbd8506294125e49040c6623728b3eaee5b559770775f9d37',
+ '25117774deaf7c068cbd4ce82a595a584ecc9dfd541ad81eb9d71f12c53b97f76d797da7774d6ae8dfd4d5e37aa1d9d8d90d380f70cea112f7cc2e19113031c62cbd3012a863',
+ 'aac2322ffd2efaebccf8389eabb3411ab55f21087d90322c48cceeeb7934020a4c66a3b8c7a325cfee2dca5737f3d84c3d70eea0b8d19784ad5620e4e2faa730955675626dc3',
+ '100bd00e9c4c9f2becaec6145640e57d1363a9e8e8dc95610627026c300e643c1b7bd0251a8bbb54fbe305be2b47365621690783fafe24d1611730e7b2af09b95f804efe921c',
+ 'e01b54dfcebf64fdc61bc0d9a46f3850db32f7350958b6abcfd130d1df52d6a55657c3224d69f2acaa9cafae3c5d4b82086a1491dd2284bb2fffb9f922612540e48d87a940f5',
+ '4a25e3a88eae864851b4c6d01c6b98b799a70f0ca49f1860a4f167df1ce7b1c07df91ce03f93f4a92f189f390b26d3c04c1c062a43d926ff67c78b87ee192a319a500b35d604',
+ '13e8b6568b1d83ee06235223caf6be6e76897ffc950a9a0f7468d5a231136e4c15030c6623fbf670f10f83b1b764d21ea637ba7d7b2004ca5398d8dac1ba763e1e46276a20eb',
+ '583e7b26715647c6c50482866f84c9a097ef1f1bf4b18ee48e3e1120c901b2c19f95f0572d386329717da38552416554e0dfe7f1dea88f3c7e8dcfea6b1f4b1f0cba3e3e08fc',
+ '381dfe5c3405f0c67216a34475d453af05f8ae8fd47b92d561f119cd1d18d34ecdb152342f8eec0fe0edbc1d7d04ea7608dd2c878e648dc107bf6e927eddca957252be067b62',
+ '772619f048d8cfa9cb846e1ac8deb0ab56b0029eff70d0441f1802718d32c72d7d3291aca50961819ff7440e8fa11d3f0563a67825e7b2cb05f7b56f568f856d4737629da68f',
+ 'f39adca21ff0939639ff8d6da236d519572de92a742364e7f7aada9ec7a10438f5631d10413e8b06e027c2cf7cab668f7d29afa9873f12d543821e746372a421e0ad1a898662',
+ 'cf20eaca221a646675f696c2c9ffab2cca83cdfa0135f4154ad0fbb489fdf96a9977ce63856dfcebfb28b92ffded4248da2571755dbb92a844c67345f368ba266af57be27558',
+ '8c26d9e739fef007ecf426612f7408daa6a8e41aaa918b3e335755cdfbdd66eee09930d88aa339894f0b1ebb5370d914f4ce3f9d6598cc759807a3c762b1d1f9da5dd2260216',
+ 'f1e95a2ac2982a63584af1b7aab0ee739bacccaac5058187755e77e1f669e910135891ffd794808397b24deb33a371d9982af25089933f0da0a35b1b8fcb3ea2aca07900ad90',
+ '4c1624a9407697dd3feb1bddd4a9ad07f99039e12df356fdc69d30208916c5a278225518eb8b1331e22021de9afebbb65e0eb398a0cf1d9248564b014c93fcfa81d5d0e9b190',
+ '0531b92d1b218c08cd8630dd4861f7c80aced6f75d7e0db81e670ad6c3ba8b269d16045d59fb4024cd814a6ff24a8e0a2cb53c74d254edf1eaa189db34ec68396b98b793c787',
+ 'ecd29cbb1a39d7fdbc5c92a096c0cef1d4b2363e9e895537ec2b079a9cd32d10c211a5523f127a8f95215712f96e4220aa0e861f8244f1fecaff40d053a3d8bac20cb7102cd1',
+ 'f54e514eb70f39579c9f175afd7cbdf1de2fdf102b8276e042ee63cab25355d142ecec2636811ff6cddedb870e85ec83c4a02194c839ab307eadc7b7a25e9dbb45a9679e1218',
+ 'e88006364955d8110c553fdfd59db9baaa310ae50f9081026f8b7e85be5631685de0a4213e60fcd14830fcbefddfca035a82f686fe4ab82b8f5c79475adc9558394b60f3ba14',
+ 'deca6cc2bec006c19ae4b3b2246fd63608aca28b225ae80bee522df5406a007035988bcd695b670d6a56b5a36d3e6a7b40f7ea3a80fad9c80cfa2d0cb9c788f64872c6c395b2',
+ 'f0dae6d8753076b1895c01262ca9b57633eb28b3f963a7c752e2cbb4c0314c20eab11a10493faaf4255a8ee4c0884929d1f561ff335eb699df2d116618e60093e5c1e2d1c499',
+ '65af1f17cd7fdaa523b9b7a9829d497cac7303d450c59e9888cbbaf3a627c8a830d327a529578dda923fa94b31cc076491ea338d4a6221ff8251ccd6b4d91e67b11610d3e453',
+ '538b4a4753183ce5607fa03636db2fdc84722aeb9d98a6ed70d0282aba3571267a189b6aa6eb65871c5dcc59dbc7db8973c7c355ba2a2e94c110d1f4064a4087eb07077e67b0',
+ '1e7982d0975b36da4144041fac9a7f70b4d5180bed489f11453e073be4496ac957d74cbcee06244562ba197dbbec09567145cfd2d2ebc673a39b89f20af8fd34ac229279128b',
+ 'ff5f9fb03fc15b2143ef638bbaac07557d3efda920bb9bd5c68349f13a0e37c23ce84bdf19f95e127f0aa7018e85770e327c277bb1ed4fd2804539845b2296d0945d6fe6ac48',
+ '06ec0e5bc833caaf766f8a531b09621c0c93e859280196ac5f166f18711ce55af8d8fb7da9bda7a9d7607a3c382c821bec57704bbb14f6bb9f0b73648206d29448edaf8710f4',
+ 'a52069d08c729eec3f803df6adcfc53c7eb6456549bf29fa084f5425c98a6fb8a6718070f64dbe7cc551a439827b4440f8bbdea28057b172748e1184e67cba75923d64eb1255',
+ '5a04585891a5ddc97a7ce83bab92eba55133905c7ff4aa34c5f56be80564d7bc824278603a6a541876cf1c1a9f05a63753039dbeb827789e107aa8ca8e3616e26885cc0f2e8c',
+ 'd5ed1cdaae3edacf80ee9487eb317df46ba293b07ddbdd350443f150ea28bad30a0e788b4e46087114c26624d72770970b24ed074803cd31ab7db2c17ad3b00d061a5103d6d6',
+ '9fc05ef49579aaef45c00586c8a35dc0960513483e8951715bb29e77c348af0801fd80020650a47f1bb2da0f1ae7e044deb08c74f8a718baa36abe3efbfb84b669675a2d62a6',
+ '3e3b577a9cc800d2dc69362837878d4f7ec0fbf3fe3ae08aa63745886cea61d2ec8a627652a46a997bb5d7b157f8c7f4927ddb0f737b3c1c04e7dcce7345ffefb8bff90d7874',
+ '98fdfe9b591008fa03fcc480809410a53a2a4175de480de360a1a95f3f462eab0a1d41ea2390f3fac382e6033e87b2508854865ef87413334d3da5f1ef0393ab778bda7770c2',
+ '74c6bd81ed71bebacf5f7263cad715951c690afe4cd127e41b1e5468b813540833cde26834a60052ed5a8cfb4d68148876bbebd0728a7c64217ddfcd7611aa14e33d0a881256',
+ 'cea65320f0ca8dc160c5ff83100e523a16b7651d5e4d9cca9c007b8b850373d83f36fb1d1603e3bd7085e55603f07e47452dfc6f24c4d738f8ff44d4b64d08c766e48aa6d7aa',
+ '314743435cf8e0a1e1c4a321433bafec55ec262de77aebc5a4f3ad3f3b5e2106bd938ed546508f70e0881592a4feab262313feb904dc9c30ee78fcb6a8a1bff97e803596e7c6',
+ '13fb1ed6389f32d1de3139cb04bcdd53525c9889b85379d3535a25d290351c95938a3d0cdaf38dbf1d5234bf7965c8ddce9ace1b66247e60d74ec7702a0f931a3cdf4cb465ca9fc458c380004a3a6e79',
+ '5cf59e34f1ae4ed732a95cee65eb494c1f7e89e1a2727cde68229f1a00b904b519f4fffbdd29238b80886cb818a1be2faf268eda96f2df05fd4b71c0c16435848526031904308fb6a51d9a6b510565bc',
+ '909d3891b6a5ef3c812128cc630711861b6e73dce4f289efec5a12520778a511a55145f2027e35fa9cd20d33ea3d0ead4bf0b3c33dc2889fcfd33f01596f013b6a3502810278585f01e50d8be06673dd',
+ 'b51ec038eaf03b3acecf407f43e2f0f4961516850f5e5d87c645c153b9a344341caae284f025c611d701bec6270ded873dfec05c14b623d216c6f49e3131b7842e738c773ec15f02d6935fe6bd60b105',
+ 'd4c892aca8c9574a48b761f33f44aa867bf0c61a4929734280b77290fb5795574da61ab5b14137d1402bf662676f43719706435f3efae829f7ccc3ebfd1419a3e66738388e7d0bbb5193edec7d0fbb00',
+ 'b6294d160b6df30fa4546b63ae64effcbcf74415694984f13ecf21ccd6ca27123f1dc1cfa45bff662925d68717b3695b39b08601864b743eff8bec70dbe265c4e20695a917fc3485997503a6cb5e0d7b',
+ '3b1cbf6f4212f6bfb9bc106dfb5568395643de58bffa2774c31e67f5c1e7017f57caadbb1a56cc5b8a5cf9584552e17e7af9542ba13e9c54695e0dc8f24eddb93d5a3678e10c8a80ff4f27b677d40bef',
+ '09c8f4a892b2efd209af0a8135c15756c528213c86cac5edd9d8c3b965af158309fcc00c1424a874b9e3a8fdbd33e213736f5489eab8ad2665985e600be5f367e0e8a465f4bf2704db00c9325c9fbd21',
+ 'ae69e1f10bcc8ea9e47a1795c916a3132b9d4ba7104970fa0bb551236c43dc26b4bbc5ba4c34d650763886508323cca647cc357dca67354a40aaba0d3b2f07d4201ac080d7fb41cbc7f6348a02216330',
+ '7f0568cca4ff79dcf1e5a306b419d25d47dd4cdd42bb86f3ef243c40fe57c09a7a849353fc3132be1fde32f033e48fc436a3422200dc1180bd5caba8a0dbf4bcd6239e78b975f9b847280c3ad293e4a4',
+ '0daf68d47caecbcb7373b693bbfa4b98a39d88ad3e7e1b99cb2478d2756928883d9364e534c1e294ed89ef8032dfbedef638006d8bf0b4fc15e9412e3f76c27a2c77a175b1c56754c1d0d2ac2886297d',
+ '01663b65d9d258268b1f8c770f713cbc857c1870d399e7ce901887d121d82f5f2116f8c107839c5702997d8a282ee901d04a9c183c36868e7cd5cf7d8e371990ca6c05707e96f87fd5421fc9fdf9b038',
+ 'ab6832846f39aa9be6dedcdce2f0d5ad7d331129b8b340d16212497e3c20909b5aac7590cc9a1d817e3674395dc87261bb699ef6f514d1fa53003d692f2dad6e5a3d0ed7bb862fc73965c5aadcd5b26e',
+ 'd14474fe023c284a27f7be751ced9ae210a4fe5ee681889bdbdefce06a5d44fe6d7bb58684689439ba16d9c0668f329e508d4b6215444d21cd83a523eafb06dd63deb11f13adf48f5c4bf0560f55a019',
+ '1f9284000341a262e7b61f949523b7744277e99013d5a03be04413e137a8ea97a4f1a2f62f92322a2734ef461eacadda352b27c89ab5a1534ed5cb792c8ee983279431dad3bd741c27b2016f81eac716',
+ '24d8938c1644cbb080c4505539e44c8a61567ca74443363b80dfaa466b4068a9af7022da37c1b3dc4f60616f062d5f84d7ca96f389f2a670540d27bc45013418e44a2aff134dad1439e9ec5aa05026a3',
+ '4bdc4b8862956899373d3df4da7281c0ea2bdd57634059efb82d157a221339cb37ff2ef9be6f0f08c2125ac6e5d0ecf4f70a2ca6c72386ed393f1bb2994ab6e52f3d02d8149cfbe54443a357f363f688',
+ '732957d1867047f2904817b4f559649059870d38b2bce77ea2e8b27205464ccbc6e02589f655f3d81fdaa736d57f9fd88fb41d4ab50bf857fa3f9128ec7609b0c9c3b14795efc29469794fb10edb778a',
+ '0541279805ec5e82ddea16897848b0dd584fe59f2dc1ff44a65f493b87aec4cffcfb1b4e2c9dd96b127adae188cdff59a526268e49b25aaff6bc4605e274f0d54aefa48808702d0968e64c6f38b562dc',
+ '68ed9fb90aa9c95ff1add2476ed9a8f9f894a3bfc514b70797daef0ad97b16abeaa6b7a2b96349d99299a31637d3b6db33437a8b6b0829cdf6acac352ef1522207cdc8e2a0b3461d18140670a326fa58',
+ '772326bc0d10921a489a82e3651daf798b2e2a39f72fa1ad5620de0272b890bc11b54ea81a70d912fab4a13946d08b00a2ebf2e6e198ec386eabce86ea4af2531647b710f4adca4c2998a425a64a5402',
+ 'ab7b932494ccb9a4792cafbf75988ff49535f837903761f5b201ad521a8dffb5250fcf862ad53e366872a6803c1b76ef98513da1b0c1044af668e17b49fac9256985a659af51a951fb0ce2b4ed230e16',
+ '1f8509c8553d0d77593d261fc9fcff90bf77b24c4bd3de472144faeb8e2de85fb189cd09e7892152877e02a9d0faceca1f32f04065a7fa28d906f539ea4cf401782df07143b7cf9ca433c6bc7b4ce176',
+ '24b2d6332eba8fd719b4b37463b456e44b9140d9909adc287c85516821a8eebce36ccbed36feadbca9472b76241f0fc86dbdffd5f1725d86c2986b21dcc5b31eac44a636d3c583bc27537a30fa871212',
+ 'b712f94e606e293683b2968806ff6a1485504a3eebb8895c3feb9b60c100cdb7367534718074e3a171546107e1635becfee3954ee452263d6eefe5854b791f8d543a8b7f1c447fa9c9fb632423d367b3',
+ 'e199ddb8612936d2e46b4e301a1e772b0312d5a903e713f9381754fe0b376d900579511fe576cc99ef2a758e8640de93fd900de4abe7304d3d068c4a50edb76d405907003a8b4aec994bb7d96f2d2597',
+ '483d3190b2bfaf492e9688e61db2b9ff0b7dd864d76b555314d201eeb0fdccebd37cd38e0abd9ad4a5e195f25ec8eefd3b6e82ebb57b2dba191547ef2ff96e421aca86987fa8ff31e90556236cb4df07',
+ '2aa3cc87deb165b2c4114d1e5038b882732338786de33223e3588f16313db3710164b34d1d43c25b81b0edc7b5e9096359d7e9010194d420442a35cc109e95bf402dc7cc71d5627e111775fcb8fc752f',
+ '4de1ed235e4247d73df86fc57e56360f0ca78c6c137d8e1d1d46c0237b2096afe6ef3ada66ac899673005ee45a111448e39c467a3144d95fe9293d3797bdef184dd3439b8df960d568088c89e8f9aa9b',
+ 'e48825a5503a6afe0bf9a240c67f27acd4a8f6993834645e03c80c72dd370cd2e10071a3ae18ef19bae9d697ea9a4118609190cd95361907a7fa1b58f499f3f5e79b935f12212f437dde399e3e649024',
+ 'b5438e3845f39afe7deb0fcfb86e2dbe4fbc489f55f01c0f842961b576e89fc719b944cf5d16f4af2f8820e2ab0fda068dc4e797e9bd16fe1d31d1ca03dcf23d6ba5d80ac87fb95d298d391c6b893c6c',
+ '95f2c1509dff6d162edd5de32ded423866dfda682bc7b7503e734142f2fcfe428c9c1175efbf01d6795dbc2b2886dc38013f2832b28c5e7676ce307b394f8c05fd1c209c7c131e3d0e3c3c4fce5d00d8',
+ '9da0c114682f82c1d1e9b54430580b9c569489ca16b92ee10498d55d7cad5db5e652063439311e04beffde8c17688ffc7f45f0255315dc8fd2ab28c52124cbf4911c41b4252231264f684d3ffbbf7963',
+ 'acc3e67746033c73958992fd94f457d6d12c29367050f66372f06181387d67ac42fd42443d038d883ddfaa67471261921205c9d60efa6ca9a642a603c2b04e6f914f986185503aca9f46ceeaec967865',
+ '545514c74c932e3ed856e93d878ad42cedf8e04434bd09a1d4fa38989ece684aff8108798302a19b9894b92d95c4f74afa9e887cf920c0d236ef0533cc49e9f1903b96a199146f2b0019f41de47ae645',
+ 'e79461f00c4c05e2e01808de1926f41aa8f45ea5ebb5baf124f674902a813c3b5e81a118e1e8e13d040eff70009a1730e8a6effadb1ecec57e6991cfa94cfb9b610b4d3a07d116cbce514d3e73ae9d5d',
+ '48eff7d489f9b25c0c65cb3a37d4efba3a84f79be7cf62b5c3f403e05d1af712de92dac7e25d3aa686ee4c61c230deddfacb8d93cfa438363ba2b595ddb8c2c491203e7644e499ae07a389976192feaf',
+ '6ad25e9dabd163d092e124fa0ad1867fbb3e020389074a7c5e01308c2aecc40f28a6bdf0629f1b40778d0a899c61085fe1794a39b6175c7fad1209e481cb7af65863a2f3452bd9df115cc6d33b098398',
+ '58812ce4018d2cb65571271492fef87c06d703d4d52819b8f7959c138071e3ec2431df83fa20ff9d8054521ce0e0ecd2714b8a97814179995289b3f462374c83ef230cf5bb995e230d5268a0f8a37c92',
+ '20c0db0aab2f9be21d2bf0421a16c6390a0bdd57c9c11cb4a0b22933757c36083e871e78bce8b0e065854af9a27aab5a3abc023f0efc4a8808cfda054e0b38f0bb742fbb8f98210d65f79e07666734cb',
+ '282d222b848ce96372409931abe8e1db709914b2d6dd213d62fbc593d579ff0949e0c50d7dbff5526ef28e2e27242040d99381552e13c28cdb5661b9756ac0088583d6e3defb25152e97ec2fd40c9d2a',
+ '82a19090190ef59e77a26cde0e1799ec5b0a796bc64e5af8ca862b5d55f3f607728aabbb254a1f8496cc54f0721cfb7b8fc7374ccf35a41f463998839fe7a945bba66f2c9c868be682d3e74353ea40a1',
+ '76280c24849f0c384d6e5b512a9fb1dd2131da0307b2ffdce71027e0a8acfd9ee9b0d4b130a3e8ef443ae7e3d771b07e68db5a096836785e9c439b58c2d5198877270d2958729f5668bf867bb2facb0a',
+ '72ce9cfd27b714419bde4dcd9b377dc840bdc3adaf5a734c0307af128834378b2a6a81252d2f0d371e2af3410987be76ec9d7c776cce1662c7afde0b0a696789846099f57a12046e1c417560b854c706',
+ '34991e9f5b19fc2b847a87be72ff49c99ecf19d837ee3e23686cd760d9dd7adc78091bca79e42fdb9bc0120faec1a6ca52913e2a0156ba9850e1f39d712859f7fdf7daedf0e206dff67e7121e5d1590a',
+ '4ddd00d0ab6aab2100ce9754c3b3987c06f7e58656011d26e3518711e15b9e6d2d96cd8534d077c211c43ad7f5ee753bcc9e07dc1d4c5a12322ba1d17a005d242b3526d62b29a87231cbec6f2867d9a4',
+ '7a31553b05e96a8da0a4d5b81a857d192afb6aabb1f127d740456a8eda7cf696fbb4c121d8d952a4e91c6ee6a5a1f3588d7804a46bcf6688dc662ae50c438d13c1a61c789b3f1c599a9f28efe0ed1cbe',
+ '6445f6d884fbd57a1eec0716f893aa9f4728aaa07d2038da62f3782e66217abe35776c508d8e0ef34c9666e4ce51b4b27562a8a189c8d34c43a65c8f2445f4a48b5b0b8c878e44b1ea3427c99f5d17fd',
+ '2967fa4c626d18a77aee781aa5200c227ffe703ca0901e4a706ce1393c7d8ce18a03eb2caadbfa7b8e015545dc53f0014097084707c05932ea6d920827b3061dd71ca4f47bef29a8d8b2948a05eeda0c',
+ '58fcc3895930c2fcf0d7c934a4ec3625633509e3c776466f98e49bd091dc436667d52a7c0794521c1f9f7527e1f3eca504f9cf590bb75e98c9439f5c257e49951bfee1bf034c23b91650a3d52e09b42c',
+ 'f6fb322a18bac34c75998040511cf04877344e7d2b6324135f201cde2a7d121575076d57f8eeb0eb65664c4ce24cb9e5bd0dc4195bc42b8672a2678b7893c9075c1ec864738d9ad5b54f01db299a680e',
+ 'e03e23e502700421f0018449c0fc9164ea488c1d00849fc69936519e8f25574f6a03adbb1b4fe6f8ee7ac199ba49fc305a7a6d1161aa4e580a76d92d6ee11546faf5efae1fae8cc54b13de8919a67513',
+ '9e8c665ba53854f0fd27ec45eccfd03d58d1360a3a94f5f24f2ddf52118352e3e5b00a3c96aa39980222dada13ac42cef121f8b27641c6f5e39d103ed1b565b06a5d546dd8658158fe78f8206645c07a',
+ '05b0363fc500dccbe78ca18ac7d3521d539dee9e10e9c4325e27d5ddfca77f9bce525dacde98692fa2a963f27de87789879c1a9d91e935876400851d4a9241ccd08afee8c9fbd13f9657b3f4a5e3298b',
+ '5efb39ea8bbf4bdc7bd985dabab07db427bca4a85550c8d832b7ddfbe683fc52fe22acddcab261d003164241b14a2f234cf30377223b16c1f8db07b9f479b844bb3599a2d67f2ae95a2bbbb2c8c77612',
+ '3724e4bed1e72985fd1f879394543ac9448cfb8b3363c771e55ee13f607d1a188e0f50eee2ca353d3e1b51f915bb4bc5cd83646567814476614bf95cdb933d7dfafcf7ad8a2c05e8e72339471dcba12d',
+ '4718ad423439cc9d3b1f691718e34a30df9b3c4dee7ea9011f496d8a42e1e69fca394a69c6763ecf1351a4f6d0bdb40813ca4e35daca8ef845b2a29c02c3d8fe0869fb948863e0ae20243cfc5379b851',
+ '7fc4aa492a3d12da5d2de0cf9a61c0fbf9e4a2571920554a5c45582754efedf878036e7a1cd9e468a0a1d6fce7ff5fb40af983524e13c32654b8ef8f90dc3cc0fce097c00eb638b4e7457961cd0fe9ed',
+ 'b6ec7ce6448428c34fc6819d50507a2d74ae4175fd2ac53ee5e576c5c5274bb2f6f40a49f6e0c4e40d249ea130f0d858250307d0e87aa5324ee5ccbde8a03fbc2a61aab5cc0d2be471d010e7876ce3bb',
+ 'ceb9aedf8d6efcf0ae52bea0fa99a9e26ae81bacea0cff4d5eecf201e3bca3c3577480621b818fd717ba99d6ff958ea3d59b2527b019c343bb199e648090225867d994607962f5866aa62930d75b58f6',
+];
+
+const List<String> _macs = [
+ '1ba0e66cf72efc349207',
+ '007e4504041a12f9e345',
+ 'c19d05a808054b8039f9',
+ '539d5cbb60739e152196',
+ '2ddc8c4803e5a4c7871c',
+ 'c1ebf896bd26a30cf668',
+ '8a3e105bffc04ba113cd',
+ '4104ef3c144bcfaf8dd3',
+ '838ba0117e413095d056',
+ 'cdcff19dc81026983e6c',
+ 'f069430eb49866d7d39b',
+ '0f4fae1d2b5960a54b82',
+ '7d809c2533c47f832046',
+ '0c7799c513f4a3308de3',
+ '00e416c156dc85d4d47d',
+ '42537b22520a085577587616',
+ 'ecae138322d2d4086aa2bec6',
+ '2fe2bd1355a64e4661a6567a',
+ '144d3a67685bf4ac70bb7fe6',
+ 'c3b94fdb9a6bc9b8e0b7ecb4',
+ '2eca333903bf60931eb08ba7',
+ '04614d9e215e11546ef411dd',
+ 'f5ec42b8e5e3ef658223c8a1',
+ 'a055bb1256afef8fac818a39',
+ '449a3eaf1aaeedc860a7c522',
+ 'd991f360f28b18086fc552f6',
+ '3f99eb6518dcdcfb45eda5e8',
+ 'e4183c3f9245e63ac093e070',
+ '6a31ddbafa486d1a847e0b1a',
+ 'e2cfa49f38958405705dc320',
+ '73b083d8be0d19ee7a697f9e5d76362f',
+ 'd72b370a1d8290105173c83aeedb8358',
+ '657db872e6e9aefcc3d69110c7591057',
+ '7bc8883375527df5ac60fe47357e105e',
+ '805a8f3cbb5ce17139cf8bb03db6b9b4',
+ 'b9b6e8e09db8509ac5a6609ad5e6390b',
+ '571b3401f273a16d9d6011993c78bcfc',
+ '6c82c5f72dba335ff85181131dbeb990',
+ '9502475fa252e5bf4318e451c7f5fe41',
+ '736c3332227a1b48acce71465f5726cb',
+ '66af7ccfa98bcb8d01ead88d046f1038',
+ '2993b746cb98445019cb1ed31ed34070',
+ '287a4765a91fe81c21c4593f985a1253',
+ 'a8483672c40305d7630f3e86b80fa4b0',
+ 'a7df6225fc8a9bc8b91e4c39eef870eb',
+ '3c8162589aafaee024fc9a5ca50dd2336fe3eb28',
+ '2fecb466bc920f610e3eae9949e00f454a714ab5',
+ '3745829991354a1eb42277bb9aff04ab2abcaa47',
+ 'e7c051682dfbbdecc828606868a8fe2eb85919ba',
+ '60d775c440e378a5b3df018edb08c33c063bd8a5',
+ '3fdaec4c28dd5758d937efb8cd4ada0cd40a5d13',
+ 'c3b30827b4e2bba31b6fc0985fa597eb4896c7a2',
+ 'd7264b214307520629ee5e76aa4a8dda4b556b3a',
+ '42ddd9b92c2a45420a770b9727bf53dcffc84d20',
+ 'b099c135065fb0c4c71a4fcb37a95b13cff95437',
+ 'd8fdc66e0c97c0738f236f3dde60af8ac6c3d29a',
+ 'be13212ac81902215c85a7697a2d1870ef74f9ac',
+ 'c87995813b3156fd712c511c328bace2d05cab41',
+ '57e9692b230b55a8a206ca48838d8d1f920202b6',
+ '0c662e4793938cc37f3d51d2b40548ec55914f0d',
+ '402493fac26c2454d0cb',
+ 'b96de3a219d76614aaa4',
+ '2eb0b56949f78f796b9b',
+ '5cee7667d0a29278aea8',
+ '476d8d8db76e87df0a3f',
+ '3bddf9f7384c84b3a66d',
+ 'c4b0bc18c2784c858754',
+ 'e42a3482a658c651f55c',
+ 'd623d5ce7f0e22c269af',
+ '6cc56c226b22110fb13d',
+ '51ae4aaf0de1921b08cb',
+ 'a03712aad2fc0e59732d',
+ 'af6a6235395d057c6d2a',
+ '190e04e5dfa9eab70cce',
+ '2394aef32f606989812a',
+ '445aa92b032c6b65b28a6541',
+ '2f8e18b75cb37402d6e87355',
+ '9dc9ffa7894d69c67295c994',
+ 'a246956f07f6af8830fcd392',
+ 'cbdb6ff2298283b4ddec7526',
+ 'd7fa45de6ac34e2d3ddeeb97',
+ '7fed72bdb85fbd6fd73f9656',
+ '1dd37b69db9cf4a7494697f1',
+ '24a2f45f719e993e63adcf23',
+ 'cd4057acd7ab2b1909ade91e',
+ '0695b866fc28c2a3390e8449',
+ '1b0dd1dec270305c1a669ca8',
+ '8e2916ef6b7bb91c15901210',
+ '1930cb1a51265b09b0aaba99',
+ 'e1c43cb277d8c07146fbc6e1',
+ '4c41bea823ee6791e83636bf752c1240',
+ '17cb2e9e98b748b5ae0f7078ea5519e5',
+ '9005e6ded766f31ca4277bb116c483cc',
+ '9a148fc9f2372f9c07c328e832b96430',
+ '85543d27b8a34ed9e222172ce308c672',
+ 'd9f1dbeb901ac73bab9b5d40065c21e6',
+ 'adbffa3c88f82e0991fe2128ba2798a6',
+ '9411d3cf30e359f33328f80a07b7ba6d',
+ '79fffaa6767b3bacde8078aabcfbda9b',
+ '8aef0e90bd29fd1ad4d80c37e070dbf7',
+ '11ddc4d89e463be1338373f0a1cb22f8',
+ '7a5efb96b080064a05fd021e31f1dbc1',
+ 'c070e020d56f7e294f10fd586bc3e063',
+ '3d866bc71d43209d97bb596fa59460c4',
+ '15eec3c6d6f4e7f2b1426d01259ae8b6',
+ '374c88f4480f5e8aaa9f448b777557c50065e9ac',
+ '8c90480ea6414553df17e53cf96dcb166b94be35',
+ '1b6a55344a48f62f8b351c69acb3a33b4c57c024',
+ '7652e4b24051283af4caf67079955373f6604c9a',
+ '8a536922cc905ed4c321180ebbf4f000e2a809fc',
+ '9e35e4bc678997c18bfb39568e1f77cc49ad153e',
+ '46d9d7c519e520029320b48451faed81f9112f44',
+ '91bc355fb0221825307af876d11404b473222d5a',
+ 'f76d200078fb5b3d3aacc3d90efd4edc5612a777',
+ '99fbfd85069f25da97f9621fff93ea599f61d0c2',
+ '8da25f1b52990f59dad1405161c54eb148f002fb',
+ 'ed84ee8c4d99c5dbe7a253be436ac0c4e4b5e0bc',
+ '7ab9416ae1d32bbbd13277aeda805d66b006461e',
+ 'ccf2155306cf89a73f55a0560d32337e266432af',
+ '65437f28501640304b1ff95db6a6437cac37d10a',
+ 'e06c086d3434d79595d3',
+ '2d0f6c935a06d9d48e10',
+ '6cdbed1cff27b79ac20b',
+ 'bb7654e63c2ef4313c63',
+ 'df4a9f32c2b911138a7d',
+ '9238de28fd468cc27d76',
+ '65d6db01f95625fcb481',
+ 'c4953ddadc2acf38e677',
+ '616a0dfee4c59643e047',
+ '145ce9119643c0c9c23a',
+ '0f6585d0203aedecad76',
+ 'fd4032c4adf2a19e69e5',
+ '8e99a60f575dff478d99',
+ 'd52b5f1b01dc36d76d8e',
+ '6ece755234adba6cd01a',
+ 'e685c26a4ef766a1ac244bf7',
+ '3bf0f6f4ac757afb9deafdb3',
+ 'a8028cb31b89d1e668eb4196',
+ '515a7febe556a317919eb3dc',
+ 'a3bc85d2694d7868120934ce',
+ '03368545751957bda8ff9db3',
+ 'e2ac4a0e354277a62cc82573',
+ '31a0920da97a3e94b151bfc8',
+ 'ea5be261fbfdf4e083358099',
+ '96f596dc5ce8952cb2b0f914',
+ 'ab8810c9a05afb0169fd36df',
+ '078437f1a1089c5724eebf2e',
+ 'a1147bb0ba909865a46b4720',
+ '6eb55c6365a8957cf579ca2f',
+ '9609b20113e61797397a428f',
+ 'f35a4323cab7ade7168c8b9f7276744e',
+ '59a116a249eacaffc54498957787f8f4',
+ '86d4b3a747285f26530e364b659a3c15',
+ '924243335c2eebd348ea23efcb442cc3',
+ 'c05fea12c1594631fa9a5b7e35cc74e0',
+ '34515b41c4af316223ae43e6869a38c1',
+ '8bbe93e9a0e39128595251c7a0504f10',
+ 'b3d266e44d21fea613913002229b7994',
+ '45d9e3d8155dd1d7aac1faa36827402d',
+ 'f5d0c72599bd5f8323a599ca7d2d54f1',
+ '2c77d71152e343414dab1c83fc5f6429',
+ 'ddc60e14dc64399f48c2629cd9ef9551',
+ '2c47a1dfc80df9195ccac2b006904088',
+ 'f253721edace08cccce596b231bdef4b',
+ '32e3a37e8ca379cd7b604840059480d6',
+ '15af23331648171499b58042dbe7b2d5df72d152',
+ '5f7a57d42e3ebbcb85b08565304dab941d6234f3',
+ '5921643e2713d10428843447df91f482f3922aeb',
+ '3f74a3b2a77c173b8b6e20c2ededffd43103e4f6',
+ '3b0ce0fd9eed9287527edb23c0ceaaee4026b570',
+ 'c6c30cc650546dee441ad83d2c01b0bb50319da0',
+ '3e87e626a2014346f4d3b545f0c47043a657c82f',
+ '46251e1b289f217c0b1f0f7dfd988aa62425efc6',
+ '79cd6dd6ad3d3aaf11617b0a9303ed3645ab71b2',
+ 'cdae582296f2c18e05c47a2c3885b24e4976fd00',
+ 'd985cf29d85533af9b58113d7153732678830390',
+ '790315ef7d9441b0ea3382471dd217dde2143788',
+ '2258ded89a07b87e3397aa8a033f151e3c1a23a3',
+ '43673696e3003a2a06ab0f4bf07870fca1b51415',
+ '449121a13d619ca26cfd574204fc9643df12cc8b',
+ 'c73d3cf2bd6c5c9dcb91',
+ '3b89bc8d9f3fbedb86a8',
+ 'd6d0b96cfd9fcbacd20a',
+ '4fa9b60a5cde90c2c0a5',
+ 'b621d1fa15d9345096b2',
+ '5686971a145ca79e0b63',
+ '8ca1bbe34502616b975d',
+ '970c9b7981a9b706806d',
+ 'fb8e0cd4a7656f1aa4da',
+ 'dc82b94bb291d36a94a6',
+ 'e61320faa6b1a7b6796d',
+ '490d70fc32e3c5f6c17b',
+ 'e271addca04e8f983680',
+ 'e2280710a35f000d2ca5',
+ '2a7d988c3a8ed31c16e5',
+ '14ad915c8190567f889160f9',
+ '43bf1001ad1f5c5adf0f59c2',
+ '72ad19cc01c8933dc6a37cc5',
+ '639410b3e778003a9d66c317',
+ 'ac6f7955adb9610c7a30a046',
+ 'abee151bbe2d515b07c63a23',
+ '21b96662150e4f742128dfa3',
+ 'aaf4e6bc966753260f912e95',
+ '6ea8c31c4035c2084be1743a',
+ '07c6d34628e28c8ba39a619a',
+ 'ff39e0b4fd5cd0c40be32024',
+ 'a52411b649601f629bb75f5c',
+ '2785abca097ad771fcaeed6e',
+ '86d5e21fca7caf63426a9a4f',
+ '0aa1a8368477289bdcd2bb2f',
+ '76122c5582fea3b4f59181cb1d83a5ee',
+ '87ae0952132a3b0583317997e5907ae4',
+ '702a4317f0e27c16ad95ec8217917285',
+ 'dfc632da93cb1a878ae38c0cdf5db11a',
+ '490c969829f9413c70287001488b0f18',
+ 'a7549bb8be315b3a8fd3e62c8d960758',
+ '9d0b8ca2dfa14e8aea28a65698796da2',
+ '0d5aed6fd871560f8123439d476e19bd',
+ 'f137933e9b264f559dfd0fc262a69c0f',
+ 'b4276d71392026f683012521bda55952',
+ '6369914b2350ed960f0e8128c02f04c9',
+ 'd598d7af92d2d65d418a116484cdad9a',
+ '0dfdb14b000d0420880f83192888bdea',
+ 'dba4d87dc72e6187afd8381a490b0d0d',
+ '97f6e4631174e11964193a37a916f257',
+ '62ac956ada19f04be50c23f2328a32477cd58fb9',
+ 'a279d055e2d73306a8187344fc32cb0b5b80cd35',
+ '05598da96093f17687d9cca772ef61ea2af8ee40',
+ 'f174bb064880c9b111d71be221ceedd9add971ee',
+ '2f5e0b070c0e268578ac6e868b364b144abf84ad',
+ '4bbcf1bf06f47a720078e2a886d70c8e90ced8da',
+ '2835d14142e4b662578b4c0879c1831bb7245a5c',
+ 'b8ffe657b108b8367502a28c0fa1d595ffa853b6',
+ '7056292af9371cf9ad3e1b9c2743cbc1f52b4e16',
+ '4dcd504d883e2b9d5d1e1ee15c0ff396f4d1c42b',
+ '49d70fcedd5029673d8027f34a4282968237cfef',
+ 'e887df3367b67f8c9e7386d13d1a07a08de9ec68',
+ '97284bd4e44b2e7a034a2f2795d70250ed5c84da',
+ '0819f3d43c19965373a3fc72c446508c969d154e',
+ 'ad4892f36828b64ff5c3fc2dfd780dee39ea30d6',
+ '7653dc1ca2b70f058614',
+ '8db94baaaf03a51acc87',
+ '670c4e2d2661928b8262',
+ 'e7007d2f4a194a8b8144',
+ 'b58e9dfdb9d88df4c71a',
+ '97eb7dab4c4d89026158',
+ 'd56a5de69805f8a9906c',
+ '583bc1ca3c68ecebd811',
+ 'd4225a4949faca02f3ef',
+ '85a83e94fd8b941124e7',
+ '9d835f06dd733eeca888',
+ 'be05ae222904afc2c266',
+ 'a5095b5f7a26ab55a37d',
+ '51d76d949452cbf42262',
+ '7832413077e6bc1ee994',
+ '1d1d12f4ff4e0debb715b9cb',
+ 'b3ebb567bef1fea5d4f954bb',
+ '243785864b714d4132b916a3',
+ '3528e08689fac23da65b7024',
+ '3f172df211dc9da262936060',
+ '46a5b300d160deae52b0dc0a',
+ 'd012486da17a6c96d6ec6a85',
+ 'f5eddca9a528054bc587c7a0',
+ '109f370cfa011ede8627fe4a',
+ '3072ddb57d76181c164e08b8',
+ '5b3a0278b3e71a3a93951b84',
+ '994e9838eaa0bb1d6515c12a',
+ '5a745e9ceda09b0332cb4cfe',
+ '7f25062caa0a514034f793a6',
+ 'cf0b256cb91aeb1bf3877d4c',
+ 'a95cf7bb2f67983469d4fc489e3192d3',
+ '0a060735b4799eeb204c5203e617a776',
+ 'd2f6e9f1ea2cbb0519df68fde357979c',
+ '104ac1da3bc023eb3a94c45f7c42be51',
+ 'f72b19e31efa84db9775dcdab258b91a',
+ '04d599b40b7623ca25c8ea694aec3afd',
+ '7bf44b98d95c3a57d83f8e8bf82a1cb3',
+ '69211fd5573b030e379f7661ae6e6d57',
+ '85c9afe1502539c3140777de9b5afe35',
+ '66aacb93fac3b3ab7f9a61ea907f863b',
+ '5e671f68bee18089e4fb7fb8ce85e66d',
+ 'b1fbf176cb48f5a90db4af7a555a0c65',
+ 'd65dfc5a7d8477da3f29a4ea7809f265',
+ 'cb314cbfe1f935b03adb10e5a8b88c96',
+ '548cba2de5c3944be4d48ec1a2a34d9e',
+ '393238d3afdb7d970b966d374fe097ec8797a870',
+ '0fdd3f836dd7e5c506ab21adde9ae5dc09cb359d',
+ '090cedb3f2833a3f260b0937baae56267a6cd935',
+ 'ccbecd82cf4b29b535a9d57137b853076de78ddd',
+ 'd8013127f8491c97f1d5d275cabeb1ba3b71a2a4',
+ '75cb23746c04f583b8ac78998537d98022ef2440',
+ 'd78807f2a69d8e348cbd2c2d745f342397e20a41',
+ '9602a3a1fd2dc3c55df5815ac0517001f8c6593b',
+ 'b95df20e4e63936b74af4ceb7ad94d4e4b56ea8d',
+ '5f009c918e2f8d7c9f9087b78af44f54518e1c5a',
+ 'f92f9c4b8d423b14ac7ad924f183a1cc27de6afd',
+ 'f476bd42bae22e645cedf601511b1ab8f2852b2c',
+ '48d48ceb4c1f3e6b1e9c0fb8515f1121b846c19b',
+ '9e51be58cf2d5c8e85556b8f3d484109fb49553a',
+ '4ac41ab89f625c60125ed65ffa958c6b490ea670',
+];
diff --git a/pkgs/crypto/test/hmac_sha256_test.dart b/pkgs/crypto/test/hmac_sha256_test.dart
new file mode 100644
index 0000000..7b8cbab
--- /dev/null
+++ b/pkgs/crypto/test/hmac_sha256_test.dart
@@ -0,0 +1,708 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:crypto/crypto.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('standard vector', () {
+ for (var i = 0; i < _inputs.length; i++) {
+ test(_macs[i], () {
+ expectHmacEquals(sha256, bytesFromHexString(_inputs[i]),
+ bytesFromHexString(_keys[i]), _macs[i]);
+ });
+ }
+ });
+}
+
+// Standard test vectors from:
+// http://csrc.nist.gov/groups/STM/cavp/documents/mac/hmactestvectors.zip
+
+const List<String> _inputs = [
+ '752cff52e4b90768558e5369e75d97c69643509a5e5904e0a386cbe4d0970ef73f918f675945a9aefe26daea27587e8dc909dd56fd0468805f834039b345f855cfe19c44b55af241fff3ffcd8045cd5c288e6c4e284c3720570b58e4d47b8feeedc52fd1401f698a209fccfa3b4c0d9a797b046a2759f82a54c41ccd7b5f592b',
+ 'e0eff00f3c46e96c8d5bd181283e4605348e3fa10b47945de3dcc159ae86e7bd3fdb13f2ada2c313fce6a69efa49a470689b1ef05aab778ae15dd35fe6fd1e3a59d351c68cf8f0ffd968d7e78b57377afcc9dce3fa5db1f06f6985c4414c0fcc780030f49fef791a6c08edc2a311080c373f00e4b2044a79d82860f0871bc259',
+ 'bfd166793abdcffbbd56df769150d1466c18a67af452c7e67f86ed741d163ebbd874b9d33a91d3671099620b6eddbbd0f31117164eb73ca201db59f1650131cbef5c7b1bb14089fd24da2919241fc9303c02def424ea861d88636bb90b13ebc38cf177f8a8b139e68082fa46bcfc428bd054c1bb7dd3ed7e9b86ed751736b6cc',
+ 'f6989ebb07aadaeef970f0b5ceb806ecffe77cc20f3c221a6659a9315dff5881961900e68efc320075edafd83de320c6f18f0892489af6d97a2effb252b76b9284ebaf6d42089c1e0a5cd509c20b86ff060d5362c1768f89fafaaf65f1b0fe656b1692984a567e1260c7499085b79f5fe7684779a25855f291c5a192637177c4',
+ '71299ca3daff2331082db370bdf8ceec227b71bdc49c3b14dc3fd213d3ba83e2058828ffc6414fd5a2c99891e9c85f316c5b9bdd810a067b4df97f7e4262acfee642e30ed6534b4a0b3b3eaf5d03f2b045ca5985e7bb45c7503cd03afc68fbea9bc09579141d5fb7cbea6d73208fcf913830715dff98401f6d708ef009b5b8cb',
+ '8b4aa20de6c1f051d11ad50ba2e4fc4ff1ec478455f9b5b96fb9893d2afca969402044c101ccb73c50e2b2dfeeae9690fb64222ab9c94fcd943078785fa8bed9e174ab6390bb16a29c8146cb2fd65a98f44de752d6b0e42f0af2c3df4f65e162742d201c1bf5d22bbee1daf8efc30d0ce491df2632173b8ad9e9b29b819cd8ac',
+ '3274a0326682ba59d6c47db4164e3e9937bfad4199c6507101e5305aeb75d2bf22eb68558d59496f4c389fda04645f0676687f6757fc631b5bcc98cd947bc4d9fae8ddb14bb09a7f15f4270c105c1de0b25bb1abfeb52ce39d3f9baf2fe6c704e3f3670d458e95d158807f10e53d5f6d1221add336fa9211ecc7a1c767bfc286',
+ '0486d2647e2cdf7bba36c8f3ff9e2941001c706eb1a44cbd582f638ee7be4482899c9ce07be4ac381d44fa4649004718e33ac273b1707b746d461a731986d12c93658f216908773aee4690af8eb0be275ecef122f7ac9c94859569d21b1f2bb24a6813eef19e28ca56c5f1f776b474b69a6165412b5f9766c7a5b6759491385c',
+ 'fd5cf72ee0779aab7daa27d5c8a8d31f4082ba47741e7e73c6e631806fbd7597c337e101b609a73ca0be744e3dac9859f827677069f4dfa91c008b739452a62a8f3f84e98cdd2ea08bba4d6614cd49107aacb1026100de457e36d3da9e78684eeadca88f69db77fec60478c554f12d6b4f7b60a6652ac27074efd35c9616012b',
+ '31f51d395a06885efc34032349bc635cd4b1004ceafcb1c426a2f88b4045790226eeb1084e09e41c4ab157c19d2ec027cdbcfb07b98efecf2d130fffb47835d3ad6eec22a12d1c86d4b94cbd1a64134fec94d071bbc69b2a84d37cb4a572da25efff364ffc7b19e4c3d34ade6965451d5bc0e95299ab711d556aa572bc3c5141',
+ '48bdae9d81f1beaccfd00374f522f90cfedd8e3dd93be13947104a89f75b9a48ee1ba48f2d64fc308eb1fea7f07c124d930c2fcfc58f9edfbf680129caca9389a686b17b2b219ad3312a73aeaeca8ea81e9deb4f28c0ffd87e2cb5110542b39736a6de49c45120fc7ee269717835f3846537cba548f98d8c4c036e29efea80da',
+ '1e1bdaa984ca68730faf61c697d5fb15955b28992d69bae86c68cbc9ce735c4703083c04f2042cd0ffce407a89d288e6b731f06075b66530b90d396f0b2fc91944215d6396de4f4ecc92707cd308a7427a66db00761813ada90adcb6a41aec096acd046c76401b140062b8737d61a0516562b11e38750e87c3c87c47a01b0c40',
+ '490700ea587a001c7162f0946f7ca6a5e3655c6e09ba4c13fa7e7d4e22bcdc27f56d8effde9b85d378c751bf018939c10c768bc0754630cd9a3783a8c8ac6486f41a8711ac2412b14d05680a752f3fc6bb31f9949ede3170bcac9426455af211aed69429aa5dd13d56e4dc7cb3b7e03a5a604ff16bca7786c7a656ce7f0eaf51',
+ '5bc93a655f35d346f9e96e96e9bb560178dad04ea46259917d2d30a2cfed14cd01774fcb3d62f3f1d2d164a8d68d161d0f57983a147cd2d4afa98b2686012e7efa6dcd36503366e60ecb65d8a8ee6bbc5cef4e9d5b4e6114298bf5bc46381fe50e52bc8dded1b38c787e7a0ea905dc46294bf961c2018eb9b47a764c59b9716c',
+ 'b733d51a7eaa4b6bb0e378a218caa6ae7475a3f32909184d34d7165264cbf2d8c60753b861cb89d12498204f1d95b52dec3109f8760a54d6de0edcc8b1dfc52c607c2b86f41f6e7ffd61cd2ecba43797e1b25d71a7a20c2d5ffcba335a1d5f6f6cdc860c9d6da37f2186a7c88bc1d2f43d42c8e72399e858a1e9d91dc94a65a9',
+ '0c294a318b7c1e884649fe54e4a87285e42f868e3d0a8519414e05f9c78b236089a11052cbd4cd593e22327b23d33569b35369f9bf3dc5d694b8a7762106184d5c5a5241e1ea805ddc46c4c92ae87efabb0ccc263bc24dfbf1412b90e77e589c4bfd17e615e7bffcea5ebb28400dd6a0c403b6fdf8c1a5ee2191982e601a69b3',
+ 'd60812433098c44623159153de7cd2721b349f685c43388a74c2a3d04a8e972ada4199177c61657369d78f907ba26a8934cc29d3029d4415c1101e3a8283e4c48bb2b8639fe60fc67f6a57b1b03fde507f10efcb43683e1ae223851b962370e1f144b74f1f9189e66cb831dc05bbf46e03e93877a50dec40dde5239a0fd5022a',
+ '3db052695a599813309fae5cf5b19690d3e1e63b3caac1487ef10766978bc9b04a00008c728e7ed397712433bf6256d2865eac3471a8ea5f8011333d02777941ad8c384deed864d47e02a03c364bb086245b3130de40875a16b418296f9eb8698fdc63767640325c0ed8883d03738cf3d460ddf72b7981816a611ef186096c6e',
+ '9ae4b799989bc132e5a50c4fce6d6e44e2940c6ba7dbb8248b447d191d7477c77d5ce83a111889177a171ee0c77d4d74e8c5b0d565ab292e504976157880050ddf99094f6e2ccdcae84148681db6f39360e1d7f83a75ea8a60aa9bcae398ac46a7e44060169f3551156bb36e37e005a9312ea85a8f03a240a5af15c2c786147b',
+ '009f5e399430038250721be1796535ff21a609fdf9f0f61266e3af75d704317d5506f8065c487218e99eb4c3d4546c4d607016901138739dbdf437a5e6f5021a47d69211ad0237eb08768734c2c952cb4f69d94306273a8a2ff62fc85deff88afe99962030683a43d683fdfcebcad1c11718b8e080c53421e370fea6e3fbfa17',
+ '1dd28756d292e5a4f3537e88777933335a64f79a4d50257aac791799b083f450e61ac946dfd6dc7e29613d947fdb9d433d7d632b177dfdd1093274e8917944cf1d576a5abfe0bed528578346d4963df382b0c224e7d6942aa3776ea074ab1df1aad2911bdb7834b2d77d7b27de72ba4a11453c0e2721938c61902d4bc0e328bf',
+ '0c245de3b250c33282ea1a02d007f03b34ed427631283eb614db4d521f555136e7e42b4cfbee8134c63dbe3bb79b5a8b9f9f5b9f5ac61cfab1c54d197f1e3ba613f251eed616df952d691b88a16466343ef2d0f63882ddd2d55b8a6786308b2257f5d7b38af166bd7f1339d2d8899c9eda8fa86215850ba547450c267eb3c914',
+ 'd106a9aec442fed61629e77566f789b28c2c2c3ec628878a12f73d37da6ea7ced677d4b12fa9ce51e01c1fa2627b94cc885a4124a8cac55afb2bd0f34642e2faba8c55f319d19d111bfbcfa9102960e5c6002fbdad41c62339a1dd7e88d5205a45ec335ecce1f27e8f71fd72b82a746610c5fff31fb5124e95006fbfe84eec55',
+ '96560a07f7e398fc739648ce9a924350fbf9b45239ae7c7f626026867dc41d7862211c71cf12e77bb78839afdd0efd9ea251c0ef1bdf6749672f1d7340e290b9cf485d92c526c881a7b6b13969f0c4043f08ef65b03819fcecbf11ab5f2ac4f786d2b4b102a6a5d5eb2a99b266c0ff4b7a2728fe1f41fa639819e877032422fa',
+ '81b8de7e17cc5ffdce4f2213b561d67d244ea591aab5c37f47e946d7db97384bdfa9eab7536b8c5ef7ecfb76bea8dae88063e451ef58804ccc9396f35b9ca2a3145507009b25a539f256ad8eeebcb40fe79807a6b4bb3f57d6ef15c7f49277fb8884db63d744d3172655e1602be78d7ac2b3b698e1272629cec3695a8fc3dedc',
+ 'f4d6aedd9a34e0a1822362714d4e81794b53b266417678c16a97887bbb612cc96bc5e532b3a654e5d3d65a5155427ff09569906381138cc49e3fc2384c5d33c34abd3d617c487b52ec6ee7b5105f41584b7eb5cfb512b8c31f3f338d5236e30398a8ff927e801c8ed7d14fc5040d915a737967d166ddc266f68023a357530431',
+ 'bbf96d794a6a062fed76429a8b395e5664c6b1b0a26bdf083137507ad1bae0bd6a0cd84a9f111ec1a5faa889560f36b781ac4132858a2e141e40c8537e0aeda0a0c8878fd94abff9b0ca6d9fefbad20ffac189cc6000bba9b09993768e72f1de053663901f9d519db3ee77217fc29826760a71c55b53ed8e8f49972b287a543f',
+ '99140d978b2e37f32684f3bf075c4678fe4b3a95fc93df7532af9096772b7707eab95420d9827970e2ba19f75877c395e9c32ac37def2781602b018fa454ebe0c10dce4c7f11498516c8f74c9318f0e57d7d92c8b95c8199ab94ec5a9e5712e0663805834384ae1a09d612277ee6d34e04a2fa0c7880f3a55912d95e2ddbf5ed',
+ '41677677d9b19e249d4488c3eb18153d5b705002ea6aae4258d59560ce421aa4c45e0f30227f3d35a57cee6685c2afad55a4531d2af33b29ffcfd51358bc63a726f9fe28eb0dda8b1ea2cbe3d196081d915030ed8e508a08fc0a9194b8f5b0dc2fdf4a497c83fd8ed05d282217bdaaf3d81bed595daa2448152fd0cb361489ad',
+ '50ee2389b8b70182548ccd7e82de8496c6b3602bc99efc7ca2efba77552762d099af0b51dfc93f718fc65a27957a33001cedfe70995371650c3e26228313414bdfba523cda9a7d9f49c5d83e9f6f1415b3a560acc33c8aa4b807678fab4d7605a979c0f4b314023709f10e6aa9a76ffd12444c884d408f5e2eb04565d8bc4825',
+ 'b1689c2591eaf3c9e66070f8a77954ffb81749f1b00346f9dfe0b2ee905dcc288baf4a92de3f4001dd9f44c468c3d07d6c6ee82faceafc97c2fc0fc0601719d2dcd0aa2aec92d1b0ae933c65eb06a03c9c935c2bad0459810241347ab87e9f11adb30415424c6c7f5f22a003b8ab8de54f6ded0e3ab9245fa79568451dfa258e',
+ '0cf2198c31376f5c8915660137725f2bbc180a986e5a7bda27fa81593a4a339bab92cbc39fb2b8581108ee48c794812d845a72ce8008c9e915d9e330bbb90e9136aa53ba0e6693dd4046d6b03362dfb9edfa04c887153cc5de677aab8c7839d517035879679c29727e96c5426324a2575fbe678d6cc7fef5eb6cebd595cfddef',
+ '3fb301cb4092f9623aa5ffd690d22d65d56e5a1c330b9c4a0d910c34e391c90a76d5401a2d3caa44b8c5d5aef3e928b90d2ee233e9f9a2cec4a32cd019d06a0dc1fcb1125f5746a4fbd32169ed7bf0e4fd065fa7c8ac97c366380484495f5c5b6850dd1c9d8cd6694cf8686e46308ed0ed1f5bdf98cd831339771db63de5a7de',
+ '1c4396f7b7f9228e832a13692002ba2aff439dcb7fddbfd456c022d133ee8903a2d482562fdaa493ce3916d77a0c51441dab26f6b0340238a36a71f87fc3e179cabca9482b704971ce69f3f20ab64b70413d6c2908532b2a888a9fc224cae1365da410b6f2e298904b63b4a41726321835a4774dd063c211cfc8b5166c2d11a2',
+ '4953408be3ddde42521eb625a37af0d2cf9ed184f5b627e5e7e0e824e8e11648b418e5c4c1b0204bc519c9e578b800439bdd254f39f641082d03a28de44ac677644c7b6c8df743f29f1dfd80fd25c2db31010ea02f60201cde24a364d4168da261d848aed01c10dee9149c1ebb29004398f0d29c605a8bca032b31d241ad3371',
+ '44131187c07a8e3979254b0c1d1cfa8081f0beb8890633744932af3f6987c7eace6e153876f639dba46b1e9f3e2a7fe673b3a954a00082cb7516ca9a54d9a1f1f924499960192ee1e3b623dca4a9efc92a6608d34f769efb5912db5267f06a6b0f5d3610458c74347e2ee32916425213ef2f649d5c1090ea3d4f6bcf6b752a3f',
+ '32b45fbcbaf262bbe347360bd6076c43dc26ba9573fcabaea14595de886ccc793b09157dd0a85d74b6ccab9c49335446a45c6e7cb64786e6997c96ef1e4e3123ad6101db4c6a731dfd36b1be4deed1c92a994b25f5e2b171d81b9a335a83e03230c40b2056c00c7c5f8d2fb70abe4b9615e53bd756569217072d8bf362923f6e',
+ '14890f3b2ee63746c8249909013571a403eb54273760090db5959b06ff59acfaee6d0c4aece58b5964d10b4b771dd90cf1b63d947bee4f6a12220d67b79aabbd68b02a3850352cc33b10072d4c28182df2855aa418b236239c659dad036155be6b9c908bc09dc38c3329b538e81ed710ef9fd3de7671673f3da5745f4a785204',
+ '3e8a9030eae1bb6084cffdb577623c4cf94b7aee3d3ca994ea94c12acd3e1194cad6d2ef190e0219af517073f9a613e5d0d69f23aad15a2f0d4e2c204ab2f621673325bc5d3d875984145d014bbcb1682c16ea2bdf4b9d56ce6da629ca5c781cfce7b1201e34f228eb62ede8d36cbfdcf451818d46721910153b56cfb5053d8c',
+ '97d29ac5ede94c0a5071e0095e6102123d1726132f9dc102672ab87b1cec18abdb04096c21d3fdb129742d250389460fe63b5f79c77c2f912a8f7d4f39cbd758139c872366cac35a40fe248322825adf57481d92832e66057f80e08964be993de6a0fe31e45806cb3c17ad6ae4d2a44a374647a88c3acf260d04c970c74ec720',
+ '8734e49e3e629deb352c77f58ff4dcce2af3b1182e7d896ae68619f6cf66ed69efd95913684ab1484d51bc06b47a67d70d48b7f9b27901bdbf8c5d2d238158f1f7e0e9740ffca742cf7938b5400c0dd063824c6bc6040e905499cb2671ec12cc47507e085a01e5a163acd2495b32367fd6aa5ab492a518ad50b54b28e23084c2',
+ '61c5be972faa61f67bcb332542c0b8a7c74ef67cdb95d6f65c8acec8fca8bd6043e31677d8de41e6fc5d3ebb57fd8c8cf723490b96329adb1b014da2648cbd6043e9f6ffc67e1a2bbc72046374612a50c854c8565af03b6a1eedaa2319caec1368bfa65783f4b46dc3f0cb4622545c9c43c9bb86b237804a6c382e72a2cc1222',
+ 'b31d11cb4f5c572ccf3405c65cbd218ee8abdc08b6c82e5d1da2baaf8980f7a9c29b915a718b0d43e000adae01b29342b29b28d53f63bf81281c76fa252f5d1e6896dbce224c4dfd4802ef0697140043d6bb21db5b84ffdbd001318937be64f52c76b5d06a875e8191a4957627cab1b8dc758fc3121334949cb9b303c6155153',
+ '3ad17308cd259688d5b52c32d01a3b868bfaa4758bdaa5ceac34a1f908ca24e71a39224924d17f00cda4d4d50fdd716b50549e71cf5f271c42ea17d5becac32fd64e0a1b0717dc5f542af9442d44fb8f956e97b384d020458aca4cb0b6413b2ab637b5e73f9fb48cb06f22e6f2f6e3dca27016a272d89830ccfdcaf3b9d895c2',
+ '46eb5059055d3345c1ea84a4ebd2d7cc53361707eccd70e7cfd86bda83585bfe7c7ef937e1634b7e93f9ca7c6a42c357c2bffecc362c9e7eab6a488d91bd876b65376feb7a74819bfa88cf542736610fe763d6fa80c94ecca0f08855a05a485909fefc9e58f99e44fe7fdc55ab17779dcc08e9bc530e4a79b65274593a996671',
+ '390a9dc2ea20221c5993c581892eb4b04364294fad919c451e83376531398a4c18ea808c334a910ae1083aa4979baa172f3ebf20823930e238630c88dfe5632b3b4042f6dd92e588f71529996fe840e13212a835cbc45ef434de4fa1ecb50fd14913cd481080875f43c07aa93a9dddd5f5e7ced6b1b88d42b9fce8f87f31f606',
+ 'f90768954cdcbd5705f9d318fca6591787af840a921fbd06f24b979ef612034f3f64c71cd2012c756c83f75d169f9bccf8a8ad52725498fe69c3927edfbdcf87c73cf478172ace3a1e6b446a181e8aba00209894a5d2db01001d2acac5b3fbdd3897d7f142df0b6dc4b9a1862bac8ea845202d185321ecd75f6046c9cf7af116',
+ 'c1d80128fa208ba18bbb13424012ea651ee75e73f796e94c3b9aa9e911521040a605dd67c5254bfda9d088c60f9c68958f945b6f2b7e9ded2960ace21e42ff3e4c34f5322d930c955089538764d3225493c7089b119505aff4cdf93d46215d2f586d31d15af4353229ec5cce683e7e69d2874d3ece628a5944e97942b07992db',
+ 'f57ea84caaa2af18dd7efdca356b9625f9e70d3a803a9d31e95976460c0a5512af49570cfeea0f4f3581d69ea07f62a5c59d9b81e07ea9838f8f5231cf33838e271d2c9c23fc511e045e5fa2b6cebcbf0240a19c05b02cb1e105b1d2b23b5269c4c1cf0303209f0eb2de3fe060a2cafc1898ca91d9174d4445823c2f9d6ce92a',
+ '33ca6eb7ec1091b406cf64495ccfa2169f47b3b590477d4073537c14c05015d51ba527b3869ae4ebd603df906323658b04cb11e13bc29b34ac69f18dd49f8958f7e3f5b05ab8b8ddb34e581bde5eb49dd15698d2d2b68fe7e8baf88d8f395cfcafcdff38cf34b59386f6f77333483655ee316f12bfeb00610d8cba9e59e637ca',
+ '74c4ca4db1aa812b4d75852c6717146351e83299448ff84d52262ff99d991d97c74f9f64a90d78e44817e926049882491343373f2e3bb6d18a30f8e30acb16fab34d5ffb6073a736b79ce1a25b2df16a6335bba90c4d8072aac36a14e5f7659c2104319b3ea3b529824d9729d3a009cf2a04e660448efd399b25ad1394e3b285',
+ '68bb5b6289907589f8d91e46d44417ea80bf6be10245f52ba9f82211f371f810ad54571a5c277ffedc64d32447ccdd7d19ff91ba914ad6bc5ac0424c6a8c250d2b85caaed803f9642af1c098352474dd8cebf224ace82a33981edf53c04aa84927773b88c5cdeaa52baa6e0b65f4e4f024ad15881dc7fa78ac3a808dbd5588ae',
+ '900e4152131d8c4dcc38a9e8647234dffc7ce88ecbbb65a8089d302c0a2efc95aee62852f9c58875fea368af02c1ce7cdfa3009ba62246c188bdf18ef7309cc00848b2a71cf531d9bfa1ad26d0c097cee3a8bff2e3a31849fc43bb14b7f62f5467dae83ac5d30ddfd7da7f351698163ecf332e7bca6862a82ada97a694a93db9',
+ '7159ecc145a3f919044c851a4eca428279626e68cd8fa4c5f4a7f932acbc44f3bfc0bd3535edca94c86415e09815e22120dea0d869f7bd887d8dbf751fad91acb9641a43962514e2516a1c838e9e0575e73b72a72a30a423c18590d97141359e488c2c74d011810c89a6c189962f5487b7bf0d5c7701009da7d794e50a40d9d1',
+ '939bfaab9f60369542928b1490894259c22706747f0c48215b08e1e59ed6f95a460728c74f3cdcf43198fb3dab75c9e4bf560bacfe1d6da3057f213f48b4c9ac0e739765bd1db2025839dc50462053a755f9f478fee8a626eb83f617b686ff0af4c78dab726c8264be5b7877e9f2a74a8cf9090109d4bd5213fdaa9571b2641b',
+ '29ba205089b12e8be5b422faf99c3d69aaca324eeb732db8e13c148245070dcc0b0c40ab412bde2039806247ea3917d194a4dab4a38c2121d6c63cb7a007dbf6cff9d1f66b8d1759e192147e60871bf784ad363e326122a3c3a99a89640dd9d2bca85a98d07ee21e2410c006232e53c4c10dce525f993825ef0cb76158c00d49',
+ 'f7321718bbd3b401fb5d72f2e8931a5ebb18d2a1ecd4f189a59912157607687c4aad51719a702da6e031708f4faaf668c1999779f121fc99ea6db0f1bf967a027dc7ebea5e9f33e23fd6390c5424ea6c1b5ed0338ee3e7449d36adf1dbec790578c90d086f266ebe0095f4f161c89d70b1afa6582de15d92a63d319d33d10b8e',
+ 'cf25d619fb46bfbc39557914dda02d767ac511120d173b787743b35b3134cb943b33b36955534810720c2d6f6a261d26efd87fcfc2323b8426b8cda2965098cdb35e7c35802daa17d191b78601caf06be4aceecbfcfd6a48f01f52eb39ee1b201fec5a02e49c8ed93f2b40e10c554f4e4187858c24416dcbbbbf69bb84d8ff94',
+ 'e2a26ca137027066af856453d2a4adc4d5d0c9d5bf068f8acaa4b74d0c7b9c9e562541065d98924c17fcedec68bae1c5fed636127a7e2d9bd0e3082df047cd47a6574816bebc4fa36ded4a4cec47f271665f586f149729d2a7ef31c6e61e1fcf98e288baa4942ed477ff8159a672662fd41438d4d7780c9616713a023528199e',
+ '3b9a4948d67dc894d70c9ec37104a7147e22bcccb98983c22d648b21edcc986a06ec3bb8b263a648cee9bf388e36738f70204d7e6e0347e67865e01921da6ee59926b6cfdba2ba9c27e1d216b392fe0c9ea87b9b25b994ac19a4bbbe9077d8e6dc90e113b902ab97ca3a00e347e2f192f0056daa4574131ef8694597a36b7e73',
+ '935a3c27249dcf92aedac8dc76d22ff7742e5cee57711778c92afdcdf36e26b8448504ee6ee48e9eb25b9e495e9098d494ac4ddc4c541f499cdb652638b611b0353090ac125ff1fef8564a78419c57f038dd65951fe06e8377b986947b407579eec1a60a16f540db0931921027deb472e8296bc2d8fb4e4ddf2c27c0c6f49c3e',
+ '548564e5b7370426d575bbe8175b48c244dedcef3daf7252ec625fb777d02a5cb9ba9db0f2af1c5abd2f367d43107a3aaf218c77e20e78df6783452aa994ce9f635dcdd759e539c34649d2f11516fa0a53f6c6a0e58f5526f6a86040348d133e3cb51be252a3016a560ab6caf3346f3a1aa4b2f0affbb12f8218d8808083a240',
+ 'dd802635f714060381d2ee1dfb50f2daacc637598965fa7158ead3eb15723bef95904dbd699dc99e054f5e19228d29696082792f30f1d565f1c8409359f7bb4517820cbcb6d5bee4c5596986354433bf02b597b1160065786a460a5f6e4a1254ab7feb9aa666ecbe081695ccfd1c19c2da861945023bb3930a8ebbb91b124806',
+ 'e80a112713b2e0aafddfdb71c091141719e1501c1ce55ee526d4a804146a08bab28eddba76335d306f7c2d0278232f56b11b9b543074512df3806d5c19341c2c52d0af7a95c3eebc11c8af426556a7bc13377ffd32762afe647f77260882e2c8b118b0eed6293b55cb0d8ab8eff12451287d269e8cb49461611bedea481d0298',
+ '7e5d6e5e9491a965968a08adcbfbbdb19949f00903f7618270624e74aeae975036002079b2ed7755bc33b7a3e9a7ac0f066f3703a171f4c1cc0b1baf1d05a4f1f9c4af3d12c022eb2f38944c2c246a3d416b3ffc87568a3ab7447a7135a025774e11e254bef0f35176ff68519c583f64d2a3d09abb8c6915bb753562ff67620a',
+ 'fc0624c9d2fb237707df2c7bd9090b031329835432d99304c575f8691a2df35116584cf3650b9726d4ebb6d1fa3f9fa31e4a600455d7604beb15e73104a5e08583f2de222bc15e1f04094c450104c8c6df86292b508e428f591ae50bf940a6710b7be13d6d43ffc862e0f4bf357f0cd42086e8b36b25c338d82dfbdf3f26cc7c',
+ 'e35dc1d0e414ae0e586ebec9a44c1918d795db378a89177d0b521c8ebadcf6d2b2e73826ac5bf9d121db1db9af9cd6d7be7869e8633e3665854df3b63e6138a383ac400b0829eed85e2d0e325e3fdef3cb29cc5b334f82061640201a4b8bc8c59ed460e7be26930b578b199c7bda395646d18cfac263034608532b24a802b022',
+ 'dc4354ff557dfa58b17a0e38f63a61c20e0fd1eb6cac102cf37fa77913413a7735cb0dea592bc76cfdf7766541e1d4374a8cc9b9e49e30e76b17ded8ebe1e0f086a7055616eb9da814537feeb94451cd62b203fe39379dfe12623b069351553d9882442dd5e60273be3732bba38c60ec202b89a0b49eded7b009c5ec53ba21c8',
+ '36581b498cc8b9ea79de28ca91a9cd0a87e30bcefe73b9e59c37d3a860016f2436dff37bc9a086879993c4c14d92b6614a3f01c7848e5d1a9484492f0c3efeac0734a16d04bfbc26f4d9ef4a9124e32cf22f80655cf460755ca583ad12a8444cd0e08be8e42e450fb137112f05683cb3a638f06f2eada83e1922e7e91d472a4b',
+ '45ae84fe11078713bc87c465e8d88f0b23e2804a6a3e19afebeeaa5a0f4c729db84107c6c8b7f838e251b0c174599d27f5fa92046baf6ad431fbef4df75bfaef0a79dbdbd6a2fae8a97abff4b9eeb078696bd95fc84d71195a9bbaeb1cf12989c2bdc7e643aed74b976ab9a7bf800e26079d1d04880276a4f035d4dc86f74893',
+ 'f6f83ff6ddf386bdf3af9409ef5cef16acb376182322f57b9729f76f0f04dba4098a2a526d55287dc023a9779a7c26a65a951087187564f3db5680a20c4e35ed2b2e1dd8c1ab2f4f96bb90b02342ac8a4aee86a5455f4c42dd8c2fa3dc6272cec4aec08fc13cc2bcdd40f1bc73f6a94ae6867f77922ad5ee0392ac7c6588b9d0',
+ '25c04b857a224389e8a2a304e1bb8ee1b352e4cf5c3cb6e99f01fd9557df8bac0c1241dcc453834b1b9fe97d9639377835f2902647a8e6fa820db5d653a9f12d73233d65bbbc5d7f391ceef9835154f34b15f592344fa5a2e4dd607f5b913f358379a5e60864b96c69a11a40500ace9a1f427bdacb3ad927edfa6756169e5d0d',
+ '6c15d1686e680c5aee2941900dc9af9d2503b3b6a5623f5c1c04873c939dfd5320be8055b858d050457c468cf864c2b7e1b7e43ebd097ffe0fa14a1c7280d9312d9fccab087747705ec6a2c47491616c096566132ee365ee587c999cb478b550ba3d1e3105ce57016292bcfd27577405c696a1fda1f8d973201ada82018d79f6',
+ 'b99a110bee03f440f15145e28d32c340297fb810efcc36a82e3da171fc9b6d981fa629062eadbd93f35df07614d72d00f205868bd22df9ad3bc6f2b19e8b12473dcf2f7a45109ce33dceaa1ca49d6e78d67ac5f1305b9662740a57f76f32d3e1d9ba2a4e7c531998994d7bbc87af100f9d867e2c527d9531a3aed72bb5b838ce',
+ 'c821be1cce09579ea899899d24f8329994c2c839cf0084e27857c688837fb5c4f4f72527eaf7bfcfdda75b37248eb153ba4d31dd418d2fea473643c0c9e1f0ebf591838e349d3ef868f1b67772777a71f8cff5b0654696fe31062ef2628a99095355a0f8b4e41e41d2e162051899d519d6b0dc5c42130047bd2f4dc55761f745',
+ '53cb09d0a788e4466d01588df6945d8728d9363f76cd012a10308dad562b6be093364892e8397a8d86f1d81a2096cfc8a1bbb26a1a75525ffebfcf16911dadd09e802aa8686acfd1e4524620254a6bca18dfa56e71417756e5a452fa9ae5aec5dc71591c11630e9defec49a4ecf85a14f60eb854657899972ea5bf6159cb9547',
+ 'f9660fb784c14b5fbec280526a69c2294fba12aea163789bbe9f52a51b5aebb97d964f866c0d5e3be41820924fcf580db0725c7f210823cf7f45a0f964b14e5555070d1c3ddb2c281a80c7fbf72953031a4e771d7e521d578462cafae5a02ac8eb81f082e173ddadc8c41d964bbfda94f5180c8da28a8ebb33be77b0866fa798',
+ '64a78a4d6fb8ff3813df8dc022faaf4415e4df2949e16467683c6c47242e5a6b2c02610e5877528d2766b2266ca41000442a956c4b73dd6b10260570c6f506673cc541f50f0f5b021e864a753efab03e2f7c689acfc35f928ecea6c522cbc5687c38518bfa48c19ede887d33ffc23806be21803a3c9793e5ca7c75cfa1783f77',
+ 'a7734a0739d51af0ac2c4039dfafa86f36fc06c2355d0f654d4ae938f52fe0a5fd6f5ac71fa80dd2d8396faf76016ee6716a62c1fea640afe23910e684b8a14c47d07b98168915b441cc48668724043074c14275edc239dc09b4d5fa2255652b2c9e94c046019a608ff0b3a83b9ed015e6098d24273864b769c120bbf68f9408',
+ '0b9a58cd96351a135c559d17e82ede3434a0caf0befef5dfdf138ec5586793fb2ebe4114b9e2cfbff7a25bef261b253a9136fb7faa72f4cc59e4617f947c01ab308974bdf67ff25ffaf83d9c28fad44520786a94441b96100e42ccb0a8478c43b604d90f7695edb90c602b651753551d886dff77b4804472a835b7a2bc509c8d',
+ 'e5804b099ee4b351843adb9c9e3c231773256e6a2070d697a9e29e258dca677f9d88a7970d4c58cecc20ed1811298a5b37297419ca49c74fe216679dafc938a656cb92bafb78efb31f24e71c2d5b5f994f6dfd82862adfd2faeb8c408fd22aabb852f2bb90f1e2c6274cb1f0195c089766f9efee7d9c86e79a69f557526da555',
+ '8b1d4523b6e457f856e5f09875d389eb6587223e53477ba01f49878c6c731ec9f365f28f1cb9c4ebcf89d8648732a6dfa958d2c0152b5e52fae81f69eea26d463e421fba82cdb78f75e5d92304930256a54376a6ea107a995642c45c6f1530a914bdb4ed11a696abf100dc1b147b0518014ff639fc80373ddc605fac1755cdbb',
+ 'ff8662e9af3a38d3efc0143138fa619a57d569f61e29b3895ae08f2d055befdebc11787c7379d9cd672b5cc25442bafbe804348c78c5df02f30840a114e818f0dbb681783de43ac81b2140bc71c69effd07185cf0eef9f003c60a144d89520a944bda563774103ccf3ece8a9f64fb3aff564854646719b8c1d2fdb9db92cac12',
+ '33ab861f089bac0e5c886f66adc568ae7ba331655a371de7475e269138ff2725f7904c702fdcc62ac703c31d70c29d8a7af451c8ec59342ed397e133da7e76d41b90003635c1338d9f7b5f3c3ce59f3e2f6554c4f064d11f9f5158e199e8463f4ab48aba42d25bff8af92b0b38b7d69241fd20a28fde5e84539473e39dc4fe2f',
+ '5a2240f64fc704ce9f8ed33d019e4155cb46747a659e3421fe6b42d67f44eb84bdf3dcf1f31e38886f27e85b8b503368df238e1bb511b515bd59fa2c032bddb31d0ddefba97f8f19f7daedea027ef055a52c61d00bb1ec2668c57677e632b180e339ed1c5931310b9d718af34d70a3a4832b96a04fc702db65785ebf12a18c73',
+ 'f407f815a33cd450c0b72a378f00762788f91bc44f09f93de67a41d2222088935b3c1b6a689f935bca13a90b28f64b7ffc28ef278b28271b1a7975a45f4b61fe3657ca5c950b7a2dc2e7fd9ec327b26017a222aba3f29183efd5d33a92d36136eb21acf412c6b14d0efccef849d9d451412e5d587fb060fdcd55029ba401afc2',
+ 'dbb84fef130f929805b0876cb4646a046330bc33ab1cf1e9ca3869573ee1a1549341ab007915dba719b3c4e8a94b62163e6d99dee2cbde2ae74135467b125b417c7544978d50c80c694399db77e878109f59a8335df3a326135a0d50a4bde6fc3e5c03fb7747bf919c68ee8f45c312bc2dfdd279411ba7a5f78dd9bfe16baa4a',
+ '1de00288a6e93930070183de9d9ed0ce86f6cc0f64b7bedb5df8af24676fd06fc2e516e5c5e827a7dec07963d5a4b825502d696f9c0ace8baaf6092058e78304f2888f51f9ea4bbb2376c720a2276a61a9f691712d9578abe95f5e69a490e4d2b6b1b7f3c9576e12dd0db63e8f8fac2b9a398a3d9ebe86e3201df726d2d1ba82',
+ '2937aa2ff7c942bf7dcfa670154e988c28177391969db4995804ba1a647acacfd0ca56f63b2e7fbc6965d8f62d066d118c14044c1fd2a224b9d951104a67216f03fa6dbfbb1e5f0f9283b6b7d452c74620c1c2bcc9e637fa7cc8d97623bc81330aef76f1403feba1414fc91bd1daaf132b4737495b7e7c01e9fbd9b3b720f303',
+ 'dfa3b06eb1e30b47ad9f0bf0f441fcd94856ca8b1f4cb88cf6795582e860ad9c7f30bc2eca8e289bb0942f78831addeed934836097fb664e4e91b47acb5fbc49e9a15d6baa25bfbe864f42700361b46586f9c7d869dcc2444df17685b291743ac5fe7d6f78303a79d8d82d209c9fe804f9ae7d39be7435359ca385ecc57c3d39',
+ '509a0a45a1512b5072474b297f9c1a8c24890016144468504e245fe94d065d437fef6232f9f34500695549b44ceff29361d417e85d353701e081117aa8d06ebe058242ca8c23f3341092f96cce63a743e88148a915186ebb96b287fd6ca0b1e3c89bd097c3abddf64f4881db6dbfe2a1a1d8bde3a3b6b58658feeafa003ccebc',
+ 'c28f6a09ce076ef270458967fe19d46e6f6b2cbeb6362bdc4fd55684177e984a600cf0814501665c3bcb4353e94681c83a8381ebb0c8fcdbfbd73c0eca738cf2e121edd46b2c0a0292eb6e2c4e46f5107a7780572d0eedb9473847684a4039ac6c56c9caea90432b9e2e72bad422168e5ad093c9d612e7c05c7fde5c40ed89c0',
+ '5a600c468ec22e42af5ba93eb79452864ebe469a86f83632c85201800f3288b553f7bec649ddfe704920a27a8f65d13aa755985a238b3cdc8fb0cf5ca7e40295c7603a27a25ae69837290f9801aa30896ee2493e93e52f031ef626de8cefb1159ce4a9f003038dc061be1920742d1a7b8bad80cf3eceb5b05d6c2d8f261b3f3c',
+ '04369f9592b00626d15b0a4b0ee2f92ba0d086c16d016ce7b05654b4f9adf90875118a656f2d50011707901982ebb387f3a4a49759f37a17183957ad0c778f6ecb780dab2b4df30e05fa81e6386f38c0f0ba3f37287a050d6d97287ae53096c391d5f20fcff73977239ca55c3657d1fd1f781f48e28057f136d890c28cc25432',
+ '59a6b0317f130f6248e746e396cc684b32b9a0eabf15c50bec1f2f76ee8dc9392e7368a83e675ba312e344176deb26c799efbe4d5bf2175b26ec59478f6de1c7018497f9b2df7ca6d53383c712dfa24833cc280d209751330df21898f2474c9d3b9fe62ac1c39af3faa0acfa6cf0055568178632f44b9c1809f81570ff633243',
+ '952e93853e9579c2fe353dc83203d34f04963fd64880a095a4de6eb4f42e00baec615148ff31030780b5a4df0833316a1735d8a8fedf02f4fc7f9136a766665b8df727021cfd3f78bf4226e74a5de2ca98cbcea472419af2b341935eaaec2435c0179d1b5ba034fe02024a48c128ef59cf7fa7346e4f6e78134bfb93c7674232',
+ '7d3d9286c1fa057175c33c556d2c4b87fe46d1b764727d6b6172d1ac27c626fe7835f1960caa44c8334198bfbba2c970148e62d0b2b71b45b3d5a05bc2f694b93b15d6538fef03e1eb123c8f143729f696d13d4b1de63cd6231efba6cb1a68840d06c925147249a4e45db02f40937200cb3aeb8e6da7e905f8766bf40cd9a846',
+ '188a7fb0222c9d8e19d057ab22d71e0356c4f8d1184179aea663eefcef2edb85a55ca860925a97152f94f90073f2a2fbe9a29a370519156bb854a5314264afac48291c6f265e509a86d5604632047f2426c1ba60ea4ae6cc1e88d63a5695d129297b42a5853fb268451ef44506169fc736a8c2156dddd2180187e7e0d5c92844',
+ 'e105ff11481159c52baef5de550898214e1d9a90da2d9083c36b29fad8f956323613ae76c68b103807758a600e2379e4cb54f2998da86149c857700517232bbc7d8b610df0424d5a18df751e54d6d380fea73328f055dc51461a721f66591b333ed4e17ecd1f5852e55580bf2f09ec1c6f7f24e4091c49c4c51cf7f1cf836fbf',
+ 'ba527305604ef5581850b222fd192e6260c3f20eb30d8f04a5f4e1438f83915b0febdd22f2d69ca958f97c6e12e88fd34f2f06cf789e3ce458e4f6518060e988ea337ce2dc9ad0920f7bfdd8113d9f77e8dd9268f83ef9d027c185303e16f4db9252d7aee54199fb87fdbdc6c0bf673473f61e40fb96d0b059b31647914eba3d',
+ '198b79d09a3dfdb5d41043e679baba6592f3c751cd7cbb0d1860029f6e7a9c56f137d2b03a9d217aed8c7b399044afc99d282544d5c2ce26d8065baef3dbad8739d78da7d54a9e789e7f8f35ec3e9597aa9519b2add9ae1944e7454911afa44517f4147d134d5af41070e9a236af5618e3c30c62fdc94131868a293a70ff69d9',
+ 'bac0889281fe55dae17c45079bc44f8976508f5a92953c26f940daae77bfb16eac037d7d5f8467b615863415e29bbd63806a9f169eae33737a82c1f5b2dbf0f25856817c44343d86aea22c47fc3e08e4d8d8f14986756257749a644513c70240e641fc55d914c091d35995678eb51a51a722efbaf1f2b21c0f112d66428acda0',
+ 'da32314c22dde556d886ce2dde1291f1a4c1ba14aaa95b694063f57e91049c2cdf4e576c1028c66c6a4c07e39b40d9a1fc87026a1618ef04660f9b8f5da3b215ab58f562bd75e01684b98af8794ace8ddeeea8ea467de1c65797efd3cf92174fc5b6d4d532ad7c7aaf3521158018b5ded25e723b41c179d69d61baf3eeb91301',
+ '557f845dc8962ae11561f63ff9f7a9fd73ad5da479f1d1c3e9760236c292fba894e4ed5735398217b6b06f9a951d49ee34ac99478ac732ff1939c2db2093a89011ce0586453316dbef78c1ab4f2c6d8f285517637357a24d55176ffa4f612e2bb587f471614b8d34a8ff13fa8debbfe635ef007f9b6acab4855a311cb7c43682',
+ 'dac416df793ee5fbca992682974a0c2cca63eb49805df0a75e1410b628133eea8f12e1614bbd85c66ab7d075e8dfb8df7fd2f430c0b1b03063248567dc9ea8852fe3620104c8c0fffe3a8b7749827a9472c7a75a7cd5408c301d7fcdb4fcdc055f408106cce8fe702d2b3ed1e2bcb9114b4dec0eda5206836c07e52ed9b44032',
+ '5cf3a5202df8706f6bff5bf2590de37c902c7ffd4e6c8ea611288e4e658a8e15fa51e647f9d22583983d4b1ced2239bfff346556234cd22d86b140530696a04446e4cac4013a720e9e32582e05e7c0acb2b4226a073e22cfe7b4c2258055d7406833ba61ec373f5aa566ebf24c62618ace341e01a34866d65cb97e8c7cd01c53',
+ 'c1263be423e7888eaceccfef26f0b5aaefe03f3ce732dde98c78a7f66435e6199cefd62eee85aa2bc8c3d156aa3478b6cf3750c71155917207d23f3b7082acbdd4de3e536857721933eb21136ff502ab324971614d806ebe7491e989a0a23d3eb21dfabc5905e73e358b478c3ddc5c735e3e2a72645b7db61edc2d49bd3aa186',
+ 'a5deb712fc3bb9fbaf1398698b5696600fcd61ac68489f26a0f8ca32121a3e8c21d5904529662208b67af4a2f4dbbdc1674f3bfcdcbec714a0922c7aef63b911afd495345fb853fb4a7ac6ba00bb17cb063c148ecdffcbade1a958a5632bfb82b9a16ee9847a755cd2dab6ba963ccb05555c96682154d479cb05f5bb55b82c67',
+ '2dac1599844d82a79c7cd1669a1c6976267f655167872f8b2e0c5059717e8651fccc1770638466613b3bc4fc892f880e7b2b625856abecdab0418251df3754feb176b9a95ea6c7e6ba972097afe00eb2ebc6d344d65f3ab6c7f7724f77b21cfbb673a34b5cfdccbc83588e3cf37723eade175f1eceea41a9dbf5c85e213607d1',
+ '067ef2ee1e95ca546882e2a9d441dc563235198efeb52be97dc7894f092b8718a89c8571e4526602d7cb44ce86cb615a70a2611166adb7e79c1f5e3d0101c904cc781c2657479c21319464f56fef5b41429062a9cfe0d27a3a3c259104f5f379989b21d3207b55fb9d66ace837b4b054d189841de15762ec7fa44814bc0eedbd',
+ 'd6fc8b4b72b7eea80b1c6f53c11a52510f920527feb8f95598bdb120a0ab1994809018ca83de68674412a6656794a51686de08656ee110608ca4b2f3a22fedf6bea75a6b6dba05002c3e7bdc1f1424970653d38a6ca29c4a21e6e66feb1ec09a798a79b698136a7daae7173e536477de75378f1e5fc5461b41ca741be33f3c86',
+ '5e873df5f280723dadd718875684592a7b2c56916646bd874d7c99b1c9546f5c890f867a48d286e6fc0345f051f6dd1555c9020e758c920da8a56e43ea7389a5ec323ef00a1fe7ea7ddcabebd215979d9a64f0006472c8b1e860d06b85656dceeeb80e5f20b0bcd19729f383c12bb049b3c6cb6f1b4087fb757368338270445f',
+ 'c2925d3d09cfab81f32f769d61dad5a03aec0423be785a7417cd7bf331f7cfbbcc893385d09aeecae00ee628311714079dfa357cf317c26e922423f736b9200c111198611e0f7587b27fdf57549fb094cedd28cc84e3e37f05d10784e0c9c2a7b9b1f4979b342800900ac9f46f7a938ff61d47db18e4a3f1985c9161d7319fd4',
+ '5c32698a0a56b9aabd41270ec1e475c5f965bdd07366a7843f8adf2f8235c7fec694691e94deaf2245d9d6a5159f203079a2c95eb3ee3d3da3ae88f8e0f20eb307af7cb75307fecf6ecbb3f1873f5e21a51d5e933bdce010fc31539af0d71c53c88c8b9b6f5c0e79e121a53c404b966225dd62b834b8f7c3f31c275fdc6c59a6',
+ '70901c61c43a67e647b5274e55fd3a934b0b8790eba58470027afc67476e0fa087337a76ff1918e60a27a944fc6ad32e4d8d66bffaaae404286041b40a26e71b06defd5813aee9c8660b13c24d16ec855b2c306ec5b8686f0c4cb2bcdcf1c4c735bb2f6fc8a0e174a489ee2f11aa9080bc0f6c0715781697f667d8e78577af8b',
+ 'a85ee973c99d8da60d745894990b24b9cad7e450be0e4369175e883bfbdebdbb5f45106e865a797bc4ab9d048882f3b69a15259fa0fdb940e7e9f0e46094ee30e9f41cfaceb5cb5f90e51a0fe5f119ecffd02ed4117eb8ba10acf3fcb7b61cf0cdd5d5c0aa96ca79f88a955eb73fdf828370c8961a7989ff190d582c062b8d26',
+ '7ba8ff928460a47c78aa938519d33978d7172ba2975c0d2bb421b2a643b184e69c9c2713166759fe11831db23a7c184c0a733b0c90cea2ab712ebcef2da1ad7ea31af0f0d81e4127f4bfbae38dce3c91284d1064fd23cea7fb137e520ceffedb9a09a44e52eb23a02848b3419b326cf03a8cf3d367c359c75bb940f56a0240a6',
+ '20dfbdc107b5e0af83b2d16021039d0269de2d27b40bbe6c3ea492597c19e589b076230bbae95807317fe8a5b22e802a78184c652d0e6b490053a0dbf8a34a4f8874966d637cf33a9173c6d5c31a5f9fe47c2c9ef0742d24096fa8abc8731e04d1617db1aa77978fcd18d3b8fbd023a7d493369da545ee448180149293914bf1',
+ '62d432e97b1214a94ab922b6bfc7f0a32f0e9973a737b0b67f067af532e05a506d8a8c66653316756eb5fcc2ca18b43cbe57d95ceb67244fdc769757dc71fb6f0ac88d2eaf75f5edce3b772cfd2b6d32746df5f4643de7388a340afa03c9870f62179d0800e1975993d3fbbb020a05ce78d75303b8c0e2b9b0c839a650f1e479',
+ 'b08f5e5926b68f1c18652c7f7fc593fb3c3f5370fed6331965bb77be681b5e2bf43cefe2d5c8f50dda6949b634954f3a20acc3fbc640b65660b3d3d59e08e7a549f3a14a28329691202087c69e88e7283ab7989a94d5f69b827516786e6a4fc0f9dcfaf9e49c779131b57118854462acd18959b4313dfbd11526c7119eea9f66',
+ 'ed4f269a8851eb3154771516b27228155200778049b2dc1963f3ac32ba46ea1387cfbb9c39151a2cc406cdc13c3c9860a27eb0b7fe8a7201ad11552afd041e33f70e53d97c62f17194b66117028fa9071cc0e04bd92de4972cd54f719010a694e414d4977abed7ca6b90ba612df6c3d467cded85032598a48546804f9cf2ecfe',
+ '6dde9ae867e2feb367008a975d7853ed8f89690f3c87a1107f2e98aa7736f477a527ed64956f0d64c1b23361b261de78688ea865fcff113c84817e5b377e829cd2d25bcf3adbc06762cfda736f5390d01a49079d56e969f03313e6c703e3f942bb87ed0f9c4d9f25120085b5dc75ef5d6d618da0926d3293568dd7d8238de3d0',
+ '107bdfb55c601e74f6505015a5cb87bc0eb0b2e7cb04594fbeef8e0fa5072007eed21183cc854a188a128ecf2062ad8604dffa924236fea9cf5b6e001acd5bb0e51ba95e53a7c21b42aa8b89da78983f66069c6f63a923c6d7208394e5d50f2d9d608f8f194ded45c51f318bfe94afb2df2b7fc657e42e6f7f47b3152ba7a547',
+ 'f62796faaa333dddae596f98cd4de3931ed90710287446604a158b575b4901fd8d841e8697b4df85131c555c246060f75ddcbbbade3a38b7c0444d25b4f6d00de6d8ff47288bc3a54ca1366ed1b2620ec3ab4c0bdc6a313bef880f3587766705cbcc4124a4dd72a7228f1ab61c6a704017eec2ed692ab7549f8ad86f1bf14e4b',
+ '44e9a1f1437791963c1a3e0aaaae24affc3b405844d16a5233b6e5a145c4358b390c305bc4bf585f864f68333dd12d4139a69789105a109e92cc0cf1ff8fe2527891dab4b4fa8731f457574e39f8687fb4969dee7e3af27889590cf8d74415c9e9c0c6867bf0c5146e7c32e306ec7c7055557a0ff738b7e700a70d3e33a975f7',
+ '0ebaefd2153de2c70537ceb27e5ee70105ae85bd4da38462b4abebed11dbcd36ade16d808f3aa54ffda5897a3fd74780a670521fcd2ebf231f60ef7d999e6e94d1b81be038ec89b49c5ca65bf1bf9a675056f2464021fe16355477ba5605652e8327401797bb569fea456c7f1b7da85d0c48af592de60ae3fe6dcecfcf767cab',
+ 'd98557504a21fc3a434c780c328ec239cf8d7c26f58d6ad7b23329c79a8e1e176058aceba778aa1215cc14e5a92600714f94d4d8b2e5b7f45268453ed6f787eea3342264ad13cec78d990aecd5e30f79a069024a6d846d132d2ef0479a093439cba4218205f951a2d53ac4ea5bcdd599e9956c45cd73767c6a0c92ac8ecd0d40',
+ '6e09febed308baa41a8b6e0f7fab61808c9c8471ea32eef178a4888e9a910a77d44026e2972c02ac5ac0ec3fed5f4ab90aa7cf4b2ef7f5dea62ea7fdedb63def35c2ae2344d301d2818105df4f78420299c12f25ae43a60e5089943f07c5f51abc15004982069e5db75721b54cff33a261700cc8151ee9c89c3bb91c92c51942',
+ '7af390cc4edde0f3d496137d0cacd0876b54c909dc5ce36705619742cb42989418d4b6fcdbd80256512a338f843b48b711c06f582dac2607ea5ca038b7126a5726a54e14f37778fe41a6d7532687c6166a50ec638c14600006f51134d29566dc2dcd21bb9ba289122b74c870fc7992cc006a07d1007cdb79e192b4dd25b1d34c',
+ '75ed3ae9085bbf2d034b864d7f87057c2d0b12c7395feb0375237903b3ebd60e724e0c8fbe3a200f518a4f61fedb971c509b794f6e62fe6f4186f894d9ea8ae50d16ea51628d66812f5aa50afeed30e634253025f5ae7ae0428dc86f64f949db8e6d5d96befb996ae4e312b04664d8c223d2c0b396e9673dbe6173fa1cc21cd7',
+ '7809e59ad48aeb2c6f03de775b1371b7f86926ae0b87098e10c69e19d29b18073818cba862b6e4caf45158ddb2741a554ed791507d2649795004e92cc25065db8ea774b0432a457399816daf062025108dc8b210d75124d284a8434ec314c7af20bdc7f99e6e74ef069a07347e9df8b05d4571353e91026354b896c9fd6da64c',
+ '4745100cec0406cffa146350ee12213330d192123af4a1bafdbc5c98801eaf6ecb19724a0346a7b9d6b1fc381ae798ebb0501392afbfc6b8be48462dc2522bb7baec1605e665f2e42f1679b6c383fa1f00a35a01937b5aabe1f2174da6e0d7afdb680223de886fb9cdeee1b1320dd236e6716f492f4fe3fb2c61d8df73f03bbf',
+ '91ea78334108ce6261ddee5d98045bb307a6e8f3d0ee65c1d9bc7d28cd9edf3264fc9cb6e592d072e9238559616cd42eda584d5200729adb619f5ee5740d632dda67f5dce34b89a054fda301685df6f31416cca78f19a8a7124a2a22dd7834847a934b4a451940152cd20ffdb4bd07273c4a2b9a86c9d94e7323a9860ec89860',
+ 'ec638734d336b8da6dfaf3da9e18c7131494fcc0709cd3a9a6618e9ba62533153c958e44345a7531c3eb503a22a5d8bf7c1d1e1d0ab5cfe07d6db7349cfc859d2e20cee81a325462cdfd8747dcd04c7dead2fe82cd96b2a4ecefc070eb067f6c8ba94f09cbe6ddd354d9a2eb13c2adb7285aa3d8ff68045cbc8faf35dd6aa9ea',
+ 'ac4756b851fc8866b9adfac2d02599148e0db7757a62b1e06d26cf8c99556b79c91a5649ea437752cbf3b5f121961821ce1a2a4c635da461e3e14626cac707d04dfb6ed1e4ac40f106ff5ba03304e28a38e99a6daf6d9427c5980d1440a99296c05168f5441e2a6af13ab4760f55407855e0cf7f667ccb5d9bb2eafd03e455f6',
+ '2aa1d94ec83ce7c3c75c6bc847759b085234fd44b407d8f80ddfe93c243556e87e4be8fb30b4743ef1169a24732fb2f5f416042b10c3371dd9d20dda29844d58370700ce69f7df5e69240df77b96027a0ecec71b904f690b875da854de05ef047c5d898d1c0d116c580e2a0906b271dec8e5b0dcdfb2550a40092270eabf2533',
+ 'd1a7086d134c11a8a3204e019f52843e89f2d01a02a88a94d4a66e8d36dbfe924c6922f7ee5a1225aa8e75340cf8cbbd1c0b08e9296e81cec5f70cfc11d763523b12ca174433f246073d1c2877e4812828fdf2e41134bc8090fdce3faecd1e54a58948f59f3f78b2c1148b05687d712ab2b2d630416001513b9efc7f9523f53f',
+ 'eefa0d62254597bd67c87e00fb35f69c5cb2dc09f58d9d14292b547b964232b79b482319172cae1874431deae585df51ebf92ab81e6ee57e2a6cc492186ab540cf417b4adae1983b6b4371f8a09fad9806dede755c52638399a58de1300f00ae92cc5c1ef4ce1dcd53afc053b6e92818b4493f6a35a1e0cc7dbef5916699dcaa',
+ '56dc2b84da28f94847f598980ebc2d5892274e1639d0b7ecc24c3ea8d968092be8b2fe0f313c7b8d1a9c479dc737c95eeec078b9e7fb934103c7125e1f5bdcab79d03a9cc2e08c6474ed3b166544ee0a9da4018264fa338da06f9e2c5ea4edb4af3cc973b59c9496fdee5a4a0f6c042244dbcfb9d855fd98404ccb5abecca20e',
+ '3a51f6fbfef38724347ab1a4f7aafb7a999aee9b890a19e87af6585dc16c568bff9a5148012b1da5e4d46c207d294c1bf8b6f18dbe4bb5f89d975d9b23f89ee84a92e0385b9f41be0c05ddb9eb2e4dee00146d56ae9b6214db24dca9515f996b63602b34d3f6fa57f3388cd80b6004dcfbdde95e21a329247dc65ef113474ffd',
+ 'aa02f0b377f161ee60b0fbd6c56a537c0358cb8da62b63d5daaad203239cd6ac4ee8c892a8fb73256d6a264a83d8085c681bac706a9ae5de16f9dcfdf2f95f2d6f997c1b19824f4011a118abbd169001be4d7ec2226a85cddbeb4027708891f8f35e35d6334d9c46329ff880daea9573eb3768093863eaac13c6270906131114',
+ '72d18951da90b1f6d908253e55da1b5b476d6a936cd6e4433efce72422f92fcde3c3ee795f0b1f0b8065174f6eaa5d83039abb1680c695af7eae7a712726f97ea5feb6b9dbe1bdd1537e157b78e699fe063503f5be754a505ebf2e9dd0a31086a2cb089ab6da32503b9a4848db5776d5368669b990abaa2fc6792a2f873a1eed',
+ 'eb6b60d0858d6f87f5b9ba7fc75acba8751784ef886061700047fde7f692d868800e5751d5260c7cb1b338b9fb168e7ba6853ad1d5a2229842526cf0e0cc40ecbff0cf8e30db94f22bb8d9c9edd87e09e506f6e3d11492f625ba02c2aca1195f71bad06ee0d48e51296ea697e5c921bafc42bf0dc6df38f07028c746a238e929',
+ '36b5cf31af37c90334f2f4adf6a918a22eff5e3e54dc1a4f9212e8d47841fa05f1f8b093761c6930818e9a5245081d349c48cb1e41714ce73fae2eb8a91835128cdaf213229297f548fb0ad732ca38c05ed5ace1c67a601a5a3fd3c0adb65b9eefa4bd391b61fb5971826dc427b6134d5cee2a0d4dc1fdf1cb0efe75ede315ae',
+ 'f1ab8fda839d00f0477d1ab6f3badd421834fa89a4ab8075ab77b738677a4cdf7d54af2a81d5ba9bbdb893cd2e8ed307d0f8e8111c19b846ce4b86ebeb111abf034e1cd3b3b4c29c6f7eab477e620a4c46c10646ca22610271de58d6091ccb340b009e7e21205f1ce53829cdec1ec83a03f81dd1b8acc4d01d98f5a0c884a865',
+ '6bfdc8539fe6bf99892c1c36d521f7b17c224ee3837755fee57a0dcecefb183e09e4cc1dbc19862253a2412eba0c67d2cf0ce61117668767af0d7c0a868c376fcaa48310a037cd6d1865c25060f4205638f5c5aba5a40d15ea915a34b4fdf408958714b3b3083b80c2bbc8252fa1ca459e23133997fa8e107c4cd2d4bf17f60f',
+ 'b551096a194aee8992991325de92c9597c4d1c156c57b47036a7f93f2dd47be6f585906e43283fd8e4e75cb101d7f5e7a173eddb6f4ae7b7bef46502ca4a317240d7fd010189464223ac7ef6391969dbd5abc8c44bf335eeb72d4e92417215b79f2f974adcd5cc7058d2bf1b11c1eedc20ddf4f887bc65bd293afa161ab3ee5e',
+ '868bf010b6e26e4c1f91f0614ff42bc1403087c33b7e229af6c718880072024f5e7abce977c36c782daebf804deb7654298e22ce83652b43ad8917b6ef34094c29d28800b95b82989fdf91d8df637cf527eb014db2d8d2546c74ddd257ccd04c2dbeddbf4752bb95bd4eedd1cf04468d846fada6907e1eb67bb0f14200e15f35',
+ '852f420342b4bead2e714424eb0f287f077602047f40553d816d6e4e76588f8540e94d33c00d37ba9c63b8e83f393f8321b69c254858ae4a0fa23ba8260e1fbfda49a9b0969f4252aab44f834c7659bcdc4f6be96d9fbc7780698eae124d5641dab61d23cc54269de1cdd19e1aafbf52c3aa37f5f5fcc9ea5e2c310744fb7e34',
+ '01c6d5c0272b631c3f9d1c0687f7c1496e77e1479bb9fc8f31e6e8b252297453e2624c7e8d1f1c3b0bc8f862a219fcb0edd52f1bddb9ad63fdaf06eafa45e1c5625de513ac26d98d794b095f196aec3751c7059b5b42077f2f863c17018427ea0b2069288c29e13d118f17a6f3d0db0321b4296e1f3a500c4fd253e170cc90e9',
+ 'a74100cf30cd26416e9878739dfdb3c1fa569d6427ca8ee9d06630e18f6f83db0df7248f6bafce5ce0fc21f5a34da2570bab04fef492a65866ff5c7a71ca72125b36ee9cfec716d96b53327dd35c9328a89dd498ffe3601d391e344de2b8e7f8d925e75fb1bc05a058c53475f6d38d1e1854979c0e66c62091ec41c3aae1e877',
+ '72c21be6f0c4df7cc8a53f9226f36146f9ec5bea9c94f3b7b604a8bf5f05f72484ddd7888c6986c43b6c87ddd727ec348a2ad1fc086929f17192bd47799e71e1c6a7c9c49af9adcbb16b699c6df0f8da3069829d09bd231f942ceeb81be0320c01c5fb83619bdcf9f24aecb72e750fa2b35177b3e9b86aa7e57945f88df3c10b',
+ 'c7627c9a6d1e7c41c18657b598ac29b28c4d0ef047008af7feb329353b58624ee0dcc1b369594676718c085d77891d35e3adbe6844d5a7d2dccdbdd15e0cf39bf69e6ed58a61e8614074527740edbdf7bbca7afd2c2b80b6ddbe0f73ad7a93fc1290cb275a9e2aa936267e2b7840cfa11c8b8ad78569df4c0a6c6744b10b0a19',
+ '8419330710968fb40ae915e66548f1ac445509e361f583abaf5f87173e7346295f4e3bfd0a1bb0447c2b85f424492d3ec047f9c1c4dd99fdfbb4e00a70bdc7898fc7b5dc8851fd92f49ca825bb0576e835921f3b8fcbde0171cb3054dd96da775bad290b53e07d86ba6409e2f025d492e95d03ba8c665b9f58cd025d4da785d8',
+ '57d73f3bdcaadf51fd61aa65a01dc75638546dccdd899a1da25a086d23c05d1a5d93a157c34cf6168e0f832c54e9b2afdc569ba33106c0d6f5e0fa09f848b350099d56bc0c0604364d6f89ae14ce8e767aab0fe87adf104f4b9c8c05edadafd803ff45b2e061717ae488a2350956c371b95cb2e3e39df44f4d94a7a82c79b779',
+ '0c8404fe10870fdac0e8d21c99c73d04a78b6d4c8fd3cfb8d3ae87ee520e13880e7a2b683204ec4b547b36a1f7e1539d541fd9885af8d15af33c188b893e0627c9874e21a6cc25e9a11ea7404861764cfdffa4e7f9ded33d918f9a96b7c82b70c31433d174c902db313aeca1952fef392b929613766b1c88350fd5b6e493ca8c',
+ 'fe1c33cadec693cfa53250d906d35d1e2db8df4300be8f2aa505600b44a063c60e91e7777ef4e44bde7a9a930e197517810234ad88d44a0ad30f84d734cbed08a7aaef69900bba794380ea7cc98363cce264807046866eef30cbd2661d4db2d9d14f92c79c73dd01db2d87bcc177f1e458c60db3c23dc283c52192e0878e7ae2',
+ '023004dff89f0820892be15fb91dc4c498936bfab92320eee6c117d412e3006c8fe3dd8382a411bc9378ba90e941419455d730facdaa435b1da9c1b4d9620cae966a772259ff59dc50ec609fc0ad276a3fd40afa23ab39903a1b0bf4bccc95ba7d8e7cc467f80708284e789328a89dcebe51a201a36e2915a7e09c9ea26bc219',
+ '0d612e1953e7cfde5242fae7d51c8152d2a4a7e44de128fb7a467ac4228653ae47aa6b1f0b608365ce96a6ef9747afbdb5950b15a619c0783777aed4ed3515fba4cd5854760001d0de6e04201d644826ddf563a9154ca64c2c4059c16129473a6af27e205b705008caf29de3311a557493eb38086322e061a1ca02f3460bf153',
+ '62908131c688711835177348434fdd1016941788765b50752430716e6dfe4f3dfe8b2588fa4241b14a35fdfa3562f1ed303567fbf74f0f63dc86f5555f2daf570095dbe951d3c9644fc47428f24fb7f603eabd9b2e60bacf58d1d85c33fa75830fb68b9bf3c56ffbeccdbf1aa59e95f538ba01b14415b782401904cb0eed0787',
+ '4745100cec0406cffa146350ee12213330d192123af4a1bafdbc5c98801eaf6ecb19724a0346a7b9d6b1fc381ae798ebb0501392afbfc6b8be48462dc2522bb7baec1605e665f2e42f1679b6c383fa1f00a35a01937b5aabe1f2174da6e0d7afdb680223de886fb9cdeee1b1320dd236e6716f492f4fe3fb2c61d8df73f03bbf',
+ 'fc0723c3f84de1178d14375c3307f0babdbb2086813f6970b8f477fe289ecd3900bcc4a60315d077e89406030155db741c002fbfa7568ada1709a5298ad12c39aabcc2b0d5c646847ca9546cc9f60f9485651e953869f5a49208560909ea17d4c4b025cbb887c9a611fc2a7fd3121484c191f7ef7ea23338f2999288ef121672',
+ '5a40298e323ce97549d4c820b0a77cbdefeaf6ca9bad947a2b60985a0795d934e208b8334adc56497d2704ce7fb1fb6a69f94e3404791c1b962b0a86fc4cf037f960d375ce76146a0bade6caa4f705b5471da6dfed04a9eeb02e1623dc83c73d4852629ae7938ba09a6f575b48020367315fe6117fd4a4b91e70a57bcec3c50e',
+ '99958aa459604657c7bf6e4cdfcc8785f0abf06ffe636b5b64ecd931bd8a456305592421fc28dbcccb8a82acea2be8e54161d7a78e0399a6067ebaca3f2510274dc9f92f2c8ae4265eec13d7d42e9f8612d7bc258f913ecb5a3a5c610339b49fb90e9037b02d684fc60da835657cb24eab352750c8b463b1a8494660d36c3ab2',
+ 'aac4256339f6377a4fe225d50e74424c80e0f96d85d162c410c3135a93ad397bb8e4e7bc523cad3d93706d2c7fc46a8aa0e8a232fc205e1744a207cd4e3f3b4bc54620ef20a6f8c2d052f6febeea50cdf49796549a3742f025ba90bfcbcb90633ab37902897b40916f516953b32e1e9ce3b57edb495d37d71bd25739f2995f4b',
+ 'ea7240529980076d3b028a083ebc4e24efdaa06c9c84d76bf5b2d9fdb842e1038e487f5b30a5e010cddb4fcdb01ffc981eb0fcbc7d689207bc90ad36eef9b1ae38487a6dee929f3ff929f3357cb55253b7869a892b28f7e5fe386406a2776ed4b21d3b6e1c70cc6485947f27e9a5d8bd820380b9eced8e6b865206541be39fdc',
+ '93b7ef0e470ddfac6aef93c0dcd37b8f1c4baf5eadd978e3bf0512fa0baeb099ff9ec1061b6172479b5674db5606ffa7e6b5173309370e1647054aafd5904816bad5e1523032cccd4d786505e241ac83a484911189666f287553d6a8164e8dcb0c85d75c4e29f624c97ceea64a2c8b0c9ddfa560f70fa3ff91183e4b968f88a1',
+ '21063443bf02ffe9f813dc6688920d036041a2a3a63a9956fc254a2c05ae03472537ef3489c93c7c68517c7588094c5e033434ab4b0ecf9e6c032c17911f73adcac6ccfd0ca57c427ae85127e2ad41d98bb94e5f2e6aad2e42ed26f87cb1bec6971c9446517c0966b6402321a06834997f3ab66756377a2f064d0277cf4e2bb9',
+ '9724c0d5c989e5adafcd7527fee269ea14c0aec3ddb62596f3fdee9b0993e6c689466e877c0f6fb4aba29bc40343f53d3edb936fc04ba263bf00ac0fa7c816cbbde4ed09025ee2405a9d9229ed360b2ece058c20db7d8d28e43cff000fe2d5627a24c3c1231c463805e3e4c08462b5a50b65223bf4f1edcda8d872d6078a2c73',
+ '12353bca6b0f3d545ec4b470c69272f72bb5589793e6ca769a226018c5acde83145567a1d6fbede5c150ec3142dc58f81246d4a00acf242a381fe51432447b7eaaf84c8d43222c0da3a0175aca442680a21cbca1d7f70097e82491db7f7d75a5fea552555a8de0122c3d9eb105d1c4d802c17963a1664706d3bacc345360b240',
+ 'df073817d8687293257d7ed1816803afe292d779f34e14b0c5ba6e0ac1e6c3b9e239f4f02110f4a430a71e906a3dcc7b0b7325bd9cf63600b25d4544d8556126cafb3e61e4894095d935d647a8560929ccc9559cb393b77472c707fbb7ab8838ff16be71091c7fee8aed4d0022fbe3428f5b0e1f216ebe946dc05d3746305f79',
+ 'cd3f17355a1e254b9821276141a850f0b71cb3cf4824a803b01c71d8dfc31d31fd33ad1cac1776a98d18c6fd0598caa241a3af21772208d36f5270f4437570f963c8a323dbb41755d948f72369e7672b843eb0a849799d448ab7252e8abb496d05e44074715fd2f6849b02fbf6fdef3488d6fc8b45922fff0832d7af3efc7234',
+ '934dc1ef76993aa82061cf67aaac7714f12e25aa8f6f54840a2ae3d84af32481511d300126db7dc612a5b2ac0fdeb9c47eb316541846781e270c8ee5f6731c2e86c94e4482594c7e75d70ec43bfe7250b6778cb2c2fd3d176abf07ca5c051ffb9a17c4c0735bd059b2bd8db81553c94100412dce73dbcaf63a0af58f63f15571',
+ 'c84394086457d8fa900a57f18ea50a93be16f06fc28b5532de40541da5959bb6d2646ebe7491ef644ee39cb87d1219625b213094a4ed163dd707ef80dfbf9564f38195cdbb657babb4015071d58260c973fb418562fc10d95d67fec8a77f0bddf342121b82f906368b0d7b04df1c682ecd4c2b2b43dfcd6f370888df45fd8689',
+ '36bda8d33b3bc10f367caf71c5ed387fe5f1493c1d3bd2aaf97ad78cba3cc5704c0c02ed78dec72a5bae329f17639720c8f91817badf7511d99e257c68bca5aef6e0102a8e36f01f2f1553327be0227db32aafd8e31d8d575a1ca4145da7842e1d7ffa11e60be1f898fb3bb15b2b81a08fca370702bbc285663b7edc02c50cf7',
+ '3722eaa433830abdbcaa9177e373bab05fcb8fd82fc3afa581e34f08d3c07f5f58d0aeec9d7e71866c7a808ef15301251b470a9c455a612c16a586e8a5f1f3efe184a2e6313bd0a657d901319a9f44eb241db807a9474f3f49cbd2c8b8a225859ce5cd7b36e3af8545701a482780086a42f4a1ffa2b30144e3fd3b9052fc9e87',
+ '03074e714d5eefdf5b714381d80e694ef37c2647b374d8a38a6dac2a2e1d11dfa43c6de19d8b0e93061563fbdbb46c683cd86f58c284ed981399d4adb457f6731f21ba04168011db366bac3acfc66dc8f3281b7fcde159c5343cd9d98001cd719d3e9ea25e47e1ff13fc87055d4a53b741f592857c94067216dd23763a227e21',
+ '739f460034249e805aff665d6248a594250695835aa24cfa5d9c9b962f7d374abd0d163f65c51cdeb687f72b778d4854eba00389548a180fb6cd5390dd9580b6a1ecd4f8692d88b3eebbc77c42f2cab5105e425e252bf62e2fddade2c5424ed6a8a446d249422a268b029df9c96075de1baa19a8d56f2d8051357234ef6ae7d2',
+ '082e7b4cde8914bf07c288441be643e408f6cb5ca932f67e9b975bd54ca706885468708009afaecd4d9ee846ab6c0d70a364c5a24131a766f558ad219e06e4f7e80c68e9d8289040a586662fca865ab459c037bf92465596b4281178133e7a806b214dcd747b24e0b681ea459fbd9276d31108fcc3f968d781106f20d3d62fed',
+ '892525a0f02aae7f2264cb024632f11e8adbdbecb7d0c7080832e2373c94014cea02914c1542d1d000593fab43524fcd1f3a63670f6ff8509f1b1da881fb2abbde65ae27ea89a942bbf7fcb65b611d6e1ca20fb62b00929d68ae979e7595f6800d55637b98869f9cfc43eb6bb5e9c2ca281cc720340bfdb70bf5366340edce65',
+ '8b7fdf792a90218f91998b084756f32ff81488466bcd66ceb4956702ab343ca59c15bdfd405f7e20ec61a36e0933f55fc49a357f062db0b6a7b613cddfdb812efdfee3eb5b617f02918ecde0e9f6852313d8fda41a64b2b5972124a7258ce8901402f84a62df4dbfe6e8b064cfe6cd044d9489bf8ebb9552ec9c4399658e9952',
+ '6e4abd414dca21a6ad433146986273e2da952ef613cd1f9a0a836ca644f9de19d6c24abc77845002d9fd48333a447ac936518d1bdfc043380fd26316fdb5f6ec0f05b5dcef92c3d5e16498b854fc3db9b6ddbf098d4bdeb2c45305c2420b7fabc21be7eade7ce0e76c80071c0e13267a0540ab0846f758ced00d3bf13c84e11f',
+ 'b6acbe5df01480614143c94790974c82d046352124f56a0246861042293152f7ddd65d22b491afdfa39092dfea21e318f70f18bb882f82671136ce9c5dcdd27277e8878bcb535146898d87354ada2fd2f694096de5c2d06944ecbca8bb2d4b444c8941807f81edfebce5af32f8eab716947c0f1f81d5dc70a94fe14f8a7644d5',
+ 'dc058f909e7170bee56c4dfde862b4314f68314a9717ccbbb79bd42d0407db7552eb02c45c29771e66043b0e207a2997ced4346da67bf066790d542b96b0be33eca737f26e23f84dbc5b2e52ffdefb261428bd3eee7492d235d21c8f3379818df15eb6809d06fe322f98ad314d3632c46b8d542436abbce93311b4c3a30a2e6a',
+ '48ca2fb5b7e4f471a20911af6a66158e45aef700ec0262ce941350dc208adaaf95a84e2cce2983a2716f690b21dce48ff580db4a29f48c4f148522ed5a958931633f81ab0c3af1759c007e72f92f5dd41c2f65e1c21569f664c7c4cc6a6135fa9cd8eebbd9dee7f20b05786b5a262764a004bf4c1d2da2ca6d215f01b6b68713',
+ '7e8bcb42e9c0015e96f4f802520a15cccf3fb280540e7108b251cfb97aa8fcd86d1eea5d340aa3f65234e14f5639d89155315729978e0fca914732b513374138c3c01f74cab36964cd740a1b1f59094d3554a6115ad2a6e5a3e2ebf3269a479367b692101383faaff1fc9bed1532500957f1c8c203a0dc62d2691ffb199ab7f1',
+ '7d70d5d8676518e8f4ccfb3660bfc14e20aea6c775a616b342d21d3a1b421f819eebc9d106ef47f5fd1fb7e3b2bede9f2c881a5ddef398e67bb5c73c0b860d813f27b81501a337ff50d58a8e4b2af73f8ba9ffe2b63090f951007c61d67b2a34072d8ced810a50cd94f65b7e528b73f7e6163b9f28e265b56eba23efa4a9de61',
+ '20a0f85250a95615b7a40f25132af070aa388d86df777bfb03c0bf0d6ddf8787cd9718e6bde708b9998cad4e91c7d58afc60b719efeb2ac80f4a152ea3732792ee74c809bbb44fdf397b753809b409f796f2e6dfa5b223f82de08935689c4a532a3def047296934d3e794f2da47af57f1ff501212753cc5604880369e3e05894',
+ 'e37e9da1ddfe11a2ff6a95025d1970fa1c2997bb7974d0010cc017ec4e36410c5a16dfbaf0a865afbf768ccfe4b8f446ae100ed6a477396fc9772b011e9c938e6925fc8335fef5481af36f163e1e66091ca1c476849b827ee35410e3c5bbf71b9813bda3b3e908969749077e74310e6aef46804122c6f255e4be8d3b4b7db4db',
+ '4b7ab71376d83edc4149b74ab10b7c1b1b6fa9ce977f2d63b2e321626306591e4174393bf287ca6ee7420d84467d90a628423edb05787bce6cbe71d2f89aa4237fd3cd6e8c1be59410f180ac54c65c47325f3af7857aec12deb4b0b379aabc026f5f1ab52cdeb6d72420b6c8c22f0986a18c432affcea8b66f8d860dcd7ec943',
+ '806e9111c731be67707d49b9e4248e82039608dfc6fa1645227eff6f30eb349b8c7cd6f6fbf0785550de26259049a6a55474fd536ff736a3d1135ef7ab43d3ccd413bf316c35df7ebfd289426b1eed7dc62f9b107a0f45717210c6a3fa5f646621dc52ab6229794a840179f7bfccea732070e7ff2f69cd16ce1c405b64686fd1',
+ '85a438185205f773b7b39db2a71ee86aee341f9b2285a2edd7a5c53913d2de4b02d79de7ea309c09606f3771bddf9e5fcc66289cc5b0ebb97f89899be18b4c389afa769b11ecd22e9fad8f38fd614ea5f8eb7a066c0ed8d86fd25f09cd2a49b8b5d36a3db17fc169db334d0e4fee21c2dc8bbbe1ffe892d11148ee8abff6fc55',
+ '18915f3811cc77d3d9e41d543f3bbdc827f5781cddff193da94f4b7da46d0a39c93258b84fcf31573712c0e321e5d34763188d675c605a4b069f2880cb65d5bb9ab7e3c039107382dda6718cf8ee0c9f5262699d5b8298a5c019c7803cc1b53cb1a96a167796269ef32897156c5f4e1a1b5d7486816eb994fe458e459e899402',
+ '48dd9054dc7703793557e492fc0fd0d45db0de0ec48683f1e402b3affef849c9600ba9212c65a4575aab9c52002fe81dd16879f5e4a0bea0b8edc6007462a5e77386182dff056c005da69b7c0b7db97b45628eafcda285eeecf4c5ccb4ae9d6f8938259fe0c1221d45322b36a3600a97c086656307f29e838afef73e4742fa09',
+ '3978b24f0bd0829e22c0596627d9d6d858f1c69b8c19486771cf30d01975aa5fb50220e7a0f85d169f96f24b674ed8a75f795867a84a28715b00d72c11606a95a9634890452c537b963c58095ae9a94e220c081659fbc77b82b72eb7c1661d369d03f2f00454adf58f1c5349089390f32a139f51a7146fae705afe16306d0969',
+ '67541f77f4e40d143035462505de14a02124b992ec1d0064bd15185d4d30a2696c510919f23b12eaf9f6b4ca497529d81475456ce4a80757d1136e6cf7b48d3f2769e22cdd0de49b72e4db839339f42df245953b3b53eee84a22d1919b8bc375026353b99ca3aaaf05c66457cb739e26235c5007db66dea0900ae9d621fb6b93',
+ '782ac16bcd744ec016ffb6b014e0c8983dfde231fa72c31212349a7766f46240e047723da60350a893ecc7f3e79039c53d6f363fbe5f4c83952f2177a28bc0c6731f312870004ce45547ce93e6ffad26de41a92a289d244b51bc33173e44f5051afc24b69331e97a4658f51677f4cdc506ba657c9ef3f1723023f8e0a0e8aa05',
+ '7b2f5c2741338d25d8f9d4bb0fa718499ba960c65eeb399fe94b59c23f4e81f5db11a86df583559c02d24d4a7a236ee7dd86db20f82959b065ccf9795174f8d38164e3249749feb192b5e7b395ce77aee948e9fe44903eb24c4adf9e57fe85ac750e5673b0ec510b9289eb1fe811fa43c6d5d388cb89af4ea6af545ad953f129',
+ '8917aa6e1cd35af30eb5c7ac200e54835d4a0777a06a2fa756b44aac85a8252c0e3745ac2f3086a64bfb02dcee8934eb0c8b5e2389e22796fe57896fbb8dea8608338931b17e1c5cc1d7b8dc8dd1f000f45d4169e641ae1c23c6a7d645b12fa001753ea2aaa7643cf6b2b05305ccd0e99f2979f1be6e0a614c686c882dfe3ca2',
+ '1c685e17890ee079ee85cef5ed709356f4199e657aaac0bc85a1d5d5707ea666ebbe0ef1430d5c96e4b8f92d1c614b9121f6d83e56e4af1fca8704a101e51a0cf89d6613631af1aa390cfe177219ed4c10cf5f745cde9bcc728430b4ff48dc064aebada6719c665af56b24dc7900412ec78d792e14014b6a857fa235f20eb5fb',
+ '9706d7370b66bfa78abb8b25a9d6143a9aadcaa4f60c9baab98717ac8fb3d2fe4e960af7c35b8a44b14ace8217f8680db2bba312c36165ec12225aad33d24efa085cdb1d876b4555bd6aa27013af3e9cd1f33d7be0068275d4c0d0522a3b2f08cd3f92d1dffeb681b7024d1726635c92ff3de206d661baee074bc2c4fb553dcf',
+ 'ff8468cf11d6190cae4a1e16871ae0817214fd441a889bbdf564fdf5779e542686d2d77a2d2d151694898a5730d9715b37c8dac4579dfcb8a762cc2cde45cf63c33e2cb1e4f205858bd807a7ee9a40bda6be31146285259ddd13c1360dd1db2b9e1090fd9eef90627a7ebd8c2923f5aea73d2bbda508bd747fc1019a6e0a2187',
+ '32e5a9f3c3f9576a21dbfed017b961f118cd23f3808f2c2b1d294e35ee2b28432a804bb584a19ceaae08fa561ce820d50a1bcc3fc05b213d15b6495b323c605e98fb8dd7652d72f8d2afc7a701b541d1f6bdb901e3c18a31a8b13be09a205e64833eb782eb06a13c96b8aeea4e8a8e8ce39a325f6f2830aede026aebae3febfe',
+ '4bf841ec0a4211b05f9a45a127bbbbf6434e8642910e8ab11b2a468e8feaf009f096c7388a94a55b2bd0d364906122b71e69372ed33c27607bc544232726364fdb9f4dc587b115b038832b0b908450647452bcdf04dbb47dd0c25f9e4804d6c575db7a9ce7e28a38ef7af59d0e6d6c85acd2bc5d0d315b9182e74009dccbf8f4',
+ '633974ba735a5e57d1e804bcdd4d72d4a9e9df0fb9bf8db2076ef1714a64143f784e39658ad2c0d17f814ab1a3071e4111a5cce177e2106b197df8c319a549b0f56c20ea517ad574f7fe242b1ceb8fa0e560fe232967a92079e337af5dc42766e17d707150b864e54048da52ce5f8c982b01befb58b821792d8af65aa028760a',
+ 'ea526480a096a4d89306b3cf86eff742ab46e4e9ad991ee7f344dd9f24e896cae619d8c6ec5774312f40e0b77b03dd282e1858ce3d2f8efd776674eb0ebe56c253d0bef4c1bc97cf3d6392519cd6c93d660da36ed9ddf76c3124743d2747407eb8dedfb227ad57d945d79145f04e03a9da8e8c738c8b9f5baae7a43c78699b23',
+ 'f6eac4c4099c3232df018fb3c837527b8021a1a20cbb5d1be5aa5ee5581800852dbedeb38742dd540bc46da844b40bc546e60a4492e8943a3a93ec6a46e0f5b855fdf8e188a0a26a9b9c4cd655b2801c23a9b85800a068c197a43fdbac7eaaeeb8ce9bb6d35e885cd7b0b6a5c3d9b76a5d9232481c8de2984405e1a15399270d',
+ 'c9f902c8c02c5b24bb54e2dbf5c9573bd46bef39ccf15462817eee152b7561f03f8f57884c2b7f5d22e5d60d3a6925c7528aca03588ebc7089ccca2eda7a233e97c01b374a102c3adeba3b2704bb1d11d6d65af0bae731968a73dce5f283153e19b3d83c83866ba336fc9c931b674a02a87a2669bca3bbbcca9baca03a3b3dd9',
+ 'c1490ae9579828b2d6d2935f417e0dbdfff5d424de5ec50557ddc7c3140867c4af9bc0c7bd6c9e780ba1e341272029642247a84795de5a0ee2495e6fbc029bc2ea47a5584710e40e0e44f322542c4645d62810f1f5a163fcff3e996eb05bf490f9b78145ff6c429d67258ba8d18bad88a200d2ca079028f737244265f8f9bb53',
+ '45fcbdb93acd8300ddb88012ceb55950f4da61145adb0d4c3dcda868632f4777ae2a008cf01857670144f9510ff0ad48369d875c50865e590f6e81a6499ba66d922323fc1066616c8bdc8d80c41190cf08ed42260439da28db5faa37767109981c6d90d142c08956a408a465941eec2f9254fa381efb6800ca2989e393b9573e',
+ 'b9e944e0b42d0ff454f7f8aa24f00e9ee039058ce4094111e39731b6dc3ade2a4acec4cf9c5be078e4f10a72d3d685c1e5e4d5abd92cd07b64dff87f266f0853ddf1cd61d9c637a9b07ab0be32ecac119faf827218b17ad4541a27519477f76ed918089f54b63d0e1e5a92982979ac187764b5e989e066a61b1065340e9cd203',
+ '2ac0bb0524c22b902de34ce64e6172d1b2074e159f517ab1abd152622cd10669f03aed8e2eb51c65bd0f38d084e288c532724e512fd558ddd257d2b1d41c5eb6040767803ddbb18b95a035c5d8492d4d35936b7b3630ee20f625b70f8e71d9dcd0efd0e3387d138c1f5eedce32dd88f223334b9a9eab65017f04aa8442179f62',
+ 'f5aff283b3aaa4c71b13c590771d8bd3358d76988ecd1eae653c2f9d72c9b2dc9fc08e44b2e34ec52dbd245872332e342b5cf945e99344da0bca069ee221b2c913b7b9973cbf50fadad7758b6a962cc7ce640f78f38f0571b19b527ef2d9d09b173b7b64976633cde909be13a56d0df3e64ec019f2eaecdb1d571b27ea1994ba',
+ 'c0bb12a5da628363a71f1f5c9ce715ce8995e607148d772b669f6532242f9830a1931bd952bd2a44821a8def46b92504b4b0c5da50bc43bfc727cef5e0ef81faaf24390c0c92a4ed43a09be40d78b204bf680db0c288755f439eaa9d2b3efb5352361547ef2919e65479f142d86ae35714856692523b359442cba333ef662ec1',
+ '854b32866273c6eb110e380b8f3bfd169cc87a6f6149c75e5667b305637b0895465c10c134745773c31ab3be071c8215fb9a33ba231b087870da199564619d03765965d6b8a1a9fbb79d0726a3d1c90cb0ae67d3bbab4cc63198dd4e2d2fb81de0ed39ad362043e9b6403d2aab825a6481ab1ea271221eaf614a0716050ee14d',
+ '99494422460ec858a24394f603b1d9b940a24ad9c6a3d1e9e88781fe77afcd139389f7acc057cbba3d328cbf914e2f32667fc7259afc412594645162d4feac10ce45780cf9a400c3237ead50077132e421dc066bc19e176c5f21bd312e98ec29f384af8a187dd13afc2fddf08ea34a971ac0eff36311bd86f1c8acb5ac03f627',
+ 'd8efcb416f237c7e05bed9212c543011c39e6a5f25d7e2cba065788a29bce1464d8041676be9fb91216cc76d049806ad943e534a6fd45b10c41bee5d0b005626f3c0e73a9c50d7cb07fc502acb4ec4d2093181a8a1568581a6d793e5101b8613b1f9e6446b20b9349fb69bdfe83f11880ac11b00252508252fe18ea9a0d41a15',
+ '1a0223261ab437a4ac1701b4780776c43f0f8949b3e7a1618c3b4ab6d8ae2aa6921f38a2772b28d415f32905251fd3bd1a235bacfac00a486dceedb8143acdf11b4b611f1229c346f89f21299920b56b1b08f7f4d32511965d7693f0eb326893dd0c096492b6f0427ea450e87d1203146748c3e9e51d9e9183baa42806a0e3d5',
+ 'faa6ce40d931f3c0cb4538a82a22f0d4f3221f027b99d3d85dffb729b751e57496b4fcadae5c72404fac2c54949e4c4cde664b948052479abcf59e1aef84bb9f088030473e9505c603c350ad33bb06ed928c1196757ea3e5bf3ec97e0f3c43f638529394f2a65459cfd1cd3d7041c6bcf8db9a91c1e58ec24e2461dc81412580',
+ '28b18b862ce9541ed6daf81199f9a331133b0ea3e48ff486c1acc6d5c40e9f8f063b7a15704ba3d3cea76b222511206d47e53c93a49edd8d639b7551b224c3f65aa802189648607e259ab1fa9ea665910435b7dc9a4c28aef8f32cf85f3a23e94a7e8a5945e9736702383261aac15ae571b4e8466da1bd31a83a5291745ba7af',
+ '80f20152d12b0a5993a2b17d1f55cfc0c078961ed00cd1c21db36d7a92c339691399eafca830621fdef232b06acd5d33108a5fc8c35a6d5b0eb2ff1bb2598c2d91c094a1ca91e4a5268a16f8b38c57a2aeef6de3a619f869df4ff7c5f5ca8f20c10e082a807719543215653f41ba45746350c855c170f85459315f62a13ecaaa',
+ 'b11389c7dc20ffd0c4a5f887f2576bdc302c7d2af7089a012799c528fa7f2ce23bb10071b31c83d9e58d63e6fbd04670ff1aa6de4ea4dfe94a9986a35032fdb7ea1f44f2452a1202e517257e97ced627a7bcf06e5476c236819f73daad0d96722527fe527891d4d42c0ce658af97428890da04e1efc56c6f337534d7fb57209b',
+ '57e1d3ff5fc4785f9370df2e5abf454579752ea934d2a9bab568d5aeb22ba43e4bc7df9f31366bb40d91ca822026e4e426cc088081732ef993ff7f676c571704a5b809278b50a3778108f4589fa18caa9f0283b3fad0bd594e406b950329d5242e5e5880b53aaa0eb57c66992055c4ffabc0a72ae712de42add2a321c0ca6808',
+ '6b8db9acdfd24150808a92368596557181d445e5a04e91112db2812b58035d72378d8bc00a1ef75ec373b81dc6f1f0a2ed96f302cf2eac8f42ca3df11e6ee678440a28b0dfab2a36eaf35bcbf3c759a71e47120f6c03292a3d6b9b111488a2259bead9a5e7e2a180fcf1c467947f59271cd0e8360035ce8b287fe2b3c3b95822',
+ '138efc832c64513d11b9873c6fd4d8a65dbf367092a826ddd587d141b401580b798c69025ad510cff05fcfbceb6cf0bb03201aaa32e423d5200925bddfadd418d8e30e18050eb4f0618eb9959d9f78c1157d4b3e02cd5961f138afd57459939917d9144c95d8e6a94c8f6d4eef3418c17b1ef0b46c2a7188305d9811dccb3d99',
+];
+
+const List<String> _keys = [
+ '6f35628d65813435534b5d67fbdb54cb33403d04e843103e6399f806cb5df95febbdd61236f33245',
+ '17b52858e3e135be4440d7df0ca996f41ccb78b7d8cc1924d830fe81e0fd279c131ce3546303e95a',
+ '7c67410e0a9e3d7ae4f3d04eff1c2716891e821c6ec1dc822142ce8d9949b1449a1a033a350f0ba8',
+ 'b2c450128d0744421c3f31fab37bbcdfb5a2ff2fb706d1f7e23c4886992c7d215c648ff8edb2eb59',
+ 'a7744321d73938b8eea13754909029881bbd727439fe2731b1c67b7083eb7b5d33adfcca65f5d189',
+ '795a0ba9b02984cfce5e7395fb94d98fcf12ae5db8a06e239c9ad439bf42e523e65a31c3bdf356cd',
+ 'aa41b5222efdea882cbebd11d343000ec2ff6b2f7bbfa746158ea54f32d534ae31c7d3b7a5fcc373',
+ 'aaa449923f0cd3e6a7e74d9c56a7eb6a3b4c3dea97e6a8400e5517fcff54ee4211b640280eee415f',
+ '6c13d74ed004ee92adb44b755be92e8440434704a1c22790b788f50406e0629aea80de53730b0d99',
+ '12541d81c6958221c44a958ecd7f48c08a89a8687d306c2f3814c93ecd498e0485456c33d5fc950c',
+ 'a1e8cf95c6d729507661fcc687156922c8975645e5f36eba8a3069eccb298e96c498767c7c741259',
+ 'c7e5ede152c50a935e76b59979e08638a09cfffd01ac7008056a18ab8ebf8d347e955e06788ff6ef',
+ '6ab37be64f4b1e032c5a43dc03e4afb65c6ab1329fbca9c4c10fc766224f158eb6b7b85d649e7319',
+ '785a1189381824a8131e885ba4b23c2e94e3dfdc03652cc32a9cc1963ff72452997f077315b0cb67',
+ '394575dded531000e776ae4adc64c4affb5b220ac5a96ebf1f72d19fa6aef00c42711e5dfe6fcf84',
+ '14d45ca2a3d4977dab2b7d442c6f9e57ce348e0a6a808bb3cc7f6002b87789912afd98bce26ad8b3',
+ '2a0466dd515d2f48fec5e78e22bb22c606b09e8184691c5177a46e8c70fed24dab147ebc41e97c8f',
+ '3a4182af8c3914d1df57b6321fa5dec68748ad746e0369bb64fc2d9b7dc3dfb3ed9063a7d5cc0ec4',
+ '56e8ada1ebc8706b94f99bf2290365222f6619a7fc3161151cd0c566f4266faaa5dc31fa34f8c9ae',
+ '1e6d00b386bbbfb7f44001c5915448a516954d7a2ae8f4e9eaba807dc98c034a9aae19d1eb4ad624',
+ 'e2127a48f615eeafb927ee53222f5004d11dd2d3a22e5377826b43f08174586a297b82630e932210',
+ 'ee0a81a8bd52c9b1422083522d37f8071896ba625ffa22ad32a4fdd1e85c837796b6896ce194f74a',
+ 'd4254694ca38676404cc2cd6a444f61e230c188a9f92d4ad769287bc1397203808bfd6cd5dbe1b7b',
+ '61b83d7ff9b82b32a89225eacd7c9c25807c8dbac8cf56610e88c875d2797df99d566bda3718ba73',
+ 'adf13d80eef135f3cbfe63ac19e8679b98c01dfd263d72db335e76d47551b31ddd94bec6c95a0b3f',
+ 'f870e26dd47b20d386f63d12458c46d795fe0790bdc81d2e7c025329f8842bc5f74dba955126b93d',
+ 'cd4f85a044eaf7c5a9850d0d708f0905049dc27718679a8f3713af3ca3b756d95c19c50d7fb90ff0',
+ 'e6e97a286f575855cec8a0f4d06327929d41f81d3fdaf9f65ebdcc474d85f4974b08399c02d14d50',
+ 'd763c6360763561ed2bf47749080549b6e2db87514e1ee1c85a0bbd346eb6e3cc29267cbedcad67a',
+ 'a4b540971d9bdb20b47e8282cac841a86fd94fff27b4eecfeef893cb7b1347e7c2b24d69bc7b0543',
+ '9779d9120642797f1747025d5b22b7ac607cab08e1758f2f3a46c8be1e25c53b8c6a8f58ffefa176',
+ '09675f2dcc4783b599f18fb765583668a0fd8ae4096f6fcdc60d4f35b4130fbefcd542ffe7459d2a',
+ 'cfd4a44910c9e567507abb6cede4fe601a7a2765c9755aa2cf6ba4814223811a26a8a1ef499cebd9',
+ '5448998f9d8f98534addf0c8ba631c496bf8a8006cbb46ad15fa1fa2f55367120c19348c3afa90c3',
+ '9da0c114682f82c1d1e9b54430580b9c569489ca16b92ee10498d55d7cad5db5e652063439311e04',
+ 'aaafd08fd89bebe239ab65bb190b86d49c5d39faa50b1109f7dc8b179bc693f0810449c36a68041a',
+ 'b06f7ca7a5dd8baf2ca940811edad87a33da666dc427bcf4d54a8e03520dd5c399e9729d39be1494',
+ '2dff35c2fe5039123d4c5d9feb7d5167e3e959b31841abec1e5b18b0ece2ef25e04d1f8d030d9b1b',
+ '9794cf76aeef22963fa40a09a86bf0e2ba9f54f30f43bff09d44f9d28cfd7b7a45002797cc1437c9',
+ 'c1d60814376aae39c4111246353485958f95558fa38ffc14e4a0981d76249b9f8763c4b3e2ce4ef5',
+ 'ca5f3eb9308604f9fcc2af1c6a3175cd8a75045593b473bd7ae37933c345ddb0982e2dd7180db31f',
+ '808d7aa9aba6a40d1bc43e9b932ec8e9273b892ffc0a769e4f7255f3b83c224bb090b23952ae9616',
+ 'd8b994bb8df02d7803ca2e09d601b918d6b5bde90b611bebf70e078d1ac7b152bc4c2528e60b70f6',
+ 'a89bbaa86a339951ddcd37799e21b5d1688e4abedbc72daf7cc9b5adfe10be34c00a504196cc7bac',
+ 'a9560fd61746d7f986b691f070c920256a535c21a64ab5a2bd771aeeab7119681bcc4761e68ee230',
+ 'f987eb83a3fd6d94ebf3626b7d34fec23ee06c63dfb4078cb38bcc97bd250fda0e286ecd4e64046a985bdfda8b',
+ 'ef257132b7be124ea0886d587765e8e70357959cf39ebf621420c3f3c70e219fb3c5d349b7f2deb222fa26fa27',
+ '2cb8e269726b75e3a6258541251f6e3c5184c5e6878decea51eae315dc656115acc224818ee9851ace474f51ab',
+ '1eea906ca11432655750a4e1af21eb1e03465c6d6f3b0fd8e20391077525d965fcf57d7edb1426ab1c3a42f2be',
+ 'b2f1adfbbde4dd9a9674166ee08c2f4341072475b9b80b1032ad4a3658b408c1aa1fe12ad1c5deaa3149a49ebf',
+ 'a2617206e2b382078fddb0af3743a69a5a7484eecfff6cd96288443bc21ab79f9bbf7d70ff4edd6a0a85704ec6',
+ '7af197b78a27038b0cec128001ce6bb7dc02c0258956f62ead678676301423f4f9329d48f881054e6adf12f358',
+ '96ab1d64acad8cf69651c13e4eb42d7382e38019f3a927771ba6134c12a1bdbeb2206793fa35a4a3b09a1a8d4a',
+ '582c13a6c4d497e4edf69bde35beaababa1b068ed168af20b04cc2f06adf0478210ebfb27640cddb453af27790',
+ 'baf1d8aa12f5ea6264d122938593a8d677c82a37ebed7b43042680625e334c674f9f8a666c3a1bc54fca019698',
+ '735d943cc93f783050c7ccb09acc5a6f60af4efbc8919793e7c39038857ee00621d59fc535e7babcbc5998c5f0',
+ 'c782597141b52135e34d240df67b9bdc274f2d41e6866e0f0da3a6fec241d3a09ea7f1960f9d7803fa7e2741a5',
+ '498584e364f632184bf26a253d0e81e146730963b785eac1d5c2b51dceec34e3f16a464c1dece9277a4e99d868',
+ 'e4298464a0457dcf98ef09cc00d92238d06d9a7574b46769c5773ec939a4639756f2bfe96dc833ed845c2c2a94',
+ '28ae9e327911b76898af1fa0de56069e0d8b67bd2813828f87b88dc42a49a74d4ee30dc13e6f90ff6c6c4715c0',
+ '9117cf3ce9f5c6e19752bf0b1cf86a78ce3adbba87dae1399a2a937b0b722ba3ff92183871e84e282774e10de4',
+ '363b32accfa593e454cc3ec83b9d775a0dd027b017ca2ff863c1fcb9e6215b5cfb2e8fea10eba2179f3bf88061',
+ '134a50abffc94d8540d7ec939b7a28b10916e505ad90843d08b4b51770d48c27beb2d8d548a1b0a50fe64ebb39',
+ 'c83ead9a131a1d7d126b88642221ece7d3a6ddd6016ecc6f40d089d47e1407bce3cd6068fc6918d91906a640f3',
+ '430a7dbd62b3b3cb6a4b2024bd796048ea60990d8222f94228a26093e88f59acca9e4fa2a616fe8e3992277b79',
+ '4953408be3ddde42521eb625a37af0d2cf9ed184f5b627e5e7e0e824e8e11648b418e5c4c1b0204bc519c9e578',
+ 'da6d09682610d23a666ab7f63147a1f05db8b3cfc2c12de3415290b9067803ec09d5f53ddb4e04e69f031d2c56',
+ '22f6c7ddb0e46ecf627aebd9ffad6f36682ef5c98791d25e82af8d333449f0b7ddee5f91181e69e40eaf9dd1ea',
+ '2e2b999290c9b4a3760c4bf767ae44b28a8d12461552cd39095088291dafdf0df7c9cfbda2d4cbb53dc20b15f0',
+ '089aa37f72b2962c18fa4e9858ebac2fc1655ff41ba30715a76d9ac3a88f0740218b1a3ae18ba057bd99cb111d',
+ '4e1ad1054c00b6cdd0267739c8c92994a4af4bf373ba066c48bcb483e38da0e58d5b0c59444279f3181c228ad5',
+ '36e8128355a3dc7ab3fcb28fe93c8e695066334f6610b398737233626cbdf28717ae88cd70626c5d4c6cb9773c',
+ 'ff469d80d2dbef999d7d4815d123cf50ee9c2c23fa2e9aab2c7e3d4ce8afb7f5f0cef6a5d86e4f2eba8fd1392c',
+ '93fd8e208a1d6052388611beb9f047fe91e33afd4bcd74ae6152d5fe5ce3d9073c921e861a24208f0c68477f49',
+ 'f189baeeec507e945f0c4d628a0d0548eedfd254b11faf25458e29a3456466ed9fe76793f83b8a064c7c534cd5',
+ 'b763263dc4fc62b227cd3f6b4e9e358c21ca036ce396ab9259c1bedd2f5cd90297dc703c336eca3e358a4d6dc5',
+ '9fe42dfac92a4a136fa7c9f6e331b5d3a61aa73035b53a8d2517be43721b31b215a96b9bd43798cb5e8febfa97',
+ '98fff7b5f77326c24471bb9c317490be1febad28e2e825afc41c3b97cc03c963405ce3ec68dcb7b19523b76e62',
+ '8d649e5ccbb8bb0032cdddbbe44ed0b5bbbde78a30c0f8437bbca985fca5ea08da15c34bea9b5086d2550ae16e',
+ '57958d7e4c73fa606ef405d77ea4977ac96b8813fc1210483a037e7b6c502ceed8f7b22bf6655aa37e38d495c6',
+ '6d32ba0c063774bf8d0621b208d72095f684faa33ca6f3dc62fbdf95ff0c3733720c6c34d3027b6f2a2bc29cde',
+ '6b97478fdafd3a85d0d9b339971a70c2fd24d542abd3e20eb2bd630f67b86668719df258204bf66201ee80acaf',
+ '89c77d79de98df18f0cf29a9316d6dc46b61eb7af7f1e2de2f5ca6c525bef3c996338194193fd85b9c6e66a811',
+ '08cce7d7f3ccea0212cf0299f27f3d3f393a97d3dd71caf1954e67bc8d9a26db5edd7ac23dc7693372ce9b040d',
+ '1a2e86f6ab2db235e5d7f00cf438680fe5b442dcb1f8c3ae7730b92f097a1a8eaa9be8d216f2576ec3aa321567',
+ '3270b4e48d575f0312659a6202adbc4e877d69298de4090ed47278b4433fff95802e844fbd73fd4ad5532b9b97',
+ 'c704d5793539ef3909bdaa7c29e9c0a0c441814c37bcd062325f6e2e16107be4a2aa3949cf4d14b0f8f8df283e',
+ '5b2cced47045bca47512fe226c1f415ef127a209bf885b8a76f5a24f9c6bce61e166bc3ca75471ddc14a001c7b',
+ '0d4dd35f90f0a10d7d8030e9919446f3d5e2532472bcef0cc5db84bab65c48dc46086f2768d89ef912b8a23d93',
+ '5ef946b64ff80e4df8ee98a357f07c825c3acc434d0f994069c0b88ccc0ac5e192a469d93f19d9615fd49f6b69',
+ '79f87734c46c5a11d86aedead22ed3ea01577ad4ecdf42969650e12000350676f0cf3c04f10a11339baf783914db6d35d7b0d77bb44ab22c18f56d0b8f9d918b',
+ 'eae255d9e083268f896429ce36645502aff9dbeaca7159f93c7d51fdaeefdbfe14c396693a5ce46e9f1157a687e866f94ca165bff5f7b425092236d2a6a004cb',
+ '42521bc3f168b2b3434cb4e44d92f526b41c5f10bfe0a0e6b0eb20c055a636e9da599b86e1ed1f78d4f69a837af126afc9c98beefca1fb00e5cd00948321b2b0',
+ '81b5f12a64f3c347902549a1fabd39ea1d9efeabed3851880df40dc541d23f0926507d62218f7a8a95b1d76959853bda6966a5b2db6001ff1595fa8d3edf10af',
+ '34f5d28d58364da4b95a48c07e01b0a99c5ace173ff2c9216bc96df8e3ab2ad54abd60308857da336f11986e9f21d1cca6e438c66cba7fd6cf17192f8ad745ab',
+ 'cec8280c87170f1d4836cdd77abb2a34410b8d5351d96d1a03e90920a71a59ca1ca344b49f9d1352e1c226d75c74e555e601fa268725be8c88d0f094cc2aad40',
+ '9f65a426106db99dcb2130be14839241d4a92c8becc108d2c9521b8238c5c0df7c2365ec9f20848c0559d6e847dac3103ee31ce55dec0c3644e64c2993c497dd',
+ '2edc66bcca9f99ee1366992fd0f0f954d3d4c5ca2115c2d053f6f8e33c0f6e7acca135f43427a7cf4b2df11a3165cf2d32f89797ed1a7958b5e105513757edf8',
+ 'f987eb83a3fd6d94ebf3626b7d34fec23ee06c63dfb4078cb38bcc97bd250fda0e286ecd4e64046a985bdfda8b01b34d9dc0cf2ab3bf5168ef64963bc918f5f4',
+ '5a35a2909aadd278b810b101ed44e1548ddaf9ba8c882bb142d9243f6b23348672baaf99ef63938e6e0b6ad472b972c7b9c2fc82c23c12f48db45c37a224451c',
+ '96da746779ee441651fb9ccd2da621eff4091111f8fb795cce92a8335ee7e31636195ac724955bab0394c672d5e5c1fb12ecac7140eb58bbc4807313f86f47f4',
+ '43aae2621459a8d5b5cc919445f3dabc0165d136ba01e58187d5ffb2b73f15b90951fce5207a7dab3163aca3ff1875d309687830018e17628111ccc8fae8c0bc',
+ 'fa235ef9f48a666e2e55dbc448ef934de0d22ef5c0ecedc75548c8b364eaba8ef8fb605a9f26c2c8d54171fbc130d28f1f06b9da7e6e3971ab4abbee6d994ef1',
+ 'bf248c7c6101e6e0281c8955e5cc028d98e5688d3f36d754f05620bd26a1bfa6597d0e52d1e2b80cbb196f0d7dc3e2a0471ee984ea840392ee34039fde5506a4',
+ '8b4c9c2783240e19128fcc2754c47d68d6acb3365999cd85d3351c74b7b94422765fe5c346197bf3228383491216e030ac9f7cf2dbf03216dfd6ecec954b0866',
+ 'a5fd99ca57c1fec8159a798792426d296fa1b17d539241de3dea335819b7ed0d92c596d72867ca2f8273924e058f9391a5ab8522fbcfe7d59817f1509afccb6f',
+ '30bc3e321a8978e235fa1b550064b82eaa0c107525eacc827cad6f1d66ff88e31b092cec663aa3aafc4462140c68390417f4cede020a4a736aa2522537d2394b',
+ 'c189ce5334f670ed2815607ba9549f07682e11f70259dee3854019a431b3a0ad7bdd439f58772817b73c6dca4f9d10d59cb50c4e247fc51fff47a614965e0932',
+ '085ecb69492deaa704e25aeeabb7b7795fdcc807b3255f2fb30081f425a9c7990ea104b7785c288c733965965ab8906057e8c99d291e5e7325eced197b51c9a4',
+ 'f5a07e3741f03174c6efcb1f9f186d1f233b367073c56e814f4204db2e203b048db6a0a387853fe4a6bd161ef903cab46671993942de90d71f60fef1e5102807',
+ '887c37f1f09920ba51885934af50a4b065e9e2160e971ed8a676cd26ed5554610cc7cbd17b78019a22bec0ecbf70527b87fb432f10b2691c6e6622b49d37dd3b',
+ 'e9061ef9b298e47af4bfe35903d22e2ea4cedb85c53e5ae16b5e0501eb7ff7615dad22044e909c71b5903afc283c604650ed17079ba6600b303fc97b28c33d5e',
+ '78bab2c40d60d0770c5d2bafc455265942b0d932174afe255b6c0ed4f1fca7750df031dff408c1e403bd3de2f375c2955bf8422f762772ab27ece35e3a6d6ecf',
+ 'a2f1635f239f03be853b26aee7b8035a5f267bf0ebd7a8ebabc0b8984d21fcd3c8693c124d544ea67a56e63dd23cb0aa6a119ce9e43e7a5da1f6c65d33d1c5ef',
+ '69f533836771a3cc0087fc2fce7c42318f24c76acbf8f139b8693db65a7484e8ee777e3989438426fd729a3bfcfbac3f800318ac69f66d6268d7729b1dd46b22',
+ '2daf08cdc015bf361f66be9cfcdd6aa7f1003db66fc95e23f70475c88cf8bdc268495b74ee1deecfe07e67d1d2001b4cdea316e99afab26c478d693a4b7de818',
+ '65e35c88ebfc4c425d0362c5cd125ba40a0aa76516347840da281a2419ee82fba364292fcbdf1b6d1a154aa9453b29625d6a76274647575a6ae3a934aee09509',
+ '84d5824f5b0deb22f4476578e8d0dd192bdb87f93019236a54897e9079923b15f14fd31f9f2adb7f58ac862c8f936aef3225875fcfc58510fbc43d08f4797b72',
+ '833b09f3a7e41110f35ae33acef5c9a76ea93119548154fb154815ac60892c1b3dbb839493b5e0d9ed68c5757dcc954d621bf778263e7f508b848cc9879a6c02',
+ '5efd2d24a034c9cb778e6730c3739a2e48abdfdb0e2c2203073083d5f38b59db813c7730b742afed93b195e4f3048591b2b5e84d140bb2c564342fabdb9300ab',
+ '992868504d2564c4fb47bcbd4ae482d8fb0e8e56d7b81864e61986a0e25682daeb5b50177c095edc9e971da95c3210c376e723365ac33d1b4f391817f4c35124',
+ 'ceab398e4107483ede64ce107c9270e6022778b61f6a258d3b7045d4ad8506d32ece0a738d2cb948a562dbce8d7b66f30e6694d65ae439cffaa454af09abe449',
+ '6a6155dc4d59c6bf46caa3de09666326da308c51a23e6ec342bd12b227376e8a1f11da906b58c8c515bdaf0d84dd48904dc6fd614cb79f5ef4285757e30adf72',
+ 'ce97ded47e101a6d0aa1041138093586046524f54345ec9e860550c9415bfc002d2c0d7beaa4d4dce985d71d89bf19c680429c637d1023350c963c28b93c7e05',
+ '554e344537a09659920c19b40f2850b07235c3c7209993a6de905c82db1e5faff148e16f2883ce087c6da219e0bb892d8272c591515b5163bdb0c4ecbd1c7730',
+ '76d8e0342011d2bca953b26ee200e56685b721d50ed4dda7cd3a05633a50f153884998e67da901528004fb7df4090e1ec4c0b11f3f10bd4727842215044fd9ef',
+ '731ec9f365f28f1cb9c4ebcf89d8648732a6dfa958d2c0152b5e52fae81f69eea26d463e421fba82cdb78f75e5d92304930256a54376a6ea107a995642c45c6f',
+ 'cc38826523a9097e0f7d075a3a039a70ca1e2b5590a6443e820ba1c16c3b89dbe2c65f37794074ad37e81f0a4786100ff19ae1bccab2eece281c6786d9bda3ac',
+ '62c1d149567f05a0b76c4fd32d1f365d170cb165cfb38f922f1716225472eb36a127327007f8f5c08479ca7beac4b0aee26f3bb130bbf1ff390ef344c2a4e0b8',
+ 'af81e327525f3a9104b7282959a0f6600fad7efae7709bb8b33cde34b12f830c1770a342efb6abe3250a0ce7dfcd34590cfcbeb840b3e59cbff03f9cd89aa870',
+ '17a5baecf916634433dcf133ddc2dcdfcf4a680e088928985138c01d1d09eef3b437cc6290614f14079814c72bb75c45eff255968bb29b7421a1feffa00086b2',
+ 'e09ad7d2ff8d559a26e0454bcbfff844e8d2415b07872bc59c93e73698f308483bb8f3212ac29050c1cc46f9aaa92732afcc67accc0e139689acffbe878f01fa',
+ 'fd013d615c6ca959030a520e148808a07e27d38a215634d53486ae8be43a856f3e5dc6eb4fd9874a8a6570276a9e7b25585af7e1ce39d325bd7d195f2c1bb951',
+ '62e3a735edcd87fca0dd1d2797cc0e574160da9ac23f60e39501a5b77688d1287f947a0791922556f5b50afc434818bc83433968931cd752c9df9f04d8818531',
+ 'abc9ccdfbd92b6919a5d6c6b5a765a39662ed90080d3549204dfaa5f6d70d48e1af8c84d53369d658765ef11d7b38510d9f431f99598f8cfd4da73d59b3b75a3',
+ '07c358ed1df3b06d47b5ec763afa07a6677ca3a722524e6103c1056d8c56f6cd0d318adbc5a4a3804afd23a62b9fadf0d358afa8b0eea0f995fb865e5dfbbc5ad2a4f26acd76',
+ 'ab8dfba4414e6986513a9767af5eaed9720811c4b38040b991f3fd8278b0adfea497002ce0cdd48594b5578ffe1c6cafc0b4513e9bc47ee07a1dd011b250e601881ecca2f430',
+ 'fc68be1e46a7ed0d4293c6ebab8d7546a7b6e95d495f7d315ac1d8df59ee112cc008176289b1515bf1c281db7c40ee23398cc2c247d9b1af98e3db95f5dff46e42ada2530455',
+ '6e9ce34b4fbc78ea92d3d14592e1c0725bd053d70f4c599b89d4215a3f11851d6d67278970cbfb566fd40603411465c88ba890cd290ee099d0374fcdf1dd8012e017ff50352b',
+ '91e87e19a4a4af9b2068f842e624da9a21e57c40cc4d4df57541ebf140e144792ebdfbb49f450dbb1682b4ef3d048b8f291cf38ade4bb69116f9eb713e6a1aa0c2efa0158a59',
+ '1abf71698a7d52b41caa5c26558d46e8cf27a490d270168c23e4c0c4213efa7b0d844876aa438c61061c7a6e977f4d3f89b7b806572720eb99d308ae1d22cd8d38e293685e8c',
+ 'f8dff7f41b7e3ef6d558dcd83d344db5551d410eecb5a0bcc2cccb29ee3125c07dc8d2a25cddbe9b78b8e1542372c2caba073afe84ab7befde6250c595cba74f943c4cafbf14',
+ '9fb4d6fcd697d4522dc7e386ab41dd9f8a637906e0fe123b7facabc719643172a84bffb50ccda872f6edf0e306d91bd130c26b0664eae4046eff52f71ba78de99d5cfc35307a',
+ 'ce3a2bec5ca00b544e8d392ed309e9ee5d48d185eddd8b33902a3b9d291b711f721451633e27f133018b028b9149b3f32e39d20bc12d3468616c589e1b62479ef395be4326db',
+ 'b127e4819e172ca09868c28636dfa63b2eefd1ead22dd3f0db04bb3366aa37b53c52fc6956a46845a16a6698fe8c939e8d3e9f512b78f58339a69e2aa0a262fb11df313a92e7',
+ 'a04b6205d7e712aff28a8d520a79547e41e42800001970b383f8dc9998a7482aa387e3ece6669044fff68c8cb27d5165e9cfbb4ff97a6a77274067cf6bca0a64749a1bedeb42',
+ 'beeba7959995358a1c238dc2f457f3c0aa6f47372f5f3471b85fabf1cba590589a74b385915501002ba5fc99094f684c45db476804a808f14a75fc42132609f69fc5a2090dc8',
+ 'e7747f39b1c6c0157a9128c012391e5148200ed5006a193986040a6a22e48cbaed929b86e2e73915381462c4f0e74160aa4aa4d4bc0dae0485e5cbf8ffb4e93d940ae68833ec',
+ '2f95c1d1d94db8ce7bdafc8af1b7e48fefd96b7ae8f733f72f29caed5db42df6f2248a123f9c4a9c836b4f7d54df7a9f405e71a5b5b29fd91ea57c654fce0ec723aab07f63ef',
+ 'addfd600416f8511f3f07b03df2248b6bcec047003f49317546c26a4172f05d45f0c8d20136174f04fec550c08df6853ef3290af983d9c48dc86c6f87cd88000069571f9fd4c',
+ '058f604e53051a0f8550de16b7245fdad3da639a6cc3c84eeabcc5dde8027390da488cc7f30772eb461673a32b7a4b4be47feaa2800878c200239756b9e0e807f964d037ed39',
+ '986e0d3c3e7645e493d35962291d979ddf09e8a610d5a73d0ae7b397c2b1c35ec6d7fafa7294bc0f675abf4639b8655168814929922b179ae675a202dc4c305623f01865db53',
+ '7a41ca8776a3dde0f5c7d029f28a9bcd3c4daad2ccf9d604563f95501e256d6e0dbeafc304386185701d7c201fd258d8526464b013831a8bc8cf3292095316d5af4f97352d3b',
+ 'ee36e5784fcb43427be072aaa968ea52bf3b73f55d0b45fb1d996d4a1928725eae32399c805b26e3bea38465a8df27b54e6a4f209a18d041906b70d0d50a91bb6e6e1078cbdf',
+ '27e1dca4978d2a05d3f9cabc29cb18c76a210b4eee825d37d915ecf59d1061a0c0740f4be0f81e92f442e872d45da35efc68418e8c8b949b9430b6498f6fa8a32dc9394e561a',
+ 'b415314e151701a503b62a5c8b5dba5ac357235a533fe2f634b85f04b85f1426cbfef29d7803005eaf3046684593e9543cb9972e451f258383e977bb92d6a1a9c8744b61ba90',
+ 'e04e9731742a767445247fba9701ae17fc9acc451b8c4ff3af307c5fd3cece277c0d9b5d47aef5d9757acfd3337960b11f65cd1d095e025bf6dfe0d96bf19e08e89f696bb2a9',
+ 'bc3732e901768fc9b98303d599110be8236c5151780022796d1b22c6e0f43fbe4debe3709c126e0f3dede3e17776e157fd64d67ec3ad6f960f4a53ffd33a105d3ac955f48112',
+ 'd2229832e4000614fac6db5c0a235e49217fa4a9a831f9aae7f282eec79120dddce9963fa211ef0a07d21a782a5ed85d633ed8b8838d1f885d64aee185955f3e579c11193bd2',
+ '043899af301424ed13d00066c0c37a448591f27371a284b314d2e7ec866a94c1ab502b67b47a13b8e9a86183a653fc27a4e0fe607a1a5d6064dfca224219d9fbe4f16372843f',
+ 'b5fee466f106d7a526d468468a16981251815a022073a402c4d7c5f6244af9fb747b3befacd85a3339674faff2f1ce174d661b6dd37d1fc8d19bbb5351f65c9848fad0ff11ec',
+ 'fd013d615c6ca959030a520e148808a07e27d38a215634d53486ae8be43a856f3e5dc6eb4fd9874a8a6570276a9e7b25585af7e1ce39d325bd7d195f2c1bb95122118809c7fb',
+ '05915a68f16938d7c6c5d4326904e0f3b89acf4d7063e01a4e38581575bf0e4910872dc9385436a218b7440e4fe294ea95bb446aa22f5b0c4cc90acaef83329411dc25fd462a',
+ 'b05f0e3bbb12b9351c465ad5eff31e65e55956c5f4e4ca684d53509f8f199d1a3a035aab661c7b4eb5cecc678649cc4a6b29bf00de52ff492f1f93ddc1bd02f776d169146861',
+ '3714707839daf79122c782416351385e88a81d31c9f641d8dce538e90e63c95892a2ea9b1962ed0ba372f48e9474aa730ae2359d6e4e66e449ee33b859576807e58999614d2c',
+ 'c09e29071c405d5e820d345a46dbbf1e0f8202e92de3ed3e2d298e43aa4f846866e3b748990946d488c2c1ae5a6e99d32790d47d53d205481a497c936bf9ba29fa9c2821919f',
+ 'bce50cdfff843885d4f364d69f93bf58a2322c707b82e878eec96d11e5db97bbb54606a3a3ccc3bba716261070a6f759a70ed3cb785fd1354fe56648df11863669b70c803b7a',
+ '0cb35a02ddc8c7fb7c93aeab77b9318118b0fd449524209d879a1cd69d5439e192741f9c5c64a353a774e28681c58ced576783ba20bea51ed82ae50e30e6a147843130900dac',
+ 'cddf76f985d6797c9fe3830c210567c5094fb979343fd5a1804c239a2ebe9a0e8ac283b0cdbe802c42e2cc5da800c4c1d89da72ba7489ab80e2aef0488dfa69ebc8434b95c11',
+ '731bdc9fb219f3667c9a135ecf34c7f52cf638c39c554f1ef1691ae84e5a71ace915d9e91043a8ae6a7b6a6780b684f77b0417072f7e279d597cfdf02508c97bf4928c505be5',
+ '85806ff2a642f729d28ded0734aef4f6a3f0bb32771e77729b4391cae4b49bd0a15089fe74071e576099a44d22a0e0e3c5d1450f717f68628460b4eae3945f5893e39c5e8347',
+ 'f13794e5ea5e27507a7bad638f8eb8b86ca5ad73b5a17424c63c74ef494bbfea084189c6fff5dfb2b6a5967cce3a81f9d9cde7a86f6b33927e15ee74e10beb20344bc121e754',
+ 'e3d0c3abdef069e6e4fa35015797bd8a9d64bc9b75f20b028b12cca04a4fe80ff1bbbd88e9ef1003564d499fec88df4503671188eec5d7d089dd18b812c41db43a3746f77b97',
+ '51bbdf37124cee0cd5830e9d8f4b0ecfa44c8b1bb86a6433c18f6ee961ab694d74f93316e5833c44c5e83a039e5d1ed104f246e36e17f4c5445eff423982c883dba9707b68e6',
+ 'e95751c99e14bed0dd9ba102f48e5e440519c53208e03ab7133613dad99042db7239347f5a47f9a8bbcda428ef52f5d7408235e4f3246268864c8c4135d27f1dc302a2d57695',
+ '9dd10a4c713776700f7e7e0a710a014b923bf228234daf5e807c8eb3e26cb97fd6c93d6cee2a5d7ab63c2c46e91c5b8be5044fe95d2a76e54ee5dc323412f92f7db6ceb03ee5',
+ '36bbb59925c6432139c7cd1bbc2b1b05c4010e09645f797e230131b2ad3468e7c9f2369b8b4f790dcb14dffcd6a941b262383341c80fd90d6d46fc8a81a25c47edba482c8658',
+ 'ffa63ebba8239b6896bbec6af1c7b87b9c69257a0d146c0d5c4e8b8a99b43a18633f1f11b6c745ab05c5cbd8895dd96ad89cd87bb9fee30c373378ecf42274dcc02f3ef06ab9',
+ '30be326c2ffff6d031affdab0a27d5a8cbfc4ba9dec626ad522615f77307e56d9e23f73e53c9f2c78cdeb5b84d2390727db5b3b4f4dae677d5fa7b161eec81b27d743bd56609',
+ '19fb88775a517bfedeb2cde7c9455ca58d40d150b0a47ffbd0288e42e4725822c48d130eec98b13e7cbb044b846026f97f9f18531df9a9fe464a99c75bf9ff7ebf72e80796d6',
+ '815c2a911aaf0f8498706110a95e6f9c26c3ef52a3b13781448cb03fd2c887520df4a55144f8e206249b7517ce48afe52c11eab584f4bc0e4d5d706142edb6f0b67a99e82757b2d015d5',
+ '4809f31e93423cabf44cddcad23da7d7aee734d311fc7babc276a1bd3d35139861ead10369350d421d0af4944959cc006fee3f51b996f66031836a9134f1f7a0240a339e5e077d366c99',
+ '1ce3f5bce2b176bf89eb7015005ed1ff5177a4746cf8ed7226efd49381e906e02e6359e95081af1683031c381d744b63b4a41d00e059941e4142f009c42c171e23783addabcdb640420a',
+ 'c8fcf6fcfbf498b33d3ecf12588a596d9fecc79ed43384fa4976138446ef9861ab0c9a8cd6c407cbc72878e2823ab706b5017f949bdd82032019b01846bfb758c7b0c6c3fcf397bffd4e',
+ '8985c5dbc6725a4e1ca26f5667d6da4938a8d542cab69a6938023075ee99846f5d73bbb8f49bc74d4b8f384aa1ea55ad88406c5ddf4a666b01439e973c91f41685a81d92692c3d734755',
+ 'e243c480ff1de35ff7bbb71963e145b20dc43b31afc1d4f4fe4ffc46e733b53419f3b99cc38c60869f67c5b72f8a2484470c87e5cbcba2caba61fbb26b534e79178c2f71980af1b570d8',
+ '2293336d9fd48570e6515a4d7c4985daf0e1230d6b6bd06589e71b8567ca3723fefff320af2cebf81e36005d4407071fc08fbe4f6e0804a43b7f491d389043e8ed71e283ef328721b542',
+ 'd30c4a44e6429bb5a319252763da22b8593b7884c4ca9124698f677441edde996fca574374f08230a6b273f2dfd2f9f172a22bb3636a435bd70ab070c9e066e0ffec79453c32ea66b860',
+ 'cff586fb91a1e9d43c36a76a4dceb9e123df15670324d1c75fdb8c3b58310a8281fb1e33e6a6cd514d71b01fbbd99a363a557bd4da448477f6248cabb804b320df3c45ffc05be17e8b61',
+ 'ece40441a168c83e0e356e687788081f07f4b299726c5f8fd89fd836ed84017157355e455700d78dacbbb8efb459fc0ed5bbcb011bc8410522c0716e37cdaae4badcf9cbc6aaee031522',
+ 'a3a9c55995ea04d6ac3a93ee579f6e7c966ab5edaf1801472377f86ae00a1f97b8adf02e127c2dbcdff27334d04e127dc63b1c2d8bafbc95bf14c9fd15a69b30bf1c1e3c268a2473df86',
+ 'ccf7c4e2a8e7a27c7bc54422214c880e7c2582d0680b1395f02dbda8c2d3b539e0453a5e99e92657b8abc316fba1dfffc6ef23ec19e9a074c078ab6dc9bfebaf3bfeb01b05b686dc350e',
+ '8a81d2ad65585e1e1383783faa17f460c39560ab730f95657d8c8c71c5ae731608920002cbf8068e91a446435104879d2712e9104a7c76493e02fab64b2014482dee8e780d44ea88b021',
+ '8281addf9835f1308be680dfae2dde6c52a58b698c9ee3d3391643a240e56d9f17372e76893f3e0cb62a67125b52e9db53b51e6a5ea55ad022c115b56f234c34c7db24ec1e9cd153deb6',
+ '183b4cda5c0282dab62aa4e48a19d3a5a00aab5524046e45f1085eb70f8f6af379340d9724ad742f3effdf05b3f2493bf6c34b16fe1a3e9d8f3ba063ba80b8a1a7077d8792a8b5d4142a',
+ 'fee603258582e3a3e8feb886599d4ac405a1634c320e85ea8ab0dc6bb65f72012f82a2e951d2cf4ab2615661b1dac0db520a3d82499f4e1c5430c190ce7ee24b82faf0e2bd87cef9a780',
+ '832f87d596449aeca656e0e0b4ae92dcd16a66889020a9d2bbc48eee45ccc69b809150a990f993b82053aa425382ffdcfd5e1bb81457bc6f615c28fd7bfbc20df6c9db78d804ca084c77',
+ '92a0e01315efb0b347666581560b44bc582ab63e8f8ea651ecf72bc3d3c9673d1e02afd0646eebd17b1e40e73b16ed62854673ce84bcf9c83317ee11203ff0e16f53ed7e21e3880c9760',
+ 'ce4c926c0922ba36269a20d60dcf08d43a1cea120f266af76f1c8acd883d1f68f09b8209f41f87822dceb39a544aa9b2569ce6a9ab30aefee421463484b8647b112fe48c6bbabcd55cc8',
+ '0649b582dbc59816a8042cac30cee6772a0ed8cbe8e07bd538ecab8a88f3f3dd4da70b35a5c09f1e3a4c523e6a46038ca66b4fbc184957fd8999c3e781ce07afb0eee49e8ca132c13c88',
+ '3d7094e005eaf0b1231cf60536f768e62f79dae86374660bde91a2e2fa94cff531e2536530406ace2cdd187179936293596abd20125ec7944362351b77a40cf7fb131523ed1f8a3696bf',
+ '74d72be7fc8f4fd566f863ef53bdb361137cb6d96b79efdd95941161897866997b16710ca552d3ea46fb6b9feb01c1a8ede2a5a53b6613b0598c5aeea9c47d63ea5eda0bfe430926f0e3',
+ '94869ff7b6164a24e89ab734f20322421bd31581548139c6b41f6d46243a15a05c02b41e0eaabe376012a759a0a440e6337c437dcfcb2c7aeb7d4bc0731918b6bfe9c68fc65c1bcf8fa8',
+ 'fbca586edfa57645037b6b3cd70fc341e4d4ec97af4b3dcbe18b36e9a6210aef531b5a824b6044e023439c16045779735184f43c8a5a2ca171a68ef06b4353092833491286eed76cb3fa',
+ '624248769dc2742a13e6b69b5e7212ca459b36bf86be5dd8d35273601a1c7a6309a12cc1d2e1e2822b42b46999cbe2ccef9273a311781bdefe1362fc0eec03d978eb92c7160f62e16d62',
+ '25cdcc9cb014784dbbdbb13f56ffaa63fa234c916f02367dec0303e8810fcb13b29fec7965190abdfe5c54e2c89909ba97663ba1ab0dd46bd82ad69ae475e7d431dc0c959bd5b522a4f2',
+ '3ac105a2bd07056d3e1c3ba547359dba94e8f79a6c32ddd532bee4ff37641257d2f192a5b326ac697403f5317145c34bda2de49c068390d00adb9bb48b17efdfd02d3a981b2ae4f43a77',
+ 'b8d9d674cb623d7a449411fef509558992b7f6e314c64f855c9ff2511946a681ebe9acdec9b94732a0f87bff3c5314716c73ea9261cf64bd58c43b5579e780b6fe9ae16c97dd28a40d67',
+ 'c39ce5407c0c03ddfebe82dcca408c52f26b64027e38edd00dd57079c0f89a825374c46e8d0a7834db8130f038f860d94f7cb773e4d6a20670a6134e0bb680748f882e3dfb31af82156a',
+ '318608b213046a3badd1655c51135c7e1492c6cebc0f2f36e0d77f8b4a987f08a07299fb4451e0be787b50e9c66556c69fcb930542ffddb1df828663fcd1e1b6198103fa8f8ec72dbef1',
+ '81574323c973540719d192833ddb51f13a52dcbae294aebea51be5f6aa47f3571f5d97facdcf0c7befbe809f44bdc73963d8514e4fd559774bb96087ef8eda6e7c64275d6d96c42b4e4e',
+ '44f71c2317cde52151c84260d1d3c04a28cc15ce5b3802b2e5357e2bfcaf10ab15d77dfaaad1a3883bada502939948234c559dcd95e7e158338fa12ac6fd21874ec2ffabed051416ef77',
+ '7edeeb6b63c3b9c836c4843ba46bfebd8ca9a6e205c7ed68a29f9710f50c65ac519ff17ad494d9b0a5041f587b5cd05e5f0de4e8b28566e5715fd5e9b8d6c9388580d921bf39bd8d775c',
+ '6e1b663e808a6986f29956b7b9708066696f9dfe0d7bcdb55696d8bef9b3b7c052c857884d2499fb86039d4eaf604079330ae3e818fa6f742ae49593560c5bcb545bd46d89b22e7f2b7e',
+ '208f91ccc87965d365cc325d3262b64277f6112b0b9371a4174cee721c2eb32638735ff2a5f8abbc82f24c71d6dc1b9cd2b473375666dac0b789e490c0495569f6a4864e20da0a97071e',
+ '915794a6c6540f1ce9958c2784cefcc13772198cabd4fa17c88de45c281d648dcbd59a100cf4d8c8d3106c960db7b91f59578dd0045bae203897b61570e6210a2f11a5aff2f3c25163db',
+ 'b1a95aa80bac5acb7a18332fc03067600610f376d99e77a272be96063ac5a0ca8d316e6cbe978e575cdca1b8b4a8008d9718a6fe5eb34af12aa0cbd97116d1ceb613b2e3975192b40d76',
+ '9e4ba7d72b76edee6a6f290ed318bedb0ad88c8411f9c449bd4ffb3a661b7e41e32ee662b552ec4283e57ee6c7c712bec6773ae2c578789b7afa5425c1b6adb3901a4db42da6c0559e96',
+ '8fa12bc017bfeb6c894020e420c5f76f9080e8733b998ef3a7d0b6563063b66afa3200a82a21f6ba56be003a3924dcbdac1f3610d29079c19213e4e14ae0e009c1ef919b5e60ab4a9819',
+ 'c18bc28d496beedb25ca42d1b217bc81891d4c2bbb35380e5bb9bf7e3dbbfd37fef70ef14407763447d6c06e915766430277f124165061236b9fcf057d785199b4381e49a2bcf3ef85d0',
+ 'dfd4faa6b9ebfff6eb33d4b536f3f18785fc33e82ddf3908735d0fd94f1f09666fa8f2667f876611a8d17d3256ceaa7e3ff3e224a11000a5cacb68e6de4dea84d53bea67c3e8be9a5cc9',
+ 'c96c04a3bb0816fc47e05913a715fbac9a3ad09db75b48e8013d9f27bbe8532d7e63dbea88bf968f575602f377552e35987872a4e3155ddb8e5cef30aedd08504d4b2123bd7f3af62bbf',
+ '9319838432ca096960e2196a06398134ea06e4e8799ba470c54f0512cabb9045f529b6c4e749b6e27626c11df4595bf5b47c04ffcbe218351485f49077405ad96a3f17bcb7b3e21e80ca',
+ '2914da23e86a603cda1eede153be2431c2947cdaeed6a1ea801d18e2c218220ca682e40f0a51c4c13a31163cb730f83437bb7a88ecc903160956f0d483137d1d145ce948866ad57f2eca',
+ '4b7ab133efe99e02fc89a28409ee187d579e774f4cba6fc223e13504e3511bef8d4f638b9aca55d4a43b8fbd64cf9d74dcc8c9e8d52034898c70264ea911a3fd70813fa73b083371289b',
+];
+
+const _macs = [
+ '05d1243e6465ed9620c9aec1c351a186',
+ 'c4061427764f979468ac422891dea9ca',
+ '1a0d427e79a7bdca7b11579339d0ff77',
+ 'f0d7c63677033ada0b502a4e95b20e43',
+ 'f6302c5fd7c8495e233b5d6129f361da',
+ 'fbecae19c2ce766d286c8ce70133b669',
+ 'cec1ed7aa0f1cbd6b7f667a079a88577',
+ 'ae73b3740a7a8a07223635faaef0ba71',
+ '4304f9864598f801c6aa1a692aabb8be',
+ 'edad94e7c30813be7c5ac58df418d8a8',
+ 'd78d7d266cf83add4355e7395b63adfd',
+ 'b7de3be2fae6ab41aa6386b8460223c6',
+ '380eaf65a9be83322508498748504b50',
+ 'b452d180b9cacc10cb012f48dd19e4cd',
+ '3f6417a99d7186bc36e6d0d61467360d',
+ '28f1b663213043c4d4fb312bd36d85fbe62c8008ce82aabc',
+ '7c2e5f1fdbda3c153536ec7136091eba0ba525b950bfc84f',
+ 'dd3334fabe8d0d51084c1e99a2a7fa8548c4cbbeec854fb4',
+ 'bddd77019ee3e2a16e65713089b23f0ef13e5f3ae6da5052',
+ '7794f8fe7ace77512eb98a5459aaebe28ae1e8c62832b5d2',
+ 'd0119cf3ad1dd9e917ab325c0b85927819ed606084542944',
+ '335ee9a4c96bfcfc38c76f7ace6c84adfd0a57a94efc23b2',
+ '5adf1391c94a60602cefe1bcc610060de90a4b7b8822db1b',
+ '312cd3f6c27e3ece5ed08f1020c815277f7e98bc3bcd0248',
+ 'a80b1a06ed13f5579a785f7965ab180908a07f152ea81e2e',
+ '68934f2d0de64c4e4eede0b1d867630da790c111371458d5',
+ 'de9a7e21d30725d253fc4d09a3fd21530d788795d672c057',
+ '61a0693f740c3b121238cc904e98c671563d506780960a00',
+ '014d599f9490a22b69824f8cce92f30c0542cea92b621a10',
+ '431d287099550ba9e523dd1308b0514cdc5faddb04ebc4c1',
+ '769f00d3e6a6cc1fb426a14a4f76c6462e6149726e0dee0ec0cf97a16605ac8b',
+ '6b142d4dfe217f1881aa0e6483b271dd5d43f70b85605953a0fef272ddde46ca',
+ '20153bf8ea2953c48251ebcc4161f8b6e28499e5c76c24014cff4a9e2f62d25c',
+ '7e8cba9dd9f06ebdd7f92e0f1a67c7f4df52693c212bdd84f67370b351533c6c',
+ 'cdeacfcebf46cc9d7e4d4175e5d8d267c23a64cde83e867e5001ecf26fbd30d2',
+ '0c19ab5d4ee7b64396eff7b2ca9efa5ca7369c1a1ed14952445d2fb5ece9473a',
+ 'a9c9d3993fe7ec4c2033ccf3b73b3407cd999d67455b43a75d6ba97efda3be63',
+ '468d8498d46afe74a0ffb541b847bac724faeabd48c41322bf534b284c4e9fe0',
+ '29973999c4ec891154b83ebe5b0201cf29205d68e7be2c1d59bbc81658d6668e',
+ '50db0ecb5b31524a6914264930abccae0da07f01a2bbb9408207156f8e8a340c',
+ 'a5772a3da86365b46638f1e97037fc0d8351d2e19ed929f85448ebf4e8379a8e',
+ '5f1b8de0e3b07da6f9ce1a494be5712e54ac16080bb4f6d5373620d86d5ea5c7',
+ '8e44d685fa79395b4761cab89688e37509e69ad007a2794c8c0b4152b67036ea',
+ '905d55da5d290d023f6940fcb904c50e70181c95000eb1e6a33aa01077692736',
+ '9045dd3fa6e8f2ef7c57b03932d244186caa1bc1d4b694c47e1f2901d9eba193',
+ '0b3b220ee7a4fdcb0d17a5c8b595b981',
+ 'a17d0e0f021184a3937222de81be627c',
+ 'da4571749322008e73dd436a13c5f11d',
+ '20cccc1ea0a8a89b3bc5fe3d5a9c2b24',
+ '5eeec5bd9583ce715d613d4c04a702f9',
+ '64d5ad7697a29529ca3ca4ff65e7d735',
+ 'c4fdcba979357f639cc6d89e7970943a',
+ 'ea411f749902bb0d2fa36e07e694da8c',
+ '7a699c1ce4e323fe1b9ff6dea2038aa8',
+ 'e2a380effe8de7d29948c5d9d7bb39a9',
+ '54e871ae687626fee5669ce20cc48041',
+ 'eb5b96d2f51d56464b95da4927ec5a64',
+ '020d5aca34d8c7066ef5d8c9b3429669',
+ '0d700ca9ffc418b29fc8e316acbc1abb',
+ '6696e3812da4807f05b84a29ad9143ae',
+ '4cd095ce641f217f8b5f355152eed00b1d9fd721a08dc5a0',
+ '646abbd426255d2e369b7ac9eb3c3af19c7185ecd28bd82c',
+ '3d731839c004ecef8ab60fafd811d0bbe6e306f7cc802bdd',
+ '0a4f17a280f9017f1435cb8a11738fda4f14e3f222f06b86',
+ '5007afb09312d144091f2b35618c26714bab8784d8be35b8',
+ '08c4699d15dcaef9e99556ece73793e006c86d25c8be3fc7',
+ '66a57a169d8d0ba263dd954b342919f4622592eed20c1981',
+ '7959e5367720f3af55ae91843397134032ee73de6a8db8ac',
+ 'd39eefe024ce0b545d77ce327f0731c5581095ca734c21fb',
+ '3accf0eec5b26ea6c936323b42636e5899f4bfe7e7cbdf3a',
+ '55adbc7d757e6904448ebdbae5a8773a1781f952f5bdeec0',
+ '22950977bf0f3fb8f4fc53ad2ea2c91d936aa98d06ce067e',
+ '646031963fc8bf827a30924763dca11b589358e7029daf1b',
+ '00aafb9109999ccf61f6689b7405ad2fa54129c3bc4e67b8',
+ '1c8b29577349cf99f80ca11477f401f61e0b1a4d6974fc61',
+ '737301dea93db6bcbadd7bf796693961317ca680b380416f12f466f06526b36b',
+ '7786c155d10c741b63ec650b7b1aa3bfd71ac71881ad06ae98fb082f17e0caa0',
+ 'c02c6022ee0de099e3027850be95a29ce800118ed3a97757dd8ab9e60f69a005',
+ '13e0834e4dd72a2ef7872249bf895da4432329c6e8ade8665d702ba33bb677b0',
+ 'cd251e66c421bad1b37cfebfa3c04ef30b8be4e5526b10fc48fd5bc5d6f04bb4',
+ '9d283d8e8e473a16162d186e96355b1885370e83954dbd08622dbe64f0aac695',
+ '6ab8f69868b4c87fdec9a031045b34b66660212f687a83d561bc4f9caad59fff',
+ '4746e6f151caf29b3534b2f493f7cc1308fa119116d251481572a1b53a8a1b3a',
+ '2c723282159ceabc5b367b95cd807f249f1dff7f9ebf5ba179a43081454e1b05',
+ '22de07c3055a8935b52bb2c85a9a6b7ffd4038b5db4069c07e9e86ee1b171d25',
+ 'dd1a8105ab753d83d90ab39adbc748940fefda05bedea7eeebdbdf54b02d9ae1',
+ '441c7fdaa40e50bf1eba073509769b1c0942f3a16e1e183435819d3b5f8538cd',
+ '15c62ce7a3bfd5b3b3856d6f47cb19bb7030dc469e35a27807511f81ea83091c',
+ 'd5596bcc39af2782df1cd9fc8c37a8f96789275422f511280971d8429a8cb661',
+ '223dfaf583140a769c805c33f1f30bfb2f0926b088f55439dfeb4f5a9ceeedf1',
+ 'b5b0c43028e81628dce82517fa36aa29',
+ 'b84003c417a472fd2935341962744330',
+ 'e1c3c6d90820511c8d685c73bb757ee2',
+ '5f840796e0d35c807b3d715727432e68',
+ '5a33b8f7cdba999ed61fab3869b8f1e9',
+ 'aedb7ea80734d1a65723da4f3ba18f86',
+ '9f19ab5e517e884cc1b1d3124ec9ca50',
+ '03243d10c48609e8f4182638c23516a2',
+ '03364863690c439b306a2967daa2418c',
+ 'd360c381d230d21cf828782ae5e389f1',
+ '3df86c710d782309023d65fccdb91db4',
+ '83467cdf51f59916b492c5aba554c606',
+ '0d88a7f3a8369888b4c3223499412256',
+ '84ac389ad6e42798a97784941bb76fa4',
+ 'fc38c3bddbc320bf7373834f3c83ac67',
+ '2c2bc8c87017f204c958abd9aab2beb6ac67781d8d9d804c',
+ 'd722b57c48128b37ba38770cbf4660697757bab95c00c484',
+ '3d6305ad9dcb3a50105b92f331009a3cb03ca7ec36882fcc',
+ '35fa859b3e4a793b2329652cc61f9f68816fed67fa402e1b',
+ 'aaed7dbe184423f0b4c9ff72dcf4557ec123b49682fc24c3',
+ '51ac4d2b5923a5df8ec48c14ec514a0629f8e385a9ea4985',
+ '20dc2be5c7f0b2fa8eaf026c152a09cadbdb82e52538b393',
+ 'da713e318a9e5b4b4f1dfe0a2af0837d70fde54442f264ff',
+ '5ebf7b7d25b0ff498322e4264bda56f7512e9d4ce3c9d51e',
+ '4f0a78dbbe767218eaeac0400656c4b4b23f908a9e7f4708',
+ 'e6e7baded94fd4042c2d3ccb586d8ca983e8033e4ccffc68',
+ 'd9eafa06a75b5b671be1b1f1e6296f17f71ff467417b7837',
+ 'e7928a55a3e4274394d81988a08196e07d5a5df047140690',
+ 'b4c5612cb1c1dc4333450daae500cdbcfe3ee1e3ef7a0d61',
+ '3d0a38dfe4a8801ab9f9dc1446c535d792393ea8d763db4d',
+ '2f8321f416b9bb249f113b13fc12d70e1668dc332839c10daa5717896cb70ddf',
+ '2d3a760595f3fb19293cc6d23651222a9f5a4f02284457a9c1ed4c43ac993ca5',
+ '6dc2b05619ad5458ee3de70b0c1649b3788e1a5312e8924b5486905506970881',
+ '837ecd647e03fe8df9a92c32dcbc87d0734851ffbc17376e03218cce9cbe974f',
+ '9cd24a0efa26c107738f5335526b57d8c93e54fef8c1babbbbb2d42f3a1d03c6',
+ '1cbd4f923d683ca38aca6cd0ad81151062fd642b155b2a950eb551ca8216b0ca',
+ '4f2501d2a88cb13046a6549f90e4ea924773408bb684025b5126a8fc21f48670',
+ '83b1403389173568588e5b6b8cf9da180408c79f91d054ac5cd99de0b728ff66',
+ '2f1a4c2bde7c8bdd7d8a9b6315b19ac654266120c652fc24ab19e00ac11c5461',
+ '579d35cef5b6f8468c8285829861e93587c8dee5791208406a7f4bfafb70abfd',
+ '810d7bda3421589a7dd60597447edf2b987f1e7283f3c65890248712c80969c1',
+ '055ee0ade716231bcaa0a7d18161004127a37e7aa12773433a376073474d3d58',
+ 'eb5aaa4ee702ff7b5324bc72c98fe87df6d9cc342b053ebce6cbf27fdea0eabf',
+ '26db47a48a10b9b0b697b793f5c0231aa35fe192c9d063d7b03a55e3c302850a',
+ '0e445d77789a6947da70848dc4da5dc9c125869bb6945b04304bde93829a75d9',
+ 'b3a189a17e8d9e986cd31bbe01b49fb3',
+ '7aea0e2d93e9a6a3004117ad4a4a72a3',
+ '04c8f6ebcbf13fdd2ab1e5c5c25bc7ec',
+ 'c7e82b7b2478c319194fed944fb7c772',
+ '589afd7086a58d77f046c59a419504a1',
+ '8cbd8f921c55d36e5b7db27f7891def1',
+ '1c649a21afe336c72c4593cb3d3c9462',
+ '9ca6f24c476e59b5b068c37b0383ff4b',
+ '48fc1d0123e5c7f686d74f5903323f9b',
+ '41fe6d923bfb13fcec839d3c272383a6',
+ 'b6aa4e0beccfdd37588699435e2d40de',
+ '98323e25ea0635d6abe384e8960f373c',
+ '591d11b2bd18f982bccb6b3a44f760a3',
+ '3d4a25554afa0abd26f72377c7180e19',
+ '2d2ac1291e545de46a42ce6c435518f8',
+ '08e3a1718c6d1cdef2c0c67660f7c1e8a45963e5ffed54a7',
+ 'b579eaf7706976152b1622c17fc47c5db3802aa3f46f6a3e',
+ '53f3436a128fd497c5cd1a534558d6a6bdb5f086efabc6fc',
+ '5a841e55fb2250c431fa397f1d0ec858b2c4a08e40dc897c',
+ 'dbeefbe2f550671d7fcd3d5bd66d19ce9faf5e6b29308ef8',
+ '95beb7fcb2b8d049adef7e0f33a7792c8d71e10b71ad3efa',
+ '2f8d11fe7f6c07bdd0d33dfcb3fc7dec51fe2048d1e8db44',
+ 'f51032cef423d7846270d8bb43f7d8426e392fd92b753a57',
+ 'a87d01c705415dea8cb9f0e2b6663b629f88a5ce793ea8a3',
+ '97f3b4e61b5885dc4c7f69f79a07d7a40c2d1d2e3936b91b',
+ '1fc68ed1bad0898d691970b530b54cef7c2733a7f1ffd276',
+ '10ab06d732cdf46a1711dfab98e136c4e6ed856ea0678efd',
+ 'aaf4fc8d00177a99d1c895d72b3a63e7ce15f1bc3946f338',
+ 'edfc7a2815d6779681590f3855e668f2c2d44e64c773e711',
+ 'ac38d22527983468cc48efbf64cbe1307022327207fb7f94',
+ '49ae1c4a7a570fde47f7517ab18898b1b991d03cfcf8c45bb3615b5f755da682',
+ '37f9f32918308210849dfebf8dd456804babd6845af07218f9d9be9df9743d55',
+ '5c258ba6241f65c2ee5356bb47332236baea227857e29506165861a4c7379c51',
+ '3c5a9ac2a0fa2f58825233ff676bedf93d8845a409a42a05a9ae5218cea14680',
+ 'f15a210fca2cefc4d92bf14ff572d021463bcc28f60d034e87222dc6076eaffe',
+ '6c63bed6c6082bfb085cf2426ef3d0dea97acd717a57ff0aa624d0b803f2ea14',
+ 'd08563dad7c32c02b305b87fad504918fd566c433e98a1367a3dbbadb26e9b64',
+ '5717fc337916d66b4e292e69d507b1c81663d8140536670f3e70e33b04c83ac3',
+ '3e0212e7982f43fc303d5e8457d2ab630aa257302ac489c74976cc5678823931',
+ 'd965907e6d0f926a7ea719464b1034a5879c865a00d4df0342b2d4f4bde0976c',
+ '9c22961d48d0651bd592fd369129e44822ee22d35c142dcb6b60a725bf177c85',
+ 'a6109ba372c4564f4ed8c875619ff5bb64d503225197ee9259dd50264eb1f4ea',
+ 'c580c8e0f6a1f36403322f7b0ae3d06dd2dfd16ebc6dddd205704e97dc2998e2',
+ 'a51f5988a8f0f3992f549ea7f8c370a06d5ae8d65880067997536385d632b206',
+ '974752b18d0dcbf29cc6104295e041259622cb7733cff63dbcf6808b15a5ad45',
+ '7966440df79b13e95c41346eb792f3ec',
+ 'd7baa0117d008af786c2bacb38b9d386',
+ '7588b290c3adf86198354e3eee4fc06f',
+ '99066156163139a8735711534c022937',
+ '0bfa572019e6d0f987f79b03ad67ad09',
+ 'ec8356beca9d87dce7d010de113b9fd5',
+ 'b7a1d83414cbbde7a7738c7e77cbfe3b',
+ '495f4ccb0530c7b1f03f3285faaae818',
+ '836034775fc41e033c56ecf21d1874aa',
+ '43385c80a077720fbb417848e4fa0138',
+ '9014a5bb17057eb39ab9fe59436e6c9f',
+ 'e4c09bb7f5ee13351baf8f4fe7386711',
+ 'a43a35e87ddb24ac3420c60c99090ba8',
+ 'd02c59ac11fc434a37eded33245701bb',
+ 'c6d5ed018b85568d03fce635a1332e1b',
+ 'f914c842b78c3b91fe6626272c04f6bfa39c586d4823ce0e',
+ 'c68f215b059881c9f97117b3c6d9d6deea2e0945e3e1972d',
+ '3d516a213a6b8c7e3434138238ca5e339fc21038fb7bfd21',
+ '94c47b509bd0c9b7aa95289a00a8a54efd425481307e9ebc',
+ '9bd70f0386405c04d1bfcaa538b4099abea343c5c4379482',
+ '59526ab645c2c0f464a48e411d111abe9aea19edced55383',
+ '8ce0b5dde0328c9de6d4acf84ff61b3f7d01f9e9e8e36b91',
+ '549afd1666a491b7ee9ccf6db2a33b2e3c2a21cfa69a1b17',
+ '0cbfe6e817d297b69d5bd7740bb0e5172d86cf870a9c4da4',
+ 'ed1fb08b8473af53d2fe4c607e5ab9639cdd11f728462294',
+ '4cb070e34b3a2ecb460670ffdd457f23c9a1174bccd35f25',
+ 'e5d5cd2e163ec1c883388f5f01980d3bbee914586ddd5b0e',
+ '64ae3ccfaa118acc556ac50e53cd9fdf7d7e3f4b785b2e20',
+ '0d2e37440adeb6836d7f47d9c516124ebbd64abd435d4c98',
+ '95b0a9f0ed9fc80581407664300488f5223720148618b1b9',
+ '514bd18495f6de0e237054b8e3ba1a74c3fada4279ad6b8550f3a14712c528df',
+ 'ca0053d51f6cf6f9998ff1e0db00b90e82c7b18cb5377acc8ebe9afe20da1c3d',
+ '5131ce486de164491b4bbc84e7e461a874a2cfdd769355584a063e306960acac',
+ '665344e5618e0c1fb8758d049409a484fa69b89b009746067ea036bfa0ee8a37',
+ '42680195f431e71b592899686af630e15996dc718cc29030163d677688a33021',
+ '2ca1bb808448eb29085286594de21e254fb3416f9ab01e99ea33ca83c1d14dc3',
+ '988d4a6fa87f8138d754c5de9d176c45eaccf8eb8ca1799d87c8f04a966b6f4c',
+ 'ee6492a669e22bcf19bbdfc45495cd0efa9c2f2ef5d42831e3f13a545cbcd6a1',
+ '9611e838fb1d816a0ff9cd269217d93258c34df9e26b74476fe4da0f7dee2335',
+ '0bb4127d89d9073ea425c303adc3f9db39e40adac23ea61fba8b6e251d79390f',
+ '109ebb4cb2ad746762b6652fc63b99019857ae89acfe9807648c3cfa151fed42',
+ 'b53db6bf0c8317586ae6c1a1e2857f241bf55dddd1b423578c6949d4bf014611',
+ '4a34bd4dfeef7fa1dc739280f16a3fe1281a51311c10a920ab43d406d4ae3370',
+ '4de7bab7fe9a0a9bf7b51a7cdf7d929f2b1c6ff4575fd527baba1efdf4254890',
+ '4f1ee7cb36c58803a8721d4ac8c4cf8cae5d8832392eed2a96dc59694252801b',
+];
diff --git a/pkgs/crypto/test/hmac_sha2_test.dart b/pkgs/crypto/test/hmac_sha2_test.dart
new file mode 100644
index 0000000..4f2abfa
--- /dev/null
+++ b/pkgs/crypto/test/hmac_sha2_test.dart
@@ -0,0 +1,129 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:crypto/crypto.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('rfc4231 vectors', () {
+ testCase(
+ name: 'Test Case 1',
+ key: '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b',
+ data: '4869205468657265',
+ hmacSha224: '896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22',
+ hmacSha256:
+ 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7',
+ hmacSha384:
+ 'afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6',
+ hmacSha512:
+ '87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854',
+ );
+ testCase(
+ name: 'Test Case 2',
+ key: '4a656665',
+ data: '7768617420646f2079612077616e7420666f72206e6f7468696e673f',
+ hmacSha224: 'a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44',
+ hmacSha256:
+ '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843',
+ hmacSha384:
+ 'af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649',
+ hmacSha512:
+ '164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737',
+ );
+ testCase(
+ name: 'Test Case 3',
+ key: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+ data:
+ 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
+ hmacSha224: '7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea',
+ hmacSha256:
+ '773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe',
+ hmacSha384:
+ '88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27',
+ hmacSha512:
+ 'fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb',
+ );
+ testCase(
+ name: 'Test Case 4',
+ key: '0102030405060708090a0b0c0d0e0f10111213141516171819',
+ data:
+ 'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd',
+ hmacSha224: '6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a',
+ hmacSha256:
+ '82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b',
+ hmacSha384:
+ '3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb',
+ hmacSha512:
+ 'b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd',
+ );
+ testCase(
+ name: 'Test Case 5',
+ key: '0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c',
+ data: '546573742057697468205472756e636174696f6e',
+ hmacSha224: '0e2aea68a90c8d37c988bcdb9fca6fa8',
+ hmacSha256: 'a3b6167473100ee06e0c796c2955552b',
+ hmacSha384: '3abf34c3503b2a23a46efc619baef897',
+ hmacSha512: '415fad6271580a531d4179bc891d87a6',
+ truncation: true,
+ );
+
+ testCase(
+ name: 'Test Case 6',
+ key:
+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+ data:
+ '54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374',
+ hmacSha224: '95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e',
+ hmacSha256:
+ '60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54',
+ hmacSha384:
+ '4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952',
+ hmacSha512:
+ '80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598',
+ );
+ testCase(
+ name: 'Test Case 7',
+ key:
+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+ data:
+ '5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c676f726974686d2e',
+ hmacSha224: '3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1',
+ hmacSha256:
+ '9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2',
+ hmacSha384:
+ '6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e',
+ hmacSha512:
+ 'e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58',
+ );
+ });
+}
+
+void testCase({
+ required String name,
+ required String key,
+ required String data,
+ required String hmacSha224,
+ required String hmacSha256,
+ required String hmacSha384,
+ required String hmacSha512,
+ bool truncation = false,
+}) {
+ test(name, () {
+ final keyBytes = bytesFromHexString(key);
+ final dataBytes = bytesFromHexString(data);
+
+ expect(Hmac(sha224, keyBytes).convert(dataBytes).toString(),
+ truncation ? startsWith(hmacSha224) : hmacSha224);
+ expect(Hmac(sha256, keyBytes).convert(dataBytes).toString(),
+ truncation ? startsWith(hmacSha256) : hmacSha256);
+ expect(Hmac(sha384, keyBytes).convert(dataBytes).toString(),
+ truncation ? startsWith(hmacSha384) : hmacSha384);
+ expect(Hmac(sha512, keyBytes).convert(dataBytes).toString(),
+ truncation ? startsWith(hmacSha512) : hmacSha512);
+ });
+}
diff --git a/pkgs/crypto/test/sha1_test.dart b/pkgs/crypto/test/sha1_test.dart
new file mode 100644
index 0000000..f5bb31d
--- /dev/null
+++ b/pkgs/crypto/test/sha1_test.dart
@@ -0,0 +1,334 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:convert/convert.dart';
+import 'package:crypto/crypto.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('with a chunked converter', () {
+ test('add may not be called after close', () {
+ var sink = sha1.startChunkedConversion(StreamController<Digest>().sink);
+ sink.close();
+ expect(() => sink.add([0]), throwsStateError);
+ });
+
+ test('close may be called multiple times', () {
+ var sink = sha1.startChunkedConversion(StreamController<Digest>().sink);
+ sink.close();
+ sink.close();
+ sink.close();
+ sink.close();
+ });
+
+ test('close closes the underlying sink', () {
+ var inner = ChunkedConversionSink<Digest>.withCallback(
+ expectAsync1((accumulated) {
+ expect(accumulated.length, equals(1));
+ expect(accumulated.first.toString(),
+ equals('da39a3ee5e6b4b0d3255bfef95601890afd80709'));
+ }));
+
+ var outer = sha1.startChunkedConversion(inner);
+ outer.close();
+ });
+ });
+
+ group('standard vector', () {
+ for (var i = 0; i < _inputs.length; i++) {
+ test(_digests[i], () {
+ expect(sha1.convert(bytesFromHexString(_inputs[i])).toString(),
+ equals(_digests[i]));
+ });
+ }
+ });
+
+ group('large file', () {
+ final chunk = List.filled(1024, 0);
+ test('produces correct hash', () async {
+ final sink = AccumulatorSink<Digest>();
+ final hash = sha1.startChunkedConversion(sink);
+ for (var i = 0; i < 512 * 1024; i++) {
+ hash.add(chunk);
+ }
+ hash.close();
+ expect(sink.events.single.toString(),
+ '5b088492c9f4778f409b7ae61477dec124c99033');
+ });
+ });
+}
+
+// Standard test vectors from:
+// http://csrc.nist.gov/groups/STM/cavp/documents/shs/shabytetestvectors.zip
+
+const List<String> _inputs = [
+ '',
+ '36',
+ '195a',
+ 'df4bd2',
+ '549e959e',
+ 'f7fb1be205',
+ 'c0e5abeaea63',
+ '63bfc1ed7f78ab',
+ '7e3d7b3eada98866',
+ '9e61e55d9ed37b1c20',
+ '9777cf90dd7c7e863506',
+ '4eb08c9e683c94bea00dfa',
+ '0938f2e2ebb64f8af8bbfc91',
+ '74c9996d14e87d3e6cbea7029d',
+ '51dca5c0f8e5d49596f32d3eb874',
+ '3a36ea49684820a2adc7fc4175ba78',
+ '3552694cdf663fd94b224747ac406aaf',
+ 'f216a1cbde2446b1edf41e93481d33e2ed',
+ 'a3cf714bf112647e727e8cfd46499acd35a6',
+ '148de640f3c11591a6f8c5c48632c5fb79d3b7',
+ '63a3cc83fd1ec1b6680e9974a0514e1a9ecebb6a',
+ '875a90909a8afc92fb7070047e9d081ec92f3d08b8',
+ '444b25f9c9259dc217772cc4478c44b6feff62353673',
+ '487351c8a5f440e4d03386483d5fe7bb669d41adcbfdb7',
+ '46b061ef132b87f6d3b0ee2462f67d910977da20aed13705',
+ '3842b6137bb9d27f3ca5bafe5bbb62858344fe4ba5c41589a5',
+ '44d91d3d465a4111462ba0c7ec223da6735f4f5200453cf132c3',
+ 'cce73f2eabcb52f785d5a6df63c0a105f34a91ca237fe534ee399d',
+ '664e6e7946839203037a65a12174b244de8cbc6ec3f578967a84f9ce',
+ '9597f714b2e45e3399a7f02aec44921bd78be0fefee0c5e9b499488f6e',
+ '75c5ad1f3cbd22e8a95fc3b089526788fb4ebceed3e7d4443da6e081a35e',
+ 'dd245bffe6a638806667768360a95d0574e1a0bd0d18329fdb915ca484ac0d',
+ '0321794b739418c24e7c2e565274791c4be749752ad234ed56cb0a6347430c6b',
+ '4c3dcf95c2f0b5258c651fcd1d51bd10425d6203067d0748d37d1340d9ddda7db3',
+ 'b8d12582d25b45290a6e1bb95da429befcfdbf5b4dd41cdf3311d6988fa17cec0723',
+ '6fda97527a662552be15efaeba32a3aea4ed449abb5c1ed8d9bfff544708a425d69b72',
+ '09fa2792acbb2417e8ed269041cc03c77006466e6e7ae002cf3f1af551e8ce0bb506d705',
+ '5efa2987da0baf0a54d8d728792bcfa707a15798dc66743754406914d1cfe3709b1374eaeb',
+ '2836de99c0f641cd55e89f5af76638947b8227377ef88bfba662e5682babc1ec96c6992bc9a0',
+ '42143a2b9e1d0b354df3264d08f7b602f54aad922a3d63006d097f683dc11b90178423bff2f7fe',
+ 'eb60c28ad8aeda807d69ebc87552024ad8aca68204f1bcd29dc5a81dd228b591e2efb7c4df75ef03',
+ '7de4ba85ec54747cdc42b1f23546b7e490e31280f066e52fac117fd3b0792e4de62d5843ee98c72015',
+ 'e70653637bc5e388ccd8dc44e5eace36f7398f2bac993042b9bc2f4fb3b0ee7e23a96439dc01134b8c7d',
+ 'dd37bc9f0b3a4788f9b54966f252174c8ce487cbe59c53c22b81bf77621a7ce7616dcb5b1e2ee63c2c309b',
+ '5f485c637ae30b1e30497f0fb7ec364e13c906e2813daa34161b7ac4a4fd7a1bddd79601bbd22cef1f57cbc7',
+ 'f6c237fb3cfe95ec8414cc16d203b4874e644cc9a543465cad2dc563488a659e8a2e7c981e2a9f22e5e868ffe1',
+ 'da7ab3291553c659873c95913768953c6e526d3a26590898c0ade89ff56fbd110f1436af590b17fed49f8c4b2b1e',
+ '8cfa5fd56ee239ca47737591cba103e41a18acf8e8d257b0dbe8851134a81ff6b2e97104b39b76e19da256a17ce52d',
+ '57e89659d878f360af6de45a9a5e372ef40c384988e82640a3d5e4b76d2ef181780b9a099ac06ef0f8a7f3f764209720',
+ 'b91e64235dbd234eea2ae14a92a173ebe835347239cff8b02074416f55c6b60dc6ced06ae9f8d705505f0d617e4b29aef9',
+ 'e42a67362a581e8cf3d847502215755d7ad425ca030c4360b0f7ef513e6980265f61c9fa18dd9ce668f38dbc2a1ef8f83cd6',
+ '634db92c22010e1cbf1e1623923180406c515272209a8acc42de05cc2e96a1e94c1f9f6b93234b7f4c55de8b1961a3bf352259',
+ 'cc6ca3a8cb391cd8a5aff1faa7b3ffbdd21a5a3ce66cfaddbfe8b179e4c860be5ec66bd2c6de6a39a25622f9f2fcb3fc05af12b5',
+ '7c0e6a0d35f8ac854c7245ebc73693731bbbc3e6fab644466de27bb522fcb99307126ae718fe8f00742e6e5cb7a687c88447cbc961',
+ 'c5581d40b331e24003901bd6bf244aca9e9601b9d81252bb38048642731f1146b8a4c69f88e148b2c8f8c14f15e1d6da57b2daa9991e',
+ 'ec6b4a88713df27c0f2d02e738b69db43abda3921317259c864c1c386e9a5a3f533dc05f3beeb2bec2aac8e06db4c6cb3cddcf697e03d5',
+ '0321736beba578e90abc1a90aa56157d871618f6de0d764cc8c91e06c68ecd3b9de3824064503384db67beb7fe012232dacaef93a000fba7',
+ 'd0a249a97b5f1486721a50d4c4ab3f5d674a0e29925d5bf2678ef6d8d521e456bd84aa755328c83fc890837726a8e7877b570dba39579aabdd',
+ 'c32138531118f08c7dcc292428ad20b45ab27d9517a18445f38b8f0c2795bcdfe3ffe384e65ecbf74d2c9d0da88398575326074904c1709ba072',
+ 'b0f4cfb939ea785eabb7e7ca7c476cdd9b227f015d905368ba00ae96b9aaf720297491b3921267576b72c8f58d577617e844f9f0759b399c6b064c',
+ 'bd02e51b0cf2c2b8d204a026b41a66fbfc2ac37ee9411fc449c8d1194a0792a28ee731407dfc89b6dfc2b10faa27723a184afef8fd83def858a32d3f',
+ 'e33146b83e4bb671392218da9a77f8d9f5974147182fb95ba662cb66011989c16d9af104735d6f79841aa4d1df276615b50108df8a29dbc9de31f4260d',
+ '411c13c75073c1e2d4b1ecf13139ba9656cd35c14201f1c7c6f0eeb58d2dbfe35bfdeccc92c3961cfabb590bc1eb77eac15732fb0275798680e0c7292e50',
+ 'f2c76ef617fa2bfc8a4d6bcbb15fe88436fdc2165d3074629579079d4d5b86f5081ab177b4c3f530376c9c924cbd421a8daf8830d0940c4fb7589865830699',
+ '45927e32ddf801caf35e18e7b5078b7f5435278212ec6bb99df884f49b327c6486feae46ba187dc1cc9145121e1492e6b06e9007394dc33b7748f86ac3207cfe',
+ '7c9c67323a1df1adbfe5ceb415eaef0155ece2820f4d50c1ec22cba4928ac656c83fe585db6a78ce40bc42757aba7e5a3f582428d6ca68d0c3978336a6efb729613e8d9979016204bfd921322fdd5222183554447de5e6e9bbe6edf76d7b71e18dc2e8d6dc89b7398364f652fafc734329aafa3dcd45d4f31e388e4fafd7fc6495f37ca5cbab7f54d586463da4bfeaa3bae09f7b8e9239d832b4f0a733aa609cc1f8d4',
+ '6cb70d19c096200f9249d2dbc04299b0085eb068257560be3a307dbd741a3378ebfa03fcca610883b07f7fea563a866571822472dade8a0bec4b98202d47a344312976a7bcb3964427eacb5b0525db22066599b81be41e5adaf157d925fac04b06eb6e01deb753babf33be16162b214e8db017212fafa512cdc8c0d0a15c10f632e8f4f47792c64d3f026004d173df50cf0aa7976066a79a8d78deeeec951dab7cc90f68d16f786671feba0b7d269d92941c4f02f432aa5ce2aab6194dcc6fd3ae36c8433274ef6b1bd0d314636be47ba38d1948343a38bf9406523a0b2a8cd78ed6266ee3c9b5c60620b308cc6b3a73c6060d5268a7d82b6a33b93a6fd6fe1de55231d12c97',
+ '6487972d88d0dd390d8d09d134860f263f88df7a3412457adf510dcf164e6cf041679b3a19fcc542af6a236ab03d66b2e8a155d1061ab7859f75732775fff682f8f4d5e50d3ab3770f4f66cb138155b4715d245b8069948ea016a45b7ef0fdde93188c57eef4717f3425181de5b9a5d4e0a2963f2a67a340eb1ae994b98a48ab19b90ab74391c50426d28287ac4f1eb93f5af1a68c7dae40876b8afaaf35a19293c1952e957978abee40ec32f2aa880c956c7eb72f117b397cefcfb4e75ace3b081776e46b13521e93559d453e32ab74ebc0859b9a8dd4d1d39000ebe95f984d80a3f5004dc91a051dfbdfe9194f4f9a483e4e7955577fb0933464c63eaec771044d59abc3029a079519f8460a693b25b4ce207ae9d9447fc4c5446e6dad234e9afdec0c562798cd0297318399e838be385845c6dd79ede66e2ae80afec6738d4d9bf44c8d9eddff6c5cd2c94e340e0ddac40384b9a1408c9a4b98c37a6081d5220fba92f1d03144db',
+ 'bd74e7f607cd7d905e90175d67650a6dc2f8a4e2d4ab1249ca88812bda7984deccbbb6a1ba90a0e91434ddf5e6137ba85e39a598890a7f635d335242fce0e9e037303b6c51e54aec06614ad5ccce06d9599c80016530d7fbb1da6eb548084b2b05babd7d553642443efda726a1fd71a8bc087c44f285e2bccf661ead475a72673e4386fc4eea5197c4f13c0feb0a85bc8e67e28ab872684bbebdaa527f3c253debb2dc12c2693f8e9e2651b9345c0abed7a0fafa3e5d305386c95acb7a172e5413ef08e73b1bd4d0d6832e4c035bc8559f9b0cbd0caf037a30707641c0545356bee151a24068d70674ef1befe16f872aef4060faaad1a968c39c45dbd7595de8f472016b5ab812d77e545fca55000ee5ce773edaa129eac6473410c2499013b4be895f6c0f734becfe994306e776262d4528ed8577218e3cc5201f1d9e5f3f62230eb2caea014becfba60fcb1f3997aa5b3bb622b7205c714348ba155c30a79a2cea43b070cada807e630b4086b129051898e1d9e68d1d0ecc9429d20d6a1403e0035a442b37bf508eb87e8ea347a3e68427b6d48ed299ba65ecb37b38754f4547423eaea2aec403338db2dcfe61cff4a8d17c383656981e1838a23866b91d09698f39175d98af4175caed53',
+ 'a52638f0efb19bff5ec95fcde4ac9aabd95e14d2e5f84c551f43bc5376855e71519b6f877248739a20cd790b85baa00d5503da5cb056f02d4aacc760c91fe1fd6efb26def817e5a9c56616023bc9e2fe662765dae2c0b2edfcbe17db140da30c466de65c49c6f81496bbbd1acd81666455f23bb243dd987d7ea1362a20faac841f1a36692cfcb4c3dbf5f6bb058c36296b8be64e9b56adc5187cacb7b58c054f422a9e6d6a61229fdc3b494da98f5a33ed1bee14b2d2f6ad1177ffe99a6bb553f7c4a6d0cb9e498ee0b63f388235d86c26c9d96e50fa7d1eb3bcb9279940c47a8510d7fb175b3279318d5fe45823baba5dbe31c33c7649fe447061db78b33baa3637b854163fe34915e931b9f3040807d9217d7b3fed62370dbe806c006b21cd5061d24490f366e4d5f23e201a7ec83ae31b46fe2108d1af56cc9d42f9117eca1cb5ab344c1fc334b9cf0d7f9739043bc3d413b3aa6e9d5067c240c52b4c5b89e25ccd8a136a002008a9273f30dec3f2c1736c04a1c7ce0087c9f25d5ec5bff2ea7ec0b0ad7c278f0ca712c9ae150e472521d958d0bd6da9ff0939725924b2ed7b410a0ce2fe3f6b0bf25884d885ec223605e318fdf6803218a9a06ce5103c62ded035087a98519b4eb180d778d7656b3d4811aaf11a128317d1acb3ca3166395c51c90a3cf164071d0d132c54b3810a8211ec7774d2288447abe7afd030375a3bed4c7cf1b28097c02e98ea36bf49e74d89fbe74ec6cc1def5cd8c8beb5b8adc3cb48c56182ad337e3b9778e4a6c4',
+ '892af4c05368aa9242acedd87d0fc68de483ab59520aea621f264b65ea90f005952c8163903d86ee5bd6147d4691ac9b7c8260213f6e370b7539d384649e5143ba23711ad04bf7cc2f0d512054857933b0ea1d12f3c0fe888a4e96356653fde000f50d0f9afac5d4c73aebe92d54f5ff8aa12a54f5660584674edaa17917bb856f8b9d6776b2b7ad2a462b015b67e8a71190cf0ecdca15a5121fe8ef245255da10cd694decdb96006017599066251ad34d9f54690452f59395ab0848f06c9186eaa3b8e785dd2a747297bdbdd4f5532a47b7008c21686ff7f8d881d464cd383205f6d45dc8203bb267ac9eb12f415a5406be1c9fac73497941909dba08dd12856aac03d83e0d916147404694fe70f8fa929ef0cc2edb4cc07abaa2236405e62820af8e806d0af32a1b3afb8dcaeaf5c4f43dc4392e074075aa3ed93601ab7ec22fe5bd7cdf802bb5ea8206c41a1619593385e00e3461ed3fda048a1c6639a0fca038d7f51cd8ffa9bc00af62765e2b62575c8b74c8501ac711f3fdfc1b15157e7a8f2612aa7838af999c3d8f6629f58669ac0f93733c91b557f579ffa9a9a4efc5d1f0fc13ca9e6e8a3efa7273e03d6e705cb292bc8d18b0b4f1484d975b17f88ae87edadf34f88f96ce2c3424e9ccc17454bd992cac786031d0b00d6d953540d0bb18d5942010b9c6341cfc02ad6a287e7c78d249ff796ed578fa68b4bec5709f320515bcf5ac95215812f39494de4b94bc2a639eefe282a9d26d85f33d902fff358fc1de1b95caaf2255416207f2d1c1fc1c74b0e57d43b3c6538db27c5e26f9acfc0183fa9301787b2f0df46c6c630a24972e0947105afd3df2a779e2f6fc947f95ff32fa6de28549e67fd32c15a8791ce1b8307e646e8f1d94fcd1d7225ad997a2e07383ed14dd76c3c186b0b54915cc',
+ 'a5045d24d07578ca31987db3d2e25e12ea38bb1da7a8bd642a574261d4ba3a50c009504190f1ce6b6d8abac3498845cd67b567b21e9fc394da8dd01e63b83a5f62b886d8213df6d392ffacf793f8111a70d07856a999ff5ff6bcb6138933045393f9461209bfb8aba8e1997837988aa00c713830d1fe3a6e88cb3d6acd935ed55bb4d716d2e1de9bb817ca6dbdd278084380ed691d363c6897a2aa48b741118dc3d1820d030a2e4ac88987ffae0da2f91de5e02816a9cdf62c2948d7d0a3e522d2398f1f25a17261e31f185690b0d11ca388599642bfb5c04e485e3f9f22a13d91d24673bf1070870ec1c499ee25cd19dc529fdb2be1bb6d05e733a8ad270f850685ee3259bef1655357d4f14dd35e97d129fc1e5975a9a559ee10398018f5a33b3bd1837c13bca3b9c9908537224c3e88f7b68753e5451253453d1aa25e1c3e38da358fae779be848ff407e337a5eb70ba21640a197585afad402749b624cff034b637e7a5254dc09e12c03ca435daa6213646ecbf5a9255784a76ff18b4c8da677a377650cb02803589c3d82e512be9333e83c5965021c703b73322e40e69229453da2f90d77743f4ad753e6c8429ca8e9ead0d45129e64fe2afe6d9ebe0b3929c7828bdbe7167c3a1266e7b55b8eca81cb152c420e72cfc62a4b27bf3039aeb669d31398565aa9943d1b6cbf23b559cb686ebaf3a04967da197bf9bc017ef3c8af4e4f6cb1de5c91a20525d08927f8b9eb1c21f0748cbdc89d334c1bae4598bf0c56a7bf95fbf590c5a6bb90086137dbc7a019bef7b7421019f3a76493181e28058eb5075f4e05303c9286840dfb97bf828cdac5a643852f042f940d5c80f4822f48efea9a4f1bee6b3b2f13265188b3a0551d8b0ccc079400598aac66faac6bee37b0cfb369aa39d6130dc3ddfd9b86a57b2aa597bb49dd830403984effa623c6bdb02d57482090f1bcbb2c817a30770671ba7bd39bbc7a00b18777710a82684d5d6699e2452f82629abf93dd31f82347db25944ce7dfe80dd49eb07995c1a7e6993c8be0fb179c9d2f73c03dcf5309fe19f47',
+ '912e0dc25b52540f4d33d26fdcbaddb420f5570141bccb8c2c94b8a38ad32dedf20596f35d8fd6dedb9296828512dc9cb358df586f941a1729c79f6eace0ae725025863371d57b86210c49081ae6a85ff6e720c3a39b1fbe1179492f2d0d0f951357838a7f6e6a8e85689306837e6884536cc349c51703094c725eeef7a279dfa361350170a0cc7e71701e86a822459431ad6ff3bd51ed80427a87b1f1e713d6690b469f2ab4c9df4cea8f8f711a6716f874cdc8739106ac5b596c8203240604cb1f5b6d96f288387e9f912ac6adf5920f8785d0cf1f751400d6b46815a079f132631f719ca132116f57ca5e8f251791e0ae3e13ba42634097bb076c0fa4952307a137b5250aeef287dae233b4c8f79ad2b3a09a1a43f8b98ace0f94d9788124b09f4e411776e5642eef82b11ddfba354d5d556cd96a5b063fd871ea5c64667c97260a1b5c2b3feecc6052e1b2b18beab9730291ddffb5af20a0d8767eb06cb122fd134dda722319c9f3f9ca5c8890427fbe5212104a2d3d93f0ea3f28a3ba4dbbee12df7b92b96c8d71207401aaf1c40506eaf65893ec37028e4f4d438679d8c9bfafd725d52a6f80a16ee88a60d7f9b41275459f211a25d443b0a8b5a1d0d8b439913fc2819eaa0a4d8c2de0f26a67f4ac9907cc3dde8f71d7b559683ce8d7e324611e39df3ca6943b214be9a8d1982e9afe45c72f60fe41120567429fe95cc048c67d72372dea8434d64b8fca3514c8a54d07783fc9faacbc49da2d12faf0b26c696355d199fe44005334b99fbd612c952e53c7b541091a9c28ba10dc431a215af1d8caf4a76b3a673f0e4f709209c03248339cd8efb5f37b4b10d246ed6275d807e5b9e97fb8d03142e23885db94ee4444aedff1fc859f2159e35d98205017af53900af94a6d6d2505b75e26c1881d92c9cc78488f018656fb3c981a036d6da77ce3a5693013780d3095a89b6c6fb4e580964f25d1b210e2d9226b13bf40e0872be6728458315baf6b84fe2b03d01d0511134cd0ea1fa68c9a9dbecd7b51d91907a05a91eb4f7dd35c8d4820ae34bfba234c589001d1ae1de7b5798e6029be23b91943d710f54643aeb76ec0972202cc5e4759af3e4e925e6773859f964ff86ee859179ff0ac1ec6070b5954e3224e026c0e3973ca20b814c3dec848444bf0c23d69bc31b2fb6d23108fef23bdbc0b25f2a9de25cdce',
+ '2298096d8a02225d4a5a91e95b43bee70f5a23f95269b1602fde6f11967b650b5c4eb8e783e416b1bcba54f62af4561e695130fccf5f8aa4f1eb497d69bc6c97d781333e260787cf11af96cae520be298839acf0ba49c5069b83c4436daca5ca9c17c399fbd33d5e51239d8c142ebcaf74f8e0fd9c91282d348d2a8c2ab3da4db2faae208bb1ff0784fdb3654088195836781449fb9e7cc2c4f0c17f273ad1c721103cfd5d079672b3251e7df0959cced59f90ff62d8886c5496d245eca753e1f243b755fa3ecb46e68226fbacbd0fb659579b4556a716d4ea66a405016428432c796553e8bf642b23fe1508fc6838bbcb877e436173eca1914881e8efd71894d79c901cb1f129cb748031cb69fee183321782230aa4d37c4e24af163d6aeb7cfc937edbdc3be4cbe0f1c46d7ae7d0b696eeec0ad9a2930d2be277b6738468a5a14677b6f2075bd66f371415b88ccefdfff6072257d6f4fb2f6b21f0198c59b4d19dc5d57abc57922a3b6aeca953a20076161a930ba6beef62a5f5eeb8ec84549180af61fcc1a0a718e50d1ad7a5166602366c857e7bb890cd793bd5d70bb12bebd77c820180febe421e47c6caebf0d7ac3e461f36beac8777cf3ad0ff51aae1e68a755f1060397faecc5e18088bf9fd7b17f089bdd5607b69903b04b726361f8a81e221b1c9189166d89b391beff97d77a7b2ec9b2a9c15a9a2869c87f21c8de0a50bef6c23659d722b46518b7db802a8d7d47056232afd41ef63bef71d25d2efdc37f2cad7e64ad8aca787de9ffd3217909d3c782ad1da385e1a93900f1996c00faf52524b6441a24205049ebc91b5cbb8577989a6585497d6f242d931c0835927bc368de8a629d8d7aaf0523b3d34cc38484e0fff8814654134f35be9e13fc40aa4c6011676ab8052dc728386c75723f9b8e4949c29c2aa8629d09ca0467209a2af2c383e9a6fa49ae4b2b804f7c5d7e2f1629fe703066f8d16fe26bfb5c52ed5278dbac6db1c4b990ad9791d9727f0da3af1b947dd86bb3e46a881acf7df3d8d52140d18015a7e36950f4f396d2477cbdab9682480ed968100f433d1d46a3db17ae6bb9ad4d34459cf7bc0c04365739c1ae137e7b5e1083e8b0ac695130b3729e52e4cb61c2ca5eafe46561adf91ec354292abf6420a1a5d3013c25f7e6c32dddb1246d3a010a9d26b9799b00951ea7e9af34ebaef12d3c63737ad99db3536b5a6ba3358292559f75e9710e88b4d765f692da79b869e3c61e89d11aaf30e4c998d4f9aaf7f13bc421e6e432b2c2c97c0f9673e02cd595b178a6e75fa8e9d7a71d7f9043f6a83da9bf543bae2b397568990ca9c558ee83ace67',
+ 'fa15cc7f0de294d7341b1fd79326c8be78e67822343c19922ace4e7925076145ef5f7dc91fdc1de032d8c454dd06effea2b0472ea2421c4db20c0fc0b0440e10184a8648d230d39f4e7afc57d3229de514e0245205a840e1ec7397f2bb42b8269d6050c4cfe8a05cb1882eaa1d84bbbcf7fe765705746f98018a4ed7ed0a45d0a7294305bd0c6b5e828ac413623432cb7292a5064bb090b819d99d36efa39f565e2cc7d245a21ceeea09255b4a38e85aae2519257f638b8a5be9ead96815ac00e9f145f50fb49a54118cb94a7f9ac7b1d33e397c49964856f0419e860169561670002334c249cfd81e9be8a7a662b61808666fd54f50ae64006a220662a683df1de2cb58066aa2c23abe1a3c6a969cd6752423f63c99a7fbb2eadd2132d41da4161ea329851efb598c7eb7cf704063344300bba8b6791b642e4b369e1afc0bad833c156ee46dc2e63d6227296367f27a9a82a0b365f9f0e89d149747c12435428dc488f1ce5fdfb174f3d212e91431f0a1333adff3200fcd27ce67e2d05783ab5c3f6478e9fd3b025ab72151aa4e08dd819af1f405f7605bf3000d38ee9add2f173510ccdd4ebc2117387ab0501d5f8b61402eb94684cbdc2a32f311c4f72b18e62cf6b5535a4b55d2fe46f580891e406aab57f75bd13996f3ed8035f97555acf2ae7dfaf32ad1e8b38feee9e49b2d45c465d676efe690d277b71c6b361c433463420d6564c53420e375d854245a74e296f611fea8c9bad8dd1b2f7c23f5def761710ebc4f335e468a386efee8cfdc5e08e472572e849df04e9e213167070c3f13c1e8c85b7d35a1cf5e17aed7004b0344b95f482a1f2362f2ca5b50ab5bb652a1bc045131aaa37bdb713a2e99f7aa176ffc429b44a03375f02643a196f7c57934eac81f78c28f1ad6f94144d7bce2e3b43682162311b473713a42eed1e51ffcf4d29df9d9cee0c7e77c93b93955d9af39ee8782707990a29c8fc1fd032dae2308fceca8fcd580ca3684985466cc79c326acb9a6d2e1ae4b9aac2697d5d5583698f01bf588df566bec98b8df0729a966a4f9804cf250f6b59219da84efe7077cce3794a526f54af231415b20c37250e1db5b443a77ce502aad5f468cf86aa23ed058bd837d1d44a62c05e9e143b1587cf25c6d390a64a4f01305d177996711c4c6db005636612cd1066fcae82eeda87f118463115318da50eb93e20c79e53c56d949c4e5f8c9eab9e60466fd2d2f2832625a8e8af9f4da925d92e31441ec0b3c302870f96c5c67a6f54e26eae87ec0dd0a66576ca5008cfe93893b58988566bdf5036e5a392289e25bd4707606e258c73430247efe43d9dcb200529d27b635234d5f25d0082339b43f1ead683063d83906415e89adc5a773e57f90ae958960b462c6fd2381686063c9b546890d0a287ba8206e55598ee00c528f5d528b06cfb95cbf5e1a4bf8e4382320a1a146de31d54355baaaaba76aef21b72150b134',
+ 'b718c968e8ffe4ea282fc33f96da233b8a8ab6ddd55781244a5d82237d6d9758ca039b3a9978d211e179870aebb8f38b59e161c466d090876f015959b34891c957c23100ad0bb49ab5b1c1b4e4e90a46258174b41e16789fb487c901d1a93779643dd3e3aa1f542cadc0b9640ad53015f65137d48391011520d71b445ffa4f11fc5cc90b1a1b7870cf8cb743e3e52da0d539f14d1faaf291bbda9749e6a2a23824075a9f8469e90d25fe0379f97fc88ec921ec467ac715ba8e768439ee09f897e626cfc771706facb7fee42dd40dca88dbf16ee81a523039a0942c3bfd9719d549a170ad6898d1f58b75a488faf5fc351291c05a89b10cb5fa1dd5789db4cc9b55608576f149d98fab4989b1f5a1233e76ea2ac54f4e71d7a2f7c81755c8da91134b564d94eb4d231f64dcd04d770a4a0fe2f351f28f2747a20c4d41ad3b0c5e8a4b2b58dae6f658edace40f88e17802e6626525fcdef5ac0242ab1e2e7528abc3464bbf4aa39cd71f0beb943045340d0253c66af5a2a4afc8832cd55fdff61fc425ffab6d880748bd683787cc0d07156b9b5f476342fcf7feb6168fc9df406397d18f44c9fefe51cdaa1111e5a0b9bf2a2478e5d028c52dabc3b273f2decc1e443143b1e86e4b9d59bbc15a026612b546d4596cc3bbc7f8d89148aa644563f9d12c621b523eb4d268828f89abc7da9fc7954903c563ca018c0a205ba77acd9c48ac36a98dd8029903e7c3c6692bd824b64e92d25d8895efcf1581af41e7d2aeb098058423a2fd9931d2a43bc2fad5ed1ae77a021392f16ba99ab5cebcf23ad812d718d39c066c7bfa2b7b0d409c99a2fb474abb6feaa61d238202dfa005ccc17553b7bf7e6a18e666da90676b7aecea61584924faf67cac44b3b10a73875111e1f32a705338ca837ec82b6fcafa966d5501c1663b1f3bc115160979bfe092725f9fb80da2d748fa49db944de5855ed4de2af8a8bacdaa039c9354510b77548af53faabef4af5af2cffc122a44840dc705bb37130069921be313d8bde0b66201aebc48add028ca131914ef2e705d6bedd19dc6cf9459bbb0f27cdfe3c50483808ffcdaffbeaa5f062e097180f07a40ef4ab6ed03fe07ed6bcfb8afeb42c97eafa2e8a8df469de07317c5e1494c41547478eff4d8c7d9f0f484ad90fedf6e1c35ee68fa73f1691601da2e87b00d1c6f256431227576398bf21945cc44255925bb7b6517e34676c959812eaadeba7258aa1562c102938e880d9466aae49bf361e852c54858ce2dc02313ac93fadbaad8aa936b17a9a740adeefffa7106caa497657a72d5fa0ff4c506998f8b2df82eb7cee7356d9039b7c33d61e86ad438d591d9fb5206f093349eaa1ac1d89f9a65bdbd18a70adfd15a91a1c318dd736fec15edde4f2263e25614b89e29c27748b7b11f2ea838bff793e1c32c72110ef753ec492a50737a82c0efd82eaf93de8b8c5d9e32223d5834ca794ba4de50cb5670de94e73c3f5efddcf7b1d03b91fbea4c87e02bfc62d10f6522e03444e0d216adb2761dfdcf36db11f4ec8eb506f7ed5ff88d211eef5211cda42ae28c0a4cbe713299d57a6b2ba2c6ad30700538f91c2e784e1c702c05c06ac7d3b89e1661d72324a217',
+ '32245df514f6c273d252271a980929e50a7cb0e77b05c7d46092abc3049321327d170d4bde314166aea193ce99b032c8665c3ad129b58528ba87c58c6539cf47e3f53a6b890a295cc08e658eb547af9052cc544a6ce701833e3ed9a61632c5c54e080bde7e46235df060c6e354944746b51326d9ac61e3edd4fe10977d46aab4a596a92b24b0d6722661dd54de61a3f1797ad90651ecd26e641191e9043d271dd0e83cdae20feba24ad7d369bb746a9985499559c350760fd6bd852312dee307b646eb74222a09f6440bcfaa54954546c1c8815b6b5578d7124b14ce0ef2877a41f7de804bcad974fc45faa00f8edc01153ec693afc380cf000365716241ba7e58453e86c5b702265bcd7bd25526d6d169f58b89f86135fd892ca1947593251ce376330ef7b92d1447ea7bc88f24dcbfa533f9c6aff8406b930fefc0afb06f5bcbd3e4a14b980245a9e5220b235195d2b14138d13a50482107f5787b78604144f6a47ac6281b28c16a0697227b75aa1275676f320331f625ce246450386a43dd4d311c06f60c489070950395fd58c287daecc7727063f281cee5dac457971c30b8c1f3e81e3109bba5da8ded13c1863ac61a6718ebade33df17f02613daf7545209e27f406521448f01d5eb124799d32223777acdbd9725f1e3c05ae537af5226b0edfb21739104238a59d699749b177d78c21b7a8ad46f13d620b33ffbf45d1835a43abb9ada6ae67bb739ed6f76712cc618bc0b9f208fa353a3b79aa480c5a4eca7c6655757e9664a708d6484b690ae8fedd4f786f5f83f00cbe07bddbf3c3b6a5b26b515a3f0117b1839c550f5f6715aa40ec4ceef4935520bc659e41a216a2350c43172492f868210d756509f0323aaedc209d356e324cbd5c1cb742c05bf9c0b3750d9b1e823f3ecdebe002c5723e52d872d40e7668bd2cc6b36fa5f598a58fcf899d868ca78451ec852fc3862f0bde5c6b573fb43e90b623b22d34ebd78dea87082eaf836f1fa291ccb811da71889a92918f90cfbbada19ba25bb5471f9918037927dcace3f879e546e4b769419dcea06fe4cb70e8fd35550a60f1b479b1636c64f2d6af0af81e107d1b7bdca632c1ae8abfb63ecb66bc7a72a4b0d8ebbd11ea51f66533ed05d839f9c627dba92fbce56c861be26fd17c31628fb95b80a56ba4c99b50e09208f188404b810d517c076c9ca3c003d927bea36389d6e63d51b9c35349615f03eaaf26dc14521ba602ea6ca27c6d4a134ecaf7fcfacd212caa436e78685e5848915b3b558761acb0a7ad0d077bec5e2430e856b64a67b3549650cebf60107267e73cee310e786978549776520604e914b460e818e16c45bdfe2a0bb09a3f566ad39c68fa105dfa05f2f1d00b877c90ebc179d4aa27a47e70cb174cd37cb3ac583cc1d137f5d9065f670342ba651dfdb2417d43f485d70774e360b9b16f331b3a0cf4507124b4358f9d15f5e808afd8711bb25c7f61cc87d1304d7bd1dc894b172a7d0d2f07b6319c7a6f111cd8fac82e376148d2244ca7909925babb297be5f77ea431f905a79f8ee859bddf3dc576f37dd12e75371f0fb805329df8c0d291e3f0b1e457864e2a6ece1a21b89fda8ac7d54c37f1000d66515eba4d0f0755f6e168eb4dd2f274784313fb662f66ffabb327188bcde9de54648b06f28868cebdfcce9c95f1b2e13115a144b4ccfafd81bd5b7e5191595983f7745eb3ec49038d390a0ae33d2c5dfeec5f3d3218c39bb5f059c6b2c6b84798150109b8c2',
+ '9f07e6b7ea8b6d2bb301d6ce7019e0f27ad55abbb799e6d47681fe609af63434fb84be4309e63159b3638d0d875e7af11a28d10baa185e8902dee5b09e14621610169511a214be6f3d65a667891eded056e44b913bfee3597caeb19031c21f8da5667409fd3c9cd31aaf28c6c08495f9f7b1d135b173fbacae9b6ae79d28f201841b6213618751ef12e81b1172b526d2c5396adf569e30ea5e4b199f287063da73de6817181d672aecb88730e8dc19c587211e7770a8097b5566c69f1bbffa803b578dfd682566eb72c9750a6a1ff7380714f5e548b80ec75b9577cfbe40405ba42dd9ad9ac7d49c6ac0ec893fa647950bb8f81126f7c837388036175818bcd37509540ff52d3ba49d48f594b19a91435cb52ee4518dbe31b3ce0a5f3372f7517892070cc37c226bd307971306235eaac2b4a04413a1781e9527fc8f9574773b7371f98a4adf1259d3a5daef87683432045d541ab25b7f67a635128fc746c6fb2f4d3272d47c92d667cbc60e7c929e43ec57544f77e45a72ae9d564711116cf774cfbbada77b2a4a552164592dc82145404ba8c9aa6491a9750ad0a0bafdef99099f9b220b05621d664ebbb8e13347a0c9e056729302ad73c22287800c31d948b864dab84a42c3b762fbd314e2fb97bc4fbf68317ae735375f8d83d14dd6b16b47c68159ab59d48011cfb553764799029a8fe5eda63bb15f12f4cc79c613006c7f6f97ec75721de13b73685fe63fd6d871f9d6906025aa52a4ff6b62bf114db228042458f1b72740a78ef41e7a0dd5a79da54201f0cda778dd5567727ff720a50a303187674e79061ec9627a79d61ed8e73a31289e5c3039849fc89350ee01adec99c4601e5f9c9c68ccb95a2dc53ad11461acedb2facdfd638496ac781e793298e7e8cb601316684d3e01a5dcffb0fcefc1b93873ce072c40addaa440ae0f9cd4c3a2b0739171d495c74345cfaf08c03f0363f12a01652ee4c19c65f0c74c5369d5fcf7a0023447071086214efbcb84cbceaf001fba706b1769e2d6d090b7bf1fc4fd892f8ee8296cc1d221a00b80b25ccba74d9a22ae4ca04db6df2832d849bd38ad4c685c14e18c822f2d0f08afb1baa152c1e361a93749141f683fd437570ddb1529939540d92ff9a62de11ae1e9adf9b842419ee995d86726595e9f5d53d5523c08f760f5781dd13e095f689cc2fd7be2b9fe02f4cf16edd19acdbbd1a3de482bd2dde6b9261db000a9d11b6ba471ced70f60b4544bcb4f2a14d44f1bb1f063e86d8d4f174bf93ff2f67f5ad3f7d39b9f2ab0dc9173bf3439adbb83c4e3d34b7dc34fc2944f77251ed6b04e5e23e98943f435a431aeb945054ec98053a34ea9f1bb6b67ba9b600a8c32ae1f93907c41ca543932be63832a96e0476e50582a254d3c286710957b9843f3bff4faa6536a3c3102aec0fce38af4497d7543692f669830d0ea1ea692754bff2cf51cce38ada275d941bde0a20d2873b3bbb5402515da7ea9176d366b49ac403d4c806ef1b2030706133f77885c3944316b2e44d4d91c0efc1784aed0bd6e9d391eaff0472067cfd14bcd295c1f2fa63eab34dd045b65c81012eb7487789afd6a962fba02a0d6b58211f05ee8fd128024a351737c43bd942f2f2bf25823384a16d98a36ead959a1608f2e7ef29febb9297d0c6e05382c5a9f96cb8f0d664e6b861247cac674f77bb4ea12f143adc13b965eed3767e2bb02a97053b26ce8e6480267efe06018b92bc64d211fa3ce9dedb3707d346aea717495e54cc53f5207c9d10009df7e6ea599dedee571d9aa86b7c7db43ced5f85798ab1c3d2f4c4bbad63d061d2fe91dc6ae44c5e54dafea84811cc7c86d72b37356333eae585c7c06578ca1b43869ce21503f2ba91ceb369f33f85b927a07c4cf97747227',
+ '25a43fd8bf241d67dab9e3c106cd27b71fd45a87b9254a53c108ead16210564526ab12ac5ef7923ac3d700075d473906a4ec1936e6eff81ce80c7470d0e67117429e5f51caa3bc347accd959d4a4e0d5ea05166ac3e85eff017bff4ec174a6ddc3a5af2fcbd1a03b46bff61d318c250c3745da8c19b683e4537c11d3fd62fc7fefea88ae2829483871d8e0bd3da90e93d4d7ec02b0016fb4273834674b577ce50f927536ab52bb1441411e9fc0a0a65209e1d43650722b55c5d7ef7274fb2df76ac8fb2f1af501b5ff1f382d821cf2311d8c1b8ec1b0beb17580ca5c41f7179e4ab2a4013eb92305f29db7cd4ac3fc195aff4874ca6430af7f5b4e8d77f342c0f578f714df4728eb64e022e9e13dcbf00663e34f35368a362a91026ee196b746b4437cd1c546184e9b1301e8103367a06adf7487c8cdd330c04a6f6546897d19cf3bbc9eb75ffb18e05cdd329d4dd90fce9c84844cd2138487ad1bdb6d749c1f8e873ee47e3ada307be33c2f5032282779c19aad88ec521ac8e390391ffd1d4239508a0ce27ebc7eb4d1a947f38b5cceb5773f6c46c499daca1356e524cf076917bd297cabd4aaead34ea9e24cff7eeec8e6fa284c02efacd766f3494490627c71f7a29ea1e3ab5c1f81c6682537946efb35534a634d5d783504f1cb47e936628f257dd98c54c7bce193874144daa936968dd238534dea262d14d8d5f4818c05b970439433ce06f262ac74d57191c22ee115005be4ab9e9e07bf2ece14016b4c37007b395ffa71e6e7f2168c7604e93e24f6641bde0f81c80b2c7d1e6f10dc1f50fcad2fd87f0f81bb90f4cf1ada254ea65787e108209c8c81844c2ccd57e6664e8c62de6607e9a925ac970424bc7f46b061ef132b87f6d3b0ee2462f67d910977da20aed13705476c6f85955d51fd0e8a3b261b0fec9783e1938c27b12be5f1140b7207e0b96d44d90048e88d42aa8e7c0fb45f7cf588865c9a0ce3c809eb046c4add515d352986b48768677c368bafce021f493a4dd0c2692c2cff01beaa2bc9bdebf40e523ff7452e6b78f1d6aa57c73ef13f109a7721507175e125f32a4f718c2358bbb9b97ed31bdb85b5ca0e6fb0ebb1abc885868a58906ef2fc4f7456ade00de52e129e02a8763ff591b9bfe0d130e8f428b504e4cab2a09a4d7b8f2ac5e132042e04f76d0a6820304a4bc69072361d82f9d3f919eefe9142e21e83b101b6191b8237cba64219059eab292a69db25d8bd02866e100c9dcb5081e159d5a9884b94f354229597b076a77bfbf3525424a20d0d7769b16cb6d62ef36c187c047e4ed5490305225355fbb381682932245b01dae04df5e456723842ff66c8905bc1ac484ceb7a35bc321d2a8619d5f394f37f8c45b1179111f97bf66f7872f8f678ec53c3b58cb61c6c637452b6ff7cec14a48b014bd9a0e67226b10a491d9c1dcc97607808408db92e56f9ade6adb574e5f73fdfc242f91d05c2da9782d16418e534d6318da0a2dc9e7c215f51e986738f0011a6bf5a85fedcd6dbdfca96382eea4b1db7ecb3ddcce460552fa0bad7333947671de92a2ad01cea1baaca7500a903659dd2cc8127d32987fbe77b2990fa0c55aa0ee9b9d1ddf08702bf2975a4cf5a09bd49d5136637957b7d4d893c991130b1433f6610636b7e34f8e8909f0ce914bfe8e6b07084414fc3412a73fddac0cce398780935c6c3ee7965eba7f9213e5c0f836f05a0673980e7b145e0743c4e097413837a32e42d69deb191158ec9185882f7ad7bacf9674f6f336879a8a5050eeb1b27600fa3f017ec44a28363edbd309fac68bb9b2012e5e43159e6a1fe2b04d0172b63d2ed561f2a87e6988276760dee0a686d75c68469ce12e1ce67300912ac71582c85a9a5a920e025fdf24a8b17f87a743843d20304b33ec8da0322e761059076632fbf26df57b82659bb534475446256c40c2cd8de1d1dd6b17cbb0d1866dc4db0d91621e75678b255e677e9505b2bd4bad8bc4b1e9317d3fbdae5c26054bda4b98a98dee9a586919979a0c1cfc33eb7c2af6aa3ed',
+ 'c29a1ab020e6434a50a271e5525a47a29b447a76162eeec569b51c3379b8b7b7300c8ff17e71b5bd9dc5e0089a780fe2114070d5380e81751e4075393518d9890f6d771865a07b745dd2d4dc0c54dd513a5f3def66060c7e0a683745212a251ee5259ad0dd5bdc9817301509b3d7f917a10aa86eaafed608b59629fe43d7e29e3d9cc0bfef8a215154476b3894e7aa5bcba77bf70cde283aa630140da5055a319c39b18da21693c69b7f9e11b96d3a4542a07c35938e4a3c65a0c0194f9dd3fd8c6634e3ffe577207440753b2952effe8d5b74cd47f684377a4cf5cb4788962d948b13690ce0188667f2b95fec7c12ae34422a6a30ff1e536e9e7bcb97acebe73d0e14c6d3efbd21fdfd32240bd5ea7cbfbb68b2578f5fb7c7fc19c047f319530d5800a25cfbad19bdc9a8338d44c191b730f44dc38f908c10d099525d446a9b8ed19ea7adea319530bee3337ab0dd15a40897e47ce8f9f9ce81c12ae38624e448e1b87bd0a691bddc45aacdda03872f0cab191f8b80e2278b775af0e0a39059c2f114c6cd1515ba4bc4c7a9b6240707798142a5f741933dce1a2b4c5d82f61f84677c31aa2105b405a5006e15fba5c672f2da1fc812536420d2fee4610b9e6116adb56371b1a8d2904e1ec40070a9948066a83407da6cc408079963f426cf4501298a052aac473d7629e9557e6b5a982945758dbb8324840e21c56f1ebbd3f3cc45c2bfdbfc2a1d3f9c28c697d402fbf8f709d1ecf4c4cdba884ab0e8b2f094ff6824388e8899997111a5c25393e7e472e42ca9a21593c695a4f0d059f36f5022f97a194a38dcd996ef26efbb90517c2174a6bde6cedb9826de7f747a67984ebe628a0918f43a06359e74f5d6b48aeb8c103eb4bf07e26af59cbe4651f4b2b75a0a1db1ffa4fd48d786577dade5d9583b1ebe3736a8f2658b4776eee98307b27f59fab907306bc6030f962f460c85ebb708eced529951b06f486f1447fddd68b4b7ebc83880cda941a1fbb2ab12d7ce8734907f1bc247752905715f75487d01818cb6869b7d6a1819a44cafe4dd1726330c7494990c1ed942e844777a4e2fa46e40249d370d8c3c148052cdf7578d1e44f65fd5d55d1c064158af055ef53a79043bfdb21419793db99dd5b5ee6780db415c18e9d69f8b24aebd7cb12927e8a9cae609703b8a7a4291639d0ed0f43a88b2a5687aa4b8b15a127e7122e4cb7f5c49a70f7cb346d773233b7181a6e8014b1f39172d4892d7d1f405570197c948b907e7d9818437d8f9f78b1ab6772a1e4c1180edacc91344b1dcb9f5f548098be98e0f2d25b744c5fc95bc61544ba2d9b410e2b29f2f254221520215a7017290146685d4105354e5a386370c042b3879aba2c72dad83af1749df487dbec9ee9e6015b396eb605181175163e36d1dd448585197277fcc980c520af3f6e3a965fef825ff3a5ee722e1807ea7b0382c5e8ce4a4ba68bd12ca69645c6b48bea7bdf9021ed38a10eeaf4d05956d390c5dbe8e772398b80e5d2c76a65c193bf6cedfd5a786964caa80e00dce1f1c4792badc96375799df1ab6a67b419263973423b3da0ee7b049d3a29d6804a41ba2714aa0eb4fc726a48a2420bf5d86b2231fb0215260c88949345ecea8cfaad4125215f3d7e5fca5d006b0828b20c16fa8607c1283c4b2891475bb5b1356bbae5fdd24bba0227c802b3561b427b5ca00ee9e8f6cb6632c18713dc22cf2c25e1150b97ee28f2dd11d7dc03f9fdb4229cfbd82f2193464be9e293479298c3a1c65af8f2b4eec2f82e68e4e5229eff06742ddb4acff42f0f0830403ea3b2be77b13420634e9ff4f18412688a33baae60bc315dbc5082b2f4b2fca521d4815f10581d2c7a0990fb61a980c1639be554d9db92f9f461b3548560a43c81839937f421826797748668b1052099f1c98384ca58cf1aa361faa64997d370ee5f7edb9b94008c5c2dd4af783d7e5cb55b39b0caca324a19dfed0aa9dee6dcc8c696bc8f2623e5388400422fa8f6844ebf5c6b43968902f839ff043e9c6aea9137655d475e491cad159dc33fde259afe648006dd542fcfaf1ea5156066ec24d8408f204cb30c9d3a5101952143882b74f93935f079931aaeec73d0c7a4c7161e6068b817bacae150d4d05a9c8f9a9022dbec5b157d6f8e8831efa8dcfca838d425768730dc2073910',
+ 'c3ec01c755385f27020d88ed2c578e73185c6d514c9192d13cb29ea4261167d33b2f3ff8ff897aadf2b42a4570ac2dbad66a6ae7e6b457f76d39bf1e22ddc287d2521d8dbae8ab2d35a62cbb979946d5586cc9967539370b139f84eb65151a82d17d20ef4efdfc8f110a16b968c5dface68b13c5c0c73bf6770b7573b76077ae80dad286836f74bbcf0871a6acd90327c7eecfde9007699ee1a61b1ee066e2f2268ebaba21e61b9ab6cac4ea2b7cb72e45bf8548ada1cbec9898fd55a7d062360cc460f4ef0cfa12107597edad5705a9a623bd6bdf3c69c8e608a37ed64600627ba24d9ab686180c23347316fa12f480334400afee80491b111e9603336fc35fb95008163eff7e71392ddecfd9548c9b344ad57ca11775cb62045d4a87f4b3130ef719ce4f1d32279888628014c5d6e2f15dc53ac1a6f5c221df80bd997cd867c4bf092cb1883e18886e878f710ed93eb1a3575116d8cfe696da88c233b03b4322cf5f962be9a92a5307d465b9d79e95be47132968520d21091afcc31b38e3906f50a37687e87c47407ad16ab3c72bd15e6f812a7fbfb75ac1ca64271abbd834f4695e338b2cbe5696f0060629878ad8da442abd23c5d37907104956f8e22319f9431735005e773f9e90fca2e1bfc3947aed95481b0c6b65231431b87d54cb25c50556e4ad25b0eaa0833aa4a516dceb85924a35303d86085dffa7b571b9d842a2d8a3a85c2a703fe3f048763b34dfc7455dd2ea2a002d49fcf930b59bbb5357d6e487e9d315bf26b100af7e6bc2d30f0074b4d1d1fc67104a295620c400434caa50890fdb8da58750daf626ff68c1abffff7850ecda3c458db8a05eb430b009664532823c3a2b4a09a8a5d5bdcdb0828a27a7d14541b4d10ece96d733f4a27552ea08aabec55857248f45f26f9aa87ee813c8bba2dad89a1591c1f309f4227ab66895f029d63596e9b95de7db76b28663ed6376cc4daf89ea2ca81bfdd737ffd9e661ba4414c8efa04e751bca0ad48341da006a8b414186d4c5d4b5d945eaed048df271d8281b4b907515f603fe185bcb0428ffa65f977a1c85cb2b63e8422a7f85d27eadb936900257c6e050f986f74993629de74eb84b0b9317e36465479f92f589478b701fa83e1c0f4177a3253f03af37ac14b6ace3e7183f47a367013485059d363af5e0798ceb798141a5fd1b407e2e94f6417c28f83bccbdea9479d29fdf98b281ef81ed34ec8b0876a716744a2bcfbd55952f04882545afff94b65f29a802222a0708eb7d49cd3fde50793067dca28ff95acd5eddfd3284ab10c0c46b8b61f0fbe47f5ab127c78c40492d39e0ba3073a9395f1d40ec1ca4b6b0a0eaadae3f83bd2fed2416b1025866393a75fdec00cf2fd9ec2bf91a8a77e81b5db837392343378f5b30f40c050c16c9a9ce059a9a0c51e47c6f50ae046509faff155055969833add0669563580e19a1812b42ee8793d8ff18d18dd012d6e0f48feb422a1fea773054ae40dc84c83768ca73fa0e4ecb8bd4c639f7aa3d3236b2132153df46a1cdc1eff03c9f10a037c78c907622771b340b908fd7610ce1d3db969fcc9c9325fb08aa14d2d58400e365d069fe538bed994c7ebb7520084b7f181d4df58b8fdfc9ac8c024aa6694f01eb9de6d9c811a8843e97a6190db7d80211b21315d1c13501569ea3ec3945f55a00fcef51ab91b3bb89e3360b50a3f1236d5cd97599b19069ade7ddffb7a35ab64df46cac21937806d66a54921254fcabd524875e09e859cb5a6f99cd4708e6dd798d453354a05e2fcd35e9f87b516363f010051649edf6ed043ec09c12fe01962dcf632e6c3fcdfc154bdb83b2228c10672b3be58248d197545d38b5400c13aa11c3ace590f92d3757b4147ce04fe17de17a1115dc825093f1d3eb60f8bb84e2cc70099fe955e7a63a797a2b2c60c871070770ed7e22dda885a8bfe56291bc0407df62a69fdb611267a1f7d7bfdeabb381d93eb491b0df9db5e49e8ba71823d86916a040d9130442853472c9c051f10cf6f865b33cb5be3b2b906f9befd821289b1fa9b6bf8638003d3bd24a583f02440e6dcb32a8b8e14a8fb41a5d61581fba440267507bbb661237bc01a0af324623723f5a78fc41b29288568619262083570dc5c155323af4411ac2e613ecb12571ca76f8cf61d898dabf809d1765b8b7c79e729e0f0f8c4c558e5269ed384507f5bd1b8f7dff06fbecdc39469e47a921d29e10e8c43738d4163d767274ba745478f43406cbfd52438e868a69f8f4792b40b6a886bdd5c6f64ccc35e9f29bc974c217cc45018445d9896579ef6b93b33cd88d4160',
+ '7810aed4d42c0606d0c1f76943d0c63f38d261cdaa6244b58c36997f0d53a37919815cc123fd5da0226fff19d91bc0c25c5be8d3d04d6c7d72c9127ddb96d6f082dd8c6982ddc8419de1fb2e816fde174bc314274a7c0b21059423f37f95128db90a87f379340d914aff32d0c434e9e60df02ef2a055e8484d7f130981ba1ef8c8f29288906bf53a30b2ee2529d3aad6abcc7d5b5b42cd9b53732ce96a6cc4d8b67bf85050e848e157e0755838b2e6902c3e4b8b02a980c11e56b4b8c212cad58c8fff724014ce31c872118f793a68bc982ddeaa1df4ca63b612f4a10f16f9985115f117e9574ecf8a5107f275d3f701f88380df348a7329248d34cadbdf19c90df51466d11a9266a563a2abb3e65a0753277652d0d343ba6fb1bc5badd5f210c917b18882c3609c229229dfbbd95a77b1010b2c783702bf9f64d37d0e604b138c630fa484bc811908c5e3b91616bff91af98695b51e77dfbd90c25785e8ee7d5ec178e35d6bbd865fe4195e4b03513497f72eb40ef06bc3d01cd2139ad5a1f44719326d973adb8b30d614f9e20ad7d12fe34db20b15a613e0f048d6d58f2d2050538669b990a5cf828519b064921b77eba529b634f6f076f6f46fcbbf7e5aab8057bcff4cd4e1fb5dd873ab5802e3cfd1250ae912f9119418108e17df0bef3ae00d1c59d77058b6c9b7681346c4f881ec4c3a732c87d016512cece5bd9cb678765dee9ce2cbd2a9cf0a4210b63f22344100007b0a09f6a4a630d25be29b750a4c3079f3f64d177c76b947c931db2890da2aa32935e54be5210488a1d56ef59b6a6c06849a5eeed6c7adc0673e00d43fbeb36ca634859782c99056e01e7ffed1d6fbdd775666205fc8ccf4116616ece6f581a31a8f4fa222a6bd8440463458549ac346f5b2cd76c083ff2df030853930887e90adcfad346ec17159e8d4f7cacdbeae892637fbb5a1002fb12c24b683c27e907a857b06140e21951e01502f1de448a3ed316c59a8a94642caecca0f9247dfa1abcd1bc10ba9ce121cb2434319404289bb3ed94d16815d22bd58abf92d65b39869ab3848e1e7d1ce9824349d868ab34a3c770740c6d14db5d59a4edd1ec4035dfd4759025e7231b3dd7eaba42c69a4cdb5027d9b81401ee559d73b212b0dd6d8afca065749eff6a832e930c0d3861cfa7107c3c40f76d998903afb2f1de835f1c65cc7af6c092994de8d4c59428823b9b7af6225381c86b8c3e8156dbbfc27908c2425728d66d1612a9186d74218c1f2ce21e124c4da2b2c3b0c1145cff2b49d474ba70875aef6f65e1e67a39bdeff8dff86c82b7a57d2dc3dcc781e1f71e40040f8d6daec8aa03bc25b76231581e4729206a0a1233c82b01450d15f7522c0a1bf54384ebaa2d8189d713bc077aa798acfc8f0ee8730449007c1a47297ad4f680b8757cda69da57539873ee28b00c5bbfdf540796edc1f645d477abe4db99a3e6eb8bbc07923103adcc608f2172cd0ee66b419aca0e71b145f09d9ab61eea7092e10ea8dfbde204fcf562056e4d5a20c502e01eee4fa408855304ca199f680b394b66e9ef473dd9c5a5e0e78baa444fb048b82a804bd97a987e35808bf762d22e8d2cf592c8d4f0ac4065bbf6141bda5caf22440c6d7275d3c4b87489919b440728e93286bd27f7f57788e92a05315f0e98b6e1ff3f1f88dbd9060c9f0841ff37910447278ea74e459d92f5b408254c6ab7fe8ad53b2132253d96bf48b6276254780699e1c7e36221354c6810a78830e56f61a52adc37f02444e312f3459bfbd22078b161f36ce1fcd0edc6cc3daaab033178d77cacb4417d81939e3b11104a353cd314149b943c5cf32f8833653cf938a0bc88273736b47595f0b79cb344cbf22f9e38761b09dfb60e6a3302a89fca1a3fa53dd6e63fb7c0d4b30574a67a0f9d6b32a5031c2e5a8c95264db662438c1c50bb7ee8342fc9d3e022fe7f6540739b9258c047f9822b653a0c3eab3cd8cdb3a667b1f7cb9779232af909097a389671174930b14d95c0c43f548c6d92cfed8483427d7206f72433178dcb9f4fc2e6b27cbc7ceb82e9b92e47c7cd7a0e8999e389d447d360df89885859accd605ff2d4350afb3323fe8307d5ae685d0a9621652c8597b873a0e7975ff523005690395ad2bd3234cb34ace55ba0f3930196328dddeee38db9fbece480e8d4d49ce428cac85bb87cc33ca54b5c27d5989dea3bd23068b1cf9e30f7f47d9d18b6addc5f88986f0457b666faae59aba4fa3a02abb6a69b98fabaf0a74ba89a9522f3d93c38d55f9c721f541b92d6b4e814608010cfb2efff9b7abb595e9459a0a6196b4d3fd1b5e7386874867d55dbf593abd2f961e7ee6c2e67e1acb1b362e1bc892311224ffa8b371c58d9d2497973d4668bc431a81f55200d141fc9984eced2cd71166492a5eeeac56174463425d9734b1b1f9395eb412cd4b3011ac565ce8550d5cb9b3',
+ '6b50d70eb3d958730f650f7f99f9fb046d942f985a112997dd4e60674f8e1c005d1c8aabb93210090f18de583b90c6f2b9724d165c9402eb43ec0ec20af90d9c3d5e1cec12d1339e5733b657a90046ffe7eadd7de6c11ac16696d9084520075bf35fb559267e6a37cffebe054c112433df4408535f611a202d94e9c06accb34667647b7b5d035dde5fc11fe98c8b089689c8f5222f3ca911802d6572e0c5b86482b899d92027b39aefc3008cd2359931cdbecd71bd1a709b47ab75a70fd3c0be2aa235fcd5b11574674d8a7484d8800b946db7c973c316c66a5443e55fbe705a4869786ae66a2a72afa7e42b0c3c652cc41edcb1b8fe449ad271f4b7384d7242c55689adb91a9b9faf193839d029ee9d471963b1f495a2206549b3a2024a6e7e87b1904db8890f0050ebab243a67c66503a67551904ed75f0c26a630257b0b1478c2b7d0497e2f9f78646776b0bd938ce20d3a1af2f28c5fb04ef5e809a8f20e7fd024c0d6c2a38310cd94b69cf5fe1bcb95d99383496829370ac952169bcb738325ffa4c61e12b4016e596d65d5ae19a5877b45ab1a14c48ba24af7b51b3d4c6e0771058157243b318fdf2273264c8e5a2b47b6d32f3738925e9f5e4ceff0a027bfa26a6f38821f8a784e5d2eaf7f83d1c96670614e7a8e3686f11045e08d779694b95bf888d468f371cda7fe3af0fef2a9fffbbf4085cd5d61679306b6bcdaa3d0de60840ec11e53c184864b8d460aa5133bdd53ccfffdf1382a71f93924cf36b93b027b93f24a94b19c847d722aacd24e42a087bc9127d953613184306e613799f5c845df0ff49d893d29fcae44ee61a33bcbc2d7e252fdfa355c116541958eb6373b4ababf2256918efc300c3bd73a5a4ee76be49b864575ce79079e4675235927e1f2ecaadea710b8858253b86f46bba57becac63cb990b5310cea42508dec9ed45a63c792f7850e24c584a62bf6b0d650facf7e32ae106ecaace3f8556a850b2eccc74d41eb19735da1bbbe2ce929ab92c138cc2aa05acc3ce6e360e6867349e60ce5a62b13a2ed9b6346cdfa5a4a8c7598935a954ed46fd041953694505bed82812b7ccf2fb5df5680925024a8780b71e76b8402e821bc5d4345c3ef5683689cc0252b9e9dd6bb27904b0f3c7256ab20342de2e43aa7541c7281a34817ae4d8d404f5d29dc6a237708cd4592464ade091556f1c984e9a99645d55f4f0210feec98266bf169f48add50858dc672e93684f1833b13757d3f6333bd5264a4701f233e36e275c51a63b31e205259a6a6272c5f1f29627ab6880bd2b617198d3000d988fd5b378c3040a0a81a3dcc40063287c4973727034a15e8993c37de1ad556782ee630a71dcaa41eb4dfaa9eed7deb0fb897fee1bd8c6b920dcc1f32dbd48277868e0d44f86df0959aed1321fd91b32ca17deb22e811eb8086f247b84eb2076036513bb1aa8ec8ade0cf1225fed61d7725d5865b416f284cbb2b3bcef1f277baa4dc565db2919eb01cf231fb6fbfac67ac1b4afb27f8a44f00f385f7541a35ff588be7a9af3ae554b5f2dd12dec2c286aadbc3a32a42e2100ed790b1f39dd496c7ec6a35dedf3ef4225d7e2cba64025cb8836ab3b6d264382b44069f4ef1d629897a5882eff30e27087ebf799127ee424baebadd6c2b9d1fecb5321fc4babd1003c22d01411ac555dee2fbb9d182d8efdaba3e60a8b31f3fd9c7ada3f36cebf2cd30723180bb0718fc36dd3e1a1964adec326fedfb0d4d3068e7f3cc696cf54a5c61a2b40d5845d906c6bea6d930241506a3b9e5d19eb96a10929f19855f6b7f27b248d96587042e853f2a647d8b79bda08ac6e8daebd6756753f9ebd598b119b5cecf4227abc481ddec9af7956fe7f05053f157658946cae3b8aee3e8cd68929cf3c06eb24af96b977baae0bf71e1558c9bd3c20fdb6cd30c1d28622d41f48233eda6bf93f925544858b4b03a161865bbced8a94866cb36570de11711bad7611108fcc54b1adac4470052d6b3e0dfa964699a8d9dcfe46d3b078353348c93a7bad23d1056448c4439ff0fd4ab56b9892d0873df7e5b4ad04ea669a7143bbbcea7d5e21133eabc5c87c1462a9eec389d6c080f2f78bd611808471e933f4cb25e6e8086586291ed65c6e38058fd15df5ea804c6fe0b5ab99cde861ca7f43419df556e844660ce81f86dd268d044680035776b35bba4b7c6e757cfee45f18644ba12fc767bcce52c9ce31a4a3113575dba40c7d5e8e3491b700aa10e0da5b7d5871db6d758f59a4fcbcd37befbc8685a659a97121635a329df4d95e65f8f4d4ebedc2a217e89426dfd92973180f21f58cffb4594c41a4a748db70b11cc2cbb12d9e4c2ef5ce671f9bac9c53c712ee10b41d97fb8730fa37df3cd9d1ad3fc85c460be2d8b649bad957bd95e5a3ccd61d473bb91f7839442c8aa07b86bf78d41c5dbdea690361759a3c957aef5545bf636ce1828fca636acc738ebe98fa73d53b9a3aceaa831f81ab72bbb43a8485932b4c985a1223b75560bf8e0ace083ab5ff260cf460df8ac45420b7ac8ed99538bd0ee7a96f2c3beb2f9928c7f18ed55ab129bac656beab27dc6f12c9b2fc7c9861dc57d76f',
+ '43b1ac9c15fcc2b0168aa9862db0304441ce0c5659db1fa80244fa18f2f7a02beaba8cfee1c2f6805e8153df26bf1b4017ecceb354b53966a2d5f619122e32d1e118b2d19cf918c68716634240a8b66ba0335af5e213054d07575d1778d3b8dbee7126fb8fc8b1e95af0e396c494892ea348b7024c1d0cc6f87337fc6d0fbab0da6eee66025848519cb8dac5faaa1defead6edc4dafdd5373fd18daf370ac1b86cb614f83cd06566181551b62a13f9173b830521d3d8e909a21866181eeb545b6ef2a09b8759918f95b04f519cf6a50f5ff7060381d9cea5eaf1cb1f6cdbfc01a6c99836291b5237da30dc7e987caa3e1edbf8512a250e71df03c3ac67014012dee406b16b3d33c3b03e002565cd8f0b3fd7e4f317e731d748f756a75986a8f6dceaf1f495e8b99cdf82c42e4c10dce08c92d1d09045bd3eee748cf88891bc15698462e6ef436e2a2fa32f81956e1a24cbb5c7d2dc673c0e9a236e873d4b05d84c5a6071c177d9d5684a4a07880ed03ec5e7cee0457635ae12ab033cbfdb0aa54f13f37c52ab8206511e1ca66c19869842d1efe2119a31881eb65400586a53e5385723f0eb08f223b3c8ad478bb6c4990a1b31c189fab70388e967b94e206901d0d0f9b3d4b6b09656ef05d32b0e13a9e46c9d63f5bf4f8717ee4651ea24d35fdf247cae55dc44c5023c2d309548fa30996c39b19d10817c926df9ae749f19692dfbb5c9b6a2371a7f562c48118d0296f2c40f93c816d64bc20d86ba34b8c48681feaaed3e3110fb94e70a01e605b144b41c27f2c0f9d55a6f77f75b71985b1da4d4650036b157d20b94cf455ed792a0aa1b87b4cbe007126053547b756666985f26eeebe64a9506aa0784fbbf2c2a139b6a39c332f3f2db5f48a301864b6e5e789c4b97962250ff3ae8310b522b03064eb145053d5c201e32feeed5ed6ffad7b7dd86eb8e64132582dedc5c5ffda4df8c97b16433401941a21e3cdff2f9926be692a7ce153663e04c928fd82ec995081dc487c75eca63ae77509607dc12be82cb62b42a75c0ca985eac516606b85fe7c9e1cf15041f88cb793b0335f5e1078430f6b7e6f42bcfb581d32bee31f289e658968f386e6a100270888b51838ff4d9dbf5b7eadb9ffb9f7daf2359f59e9b6b918ad117e4d181ba23de3643cf430ee99408bd1e7243d4be1ae9448d9be41de03d669c9aad7c655a5be60df32126db1d25d7d06a0040e47b202993736aed98ac24d1f9a91394434ce0481749c160e5db5509f8b6cfbeb33c56161af3ace194370e74ee2c5c41a4f77aab5c2ef618b48ceb473dea25e4c76a8559e0f6a7e897e9c3f6860bd1aa0fc3f1b7e5880976ce99b038a8ee4bdaaa6e759aed62a5282b2a0a01c62ebaf80c180c15b94142a3bd686c8540aa89c9e4aeee804a21ecccd762ad3ab87e5f52235e946de03fe9c70963e6d50e0626d9fb94b8b3fe19c4fa24f9724b63e107e1ddfd5266636c460938f1e8d118eb6c3179879adc113477da985722dccf40fccdc15d0ba949aea192d4793821683fa1fae6ee5ea38c584c96bde485940584843d58e78ade9aef418a65659f6c06ec0e5bc833caaf766f8a531b09621c0c93e859280196ac5f166f18711ce55af8d8fb7da9bda7a9d7607a3c382c821bec57704bbb14f6bb9f0b73648206d29448edaf8710f4bc38b71364769eb7ae3aaeb76338998973b462b695971f8b2ec2fe1174a28640d3051f70902cd510ac21599a0b4b48c6d53fb0ff1dd9d113c08c202e90f69209b2b7165f458463a14477f5eaaea95235e40392ce52511e065198b82b4caabcb722f7a5c8cca6d2d040e58b8e957d3f3d67a90f0b7d2891cca991cdf0f0e78cb2eb6dd3936dbbaa076712216e08ed954528d8309ee685afcd901d6865c4d48b63d5c0a8a870eb71ad80a7c2724e21deb7ed39fc6fd5910272cee49072109a4030a8992cef1d5db129544b7382b142a1fa7f747b66927411212a8f4dff1b6033822b9f6851bc3af1e5aba73e8677786776a630b56c645564436ec6a7f42e4fedc2277b63b494a9ba484c622a66e9eab7932915b367955c84416030a739918ff55665d42502eed393ba01253f0a4fc119b9d2cc7c416bb3f881c97654b68c47d3a8aa53b72112e004a39098865af124155067fd18e02f7f486d7040b754679f101ec1a020fb48f7956cc262063f163c34c0b150902e28ebfd6c1f35d6f969c0332271626876d840cf7b5f2cc89f0831fd71786beb11a01c9ee59cfdbb8edbd2c41b8141987c09e439392f9dd2640d2af9cc84f93173dd3db342b0416efc05fc4c71bae7b7f4250b5c0ef95e2e746e4fae379ca06a3b2874c4ea23a9f5292f67528be4f9cdc572dcbe638716e4b973c9a61b8a089f51c9e95a45bddc5affa13b5ed3c722e3d93980e99e9f6efa1963c069e114dad89d08c6fcbb4683a565a29ff8b02a08ff17c11f65290a0e7a7e885b7def03be1b062d3033b48545dc427cbba98ad6532c6754dfb86a909d6bcf28c36caf1e5b72777f51869843cb098075b8f8ca94ac6fb138eb6ccbf8c4d6f48c20be872f5ae4d547517dcf48bc3306d6be6ed62abbd2ddb66909b20c2ac2d4fc99f9e1fc627909ce58a0c15cc163bce7f4911760275cd41682158992783759bf56a7244f1c3afb598d78d74782a08aef83ecf50098157ca05d1ab753553e6a1f804fb8ee302e9333188c77d0a6f258389304d9d0b806be9c239fa4176addef623f7a05a1',
+ '0a72ca03c9977dcd0169da7af1fa3f3f02e374175886de21a796f54348daf8148c2ddff950ca918ed1c65747c2de90579c73a7d036d3430c95babd4d0519d7a06815ab07cf53e1d64773255ef6dad8c966b50645203a99657d31ccc3b9b4e2eb493317746ebbd7700b772e07b477805e07b07abe3f4448f2060408f08b337fbcd58d0b8a5788d923c4da5889243beede286ce982ba78b87cd93a5b1ba41f18dcb42e708faf4551b61aa58d2e6fb08b1170f23ddaba5f51ca9ddbac8b2b0014148f1b2ccca177a6f2b7dfb43cbd5ebfbe88495c0e677f7ca6fbf0e289495cdb2a0e5d298952a8409f4090b5fc35ccf3af17793066e8639fd69b80e75d26bdd5e6d8fd4d0eed5f878560c078b600828daac68b9f29669024232493a24fe9aa6a12960382a29825e36bbd78e4b24508f7783d8693a1089071553f31fba7bbac0274ef75af8e7b81bc1affbfe3372de797e12372f314f7e9f0349363daacc34a05d68c5dbc1bc0fb7a5bcf9e5d8ee0a6d7ac2058a7cb5a260787c93027a72a0cdbfe14c2908e8c1b85f4d51c380085cd1ea3de3f960e5acc201888a1cae0177aecb430ad15320a6a45adb8415dd345e4d38c022faa251f65a2ad79bdac9fb31da0c28825324e5f6f23502015b44f477460303730ca57d079f50f438cb32c257c60efc332cf29b6b285a3b7a125beb4042c57234bdeed968e81068f16c8ce961f92028adcd50c35bcd47022ec9966b31d9fc86e87cf2f982ead5a0564d4cf2e8fa0c4842c2a3f0414797d0cfef6916d46214dc1ed8365ffe0e3d24c7dbd751453f0fd5a29b70a4c42da921be0268509071aacc483e3d7f22d8b370d696d0971f3ec74b3dc64b535cf6179f7990f8ab0e8f2ae1e53d7cd9a9b0b51ef31cad26cf8faf3384b1a87e64275f949319bea8a72111b7765488e1eb4cce89bdcbe1a2ee984409180bfc988237dd9b9b1b1ebbe2ce0bb79bf1c63a70036c4b8723027df4ef12465833cc442fbe3e2ee2038d7759fc556ca6b3d945d06b2aceffc0743a5b0a9675c5a7abd3d510edc91861af4d65129b312719169674ea66ae8802db4ab9514d11f0f60ffa0ad668f49ec3e8b0acc759bfad70229ee607bc44a0989c217889a2a56aad5d1949753c2bf598ce338980fd629a7771e19c59a83be9c03b7120ea339a931c37a41983d3f9bd5ec46893b612c49e9d78e1104696feb4383d9c3b197c7beae1143ce378ccc846846fa253fd165ffa30cc2fda5524f7a05f17253f8de9c4028a77464fda832221b8248332cc948f5dffd020630bcec12eb35c8e96be080d5a86d552a71fa381ef58878db88b09ed3a49296542e0f0f5cfb3823ae93053b25354b2d491be8a820fe40d247ddee2f40fbb3c50e27b27eff3fe0cdcaf7b694d9d72946e883db49cf3f939e9cb2ebc3e5ea48d85da10f02a4bf160d642059559996efe630323ce2d4bf672305900e226a7c391768268d62f382c32aa49458440c7b855649af713cd687a6aaa8fec11376b66eca583d94689390cd6db3dd192adb8dd3de5a82e41f7e9d367bad846c60b1a2d039546f8cda2df11e1eb98306ceaed5c1c58b34fb52740b01de3daa75cfc54745ac8542dd8168ae9c85dd0c85fa2b593855064c209f5fd9ed1b80f9452957adb66a1240f025edcd31e948020074fd231ed4f052bacce80de4799df8443512dd0cbc24f12b8e6359c49422ecde05ca3b5d8b74ce31a2b6b1cd41bc30dabd9bde2deae3dcf78373573ccc925387753ba7dbc2b749ece972cc8fbc072770879db8033c7689bdcdc5d183dac0be638cf77182c1e44b5569c367142fa4676c5dbe7475f90680e33400ce8006d5b5da12a7a138cf215ad3e9528943e5bb783805a6196a6bba4ad380cd571b97f9c054cef23de7359600ce33c63a042575e03a47feafccd8bb6ef379d3733cd753683989814f763c6dc4ae0dc8823f36f929dced6e3f82893074ade7bb2acb0c0c34f10bfbdecd29cb2edc40006adc6170da85bd9dcf74c642e568912c84b07f4bade41c09f3d447dac7be9415f9c4ee027c9c81346a8ad719478b40cf8ad37839597a84539573ddc216bdd0b038dd25d6968bffda15ac03dc2580bc4c431d3efec21c0a80cc9232aba442be782d104d15b0b90038dac293b404aecd4ab8741bc170307838a0198fbcf7b32416e246b0e6538e4bf6c0b4cfc86e7d3b71efc255aaea2094251af03c1d9979cb315f6594720572aabbcf6aff41ea55cd6af2ed35e3b85227ed41ff81f712fd7b72aa5642e151cde32f10cc6b46019e4cddc9de03916230f8381e2fa672e8a6fb80cd02025abc07bffb8ac35b0039c081717a7e07df7020d1afb766f2b5a5db1505d0501c05d08806c7463516961d2331cf0fb489f36b8c78f9168daee9d0068fa6b927d70b14b9803a4a0ce5313230279f8f67d0f5dbdbb6bf438d2335f28e320d92717c941000f4fda02e10f9024d2a880381250e467553a9444c96c292dbc6e2631553c74dc62cb85085df1514e3013272e9d06536c2175e23b452b7385513cc32fba4cd5274ee1260f799aef05b7546e4871924322aca87e8ac9e3d6d64e074090bb7cea77003b3da6e88eb1f1b4e6c62434770c31533cb991bc17cb770f782ef2dd3f1d5243344c5d1f2f5288d544bf205a4746feb1bd340eb049ba1e11e9ded49425e63f6456d2a0820f393184e8c9fb57655c1144a47e403afc3b01f1e6d09474a3ed95003d510fa5a0ee92306d66e3b063e3ef888ce8e4b0a1b6e92bf9b4d0f34b9c09933257f91e86eb01842d2697f9c5570ff9d1045ab5ccc62a2c8cfc18948f69f399e0480b4113a735ecb28976a80c2de9250b6110beffd14b803b06c9ecd1efe980c1b194b1e9bb75d697f00e2f9',
+ '09ebb3463b01b2c492ca2b1f6ac7e6145eb40646537230d5b945ef330d3f5733a2fce963a290b79c4fbec9d78f6bbe42a851b69448f8709dc8e2b021b106e4e68081060ca687c49dd39fdf657410d1047b96b2415e5a5ca16221ce3919c4cec029e0d3e850ce21eea5d63670219f65805deac1f69d803c0a0e6910224c5f5ee8278315a0a74e16b94ec996a19c01c3ded9b5aa5b0e5358ff55233f8452c1dc8702d097dbb3edeb2354e2c6a0ef1c334774603617b8b9f7a9bdb5230934d090c4403120427d94e7564188901422dafce3b8512dd3a49b6330d0888457f976c1c86b0d777d0c2c537a9c22baa63b2268d92cf15736d8e2e2bb16042a16a99ab9ba0acb6533699b77b6ee1a0dfd44dbbd5258a87be95e74bd721691ddef4d24bec3a6d5b20c9acb9b33bed751c244ef4475c5df63933e3b3c7e58986489ecfb190bc69226b2a9a2071994c14e9c4445456bfafcd5dd7e1ea607647f888e8e0912b9f26a88ca9d0a028ff840cb344bc5085b7f699a6e28044534c3b011a33b35f0f6b3c5a2ff7fead6bd73bc92316157d46edd8c7af043d75f2efc91c772fc67fde98f0b3af65629c9cc8c9d693c8ee3f3cb9bcf3c08d87e3d1d978c71a3d8877fbb10a4195a2ab124e4cc4b19fddb51ab9c4199aa60eee127281c08d9ded87ebf93bef907d104692f2cba2f6a1b4f89450658518aa08de86477146dc5ca0332059a2070cc03eb3931cddeaf233ff37408336761a570bd7b3e330722fe0f618c99f7be722f9ae70974efc0340e10cdf83e4bf630c3768782fb847b914c56fa74c2d32068f93b00c13eb8e927f137e8fe2d758d26ac5df2e5e491fa217647d7d3c956cfb8f2903f4ad853e0ee955b496f1fdab5ab27cb078c41830b3a4689ff8ff6a752cce241ab8a8ae62df3c225fa315aa2f527fd69cd5f5a81374482c57a9291ef310a91f64c6a9b9a599c3f3c022e27f4d602f6de4c4776b404a7f3a251c2e255f5dcc7562bd25596eb53d64a694ccdf8dfa4dad28c2adf44fccc61c98b09310225a94b094fabfe036b7f4df4377596d8987671ef96f2db58a71994e1304ec51e49d8e6b8c1dbdf0861876de47590c8b989de83da7185b3188cf753934979e7d0e9d3600b874c40ce56d5fec22b85acc63b45d73e25cdafad33cf6787dc71df408e0181a9abe4697cd2d0c8355f3c8a24351436c1bbb0163f24079964f420f597bfca103b348da13b5be092e61b9caafeffb1680b3a1832f5e809afd2966d71fd0596d7682b2e31337b6d267d668f537a228635c5aaec49f8063b717bcc409a99e7cd9cd997af618bb9df4aa149fdcec025f965971314a4700607a9049d81b994edd7283580f7796c9d9fc7facacc64f99074bf287e778b8471d41d18121816159f1d4325eff0c1fdb0136531f4e55a4dec5e0c21f2ff455ccd09965d31eef9458605b451ea81816779a4ebeecc30fbe3bf1f142978931c21a510dc7b04e9aa4c29f845607c9200d181ba23d85c958ee4941f9fe9171b56fb7e50b71b93f27051105fbcfbaa0c87644ebed398abfd5a77f0c57509d7803c11e231efe5e4f2957cc4a0e2c97ed55e476a16c4d6c14ea8c55d7b5d30c0a8168c581b4b8002cf5ff6cc257f73ffd6cda35d2cbe39a772c0f662a92106db7c2c9369769595f27317e7b0545ba035f71ca0ad678969644fea3188b587352fe4c54f9baa93cbbdc40477f9973df929219265e42eec0f00cf6e9e55085862c4c92be8791f0ecb6cac70cc2e55ef25e23a781b89ebb0d384d99366530a5b37a311a485883ecd3c0712a111d7f537cd682b16e925059d5cf754a3b10a235a5cd3a6794e526d9ac794dde06c7de1de99c4ddb4f83fe47b53612ae4a601bc1b795c6ef26c5e153b141df77505a780ac30fd379a705ff0125fca429f6ec03b683547535607349f79caa947a805dd3a683b1b2010780e912a293b841b30cf0a07389b3cef465d711c9141b5c194a777dc6127825d38d22f8a58bbd8a215b78fc02b6010035260f5ec13ec2907d98e9fce4b2844cad93632fb9553267a45ff345db69fb9db53c592b1f5b28bc3fd191a07a1264e9f83bf0245880a56ece72f60a4805f1ebf7015af32e29bc33e27d1514f0a2a88245df70730d8e8504024cf7a5f32a827f6d1d7b63880b0babd803caa6d2e3adaa09065a9842ef5fcbe2368ec547382bcca9f930e8b77f8568b30e48e2bb6612c5d43915108313a43ae0d811d4cecf6c58102d11ff3707b80ef5e51664f4aa466a19f0465858abe0b5709a3750e450b2a64211a513813422130330998a2910d70b5cc454fc3e0893e0240555c6425cad3bb25f50c2107541f97d6968eec34a332e1f1dc758adca4c2f7d91f3a143439a9ce35ebb877f5ba646c6f80aef5da6e946c375241a22616817efd8977e71b6392e47a83bd02847ad6f7284d62842c777fa0c52e19d265e761dfd41c7ba5824d77471c45838a5d9e5f7f27871163d2c5d9c3c4f867e341204c61855faf161001413d42b973d7272de64e94b5225873608c1e5b39929e64c8294d39db79016e86d60f1468f3b08b3052aa9860ff2cb7517ef9b37702c873e7e0eb1716423044e42005bbd96cdf31ae8ddc5b0f0fa7489f999cf33d1f2c19865883489a7369392306665f94472ac0af7e2b044aba90cb52c34e44105191fcab7b5df3ef7275f54c6f7c2722ea5ae13c0de1bb9a68b1eb73e658ce7a00bec46130c1419ba91c2167458d3c0abf373b5b2245aa8581d04e09e902b802947c1aad5ff65a287e25657a6fe2c6d42c887717a59ef6956db69c1cb4942b317593a699f045651e5b5a688fd5c3ec099b173c757e35ca52952b7eb52f564e8d0bcb0f2cccf68a03a781d3ad5f77d63073aa337f96524c435ff69bda42904aa0bfecfd6ed951f361ca634dddf548add11c0a033cd33ca4f034e19d96d58946f2f7bd1a68009dc5bf2cc87f267f7c9974feff55b41e3dfbe17db229eed08a6b091c070b212a242ba635781090e55cc1a28150d1f0609b',
+ '46cb5d391e751146ba9700b4fd5f36ae7dda1758d8fe50fb47ed0d6275786d8491e23263a1e7be331afd3bbfaeda19096636bd30f0d277973ab9b54440c67786226603db799fda10eb52eaaafdbd0585294392bb317083c7b23887ebfc7f80cf21df376a4ca54e25541c773e910fe46bef89ffc140df5ad3d7f0e91e52ac6fa5b7d336d8c3ff03ba7ee5494313d89d03df8f6a09c827e603d06b44a7e9542c510ccc68ed85b7e0179134c8812a20189522dd3c5c6f510d9fc631014c6b7f9e1a475135703bd5cc84b4925cc07ff03d69dffbde82dd64b9eee0c886d67d35af4a90eb052b8c5fb1480f866dc7ba4ff4c73f72b643bb68d13947ba3d0cc97f4628112040e4215f76accb98635f824625f66ac82e67b1663dc8228f8cb8f7644bfbef7b4e64a1dc03f81050a6507fdcb83f8788adb56664e5e39acddda0afeda70c55819773b5df407405379e625a1995eabe379af6836db1d2d7fe978d982140a369bc84d8056f1567d3d4b45cbb05a43f395f5ff2af8689dc00a922485a08ff0753b37b5d38946a1ba1af4e0849a9ce851d87637193b9554b3d57e6969eaacc823ceee5c8f65627d69851d62cad0cf90695380e3bd70dfd65b88f4b420c10905a4cf62be2e9be34e1e041b2918f360e08c6f9c817228b697396d9b9124b4131d8aa52b373b7d37984a0074cff9530f6d4db52f9cf1c3981bc02bd98d0044599447f8ae743089ede06012c0a3e6a0197b2facb09296e212e8a22c45042de25aee6f2272e1985254cb12a375615b4b1dbe94ced61eec04b56231e75493182e85a052cb0efbfd572a9cb43b0974d1c49a9c3f83f67e6b9bde2d01f59eb64979684eb54ad94fba18ddf9d762034ae49d0e886264a84d80281bbd94df69fa5c63814de93a68496917cd46fe90e9700e44e827b0094208d439fc786cfd7cbbab7d4f127112427584c497289c402270b94cc5eeaaba7a4ce231df01fce81d96c1175050ef5aee5087bfc9f3230844c970250641b520b76614a051deb717e2f837c2037da68cd2670c59b45b3551d6e6bd5e57c551b46000e615f3633e15437c7a2df6fd591085256d3304b545a54f550b6908ee22ee2a99f1031223f458e570028b9954599e7d1834cc2995d67b24a0e4d5b8208b467d8dafe85cb57c6b1f9f5b9b79273a7f20bbfd95a1716a6bed36d414d4010d55bf789d46218c38c47846ffbdf4ca7e4b269d122ffadc73d00f9353b6eb142b8486d7239d1f1cabed86036963bac2977ae5183ceb943b75400242de2c7bbe586b5a25ed6d83eb684eaf41233d39a40896e2c9b8690c12f1447bd1edf5f4743662bfe145382e7cd0707aacb7aadff35427b63e2f18d0f77a45c2ad0d93f3ea28131e95e57d4d5586fb6e92812d3c150c95c5c20b8b715d72dc7d50b796d864bff4fcb028ad8ee9ee4801af2a44dcad94799811d8217bc97d711249767f30986070d0cc995951be98deba3f1d7210018e3bb39a0f8b3eafec9c1813b4ad9ad9ac1f4147b2013457f9281eded54594d55c649eb73c29588552c5f53c0ca255cd1568b4be0d25b52a91cca60aec2fd98d717cb015c87c57fe4277302ef90e1fd71ee5a1abf54742caf534d64fbca13c9e7ffcae224ef49b5f3e386f68e441478c3b0eae7e24d66b9d95e92629e14a5c7cda6cdf693a42b14ca881f96658ec7b50fc5c21b0f663ae36f6521c05d47ba7cd1335ca5704b7383b13dc74c3e14019b9d556b1f0f47f790b89283e8010b5bcf3bcfff57858f27e9ef2a0580df81ca14b4876b5aaa97a5aafd0b3f40520a8fa852a13f7498155130cd786198117b2a089d834c33a7ff4d1886f8dd3217e95eef5fd2a3647288f83f934f63fd9caa2a5da1729514d026f5c29b82e5251a53d08caa89b48fdb8e25fe89d6941748b8d1fc067ccf64ebb5a89084d1e08121eeee687beff85e9acfdf55f6367b4edd4a28cd14c8818ac1536b6a880c56adf562bf691a2cf93779f52e2c2496a10b220b35b8157f33f01aa94838f15bcd135e584b78ce673f833ea51a6b591f8cb4e0a002a64fc86efdbe5e46e205e7cf1a23789b7ee1c850abb289acfadef9c6b3dfb4977d0bccb819741b6d500dd8e32a0e69b6619978b6159d49ebc1fb4bd76ee7edfa2791b29cac0588c66b505692abe5d4a40b3f9ff92bc78de0a9f73d454fc0f3358a29a39f1e3a4c58191888620571606e621a649f54f7fc91981cf99abc316f50901bc74bd8d9102c43ab96dda17ab61b5074f032f7f73e0877b0a45d1f04095120ae452740aa7b48d252a98be5c87db3bc936b3a7e8dfc4d2ffc6917dfff6842212c46bbbf7736b6ac55e9f33a225e3f8dc0fc3d5082de66a6486e4f64eb352a7ddbf190be06e87ebbfc7d9d095101c6ad43cbc5d59d8b5dc6dcdb8d168f17121b046f2da3203aa6e58f8d11b81e0d500364015975a8ac3a76ffd95a5db5b701e3eee71ad78dd438145543d8b14e2be6776bc6829869e8039dfa903ca123bcffbde382e0c3155d3b2f97c5795aac028ef19f41c6a6aae8c2251527bd4aa2cf1591296806ce807eb81e9d3b7c1dff3b52594a9bb00731537ef598c665c0fa98494709c0145f95deb6c9afce6a610e7d3a97b2fbc523c6d240f5cb97bb6bf3bea5c7cbb293e01d263d1815a5c98da2714d941f8a8f63330d0f0df6bf47b455ea31f9b7680ab8e1fd56f316ea240b83be9336db70952d3fabf32560699101e7c3f4c61507014fa60c0742fcc20042790d14662dd45feab155f42552bb22bb72f2f6142cba000d37fa5aed0d57e79a4c06d90d5cde760352b21bf514dd814bbe1e3fcd45a7905a5b7057dc92d1607bc350e911b1b861deea6b6f7eeef8361793f0d8d7a8f6389ed91605f7d258f44da8944c5c7487a8e54127f8a62834ca89b910c81c9dd081417a936c2717122978c1790bd4ed76d47f1e8fbf5609b8c408f72517826c5df2ab06909452a72a8a64d7a82d6363aa6c134a4acb77daadacfb17d7cdf35cc4134445b48661cbc69c7ab1c8baf0204ef80b8e0125efe43a0bccdfd0f356b62e6c75fea8493dcb0fe9201982bb626a8800ceb05cd3a86c8867e218b59192c3c286a4fb13e5ccef2cf8bfd57e37a38a800dc47802df88bdbf4ba58a31ad91c8a9e83b029e63f87f4551c0ae63369ac860a6',
+ 'ec2f7852d0a6e6d13fd4220233a00d9c9c063d24f65e3b5620e1efc66c6958c7f378818c2b7cb08dbb51e02c8d08719925e71ff332b031b06327f23e7cce65eaa9f3350212eceb36afa263445e4c81d5337d20a10f614bda7443b0c8975351b1b7a77dfbae7fff94c6cd9592cdf5a4176cd12978b4f8f39efa4010ace58185e1c59c42c126bc546fa6dc5d5e038a412878ea23be4afd90c29e23f9318ddf67457adb6a9aa32c528ff7d6a2ef2893c2d100d0f4bcf8f9890f07f655a0b8f660a47f6bdecf4d5562bc62c44e8e63988ed8ac8c86bae773484dddc10b418d4cd9c57b5487a74bc1eabd8ead4883dc220d052325bf003ef33444ca8a035c356b3871179f4c6cc6f8545b25997816bcb8a7220ea389d52601b5bb745b2539d7dbe670fb531464e580065ecc91c68f2be3c4f5140fcb83c726337c833b59209c224c8ace78c9d9d1e36a8e2d9b1a35502acc48de706d5048e9164da0338758accad18739175211b1a9e6b2f0c25c51541527e113ce5685d2d3c7f77349972a2e5bdc2ee3369755ae58e494bd0b742b5e2c3d885c3170698c6bac42a38771de4a5bd74875e080ecf07acbfa3a804a0b97f8770761a2a2469f392ef5d9f5fdbc2a54299d961af5209e9603ad1228c73927003b25c928d46232c5b5dabc9a240bf3cd3af5efeede37e135f475eb0bd1fc35ccf2a93dccee076e98aab7f57ecc15d04f72182763237ae0de06196e32519ee9e5055c6495d97b7b3973552ba9de20e76139cee781ac31c419a16342a430656cd2da06e78b7b0680307a7c07244375608bf7ded75161a4b46e2d190f69549ae61bdb6f6db6bdf2a50626f330f6e15c645514119eda2b1ad96612047f8aa7847e496f5e9f1f87851442de844f27a21c1b48f82fe525f0dd5a88b8ec380e106d5de3fd9c25cdc209f26c0cf50cc06dffaceb0b0053389a33605d8799e2fd769bab71eff2a6c854c46a0c170f0ec7294b3fc6b64b911d0f65136ce8d22660c3578f7cac25ca1927ffa1ab679afe47c049fe625fda46dc39ba9a3d4160ac3edee9318b9c003ac72201c2d0645e834519410f4670731b7bfe7c1e58fb0c1b9faf99ba26274a9eda2c14f304762346cb1c7b9afa4fdfb80448f1c6467f9c1b8b1eaf52d5b5ca9d5b2f7e5cce05b0efe0b13ec80766e6c47efe63bb8e34d8560b13722021ae49e051128827b679ce258dc0d4c0f41b4fe8f2081824b8818a7126762b4d917a8f0fc4bd7a79443a4590d93183ab49d8e4cb674e592a4cd07817e52f2300ae8164d1bc179c7d01b0ddd9ccec94b18f046b16e5b76df5d3886bee4e269f62fe2c90ce420a355874435da86eda4ff94d06ad70752d9eacd5102b9e6c44ea9b0be1daaf5d7e8f35265c8fa4c8e1fbac0b4872821d983278d8d280d0446f4bd25d090c1c1659f03a9d613976e1eae1f1523181f3e7de72806635322ce09009307a0decbc7484a18f63bc24c6c1de4af1a829a46cdbe8a6ed06a1085947906ddcec5343387fe7ea5d00d3183b71a37cd49898a195009e16e6417ecc008155bffe3b45d8373f6a12ccfa10dd7df823c0c1a7e641155ee809949d3544c897c947c0ed4a7562bdf66303dbda3a355e445de05f7c4c95fdafc91ea42c395a90d34c488cc9e0610071232b2a98f80bacf09d5a47c08abce6d99caddecc725d745a18bea02ce2db10c59b6b70b4dfa6e90ec657e71bc3332050cb69d27db97a4b48f14bafda4379f6d813ec3495b7af1d8621fec8f6bea1b3fa9d7908a8d4591e842017433bcbe2b994d3d5fea348cd5040f67871b744afa8c15c0608b38ca1f4f6ec49e3b742be61df224f57465aa98b238ded6ac81d05068c4e375b08a9fad6869f0918b66fb7f7a34a82c5e6b4ead5192d843c8f114ad542bd35880df30ecb1c808168a01b7381c79195d2eb1f39370a1f656e76e8261dcdef27172c3282dba0d6d65edd0e9a0a3340b106bd633eb8dcacb988e36943e7142d3690cc2d010efaea337fd510d597cf9efde8c448a060781aa813405d463affbe8a7c54ad316d1204be55f1e9cc3283f5a20069960837c6b15996f48cda1f76ec4a632e7abaffd06b9f67976026e2378bc7d612141d46aebbf59967bea59d61fd9fcbc15c45cd1d69ff3d303f8bb0d3aa95f3298b8894197ea3a401bb4fbca83ab03e751b7adddf440660254ca5a23f9834de14c3f029ed438c402a4a818434eba643b27e00390045db57dc5019c3639dcb1f3d84fe0e1452d7f44a35e3feeb58a863e04e80e966b4a7aabf1292182703823f0a965a4a74f3ad49c9421c31b6c8df246753a1f3fbd991e2355cb6ab741082c5e5c0abe5f76e36c60f3ad5267e857211b0550c61a5fbc286a5f42d83300ef33935cb99e8840a99f384e4b5e329d58aaf211c683b4e64611e79a3a0a84543fd246180ce5a0211ff58910a6572a0014f88236f5e87dd5a97321831b72399f8c60cd3a4ef435bc98f7e9c728cdbcc50e8231f18964f3a268c4bea6619fb1674797cf202a7ac767b72ec0fe5d324940c7e087bddb79a4d1067f0570a6f38a3013cf926619b9c3b6ecf2a502be257df7b38c0a1876a71fe5f51aac7e460e327e5370dd788761b92eccfc1c90c607b97e73fd2f7de56db355d7100a2bd95028c69943f6d40de31633b9a38e050f599a396bac6e7a924e0da50f07a505db5a0b9d5781750517be796a45717ffbe4ab8ebd1d225d7b27b88d581f5a0398c69c296710d1ee983f744136f2fe78d2007e057dfaf7531bf04dc0e38a9fbc61259720b847bdb9e9ef750c2e4492ef23cd419cf0a78415c9966e36dbd33125db62cb70058ead7d86926148c4bce7795da576c3b98560ec0084aa5db57bc6d68418b9a5d33819800ad299757ebe547e0c43be083d27066c5d3582b3e4f6c954d7c1d210a5e68a87c32abe20d0db7283ac1267e8f00efd0d3c4377c80ed6a11762c8b56ef21c88fb6c052fb94f96ba10b98c14d476afad552a190a08779df69491c7c41f5c3c9d3141fc6ecd6f72a3bbc12b3559457bafbaa330aa03d3bf226301399e9028e92fe0016b0bdb94f1c7cb3f7a49e5c1156cd43424e83887bcccf92d88a56ffc84c98e16fb874274868ee590f3e3189def7d086960351528094ecd634f690d5ba1e271ff0851b072b3719162126150107c58ed9f6d2138293730666ef85a06aac31352262c0b94040b08453f70752aed3e78ea52b63d000fc91a4a9d1e08da8e6ac49518c1057bccffcb7765787f1768c865853fe5d90b403154e07a2af5f76afb8ec16381efc6220423ae90a4ef94378c9',
+ '0b6edeb5f06b22773d0af727dd59bdf552a130004ca497bd7a233d9da0a325eaea71faf280e445685ae2e30756a5b57887bf9976d05c9930b2c863ef6331f9f820adaab4c37f410e98967c1d6d56c003e89b0a151efb293c604c2b9a58661571562ad741e4c47e31a02cacb04bf3455c1d3c6c235b09aea82cb87ce8a9cddf1d33f167e3093b659919af590a1704ae4ccdaba5e9b20c903dbd13401f7bebc0c4600944df5b6d5c0dac246d71fa12629ba0ee9faf498e36c3bc655e88f94a212d847a548001e1cc570195cf2e1ca4c911400f40bd48160a02d0b6be6b48716821484d810d231f1e3dbf096789a4424b765215725ad82d73c1a20f481093e8ff685489b1cdebb0b8888f891dc9ba74509181091ccf2159d9cada77e4be00384cca4f36ce097f1b0400181cd93888c3402b72f226654a25a4e31ff77abfb7e8b90fe15dbf0a07e8686c03ca831c33b6830cd0d877617b163dd51996f259e180acfeb3056c15aca04e95f79b03bee6d681fc41c4f90edeb60a67715c34d5a6888f606d36bd7595ca1d449d984166c7a9a3c36dbc93b3988c7463cf51287b2d89c9fdb7f89a70ecee3d3f9dc8265cfeb94f28fecb2d97d420e48fda7eb7929f0bc29d3754eb50d694164e9e3498e7b48eeef599f6b003b8fbc0b53beac7642394e2089851985b7d45103b48e2805011aee9f0e847023f6ca4719b9a9d4137e2ae910580f889da098893cd44dcc7e03ca3a6e293c50c9319a3600a9da00e5404e0375e9850a714a2e607cb3a2a53dc5ef58f924278b647e781f4c9effa1403b0b23cd98761d8536ee6d4fed1d20e8f9e2a0bca9c69e9a2fdc594a236b33d8b0ead083ff53305dd9810622eb2dedf4025cc8150499f8bed84f7aa5b1bd47036475803578ccf17fc46ec19228555ad361a635bedf2228571a3a09dbd4564954a833c96ebf13cf4f5a10362a4f14062baa67500693ffbc0738347d5905d6b9310e9df27c1cf828613d0dca37a9ea6e514f18cd88cd731233e4b74ba9c0af254d0a2cb20a3ccaab39dfbff456d358f1e8c222f4b1e63cc951924afb4a8f5ffbfd2d588e75790ba65da4cf5b1455e04f56a62e7c1e68ad5004b36812b7ec59dbc5dab9ce6a5c4bd8313e9454ecac00b52f5d83aa2adf5534b1da87187e423d133ba4c91835710b8f591fa7783c404af1d76adb2563b4b4e5ed7a30830a3b7a50c32dfef28331bb5a399a814bafad1f53e3508d7455835cf21c14ecc8e8328202f0b8d3c3c038ebb75761aa35a35d0e79d7a1230d8cc5bdc7c22d247094b1f4a858d7d02278d10d3536e7aaccb3da98c238df245755e6480574456010ac5432cf402d8c8509a4a0425cbedb774da03ecb6b5d19e86d8f9c09a6d0381f7b73dcd65b0c51721f1e456d3d39d4dbfd486103f3cd7c47100c1a62de6014f3aeab436c1e06d76015c85d145cfb2f513f2dbffa7682b3ea09f6539f8f777f33926516deedbf76d58a1d57e63065438d8fdaac1d482f694797c8c81e3e78df55e32bc7cd6e68c848f897e6416c2a99d77be9a5fb0d15f4f6661df87d7006dde10d89c6a5f4c54440cdc258b4449dcac56fa54e0229f8ff6cd140552ba883c36b6de994073537634386275fcd6e513edde7c804c1132ae11185ea7ea76c82583ba0d5c05f9451bdd7be213beb5db76e9770bc5ac67d4e328ae076d58f1084e4f832d8dc1d9686ac53e26aad9c7762f278a6ecb070bca56c4f7d7fea31590df217906d47dfb058c76e7f4e056f6fd632f7d6e3b65e55f306c5b9603d3c8a70182045fd7404763a878e0155d3c29b73d8abad3bdceddda99a9420b23f1f496dbf98c024112a5cce7518f51ca9348ede2bfa765f84bdb82b80214ff070480a6970e79b5b8fbfd86718b5e6fcf643ae87d56aeeb95e3c7a1b6ff393a5714541c5a493341e40437da6dadb43913b6e9ed34d8362f3b9f897dba281a84ba2a58434f33226e6f343b100340f8753f913c472fcca6f79385095eed061da5d84c74629b53af03fe94f1705dcb94ecfafd1b3c97ba680c45a0308e7720ab645a8590c0693140ca3c2a4142a0d6ef66ed036e16942ae336f8f5e4547ffe2d8ae8da94a6df563f89ce0014cdf7ea71abc0aa1d1b4da57f3c548e0ef72d2909df2955685c254912095f1e505a888e82821afb1194ebb2a4e8037297c0aa28a92bc6fdf42a64922312958adf317b4a8ab4a3fc30c895daba00aaa965f71e83733666da2158c4bad86c184ea79af9a6f10a04b7630174a4294df43c62e4b1c3d1c8b2f5d52d6c489bde917292dd2a2b1f49e5349385b0985a97863274ce896f2aa85255f9f285c4d331a8fc874135607d3ced7aa69e703eb3a60b9385ffd10fe59fed0276f036b7e72d04f66d0f42cd71aac5918691dc1f9d4129677cbdaf2c6c752b05326ca8a8419a4e672e907bfb645a158119a91ec2813288b741514b4d26f2b66517b1021f48402d58b1090671bf158452492d5bafc53fd18abc03cefa7bdd332a0c066da464e74ad0dec50bb7e8a3ba0dfc64be6fd331ace9d51a60bbd3004d5df8b211c0fd564cd79d0bb35649cc60ba1c976c8911cfc0db74e028199621aa05c5fe15fa7b56dc75d62225d548581e5f900f9085e9e3b668819b4f9b2c09f22a5a32a2db47afa2b371538abc4f0e9b06401150ecc2333598e494fccafe80ced49f96dfeac729459856e60a94c5b780b614e8d4450389e6748513582c724ee60c7c71f5af648b6e2d6e23cce4121b7478f4db451816ab71034c5f8b4bf13ae1d9d90d0bb2869fc4799f51f9349d022053c831cbee62617d4e22c2bcafe40d67449eb04a7c962bf084d2bab80dd0342b4f78338d4d4f75b25bed8214deb18f2254b3a3da94faf89956f0a432f512783e74ec29b4c045adba3497e8ba62c288b711002ee2821cce68f8df588f76cc9801cb0d5b67ccacd33ae1063cd6c37dc0d1836e988acf63750571891ef618645a1b5bc110cffbecaddd6824c692874cff16b3e32bfc0236b417c9d43d8f624387352cf19114d46d0448d3d7cd1438960c2ea8482d5da3ff544608aaff83dcd1e7f64786275ddf989f262a099b845dc2b0c26a86e7d83a251e3c37f2aafa0e764107b36618d2a5d3481d73a1760b7f3ab37a0283a1925010d79e5e94871b819b5e0f787bac9dad87c5d5b887a7d12565ddfd7729a3b66c274a178377de0fbca607b79fab2de37f1ddff800a376fdd7abf5f4d15f346a17d43e4db085f7fe470102a72fe0e1cfa4fb5e2b54dd2ab71e74c506190c9dd6d87f7ae8eca5190fab12178630011286a38b0a18bb1d0d29802813dc561a2724378ec79140bf8e6a6f4310fdabf606330434ab673d4b6578872fa81d90701779bc6aedf0b2bc9c381bfbb4b3a6a705fc505d08c0e24f7bcfbbf24c72cff6b800f07bb4ac4d828ca138a1ca512cfc59090e70ea',
+ 'b0517cc1d46ae79e220c9ee73a2a54d67e6da0f26834f63222d9d665503643d13067771be6d2d56711651fbfa21fe9b9eed24e540227e12436e2e6af0567c3161b7db1f8b053b79315c1d92c8ccf8db15d7b6e9e26b7341d73b2e4718e584494991c921fd9f5756b55a634f6a0432608f3f16a967eedd76600d036749611af95d0cb825a0ac0f837fa9f98e485829d04d7bba805b2d0b34706c44680c398ed5feb12e96febbd263f2b316dc0e494dbee326192b26a68ae07ad177b5dbdf7e53a10792f2723f3e8ca11e61b506482c70e2b6c8e674dbeb1f01c503cd22d367e706889bc4a5b6b2721d3450a5dae5348abeb6306ea03d9a5487cf7f3a8bb5ba2481ac9f9a03a2bc98d9bd6a3ae690f480e99ce610435c27058f49407a70e7038094ec24cf0693db7548e224c0d3ebfae805e36077d8b7ffc68adb0e097cf7c27fc2efa1e048fa8dabed6b06e40d56a62476221601dac1a2fc0cfd2e640a5885969dffbd8a2557519159b087210d5184babcc1ad4ac419af3a78183816a399bb5988c4de09363ab5b9f04b3be45e7d153f6c4a6cbf1f1082f67eb4a19dc33bd23d05b76a09f60528aa63a38bca7b29e616e744fadb5656bcb4636af165f3af68b5a74007e8df5738d70651fd3fddf865e5d029ce2c044cbae8d8a3ae0bbf64fd57e0073e427c9154c45abf16a11159230099615d2da3731c2830e74dfb810cfea84275539338540af6f3735eba9fdc9c0bb5943e5cbe6a3ee72ebe47b1d307fb0b41030e57ad0fc9e352f73bd8e3e33f6ba72ad845af82c1aa048131db4fd651056e48b50c4535201debc34488881d8ba500adc155116d12e564e872b43208bf2b1caefe2d9b549c0b305fef45f6ec1f5c349560276e79c13dc25ca0f9340f93f0eebe303809feac3fc335c29daacf58d5c56a5b1921494af7af4642f6c06b6ddb56fef1b83b93cf2016dd34fc2e47c6c635a508c6c44c1eb78e3dbf5961acab6ee7d9b92a8aa473609dcedcedfbd5f78207ce0f9ce202cb01d1cb9c8d8233db1013d70d0b81b13755da7310ef9e0a59bdae5dc627e4fdce4b3c4850ffbca17b535d8f53d7ab3a99946f82778d8f456bcdbbccc2e457ad9708006c834c8b661acd476b341b81b10880af4587243a27bc3692a39c5eb492c3dcd08099e048f237d243e304538fa502cf1c54b6504921a97cd57aa8f3863dc32e1f2d0b57aff63106e59f6afc3f9726b459388bae16b3e224f6aa7f4f471f13606eda6e1f1ac2b4df9ef8de921c07c2f4c8598d7a3d6ec4b368cb85ce61a74338221118a303e821c0f277b591af6795f50c40226127a2efacce4662fd7076c109eb59b18005e7165f6294a6976436ee397774e0df5000b17579b38d58fe0e1b5a2d1ccf329b4fe10f71e8180fc5165a369c705f6150f8c8b20d8b7b6d64cdc0ad69f2b8373e734055a2ea90575c5658610dcae483b50b73c6fc4693a74f363f681444031a6a0182c67804962aa4a7776d3ddd16b2d6a96138c87d8ca307e8164edeb93638986b46d663de9fe6086a25bf9f3f7c7b40631f8be488cccd3953b3960baad82e5420fb19e8c12416221ee1bcb45a7c497cc8ed44e2f0caa25df9b5e23d915f7827b31de58964a9377c4639f91fc69caa063b78d8465e0caee05a8bb7e71532928da23dedc821c5c66170acf933fc5419574b40da8129096f6ae6a38b8aaf07f9f06ec9772790d04f8c1ea93183744913fa68b3a025da4740583eabe1bab7363aea894f362a3a7f3f56b0bd46a0b6d2266a246feda6fa5cee22c2f33ed9d643c1f6824d9f327719225bc7678cfe4c85cd210ed4077701b0b5650418177a74c71b8eda3306e2ef3474f5d326990eadea84a9686e822878c932997298e01f2b16c42e019e21bdfb67b3df5478df444366c97df1bdd23dc82ce23abee44d3a61e9484e88ed642634197b52dbece451b59118191b309c29884240b31988934ea185148ae0bf42be11c0180ad9e13c996cd00d055575347e31bfdabd430476ee6290b54da97241e82d023661cef43cade1ca04cd20ea3f9e4cdc1c93abd65c7c3d82a71133b4e626ee4642e22ba488e1acd58bdb1e0e121c425d82e0b47cb88a9ad166701fe5a40cce02ba26806095e736992ea99d5f507aaa8aaa2f0d761f8bf3138fe4de83000c44de28896db6e811177b59c33f6c8f3bfe09fed90730f612eebf6fe9f01b9ea80b2f0a954415f411b7f299b274a402d2b5420d69526bd091d64b92e9e52db452597bdcd4841c4e4ba0a55af1cd946fc158c9326a4f55339b522ea57f3e27f5bde84b1bb1de285b3159fa3a0baacc3aaa51162a568eab9391eafef4146b98e72d102343d792d8bf655c67a35aaca9d7d056af31b860cd7517f9332b43ee0eed32698ae190528bcf5a1074237943bbebe5a1fb050a96395c900541978835e89c606cf871868dd01f722eb646f1f080cb4cfb9000c77f8dce8cb7c0e54be3b4592992e27024a544346fff946a2f43871a989bf4a1698d292f80593781297800c81063df69f5594682861ba519bbbd3d4e3b3b9f837b5f9a13fd91fbf78b534c5d976845db72fa559e670b4ed211be21cab732f71377676ef066daa4a4fc15f58e3108cc211808fffc7537183fbbc6c3349f1aa1dde82506694e9bb835e6209ace7fddc8e76f15a4115337979f24779000557b264f3828fed3376dbd16f413bab2d64fc2aae290f0616375239ce64126b27cacdae401d3c6b293c909c4805fd3cfc6e75fc81d1b6381488862957ba3d5cf67485638bfc5ecabf62654db25755479e42ce6eb79155be554d9db354f204bbbb7d61eb9dc6fdf13d10df4a75df4db5590a8fe71710f68022af1d3e8fb36f70bf0de9ae3e2421c8eb7088fc5944ec6c76eb41cf6af7a066c2d69031cea68564474aa61535bed33710a7e7cb262f3a553c0f6b8d78ed5c587fe97df6da734e7d9e5f1f864c3b1a26f6e08420a3474058f59e958b099b313e9f116df47bc1d2a40b72dc6a4944ff7de341e8619935055ee7bf4730e5a927006b75e79378381ac2d5ac662af580892420f29af8d1a0914d5c9b0ae4d3be46862b3e733b9b812dbd4534442c1898c003f51c224b1031ed0f9a5a650f9d8297b827939954aa44137fa333feda7a33ac03a9e709c42190208ae923e119099f217fa69de2466e28d5ee37d01d9be2fa560a867ad6c9cb6432a8931e046be0baecc1f283d57aafd67af4483428d61a94c501d2fe11c4d5552c4fdf75596be97e0168516efb5635f60a781f86a7f5e8ab01d1d69a431c080d1569144d6582ee90675a0c86da43c72f8e6105ef235f15e41360da77f3392c31f5dd7bd1b218b59b26816af2fcaa2f290c994097237c69e9029826bca983096cd5935c26c796084547c3b5dbe9f1338d8f0718a52fb4ab62d6600192ed626663bc73ff772c62ad36d10a336827829c031c93d741cf6fa5f6989fb521483e0cc1b265abea6ae66c17cc3d2ec240c33132bd25c3958c151d4e4f3f8890417fc42cbf51a9a708890f904144ec10bc1ebcc379a526c6ed0edc120327c308618d544cec1f42d78eb25c483707b67b21fa',
+ '5e9d7b803f8a40cadd83200abc49e7ae245635a7d1c2d16dec6740443a4497bf941f8d82976ed44b9c78aa34eab8ab322b82e9e21de93e858adfe1487a9e38caa747edd831c9447b9305ac34d630948605787fb5e0ea5bced4930ee72be553a8815dc40a7763375fab724e93e7784ab1988020a8828ecf50b3caf0a8b5e18f6208a939a1cf045601ca06bad8845a76bbcee1f4446b9d43130dceaf13815a95fe26727524a3734968d90a158b179cc0ad8de5221004df5e20cce572b0f5180c87c202a01b5a79b79cc1c68a340707cf8ebfd2d395b31bc97ed65861087ae29d02c39fe10e5cde49a668823e5cbc634c664bf12e59e11b2b35156fa6a27982f07913926086116aa68db8865c8a9e78de3d198a5ce6f7a52d4e6f71660658beacf39923460be1e4765998190a47150d2e1c11e584c45b8277d0cea8ccbd815f79793d99bb2334166312ef85701a89ece30a1b49cf79777ab0c3195afd4e5a2d0112e73ee65872c613c1a710b88b62dba6101f00658fb254802f38d024414defe9c67f58f03103bd2e6ea703072208a31f3505506d8e73da911d1252671fc06fdc9ded3000364fc35d1fd7a68868e30cf581b5820ffc24d288949127ee6f1d7380a0190e3ff0bfa048de1f4060e45bddd1fc17dd75632c05109d9c99e2b9cbc47dd6ec39d5e6b91b96b2671bba1fd9e05aaf14abc44af33bcf3f1bee6b86322a7c484cfbe9a0a6fdefa4977dbc9fc3db39a19232273ae13b6d82f76fb05cba6c25fc229aa3a7ef0efdba97af8eee839715da7abcc4ba5ecf936e1664dac6cb541ffc575f2c82fa1665fe4ff959947fdc9763b58fc352d2bde90c61151049cda81350d192acb931ddd278a8a24517217167432af34d8aa5ce1663c0c97b6f2831a8fe7a7b4ad5fb2aea2f88f47901d0202c82c0328aeb3fcac37b1cca43bf44b7871039622f5dbfc7552bd9351ed9f3af8e296193a1fc0879975d5b5e8fc18a02578df58e83d9e77abc7481ae5b28f4e7373ff45dba4569a33b1067ce87fe60d9c17e98486dd2da0cc7136aa759753a90cccc60d9ff4fc80f569c266255fd2f056dea09d815cf00451d0f7a673f482d72f8d98f4f96a18e86910a82611e46604f02d93086a458c1ec67709b3836293554616c6806a7c424d0946162150d62597c2954f59a42f585cb4c3eb46066a1ba00af90d3485b3ef0b506a9adc447d884578961405b162fb4a87582ac28f637243c8b4ab85bd9995cfd8feb4db7f73048a7cb0bf912498db64c89446dd80f74dbd19da4ff884a5ccf6fd82e293643f30c33965708070298f37f318adeb8d8df47878ca59117d610b1d4897a298853a83ac4934f55826ad6d408155ee7107a00d12500555450a43c69f44ad735f750d7392269fac9cba9d1bfb1dcc770271c5fdf75a3bd317bdd686197c14e35c962097115c1604a29e6111d402fce6146e785db3d1ae410dfa81d008599e61147b0c44a65438ac1e64a1c577eb579a2b503f92b4610d3dac52ee1ae8578a8b1b963932cd9967f1748fc7cd33317d21cbc974339582b907575973fd361079afe67a2fb7f3b6347329824b9fb27fce1b5a3cbec6b3b1325cb370abcd7dfedc6de989686ab5151ebfc1dec5936210dad56b1c87b2bb67199c353afe223c8d2343a9667bb18e409725c217ae6ebebf23ee82fb678e092edf54410af381de360ebfba73c222ebb32e439eaf6e8844b529c5165bd6bf1972e4038b832462efa3df307143d4456a0754a7dc189e5680ad5d07b9b03ddc88ddd8286915f95bed31154482594a8f6597aab0fee5b67fff024e14c19b356cc3ca1c416e45fac36388516a52166d778afe80fd7b993f5b1c4e7d7ae26f35c656c230dd0f85a13fcef40420552de57426a687ebd6a5918e650c5ba880ceb79fbe40b659c1777537ac0ebe052fe21b2be52a101a948d756065a6793c111c534f66d00d46287def317752ef6736e5a6f522e3c9f839c323a79ab7569437ea615bfcfaa630a91b87b3ad4b08e50eaaf1768c8e06133ae9549a70b9645f59bb8a5bcd2b2197c7d2d744da71aafd1b9483167e6364da1c6260df941722ebfaf236b563dc0ffa0936465b7b41362de254e45b751e56eb62c0d0dd517b22c89040ff0f5da7b1b5e1b86d6e0c444ae5f74e9dcc0d196c9582773d1a453fe473be0a3ba026f8c779f5cba4f309e559b3cef407de92ef168700180e2cfbefd88be8c0753e3c59a1b499f29590f0ced315dde7cb09c2f9d52e7005bc7bc2058f6f850644302a44e0d462cfa7be5d4b479aa89c4fd419d438fa36d2d08d541b79ad273e210c6d450577c4b563e1abf547a0c3741ed3e408a288e901d2e81e8c07a343fa844961c4701d54465291695723c69321b07fce01b248fb054c027df1ea007fadf66dc45dc11385e4ec4411eb9c8abc079d3e3459d8b8d16f94631ec771431edaeffff18b6918ce23a970421ce25b82a83da5c36b965720b354806d874dc9c603e96675a7e88bb18502bc5685c5b7ab863a3cd7d17bb25d5304f0e6abc022e9ab6b37cd6dbffac48b907edb90973d7b13eb79fe05e948ebc11e2b16cf88ed1e53fdfa55376fd47ba9cb3e5dd7d74b95f3f9c3b2837f9950a018a57a4cf866c8701a04d98f68a74b622b8149c616607088bdc071d49f1220527ce68dceaf4e7c92381d96e04ae1b83739de1bd5d52a9f54dff6d863d841df7ac364cdaf0df2af3ce07b29d4872246ab6eab60a183f866eab8bd42cbaba6e26b74a6b678a501c4d29bc40ed69dd77b31428fa493b3588bacd0aa4d8699cfddb71932e4a604ea71f5d27eb2610f8fda6b4de14436d3c9623dc034450f131b25d0198fb4d19e1b2b091d01c0fe4ca9c8abf946ec0575d98ef00ff1e5cfc8276f690e13b365d112649ee4039718e5b3da95cd26f88a19f7767608599c62f952fec46f757ced6e7e9329cfeac14b5b3c949b4217f62f20b19d3251d1d553474c7884a61b5dd2a6ae4b3c292dbc002db26b3ee080617f2a7677b764f12d0b3272412c5a7bf2b01a3ff148885303d1cda3e2f33106c704a7d49a67ca4e10053b430d2de52dc7f0498239c175e1152adb8f704abbf1a32a295a89e5fa3f0adbd25d10fbee973a2da53369497a5e8c95a7d3b7c7da07628a1f56aa946d5a89e9982f1138cf4ee5d2cdc214430e31c68cd32f1ddd238e919f0a7791059c0719d8ed1772471fcb476a239cdf4089e15f8aedf0170d111dccc37b3bb1bc2eeb470441c4b8b95882db5e37421ec4a613b40a48a527da3b2b50a1d1f1a11a6e7d7e0646a55901f20c1e4989504731cb1f60a583dce4c6fa3de9b4af57d3c303144b596c47d7a384cd8c968a260d3a618ae1c72ff5c245e6dbd47673dcbe2855661b78131ab930795da2efca51c52111dcb3f99d9e44f9897bcd61cfdee4cd0de98aeceb9c721b5822fd9fba520398549b53b75d14f1344a9410f103fcdc2374f50612464b96d699c3f920eab54d02922d4d8aff283b98a2bb6bbec0a508be233f0992c3b69bf4c697323ddbea05e263291deef41698893a682a257675ff1fa11e21e8e45bf5f867331530fe6ec2da4015214dcc8e9ca87a20d8cfa5ce23aa7728db8f18aa4943e42e2e94d2b2083ca1580431f8eecc58ea5bf417cf4c1af10dd592ffd13fea79c5cefae3e9624a9c0f88433609b58c3ce3900888734e4985edaae4a5be7b7e0c94bbe6a8b2ee0e7af32c4ac',
+ '54ad09ead61540366364b6f311e3d9e3736c71c31bda3b695cbed40f5554d9ef2ab54d10954d3b5f9e909c01a6e97ae8aaf356a4c6ecc87cf86765be2740e55364d586966f73ab677d0fc97a383783f50848143b91e0ee027d96a0ac7be9fdd487777b276d70d97588490507d0b53c3414d1732f839ef62371b54f825836699a1d02f569952a0db248a71750754bedcb56f73b29a40f28065e2b38e7c70f70ccaedebc04f18a8f45448fc9fc2fe1dde2562233d0fd19cbd4cb602484ce5c5c92c07298a18978a657046ae1b4065f55a29dbb24cd95a529b441bcda0178057315dd2851e863dd9b1011a1281f03ad9d32b228d6c7759c88cf47a72405caf3fe7d8c67ae80899fb697f29a66e62db3fdbb1dd31167a3e4314d6589c838ce0c44f25698781203a83f152fbf63b08d5abd6567229d5529676c5523ca8f438b398f9bc1217745d7de7eb15177e62629882457177f41380f0b800f0ad241ce096325a0576b73c20f2bbb94df29b9f00b267bbab551c6b85bbab7a4a109a68051704f2aa0de3430b3763de5613fa2b53b1d0ab5c900f57e175b573c70d885026a4a556123e28138c9a74dcd60206a1dbf531971dcf494324ad6a9fe00a5a8fb5cd77f6c68e024825ba533746334d9d2a1b2f01675946b7cfd13f513d8d9d51430011573f73ee3b5705a3701f2e3b679e921d7cb1d4a440237f983a381ddd5f5edae5ea05966877911ada19d9595cbbd9d8715b85b7ee56f00729ad5811870459bc8a31915bed8784586b86fd5b2de43c7cef306b7961769606683d162f16dad43362c06b3b09d5714cdc5a039a2b8b66eddb9ddb9fba29860bb87c0abd296d4ebe04190bba3a0c1866a10574acd21bc9b9caf64ea154ea6075aeccae522b1639eae2adfb6ffa75ca446e1bd8e9ce0fd55f31cc4d14ce3385e2bfa169748870161882e1a2c2b7bd0754780fa8f75bf23a4ca4a24f70928f96b16fbcd49aee0573e569769a391e4c601563435d5c184d390097fade2b2e68e3804351684bb840c3c00abf5a598a9e6515c4796e6e9f8b7229804871cb1e5a2cddbf11aced73ac9636eb3e6b9a894d76c3fff464c53e377615f21d92d6ceddb30857700b26acb36bc89f66468296b425ae9a56d8f690dbb56471dcb9b4dc6e16be80ff1b5dc00fa4e37be963883f7ce2440803235923d2a07364287f0ba375d86ee011561969fbe226151a4b31f0024d12edabec8353d6c7e15d632b31d0af7877e94933dfe70293ef0f8b761634eeb699af939d0bcd32ac3cd22f76ddd0556787f1294d17d3de4accafbf7c9b8a8ccf56b26cad38ec80cdc446efca562f12360dbc13fa67ccc9674d9a28b7387d76f7c8ba9995b13e3b9d3640269e31495054879eabd4361e6e89c03359be736a47b06e1cacfefb3eedab0142567b05bbba53741d435309553822e32fb51ae2fd4999c55d19418d6af16793b201e929f29aa351bc9d0f681db0b314d3dd34fd8327044cf050f5ce4f01638c33bb51348a8bd4bef0fb61c8c462cae3c4349529b85a90837b06946457781f493be54bbbe00867fa5ef0e2a1d5b8cace755dc40df94ebf07518c95b610c00b693f1251169f9acdb25b100a99ee3d43336bbb39f0b28df0372855825a1793b85ab1c4d9db25bd867579db62076a7ab4c11bcf8fa89092c4914413e2b6b85d969c386f7e7ffedb12a24fb55170d6cbafd60a2d0d6c0ff7bca4493a2f528f7836ac3784978b978e02c72120816cbfda8500bb365bd18d2748febc2ac0c4198e091933a6bd749c40c752b2bf5a618211e4dfa38df36f949be9fef1786f71c3099e51c14868c1599de0e358e444e5c9fc4fb157866cacb2e02023ada553e2387556e444ec22087bffefe7a831e97ff40416245bd20fff647e7c1b253446abd64bd35f42f461a06fd134de052ab0869cc3e8a704d3860e25d16e341c978025190784115003b02f91dc50351421229020b627c7f71d472f8373670ce861c8e49d42f9b8d0ac861cae5be29b49c7c8233c4563f5b711dbf9e9ff07140d056960cf68a49469216bde01ea3c7f0a9109c62c1c1dbea953ace3d5beced81f04ea302be305526e34da1a3901fe3efaef7fef9c84c59162553273e34d1ec782e2e3c93f6cac6174494927b02d88798f658305ea29fc0c668925307f248760dd11bea2764ffa500fc131ad03d76bad3c85cbbfb176118e2a71dd9025df89428233f3426d278f9c854f3c00a0aa285886b2a2636ee3a69512a1c41963c8a4db16ac2a2f806ddca59945c0c912f04ee9f28ecf979f1d4bcfd39b8142a59a5aa90efccbc05c8d5219a047587ce7443000147c7bd2be6d418cd1c18d8287af2b1aefa830bb6e2080573eb67b827a307c09410e5f9b396e586a91a6618f768186cf1d21216711a1f7ecc9359280582fa3841ca6e357bc9ad0d797dc759ffebc0e342c19f659f3ac2948d42745dd1dbc26ff1bf8af9ea46d4b5258b6525a8ca921d8a0d5381a90898509f41e0e1f174076d8a355fce68d70386968d68035acf3522afff55f1f54d4ab9e8d8c43ccf15723bf575183b5d42e289b2caf87c7dc052fa9bdfce3dedd07fd7514e48f4d188aae01bc7dbc9315018c5628c3b17796690ae34f5e5eef85be0b3c2ed969361945864e372d0fe4dc94e428f195c5cb68998446488c38b7db4155424fbd3a1e60024d034c0216517752b091fbb81d39df111c711e28f9ce6a4c5c35dc12aa4c895b52bf8f7f383f81c5821fcb7d3059465a43c254972aa9af398065787c1266e1bb47d166071e259857c920c58797904aff9ad8706943c01693827f895c0ae425ac8ce7643c009a079406539e59bb75695b7211f611cda83ce4a2d2a3250c5ab199a2700e80b8037c04ca169a56348f0e087a1d5a1320c88e97921d4a799f11122d28f9c9678d08422474e86e1f7b33c5810349110005b78836a0ade3dc2bddc3b170f32972f80f167d97577e27f80a0c4fbe23bf4ab4ebb64c8f02f39f3ae752d11aeaa315918e456ab1d24ed243886edefb3bb965e6eb95439dcb1e6564e42bf6974ecad1e20c7b8654e754d0d62559c95b0f93e3f41db1b65d44b8b1024acbbc769e053a5210155af1052486486759795e0de3476518780f6e3e56f4cb81ce7d2966f6a17a3faf52f6ca3284e2c4ea6964c50bf2c26264d910e68db3093f80d33027f3c9b2c1a6090695033f5dd489340fa382889462148e05fba17e43ca9f392b5f90f5a46c95d781041b28120cb253cf47fb8b43bde3a8bddc46b913b986295b8c62c7c786fb690685fec1a7e3f2332420bb4d68dc7ea3a906e1f5f192c21e712ccdb284a74317f79902be67e0c56c9eac66716c243229481a17a755dccfa2ecbf56386454097ed4bbdb510a89a86aaf697189d64b9a841b743c5fc8fe2b313ec280ebff03baf84e7cfd4be84517a7d6d650e92fb9345ea3a3d491b38d5153d7c4d22fbd4ce43e954accd199b9afce9581a921e0d38c13713784bfbdf0de855834be861775f19c79a3eeb4874dbd296be9dec692410e4cf49db16c30cf2f4020a0ca81a6358fbc4c26b7573977dc52da7d6649ac783765be44df19c47ec00ed1777aa4d201faf88d21db2c48de99d561cad42da7ff93e82edb823ae1963d6bdb5743523341efdbcd53beb61dd8622b8230acd50d2da05ed6b03f52009bf3c1be9eb92c429bbaf08d0ad69720fbb1cfcc7d54e254a8e93436616af1ba068fbafbdc40a5787608b13cd5b7120acf252c90df60d806f7db02de7d999c664c6db2038e7e305d4745b86d32d4e923b928dc8ff55528ac8102453f434fa4adf41a317623d65f59a5fe508eb0b46f2440395a1a4db656addadb65c980f1cce99',
+ '04c873c05daf299923a2bfcee193aa104fe90717193083f1e20f799a897a5bccab28531869482a366b70689a24d6bd4758c29fe8dc43351d9e227413e5148857d93375ec45affe9b9cc1c68a3ae1b510ed399dc8b4591de4c62cc6c4d62b7dc896d020627a4e6d6fbe7f1fc7aa1e5912153648de28da05ef6417b8d6e62703c6eae79ea28f8c3e5ada91bc78fcf373f6d8a1ea53c02eb3e67fca92719d70e2f9de6135d50cd03b06f6dfe5c6b9cac9633e62c94e04beef6f202d9cbc826ee20a79242e237a842a181d51e1d9680a250250622df87df083354e281ee01d8acaa1c419d1b35f0fd43b54cffad8911b4d7b15876079b22d35de11a35f05f62a6465c52865ae46d90115a54176ebbd65097595baa9f82bdecf137186a85196b876ff863a343bb44a784e178f9e3c72502399d9e44f9d7169177b77b941ef849ac9160f35848333ca038fb2a1baf03b44618ee8eb9b920b38d6bf2a247205483a255366039eae4ac168807f5f12329da98dfccbb9d5fc81b1d38693b083bc6bfe525e958acae3829770c885b2ed2822e76d8d883445065c3ed879b843bb3b745017dea4b44f4a61b4e30fcd8095fa5166cae7294632d52346ab40a3c663abeb973d7c9967770c718089ff5db350d1b28e6bb2b5d6e6945e3115825c22c333583a8ddf7e8d88513a642a3e3f3167d5cec81a9735cba7699666dee7e93d23fc44a3ccaf5a0dcb4043c68d747be4222d2c7a9d3db00fbe7c514fce195401cb2d3739c59636cf8802140f7b4a17b2c802550ebd4e2e8973f61a53adbda55502efb7643f3a19bb07be35a8bc671d85a37bcfea426fb8210dff76da427ee220126a4e8c01430bb98f9d2ff718759444f9c12478f44a54bfd6beef4c5601154c41c58319d45a15b169c78866571985d713fbdb1e9b870d4b145c0c12b1f145c0d829de7380273d8bde63cb5c40fdf72539527d46fecee8ad100155921bf47b641ebde803cd518d2f349a7d419cc9f218b2ee9157e6c5efce12d353355cb2be205daa282f83810d85b393287c33257f97c8f69fb91b17299461fd8d633bd516dcdb172760695ec476a5775377cdb7a48bc19230d3656a9ee847a58c8582028b80e22d6bff4891bae8506d8799322a6bdae6eccb0f8c6757b30af4d601f7e326f4b8137e72e8c1f7c4fe9e4b4a2924dc6d7f29f8d457b55bdbf311f5416320ee20a5f2e823119784f3f53127f27c4dfe2cd4743f8b8ffcb24a4a2471ab8d61ecedf3f22f788bba685c7d4fa3f9f14fd9ff2cf3299afce665e65757d0a93f4d2641e83adddb1dd4abe6e02048c851cf75cfd1ce3d6a66197b9961d09ca23f8ca606cef379b3918a567b64cb9dc56378db82092e0363953dfc49b2b75cfe56c77422eb448c68ad866f0253792b59f1ef12021d3b04ed51fbf1e0903599244ca6967f88569d623a700162f35178ecc1df2235551cc77161fb61454472da7ee9d01603ec513408ffef11858d7c0ee79dee1405f8fad5558ee454601695a773f5eefb98615cdac4c6aca952682175b04bc4ef5950fcb403a05ed2194dc6886b37a74e252d9f15fd554fd0b1ce6933b1930abb18a34beee15f13e458332f06ce78a416919943701c757f8f8a057cd2513f68802c3a0e0b5992a891050ef5a805808c5bc6ed707087eee4edc55681daf71585477c5d6e91d203c8e2082743f776170826ab714d9fa78827f24b09a0d10ddf0a17f053930ab47819dd49c63f7a8a05c07e286d0384e40bf0a602660341fa639ef97066a4fd66ba438cb13311b9a9115b6b2528b9a7a73ee612d3b5cfb1266aeaf4e4dcc9f35291eff726b5e23c3c0582f58aeb989156eab23da63d2faaf9bb961034fe2c73dfc4c5259195da8ca9a7dc253ffec8c95bd7fc2f644749b3db2049554914f205751d6c1edb1c20305ac012022da970d71ccd6bf1f31b4554345fabcc096646317c628deaea8fddb0b517cb943a34b9440394a78a3d014c156c41657c5d3b4e805c5ccf92a83938952476b0e44fe6ca9776f359022941867feb8e1f6e2ddd32797ed3db1dfc615a650ea368f95508cc58dfb429629e221a19190e80a862921ba5488f5893cd4e6aabdb679cdc32e2e610a59dbeb186ed306b5f883134e2a3318a2357effc054991ecf28af493d0bc41463077c1f7c8ebf2fe23c6da1a97589bb278f448618b9af7b2bdd4172815de0482e809d93c4c618659ce8e226068f882a5ad2f0ac94789c384a30daea2eb8f584c351daf89fa9a1405c9a9b1103ccd0de92ccedd3d215e1eeb0cfc600a3919652d7f79eae5baddc5887bdf3031fd1d65085b996bc401602f6e606ad667e7c252ac2ee633597471c06c4bf747cc9231b18aa45a5966cfd81f95081fb8c1dcd34852aa2c32ec109f2e38a3bb9de8e3511af56ed7522b730e15e86ae3ad2102936ea55b138ea676af3775eaf1db8dd8c4c8d320d9fc1cd54a3af0ef7e5d8e404ced2faa63f08f8ee902aa8762a8c359d4e2ab2428ff40ded4b534ffb771107e44ec78fde3ffb04194b85fe4d6ad934ec79006e18c04a074f3af3c035791afa4c59406bd5c641075fa801d681592049fe6fc6bbfcdb34280f15091612764749b150c635397c6b71361836a7be6fe1f34794b6226b2b330eb14bfba83ec9366497c7d172559ceae0412e9d1851299ebf5c8a8737e05ad729ba5253fcf71c58d97440fa89d6d24fc2e55d9d7ee620c70cb1a39272f8c480e7aeba9a9af7da3f26db3e9a0229a6fa97b727b061f9bffb69cb92605a1102d0e6f30747f8ad7d59cb41334871ba757bed2b0f8e57e8819c652eb98963d58037961baad49c848029352aa17e3f25d86421a5878fc74f003a7d3f9b760692e73583ad37d90d098d2e031c1bb3e0e84a13d3db222d46a9a6561092baaed8e5825b2e1c10cda0c8fef8a379f481fd7e453b822061ff4c64fe5fddac89ac5159fc08f3ecc81b2e3f4fe994e8ee50fb54441b9b19c97e4f1d72e82301aae6e64cbedf8393e059dbd91aa165dd4ba95106d164bd2bbb12d54fae6f8f2670f72e5a453f3ba5dbf25022c98084cbaf039502878736dad95565680b66708f8e459fff19b8ba973d8d11b8e73770388af83dd3b103b6ab86ce75e3045d8591556a9197c6cc5eec677296e7fe16c69861efc206e85aab1255e69d6d33c52cf058dec9d0b6fab719ec5b664c78aed68fc662b7f8b7fc82b3c9263253142de5112b0a9f2674b441b45eaff662d1805e731ae986358a89ebe44315db3120083c882e7698058a998d2020d8dda7a30b9cf6e1fcc359fa533538762dfe83e1d491a9e5cb3afa631b07f1c56e629767c1306fbe14e5b262190d34b4e722c7c423830ae340fe7188a930bdcee94bce9a41a75201ba63fb6c2bb24d91c9de7961759f2fa9a0590775d495c8afd1ffa9b50d60425f65d471630be3079f5e9815243b348c9b41e128b51db5c6eaa0d4a5427509c5199fadd1014a1dd7201dd62796f4e1b65aae1d51c0f50f1cf1ee816dbd18f23ed2c05686a166a150e6701f2d342335114a5d742f23eb005f78137c5f9f79b8341d90750eddd23bf9350dd9a276569d41fcd86bfd487047f2cfa83bf76417da295c687fc6112d3c34ae3fac03f7ff88ace4978b58c925347b7b1536b1a563c6a311b0dd68e5c83097b49dfcee139e95d6842358de006a545e0cf2f33acdfe0c15c0121453b643a786ea9142ad63b433437df43ad998c0261ee7c9f7ef683729160a04cb132d200fa6a2c223ee52c0ef681492c7f7fcb73832bdf2cb5beebf9c1831f1582394ddd76b9fa9070d8b5538d8fa77869596cff93dd215d3ecdbe7d390ea60521197ddad5a13ae62a767d19e0a922add5f116af794d69bb82eba507e1495fa2f49a0bfefd6b15add3862d68d716e2552a0d728a1dc3e0cde9df489da17b707764839f52d75eb26cd2d16c485a200ef7d07627986786ae1bdc734e4a61ed0109da9ee0dc4bc43aab911fe3c2510dce1c2ff4dee140e0fa',
+ '13c123ac379146d066767ac02ba4bcda80fbf8a4e4cec5b0ade84fc3a0d19435bf4dd49b622642a4892b004171794a0965f9f2dbd72a0cc5af21ea24e3ce4b0d4880cfeca8abae6b14eaaa967b40423c7ca3299879bbf630ede71dfeff811ece5763fce730a9f1edaeb9600672810b3c6d008623f108ecbb0e42d0971b72763f93fc43d423a873f200a20ada7ec50dd1df18f1c36899542cbb3aeb39602abc2aa5558dfaa82e9c42b2ac905bc692b0c27af453c106f7974c9bd8562af63056553476c0a2e8c5d4a46bdfdace73735cd9e79b9265f2a91ee35723fab2040cae88e965c6140af483e2d344d17eaced79dcce1598f7553750b99624bd1bb2472a8d6c2c8598374411c293e25bb29a8a6f94d66b4bbf562a949501e188ab2a68342b64d3e776973be60d53c261b165d1a6c9a8a495051e0954413f6444ac91f733297960d3f551636a8abaeaccc4344a8743ecc85d10d45cf783f9b5d764127c8f5054dd305e8e440603716482332f7e78c949e08b29a1ace524d7da2b1cd280af689d51e8f97564203e20386d4680f4e22567f30698ad7f85ec80dd261bfc8bfd39fbc5e20e2f4d22056e6c74454c342e1def09b8a51f6041a29dc5b2abb623e08a174006e5e387721e030a7e77bec7c27a892a889820d48010d59bb61228d2c02499ca3cc6ba987a5188197525fb340803dc5f5eb8d765abfcd16619997c1f06d0286b6cf8dc0aa068a5a240972e03668291af224e6d9a282f392ec588d79218546c2c7ec470654e2901acc7157dbd46bd4f23bca209fb6071b4fca12763b45f780f145a729e2feb5e453ff2e710e90f7ebfc215fcd411bb89ead795bd480c4306b62ce94a90f2dfcd1863a954100f298b899413a4f663a24184c78994ae232dc40b7b11936b35913f2321d4a5a5b8fcac54a19fe1967a7b5f2ad465f2bc7f837cb609bb975a816b7b0e805b23f66bf0abc8f2a2fddcdcafac830711209aaaaef45fded09c835dd44b808926132cb06d4f8e8e023ef113a7f038677666712c17f5ad0336eb0e51347521431dc06e0fdb5f4e7da9edfda7caf3f0fc7a0b698b2546487fd7cc24e5f4c29ab62971e511a2a4afc87d51271e7f7c54cf0659a9513fb1d95a9986eda27afa93ea306db93d2ae65a7668b4980230550ce703965a05cffc089c6663900f2fe5b3e81bfd111bdbecf78f515c78da4444bf4d570ba3303cf07c4e25a935b57b4aa3b7d36915341e802d1c1f92ee2f23121507ec00ad59ee55de78bea1061ac7f30b5f3ff9ef0f5968a423bc9e22883587b81fa8bd9f084df3d520189328c879a691e946f5c435f66d05af0fc83d6de16a4d9c7589a2c6c1910a501dc7c647fb2ce05cd2a4bf2c5b57f8c50058676692857f873aaede19b2f9240fb484061db34d9ec0ca4f057ef2ee246f7795c7fcad9ef3e7df727a8c88f1cc66c51410d40bd0741d153ec1b221fa32b45cc986b69b7e54c44b1e9fa4ab42aa5b39bd0df4697f097c9db919515242c99d973acb1dc4ed482768f974eb83b465f9f6c82503372006e4490835e2ec8f92301130bfb790b277171d4d22e8790ea645e57d7f8bdc7c125e01723eed57a93577b0f58a0f68978b9c5260d023f31a1449ee234413c05bd6f1ad405cfbfa58597a5dd053aab26229beef7ca7255a9e580cfa039b244b85f9a536bbb6933f64a64001084212d7dcfb86dde7cf7517631996ef66ad45e5c124828228753d8d94c6d182e681ce40cda9fb02e96f9b903100f0b792a2fef6d8ff917ad2c0814db15e35cab2356654fddb2547ccaf202fcfb52138d0a1d7e69331d90600c0e8e5831974bfb489627a33380d94d6b88b5b07df315c67d2591db863620ff99df9bed29c974b33a34b1c3968bad251b2647b9f262909a15e0b040f3c357b067e3d406692a65579aba9a1d51434e783c534f960341029c46d7501626559346f8b3ad307a1a7c4ccca0271d0e484bdb517813c12aeeea31926207d7785d6207cee7ae07c71a4827527e0f4f17fb13b2ed3d6ac7d3fcb5fe8b293e11745b52975cc85cd8eaba476bbecca92028ec348381fb8b1688db045793956930a4dfd36a150e10405f7b088e83e49b3c9b8c3ce1923b1b39d40a43d13e2f2fd1844b62e499f18eba9fccfa04347e4bf10a6b8b41a09481ae201b02fffd5ee8509d3e9fbb5e4b2ec416309a6132f231e9dffaae283f6064e0078db03863bd295a4a19d842d45356e97d36682a11e8e38386ca23f9c1471b7bf4c2da1ee3c2794b257dab1f9ea2bd971f5ef1d353bae75ab95a6b5ac8b13bee625aef17fff74eafb9ca86a60fc1b949871ab5d16ae0a3ebd21c12bfd8374c93fad67dc83ad41fe47191097aba38e09d4eea32b8ea02af935b9f88ad5231a4290895f48406d173a5e75192023060b9fec14dd70e3399710dc0455b87d938f8fa2649e1fff687c050859cced0d4e1abeaa8d63125ea0d8e97aabdf9e3dfc5b1a3de42d4708c5fbc70c6d2fe7b4a243ced4fe3dfb47fe75eed7559e245c86044928b113aaa3ad19e933584df45f2b0f3733127111e67af785baab9b33245814862d74582e184860d145c32bfd551105628f6f093e823de518ec54ddb1db9b133812d505bdaebd57e80a55d3ebdf7baeb5b0bd0c68656ec70e36f96c88ca7687c6a07b213eaf35869649b74ca4459190995da58379d53626cf5e42519e3912fa9a9f0fb49861d77644cc909e12cf7d357760ce75581bbd88c32cd693dd7096f31bd738c7b50dccae585989d21cc56425b57fe2eaed7f2a78526a5e3a2bb62bfbb1109f607cfa3bb63cb94aeea96e71e6bd8386eb2048a57be4de814f7255f999c411ec8ad5724d1756b47afda313c902f533647ed9c0581be151e8d999932755bca3c64aa8bb2a581011c104f1fc9701c75924ae002d69dfb18c3be088b9deb7028ed5aadd1ef901d19ac90d7b7101699abb6e807dd8004fbc54216d270e4548fc9ac2b15de3e39b0015371f29ba2fc4d523e8fe380946f46a7442865edc858f138e35670e520fad074bb643e31e4a99e2573d2f1a08625524b247361569c514af34d5d5d9b3a5bf4d04ec8091e67a71281f131b091c7dfb50d8d88234ff2e6039524b02a64dcf593a0781de1b5be6d30f4513cbee8ebf6c58ac9c74a3e4e8fa17b13ef75e69b304361e1e6569c2b747ff8fe446b2a64f32a2f73c134a601a6ab31957bae74f7947a90f6b1e6366145560c72e943bac56d598805f6711bdec3974523e552b474aabfba30f10f28e26869ab39bbe73e8fbdba011ae79e14187eec1239acf11994eb794a2b343fc811561151cd1cb41a267ce2470d150a036131104551431808cacf3ddd4fec06a88086f3ac978c38c21c1358b666ff438e2b72ba4b0538262698de73c01998e25eb27366f8439af3eae32993dbb306e8f8e9cc309fc00ca9e78181c1af02bb514f29b401d13bc963e91e281a237bec58f81ea619b01c2121c017619e06a5d3e1ee58c15ad3fa8807412f87522a2be011f05c88dc2874261c44cce66f437d7302d0b213b85d0a575c8799dfd25c3db2b26605ed0e65527bf7ea1498cc01f409328ad833c0f8e5d7e220df8a21363bb4a8edbd5b16f341a3432470f12aaea4070f613daa0b24175a26a1732eb544a06663ebe55b9c5ecc3c9c88747801c5f81ce81854dedd5b098ea88df7261504065881e51056e5045c98528a9195f7d47a8b5b04b04ade2a46c5c64ade18a6f0d7fb616dc0e5a7807d5713af5ae35356a602d6bac286740e59903e7c9a7f11a78fefa0ea69805a6f98e93e7b22e8dac904f3f9af1e1a4573bc8e4f77aeb1bb74b875ceef8caf640e49df5152ac1ec49811df2266356eb8f6ea1097d0ad592b04cc5e39e1accb5b090a99fada38ddc7604734ff547b0c45045cb7962bf8edd6b445d970654c7ca5cc55b979866bde49be3f95cf0e816b70289ef3c8ce23e8452fafa800feee3beae4b5be7bcbb778d1ee45623ff8db14d0d02b45be5ba0c0fcb3842a79f2f47170ce9509703e9e35d68d032ac0b7da90dd978c3dd5491210740c4dd139f601c60e069e2ad543a2bdee16e37fdfa012580ceb3c3cac0ada5f4186774ccf8c9891e9191ba3396f47498f1880b20b6614d2c557a5d2a1357bf5cbb',
+ '595f40b057ef2d4f8774a22899acf28da129fa406d530c9416b02cced6637fd119f300fbd74e754a200ea2c3f9fabc1466d02078c84245db693eef3f5672a65e6d106790b6ce99f0f73242ba820c7bf85244225e56d5ce720d1a08f05349b86c7b3ddd399d78818a3168edd7dde919828c0c66bbc0168fa129ccdda976ee9b446b02cabc3452165ff93808e0b2997cfa3db05656ad0d71afe6ddd834676b392e66e796e222673eb9752bfc9ea8258ea88cb858f9c6c15ae66bd46058cdc878719475a97310bce2decdc831d9689435d3a2add66ab33a338ce139dcdc500b42571c336c37a55beb172a970f599aee5bc5a61737721b80e5ea6f95b689993e7e2626a945f68a4b3facb421ffe5e53ce7c4c17ce3d9a79c57483e6e552750681427dc609d776694c8e592ed6747f185c1191b664267fe9570ee754f217e1d92eba264dfdd83e23f6c0aed84b04567d1d10cdb5cbce4c8731a233dbd8255a6c3eddfe6ae6be2a6521562ec6c43a8ef28ffe42ae7b917af3e3c30be42e075960301258b56b15c59d8aa36b82f8637309333eb2f8ea1c959ffbd5d1f65a3a7935a0fbe7a5e15b8a3d613ce7854e3bcd319556713d9dcc26ebe87f289af33b145d100f0dc4e01c02e5638725564c1fd7fc34da1fd50d2ca9781813723a6f95b566fba04d9afdc3a9f5f016a77e688c4dd9803e1167ceba97c52937416d45b6f6b3d264298080eefa1fa56fd05629fd795a05f6f85e49026c438a5f089c1c2b32f412cf142e1ffa7da2e1f75276170fe4ee34a927310270b173c9ff4a5f397f14785b55afec2172af2034418076a6203b06aaa9308891a1e1f6469c891f440ef5e11a7c6f534be3f9281ad2fca05ddad653c69ba6bd6cf2881baecb4764c27761aebec7b4fbe5cb062b142019bba49c312616d4fc57fb0f0e8460e007c81b24d231d6ac233e95943099aecd8a0120f0e62e2a09a3d0d2340fa0fb8f3ca1d4b3e22af0be2c93c1dc1304491fa01949556fac6e8e3fc0792de5f1dd3d689a8590fbfa7b5253a3f10f17eb81ab0e7c9446285152f712af56493c07845f1e0a84489a10f52d1ae7a9a9d9cfd70427a3784fca9d75c8dee5f0127c529f88cf8a7737471eec92f4c76248b311b79f8e168beea0e15577f70ced1621537d2eff92c5098d64d02873dba1484e61b1f1a45e458f55dd7088fd9ca3c0c59aabd620ac042bc7933e521a9ced450630449efcd31bce53e23570551d9aaec388aa02c53eab1aa01a85a44b73bcab74fdedfc0a2d9508258032c28ff8583cb5be06296fd32052817b549398f88608152b2c8d5eb647e94547e6f410c552f7169b3ede83020a7ff63609a495a3dfd751587ee76d158ade2d99c08989fd116a60b0c286a133dfdf78cb335b940e3085d406538eb7c3f44359066df75e182a032e9f2fb63cf1070d73bb602d46801ebff7b548e7b13a0ad5521e3dc20faef36dcaa6d4e1d8b2169691770eae1fb1f0d236c5dd870be044f0a331ce8e011a13e6df78509de70f94e73c9e9d32720c5d693be87fe10a7f2921c6e17e9ff4e1e22ae774315efa61f88bef829a7ef007cae1617dbe9a4f3f2de527cdec9c3daf04864d3ae5898541b80124d394c81c2cbfd73205f7f73cd8c9b7502796e75dd9e1a5ab2cfbb20a3769d367020ac25903b2b73801da9c75b49314dabeec25c7eb1fe57bdac26d1bab746f408e6ad238f53a0dedf1d50e6c5b009a21c47abc2e6b05e229c4f82f1c266e512bd9439c2e99bc57ce7665a19344a893c008c13ed3d23a184f6c0b5c9e20ce1593930374cc69b00143effb1a8d09d1ae3fdc3e126aa932f457305a9a14330a29121c58e074ddcba708cf33bdbc033255ebbf6fdb55587702dbc2844c10c5a90822058283ca7e55c567a47e2fa2d941076e32c4ee26787cc0379317070661213f3dcf3ec32fb3e4c8faf058c4c3e4644f31d6ebef5081bab91512614f779e193aefd9dc2337270f4e3d435231a1cd32a9d10c334355fcc759ded11189e6c4e78792c5f92853402bb1991dd8eaceec3293b653399ec952192f0f5f963ad67e22a1d11404471687c08fb8d07b54add9ca897c4c6d360d1a36a5210e7df6c942311625348c13f3767454f71ba803c11e81177d385cbd93cd8658be6e27323199b950f9a7fef37c849d9dee4ffd7c9b12ecba43d7769a1fe4aec6220f207191ed21fee90eeb7a144ad2c708fdda23be5f73ee6a8a496ff3e8165a0661f8497cc4f15c5db9c01c4d218a6cd1a5cc9d8d7cad204bd15383a24043a0d5f72d0e54a9ae15d2391b6e99b14afbc2c8434e9ac2fefc823d1389bda5bd171b4f2d44bc13be97e11d6bc58c628af066d5eccb58faefdf882e07f6a850e94940da8781159ba97ef4c72fd597cdd0e7387f17786a6d0645d844bf4ef50a5e93e109aa57e39a0527a7d6d6034e5b934cb1f451ea2191c8cbfcf197e7161a93a3668d241db8a7581e54cd0cc30284689d6e063aa52111bdee60b52073ae0a2ee45bb58357073bf8ef960a22b966e0c765c6f5201deb653c099e1ff7690f6166d33b2326a851d08e07e62eb64aede926124771a0d8e2f4e9ba2f827b3bccb8f1fc8f46ac762b0d7df3cf9b35fc0a160b3f79ec4b4aaa594d8c7fad2a50586946ccb2a08334f53b5f3fbce030414defed59d8c57e0793fabddd18c0836b54faebc06fb1298932e29848289e23bf2bef52ddeeadb7844261d148758d24d135063773f1092dd776abbfa9ad159eca169cb2582605964538172e3b30637d266ae3e053f108fea432ff3bd0b4e6fff6a060b245095d78cee7930b41b3e40aef794c4cecea412a73fa45a359da92c5c95bd3a91113260d85d36e1ee88a7f4c70e287f3bb37422fcb2f277cb178a98eb6ab8e2d68ddef930e7df0cf9c3e95b06f292f6b2b827c7d1e640d2e54398bc95301c8a5a8c42ac7cd69c3a3d91ad7d53edfbb19ca365090e21b7f4ede77c9f403114bb85d60680a47097f222bd9b6397458b39623dd8f19bac7f6449ccde49d5b3c5fcbf32d17e90fef5bc100d5a14b84369156a4e268660cfbfaa63ba64c33dff5ad5706a4bac28c7e1206f4b9398a02fbecd1e9ef7d145d1a04fa179b9145e5df9ce00441d14551581a7a73dedf83551b1eae5f4f4d833fc49da6dd083442214cb70d889efbefd2efdd820ac113b61f06bf3261ac4a51096e2d32e886b5c706ef7425e0168b0095b7e3c425fa6690b5613704bd61040c6e895c34b6918632fb1a5cdfb7331f462e42c597620558b1bc9d2e9bbf180af3b3a88312e3b33614926ff9717a8f292ee112eba22b5c6a77892d0e7de33bbfc59d4e3a53ec6635fa5152a2a1b695290972aade4b0e7a0c80cf934f11c636a2f06fdcfa7e3d251632bc6510e6d7cf9f84476d061867e8bf3be452790dd4b344e2cfe74c08526a478c3809ac977a990d2dd3ec0b70e42313276c0d04b89b1c263b21ff9778c8b05a3558d2de5a0babf2449caa471aefb378c1cb058aa885ebb7580a8865561c91ceec93333ea4f752df87262a514d070480a995ee635421ac88d7ee145e16aa8906007bbd45eeea483553f4eeb2adb6a0ab2d312a37520ac91b294125ca310f00a01f86d37cbe40d684498d59d3b37b1258eb314b6f188debdecaa82f323bb6831da829085b8997985cb6541e3cd4b0d42a621ab4831e376f543a87a33ead4d9aef28e6ee5ae75af82f58ed8e66a81460900062081be9a3de0c07642437fc10b2852054d8034e07906c7fce3ce99402321a648bb881f13fb276afc224c6aecc64800cd767ed2429db94b95a9c3e3f16d33a0d1c486dcb878714a23627634bbd2b606d031061003e444884274eccefaece6f48783a27ef07b6766d149e86498f6196cf4c540778b164f86ec8a71e4c468e3ac5440058c22ceb1c8ef20cb82eafb1938237c558e42fb814e79347badb7a9d1d01f42d68eb837f678662f461619aa5f74449c6ddd915a83e7d3ba32b03b765966d0d23e0d197fde7c1cbe82a98dc993273f6eafeddefdfc59e064bd75b99923784e386590ad6e13defb15a7c2ad205d5afc3a444592aa95ad8a7a448497d8d60d83bc729fdccb3aa6ea7cdceae37963146248546e162af6eab743f1663ffc1a2e56a68ec20e60fedad03a49a8979a505d5bdf06eed145c6185108eaa217cd99e2af3de082abf30484979817842f4cca3dcf48824f2ac2ae403f1157c08912f83176ca91661b4df7ab26de6e06145779bda4ccf1188b6b556869a66148fc95e2239395c8f7c6367d58655475b7',
+ '215c37320fbdd5520037bce5b02b12871b345bbd84169d87bcf1c134a1bb3d7ae5ecf0c6117b4dd1c90abc74515e3dbd50114f42d48b10b5972ea5b981d1dcf46d70106630214ef9d74ab559311223058e150ea7c55cafa17c8c66e8a35d5a15424e60b975981ef1b460703b58300a885ba85f936071c270f373cb681148fd04ebf0a568e7c605e2e8b2b2c3cfa13b6e42320baeacb2914d844b9ee2d3780eeaf0bcaa1a8e944df4f9aa46999d4bfedec81bdba1b108635eb87ca5fdefd7d4eeda1c367873ea3c4e71aff364ca189b0077cc9414775982cb166ea9626f4c99393077102a9db11c19d82880cc5fef59fdd6ab01ae078f34bd278a71b85abea3f27a3501d714cf337cb47fb67b63b781fd6d21e9186890c25c7136c7a8b9173c4241bdd127e12ecaa08f1b5d16de5a5b27c59713faa24674cf7edb71da933eaa510b7948c40bb428adf0643d48d9bf2fa4657348fabe97913fd6e238f5f01b354663d02d539a4b97ca60c21db65ace459cd51e50c3c36d63d3ffb1e4a2d996274ace2a4a7f97da5d1f669dc60b6c6fe4369e01f3fbb9af30b483b23d885497c684d6ef65ed0949c3d58a5d01ed148a569a4783f94ba8454109ea4c0a506c065c1d02884748f8801114546a94055c07e1f1580b295a9916defbbae615a126cb2f3cda5bb8366d668f034d2d47fa4bcece635a034cd1930c4eb27dea24248cce870ae7d1805f6ee585cbfc0ce474e9c86517d4d22a579f0edb55babf0080a5f8aeafb053666d06e43a93e970311d3fdbed364ee08b95c405cb0cfacd715e792feb52be4733053a4cf7849dc2f89a54f0b0e7509537320ad76701c47c3f66115c851b9716afd1140304c69f68ff9631f0f4536359f5d7796df759a034313f7468c533c529a2799bf2a98077cc0fb7dcc102a10e948f2c1aafc33f165d1092aa39f3c2d0e7d4a5d7012edbae54efa55f4d22fadaaecbd8f48512d9af5fa406bcb957ef3eb70dfcd119dafecb6a6909c27a9b864e0f72840fd82e4ff2a2b544b1ce38e3990314269020f6115675438b0b32b76cf21f4cd7748e5dca688f0bf39162e0c66832b2cc1c00ca3ed8dd46d2445cbcd54e47207a2a91e872978c6dbc655c95bf34acaf967e9f9eabd8093a8774e0f3e8ebedb81439c7176e0902a54734a4a0f684d8d32bbde7ba80de63e751a4a6a4ce507bda4eaa1a31e7465a793b06224994e020e534e1be65e6725214d9db9517ae05574fd084718004d4fab241e3bed7c1d0ebaf58f30ee9051d3e8bc7219793b193ebde41cfb34aee3d4c1800d46094a4dda2f740fabe8c04668f12c27e9362ff819d514a94cad8cc09b67221e0f0c6668eab8693feb6970bd6ae7272fb72cabf57d76f92da9d72c7bea28a4b1056b62e6c6f24fa08de5244f30173809f1a141a9e00ffc2a9145f07e67726276b7aac25fe56981d1e1e04d548f1dc9473748737dd7fca810917e9b3089d0f5cf944ef73ccc9aca34b5ef6e65ae777557d686d3f9cbe9878038e56f3ad7c0d93c29dc93f5e2e2635948671a0b3490a6cc7df0c596324304e9e61eff15c7ce774cf6b80b13deecf7a037ebb2ada805e8059bfaeaebb195cace379fcd29d0567a627985df3f0726f1b9f2e1cad57f53b3a39f299652b05e23ad8bcc5c1f87f53d2d20aa82aff21cebf707ede51b30f6842715e15a73c518b9f871391e4f652749fd9aba981f362b30f7f57483d7535af3f09ed6c9c74631f84f866aa631ee692b64361a81e529fe8b2d39fa19a25d1d6da0786e46b5ea46690329e5667f9a375be1816ec29a73f33517440328f4b4aa6ba7510c73d7f7c286c3da1de180df2e46060b1becb77aa5d946b2043457008e7875a755b3961542cbf21598a9de539a844241a662b4c472e22bf291be41b7361ebbf9ce9888b923b32e6ada11f06e189116c392c73ad806da478410493d5f3db8cab6db85185a01d6d95846dc5fa534f703ef657c823bce4c19f52447a25f01f1226d012bdd8e49a1736c834b848f6c208a4393154356459223b4324c293d2f32639ad3df40bc879d8cf603f1f7831aa82a5ea003f6bde956f54fcec93a7012070eaec821da6b2845a6a34d623126ece8549f10db14d93604ff365e414eae56e9743752960310c81420e2c40ec9f14f7ba9936a0d164eb816a1e66546ee3e6a4444c307ae6353d393bc430c7a1a78bedc89ca101c7374fc269e0e783c81b6d8c1e0c06bdd73aad74eb9328b16ab03a78595b1b77bc4e25e9f43ed0ba4b18e0ecce8bdd395bc6c4fafa83fc4770448b6012dc8a4bd832d6bfb24209411f64a98dfbd19f379863ea92119c94d1dbeae56c9d29d8c6426acb0c4cf37a606b872e374ee732ffb9988706d8e7d897d32bb066a24aeb2d237e6b9869590c5f5707d9b16ed480d9e4ed031cf66bb1e07f8d5514c845adcba2f71d2ab27da5850d6e11c505a06f0d42ebc69d143005f6079a3a3eb82404e7e85c4b8ccf662e1bb2433d39b854e9e2fa193850d93fbe1f94dac8ae1aefdac81c355c84671c9069710fc7d631f6d5a13400c2ffee9fc2a44ed4672b95ac16b7670bb8db22a8b1b77059166418911a931a26ca70fa58fbcd5c10807cd165a0fcf164c759aa117b4dd7a992ab142aa2fdd115ba6ca6734fe1e616796a772160dfe1cbf0c5a45fd572cf87a372cecb542a8455f8bb9af7a82a166fbcbd2fe93ea85fc59ee8bb9ba670807cb183ee7b1861596cee257decedee12a2af3da0c4229e95dc368b95ccd88d110f24a41b43d6e978e40272f75b06760237bcb173baf40aa9972174dafa5212aac9649efd29760b0a459e69b24bda0a0fb64ae34fd39c34c37ec76c332dfc477531d9393d38e10f371529d453c453f161a8c099dd1802640c1a903a486ebe7397cfec3c8375fd3d26de0b7985ce58751f95889cc5900ee2abf2e5a8c0c480df3b2b037176eab3dc0027ab20ee72d2dc710309b4ae43a9f5c98f2c7c43382ad487ce889ebf9eec36ec79739336b7a76f807caba8403ab9e78e77cf7f7bd1a498a33fe18c06998e91135bca9906a6c0767487d642247c27fe213434790d97d673b8067803f2e482369d5518f90645053975adf2480211dc83ab4ec532a492a9afeeacb3cb2b86b16db1efc67cdd9e5effa97467838102bfbd534be871e6cb03936cb8fcab5a87027e77b23aea33b9b4123b679ebb4a56b7f642b507007b49ce665bb2ba6c27f05cb01825dd0bb29cedb8510bfdb80515ae749f1389a50c14f071e22254d639c8a94cbcd117a60051f33a14eaed4159488b8193eed629413553fc2a9134b13917d09a8a3c5185c5e0ace0ab8bd720eef6366346cd5653c1b3dd4e5b87c1c5cee5b9e2abf0f16eaa4f02f13e76211b6d279662df3871ed359678b19c8a63daa13b4c6c4775612a56a8dcb7f73435fb7ee395c887b78fbd44e70b6b152482b75920717f8551078173f32178fc4c7987c8331adb65d3188d97ad7dc5efdc86259f9d10658d0e4d3aa636bb7d75465789f41e0ee5a2137423d5f0b807523ad8ec1bb9116488339a1f997b910e8bab36c7a9ad572c65000b47a7b8a37965c7ded4747c5cc59e4955f6f4c98b7265017d0b90e7def9d72045c3b50e2663510a01a553eed9d0f6d7e8885e2991f32dd3961b51d48b931ffe8b5ea6f9290c3d8ca9265f1871ccb965ba9d80a18bd708a6e8bf937c4744671f43df238294bd52d33f2041010a030e7c33fd023c61672004dbc1fee8f852d40dd70fd3b04fbeb869295ba0b18dbb1ea3bb6f8bfffceb9d74d7e83b1f8706904fadb65f8b435796d6d19f2531e33d1062babcc3f442aba77f44fbf229dda8c36d2f9c6e1b56d014a09db47888f2d10d4198ac54221cee64ab8ac3ca0fe08094efc388a96971705c51f76140bea4be3dc9bd07e39172feff8311086cd87ad52c5ed343b77c7d809370466f25dce04ec78192951b4a2d219e8c4291808c92f1b342c696425c6048e486f2a7d1e98dc7d4f17d1ea15433a06a508328ad34101a50210446ef120410751a63cee9ed95728ba2e76920b76ec38a563d939bd6db992b85f51e68a54f206eb400af18f1df97151b393f3e7cc5d12626d99bf37dddb66df501e5551d2bbff8dd331104fb537e99e4d968a3aa1f146849bd085d2efdb83efa90625d837f373b1b64bb5516d96e408631acf84966d2764653a280f323e9c51b0a5e29de33ce5ef9f976b44759b13288a7d3e562815478a5023105d3378f2be0d7a161362ecd89fc5b0ac998bb8d9672a5a411fb58e297ef317c93d722f397d15ff3ac935a7ce6aef23f3b10e74b94cd92e8251fd3c3faab4a4cd305ca5d32770a1cb2fe9e229a9626ddb2b7c6325620d667c8d3da41cb61b4696d671814245941e31c7ee208d03c60abd8963e8c01f3d9e9a32155a22f99d79b0805',
+ 'f9ee55f87ae8343e45f01fb285953c752c15a1d8927314145ecb143caae31e6f62022952ed0573bd10af7fb50f415e9b154a2fa2d5c1e2877251417c9cf43065fdc33346d30d32fcdea6792c7c81037a1381f8fbaf8d74ecece38aa417ae89c790da7dbd7227f962767c14ff157fb27aed6205c966ff53ac9528f99c6138b0fee4ee0f9d147c5157a2da59172260f3036d945df64341063035c9954cc2bb2d73c1a8efd0ff33c14328684e5aeb4f4e7d59c008688e7815df946d669c845f898deeb0273c7b75d28fd1cdfdb1b7724c507a8d0f098fcf092079bd7575ee4b4bb335adbfcbd26a0aa165b26e04d0f174e498a479bf8e6c685dae60c9bd47a8fb4f5c48bd644a39f4e2acbea83c7cf54fa17bac4e74d277bdfdf9ff6a5ed89d21c82c282bee2d0b15ba6e9ab33f04a663f0ea4e960fa4198d682342613ee95346866df51053c107f79272ed97f7b02b3b37ae325a784c796205f4d0b547c1f2f1f1e759757a4f5621d081605c4bc7ad5cdf8fffa29712c1c33e33526e5faaa1ab7161fa614b1e1f1bde639b0b2293535051555e74543d16397aaa6f9570ea88fb6ea580dcae788b6e22e045ac665a469ef4c8f6da9717a24b221fd03161cad069507994ef8ba3c2a106bf0645fe65adce2fb070db48e68d819c5b1d4a1a92a17d7fa6dea0cae8eb3cf0ca88e0d2fcb1686cd4737f4ff3ff635126fde9838a22c063f405f9538f2ec74ac77084ca667af512fda8cf94861f7aa947181484fa7cb9642ab2020ee0b4cb7b7f693aceed2ffd89f3b6d2ffe7154d0d8817d60529d6f1eb128cc2e423a5d0ebba1909c6d7f806387e4791795d0a64e3afa234ee6059ee5e723c41bb9f295c024028f99a6dfe9a89660012e83126489485603827e72d3a271369877d9d66f928d83f1232f76940e3728b5f36ac908089d2fae99806795dacbdbc9d1065872ec54c065d76bd6181ae6c908049137194295e174f2a0565dd5737dc8a5e3fb283416224e14f060de3531ab67b0bb1f00ddbf06073c32b1b448f4b73564d73108104e342a6a31c95f03844a65a62cd367209527d5c4cc1c019bbbf260ac748c8af769607b55c452230c6b4082538ae6a4b1a4a1512ae0f7fe5455c9facb307029600451c1560cadc2a653183e2749db52176a1d09ecf5d7e2f94ea8647f8f9e8bc08b628ce99f3ea667e82bf9bfee23f7a851f580799f3e57f103182e080639fabf8b2d4e9ed07746c77706557bec52fe1ae8b5255f318dd5d21f83c81329052eb3601c86d4650a4c5bac31d1f9c8eaddb5cae6991c4168e522f095c31f6c727022c6bab628b14a0f8ad438efa8084e3f2f45143c2f6331fe5a22a89f9b44f467a40b825d1a49c908dbab761f052f0f7addf3a88f070b8b89fe2246bdf5471d8dbdcafe0c178309d0c48e93d09fa1a11948532e1231aed830757bfabeebf7505ab671a813af117effebe9fcb4e604a5a304e00f664dc19a5a56ac2f12bdba3f47449bfb344f69badeb86a2b3c66cc8f908a36e6eba9e85490181f7e4a09142cebde9661ce87002ff5907ba9c7907db17a5ea42f12e487a95a406242d54ca9cba0fb1d9642d45950ed2a9ae2e7017cddc8d8d4529c7c23eb1155f12744f6cf7e1f108df341c5e9c02ddd44812b285e46f4af23fbb8df419c6dcf689609a609c6beb563f34bba35f0303f04ef0473a69f96483f85288c755fc823151993c8fd37f8504c20b14fc2537ca65896f381da3a161a63794c121397a8e7a31c83de0e445487830612f5238c9bd9cc1388c15dc90cbc5c6293fec0c698838f295a63a16e6bb1b51e0128bdedf61fbbef34b0c5aed29476bba0a0e17f0f8d25ca77e87b28a6755ec2ec79160a240eb47477ee967e10494efef2b71a23867b237a7cdae0058d28fcbf3564a0639e1d526dc2c944994e314196fa9fbf4695d3f4b3c9b974879862fb4d8c5a017cccc1f215b5df4482d4e2fb3e38c9657aa60e1600ff12ad2150b9f70841e7add858a33016c19f3aed5cd4d83f2dd291123fa003dc7d64fe553e745c7a169bf9e8aa2778db66978c1b3e9d65345a39b6bfdb204ab0d53eecb5ba48b80d4ac59a3039c558fe2546ccbf02932e983e6d6ad60105672896fefca56c9d865c7f12f34190134cd97e3b512b316c90d55aec11f739d5c5ae2323a2b6cdf933c223f2998f3577b117e1d3cdf25360389630444095fe07f2bc1a4b736c46d26ce8c9f2f19bb2994213f0ae9796d14492454ef47b24b6227accdce4f3287fbf8e3ae1729fd96fce6c581b2a52ab53501a5d178b26360a9bda6afb7e869dc12714330b2ff8dae5ad9c7ec1e5638222395d581a66d64c63fa7e10e676b21ec39f9b5b9759a112bcad5ee2955e5ecde656b7c0d8161fda4ac4f2593e7c1a3def8f802f16ae0d135d54201e05f3b8e1183ed621c11747622761b3ae63ed037dbd7d6f28298ba14f20188c9b8453e66e205814e575f8f166a2775e7ae7482240b5ffb4d110710248dd90f0e5a0ed8bb7a74910965729b26a146c4f59392beb49517d0db49c0cb472ce240976ec2f0d70158845cf0527eeef25c702d3f9f6b2da287bb64cfcad1c6f8a6812e9b6a6e009e37c20c9d0822b683f0e15457a373d8593825af4e2d0ce918ac3b99890c397f799bb3e4169b6dc67c8a7e3586a7bdfde3b177856cf263f7b47cd7a1e1b33b9cbb0bbfab0313496506b3b19772b131e4677a17aed120bd3af69fbb0e4b645b9e8c104e280b799ddd49f1e241c3ccb7d40e1c6ff226bf04f8049c51a86e2981cf1331c824d7d451746ccf77fc22fd3717001ee51913d81f7a06fb0037f309957579f695670f2c4c7397d2d990374e99f36408e3ea3f71f60825452f82810d80d9e5e71db95a897822f48470c5a9c6c5b16263d02e539571e988014852c13b2843808dc8e260f4bcc8a86ca463206da49824b614adf649786759b7b26f5b9d76fa726fffa9ca7400aede12de31464c1cf2cf89172fd197f3c8bdefd5a1f63b5248e21528d840122c1dbcff84f8c06a16058e65407c8c86ca55de3219b03a1ba573f808ad3569d5295b6aba008039d07b1b87d0f95bce1ee556e407e663d14755c4decff489eec5ddb011cbb8915784317ae254aa963f682c13f7f7a48360c74c83b9f2679b76ea3166d9bb16f3c290226ac879b9f3886b88d33d89bbd892a170f8b4fa6c35aa4d0dc4e911806d23fb343561c68f3b5130dfe0e145932a0cdfab6bf46e6d1d32f55a116a5560c922ce5122d4c3943541bd1b8009b394417989e423a4d6d11cb5eafe9683101dcd661060784af830ab011c22fcde5c27e57fa50369eabb00fadc35e39b5dc91f4298c94980eaeecc633955de9c87c7b2ddc63def85eea3627f4eddef671f08ceef5f02f482dd2cce27906e35a72c7c9ff2f75892bfd9195f73b3ea0c44f255929e64c249c54a3aa0bdae711167f70454ecbaffd35ed3a25f9db5652178fe39d3154f1130935aa1a8ed3c6559220ee63b93b6399aac03c8cac6fa55164c6a3bf91dc7f7913234e85081e253f52199aabaae940ecfef921208b62ac2d3085fe46c7e747d54eb0297ff3f4742ccacc1d93b07fb865b70a8088135eeb43ff404ba9400ffaa6106e9371cf1143ac80aadfa256494aa24776b339d0bee3444588247da6b1087a0cb134f115df044d0858795e08e0781134c061ac5ffd149c97b0013a4864e1af982a867454c8466cd637432d44dfb1310369f465fdb3ffcb7a6a7a45b1a626d5572cf07208578aaa5ed9e5a69681969047e5f3dd565e254f4219f8468eff3889ae4b1b80ad27318416b2d9407a9088ad56d6d898d665f5969340f3b31cdaa71b22076016bf91db78925496916d6707e6d49f2b1f1a56113fe271f4f207c2f32836e456babc31f8f65621860feb8fb4eb25a153e67ec8e8b9c41f94a9cc329d3f716467d32f821a8be6cc50127174f018eeaffb759018ec829cbc2b40c6c415af55fa3bf6960ca0b7a8976d4f9bb149fe83fd7a42ead0ad28e0da513da3d1ed1649381b9b6c2c3bf83025462dd6bf331a7a2c68e4eb8aab2b44fc8f16dff693f2ef80bf482e8b3ccbf1f863239f193beb55bf4fc21ea156f82d953d52d79c9ad3ad666f73698433b182734cc76139e4ef9b288760f0bf411dff26f488275e7227077bd4a389b1b13756488b9fd9ab9ea5befaa8480e2eea1b5e444d1d4b96aa6b8223676f2b9e25cbd1ca880354d8e98c35984afdc38ac25ebf5f9f88b0ffb41fa1ef902cab9411eda98bca985f6c56219393b7e8bd5d5a8696eb6450f3d42fc1eb42f762a65df62b320edbd575b065045d7fa7af581122f1797a541c90be6de0c2c005b7983652f30fb62431246f869307be72982040bc4ddb7eb731f4390f0adce93371fdc7a8e397345c31d7d43b5c06d2a159b25676ea317b3637aabe739e7e111958438c786b6cebbc5e2c8903cdef4ecc6a6adcf365100239a430d94c1a3afa1fa105ff31f8f55eed2c8f18707735a55c30d65ea22cfb8639fe02f3e90ca7e6cf02b18a761ad50067137becf1d65e58c943612613d05879cfabb',
+ 'c8c107930ac3ec654f043992cfaeff31552d8ab796374b18c109162f57f48e603d19dd7c1071a8e4b81041f240aa1f94e4568c3a6c929ef3b98768d29e8f7197f1f5668be1fc0bac8922770ac6a5817146477648e24e0db92ed09c134e2d8b6c0bdd098a266cff04ebc242a40aa80d10a388aea9a0747fb4476a18b80fd7c326b359313f86c96b3306790a86b3baabb822a29e254d0cde2a2ddf46898b94010f13f24374aa1c368201ce38796ae443b3eb1cac84911c116407b78d50676c2d6d502fa8ef396d4a39054a3245d72dbd47277e428d16ae00aafe7854d34e6730899599c879dcc28ea0397361b2a19d01bdfe51c70981c993443aac05dbe68ef0ab08b60bd93b25eafec6d42d88713cf85d971ba3c17d76b279e2da0730d7e8561bd111dad9fd9d469dd3f2ff8eee13886e1b673d7ab0bc45921f8bc29aca7d4a20192f9b3fca328ac389573d8dc1299a3ab1baffffc2a334d718469ee16756b503089ab8d44ced9fb9108a514e91861707829e50175c336790f69303cc557a7d0dc5d9976028d56bc78f13a1960733e51eb69a98892675c605e0fa59253df18c837974a2ab09f3d7342e7b9730cb37eec77437401ec7703a7eff0408b2c6c4c8b04bf33f7c954dcb4a174899e3849a1849e4fbae9ee82ca9427a38783c99fa1bdb64dfd89c74ee304f6f051176da654dee2f704bd130b2fd9a7a1f118a5d9b6c4bebc0d4d44fdbec8c613766b2779f74fc7d1e7f7e48091cce273f3c66bbb0a249091c9beace1de9491268005f005075bcf58cb36fd739f026a8235f965b40a71de67d95a698bd0dcead1f474520803876c0424d6a864b5fe92650e4e3e453620fa96a2ad256c3426258e5a32b7d38a47205c8b738fd465361c8503115fff1bb677b6cc234af356f4e3b417cdabf7fa3f7eda757a1e332b3d4b7a9b0f453239a6c830ac5964c1d7cdb80bb3a1b8f5e1d4ea066976ce018678b1ae6c74789f0e767eacc9bbed482504e4cdb45b495dcf8c0458dde639eff56ce1a8ce0d848618aa0d73aac74f06dd5f2ca2a056d78011d9305a4934cc2ef6ae5df25626d397d6c5f73dd608248e5f20e1f2fe310e0d5740f073420f0f7f08a179039b5cf034c73ece53c20af83f28fe9767245637761e57e74c4ec17e30b9ead564e41c64fd6888e56df52c24a9c95ccf57c9430e2ac592673dd5f882e478fef58ee6d1ac524948fee4f608444eceaffc4d4393dccbeb6512d06e10d81ad4325bfa0a3920c3d7d35d413b0bd1ae977ca0c029a52dba0e645c9c7da6c8443a397b2ed4bf7cd292dc931b3ac34739c2475f58f2139b759cf4a70a8b26ede13978d5a5bcb11aff18a922cb8bab3f80bda47a60235b909f15baa4a32d1db3725084ede748ca85b9c7edaeea9440051407f8948e33d99797171ab7eeca07b397fdc2367c0f6847832f0e79f0eb1e42543fc8402bba3a2aee0f897355f85168a2bfd541dc6726cafbcc703657069271c1a3a7dfd11ce9c5146dab49611e973d2315129270e662aa840ed746b55d491dfcf20bf606d264f09acfe4bca8c355bba97c2e9ae203b840ac94982d7485aea166a9591545713827f194ca3f858cf96e96737ded9855a437e5cc377d2ce63f969f1833a0158fdff5b95ac0649fb21ec09a9974ed1c4292fab034399837157877e6ed1038ef74c8c442806bae5ff9125bf63cc82bd65120f3ac5b13213b89e5c00e8673424bd68f2e2db4208f3ec8908b59fbdc2c6f07cacd2abf588a92ba04095682d15ea31baf8deb548389b48705e9364525614eecfcf1cbbf8e36e53c5fbe5f50bed09dba868e0be0092079daeef00bb7385cee7723ebfffa08d8ab776549997e906a8439b098fff535e5c72ab83a5aa08981d61cfc2647fd6cd24e019155956afa6f0f2fca2947f27e3c550cee22a3cf9d728e64d22b34283ea64541804cc3b4516096f31fc9647666a68be81d336762e8a18fd542853508d2d739dd9ea9b4d939e1a42a4df3e5df63b6d442c20716290f9142f4c9aedb1dede7943c68e6e9581854bf4bb1234cbc19efd6a358f8507056c45029d41286e5c459dcc45baeb19f815c60ce05f1f99addb40b905e9176d762ad200b0e5ad8df1a908c2c034bde3de94b0127a8ca8cda4395db804f5d29dcc7ce4b1eb4e23198454e2ac9ec58afb1d4b348ef16276718d017cf09a7d5b9eedaaa39cb7433317fc8c52134735fb679b827709aca9328c4f7cc7e730475d78c3fc36497d8d8591439a807e234cb7314281a40b15298327d4ef64272c1d7e3435b9c640a3f4c08e40c695759ad26761f88fe11a93a9124903a57b38f8c566d92a2b7a0a93408d17db57b980148eb2fda7f556c08ef386fac4e535a0fa07be6f8c987b2eb3399333fc971328f949410f36fc2d846ecd8842fff6b9e99cad2eff4249f0346da77bea8bccccf4b1cbbb9e8de98bee9c00c02a9c21309a457d5d8f348602a52851ec44703f0b6da4dcc9b394079a877e54d5b984aec23c5c41f42a4a97d9074b008f4a9338f9193a441355339d82d67d9070f89de596564bbf9ad56cc39ce5407c0c03ddfebe82dcca408c52f26b64027e38edd00dd57079c0f89a825374c46e8d0a7834db8130f038f860d94f7cb773e4d6a20670a6134e0bb680748f882e3dfb31af82156aaae054e5dab0fcdd59398bf11f255432c5326a7b8f2abf01aa158d2ab2adf5a37812e7ad01bf41b7d2bd3b326a1602a1118da3efd08c2b06c15e0c9d899ec35122f0b8f8deef6632a866bb408dc2c21a7cc77fbb4a831bc0f98041313a3ec79f30e0916f7726b275659bd5c59010dcc59048c68706f5d656dde3f18fcf7449b32b4c38b9d64d6ea990c64f6679e797cbd47940fa0acca5f1f2f0e75f4f2790b59b9b767f034de3f5b24ef2cd52313c54d0c0b4bd60eed0b9c20dea48c341e5ce06351369040c5682529b86a223d513870d86ec7810459fd5d4a3c1f232a99025f682d71ee3741277f815d38cf2bb648d1234aed220b7596eb01b3506a447d9e4f2ea8a47a86c5efd2d24a034c9cb778e6730c3739a2e48abdfdb0e2c2203073083d5f38b59db813c7730b742afed93b195e4f3048591b2b5e84d140bb2c564342fabdb9300abc45b61a1de5dad09021e23b6052deac8e0b353d80e4c5f75361581d40a07a4c36f8370dfde2dc9070afe9910c395d0ba1acea9e3c6962efbc6fefeb8488e4e0bcadb2e527f5b0dcff4798059f3e53f51a82e70d80292293f5c1530bf5dd0056b1c8c2262888f814908b65ff95ec44074d1fa331e8be8572a40829e521076d1cbafbdd478c3702c5e8ddebe58ccdbd90bde5b771d293fc0a2b96ed0d72a28ba13c997cdfaf6a716f4cd1825de05d214ff1778c63da33f6d9010014fb8748dc92bb3429452eadc47f40e8d1df3d050f936c47aa7e6c39165dd8e62a25bb34e05fbb5e5b1e667b6c84799642dfff6fd8f992d88a3804fddb06f78ba512ab212776c16a8ad2035dda0d3b6c6de6a4082de109acb417310ca57301930e58b3882256420b40f671bfad782acdbb79c7387ee84526a0927ce016107b8ede5e80c4619cc19315f22e2b5763bc5ca40fd5ab3c8db9e8e8305512ad6db9c18d9a8f7055b8d4a4726bb52b583e547bc01f6bcaf73ffc65f387360ecbf960eda4933c167f18dfb1cea9933a3096a7bd883ed6022f7d61204afdac5ef231f565bbef13216e5b674db36244d260db1a9474d4b0fb55d4ac9a670a346dc0a5ebcc2c04a11b73feffcaa8fc468e799a21930e7799110ac42356c0434ac5b7c3b8838d5a628f5051fdcb17fe14b8db42512bcdaddaedaeca59c7ff2f7be13829e01e4876d3d7541305d1a8de3bfc16722de13ade12ebc255d4706c25246ad236f70ef5d0719e2fa09c50a42328c2bb981c35ce8ecd85d60517e2afdaf0ad068961d80dfdc84e239925cab24367a72b22a0ac014657566a56989132a75d42557fb50c09654461d05b36c25bd58503f5a06fa66b8b6cd7efa8dafe8d10c6a54fb8751d609d8263d66543ba095fed839bafbdd765c46a84e69a539d27adc9404592067ebc1ceede7645d12433292d809d9f2f91a887dce7df9996ff8ae4d1cdd7bafdc2744a063c508b639361e7a1956bfd49878c5c307b4b2519983f4c7c989681df6b11cb4507f5948f8a2e12063c9758700b89a801a9b9db6ff9ad5b262ad2850feb2d0747cbd5ff997af01ea7e0a02f57903901cd0d9c1aee966d876b0f4c4323b51e947af2623b25d84084231c06e044d812efff11727229e0e857b7b0343aaf7b7ee94b062ac5c944a7e8f4593c29ec259fc9245fcd5fb67bb64298a85ad9f780b67c5481a03dd8228e938832d05aa22b4823b9331d51f8c95fee9a7200afb0876dd413ff62e1f6f47d3a7b0333f10b3b94963a55d2f7855c3da21987c63a5ed20d7705d9d3708a5cec343975078b8be91d8734129e9ed096e803b2642bf856f30ddba69b825826be64274ff2ab98a8a63b7d1303d0d65f2bd799d191a2783d8cf77872dee017408b7d7a2af69096e61586fe73940a2ca56d94cb139aba2876e242e3f6fe8d2c5c5680a3570b6714c8998871c26dbb1037ee981dd4e9e38797b58894af84da05fea2263950ab9f80c4b4a87d7beb541f8b216a18b1f9af1414592111090c67429bf0c6b2b4519a696ef96f782c8775a913a8833227548d6c715fb4cfa',
+ 'b00250cc952f6dc3042600e54b896d178c8484f5bfbba96afa81327df04b116eb964b302d1e2281b62d8838bc6cd842a476d74272a7f519bed172b64cc0dce308aada1d86db0cef08b6ca39c444739a4107153cb7bd3885d6d42a508aff94decab46e2f57383a969054828bdcedfd3ad6cf8e88cb89e98d8046a6711a1f7d5cba5953e03ea42ffaf5ad6da986a7d9c6ce56afc0febcac73339f73a28abefaff5fe047da7dbd519e9117c81d52309da0a023057ff1b3e5e979451e6f5d3c9249141fa668b4d233f40b3a4e41cfe6bd6af4bb0c10251e2a42b9ee1331f236d7ac8f3dfc2574816b8dcc7b5cc13058cd881495302c0949ee318de0de94fa3c3f9c19e1a59b3d595cee4d51701653f5227ab8381e1e3ec5a6185dd3ecf2c5ab4eba5c915f345fa89c78066314bb8b4a60d5382a3281061fe689b21ddae5f5026969bfd3758b8c1d8ecda016d72b56d71d0a2cc1f9df1fc723e8134504e8f8d0244ccc1e84fb2326b85172e323d037199b9bfeb5f092ec49e2b609e0177651a313b5f9d90a2db542ada6275e9754ac80810d267c9336fc26b7960e556f188fe9ac37d199717dd2ffd32e15ff8e2347ba41d05c6c7e55bfcbf6ea893b983a24124264ebe66775dcbcdd7bc73c84c679157277e92c0e59a7c8454612f91f758ecb9aaf91363890631800f1c39c17b8b12077865521cfcd54aa071b242461354054099a7a1f7177d680023293a4b3749079e56e38f42f2b46cfd0ec45340a03e97a0397fee8ae76d78335b0afdcf474977030a20d09c8fdeec8172bfeae665bda7c3d3aa8485c37c6a03fee80bb374326a1edc439d919bfcd116e7ca90a22c7a3f90ae4feb4e7152455756eaea6186ace8d713747e89ddb524a3b30dcbdbbb1d66ef1497a94fb9981116a939243f4561fa16f9ddfcec1eb2ec0f1fb126fadb4d25c84baa48ef65f6d62a40fc41b778f6a7c3d4a39e23269a314473de266554b283039caf50953b139d7a635cc730e916f8c6edf1ed94bd16fc29f7bb5585eef588894fce47ab05986dee598140125e67f3078ced70a8abce54a6f3713ac271be3c40ac31b798892c4f6e6c9233c4a091a26ff9bfafc7b76941a3ae275d85a4b4a811fbfd27c490784ae2e2b729b0773d0de47b90325aab90cb08710647345080d3e4835d2097e1246632041aa93daa133b4f5b8882c74deafbbd84367f393dcac5a28d77297946d7ab471ae03bd303ba3499e2ce26786620d8ab2fde8dfa333987316173cad2853922076c3467da48db00a8558ba6d3bdd96ab8ba27fae1fa75207b477a8b0a67f3d25b413cb6ba421da866ffe68b421cbebacd6c384d545927986787b4f589b4adc42be320afdcb92933ba27085b2c4976cfd38e3a0ebd1af7f8dc68488fb7340efe609809dba675a6a98b1418a1f90daab2b06854c683038c47c4335ee1fdaebf8ae0a91fc0813d3d12c30f3fe2103002694e42affc0edd8f8d06312074c1ec6870955e89e8d6da96774960a5a8db7a25fe93647238c66fa7d28aa7b4cf6cb4b0b666fe70db0b1558df054f717ac1b3bc786915c60213837d1f38e0427b67cf3f663ad3fb1f8ab42b53df24cce12aa26ee0b79fd3e35ddfb87bf823f3fe1905be87fb23533eb97fb9dabf26dd647e10e43d6548c0620c4c01efb2b7eee2e91dd52290379fc00240a77c8d9ecd8b26c5c6975a59b608889200824ee55cae41e12b3ee157082bccbda04131d4c3de8889bbf78019dc5b39795c3cb4f565eb881769e3d6cab6097ebf4a329310e8e60d246b64bed25be588c9be25cc2f30202588361957dad0e1820e4d569c9a632a1d5d7fe6fcca5a2edb49cd467fdae6d582fc3be94ccd7e3c3f7252b632b95d3221fd9f85224b02bc9bc232a6b340ae93063b205a9deceaa11db301583eb7fe877fcd724a199b7a1931fd944d51a7b1e0190c8c75327f399884980146a9da6db0a192a13cc702ebcd03bf9c444258174723382741f3ce96a9dcebfb88596bd335ed17d36315ca7d5e7bd3f2926c9b074d8c889ac6c920275d8d72962438b1579fcd23b1c8eb39575600003d3fb9b8a97cbdc18d0c9abf143bfff67b242df62275a87de3723299a23df90d255410f6265b1caea71c50f186cc9b3e518f1f805b3fe6ee1069d0308599d0c354d8589ea672121691fdd1ffa596c714c16ef8992b86ee3ee0b6af4729f4ecea6fd37bf8504a08c0f3b707319823ec3e73c89f87bad02a35fd60b525b6d5b54a214e604c4d6a64757353d8ce88fb73850ea5fc922fa8019a0c6fcc1453c593aa0f4fefe2c55a8ffdbcd82e209ca4c2b13b0ef704b393db37b8ecdb5a284beed3e4e11001dfa3f220744ef06dfda8438aa10978236d1b20d2a6deca405eef2e8e4609abf3c3ccf4a644bd06fed28f5dd7e9a1673986c73934814d810e1d39bba1ded1a8fe9a5dfc56d32e571b44df7762badbac8c251f8c25ef42e70c8cb2fed45340ef6b8cdf74f9caa8cd0b7b22fbf1bdc12f6473ac826d98c3e682d4e15df14d5e6982c0d9c357d0344f189edf504d995ad90b98f584d326db65b71c4e41be7634fc8a5fd351388ed9c688d59fde3ef7ae90c8bb83f8203e8f4df48d82130573c991cd90558664ab9f18a44ae90d8c7fc63de204dc471c8ae984814f04398cef2611917ce8caa2d08e2eb4224545fed8a9c9a29c8ada8fb2f0f3a6895c1d1c9051621f4a1385bca5aff000883bee5dab5f1a50ab1518415eac82ab6413257cfe546ebf235f1f78d10946cfa25470719ff11a34580368fa35261ad707b0bb76e2371bb82f53009ffda4196b981733025d66af95ccde3481df65a1739abb46d0e4005354957790f9d0894f1a930da0d88cc6c3bd2f2de39f057101c747bd2e53abb9fdd97e53384df3bfd225bbbc1dbad51a3df2a879dd1c4f53201b343ddac7e069019011705e650d4e88d437ae1372e069057d5f4989c06412e8b789c3b4f42a1947c177556c07c73f5b6e306bebc654bb03a67d255152edb63fe26fd723a132d0b6b4d78ac8fcc999323dcd790b7fda181fb42a959c9c91480fe60e028f98a09638b05a98dc0bba64f4873762dd65198941f18d22d364f9cf3f098dcb609f1b73b4ff28060efe43a98b9595aec73fba1551a3cf535c73cc53b79414bbff7f4b7013e7685cc89c0b6fdeaf10e333d764c5371317b1a091b3dd5fcfcd58d200d9943bb1432371acbbbed51cd08b88f3c0a0db898ec3078556731f01de2d42e96de815a4e0e270f7fa9e5826fc2d2e5c75ae254c5c11fa195c20df736fbfb804ae72890a68212f4571184f13bc528dda2cf7fea6a823df136ee9876ea9989a17453c80290268155dc733a22c3a810d348d844cdd9a821f3c33d8ff38b33f51ebd94ee04bd7408a09a5f83ab99b4216343f5cf93a5cb5235c54f42f19b63c464813ae93b60e30f60fb36dfd020a1d10a0eb87eb051344523b7845ff5bda18e0f59b667fb2d0c1c238989cd44ead9b6341380e0c86eab813a048d45845465a86bc187e8e894579544cfd8da7e7ac4377dfcff842050797d0556ba8201e238aa26333fca78194e3151389475f13309eb442574d77c9926cf0208ac9412f98309bb393eab1e4e6846d55e5d2e21b613283317915921bb4bcdbca4d40a1c0ced5d974e04f96f862e6c5d9b8361a47668a4a75dd597b439411f81b5b142a18ed00c46ec4343d0631908368ab7beede682b72d62a211a895cf2b1da5d4dc2811c3ac468e5a08e557a0a11ca66aa452a8e9f641c0973573431e86dd1faf45341830a412ceb9b712f66ddd5c790cb0971016d870f21591a8e3d7a95c6db10c4a14bf8a3807f2eceda1d903926d1e421fce81d42771bda4bdda8308f82a8a9fde99c8c522d495f8d9fc6aba3b1d3ff75136c37ff1b9efed26a9a92c4cd08c8e6619d4fb6fbf03896c689b67d2e3b23edfdb54425c453ce977d3a299c6ea373675177c837b11dc1d1978f3a2e66b4597104eacc1c3ae151825eb07c802f22b5680051803e197701275a00bf1e21e4a8e96e33554b45f2907c542513d6d62d93d1b754fd31f9a7007e5604cbb52773183d84b9691cad2b916ba8c177072c6b178abea8c97a1a54c6c0d4c1e85b3f0ab1558ea48ff639365e39a3ab2f7cf985487b5d746c7f44275cd31c629d7833517c19d41c5041b3bbffcc8a0cc39c05222e8ddce06caa3ec7c9a1760d7274c9ef80729d483266e1617a0ea80bbcce17ebd2a682165362d2de15102aebf0b7ca8dc5463350bfcb8bd1d9e544d1a17cf9883baf983ba80ec611490a7f239ea9fdd2547fdc5d7fd97bb3243ba585fa0d71a07191667af418e30a6b76bedd05b32c673403e197f9f878ae61f7145050e948db7d3234f9bee7f171863b3043ab3b1df36dbc8a25b591496a9a01d95a297846e3667c4ae08ee3b8ed9f431a7a1aab991f08901e2f3b0ab790d6413cca1021325d3456ef58ec74ff27c075c7adda6968930c69e7df14cd8ac81e9f85c88a4fd5f4f0a76d89610290c7f0b97e0271df52f6812e2b5bc7408ab97903fb7e2167f84ea1590a9a74f5317438f786a169731ff070c733cbdccd7e0cef55e7125cd261134f530fb3aeb5abd69e1728b34a8f962be01b4758dbdb3068887d91acc3f8d9ec027dc4fe96aac6962d02ac609a9a814cd914ae2a4dd166764d634175df412781c3bf70a0b43d495cea9e5acfe3fca6fe6399b268ba19e9de45ef3f943716157999015cc490d4fecfdfd47929ac1ccde78793993aa81a8147780ad23254dd697c8d2bd190b3d9ab98138d53957e64c0af4ce8acc9a13cf559ef9a4477bc00ec34a625152ca4b2195f8eaf2e3ce03b46ffbb81',
+ 'fcee0a4b7817f88402166350bbda8ac2f4be6ea3e6692c72a3f289a94d48cf4286d2d87a275268d5350fc06211336f40ee726c6188ec628e14554bab7253403daa278f2996900fbedcecb0f620a156f977bbe8e31ed7a3c76c3fb5f40556077751375ae12c99954adff65d954fece7f675e30ab20ef0992694f9ef0b6c1acbf861485f285134a37e2672efc608dbc93ed230fc55c200eab274cb2278116735c9c4a3c6896d2be1649aab8e12b337a5d974ebe354a0ce3e74f4fc76c45a05edf16090b889e844f60321e86000b6c822d0455bea3812243e72fdd61276b1bb9a781f565db22b488b63a47090187a56e92a2bca36887fc891b6759f1f167d52e467e73fdc8b9cfe478d0c8c44e267a9a1ef107ef2cc4f83e04846a0c42d269375c5a2915d9ca430d3883f84a5e7e688f328dbc0448de91dd32e56212a421443f29a37950a6eaca4d65c27a0daae5dbd87dc74d85451b75e11728f6a78ddae2d06ee8e9309881a23f912ab280bbf350e0413c30e4ba3200e431cd7c2d7865e1857ca8fd382725775e4b1b26362a3d74413d5afaa51088cf4103218736fc68ccb8d35229c9eb5cc623e41269a04e1a9275b2b22f38d0a63d921be39c367249e0f51382f3884d8e0b2afcbee151c01157e851c043228300e851dc722fbe829fdac4bda9eed5e63fa2ce155f21cd08c821338b13bb04a02f3c0ad56bb62195b116a2223570451df849a79ea1af7480958ac1df1b0b219097b527972ec42234542117e1b42c487d3e5c2228f4eedad00fe12dbe44b83c0cc0e0228239de12d6cf96809cb487728c7856c824e764727f9de0d1b92f56a65d415996371b689605a9c38683a4f635b43cc62412e7a4edd7d5f64850494ae31a7f6e0d1651f80e4969549467040d249d0226b08384247f813e9e1c04111984bcf1b9c1b06c00ee0a84a634976040a1af5ef4e7f72b67d9f44e44a75515570dbd4ea98e85d817d7c19254e19538154f53b9bd44de6bf37fb97b8684b3d477e0b3ccd9be1704b13e26f8cd15f0fa1f702298ec51a9c43bc3494ce03eb0cce0901912b6cae49041a3735e9b6c3b34b3d6b4730e9909a2b5571c38ce3fcc6d45be55a6cd4f6f096d8a6f0a3c3ec46676c551dea0755ea604adaad5bcf277440bae020f79b616be796542a22c183d0dccdea3422e91194c9e399d9a490141cfa6f1a6a368999c4e19b6c6ace772f5a94a8521341556d9e4d68d3cfcdee6ac9e9c1bac0906543036b3114390faf99ea7645b542b0141012d620b31840b1d280f7fae8aa6df90a2e6c9e741e4d2f698b6aeb3a4ad6eea4f74b545e3b63a1f34b0b61ceb1350b934fce2bb6a1f0c0464258e309b21aaace56934cffc0a08676310d3d915c5164896d7820ff4a602ad81928764b02e61238369850bc305e27023be6d75c3427cc929152c57aa20535c817c2e928c3a1ec8a9f41a8bd12044d406f7c7755c0200b56c244614c3048a9be440f87c77cb2016b9a769b2beefcc0d7d7b864a488a4e87f08363ea07c8f4d61a9f59751b58319842d1f722e4dad48707b82e872141c2cb26b10a29c0f43ea5a4d5d60edf67bfc7d632576edb57fadb361c349e7edee9f99f4bad66870cd485039302bc4c80271fd416eec91b1dab6479361d02a9a8409dcaa1c222d27932fec735440feb28041acd1e31f41c6262dd51946c564a3453223961fcd13bdff67d605b3e7c23d5d34341a6c56267ecbd804f95870bc9198e215bea92141b978b7b5f6346838ef02123a24f2d86860317f7d3d81185beae7e05a2ca364e0a365e9324fbe0a8953d5a369f85bee2ef4c1ece8eda807683999f59be8f6df170430c3f4173b17ddee3faf669d91e0a0c3e1e6ec0fb5830c0316e980f888da0f63400ea45692d55b4aa9fddc1b7af6e854fa3431ad8fd56fd2c584b066439def48fd91e915ab8d2cee7956717b00782b2f759f60ce2045b82d108dd43a0e6fe03bcf166c5b6e8677621982cdc40aad94ddb8ef217b4f1a109d5ece937ad09a0ac51e63d430c30a652fef4999fe7fde48e52dec1bbb049e9ea9180d96307364946d5242ca9c925f1edc65737d31495372cf3b5df79627178bd9a4138463de16a7bcd378f6a8c3cec9f1e1c720664f543824490c5c14a1cefeb56ba8061cf9f76a390ad0ff5b3e9f8ff6cd0e2ba57929c26bc1bff33e580b20c6d593c462ac51066c5d118ebeeb1a9774901045f4af19392c0a3f641b351618934b9e653ddf6aa2dd35024ad7b2870af39295175dd96dc5f08c5456b320360fa4338f92b57a8c6715fb6ddcb07c2d0ff93b6549e7df6e8d3dafc5710f02b42d82f62ff2d365fd7d9b1518eb512f55cf10f347829aa961ba9edb5c5e36c1d899b4fd462e9e89050bf7edcb20c0b54771bf22056a7f2091739878dfc53047ea7cc2af9ced1fccee39b2e9502307f44b1e8f3065aa9d2a45e1b5ee174d067a32fd3573f8d85c17fe3153736e9b2ed6a9fe068530eafdb0c42c7ca5cc9fbf44f84594b324965f537f1862f2ec303b42a838ae892dd1a59b577b7506c663638c837b67d6e6d03066b71967ce938b381f91f50fa526089fd146f62977cc40fb3a1cc83744072ed53aef59eb6e2b542c57ac5caf3fe137f33cd9c71f61a8de8e350b548a644f5758b56e03763c7c3220d1419618c12805a7c35813df2d20e624679846eba085f4c0c17e3d8e9f4dce1b7598cad291c11ac54d0a05f241fd00c5b70bc7df5f73ac1645652fbdff67d0252bf9216319741f54c438c2df0706d37a0dabfef00adf2861286c038ac593df46dbabc355bf0bbc5d0f2a752ee505084a51c114a5079210a954dbde7d5797a3876df7d730ed4c98e71628446845c0463e6b953086bf540bf7b0faea1f1e3bc6efc925857a0a015cfac17a57148e01365d446f7b1c9aecc15224104ff78249ed87d87df7bd7ef0af9ef867d7ba288e80afc2971dee0124dbc29867358eec87c25680465280b0e23adca338ece37b2fcb3cce543d855ac2014ff445c36ac2bfed64aacac14c0a9ea5bbaa36bd16efaebf0d51f003670e8fda0220f321156db716b93f4f6aa8f3ee9744f5a673dbecd2052931b1981e86530fe205b978175638e45e251e751cd398b87e6cd335bada62459858e0243229d647f789def0f6e409ff5a467f0b301365b171f8042c3c21272663acc4ce295edf2b4a95acb03c7ef410b588b9546d191d2a257f8080e829e9519117a7bf8d8f3863e21269e1708ebfbf77d516775a4e88caa3ea9058465a6f6e2a80cf1fe523a796c8e65eaa1b7b33b3a914dc9c801a6d3af2227cdcdf1d832437ce8515ba82f56c02fbd334c4ad1895532d54ed65e696221a0e8c363ad8eb1bbeeb11c99314ea8f9a3710a6f38c360c7b07c68f9318c9282495088be0f570fccabebb64f8404da497845c29318054c12b8c7aad921acff717a1370657dada6f602fcb0e7171e85602c901e504f13c5b6aa3b76de8527035fb1962cc29f1f11b8a2688ee870c814ae2ee4501f747b4834134c7f71f2a738bd8e4d108dda07da94f8b3c2dc17ae12b3fda71a68fea85e1b628f074bf08a2a0b7eccce0fc5145c0b8462df2a823d09f2277ccfb5642771cd4657b0c4e56c31d9f189b7c0d6b1209cb40a366c26f154e92aca029d3b851dda0d4b0e6567b9fa9995085059856ac2c925fe8b19ac77ae2976133578eb2ddcb245dd62b5edffeedac7cd3a32679dbd0158c43fab591c500397ecfae1099e18f67e93602efeaa890e085ce7d3e3e679d5bb0fb699d36bf5281ecba56e0d626d0715e19949004643b3d51bbbc680c173d6cb15928d91f308076913c7686cf74374ba6c509c995fb96ccc9e5872c4cb4555079a55cf1b3e03220569f368bee926cfca783882205364894d59307136406900fee27306d59960f882329bf769a4a168c4b9a3924bcdbfa9d5e0c64a4bdd593b2fa26cad67b1cbfb5e12439cf3a62dd047854455623b253f04a99c568bfe9094184ec52b48038ebaf76d6cc1f38a36b6b18f7d440a085fc94838252e5d20a98c273bff18dd0b33b7fcc889eecfbd565c912cc0d6b9c1a9c91ef0f35a55fffe83fb1e8ceebd354562cca81dac1ebc076264e1b195e803adcf078889330cc91a2bf25ae1355f1e5e5be570ba623702b448bb42c20a1b2ad64b80534970c83886e4bb75be554922c8f3e5d6c2a9cf2e077ff2c4649bd9c3bdbf17d5c66c3eaacf3ea4f366e6f1ef3fdb3c3ed90b3d9a5b88b9eb2bc39a4aceaa4ca482bdd6bc4daa4d586d62efd00d62571d6fdf18d43af36f2b9a29d34c738d8d3400ce06d9aca8131944519971bc39d4e6f9bdc7682030810a12372b3556e95808c315658f46c8a4ca8e2b9540e6c2144ff92fefd295c09e0b2663f891e33e3b973c3c6939b68c60c09d5959da078bc3ad00adf880264424b36948c1dea30cb663eeab98857653e5a014735d898907319282a0581d3c0ba3773d4e2d9810c546f36cdb69eef0bf81fd660226fbf5b50c7501afa4e651b798eb24fc724ab7087bca095453d2d04fe41d147e3c8dd825a2d90034659801b88363b2cc6662f046a36c769eecdd7f558aa3a25004dbaac99332f0d6f08eb68ee1956946408d66f08c3f2723ab6b6890c40592102641d8216c2775fffc570abb31d4baf2b70685a664c68d8b061926624ed75647077cfabd8c0ae227ef7d58ce02c61a4a207ad6c8eba72c2d9343334a797d815d2ed99d0e7171d7d7205e3b27c2de29c51356c4e87f358583b98609c9e28c85db12e41994cad0c99655962c68f0714bec1636fa759e162c460f6e34510878e6493a28fad0e6cc39dde5a1a6f22a4403379f77c200d6bd82bd0b482d9059c725103b14db5353a89b26670d3563bebad22015b5c61a97801b8113c06fd864fbb4c86c34158ca01a80084035423e5c4a5b4e2f5d71138f22690adf4365b9988b37fa640343fd4a866aec07b667d25176e11a32fb4d8bfc0',
+ 'a24953a800e0b73b4554d4be70f6c1ba76383ebe38ca47a6b202e91d758155615714334769d8387e29a2fb17f99d0445d035266230341033582b1e6dba147578af354e726a4892772ad8a820b4ee8a4901ed1f1834bbc53bcf212c7025756b4b13764d34eb77ecafb1c082e08a317b4e7128cfe72ca58e447eacbd2f9feb6062e99dd892d4ae6fac2420325f61adffda88aeded7003b94d8cf9476b00ebf7c469a7396960d3543f8edc15fa523ab3c77ae46f5f098c5ff7e29a001fd5c3ae67e8fc030477e548f1b726bb2bbb6735dac4bbabc3bdc8bf7bff49a061e6fa1c7922f4c4dad10537b9b1de5d2a8044d8801c7d0dabfb5d4a32199482c19313f460be1de96d1a979310255f96974f381e6ff8a51f88409ea2b2e7e721cf8885b8c700f40b3ba320fd6d7816d1c286d569e2dfc04bd93c213b86e0ce27ec35e3cc04920384b70945d95a30b0a95ca5915d81486b3d2f3c6987268ab5ff9809a2b0b1f7c8f06fcb5ab94ed5a987c659e07be3a8e24deacffc180a4c4b03539247095788b0d8e657f41fb3dd6df78fe267175297e208ac753d50aaabd9edbf5e45385dfb47988b3d966f31be7a6329fd89e2869bc6f7e4bac1e3a0300f193bdc21c03d9629c9fefaa64a410f5b7524f9cd5fd80b2d96140f1e23636f3710498a61239f0fa3f7920dc8135a368d87f175a5d1cf8c626dbaf0a6a26cb00e5d78e787e4dabe528be4e5606ce5da8d261fdfa7fae59621d969fdefe334a8e17b3a720a867928b201781003b99c51d6da10c6583db29ed88371857e5853c04cd41ec86d8b02e54ee2cc2c267bb633070e74981b1caf2cf2d69225c694329ccd0296492564f06a95ca41884d35fbf47a5dabe3750a43b6fd4d2c6d6d095974de812172d696da3f030278c2ec8ab62ccd2237270aa908d37471a0bab63a410efdca40e3d5b328b93335f25a88cc7d325c06a6d1205b76f8e4deafac46a981b1a768850ab72c548f82df1ebda67dc9abc3756b806aa4169dcadea99092d9941367c66e560f74f6289e688e6ada31240f7ff8f5a35e155038a30c1f262f3cd08abb7e5d64331f75fac25ca1f0787904c40dfbe5b86f21bc6fe9e170db8065ffbe2efae2a3b6ae6c9cbb45f9dd25a7f46fea08bc4e024bc39a1bf96f0f1ac759f41ec69e932e843274d59f068f46506b6980a9d9c2dc060e5db5ae4a5f72e387e3175bd1c0ff537029add258957f04e2578e59deb540e2e501539a934b0d4cf1f1b5452cabad7eae11a07a507e1427f1b05f932b93d564f04b5228ea306e5620a654fd1fb1ad6834c35a119ea7ca5c01ea70e050fd0e0eb8925de3afce0ab1bc8792fe2b7193c2bcb5371283b0f5f39b8c6ebbdf4f5f32965cb355747256c20e0bdbb2c079e4f09e7dc417b0181b91370ca59037194d9312211ee8a8abf7199da9bbd58f29259462738d7b944bcfb76ce1c207f8d95d82c475ed37dcf9502af3f7afb0d81dba00914cffb8b0ca76d895b2208d850e039425d19aad81d8f668995c13ff4bb626d7b34097799622a57759e45d9b7c25d449aebac3c427d95e75167da4fb5a80f07c3124f128a4d2d006120ed5ac3ecf5405d797e5164f58fcb2c3a2cf3f750cbb80b33079307d698b176678354a5d58e77b290f7b1e690655b44981ff562bc7cc678219bc3b70453b2dcfd6d8f0485112fc2b77f236f5300dfc1081b1c9ff30b7a3463716a43df474bba6a15d3890567b1b4767e70a748469fcb13882f56fd611c6781f350526f5ac43834e1e8dc5c7645b1555c60387620e2883fcef72ee364f43803873c8dbd756480e53b95a4922832e1dd81bbc7e576f22317553cd0acfe49d07297f79bd08174b7048aa389b064c26b955649ff9e3115c22086c5e460166557568a4a26b0643c081a36db35bd113b780541d285a837948ee4c75c1108948ef435c8fad366080499aea7024dc119e62fb6ab1d040b72b7aeea81c7ffaa5f0dcf99b9d24cf95314924844e37cc5630bb92ffdf322d0c9c54aba1dcf5751612a1109c59939712fb31c71774568cfd7f23df89d1c87fe23088cdd013cc102812ce20e541641d7832b5fafa8efb9ea5de2e49af560dc9d6ac69ada97d6e4c7a75d692fce120d3237c2828d3daa181bdd25d69c6b87c9b685489c39466569a7bb03cff49b55458a32c1ad909f3e2d6c3f013a866958f54f5cd6bb8375b0f7aba6673be523a790e75e700a4236739fe46bbf38e1569c0973d7b71e3f8e8037d94ed1d68bced09652a216be2a6a11168b4aa6fa349a1bac27de35eff5f89dd13b9c88c86d059700d2e6fcf4d0a4df3c6ac200a5079d9d87755996532d1bcf6cd978d638132ec67670126bd2bd4aa6b688bc1364e3c6ea4264302374705fdeb0b9cb014e06b3239f330fa98078c62e2fee21295d4e7fc984fc4b247a452c9147e57b5234cdfa772423c3fe27897e3f4da2d788b8d23004f54692e18d35eab1d66572110ff06d89ac4817b4ef79dca1b8aec8789ef73f613e49eea1d3c70106083f96860af87223e0abf312ffcf461b1919da43374415a9070e45ae7783d958827dd1f94a6c730b6853405f8013267718ecf730aa411aa3f79b814e9ff1da6fef270ab1fad4d70aeb48e4e499cee37b5c2e06862076b619b7fa88ca749a6d13f54329f740b906e81293c9e97387e5f08ee7ef8ec06c52d1ef33446a1d05f80b3cdb151344a1686d843bd5b535c6949d55209a90b3aa5495464c9b75c2bae5206df6b84d2f176561e948f2920813584c5ae0efd320b8262b644f77358d429a1a309df9ec29a0658bebc307c614f799c3452fd6a1301f2e5bf243bff0a42481c12cd7c03a59c6d0b430b3fc9a80f9bfcbbd1537400a66d6ef98315bef2fe800dc0aacf57de8100ea46020b95675a2322c6a9bfaf9d81585adfd20327a57178135712481fefe068723241225421a785aaef5c80714bfb5a25fca182ed843386b920a484a05c131a3f924922aa69805a01906a546d9e9cd97cba62236531dd326bc7d1f39da5c18cbed07a7afc916d16a3444389f907ee5cbba3a4433310a701a7b71b136c4f54465c9755a9df4bd6221c8588bfb80a585a6f32e880bbb3498af968f072a0cb53bdb5318b2da6fb5422e9de9a640f28736904029f6a739c3d24dd77e28fbfbc387935495b74f8225f9f77ba08f582e4c7ff167d395eff3dfa75c04e61c93d748aff7939771e475350de62a2550297c1be93131d56dcb30ae9e44e671ecd8e86b3c6ddac4e9828f0f0862711ff19d241cfbb866d786243a5b3f6d45c59fb47855b55fefc4260f19d872d21a37789b6d793defce80e9e0570c123fe98a3d0ecd2da2349b4030745bbbd1a0eb14ef0aa157373c30799de9d0eaa0239969cfcd301c8c54a6e0109a9ddeaf33ad5f5dd406e172085669aa25ed1c707f1f095cdbe94afa27d84bcd68276993f327bfcbe0e43c75bc06f9197ef5ca0abc4114ec1f40de7415f92a6fc54085064823dd40168593c8be09d1f1db321ef743c82f88817d008286d024ff9b325a8f9a6760c45e300cf47827983a23ea3ebe7b7b0436e9e7daade226e283d1430fb651bdf15fa02ccf805027f7fc406652e7cb243b003fc7917c91c30b064fbccc03d5eb381ac4c205f4b0d3954019ee83fb9d897cee6b655078fb6f488dfde5bbff8fb9dcfcb23ad6d9fff11b0d96c9f88158746e0756093d2788f24122c3050131e5f1860e53dc69b5a54a306c9f41db0163abb953e6fb8013a1139dcc8965c9214059dd578defe7130aa67d641c9c510328f606da048242c4ac9b0594374e395809bb8adf49bd777896cda9fdf52384100e1ffda599e8abc113532080b506795da6dd34ae708c426eb1865d3f131e9caf7dec45bb8b73e2923c00979bebd5b2b8818784cde8a5707cc39a41335cd5c069dd278724c46c10bf916d11eac1050cf2008321439b50282b34d2fce0b98f19c597966ae92a1b5cd07861377720ddae928d98b5186fd592016cf4374f1296cf4b11029711a7c7ef4e5ba3b149eea4c1208f8de5544e7bd788d3c8998650300983b432b5a422b9f0c1a1fc266815a36c256e2b5b001f8b1f48d118cb8f59a6eff6e8f06dab823a88afb2343edd7b22d828913abf24ca4d91c8cfaa74721740ab2b1602672cb190dfa2f613a2af0a682cb16282b8c63609569033473d35714562aaa4314a3296031c21d561fae6a8bf914848caeff04d07952867b7cee24eff3ffcfc45bec219dd68b5b7e5ed8b3f6d2f76abdc0ca9f68e7719d1c2ce8098b467a884066de62264eae4046824a4b6bb2dc2f37eb6fa19e824e9db30e61836ad0536a63cfca599e2cb3924b2473cf5f1b4b5897995e99f5bc323ecc8bdb110323f2fc9ae1608669d32397f8bfd58e457ebd5d39452816e307d4c6b53fc530e8a3d1a5425611572de486e7dbbee026b35fbad3a9995c76faf79eec29a4a0618ff287fb16985d6a3ec345f8709c34172d20bc2274e05c58a1fe090586250d316d728e647422b53e2111f94033e241ee177449e007d4b82a8cad9bb9576b5c1f05b64d87e78fb93189331965b22b89fa06ccec82eec0f06aff68df6e19d22d98ed305dcbed29c9e2bbb91ecf57d28cf97f9d0c81a64f85a89ec23c9a49e3f22d887327b6f19b77c05d1681e3b171bb3af6672272bacae851cf4c4bc4642b3a4b7be143cf915f3368c1dddb593b83a55cefade6cc88eda8f525598582f51276711c2d3a7c58ef9d2aae6193867272dbcddfe391f5bd024811159c624c8934274d0d99644394c705b4676442f1e2d9bf0c5baaec84b3b3362434677a977cfadd2da4c859cbe1601d64713852220922dfa6c7662f00097b03acf65d26da5cf0f891963ca36bdb6544d9706049ad51e8ae1bc7a801ee2ac42119dfe00fabb5911a273658a9a9cf210c71d97ea1fa5985aad9c0d2edb594192f0955928d81f365b24d29cf051c593dd4dc10d5f37f8ca4766f37994aecd2047de9fbd738d3b2e94171d4e21e29e6165e66bb28a6c367af715ab04dc1fa60f0ae4a37409a3760864833cc448d591234d9a03cc9445c77112c2abf72bb64cd7830989c411a2378e757116468bb304a3407171a4a44a13774173db9aacfc27405955',
+ 'f7a5098b2a4d92a7e71e4658b458f47a0b5e0427adb967da3a60ced4ff361abf0fd51492958a5fb468a0ab64e0e22a58e95b48a4556097de77d10880ed9b618dbd81eb78a41d6b41aa2154e1fae33be8f1198b6575e07a0688043c801c7b76312932f504fe0da096d529ab97a9640e724c1f3630b442fa999581d09d36de41f37d6f9a004b62e5fa103e174d966b8b3e21f5afceba8dfee1c8d12e9fe0cdaa1bdec14232352421b783ea00cd69039a939924600730c96d24477bbc4ec44e99f076af5564625c3e1357b4ceddc93123bbdc33afa2beff31ab3a07e4728a6cf6bb6dc13b5c7a122357b424ea465eff0efc11aa06690b3631becafd0dd2da2ca9c4eb7f5de3264cb8cac1c3bacfd174439f6012cc22c07655a51ee69e375a989a53177221c00e14e5b6a718a742ca98abebf2f1699684c785a7604a0169b5b7b2b01921f0bdd97192618dac1a66f0742c2aefd2458d0032a90db5af9d309191d7231a1433a02f6ca7149c057902ec0fafa27f3ac8cfdcbea920479fda54972ff2f342d45032ba0b0c17fcad2ddf65721d9dc8b35a23bf746d253ea1209c6e98ec69b8e8b13b1f58aab2d42c9fc504a35c61f5c46352515ade67c23ed7d1bed4abcda5d8bc83095b672d4c08367b71ac56362cf64b253b7be22df9fc67bb31ec1967302ddbd11e1b2ccf8ecb59cb5394f16695cf7a6125dc62be0e6639226de71d7e826e75ee06a0e2e2bffc727b536417385ad958d1b68747632701b3ce1acd9e5bc223f1a36af26fac0a24e8541823aff3a09c4e3c978377646d573e87e1a7864719d5b9b6f21abd7695ca231e4bd9a1e0929fc26970d8dc0907ef43146a7cbc88af0b34ef451fb28788768ba1938fd547556a1d21e88f5d9a1d51283e5c542866ab4dca180c0938290cb188a4994c32701485c82ca7aee15ed90657cd5f37b22b3523e3f7eee036a2490182f10418a2a2f57929525640529e619536891d2e421d7716e75694ad933b66f1e14e7dfb0d2620ccaa5b9d4a97a2dd862f393b40c08696ad3efba578393c8b060d84acfe5945be09b20e23d698b27662a8a7647614acbd7151aeca470fede2ca6e5b38286f44f7b5a83491eb3d1653af0b993ed626d812e88639ab24fd9590c46c9aca82376ef25af6958e926e159ef8bfd8716bde51bd9c4663ef16eb7ec07c700b0912990ad87f03f9c3d213f87cc22c2ca63a2561e715faf33f26c1ee987be0749ee27e5fd0ad3728d7b314081797ba5c854de14eb8d908b2425a672e4048269e30faccb6036bfae9733d598a97fed132b5abfc615772da68a1bcc686e16ba85168606d579941b4063f79cac92480d974df5c5ce2ed68d6dc0354c43da36dd054ee1e478ab9b7cd45e26e500ce4a43aebaa69eb19a14166d811284a9dadd50571693c44978b56ad6f0524d19a02f25c5fbfd98f4d9c87f122734341ec282bae6e81c04bc538a5bd4c4fa436bca4f2a898c5b432c805c1df83d0aa8f733bf83514dfb4435ee82d63a369f568baf32d845d6502bbd0057897c3d0671e7a0fc2012b2b1f16a8c274083dfa1f4edc162a597747cc12aec43383aa1c80d449cb147a7b0c0aabecef0415e3ab2bcf6a357190af121a1faa697a0a005c009b27987308cb2b7cea719765f05b2420d5ab7a8b8fcb6ef2ca0b1dd5948c37ec5a5e9e6913e5307dbb81e01d036d0c0647e80bffc093055efb1b07cd8917564ef934047d038fc2150662f5b6b5e30ce60c6910558ad17c659a2050e95269612d5ff2f3384092894db35dfcb86d84cbc70e76b216544b7e0f8f631fb2554aff9276df922032b62f2caaba1ea99517f2b1345718c988cab165c22c9daffb82d88425450abf42c259bbd4c18213946528ac66536cf68d16bd6e1bc3f168acd8950b546a829dd680b10117ba517dd23616c18cb3d325cbf74b33836f4565d116de2feb97234058b6df065cecb270b75163f78fc077dfaa3503bae079be2fd0025af9d31415322e2d8bd28ca0ce73ab80b85755bf80ab92978c0d1c29864d1365b270f2297ffbc2ad5c6e8d1ecc0e1689bde7c7fb1612ebe78f341dc7c54700068e9d311e89217afefae149aed5c9603519b1cdbb5f9b1debb335cd9ba2a601af9486783a5d2ec0e70e33a698112df14c75bd504686ce906ca11a12ed46f07d266f35b0c720aafb31406c8e23a7c13196781136e5b133ac3100ebd604d9b0dc34c5b2bf0bfd1b92a43795e898c00d89dbcbb769e00953da0479ae0029826b85a13f038f4f1a0bef089cb69c3f839a5fe215b7cf7fb5b580abb46d78a469abe235843207da9f01792516d5916019ef1c74ff17520adde108ef582f26bdb7f75ab83d470ef6b58698c34efcb143a995295931e1d9c8ab60d8980dfdf2bc94d855f111488ee98421b4a9eb32e3305c12ec59521d4b0245b95a6e7ddec4e827d53ba9a93f6efc335a35a096800f6e5af0cf3b0ad19200d374f4394eda848a997675a8ac339677eab98470f7ec1d46cab639c90307950a7e1a10c028f91aea114369b6c32deda2d3c707e1b581f600d4ed92c9e2c63c686d3215ccb4446e50b8c5809b96345dc4130b27ba794480e4a21c410452176f61ca446b2599c26804b683221ecc50ce27d50d4cc5ea3fa43959cbb042f900163ebad87a93807bf14d3205b8090d8926113f56dfc8b1794b492483464b7f8c19486777a9de1178ef7554d4a82203e84ecb796d468c75fa5b5a29ca6be68dc060c4f9a862cdf3c04cc246775c3254742e9dacdaebd9dfcbbeb5902b87ddcba6d4fd98f40d29cf5bee7d2d763a00a836aece0026347797f35aa2822b02f6e0455b3a6ae210ba4c52bfed345aac56a834b7a89cd88b2d447a1968275445fa75a5dec29afad44813aca55c80aa19fafb782f71a97857c48e69e151a62db6b031cf46de4ec4c19bcb718a103ceee9b54a0a00724e8f00051fc79ca3273ebee2bdca79d6afc9407a1daa55528eaf834f3df010f3b4a4eeb59c9c31a7d410c656c09e61f2e490b7afb15eee6a9e7351907b34493c023f889fb0f088a5d32a34d5e354e57a15a18f002e953da095c5ba40adde919461e8388a01cc89e54c147127cef3ecb56c8531363d57293c9b2a26267af4d245f928663d37371cae6857e614288360ec0ec3031985ad9c85d72cfd0b8b80f395f1867881fb3a294a4e7afa64990d286726e36f70af9e7ec47252a8b78789dccd728bd71ef5dc98ff280514decb972c6eda6edc056233b54294248df217187534a3bdebdccc2551161b819e4c632c544952ebb29e47732a44632b1584e334a614ada71c83281d3cd65175ff740cd1883fb7e258040566c5150aeea83492e557b3b7ced3dab3cd4289f2699f1e6c90b09931db38ff45146ffcaff6afcbcd33705beabc76aa123c497525e5e6142b70b4a0e75fb956af860e407bc990123b27d9526ef86fbbf0723ae413723c1df27a7c9902f543d3eac38b2a95f1b5ce85c87ae06a0a24d5f378fe1ce497090069b4f0cfa9263e3c9fd3cf0225f684ca521f3b4f067bffc0c3557b66bfddb5863728f9890579125a75bfc110555e67cd4b3205e56cd1664309119b09cccba87704de7d0e3e7628f5158e489b4bb3c59e180bbeecc197c3286db5454f35e94a9b7adc65a77ba5e6d526484eed2f7c060660b250aa30527d359648617e1fbf04b93f2c9a9ce48fb5c151f6ba4c2a4291cdcb2da168de8cfc332dd2d6dfb4d63c9bfbd60335a3bbfe823e9e7401648cd0bb03869b6df6cca8e9d95c8eba1cb55b0757e087baddb127e0944b635304e22a97adc525039e9be92143ec70577fe4cac6fa541072bdfa9aa3fc02718c32cc072b74f02670fe8027a1138d64fd04ecf0a08e3985a6681dbd931dcd85f318d3cf3dfd1188fd4003ca32f04452f5d354345cb898cd9e09a2fa78a0b387cfdb7eeb96f32f32f289ac3a9c821b228815a400c42278d2a2c612b8192cbd6069a656c1fefc530c970404dfa77219bcfbf265bb9e74e17bfac7f45e3f6af1f6099fe2ba3dc084fe33d692221b68460999911eccb355dcb0ed35d056b2015932f6eeaa3e1ae9caf0102ade69bf0babefa91b579dcb6e6f59c4382f073a9afdfc7abc36b65e1c2dca7426711d5c044f5772b79895ae67a55fc8f797d99fdde33ddb310f88d103b674a8f2d2a7bafa3b2a3d8e6a1c23e783a83e9b9334a87115db6274bc1e3b466cd6f4b7896da196754e52c8549af396131d714ba8801fff9bc057aec5df648d58d99f9d1fd9d98007adf98cdf77e61e5ca6a8306025ca2e7bd20206b332147f8063f3cb1b52295ff82e7a02911cc424662c2a72428b71a7bffbaaa50c8112c4ee5d366a053f5bdc51b81c53f5ef55533a954038d61bde126f2299b25b332705aab0b1a1660a359e193529a790596150dfcb32aaa53bd816912f155625b01beaba42ac99c51a804e588ce725ecc3afc65db448f23654265b2f0967b9f45fb61a28fd6f79aad7039317a59ff69093085bbd3aca3511cf918a509ad7024faabf3efcc8416a9da988165d689841043334b70644ff9ebf12e14bfdc9ac5abff800fd3c8a6c9427f8d57e32bd1c2fd109fb8340b93052c787de453d7e30e8cbb23f00f22d361ecf2cb4749e8c71e87e7f25677383a57cb1954f2118a1a9d5fb3e45ee2598e8311eadeaa0aabde09393fb790aa889a64206a3fe86961b6048d705da70deb3c9f49be442a95d38b15998e7c015e7b37bcc4d1bb11dc0d29d6ae86fc52e24662390ce378338c0e52c6116aac22f36e96b430e64318e9dafa862b5e5d0cfff993c2c3f0f74f4d9ac99d495ac47019f13bfcfd2e64680359ac859c6cdc1fc77345ef177d5df86b2763fd99b5517332919c0971f09b79b917c4677a490615c951fcf07fdef8a9553296799b20df96cdce3b3c480354e88b83b6ae3d69778986043d79559c73dcac2af593b613cb754c15ae37d7ad2d1efb2c17cc6e449ce57e186c0c314c3c2cd09ee5de8314a1794df6497eb9748097788f4c447570d2a421ed1d0bbb54de04530d0bbc8a89fe2d43fea16365effbec941be8a8fb64d5600210d51a2c4cc5eda3d3cba0250a3dfbbe7d5a9855760b88de50615c58970183af22089a3c9a805353a19a3bfb1bfd8f2e10b98000bd1be6a7db4ae1259de399897f4c1e34d489dfe2e51be265159932135762bd101bb9a0810af9d9eacfe81c11a6f408dd816eedc22cb5360badbdaefe9fdaa1dc1871210a6e12a900d3ab75e827b50c7f079bf781d6f',
+ 'caa5cc5d0d87680eafc29429bac55c9e33167d485789c7c124b5c57a1ba8a00b45da41c77460b694cb62d7fa80cf2979e14f0220957aee5b2547520dbbc74fde2913e9d72c83692cf220ff58db5cac6f7d015fb0ea685f5a35ebe8c2329c19a17e380eb2bf56497d2de4566d52d4ae290d13dd21ddbbe0675c89d1c10a91c6fc4c30f683b5431d30839622616da0f74f9c6dc29bf7db3a2aa3095333ca0d1d969ce5e97094b0afecfd1fac5cb4264f882ff75645be30354a1153b740fb78e718753e31a1e607c55aa2653c85b0cf7e7cd099e348bc239870af50450f2439ec29e023153f32af28217a511a04e8034bd4863bafcc791a2d4384e644c9cdbaf472e47cdc720110a0ea8dcb8d02e42b80385ac503f87c7eba6c98fefe957f62c79b8931cf61da92f45de4bcdea72dade34f521f27f44db80892f381b99cc0992c4bd72b3635459dee21860a561a4af33dc2793163e9742edf5e9e55be051bc7ed2ad7505915ca9954df7b9f3b84c3635538d4e4ffff794a0678a06455f91554d0e190897f2af2eeef3ecc61d50c2167f55a6d1e425de57347870194c5a038a99e180abff19c440487e7803a6edbeb66e3d04bc8762c40106833c9cf58210b2c1e764ed8f8924944e4819f114c18a9c8e84176cbe193108b322601fc54a516461aa463beda348714cdb532cdb8ece4f4cc56f70dcbbbdf4b6d05b1030253e25f584a5157dfab88dd0b2b3f58fa7f225457b6d5787ecb34b8e17bdfccaa54f6e0a20f218d511fd408678ad1995af8ee4f510918f341ec983a552e953e94cfda2fbe9bda4676b7f1fba67bed78207fcd4d81f9c9655b46923993c6da4307ed17b67497846c989c692093a59ddd933e49b6b02ceeb81500aa1d61ecb7c24dd634dc8eab28e6fdf6c4def5b1e8b0fc5ae9f3a64a92d3b743684e884832a4acb1b908d27ecd9cedec889c9346d7d9a3fe356a2bfcba9e89365535d08156cf6da62fa40ab97b76b2a63fc4360d7041d050b68407ea7001d202f838003f282cd7df1d17fc033a5c934d70bda6adbdcecb78f3a901bbbbe4dcced9c0e22cb2a334810bc971051336d709a4efabcfc669db9f7542e317a42fedc381363ceefb1dcab7812230670decc70162c20d1b92fb4aedc2b573a831ca4e097700d72d0b80e3a7088a03d03166ab5e329e9338296a5e89646c7a136c9d47c743887b92ebb6c5792769b0e8868dcb479ceb07cf93a0609ce3cdbf035d911f256e34efc4a2a5b8566727005814476ee529112f87d883974dc5420c1e0b8c204c7f6efd6c383706664f2cbbc8e37ddd606078d30901fd4dc59432270c7e779064fe9d6b32b652f5d067e0a9dffc1861dfca88bdfd16f5c82bd705d976be3bb894742802bd23e0cfbd37ac914666fe408aedaab4091d5252a81722ea04d4bee00568798ab687c8da5448f63da52919c28a53447fd820fe3164dbf3225dc7ea50df62f7cbc4eaf25fbe212773a34e4f310784c0e71026e0ad86abdf492a9fa64f49ea0a8d905546a5224aa8fce8db8ad3280784b45a38e010370f4e261264d9266b891a97c2cfacf6a94ce0a01ddbb1f21663faae5d5de6a09e90a882be1f6d1e6ec68fb201610c987aae3626ea53acd4f923889cc29ddaa7e4b55625d5d8497d7a2ad2a6f5124ed4bff81458f64d63c1f8cc98483000a46b3007bed70095558bb63c493b47ea5af29db3e1fcead0be033be89178508f2d35ab0d4960e76079924b845d389ff1183a3e6604db6de5a5e1ebfedbf5ca515b4c7c4f5f8731409dd8618a7667a43071f4ca99e7bd289300a23097de87454f17facd556915873ea9a61ed7fd8effae4b6768d4f16ac2e2b78f313a01f5698f4a85c3a8cdd390608544adf25876587390dc41a08aa9e4dab2f0176faf09df1bda3688cff586f5b01afa3463f1e75588269b7d841a433684d90d09bf4d894ffbb155445247f95d364e10dcb32fa9a1f4f7ec430909015fe7152d30b0443e6035b52a1eba2df371f90acdcc697983e2bfe917bbb5c0a9080b4c99b4ccfcf0bbd3d0fc3f8d0e3bd901377b2d0d393ec1f2e6630f13a503d8f9679abc9bdd6708dce915cf56529a3c56bb602627d6a2e594d51a64a821d978b84f7670a4506aee59e7bbf59a60d8420180c4e040b877f7ad9d82e5fe9df18f50ea75f96fbbc31551b437d9e3a2bd94096cf182df47859e4628e3b79c7f14c6ca22e17f84873826cc37d1a4b87f10da76692e358deb9483655d87050a300ac52dde00296c1d92c9d358d07ea25f9bbb505ec221d10c6b4d1524b5f5d1199b3381061c20aee398a56cff7e8e28aa24e0a032f66d3312d3a55b65b4af78a18fb9cf817b8cd2431463a21421fdd2c974f16ecf12423b6594334108cd5c872fadfe1e39659460a4ccaa7a7f02f228225395c01c5ec7726d769ecef64824862dbeab76152460e16e8a23fe286996b31e8974a00121255f92418f0a156d2efe028a67dffdff19dd08147635f89d11fa25dd371566a5838b3dbcadfe4e83a37716d9db62d93de7dadc324a27d5e88a85a018862733300a7cd4b0a1b18ad4aa77d173ae069127f16251ae47dda89029ddf50208df500be1bcc1e5122bafa66c889b2089d40e0560fccf4f165e5ade18898e636644a67e32d36a23a975a6421131dca714d2361f5b31bedc5fb2d11a7c11d103485f1bd0224739320e9658f0c0fbfcd1f60af2bc0b87871ec9e2f78c80fe28aa5436984bdba294d9e896acf8a16c6366d8842b25988890ddfdf5b37c49d7fa1f35d40635856be5e1df7e89a1dd0e792e6147c7a329bc42e0a3f3ec310224af2b913e4bd7472b93139c55d9349c69a7f03a5bb07ce6aa05f162e58cf4d16eaf96117e51794a690635c72383f9050353760ac8ccf8f8da42d6e2d27a0dde3b61285c9afe63b6ada60f08f16f384166e7867a96056187d45f58ccc29ec452162fa81b9d3cdcb280db6b05c68539771ac9e932ce41fdba21c63fc8bde060558480e0f58cf22d66680d0f69aaad43d0a56367d9786a16ba48dd537dcc282b0e0fbd969371089ffbefa4c4daa5cfa074911bc7179a67f2afd10e5c94f65e6ba63e4c939c536578999d085200c0d3968a665bd3963e20d9c045c021b4446a694599969fb93bf30067f9a1818502a16e3baa8a51fb6b7d15152a5a6b86bc346d11a90381923099818e8bd8190e742170aee70f0af12a66edd70b4602b269a5bf35f5fc03ce3a3f4136db13e1461c3ce30ca454c61e82c3a82e6debaedf50a3a6d706e7eb1561cd898572bba2d204d8117c6ac04c2a7b7c8f41dab137b57b176c20622d0211ae2ca1a6d739245d34de4027c0bb66be1d79ea39d90064def1ea5737933710682842d1bf92f32f8db237b9342eadda8271a3013df340feffba02b044216cddc2d8f861f92c538b0a88c9c4cc3cfe711d7ee01b76aed9cdc3df49be71923330c8c437987b2cc0ff7dbe7ea8177317f3384c19810c953499cf67a6cbe470f6d321f6e5c06e1aa2558e5a3daf3c5a5e287ae4377c262db72ace5a001dc5421c8c7676eb1ff97f6053e466ed1f647a3cd88c4d2052ec00cb4866c041fd3d910d246f4a32fd45e164c228e97841b6591aca158fbe4b8795d9ba3fa250b374e43063b37ca1a479cb156901ecc55d5b815ec7beb3f7b11f7447490207158791c3ef10eb141f5bbec2db121876bcbb7a7a72972fc0b5cadb267ebd57f878c1bcb6b1f5be1896693c501e83148f45a23ccabc020fbedfe0e432e7dee57c61a81f46dfd8d592ed171afc46859f3f485cc9fba6d006b65d396220e973559bb885dffadf82d7890cad814ebbe05e8fad2f489596c8beaf171d7c79eb464e5d65a0275b1abb6d06db7398cfe65cfb865c64e11ef6b3dcb1f4d65ac3571d79cb50411df0f84a3f1041b088062dc11e2d3e42be202d590bc4dfab258994c17eec62b0e941e2f9f4af29ae787cf9d66e8a39130422a382f1f1bde305500afa04c98134b4d63e8e35eb78b391b7b36494a8361ddeadc0f6363f77c721a2218fb3689617a63875d2a9cd1708fa41c133378c1eaa7248ec7c83b7f59fa206414a35d38a9fe6eef08df95ceef5dca28d0b0040d700e87b8fde805f1fb3af05d2f12f1243159d801687cca1e5c15f607db497cb4b6769ce11e2d441dd4a71263c4d4c2babc1f2774e87cba2e5b6aa05fbf5a33560291dcada51276518ad10f1e7263128a9ea0e5902579e69d41ae6196e98cd86008d2bf652f223d1b625b3ee3c44891024d918b199bdecfe9c363a223e63bcc712dabbdae28f6e8fa1f882a6a16efaec06d739047b825d672352cfaad21f18007e59f7fff0eeb0a7bf6ea6a07f6e2cc3362a99dc0f6e9aae53b6cd3894948b372c5205ece6d8921ffad147643f0ac99d9c1a5fc0bf484bdb12a95b55eb89bb76040c0d292a15bb0139678c7b470b768320f1b439f3da18f44a74a1873fc750c4edd1383f266dd555647a9e6c0138dd7baaf5bfce11eaa703e260c859f917f32ad2e7adb540a885216250a5bfd35ba6902270a9078241a30fc2b3f8507f3f4cae98979513e28d756f1d31c8fd273a79c770a8996caea7b221d2b558f63a07025b282918e273e64d467c672fad649ffc2a7ce6b886fde37c40fab011d29239be366ae55da95b79b4af67390357f250dac02e712ddcd8bfaa7422ea4a6cf09b274946138df0010f53b0c6ee6c833915b9916f9321f6a501e4c532ac2c4dbaf7e69ba5facf40cf6fd25481cf91baa1b842a62592bc5dcd72d13c123edffc5a13a2346de34c1f2c63d8a081249b8392ff1c063ab72598b9da1ae0aae88a0136b7041d88162c1880b10d9eac35b16774b4efb9944a852fd00167bae2f256e5b8adb35ddcdb96b034221b55eb49fcedaf9d65c81d9303ab79ae5fd0a3a36a2f46bc58fc537ab271ae7ea7cd27a9a49dab83243abbd9c8931eabaa2cd345ef674aab9b03d43aa9e2578d5c0f469ed0ffd02dd4175866fc6f26bef1d65c1e0c162b4323794665a38b9716df22326ea89c87651e68db80c5c8f9b0dcd42477eac3514c99669341c7f5d7e3db0ed16155fb36f1aa342c704e24ff4812301597b0f6248ea4d2a2173ea77dbaf6dc0dc1ffa4479a1f83337ebd0ea0503cf216c887370cd0edc65b2e3029f364d893ccd4cd202028255dd8f13b0b448e01200e50970f71dc1c49a6d0c4049fa92a3bf8e4e8f62b6366cb0313efa553cc0ac4e7780705bb78d8646b4322bfeb5094dd783778aece1387d49c2a026335d0fee5888800a2526dc91e92d073e23e23bd7f3415a4d173ff33818b7f9bcd5262868cd9c8a96c9e82987f03bfdff6ffe84e2c14c894e681f010d9b85ae36c124c4ac0c27f2bed0881ed8fa7588d829868eee90097717560aec6e40b0202c7de55f1892b',
+ '141fd0b3d111b510ddcb31dee887a3d463461a95ef72687a15c17892375ce1e7c641ba03b6e5b1b32f1e570b8641beaa6b87464064b6b44d7afd842b311f814ebed492cb756cd71781b5f411d71fad436d1eb465a6d0be2311e0dc2154aa093b639fff11f6eb50c33956b1f9c5689927cfd10b0f9f08af874431287c8744a2371d6caadf21ad433fc1ca36ca3766a9dcfb69f34336a5affe7aba0f44b13674c954013b3cdef9d9147fd92a8c145f06ec57ae160b53f1e5121c413a82bcc9a67970275931151639c9dd4a3648469cd7df4d67196ede327a4a908f513e8f4260cfd9a6acc4ae4d8de641e70105b465453b435ea775c0b1962e3f6cfb7e12eccc54f846ddff91e6faf4157634cb4602788aa3596626dfb65f47919fe04c2d0e0f8f33cf94eaa629aa7ac0c076a2e4ba9753d421fe8b2488001ceff2a9afc8ef5408f308788cd65dc500aa8d709376d6cb1f3e7e18ac77719f36bf2bfeb0cbd8c148a1ba32ed07cc720e3ba5c9a5e49e3b7549375c8fc1b7651b6a1386551e117ed6a3ad6a1522bcda2ddbcf2ae1165a10dd5d16713ee8a379555972eaa8aae2b43a63a9c70d107625e4f2d53b4df55271dfe2e100c1d67d036cf310d2b155938bfd4776f1dcb7427abce87da3f467ce87044061b01e718d2de69fb4e477086b2aa6b9db918a0167013c25900bdb551579d3df5e2a5fa31a1d4dc728cb02acb3babd20a24f20d52fe4ec11d51a0ca87070d528a0158c536efb28d2322d5a27b462cbe491d2a51ae048541516798e4627949081ee1aab69cff000289bb38863b34b576c71c321bac357fd9719cf6919820c8e5311e1c6cc86245c312a049346fb9ce92209c99c9c20396e01a7c5a508c8015707d211e466dbbec454a9c983bad37e096d238d1fa83f162fb988034bfa439a7103f7520e1e15e6c0fcdea960a6821940b585b6b1c66715c929843063d9390066b1484e4bdc7ec6d98e934d33f151941563f8ed5bdee25ec3b763f4f38cf35abe788faaa3885c8396738e5c0485881811dd44da24d8f61aa5cdecf905fbb9d1ffbf92111e0bf8488013987fd9496fccba8c3124149cec71f8d2e8e4a00ed38db3f01a29c54b9a3b1dd6785ebc254dd99bd8877433130c8a422e2060cdad88b56172ef9a9f318a84f825f8a0b4016c66392a0d718a239d8e0e48591393c0217292add90db4a50f4c9666deedc9c5129c1ee88cc420b5e9a4e18a5ea5fa2fe6ebcd09a02a0d9072bb8103f3ef045a88a3d17ccd14fdb236f5455bf6bf0ae21f499aee0b98b1d8fcf84062ff4b6ca616a2da4c950a2a00cda9c123e809ccc114b381c4e400a867f22c5bedcaac0a9203c1c2c2af4eae89f6e7de4bfd2a47b50d520bf3f109fb239f7e5a0a1bb8e406992a0f44e2879133f8d72239fdcb83a4514dbfe3fb5cb1f64a17c623bb1705eb1e024c3cf55ddce81da21756b093897829cd26fcc9a0d2c73a1e279f73727227db74fe11b17a968fab70450add2b6017ddfac6a7257e677db8bc03e6097134a418a5af2bde83c710eb6833be4e3a106bb5fb2a4ad59e77020c19e46045bb54481dc0e6f24423775325b369d8c969a25af8f9d74fa2a70a3d7e5c5175f1f9dafd31eb2cceaa00af3fa1786fc217601dcef01b571c5442281656aed38dd3d2ccaa9d4e0827d9c276bea6e0cee200c689aee38a301bb316da75db36f110b5ef3437aa1302659a12d5b87d130da24b43efe21a6dedb286cc2742561d33665df7198b9d5fa2f0b398d3136f38b469c2815651dded134b970b18650f8a21f793938490c15d7130ecfb78b8c2784b9e2b25c6e574322c4dac7cb4c74ea6442b216b7c2d5d32f68e0fe3cc8fbefa5bab4fda47852663c0208ec6034e5b98236bce26094ab809b970e2fad880ade76bf7f646e2193ca9552c05920de37d89461d616d33d01b08433f2fe5a374d56604eae7119e8afe2b75d8d988db6ffea136aba3e703a5ce571b64bc4f355180a0adecece484beb412a78ed14f74d824077a7b5c3d80b2191fc94551de9701f4bcee65cb679a9ea68574b6b690e00838e49af75316b3df4488d64cb83ad06a79e34fbd4d41ea121cad62b650f228e5815f1f85521ba21596b9c9e0b80ce876593d595c3a1a7c035db1fbf7671e535949a1908f1ff4573a58db2a6818fce80cdaf193ab5a9c5657b2bac7e1c3bb694bd6d2757c8348da37d315824ea1b1d71346288610756d82f863f04ddd2b7273a2721857b446bf31f54c9058f91bd4bd75e309b8f4523508ccb87a155169eb7748639ebc9f3002665b0e7334d14e0ca319fabdb3c0ba9deebdf881a7a643cd8024f18a2fa509b9815060e79e3e010290e7d26bffda754c3eb26d2c8c4582c1931e6605352e988c88be89141fa8fe5e8cc7b53c22ac4bec00925da44b94ee6eba1e083658a2a621858cd2213e770bc79fa1e958a69c04223a4711106cfd4e7dfc0c21461f69fb237fa283378413f1e5d25db7e613146798f6b8d19977e76b9562d0f75c12eb5f387fe8e47d78e577612ce3670eef7b3df63bcde567f5ba0e5ff253d2a1ba909a088c463c1ca25367e3b51b41fac4394ee3126e94a16eddfd82b67bfc3d9ec1733caea4d53b8ac6881276ee8dcf19b662088183277068ba01a7b631bc5747e4b47cedeaf503b9a7a197764292b87759410d93f4e6fb6db8e176f95e59173b63236f5200e59cb65c7b19be0199db658cb2994da9196b043f679687e81ca604a489bee4ceed2d094fde415411ea606bb77f54b98b08e7b6b759b068b94d2c2a11ad11ac3c54de3be691b7425ccd7011406ee8de80fb980988806ba5b734d03310590eb03364d9d38b5e2290c88a33e09048fac47139a5871ba47044cc18bba90b5360fa99634359a50b2b443f68d05f0fd43574470b37b8d68d6650df4315136964ad92589a47559c617968a8b06f1725dc3ef5e8b976232202f6ced7fb05fa92549e7e56510a50d728b503eaab3a8e3b26c04f3e8b895068ccc8c89e89b3e5eeebdac87dd0b7d2c028861eef9e574eb77c618b30c899c70eb383451b35485ce5f10a78b35e7461be2895c09ed4eedf03a4c9b0a5bacd117e7fd04e3646ece7df2dd594e244698739f289f1df9428c78566a1c687a74eb51ef856ead706c60f4468e426f1cbc0cb994c0bb99a252c90a78c91d6bdd8433b58e6be21e6bbff5b7c6ade35c8389eb547ffc321b7d023c1d0dc40e62f95d52c9310affb4baebe54effb6cca4fd62dcea9d358301fdd35e367205701c5262c0e363fd281ee272c8005e336ec6eec959d288f73efb894897dd61e7d2c67d26f6cab3bcfbab86d716927e9e3a30dc1feab2dfdbb646b3c4817849f5b71fde2c7cb59cc4daf8fcab497bbd71bf7149e8f7e1ee3d999211f993ad96a99d76f9e5bb5a8baf4665d841d912b7388f16bcb70a0640a7496c083a56c3d49de66a54e54b100cc6de908e4d6dfdd86d098fa90ca99683a356131b194381802d227873ad948c9cb6040793204093bd79bf5aa35c5ef913ac3045df18d23d25e1e21feaa13006b80747199b6d297ab30920e6101882c46d4c8872b8bb8b7d3256a5df0e529644eb052864fb8661297575ced083d3cd7f1cee9f082c63e7b841f5de1473444f9db26a286827fe8026615a29a88320879f9f1d0494ceb47f74b13a0b7e9df8c4978a90b7a1c5481ed80320c1bc7251599c605259a7042fab491cbdbe7c02e28db8e003569047f585d4d76417aaf618abfc0d28fe9d6138039bf0db577b268413786f4c95b224897d935a9eabf272d90744f1fb74066a6010e3ba2d671a9d7fee6c64d6f595ef663eaa092aef016d04f3edbb645a60842a4bc6f52e7dc8cc1886fb8d3ce69a0d3e716f6fa36176693eea8cc5de024a43191cac1e490c1436f065ac34d8f96d02548e89fa92a3bfebe96378add30c022b9f1c09b227827b529a1304e8559e5d635b1e503673165c6996e757dfede846a23ec2764d24816cc378177c341d5609a4b48978afcf39ca66b9fe90d87927864b7a98684bda7976fe0cdba894aab0e05af35859d2f19e8867e501ba342f3a3f9bc516563ab3eb0866dae7e086882f7fda8a137a2c94b514e18aa94a5f5aa0d0f7c0b4c6964b56bfa264b4da86202246b7fb436039330e0e682d5db7d695fbe8f3d00c4feafb3d0b153cdaed102d49c387d95092652719c3604f8789166b9bf624857548a55e0e6943c5b2aeb0ea0674ae76d3875d1b58e27e53bf44bb460176ee53985751fe5b58b291e485e4f0d8e8b08634c56d7a5bc9f6fc7d6121afdce9d5bcede27d26a457f613d90928dc418e227a0cc332be93087e8c4a64d6138edd6f43de70839169f562de18af0906d0d368b4b40739628f2c8995aed6651b87a00f6af28811b92cafad532bfde1faf76717d8d307ee00a0848caaf31c4b2268005aa4b2af83f85ce51a157b6c504325a7a458e25bcd1397cf1c3eefdcf4c2904cc583a74d66e98b445d879f70e059fc1392b75a795305a56aacb3dd6efe76a103d48a38e84707383bdc4bf0b1feb9eb396776b3c71c7189c5a2bc4468c4a90ab40c1af01680dbd43a0ab5279627dd6397970976eb85c1858eb2cadd40e3e44debd0d8654ec0d1ffcd8d659c93d85f05aca5f22c4d2b8059144141d09dd8b2eb09c724f0f773740b74c8dfd841ac9931f718c33c627a385504d2b3e6b61f9f529c53933bb7054c97ce41866316013688e563ff3fd1fe5409ceebb3884034f425121a959df412c615188bebb58772917b262c089f02345e07d0f0a33dc2957bc31960ce9035187b14020c82581c7d347907b561e28998c0afb986156f93dd70cd00da80daf082d6050947ecb35b8dba0328a4bda2beb82681f7108c965a598d9366fc7eb6ccee61789cc28d6fbb208cc9f78e5e4837fefa2f08347b5a8cb62cc6ca2afabc10b797ef4b10e6d5c1d2170df2b6d65b7bf9b6076b466424815fd8d7990a8763727af3c982978b9df61ef37fb8d2a8450124e49baedac97cfed30c3651ffc74558a50fa7e1dade10ce63ac6fa85666ad5dfcf05c31763ddc5bac4163939f1cca39d245fac76f60e6b14c9c8e4fa673ece90e73d9a18d13bb0e38230fcc5d1a7a9c6f2142c1a9b68855466e3c1d67729c48c5e9945da3eda1ad22fb6b6abe22cf06e84c006f3e416e10cd7bf9a00dc533e3bfcc0ce43f4e18aee96536fd36d84fffea00c40e8184107a6e5057660dee3c405885b3c3d3a79899f7ead3025b9d65edc0fa0e4e081108088585d5edec702de52cc1198af57ca9e4dae6c0089104b96729823f9c565acd31cf86e5962ddd7158a8e8be98094fb5160ef39e8e7b80b2e27053e887e0d3c88c88de16fd46a8bf0159770379a39352a4009bcef27fa3dae621d9898f3c1e928f6de5da81cb445f85baf698be48e9fb256c49c1d311e099e8da7da310cc9db3a0db48b0d22042eb3c59d1eec46da627008e8817aed6c98870f6cab5bb16c394675d713a5cfa16eabb92b3662a867a5ecbf3c150f432c12f15034b41fcaf32bd4950f9c7909',
+ 'b85e29875f6e2a2ac2a2b8475376eaecfaff0f76ad2fe6fa415512e480e3c8de7b74cbf4220d9af511a3e71cadde4cef701d3a6881ba3253888f37f7c0b983f84e9b797cd126db8d3a583dbfde03b912c9d0e5195583025cfc8176fc6b8f7d95d7dc1b68944255bae4c9a0770d6d9a1bae21f8d25213bfde4632b83aa8ee1d7dc13e990095e87043b7fdf98d62a255d3c6165bdba0f1d2a20daae3faa05ccd77b2cadb8cf9a094f25dfc3149062c5402babaf67c66a5a16dfaf2e0847a63f54d5287c954ebf3298d7bce2ef32193fd703112b1fdcdb8960ab51198205f8bfbc54b7d4ca0916797ddbc7cdad3da5dbae4d42875a5fcb1183fe50ff216775b48a842b44ab7138646aac50c1c315a14f2284b0328be1b188ed632f5d5ade95b44bde235ace29ad89ebc4189db54c93f0c023dabb48e5476629546ca2b2ede1357ced0075b694ee408dad6f801854e67723b5229ff5ecd52fb45c696dbe17d0ceaa1b7323e945632eace2c63750c11138b9b3384f375ae34c1ae5d61cd0eefcd63003dbf3caada4aad5eecd11f313bcbbfe988c4771d20a41c97b134e9fd5bde2cb10ab53ff504b5ba53be4be73cf41876eff8f2729c4b2b74c96a1617f6eac8ab7cc71c2ebbfafa787449d8b574638018732c14ce3b5650310d31103f40c4124a2b1cfcf045e4a14e8b36807122b18d0d3ecc357242699cbb29ae29492410447084b05e6fdbeb32a65e2c4b038e05c7be187f5a46f9ae967be588691deaf7e784512c4992c53736e7b7d442530088b591c8ed8d32a74ac6d70b67d8a3daa082f05837c6414aef35785cd66c4ac062dfef18bfd51e9668b43861f57fc43b339d1b627adc64b33bb5c315d9d2ce15bacd41ce9d3bf20c2ee907b1d7656657dac06d369d93e44844402fac857ac849b808edb32ec59652c4ecaac1b8927274bb744e9e47f3a751325d24e7846e21a286175d8f1b7df2b053458b593e0fd1dbfe402660200596162d950a907bb6bf694982f72a0b6bef6d037d104311e369d4ccad5d45d1d099df5c6e4a6d15588ce52cd2254ba79673d3fb1ba346da1624a64d425b15025c99f3e7724a47f85e6f60548e4ebc9706672864a7ab2941b1e99ba88789985ab27c9bf72973e5cccf4f20ec3ed94382c3b4b565a990b5edbb9ff906044d9582d92c1fb41a2d113ab4166e1a6a30a911d640c227aab9b2873c30098e4210d622d98fc745cde191e914ab92069bbab5eb46f597d23290e8b63d831369c83b21e1bb8fdad2caf52e83f7f6d4da58df31b81bba7b8dc77c1e23c4805fbe1e343f678613a2859ad3b0ad66df7cbb2a07e3225d76b880f3e51e76dc0f34b6cd65f85d42026584c4e1df11674ed1d3989a95cf151394d43d33ae568a18dc795c34136bf8466cf7d0898357052b1c4a2a000d674b7858b12dcf976bd8839d2e530b5a38afc6ff0746326327455ea54868a21493058d4b3e4c1fa05ecd38c0fd3b51936d6f6a66dbaf43482731cfb4f4dbe671fb4d3ab7a4218c93d771208c0f9a6e87b1401ae89d9326fa02d06791760a35ee462a67e20a357f377dcd214b8cfbcafead2bbece727842415e2a0c84f77df8511ca5fc15990e5e53f9e824439ce3cdc009373e6184e8ffe5e448a7d49fbd956327c4e198793692b0f2cb12be65dcdf946c6d82e6fb6ac5ad3b3121ca955176ec0c91ffb3d1358416117cd102126d68437ed373a8ff87fc620bed60ae02c101b476143caec9919b4cfe054b57c91fd096e874f7eeb6c50cccfe854ec80d96a0820b5481d08bd43e1c606d6607b2787f525255f7ff4baf5eb3ba00d25fdf57ba1f7359b7633c85d74ce0bd0c59f702dd4263805c24e4ca58dfe766d3bc9f8cb654572bc4ad072dcda525fc2494bd45532dc4fc0ca5aaa063182ec03b2876befee75fd392f7125388efd83296012fc847da1dd1f1cac4b8e8253715f1e98c74f9a032580788857f70f2a0684ae8721322121508f2db5a9a4dcfa96ddd4708360f4979c20daa893aed7526a52791b1ae4d9d54a7f61f96cffcde2cd0e78c128ca8a8db198ba3460a674211f1174312222e4383b9e7df9ede5b1a6a47f7fed4ff2c87016668bce37a461f0a540247bdf52fcc4a43ac639a4fd44a08d9f5e7731603ac92b18e3d880ff2d5b9d7cee6dbb2b7ff504f2df3b3abcd38b18fc98d1a5a96df3700e46e5f53d32cc581646594de2da53ee452d71078b0aa0b6f5b39ef514bea985d52968dcd51ebd75bac0f0f03fb86bb0b0356db41e469be8493321a858b945ccfc0ff3d05d5dc966b8e2b75a8886a70cb28b9398bd13d73d35ac2c47ebfa42c5f16ef9584c5c5abbbf300ed563c19042ca94954e0dfedd266962f15c24de3af133cafbdd18b6fbd53c1d7a0947f5a20366e4b54c77643824431c234db9f198ef51b87de748da27539e2f8b6eab9f76cf25f0a61c9fe052c7511c34a511b0d700d99be20f635257b773cab56e052b68f6765cda16ddffc7702207a7eaa2b89fe619f9eaadeeac27615b938a8ffb260329d66db3f3b81f00cf2442ef9703653e0fc166da5b4133f0e1940e6d5ce42bdfc9d4b7d61bb4da9924d6729e22aa434bde3e47438011a65ec8db8ff05d66894966efdfb3076a9eeb21b70b16261dcf43d20f3fb8c4b66fcb8780bc95f9d8daea718639dd3f3fe881465470fa19c485b09b9294ac81d5fcc19e3208d0cad1ad4d8a464ab72bab5405f33d48bc6634f31c9b970a815fc6d9cb8d5df92348e75ccd119ea6c375434dc3b8bff6cfa3e593d2425af5f9b72f8363d563022fdc6085e397fdc294848e5245277b0fc64b6ce48c307ceb5810668604f6efb8392df3a54b9df212acd1e2fe249fecf812d52171a4e66b4f3f04125e3962628fe19617275f840a3b7ef5f79dccb2844447c9b9a7b6c4b4b600fa99787bc859fdbbbd21a889faa4918d5922ddb7efef78d7a18c033c5bd7a4607c82713669449629fbc99565525fb94a93fb2a70a87d0a44e51f10902c429ebff263b513e5a0cdbeea657a7c3baa17490ee700818ccb8d022ce96c7cb68409820493d07ecdfd18dcf19bc4290702401b428cfc650d3955a1b181533c7b2a89592bb93fe182b81c16b9c30f165506a803d7437a859a6518a63b6d8169fa9472a7c04a7fe694702bfe9b71b7aea605c3c535b1078dc4dd2a822304537fb56069f06aadfcf873a3ecf72f2e5a6c6aae27c1c64c2fc80ce02fc7f0fc66081bfcd3b5a37a5381b0c1b392ed6f63da236e587c317b5fdee33c7cea3d9c257dcee85489d336002cdc5834444eab607250a4ba66efc5142cd840b65b619a1e5b2eb140cfa2477f5446e5d39ddb68eccf830fe21469cff95c6c7b50adf54cad2acbc64d0979454d9290f916020c3e453c2b0e440727e25bc8106ad054614a7e6716b5cdb9c0a5e7623ae0601369821652c90e74b1a2a2d80a548db9e14e09fe9aa00e377320ffd94db55a66446beaecadedaee8968297da9da96271d71411aa2fe81e3ea812a99faf80b58d179bbf14a7f96e04382027fffcaf779c984be80da16f8437db0e39a7123d9048ff71954acb7caa7c1903d994a1b73b9eb76df3a59996ceb78e7c269c104c592e7e75f3eba30802a4bbb6335517512cfcb6e2caee730e6c22350506cb242daeb217116173a8fbf51292afbbadd81dda3b1952e454c836db2c510140c0b861df585bfc546f57f9004a2078d90b6e6db1de5136c674f3909a3a85296b1967798995af6f435b3a6f92bff77a11fa44d1426ae0f6e7dbafac27b123c5fc419be52c0ea412c4b3cac05ae89a4c0ce6f5e91a456b1bded5370a1234cf6f6ab5d0253507bc6f3f0573ab97585b67107dec059812323e021e341ad839ea9e3d02aeca43356add48ccef81f693ed53d32ba1c74a35e8a5f7f3115ef834f7daf9948244c4fc31f5487678d3e70fb27abb5cb19ebf44e11c37107956d0ca999e1707e51538e0944fd4aeba21e7303d743f9d960c55a3ddd935e2b683104f22601bc951bc2d67243563b21dec85b9f0b8d66ac980abf711957ae66374355734b98e5562ca0114444e7c3d3ea430e17ec12650b6ac30a33eb98c880aaa9e574312d538629f526c871394bc76d9776b3a1595cc07ef723cb7bdc1641686d9e3dff486df0bdc9fd46f0d339c610cd7abb52eab4917baef281f2aeff711a976615de719d5b8e257e06e93df6987bec0176294ed6470af05e9d7893ebd7672d2746f6314b8e4410914f8500eb0555fcf52d4b0c28aad2c651663415423cf839c8166e0fdd5288931e6beba37fd546063d28e3ac14817c37b3254fbb6d6874c231ce6f94bc6f02b50da045ae19cff810c1af17b70196af4c6a23f10dd22384d14ab5204650ad597e4637b8aa23bd1025690a0fb457af140c5fa54094da35d06dfb15aab001d435f6b1776e1e0465394a1d80f42b7d95527f7af667d7ed65ff9e2c34345738ab402637aa8f92248f1989c55aeea4dd1012ada45d8c5f747cba6cc55ce7c55bfb1f15be16428eeb0558e949e124a8dee7fc9fc321d8b2d543a8e0aed3eb81d48c7dcb2f05b09bc18e9f73915abfbee8e4b75cc08b725a7a7f7201cfe167130926005173bd01400ef1962d8510e66f63cdecec84e382dfc9fbbfd810d08304649150ae70875c063af7e4210e4ac242b76b4258f67132c72c7e386b5ae669da4b9416ea10aae974ad683157124f7bbf4d8662a7fd7ca89c0acf85c4936d3e2028f9fa0e3a70d212cf0c27d57a68386e8be6f3e1834e0ce5cbb74deba5ab2d5b6286a321d61f3c6809aa6ca52fdd21d8da529e0f6f2d872bd6fe38e676e95b156104ba2bcb0051ffc10ca8cf18f66084a3930b37a96241f5956cf0bff06ef3d58d3ae635035b395e60f884591cfb1afa4c71e164183461cb6fc81a7efa841b244ef2d04565781c0d4f37a74d25337ac33fb4c99ba6ed0f35ccdc61297b71fb890cf22066d99e2195f591bbb21ae7a566e2246ae63ad475bc146e6aca5d7ebd8c2f037d9d4716707db9cd6591e25fc66b9f896d4cbc30b8e7b047e06887f386b51fed8ff49729324d05b54ff167e3035bddaa79d6154a033f062f690ce118818d4478ff72b11c8f8e400f21da90bb84bd0350378a2b6a7c4b7feeadbd5334d39b076c751f7e3aaec7d1a48255452e72197b434e72e7d74b93834b5683c591a767ce909804e4cab880223c1b686e85f5f8ac4b679631db999f7b0f09661f75237a02ad1128cc13d4419a9f941ef40934e0fe302afcf2bc8ef8cd02706f49296f5b0c8b87941f5e2b93a194947cbdff585cd9b93eae10c35125e3ec33a52bf5b49252f2a34ec3f3e5fd9fe4c38cb2b288f1a5b04cb475380bae2495fa11ae201aa83eaa0d60a21a2908fc57cb55bb69ed29cadbfb140763e31cf7c56cb9b8f4c824377a6cd1a31b1f3a21b551dfc16baf8bb002f4d8b08b02f5c64331a732b7e78ea42c69aaad3df01e74c60033aa01f59fc0efdf0857fa8fc4f8d8f2e305b29e6fef86abf2aacac4395e527d586073e7ee606963aae4f6b30ef54c5773172d164e7f51dbb18108c21548207356c909affff93728c83ec8965d24670761527076b3bc54a0f01a40133998f98836cf0b725af422d7694fb85f38eff0abb59dc2e7260e59a3b65db9de2db8a564ff59c05b88b7f21896fe0d3728bdb1ea75df6d9130dd26',
+ 'ff5be1eca7d45eff12e9645ddf05c1735b973bb8b06f6c32596bd13cd9541d86ad03d35d7fc8132d9c0cb444a83494d891c92c4cc1d668af9892b586193f5bcbe3520d3563d4beba4908b753384ee7ffc2477a0a933aad8fecb7e03c547aec558a91b8fbbdc207afff279412f81b61eed75a4c7a8e63e3da3f2179e6f1cb7a2c8809fb38f4589513a8af74094e63abafb948ca251b19b3997819a90c5afbaa59c7ffff73705f11ee2be97ec1a3ed6c4a4e591e92a023c5d37fe79837f6d226e32dbeeb34999e2248701ddbc160824dc580d76d49874ac0903cd36dee2d1796d2a48de804d7df712a5f93b291ebeaf62a3608e2d336564cd8972551ba6794a12f13b31e6992e8a69be0922cbaaeed0e815836a2b7170f12b478246b220c0ff0016179b4ed328268a4db6371c30f523ed0cda7d87d903cba2bc719e4ae84512b507827d3181f755f4c384fa83478e32d217fa3aaae0ba7ec466c4ce3e63822f9243f05a271c189349bdf9cfa965066837b557440d1ef164f0c4f0591538e4902869e5a4a8081868f99b8928e6fffa4eae73761c36ea4528b85588c157cef90ef7d7d70f2cdc533174fac7b8d4aed65086b0db15b0e922349b9702898e9c4bd6812c48dc3e1f65975c4a19d1eac82718512bfad2f38215031b17d2342237399144c5bfae5437dc0510080426a1f268f0ae1369d6874b9a3ea7468c33ab166ec9c332bff7f7bc030510c32b0982a41893fe258c92d4ac2e21d0a1a51a91b037ea7cf1ae3b912297120ef9e2fd1563b25cb1c0e78a743a4d6717c7a14b6eab416de2911f0bc8f1f4a64b1ab093d297c2c579741f3fee6c7478fd16f568aa5e07ee948f96cb07a0985c65b4384032d6c658a5cd78359bb93fd1e11e35d4cd4ee7c6f34b8a0bd5c51e8edf44dea4cea739a0d72ba6ee04b71bfc744bd73250048d918f638d409bb5e3c8284b0813fe7fa9187e1d0fcbd1dc7563273e5de3db0555c8e955f50bf2298482c14569205ac6713a1b2037715bafe8b06a642e6bb6c7dcf7619175b051667d694aa2664c5c124e803ec392513a87f24ebe3bed5c8bc28f87b8b473e032133ac61510841a8af97d854975c50dc71b1e98b3440f18b29bea143f2d742e3ca64933c0ee46fd6fd2aab3cbc533e9b9cfba48ba0b1842d3aa7907656aeacca996253cdeba237763344286561063776b2c71b962ff7d4d5784c8ebdbfb6c7e21796b4bbef2be1b0d94c1974915d85953a7c1752eafb2aac691362c036d6da53cb667eaf2265b5ae78ac441451406f21bc816067d8a5cdda9765d626c2f62c453e3b78508d39fb9597cb71ebab973c42c140be7e02de07868aabb0976cfe06dff67e6c47378ffa90bf116aa1a64a351fd020f93f6e8c1ed8c84bb9bd3bd7b0497d95203b2950fbc477e6f6df4a41a2e17189d85126985bf9dbafb93d376772ac5bea5cd56962465c47b2dc4da065cbaf2ce3255d32eb06114b3d78c26897943016e3eabd01af237eb7dde592af9cf7b28a97a60a986c67479e5fbdf2d7505dfc38ea919af81eda5350cfd95680cf6f12120b24016202c5c45e7c51758b781739496618145796b20299f1804319e77cf64da866e998976ab012fbfdf48cf8080ecfbf48d42501f4b31ba9a1ff84ca6486c1d6366b40129c3243468717e3b978a4545ce9826a46e9905c600632c9626fecf8fe5a2f645aa47278c4b78597a2b1225fa7c3c62f4dd6bee67f7585ee95e74d7a869bdc0b59ca9939dd57e7b09afab179079d467bfe0668416cb79ffd4d12d4cdd8c11a3ef655cf0ff92de4378b9a7928e440ad5641c0b4f391942afd713aa67b5a949304198f3b80800325335cdaa1f7a775a1c8fe4bca8655d3cbf7e9a5ee0c76dcec65aabe0616da9f51acf502526bb602cada1df0d3821f3e2cf29d9bd2069360d069922b575970c911aa3e5ab30e7aafd29386f7f76d599bf5f675667af318e9e519b71e57d0a84b0d361f29ed675b465efc21a85dd16ab3ce0c594d0f5a6f3fd37c02de2e03ebecec5780b927fec28d191bd74a2e35ba4e5d3a3197b9c4ff2a439a5bb5037aa273571649508a5c154ea8fa8e279122b1344d8ae58d9fb83072dd7cabe9febe33a9f57962373e08bd4fb6a12f85af1fb72c44045d77eabe6ade4829d8c3495608566f8bb3b66a8a2359e916a3adb0b434ffeaaf819dc15e15372da9cc8b09ca87426c512414366bae33e963d0e7bb699075e9933a464d21994533056d8969a31a3495d59e9bcb32c5a75f90a07bf8c73356e6b86ebbb68e5fd00344a5058f6828f5921e07915167d27bf3a3fab09055856a8c270645232ecda0446e5b46a3a1194e0a34493fef933c784ca6c5cfab9bef7980e7eb2abc874c7c9f8c7795ced65404a5204aedce3d6b6613a0eb207022d74a6d0003b2ab23452ebca5e03a379043e20ce9f4e316edc70def9a53eb0871a6a6f97b3827158a1e7c42c1807d08564dafe7972d68ee2bb834899be5789b11c555cc5f71cf2542412fdae83ea566b1da32dd33dfc57e80a6a588ab890e50889a1f8fb496f400d5140f2b2302fc7b28523497c3f143ef73f92695d227f74f608fccea828edcd1cb01e3de79b4c86f431e7dbd524b28698d19805819a779c1200b2384d243cfbeaa6e759af33d526a8aa4d5f6e5d5bc13546e7b7887f1ddce5176ed06ab9c17effcc58b089883e293864d04ea867749165261de9e25ee6b9d7a37f2171681fb8d486deecf706fc012be81d1423c19159a0f587371fa846a5723b8fa601adac2c017ce669883e93310baa906a610a369c612bc009a0e9c2423d560cd89bf8fb5aff050ba0bdada84b5032b69bd08fd8dc2e3f64ec0691fdf2a169732390d891c835b5bb4cf7c28fc282071c3302f0fc9b70a6c258c14f3e49371c5f3180dae3f63e0571a8d71bde19299e1dba68ac265cd0f884ca616027b876b52d6cfc6e7657808acb5ecc27f7b1af7e57bc823fca82b7bb18db57732eb2e8ea7a406401dd7ea5ac24d95765814e9c1e4693e01a6dcfad64eae613f6d7eaf612a24648436dee05f02aa2f952ff2267f466eaf2ca94761a6c97854779a7a336c442092991cc0829dd2936328eb5efaba7252c4adeb31897589b33327a128e1385d5e3887b5c5f99e9bd1d93576a08df8d2dd248b56e499caf627a9556ad0e24cdea8fd57eb376deac62d38c7b70006c4cdd777ced1a7ba2e789b5c0bdcba5d302dc48910a45c0507b96c29e396c68da3cb07677f43c1142877d9f450e12d7b6db47a85baca7eea7fde595393fb394c1f34369aa4967bce405ba71a2d6073648ada94995e44e344da9cbb5fdecea268bf712cb848b11d11fe8cce76a842d23f0f06d86c03fad33a9e5a59f4cdf7490c0be8b16a707cef04eb7316afcc6d933485a210a7b1d498f45582fcbb665f765e8c028d5826df38d08e76466d9ecafd6d731502f170ba799b867b6c5bb3ec7186c927872971c2429c6ffe285a28415a0f61c777f34994bd57baad717dbc8781ad4bc069855a0d53911774821c71bbf045d7203655434aad4f7880d3c98819f0fb9833f916e7a8b4d70d3e1d5b811e09355e8809be67c491ac485c59e61f8804973aea0081227bad95d9c6c1c06154eb077d67b6d6bcebb195b2a9cd7d89be06aab94b4571136f85f3047235f21843dee4bd2506464aa554333fcff535cea13d5b9bc0928cc16a861a15ac439aaed52cf4a52ac1b619dbc3ae763699b7f71bf3d36ab0ad7a3455f63294dbb1602cf01b5ebddeafcd3276472de04683ab0c136e39ad835fe474191e620a6648eb997ef54b8daa97349c5c26d803687ab71370f8b6eda070728a9c46d3feb7d0e4fd6600e29ef443935f77a8d869024586161cd772b171a8cb7dc8b6adf83511f26090854c7725799184ad17559a7eadbe1cfe5394815dcf985584b5178048d3becbe4cbb0a0a602d611e8b9761d427a082efeb645e5ccac801ab78c391f58ddf0454a1c37a2dea60b110fd86479b8740b530446ac6626082cd54b843c5ccdf82fd7aabe0804342cd8890aba473946627093f84df28bd4ab438e27e348c010ec2388ecfa4d4e125ee483c1a746e2560edef6c2113edd3c5d6e4b33f184d287817560f8182b09df02d8061ce989fe4f3efe854bdbb9e3c7e6551657f8191a9b7ecd67b660ccbc02e15b1b03c7391499bb784779f28a25dd0d9ff67b3e2f20b4add2a60f1a58efe5c316c95e887ead9c4df34535a0db2be79bf5f4870cec6d3bd42d24e98df62aef70b01815757f50bfbb1785682bd17e4b59fe1663aa7b8896bc86e92e02cff688afd21010d665856dcf8d11f8dc96a5730c6366f86002f92f2d83fec4c10bda184924ba37f357d50b4ffdf1cffb5228c57fcbec2c76aa496defcf6a9512d15f07074d1d73137ec602040e2bd3d02c90ab79dfafe8339affb7c91035873d490bbeaff641aa7f02e2bab669c9eea24d638cc147b715e1fba37784ef6884899d69a309c62fc5800ab19b2db6d207bbdd2cd020aa0c99c6f4bd14e8130a7340f2fe1fdf66eead5cbeba0e309c4f05610370de5eb0b75e2aaed6f6bd25233ba873549bf77d86485b47857989c347d2dde7b9204302bf4a57507585cbccbcc60945f27d1ab40080c7cd9a45fed4c25577b28efd96482c4794032329dab1dbfbad93602e10b3633ce67782db5877c976768c78dd2129063c28ef674ab17b8bc2db832717a121b764026ec4a15e53621d249cfe28aa4e56105c3bc95019d3d103da3dce9f95a705df61dae801da119832c70ba847fa5f24ad7810d1a3da61b7a6f3eadd2db3ec54974be276629258709da800fea0d90376fac49508abbccc44946eb2dc2bc499ac730aaef72c50d1f4460fa4899c0fe256df87f3f5d087bd80b393bc54009055155be567f3c6da242b16431fd0a363a5cb440b51217b02db74eca931cd14dfc998372335f09af8f81df38e985b1e9ef4dd1196d81212f6cf2728fa38cdcf799c3cef0d3f780f74f5d3c63637b3738876952e71af52ff2b40a14dccfade9926770a6cebae128d15c4547884265f0ce9d4fa84e40069a869d7eb44689f87daad02089405a9477ca20da8efc08ae74f89b615f936f0dd77783c0cf6164e51fad7e44050a2599d45e477d815328ed4a630c2ec76b1a938d21f160c4a1e2fc2616cc6ba890be69e4ea3abebd12257ad78a5a38ae49b530123270df427c76b6477344f7d258977d200a651e90ad710fa2db1fff4293d15c1d159cacb775a2abfa5f628910e6234c0c3b710dbaa9adf442d1c7f342fdf18ff447bf599211f9359517cf8fe2ade46f0009c90c898b3ec11dfaeca50dcc98443e45536670d5ecfbce58c68cc6347d5ea1d1e7ab8bc6a60ecad2e89531a42801619b1333c235f0570c7ef20049fe30837840576b3fe06635afe666342d09334fe4b5597204b695efb616dc7aec8fc085e9b192bc246d11ce53b1c0127e605c985e20a081b6dc602e719cd05d0c5d8fde7cc2242ec7c11b414ca190a0943de673346982e3671bc18bc59d984747ebf0e5b7ee7623a880f536f142527f1d166358fdfc7b3783f94c9008857e9a1dbfb6340183134b252131f7f810700e677e75c21165f3ceabf963c3d0d10df60c4d3252104c8605fc36543cb7a893cf01da7047151277492da099afa52cc0cb8218e7c83a9748d46c14ce5dc65718317abef5308f9e8007a76370ac0be5218b1da758ff06ffd637135122e38ab887f640a07746d776a8baa7c66b516b96cac53d7d087efbe2a8ce708fd4569b596d6de20378d283175bb28d5dfc284abe070a701e78b09fec4adb0205652',
+ '9cef2854f7879c830ba861f6a92ef7025fb98aecf1520fac075190f0247dc464d776e3313550b8c38681f8d03ffdd296319fc764ffcbbc5bd5524e76579565c8d8ee0c05cd4b8044811c86030a70026c7b5ac6d75b9aa88f5679d7d933f56f32061e4ad9a7c01a8d6242b498f0ceaf0ca6b32e4ded07facff290d4219fe5092bc9d046b4abf7ae2793bb2a96db6100ff1b6fa8ce9d59dba4115dec5782ac5b3a89caa89ec13765987a4f8f05b26f1ecc4159db6353da72951baac882c6938ee7aed5179fd1ca666a81c68cee5d4131fb7f387013f7d0e82a783d0faa52e2cabaa0b93bc0c3f6da240fe7c858c31f60153fd30ee7718464eb91de6ad70ee1363cc83ebd087f04e94f8b7bec4bce50d3a1e8f35ee5a93e0f61cc578112e2d7fbf48c2c4580ed3cba725f4a9fcf651eb327e260071788461013926ffb62b60e406a554a7a2ef6e57615f21c8aed70cf282f94e2b285f4e390894ced81d072cc2e278c61051c804844c1acb954acd8ac594268410736d65eb74c609aba63fe5aedb297398d5274ae4dfcf5e9a61583fd6fe7c544ad3217fface3383e4f234200a493e09abbd6bb5db9513573282e5a0a91a713b54d819d0e98d63aa1e71827057c53cd4c37b23b9eaf45d4208101451ae809bb5a8e2c52d3a2aed08a2b4b93b39c79c816a8eedeb857623ab85a962d779c9523fb19391d96f3f261c2c2c36f502c8d38c2b7908bf5da2fd0035af3f867d4d3b46295b3e59eb22a54125688a425561885160ba22a9b6034fcc82a229033b84cd656ab9f943408df13408ca3197a40a116db4d2ba2f310fa27d1712bdb237187f29e030711a01d9705140e1bb59712b055d82434dd451308bae4d814c37e270c6e0344f2442a18dd925884f862c3f5cda9d739c4c2d991e61bca07e72f8e0164b44d1769151a223622d29543074711817a9e33e339f6b11db44ba5ac069928162a4423736cb7622c1d4bea038b6b8d5331f7bb992ae59b34ec2e5a6932e8c4aa3aaf1118314a0146ec8c2b40d87791cc34a879ef7def78b32a3dd0289ac3fca94b5888604c1b260df55aff02d5b34772ec7914ec1a5a7023d83eacf02671f89ac4053154a572fa07a1800e526a67d5d0c1343599ea6eaa0b5dfa99cabe3ce1050f7fb4fb2597a5be58c401377fda636b1c9f83fcd3754ff321393dbc4a6adf72938eb85eb2d14eba83f080bafc551acd6384a720beb586638cc240d1b244196ab8f9e583356a1d8188ca32e2c920217d00a9480a947778e4065f4b0ede12b874e68ae47497a83b9dd11bc0b7cf83a10359d60fc43b034cfdf7d6067db71ce31985075d39bed4d096c4aad141f4c6c2e8d8d5a3559da12b37c7f0182523a9c3e0fd39f7d8943503f2cb410e892130abb3e36daec5a1993d19de752a0e0b038051b7e85b5b0015fd3b4da61f963f0a85ece465d51c2e32a92ba42de341587041f419dc31c9e9bae6f8b2a2ab70349c771b4286c2632eb698f582e0ecc5f0392e528cdc202b396de5b261fd5a20ea2eaac965981da2886b3100de55eca2bda670f279b2d088d77622f0c47fd6ad4708467db5638ddf249a9dd321558ce1a6fe2f5c4624eae8ab1dc63a42f0c41f357272c09aaebb24ea8a03f6b4a87489928b92195dfb16549daaf9184317cf7f9bb356197c434f78c2858cf2fd164ba26b93c2b024fefbd29564e2132fbd9dd460141b10c3e8f0e494d1604e6667b935185fe7a905f7426d7b95aab26faad6266edde92dd766466e48b7a692566268488137288d66ac922a3783787e69a6237dac56e7f92084fa21c67f874735d0fde68e62ade3b1e79413b17675fe86792ce202c63efb070cb402f6712af46a792314272a9f334d6ef02fd2b8c9ba2eea985c587715bbf2c41b1bc0d5b8215f2580dc34d52606a2094d8680909b3acac30ff496af95c24a76d8730df258567b9cbc459dac69e218825321a4451307c0ca3bb1a5b7e69778e89312c311331c26a580574af7915217a0ab0727205ba87ac8c19b6bd72fc3e2e3f301cc7a70fac80a741b23fec5db072afd40ef6998a55e844cbad15699ccf22ea0470e753879c9913f9811082e0750e9f0d5e668f587d6c88217bf7e4b3c0f9343ec734ab31f920389d7fa5ff35d5aa52dcdf36498d4495a0bb91e956d9aa0d884f9e24008778927fcdec8493e658e255c30fda7a9171f0390a8d4e4296f0fc60a7542a0600617c73eb7610f34a852332407ee7751d5bf8bb9edcbb3a542c3257e687db2e256a4a9f76aaff9ffad0f952d59ad1db79893ba2d8fe94a099b24bd87da7abeb7ee9996d0fd984ec7fc2e14202e22e105e70258959618f07e029a55ced4210c06565c56707a0658b31e1578a58e350a604b742c980b3fee2c008db7fa5d5ae4f81757b181e8e05dec9a2e896382cee2f24b51ffdff5468c3a1c65a9b47e0d8db5b8f16fa8500ea69af8e00f0311d5afe36f0299115c1412d0df8af4a43e225064c191578fb9777be3c192d12c4432ba5b3fedc2d74893c818d0656071a581752dbaa133de2b0523f27cae988722cd4b81447b42d9c2aaf637775cb4c4bb1344392c88c93daf9fe8edb19b2cbcb38ea97d0514e0bed141e3585cd8ba4489be04b09faee1524b2d10fde9c15572ff5e1e780a21652f7e41d8a10842389a166bffba997b45c80998e449aa88db510f23f09adc089ad3d0901d3a0200f76d6a037b7da457bc45f1c6acfa7f588503eac7680a02334453cf17fa4cd27cbf668e6cc12447aa0d710aa0b037ed991c2d9830cef104082e5683beb7ff011c572d899019d50bdc01f65c0e37297eb3697a22486a766bf11f85f56e9b7a164a89696355cab876556079eff98bf7b90e318f8ff583c2be55de882c0defe6996d1bc225a51ef7127df2a5cc47f2ca26123f17e72163fc859c34063084fb6a12ecd6e2d6675bb767bd7e1ffae2b5ca4e285ab832b3504d492de9a70abc072f0a31826e7e83ca23fb7bce9281b01c1eb8b6491a799393dd9072c514c19c5b5d09a3e71125e0b3605920a8a46a9b6eefec26d5e6c4a974d3e5d290f55d0b3f1b95aacc71d4685e990615d5fdbc8af56473dec6c419dd3a57cdb511f1e7f3fa9138aefb36930212f48bc11d467f64f7a6d448e45b82172f93d28311b1663bd5e879173a0968961999718b4727bb13bddc97caf654063f99ac7ba558dde2cb1848e04e52925d0db9b140dd1d47797e8f2f45cec656ad3dc1cd5081ae1c638b0bd8f6b90b78794d3b647e1e654fc18db4c765bb1a0a80f9ef1cda80db16f263f5c142c12c63c43fe7d9b5b5e8e6992876cb7ff9bb18865b6bc1a00aa674963b202a4dce7ffd47cb04396c0c6eb50b371b641cf07127c84c7ce52b88d8de58fbd23c9d49caeb09dadcab2c8abf641ea2ecb9c30803df4cb26bb0016039fb3e8b541fbc98d6d81252b5587c97a29ada5131f3fca93bea1c778149acfc917453d3eefa32256b6a6b1dd868e49be0ff482d32394f3bdfed408103d9b4c2a8f09cbbe7f5855750491b218d02ecfe413c6f1c36b44526b893beba3c879f1c4618232c893a3f954f1e2a481b4e1d298df95807878b65d28e81a008737cf7713294ef0c1aeee7492e91a178cc75fe828cdd09d76be737a72962ee55efdf4329350b0845e33c06dab649879be69f187206918c722bf0440e50dcae88f9d90cac172677bd2c4a23b0afefe498b2f3f162c2bde9c20fcd136dcd97ea89d992d961a08c435e3d40c633e12ac157789a13b8890826ae5ea72d2d663ca940d73132b297e5740e5d4778cb14a325c080bc06de231a4d1d62a518b7e473f4953bdf9f06c6db13b7d587bfe00f0218bb117f503a589c65f961f05e720ee3d2704cc3a9c6f3142640ee2726da9beea230740e7b36f0367729a4af86c5ae354bb95306d758e738b891ed4f3221800fcc07f28f0f38b28f8a95730b191a8a11675898cfb2256af0ce921d296d1d860e9d28d12b92aa6750a625162c9ed86c1d2f356347c19544e4722bc5da5e3674931e7b59098ef3d720d3c1d4399d661a04aa38fc958c113cbbab442c8d8dd5144555e9d4528a7bcaa81a51f65b9f2e5c6ce04aaae39bff1b1d82c59b6883602ccd4c58882d0faa089082bdc4b92b97fcfeda51b75677c8a9b4fd965a93c74185d20bb1bec3a4e8587f14ed867cc909c0619f366918a7d5ae25279fb137e1dee7fd98ddbe3bd19d841dd7c984cb01ec723d37e20951b38df21b05c9e87c5aa11af6fdc3d0be1e315213d33a06cf5ca9d83cab3cde2824573c3ca1fa4689b9f1e564424a3c74140c8b09102653af61a6bb04022b32c6809d5630021b1487863511f06d5c49843a96f7a69777b494994ce23d44994b5352c606a030159b9d4ad7664188e0411718385d936f1371a68a0317907a6d72f61f3a153434ce20f48b3eac009abd6a5437588678a0e4d20cbe3420a4ab8fefd771604b931530eeb3d4d2abd4acdd0d641e603bfb33d01eefbd45c623dfe60a1fcfa26f66db224c03aafb2b66c527716e55b642c72fc19f760da0d1b21e5c0bf6c2674b548e8b810c9721f35ded83e09b65c463829c9e9bca38ab09fb71d83983d118a5063755d6f522accc622cd9a013d5f068d5824f5b12c6d036a6df43deef841b4623de6793e7d4047e1d8b11fafd2da41767eabb27773d761f5b7183406163d9f65489a900093391d13511143681f6473ae1dbcc472127048dc12e81a702f7ba7c4142036484c9bc8d53c7c89cc9b74197ea5e6902370be448801e255dfac727a291105d097f1eeb791eb31674faf8f9f72b7a7aa1e2727d18414958a9705b862eedfc9f3523b8875e3fdfe6053f42d9214b37e86c49e337c5acb800d28c3c40e9fc0cb1319821f9045d532198be1b48dff39d99ab95e67a166872062178f1be9b674a7b4505e1a8332116ad759f0eaef7cbb576a6ed03aed41e7f53de5902670cd5bee6b8927efbb3322f74e40ba074327a8667a57ac33bc775e8ce515af8a203ef6fd8d469825c4b3aa95d2d2a5b0058a91855ef63ad8ab716b45ec1a05ad94a5d653adfaf7532c5de894f9723c6bb31ff7426cdd14a016ca8ebed7856b073a7c6a8f5228fdde4e7c8d9346b1f690d8425a1c487ec2009add49662bca283d8e3d241efe52f44dc7aa0a1f3f245cd5f1bc2a71565dbb90b4442bba4d6ac596fc62a371270b73181450de77471be7917bc7f8f03aec79d6df799659d0e9dbda2177692450a502d3b12b8b59f33b1f59f30046075acd752998f819934ab9d34d8a05d5a6ffc22bf72a749125c7f47ca5d3ff82252a53462f5d4a46151f7d3487a2788987a8f546f8ecd6707939ca77fbbb004de84e20555eb8da7c4dc386880ee759f544d080ec5f74cba9a2cd3fb9c1f4dcf9bf2ab73b1e18435cbbeb784649d524994d0b27a4a16ecebd50f6c68aaf3dc02618448a600417ff47cddbc4d7def852e62ebd4bd855175a2c024af18309e2644382200c5c972478ce1228eee524dd8f7c586b502fe11ae866254e333b688f33e29b41cf995dca4a60275778d6c1d114cc6899e6f3ebf6040c38552e0c4190b973b22e469ebe75deae5bfbd5351c8f9d46bdcd72cccc15378eba04248e3b935f87754a03e53fb3cff94e6a9678bb75838be68a86230814fd5e38efc939ad03b09e333989f5580078e17d483f1a251f620c7135939f3651cffb235c8e872c6e3718aa514b57ade873e746f931b1cfd9a328dc631d89cd7819f607fed6ff203f6d971935ba7497d84b8b5a1200b83250e19186a7968b33e485df653b552a2ef3be8a2e6b69e4bc6c6a3e25174e95e30187b70e57a10c10237e07b9866b60af37c4724846dc2061f14a80167d5de368681019e2179f94d8a17d1f73849c5b3757f9dff57c83a04f1376f1cca8c12928f1052a904c14adf40afd7721aa6a724df0d933b460e2fcda5f89f3a64e1acfab28f17997899',
+ '1d91b86acc6ea170bfcf187f773b577b95e29d36fb30779d2ea23e2ffed9e1b46aede42bbe03a904fe22ef8f874298b5f4a6afe63f6ca9522863eb5cdb1c8d4bcd445e43e7302875e6ba3592024c1185cd3a92615f551698b0bd0c6f45f6b6ae0f3e2c9c901ea52a3f40f26f2e804b54ea454e91a21245d88c58a84f858fe344f884581d00f5a88dd15b2e0e5407cd8b1170ec5c52ccbe7885ddc7e6e30e9c754fbeeaad81dcb90563b4f257bb081f900b6373acb5aa0ae263f4711ba69b69a9de94e83659fb61fabff24532adaccacda0c5eb6815d5b07cee44afd61860d3486bac5c9fd17b27d4abbe3087701b55a8973f5d78b87438b0ee7688ff7f8261babfb14dd0318494fcf0c0ba3e4d7c488bba78d0c4e7b02be52a31057f242c9c68a249c4b0c13d2fd8569feb3f8cf72cddcf194c33e9b110826b1e2d3c4f840ab8db1cc829c9cf80d1a404cb7275b688069fc9d9af089e6ff179e5081f48b2351334d3612290620f50a49663bf50dee46ef23180208a9fd1991c2d9e1056dfa5e2731697845f1c65bb1cfeba0f649ac87d90f8486cb8debce21c9ef8f8c233e08b618c73357e28097bd5e3d848fa10bd9b40c73a7cda80bb3440e11dbdd5d57078c6defc1e35ac83f997eec6545a684a30cb465c3838b053f7519e1549d4eedd0f6ab43b27ccd15c9c29c78b19cccbf8a4fa1cb88819940e93187ce9820aa5adba14b436398abe1bcb55152f8198614e5f93f25655c75473715a24a052be236ae08e89f73ab89c48f0e180bb730551d4c95e6f3c8588190af7e23e42a0378f9be89ed986149e926b7296b236d65cc4124a253c7402a50b5c8e7771d853be12c93c0d4de9ad84c90db93c50a894e6e1914bae0006b26651f09ee06568559bd47b43a28c2aefb268b52d9b051a94e8d1d832c264f87f12144e90e6c3fd8d16fc3965273f51c06f98ec367a7692beadbb6f6929105450f37bffca51339ce377b38c0a620d640a0581393ca34e1bfa7cc8df56abab220c4faf8ab6e95c4f3f0520fa9a1d9f6db4ba4a24a7dc33ba309c1faf6310dd689d6fd777bb75e71d89096c0d7d1e9c73a6d71c35dfeece794856ea67f71f5030ea9b1c4f40bf7b0fa9cbbe4d1c2ead7fc8a31ef54ca1c65f2af24e9079a1a981db1aa699af8dc12b88933f41dd147a98026879eb56bcc374bfb875b1535f936458369cdd6fa8617b0ca91671299aaac63c6c54a066096fc1edef8752ec1eb4df493526a4e8d802071e546d656986e5115ef9d89ae24e6f4d9de1abfbaca9b4fd96606e7482e44cd5a7de69a603a1d58250552c6334546e21b8d40a35fbc73cc328ff99d8a596e9f08d8d34bb61b32020fac87a83c2e312432411cc87413ff43be55ad255b9b47e5dbaaaf62ce9846ef4449ce780f6c303bdcbe0187ae6da836cb6b83f752607b625147cd68205db680417179de437bbea97938816955260925e836308bf54573651854dfd441c81b55a5983c436ec946fd76653f060ee99c81a35a156bbac6ca9e9f46393fa953ffefef42683ff7f1639872b87cb63204ccea7b2bb51f2940db5f34808bf2cdadbebf6ce4903c65709f1aeca6dd22751434b0de4f920eb750402796b81a963521d234cd1336c13dc353e552a4d2a33ea44e855b2a2ec2eb817398244197a2665cf4f08e42ee56f7662c983356ffe0f51184d860300dc44c30f0217bb175afe7bb71630ee8096608d573a40d21a7444f08721a8c15919b400b3043fb8c27072fc9f21ced972a87089dd3894e998b4592e580cc4d3f6ca06d5cde8022364e50a504d18e98c4c4327d2bc6b88632fe7d67255b8e0211f18c3ac235568e443e04ee089a18aef568e0cd0bc7c23262835442644ca07931b2b72ec7ff47ae4a78ac7121d67b8ead8b2a7eca4136e1bcbc529b1eeed3e11342a9bfda76d3f09da0bfc4fcf107b6519d7808c3ed76f2a5fc0107b1bc78f83b03dc7476367bf3c238a75006945db486223201a5071303c2e4d7eec920001d1d88e7c327d8b0366c92fbb8bb1ade17bfbc10efca388b9377e95fd6c1d419ce3f4424526bb80126d1524555a70f194e62cd7d29cffc5100598c01463823269a14c84a78be7eef53b4f8ecbd36db8fd723d8ef5602cd03f8cc4f54c398a7a6ff4277a2cc9c77fb2b6bf98a66072ab2205750dfeb2f1504eb6495c2b56fdc1b7c2cf4c5b4824d953c8ac676d6845720d881d7d75f917ee4369711e3b22a3b147f58a23bc70c5a4df586026a853afb4c6e47d05e29c6751288f8263040644f02973a127d8aa74895f4d21fbe088781953ffddcece05a621040fc5520d68a72665265c7f365cf72fda91380e9b71684bbec385ffc19b9f08e0d18214deb195fc01f402545ff01740580bed88647547ef0f17bcc141c619acc3bd01d0ff4ad61907c7ddcdd9cc9c61e2a38791c0fcdcb04ce2cc3cd8eedbb5b5bb89aff99932277e8633132e5a4e3c7e415050396ef0337f0efb970d7baeccbfb363d9520871cb6f194d4de500f57937cd8eee56344b23af8292fb68d55a99e78dd87595fe5aafe16773a4872858c0122f8a939fb4b526e526d88f7265a7c337312eac47e3b67bdc5aec409b3940b719508c659d57f6e428e2177cb2915df3b787ada5f21e4dd769d90248a99b75095316db8fd785d507809e95e9b1c243d06e789f891d19e7698ecdfbe43ab5bf5cc86ac137d6a71c34f5429cfff561220364ea4a7f513b4cdc551c203ed5f1e659813584862023911590b672e508d4c233a1c6f8b015ec43d6c6afb97b02b6b1a7855d6da33f63fd5258e25fc47285eb092ef5ef43f19496ffe86e0ec6496de9eedcccc4b6bbcd279356afadc4b9da652c125e77616d9b0b01c3416645337c56d88f68d1e91acbd97f9003a20c673df74655e8da32126a6b815f110b20474cc00ba51bdb62e6c4d9be10c888c503156bfc007d5edd67677d2ac5c443800d45ef2f26cb2c49a620f0f9dee4a51616ca87f819044d8bf5fb0ba1fc44578d0ecfabed1b620ac7e346e6d80041422827c414e2ab64ed63428edb910c778f6ed153bbed8bc7be0424a0830280c5a623be6ad961bb87878ed889b7a0fe47324cdf37e8d67ee29027f1958f20655a1b2e6426a01e535462333f526576d99b8a4ebee5fa50fc9bc758d28dd1d9b8e7e7719f5f2fc17ab3c87bfd53adbca55add9df8c3b9050edaadc150f012d680535315fa7e4ff1398cff8e9cf3591a6a6e7460153bec9abb878887f2271abec588a742fae9c85697c75093a4992f3731be97c09bb45dba0c8aa1d54198b13a906d2b1f290962999c4d03b29baff9bc01328de51496d20b07cb40d1c4ac9ff2e8ea27d50e46e562500460150b9c7d50e3b2a0f607481435b633ada303cbab8dd5e7e28b31091858bcd5afe17bc8849cde26161bfd34ebe121a82f74865e9fb45f4ca56a1bb8c424e7a83741749bd548fa76387e7dc11eb74f130f6cd6cb410e8f01b89a53bdb16ed966415b7d7d3afb3f8b4e440f57775e485d96b27a7c5a446cef6342617ea7dd9bf515571ed6795db64ba0983b5ebc7f146c096ffad7b1fdfefcee8bce656e19343ffb5edf0e5b17669f75a08a3eb6c1bc2ab3cadf4610e24e11a09c21d8cdcbac2b3b98498da8d1586f178483602dcd477edbecaf303d9a6317c29ee2418e9b0ca01c2ce3bb283292a4cc6daf2ae94abd4fc8fcf5fd01ce46f49cb89a0777305b88e7423501a2e31e24dd839405b1e12687c32336142426fd927613d0925133a1cae504c8c5e08c04d992edbc5a4e3a8b0d1489fcdf6e4992d798d60c4ae34be64e5b982370a8d44aaa32d4af8f89fcf3c90355cec5a7e00cad492ad697f72133fc9426cf6bc363fa7e075ff30e28cf67a3d8b0352e969274fbd337b7e1535c8fbcd7d7521df9e21b3f57b7123df35dee83dac1b8820408d1a97c243690c0a503766bd236ed11f9b6d46b039486b44b905152b1df2ab9ea2b9e8d1adc0c06a4961299400245d54fd2258b6cff50314455f588a7328c7ddf8bc44d402fbded005078a9493f8c0b8d771afe1add0233ee4657c4cc3a11188ff80206bfb90c3d623990e31474297c5aad9b0e34b50682f16a604e477e2151a37a40febe025fbd715a438eca2986f05f7d9001e210c3e6f6caf66d418625f1c319667f66901f36d6ba77f492e70a2f44eee1204e75a127a56c026be2db83c196de5dcfdeedb5713861155b95341d00b00976b39d6c080ca55a6d8e5104a586c5d00b364fa187334058060cfb9b272c4bd53702fa7d605f9f9c1d1fb789f10bf7f759fee132ff4796a6304feba1907cbe5a0d548b3111e63a38fc653bf3d117d55c2f6dbb2a8474e1537d6c8dd0c1b5b1a0def3780f836a1f38f1aa06c9ac71070676cd06117d81c968d4aa0aaf20a2cbb09425eaa01fb2f5a3e3343f93eae234fd1464e96d5437f8ec1c528ac6160ab5911533096a4b886582bc4d0fdaf232044af37b8dc8705e13e73fac349e0cb4174b4b65fcf770b217dc633b9e242e2921f4f9591aa939bec562240031be686997e71c02eccff7f9b2b9c04613fc058e30031048b799171eb363b396a9ae93f1e06c725400784625df22bbb897e7df2bdc801f8e8c1f724788f5d4b5c3f7f61498e234a1617cc7fe451d3cd7516f24c6ca720e74c2c3b202ea1d6fa7a720f89a68514a323663e14b8db52bed6a1b3d28a5e1c542810d3f1582e56cb27eb1004af7c29b4fa8b3fbd65eef70400973901913d62b40f0868248f754b31f703378edee3c113fdff67f6561d5f31857008661bc572ab638b5e165f1722d36a42dc74bf4c8934c02b3d4c13d6e9dbf7c4988c74a6fa9eb8022c5321a48c03e4327552cb26d0abc397362b29bc2547c9fd7fc1462239128f156977917dd558174a98a58cf335cd8aebd910023da0196e8304b10ae7dc8b5b3d8bd00933ad545603cce96f42272e88619c9727cbd8d5680dede83d68437daed30a190346526d32e6789b0c9433434ec0f72d14f73de048f691c358240c6dfc8e7a9f0a268327307b3236cf59d8a030628f4e54305dee83c576cee59ba5e0b6b843fea864bdf6c13a7049321385d9ffcebb776017a7349b032387503f9e5a7fe8fca448814170879cb94edf41d934ef7e1244c30da87711381e793290545fbab91b2c74fcdc18d62a73c41175316591f90a12e792e01a68ccde11073e7644f98115e3b847dc544f5c4625931e2cc089b8297e6847dc931439d8db488a62cebed973868ce2d0b755ba970fbebd39fa2943f6f3af8cfc75a61795546114a8316d3aba715d6e47a68bbdeccf0ed2d7671314d8ff37e2ce3680ee9a090b5dc531a72d6130c44aafc643eece0ffdef838bb9b3661d9bd9d05a57176d7581d63833df19fe413bf8778b30c5a12f2673dcfc9f9824bb35e291557b3a76067f0e7fbc8788f83f3ef84d79b4cb0cc902f0322e374b7b74b08d9fbc7fb05a485d771a303312c56747da8ff65277775a0df521950345f0c6764b49f3d72170b797a0763354201c65d11fcd958c43674eb1e329c5a6001b2d019c2e9004065fe0d80b423a7d3933c7852864dbe4c75333995ac93472026114ed00bc25a8c77e307927dcba20d6c1e8be95044dde6bc1951ebc7e6609e5591a8319810917ed62330576c436c1713d55f7d62a4ffbb948efdc98c7eedff169aa8e370bdee400927507888734d1a10cabcb7c2576af284fa03d70141366ef194148f9bafb9f798562f9cd9438f3eec64693f7a4366b415c62cbc301882116fe7b5dc22d03ace0c17946c689c79aa2e0a30bc92523d29cd58402121eb1b1017fb53730c06b9ebebd4498f3c6452875e26d7d7106b3578371907addba347947238b6fb613b7d76c0f414ae5d8563fc041f2737fe7598cad871490966264fc50607a51d2956cf9810dfbe71d4e5f432a95de884635aac463ac9cddcca5e7bca5ecee9816d3ff77865fc0f7fa86e4c51d448e2684801ba15e4875ccf0f3212ccfff64ace35de3d4046b5ce81e106b5800e48dc89b452095e5e15be8a3e895ef273e890ad871be8153c71c51e889775e7dec5a08faf35e34a31d9bbb45f4da565410b83c56dde4221ce99ba',
+ 'a24a4cc29e44d50386c9cada21d741d35cf8aa713c6a5f72167e7c55023ef01a8d52d449ae25fc35fc43cc821d064de58271802b515cf37da3d191e2f0b7be05c7ada439c339c7baba22e035371ae88b2ba073d3df253f9e2d6e0d7ef039afc9b923639ac4c95f192a2eadfc575d394ebf4f296fcd0e6c5d1c1b9631ea0debedca7cb974981decdab22edcde651569b5a6444a0a035fa242d9a1d404c67c99f9617f50d0297d9586bcec14e44a8b9f4948487fa9696008d6cac871fe6ccce275e8f6cdb55e3182a4af2efe15ec0704900e227056e759c7a058571228c545edeac6a7db2c1f80dcbcf3bd427934d0c0145e9cc41865910628eb186188b731e3e0635a203c54b4cb56f06187180a30fa5da17c23f8cb51eef7ecd106292bd6ebdd27d944ede51376fb0ee175fb576ddbf1408b37fc01e1a794c14ba9100a7e2e6b7aebf047bbe60632b507b52901d08a5aa191a52eef895887d44ef1473a6fd311c457cc53bc74a2844d99efaaf2a12f202e5618967e912a598ca286d5a5f103586de67f18d10773783e60ca871028f4c94ea1363b94404491500e11c2314ee6c6dd60b29ee3e5a196f024efc745adffdf683ba7251adfceb78a5b3a16c8cca3e57c8d0ceed8575366cbad0672787778eae6ed145cf9b6f254a151a8da51b5633db334360f9aa5b17138c2b69191cf88702f7d25e9170dd6effdb80416b44f4d54e81fd7090f17e43e9d2da72a77fd57fbabb381d35e2ca2100658f5d0d9e38ab4841498e521f5145563b4824814490c12c259d122b55a7f3f24f92412a8b841e5f0dc21aab786683ff320eff04ffacf3edc35bcde03e19505bf6238e309189b6d933f1960b8130983b338952c105aca056111eb1032070be93f5cda4ce449c0d6d40428100ee41fa90dc61d033faf22f2b9b305c0291509740cafb2532194d7a81df5f7c1a041d13a68902e7ec542028a4ce3b3f4d052b92c8e236703a8410869d5d82e7b567247bd2c6071a3885b9057836b9db60f08419b2d7fa26161987da36376754618beaabac0fc8e7c7142f4e0c4126a0dce7dc949528d0a7734e15bdd3197722bce6f22b98e2c2c11683e57ad789fc302f5fb7abd6313484c0a63cda0a602d03160cbd643fdbaf3685698e14708db4f9b6cc87b7fcada03e8c967f9732a813561b61befdf756c8d8b2199b935da8eb7e21b2cf517edaae4545b3507de25da4ac1b9a694165e0c9f82b38608ff587fa2dfeb71100e364b9397a6152069ffd6b65c4fb6ee6f2160d453f2aaf52ee978b999c2ddf3ea0c84b7b9944f6c9d213d2137619125722431d108c84ca949f43fa4188abac736d61ef8c30c2a1201f1d7355cd88060a7d04641a91cb34bd8e39e0a64294eb377fee200bcf5ef3a1ed86c97d29acbc7978d69ca3cd52804631d5a938689e2e037eb9574ac39e70e7a3fc3f2a191ba83c9c46014cbddf3fc730a3ee88590bd76fd0502ff9bbf57b39f8ca5ecd2a395bcc5dbf4c85d1b5c5a8f1211b16928299c52b4f047926f8a541529da2d6bbaa399143ced8efb77ab47409d9a953a386c7abd6026f49831c717627c2a5e77bd2d433d4d130dacd927ea0d13a23d01a7cf39c6716dafb6ed552410ef5d27fb947be2c8782eee7829196c7edcf151c65f9a01f54f8d20f38b7da4a7e83a2f0127d59d3e2405d8674fc9f41b604f788f4715f9d3624eee57f387bfadd18a1f905e839c26b8617482347fab6d08845a6647884ae71378c1ea0ebb9cac11159eb121cc08089e0a6ad0be83b8fb3a57a052473a1bb9c8d243b5c260642b10a3556b58fa096c3dc86159d61c444d5f92f25c2f7495d2ea251abff8c03eb336fcecc6eb53c6dbfd630226659477ece0fbf78ae77ee0b9e239ee10992153cbebe70acac22068dd46a2f43e5131785f235b58e658a023f617d668b18bcccbfb972e5780c5a816f8804edfaa843c702e9279bd7868228712f0c42fa9b809cdcba2977defdd35f9b6132f6d70e4fc86e2941fcc47004b3394d7caec00062081c474eb211ff00d399e680d449a5bbbe3029013305b09644f0433b247bf5f58106d75f1ee19e779fd38e5b00c2fd0bfae16f01e8fbc69b505eb6b42e7edaafaa24e0e7389e4abc16d0df3e06e382a5210a71b0892730a867bd0e9437592cf4e5ef0a5379d88232db2a4fb6411bc53ba313c7999e086d21fd93b147c98b7b59c6ddaa407d00e3605f48563059fc3323f385d72992200abc748b454b7f962462cf79471a9ca7dce905a39948bbd56af2b4e926ecfffe67cc8f0c411ba409e694523a776e534ddd2170d47f7be157bb2c49a64d50420422d68f8f2b34e14700631199a1985b63729e23537f3654f3c2354455a0f002c1ba5f088c7a23b1de2063602f5c44ff792bd39f892ef4a13a1ea2176fd848bcc7acea8caca474904fb4f9d0641de0da0f6756481df553307b1f07456d39d6da8668fd7e483084071c3caae4c05cf85586b39aaf6a68ce9d6741b940d66c06d67e7d0c6fe7a4ee70b435fb0fdc9fe80c8faf1558070fc3426f254cbc23e5655b10579be413882077b82f7ed4016d5c598aa85ab46c30ddba034845f9de1c8eb30c97305d4440a68688878ab3e72bb1e6f84def5712a27a8eb419199c7d97cf8893aa4e3e02650d27b5ecc331e681851f58ee27a282ab261af2165c168aedc436761f5a28d67ba0d5c0bd9cd097d5527d3d27a84944d16cf96dde61fa7e64f9670444e89028eb2e0b29789c0273bd868b1588f59dc1abbba467cfefaad0b3cb74ced98df68239f15260bc2569f290adc362607422a190aea6706949ca2a40d6fa464b9ede6aee9725f6e6ed59acf534a0b46cc87a3d36926a2848f4bad3a298620af9bfba5b8f7c006c874863fb61c7cd8c0c47071cf41379ffdd950f654f8c467d82450cdc833c6c222bcb1b765cb38449ad945bc95fbe6057959f3a67ae2f122e73e368567044e3c832e9e2964ca47f7def24dabaefdf97c00f77be5354fd4f8e2d0f3f51cd21e1bf3b294be3c7f719a94f6d167a1b138ae9b9b32da0ba73692d3c2d0466f0600087a30dd9e74547dd5c2cf1918f67e6d40512d5eae8652df97c1fc15a0e806b9ab2190bff094af354f72646de436cb5edd2b9548882eb897b0b5650a2a103b14abefba83dd25fa5fb1ab9d15f6e802d42b2fbb38918a422685b6e7f70d6e0dd8b1ed96708cae9cc4a276625874948a97d2678875f1225653bdcc6923708be5cc64210dd025b7fd2ccbda9b8087c3cb6f7bdbe249cf7e5ee701ffd4d77ced29ba6d9505e9d2c8855fd3df30d356fb2d24ce92b3fa5327c0abf8580e5b591e436873516705b96a9c24648d099a0ac7187d7294e1d1a7a4e6644bde00f72ea6999e1f5b1c6a0224aa4423ed0f1ccaacf44410e95516f07d36dda19a92f3230b952619bd0b60d67f1788ba0628a3bf34293f4f9af811593b1adda392ad9662d79dc7087f1b315d024bb5d1e03d7510e61f37d8adb10a0765f92bf9d0372910911b4894a73623be35af960f8437dbe64a3ef3522d674825833a904a5c1af458c27672663f438022a0a9f21df9fc1d69e9ef3d661f0414d91d47d43e3c3c3f60f1160d264e298eb0cca290a2477683c04a98dbbc8d6fb64bbc87bf7e7a875250a663e17cddd2969140033947778b5514f6a396fb7e9076a5e76218b21ee174516ac5b50ef325dfec8432b5b49025da8c737636cbfb4f9b0c2740a9822e34ef8bc3a45287980ab3ea2199cc909a2b5b514b7b83d60b946faa03893894b46709253c68818dd46958b39e9e46849e85208a051845c1b64738a703a58e93b7620b475a7908c8b02a176e83abea37a21b71602ab7433704503f2baffd7325400d3d1ba73fafe23336384359278152b1d596fb41bf46defe97cc5d90f7aff25650e6c6aa2340806673035ba67ad37cd09bd682d298165eab0527652dfc09a301134f73eb8b814d4fcac0def50ba85e09557b1e66a97b601480976c0e754ae0493ec148f3e03ceef823b6f4cb44c89f63ebcbf6845c3d8c3ff1659abcc83a5037b9826d498b370e69672ec3b28cfbe8e7450f33b41823893641da16be5fa19cd26cda0b75f23b53a97c7076314b08e19b4b8efc7e46f60001563c099ca0476c233f134a007f0f65bf4cc433d1eab83189e6927a6b4c7e98a61ad39adff5f466301b745171997dedb6be722218cfd381b9fc61d4029383fa2f74e9f20ec56f3503e6444950a74b9393b9c16d9063217831764433cb83cdcda34bc438b177817fa48acc59261342fe6027fb39c10e69bffb3d83bf4f8423ba0b89ad955175f2ed19ed54aa79442ad725ef66b1323975fd1f38669f15ff4f696e15ec3175268a266cf92364d4a2cbc5e8f94afa6b4a0bdba34e35fca65a1781d4d7c933a5f210d3a59483aebc95ec71b32df13ff4abf401916937fd88ff44ab46b78cc369414e9bcaa8bab0bb8557828d73a2a656c2f816f070b5cb45549e8eca9d7c0b4a7b0a27e51c119358dad2a17fb3a45718f9dec3c94af78d65c3ecd36b71e230cf080d1efdd8d07f1cfc26768fd5407bc2b770af23e1456c6eb3f8212e1b065d81511f291bc43f9b8d541ba8c7c1be3adc637482061ce790ea8c88211d8330b8e6bc07f046c8a610354878e02f5f66bbef67b3e67be3242060b5657a3f92a86988b28f1a86cc4c059c4107c5ce987f27822af581881e4645599857d59c2eb599ef9c7d50e3b87aa348a88e00ac5253a51e1401fb38b593265c9c25da3d40a170a1e09a3966747812c3e3d638d176285e4a8da1fd909154ecf129993029b1b21528e8ae7e16e88e79955ca71646ed477a8efd9b2f9a98d0bea0a77980686731b10c1a81c6fcfd04004479c842129df82072ccb8385db351c5f27e8e71034c666bd3e0daba1e9921d15aa403633d7083787f62c0c1e1cb1d286e17a0ac0147986c07a1830186a52e115f441e21d04d2fab3c287b712fc67d109dd877d86a3f10db2fc442443c0a73ebd9c0e226d21b45e13284b1f14e8eb9a5052ada9e471e17e1d4b3e02b46ae3885528217174bc40d41e7df29e84609190e307692a69fcbf3a67dd5255dae7bc50751f1859f432fcff4e5a2acff2021e574a62268977a2eec51b292d8837c58619a5f75f364c344d322b43302dee3bd64feed98211ae02f4c0bfc52c344b62c566603762b0ed2eb60f1dafcf32c97c4dfd58f3e88d6dab659fbe17dac4966e1ea92c55cf346790cc08ce163479144209e20147e64746fab5d4aeb7b5c3a935e66462d9014b4bf8f391951d2c5b7f3b8e90802bf7c9ba8f69e1fa2b59bbe468b12acc47856ffac5c14c1b0b03643ac7408b5e36899f48b7f65a38d91307d865035e9117d80cf485c99ab886562e0753c424e3ee38326232ff9fd3478e5205b9518289c075cce9c750f006059113458f8e1fc9c9702da75eca4561fd3804fcd42048fee7fb0a2af90c0e7c1f40be75c902be684ecdeb88b9facd6d708cab1e53f3f468e4b45f38996f289329e17a289ec69357e4ade676c315aa4e3818befaa74117604d5e36a336dee0d3bbfff0de8f5e2101219cc902088f6e9cba48bb025cac447d98451aeb4fffc9c64bf89cf8091e0a0c9c16edab08ec8cc18db919d5c279fe094bf59681432635e36067e905aa9a90c2aaa8cee23291776518d675e5975e96abdf0c1405cf06d7a38fca5fa7c26867dbe3df07381432d0ffe21d39a249aeb0cdd7e52dd93206019f309c8b3f0eebf1b0be06112d2c350bea7019ef9c380edef7bd1d4e8c1aa8562ed96ad63beeb9c0d9bfca6731f91c9abd5949025400d363a1f510f08ee75247eb0091db3ec03657cf6fa883d6f95e0ff0f4270c3a22b10165166cbe6236b8594c4cce04a8420618fa240cf19cbb7dce2de73087ef2c1c1ab9a78cf2a6873efcdaf45bed28d29d96f293843ae3adf077bc98f1efb37b6922081efe47bc375ac51fbde7ff0100615431349aba5c4f5a7f358fe7be579f4cb9e8f33d2813e5a02472ffeea4e149f5d34e6dad1a571e105711329e7e2c162b44c4aac61e5e0083ad7d40cc994a1dcf96a2c557b574a8b691e8376299a16e895533cc2584fb1689b2b7172e26abfa5300c6c21726256db1a222f4e0bb7806eb5daedde8166ba626f688e97fd7677e24c432fa67e709eba62a49f1a53de07dc5d0ae466a2d302dffcb9b4e3e463d07b9336fc4c6626280e87cc5c40cab9b41ad50ba9c4843e91c58c4469be5ed7d3',
+ '0e2fce9e123c9e83a8ed6fa9aac879f9b112c0f77c9f963e91e8612a265e9ed441fe26431f26b0e0d3a7982b2f1bdfade779722df4e6af2737ce257a5f349b610c4654a434359210c74359248e1e750d596cbfd559a79bd7cb2bc576d68d4e0eb72fe12b1b114bf9ccea3afc907845014f142d55738963349426ca845512bdc489e0543f9b63e3852cc4c41bfdd15772109846160a350e28dce8bb0ea26bf269cb8235477bd3639b2df87eea9dd3b146e5219280e652a49ae999207b863ff5e6c63c0dad8408d22219aff1cf38245d6716d798fcce892e71055f8233c936cc24bf3763d87eab38043610a333956c63bbebe1e0f08c82b2977665679e33f9810a019abf3031639e28cd441e7f7d54c92cab68f2c5e6e43bf384d15a248c301c7fd38ea91d64d90b762572ea19b88399a1a09357e4a558ce6d79cce02d9b83a36d7c3baa07e1b587d688c38d6b0ea3db01108b96b3918575ed9b7d8321299820bb45c849566e9e1a303c5f91db475995364477379c7114375b340dca68fe1a9a51765e0f72d43ccd6c8a6d7ed32a4fd1278480c2060eac1d9f8aa33d6ae2af1a17572483c4da38a772ba15daba802d96bc18cec05931f62da2e568658f9dffa7f52f432db24a3ab02a14812ff8119aeddd4788b9fca099714a8f84c940f6b349d348e295a5ebe9f17de0d604f5a53fdc725ad73359243ef180cf1ef2e3b7730899e82a440684ee7151653fe21804c46e6399e2b8d7848db42dec5e66e2a6e6ed2f5843c13bdef03990ece250cbf5d0a8984cd2cfde8a2dc2372f6dafa38cb5ff7de05494aec1984f20bde7d676420b94fabbce01d6fcc72388e07355903088476bc78546c5f48ebdeb2077fc7fb11f396f2effd427a302e0064797de0f5c05cbe257005eea41798bd75dba4b4f0bb19fe0ec8cd23a4787ff9bab02d48ad6d795c8d6ea64846e02bfcebbd74a4e176ccf363e9e8375b0fd8b2e56dcbe6867a4ad078d6ee0fb44d063b783f682e49ff5d0576c5d6e41a50d89a68e4c2511d7151985c4b15bb68b8c7e79fe41797a69f7aa2dbef01b07ef5f03ed9c7a90edeed1e32cc3de5d1f0bdd19fe71deb9763f18669f7b80122d569a00eac88f8764748113e2d11b6c9d8b6c3b2d27f5ca42e7000b94ed34dc1da267898559b392de30ccaf9137901298d5e0ecee67af32442958a1f65a35003d9b6da5a6990d3ac3eb5bc1203e67d678afe2342978337be6cfc831ac0baa06f709555c35cec6067b6dd550772bc540a6e21a1cc6a3aa2c8f9ff7c19e48bc77b2b3c6b61a41057f6e7ee3657e49d4d988362fabae303ccea6638e5cb45993d9d56269bc3d3af32b04e62d071ddfbc288772caeac76710e895e13407d68556b7cadee6758700b894a66c5a3e3c34a5b60c6092dffa8f4f02c3e292ccec152e96f8efe4eadedd7b42bada1212c391b6097dc6309430f220a5982d50b2de514200c75d0b212c1764bcaaf6ff8c9a3e17ab436d4b114fd6ac577c8c15c19481bb7c9fef042457f79d8adc89c7b3a983f124c71d8c5c40841ba3d7c58902f6edc093e86e77fb48c54b34ba5a1290d9a86cfa709d9a7fec44940e11a1557ceddd7acb0aa30bace8c99942aa33892910f4afb7a5b71f823a5e3f2292e821385f9810af6d5369411e4bad3d16dad38837b0e3e2d031c06b11194566c362943c3667abc47a4939c1d192afad651899b537252f0458d427445bbece620ad6579258927394974c2235ebe7c818ffb583b6f698bca4a568fc15ff95019fd00e1242af618fa62d23cca453921f084c7938955e54b14a1fb5e6e4e5e607a47ed06c52211b2882a597e016f1dbde04b42c615a56a0377f2e828ebbf5f908f97ae50dcc980a65b165700694ad092a959f95a50bc5c376c93a999ca117152b272e159eb7fb746fbad776e5246f662e41757dadb2950695b3abc0b79f338498b50027c71c32a26d25627026d11f380f939eac2156adb1bdc2e9c087bb318c782b5ae52f0224dc887b6d2870a0a5c8f81082eaa800f50c15805c61b5fff976f312a3157f71bb6ae84262646c9be95e0f4289ffeab7555ec6746c6ae973738a30f143805e72de93b405a8edc2c9d4427cb01cb29083b5f1f72682a5ca1e880f5850a2ee750b75a01549a78b19324cbb68e2a1cc426cfd0bd11f04d801081e4f92b728276c4669d93298c70519df3a12fb618216a77b15f57ce65ccc36391e9007af3df2ea2ba086347970256bd787905cb4255568b7e5f71f03f04910ba711bdebf491897c103ef42750ab1b722197ab463f4542c295658e2ff2a173792fd384070b4621c107a5c8513fd72a4c9da1b2af755da9cd74e62ee6171fd54c9ac2e5549e6952120ee1424dbb130dbd3b1bae7f7b2ae60cbb65b6bb12cc40f68654744d2477c4dfa456048558fb30448859e12eb72991f0d778c8178c5340f750fc9369340de49a56988190afdc2c6314010d45bfd6381a3773d563ca315fdfb94fd52153782bc2940d4be816427c995c95855d0bbd43097a0b615882e2f80ffdb2bc1df95314f8fb48b60422da8b67c08bebcd214b3d1f1d93ee4e1ce4a418bcc9baa79c3b3aadeacf726d6be0e35eee58a32e770ca0fb7091eebd1eb2de7a64f94366c27d0741e0f5e39c48120edf44803de9937df8ff31a9f54dfeda11b594c608d3f2b505657c709c094dca887951972e96fd1bbfcacf30744943c6e85abab45d67a36faf792bb40e4cf396caada401f7af1a626fceb7c9ee576405ccca4548c3aa6af9700d7d34bcdfcff36ffc9a552baa81ee837b79dae5f0f6232994c307ce04a00ef182cf771a2a396cc2e6d3153d01ba2c857183e7ddae708ba93ac255ff0ee90cae89b0ffb8c4c66f6decbca69e5d3988f011647547d849cba63cb1c7b941ac7f0172b0331b280d77eb7ed59de21566a05dfbaa07b707084fbb0b1fe1af2570d294ee4bb5b3dc6512b63dac7f8ab2e7ef2990b323adc332a452367b182322cca3c35cf20c154a73cc4879afa00ace23e1ed711e3d9e953f46064f41ffb7d2266f273f318ab20aa0012ce36dc3d4bfb115140d59c9fbe5a4c131a602ecffbc04913b1598c60c85705ddddb554f9b1005e527c5e46d684d09927edb4c844d38edc67960765297536b3ec5f1f497a05798fea34b5c7c4623b426587f7d4a42e1485b5cb07894e4fd07309fa7ca50a70ef0be110e009b18125b1928d313a3533dbadc7f761e2177dacabfa56d54fef1ae93affb7e9f2e708154d79aa6ae2400b6abd63c31b57cb2852c5881c312f712aef840bd2d76cad20947ee184abb40cdd491b52d73fcfb4774b277bf4992ddac98951cf08b35e4af129ac91c3fb98e3d201315bdad43418931e3b9b851431701e4009110284af07a25c3f521063760b1219664875e36d40a35367b078aa237d529b149a6752492c5cb59fec13ea36cdc41921e04f736274d07315817463eb478c23da32e026130146bd35277398c0711089ccea118cfbfc4205acd722487117f55ec4a01507f5cd89fb67cbd875fc3f1ff2ce2f6236201f2091947a2a609e34b5d638aeadfdd7da4cf79e9fec8ad27f19dc8f77eb7ab926929f343233b458e8f3139f225110a16eb83a436c54de2b7826cd7789535fae59c2b8f6c7e54a8879d79b62c5a8493bd2f54bbcfabb79ed736c4fee2f43eee700d5634100ea2c17308af8e75f5baf8e4e2ab27311c76402b816e95c2f632e4c63f6297ea6e762eb5534b298b8114a80297ff8ce7920a6508f4a2429525a27c6ca4b91138187f2ee30fc4fdc977323faad1da437f96f47c1403086bd60d1e13b7cfc2369596fe606080b591ac62841e5202c3e155b5c503c12f29980216f6595c23267e8f64a451d278958bc0bd9ad27cbd34fd0658ec8a84fb5e5db5dddbabae415e6f820be181ad39dd2292f2e6daaf63b5ed0e0aeb7ef3da4f134dbc2e8942acc27029e7366e5556f51c9face8b54e98cf37c936326f824e445f464c7f809db80b26c39133766f5285c0433620e0febed963e48561bab4ea06984c094f103415810a0b9439485faf07c42a491ffc24586d07dc52fa1f002fee64ab7d0db69a27dc804e6ad832aaeee37eb13046555408028a2d395bdaf872642b01023be234716620287f90f3d574b1867496348af220327133a3079d02641081d9537a318784c670166cf3da63e2ea41e0e55b1ba33365339c2a9dc3b2777bdf90cb191327d475e6949d5174dabc065792982a65dbd42377c33a8eed9d2efebab3e3c91589d2ecfa1f9b6a41ae529e2de64933280064f584554d4b8906ed2199df37eaa72212942133e18ecf63690a3b685305a0b578440faeed641494547d036f31fe46951624026dfa4f8c7e41d316a550028e7f8097605c95592ed9d7797de05c8472375fe5042a601cf7738fa13609caca3fa310782ccadfab162bf8af6fdf321dc89d528fbcf59d779ec7a4cd10222dbd32d4aa6edb9626da892f3a775fb83d1aa83b906c835c1d0ff10f23ef4b7ae36c1698a0d3d32af557f6381f69d417e81b9fa68c03fdda69268c9e8f503e60a6574943b65fda15e5b3cbfb0a0f535abd812d42b7fe82dd0c5bc01a39c86f9fc0ff497a3d5b26d3526e98dc9cd0640d47fbcfb4a6b4c8e0612049f6c5905574ddc4a5b0a86e6fefd5f8db514a456cbbf1dbf550dabf2691221478b8b540968c5767dc7ba9f20bc7dad311ed94e6f3c355b24ccbb686224ba998dad48b719942b8295c2fa49e90f7fb9dcb260f3d9fcfee1f2479ec92529c722deedeaa7be4349ab9b3611acc85fc92a9658f0b91a74e25631adfcf7c2de00664333bb4e7fdde596960a48ef451425a967f8d3cbbc0ba962eae81e19c1ad2f0ac36701d4e4cac8ee8e26e73be89659de587b4f4f47281aae24df4c58abfd1ab9677105617572ba598c7236b73a4d2d7070c7ad6e4135ffe2e77acedc07358a1936390f1bbb3e827f96d67c8cc2a26b08b8c5354f34991ea63ba1ebcc5dbf47d238a7672d3d94ea0ab73a03e108fbd94d365d2e1ba7bc3fa0293cec503602198d75a454bd83ddb89ff48611df95c141e8f478e2f923062c7bb83319921866c8f2f6161ff41682b5e857a2efadf05d6980fafc97122fdfacddf16ebab78e531ba8c2e711f97de9a98de776e575a13519be4ed3968d53af866fdad617ea1d31dc58e1fd70f5407c5c36acea3a5c31b31b4afae3279317de83d87e5178514f68d1a95c42840fe8a2958aafe7388c273e36cb0616914c04d46bc0e2c82414ab60c3443a9c9770fcc21e31f753252d0b3d31e978521edfa1f906d11024645cb98279d083f5ef3f0446bdd48c184a66661ad54d5a81147576b61bb10f4b80510234bdf63f34d5a589953b1b771cef60bf3cea9fb38abc350c717408e727c01a0ddf555e774191ca12175fadbf495c439e0b38868c555e48ea93d77fa19f6be062ec0aaf33046bd52734f3336c85d8368bef86abecca42d599850dbd439acbca8ac1a4917965abee5054bd5487baac610f509db6dbd1af059faede6bd80226010cc8ebae534c983f16df87b917cf21edb196464e6252ef008675113165bec5ac7068a7abc8a17bed003d170924ac7d02fa29471b873548edd54470b6f4b6f0f43f089f33e04c9c2397d635edb73908c7717268c7546203f41582c1a38cd2ef0100129ceca43543076113ab0dd65e0c659ed773f7d1c1673fcd96cd9f36fa09fa3bed667e1b44b8c1e85a40108c03cf0409c12e55053bbef385ea5c53f91687077901c5924e62427ab414bb9fde109aca3b996389f8b64e3bc6e53667dd0eb15240b681d043752b5a4baae561d66db5e2bbc5d83bb21b613d16ef2282eacacf001b7461df0466b988378286ff7b02687211af56123c533d210070e94c2932293c8fa32e68991e352d00066620d5b7fba3c6bda4b69d452a1009d0536fa12f072c26abb0120884e7702c9d4c6ded75b59c61598cd0ddd87232bb7828905aa7b0f867f4af865b7f167a45ae018dca22e0866aead40805f65cae9dce305a5d846dc9dae6f36b9af90b72d3a34e2974a8c28869d845051b862fac373b3372b1ac86c708ea436acbd90f815ce3f9e9f4eda3cbe78aa7702a6f34561228f8835e0943197866692bf80768cf6ad6ca7451dbcd766c6ac2f0379bb3d2b5fb48336d81ebe8b2c42b55286a5e8384e48b73935987b27edf5d2e4cf1f348a81eeb2ae5fec85b3f6a529064ec3bc63375028dc34e18a7a75142d170593ee174f7a911ce67209ba6c2a686b37445bffdf1f86be8a4c97c6e283782acbbdac4a0f04a9031a43ccf6f32eedc1debc6976dc036b50a42e25a5bcd05e907ff101bb46f954e159c64202324f43daa370475084a81123110ab68f7a674ad89',
+ 'b3f197b98441a1ef2bb353f6f7ea1c975d1ba5e6f509facfc533eaf2c24bb0b194ebd3869a844a9a2e97e4942a27e7afaa6ef71014cf3a56560cba726fb90bb931f02d374547b3476fff2561137ea432f9ffccf24d89dff2ea1d1f74d8347bc012696e748d72251c7754e002bcd79a48cf38ec33a71f2fca08d0e1a003a549eec0bc5ee47bede641cdffdb222d1b1217b6801f7c2b797307388cc79dfaf5be6ac253c53016a03edff966df676d3054ca353f2c75df7ff2d002ba9a14c50a205da0946b006773771e84f3b3798300a887d5dedcc5cd1af64eefc022ac6aceb7eee3e918fa744fd825f50d21017a7256761cc3f715fd30c5a88607270ef328cd4612b993c9471aa81dd41befa7576da5c19457450c75aaa8074ff771e167ffa88cb56ba513e8be302daa87e61224dbdf8dc5028d533f71e793d3f8c7cecf3d91e9556916815d21b87efdb8ceebe9a34a05362d9991636cca739973f37d32c9d085791aaebab02d873858166fd9acad2e4e6f3e2f6fe78299d02acebb95988bad87a9e63467e9c8e7824009908577e1d593f89f1895d59d9bd10c73e8bf0a6b70176d35722f0edc1e843ff9fb96512786fa5ab61caa34a4199fc9c842a28e51605f4e6f2ebb29507567dc4e76b5436788ab1ced02570d7e5b61e93790e72f25c684d3fc31b2410c3453a070ca8a538924cfc6ab94436715a940f2279c35f2cfeab1854543bd5ff8a36f11c8bfbc8b75a28ee05798164a504b646e002c35e137140ceab02b848afc0ae4b9bc1cf4f3134a0ff35bd77abf1788f4e429098e03468cbd8ca6b3aeca00b0d920b5abd9924c637b861e1915cc52aa19dd0cfbe960e299edf390a1e427ecde77cc1c3214700637f92208258f7ce9f7fae010c9ee01f485c4a5d4becdaaa8dce647577a4c952a0cb24e81c591d4c5b8c0759d3d44ed6596692cdcd2125a1cf24d19d04b2a0aa12bce92f1bfc3decc3492241b1d942c1b1505100ea55a403168d4c8ed6c56d651dc7476c0dccca1e7b59976f23285c7004abfd7d4fe4e62cd85a5de18a777012467cc8356e45525ff81fcf28b44c0c5fb7cc00b95a795cad992e5e3b8c235940113ff401c9f0573fae1e4214c1bec2ef3f42f33b866d8803ed7bf5d34863a96cf29353678d58592c21d79899e7eb20bd2fb35d8a704bf8ae29c59d6f6bb2b0f78f195ebd34d7c8a3d7de2b4ea36ba637fd7fa81c949f1f2af29dbd56529b307e3b348e996d093645549482a960cab3ee2d0a5b686fc17c08cc56ee3e9977887f8b776b827267227f1c8d2710cc52ea4e3305f0046e7d8fa60ba3a87eacf22969f4445def017c0dbf843a913b22cea9e6a3d4fe571d0ddaa154e6649d0975a3dc0a4e0e5576b25885402f498f20888335f419eef6f80ced792ca1ab104b8c883431d6d55c6e94b37ec4ddc863320af6caa8273a6a9bf528abf768048ecfa137010f815b4a45ad7f0a86c8967dc3d084f3349f791855411ca8499bd95f124e30c107dff8c598674b970a622c257273ae7e3dd51386c09b49fc97cfc207b00e26029f354644d35c89c2c45d0200ba6ee39a088aa23c7a4e31176686372f354b67344a43bc5e82f7ba0c48fba086082cc4f53902adbfcee452973a31a12bfe9074d4ad6dd36923b36cd20cd0902be827b30e53ef8e775af83eea8754a8849877ef721d2613fb323a12c2946e4968d70a4e43cda3dcf98af4469fe281b5df8f5c278b3e0f068b3ad63c4c544744d3512ee7442ac201ccfb531a05b03b41833fd7cd8e647b23afaa2249edd0de0ae1e3002e7ddfbca55818bf29da94d3e4164655420a451ce3cf0c98ec05aea5142b1948745f711630215f72e68ce4fe061f2f6f157d4446df7fdec47342223ef8f54052696773412abf5c28d07b451c3ff4578fc855e69b6f18ad1f7021f00e11f704a87e345adffd988b4b984419a0ea3c5b31cb22908d2b4fd41473037c9507a6e2c513349c45313369457e65f74ead5da6ffae71f69e8c8c004dec854c56326b4732d8f6bc036e2672c1236f5257f1eec733e2c27d321b339e266d15d3d43adace7c2fe93ebcfcd83428f7beaf6f40563888f872990a5ffd2a384543a791797bd4fb988a98b475cf29f79bd282f7213a77695020ee69b33f20ee258d3c1086a4e75b93595e9c5171d0b7605961820ce2005f9a4bc1e2bd800ed28e5105d3eb0c91f6b0e3f4d72876a4d2e5a1cf927c036fc63751c7f7f756606fe03d994e0f09516761a8ffe7633422f4bc4a219ae7152257a7e1653bc928b210abb16b017b31a2284626e46f8a30e77724b10c1de68da46e7c693e00db8d708f714aff70a80c00a3aecc26b206034ee4df84e39df2d382852557970986db2826b178cb2e2dfef9842c275b617f11e5c84d245c9d848d2936134b249853c84560ecb9528c6580f9244ec6d6f05de3289bfe1dbb9f142124fbf6a24b3fdaab54d8a38b3a3df7488c1e77094de12b0da3ca52ef95054a15f2312ffb9f82841ad2f8466cc6954bac2edd458d04b64ee7d3fdec088d726f020d803fc575f2d88d5c4a75ec9c34eb326deb3ac0bfd2620825813a06e9692b4bf363968e82f340793d3982793f9f5e51a5b2b722c3d7ecf5350ecd495d5bd77a3055d4b53b16747502602c9105370da072ee4b41b53948257ee1066e3dcd2c0340d16ae802deccf75838b4d2a19e81a561d8779c08791c1f6fc285d42f2f718da160d9857eaeb2768b3dbbac892842b6df1bcca03209d149840dbc299615406fde7e911c0328dd8937e9b18d2076d97b6712aaecc68df04a584d29ccf6f131205495321a34bf9695dab736f2fc0cae697677a2d03a58018858eb4f3ce6559c45a04f3d1e87e58622d040ac8c905c49e7a99534f9020a7d9a1262ad07d8b51a564e070287713d24c6d4f092a871e8349c6d15e1d5b217dcd5f16dc3ac428203c8ca5732e38528eae8455179e51522e6ee3d5439e4cf02a7e282571398b85f111381aa3ec1483b510dadedd2b0febee96396da9da4cd4d574651a92d5baebeaadd108a969f1ed6efad7b203d9a92fea48306338da4117a7357cbee6173aa03397c0372caeb9d9e2f5e39830b008674b0c307e99a515ffc74bd7d91606f1ecf5576c6fd5c1528f398866590cb912da386aa1857443aed55d3edc33c9aac81958763c784caca6579a3cc8bd40fbb0d2daebeb4170bdf6e09394f593a80ca76e837b9a1938779b792d98718c747ecb955816767a361ad36a8fd789c25a3377329feeed1c41281b3c1c24c98e4f4b496cdb74aaf76e622fb9798eff8988271eaed3589c4710c90dea8c68398b7a69149f8b8bf082bf9ef1167a42c1aedaf1862a484011634d6158c9a7ed274a9de012768fe6aee1d1d501c9ba7a36f9f79895ed252eb337a0f9e622953afc945fb92d39100a4ddd4d0f471a60bec6348824354193aaea8daf989e3c7ed7213168fb4b2f3581363edb54aa519496d925349e4d6cba3d1e2b25466861894dfce961ae56a127d2d0cc22ad15b5808bd796a40dc5b7c16eb7daa80b2cd7de23f784f2db35d70d85824fcb216d8f4924294d8079856ad1c61d62e0f0d2e7a6e179c9c289d0191022b68e7db99b271aea35126feb74cd11ccb98b77fc43d90910e9817ac00faf5832d352e17c87c5196464af19697c28aa08f11d123865f52e37b174c188dae00c3d41639f7219b16e1a1eea27fe84c2c3022edf5caa21833ede386a40ea19f655c967895ea33a324294cc8d41af75e48543d99afa5c60ca608de62b9fedb4375a60af8cc618d092bd5bd45e0e8635d61852581697560813bdcd237e859a93ec4489803380d8c41706f6a026378aade0a3b7151bd99e02a67c25572d9af79f5c3acd424734ebff0a46ed96ac63c3c54a4efb7671683e37cbc71eeafe870fbed165b25e91895a68b3a4c920ba3b3a665b43a5e5dfed3e8ecac33e45baf4e7d991ecc23ad6282ce6594910a51667f6765ca73dc92f10a4eeea9a10ce298889d9e5f8853fe1b96963455c4d8d898effde95a54b8a27787a41747419ee12ecfca415b182d579e319c6c006053fdb585e874e62589090cec86eb078e30d3ec84824693e4165654e45f7106fc225d46f1a58cf09eb4231b9945de6cff59476c3763f29c84a556ea3f5dd7dbe0ba63c783b385fc08d0ad4e2e8f65ea0d7e980858cdf9c76260f5c8a2f62511c692ada1eca1148afc54f3ff0cb215f14127624b795eb0b715cf9f7aafd6dd2b063197706cecad0001b7a3b8c46c633532da22c01964bbee0747d06ac66e74aa381dcdbdb4f4b40d817f1905e5fcd2084b0d45e0a99e7fdecf602533773b6ff1c4bd9ce43567ce062421d060d201e6fd02376847ba5a710bd6bf0a4f42ac33a444a7918e6e945f7c32366654291a1685e0fef64fbc3733e7a5baec28b95f642420524806e138ecf26433574a4b93f5257fca7c733fa33d14c4ca675a3bc37613f0443d080d93faeb128f0fcdca0de77e2700674ca52cf0f5ac83f84e4a56fdd63d03ccec74540dd8c5c01c5914e671d28f5743569d32f41cd56e1b9f85a84ffd5f07943e85e79a4e067ce976cccc38d501259d0b8863baecc7ff4da84e3600fbeec60f68e2cd24ad5cfc13a1521d80f83501d0e5e72dca080c9e0b03346e955454d5bb15fb83419921e4075335590eae93528b7049ff85d10be0e03aa8d0918939ad13a0309855740dbb5126e71d268a94be2935116782ea5e6e49b94c0a7a2cf5b2a5a2327af4d068f87d7707b85cfec1ab469eede455b67c8cb3f97e5ab392219cdd9671b98430dc11e8dde7e9368d929040382ff452c7dec2cb95b06fc26b45a247f76ec2a807cf9e2fc637fe37b99003b27b68262e910da6dcf892a84b1aca99614f9a24b4e7cc03beba5885d505327c29e326e83d9471bf84ac95a2a21338b8b5f9746e5f3359c91234ca0e92e3027ff309dcb90454b3633f1c29dd6c0708a6b29f9dfdfb8ce184c6d01d06f5f58865ca4a0a2707543b3888e1dfb70d48c2d9f3ac67521e570b9d48f6c1fd729f2cf40c4e2fa0db1581b5ee7817ce1a6aefc8d5aa71193c242099151349509d5268713560cdb4e41b2f41cc697290f7eff809e5144b91d976d8fec7d013aeeaa1e383c23c54d1b6c78c92cf10709e3a4a7403ae64478a7ab18d34bd97d176cf2ff6925f3b6595c7cd31ff5307824455fcdc5cae3505319476c5e172f4e336cf3f4a3358e8606f9b7ea80df4d93831ef89995b40e0f545bb391b7b9451c96d7f7226dd4bbde5ddb66e673520eff2d54b7343a622f2a825537ae6697e390499344b44f6a446664e8d0ee81b63d642ad1e4c63c3a1048e5f01bebf41bd351538a22d0d15fefc525093f2b3073a06c837bc77621a678128612a671e8acdc08bca2db9f7c1c85a4a827f9c40bf100bd3f3ca86d730d2e2f6ca42168cab1c55d8dc5b648d707cdaaf32847e2979924ff66fbced3b9d7fb489f8fd4f8239457f7cddafaf36b89918010f671ad5ed1d6db01a082cf7c6ba70528b9074779bc5a7f84c9f0cabe0b97cd0777bf4ce702dcf817120c894333fa0d0e0c02805791969cba7ae0f25af3a83ade9579e8ba95ff00b03bb42a9696bc09596f0cc9427bd2f778d41196c7aa8c6f9f36e6a860f00798d402c2dafdfcb4a012c96f4ac4e2d838c5c1cddc8b990b1352444fb5607dbc6a8c4f7dc001cdab7b4004712d642e6e06aa295aec3027edcefda6fc3642a3e61edf0a2e0529726942eb075b97abc75d092ef201ef3ebbf94aaa4435548fc94c5cd61c1dd0fe51b69c1bba75b21f166cea59050a0d3bbff82c60064237ce59b7cb786b924a07d35a31d90506a64a816551334abd6db19ab31f28d46a0687baef13cde0d59bcc601caa2cd0589bb4710ee5c5a9623827b4efc90996f7ea4254bcdddd632dead561c5af1d03b1b8e34f314f160b4095267577d20b342f0a888fe6d1b1dde45fab3c1de7b3865a25618194372e56a0ad354512e336965b8fe0d3349650344024d55becfbf6419b0bb67100431674caa8dc8c87a493a5c2a0d3886fd5c2528a5edb24fa92ee1dbb9268853c1ed54b06ada3b29fbc2947ae66e8165f35101d093846010f55a40004e10127126e73c59ce4131f22d400656508a7e5cc5f417f07d89c59f2ec1fd4bc2109be48dcf9c9d376b33bd89321e830af985d7efa5d5fca6668946cfe677f2c7906b2a70f6e3ef58b0b6f88a293b6578344e73caf6da49b0b2f19453385eb9c12826af7b0da0e484aa421fca85eb922ab32e9d0267738c2ee7b52453580fe531304500066462015dc05bbfa4e8bd7d950eacd000686028739d3a633a960a29ba515cb89dab95ca369b6a34b3c21fac399f5f995f79fea3211c07dd93a2ebebaf03c435cb33baa3c184043b719280929afdad757a3ccd80aa0c940fd8ef139f91b01203f9ad4f226112a01058da9ec53b921cd0daf14b4580e7655684db1fda04feccbfb378d1caa7dfc47ff42aa8b89e0534581c6806664834f25e22076f1f7b386aa',
+ '1600a349990df42aba9fa03f70deff0f75ae35c1a882b48caf75026ee097bd216284dc4b8f3c37f59d2e4a3e7e96355004090894494e3e224e70877ce211cb7bc6016b890e10ca11ca200c34e67e1dbe4f72f55857141bff5b6268b4a3900e75899fd96dde31b468899c6e89713dbe3f9e0f85759b7b54091e722e80eaba8ff8f585ac5dc648fde022caf9a5e77c21bc38083f53da2cf02adec96047813755ea50dc6fc390fdae63dcd334f110e24c1e6686ac5adfcff749e58e86702eb06835e3cba70602f7cdd801dfa7d3b418494b70b152f3710b724d79ea2965bacdd1bff67bde8ee5df6526d715dbb49ac5202d9eb0bb8457886820e305d08ded359772d1149bc3005c7b37a79e57fd8b92d7ab37ac6f775aefe1a96b0603508e91c234bbbd670d1d1719f2b8a2a3144f2678ac85ccf43242e8e5d058164a1667885beffe9ec9d402b7463f54484863ae2b0a1ace39d41fd71a7d7df45c2e473ec3688ae0e0498078e50b06c1b8cd50704696dc5b1a97a4e02eb0988501364bdce9f4edaaab6f7947496f2f481bba455c2123da7498c32b27cb8709542ceb8b09a30400a326c427378a7aa3319998a93b64b9fc61dbe21b729a08b8a906d36d8c99a2ab157acff310513448c459eee4d5b7602a690a7bdc8a433d8eccb7785a2f72d5d646ce18439945a6074984445ef2c0214cd54d17d6376d2e71046662bbb8d7a6697f4b28809b0fd7c90741237e5a2a034aedce3d7140c0e24a9a3b17f6f06f1b4c08198613df56cf7447b911aed49b0f0cf9b275156fe66102d65f21759fe33f67295fba622ac397f1511398562abb4c7a4124482a8a84b37f00fa089e8dda17a22a464dd747fe36296d7840dc2234c27d0d4a3c185a45e1ab603352db81fdade652f5c6d9fcaeb403e31090a985ab79fba44cdb47a7cef16d3e33899345f40819eb94adcf137b1a66fa0210251acb7add4f53ad1f39caeeace122342d9f6630253b4d8b23520f6f3cfb7748b8ab39cc0c56873909d7dfdd529227dc1358fb2ef08b46e73c820fbb6c2e96c1cbfea0776f010f076b4bb5c846996a08ac385c096422a749826b2606dedb8802c4dda684ed97139fae5bf5b6703e1440600631c9684a99395dd4fa597a4a74930d0ef3fa7062bd8a3bd047b0b8c94d0d978c2177e3494440358633bb28c383fd0c5930e1dfa8334f797152bd06813cf5b990d519bf68cfe5724a7a35d08dddc72b88054121b29d76cf08bfe542af0a822edbf5bae3ef62b17ce677ce5af1a979d161192320c82487a75b3530549bde3c5f35285f3726272bbc22d18eae37109c65ae158ec332f00b68345da7248de1aab2651612a35443db98c8307db4a739b75135a08bf237288a7959df519bcd3b54903568da0fc3e149799e3ea455884c52fcbf63219520f48a449262379fa213c2626bc6c063b927fec86ea00a772473f5ce13065895357d9530a98e5f1956917d8ed0d7ca877f3d81ebfe01b703c1d4292ffb3038d8beeb32a5640dd3f22fdf0c7e2c4402635d4c5a4c1607b4e2a89775873d89ca470366ea0b8d849b107622f79847b470e09a7c1251805a08fa21e5623ea2ba15ca4c1543cbea9ebd5d7285c746e8d01be480f4306403a3bb3573e677bcf26b214ae020c74b440143c06d2d03efd9400b5855dd3cec668ad67ae8c13c6af543f7ad08b0fef462553420ad4533fae0ab4825625ebe5172b660b0c69f39ae72eb9edd0ced6f2e0e4399677bf3dfd1c6badb31efa03ffaefd061c156a7f7f1330b1ec034fea2620e72797c1f5f90db5214cb6644cb4751a57fe294e002fd9cfe4a8040c70b1ff62b8cdc47e1b3cd804e6120aba8065cbd5b6ce911acd7d3159c50cb440f3e6f542d36cbb009141f28804be2e7657908a6db3f8120014f02c6d5c607b352bd19e2dc1a4c9fdeac0d3bb2781a04c14dd274eb9fafa92992111570543d77d9b0a50a00e06afca9a31f21aa90d0e905aa429054347d946ac7886c37a17477ec409b3360302aafb221dd43313c0a7e78b7e160dda7f2c90d16f032f056b49bd39484c4c5b096a0414dd0070e24d2ab649d364d50a0163159a8cfcf641a05cbd5d6e827f70bb5b89e4f7fd60bef2f580f83cac074c6f2b298a5b0ab5b9670ef3b2b4dc3bee78fdd11507219ee452a6dc292231f10c28d351035182e9d72fcef4ebd3d00e2ed2df8e178323680d0c9d2c5f2cbd3ed3bf0a30b7e91c0f155b2b353b43f462ac496f257ee46978935167060ca4a45d3da21e2cfaba2c920e798249538dcfd5f14d2b1bbdb36a2d11f192ddb4226dc89472adca4e2d4b1f3d1b9285b6f9a8d4987ea1f55668bc11f34d9f27d84e9ffd9291277d44bade02fbb1aa8ec84045fb0c3e5236cb8cc5b3c1c5ea890b51a188929e29da610b7be9f4cc58d919d9fd95c70cba449f881d7f180b0355a00428e62eea13561567ab40a2dc1bd92e3f5641537a58c354f339f0408d883248be8c92c2157274e487d2837061622377d8d69a2c07ac276e5691a3d5bdd78357e9431af690d5bb50b4883ef7ecda3c893ef4eccb2522e54e1abb06622c7fed702cf2eb26fcab8deca632efa3dec278c4fa47d5bb535b8196b81c945049efc503f1f28ffda962810ab578f20ec7e98ccd7335c17732c480bad74cda4aa6b59e95c0f875c53799394d6aa10293388ddd0532455c7913ec674cc0b2449bb36ffea8124b392827cdfb374e718dcb914dee0ec00ce35ba741a9af3cf37fce005d59bd8e814690ea6f5b2ad930e0227298344c94d312794d42cc648466c87c1e2c34386e212a8a000bddca8385c6324d0b4431496e566c769e43ff3fa2cb105d4b12cb69a1619df901f788e5d69c759105926f26052bafc382b4f1d73e04155e4879c8e998682746004cd868b9df66df15a368d35e0eadb4be73cee37ee0058182ddc3666ccac06785224b94960b7097b9fb80d9cf7c00ba9024f8dd0c0b6b77a8c34d7894e5f3edef3c54f1f1ad4d5b710f79a1eff02355a62d023c6bc2a19128aeb998b7662c49ccdf86f95331378ac963a5a4260790067f107d79f4c2627e6104ba3f3ab7f3fbaca054134133d9b6217c8789082ed15d7953bd2e5d7708901079a1e49547c573ed133ee83231ae5e27cea1a90ce261bb238b63b1fc5d1e66855213116db22b532c9cc9e0bc971ff33aee869cbaaebbb00c1be3bd67d11e625e42805e0a739019d9c1678526e0b905d940a8cc87f059dca189cfa9169f8323e9af7c1322089e288315aa5e27bb141691598ab0fb63d681825989fd8f04e72be61d58e91aed901fa70df4d435629ba5e1bd9f029b559c4f0d0f953337ada4daa200c9487b9f306fb41ef96fb693ba2448d16819de6215b5c01093d2b6f656c0cbfe2fa9ba99d98938cd0ab941bb1a224529786f3b05fd263a00e864738b777eb25a0e956e6a05fcb39cbb296d352610554a428b47ad9d40d955155103b9815503c8d883ed8c405aa3028d487fbe588ef7a858e67ae580763c81ad30de1d0ee42ebf300dde8d02f395a2f38f1fa33beeaac81e294b3a4136c21b127c69226abc91a1878bb7c8e877f9d28075b3804acffd2e149c3f185a131895d7bc3b764f4edb145195efec4b584e25a684e1d1ff935d5eeed7dd0c359b8e667f48db65070b799c20b50d43b6150120e8451114e4e959bedf649b825e20b2217bcabf9b3c82eb8d7751968bb32ec138c82ec7c5753250d298cb01175e53223b5fba5e654a45bf7cc7f3f200cd05fcf1c8ba7c603afef13e9d53943790933d389d804e7fdaa443ee8534c66f5e4b12c06c70b571a0bfb9674fc6cb6faf71818db7c4e637485f3767516c8a13f16ea14915d9f97640e4f7c1327d1d2bf56e9dcf20f0ec282db7085b9f33938b20d136e0676ebefa961f5af0e7ff10092cfac0617b8c96bef55c155fd10299516723422ad3978c5b0abf515ad2b53a6390a6b7e929f09c8839af0cd88951f1f33ec5317cecfcd824295559838537f2e7dae2d2cabed5540065305d4a4aba43ed1247a37b15f738f27c71f1ff621fefe2663fbf8aaca363b3f270654977a3f5ab7c1b0699e12f05227e85b9130bb5780fcd3dccad65d3321f7bffd34aa2978dbae6cfe95dc10ce3509a00fd82e49121ac7a4d88a78cfd45bf6c2f15c25e0d72a7ecd6aa3b480949f979945db38f4b8364e7ef720d847a14f04d9ebb350c9e5adef8bff7c6e8acbf89778048296e3d03b5a0a42743eee2366e9acf223720929cdc84fc2065258faa7d2e855b58f40e291b3efc06ef2ece1086ce20e94d5cb2bf2d3c0bd2aa70fa916108f3e5c6c3076a021d679f73b68639e572347ecbf357485d687f7d1b7da61ac1915ca5f76dd15cf6c6776f5638a328e7019a614796f8bec9a4b78e1c8dfa8d1b423fea6f26f46885b49be52b07cd542806a32f44ba2f891e7b54944234609edab61e41a2b0e9233b72559f46b63d4204ebef4747cf644da856d71e010380968c47683a168e0803648a250c5db6ab892a4bef27d5692f6313b1af89fd6dd32abc80be324f01098fad669aaab4ab608ff481136f51f9f96fdd264e767bf5c0b1c7ec70d8c0cc462e729216f90fe724ccf0360c8c62044ada613f5452211dd1c24b05308bf042567660873a85b40c40d699d53aed6a1aac294c3721ab7158aee2c2456dca7205a2edd3d075cf458d4d137de91f20feaf85d2ead866e0e1389089aa379922ba88b3fb58e8493438ecb23a08ec39c57425706de98d74a0f5302f2d7f5643132e3e22357c493552f2ad1880c7490b298f3ef460c4b0db5a21c1e354ce2bec1a61a846129a7dbaba2730d8ae359a13eb943e7a41e8fd1b8afb8045860322f4b6e959c8195fe059c84b2a8b08e059d47a27b68a97d2ccb5a1e6ddee37aac61d5729c500c0293b831bb30ca8273402dd63be299db91eaa2a3d784d15f041af96a9a77c889d82c9d7130329989fbd2f9f26cc2a3c5c291fe9bc24407536542a20fce6d2d807e925b64cb03404c8e82a8a31d61adec79e8894bc8f7a84705ec02cef6c7a7795e8d9b1275eae5549519e3f13609e0da1cb8e8bacfe3dbc34ce2c1aeface5dfddfe40584142eb127766051e59f00641615c9c6f0c052c950a2eb88b8c2c77893696140c19118cb4f9b1c9b86408e6028cae3c9f6848a9a756fcefff236fd1fc464caf5d1d5f05b546a1868dd01fdfc99a5ce99d301d475bb1b54cd663fb77b1738e8cbbdc7fe8bbc4d1b61f0cb9362ef0c51df8f217782a90e459a1cc33cb4144e836b8c4e898111abec2ec9849ab7b569364333cbbdebc7b069101aabd5f1430c637c4654db65daec5476701658267023436cc6e7ecd59d6a87d56ae0ca5a2ddb4fa7f4c03a60a0771be1eb6716d4aaa24103505c4d6ab4a2699114496319592410949c7aef9aeab6091944611becea4ef7afda1428d293c1d5ed1ecde8534f4d6eea4969413a4010e5c383ebb194b98f2f196af6c535c23094909256a03398c0315200777a682aed798aaeec1ef49ecdafe90bea6bbaca566e2477cbd4debbe32173cb8be14e31b58b13cf4eab36217040c6c2d86389329a2a4ed613cc7f402006f6b18dbd11dcd1e409edf8a07fcae0aa5899a8f6cb826692b8b618d03cb0dea782bb6657dcb4a74c1211d245fa2285b548974269a843216c0a1fc1283d9284cdc0effcf7d540a08bd642c4e1ccb0c8293ec48f0747fc3281eb1af61bab6e7c40575738e424082468ff88b6be20fc0fcde1297517d135039904ad9ff2aecd2dc8f0fb0268f2949cac32eb90221c8fc36adacddde06d76fb7cfde3a5f66d35ff2d33e143c9676d6e1f19743a56e8f04dc8d0b9b0ab444882f17dc1ee6c11315a36c713e150aa1a04895c2a9ca2ada713af6cf3d4c07e3200c12c51639fc146a6ad5599c645e423c5b2a3169f4d933d7fe75685336742bdddb4b49aecd6a6fe4b588479d8f03c111862953e29a1c139f8373576b4e8b86f8292baa47258e8e0b1fffdbd0198db6a6760daac23e46023e569b2cf3e8a09d35523e978db6eae74416c686d2887e8b919751b68765f9784b0437497bb975d6ebf2fe61d1160204361666c8f35038c8396690078085a8fe3c08e24d61255dbca297d5c9bfa7668d47ab4b12d0f55ef52450430e6c31d6fc87d1169952b0dac6ef9d6f1fb80f8abaff51fe04ea0c538ad900d54ba8eef60bb4f96ecdbee06f7e299024d448a2d145d386fa1fbcf9a901dde4e13fa501a8a459ee354b0438ae5af6fc9197eff5ec865ad6c9b3b841e1e29988d16b0f3e414edd2c24a1c12df9e5ce5f3fdb1c9d3f24fbbdd7716a7970b59249304e35e96273c53f87628434e49716147a0e6cfd4b8a4c98cdc94214e7e35eb28869c7977eab98a507fdbbe1f0f764d8eab25f9a942eb413f73bed88ba046551344e22cd186ecdd910e3e6b9a17f41090c5742f99b9ac8abfe49bb5969813cb1ee5bd5fea225db582ca5732078a73121597dd59ed45700cd4d633a0b68f24b30f1235fa0ce7957bed5c30fcad8f79bbe5c9675fee0dbd2f45f18f8234ad0276af7253e57dfd1b95986bd1afd54f9073c7021a29e13a1e5bdcdeb6415819347dc6ae1e09858b776d4ba04035c7f13fa285eaffa011f318f85f45b0c76afc422f1c6d9f4c6eb93269d0a38774cb9e0fb0e5',
+ 'a489cc5f00c1835ddaf2f0586710850752abe68d001f4e4e180b2f0043041805308adcf8dc3af1861046167f2b23382c218197e4c48025da42212e39effa3e73452f40d5299de360705842d4a258c30dfe6f3f92be7e646c9ce9583494489f70ec603f20725122930510bb7f5618ed51f05d28c27682d5ab2c4bf41ab95503a52c0522fe3cbe76c8d457cba9cfcc7da10033989a75f23e40fc304912e78932b90d063299114ca6a7e713b87a93da3ca434d9d842423868d2147ea045a54cf355974bb41978637cd7452ecb192cacf203963830e365ba1b0a7a1f41db7b061021d3bcf3a6fa6bbe01f68e4caf22a866652e36e7a567e21e9038f974fbf11f4fc4c84236661ecc35cc031d8363fb38627302bc47afcf173b0b56f681cd90ff79e77ec3c4846ceea9e173c1b75e41c3acd51db3962a25c03823dafdaf7adf0f5631fe28e6266c3ae2e74e6432c77bb10d3284011d3df247de81cef5482a67b5ad4b4f5ae475a716a7879ced3ac732694d3241902411bc13f5cd39c89204ae5a47dc79400698a4ebc16966441886ed55347e5a46f3cd0e8c45ae245dd6313e67ed8d85c194b7eb22f934b451142b34dc8abeda0dd19a6d1a95cd969c5bd99f4265067ac7d5fc052115908cfc75df8f661699c6cc08a06325afd2976d6b22575577ee6039128d7952dd27f82d85c9875ba1b8286bde06771559642fb84c37f007edee40fe9392cf1c1b9effcc8a12a324f3c307d19cf532525c2b6765473ef2bf8ead2100a03490e695a0a9c1cde16c27d4616ce889941a4480d1465ca460e3e721d40b26819a431a14d3fff4965f69cd0c3a5e97ef0cb9548cfbd586abc44de66f0a06587dee701f60df084d2db3227e62f7e5c6148497e84a531bc9a493b72440f81b7edd559f5d416dcdb5d9071fa3a040095d41253a6a8081200ed6f4aa095b455181eaf9593c7f255412e380e9a28cbcd345be172c40f72dec3e8a10adfd8a9ab147e9022524e1aea74e934807e5ef144a64d381f5d477fe883f080e4868939f41b925988c7d31b1ce4f318701d290f077a3c88b1b8cc89cfbfb981703b23ffb0bbfe5e115af35d5cfff056460d339f660eae45f28d2b1b04d58825367435657174270084822b6c3b4445708aa4fb0d10f227122a40dfbe286400de9fb83a05a6b280f33ad3e7b2285086e9b6aaebe278c31b5ff15a46ed9af9a820247dbe5ad115b0a8bcd6c4e9b4832934425572ba1dd01f91c0501d23ed04e29c5d4b1ecf711c1a9372f12f5d607aa0e2b65b4bfe60c7984a1fb8befb8ef434a5b296e7ee17144345f5b9a397ac9582779b12c429f2180a0b780aa8df016632debcf7b63133bcbf22dda6ae22f9724265692277b73220093861bc6738d4c951a9e4c3e6334773d2cc733ecb89f78f652e98f0d330b19e0a63554476a389ac1589c2a2145ec2b842a55ee86837074b6f45b3047320e0d0821ecb3963a9906cf300cf08bd3e56187340094a20a4a934c54d3fd3b4025075f4cd5c119ab579ba8ea1627e4d3c4202e92efaca716d6dea0ba7a7f5225f80ecf6e150539841b5e32cee456930e3471618b4cbefd6fbb5c9a6e783df4a82e2a40d1d7075e8f8c5956239b05024cdb5a08683c520cdda21523b7f4bf8a936f6398bb4150f1925393fd3366bd985561e60b72e9f13b28331221df168e7aac65c2c0757b67585617140d446b04bdf06f1a52ee7b22f417155a7e2c08312ebcb64ea047aed4fda381e5709fd265d9e7ad00c6271a6e9f73f1f520e7ef300c8a0a10207802204641390d0c8cc4655400c29f4d64ec5ca2046eecf157f6147ee00a0e29529ed29df7e694cb52698e970457ffd0ec1c7466923546d7c64264eb845d52a11bab72698e3083933be86708ba13293808d03e53e5ed0bbc7afea8bb3face4721c508912cfc1e14e8d697810ec9f246b003143d2c43f4487bc506955d99fca829db69e007f3eb6e391164a1860a2f8531c660a49f9d3f820d4602d231add0ebbe604399a69520a3a8f156486dfc5aed7a4971b214a502f6f0a577f8cca0fb8033e63e24a54a3e63bcf8e4ec331b04ddedfeeffc3805ff15ba65de4f8b0dcce44effb227807d951ce98aa91381e0add5216903d9563a747ceef99e6cf95ed5a653ff3808a4b9d54db3490b44c6e7b671a91a85d01bad138b02e340c7a41e9634e777485e9e897f64ae96a3f66e8adf11e985ce86e4f84cde7ac56de5f7c79f2e7dea5b7fda66e3f03005dbbf05645864673d46544e8690d5cae25e5e70e450e18beafa12e4dca37eec093af517eee2b7a69395cea4e2700f77fcca87abef4bfc95db9c8e5a455e7f47334a3f1284eeaa2c3b355ca4967aea16671b081552f0de205ecb68874b456fb5f671f381e0dcaa6ca69d94ba0d12040aa3d83629c9d014bfc70f28185928cecce55ac8e27d4d46ec3846fd51d0c5dbd9457ab8758e7a2ec8a6c04369f9592b00626d15b0a4b0ee2f92ba0d086c16d016ce7b05654b4f9adf90875118a656f2d50011707901982ebb387f3a4a49759f37a17183957ad0c778f6ecb780dab2b4df30e05fa81e6386f38c0f0ba3f37287a050d6d97287ae53096c391d5f20fcff73977239ca55c3657d1fd1f781f48e28057f136d890c28cc254324c8fff3862136861f956c321868cc66609470b7390ecb6ecfc63572d071312e0860efdcfec88c9f6108ea5dd30f55f253590cc6038a66b2646a24565600d17f8c6bab37b7640a45eefad11393a79e45f2bb92ab6e595bdc69cfc210f9f97ada095fbebe5062241c11e1cd0dcae029c3f742ced1e9ca3f6f486d9b5d6ca981a007a396bb5a716e7462642aa709377d0ea974fdd3f67b75dda8da1c75febfaa742fddcfc925e04df158e86669af2bfc88b1c8cc2c24db9399d38bd205509a49c8ba64c662435d47257de52ce04d2c4cc488c4a634e5792d3681093885e2d7e4106fef17114336ee5349f0da8563b6d24496ef0898c8b2873619c8cc7225e70ddd88c34e50a60bb83d3581ebd3736a217b74ae8fc23f36460b06410a44ba462ba2cd87b89adc5a1935d91efd550c94beebaa99984bc972ee47ef088e87e073c1e286b2f26a669095cf9d2e7b849ff51f279116be9ff7d6f45f3c95a5b6590e652f4ccb9849c55dc27d0a46e2dc9dd9a681d0dc6f293af0dcc3676f0c5a846489eb9837f6b388f003c0a8eecfd786d0f9bcd2212692135f2c1707fb1eeef324b499f19eba322215fe3ce19c9f000b698d2b2dab7145015046cc86d049ee15ad59dcd1564f30112e06444cb6ece06c01e54f4bc1dbbc9592d1467c6539c26c8cfe06cff51257e6b6a06952f415f3594876aba50ad2834095403741505b16784225ba3601cff4033e713e9caab6b3239bd5c2c1fcd22382b617f18df82a54c94b4569bbf2c4af0723ed1672615b9a8b7a67274b0e6707dc93bd17bae31407c026f197ba4e9cd3531578938cae5123d172cf4b78b61dbaceacc41c4097c49a0d63aeb6c97bb52b8771a82833e853e996036292039a42b6d97fb161c79ca8a5f16fc1696210a9f204c6f06710b5b05659aab5ad441192867d7b09aaa8584c962cc9fe020c93e7e16b83e5b2ab8d12f49cd75cffe2b279943b2d31397b510cf50ff0a923318bfb442c46fcad5cd4d83ec027bd0c4803548a8304dca0a91d764d2b82573f695f60c4b77ea9b9bd239caf741a5a54ec7adfb3f5a04072ca2414f90fed8cd92c8494ddada9716a350fccc1190db95c588f67bb037e112246fb75a31d90be62e39213e96f35e8316cffe51e3f905e9514c7890a2cfcc321b809f4b5e51a608f371e7a928cc28291bd5a72115830bea19999b01bd2baeb0395e62ebbe6f917909f70154376ddb51dbec5f034e36d5dd46fac798aa526dd4a5906902fa3ab5819753d9076cdc61437d9b8ec1361b4c0dfff4641b114cf3e6889e1b58b9bbf86ac50ed58c6f23a0472a6b9c21763956c16d11da539922262e0911dfb4a4f8437abdaf5faae74a82a50ae2f1ecb699dc40b8d89108ebdbf0f451701fe062fb7ffba4bede287c57eea4448af5e99d41c7d307d1f202af7f387f874342a29ccc9233a5c3bacfd754cb8d01eb11e2d43bfdc2828563088c17e618d413b0c3fa71666be5475a67a04803a8688bab9d038f6855537b4de42aaae1076066d00b23f4e1ea8fd228b87e3c7d3da2f42de4d143efd49f3b195c3240139452c70c41c05cedfac9ea8b891a372194d6aefd7de6617986914e2d394ce16307d3bbcb2f78b271e1bb19eba31c41d7f52d3f8530ebf0f0b44e3bf3421f96b9a70acc769bf4fd54e88fe6b1cf2b6287a7cf312bc788f93ba6018ad1415466fdbd2081734edc4580576ad943d3efa319f3e30c5908648342a4d0c431fc925a17913c622b10d793dc76767b0a77120b7521915676bd2896edf6e3707a3d8279f06b87f806a88dee508cdb536e8539a384790399eaac7b3a24e3631614cacccb6e9329ca6de0a75ec4e3c1ead8c30e722c425e5c1c9e0678cfb4783f676b17587a504961c67ecdeb20c14fc6aefb398056c6cd28765a7157d6b24972dbea0b29fdec0f437a4ba69e4c6fad7159f362d5eb4b76845faa63e02122ff37d80e5145ddada4faf20fdb7e313504734274307ad11a81f83f54841a984fc116c69e91b404dc300e95921393b55a7c52d0454b76f27b170c7f217d0d2480b8980d63727f58c0da05ca9bf7e6c1283c986a305cd134b5604985d9f6c1abfc0c4415259dadc3a3cb69fbf42f7e3ee56dcc7afb0b9381128336ba44963f160ce4a246abba462ccb2bc18f63626412da3677676fffc5c0d8a85c8629068e4ef8683b09bf70537a812196eeb1389e274fc0209954e16fd950f9415252eeb63a08c296c42767da970dd56f80a65b36638c324f78725897b3c29b6f8485f4c0c184173ce1ac48e66ab770d4ac097033b0d8b58d6c900d473876b96e868bc3b3cdb392b3c616bb7cdbc71a4ddda4229ef57d7160dd78a7864fb379c4be2c019745de5885dd2d67a6d284fa63783d167e1ac18d5333f0cf5de0c303fb962f5774104d94398cb9f56b3738399de69df7db06ed32ebd6c12dd2d4ec809b745e6c5318486c583d810cd4f229fe848f8c6bbea34887b22eb368f01177182ac27fe93b44170869574e55e7ec9f729edbd11a2ed81cb52fa48d29bc80acf232e75b75357c0191f442e878ae0be4bd763336ae338dafe3ea9e19174009d2373a4bbab948a84f2f8265171c31383f0691fd81ccd5aa4b3a6c851ddb8395320ecb56645c7cb14a099a2aa3e9775cf77579a27b1e1d1836e23cc2621c8d0a15a06c702007d97d3748c4f85389885d5534b58bec4c12bdb802e2bbb0836752c115a501b76268f561138838f0a16c25a168cd1f9cfebc821bc2e7daceb818537f94fe71f21430010f936f5042dc2b9a233c49c552db244fa54bd2868662a8f79645002897c6398a88f000a911dfcea622d6b2e7d88b510da0c52b269e2920245051328f6e1f8c761551c4ab25555d30e85e90ecf4b74ba252587b24dfb787c4f3e01c0c41c830affede41be46e4de1fbbfd693c6f071bf8042a48e711b1e5bec8194708d6682d1b8bc1014b3b345b5de4dac73f1022c8f6fd661dd7fcc242fa17253aecf6a88ca4041f8cb8cdeedbd1aa1f315da1b15a8387327f5c6790a760282c7d1e69305431b023686fc4ba676357f130fee85bda89e8b6f8de1cc31bd842559908f7a78da9d8f21fd6e83f06fb327a4b8aafc94fef691c0fc5e104a74aaec8151068b640f6c4b739570026c08182e20a69bca2c19d52894d797ffb529eb5ae79a0830474ffbc983c59d6169ddd9051f503d78f397aeb273862be4f24bc9d2f4e1f113a31ac08bdb24430b8a6f8a4ee95c0ca38bd707b1e5ae965a8258cae721bf5daff7fe5ef4f227fd7b4e2b805e171095c4458664c963b743eb05ef732a06889a6fc6792ba76157493b15a06fd531144545c0f45a4b6616d0f0cd6e36fe0be453dd8f09bb259128a2b5714cbd26cfedb7b27ecf3cca6563aa167953aae5ba390673c23e81c21a12969501aedcd53bf34994ef6590c8fa245bc67a4e23738a2d2ebd0066243f54ab9134174563631dcb97678355fab99cbf427b40ac552a04074923ba4ef6efe96a2f2d528ec552dded0d94eb2eef3eb5bb1acf7cfc947bb07dc24260278e4640c4dceb2409971704ce38b7774ec2aaedae311d8fcd85db07e7369382ae6ee4e35206f80c343d421ae59559c83439909cef11ffe98d9dea82da1281a231fd4e497849ce8bad4c4698d9afd65e8d98825c1459e12abb310ca9dcf2b73f50dde50bce21f912c338a706f0e4b79aa983f293a4656bb3e503c3f556338eca99754b72ca0be2521486e5ddf1d0981d166053ec25c0fa25797a92eddc7182d45a47d446d284249a2fbb758622ffd24662d248ce0ef906f0170a1c0be6193ddd41ea21c09e072a7b534af8b82acf00b70d4e23a1c67a2c941c36a1d7f9b70a45bec0b6a883218e765db9c1cc6fcabdef7438871fe2d0d5821784d6ca8dc792ce4f600547085fab1b7d8c733b687f34404625d580fa799c5a87892d6c28b741a7624c9024b40e2abb51378f9dbb593e59d19ab18d63e0db8dea9818254122a191a5ead9da0cd96806675f795bcef516acd50b8d8db5a33d8ccf46298e6d863cfd78cf54df893ded6d2e48b30e29bf77b99efcec1a764d1ce79417c420045e6e4b596ea39dafa845602497df2d3234bbf0bde33fbc1c2b041ee7918a62bc17d01bc64d18ace6a4ea7fd8d150219ed16df',
+ 'e0221d19cf617ed827d8d8cb8d2c8ed81b9b3354a832f1d14a402b371a0a611737c0543b0eb06b82d8ba56eb6304f1ef16ef6b143049a7bf50c4e2493aa69756d8c39f627fa89d9d741a99f9afbfeb81de1a5becadbd867245b984de125ac7e5ca1146b6abb2dbd204dfea1ef6c444367c064b05a9f556933109571d2b8cbb6b1594915e1934efc98ef39181c27dc4b8f7998286ab5bdfc91d9ba42f0ca63991c232c88351989f291d3dd64c9a5132ac57bd4a983c56e8bd1057b23c3d0e0affd8d5324cdc44b49ab6a501a407a858bda559e963e34e3b161225cb6b472ef65c5f6418e241a2f6c6631359b390ebfc91e854a80e28ab9e7d2bed35e4b16cc45060c7c26c08182c522b6f27a1bd80e7d02898cfc293d6da9a649739d003323e85e1e67813ba1020e917a1b39616d0e1236d0ca6a6ad35b714c40b1f57c6e72aa0eda982ffdef198312d9245b92443b232c09875e3fd31fc9453322fa081f2514c89f82757fc235dd2772825326e4221a4282483a00b3373c11af2b1d4bcbd7b2fbfbbb1449c0e3775fd4c9b2a7c33f721ae98190660a1e2442034ec1a00af6ff196da66ed471aedc2c6e7eec9d77ffc5359dc7f3d83e412c46ee1299bd330f73b173d8f7b84af4960dadf6736068ff8df31d9fd6cfc65cbea0adfb07ec0a8d9b548cc7315dc3511f8411e4e9a91cab3703e75dbbb548bd45efe18c9ec4c7fde0633705ce4d6140e31038aeb1c0197f4ac8999605312bdf7c75272bc0d011eced6e00f900ffab408b4290d85e5dd60853b192ed55cbe65facd0cd7d00866eb71cf559258bfd07dc728bb8e651cad8c2cc6f61a82fde1eaa619362e4e06034f8f979ce2fc1ce1d81236bff4d4eafa83b2f52947af5f02b4c353d75f7e2e61b3efd6bde5c44db42bb2863f2918774aacdf6fb99ba4aaa92b1923f851a8a9077d5a35be85f87bd1df70bf421479ea28717addae00a8055eb76f2fa0ee149e7d17985ba9a9dfcdc15d1289d19d416b19394de6115d0598aac08f22d71d0a364ce771f28539c26c0b0c608ef3acb624a76d1e33e999018c14ae3fa2a5c6d199fb57ad9f5b1ce97f37532473b029223a5973b66b3e9ee50925e9485713d1b2ee12cd130c323c9c2fce9c24e310fccbd8b7b4dd371853f93663d972eb2827f55d1ab36ce1128023a4574e29841c0efa244a985a3f80a0843c58e68403edaee2109a7080c504d0c5055ad0fbfedb45f6469988f72a7d6dd0438c79484a684fc02c1a6c17610e14cc7a5f8c62fa568a09672dea7aa118a2237286f88465a3f3d9dc220ff95d3b347e2defe9262093c679af8c27ac4b5828c6f1cecd01ffa68073463bc1ed6090192443914f0e179cdf8f4540a855d145e501e9ec5e87be77e0d792eceabd1217a905e7ce26e48879bf9352d1d43ddae7c21b826815de4a20f94de9bef22f1dd7cd6b3f2dd76e1a304ccdfdc5122b0f47a75ee3cb59cd4ca9b51c7338410c26d86e9c7336c87576cb70846fa6cce9176c7d10359d911d6c18fa8e57e23f1bd82372e1ac7162c043511102cffb693d9c5e2a7a9f6427965177b2e9be445526d8bfc38212670d192acb198d464d51f0432698e44aaa8200a4cdc476cfe7e9f3d1b447c7cc9f3246d3406f6efc23ebcab6cbad955df60a230c23187db352654c690be8f803a13710e5b8fbed1805a1929d5553d701d127c732809646910d2ff748cb8c5cdef6863f093ca551c37bda129fcd71f05ec241b3bdec261892a1b1bc17fc1d3c8625859be6d255e4240eff10653aef299b51c0b05670b03dde9551657ea682e022b69eeb2585f3ec6445765597a4b4cae1060c86a5121cfb9c7179b824d2a390db3238f96d5a67b2aaa9a8a9dc13aa1b576a8dfd51dc529bf4be907fb2886894d920ba8b675b997e1fc1fdd3dc90b8642604acdc03d63a640f02fe80cfceb93343088e6aba8f7298ea15de9d4022c6ec755ccfed6207bda015c82a8424ae7eb741721b5e0ffed847352db1cdb0d2bdfc6dfb60f61777c1952e957e52592f9f66fde7c92f0eb1b4d42592df2cd3a783dc3668c5c0c92e2f8d80f361b4155904899cee00a45eafef87e4cbd18252a901d4f72b68132135f0ce36693b700035966749102886a8006fa89a60ee97d4e01f2cd8cdafb7c793eaa07680754fc3ca3b4a2ad4a99d93ad1e7f6ed99d320255c62b02d504736250074d68be75190621dc7867c2ecb1c8bde35489bf481d840fdc1a9dcbce109a752e1c36aea4967040623a2962578aee38a56e139a561e01164a41d03874571908475405d56a8daa31b3d8983d53efae274e1633f02ffe2273f9e8c4a3600d8414393e00bd30c85cfcb99adbda49aab091c594114b3b848e2dd057a194f9fd869eee224ced5c0d2da75ac81bc19f819b7ef230d632e17aed4cb6d58e93a7eec5b04d4814ed5825c53c80a97f7bbf3d18f6bd32aa2e8ca6e9d995acb329d2a7e9513ef77e468ad8cdc21286308f0fa6ce020ee1b4c6a1d228da542239dde2f19b8aa978b27f5ceded42f8a75585f98366a9cd861d8289c4525adb8048383f9483627614d920352eccc31a5417e6e39e87f4e2690ceaac45105e120e07c1525848e18e101bb17c61134d60d59e3937bddae935d2aa87721c0af0e1139f008105920fd7b79fbb9b4f69e8b27111065ca90693af091fe04413e71730d9387d35620bf53a508ec6f9c0779df87298d6ba2ab4f73655927d88f04fdce7a6f8c1d738599a9f220f58fe7ce5731a1e5d2fb9078c1fb3002be7a87cec45eec30a53cd4f2406ef764bfb124a5a7300be859cc10a35120f5014a50a7f635d2d7894bb816f154210946a369df37ea492993ba23af958d8308e723f18c15f298c0cbf825960d355ecc493910740fa0eb9f9a5cb8c9d9c7a0001703e380986b3f4c9b22fbfe907476a4eee95b8f676b95b24eb9e1fbeaaff66f33329d42080024bf5964a1ad459217179a4c919e099ae616336a35c24296f49aae68853cff61e7bd9925bd2e770eaeb8fddb690c50cf4b4feae63a996c2d2936fafe41ddc90106abdcfeb4febf5aabfd6c0e2866c77933a3d7a656fdd1d312b8ab6c39a0ff5df02e87481f8a78f69d59776d7649e8094dd68c33117c1e193f94324b03232ce9329a4aaa637e5e7301519261d9023d2fe84c6b050070ab701e5e221304d0bc235c93799e1736ba50667a6f93a74c313d917a06a49a9804682d7aed4cd53647acbc9ea68ba150270f4b38417b87646ec2d7d3e18a464eb371eb7f0660eac039d60110540b98372f001e1fca71f00730403a0e8e3ea9f1231dd6df1ff7bc0b0d4f989d048672683ce35d956d2f57913046267e6f3a2c4beddc8e1f6c59df735c7a01993a94b66fdb29883dabfc449ee9353ba7f6f543ab7e2e6fc4ae3fbba591d5ca9958aeeaca74333c7d09713d7cc5add81fd7f8969c5ea40a4347f1e6fe37f36211f14d65e4a2a0d8e7f816b8d750008a4a64dc7b9b09adcc2fbc28c809c943469796e8de6ae0f2ebe30a653d2b02edc0cc3fe99923f9cc43fe8d3cf534c056169ebc29baa9b989f412b2d530e478c629f9bbd845c3c98000143da6f52bb79c20abd358614d97067fdb83ff3aa00c1a14678b0127f6d3596f25401f2e3b099613236f1d88a2f3d8edc1f04bc0ca476a1eaa0ffca639a1c90f9626ee270f40d45ca9f1e187667a81dc5a7a3359dfb526b715cd334708df5f5fdb749c660507c76bb40e3cf3f47581b9f9945e7c5ccfe06c5f454d90f0d67cee899bb271b898efdeb6169844d98f690543d1158ede1217accb0a940a6e11a22a151ec8c096aece1444ad8bf08212985cbdb30127b0064d070d8fbf29fd2b7f91026037d92ba3e2aad435709d8ac9a00ecbbc99dd9f26f0f97b3717560a8e65c1d6228821e402be00d930ee825fff00c9f25057f6d7ee93f2b8a3453532b4e51b04b852415c55c0c4e8326e657e4d4aecc600c42a16e7b6913a56b13de2298e3b12b3feadf1179b6575bc78bd620eef05bb5056ddc89cc94374c3f3e6b576b170848815de4ec29e41063185e0a7ee5d9346c1889859685c87fc7d85b619070f79deb71038c1e14a061e242e75cc5627140cd6f1d065e4d0dd35d5df228964f8a585b3767406ee3f132226cae32bc0cc1651f2fb1c6a9cd6c1b58f68c2831afd822e2fc0e66a63ee06b69bf3cf52d8cd08cac83ce2d3eacec3ffecdb0c82838eae1c6e882688ccc73f40063180ede89a79eba91fba3e79396279807e6734593c77f50224aace936b4b52f7fcae397ac4c819281c0906a2a70bc6dcfb68e4e6877e2c8d3ee9c8423ab973d3cdbde229dc91f7c7ca55c901885e4c74ba9820c5e03f47622f69e273c5f8074c1d2664603265ebfaa336f2106da88eca346b7471e729ba138eb1c9829d8591a3737e2ca866928a532f132e59b799125001595c750ccc1933c557da0911e0b78c593bc77cf9c64743c670ba48069e5ba8a9f85539b1a4d9257ba29071d3a7dd6946c5b1586c6fa75f21e63255cb26ef3686bedb5a76327e7b1a488957433967ccb4d276c53f0d1f07a9da331d85d1218c30236b31daf37da719a02123e1140b193bb8843dc21d32dfc6c31909c70bb70c2eac916a50665b2c025ff4525dc62be1440e663cc61e773a114770a4ff650448ead0ca5e67199b9c5cce7f7cf9de3eb7563a995c9b8f50c901cdb6699f4b9dc4ed2692a89f7bb35a91722f0e45cad80dfad399f7b2fe63ef20cc76e2474de802be2476b35a1b36f938e18f80d1fa7c579a4ff498ccda052ef6da2e8957da7784d640e32937669afacba31d7f2f0b9026cd2615da19b0c90aec6d49d9718fc0bdbe025454b7ec53d1b22d6543ad6cb7edfbfc8a1659f123f589fc555d04cc583fddff66c04c1b060094e6d398ddac582fbf19ddf7de39ba81480ddd8e821ea26dde8e871664810619ec7604c95028f56e7f246dd946f8d140bf284ed889f533da083631b1b1acf8801141556f7581242cc4a2d645049d2c9657c225e58cfe80f7138cf31c83fd31d5bbdf61e5ee4348ed27fb83b597b501049a71ff1ffb892e67690957c18c00668d34b696e70adde81201b512c580df130dffae36f26623708d7121bc4b0a44eb19245d80cd5bbf964dfa65ffdf5059b7350fdd69f74d6c0bebd47cad03bf3953fc033aac136166b7f36a64a77d76d381fc438604b921b165050b50a6a5fb5c25b849d47553bc71e1a4ea57962a74caf7d6234ea3927391f06b7e64458c4f3bd4d060c17908bd5f87d4f2a56860cabe8d55378da32dc39c5281b62de6313b56e5d6f08823acf48e3f060be48aaaf91cb6fbf8c9afe5922effda3af4253f401bdd3e717f01aae28c6baa9e8b79837064092cdf9906545fc85ce7857d6f00ee192f695c3c9f9d17e882453300b164d132f87a546262f48d06ee3c1e204020a5cd50fa293759fdf511376861eaded99be809770fbc3230ba59484a7d633508f7a6e66350f5cd481aa4ef8693b03e5c2111b0756a1ce0b3683def952bdcaa34644454377c0e6605f9021f1250e76c57114e8227ebfb1ad71723489e91e33c3d7c142ee902d7c0a200b74abe11f6f574dced02f11ddd3d1627b7e7e9482d578e9fa4eaea7df849b6d232f6cc6f75461ff47b97536839c41b4a5a41e7d70188a24f54fcde45c68dcede13b7dfc1d8eac2e878d3d163a74c40df98fb2c97064c032bc2f91702e8e92494c7e26aa198600839732cfd889c321167d2581a30a21e3b857d44373dc6088286de1aef974a324a67e9273a6f9f121763b4fd531c7b7bb61cc282a14ef4485334a4c5f10b54ce23e449371a511668cd53bf6f49b2e8f10d22511907ceea45012eda87f1864eb00f3f21f10ff598b2add8400270ac80faab66a79bbd361e7b26c5edb57380ae7476f8e1d9adc59c6c2341d57b6a61ba051b3a147f440d17573e9a3cf2f992ea87f57237455639a801e8211e1e996b1abcc71d46383ebc5b65b32c66a4f6258967056b60e8abda3220470658808031296e4990ab4b0fdc51231efcf96febc1019e11251d28011fc123da523325a14c4ca61d34c24c39e59f1bfc7c43a85cfdf999c33fba881882eba91ca53c928f251b00bc6a19a03f50abbcb63af3e2cc24c7e6762bd78d34ac081c787fb34bac441afa91b11ffba2b5fd81e6b97893bbde0300b479e2abf7e3acea983255b58b31a2e057aa4392e67e1b080868505faea21175089a6a78d4d250bfd67d8264ae76697e5896331a7d216abb95c8099b16f999bfdd0ddd585de079f8cda1d7dd787ac5112f355cf6f9488329096902da774d025dcc64c9def5a6bf21e85fb4849bb75c4545e82fccdd4bfabcedcbdaa25224d1bb311fae712e8d66c7b107a6fe6ad48587273f39e08ba42803bd510f673a098e74b59ef9c37b1d2756a22a2daf782ad7536d9af9e697099179f2a90fad91739ef6c4734a2a6dce675b4637c72c3652c836638bee5381b4cdac283469e9fafb89ddd82d0e3a7929bd4217d0f1d947c4eeacb3a295abde6e32f6c8638cb0c8f9e5868b3cb4682fb77fa791563c4b0ef9a122d85f7e43ff7e78064de706769e07387d3822eb27e3e044f84d6815060e7996454c1306d0876e0347331f65bfec9bd94e7960011e484ca3c0a6570a7ec8cd1460797dddc5e8c54b36128d090137306e66c98494afcf45027d26d38b39c05cc2110ae05198a61cd65f66a08edf006d5e52a2f11450eb71e79a594e25ab87b125e35b0b00bd31cd2b2f9a0a659dda9b3f9e90461ea62f4bc9b4b827586871529633f42e69c83e5ec023471b0be2184278a70bf402140d4b3f38ce0f91e52fc9b9af50eb0b3e1e6a1bd6d86300305c0b9008807b7d2ef7f89eb3056770a6157f06921bc153834447c4b6d862d10d185f1c3f984cde5b81cc9eafe8bf532fc4fae3a89f41e14c52a0214fc1ab0cdcd',
+ 'd2799441abcc3ba3bd2aa715899eff2f18a12c453c483e3a18bb0c99e2e91be1e87ac221d1058bc52684dbb01105d68b64a27d5cdcb2195aa841dd0025bec760ecf4c7ba0e3c234d9ef01a6d7022c8d6218e494057ce21334acddb1d847ef1a2e5ceec33e9d7cce22e56aa74305f9cb8574fd91835031e6e084750198791624fdbf079b4eb3f4e2cdc9ed4eed8231cdb0fcc750dfcb8d7b2ea97821bb660e210d642b6679adc71d5b2b0e003f1d50ee451ed6547365715a8f7a6ba4c9a51398ddfabea728116fd82b87416da02df3b7e239af0ed6a47a0f8375a3fd3bacd2e6dfd165ffd2556b9ddf5d3be9c93a86b4f8fbb5f2721f04049b29c5bd99e3d8a5839685b3110026e8e71b31f702d9865ff9c38fa1fe2babd4371555486cf0715f89a8a2735c984e43d34fe827f5717f33752f909fa350dde8f7b6b7301d49597f228640b32d842e391479a2ff1998ddb9fbae7d4e3a2516cd5d8c80073354dd8f1ebde4e50c6a63332b1716eed7b62e6dedbe8a300b2ee30bc915243037b999f9bece13ab619169bc97a69879b86fedc9dbee5bd79ec4cefedadf383da2de0b6983253158d5cfce5feab6cc12441ca858369c76b77d9d3741034920a1fd389e391c51e28fc14bfd7784a6166b0342ca939e674f522ffec86cc2f161efbf6feef2eae28bb2ccbc73ae0622fbc66e88d0f663dd6a3a1e8fdecc9a56908961da6a216f45b1645087cd5ddf8c00c6fe45680b374d4bc1d1c706fb09e721017d0e9c3f29f9d806e5446721816fb9df3b0edc4c795558abd21abd72922197da7972f1c69b8d843cf368dd738c3c8b919d5bca34ca74353aa8118ecdb3a46996dfbe0732445f3a59136b9cde6080fa609da29e5e7b385600bc41d756fede1aa92836491ca51d6efdcdc321ccaecb8adec479e5142c003f90970243c8c44d2f93db8243e04e16968d7b1608c8b77ac16eaa582b005d6a566cc0f9407db4501ce972086417aff945aab3cace5d2b1d1292a7b3dcad8fd53ee7b28d59f04fa5771c845f364d3b23f0b7f057cee46a3102ed5513767613ef5da3e444fccae6bba29f7afd46db80352c47c9539709054526c12b0d74f4ba073723fea6a55819f133f4ce48f25d0f8b5cdca734c3457cc7d2c0a1e87493c2cb5fea3a28e04279e4bfbf06e8e54c49c506e90271bff44147ab6e425af1fdce9a3c695f5239f457048a50bd537c23bf40beff73a109cff333252c94db597d5df26123b6991ef861ef485c49ff436de0acf97263392d12f318c48e11d027acc162429bd8840bc8fae36e344562c7a55e582b0459a08687c176fdf531dfe8f6c97865444da98ce0b2bf506b3814654478d0e807ef8dc2790ecc56048d511a05a6935dbf8e602eca09a1b3738a9ac8d5c0b5950f7d475d710b87513e7cea473419418060034e4e0f6058fbefb55c5ed3d664d67d8b63b40a7836740598f1dd2c721161c844ca770b67339e3250df93b92a6d10f4a269629fb56832fc5a0e68394171967aeb7b46588ed01eede5ab04102d4cc8e75adee5bb438b128548a654e517e082da4fc7286137acf264dc252c536f6282d80d2ab9f7b32d9722ed404fda65ecab78ea3489d00228bd49bcf4790d617cc34931e35bbf84be3567a062a563ac0494d9534a070f6ed1d40ef09f86a893ca0ebd818f577d28bb71c61a93e23b6ab6c37e138e1c99ee46db272f7cb1af76f8c997448519db1fa3d0c0a44aa2ac1281bbc9937726ea00e64724c21296ac9b778889236b2f33fa15e6d9d5fd534043e3e25e5b7bd27a374e8c7a40f7b20a71d4cfe586b8143218700894482270827f5bebaee4229be4f1ce2fba24a270538a7f044ee6e245302b40ca625360870d15581c3dc5f1ef94e103b54705997e1255f4ca27dc705eb88316b0d6802e032aa677088d79704a24558d91b33c4317c154d495f2dff8ad55b050306366f17175b3273510670447fdffaa69716145f9db178cdc5259d1dd82438bed8b70415e78cbe736591744459c20089123ed0880ea1e8c11a8e2925bb8bd383cd1249436ae414560da12b6df7967169d2d68013958ca50ea78f2b0a4737dd392b70c607670c3b06915e1c27230456302004ac6afb1bb89ab4512c3344d15eca3bf804caa8ac3b6939efcfdba3b3b6c544f0ddf407f5284f89fcb40a7a005f1d45d38af5f36b1d694c7cccef404d991086c49a1983c2fd146db749f6b06de61a9128e0ee11f1e8ed142f9ccdc27708f92ea56c41352c3fd0aaaa755fb0c0fea966208f1fcae7a4dcd72ec6dbf1db096883bddad4e7d9f76d86febfad40a0acb20ee31eeee5f55a20dea1d2839521a745ce346c3a5f712dd24adbaf4929b5fa1dcf9815c52cc211a071b1482c75f1e7785732aadc669de7e73d68ce9932a8efd2f267fd0b65f7d144d885b13d5af1d3e966de1d20c3052f94a9cea306252193babe795c28593ba2f45a4795200ccc728143604c6f40500cc1d434b7f9c9c7efbb7df6bd84d0378d98401c7a3c5328cc2636e1bf32a326875607c390d8e5430abe7506a58792939918d32eeaf9f7aeeac18689ffcfb531a63b8aa3b80c423cedacf0cf9e9966f6cf5c58a56cbfbf05d33b7c7f93b03e167359a5fbb7fea85b9e6b347c2f221508354d1aa989f674d58f7a60ec033b0680f696a0f31315de4827e58933e18a6872d6d16060c706dec827687af7d8dad41536dbc2b556b8aeaa8f00391c3a3924dcb7d171f5b158c584a2ec11c999f4717d3b1155660751de2adfa68b61c49714ede236968ecc52f1b108ed6e89cef0a6610d1e9f31af47376f1ebec627070dff4e5ee61754a25d2af86255da6000383969b5ece93cd5028122543033998a01aca733cb3c6ebae23701b70b9b76b633f23c3a617aaf01ea84ea8ae41adcd5dce49f6acc4d049c47e1730ff7f9f278499b83a4679cb3bc670770c7c1c31b70076fde09d09140d1f6f0f672013efccee2fae5fbe595708ce1d45b13b1757ce4e8150d1bc15148e0552c34e911b0be4166d90b48c2ae0dfcc0b154769c7927b7e99ed4a586d54451ce5cd27b0f995d583dfe9c93e82fb2916c6703f96818457471d1dab107655dadc74a7b31e33f049cd03141e23e6089d54dbe0fa3fe97fea0e777c8462c49ba7aaabcd51175ebd993853ca23fac88b74fcb7d217d464179c5b558456dddc88543de7b8826ade404c7c5e666b11af167874f6ee08d285ddf6a423cfa2d022be38dcb4f3d757474aec0f9f6364170e1bf063f57f5dd8d325718f6eb8e8e83bbfee2d9ff9c08b0e1ff048735b9f596a1e753050107755b090a56392caa9848bdfec970554ce64b741e1dd96b98d7757908f60734bf5b7c2a20ac73c9f654397c0bcbf817f6172b753493555f7aeff3b04a78afb7c599d6ddc0a20c8819c93f3f4b6fd90e41a43a2c68ec65db0843f990d4606454c037536a12c16c32bcff404de93d7b02d6945ee0e3b270a5eade705fcf368dd1583bda183f90fc8f86ff25ef0bbd47bb5cef81dbcc6cde86c7c53e66628394f736af52316f98226620d0d76dda78a3007cbd8a634ca3164647024dedb6cde08e029aa5fbc95f4780317a3d0f7df5f89ade0761c18bde82ad139b833cf29fd95e6305babf766fd4a662063e1d1cbcaf5229d5ee3db0a58983d39bca9a3f7ee72e02f779c49e502f9e5b7bc4dee1562eee052d191ee480938f2e070951c5f472c88778ca546788c230c3e1b69bf24adf361c19caae4ef8089acde928a7ab88e2f2999095b5fca1f4264341fb9c772029f413a0e9331794516c58480ee51ac39f75e048c23083232c4782a27d29996173ee95ca5ef666ddd4cd762da552392b111906a7390d2594d45a290c238a7f9427ed48a0113f645afa85cf9fd438314aa1a5b0b6e394097e5328df87d5065341acb9d429ea5638932b5b0ca683dd29a8b5e3887ca60d586811794d1c7be63af06a1ae26ed2820c10d019d54a9a8a4acbb79936016bbf39db76141ad2fe735e2ac9d81a75af0c055a4f85d1940287bc3d0d90624d1bf3d55eeac3efb244f2e77631ba23486b3c4df81268a98ba11ca6862190b3699cf15379d54c74c236aefd5d0a8aa6efec5a0c727f8905386091302c5961d15cd801dcb49f78500cbceec666bc0e4a701cae695100b28ba1272d84b91fca652eb56b9898b00f2c98bff96d19af0fb8d5e1808d1bfca9e6d0df3ac7b5da9417e71d76aeac70d9af6c251818fe4b5497262517a05bdb37bccd6efe24a6f1878e90f76b4778489e8292d893f09ef9a79069cfbcc9960424b69ebec2c116ce6b73312b928a859e254c12beb21c801fce4ba8c7b73056e1387b4db6cdc68cd8086dd0a033a05adb37b6f2c1bdc42c27926feaf550fe22d93bb4e23d695c91772625774096b080689c683cc8fb122b77bba43841cf5b8354f408633ddcabac138c422df203e37dab1c09f8bab52c0479119499937b6e00ba2c20da9d6fda6514036dfb9ec161db0e7e412c81333a3f935ce515d39d602174aba34e456a1144e3534ee72c195121f88cbae204bd65652f633fb4e97d586ee8393ac81c157ad2e6448cfc8553dbd8d10c19212b9bd4fdb4ef4b7fbd637f707f9e8d4f0cb73a96869dd03f8fb7298700c709f2de14b18f8ac8b07d3797fdaea1a143ebfd9a7c182b28c1ba338c60b6ef85305b057121a319b617b64060f9b0b70c04a4d50466e13eab08745a1cabee050e366788fb4ec2c8812833be089bff27a57a837d3e207825ef4c75eaad30b5aa29c41b4ff7607301f08afb9bd50d225b354b8fdd90d3654abc36c6cd88179646a0828143b07f3f2ccce616bd1074a1b9831fd1dd41a73113da6e6ee9f58916413917a04a6dd1adafdde38d0e00fb05ab599f4f669bc363ea109b75283baebf04bd2c804d758145f3eb2a6774efc7d5987d72135ee45083362765e470a4ad18f5f3682a3511b58f60be62acd9230399e8b84125af65751a5fe876c2ab76aea97da574a5e88996145f1d346524e5d5da02d2b48b3f665bafcd187317c0fadbfb0599f7f950254b5b56d248993d7d651e5093724ff82f29cae78207eb97785a95f3989a2f542dfcecaea345500c33dcf039c32479c00708f317edd847183853ffb06a054edbe8d350d059d492784f62d52529710f813820b3c5208e322b81efc2a5d7df5ecf9ff50e22bc9c686b181e577c8e021b2208989157e4d2b5b89d556539a7d068179a8dae0e9346d9cdd739fda7ba7ea48bc0991636014cd7a7d2af70d3a182729b21c1b9fba879ee84d5db4f0c758eb3a4a74ac8ca3fa3a0e069226cdd8f9a87437cb9b651c1deae79572ad61487da4f5507d4327b667f184ba9d8e0be37c3acf7f29e2d77a71c2194a8511927b7098086265ed9b23d8a48d1dcf954de61a3eb9fcc98a6d722dc4fbe0f76a1aecec44e1f4e1147d58d69375848ac50a5d7e24b2353ceaad8f9c641ddd3c2f40f95b2c208c515034e1ec7edc9371a5d7b6029d56bcc69c0e2f681feda3d5c9cb4d17ce329d3919583b84a730def6b02c8c3208f5f8290715ddfb68e9e956c21034dc9ba6ac2ad86de239ad00ab9c6c8f4c96e83e8bc6c44b17d0cfa10231c0966d0424db4d8935cc526600caadc5336673abbb9ecc125c147982c49172e92de44218fa6ed5f68a2a28b4430d4a23cd489deb13754dae27085f8bd839d00f627050957cdff9d57dddc18ed437051a62ac37cf607094fcbdad46468b189df716d105042c8d0db8597db96044d6532bbc17bde3231f2680a869da5d5d9c3142b37cfbefaa14a601d5707cc30579d8da3201aca0ebbf66dc4fc4683ab0632e64b0da91a2460547645a6c8841a67ffaa86cf090931afffa524c539c6933dc09ff977062e6b0bd563250b86846a8873d08b57af634514f4594f68dc36348854865ffe4ec074acb770e70ea995c7bfe148da3f739feff3bcd1706d999b37eed6e2a229ea99ea4ae1e5b037997fd916315b9c0fbf87d9534155ad5dd7bc43782ffced81408173ee3b0aab0fbe0a49944b4ef950fae1ab3c6a2d2ebffd62012c451c668db940b79fad26fd1d81febf41186c18898b760cb71fa01c1b75551814018935d5bd9d60651f83d1e94822e2267efc2242dc0ea5ca48f79ffaa65d31384b8d83e1b561134879674dfc7c3f5b4a66453e0354711e80ce7bdee2f842f49e0c6e783a07547a51a31b99dd861dd1b6b99095555b661ede9771025ed3cb08502a753b671d3e3d8785e7de14cc84ed705d254fbf59b64dee8c2432f39fc216568feaf5f05ee704f30812442ab83c57823c4c93cab62955b795db972bc4edc5b62115cd5e3117769b12e6f2a6b10cb6f33d4d89fcdb87db419bdd598daa14be7aca3dec3700953b898ca911101ebeb3cc476f5ae02e988a9584739fa1ba01a9aa71ac7906322afcc55fa4c8f16918f51444fc2efb182407ba3ae591f05a7b2d4cfea89d502f9e6155711f40d0f9e325d31b3a2cd702f2fdb8a37b5901f349f26a587a809f5eb91143ac62a03536e6ea12b4727dfb52cb1298a97d1c5cee34a10b696a497a2942b597f7b411a888ebedd8f7eb48ef7277ee613332058ab104e23818bf216af703f457395fbbb257b8d52dccefb63cc8be3a1b28e33912a6815cf84dc32ff631c2778d5ca9c5da0b174b191bc4ef69be689ece72d8e37c3472b853f0bc9ca63d48e54047db58a90ccdf7a5ccb31afeba3cc183f8592951c2a19d51eaf38f02845de779e23c9ba6c3a580d8c704608326034c42a2ae3318bad4b4fb21b031936ec8253f1516b75996668fbbf4ffe8816985e611581e2a5983d4a5587e3ba9e06453469ac8afd34aeed7cb543becdb80eb8a38fa02f2369d85691770e9db2071ccc4f392e351180234c2ddad8512600614daea0488737e6a285ab24c4bd9becf0fe4d3304e42ae08ceed6cceb8baa09d4c3ca337d2e62b4bd4711ff38a7daa81967b8f7bf2cabce4262021bdbe2cc2ccfb7a0b9cc99664c5f174066d900f0d0fc851ce7cb5336a1fa225a4847db0ef1c895acb3a53ca5262e72c0ec343d70e020c2d393980c3e240acc39c2bdf8e4f135ea58b1a59c91da91e',
+ '9c4ae2217c928dd5864836be5d4ec281211471aa441a594b99b013e5ae01b48c5c4ffe479c80d8b69cdafcf7130bf0c9d16c37f29a86c8df34d6bf8bbfcd53a2451b08e5922d25d046f4697a28e9fabebd4e9e981ab62dd1f6c747df033f42077f3566c405a25d6caa1fe51145f0c8a50e420e626bb17169060d11aa235e6903312582ac9ee566d2f0e2d882122942c9ebd0ef1a357f7aef8d3087b3c632b0e08374c36505002a4a41c6aa96369b51717d81a986222932abb2b60fc495c400e49ea990b6e1fa901cc552c3155a4edbe4ecdca46fd8b680e59e2913a3381b3f59aa4c5368dbdc7f8fa30e8cc7254bc96b5f6a499cff2e4be47810fa19563f5785cee673439aade1ec04826b744099044efeb1fed7409a7df169f42cee97392eb133fe580fc7759d7b0f37c9e3524073d5f23b2ce64301fd54995c11eaa510ae24011c6a94093d9b84edb40fdd0449fd4863903c92af6bb3552fd011d525ccbe285fc8119a090cc34fc581a32afebb8cee783694a32ce0873c54d89476c2764503758bb5e86eeafd24c027ef92e79e07105ab79ee1646961502ffd029622152dfe6cecbe47e1df314a062d59dd67fb55f6319bc114fc0d29dae9c6c3fdedb15d616c74947f0af4470abe1b4bd228d960e8246864039fd5c722a9bc3f73cc53bd749ca99f58903027ac107a2c3acf400f2e5ba8f177f3c723709865dec06601357f2515479a1c9cb97825d064dd07384a0fffe2cf38a0ee260cde4c09ab65c5adc92f40f50c553ee6b525a85b8376caf9d4389f660e4f4fb63da2f626fe6ea70d9be1b10f77e42094ad388778f4c4fd05c62d661ec1a8f4bc4d0ced9f7f2cae3d3d63f0ffa3704aaca684f725f79ada542a07bf5f1852e4218245b8daf744e225f7de852b58a2c217e9347fca56a067cf4bed3291dcf627cb2575df412d614bae117af9fd5e2292bbbe6ea8cdd77dae5e7008481cd53bc6a47dc3ea3aeb29f2bed25ed2db7c97c2826c5af89743d9b7eae0808868cbcac893d458622ff744de3e83b39b2f7ca958533d22f990615e8859e47c7bdd864a357f705ccf871a5b17176ca0d96024015e1577eb3320fbb22b95c424a7e4cd6415529af6b7aa1c16a62fd54e16d63d47dbb263e02db0d77da884c8a196e36ac071f39a4ea400c51c927006e4d9f98e23d81149266fa6bf680510381bd0442305504ab88d1df1dd16e3c1468bab31e13b5c1a71e8816c781a4c205bb9db6dc1dbbb3f3cdbabe52baba209df6b13e6fc3a6fb5224cec68cd3c859b7aa441c80cecb9ba6d5fd4447884d63217ab9980a8c619caa67a37048576b66bdd048abbcbdd52a3954223768c4887233774c567ad876341439beaa7676fe977cc003cb0c8598ee2faab1de32fa0bdc486ed2003d327540242d78db83dd025a78fc0e10ec906765fe4c34cf3293481fd4e3f31bde24bf5f6eb55a9a1cd4073e36d6d003d978c11039db2370f41abdab6d59180fba19d4c03853ec5c9e068da13b02f22a5fa9468b46c9f669d41aed1d7650c9626d9c564125ab96a983df0621f9e2dceaeb9c6815b98b26207efe46d4844d7faa7526b420cb0ef1f76ae7f13ef80908cc09ec966e16a15b2b313f6b6f1ba1c34e4d436f0d7d0086ccbf9bdf66c9d7fe4436f461f2c2aedde0b778bb4bb1100cd4727ce755d022bd9ffe754127c3f235f9ff5e4b22620656f75e24c708c8fad4c3712dcd9848d80ef7a40e96f08aca4843cf84c29b693a2ec5b1be0f14e7e4b391c5f082436bb3035a5cc2716f92d29dc8240258bc3cdcfdd24ebf13fe724011d5dd4f95f5ad26334e652b30e350adaeeda9418e1371412ca381a246341caf87f643a5892d81b4072a69c385fa68be042aabdcd32f9b1933cff70236442b758e79858ccc398def0794e705c1233f863605b84eb86f1ec7bbbe1b5964ad43548dd61f8fefc81639bd14497c72ec3988ad156af1be6070cf2cd46b0241ede8af0ea27e095447df16700f2d922bb40b47e1c02060458235b1ffc962a76e747ff799552d93aa474d17f90befd15c2919467c9c6534fe72d1f2bcf39eb34adf9dc0b674aab95224c4c090912cd47499e806c600c5ca3980da13ece97bb0578db69cde7781e4dc9eea82ef1550f615b4fc0b56a3beaa975ab297e8218629151d5eee89d42fa0b9e422d15d2ef8c0afd8c9b6b6fbac50885effe167225a7813fcfb28411110676ab3025e733480ceb393d5bb6f5f3b569d0c26a2c2724537d4bb1fec7cc4e0b37f7a1d6cb9dc857ac164a46bd864d82745f0ab3e9277f2e2c7c631adc555fd1f58a7b89ea97d734f49d9ba5a9a930f3d039b12274fc9c0037dace4ee934ae20596da8876ba9dd6c6bd418bfa1e704d226669dfff62de591456dd8e217f2a1c00ba70e15775b42c7bac5aadecec632e51defb8929643c0a9ff3e6a1d8d16a278b240977e36366e0d8978374858d020f55edbe8227dc5970f0d546d4874281812f6412a08cfc1c9067ff3a846668d37c9903217f9d23bd18c16b7c7abf3b06b5481b1aefd6b84bc7a4540e6202084df2b5cfd2c5dbe2ed6e8c270628e33370207f8980da33108ad308367d81eaf3d81e16a430f9769250eebbb8624395b4447f306fe2e434ad405f1e06e65883b4083b4610ca8effaa8e15ca601f7f39460a1df51ecf924dd712d85045f77a5eb164f6cea60dfac993fb7052eeac206061278948169569013658bb49841f5bee8464bd1ac489c187073f8acf9486db8d8e235db3f7e2dcd143a8946441dc61b5a58e1aa50cbc779a4cb0c1aaf23df5233583e7a3332662407778e02affad40468d478c17df2481860d05b17f983021b3efdfc39048de9dd14a501b182e006c13a4377dcdcc4073dca084e6b7f71bd5d2258d7e25a979e40654b4c1b64d84499f92d48c41449ec300afd4bdde8f4f85e06f2383bdf596e951ff1d608fee876d6cd185241ce89e0384937bb36dc159b681351850e39da3f236d200740f81b84bedd8efd1bb28dc99eaf3d073e05db8fc170ff28211022fb33755e478af976579a5216b119675c91ab639695bd084ebab14784873c0d3a880d5f36feb0a903d5b15339d60496fe1ea48ecdcf7b90760582bd660c29d729d0f9542d9478b1c9e74d097dd4a4e67c5ad457671c3f435c587afc2f1daf17f7b93f7ccd71abb9d076b49c6d14c100dd2b82e7eee3d3c9079ce32c66249575ad2d26fe9dfe116821682f338efc238b297bf565ea8efdcb7fb703749488a4985213f70225a17167c3a706f66af59130683176fae18486f1cd9323fd40b1a429ee52fbb2eec3945dbe19810e8868d597a3354dce5d2d36e2f8178aeff2075dcf8ad477347fcd43f31ba8e8a370bbb567e26fc208e5f1d447082a827133f296332c80b6b0602544d1e13b82dcab58fa492bc71dd102382ac706b651ab89da19fddf3eff4f1a355f9e18b998e2c0a56ce48825a5503a6afe0bf9a240c67f27acd4a8f6993834645e03c80c72dd370cd2e10071a3ae18ef19bae9d697ea9a4118609190cd95361907a7fa1b58f499f3f5e79b935f12212f437dde399e3e6490244aa1f5e38ba9be2433b6ce924f6cc49e9f627321a5df9343fce1b59deb647d9a3ae00b234414ba7b4e020d67173be693b05cf9318ed981e3def931db18226e40d757bbd4e8ff75d5a1e8c021748bc19dc4920ed6f69762e9cc2f96a1ee27cce0ca0dee409ef257f9d36fcf068de6bd83800a985a05a29f1e6d8aca3795fa0040ba0a8aad8f76c8b18d9f225f8bd3420f8ece8003e2819e4a6a12b6e53423702950577864e31230c37781a27395dbb80796e6af1ab6bcc9356acf42f3f42b866234fba1adc14ff7f8381c358cd48487343f580ce6a8dc87a616ca57a8fc99a9b50dfeeccf51b485187979c4f07059175e322e7eb1e1683f67160adb4d75f540e884694014bdc942ebe19e38e890dfd14c1770efc189e85b3e1197b82a86c8b5c4404387c22a169738199577bc5ca01bc895244660c7e442f4af7ad48758986f11ea94a822c224eac9c8666bb4fd235e9854546bd4c3e55c5199ba19ac12f0d69729658b7cad7aa4649717fa189bf00385fd074565a672111c775f6de0dd55521da6bd181813a02ed22eb20e2f5d9070573846be5d9814c91ff072ba6de1514b6d08a4373d1b1feedb343e8e426c8a0fd6ac18bd02c052ec20adf9e799456b294df822d035ed7e4e4652c46299f06647ca02852b9e47b4e2e856ffdcad322c54861e40cb46b245b5dd2f4b727c10ad7ffae195ee7754c2133f928981f0cf1a35e0210510b992fd8b6674dba633f4e6212ff251efd51005e8ec09a1beac45bf3222aa7a87d6b42d37aac87ce605c5999431ddc01602a304c7b9644e7b27bba3b41660a519e4415d2371d4371a3cab4e40c849ea4c453447196c4b99a0ab5a4c2482ec90ac2d6015b6833f132a1ecd8e8dbcded877e5eb5747ce6588157e4944e82ff54329f73ade58a7b219fef454e0e3d44591802bf1bbb7a75b26c2e8b1136ae524c4798bf73f71a7d5507e0567cdaad9261d505f6efbd3bfd4788f33ba3f106b211d8231bec169494fc0939b2fdc4a942885cec989a002dd798865a58b52448b93562e30b7eaa956c97123fcd4952bcaf4d0e1b06b31abcbf25d2a884dd46d0b227564d20b459795ea6c17717d80b5463206735e3c36384fe6ffc822274bf2b477840c5de2579cd05bb85842522651cf89306b1923381032fe7725bc42413d4e3a8a0d1a63aa21cdc35dcdb77c0e652ca155b02a48be9b3be1cc2b9dc63db5a113905151b7c58c55cb645d1823c05b26237d2cd9d45041343706c00fdac8c274a89dbf4a053046301bd4f2ac6ec59985b7b0c66def766ae3aa2ece58a669cc91a4b93f4c7674b8265ab573bc86a4891104f76630c0b97e12e04a3cc153a83bbeadecb9a0321849ff300fc79093e59191b359f46870a0e5878165f292090d78c46b3f158f60a4608247ebd870bc111bcf6bdf595e26b4070b3684d813ef21e96b88b93b5f29a0677944508da429af8e91d5907b5a0b7b4d65a7b1d68c2e6bf77aa9631458b495c5aad247ce73034752a0997443a3477f1d5f7bf3d444ed730abb784386c42a0be2dd97d783277261cfc45e9399830148e0bf5a53ba8455c28bf49d647ae9b28826dc3b0894069d866dc4ccf66640625cff4eeed770948edfe0eec0cbd9117596f96e67e2ea59ad3d946a8ca490239145e0d4dc89debcbf527371888289829abcc35ac36e6b67c90fac7c46a50174b68bed19cf71ed4411a4ab459d9074844a83ec69f6b3007a1a831dc3a1029f7bfbb48de46d968373ab5cef31a8fd8061ad71dc63a9b992d8c8214f0746c25480864a27fe5e008992b502a9fe51d1975fea10fb10838907cafa71aa10dded07ca7fd525304bb7e8e9591f40743f15a0c82ae8b2372d2a4998d8fc97a50d8ca0a20fdbee40dcc486c0bd3a1c5b1bd183e2947f67a24ea43e8a8b497c907ea506505ee6eaf6b82e4d5f09dba57b8c0b28f6515f83178f00d0e83be0c297d943231c937d811c88fd240d668a439c3bcd774b88106f5f9845e6f90f4ad283c66bde4dd6291b78f339f214fcd05e85cf1e582b62189242107b6706300e121bec271f2d517020d4a2eba1baf2345efff107b3dc070153879fab3d57c6b4ec29f47ece0229ddf8f63c1b1848703fabd2498e70a79e531cf8eedc4f34f8c5c7738579d41589f50ba66acf3f05a077adc67ebe77d44f94545ba51685e5eec80bc3481e17ae9742c2aed411fe3ba866cca2399cb0ab444e62796c61c7b1d1451fdfe57780157b4b83d521e83d30b094713021320374a208b01ae9c6fde2c9c46d8f038cf7915e98b7e2fa5de42ee1880236df875976944ba505c74c0fbefdce5c9d4a63656b68b2c79e0676db7b62cae97d1aa398fa409ea904acfce58e96ea54cf6406b0ecc02019d1ee3d0a5f5ce28ccd7b8faa48ce8924dd41dc6899bba1bdeff67dc2598f6a174259ba5a24f75955eeaa5275a867947e592df77e29957a00c3abce81fdfc1928bbf0f4b1105ab5aba74ef4e36233482040185210844cfd83142803d71f30b4b3025bf3f96dae81f5c10f7edbbd592c705aacf707f307b2afa91824ab0652c322e141bf18811a429d0597f7440201c9d76f8eed49beee22ec4c2137e4fb8bcedc1d4a61f895c7820a388b67087a55a5be46693cc02d2ad4a3c3eb466c46035e49811fe07ff0ddc8a42596d60e6a7a4a8ba8e3ed5288e3503ad71f582a2fe1fad1d4684497dc839dd3935e4d3e1d9b40a85da406e916401f1b50657606fe4e2bdcddcc95279fe188ff498bd380dade0d7b2d55926c206ac8acf664a7e8fa7178d265f4dc168220119010c671bce86b3b6ee09e96c3ee89d35791c5f3b86c73691b3488214ecd68c0eeb0b72eda906b52e73d261cd6c8c4c983e82f625e9c7b861f122f6ab6c8e01e81f10512c21a06d8500d962b8df6433197c44bfdc3d2b25847c791c136bbb1b2b29b58255dce6357101895ccd0bf2f93fdedc1cbcd563f66f91a572db6ed0204e7755a7091f3ab568f26cc06876db0771c2b38d4656f4b389ed529b71ea7fc9d8948b1fa47d3055e35ffe3e19a46ed665c4659217467fe579fbeae655631c70401dc05cb68b49af66a6a8165f1930fe82505cbb9e0847f4a629e8f4147d38ac1b6f9ffecdadf92a696314e9b749b3d83df3136a5930ff799513c7453d7d56e5d0f674664879c14f6211af48a0ca8a0c1f29244eb734f8dcdd36203a2ef7e9a5f76ed833b5533486f73ee183a9f006029c7f09b956a8e6a6d4fd6e7367817364f52555e7a2eeb5798f47465b416eca443c413e3aa5f12b5012f61c61758e86cb60a657e28f90d69f90d7680ff5ab4949852c3c53339259712dacb494e7939621b08e68c4d9ee1a51b24ba142352a059afdac7e837e20d3827b8dc17e1c1524006b39f2ff204af3485a30ac729e5f81b10c2e8f6be4112bfc8315cd9db1a7a2fff735a101d15bf3ebff02c4118fd03014473ee94d13e7e557dad1236416bfb578fb0ddd7d9b858ea5275e82bf8810a34d256a16c76c19721cf94c7d71d9c1bdf3dd2f12b2bd5c460c598082dd25855dc42d2c826b7b2a74c454e4363769423ef92415eb41d5549c9a0c352eead6c2c1f1d4e2d6a9cf9f4fd68434e83553d5bb11566ec1bb61e304d6c5d901791a4d728ea3496de2c7fc75d',
+ '198a696de98961e3ad0f953339350b5871d87a415492676d3ae88eb859c583758e2899bc5b1903052ce139f6de7a9e7a81455f135dfb9e7134e6d043c751634d9b3da4bbfd55e1b54424ca3356861d136e5c83691c84b5168d09e29c0c965530c9315311d92542197f401191aa3fd34abe87ade99716f383ccb88e6a307ea1469f6c6c67f7727059649ed3b9e18f0748fb2da73ed897a1e1795d9a553cf08eb6a556b7645d22b19e7ba4db53035d0034acb5370ee7f107571c5fcf0737d365a96357feb1151f4e104dbbf006199007375a892602ca0a6d0e3e26f52529116f4662d807f5274f8fd6614bd97a291264798c103bc18bcc1b2d661ad45a1b91f1b6293f9bd9999cbb193fbd5f583eca5f21cab240f4321d58dd1da3334b133b362040f6820575ed1c0070774a485da040589dd7fb42c5e76e46ea29dca31cf785ad30638420c329f8b47be362f1907c4e458be736544e11a38f551495083bad258480f069d2893a985ce8686efcde46d2111e4ab7d1d48340e1e6cc27237a455fcfb804f8c5d1074421406d381050e614e3de69476260258c6326b95a9be0a77bb36106fa9ea278432b0a5c86d6ec455ce452aaadaff9dfd13e9c43f51875bcc15a026038080575dfa1ce9c21f6bc49f23b0276b35fa7995599d1d3c244426a78ec237977a900e65eab1595d117c8db1d1276361f1a723b629d4bcf87c44e9aea904cb982299eb3097365af5de116e4278e3750ba8a63ad3e7194a10d43a2355d6f06882031e4c5281e49528ffeb14a56d9cc2e4c5581d7313f34af1c6744211ab1d84e1869dc0203d746e62d139ef7f31bbd369d23d5a1852cb1637cd83e48697d4b7333788d53e1c4e6d300cbe6d1457b93d2749d65f480629524f43f989091fbe65f3907ffa00c0db081d0cb92c29622d5699a6a3bb66c0967594d4458e3dc55317bf2bd3bd2cadb2c5965beaab12c380b62d8335fd18f48b06c4f8e882890b0b42d254348cf6456e3c1864a348551e4bd27f4a0d72b3248f7c4c2c62f47f637f8de9c402e8122a55c22fd173a284a4e741fb658adae9c31c4c96e935627560f84f71d5df6cf4bf11fe590cec381629fb7b2e804e941172aa0e31b9e04b2f863e0de7142a7a02961b5700817d878e0ff0f48504b91cbe95b117a908f41cf235995fd609649de021ea52d2c9980f50b950349b8d6af365beda1f6960d156621192abbb101e5701f4f7782c6fdc3e02d8a1b1de64d4a69b508d8bff5c0896038cc277f2e2d813ec81db4d99ceaa9218c05df2da2566b4c3fd33d3d7551a4a3b19a15b39dbf283044b0e39978093ba1702fd8dfb69c19c7417758d9dd686f18d4aa7abec762df2edd2f8c2a2002804623308d9ce8c55cc2021cb1f9f7acf0a7e825e10874bcb020d9c238f45c7c80f9ec2f86cd027a3c25cbe8d10789c631393732cff96b75a2ee81936d5ff62044a312544cd8ca62c2d8e4063f695c5d3ab407facef97636bd801dc7b6d3b495d32adec966270926f5c3959c8389381a1102a1a565705ba70abef58998b86083367898b35c20866ddecf26d326cde5a5c0094d5a1dc6e36f909150ff9d76c0947373883db72d0be083e2ded2af9f2aa54fb352417c63d63658df78514c759d6e9405f58cfeb55e27eee506c2a666d0c3c4e6fa830caffca66892b3a82d1dbeb9a01529cbb92048e927c1e6ca9389a0a6298c8b4855131d65ca904a2cc9d7ed1ea08a6a2071d591ce345cc613ff725ff6858c4eb7def5001b86c38504a709f0449b4e01b3ac3e01801fa0eec47288d85de1ce8bf92fbe6e71a211ab59501d90d5994836adf5c260a4f074a5067233d281443a8a35d0d72f0e286406524a799576a3605036d1c975e5adbf350189db140ab6b892944aa60b9c1fa3dc181f87c7d1bf7e9975cabb09e8606be4dc3ee867a14030ec3e2395cac090b676d256adee8ef88a57936701e458105ffbc019c293eee88ae15ae60b332482841ca97855d155536cd3b83001cedbc683c1d4a3d26098b8aeb71118700f70fdf715c50ce639ccda0d550aae248f1989bf8041637e9648b884b68a432b264c0af6ab4d9ac8ee9e945324b468d2eebc61fb7e2ee22ffca5eff528eeeea6405eaf6a2469ab62b87189278adce774b569561f05c4a64e5b5865e75364a2482551f639c943c61e5feec272cdabb170aaca71135b2a39417b0822438115e6799ccbe74ce2007363aabc461a364850309687b9bfc8892372245e2ff2f82842a16f74898bb7cb3a9a62831d090ddb4c1a4048370c9609ea471e0193db00c63e2864f6e4de8fcaa4294fed50259a8e2b9872b4f1e00bd1182a309b56e5e317637744f6002eb425387939a0e65824877711f273f125710a348224d181fae74238f4d61fe6616c057ef2e844e788f5eeb7ed892e3fc10edbc7b7c04d1a0156704608e4169d610092fffc24825c9f53ec7493ff8de19e2b6d5831cdb72ce176c8131b34862d14b8f34200b4024c8487a9356d1c37e1a20fe86f5712b6bc849073299690159f64db51d064616bbf1fd94c82e2a8d546b70473959359f3d4d1eb9810dfbe6bfa38b3d32f92f365bd5890fde35a6a0e19c45036504c68167bb0d1604e608631467786a2e9f0329a4f17f6e6e13d2b8b61b03c5a2beb08c6ba0988f36d80b20f8b0b9de586e8e5af7c44512c03f4b3f326651007abd601ab98c45978f5a608acd2db40c58ce1d64f4dc7eafad2e5caef71ca14645a761b6e1dca8c176c3c52b122cfb66a8543cb20c8880976d579382749c7909abf331eb7510169fa5ca956c584cf49fbf8758dcaf770f6c89418c442b644cbd06276d12d3d5238464c55dd91ffc33fa06ee1e4f6e265f819972b38e9c0290e2fcca5fd79e8a54026c5a8aebd9b93e1836eee974c9050589167185ac3705ea1df842a6a435ae3cac04e0a93f0602877c980047073091a5f578b49f379cdcccc07126be0329cf21e3c6fe352d25768499d2c8b351455a952948b24cab2df59d7d0ffd227eeb5bc84c3b09bb40c873c9e25fbebafd67de77c6ff013a917afee0961d2c50620ad28447d9a9a8146f98cc089748da66288bb2411407d564f4474abe313ae47708d1da591b4488127b341f5f220d7e25f7e0c91a106ca9a03a7d1057d4bf4b0855c85f027860e7b58bf41868885322a7667c40c48c7613ddbdeef5f5801ff0a0822476e69995239812576bb722d07474f4ccadd2e0dac077ef3b4019bbd371ca0e74b647d747d8149ebc7dbd9a0bc029134064c7a5f12c095ed0a326f0704b0f6b4791d8ba0f81c581b1b30e464bb21e42860a15546fb2fc7d5d83cfc68ce8cb190b9e0bca32ad257bc5c00bc036a6cf22fc023e649cf1dbdb206656967fd797a84f3f437077a6aeed77194880533ad60510e1b56f0f7521f8c7bf6df8df93ff9970fa2255352aec473423471f07daad99632d1ccc3d996946c213685602aa6d5d1f532ae1019648298b7279f4eaf2c0307148d714bdeb1c459df6f846af976b73a8decf9c53655cf4eb8b739a633fb86ab9d976c0f6e6a585b9de70333cc7646dbc0172ffa1349549e2d2a678170df0b8bb336e697f28af87a43e82b410c6f2bbb74fd77381708b98ffec95366cea03aad3fb4076cd0da4bbfbab8231e222a0de292e4fa05aa59a52a13dc22ddb505ba1b2a57f615721ffa31e89f1452746c95590315e0ec3405c07654abff6e8dd834dd5a68dc1a3e24acd8b6e726636c006b27add850721dd5b37baa7d10a2bedf4ff9b92ee0049eb00abd0cd680db71ad36547a757221ce8e2641fb9c7d00b0e3b4792ef32b5af98abce7d3b64b42ea3a9ee951336f80adfbc0d1314e6f11ccab0a64a0fde4a4b77b695ef1cdae1e41704ded195b6f1723e32a3232dc0e5b7801fac3f996346b3f4bb92b5064d2f9dd17e868bc35996f990d0c6ce88e81681dab2c9d20044335fbc25655d6c2df5a26f71a632109160f75420c8839abf1c0247247fafd8f388d52a7e5e46d31fcca84d788e4301f0e7f204ad58d0f7d85e9f31ef4e378813cf8acc411e1fcf751d05f80ddf6d1bea84803ed0d83514a666df477da5e7920760071df7f536b5d07f41fbbbf9eec300cf185f35d72e75bbbd48e44a276e8b60f2c0ac1da42a2ab46be2c4ed7e669bb63020d236c63b8f8ada4284ff330ad892e04dd32c8b7882935b7ef01ccd530a8d5040546e35dae6864672c5e7696b0e1fc1153faf1b8071ffd66ce9e46732a3f897ed4246969b0f507a4013ad2afda08a1dad5e0ff5006d4c55332ebefc93dc7a8f8e285a78ea7e5bee40fabb083114544e8c9ad97846e91b60784b26cde528a59fa973144d47981b3aebe79b81b3edf0dc19829635e259ef8092ab968bb52cec50d5befe9430f110b8a766b917e4a1f25efab1c0be51443b2e18ec9896623dc13b896893ee451ddf1009f4ae4b41b4db4fa694e372123c80d1816a977e9affc6c84b0c5eabe272c8b99a1de58c69bb1e16839878e1b3b7430352c23db4cdc75f8f8116c96d97a4801dce4ef96bbb092d5c3858eb8da429043c037fd5d959491de9c2146e93e30777c52d145459b443ab892b42c5e66226470429f00a4cebe9647f8491a8cf433022481cea70e6f3eefb15e2e2928c8c138dd42cbf459fe42cd5a95efb22603693120f344df993e86fae8ae5a4dbd070954b24825a1232e087e40fa7dd46074bc4b99309cb38d1004a3860aa25fc62be0ff18dc14ceab65f7f120af5949e24e139ab6be3cec93eda031b3fa6d29ea3d30833787004599abf96d233dedd853995564a0278e5252de42ee96072471d244e02636d051cc9408f53c712b9f4b5c5e6cf27ef3f8ac73ead22058b82d002548d8313d40702c4485d4a43101d969a4a43f8c79a8e4c49d1a38102096f19e8c5200ec765dc32d52297cee8c4802f9d473b679b9d0b96079755c061728d812af68409e07b2b39666fdd661a98781c493d3b90365f2df49ae03264965f64b263595809018c4a9312707ca47b146c0928acd3070a597be47e5cc7f233188b0a0c7dea80bd21d0bc43e5141e6cb0d262a60680309a346be21c6d94ddaa36770490c4da0555c44421852e3218820651b56bead1b16848747a1f8317ab7e51af4e9ade950b6dfc1b7f50cc35473d7d507dc4ca25edf73e2c18d24dcec021c29a34851baa078831dd12214fbc0ef19e65385b151b5ab96e094aef1e38d03731de42da1c979e26fdff76911934f460e1e651996c208c4150828cc084d4302107c2162441dc13bcc8f9390ac91e8bedd08a660a2fc407019e3e5af1022f8461bd1c1ade3be62ccc1b50c608cff72a1af297e227e0743f8caf7341eb26a9318bda12cd726b542dd77dacaa159e4d735ab0b5626d1ebfba44fcf7be2962efcc01d76c9a8b4094bb51685351bbd49fec3fa35943544b04fbbd1f023c363761b8665d702e736c2639ec53fb5955e35cf92c32fa2177f5f268777e49589c248d8b1ba4d3e5e53e974deac8fb2d9e4b032ec2f82e69429006a4e6a3238fc5aae1b8b465fe6d3b48565bef4fd94adf8c30f1766b5ca73ea990259d16273e4d4afcc5a3195600f019e1e86c511c3da42744e5c8f50b47e6c34d6f0d5fcb008d753d367a37715dae13d877e4654a72869cdd703d2db0e7b54d8ce5a9e8956310ca318fac38e6dfbfc6888eea04da85e16d6925458df3f334d8ed2e15391cd271aa6835bb961d13cfb2a091dfa852f46f096689749e4af6efd5dcacb1c9a7cb2ab65fdd1ae78a7638688f2547d82a2a6a0153f13cd70c6f40c4d9e61222ecd9a64e66ca5ca5c5839448c2bdb51ce6b47cd5b01110a0dec8c236636eb8dd427a6ef33ba2034944d86de3d9cfa9df1f0f1207194d92b428900eab17ab18515f2a0f454cd8715a8ed12fba32ef989719b6b405ee2b356970516083553fa3177974e1a4f8f2594219fa444c7c71f3fd8d37fd72ad1e7b495c89726faa710b9847c780a2ee641da86cd0661b050b5e4adfff38ade4bbc6cc33a4aeb09603946c40ae86cf9bc220e5fdefef966b203a37989900380377ba6aac4f9006964be00bea965e7b6f81159353a55b4f2d351a2c3d81eea1b7c6d8cd0cc6a0c229de70efac2b6236f8256e38e49d33c5b9de709135465e6b404d743bfbc66b83df1fb9800bba4c92b4239d3f5723f36a9f70c01f1652ca055b604f6fffab5acc14a44c859fef781f3200251ef20624de5c80ab2e6733dc0057a3bcb16f01f8fb68f08c48cf177f2ce04f88706b8052716fa274379e65eedcd38c3ea8e046c0925a890b421feebc5c4d6649bc1a3ee6c4474acdc332658cfb3d8e57cefa063c4157c24c1a08c1da1dd7e8eb5a849e6c5771ee1a79b9bf30fc243e9a9bf2bce658ef50b139202d32f22e4befe4412b4dab8e00dc939aef655ff5f1798880d739798fa8fd17fcad6179af03c9d1c6722520ea2796d95c52b487415b672d1ad1a003c74f623ed4bee004d8b4fbbb9aef6d5824c6eb9384a5891284e105e23758d488149893266ede292449b4180a151f90d1bc632d5a9a346d823f06b8505cc93187062902671187c76860958135eb6f39fe1f80dea703abdc46ee4100fff1af3180fa75327973482541bbedd9d7847ac36dccb4920167585fd1013472399c876ef6800195b5ea9bdd30ca11745756aeb7815252d9e22be652c116458e95c3ce44583562ced51e0f59c60995034df897a0d93f0008d1c7c26f97abe8a8acfdf05c4668fd203f53ff2571f90ce913d0b1f9e5e120b148c16900b520b262e7b19a0121b9554c6d42f7bab526ddb851858a3d37f75965cfbf66b0ba13274fce6537fd7aa4efa5d75195a400018bd38f7d8cd53fdffe88df1837fa06f1bbc1d9af368bc19a40bf0606355bf3178bcd162f367c7e09a4bcef4259473c5ae46b91f632468727ed1a91e7735d0ed772279e11137d6312d05478e44712baad359f7fb097b85bdc392ae36bbc11a3dfc3557fd9a0729f79f5f214648df7127723fffb84f34b8005d97273099c34628f03f943df69d673adaa184a49aa6ed43733efadd9c19ab4533283d957801fbb73986572a8dc13902c5139731a08e4606be9f10f357f006932d8c17ebbf45e2f1c053c94ac73d475848fd8374c35f25783baa6881ea8270f3330ddfbdd855a3de6ded11280dd838434ba66ff66be031a2d3a62b0fbc97926b2df1ba902af9e586299e5949c559b5ccb657843d01da138b6cdd802635f714060381d2ee1dfb50f2daacc637598965fa7158ead3eb15723bef95904dbd699dc99e054f5e19228d29696082792f30f1d565f1c8409359f7bb4517820cbcb6d5bee4c5596986354433bf02b597b1160065786a460a5f6e4a',
+ 'd2f61e1a3e370e78db7a356cff4e3e0a40800ab936d79b89820131c60eceb2cd979c4f1e691365b36a12a1905ae8689c59c876afaa77c5ecb648b051544a588c47c0471008d15369c781c5ccace0bbf36281cb28d62ee99f3cda8b0854d70b65eb4a4c19a4dba042f8b1e9497c9dffb86295524b4365d1491ca10a1496de92ff8a21a761c49814e80788552f5287fc9262eb5341788243935c84749da2c5b6042c2fa00ff0707600fbf050a5b606792a696b1631fdef0824066a13ca01f63f19d95b7e4cb1cb9035ddd024d3318240277ffde2445b12e8a6213d2dc43ad7f5e89a8d8b3f8d11282cb40f17e24a631732ac8901a11955aabeb6e40bb3d5d29f1976ad0d110eaa0790889772fa8a11c7f3f9f7b972bf1f1816cc47f5be5a4e075476a44de9bcfff507624f7d4278f518116d2f53dcd6d7f8566b4e320d52b3f340a15893f01e76ba39491f7fe39c75d135111a1609e5e713269be10cc945682c85ffb2274dbc781dd045efc2057efabc06eb9e4c2174b6312c65e8c91ab9d77acc989a50291e6ee7715ea78ccea7ed9f2d06a43b4b0bac1a13d04bb2273867a4bd75f957accdbee0c69d3026699eb4435167152db033319581e5f20f19498074ff9db584fd50d2d0770970d8fcebb9701b18d7687873ad6b1fabc52817758ab03b18b81f452f107f2caa50e9b01762ed3220d435ca548864942aad444a42ed2118efe870abf3e2b58c89b8a3aca919844087f2dbce5d2a48886a90adda3a128f3f292fbf5823af393fad914c192945fbea551ba4ae16fcdfa57458a9eee55fc257ce74537447ded4ee1fc12d1e1f4d1bda9336d68f0764f1904feb81aeca3d8109e79f7526e7d94e41e5328ee4149bacb8492bba9cb9cb45f403337d43711595fd7a976d968c076fdf09cab7929096762b7de298fab39aa4779916e4d5624dc7da924683edbdd0fe719273ef5119a640dc3942b8d47d37d6c1187e008b264fdd483493ff53039cc59e89147f493933d47384356150a00ca37b8852178e44819ff16a6628efe5d38a0ff595afa177a5f89767a3dc96a37fd34df17a7206444dd77a3ba747eaebdd166bdeddc7825b6575f0e87272cf0efa2dde5cdd591aaf1a4b8eb03ea46b9d23315b4dd60f136ed91327128b68236ae47ad7d06513f20b4fc4d3cf14879b840ba2874867cf7d376a99e4ead609b93da582df56f9eab7e3560af1a20f38a227fe3396da784ee73f80d1ce7a3b9cba12a50d9ff5812491bc96e5913118fea70e65aa3108f2d57a0c84818ec9a3684d6e4be3bfb5e60aa720771d5b821823d2c2a6d0ffdcbbac5d28dc72ccdff4b69bdb01cbe69e0ee422966179f6a0f5ed4cca2b6da95dbc1bb91bf26c2fc518cecce02f8fbcb9e2fa29a7bcc30b9099a53dae5a49d693dd6ab66b2d375f82f5f807063bf5eb0d4a93e5d9f0c1415bea92cdf79d2b2f8707e07ba2f49a051e757c74d38ec618a22ff97eb7653c410ad2fe222c5bdd5b4020c63147b15ec9a27fa13cd190c9ed8177721bc684fbb2a5382f67d5fb2503c61164ffe3cb4b5215fab18788a9c812b10c47e490b3c83d32036ec27be7cccf22c3020efdaa29497fd0f27c7f42892f3ad4c0029c5b698abb1d035ba5869a665b1de8861db6c055e8e8ad443ec1d6eb25b9249d72e5a740476d365bdb40567179065e8ecc57d81f592a29064d9075ee79a2bddc9bb74a07dfdb4feaa57dca978568ab4e90f5384ed97eb0eb35152ee13e76d1e89e3b1a898a4f952f8ca5bc81862a18eff4f8a98b71cc881b7dbacb6c7d1fa9e903d8df6b50b151531720f5d78434ed997dc8f37e28fcfabdade612363d848d0653f5605839e9cc7dcf573d40886e7273b5cddece06f64efa4d00cde8868dc46715fc66f64ee04bd63ab05b6498c0eea6236f322413a2ccf9e672c22960229835d154b9ed967c1986e29419ecbf12ab594f17b62758e9bce3ffa3baa2d42b4f980f6521e619a67db44f6c3d80024cefb5c22b3380dcb165df21fb7cfbe99032b758976689e047be89079e90c76b5603e2031571d6a7e41016d3e2d2dba83989bb33524f7df245252c9494da6a01182f079a3b38d2c26805af9dc082ea170f9ccb29fea56b588afac57eb4e310cc7aba4d1007501c34278cd307fd55d141f8b210c10330fa18fc857e4b687262d565eedbecdf805b0507edaa9a0113382bdd15b283c9b8d33c850d7d8517510823bf11dab62d91373ef26f5bdd3584c6dfa70bd8f7b9059dcb0cdceab32846a9be726c71f7584c5ae6cf5b0f59ff6f24643cddaaa639de01ce7838ee5e051aedaf6447c935c876690586f9ed94c89efac286d35117b20da74cb36ab10d15957efd7fea09fbe5da0fa4fe911e18f9d7ef016c34f5d28d58364da4b95a48c07e01b0a99c5ace173ff2c9216bc96df8e3ab2ad54abd60308857da336f11986e9f21d1cca6e438c66cba7fd6cf17192f8ad745ab5bd2480565b1f948d3008387be8467cf50cec05a2a10cb050430a604931b58d5b05c1272b6edb5cb2c4c9373a4d27a9ae241ef3b419cb796533b9ce1c81e6d3b918247e145b213a4c320509b19b41315a4644bd179054a720460812def898bc5456c6eb9d8a91dbce0a24165e4d13828de605e859af38c7f5fc9df50d103bb5b16430f623879daf9cafaee3acfd3f4bbd75cb0bd6b1086a6ab9b3db2363504e54a2fb2442dcb5244cb51df83a05f4cb6d881c7e2b5013fd0320124bbe6c66e4b2a57e0c77e478e8629cf9da620204a0129f62d5d4071bfe33a211bd3a85f0175fee42053f59495a52d9baf0d17bbf58412e46a94d4060fc90c23aa6245a8c64b0efdfb50585b6f8b1fde9d1e4ddb5528db7304f139683668f03059d08648c4b6a1cbf8267043251e47bff043892673d3cbca85db36403caa2d70b18530e8039c01769f3d05f5be48ec672fe39544566e2b1275ba95362a750d0a39e90e2f8ce7742578fddf1842ef58fde26537de06e243725af51cf107b3d6267964e7c6667d43000ccfc555bafdd1aa9133389c8c155b13bef6941adb4bd1eefa5f82549948b630e21980542d59a096c5b45f25eff1bb1d2824c458af2546884340a8822e2e14dd8244d9bca3eb0de805507622375dabb721e776548a297d1cdd7121f82c19a72e75b99536249b5fc9da6f433a6d40720930a770b6743fbdd34e58b55d9bf0b54c42df7f69b951e060cb7990169884593cddcc7b25754f50d4205f2c5a849b875f711d5efe69f5d6d660d6235dc010a878b0be5f4996417c48daed4d96ee01658e8bddf99c3d6f8a5221efc4b8edcca7e432e6e4cbdefd8a570569e1bae10c9601178619ec3ba744fd972a3dcf28a09da9eaaad253566bc228283db06d65a364e19d8086956d864cfe49f055497874d4ee6073f08804746be4cbd0825883ae1556a5320840dbf2e97ebfaad3cfe63092dc1daa0713f2feceb2778a1f224216f287b80667958cd464a582be800879bc209ba8b5df9b28f234bbb2d34f74abc1439d5d636a3b8815059a1996249d3900c65289fe40c9ac39e3270713d9f6c49850bf1db1dbdbd79b14860c9887352ee5cb2a49ebf24588b9241dfbb2864f978360167f59801c8250d990e42e70796151794a6fe6bcb6c45c3ec518181e282c6bbdca0bc121a78f1b9ce128bfa810d92d16da835668c566097b48e5ae69288936ad024952882309e3a4a060b2f8a49e62eb040ea3bc1978a821ceeada2ef8b8ce79e6c2747c39f5923af6157126dab9d740ee9734815b0120797eb005373fc119699ffd90c4915d0fa60bcd63fa5c31735dc0a6737e320a5bcb81be984aa6aa01c455820c29d24842300613f03ed20e2089a3c3c77d479a405fff9cf930d57d6dd42fda5273a5bf1320106c80b8e359c50dbf77984441b9b77c1fd392b1734244df568cc86ab1c96abd50627b31381949313b494f9dc96fa86b09eaca9428ff702ef220d8d59f6e2f50ef7c4a727956963d3fc2524e87c5fb75e66908a39ebed8092a1fe97e16b64a21144353404d189379f5680a1f22c298e0ded9b6a47a8664b72610622bbfaaa378e83dff665c68282d83c6dfda436f68f418c13b606760fc820f7bada54251239d93f4b6aa4f4d3da011613a4591d251fbbda9ff9f0b5288b00de831b446b4b0d73cf0e1d6ced0ba1bc8c3812ab5f63c3bf4196a280e67f08a47e0561f73d933700bb7e6b080f887e6cf731e4f56e012fde69ae4c519a41c58e6ea84b072ca95ae29d32f010ecd8d494ff9777c1d9ab989bab3d53cd413f562118f232deb8fbcdabecc22901f57b28c70def67bb12b1b2799eda556e6bb61eff1569b5f852e923bf12829275c9fdff659a90113bc9b1f1fabcfbcdbf8a44949aec7550ee9b1fe1beb71ab8c6dacfdd033e2020be3f2acb817e18293b1d796b66f6ab702bf1fe7c0d42e1088f986f2622511602cd039ac3c06ab6046ba5b2e7f5cc82a02ae813773d3e8bfe3a836a8f458bf833e2b7806f19fb6bcbbba38fbec9e6817d85eded57c1026226524c305cef473d3099bb5a2200b89ffbb5a77e4445dd2e44a783ea923b470a0d61235f33820b6b854c9fa681071ac920e421fd0d1c08ffb21a4aa9bb0e74ea1fd1d955979040f9ae95d4e400bfaa8db7c1938a9d9f026c2248928936ad0498da3ebea1edac906e18efeba1257ce0444cd87cb56a5151fa31dd0c3799b98e8cd90243c2ca39723f50c5444c63d144834c836debf7d560304d22080358966b4c78c71936a1c95ef3e2a08f74405f5fd1336cb7ccca8db594f0d2269af7c6334e45f7af8c08cecf125b2c2d874f798aa48dff1aa6570cda9eb947e85d0beb7f08cd0e672163dc3b1106a29f13e5b26d73d8b4e72721b547c402fdbdc7e96897028a95d0a547543b8681ce6498b78c60c59028adbdbf29758b6a8e177a24b013d1eb089c7637d948d968ea1e84197a1d2040b4eed883dbfc8b5c6395d252649ded8ae0fb1299dddb9008901d74a00deb0a67042b16144f351a5508b96ed6f86dbbc6a9ad08f1c47c210d4ae1d8cfba62fa7f70d2e4f4e5c7dee0f7e2be46ee3bd34aed02d7d44f9322d3951c2f1d021d57069576d38a521d3f8cbbb23f16a860aa74059cfdc5b1b90be7e92cf60c6762d678dca74068f6cdb2c4bea86fbad974d7de9debe92aad7652d6c184bc026cebcf52d0c4d9f55621ef059b25dec7d3f0b1d71070389795338e1cb8c3efb38500c820fc500ed3f2d23adbbbbafab23a5cef1a07ba5a710b791426395b0f84425a477fc9e93d694f572cc3aae8420e7f31d603ee3acd6b62cf8b8fbdcaa9f92740c3abc3e10449ee194b39e33e433104e81d212e621fdb4daf6ab5d0833d86a66cd35174f7e1ea31a10ccaff1d8cce8e081afe52d0b0452e812834fc2548b13236ecfd576e64cecc86e7e359167d3b5d1a5607f5f72d2d87ed5a89ab214a0d6d2776fbd5d4fb5c1113730f7ba9a30c049754ba8255e518ec6c683067f7bfffb9b707f99acc1436920f24cbe1578cb516f7ad92827d868bce56d7a5ad29191caa7e2662f0b45dd2e1b2739524a098caf6b3b72c60ce7ffa90652f660525f3c8a1e558c4720e16b562b5f5f7b00555c2466407f4b7d94ff9e4ffe60dea8ece985284f59ab969fd62a77202601df6521f68812668021e64ee3e8f81e9d7b408101eb5a3522138704f228f5f6ab9140c56ef838912aa1e5d5c0fac89a657464ea4792fd88733fa0def665633742bbee2ed4df3a6614a4997c7dadf73afd3b5f0e9f43c1ba67c4f24c7d57f4518f0878467064a14f05d30b2c61280c682f7739d350262c33c6478537d1252fac571de5f389b0f4b3c4d642cbb5948f8bc1d0fb23a465c85b5873628538f89cb065a53d1f69eabbfa1a544642c118080a7cf0ace5e1251d9be4ed9020fbf8c4c6804688b1563b7f8bcff5207acddd004f287c54015091b159346017ee624c2e546fa8cf9199cbc6d0ab62d75a210bffc18d1ffb5e39ce0ffe0e7200d9b41d62c4fb741aa77ac11b30764373c905c98bc8727faae6e407bcf33bcb52c83b92e97cec526fdb40450313f73ffdb1c03f2ecca14469809862419f831415a23dd44ae60ae9d815358108e1f7ff7cf99b966f35e0173e149f072769adf55151030a0d681ce25c3d9f9ab1033e2bf889def6d66cf8a0338b3f1ff6bb83150fbcd55dbb6cece4033bc7bb86df946a7949d186ecd7b1864fcbc1d234fccb1d57cbfaf5db594098d6f7f50a10dec1640821bb6f38dfd2719abd6d476b934eb42f66bcf9f597e1a6dc5971afd6688cc2acf9e6d843ec250b1d498aa722ead483a1756192928e97b51e4a82d361e6be2c6eb998ea936770f9df586219e2682532b4e032e739a6296da2b0719e37be1f4954491acdb2b67ce6e8d03bc632750868708f77217247617872758737e25a77c26991b4a828431178419b5ed69a7946cdde6b7867c5d9057b967f09c7ea208e3eb279ada37b75f657f19f1370a2dca70a055a7ce2bbc4de2114b84275d57a2c6639ea4fe835b3f52c00c6a1bcd307f3c7ee45cdb9db70b40ff606796a2dcf18e205cae09cdd67d49f77f652e3b460f2ab9f15b115ad454ff04453f5cd79871b591e03a9eafcf85c575d196c0dce506c5f22b0711ebaac766a0486ffc7420afd748d7bf2f819ce55fdba163861a740288b7f0055fb890bf3254087df800fbf86da591c03eebc51873897d5fe08c996efd6bceca4d5a3cb8ccaff3edd1f68107d338acc23f56ee9bab1fb3e062abcbc89e8e35ba8d38e550a014a9341cdd564ac2929759b35b5feb510ee3997ed0618be6afb37a71ed4103471286e52b1df24d9623cfeb4d51cf841fc78aefa7c311456040d67eaddfc63d4c3a1b759f40db6cd86ce4fb3fd283f261da28578fdc516185d20d3b9398fb8b09fbd569ebde883503ee450e3d7633b2c1c73a657994888cd1e3c635c5a147692ab85336935f7f32a3288e98b8854541aa1f4f9df7744d13dee69ff1a671304409200360283c26f2209c102f23b83f3ba532b675ac040593076b08f3c8915cc82fa3648b0d1d15d016b98836116797ed2db22d60e9bc5886e5e18fb0ae7a135228c304fbd44ad268f0213542041bd5f2be0e12f44315ac6be4daa2bccaa3fa7e6eff5346f06dd3394733d5d77e5d7fc8d0b6e5b44a9877beaeb6d07caf9778a98cb8de00f3fe9ebd8691b87ae2a50bfc004d341562cd40fb8e169784cfdd247feca013ae4165ab5228ab7b80c37af0f8b7218c8c2e2caeaf1d7a66499ddd744e192582938b4b6fb3c7e18d9da357c53b7fe43dacff2f0745d742db5fac20fea08cb9a9751418824ecee46d3614b40f50c4f5e5ec785ce16c3610551b7d400a13d1ed06b78e45598fb824a8ce6a2815c520e703bf0c2ed36ec463622183a34d24632018df5c5a7be31e12beeb461caf051e2825b93d2d43ea1c9c90e641d33e3ecb135e4100d050055b4f1e012b1a019d1749ba5f7dc0b94a895c5ffe5a4833ef7',
+ '9b5f37f5dcedd96d9b7ff6d852b77ef90498311d24dfa906b2979b28a7e85a1893309c41855581d92b59d1133a2e859610cc8a2f9982c1c26f894a8745df027285524af338db0be0272ef7b03f8f11e93ae76fdb7c173e8f3b8c08fbe3143277b9f0c975be2a7e6cd629ee15298227daca11688c9749295460c85bec4b2ef10e76309f2ddfe8e264816f40acc0aed1510771fea7b0bd89f92464cec243d6481f063a568562be3faf702b74dbccbc16363b30b895901e6665d089e6e594b43d93af3776e311539e37eb83130c1453ff71ac751fbeff12c982ab5e2dbd066fdb50ba4f85b1b25006e33a9fa4a6611c92eba269b98ac441b937ff0c2ab360b0273f6fa90d560e5c809ba4a8af117bbfd98a67341162a9553e6c12ba652d6c1e2b48156e953aed20134772c6bdb42ae3dc3742fdacac74f360092e916794f062ee54f5c5a6c51743c7d0ed2055f93630a2db7aec14d1eec528f799b9b751b523784958d7c75f536ea41c5adfff476650335c582bd03adf739d1c9b59ddca830ad21184cc80706a49b314042a430783e897a424df684e0fa5c7617e99626921bf0392c2cb5960257bfba0322aaa9f55a3d699263364744502afae88a2cd9559e913b659fcdb974aad84a92b07bb78a426f925a54d4d164b325cec039ca6b5f1300b6393888d7ea186571538e8fffa381c082feb55ab9be7ded60135af7633b23ef283b697f77bf4af7bcea1f5fc8dd92b099e3e74046be2ae26d76701c37664b8d0fd0b50a2f709cff8baae583c9a4efb065ce7d1e2ee03495355e0bd18e6cf49adb9dadc155ba98fd7c3a7364787603506502d96cc8c14586562ea09faebba97929f6b63d80d9c971fd0d3baa3bed78112625ae84baddb8265e8cb0df3edef4a8697050c7477aa8ed8c87b09daa57b86317ab5f1e6b922705aceccf38a54340b9289f1ff70ff9b1d0b95e74e74a613ed6b8085d92518afc94cfc35e048885282bd5d7865540f36ebbf1e5faff728695dc85c13c890324a3644594efeb3f111560ffbe066a90e44a1fc4b2b54ed93437f51f7a7e5b06fbd5f48cf5e7555f8382f904b7129f6648de6ca049266dd4e6afb0d3788580c38cfeb6345af6db60391b7493675d7c378d9633231dd0d50c3a6780505004a2cf347839aa4870d5c7ce29341a2329799b4f0bf3bba5570cd59be9e3f4a55e3990aeecef7d22f7dd1c9f46e8079f192fe7f9aa3ee873fb8dc787c17c5ecd04adae38c7581b8efe69d548fee0fa1faef7d419eb75181e60c0588a6889fd5b9a877e8e91f403e0e7046837abbf50495d79b63c5a26f8e9195d1f1059cd3eb5824f97fcc753d4dd64256c07f7e3a880a72e24bd70d4d97877bc71c61f96b18f4e7e712fe1e7fcb8d85557264dfe717a0e7d9629c9ff58511e5706f82476e42d718c90848c30ea27c60c900f2850398a15f0810db016e3e77fb52532f2fe55347e028c9700cf3b8ebfc3cd4f11996f25301f8be5edac0ac01e7f7313258d7328d678abd3ea035f7228035552942a90ffff630d2ebd3f4b6f7cee76f516c4cc7f1d47a4c7c28dc4568153deb62a942d6ec6538b64b941043a0dba87755104dfaba4f7ddef04bf18c07e3dbfe63f66c2f647799d046c41f3d4533c4af05eee0b332021ddb63b27bb3451197f6f5d02c02ad54da8aa30b268b2e01c3812bae10da9f13e1ab9e0582a26bc8f93ce0df8c371023834b2c132f15a36b2b548df8e2574aaa51b666eb0f41c02f8a36eccc93b7d50d1d7aa78141c3ec99868ff57260127bf0f664860c28788e6fd14de03f496844392f81dd00657d50b45b9c29c791f47a0c571ec411d82f1baf56e986dfb733a5cf41c79636a22b18e433e2f19d7de38e27fd4aeaa2244eb118a273a455e4003ff9dbb499cb00b58d5095c9179d2dc800696e52be6616bd96d23c510348d9b85bdd86b0b0688703f42109b9616ea88c18f9349c0906b5641204aced6b619c4141a3c923a1b540fd987e171a99b8f6151e00d7929229092b6fd67baea448378539742d753559328cc09048548525204d5aa5dd9a23781bfbf37130fb75a4b16b8b78390e34fd6596b37f23cfee5b2d1b1411d01e829bf2bae8fd533ea71e13da7ed675576648e204ba7231f49b022566936b37857839965294a16dde025d64bc5bb769b693e3b0bf1d91f82956c3111820dc9b37cdfa10a9408605434e0aacf86a429e948275d7ae240502d7e546f818038c839c498867a933d4a3d553ccf476f3a09b5afca760b817f6d7671132e24e84a2771cb488a339b7b2cffcd94c431e3ef8e86ec92152c73d8bfd3fa22fd7a2eb47ff1fd5a5cd4012481220a731a1d893730e3ab18ab5c2dfedfec960e7e0fc7fa2a40d7585eca88dbff3a98624168c393994247c8a92904544626c13ff044489dced4e5cd00858703ffbff3ecdab2279710296f1cbf01bb7b7af8f82224c62511c634a522f2a3803efb08a97d367829b43e1f7d9f2d74a7d6e6f9c76f6be3e1f8b8c691f4958308ef89cb259df5394e7d8b7affcaa4f05de9229fab72365c13b51f3148ac89c28588247e04b987541a4580f2622996134234b66110d5246d1ec951db15d51fe08aab4387a36a7d76f1ceb6ec3136714c095c0ad49402b6b577c7f94aa5e8f85b8ccb6f7eae2b3810795b75ef096bd718f791a860a1755db3c3138df655627392006b10c96176579f258e7661575437e8a1a8079bc5b799e6654e8864c0cc42229a0cd00e89d65c916ada10f9876a04599bf1b0fc7d43ebdbf2cb611c54a0c49b9e13159463b5a795ddb0ddfe2627ccea5af13cf934a4d3f2e03cb093ad6a7b5b91206a21abbec8fae2c55605b00811f94338f4288854d2c9a1f4ff612793e6e127b7360cbe3c415f0e69e1a6b1a55425093b7ee0f4ce78cedc9695eb5fb797daa64a11dc17c8a120d5213947b76a03fbf17b45d8e69c3680e4941cb8b24ffe96b15b760644de68fecb8d956f1de0b1ccb07ae176fa288c7e5e700c4fcbc79ba3cd5deb21c207e9375601be837173de35baacca218c0deb25aebced2708a8ef904ee3e9a51bbfd269091ffd3b3ecdf9c56493788f38b6f30559cd27b4f57e7adada6fea06be709502595ad9ecf24994da62c175166cae049be44354a01eb2bde1e46474cd26c4a1a1cb24ed1f2861200329b9383db47dc057d291ec4ee0e03943f154027ee126a8b5d310af483dcf3bce2ded3a8b9c8096d7a93b6737e8817d8f85d12b828a10eacd15a0890ecece38a9e3c004768160f889ecc25de1a200eb13164e487e6e0e0835e74712c947f8b714eff42e950f9975fcf1b928d28a09128d274df1d9198881bedc96c51e35c9379da6dc015d93849f8f6c7250912ce4744c3d32a019291ae79679f2286414da2aa2acfa3536b9dcc5dfc1908d93e72d90decc9efbb4f93f9a7b23fbb531618600d276c122b6eeec996c75960851656ee8b36a053d4326611acb8f15e40ca8677a9b78e36264af4e7a941cf589600412fc7879e80d3a2d19f905ffc33d6c55f8c86c37b37cb6777cfa051c2159366fa43c8c90d9e40079e4b5b91aa639c706b4aad347c3ca32d3f2882de7cc204af4ad496e233d4a4c893bc163541161b31715625f0d96d3505139b58d243857143f9873abc594b864f799bc9330a73d9713b5bf6e1daf30955bcd029146086638acf06bb3dc62b6e03178f7a734da360998fff29eec7f6a786036efd8c1bee62ec94f9214fc49be44c374133dc52ce380f36eac5fee79d9801ae1edd22bbe5f4d10f0775d999c371929f58fb58601ae73df8c5d2fb8311632d8587cfbe8a92a3a109d9bec28ecc9c3d187ddbcfc0b2f7899c3859cce37a90715252de48ce1ef6c44a1704f4ebdeeeb56a58d927bbbcf05decea60594fffa737db260fa8d0b175a29a684f56f820ee635d90004997615820ae84f28a0fc831e6e9ac6cc6d871a9a3c174a8d0fdbb24adb9ce551d9cc8b93aabad14476afeb6e5448bfc8a2d89193086e4164a41d718fc45b9e28b141a9a13ab0ed078aac9bc9eb46cc7dd191f4eafb260a2ac0d9a53b9cafaae7c457e8413764f2d051550cd7801f7d6a5e25cce8a0d8f53dea92f5c4a1038c1d6781dfea2d31734d6f4bc70dbf2d330ccd16723275f1a31c95dbcbb19df1c2483f61e90288b0eebd38e342e2f51a9dd382e69d4f070a84453716af98cff4ede6904aac20d66dd5ce52de18ddde420e6d341896a4b08e295652c609d0d3775f772ede91db92c2c8ff217eb174b74e1528351f06ca2ee702be8d7c72f0351397885f7022894a5a28ae3957954e2c8932932a8c5625cebf90ec2bac637d6134468896c1e6b0799e857a1efb3cb0aaadf74c78c31d5e1c72547dd1d863eed463bcf6892646f78cfa6fe136dc2042ce06d3a2a465c4c994a9edd1f482ecbb2b2c9b509b2fdbb501083852057ce87ae33e483431e6d4fec3b09d87282e7678c1e9423541310d8f82427f6b2f4feddfa6bed57fa5b8c6642641141bd15d999e353442031ffc64cd6d33b58b08d7b8d76502fbf3747e31a038b5c1fe8472be9201a82b588bc47a154e567b4016a6d1f8ca953c2e22897f29779927ada6106dfa939f6e94193ba5ed92152118fd3fb1ba3400069e347d37766f65c5a7daa9104e77847c444cc470ccc50a57741104d0a22dbdfbb22ecbd2fd9ca62c8b86cf5df42a11d4e79af1832973a07efff688c74734397c0875f7da456bc4bcb73ed59f9237a2290c9845258a1a7217fb125e0dffd40d180fbe73c5e4695bf6c9677e6d8f0cdfc911a922007525f9b323f8d70d5289a350464cd22e4121d68b20a50c306136053595622a8c512291c0d92e965dd5c186a53ac5a56bd201ceba5b5c01a0bf2fbd0f1637c121d49cf4c1a9080e68001831975b9d30174da5af34d8011106df7681a602be887945f17d460229c1c447fa3e97375834a8ea79e26b35389cfb6886edaae94ae2fb4bcca5ce731832fb43f408354c6b15a95eeb22cde17727f6d0fd4b8e488153104c9b08bb8a37e4655a7228e2096a45811195caed6b212471bf3635b09ee66b50cec900ada62d589b12010b3dfcca56d888f6554a40eb250479ce36c25adeae5558e33805554d0214f13d49a9a50fcc184b895c54f1299c279721c9241afe6e7661862963263b736b7e634ea590af17b8cfcb3aadfa511c43addd57663dba5e3c7f0e3f47876d1ef7203f94c22e2ccc429c389aa5db1607e1045d8c096196e0201807e412f74677507d0eb67ffc0d4c3e175dd6ed01dcf198612eb17df51886b9b2ffd265f47c1f0feb7d1e4f78c52a13f7a789d40d1a6bd21acd723486b3c481d64264a11d62787e01e746a122e8e85c83a22e0b5b42d916b7b638dd850d2be1089c3564d09e162336f9da2598ed098061ea2df38b0acbeebe859fd97e692f7fb059af119c836aa82111233d3946001808cc241d0ac6a6b29597f1a8e16c31b664074c47ffb7087526c9cc7892985e9beed48af8691b0c1ae379f8dc4c9af51d9a21876868ad5202de802038133897849aafdd06145c6e801eb7ffd41e59cc2dd9350b0365dae9e9aed0e91c59bb2d5a829a94d69b1f407aadbe8130e53d396f97be21a985d422822e386195d4a492963d414cda6bd82473271a17732fc9cf4b6c2975bb370dbe74b3233424f27959b031205f92152b7cf201474d0b5c73e049bd0371c907fbf03a042ddb5a519e0540f4a4679e156dcc8fc2b27c7a09b03f0300d8a04357337a3a67c4b1a670a707c0fe69df4eeb339594f208303fa6231ddfde257bcac328befe74647189be18f3a8b4dd312514f16ab9f5a502dcb0311f58bb568ebfda60310ea0997574b8683b60ce7b07c1114bbe5774156ec1c66eb6061ef833a2eb5e72e372e04807ee09419191cfbda36e86f305c3d5ce9f473074607f9715149497e70571b563b3dd90c8b3b547ed3c9b57cb4d8b62ccb5b12acce0639fad7554911ffd13a552f8f583133f9f7ff10d062289872148c3b592b2420e519e5755b9de8032df2c9057c464d3adb6d473956d7bc05b3bf45e1f7a6b5652c00fcd2622d4ba3f4aa79640c89a6c7691e1ef560fc7f2221201f643c6ba8c56456059772e18207adcc2ef5480a84032c734becf8b9bb18469de16d316245671482c96b93a1d458e0bfb06037b13116abd298c725f6b60eaa9f55a3dc74d374c4ee10f7ce558bbe15ebc74ce167f4276ea4cb2ef09bba2dd38f41af47879c13fc01a2e22ae5ed60d5b83b614f12145efe52adc85f900d9c4bd36e387a84e66d452346d5b0394367a78ed348889bdae4e242063e7dbdf7849ad5a4e77b54faaa26bcc6786739d4fa14d558a994eb8ee1a2de9e374f0ac20d46fbaa6454dd20f12834e87257ceea42a3f5932b7ce9787cc78d3c5cdf60b45ed9af4a560d099f6ad1f4756c88decb67dc564977477cdfded8b6aa5534a517a0db584a65acbfc13eac62340d0352c09047604535fd8e0d2f5dc3aec956c331fad25d733a3be7cc953ee7effecf1311e56d7c4e0ca7064896df1b11614ea04b9548288d7dc168099611ec6ce6f408068fd5102ba44ccbd93be5269ac42326ac99c42060d6472cc06aacd7746e7b18e7b60786a5a6f4c70847f74c139add3b9e2dcfadb3ebd41a39389711cf3e6b2dfb818c4484baa7e11ce29df5428d85c96779f0375067701abb295b0345fdcc2e8b19ebb490876e015f336089f14321b750a6af26fdf023148f657f149e53a602dfa6ac3c90b6500f1763c770e664bceda1dc94e3832ef6f0fe138baba1ea02933f4f58464eee56f48d995b12ea995b53a24228d4aacbf0964e5c07321867e7c8f33c763990d8879609fea2d8c48a08d19b01f262396c1aefc7677c10c9755e8942968e7d1f1cebded2ba26283edeca4fd3407af5fabb7ae1b35d72ad7cba6ebe7685287ac3618ab432f46f6b1e3daab5932849f6b3601b5558656f71fbde1f4fd530cd98434f6d016fd5030a2d51aeeb23e1e6cb2d03023400a8fdc40d8a7925a8c0043f698f9babd2846c6b33bfe0d9cb92d9de304b3964f14da30e79668526365c56d7fbc91c9ca32932f8f8324868d364ab9684e0c7cf737deab708194a3bc92d4ac8c2a4f9ba2aeedb184350ed7e827ee35af06bb45bd0605827824cd04da75b687a86c939efaff9f132ddc1d704210809943d9408f24e1d77c6afa62042190d38550fe0e4227972fcb08f2e0ee3f82ca6ab3302cc7b37ddcffd56d04104676b43c22490033bd18282f91f3f9b014f1041079a5e08ded1c7e63241713b79d99e10278f819c21ff510d75559b85486edc62103a4fc203650446ce3632178bb7ce27ed165cbabe4b06248cfbebd49f9cb9912edb7e04d23abb773afebbdc214822117d82c962f9fcc950a6d7d690ed23cf57c94492d5339a15ffdd61b39222d5c3553d9a6f9eba5cc4172bb305c21c49453b493e343e0ecb3a681e26c24278a6d97b9728f775e9b11c0483551f72135743c616910c454b16513a671791f30a038b0cf2f208f06f44fc9c1685cda6ba94f37e9805c1f5d2c382fb1ffac8adc034018fb6c24b15325d8a694d0db768f94a7bed3761fc538b1af735ad980f788280648c4a5e68ee1b44eef28eb484bfb8bf039b5c6f64695e63d5',
+ '5edb47d07e856a3dee51f60a723ff8dea7cd06c7f21cd37fb64e00eeca3234ef2a236e57ec2d9a3476726352efcc4904f3b4f3208b63c64c5c36b6781e575cac509a49042aa59bf9d454cee1d55cd4b9ce2e6723681e9eab8bc4be4ed1a2533d3a0880de213594146ebfddc00e35c188a0020ad771948224abbec078c95b412a4e35bbedb725ad0eae7b3bf6809f39f6d1f986448f0f9b4024ed63fcf0ca4efcf6e8a13ad5bc106dfb4f8e8b15e4648dc9db8072a8d510865e1950e42c37a03a99ebdacc6443e2bcbc047f88b34b69d4f170a36aa52f0a7fc20a2f3867e9626c9e040fac4a024e805b62b5f4441b7053af7f94336f65b1b1b687a7fe8829ba1b6ffce8e0714179433f31d9d7af9da3936cd2fed5ecbf2ca6a35a6040773fd0ce739a0c72bf488e8cd039923adc19281b912f87590685a4e6f24903051c73cd0d12a824691e5eb3e476428924b3d62773845fd7c6a4fe40f7091d38565f0cd960b4ecd7ce75cd10d29913659d1c1ec924af2a9724ae732963529da63a28541b50c130cea8abdbcfdea175cfadff3735b579576f7b0b2c86b23393f6b95f91bc64a13ab0ffaad11590f6306f5d446a94ae49b006d4a571806a16c5cfdbec0ce325bdd226dc59f70d71000cecb4d3ff0e9889fb0536638a3f1562fddaa9b70db9197bc2d846a094dc0828d1efba943ecafaa00113aa2dbeea3a7f01bf2aa8dc66ca44d16d4567f1adddd4461f78706ff15cf68ad937eb57aa62d5992566a8c011c081c68ee19657a6796d3425f54dd9aa46f35effe5859ba614cc8fb4669d03e381986ae223160cef635c63a83a15c51e41ff442cbce4d307d8ccaa153171eb0397f3851212cd58b123089b514dae7b75d4820508c5ee46f4363db1f0cf0ac1998af8dfc5b6a4851442d8a4c8380243d688e4693c078c3e96b7876dec4952cccfe0113fa41883da3f473645b403f76569e48d38708aa70114e212a6b2a62f56cb23a56e563f539e5bba8948bdb477285870e53a4b5725d897404623eaee8ba5b5da1b358433fa1a8f2438738c5569d6c8b455377675f00b47578cae3b2a4d02b68edd5ad6fd6296040cad8fc9edb4b5e33943f699eceee24bb24a0d4d615db5f6c652a5f3a47159e1fa4f631c85420ed18618405bc509a5ccd6e909c99ba3069c0ae2e0843011ad4f7686b92b24fa28ba233ddd6407279bf14dd26a57e0063dd0e2f5d130aa29d87609ba57a1d2c44dc59918955dba320de39e6cf89e3971a1bcd7f342a019a1237d3a5306249788c31a6f1330eee71143c9511e7b47adc97b857045f97c8561d68d92b98e5c7c2ed3e22d9575be95ac85ccee52baf945f713563651be6bf75039cd9855b7f3889fc5455c052d76cceb1b14fe6f7e5d08e3b155b0805b1575589466d48d498ec4c1e16a83cd20bd94b64cc809dd8f1bfe759daa663a96230a602e7fceca0bd8367d6f7a2a54163cf6f562119503b5da2f961e7ee0e8393dbb5150410f75c676e8bcb69cd902d79bf990a3162c4bb842a42c7ef9a7f00a0a921142d41ef44213e264fff9193f2a81a66f5800551c5ffc6420034242bcd23396894c5f83b147552a5e92b87173d996037bc8f699de73b0775bf68239b2585fcfa1b60ac7129de4ca93b7036a06aa831b9a3d217efabd05e6c49fe0153c66374642c7ff71810b69caeab6ff8a6166f0f3b5fda88ed602a4b84245855cc1c2630252c86309655b8c304ad6d65cead58495b551b451db0d35f5bf319899a9358a0bba0161172653f4069d60b0650abee80880816d4e71a55fa522ac42515b87546a63ba1e242cbc4a54ca9cb42f29eac45400d5fa0d0191cad153fcab0e41806b26343bc5b7de5d3520b9d20b41b022bf821b958416f19a1f813969fa57c2e8744714cb7c59a6005e74524adc23052c8198bb0832f2ab8806bbbe3d58b546861ad6edb46b91eb6b6c577d4b505e92d0b3a1c7772d2895232689c9beecaf352302daa63cbce7166708d2221f8f79bed8fda2272a9c40193228f24336dfcdf887d75b27ac94e38dbcdaeb290cc0fcf00a06a5c369a8c29e7fb3ff12eb5897f57a3f62584c1e0eb93289ac7a5b0c6f923077b1837c79378cd070b61f26809695c4e2f521637fef0291be1dfd549a5b0b10f7cf4faa36274aee0721522e7e51402c6a1f6a3be20fd1a020459b3e9348c3732f060f3d081842a11f48934d7b505f7c7ce51b1a6df48c28582c3a631ebc2220c65eab7b1694dbb06031cbc99f1c587ac3511c4948289df10ddac309644190e165df0bad89227de574e6e0eb113c95efe46a55109c336ac3e8581f798c5e757cb492171a884b9006006fbcfbfa387ca28a38aeb6919b5d6691ad34fbbf9c39ba5a7eb80c3c957cdcafd245418199775d5bc410854eeadca1afe1ecd62581446300abd4c9cc8aaa2f26196504cfe6ede6ff1561c3fca7513eeafd2e54e597fe3f4d22549a61b280c7104c038e0ba746061f338bb9c25b2303ec07e1bb2866d015eaea21c72394676b137107255e65e985632774afc98dbab95a0aef54157520af7e0b219839b8c8e5d7925812f0a6402b72f806ec38c57e28df0b3f67d54c57d3b28e3e55cca609fb05b6e09de8f5b2c0f5ae27ba388cd7172114b93c8f73de4a2e7a5f4503e7947645e6860d6fd7a70b9352c15ff1682d4fc82c451a6c731bdc99d76cb10702cf1d3e2932acacd687d5f5ebbb248ae4e8997d5ccb4b94d278509cae4ce1ff7f24ba9987ccc0c879bbe6c281fd512c8570bbe8ab33152e1b5c70de06e91d14e7fa13fd083d92ea48a906a31d6f2efed52d7db02165c162d32f0208e72aad6ec0b8f213b56b6a3bccbafd40c5f903295b88a97dc64a965a8458cfc159f7d83495c81d83953f90e38c569240848317d49fe705f9a3c8de3bb5419fb26538e88feb915cf4b0a14b0911e68f4c5ff5b3282ab35bcabfc8df30ef513a8212d523a64c7b790f0f7ae4d5e1d0ba0b60371f4d835571982233e35eb9f067da69b7d2a23fee3a7bb9b0a370867edb268e31a36a32330f0cd5ef30f09b86f06270c43caa4d5269e0b62dd0028856bc44f1cc87a7c0b7ffcddfe60e0c1d7b9bc131e5abe7fc0fcce80fd7b3b26dc2240e92ee122db448088448fec7dbc83831a0b4036580d78d2c39cb9894a9bf29da2b78808d24964df1d91921a284232379a6ff04b39e6335507bf257ba0f05f3a744ab6b40b4ef3083735936e62fb7a53d7c1cd2dd692a6d02578dfc6e0bc887530134957303455886fb1025dc060a2743c913532b08b39e0874a36ef8c58f4fa6091ae86e3c232dc21846d6cc2ea60f5436ea71e5a486312b7c3c97a066e2b26805654b78850e17fe730430fdd213c6386bab1252642c8c3314605eae164918609c1ea5db06907e04b4511db918d7b5b70f5e20bb712c19e8e618ab69fc4de957774ce0dde930ca9a82368546287c4399ca098268f20e3f129a6b661a41758e153ef7e3c75377638985ba4eed2b8ad7a546b620a1105b6578d86278090c4a6d62982796e16eebb29866e561f64987dba4286ce2aef39af5e34704c77e8653ef062de5e17262161d91cdbfa6a9a9fdb65f1b34b0d6c253561b8f593cc1d7187cc8a638acc457800d3a6151054e7473d09bc5157263a60ef0e85969bf1926217d71ab29df1d74afeb5dcba2672cd1729123ce17109bc6542b124d3d39d09bf758c9e3bf62c6e12d1dc0b3bab28ce98161bedf4272c74bac6f6012dc902c60217798f7021d07c7820d2cbfa2d0b6e428a833a09f812e5c9f249b514eae769a2740a68efaf9274e3421689a61a7f2e9efce4bb835231a22b4b28bb19c79f574921dbb51b0dc709dcdc33eeaff2188205f3bced00c41d1c47381f8dbcddbf314e8759ee82bb028ddd55a74588525bfb2ff57848f2dc49c6b64f9aa76e9bbb6a2b4b018fa735b9835d24ed38bfa9ae1c7578b349e9f92c33f1083682bf90087477b35f12f91c708ce4e06232c666a3486f03da8a46a958e67fe85fca20394ae2ac76ef1318cdabfcfd0ec2ce2d27c011851d7a64a97b3450cb6692c82125e73751fc2dfa56c7e0761e167b55645b88fa502bff7382dd62f55c195c1732b461e4d0c9985363a9a26ac08df882fc70628548c5f631534e5956ba673831f5834f6dc155ca98afd1565d7cdbc6b16604e124d761db189b5c8bf75ed69392ba010444ac2711449bdd90c0bb86727b3197896b94863ab3eb667f59c1fe1ad420cc250bf9b418e1c82cd636f524684adf96cfcc08b9817d942aa7f08c342a50aa23362622934dfab55d9b22c22c249ad08138c89c623be055e79a4e061f4ea2b6c49c25bf3b3ce9d3de54069b044405a4f538e3d816e686814bcae4d815f444e72edb8d38124249a43d9564f85322f5dd515490345e0dcaa6b5be376aceb7a1bbb088bf25cf0a94c6e9d3c0628f5df5da342060498e1e4746c3e8c84690056394f8084df0b802558c61a3ab521f61803bfc00d6705d9466e1d4df09e41069e51363febc3163b606953369884f99ca1f73379a6551130cb3f887159edbb651b0a782b08c5068821251eda9d9c374e44e0075319f6464f3b8fb5900d985f85950d1e2e220e62d132342eef92256376c7664138ad712b9c5e08aa9355c8b5ced401302f82c5b27d593f8b5bbf46067bf332eb0fabef4880fc50716379a546a99f8fb41571137ff45fee3e086c28a7c590ec0cc05b972664dc0f12e6c156997cbf5fb4ca35204f9d0ce84edce1f3cebc663cad3205924caec7ee9306750436b0fd1837351613ade227d6c3c165ce03c83cc8d54ff10742c88594feaadcd6a6ae1f62463e5e141e2a7a5fa69e42e1cf51dc3b8cfe671f9118deab5aba3ff570ddede7e8cd534d2cdecb2cfe5abe55b9bf0c3bda27ae02c0089ad8b01de6881ca4314c25ca3fe35e64e3e4d3df22524a7a9570c8b20fd757fe80c507bd19b8336db13c051d68013c258a302d14b6ff08ea3f845d88d2c6df64591a678120ad3c9f365d91af17cd4f8987b815e1c1857d77d11ca28f3ff9dfa1ed7ed951428ebfa18857d066936f1ef9a423922e8d0c318a15e39db09d95df1d049a6315c98344dd6fd3cb2b261032dba71aade360bbab89d17e58d436c56740661610b5fbb654bab37988e716d8119859446de983ba73dece98dbe150af0072da99e51a214c76a01bf01180acee4599824ada263e3211efd4d72cc1347b311b2acbec2a827033f771e16ee0307b5645ead3fc83d84b0d26abe3555a2a5283a31c562cce684812287bf3a5edeaa6ca035888dce89b2cf0ea1a655e96795c7d7279dfadef19fc9b9902cfc09121982b3d9610813ecdfe42a2ea054d5b6bc0a7f008117873deb556b3cd1cc6b21f78005a6a15f36bb7e889b8e36a2c9346a7898693919444ff9bccdea683c03a2a6fd2c29d3c3b59a1134f93cd3d02255338ac9ae00a0948c4d5848353acb5108ba528dcf72f60eed2a61b9b7025d8f9da9c9796b922e069758c06089d3ead1c8a418a123f966a1494ca763894cbb5f723fad2e0e481208d59e7dbd74ce2b1466e6c80cb3f1862b22da7e7d200d6a107757be6e229d0d19eb90f8a56283fc776814d9370bd5e4fb0e664f6d17defa5111a475a0252b7fb83dd2b9beff8fefa94090d4a67821039f5072a2b9dd3bec7fd5885b6aa25dda7240d64970986c64f884fd32e1956d5286bd8200f9ecaea11b3785d83416cebdd6991e2050b9840d03a9bc8c6ee005525f9a16f293d291c8b1bc1f8472fed88a33ab0cc6d5ff713849335139a4f4a393787a93e01e0bafb3c1e7d7e8ddb715d037fb44a576c6be8737964de32a2fa2967fe39b620f143302ca2f217daafbf120420e3864c9abc67a6d5714e1750d9ccbc4499ff0dd68d6263906ae9b812a14eae0586a5d351b1372490a48dd0c2afcb3e42114050f9603aead12b1fae6cddb90a17e4d275f91301f59045c3e55f44cdf5e0e9a172c08460c84b7ddf45263cfc1f3c4733b9faaa6c22bf9c17de8e5f7bea07684bfa95c07b05e8aa5103a686bff65df5f027bd3e21b204f1b244b9ca7d249dd76670dfdd33aa8ff99f735583a99563373247a897c7ad468bbc9f474b7aab8b7e195d05a432bd28b1ee1a9bfa6306345521a2247c071c6fb35c75a0ab1b88baa58d4871da1d499a1b6982e1b59551ba97e4bf9ab7890666b1ba502cb107c21194eab98a4a9f53cf7f35328d7ae67cdb45a426700ef47313cdffb75227e66177925738b8bb5ee5ae257db20912fe9181a68860104af16b5e4f53337dd3626ed6e9fc4e63f402061f54678bbb4586979303b9f4e03327f72f7edd4b127f21ef15df0282f3be34bf0a551b440dda2697f9a45e6a4a903f1e6ea3c6877af1cbef1cbd915dd0e19c175771c265669b85989bd1a04bb42be0e90306356f1df20973d3cbd0666325e116efed84762d4e4ca261d2a71e88cab35ed6ef538c634983852250e8251252abc4b8148800ccbc22bd33ee9a6b2d365f88d0a64865b5c0e02cf1960122462fa42a9b2df78baaadb0f2f96fa1bb0b378d6cd7723b82791dc87128e341e66885724f807ff89d2fe99e489b9be6948a94e514d49d546c1929e55aaf80d3f8de1cbd3b5053b4c0c33793c801bb8e8112170e87b06dbdaafbacdcf26eb5dde012bc2ef7f1f1537bce6037791316c4070b86b1cd512b9bc4896e583e4dc988276a2d6c8a4da1fc56b82d3363baf6aeb0ddaa75efa11e19432936ff483506fb54503620fc4babd7dae9632f8edca17403f04b03f487905cae45cd79170cb25fef2ba1f256d2560d8ecacdd20362603f88ad3102e4eac78f8d44fc6fb3bc16db806334899fccecc6fbc132c5c9357b37c87a231bc7b40cd8698e8989252b7a989060b88edf20b329a73759b0e83b3a834ac0eaf6a6cca75fc88b3c6e744325bda689f4a81eb6f45f1c950b39695ee54c96e84e3a881ae04d35a3dfc7a8ea61734b48b2806576f68fe241100b5385c3b9355817907686ffca353e383bca0a74e20604b8bebb4803e27fa530ccae981df3ae7a0d9f77f84bfd52c41173e08aa7315f26cf09291461807d273325827e3b2189b5f81093580d0f139d3016f5524289de14bf09b6c259906431e0bdd821656c4f709f8ad28e8e2800315dcd0e4a8d110c3750676a3a333bba2a003db768e3fc8715cbb63d82136ea2e86240355ad0f270f74acb4d6437a8097a5b4c283bfcf8b90b443bdb1de70c802b0616716a525393bd74dc6828ad00d888b9933282fa886ebad2c3b5455128236010f8cb625c5dfbef9743c26fdc6ab0bee2b9dd2523d62c8a3df3d1e398eb11f1498d492a363732f77ba8631f0d0f18839d67310dc0077ee34c7539e0057cd444797ef6aa280bc03be8b154228816983fb44537201846e92dc9f1f522e003f4c36872d7cb7afe2b25a6099c5e95a8b85bedfaf64775b9f7d07a0e0b9371aaf7d4dd12f673733afd1b7c5643bd7ced013d112f766b1d882f342775851aff9767e1a1065a8b5ce7bd8734415226840cfa4ee88bc07259dc19c19ace6a9ed4f2cb517df50bfc7d2cf4d09471d080f62e2cf639b7db4671dd3ee09ac0c30936e4ac8c368712ac1e32295e658fc5b51a7a1d9ece5acfcdadb225f7eef397426e9591fa976576858721e4996e423aac381957ac6d1da5f1558457705f10ee31c32c020367373f6e0020aee97933354d9832628adfa86498b07a5ab1b5bfd1be7fd1870f6da4754ed0570db64c09c18082eec49eeba214ac07bdb824b8a746d1e8aea9867255210f',
+ 'bba0967875c4743a54f5b6375352cab3f662f2792e6047cd7dd6fda15a6ee80cd7043ff781ffa11a88e25527201ed644262b8fbf07d6e3fddedd70b4dcb9955aedbb31de985aae9527cc3f7709d3658b74dab8a04f40e43e4ef4f2dc5f42c95345ecf493827da5957bdafa91d71a80702897f684cd45537717430a81aab08cde26c00e80070f8d01ca3510db529a2edb898ccfd34a8e37907ff3400b86ace6e3da5f090befb96fc05d0409bf41fc77b4e0decdf58ec39870cf2c1ce3bbdee04ba7f06d9e012252bc7c706ba36de763e375b87853618b7e014e15276f11ed81fcd69bc0a006f23edc6fa1c0f19f04fb51904057538b8ef22a46d7e8185082d2dfae8a8c79c7d33c087dbe8f109dfb46e4799ef25ed375fbd3fd99e7463f44d9dc79e25890096b5228efef61682f734c8577fbd1dd02e8a2e4bc84ad62a7dca0dc7dcead4f97628b250d5ebd611f14161dd47f7d36e08eee46cbc0c1d250f12fc50474121d3861ece51f302b633487ab92d6517dd33510e7df7274ab00022c8c8154fc0f62b3107f516d9033d6357414cec69a591ac9159598f9c9f4528535c1f6b58f2c87d1164b513fa45e22eb8257b7ec819a756446015aa7e62332a0b3d60e7155f2f25a1c58cedc9433af1e5a7e378f2fc74bcd4b320bc6f3c0071d4ed1afcf75e80c16c9afae8d893be695a49035f8cb6803cdcb30949c1a5439c2afbcc31617973dccc657db9aabfc2d1a079698707e05072c6f04de72816630587e9e318f6585ee46ba583b4b2100ed732b974a3d7027beb2deb5d08f507e53a66280e182843c854a4ebbb8e714cf8f69b99b32a7c8559026c04b513db0240c760469bb369f446ca12a8739b8abef79c459ab38f8af18e552bfcf4bc2c9e1c38e0c61a7f5dc230913f5c4a5040fea154cb2cd44764cf725fc8148a567c23cdeb721718d056363c667577ae6146748cac96d0b3e6bc87ab8edaebe4774c3bec6b9eb9f55af5d8b0a67fab2e330dc8fff02316d0e1d4a2907edef391931f6ede35c14f5e73bf2243cd9837484a096491ec0a7fa9dd5fb8d78c4ecc202e581549d68417b2bf149b5c6869dc6b1abbcfd8ecb77bad1da022d74394c60edda8c785da41c380a198bc60f36eeb2529a7634b7eb48b5937688916415b71cbf5640e389d94d346afadfe07fb01e3f4fb5ee7501e8c2f4ccefb542ae20d7fd61a2c41c8bcf7c7735dd6e8a7ebed67590444948d4898e7e628eb0c7bc22510bbb0641abc94e500a510a604c7426be5dfe8fb2359897545b3f9ba2a8f4e3d04eb5c9df19ad1e71f4a8c9dcaec9b17dfe7fde4fc5b5dbbb94495eb26ec02afacf3835c5ec9d06883d20620a39e527bc61fc78487f931a6c306a1e09a087c177952a901caf03d03deea31c13743150228c5ea6c6ec9a1f0f378925cec6b06ef0a875230be74642370d18411fe713f458898bb0f19233b14bb28db92a69a5fa0d11ff36bb1ece251fa56617551bf4da05606ddef0fbc497c8a860234510cd2d75d7b21154db03409cbb77e7de973dcbd217eb77ecafb79a2f21e9ab464390ced10274fbfda74d5d575932f8e2e3548f66b8ecc50c34728228251098568a56a7c89b2f3ede091442f7867f948888a3ee6b4a5a0e79145f175abdbd349c6e877e03a8ca202089c0b8254b4601f80d90b086d61c9b5ad7e4206ef0d8c541768b1c29342deaebafb98789af6f885bfa859c61631ab4d8036b670bc749946c2bcb49e34440e366046777cabae371d9d8e97ba4f93ca11b225bb2da48f8e94613adccd9e2eb55ba0f335091748804992e2415f7a06aa94abb1cca837dce0cf7b89a6fb21516860e5883d985e64789c4849e3200618c158d571677ad6eb144a6a2fbf817e6a9bf8b68c0db4f1709478163be9a6a438a2d0e7db18000228ada7b573630af5b8c4859c6531f960fb487f951ee14db4f4c39f2b555af26a142169f61b5df237fa7699ffaa26a03d7319153d8966afb8cba8164886acac4f3bf403a48decd1a57e26a868d17873e7669adb8801c627402bd4d8a7c589a8521eba073921bc13ded26923506193ceef4432350d0e9c5ebb93be48c1b87e70e31ad7e73c9d3853448592bd4ed0f53bb5aa63a4250655a0bba1d8f93ed5f790a2eec2162746ddfa670657b8dcb63924faf7c3bb788f8eb790c4c96c77949171f1d92d2671c53983b6e30cb86277cc24ffbac6ed6010b3ee0b7af414f47ab8bf50886d9aa48ec789c49a462e789c2ab66461e1bc842041de6c42dd753dfee9b35f6b07e5480a0467109a88ad9799d143a99ba8ab4d34d4e333ab0a2fdca7b1087f0f8098d4dd7cc61b72389848075c673fb6803c33d4c9970211fe8738fb9b192fd46c17c35f9d01559ffa80f25b28aba7510cd1d076bc8458161f2ddb60f48ff2582ef4ac26e1b35fa232fa2f1bc26b70e9a31e9b911a15963600864c7e79b757094db1e7c9f75689f7766676bfc6211ceee7750dca5ee55ce0372304ba8749de764cd21eaf2a55652e394831bd80870bdf4e779f79175e0cb327768cab9991f91db0d7b94d075a81a4f032189b9de7ee495c88c923cffa361d56034ca84d2a277dfe25302a2ab0600a3f9673e08aee04ab764b3350e534698d575bdd570e9ce9f5996d1bdce10170ac7bf7dc12b3e41f7430114696f3b707818fe2b72e5a44d13326f1f4cbe6c8442a39d8c9a8c5647f422e8d7b5c77dc90a8743a62a4bcdc4db50b66237d887f4b020dabc5291c09a483a6125a27ee2fa550a8c55830b2adefd9db2c5078628105b24c03470a443e3fb75b326b7fc32ab618a2060078b84418300ad6d432f19804b98951d7c2ca6ca16ba28017caa358337ed48f03e34a2957460ed85733a20eb8865a29efe91b2f6a0f006df79eeb22bde1d4c2daf6e4e83d9a7bb331f1106fa1b712fe07f3a2f10a10196e73731734b5e00743ee2a24eb2b9bc5f9fd01b92540ba6840b8791e5bf22d420423ddc3e8ac080074b5f366bcc1c8721f30dc08ea160ebe8469cfd9bac2951ec171355dc900b844f7eaf946d760bf049545f68c08b2227a5b948e61b7fc160418f4206150647f392fd59221c5a8cdf1eec4d7bf2b85a44018d12b42bdfefe969d25155b0947db719f0e54a4020aa3ce9e35f61ead0102945ea82d09474bdd4aa07c8ac77e1b4b72c80db73a0706aeff2611d83717c4abeb8f721a01de732094d5630723096f4db13d4c40405f0d6e0818d10474e6412eba4dd768d90e0567199e80f0fa45a450b15162867374bf5f8de8fbf164b2f6f984fc30a00b40632f2d8e5f0eb9bd6b02f7b6b8d03fe27cf1d5190b2592e856aad02d2635f5002cd755075586eddb23c2f8efd7d40222d6d3821b87276c010091205320b132d7b30e34bbed1003195f2f393f47f866a04d632972e86d7c97556b0a00a8a85131a61220febe2096027d864d5781c3d9f5412f1fb1b76e2115f596d1b82661cc9876a1ce42214f13311f9689bafdacd89c72a5f95a6cb015f741932bdc4293f196952b7148bed206ffb5ae82cc4449ff0032563acf80c9b7c5c9ee8d0f55a58c96922dde650f7fedf8c05cdd1dddf199cb00be48938c11731c0f0759a4082cea22ee175a196baf44a6d01fdd22335a45577e5cc758f73df444818c364cb28096c6197678e88bd687746566277bdcda9e200ba02b625a95a7d9b1db875bed471efa94d9bf54b88c32fbe0de308d32f8e0cf2926e9421ebf0a662073e17420f6ef2af0af81e0aa36e3a7d2c67cc8fe4bd9bf575f859abc1098544de3c907f5f683f1ad66850eb97cf602cbed80c17739c57b36c884bedb40de4eaab99299c4fc79c93b9d3d416ea506973c81d1093649507d17e06b40c4b6489fb763f2ac164f3d2c2bc1ff3b427581cf9541e202c400e75fab45ada330f773c204515db182854a94ee635f2edd34e426769c384098d7167d4146c068886acc701220383c62252e8e040fd1ce8789ca36410f48354d625a607a9247f333a6cf14514f16cf6da56591fd05fb8ce9da9079950996632a092fa3c786b8f5db320819524c7dcced9c6c2b4a0440dc6cbdd36ad319a76cd75202a1b8b277c2e772e4098586d1c76a60cec46b89264f989a0f749bbdff84ddc37004be9428fcd1000f6f7bacc7417c98e9f7e1e33058f5f5a1415f75037da5e3f42759aa2106306fc6a5952ca2bd9cbb6a204dc0d38afd57353b8ecd67a9a82a0b940a7314717df8c666726508be333ebbf7ffa0a458174537ddba25708b8d0c22d5517d57b122517b0c94147da5e8994bc977e11732ec3635a2522bc2a5ad00e665bf278f67b5f051126a8956171561b62f572090cde4b09b13f73ee28a90bea2bfb4001fe7b16bd51266524684520e77941dddc56b892ae4bd09dd44acc08bf45dd0a58dc3ad1a938727eda37017260c922c8719ae522bbf181a955d8eb4ff67da85865d8df18308eb2fea115ced1ee19413ab01f8d839669fa9e5b193069f599043010399373ba1a8dea604cd4c7f933463b812fd63ba97be284cd56c1dd26619b9c41497d6bafa5ac4cff223adbe9ddd8d3cc10ebd45bf1e26492d7c633f09f12a3e04ec68778f7b72b65e0299626e09f0b790bf2d61392a14594e468f4ba19144dd59195507bd855907ccdc87e180445ea706814c73b25c82fbac5cea7ee9847a3085a134d21102e822b33401d28106f799a6f78313a73fe2aecc122d4f3e453ac61f16706d266eb095a58b8fbbccec7bfdd68479b7844ec3f1221890331c5e171c99dbb03f7a4342df185599e3e04f5c4229aa88e5d5f3975152e2dbd100399f826a734cdf690b0f7d9024b90147ba190524ec491518e8ed5db2d3689f865224b6257fceb39f3086ef817b559a8fb722c53ccc22cbc9793654d69ccf051b5257263f53be52694e49b370cbf7f604c109f0f5ccbe70643ef2f5329157983b9497313c91844273dea847e28938ca63524f16c46074b975a4b3bd6b43cacf63a3758581bbc8ea3b4c533b6b55608e17f562a54d19ddfd7a44e8fbc53671112ff96291c324f4e02c21bb0c5f9337978f24d53ae46b62b2fe9a135ef4ebf3140d20fec4657f809ab2f9501953d5069d556b27462ed79b805f0eb3555ed6b93e6794aabbed2df4908c3da300dc8d55f5f732c93570e0f0dc282d595d87893bf6ebdee6d6ceb2d958046934514e4ebe47e1164ba77f19fb3cf67075f5f36613e3e66a33b38ea0a767b7f674694d7ba7f9af701f0a9de52309267289bd170fb97c03c131c0a169d736137ff3d74ea69b81beeac3ed51c500e7549e04f186e89525a07e418cab80f149b3602319c6520176abe0daae3f4c0d4dfd7d9851b7834f8768ebe37601887e18f44192bf3900925ed2fcb3fbcaeca0b38d7b844c2d623107b9a4a82b47e2e63a629ec3263b249690d088f024692983ae7122895f5cf8022d1f32f00ae322c2148ac224ed4150b6b321300fd6ae74fe95650cf7e9cac70b6e06116b9377ba8a3de9763872cff75b4c516c73711f71aa1ec59550a9fb61d550faca7b635a3ca72ceb059e654b9afcb2cda8bb821081fe811f4633ee632af86c89b89fe92f0bc1582c0aa72348be238d127f589846386492deba1245a6b4ed273f7659934aba314608efe34b4bc36841ac5adb120507849a804b6c3e1a820c07688e290051baaf8d2e4fe32bd96d236717b5a38df161d72eb084b23643050d83a16a9eaeb8af6483f885176355fdfd63d12a427a7ff9c4b5cbe074dcc4c04a2276961afd03eb28c0f43a008066084ede653358562511d5c64f09348ff4465a7a648b3e89b80044da9dd93ee16e1ea02d403b25af755ca2e6c64961ce7609a6e1d7d479c34981411771ed1324f2a389d6eedc6ae4e53c948dae3b9d26be8cb7ff5cec4d383ce0b63e0ce03dc978297461ed8178a4f9342321735a471722322639704d3956f6a11c84cb4d0f2d8332f864c8b4df5e25c5e758c22fe01e65ac4a169e71bebe2b34dca2399e17ef98327e45ecf11f8eff84498a0726f8baed5dcc1d53e45dcc4f2e8f0ce45dd87e2bba8e9bd6c0b9a5ead1e23baeca115b2dc904c42d3f871fb700ac2b3806d167b22d91bd12ae2e317c4119f445a39aadab708c9186cddb17072d9c93b123932ac02e30502d13edb02844793a582e24f0ead6dc0bed29ba40b43808ccec2e8e35da1ed2cb928c98b0837e887452c420e3607e7b99ecddb52b52a2594d55923317649201a5cf828fa0f231b038c2201ee3a0e9d3d1f2445c4546ef167b6a09125df40a4565509063000920990e22bc8f80207b8d3a9a18001d1580b8fdb5cdbc680cea0680a230936c4cadab5af0e3f32763d5f04a40a351115309b94aa58a81d413d3cd9e50ed9134114b5fb5b940da67ff7bb5778280bfd073ecbfc8d5ba1300bd3a22f4c911fcf61b7c2e94e85da5c037cf4548ec3abcc8ec9c151eb2c6e09c4daf7f5a97683730bfd2b07f0a9505aeb1531834ca3bc86941ba51a2c94b6b0569866b06383ac06272c15ddaac715aa200a9a6d1b8fe734007aa0e0b75b212ba75614eae28143909c8daaf4e2a9d15489a359996450d4dcba2fda2af6495984a15b2c2a8e37ef1a546812d301152d5e0d28938f8dae2a89a9817a80502503c32bf1d4f9cf6f59aa3605750270d4d0d296e073d800719240b7baa86a2db9caee2c5e34de0def294b2aabfa0a96ae64b70b141efb2361b30bcb218e71f5cb530f8eeb7661b080060218a3c972da6a8e16530cbf80604636f1cdfd511c1124492d38e0bf0a20ea98d9e8360bc2a6d2f8f658107cf01ee7c2fba6971cf4e78c525112853a62c5f588d782a9e88361ac4a5f01687dd2c40d002f3c371580e0cb168204210cf008697e04cf47873c72c12b5a365bebaebc8097977824644864a83fc8f6fe603c4f0d2169b557117543d944eddf32ee0d6d08303b42a832d4dcec722bd0625ccf03aa1a0d1d20adb63bbc3d23e536ad753f6d73218309a7adae5f59b47a9628308d0810f9f0eb8488c231bd012eac51719a7607532190ea5a44c99c6adce2ede753efac331400be7240016c91a3df0186e6ed95d90685246305f2e356df8dd37dbf2796c573a782fd1df4f4da2c16e63e8b98c4b918307c5158a2d57e69e10179387775b6f428f8afc2d2fae4b2982820f13f3dfd41d81baaa7a01aec6324a06325a9f20f7eca4913956329b81baeeeb481aca8ad68c728f959b55ba8b69fd7c4f083b4d7fbd79daeff6f265d51a06083a1a6accf7543ccc3d6a940b8489acf2300ce641652951b0a69bd9cdb3ab6a814baf4f16d4f952f9285447026035daf08bd51a9d56b4decae3916313bc038ebef355f208ee00578edd04d94face2fa0fb8fd62c1b2e463722d9428d84ca6d549d78afe13b0fdad05d1e8e533a905aca85de2394f083ca25efaf0959be3f9e08a5edacdb7bc45e5e69d8cd56f03324542d12028a5c521fde9c25333df10913328ffb1db756e7d74b9964d344239ed6677c9338b668838e89b1a187e0260f14f8e409cc1cd4618ebe752b68c6dcb9b72af9ca90bf1bfe5f4fb68dceeb6539e9822b817fb3fe18cbe086955384226c11c62c1dd14e7eabda573450d005b46fd9f9eccaff24dbf5d6d8530b5e25fd9f2a629df5c20a977247cab35255d71d992d85b04c141673e0f6cf64f34f52753a4c27d5bb2d9c703cedcfb9fb2509a79f2e4dfd6f8531cfc274ed42b6efb29325bd3d5bd5d8ab11ef158fd0b307425a69217a5e9b1c1ef681985974bd06ee5e49c5cbb7ad8be0807507317fe2c52a3fe0513358389f85f007aa3c826f5caddf8caef972a910e3c7b40bde4ff0256a5dea05a175aed70dc63af2bdf533b8981cd7bef113332e5bee9669bec645f0aabd7084ec3c658c5f7f04b8055473e4561f133fd822b2acf0fb0268f86e49ed91655b',
+ '52a76855b415a357d6747842140141dca75e257d1c3731cf0426aad2eed4a2239262ca7d4f078780d8fa48b12a9216c3c1ab6d150b4d4a7b1d888541a5a2616d1f7562454c125e11e0aad7227baf8813db363e4f50a0e9d37079f3360ba0d0e662a8d7b4937f5093584dce9cf19fbf565fc54135d378376066c19cb70a161815c1c5d1d20d96848da7abd42873ace213b4211dce7d1f5ca968272acf894b6082a592faa8a09e2387358c92cdea1c19d342127b2234dc7f37dc7442837188d1b677d9f73d35e154096ab8af933c388e1d7160033ae1f6c8902b708edda81593389d60739ab5a5409caef6d48252486679a9d25c1d6db6603ebee3b6e4173acd9081f014c506330ec76910a9a31494cc6f52312fd3be646fc9fc9562a0a63fa847895082c812d3e71303ccd5fd6a63e688d4452365be481cb74c4e391a3e6b4be41f4a66abbfccf307e4f301983dffdc4b97d6e1da53a9909218d5e359c507deefaaa46874f768592b744dd47d73aed74104ac103a67d1f3e1c7f30965255b8bf192272f2da1ed42071ca1f7b3f6b9fff0818e598ee1066c2dc170534744af78713e9b64dda5a4d52442b91142ac687be2774664dda99123fd6d1468060c4bcdf718c8ae8debd53b09505bcb337f02749f4f9ad82fa7ba41d935a6f1aa6376b30b8775b6445ac89b3eac50cd8d56d111ad6f535e8cc3c8ee4980f0953c337a5236f36c240adcc41e4cc05fbe58181b7b9641399dfde50551d6b7b8fdc3639dd1ffc4739fe75813ecbaf252479daf29d9e22b133e89f5b7930740c7d047db2858ef6353cfe4b7fb2c10acf00f630243541797abe839db27db6584e5b7d18363118c36d45d08dfc507d75500bfb2f9b014bfecc744147f9d5277ebd95a6743952261a6bdf15cb9b8a496544bfe927cba40619230f922c96020c5de6d60140307b3f31cd832e62d1e2cd51399750c73a70086f1aeb06ba2ba6cd7c36772ddab02edccfeebc9b0243dc61cf9b1cb27c6c07eb5710811f8f0f15e36039037cc23ccf773b5bf5dc2845f9bf46e5da9ec5e4ddf767a08c3d09d4e206907b058e853adfa70aa1c972237cad2e4da63b76121964e5174746ffb8f19d7f8368f7c3923ef1e4c44c91fda23c69475a68c9c90f8e2f1cfc715bc82b09aae6cf7f44cc87cd98a8eea909cf2329d092d38a00181cb7bf077dbb3536ce619cb4bb4a96f9c44b267be0637b7704b955897f9678d3b83a774d21816dbc11bdd5620d4748ebd65c3dc64ff87175e55f8aa3851a9e9c606afa566e705fd89362f7870bf1e5134c55412093d4864c33a0c269aa92dbc2a3edbaabeae4961cd1f5758c5dc6f5f084eac3134284248a8e11af54467bcaf6f1272ac5fd6aaae95be9d20a6952e6141e615606e283c691432693ebef51e6a9e69bed2d3c8f08de7fb48f59c5125fee877d5c73ea5006f0f15432a91b91b94bf2d0545a1ebe3a5cdbea2012e791adf04e8358f2c075403a272eee1441d7ad5d845902c51a64b9f4eeff16ce473d6ac9d217de0c0b601cdd331b38a5f8705d7f399a7b06b63ef2272767e5e46a8210cbc0af5e1831acf74ac3ada4d6a61823f171191f9788998d7423b91fedd80c2a7678be5bbfc9b85a13575ab53ee12bab84d95982e00800e65c526727430648326a98c9495b4a2edfb75cb6ec4730275e89c0d027789af1976042068e9c7ba2a3187f54b983195cd2b74226ac87f997b770c6118fd9d8081af050fbc852beb806f0bae52ecfddeeed83a64e8859c3f930ea57922e8c35a0dbad2ddb76fe3604d893c9ff1b8a0e318abd0773026515c8755703d686084a5873f73709ed07780592622b17024a00e124b3d458ad126581df37496318c66cab5e5eeb2bccf70b26befc6ca165a87c6a66289b43eafa49b1e91b96ac794f32f5f554d89589555604d8c2fd32c7fdc729a95bdae93e7528d51d648a370a1b33d4f3798dfb949aef1c5a465b5fabe287cb78edf1ad2a1b997806b282775db2d5c4c32d59b281404cd9cdf7156c83df24bc5f5fadf44075f1f71f761e01e69e9f51dee0ea5ed1edd5c9ae75aa0de24c2478c7113e72e3ece8fed23fcb4b2736f6e8b144ae5508ec4058661287a839c20d8d3ab3419db718e4dbc97008d7b2348315e4c9243998c3e3329f8e4cb01cd9566644b645d92c625c3a6fa7552bf9ffba45e3deda70f42d54b4c52957d9edea85905f8ac9b9a651d5773f464ebc70f1031529063f9fbd610b6b5174377a3f7e2197f5a12bb3c77fe73ea2fd43fdb9c0f3f04ecfc21a57077dc2df0f6a15842ca0e9a1aa1a6c0244e7ed550cd38426e81353afac107553993257b85b7e304e4e8a11de05e426e9397e0fa0257bd46acee7dbd62b9935358ebfa697d8d25f008c438d25353788ded600021eb7bb72d7edc7e55cbecaee6f608c1bd80814f65d4e73d7f1c87316759324814b3400c400dd5a0c9dd633e583b70e440389a49a970d816ede302534200941f9a03afa5c781604be341252cef4eadc9ba4ae0fb04051f2de44fcdc7670a0eed7a83ce6a0a0206e7699f3a61f45847daf3615b4ec0bb45e82c08ef761e9e281b7ddaa74350b64ddc249eabc4ae80c47db223142824b9d1b18cb77047afe46b0f6bb04219e3c8c093dce77f3c67efae1cc138127377284befcd04592161055e320cafa5d2095ee4725922beb365cc8c1ee6495d15022f3b09b796b1ee7d298aec277dda580ba143e262f67110f240e7ebeafefef80df72a69121680954b7775a686c2e99131b8644cc10b9f3b547346eb94fefc02dfa8a076a62bcefe1318a9c6ef27d867c2cbcf163c0a501bd38c3186aef25f1dc26923983b7ea4111d34aeb62b53b1c108040daa9c9b8c9ab9b43024fe813030fc623d3798b609b6b0f20adc02f07c864989a56ea8655c9f4c12cc2d4e547622d6bc75bb867c06d5167a47a23ba33fa0ce821fcc2a11c713d6cf8c09641239dd989f538dcd78a25695f5ec6fa01604f6df18042be846d6dc9d12f920086481488a3260133551e521768b82aaf7f1d270c372daf2acad90e3ea0499da04f2574bf49e23b686b0d71e016390bd09dbb2f6c4ba2c8b3ceefd1004ccf7a01f63c2ce1d0a25de873c81367da69e0f9e7daa7028157f5d60b0254c359498d82060cbb94e9fecf4019eea4f347b35087e7fc5c638ad5dd0e29b117dc38106ecd09079f4cf85025ebc7d1a526c0bdc10708808e13caa4d4c8958c88cf7bdc842f79cd468e8e3ef8680821a286e7d1b8f3d407da77c34d8391c8f5262197066445d2be4fbe1e139d21555f1b782fa7aedad512b013f7184ff64e7b8e571c16858c9e6b29601a96aac429da7e9efaa829288601ad7cf8cdc06290901ff46d957e8047445229160097bd00245a5ffb4bae79618531272ab65b7329d3597e2e0bb5bd77fa585d9319fa7882ed2d2f841aa529f1edd9871f7a978494a5d958bfd1a1907acdba92142b3982fc6565a2378db3c6a1dc05314972fb234b87fe08a58fe8a5fa5ee74b1bbcdb59075da24c882d40ec0bb052aa2b37fcebc90a662271aee16a612e6d0d0c5766872e164182f861d2e69a0b30465752232a97ad702a96b7325a39acca4c88834199cb2ff1e9fad3f062d75d2cba3039f48c31d1ca85a72141f1fe6a7d8df2b922ed791b01e621fc1fcd4e26b66a5857e77d2227c3c8058596ce29e7f535ed61510eb268100be032b7a258e84bdb32448269d3000a76444ca74b4695cff8db34727a01879acfc8106e7e92228b8140784bfedf0aecf4e5ff09f5def47c3b3e7afdbbe0fa00b63c3d9abe8455c3f1258baa98a0a909d85d15256a4d94787199dd5950cb5ff033dee2c2eeaa02a3af33c724c3c25aef953c178ff53cf653308fb42bb53af9d7dd02d88d7b7db999100dd3510cbe90eccfe57eb043078a8b0c6297db75ba8836266a67310169db12c81638a5dfe00bafccfbd32cb047d18e49b500eefec46b79845817741d18e7bf3bef6fa9b9e0fba730e18d5be9685bdb8d1987ecdce314309b5e71cd0ae57fecffebc0c3273c1141703935d43b039a014af2854b7c8122e9b0000e92676a043a68be0488a45bbd2d2f65351c41841c8e17c291817a49181386df366abf0105062ab88360bdca8fc8b2e8339a897443d0581c25354285543c743e91bc7e6502fe9a7dd5f1e002e982af4499e57f5eb086a061c8cd61d077c30cb0991e31e08e825c7064a2978f5b09690cd0639fadd30f6525e4b054a4e355b4d7c4f6562df81fc522b7f960da64bb94a38fcb66ec2bd93afb1184979d375301069ddb7787d0458927687cb87e9727a69b205361844b828633d7c0a703e44975ef9c43f288b7820cd0de932ae652cc13762ab21c109289db729feb0f836aa787d538b673cb1e63c4c182d3149c38176fa7175df31b915daf39e27a3d963b0bbb6a1ba967a96559357c0dc3222aa7982fb07ebd830fd87c65fc37d4bdb6e5d485108da33ace3cd0f352c7d9cffc31dcb824a9674867d874b43c18a11c6ffba0796b272a8983f5797308698d7a9b6743ad765fd1cfae01c50e6bfd65b61bdcde0cdc70a5c0753f9148ef3b54be82a86b7417ea93656ce4fbe91e6e7927551a0bc3d6e2ab7c0c7bd6c989d5d6083c85c2b09be202c60f1277b8c5e471fca623b812fd05b218d42ab8896ac177e4437fd7c784cec64e1eeac701f4e7e682899a419eb152402346cca50d0486c0df11f7194d4519448a070e68592de12d7579ee56ab9640ec27eee22ac8d97e375532ac15965f4a13e671ddea32c388dd31e18065ee1e5a0c93370bb8517c4041e32fef4b5755e9ab0fc9d9bb0368177c347b00ddcaac262801b999ec1b4f557705643128f4ab6070528992595f8e45611980d04cb5e20dc4dfd12d24aad5365d486a224ce2d2571a5b3b9853bba87b424dad8f215432a9799a3825f064a05b5b08cc2315c66d35ff865a4c289c4921864d4b8e0a1a1051acdd3924edfd233cf6d2e4d41203de759660603e4665d2e0b395056d557c2dced4f56eaa6bc8f073c7435e85d02fa89e7575d7df4bccf83140b1459d91fedd59989f4316f84c7a7832c683d8beae4e3923333bbf87260b8bb42ea6af4e16918a5e17410b121c33b2cfc91f4d5c0441ae1625064fb7059f5884698312f857fce99c1a02e757acbecc04e76b5436c62595d4c7c21029e02640480e65591f3771ffe903e34c27726e1c689e127dcd786c68c597f9a17e4a22ab6569e7f231499325ab617c9a1001e44c61498f2a8e7f889f8f22076412a827226318656ecce2c30389bf39619fdf93a485963bfaf85ffcb297d285e8958eb62dd7a6883f40a7a401da79a42325600edd5bf0c36fa9ae5b4a66460f9f556232262970aad43b1c98b9342d376f4f47b85f4ae59fe900cf06bf70d8df0900c72db3df2347de2a9623921d467da68bdb2292e8e14c079c56919a4e27aea5f6222b5f7f1b09ad8dc8d7150c51f15959aec020ac80323beab98e53549ee906c417ad717fe45ae2d30925ba67dc1d08473733810c2efd066a8c4f833acb08abe8fc16a2580ba5ee98fb820ad6415b23b318df2c8e59f6e79a336792672897f5d364095aadfd85415e048e8ede2e564c4ba8358bb99dda60083379194034117521c3f812d826738b90b8adaed60f78d27f89d9470476f2001320d6807c7a0fba42b055536d32fb1dbf7c61f354414d66ad222cb6f551e838797002506266ed35b49dc3a4d39f768858e44de4df804e7972f5c28412db277979a5af11a88724680600a58fb1c8905beb74b40bee28f67b5f991d06d35f3a63b2361f3c95575fd1957d8d6c7e4ab2f8f5a256d0e6c7dffcd170aee7bd7a9b57a1a5f549ef53fa031168edd3afa268efe60188cc8fd9556e6710fa8f47a9b4f5d679a3b1ed098d6cd857494dac4c1cb16af6dc671fca01508efe7265f85921aeaf35bd1d34e4847f78cd22432ab468edc306c4293d367e33b79dc91446256be2ba4e93d44d8169cb613efb4c7187b7e5acb5c29b5e9af6988f734112b78afe428638ea8f9d4cb7d1302146d23712a44976987a260a6434ef65138cd9d2f6534e819903a7bac2f9114418977cd1f19889bf033d61b72ea3b8e6f30ee21ef3f5573ac381a51c60a81c4b896f94d8b11f16f4aa9ec6eb56bd85739649b402006f0d106ce71038c24f42850831b2c1cd9271a5d310de1d78fcc59981bfd812a82ac0b0d9a661a6445934aef9707f1393bd3a4131261fd401ec09c729f3c6c767b3227abe3221db83406317e1be2244cfd9d1652960f4959e05b29b1367f896ab92930c7f3cd94efbaf4e5e657d74dbce82c770403404e889c46073b29ee616f033a2b48108e07719f066f0d930bf5f1c429cf10a21b92361c283efac0b37bee230d2218838dffa6abe3d6dd17a9a353593b265ebbc99fcc26aad7d442f35f4ddf491c1e94f304f6a533223d2ad7e04a6e0b85d69671fdb08374d9b3f9d996f6009622428ae89cc957a05b1416bd333ecd40f3607a3c324cfb2d4c7e2b8aa74f4e9e3b6caf93518787d08a4c218524973c8379e4b152c807557d381d21311ac290066f84491daa98fe6bb7cc94543bbd847c1e4a81da47485b0ea67723f478b59079c672a1ad2f64841ae87cb75501186dd2ea7a33fc3579d3859d7678fb4892149d491eff6c6954e1852744d1adfd10be2ee55c0ee21c01868d26016a6f12c0e51aff71aa82dfcedc537b0c2c87b80e413718d95cb183483ccc48003f785fa7cb99f151ec16193b3e2cedbc0e09b878bc968ad76394f3b6bcea0b5c403db5af8dc225a70b50a004cfba833cc05b87b8f8f2433f6db1f3b3d09077fbb5e79f40f53ca55c5059fb8f57a247748aa6ad34bdf44cfac0300ba73e6695c6e69da2c2c9b079560359b2469e8f414db312b056d244acefa43ac3facf136ca51a88489dd2c5a77f6c774e906b2778ad85c61a8501896b0563f7a4144b4004819bd01839d82439bed3dc56d48812f47e46eb7228106b9226448e355d7a946640af9c9affa37436fa7fb0527cbbf9bda63fa841a9c89428398e27fe3e24b899cc9f145755fa5ea71db91acbefcd84dc0fb8260e6ff1a935258aec881d1053a50bb362d6a4a68a930ec9424895865a589ff88c61e25355c2d480035a63070e93e732ab9f55709f02b5f75151d6ad2439404903c26ab53ed52db794fd79d1916a47661a3bce1d46fdfa8b6f6e1a0c8987d9f1b3ba6fe10c791879c8f7c53683321295d4317978268ac5ccc328c969324b5139c3d0599d68b37d0cc6d2b1fe8cf432080409bd48e8b4fe0376064b8dc68d92695c8d90b2fa8d44b9b715a4f055b78e07d04c015433808d78af910840ee5383c9a5787ce8690e56c8c34906d586830ed96081af65b0f85a73974f694952a70ca1c71986065d3e998111f53e75c7609ac8dd302e4306234e3c56cad47299901538e9f814d68026f62ccaa1b698be571a1a6fe2e6b6de094f1a138ab2816c1786d7349e901e77cd551015d6d506166f76183d1d3f86e0b34657f7156c3f724247c6d09260a7ed3c9f761b1a038675b1960a706ca17ab3281c3929f56874541b414de45cee398879baa604f8a8e2842363382f54745c0070b709d1b210e8b9aa5ab49885a09cf08e90e8f7f52ce0eacfc3e9a35f20f26e94010e19d2cb624af67b421594e097ce8208841c9bf8494fbd9f67f4140d05a69be19227df00acaf078d40831115f3ca5e09c19c7172e45df4e0f849fba35c4f8d3edb2361198c8fe576f60a84a6788b297de99fff037bba7c6ce5ca9d4bc11273023236de7dcb929920acd076fdeebaeb4b424cce13d40e5d2d76a1816bfa975414df88e09431612a71aa006f811dfead5b15cc650bb9029f0af2f3306dd085385c2a40eec685a4a05337c0f59acd007695a9474c51f03d4beaa3eb30b70b8527c25b86917b910203ad9d3892b0c5684ee148e40c4f416869b7cfae0bb3733e2a2eb9844f1b1b245c662292cd2bcedd3d2738cb2c6c762bff58bf6748c7cf5948888d5fb30eecb2b2680ff32e74b0074c4fc225340f552d',
+ '0287fa0e377c9dd608cf9853907b010bcae4c2160275a7dd8988b522ad86be41e84f32b558dc38dd6f23fd00ec3a4c900ac060abf779f6e78738a64f2a0272a91c70a0fbdb55c54ddea123af8485347e4bd887e442bacb9ec1772a0257aed84004b2ebe8a8306dacbc12af6840a4e15ff4f5e0c7cb814f899d0ce9421cd1158d09dcbb84a8b55784713e4c3108de7aba6fdf125f7b15a93084c18c1761b4541893b8bad8c12bac5c65eda014c47d2818235ec6b138c0021bdf5cbb890ea0bb6a0b0c8eebdcdc93bd00e753185cc71200783aa4c7ebc882d314a61da10bdb720ad7a1dfc5e20e352eaaf30e45b80561fad63a53a87d7650df8d675b6640ada280613f566fb90ab937cbdb79a4c17e3c8ea5287c5cd41295c7b0671ce19660735510ad9af04b184860cb653b3c5d7ccc454dcac6ecdae47814e76d0918f33b0c104bb554507e7f0a32125afc16754538a636e8da5f7533224d9943ca15418596397c1d2c983c89263408816638f222a93ac94c5fbd8f49bbfa2daf06e0668738590aaec9cf6c7cc5ef15a41facba5b47876cbdfe0e0f6c6aa30d7a657f4c891bf75d30d4fdf6a10ee9a289cf7ab738391788025f5bdde557d1a06c91fcd9d2669bdd6bf42ac1402aac15f91fa8cf01a87286e429abe1fcab0b4e4c2f5ef7ac42cdf227d25fb7a140c0d8bcb640ecfdbb1ecc2b050703f88eda7fe4eaae8d5dd716042b16a4bf0b79ab519a3e49f5759ba5c49f9a762b2327c59bfa67f3822e4cfd7b4067ffc1c8fdf7ea5b5cccd2b16f8fd507910041c39f540a575135c067ca0bbf2e6d7aa61ef32b0ac6bad06bf96062662d91ad2d211d0f35c34e7d2e5078c638bdd11c545683d018a5005da89596a8e1d74386d785ca7f820506d2b4317fb84eb43bedb4b7d76d7ebed67b71cc38e8adce4e922736ce2b5ae7233c3a5106696add52f6ae8b148aa3d9e233aee86fab32da5cda067e509b262f4ac3a8f93660f2febf3e2b1865b0efc0cf8c472f6278d8c212645aa378584ca62570e671372550e02acd11a8f065ca3a438f24ea3ad707501a3a0dee6fe936145c4add013040ea4b39ac4a81dd349c0ee6432d601e50274a1c6405a75dd64a41975973f1493a2a0797e2bceb55a2cd0562b04bdb376ca079bfe82c166aa8f2f47da69188ac9977dbea7751308039c5e75cde64a1acb2daa5acd06883bce695f7b638200f7ee83890db74ef978580ed7c7fd661fba6ab3e968b24a3357e189a10eb1806ceeaacd7ee11e080678cffab8b709f2b314ddd321303eac475d6c76b08c44c2d0d156fbbbd35c3ebe9bf3f68deda41a88bc8d21fe6bc2cb38bec7a6fa6e8de7b142ab84cc5ee26186544c78d3b63c5c25140126ed55ff158bfe9b90eb400d5da2a4f10f2cee510ef22431f806bb33260cadc2385a994429b58f50cb0f8b33a3199acfe159fc189586ae5d0ab3673906a3fc58fce29022664a037fcbd3caa1467a76b0a1d012b993b83516175634be7c7f822deaf1f52a59bddd8109d468c6b669db1bc72bccb4980b6b05a45bce240613d1c9692512bc72858ffd1b9de024815c3a99d7c9848a00f4b2a448507e1a21f56db41de893695f359d5c577ceb4b25c607834a45d4ba6d08ae6a69c0def16e98a8666fb8d1b16e428827640dd49b123bd490950d27b64acbb0d08f296b5a3a723468e51258152e40c2d6c7dd26a4d522342a5e9c081e18925c6f2ef6adb5141674240481b1052d94fff2d9476be8fd2d88b8fd8ef042651113aedfb500828a09fa3044836711dad371f43ef91ee7e89244d4f8427ad39eac791807e11e431aa129062b93d4cbb460db536f4eba1226051b06e543024243e8ff234e0751873480a32e303f948358e18eb8c0d4b80843fa6db73b2d110ef33b1859089444cf663cdb00e8e320e926ba2e7cfa17a32ab0f6af7e605d419a0b374741ce14627c3e1a4336cc2af46dac7f1d1861741609fb6e62b50b4ffe841a522e460514352e1acd7e383083a9716894ed23ad966b2691e62a038291b25d9f001ffe53f027558aaaee7dec699a94d990112724eb1cf102d257d26cbef78717e5aed32144c3731c571680265952587df52b8b6deec609ccd79eba2024587103674d6cf39e94073e3678d794ef6b3cc4289ec8ef1dd0c16e5a4123536e3fdbe0099e14514a13926eed97fcae884fa25adedd883ef4e7c855def1966cf928083c40f361b0f3cca53cd0f657d9a07a39905c7a11c410558f11da229be351ab8686a1ffed991819a016851681ace465531335f72e24dca47630c0569c4d1434f74db11610801395238a7e7b02aeb0ab9f41ffd715c7c67f1e11460020009d5eab0fd2d862fc92c990072186435591b77eab1e9c61236a2ff761cfa1bfa46973422b9bb96d650221862b12eadc17f41361bd26a9a8ce44519270d1cdd3bf152d2d4f802b885fee377654c6fb258c7449e9068ca1553ec16e6fecd0e704a70ce6fcea04b15c53b365d122b249c8198eb58505c4f5eeeb8de0f024518fbaf2dd3b1169aded41d6fe5726f379492c55aff0c6397429ee5ae643b3b5d82a6f38e2940b6cf031f1602b65f875609bdaa764961d200ebfcc1387213b2fe939b9dfd97c5b626021b365d72cc5f71c944ba528e00a47e91a108bdce3f6e0e94ff35e1e7e81c8622f208e6e16001711d507d579991e1fb7db445a541667776cdfd43a2df50f2d9accc1106cac4743c4d097aed31bb915ef85efd579330d61f86ba50a848a64006e8d0db25f6a0c0bca196397d1d26bd8f48c7ba3d8c4792f00761e35ae9910cf51e27edac2e9beed76120426d267b6d75b51603bef450b3d0971885228cba608e96f8cf01385d0477d4ce1e271462a7fa8974614292f642a98007bd67f7c843b9976c0a8edc8f0d8343c95411af8275050a085b312fde466208581392f364be5e6bab25bae4d90ee3f6386c95be84de7f82fb79f493b3c7e378300f094836d76558dca8ec16e2117f3544ee1a0b0feb4e377443f1861bce1418ba3a35bee598b6a7281b8e3c531d3f481563085ccca25b729c4291d0be61dd2f1b1b7e1d1a0939a0b607071cd33b0b76d253c67a630d8e7a9afd3c38468b26077e3b4d2c7c31d78aaff4bf7f0b72cb09a444be2d7b34cf9997fc5b885851d7e6092008b4b41876af3a681e2ca2ca6747b2c0573cbc1d0715bbc854869fbdd815e454197d69c6ff5580ed8ced414bc779254ef971d0d21c372de891fbc0d611dc385fe64f44445bc5a80a718890fed3e624770c925c5bf84716e478ae66a46a822dc7d9f2ed997047db4835c636ea74d8d8c1f8680bbe818d9d4573693730cc51ea16582d0bcd2822412d406fdc17909568b626bb8205a150eb92e9f2db811d8f98d3cdca46e96aa00143fa4b298e1066fddefc536c383fda27534212fb9f47855e879f8f48f31d074412cc21c656dd93bfc0e3f76f5d43717a11e5913f93307b65b93645b6f62ba031211cbb5a77dd64d5e44471337e945e0c523c374e64c2b8d4f1fab43bf77bb3f1f853df8efafa2168d2858761a21ce904a1aeccd11ae386d4b853a37d00f588ab1fda560ae61b119f131002a1d2c2598b83a3176fbe7d2b8d94a9db24188166881f17e87543287da32e4ba99a156ef8c88283e1d15777f026106425d9480797b07e745c781a08ad9baba046f573080ce425b7f29eedb91dc8b1ec4744976f614ac7587cba72a5e9b013769f59f479de06f4a5127f892bfa9a01a9090c0da10d7e7f2b0ee453e6790aec347e6fa1a7b65778b6091c31b5cfc5870435d9b286e2763054db9dc5b3a4d0c144a9df817bdcff38529e1cf03fe370cb6391558f042a57613dab8ed1f4b42b170de8509cdd9725bde29b28cbb17fc4562fe726ab042b4c9b4d465e7e91ef4278f7056bfbb630f18dccc6e7ccf3ebe9afd1fdf40e6f2f7a65ec73b6d5739e3e6b6ac6d7a5ecef8c327ae702faed6f065eaf9b68c12b7c0c4782fc3edc80084679cef53ca2691c1e3452c8205d885343ec338b29cb225a28c977a79d9da17783288b5844fb13ffce19be30eafeaadec9e0c494e0343a13f774343d7c20bf311c0309b895b7d4e0c56b25e607e43c59c0c2c97d35055deea0cf1f85826bc07f3a8fb1dcd7de93062bb1efb32017270c501bac0fcf4572324b63a14958888ea90556e98eb37938ba2774835fdda0513f9f71d41257fc612822b6234fa57f0ff7a4df1a94d08faa44e13b4bb2e586a43ad84fa94e743212184a520b6012562db140b2adb7d828d3ec828eae74e1d10744213a938acff06c49eebfc2444717ce1e005808bf704c9afa32f5146c788a61a7a2bcfa90103d59053483b1c3ebcadc870d5895744204e7b518f9e56353b89ca9855c4626de22c19244283fa5a6753e348e3abb9ef657a2665d218a211a639f93efa3df15e1a68a394736d3b12222dc6dac87e104344eb45528f6696e749352ad0a172e24c2d19d426533d7b004d89e7a8fc6716fa3f003caa2ffeb1209519d3efe42991d29abe2f5c5a9b26bf7a06ca25e7fd2a7eb45780df3c478d482a46890f3ac89c6bd3d419a901fcca7a1812e2f423a6c74b555fb6542cd797d87959be910db67e9278ea378ed1e8d2faa83cc676280a79ea929751cb7a354d5bf2b1e927d59994c0fa6eed8052d5dcabbae2e93e7d8ebec6ec8cc787ccd73a4d36ed9d363ae89b81b8e0c0200d4a43f7c0b3dfaf8cba027ad3aeac2b6d33cb26a66b5f3ea609df4f64de33e059bca5794a1dfe6bee02e170d88b541903e19c72d1c983c39f93fca46eb5dd43c0b37daac78fd9d609ffd8437b9173f309471aac4976cf47901d600b471610bceab53906b99806807907536d2d5f702be60ac24d6df1764d1feca5fe7e6d62de30387407a0b4e8fdb3cfff487e53cd3632731fd0bfd83d46a7a82af8852a680a29c39b480d6515a032a0188feefd0fa46736afd0df8968b6bfc68b83ebeb84d34fdd3b226036f11a8e2e5b8defe9abf91cbbeeb81d83ad3fd0de341b231f4dbc1aebb03149992fbf1ed114dcf17826a69b8959112a656f248345b148bb3427470385b6ff1a0a16107d2ef0f7b447042f8c158b56669d13173f938f7724c8a5e692219bd6521848b1119e5c5878c4c90666e6d20252995d8a7e4e3b30f05b4e2d5f45fb71a2223c1384b5d399ef8fe9cdb473d9af8ee892f0b7ec21009e5a848dc379423b5ae664ba4efbe31668e6fae7ed530eb87c1957ec84e3ed509f44fd8a5721fcae1ca35707c8d70768758850e779fafda79a9a10c05dcc0ccb63b8fda592d6a744480787ae9addbd0aa5e2904ef2d203076af9522ebb1aebbb9c151951ff1dce886d717af12d8670677a744d70e08ecb528da5908a254716bb98f7e522044ddf050d8fa5820957ce2953bbcd0fbb77c313432d606514d72a45fabfc5983b1d5524a8909bc3a6d822aad227b37dfc2376c45bff2642025182d531fb5f271bd2cd71bf42d2589e7e1a766646754e1b2842d018a96693863cad03cf38f6512f24b476b214cd9348b0121690a6a6e2a0ecd3e109aab5ec18ff253c22d74f98dd7986ae4164f2164e14a605d1c6bcee15e79651bf7178be232f77f8ed74bf70bf47c082cdd1f454172252beba051bd2f2bf06bcaaac438c4d3411c48f8fca7b3d260e8eb7ea28df2c5f84bbba006813cfb9918c4ba98ad8ffa38f298996e51aef8aad3caffc241a0622c89742371622f59de33b22f7d316a2f44c824c18b3d23eec491706a66a872d22aabbe327aaa30ca26863b125a0e7dfbcd6897783ab33b3d14ea87c6760b919c597943d4099f69d8dad7086a168af1e53b9897c663fa1e6c04a6b41cd9b2244820bda811068bff0a60efc03f9beeee76f621ccb4ee5bf303fa8ca17379e5545fe93d98f159b41de821960c99d0829b3466cbe049c41ee28b6065f6d33dec49681bc2b97deb63e9fb859394819d8d21a9f35d788ecb8b158b9df95a450d0e2aeb1d14b3bca2df8bcaf5b0fffeea59a85d06a13f89ece9b18b191076cfd1951f7059fb2fb767722cded7b3f6199f2c57d7055dda441082e1133c72f27c71c75280363fd5742362f4bd946520d9a54f569f5a5af4c71d9ccbac6ee67551d6ede8a21ca74cfdbcf80af1958e2646e20ace3d6b603318fd8097dd1736a758e64e0c5f73fd3d5a1c7c970241f6af683fa4e739a2373f4196766e2f9f2832988a34e43ea4074f912936c276ff646448a467c81c66a6c14ad2c578296e85bb19abe598c7158e2baccd6db4d739a2ecedfa999a6e1e76613997b7dc53ec14ede42faa51822f5978b2ee6b94ac5676f15f278384b5346fc618cf92c335829f6d0000cb37f85a32dfac76768b7ebea918a7156daf7b0f5999ee619f545896ece675049071b0ffdf08a14cd7c1d4d8030bccaa9e424717fc81c43526b84392c5fe4c2541283284299a9958d65d360e4b72d6d06066a2b42027a1336f167edcf05d8c4925830753d83e9d82ba882cc74bf4ce6ef943134e8c328d43c19792de35ac3d5a85abb6d2b49bb3b2cd693f6cc93abb6cb200224d094b914d666306fe5e93673fa9c7c76b845ae6d05529f6638eeca5b1d3c657195f1ab339a4a162a631b913e0d900c38512223bc49befda1562b19427a4bc3b1a5646fdefc4792f3d78c749255d7e3187249cbc76e7e472ea0c3191b56d0b55b9b57877570d14f32bf9640ff67c6899db836f70cf812f464b5615a34375da2d5c4657285ac99a39d77d396f3b80e683e8f7445fb5b0fade9e0605d3ba05524c6dcf8c18de3e3386abcbee70dcaf22781648c39211bd6ac34ce5a82800659b395219be4fccb605640ead2a1ac15241ffcd3d9310ccf0a97cbebba7aafedddc7c75ec96d60d773b5a68e99576d3b06ea1759f5de1cc91df915b50a9619cb53d9e3c10bcb487c9fbf12248d88bf813cfe57636c80efbe8338a8a6b5756c334e726114cd7f124f66dafa2929b6219c18c5339ca7d9e403dcef0b5d6599059a3029c5b698f96cb45bb1f518f85011f03ce732423239982d7d84b43575617474ab58981308ea964fd0ac697063f72f021bdcea0086308abff78219cb7f7f476712974ed667935d667852eddc171bd766380eb1643e5f2a2fdd6fc28bde32ecd6086e506d6fb3f0bcb51de8986c2e897114052ec9a505f4f191b634e33c2cd335ef3643447bad1ea71995e051edafd3d72248c8cd64d579a9b6fdf79df3eda92b5987adf805ef25ba08337b75162034fcf55205cbf83e36bcaf9f70e8bfd5bd1ab9eae6f4a90ab46a137f009e60203b570fa96c61c9b0aaaefe23376fda75bd8892d894c6df80393bdde1162a76104579d3c7730b73cd53f52511b19fee5df8f976c92dfb13d022b39a022295a280e1d9e434a04560f4a123b6f385f555ff1de1c84518ede07b0f460a4acc8c8fe29efba3697a9c2bb740cf26411b1ccbc98ad629d4ec2bb0016d7791337a6b98c5eb53b3de787c3e95813be572e7529a4dde4afdd12e411ce3bbccb14492bf57ab4576782c00620410010e970181535c1c66b4c6f245aa819278c9fd06cbf436f34bd872aad8ea36a73ecb9d956f7b8b85e2a81790c8e488ae32d3d6f27ca6c13e5cfe2603387120db98d77b770857a34aa09952453e6b9c87689ed1802b4390fcf76c24adc5936d1dbdd6a35ef2542dbb4f1d29780ccc27ce887f7f386e56f0bfa986d102f6cd75fe0c3dc785a41d95583c87efb1ba72d4e420ea2293ac6d9ae6f1e2cdbb429bd5ed02613ab3940084b1f78e277acdfc0e58b0838e2a7bd3ea135f147d3f4f6af77b3f058f4158a956591e74634bcdc44eacb5cff455333658c54f7061f763c76529208f37b7487484ae0fff159d20580040af2407bc8911715d51ab1e8c264da9674562c69e19feba314a62d0f77c43edc51b242ab8e3d1ce7bf418f5561d4a3ec62c24be6e13a441736c6407e3280441aa7844ffe2ba13ca81a54e98fdaf699fb63349719fec01d4f4c4673cd8ba25b651550ad9c29233f01ee3a07bdb531846c7f94592bbf993cf2610e0e250a904b65a2fea5ac102dec9944dd31087bec2be6becada44ac2d69a97a0659ad38b3dcc356767f5766260c192324af98b391571229be5f8a4ee46e1ca1169c8e9c73d62739a083c4b576672894f7c894e587d097c75933282c2712f2dd261eefbf3900385464f91c8484c56f9e3ec6',
+ 'e36e18eddfe2c21d097af7bf9f8d89f1934ea6b434e8a3a10bcdf7d8034a8b3ace6031d883cf71aa8c738c85bfd3bb47cbf8b855d67b7f4764e256e17b2d0b4505ab7d6875125dc3adcf364b1f9baba2334f018fe9f9ddacc02f4e5ed6a30d0a50f80486fcd40ece3537fef9080be26b95fc894bb7894238e75be7375dd61af079efcf1b623d0b35ed52ea77c04be708b7a6587286543853a00f295558eeb95f4637e50ded74dc5e9ac905ad8f8442e361f677ea9f824665b4f31d9e0f1273bf81794e46e6a209b307435483bfde7c625d93ed9d4a3af5d6ecaecafbc96bcf79f05c13ac95ee1a9afb69e1e297801cef7227c271cfa4cb0e0ee93954d02155f35c893b294181987d3de3b3b05e93aaf16757fe5075e95275e24b70926a5b8d968ca7cea433820bc39658d2d75d3eafd005cdaa2185311273327d799be04100ca4a5fd504b41af9d4ce70473ddcaab2f31431cecc47a39271c4265c597afd35f85c5956330a71ec18add419cc22fe3bc45c2a703803685ad561ef1fb37fb4b4868b3c5c187dae6bf7fb2c506a7963d2aaa4619a4f01a7f209d180cc90399106de9fb0ec9b57faffd97f1ff5012701000db67e53f88889e373dc806adaeeb9b266605f10a4de7c2e2602560e7875ff4c555828a41be236b37d4a980278e757408896eecf1b5c5d83464c79a6b7ab863feb530793a155d5ed8ee26f9b526c9139ab080832e817c483a1252fc2a611bd5f356ec42b702a8f160d6dae3d50c48f24367f533ff458dab89f55206c2defcd379c73077171a98bb864b4fcf05a397a8993c377002566c6a9a9a3f5ab348cdd4c8928776f8c19f25afa7c02bd58117af0299c1d7d648bb1de25d85688a33116286257bd2caac4eb85379067ab3295b6e260d2edbd9a0dc3e07dbcea096226a05290c681b0b1f09fc083b7d4c3d00d57e6a02c8ec8ad35233617175a3959b3a2527c3e6a048be6359346b8f10c1ac184855173a9a6874bfa685db14d1d712044b18d86202f1eeed686e85c658ef9f86646db6fe5600e976fffb5526cbf90b4b0f62a684c39544faa22d16bd95153fc25b1a7e8ebfc2c60ab8289c15f269fb80ba9bdaa962b1353d8ee6aadf45e1213e84e1ba66285c8f0d67940c7cd5a87748052ad15a50c4540897319e95fbc1c86d7a6a070f300c98d176c422c5f642e30347a627122e4d15fe43747e9c1735b9d1c409f104677198096e4f7b8bc4b7e3452a84486b4bb9ff812c45d73c38cb59fdca47e4c02f19f117c69c7328c175ec70065bb1b049a97533aa2c1500d0e14d6425ba7edcf774387f1811a6479d0b53366382ffab988a1b78557dd4846015f8873ec80bb5710ed2c1cb65814297f6dbeaea908e97c8ea56e1b6d1822166d7efe9adf737d3fc48546268fe90b442c6db1fd40aeb1f5b38bbe236cd9a8275b5880ddbde6fd329bf31e3694351173dd9d547e86891acbff3bf0c5da8aa01ed2d755640d4301d69e1ffc754ea5ee5e9ce5ee560201eba77a9d4b2b4192d3a6dbe2c75857f152d3d4f2270b449f69ac702dc9b03d7fee302d9a197a285909657d611ce12458b8d24652e91ffe8c4b05625d2bb2bd69e90f1f18a0dd18470903c03d9d26fae0fa7bfa2888d905d55c13785d5d840c29a5eb58002a7a98c49d29ac5c925064f1e275fa5d4d62e0e0647bbc15a74fb225ea6de13bfd7930388b7f4aa74386e7f336694ed780e217172be8366e503b35c77f3dc1b06103680b9caca106f7f10d4ebffefc80698033e17800ed4ea26f1f762e020046a040c9a59859b962f8b95d6a45dd0e3f5065bba5156be99fe314f799bd64a7e70157da6e472d9ba18c055fb090ad8bfb7cb937cdc8d232475c51469af50dbd7acb7023cf71d14eadfcfd9c8667177c9e36a330c05addd130554f93e09ea24ae647b52d2eddecda6c90cb9b5593c3e80fc64b2de69333cd40153566d380ce5715bd4c7ff4542748c88f94e486dd58d73674dab6a73a7d2e61e62c47db6237eaee745da28a9e2a9fa84383007f0a7f52fb8878536b1962fc7a0e5cbf600e850e59b6111c56839ce7f3f2bd76aae5df5f46494c8d9af68b4072d6ec55ca4a61c83ec98f77c2cb252993e56dce6e352109ff007feb9c9a9f023386059281df732285f6003a353d6e6c6f64b99132c1ec550ef538f6d9cd4748a466cefded95622317858cee17a21fb86fb6c9466052e94d5b70a034ba562b1cadbe701e7733d6d3dabe5a1acfc2e0157b25c6755fb0bff8cfb9df2fa5fff28536d5127d8583793bb7e99346c765578c7bf7d8a797a3175e50171c6f038e458c27f4dda3814bf062964d5efb66532f245775266270f39988c7d4807a3dcccb376863f917d154e6516033e4bf14530ee6f7e7d9315267ec7d1d3632dab9812d90e26a8d61df66010efbe34d1d392f9dd844951899a5c9b94538865a17e175129f7b51c10a1149e9a351a5c09a3dad830424c60c24d1dfa7e9020b22b71c6f236b1c5fbe99d1c4471749c165c08996687280c4dd593e725f70620fb044d13908fcaeddac9686a0c373ca7e651683b1f62941d5b2c0544988e23fd82c466a99685a1713b38997a504bebe26e82494c8d3314d42b3e27c2ecb4883afbf434e58eb8e4149f9fb6e9bb3000cff157324a1a7928e530a461ce7d0a915dc91c5e53e3a9f12c7a974724a77429c52158a9c061b54eb4fdc759e3d3e0ddda7c9539712fd9574d4e97a0b7af98b566610f162bdd6804b8054f568f8be7cc3588f76cc9edb784ebae197a1f2e1e322852d31e08e811e982c9750e4e0f7ffaf360db4eca87d5187bfbb529c7bd652e8f594d17d43c434b99cd00d78bc929d898a68985ff98b27092c9c3365dd80e190ff6e59a0a246cc961825b58e56365f399ccba78d59ac03bb49aec362e0889702ebe352b375b0ed17e7becf8d748c4ba7b45c6bdf8f88ec855d1989fc4b967d26011a431d82a9a02453c4e3dddf3b6cbc1a436bbb65c8ea4fb2050e9be85199e4893cb44692daeca5a25f7ae65fecc75e15a92f8aa11405018e99d8a87d62f525ca702d966cebd9821db61b0fbc476676efb640b50c1346843afd7189d37099cf0ddf3775da836fa30cf24ec2bdf350edee9125a26e6eb4befe5a61a8b2248bcc9ab0594ba24ab7f0b73a9b672b998da95b6366bb8fcd589821d82e6ce226d502b90c1c808deab85265b78b64cee026947e538c6736d638faed9e623c2a1d7f5bb21b077c63087aaf6091a5226e7122959e8a1e0d2d345ecda8d5848253b28fbecc04a2a7ec4f06f1eeef7109d3f38993042fa4173a8dd2f79ba41da5268c0f1250f0756aef6463fe58f97882c40f6378f96ba6ec268a9a620e34b40d685406418606092b36f299c0e6d941f30b6db92bc366adf4b8d2c0f448acd122e4b17d32b66fe553879fb11ddfb455c56eb49294aa474097ba0e4c979335fd4b614c7bcd2852914bab13b2ff8db53a61b68df79bf837e83df2e5443235f7bef3a6cc08fa24d8967a6af7a44d36017a2dab9fef28f35d2c4e2a00b2ad77199f7f7cda2ca1f1297b478bf390ef23c39d9314938223e5e2943228069df0a1c42558ab1ef0aad86cc81cd8ae7dcc9c2129faba10b87414eda851b5a00ee2fb1fdae6fb3f5bfd21ca066017d173cee843a8ca66bf9a03c9a7f11498760c6dcd53459134e67683146abc7a11037adc5152a16ade472fb84b37f6e19b2f9f785e87eec21dba4822f87572d48eed9bde1d769d44ea52807ce4c63f218473cfecf7e45edf7f64d9c31712b0d55637ddbd95d623bf54859335da7c553d1ceca5afe0921c52228b314fb76affe53088495f019bbb43e38a72cd7680bf9b2b4028aa61b8b081ced859ac21a4e66ce588e5ada9eced385e09252a00fb05a5791761c5c27ea3d64bf258c49de9eed23ee0bb8fd4f242078ea9b236c014b0c2cd1a56e94a0d7cb76083367d72619d800038d3eeccd541163b1323d7fdf44759f0f011fc428dc38ed730fbc1ea337edcf63733b0d7bc73ce2cef80f701c252d4ab429ae7f22601c276a3b0774b88164fe786e5bf3ecf5e972be4cf7d12a75a9ac4b8f793a2febcbb06845d87de87ef429acdf10149388cdafe9a1a5179cbe61303f932e79a407fe14e7a08d8eb59901bd8b35d603f1c85fd6eba685b7e557ea732ae53ca2112cf9c182ebb54ba25814469e033c36aa6cf6a43ecc283686eaabe177e6026819c36542a487de292144b2315fd7b1110fbf48745d72fb1813df3c07c1263e200073b714d6f576abd4e7522a4b23b347a467cbe3c24b7cd0febe015271163e6e77b675b494b4fd581fa87a7dda67d13f0aded76ef7a62bf5b9ffa25e024691a7e1f407ae68570092740091c89824896b958d1162782396fad82c75971f37c660c1c5037ecc5bfeff64984e870eda5a51f70af08ace443c54dbcd117a125a4b2db9a1ff822b2466f383d80735a909a28681d10b3464788aa8274a0c83763e9e631c0332651f50c72334483250f123207dd867b7fbd53110495d2bad5fc5454ea57b6162a413619de31841766561d722ac6d478d4d8b1e5bdceffa9a4b8a1eca695a5506aba1c769d7828c39ef956647a6b2e8f1a60090cbb7523f59f32f1ba0232b56750844f2a9550561035be670fdd13bd49b91e028fe8d265242d34edc4e7780de54366783db0033684936ef87b3c08a92d19d745752a2deec0b5843d143badd652aa2a3fb8d4d8f2a920d1b0b1809019c4a3567ac66bcde651a1eb88b2e81a3734ef964ba2eb16af0745ae38b88ff43594bd62488b92fb745d583176a4d407591579734503dc06982468ce3798537c2727d0c257cb87ffc06e421eb0f4e9bb637cb088d4e38ff0d28af3fa565ab36f1405453f508e3221f30bc77adeaa0d807ef2fc9fa14bec1c88c14cf83d05961727b482a1dbdc7ba935bd4f0ca8cc2a9cd2fec57e5342dbd1e04fdd9093a1e6ba85bef1b09fbe4adead0a429db4642758862873ee93d1445802be0c8c01378aa159abf933453b8709847d08f5ad79ecfcc3f86eb709ff0ff9179643b32ecd6a14c90101f4745c1a72e19b09dc391461d0b0f71fa15cd424fc475de9ccc36e6e5afd4a73b9d528eb61edeeae1a003c6e1c76acec7435586ba9b99d65a0671f7ebd1e0db64b10a7e21ed0fd5d76abf9fb273c9f3ef73b94cd4896dd21f7c3ea828354d33272cce1e8f0b1507fbae253f03a2b597ef3178f302005a684987ed662f5c6234ff5b1dfe321b8707dac4c53359d6b61123170517e7a2f7ff64d41eb065b9d85759e68a8bdf70fe4bd159200b298233f6476efe39d9e3cf0dfdcb43256fd00810c3399445cbbdb34a9bc5c6350737b7cda8d3c4c77d45381a80e7eb23b2218a07c7decbcaab447e9a86b5182f5759c9fde3dfe94f2649cbbb7ab48749b140d692d4407178d2c788a21e819678ae7ae30d74dddc4f8954025757bb97e601906658850955def69999f7efc5d1af811d8b82d8683e370b167484a457ada707a2b1af3683c96b7b4d0b0266fc5a1253b43be47b095f941fe38aa9fd33290b57fa512d756a4cbcd5545468109f806fbad17ca8f93be0f55c5dcd5daeb8aa6156f9cd59de07604199422e8f7929f5161ae7b646cf6a42b934d7b5165562a267a9437e9abcf5a4782b98c1ff0d161883e88971546a2ef58e3e03ba4a6dcb917cb349fd86beaf79ef30bad72d48fe7b57c8194648660489b2dc447580744e082f098fc2a43fbf61051d3ad690b94d8df02e29ada92f657fe5942c5f5ee58466ed00875659dc8fea5855ab48cfda8dd0fb8f3d0fdd32cc38850a2de01c5de94e355cb3213e01bb86677e5e96413939e4c86fb5f58b1cc3441f82cb906f39f71ea662b5cbb74bacc5a0fbf7478b31cc29e54446f70959c54323a287f8be0638689eb6e1be6c16de18a362f7e50460df20d0a1427cfe566c862ffdd5719f4f27acacddd961b2ffe9ffcd27c2f75352cd5a290b4affc0bf03bd92d946a379713f6b5f2c0f6e22633b0baccefae6ee7421464ce6c307f6d0353a0ad95df6d3190a251435f62c30ed6b9cc0dd024c3c316565cad83d2e17566b8be6828df432a2f25a6a80103474fad65387c67b8fd337244901343bca989e3133b45959242eab928bc0af001f55181590800fb93a39d1c850ae9f2175f13400c202b231ff1d9f5529c4f7283567c19404483d5dc3d6bddc2d218d90a8b7a464a74041bafd860ad4c4d61d0b1f0393fc0f2ec3ebc54047da3ee8740beb626bd763fb7c56980e5a7bdd72652b44eeeb9811c237c5b6fd0c4cf681d6e5a677f6d378a2c697670d2ac4e43883fb4f505502cc90fd7e016377aef48c4ad0727ed1d365c4b4ffd308d84a7986ee1d86fe4cc69029a9973d1a166aa946343ed7dd8971bbdfcbf274980f76fdebf7e4942f5f5dfceab083e297651a956e17933f1a8a18db527001c4211cd4e1d655f2ff99e9337352b6e66a51e0394952897020bc504344db45b03a3aacdee5e47ccb119496d193f001116ce0244125a1fe581549788b98c8b1804f5b2c1f4a84d0cf72c5d4c2bf5133a742a88dccbacaeabd95387f4c47b947f49bf916f0b045f692aba42f8185e4c30a84319926893bd303190fb12fc20b8d7f789ae9fd979652972495f398682933ddba11e2f911734bdb2945ca80a85dd6a39dc731b060f4795f6631ed71e6ad1a735cafd7ced41fb9a83e6137f95b2ec7e353e47aa3bceedf5df8fe699871decb7dd48203e2518fb0fce0f865f46adce5c133a921320bf40915456204869a3ceb5fca3ed40e0a41a64b8951f0fc580694cfc55bd1f5ce926b07e3e32ac6e055de9b961ce49c7ee41e06b024559b933a79518192e969855889c85d1858d3c4a839a3d0c2a2082fd59cc0fdd5f03cbcc6f818e0d4e407b094f9b909781b37b7a2712af2b688eb8da4870cbafd7d6a255a85687b985e4ae0f61f6c7178ee6d49e31973847f25b118bf814c8ff149ae7d53c5d2aa63c4cd86fa8f553d915edeebd887200e72f4f371a4f002e557e17415512dea05bd93ea22f0bdf5c657c9173df16eb2e938731cf8e377b24397d1459dc12211060c683b35971fe09442d9a080cc249ebd462cb84f097cfd234795bd672246da33e69e3b5f4c49883fd8ed0574d74d65e3028e3fb47564261cddab2611f300c807c2c254d09ebf6f21809fa08a914d8b0314142df9b5e1df98d08e2a2ec1e44d1a27613259fce607d1d05e2c3298b98085f16b0d6f596a8c1fb6cb340e0d65ebb39ae73e5be551d4c95ea4e2fd4bf5e8f410df5885ce62ae29f6cece40441a168c83e0e356e687788081f07f4b299726c5f8fd89fd836ed84017157355e455700d78dacbbb8efb459fc0ed5bbcb011bc8410522c0716e37cdaae4badcf9cbc6aaee031522a3d21de6fb1e7f2c28e0a2cb70d59b95307751e8212bde80dabc388f9608101038f9fa588cb7cfeed01c4f9c73690bc61c3783fbeeb08bca0bfef3d75604662e7e4c93d638418baacb9f6a64d2273afb3d97142f9ad98861937b40a9b75fdf237db42f8985247c07224f3b4a1679f0db9c7f4eabac109fef7a19662d408143973d171899fdc96aaedc160a77c6c6f40e40d87798acbc9619c8c2af8d79d35a34c75f942d28961d4601db1e136a750f3d3288d81d22443689865d61eeeabbec9f2272ec6d8df45c789a86c0458af009107879b963971247e7bcd2c57b1ca2c083563a6882b44cf0ecadfe3835af9eaa2e1c916291938d91da7009235996f19f866c9d4f942504a249452dce0c60e7b94025ba1ad09c1ddb0bace9c3b3e00e51506b85ebca6986383767facaf2263300ed9cdb2a483c2aef22775cecaf83639e8a5bd9824d07387a601d4d730e88ea45de88dcda20fa1c93c6da4ae77506564edc44815d45878432696e0e12890e7451baf1a472815e5c909fdb99fd2ef15128ed2f64c4d972d126fd6325ef8a403aec2ae01d3a92f150ae5685327302cdbbf566236cfe314e861fc2027b523aa7a2ddd65b2e7a7c3a61b493ddfd941820fc7dee29805576a60de56055f683c1ca15ee656dbf7966c2f76cfdede2a799757c882e48880fd0ffcd4080647fc94631fce5801bb980790f7b9c3dbccaf3ac51a2dece886d7566e32ef8ca35ff6ef165bd8bce6f02d7dcab530dfb529bc17a3ed84675f115cf61d998d4e5d35cb8ebedc2a8f87083c6b81ee987deb83f592bc3868acb8c6965e86a739e7a4380f05c517104a526249535eea4d28ef59c03e66912707a60517e241c271c308e515d6c1a34667e9ace8b7aa5ebb5b4119c07d3b6e5c12775b6643d7a1c17b0bdb94941cf72982ad367f1b0ca28f9',
+ '383242c709fe5f2ce782bf8c83b645d171f2bd238abc655d8fdfacbd0fbd39df8ae52fecd6e8b00fc269a028fa74abc52a11894e6618807fca462b1b5d917bdf3bb9fbb5f420582b2fdb20239309cacce763f7d17715f7d0bacd8f0d3311f96895d52d8c2a4d5f6a7500c9e6171eaacfef138f15855cd136a9995ffa57e4bd60de624dd84117ceb2deff22d74d5a54b78b47d9825894169bddd5234a92b3cfb15f87e4010228acedb000b35fff66cf6a03285e81b766cfe69fa76464ac263541606d796f32250102342d05e7f3e923d29fdda5786c7a03ff3737a8b26de4f9fa293b94899cb9d5d9b2ac9fd5f28c59d6a78e36d03d77baceedae7a9b9d9623c2011abdb9078a315a72a50992c4f7785d62659af2f306fc3a09345f8703e3b98332327d673a401c6dbb41cc8731d188511987584456ced22dd2f0e1de6874c52402aa5bf9fe849ffad7a76f1b01c29299141ff8302d78438f910b870994f04e8dbaabe0d81bfec1e90c017ab5fb749c1d9b53031d42ab58468fadd96e4f005da6a15c926c59558a22a37476bfe98cb1c5f64b00735b10183b11fc6076614cf95701e6fc1d8031028de32aeaa091b5d6796c307799414e8b566223a389917b2a882070a354573c3213164b5ec0bb951521462af0f9bc0eb980c9482b10a836f82148231177a71b219a82fe5a8731d475a5cd60f4fa93f8ab9f8d947e716f246c0abf27cdf03879d70b716c675dba1bffed46fb0a0490b3689cf72e2616abee8d2bcda35f25d2fc5d4f29bd0caa1d12b9e1fc22bb7f79e8f8604f3eab65273b646cbcbf50803d4cba4cf318d2d62360ad6a36fe8ed3173e64d2ddee93c8aab4f7b6d2a526674012f6ec16a5404994ade36e3bb70b69325eb3d9e86468a6fb0150ef597a6c44a5f61a16dc8ede6b38a361d65474baa792efed5fbac8b167e3c977019769a77e329f2db28bf834a5d6e8318bc95d24f6fe9a1b4b9943f7722ab472d2d597617db0b637a76c0dcb5d38245b74e29cd0bf3f074385cefdc131986c4b4c5a2f21a9e6e241dfc7f52afc2400e578e75646681ddd70f4a01d970bf4960a567057706a9ecc5141e4d8d9eb6323d9811fb60f5b60c5a78259cb016808ddb5d75d37d5289e1c72b50add6191bd373e76d3e1b2fed066f216403188b09ae656b96af9d84baf79a923822c4955f9e11d3e4b02b7bb356958989c74b34c735cf4e3dfc2013b998b007395ee19a1e1cb7dc3cf3fa7f95675e2f1b6bf0ba25be5983d04bdd96024fb7e8d884b5adc3b9d66eca7c0091ffc339607d638171b1a2949af200fe72318712b5aa66a936dd0fee1a11aae6597ef4a7ec343075f1f77d20f217de3b3ea3c9410c036744cbe6897f4ca713144c8f763a20d47556b173b85f27b615fc61e590d34a87f900d36cb10aa50f5702c1adc2608ce284ac4692eecfba515aba7283783a0fbcae75f3dc0100819eb94a8f5653abaec2f0df17f18af3187e1f0de6e9e9f5a9f5fa1c93b103f180e9ec43dc15c48c051a4c77ac0c1769d0a0c56f45a56096c7e86e5d4988347e117552975e687f720e3cf9fe893f1e84514e00470532668dd7f87db06bde1cd6b1d57ebd7ccaef0e48cf7bec1626fad338ea323dac0d865b689a9acea10f27cbf06ed31ebdc9bdb1433664b9094046e6f619edabb0b32a7fe86368005fa7ef9e4bc5f233a7c155fb6c0626fda9178d3ff7319529a9bfdd7bd5d747ee1e44cefe225f5eb4b15e324d41a345229c09383edae5cb2ffd8009cfcf6accf05342504c22bf7aea610ced3752b241b048b1c2741f9ae23722a059fc239259af954d1e08bb5ac97d4d39e14a2da79f3f459dd66013b59cd7cf9d287170e290846aa182c45aa5dcb5cc81b8e620f7d0180939ce9375ea3d7a4ad31fd035dfe4173a0c290f8f45275c6560ceabdb2766e309f2257ea49d56a73aee7a98f0eed6c089c96b3ad7ad3bb9be43bcfbbacad618ce6375923e436ad7065bf32c2093eb28d085d3e6c2428c562dc6ee665e36a031dd0a297e91710c923388041a536393a8b4bdfd83bda98bca3a56ed7c240f57b6ac62db844caa9e51490f17d3e7d262d8acde42a24846cc8e7a70349daab95f2fb2e9e653ce54b2accd6dc8f97c74cb210f634dc2e0aed10b44af4e4b60d93905971be45da503cc0d270071eb8faf4f2a72e9695615460bd95f60b515d4c377c0bf8550125f4c4ceaec83ad3a7006614d6ddd4fdc64b10f60f130e38d752c9df992a2b4026b72d7ce9443f566ebfea41266bb4bd64d544e4ac09c6402d0591e08c6e07abe382bdf40a4edd4e1521c8a11d40ff7d44db43aff340fb12664fd7a86b2eb3e9663ebe5b994ddb63a20d475b45c47ce46c46567e6c2175568a17e25ebed1f5a3b7d176dc1ea9023e1f6ab0982660f59be6fcc579a012fbb3a245fb2b0ebf9681dc252e9c22c91a8793224b7f467a304abae7d8ca167c57d1b5c06a37e15f5e2adf202dc62d17ebe5071c60392f7cf798eeeed79656c84f59cb7277a9c21b1447c7acbd80c5fa3c01824037ed69cc102d8cf80908e95cacf3ec426aaa365a827f9db024f274dad6830c7618c47ad443b29befb74556a2354621188a61c7856e7b6813ab46c1208212ada64ae6ecfa5acf24ba29782500b4fb71dc20f7fc02a1e330bf9aa1343206566eb8167a47a81b2b2e41a7c7dfe0efb9e57674935d3ae35efe9b392d56792af95694c4a81145506fc16c795a0ba9b02984cfce5e7395fb94d98fcf12ae5db8a06e239c9ad439bf42e523e65a31c3bdf356cd7680c57cb32ec983a678c54776f5bd4be57517eb314da34e37efda96debe6359b320dc55d1d4d65f0486219d2ea04bf5e96463c56d3802d5b5408d8add32b45ccf663e891e2d090b32644cc8a649200aee8d3f2e3daa0ba0a576d20781f850bc107b758162e26970783bce31a79745703d18338e674bc59752b8317591b83f63bf8709a4659afe741d332d3ff832c1111e2ec74eb4c438a3032f333cd6198a3723b18059eefed1006b73f35963c39cd3d8f784d4ecbd6caaff035fc418c438228622640ac7b6e9fda824a7e9aef2dea0b59af189d7dd6a958f5b3d751e61510b2e023c1eb6694f511d6dd256a266905ffb3f97d53ccd394dfb5f56b8b297ded9647891fd84bf09e61277fb0807c8baf8f310fc21e535e1b98b3931f39a0ee57670acea0ff96221a2cf69aa67a5bf6252e532aad398bb6bc0870e57909f6f71c99ac7cfbbdcf79e6f9b6c68db43f4925719d029551e0ade4c36094ef5896287ba2af1c86cf7298934c0a8bbccab0e51eed610ff0d3fc84244e14fa08c208e313167515e87109de9d984442ea2a3b6a8ff661ab665c29e9f8fd00bd4bb2c9c76169b101875f0fec645306946c5f4949d730f17d6c37133fe174b6373ec74335f510c557f9e5ff229620b3e8d9d664f3b301a2fe591123066c39a7f0486c1fcf2cb0249196a242119175fec8a93c090871fcf896d366e3ce07b04880ff1db9f396ac714714209359e4c729ac50dccfe8b28754ef51a4d007327d2a61d948ac33c17a2dd0c8cd4d3c0e98e71c77450424e3455a506a5772327b04d00b5d9961002bbdacc74b14ea588d7f99917311503d829b8b7273fb34e04fcbabf5f27c630933cb80b301a3f53fdfcfb393daae3ea32f1e4ace050ca2913f4640aa3e7e3c8f78484bfc82e6f852741de79c249819f637222abb940855b5b80920a0a7fb58336798613c454a5e20f8ee8822d75b9c97396b9dc3b77aa8de4898be71b5804065905052dadf6ab12bccc637c069551106b43f368ed5e0166b7f598c85fda98fc680f4b350b7b47be36e1958fd613121e52631677575b548fdbae01d55c6d390b697e9e54644b428e86b7c7e12356c49830dd6b3002d769af589a0e389c7aaedb663c47b142ce6329b335409d78c62f290d993abc753b096f37a30716a767c01566308d762c6c7438c5424ae95acb1a77f27fcb4338edfc777fb0339a039e37617242bac8ab8d3b62c5c82bed53cd4f2ae67765ecd4570a6e38a8dbe93a85db66915a15d146998250baae2cd3ea3494ebf26951dfd0dffbfd6b75472ed48673cdcb60e5b985f80fa9acdc95c0a868b2621d3dd845b4ef96cb1ffebf8f5708c93d283c73a8f012aa16a439aede13d171366fdb404609eea4815c2b8b344d73a35fb1d707c5104f1d3fa8afbe55b5d8980ff02bd1095644edc62ae4f2463d2ecadb6d17e8386c182fcbc3250f4d16e3f197a916d5b72358394392113dedb0a3065865e5602a8cd3a763fa84e7edbc5c4273a1829277f994509f9b9ab5502d391e7e9f2ab5c3f9ea4eae57b28f5d31a9544ee05951725e5ffa834e679f983c58dcf725cc302a3ac3ec55e1984fc6fd34efce6f815acfdd21fe97b16146ec65680668ffb51988d7c849ffa01e6e50a663da9b55e4f5b7fb432582cf6ef17531d1657c33cbb804595f2c559d2d3622b6a0df5e9a686a52422b37edad77e75b27fcc1d9cbf854c747f25efedfabed65b552c4bf47f700c73942fc7f556571c5d04fe227ce2237f829e8a8a36e82dc4029e052656378013f68f03be1ce1ed7dbb2338f0f4533a7c088a9d0ec53984bdc9cb451f9f6d2b3e1589ebeba208c61c7571192383712ed47ea9d9e8095d782609535892209ef5fd690b24bb3549657ae474fb14caca751b4da2cdc083c25c8f59dcbc289a2b64c459896ab74702300bb0857b5f0adda1a2fddbe502b516c67c33bdec3d6cc0fc457f9b0a6a47f1da513248f65ce409392e27dbbc392db93a5f1f7d655b08ce20d343a6a03eb866b8ee123618b8e70bafff3418bfee5e282cba8591c40bfec177003e32b8cf38ff5034b8b34edd84237aa8ab196c6cb6f21200fea164cacf39735ed7d5a0761a1a34c79e66f5552c0f2dbd05644e6ec8858ee4f312d401fa948a4198f613de0c55db094bb7c89a7f1d4daecb7fe24f1380f7b8fb6c9e6bf171305afa1a7f616020b78c493c2517dddc3ee075d2a4a82842e11f80253043544e09fd93b94f9ab6095ed30f5a9777d8da86049ce321e46769df6b29c3163283998bbbde6901048e7e354016999c14e086c78d9947c69e6154472e40ccdcb41fc21a18329030195a0ddf85e77faf99856f57ee03772f209690bcfb6db8e0428976599548d559539926c2070a834e505802dba853d7a83587bdb535190dbd584114beb5899ee94ddc576135f83af4e3b8dfb74f130fee27b529a48ddb31e07fe73badeb6d537c62842e41a5291d4fbe28546f34b9765d819f632f481cdbe623dc49cbb97c996f3c3109f7d715809b6a371f880bcfb172dda7089a0666523aeea0cd8ca22fe74e255378e84e562b7452658f8636ce37ca968c78993b403b5b3ac54565380fb3a5c87c09877d637477112422ee482d7fc1468031917922fdc392ca3fe9ed8484e2901b2a79d6b5d1f020f37824b275fff35852d2072287200071101b8f3536e1a116a15a23fcd5ea9c0c740dcdf8204edd5654c88ed9f5389e604766c9919f404dc6af270a524c24c73df6424e9bc4d2ebcb06838d01f5bdf9ead0b02d951627651ab50fb17970f6fe202ac42b1fcc32ab20f8a1863cf106af7b3c762fb2341d739d2372add4ecf7cd6d61e1e7f6bec497f29b810eed8fc92b9bfb37447b8178f5c8aafe53e7289da1703c5a19b3153f4eaa8fc08b862a7c0ab78d52104386f068279c114832bc6f16d32a6b14c757d91bd315ee80a94985a9687374f7ccbcea3734774a0f5a00d29a00bcb37dc5ff48abe6fe5982c9657ca4293e1e7f597bed0f69dd16fd9fae6ea77353b1c91183f45b607990066916c767745d9d2b8c7c6f5d523de6a7a60d99cbb59fe46b4c8e62c6ca4820900ad60c8fd4529f60d816f78d680a5791ffb6fa7341e1d9f8c9671a5aaeca9994111b9269b3ad93d3bed3fc2c25c2e850ff32f73aa2d9f0e63ab691a3687159972e602fa1bccef8e8c35c03b60617f74936fa268e52d8c7a7f2f56f2d91ecef2db53c0ab43a475d0467e7a4b7a35a230f3974eefacc7eccd2949be955b59dd8ac4817da1dc6a72edb2f3f45bd6809f9f7794bd6ec9a3c8eea9212b6b84df494b7597c044adc6efcc18b9b6d13eeb7cab678e774f026827c547e024dc1c591a1c35be12fc805ef355c0fb4817771d433b0aac02f820be123a4bae3250ea6e59e44e1efa311bdd8670f1df33934cbffc36a917e8d3eb4f9035fcb2db2fb7c70d8d06de004b47e9b005f58dfeaf8479ad868cf7b1462ac0a99ea415aae14b0b3efea627acb2cc2a7afc122e31d2e6f26012fb73e3bba7bc655d89fe24ce6ee3f41f752087ce724aeb3d91ea54633cd31cc23eb3089928e9cd5af396d35ee8f738d8bdf2180801ee0cb1bae8f0cc4cc3ea7e9ce0a74876efe87e2c053efa80ee1111c4c4e7c640c0e33ed4518c74df6bd12e5f2249305977bfaf7b72ed08007188b2d4bf7d71f8687bcd294cb1c3bc73d9bacda075b9982958d6270569f22e27a4a3330a6172f18ed947ff02e621ad820a0a2f83b34bfdacbddc79e8391ed2b96dcc29cf1b2ff3d907929f9bb3f678406d07ccf28b4e4ea9f6a7b940e5f6b6ceeb1600333412c6f10c98513ea0aae6570997ee16862a54c709212f38f6e0a10f2767fe603382317ff03f5c1336a5bf6ce6a3db172b47d7af003122f5f3465a2328a96d7ea0e7fe2bbb710a43fc50cb2a0d14dc1c030d9f08e2bedca2648faab4f6c293b404a8f39c7615a1f67b11d13685d0394d95e5737bb8b2a365d12359679a9cb92be62fd97b29136a5339519b3b56c13ad369351cb089f4c066c36a2fe61b1d260bcee3776fda53ad83c079efdf89ce27d60766634e5d6c7bcec2ccf4d9812f247b442c972193bcb2ae98ef96ca25de477df8e10efe3d021bc554b16fe7d5b9f9a3d172ff385b38c0fa471d58a532dbe3f1c30de5672f9eea72038169b91ea2eeacfc1d785d3baf20925769fc18b9ac435af051b2323e282efe56a4aaf764d44bbe4e95ca3839071b9c503d4ccac039afdf07173b066f883091d582fa48af3658004e43bd706029f741b8000e64526a6fa891c6498ccca638510983215408b05d9b848d19aeb5fdfca8191a0b8d7427cade16c5a46b5c6a8d697910e6c4762aa1b778dca599426b74fafaf30bee30580aa91c7e144c27bd79ffae8f1240028c6d7ab3992ada0e5ca55ee4f3d62f8de575302d5861d73685423c2e6a6d6fb3be090fbc2a701821b6d8fd5e8233f794b6549cd0bb52b390ac31478307bffa91a9bd9c1bf93ffc846356fef008ebee4bb3ee148e0fb1893d188e4934d0d088a433d14a596c5f2e3e49648a22edc6bdbcc58dc1edbd440046b3a169ca2b68c2f5458c40fded975557524dc97d998c0cefd277cb772bd4c1b263b1d0cc824e508bc837a78fe3b119d86557e288740582eac3f559b4c22873891208a5c23c4bd96ea21aa697b67324c869ccfecbe7f9c8b7814f932bea0abfd4a7ec1135c12705a7bd7d669ecda61b2f48f244cf582f865ef3cda2640c404d9a0aa63cac79aa7e3dffa80e2b9212a915e912dd1b307063e500b7aee78e93c4e3237e4dafcc9be93852e2c7c76c7e74833473f038d88407569254de3ddacbcdb7dabb6cc622c4f1a19d75b9f9c3b32480115fa6acb6331bb890ed5bed56b00f1f17a7c37ae3eb3c7fc8a70b49007a6215681c270134454714e1ca4d7f6c093322c288775277d972dabac1e63f8999d64921f39abeb98132716f33db7b83a0e0c9c3a7b3d746563cba5d0a7164c7d82724242c276cacb085d721702d6a023bbf1b024c9d8fb92a422898ccd53f2201fdae590ff892779ae74fdfc86cd453a3772067bf5d04361c2b2b534d395903da02f0a9e432b8810701df9185c03fdda0b1e0db471cbe26f59fcc76d7c638d02ddcf1faf2006732bf7b921fed503508fa1564442d0244f27d4812eab0a366f3c033b936828825f289fc6d7249453d3c784ab38cba361d007ccb059520fa6bd25bb05143dcf27d292f2c7a70e0c6e0e1e3f9d59933e1d4babad3ad6071f0cdd7c8fac00b4867f43f77b4c002fcfca45f03c9d355e32aee87fe061333e9af1668ceba0e740e0f149c2a3c4711e30f141fa063b4a6113af5ce120c3ef7d25fcdef34984468751164a9106b1883de26b587826e8076043871f9bc8e369272277bd3c33b69cec6956cccf0ea3a423569110070933fac054de86fc53404ee1230a77c434c858d4ac5b56e9360b5343001aff3347afa305ca1e498f5a5aff2f9a2d9527c72a18f46c7c6f8c2769243171617c694cab9ea515becdc138a3f8571a49abd3556c3bb05c327d043d4d85631cfd2e3592c82c22ca489a3e98abd91c05e702538a0f6f9cee26160de218f54c75567b04b475fa034a341553d4e30fd2ff9e6f24bf73f31e84bfb0f5d06bf2721d05e9731c4576cb2819f5ad87da0b1069d818c3703a84b9c287d44862a71b6876740fe',
+ '9d1898f04e38da85c27e6317c19ee81c3bdc6b1ae72102622a4edbfa3ad07ddb8375505c4a7d60ef90f55e0b3940e8696df709733ff304fa8ca74d2726b3d9b432b7975bcf653f8ff9db4a6bac2f41e84c4b3b5244fba2fdee443534b3f85372738776d52618fecfc4d8301f63927eff9f81089f3f6264d316cc9a0826c4737d0c8df4b098bd2516bb96170ea692241830407942784fd2e4cce8858dca42c23dc57019d56b7d3ccba4ba0722bd57bd9ac531952437eb7598da40fdaae697c71b68d8ed2ce77fcc5848ef0842ef08d1ed27fb642bc45dd458077b0a47c1ee0a7dbb6f799d56ec5c246fdb235685cee6791e47ee4066ce778a1a42b44da46d14cc88dac411feeb7597e0265f47c7d086a572bd3c4c6766e798dc3c04a2d73c6c7195c4d66e29a59e196579c5fbfd3738dbea0455350d4cabe81512012fa2849ad2aad3b89e1d415f12c47c5b5b6f2a85834fc541e5a1a94be48c6bb4dc4d6d275991af718722e840eac6d62b4f65d2f30369a709426aa450f20bf023921f1e9a6d1101091382c2ca09332f3dc026560cc40053b436b266417c5849583761c07b75f171892983f684d8d3319794fbdf582edc704aa8bf17a6e93c1d5bb45c7a53dbfaa6f8b06fadf7bd6e8243b52c955eaac4a7d47fdbdd08a17f1432f25575a906f4495e928c0ea921cbca4909019fa69e82a058a54ce3ed0ce9d7e7d897d8055ea701c4b62342246db3b2af7ac126b873dc02e1015a4fe092420e824d69ccef4bda7731047b70f007eee17fefb6511d929b76747b4f4a669b5161093b1279f0355753ad64dec193594401c14f26495c6187a31bfb71fb098fdf768689db068f84e43c40be925d97c97db77b845a35fd4a6732dd690a8b50d6b4f8001f0c9c55a04adaf3fae06b84c160adb7759a3e88b404cac3ad6021c3d8988d80e5ed62c9f250d6cd001013e0a8b68b7c0a2e8c86072714d3b0bbe6ebfd53dd0dc3c58173e0c0d8d6b86a7f35e647f8d32b5d467faa96b7175865024faeac91afa1de20dbd36187170b36d40ae3db9dc2c07095f9071a5c978ea59c78516d516e677e688a34fa8c97dfbb3de8063a2254b1afa07e857aab5a3bc2dcec505cc453fdee810769548e5f1e42e056b92b2e8ee66290958c804b684505eb35114293654b7692dadc373675ae13e5dc6478697a48d18cc784f2e5c996b6f1dbfee666ad395dc38cf36a2ce3797675026de77d1fbd44bd9ad5992086141d15647d12e331b0ed3ea418b717b165b3b8513d410f852e024a98e83da5a5a981805af88cb5fb966c28aab2e4a0e55c11d5503c4dcab584545c4923a61b313c2c5a44d61d8213d523ac2629ba6e8945d9f488d2d553b6a5821b34ef9b2b2fb464caab7f8df37f535aefa1e4012aa407543f7f689f55907bd4aee1b5e57da9fb72f8165ba4af49fa591ca34d817b3f8cc7dcbf6475764ced913ed8db4cb8a6f89e0d0dd22a5f79b06759b2cb010a61bb7df3d0301d5ef1e203f2a2cb98852f932f31184ce6aa155fcdce58c64b7e127cbdad38325fb6874470f3c6eb918b4bb46f8bc031a13927eed4a51ca625805ab7ce3181d4052617fa2168cca5ff730243a4448ce923b3b645c10386d458b84254f9a8327dd555a7ec5e7a3d60a9e45c28178305dc34c1cb4cdf121fb6accdd13c863ad49499ec42026f519f835887624b1071b1729c0b6deb75fa6eb5e8681055bd0eee831792ed249b147d78d4041b95d6361a142238a40acae3fc3ad6300588e54d08b118f0b23a2bec5ca6e50290ecb3f9c82890f0789127f44fab3ccebabe481eab8663ae982c6700c6755329c73aede24218acddf268d455f171e3e937dd2caa5d6ac273a7e29779424de522ca65cd1b104a3fa51977192f6af5932a82ceda19a9c5fbe7e844037e59966495a1569bc9ba2810f0f6a73eaa409e1338a57a9a9214ffd7b3623bf33891b8992952f69f17c818e9678fde8aedfdd32dbda8c8b43d28801e7f1eadaece751b4987e72c212bc38490b1ee05462128fea75e919f6f436cb198f222847d698a283f5767df682d33d3ce771b05bb6d4a864ac56ae914cc68f80cb578a6a8315a454240dbf9bd469e6d6b611cc42efaead3de9a3b8debffa5be447028fd774d6cdb19ea4a61689d8eba2f16fb2ceaef904d2b840a733c05aa6c06ca386f31e648538dfebcffb15c8a22e23fc0243675c55cf82ce1834f06ff681b6bb2e62023771366243abbdfa81b0d4b83719359b40f680f3ac7a56b292d1c4bfc9d498a2d80856c03ca7d3cacc7e3338b18639cd3f9d93655c5c1da96bbee5d250280b82beb106644772d0e8d190c62ffbc7b47fb08173625e1bfe27631481b8de872f246411b1e8e46b39e7696f0a08666c3a253c68ad7532587ccf118914bdd570980a608105a8a41f7348dc8f7b5c81d23f404ba9ae0879901e02ef731b6bc582ca972cb56e3e06fe218fa368a686ee98387356cb01b6844166556569024d3f1c3b6d30f558137d85a91e6680f8220d2cba10f65324e9f2eaca590bd165dca2cb7eff05e75fb378548e879e7f0cc85e1e38bba2c8a42d45face605b52b284811ee9cf23f1e1b897d9566da3a930b461db38d5d491ebfaee0ff71dcc5374ef5a75105003bb8a7d5c8275032e9620a0a8f24ee2045588dd5b88b8e3e76a2987af6c87243d9ab68c26fe8f1a87dc3907a3d1cf82ca79f73f2ef3a84534fd4cb7f063c2ae2a15f26f979bf90657d20643e3184f1a9f75a3aad8ef39d42d835b2abe09376061b3da922ee93749071e04ffd734522bfbef3aaad9b9d1f34992e14a78bb79ed7d0abb8e4d74ee652e16b195f0945d39482d18b9b212253501b25b81a0f8eea7c47121de73bd72ed356298a0efd6e4c53ce5ca51e256981bfe58367ad7502a11e08db9ed42216943a588269af57a7d42227fcc0dfb15af1a87fb1e908c4fa0de49c6c045394f360b06dde80ed1dd7b4291719a385ccddea34721506d2045d74f78a2f160b9a56d95c1fa5956d59e83592251b17b97fdab68b451986b43d151f7e5a8a9ea53b274867f53f71da12c19d82da6ae423d1399bd480243055780956a295e762c8804ef5f87714dcda514a3423bc6ed26acae2e238ac9dcd5ebd21618bc2ad2c1d6fb328382e8c9e151d6b449d5590a83772bed2de50ee2576124587606944c24c133f294ea1107e357e0c13182d363031d2b3b5eef47e0046815d114a1214ecfc71d83f63590645df7c15eabbcaca3001f1aad19220b5267511514770468574d59367b49fdcd8bbbd206e11aeb6b2714ed78c70f05dfb5facca0971fb8cff218180d5ce29b4eca8777100d01a7958bbc18d3fd83032b87293b56ed7126deabaa54008d62a68acd4b577b16f279922d6021aad517b285428d1d966c1de70ae08ecdcea13c9817f071e3b6a35fca07a89b886bc25f9c637490f3fda76861db3d3fb5b62cf2f86cc085ac4146cc216c79d8bda569ad194ca9df4edb33f33fc61e27dc5575083fffda0121b955aa08170db251d62fa2c1a73eb29edd7640d9621ff1822b3e0ee757997ee46d747bf6bdf082b57c88b31e19bfd5547302d3b7259f0747b5dc5986fa8b5954fc07d465b7bef489070960970ab992101a5e1e618737e3ad73d47a875f2c1b03d3a435edaac5722d14262db4f098835251dca3511d571123f3bb0470fbf85e6192ce02f6fda0761212639d0071f9138ba822e51e4e991a3ba3f469cc677c7e0ea7d9de0a26dc8ae890461101f547ed3c9bb56611915a696503153d021825045b817c29afbcd62b110c423c21f0f16ad59b08a8e39c3779209f91d0aa948e8be8ce1978403639d11be4ec70e8fba206f72491200cb5acd40ee7fed73e43256f36dc363c7419541769b8a951df8bc65c01c6e35de5742704806ac0a335ca6648b63a5708a3dcc158ab060d517e27dad4960073c5065e228515a66be71990dd82f766f04f68071e2f204b9ce24d365bfb145fc6f807ea4bd03f0964f5521d07b86af09683391ea70599f7bc96a55dc65a1d435169329eff608d22506087e551539155ce468f8a187658a900e14ef4a65c1149a79b4ef2c9c0508f92bb238066eefb04ebfbd3efcefbeccdee5482a1788b80d14e00957c177a598fc067ad54a4d5189c8435bea656f0d6d4f962e8cab962fc78992bae9174f8d8c14de89df887c06a7b3b66a8443d1fa76ca68f09f6e57cd4be3d8f02d96ec68eb2bfd07de2ac1b713f5611195fedb2cee36a5b3ebc9933ba008fad3aca616dcbe28a91b5897e50cd3788c79a4fe564cdd7d93a2f7221120cee2408aedb094910cf32bedd737b0acf1227fca39aa09014c867ae24be29a25de57f13e781a2f31dd74cba6e272e94074dd812bdc6cbef44139a49e6f72f6f2d7515716d64eaf613a93d06e02b05a6f6590dbc416ab3bcdc77e58fcec38ed2ec1b7b83b8eb2dccb2938846cbd59c9e2cb23d6be6ed04933e33ddd24489e4681a4715dd428b07d17b54b2dc99ed5ff41ae7db8416d41b0ce9f5e7c3f0e0bcb9589668e0a2daf5cede3b414ac2a8cc4331788c9749967384702c97d75c3e7420f7780930524173ff3bd5b813eebe7df600c2b53807daaa946146728edf199749a77a6b3ec954fefd44a28bba7684c1a71984e9c8d9e73ca72de1ec4d01f8b29dc90c037e708c1343692040c0e29243b0ec0d9edc28628647467d79b45d624297dcab7672006d4d5c29ed5ba9bb7d80bde8eb58e47bc333ff3b87bf3759dcc3b262d718bbc16f09c1f75850e7899ce528a09c8c745c8fa2398d7f01588fceb297fc2d7ebe6c17d4ff51ffa50ca5770d8b939fbe0e433e7d80bce2049a57be9b92f90d1cfc48ee3b7eea51ec8bc7a2564142737204b836025f3a5f1d7f4a32fb4df9f487e7e058b9cb00ed7be738954c043da62d1a343cfd4f9624d069efb23e136f822413daedbc6e3620f791a0c67b2ed5a653913f89babc40f1b1fcb0c2e0adda2496029123cfc30aaad42d78bc503bab029a0c42c2c73fdb3436aa25cb9f57ba5a32369b817083fed9961d28fd67b54e39550dd66eaf34b57ac2f4c752a6bb90a1ad12628d2b0b733bb1e359f02e160fb9090872f3df5570240b6f1dfea343fd895b487ebd7bd4084fbc02544b1ff890104a7ea0e8d3fc8bce646b7206218b417fb12b5888cf684e94191cc054b9b74fcd8de2d8b429b478d8629c9c2f9dfc0d03634d7875b25286975d7526a387eccf4f47d792ec4cf7c7e09a54d4d16500c0a2d621671ab10d70d7fdbdffbe07037f240ccf5807ba30e9655036c47233540cdc8e49acde38037dc47e59941ac385dfca412ca08d0e1a6de538b4d3a87540421ef018a236d3df0deb53dabc302a0e81d08991f4e294feb5ceb8419adaa75fe0f8a020efcb00fab1bb22a3b094b6e7d5a54a71ddbefb7861a0638f729f462d99e812f50de14be109c24f5729cf06bc6bd7085eb368bdf1e208aed1035cde23bd7bcd075540111c668f5e77703575f6b4d447bb3e7638f5c7461ec8b599dbccdc0242da8e3dfd276870b4673c6ea121412b8d09a77e191e820717d911dccb936649f8e0f1516c7c702505a8d6e104b6815b5ceca6d6cac692ca4e74a0ac2a11ec8163ad2710eb962e0aa6dee8230d40f5d21bb6b516ba7879ee074dda7e73c2ff6727a1a7f306fee2903c5bd8ab473b2f2e6e0a4ac484beb800f6a737ca4510ff599960fc8e2b314e542ab230f03f1f9ddea859e056a603dd9181233125faa66cd75d6d8d38d4b7c1d4f67489885158b2517f281e439ae24b2e3b446f0810ddd87ed819e289a00cdd9124153ada2a911d2536e74d11dfc49125898d39f73431b29e8e188506f9aaf1f73816cd6c27332ed88429f557e1354015f0503a59c14c9be503cbfd276e1ccf95a6ebd9c4d3d977d9db72bf7c4bcec88e045fe8c63478f4b0cdb6a36169dc96eaf516b86abcde78a7a13404057bdb9adc39eebdb32a614da0406b205b69551865a72a11d44ebcb1d079e05b7a0de657af059f21b70f701722005bfc6c0920a4a431985e78161eb3c2ca2a3e405e995f74fbd3dd38403fd1c481fb8019b5c9cc1528e3f6ec8f6eef2d165424fefa4f32bc914f18bd892b1df649dc53fafbbb0f47018f83a82717d44de300881c7d9271e44a439ccd36cc5c35cc710066477eee98301d8d1189b3f3c3b4b2001f34ed9d8a9b73b1cdd58e0d018e5bee12257fdaa748f06bdf03cdde1d0feb057ddccb062e1931f065e1faa0f803ffa555124863f2c0bab86741cfe3b8591e10ceb1a54c3d3800e0fbe89cd877d1deaec59a630ae92bdeeb20b024b53434bdaa787026e0366c830c1665eee40f5c6b62da2a5a4018decf8cb1cb76b30f076c4b0184aa62b84b2aca3bc66b843d28387a094e9894007e1f059110407da7658c6af06bba3411485aa3d2969d097fa9c85ce98398871e7e5ffe251afc75abb39092db81d0e50fd8a5418162ee1445759d73e145f499e153e4df0448052b7a963b4b298388e5d2909d0bbe97e9153cf01a678722173ce7834010a524151f9271df0c40dd3cc727f4946ae0c2188cb4cde19051848ae0afa1144b9e3b86b291caee5ed7fb86f96e794df7ae5df6fb4b536596c709ca459821b3d835fae494e725fbab456ad20d24930c2a6de80ee17f2528d35e0e4fac3f99c15f32eec9370107898a1fb7b872d8d6619ba5dcbacfbea7adee9d2ea5b5045daf38eb837f097de41a63dd4ea4baf9c7e093fcc89526887f6740fad746d094f1e00de665a08ccc4010d0f4ac115605cc0bcbc92828766b00d762fd94bd8f5ffaf636a9e1b8416aa02f4a0c6ca8c49b6745ab9177ef4fe4d080cd2be37451c3ed745bac9440c7e788a8476aecb597192e10abec3454137472f607fc4ff5c87f2dcce57509b470b16e2e41b6b8d23e0d950f554fe9e151a84ca97be536dc43d040725c899e9dec56c523e1766d8939f71094302305318aded21dc17d34726465da073950ef578b46321b7f0067351b544541b51c12af3fa6a7c5513ac5629abe3efdf471689bee1e1997930b228042a3979a5c819ec4e09e4222a3946627673803b9265186b5853cf00ac5ed4bd540737bf0befa061d0e0415c84110933b4a61bceb4777e64ed12169f7703d3fbeb532870724ebf5022896b728b245e908c4d9cee6c05af3c25279cbe03a617aa6e16f3d2046edc82ec0c48ac66f9ab42a66feae4e29813bbaa994ba578cf08928858802ee9d661c0d56fc2513e195912a914eff83fb712a921700a9bfd070e7adf22b7cb490eb4d085bcc0ab3a0ad1c53e449271abeb14cd35b5c0e9bad4912c1b7b80f34b9f3f7aa5fb290083567a260c08bb994dbb81f08c6f57d8d8c1f96ee56cc3ec17106888dd32e7994084bbfcbc6752b64eafc1dacea6b6ae7f53ae09e5fc68ffd6e999c0d46be1be9a1dfe0ef56a4011d54f3c53a462c5b3d61418c5c2335774b0b339ec33adffb7b9a8aa2560186bf20b245b23b6ac6c31068b9f6924197893ccf4b0d2a10129cbc4ad2709a479bca018b58411ab8b936e3640acbfb5b7b3a35337653bc76d4743e3b5dc826a951b65238a20e72b0822b38fbca58d1a14f1ee6c01c2ee4cfc4167404733585a757187542c986be02a01483986f49cfe3818ba40dc2eb5dab3ff7f00eb93521b20a44fd42252666ff919755b26ffb4072c1250f74f1156169c6ad34e29643a569e9e05cbf4b89f837d50821e25309cddf7c5f8b8e3d49aaabe68ab508a0fd6b2f845f1612b31e0c2bf8fb6a90aee1e29a11bfaab1edd493e21e24e2e95faefd835835bbc4e24efd4c6bf5b255da0009333fb9df98b952b79cec10511d38e4c6f5d3f8a07e5fb95629ac6b7b9a7b00bc2b44c2acaaf640704cceb1821ca33b7207961c768791d9a14448e128e6e85075f2cf8e94514b3a786234acf850452f6938fd05a0791f2c691cbfdb6cb3d87bc11a4e6229341e8d1a8dcc571660951d26faa768b0db5e2e18237fdea99991ef28122fe1ddbbe6d4e12fe4348eb5f9a135dcf3aa2a26d55b28e9175f5200cb27057b128214614a8e66b91ae9a3d909381040804e6ed42b3025ee04c20871dabff3a564c78fcca03605c9eedb08324a6e30d5cbca017bb6499992c6cb3f7557167d21b52682468e4868c2be8d2e6a13a031fd44b184761d03fe87dbcf6973a6c70c807223ae776b51ea44387488e91b6a7e3769796a6ba60bcf3dc2430905605e1c422a5366c7dddf14bebb259a27b8498004c89625c507ad761508cab0931a2846d75c1a3dc05c4c72a2d514e4ae80b9e1f5e09c390ab8859dbe2dcad2b51ad1f6c075fcb5e94d268e8104c6fb05fb380e8b200036b51f00b0899fc7f1d408c7b68e168f41bb46f9b2e9c8b04f968e4080252546814cc1cb2917dd5690886a9600a09c2673aec0329a4daf655508b06fc1646ef3bb3a472191d964db214a96a96fa89576ce4c4f6dbf1d176aadb518125cb94b7c3725f5c0755ed4da4683339e4df690d4a41c5b077be8af14ac241be4bca46964a77874043e089be852dac7d1362afce4b78769ac5b20b507e2ee42336bb647316eaa388966872869e8a9a9deb2a6581b5b2601a8f765e7c8e47c019ad44f43570f43c',
+ 'a28331a966b0865892c2c76c124c50a04a6ecbc74c4101554f75f1205d391f684b5b2f15eeb1b2418cb740f69179c73983b422473838ee1e4790993fbce5bbb31020aa4c584f492dbc4a208c2b96b2b74d890a55b9af98ef696d3baf6ae49f67d818c9d40a52f0250ac38fb74869b8238f37ab7a3770a7cf9d5400f6d0fe728c8d8db5376e82512f5e69b4fe50bc828431042c3d1a41c4c7cb8c109e55fbdd2b16c595fdeaa61456a085b8e9ab55b7e21a39b627cbeb97dddb5e922f60a2874a5b0992ace888e19fb85fc200c1fc0045341d70bfb036c71bb512ae2f5bbc19f444a0d4cecfee5e148e3ffbccfb7b05b666fa838d32e9fd8941f08e28ad113a2eb9d482ea07a136bc0b6d8bd4bf996d3c98161619b9cee02e683f57a1be699302a2ebc589f8690f9f153099a0761de1e0b2bb52ecaeab191210493423f68ccb77e72ec4320a0d92c695d24db989d008a99d2f5f8d77494f3d22544b35bd428b9570e5a86da55766387499d0a65e7a8b9f3fba64847e702bb887a9c45f7b527b65255898c2310d33fd98ce4aef5fe311ca81a6895a2ae7548a2590c829988542eeefcebdba16f8a31eebb8e21df3d243334b39f896e27873bbe6507f1c7ca38939b4913edcbce05ca254a1c1b78110c9e186bdd6c010e93054b13310bf8f74f74c5ee744b18b8d0691bacf0f4573664adc18784e601b03325b6d7fa39a3abf3531d319f7c0ecc64af4078bf35030996e2debb385ff6b8e22db047d6236e34eeaf0fd6e7e914554d0d2221d955f2074dedbe6b5a6246852a7d95d75731af4e7bf8fc23002acff003f33f3cd1efaabbe42eef0c8d7587a176a5f60affeced3535c180ca5aa9a83903f1f62e3b6a9393e416ff332402209a41374f5722cdbea5a6892c2179fe238cc7a9f57a684f532bd8465d63c0b0a7dc24921040824c89fc38c06cccc080c857e95baba5fb165fe03b3d8812e5d983e39b46d75b70f1d5c586f7b120d0ea0d46c3b797357648205d875d0db506155b4d1fd6030c8156388dfbaf97b21d9278c5f12e26ad3c6d2b0047256cee93cc84751cc021e835d218a211489f1529029141fc200881effdf654e53714243197a6083c85c252f10dfcce626315ce65c2cd674f4d8b37f36318d80c02a1da41ef1652d9a752e155526b5f597fba22664ba39265074d43d944e91606088485573b7c018ea55227e557cad1810efac5ad15aa5fc7ddbd4a140c0d7b7dc93ab9e4154d70c5f05e7b0386c1c15391462caca9582c0241599f3620fc94cdb532ec6b04e14d1a18c67f4257b6ab5b972acbd78f13938ec2b0d7b24c1cee906d1ba17e72fde2e59f28891443300c03911737d02f8302d7e241780ac604d54051435d70f7e9cec2f4034d1be1b44fe60fa9d509132d06681089e4c2274b0567f24894f4fc4b8d3ca7d52fabbbb9f37d734147f4d2681ad9edf8c25af835eb71d0a9cc7d08899abd3b1ca55629c7a3245c7be515d5cacc87db2c8547b17bf3f86cd5887b952a73cf1e4842adc453bb853bc8510ea5cb780c5883a20adb73bb66275a3d633ab4a4ecd1f67c1513e4c91a91a50021baf0c9d1e6acad36dec3ae35b0b67fe6619eaa80e695d61e8101385eee9067119dc11e7325f60b4e53c248f17958b457926ef135dcb4e53c942fa5cca3191a30b6b3026a66fe40a3a3261823e1ef7f4955ac157624c20e01d5c67dde7fbd8e11ae4d02125a23e1e9753598479ab9352e13cc83cc4f4bc4e0ce7c4d1ea4ec3726ed058a1550156382229755d704647a986546d8a2ccb0ae5bd6a78007e333aa02eb7326ede93149f033b1bd4caf6fb3fab2a160841daf2ef596deb3249b125b1831fc5506961619d6311b4b32fc4975e79472d7fac285db20778852ff3d06cee9492790f9e7123786a34a9c049b6034c183218b714bd3177f014aebe2598f89f8a97b67224cd44793f2b60c4bdd72751af73417822a258863bc8cea98712af0c8cb7e442a47daae80b7e4386362cebb766930e8a7edeb827111d4db6c0457a7cc3786b47c5873f0df5b6b9d05abf38c46619e9b4cf793ba29a9a93ae793a42395665b44930f5e92f265a2968d197f4c2d78d39bfbd7cc83efdc7085859f7ed896e0325108ccf9298c5f2fd1744bc09f7e1786574f2ee4645632c157e098664b533dc2763821b218efb069ca55b375dacaa60d79813d79ffdf35220e630ef9004cc77230cbae37e5af01f6edabd0fda285dd0f6f6cb40baafd6c09359773858c0625c7fd1db2e9144cbc4db7e134c67b5204d2a55bf307fa233fed49f866ba32f1c14a57b8e054293b57e4b5804f7eb991b61db7c9aafd62033954d80048f5b9b2326fbd27a6f7991d5d426313916501d7893713402c5a76ffe0c64c499ad674a9e1aeb9d48741e84544ed4d159b47d895c6b54459f7bdad8bbeb8332eeccaf85b679dba69f1c19b55974bd000dd65a25f172ed771bd857a393bb1194abf41b9393c935b32870526c0dcf4a86fd86cf385f2fa2921be40618ad0276b0782d93be5c95608d8a77b1f197e6e12ad0cbc40cce2bdc5d4aa8d07f324b194efb80ff4c3a62c4fc6e391f8b2041ecb52fae21e765ec04a14d2b9b1f491b6438dff4478654bac9c77cbf8283d069d1f0c135ce124cfc8026cf7651411bfcbe35ca9253b4d324d7b85b10c421cee5faa02f6ab3d5ace3bab4768fd82dcf758f0c65610b1ede295695b434cbad433dcd902055b977ad271813ea801a2b8e0f40865769580b9e4fae272e34816f56fab4873dfdc6427652040ad451fd838050376b48b220c53a2147367217ccaf30a3164c2e6ac37c30ece563dc086b7cccc2dd83e2454d92db248a8a27e596b4f8c05aa89aa4996e027b23e4a9af4b5f9bed9a95ca29c5bba9e2ddeaf6a7c6c7d7daea6329b42e6015892656e44d84741458d76bab667287cbe2a91f87644430f5782d4584a16c9a909d558034eb0003608bfacb2b053a406aac57f28124f83779228b1653733a639c401a2c4290a654a60e6d7ef20d072cadc94d288defca248c606c9d3d327f0561f8dc20b6445eaf0f6a964ca986c095bdc0f58c52cea23e55bb38a3ebe725c606500425370f105bc326dffcf8784dc1c119b6e579c868cefdba57f0059a13efb4cdf8036dfaa4a1e9caaf5886ec96f9e25df7faa9e6041cabe6324c325131c792f92182010c03c9a9de6d26fce98284481322948b2f5e7088c52f7f1a166798c95be76a9b4e131b2ceb832f0178fbac1ac39e7bc6ab5e12bce1b75066f09da8807bb382e2c6bdde9a79583b3ea0e9b781f5d3770362ed496ec23388bee8bb41e0e2eb937f7eea5c1b0e54125b6932eea4322950ea5df15fc6ee09efc904a9a91117f965197e80dbd534df7bffdbbf99ac0108cd22a3539aedefa34d304e4f283aa243c059cc69a4f372613fd2ff7800c0ebd8b8543cfc430b4d676a9ace9608830c336ce7728bff9b5042267edc456a097701d72731d3a1478ebf0eb08b648f15dc2f306a78d033f657eaf8a87a0f21ae2debf34489bffeca3c12001a8da307189aa1d68be41e8a2b0ec27dfae2bc6bd895fed3524caaa0bcdec7097fdc39b6b3cff024f1c05f4a62fe307d1c1b3691af38a341fa827bd044fd48f1888110e50f0284e344147abcc5ad9dbfb62d63da5a9d4003e4341ad68a20fc80dc830edb54bbc5da2ee6572879a5720c6f212d90024c3fe2b76a6efab7cf4b7d24ea1de2a9821bd35540ded6a96e152ceffe7bf9cece06a61c2a6184f3939db207be244036e0ee946129f70d7b8ef0e7dfcc345fe7aaff17ba7edabb65f25ae52e080a3e245ca6e7fbaa8a17178f6905e7944208746890fc3a6dc2e93676eadf40d0b9249b7fab92cbc97f3aa6f9ea4dae5d8c3d9e91231f43ffff548da7b668e61c183ac2cf655d7890bebf5052da88dd2ffa458dac1f467e3d7a44930c2448c8f60fc7c2d63d12ab072fee3c24a17e1b12746a6841ec3a922e1b03702d9d468d658615c31c99770b35bb0e93f6a7f7110fe2f258d8f2c328dbcbd84b928a2bdd72656aec28e256412248697c5153bb672d2c2684fa98a8e84a700a4cc451bf6223940f65828175f4d6bf4520bc0f91c4753b4e152e48b37985f3e29d4a5ccac182fc4c57b2dc9ccd5a09eba7bf4343d0edf5b232fd6aaa84943da863ac1114a5978368ea405d9573b50ca338e25597349f439904ee6456b07d35a4c973da64b46912ad45b56b2790efb2b4fbaeae5698930e4db2899f7fa64ac21df4261e849600926191996d19c911e26819aab648a3d03c14655683ed0e03ce5d0f4d443af464e9cdf572cd34c8218405ba5fd534fe5eb63745de79678fae40aa4070b64f769e01399acaf240352a7fd4055374e3514565fd79a8e7b7d155004daf18db8bc3b4c0eda7284405b731bd1d2310f91e438d30b02a3c36a37dfff58e86cc1bb584b1103045152b4af74025283c1eceab7c3ffe967f23cff43bb509b4ea15de97609ad84c9c180fd99d5e9f3c77035952a563b9f9a1e41871b27e2309057a8cf700790087d3b95878509413a2fc4904dd66ff481fb7077be48b622bd3ff838d9e0b556f2a13806ef0b8e969a3f4773612661d9351ea155f136d690a5b00b84a542a370f7c83f6ba087e658985871656bb4ec482d662952cc8019ca6e92fa229e00526c7c74ca2a28a4a105c90dcadce9ec5fdfdf8460f49c9ef02e8c4b4c8930c432a7f51950c8f3cc3f3c80e1cc4058474cfecfdbe0b20dedea3b836b8b2f19e8d4b1f6487bdeae89215b6456ee463340242372ef41218d17688ff8563e9a95f8a9290464a3c19545f7e0f7efb935078185ec6d30dab996960b8a3fadcf42517e306b83f05118649d137b901ea5c34165e2c8134345f0b6d443b8edd4c5bcb8e3fb08099e0c5978e90479e1664c477d77bb351988fb5d7a6c91aa676daa7058b4796d09a04dfb6ed45b6a5059155818318fb1c49463563dc985cd50caf5f3ad8a3c2918c99e74a2c988db5159896bbe2dc0dcc8d826718d6590902e9f20bd13bfa3ca9b5c666bb1430d3b890a20f8bbb70f047ad6bd45e5eb32a1553cc34347df40a025eb4e3706db69095bcd6495ad87bac77f028039e3a4ec6f5936561ca663a3683ce8be6d3ffd1cc34e25dcb91ffa64a42a0e27c3e33d3e58cf1e41afbbd74c8134f72f74b912a907d495e1d96f999d19203c0c8a811ea8fc1b471e72f4c4ac69c27919f40ed68e3583b02c8d38efb494e63eb6e429c3acd7c263651ced9623a059f11651ad934ceadfe7153ae8575bb5e068af6769c6ca6a58d58df402d672b36ff1ad2421e365864dbd24691a8ae2eaa35c6e9abd827232532021aee10ffa2168fd36d1e841589651a4d2ae3bd4faa1937c7ce4819144be25d5a883e49d2a68f9765be17d27a6998030c860c8dff379556b42db727db26c55688b399a2893f885ef84d96d20c01e5de34e274e067de06d8b58f5fca23bcb664b52368e1cc75ea2db2a3014a745835f2f0c6837e2ea65206f47aa8ca94169ed0095e11cf42519b4cbe47c10359766e8866bba12efd9862289679faeed73b63e3abeefe3494da840342c13fc564647ae8be4836b97cba1e86c611bdff9433efe2c45b95d86a198d9a679ae2573d293f5a21f6aae852166e67aacc59c6720728187b2b40826a149f58545911838bcda726b74c658128b43b156a3137081abef8ccf9e866fc3fd5a0863286360ff6599993a7d2cf4ca59a6bd62fc8bd903460464565196e0c8f647a6c28a9b8c428d4fcea1a10501372d2e8f3211a542567f6f9cce847c27b03c713525f4bff6ac646ba74378f519c366498b467ae648e5b86fba9aaf9c91042085580fef373c97bb8ba7627d638b09a5665602a0c5694b91b3324d2b7eb95f52f263d2e2a7ca8ec30e43b116d6f1746b2364191bd90ff29ba8985890a90d0c34270d3177782d1c4b9200a1edd5c99e180f29dc5487c97526de513f678208b554b1a88e062ed0aa958562a75ad69fe13c683bded358123dd3b2f17428b038c52510ca5b273251c44176ee44065fd0d868ed0e9320d066d712d4fd994cba29c635d2e71b43392cf3cfd5080a48e7f8fdfdfc168bc57753bb7492ea115bf6382307668360f299cea3912e546cbaf594dc9c23e84b29b35565799b2b7ecefaf0860a4a23aeadeac37e55f137ddd50f20cea3d2f833c62ae86f825c46243e3eff49befc4c76ed8287ce5ebc36f01f9b297ea5b7cae8432489242f34dec3f24890f8b60b623c2a17a82e4bfde62603301b572210e4daf966b44df0e339b0a39e721d6fe609610c1081188e14a9f2285472d2a99cba8e8137f62747b60b8ccaa6ff224d2aa6a7dbe3cf1795bf16e6e78ecc8b9ca9c065533ecb5ac43ba8aac008b491ee27de533227de96035b9166c93584ec8bc69b11e7d1a052c6f2b5bd3c2a2ef6b38bf515a7432c2e5758ce1465ef487fe781bef6c79721a9f079a7169781badb4dc5af4193dc544af2551152161263a0405980967b05abbab11ee1695aa6049a1d4341799049d37674cf0fb28255731816bca4736a93bb2f897118f7fe1d88bd92a370102512263b4f350db8031f0321eb398f002ec7ec52fa0ea2750a4eb3d05e64afc7badbc903f215bfd540d570683e4e94ef20289bdf8356125307c0125cc0e544e2c9ed385b0def8a8ca42f6755706a7a72f72c661fa5d5ed046f0820dd7ac3f403eaecf0bae320f260a5734464222b944e0c49b7bde20c972bb88d6bbcec7f21ad96518eae12689bd3dd47843ee3e076d2e8172d14b868a51b6cc66f757685524244a16eaf2c14cb68b422c32006579694130b22dea988ab56045988b1d9650a648e532d2890c41591b27b7dc239cf7824059c355635dafe8e17ad53dc659cbe76dbed26c4d0d50ed160e8109fded69fe53850d115ddde23160f386d7fbb9ae30b95e607ee7ce62bca37cf280365fb726a43af82b81210cd70539111f63bfe1c9574c42d1fe575d0cebfb720b77fac9425803ec6976301323fad3e0dea7724cfa6d10e22d3bae2b261612697068c7879a9a655195e413e15d644959db6edbd80b2433a65a46ddb2524b4a3f78b0cdda7590ea8f7575d05ccd6e24e6e061006b43a33cf81fc6ecdcd7b2295cebf715a81d62190ed2cf716eba3debeeb328ebeeeb13b33f4564e18fff74392891e7a5dbd8397a36739afdfe6efc26d91d6aa44a4b9b20e295c7c6b510b9020c32268759ef3d9670227f7df32fd15e0ee6cf3165ab640c4bd497a23553a80b6e955cc30caa95f422a525f83746e419f7d6019c295395ffe1cd7e7ef7106adcb783325fda506a1db6c53676b1fb5ee2fbb5a5ed7d8a577f0c472261e38f26fd50c44f65ad2be397924a57004995673e824a662a489ad21634092b53e11705f79f205ddc6c8129ec20fc329ce75e26b7f45ee95d74c92d4bc1dc425dd9f12f500e5f01cdff9b63a3c6a36f55739cc4d4bab9d98de86091a61b880f85b366cb930448e53f4dc1ba45ee06b630d598691d3a23dcc38ca625b0abd539d557bc0245e9a4b13a403a5659ffeead37f69a3b1bdd649a0648e512f0d78b6955ad3bfbf0243b0dc7bf505e676123ff71bce965550b43bd5e04637fef0871460c9e2ddf1b1fd59fecb349faf87b6d52e610cf0d542d8aead3a86a9533a750790210d8d54f413a12b3026e5493b7944f9fdb925c9ea9ffcb6e1c407b461af0a93fec860613f2310c24950791c07c5f9da3037b79bde96b0836482d5ab0cb197fcc1b7d99ef9a843546545cdc073397e4fa72d28c3953954934157b5af72093a2b08405b76ba1f9372127b45bf14204cead0edfbf985147356e6970a2a58325bcf30e6888107059b7bdf7593605126e01c3c1e938644a4774ee2e565b415b99729ea3ec33656d6b6023aa9196cb0b9d2544713a1f24a65eafbc4a73ac5410b56eb72adbe1f301f9d75a2aa5a3cc9559e33908189d61cc548edea710be650a4258498a7bb94094c9c76a5ddd848f1969e3a96dccdaa226f1b85e2b74b9fcb007d759ad15e4d68fa5d65c2d80629dfd96cc9c889efd6f1bfdd643c0e9c4a26cce94f17af0893d5853ac3781d8647caba8461a41d52e50133f8f964db507dda55d3718d956d1fb599bf203402bb3b68967fb2c80bb97ce76808c5eb921ab38c47d67d7b28de49af94750c854c276d3019a90d92d965d70ad8874a3ee75d8382ff6cc35eddee9a29f1d6d8fa4e5d6597a0bb02a30afabbf1e1ff06ed3f9b5674f900f3a73910899b5e7f25f18dbfbd25c800125714ea742320da8c650f56b4c8c13d8ee2aee6b09a26b864198ee0cc50ec22bd4d9bb7981511943b34db0216f4f46ce5afb3d3779ce72d23c0c6431bbfa99d73f4e3d4a04e40d6a3db2730059fef29a85cec59772a1c37e585c86943b87ccad6db91d425451afc46d6786309103dfe4778ccfb17b8328319d719c5acd1d25546603a488f802c8fa4a5531d89fc4d553391ba3ad1cdc67931431eeaef9246a7532121745785d1fb3dc62bc210cab9c8fd26589b4dd14494dc485c3b6480464b7dad0c1a05dcc7e982c776e325f1efb414a51c9ee47939607b655659a4126dbc36524e9c22db6ab50417d90342087bc11aac6aa82e1c11668f08a1a836df09740dbf5d6d273836f84245a6a4ed84da5a6f9ae7598330790197c0a2b9952cb5fd7442dd1f9b49a92dcdbf844a90ed87fa93ff0735ac6cbcec262bfe0037a20fc30a9b3225f7a65dcff703d666c4a9011c18474e9',
+ '2d18667cac7403ce75175d390e00c5d40025ad5fda64c5d678bc634685bd28e03f3de14c7a4dab40e86c5b5097fa1c08bbef5a38aeccdf8f35d23c6b05726bf08606b258d6beca8911dd41edd0251d7eef8fc220944016346cb9e26a384e7cd689d9a347c7afaad478d3d9b7bf6a105f236fadc092a8b0eea6d91dea2737a2bbd01f7ac156089147a6b7e9576eb23cd2e0f6e3c0b3ecf6e46a9ab593d81626c7e44100708ab1c80a22ef3a74e5e3ea00ad8c2bc7fcf5303f4064710f55d450214ca5fdf96b93974674e594b72be6621012994e7d77a9a626b09f1a03a65768f290b539070194452325ffbec847a4ec2b9785158b2bf19fc243b3781ad189b66139d87b40559d1cc8bca7824e4404d079c5b9459920b6653a800853fae0518297ce758c4c6e97625ec144a6f227ed5521239ba94e5fde3eb7f006734bda9613bcd7f635d45468600cbd3df35bfa49c44c3a940853ef5273611916a0b6c842b2f7dcc23c8010fa5efb37fc3131c5ff6521900d294fd8fb4b5f859ea1e2b13ca770664169f7a933a452b7e8281e8ef780c9af6cd5eb23c010dabc083f799d6c3a50fcdb86e227fd793ac5699fc85f195d6b1f1ad4cfd78808f944ba422095bab3bf27f859e8933f27dbeae760d73f4d44306680eea2fc3d7de5a71e72819f0e59e46e00acb2f4e1f45cadca31f4377b7c400e05eb0d9bc6b5c56d9a9644c65075e8595b45e752ca29e6977bdbc74d8b4ec29f1092b7ba0bf9c21c9878110f684ff27071ec30b5e40da02f6026f78b6502de9f0bac4964f490043e7fde8e843e2f2b3cab6b352616eab3fde2d92df9f1e0be985d016a9e69c4b25ab791662cbb5dbb446f899dac4806468969be109e182f87116e59c37252dbf5f9a8593f0fc520c910260d115062ab825c5e9b4982c0396493a67cfbe7971ef4a2bafdc23645c27d2939c038194d1f8ffd27397fadd2447ba56d32bb0520d5d8dd554796a8248900160e6abb0fefaa94f42e605b283799f4cf2b42abd5d548c832e1fd636d48be7a5f0fd3413a3196b9cadad784fad580f8994875725e9fbeed2ac6e8d38e9ba8128da3c273a3fb2928069268a32b9640af8c2c93b9a964816e4c6cd08c120491f1273100f95136ad0630c0d960c46123402f3f6427bc0ed774213b7d36016abf3fb523567a4cc8687ed0cbf362cb1d6fd30aeaaf65a1830927309cb67a64b77b23c0e0899e9d9ba3b56f1b7e524bb46d92a6933e1a60ad5eae01f5440042d20dc5cfd0640e4b96a5d6941842d7490d65a38aa4d7efff7220321caff06fa3a3bd4e6a5bae725ea0b807c82a079acf109f2e3e83438c88bc95da0a33806f8f12d3e619e92e71dfa3227092b99443e4a5625c4b9a4a98027207cf52e8bcaa0f0796b465e2adb4d5862c3b7a2db27991b4f854384fb3bc767cbc387c356ec52a6a4fe1d5aef3e348311e8d08ee29e4dd25a73f8d0c489febc2fd3e10845c6be9234794f2b5c8a5408b4091c564c12dd0e0b845d338cfea692b1109973c4f42521ac3f64260f4a2c67ed96c38f741fc72ce738d913a1144f9a142c099c40ff270380e2f4f153e83e1f23349ea1073f8ccd51f404f7cd656a10cd68c9c86642448636f66a13d70f09acd944e61151dcae5de05859665e5c76b5216942ae91680e4842dc4be415090f8f845a32770081ac5d26e85ec5d08405f5c4a01ca55ecad4b8491703087a70c035b8e71c487fc8f7597a068dccc05698412bafa0532b0548549e3927f793c0bc3deb6e0bec4c1d1fc17e455eb1aa5e9e25cada861e9281c9bbd6b54317ed936416a07179f8e1e8962388174a3b0b06981236d3268e01dae94c770dcd0fd443584e5c73fee4cdc5fb0e4c1ee8bf4ee906a4d40c1a28056b1784e3c52e46046af94393f7f3468c3faed02eaeb2b4f2707a4c46f7d96319dce4f3c15dff30ea74d7a4cb700f8499b03217a45920c2a2275376e418dcc5cb8ad227844ab876f2fb63d0877e9c2572c2154341a0cb5eba8832c35001acc6770f5f8ea10dd27eba692e553c6631bfa3efd8f17b181aefc81d98a00f24b1fbc8d4eda7ac39d5cead38b7b17ee96899a983ed90d511880c3751e59b661494cc1d762cf10a415acd47f47053b35a9969f038d3bfe43f9b2aa4cfaa141933bdbe016d6df94fa6aa211726e8ea7e4c5ca714792bcd0d04dcc17cd176b88d14a5480115512ee0ec7c30974a91b434211ac782cf4646c3e3c5774c11abe73629e400891857106285299254da0b6f799b6c41d7a5c3bbad5edda28f0aea3ea905e27e25e0e03c48f33abcbc4fa66ab2fdb9ac6f8714aa2df89dd9b227921d5a1b38f754099d1118d938164a35f34474ea9b7dd6fdc980da237e8351f23401cdec40229ffcee1d3689aa459b07926b33c48a2c8a7442de16720845eae5508a88fba076662543df4696f9b10b4ed47d741dfe3f168521208766b387e99b7825ffcbc279432d4ed5ad83beef376669c9ba79603be7aae4e6817418dfda6f0b52a6cf3e81b37f5f7effd252669c08a2fe8b4968999a4ffe9eb92ca0a439ea9aaf22986d564396065991f56cad9580107a4b207fde9afedec782e2d37b84889679d799e73d500bc3f4288f562ad07742cb9e711e81564225ef635939cc56e39f614a5634cd753b28bd17e2b764c958ba70d9cdad0878843474fed23c2d0d6605f40f4fce7d3fcea532e4a208f1ecaed7f8a188d40a6e6fbb06a9f06304349a7a808b092cc2fc10b9e4134fb348b6e43bc17a550bdda45efa02f92636e848fb6db531f4c84556bbe75f283e5eefb4834679b894bd18b6cca1f86106305fd7034ff0b8b5396abc2aadf29810544d6216986000da80321243235575f2e7c14b4c91d173ace8a9b8d78e4ce7484be84c189242d798cdb0435cfeb8ac8eb5b33221e3c5f75e6e98b96cf8cc9a589e46df03d460a1521e29d674b480793c32bc184db64cb83c339e5a358e0025c3d3ffa762df67f288f9f52824b54b608dd7226a0a89d43ae8c05107dbae761e1c756911a003b74fcfe9b8c4d7a18806f62bbc93e2bf0af3c6ad274ec9ea9cf7b50b19ca55f1ed1d7955cb4917d9b4b0f798b14280f64f776842a79b7ac2f327300d981e0f1a57e027c6c3016ffe601314b6c6e25fa0203a4039487a88b807411fe55aa905fda63c5dc536aa4a6ff881dffe53ffc95d1bb0e0e990685e4a47b9d73ad7d8050c56967dd97c8031af0ca1bbe7ff07687d908fbcebf5e175ea4315f866a64776d6d7632a6c2b4fa04c1ad73b0c0e75b7822d0b56a91f726a2877c9f6013c63c5eda736c605c95530c781b6cfc328d7312b5fd820b943a7a575546a428300a98ca14495e32ebd3d4d91ffb4fcb5d4a85fa9975abd9528dda261776b7074a9a535924de5045f9d64614bd346444c8875bdbd62277fb52590fb7d4f42025e8dd35b4111c8ac00d0570645bb0f390fbaabb5b75ea309a1c07e2b194a827a9923b0683e3ea53ccb0ca1c72005644d67e1d6e227db71cdd39fd18bd5f7a14bcd01c8d6da22ff591688c10e6b40e9f3fda463cd9f67085ed30a57c823e522e852be8931b57d5b636c0b415677fc04bf3968fec28e8fdb1f18966d5a93818be2d2a07e0350ac3dfe43da8f39d6a5491193a5f48b65c46e912cda7ead956b40cdb56e23c62c1e1b7c269d172317cb3b9d94e1d162c5932747883d284bb9f0e60b835df6f4a861788f9cb975acbbec30b5c5b331f31e8ab9c4a334e6bf61b0e02ec51674096604d98b0eb637212366dcecae9082b6e1099a7b165836733d29d399e32e378ee586b3110529b83afee9a4c4b7e04028bd9e2ded4a2d9401acda14ff65eb9dff97459994187a95549ee30cb05a48f6b2f4b6f89dc71b8bd5213038a1d5f533d60beff186a12f3b0893c199423e2112f026f28f0f05b88a884acac333bbd175aca3e46f8b37ce35c17e23befcbc0f216ae4cf55a39e7b1c757a1839177fe6ffee0fb147f454cdf209ae8802326c79ae8d8eabf11de9d9be374f96feefaacc2f04afd53f7480a51c6bb54ce7a5b7e726d2a526c5b0805aec382bc5a90ec4e77f9af4ce9a6e33fa01421300f3a926ee06d4c8be681dfa853312af22bc0746df8e1b8f1c0d53f7234d374842aacf51d4dafe69d13ca8a0df0f314a4cac6ba90ac700cf3becb842b75ca5e560718a91522fc9f91dd8032bcefd2e7da1eeae73ffb6d545acdd2d9f2cbc385b08ec6d9dec51c1f6e2be9fe3eb6964c9a117423034da2372ed43066509e849199a7fdabfea0d70f3ce44f171aaf07eaee8aab95620ad55e78ba2e54cc56d72b1a3a0747ff19f51704f2f8a4d840ec6add72d966e69acf70406914cbef5b82fb392f2ad6699a3ddecd3c2dce01d30f736faf44bd176658168cc82af23f154da8006eaf80c28a780d9faa35bd1ccf36aead2a34c37cd438f866bfb7f246ed02db77dfde6c94516e4b82245a98b19c2ac29ecbf3a09d4d36bdd53605f38c49673ba56bfec36657e7417f92e28848a2b3584b7bc87b023a1b0de2155a1c9892467192f859acc103ab979ddc16b46a489818ba20fa7c3401af929344ff95f2316524466f35502cbf81f4e5eb3e459ef8a3a9f5a3d2cdba0cbaf1f2ac3c87228c8cfdcab9dbd72ff333005da5a2626d1a9ab404ed98931cc104d50733581ab00d85a325bc9368520587640ff389345e1446e0ede594f9e38a54e4d9c29d75251b17c05f62a42b1e5e46c803be3de2f94f6fd6ba720d2496ce74c6704251c1091d09f9819258491a6638d340ec0495c633dd3e737e4d3fbdf42a24d499bd25e27d24f89134f5eacf85242ece6627754e2957bf1ef09a70e0663fa60eb129ca3aa230659a2fc435c324d381b515eda91897a701c5b03ddf888b7bf32470ddd798f4f5e7a16d0d5380a90e73fde0a05aace693ad6fa57eb63ccba5b421c0207585db3a27b0d5186c8e7e9bacafae86af937fe46b25b9a41a858e87900a883ccc88bfc9cdce4f2ca7730942d5d369e9d154c861e2eed3f935ea3ce730e9b077032908688004c3922cb9b4cd966ff80fe7772bd4bbd2dbc32ff33d8e3bc51f1a43f01ee0e859199324e7e602968d43411a850f039dd9ba4b3028fa445aa7bf6cb3666af8aed53975b78606ab7e3432c69205dcb8310c56d95f12d9d0359677b77c12527a7a800c800c1d7e8ef56dade8767ff9b91f7298b4e43843fc739a2f41c57c3f2cf36378fe4c34b574a43f9cedee7bd0ce0e136826e822a18ebdbbcf54b72d9ad8c28566359e54132432b2e71e2482c8c1a6f8af759359594fba0240367aacc9448feebf6e2b03006848ce76c33d1b4959902853ea0c64d5071376682f3581363901a769f11acce4068e9c312464cfbb5d74ab3ecd7ccb7b7e6f2030891035477ec0b74e06943427c7ab230188bf258796f98a56660a17b57b7706808a344f66dd752655f5c1c53173486bcc3976a513ccc8cc85fcdfcfeac8d332417cc957fa1cc8fd6505c8066bd20c7d7c7db2d7c8c68979b2d9227b812b2aac59a5f6d66b0460436880d27f3baba060f79d9b440e4ee39ac543fca4e46d247ab24ee453205de60045ac06b90ac8ab1c27e058734e2a7e36a58395d17a566aa633bebb5683ef013ce4d28d3b41efd6baf29012ecee2303553ce1148aabada438c33fa267fb815a002d398e8d46e9c94142f3f03858011aff71a4a157df3c7a364c17c10ff0dd9853082b3238837e2bb9fe531bdef28c6a3d2c1666c17ec992aab58f41d5cac9643e5deea1c35e75a52b0ef6ba66268c9c16557d884ff01d979be6ef4a42f20e66c814cb02b64198de9e5a25f6595bc85793d522328a9e002f12c67f03ddce7445f9150a7d9a93ad7f1ac927f73ef80944bb924d8af8ee3902163f87952ec7c2aad948f33cf6bc7d2fa74546e49d67170b967546b8234992b9af384e28bd46c23c7195ce645224d0974eb6c8e5ff0fba532c6654e59318d1fce59a1de213b13aa4c8e5e22036336e5e1602f624ea58bac4864ec039cec1bf72c14b3ceeb771f389e61e78962b511e2d081218c6e9aade07ab13d3db5eae24c44a34f37231efb594d42ad8eeb8e6a9ac2ace76f6816defc4a39fd2f4cb45c89a893d8a970a92476d99ef4a51665ad882861e57da3c09b6a277d808ab9bf0729a844f4884c9c173bb3d5716e7bc15715824eb059aa1e7f84a2ea8bc295816ec6f45507662f6e6ff26c94983d7427f3cd50a1bc65f386d8064d0f63595891efcbfd42a5c581968b45844acda4a800003a24298e3518a04159766a9bc18b7726b3337c3428fbbaf5af6b66a6467ff018c66d448a4971e147e8bf6ab99da697900727805702e6323b9ab286a665746e8a0cac615505a7c0684e98be31402279b85259fd62d5e9a2c981ca0942b41dd1f077622e631c12d40ef592a3975b97a40c0f3b220c347e732c1f429d0fa4e4d0c8911f0a28df5b82c8782dd950326e8d07b85bbca7810d29dfad9c8061aa51d19ee433633ad1da531130dabdf0772fbb37c82c1bc95e2e3d50c74871ed470d5adfdb6fe80d9627db65da19dffc9c43b528c93cdde61e4a342bf6c6d0070d4fe2f7f0f4f7158ecc7252be7b9de91452f137260e618263f994cda69829536fb1f093df932ee74e20be546870962a71c5f5ff89014522a1a4e5c2715cfc36d0dc0a2fc1aaddf736cd25e762895745273539fe4cb5f74b79e48d86c155b735f316962871acb2c9eb4a2f8827263fddc6f9439ffbbd06613f2112a72142f8e5290a3b1a5a291be3ddf061b340f7ee38b3aa99711066e10864031b801b851fa8f52f5f71d5a097b9818832f52a4eb10a8fc1a1fe254425a5aad2eece1a9cba15e9ee89cb8e93a4cf685a39ca5dcb36a7adc703b394e522dd563922dd990a569069c03083ef82c19a14eedcaf55cdfdb4b033252df9ba86f418ae4e2b215e53cd176cea4772ee8cbdf9ce9220acf774a6199e8cb9de44c0ae449ad7534df95010551d1d62a347548f997200aabdb819e06c51a1039d144e407c9744d2797bca0eb58950721b1db7a376619eba22807c17faf51887b099ae7f132c1d253e46300cbb0a1a0224e2348e0325c2eb55f109a1780149b2d3556faecab1d6c806f910bed847d07f6b354993e227239ad28a5111560ecb115fc7e043e5c0e8375e04ea898fab2a665877657975fe276512acd0ec0766032fd071cef306e1e7ffbdea2bf163ac7e4367292693410344b6b0e907962ebf886abfe37a20a09d5c8da119639ad6d4428331eaeb75d5ef122c17bf63a41e63c16e787d8051454bac6da2231fca6bbb2dc2230d5295971a0621d1a083aa590b5487b4d0600967855e2c2b97cf1494b9767cc530ca77270c9734593cd5fd811ea852df6c97bd79d2e825c7d7d00dd467e31f92d5d19ee83f5bd018d4d9221906336c72b295e20e9ce106e173e12acd3eca27066cf6716d4aaa81bb191bcc735a9c10f932f91b987bcf2c12ec70dfc1b6b22fba76a793288739c59a6a0c3132a4390ced7e2fcea9d80921d6b1edb16468219609837378acad501851483914d94b909cb9e4b88fb54a3564a764d62427bbaef68e03d8a65c05a3f65a5400cbd9e9496d091701b33d3aca80ff65c8cc7e2990cdb2785580d9289bec2ffe11eaa6d5b9ea3585b9edb4323ef3594513fefa4a9d88117158bc869eae663ca6dae5e2c96890d9a14fa3bcb909285f7366500e972bc2256f709df6d062012afeda31f01cebed6ffa1159b947cfbc4c712e543d87b1e858c33bc9ac564d2955568e7361489a7c6310cd943c56f91bdaeeea7009a4c677362cf8a409a8d66b566e661eb8e52aba5d55d2d7a87efbc3752d1937f566d00b47fd55d1b1307423a8770f98597ae323a969af88a0286e4f95bc3910735674f108d4c1c71595cd0ec620c974e5627a44e19cd24b95dbb71cc72cd89e15fc0be4f313daf8b931376939fc5238cf4446aa909fd9850c116cb783ff56356fb03b43815affdb1eab59ef6e6a08588998dea130c333c9655205bc5694507ac32b59994e1aaf02a86c9afdedda7a68a6b8b8d54ca76f8d54260877b99c55d2b2b60d42b3d376f408ed711132fe16cd995c5bb33edceb453af5fb861767c4e1d0f8bcfc5cf5c63392a682b5d8256c0bf3dc4dd7a1a43585ac5a35740a972c0c805bff12a4223b25e840367847f0f8b828c0e59effbdecc30abb6cae0d9af9c763683a2213c9445f7c1302c4fcd13b36ba92d995b38a6f81d8a207b09af3ccb6dc95f4a7465b6044c0c77e387146a22c2bca9038579a4c56b8a67d083d22034f5e7db51e96791d7a48b61f2f4fe1dbecfef08013765de1960d2153315ec63be58b8802049e72da7496675c25e313371d968d4fa37a45ede468bb10b89a20e53dc63d51d5b90935a81c63bbb8d55b4212a4f94564edf2cfa63dcc4c0a59e27dacb1163b1e907cada95d33c454913a9b808cdd17c44be4e5700eed5ba3288b93a6be9b044b9f7da0dcdb4ffc95913191dbc0a12d6e1e8cedb64e96c60f707c95d0514463f9506ccdd70920dad86fc0c388e2ddfcbed06b370d9f4121cbec4612ae0b9ad0843dca731c894ec5b98768af1c55ec0c32fbf06fda5374d554d67888946016b098b2273f01be32639572b9c30abc73e2670bcce6911dba8746d9747627d140aa937803804b2a643b18ac049dd868de05d78a4139e6508a8dfe6fca59dce19124f0d9dff54d506c92afbe09ae09fdb2fce0f20de83e9e5d7ff803faa2381380965d722cfc8cb2b420c703195213ed7b2e2fdd83600e59bbc3c03085edade9b79525bb6b148de3c35beb25b4489aba9f101097323fda051dfff36fecaab2d67e97e6202acbc54612baa8907affe2d7a7494fd9aad626330c9aee2caf57eb5c7e251b3aec80c76c4ab37bcdddd',
+ '3a14145dd1fa9e46c4562eed0b0da10d845ad84f43cdb16e29933699b8f7151925295133af3e36503079925bf2c9226bc3924ba24cb00a559eba2e6c0e83c50c43e7d4748dc44b2578463746a2683a46c9b738c3285954ab044f1ba182f7fea2bbd506e81292c30ec6458676c3f2d0e8be50097b80d075b982da65febb5aaa21b67b4f56e7b288533fffe5b2fe70cb97c9e62592fc1b57c741e4734c62b4b0d25b621888b42c803c0dfbbdc3fbe9159c1200f4d04344e01c69f4af521e0ef8fdd311c7442006951158c177726165953fc226defdfe53fa02219380da986f6aea4510c653d34aae1947da7985d8ec33c701e14be0d44e8cbf91484eaa77dfeee0dae87b7d7600b29d03cd2dc40a87d77ef6b7a3426e3f7e9ce29b828c64666c29ba205089b12e8be5b422faf99c3d69aaca324eeb732db8e13c148245070dcc0b0c40ab412bde2039806247ea3917d194a4dab4a38c2121d6c63cb7a007dbf6cff9d1f66b8d1759e192147e60871bf784ad363e326122a3c3a99a89640dd9d2bca85a98d07ee21e2410c006232e53c4c10dce525f993825ef0cb76158c00d491c4163f938a746574c23ef47fbd7c71e95eb2a5af3dd6b90a31819a546c9814135ee74816baf4bec9ff227a9b02a7eef466fd3bcb7d4c4ca27f54abff4cf3da351d516983040f9c566a6f39409ce801d1dc350e270274abcc3cad2152a7b4758b61ed0a650ff59cbe866d870d06cd591620c2932e97d064ebfbf3711b275a947acf22b13949672e46f5b60a5cbab86345d75e716e97ffe6962fe031953646b577d79ae47c1ad4cf941ac129bc33499ed562311f537d53cf3f5acbd97d4f093726fdae1aba2ebf0f3a78276ba7fae19a394412f369c26c8d6c0f4eef2fec22b7fcc3e4ca5fef965b8e905156bc9c20b4060f5c943e01aa8f80bfc1d9299823a65dacc789e9c7eb3324f5c7614671879ab02676883cb5ae6431eecd2df6dd8c90ee2adecff4523e34721b0221f22576accc2c1935e248e8a9d40ed9641416adf612b08302ec190fce1a6289ff2c227e78be728d33cb55e9af0bb27ef20dee38446ff06cd95d86c06e727ed77f70f32f7d0bbc6af8544702023d5c168e40de9c0a5a4cf4a9a52600a41ec263194d11da28384c3afa19a6f231ed7e386f594249c66638a2fa7f6130ed73dfc5633cf93f08c8b475bf97f01acc909b7d3bb3b3e1f72845f05238d2e1d9162976d3bd23aead318793cf3bbcec20cb262d69fccdc52af4f775276df583c57a21efe14a2ba97417381d9f8157f6dcf1b0f17070da93b060cfaa107b43a751147ba922507bc00bce388ba7156bcb5fa8de41f5cc84ae45f02107740d47bcfa79792b0d8c9e82b2db1b668c4462ca3754e097507c36a55a37adf5e8807c45301dbcfe094afe5227d26326a5bad783e28a6a7a16ec7af95b8bc92dd4714bd07075a98aac2825ced928825489c53488ffbdfe62cfb9bc1ab88104f7de6c40df5a25e1697c80af492561fb68bf100429cd740ed9d150949a2fabe3ec4cbdf5d25b82d702e0f0f561bb0350ebac17b116fa210e57c23d7ef7ff50d893c5f2d549d3210cff7ff59298f8710545d738d5b104698f5528fce5a4c6347556d0a759b67f94f5b7b00af16f7c5f9b1fd71fec985a92046a5c0b633112bb2cdde3581d98bf4323b417bdbc55a51384d21229602d8b5ef00001e5721d4359616174617b70f0a0198d2d6a3ddc013154f51ee1caf11504f4ae81178cd9f693d5ba0a700ddfd250399b47bd00732f3d8df153d5a773664864ce701e3de79afeec202be04f25c2c816771d02aeab6d9c827f677160351d8dd2f84565efd6beff073c4f5ea9f3506c329913f782f57ad2e4c7b0419fa69949c1b4878b2d27b118c976eb37c8b8f9d11089a2f847d1a5752792d4d2b0587800b37b9d0a704b3fd0a56885f805e72d8b32c1608147d09bf7cd492b813ccb28472ac61c4043c1b9bb2d79b63bfc2e79ff0bc8c31f1d62bcef48534ae9bf6f28818a1c8bd9321bad4cb432e26015df4da12e18514e331886a01b59b98892c4f74463f74241a5c988e9fc1ca100dd7a4715fc28818b136297ced8c4ddca615d23044aeef5f6294bdb2747af689add9fc4d20881da5258c15edfe31d4e4ba5a82a45a15c1d83372322993963af9a70b06549c5acc2305dc54a37dcdb8168da268b9d09c70f5549efed9443c1ec8c414c96f1d611efa1acdef88b2877fdce6968a55ed6d86208fbf29accf942b5ecc9d4d87e9c49a932c08ed83e488b39d8fddf261faad8bc0aa7dbc897bc7e824874d9b8249acc9540334567b5cf7dbc04e20a8c63f87053c6e82be5791fdde80bdcdba4a854131a666fa335a63fd80afec07b26a04217efea3733700595d93db35c4b2c5e5aa5cf21e028b073fc229d131391a3791a37d6d11fb2f6b1b10919eb8db8cddb110d29ef4f3666a386d5e8ee45fe8142d368bf17fc0af801f3e602f0eba4f79309a1914ad76cc6b9827a84ecf2022e822022ff2b76abe27ac0d86f8ff080380ab71bbba1432c6f2a5178d79b825d29db62ef1d87fa265480ca88d5f536db0dc6abc40faf0d05be7a96697776816ff1a32e2590ca010abcb8535fdced1935f74b5a42e3b08f79432ea3b4eb1a79ab247de48f0f4e25b989860dd5cac421f1830d4510fe4255077bbb1bf398d3c59f20c01853df90c2b3498e5c734616ebce1f80eea6a5f0f820f6b4519e074f1fcc751e4c4c883e82a88b15b1c0c551d10c4b4ad98c8138e366128f072cbcf8c2b39fed02b1afb3cfe9bcc0c036df017c3c84cf782b0686a1477dbf8f28304d68d51fb0be2bac7d14f75d23ea5de9a237ef5a835d1aac66ac3586da6c08f7d97cb1630dd1230516fc61fa93a29e7bb0be954b1aeac3e9558ec0cc4420577a0978c918690e30500dd0aa03b48b810bb95abec4dac3cf53dfa369cca14e8c4d79d79c8e36b7cc03be5c4006eaf7ae2028a6cc66575a85626184a0f656392fd89733ac531b506e96c4d9c482cb996e4f8b1d6e8e25219eab97ccf6d7f792baa1ddf769056b7a809fade397f5cac359f05d48f5caa8bb7375ced6ebeff9cda53fdaad52f3cb98ba74d6044ade6d17e9992b93f2aa768a9c77832cf0bcd15c781909c01acc902d64bcd9b64dab1709a5c05298f58bf3118227614995bd12c1bbb3e7c9f0ee7dcb27de257420fa7d1b070c8ec26f0dc2d2bcebc5b75b7f328fe8a6f145a5e7d8d47c6f45b8654af3be95b41caaef9e5a50b55b4cf0a261b5397758b2ad7a3725ebcad6b70d7afb1f86da7da8bcc7cc2e1df3fc53701b031f30f04fa87c1e5b0973abbaf5edd2a964e63dbfaf62a805b29d012565d015d1d518dbf25f3be2d1e80e87628ed41cc4486f38008d5700d98c50658d107b336c7b53a2f72357682a461ef683ee4ab9da4e7471d6eee462b61fca8989dfebe4217663edb4a1793ec2a8176195a0dc2a69ebb843a930952e39e18df5b220acc8af6aec04b165fba739829a610e22e2fee1b48d560dff03f3c375fd228c8f282144ad3e8083cd69520d6a1a7d540109a7d01d86015ba6ab33f141aaa87f7808aeafd1edf992644ccfacd31a0f0da7ba95c3ab14de48c3e56f31d908e00177a8c14f5d7cd863a7107096321b9ea1a370792ac1bc552bd35d2603b0ba71c90a92f981c46da58e224ed5681b81c49670b5a274160f0e9b517cc8e54d11c62cad51c8058b32c96852726e8103fee9828c04b24dfc7f530ddacef86512b165b2ec6fbd49365eec88a405bc8f6fe5a5cc71e81907097fcaf9bbbe04f1b61bd8d2243739ab4a546775b3834fc1d3d851fabeda573db192fef580e4af198bb38820f162cdca3bb5c2a5fd6588e6b449a683cf55ed60895b4777d6bd375b281b0c25e05cfa148ef5969fee47085ca5abfc0e2fe55c0df52b3cf709b23e250fa4cd375d904f28b8865bca02823ea21c91cae05cf3139489a55809b66e3405a6f353fbe5972d654d0a7acad6c1ac457d7dbba0d319b492bb3c1116593bb97b728928e9f4fc2558b0d48c08d76fc1b56cd216c62ec3bf970e6200a35ec52f0516d8c4682819b7718886f81a90e72f805f3194d6cc8b850ff7b9af4753751520f864bf1ceb9a645e389457567fe24624c90e8e4948dbb56c0ba56568c3d5fc6d9baf616ebbd8bc6d458f226300db96113edb9b94002eb149ceb7db8e2c625539753b63e4155f102d43c9d1c6d02dafd4253b255d9f0f191795536a2df9a4b013197b2f0384b8002c97f6fdd84a62e3fc208fb3fc81f74d64141aa9deb8078d890cf13b43866e1cd9d678ff3dfc15e2e7954bdff74571de9daf701306e4154e19a420012a96dbc6b363d25e6e41b11d25081201e446094d42ebf62e4d0a58823383aa293f329b8e57e485b3cfd7bf0342fd64b23a201809f23e1f5407974bca653fd20be7e627e425bd2577f91aaa25bff9a6796f5048950a3a4e4ccd1769773d1d4a31cb2dfb68ab72141360771d04fa6169b00a42f58f1955254104173c2919c075333f86a07c6797e42eac99622190e9210e8194b9589e0316f952f32e5089ade578eb6c919fd893182223ee13fc01d55edd6bb1fe8216e8a5de2047ca7e1b5a1d8b255c59537cf822866ce1cd04cbda95b52f275f7c026a4467f2919b023d397fd293e26237c32b95c3ee10d7cc6d5d482e526136d6ef0c951f504d1a9d6de09ef7ad8b46ad59d1d4833df7eec354d1f8916bfc2f033b43fa6cbff6c3a03bd3fd52d8a371349f5f711cc3135c8a10dd2996e254a28185a4f6e8981b10ab15881d8cabe76c5e1238fe2923dfab713fc35d974c173bf24cb41d1b8f169c2e8971720dadb3a29a40f2de10c6c976191049072b0f9055a60ed5df6dfb95c09b06248d4e5494be79aa11936c226d26f260c2a8baa36c7a4d2a9eb068640528812a15e1d716f71a6cbc29a0a3cd47589d7fd4c4debe1824284e8322835ee13e7153c9f2208b7740e4058fa8503dc4656aebd3ee0fa60fedf7e907b85752b66cdc21b540c31881bc8004c7fce9ea80e7fb235486b5f1d0321c68a0e44cd5f15e21f27c402754a2f7c1387720e959e94abeb4db216a37e59b066bf338fc6f2e6cf3746392d5a6679d182f01b6c7128a28362eec30b4dedc7356616328be64da23c0f61f9b46a42be70546ec111b8adfeaf1efec46fe5d11758cc765262b8d611d0b1614dc02d47c90191ebad24f59571d62766fd6df3920fc0a2c9dc3cc1f6fa34242c7d792add612b414e28cead47c3a0860fb62a00987816f0f618408b15261070acd106e96d4d966d7f78376a2dbcb742e037d1934a1901bce54e979d9c5e0b9ec79190f25d56eb1d65e586b3ae24c063c0c7883512bc2a107ec6687ff168cdb467043ece1744d257eab9e41132c266f299b0776d572738f3a9c7dcba7e0cffbd7373390401dff225f53a780b215f4ef65238c8c38223d46e4e9b1bd5aa1449bed326a81c85eef48e6fb26b29e4c32377d3a8a0bff978a68755884c58dc4652c16f65b49e0a3b7f9b3e67e4f3e1b68b7e04482aea25ee5548a6d798cb7e6cc3cd2f788513f88c3c524ba20cf281002e11cd5f8bcb6e4d8ab929d026b7f74c43ebfba64203b6aad3bd7eaa0aad2c68b63b1637eeeb3d5cece1c7ba1fa4afaf7b22bb3914f4ae5debe4bfc907ac4bb8c801c71679d0f8e424c866dfaa180e5c127a57772270476c2ccdf7452b7844b60f6dc845540409add976ef85f09d7c1db1fbb7a995fee9a140820c679d98812b3086010ca80fd67fb4f44bf518ba61b800aec3169427fcc2cc0be877869468ded6545ab29d77c9225d4960774bf825f6a69a64084871e8987b6e71bd0df56399a7e0bc815ac6485d7b7d1852b1dd309f4cc780c5d86616ebf2b591805b42d9224b310dbf0883bdfab6995ad071f3ea7b993e00966d8eec83dce82f0a970332426b4f37b5ce378fbfb8a30d37b4c2bc513606cdc32f70d327df0d33a1eac1d5c1af4320abd569267526a61bd0a1d10cebca27cd94459434a1a32e848e7c022c67be14b2e844a1eae4aba76be361a8430ffeaaea51d88275b7d1520c1974519efc41cad3b6446843d3edb0e5b81bcfca867a960b410fc300321182b289fb339347df6e6d5bfd44990b94c87196f8cf0718e5f318ad13de3bd90ac55e28383273114107672096c0545549f8f7c7202e648ce8caf8dd0b5b90766523f83c54d5a7220e9da94d3861dc77b4475f91ba7748ac2a22951920c366cfc9a4690e76a49542ef391b2a0ab199397cbd913dee2f1b3e5403d6a97a9c24aedf5197e6c728a6398ce1a5ff3537f46549627612e6e0440b0d75a3d4407134d94f316b0c6fe842ce8ca02f13e07b53c1c53ff45ac7112ddbfe81e4e49bc7fd18c04ccdc7956dd2cb987ba1af34061f17965bf45bbc4b3d76ce2e811fb228e735dbaba660613dbcf6577ce31b595fc12d64be5f5fea15dca3268563ceae1b5af64755dc1ffce26a1772aadd9f760e9fcbd8711bac7cf7722cae8c7038b629be25ac52594c8ee442f8900d7883b39c23bb997b128a987967d70d4d91a7f3d87b88b4ab032f3ec9ae605aae9a0e3990b4c450e42a436724246decd0af618cb3f9e80567c410351b151677942c893072b9ada5b54d1e107f0fb5f21bb0afaa3fa10c478e83369b61dfe390c7173cc0cb9c3f3ff56262bb139179c8387ed97506d9be232928ea9724738f4d50416f0f21c442c7ac51589266137f152fff27148f0ac4403f9a7451eb3be25536946a48ff997ee4e20248ba02fb9082061de1b0629de748d8c31cf23e9ea45181f77491ea83ba3fa05c795e6fb274b7c7be4e7008f8efe0fc8a2aa2a5049ce83a51d7126ceac080ed4935a433a1f35b7accb77d0885a4b2b4d7e588a9d593c3688cd9f50c36564ed2b1c2b4d82fd516252e64feeaabce66079296cdd17a518a138fc35f53cf4551567a69b7e6c3e192d2cc9d1c37d134a4fea48598a6599ee44342dd7ac71e5432818d72d5e3c7e074888eaaff76619f13a0f3fa12afdb4279018d6e6ef2894d995bd2253559a29b67505cd2ce2fc2d75bf5683d63746804f25458c0635c79f62ded31ca00cfbcd711311e5fb2ea5ca42505eb95b27d69adf7458b19808b5719973e93a85dce7d5f1a33bc97d23097ce19d9654c275344052fdb0ec2ed09897c7f56de0875dd4dfa2b5e1ec35788db1cde78bca8ec7d63d4431ec903d35e79e88b3efc327084946fecbb2d2a687b90571deadaf226832ce2da16a5235a108d2466fdd36e754bba870451cf162e901e477d38a57100ee09f79dcc886ca9a92ffab69b4d04acbb270a1c28edcdd04fedb4a769076fa04461da34475c24e9b1c6302421513b3e5b43c0db497098774065664285e7322e109c54468f079441aeba8f5796c65d53b37770eabb3ebf4becef24f7952c03d3d7212d7bad7304da2a72dff80296b0124c29e4f086418a73daf1b86e9fc02ab6235a2d7da886bbdbac58e8ae6ea87da4adc3e296b35f411892d5e84eae8aef017bae1bf1882a036dbdd37122e1e40b315eab338449822b619d017d3fc7729ad96885c182564622b8e44b44fb6332a4e0e84b9f615091917782df3febf46072687148e5d619c161e3a92827e2fc7a8ed9d209edd5d174bb81c9d5f5f73c3cc0d61e5d5095d985081794d3e37fb5a41245a44fe78ad213f1a8fb4d690ea8eecc4bf72dca689e795f7b2eb240799598784ce78453255e567b149fae61d63e5fdeee85201bf77185ae38fe2e0579a43f0815220ffa517a25a0ec3d60a6f708753ee74f9f0ae959913c758cb0fc26eb7f0ac9dd5aa4b43068aa595dcb001a0e19345fdd1060e65f85525b619eeb297141c58fa1cc18f68707df82885736e75734077eb8dce5988a49381204619b293f6e8290f4cd20c088ea8890456c1205ebac006b676c61a4e2c636c1fd62d4cf5bec89f361c582ba39f9ecaa1d725a1dd26b674f72279cb56fe29490d5085dc3cfa522e16d1c078ba41d55f997d1d7d61457845162745d713a8699a813ba00aca37f9582a23b77dbd13c09a43bf151d9ba5a9e9abebd6e804a9b8e313fe28332dd6429fd87889a54c63f51d4913a90cdcc5bfe510e69958ba707bb52e2e7affe873b277ba46c389c8d0f75b122155b5b5041ed9fdbe09b3a5ab4683483314cb8a8ecd7238250185b2e92bd6275e87b2b50f6b1acab8948346a88ddffaa282208495e811ea89a033aafb27110121cb9e4d361929f09ce6322df6d61dadf34f894717b6d939eb4c1e01a56d8e2821adb2ee26adaa07a16b6abc24a3eedabbd9807282ae3abed041af776663b014c49a9b384f9cfd988ca07781a06ba61952bc80776532a8e1cf4d624ccc9e294f810ed18c1f6bb6fba501f30ef8b1e5e26e6513c64de8b63b3eabc11236915c40fd96d08a149e48d9811c67c49c0b20be456fb50f9b44e523b509566832d1cb9180bf2292ddb9359ab75c304318dbd9159e38de83ebbbb853b8d29caf5fd3e9a9b0d44236c920ffb7ae5e06faeda89180df6d1af39dc19213b0940e67fc1c58f20492b9f6757a29c8ec7e366c98f5cc787f58d4af400b251c32ca2622c61f7c230266f45241392646d84959089957fc64f4a8a64770dcc3b5c5e16e501c61d58520cd7bcadac287aa185be96f6d23a3eed5b90a3c8edb0078d07661708d67e7c0f632dad0a0cac07b231261f182fd457e99267aff186a6dedf8f58a2487a6454ee9437bf4119663226ef94d4f8949738cc56d631fac2f5e8d95eb52bc99b15087705be9b5cbd9d248729d25c9deac90a1e0ea6d1e987e74c03dc445d941fdac1321f89e862de9b045c46a6610f17b3f465249f36c8bfc233e572cfddb0f0fba7a84a624f5c66a6fb2eaed98857059d1f2bff89099e51cfc408861c5625f4c0e160ef0f78513c073184c8337b7c9aceb2f7072cf174255628f382f56efc157198e274590a494806cde6fe7be286c090d652a4509751239f862ecc20cd3c3955f3b74308ae4d72eaf8dcb77b647e5e29b3c33ebca23d33f1',
+];
+
+const List<String> _digests = [
+ 'da39a3ee5e6b4b0d3255bfef95601890afd80709',
+ 'c1dfd96eea8cc2b62785275bca38ac261256e278',
+ '0a1c2d555bbe431ad6288af5a54f93e0449c9232',
+ 'bf36ed5d74727dfd5d7854ec6b1d49468d8ee8aa',
+ 'b78bae6d14338ffccfd5d5b5674a275f6ef9c717',
+ '60b7d5bb560a1acf6fa45721bd0abb419a841a89',
+ 'a6d338459780c08363090fd8fc7d28dc80e8e01f',
+ '860328d80509500c1783169ebf0ba0c4b94da5e5',
+ '24a2c34b976305277ce58c2f42d5092031572520',
+ '411ccee1f6e3677df12698411eb09d3ff580af97',
+ '05c915b5ed4e4c4afffc202961f3174371e90b5c',
+ 'af320b42d7785ca6c8dd220463be23a2d2cb5afc',
+ '9f4e66b6ceea40dcf4b9166c28f1c88474141da9',
+ 'e6c4363c0852951991057f40de27ec0890466f01',
+ '046a7b396c01379a684a894558779b07d8c7da20',
+ 'd58a262ee7b6577c07228e71ae9b3e04c8abcda9',
+ 'a150de927454202d94e656de4c7c0ca691de955d',
+ '35a4b39fef560e7ea61246676e1b7e13d587be30',
+ '7ce69b1acdce52ea7dbd382531fa1a83df13cae7',
+ 'b47be2c64124fa9a124a887af9551a74354ca411',
+ '8bb8c0d815a9c68a1d2910f39d942603d807fbcc',
+ 'b486f87fb833ebf0328393128646a6f6e660fcb1',
+ '76159368f99dece30aadcfb9b7b41dab33688858',
+ 'dbc1cb575ce6aeb9dc4ebf0f843ba8aeb1451e89',
+ 'd7a98289679005eb930ab75efd8f650f991ee952',
+ 'fda26fa9b4874ab701ed0bb64d134f89b9c4cc50',
+ 'c2ff7ccde143c8f0601f6974b1903eb8d5741b6e',
+ '643c9dc20a929608f6caa9709d843ca6fa7a76f4',
+ '509ef787343d5b5a269229b961b96241864a3d74',
+ 'b61ce538f1a1e6c90432b233d7af5b6524ebfbe3',
+ '5b7b94076b2fc20d6adb82479e6b28d07c902b75',
+ '6066db99fc358952cf7fb0ec4d89cb0158ed91d7',
+ 'b89962c94d60f6a332fd60f6f07d4f032a586b76',
+ '17bda899c13d35413d2546212bcd8a93ceb0657b',
+ 'badcdd53fdc144b8bf2cc1e64d10f676eebe66ed',
+ '01b4646180f1f6d2e06bbe22c20e50030322673a',
+ '10016dc3a2719f9034ffcc689426d28292c42fc9',
+ '9f42fa2bce6ef021d93c6b2d902273797e426535',
+ 'cdf48bacbff6f6152515323f9b43a286e0cb8113',
+ 'b88fb75274b9b0fd57c0045988cfcef6c3ce6554',
+ 'c06d3a6a12d9e8db62e8cff40ca23820d61d8aa7',
+ '6e40f9e83a4be93874bc97cdebb8da6889ae2c7a',
+ '3efc940c312ef0dfd4e1143812248db89542f6a5',
+ 'a0cf03f7badd0c3c3c4ea3717f5a4fb7e67b2e56',
+ 'a544e06f1a07ceb175a51d6d9c0111b3e15e9859',
+ '199d986ed991b99a071f450c6b1121a727e8c735',
+ '33bac6104b0ad6128d091b5d5e2999099c9f05de',
+ '76d7db6e18c1f4ae225ce8ccc93c8f9a0dfeb969',
+ 'f652f3b1549f16710c7402895911e2b86a9b2aee',
+ '63faebb807f32be708cf00fc35519991dc4e7f68',
+ '0e6730bc4a0e9322ea205f4edfff1fffda26af0a',
+ 'b61a3a6f42e8e6604b93196c43c9e84d5359e6fe',
+ '32d979ca1b3ed0ed8c890d99ec6dd85e6c16abf4',
+ '6f18190bd2d02fc93bce64756575cea36d08b1c3',
+ '68f525feea1d8dbe0117e417ca46708d18d7629a',
+ 'a7272e2308622ff7a339460adc61efd0ea8dabdc',
+ 'aef843b86916c16f66c84d83a6005d23fd005c9e',
+ 'be2cd6f380969be59cde2dff5e848a44e7880bd6',
+ 'e5eb4543deee8f6a5287845af8b593a95a9749a1',
+ '534c850448dd486787b62bdec2d4a0b140a1b170',
+ '6fbfa6e4edce4cc85a845bf0d228dc39acefc2fa',
+ '018872691d9b04e8220e09187df5bc5fa6257cd9',
+ 'd98d512a35572f8bd20de62e9510cc21145c5bf4',
+ '9f3ea255f6af95c5454e55d7354cabb45352ea0b',
+ 'a70cfbfe7563dd0e665c7c6715a96a8d756950c0',
+ 'd8fd6a91ef3b6ced05b98358a99107c1fac8c807',
+ '4a75a406f4de5f9e1132069d66717fc424376388',
+ 'a135e32581bb06289b8c83f040e9421ec79bbe01',
+ 'b22b87ea30f4050913f8f0241fc2ae2c319f52e7',
+ 'd742931bc51d4d44ff938783be73dc7beccc980b',
+ '20a3a677c117c61ed3bb19e2ac77f69987896d0b',
+ 'dd4374e29b17e2ec533813feddc5253765cd37ac',
+ 'fdccb6e47645928fbbd51ccddc6cef48d6afc011',
+ 'e50a54470f59fb9b654bffcb4c353e58b683ada5',
+ '9b3ed390fbb328a1641fca93691763000523569d',
+ '09bf403d8a9d2334f28fab704d9cab87da43731a',
+ '7f32d7486bde22ed00eeeaae073858144dc3ee37',
+ '37b7277fc606556160f9bc28b06fd55f4424d9cc',
+ 'dbc7ace190c9dc985d2c3fbed5fe90328352b3b0',
+ '796135c20bfd2dfc7a1ff2087aba7f93b2814ef4',
+ 'baa2e9bef9dd836d3d37013c296ec31919fe7840',
+ '3d40608ab9bce3f372bb29a62ff3fcc68e48385d',
+ '8bce8c69fd802389c805d2945c7499c9dd279ea2',
+ '064c6fccb707f0f3929084eeb0298e800d542370',
+ 'bf2d47d4435ace28d3c336acdd6313aa8f9c41fd',
+ 'efe28211673e7bb68657243df023d4b70c0e5325',
+ 'afc01657b55fffd0c739cf017294a8379f60c2f9',
+ '8a148c03dfc846b484ec15809d9cbfaa4b74a060',
+ '8ff89c859a6ffa3d3874d3d1be4125f9de62c9bc',
+ 'c0af54b14db7ef0c68b1300b7350fd2a82fe96e9',
+ '4c66ccc9d6a9f1d988bb7ae0fb41be3a1e1a648d',
+ '0f5176527280b8e3fa69a6c14ce1f759d6e9c67c',
+ 'eef7dfc20c57895d31ad15aaab13cf710aa0d739',
+ '93239fa543e8bd68b59a4bd55a7be068f18c5ea1',
+ '2393e09e218261acb91ff9fb4783253e9b44b9f6',
+ '7d90c7a14fc71e228a4f4fd191d3b7ea98c6509e',
+ '07f84b3990bbeb9fc280681dc25d96bf8626992c',
+ 'bfa71db73fb3d8103fd7f2965eb89f2394f0b751',
+ '92588ff54cd3903ceab98afd39f1854835f54492',
+ 'd947e8fd7fb5d805d70c1a21bd6eb5368f312885',
+ '66ba577de1222642fd9e3b2a6e20741905356c2c',
+ 'b1542439b3590f2e43fa30baaee0ed11a9c46bab',
+ '18de122bf588dc3d1eca78661673fa8d8acf254e',
+ 'e4ae28261f24a10355fd1aa1c2554592a331ceda',
+ '290d124e77abc911e4be375232ff1798c4b48cb0',
+ 'fc8456f92f8a8bb38a3248e988a3e12271061510',
+ '94a5d77bc308382a8aa317be7bad0a870f006c67',
+ '515d2a8972936e6b45b9b457d9eab8e2f62cbc3d',
+ '7fb74b4dde68f8c5e0d9b27878040123a9ed5fde',
+ '534702c37c6fa8e1bde879ce4d87aa10c4cc8c8a',
+ 'cdd84a87e1457601d899b2abbe2e0974784491b7',
+ 'b51232c68cd82ce9ffb4bc1fea0ea9f71354314e',
+ 'adf2ebb0c337c89334fe8580b53dae70b25d00a7',
+ 'e2eb69f7d6fab720a3f038ac773b3274b6d113e9',
+ '9c5bf7e24e8764745642e23e7cdc5fd44f91bbf9',
+ '7731a20dfb7725e61d7aacebb41afbb4a05ffbfb',
+ 'fa47305e71a8e1e536486a806cbb839d813caf9f',
+ 'a94d7bf363f32a5a5b6e9f71b2edaa3f2ae31a61',
+ 'ed76c5bf4ada6a2092e6dbb40ff40909b8ec06cb',
+ '6a5fc2f4a741f17a2e62b198d65e4a5ff6a1e748',
+ '280ebf4f434e4134fce0d3f7581c2434bab1efbb',
+ 'af75e59940783e84761dbe59727ed7908a8709b5',
+ '06f0df10ed7bc4b446f9271fdbe6ac81e36bc142',
+ 'e900914d8a38d14b307d1eb8e569a509421d811f',
+ '581562f2a9f3097f760488cbe87f823d0fa7524c',
+ '844e1f50dd792b283902e66bc1086a273c05d511',
+ '61ca85608418090c78ebe8614bb2b80113fe130e',
+ 'a1f35ddd6a6275fd21bb8c2ebf290a06a2563df7',
+ 'b09d1a963ba9bf92907707b7d48b96e0d37dbd79',
+];
diff --git a/pkgs/crypto/test/sha256_test.dart b/pkgs/crypto/test/sha256_test.dart
new file mode 100644
index 0000000..bd8d23f
--- /dev/null
+++ b/pkgs/crypto/test/sha256_test.dart
@@ -0,0 +1,371 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:crypto/crypto.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('SHA2-256', () {
+ group('with a chunked converter', () {
+ test('add may not be called after close', () {
+ var sink =
+ sha256.startChunkedConversion(StreamController<Digest>().sink);
+ sink.close();
+ expect(() => sink.add([0]), throwsStateError);
+ });
+
+ test('close may be called multiple times', () {
+ var sink =
+ sha256.startChunkedConversion(StreamController<Digest>().sink);
+ sink.close();
+ sink.close();
+ sink.close();
+ sink.close();
+ });
+
+ test('close closes the underlying sink', () {
+ var inner = ChunkedConversionSink<Digest>.withCallback(
+ expectAsync1((accumulated) {
+ expect(accumulated.length, equals(1));
+ expect(
+ accumulated.first.toString(),
+ equals(
+ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8'
+ '55'),
+ );
+ }));
+
+ var outer = sha256.startChunkedConversion(inner);
+ outer.close();
+ });
+ });
+
+ test('vectors', () {
+ expect('${sha256.convert('this is a test'.codeUnits)}',
+ '2e99758548972a8e8822ad47fa1017ff72f06f3ff6a016851f45c398732bc50c');
+ });
+ });
+
+ group('SHA2-224', () {
+ group('with a chunked converter', () {
+ test('add may not be called after close', () {
+ var sink =
+ sha224.startChunkedConversion(StreamController<Digest>().sink);
+ sink.close();
+ expect(() => sink.add([0]), throwsStateError);
+ });
+
+ test('close may be called multiple times', () {
+ var sink =
+ sha224.startChunkedConversion(StreamController<Digest>().sink);
+ sink.close();
+ sink.close();
+ sink.close();
+ sink.close();
+ });
+
+ test('close closes the underlying sink', () {
+ var inner = ChunkedConversionSink<Digest>.withCallback(
+ expectAsync1((accumulated) {
+ expect(accumulated.length, equals(1));
+ expect(
+ accumulated.first.toString(),
+ equals(
+ 'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f'));
+ }));
+
+ var outer = sha224.startChunkedConversion(inner);
+ outer.close();
+ });
+ });
+
+ test('vectors', () {
+ expect('${sha224.convert('this is a test'.codeUnits)}',
+ '52fa5d621db1c9f11602fc92d1e8d1115a9018f191de948944c4ac39');
+ });
+ });
+
+ group('standard vector', () {
+ for (var i = 0; i < _inputs.length; i++) {
+ test(_digests[i], () {
+ expect(sha256.convert(bytesFromHexString(_inputs[i])).toString(),
+ equals(_digests[i]));
+ });
+ }
+ });
+}
+
+// Standard test vectors from:
+// http://csrc.nist.gov/groups/STM/cavp/documents/shs/shabytetestvectors.zip
+
+const List<String> _inputs = [
+ '',
+ 'd3',
+ '11af',
+ 'b4190e',
+ '74ba2521',
+ 'c299209682',
+ 'e1dc724d5621',
+ '06e076f5a442d5',
+ '5738c929c4f4ccb6',
+ '3334c58075d3f4139e',
+ '74cb9381d89f5aa73368',
+ '76ed24a0f40a41221ebfcf',
+ '9baf69cba317f422fe26a9a0',
+ '68511cdb2dbbf3530d7fb61cbc',
+ 'af397a8b8dd73ab702ce8e53aa9f',
+ '294af4802e5e925eb1c6cc9c724f09',
+ '0a27847cdc98bd6f62220b046edd762b',
+ '1b503fb9a73b16ada3fcf1042623ae7610',
+ '59eb45bbbeb054b0b97334d53580ce03f699',
+ '58e5a3259cb0b6d12c83f723379e35fd298b60',
+ 'c1ef39cee58e78f6fcdc12e058b7f902acd1a93b',
+ '9cab7d7dcaec98cb3ac6c64dd5d4470d0b103a810c',
+ 'ea157c02ebaf1b22de221b53f2353936d2359d1e1c97',
+ 'da999bc1f9c7acff32828a73e672d0a492f6ee895c6867',
+ '47991301156d1d977c0338efbcad41004133aefbca6bcf7e',
+ '2e7ea84da4bc4d7cfb463e3f2c8647057afff3fbececa1d200',
+ '47c770eb4549b6eff6381d62e9beb464cd98d341cc1c09981a7a',
+ 'ac4c26d8b43b8579d8f61c9807026e83e9b586e1159bd43b851937',
+ '0777fc1e1ca47304c2e265692838109e26aab9e5c4ae4e8600df4b1f',
+ '1a57251c431d4e6c2e06d65246a296915071a531425ecf255989422a66',
+ '9b245fdad9baeb890d9c0d0eff816efb4ca138610bc7d78cb1a801ed3273',
+ '95a765809caf30ada90ad6d61c2b4b30250df0a7ce23b7753c9187f4319ce2',
+ '09fc1accc230a205e4a208e64a8f204291f581a12756392da4b8c0cf5ef02b95',
+ '0546f7b8682b5b95fd32385faf25854cb3f7b40cc8fa229fbd52b16934aab388a7',
+ 'b12db4a1025529b3b7b1e45c6dbc7baa8897a0576e66f64bf3f8236113a6276ee77d',
+ 'e68cb6d8c1866c0a71e7313f83dc11a5809cf5cfbeed1a587ce9c2c92e022abc1644bb',
+ '4e3d8ac36d61d9e51480831155b253b37969fe7ef49db3b39926f3a00b69a36774366000',
+ '03b264be51e4b941864f9b70b4c958f5355aac294b4b87cb037f11f85f07eb57b3f0b89550',
+ 'd0fefd96787c65ffa7f910d6d0ada63d64d5c4679960e7f06aeb8c70dfef954f8e39efdb629b',
+ 'b7c79d7e5f1eeccdfedf0e7bf43e730d447e607d8d1489823d09e11201a0b1258039e7bd4875b1',
+ '64cd363ecce05fdfda2486d011a3db95b5206a19d3054046819dd0d36783955d7e5bf8ba18bf738a',
+ '6ac6c63d618eaf00d91c5e2807e83c093912b8e202f78e139703498a79c6067f54497c6127a23910a6',
+ 'd26826db9baeaa892691b68900b96163208e806a1da077429e454fa011840951a031327e605ab82ecce2',
+ '3f7a059b65d6cb0249204aac10b9f1a4ac9e5868adebbe935a9eb5b9019e1c938bfc4e5c5378997a3947f2',
+ '60ffcb23d6b88e485b920af81d1083f6291d06ac8ca3a965b85914bc2add40544a027fca936bbde8f359051c',
+ '9ecd07b684bb9e0e6692e320cec4510ca79fcdb3a2212c26d90df65db33e692d073cc174840db797504e482eef',
+ '9d64de7161895884e7fa3d6e9eb996e7ebe511b01fe19cd4a6b3322e80aaf52bf6447ed1854e71001f4d54f8931d',
+ 'c4ad3c5e78d917ecb0cbbcd1c481fc2aaf232f7e289779f40e504cc309662ee96fecbd20647ef00e46199fbc482f46',
+ '4eef5107459bddf8f24fc7656fd4896da8711db50400c0164847f692b886ce8d7f4d67395090b3534efd7b0d298da34b',
+ '047d2758e7c2c9623f9bdb93b6597c5e84a0cd34e610014bcb25b49ed05c7e356e98c7a672c3dddcaeb84317ef614d342f',
+ '3d83df37172c81afd0de115139fbf4390c22e098c5af4c5ab4852406510bc0e6cf741769f44430c5270fdae0cb849d71cbab',
+ '33fd9bc17e2b271fa04c6b93c0bdeae98654a7682d31d9b4dab7e6f32cd58f2f148a68fbe7a88c5ab1d88edccddeb30ab21e5e',
+ '77a879cfa11d7fcac7a8282cc38a43dcf37643cc909837213bd6fd95d956b219a1406cbe73c52cd56c600e55b75bc37ea69641bc',
+ '45a3e6b86527f20b4537f5af96cfc5ad8777a2dde6cf7511886c5590ece24fc61b226739d207dabfe32ba6efd9ff4cd5db1bd5ead3',
+ '25362a4b9d74bde6128c4fdc672305900947bc3ada9d9d316ebcf1667ad4363189937251f149c72e064a48608d940b7574b17fefc0df',
+ '3ebfb06db8c38d5ba037f1363e118550aad94606e26835a01af05078533cc25f2f39573c04b632f62f68c294ab31f2a3e2a1a0d8c2be51',
+ '2d52447d1244d2ebc28650e7b05654bad35b3a68eedc7f8515306b496d75f3e73385dd1b002625024b81a02f2fd6dffb6e6d561cb7d0bd7a',
+ '4cace422e4a015a75492b3b3bbfbdf3758eaff4fe504b46a26c90dacc119fa9050f603d2b58b398cad6d6d9fa922a154d9e0bc4389968274b0',
+ '8620b86fbcaace4ff3c2921b8466ddd7bacae07eefef693cf17762dcabb89a84010fc9a0fb76ce1c26593ad637a61253f224d1b14a05addccabe',
+ 'd1be3f13febafefc14414d9fb7f693db16dc1ae270c5b647d80da8583587c1ad8cb8cb01824324411ca5ace3ca22e179a4ff4986f3f21190f3d7f3',
+ 'f499cc3f6e3cf7c312ffdfba61b1260c37129c1afb391047193367b7b2edeb579253e51d62ba6d911e7b818ccae1553f6146ea780f78e2219f629309',
+ '6dd6efd6f6caa63b729aa8186e308bc1bda06307c05a2c0ae5a3684e6e460811748690dc2b58775967cfcc645fd82064b1279fdca771803db9dca0ff53',
+ '6511a2242ddb273178e19a82c57c85cb05a6887ff2014cf1a31cb9ba5df1695aadb25c22b3c5ed51c10d047d256b8e3442842ae4e6c525f8d7a5a944af2a',
+ 'e2f76e97606a872e317439f1a03fcd92e632e5bd4e7cbc4e97f1afc19a16fde92d77cbe546416b51640cddb92af996534dfd81edb17c4424cf1ac4d75aceeb',
+ '5a86b737eaea8ee976a0a24da63e7ed7eefad18a101c1211e2b3650c5187c2a8a650547208251f6d4237e661c7bf4c77f335390394c37fa1a9f9be836ac28509',
+ '451101250ec6f26652249d59dc974b7361d571a8101cdfd36aba3b5854d3ae086b5fdd4597721b66e3c0dc5d8c606d9657d0e323283a5217d1f53f2f284f57b85c8a61ac8924711f895c5ed90ef17745ed2d728abd22a5f7a13479a462d71b56c19a74a40b655c58edfe0a188ad2cf46cbf30524f65d423c837dd1ff2bf462ac4198007345bb44dbb7b1c861298cdf61982a833afc728fae1eda2f87aa2c9480858bec',
+ '6b918fb1a5ad1f9c5e5dbdf10a93a9c8f6bca89f37e79c9fe12a57227941b173ac79d8d440cde8c64c4ebc84a4c803d198a296f3de060900cc427f58ca6ec373084f95dd6c7c427ecfbf781f68be572a88dbcbb188581ab200bfb99a3a816407e7dd6dd21003554d4f7a99c93ebfce5c302ff0e11f26f83fe669acefb0c1bbb8b1e909bd14aa48ba3445c88b0e1190eef765ad898ab8ca2fe507015f1578f10dce3c11a55fb9434ee6e9ad6cc0fdc4684447a9b3b156b908646360f24fec2d8fa69e2c93db78708fcd2eef743dcb9353819b8d667c48ed54cd436fb1476598c4a1d7028e6f2ff50751db36ab6bc32435152a00abd3d58d9a8770d9a3e52d5a3628ae3c9e0325',
+ '82829690aa3733c62b90d3297886952fc1dc473d67bb7d6bb299e088c65fc95ed3ca0f368d111d9fdcc9476cd4065efce7c481be598537f3f53bbbb6ff67973a69837454499e31398b463288e3aafb8b0600fdba1a25af806b83e1425f384e9eac7570f0c823981ba2cd3d868fba94648759623991e30f997c3bfb33d019150f0467a914f1eb79cd8727106dbf7d5310d0975943a6067cc79029b09239511417d922c7c7ac3dfdd8a41c52455b3c5e164b8289e141d820910f17a9668129743d936f7312e1604bc35f73ab164a3fddfe5fe19b1a4a9f237f61cb8eb792e95d099a1455fb789d8d1622f6c5e976cef951737e36f7a9a4ad19ee0d068e53d9f60457d9148d5a3ce85a546b45c5c631d995f11f037e472fe4e81fa7b9f2ac4068b5308858cd6d8586165c9bd6b322afa755408da9b90a87f3735a5f50eb8568daa58ee7cbc59abf8fd2a44e1eba72928816c890d1b0dbf6004208ff7381c697755adac0137cca342b1693',
+ '5f664be0c0f3d2fc9a1a7ed6b515ef9c52ad1c7fb3acf2c2de943e109f91cc12ccadd041cc4386f95ab616cf8762ba25fed322fc8c351809e00c600a8f26e25a5bcd0bc3b44170947f65b4f417b8ac769187c2ee4561978289cced04c036c37f942ec10f7fd4d7f6908e22ed6cfd0fb89330c2fde417b956643aaca53baab8a8ff38bdcd35e60547159b26618e1b29128a35ebd2733fc4adf6bf6796076b09fd2554c6a4df5e40ae97f389f986f843ad00000515f9c001aec9c4e47e2c60fea78de8a33c8423d1539dfe125c5b7ea4b17cf8d86e7f84b88264afec06b370dfcebf5e1d3e2c1f005faf248b321593964587852b830c7231504fe947d6a385f399441cfc52df3914fa55cdba25bd215f91a80fc8ffa872b34113dbbd9504868331a38c081fa659574b186169db590f48be67fe75885b6c877d37ec16ebde5ad7be6414084e88670f7b7f485efcf44599f44cbbfbc62e48f62b438319823aeb3767101ec6868e4c85b113ea623193ab9a5ae0ac226328ee4674bf0a90ff1f20eb542e110870bfee01165ab03c2240299319aa3ab1045247bf7f34e8410d96e13aae465597b42336cad2de00b67602a7cb5832cd7253b239ab752a85f452a6166e9de0523bf9c20c2a0c274396d5',
+ '9d64d891d99bb8aba23a29a8f69b32482714e031d31dde3317b046d000f6b7fc421fa8212d91fb66dc46d531b06faeeafd5ea40302a215351f746c0c42523ba5a3e98bb7b13870d04bf3e0e13425c4fdc11a505ed57c90a90fbc447242b3ee03268a29594dd73c705808efc16a059e08dd118b4a34f178175151760de963f89d34c92b12e9b58ace694fadd73a576193b80bfed0074bf5074cfba9e21da980fb366f39e76d1b8073e88ebf2d8d623827bad051f736d02e02688185fbc7ccaea69244fae2c15146e63b8ed0cb496f494b4b272bc8aac94c8f0dadb45fd015ab25b210170acd9f05afcc1786b758c6bc87d3d93449497d7637a345db161ecc9f00fc9b37677a4de55701f189fba0afba63baaf1584fc36d5819212a5299b39b2c0daad0302aea20d6544e3829f0b726b68686e7681ac3a91f543dcb79f2da30aecb30d23e252e7a661fcb619a98056f61d46e1fe473fd3d11b1c6bbc80be54d20cee843e0f4f65d7d49032f523e6a4830abacf56de9f46bd7c86865ad4359230a9f5dafc928b61c9456a1fbf1427a53cb82dff264eb2de7f9feaf739a47aa64c4a2fd70772f026a33cf1451e852a9e47ae083a159f62e23c0cae8402f775d84f77044204b765fb8e418d6cbb7dd7dacc74b148cbda95991f4c3cf65dd60e6f61b8dce59e6ad127b2dda65b3d0416a0f49392f1f107354c4de6fa14f1482db5a9961f867b921ef33697a4db4d22cf37e69211fd2f2c2944f16252a86755baf0509835ee433733a743f8f0b493e0eae8cb',
+ '7dd546397a9a0129861fb6815d419a307f90d259d55f3503961754126cd1b776d3236aa2c239b93f8e2837220b80057cf42050518d4f1c2c860840102394b2b19a5f05e4bd043055d8aa9178dd9332c2bef24a181bfd07881d448a37a241349a9a3020e9b021f0d12e4bcd6a1aa3a968a5adc795c7927e7f23743a6d30fec3989a3fc298e6b8811d56b3f2df0cd7f3d871fad0b0d83609795f3f569c16f3e9136433f3d9a6f2699f188b08c1f9589778ea806c51981031de9a4ee8ab9d4a2d73beb5bb9437f632c13e7b18f72a4d1db2d8e8a3604d497d169c48f7820a281721716d23b1e2ed63ea8e2a2869e7df0eed02d97dc5400876892dd68c09a8b7308345023219efff8581d24143ff7836f09031fc0368b976a29f15a0ae28be1fff02011df1b2a6531ff0d0676ea124794e052df93c32ffbb8bc11b4d65c793880d076f6566654e12b99e5145b33734d1adb3be7731095cfeb9550985b9ce7019e0f855839b1b3163dcf31c8319a9f0659702ac1ee8d71667b3c5a5f2b3259dfa023e7c1e98ba956f0e57fbc8a8dfa05e935abe976b8276200177b83a5ab46254fb42acdf632bc35eda32b4bc69c18ce32a23bd8ac2f3c44e2bd50905b764074f516bac6d06570357c5ec10086338fc1de2c5729ef313481cb94562fcd01bd3128e20467289259d8259edd7549f2a373346a8a27c08c94ab0343189c6afc20fc639cb4093f27cd8081d9ed1472381864edb3518cc08fc11322400470c5c420492dbd3637a4b46fd119965c58af92331962bd29b35fc96e6cb0f1a6476dd81f79ffba077cf9c6a54c456ab7dc529fa8032bde8f25feb7e11a27fe7a8ab3c693314219a4439ebd0254adbd9bf9fb9ecba4b19e0e6f3dd9cdae1fbcfdb5481e1ff1ad62991614260b8cbb05554c0b3e32908c8203f99',
+ '42172d5fdae447c4cfdd13836bb8b833c0cb85f7327f92983501a4d7583a5201830266c37c908640b0351461314b526cfb68cad97bd7ed615248fa5756c6213bd9eae98d2f4ecfdf6a452f2e68c9687210b53c74d83575e08a7ace9b49b21056cf377c64f80669c884742e93181c426d871ca2715081733e68ffe94a39e6677aea51e8f0e1a09d258629d7374a2b2884e903c577eba32fa2713f130d2e496eceb4a0f4daf105b31bf9cef4c306de62dfbcd46e2fb283f1352fa3138c31c56d7bb48d6aca301bf3d464ca4bde521d37a78bf66340ac09011e2991b36e4941aba8727e1067a7cba4784f85a53138d0f104dbd16d54e21ea686e772b95c7fa6717e77dcb05a5dfe102e4267c963bfdfd61d36cd53105aa82a95f2afeefddada07254a10104a5a9a7d1fc6d8811def322f1b2352df1e1e90d372d1ae1afa62c6b5c47380f9e0a788347362409307d1b243252bc8d72636bfea460cd905fa1f52c3847b9632c44bb17d519f07c8c86c455c64d49704cfa81cb6382c9776a61a67788ce9b9859d4efc9fe10495e809c9d4c000a9272ec27e8e8171b84f37a65aeb1d054550b814b950e44d1952bb71ee48b8202fe11ca7c0ff9119386b0ea1e7c8fa1618c594d0939792ba66a708a9e5878cecf02b9825745630573452c43fcae457e8e87fe17ae4b8f25274fa9958b67b848d736e68e4a47ba453356c21290a297ca240e667b9b59b4c3dcab43427670ae82b4013558d57553536c221ec07af7db06da562ed360d28e8a3f03ea2be021effede08027c896ce2d2864d9ef80c2ca3d71a15b3d98f4470dab6ffeabc48e9e12fcda1fa63c68cdd250a2fcf03d49f769d5bb391d8872e0057dce5e16e214726980b6579a92d53b6ed704f2b8e64fec7dc27c6456ae90db164295c5adbf9b824ca0fd8fca71e5fe47e412230f22d991c05f6a45b0b1552089224d9b36042bb603843631ff82a1ffa5a055f8bc99f1ce7cd50f42f23aca97a6447d477a58ccf6d555e9a4016d1026d23354d789f49e8bf74bf3c4e6f0f529b4d1ad334164872a0c3b9e5098d93a',
+ '9c4bdc3b1af6ab9dc7bd2dd90e2e429a07d5dd5c48bb7016fe2ca51d3cbd4f45928ea049e2cd9c6d6f7bcd613773396983a891bbbcaeab28807c32fff5709d2f5d935dabeb1f5b13d53ea190ab155700e701f253c520a834551427ecce03868425e27c2adef4d0d7238d102e131c86a65c6868eb0c1a4f82a47ceaac6e80f48e1104638e6354e3007ef182021691ada40a665b4d38a3885a963de5077feece934a807c9f21487cd810f15fd55d7bb4421882333ff2c43b0353de7fc5a656fcdcf8de2e25c1d783a50115106f8fe282c8ae45588ae28450c602e71fad8dbf65b141a7e0e7ea0ae0b079e5fb9855ce017ef63633f6afebafebcbe02f89dc31f3595062fcae45e87b419fea8918574818ac15dd2a4a020141bad752161f3bb58d1e4b97e9427a793c9f9bab22b63c57af9936c2a65082cfec7a4ec53c3750511b465bcf0f6b30c50c1496b02f3bad04af8e7f6e10ced85c997558bf099bc60f861aa790d6f10fd5d1e6b88216705156fed31868ce8dabb031f11bcae51243f7b4e25865a69bc1b0755e28a8411ad15585b02a384a55a4d49a37c26d38636f108ee695d3e732eb5edec40faa1604d4092c6ddd67eaed6bcfbe8f73316a57f462fc6d8764017f38e8f6609411fff5037bdc51587c181fa7a98340569ce3b677f5e7c1559f5c474d55a379e06463b406b27ba5c4ff3bb1006bd39495380b48a3d23528280c6055d5adcf591a2baa0a84b6f2b14878ba6c201c95d1558d4bd41d00d0eb2834767076f861466bef3bbf25902abd0d70ff18acc4b140c121092490879e527c9e045fd83f4189fb36809b92470a113b6f717d4f6b0e29fe7faefea27089a44dd274eba48a576af18be06673e379f5f9fb7862af1a96d4372ca32bfbc2782bc2592cdc82df8b307573c3e76f6d61b06f9e7c9174d9308892b14f734485522d04ba96fa1948c525b17891e72feca98bc6dfe5d047aec48f3797199d25c101f33a7d180c12cced8fca21b32e5b6839ce26461ce8d0a33b2f4f666b73457f6cc58d2b1cdc1473ebb7ebf68f849ae9f9c1b65c87a1b6bf7bb102a4acbb4dc77bea254b0930c846a7e53a808eb19478d1ab9fa88fc2a10a6d5d77db433ee49f16ac296547d1d64c0961df46187cf21ca9d608b39c153b8df97ad7929ac4b3112551c2023e87e58efa7203d196ae5cde69881a031760294f0852',
+ 'bb64be5c71918756c611cd6e001dbab53e6bf9be164875537ce76367e5f9824cad7da126b6da63a0532b3fdd64dacab2c2703912ddab21c9a3d2826da44504927458803e5161c29d06108ef50fffe0dbfe8a78a81ee19627555b03904f0e50bad89c628c8a4f2fb5a969c29c4bb5859abc62bf6820115cd35a2dedb72d7bef2aa1f250f8a9cc2f5002dde4bc5e244056c2a0153a2d64f9377aee48ca87b5684c9701516af5ff4cd6db15fa3c91739978d9eb83068b02f7b97d471cb0a5e3438782726dcab7110daffab80f042ccb1866c9eb10b48312dec32adf7253cf2e094229ecac00382afa43276f28fc775346895a49c42c5dbd34bc4a5f519a4dbe41e7551817f4bd709cfa2ce24f0cba34aa4954ada756612a830ca56ec26d66ba73ddce9db58f910e7a3dd0b88b1c3c95cd0f7ebec21ad782521a03b5ccb4644a288c5c258fb7fb2a1d72da9ae514469f3541a1251c6106ec2a502cdb77578d98e65cc755ab5542ed0b03132f63dc20796c49858abd1137919215e789cb3f2ac938b5d6d71352af7ece564320105c124dfa8df293ae14b29812d1fe67d1528208a3ff5353cf948a05eed53214f17d64406577b0ebf650bf2a32ed371c9079df7bb1a20470e5051bacf1e6a7b410255d7c376d86389dafa66f7bcf5b51109d874ae906b1d75f8ca99961f36ba8743d4629f7d93e23ac18ae8e74e032ad5aa4c39ed393243044107ef4c563479725ae676e2e229e532a7220b0a68883d97578db9ff8b224229d7be0e6a69e00292c5e087463b06f711fa744fc9730187c69ff1774dfc9785571b418978b0c6107903771631eeb7824949e629bd13eb73f3f23bf461142e972c8a36d2efc1531d95920ea62e83b83158f3fc2b4dc1c29cafeca1a3c14833f21ab3029d3812137468f00ba99470856eb1b72ac703e3035c4aede717f72f64209204392b0a3983cf73bc12a31c5babb4f3d1f67f781e4a5d658460c36b201b9d332c4f2eae9e20894654a8252eab977e9ff2e3c702c9f40a703ea338a5d0e6e26e69b8facdc6763c413830a233d6d556068877303c8c8cde0b7b22ea3fa8427ab46b0b21c028f152f4f5409cd463f1c5d801354dadcc915287c8644a811cbad0a59eba262e6c3e57e20a5c9778d95938750b8261af009e0285b4ebfc12b4bc8ea2735a9a70d699d598f5e904a9bd88487df5f33ee8df0f5875bf2e518cf6b3ff3b9740d1301eb0367a267a76ef771b50436f1c17c3ae61eee855affd28596dcce169217cd49afe05163a8560a29c6eea75b5419ec7f532105df6f541ad531652346750ffe6d1ffbbeda0ae447ba41f91858728367e49fc77374',
+ 'e5098b6a0b1cfc57c6a76537104a39c48baecb15c6bbb46fbb0b745f9c9e5c05cfcfabb33786f7b7b5b0ce74eeec9eb84f87d2494fab3ec1f4d3bd9c99821890ee352a1d40964264fbf2c93c6ded2583cc75dcb27bf4fdb489cabcf97bfa5cc64b2352cfb0b3a707a0579eb713b697cd0b5e3377d1feb9f181d7b89cc86dee4fed8269f10e44ec48adc6940c6badbb40122c1dc2d9323920e4e1fbad0b4397d4dc38b8ade3b3dace2926f464fa3b5b82ebc5e3b81cf647e8bbd2cb55c9e31ffd212f8729b66739421c6106e64ac83d3b9e13cd8321b3a9f10d9171bb8cb74e71c34d1e8d0fc8d14b8e5e12bbe2bd2a1431fc224b70d228e4e2063509db26ecd9ca7cc402763e69928805600a4a80eab4ae6a2c3792b98c6942195e643f98c0dc3fa3c2b07431cbbe113e38fc0b7b45c51c4431700ed29d2736b236f63f75932329aa60be9009bd7832f1e1b9ac1503ec84727a1e6c8423c7c5b903e763262d559078e654532e0868f206a468b5b5ebd3eddb4f673536e5f0f8160e5f3311561b7cf79c9c440974355965c931aec5c7225f69f776f052ac4bd6b19f85389fd61df60ecabbeb00c8886ff7983d20ac5d81e303bc71253f40806772fd81f938740205a5b7dcd07cce083da258b493d275967f91e4815d656936b342727cfe45f973b2a5ac257ce64c5eca4f53be8d9fd90c3dfcb8cd1e2cef15c307449ed02c2e1704f4f1be76a40b311ee7cf81987b5089252a807ef3fc99c79eabbc0ef657d897037bced04620d32a425015283bcea1b53e0484bb613d30f14c1422f5f82cc29ab7228b8375c06bf13d746dd9ff00953a90720badf2577d3ed62cbe7a5f15b3c929d26ffe8aee9d2d17391ebc6a79f4bd235d5f7b2db2455343d9d7c6b27972cc6071c36a0d112f86d98972fb06a186e900abc64e9ab653db9b05b70079c0c84a64e8cfee8690eaa68a4bafbb5be112632e46894ec2cc6e7ce697a4513d517deb3e20dbb37ed5963232671e27ef9f62d6b514f0a22c5a5dde2d77e7e184965958f5002fe17d47fbd5d9c407644d443ce89eff427360cae9aa788dc8d7d9f62439916f139f094ee035884cb29dfa396941f0eec9e8e782da88cdc18e5bc1d9a5351b57ce15ac520ffa47e666f87fe5b18ab3c8cb2a48ecf81f36fb8397c6a7a5f59a9fa96cedbb4ecd1c7a6d9d65afdb6bef7791600b6e0a18ba23edb06fc9ec21162feccc54f2665611f10db53401b18bade263b3b972da1a612115d144a5426097efdf5c6a5d1f3c2d318f687242f993f5f1884bd95f2ece34dd4320cea46f5a26c7c945b665402778233bdda9d97c2acd8c4a4ff39dcfdc3a3fbfc5942e3ab8ca9ff4aec17293c1fbaf583d603002f93f9befe8909485eb7c30d0e91fac6c228c5fa6c011eddeafbdbe30af20ae53a85206c03d37ac17a30096bfb4f584cd3f72ef28a3303cea9cc636095c70bb36de0eb50577704d4faed05bd54da020',
+ '681737f93ffd835d7b51afa871235694481272c75a1adb4addae0a3cc30723c8debb33544891b5fb02945c3edb660cf694d7298d41b6156ef2e8f4ba93b6b33d116b48a0bf1f3be0f7ce65ff04adf8f93fbdbff979a0a7cd99ac7f97863efcc6485000456a4e1bf2a2265352b49f208393ebbb72c97f984e1a22313c6444064cca92e2ab11c75f1b4ac5aca1b2e48e7dd68aa55ffbfcf1d8bc73950ff573dab5e058763b7e320f4239d2fb53c7254ad051c1062ad5beb955c9c7307901707febd2ca455b7836314fb576c5d0bb0a5a624cb9653a206ef8ac87458ce34fe6fdd4e812d674c67bcf2907d9947f563ac81d0f994af7a3b3b7c53f1630b3a87b5d5a6a55b1ef31aaf0ba7722efca5f5e9ce8e18a3dc92836fd883861a453d4d7a6649fbe5f32816b9de94c7a5f18a01ddcaa0cb4c718759ed2ddbc4f71299ba3e0d9d07267a77e65dd9ed0086bd2df20924dd63e6f4c54943eac11081e9fc58713a3459c51b5ef41b8c149f59b5ee50ec5b88851becd8ac04add80b3331b192a48a94662a6c39ea6363b006877257a907ed369143b04e2c9fd5851793807603587d31beced3b513d60f23d8a888f654ec486c3b06e5723586005cc81b6ca624fd6090b63ae84d1ae3dcf4882550570ef9fb9dc4cdf2f141479cc39f435cce7213f335fef7206e6a0d5ba687966ed611c1754fe1117f57fa65296dff93b75b753c93960b7bf2957bb319ce69744b0017c353f6f279d0f2ab5f34907b9522d998c7c0353e42055eea8585b0a0376b718b8006377b5f9e7ddea62cff95b015c5774617a839d1af2710f52e11ce684696e7781a660e3b4e362eda90efd08d16ab7b47f84370b3768a99728146467f72dca62bef170db556d8065d0f05be848bf82f4df0ab656fe1f5ee9e1de2aaf566df620c12df1c264ecfefadd5c5a22f0c37fdb87f549a5f78058ad8526b5e52990b85a924029c369c8a555da3943df51df7812812b3820abf15e8a1dd44e32f9fed9b837146103ed683d1ece715e46f1793341d596cbf1a1db3a28b0cf3dbe4c1e21e1ba8fe0ad78368efcc2ebf3805afb7a0f891d4a3d61a7b2304601cab0ad5a577e229bf0e790995f98bd8f4ab414cb1324a7a7fc2f74bb8f4ae7513d31a1194536781fd7d9bff9799ac745ab0ba553c629361e2aec6181981d9ca7dd68b2c4b1b2c302dead8cbfa5a9e8f55dad97f95dc63f691c9fb05f97526cebf37e67c1649a0b2e4d887f51a96d2987a9c172279ea2c9701a6eeabf52906005c79a48792695598a6c9421184d9091e3d76fbc445366dc1b6c81960893807fdea1e5de54be4bb4fe82f9f97c5bb729f22fe1ffb842b9805259013ca220cad15cc987b0bbf6652472d9df90e6998cf89af83cc2ea3444befd2a1665f5c1155e6886a74714948bb1ceb76d6cbcd1b706c47cab8e44f0af9d0428e7986940092feb226d29c8616464fa65cb1767c9e05b590154ea2d40a1264f989d5d66644f4bcbe302e040259944df2b2219504aae003fd05f5e0deac260c6c55f3c54f48fbaf2128ef4e3a8d15963509af8de1bc9fc6031f5724db7bb5352f656be9bb9708546f638eb18b1b5ac6f1e5a3e7806da57a26b3eaf536f3407d972',
+ '3842b033f3ca31a6f8e5a638b39efee6bf73cdcc5aff57e816d6ea21d2b17288e9cb47dda98a495507622f9a90f71c14a3817367de75ed3dd662e9450b18037c1b10e7ab35086830d1eecc029eff6af0bc3078292ccd1e018560cef2e8d4d8e135da39a37f8cb4c0be502577c40011027348811b2c4f11be8a994431512c1a42a1f1e5d0705e588c3752e101ea4039d22e903943c742effb4fd5f1092e67f124c61d9237eec57a1da2baa8a8f80808e956d145abe3f0df413bdb7d8267ce84110c26e8e2e20b43f968512475d7a0a9ce54d3505b699f0a17b67591a4e4a9fc90dbd391d83576daaaf2dffb6f6d5042098e5e0059429897052869d5788e40802a9bed3221cd4f67b8a72cd59fa360fc236e3afdaf5423af93f980f0054bda3965b43c76c694dd14a9eeff5b0b6217fea35b2ef06589877a4a92828b5304c04fcc8f8802ce716a0707312234bd90bf7111bd048181c80e1fb159374d6ba23be9e4929981414b3c6859d75b09bd169e7cf6fb82570df8fec751b767df540b912f37263799270c9c602848738211ddfc48d87f711b1003a099b015d9e816290a4a4cb429f2899bd217fbeb3246c3cc23fc42b0987493d03cbe58d95611bae2f062aba238356ee026b45a2a5ffedbca5ac1b9f6c10b9d471a5dd16da6024720899edd592c0adfc3e05fcd6cc4515fc1e8bfc7b9b2873650819fa1379162c5ca1f276942cd18e32c4742dd1a27e85998161df361349266d3bbac52a1dfd93dc8f825d7c4e2088203a482119a516ad0372c04c5560fd136b80eab6c115711b6025cbfb0463581c4303f4af2550a80cd86729bc6010beeade7c3c475f1c2af385f951d7a328c2cb29b60c007b319d2576c2f0b7dc8f091d4492121f7a8e85ecbcaea68c0efb0b1532d4f0cd81d480776d4ad7b73148561b1c472e7799e91c47828a2c807e569b7b0cc5357edf95dc832a332142e4e93074f41fc41843b858946620664d97c7ee6cf61b6c9cfb021bc94c207fd38dce22ab3a909559fe78b563e605e65bd1cb9e8bad5be8905ab5c8ca319745f19283a73e7e2df4b520a6dd3660af2f23c2de062790eb4c01751d6df6890c0625798638af2d6c64c250cf1a7c8480934dd17bcff12f6e0958e09565ee910352d7c962416dc6b0880a155e07a6c03ec53284215648f748931f03da6b1307f19e9108947c0ad8bec0e4d4d01f65821e476a517b33cf76ff8bbc8548c7f45c7b5bd99d9922f6e1db91eb15d1ec1968c37c5ddfc5d2d53d1765c9bb6d1702ece51d2a1edce0b2709b8da56ebfd832e2a2d69575adddfaa81493cf3ca3d2df57e3550af2fc3fede373168c36167e526e5108a9b9af9fc0467b98257fa975ea2bda85e4c0638ae9d8f6bf08025248e88a4264f32227a296a6b105750aea9ab3b75f324fedaf6c36bf8b09b16a1fc285c4fb9e1a35c2710e27594e2654c39baca8d5fcb5014c6e6515d46900c3bb758a8cd0f6876aeca59776d8f4c1e3d103656ed327bc71a6ebf55a376f8ea5d1cc87608700b348229ac2e3b47bc03e9f6c5e87db45bed55b6f582b388fb396ef520cde726f2643f0eaf11c7055b9db8b20f87252f94492d6831dd75c4c080d60807b65278468e4d3f0d27f9105073130ebd3bcde94d630b4a1a70d1727047fc1e263731ad2f3a14846c78bab2c40d60d0770c5d2bafc455265942b0d932174afe255b6c0ed4f1fca7750df031dff408c1e403bd3de2f375c2955bf8422f762772ab27ece35e3a6d6ecfed',
+ '9020918aad4ebe24bfbad9f9109325d09ef520bd79ba08986d949fade1592cb5ff9dc2061586c4063bdca9e53760fd8c9d5fa8d03b8673ecb3f8c82e6a9eb9f0a1be45cae2d0d6069e8d0d541448c2bf748147e045b8ed52047fca660ed3b917c0aca140dcd3fb0c2ef48eae70f47d536c84845560f77fb2a6502cbc94a03112a28f61ceca383b00353ab35c130b362fcb90e89854eb30f4e295769ac6ac2bc98f8e0ade76a69ecaf98605c4c536f33bd9ccfa0fe93d0800007331676aa0ae24d1d126d7a6c62d53c3010b4f4e1dbe5fe0614223e6950fbe4814e48a4923c30baf813c212340ef81dad24d6575679e832677483c159a4e1702a0176d2bde716670c6d524b5bbed3d8823536f03bd9c8ff43495c33cf5ccf1753e5277d878c01d5dc7784723df2d701319a6d3c1c6be6b92c3b01e244e9136ea171e10179ab818beadadf53755b900c4decdfb742b0e00484a21b7954ba6cca95302a0b1ec623fdb9ffd93b7c599d7e39a504de79394345ef271f55797129dfa19878f6f15c57bfbc6ee8a6cd6d3dbb874b833e1a757f01be2273f31d8dd8f2591334617bee9b2674a0a421e3171f68a958b14290f5f1dc943cbffecb7108c71e5912b718ed7cd6852d923957e7a0fa32554588872b4a1ae3ce59c50dbb27b283a26a7472e96b54406e2969864f70d494b9866c6785f6612f6fe7e25edcb4390bb7c235f452e50438fad01f18befdac52fe1a8abca67523f989d0d339464cef18d1a05827ca888af15c2cd669c6a5d5ffab685fe10d44f7c4b4bb14279830395db88b6787b0b44cebfaa63c03f717e5ed4a06589f1ae4410378fd2194333cac3cb4f9f09e95f6ceab6ec29c61b0a250ce426b01216fe184483f1d8819b790bc285f627fd6fade74922108942d9403aaf53d0cf6227ccb56058f92b42295faedb3205b51bb4fc9f332a9eeafa2018a59048262841cb1e02acdd30332494ec9c56fa04b32c61547bf2f61fb4b8999c4ef7ecb12477aafee76f3b1d58ef8528bb7b047c88f81dbd63cdaf1b4e42ecd31e2b67f82bcb6d734cf39949036aa31cf49179f59c4791403f0b7d182260c0c5fb76e083a606bc85197e203a9a5e97cf30e280f557e164e4f7f587a097dcbd7bce1e7fdbfbf03e3d3659f77a8793084955b44206218e3fb274d3f63a157d8cfc806c6e8794519ca28ccc489130d19f934c50e7af6215cab09cedf16f040ad550f7a8d20fd7f17ebd011e3805ffe004b4fefe9679823face8588aa1c5cd4c3f801d1ad6fc2e988a947e99f1605a87deb4520677eae9d48e6291f32ec6d60b7393d90a9fd5000d6b32ec839b29ab8fd59c2fafb38cff9c17252d71bffa880e199112bf5822b519c79c31255de959c192737f4272e72d5ef039164a7ce84b1fd883b282276cb58447dc37c76027cce3bd412907db81d9e4c0a632c68e1888045870a09b3439671692f8e4b1cc6b6cbdfe0f154617e46df430746b2f1d12a5864260c452a814359651fb222ac83ea119fbe42b474d984f57e8aca7cb505f0c6d3e5b06eeab8286ce2bead87b7c26d3bd5fc85351a623e9d58f56d0e450862381f36a4eb9640dc384c9cfeeed11ad6a72d0c375ae4a0fa135cd78cdc0450f548a0a9484f9fc3c5281d2b14bc6af5bce00f6de79a460e4e1414c1c86a75683064f2ae290f79b979c8def99d94e7db7672f7b20477c112810bfb149e3e3ab68a099fc5a5afb67a7096fc88e7fcfa4499ec70492c77e84659578a708ccbb6d498c302807cb4d8bf302f10498258f4c99d98f3c3ae2f1e222da34d4602976c4ab31dc55eec9342d04edd94bbfb3d79b308150c8227e1f52e846bae059e25dd718f7652b193dfa766033f0470c12efbc95ffd25352844efd3e41d474fbdfb8cf174692548f',
+ '562d412b2b65b5b906848ae4c8b6cbdbf34726e6bc659d4d62267f76bfcd974d1d49a3e84afe086cefc8c32a1d3da30e2933b53aba8300ee200c73abe7fa1c98ac489b243083d5edabd1ede1633370a27c07cf2f12d113c2853accf414594a27ae321025047c8605e3a8ee4fc11e996096ca5b0fedd73c903aba70996e738ac4c90fec35ef72827c3f53b0bc6088880d1c844120721ee422e69449e21ccf4235a5e8169a19ec311a66dc197267f8a474b93d69abca2d743e32ce3e1647f5dc43ebc4769ca972fba6014a13b8fff7555c13febbf71c8c52adc672367f166ebeb643acf485c88c48eab7a685d5500c038cd2ce1f4e91c4e83649871b63b2c1525654a7789b9dc380ac31f7561281bf16cd9fb67df6515c9da36416d40b4276feebc7bdea28519e0bb5164570b7bb98f6e722bdbd3883dcd8bbbe2649bfef162c3c43f632720ba651cd0f99ba0c25200ca202ebc75c4fcc034500bf62c7e1284312715b38c2f461bbbbc4fa1c58e8debec6e74883aadeb5850ad1e9076a3f34ab35f9f3e55f3459af49dc707ee52b5a751a7bddd96a581fc6d2daad20f131c2cb6d82c71f93f216963d0003c8f9171d9a6763b1b2e3e5902e64c21295a4e15b0d82b4ddcfa4561ed960d7bce2ddd4ae93754accf45eab92258a32214ecb7486d83b393fdedb89fbde7a0aa2b5d98995ca0cd6378923d5ac130ad2e133ae15cc9561b6f53280b3350f5340bd27345e5ba5f4aff9577a896760ad495a2d95eab4aabcd605b5dbfb52d3b2221621387c878ef47e1a48abef49743b409422c71bc680143e794d338adc916157b48c2b0ba1ecd6eba343fd31dcb77c5e98fc8dd18a7f319958e4d2885bb32688a04e56323227609d9b2fba74a892384c6da12f34890fdb8dab61fe1c55a0ca5de051011ac1a75b3d0af628e3346b36b11bfbf56b4a99d9c279ac006e54c0157d7e7b74ebd6d38e72f97f8fa308ab3f36abf0269f5583f4e2caad20a7df7bce9181b6f6ccf915c3e56eb239dfacbb1b83246a0cf73337f492a74ca5ef7f39bf40f2e9d0e5b3d4c03e74776dbda901923f8e50ed9c6b1ba17c1671d96dba62ae33d8fc4b5f8b794e2410b6726585e76b12f18a12b58599c2482204024a1b5e64123bd6ad620a6a353f4c579100cbd470a656ffc36b0a18219ed0ed69ad2795a98424613e15b6103d382f4212b6003067a0c49948e6810084242a1456bf68704431998448a11ad32eb7c1603daeae6219ab443fe84d72b8b485376110a86555ffc2a527112ebb1ccf630b9759115cd44e6dddd9ecc865aa7967ffbde18fa6ada0df4d32e2e32351e9e514d0b00693edce8e97509c81e33d9a738b0e0f9cb4e1f02f9b8e03e8d9dc44e4e5f488b6da1560b77a8a409d73d9e150e23ee3f91657b75b9621bc666523a80984e92a49b9b4ce908f7020479ee614a60c33a5cb5479bd0a46d455855090ddfcfb99dee6b832dec0ddea84a5ebccfc1f12d79d3df7aec9e5b490156c2089aa6452eaecb560f859f06ce1b3dfe2f46161fb101374865da594e73b46446078d9ea8fc69bdf386c06c7453bf61e0bc40b14cfc84e9b89f017bee7be2e3495c43035bc14348cc9f7afee6fe2958aefa5c1e197e697d888cee80bbd02156549d578eee0bcffe399021a0cf2bcd84a15004d705a5236530c5fe02570ee45fe031378bc04a5440d32f310cad7b30687053b756820f473369c0bc6b1086905284ce6fa482fb94b321c3359d025ae696848c00271f1b495a6c13a645a8e5d9c0babb6c43969df5b78dd2bbdc5e494471665f5bb35d67ae6cd025480c509b153cea8eba038baffbe0aff4204511d4fe9b8e4446a59d62eacd3e7c1e39d814d729acbed54ed2b02ba0e0adff51dd7c697774e14d588fab830e0f8e95588ff1941969d24a8ffc3ce98ad846c5ad11ae1997b2accc5684900ba1abe359d2e89fd07eba5f966178b4ca00ef750f915ee8836eb00d602a1cbee92acc00b85392ae10ec06bd254dc8964195aeaf39a8f5ca3b7ef599954dc886bffbc86d4d34ee7',
+ '5310977a5f3689bb9acd32b9ec2a60a027e912ffeb3c7fc1c7835a5fa01d5554577ee4d0a68243b04b01baae69a420d52bf79e39aabfb5e4118d8213ff9341a32cb711c650e6f6fa40ab243c5a007b7824644e45302d68cf43205114b53fcd541be2a6c22409ab80c1f1f9ca89e579725b57aa8c452fa16aa4634ecb8dc8004f6c282b2eeb946a2a16fbc0c2bcfc23a918f93b76b06d679d7e7f019e4ed7e37c67b029716d2e39e086f2018bbced8006a371886c3b8ec250179bf2f6bf137cc354a328f37280228a5afe458d515f987143e819d4ac3bba6bb9e0aa3caa25d50d7a28ae1cc2322c10db46123042fa74d341867717cc1b58d6aa76b0d6d5b4f6402268455424eb5f5a4ef3f3dc59671b12e572299e63d7a37a32848c2e0869e546948a74036253c451fe2c6df95c4e951877d5b7d03916a43d9b32c7aa0decac0518b7c491421362db321a0aef1456ebcb3fd6aa14158161082baae8b4abe45367f617bb668dd342e131e5512649282415859e89d4c1bfe4c42c1177b3a9f9e05375d1e3ea0a3a6a4c44b4ca07c36c48dd9054dc7703793557e492fc0fd0d45db0de0ec48683f1e402b3affef849c9600ba9212c65a4575aab9c52002fe81dd16879f5e4a0bea0b8edc6007462a5e77386182dff056c005da69b7c0b7db97b45628eafcda285eeecf4c5ccb4ae9d6f8938259fe0c1221d45322b36a3600a97c086656307f29e838afef73e4742fa09aa0818a0540090551b3692a85240a4194abc463a18fad10899f5a57bb488835cdfde3857e52b7c51e69919fc9f8650a8ebec785c8a20e82522c017ae83e602112fa2ceed1aa8afb9ae4508571298d4ecacfe44f0e5cea9812c4795fd3dc63dcfa33c22897be0f1347c21a7e334dff88f94daf21eaf6bdec5bf726790698ffe3f42957c54913b096a57153dc88cc38636ac69ca10725f4d98e329d4908fa90b0914932476e322c1044709142ea3ad448bfb9113ebb511bfa3a1542525387c2cd4211f6116fafbdfaa54e5723cff03fc36893b17da01ebfb8e00dba376eb702b4872227d5c5c2f2c038791a01a2a2e74df5e501c03dd54cb0009a693ac30edcf0e0e82be71932770b8e7f6e6f1ac97752d83b66b8ed1b4f5a1c39d40ee8f5bdfcefd296d7d274a73707cfa8be90ba5c6c8ff574ca46574421a36a9f1659122242f48fdd1a79efffcd44f86d929d1bf3159da19066d22fa7a136ab0ed39bdaa66daf6e8341882b0d58c678316da854f7c881ce6e3108faf6533689b7d919b5b6c770fd1dcf85ac4c43fcdd78b23b0bc70fccea529b535fbca233753e995b49e00ad9c9f126eafa39295876c802dd96ed55ef83e21869a47738bdfbf796f8fd9e824a33c1ea208b507389f283d1f88cd7355a09813a848f9261169c67544b576ed852f8f48bded61eacd1cf509224641118ad09d4746c77bac60dc52243facdbd7784580d8e7e61205ff07ba2e5e993279a48f3404869d33cd1e404acf85fe726ea4eff715477c2d1e73675ff2fa0c08714ce6459490548b50f49d95be4132a17ab234dc4906361bfed444cb9e1f242bc22adbb93d8d74e9ff89fc1d3999d1d2c2491d17ba4b9d446dc8a7e879f46b0334e5797648a583b7a08664c988b5626c0f12b091b03f371032f7979dcda268f98e26c565fdff0b5cfc92de81f2be6dd729b5f730750a8fe8170b1cd2e050a3739a94c96ea3c49340db56712dc0dc7b7ad8d9fadd50d32a32e2a93e6ffc27a5da1ed88c7a831ba4cb057925b63b3613bfe426c08188c292575c7b065d674ff597e399166fe62dc56541a40202a8f5bd0d1446510266014ec04d297b8269ef1b10b180703712227e76587f11fa524001cd31deb54a32b5e6d47d5ba5c74c2fa0af4f35a1b85021576613b15986804068650399f43f3409e2fab3b88d1456380bdb875fb1dce752d99d38e3e28f791d793e521c17cbb323cb9cb9e83a52d132e4fb1fa6a98921de8b848fbcb5fb86febdc0e61226d5f92b29215594b4670345b479e0b490a944edf581e2e5d9b4ef0bba53872127444c4a82f15064043d359701bc922e6e2399a6a0fdf70556a0fc6efa60fcf12402f5a7607471ff31bbae53562cfdbf4bdf6832136197c535a9f54c054d0e2b24f63b6c4a12820e43c8b89f3831e80d30c5f9b9d6138acd7db621d0618485',
+ '3cfbc77b8897b6a5613f62f6b1c89b0d68f272c6c19b9e0ec6331ef616702006e64322d3460a57d3a5074c719811cb5dd78900268890da0ac177b40d487735489da374843a1a6007198160ae77b1363cd8ac29f24bd66360ef62987ab60ae8ee690307b5ec309be8c496e5d6d610a453714336538fd501b758da1166e88f02a0524b218fe5b2ce1fae2c25103d96dd4aac376f70def57bb705c868f967704c0561630b3aac0ac254df2c668535aba8c8916e1c72bf9e9b09fd15e65aba138bc69d330dddc99e3f2e607ff15c45b7ce7527d18580bb38275548a7d0b269e28a8a0fa46ea0c5d80d55380b0ebef62218fa7648f2c3592be842ef687128fd4310fabd9c78ac271ff3726fba04d3cf544bff86bcaa6221dfa679f93f10e5fda0e4beb104712977daba2d0e731dc258b5322b6013f6869bbd29a26e13f410f160cf7df3c5a23f3e732a2d1b1d9fb419ae94270b371e57502b386457ce66d261eb99df89c5531402510b1ba1a2d3d09ba5389a8f0e6afcc7929d67bb57ae53d6a90a8e7deeccd34edc259ea5e9013f6503e72df582219e885b1e54a29614bad802e92fd72754a2a77c405f31da7128ff316988447a8d641cec84d31473e030ed5e006d9d5734a9979923054c5d6ab4f295865284ddf4770aaf968b1ad659dc9f2515edd968b512a59b9739ff5a360bc5990634bd959ffeda0a1e25bba7c8e775bff15a92411d025aea64a351b91b5400a4b0d5f889d6221567e24800ce7578f7945c5ad1cab4a33cee52ea4a6262b82c1d4dde3da1e10b422dac9def33a8b8ae0c1e959debc41dd51028b7f23e525ed06ea5f692c507e0e9c442cc93bfa9ce2190feb5fe8c9398adb6b0b15233356e74fe80c601dd91ca92946c7cf158bfe3d986c55ffc956a3b4a4ab081712a5112a035a6f591d1c7f0c605f45513e7341f5c583b3da0ee912e3632f1ce570cf070dd7bdf2c4a89f17b0c7fa10100554ea9346c28f7f180509af9d83b410dbf58b0b238f21335ee5792ed6a3a25c08856aae82c5435f731efa719a256829a2dd1fba8d4a85159e6415ef02e886c5c6a934901ff879ebbeab57c20fa0938063ca57946b7c98fbf5af6069d33537bdbe24faf064c88da4494ec4296471d0b5ad61a51144a1d746d33bfb37ce162fd45d7d0037f7d20929b15da2001f05ba59de27aac9af36ba96d57e48d16a17f98a233606ef5f9176ec657c73feb5f88abd7480bb1611b7dd5c7b0adbbf2d970f4fbdd4518ef283515cf40fdc6dbfb6fc810f01689f02b19a18125616698f3feba57bdc728f5724130bdbac3de5a2cd7a251c2e25bcf8908b5a59d4ab59155f192bfbb30c78f3f056699ac60afef5a87e1a1a6b950879f11b83a02aed1646911233abfc61c46f747606afd1d6022ed482e0984e1909fd4fe53493bd6e199952616d0b1d350a102d0c53897421a7b4de2319044a3429f8f962dd9300a0eb943f71289799d8a29c8683e0c61170483e31d913ece478e0f3aa0eff2c992804bda4dc06eff6df364b2d673ed43e347c115c1f61b0a159d783ce5409ceeb55ed7c2f0c8862c8ab3dc26e40be325f00abd4c950a30e0559762fd2dc9e087bcd0e562a8d1dae63ef1198d58aff6ef847600f1627284394ea453e53f31c57cef00fe243d837016cfd5a150fb062f89e8b3116af0d91ffb49ead55ce52154b59613a96f768852a58688fed595b995dc64e8782107e50a108e946fcae541941af9346fac1c858879ab32b886ee6f30ce3f3dbe7c6f3e4c5e65c8aa9708388d946089d741f725187c86fa55cacdd10948c3db6ae5ef8dedcf16ae0c2b16af4709123a6997cf0d0c945e095f08fc86273336ea04c3517af64ae8701feed74dcb635870ad166ce86bab7d875eaa41379375e191157e5bac6eb625336b4913bb20887a532c26965c3ea8299bd817658c53f80cf9247ed6cc5dfd132277a291a04e62c00a6d3014118c73ab6d57b7302c71a2ff4ab92a8981cc06fa62cb621aa55ed771bef89aafb15df44b2662f269c0f45d01ab5cec3e74c1b9241eeff2767257d1d79656ce2fe1ba1350ee8230daefe5105b44962bbb4d47fed87c6cb86dae413738bb0e325c4ba9b804ec3dd949624ba3563eee50c9b363bd5da4c81ec60124f1dd54a830fd9735cbad491e557fc12efd38c1edc1304e1fdefcbf8e8b4b1509e60e8aad2fdc6d6334942caa0c44b3970849f419e7b8c78249fdf54367c28ff7001385a497bf733ddb01be6b675fe08cabd300fd2a29c694e7a86d7119f41af726336c15cf6f54caa83b27e8cc9af118ce1c0d1ef34af25e9e44509f0ba95c',
+ '3fbcbd3f57a9912b9717f91e81529f6736c6d06f8bc1309c5e7aad742b51b106da589c85db137137757ccf8d5b4a249481731d8c2df061d551c07e13182f238abf5891caa6e94c91a72616eda6311da1698874caaacfc0c26bd034458ac0bfd295c38354dfb38a02d41db484898df457980cbc722ae6b62a55b1b553a98aeba805a25c6daffa9252a46a939b2de8107eb307dfccd4beacd4b76beb859c1710d2b7fc035b3e44ce49c1149979bac9d9de6582c420d1de08893707bc228cef971952d96066b31ca5aed023b06857b9b73e538353b649ce3311808c1274a098e6457f425bd837077f4b7ca0bfae2c3f1267281574d5631796343802d4c1019c671ee53ddcf7f18fc4e66a92ae9462e352228a3d0fc7474098ba0871ea52d683816b2dd5cb0d0a3bdd4845016562212ddf3522050cd21ed1a06a97e23b6f48d39bf6fe0f1060a9933039b3f6cb6090a622689774533f1053180d8e5cb15f7f161f8501f59338f72026815c77cad6d8d581859192cd56444d676b94e8336ca2d374e1dd8e3ddf1c6928e7ea8f490b20426552ba68605ee347f54c528daadcd99302d23be2f49c3ff79c340243314891763799b7fdf5a114c92a786f53fcf84edd3120c3faa1b68850eb304e9d68d553f85d20335a7565005c6eb694421208263969921cdd2d7620fe7ea376a4d7fa8d5041b0d485c6f3ce8729d06963d4548c2f12f1ef937e242f89bc55226066ff680749104288d293a7d3c38c95bd2a23a6489405e3257e08dc770c0ef9dafe2b0ba4df0a266b7f8cb3c7a4b3c158fdbf9c0b5796a19a13695052d895a31985c79eaf6a642834b72819eb340d943d336e9701c46cd0fa2791b3a187b3c925e51b4bb152e7efed61ad02c7dc61b773b679bfb0414e194ea1c62ced81301307046f3831fe5b249d656495e40279d1026c283ba23706249d6951e3bd2dd428c6cc0b8db7d8886e4dab95fbc9101b8bf33fc2345f0f5a98ef677a0199f566b16a233673cb473576041a885bfe6c108d4d1691d16d2f131e4c8388ae0940b055fcf33a12f6b32ce4ca9c84de0c801f7e18d8db3b5601f95dbda5c511967778bd83235d91bf84335fa718d3ac7ab212075d6fe999a2ebd3fe49b57753c7008790079be818af23138fa477bbecd06cb0cf23c20314d7576ba8f99be79b2544a577914323d14cf6941dc89f85c79715c07f72b970d47fac8704d43f5d1237ab991a270355e221667db11c79786456140bbe523c7a002152f0b9e28920e0859e2d20d4c3c773b3dc98f2a6eddeec95d35935cc3367c046b16e702b627a2c978327329d6bc2ad8cbeab8970e4fd86dcb9718be786803e7531c8d1b8b0b11ef6c350f78ce38cb733757f1d82441f5bb42e1d69ac1e0d9f363ac883905d0ee029c77018a8136dba306adc57e2f78a92a49a237721e1560e1134342fbb4fd997b8f64f3cf9e3afde0c0489df9ae3865435664c5c50b8a2b18f87d57c42705a6e2cb7b40c883c994a4eaa505cb2f25dc9edf860c1418b3bb0379b73132ca6d98b0b74692f5c0472277ec6f777300b551857b9274dfb43673492d8d69b81db9d16e094344e7d25839c24bf47d71e15b86af81169a986c66f6526a7c5bd49f6ef38307fde11f48514d2c9fd082483facce65a69ed7ceafe9b6a7e6e09218ddf1718859c2f1fc80e6cf1f4e8020868a1dab504277c6f2fd2326c1f966a6e557e5de06655ee2101a6d5202ee8fc29d4f229fe7eb5de2a5e297e929388b04da7bb08b55c11e5bea8379587fe65c02fcfd503ea5179db547fedc50561aba3658d7e62c1ee39da9fc4a8f54fecc714c36232aea104dd3a95980550ec11660b5f0eafb12ca433966c5de13da08076c18d93f1734a1f47c597b35967702b23afe232363e1e0668e1cc7ccab5a143da8f346cfa9a0d2a2142ffd7d151b93849589702fb51be9d408267274ba57bbc5b4561f5356b13aed27a780d5558eec040ce2585e63234f02443e6694c54557462adbb358b2e8433c235a85ccdc237496fd936c4e028a9877aeaf147b50b58f1558deb34e22a883f593b37cc21d8bd5d40416fc5cf7c45ebab402fcb6e12acbd9eae98fa24a8455cff53ce6539e2a8831acd7a929d772fa3200d49fc5fb17860a3d86b9037f0e6111c471530fd2820446547ff33305e90374019e6e27130e460ced20bfd054a91e618ddb33456f14c268a75a5ae727c8f30722baf868ad61a478666224974fe6f645bde51792a8754b3ef1f150d49185515200d4cef93ec3f9c325a557d6e53a7124d11960863306173fd0ff7589f79096eb185642575ba7ee83c774e9fda92617957402be8e7359bed1c0cf2955be8d4c48d5c9f311688fdb20b85b5ba9f04c71fdf31b721a0de29a9b5ddaece65adde9d1c5aa71a609ee482074c31d3a7eefe8e4fade3bc7472332f2e4bd40aa5104e84c54621e83a435ce098936bf4d9',
+ '1718a57fe1a0a012daddb0d30069525c5abe69147ea9df4957fc8f7f25846307c53ab9d333bd0c884d00d0d5da409d04ca3a90cebf9aef74cdb60b0b7e7c6b171aa9265c253d91f1282b1a96b5547447a5b6f512a4e13c25f0ab162b5d25a3d99dceb4c3be067875f4c558bf998c1f50729c5e8634670f8546447887c9665273d60f2bb00601093b383650f78ed0e545b95394d57712afbc59e7f8c6ca27c10d4dd552ca06168b6c7ce5cbe79d08104f03435fa575cb66f1b1fd6090685039d08b89f2bac52e482f493c9037cd1ce695d6d4869f377b7a4cd4ef768facca00e31791d3274b2f86ad25a2698e27f5b040bd6ed36ac40834f64c2303689d7b5e6f7957bdbaa1038e0d9b7f1c94b179b5773d790cae245cdc17a103cec6444c9d9c3a41781bce90a878303c72f275913f63e05dfa15605dbad659f6a14fa7250491e1b9cbf1dac01b166e3f33507b26942b2d8265457ff5155cfe6342abd0efda77f62680e5ce79310edcf12755c91efdf9ac5115e6890b37d117b47a83c790687501f05d9eb1a5308902ba15aa6963c2f2e630d1a18786665c2d50117f92f2f6b48b7e2bc58b2b61ae6903c7f763db2b406288621e8050eb25c79fcf463bfbcb5c1b3ab10165f30ca6983203e3bd70800ba8c291047c500e556d097c81ca9531943a8bb9fb46e5799817b192168109087a414424fb836e1614a8f6dfd745a76e846fd9095a36e9efad6fe63c39b78d0cb6b478e3ad9e924d89fecec1f1b619cb55428d6bc73de7b80d273bc8f465e6d4e789c598bfd4a4f9ddf9f9ea7624d3902f3b0da4ea64c71adfc71600ee95fba833499209dc2e8c633721df220f98bc0539e3c27f6ab2715e4cd8e1aad04eb4d0c57b49ffaab232d3c0fde9316419be729cc114c3f030cdcb7be1038f4199f993c3a75142d16a7f90a0157dde1cafc01e7f5e6c3723e4c4ab946ae477ea7db23b5656129afa5e59e4fef105f2e062ab520b4030a5acd83c44c1fbec2a7202c70ea50ef4cfcd95aa15021ae736573b655668f1cad332141ee0281d836f83302055d95a5fa8f117586cd6178d6f2a41d772bdf9a0895e9e53c5f157110fc210a65719bfbbef0fec4c319f705d68007de9ee32de1c91d880c23b4532fed3d9a93f5e8ec7706ace1d6fac88aec1e882f58411f12aa4b247c2528c4c35b2807003d4b05ff9e6e2d7b0a825b65820b658b38e241f64a2b3536aedfe2de896e12e274e96b5dd851ed1b0dee354f6e19b29bff16fe7157d5da5827adb11d310bdbc1c9358b8fcb6e86522fb2f88106e5f9d1f933a6fc49d78e511e0691f7f89dbc1ea8d3e8fdddf063b10e6517a2f2eca5ddd5ecddff96accbb2009f180736a04e69a229063096a41fa81879154da89a31101386f603d4c20cdb6dcba5b371900d3333c955b06bd614a7ec6363b9ae7b461910b6a1a16ab3dc6db410ed95400972fbdc296a05e422ed50e8b8a59f6b0c3f397be04340fefec4c97203322518591a3419cdb59985f704039bed3d6764c99721a3986d6ad80f307f361725db612b5d6c5b2acaf3d7eee3607475adaa224fe842364382a7ac61aa1f6ed13b20c0ecc7154ec51cad406715d810e678c039ebad1b9276155fd2a2bbfa9aa5e4ad9e19e1bf33211d1e55fc15daefc421420239402e46a4a82be12ec0c12819b4da8f2f37dac6c36edf9f0a6df97f7329b811c55bfac153cd746e7272d3b5a11e3feab933e868206459723e88b42e88049afefc5af1a107d7c1c12a2cd2c1e0932657346078a5cca02475f4e08b9b875453106ac7b3543559ac8f92692ecb3cb6564b2d380bee2c94b910da1ff044d57b88fc7d2cb06960e59d3067a2482039fa284d103502cfd4d49fe93a91729916ec9529cff4593f2efe0c4a0682cf9f5cd114b369e20dc939c23155a2ef9326b04a101ecfa94e63d3f58232eb65256709cdc434a6c97d48dff025ecaf84c26c25f67241e8c5e076147e8791a2d3da35e5628f475345e1ed4be0e624bceb90e80c844449c7d4cfc444fb94ccb9596e8a64120e520c01cc24f216ea8467ae8f18c8760e8cd9505cfd09ef327a9b6042b30a5e99eb1d67ac6e5704bb921c280d7cee4e298199b3288eb7ebd8f1c573076e4ccaea7923f203480f2cc1b466f2ac92e27f2dce8597a2d7f353f0e8c42c1d0aafd958929c3b51307f01a581b498dcd9497ba3da29586c8730ca22b613d60426a7fc6cbe029e26c61b25cf80a1752eee87d5f42af18fd60af4b0e6f0311b5d20c9b6af51e3b4e467880bb817aa3203112210c7478aa88fe1b3aaa60db86678a7899a98f4abfa1a933a25f7dd3b3a0a0facbe7596312cfa99f219f884f631f7296c1aa22ce7e859e7a5f6f737c10794289c3187bd91a79692347146fc87284914f5a96351140a02b350114ecdf82541fa550c86243882f468ff5329baf793fef89ae94517bcc9a5b4cce756f63d0d94037bc1f9407259849cb7321966a41f7942d03cdfd74ec1c33a80fbb8470c4afac284d449b8fb95d79bf0908e86392558924a269c16466cebd2f0b0db36e247f6b5e4c60ff410c25df54ff0f17003301d554d38f25b3035700ca0c9485c5b9f086c3',
+ 'eeb1a4c660be97e365dfe42a4d3400c6e661caaca02accd2ef41be9bf15b4c9651891a696bc60408b0ccaa2b4c2d2cfe079e321a699630b42218e814a9cc30492255f51c85df8042fdf7f8d68ea02806fba3830ce72665603a809c2bc64c27ff2bbc3dc6f73192f91208d5135ab67d448a17c5696003f53cff23e4c89202bb213267fb510ae3c295b8a64acaf796b2227ba3011b1d5468b238a6c7d35317731500fe37a4031d987eb7795de3ae6a4f0698ee3e0966424428afb44e3552b3d7445d28f7a72d099d1dd72a1846c757dd5aa7a1841b83f513082af37fd4d7fc7016108d4542cfcc58d8e06183db8a87e3857163db39bb945cb9720b6499291dc5f4e3d6285d3091511899c5a58b3e22e9efbedd4c4b5748a8a34fa5056c923c5f449caba9e0997e1146cbff863c2d4f770056b6de399f387e2e886968365882c46f04b3ceb352bb1fc83eb72ed79d37162000979aebdb8d66c2e7fe97ddc4167edee397a1bfa3710308ba94a645d7024db78628864a536ee8c7320d9a4b1e2015f801ff2aead4c8466c073ef56c23d7a52dae10ad3c4f048da5323d7766aeca0f242591701d2ce76f5eec5e2336c8dea5ea41f814aa1676dcc4af373818bb3af6cc19f87b41f4f70645339c398a1041d5560687c57df1ed5e8d71a2e5488f985157a3da533c751f9489a29f3e4f4125bddac766c79b289199663f2784de700da92d8ce001f8f488a09102103a6fa4b4e6dc4a3c22ee038917b8e26e1fc1a7c185b69bb18c5bbc59b2c71a9635d18116d7c658b2de5dc9fe60ec231ebddb7cdb6d599af6fc4f14bb5292b4da385d207318feb97004cfc417fa68c8df67133683e9814f5659bb43d6095a96834afbc8f232ee351d9c2e3afd6f96995b24511fe38293847aac8692d15e88893a7493c3bbacfc9461ac6174d747dd6037fc7d7d20bff8ff09fd9a49d5da8255a7bd0d57f70e929de63e50bace08a4e31ef7809965291889ac52deb00903b1c2712d51cdcee117195159e3540a3c55ebb61e40bbd8465be90bb53a0e96647d9841cc486d67abf3d14d060289b26a5740a778a62ba1a12ae9cd2d96ada3824f9ebea3d87eebf78d8a804c95a2ef1b12aa9a0d9a30e9bfeb4f9ac2dad359e78d9d91b9ea4a814a4f0f923384e7e8d6eef137e60513d82a08e41c7defc9e01aa15e61166717522ea0272cc3b7a0c62353dc250acd1d9569e770f865bbd75fa3f1a6d7c3352e862ae899f6051615b08aa9350d81dc934904f2bbd9832744fe0be7409bc73ed744c7902e97008a8ecf9458c2965418c01b838f8c65dd1b5ae7d8e9f3542a6859b48bfeaeb8bcf9524ac8c84c698a6beb346f28ac447e805f3f956186aaf59dfeff009be100424daa4aaf619a2d2bbc5bbb5024e41f6b3c9c31c7b6c2472fc40c4daecf8e18996cdef7cf8c768b40f259d9acebfa9ead3959e2f8506fd0e0c5ccc51c037fa7c9403678b3afa62bd0f72db60de5b6684d5dde7daf9755f010888690d29d7a56dbaff9f6e034f3b4e3b21f79fa7ae2265392722875f33b4dc8f482d5580748cdd6a37198e08125cf810b774bfc12447fc5bf5e0bd1ccea8f0ff307bd37a7b1b3c203e48739000423b3ea7c539a15a61cadcceb504b8a2b5fee6d5e70f6e77cb0a8b79bea76175759803777ba5cebcea412a05e1c6b95c4656c48d0151d2e736e8fa6deea1c30e818f1dab0a7cafc84c0fd25029aba557d48916da3d534e35c927fbaf5afb5b27d090dbc6f436db0921875421eefbf3320b065c41fd7c47000c780da2760c905dfd3dcc3fcb5cc70bf5382dff94602957347f1358e44543c27b39beebd26de91d61f66d89e266fa2d21a2ce5dcc50ce440b23ca936436daf98fed7dfff287ebd2a95b4e49fbedfb094147c3a0f9464894d9c4e0661fd96311d513d93358f30f3a2dccdcd45a4a300cdea79c7dadc92ea62ab30365599572a7c54d3f3a7827d9b079db97dd90143fc44432c7485c51f714987e91f5a4038027eaea3e79d2aeb1b217f81daa2fc480ac3c89b2a57769285c9d981abba1ac221eb07b5585eae04dcb82b2cceeabe39941021d0cf9918738da94901c1bb4e7cf08b090f2c333750469448c240f76f9e01f4f5d34c94d24bf3b27e7048a705efd5265abb4d64ed56c27c7f4c17133500b937ecaa8a8dcda11eac21d62ac466a13983a2c1a139f79eb63a78d03d843be524a1af5f70cf30fd765fd93c4e5b9a1c856b8a2712f97eb08b94da599992a7d8aafae6fae5a124e763924fa99cb3c8e81fa6b9f787eea915aa534eec1387a25eb3093981d34ad1e84d0f2b25fc16198b71fcd939e75ea154793f7b9393a95301a7974efe21135e879c9c14b856cab58fe1358ff31c928df5621f0a550142e348ee6cd078b744f44db802b26b9218c37cd918852f0dd29680ccbca23b459879bbf05065f87d25bac10a08ae4598486bd8c06e63f4a266e47e1fdfec4b48f33ee3150bb5855bfdd96bf878b04e50a2d72dfeffd04bc3959e77c24e8f8ff09d5a47c6646927391678d3eb195f8fa36e2c02fb93753a58a8edf11fd2340f26ddf470692529e6ffb6c0824cb2640f77f395e01ef2facc49e7f8769d3283d2d3fa34e468149ccb9526d9ff810c66d7b67a384ed1e306067e9ae88da43823e0dd3d432d29fa6bdde3aeead2f4ef0eed464b3dd47c3041f2e009e4bf9caabd412eee49d3169e3e25d1951b840b22045b11aecdfa859f5597557c1592ed51',
+ '22e9355dee8f5bd9ee753acca4e28a5eabd4dc284a47d49549b3928e03d77156bdb5f4b236defd4e7fef405c0d5cf87e0d6cd8e6f78545bb66aac2bf2742ac30901b27ec13ae58d813a5d581f59a59047b22bd2c4d0a23dbd9e075ab5db50ed44222b651ac89d43e4f9ddf85842516b99cfd7142745dd7bb495d9198ceb9ff0c7cd2892a2af6d94adde147aee18495651905dd709306c7245f2affcfa2d5e79ee63489beb47ecfc26579f645353f40d09942e9fed38e5dddc34ea89800922e53274f9c5773a71e6f8e6bd391a7df7ec1c5bd7e335b7c00b01cd1733a10773cf3e82cab3b8ffc2e1901c1c5dc60ebb602457d82585458176313bc47fffc0c7946d0d19fb32f0268876683b66c88eb5259c08adfb5e5ab0cbf4f160e2de9d94e1388485e077b43328364b78d8b4667b98fdeed7b9237fb468c79e7af0d01672124b799d27f9c46f5d3a367327fc2e8536c48ad403771d32d92383812e76c3d5b3f06cae163ba931a40cc9a8c9025f588b7a6d214ebbcb8299835101f0352e3652d1de575fafcb30792875985089717c03a9f65e1f84034a2681777bea8dae2a5b6a3f1758d8b2d26917bab042d3fd563bbdc6c8ea424ce00c88775d7202cc185a141f7c9648f89de055198f24946b7d90305f20338910fab812d1352b171086cb89efe59c72f5096b5b02835a2d3458adac1fc28fbae7d0c5bb05817a4d140c2c1630e0dfe9309b86a4164b6800f08df07b894e537a4a2891db94d0e4173f0ee85372fcd50e43cabecac535d2d22873e5493c122329a068b91eff820cf80a05accc36a81074a71d70398fd13f0334a1087fa8cc736b92f7b7dfb6333981fd86d5eaec61169ec31a477ad6bbe106adabe6994fbbc8928a7bfaa849630e279218f02a14c65bff3037a0f94a447d6f0beaec20a19e97310f6c1e0339700f6b6022882440365508670c194f5b0978aed7e4a5a03eb363e99a2e7e6b070891cfd04a0a43ef3fa66ceffe1cf7798957488a3f9f8287b8c30ea359b86a229b59b76ad603138240a47f3384285c36328dbeab4226621d0d1932c374486b1c86bbd4a88cef0d99f7e3c1ca34c8181193a093b392f6a95f2efad862e49af865793c8a11a53abca6b3f8d71c9688a58c3b244a2bb87cd2deaccc721a414c9c9dd1405a2ed5828b29034821b31fc67f06877d547c1925690bf522825200c9c2263d8bc768843303a174463426372e1f210730453d3b8cf6679671ab92b99ef37f0c63544737d8b0a0759770de830c006c38d18ee7961fe5921f5e3054c238cb38cc6b735376f01c9685a07a547a0ccd38aad5b3f3cf5ad0fbde3cc320b12393c3fc08b715b805a1f422e5b320828cc026cbb136fa3dca23058191d68f0cfbf7a129cfa1f67175db69ebfbbdbb4328a3a72a089a5377ea665f77f1ece84c0e6170c424a68e39ed43383f46fc8d225cc24a342050a7e448056a4fd178d4e4e75b8369bc373587c8541fab5be22ea695b68078acec60294faaa667b4fccd6e13607968daf694e1938860aeaf2118466ffada6602e004bd7da0946e10fb4a5e2efac370154714bddb7aabb7b51bc29847f9b89ad6be1df76e91d4a179f6d42b7694f1ee0fcb4d930c9561551244c602ba5dbb757ce9048d7ef8b7c05471e35ca6c975510ed597f7a35322152b2e8b2c3371538489f350e64b4fee4944ff00782c02ce37b6f89c623c4e5e66745b9a6b4fef0156626fab784ff1c89aafe0a3f462cb7d007cc6c34933e186c7923e7c466c215559bab05cd9e998d73309d94658079e0249e4a2953326b72bc6e959994414b2c00ce757ed81c4bfaaa0f74b802976f4ee82733e9ab69129cdd30e3f79fae60dc1fe7cd8e5e65db0b372dcb9c9df3d6aa248acdd3c29efd3dd7a5552a6c13cbcdb8cdfb5de9e111672174ea68befc769bdeaf6110223af2feb3b7335a83d952a8d66a471200f8b7b770cb1845ed1a17ca207e36c753a6ba87784dc4ae6d69be157ea5f8d52c1293a22c199f5bb3b580def1df9621db491d7f2e4e514fb129d6bbba6658d0f3976fd15e48c5d197357a805abac746448ae28edea42265d78364439195ad28e0ef11eb0e3fd3cf2202cd5861f7125d16d3d35d8f367f1768de2e540c1b40087882419942e6107fda84410d117ebf8ed78bb69449ecb08deba2fe9731de599645b9c9830425c60749239d486f371603111954db14aefcf0e8369ab40ac740facefa70505be43b8ff748abb0850a5fe14be775d8f964ad68e704ca529fbe3c5d9baefda9500bb9406b5931ee5ae0e1530e5ce036fff6091c71735819f6ac96e7353e3935e98286412dc0f8a9f69fe57fe1e05f9b2f4c59350deb0ab3197797bf2310ec9d3031eabbc5e3c88a6231b38d4cdbb8b08f4fe4da44d4aac51009840eed8edd882d012fd6bc2c6939a57c93a6d7b5b41c2ae5f4349d97fefae148a8fb1c1fd69fe7d89bb99802a25dc76acdf20bd71b8705f7cf6ba09acff7961e4aaf372a9d8a66a0788de4cc3c9ed1e2a8986fa7b59b2a6ebea6d546e4673772b151c619fcf0b23f0c7925f400a6cacb6dc08c8e4afda030090dc50307f0ca2b260a5153b879d49fa0a6e5e1b31aba0f6594e8f5a1586f27f8c9f73afe3a5933f10261409cced3a67b3cabfb20a5a995d5390d428352a039d302f35192eeeb715a607d486f97b73ee76200aa0eeda04508da83dfb8933950e1df42b28bb489cda94d2ed3f87b884e2a406b56449aa607459dd03208c445536b896bc3b1333a2f25f0fb4900234fbd8dff74678b55c4c3d3aa7ea53b8dc92ab928f6eecc14067f17726c124f37147558c7a345abc60161fb2a159e1895cfbd13e36c9aa3fef5a5c7fa0996ad5edd5dd3bf81586c9465c905',
+ '9ed1f4045de7f9652af2c672e265d35757e111caae0373890681ad045b753c56a9f8a67a54f303bcc732bc6d5bba93db7b5de381078b297af56bc43c3e2af982f8943a8abfe1a8815ad1d63c9cfbd02887615c84b8497fcdff48437a4da0b8ead3cc399b6858f09c2bf69d456acf9225c317023c89e47c6a6a40b3284e7c807681c82a141501e4f582ca97f190dee2ba77a560114405293a7e7a9bad0a695a0e4cb1955f8f848b75a7754ea8d4d1c7cfea33fb6caf538c23851f8371490d4c9a7aaf02e39e88ee02e11e4ad300504a4c65ea5db39477b00ec556f3dcd210610254e9fa0809b513857aed11d8da02721930f10d50b9189ae4874874566f1b9e2e228804369ab404e7b80b2d212b90471f937193a9e6df548b131c8d47e6d60d072cb3a9d5172dc90ee4a02614baa1d144d6927828e573e5edb1fbc71700e8b573b993704d49c768e445d3821b51de1971407b43337d7e55f1a90da92e85fa9e5b1350e107f82beb5025cdcb9db6ef268f1a557c3475a5ac7e4279bbf43db3d1a880118469ec015939cab68802b27b0084ac47ab929692f37ec66885d2f0bc554049fee2dcecf0bb897db542b10d2ab03a3e7a59b5a8ed32d87b01902e65bc320db64082c9c2a0182786f280148fa63d9718160d05735d6f74aa6d6371655c71f60effec9634ba78fb2d96ca920094af85824250eabe8bb43a9ad38604b84419e29b62a9ad0be6e4edbac9a893279f0febc326a9cc531f0812955c824d261b32bba39240740ecb62e574b2a37fcd6d64b024eba013f8c074e4e130deb67789c5686fb739550712fdcba57a42bfbaa6b6beedb9bd277616fe98c77622a67238d8d147f81db6bc62ba8793480eb9b6f30dfc66b7897094cab1168f57d785e0cd76de727d7301f764a30186458ed689fc7252d60e8b571c84924681fa84ba89f3a4773a6fc39d7ec0bf22c8994a7efa68eca887e54b42d4ebab10e588b1af35506c84e5f696f2191d16d0a06bca1ad3df84e6bf65529a86a8035d0228e6cbac8cd5edbc3972689223b1f455e39710da0b41f5c7b627c8f863a2132103ac76b550e79a0761e267b07b9777302b38cad8912bbbe7ff5332f3c3911c8a408a181a44ab730e956b573acdd03f796835ae941c4a2166695c423e70d75e85080919a10f286118fe97a0f9c0653678c51726d852a70f8e1bcc5fa3137cc35a83afe21359674a6fc3edaab5fd5d43746864996adb496ea53d2af0e854893250d807d937cc6e18590ef5de352cc04db779a76f13603824f4f9ff5fd6da0592b0309667d70e47b11f0438a243f4973e8721650f9b897d1bc375d213354ef8cdc2b2e6f4b9c7faff09cf3d5fa5fde9fd2d8728e3cafd1f395ad92b74cd576acace877ee9a6a08de85e978c7e24f0be72f1d5775268652668004e92ea659e64711cd6e6ac66400a566883ae6588751e851a30dbc43c689b8ffefc90ddc7caedaaf4c8a7a7c15178306dbc2a7e9933121ef2c2dba10faf85969c0aacb5b9840e5d9d8548417f78e05eb66a88301fd3a125bfb3ff52a9e1ec6422a06fda0474ce72603451e1675f49c78bb351e2e1fcd482ca2bcf3dd2a7d8e3d8e860b5704135d185fd4af143aebb5533c20cfea857b21d5b51d4cb52a952fa060707acc4944b635409bcd8d90d3feff8bd41eb3c8c43deb94d9564f08e6191f328ec28ca14190e14802ffef122db7d20cd791165428fae2e489a7175ff0e91e8719892486aa6bb0f8a29cb559899e8f4193ad8eb219f73006a6c58aaa360e02d0a9250073dfa538b4d34a7cc158116bcca0fa895d37a1778da928354235f670a1c62cccf361ebcd1f4d7fa1d419af0c0077de92cfd2880dec194583e26130ec7cf916c67fd19e029a59b2c11c6ec5e47f90e03794ab2987a46fb412f5585beab4aa69a9939f3a5da8806a570497f499bc7495e415f1f4593924ea5ee58bc5dffb629bd2b92b5f52ec7a2cd02755c97029ab6624fe7777173cc15ec44d6c0d40b3aa23ce6b266b76b87f70d8eef8a32c59ade786a88c203601dd97df9779a0c17ff9a81923e0ed3daf38bcbde6211002cee480659ea09e3a3ad20c2d5e451ec6b2d99f3f7e1b530c330e970b874faeb7ebf7c76e5b654f984a37522a0c5ed5b4feb25fadc4164b86e699665e5cd4cda0534032ed694a92d4a275d0521c177430a61c8fe0b06235222c41b112d160fb407766498f42b12673f6362d32181f68be5b809674ad9f8e6296901db57f74b63cdac271a0624968fa97c0bf568ad721b2818709e6262ed5dc981d02d2d1709ce9fffd51ed6263d8dd19de0d1beecf3db1ba886091b48099d4b340e6e751f51ddc497f50b2ada1049ffec6ad0ebe682b30591d2113c33548ff7a94505f8b62978869b0f49cbe816ab7b91305ab644268b30fadd1e8fcdd71b1409fc5c7e859b872b34ac4081f7589c8cb5bbd8e8aee84be20c2d99dabb992aec7bbcdacf34aef2fc89535813afdc027b8091b049cbcedb64ef4ce8e6a8a2a2c5280ce7b26a6aa141c38cf94bbaacc791193c4ca144e323fecac2757b3afb6de83874ebf0cfd87d4209df8befbdd0d113a94412b8c02c0bf7c515a76019ab719dd27c4e510cff6c4684d576c8634e0a4c572f6c56e8b37c990acfe55feeba982b1ead799dc9e857975bf5229e513058834c87c142647f5c2abe77993e6131c8dc458ed29cad99277bb7c7f739327ea5ffafd37945db9895f30c962ac61917ebf66fa8c216611cc23bc0d45e60ac3ed809a99cfc27f6070d3c1ab300bfbf74e8c2c381b2d28d79121f9da3bddd677ad9e96269b1b05cd3ae4d105683f1fa51a7886aa30899d8b6fe6e77bd7d760c3990e701c202a7b5045e6d17081b2473b510908c962e33eadd6d7275a807f44384e54baf2f56d6aba307e5574f301fd2a80b214fe08a686cf54971c0eec21eb362c05c93391603e4b690fef7b62aabbe328084de0b8600060779b47f1615be05db0963d667a8f70848e1fd1fc27190df5c57025f9f88c25f1a',
+ 'fb9de43c9c7860e1a3bacf321b5922ea1d15e6f43950c7dd181f538a60a10126d890725fcd15f6d2b4e152c31079c11296d1386a8f1deebc7fe41cec045601ca00df2a43fafcf0d9c24e425054eda5aafffe7856c7887da50783957d5b32cb51bcb39450edd555dd17292640b05cd69ba0c32f1fa7387b7ff92547a52cde66a5543439ef6d472ec8f99b87fbe96e5dcbc149e42df8d6f0349df1b8cd4101daac1089a08641fa8281b1ccece9cacc4124aab81fe88109e5ab3b10725b6044305038db527f329759f086e3dd721a1e8a8da33769b80c7a60cc1ba9fdb9524f6f771351d54008c6bcfdc208e2fea00b40edf3ee48055fd06c7f85e5df4ddec99f0bea14b3338b2eb190ab6584f5253c6c2ee306463744b40eacc0eca281ce5bc9f3054b73be827843918b1f4abf71591637bba7ebb680ce503b15e5cdbce9acf417ac1f9e4bb74b77e8a861bdc44c0944f0c3f8beeadec53914ccf8e965f665285f27b8bb41f050577d4b51636c7ef400414222473c2542d1202d4f7de6be2d6db3bd3d5208f62fea38bb17c720cec35112824c0e121cec21457f0f11a609adccfbb8d6ec08918f380242dc6e46061e404bc99f9cd58d6c306c0c632e0d9e8c4c1f5ead10a888184ac126c8e248ecacd3d4604378ee3e69077e1a715b834773f607f8e74653a573275fb002aa8c491614958f5f2ac3f78cac61f2612070ca0ad09ccdaf166eae48d7c228b34e7ce4030b779d1c3bbe827d29ffdc5c053cf0f8e5c4272b09626c3e63d4d602567e871cbf60d4a13246a6e3863e1f8a934f81e63a684c83a697a7fed27b01184bfcf80938397a1797c22a66c90805125112457bd97185e0757527302ca1f31cb55d00717eebd8a5f39cd7affc44d41cc7eb0460c9c51a6aff65b37b7a2df2371a0967fd9a5d3674cc3b756626dad5d27e43853c124e5348cbcd1587c30747910468f5549376dcabad07cc3350b1505afd8391b8bdf278a3621613fd0f173a51fae2721e712011731b68a3bc81b38ddccc11f07cd09bd0a24aa97685664512a085dfa5053c421ef4a0c6c3acc1c7d38174576a2f5630a46e0dec05c6d17919a52cc757affdc34b55c389f5a918d56c707cbbc67954993c2e32498a69579b9589770250c4bc8de0bf0d19ff5b8fc69ed26ca7ce04d4bee00b4227cb150193cf73eef858ed7f87255904dfa96c2d80b2cfcd824875dd87d82dc4ee9273d6490f8c1a1137d25099624c3241bd81fbecf08e26b4fd7778dc6bbb4005a52bdea59dfb82b6b41a36abdebf1fa80145fff29f38ffa5b5b0aca5e3dd3fd6cce42190c0e9ae4d996d3aa5e2db517d3f5d437b08f4e0bd38c881ca86bd48c8d146a8f0c17e2740ac75626fb6c752263d74cf6d74eb52644d68ff339d80e3437bfb7aafe8e174f5b6d7b8f2a2acaa0dc430a9b22945dc02be382fc86f5198af91a89fb37d3b1dd67e3e4414eeb2df989b39db30702907ff51df18724656f075dcf820e6c8b3ab49ce500dbee1007b318c386dd190bf5ac3cf06c21768b772fb2490042e1172a5b86cb51bd9c4bf063f5dd5657bd3305db6ff5436a01e9da7aa301b53759fa0938a2116764bdf16062ad7b7cd187fa49840ab727bf6b03015eaca3f15a2bb64fd27b51b27fe7a2e0559c287ac8fdd4294ca990799ff66974624b8a4539dade66cf7f06b35d8dd2f8a36e6ec0bc83533d8ba92bc99a588fbc2bec3f3154ebd4f68629aafa8c350401e280a8ff1abcf9fec7a5e3982cf5d830f22b3d4619d33c777d07a8e88a61a894897f8176620f2ad5970c5255c652e58b7b650ee4172567523a959b1de68843390eb762d730fe785a6068f28304e43a142e462eabcb56f82f355ffbfb1f3d6667e8d4d791e60f7505ba383eb0574e4873613b06fda4ec6156f3c34610a2e58499be345a271fb673601ae6b16e3be59321daa0326a425a35720c6c03ff00151056e56255174c8825e1f910d51d214e9c97e1323b721224e708d5c6a006b540bc17f4fb72d2eb08ff13db33142db5ede74e2b5d8c95a2e0e873f64fb4d830fcca65734c1dfa799d516e37ca19405847e5dd0f8ea04552f2dc4bc483ccd411bdddc7d6e0c0ee76d9df69a5424ed596f12a9d04420121c0ff1508e6ef229ac5e868a79666063f7122fb8e9a381f6dd05e1836a014398b7b4a3533c40e808cdc10ed9d4838486de72d38659d181569b2f351127971b412c088af44f24e513aa2cbff0152c421ea473d1857146ecb59bd6547b8e6f70b285f1f05c4e84b0f24d88dff8c4cb60d352ff70dfb96b8abde4e087ea28ef0a96ca591bb3c1c7124cbebbb7186ac7a74feefdfabcb9a622147d3716867b80b193012754203155d3c903af8f798e88df378354b543fa607e6271c14976118b2c513443e3cb2596bb12264aaf42b876d03616eeea9d03026450c56868e3727644796a9410dd25f291cf2f765e9c70f941f2b1b2e0f15cf29d858312baf424aac1b3ac04237eb9d11fa7241ae5af3f79fc135b88e1688e9af1aab3a988d2b1906ba6017a63d56a9b2b430f1e8ca2ac5ec7421d58332a206127675b141aa4a18f09fb71df1a0bbfb87e4bf7699b9d40106b27fe0a1f5e9aae0963853bb8937a0e655a04198e474190e066e5e55aca81f7b0690687ed2d85eb5d473806aac7c6774569c31107b2768b193ce079114ff97b5db03db5e5be677fecba7d5d37670c3172d3b5afcf74e812f076f00d075430053ca6ac7d4f3acbd27e655b28a99bd5a4b43ee2dc2db41f8b2ebc1d8a8502adfb13f15ac772c8364cdbf0da4f468da838d253537e689aa2609bb570d6d380f8c7ab394f7c97913255ca0202ba57620d5596b90f7ec950f9f7ff0af0da39f799cfcc27948206bc46551443dc5223458c63c6181ec598fbacba2263a67eff8f187d3d6230c30772fbb4815d6ffdf45caeca2b1a893a1dc6430fcc6698ab6c6ffe38c8932b26e2dbd7dad9f5c3a410cffaf28d6557c49fd74760c4bed047c9c0d36733628e92de1e642fe4b6e130c0c7f944b4a1904d68564014d311563b5ea91d74477660f144642a1c6a45d0874c6c312667773dbcbe6cb493c1083070735366c6cc78a92a5cd4b007324d9893fce52c01708e65f9ea412d45564a68cd50e635c58a0f71256e977e590574423a185b',
+ '58067ff7959a1966d37fce5cf06a8dde6e1f1c676e91b02ddc4bbba5e70e0549b4bcfe9953745360f2ffbf38506b245075f5da6da12d462f8254af96a9094ec43f25a405f4e3110c3944a180afb129b7dfe293c12b0076a80406eaf65e6d9a7861081a42622d7bf311151d3af485c59914aeef69f653661fcc2911cded8310ec832e0858839aba9ce33bdb70072f1f807c21986fe86d3e1c4185cf243e93d9a80b6014504174f68f157f8229d0f761f37f0333daeb982727cbe1c90239e51c346364e9318301565511a7ba6e3625b07b851b6bb3e61a49c9d207aa6fc6c00219f1022d03308b03753d7b2d462400379ff632e501c03ccb89bebd1873cd6eb3de1f0489ce3258257703c581b97fd11be73a104ccc6346d5f0f8ccbb173635560b5430a49b70b0e3e4ca4b5ecf30e087dbb97d461db6809185e6fc3d62e95e1328f7502771d2dd6543f9d66d911f75a104db417bfba13125d0f114435dce3013be722ab9594026d41cfa2e550634d891e90923956e0ca3b95981de780ecef57e542c87035aa2d258f8341bbf3658508012a9ed397edf1c76a9ff20817fcff37c738d2eac7ce02a185d0f65532a712796082565113402e58347d1a309b8ba7433a2fde95dc7e3763dd6f3d3b8e968a3fa52e354c5975e4609bb612fb6bf78bece69e47ebe4491feeee8482f977b260dd5784d81fdafd986308e168c309ef0197350aab676fad309541d763f97e992ad4e03c9b660855cef1f475781205d09cf4be0b7592ef292e096fce5cc613a93ca8e7ac1b99620d0c7ff6e8ed72e02ad277da0742f8d32b4ccde0dfe9d9c7613adbc659c5348891b57f78b22560197c368311947ff3cc4221351d9277cf1a6f99f014dc24c1ec627e730277bc6422332db91d19993e2afc5fd19e7c92f08d12de685b89b6cbfe1dabfd2c901ef98afe4c8c252d3b128fc84d45ebf4d7f88ff96ca6f318f7bd4e11e2604fa1c99ac42c077ef245b22b6b246de47ea19341a381e95e73d064004cfda66ee41348b26e4ae2f3ecd9bae33d9f565fdef97166585ac8d17666884f8b2f6c5b926b18ead9d9639d7d7f91c487a78afa8562eec12ed29fc25331d0f5792598c8d31fb6ae97660dde3cadf369c9cb7046ea84580c8797b28392c8da9ca78ed0291eb7ae78376f5638a2cb4c2d8479aae7a6c469830308b5a7cda201af96a9971eedb96cc23a63b13f387e089a38309f4f22a98fdf11a41d915c43f298b73451fcb8ab8e3700c1b354762cb53a659ec1dff6ba377b43f85ef2d7350a02998d955517e46f213b27d10ad46dde888d02b72c8b5cfce21a840e2ec6dbd457ae33e5ba841aac2b3a04aed4eba7e0ff2f1ae3b769384c9df5c58fb793cece1275b5a79f75b4a1bf9dd6814524616928cc35dc0308a2a319763b782fd67473bd70816f7665290c58d71c1cde0720d37fd4e21481f2429cef0c643bf9f77341d33f35b1c1fb0e38716643c6020c7cbc7ee9ae01c6ae8e968938ce6f988f31d4de2230f3247fcaa2a3dbec0143bccb80906b084853b5ed3c727bd877adf6369948da01b7f09bf4f77a9883733590a3cc7ee97f3c9b70f4db255620e88cd5080badc73684c8b80393302ca8803a107c0c74d5717300882c0f3c581626c7a41ca8776a3dde0f5c7d029f28a9bcd3c4daad2ccf9d604563f95501e256d6e0dbeafc304386185701d7c201fd258d8526464b013831a8bc8cf3292095316d5af4f97352d3bde812408a5df31a9a76e0ad25429c900ca0f87b901812d1545eb877deaa69ab33b1d3812b32fd11870d58c21e4059675ced6ba8568e43372e2f6becf10def3a860a1d4c30ed8a7259b5601d87b0b24ec0e288abcd184c273d63f7bce71a5df23355522a21ea25adb103991808004186c714f1fe5a32a831e070ee70075c3062aa18047374c55933fb7c663a05db91dfee192a088469c1d7d5eab2aef85ff11c88f6c6e0afeb8f95b7845a0c079efd9a94638362e22a33998c35d6b230722a802e45fbda49d9435363cfd6f693c71f475868a085e9f067f751f6dd22365f472e289a25c8c92b1eed8e937f9210e06176a8ad03fa0258f961e58238877053d186f667d744a44d7cd4a693b65735775ea913a52027c1c04279b58c7b5a751c541b163372d671ace7993a251dce47f8714f17e577a7bec2ed9f205a1803baf5496b1691aae6a7e5d9bbb7ec186feee043e923f29ce24fdd5552d49f912bc89b1bf765e6c20dbd74edbcbe4d28a480db40f7d630e27ffb7c143540b1693b246a563082c76aa438c67e0cabbcd114a425e30b9d44fad9583b9496d33412aa34ef82a70b9807528077153c0edfa428df6cc484e7833876dc7861282a8c2ff190972beb5efd2a915af33dbb561a1469892c400453904de10ecf0449a54e13b9bdfdac4dfb4b36d8fec7721b2ff0a44f37c0f00a5fe08d9045bbf88be1ee606b238fae7f7f26de9202603cdbeaf7d65271e75ef76ae89a1d37bbb85f5ee1887e47c98cd049046464fc5a086e25941d1bbdfbd75c133cd5d04b3ace447acc0e7bc137d1e0e7687aca43116f425e59faf3726b13b8d741d36206d594cbc40264dbf6571831e8c7742a8d32dc08f3bdf27245d17e9e259c054ae108b665d9392e277da3ca33aba6031e211ad922844496309ebb827c8ca00de36040ada318b4cce6bdba5286de888a0db545d11f9e624886e385c9b48af23fd41b09e4f40119b3823ce75b7bcfe38b8190506e6090abac1dbaccaf069f072d8567e0f6d930ec948281d74fb31e4b8a1268a70b960995134d9afcccc34763598fdd524dd31681c28012e6a99d38250828a4d633107793209efa928d8af27b464be1da6472209ddfd366b1f3caac04883cbb4a7f60594a569a602a7bbe4ec366522bf5e526fd753f2503b5b8abf87e7ce732903b162d0f17c8522111ddbae05c2b02111a71fbcc82cd605e52b19bd77880a13410a2717606914712ea89b5367a4f1ac8aee3a9f82d6503386c8c04c3edc16da27a85b503859db58c6022cc4a5336bc890cec967ca16462c0609bd1a85f021e57e340663c273633da0d396ea0696a8deeaefd3fee81441582d95171193ecb29e0d55303746e0338985c4e1acd4f6305461fa604b24fac91fc215d618b2f9526c9eb4e2b60cc7d3a53d7c16f1557a8994bd977936c6ed64e98f300316283f87b7f7f824c75b51ccf790a43da3d34b3d04c66d24a715179c1e5a8fa5f53ee5dce8e1cafc527da5a5e946e45ca99490d40e43256b865aa0f826ecd9c49c3c13d66524d27440b8ede6e226e37deb',
+ '0233e1cb2016c8a4268a16fe8d3a980128ba62024d1b13b135bd5a94a565b9b4ce8f899673063f6c49b1ed8da3b00c234ea87823ba5abc835b7b90a74fbfa01c41388f888dde3ce1a56f44deb379c8ab80b470e19496afb59f2249701701131f23d3fa8bb8c7476b016922d905bc54038489512321f675b1c75a8ed645509b253b0bd3863ae9e972274a8f66e0a2399122cd23d0aba395dd9bcd66c420d7bf96528521605c6ba95eddb8936d8e53febf2429eed278c597a3e521d91ff1bb07da30ecec0366751098ec5b37d655f62bf45044bb10d083046a6bfeefd00027fb599de63d56a031a5861cdd82897eb3489770c79172fd056399f60db1bf9541247b611b1b4151c5f09134e37d6e50b3de124a5979e046743c9a4ef526d4f83109a943358eacc3597921fe182e6e151e74cca7a37a42f2f818d43b5f56f8702032490dd99e7f3c16c99f40219f6f131c932d4e4c9c804ab87daf85f34a11473628c585b154a205236771c3a9d85a94d3c29d09fde78396ddf693f3580908c39a72837465b7fe66a9db014f56df18de6e89849e5e64311e8e08e84f379bc62d7598eda19dcf79a6d0aeb6acd0be903913704ad6161a7329f43d165f370932cdee2369d600a5fc0fbf67f7a1c07da9efeb005779ce123200361c85ec015a33ea2e9a61d9364070e79f8e2ef7ce224d471dc0144f2d5254ec7e2d9ebd3c96b3cd5c853c73e8a9bb7797abc276a1b76388d139e71fdce4c233f1f9c79c91c699bfa16d626cbee6c707d0471be6e243d2fb31f139b382cf651db907694fa8f1cd0625d83b8c5f017adf72e9a10f38cf84e8627273d1c81c24f4fedfed9f281f36ea0f512f9b7401be46a41abcb94c6260978f44fb4256d2e6fee70ab954ba58beb5875da660a4ef7a868e61d4abc6044e4fdeed3bced3f995859d0f373605a54cadb78608a498d296e5594651ae4dbd365977f7993ce89a18cf4876ce0cb20dc91cc553bd2ff7b2f9ac2d519b8a899600fd83ea7f563cbc57240ec8e5a068991b48d94c2dc96a988d22310b1a07239695832a4c54e278673ace1a8ce6b8d0502b7a1cfac03a998a39918efb36ecc3c6db3393a780a943a3a914924a381d913ae1f9b5e4df492b93e53f6baa5803102cbb55e7826b7346798c3d9f8b4397545f250fcd93f0c0c9b87975f19ee612b3d21d304d667d0828f0cb9268d56feec1c8e0028c8d5d651a03de7b48614ae253c0ea0deeda1e2f92114e5b16b324e97f7cf81b195c8c01c77bfa99977bea3b99fd0834f266b6b22ce3fde0d0aaca51378357a29d87e75b7728b617067230e52d9161ed8092ad579d6ec168bf44c9ee90e6e3df3f97d43dd313fb3cbfd083a7b684dc80c0e76be78eba803c0a08898833ac86192813bd6d8243af52e71c4a4addde6034dbec5829a6d024a5db7e73e7c7dd279a7f8309b42dc0bb9fa9dfef9749a8d753f92adce78eb793e9572fedd2f582aea86d7020da7c93086c2ba0d953a2ea2823412760e7ed77b750a8a2601365a1028c1a616aa716c37d0095e7704992aa3beddc6d4af26c197624f65f3570be450d8eadde4fe3a45801a5103d1c40bff2f8f8f28404d0b3a3baab68efd2b2a973b6f54f6f3755cd5b7ea1c42845ffd1c9478eaaf2f44969206a2f27d9a1a1cd35e50b279d8ac63ad3db3dd832534f713810f52c41842536956ef65aa50804d39282165eefb0fe93c800c174e3aef847453b76a1f81b2bc40cae482adc71a46293765f1bee11c57f43d575c2c2bec2fafbb05931a02024b88c116fbf05434a233ea72e872015e3a64a41a0e757ec75335f57e603c0fdd9bd1e81b240a8e6bcbd9dfecbca0f251fb1e82f8c0e2a9ed8997b186540e0190f79c9e658d64ec2c3d9b8637d0b43a7424822847f5dc43db4d556dc1d0f89dc91949526aaa87e3f261f95bf8d72430514120229c2fcac32d18ef889579941ae26b78a2af0dfc0c5af363ef8432897a55db88f069d146ae4f76d6a095b0593f7958ab1da72a29cbc24c7bbf8e755c7f1e5e1d10357cd87baee19b342717c041fa7b4111e771a9b8c4e7912a5c1205b4f7436d1093a4e8a1e7d3bdb1ee1261e8e24deb2df496e449df5f54de919b2dc29d15f96fe8cc0fb776e7beb84f22a68add4af9d032ec71f57e1e374ab3633d2e6c50328cc964a632c8780ea5bedb16ae27ed0d9746f4db523d9e98dd361cac3ee18ca9059a01548a791f3e08d1ae2ad96ece86e1853278d67a6b2a248e29f39a9925ed58ff249354f537bd501c1e6b832e4a065d9247e308c10e4baf401dbbb7ffa64ff54d30daf8c97c1d29f2ae4131c2fa0c59e287924fbdf28debe06a6d2d81a8717928d8940c6236dde4f9d0547be958f964ea941f31bdb25158e3827041cb86d243066e66f65c02602f96171db7fdd15e1d3edfd2453cc84674ce087dcb39dbe2e16a74cc4d136944585bf4f57c6d1dbad143dd6400d79ecdce046eddf0591233e2c97e2c8734d60afdbffc744fd378902f9e0f4ceb0ab067d33b282977a5c435a5a80d102b1cd3248e46903298bd8c7e86a207e56a42a7a512b6c6e8b7bc5c88d88a840fbf47db1198644288ed87c3b684f439d6150beef60b1bbd8436e9557dc9e585839a5268629aada61fb4cea9089909278761e2f0db3aee9e848bd949b5184d841462a9059803971d4f94fec6c541655381d75447d51083cc821ce8a39f3227022aaed83d1271952a90f494878cfc7d404debdc6e742a9fd9d560b3fdcca606823eb54c7962534c509c6a2c75b048c5605425f7ef28f77739270db7b2adf27076e93c923518270111ca5c231bdfb4e2562d0c131473f5ce8ccf6a505cb395d97282ab361aa47b7800934114e48bbf38744114caa8e5a3fa27670d3a668e0ec5e7c367a47487bf058f9fbf8e6a24c64c5d38b68b06ee907eab666cc26a9ddbaa59041859ed42603e21383ddc073b68a2636c87e0a8ff39e08b461f84d3537b1b38cd23e2df550d766805329a564014cf49650f4819a80eb449ae9bfe2b5f6b8d299720cbe5402562c67f3455d1c3691751aa934dd3799d6e68d8489afd023b648dfa757179844677bf1ec97b0445d43f3eb5da4e60d444735b7bcbe7a2fc5955e85fc88016a1cf29d45c58966847c49b430f4ae63c58b267a4018c60c79e43c5cb3f605447294e0d88d068246839d8b0112f4b91e06765bfe5020092d5788938a931f37a857b31775ee38ea19eac7f121b9e72cdd790ccf060d5df0a471ba822f4afe86f5c28180bb92e228a76003635e4c547169c65568dda256e13b6e81f195031b5bf25a0703f4a18ca6b88e58c9fef4c4ef8a94590fca359345b8f7e6c802e4ff1c68c84c896642becb141cce0b4ad8be0e078b21ad14ef9d85e4b3f0efbf15313320b358f85b9e01ff82b16d118a21ca36aa54b42804f7fb07874ba74e14bf3906',
+ '03d14676ac6afd0eecd67c8ebbb62ebe952dfcf935952984a1717a1e66a39bd825e0be274e6085382cda228322afbda334661565c8586b1faeac9c435f868d33827ccb57b54a736e89c65021ce1644de1220e9e3644277995e43e924628a5b38238eb72e678e90570c824e9049518c163744be9c90bb179bb61faaeaf717cf8e0fffc8d3ee96a49cbcf664f71bca5df4acbbddb8d42b11cdc02626bca695afde4eb5476fd2c877e4a20bda7ecae77e20786b8e5eb8fa2ed68341a7c3f86cc4cfe99a7c29998ed0d8c4a7f74fe012b941a33e19b6c590365f075973267405d60ae795ae2acd4f791f2cb3048fe5b2d4ff361bc5e98256f10f5554d30ea3c6ad36508d2699f5723862e21849695f2e980438e9db1f78b2278f741c0fa30b1810fc2d6c2d9f9a61d649ecbc0e03d8f0cdf5b192a37f2d0d9f866c976f0c3664c8048519096443831b7b54899b85d75f3b7da9fd65322bd0229d46af3b72ac109ef5607c020f7840cea122663718e5e3a94229126960fa61787c094bbafcf3865778c67d62c9c200b9625c3a2ce489e4e1ec38b9f62f202a69cabd5235d3952cbe34c0046fb9a72ba2670cefc0104bcaa924dfda56ca2791bd2f6aaef41e1a6c9ad255f97308b1295258396c3cc4b3feca92255dcf5797003b5519463f0608adb2f98e44b4dd686391dcec99e82a4ae60cdd122aaafff6deace1d2025eb3e956cf5271cd7b61533f269b2c5e0b6797eb36adae2a2fe9ce93d87e9bca984c44d1b984097d0fbbfa26db3e01a3eea6227512ac0243a53ea68787d27e8637faa28538385b3aba14e581766d7350da0d74ed6680155e562507228185821e81e48ada141671edc2949db6c0cf8b4d15c414b9fbd1ef6488c778cc8d40af7abf33843814c5527f7062a92f810d5082e7f48a439948993b844b81ed75ab0e83380e180702ee06d60ca7915281cecbd89ac55ff2078c1acd92daaaedff6da61f369ecfafa43896dcde8ade8987bb0235755e1a2d0f467a00f99c7c34f42e8037ff0a3098c24c09772d34173b5d7cff83bfe8435e771ec2be03b87e2a1b98a2b4dc7bc42b26d1679143cdc4cfc9b8caa8c18a63ec08bbf9eef65739373e51167c151911ce9770b811667c6daf9417698ec36d013f8ac627781ba033c42301e479f20ba1eadcd105dfdf699b428643e61b0cb8a5bfc5e65443babcf63793511291bb7afce8be9b55e3f6bdd5f066e14839e694d7d481c489391bd20afa56e8aaaeb78da82e0577b105f2a0a421fe9c98cf972920e68508ce41850bf5733470b3b72f6d2f26790c202d1d398f3d1419ddc4689e075d9a592943428f410cf58e36946b411937e103aa43bdb11eac71031f02a11c15a1885fa4289885752c60ec3a2db332ecc80703572f1b2fd9d7dd0067708acad01a17c346d6d0151a3a3f32c76a4f683243b1b530d97fdc49241fb8bcf66bd1c2300e2d17363169b7a05808e533860a720fa8c03c2930e0f5fc01d5267c8b58734c3744203005a6d4c573149d70a2413594b4ce8408786265c849fcadea33886ad458b168ad92f7fa115a01f609e934023a9c840e5c9d8a22eec0d75a28ab892f323a1cf7932282afecd475f9e6a40c61692a8ce40fe881b3d82e0e341f3b824acca52bd6ccfb6f7ebea818adef1af0da89248e0e07a16fa0ec5b4943b52b9b440c61890c3e65e7ecef9f941a0d9509f6025331922e107c72940e2f90d3c2fea35935dca1d3aa1283e7818e48265b7e3c3c7f1e7a6a7dceb0371168226249d0f21aeacd9e711117ca0f16b14023b57835a070fdcea52f90e23c2a9d0248083cb6b948030a02e11e49cd25a209edeb1f6794ed7594c66fe8910d2276366f4767799d1727ed6d6351898a780cab6a459a5c81c02ed6b585fe4411c09ac8bd4e7cc22e1fe0509adfd611afd5952f840b129cd0afd19b5ab9fb83b0ceb2bf1c64a11ad428d73a719cd569a205e59c2284aeade78306ec9d5207d65212652473d3fe08f7f9b3b3dae4771acdd0a5fa2cf99719300374d0669a49c65f565cfea9e1bd14ff5b1dc3bdfde3ab97fd7d5189b2f46811f9c44f9dd7eada3f603e55a8d703c65416bd73023042a4675614aa23f7ee20f42b9d712a52119b5bf8b43257fdfba75ef23aca91c5bc8ea7a1130ce110adcb4063a83ff553e78f19e8669c9e9aca3e86b73dd0520478f0bea72180ab4458f05d678dc0e6229440e2f7fb9b65f79b13322c6d8e2e359f521430e70d69dd1818b3fff3fce6063f361fec1c37945d94afcb1a730b0b9f583c11040dbb032114caaaf582fcdb4911f7369070791412dd99ba49f650d38bcc371dea3a926259758eaa10e3c7af31310d91aba068cb5e9f5966819f636c4123caa0ab169b6ccc62ec1b17be654807c7009605530ca28b0278368a164ec009847e020f4add0ac4046d65d3badf9012fd56af65326e81e02d7d942b0219b52b17daabde7e6025878298f00c803c6d9cf9994394e49551a449f0e72e0bca35ee066603e0592da35e05f62489b884c9ebe350b95b1289f8207d5a9659da6b279c76aa73dd844b6e91b50d6a1409c08b48e4047b806453609cf2420b751703727b912f619ca7c26c68b96828731bf79bfb243cf0cb81fa6f5fcf30ad496a9b00d47f7c36e6699b872b3a8cc76f9731e14cca80800612860fb33dace49f793ab8ee68195b8372dda5215f03b0d025764483fe5893f7be0027de643f183defe88213738f363b6b90b09351544a5e6ccfafe7ea4d91682e6f9f296b70b1ec72ad4689c86531d6fcbd8fc93707fce1154307c1b3e6e31f0adb5fdc7bc81774aa92ec999023b31bcc6b2a0b3aa43020c7db355984acefe4f94d4e3fbbceb39d419e5d8bf35223d384a269a182b44de42b9e21f783261e81a3cb569a540aeb2fdcda7fa71879ebc6cb6a34d236bfbbf5e994b3e3d8dcd8bcb1605e346d0428f1264c3b35282072bc4acc73e87f4be11ba8c5d464ac56865fcb114f562f45d3acc4fe7a7c4acf207da9cf0e2984e2291e103a6ac4271d8e3098e80f48c254522861f822c5a70f5dd08f7cedde6e0bfac5cafa5e4970b85af8c1ec87524db90f2da3401c586667917fa4d9f94e644fbcd97e0d993cb0af507eed259fbcf8fd708332e0517db160245091f118c38f34d823d37c02e50e8bda8cc59c40cdeda7dff1b7e0a87cad807f0cfec933831644b468724e808bb3d25fe8f15850ce513fc341da46089c8452087b926b33d72207b973038bfd77c60f475b1861b8074f4d8439421c5b265e5b2864f6defe07db044f0b2d3b60f270c7f9ade2c061c00c5a8aa2986a886262254790d22a7e5b68c3dff798679142d984dfa6f97c3241b3ed3c4383367798b937cec8cd58991bf2cccf00ed1777af28cd600e029d12d16a8f04e6ac8a10108a1923d28f6e398ea0a889eed451fc83862be4aca82cebba59c74951ba6d3b11fe54b076c17745fd9bde7389f41f07041a25d3499030a442aa1d744d56e8e0e53d6fdc7e0b78a5b00af6f798f688817c0d4396ad65b125b28219eba22801e7d26ef20eb0147b41ef16787ce581955031860d93a0cdb85652',
+ '08be55573177d70c9eca518c96b457677ce07e31a126cc295c536c175d28a67b3ea50fe35b87fd9de40f3e8e30050a6254fd35e6f5d9a9b15a8f140ac52cde0604195ef1439d4def24a47be312bfc090d26b36ff5a96a520e75f3fb34a1e8e6982a4aa4790c4af4c87126e53e3ea633b1bb4e8447a67a7bc2a4c55dc92059eede2cd5baeeb010bfa35e081a64b8fdaa95a5fb27ffa5398cf4cddbe4b45e9f5d7491cd9eefc5e494255961ba3f4b40d22b5f5fe7685625e9f749be3c90cd27d72e11a8dcf6ae2526c0fbca3148364e4f054fd33f2c19de275cb0c2a1d8fc91d05d24edd19de950cc08ddb83bfa3a4475a60ffb8bb560b0c9879bafc1175d5bdd744413293ff806086f47a226cfa7e1ea70184f799edb5c552a52dc26b66ff45315e79f50776aa36056f22e8e530f951205e1357542ea1f3e977ebe2f40c4e9e5b48808c3bea1c7786235a3df1ee1dc80da03440b3c0d97fa6187ec6740ccaae9d2bde61f704dc09513baa8957dcf36cb6ee6f1a804c6552d1b06ed4b3117b5e3f2f19da056cf4d6aedd9a34e0a1822362714d4e81794b53b266417678c16a97887bbb612cc96bc5e532b3a654e5d3d65a5155427ff09569906381138cc49e3fc2384c5d33c34abd3d617c487b52ec6ee7b5105f41584b7eb5cfb512b8c31f3f338d5236e30398a8ff927e801c8ed7d14fc5040d915a737967d166ddc266f68023a3575304315d6d74ef3fb701419ce9daabbbb5359e1741ef911bdb72542ae9dca1e21e5ef5a2f4e19d4956f014419cd28cbdbdf6cb3ec095385c749236c361a5b07cfeb8f56e2591c724c3b2aed0d47d93908f9c89f1dda0eeb210e8b3cad2c5f8ab5bdbdcc9e8cb9356680fb9507825e5be91ef8237805adaa3173e74462385a0fa9e9050bb25d62969accedaca7010adcf2db75b18910925b9f15a203f3c2dd1ee2d9df94dc4fbb2e5f6b9bb45a4861149cabbbf9cf9f6f67c070460bd0505b21171ca8186ad825650b09604c9fcd139b6cfc454cc9e697673bf06dcc966546cead2e18d6fc8b33c34412e5d5f60384e9da69ac2af69a9cd2682273b6a47642601b9a8c80efed58d1811496c0db8027887b605b24d4200221db92e26a9907b09df8ce9d76d3532708588afdcddec78defb67cdccaf12b49de1cae4448c29e23d0bb46a659456100e020e2753d7e4e2a98121b9b7dcbf0e68f91f113e1efae1e90d9af418894ab88f170b7a7902888800a14c921cdc3356a8ed1e7dbb64ebbf356ea54e9856f7721a4ce770f866c1b10ce45020a2b854d4884631ef6468e5c64ab53c428e034786d72a0ad1750b75a6f5d1962bf2770cd02e8bbf30e131ce3c506ac996a296213bef38dc659bfa8db0e2f1847bb3214291c2443009d9c7906a6e16b3137b196cda8afec6f40f3af215fbca83d78ebfde606df9eb3ff4331198cd406281c29312abcf052e457cd38a1d6ae6fc092b58c78556335a9ddb7c3b0e95703ea81e0dfaf6e7d47d2188ce3f1254cd55d731f4e748a779e4ea36aaf413ed2eaf4f388e0c03faf41c50db32f83ae405f80a499b25f08554f1edacc626f0fd031d40b71e30192feb719a1e079ddf3f184b6a1a5669c71a4be96fe6d6b8eeee76f04144c54e82ae43e6c7f9551547908eb8be4d2c9b138ee635388ce59253e810901b878c4e84a083e29e13254abafdeda5d08926a41d09ad3b1045f89c6712576596277a18682d34cde8157e2382aebf7c66774b6a2db22964bfb919be7495d5d879cd9895767cdd0e6da66988e6cc8ba449bea3d68bb1e1180c914fe0c099124f8e20edabf5b6060ff56fed612d7fda85410736d07e2f5dc0c175a3ea944595339bc981432f02383748181296a0ee338715fa0d414f5426c2a40c1bc438c1e6ed696f5c466a89f9ee2d48e01087421e128f3e2f0f4747c3887effc256f4368de3c0bb879aef72d49c6ea760b52aa2238e757ca424ac31bc7816f59d8910cc127f6755092906e64ea9d1de996ed037cb63905b7566c6399afc3697b643d3aafe2828e4ead9f60df17e1f959324c2299df9a884a9bfdf88e47ba48d146d87f9d945c9103294090e44811f7357bd499f22b9859e48163d45225d297ebff072e029401383ea418512cf1b9b21ec23abc1e009ee525b522999dd098d13c0b2dc887880cee21ac8b3401bb459475a3c30b86152ac48e85debac9be998f31e9d0f184d6dc2d8c811dbc1afb5238caaf222ac2dfd8827fa1ecba5e76412c6e19852b9316fb60bfe0345bc1384bf98e32bc7c4ca704798967d11ac46e2c236128aec93abee7423c388385ad08bf5140dd16929c215b442c66204eb42cb71e9b75d26cd4352c2c92087fec36d318fa831cb4e039d5396ce91b5ed876288e787dbf84890810369a51be9dd1e72dd53fb5601be79e191b4e3005e14ab2e7fecb983439a03ee6a315ebf941e9368f90bb6845b03b31839d72a1946c17d2f194827b926634f11ede19c1171084cd6ec7d80c3c164dac9b2c74ae6533c25ecbec2788489ed9b72f543091b68e56e441e72021c1b28167d644cec6f6ecccceed72454ed547e109aeb1d4be462fb243af7b1e49651988ebbb72eb8bb80bb9abbca3465fa6f5fd61035380a7445949441df04cd3a4bfc6b0b133455d26f4fb6dd01bb50b5c2d8145c3d5cefd4fbf6e6e03e31650ff29cd4f5e0286cbc25d149dce280c0263630f2076950a10b6a6943a07c01551d2cbff20286d0a48188887a3ed74d5b54b1b1999823dec9217c37d308013c456ce2aeedb96df4a62a82be728d47e8c9471ca13197d2cc0f1f17a6bda035aadc05fdb2eca3fd5e1abffab958509a1ab3076049e18ddde31a0c25002af08112c3ef631117469fd5a646d7c3551037abc19d63f1d201aeeb93f08d8b41814b3ea232fe13972cb920f5c90322915488639a100fe12c7bacac21d337902bdafcf420d7b40294ddb508ad4f651e33a4f40ae7684b6016833fee6dbe0b560f83fdfde5e46f2435e0f95577e1e5016488e3674fadfeb7b8a2cf781fb8f1271605b5219a6416c3a3bcefcffa866b114b89bd437be8122f3b5fc413decad14967e78b27e75c912506fcfd3ddf46df98112eeb6612216e0ec743878752fb93052cb1e9d973d6c89285258d69cd2929dcdd00d398bd5efa9c83d57e9c24531fa38aec36fde7d9a35448c106196d383226d886dea124a99e23989d1219fcda5d6bab1fd95cea6e0ab27857d016677ae0bd29487448ee0942e92e23abc8819936a5b7d23cbe259ce5f33d70532862f81cebfacefe56c487ce376bf0b26f5c979098d58dc6eb3f6b1c60be93f61606b8cff670a1e2944d292a557a8b8dd735dc558d2ed9dfaae1e39741244824aafcc4df27b5488ac732f93f8b817ca6c8b2716bccea3defc4b30d3ede961842aaec2436c6f14b5cba1afff321a94c640a7e5dbc9d30425e025cf0fa7e3d89b9df7ddeba1b4b33c234ae422f5e19822e643fa82e48286e952a8594b16a4125f11158fc556dec8623fc96cadc8cfaa66e9f9e5bab14fc4cfa04d5024cebc97452082585ee06fccfe7db799fe0f173408b83427d1a4bd161f65af541b447fdfd458b8b826c2ca2937599ffca25d5add9edd8d4166233d237f2f28c59cfc60648306432fab928065c37fed1529182cff8fc66fb2f6d1424555495435387b20cdd7c59c3f5bc4251b194d1973f0e3f022620f560ce2238f243850bc236',
+ '5bfe61888fb48779d4dc6bfbfe891fed45a830b345edced1490d2cb8c82e2adf309b3216e3bd5f187f47e55f63b0ad3c6fe63f260a41e5536ab50d85104df02791d471989cd548c739c75f004f817c0569f42161b918b1f95e27d3e4e20a1b7c0710e8b5c3e688ae89c4a386cae4d671bc389e0b5a5c78ecacca467c484df50819b85d165fd768f6f7a6779addc101a8ef1c7dccf3f48353dc052ef649fec4f34f329963789030c70bd5a4e48e0046d18a06884e8ec81a57764252f9ed05d16531e7644317f928484eb9f20be789ed141560853632c0842003e87b2e3d4a6485483f855e42fa98a247ba28b8f0443e9a19913d2fe8c40bd50b5f713c40b5c57b1ba3c65d047bc7fc7f411092b01a818b1e0178dfb7bcf59e140efccdff2f3b703279ede86264fe7f7cb470422ff1fd0faa18feeea7908ba4a85fe486809e0f8c162ac3a6669d8553c87d116f75b3fb4c473e9605c028150f4eb9f011cdec8af64e1f1040170ea49feb02bbc28b46c36d7cdb0acc0b6cdb078f84ea16eefff1d762bf9c5d7da0a38b6bad35e278949d98d15720ea8f4524b3f129983ce9c18f56db712ea6a139c941d2e549794483d1f6cf1aa10854993a3bfe0ec22cc818ab9c409f90c38ac25fe6a711bd2cf8fda6aefd8c54cd635263c83e9c328e93ee8cd019b0885b4024ada5739b5aa59ae965e8e41603e2c356e882383baf09502a7b1bef0cdf16cc45fba4bdeae88c352e57ce0a1e74ee7c8f11907454004509b4c0a5481b5d9e350f910b0d662f88ceb6c185f90e709a97e3251a7a4deeebc574a395af44c9988369b603eb77f6426f68ee38394cbf8e1b3c6e4ad39041a38d526c13a9f2ecafbf3ab4b4e08507621f2c250d50422971108132460683c11468366249c08a8f89f06cd14573f8288fe7eaad2cc85676eb7f9aba369b9035e75e08ce5d7b7578ce1ee656de2d382271cd8acfbe29f26da66f6e4e431a1e67c377b0f877621b702751908ba995adbdb1dcf5d3ebf97dd847e8e735950c941d51bc1628c0fee43e3c9252b7cb33c0c0f7191101dac25f7d79f2ecb953f95c20393422a65fd639acb4e814236f8fd7f5ea8ec7a0b40bff24e2966620a567dd3822e7bdb97d738080375cbe18bb325c22334200faa05dc7972adb3bb3b4e07f2cb4decdab425b23836ff538dcd7a3b5107a3da255c73f1e9dfaca54baa8ebaac698203bc2d518887d01bb6ab7ec6a4c87668896fa51396cfdfa69997da911c6cc76b5f0475f32193698b63df32210874f9f2ac53165d31a80d1c2f243ddd83b07f5a6d381072f3c75daef97d7a01cd02fce2b16422b968eee27dd48566fcb723fbefacbbaf6995b046f6d62aa8f168ef09b947074cc09310fa6c081fc856fd13c79b9c23853f7dda9003884b51686285aad6cbd1070b56baaf475877e484ec1c5cdec9f4547d22a2d3559e7405ef50835adbbe45e3e20a589b9044024dfefaba4629a4a27fab1aa57b5d771928feb95494289cda65fd445729e73963572daf59fe1cbaa26931cc5f129ba0cf5d0ee375e15df6030fb59b52e4acc70fb2b98097ff287a98c3b9be3c1380b9faf4988563bcdc4854339355b856717b5bc30b04d0b3cd72e9f19b76c918bd1110923405aa91341242941f2a13e9a5a4fc1a9aa2b0d68c222a956f7c827c9144c4b869eac708f5d25283c17ee238d9f2552bf0f0a4b1d516bf019e45f4b9bdc37bd992bb258e8207089522da9fae8ed1fb69965518f048bf2bd8b0499c7a932baa7856ed529c792ed94aabb3c8c524d253761376a9fc5789f57d3e2df629a1dbd5071f07daad3bc85906d490ea174c51f1b3c0abc4307205b081b0397e317a62801863713a4b5a51ec7c2608701f660f5ab5468af45728c0c1688f12b13c7d4e302eccdfddf1455a17fd2870f737f23902fd1d8ab6cafc39bca443301ba53afd79bca3aa52399d5f701c4460ec0b718d6efdbe3187f66cdf16c775183a0623fea1448047138ec2c0695c125ecb04846b032980f5e473eb3f44448d3178c9d8d05b490b5cbe5b462b882f1ad110bd7b5064233e7e58ce07c8e99e0422747aba011c4c7d41af39980f4127b65e6990f6ec165d2f01615f430a6b567261a9a3680f48c18eac62b458da18b88d5d1229578c7e49eb457ac21d824de0405584ae3369854a97e37525d91a9363f863b6d14db0207beba11170f7aef5ddf6949225cc9e364a218d4338c95f35bc9ff8d4332a059155a8aad1f6721f17a21d955e94941bf032c410795789f67424dc82d6c97ceb3589cad918cd1d2117cf2dee46651a7137b6f7f383fc9baed32f46a85b0ec6dfa16aaaa2726f49737ad79ff5e621d02c712925dcdfa28eb6d75e66cfbd7d9861598ac09d6b579774b53dfc42ba5555ac211bd602f8e3d3ccf514a56f9a8051fec26cb530500077b36b74bb5b3f7a0b7eec01a12c6c3afb4fb0f48b1e6ec194367072e0f1ed047a9de21e65b5e20a6223266cefafa61265bdaf60a042a6bbcf76e85ffc588ede10b2cd8d83d95e710a2764c04a0342f4c3a5250b5d72ded9745e9e663fdab1f7ca9082648e3be168640a2bbe28ddaf6c6584c6374b3563a5234c0738440ef2ec0895b515ed64373af039ff99048567e3fbeb1796d1e0f1994ccdb748a15bce157b50ba2449d8ac734f3534e37590e8f5d7267d8c8a2c054051622319d057a01e63502611fc0fdd9bc18a704ff496cf1c87e58c8aec7cfb14e197ef1e3979156180f26d61ac80273f07b83620f9fd66b29e96a934714a5f917ded5fe875369a8924d61495a3c035b7823d0af90e3b6c728bc8780ba11aaf3076b119eafc16abffff79a010aa4675afb187434a99e73cb6cfe96d630464669c7ca181a846afd630513b9475089963822635939835775409eb77fded03ce221ff03d4ba2dd5885b4caba5635471f8bd940b6505459624ed65f02ae0c37ae4e5561c2bc5d28a75707dc6489d3fe7f5b1f91882e218e3e89c6ab07f233088b67b741f07859d122a6a406d893c3970f5dbfb93971624c7291355ff66f140efbeab02134f40b4f411113ca7508a6996600440838cea90c94f1bcde5c7901a36663fa801eb3f3fb8902c1a7c85cdf8e09ef470f049a68572c27c7a6b8a49e8e515fd0df0c2ca67e7118581f4114e0ed6177334e2b5922a48eafe05989764cf8532afdae8be285308fbe21a18da55d10133493462baf6a8d530691675d629f99d271bb6f2c6a32da3b60c8defc92a6ce85b3d17773f1926a1fa8454082fe08eb55b0a4a14c3811a40aacb8181c6140514f519852a2d44cd32b118685b8bd0b55536433e589b0b44683fe69fd7ba5cc50b6ba328826abfc512beedf976d0429706209039c63789d3a2dd278ef88b4bd35723a4a587dfd235c987cbb05fe87e5c4f81b7e3657f43ef77ec54f6ec2327587f87e3a74174c545dd767a3520b9cc2bb9c181e53d5b80be6ed43681bc68387b0cfd2ad4b912df1fd8f2c12fc12d742ccb48b6e756b48a6b0ffd95d56f44c86585ef3fd4f69f69e842626a47482bbd890d7f1e90b970a71ce2cf399a0d9e1d3d72c4eb500004abcba1303b24bf9af16707cc80896d7920fa70b3e9799f5d802c5ce4f6f0e02aedee7fc3fd6f2d6456f14298a6797ab53d2c400f6f192c6b395acab7285b9df87779638377cf9b70344c454c5609e90e45dada4c9124bcaf90ee44a5493a3f559095d6d873ed10a6d5a3984e59e1c16b822ec7d3183f5811cd1052162034cf2b826a5f2ab77094ee801cc40877a80ab33a4e8e0bcb14067937ea7d7276f25361931f86e10d012f307cdf50e07707ccd0b662b75b744bd0cf17a14c21131bd6df0cdcd3653b48ea541215c4',
+ 'f573042576d47a37216e1a4e3b45682998abff4eb1063bf3f7d3351e67ebf40c5f05e1d0b07d7c3cf4544e0422771e215f446874174a0bac4d5042692f99d5a1ee679144ccbef51b776a2ef695444606a0b09888f46a87a326836f9498a6dc084aa0fac9f31f4d9d51baa26cbd3246a002f875d16fef15a22b72a5e6c99970081cb806a94d29ec8a2a4c93adc1cb87b72e23e999b1601f6f0427caa8ebfaf8680cb89c2a01633baaac26e702ca977113cb39db26e2450cd358aac7232552def1c1a7a3963856a0c57d5288b3001d6d7b824332c228274496cf01859ca028896be48d0533198884a245d85c088ae5e1b9fb47d8b3ae8c2f801236eb5e8304619e1c73a94acd83500aadbe4d4891773693dd50b4419aff3559c951e0b0f76ece51126077227bcf6ac55c0e42bd3c5cd5d2d163aeb61505bee89c584ed924ef384e6e5c57054cf23c9bfcd022adb41b243e7e8aae58462832f631551c22310e075bd76f313968762c54320ad761203c8d9e8dcc9c7156eff94b334f32d34754a341f5a2ed07f6e3a4b7a8c64821a94777bba53260476ce27baf484f78348d4875c771fc73b71ebf0b8d060b5d3577c54a5e6fc2e322b18a20ea185cd68c2c72e3b7f385ab910a94c99ef3e2fee4b13e6d4d92860b4c4d6e51c34c7e34254b5a56d822467edddfe946b21851137780384a51b786c10c671774048cbd7a45138937f1a746d2e2c847e9911d6384360b3d483186c9eea9270bf3737f229035ac86eccb298e91e9eb351c02a91d39697bd4cfb7a657786cb6d434d9ceb45e3d3dd9df2e5124a13a703b47cf64891b58f78647a9c038499d3cfaa1fd217ccca4ee0b76e1c712ec1d80e1d0bef8ae104ba8d918d07b754784e003a0a91e80c3b4e9a31bae326058da43b020980a940189b557abf480145c68cb799ba370ca29b35329355b3b14cfb8e02f9f244544d75b478866dd76206f9325e3f9b4bd62e8ea57252cb1893838007fe7b52c4ec5780986f3b252069e674b15ca22a4ae4ee6a11a206778c5d37afb4a5ecb76d01fcedb9920f81cd8982ed9c3b57e3bea980d20c7a2507896ee7fee671e47cc715bc018a9979e039315ab85582c75411a4843ae84314e78a5902e24feaf93f4d980350adde10ecfe01ec696fcb76f7de56472947957c94299c16fe4389715b6b19617f75e85ae4866ba6756d6dfd4b8f4f6811bd09b299c1b892a753c6037dca1a64d28530be836cef1760b0f2b0cbaee055888ae85d74fd3f147203391602c50b6bfe5e5fc2360bfcbdbfece247f4b7c9adf263d9e39236800af2d45b3f77067d155763cdec68fe2517e773c50953346bcfe3ca56db8df83bf48e4994d666e8dfcf7227f3c3b8bdf8a48bd8139b739f3739d110e7bbd4dcb34fb8c58e714ef6b418d32d79be91c5d7f1f1ac4674b272bc7a4ee9f4eae33e969b16fa90a69baa9a7ffee6b85380a0436edd42d61bbc398c1fb1b8070f45a846650d3b53ccd99ee36359e6481901c7db99834e6af6dd6e0688ae0da69f88a4531c101a408a852e2f7178ae918591b7010098214d9cb27abc53a85d5bf218ad3d4ed419df362ae2ef18efa23d0fbd084412906e2673b80a2def15198fcd624d857bbdbc88763757a2d80353934de006256df0876e227a76cbd988d4ca7811ef9c012164e4b146f6db1d78454c96b76b25612cff8d2f665b26a188218498941e019dc3b57c7db63b087792f9c1908f91b4aaca491bd10821ceb577525695655356a00920a84211bebb7507e38ab1e50453994cf682dddc6514a3fb19a8f50229fb9666390094f5dc54742b851071644d92bb298a7a9c5f9fa8fb77da044df6fc710f6f611decbf2e64139270eb6569d7f29ee4673ba30e2bec0a6205f0e6b0655769892b48cffa2f7ac1c11983f4823de393023f7e6864a46e7f6e3fe785cadf0f43481a19a5134a091d3bf162a539d9f66607558b82ff93a0b8e0ea6071a2d4090b20901902b7288f74579b100ded569b56685c1b593b7413556e97e450d4eee54fa73fcf7f42258e65c8791475391e5302a9b58671a4c036c36bbafb99dd7a248f407b956140db767ff30dd8a199abbde95ff45552d7f29b816ce60eb33920373890c6135f3fb4e8016bdbe4e98b892c78df8b10732195b21de68ba643545badd9fcc1b1cf9b4c53b8a765b1d38b212f4fbcfc40693e340b076d2e5ab0db96f8e1f8d10948effc3a041a3dd7e6152e46cc8cda5d9b6a2816cc654084821b6c98ff29867241aa5f0b6248c6bfa7b5eb037da377d080521c55eb7354bc4db0470e7fe354955056ff5851b792e18ee9f1d5e1b58731ce627b58c2fbfd7a6a26a0d9e2dabdeaeb7227a150fb14d6022ddd4e87277b09cc37bf9001738d476ddb148bd66444db79889266fe67df15a80e07dddc6db5b1003e638c258e96abdf6a1c84a9046bff8116ba29a8a25428f6e6fbf4601cd9d000e301ad8d81abf015402365095fa5bfa888f959ee1f167865554422a0a959ef54c21494c8113fecd5f9c39d45ca84a4466bea884d52889c79e55af91c97b857725806a263a1b4da67f377ae0cd98b35e14c083534d14cfdbaf3bae8326c77b0c0117286cea7bd4161c9aa07bd011bfd1f85dcd1330fb6249f2cde60909c0a73e48ac28287b7cbda49e8e54ae4d5d96247c5d2fcc68266999cdd5002a5aaf329462081561d4642dd96ddb3e802cc25fdf07875087dcdd0d54aa19a3ef01dc4396b7f39520dd7b4e3bf14905f95589438b00a4d94c74687601f063785ee68f03cdaf35506c7e0b4854939f5221b1f969afd17c7121180ecb2807792782f21099c0b395f04d9f5a43acb9a7cc01265cf9d3e1d7c110b0183551572357600ba62ef829dc1acb28b166c9cd271ca09df81d7f987d702024cedb05482bb0021a9beafb7bd278ef0158d93535e8e906ff17fb3c5e3627595c78e6cb4258dc6f0835f4530f3fb2c01397c5785bb2dc3c32ffbb919bc98ec4415e7d2cad7180afbbbd75874feb704644b65218a54d9d4920f88607b7ff4c68b9c8c9aff13f47cb1d7a9420a29e598a7dfca79f7d80d335af4c84251aa00a4eadfdf1dc768ad6ca15b67aa56f1f1593a7b3ed954a142609c941dc732d746f7c06461e3ba419d8b48ab74e9e20374d6aa8214b8073d240cc5521b779564224fcaaeb7855e4b6f39166c739941caea9156a8eeaea627fc6863b323cc0fed4d0d6833426b19c27cf6a902c630ecb4cd09ed344f15a7ee6633f63bd94cf8ef01c10786851d736351546f02ef39495d86b0ccd8a89592fcebd00b509e62e6c5fb0b470d1205018a86d2e6e1712aee21c21683fa7da6eddfe7cd019605b6602e833c955b5bca3ac487e29f22de7e51ade9bb910e40b21f03f49b877081bfeb7554e580e5d4c5858ddf13f64ba9bc0a5e780072a8945fad059ccbfb74a4d7ef26da8688311f9f8862e6d78ac0455c8ebff795b74deaf82e614acedc16e196e1df7ea019c19eb0a9d049bddb2543e8355ed2ebcd62a72409838914a7dbca8579fbcbb2b41bc4916052723755ff17e7b497b463238b7b5bc4f8c25dfd37c22c1e084c4ef5a433ba4255fac4999253c38306f6ac582cba17f74d90db3acf5af324816ac54cff6eb3d6774de5f4936a0407392417e9caf5e106de7f4ceee7075343f3c1c63881d7e68322d63c1586dd31d78ac74646fab13f7a47e803983359f4e2dbcbc236d001faeef53e077ac12fd57a985aa7fe106e8bd7f6659fb518c53097a5f339c67e7f49604ae00872f6d45746de48cd8db0a06e96866281c42bdb9eb8eed2ad65c1925a8da0668fbc7c5d4fe2b93c17646fd9503c64895a53d0ffb7ed12e48da36dfdb6cd923c3da5e64d27d4d58f2620828bc9401d2479d29cc3e4aacea36afdb813e6b69cdb72ddb9066773afcc7a20bd3a42381cde70cf03aadd685e89b5d3547752cf3cbecfb2692b8765a47e74035467538ad001cfb79a16daac5f120ed2a78a937dde60c4c27b2',
+ '1ba0728993b8f0038cd7afac17dfde8c712842e327187b5f7df4899111f58ea2bc9fd542b94e14e20912ee2315bcbc8e96398d85b21f6b796786e15e2d19ba5cd76d0ac8bb91735212e6a180a230175b9aee2e68bde75ad9c206b005fb67a51636b21901e1a894bc71fbcfab0463765b44ee2c728f98d0e6561bfe6ec308d9369f1708d772bbff86ecfa808b837c61f65ee28f8d72595308eb6ed481cd24de26db43e131b8f43d1a4c8bbc0a69a46094ae7160a0ac526da748a6392660e23a4cb2e0acf6f979d5de58558c0ffc02fc9dfe44a69286aca52b366c3645c66a7712eb936f107e724f3cda01f78386bfbc791402694d488f83fd7d68d2c645e51cdf500634668f08349b9836064c4d022990b854b1b83a229083e5411591267bb21aa7e102e073620a625c9dc0539af4c94ab4e287bba48e532636a0078eb153c02db9cbe26674aa5cfbecde2d80b4bc2710cf53f23ecdfa554e372cb1a62d96ce7f4e6ddae0d805afcd10a055bce584c848d050fb29fe8f1c64b18e1abfe46b65782e6ff536e89d8d40928b41ed7371365c8080a9647f7532ce6c6d4ac21cfb0c8020783851ec9a7dbc3948f8fca7adf8b2a78c04d898d31ff630724a981e6d4bffed30ad172c2408a9cc7f82d2c96096b40d1b146b91d16942c545212732eaaa5d5d415b71ef61b46f14d7e85521b198c879d6e206b1999a1cf28dd7599f9ab20238504a477f7c0c76d9f9316ff6c98758b4647592415039eb80f27515afa4ea5746088382229b9527e28d650792d0ebcb876fd5f58921cf17381e0242a831dac24f0e0d0821e7d9e8f93a8d053bed4a89b79c0b19d132e308dfb686cad3680bac1a0df0c3c4875f22e4a8af503a482efa0f951ee461abfd2540650a947872047e70ab359a52a3af3e6c80d6d2d70a1e865602164080b5a6d822d19488df764d9e1990e374156552a2596a0772595ba395538afc14787864894ca22f8e4f9d6f76dd65023b53f3cfb8730d4c180a62448fcfd6d7488f1bc29e8781e08e120ff16c84d29bd65575aee78212acac304889dc928eafc30ec645809b16b1706ef35bead57eb42d631478964d2030171434fae464598c6f3da377975008a91cac71365799125828c4f7b42f9de0de3ffe4f200f0ddf773363d7df4b6effd07b13b3091b98358cafc248ea193afac00d35c182c654c977f7c98d0505bca52f73edde5c9e9a905aaaa32ca2bcec15de6903b1a86f03bf95a3b8eb722b039fcded801db84767cbc901b0cd65807bd93e3cae471b74e068d8207619ad270b98b3d21696a380c68de19c3153bacdaef0ea6c7fad623bb46d48544b403e5f9c36e708f571a7b1817eab636ee62a0b4effeae3be08052e4675ed288188ae3d7e21737965d74c405e472e3faeac3ca223b14487b3838db36029d0845f18842778025e0bb0f01996269073163bd078faf5ab0242a6438134c831f8512c1948875481b1a81de3961ba00080d25b78667b8c982cfbde73691847346d7531b283c4d8457337f3d94b3796405f5957cd8f8eb01ec89aa439cec7a5db38dab60cbc46cdf7aca07bf9c153cef8189577f67a9928e5e00640bd36a9aacd6066024e7b75fa3e65cbfd6c7fd512cfb16c021baf06bd967b29fa282c261eb2ed035fa148b929c3a5a0c0590d27286d34c84266b73b37d9a2ea19a235a6c834e898132dac7a202e74070ee9f4afd87ba8907dfd19a25e555c31e752db9171f9580d67192e20a826dae82d43bbd7eca116acfc085da1f9b3b1e6aa61a54fe1a9454aa38e57138953e02b6691ea062a86850307b00ca2d29aa9f2abf71b48e0edb7360fb3adbb8b5e8db801f80aa35cd3fd7fe51cad0cafb34d3b323ac6e4366db7bbf3b1ba8176efea7746a2a218482f8707e076c84759a6a4aa87552d8caad04cc096af06cb11adfd507db73accb74329843ee3e5bb981ab627f16ed9a606be7f2e41d617022541d02ced5e5a2770b7eddc488d8dd033b88c9bb9ae509c10e157eb3c4e778f7a526bf1dfc08b913d32dbf8469c47eef4272dec283c1cd491dedce0cf3eaed8d889d4899b055472e45cfac080389c7da10fa1048441c4b8506016f542ce4247078e15da69543e322ede521c3d8713e5031af1c915e4d1ae170d0e13dc28bd43ce0836ce91d5d039e360ebf5c7b4348ee2ee2425254eca20ac8dcb20e671a733f7a03d4c5ab292f0cb89ce3164e587e0063342fffaa63775d8895505a8d4868148fd343b0e100cbdad0202ba5d4395443692543edd15a11563a9b5ab6a77e4948ac82285f31ac70953728f76faf5e5203cb261cc16c38f681594f2f9748248d484a8101151f54a3d83827e4f8223ffddde3d7190edd789d6b6f314d60d1e3ba9004605c772cc1dbf06dad62c3ff76ce4e5d88d72aa7917c70d6d242c7f73447bc449098f9dc5c9f9a4219ff47797b39c182dbb5a94d3c2a3e3ab28e210a8ebb29a5a24511908d5512400e191732b5c6c41e40530f40a11d200542be009e5b8b648215ff52fbec931501ea5ea7bdcf0be26a573dc12498dbc2c11320eb25434f09645c163270f2e00ee24f2e7d6d1f6f991170c4736b6bcc92f8a4754521ae6c64e6f538449228a26c7b9d170ce34f68448e2d857a8792862d066719a326256a136461c3edc20680ba386496da92a78c913afdff5afbb59edd84eb9c89b520dea1873247a2a304d174a3262c6b0dd052fbc262a0a95a4d6d410e925c6d3129207ba9d7321797bc3b2836624642b75241e09eec07ca05b277d3de0c07c22cb6d62e46d12191229514417ca982d62195efec4b0a9351909d4522ada8fd2d2a52761608a084c3d618035cf05c50a9cf23a38b313ee1b48d565b99e809cb12ac7476ef59e5da0424bd6ed71c0b4e3232840329fc0961874e8631c607aeeff715a55f06a4e19ff6810ddd4c5a26ba13de2f8554f5ea913e66956e2396f8da6f085da4f88444550f8d81d529554f42a3348eb098d9630f3e781c473f107828cef715df21728e97b3aca1dd0a77d57684d84a0b71f1031a2bd58373750ebd49bb5456643dfa10b67760f65b4c4c69a42d2bb9fc5ce73b707b01536961ec38bb98c2a7da9a758b5fef4ce7d68b4d7c79f0c18719d6267c70a0ae14af82236b0d0ba482b8c39f5ee27fb30ee523f57e56941c09da5a3966b9311e3f9c525ed49c788c6f7545b1fbfcad55d3edc2deffd87badeab4759a5aa8a0f44ce65ef9061863ecd6a6ad67e2ed0c87cf86d454616e1bf9824ea4b3f8dd74a2b4e4048c6fdf524840a3471bb461f1db6a16c5005385f97168ff5420a1a630bc103bef83727fc8214621bf67e0ca01c32c34317d36be98c8174d206ae5b14992d7448c68ce6839fa4f7882b6e8a3b0c19e5595f59b867e4015be85fd458d33e9829447b6d4372c45d53e1da34c7d937929016f33e26c1e8bdcd9f15241889402aab1cdf8144b13967fd8fc978cb60e3c64be29a2bbedfe0de515fef45a31ce80a8136ba71f42846e97dfbc1e19fba59dc9e251bc887a9148b7090b6e001767e6133fcd4e458c0330b5c8c89174acde1fd11473fbc8908cbedb19a329c3b7d4a564ad4f5837a5f47f721a9ebc3d483c2512db0ceb0cce24f2c9ce62b22571b93124d28e4f661eecbd254bcad8d46c246ff79c9904b5921e66fb9da69196037a3bdc45f6ae4155607c77d2c981ca774b3307ef84c3637abd9de91dfb32a5bb8c07e37e14ff69f7d43fb25617419ff80520bc2a6cc186e6a70a3ab5ed3fd5288337d2f165bd3064dc799d3c3092b56ce6bb9cf3cd69a0744f2e38fbd8f394c8e44e33466552352addac710ba1efbb54646612d2033640923a9c9c666b08652b23ad8cc3907908b84c6ad63ae7d3023d02a2c62bcab1d7ceedb7ad7bffe8e6bdc5f8381f737c0babcd1755407ec73a9546142f877056d688dd1214a3240f69ff4f90dda08ae5be73c44446b2d346c4eba31ca77b80bfed6e7d3e4a8254069f6e2519d5fd4340a18ec674531881dc21aeae3ec65e98375cde628eb7802c48d798eaf5c99c4917557b4819d2d806e1e15ccbed24c71367d56711a5f4230950a2fe15a720f3e8c43ab7cdc77e87ef12748490ca1b3b8ecbc7870ef688f177b8921c19c0649abee1c1d2cbef9e8',
+ '9e014421c88bcf4c2611981d723747ecaec70e75b8f6dc3a1ee9d4233377fd6863313bb0c39f7e7beccd39bd0a0616cb30e0e87bef79e08726ff05331b76de30cbb571bf7fb2721aa000b40fcc96e3ee29d275a5fabede2a70910319c279f29f8097d096244b12f1eab3f15be16e171b42a69f3b014d3ce9a3a6b81d4f08a17c27df3f7dd9f3c8ca70edd7e971171b1b23634c842eaf648d67470b87ebab53916b93a5bbc631fc6bddb65000a31756b6731166c9a76bbcde667be49ebdeb70a4f1bdec99148d149dd71644e99ff82fb3dbb9d4529409aea3454a2babee4f607e6464fb5feb8f7928061699ed8e4110dc02617e671e11a1a6afbcc8f6a5608be76a91ba5572c093d414bd3852987b60f791144c506d0cfdad4adaa4c70ba45aa6ab4b11c2d2a7ca69a6140960e42d86cd2fd72654a8822981beaba56648a53385d19ae8f032cd87ae6721f4619bdfec2685b4f4bdf7f98feab437b41a83eb7f4a81862725bbb8306cfceee29be41af9ca3ab29ed183fb96afd487ba2de4fca7be6518b6aa95f22f6b1083b957e8641580f7d90cd9740fa69ac5a29de5946211a925577dc7e703ea7349e663ddce48a8116432271cf2be27713090478bbba527d3694c65c3d971cb4c4f21c675bf4b1a4628c4d5e4248e81fdbc5a9e66804b5003d8fd54e895c638bdcb3ccc67e3f7d4e5ec98ff7732091d0612078cc00cdef3031a74337c40899b90653d5cfa61d33cb657b48e1e45b76154d99ff8df87e67a99d8b9ebc805435b8bc42e5d786267bb8312226821851fb6aeba2bc90c18e94c8ebad262140a7be0779116b9270d3cba12c4ea77c819c985dd5db2e98fe771d1a967c982a877b9921b73fe57508bd99f2c1612a15b293d34aa693c3fb01d0571b488a632cb7586d94113ed1f3a03c6d399b0a4d021df042dda87d2b3b7e22e9475c46f59c62c681daf18ebf92f9e8cbf2139a7eb6361a5d6385e7a2deb5d72a2b6c42ee1e1c3e9c62e2b01cd7584b2ca8c5a24da69d7a8998b973e9596b75c033d2feab16462913c86f4358355dcc05ba1ba22857b6a96dd9ad926d3fe17711fd2aeacb19733e7e77f1a910317ce3fdc2902a9d4e141f5092ecd39d06ba63b5f3fe97bc9c25b7330e1461d350384b6b4edd9c2afe28650870f1a7f72278c41294b875fe12c05bd7d731fe0026123ad4bb2e873fec85e2cbe056900a279f00be6bd3160e11e574e37fe4ee25078d6ac0943d6c6a9d9f31316d862c549a8fa22ff5e8e87b1e079adc74a7a2b0991c33c56674cc3985e6d8cf74facd54dfdd40ddaf647db3aba9462931f4cc413f412e49ae5f271b39ef420c934f03898c0a354e14d036462f4d0f605456cb458e9ec2d3d866fd8e3e3ae55f44dd5f7103992807b2a6b2b12d554f084838103beb8799ea7126c000e73c6b6e06304199fafc6180f56263733a649d877a333b92fa8e0bf405190f21d9762c8f63d24a62c6c6aa4079add91abb0d637486b2cdf92024a18710a84b5f997ab159ca5dea79eb6a4accabc383f643824935ea4117dce0fde32a007ef5188bbc8243b61da23dfd69cb9c084de4b44895b7721168d99cd14c594370e7acfb44be17d3c3abf22d6147c5251bc78bc35eaa268f988463d765c26c69ce8cd36648e20a905f836ac6bb76900e52aad9b1ea221f04ec3e470b12e2a51395d8f8e80cdab2f0063e6ca86098d4ae49826efe6fa01968890103b66d1f8bc743468dfef50aa97944278830f5010b8b6cc8bc0773a6ae20fb572a47d8eba8fb73702329b5f466a285f93e0eed51ad2c38e8317b8aa835d4cfab1b6dfad9553692c028e6b42a62e2680e0e70ce0d12174a8b6fb9191758ea52975767276b38ff1dc13fc4740f46ef56e06a24fb8602c2fe0cd74ea3967fe830868ee1f303ccbb90fdb8317a355affdf1ae19a0a7ed7b5d00708aabe882334f613025cf25588f288c022bc3b1f37ba6d08053e8f3a19767ce4646bde7eeb76ffa7523689b2d64a61d31fc34b3f65958d523580de5a307b313f8af067ed2124d9be0f1d3dbe6d19b8810ff50f086d27bc64030f9c51c53ab962dcd6ff456ca4f0e1896f92da95f6f96463b00b2c96d776c7ee492db3304b6218ea9e093a469df1ac61ac1d89e948b0f7cb82d3feab2b48f867ac26e11a13b7ac34a42a1e177648692861226effb55bb597fbde10f299bf7fffd6fc8ddb2a46a73b97b67387a461b23e1d65dc119366286979add615b926b9272832fc0c058b946fc752dcffceca12233f4c63f7897cbaa08aa7e07cf02b5e7e3e5ece252bf2fe61d163bce84c0e0368454a98e9fdebf6edbd70b290d549ba5577d476af04194f82348d85e9b299f08ddde4ed91675067a1707cbf19eeee675d73387802246af2717f24da7c78fa840457afc4666c26875c4240785093a5efe6aeff64e7136491800fce3d0935ac185c510fdfd162fad07eb15080dd69ecae91899daf964792a76e64ddf0184f3b37f64889727ae229cd993c213b28e84f3dde9197cac84771ae7d2cf8c6424c045d72aca13ecb605fe93c413739bb853a283e657248d5799e113ca69311efa2a41351fbeb973b73f9bdd86f06ef2dc739d83163c1ac4674546a4d903155a8e9a6d404c4dd0e539383b5de2414edc824cac8482aea57a3ce4133f0486813e697693a1b85da269c258a6bedfee59833346992e30960ed75cdf0e8e55d8bdf2122779006bd77528dc07a3a686dfd80f76c92b2a2add9e02519c0039d3e2119f1116e8036ea4924a0eadfcbedefd12a8d43d292078699a24a385b0e8868cc56defaaa755c8aa41f6e0b277adef2e5174d40a5cb720244d64d801e84277bf2083334a809c81d3c954db7edd1bc15bdb9efe988c982cf5feeb7a776eefad616b0a65603981a206748d8679f5b25d6dfa4cda4f806ba3c3f4e011510b98305bef290e539bab322b88c96a0c8917ca4af3a7f19802d78e78786c23f687e50996f109b58b6ec339294e2f0d9d5ff510dc11d6cad9d884f4fbd2b73aceaa7d3ff9bcd1e2707a70c0ea0ee8f99641f238099b01ed7bcb1bdd344ae8dd413c09da6b97aa1d6a865bcd55101bb65df5b648bbd852e3e7a344520b282895b70430e3d6c9bcefe15c2b6b529bf1ca5c43ea4bd911ef338428a65968b39ee898cb9b61219b2edf3dc2899fc9f892bff9f536464b8f0065b922eafd333473604c59f15c34552976e6811d73a270251d9ed14143f997ae02f058b74354f3ce853b76dbddf73fd43a44618eeec549d36ab2a1b449cb7420919093b635b338d8deceea01ea52a058b5bf2985181bcbc3309d2b3661a96c809a019b8fda394bb8f36b7c0a8e2dc2dc6fc0a3a9fa8a401ef6c63870927bc9f3a5aa38e93acc73974c7cf69fce70eb89efad95a39bfbee0b45978f0ae429d1a33bf5326c5e55fa9d72671e67b4036487bdf0c81a04571eb3a477faab5f9bcdb8e34950c14dbd26f1ec96bd0b47ce75fd61ef04b73b84193d9bce3ffb0cb9045a260e5b900fea40bc426f9a328fb96513631410224a6c24d2b20527a4f26cc0af8bad1c80adc68f25fd5c7f8595ad7ef34cc6b60238f58a9a72827a4b199a47e29a8c583a2e385d55a4c332ab609006c2a46cbcff0e0991bc62ae009b8a2ce319db14da669a27f074bf0e7c4df84c46abf170ebc2d38f83610bf180394c0bd97cd7ad69abfa7d92a9d6a4251366c786d4bd390bf38f6fa6b0f3b4c4d0671d743515c0ccb15521881c72edf5a4b1eb0e658f2fe43a4b9143d2a45d9206e44cfb691db3cc21b3fb1df61a51b4a9e19e2587f0ba3d3d0edda1eea656b383ca7fb54378f031a31cf3985f573829c9ffca14616742e0a7e03b0a2d7f05eff0219eebe8adddc3de99f1407eb00a1dad1256241d7c2f931ec993c4b7b9d40df5f290e68344e4497b31dd5f7cad2f58fd222a9ae0b7e91f4ad2cd18b3db2ad739443feb3ac66c8d21ed9f3b80d610a260382ec1d5a1d84cd502d14e496e6e13651f924535badc5579d31f1cb3b413c37e5a4ae021c165e1646287aea3f90a8a208b713a9da89e6a2bee464c3dcea1820093663eef9ff6a8a2f8d780e60465041391c4149a181994de43fa1245ac23a88ee86a2465c4f56734ceaa0b3d18e749e63873195393b59a3adc24b5f3d7fffdcf633edaabb7c8e7c5ece698cebcf82040896792f1a0da46e9c0ad7e70d69f496c0bcaa8ea00d9f0fb58756fc1780052c98a86c69bc8f05e90f77bb5e43169540ce8f7d64a288e4a7e1c3dd83fd467a82a45b9ff7a925fabda8a',
+ 'df753c3c6eb6f4e34dde6ee5388b5b818196c4e7a951439e2d0d7223a2e0a4d304a5d30a4c43da8d4033e4258f3c258d1d81c88e9cbe28695ccf427564726b09c14d3e9c1e33d92b95a349dcc59142e02569139d9752da0d41b0f46198ecb8ebbd77653a0d0c5887f436835001131a4240578f5cc493c15283b8712f67d7f53fa12a302c4158c16703f57bc96c48157b16522313490b45ffcaef9f27f8b989ce146f87b9e19e7b946de719912eba9c525062b7094e42cfdec05e6791741ddc60bf4d522b1567eed6d81446ef6ef11590968f3487fcbfc5b50b0cb1b4028856bd4acc79c8073a82f4ed85e82a5474eafd8fe280d57a0a914d530166d9cb845fac5cf4e4d9dc9f9fb85341e88b0acb8161e462843bf1ab996e41466c48f6985ff69dce7ca5bf1eade8b2502f5ab57e44843d182801892a47df7ce2ad49ac4dbe63b7612776d640144db4bba4df8015992e400a556e6910026aaddd10241529e4bb4fb5ce92498c3cafff892e619119ea269842f1cbbae531d57c407c2058b639c610d35a42418b8ed63bc2b72a10129e35ebc8e560a32f3ee99012db869e7c264c9fc2b1cee6c37089116f268026861d23e2a21a754d78906e925b149e0e021114a78abd7e5fbe4b92289763acfbffcfb824da7752e0478f2c29230fb0d1d063e7ce34649ad50488b72a4255f5b6041cbb5a78e33f8325a3ebfe73b4040e6e858dbcc790696f1b8dc1bc78e6a57f1e6cf62b8248835e993472b952ebe2ee65ddce1a4b2834568dc4778842fd0380d3272f40028d679a2baa8a01c99c91993bd57290c91443b1e293a336a78256deaeb52f40ff9a335f636dfb7edfaf5a367bd440c5473cdea9a0640b6eb05ee7c4347ebed482572fd4cae58dd99c0c85edaa410162d0884d66519e78d76477fe58efeba3a492aca222e77b07ba089b5ae852867d0e69e0d70f7ca9b319cbb6e2c26662a8573781d670429533c82c45204cfa0a7c721c2cc6ccaaed81dded03dd2b214c939579be11e7649dd7ac0ff021442b35c636f05460c762d2adcbd34735bba9122d08614b9cafd4f9bc0eb985a3f56c6f2201ee40ee252cc0fd8a84683399851a3e56f610bfef1a13aa9433edef8a45205a7b1cd8b711901f3bc308501fee3dce8e2741f5c1d0bf5169e9840e4d293eb0344d840505b117572d1d68e541e15a95e42448e3ec794a1dc0b3b4d204fbe088791f2ead1e2d48d09c41ab11e09b63d60af415c0861243de789bcc205e27b20ef19820b94baeacf24fd6d9bb4643e0fbc640b076d8c533fb5948d3dfa1d53dab63616412d45cc9261085fbcbb902596be1b01feb2ab8a1de2b63d1f9162f07c79822029e015e2ad2a5f617288200578e23eb6c09c41315eeb693cf55055a9acb598200471add5460d7d038bce96461cc539a9fb6edff1793c281d3dbb6817e1d6c2968f46600d1366ebb636e3557008d3b64bedceea4303516d4e87370ca392799c0428d2ef027be3261a226b000bb39a1f2d3620f29e73c7b3513057d5d5550aaef94f9bad4e15eeb67fccca0881a384eebe53098b6c511b94c5e7923635aff655d682252d5848060787a494e16a5f20af8fd2ed175511a98c0d0b7ab04ce9e94b3c5ef3e1b9a8b5a3a228302d3e5d09cc12244028c13c0e03a71a85d673b94fd98b448c5cdabe7a155fe6304e66138ceed5c5a7223936b58614a3280b5284969b53b1531133dfbfd7216d19b78a1936625b3586a635a84c9de623e5e151e259fb2653ec816da31363970b610b12e4a2d1030263273ef71fb58c2b53db0a490693637acd3fe09f0135116f8abb88fc3eb57daac127cda9cb678707d66ae42f28de9bb4599f353a15d631081b4b64c6868a1bad352a6a46bc2e67680213680b3d474f8f2641c3eaadc16643773d2eea06b97d6813918d448e649dca4b570e73b067ff69c7aa1547458131c043035fcd2bee1389f10fbf29aeb49a8d36c85fef2ebb1be2924ab1d67990140cf3206fe682726e8161f7686c99a8fe4f48c9d4d414ab35783aa6220eb6689d680c26e7dc96f0eecccaeb2193db9c397ef5edf5d8eed2cc99054ede5698953e8fe2895005d5e434eab05d7325d108dcaac2a74b5429ac51d3a6a74f447c8067d33aca20f8cbb6a169a6c0c5a93699384123884dec61a265a673321d6efec4a9e0696be32be502e4a802a7918478a80cd0bcf365e8135f68801f81a12c7bdc9525f2d10f25be334f4478847f4df3cfaad7f38b2bfdab2deb0ef018730390228a126bac1d6cb3d271bd6ce9e76c447a92d54bc21883b5b85d8199691b8ac8512934385f8677626adc81544a5275e73375473a1ab420bf6940b67ff6033880c2d319fd6de2bc5656e02ec31858e1588492bc42f7774b14cb24ec1831b293460218220f59bac1ec414d143df6534524d8ab82fb0aea6972588ab0f6cea201a49978eaf295672ea09443d02946e9f7b8b9f059d6a77612449bd293900f6a2a18e375e35bc370392fda84f1199c859fa0a331af4a6fb2bc07ffad02788673631f9a8f998f467e97c68e80e58369aea3592ecfd1eac618fd390e7a9c24b656532509367c21a0eac1212ac83c0b20cd896eb72b801c4d212c5452bbbf09317b50c5c9fb1997553d2bbc29bb42f5748ad83dc6e538736763eff646f0f9aa6a5b028e575de074f5fe5de425dab8e6b73f0662f88d49749749ae7b55aaba9cd38deecb3bcf6f3ee5a6de5589b70c637b82aa6aa67451847f5aee60295990350f8c6c18d1d02449b6bf037cdfd09bd033de2ab16b4adf47620036511c7e192770cf0103c4009e49ebaf83c682a805d172aa0dd3a1615121f1e20caab99893c8a9ce43f89cc13ba3f700c5cf3cbdd8dbaa8eda4e036845a89168d7e98b39bebd0d22bb8396a29aec6b5b5ff3487263435df4f68cebb02b4fe31d3eaa0bc1e8692d44ce117c151a87ab0798df507653cd0f65091cd4e9808c49779758b5d1bf518cacf24ea7a9590a58ae36efcf2a18cd3157887a5974c3e246f0786203d9920aadd3de8793cfd4e8d6a780a11f1e30c86664eb21e3d283e66e106872805a0ef90341c948db090cb7a79c2cafeac32a4310e5a7b00e934f0d217fddf6c5c394a11f9f792ee7d56edb6df48148aca963c1b3824481242773db03674f6b1eb6a1977515349644c579d88c392517cf417bc8d0a35821df1f90b4769f334c2593d440866ba21b7c59cc4374d29ea9dd1be0ccfac1492e99dc244e0d60301664fbab30d60b382ae8f2c480ca79445b50a9215294749de3eefdbcec3a52bc56475e84ecd67c7d413530f134ff65bcc8c469bbde3035dd0e48f4b9338fff4e88572cffe92a17c7facb84e0b486507e6e92ee2ed4407d7cfa2241a79870ce5d291ef77a9a2395deb4267f01a3e191ffa95696e8e957c3f424256506af7f386eb5093e9384655ef819c07fd277b3d2b7259a048861f891e938acff64dd1dd5f9c89072c94c61ebca784417e1945f7b3b5fb7b76bf2757fa29433414095ef1f94b06f248a36abb4bc6c0bd7adae56f402b43a1021ff80e5e6c3530b088e358db628dfd464f7a5424471922f951a3cf593cfbbbf390d9d5cfc42769d4b1ab398d47e7d02d7f3d13d09057190366c63c8750e97052f911d4d799e287876cfdbd9864dc12051bfc1ed60b4249a10198e051cfd7692641e934fd532f33d2a1200b831f336bd60aff9dbe1fa15bda0a5fe33f218b4e7752a91dc95ad415bb385d4dd6e2b685a9368eb911333ef6b769646dd9aecdc64e1399b6c41799d44d1afca81a142b058586f19955b1dfc33e07efa4a8df6779b26c002875f048188d2d4546d61c5b9673e26f6715004d6979ca47b3331a1f10ab45289a654ea78b855a7f4f92640ede7a325248d6885091709bb6c002c8bf33418657351a9a80d33c8af4ebd6530b85a25ee06622b1afe32a6382f49e520d897d18d6211e3a9998baa3ea402b511c34f34f62e980e33406af08f4768e1ed2bb66e1ed85f998ba83088eaa7918c6079376eb1ff97986a5a30774546f5a96d57004cae389311aad3b2d347cbc261a9549321b61bc9402cb613b8dea068b21e05160bb0257502a3969317ca731dbb68eff2fc86e5d59bf6ee9513dfd64a826b906819d9014de2f25b51d4d7502c200ba5c76d89002502656e2546ad7b0390f29367056d6eb61913ac6f8912c546061e1090e350cd4029d4af549febe13c743f88933d01c0263b74045ca2f15523f42caadffc52dfaf68d14ca3ae0fbb5d92aeea9d4f1aa816b0bffd99b0f7821e6093ef152723a9cb45f7a082ef8d6bdf72cd33b5aa3c79102f43e2b74199decdd20057d0e227ae4c57945582e2e9653a9b16eeacecdbc5aaedac7e35c35cbd9adede7f83bbf36f8b0453d61416a85a17821885b3757d203fa2560a85c4b4c10',
+ '6d7262476da95db63b322c5193ea05030923c3cbf0f8e8b17bdee2fe227c8dac47bdfa1c1a236f07ba5eaeb79d1d7a7731245848c39e93d5a1b582a97b610da00f7d6e9b06203578182a8f42771dc7a46b2f0da4399d778e4a41452d896769410ddd472ef849b9f29fbf5659eb93f474ff6c6b471a9a9cc2bfaab2f31437a87989030c3cb946025b95458b66bf2707ce3404f9992e400b5a49175261e478d22fca17452d12be189d43e3b7d0bc800a99531f3f033d34cb3f2eb0abe0c0d3f04b19427a68c859049ef1c261ffaa4704bfa4e4c6eb0e21e457b69f47d972f009b4beab3457a6c0cd48e70a115b5123fe276f7c15ba6aad5f8c2b09aeb2c0762554017cfa61739b7b816ce24f4c78bdffb9fc0fb5d9198043c5d31966d5730e4c1229da55eef6911733c972a43ae9bc0f5d92c42caee34398eaf8f4f9a8535f87d680efcc66f84ba74547e3978d6ac936fb7bc304a3909e66e2e0c5ef952712dd884ce3e7324171369f2c5db1adc48c7d7bfd35c41fe738b697d3b2ce02b9ce5711d6de2eb899aac929c0077802bfacebc56142f58ab1ba8ff01be32059cf3cfc5766072a61c89e6acb4d0a76f522d289c2ef111e4bb6cbfad5ff816c013203d4434486629595f6206d198e33ecb7a55e58eecbc7ccbe14fe2ec6b43b62aacce7bdb7845ebdca5af4f76dba365044ce7270a8977974913da3e3b9a314e4fe3f3eae0829a73f2d71ec5191b6078a92f4cdf3639ebaabf6edf1dd20777feff803e0fc809cc48587e41363cdab2e0069c078c7680715d9b7ccf102435704eb5ec1d59165063df59f5a6e1669c1331c90da7ad6fffb0f669a83aa3b696c2c40f9202653ff8e9bf93f7c0750ac1f9f45d1e9db066fa232bb68ba2471dbde7e5691c9da2c985d65f82df2e5fae0eefae2f295a3410223053ee818688ae2d48396ee63f903769a235a326310fd4b55ca5dbc88db6efea71058e4467d70c476c166d7978cbfe26e5e861678913f357d991df7678b75ac55dc7122ec6b09c9edc22f150d994a24019ce6a1d86faae88dee8a6bddb93e5456f0f26cb13b3f3b610e5a716c2b8b847a68e19b2bb225abae520cdf906fb03ed1d441883df4f860f92b4db05d476a4a0147dfcb1b6397c5084c0b1d28b4b5b1ef11c83e399e1e82dea3729d87f7cfbdc0c348fa4e88ea7fc264efeb4e9134f7d82eee584d4298e738fe30ff9342a226dc6385f06c2c052105222012aa0c1656b3b31a9c20e74ed72ec2ee9d2831cbcd80affc751e54d0f3f80f075ae3304117a829b6d45b95289ce8791640efca33fad23016510c0a333cc4b20a8aa1029e81e11729c6a5540e7d8faa0fb08f17c0f5fa6d3b4bdd5516469093926c8e8c15de8305db3ba72de8c14bde41b5b1205b0521efd42d393e6157853b08c650d58f74b97b34fa09279eb1439c1417fdeced746f3c47bdcacbfcb8cb31d2618fe5f28da9029ccd724b1895a06cf09d1a835c880bdefec43506cfb189fd5a05f4c5286f7b217b7a8aa03fb589d63d11b1ed928a1e5d5f0925f7c389e7280679267c1762586139cafa2ef95827636339693275c1cf3fd45640a5be8a0e394087ce12a9b068493e856afd2fc7a29acaddeb5bef77470ec4712e18a9fa26b86ff59490c49fd261f2738116ca4b2104dac7df70e7f2ad51972398586d22562efa8c1ffd5279bf31be5be880b02acc27fcbeeb77447c2a91b434266ef04ac7224b1418613a84564208074743cbaccc8d9689ba7af05be655856c7f611cbaa11cc95526c46409c01b393d4568011b49be12f1f280d2d7082571934c39d8b90ba4dc17f22fb84f2444bac68af53cbd6a41d6be3c92d23ab989e07034fe0b902d43ec2124a91ea0ab46f26abff563dc589d4cb83fb7d8ca200a3acce0b99f883080613c633b7e427ca848f08c6d5ebe3b5ddaa6a4f7ccd1662fe86ff7727be73ce4a43057ecc07ec1f22622986715ab3a06ea52125a9695b2815021222f87f578f61bc5a9c4cc9c9fadf3c68cea70ed7d22e38232e91f5f2d87dbaa09faabfc0d3e2fc201cb8ae4406016a50fff57cb2d382dffacab4d76fec9f1d153dacf42234448f1060ae39ecc93f987caadb28c72d1309127a909244fcffb5fa9d8db10e091cf186188bd116ace033464fc6979737453ff4be93caf45225f1db2fab7ae6fac2a00ba4d0286439a9c7386f464da59a90113c175cc600a4987d0174a4c64f61dc371b76298457f7e2c0dd89e8bf74bf99f933155a37fc6ccd9437fd0807a9e6d013303ce699645031bcb0101c71772ea9648428fa754a034bf3d93933c378e234b0d44995bc1bdafd273aa25fe83f6064efa9d2dc226c107a085fb9b69e5efc70bc823cc580d110db7cd7a131984fed52f71ea36db3f51d0e4e45ef0edcae8e1a82c476e47c466a233a31ad20160b2dea274d0b3d9e57714f222649bb22ea2fa8a0159870f2ce7f8afbb316a9c5f3ba90dcdf7aba6615b5d3407b6a39e5b4499190f00209a8db99691de68e4d4cbc1bed942082629fb2632115afc109b98f747bd1ee53fcf31070442d4674790ea6ba66f9cab2d4afa001cf1e5ddea902ce38bdef5afef965ff7cb2b65d59bb80662e835a287c6f1a810a23c6e023a64602fede45d0735d75eb172b3595be93365ce0c951e45fc064b7f4c5bbe1308017c04f5371e951b7a775e814177a038eb4fd69af6d68cd4712c74a2b49db75a4ec8892c3f0000a868f226803f884d90c70ea21bc09405581a93182db0d3963a338be71964cbf1e4871730f8145409f9afe95b175a1e588feac79374b72759af980f45450f460fd8c02da57128a37d7c8b2729142e3c2c81c7120cff3262a8c1659acc36a63a038bfc7eac71e33d50204311339ca3b829379abaab57874c2a798275a376936f740521506e82adea2beec0efa2362159f8b84c089a0320ad88ded8e8f48d3cad0b4f18ec132bee71b6ece8099d6b10e6410cc344fe8b634d6af94d3ae4010bbc7070ca9ac2f50e9b9824a4a64dc1d928ab3ce9b60278baf476d0cab59d5c66634a701ea2a36675307a9edd0fdac2e2e7df4fa5a6cb518c69576e389f4725b76b4158fb4bdf088bf80361798d6bf694ea854dde5b849e4175b3d87d4109e5659dfe2f4bde9e63b9badc75626628e457fb443f7e1e53e841e0331883e30c23d8bf29fdf5a307fd6540ace27ee23a1494e0c42c6c3760b5f3727e3430cd786778996acbe1e24360f9501cb0b74bc90eb162ae1c90eed490c1f23d376e46743aadf567a0f7e37914dbb3d4e8f3e7fc5b1849aa17d28ad7fe122a172624972822df99cb841fedd29f75317b921c00fc822f5d5f261a5590894fe0b50b3a09bda9764e3c7f414a768b2d91b9b419dcf10b60667650509528b8deddbeed97e25b57dadc629a45408c606d9d3858d2c3027f122b969e5c93c71328e9dbd5256a29b3730be7dc13183da49c1b9d852fff5764ac7568162697932395cbb4cacbeb5045aaa3704e931ab0e121d4934418d71945980c94c397e9b76f8e4df0d471abf895e56ba8f6ae8d0e9e6690c09c759270a73db8c1aa95d05980793537fbfff3472c8d2c34de4abb7e64d216cc952e798314034197d50996a2dcbf4c33485e0b68910baebf0e50ea29bacd3060372bd47b13526ec04bdc81b90dc95a8ac2743b814cc5b9ef8ca9633628bfa4248b55eb7f2d9208e114f4dac69bfe27e4722acbbbe625156c623b6ece36103bbf989205b8e82bd7a5393be8f30cc57aea5e69023de69289df98f052196d29bdda66cb6b4ecf86a3b1c198f566ea881d4390172a30d474dff034af593e5470f21cfee96668670934b0b4f24747bcaed698101d89bb3932dd46405fc966744ea22e67d23c2e3a1d52481a3327bf0b9f6e91e46467079b364c8ac3eaebb8532c94b7a97035c9cf2bc421fc42ddf65ec2add516d30e3b85e7f363c637075d7b709160cda99b61ae9953e46107b1133d815a0dae51c5807cad9c7a502e657c748461d1da67b41d60d0c739526aeb3e30433fe0b2c8d3fe0af00d7669b74c3ec4ceccb1d891ca266c39e8c7d653d2a1c07122f72c1f81dcb6180f0119ba06cbf5b89aa8d00f23a45a3b7d37f5aaaf617471ccf9233e1743088d6ac0691fd94a8fa89260c9c907beb33d5030b757caa9d5ac058fa00ddd5a89b8e65d60ce0ee318087d7ecba6c09cfda9275b25426b9f6a8a9461aa731a4ac0ff4b8007b0ecc01a3f23adde919567c3e6cb604654da6bbb744316495b183a36cb60d064abbd061cb54c930b6fcd478a5c04e623735e3650d0d85785aa1d537185eef682a8c7e0a7d2c0d85929b163dc739995c2884128b2071ccb674972bcfb93bd996690547442fe4625d1a789e4409354172d7cc9686882d71bf6205f9e5c5f95621a49aaea75a1a82279d19df13f24fb116c353f1ffaa8d1ee3b172b211a4f3ef9a5bd116bc823ac765b8f34b3608e572e859deb74f1e0d1ed3c17206cbfcd7f050fd2d31fc4ec1bc97fca97522b393013829b1490d394a1c0030684a8d340222f6072372df064bcc5eb680ff5288e4e6b6a16',
+ '3ac3e86e6d6d65ca203b850ac36fd596e8e01f214bef8e390fbd141c4a9b09cec4c21568fc454fb36c43a6f50e61810b1f77a2b8238a503d29fbb52a50fd85738a4ce0c6a01d7a1c7750f98f91ed9e6bd6ce28879599f5d6c6f26b3992ee969715ca123062dd2c2ec7cb447d53fc76dd964c7936a804c62b6d0afdf116548562eba2734d486dae11e61a506a5c744f8ae6595c6c64b30b65a6ab35fce6199bfb963ecbc65db548ec5ca7e5fcb53f729a4e5d9ad1d28f0cabf93dd9ff0a231d8b9e04e242a69d41e7afd9cadb6543273456c0fb0ef97e1026ef28b2a5885c5639895e806a2d0ee32c6917c9b0746ab58087eb47cbe26961fd0fd488936aaa8d2ee1b36ce6f9ee74e011cba823eb9a66a7684446af93394559e1a92374b8f709912d6b6f5d12273d2e305c30dbd1bd80d18234c06316d40562ee104aee782a138bf6ee5178819f863c4d3229812eba4c255b247c8f7324e93fbd6fc7a9b42bf344c3a3dade4d409732f0b55bbc0b7912cc7d7a43dab0103819d72f604e73e2f1e31cf4d1377ef0b739a24d8e356fe21aee70a0ddecd77f3c17c2b9de85be3755918948002d1d992f79e962899462ddabb1bafc126eef5b3b62087408f59c12667593082d6630224819505c1580ec520e40e5f8aa08018b7e2130c7a847155b6db8c19a218ac27347415ca3faa116298cc179fa6c6114f74d7dc31c842331fab2819c67a442d874771b979f00a0e74a6b5dfc6c21223087f09e48da662ff9d77df9abd77e367c0d1fcc88b8ee25689df33bf8b591f25d23aeab4768141674da16477653760db526867fb7578ed79f0b6e84f43d847aa4b3d0cd4930567eaaecc4958541556f8ca7f55aade2a65f967a225f796cc2620c1f9e2bd599f610a4f3d108610ae3060778b485f1c3ff6455d358f50eaa12519e4f60ee730bba7369d883ca9117e87731810b290b60618fe2ff586d3b5f3eef612b5e3dabee6c4f018423039dcf2c6d0fab426e8423948847e56af088f30ce55d9ce04106abd24e75def8da0e99768eabdca07be3735ff68c6cd6a67ece45db9a882d210ba8b516cccd4c786adf90820cfd6e79b0b78b82b77ee3b6a458b17821d98e06434edc4f0e3b65053c0840f23af7f58f7459e0d3d202db4982fa1765f9754b18340511a2440f8ca8097c4f863eb07ae6b5c02692e4df0486a11a404a2a46ac7e68361ab6753109895ba285e51f1248a5fe542566f6ffa7968822f5cdbd32f8616747c035a98824d1b9e4a9b8e504a6f5d47da5f80f490ba0bf78fc99b92379e8b7a2cbc4e8fe25a8aca985a6986ddbfec1a36def37a57acdcf861d542600753ce2ad030d3b7a5335bb5adf58413730e74c2e46f476fb3a453fb222a70cb1358470d230b8a9e5f8a16e5d8075e849edd9fe86f8afd533942f9766d139741d01e9e778196d3b255a134d1b30a6938b5afb5d134bc75b36d0b36009f4b3652e2193d10687d3f823c1b4e1fcb6934ce5be76f33e07e511ea36fb210351bce8cbdca9b5e64292e8c777acb169dce31a3f6371486df3b0fd380e2c62bbb1fd04dc7b541c7f125313fac32245c8683f06818f15e209d8d129089f71ac9bdb1dc14a46bb8d39bfbe82a2ec3e2682234e16938b8a4b7b7eef9d4332f0850a99c527fb8507907c107a3ca83b2adb00d5b9545d9ff70b2aadb360cf0fd17870b19d3fd8805fadb0ce3049f5f80beca94627c8c81284a87d2dc479967e3d0a36ec4c10c97fec6d3dc187b2706b0ce2e43d4179ba2e5bacdec5ccf37fc75cc5c2127ba2b7c9d5578cb1287e00db52441b84af6f1c39a19fb43f70d3713155307debd1fe88a743f40366bab58f92089ab5e118b2d77c810766615a23560551d3cf3ef72b26615eca0ba7e66004d6546d1a1d244bd13f216e5ef432eb158c773721d59431773f4d630d3e548546f05e43b1007c41f4caa95b03bf9f31960bf0e3d9765119658839476ff1ab2f3f284fa7e451c89c27644257bd8c4affc1cde35f61ead6ead78649fc9b899363d6c54f1e1adaaf64515614e9f421db5c7e1979341ff9caacb47fa60cf7ceb62bf3118532bc61daa25ce946991047f951b536d9e97d6ad668e6bc77ffed87b98e7e521a6a30aff15e2f6200ef79c64ce44e6d2d06e107a1255485e55be37e479560d1364edf8c9b9eb20c6c7498667d1f31069a14b596d4ebe62218aa437906467fd6ff6731bf806ccd2eafafd3ef340a46494a9a60e016c284da377374419eabc4a8a03c8f1249cf680ff428932818ac767d65204aad10b316f66b3fde9eb8cc17913f565f4b9ed06bf8616841dad20da7ec122edbc569c584bedb95e957fcaf61d7053b0a332675be311ef643cd0a35c2dc7a4d7befca01b67ab0fdfd36115e88c31975f9928ac84a02545a03c9672db096c50492f5db6d957211ff8201a0e1769da38c933072d1f5b32b1de79691fc57621adc889d431407ee2724e081503e11adcd063fd48497a88bcac659fb31aaa187a15cc5b0abfbf53519f37f7eca7f433abd6d67486b224cd35addc0c2ef40754f840cb1f5ba2c489010f5c8c0b5acf38e9b487252cd7ac7d402eb84b172c5ba00e874b53619aee64734b0210ebacb09ef9020c8bb53a803d3eb770c9163415ae3f71d37396f89b9a2acaef33181e66ca6c47cd14aa4b3e61b3c09c22969bc3c40d98ea1c765c5a8e8a177a7f2b10406fb5ee4f4c969a35af31e290d432d3f485fb64f59a8a36b9a633a91a317f22ebe3586e09cfe498046a2b96055a556d16687b5e9c9a4d0837959a0865168ee6b7c9e66f92eb3ed539171343503188d7b7e02fee3578394132c13fade18af4ac3287c23b613aefc2425a8b8317d647a447816bac56d0c99259bd9711f5fb2b13eab18e8a0b3b81ff9e98f6cda2c51c4343c0c1118720884c0aef32dd3903ac9e5ebbadb3d7698fedcc56d79bb78a71453b32c2a62ce4000ed4da85581120f3abfd1aa2418c51840d4a18c0659ca2d11aac3bd2e2ee879b3b3604112b24df9add4452fecb606fe8de965323c3e88ac6440550944012a7e451acf068beda9c0ca2d30925ba1a3138f24faa843f11cedb41d52569565fb165f2a823fe9ba8e2b38d1781c9860021feb8c463642faecb5aa4aa0ed49e1c308a9ec613453a16404a0c80712cc7b8dea4c2a322361e262cfeece291687feeb1dca67552df22b9311a91bc3bf1e7aaa3b5804a6b9ca2fe40227b1d3187742d91d6ba34471eddf831bfcd1966ab7e6c3dbb7258b3ea26cdc15fdfc883d4237f6d033a918496d469ce940f2675abe473f931292c7fb141eb1d11ab62fcb1065aafdcb80b7fd9ae647451e871dd85c2386291154443845cfcbfe23e7b00b08535e6eda300bd59b4aeaf53e97a22cb90400655b74e83d60069264c397f345538978e909c2fa1899f7efc2472add9efc71151199fa9d518b4c6ecaa0cfdfc1188f6237003d6e10bb77bc74e248b6764ef32df372ec4abdee28c7f962965ec680ee822066a94e032a50bbd3b6fa15fbd611b0d58f54d7cab32205bab2f5589db32d426be30f823a0d0d52a66c47e276bd53067d97392bbdffc290d338f3b38fd8d409e22176f1fd8d33ebb7ab38052f2a4197b333a430e1fd91d00c9b9858e2186b3e4bc5e68594d24cedcc1cd4676e4664cb410b9ccd7dd2162e2f83ec2fde9a7b4b6f7a67254603e0c0ae6623ee7b38430beec629ead8a9d910029af820cd878b9716e602b95c4975cc25322d839d2966bd810d53703ba863df4f85c314f506248a07b1be2a1ecb9578f928fb0f1e41564bc3872345eef73b04dcef55f1a040cd8c0c84a45ed4b2c72ef1ef947844a79a1b7cdda05239bfe9e5717eb7a1145c0e05eb07ba3ec6259456d63000a85884ba9773b6d37f6428f6ecd8daf00a99836f5d6de10ab23c4d825670924885a1ff3f2572bbc2b5b659e980d8ac081679dd79fc5aba9ca37d511b9787be73f96941b02f3f9477da787b4a08389c08acfa91b34b7a3c76f7d25ae781e35b89ebf672951ca3e8fad7f3b5a2eec151f7b366c8a6b0950da29883906d7d4b12934292b87754665f51956c3078993b74dd1503a9d89472d5286cd81a35f1898b97e8833edc3f50a286fb2e1354716eacc3b91a5dd360da3d0e5d1821c746636da0c4112a4f452959a1f08087bede21a2b087f20b1f7a95ec5e528dccace5a261b3be86a555ceb82ca85ea9b43f481efeac67d5dc424c6b8c20327d446b340e0edabe28d67842c6c1a52cf2c15e172b67bf4109f8c63c24c25ae731b08c9d6e1d1cac41e63f091bc39f42a3d7a4b31185f2fdd78633b487381658f13997878b35827017fc328b7fd89f88041d988597014c8387ae0f1b5d965b6d0507155a2eff12f3f241da30baa8ace65cbcc2f38783d5bb619ae4d96e83320eb4418e7d1d22d61b1cddbe6193dcce44f5dbaa66a8b2968ead6f395682a8a1234710885a2147d6d1dcf76784d41c0d8a15a3d947c13796e2b25897f961adb394069b8d58011619fe79b75b03430f72a0053cd6fc9bb9dea1b97b852cd2396d49390b24df8736a7883c462444a95e046e0dcd29effee174b10a008b579ff4d92b2887d657795088596dcc4ab1cdb1ccdc747e5b86b15762fced1031e08e88fe201b382928a00bc557547053b079aed0d38479f32b7ec2b068aad30030689ae4115945a7bbc410646c385bc9ab73',
+ '9a9667955c84473adee8980b59ea750bd6414a4541f689b2c06a9e5c439a24c45ba259814f80cf2b6d1b65779e8476a33d7b50152935e83f195cccc5305858e2d2cd2d08b564dfc63bcfeeefff07f8f9add82ca318a002da865429de4ec3c1a1a61fcb70b6b9ded4f10c1bbba4fd63d3cf61c73735c03f4daf589feec565c8c87fa01b017906341d36dad422b6ed1efa4b1d0718a81c085f3b73153fbdc6d3210759060527d34869b342016d5d609336c815d5909a3cc3d7d3aa74175d6c4c72e35172c2e7a984800f4b8ae5c0dd294eee4f1ae533a9da7e1e07a2bfcde19984be904981e20e4f2af3fe57cf08ec480c67d5bc609aeab31cc591887f36ad241f3e1f718a3f8d112e076151765c155836d8afa549f887b8796f500e9ec056530a0b05d42527ab3355f27f5e21e5e1c195ece3bbe874094f5cc54d1d669266900fce4a589ac2f21b675d5d6717cbd7ecf1497be88437f7e28e6e8f9b1dd56542f42ff7e73037e9322cef0555a135803f12977e126768d9d8d8131e720cb0d9d082d5136831af18e06b517e0e074b6223bf7ff523d7303899005c03887b4c4ca48169a6c2e351088eee2ad07c91370701c2a8e7021db79a8dcef045c7b2d04525b992b9df8f4640a27b230486871ef73fe1bc5c9781248a1c0c78ec15f8654dbbf2f952a67cb54886dbe42013302bb847e5605dca4a9f8dae809b015c9733ebcef3467cfff4c7a9a0d6142f0dd583d47f953ef083c2522dabf3028b83d561a7c685203cd23da4ea5a335b365bbe51d351bbfe20aa2f63f17b2e559272fb18090c702f9c07a073931bbc633fe533bb3b241d48f227dafa6186f63b1f11507f9a067c1ca387655d2d4a37d151937ce9965f85c270e20b400634165c38481ebfe11d708f5938d4893f516d50754de0046b6cb917a1c40a5c67ee5461e8ffabff66b9162fc703351f51972686b2fba83443b281ed8db31e770d1a7af2341e4168b24334a5f4d0cf49eb84291b10851da0a599a7ed3d9890b97e224eb6268d26224c219398e988eb2d968040b897d384711989f1beb85ca849446c9956c534a254bcbdfbb1ac11d1f3d9f3310550cd28ce27ea290806e5ae50e762227919fbff268b7e34984755c9c43d45c5a9425c50e058241575301deaf5ed6ab3c82bcb83c140cb05f4c13b1bc849dec44d756643cf339f7eab3deba461e0ee12eb028a784d630e376e23aa0e268527f698f2d44ac241c42b52353623bb47550889a62224f17dae92ad748ceb0779862333c08fade108f9e61321e8b02cab075d4a2079d0d61513de5abcc8fa92ae6412c5e77eb45512a7759608e893dc936cd9d87779c324b3a5e31c044683f0ab2a8cf77088a746e737a182f21e14f287ec44d8b3094702dfcc699c53eece48f83e59716297cddb0d0f327fe7727b970fef65f5dd10a29822f03781b1a823c31e0a41dac5926f3d0e99f037f0cc0ff2b4dd05d5fc781d0f03791674265cc989a4f40cdaa260d594723ca34f1414192cfd1eec201828008f0bcfce1dbbf209c11266811b00eb4707bf5f12725c32e16c731ae27f3a08933fc05a272377522b93f1c76942e71a0dbe7f6c0646ce0eafdb9c39af9968001e48ac82e92f63647728c77f8cfa5fd56ee239ca47737591cba103e41a18acf8e8d257b0dbe8851134a81ff6b2e97104b39b76e19da256a17ce52d81eda10ed83a04484ac6f7e73fac3b7e93a3b724902510bd19d07b7b270ccfa47aeedf95885c0607ba720391d725076ec0260d815f096a96daa7a5eb0cc188ef35e67749f096011c66a7b589c2e83776e505938e5aaad0898cebc9ae36e438961c9c1432f9301d10b82db5d9e63df11f6806f055694f6b1f97648ecd8fbc062195da5c598971816916cbaa892d6b5defb2f2ccde753f63df63da9d4ffe931b190a66f7c589a256b2ea4b3c9f7ebca5702b90d12f64c3df5258159d3c6aaf9f06e2098e7ee20bc3709b731425c076ee2a3baaa552202307fedfdc379cbd59b96e858bc98d7af4f12d910cda22dde263a44b06f04f8fa7046d5ef91378ded991db5bb44ff93ba5077034dd369fbc482966c16e5b2c9b97fe273f32d8d7b57750d4cc4cc9ca0c652ec2ecb0cea345f06bf807a78df32b3a1cb2e8502ec22a7b1ee2401ade09d47e8eaa214b4d3660214fe3837c2fac26d98798c31bbf40252c228b1fe737bd7d364eb03b775960f525fdae9fb4e95d76bc6761365a30598ed4855ad17b7beeeed6975ca2fc9a2aec429ee1c2166210022be393abd72c154db96ed4be9e53a40fa59037bea2ba9b2dc15a04536c43ccb26e23b88958597d4a2306834e1867e8c8ed62c8f8315ebca1ea58a0bc7c339a6ecc505738a65f986e5c75eeeab05ec101f43497b67030932e04c13c640fdb811e8f92f92089e76f9fabe29df830e138a2042f2d6128d82bbd59bc61b3ece389aafd1025a9c89908c6577519e25e975493637a116221fe89372f729611ddf2bafa5ff3633b2d1c6dcca958e90e02291dbc593fcb1f2349b782cf0b63e6a8b01df7fc623b3e7e19a697e9b095e6e63e9b0630a3e402f661bc8881ae509a16c7b9871e86361a8437b9115fa8a6d0d17fd7572ed0e37261efa02f8c83e695efdc96930b87db37071fb0215265a3368a93d8999dee50d72e9b6b613ab3ac40fb9abd040c8cb3fdcf7892ae0c12147fda24a6b2c324b498230c2605d1662b0101a635c069bd45d5a3eb68a2d3d5811389d74a8ce99b961f09ebd9039ac3e941cbab06b16a4319254c2039e4f0d6735a5f56cb0d567b7ed3a7df8a43ddf370691f4f39be5746b75693be0d5cab3e72bc2449311f54ebf41cc79143ace21e48dfc63ad5ce77eda8d13e6eedf24b3504a19f5785a9291381622fa80b28f06af70d8815e2cb136173fd15d7df8e1ae2b3719929ce921e1cac0da8cdeb9f500207592c88699c73caa041e2a2ee185e6e0694da6714c47b2efe85faa8e9f74b0c002d7c3a7029df4fb7076b761f78600185722ab85f268870fe33ede0101927b168f03ed4025a9f1841b679fc7c9668c1b97351c4b8a08d0b347d491e65ed2c18c90a3bfd2417fa4ec6b5d4db0c5abdd929001634261ad12728ccd509f25ba46aaae33b5ee0483a19cc6e44bcd7ada96f5e7f42b0b27c9dea63895a4abe4cd5ce94e3069f3dd5a5a0dd489147e67572acde5a9ddf63ae397f6c1aa088d1a6086d0e72636744a6840c80ab8223409c61b733f7ef6a4199ed0ccbe96f6c3453866ea0f81b5efba31e843effe1f9ed08beb9e4c000f8542301ba095c8f9eee3994fad2ddf62d6cb5bd319dad7470f6a3d1d97a1b9832a535bcb0adddc7c507427d392d89bc7c9fc2a73b271b04316253e1407c727ec03bb867173fd3e18988558752386435f29ab325c964b258e339023815722c7b491e924a1647a6a3947859fc6e7bd7293867717f03dbda3a2f84971c81e5b1577557adadd64ebdd68bdf3822b47f485ff60ee3fd1214fc70bd4afad6ac5c69daeeedefd87edb824219c5d9424dcb20a0d395f0e71e977ec3349313aebd5fbccb59e8421237775caee43324a360e8c8b4770682844eedcafb3d67caebdc7612f461518da529a9b3e39430018f165ca6638b68801dcb9d46e07fcf07d7332ad31bac2fb8d77bf9f0aad2c5584c97b12475b7c4d1fabd2f5c39a3d2b2b8e7026709e28aaaf156ef79c946f911230a6ea9b1a2215f634b2b73d05079b3d723873686ac6a4d3ae114b70897d4145c971c9ce0f58711a09d1af26b2fcbf27c0d3fa95ab2d888fc45ef12316d5e493f684267ca57ffcff8dc8443d3e7d057efcdb4264c9bdb437da909f30a72a09914a2687137718f81b530efcdd02190cb778c6e16250f7c660736e8c05d4ca1ea22701722efeb420f1dc0e5f82a8514345d72f4e2adb8c2dae01316e3f0a36926b2f8d5e2b96c1d6279b5ed2ca8ba637ceaa6cdf0ac3bb585506a1bc28b2001330c622d195f9c2a60103690d113698589635184aaea435d50a1607dc7f86a70fb78d7a42fa72470f22c6c544f33398345139a9e772e76c323191fc658fe2abe643e7fc48c5aacf701137fc40fd0d3649641aaa5be427ceee702cf7ddf6408f458a581149940dbc8730e966577b2de306634a821e9ecfee682a2972bc3f3ab19bef6051cbc205aea3265d9c1f0a003ef9c35bd985ff5a4b4de42a0576c73bc357d7a35655ec3d0652460715fe364eaaa208c11948825155fe229128942ace2517f763776e8f2e642334786c7b6c43a69da81cb9ccc43faef75a1144aad65c673ab3533d7c073448846613f82d3899c32b25c14399319fa6d81f0ce20156810a6e9fe5211500e913f44f7c517a07bb70f906413f1456dbee0ed5f6996e27b67ef2118bbabff8d766f1751400e876134075ce2f14f4a08ed50a1360d317c6773583bcc982d34b69a21a6b7d7f0ee04ba22fe1ed5d80ab230c584bde17b4bf3dde820620e205953a65dd971b2433f2df2695e60e816ee322f48803c0efbb8e94da7b622470bd52a412998c6fa92e1a283e364f051905c5291e07cfabb39290f6a9ee6536b761004148fcb00d7623c2c1799f91539cebdd8ff96614011be072fddd4993fa1e972a8c6965a65703db89253910319c8597a8d207115563811bb0f4d51b52c12ed63e000462d3eeeaabd9ee1d56b4225b8f9399d79818457baab78631e2363e6094b726aa82dd27832b316696d1ad97973a4a41db68d1297424131c742cc2c44c69227abc3406b375b02c3925ff725eb13e295493122471f30e40420c597ce54aba0c76ca04f4f53b0126d05b5e970a41f1ad6c9f1266c180cfbb717b06b934040',
+ '92983501a4d7583a5201830266c37c908640b0351461314b526cfb68cad97bd7ed615248fa5756c6213bd9eae98d2f4ecfdf6a452f2e68c9687210b53c74d83575e08a7ace9b49b21056cf377c64f80669c884742e93181c426d871ca2715081733e68ffe94a39e6677aea51e8f0e1a09d258629d7374a2b2884e903c577eba32fa2713f130d2e496eceb4a0f4daf105b31bf9cef4c306de62dfbcd46e2fb283f1352fa3138c31c56d7bb48d6aca301bf3d464ca4bde521d37a78bf66340ac09011e2991b36e4941aba8727e1067a7cba4784f85a53138d0f104dbd16d54e21ea686e772b95c7fa6717e77dcb05a5dfe102e4267c963bfdfd61d36cd53105aa82a95f2afeefddada07254a10104a5a9a7d1fc6d8811def322f1b2352df1e1e90d372d1ae1afa62c6b5c47380f9e0a788347362409307d1b243252bc8d72636bfea460cd905fa1f52c3847b9632c44bb17d519f07c8c86c455c64d49704cfa81cb6382c9776a61a67788ce9b9859d4efc9fe10495e809c9d4c000a9272ec27e8e8171b84f37a65aeb1d054550b814b950e44d1952bb71ee48b8202fe11ca7c0ff9119386b0ea1e7c8fa1618c594d0939792ba66a708a9e5878cecf02b9825745630573452c43fcae457e8e87fe17ae4b8f25274fa9958b67b848d736e68e4a47ba453356c21290a297ca240e667b9b59b4c3dcab43427670ae82b4013558d57553536c221ec07af7db06da562ed360d28e8a3f03ea2be021effede08027c896ce2d2864d9ef80c2ca3d71a15b3d98f4470dab6ffeabc48e9e12fcda1fa63c68cdd250a2fcf03d49f769d5bb391d8872e0057dce5e16e214726980b6579a92d53b6ed704f2b8e64fec7dc27c6456ae90db164295c5adbf9b824ca0fd8fca71e5fe47e412230f22d991c05f6a45b0b1552089224d9b36042bb603843631ff82a1ffa5a055f8bc99f1ce7cd50f42f23aca97a6447d477a58ccf6d555e9a4016d1026d23354d789f49e8bf74bf3c4e6f0f529b4d1ad334164872a0c3b9e5098d93a5c15c497293cdbe9b07bea9c34527ce0bcfdf065c653cf633aee5dde9d8c6e2887b57ba7579ef5d8254ed994f8ff859339c7ca2e687742690ec4e430f3a4d5e190fb810bc777eb76d2b841637ab5b414895b878f817765a08ed5f71dbaa9b66d602ffe4be38f64c89f034a8f203bb16d92014e117919df10a36bbf2c5a64b8d5de9919f012ad09d875751ba2545b23a63e00473ab92659c133cc64e53b9a8420f180fb81bb9b82ad3a58dd247ddbb2c574a29b95a7657abc27410dd0c516c256832ecc86481f764fd8f2b79b028407d41c2d72a7aca0eb086812e276619f19807be9b38820028ec7358cd2914d1ecada1deaf3fa319d53addd870c5e75fc31a5c0fad80eab0b711c3b6d568dc571a3e0612147159c255bb46de8b3106bf6b3cd3ca964a05104c756d0df6a18d24438edcf1a95805600ab24027cfe15a9554700d63d7fc67ab33a7ede2836b9dc6134094f1c06c4c8f6ea05838c16f499e19447760050098ee2709a4c91e3f84b8e3d3cc970c26859cfc05fd7602335a16143a9043800f0f55711b50262995d8894fb8f255ff0f47052d73be8404c612b9ffb2f692eb60417dbc6d4e8e37f71f93b18094b2fb9f07749d4a09d74b9ff9e6165e08b2a9bcb5d353701e65cbebb074a39b3242844e5d57a6297de439397627029c45373d7cf2d2f0b43e4147dc31a8b08939694ba5bf2ad272793f702b1df94eee3a9539198f08feaa3ca54e5129bc42db48ab942d836accb58a4d62dd67d945c467610f2ac0f1e7e2780641e2ff0be501be9e105e6093ef732fa293d8da43a1cf4a0f32195f0a46cd9ee8c4fa2834118d299522a0cd32a53be7b37595fbc4cd6f5114446dbfde95fc1fd14a1f4bb152de08454dad3fc398603104aaad32d933152af4b8b049db4fde693433bacff01384d90313dc1ec333909a2a858715fd7d6126450169a37ceee5099624552b9cee121a72f7a600be013d9179a8ac1bae06d3d179a0f253500db07f8b9e96f5044cf65b098ba38c207bd7a5968684ca7759ddcb0729f2bfe106c1496904d8a2c2bab2193b224cf7772def44e5a1b998c600ef51620ee36fac6487e5d2f992bb54b1c5b38c6e1af93e71f50e0b8cb30d267699333ec23cd91ec184d34ddf6da536ccb1d871b18607f2f2895f6c99f9ae25356bbee1d66792b48838902e48c206e555f6e68fbf268a212a0cb77d6c05e22eb7c772ffde1c030a4323bb18a82846ecf8157c3ac975163572ffb4d275604fcf984ceded2b92d08c6cc6b2818008fbba2d9de80772ea32cc87e2c5f048815d74315c9e4e519451b76fa1f4fd530c7bd960e0e87a4dfe4642b356695b57e181b93d86e277e2792d27e64610e0b38b6dc72c9ecc07bd49e7249fcb1d78161952faf75c790e50b9b93a5b1384d0040e48100b18213443258c0ea79160db259147d5f93dc0761eee8c7b28aadf4fe71d812066fd76946249bb5d5579c1f8e8d1e6c6ddab3753bf802d76e96c6eb4bf21af94daaf3a8cad0eeb9d43c4cf55e263a649ff456c0bc507029a17e8d1a2fec329c9d0bdfba185ed934add12c78694bf1cdbf86cdd2291fba2712e90a6af487a965be3aa28578f7e48e29bd478ef925ca10933b1e91cd8c69388b8044c1e0ea05bb77de44f332c3983010a8a22054dc4d93e4b853e7efc004c3d2eb43093d3ed105919fceeb8de97c802a3c4717c39702bf79a874bbd6e21332b1d10f2851aa92add5274754d29119e73f1e3d598e7e72fc1cb187f4cb1b1eeceddab1086557d21a081bb7184ae5f5ce16f98cd0fdba24b3937b967c1693ae5951af308fc06d18b4f526261e3a0a9a9b78733d625873a04aa7afa83aff714ae1a4f894a7ac13ede363ee9d4eed2b90b82d3456f9e6d06f2b20f5c616cede7becad5ce5376f71d80f191b2390aa6e5d8bfde5e27cf0fa18bcc6f4f7f8ca01c8e235842c2652b061a0e056c1ccb8fb8b7eb02ee6d3be192367615543c83c03b92b0418715e9df810fe80477eea60fba2f70db66ce698541993b8bfb26e6c0bd62fe2bfbac698706e91c19562d2ea962860dd267b9dc6d381a794db4dd3a242a857972111468b4102c26be8756d9ef3a720e8251ce08fbfe30dbd511bdd26cfb609eae77dc6bd9254f745eac0a1c33ba69ff65c56973d40c72cfbc824753fdb88aea5b9072e778ced9918414a57a395ae45cf7331aa167db66c16e97184378ea7af8e3eb56601575411ac951b78842a467a3a11b501639f3835d55b09f0540f9a726e1f9157a31a11c6c98f3ceaaf22f6a601deeb846bcdd3ef01c6f5a3df87e9610c04a3e7a5fead1f37d6b4976ca44631ea84da1c7830110262d43b831f1d1de33293f2ff4e2aaf86bd138b6503d8bf83bea88aafd0f079d7c02be57a5efabd5fa6778688e7c69f6225eeca3ebbe7e80444f50426bc3493d4e373fa6fe245513f31566768b8fc061a350e7809ddf9491d46104f6a8424e86293558349f52762ea9bd99e8909f26b18b61c9daeb1356b348aa4736270e9ffea977887f2ddc877c8aca731d122d056c36fbf42147fc4d5b3da5779f5c8ab60d2b8860e51e24f18412c692b2a4abf4f832aa06d258fce0f00fcd1680dd3919483be24214e4ee5cdbde2c6917bcbe7dfc0ad6729c8287aa285b8bb4891dac4226671fcc6d167b11fa497676daceb6f8de2c1bb7be594f015a8d8bd2268256c92e298b607c277dc955e13f3c6a4f37fe2512e446d651959f0d3227efd7cfcaf6d5efdfaec09c48db8531e13a54d2b416576bcab062e00ddbc6d60a7e1b4a7b83a44666e7c8f97ed0eec806f118edadb9eb733984e2991a300de58dfd6f86dad81fb9cbdbf3a3724218f00ae124f4975157d5ab24e3e13d4448dd3cfe53098b7cbeb678dbdf3ee5979a56878b078c1385331faa20d56d09711e4981f15446efd9e7c8e877fa302b49c977e77f4f2884bdf39db0f93570e15ed4a71766f1d38a88516db520c30bc5e14cb0b50e5a3e6d741ffc5a2e92b16756327247cf9a13ddf052114966c84647f69abd6ae8c74250402e9800316b830e0f8e47adabdb9c7cba80502fda885423e219ea937ef4d8cf9a961d3d922e8a37e36e73b38c34e1c93f52a6d2dc71d9dc4c60b4a93181762dfbece88cd16a1b976a4992f3d1146856174f91dced133bf39df5c826b0fb8f0ddc9986586f9cdb8f1ca621d92b18b4a5aaed8d989cfaeeec4f5f81967da1a7e1c532633add353e91631abcfdfab03b3a82a306a06c878738d8f47d72e832353c4e3e2b9e9ad1b940e60da0bcb05980873706a99ecc47896bd35b1846912bdb9fc0233c1e38e0d0100df599afec93190e209ec2ce3cb969e7c709ee7bf8dcff6ec378111427e117715378e4421ffb5941e7c20eb95e6bad5f1c676c9d9fe4153bd0a3573af850f4075efe3d0dcad0e5c4f516da0a71ccc8145c1a1f25e6cfb189703b5acd2acefa2478fbe08d6dd23309b113a11c476b4eb9fe9872af7e0e48da1ab6a8752fa99e6b4a089b2b896dc582d43f10792fa5a5b28c591394c61e6fa0489bccbd878f553e3a9ab9729e1211b2b6ff94ab9b2a71808dd25e604f7e8a6e726143f0b2cec33fc328c7fd2c5dac3be1ebaa2e2a6816c66b9adfac8aad3da7308d8ad942064cc29c394325a4aef960dd69cd7b5ddd29d6ae28f3e3f838dded0b972db1a5c466890e52b776b7848dd412207b0d95f80f43edf35771289fffa25c1489146e67b4d591fb917aa58cedf818763f7f73474b907380570e8c511769aa6c499c0c8eac3adfaa6dbc1f167e6f68f1872b6659734f07669f06a3dac9959f24cba2f0a7a14b4fd5a88584bfc38c7c18eeabff8d0ad1e20c8be40fbb6ac872c4abb3bb2158695b03ee9166f761e1da52d26b9f8066bbcbe89a3110719f74fdd25658dcaa263799bb8c5a464bbe020c45ef04d6a23b7f0d816678567bbc2',
+ '2e523e9d8a5532127ec63b220838f11b0f8a09e9a317c1e4872d7fececc1b4b88060076ba769b068087a21684c28ecca22f3e12a8778763444e96dbab8bbb005d79e806973b2ce1cbbf8e94901075a5fc0000cafddc3b1362d6360b738d8830e3cf4f0c0759956d69c28dbecae3c0385ee99d4a12d5f38924984a20bf480f47ab64aa19735840e3db5f23f7adb31afe2b6a67f2800b4d3efa0327add741ccdf14e88d9173cdcc0fa0d3f5c1a104d261e1f0f566bdc2a4cf53b562f554112d41d0b97e168110a32b5acf57bf5d6f82fe231bca1934c296a4d21d90a9de2ccd2a3f55d01b13d742d159bfee432b49a94d6f595a8c7d519f49f5aa153dadfb08e2e14c3801b468478c2e140dffa339b1ba17283b2300162b392ef985237c128d26471b1a8cdb6a1dd6586a5b475d9648debb16c09f5576c6fcfe7a54eda0e9b64ed1310bfef143222a69314aaca315bb15eb83af3405fa0effe4aaf91eb51710d7202c60eabe9a1102c0f740a22c0e951a091d3b936a264fdc621a061930f11959b47bddd27bc556fa002af1ca4107633594421a301a7215fcf735f07f9d2e5c40fe9db748b15b607a974ac2879a86b2032d70af8c9f640dec248d4cb4721546277f00a1a007c2c9ff06e5376c8102ee0d2547802b518274247a8e7f4a285c6367a653118ae7a1f011cf78c4ebad1293be3c25207ee944053059c80cc3485a309c14f38b6bc96f7f3d6183ff77772c3f90a2a80509cf2f2d9a52b879925a954a3f3b6063c52591c3ec6d854ba267b63e1f193e925e5bba49bce5ee4a49d793fccb9a285f29a4af7aa933fb3dfef7473bd400577cdf476c062293c7f35c37ecd4cbb1c9c20b9f1eefee65545aefbe65a539f891192efabadf65ecd4093bf3f66eaa02b330ddad66a046623f06e7259806bb4259fbaffe3d31f14191008ba44736f11d833022382d482bcb09d697c534dcf2ae30a8e4ba49aa5f329d5dadc165aa4b52a8247bc7c92418f0435e53f5e2946a7cb3856fc796a4fa50479524c3c854e35290924ce4c0e0988289e2be6017c97d3e4125a39e7abe6cfb2e2b8333e5a3838ddb0e1817baea14f23c28397c5ae8b583680e12b78c5331c3cfa54b8a54329674f60c5fc90dcd38bfd87347a3027eadbc96b35f9b320fb31a9a76d04f8a7e86a86ff196813ff65e4bfd788b9cc4f7c07a6b99ccc202409b901d34d3ebfee3ee88a7625ec8c7e20047099c579218f0881d4545fcc483a245a4c653a8f837ff38964ae31b184c3cc9018b534e5b54d58f45b22c620b2c813bc93457d1fcac4cff61b8e85df83353133bf121d2213f23206440d18f1e6389f88de5b5e151f249ad7b7fd699d0f3c16936e9ac85bc0e75f5f96fc9f666df09066338f249907071bd341e52413b24045582d347cb64593a7a859d6a1a8ce5aaefd9cc919d50cd51b93c02dff6af3a9842b02c8835b2b5dd189958567de91dcc0f620f183eeb5f762bf3cbd42ca5ae09cb4f73f2373faafa7a953f039313fe090f8c7efab0f8ad3b8febd7d355a704b559a137fa52638f0efb19bff5ec95fcde4ac9aabd95e14d2e5f84c551f43bc5376855e71519b6f877248739a20cd790b85baa00d5503da5cb056f02d4aacc760c91fe1fd6efb26def817e5a9c56616023bc9e2fe662765dae2c0b2edfcbe17db140da30c466de65c49c6f81496bbbd1acd81666455f23bb243dd987d7ea1362a20faac841f1a36692cfcb4c3dbf5f6bb058c36296b8be64e9b56adc5187cacb7b58c054f422a9e6d6a61229fdc3b494da98f5a33ed1bee14b2d2f6ad1177ffe99a6bb553f7c4a6d0cb9e498ee0b63f388235d86c26c9d96e50fa7d1eb3bcb9279940c47a8510d7fb175b3279318d5fe45823baba5dbe31c33c7649fe447061db78b33baa3637b854163fe34915e931b9f3040807d9217d7b3fed62370dbe806c006b21cd5061d24490f366e4d5f23e201a7ec83ae31b46fe2108d1af56cc9d42f9117eca1cb5ab344c1fc334b9cf0d7f9739043bc3d413b3aa6e9d5067c240c52b4c5b89e25ccd8a136a002008a9273f30dec3f2c1736c04a1c7ce0087c9f25d5ec5bff2ea7ec0b0ad7c278f0ca712c9ae150e472521d958d0bd6da9ff0939725924b2ed7b410a0ce2fe3f6b0bf25884d885ec223605e318fdf6803218a9a06ce5103c62ded035087a98519b4eb180d778d7656b3d4811aaf11a128317d1acb3ca3166395c51c90a3cf164071d0d132c54b3810a8211ec7774d2288447abe7afd030375a3bed4c7cf1b28097c02e98ea36bf49e74d89fbe74ec6cc1def5cd8c8beb5b8adc3cb48c56182ad337e3b9778e4a6c4eae6d7c663469d0536560f07675e67ef1b3e14444d540af4c3a05d9940260efafc9425d55125ffdcb7c5eafdf276efe68af2efc97c92f25c2f6ebb25a9c6a0f403a198b11ab3965788841541d3cff4a5e328855ebae2e1ee5f307ec31b8a03b9e8535ae127b8078191dbb95b70311f320f28fd8b6f0e7fb13b2ecdfbfe3cdf5194f393eddffcfd5fbb12fed433641897f53a80d803dc75adacb0d156bba2dec5eec86a5ea9461efbec700b33832f86dc7ca636cede156bea98fdb15bb885a61cdd1c08baef60125c0d3e0900c75b12078eb346f468810871e95e96935eacdf5e4b35958c1810828a07c51fc469b0632212abd9d20ae7f549851ba88415e132941f5c38598c1f168ec04a7605d0f62234efd416f12a10da7a567c0eb846ea46c541d919abb255756f2218354e64f5f6460f7726d832c55d0d42c8f1b75790c5f998f46109f4794835147685248d75885f59db300f88cc290933497807b29b54380ef538fcb95536e87dab8e11b33d7f87b54a5d1f96ede4761045cc32bdd39d8b8a23c50b6aafe8914700779c3e1684c60b0ad58fc2f2375cc10514c0e20048f9f5c831be6d50053859bd694e96c83f254364bfe776a1c9c42dd1793788e9fd8b352aa39d2b0036e39b2a8bad231b57ab46a043b019c443b53ef1232116576348339144310c86239cf58e06434ed77561fa068b7113214c38dbac3905f6122238d7473c0179ac736a4f3301987dc3404d48debcb2cb818d54ec4be46c8fe2e3630a93b295d838cf56915fa53219a86179186f01fcababad115a164bdd498f4ed2b2bcce7692f3de66a35b1a9b8b4e7fed530280d51a6955770b5597e08ce00a8cb80bba2b10a549a46d6f875b3a7d43b0dfdf61c880812d8fe850effdc09ec09905c89b3cb916b718d8e214f88dfd54c9a64ecd5a46bcdc60d94f7cbd4d911702803b9c32f40dd1c9cafeebfcee955c438f97ec15d2e20bf2c79965a79c81b8ce10abe2942b543fbf2c0931efc40f00238101e5808dbb614e9877d34413364a059f6298eac5b1a802e74c11577631ea7366d5e123df0e877b3631ee1a1b7776b014a6e4bd2aedb49be10fb1be6ec4c23b255c078731a52481870fddb31d0ee4d556c0ee93c1d00c91049a39ab138f2f81a6db8033e946e1697558c9977fc13b7f4ff8dff7f42158ec3734d2a7cd5cda4fd19d73af71ba663ae756d94cab5923b3e695df6e2aaa3fb46126a43904f16bac8ee909842fc95afcc44f365c079e467b03e11582cc316af26cb9d6e9201789a1c50669398d3a66b8f68c074ffd5749de8e22aaba407f81ae3f32903f8996dc345e3dbd56f1d73175645575abf34ed7e570a1c69eef5c2beec2dcfcc4a8360d6f41d62a64c566643bf6f2a8fa534996968fa68fb7418f10bcddfe3fffe3bfc45a5619daea70b0a61294aac7c384112efbd478308c9fe2d91f78df8478a3f8a8fc864df5705a7da00326c6fb8fee6e481c2761cfa66f1b2e207bc8f1b851aa625db7bca27eeb95f915948e6be5f9278cff71a7958b1a03b6c5ce01ae46539d9a85d2ac0a9d8bbf5a51c64a404d0d06a1ae9893a9c509621a185ad2e4aa1399f77dc0665554a2c56bbda542a14f92d13031866d33dca30002210583bb6df766214c6732aa2c986dd36417beb774f051e08e217d5d564fa414e7b85b5a1669cbc1fab15731acd5803b4b0505a9438f4e5acf530a4dbd7718fb725ca3ea2dd0927f90851f145cc1c54a7c5860a045d89045fc035e2b9882225dcad7a4923b94810215cad078c4c506a9fef617c40031de4a1b19bf2070d88be3f813a37bc71c61f3916ab3876d4709ffd9c9723cfe030111367c7654cce11a3403f6ebcc59d2f9f90c4c1069deb197f515b8b831c7b7c2415416cbee340499f9f36ac3ae791d13bd8f582f469f697833bbaa33cae1b3e7827ece051630acced9d0567249b06575e62176574539d97460d3892930d661387ebb8c6ef6493e837da3a141c48513e81dcb8ed28e33675324633ce38a2e287fda13384cb306237e8c74357848818d340a9488e64a157ddc2975aba9016f6f82418ebbe878f0c388af49f95ffc2a3c2154844cdca16882d81410bec3bd23a00a5935fb8a6b8d86688e2cd53d090b8877a4a3c3fcbab2de10903d5d78e5d122dfca0fe17ab468d5e8d024b15c96a9dafda1fad38dbe7ef84943037011a2025dc93d2455ff7c0616ce1d397502cc8e987cf49065d9d4513a4ed56adfd61b3db9905a7d4062ebf1b3e31f740a78d3412cbd446d622625b50be6ef7a920f790a9efbc82188ec28b012ef7bdc5606d24afc853a9ab0bdd931d3d8393c7104e3f174d4301817e25ccb9dbadc7a42f3f132729f7e1e39e6174efbed5ab765fd827ba3e1396bd38faecaba0be854b6895a7ff4d2b701e3e80792e9edfbf354417d2f93eb8c21a63a4736d3ab47759b0e32bcece58d4c980dd28706a0c3f92819fd96acb9d042772a4e974f63a2e2d7cba46ec1a1aa063f9ba0b5bacd5bd0c7cd2a365715aed72da8ec7396f9a1a45408d51fdbceb337c0db98a36e3e6a801ba52b9afac5cee7b2fc49541035ebc4f80df056a23453e70315e3d988b999120ae82947ff92d77aed6e8cc125e1294aad211b9c7e9a301fef91a8df7207908d7ee04bc7cc447298c646de433dc3023c5a8d7e78d7c9f2e66e96103e92f0f6f95ed3baa0cba3464a25cb6661c0a51fa4e79a4372158a4245686d437d523b735f920a9d5d6276fc97f2464da3164d27893b8d8f12a0a3c843c35f68',
+ 'e70653637bc5e388ccd8dc44e5eace36f7398f2bac993042b9bc2f4fb3b0ee7e23a96439dc01134b8c7d3a4592d24b200f689f25405d690a0bcde95ed751e227a1c54dc94c4f4f29399c6913186defd9fe53bb3db7b622915d1c271d29a8efc18ae175dc74b67f6cfbbed17620c4a0a8eb82493dbaad4321d832525551c0fe9605864439fc3e8b5af96ada3552dd47d4fe7eb3ebf049b400a396d3cef79ef8ec3b3b22aa8bef5b3c5c28ec1a55c2cda661ce5f0f02925d76e8d01050c24cc30c548877f5c9d2d8594b806febd27b186639fab773979027cdcc6973a35ad1493e77f5abe360eee8fbeffbcb71700e125cb18b21de584cf84b79e8638e683570c9cc0b263cf54b746870206874d885a2cfee080717eccdba3a17d548db948022f77c5151c833f265e9f578aebcb1e7af091bf9d0e7fd1b53e0cdb9895085bb460eaf50924ebfeaa1c6a68a0610a43d23505ee6e416303fad86c41b90b6e4eec4578c8e5298443b1247acded639598ee5eddf58ab6c2f40ae732483c4d4581f841a3c95fa6c68ee9fb42ffc870077e2dd28c7d78db1a22640f114798d748a586d9fe7edf093d30a2f54566d822ba742f3483ee9f2ac30fa4a46bc86535c21a0692db31c9ed52e97ac704ab82e8290b40f976b18422682c3b3bb45317e55c600600dcbac6af9219efd503365f2cfdb43195b77ebe5e740896598d7037627217e38885525bec953250a3c38fc38d82ff4f9dd8aea43b7115447259983a49ad925dae47a0d010b6d37bb7c8106676635d19765ca9ec45e9d2d41aab4396e7697fa2e6961ee9b8816d9f857370be64194e1db3a628ed1a38d1b3b6e50ad3d8202071c61334ff408f715a91782911f31f52caea67843d04f89271dba93687a87c3538d1217b97453b8f2b602892279fe00bbf6ef35432b2a3858cfd6a8f18b4d81e667c536b383300079076175455c6f5c959d5ec01848fc43b63a0ab5d0da9ce5c994c3b7c5896877b0847b6d83304eb2c2893b4249918d5149449ee38ecd3c9703fa51c377eec3c6a1169a9a625e61a94ba4cee25f6ca50b1ad6d959b2cef43e9c83ba82521a099554a304246c4c71ea37d45ec9e1430b1930d90440c4448e829d16441bdd75028bcf1402322963451c8e03351e577d8822933367bf4c97d00d0d9a39b7a06876511c52dcec200ba7e85918990a4a82e4ce4cce4affd32e8384f4f9df7d24592c8f4344da7bd9ad5df69ffbd3b541bae7c76290f527e0736f925a1a7f96f0b1edec4ad14407dcaf30ed68942b46c48d58b2dd63af60fccd5bdd48e560298dd981103be361b7b27be876bccbe8e55b63013ac62ec2d2aea4100dc542cc5f8137b0a41d617ab4e2774d38a48854bc8fa4a80524d974a47e6157cbda19096056354250f932d726f40d26dc27b3b5f0e7b816bbff4b0efff46af6bf8e526053933afebbd640c03470a43d094e3454acec0713055f6ed70a9928b590e9d51960c1adad8ebc727d06dfa3586820f3791624fa678b4d2919eef4035ee6f38a7cb167f81770b4b055c5c97440a0a5d86c5619f7d9f4d0641ad28e64b76bb55ad16b0d82040ac4e29299b47ebd5ae5cc7495536732e8f10724dfedb18ac5362b5fb93cb33c04f7f07aa92a29973ee9b5dafb59b33a11b7fb7d3c9b549d9c7ee76fc17af3860552c3561eb2dd95f3b87de7afb241b9142a266d1320b3b899967449ab52aaacb5df416d1fd50280225a0ae0dae97b779c52713e890238a56385bc35a0494074bda55309d519002072d84610a383c83b4f38c436c8cb492ba353a57bc8e91da7c5ace2706809403d8c3917be330a8fcfd5e3089ff8025011010919624ab9aa0d74fdd4ebff2a289f17856731b1063af9d75b23bf4030b42024aeff334e414d6d739f13c2c206210dbcc41db246fd3082fda9ffdfc9dcdd20b54a3e37fe0a6f90699a853dda24945123ff21891a79d932eed48346bb8e33d95be6c0b3809b793ffff714a46f0ce731f33e5bfe54019dd053e4963e3dcf1e12bf886cf2fc7cd140ddbda73ea8478476d587b3591d1ee426d3e2220d77250695893b2a3b9b36e15baaf42554ae5cd1e870e411e19c5616fa17d50efc921b53286004e2d58450105a0fa4782ea9d3384e6d5c5ddffa349b15c6c54253b6366d94eced7c09e152c503b3d68714c0351f7173f77f9b5e3ddb3f89baa55a0a00bca0d6fde11fd7c56a203f923a4e1b08f01a2bbe5f5df1fb3f3f08bd2159b700a916c63cf75f903066775ade7923e3d7120f3cb8e56ddaf52ddfead59d97e4be9abc63b3a710341b21d1adc42cdd4027ed1950eefa1242b30ee5d80b025dbac3f85c5669da7cea0e03f5df4b3bc25982e9ff0c66849651be4b1a796636f361962cbc466676e9db9274ad997b8e1b576f6e8b1a2a6c3f2e9d4a4676ee22a100bf9ca5dcb364a02f2edcbf835d0e2f6177e48322429b5602d8753176abaeafc0c7b2dec0578de90d6ba3f444f8a148a020b3b0cbb4f822f4f833a222a6c364c835d531995ad80b9dd03114855530ac3cf543dabe8a6aed2d84eb6322e6470941916c6410c52c5009e5eee7ed7aa4a45c7bfdc8a3ccdbfdd504073a1d7324e65264e140cc9f73fe763243342571e378283c47442180329c1300eef0bb56404324349b8d76f0945e0e4096c5d442d770c9bf8a14e0b057100f01084da5968568de8c0213b5c177b8f9965060a3f366d4678c2f01896331a1f28ebd18833bd99ca9f17e99321904545fbcdace3a8e7aeb292c9cd3c4a2aa99e430751bfc4e2629aded77e994002379673b9ad7401afafe38263b523874a93e0c6e981cb9d55355110755c9847ebc71399afa425ea0ce55bc0273468ee117da93d08ee462db4869fec88002aa9cb782fe6161d93b27de38dc49ab766df1dab3a0debf3b9e65edac9bb6615cad61fcf5de19376280b712efc0824dab7eaddb115c2194e8157d2a68115b5e9e36d673120049e3a6d45852c19135c0ad691c023eef2073b5702ae7e3873fe092ec0105208d79cf6de01386f877ea6c44d54638818063c56857750c6726e850fe78ec9869ac31627f4bef96da992ab9386a3463213773f3ca7164813a15e014ab819f153386fa04a3bef56ab0207c0f50d1ed6c673dd763a367022ea47daf9661b02065c7435b1da3e12ceac13369d655b2793c9bba177fbbe054fbef86db3ce7ad796e6d0add15455b9cff57fb787610b4e1ba05d5bcaed98564d16157ee70071fb21a6c03065552d54d8fb8a0315746802ccdecb74d57c7fe39964419709987aed1500e57614391f648832d491ce1c2be625f9a8852e44bf2db34def3e71e3003e0f8992e7348cc6794c4fe1ec27d4b158c57556f54bc2e0a5391780edd69cac6e6f956afc6cdc9cf39397348aa91a82db19c6694da4737eda8975992d9e11d9fec3d8d03e13851d740c9d4ef5c87a2afd91815206ced3043e22ccba664eef034f9ab86514cf22c27b05e683a61c501430cb2a93b9216dfb60a3a147205f80d30152b88c29064226691df785240b58d9053526c0cd52a0eec26a87d1f44673a3948a5dc7e34f5fb3ceb334c5f81bd0d3fb5e0a4bcbc91838d415e4ed5a9a440f79b01cbacc00c7e53c7442c88ad474bf73b459a72d0b307426044cfbbbd15e71415279b75bc1375502cf960f54bba0d61ec67965797f961b38d4248fa0723f635bf009400b171a6de233a2fcfe37e1c25d02fdf939bc95b87cf4000b90f3637049f720076278ed9a3b3efe33efdfb40eaef85bf4d648cbb662f2640215eb77029c06625fea4d2d847e400c2692299405852527cae78ab4afab3dc505a7b0c6a4c27c54dc1b2a56a73be561579d9c0e618007c5fdd618cd0e8654a788bee9fed14de75bee6d86f56cf4ad72395abd8f8d201edb002a79915db4d5900dd40d7c129ec60c0969f9865028f6c36e61f493f2d5e8bb747d039e07972ef2f77f81bad34596c9df98d885fb595fe9494bd7b83d21e40bcd266d3fa6adcf54e819ed57add2d839b362ca70c8f657386c60dd68c6949f306cbf1d12c579354a525bb6cf0cb718c0476045e333906b554a498c32199e88cde5bf79a3ce8a0f27c89d648d7a72d6f1ee09b139e5a80aa4657e5a80c0a01f28caa0296f2c40ab91bd577d1f7186714329d7b2f139bdc3ca4077cee13659f0f58df992d1894d990c932266f18c7296387d42c1b5ba42152b5dbe6feff52c7ca892245c774ff155ed0c86c8a015b7a4467ae343e3e1c57d3c2fcaa3e9778830b699d8cccd0aec70d76bed08a7b7d639f18531dff83ab87a913925018a580daba3e75f9a4f248f784043cdbd74ed5ea5ce3ea6fc8f4fab8bbf0c461f3ef11d5c051d511b7a276ababc16c13d9420a1a63109bb00057b1f2f1a1ba64373fd47a03eae35e6eaae0ed6af77402f81ec5f89ce7906a0e75783d336d9de14b5b71d36c51c7672fd12dc4a9ec7c309dacad8eefb0ee245c16f5a2699e9560990b8fe8e3cdaab463de063950eaad242eb26be345b2e0675101d3287b7ac96a8819d6bf51a7b4ce739a125247372e6615f9a6ff84368cba5500b8d8514a6286804bf0629c280c3cc5cdfa19761b287eb84907eb96841aa5d0d94db8d455873de96b82d9ae95db86c33e6596c6e0c3f5816a36ae61e4b3b02a5539d3eae561162c3f372a6d394835d4b7fcd01dcc2651d723da50cf9e7f64cc3c242e7c401899af90b45fc35b7cd05ffe67cf65297a1d213d9bdac7f9a5481c56b8d373afd34edb25a48097429edfcedd4d9b843de6dec9812353303e4de5836b9ac9b57ababe18c8ad93d037d7ea8819563d6451931e36b417c3f4b6a1c16a4275182ce9f670cf3f77a258824f7af57d2bbcebaca964d0a12232faa6c66637a4efc9be44afec6653abb4166b2d167dd0742003984f39ff0feaa92a59e75c5459b0e255d20cbb47ccf1d2f23a9a4788d9d871935bad242bc5172f6c162a292729616ed8dc3664d872f003d436bd947e6100b8823eecadbc8c52cda824b7571adcaa722faa556f830d514fa4a8bf85c73094fdba89345c1a2c438ac6ccb76e933932f842849065af64bbbe4ad8c7a2cb0e3b462284acbaad6d916eae5652f4fc09a207f9b20fcdc340d759af0efe74f3c39d9da2777d4ede17e481490aaf73bc14a1a83c7bfb2f29694d27b9928b82a516630922a9cea013f1e6cc7d2ff7723b22d8e2f3297c1348a7c43051ab97544ad135938a63c839b5c43d56330f517ae1fbefbd0602b90288c2e57d60',
+ 'aa2a95be717ddf5d676aeb0065f400e68855c23034f057805887c9c6f3aeab57d77f0040ad9058d939223c9ddf9bcc386637a7e2fdfa0bec7be93e98eb792c2e4848514c850bd97ed0c7060e1845d31ecdc0d7f3e7e06b9429ec0f94a73b0a2c86eb518d03d6aa73b6c211fe18d85bbe4458190cfa8abfa1e9f806612eda8e7818d2c8a82ed913e173792513e83ead40536736d53fe04f3a4475e9a88840003b86637e480efd5cf08d560af58f5d11cd8255f7f5bdcb6288c1cb8110be53a89c59083a13ac28ccc78ec0874d151fce8d5a8a21157c3142b3e8629642d7fdcdc41828c6b10f43ac8ffe1f66c3836a2ea7626e7fdc85fc35e241a2f0e5db24b9da4b2ae8cb3f37446f63da6dfee02877432269d8f3df12843d55f456a2d3b2b2077a78690945eadc90475b65a73440f28b23e4f301925d77edabbe9121c68e01732e7910122846bc1a31091565889ae7a5ec4599afa7c3551acb696a09bca0ee45ee95a78ff0322c34aa4c47e1e31e9eb906f692a5252e68eb3e5ea603bdd0c0a64334f427a6957306398cc1c34db45ef0f75da68a1485f6898b0410b6d206c1bdb4bec1835159dab966314cb2ce44717149e49d077db0481c3ac26fcb022a37b3c99bd44af965a975b9a3b0566fb61d6583f23ec36796a6cbd4028ae956246baf0a34f525a6a12861bb4bb55837f2abf42eee5267da2157bee02b2ab9d4dca5da00efeebc61f59ea6f38f23602fe06345d142a19ade38a51ee6a517e2863b2d5b323586b63149556be9d8c1155d698c81f455f3057cc3d6136ed7190d74274a5b286f84bc1f8593d9268f5820cb736fcf208f104fbbab33c4012bf8e2a58945026b03b1753291a118311ab02881e75558db58c021a4d6045a26087b08214a6677825bd58a7255c74f92e391d685ae8444b018ca233d2d91fc66d66c28f050f5e3f5ddb8a2e7ba4ca7d250c3d2e1ae45ba2437f7fc909821d348fe91e91b853a6d4df321669aa67a4778cb0dc39dd1dfe2c11d0f55a500fe0754e6b2f4a8d07d3e1104d97d920297570cbb3952bffe9ce50e33dad5824b6ebf12f799f0a218057dc977a991d7b7ec0117880d26511dc2eb93df1f253163ba230b990d860e471b53feb6574772acc16b209952e85a159a1bd98aa8ecba2e2a5cc635d55ef64407e83628ab496ac85ebdaf58cf3fe3d06c9e679d3bd323960592cb31ba1f61f71163fc356f3fc7f50a204c2c4ed4f335809cc57ea182768295eca3f78472584881edd54569d0921a0ebc807d954e922c1d3c7c97a2a0bbd92093d5edfbfee21f9ead4bc062a5d21eb2b8d2b46e56c89d8ca6134f05d5f885efafe97de66c0764b1cce50f23668feb3e3ccb379f949e701603120d94cb376d4a67680e0f63cfff02712c9871bad168fa72f16ff0af1b8d017b023b15277f7978e3d9073c8c43d8d9c5a6c41749a17d2e80cfc42b5048dd95356a405194d991c34cc4d2368f6ad87ef0ddfbdbbc0612218eea9f161e2b461c5ad28410b84b9d71cab1d6c5134de5381959a687bb090f1cc5cc667bc2cfb1dc11c26f193be085cb84297bb0c0f2e85168a02be1edb15c674cfc8320e339071e83c36936c69d3119a3b329c13f63ca0f063cf4b2fb06e24a4c025ccd2a732e2ad75cda2d018c8aa34ed848be38a871bb1bb567c18c10870ded675b4c3e84104836162ac793b476b0ae1f407052c7c79cce91eed849d834f756b2e664c97494c0c878c1cc251dde8aeb107a9f36cab3fc485af0bdda65d251b06b67dc704ba1d9b40a07045ed0ab772c335c138cb81c21b197d539e6bf4763221a457ddd1221dd23546537e7d4c3c099114f93fb9aec5430d1041415ef7d75d548e80fb8d1fb123cdf412c673110ad5b31bce92b770added8fe71611fc5acff17cb85aa88e17c1283204ac87f079859a1b09dbc5575e041159e5077feec8b99d3f1d2556535d310ed5177fef18e5927d58a0327143d011c4b766ae0aceae7a01187f3a6a27b5ac1751e06d46b13e1a2d6fe7b5d641484b2d32d2d458a3f35b468f465f8b1307786b2dc93e34c46b66d2e8482e9d5caefc75519241117581a4942cda5d611d3bde31f139b9635754be934c29636d99ea6ac39d0cbdde4c3f9d1278a3fb95503926922698a77875e1699822f41eef02e4dd409da9106731158de9fa0a03cfb1e998b5534dd01e23fab10af21025cb9e859f14d9d101532bebbe403a753bb64a337cc300c2fcfb6b87dc1145e540a875b3f766d9ed5a4a43a97640c14d6df3220019e3b55ef3b7547033d4a1db392e90572c5e2663e1f68038fe1116fb5f2e4136b83efb897cfbe69dfe7c915ad706f65a8726479d5ffcea7e9edac2e5da0eb0bfa1fb59ab616f2af685309acae1d8afa250e3c4019b0789649dc44b75a53b76811c43582c68ba27640f194693204bc419cfc026f95fbd66d245f63b128a5e9b66713e7e755a84e6cde65e1c1d5c083ca64be3f3ec71c688b1dc9819234f1f2855e71356ecfe776d1b5029a4d15cb71f300e74b6429acbf7e7abd46e12bd252435c7eb65fa1e39c634969f6715eaff76a15e9cf462a274bc5efad0c1469c1997f059ed35078072f90200eaec52cc8848e0848237b65168defc11b49a27b4a2896de5424d7cedcb0c6bd532bbf1cbfb9dd5c85006a56f5065ab37a9811dcc690394b31135bf2deb09595f9e5d58af007d68712bea97c3d35a52b5d7ff90ae150c4d0b83763a087cf7b3e45759f1403ef181c93d6af4169ac4d9d3659be8204fad8034c097544623df61ad853723465e000816ae0e25304cab27d97bde8debbfed1577ef2074ae8ac84a024e80558807b3e5a1a65e90d99217260f434fe8d70cd4f41c3899040a59ba582addbbf1cfe21100b24ce39ed91411bdbde2765fabdf6a066bc48b6b2038be726f58705ee397224190c824b7f779a0d42a83db5b31dfb831abbb7e11b8cdda8017e828048ccdee918543a944fc6acd549f4c07452fb5c55e2645f85e9cc3186b6bb4694b922c7ed6d7e5fbabb18e9f23646581836e089976228883ba93c08019b3e5be9bbc5ed9facfd5a156db8e1e2ac1f7d17fb6813d5fe8afa68d646c197337a2ac5cd30807e3b53c23ab45115251fe2a809fcec8b803c0e0ea3fd871eb613e14bdf6a0d5117bd1410a14b6c044816225154d80841248f143d538b774fecc126278d1e86afea86a0dddf8f543fba0361118f0925d5418c502f1e0c9205b9af9b565557672d654cac724bfcb417f97c21511efcdfd855a8242c6a0d51a09b53c350deb1193a166379868d00062d94f4e5a89b4f909adf6712edadea50d10032309f7f9807568c6faa82f955c4f10af220808a6de2cd4d3daebb803ff9f796ef55aabe98cc1335c5b1e0475a7b02c9e8646142d430c03db05a4e578ac784bddfc4fb221fd1f0810a1226cd8a82c3606c13c37b1f25142f5397feaa474418e377e11930b9b36f1ca16def126286c35ce1c13f89ab1a49709c0a450ee1ea24a66c4c7d60d2daff57200771ac8ef1831dedff3df5149ad0c00e03c9fc074428851169a04917d311cf0a8186f24c5d7321e5203753c8213a8c0e26f5d813cca50e0bbb2a4fe51656f2c56e779a372514176bc6c41c4237e73320e6414983fe1a8fbfd363ebc72f3f50e520cbfadbd2f65ce6755cc51f698763eab444b6f45309a8a224f5b33a882b77fe3b0caab6f19a70e99e79c4cc106b8cd03368b6d165f2d75732482cbbababb6552fb200350b60d1e9b4a3b1b4d7341c55c635bfa791569a438de3bec72450baae8144b1f28afa2e6b5a5312862851a10fff3437e37fa5700b9a40efe96c8af34ea24d365bfab6b4e2e2004dac7e44a94340dcb6118b7fe6f3d9f8469efeeadeda3523e3fdd53723f50e53e984639d93b42d97c0ce4f467da0ef62495455c0fbb5ac71614494fbe9f611966cca52cd0eb7380dde56358982a72d276ea60bddd8856aae24ccc465758aae705ddad64368e0d2a77555d8c9b45b25b03d107b71d3be0242b4f2ee146507375a233268a130b59fed0c59938781516b852b004f2d9bc405d788437c7927104ebc536d845704a9f25729be0033581e512ecf01d1718f3be7c5155aea043a1a472f74e8b3e543327c7e541fa95dc70699057fa1bcee3c8a2aa3e295d1662ca9c32bee3061dc7ae380aaf1daf9774cce85b1d35c5be4123330ec8690acff5d33552a55d1289d1a31b195a99c7b1067cab9ac508e5903aecbad1767b7e7307a4b4fa3124772a7bfa191a6e1b098a9968fa76d8dd41ecd60b7f1ad1c881ab9d256454eca0fda9cc9b7836710d3ecb35781d17dd1ba3781e68ca1260b8db1fc13e8c855d396baecd6e8094edd62e1be2945ba45c29d1df19ebe3e0abb453767ad773b80c588be0845c7b5d69dea123a9a4fd46de7193c7cf7d11ae3a22b258d1d86213826323e8fb4bafb86e8d5f8b91904b24ea5ab3d949049ea1966bc06fcc29a1be46c4fc6d3a2465ce834b2ffaa3408d67084cf7bc8e69d0e346f8456076b278e2e0e1de1847d0e5c63080db18e69c0b36c0af2918a695f09cd23009d6d45c2fa7d92491912c3677c9fd94298e628d0dba9bec0f979a6f45f3c37ad377f00d0d34b8f4dc7ff13d63ff73efe2041ff9da1a206972df71de19119f406debd1ae5bb205b8888b9ccff08f19f9ce2126cabb7fd88f199833ccb21ef1e99ddb7f28624849da5d5c37d368a2771bb48e7ee6f81149c6ce6be92059413edd2cef0361671f0ac1239bc930115f16bb5df32f37d9f7fd7757ff9ac256a21083e2ce6443b55ed3d5a2ef5bd034ef7d4392f642a3a55a7c087dfe4e99f16aee458621aab5932e297ea201f49a187934191942be14a6614ce3080d0d8f72c1618ae28fdd9e48b79624ea789c760726c4a7ba7dab712235f76ec0d6e08b20cef0bbbcc106e81d20a12f43fd5b4c471eb4533f5011a262fd05136d01ce7645ba233edd5e2d7a5a92d30775383a0421888c876e62f98eaee2fc39d636e03627ac827c0d0f583c4734b21448d04087dd8cd5aa115c6a1f4e0c6647c41c1db40ecd96dd137c91162b2fc8bf846e76bbc3541624eee56d3c89a2caa7ff5b8dd84445e6cab94cdff050aa9d6ef0b9e2891b05d75ccea609cda8cdb1c04f9c388b103bfaa9c5284bb2fcceb78a555a8ed92ccaa1bc784646bbf3b4a2fa7c8727b3b9d75be300b7db4478c3a07c7bf882943fc9faabb66e2cecb28025bd4dc36139884390e132a2998e0cb0e0ab2a3cc5a09c2a6d914ec6c4492d58c2718bf9ee06c5a4210a23908d79ffbdfd7e2acd5ee78b167fd709f515baac65027efed0d701b82597c59a2abeeb9b14815f4255585054b5bae3afa4272876ce6c4d6ef12311a8eb797c611834cd26daf4b53c79b8c23e2ea51e',
+ 'd7a5688c0c385edcc1604930cc73ba22678cec50ccd3fabc02ff5073f6195f6dcd8296b579378dc98a54834447d70abafea701e498d5c3fd70219e6b66c087a22f5c0b46ef5d898f09679ff23523e2fed443d28481c00dfae966c221dc9369e6a43cb1869530baf6e5a18582bee0a9b492684777af1e3f7c13d7a4dd811b6e01d4296fbf943a89c6c70a1d3c0995c6a5df1e48074976c34b967b2de77cdffeba682b2d3713035cc656ab50673fcaa399646ebd7a7751002f1b5b4386f66782da084ad3383b119cbf3b8b044d8708a758c95f8e1963365ef04a7dcf04173c602a5b8f4a0833eeb27a1db222340ad53aa9b5faa32c32ad4555caebbaa706e5026f0a0178ef242204bca5299365f0bcc455d046e4fb0f3e1d2844adeab8eceace74bca846373633fc507bf73d286042ad25c34b3acd20724e2f5fc9497ef0d42e001a8d9c2690ab01fd46240ac582f15ede36118bec8dd04033c449be433b2a89daccb763065b127a8aaeb9e1837f503d4bd0a1c19e7fe15cfb1a34304df47447cb792e811dbeeed1c05dbda9ab7fe7b3333a02c22c1e2b08510a9389e6443bcf9bc9fbe0b4e2c96d67f8384c85a93f295d1c3c78de9138adfb3c6db05453058b1266256612ef2ab6472a33b15926df05921c58c9fb0190b4c257ca8ddf485de2f7f5b4fd810c9a142798b4c06f1e4fb09ae55d9fcd95b9836e04b308d14cc83c1421b18c4761a0efd0edc6610eb818933d1d53e19a763d84c7ea2e097086d0012f8f23fbad17c4af0bcf6e7c801cc115436d4277abcba41e94b24678061ffc9a11ea1232fb568ebc9ee7b6f90b73d29d737c334848bd74b89f03003dd93ed46d82d887187945877f51dca5c0f8e5d49596f32d3eb87437bcae866640310ce1e34a0188976f0d365eee5643ba8f994e6474793940451774918ae27f6a58b1aa65300f209624d523c23bff99cd17b8c872d5b75e3735ceb49ffbc053a19554b859fda754fee1c6d714027caee2da69ca278154a409d1c37e4ec9c8ebce2f1d912879732eb5ee08d9ba09788be21ed5ddeb3ff9139f611b5a06bba14eda6f35bf3b6c1bb5a493c2b11e199936b32c238826d94eb4e12d01d02f9af484ab9dc4caf99e47f1b3181de8a6f987b93f4c7c544015fa8eb77c9b69312e68962b01f138c9d79eebcbc44005bc73eb1c6cc508c8a1bca6a90a9811ac743fa68b40e2e59315dec8aad2e05390c74d6d6524e1ee6cb196f90ec0c78c226195556bd48ac862447c6e36b2b480122f50b49e4ee657c8d96a9cc4c35234515ef71e3fce3fc12ebe7938985188ed125b4469ced21d6a657f0236d3f98130c3d42fa90e164af87eea9dcdd799a4c218b5f133fe98ce50ca0d2470444c9ba9002c039de094f396da32afd6fb704f28aca41ebb358741307fe999f21ea3eac68ecc3ca3bd3081c3cafd79fa0dc0d347579095a97b89bd330d7d286369e5b4b0f71ea262aed23ea6d7b4c1e214707646a0e11ca4d8858c81fb2f9b6c2efc428ec388fc83ba62706888bd50351868814d10007c545564f441d169b9b474cfdc89787414adeac860306681ee9c22903c862d537d62f90c3e9c189249e44346c9c9a049b08945ec5627f86862bf38d0ee178243e676cd66b1b9571114a3a3495375c26f99bed3b69975c6db76456510e02894398137d75a97c11650e29a9dc0c0b5674e97f59c0f73415840f0d7ae385be2ed9b144e21d136dbddb67a70389359b3164e71d6a9dd2ab33700991c1d30a56c14d26862b3b1d83035aba6ce7dab669457d7f108010a07f5ea8439bdb9e006ef9147451c93e4c3e7a5972c5c72ee2f83b251dd34ac7c522cf93398618a30c893ed617224cb503f29b6e3d5c12145fba6b024fe01b31d5383ed747db19909327faf87b92163df961eeef569a692981425d8b81c181dd352204cb1b254ed518bdc5f23e0bc61780eedd836b0b2ccd0c029b375ff20f288962ba151fdf38ef21cd1859cb09ccf02f1bff90e728ed7d348dac7c46ec23a2368cd71bc273685d22d87aa5af169b46785bbdbe676e1bb8bf45f9f0b32a6fe8c102d4659f8c4d9db052655c56bcd198e130a0524855480df0be1c0b3137346abd675a792374692f3eed50f45e56e055fe2d3ff32678f2fb6d787b425d9d2186801ca1dceae63b9042ebc5f4229f480c23c3f5276e439d0fa9e7a02c84c6a7eabdb562f7623c455f50e04cd24fd08939f6776855ac3fa6992a9e1320334e47f5caa4165f05f116eaed6d5e1532a5244deac9f2044ce7c046640e5cc4058e72363b7b347a52af10d17ce56243778799d6753e2ab9ecb64a85eaaae59e6811c73a84ad35efd4b0c38183eb01d38ae26a622a468afcf835d5e623163615d772a7613abc931618c0bfa996d0a55bf960066f8e759b43fbe0d2d5a1b2c6a0c02bb358d21be4837b3965882a48d5232f6b0e5cf63dd4056441c1d2eb132fe5dea74b6fcf5da2ce889545cbb2b619efb97dd2b91611add7cdc3336c63b9da4b7f6ff034a704464ddd6ee0d2c4aadc18041304124293b121950fa810a0195e582f004245dd725787d620b73be4999412bbb502e7203666795661805e34a4147279e2a1f1f75a4f12ff45497576f4fa863cfe7fa6137c46558e736ca3760c9540cc81afe7691ea565d567180e005f47d8f439aaedb0c7c93ca9afbd9f08956949a387cc94c721e2f6e4e09364253b34919e01350c7cbb67a54491039f40e108e39b5f78688616905caa5c4c263a1b293686efaeb0cbd9ca05cba1cf22d371eca5206333f12a6f35ed9234c2b2d3719e3e4b5f6849427538f4cb6c80d814ba8d04bf4d9fbd289e3c5028f4679875c11c1f57cb025465406cf8a05bc91da94d8298e4791c3b05261bfb0b25db5585ac2b6038dd0d50a8bdbe6806f9f861f58d45c81c7029e944897d6485537e68a77534976aefd9dc813fa5e94bc19f538e0c4f18e3bd59466b4ab91333e7c1404fcec03b6a8df8368358cbf30b3d4e50c74d1701c6db1ad0edec578d936d547ae31b76b2b431f92d339b98fbb1e755e855b236292233ff2740e0e14a204ea88705dd9093e4665cfef67a8589dc3a7688adfe14f5a26276a808cadecf77262de32c97d65557b5844a50682a13d6a100c044633bddb3e101d1b9fcb893e46e552dcbec908daa8a1f8de606ef305dd5851d9d942367d32a2142b919071e1491deb5682e5793396e8f380bb95d55ce32840f2e03641a8cc865699a8232e4643afb1885dfa40ca0dd43e74601fcfaa4328df37a33767c4d264eb4e2dad48d7fc46ac5b9930e605d50de0a397336958c6ff3890696b2e390de3dc31675545fa3b88e5b57daef24eb53ce1f4a8f92ccc345553e67cfd217045ac3029a044af19ca1d6380509dec0c76094a4eda9a9f6f55d4361f2213c5cf4267ef201ea10d9b6fae2ea25b245ca6e01b229b763e4ba9f022eb25a4d6a4bfcbbb22fd2b95e8e8f3e34f05e66ccb86bb1b71c2c40cb6e3cfe77ade4a6d45a9aa50d0a80d48c7681d63cd8f389dc113cfb03fab0c2d6a83b8cce9345b0030f3a5cf01080ce13283b7d7b02a9bdec4b5e483953e96c9eff37478420e21b9ee0785e0bd077289b1a187e601bd55575ed4c13b7a63a907cb99cb958d53e2be064b112a0d8c8567cd2a5739c72faf3116d82f81f7028f0f88df84c8d0ede73700291356f8808fa40d86ed770a6af194742af6fd13038e0aad2c69bb15c3784d6508510b87c9d667fa1d1149d25563a70130de56d64eef20c3e7401663bddc27bb4f95f2c150a3d91a578d5728d84d934453af3548cbfa78517ad280c3c35def8d68a5b3aefd3d21f89d284813dbf67fc6fb30417263f63bdf4ce7a23bbd41ca60ba49f556b9ee691955a9c59c46a7940d912a235bfc2d90f6c54724d693d1e2bead4b60c26afcd607e9d975ca01a8faa37b65820b7fe5a01d4db9ad992e555f2ea9729df9f9b09249bf8001a5f541d81023a9078bd56792dc4c06e58e74156939168554034f52c5674166103508ba56daefbe5ae2e4fb8fbb2af676bc4e5826655c4d2ce9522a96e9d5524b83235a9e8b496b221896e8bf434f51052ba468c13de392ceb2ec7f2e58e50d59962f6ad5aa98790736d27849fb24df3de45f8b6046141be266049bb53cd860e476123226c044b1b3437ff1566b0f68c3ee650a2c0a55b20f0e82c4521b1610912cb6fab754b5d8cb47879c0d1c79b34251a02d4c100f1dfdb04d432d7f07e80abf010ab0ee52b9fa4bd1c19795cb896016f73be8e0ab2555574f606fef13aff737dda93a17851be65545a81b5e6bb2c544517e2a038f92bf086f6240f4a22cff9d31217d55b6c53770cb98c200c3f61ae3983c3800252ce2cd9593c7151c28cbf8e5b8a0334b8096a14bdbb402391d07fcb3be3a5fd4bb67ada9392ddae96b0350e0b7cb36ad14abf2adb718ec37ee33958a4c59a90306f5b1949d99cb71bd4ead0c6628e844491d6ad9751647243247b9052dbc651cd635df3c0734711ce65418500cdcd078e79411b85cd89a68b1ea6d0cab8d1b86f3cf418abe265353adbe9894f21276b2b5baf030b6836d6b33c5bb870cf153a9256adf660c96f97cdc3436c061f063587c340e2ac94249948080aa5e991296ed34aebfb937acec01395df9bd9f9e1ed404ad748fdf3bb44d5fc242799a186bbae745eb698892b3488de2e971f4452f8c15cb28453e9638fe9a338cc0572b5b7807d46b60476dd6bb9f6a0ec5aa0d1fea773634362e7c5d0df77c8f1c1771ed8f5c064584bc68dec0399e71a1081d7554f1979f48f915aae33f4554732415a967ae8083bf2fa852c1212619ee559eb9344d967265e688a7e83935e1afc43d1768c5ed2df8e758ba2520a90e92dab774f151dab4743cfedc84aff540b64012c0fb69f349172d9e9f54fed05977df912df3c60c6d5d449ed220672d7972e0bb2b86613ae10349a287b683420183ab536ca273d3a608469110222b8492c9eb60e073766bfca6838dfdb377a70ea08826b96622d8665d89fe1ef4c1a295a5ab2b6828702ea3e7228786b3a1a989b5bc329da799c3b544570e85fcf13089ed66424a0f172addae70b7ac1ecaee79aa909e9fcda13b9ba5f6e8cca485d6778965cd0e8e3208b2e3b346f65cba1ee6674c93484002e82168afb67b53433b6660f0f53d0f8b6f2a6f345cecd539e2d8d338381e6884734e75ca9d1b70f5085e5c4ee15ef9e8084e383b1f17f10fe258d89cb6abaeef842bb48fc7336561e46864c9bbf9b4127b252710bafe1903c56d5dbf3477780ac2be0d465c4b9950788a61a7a896a8d16fb724fa532ab3df33204beadb082f9ed611693683eb74896cce3931d2be3dec8d0d184213212969d6788865d3e83c73672caea8659c5ed6673cd5af61f8db2d7c2ee931216de16a537dfd78439f05511e15c6e42ae5bffd11db1d697dcdea5ae422810da69545a95537926b53b3f400a3d69732f94fffc713b3f9a64cc4d23c6ff2c61ec922bbf082af30852dae70479d770bdafa6186fc15cc52b0f9',
+ 'd2967ddf69ef62a9e23c9118dfaa55df92b4116322f1c9275131e3875dc92faa232b26b28aba6f351fdca8f1fd5062bbf0c26db9da9c57d155202f6199b48e31a17cd9a6892ec0383b220a254aa995671ea098e452452ce65a490bca692697fc91b21e232df45c987c37411d8e5ef5cc64771a4d5393514ca9d4ff2a93c751f33ef47d913db44b3f2b43d54091168ad0096f795ba42ecded8aea93eae040c4ffef6b7f58821596138f6d4cf51a7a5d5c57af3f750fbfa8af44b350701839f806eb3fabe0c4c044fef2de30a6f33275599460f3055aeeea7c2156bd250359f6f17b978991ca5d5de79abe08bae2e5dbad09a91e724e629c3b67fb63716849c5a9e79ba2d45e93eab5d0345d99b03b9542feb34724b3c4c6d45fccc8bd11b16d1577757d0f460af152dc68b6ab25deadfebba5f68351bb6e2e51ee766fc437f71c734aa3ac4b6b7da506839b5708732acb87a8b4f7eff09e33858cf5f14a866aa822459a11355e939696ad940823a51590ace407e8570a5dca42cccba96b44cea0cd8beca8cc8a3d0dd30d2a233c19753570807abe4fb2b4dbd2d68201ee1a2bebbc4720d7d89882f207cce4111c7a52a11ca592e1a090a94f7bd3a25f9daf8a73379fbf08202f6b2d78114b3a8b6eb5bef77ad9b9124b470c86ebb12d3eacf21f86bb50a26df8fea76e05e506509da5734b28d6af6c8c93fb3b4539d29ab86cd7f0c45b0d879b454633d03dde35b12aa85ed6112978fa9704e10ac1b6797cff83bee269b036bf48f30e99d828004f4c457aad1390fc3e5a10c161d241a39fb30c393c01f5420c0e974688404d7a211433e5d5634dc1ffe4052473f3dbdfca9058a6fbab43722c9f1824c7fcb66bcf8958e77589c68fe63ecd5068acecf6a59f505046ef038fc40360f07b94ca9b01b39dcab50e652d9b6f4e8e6785dcb1d7e7dc7e46569b617f4255f2cf90f0d15e9bead4be799165c57f7225980713d60970e577236774b00265e171e97992d78e48284fb852b1fd0c771f5f24b9fa2de243518404abf644f874ebb7f1d717733ac23d81cb222fbe1a5e3f823323f7800b670aed11a889e507755a0a1030e76e0a1213b31d6f7270943cb9d7ecce73952be4f6fa74e8965ad77218b36d0e6a8fa53f912cd9c4e2db251975a67841d735ebc3fff352f3836c11720cf932f808a0b4519e3625a6e7c673fe5b37ff048928f30b0c1fd64fbae35d7e1e2684772967de512417299fe9fb26f253d1a8a0f336eac404c72586a187629d36818d1235aecc460a180f365040e7fe45bd9b9c7b2779f7d1336a20a21fc9d73c517551943b25383881fcf4845e5e29194401808bc247ab5aabcba32475393418df64bcaed69230959a1a5fed1d327bb7ac02d58ad0fde8877770998f4f5bbdb37381dceb49bb340baa9101f4440bfc073dc522e01942b640de82894a76d779f354f438b45b474f81fb8962ff9d93b5f24173eff465bf1e6d2929fcbfb25471c1cce6586fd3df86e0d3290878ee6fad5efe33c5307c1b27f6a18c7955ca7aab06217fc5867a2ae30e7b997dc5004aec35bd2e2afb26ffdac38bd4887488f331a89ce6065f18d16df43b0249509bca7b5dc68ba9bd2a133dbd3228b184b2640b918544a364757b2c7a5a8cf27f869761c06173c7df51fe5bcaddb17ded11783fa724c025fb58d12989225e02bc8b5aa644d60e0ec7636939d3d39773186dd5d7d4c083138e8a7a6b07d9a1016e70bb53d36c8305de28fbe1c15d011c6b8e23dafea3b4f584d41ffadec87c75fcfe616f546dfb348c675d5a7ac317fe4f3b0b510f137c5445fc68dff43718e8d0fdf502ae1f9d243aea36ace84d03a32d3e3443ee5903b639cafc21336e9ce151351e15cbcb925c4e772fe0f243ea936b5d48b883bd70a6c80884c881b431e9e76e85ae92b8016432d7f0fc7fecf7547361ecb9b686eba95a7ca7ddc853ff87fa6fd2d8eb3013c54cd2200b79914f70ab11ca4c6ffe9e4957ef0e67ef912d43f201ac98be79f006062f6712c77a1d6f7d378a7c966baf0d272a616edeb7e4a538ecbbf8f3dcbfafc7b86f1b51ef87d75099d44e1316467d32f247fb7d0c4b3632f8a8ff73a1a949f633dd2dff38b5328b014eccce478c22e0126814b4da8b7d49ec1bb52410d55bfd695c51b99cd0079835a3fdb046a8839a506dc46b67cb02e592bb23ef716b6d4322928e676400dfdefd79e99f5ac329c676fe108afd344e6fb03150bc0b9507302527ecf1d96c41c523799984cc059ace4ac202f9e2eee84d0f2445742779b22c3cc4733f403db5b56d4a1448e49560189562a1a05e2058e9773d08fd0d3ff59f2d112c39e49ac316a59462d1ea9c030e1e85f567bbf227aab024a341051059f1c1b51ec80cacf45091018c0926410916afd407373f8ddcf781801d275714658ac05951d2ff9bcfcbfa3799d7e0adfc9a0ef7def63419ff3ab3fa7f964a72a1d3f2a4ce40068eddf0a7de91225f0763010495828ee0bd19da0e39d078275395321f3f9cfb69ada425629f7dd4ec78c7b35956fce58ca2b13f5ae618b436deca100ed1c3ea7021aed3f12264d4cf28af18d5e453e57ff04fd15fa86032bf3cc7d688034cf4631283631d0e4e0a503ea39840e48f7503ce7bf8e528afda0a9414ea557d3e0389a7a93bae0b435eb7e32b9b6101b97c5e64be1cb30d0d945f0f3ab63de6a1fe2b09aac56cc34cb84b3ed08901e1d8e4d9db9fa59200824805d5c0a008e67ecc91600e681fdb7000557819cae82cf494bc5ba7fb4aa917de450d2e127d27dc5703d35b8b8efeb81e1db88fc6de8b16744f0b5c86a3afd33e67df0d73654ec386c98a1b9882d22afaeb27b8e5aba5446cda1448ae65bbbf50b374e32b88063b412aaed5f6c3ffd17d07ed1f9016248864a49ef3dc5774e1fd70f43ba2aef54d706f67120d192eb28db831492de9adea44f6d0e4764283432be8879a3f9fc300fce1dc59454c07d713481bc026eaeb71f418d2a3e6ee8fedc361538a6c22195dcbc5363206233147f8fdd40be1e283255c52cdb8922341e5ae24839291019f6ac6669c25b867550a222084b2c898200e65ece9ace4a94135d2793d3b1402fc31d50b37bbc3e01a297bf8523d41d66835f52d4d05424736e519a7a90b5eeed8bae1fb2f7b8f5629abeacf6735b66203a1f55e224e16c7c8fb6af8f18cd78cdd0bd07fabee8491299856ebeb228e059a5a4ec78f0cc7efa081acb23a46e7a6b12e8cdfd39a0b58c3f8a995058a3187d569e6b0bdc9bd88667ec91eafc0f702fc2bfb63d0098a759fd4ca15ddf707b05bd5a761d30c6059c2fba88475826d6a67080753cac36bae6433a1b39cf07848d5a67821fa5e182c5aff312cb714b3a399cf97f6e84e14ccba1e16b76afeec16eae5403a5a464dc7d9924c5d9bff2f783b8892955334ef974b9c11686ea7cbe34399e9f21a4c67f3e5bce16149ca4bb0f539e27833fd62d6bcb57a10d1767d7787ca5a9263f8ce782686d87eeafd6e126eec6ee7af2fd753b87139f5baf06a9a5f807b615cd3ea1e46f719fda620ff684fa81e2aef54b5d4e5d9365f529ffa1473c434a92e6424adea188d26a4bc68783f76da881cf36422ebf45a98c10a96ac6a9d6db00033e2ecbcef1a400d58100be75d8a4b5e954c0573cb8be72eb9a42ced140ec0fbc98da46487a995087d5fe8ef6516b9dcc1d5442564b293c1358e5aa330ff1f46ad8e9d8ae5236bbeb2bdd9fe96661bbfff67a9a8c9b8e5a405bafe35f928150b674b9fd5136e0b577f92a4bcf963b82ed76afaa5028325fcb192e24a7772ec6f802e72ed1874bdfa6c4612d395f3da52d24600c315fd9bf4c27ccd8bb3c3ea9c7f9edd7bfc1bc8cf0241ebe872ffc753776383ab0c0d8eb1bfe2869fb40559baec03aa27c74d76ffc8ecf7a69970c8584f294b04ee9a485e302bd630821e7ff050c49f9882f10db247adfdb2112c2589e1011f77c48e0f219dbf85e326f8a567324b857735efd60f05edc7b7e21d260fb551c8ac95d02c228f065b62a77912471aff236be62f193f8c151b5b152a131253820f4a6948e78a8e6820550d8b10b79048431d9f981e6a648bc246b13a33b944fdbafa49de8781204d9b636115e5df1d8eab3467142cb613b98421be37cf2d0f2991633b7a562ecf1d9535aafedae848392459478b8c4e2305289445082f963c6d5e2e4a049aba2240d673f03037fa9ab1763445e387581cd978464c959b1b5333e7027b649c4da11e26c43b92443c9a5f696c6c0563fd849c3ae0dec65be4dde2f588d882a40dd51f4dd0940c49d7d0a9c5aac1d96864e5b637090083b61a62e150676846f92545ac124002868df3c4f851954e47e0b6c68f376abcb4f6e5689ac0483399e5bb7a2b3ebc8ee859b6ffb5d6d61a38111ab08f02ab1941616c79740dd34261aef8fa0699eb3f1af54b08461c142d9244b92a1e5f73201240d81cd7feaf9c889d034fa3eb761d05a9d86715ebf8903fc2babca4176ad70fda50da2b5d8549f4fa05006cfc04308fbd86a5880b2a4a25d046ee89f239482179fd39d9f0fc528f0d2596c7943e81a1787c49094351632eb9854935b8887b2e6307c34780bdbe3f1d8c981e7acc172423e3dbff5d15e441c39e541031fe761fe19500ded46f95ee74618ed87755fafe06e2e3d21f20d44538ba9783254443dd3bcf7706b6bbe08358cd015d5381331969a2eae952173b245e009bf45b02ea4fb9deb028ec49a6e612f87815d6fac95b944a77aebea521c57e99e7cc9cdf715ca3ea33aa3fc0efffea097b68c765c4aece0313882a708f10dfac0474b083e2ee401a89f677c9c3b6272892bef06d2df961f545df5f208cedcb6278525f9744ecd99739725c0b2bf3137f467f17b80b249347951c265e214488e3cdd071c3a03db689cb88b52f2e9ef4331e1305ee6616ad228ba545d255fd5f568a55adaefdcb1f17c79f4cdcd59f136fa3e282b846b9f6adb0e38423300098e33848dc01637d5c69b61ee7bb27deb8595b5556beb4f4b8118b3eadf9ba357bb45e13c663db3bb4a8206f4f732c432b19d0d248a7b7af3975a51f86fefc8550ee841d337d6bed71fc8bf94cadecb7b3d88ac2211b58d2c30284ecd9d8fdd65ebc33ceebf71e7bd98c8124a611702099be108ea9c49e469cdfb20f6c2fc512ee44f18eb578f9ce358189582446bf6826f2e99ca84791f10c36b7ee07ac5d1f48ae49c55ba806cccc022cfd8ff5e1759f9da056e64f39bc5d2c19f374f6cce7b423c0dba3304c5ee838f07bafc5df314fe6ba232a829f8fd5eb62847ab61a507acbe03856b8d36dcf4b603b4c5fc0827df6c16a3e88ca53be9b190be0945044e1cd30453ce7a4dfca6201a32e6a8c5270f43d95e80ac2ee5e63c7ef6f3775aa325138681c66c69e21a55d1c1c8f4b887109b40bf1b0904afe6cf398ef489169b681810abfdc41901c3dfb0fe076060cc85db03421213b4ee5de256e286ead6bb2839294eef21e9f035263e240c6c5c6bd17b8783f06cbe15de0e6d9e152cf97717ff36c6f5064b21d0b1eff05288e9e9860553f150649edac9abc41e49c02d53a9e2dfc0a9d1bb0b391b3ccf7436b7ca05f0df169cabc591b35320ef7f34b0d5407c7ab89824b830d0caab3ddc063481e3d6bf604f92c0df2d9cda8e3ffb427',
+ '0f5452e6b51540cf219998590995cd7f8785fa40b4f217fc79f07322a2ec5e0834a4261a0177463779dfd958c33c55730dd3759f20167778372688c511967d584572c336d67f99f807c57c71704be39152222d8928a4d8307efef3a606ecd637e9c410825bb6a1da72526aec384ae1a2ff7a0948f425a2eef7829c0daa77d26dc8a4f545b9a3c6a5638e891142c2b66ebbe3f123ad213c784ab96c4125bed9c18b195ac917cf71208182c227b73bcabd2f66cd617b1e10961eab498c9e4954fcda2b27549ec008147535fe78be3b8557020a854b85a685121b611c343da1a9e65ce3442f7500f549e6af234a804c4f04ddd80229f44003b3eae2ce822c4d4247ba489aa2c6179e877df91ae625f5908b68d62a43ef75f240333645be90d585e79c630ff4b68b6d96e21acc94d4bfb0b54a0ee6e09fcbbb829d666b3094c2dc8ea83a8c6f6fe6c83dbc1a209cb530174a2c881f492cccc441d17927205d9bae0389d8fa5919af1945b302fd45f1d22d12b54bbbc7bd007644777760d516e8630fe5423ffbdb6fc77770d94dd8b02d5bd48e5fa4a07aee395536690098e532637a6582459ddead3a999ba7f79d19c7075a5eccc01c8c1e763ab656eeb1f2ff150cb09ef2874af1da73dc75e3dc552a9b6ace9af9851b1893bca046126866dae38c6fa1300046c40fcfd94af9dd8bde7dcd86d235214e65faa39c415404694834c44990e651faac41733d2e21e7e469174b2d7c5e3a4e8c11b751509ccf22d3717ae775fcc38f33d828ae2943448855cfab6b9e5b16431542c0687ad20fba020077f057599c2de13cb6d444473a9e2a0fb7ea4214fd5489f48588c8de0d595d4a830d3fe724fb3dd5e5598615124f6e3e354f6f7139a96e8ecf5a40a811256db765e6348da522cf0c7de2f89514c2abc3ee452e5a116b4f7a6686ed196349b9b0e7223e3365ca1f47451aa0b087206808aa7286b7ccc2b11f12b3d4174aebca9bcf6965c1ad19b6ef06a6884cb5902e74307e7f70b3d51ee59b89ff8b103426e1e665b220c53a1b6d8831b852e43b846f4a12216d0ecd1d34c8b2755efb4b57ebf4bc2c36f553d627936136ab5d48f261bed6759725d1377462d33e765458e520c116dcec858d7087efde0c3d68e000b2557182d43f0af20d319763bd628556e7141cc82bbc0f70f4635142f24c2b37cbd78c500da5a0d968fda3eb1a6ff834aab775fad9e4025ed6b962deb153501e120ccee82ba0ba71eb8ea2de74c1d906d070ca7adf438dc394b7b8ea61c3783ef0bce05114768044ffac3a44b5a15155c108c34e2621d9259826fc6dece5ca1dceae6993b26f1bd90d1e1326c45788a8e447078095c80d0f49cdd57039016f4512ab12b7ebd5b3b87badd68b892ba587a3f43f18137f52060f76ecfa305f8e3e267b83c4a9ee6f6ab2212b7cec65d07a65d9121c8a8dd09452e7e813b946508e70e663c2d30478761b42b900d54c330a93bc2996e13bae407ae973c3bc00ddbf5ab45aba515df6641dd7291f2c29f3be93662b8d00d11592ef44321321d35c594aac12b30b4110bd1faa22e1d9443b1fce9fc10acf972c131ac0cf0ad008f5e2aa9730b5e8faee078b814d4fc4e531b4ac2e931435d41c4a6125f2b1b2e6d2e4e13d5fc8d3cfb9cdc8255270d654fd0596dd48b31dd20cc02d3a420eadb718e6576645f5b10799943e5ed84df5d8c89af27289ef6cd725fbe7c8682caac1f27174da8a436bebb5e655f387ec0abbbafc29b6fdd10b2c8e85f5970b10924e860ca060d7bbe9c3364a75ae0957fe43fab2a4714d60e21970e6c16fd4c44ba4fc3f43c2d46313d7434906550396b7b9b144ca6b20a5d9e5f3a4b1186b4fbf0b7d92c5c62845d16e056a70b120af1f65063b026b1fa6d9da3e492f5977b9d4cd318e8e357b690cd1a4351b8a05ac1d8e221db63bf26dc83e7a5da2fca10a74313fba06d677d5aa49473270a85d94987d2c754da14002905ace6672c7904b867ecf9e9673c293951c16ead5d2ce707a7b4dc82f66b16b177663ee0683ec84f2fd0bc3a4d204abb3923ae4b3d20047aaceada0c352eeb247da617cc8f85fbba0f619b09abce623eef5dba8736e9d2110be7384732c9fdf06cdef991fed8ffa78b021eddef90f052d8b20bf7b6f4a079495c8fda7be6cf83e9835cb732b244861754c03da512959589e32b0359cb1ff1e99d392c5cefb07d6845c9d2d7bc7ae468bc179f48cd9255674390307bf7a149614bdcb3625f713b6d78c94b3a320320c3ba9d79671c97311555c415b9e4d4be44cad30628b0625c6d9ec3a6e6bd6df9ccd2ca627caa14a708bc3f19803d0aef08acc4ab1c7d044eed4a516c69d92bade89716d0f5d08b35b5d979b26b4ae44a21541a08ca3fe78160edeb024fffbcdbab7a1b4b5f3a7b4ba1200c76d798d15e33735ba59e538c926d0c091ee5bba1f199ee3048353265875f325fed51c162a9936d02181562540066273408e7776b8e18760e3e0472ca475482fc7c663f08aae5b395c6dbe6ee49e0c1e456a1bd8a1fcba40dc349561a2a05945aba818c33d08ee99f384e5bbbed616ea29ec1187e1507faa74e8066f59df6d9031e301394c9137176f2d92b3d497c7fe737a202c80c71ec6356262fbd11bcd38e1dfe7f8be2f7ddc57d28ffe30c76983ab7e0f6f8748debda60b71b0133ac264430491a89bcc0a1033daa245da5042a05a5fa7f7fdba09b74f4bfa332626923ce0bbc9f2ec4f24d98cad9430d8189b7e0785b06dea07e5dbb98bc72f12c585e93f6a557eb8a01462e8b32c663409bb443a58f2785616bb526b319e4ae001e593e87bfef307a722d1d2543490f60aec1b7b4034c14acfd244eda7482c97c0f86164548cf9e14c954fcff872552402742b53a540f0cd1a74d8c8538d7e3fec087c3a5fc73a4f77b7036907b05ec8db9c9b49efdcace8dfe736839f34e8e16c5b0cf202775b5810ce1627e9b452e97cac94e686d19da2067487a6f207118328393815a7630705ff23af910646da90cdec3db0d2e66c037763f3bab3cca23008ec282ab554e45d2cfef730c6309ec4b6a3bf313ce5c1131bfca1464c4c42edf4bb05b994129a687fa6168b9239458d1f1b41f12a9443dbd887bad2244f9ac4d4edf74aab65d22ee165332bd02878f3f09aec7705bd9b62974b65e6a39d52bc90cb2566641534b838817696eac6deff1169e74b362671b04191cb0b31fd11dd109db89426e9670d6e43085646db20b86ad05bf523986ccebca113c836387f303dabd75d5aaa143569f311f34e2fe527e41670fd36da34c3f2c366a61a1645bcfd3cab486620e23913d9b8f36889d65265854decd6b67297c93fec2c455f0b8a39995cddb3137a20523d26e0feb29a43d6631b4a6af2ce532b5ccce220db78a0da0b11a4a94b83f2106683417773da0220f9019d5c57effeff632f5011523508c6d8f226b08fa6b7349df69f3b923e95298f286f13ad7ea02d9c3cff818beacae74828ea31a98b78a62aa781b2d76d06db6db847f7bc22429c4f1c76d94f3eec23a5db78925fd93d4e55bf6f440c105d1ddad8ae704b84ea36c4f63d7f66d978643532593d7a380c146666f159f7cea0d620b02be6244ba3e243f3dddd9864759142561498c252efe0a4c390596250d9f0856946c10d207288133bdd59ad87ec92126f310dfecced9a58ffd3ac133e0f52522b8769cfab61117df5c55d6ce9e44feda43903f8510e2aca4143bad23f4be4ce1d774732937763c94a1e509e3365af1d1db3e276875b7842b266eb69fb948e43689dc1fd81bc673f616e9a0b0c789de90327114116db6c087ac7a89e1bf238f7338140fa1c3905266340a37b1d23fe987e1dfa21a797ef63c0eb573b476dca33a7ac2def0df526c87719d2b60e70bb73b51e04dde295e79dac5c6206260e2c3feca5eea5fbf8b06bc68391838667bcc2d1b1973f6edf3efc68a12fc861dc476329f4ede48f4b8d58770548ac2d0fe9cdf95f1b0df47ddac9194228489fdf01e78722907371f5a3d7b3285df7ac702adb56aff8101b75a54beac7b350892043122db6411c48fedc2fa272392dab926835102997108cd185cc01b93aa6c7a6f62e433867a5bc6f1eaa6b6d4416d17e89244e555ed5ee99649286a02413007a0081f33169d7774f538e37a1e679c73b77ee38bcb626ead329e6a202d95fdca24b546328b17a1ea662242d95881f35d118dbf7e508cc2889bc107c69c1520528bdeebba5a6565c7d33b476d190d1c1495635b358ba904fc205583e5c44b4201340992430b032d6dbae8639dd685516aa1842939e36380787eb060d64ca4cd86cbb9bc65237d837c8bbe19f427673dafc454cc03ee28cfadf59611408bbe3d23a153dc892a30e254c420fd08724f5b647c79962c02856ced234b278d07f41b98538c752795d9d48702b56f334d9a151948571a0ad6f5c2b68654164447e2e33560b818e8ecb4d4a3b5eff30d1fc4154ea671ea28c1e67baaad456feed2072e8b476b2a25956e5d0b6c351b0e7f79b665255e791e59f3f229f69396e52ced482a761a4b662662f85e743969bfd317830d33713954159ac0c4f0d31ddfbee1ea637b49df33264f10ed8c414199c129f5975f5ebd526b47620c3a6887371ea16fe6d57e68050b6f416912f15504b02da8a4078b77a1f186405021d84573b2885325033c78644c60043186ddf6b9269ef6b3da6bfabb1ed4064145b6eb2e11232eae82e872d97fbdd3d7765ba90f9afa7a373684a91669bd0e402478eabec0ee7cd3e31709cd0bc52adb6fef0e8093ccadbeba7a153c58add2ed3c82b6be3866a835925c8fc7773283bdb22c89a497920baab490d1b56232ee98fc88e194ee184b542f0d8741487278ef0b889ce42cc70e9bd37d40ea2cc8dbe3f2e00deb687fd0bff7ce82d3060174ffca77cae7a9e840f285a3e5630eec055eae2c7e2a1e09d2c9aaf404f5266c02508d9e95f7b5822d5f599951b88f2905598cb2b4c9a542cc11a7327010301eab91b35180ccd6013e32bdb1f84333ec56751d37644264b717188afe76bca995cb33da350dbf6201fe0d20a26bab83c27321ee1eef4cf2d35e5eb4bc6b62f96dfba760bdaf480fe0d75f30df0e59f537d5f06dedd821c6240c44b2e0d0aba603b76cf55fe80f364a74995ccc52b71ea919512b0891695525ab7b142038ef45f38904f6a04a7d5b9b305c02fefb7b56b297d49403bd9c3654b66236ef26b64fb9db0ff303490b065fa507c1eb5aff33d0ebeb3763af22d04da78ac9a20c8939d347de590d6640bd044d3d2815e3cb4c80801583ad08a5c95d19651aed6ce07abc3a00b72314a6f625935c94033857e74dfee417543c9d682f6671c935ca54a8f13c079bf8e6b6383001f6a437cb3dca95a2c750ddcd625311294142ffa74e4afec86365d35ef6f9b0339bc7281ed53cf4264fcece3dad00cecc4416bf8635b75169a4a366efd1b12282ac7b6895c6d4e5f3ddff8a0fc60794ea0ac309d9c394858a3bd3181bdf050389b93f5dd27b18e7960251b5a655ce2fecf5c643344058f7b4a9735b55875bdfa332919c678a7f85874d63c5b3c4caec5fa59f0008648b44836e12c54f633c1389b90088a74d8f2c322fb43e9aab9456bc9acbd4d88891e840fa3fda4beb0c5b87ad0ac7145cbe584129a174dd72a0fb8e35cb261725035fa7fe390eed9762f4379f2ebc513083a61ebbea2cfea277474f171b6866d2661712cabcd796f0d69a140fcafcc05185723566ccbb60c38dc669af4b302a1910ab0be029c8fa7fbd99a76f9dfd04482a692faae7a',
+ '109317556c21c969eda65a94176d7a11462c9ae18a865b6db4d4466eb125bd0a1783313ffe79968511d214afe5a20013898b0aea5e39b8fa282f137266c6a015df72919a7e483d535fbdce0e2ab13939a0ac74497367e35c5b8e131c66c4aae790e89e2e9396a61b00f1ee778fa00fcab3173ec47218c3db7479ae365a27c5ca516bc0c3e66cb9251cf6deb3bb796910ec55d224035442c19c784c86e9f8d8044a855f201ed15eb8da52048a58442e5171ed9630cd244754fa1455d6bc3ecadea4bde30ee4ce7d1e628fcac30b0748d66a67f4b2798fbbdeb7d431ec7a0185a0879bb555e06afe9ddd3497287ecc9ee7004c5370ae9e84a5fa414890cc49f0921aa83bbdb9adc97e73cad27f599a18cb5a221a3415588bf2ece1028c5a1ff3fc866bde0e189fc6094bd8e591437a9ecca274b3c456c5b80cb43fc8a7cb8a7625f26d060fa449de858ee63726e5721830fdc785e818edf43d7ce000a8c893615687341c8906b2f73c637d3006e78d6e4095a5f86a03d925cb694e1458f8419cd76d4a8644e5e2fa74f32438f8f0d0894492957411c09034ffa5106a7f049c10f0cb37ae08eae2d0766563b7c5a8454f841c2061a4f71a0a2158ae6ce593aca3e9c981fa9dbdb95f8ae2c21535b9f3a94759cc27ec4f808d79a9b080514e7a3e0991b2d4ca056f91f792caba10c8e27fd774242eb171c9a74ec19f108cdc0dca994851a3586a0d4d079c020f1e801bba7a93addfba05fd3feac803835fe76d2de119e7cf10969a7a0029f27a2786a54079dff1a0d1b2253d93e562418f14a351292afbc0b72e1e022b602364f28588fb1c7f77dabc204788924046b2e70db61cb9a315dd18dda2ccd06a1c364823cdd2aa9bc7f644f86e0a2f02363e2e7aae78d8adbe90fa492cc0376e6556f1087bac6d5d6a5a31e29faab153bb4d2b02944cd0707c41241ac7c6a795872eb5dd9a73abebe7704b85e450625a5c47a74e6f80e713da565ff978c66a0709245c4a330ead6dc69f5a8a44e48840a1946a0647fb66be5d38738e49a8c6eb73a2adf64c65bb0c904e2598c84f6c2c129c3cd124a7959b8f4f2880415404694e0f718af0601cefcce775587677564738c7e5570f6bbbcff703467768b36bab21d370ad24e71a4002d1127258458db99a7e2c410f2f21851dbd941dc7ab45d674567208f2e2fb24ba74c48d76a15ba9c027db372ddb10e38ae3db176525d815d0ff3f43613908c57d385351d674063f332cb8e07058ce11c5dfe3104b0a6e8ebb389e0579d4894bd285866882a4d7a57af38ce5e08ca338561774ae7f404d69bf6959f6439bc6de42c32677821b16001de61eed8560d980c6f556990bb1bdcf64f836bfc67706a4545afb29dcbf0b7ba56b38a168bcb7109cdf507af64308e8314efe080b4e93c890b2cd239a7afe3b99eca0a990898411603f2cf949e075dbaed37878180ab3707e336410c433366b81d01fac05ad89aa9b7cb0bd0b6f4bd163ef6eb8f791afc5a746889660d2fc31e67f7d53d0120e04d4fea56f44d8d3f1d90ba670b0cdcfee9251005d783e98b54e618245f89e5a46932dd2b6fd035564978dea4749a45a13c7be950e1361522044def62a853bb7996071013b3dd8185125df00e5cc009a5ecff30f513a22719a4d5ea0ac80d06b25c432f8a60c3f66e10c67b0f340c8d5f5315a36cdf4693f4af3494971f045bf1146d8809e7220853bd38fc419aee454007559d12b491e0259bd07b921dd82fb866fcb61cb7863be8902be02fe1d3b5fa8245aaa12412a03ef3300b8654f6e67abc57363d625f059f0225b344b95b73d14c5c4872be5738a32de9c0ee54ff34ba9d2394e6782af9e9abad020a71f3f386ee0ff311e3ce2df4d45448248e4f8eea71e83e9259588d5af52848fbbc1bc96dbfd7370924e5f4966f15ede36176699ce2fbafdb96864455672df482f8514a085a5880b3024d311a66854e859ef2394468f4752ca032937b8a34915aa73309e945fa6cc9fc5248034348c695674182736c690b0016cc1f3fd6c2abcd492f8d830b4357987393cdc39dc7028d8ae2d9ac65affdf06ad12c14c56ea0656ea54957f2b9ddb9289016ddce966ef85a64b3420d5fba680ce06e9cde8f1bbd8832d1fac4c46ba66bc5d7d46e2d8840563583e69bfebcf18d584b2aaaaa8a3016669bcdd9f98deabda37529e4f2db001ed3d00cc9e392075cc7366082475857a9af2b53badfc0e0aec76350db9cd3b214de3c26ffc4c6240babd4b12dfc12bea27ae52edfdd8142af9046ebba720ed0c8a31cc7a608c5c20a849a9ed62f55bfa1687da1b1795b6b509c845cfa18e8e6bac0e65165361d8be9dffcac43577de526e6497ef849cbd5025aa02712f7fe5e5bc64d76b5c339cc1a1c7f5bde1b17c99372ccf8fcb54f0a55392eccbda5bbb23c01a68a0036a72d2bc897100ed09fc7879c9cb237424195c9d684c02298ad8ccc31861ddd06e2099f72d87b6e1e928963d22d3d40876fe1d0b146a41a5740489ca460a4c4ca86ebd599b7f0746b8c69c8a1f2ec90eb1698fa47f8eaed4810702df8caa12fe7e26e7ebbca11aa2de9f3169a8262c0e3c205a708f0071401aa8de09d28a5a6e590ebeb476341880c37bfee1a501229081eb27772d07b371a5b0c65100f34a25a2f0ebbcb2822865cf22aafafe08d51de7949ec242ed9cee8ce861bdfe2b0aaabf92150b59d173db6a5bdebc9c836d3cd6e16658b4f8533f35155858b47ac3851abce5aa516a2169fcef423065ba1176b69c28416d7101ec0a0252270a2a9d3f193802a084955998eda77d5d42f4ea52f08b8b8653a0cd7d7176f834e982bf5f26cd16f5d89a43eea549384c1b7b2058ea77382e50cce07bd438f28637c9526da842c6b137c008f58c9d1a03d995da100d27d6414b3e616e9a11e725de487df20760bcdd8850d0350a6dcc8c628b4003c1650ec82b3f79dc2bc97f1ac4476975aaefa081b392c235887ff5efa0a57cb86ff788c9da15504fef28636cd30d3d7efbb719a39fce077d6c9c3e327a2ab3b77da6eb4f3f080d4e4ef63b23f1e42295617fd04d364cc695208c4f5fd7641089553adf5f4262d962b0faae480812404344116d865f5328060a17cf7da199b8b55d7b0e03cb69db117dfd65e1ffe0be0f0c339757022d555694056795bf12d6c3ff311d42c2673ce61dc708f9be96c58222aef6c608207410251dbeae1917903ca223b7250fa22366f8203e952d7c7c22ec4933de5775aeb924287dd097ef0ea7ad1a82b29b63b91b76d0afbf34da0c7ad3cef6a4d8742adbfbef4b0321e4798c8ade26f34cf1258c009e047ebbf79c0f4003e622736411fd1137d1509f3cf973a0374cf00b969041fc53e5dbaa1c556b99b2ac5f118f8aa8cecbb6bef940b5e557ed9cb0c19822c3d4b7f9dce9915f1547a1f063983bbe639a72a3561738d66917c7bd3b54400299ee92e98c609ee195b3995937f2b1d4b6ddf3401fe16c8388488e5899aed6594bb4ac5cf0f88b037444618fe20539f529ff1734214023e5c9520a14d3b5a24e628ccdfb12979fef3961c33b6cbb1a494568a628641aa724b49e039aef53eb0a65e0bc6ef92623ca6c748505defa9ef7918168c3f1593e67d1924191f86ffbb5dc17425cad8e5fbf95e470943fac0b2896b024aecfe331d6a9978ba2f3f018764f99276e37b59bf33d194c9197b8aa03da5ea49006a2c89bc316ab75eac08b7547ce334b9e851f91eb7be1a3ee06c3b1e7f4ae129f7c4adba77567b1e4c69cdb4c1e2d9beae532bf2872f6734d7e9e5945d80bdca15b01c1de1e88feeaea92d0e4f1df0823bc1ea57b6655a8bb0882247a74839514263372ef77d6060314b77b99af0f3852f4296d6cbfc4eb418cb93a102fdde500c5291962ea186e372c5105f2c086d37f749c3c83e50ce4e6f289c28f70e3766e1f2bdcc0dd18e18e1aa995778c0c82b024bf3d4940f53ab2223be47da15bed651e80e390ba9c0511c60754b17c69edefecd99545384696ad0416ca64290ef5eea972575ae86d82c719b26a27f664bb43b4346f0036c99fe0816499cb70c43410a84760a7cf5301b9f9f4fe6163c694b56416f100a044fe527f6b7c3bde4452d3044825fdd7152aed4f1338e82c57224be4c843cfe0805a0be775993bdb58f83fa3bdcfe7687da46d04584143b7df0a0f1c928ef55c455c14a2c81853cfc6ce5d6eee85eaea511841fe0b41fa6e26f709f5bbfaf87e5aac7497ac220b22577b344d227090c55a2d6f27745f96b8f38f40558dae62ad89f133ad6bdfec3cd3a8cc29a3b86061608c0166dbc49efc107abc264ed3ba5098d35ace4c767d8502fc2ee8b784e2272bdcfea287989aa44361854e479089d150fcf0e1960f4666ac206174a7fc9f7d82c66fc5c102131755eca4b7c00e56977911fdcd92d4d04598bb6db3bb4a1ecc2ef25bb6d12a90bd0ec220470074a90adbbd8a7c88eba28b8f765b8f3a93e77df807ca5dff3999fe358c01e851eb0a923da69dd5bf7c45a159f932ef6e0283f6a5aec5a29357b64294f14f81f99b0297697441c081b03fedbeebfaba9dbc79a1008e526dd4ab70f1f19a13f941ab188125d07b2514ae1ad986f4bcda10ec51e5d0507ca60b5e4e73152e553a7144d5b83a6255ecc19f5dcc78bd7f360fb89429dc9b48358097d930c8561b2bd18dc0a470d1d6fed0ab912e5dee4bb6e148c9d7ed18c0027b7f9791d1ba6fb4a9af61ae8ec5064189f93d66fd2f2842d0c57856cb6eebf6443e12fcfa0158bd40d1403c5ee8ee9e34b2e9de20261fc222572a0e3e46d1f722fbd2da09d4df2edf1ce6b8a6df95fd18fd1efd8e7e371e202565670e487bee5fdf5d94c7da0aefceb8da882f5504477e03622b0edd793e1258b4c9021bf0c441113d90fcbce3e955cca416c1f04162aeec40d06aeceb0b40179c9ce468385f11b9fa3870217202bc80cdc824585638f0df3d546852976bf18ba7487ad65ca916011af3eab2be234afddc081f364ab08c04e320d1b785476fdc5c358d0e63899a0f27283417cf35486b593d7b3226b1c984b99a6cc5bc88003143cbe4b755e6e30ba94114f7ad1efef2ccce00f3f125f187472b03224414edb2e573497a3baa3a1e26a553fa61c8b4b8be257622b3f34a34163b5c7625d57e89c99382ff1cbce77028bcb9c9f219b2e8b7a9a56675031db4ad33416a67b2fadb789558ed0004322836ee0d0c68fb3fa83dc255683e3db12f947978a51392abd378df93edef6a636ed9a3196acb55a520dad84dd0168950cc5477c9d0315fd79653dbfba6f2d6c16c9743a38c240e2a7a1553564505cf40b37494fe93c700c74f90ef57c110309547fb36741a7a1017db769c386f14f7ca0d7c37dd95df5dc324b88571e55260272a8ae454bbd642b46d8619bbf74ae93602f5ca307f805e123d52040fbbbe4ebf3bceb60a173f8c4858cc33a685b08dccb966b4bdf8b3ebfec3e6d4faea5fe3a5a24e1260d7bdd61dc4d152c3c04ccbd45b575d4da97feccddefd5dfa1b3a05208892611ca3580879d81bdfd851288c950f502da73aec49c8b51e06d7f6599d7a15230c0722190ba5bc3440aedf4cb658feda8c955202ea3771d1395822c394f9708879e9fd07c5e999a4df0d13be33654f7ce2dd99cab762399e5e464880d0e6c249128adc2b1f8c97f123bd9588e9042bbf9748b94b9990360edb690f993b8932ef3eb1658b01d8dda573850cb2c27dba2a139e578d760b90a819892015bc29e9016428433154139447392e2142a5172345bea71e9938196ef4806e22fc3a0f0e07eeb51c25fe86e360dc817b42791b8f98012ab376c503f87b79dfbe569f84ca894321996de979f377c443c3b928d125ac42f1c12c071158c46aa69c2cd2ce4510cb336113fb33eb14593fa0b099810e08eac2eec4e48ab358bc896385bb35a623eeb51d1f',
+ 'f60c89481488d65c26a6ba364c5d602134ed1afad5c2b037609bf82873eba67d907f6609cf6228c5cffebb18f2839e55ca8d1386ce0174685237cab6b65f9caadd1d05c2e07709c004f129f4eaf635bd067a624e52de9dae44141e02fd033f0fc32d8ffbb18f2253adc82c539b7ece61d0fe30daca22d0111e781a95b1a038b32bf62b3ca9721b89b3ccebbb6de31147115fa5b39c95b79ac8ace6f63bfe2e9ffec1020e30e79b67fc428fb7c8eca579afe6e86032fffa502af8abad01e5cad922d63cf8aeb74af771753fcc146333ff94db2269f328413c2da391436d1db46b817d00838baad240faac2484b90e62dfa6867a5746d83364b7f7bf3fe66d935c02fe76353088d84a802e66661fb5db23cf2e19b095d678d2b13a5e29d39f10b58ca0c7990319071ee9442cfaaf2246d3f61d26ff47ebb98e04b5958a9e79d27d09ad1b346d2504c31f369ec9cf1c4af50399cac9c4df9e9e96b08de9aec482bc606b9990d7737a41084ce65ae86a8f93ddeb2d98737ace0c51736dce6b47c77b79236a34a7cc0fa18ece293563d5beb46e5b76f3be83f2e01c455ec13dcc4ac13c1cb5a847f86bea980392e3a6257f619d30ce630cc4b120de70884b8c19b29691a0a3751dbf37240d3446eb1b2279a152b45c6fc603b4d6813a12f3c0f8a80260f55c2293e91b57d7a970d52993557c752d499f5209a209ecec2542412ed40e916407370f3dbe8cddaa362a3742bafaefe4bc7b199e24c207cde8bf7886ddc10c35cd3cfb84c9cb077fc071aff55124a621de8c8389e208ba2784f2cbc76c24b9b0d5babe86e31a3d6f3732f875c201832cd659f99483e3ed402063a3b8283d801d4839c00bb58367c3c3f67a8ab4996e4ccd6ae185b05b8862e59b6923ba164ac34d3f69dbe70d6dc48b439dfbcff550caeb48a425107973d3fb2183cec9b7fdeb0d5621bd20c037b7b8a4c992bc1c0fee577aab3c4c4497db897d5b81fe2683ddaf05507b08a5aee7719e6615231695003a885d920bc5dac4b7385706016dc2e15cd55edb48c3b89bdfbbdc4dabecc848bbfb21eef60225d4afd9e1247994a43f3c4cb3a688233e1a2a829302fda5920e035a611ab405615596e860a20ea1103e76f4b7a36fcb29ad797bf4fcc149da99392d84dceed6748d97ed3c49699eed273355d39ca6c55f9cf42f65029ee1883286fdf2fd4c7b48c5d771dce1329225429af56d57d519ca2fc5254fde3c7483211250124575fec34481fcaa3210869fd7d45eef9ea3cd51ae11e51b56440ab5ae04b14e7b1266cf54bb140d03ac81432e0818bf37b8ebdb6003f82f335eab052185a50d69cabc533bad436d1dae8bf98f5fa947eabd7a528ec0e5f53c31605bc1bcb6b8d54607364281103349bdaca941019bed815a839d807334d33b66309fd7d26bb5882e4f62b15c03c54d81ed3b8d15761bc2360da47e426e33f6a424b9abee4c6d4a299dbed068a58cf1a45597bbc3c03877fa204fd0551a7c379443928539e248e2dff832e6cd7df0ffcbeda6133503ac664dc486bdd5c016c4de9510addbb4af1af968a63db3145a3da0bc4edc90c6de58d802318eeae9696529221c6afb4b08b81ff5021b41f03075cbca7860ff92381443e1388cbb1cdde41df39f06c12dbc85f8a82ec99f9bdb6df41f0de5a8ee2164356a83fc71e1da08630f7fc756e434492d88138ff69dc4eec2c4f3b4b362010e56a4fa2933c0c898a37ea6f6e2f8d7cc659d97b46e7c52bfddc01415497641db4f97865a2a420f5d8449fada8cef0cd549f0a3822ec2ecaaee1b564f92b34793c84b0dd67eee171faf7765bd7d533d2e8188203f5ff6fc759ddda3acb5a07cfe68c978e6564c48a5cc10cee5ddabd19db46ef43428a1e9e9910fe272dac648e3d818b0d2dae50bb9821d1c9bd38030eb8aa809a9b24a1bce73b9fbea2bab140cc0409ce23440c62179a439278759c2e0ad4045e62479faeceae1d55ccdd9463fc64a235e89e6e3a65ab00cd122beee43c23d9232a7b7ee0a9f915a10ebd84845953d2f8d045dc810cd467de1cc371fde74a8d7d7638a5ff239d0714dab2e80e6d321a7b8ca2fdf277970749eac4dba776888fc1b7f7db56a61a7025d356558e929fe722706e38eb30735d952eb8728d749e5e8472415583bbf1cf686b20536183edd7a22de023f7b1e9e9443b06ed1c6bdc885a279dcb02dfa3a33114a948439bbc5a6f17a13594867b18eb924881d0b6c38ba76a7592607bb06a246249ca91547bfdedb039e44e28a78adfe6f607570003d6f09d5142093a98a2b6c69a40d748bd20a29a12c67112efbda59b87b3fbb1edf8ca52c9607976dcdb7cc4035181e2e0613247b442c9dfb41fe112763e4b57027fa90d46644dcaed72cafd2d632dad963df11ba6efa4d9b52ff6fe69fcd269e1c9f12f40c3d2a15b1745b470fc37134f2573b85323aeab336c7bb1ab761d650c66475b4115c484a627519c66833ace6ba63e90519523ceef53c3dee33078c2d2e31ff3dfc0800f4f21930776aea5198c3ec6262aaab0bf198483c889c2912dd5beffab755a818bb6a46ea839c5128f116d8efc36ead1f6727d202107a2e2e095584ac4aa5a65949d1e68d1286cefb9aba11d850ebc040f6a8c1d5af537ea95925da5e1c72698b432fe0624bb1ca903ab77d6533ab4d354625fffe8cb1814a74d2d85758475976548c1cb157a4aeb07a1606de9136bac700a573c4d11b9fdb036465e124a9dee122f182c25ee7e02302de938a951271becc310571a31a4022ade46b42a82260965565fadc8703cab179170bb264c9d8e251d09d4c2c35cce91b59d1c30e39475b96f16c48fa7fd22288ca589e29e359fa388a239c94f4abf6f4cb7fac7b77fbf98d9cc04fe0e3623dd6fe61960ae65bd028677faa03df4511e1435c451962e438e16ede3761b8e1cb0cbb0ee22f5c462aca709abea07f02048ac752d510846bdf3a6fae48a485b794d10947c9c1cc81d1c6ac5a1f935b4b96966248a68865f6b842a819ccd2d24b60cef1f494e8bf768dc324a81705fb868eae1927fea88bef899e200509122807f7aa7afd8ccbbc8c2569de6dec1ee81db579f6d5269880bc1bf9df5c488d0d52210ead4af6934b2b6362e48fe705a976d3a0003b6681ccc736f9ee04d16a0c94a5a290c41d67f4bb0d533ef850e3c6fbb0ca3f41058bf576a14654e7ecd2aa364c0d2e51487040a4ece7f28e6fdfc81213a0bcd04ddd8533268919ebe1ed719edef88cdabed6b64fc8c40a3b8b223625c5c97ece84a004658b6a46faf7e2e835230ab448c8c0df2269928de5eed3989b8fee8d25f3fbf8739990e2fb78da9763e2ecad81ae56418696fa8476de1b77dc37b7f2bbe217ebc718a4f5d5aafc0791e5457f6fb8c266e419a3f6d5a422595085cbb47a991b64c8d04d8872d671bf25f2be33f92e29d6a2b837ebea880fc95f43d3db485f30ecde8934a1b943604789d2ffcf0657b6172a3c5b6c9dd10a4c713776700f7e7e0a710a014b923bf228234daf5e807c8eb3e26cb97fd6c93d6cee2a5d7ab63c2c46e91c5b8be5044fe95d2a76e54ee5dc323412f92f7db6ceb03ee5300125e26328af87ea6b9ae79e129e33fe6e58dac61a87f4c3817ae1e5a0c92d960e44b74a39291357f29a2c082c4d2eec0086a374f542dcbd7fb592abc5f7cd37a7fb050a00c0874a28cb1bb4bed4e4eba4c0870f4acf909230171147a6318bbcd73212ca05dcad6a1616fcca509a1da161eecfbfa295d8e89c86a5e2ccdd31cda3d128b3d1e64b60c36316746a07a0b63ff8c4ab84fc7e68cceb97a4bd65851115c08dedeb442ad3389bb2d8958337d346c6abfc786c48b9c72f2fb4032f503134e7899fdb60126c7ba4181e5876a8a07f40cfd9064d00839538b53b26a559d4082e66f12aa1cbd39668906f3c48bcc4f14f776bb7076c703ff07160ac2d6aa39a7e6a0c5f6e1caf90ce62f3c8613be8a4d9eada12025526cc3eab4c1f314946f1bddf180231cea972bdd5d1842aedb3a1c7714da0a7824d4409aa260618bf6415d50b3c9c51a968431f3232f8099aceef0254ece75cbd8eb03bb717ea4a94a50fac37a1b43675950bdbc13dad6709e09671dbe0718acc9117bb1a47522f90b3e62caa6fd248fad7d3d9ca1b2aad03929ffb4e51c69a78fd81711bddc62f7b33a23caecd7eef00d7a20195f3f741508c118af4666cf2897af6450202ceb21cd04cc389e341934e3c803b6aee9d8db875f4051648edfcc7b27f1f16ddf3203c30ebab84daa29cadaf756f9034a3f703f55153fc1fd11ffa4a23e7a097bce1a3fb87dbe7da2a72e5d88a8550ec91e9f8b62f5eee129ec2e9edeb85e857b25d19da293bfa0a5f7dbe5ec665f23ef0ca605d960d3449de4f5d41dde136465bfc6674d7b30593cb2500ea32f7be53460203958e4815ed182d1eee04e4d28c422751739b2b4f9df6d37223d424fb531ef9a90420f7da26d5c4f49d65740350ec44a47c7d8b214f67edddb2b6d2f366fd161a525303cc1b9c7ca7f60a7a13bae3bd253a84bbc3d1a6dd093ee0eaecb26dc31d28305230f5f5bdfea0fadf48f3aef89d52b8a37d4c3b09be7058552f62f5687c2ebdcd51db68efc2443d89eb23d55fd36f212c97e8afdbc4f028f9049c1e6bfd0b045c76f67e7abb89dc624371e0589137c83c58cc88b9e26558c0b2886e3569670de8e66ebec6b118de519a06584d9d52b4bc0921510de277ee9183506f2e87ecde57ea6ab13b6c984b3d360ac5a5c069ed95f20e8f3e8acff453bd19a9be21408193ca12dd42b3b22b1060a4adc8c2247cad5bbcccb4a31571f0a90ea34efd0bfe82ce8fcc41500e87b160fc9a1e6b2a68ee8c528fd7f4f16e4f8f54e544e5f65b0e3e0906a2095366087e3b130f8324b93fccf9610f4709f258b716f70a9ea8a21e61a711cba729d1fd0289a103d1b9788765d9c9c4620805f3687a6206f3cadb9980927726f23acf78330dca621e92acb09d194f5e0e94af08eca8ada2ab040c69e3f2c019b12b68615bf0199432565da1bde27b451355f97f89c960458dfd3f75d2ee720eaea7a4a0c0591301d5ae93420c5bd6e139f5ea4088050356635e7ba595ae176caf548c1daa714211cf42577bd4ade79b48198b3431831f3ff3cec9e1735b369b7dda27c163602d2de7b9b0345bff36d9076be241cb6c3b6706f63011b61d5803e0e27e72324d5cbbe7bca755eb7a4c043f93fa50a0e7e036637eb81da41b040c9f1497195e60582595f243c6ca45b085b839d84af61d9514d40d69ed867ec21e84177c9f30c3a8d1b9048c6f408aeac23a0486151636e2691f4b4c6aa36d1aa1559435eaf09bd4291d998a39976e83258d9c0b9b48594f9d0de2687a451195fddb32c8e4e6136593885a460a151b021578aa6fc2d3ae65186677c506b292ba9ade9a3f745950e279c1eec7cc04b9effac5d6ba74015eab43f2b56cf7331712e02673a181937dd34291ff1565aa6fd33b3ac7f5981891aa847841643310c824387a66f7f5dac0c056c03239bf50663542a0262816616ee844c2c6b5a491ce7808a31b48b77741321d16313ac03dc69d76be9a59d6241eefec96ea6831fe5196b96a399cafb229b1ff5ba42052bbb6933f5ecc92bfa9c947e2bd5584c19e7807b49569fcc5d2a0c3645632f45c105ea0054663dabb37957690ffb0113eba6c5260ec526ee121cdf2b4d084bdc585e74b303f083ab217ad065c23a3188f9a55ff24399dfdebe5d9c5579142837762791d281163b88a92377fb0f3d59f9c865a966a42f1aecb67ecc4b561134e59ef3b9d56841b5f2cee5c67f335efb14dc6a3a099d0f78b69ef2c7836089f27537456532060d934807343488db298538aa159a518ab569badc4c468434d8f154d38f2c7ef6c44416e6b15a9e632798f3b61a42b519bb7e68030da1d42f98b212f8435822c71058ddf1cf913667d3a6c484cabbcab68c275c879971d9dd43a52e1a04a0b3d37c320b9cf180d755a82f399ab97df8ed91e4f6fe822f2baa645b04cd457ea8c862703e6cd991f7b92f92a16c58f1d62b8747c5bc5a42303375343566885a87f26d93d4c',
+ '6b1d94bc0c6e45fc905c509ea667853e4b2c5a8848dd914efcef14d95b12247d3766b270bfec0ddd453bbe334474b0c3a177958a3157844b7c0ce7e2c06894d4394d3a2aa01cff80f2706759720d78b5f1131ce64d78c69f38b4584e3abe45abf938f291b9e6630e1f6513b63a1a233cc468b743a4269e71b85031c5d2fc7d2b0090a44e113380ae54818af2a383fa7fa0de30493f4a53e9854638f18f0b857cd5be1609b0e99f891a2c93b6b53045a710dd4ea125cd2e312600367779d1a5c5012811699ff208c6f8cef8aa79094cdb99cdd8f35e95776e23e4f2029837242f0a385c16e534038e77cf7f75f6f75644c51697e6f38c76cb055c3638f5254ce17a55c1b98a99d8091d98f1bf35e0ad091b205323eb99726e52cfc8c197846303d8e606fa9708ce5e758f15323cae97542354d3524ea3b57f95a57146863ab2bfad55f48013682eb6041db57415475d4a6618e11a259485275345f96fcb31813b800953f406a3403854aa972dc89547156bd5432377532b8d161928e36d4f189fd96aebfd78a04c0dec9f84065b7e9cddbaf4c2164cc8efdb6588c64b747ebe1440e0834472479a5c546244a6d8eca6c9dcdb269bacdb1836c9fa9a4ee9a5bcc23ed3e570fb80724e155d9fb746c6ab0258f43759a074f0c8c9d76d95d3ac5ad05aabd72a1c331b0bb6f75ddeaef4f4b0b6a6bdf92f7bbdb9ed8807c73a7ae0661dd0221adc48debabf9745c5175dc9f97f587f2262d8c831bd73308d26f996ae0eab8ee743a70383b8a7211489eb71083a74467d40735957c201b08fa010c4cdb5a2e23a5939d28f2a8eb7730d8536036f61dab2d134b753839a4e74afa7b1ee9a1ee8ba27e492069db4cf88a9135e13a78703681d042c4e88a7d3e55ca7a63746886610b4918d10978133fe677e325f684e89472dc9fe705a8e0889aef6ebd0404625e3082909d3a25daa7b4facfa103d1e33f9086d76e080b9b209ad7dc8e210c2eddc2c924f7a45b0fba76886fe4dab5fca23b6d6dcc7828e9c0c612485953f6285a327b6a72b09e2ef2cbaf4853f3c79177040ee784ce9778d3bd3d469105490b7df017b580e745b4eaf4ddfd90d77d4df85ad9b91983cdb4c3e0a73bd7cd7b34938c3cbac4d1083e0db2a2d40e0e4d8ed0d05c771d20322a2bc0eeac90050320458748de90d65c36c5558c803e00cee08ae50595e23c7b3575de822d5c54877b0e41ca95879f981bcc8df966a34767cf7109739a1b30ef833ca9f0229f347e91587c30641b6572696dac881bc05aaec83dce24f82a96358feab3b710db1d3f0fae7728eecef041bc3331a70437a31a474ba3783482d4b3b7fa7c559c82776f429ac3128a04a89c70b7cdd4a45bc920e9251a0bd3d6950097f6744a1a37eb75d687f06bca7ef6f91355d19f90bf25590a44a24e5a782f92bc693c031e6de1e948008fb3347073ee30b7dd764dd450394744ccbe3cfa3ce071cd241f1d96e34ff39ee1773c9ba7c2453851f7302dd2381c8009e9ffdf2580649ccd0c9c35780075ad96265752fb3bbd61cf70ec4e13dbf690ea40179971e90142a74a1eec08b14ad73e5f1928f6a125ceb691d697adab61f1de6f28abba90e46943661a0d2db8ff861a7006a012a90ad9a7c883acf81ceb1d56a5879867fe6a7b11cf122b5fade044eb07843ae7a9d90d038377f09ba6fe9e03a1d8f1f2a82ff2a3139bc90706b99e0094366bee2a1ce35a613804f0d018de35e2711af324816a67a21b58bc39d7ebfb9471b58ea042f72cd0284ca03ec6689dc604a5d1da2b4ce019257d07ddb7d94c86ea9a41b2f7b2fb6edd5a123983c77beae815275f7a04acd72e884258f5c073f9e5acbfc887a1367bdb9fdf56dd7580cfbfdb5c1afa3c1e6abb1d24420e4bf25c174f51678f4c7ea58790602d4fed2cc2e07af8a3285dc5523fae061f6dd6582be4049ef68b0f347a85de3d1337b8d082fe76857e70e4221c40bd604790fa05f7d06f011be773e8494acbdebf31432c7e6e507b038059b52742bddb6114219c3e60e1204e41150ab03b0bbd67d9269123b49b51d8c3491f11da31dfd263d78b6e19fd54e4046d2c3dfaf061c38bcd9f0165a99274c61ca04a6bcbd6420c96440560264793cde6cd50ff2c5448b5c2b695f61dc55de55ee96f7bbe57067ae856a2d80e50d3ea0c5e87bc121d7e0380785fea6a530abd8a6acf8ebbfab63b4843b4e5f8190b05586040b64425c9e1a134ddb711d3f1bb29a509193709c7ca209bca1e75f8cf9c55631a7224f5b2cc8fac8de0a6b0a97aa7189aafa23cb1b42bbf30f62a88881b8de783744715df40a62fef0b8b9198589d3602b69fec1a65a43ea7e16830db0f6c6e0a312cfe95daaf4f8f7c52cf54e176adbfc28290587b348a9d74c19a2ee46b572d3d49287cf6e57bddc77ee255b1cdb05057fcd1f24157336034c91cfa3725335afd2e1bfb2203355881727311360d1e32fb61298e7486c9be95e141c374049312054e51a6f8632c9e2ecfea2ddaaf27f60141ff73713dae070caca1212793f6e0ca1c51f6c69fa20d1409cfe23c4e6e081e21dc47b3f660b82d7da389ce5dcf55d02aa57129033623f5929d04c74560e7b6933881d94b72606cf6d163e4bc9cdf9bfbc48c9c586981380cc9dc083ea1234ee8ac984da7638e3f8aa355e74aefeca20227cface9ac935fdadefe6b949f396bc491218d94a4a039bba1c66293191b66c057b0b7419fd8c18f54d28dd1cb9881a99e84159dc634dffc453bc30fd8da7ed8ccb667fa83c0219cc40edb90a6cf14fa2d56721d38ba96ef163ca51b8fa84e9e0f671b7f18f7e47594f9e13bc15bba48b60f2fad8caff2e69d3f6560be035a9288496e49cfde9ecc502a1b4d9bccd617d124aac93293788532baa059d48a1758dfb3ccd518f294e37e15f640a670fce9f71d7ffabaf3ac4d7f44d281739b29537e5ca3bb74c27b5d87540eaf72069321a0702bbc965ee2a1fb5478520bc4f504dc9aaf6763ccb6bd045ad1047eec3d7155c31a794513c0250f6ffe561d860eff83043c63767feb746d90c9ca3da0503b9f6cd8cb57d0c939540df0f8195ceba5fa1fd73a2d6a6e062bac657b2ea17ff2e5f3f32d3f1472614194ecd29fb7ff9aba440c45e90ac2f4e6f44091d28e1139b1fee6197b4d303f50cb4e501135cf403c17482149df9ec2f80ed7a8b9c4c8628bc41e1a8cd9a72619e7a20b0e44df3908101756b06745f30762871a54d3ea2ffc0a279b04783bb80590646df763b3d796b50650cd204cf76d85fb81343a0ad45b378f35427752579c4c11f35d20c1be708c71a9e0993862c2ef9157c865bf0251b2a153e775af9756713725bffaad502decf5bcab408ca78015e51881f55b3c58222a8163f19809bcb6509f805fbb3f177d1f238d94ae482d7f534e015783e4d6fc77383859aff14771daab5ede9fcdd5b33a58f83431c733b28852c70abd8e66b81402e53c5c107f3a51e3abe5bfaed105f3d77b7dab973b25e0e1338d5cab8fdb8d5bfb0820d9dc8632d2e8038f34a20bb829a7ff705a9c600953e76e05deaadd4fbdda5b92afd7dc19e3a3c6a301b13b13b9de282463efe74e35532b3d6a4033d7a30385261519a253b05f9d8f9896fa7322be964c55ae223c0ff72368010885c1a617335fabea8f9ca38bf6a96bcbe072dea9a83ca23fb75f3e44051a253c397a3185e4a3d6e2ea4147a96084edb8738f582ffc89cc4d0d346ada3ec83983c57dcfc007a7189b48ee174879a6a0f53a2529c201b8563eaa37f02a4ef6c057c058eb661abe036c21ff9cd9908327fa9ad0be00365cb29cf4e678a4942a5c20a0781ee89c6d09ee1bf232ed53aee5411c1eaf5b28cb73d093dc6ee9cce76f86ec77d4b81b48ae998d6293f4119e6b51346b584357a91c720d764d6a57927cf31ab4a75505b563e70ccd1a7e88b62cb38a435349215a1f19a8308e86b2cff6eda1daf15fea57bc5f009e40fcf79a9bd074332aed3472ee101ab7ccff6d047fef18476d3947943a8a0f5291e00cc04b4098c749900f782bfdcb686314e4b48a6ff48655619bef40c08f96a7d826abbd8c4e5c29e1f0de3b4b40cefb77c87478f8bd04547955ed71bb17b6dd35d3d1650c2b7e5653a709ae810157e08876a8fe01d86dd4d104034fd22e5fbc9992c5c458b6ba695580aef827838fdcd77de67c23dd1f711934c53dae39ddc073f1ed7dea567cbd682c229aa74e032ad54ca165d7419593a256a7bd97d0dd83457fdcd449a151cd2bf80928a8d72e7512715e5d849c7cc71cf82f622779bb7f8e7b0879ef2dc632e172c70474a57e260f8a91c5c4541185e987c0ade59b140fdf91f4d153323669592af0c0c34f7ec83cd6d0310eef005a888400035acb63ce8943267d1e754eead39efaeffd1bacc62b049e11dcf3c5bf8a7b7204ef6127464c1d11cf995d6c31e13d59e48ac094ca4a82500aa6d8ee1f5fec7b152c22cad1787e4b5b6c611ad91284923104a4a32f1bde9a6e0524c60489459fd68956eb2a9ee537a2f016d74ae8134867f35e747f3f87e1ce705e0a19871564ab9f93f4ac3fe06b38dce52df1c257c10bcf446534f60caaae60eb0698e9ea4e16d75073c0dc0e5a8f7b528b8841c0b06f00fd11eb0feb69705f62683d2222d0aab922f512e3bd9a1963f57c58d6fc7b3420eee6fbcd82a2d6e43a8b60b05d70c9bd61d51cf77c8e591f347cfe0259a5edb7a070f1bf90fb24680a0c9e1508c3166cb3a04977d9cbd115a609d24466b4ee2db83c776664b6fb8327585fe0a33bf34f9ee312f543b713efa0bf902db21cc80b7ca5d7528c8ace9e38fb2073bb4ff2ce7a2a23e048f493f5eaea922eba620dd984499bf486cb1041a5618a7ebf739771dbc1d6d967e8c3d0bb3762b7ee19220055945fdce21c52f60327e8423e3b53c23a66b6411f2845f8b8028c69cba6a72877127d6c0ce31923c8bad46ea62baa482c654ca7dd277ce647f879a4e19effaa5f409f3285b3e5736c96e96495c91d7c1869f47e306aeb121165a509be66ae1e74ab1fb0daf31a3d654871c47b783fc684d16854a75713147af6b8f8c09234ed5dbe79778b455a1a594c07fec5cd63cff827f29ba09087660b18000286b626af80f756f7051f1762af2e3674033d2be0f8fa3de3ba9baa7f484624a77f26f5cd74f222afd6e4c4dedd4b67e4c24c1ab1fdb4a4ad63dd0ed8990d9168187914dfa01b57127f4af4c77a303691718e92ab8ce3b1592ae926fa93cad0c4f131265ea8ff7ebf7c9629b4fc890d3a9f5946461d040e878e24856dbf2a5d3e87c38cb2e5fb04174d6ad63fda925620ba50880a2491f6b8ec23ac9a81a8a1454ac4ca84edba71aa703d8cc0ab08cbe440e8da703a1a145de36b0f1961a24769c899523d369a61f96392afdfd448c905c1a6d010d408e1e7027f1f52f5b3ba2c7fd5d65447373f84b5eec1f5eccec805cb0eeb5478faea04a7b46fefb45973fddefea962960d74cce5b6759b6b275354bb75aec3af4c971761cf7348141fff6e74686dc0b989ac32519e0d48c5def583119f7cd6cd8a639fcf04cbe49b53d6febec7762708384065a7fa2b76835229017bd0e8167a40ea1e2e18cc5db0a1751f4c8054ee3895dbd7574f42bd2a2d586048be62fb3bec95032d60170c0a9564507c27ac9e912ec907ce21d58530cd2e2006bc900d6909ff0f4b6f1e87ff8c2f22ec21cfa0c86fa2579b0666572dbdf4b1345fd1c5a8058262e6ba554a061220170b0350ad34d4f2773c6177bb877c5694601cbaac7f8bb9ceadc65ddab4b3f19d6740b20c6cfb33c730c78c1ac494be5a0877bd2a35f91fa2ff179ab291ee2e366f7e6656e74ee0c806ad060d6b73b6729bf55be781da2e7069228e5a241d1062a1f100152a5c740a2c8697fbe2a5c96ea92a193cd9ab60c75a7bbb49c1ed52b2ad5d01bfbc80880e10e8947ed0b751beae6a67c2b3d951187187a3fa11cb5fa6d026ddbef477773462479e0eac04f9d32a5ad9f1970069d41cdadaf38a33b1afb8c306ab888ddc2d8f281713db3b2c5c8b5fe241f9bd358adaf9c2bea1b2d34dc5d61f0def50115a060e8226f4a653bb600e134c524c2ecd2a48fdc3dec54ef195b4894e7f4ad12a457f81d07ef32a6046c9ef794749cfeb895cde9bb7f78f4b8702c7f5defa764eebdf7878d09dfafb8e37c9413f32839686b9f7bec3a61eac48357d9c74db8b1867c2e8b890035fd',
+ '56ee7cbb745a2b1f3a77c8a9bade1e4934a08645e7d05adc2742ac2ab093384b3a6998c34dfcb71d57d688d3fcd7f86ead7b21ee7c60c06c2e02e9fe92c9f9db1247cdc088ba319253d99b44a9cd1afb2e7d8970c60e0896a8aaad7eb5817677070e8279cc9c81c455086ac46ac86c38e12c26936fe41aa2bdc35f70bec39767414821b7c2a990fc86ec5b1be7d1d56cec13601f1ceaded894d4699d589544f2e77c114212e944810d0cd710c7ef40e177b7a0db77273d80566532cd290165612133bae26364869d13f0476cb22b92ddaba24c9028a31de12aff22c7d90ee2fc19f4845f5d233f96c02111f7528cb4a9af5bce06d76668443929a15511c4a30bf447d780a92d55b53b269f794c1a8e8dac6a8c052b7904f1ab6b173d792c91d6ddb41b0eef8ab242ad8757ba4bdd08d0ca58f055cce3db30a74dec48b84d92a5fac4b8d2d6433c853dcda381f5de0bbc30ab60bd63acd3495558883e77308fbf73fe1691075fddc5147fea98f2247d9707ee460798ddd683de1354e75ddb716d71eb9d162ae67c4c592593c5ebb748aa76a609f9127ccb286a06b60b13581ef71c11b628e6e10060354808d188a0523da65d115c7a94997e20ba177140af80a59bfac78ee357534ad06fb08470e68598bf9ac6f7772a2ace29b6e0e28de67ebe172e3a11ca505c525122f3a54c91c702e8e69094b248655fad827726a107ff8cb733757943d403bfe1e939fec3aa02c549c8d7f9d2f5327bd3a746052619ed55a493e28ef5ec30dba74dea557e371d270bc803ef803a7968c3acb347c1252f6205d6e717899fcbecccd56e7324430071566e7c3e641e226fa61928b31c32adfb0382eedea6f6bc3b770a2e205ed393ea5143aaae25a916d8a4770c12df484f68c6215ea9f5f7eb10d1729023d4f1bedc25cef6076ce33bebe44dc72cb26ffdf0753a9f411eac41b30297fd462da4698df997c610e0d57af08f23c1c93bb09fdc3856196ec81da46b3101de4f12f57c46f62783922a86bc2ecc8261b125121d7439fc34f79b51fd81863699072aeadf4f44059656f1fee7eec4f011ab169e5f009db458a64150353d2ac098361e8e7af00ab96521de46e4789ef73981b65b4dc88f07ed415f2232e7452741a495e6357171cc5abe99659c14831f3ff9af51d014357b5b91a0d0dec7fef1fe1838fcf919a0c5dcf3c963f60a9bef8cdca688ce2bb79a0a5ea96020257ea8365cafd7ac32bf2c528a1649d9a10def88f1460930563eec627025dddf48edd35036a17f3802f88a26492a1e2f2202a79b0234dbdfddf2f8cc8eba4c057972fbb66fb6fd83d27166bff9aaeadfd5a1a02ed51b06c208fa46ae8210b7790035a7cfcf975877e95248856241b5d1021361a734a193c01e7e5e7112d02523255f5f031dec98fb91c4b8e04b9588edf17f7270e56e89c369a8c97e07a29feb3018acba235cc6f610ef3f387d4e6a1bea0e5cefb02bf0b22f9e0e3c282d2cb96887145ca790e852affa64f5c115644334e8a68e5f80d074b988b5123f1ad47516d52151723009f6b60db07fdd927d4d1834d8b4b81432f34f8f466a12109f0782295b024ce32eca6d52aa3f996cbd21de1b7bc885901d1639db4aab6bb89657f4ac49d5fc5161b61beeda9bbf4b2ca7cec416348c9b9ac5be93493c10bac04eb48673334c23a9a20a0cc8fc15d9d3c82f57315f96deb0e827dfc987c9bd26d209de618502010f7680d2fdcb65bbb96a4bfe54b16a016768bce854f36069cecc3ddc9c31e792991ac668006e759ce67668df0892d2abc72bc5d671e89b68c22596aaaba1c37f517624f7594f0d2263d5cf22d46a9484d91a8ad1a9ce445f0ac35d6d163b5e6f3f73e1a791d4f7f5376dc8f041a17935a41d5300820e21f9ac90d95066697f0b6af7479f2f67fabe6a289a71d69f4966301d678b860efbdcb27354782292f0fbdaeb9c08bb6fdd2505ed8fcc1bcdccbe86000f9b3d3799b023531410d21d77e697382909bd5469d10fa3ed2a9891c9e6adb093582ef1c51e081a623efe74dd75aac436cbbe7d666b9034fdb2d6540a86ad133be9e1a545cd68af862a2ffee94f1a790b7646549c9a9187078d5c7f55458f134e76deb9e2674bfac8feec23651cef3eef31db40d207188453e2b27873cb5a971629ae976491bae370dfbe4b178631fa2c2407a9d25a301328da61a92cc11686d928d8a593f5bb52c5f72cdec933e997087851f7623ade1cd09df07847cfcccae09be44d373b592f106ebe4441cb1ffccc5541934b4f2c879152e74f8e9c834a73ab5af6170b9962996cfce648f7d911bfe260e35442213cb421cf95c7d143eeda7fc307fc0b85d3a8186f2aa55f21be2b48355047bbc52480332eb2bb15149e5c41a7eaaee6246b859a9e7922aafeeb56ae4943eb6121253fb08f0e3211ff4242168c06f5427e10781b113c85807980893d222207dd3c2837c07711f46d0bc2d528fcc399c89cabc8c351d9978b98650c1d4a5664d61f16b2897023c8d041065a9f7e89611f8525beb9e5ba2070658327754948caf5d0d5f63601ac77e2a7f71ca39bc016ef4164a2265545e8dae8c0226da55f00e2594f4bedb314ca4c487b625ad9d080e2d952bc312ae4b66f2805442fe802d044c16afbd60cc386b6297b73efc322679e54d65303ebfe53d72d3bd7e0999b00c411104d1b962176c754e09888de1c93cbbad7b0c320fa88bbff4821ab41915909502888e1d9976128d090194eb427cc12143f1c9a52421bc3a7166bb6674ca3bc869af5535c8b0e81a5753e68ccacd91b69f5ddb0083dd4962c8bfc299334a684bc4edc96a7664679dc764ffeae11838dc1e13b2cca537ddd96911e2d4eb1290af92b85d54291d1b87b7a933ae3c0921024eb803620ecdc3fed0f17ea1e2bf6f5451b3736b67277e78d6b5342f21fce4e9bfbf2f89370bb653aec30878e0ad3eea8063be1897300ae1730d73e6398feec2df9db97aa47e0f68512b12c23b6899d2bd2daff46fb33b423b16757bf91ec95e3cd813eeeda139bd468e2036797e0a9d56859b87271d9a2b8d64bb48c745eefc35f2d13e58f1efc31d60a31e1159fbbafb00333973fba1fdf87ca7f55a81da2d897263e19cfc8c0a34473e11999907abf921f993ccc9ed49fac2ddd9f95f157275eb6e81203d890339a63399889798f0ff7f411ea804d68002dbdf8f4a3a1b6ba9a233956911713e7c02361e54f23a1a7c5cbc428c38e840af2bff8db3af7669835b6586fe34df2c99ffb82885156598de6ecda7efd2bd0c6e37e05ddc2b910c915b76ee55f41d4e11d32c40af9d95fcb409f7a7496eaf33be9414581a3b05b56246ea23e6fbd623523124ef2ae765a69bd2ccc93f6949671ea4fe34f45a4f04648221d047a8744d09ae149c8d7f3f26d44daf96286f16071cff105cf115d76a16b1d06bee86821b768e8dc15de19d0e1bcf3296aacc8fa42816fd48a6da2966f74a586f52028a1f4776f305f2ab09ccc0fbd8331d1e20bece7117707c23cf8b94b03c5308c8f6d8dadbe6c312380fd37248130415e083c78e81f16ce79f44387d87feaf793652d8df41cb91031509c29e3865822b90035ed6ee0db68f8901fdf03601ed29d7188f933d33f86bcea21aa3416215a4c929d53609dec5ca9d836cbb4f0884a127c27db5229251a2c1433c2ff77b6737e881db3a59e7132f3962d52d37551e65b4eb23a38679455e9977a7a2cf23df5b6a95c14272b93733d90af7c4838a02fdeafd01fcfd4b3bd5b3ba0d9a3cfdf665ebc037ecd8c79a6f18f6252fb819eac04672991febaabdcebc74ae84d56c350dd8ab9f97f084d53765beedbd49872e5353d6698ad5fa33aff0522ae10cf123a9bdb278e25e8340234bcfc20bcc44662dafb2f3cc40cc45d01bbd033d581f3ad0076f444a875583c937a3427994e3fac59672b862c0936cf57b2fc7f85b58f307425c924eef4e188fb5c3dfa557e874a1c4a037dcb9e20186bb24d9b06b30d4b5fae4e34e0b0f76fd42cc0a1d69c46c4896b5f2a8a13aabea3836b8e42c3968629da2a7e66f0c07886b0642206f0a3a30a94e59351affe4b69ecff601f2d526a3a71060a69fb672b79b1d287cbc2c76d068b40c209bc414724b6b1198738687efa3b89decf1a121015f6b1d80f59daade51916bf98809e383ba928c2612d014ad2005c048295eab0c349aa1bb9fe03e80e061090e8443ba8188d0d63955444572887268468b41692e970655ab1f96aa8eb1ad53fe04f401e222a07020af55a2c659990646bbfbe3ef134899cdf923874e31d0ed467ebd623c148bfa9aa586a52f2fe42ae72fd0c3d415dbeea2b1195d55ed66255675e253e5daedc5f5cc59d4f357ee36a64b7261643b3ae4b7dbda852c40e7accc7f154f75e25072d08831bad98e01b9e2b3534515dfeffc7bddf9a737e8dfd406fec8bd62d75e81996db0bf382ee9454aebf5f77f9055966d3bf92c0f21b94ba4697f4643066262eef1595aa5b6ef393d59dce0f7e916531a7a5b9c28980e7413639b428d0bf6a71f979e3598a29e3648816aba1546aca2f21d93700ab839ddd271cf6aa754a5bec9d4e1a8879e70acdaf731bb5e35831682aa23302c1ee5e96ff86e07388cda2a6c4de65c08f7010d932fd48d4922342e5a84ad5513187d475a7c09a0a6f11e6a7546fea87348b9e8df9f95a85cb92a4ad851747746517a58107124df794ded0f8374ad168d0025cf3edb27951f66e00c8a752aef3c36579c307c321c35dfc08a053b743e8cb9bf112f46fcbbf797ffd3ff8423557b53bf941901d3df5343235a792fd68918e1f06f2f7777b57bd8d44195e310127a25fcc058866ca158049ff16da6667ea7f55429cdf13afa12a607c5ec3653debf29a9b17d9e7efaca90a10f41538687b07074e17928b902629bf4e170e270f2c0cb40e7d69e8d541173a0d492abc95cd8ffbd16bfa27456d2074624b27bf9f705a75cc4122b1a35c31a4a11d013a14677dfa74a1fe2ff6fe78455f02f675164243a57e6bea898285610f727982fa0dd13accdfc085eacd629afbee4462919c272f0bcb01abba9a450f4228649543a9942f39b9d079a1ef20ec5eb61a555a58a986705c0d4c8a1223a1066ce7225f0f37c309aadac19c87c0d835608bb719734fb19cd896b54213a88023e609119469425d035ce44523bbcbc54a93a06e1715a11657e8a5ce29c45a14613dd24a2bdc3e67f174f9ed7eb3c81b63f860639964281e6934417f894ec4c588194ab56b92b248999d1ea1f4983f414936d37eea8b6b31996372b0a8d86bfc5f247a3b9eb32668c37d5d49ce92b8a1c4f601a851b0f3b8b01e4049d08bd91faf0369c20a66222f39bcbdcd55c8bca29be0f3c715fc4619770cb432478f959840611f33f9d42f05c2034715ce63d2ac989bb0c47f96eebeb3d6d553550b27cddae4a577b125d45225fa0109848a832781d04088d7a6730d2f23ac9444271828453402747b809a7e2b48f592be66567b1f26acfa65565b70b29c3fde5a0dee0f48fa3e149e0d08f19c952b96c31fd3113a46fb0170cc304e03e99817e1e234ecadde623e64f6a613408ba9fedcdc824f9ad735874230790febb1bf291fd16f263a8775da1360fc4dded079b351c5ec9e0682637321abbf1a329cbafa5dff2925b89e7fcff4759305400ca39c6a04986747ef8bad566589d9a0549374e297e951eeca89074f5df2a42ce82da06fbe0e1025c5b81c28855fb3fc7e527ef8bf3812a6b499696c40a1936b0b21a9a71ed70368adefaae133c91010c5fdffe59f9dd0a604badd92452939c51f869e5b497aa6d84e3a3ec8995058a2ce9ee1f261a0f3b761f14b0a9411fd01c96566d36a7f633d51a0c024dc7bf47b443c0b672ac9e3a173489b1d56e149e9b3c5eb57a76fa15e5d2a193e27d34a63cde0658c25a1082ce3c6b510b322844385ae142f28d1c7041bde2f5ea0dfc3221191d7a877afcb205973822ed25b15d8e629c9a2162d3ff732c6ebcb6f27c77fba5749054b7b92447c58e8bedc1466707f3cf2f9990c70ff4bea35cfbd33bf436d3aa3e9c78ec661f57cd007f8d5ded3f8f2787dd8f4c2e40e50f6b0b28c1136d443cd9d136fd03f6be823524f27e5c8f8504173fc709d6d220214b775b927209779af6958a616a687a2c7e70991e318276036262c0569643008792c2f4268c95ee53294bebc025ef1b4a60e4d9c408b2f293fdd913d8e8fdf680b5707b03bf9d08d3c686c38fb2e5025a70f43ce65e9b5b345df539f74b6d82cb47f26890a77957bb62b8ba268e04dfeb13114b2b8fa64072e7599b384a3c35c5beeb3583c0c56b789264283b33a8f46aebee8cda286e12ce4e78b02241d47fee8',
+ '876804e2e7f2b045cb5f6095fca5411b31efe0fe8444400923632aaf48f2c44c6bd865aeb6e8a8d4b9bfbff55fef0ac5205de8f379e7cef6994a96095e4319747ff5ff0e0b8bd600121e62cba1d9348df45b7e80e85c26c3af94195dc7d8e27f2d877b09ce2463503e44f01eacabe7fb16b24aabb9ec71f8cc085cb26df948f3dc5580a7e7fb764ca5f912f767ad4a98ec2af37ed4dcd71e5770071037ee735af328b7f13e58387daf05705dba2686ebdf8a220121777fbcba92c27f0dfe8a3bdcc8c8eebb83d16ac52fb1588d60c27c589c7e3b8904925a5c363308d773b51ffefd5aa747c3689c4d407ef0c8a127d4bd26b034f2ca3780a52605b27d931e8dc18f1522c8622599b1017e2e5432dd7a77f15ff4461297b7fd29de86e8a7ea0d8d45a36928a631eb57375a19175b0bf6efc534846b24d986cc06678afef64427cae8a9844dd8b419a9adb9bafa63dad18c69d7eb4b4827767a8647c8a8ca5264e040d279213fd5ac0d2a4417e5947192b5a7ef31ccc6a60e6f712648cf693c4b3815c3528a25bd2bed75a33ac134f9c1d445245fd8e580d6148fae11591c2b65382f271772d0941eb0577d2b748c99e7500c207b56efdd56cfa7852a302b47384956a4cec089810ebe987af0e8e47a8b91c488902d2ae4170983539e3adeb74ed451e2815c98ac827f0043930384c335ff3507a347dfbea02be9c172617da42f3fe98a372d453e025b56801cafb39d6d022643be8b5592799ba276b08b4f3561f3e644f91dd85f16ba64d891d3ba30bc0261e4e8e1d6892bc3761b60a29d936e5910b7ce1398364704f0f4fc57e1a3a967ac932a31a8cb3d0a2c58888dafe5ded82e8cbef8cfec1ca1c37c6422701ebd99d0f88b63147f37d78ed43234cd5437d70a6bd1b2c3a81ef9a517d21eafcb7e0095ba136062cea2946238657f3f0803c06afa102abc93d3745e6d4d32e6d07604c281ca0edadf04a8712b2a564c28c9fa17e4824804d5c57bb527dd465c552d0e21f7b956c4dbe87b835688a13edc12edd9cf2ff5e2957c7473ed8a435a832a8478c2e72067e9d375606187485b4e6501776420179dfb7fc960665d0f2881f5d0908a5c550c324ffecff32f33eca34a9ebb4a5c977e3156e0443b5d930e78165831f821dbfbdde37e99b4894b3806492c6b29eb5ecd5c8923e71493b75490158622b33d9c7c13185d864e9c76899deb1350abd653d2a7a0f81197056441f07bc12d64b87fd7fc74035e66709d2590b7bb3276245dd43824c9896fbd801ec1d07018b39b6b53bf81d8e9a70ea95508368dd932dd661d379dfd1842c3f4332a9afdac47ed4a3985c745efb67d80612deeef0c880a55f3cd91fc86b91da9c85463cfb1c9b603d175cdb0373ec50c9126694a951fb2129f228a2e9b7bd5862567b24eefe0fe7e63b81b828ae5ef332d1c2f073aa1dd84685d0ffb1e31f372928a10e22e35cea337753924b05302df7c36c68cc4b3939598a6a9cbd9827d5756e504f335702d5a95d5b0fc713106f7c79db843faffdd2c7627de0692177d1cbaa116e9cd38248bc40006749527046356788d92a62dc231490539141297faa906e6ce2cbe35ba1e0d1dc6f3e3dc020b71f0cbe38ee54b8d5a6b3f5d21abfde682dbb524d010f7fbb85475e024f90bd7602dfc9d7bce7f26f17f7a4a86fb8dbb75a9523aca71e30a0dcf9da5202524af6a56a728369066e5556971410b2eb21029772dd476ff873453b3c5a99f09577bd99355ec84d408b2d052acab76ec74cde46ecf5c9a63ea5fd42b7654694dab23f30e8d5130426e76a862b4f190dfdcdaefb6bf38ff451474d65c370e4598298f01ecd7bf00819f1726c0107921adf11f927685d26d4b0e5ca271903ffd92d7a74a58bb9ebe3aa37f929548adf1febc8414d7aa90ac20dffd8090094f57e4bf541878c256301483943408e5f4c77b43f2bf00b5dc836a8f41b7e22ba71c36e97cb174734f1fb8464053426eb56dcb904fd9192e1dbbd2270f918e7dad142f5b085e7557f152cdf46a396a6b5aa997ab85ab4c9eba1cffae4e54bc88ca06781fe167a3f4a593fc96fa2ca9644a44879a7a7dbf8c1d6e9a2ce84996d266bbb93ded1425d5d1a8ed32d77527e2ed06426718979a80ad794aaac4b841e5eafc99bb16ad247fdf5a47d3eb5c0b6cabb6711a45400602d205b82ecae9e849bc8fc0a34379c77c3571b27e1d287e8bec1ebbb9ebc12b9bf4d98bc3e223b184463cd7fd5cc137523db5d83d5523a7c61804c94ef16230d27bbe6c6ef4b0c420efcd86cf48cd9b8c5dee5e177b93afa5973142e03f6b3d30d07c0339e8d64aa51e087423f1e51cafffe3ef1578c1bdb0d16dcf78bd247c3c59dbf72ba6de6b993db74a003e51e2458a5d313d32f5fa702f7f181d53d5137e7fa3f14c17104e86b3a0af6f175814d66624cc9992778d6c1731c4d7e9c52a7eaa9b98a521b31cf3e88c4c766444dae8fa006931808e9f2cf2497cae673deac5438c953dad11fc694442c9200d5b5d05b0b9b00be58358392990e7abb5857adab03e8f1da470176f2f8ccd72fbe6c5b4ef164831a79edf1a22ceaed5bbe503455b792f4bec19958d11300076142c3a7e20e0daabb0275d5747cb783562a5ad8faa051dd16f549d4b52f70b334177e3e9a9df0d7fe666f979ff4eeed1900c9f3f07a900b7136857325aa996b16b0de87ff3db8ab733a3dc07b121b8b32e6302ddd92e8003c29fecc9910e48f9783772c8669cf85b7e252d89c70a7de325f2fdf1885d46bf633f849860edca12523da94b6ae90071af351588a8d2e5675b01b9047cfcfaf2de9146282d10c304df67b490688c4a03ad1bbc8bbc419a49bad85e9d1cd344a51bdd5b00613ddcd809fdbb1fc64b7d18621f491a6b1e419129bf1345d2accadf016beba9653dbd95b646e881eeef41b9b589e5bd1d3385458ffd0083064d37a87a821da9a0d80d43d08b613d464040ed8ea0b7fd3a460b6fd6db4edf1e7f31086e6b198a79d575eb3e144edb38051fbcd5ade8612a207e16e5a125e1830a6008c36f08d35afe9c93e829d8e32b08892e72f89f7255898443a497ac129db66eaf62d4120daa192b46b61227de870f244c4ec905b62448c5fdfb040cf9d44b000633e25ebfd0efc90990166254b3e3981d4f6842b7fd27eb4cd2326c1f9190c1131ee3157136481fe65e7fb1cb194c8ea95e1c7dfd49ec9b381167f50a3a4e56e00c1d5eac43ecd4e408933dcd2011aef9642fde0e71607fcaa0eccf44394d6a75e3d962f65fa2910b769b2ee130fc9f1447e78e74db4b3f0967092155f455a3d88d5f48daf233fe6491deac90b1d30a02f18db5b7f17583fe4f0513c3d52ea913f0f739dd6468c2f63fa6de600b5f96cc48687a5d1aa5f2af3e2acf18429f252ef6e95d891e0786ca6939ecdba768ec7936c206f0bda534261dbe7adba572bf2f867cdb6586d8f1e6f8e0ff89ebb6b311a2e181f8443bf26bd50a3f95656d1e2087fadcf905a5ac54d2e33ff14010949d730e12fb1630d4844c5644cdea59eb08e3a987ce04345e1ce18ad39b0f3f37249391e1839b4b817b6ceab10b9cfdcdf081d2056be94eb0440b1de30fbe0c43aee9b217173b56a2581c0dafd8e3236ddf49bc7d581dbb4ad63ae399d6b73c61445db9dc405e17de21430866ba279172dd23b1181e0943d3d955251b77609a38ecd7ed57846c58dee0cb23fd3bd01085f3f7f7fe4414aef456442b917a23ed71b03f98b16ecddf499c9ba69ce346684e27add75652a5c35454ef3d246867fcb3987d001183e974c85e9bd96d39f16174832d9948d1264b9cdb3ea3fbc79390a7ab7a6da04d29e1032db30495c53ac3a00f5f85fe48fe0f73f419a440221171f4f11c298653f3eda111cb6e3f0f875579d22ce39d3f95d482a5688a18fe486743724c7bea1db05f4b12cb6091a041e0a8d47afa503e536570f8139bade5a03a3c37f3aaf434c900a8399b22b3ffd12cd41d33133dcea089e6f50220275e81b18647f7ffce1fa498618d24fcceb49d01a6b63d768c76e4078ca31d60aef25396caaa42c29d66abef1d164c360a480f27c6a6826b66bf87a8090a4da568277eafaf69ba3b8c9245cc85ddfc178e428e65dbf5e83af330f1340632783580886ac5de8b49b1c02ad9f840e91de8832a81858dab592a5a518a732a2487591c9a809ffe18d66e64b6d13cf74395f21089524d86d1bc84c4aabb2c1ee7635965e72607b81948b89ebe945e52f14e7a5ee568866b6ff89158c4ea298983f27d82f6c5c3b49589d896cb8a26f8fd2ecb95c92bf8e78bf7963f26813014011e0d9b4fc69c9994801676641b5e926e1f2cdcba036aa02fa0dea058e60a6768ce50da7b8f5a101e15b96312afccc74d08e45b4258a53e62dbb0a4e3837f29c0ea8f65018ac5c03c300b875cae3a40a1d03c3be892726a2d9053724ebe560211591264691ea452f5b9520aca806ea572617ca4341997cc96a710342101599c640521003cb2c16d5298d714e48d309dc9a2ecbdaf56dc0ba8092f0fe5b34e9cc0d5046000b0e2ba59b6e2cb333e4ad0ad7bd3afe0af0bcf689cf82d213bfead2eb102ae407fd0e014f9ba5a3ef982e726d47ba1508deac2e049b2b65d033175b581650f818753888b4aa9e78d27a28a3c952abc8559438a17f9a7d22172ae9c5d2d73ad4d30325ac592f59ba9317060f8a729280979dccf15921da7f6f03fcb2e9e75b02be237fdcab7e79d197db51261f0c00c64577d3ae3fd5d6063e5aaaa31bab240a405281aa2a3c716657538477f5936901c59f0728823af23f1b9b8e06d6a74833f01ce58563cdd2d1680c3a85bcad5debbe9fc200ceb5a1826d53116ea9701a4843ef160ec6c700bb3051cddfc2ce31bc96e68ec783ca8698d9fddf3b127a3c9fb2559d96f19cad7c46e0c8d6b65a972cc50fd7dd300df3bab808420c9049f1b2d1ccd6ce42d31bad2225b60ae5f6f094ef6837c251e48b90ae1ed443a1440c1a18c17c1558feedc6489d100cf007d08d6cac290767e2b083128e1080d41cde3dfdefd7fc9274964020b3612d5321a98ed338162cf08d053a8bf281449718dab7078484f49dd897a34105141c9dc8fe119998a9a37a47ec0f80c8a0ff68ec93fdbc4c0d8dc99f8488300eb32b2e6250ae564a3dfb73a7f77a879cfa11d7fcac7a8282cc38a43dcf37643cc909837213bd6fd95d956b219a1406cbe73c52cd56c600e55b75bc37ea69641bc0184b9ce9e76ca27311cf49566484f202df67d35558add045d580126876963832d7c5373584c34238f11e83bed4989bd77b85a30acaefdff5d88e61cbe790fe8be8bed5794e0a44f9a3e77525fcb825df714ab109654d60e5ef458a7744eaeb559b670c378bb8075fbaa8724e354a2c85811581b3d05d0fb08a24010d16ea9b0a807c2ff2c64315b5a5d01f7a26e51c540b8499ac948538d049c87a0fc6366a25385cc53d96320ca5d60ce14c4a0f71e581067d462a6c78bfaea139c1eb54f5202387607a741b489afbad4c37072fc99472198132ea694be9192ce4f5eeacb8a5c29a962bd855462f673fd4bce6dc8ffb5f3c59d58d022ee729e9f00e58c4110095f4bad44b76a1bdb9430f60718c3174c8fd7bc06acd9e245156df6da231badb2e2453163605d7d0524550a43fc56abb1fcb4b561c32264b100227a6635c029ffbb8033430795501d2b53d0a99fe7818f4246d3909c633a5e66a5d4b14c984c97f1e3fe0b347a5ccab21490628be0638c6a735dbbbf052df54fde4ce2ef0bde238c55d9e46f7468b5bf7f929548a717507a259a0cb571051ec4b52a22a781a64a95c00bf641eb3e855d7c1d6b5ca665847d43d3a9050e8a80cb12b598fa626db5130bdd683147a4e391f37a0c514317d078b551ba374384c46fdd366d7216fb0b0e1e9763c8f0637765ccf29fafe8f3db75635968d47aba91b5fef5c8970518f58fe1810e8c4e43e8e0efcb4dfcfcc34e7b3536da631af590a75d3ad2f0988c1b9c3ce29207c90bdf0a454d3dc2e74dd093132e3a6c8bfd694ce34db205351e2440170416c1ca8503ecb4c29888105a0ac4e58bad77349ac3ac7675915e4698b9805a2dc341b777fd8d257085d7e1b1a41693935d0d7f9446d0fffc978431c08166eda6c10ffaaa74f681a02d36c11c4591058f72b5d3b29bbf6728ebfe605f25f9b8b737b3f9e6112dae9bb72d1861953e23c614f898bd6a819442b654393c4c41a3a74db51ef27c3c96a64c444f493e24d682695d377e5a1f70911f0f26219f4797c02d9ed0985c2a1f0c1404cc9ce54b04099c6c16ba14a0e25f4fb68dd4c5127a48fcf6769e5900331336f4f82235bc96a5d3ad9e3c400cd1f26ed1d81e67aa3688e5437acd1cc5b905db9d3ba2d37e759b36a937cb856c2794a3e86ec72d18b1c80e0f43ed4d8199282a85fee66a998736efe74a2e8bc7cb8a1684b797be63d5509fe139c6943dbbc54c36a7b4a016b6bd23ef3d2efb25a51ff02c1b56',
+ '6b9e10c9dba055603196cb2b7fd7c54a3e8d10624aad1c342e2d5d75a271f1d952068372e1926f382e0b27e9d628d513fa15426e426bf670215fa11c5b3ab91bc509650d4c959a21399583e3b4232756866d464323ad83d0e75bc954a0a0e76a4d0f7d4525901c43db9ca9c421b0023c6bfbe487f55a0d7d07879dc7788ec2f1718094bf736a3ec59b885f3225b7b0ed3029e6e3c05cda9f094de71bebe993d5b9c472263e0d48cf02c26ed5c92077905d5e12fe8d9a325d76146df8019aa241ad43df208f60828b97b09e5714d3a04d7a01328163fc5fdfd543f807c389375bd665a3556d4b331fe2bbb65d0fb8724d107c0c134b6aa84eae4450ef389e9a4bbb93856fe0b6a5b36b4b8092c0b837b8a636695d4ccc54d28a7e3d5968c2123ae4334f13f0891e70bbc9c29b31b935b6c59710066e0aac92ef5987ed5cdbe950daafc7eba6ace77d599b45236aa4b66df407cbb7b90df8b7683df5521bd265b4246db69f09084803cd2bd1d071620008bbee601836bb80687a925131ad90cf8f4ad7e3d72183882aa0e02e4a50da5bada2b498d0ecfd8bceafc061311bfc6f08d553183af706f1195921233a0ff3118532c13b88e4bc62108e2feefb6cfd8c484c4b1b70927ac9f980587948b49769b7f27e11e724de4e589f48bf36a87d6f763726b522fbc559f19decf81b49adaa6b6b51e196b64c95cb6b719e8bd86361bb1f4caef838d1e92a3945685cd51c1dcb23a3753ca1ef7f93791351a20397a83f8a25ce995384c1ccdbcb91b261ce0afca4dd9382eeae56d85197a12231008c290319ddae53c828fe0ce63bc6f4aab92976650410a47060c17c6356b46a8857a10f90a2de418468e853fe2d40969871781fa5908dcb1fe77781e138ffb7164fd138f2e6d224a0c7b3387b30c74f807b112903fea26ca23122e8780eac1f93bedf05a7713104038e8743c50c03284a1f1d1ce918f68ee186e83a8fa262abe5eaf3fe170ba4ea97048bfd44899d28dfa5c47ebea43239c430f4fe90baca62f51c36acaa7cd3dc4373e8ed606e3d5825552dd9ea6bfd7533766cbce21f8b4cab9bcf96c98b65e5ef21c59c82858abe057781b4d535004b169f79fded994e71efb1e745da02030a83574b9010942d646752e798e7cae2c255f4424719f0bbdaaaa4a63f47a75455e85da2537464cd52342f880c88f14ce3d8bdf025ceeb798ec2bd330e764301cd047bf16d14bd138f52568c982074e6b58742aa1c745331952144b739d19db17ac0f960645a67a723ab0f1ac4171957404bca99b673f08af8ad815949be7f0d65051e19c2ad29301626a25a19d5a9488c0a9ee47a338a2dbe50bb42c5a7afbc95b934410e74cc5770aa56751ec2eb60a2ae07b5f01de9c1eff13c9935f3f66712c58103d11919c33a2175935c46a948891d4b31a197d4ef94784a87220a1cb6e5124e859203d51c7c7245db59caa9a74941b282140a155fb3cc2e341354e9501587e2efd99acb0c3c850f769fb4b426eb0086d0d1df72b857d730c7903925deaa0b876a2f46c23984421a8cb66e926d75440d42617cb4a38580976921219707ad88831e1a6f814b5b91b044245e0b23f2f491014b4ba3c747afad414f74087425e51f32247dd1ebca5a17c673eee7f067f8583417b206cda44cb878a19a40cd1a0aa3ced5d87ac1d785983b2a85ed7ede38aa044e1ba65ca5400135a0fb07b995df571cb4e844748dfa7bcbd28ae6725e9bd19977ed4c1d5071d0a1d2ff25e09c4b82414916f7adf1a1089df2679b77f00b141a1a90beb7afc86de4c10f61eb3a396e681cc85a130d6287da1283d1339b70bab0c06cc5d3ca2d1f25f5918482748e61b7b667364fe8263fa9bb46534e67b00c82f0e92819a7892f8cf5ef564c5433a4569e5c53088ad379337318e8715cb452b9326fb13050c2e542498f9ff2e1107f6e5c0e79e7fc537d9fc56157448364d47c0e6626c1cdc2896f79db271b230d392779e47093458c93163689696ee3a8d9f40d365073246f633a0d39b67023131ca87c48ffb56d6803c050f8e6988469ab48b647f7ef5fb3bbcc8e131399e8086e1aeda1f0f57dce14c49a1796696f8d90a19101e3dd8675d84d84d0073d7ad678a4555609fa7ca50f4b9c4c408aabfed12727cddbe39f9d03e475d85a04e263826f3ad1136d72d73c7cdf00aea240a2b501ff11a8acb41284b9eb93d4982799d4a32779d5f5674be380ea9ca65a37638658a23612deccbea3f56d693c9e515eb567a1b6193b64e94c7e4586f15a97288a5396a5e122f088a8c084c25cc1699c6fe332f0a5e77099bf9f7928574968d80b1b65c5dd1f2758da41b606faa7763a4840618f42a09443830b0de99fcfb35d4fd796fdce7e791682a18e9ddff8802089a6b6fbb80ed7cfe1e9893093957f9e9fb0b2b77be6eeaae80791efd35d9036793cba9dcfda412521617667f943d48342ce405aad008ffd549c49649e7dac64622bf4db4facda2e8b9d5bf59a81baf48258c84e2c7463f50e883d650702df928a9aed1eff2babd066c2c0423ef53ad9a577d3fac0e9727b5df2f558d7912ea83ab22bcfa800bbf4fcfb2db6ac4a5b1af452e8c83107c5788b02faa7d6d5a38eba9b385412d31283936e2bb9ae2626a0b7128f1f467763d2dee2d3b8cc73452c527c9c17054bb963744341b30a32a15136de5067c8e154739fb708f581161c6a4a7ddd7e4910b77ee7a3f80c8915d616b8dfb40980eca14115ac22c5bd643131e9c885f3a5cb14ba33b6d72cf377cdd8873c426b334151268ae6a88f47058b6779821cdde324d901a2a3667b10028d7de91e83f6ac886c09449022bde8a232f640d628a4c9c20d3a7a932d62a85c1eee27adfd12b268fbd18cd018b668e32fc487ea33af204eb84d04adb547822d881488103a280c890568aace13e28f6f89bb95e6c468af6fe221881a8593671ed3d65670c29e839492b5e6b49ba924ef48c2b4f3643640ce94a04e0125af1fd6081bd7f41bdedf31aba088a73673f10a75c02a3999819912d6b19d8b1e0172fe2fcd55c5cf4ee8075611d7d16ea8bc69179f40dfeeb283e4ae3cbfdff7f3283e4ed3160a665865bf9df01c5acab04eb336463e0de82fc6ec262afa2738ec083d6f1563ec666320008825a06ad2f36f91da9d6ced11612ad1456197886a54dba4997b839359d6df731e99a8270fdae6ab0c8c11a4f0b0773c13cadd2c1492691ad5edc026c61b6bc067327fbc08cb7da8d3fb775d0d8574d4994d163bbe107a482a5360ce36998c8e6bd96c4f452ffa3138d2e43ef146ea42ded388dcf035d6d6a095acb230b809853377b6094f6f26ed42f9a16f235da22dea427d383369446b3f9becfb3ce0ac4ec6f2a611adf1c985f958f6192b30613ec6843b770e148eaf104d68d0e2793b912c348490e71faba065d82a649229f21d797f0ac00a9e7a4c1a94675aabeb7ff56e2398ad866093e78f5f8777dfae1c710b1d343bad70d1e7655633c01e9f402393a92991111b4b017ed6537b9a29a6e28544001282f05a2bfa7d122bc4fb46c3a3ad806310ba99983a26d7974c01591aeedeb56a0e87dfbde0ef4dc32368587ca716e64c0ca44651cc355bf47c9efdbe4ed29c6edb9d26481f962bafc54f40f6a5cea1748c411ad63bed27b9b2685a79ae08fd43709bf972c058f6183216c4daa8bcf90218f9dccf8f35820f76c737f01568201455e71ee3ca1ff425f027add0b5c8381e383456e3d1f4b61e51235dce8bda4c0713eb5d4445d26fff89e8c27fd6daa4f2755a4ebb0814090dcae495b91846ca44310e803d3a59adcd85c3cd9012da87a356d38dff3c7cc4056a72a559c594e3f437b74d75b09cf7b86a1f94aa72b0275c6407db5675142e2db735555899acdc3fad508e95af8294e8c263e176fc45cd43255fff68bcc491ae3b966f5b9d76004c1c4009443ff5ce8414f97a9cba7fc0802341c3de187433af7efbb90dd7db96440cef872b69ddebb5e94827c3b4474a3ce616ed60920c5e5907cdfd1351c1e54e03c5cff2342034a2c8b8b293f186b19ff4474695317458d45e750118131236938b2aab40a9a7f17439b1e1b679223ed0d73a9626fd9569470b598ec223b4d13b2b86e1bdce5a6a52c49b7ed1540539f81759a5bafe55dbda96e36bb98b91fe6d6c413a12ad21e665f2de4f8f8b15ea3e3a0b3afa3d9df4b7887a62a6538f0551d4c37ee74ee5afd44ae21d95243c98cbcab71f8ec156fe036985393181982d10ed7475e3b78c0bc85510af8e2a608dc64fc71a1d92663fe9bf6eb9011f93c0d23fbde6aff200436999cecf20a26b0f584fa2f44fb9b7008f149caabf346704be50049bf307279edea08543f3964467a954b075ce3d2f881def77d412c9fe42fdabb25a56fceb6650f29ed4b00dd3dde0e559643f26b821bcd967fea3e53fc1b6d530ec80db33e1e6157f4aff1000b8268b058a4d7d39119459d4f21a9012cb1257e5191aace659c993926d87a6b342a2cc1cb09e4999a1ccf726761ed25fa639ba38a8cc75ee1420f43e05f1d7d4c17d33069cda92c2198c3bc55357b40e9b528693633d40325b14beead9e4ab4b7ede452b17aa609bc1feae1a516e3d08f286415d1016149a75cd7b00b2092b00c1ee8cf4e8b6bc0ce047d78b370b7633f3fc75fd78fefa0eac72b2e758085edcd2d48fc7d9a5adfdc0923e81cfd56578e6dda9dd639ef876462cec0793884dc5f8b9d60db01f3c56a7056c65df7514c012506664999e19ba8c22b0c82b31fea47cc5ea2f36ce70ffdec8e89d30928bf1c07e33fae1a562c3c67d8692e7ad7b62a61f5293335751e1ad043a938ad5d1f4388e9807045bdd773c64f6cd90a1cce5bef096a36f65e7e25be1ebadb4e6edb2c2a91df3cb6991bf350089e1eeb8f7170c5f3fc6b19e3beb32868255786cea12a6f74b27fb778684fb78c636c99806dfd895d463c2aa54e5781c87f29c10cdfb2b9fa9095f2cc5acd4f53634e9e9002469badde46b323ab676ca83ebe979a0579acac002be315520099f5f441654eed008f5e2b072def64874b9fdaa274f6b0d3b9f167bdf2d3ec9e19dcbcc1780d7cafd6e6c2f9f558f81ba107d961e3c3405564735da8bc9898ea6180236b517c3fa904e02c9bc400d4b8ff88b1795e6052733e9a5888aad3d569e378ae8b8bebb4809bd2de68f46798d3d99b09556f5e55b1aaeeceea480620b1323ed4ce81fc59cafe274eee210da2f0a1a73782712dd8952be9473a1eb01944b03b3c80ce2ecdecbe65f497b4538183fa45d29d1e5f3992440257e489520d98f0062c6eeaf35f6a5f0765f228e9b32235ef50e1c153173d85c25796227cac2264284ad542caacc85294299a1930c640071c390cf4b9fffb63c1ecbb74804375af0a0e507045249b3b94a5dbffb92961c044f17b42e4426050597dc0fa1d82071333fd6cfdd355376c71614436f36b0c140220c916728ce0fe2191432d94d65b432e8e873605cd3187b2f27f696ff9b354dc3d52f9d53e01af92bdbe8996bee6b162383bd34ceb2dffe1cfca019f53dc052badc5679ce456f725599f8893ba3de06780f730193d6147865fd099f207fac779088cda2a0e356761fb62b50c46d542b70f45e71b429fe109391f8d189493fe6cc405ec1d1a4162d129a49970f3217ac929a462a3892e7ef2d1f36573784c3c4f66ecf7e6a6ff9beb5ad1a0522dc6db26769f3d427f8153dbb118cd832167e3c9679e980712b45519058654307519f6576ca2c188a45d121c6cebffc83e9b97335c97470f1c243102c01f4f8c67bf4ec4723ba9e686063c3d5be9a6c31e4bb945ac638f5ef1b3b2110128dcd6540b67062dd660dc931134c7049e4d044eecec58ed8fdb8751e75acc4f1f96aee8a2b185061f60db5a029e41fd764cfc5f1382e58678a618ab338dad108007668409d0d981632483595ecb26bd0e312bbd442f940dba89737c2ced2bebd699adc4a5276c263e46350f8a5dfdb9c29412e7f344bce9c1f0283337dbd137afe7d5190937c56b2d2090fb19d92f8a3fe0326d9564f8b12b74b76ee8ec359a685c8cff9a747dd6ac5e078a819f4484e2a00acccef2afea5371a18dc490b17a8c7225f6fd8fedff2859096c6642d550374e1c7404907b0be691672e3f5bed5ea8c3407da6ccae2b4dac26ef1c564a72ba64e801baa0c27c313e88becdb031cd39ba5d120acd270178c1e0a1283c2c42f86eede465b61ed1efe386be0440372cbbe1ec52ae03f1083624b7112e76e3675dd6ae9a9a42f8a35f2ad1ba576e6623abb47eae050384b6026f2abd7399085677269a92ccc9e8472b7efa225bad371885bc7482a43e6a7179ebac14bc152376c0f9427b81dd5653c14a7084657f2291e6647e7f10370ae0e934cba55c6dad6eb7a936a33ddfdb4fad27d56f2976c1162a46ed01bd6ffe8aecc4f9edc9250ea4e7c0d7a87553dfa16b9189310c069b1bddde50a9205bbdc5f136c75ca6bddf9fcddab609acdbebef79121c7881b109298d8539d3cd7cb128a2e4b48069aa98d5d651423568da78805223ffa42f08a3b595e61920396581295e6931e7866db2dd9dcf0cba1dc21e1a8341b352587ff346a37b7f0ce888965ea03ec0008199b62d809e9616504544f488e5c7f',
+ '2c97ef4f65319444199df671deaee966d19628052359f791d5810608bf1499b3d7c3e6fbb064be7e41a27062e8f3ebe9afcb2246cd10b5b070cdcdb4b92a47cf089c163c7b59e20bb10ff573d4d55869d6623971fa7c90b597b40354195106fc488a46bbcdf84812f14a4d4ca93b7a0dd0c1352eb387d2c8d29e6f8fe5701c621ef54020ae2938bc8abd40946f0c97fe2352de24ff18c113aaf3da0e276ed2281245ca1226d4f93103ce96f32e32f8645a7bfcfce618a7bba61b0c79e6357077ace2ad393ee1d498e4e71613ef94e566faa6565e706dcaeb4f7fcd772bac3e767534b13efd381119b66f8a99b91aa52c8d3ab5f0a60073c92b85e5b0fdbb844ef4a49dc96cc1f8de00ceb83095ac82df9b9fe15d8fb9e97dc4961bcd644a8926b1983b819165d00c4a6b687e8a32c2a7aa3ca24b33807630a21b3876684196273579510f760ae3ce1db0642ba094aeab447bc0639b3e600a4b7a05521288f37807b0a81a2399774aff0473e0dfba8e14f0c3024cced9f032491d470a6908e843b0a3084b9efbd5a87bcf5c45508daa09377d31aed43b6ed1246ac94575a7bdadb6bc384af1164511167ae801faf349b39f0f1533b64a220e62cb52ac3f4eb9e0a36202be24d40ead5a6bdfbde6d5b53ab2e276817bf053128da7a9e7cee62504fefc2c503d1c7aa3ce0b09201a1c34c5f85e1c402b144b042c65979d2b55494050f47fa746d5cf1cfac22dd3a0ae0f7cf8ebcf19cf9e500dc86fe4765c856dbd20b1c36c46e9563f67f9157d80758e883af256b36e75ab55e82c581c6b5cd9a3f69bc046463f57359c1687c7c058f81146d97ab81083a4031404ba345fc4d4195a1a3dde4dbf1a2521834c586a3e973ea50a03cdbdaf8240d55fd46941a4a847d59604db6351e6387fa62cda8511b138deebb99c7ee18f33b1a56c6018e39b56ee77dbe690dd05953de9eef21ab88f2779e663cd5a011105323e4bc4ec6bf66e2e258ba7dd4bbd66e3b35c4a301b7932fe62cb085af851dde093393bc62301b61c09dcc5518357d0fa6b3c8acf22b681f6c7b7f485ac97e4420afe6dadf30357408995e9f56e1afd097b575affb5903de97cf039e3062b41e00c6104a9c34679220e80bacff25015984a7560c9bc4d8e5deb3e807cee541d42022ba5c27b10424b0163e1eaf83f3f2f405e47341f369bdc7b6871594d5ba0f15224fa0104aadd42c807054b6931a457c5d9b549c6938ded9438b3810988f1746614ab6d445c708fcd34cffc2b6c6c9741af530f99ac8b199e74effc0c233953a4c3600e246d24bb76b1e6042839be781ca8c88e81c8bee601e5ccd33c749f1776f9c0ed8c27204d2d48f46b46dfc281be8cbcce64314ede2653f30d83c34c47437d731165d08dcd59bf9e396fa8a23a9023d9d82c2d628973860bb2c857686b7218228395b192c361df8e778ada832bf835c4b3bf05226e5145bdcbf3791a0b6d927549061258b8af706c2d7fbe90dd5f8928670701b7152959b15eeb718ab09bb3136d9e3b0634aa3adf61369ceed72ee8abfdea684906618b8e769a87e1e16442a5f64773c5bdba04901d96bcc8a1cb1af57c5284ffca63a1fc18e70347273009d2ede803bddd47982768896544c4a0e7afec269b02e89563a54eba2ce30967ce43596933151bb9bc4b4bf7e08ac7131781554ef67ae8619b2e1ffb4bde0f715a9f42b14bf6ff7939a4ccc676c3328e7112c0b1dd8e532e0f429492a85fc1b7758fb1aa938712edd7dbf6df6c178e78be0b34399766af638887b3688b8fa89fe6bbac3b53f1db7e5d98ce2a0dad6139c613bacd6e2b876acea586633c964b9277ca3e4cd25be39495825e8fa0ae363951d35955a80560aaa945156c029b32f9ee656cc659eeb0984542adf76c4192df27f319ba8e1bc48be10f666c27c6e63a1433408f30d1ccf03680c348ba8fff950f1a02e268963a753daa8449f140eebfb1ddc1909f2dbf0630e3a96cd2c004c3d3f01db67e9d4bd087bf74eda931d2ee8d0af7b1ee2a0f41fb136649fcfb2acf391f711e17597d0485f53f3e96c11b316f63ebdb64ef46085eb902c98068f7220a4c06037df25b7900f5ee26021650ef218fa4f44aa5ebc5227d86d3a530f5240d21e54bf68f6a98bf66898c335bf98f69372ae5e87387df0f06f94eb57bac3d0c4615e0cb5834c78cf1165772b69f7b100386978682a1beedfd6324f670a3d6cd639ea84476406f2d3e9f71074fc91ade9abff16cfac1ca8409cc3cb13862551a1f898da81116f96bf42bc13a4fffd49aa5dddae202706b9cd33d7e2dfa7124b922dd7641503fcb0ebbc32f53dec9110ecbe93c2fc23409fc02bdd2b4df59a97215a24a555c424fde3d6ea39923b03900b5eafe9c63980c6d3f55208c0fa14e0fa0e1f32d5da7eca31f340555473bb4390be808710d21dcb7320c50feb1d17a566aa158117af709240c1d3dd071f3eb8174ec42f6e759e1a282ea4a52ee7194111cea6148dbb9102aaa32344fca5e449f98fc94721ac783f6d8f7f6d0ed0a06065dc4f61ca975687da17ad01d7a06285d2023e62fcdf32b7adb3c3dc0d72337fed638ffd30cb379e59afdfa81a8730081b466207329663ff734c00569d3458fc02f4d64b62bfcab2a3dc49bc27d2b32d4fbcf2a8756a729358a8b38140af776160f0c0d0d2fd7661331d6992920b8281e9e65ecb37b3e4ffc86a6bc09a3c154c61e7be3203fcd12691f3cdefeb9a2482744304e3ec1b41b14d0187b90c72b919905a5cc36846517f4aefc5089726ffcb6963939724f90f3440fe95bf19ebe95576c8fb1992627dd7c8720d91171a9ab953c8793954e5a4155eb2246734f01c9c8fa437b8f4c9bcdc951af60e0ded7bc8add2246f0e59b6c100eca235f037d93b358c810611e4a4da2b5b2cc3ce282995e211338cb3dd9dc3a1de9dafde1b85ce22e265a57c5bbc6d15b30076ea531a9d9b261536e7d9ff99ec3885295123c8d1b236540b86976a11cea31f8bd4e6c54c235147d20ce722b03a6ad756fbd918c27df8ea9ce3104444c0bbe877305bc02e35535a02a58dcda306e632ad30b3dc3ce0ba97fdf46ec192965dd9cd7f4a71b02b8cba3d442646eeec4af590824ca98d74fbca934d0b6867aa1991f3040b707e806de6e66b5934f05509bea572fc0750c52d10f15f6ebf50966f7f8c714742f5de877e928d8efe53db92ec60854a5c133bdef99c9e012cd9c5d0110e49665f928059cfc62d402995ea770d363bd03e415371f2f9ab7b376fc2dbe4dceee12ce1307588f5bd054d12fc46afaaf8f61467a6968b9cea35658f17f3698cf450317e493b08a5cf56bf679b5232436f5484f2afad21faecb84db42fb72da2dc3c13a6454aed5ad6ad1710425a3373e153f67ec9365498258c467b94879f9318fd3a15bef595896580ea898baf31d1017cef689dc2a1f92719f641c0f53df23d2c1b2910fb9fc5fe7a805fd1c1abcf4247d64ca7c288a253f9c3a34f709724bb314d7cd5ad7a74f3e29effaf420fe3164d519741c677085ea4351c9a9a29cf05b5377d978ba42af8619ce59ea0ec911fe5d7d5263f0bbf8991c11a3cc26435cf106f97b126ab6c7e6fe09e0a5eb07da1a9df82bfaca51c774b0e389099e51d59eb5c77cd0f0e9df87620a58cebf9fd5e21ef3c688fd1c3aa4862d440811ada48ae948a35953c53f91cc38880c93794a7c67d4ecb69202985126589d70873f11dc29e4ba569b312f2bc5fb3a086e7d81100105652c90ed2ba219cd59ea08ef63bdfe03876ed114f67dd4bb346a5893c1c772ecf991ffc439c0e6f3431d9da5e454ef0dd44afc58ddb081e03fd959a354c844f3708ee86f0bd3153122b24d49daca846a679124e42cec54d223fd7a95803cfe8191ca52d5e9022ff1bd5021c38e46458308ec51a02b58d6faa87fc2518e4b29fd25ee45e01a75bb9adda01ffb48f1bbbe81038c74211d8ebe96073abfc61c4cd1f4a1c977e85541944a4951da50f26d84997ddc4858620487b43ce8c86fd8ef64c68ce78299ca4946ccf4bf46ed72e33e17034a041650c1becbc7d652d2a69b1e6356a6ea76fb5c1a2b4970286899a652c43cdd90a40f4cc9a34f0c4638afd66793a80f95c71101772eef6c69098e3d2a68d19dd44ac62d12047d908de71b2ba71d2a286cd433c1c59f8fbbef187a302ccb6992b94f9eedffb6f96c0d28111f46ca83fb57e48a236d44d066a4dd8408c2b4adc525fdbe13fa806aa8b042b93cac625c903f413eafd5e55b736fa0d54c53ec443d019a4e276a2c794a5d7f3c0942ad5ced0a3176ed664cd7ed37a89484463cd802366f78b2b780cd5cfc25c20e6e722c1ca2fd5a668840edd9e63eea39056a0e1dabc4623b48450fe5747d72d4997b7325eded13a1a488454163aafff1f73e909547e1344a8742e6eeafcf75e22c8eda0ed548c92620ed862c9634fa3784820672f40d1767db41fb7e9463f3c08d7409e14158a37e663759a36724049014fc17bd9991331bac08f59d0b8013f726f5a7a61863ca30dc2e65adbec144860f756c7d1db5afed63617d53fc6ffd0e5ed85c6344b266c4ec9953528354e3a0deb6adba6ac1b36666123ba94f79da8d9ce7d222ac8d9ec3fd42ca9a9e250867e784817c67fd690c1ee3d6a6ece4d2fd4ae8aeea383b9ab476ef738ef77cdc6193aa90ccfcb1f76af39ef753bcf80ce5cfa49bc4d006831fc169f63577a187184aee83b1112485054e0945b6a5ffb1cd93e33fb65c34818c4578fa9804db3014751a02348e9f3c6795ee69da903fe83dfe6c42cbc40ebb846ae327bd8774ffdb7af4b45c5b6a431732130ac46601dabe78094d08f0dc5a8e987426aee9b21df7c345423161cdc39d8b40d17e0d8daed769800877c9e6853337f19f7310deb2f0b6d453bb8610e4820a3f650f6f2b8d11399a1a1f357365faaf233912e63fe54cbd6b9003b37acd4d15d7707f73239ff3501f2c081dcd226b69d29d95521fdf953fd0a110d154a78616cf97d600bc0b0516d7e53b6a3ae30ecbc673033bc4c852a4cc2deac6e699b574f0841d0040facff48aeebf0203d06c3ec662658b77c70c5327dc9a7c78cc639702df5c5af593da50ffee9ccf70120c2b9c12c22980044ab6a95827d9526817e7cd4f99f624ffad93ff8edbb8c8d176f80e2c22fd27a894341a4699c7bc945acc187ddebc1465d026527683a45534330fe5588eefb4db72354151f92dfd78b3aead1132940feecc6fc04d9c7d5d64cad6e83d0ce76ec46d21e71f4ef25e3daaf552b29e665bc228d811bb2f2a2989c3b7e184a7cd9f8c0c61d458a000270bc709d008281a41086cc80c6a429301caa71896d464898d5ad85bd0a5f73ea0099352eebcdb9809e921a77affe0a02c4ffd63d1d64d0380575878a3ae5c94106095397676bcc8fb8ccedc23250e39f4414dff58326924c2ab1420141ad6eac13755242eddd5925f1afbd1cf82d6469fc82054f38fc0d29a7d94bced916e28f9b75c7ce09a2ddf7cba30ed46be3faf760bfcca6c955bf64ff561b2f4bd2b37010831aa5255cc959b95f6984f82515cc1336cc98aed41792a3d026cf24315fd21515d144db5bb9e04d2d43ab4761535c867f5b9143f7a41737aff50aece3463a0bb6624986201e0ff9a533b3b419223d2d02102d3320f3872d7e1c8f2049151c86dd641fd05a645d415f904ad8bd10c2995770c8f6f07456e7a2e3b848d33b0df8f34d24246b3b4ca6a51fbd1fed8da13e07f400f9e6a4fb18b71112d1222662e4b094130cc2f142ac2e0897140b173d9d6c2041d66414081332fb1b53b675dfdc7dcf58f40224f3c44301653f964c1c3a1c7c17f6f93ff2799f285e4af097b942b35562749944c4819c80b40d2f7aa186916e0fe626874ebe741f4821710aa3aa09cccbf908ef3966830ce00727418225538c762e7c8871442a566561a85d1384e9abf21bc172c6e5af1c95b83e2f2278eb4e073a5bc20ecd4a54329616fd8d65cb697137638df1717926319aa4a20703c1292f9454b27b9c5c9f4c393d6a65cf279bc5e66f8f4dac69b030191b3894585db44bb6e7e84c43d99dfec59c225123a6a97ca335f53ff8f9413dde02517ed94966082c95c1f55639e6d6c5b3c3c4059118e1e8675e5ce9b06fa733fa9b1b28fcb4d16feede0b35f5f060a404bd32d7af60a240643908e1279208b54453fda9f03733fb439b0285a64138fb1f8d322c3a274a25cd03d891c73edefca03aaaeebc43ac5934975fa7c36fac4554a22675c9c32612f24d505df7ec96647ebd8769c990a6f45241972ec5c256ed549a9446e6cae2928483e4b86211cb77304b27ad9074b066fd282d8d35efa58d8fd5001219b4ec1ef4362a337e54eabf8562001cb986553bc5c9a7458c5e349076f00e59cb07cf32b1d27541a50c70a7be90b5599d3c01bd1c9478da41847080aa69f3908339f8584df77a859ecef9ca7c659bc6c40c4295921736ddedf8e5e88aa16458cac1e40a49039e519412f2821f4f47f6c68da44fa6c055a2fa31f1329844e5151a463ec3038555485d5ed78947bc2e6c0d268335cb3e59dbfea64ef963b2ea57fa3d551d3dfb23dc2339252a664ad389106d8bf28fefb7eb53fc4ba77d79d893d2fdc36338e3ae8fbe0877fffb69cbf068d90b006532f443927a73b73618544b3d2e065f84dd49c566533c7487976c148eeface9dbc6939752c753b33e0cd1f0a1349a4cb2268a3fb4bfdf129b525877eb17ce0964091a38ed597825f5c5d626a7a80bc5417df43131a4fc749739059d1',
+ '629b37b9a056e74959346e8c40aeb4e2073e97bf2117d2ffcd13237a50edadd981b09ba88b6f06acef371876c8427238536dccd8aeecdb43e03d78041a5afe153d33f4f49b5becfa0202aae9f72389c43ffe1be3a4c91046f5a3592a4fc98dda9b0c8bc8588361dc9b7d6c0c53b9c12dd2dacc08891537b1132d0d1476a120d1a524a84a494d2cf9c090a608666de21b14e72771e738192b43c3deeb174a80a1626192a2f62217fb7c239f04b8a5b3380e0e7343459a7e5d8c4d12d7ba2c75f3daac93f9e76be887d41ea029cfcafd29c738faa92ca32eeef6b3f2ffe8afc66f16eef177a58e6848d269f19e35458899474b02c923bbf08789ddc68c283b3dbc1d0df543b7f55fe37decda8c727c1c2e9731d4cbb24a8dd04eb3d6a50338a083f7f3e786a5069503dd90a31b0cd98190bed0b8d861b8ce704c1e6973000eb86bad860f67f82bd14efb3b93728dc37e68c412f518b96d78108b04c91ea7254d1d46b0b40f37cbdd6cded6f3ba7da2dd5eb2ddd5b241d15657144f3ccd80e52740ca5720a5ea4d7f068e4a0b1a62dd64198f1b9ece814c2feeeee50ba814b70d7d42659952991b80c4147d23bbc6dedc4263b399960247ca7c21b07ed8ea01c87cb5c1683ecd9ca74d775983c5300c0c80378d0e304b28f0aab696dc858a2c21e42b53d5900d38be4abfc5735f29cfbe7c129145c4e5596920b4816fd67da1dd5eac032771e2578b62f5a83ab1388fb8defca7857b5640fd8523587df44ac7c79141b9a808d361d83e20ec21e4e5b4c34dfdb7187c284774820c034f077905a626f15d9e7e68be6b8548787020b8a6a7711ea944f0e2d595be76692d3693c541c4c5d752fa29d70ce075346f8c3ace2cf3666552ff0d5129e269745ea91f6e6114c30f0ca59f1285f7b0086551f2921a7bded38ad03025f895ed0b2c89a568cebdf5ee14a651d89d7100dc9c96685b38b08cdc338cd3b8ca800b16ddfead1a5068635ab126c7921bf76e985a8425924f3b7a66965a7f72674aca7eba2fc0eeafbd143c2c4d8aa6c8300124e843b09d27c3b05afc63babb979c33290b45654bb263107dcd42217e6cc5c1688efa91e688f134a1abcf0ffb211e4c001867259923035f03dc2e1480cf5db64d93d251d33a6d1021ecc5039ace771feb28be8741c8440ab8a138ec16b8a1e9b941f277ce04de4cc4066d20006650b4d3857213a969cf1783648930a7f0386195de828b3eb0edee7143f0ed96b150119e75aa513ad04b914b6c48689a40cc26cab3ac168b04410010e976f2c276204036418eca5cc4617dfa029e1d596e02413f08969f1cc98389884126b8ed7f674981314705780cb9e5776eb3d54b4284b9db2568ae5bc65a92c39329c68092ce32698cfd8af471fad0aaf8ae1bd886fa9688514bcbafc56f22a827a7a4e178fd05d59cd7e23abbcd477be8b87468000be12dd5677f808c69411f44c7db7af99bcaaca7fe94b0b2d951d285f86a637960a1b1f9e35137e5f1f71033b1f2b2dc5087d8f69a28c01dc7f69718978432baa1defe05e7ca3a96af4d633d8e71f0ebc64640d1d227d1c63e3eb2e9bc4b3b8875a0fb419d70f2674a4a00a886e19eeca20ecc7fce184a73e7320b409045e7ae84f090f52bcec7226bf7d0bcc0c923a88fe8644ab78395ed6ad98b653bfc9ef277d5a568b969228db95ab9c365b1e7e733daf078b5f019fc6e3c189fe4f8c91ef65822e370cb6972dd7ea4039c21527036ef5852efe83e679b619b38b3bfcef8a880efae777b06977e687ac58ea2cc0d412c84208ef26cf89a52fd76d1db17493655f511f7015fc44522215162bbbd84fc9b5d2ab9970b75131724a266d40ad847df1a5418e6dec3d9b383ef41f58d9e0e43c9b7995e83a7adb6fa039930116f842747be01b1e95be42387e753d7a423202cb11156cf3d56113966e3937993d495346598dbeaea530b7a1480bfe96adbd95ffe7e17729f4ae7a74f887c36f8d0210e5a2acd194874f8c11404aeb3488ee2e3964704d0124ad6099f3b7bf0a72bd0be10bc00c76b8653006eb947bea403e2c063b44670ff2086663d44b82e0aad4c4b6d969c7bbb51c33d0ae8b391e70382ff4c0c05dacc92a0c611eb5c78881e3abd5b00c8bea09cf182d0d819b47a566aa738996897e369cc3203788f568945451dc141ac17823185d6a8d3a2b0c3c441c011a1982eaa6cb1b0fb32785175eb137286a2710ec9d626427a1f760c2c15af53be6dbd278b65f84be16340f0b5d84cc4946b3f2bdd547ccc2e05bc501c105e662745fe0bec1a48089d510ebcafd4991bd2e43df72672307faccd9d05fb7ef3043470836137554af117440b3ccca7a280285494f90dfaea60dcbf40b230271932cd3875b1d3dca60d38865ff874180efa7e056bb9f8b25179a623cedf25d376fadae3fc8428770364e65e317810f18592bc3debc050640ed1f3406e14414ab26343ba3f609ef009ff0a13a94506f8b14fcb453c957ed6c970a95f49daec537675f00567d09d0e61e58deef1e6c0f63739916585c1e8d1295fb2c886b88eb86a39c90c9b598d98b31e55372ee3a4b2c258f7e4efcaf81cd6a5f4c34e378f3f35b6b7160485d657a6b34c368bc51cf6f8b5e50ca13a1581794a5998c9dd58b17ff5a06dc9dbe013e3ab59322e128f8881574423c398a6c6ba57c88e1e354fd5f2fc6e5714e31493120e63753f5565310646fa727f6d15b440d328ee76c4dd7534d5071d0a26d8a1dae55445e71305b92f8bf141fb40c913b4c66300f8146a57ed885507d52b9503e33713eb4dd6d9e8a32d0fd85f992aabbd38600ccdac5f44c61b3e5c9d7ab482d60c88af9b2548860b343e7ed400a0430432075a1c1422046698ac66899c9be65b6c9bd8f689ba5a0ea9657c82fe93a530bb40320ed51d5f7706866bc218b4f7196034b08889972d55936c1a901a6b97eadcf3dbcb76b71d9e6eb4b47076667db9ef3b7d79ad48c787fa3aa026dd90e2da9c089e7a7f570585d71d89b93f183fe2229888f17d33f04de6b9566ece6b4ea70367c3437867e1d449ab31c8fa34063d0033191782c1704f60d0848d7562a2fa19f9924fea4e7733cd45f6c32f219a7291f47713435a0e346f6771ae5adea87ae7a452c65d748f4869d31ed36747c328ddc4ec0e486793a51c6766f7064826d074514dd05741f3be273ef21f280e4907ed89be301a4ec8496eb6ab900006e5a3c8e9c993621d6a3b0f6cbad0fddef3b9c82d36a0407972c96d3d88a2d082ad9cec520f3a0570bb672846be0d6b1f8ae376969c87424e5ccc21e44555ff224563e77667ebc9a2afec7ab445fffc39d73000ae380ca9374dfb939429d01450779fc13b18ce341b0e6f0d9f99cd37949f456d5b5158fc9b0cfeca337d3c977309f8838b6c0dd043a09beaed00a0a498adcdc5873192c3e2627cceaa89c010f2c418344da9cd25832c151888c3a0ccaec86e10191ee38773171da8e29585083770a4dc691839b9417c889f562af4363cb7d057f96173ceb8f38a5fc2e9b307a9d54783c6017617e6569a88417cc56aac439ca20f42692318be6bfb31cd7193b0baa7324eacafc4db83f3da30033af164347af74308ed0bfb3f67851988e736b4e96cf1e8600a4987cb3c9d0a057c52ce5211607dfd2910c93f270461633e771d283fc67c67af2f51f51dd9b9cbb21550640ae4a56742b9eb865380f1a69ede5440c8bbf307c7fea3cb3d97249d05c354340c8ba38f638ec7dc9201b1992ef4bff9aed733897a3ce41345df33c29de34188c05e5fbdb218c6098c9089831740c71a99222535ef737b96dcb7ce84a1fd7f29ce64c13957de50d4ea3461ae804bd46d33a5b1524f2ad21cab29dd293a9a76dfdda6b24a7b69ce0654f62160929b4fb82167c02ac1ea506d5a3c48fa8f418e71952a1839463fa66771dcb1e275ba9ec40e7038036e3a5d5090f8f73fc97a2fa9a0fadd4fdfea42bfeaf1f97c50302b59a0c903267ea2618ef566d3400fcdc14da31ee7d16ff208d6663ae1754b835d3e7f7907db18aac4ef328dc2448ac3ac26499c200a1ddd62748ec15217903d9b3d54882e6964dbef3bef76ad9561e358fc377e6e411235b7600a8d32a844e54f1cc7ff5e1a8bbc483ad3277d79bdb52d9718ccbbbd75816f6381a66b719204e0a47fe58134b5d638b487aa1839c6c294be928ceec76f4af2459c15e3baa021b69d25427e0009af2870b307681da391f33b4dc8efa72817a7ce02e184cb96c439cf601f3dc6b0d17e37fe44546c7dabb3d4f287a6a5c770f8c55b1234eb9d76d102d983a6ced2c4a4d1106bc3707b963d5229d2fb0975dd4cf451b1dd63ddbde6fd3876a15f950e000692839f6fc6dcf87c99b0d61a59e70c218ac4667c7d24e089240016c22439fc9e2c95cfb285044b1ceb291f219c159e15ae56b77ba530276e452aed40a5cdfe6085a83b81e197a0f8c745be6665f06b98666cb8ab7f6b237444fb0e1f4150701546c4cb24021c5edad30d9b31ddffdb725a105153305c3c62c97c61a71f4cb98516f167e20f1bec88ba889eca2bca576a31496abae38bdb027bb3cf22684841789e38f704311db4ca22beb07879d945499320eb29be81661a03aa3ed643cd70b686a063d28fa91d6ab4b8b785ecb50ed72a9bbce8e9c547730869970cd962ae067532939452478f2e177d9c37aee51bdc9bc5c53f8ddfe4d533f6c30c518f633213f56f92a17f72e0f31548d7e7f1d32091a3ef76d258a7082c5ba91ed4dcf70009f1e014a5da2f46f289a2acbe629d6ab23f20d4dfb7f7d39aa0308a8ad2d239ff2ecc6185df2c29bae091005cbee384a6a041a2dd843bff26cb42979af85ca59cb51dc26a17d250f322ff19e9900c3416ce3b29f348dd5477e6c68f2dd6fe0b898db97f7c0889854658d3408c5d8043aad2f4ae4a89449a36f8a3b86af51e346ebe8ae0e23cba8e5a89f5bd6dafa67909b3567c09d2d8990a0ac35b7adc26e9f151762cc53bcb34d9bd4cd8b50de89ddc7948da59a801199c230dbf84137cf0bd4d67ad4c8b263f68fcc2d98c00ad79ca2cb10f629527e1934b692eb1238a1178ba44a9a0b63177b14f3e07190f5ed37cd57b644b20b2ba988abfee2993b616cba0f569e84456a45cbda9ac85c26174997c8d213dd20a6c53e9f74813eb6ac55d0861354dbbfc8d114fe90ef7a11f24ca27cf8e5f3197bf6b6ffa8497b40de8d7f97255f7a58afb770ce775983500aab7e029f2dac7c27b8925aeeadf750745e07c0f100d896ab636fc8b2b90056dd007ae8397d51cf1675411fcc210b6bfa5219820afb8f00386f727be770699cc5c639ac5c379b68d4c753dcc5355009fcf41558e7a1f61bd33629666a0227a861772aad7bfee1ffff293b107e6d4d90e8de9bb3fb96b6400841e506cef0e2f04c9dde8a1d35bdfae21577493c3b5cc74a5c1d31cb4c3813442d45db9acef40ad3a4b5c0c46e87c01a1688d5a32215e3ae070a40db35041c1352bc6341ff036e831650dfe62bf8062397007437b4019c6069f36fbfd392b1025cc92c48f36981f78d303606d057338b314e735e5398481dff592ccdba7c887da5ec95346eceeb9d53c461bb0e5dbead5fe371ab93527d3321bbf6a95c29e8f1b8a74c88a38c11e1148df15738542e268d6ffd9405b8b25c9b8c5bb73b13791b2f8990aa44297b5f4a0d538fe0d2f873343455abfd437dd6a75ada9bc9d1bf3654f4303dc33fbe845ec306b98a62b4100ee138581c7b365defa7e1bbaadd01ffd37779a7933721eced3f8958a78bd78957f7042cd5d8823edee40f8b30ae07b425a3fb7c56986c055fe36a01daa6d94bb9d6579eabd3d815efafc2af0dbd371fb5da24206febe34a315f2739664c3fd3af0f81221115ae5fbc5b998b9492f67f2eba924851aa2e380bf32f640d0481cda2133f5ba7e68b98af87f2a567c7ec7edaa5a438ae3bb35b3a400116213835763d81e0afd2eee64d43a56c80e9b47f8b542bf885c849093066d63ac4358fc179cbcc03a3149744ea134475a2acf1362125ce0db4523aa633fc264af2cc53cc54e76216ba0f682dfb98d59c8f23f696dcba0c3258be98f0e274a09026582cac741ae4986905ea17fe3ee95faae14d181aab5724c77fdc9fd86ae5c8e36b82e873d0b41744aa95896e9ff01d9a675d3067544f53a5187e09724966539e78ff16b0a35101b16fe308c9167a84b228bf864f5f452d561b15a43c8a82becbf2e9add6fd5be653b85b0c9a4cf8f39697dc703f26e9307eea1c16b3c8fb69007beb14436935439036860b5e9dec08cc474c94c3459c0b7427f9c7646f10e0b0866bf082516d97eb4ccc8b92a3d422a7b9884a5825edfcda37474ccaf773c47074f4b900507551e97db713b2d931dfdd6108277a525fa305aa8d0d53ec8472a4fa0dd469d0dd000f4bde2a5bb8959ec1c054b2b665518caaf4238f8bd8d379e2782fd91b7d1530c139aa35d9cc7ffde979e65278b964282e96a34a0f26d148ad61d34d919fae52a3b993dcd98f417bd8d045ad5dc927f40524a4d32a711e7d5a59809878c318f42b6e2375b77b8a770475eb29c23eed7ec30070a17a220b869dc1c0501d81e583cca9e197100bdb9df0dd5f3e32655526fccccf4b7ac438d2dc3d0aeb0c5684800c0f56c76fca854f22791a714ae6df9af6ebf917a8c227009a1dd8ffc1bb86777e00eb6d234248191dcb9559683ded3955e086cc90c6468e5c06a2d4ea1b5be87711282ec6dd747a4757686d16d51e809e3d00a0af2b17da966a0120c20085419bd28552e7269ae133aefc12d346e41f4463ff5a707b3b83edca7c5a0615831160a79b9ff0a83bec6734d8e129fdce53cd07cb406130f208a6aa6ce70294e5e64a12d35c7fccd80a568b775e87844dccbc6b0cf91de70be7657c5b6e64392e2edca',
+ '2c9bdc9d3e5cf09addfcd9c3f24f6bb182d76c1d2f3b3ef502f4bdae674a6bdd797fcb01804a0aa3b887e4a2552cc6c8c37f30a032865e8c52bf27125b1225775104168f862d82d360ec9fb45c59712f537b35a2fdbc00deb399a47d799ed3763b9287ee57efa0515c95cfa211beb40fba3543c996c36c12aceafb98c8d6bd01c091e4c7fb76ac4634e83f137c44d6907e1322bd0785ea51c61bf50a50a32a416669f6c17baceed9714fb6b0a4d121d9297f992758e8c2c387925a7b19eb645db8828585de77ae339a31fb21914edb1343e072af8fd63b8f79b8f2952c98b17e3b4559ba2cc1337b37f5d70d4ba44d65529e73fcb6369ede24f584fd90905acc791839096e71c6dad105121cdbb9bff8e02165b7d4ef33d70fb2da5e4dbda66ef964ee1aab6faf78efc874f7487779f374d00f87f2eb42a3c255ba5e6c05df8d43924194eba3367c19a6c5469ac5c27f97153b1517111369c548eda5f4524b50e008f72036f5a30fd707ccb0d98ae4c4cf57af094b49ee52a1f13bdced8b34e05730db65954d3d58b25352de0eb025e9a7ff340a967b1a86ddba6bab5f98e0643071b40c0f934d8031f21f78411ece17a47392c0018fe02c010a47b953619805f43249bcadaf1baedd4dfb2578bc2675501c91d4936e886c954895afd0cda3eb2add631e7689ef0b3ccaae8dbd72772c3262f9ee55fe5a6f22de1e34dfc64b3d5ad9cad9720911bfd8d2ff5917280763d2ae91b54d289d5d3033aaf18734d6240c8e4696a918c8f139ceb7318fc62043b96622d285d59b5e45af018ed0fd8dc7c9649ffd249c5ec9e5249ebc2b3408f46ef474ec05eb9a98ebbaeab2c20ab84f18c39cb5ea7e1970663e7ca3f55a079d79be4facd354e336a9bc2e0566439fe38213076703a7420e4482198df5216766632e7bbaf9f6be5e071d9531c39089ac8ebca6ba78fe20ca055a3d23326f6e78b3aabfbd7fbb72398e45e7dbe1dac0d1c4264257506afdcd332daf0db42e66fbf19ce55e6b8949eec59decadc9078a7bacb9a2e593d51f3f556238f283449516a66bb344c74a89a5358b6c4f820130d2cfde900d4926cf47f463a07ba89b44f2597ff2179be57b8864782e6914aef9fdb4fbd2777b45550d9797af4f2a19bab792406981ed4267bcdccdfba288f82f25e37a31ca3119f9bac6662c1711a8418cd916e228c749956c25f09ba2e5c61871e5c175af718c03760a38e17a652f1f99b76923b430e24cbec54bd61233fffe0a413a66cd4585ba68e97594212f959d07b3a1f6ce31f5d6da4644749a7b8d27177f4bcbcdb4ed68b9ed850eb377c403fe5626211ed06a79e930453ce1c457dcc1285e6e56c6aa53909ed117d943a399a526606c4b17297e1309b5f79016456430c3da151184a5c31683bf773b9d1aa625adb8a1fe0e1a2d7f5bfc6d4e793f098bd82789a5f5c6027324b8f5808c1174a6739486debc26cd56ad8266c4f62d11fbefad92bb22f657ce09255a501970494fff5de5942933a8bf88aeaa9a94f7c5a791d3f7fb111b094fbb6c6e554a1f6e48d97c84c75d2a04e373ea00775c866db84503820ccc1e6120c1f7f93429072bba89572b2061f42aeaf69e320354c0515594a5f9975be8a766a32cc398be0ec7cd9758a4463c7762fb993006e7868eb8a7051f2543c460234cea759327101d8ca88ce6b6e3f69df33ee50945cbfad9ecd520daceb9116d1cf71d393389271dc074f068289c984d27dc6191b756b78733086d3ebca14af9dc92c4df69670bf4ac584948c31b286f44fd96302331c581d66875b4b6a275eba94367d1fe69c58c987d21ed4f08b1ea93c80a4f52042cb01c1cbfa286649c2bcc328f76ba365bcbcc0affbf940a38a85c6647540d76f4fb4f4dd371f290c6a08d89c3366477d89b8f286c2659cac790e7ffd5a91a2f2649b5223a8ac3cefc34336d8c79c6d341e32bffef8a68f0658a7bba3ab441d8ca8498c47b53c0c545ce080871a7a6f3a08be6b6105387231b7e61bf00df4c19e6933e5a1e36b31ea9565282fa28cd7efc7a097657e97f0bf054e237ae910199845ef00dac9a628030b55a03af65031d5e9adb664db51751ecf4444acd312a01abcdf505ace79794e3d168b219c40f78cdd2a6af791d199d1747cf1fe4298df864c89769a6841802c993c3b7bfaebb36510ab078638a4b967a33cddf7be0f327b6371c6122dbad71eae3b53b298ccd00f22a43ea946577e51ca184b11bbe2335a902cd17a95320894faa8466e0c40e7db91ba52d93146332057a3dbe4a2be7cb4511f2b0c25438f3ffce795f6bc04656adc31e68e801d8243ea4402d938f0591934cd357f646bc570239dc4f52e63d32d70bf8c31c929d63ce09d5277b52462e9a9cb732cc92755c61d1f55d1ea0baba3ddb7967b6317c98bd9044d48cdc724b62e7e869cc9e2bab23800bb256558796e91ceb7c3e453b6b1420d45b4d96518ff417ac257ed3a5ec502d6875826c6a6de3b494293d36a383daa3cfe546e6aaa357124c8e6b99adc6f1c052cf2b4f2ce7318dbd973a6b7c8917007b990035472e93c20fcb1a5909e10d2012e8d86595addd8eadeebd4e8e24d31f21a002eceed9b10d3f05137982e6ac37f0e711166c67ef9eae554e46a0fd17e80821d471684cb8dd2263df63e07dd6ec33f45eca7bbd6da706f476bb7cbbea437a45ff2e7d2eb019020e057deb4d9427253948e988556190fef4bc15e8075518bd340a89f428a2a9252d0d316bffedd00cdb56db5cdad241de7da9bdad895f4f1a157dec97e19575d4e980e6273aead031051109bff2c9a1eeb6c41993e810d0d910e1be2c029164566ae503e8a7920ddcddffd978454d9a76c8959261cd7083424fbd677c329e60f5d7c4f276eeed70558baab4517c6613bddba491e1df88509df7994f4f1ac551f758a61a99b603e1e3389a03103f1cf50c157a7cd9c75f203133fdde6d610d1da51711e319a7dd49cf0c552c7e357826aba19ebc122cfdd94729727c9db07fad473990b5bd6a828e622e65996307818fe2a598ba54b76b6527017f91a8af21e925e9a8f84ea7fa9ae1c752b0875d4018f94af1dd6610b0aa19e4ad855705fdb9864de31495054e5397fea2dbb1ddbbaa37b7308c12ded49265fa83c0705b1b06b48dc872572d85a3cbfcdf81ab32fc2be515dd311ec9e004526a89aabb5881b6f5d2df785fc7a771c4e890093b020b854b8b8033537bdbe7295d47ab02a539b39244b18f747abda4cbeb3edd2af6ee9eb142a2fa7999cd2533fa462c0be94be3d30ac51f5deecd26282d70643fb5af605f61caee58cebfa5b56a0d939fcdbd30ff4da391e3cce2de23d5ae0644e856b19c118177b7bfb74ffad4244a86f999816034d44bcebc01b4040c812bff36e97bb27a4efd6017c00b496114b78814e7bd3ce8dfb7e665349012f96f3b3a4872a5e7c3b9e8197cdfc1e93864446dc6adcedc904c3cf270825a96c5029ebbc5f81784154aa0ab971e5923839c58fcb9f59b8855a441bc84f4fad897c2bd4b5684b9d0978a8dde0f84bb3f67455afe92c60c875f6e300a4a9059209836feb1a31d7315720017798c19d0850ee6b43cfc290923d53270a56a605db6efe6cab753cb2d99cd35a746b8e67e3ca007cd7b9d247aa2df7969558b6cefe1c65a8a230e96cfa6d1afa30b38f2aeae44300d861dc2474da7c983e155baf8eb421ba4be7e874182a5f87591b746492123eadc242508596c52ad261372555e1a8db0825049a56b75739883a0dbe835de65bca21c5d0096470baabab187420bb7cfa18aa117f935c9601537004ce25c2d312fc7376cb1e725f84aa7843f8ae5092f772678918988c496f9f878b33ca455afcab33dfb233450411455543f36d65ad6b9bd9e5e5f4ab03dcb2dd1b8fcf7a00915cff6b15f660e0f902de9324ec5f0ecefb6dc278365d37c1440d3022c3c5464988129376c63a88a47950abed8991598a17bf894fbbf767c5b98463ad35bbaebdc32a034521566d9c0f6818bde3c0e025873ba4cd142e065289df207fa3b1ac3684b21511a638f2583b91442679526539b06ecf80dd55b5e04b79f12a8c6bc17c4327536cb346d9519448c8b7c8ca4b3f39c543fb55f2d8f754e1403e5e3e70d7951648a6d7246520901b00d2409cf49e79dbccad3ca9f2105ca1a81b97ae5cd0e0fb5f509046383db564a7167f4f13eff71ea416efdf93c3c9379342d74fc800bd33221a5d20c5118ad205e4d3550b9c381a64a8fe08307a111f9c548b7754ef907a1b34cc488e4476dfc7ddfb534e3bc33ba903d5b85abbcad61dc132c985e5e12cb5603c22163f0fea476245ae7e471f0ecb98056465015cd7b2094bbacd9c55c78b0d4b41df69cc8e0f0240db0e36a21066e60b90f1c35db0d3ad54dfbe6b34d6f69c682c5c7d1337a94e8b7d608302cce56e66c283aa33de0d1dec8cbaeb24d7a2ae11461fb1aa73809773ea27d4a7b0d1a1957d9d0e75dbc8eb516655949c45f9b2e5239bdfc8ce4f7da8c5c6740748a6102636a078aa16919641d110e011ae763b4259433014a440817874cf19b810fd4a77622061373ae19f3c716f560b7da2a7c73bc85b04921e23819b940e209621b12794af60a3a543768fabe37f003009a8c26f7dc91f1422d4429ed7f9d744cdd4b552afef75d241acda04ffc39672159ee248e602dab7192449e2ed4552995c258f00a476346e36a29a0126bc249040faa57c9380bdd74b83f62c56790920574433432f8d65c5cd185e24fad13127265c6a5ef8db4f114493d5cfa61d91664981408e93ad60756f5e84e4ee9b42b33024cf84a86ae4d19ea477414eab51d79d9d1537935edaf987e5acc56482efa0f904337204c835b4b4563925d29ae0f0ddc84ff2810a2bceb15e444b0f207e9ceb4b44aba06ba8172029c9e1b474b55e84c34f33ff47d9617628c9ea50eca5f3718a61860dddc2955c9f780976c1455d2cfea17050807a6e40c5cc27bc5fcc41364218b59f970babd407e2119be9278930dad53c475c7521abc5c987a3e277f2a402e8aef81fe9c72123867e8684c26f2b0858fc262460380199309eca2d2fa4452d3fe689d0f363ce52d3b4e90cbc8b95d7ab349f80a22dfcc09fb1718869c29451acd0d772af2e3626686915f95c4aebe95a79f5e9d15ff7cc65745c9cacda0bf0be02634d7372e30ea2efafc34849a7bdd530cbd8746a8d2d306bcdc26f57368ad1ffbff9e6ee6f7c11dd18f306e443c5ba0da3d4e1ba27537efc47a227c68ea0872d3fe08fdd361f4395e420fec76a815744f057cfeb40ffaf9a7cb47cb48ea24c2f8599c4dbd148a6ce83b5b65f66715b9b53e9856a845250eabf61c48da130af5b039e2c66cb88b9cb9a29b418d226355520f2b8b44c1be151a242a5ce80ac1f544c663d0a8f600b317a058e7038105326fa1bc05512bd0f53a7cf76f387a51a8fc27a6d43876f0984b5d19c1202b0536531cd32b962a609854270dea9409c3f81f853438e5df63339d006636acc96a4b48a7f967dd6778e5af4cf433c25f1eccf707936677d9616c54b1c7ae6e023d58946ab420ec8a1fc4951432b48a256a0bcad64dc4b60ef32bba9ac5912f7f8544808e8fbf8c3a5e1d4ca751d4b603af9fe119eabc6923205815e0e748b7e74af9543b0faa851f3cd81d2cd9fa0ca0f66f84f9f0b55ac3f1dbaeaeb639cee3955e5898be4a9fe2c1de50cb509056a54663fa9ee9174f946c9ccd2ab9cd3c1b6d5bd4ce2307a22bf5152dae40fd5ab9a8638d2f5c49113e9b84ba7c786cc836dad80f04c64a55a1e166fcfa30a9e185235783d4d2b5686a8679bccd7b7f3cdde4bd5263307981ed8cb904daa9fcb2b1bf2725b7d2c21bc034641c454b6c5eb794f2e513e8feeeb7fef78e74325e97e484bcbbfde4d8f4e7ad2e230b6f9df76df160103b763f64a64006e2f0533756c67bfc8dd1905fb988f9bd16486f78cea603ed1b0463a6ab6259d0487794efb800abd0e2595fbf334a21fb4023d467ad0bd3824d9536998a94513c08317eee853a1d2004bdd8612ad62ccfe8c524d15a436808ef177782cbe431316945989c851bd7d5392684ab66d322205555ea1e9ef7cb6549b1afc834f90099b4db6a627f4ce3d5cabce906aeea0bd8d0fcabb541cf0283a38c65d38e7cc65b321d7da63d75490affa691dbcbc3f0dea1e2f1000b72174845cf210ba0148b5f283158d1853d0f5b1f0d04908605d81a102e4366c489119e76a36bd8346a588de13f844c204c3faf7418c88fa558cc7465092a4f33bbf96c8030cca2102534cda470877ed64be0f044f0673ef066d3d4e79a2d2321b1d69aa99dcbd1fdee2807b8ebebce6ee2fb05d8bc690731d4f522df0ad44b5613b00f3a13f1cdc36d3b2366d937a6b2b897d678a554512e4d3a469580a72d9a8890b57ab9c4fe2a497509f662f46d6876bc4b6bd3b283b077ab2851252f32b5b87d73b3b8cecdfa2bdea1b0ada98a59c3724cfb6d0a077ffa4fb15b20f3c26e4c7f312b3797a0faba7fd4dbe3de674d48c2c16e9be544637f2fc7fa9a7b468eec9ed72c5fa5e0f39f5f88c12b1c6b0cadb3920d87caa224ae515378bb5024de3fe34efc5782f6ae8186a6a8aee9fe5d994ea720564189892ebf3e1baaf9c623ee557ce348b648f009fd17d994d08809b49ae8ef33d2615ca8816e2c90fe9228a0fc4a2b5ac74084dfcbc19c045671d9a79f3ec144069263c39dcdf294e8f2b4fee1a01ab43f4531aaf88e39260522921a658fe0b1648467281d42b7df3e4d8eb00b67fa7fa6bebe128d65f7236401ffb4f5c592a375a02f7e08b4c198880b7f5d828511e28215f4c1912d1f23fb77e71e56c62042bf4c856e670d5ae84f934fad4fe4a8065db19b5c0cd0f94d53ed1100907cd2b5ccf12fa04134c8f7194fc64a796a0613befa7b8e0735cdcaf3c94600accdef2524f8f6bb9e1153ec71a6abd9080175302e585bfc8844e3c263744ec9b1a3c1b94dc7a268878c45f0dc00c80505903b85343304519ae5825c2c57f101a7e58e9ad1c8fb4e028de42ae4c5e37377ffa13b58f33f334200',
+ '18df82a54c94b4569bbf2c4af0723ed1672615b9a8b7a67274b0e6707dc93bd17bae31407c026f197ba4e9cd3531578938cae5123d172cf4b78b61dbaceacc41c4097c49a0d63aeb6c97bb52b8771a82833e853e996036292039a42b6d97fb161c79ca8a5f16fc1696210a9f204c6f06710b5b05659aab5ad441192867d7b09aaa8584c962cc9fe020c93e7e16b83e5b2ab8d12f49cd75cffe2b279943b2d31397b510cf50ff0a923318bfb442c46fcad5cd4d83ec027bd0c4803548a8304dca0a91d764d2b82573f695f60c4b77ea9b9bd239caf741a5a54ec7adfb3f5a04072ca2414f90fed8cd92c8494ddada9716a350fccc1190db95c588f67bb037e112246fb75a31d90be62e39213e96f35e8316cffe51e3f905e9514c7890a2cfcc321b809f4b5e51a608f371e7a928cc28291bd5a72115830bea19999b01bd2baeb0395e62ebbe6f917909f70154376ddb51dbec5f034e36d5dd46fac798aa526dd4a5906902fa3ab5819753d9076cdc61437d9b8ec1361b4c0dfff4641b114cf3e6889e1b58b9bbf86ac50ed58c6f23a0472a6b9c21763956c16d11da539922262e0911dfb4a4f8437abdaf5faae74a82a50ae2f1ecb699dc40b8d89108ebdbf0f451701fe062fb7ffba4bede287c57eea4448af5e99d41c7d307d1f202af7f387f874342a29ccc9233a5c3bacfd754cb8d01eb11e2d43bfdc2828563088c17e618d413b0c3fa71666be5475a67a04803a8688bab9d038f6855537b4de42aaae1076066d00b23f4e1ea8fd228b87e3c7d3da2f42de4d143efd49f3b195c3240139452c70c41c05cedfac9ea8b891a372194d6aefd7de6617986914e2d394ce16307d3bbcb2f78b271e1bb19eba31c41d7f52d3f8530ebf0f0b44e3bf3421f96b9a70acc769bf4fd54e88fe6b1cf2b6287a7cf312bc788f93ba6018ad1415466fdbd2081734edc4580576ad943d3efa319f3e30c5908648342a4d0c431fc925a17913c622b10d793dc76767b0a77120b7521915676bd2896edf6e3707a3d8279f06b87f806a88dee508cdb536e8539a384790399eaac7b3a24e3631614cacccb6e9329ca6de0a75ec4e3c1ead8c30e722c425e5c1c9e0678cfb4783f676b17587a504961c67ecdeb20c14fc6aefb398056c6cd28765a7157d6b24972dbea0b29fdec0f437a4ba69e4c6fad7159f362d5eb4b76845faa63e02122ff37d80e5145ddada4faf20fdb7e313504734274307ad11a81f83f54841a984fc116c69e91b404dc300e95921393b55a7c52d0454b76f27b170c7f217d0d2480b8980d63727f58c0da05ca9bf7e6c1283c986a305cd134b5604985d9f6c1abfc0c4415259dadc3a3cb69fbf42f7e3ee56dcc7afb0b9381128336ba44963f160ce4a246abba462ccb2bc18f63626412da3677676fffc5c0d8a85c8629068e4ef8683b09bf70537a812196eeb1389e274fc0209954e16fd950f9415252eeb63a08c296c42767da970dd56f80a65b36638c324f78725897b3c29b6f8485f4c0c184173ce1ac48e66ab770d4ac097033b0d8b58d6c900d473876b96e868bc3b3cdb392b3c616bb7cdbc71a4ddda4229ef57d7160dd78a7864fb379c4be2c019745de5885dd2d67a6d284fa63783d167e1ac18d5333f0cf5de0c303fb962f5774104d94398cb9f56b3738399de69df7db06ed32ebd6c12dd2d4ec809b745e6c5318486c583d810cd4f229fe848f8c6bbea34887b22eb368f01177182ac27fe93b44170869574e55e7ec9f729edbd11a2ed81cb52fa48d29bc80acf232e75b75357c0191f442e878ae0be4bd763336ae338dafe3ea9e19174009d2373a4bbab948a84f2f8265171c31383f0691fd81ccd5aa4b3a6c851ddb8395320ecb56645c7cb14a099a2aa3e9775cf77579a27b1e1d1836e23cc2621c8d0a15a06c702007d97d3748c4f85389885d5534b58bec4c12bdb802e2bbb0836752c115a501b76268f561138838f0a16c25a168cd1f9cfebc821bc2e7daceb818537f94fe71f21430010f936f5042dc2b9a233c49c552db244fa54bd2868662a8f79645002897c6398a88f000a911dfcea622d6b2e7d88b510da0c52b269e2920245051328f6e1f8c761551c4ab25555d30e85e90ecf4b74ba252587b24dfb787c4f3e01c0c41c830affede41be46e4de1fbbfd693c6f071bf8042a48e711b1e5bec8194708d6682d1b8bc1014b3b345b5de4dac73f1022c8f6fd661dd7fcc242fa17253aecf6a88ca4041f8cb8cdeedbd1aa1f315da1b15a8387327f5c6790a760282c7d1e69305431b023686fc4ba676357f130fee85bda89e8b6f8de1cc31bd842559908f7a78da9d8f21fd6e83f06fb327a4b8aafc94fef691c0fc5e104a74aaec8151068b640f6c4b739570026c08182e20a69bca2c19d52894d797ffb529eb5ae79a0830474ffbc983c59d6169ddd9051f503d78f397aeb273862be4f24bc9d2f4e1f113a31ac08bdb24430b8a6f8a4ee95c0ca38bd707b1e5ae965a8258cae721bf5daff7fe5ef4f227fd7b4e2b805e171095c4458664c963b743eb05ef732a06889a6fc6792ba76157493b15a06fd531144545c0f45a4b6616d0f0cd6e36fe0be453dd8f09bb259128a2b5714cbd26cfedb7b27ecf3cca6563aa167953aae5ba390673c23e81c21a12969501aedcd53bf34994ef6590c8fa245bc67a4e23738a2d2ebd0066243f54ab9134174563631dcb97678355fab99cbf427b40ac552a04074923ba4ef6efe96a2f2d528ec552dded0d94eb2eef3eb5bb1acf7cfc947bb07dc24260278e4640c4dceb2409971704ce38b7774ec2aaedae311d8fcd85db07e7369382ae6ee4e35206f80c343d421ae59559c83439909cef11ffe98d9dea82da1281a231fd4e497849ce8bad4c4698d9afd65e8d98825c1459e12abb310ca9dcf2b73f50dde50bce21f912c338a706f0e4b79aa983f293a4656bb3e503c3f556338eca99754b72ca0be2521486e5ddf1d0981d166053ec25c0fa25797a92eddc7182d45a47d446d284249a2fbb758622ffd24662d248ce0ef906f0170a1c0be6193ddd41ea21c09e072a7b534af8b82acf00b70d4e23a1c67a2c941c36a1d7f9b70a45bec0b6a883218e765db9c1cc6fcabdef7438871fe2d0d5821784d6ca8dc792ce4f600547085fab1b7d8c733b687f34404625d580fa799c5a87892d6c28b741a7624c9024b40e2abb51378f9dbb593e59d19ab18d63e0db8dea9818254122a191a5ead9da0cd96806675f795bcef516acd50b8d8db5a33d8ccf46298e6d863cfd78cf54df893ded6d2e48b30e29bf77b99efcec1a764d1ce79417c420045e6e4b596ea39dafa845602497df2d3234bbf0bde33fbc1c2b041ee7918a62bc17d01bc64d18ace6a4ea7fd8d150219ed16df2a052fadb1de98da31827eeceeb4eecef5def5675c4b0671cb969b893c631f82fe4c0cff001f51414c46f63dd28602f267ed9df90d05725e754e57aec2eb3051909c101a35eca21d46acc8c15e9f81161a77106868005b14029c919a35ef0ff4e7db8526d8af5417289b3b9f1a6833e1176597dbf6a5883b7a6790741cd685120bf3b14a72cf2fd6f9fd98008e47e0fc65a07a7a3d5ac37ce6999d6500085a5305caddaf8ab4fb03c1b9270b43a54f0e0c0f016ec788d27f4d19009568ed5661dc4a507da8c6804589b730e9c0eb49c0159974df1c987eebb7cf012bdced41e1985a54db546d864558dffbc18d7f96ba87281af4c2f08f682ca3e850e470e27e42e12ff11711d4aa3619d0bc33cbfea36aa33cd643facda0b57dfc2b09de02dee1c92ae8781a331d2b4df60239354923c7f122ad271d0038594586e5d29f69a97df98de800f06b464063b6ba27273ee4a5fc14a0f4c0efdb21e3cea5c81bdf881f59a01835fb44cc7c43580c8608a68b0cab5ad7344d632f133f1c9471ba2c22cdd1aba1a1a38658e8d52421dc4049c304e63b7b6e2b24dc3a42b8da58e517219149f5abcc51f8918a026121b437ef32969500b42bc2fa8b9bd2e99e02026a2a73a9c75d3d6b63206cb0593493546080c9a1f2a9f27aade440d8f908f97bebe87ad969df7c5b8fac96c8528003016356a6f056834bbb048e303d2e41c4b66300aec1235a118744de0e3395308ba6c25c336b7769beedc83273e7d171eb1d991d174a3df685594a5eded76a6ab4a943397afa9c84d478c17712c029bff61657e5be5afeda5e3768d30e9e18560473af9583860cfc14c4b70614af80546e0b6300aadaaf2f278b68e5a46fe91e056ccd1a54f510f807397286819b1c58db638617e3b3981c65bc103daa3123e73ffc676ef731f0a03340b9a0616e46f2c38688d272cb01caa232298327ce0fdc398c43397420f41d223f56fcbd0f464ab0dc31d85e0c326065557a5f242df5227b822b24d1ac64975ca00f419c66648929cd49d2af5d7207378dcd77a8361de48d48e4d618bc873567c9ad17075be8d7b7c197676ea50be79f42e876bbd1ce48f84439f85137a620cd24f82805d6195be020e440a2ce432725dc940265e6527643e0f132820709801617b7199ebf413e2f52f80453bf63f051c399c3af5def97f683d32bb513f87cc80cb495dafea6a729bf9b5c8960b269ac5fbf63a01eeed39994a98cf143bb3c6d6ab542c27b90bf58cf95f04d997abbbd19ce8741129751b57d39fc3f7f99e98c9983c85d1f49ae43ebad67a652010d0c578dd9981f313ad1a54c24a6afdcabae01e6d0b4d0189b7279ad6a9d7391882282c501b02e06b57674a9ac2ebbcaa95a0aa502cd7744ae6ee35ba039e4705386033ea78e285ceb2b521a3bdf8bac0c181c8a05b2fd1611be8f7fb2828de698c040c0723cf37c0478a76579c208c9fb709f5b826b48d6e9eae7e34780573d7c59a3130ac179ae27e5db5310de186b9febfbe120fe42cc617b514e083c28bb29d893fe1825a0397cfb56aca53ba4d8201098e48875d23f9ef837879cbdeaaac7c5784b447052672c418138e3e29559a568de2c61d7df79cbe90057ad0b83507da9b9c035ab767e5f40c2be6fdad136567a3680542d53c00ed61486e02fddf6740bc02694def4c73c7e8208f42b42b75cce06a9097e155d8f48dbdcdf30dee3d473f444080fb48aec852adc18decf24dfecb77027d20d9877c7bd2152167061c469bde43a489d0f97dd200383fa5fc4065db29b573223b8eed9221ce0ea7ab66c7683ccd190999d630cce45de87dce0faa85ef240a43f071b08729632b3e32bf521aec576f0907d7b9c9a69d18e2dc0e355223f8b3349cb27db1557f079950887f3a697d16e68f80515ee3903153aace8ec6848dfe4294d3ada7327c14477954973d40a89150a542afd317b1d27ebec31f697d6e5c1c7d57a8ef4ce4d1d711a64299ee647ea5ed911decbf8c6c928c7e7fb16b144d10baa127e01133d0b6bde009d6df2b2f74cd1e33f2478a705d98732814fa1a51cde16283300bf39174d2a4358aaf77343bd82c7a9a4c368e2e6723912a96eb0ab265fe5335b9ae848ff4658e1bbbd31d69735e6a3b3a0b06937d125358cdf0c85cdce7008786cf3a66a7a65c00a0c95b9c43367b5b9d827a0b4eb1d7360be62b2b9abf239c1fd0139740e937efdac47f32ac173671e337691460a4a528ed1593bd43e924f9c15bb0a064909a2fe64ee8cb32a32424a794544d374d45c7ce19a2704ce79d1737bc9200974f0b474fe328d46b4cbcda5723bbf4472e21993b5cc7e33a5ce47adf8d28363d76f3cc740bdfdcaa9679098e6010c824c9c103b1798494809ba3ab2547e3cfebaafc35ef334e4294f2d14899c3f33744a2bc9ddcda68f2b436531ea577752c065d7d0a3df424a4aef46e0e15d9c3a01b4b7ccdfa09d58c49bf6b4bdc862cd931f10ecfdcb8d3815d0d97d09f1c02b13d167a2ab5826acfb58954b9371eab65e32829ed480bdb5723c0f716720540d91ea64d2a7bde894b8c46c7fd418b51409e4546e91c77bca4979104665b200e96247d6e43d968c17e325a0d7e8866bef7b7eafe49a666f7e82d003836a6e6bc67030e460d4adb93e64c45cdc3783b54f9e47ba89582540d9058910b1dd1d3eaca2edb6cfd3c8181023e9c6142ad73b3d59899ee433ce96e3baec6157288720a4e0c575b9a4b0509508dc06092749a4c0948a827e94271ba58a411bfdb274bab4120249a4ae2d0ad5fce4454397a298e137948fdde1fa75265bcf692ce3acb4e720ea591a5907eed9e54aab49e3aac1a72bedec8b840d4c6e17df716a23f6b542f3c6cea20d05a3a8ad575fa27129c41a56ddc310e3284986a4b95b42da1c65cf71ddcfc532f0d24a3a508ad9abe74c42a1ae39f268151375eca5503970e46d9583798509309022c876805373f8abff28f4a678bb799b5343d5eb78d94d17759c12e018f970ad3c29472ee3fabf3f85d3380fa8f28081b1f949d2faa9da7d24ed045baf1a580fc77597a161f66a69874b532ff020e490d49e2fa3fe317ddd238f433272f6517d6cb44d22f6ed60f2ad992f4f79cc0d90653a6ea7aefa9f00198ab5ad8a14c4c1d3ef51f9ca6909a29ec4b3b66b7e63490bd6f023308ba9afafe744141ffc17a1a32e8b6e04f1cd4003d4c6aeedbf4e826f789c62ac6c01b08dadd7ae5837b4e68577e3c9cd0e149683d2527d27153605392b5aab4b2611cb5ae455c45e4c015820d441514c46466ac7c53e6c420b839810f41245344040cabf94a89e59c368be1d4c8d4faa24cb8576b572c366269d049cddac799142f57363c6b78470254fe123e7af0b0e2d0baf39aad5cabaf0ec1086ed118f87b59a90ef826abecbf8208adbae8fcda1eb6fafb8ad51c967f0d986762c27cf402096e70acea99393c7427feca815dd8ae55d4f9ac5cd0794aeca2c13a3d780e40b51415db45c4df0171d8900de2a82f2a33e5588fb32cd6ab328cc06590aec9afe33658f3b6b320972196fbc56b40601ae7b8a2956666311865c2cf656a6598b82a41d496bbe8b075f9efbca1a9cafde8d7ab626de5211b0afcc158ca39410df1e0c2f334d3e9f116714f3d232cbc6b2c8a5a1269bde1f70b7e24e7047bd59bd5bd364f4e8d1b85010e3207ba42892a4e86312d296f3d4782505a1494a087b4dc061843545601cece5734a6e7a9a5ddd741bd3d2d67779f1819cd4c0b55cc0b9d51e579d6ad4140c6b2d3853bc0b6d85f437aacd635456025411b07c8fa36e262273a4d56113e8d8',
+ 'd9db535f11ed31c906af44c6b10a631fc6b004c289a4b066e3fcb472b5e61a1b6ce9d7cdd66d46cd347ed51c90cdff9e50f77a7b0c0ab1c69d46745d7620ee10388dd805807ced5a03a49e0dde810619279920e2c80438d38d2b9f467bd0a3a4644fbecdb8230eba9eab0561432e62d8721e60be66992dd7dc359df66cfd202018b7f2ee266991332b1b74df69ddfa235a0ca1d68dd27818b1ecd735f0b04dcc7e4eda3071565e0ed37a5250f596b64207ed4af3e6b501df35d7b3caaff012efbb9bdf5a41f25e93bd52077c925f7e7ca048c5dd184db1738f7e9a7f52c557d2fa26693640467709122afb2be6423a1b4ea6795ccc9f6e1ee869f51a813704be6178e18a145992baf98b96259946d265388ece38aab5212668212a64e34f01f91818ad1f653398ec9bfc403154732ea387882c385996b3d4362da325211436cb488e37bddcdd6fe81f056119bfd2371c621cfb26824a0a707b15c625e28f8c7e1963e62b205e01f6ae2e61a3816ad31af2d3a3c8ccda10425e62fd2bcfc6e959b21e2133dabe345d7000a8984244ebe35e348ce6e04dab91089baf0190c337a33c47529bb206f2678c029ebfac6668c0cfc81f4a65abc5a7a148436dcc8e5ca5de67e02c4f3d225a2bbdabe265da30f96d15c2bb04fc45ca50f123382b2d42a7c10533ae5a6bac7f74b738c715a82dd65fa5de00654913433d1fb62a57aacff00ca9b3e97a98771e907aab3765d6459fce00dce22f99175a9159640af50cedcae8dbc8a558cd6d7fa68e6efafc6e629be1ac22290bf53956742895a2b05c837b7d24daa99c1275e9df79e7d884776b13c1331a9da06461810d13b1b82da1784cf20b51beed6d77a663c58099a2fa484f951d2b0597ee772185a2201517e3b685701995acf3e85cfc59f9a00400fee19786f0bc2b979b637f035d4b81142b246e1ed40e3b578a0a34e99eaee3468b1e33bc3637d549740de3b0cd98c3cc80aa4f25c62432c15b6a953d3b14fc1ca9e76a5e7603cc54805d94b47970a5e9ea306fac77405bd86464697a58391728612486953988d862c83cdc36e93ced10719e17dc6ec87c45ace1f6cb7e85fe15babdcd88062ec0185290015ea66264eb1edc8fdd33265eb03bc7865633607889bd9a8860655d4e2028434c55374cd222f8d31fd64ec0c9bf1a005f4302324c2c71a3fe44de7d4822523b05432460ff5d07690fef4800851d5072c9bb706343a8dec1795dfaf677c5d6627450f608b2435487a652a74d5970e5070ee6075b25a20cda3fd24030a3b2be9ee1a234abb57ce6162d24f60e0e61e2a575eb4e83bac504d799cc3994949112d9936466b0ceb1c6fcec90baad6c974e3645f2cd41c9b6cda1cf736b881109d8fad3bc1581c68afa232bbb7d913bbb31a1c7250dfe1c206f1e29ee6075e4c21ae9e9d2c88564ae8c7132d0c622437494ce43aa952379a9f338ef66a0c4d365df8e1dcf072472c683d048f51ec84c6b7ea500a9ed16a9f960d66802b1dba79a30d1b6db5ac6b679e827b7520ceb1d47f70e484b9e4174b9bee0ddeb1d242708149afe1d4ad3fa70c25dc51118f37fb107f161b72f0ba191153d96486c051d5893d13bbe5eb30452195cbb57ca483a51b736941628dbfa286ed7db4224e84f31b55eb6e51fcf9cc8f60be14fab5216ce0ec52990121b4527d095f401cf34873c8329b7bb138d8c7a60527e1e427adbd11486684c74324b35445f28acf4518b21041a8668c4569b0f5f19dbef17265001d2629973d688d4fbd11dc16c1ca72387401a6a13ba939030fb4841e8b3bf3e074f3f032cc085a8217fb70568ce9e1916400fade57baa34843dc8f6319dae6d8a9407efa0bb918e4e56163dc9929e34770be3950591054fb42c7f42f35d5aa533a7a0a5f74e144f2a5f20b0b6f00f3b52a97c6b9b840aa967d0566f567c2aaeaa92f46d580276b35ea1bea8587159f74e23c476d1da2309463235c48181884f965de98feb5e1f829224d96bd69ce4301480a100cd1db738f85905889df4db529f0e6952daea77846df1574259fa18ad4cc8fd4fb7d42dff264dac04d15e8a7d6eaf5b004a2ee781980f227509115e389d04bfacb888ef24781714804646ff99ab47a6df65b3d540bb86204c0d6c1c97af3bcf5ff8c0646f95be23432334a16dfedf34385300aa8a7d5f3b0f8e90bb932b7515b09e04f4aa264ac39791b0d8c30d7eec523c9dcf2786a15905b307a4f4b9ba78e7d2dc079bd2c4dbc2b8430c61832cb6774713aadf7f546477a5583e820013e34bbed1050c4233530dbf74a51006f17aed9ce9a57a108143ad8b0bf005a9873b25876a57c31e9f13c0cfcc0b983ed620fd64a727bfe02fdeef8ea824445b1f69bd583063880d1102230fd2a7c1d5e291efc7d6977798661d85d6b84108bfe2555b57aa9225b78f0a7ea80edf53701eb30becfd5f6adf2ee8b68ac3af197dd8e747ac604c60c5241218c1081c15ba906f99fca4e6605e9027ccd34fd53f3c0908c880dafa03dfd5033a8349c7d05842aee01e539421bd93c20dd8e61d42a47e9e28fba102d4acacc32c1658464cf53c56297b93d174a340a1c2c20feee95e3e92ad443cce9cd5b03b36a1bd0351378450bc3aeb0523b89dad32b853b0fd1251cda08433c42201e953a04c0164a7c62485e185e498f4a5b5cca7e338cc4767c03649e20f4e30d960f4b141abd3154b24dce08104f3b0128de7676a48a88c0692b4ef8756d38c051c04b2a543b7656e8a3c0058d67c1fa62fe7bb760bad9797cf31db9a93bbff2c256ceba351785dd6608f8a32df9c080aa2b2eb2f08432fc17f9456444b6d51f68415a4605e7ec62caebcc636a9bb34e6ee323fbcc7d31b2cabc5a6ad08534a0d40e62507f13067177aabfc8dbb426ce11c4dff46d0c324815bab13fc51d4b21661c6b5be93f80a40ce44745e9d9776a332ed72f4ac7d12689530e75aaf5850bcf09f9eadcb3d754eb16f75d2d8241ad53bf9e1efe267ad88633b68eea947eda4f45826fe216871ea2c144911f350221f3c59945efaebbc8accb5e0e1c70a517975d996d61ddfc6bc451b3642768254283ff5d36a7c700915d984955a910744e17c0a3660480b3c6b066c858ee9247a994bb5e63bf15e5ad29091d08290a78e840fd34dc129549ba077ef7e1cd59d5a19698114f8e11c7869fbc4d8804b52f60c391c25244923aa029f8d6815c255bd51a041a7cad2142b812205f77d4a71461effcd04af2ede323a862c8da036b46ae8f8dde5d84ead3ac20b3d73a1166a44656bce338a62effbef34b533e6bb222b87793d174bdc4f6fd6c052951336a7b9868407fade6dbccdcab211a30ed807c6d62c49844a05629062492ea5fc328e6c2c5260a0d3d5af70730726254116cb047c18ea76fe4f4e6611acb7eb83938927fddec26f90242eab913bde00a7fbd6ad224506338e447cb988f3d7aed1e0ffb0a12f13ad3ee1a348cc57207e671190896fbc8604236c5251722675380da158d0c14d3da30495750dce61d3e5aae0625f0c845331e54e39f5754b79cf605ffb4f054126990bc70cc33c64e17e97efb2f9a0a55302fb729a6f396ccabcf2a052f51412b8d67affd032c165319157c6e91dd42870bf8e60db8567905247eaefc48a97c8d9a47ac62921036eab6611ed7094501491afc5a16600a7c0f8b771d1b9d5356c734933a27f59aa863744eeedad26bb719e1bc9bca1a7a003a456aed0999f97056d0ecc1e3aa35fb6bd75164c0a9abe487e4bc139f644fb613e6aca73cdb0649baf3b6ebfc2c5ca05253fb095eeba00a01b87d6a1cc9d5fe2af3cec42bbd045372118400f7f87927b57ebe44dc14c2a815c17307a8a7c758fdd143dc6ccc7e2dfabed6b95dab35f203cc0dcfeea19e3f32942f64f9aa7e56fb13ae586685ce29e350116d9390fdbfdba08bd2fd3e9d4ff6a5251a563e6568a13f50b0bf859ee79a79d6a640565ac19ba09b269e384a4ac078d68064d0371e9ec8ed7dbe284ad7ae9098fbda77b7a7c250de03382b1fc03e93c64f9c0c4dd93224d728090a5c8a8e38dac6c8519ee2fe15a7215183a840af6a66c0724f342909f7a04826886e60b7e71e9fa0909af39d8388e970de5e3cb1715a9b6e1564ae9495db467d4eeb0aca0fc6fee9a1266a2f0f15d42ca7bc24e35cf42915fb69ba50a3eabb93ba4cd92327766d09ffbbb7313cc069db5c3c899361d601d07daddccc96411ff0571e6c51d2af629828d55b32810b13137879eb604472be43f8cb9676e180dc2991da34e475d457faa90dd6a17068884b98debad8595ca6d7092d67de0a2c7896170ab349a587e5d7fccfaa0c8f6b020a19e5884a34f664003b5de16094a68dcc432a77ffd62c7486908555e2227548d58828d4544bc26e65ebd763ad05818432a2a3e3857c548cad00e96ab0fe39a51470131f8522533e6eb7248f21d13fe47fb21196692c72b6a1ca464907770aaf6d5e3eca3b4d2b4c8119cfc45d8c4436fa0dfe01195b195b1b59367144017ad0469ef6850520bd21537023a3ceab30e447f2c3a4148cbb02504a2483f5320cb016dee4a061418b554c76da9de3928884d01d0cfff935ca4506f9baf1998d32b7748a93dcee240840d28a5f133ae6bd9128e9248525eae99ba5f4443abf778f6ed62e5e7afa4cd68c8c272bb4331c3281b8f3b4d439107b6e93d67adfc595653ec236df0b14205880efd9caf17ab7a9df16a9385d914b023676fca9514a4b66bfbf30801dca4310d6641ea74b602d6621b8962991ccff109bc5f36a1fcc2e066a23a7dc239b3981e59f625b32820209c2152703014ab11906e73727cbb991c6b696095dacf5584e46e84151376eb9f768c2f85f3ca8e5adf071548cc5913b2a2d9cd18f2a6733b862d53823da74d9d16e287688a4562121f0fac7e3adf17e93479e9ceb1c7f6062d1ecb34d8c32d60a4aec2e29c8d0d82770ca0dd6d548c0b49d11d7ec039c42d01ca55f28dd37231497547bbd1ab79f21088582d0ec05c5e086acff2c604c7f829b8578604dd06582cc180bbd3a68ca9110a3e36c42c6e2b9acb69f06c31e5afce703bb6e4ecd01d7719d4a9478630f1a31bb9bf389faab9277c51415ac70899177b674bd505b8c84f06ab4fd8544c10ee4231921121f852d606f1ad37ce17ff2d60450d8132a6a0de0f6e732c17c74f19070e75f8ba403ccff0affbab4ffa2937a73bc38d7a82ee4cbc83deebd78bf64df2b93195736ac03bc2f5f56050995f5ed9b337a4e634fac1955358be9c7f4db18c988e8f535e2642052a61fbdcdce9ca7d142f6c7ca50c642346a94d6f8b9c6e5c14538f6205b60b40b50827f8ab0ea21bc6748d46bb6c5dedc5983f57972aabea964a3ca945b8bffb00b28aaeee180f3b31ceb04c5f62d57b36ee3c5d8b4091bf84393f2f077385bf7226cc058d4d3866bd047d957a35f4560273ee884f26486247e93ffd34308612732e960670f64623d2e09b29f22ee10592effae0cc24f75b4822e519b237e97f6b9090b77f5a60fbd828b310b195c5104d2a0b6de882c633eeabd98d6bd0d78bc2156122a3c3af7537a56c529db2c7009aebc7388c87de71a04ec261454378c0e185b14fc2775a19571f6ff8a0cc105ce3fcd648a17c679dc8d1489b2ad7f3a52ea82a44c5bf8a6fa05e630aa12cc7ac8f23e1a50da98840cdfd144501d571c21246720b95e367007b29f18e871b157906299058ba566ae1179ace66b28519a0f62631ac182b18534e49c18e3cdf2249afd68a688ecb35080e701e07242f14c64011102d38dfc6f082877a11a7c015a7f4f178dd734f103d1a46aa2f411b89bc5acfb698dc46986080a9c420b00816183c4401c0502c2309a3e61de6f091a10baa28cdf4eca9b72aaf7749c23ffd4f26f095ce180ca897f311161d3419d8843a9ae4fa4a4f504ad676bbaeff3ce9e55876ad86ed910c9484cc9f5222a43959d190cae7a1ef837aed3f3b9edf8f1203363754d247c6a041423c4966b77afc4859f6a33c01b38a6ff671c0bf6b21c1cf499b515a2f93abc7b7c9dbfefc81697b593337a07383ac50797312b9aad83d71180b6ec5ace0774e6bdc1fc5f7a2c71759af4ebcfd473345feaff935a59dd0122ced71bcc7954afde5dbdb60b2c66047e1d2b038aae765780e8c3ddb6259c0da0baa23e6879b6dfcad30d87a35d37a317a0424bb7f4c8b0e3072a552ebf256f03fab4cecf4d828744b41deb986b35efdabad74ed865cec32edc9fb434852da7d5057b4751635c28d4778b5c5af4c54d4fd356678c600d20accfa05557f5f94a8c415222af69a856e2a3c2447b888a3def704c301472183be556b8c319a03cb9a254f60b29bf0b7407cf4f4e7a74d95422182523312a548771085577ea024c97afa6c34b70d1da12ac30aed4c868570290318ddb32e76695d86b030e59aacd471ccd467de550b8b5589e5d71f5c177dba262c2a2ca37a2e973e55dd0e8f8235a722eec1d17aa0bce855ed7a0732eb041e112803447a9bdf4ec0da27a0878e34438424093884c67e0cb08a9ead0fe94d7c4c722ea3babfc3a995a9d6448bb8186e7add09ba7c6417e921240ab8fbf99ffd607473f26bd023d14428b2a1a1e82b4f8d8287006962d0a6302387d1ff0344c9d4949cd995b547ed55c18260d406b30f44794cb253a7f16c65efdda021ef8207303c6fff4156772651db102829faf9d5282b2077421dc26249f0d4cc6ba5a2dffdd3b60d779bfba4bfdd22a2aaac45cac000caf73fab8bb1638f33fee50deb7ac1c137aa3d6c1a8e273181755e05df15946114db513993803b32c4dd9610a700076dbb7f9db11e0c113ef54a4deb0ee02cb4c4cb81b023f85a434d1286941c99544109349e524d48066c46c980471b501162a36ed6f6834147289744bd82946b32a4eb704837f0678d233b99fe024e8fcad4796d58f4fb828a4b6a1c44c355a128fe27dba4494e942fcb9d63cf02efc5df710e6f0f92d3e3ac128e42223da761bfb8861eb96eb1f573cc34cefc314619d8a0291d04b9528d34e7ca5d5fdad4d37d38d0e1908f5fb2e18fc8838c769bb43ecb941f3e4fc1a8aa31e71504812321ede7cdcb2f95a1f4017a212987b835be9959151cc53d685b9757ae14171adb70942d4485a066bfe35cf60669f6e15d5e6fb275bda26c8fcf82c3e4ffa38c45ebfc73ffb7ebec01a6796c41ebe0d85bbc4a3e7ba9dcc37e2cf3dc5481adc1a9b7ac1fc408160daf66e04298564d857701b164f1887b297c99720ac5403fa04fff2c9b5c202bb3104a1ae2d45345d0f9552d8f9d048888d33e88b5fee460117ee9054d8fa831c56d7baeb2bd89d1a128df452449',
+ 'c0a4f347f790b9985d95426bd59a30eea659a0d77d5852f8b61a0a14d79635f25de8d0afbc478658a13718b97aff026f385e5c45537afebd0f19e7ecff13e08bc7085c4c254ea36a332d2f84e64f56abdb722aba53609ee57c21ea95f26f3074856a5fc33d8d58f49be14f75227074ca16fdd3de84f2799b829ad9b8b8906ed21f78a1bf09ecec1f62b94033354ca4f37167205aee19d905abec7dfc5a60e1d01e98e9e69354a9120f1fc96727ca2e4e7518d6d699bb044b7e7f9e0adfc6d393093400e0e2abd5f62a7ab4b901d15904a979c0f98ec1439683bc04894fa98a4668b3af56fedbb9f23b32c7d0ca4b6f3d96f0a56cd414de0e43097622b2f34f0fc47dbcf0f0ca9d2fcbafcb558a1fb620c2e64cd7739a1dda45cfbd7d118b6a16a3faef55bf62c8d4615887ef493577d6b7c47ee0743d48231177a141735923849092122e7389840a8697c5471075f986fdf00332dee4a6103067ea17e145c28110adeb22152917f9c1d34b05e6c786591ad4a373b97780ca290960a662378e34adb344daab29cac89a75e2d0d413647798d6264c0ddd1e7884c4cb97fe17d0c6ee0ce3fd4071e5a099e35250e141dc07ff605364e300c843cafff291bdcdd15f5b209034d9e9b0a84786632e15393632d5d9f36442c7c65382249e3da841d6257d571527807973092bc6dd1278e3ba1fa4a9cef3331a5fcf349a9742c65c2a43db1a397d3c0975eace0c87a31327b0f7f337acbabde1dddf69c9a54a200414dffecfc3df1681ff74b6be2a8aedee5fe14bed5560e80486ada71990fabc22db226ce07cf414c959ce6d468e6f0e1e10063332908ebc6ca35e79f21ffca49fc833afb32c6541c9cb227b0a7bf87fa10b8d336e27ec42ff7b1d64bc1cf8e061233a4fb29bd9a26a9d956674b9d0475d989fd30ae02181a65810f2df4d9576523461d24390f941921651b8ea0f9d6e3cff649c6c84d6814a805faac37a7c705942cec2d0d46d252a7f7ef8d64708fd25870315fddbb24bd13c3314d06d55d55d97a9a6c030dc561b2043af9f27b8fbff79a5c725ac5ee625d4b4b96fedc7ae48791b077b69eedd4b41c964ad785670b41d71384dc1815a2c90092006c17a0201314c6c694a4cc1a42a389f680189652bbb90f2dbe0c4b0d0b85bd2ffb217d97006635df29a9e0a0d23b7e9388cfae04c9b297d39d2cfeff793d155c088463c7d4288627e208bc06d736ec885df50ecff05655fcd5e491f8b6a9433b30bae31102be50475a5bcecae6386c6b7c4348e2c406c22014edb497385cbf33e02f8c3110b9fca3ff1d996b73b276b36004ed0bb95654d3c692c74972c9f1ecb37cbf76828e13d44cc89c47043783e6a5e45b9944c7869e576e5a8dfa7383bc170d0d7fdcbd1e3ecfe7480c1c2a2c7bca5c251a0adcffd663eaded333dbdd28876af6ab83b9747a04e43d19202cc8e929c6ca1c5ff8f9126892d4c7b38566c88110fb882c25ceec6869ec0cb491f1c550decb0ec8ce3ae8d1e0ccc9fde2d90280898ca41a64862c86c2c5354e0c3f86ebee987fe9af1db03c7f376877867c6d325f3a7df30822a0cc99694150fdfaa43770c2ce172e1a0f04a8a501c4d2f96ee2ec85742a833cefc64838bf71d9cbb3e02fda97f5cdc85bc70786544a7ab89e2ecbee3545682d6fe079c3fe05421b2c6266306be9f0a13cf0166bae8cc032617277e52fb8198cb7c7889b8b9fa971742aae649888592d192c5fb59f10560f5f5a7b0ac21739c35dd80f1fe6b5825731c572f7cc4549c476b84e049459aea7fe533fbfaad72b79a89e77d1addb6f44cbbf5e6a65a5552fec305bc92ced3c84b4d95074387c71184e875d413f65c2b2d874cb3d031d0da7d0311383d72f823e296937d8f97bad17a62f29ef1a091f39be8233c01330d5c4c9170fc501b5022ca29f605e6c59220055f2585bcc29e742046432c41475301f4d7eaafd6b024ee8d6c854651f99925ac47d72f7d43cbd5430975299855ecf0fc3b46f9d419bcaeb2c90ae9d71b1509f782d0443c0d603f8d997fdb0f461e52ec274e84543e608bc2a74b9581134ff36e78d86dff07a5d9845f29ecad00324f4d02c8f55d0758ad446e12f356c98f0c9a91b752d019e2ccb2c13d017b6c700ea6347df7f85ceb3bc08525ab5e251d7b0236349ab62b5e3f9f2881c57f721fb87f2535302c25635dbf564c64a11040692ecd19edba25625bd1fdbfddc3fb8874603db848014a063dc84851c6287041fea7c02cf5e2ef3647d2a6bdf44fedf46bd2e4cde87fb31d0063c3fb7bfa2f6861f47536573872222c2a8d44b02932a9c55c823eb8af48efd1182f11281f33fdbb9d56febec4946e325f181bb95bb0f0a9877fd15f98f2c6d670f55c78a06648332bc94ed08d3e6180fbb10cb8ed51103fda434f864297e837f27ba41084ae91f22538ec359b59443f86bfdd55e5fb53b0dd367fd4b6209e1d272ab09bd3f51f20130aa196f6cb5f9686aa57374d98ff2418c02bf2fb7630b0956a4abf95223be47da7359ba77efaae85d942072c0f5dc2144a1987197fe617c6a43824d31d2d66ec7770ebb5585daf0ab6666c8b48b5b3583a123c1925087b423e437395ce3dcfe8e21a2f2843d0c09ea88a0f712b4f1f6cfadeb9024fdd038fa23354e11db347f750fa0e08269a09c8177897e6a0722dd9f045a5dce5aed83736d06ec1f2d6f5a329d9315ee804b3106bf6adf38f670526860fa8b0cec321c264e26a3c35ef0273d57f3f317356e6fe0dcdf99ee077d0db23036b85f46407b69330ef5ace4695efbcb4e18aaed8c91b63c522f17be7b6812eeb96633ed9b29d2a83a624a523d7a04640bf7081d185eed6a5d1f44802de9f118dd152946949fe93a337a1a2cef00ddcea80ebd48b41ffcf37adae300f71bd33b1c25bde5ef462355849daae8e07bfe47bcd038f4c26d7b4415f2719559663fc21148504fedd50786a84d5afad443cc8bc4dc19b5d5cfdee8c8e67ed1d761ad4a46dd9ef922950c4a0929c8f71d00eee72a92cde060af9e6e0e37792af38692301765d856103ea81c318373423d3bcc068884d418e59540304065ef25306d95101d61bafb591a7179bc1ac880a74cfa465932aac3f7095acc29e24f35105f1c66c351b56d4fb0eeff032057170dcce043072cd085f78444be053d27ff05f39eb0a3d6460076aa86f8a164ad99b33414792cc3e3b3798ea4727cf6e7d7a3c3926a294fdb79606fb00311381a7d4a0b1d55349832f0ff90e085fb703d435c37ae0fee2f141c9f6910bacd1b4c3634ba5163b92a6ca2fc238f650b6966e6a1d8382e4d045e8e863f2f6c4f9ea14905da572114faff6de1cf079c17231326071dc721d0503923a74a42a41332c84a3fe39519f27a49a652fbff97c93db0861e80e1a712a332908523110eb2681fae355ea1babc38f9e0c222cde47c29dd8b3aaa0e1a7e7db949a24210f897bf15f9a8b3f38fecb77e91b1dc090eba77e8a9fde1a2e89e305bb3813fa8ee5da84990ec7c11060e66565da4a017730e986076dad056bddc7b8862a4746f7c5e943918732f60b99bc60f991cf79fbdd30bd35653ee7ff6c3afdddb7e78cb1f253352f4ff4689270f6708d87d4b085bc262cfdcfba4b52fee5dba8291b552434be625cad349f0bae3c9d0a05822d7b9371da47f248b6cc5b705acfc60d28b852d3d3fee81a22e01b35f056530078472bb9cffc1711b2d54a82823ad7e28dd516398ffe49d079777f8a705ab977194f4ca71402863cc6d594c35cd2a3c50ce346989e45187b2c3aa2e326fe7f0f98fdbde2b04387f27b3401ddef7d74dc2e4aab9a09ba9d46c38f4ce6182becef7eb84813aba6625b575a59754324904f7a720de5d7441f57c7e0443e50c7494053e7b3e20e125e9ee4af643661b0404779c4342110cde8d6c0945391d6bbde299df4c7f6e071c4e4aa9ceac55007ccecb1a6e7f7bd3c3eec34134188b0f0f3d3e6464cf829aadc543087b2cd18137c65ac81f00ee5796ac1adc5d6cae84dd066b5450a8ff1a5ee17fed985f4c2ba98dbf2be1510906bb37cb212d90086b9bd099359c964414a42549cfb2be255e6677509c31fa2c6fc6345cc6214d1901e01e407ea501f2081203493536c40d97e325c7bfad56e9013c146811d4de61063e520996068679732cfdf694b10b6576a41b7d0c9011cf59814a45cab4de60d70b1e5d123d9e4ad45987ca94adeebd592dcaf8e9a6954fe74a58ae274dc7a902dad5a389b6b3f6aa2c333d8b79f885ede4b6c3fd4d6bdb28dbc1e9de1c14aa08220a85531e520d631982e80fc1b34f736d5156adf11ff19cca0881458c6fd455754f6b04b112efadb5e9228801a98aa82c4d29823dfc33b199bf1cf42b13b87948f6dd253b512dd865f8a59ab645c7eaa5ecd50080bb1215df38157084bae6f211a3542af9a74871be13b9ae5c0277c96ec2b1af65b70d27ef15f33105cfc04e63d7588c988fa01832dd07396f8a1aa2eb5177e5599a95666ddb4ebc4accd4fcb3f562d07474f52a48b6f6c26d273125b03b7058d8b03b6fbffa7088b02ca56b96affb8d39e19826842410efe1745bc031c333606fa27f9279e6111cc289a23960498b19456019ab69c6de0848fbeae0f49b49a28adbc27d104098ef1e4e6be4c564ba1b137aea175df1b6b210187f268d262b75c6e6b49e177c80761ff562dc1a83e0f9409c99f8f5475353d364890f4ff64180803dc653e40c6058441917adf429983a494f99deb43a0c841f7a88e9d18e3429635b686d2a72a0fc27140ad8c6b858549a0e7a171f4c7ac930a4ff64a6406bf6ccca1b184f31dae83590accb26c2a9eebf43ab609ae10679d37e6d0e32cf615f49047f03c6ec6692fbf98e1388daf55f2599be19bbba383c64815224fb8dacf94494d4ae25e4ec82e6c91f306163c33c797b22c0f61a988750383f67850db72b6be5ec85f0cdc53e8289e0b044ffbbfd076269e4de94afc1432d953453c2188c1b8207099c09993c6ba67301a80128176bea03f3e9bad690a9bbe429ec3d3991dff3b4c79c2ac0f2eec936a30f0a9012136c718c8befb56bf07dbc96b2f192b315b5864e7593e4f8984cbc12ee8e9c83aa74430d49c5b4c9b3dd8f394c61cc867941fbeccce77dc404bb63efcb0f95d27d9be904b7bf6d206075cc502d104f7267137640e697e3db909fde8829002485294de3e13a4f470682b0f499b97f941d7bb495e97727cfa5e8647c8a9d02c1221a04c44165f7cae29729a864862222ce03afcb24d31989c96893de457b79e42fec4afaa3af3b615b1a4a584f278a742aab6f96a1aeefbaac8dc8746758e816050c9af7669aacb2c6889d74c7f22b10b9df7e78ee5f0ccac5e45ccf6ec9dfcf47f78f5b28acae37bb7e8d9e5d9a8680e21c46f249680342bbf8db4faa22c3887f3972965859c258b048b5257e652f3d39fe7393614e65bae537ce95c81835da013eef3984268bae3b838ebab90687ae27d26eccb496a68bd82d9102470ea92c84947f952a876ca0c3cb84ce8bd3127c4254a30ec1c5484e0b46ed45856a8ff86fd68e69ba10d7c8cf7d31c1657f62000fd1bafa075ebd6a05101da021d06cd62951a607b1576f8ed5050043e45ff8d1e6f600eef3c788784c15bcd29ac4c24e47a250ffbeafa67b6b1f9ab666f9497a6e2e3f8e15943be156c4453d6ebc22b9514519d2209dff1708194ba99bdfd6621be6a137fb594a4d9bb831410c2af0bfccb66aff95a6dbda227ead8dc178121176abe07d036b3615a14e2badf195deba2082bf086c5eef4d40dc3ae39656af00e50a77ddcc5e71c20e027ea4bd812f40d316905d333a8bd8f9ae7e3b78ffefc90d7ec1dac4b7afdb1881b4e5de7174ec7b0e899e88ae44159361d205e7d866d2467578e47aeb22d9772868e1c2eb42058eb7052cbb4eaa7bd492e0d3718496b5368ae79b5d8d8d45a08305291963092464cc9d886970218403be3514946911da342ba85ffaf331980b1e041c205d5ce1b39bad4211d74bc6c7502959df0a4ab9e5e435b2c1d0d2593d46003b964e9f95e1c0deea22d87bac85d538039ffcb3ecc2211a24409ac201bdb76417e9cb53e985c88cd13ae853bdc5ca0bb27594efeb4f7eb03505a59319e2deba3179381c35061f41a7b8ab46631dbafed6fe87512de469a2657fa5c80a63286d08e3395b00e93187ce3a85d644e4049bf179288a2d275e7b261d0fd36ba521171eb63382e5b5abe9c52f0c75cbbf436d92941ced819ee975077c4847b63f5522d340ab365bd1eac21d04a3c7701603ee2acdc90da3a1775a79fbe3876278313ad73124e7cbe47abb4669e02dda7eb2983a94b16d0bd5a4860e3a6635091641a98af62519be63d83fd1ad462b535fcbe632776a8e32b1ed7224b644b3026002f97f1e204ecbc68fca3f6d4a42734fdc62b2e458e3a0acaeb96dee1383f70c01c52407aaf94c831de4f0286105d2b550a82c7a00fffe5c84c63a40374a5f60aa870a41299be92fef0a845d3ee7ca26c4f11f86f7557417fc232a5b3468940479a9a25920b90a338fb57c7a185af21aa607fcb9a066ca85715abeadaf513d7e0bb77cce24d328248c70390da1e1c3177047548090b66a1b80c757a5eca6d423bd3b0a0eba7cdb7941c55a96401eb593b029b76a4cb6db50a71395d290dbc09c2aaeca9936189cb86c2f519297645225e23985d5490a76ab50a9e9c21062bb5dc07d4f0c3c28d4585808ae80fc55213482f1505ffa03f4b21a04d3e30fcdfcdf0b30f7c641302adf820bc1e003539b461eeb9778e445bddb7faed4b3d3903d9687746565c9d0f8c496835c4e30f238d12272dbaeac424aadde227cc2f03bd61ab19218495a5dd68df219ae29f9c3727c1a418d6f968c139fc1ea3c8f0a335ee21614710f57694cc2213967e1e219e09d82b23d4a5785a127d770847676195073219610166575b3d7a05cfa247e97fbbbd85f6bbd53b19bec6b5ff517c84023546071660cf8f5a454fc0ed55023b03c6da7a389371fa24c441a02fef1756a6ffc5f50f341767ba6b090d99a0375e51b195d1916460fda3ac55ff4128201da9ed17a9f4852827a33710b27b89ce9b93045854ab378c8cbfff699cdc057f223387a4eb6c5ce9173c32cd773a1bb03aee488ac92b13dcef9f43e73da98c1dad6a56d5851fc0427eaee3a6267e5bc3838c0492f9bc01386d6d0336ab4fc7e00579e1103db6b91ba620d30485f153f0bd95c0daac040888504ec4beae77beb17486b0beddd94f5cd5bb88505e390a20323104c9a9ab30b4de6c10e70dc9787940ba4c482b2f8bee54a78cf077fe8839288659c7ba5a81a560ef6e1992ecfd1eb23c3c14f06174b76b0b674e98e9d624e8bf463ca5b904411dff67bc0389558a1235088cf31612a0610f9fd08ba1fbcaba025490336fc0715fc28238294789a3a8cc3917fef76e9180ddbee017cfff12e577092c2c25bd1e6c6347f5cccf9f53bb',
+ '2d819b81df848a0b7e302b768f4748374581cb60f42ed16ac91cff31b9bb1940b77fd04f2a86dc0a9e7b4b14b9ba194c71b4004f7d95913e092584c1aeec4d4ba19af0a02ba6159559345f17f43cfa6fb3e973c4b03fcde21901d13a28d2a529559aa07dca2b5d3517250e882716132972e6ccca7573abdf5f788da40eb34a6139478aecef5fdb704014016e3a918d011774b266b47853bb4551600748d8637bb76856f88288b8d13ccae0d114f39080085bcda25597ee013256f46cbd89190036c7aca66bef1bdd730f52ba9f84432cae63c6854018a4368e4deeda570e94771e2a320092dc2d1e4eaaff2fc28ecf90715445175e439ce4c0fff95afdaefb68d65a930ddf96161b3365903b65575c31baf5f161955fff923234bbf397b2765bc81f75d53b67fd5c8b06ade370281199ef0f736aceb6f4c94bb4dcad0e622fd95b4081618c950a6abf56fd31cc49164f7a6a723bf28ea4107346059048064b69f7875eb8bc6967cc351d292c5f0294dbc1ce97dae73037ef12b4dea52ee6f59404bab4e2b12b390a5723d8dc129bb3c62e038a5197e4d4ee90c40ef3a84a53e1eaa22de85523abad8c2fb34ace5b9a9627d0f2a8e8f2a396563f3e829f798dd812076fd18e99e23c3b0b627d798c72d616ff78e5c4a1cd6ecaec155bf9bccc01e2b122a546d4093fce7d8c7dfc74620b6256c312123a7aff55953c85a05b38e5c367ef6d641d463eac4ed953405b93bf739e7c36ba05d1cf60005a087a9ef80b1ef37e30d0bcc1c23d46f3062c8b2c79b19e4a5aa34afb6851f618ed71cc35fb591b76f672f9a452e92c7f9ea74b56c28420d685b7512052913f1e3b6c2f163edea87fc7321b147718ca28249be2392154eac7bfdd61661389a313fa520dc45b136143acf86c3bb832e6939fef99fc1e89c6c610fdc4f835a0ea9f330daf66da621067acac32419d9f496b178bb8c8418b7a7b8100c0fc403ddb6bed845d2544327b96d036b64eaec7bb56955787c413c2ce8d19cc9e9bbaca401f309f5f2920ea6b761f7e4088741303fc1f3b4d191b978ca5e14aa6fc2daedf630446fc99f6f4a8afa16c181a76e9eb07c01f54eee1707a6adf621f48205180dd72617d9d4fb7faea5ee9852a3cb2391c7debe26ceeb662851dfd53a61c1bed3881d82a5fb19b29cf5fdbd0e14fd7b6e5608740bac9d20a9da301883fa874ed1a3a934b04a708fc05ab2c426636be2a0a70dbb602fa2a3a5b7758073a4cc4472ca37b28b7b6663d354ac221e279d15d33b5cf8b690a28bc67a3e818c09073c415c776099e681db2064587b57ca1771607384c0919803357682f9b02f9fde92c7dab6d35e144952421a361485d35171f2e0038763ae4b2d20621570f0c1a8e470e5949f5a9d2375a2d6c3a20acbd1b3e51157d1bf3bf0ff6ea830685226fcbdc6ed8f0911e9b691ed3a8f98692c2ea3c188001406d98b18bbc5c8fad628506b545304c5167269436bb6086c237cbece02a48ae2dd0f7005923b5dacff5e3a8983c6a447cadfb216b8c9cc91cc26089f430756a2943646427c895cf3302121a4efa8cbf5c17a37685ee62aadc5b09293ed1101f7dee6845be53045620b298c39f28a1a737cea5fd4c8bbc11492c4f3488b620b472fa8a0be76b0b57e02dff0a526eaee356f9891d8808b1e6ac5ab9129886ad114e8f531e68b8b0bab99ace593173bd5b01c1d83d001049114d10d02b36eac01e59d44ae709e4fd67f4218e1702e0d5f7804e19c77d498d7a74741ae82c8a5fc3ddf2f7cc9494fa46d8ecc6ab8e5cff9f1ac7d422c75b840969ae21af410d95e82011e236cf72ae40af20fe7f9d90423b185492b6a6ef37a773e76de93c1c67756b57948e8426213bbfe8bffaf724a6b3c21fd9ebe2adac3adc4784ffe65edf5206ef943a79585b20526a8f8463bf33ef604d3429423cedabeaf5f057077c046739ff6aa477af65d8aecef985aa09f2d73a0928d88f4502e3e61a395b671dde9c4cb09f3245d3464a1b3d826594acea5498793f60916ff1c918dde572cdea76da8629ba4ead6d065de3dfb48de94d234cc1c500291063c444c7677e03028ad3073193435f7525aefb4ddb16637197a6a9fe16f39bd4c8833e3cdf8c78cfa6fd0d9315c2cd66a2440ba0593050f42f7a519794a11fa447349ec06e9538fae6fdd2f4d8c0ff488dd37425838bbd39c0a72ed466911b4a88e14c993c23ab4bf1da409e03c55fe38d6020247fae1009f03626fcb54bf98c32912f0f70bd398c709c3ed8bf5754fe4bf5f6e47521b32c672e2359a8581f33ed4d316c33ec4a830a4cb3e9340046636e99deaf8e6d0c6ace970c31683ff707631c39f6ac3646f968f1891a89479dd89f55c0cbf119858bcd7ac9cd1c88e7cf390630f7042695b73293a7b3e7b4c22686308481c8ad84560bde4175edbc59551a13fcb562419f820f3af5fc75d01b15ca32131938b2739431cb5e710362b45fac4c8679ef11cd25a7ef5b3c5c22116c146353db6bafb90de6490708850260b697b8b68ad21d8566a3b9c1bdc3330df50bcc2d0f892cbe89d36124839b25c522845f753234ed300faba10fc5d3897576587489fcb9b1ead0a01a9e2a6f524b6a1b4aa9b73d0a37e666ae2d9db3aeb2b8a26350eb24c7a5a9f47d5b4526115a5803803b6214fb00b47de8038d8d2a2d8392a26824f2f557de7790441cebea5ab2e509147be248f54097577adc86834d9ed038bbef4ac46835d25d41f7e466b34168ee1a4ace7a7d545d0478d4c84379d3032d309454bb4077adec3b1d390ccdcd353637160118ffb677f6e6bb7bf11be8d38aae5a3a62476caf4318d71364101ef3e95229a3aae52f8c35addac6169aa6ea561f8b2141e6db816cc26d9af7e5fb4b1053c9dbb8968744cab379d2395ed5ed996a6d33e1838dcb6f1f27f54cb22de5b2213fd1cb7a8649bce2e0c7e2cf3d875b774998c9884b3452194c4dece07d68d7a6d3ae6af9f440132156cc9a55a55ea52e6678815bdb641f9726c95dd8b07d87c9cc9a1e7b98d4d8572409e758d71a7e770efa92660504de80fb92290191cbcb7274bc6e683bab126e93a754409964ef3a8746830f93bc95be14cfd7c2e7fbaa89113de228a56dc94de9ce6675b4e4ff5f1a85dcc04b334e38b9a9f08579b42df7ec4402239ec305dc6ea5a75ff041b6ce0fdf26e0f71210d63248fbb1109f5ba24dfa57c2ee55cf036c559e6614231c6fae7be7d75d00e480c6d380ae74633c737b87686b5cca2eaa2905d3f3ea343a2a3e82aa0c46a6d01ebe7208e36da9d852c012bd87b2ca2bb9eedecf6c381917e8091bf4e979abdf98624f1abac319382862c3df7fb6ffcd180308193bc77dbb8f556f4fd5c0b916d6fa8f3e9a3d5a63dbaac355b9310e1cb0b0aa39c65f062f096ba64238e1757fc1ce5dfceba5156155e03aa79feaa220673dedb877c75c7a3c93e7567ba589213f26dfdd16c1f13b7c4c505e98ab61e226f1f81db5c928a412155a1104d49d0439493d68815a97f83a362b010b3af369d616f4f0e72b12b6723828fe3c3658319880a0269537aea076e5f618626001bd5df208ba77b6c1b7434579a28b9263af4213309dba22df3c18a2d72c04e533e7a5cd5d01cc32deaf6f8474875ab26586214a145759e1d2d207b5f6c4599130ee94f11a504e6710a7dd3d42340f5c07bf1c6c75dcd83d2e6d2d667c68c92ca1da14a5682c651d00a575b80a311459f1611ac37ab2a1fe9c1271fd91a0bc7d2db40c306f1f791b56b41f3cf507cf71ca7405954c631aecc3cbbbc15cf59a4d7d8363db56b0f22ab9d9c3132b3daa6b3b01c42b1277adce4c9ff3fb06eecb64384f03ee46683812d11e4983d35b0b11eb0e3fcdf574c16ede9702f3b614b78a0720d3b166cdb39b478a99d516e95c2983a65ec31de4bd9222b9cef195edc78792ffbb6bdf69cc8cc3259c511b70047caace0954b14e0b03bf0f403dcb03612280f3a366d8c4e9afecf8383e0b786bd2099028c9d93b12895ea1401e7fdeef247a0573e87a6474ca6b0a56e60d87f9f347f3a36abf323aeba775b7deda04d756273c133aecd3d3a6e764df8f4a2716a60dff627f88bbebf341958bc303ff837d30a06ca45799700b090cb982e9df2ab403bd4d505f5e8d67d509a616225759643c39646de33cb1c1790cce0713379de6d0344d256aa488100ad7ee1e6192c3d2f410e4dfb746008fde6c0465f0b8d7021d6d4845d11445b689094d25e63a278eff2cd540fc3fc43ee13245e6a7e941d0916e27f9bd372a21faa7aa71d9d6ef6107951dd9d9ad6496296878d06153411ad541d3895693a01803b7ec4b63ef8213b154a6f2771a4a5f5f15856c457c40a690ee333dc3b210654f43fdfbac0c486ca77056d3fe0ddd612a4253027ceea73d6b7b2e5c2d1ce0ce8452c4c9dbb5a93491f117c3950f856433f6ccda2d31dca70ef2ae30bb577a1c4bed29fd18730d3216e0d00c64a7aa2f2139d73b81cb49ac205a9aff96ed44174b1b14de8dccdc2fdf4469f4945db48b9d135c6fa04654144abcd912e566a8862de46db55f4a7ef39425c9c44db90c404f8d44bcb12a67a6331f55b1d1480c3298501ccab9755c46dc8bdac3b6441816deae756bdcfde92b838bf524821ddae435680a65c1d4dcf05f7f833bf88541d5f37fd8c34b49426ce409a1b21f6c7962ac331dce9fef67b8c608ac723a04ef443506550e2395d92efc9f41a4d0be5174a4b93e4c7c4e6667b92f3db1a54a8b2c4d4dd5928f6a183c7358be42bf9acae4c5035629807a3fc5dffb1f4061de7b55e25c3e99b54f665df239fed411884c79b8184babd2fa5c9ae1743867954829879d397a69bdcc2b58214ceaa2f1103ffa2501071b9b5623ae66a2716e6a11fb8a26ee6f4ad069d0f6a7986af485bb0b30b3c470c10217fec9178eb39c07c05b252483078616afbbff2d256711f51018e30b6b70e430c454b29c3fe43b94ddb0223ffe2864b625553b5afcc7426a98cfdad5fdeecd4ce58c329cbded913bf162472069bc2bd8540671d72474cf0cd996dfb883d1c57f021011c011b7e72283d95d6fcbaa24a6c2376b4ba55400025dd6b85c040312f1a44717af422984711b7c2bd32dda9745f94eef5f88511c75acf82e6a886bfcc952990b11582ab25141b57bf59634cbe081b5c6bbd4526d8c4c62098a18f1cd30956eb7905ca4b7ea7eec137d0ea00a9de0d2b806913e3970c77e1163ba7dcb4df42ee1d2557edc635987bc129e069aa4a00f8ce8c7ffc948a5b30e9e78f74049f741527f4ba069e45bef3e5c4c4ec41e48d30c0b7f7c653d6c68c7e25a47724b2610d24046f2a6970a8c61052260d0336185fe14c19d5577a60705d86756aa7c0a0129bd4f5bdaa2f1c6edb22824060a728f2bae934ae01ddaf79028a70a2f032a7d1e9d6dc64f2d9506a90d6583aecc03585b7fff6b4f5791a03079224c4b090eed7a88a9184b6180b25ebee1630fa09d48552f0ce8ea4f526aad73e10544b63fb705280a3a17653934d46da4ae72afc7c7e367c2a9f92aa5bdcebfeb1b6331444587229814fb6248f2d3c1b74a1f21093dcbcf2a97c41312015c054878f0a3a5fdc8215b8e3e803656236221215e1deb12d60e4b989330229e20ec01023333eb498fcb5c912ae689c68bf9ea1977cbf6aecccd2e95ada95cfcdf1d1da3730a9d90a6c8a20305bdccacd52e1b1cb154b17a75b3d9775ba749836440a442fa4f4339d3c135ce182c846fc7a8ba1156e0a4dc99695624e5526f9c76fc9fc60e2b66aedf5e1ed8ab5c3b772a10d2dfac96583dff96ad8693d158addcf91519f8c68d569246640b0675782cc50d6870fc9f540670e3e484ac4d8c205fe00e2200474099b3220f4d709ec4f6624886ab8d23209db4f705057aed915bc75992c96dd707df0c99288833ffa63dc7683378a949456c937c17f343daf1c8dc0baaf6899d6b906354b5a395c1a669bd1678bf96394ad9d5172335503b34cd659f5d919ac2f31481fac186df0c4ce20cfa5c689d1cb74e417230b3e939e0d42cbcb482e96d28d42e345119580721107cbbe75c061a1532a035465556fc7f44f70bb5b1bec7903631aa6f444adbac5cd5022181195aebedba086d03deab98858f346a0761f8b5a3540322effbaf752cde7613c01e05c1e75791aea5ca6eb4ffbcdedb6a19271f845de87302b4ac05e960c0c4025c4220873f7b1023626f5a1a45b10fc32f7c543f15006865bdfae3fc24873c2d1c3a8e37396c9c65eed3123ef4570efbeb20de3e44f40c005ec097cbef74842aa614955f0f0c5953b6c565d38f75e0f8953b45e7b26b64bbdf27aac08c2fa3e242f9bd6587054fd6026bd941dfb68ea475f40fa4260e7a7f8756a342c15fa13c38118db26afc86d419edf8f0acbf1ee6d374c6dc6a855532a0750ab858ac871fcf838dae7f9c073644744a48be3b9ba5f6e3f1f64477fdbe70ae688c17c05c75507845c1908ad9e5cb2856b2dcd00acb2768195c7d8c7b0940274425c740282503173e54637104c8f630a494d026753fae2bc575dd6dd6fd570826eb30d7f3173089ffcdc2f791c60cb4bc5760e6e3e9d3557da92bc21681ff7a9646192bc6331ff5109673c487c957de276455b85db1de0eca603132447c7ea51d9e4be4a8611884fa153e81eeb81dd46c227643ea7f167d3202b56666d81db0425b8faba289625e44b4edd6ce7aa7be13f88d30923bc4cb3ff78006877c24c38ee5ab28bb9346aa76da466a30f93dc5a45060265dcd301f79a85ab9ac50db0888a56702fb670c9119ee13e61b1c2711a891b9ce541da2a2677b0a27df3c89a4b8018de16aedfc3ab45a1af198cbacc9a7225ed07e14627c91d95af9272878c2a662e36f110e5dcdcf852434aeee1515276d17d3e498b7d47f2b10d78dfcee88db313351f7b07974f2d7f857656eaa1eb2d9ae7f8c92ebd2bad7f56817a0341eded4433224bc9655a6b86001c531b30ccfcee97e80eb44a0c947d155cffc92ad37b5dbb8a11bfd98729022dad720780707b49275c8890b6334e16c45bdf94f5d6f3ac58b75b3f666dfc7078ba2234136e0f46f0f6a568746840f8de1e87db5550dcdc854a86ec8e774c5d7bc2e2355c1e4f449f27039026e4038fed158710abe99d5abbb4333bd42b9f35a148406fdd19b1de7c6e119c206f0c385bc15be97bf9bba37674de91ba036b7573cabb6818e8cd71334c6e010341c1aeb4b284aa4819ce33401ce7a2b28747a98dcab19291f3f8b93e3267534204da4f72598ff792c24ba6a30cfcd3503faa5e39b8d193cac3c9f307d09c076f40ac973f26dc45de7b9a6a09d56ca8158509e0121589d045f0c7db2a778a91ed5dcf1255caf809adae50fa0f06e595650ffcfba17148cc75e368be10b791873ed4a84d709671d762e2f5d30d01c62fe138e9ed525cad0ced14233855117f5be64dcea4fa19d823a0b1ff919e56acdbfc885751cf2815061e1b8dc6521572847621a8c56cd57972d983e4922d85f82e2976dbb552741f4c8b521ed4a214399560e8dd0b3359f4499fe13b61551036aaea9ccd8496b12a44a7705d0fcfce2313390bc1f30807c71c46427a9b9f75f6b6d262d34c067fd48304ceaec43',
+ '7bd0a59d7eec22eb806483b0b95de6e15c142344252201d531fea96913c35a9124335fc11afc3eb2dd3b3304963fdc28088f367d232ec8b9d61d1e8b2622797f7dc8c52144a7cb65b3e5a846e8fd7eae37bf6996c299b56e49144ebf43a1770f2d96bf05227431cdac6bccbeda20333a92ada5d629e92ebb31eb1f4d92c9bd0adda536b4dcebd9b26ed485b4912f6296c160d08781a99d6a37a87b7c3c21b13734fe10779b9429dca128bd6f38b275886be0d3eff15e6941025549956932bc60dba8379687f88a12ee705b38f531d3d938369756a292fa093bf971e04082474b79ab9159c79e651efdb34757f035e451d2690306bbccb889899ddaa5ea9bb8c1b7100807840e20c675f96914534510b7de81fe4665950b8b95486e2c8f05788a3dde83eae5d125d3fe4aa9b643eabfd50787725dc8745bd5060214bc5546f06eb21de948136eda0c42afef87eaab5971f28246940bf1103185d3b49f67e88735bed6246a356da93be62f23cd701046544a7a623357949265bac4371beb73a4a60a101b987457f92695d32dd1fda1ee46b578ae82d3e649394cdc83790a6db18b3ed3b61af8b33196f5a2f5dbb7e7ba0edb8212c8a86e9e77e4248101eb6015aa0227dd37e809bb53aea46333c969107666a3764ffdb4f7e529a219fab7d338cbfc1578386ce2fe2569e1602059af5944fbe0c25dfb41bd4dc46034ae954f82b8d7a46f65aec462d4ce62d2eb9042fe414443de3bc99c59755c66b863ae5b5b3839ddaa06d33a4f27842ea3dd95a96535e9d3b231914d31259aba8f62297f2b12cbc6126306c92de8344db93c916cf8e89298268678ddaf2726669ff32f435a0f346a8dc46dfb64dd85c8fe50ab141e21e69b2384a8d33a42a772d25de83050f71cce047030575ccf20a658630c283eaa8de38f7149b7393dc5e40eea7bcd75922dfc60b078657e85acefb1bbdb30c24930785496821216e129c63de011a232a70dfb87cc6dae30c9d2abf0a141b511c77763583b7c6b38f67b1f4688d138ed0064d3630c36b9db613ae3fd5d663e93eb09ab85a85f4a074462f8112ae4160f63db8ddbb7fc0ee8168b9fc0377153b07f8f58c1245237eb928758860e71c50199192fd617e636bdb06de521778a7cd049b073f6f87dbb4ca992478d7ba1cb2a6ac7c43e721db8c4f78abf0864db425b1315030b6d095d92522ace0ebb9cb2f19d8d51fa11c81c64b387345d47892cb9a36510e8c91ea62553f7f3f1148901c0fc12e0f23c10eb04ab0f4c0868bfc352c149a37554e31d6e74a3c01ecf9041fe4dff780e3b1dac0ea8c810f10fa38d8f3769c29a58814ad37fd33d7d33dbf91259fb22365516e7e9725a8700845d14d6cc6c783f1b20b5bd3a3171f78a8fb166e8afe552d32325fdd16eba1e0e16e09047fbca4ce6e6e7956f6564721ad7f261c2c3c94afd6eecb28dacaf31f7f6772671bdc2a901fe67c1e4171ed72dd9426baeded6061b1cc7706dbb7022852463fe125b673eb48ebdab62eea80f09bd2cec75a8156e75583b9f3a4405da01c92c82f27cc1f347bf72b9b0920901f6ac354cdfcb8d6b5348d2a7bcee7440b30c5819d8c9d9b101b80d5598ac49943ecee7f4b4ec46e3fc1413f0bb62c73be216712a8de9b65e2ca216e6e0e471b2284ac94abcb649c0b9e586e2ff4706b5d70eff1fda8560e40ea415d451e18742fb4863b7bfce9de8d2e3b4e64f463795db1d885f8545e029efe79386b34c962b00f23ea484df4a458f352462750764c3346b965256d03d1719921866cecca3577f6ee177d48f59bd37045ec0373194262a1fb06017cf7f95d3ce2adb690ab8adbfe4d49a7786d13a1477eb665e6cd0a807625ff18ee9af8a64c3534d4eaddc150a7da073356dbaa36cb752beb2621f30115b296d84c7d4e015981a24435e1877a660cc6cd6ec1de088eb1b2efab889a79233993cc211f67e2e7607c911c573bbdcb7e0eb21aa01d8b03ccf20001916f3d01134c60d6e1d4cf784a3a28089f5caf4a7655adf506e752cd2f5fb8a2bcffd141e847430865232b7eb75185753a68a365ae220d8856c9e43d415276196bbada58110acf1029c18b8d2069460ca8fe4eaf8dfa5d4f2043b3e6ab80c4d03e2eccd6360d71a8a04e64062dc7c6197b7c057a8e4519b3f3d3565be6512151a4da0d2ecfd5e71c5918498c2813923612607c6937a8cf413207200f5a4a1838203b2fd436ea7bf5c4bda081c7d346ed1e59f7fe128bea916b3db573afd215de40c0b96913b1ccbf63ad1d79556d4098ffb72cab9774f80f10058b2b3f9fa6ce77191e5fded245f3c674f4f579680be427067ef43eea7424c0fd258881f947834392e31b00b2641d6d93db68ac4a253924d6535a9152eb7cee62f92e2f75749726cc9c4d21d8c3a6b9884555569a191299956e610744da6208f59a8e90aaca64a134ae48aabbd12a3ad0a74e10ce540ac5c2148a2946ad8ea1236e2c4dff958643598da003e2191281fb95b5635d628c69eaea487fcef16d375b0879cffa72867da9dee5d5d207f9ff67851db6ddc101d68e541e734422683c2aee198f01fdbfc0a4067c99122a3b33b2e9f983a5fc259c1ae69e9c5cdd0a3011736e13cc83ccace698f6b618fc60af5c58eb627c70423b2f162b536f6dedb38d5cf9e6a09127a2d00ac6c55cfb04c2c24645db97c9f23429675740f2716d271cff4f8034c24b330ee07f3f54a2922e83369b47ae65b007985ea4ed9c74231b3eded178ef83b1ede19e829ca69f93432dae7e4f16dff625fa6096a1a3e289f0717012293751f623f6a96365b92e72911012414a67a55f3fde119f15df391c5733579419b2a98285bc95b41302ebda98e90696d227323585a1f640a2750444a022f9d9e2d816e183b1f7d72d2a6415eb06ffe17ecc2323c7e4630de02bc0bd8b9edb55ed1e168d9e65245f2c8603ced7f872b398ccd4a457240c695759fac14a25809ac0fb25014644cfbe99ac6750103be38fc8bfe321b3df36e560962f0a8c456e1ae705dec70bef3e77fd13fb64045fd6c8770bcb467e7497049446b9f3c27d0848c7b480596fb3151daf432c5524c2d1103c36e96d179291397b1238177d4af3b6fb9dc622d23ed80258b096be020346d970d7ea100fa7aa068d5f25d02d2d94e7fb081cdde3f0fbd861f2b7092cafcc86cd4539d9d72265fe33a41fd84293805e3eaa00c51557e502537009c0f516b6ca9a355524fea149831677627a6e2b3a7c4ef9fe82d7024812b5bf0b700bd6ba077ffec88bf682c9379e4fba41004317e4945e8291b95e898c01363880c17e3abfe72800695f75697e43a363c69979cd09b76e197c2ceb2dc0be8c1d8dc66bad665837995789c7aee9af091b65aa4dbe6e10af4f9cb226a963561726f17b2da69d5bb3ffd106180dec28e72dc8a31d60844878819dd9af90e6507de1566032aac75a03a06064d50b0859c4374a249757eec7704bda13c458cc4606e92716292e66ebcd3770268d3ac0aa36b4749f1a08f0061d2aebe1237587af688addfd6fa61e797701aabbbef197a2b521d28333f84a8ce59b4dd24b7b9ae51962f59684a6309b77bb5aa4d8d41fcba60bee6163aa50e450196a678aef989f7fee1861f6a35fba65a11a627966ab9dafdf12ca2793a574e321ec018025e32722a880f03431fe6ec77f6484ff0ddd812917fa2e0e48bae715412d40c9d31d14a80ef9b1cbb68f20f382c38cf85e282ea8431efd764e03937cf3b895a65b990056addd373bb5a4699077a1daf3bc0dfeb3ecdbc90df8020270c61880c4440952d4e195e0f2c3b0b124ba9a0f5fcc1169c281bb0113eb48cca714c792a210c0849966ce6f8f45468006b8168abaf2b5086a8e574646ac4a5a667c302bae36612e2a99e1037fded86edd1ca1ed83c47b42f27f1af914af892455790cec67496db7fef7786d32d4e8d60e44c69858a5d6b210e080a9ec5977c2fa7798355bbe48aa8d0bff0d46ebea4e20ccff2b983b59ce4c3f22d95c5eb8aeac147ca70a09847e6b43df543a787f3fab11a607c0e9670feb077bf7d313ae5000bc24a1e0bf93cbf03ba3e27c0efa3303bf65cd6134a0ff93b5f695ff0cdb4f9efb1ac67e4508ebd25fe7388a03fdc0c132b8ed07b17a05dce71d242ecec205bd3dd975313e5686883d13b6e31bec5179f819d712c464bb0d076a22e0cdf51b0210fb4eb8100f04783fb3ee25ab687d848c032c20f1e3a7ca937832c38ed6e7f400dab3614e94ee28e4325eb036d49d776aff4c90ce99248c0791b42585f9f51989f233cb5e7abc10d729892142c349d92178c900ced7e9d7f07127b557d0ead918fc4064d442f6d66503ede763b70b102c0a11ff57424024da811dc158fe01b93a4379bc246016d03a5c0d70fe2249b3072dc7cb1c4acff9223c06f81dd402306a52404fe364a494a39c585c86979e482f5e5b126253f1cb741c63b81af544e525b3247e75c318aa5d4f6f18a179613621063c63ee105e222cacc48fe4c4423df8e0a6e67ffdfb77b3dfb223b36a3b0378dfec1df3f25d83c86799fdefd392b1605548065687b58333d7b20c40cca4469325facf386c7876d3c76470b0a5bec5d8ab786c8e02ef5379c1ff40ee2315660c82e8e61ff15751eb66a486bbbd1f01de3db877aad2017ee228ea500ff006e8316127011f2342cb5bafeabe857c4bfbfd9698b62177845c096703b81ad60143ce6b459eba64f349c11da7769285bdfa934099b68685f0815c26fbf7b3279d1f3febc51d7658c133983a5b4d574bfda45bc62bd74e6fce7556c3138d5d7722c0e27d161bd751353f6d7076a16841738fa391dfb6edb786ae85d5eae77e473aef6299b8f851f0d7b3ad7aaa213a08c7f2f72e15d3e8dfa19375d9f2949a1a6dd13ccc4e517aba2146c493364b41046394e3057eacd45ab7da8349cb232553144cdd8f16b5b61f9a5f6f64db6e11b6cedbe3b27aa014ab21da4a9fc501d4a3c688d14cc283a893bb63fa69ea2820a2624e75bf8a5c3e638819485670cefc6f0d3b949fd249a6a8fd0af20ce2f7f259e65315872e4fc5f6532de9087ed9112740cbb3f2c67654d1e7aae2e8665c0b4f93804f93e6e92fe60b7ec9201fbeb76f19bf9be00fa817a103ca4966e4d2eff6225c807044b6f0b52982453803ee5398f09dd8e21e4d8398c9ef9212deeaf4effb5e54b6b8479ce625ef09aaabf68a575dccd76382d50d2ba2915976c512ce8f86fc38a1c209a8eb990b73127855a00cc9f3340edded38cc36461c0d27c8e1676dcca8fbf27b9957eb6e0dea098a593bbd776b2e7fd48b1cfd743640879bb20a1826321fba2bd29da59d56abd012fa66b0efe95cf959687050b244ab5509d4ea0ebf40778989093f500fd9d25c7e28104cb33b04caba7578e876d3b82071be79c9fe70adf11191bf633c03e81f2a46bad977dcd0749626892d384ff16a81c35bc24a2f2d8154008067a8617788b5fae8ef944b2a776af043878d35e1b3a8a7e29048f870aadeda44c5eb7eeb398fa90d079f028d53b7671a8fdc02036c64c0b0ac714901ee00835f166bb8cd9245d5a9b9fe9f7399f9ff80beab50c11534a2488f39bd7bbb779041c1bfbb99334705316f497e77aaa13e189c819eb54e96c11cb128b228a382859f603dd3c89d21bc56aff63c04164b874491e5485a7909a19faab4c3a336dfe7ca7a8cadf620546f6be48b34935d677f97474cea2b051bc853d4746e75d5ee1b456922b68c7d6d92f3b58f04bff0739b73153ee0a1a33ba9dbe6de1f0b012ba4a2b4766a05f4bbcb809b6838f46ffe4f7a9411e848b3f7ca9a2a5851fd69172864f51e54fd36fd7d94fead2dab3c7610103c8c6daab7ceaf114789a3a74e3efb0ea380e3dca4712da367d47f7ee66991d68704cd224f2edbd3a6c880a35111b1023824b31eddd5602e5f649ac9ee4739300c019359ba96b543c0bb757be21ddee23b903f5e438899b84dd3fc36b4ad6aaf24095cabbaa4dff43dd8fe9ba1e1c9112649d4f566095eb3dbb9302ff9facab6778285b29fc7c0677697987cf0dc242170c8308eb5e603b3617ac32ffebdcde86f13bae90a7dfb1ff37dbfba5f8b6495a381ce84c39d4d29779f21b16364e8b7832348d96b45d4703cf1e0be5e6e0ee0470d0b621f9d8fa8969cd84ea89eafa64c37dbee32ae012adbaab8037e028793d3d073c11a753f93470a53623eb9873732d7c0920114f58237fe92142ed76312c252940c5492f18bf15e2fa6a65403dcdf79b38f57191628da63fb8af15b25ffab29e8be5ea539f52e1ec9667fecaa199a941110953907814b7170b2560c44bce6ccfe5188fa7d6175b2cd2c1be1ee700ccd55e05be33e2e03bd8e44dd7943dd1da221506421304330b087820ad245729e7c1e590bb62e718bae909cf810b7d2b8400e5580a7f73d179e94e2ed4c5383c826c6f535105d0e4bab44252670f5203f2d21bb6f0d369172e381e903ae7d463253ec3f68805e56dc5a0597e8f890681adfe80c7faf72f2db15baef1e77371c4599fab2068486edb621514fd0362dfcf3ceedc3432d30d3c8abcc6f7e4525887a5f7ed79fab7b3561a7b3d74cb8026122708f7a4a0504a6c42c1304a386ad1d592ae52aa453afc05733b9a8eedcd2275f7b698c04b5430e460d4b393eba523d6a72ca25b51ba9010f7bbf4f173301d1d335f89303bc805cd6c5c167a06ea98b16cdaa533271db8a02e64124564905d35636a909b7895124eeec122a69020768f691e22dbce4dbcdd9f9ebe33c9d72be30f4176aa0ba19f906f6a9fc0913106972761e3fb4e30e4eed2d2be62a9eccf4fc04b0204fbd6f4bb77c1556f120490f22d0cae3ab0b077fb4f66db7c0cb791a3fdf0fe51501b58f68c2d0b5c19815cc703854c40fbb4bf2e9fc4dd26fb20b9044f98303b1257156938168b2fb190b13f323685b6372302ad49528c0a7c0f8e9c3dab62684a74713d02e49b8fdf19d982418520446de789a0d57ab0d09a6c59934f915a800059b59a72000424ad041ab7b8dce791a4e79c30cba310205a1f62110db3e12c3aee28a711d08d972239bd0620a2a046e40b8384b43475a0c610f13d0077be14021b148d66bd009046a10998ff3f140a353b85c61897a7fbe5284e3faf46bbdf5fc8bf73fa3024b622224895e01b0b86a05feb1b7d336ed7bf4dc140a2899e355add7282538fc0b2bda5c025489e4d622c5ffead6d7092dd5d68916862a630c0f94275e2fb439d2b2013bb6ec130a145e22303a46fb6dab4c66207de7607392546e010ba7ab583ca51527f9bd239d0d7c89445280c6d0f0402216bcb6612810f499d8b32564c39375a4c54d20421a3c1e9980b5fcafdf9771a8f0556bb31cf2409622d74196f891942e7843bdd50dfab4a189c7434cf60b315931ca8fccbec0c8e1840a54a883919120d5600e72e4f3cf5fd418dcf490c60fb14f9e8b147a170e0a50af072fe12ded1a51146aa24ba1df12d42d325d4c527d5fce53f630fb4f808f3d47b1839dc118797fe38ad98e7f64692e76dbdd952b0aa3226d5998aed4b0de8e56ec4a15f593dcb65fb78f39f6efd1378831fbe7ca2829e04d1db201612f76e87f2ff9e625ffa288eba939eaa2d1c973c3fd4ed0ba56528c95bb139f5f4ce6c002f4f98afcac698c09b87ffd0dcae087cc763ccc7635cb8bb9da43d549a9bc9994c3ff70c43a90344c6da80d923b84d804354c577816e3624bc3b94f9ab666f9a7154234272ac4f636417888ca9444e90dc',
+ 'cb7090f7a465782f680fd44cbc558107825c9e53f24e4140ec5b68208cfe33e8008250d996c4b65f193a96395e348eda1a62210ff69764e3e6ccf9c0b66841d6e6bfcec0b7d817658673701d594a3916b89855f5bed8dec06fcf16fb4ccca111252ff2f62f28040b56c14a42d41fe8820304234d6f9916a0306ba71a77b3ba6166daa5c2253ce317322d0ceef83904e7a4b0735e3463764ad0a895655ea4f48e51c9cd6bc19d1d52c1bd9e6aed22f60d42a42e7133bc6588f88ec6242a27b75f8d03533a2dd21b84ac7be8feda6225bc86e73ace942f4b205026239f02c46b9dc39020778e6344759a6fd3f1e77ff8bc178081bd804191db7f77941e0f0478109a791b6a0fd9ffec34a0458d3e03d9d49f7aea861f7c9d812e8902e8786e4780bbc52b5ad5bbb6b32216271804762fcec3da5c709867d8da04655346668b3eb5cd7c3a910b91d0900bd962db645e61702daac1c4148aaaadaa12718001e1c67679a72f55d7e4b54c97f2dc1e8445d98385f200cbba6e7cc4c79842de70fa488d674cf1ef613accaf6f687f298cb2fece72b801cab39ab4e50400b0a7dc5e2ac03e766aa7d21e7f803b433243a52e381b9d06ac0c2696bcd90951a2256c93d6d8a62a81c452153589a28bdcb5f740ef30aee3e9d83cebfa687d93ff9e4923b29d49721e9008a957904d4967c8a9708bd64c58a0751c20d85909cfb15b9a9628d6cc5529511d614b5ee485bac1e34674d51f6bb92cf0bdbc76540c9fa488729e4ddadc8b50b9c57abb0e45bdebcf4b13f5d0261c45dd4b10850752b1c13b417ec8190d2ad5025e4c6e7393588d9206cefe0791b1080d513b4fc9a9cff9da8a2f1031f2ef2723b32a41bc7673ee57ff0eb76bb361bca857a59c431ae1958ed619edcb93c290424d7afa91f7f65744bf3ce9a59412abd5e5b6341caccfdd7eaca4268ff68c5688b2a562991cc91081cdbe5c5d3cad1bbd84d6defd688119d3943885242533c21c6c14417defd56137b1fed83ab41b5588bb6d648e54e091b4163dd03328a11c26032e5fe57800928ef46225e40342a15d13e38c767bd28d4abb1bd06305bcacd0fd5a8377be33eaad1d540a04310c466413a01f5ee24054bc0a3a4d5dc66eb940e40277dec957b2bdb43b514247608ec7430ce4ed93132f338c030fc8739e8d9e3f8d01c0a15233e5427fc0454c25bfab2b90a5cc953166d7c7dcdd4d12346e139763eac64f0eb24e4e8720b64845223a549228a2f08441cf452e8094ad17a5c773ecf997da717a1f60bd383c43a7ff9c457c3618acf307448e62c1444b317ae15b8ff8dc4a65d2810088d28046339ef986b497d6d265025d28de6605f5693489d7b7ad3ca7d1019cb750b42958d14678a0a413836ac478439d0c3781b2a71de970c9e260fa9c50970c7ea7e0826941411ab272c77af7b21d7553fe5d8ad37b26ad2e5def4bb0079b8878734dcbe3d57d7c487b4c0ce6f814361a814485d6976b174ee792a0f46ecfb1a7e0173b274b544fcdf7b7277992506acb89dcac96b9de349147f10350cc5c1273b6e7da1eebed19a9afb3d498c7f898cddecd06fc29a78f6047b37bac6b693045bc64b1aefc71654368e61a2be035498848c09bf02eefad21ad8010f0911f9583bb37c7c0c807b805edf8011a252e04648f5d745b11de183e4202cb0587a6c8977c07243d95d89c560592914dde0c51c97a4b98231b9b571dcedeb3d1952551e7c47dd10bac0c989e775cd3ac53f6b81fd3330f32914ff22819bfd13d02c4b9dd5e41d51455ca35460cfdce20cc15c2ce60bce26ebe2171c5ea4b2b3118ad86df11fa383ed73af9b48c1e8a8f9090f63eea8f18c1e93a5d5e0a2acc961d054357a45003737c856e51bf6610e3e890ddd73853b91259c13e5a4205b9525af775ace0ed3d9a8ad31c856c7e67021c3dfd0214b1ce4857df9a215884fb4f17a8a6468d76ee9b4a4b8ab95d0d97d674bee12544515e4d2bbcfb1b144e9b739c435d8d73961e5e0416405358bca945373c0eaa91da71081d7fe8b0400ca1a830ee23e959f3d6ca005bc6bd2633a439afde0ef7c3f50f617551ea48a26513d51cd17fc208351dc98543a55d8f19909922cd6776a24941407a59884b0402fbe2a916f9804905fc43dcf6649d5a16764dd930a48df57f0e8d75c04a212c8a60b61ce2198be20b7a1e4e9b8be451d45d3c1c304aa6863e7f30aeadb832e2a64b3bc060684b9bee9f43215ba2993af84d5033f5e3281b2f9b126d495be6f0fec358dcb56d293e56739a9d5d3226de2d4e7065a6f77b7df96a19fabbbaa61d39d0f5b70cb08b91edff0b0ccac2f205196984fb6c0c077445f42e9c2c5dd830007afb9a8bbada5e584a7fb4c9db657664a5753d6eac33c8bb423a9dc4cde6f2faca50cd5a127f6b425cdcdf8304e7fbb70b2973d55e6940025b2343aa91362bd0c7dd98a240e080513e0cd31fc5e05abaa189e5c8ab11a5c347d3d7073132a2297a8a438bcd67672df7f5d8c6a6b85c14717ddb701991bfa50f30a002364021aee940153c407be77f8f8138a030d7c96d83ee9cf432525651ba24e8c427ca6e071ee557de2183c7ef0f9739d4eaa6696fddc2717d7ebc326e5096f5758c3752db21625298f9e9d0247e1e258b5d8b04c089e3a23384381aa5da6d113beeef95d2f8a04b934f14cac7e40042ec0fef61088f37773dcc2cd89e5e938241d35aff95ad868df6ab6c7d7f4a24d9208a3b49f0f5a837c4de013200e7ef90adc6e058cc48fb5fea9559b0f5b772314e56d5841fc51d2cef4320f1747a6985a84ce9671fcfe9089796432dd133d2949ff927da3f077d9ce782efdda121e9a759efddf5344c37d1243592babac62c483bfd9713f5c1b2d0d323d8bd30e35740e93624cab6c6cd02bafbae205879689649ea8ff6ebfda310dcae425ac8c99b967fa926ae4b4693e3806f002654536ec6f146764cc9c238a42aac957d12d99ef144d14b699bd371bb9f19ee96565c26205d9bb4dd361959d3e4a538ecb51d469b6035fcc24f754fd43e81e137059d79ecb270b171db08eee6331f95705a9d7cdbfa5e9f830f341574863042d00a1d4d711b4ed4d0609adf253ea413d0a96029f83ad29e728219abe49fad772a0ce58cf8bebc87c16cd414d82ebb1ad6496e333b491b5f58512bf96cb080ea0db4fb1cc9c342320ea7c076472aed1248e3cf84ec4fc14cfc2473e753e1320140aa69e72c53683b7dbb65ebba5bc063ef1e8c0253734acb28a1f75ee35c4a268d9537d36996b70bd74dcb5c78e8be7693a3ab97b7de4046a273c144624650c0ba1e6bcac7b3b60ae308fbb2cafa6fb1fc19372a62b82a243340d98319cacc3790ec0fb38fd2eea06b91fcd3f8b95a7df12935a29a2891cda3b14501500d47f2d7a82a750a1de5363593d6a94a4c07433b7bafe9a856fca9ba6d0fa84f3a495b57f9c5fa7dba25320bb4921b07dcfe69a2b6ace6d46bf320401d3f5e5d7758d9d788e72933d136feb39c37107990c859ee833581658ffe9afc68db0dc3ecfed421fc98ea738b9e00f5f4cd72e691ec79e78951a2a5e9a67fb7f7ed9ccac3c2b4fbfee025840fe7a29b645d289706f23355710831daa2723739717cf3b7212ce22e36c8c2af86984b6b93ed1ad41863eeffe262164de1eb2a4a7f91fad530c5c4824b57aa3c183a622f2a8830e5efc511ae85683946cf97c0b2c933996b1814c3bee696c373ec5451add0335211b2a3062a0221830c94006238f66ab8c001a248acae8bf69b2b6e7fae2ef868a18a82343b0096dae957ae76a6ae3e3f1d12f5b9124c402cfefc1154f9ecde5add9eec03a927cd2823402866c29bb816ca73977fb967bd4be288c33858b1ed100d1768db3b20d0530379f6985563df250f4d100369a8083c90f70f9391252e72791c7f24ba93a744d127a07626aebc5534c8e6ace9b62be850707acd6df1a86969d6b746ef2758e4d575ae66ff25558c800ba3e1033b2e366a1b8e4eb4bd64db246c9ba99f85ed1a5789a9d2a66fb4b33c3fce0c7ddc170d2521d27f2e3068b2f307b0b695556dcd9e8b1921e435e1b3b18bf0942043f057caaacec6a2f3bed41ecf80d3983ee1e7f3a484bb811fe272241890e1b418c1fba1dd0b8cdaae6e2bedeb92696fe8504f9efb740845d4c1dae00d365b010290e1f2e3103a9c087e9da7846ee1e3fcc2ede7ba7070f8fd86d22c936b6aceaa67f105e46536b1a9f81496c19da1bc2240a91506015b614137414fd2b576e5b84d13210793eae13ae37700cba613c201d06b720aad314949de1192f19fe70cd34b2f907421bcf36348ae2ec9119ebc427d1b44ebff5a78bd82293d19ae250ca3672d98549716b1cb2ea068e467a9748c4ca1d4d47c3eb6442c7198716c98add1abf582cf7e5fe11b9d37f6442e9eb08847e56aebae45cb86e3d1ea2260a8b6051f6f960c5dff4cfa9e5a1f10f1e439de75d04e581cd87e2caf317d5312bef1e6ca841b34bc5eba1137f12c7135e3d735de48c7444e5d98de9879972d0b40241db41a5b6e23e4f30872e5658e840351358b361ed7cd3ee243a79181d8d2981852e51a9ce31bf9be86571c129065ac718783839c3820e12104e361837bef2c952c666d27ba2b05e433ed7f7b9e6146fdd3ce158690cfb020c855d2e43eeb6663533ef9441cdc702eb83cf6e8e1ff39bec075d627066311a63803caee4a4dad497075a83a31b5a261bd4c6aa080c7ed7cf77fadc6c3636e7afcf732e7c79d68102b146153203e0348e926c1eca441cbff20f481aaee572e65bb39f318dd6401802b210bd512fffcaae5d1e51475f6390ee2dd3c1cb0c7b92f1dcff3c9b228df6119a790092e81f34e5855bc601a6da073e541c564313c77a0ccc31c4271746a7b9cabd7379d4b51fdebbb3017bc2e69f4d22804ca983ed07f6d1d55ca40cd0a965d37c8a786e57f7f460a288382c2fa5a519ae1960e532c96abe385aa47bd277903524ba012d8e39c104a8bea95b0bd6c09a0440fba05f3ab2b1f962bb0cb29ed3705486c69abe71cf28343951c8018f4a341157ec5db8d8798d86f93a08078baa157a751b2e6f2c693be37cc40bb75b8c6863c2b565d62f29817f82a41627ca8e850509816126b639c034fd729e83b821b75a15e9614dab4d3e3193abbfb9e7eb5f82c88bcb6bcbbb45873fdfe8fd2184a192d5fac875e94f344ba0936d34406af58216bcc5a4c9684c78b87e838a242fa77c675c13f545b9d42c3e0d970d8067f771887708683bcb3577fbd0e6c13cad39955eafdc226d17be61c07cc5cc047cc5dfa26cec3c9f5ea1037e799b28d777f86c304879a3abe3537aec6381f66913f13ad8e0e988608d1fb9c4db0937810a346f60c884efaea733d5f65453f30ac80a01b07598e22be0f1f94377c963c5eeae2d8492767cd1ddc01182b1a46885f360c2adbd72c05df4d8508ec24434129f96150058d6c1a1e188ac262e0bcd5730960c450affa98333233e1b5d122ef387bcb4586e1e54bb6d0e14350be277eb1a3a3d0b00a804181673305daabbcaf551628a1164c5bc7adac0c853ddd11eddea9dd36410f476993938d5ea3d998e74672814bfbf655f6cb53156e73640189c5c616de4ce7d6792f5f47d357843e01d438dd2b7d065b40d76e03e8397c80ba2da057b018b9b5d71e736b4b40d33763c7104529e6a5f50d9ba4dfdad64d15e8dddfa794efae6ded277600b0a4ef0a77e7a1c02d7cfaacc5dd9eb02b93fe487b9e2c0f7217ee852214110fe9ccff3fa3fc94561f892e07d066037ec5b8b8a1db382f9632d9a085ddaceb9fde002c5f51cd900fe753688f96fe7c528f8da2a971738f44bc7decf588b6adb877889ef1e8233ef46d23d1ca806cb1337159e5e7a317f421f84bfb5e8afbfe629cab7c7dd0c6460c9a409142f47a49295366a8e3daeb0836ce8bf5484b4387a635b2ee8b707e4f6054f4cf7f5beeed3c1dfa14267fb8708a278402393355ef8b2ae51ea4321301f68485b3071e914475b75e5137f846d725e97f48bbaafbdf1a638c314f7d4c06d8133c6664a32bf7db0ada869eaf5de310fec4f25518fbff5ac8ef5215baf2030ee761101e5484ad76fcb5dbe40ac53c524c9b2690e51ef6534256a77d4fcda39eea3cd8bb98630f4f7639799073458bf3bd4c0cf2792c6d0db708580fa5a58b920214e7c5d692c46d61ac882a51a778fc381ece053ced0b91114e8e53244266f1193365c478d775d9a3572f3b0cb1aa74a45b9151972979e6367071d2346cdac416df793ee5fbca992682974a0c2cca63eb49805df0a75e1410b628133eea8f12e1614bbd85c66ab7d075e8dfb8df7fd2f430c0b1b03063248567dc9ea8852fe3620104c8c0fffe3a8b7749827a9472c7a75a7cd5408c301d7fcdb4fcdc055f408106cce8fe702d2b3ed1e2bcb9114b4dec0eda5206836c07e52ed9b44032c92f26bacaa3a7dfa091d1ccdc14de1fb169ab9302ae6cdcbfea9fd3724e3ed315bb396327842315ff742bae5f134f864c25cc321d74d961d3cf9404a8533db2ebe9a24a0a10dadaf1dd36bf923f750ec58837335329b5d84dbdae09a34aa595f1c349b79cd135f51ed2d9499e23b87fc49ad5decb57670ad8bdd4298abbdd45d25016f1054e9a302f5efc92ab4481a0f0648c7aae8559bf1ad6eead852e4f8a3498f2426c0f7251cc8687e3e02c363af2ed4551233cf2bfbb10e5ddbe2c622bc0a4c3f0f99d26219c54638465624115713ee9a953039ad164739f015a3c7ef21d7b7344d67f1c6848cf76bd636e08f9165d5ecb6662b9bfbd08056184e70ba5f325e886283dbeee77ffa9d602d9f5ae89548eff83e1b74f6dd6ff4562b4710decab0cfe1a60737ad2ede51669296efb712b5f8dd209fd4a1de576f41c2b19f2ae14c5f4d16fa2d601a010c7c1e9ddaa77e8bbbc7c61f177743e50b7dbd4691ce168fdaf78f2b5c8ab20132f319ea981429589d5a972fca01be877d6c738ca522eb09b78357113197876d796e2d23a497b39b12080c2878bb9ebd19907dea55e3df387973075249112a94346fb0cf8c9c9dd1ae52adbf801f7504847c44e6006b539a954938550f71ec13e86a5d54bcbd588ae6ccf45b8ca7d1ec32a251165133e8ae9f6832aff1547c7b29d08b49b15464254fc958630aef25d298853ae1a4c82ef2fc72aeb99b132c286d44b315071c513f4dfa728c775fe1664aa75ade97b266f630db05086948abaa27397d4d2a5fa04775b2bb3e4e64a505bd8b4973966d5136d113de12e86f5b8eec2132689798c1cc3b94aadf1cb7e13f65a38f371d635e2351bf727fa4e1bc25208e8aedeb72464c1412ba055ea42671d772849fb93eee4a37309a950b6a6e3b68f3df4372569e2593af713780b3c55b878ac223e65a921a06099ef99843b20029d3b81fd8d04a074eed58fc2f817e4afa5ee04a2140ea2122bd0823e8184831e7b09dc99b59ca6f8a3ee9b9b78791dea46137a8f67c46ee31dd5e9a5752913f271d18d7165dafacfa269bf6b520b0652a346096d28748df97cd1ac3828d2d866c6b81def6766a6df336f839a4f74aacd04ad3ba41dc5083e90eb545e6126db63c4e8137a82e7b2129d287090375c0e44b070979c0f60e698a34c687eeacd23cfb189f7797d05244e2abb0db5d26d3ca0f0b8a5bec5ec2ff1fb1b70028970b894d6c252562169067d2051e272e74d3bc70cadee700912eb30e181f55107b7a3987bbcab7836bb5f86c811782d40a413a34c76f91ba17a233d9da67ed32b49ebc5e89f8a4784a238516a24564fa31bcd1bf73c4e2856cbdf0077b193f0bfcf98016492772bd2c798c64eb10541d9596f16572676fdf0ad2561b7cabc1089f0b33638bdfb8ce9d287cdbbe10b6aaa07047eb25df80437caff48a41b829b53',
+ 'be105a6c585a766aa1f290b632219ff864ea6674b5e3f9846d447d46813e2f92b78ea82f0b515f46511a6f161d429aeae07f8e4853b8686c191877f5a06242dd4884ccaec76e16f3cc24e6ede212c78897a1518dde07aa19b3634d4efd090a48b81a4e535359a55b57397da44a3b2dbbd37636ac1f77c875824c88bb62dc90bc517ab78579b913643d81bda11d62d469da29c50bdbb1c967d0fcafa29582db1f59c98fabff3669f8c4232d4d2332c57bf6e08613dd5db5d6e39b4a6d5fa4f35b19325c2fae79aefe36485610235007da6cc3022cec2295ac0550e18388eae156d9dba8784e2aea5ed917be53e767a26c87fcc0bfcfa870d07b43fd4cd8fbacfae1ecfaeea7f12600f6b9ef7c351d9f1b8eb048324f984e2a909d253017805c2d788edbf9807469ccd45571f5d61a05cec80a23ef349a37a28b86e2970c20fad9e7e8d201a35ebbaabd14ca249207dc6d7e2fd85c46553420b325bbe980848de57724676ed6552482f71e8473308c2ddff94aefe34c724c8c52a3388e3b541d396d6722a8e201ad3ccb9a26497a50ff0e7e81f1eb109888ceae27e1ef0537b3bd14dc8c178f0c5dc081b0390d36fc5ae158fc65db5870eece0fda3f72a6d55559b3a82c24a41b3df6618a44292d374640cde4d31deda28975bfe4d980e5d70ef591f68a35ebd953c6b34dcf0427aacc132761c31897d55ea9056c37828be5e379f7ec2fb42aeba919be246306ecfdf342ac9abe341db17753289ae2de60a6decd2db2a2072afc47d5cb35879743d7c4b6a5b84bd949e0db5c719761fe2cc306d97b716b98b3b233e422a8c31ff5e0455559e2f36fe10792aa288b3c48b7e36386295113cd8db5772c0ba69f06cbc1800812413d5eaed1a527959efc26c9affeba7e7921107fae1b97ce57a4b48a227db816fdb10f78e31bb9fff628ff29cfde5ec3121dc85245250cb2e25992fdaa434baf3dd7e807e8fc4ab0be483aa0ea0b5b4143905cce219f72006f4606eb02daab22293852228650c1ee1ce541f6a88e973d350136bbeb90b30128f4791bd24abebaeb5bb6936520060238867bf3b36efc020ff8bde14a01b6fa33ea5456c19b6ebea8c87a202e7301343bb3505eb0b371d7fafd5908e7f96336b8b0fd6477d6a1758b08089289529acb4df428014a66dd031e9972f76980a2fce0cfde0d9d034128b9a2c6b01dc4b91195d26ba2278e2acfa2537077799e5b93d2ce5d19db2835205d1e1e4493d1464c3cd4810aef333f83afb4bc50bf5c7644b735e44fd84f65a29d057714928129c56983d3014b5d04676c43bc4ae2c1db57b78dda783d7fb9f9a1de38eac3dd4ac44565c74fe31561c20288d92c8bd67314f95da75cb1c1196c9231cb8cceec9190f804c6bb5e623ede980b7bc061a224c2a62db2c3dd1c6d42988797c25ab7e773a9e8390e644d830157550bcc0d2dd7abe586657568989dc4da6604560f4432b3819786109fdd187467edef19367f7515dfee2739fb3d913a81597a0e979d5c99a79b1789b41ae57fef5b916f85a1e449bcef61d93e1432ecb4e561b497ca4b6d437c52414e0cd36917285896a29a0e8fa311791bd82466219c94210ebad1e9777fc0a10013bcc87c09dbab553472d92ebcdbd8c87275162261ed22e5a5bc1cfe81f16b8dab31adfeeafcf475e3312f7030d5e6a8b3102b382a78cd000393ce4c719617b1bf736b38e5139abf59e0f79b27870b8244fc8ba91fbe88297a5ce6a778380f34f78be8721fa905f83b8719f8c87ab01fcc4120bd6a46bc26b2214c58be5ebadefa800ef4c3459ceb342c7ccf3c353b48f4e8d0be3098d2c055e9e8a76a908076715ac405f770c95fead90a68ab4016c364f885f29c3d30bf08bda2dba457c9c60322f1eee3b1f41b0595aa0a3c24a758c37926a3d3bc40eb75a4623e9639fd9459df7ff8b19083e82e6944ff176858d1ba749b17009d690c44a6101665c084a91a9955688695df8e0d5bc18d659490f5f0efcc96496912e91dcb94ac3c74c7cdef585b898d4970d5497607fd4e31b68b0ef5a16ad6a7a54ba61cd64841dc2cc7802579a2eb339e858abfff97f9c6345cbb8b02dad0df89fa8aa0be329c801c61740797facbdcd2657a4091a28fdb7130a0bbd72d5f9a26be6f5f35b176e8006174079dda53ca723ebf00a66837f8d5ce648c08acaa5ee45ffe62210ef79d3e90272c738aab87e8d80107242f1aa6c800b0077d9fdab62ba4deb06c92462640b6367e0252fc9b9afbe95ef5405f6cb28c2c321e4e16276356b751fc828c0b6c9b4805307a6e8cf26f1e0cffe32fd3fb3b7f17d400873bf43d334ebb29f0d52c0606cae64928e456fb49192c5fb2620bd552be85fe55794621f8ae8fce9b0be7c117d40d08532a3069128e62fe0fa14b224c2d1a91a769c1caae7962b8b4350492252b8b0016e7e77f20728b066f1821ea166e7cffa594ce00feb81b3064dbed42d5d84a769aa2e3061ccd8ec0f950f6f4578991907981d38a9072a27ed3860999d1e230b506e38cc5ada75374d6d03309b884438e48b83a310bafdfea28bdc05ec151270483334a867c09c26a2d203ef1e169793f3dc269bd1777babc8c097a5b4c2e16aa3918850f31fac3e927c9817479401bed7d26caf65dd31b3b26f19f561b80e4f04f1ca52973833e3aec526259290f10de336cc3a385caeab01ffd9718d64f7b1eeec7129ceaab1ba6a3434b6a98e0425a851dbe8e37650f639eb5cb6924a3c27c3da034303f7a42737525a36d6eba98ab9bc0227d1aab729ccca2a110ad85a151652f74ad1af89be9897ed22b55a6fa189edd5739d6a4fba3d04b82d71afc00e78dfc38da222e0f5208d9406cf3aa50e8b6fdc58a145893bfd338d7841311c784de90e980002384f419bb55f8f0d182640aa7d4377cd0203afdf206e03fcbde718072f0675cdfe319e5ae7996f52079d4c363ec0ab5138b5a750079b347322ef69a2ab357dc6b150793391410aaa11b8008b975c96829bd6864ea96c5d24e9d5a54419c182bce01064ce58e2cb65b51af0232d73d3c1b9dabf139c7dd892814e7d73e1271ef108e6038384fb3c25604aad9557a2edaf0126d457473c514c77ce34ba97c2cd13aa65b7ebcbf6c759b1abab4aac664e9b006f4872301431a33bb97d80b440f7853304147f9462178317d25bbba24a61263608ae0cfdb204375bb4ba4ead1e38d631358ba764d9872201333180cfabdfd12087578ff682339946247dad18b6fb77339e900bb3a9a0c71c62ef029b17251c5e5fd763b1016a1989ad2a045da7d9f89893b405077efee2b7c5c6e97b28bb682e16c030b3bbf268a4a351ed026d3ecb0eb98a3be6a5fbf561f07b7e064d0d653e30846f851e86e715ab97ef9d73a47ed4746518c7db227fb9675f68b2e0b563fd41b6889ba572155b1a3e548557a584a858e714217afd020c9ee51217a02e14e9aeb9047dfd5e83e393e7d46095bc6ce8eb82b689f205fd0a0bcf029af7e1d891ec1e7b826296b35d9d16e7a59a53a81480ca9877fcb7f100326fc2b3447b2f748e49566f81d514179a3e06dae1f5b6c9cd210261e78d6eada739db0732549a1019ec1d9be77426b01fdfaa57193d29672518bcee4f10c650decfa3fd3e08a8d2f359397de00e8ba5a27e4ac08c74608b3d23f0639fdca8984d93c60bc3f1cc5bbf2342ac280e8366a69c70add8360c8459d57568563b85f28828a9b960ca8518e1dcc1ad0bfede71a0bcb455691c8e012faf94630ec7deedf2e0d79ca0dd5378ffe82ed72849dd6535417e7ddb8255701314e5fa260c13f9226ed81be0b4c81a5dc7f2d0f98a00afb8ed478d9bf1f36f9897d2844872e582ab3513cdbcdb437ba01eb610ec49f8bfbff297eb26f5f84e44bae2a7c286a438d1b6130891db65fb5b3ed12d9ce42623cef3f83cf908d49a9c00bebb30d1d08a5a647e731c1fa037d3badc7d77e3096a5a83d0e9aea518e302db9f552fcf0ad589e28e93982272afce15408709e122f1d714ca87a44515a61dde3d0bfbe8a3c90492fbc0b28e5dd19ec0a5e0cf48f368e9194d7d76738b52417af02641b95bc34f181ea0d7bde23bca6f64f134a50b2df513c261a1caca761224515a8dab6362cdc49fba943b704cd554165ed66fcabf8f96d1aa92955390047ce91f2c597b16052ad7546471b883542122803103c29c1d14e3ded56dd72e9ce72fcaca2e035b89dd5e24b50b0b8dab5921fbb12b835222008164e6de95b04ff58e03d3a39cb1c04eac922261e9ba5f5e9d27e3317d60330c22d353424fa3a21a9c40d55487974ce14b332910e397e4c3ec9b53a02154c47a50b08753359717e8c3184bbb849a8447a27e359289b4e00b98dc6f020f8e5aed93730f6c180925c2aae0a332f43a0ae45ad9d4513c8fb5a84a51b1d1a8ab6ad8539168074758604abc30786556e44549ccdc81e78c86fa2c4991a8997d0a78bd19a21daf44233be36f8e37cd4d27d7da810ccacfea49020a4f22dfb40d4a192e6e1b8ceeaef83d2f3d606dd5177317291fd12b74e63481c4a37bb3cbd9d8a08ec964522fe825d870be4d8717766497c7e1d00070f0d7edddef02c15b5334c360a422f9fd705a826a6aac200eba6aee197f6ff63eaab1aac89a5e74bd09fcd64696cde0d1e7f4ef7ebb12a51583f46e906127895874403d172df56a9b7e8f7da319cba347af936420380792d643afbdef0f9d1638de02394c325e0f61216b0df895a2bdb947a484f16a0185cb79eb40680317496a587613ca100edf98832e2bc992f99cc130a6c654b976da6fa7359abdb44c7f67642a51336f7e57bc24274ba8aa26ccda0b18acecaefe2d3d5c1d1132cba344aa918d75faa92314468514e1f843c0ca7e3788ad0bc2fdeaaef9106869919215ce512e0692559371c21645833ae83e122836654b449e0c9f4f1eadf4e4aebf8f2d337a679ced560cb95857a40310154052984d1c298890a7982d544b268b720d51a8f12d7cd8d14186dbb9c8c353ecb1a7cd9e741d2b20b014b591df91b0601cd63220cca4b09bccd510fa660e1c1af27256d7f7b1ee43354f47e526c8a5f03a156f97d9b70d2beb0e88780045d1269f5cb4882b6a5a5ea39244ccc53de4a8e1b9a1b4b93636f848adb2e12c0d2b95f4c1773d63b8e80987a83cc71f63e4f0a01d89060f84d687422d10945dc683efbf3b1f5655202faa5ddba052a9cd3f7df1cb761a2125546f8427322ff9f462aa4b440d61542299d7a2b71ffe08ec5c97534e095fb2ba49077b0698029539505c1556c1ed0552af07d2b4fc379153b4e3511fa34528961ce59355a2bac3e7c55bc9fdfd67dd12e7b17a7fd58df775d0c857aae478e75b5890599ef2c6ea824a4fc5a3e0604ff5cc7b06523c6f8fc548dce38a4d49937c847e5a4cf8a59dbb479e94e34b44d27dd5bb12f6816aeee4859791483e65a17c193601ea24d541e555deb4267ea3f91d8bf80cb744fe79360f6ecd3f482b95dc8f2dece127e46b82fd76a007fafc484767c8701f0798d35c5ee91409eb29e5007eec77dc52319f2696fce4e250cc34adf192d9b849d871a9fc9cf222a7df139a30e84f36347b4142cad7ff5010725293a1ed95fd7c2bd8a150d0d403fec9a60c7da7bf895efff669f1ae6bdbaabd06aaf14f51792f017cf4f44b5638605820d99db15540325e22feb3d696df8fdad546882e8895c2f7d3076f52e53a6d28f884198bace54f045b9379e71cf6507a4318af01a7ede88d7f4b2a9e0d8485baa18e89ad3303059104aacc667a9a7d09e4740e6f3bdc0020d642ae733ba14a7bc07b667c64041662b2b723cb1c4666081e0b0eddb10a9a607c807378fc0b1beab9bd289fdc72c217ea4b088b9e84bc03742028c3a3d417bae6870215cb4c8a8611d6588de8c9c92f2fc8e33e5dcfdf7a6b55f4c780d3189e88b8e2e025c006dc4d496ead35937c06d7d35c49f6a250db883efb4fd4821d89e7a89e5e9800216be0a8e3943f4a5b3d86dcdc34e586b0b53f2d94c31b6b871b97e88cf79ea76ab360574fc96268f731c157db9596f76f8614c469c7d1e9f5b1fddb3721b610232130f71bc33b79d091fbd6e2d2a77efdcc9d75d2d474a7e9ebadf335835ac51b53f6ead00846f767ccbadc8a728387dbf73dc4786f800fc4370ce30093194093ba16a4f50f016c3d4072cb2afbb7ac9aa47a22b2ff909c1502bf0d10c75e1d3cd214d8034df732b19ba8354ce4b047ba42d7332b1b12d76d0d28f3fe86b5b5672a75d6735b94f5754cefbd78b409f3ab60d9583938733c3c84df1a22879c93b321be42ddae772ee8d2ada636f0313aee7cf51485de5e54f42845e215109f529156528f9accfa499cab6681f01fa28803f5befa6983209c3455f20e4ed82a5c9246e72f432daad00155aef34c98ea558c699b7c7cbd568c6dac67e14834ca2c3661c0945f47054ca75feb5ca4f2754dd8572f1d37e38ca0108a1bfd99ccfb4bee45837167afa62a0f38a03071c2efbb883cce4f139c2f71bd7d82c5abf72a262a40b428c4ddea02995e62116b7096391c891d94a85dbb6bd4f530dc074a03658b01b73d1f486d30e65fb571da822540e5da71806e9ebd08f79faaa3244be36481072120df758c6f66bafed4578e096da49f8e94cee5a0e385cb64ad9b5356bd9caf66767dfbe408aaeeb2ab5aeaaf09b946a94441a913661006d36dd516d9db4e891b908321f087236985896d2ea2bfa47e76018f8f610b16e08b765a0ce9481712a52187be3e7550b9cbd0f6c070f9e695697ed0df266d1ad70139929c117e76ea878be3f71a5db36e1a14905696430029e7feee3eefe68c58b92a274acf08ee6f5742208330aadbe4d4e6b2478f2571ba960400150a11fbf437ea809f8e51fe1f88e6d5d90dd73abca0b9e529c81bcba5e840eef81179ea27f1dd2710ebf4268990dc7c7f0e8d4053f1f0a163dd806eaa5327e36ea288c7627b9493354459e81c03b57cdbbf179d593d3d6f8c3c0deb066b1b85df29c9244229835d73441dc37555e46f75ac10a23b06f2b809601ec16894ae5ec003a57135a02bde51579a38f40bfbbcd33202ef57d3b30371e63d723f7452d6b7ecb84ebb64109e65fb79c9369a6dfd8afd68d55e27e0ea4c3ae48df96eab763d317c0d41e9c426279c16f4ab95ada9f36aad04ac782ec50eea9e6534f80ac5c67e6b4d77a5a90dfcd5fc3bc1625d3aa31659fdc148df3c1051d5a860b5133dca20007fbb2c41ae7c25b40f2694df28206cfa114b1e84a8172d24b80b0a3756dec2d6249af220901c07c8ff39b1b61ae1fb07ff987db1b189b90624a27bf9a96ad346dc757bc84b62f8958c792d745b6e2d0e1947cad8b0cea1f47eb59edc9e04ddecf7f93d9b48c784b427738c9c923b9f76deb79a8c4e7c546a16b2d752ad134f331794cc2c10d018af60a7439127c0becce096712ee3096ce18d38a9ee6f3ca3b3abc68c0d2c1330d1c882d6e4b494078aaf32c343070c480552a5ab125ce787d9350d1190f2f769e5bfa4db19a13c063aab3b256b32eb722007884f60ee8a483e33d6d15a1df33035b67bb4a2760f275754fdfc09ff2d77cc1c6f0b5cacbb813d38b26feb059d005c5d75f811bbb4075b4d29db91b458c583e1b863680ff1af60b43e21a6326b76c4152219d9ea9112d0e41d38a4fad3e7c227f2ec0590a34f4326d8bb3e3cd4e0076e1e9e8d7b4ce632ca6697c64b455b113fad09d7d766cd4f00f080f58d6ff890d8b9fa8ef63dde0b50846d582e239bfb995541313cb2c60ef334176e9ca31cead592b260e3ea76c527054ddd0be526ebe57a26b448fdb5ed4e01e32be2f4b98ff5175aa5ace94ceabe57ade77d00986c749a2ed374ce0974a1f878a0090b7afab7e667cfe9a0dd0676fa01e9fa0c4ce7f713de01589a5d3f7f764c5d772dcdb58e5e4e3202b78382e16aee66eb7ec0f8bd9598e05cf91d983709d2c6a5f22e4ae90d829e9073ecfaae38d7e0f9cea119689df3d30a50c7397921a07e2def19157862b9480b8c85d81dd232ea8d7249c4687b836bd93',
+ '5ab7074f7be12272f9f47fd8900dd823ef716b676974502eed9a0bc038fb5ea149ec615a15dbd47c7d7bb3e37d220b38a66f0db9ce2f603d0681bc72cc39b56a8283d4561fc9ec9125d6abe0d33b70f89bf15c40d641acd9b7e146dc7d6091e2edc38aca007115b6d94c9057f921ae6bb6428383e971db0ae800d083b4379c1273f6ab3e209bf5f581268ad0599c6e99e0a9a80b708896d8812883f1d7877b01fa625e3ae711345505adc0d45a73588bff6df45ed14f844c6a9f87ad5018d9b76b51cc47bd9a7bdf4d265bea64701348fcd78e06768b4e6d8bad033f85b25c3daf091dec0afd729941dc829bdf5948d8c02f9e8bc5079b44cc9c1f30e01dac9aacd378b23603c08dca165e6e33f79e4432bd4e73288a14d716b20506c72072d64cf60520a9740dc3ce1b17ebc912be9339c8c44806db61304f39da4ca48556fa76bceebecb1803413634d49a07724fcdbf9a2891c7c329576177c987de12ff0d126b581405b64811eba9bd0456defafbab79a3d7d20a145232b5f741dd901617074c6dabbd18843bd4c2fcb01f1f6723e5ae5da19178cc2bac3a8f0109c642213fa7550a5c0460c5d8c7b626c8fd3d5054807d776bd4f6eab650b750ca7e2c31a1c438b94b4383b9e1ff16efbb402587e2392be1c8e83d95373d97b5887e7c5215ea3d41f19d971ed6b341925eb0c6d276209168107a4636194a59b8ab8fd98983628c29b5d941fd9e2b62971b15c938f87cd493ac53bcd443bf22e19b79ebe0d3754ef901c12b5d23af9f875936d5c251961feb023b45789de41db9c0045b5eed68ab3a3c1bfd464c8720e97fdc937667b698f77bfa89290f006fc783f8d10bba86ca73cdb05ec101fa0e45f7cfaa5e2323673f7f5732ef815d43934a1cf4f02016d42c1e48f7d42a51912428d7c3826d27b54b771be5d2db41bfa67e396d14085ff0ed96ab708ab0d5d00c7abc86e82e08aaa4c90e4cdb05f50b878817e3805baa473f9da070e879cbf48855459a9a4c0f6dd1dff80f11e7c150bece7eff3aff3f01a99bf09ff86e7e241d213ca8a0b184275a20a1d67d6aecb6d4ba36694cde6645b4f863a7c1773589554667570043afffc8f3d1482574c06450b306c8bddf673be61dc12d4b7d374bb72c8e50c4ecd71ea1c24f0d13ee8383ba20b8c596e9890cca70d2dc9f66cd91cb3c7b3bf49346ab70f4e4ce4fc17e3a9b6b1207dee616e0e55a5e59c41c3f831adcf5bf962042cfbaf15833dfd3f9b27afbdd379dbd54dec1442c4fc285d54b2b75c384b47a14b2092073e3cd0e3c12df38e0fcdc568c2fe5940564a28bd70e8670ae33558b047be4ebfc8721598e83528ae5ffa29905fad9b4b1406b158cf7b4337e74823a5953d4b9bdc6e19b392d5c59b2e7f76e0968260af88c250a3d2b3c28fbee426f5d6160f37c4917be8337840d115de3bddea15300a2f0d85432d5a6b6ec4a9d1256ce10ff02b7caa9b5ca530ec7096f6ecdcce8fc76138ddab31b7b441b2f9b0c8dc16c4617b90344160d50c59f92a2691e126d683d9e4c840825423c4e4645434a63fd3082f7c977f029039860a18596199a6babbf381b3a781c83054e37a2a0d5db4a260d18242a8945f4f0d1dd1edd068d6a2dc48a04e44c28c707e3a9ba0b7552db8e7ec51c452b8aac36bcef77272c12f056143c0e6acd7094aad444966d73f039d19ea5bab01c20b2d0e77e985ad452878e76f541b4401d7556ebb13dac17dceb5835610554a42e8e281a3387604674b63db45da6abf056ef73ee98b7b2a93f798a0ba006170ddcc9a41fb64e1fcaa106cb941d443524448c69a6a00d6571406ccee70bc3e29d54eaaebdaef581a801fda0dfdd243b230fd3a0db7246afb7084b8e9349469e9ebb79adbbc3826d27bb25245c444ca636de4c8155b66a77c2be9e31d5039731563cfe09e29effca90f806c0d5fbc65ca5d98072db1382a5db8bee1f76e1bc850efc0229fae773822bdf26da10aa9a47b82af6dc373195204a97c1bf3e8abf800d260d77cb45e77c40990fdcd7f8ce4eb7f636282fb9abd25709a27bf4a7a70c9de0a55a1c6162a0174d492cf08d6c58e968c9bc8c53cca24a0a16ba62df7d100452543ab6e3ecd6f8245a3423127b4f97a5360215a601786ac1a7e54edb48738ba6a18062728d062a46cc5a3a2f041a09d80560945b513c0057dc628143101f7a011492e6b64e18f6da2708fb8b0ea18727bf40cf19c5d654f33d9ac3bedefb198d36d90a5936f4a408f1a530cfa1cc59baeb099089642faebe53f5dbf4b9efaf728ebd98004e837cfab4999fa2c583c6ba11239d53362d56e0a5dc938ea9e386d8a5a756559dabc5d5f74b11ad83ae174fbaf6be1e43b99380bcb5b134b6bfe4e601fd0e002b55c9c443254d98e23595a06deabd9294bcafc61ee9fe8de96b12c42c2886a9006aceed1f8e1d0aeb0085d6b2676158301cf0db55e1424249d0589bae9187f725d01d7813ee47aba5f7321811d571181fc39fe7e90445faded4fd930d9fd892a171fe8ea7ae94241803e38f139cb6796a97031160b9ce5eb1604bd3d3b943237c33b8576434cc1d4c0adf633f24f82418826682446aa716c95d888487498e52be0095fad1e9b73d4af56fbb1dd5fec1316b0e621c7e96c92fb1fdcdb2b682c670bfdb9f77351c2d19addbbf361892f7be3944d8715d641fe946f2d7db68e7289a58d370dc81c595c1196b9911537a6ec5d6500cca6fd9c20102afc98ac7851f42b2c5928a33781b4b20676e37f07fcf51297a27af1bbc577ecf7bcc483b235472b7f93009b5fe6abeac16692e5569c2671a0a51a8ccd7896994a0089b2c8b6e314eb670df6d1f2822a4c1af80ec00ee119147b13ee7f0fdc93964a40e028316885f467a96c5a2fe7fefb06bb41b0cfc56767b70f8d5d05ea6a510c92625d4f47dd3ef77b6235193de772bc3534c54933fb1ecb55ffe3f5209c2eb9e6dfd46af1b90fa8fc5f1f29046237adfe4e6a15cd22be2a9f9ca0481f9773f4c6af3a0f0677fafe94d964e9cc0dbe4e8ef51bbff19caa77451a2e9957eac52faa56dac5e6b8855aed57a2d605387b206fdf46a231c77823dececb433a0496ed1a7d430a22e943505e605578307102d6095397670e21bba2c54112e056837d9f9f791f563b8899817079f93aee45c9a1f3f87a690739c986b107a1fb3dd5cdb991d0ac34a1251c40cc0b0aa89f354446a8347c328d7e4641fa88e4fe507a1bed9a2a961ff43d2020b99a065bb3be0726aeadf95e316d96673a4b48542efeec8e20dd5992953f6993c1bd78aedc4e96b47fbb8e4663965e58d8d568b729c2c43dce6f531704ba3d3c8cdbee1c6975578283f3a785c486f2b95ebdb271e164175efb673f27e32c3f6d737bb34efbb0ee1353ccd196b49de7eb444ab22ee6617a74103b6a4f8103d4b90e237490f38faee987cda8e3b469c2711222e4389939520fd58260f89edf91a1c559be9cf1703e77687c7a07546906dd60ccc5498a5a22a493a2bfad7d2dfaba3184ddcd75f1013fae3fe17c152a29a288bed1bc2a5280b4d332d2f8ac6d7454cb4b7eace1808bb491ae5bbc671baf53126f962b09a0e0c33ca94744bd2c9313fb03fdb5d4bd5278eb4e6532dabc0c5196558ef098c3c752088c892c2fd9db8a5944f762bff5e9fef3768a99d089c5308d0728a7684998e3ef33151964f3b20a6e94e84e13c8b6b942dd383bf9137e725f0affd9f880f9eb59538786fd08bb16936a4cd260a44d2a61d8eaa3b4d77c8984c780f2ba385efadf643d1f78976b51703b819a372711d4d9003e6b84545408a6c3387c3dae4c74d9cbc22d381e272d7d9f430ff7bfa95b00d99fede7f8a9523d94a2e0e37126fbd110934aec0b931d23ebfdae32ec77ff81cb8bc57052c108a1a23ccb5c1f82f26dd94cf2e4fe13a2fbd81def791c1264c45c8e6c8df15f9e8b728295f807e4eb086c3bafb3544d517dba305e2afced1d5113145dc132b986439bf0cb57d8d16829600bfbe7ac84ab2228b174d8dd7c7bb075ca139ab58342727523e5ebf0bcdc595b2a41f2757e02719a2dcdf35c55125b0afc287ae821758094888034ee2bca0094032c543d8e19f48501b41ae7eb73b92936f6f259387206809b04c33f82b3c7c143159d632934138fe9e1a00ec4127377172981772cda7cafd7ee0753762c075956982e94f3f3670122b1334f6370e27af91ac8c073114ba8cbc681c85a8557d2ba2e982162ca578f68bd45083e01be8b795508d3afa8fbd1e308b31cc47231b3ea336ac6fa4b7d91086045f7d857304ef452e20f372425d1ba3f2bbf38551d73635106c42ed941acae34b66cb6e4103c16bee501adf5321ebde45c2e2ab08397c201fcc775063c38b6c536f55e3ab6b94cd3822978aea91f6a62b4f81006fca762c0cd3f7154d5dbae7c181032cd9c6cf35b2a052bb3036a4c64c68b8ceaf118d207f89a884796fc11d40a5bd90f49bdc6907d134ba4c975e0451186f5bef9629c61ff773e50f8107d676e26b5824732231ddbd23ce673d8a2ae2648ef158e2b3e49524407f391bf4d90b40f5cd90d50957dd6d0840a9ae92fea656fdd6127c91438819f89b1dc6f0f0a8c743346149f9a1aee8cc583974dc4946f5a45244ec2daba1dc8181e30963e1f5803dd8be575ad9f836555e4017d2d269496baf16bbeee48877c579b46db5759972cc00d08894c565608d9ae51dda63b85b3b33b1703bb5e4f1abcbb8794e743da5d6f3bf630f2e9b6d5b5451105ec2db32fa283d937ee75e531abe16b597a6882244fab27134db4265a6d3ab77c5b879d692d4e1ad1e429da4fc9bf7a9f6d323f0ff5dd1386996035158601cdb770d3a50e980c645838e4aec38aa82ded4c2b517ee644542172586385843ccc3dc89c8a7e97314e315930c34b633e0db9a3ce7a0f8573617a75172228ec4e2b75cf4c8e379f7f20f3c198c835e7e3828ae096192077dcb3d7fe26f17e22b873f5f15e50d8052885260e771e118d3b34e7af1caf5021f2d3e09a027203cbfe2e44db5c52ce8cdff33e9c66456cc7979d464bccad2d08584a2ac533f4484c91c2d8e9c104898b07f3b3f7e0deb62b9b142d6310b305bf7f7efcfc2628ca2915ca38a926f6a78432ff0cad713a7c2db8321371932178abdaeafdcb636fe7e5fcbcca95f899870ce38dcf2991f93d42718f7ef3cea8f451ed2693af763a5017b913354ca08dc6898c287208bd8b581e4984f6662d9a174638ba6514d9286112cd55df19d913e48eaf478ba76e717af5c2bae0353a75400d500ed89806bab97bcbfd4ea9ecfc5f80cf6372a22f3c147d5f79e30d6fdb1cfb95ac64a57951b7c71781fa59667796046f14cd657a5b9329827a655b583beca5ac0b9fc9bb9dea69428c68c4c046269e00d4028dda50956ede14fd8b6e085d9ff5b4f07147efdff6cf651058a4c89f9217199867b9ab99d1b4d1f22a2bc0ee5ef530fd38a8d7f6e430b497a16e7f5f95246e25afb2ccfee6c95a09b40c15fd473cf82c8c58d6cda5f8f3652b97eae52bbb3a00b2304553026de5aab5a958290d3f72e4f8c27cc2d09a99ef53bb9bdeaa4e15d01bcca524d9253d4bbb6e07fff57f49dcea903a84e89810bcb643f29ab55f7a0a48d26cf2cf81996311e4b5c0b47ee57afe2c807252740043cfcb9fa7c151ef25c60bed3b5a05b2877577d239bea0ce258001ef29a2b0e12b0cace39442e7eda91fb3d66d0d13f13f32c1fdc968977c833cbaf7095dc12bb8f9727edede63ee0ab27499065d5edde0f64d98de66c5791a60a2089fe84758a412b6b1e5e86878c12ac8a5a5cf28e1132c6524072dbd3c31b871c45bd695e042e4e43d47f5580672e52aacb9d714a34c31c33fc221e13e8f90849adbad3f6b3bec85718389d52f868e14eec119a48d02c2c23cade7c4087a8564fc8de0c651e5a604ef171a424c7262020c39eb4b16cd4bfcbb18e3f82299039d79f0f5bd2cd68e0d16812b41f5dd1d8c85b2d09ac91239cd3b91aad00551978893836076549520e878403136a41d2d1eeb9a7df62c6818de2ea6a0fe393abdd0c9d3c5948197eb2194d2c092ffb8fd339e7f27ffb935658a04d67ac526bae2e09d60799a0c556962ecb76e0931472a651ecf8319e800ac6b7e9f7ce76668a3427e9810f98d640816f07c7dec013e211dfd09f3c7316059fccc603bb770ba7b70fe0910255d3a63a8308094dde8047de8b9bd1eaa3dcb0ab8aed74bc7ce36d7f68c21ebc0244ef8a14d6227314e408d3fc567581565b71b196653019ad754553db981283c5b18339c77afc998bc68fb9db09734ab319b17b9b36211a334995fc106954a0c6a3ca0f46fb7d066bae4357cfbab38de3a0d9cf3f692e0736e7c643ae3dcc5b2ea22cd439618f04db248c7781731c920e784f762a053e27ddd841ee5e1d62fb2534114f47b519d903b1d2df983b98a05a277d3805123bc351a36dc5c70ffecad2e3e14919fe02ce0cdc7d58bd1b0f00f237c9dc78990c23bad0b192921e880e5e36048a5744342b1e2ef5aa981967fbfc309d2c2aa0998f3fe7771b664fe810f1b5e2daa88f9638602ea39dd04089d7a19860eec432ca4f08712629ecac0618b1e9e301b4e8103dfb64edf90e955ddc08f97aebed5487aa3ef62584cb3cca6dc95970c624568a8117583c85f922cb545ada53eb4e10b8ea09fcdef4ac071d595f8eeff2a0e2ec4dda93f90a3aeebc85bec453b68f6d4555900db8413716e5229c0eca4dcf931ee96f9c8a50780116b85d3ee21357741634fcb01b3213700c11a763679f5a71099dfcfb591f5c64c3365ed068cb4e2b13ce746e6f91e91d786ea91430b0e88493b1aa39cc3f8e1ea944ea024da61d9c256d21cd1d2460261381c9aa8b4822d5a56ca0dd4d77b537b22d295df96c6ccf32572b578bb42192c39130ace22eb06b2817076f439179b8c5374268d4e5e2ca23d5b9942208d36972e805a29c2b394e02fa0677165384a8c9ac5eab47d92897e12ff30b4aea1fb407cc7115210765871d5dc67e2d867b0fa8d2a4afb80fe63e53ad02446c20ad7a6a869c8cd98ade1d5f886e0bf2f209f549f23dfb9050c3c560970ec7ee7324c3835ead8f2c7b4918b2c227a7cc5ed4639453501838646736f2343e1d3aab2d973a9bcca682a45181fd5519d923938460713f068c16137b98f0cdbbcdb6906fff7787235a43cb9c5f28876b29605ac2f5c8fc87e19425a7f441aaad5fcce993022268d732d7b9d2ba1aa1461fe979362981d3fa9e19619251cf133b5be38040a9238713b7a6369c9abdead14427b88704840c874c0c90e5b781508d4a621b1ffb622cfd6f190aa208f8ac351f82e15d8eb9ef5872d77a472f3fa8ccb4b383d700c79fbde496fc8b0bb32d993963ba62d009c49298e1e761bff165201f3d8011c03b277c9e86d5ffacd6b72618300d3cd15ad26047929fdb727fcdb01568da7544a40d6224525da8f24a90034653a093999662e80552695c4dd229a51d9c58aa93ec9a96fd605c2be5f5005a4b323b1031a3bc525d890f8c5ad0c026c041c359b5f91341b41a9a338dc05150ad37a85aa06b28c7d49f5714a062dc5c84bfe329b3b5c38acee7de66f393c90ff6c8ae1aeed4ee6ff4fc0a9c2771c36ff47f80c39406f19ad52ef26c1e57b2bfad4f14f824c855714409f0cbf8e3be8695e762ce60d6e49851599cee16f252875b33a39b492ea6f54c2cd04a1aa215c9f16050f31f0ce5adc8cfa594e44ef29087dc23ac65ed2a2595ce73c0959410618f5314dada903c01c4f8d5058f52d902b9b25cd281ef2627a658a2d672a3f776f726742a994a31bbcc3cf3ea1fe551047a1d15b6a31be52307302334b8b6112fb243398c62220c046903c9ea9df1a0be50851800d659ae4241c0be816fb4a7b547102ba73f00140321b51dae105d0f59c6522b571f91c8abdb6f3d669f8701303ef7112437cc92e17fbab8dd8011e6fd61ec176388ab7c885da4668a511667c7205eb4aa526ecec5087a6220f5f46dd1abbba3cd189012fe50c903968d4921a273afdde299ce10d8465ee21f78f0ecfe2b28993dd726d2373b45da31590876eac251527313d4a041c0cc028110ecaaab6999bf5ace56035ba680b404bf3bb0c792acdc07c969c529fe2d88efc6d1f001ee77dfcd0409ef9119e258b6c6ca15606d2842',
+ '86ffd5bd3bd1cae10706a61d247b2257b165f37cb53ff21761077a2295a9111ba6bc4b5b5f6ceca445b74df91fdd01b2b611b7cfda75628da54598370452383f72b3508d07b73e17b21e15b2c3884227ac2d6f8a08cfa7c7dcedbb7e1d3ae511734dacfb3d9a0763d5a1c5f015652ce01a20e154473508ee8d66ab9eea4760b930f2264c08fd91af36a9275d1f5c09028852d6d6a08fcc2a411830407362f060320b882871c822245e9f019fe8561fb87e2b15b81ac53ca27f6b120cbf74df2efffe98397ee303ead4e91c5e7839b828851368a1bebfa07fcfc718a9d663734a21351f2439367c2820f14043d8ef1a7a24729539936640e8b9940cc0c019f5dd2016e494aaaad406cb1d34f50f5e8dd7e4b06529a1a06306c63ab4f8857cac0e820bb12fd82555ad5cd7c21d25705b674c35a019f05652017d21d8fa2e76e206d17b4c9dcb9045455b86b063230bca51e4690eb08971560067b1426ec3eeddec94ce7e878bd4edf55191c76e101924be34b5769773d7b52b0a53b9fa4acbe7e9546b0f9521d795e6c562cfe7f481afd5c57fa276b672b7abf06d0449cdf7462598bcc76e948385403f609075d72bb4bf1e3058f45b28a6a8a169ec01535942c7e8286ebbecbf042a47f3d2c1c2ff7aa3c73161b968e3c849a4ad39cbe5d925f8cc17cb2331725bcc66835c73bce54baa49e9856ee88a67b780a3b34e8a5f35dfcdf1a74f6c86d34f2378f732cec56dfb2bbf8bbbe005c91ac0b121334ad3bca5721fcae6a5e2b2db073b6ba6ff8729c0f51d3d475a3c3699e9414d212d1d00714407fc91e26e4097993280785713c71e306a61bb5d17f85c85bafdc13c264a6cca1205f82d12c7ac61c6fd50de518f3f630dc0bef27e568c1b84fabb7ed4e1bd8ca8acc28db68d42e75facd59d2ac94b167022f92059975134070cb6fc100f8e1232ba980b42db7fb46653b09b84bc69d1fa4f13ef9004d257aafa5abcc31a04e516f821ad9ef98e4f41bb89e049b1c21d130eb5670be5380cd88e50c8d34b498cbe2b067db32f95405aac06755ff07e8123288791b282aadcf68e40282aea858f901eee8367c5bd1018eed261b0c1c486926630746e22514d6dc3d1e2ae3fdf77f69882c6a3022d46e24893ac226cbcb2c98e5918250e55e9a5f5ac00499180ca57606a4e50300e6a2283f25f9f3890902e68a986c08fdd3806ec7989e22a90131b3f4d23549587043f6796810e6f65a52abec9c528eb11c1f96fdf86605036d7a9fdd34e9979c19da1bc281a5657667b265660dd436a1a0ce44886fead4c9aa06b62a5d60526e3bcb04a4f336138b89988f917d7fb5620a1303d17f9b066e5f5c8356bc382e316ead4d9b4d2165c8687b96f96ba37f54a0946173a8051e53f5f2840cc1df7f782ae7530fe025d0af6ce2280848edf91c1cb8c9d96997813cf65f34971ed4bab4e90fb18d6c81e8930f52af48a5cda70ad6f6c99d44f0d36be8f9219023b6847a318ce59e42e41225d8438924f2b12da357d4dd19ba7f89733656b78260d3513a8cf56bbcf3baf1da503b6237c036e19817e970f94ab217e5770e72e856d9a56863cfb0640f74ec22ffb0b6fb8ecd674bafab1196762713252376e02c8627f5a64e82601de6b075824f49f3eacef3232087705b7bbd4cefd4b4269bd97f4cc656b593d75529ec329ab74da58ff136a9c927ffab3380a21256a1a3e27992c69c0c219aa2a4398687bed0524855a6167fa8199f8d4870b53f3d946570877113fb393b0d3e85a62df97122ee58c65df0f94fc4e67e150df4aafbd4e1a28d9ac348503a422773f0311c541788536c7974bb12c24b0a33a8ff0a141bbf14f650331803c7ffd9e9983e54da2696c4b2991049a39a539e2ee222c118a144344c6211fea66c8ce2610eb42765e8b029332d420984a596b6514a0e546c3e178d0a20be40ca808fcd84d4212899d66b0d58b6889f187c7aef65312058912abf8bba2cb6a2e2bc6ef7af8903cce8680dcdbdb5525ed19776b5b537f73229ff82acd6d679798178a0fd4b9dea88d4263f06bcba3ded628f1085dbdef1759935378cdacea559193ddc4b036333e0ef897524e035b9af2dbbeefc4396ad9751641498506598b62c74576d41a97e698d1a26c4c2a85438b5b6586586ef9a1c04f4c06bb24be2154dc4c8d090b128875f50ea42ce827c0e7d06e37e105a3578067fb1538ad20feadfa7a7117b1ad0ceb8b6364b8e74bf94e61626926a571e3fe86d6dfc44a880cb548568ac6b66f5a43bc2713b6ccf8d60a36c783f0f7692d82d266cc26a3fd32b6ef6841debf615ac5afe418ef42373f627828ef07a3961e54763245285f8758f61738fc267789f5b88d21dd2bef0f1c9d4f0a143350fd9cbb98c3b090262e66bee64f246783f111667d67c9317b885504e2f75a3ca6a89001619f7627a2fb4556873422698ea19715a83d44cb8faad2df5a7629e94f9ee36cf85000b579f5db06206f5cf43e9f700e352bb6bfd37e7c76de10e903f0e77b45855eb50253251116da893cd03bf582994db987d6ee0b3910974b0252348c42d3324ffcd5d991d0cddc0929c42eabb7fd187020d88959f2f6adb2dd9ec0941f6025ad3ff8b243fe754f778b9abfc7f684bdd7e78d4b71907147cae0af3f07f93286ffe531874384545a5cc91895532674657745bddd5af9c78d1d744d57edba927ece564900974bb2263e4d075956311637d6a32fe61c1740a832023fb04f49c835a1f908c4493ad9daa87e2eb2d9feb25c7e67ac0fc0d026c091f04ef3348e1cd20038357c6138109f1fc45749e37590fbf7fbfc004d0ebffcc3a742c8a571d67d737a9acfe52f9e9d2d8748c57c7ef73dc7c5e760cbb855854f90e3d6a9da3d608328fd66df06ccfb592fcad0ac01314a782f35d743b62e83bd12f8c648b19aa0b7a827e856a5e2e22b24a50f7ce68449929fad0fbcf0921d96944b3f8ade35689863e0fe53f428792ccfa12cf31539629b7f18ad3e4dcb7b6080a2ea784956deadc1ef50dddae5e9e39686cf1a7797bf1d363e5cd1b820c6a63dc66f19db452a7e2b1e85fc426359d9e21b9ff7f2e8859f2ce7c27e16d826ed337f75767a497593073346b811e8f2941c2945956f72dfaac9db874c503cc2bfca94a4950face775bef73a1a30bddb9eaa7868f9d77ff3c575e154444e94c3a36acfa68083b4a7abb9320a29872a3d7ff6d0b12d1055e97898c3d16cf82850227bf6203fbcaadebd5fac5deefe7640bd66f9c838043cea4b9a47a5ce363f92c365d2bdd8a4d6b334172cdc6f7eeb0be264ba5422997e1ec7e3367872f122b10e902b2255227f4f964b7c2fb6edcfc77657ddfef3b962ac73db57f45e0f1ad48b65c9fabd1deed96e6262fdcb356b9bffa7286db44b2fbcd7eb74bf9c7a6d23c34a73eb197f6c7a41c4a7eeb43c07b007bb64496d372e787c795381341863c73da8f39d702f3f5a99d813ab7beceb2e15fd593c2465a706e9efbde32cd346e881e80fbaca15afa0fd086ddc282b5e3cb2d4ceece3bde18ea6b37a8c8e925cad187090e730190675f6bc7f29c1e3a90264a88c01b12626393c2f5226623def3f89aa3dcea8238a000b5a0f3850fb15a9b6e25c24978bbc2e32c90d56aebb45c65cfce0173d1b856ac44be6b4dc5be8592de804fd9bd1ec9610eb72c67cf6a6691ad03f6af4e93ddaa9f7cc436eeb6a3953234c33c8e1fe995ffc4b1ed3d55c504a8d246dd775fb7943c6888d0d93e572affbfe6f23a33a5ea6645e05bc40aea4749b55cbeda7066e1921e43bc13b9f2bc9d9e058b6ffe80e6d8a74243d1fe38d20629a2a3e68a8e2b36ea6f592cbe18d2a23bc9788de4fd03dd85423615a4432d74abd33d39e27fd9a169762892bf3c0594358d3a126b8cd9cb5c8a92dda19bc20bb848db333cce655827f2bad431debde9f7cb50ac16b2d1589965eab85aa52841db9e0e60ddfc66c1926f807fba73742a1f2e4ca95b0172dbd87ece2443e1d8ad822d67516a8c4684253709d3cd8cb0caf8109e98ccfb36eb763f8da001e45ba54881567346e09d067d03b79ecfad0c433f0cc708d0f2a5fe0f22d9c9f93f19cf9b245c4517bbd2cef6302a3f5536df39408d4667fbaa488a0fb302f0be349b9306a0f955415060542d56d213d2ccb203a91cad34c1648726048ac9b2a5676985f761be125850fe1c8ed23fdaecc11d38d5355bfdb6c3fa4869f47e9e636a0c1f09f10b0ac13fe4be975cd3f2f7d6894f5151e331403b1a67cc9a92025c9eebd49ad960ad106fcc80d3312eda785f8ecdaa1cd36df25c501a88e7b48d1598913d46857f87ca830e29cf19b11002de862a4bd09d12418a33c74b5656ad12c994bf798f881953fc320fe750fb221bd617fbb327a0bcb2574df47080e8c0d8a45ee1c0424ae0414dc0a9b8717d9f27d8ac987c7c9ecbc946073884d1fb96dbdb583aa758186b16fa429dbf15b8d5bb48cca71469e7ce0ad8e7fa14d3ff6d90c129209b3b71184974304277a82d644ac8e0adf75a0c41db8278bf9d0174d39be84a3a0866f5766d6e024e5e935bc95abb9103a1e78cb5cfc521ff89e4fc5751c323fd9b651613d72a30f7f071b4836fa3464eb07ce9986c238c4067e8e66756e45ed1b0a0436f3b4b54f5b9cdd810fe1288e58f94937815200018b397c3922fce436cf4b31de6ee43e6ce370227009a7bd16ebbb91ac37f4d35fef97c245620d38a15b417f62984a65ec7d4a931b0a961a850b174f008ef0d9659a6008931eea69b628b497c9572e535993f3d78cfbf468631fd32b3c708d399bf55cf5293fdc9efd9e6c201b95d7303a8c3497cbc50ace3691b8cc67c4141c8966533211ab29ffff2530c360398e2318d0d37bde4e207588c88edb8972cda9b8560c67534c19b54326ca28a12b9b547ac7982acf807e85e02c1dffab610009af2e503bf508f6e8510700f6e65346ece8d94d4da6426b25c7252ce1d37bf563f654e75601c906bfb2329bd53599d713ea6eb88b697b317dc41b85280ca7f4e0163299617e769363ed0d636f98e595f009eea38d221516b65f76ee7d5bcd44a8962e2e0475f7e3fc8a021f69161fc9acccc1d4fbd8f763f8209e3088cad62ee2feb26de6ed343eb11278996eb72fbb6ebc4f137c94095f6a90fc13f060a8fab7d7251fb0ee14eeaa0cd7972484fdb35f5f002ab85d33543536f65e5e252cf04ea7ef0c0981823a100086e2338471f9a7403ddf583b5d88809af5db79224a57d0f978fc9aac63690b76ef4244c074d46de46cfc04366f4474456250ea5eeb79645227b70b79a0c4c2f7797eff3eec8ed9d5f747635751d039bea38658e1f59c333c660403f021935e0a8c600b08c36d159a44891ea5ec74f68d22456cb45209643e9dad2a18ce1e063890ae1126dfba46bbbd89dc282678d43b4554cedee80820e1327829f98face308ed31b89e2abc97c0c81146e9e823fa4f767d2567a1e08fab8e2ea89b4d861d020cb1a97a8abd2e32cfe704ef1bbf90511c86195863dbd82619974e07eef5dbae53f68bc2e903339939d4bd43a592c0ca9e3854b8183f24da3b7ac4445c3f45952bc7077c2ed7cf7b6ea019eb70e5b041665b7b8fa7559a4e5da0b25a7dac843efdedc5b241812962d613c2f162ff883edbb739e6f865630ae5d2be523b86c0312f316c6b0a496bd5d9f55d5c652a7149651333c4f3b7a6963dd693337d1320f72b59a4b07077ba5ddf0d953560ac93eb6c39143180928c7bcb74d7052a9dcd17572d17885e52418150240f833ace15ab58ef823587403d40f97633d02c6aef191d776bfbb92325c99402764eddd81f1292fe25d9f007e06d2ea24e9d6ac2ad86e4fd48116a3291bd136f709f3012dba7802ea724a3309d8ad1c2d173ccef2fe5138857d359c7abe63533a57d1bc36ff28b46ad1f88029d204c9c635b3a389ebe14e03f486e2a9bcb6315bff08635037b1b10e2d088c708b606f9ad5c9ce4229a4ecda97892a28af403df630059aec0cb9952eb4189c506d567a0567eda3ce2ff29173d6cab79a0ea7f573bb67c77cd696bd5737a7446bd04d75b07c9dd8807780d85a22ba2f504343d46ef19d8d853ddfb612081329058cbbef068468b10b88a62e181bd605fdf5aa773273153187ab0b0be9a60aaebfdb2842820fccd1b1ebf90f1812bccff58e523a88a07d6816969f1b38a1fc3a1d54291cbd8f48cf2609eff7e4b7ccd1c985c1babc0a24a594990280998c467f907024ea13734aedea8af184f86b84dffc647f645720b95e941adbc886b597d3abb7b2171e6c61c251b7b412edbe833f10b2f1c3e4848a1797a4915f8ace5ecad1b3373058cb1bf0fe389e3e1f21367388f9af55ee96354511ceb9b2102719e9a4cb4ad23d2fe7ee6286167317241d01c7913e96dfe6398b84e1cb6cb16047a497986359460b440f0118c33e6047a58e7f11f60322e5154f83863cebd90a66801c82f7520dddbef77c791b1c84fc7e6df2148c2762234705703484bc0175f500b6139d38ef27c72c172f8489efc2f634e2f1577018114d31ed753959c53381aaf6d2cb9a8469eb116384a5f32b2bbb57a430ee79bf4fa6794db0d1419ed38af35148e8e34824994c0440e604a1a72c5ac86bc7a0c23ec130338fd30fe8d68f5e57de9bafb4d850306bcccb2afdb5c7b2b1fa6991bb5f5bfb115ac5215daced507d1c4a5c5505be62fc90dbd299e81bf413755aa92531a53e79ba0e0247d7437e237a8b75a32d225849257e9800bea7a34f64f1737a465b8edf26344f41d6204bfd81c58819cf3a84d40359e7bc99e924d831e46d351ddd40b417de44d639f22e6dca8f00436c557d2fa2c44e381a5f1d40749a12d018a9089b0742af7f7337b9f6ad4e7a9a7501b5ae9c64380da087d11a9598e0f5d75d5ae69d27db98026f632aa29a539989cda2e348a031a7dce204b92d5773f0ca589c4911e445ca7807ae6c52e927bb0b27e970500ed7911ffc1cce945c386b722951153e1e9a9134af105e36c16399899eddb81b167643fa448dad732daac06f5e2ded5a2d3656c8c42d52b699a39f7591142d224daa5afc39cbe84232479a02557723fe96a5b5c4c559fc6af844476fbd01620e1af020867a7c017a009b52ebadb17ba3f1652899412aadcc63e22bd85149d92714c44f3955027b931f12757bd58136e2a3f119b6b614094342e9b7302c421515b1b3317577f3d915f4498c435b5af82344d613bda2aa71683be774077c8e8842782961db41c48c8b16ad01d2ada331ee5a80a11e755288b3a557dceee083a545eeb36acb5109185b0cb9709a5afe76cc4b8d4c49dca0b1be25a76c26e6b61e987bddc6d604160f1e2cfb530ac1b129159e687fd017198ed02372bc700dba46a2a604e07bf98fd34d1dff13b4a09feb82c98ea631d32172a22536183ab404a00d03c5523dafdaf75056114d2845ef107c637c69ff8f6cff9cb16e39e77809cd200b9b869b759bfdc05bfe69c640334a6ae2ce3589aa3098383e878c16c84e209423f418060927592595f2f42f1e00fbe6cde09370c230defd531e794933464cc1e36b611dd9219bf89abb76b33ddc97789b400e3555c23664659f9bef37869441d06e0c3343ff38a1d0946c033e3acf88c188f057d38931060c876e894393b98c617873f6f834b1c9ee3a3e9f8d8fe6afd7180458d9ea414aae726b97f5d20f1a1d1732d9645689d94a0978d8aa608f46526994a8c759f9bac1cd0dabbce6177379d6b33af6d933485a8ea54f23312bf4aa1a3bd82a7ccccefd03ef2507245510fe138fcc4e21409fb6364e8376964f337496545771b73d0fa6c36aa473316a8b206a22edc8e33457d39ccee612e45b7b186a98b74b9dcce555681aaa7f81aa3a6757172005838109492ec11796cff3342c0353780694fef89f8e79978a89b6b75956d6f37286a91c6d68af7860ad890715fd2f0a413135b1db92f1fc32ddf27a6cd5ece89e612f19e6d6f4890f019f6c6cb485ee79f7139990023e58f6e2f00c2870b36fe7a7857a1bf63ada006098cca6d5f2a51f5b1b186378993e453d21e502a3d509fcde4ec59342ecdbc34f27b04abdffceafe1bed6bc52ad1cac412a8d81ebcc473c59ed84d359752ef621aeafdee8f7bbba0f612d012ce454aa935c7e3ca5039824bed42052867e13e78ca023b9f3850cbc48c4b3d863c9a6bea84a2f898c157d5481b520a776625b35e4aeb4824a223225323be3b893e76367047629fefd6c773a26e32e3885cc355fce2ccace7959bc933049351839a82b121c62e6037583164432f0718c511662c3adacded4ed960c74e77f308',
+ 'b2c633e3181ae5fe7828707ed5b70e0460088a84465eadeecdbcfa0e9ff19bb165d29a0998c7545294892bb6ef297c6e0855d12be3d757b4345e92d0b9814f66cd01dae33b4e72dc504dfaf53459f1017a88a46af52fa2d3cda871fdd3527fe712da5b3aa6b925e3d2fe44024c4e5603db296d0a246e7895c122ff5d946d147d5be586d5841057b1422370a6e01094bd56c093d41a9dc0403854a4b7a5d9f46ed0abc17e7b59ef8cc945e8a998a8917710d67e8d7cc4621d59c7c9d4ad9e09029bb375fb339ed5bd8feb13d31b1d377934f29500f5e945744d02d47d55c53983e1850b1556e6f18cbb9d59eb12776d0ae89d42f42b16538d3c8d2f7845556e37cdaae994893a2b4075e422bb24bf1a73545eed30c65273af4df1408d24568f6884a9794076a16b23e746d609fafc28fda2bdfc7d6fa68d24a8b571869bd1845c310a22e1c523997b364ddd9e3b367eedf742d8a3ce188a327661292a51cc355bfa564b3e1ec89d918d81a0429075048e7e76e96a8ab35022df7ecf40ef528aaf07145e2027995fad126985bca1c2a2275ce0979a4b7cc83c0a93cd911c686b9f81ccc24f8b9dc717ed8df5d6152440ffbc094bfabb7ddac720288ae58118c0729007df93f0dcbe164775595695a4c65fc4776b5380bd6f0d48c56e03c56971715b4b1db4ff5f2af348aa1705c8491c8f9cb4616d42446d54abf3e1b5916466e40b23e4795f2df5f717b399ab571b2bd76d489310aa1bbee570394bc18d0f8713c7149cabb84e0567dd184510e922d97f5fb96b045f494808c02014f06074bd45b8a8ad12b4cb448ec16285fb27670fce99914f100ad6f504c32fa40ab39beec306667f76f9ab98b3ec18c036b8f1b60d4457a9fe53cbab23a0ee64d72d8a03d6d8d67a9f2ff6eb1d85c25d8746c8b4858794e094e12f54ab80e5ba1f774be5c456810755ffb52415b5e8c6b776f5f37b8bcf5c9b5d0ad7e58a9d0fa938e67ad5aaee8c5f11ef2be3a413628ef27f593a779085da6e641c19e79dcc3e1961ac53f9a573860cace8cf79ca99d3626ed0097460c31bbd460b8fbe6d57a6c2c662846e2f229298f443215d96d3506dcb3f2faac57e24f2b78c8e38961aa9da1d84b22e13034b5ed0242077fe78cbbc9d8df540491ebeb4c0875f7d9f7b0e0a6cf92364d97c7806477315f08af00df7eca4a35f740bb1ab68e44bb410e49ff9bdab1f360af7e338621848efc2a4dec5c06b812ecdadc580b78b980672bc224f1781f0cc2ddb529b28fb019d2ffa05ce22cad6d6dc6dc2dbb5648e9a4b6b60b68363dd4fca908ed7a1048821585b4cbb19a9ec7e29b16f636b587799265921d407392de76a7e5f95d51c24a4c5307934aef0094f3de295c1e04d992a88ee2d51aeeb29bb940d8c7dcd291f4effdf55e0e88776719f69fc9d1c2c3e76fb924bd67d6219f4d0a5df369014bf468dd2a868cf577b0bd7ac6c9d28deaca406ad450b7e8445b9a6ae1e6926c64db5f76f3a736b465f456e15ad6c0b4a2bdd32a7e197b83af4339f9012bd982610c2c620d2aac53d6c48eb0b86b0cd57054905e8e823336d6f8a42b383dbed1b5296814c3ab5e425e83cd6f5c11277ef800f09d82156f803ffe5177f396a2d8f5958b05a383bd0e41c49b83d243910f9e658c6df56d90ee6ea6ca75dfb1360696f31dcb495e492645095174a78cea05c7d4bc3664d537dfc784979b3927e8f91da498cc1185a318bcc4b7e84847830028e1188d3cf6d4e2ff3a116eac752c2eee2c748e98b42b543d791725312e0c6d260d19d90a4cf8865a19f046b6037ff6ad1d49894b4472773ba8f7d1fe8ad6db43db48b0394203388cc68cd9d25d750eced97052a5d0f8e03be6f2650cf882a906be2d9966708587fba27f8e7e0d7bc5d803916134c42f2da2856f54e8f19074e33820fb0e431fd32b3020eb357e24ba3d0e154b84a895be2436e7382f0070bd7ddbcb5b8d5402d8901219668539e06a726b8457f1e8cd20256df2752bafb3e11b1ba541800e0ed6cebad186d9cb3f451c9e673d192f25e22a8d19a27b49ca9e5f7a173372db747c3b8ce1d2cfaca1e8a039266176c63082a826b526aa893533bf69c9b7d266d4276b1ab2c0c358b8a381ae4a4b77589d7032cd5d9815c8745fcf7d05352b2abe66d1e6dcd75149d42445705b71b7509d393ee38b7d69821850e4268231e98193c91473b88cf61a94e97021d27a9348e04c310bc72cf26091d5b1f8a9349a15e4bc8733ee683e256b41863537acb79be737cd98894d6cde614cad65f2c3b95221cfbeb9e6ef7604d7eaec1d03ae80d4127a493c5a55254607342ae0e755d3c0fb513f882a994a235b44366bcee67b9c02824849a2bab842041adf0bff7155dcb20f6e0121dc272b75cbe983e1fb243e37fe5f430b04825ce86f2e59c38ccc2fe658eeb7854ea967b8006a07e5430735133ce2daebb93ff124bf9d2cac2eb31518ac163d9d672d7282705c6a5154913b34cc6763dd5f3d99297aa02741dd8736b99798e6029c4fd665aa251dbeb65e9b0d3b7160584d07cf972edbcc0cffd50f6999db632d746d0df20c58b475cecbff1cf88562d5393b1ede4aa47d662c18f979315217a686218388156ba12f2465b1d48217edec7a23e16f6c9ecdf5e8bfaf88ec9175e627f9c1c853e276335dc85dd466ad63f6a66eb1eaf32f30349c557c192a1e2064f04253135631a63ac1407d9d24ba579c34782aa18cda469add66f9aa885c99bb65b7b1f98aeda3adf57a8b8f3ac35b35aaec9653783463a11260a29d65d134520eb668fb607b10f560e2f23b275cc16b8018af4163a239dfe1df0192611092b141cae299857e50a9a9f65c1158eb3ca64a98b02bddb81bbfb23e810a089f3761a561a944584bf553274123b27df32f6e60f9535324fa9bc90a0bbb64bad194dba017379c5788715a6f3fe38ed5090c2c79a6f357f7ce3c745fc31b52f66c2d97d85817f211985757f86f06171ca17578eb1e3abac4c7704a39f12b2549a3742ea4b0c4f6051019b4494b15eba330dddc3beb73fbfa6ac9c31f12658c332c1af8ab9dc908a0742af7d850ad6d075bb7bef28b498c271cbb7775b354c8317a648e338b8eba82391dab2c0c071bc76695a9957a25fbc971c7cfe4306399c2f2e377f316a08c18c36436caf9ed885205fa249b3493bb6ffe7144d1262c51764a3a1e60ab88edd2c791432b96bb33359e47a87a470d5b79174a7ed311198fb9d4bb19e2d6b2604eaa1728d46eea9cf0b410adc92e1d4c7350a5c6406db5b50c2708c31004b773faef88704bef0635f1dbf7bcfe562e1eed3f35b3cfb88b61eba585d27ed1f2a9560c47b1f8a3989ee77a4f5e905bca4355375cdfc77df506e6f4b4a065ab1e60fe94e76661091c28101389fb05ab7c4a39dd9da2de2a9d247c8937b58b1c754b42fde9f62b9c0b622a3c5a2ae4d1aea419a67a956a3aee3b2ad7719a4592a1b8c1bbc1e9aed8d0ed84591266b44afbd16306663ff24b62cb6063e4c73baa2e49f952f1c9615c272accfe5526ea83ce62a48202fb1cc89555bbb313b4c3cf657f68680c1df972589a429094e3fe28a0c85cbdb36f203ee05a9b5980fb747f01b98d4f834fcdd7ffccb3b4543665861d8309fe09f4d31afe4a9dabbef3c4356d98d69dd9e754d977226eee1596b7488f232ae779347be929b6213e1046498f138de2b7726d3513794bf2824b7d79dc90193dee73a6a1360d2dc495376b6ae89e1920410f59d50250a9dd25886323aeaad5b197abb3d96e03020625a6cbbeee67bb1dbee325bb22a7bc8d5c3ed02b5b4a09346763f48b0dc5e35483c0ef9becfaf44975a0696de0e904917ad15d175ef7f434d24ed14c91a0423e14185fa870d2551229c99d43e99f02782602526e263f57116ccee284a64c9f317b3f946b26984e363fc12a03993436afd23468a64d7a82788b6690c998055acd0d89163a5a875ff42c2997f37c3331b6f3da0846315406c8d29874920365156f6fa76dbca959fafa73558fba0f26640c2f75a9c47e366490c6f7066465065fa705fd03688f7abfa7a9e749bdde884e4dd999d5a780ac2c4eece4beb7294389f264fc6fa469540e9a34518e7546d360ca86b90475f52fbe8198f40610ecc734d001480b16e3807820b726a686e4924c20bb45cea6282b9ba76f9794f81bebd0ce6f527e267a8a7cf986d92a59343f50deebe28cbea64a644ed561b3d3333bfe5c039e31699adc9d8337db9272c2551f639cf1d7360be688d67ec51b38cf221df7629dbd46c0f15a4c5ec07749fb5e283d43063692a59a79dca05413af4c58a03f00d38a44895323b3400a31656bc4dbea7292135b2fd0c7d00e71359d372a258172d210e9509c9a56a02b695b7013daf9b017f605e713e34efdaf09991c212e6d1d0bf9bbb3181ea4d3967772c4e585d9602a671987fe6ca81280081967d82b5073f3ad222d50313c7efdf461c6946d08172bef0c7edac489c176a994a6b99cea2c3b93c32bff728bf6a4589ef1bb010459aee66528437b52af157691653003888a2645f54b6032f1cf4c2c90c2c3e26c8c25f5aa30c301912fcee7a60ff5ffba32464c5ee81d232c8d37e8ddd649719f4323954214d3e7c3c81585391355d20d993e1f66ae90a38aa4fa05dc98b64f1b031a3dc340f0ae790c7bc7c12bea2ad143502792eb6544aaab251c138684cb6e308c57b44193c61f618437e4a62d7ad3b54f0d5a4b0576fb042b84292c4f8717700c8b8b9347bf356ba14e0a0e8a4253636d395329ebcafa449fe6740670f2a535fd41cfc286fa498f6592a7e1ab01fed23d23e424a2a126e0d4ab9a8193ae75f6d102e73bdc17ca41437e54bbea4a24865155a3b7a4aa1f7d7c5bf33a221b28374a57687ba1983625cd8986e9d27b72f43c57085f7c46325ee960149c96b92a7babb7ca0e91ec2bb1664cb517fe2658a040a0988893f61e19ce7075357c190b38878c927107940958902065b2c7cb441f10bacabf763a11427dd5ab715cab28f2607e482f8d205dafedbd2f46b9eac3c52f1e1590bd92da4d8a281dfc82f0224dc8e551b69cf1a70bd17b68405f056636fe331f78b490d7bf75e04313b978858f236a5592b1b86410edd3b73c319b99f8f0a22ee405a477fcc386c1735ab4b14af265ba3055c51333955e72a6bc2ddd6fb8e3ea6f2b5e59a9e593ff87d1353415d87ff63c03b843085a9450fc592cbf1fc960d876902eeb3b7e9083cdc76a8c542801900245f261f3cfeda8b9b328ad9d84a16fa6ce86bb15d0f4c4a1a7c538aa63a32e2f9713fe7a47f3007b3492274d7d3e165c50f637ed9f3958cffce5b76d3dcd1710718d8720551cee9d6809cac4359f6313a20de0173be6a695214c873108f7f1516fdf7a7a99f3c9acc7fff686203dec794c3e52272985449ddf5a268a47bc336edc7a76ed78f03835ded53907efa208d9a9f7ec920a8da94661af23cd8c7253c551fedafd649aa0b5173a10bdd6644ed165db4dec31784b3b62bc0d9f49c2d8f16add352eff6b9996fb8f0c3c76ed24ecea48c49a40cda0c95cf122640216497f81860406abeb8978065489f863a53188c1d10f20bb06508cbaef20b037f51bcc3099c5f8fc8306c4c21b18761f34fb2167047c23f2bac0f1f71677087dd7d673e279098a53da9809b9534639fc14c863444399f8aa4378a5ac0793d12646efe321b43d4f644a93b3568d81b89cbd4fbb041ef7232438fdf4442315aed3baeeb678cbdf9c806ba05e243a698673a6e795110702480323b78a5a096b0608cc594a52307f064ab634669cea4c08135a368de59c49bbc96c3bb4582b125b27c3963b48828a2125a2d6693b0dc7c1ee5f93120c3f4c12e9ab012ec8e88a22d3594be5b6228f61a3b9ecd289256b58772fa3adff7077d1e6389e4616f261017df5d0f635910d3c37740e3f01b195105032eece29d05b6d31cd6996dcb9055b9a11dbd9516e72356369b11b2f42d3adec01caff35cf75696eb2099d84bd05b5ba45b30b741b5cd1b9f35bd38c49a565ad24cecd8dce444aff8ed4b6a96ec0845109fd0918283b95adb98514834688fc36146095ac6d468666cc819ea55ef46be0c7205ed7f58cf5b114c33abeca0cb5f94937041c2cfaa026f366a222fdefc0fc05a3791e33cf7656ad7cd29934af47894ba875577def2cd28c1a7d8cdc3128155e1ce46543719c20ec38589d16cf1548943b85e8e08280dc0f036d5d6a56f5af38f32d47a521db82498c5955010aa3b9ab76a23ae56340b5f4b80e1f38dfde2c1ebcb03ff94eb90d5dcf413b53d0777ef9c046d80cefe0f2b5bedc3cc82a363e87d029b88066b92481979ccabfcf04fb17df004ac7b6f614fa7e645088c492390cd3d63c0ae8605a6d6be88d8544d0a08df95b0d9626d48f1b8d12d4fb3d76bdf64e5244c962233169c2a0ee44d06f11b4c5b39af8de10e3a1417444b00368300c963a6d7c62862c1f2252878ef034c134baa66d803b4f951452152a27a4cb319841db1074481dbce60179c3c432d631765ea00f9cccc525ed3561ffc2a0243531e7d0d841a13e6666e6833d7506a7d502083cfbdf113608b441d720216417ad51eea81f750c8cab1a581b5f21ea3e3e607de9bc979706dfe22e0155b48efa2c80cccd708ec87d1d68cc8ea34b6c1de009f612cc86ccabfdff406ceb0c8f50153718ad55ef83741feaca69a461faab4c672754a60d20b9f5743f766a99c3cdf9fd38e44a27bc263508b848ccf6148ad610783bc39b41a558e961aa5edceab86d3bd3feb7b7e0f7ff5b3c88978858113f9e6f14bfc19df29ab75d9522566293d18b40594920806dd14acdc59e9923f2cb59828bf510a4263d6689b37f86aa04b964248058e218fc4d6fc26c4c9906942bea491e0df8e2b4a39f8c91a0fe4f7f974ee700719d1927e632d1092c088e79b3c9293754fad8227f9acc9c411db168fdb40e562d821f751e2c7008a7881a17f56eb9548486f2f42410d04fda758e555f2c110de7518a6867b50607596837e383435011f73dd1ae337a2e28c79624b92e2f8574398bf88645852971bc596690dfffb3ec378fe2c5203f3cc3b2e013390d26e2358e81c83359e540d44abc34745dfc2b4fcfedb4bd6ce8828d06f3d8295eba9dde60e3a803f78df2ab8f0110129fb14cb91ad7a60b9c0ea5f14e31f21ea5433ebb5b11e68cc0c7a563e3d897f017c78eb4c2fe5544100a0dacf33aef8d73694b78d7ad2212282adf9a03a31a91589777cf329240db7b73200c906f3efa3b952a736115d958903007ba48e1367ac4b98e64f463d75630c2938c905f4ef9804f725771dc7aa4bb7fc4413e137a20fea6391282b3a738c280bec99d8bbdefc400b981a47184aafc528bcb4dda340878d60e6468322ee7b326383ff2650a618941468e536595cfc550c4c5128612b5a5c184b70f4fed07953b665c497b92d34299cfca19292c87b91315ab06a7949d08012297026d500ccb38ef9b0d4005d98272abe1605c976749f1e509a4cf1843321d6e90cc3af66bc7972a98a852d1c8bb547150b35084e2ea75b94d775d3c3c966edf10d7095ea93ceebdd1c52465456fb796949ca5637f3d271902f8f27eedc78deab3ad78d7497d980fb2ce155ec42224b23996dbc1c0947e7aa6a3ff3ecb27f317d5da0a2ec12c3b96c83dd61cc955242a9c1c640e2b92f454c4f2f41a793a26fd13c73d93a4ab31e98e9ec73dc97b2e864897ced724bb214dda8071806c9091f0ea1f63c4688d238e725b69204926bc4bcbb38c8b407f7dbc53b6e81f19bddc99c52d4d2f813478ec201e4c62ccca45e1a1da1db903527226bd10d82505046f5e317b3a339353ba88f431e173c8e863fe479602def1c697239318c260b316b2c4bba3cb8ef34d60fb7b40b8e1c2039ee84951cc6b705e651962592720b8675f53c01161804593f4aa31c5432b4cae4f360397eafd238c64aea73c77036978bd91eb6e9cb5eec9fe1ba43a10cecd4b0d7e22f2def26fd30e29ee4d52775abd65f599f5fdae7351d5d63f09922ad85c421703ed28e9d9c4ca31840619fb10b7e0f55851e4c857be24508ede47edd274959742d15951e5c4314c14f16e1d000aa717a2fce292208162110002b286600660855c59d0b90873dbd01d899f4bcd0820e3167187aa522df7ae3f216262e5944b57bce13f8dd63612741a595e05bc32f6de0f3a446f61268d6e98a4c821f790ae84e101e64ec39d8d9e77cb0ae9723d916f19c1995fd20d7c08a92764420aae12936526758a550ca5b5d2692655636a792ca2a1f6fa29355bd2ed03b721832f1019b5e96dbf0f25e36096be40d8246c268d56560ddcdb509573d0e4416f15a61f7e5fa52f6b565bcf155124eaf02995220781581e666cc1c151f123837926a5a947cef43b0dcf20d14a8e57751be777e431d012d935521b57f3ca4c0dee3aa035ec804',
+ 'a053265e4f9b8cb00d88917e4a194567bc7c32a0542fed397065eaa252ab946dd1cb9d554ef09380ea0cb501f67704a1acd99ddf1c49453568b6469d34867a54597ea5ded9e2074a18dd32b749221a1726d46b33e4a41ff066394fd0b1d449bba034e400d8b71097ffc3b1a92964ae51933644a59486a1f0d0b4ae42afdb2c2b0eb402c334b8d5ed0785dadd7f83e8d85cc7d23b1438f3bf10f00afb17e5492b0bc8a82fd32d7fd798ed545d34c8f133e74fbaff4c023ee58cb50c04d238c843ff367e4d9e5e35da1ac1c8312e7bf1ea9e96a7f9252baae8aeca5c64c71cd2fb52c72b247d922080f5cadd5f57e40f86e8633f3085fd5e52ddf9a123dddb8fdc6c4358bc5913685091d03cf1964b748e2c802aac56be83aa8008834afebc266db572ac1e182734d2579b8ceed2f7488ad4b311757eb7407901c0ace01106bf36961cd051a417ac8fadf2765ff531b20347c59c94e730be46c7e9c1edfd0284c075086b5de32bfa0efc9ea24be641da80ed7e7210777024993a4b6d7aacf89b92bbff264bbfaa43d7ad68b7ba8d6fe9892ea53cf118c19bfac2ad568d052f2c35d1c9a9221fdeb27326155eb5d20307a67204a13fb1694bf2c7d92431f0f1602f1d2e9beaea0d6c69d1703e429d445b6021614a0329b15b08b195806b55845e0a09f6a4ac0a809c411540006cd67b0e3ea385de456ae1f4c5e8aa12451c314f4dfce86d6f667f6884594c4b3865f047c96038060b5b413db0d4e081c62e405b815ecd9e3be651f8b9075dc8b032eb2f87c1416a5fe4195f51defe75f671f9a92d966ddf1872407568863b1edb26b4ee022c6ab148edb081306cce98decea462d90e90d60ff292071a3eaef6c12792abc20a7984cf5e4fccd6e8168f852d88ad0e2dfe2e274e90d555977ef86b1ecf8f4dc4378afa1f3e68cab89f05f477eeb3525b7e8d696e8208a4f972cbe0f4b1c12dc06c6cd319c57c944631a031921e9c3000da9cdb3bb0c78ccc5418288f8169ea68e0d162322c30bdf894084668608f2d84d802879b613b9778ea864cd986b10a235a62ae53baadcc388fe63ae0fcb4d35041677577df8c4c65fdbe53b90abf1758a4c7bf65b81b496debe216e13934a9cadc75acb870e133fb5467451653bb997184a79d4e6ea2bcfe70a3e1556137375b73d234445d62d5a3b92a2bdaaace16d5c3aa51f82468ae55e6d2a323bae4066bcc261505ee39b9c3f2af0cec572018ec2979e2492298a7d9151665338d649d63b11a57e26a8b68c5c89df034a2d8261e7dbc582baf582df2c5182e6d21df84a9e85503c21b83680f039ddf9ad31ec9d3891abb8515d0ca08bd8006b9c07c44a73980218f4746430b6b56e206311c8773133c143e9a2a0583c6f5f5ffb06364c46e43b73037ff801fc771006005f7eb66e4ca6a40878b66cca81f04273abd6c674d45cf8d33d4a8b5e195429097da7a14c46bc672241d76492ba73a19a6b2e5faa02f708e82ed42347f6bae7e2febbee67ac72fbcb808bc631635a0bd3c6956e42da8a31b6e73d6046a9a4f1315523e42d087ad068d74c18233703cfb440b478dbd596f1c3d8eed8d6acb2a35903882918c534838e9880b0f480118bc05bf405d17eadc7938650e3647a649cdeac5133c77d2093b156c24701e8f3ce6c8aecabc0502f21aa721c169c8d2b3c4692078a959adf7f55949940f7dc3ae63d5ff6c124d49bae7d2e98e8eb6f8700056723942fa8c1b4ffc47e1532d45781974dbafc7b8693bc900e6fe0bd9bf896bf9e2df6e21157b31488d95a3f5bca2f2ed6a1a430e077da67b409849f9d005ec9999f19498f32184834437699ed132d587d335ee17ce8968891ee4b0ded2ec6a7fbef71e25cc152ef693500cf12b32a98b3f88319a6366f573a86ba94f6e46cff47a797391495d19e9a367f4b373a95841d59c7261711c378108b49a164c1c023ef500fba311032ba39b0489af50a0c9f1bef81a329eb414fa6348ecf91375eba556131e25cd0f4e6ea4b032ac6b1ee4e3212492d692357628e4ad08c16c5a7273c63b44dd24876990e6f15a716b2b915f27a94115f6a74451b06d5436fed27fee6cffd595bf756452885266c7f9cfb8acf80e8eb6721366bef2103a1f72e5fa6071e7391345e027fcb358bf76bd134113bbdbe383ad80c3b7d01c21456f9bcb0ffc7bd8205fd199d68e8dcd465efe14dc9999a74250f64583721b71719efdda4436441b83b54fa4284278f8ff85991a4fb9cf41bc027dd36c01949169a6120f4646c96806ee622f39f6a1a968d9eb39b344051bb96c1e55b8a510ebf531e3535f5592286fb2122352d391464a03fc5a5261c39c0136e0b4b08516e6dfe4768cd58b28c487ab6866bb01cf396daad4667239922c5b07ccd69cca2eeaee69d5a4f10f12a2167f6be1bca7e3e899777c435cf3f9339087e8a4c49be05fe9680881fb0ad82ce4d6247c9ded56bd9611fbdb58b88119695f3e066f21b5ae9988150d3ba303c9b8599920a5fcaf1e2b225914adb0b047ab37d6e5e784343dc672062ef8031ae05bafd4b31dfc7675224f9ed8a502112321b6b62772ebe67537cee3ce7c51a5db9681dd1e4d1534294142d30e86d729169285e8b6597e51beb643b2d40db62d212f77ba4a7d6d6fa251a3009e99568289c54c7478e467ff8109797d28f45a391aee38751ef52fb24819192b6f26a8a3e283b260575acb7824f9194bb27964512e9d6d1681fc818283634d5e6b75f4148b945fb63a05d542114ecb255ae3fdfdc502bda35ddc8b69bbf5e7a079cd635eeac0fc7e082baae7aed03612087c4cc647e7d12699db76ee58988efa81781d0869cf31beab1575ef6547031aa48a7a331c1250a61d426a9ac214ea726cbc499a6e88a5800a0b5ce09d7ad9451656aecdac4be32cd1598ef36131a41320e20f9c63b401cb0d548744bd32619c4628111a605c32bf9d670b839eb764e286319897af1beca89c3a1fa22f3743261c48cba49e0ce46769b609d2df6dd1e986f30c13ba850f1d9f034c835a5126eb81fd03f3cf22a22c1d8caf668d1c942f096e9396ecba1135fef8356ea648b2f45b90e18d5c671317a13225c9118c55bcf5ec53aaad819cf5a16103eb7be3904894498efcaba3e02fddf09483d185bb9934b377d665a455677186dd8241eb68ef127032c8269db07db90d241a37cf6ecdac0b25a37ce9e69254aeecbc6029e1f2bf4df477341f2c071ed3fc18aa311760473e85975a19f77e332358204062a4588128fd933ceffcd7f288450591755e5beb93c13c67fc2f34f72b48374a615929424875a4a8d6c7f51f7526675661eac58251fe1a0c5937bb860fe487e4eda76ee9f68330df9c35678be2c8c860be64a6f3c167b7aff9b61bca17569a77cd362e5e7a4fc14909ef37201652af17537306262b219dbaa5555daa8ac3f86ccaaf71891c02de4ee59d6dc45beda5e3eac0ee03d757549323bc880db2562729acba3f5224c41056f7e70f61f7c1312c49f265720c2f62a43b92a4ddd534b1183bfea1a1d9bcddc9087327de33f5db5a39b2745b13d15153b780ba013c881ea03e249e5e8413d00fe0ecbc2357b22bd582a822a63466a90a5e2dd0612f78f4287fd33f716df06e9047f8d718ab1faa06ec7b773bb716f030f742f1e5f52cbd1ac4b48bc2dc7c41b5053f7fa57765df533fd47b02e408b02c4b662275d8cd00ddd66f8a3919dfd0e4e16abcb202ab5225425a37e40365706822426df91e8346d97dab44bf6b40a386a5219627951a8ce5beb6b2c75b54b94b437dd959107516768010c23a1cc74304170b16ec78da14f97c8ff49535fe123d78c06e7df6ccf81ca9470b94729e37c400d143e9f31272cb418e0634d98b03a687b3354d18af7433bef827b3b6ea0730271b26074eea2fc1d85bef8e8f2214ef39fd35b2bd47132388cb1f812fd63caf5690c52cd08bd245089db7899febce7e564777922fbc5c54cf66fae427875da853d82a41c21de3be98fc670f500fb8bca63801d7b435d82f5b74c0c6e428f285e79c5d2f6cc7eb9451220607ecb65b11f079dd6795da0d1af39b790eafdf83adda8464dc16b2712efbb49c58b9073de6feb1132c1ad4857e61832ea4d0d49988f43749320806fdf65e5d32ec002be8a19689a90c8a4bc8c46bd5e7708f31bb7efd5e141889ea175341c3ceaa084ae4ac81a9a9f12f665c52da39aa59341b72f7bca0cb75e38648ad6d8e7b7a1b8ab76d87b81ac24f4ecb927556814a06bc455bfaa678335c03176dab673e447f16eb4e6f5f0567332b9dea06d573284e3cbc127f1722393d890df9c1f621f07d1408b5034571c7232458c4535d88dc55c35f84139cd26fce3fb0ba77e22b675dad940c091366cc666186872697c94e6078b71a7edd8fbd564e8b1897d3648f670819ba4a70ed5d460bae9b452b3d66ba834b3a8781610b1fde237ce773d551e34e1e278e050c62094a596878c0ed0daa0da34df83150416f16024c3167859618a62cbb9d79cdc4f8450e5689fedf29773e48bc979465478f1eaff23b5a7c444f39cade3e7538695b2555cd3e8f1da36a3dc1ee2d53a705c71fb2d4cef2c344d02e80f83ba1543a7a11b6356118af64cb33964a81151f645e41945cb1d7617fe3aed6715aa4291f9c32baf6b8449b53e247503a5d83d34a9eabdcf0aeb25edcd0e155b16427bf010bb0e8780cb8eb57f6a276248744cda0cd8612fa8b2bc342deee842020d11fc60db2c923241fa75a506b9072c801154f34f9bee111e9979b56feeb27584b5058eb60e360ca1fd54339cb278be862b13e1b2d690d0895e2ce8038132cd23c5a9dede3c195c4607ffaf7eac2d7af0eab54c9499ae638d6adc3a4c58399574d467f5a63e82cdb8bac66d2efe9bee5ae0b7b8876dad46855ff620accc3d4af261be57c07e28bd7486571140224a851e9d79a9edcb03f62bae093fa765c47a26ee1b699f8351c803eb102034dfcfdbc68199854a2a48fecac583ebbebd558f8cda0dcfa5e6c459e169802e9f5f825eed3d85ff2f13e2ccfd3704b9652b67178ed613ee7600c70f87822570c25a189afd11dd6c0f0077ed3d82fa2d3d388f9ec732bc4a7269f04570a58eab5037d8fc70aefe506c886eebf639e2bba98d2581a0a075684d9ad69837741a32bb716a0fd2756740ba043e25d1bc3cf92c61cf5ca58fcdcc4a1bc064465e56ab86af4251ad05a6b18a1c7c373a9a874a588ef3ac605123eb0a55645627d4d28a2449d84e7ac04b275f299facab455033904d18ed5ba5164900e028da46680c3326c9b2729645b326abd42ed2ae5d06597624d59be1fc237ac034947d3c88625b00b7674ab9f67d13f2748065ae4238007cbe8044adb6c9d4badb1d9b74d68346448b4d5340631783b5a35ac2458563ed0672cf54197587fb734c4ac189b2dda954cdfb18b41c010a77e90464eea6f863c5da0956bfa8cc636bf0a28be5addfe8d3e7e6f79f71d7fcbbae23ea141783f91d6cc4c8fad125811760ab57133818892471a79c6d04eafef37b2fbe506785318f939837757f21f90824cbcf8cd205fed8f3a363a765d865b1d88e5e2e078a919ef6ea0e9a13202fa0b58a31cd2c26de63d660e9c8e51ee5693ec645f787f29dcdff30dae32dce89938b7d4a5e76f99c47a2769b6c333ce2ac167e0267595bba8f251308eb4f7bbb332f0b55bb630cbd16d03af4eba0a0d1dd080c1fd80c247c74f0f7350cc8c6291218bb005e70ceb533f84482d1ede9578e8c06fad410fb57f20b53dbe24a2c57b2c102c6d220ce29317329d1b95b84d8330ae53fe5f83ed198accf59e6441ccb87b0891590e3796d91e9414e0c79f1d85d1d2d3b78327d8dcb7db05b934715f9237fb46925395f06d7b3216435e9bedc8f3b458a254015c12cf6ad4d73a3b664f886fb5e09a2ed89657940c0dffb4a592bcdd4b857b1c6201f901cac021a6c93895ee450a8b0b379dda435c0654f32e2c57d412299f7dd3f35e294f3b8fbb709587ff5edacd33f3ca2bc670f6055f6edeea211756692e952e2669cb112d8143ae852b681609aef66573a5aea5ba004eef9e4dc0d3803692fb784aa60aa2002bcab8f8cb87e68526b6d96980db1adfb6a99adf776a8e9db0a17c5348ee96400e3348f0f0f50dbf6d0586994d5fcd038f52075fa3e1386bd96a5c0c1a85b34ad62f5c9b3d282564b299a2bd7cfa7c75bf330c55ab0128a9f49c3dfd82979e2569071c801be7d77dddbb545d7774cf3b3094d24af992065fe9804aafe9eb02d9b103b127f3fbcb10d5b3c402a5956d5fc8bd80c6f45c7993a05ea8a9b84a856f946a43182b2da82884c91b336e24ffe871f53f4f04262a4f007e8273557ccfeefc86f9dc2d4316cad14581793aeb2cdf1285376f91bac19af327fe962a49895ed09dfcfa5f05bf00a46e1d5b71b09d4b93b58fad03dee57d61a244999d795bc7f8f874fa2b3d48d1e58d18617b3cf934dbcd7091c35b3efe30387d0418d3ec325bdf8865f8b15c467cea9913c157e9a3d41501a437f97528492ed1600220e5a6e39960c12d2aa16552cdc9faa9c8159e65d56b6dc87f320094cf6733c328ed2c777025993470bf384d7ed09d9c5924307654d575deb71c90f9626808c3b3adcbffaaff72744b5fc75644d58403d0bf5ef0db6842e267dcdf612efb646989cd8b649015d6558329185669003844f68d32b9b24b5e3a581af5b27c49d11f71f4748c6a904c3fdfc43633337a40c99337b3ba21a75150527ddbd6947ad64d35ca8f6080975d9a29d926d7eb6b24f86f64e9db70a4a18b1dad98b3beb5fc599bf9e3c819538fce270ff128d8fb6d3b51bddd05e669d852805334334222c9ac6c2678ad7fec6d43a20fa0104450a2f903e9961ecc1f7d0f4417b83d67eac7ae6ffb57f5f8897c6261803b7675954b994b911c0f6aed192931a02bb877357ec76e20878acd5246912e0bc841b4f0f185f25d78c108fc33080f97958eb82a75602f3d105246705239cf58515b49891b24c0f4b11bee745314a6a3fea673f234df7cf9fb37aa0b28c30d0eddab7c8951de7cffcf04647c02b5be6d6fe8efb443ae1bfc4f20434a5195b0d5751995181b1bf025db66b13cc6531e9cef3c76992b33d4ef14f4575654198057208ddb5d0243e20fdc281edd2a568fd9878a4ec973d8717a539d2f57b9b64539e6ef97818dbe1b65da32f387b326d9cc3ebac06ba304b8441302137409d3c626cf2712ab45f563cede6c7861eb2bc41c1cb414a8431e73f65c0b4ad5cbdaccc4e41e9104b04e81f2eebef49459cd1872296092a7dc90683736a01ea387321fbb2cba5fb458323f7a1510b14785759c756ea78291f5c160b0aaa9d506c54387b4afe4c3e2c50a630e584af5a3f88919cef26f2b8d17192209bf0eb8250e75cc57768504b77d3cb655c15409d039735724bda2d6c4c90078e97eb70f29e930b54b88836b5a0a3200f8571635f5d0991e20d826cb5d93de28f8ed8d2239dfdc0b39eec840f3f452099e2278c03b4665366a4ae55281fd1bfbdf1776387e77e196c8ae3acbacc10698f02b63f4c9223d66a91bc5b358901fb946015b9b2039a71ea1d2c353abfbb5779679bd17c8ec8b778554b03509d3532ad4b5259146ad976b6b614a221829c66c371470725f2db632a8fc8d21bd4f1d15323aba63185c1744cf64b67e1cd4a3407b05624db76b265cb71b44bf9b8ae64408980459bfcd907375e2f1b0df83b3bb0c537df0f73143c05dbabc57cc0e177dfdd7eaba63886bbe04e3e2ff88be5ea48f06a7a24804752540dfdf3177631689b90053d02b160eee257071b53f0e0cc5af27caacd26e245208959495c50f4d30e51b29e140fee3cb7f8f86192ef925e412bdcd56bcf6b8105ee1c3cade9c2239292e5336cac8501cbb4b09727a0911ca60d098e82f259927eacd5419e992ff6e2c43b917f0fdbfab80a9e2094ec6393f426913aa52812f5f536030ca0e774b95976143cbda24973e77b3fa26d0b7030a5f8e26e44e94e38041a23d2577d56e771191d99cb1c5409e477037102864d8f5614ab9dd4ae61b515e9c64714933d0dc43da63d09d68229399e882117f5521c6eda2b4de6ffd409b4f558ef8af1ad98cd444c9b0c596a5e41524c46ffd134446aea0ff2fe463bb26bfd5a100cf11f14e6dd7f38452d6315b622be373c925ab3d494b8218d2a0f2ba542587d2d1a080ebb69cc9946bb12ebe2905c9650c13bc7369ab26e1613a4f1fb64c26b6de4fee2c569a342739ae04d611bb5c8ded66e1afb4c9d1d1b8bb391c8e5268f31ea7a3d6f6d91a87e4551b6bc354863295a581bbd2ac6c3100ab187b844033d3b82f07b43d6265fa932edb45e6d82d9e2b58035d6c5ce049609053023f1d719cd46f828dd43cfbc96ec2ad2b23503b17d807d15e2ae2136101f4547a68f109979cf07d28626fc63c9e98f7e622c397e6c43b5285b345efc10b5e82aaa6c5e30c36395e4e9004ac9a4b38223f8a392953167bee9cb08d5bb1a557455ca2a0e743cad38fc70602972d9c2b97fb04bad9a5f7500d69c8c33e78f7660556048afd838144765b6b716e3bbaf8bde5374f8f268e4117b6a4ee825f2a',
+ 'af08305484d04608c43ac58d68ed0bdb5db6044184794af8fd6e90fea5894021dd3a635a8e57c25d6a574a6e74c0b576c2e2675681c6967b3b62aeb3550ef43fdc3c69298163f1d8e13cb4e10a31c5203b130208ec0b3b370ed3964d942531ff32740a6765db1c9eb353cb8d3428820051ae9bfccc307f5301290e756bb64189694c0dbd42a68bda6702571bf98d363f8b1cfbdd291cabf899ccdfd3e0aa6a06092a3cd221ae86b286b31f326248270472c5ea510cb9064d6024d10efee7f59e98785d4f09da554e97cdec7b75429d788c112f007ceeda7bdd9aabcfca562a78a09d39db03123fd722b8869e3c61e2c36469949481a36da9989437bd4edf50bda801981f163e8d75b0dbb542bf8e3d0c7f33dfb223c009001a7b3b81916bb094390c42c24a47884fc8a0410f05b2f57b67d8d9046b2ef4a8eab880c29be09326da26fe6da713758ef26ef1af16b3533aa3c14a3260d376c890b1ce2975283f9b13b795c8368b9f59b6ae8ec7fa7b9f6ebb55fae40a98d98895d0ec5e2629fd1a6c27d07afe974dd99dc6e002b9f0214237fbb0c172656311807ca408b6cd14cb6ede752c0720c6362e1faf055cfc20dca01d36719f235e8bd91cbccf2efaced7a0454c855a0c5397f221c37beb86e6647e22529d99b8101e291afd5d959a71668ac21f2efe453c246f34e40a6c75b9035772cded690ec2f0f6dd2f57f394469fb5beb4cefbb1c9072fc1d95ae9b3e2e0b5756e08160cbb2ccbcd1a6850d095aba8a2d40e5a3a4265ba2e6b146927a8251f93ff97a89945f82d528536b536a6b2ebd4479622c7ee6996e562e0f5b955f71e344641289fa67ca6d43a6929aec0db07bed50d5b3f16384d4c86659451308039c00daf9d0527e2bdbbffaab5202c4e83c6461c0e8c02fd67cd9e4c4ed780e8f89f1a880fb3104ffb6f9da160743334e756616a4295fade6acf743c2b338e57e33935758790a9ede658dcf532921bb5bcd5efa0ff424603e0bacede344ea2f483f6281e0b8639e408df834e33622cf889abb8654d7b2d9550f575da700e03e75f3fba2aa67c0a5ce96a5c566ccdb026d63f84623528f8bc43ea31d7e85cfb59ff7ab2425d5b627c0f632db2e4b9ed662cfb1b3ebe31f09f4000c97da221d072ec11d90d3a098a6c0430bf0da3102ce1114645a2f17e5a67cf9f0776a843cb59ad6af734446fca55503bda0db2b8b5be1088c936d4f8813b782fb16702ac3437caefd45e8375695f79ea455a189bd026ae2a70a17285ac44c41890fbb6425333cc0637340004b1b109a7ca9ddd9fc5417592028cd00e22aa3ac36cd8ceef6f763a19e95dc202e87488d92f7e0aaedb36ec29479ef87c2c9463960da65199d2279c8fb382d15957cf7ce73da4a6af5c2e9b570680f1b5122ac5fa3a0e482ac26f7bd05b4b36ef46eb6f3bbcb8b9898e500b8509d9c3a31c96ea58bdb7ba8988765d44a95ff8aefdefe83c74614c26bac5c3191652772d92fd5a165c4baf9e6c63cd5367671f7cc30470522d48656d27b44b7df693133abb8a9b9ee06f3051b55e50655b0ab443e2528ee5ae150f461f462c177143a2b4e062375610cd438aad9170e24ef1beeaa000ca5dc06f4f99e8a3ed514e0a298249b0b6a8b60df3319a22b43209c445594637ac232bfe2f4ecbf216925792b28c3e9a9efb98d7452ec539a4bd512a52781b1ebd9db76bb64e105c3041528044ba074879b680d1436976ffc9ebf1cbeb2f69de84342b5eec7fff08c2c8087fe8f4ebe3ce4334a6f4cbf59baa25af501b66769e8950517edacae01e548e4135a522339326097cfc603a8936d0d15561e7058c87555aef748717fac86efbc44a832c287f0870227c909f7bf8c159a9a559f4f1c16fb8cee7fc4f962c99775b678afa0fd0fce0eccd8be31308072374c5781bed735fd4b6803f58fc725c6acc34c37433ad8ac1a49759ec998f2a997d684c62cac5ae156fe75a1c74c3403ce0583583db3f3b7b108403a455b4b0218e37deb2cee0e3e2c0c354824647cc555f7ce80d4eae9676f93a90f28fa023256dda35f143ec86a572bd3671b925881e1147e5fbd521241b266108bd8e7a0a0ce3f859096f1020c54d7c07d31686fcfdbe623336b8c06bd061a274ef9b6bee3eb83953e09b7538aa19a9cf8fa591b15b2d74daa53fb4f5fe70a5db6ffe9b5623b4472a600e4356bc9eeb978dae6f2ea12726e32fb2254a0e1c114525ff31ca239fc7e67012a10263a4eb66b57947fb35742055be583af0662d8ebaf9e656d2a6406490f7edb7c507f25044ef4e42a181dc0938397a1c71706bc643d3fa31f71460c42fdfe8ae261051c339237488a7eb2789958760f355939e5ee79b84ebc0d5c78fcb0f4baeaee689ec4e39344095dd1c4a739252e622368d01af39ccabb6513d6e6d6f5edee3f162728a19d692f4be84f1da41981d62c30155a1951a9a5ff08a081be769674f99a4fef6aba2a74af62729d27c79c19ca1c202c898b6e0461b7507df5fb3717a47163798d8dfa722edcd98642b3efa593898b12928e7a4f038c810c1bf8523eb6181c67a86d7ba010a3ee6973730ef20f04b0ace2ef70ee7b149cec8ef27a52a51ad52a49ed00671b741bc748694c97931a2b4be932c47b2ecc1e6fcd7e120bf7d62841c0913b6f95ca0c20101b5afec636658013dcf77d953f7036560fbbc334f6804ecade8f0225f219f4890daca02ee5f9da01627c5e44cb5cb0c70bca00c2d86791c7496dc72298dfb511c4a42423a552ca2057e5a5c41c1e6f8f06df5d581868bb24567a47322ab80228b4e3e35af10ac0ff11a5ec999e4fd31c956e213b22ee3b807a16dadb245d4c5c72ee661b657c6efc444f8b1bce6b8c0e1bf905028472935a48d62a742219f42b6326350b5f4224b6544509e128fbeac22f026134b9805320373a8e938098a9f42a2dd8a16ad672abc628f1703a7b8fd7330cde583eb1db60c9b6afbfec23ce652c57b953f4b3d95b1e6dda5f7f54dbcbbc9ad4d38061cc9a74cce66fa175e1fcde466fb9a964e932c1761ce564226f0e401edd3d2b2a873bcd0fcb196cf98509d47c6448d553f2c153f441d8856eaa1d521b6ffb9b769bd336d1d6439b19183a936c2f68252bb6854a4ad17f5f942d776702f5a55dd09aef46c59074a87f2bfd9f9829be0536850d18ec54605f3d69baf38816b0fec2d4fe815d7233317620d15d72fba0f21eeeb7547b431210b4df468bfd3ba4dab7fa6f5afc03f5b2a8a74451af1dc7784f6422616a160af0b50eb894f4ed078e3a7be04843be42a8712fc5108ef888043285bcd42b45c19b98687fa2e1934f95c4d9cfeda719908f8a1ad21ea524692282b40531417acc5dd98d0a3a45e6e36de184ac9fa8ae65d43df909e07419e15f9a8f99ed4efd81d412dce6dfb42079931b0cf4f2cad53913176acaea9e519717f468c98ca676987acee8a3e79ef86891cbe3376b702690c8a0b093a16663e0e82ae03283da4e66de5820b00688d736e69ea7e28e5b2af371649b02b97eb9654ca87653577d1d736b59359990e4357afdedbb94cfce5ae789cd5a867749ca8ac1e7abba9be14c44ae4c67df3b43ab9b6443aecca45c2ec38e65af9d8f5a2fc7c472f0c6b5b9536113ac57ef4a19a21ea62ae1d8a0872f3e0ba7eac6562251a2d0e4d6063967db5f37f3864861f8a17b302e056738d1d4bd6e1e9c8f5024c8b28397dc079b6f4c7587ac6389074322479ea779e6f1be97a84234455721e40d6d4906632950521f6a8f41801f9e9bbcfa4e31cfe66fb010a5047375cc3fe298398ddb1c19fc92dc94bb5d6c7abc64da19cb5777c3e768b83a41769e3d0ea2e1adee605727f4b40c8a985a7928d530f21ca282f23e0d2b06be75983f5812a77240d740b4f1dd3807c2a2c336b87d82ad79404edf83457974c81d62c97be052d298c4f6a5f61a8186c37712359819cc64b63105805224f69adda560dd36fc578afc53fcbaa34142e21361d1f6563e80906a015195193004d175aa1ceb07cb07f401f0cd6397c5deb2191f8ce9615041e8fb1dbd7e46db36c11697e1637cd0f6b63027d321323cc76f6aecad9f480382b6e002a3bc79ab1ed236fb68d6e4a2963a1d65c88400fa9f827f75be7878acc592cf3caed01003ff9d5d411dc5fdc489080046f7fc92a3c983dd273a1581c07cf50f482949a89a8b8b00057ab1269b21a8af27fc0b55acc7fbfa9d9af6e1f32b6626a1cd89b1c32513b5b50a18ddab028470953f20c89a3d435e356b8d1799535eabd5e630ba027edfe4ec467da188ae23eb1b5bed79e07c028e8b2648a147875715411daf2ffcdb3823f7aa5010a8871f7536bcc1d81416b1f20b55da0d6239d7e99fde858206dcc4e973b020897f2fdfa553ec3e61a99fca2f326481ba9ddd69af3e93346eb4e5febbfcf26f9a90fadd021f64c3a51569b39c9cfd00474f0484cca9e63c348ba95df1dfebadb2728aa001d5b0e220ec2726d0a769b621fa21a1c87e521d81ff796b41b9066b7f8851c12b334b2a53923a6c51ee4513d913c75929084158c584e89f1204cd194066e2a8e4a4bdfccbac262ca6de19b9db40580374e43e6e7db07961f93fba47a382bfcfd49663e9e79e7cc026035218b6f47649229ec3cf1906ae7c5de6586727603bbe71a4e76235eda0cf75da78a0eaa48482c8a45c1b360bfa16b68522082a8408fd224cb306352e24b31cd007e2f4d52558bd7181b34314def9a998a1ab5b6289f48cfa514673b48b1371908a7f541abb2397a2bf27ced5df7d8d9d41b10890414d083a3c93603556590a787aa686c9b89bed946ef947a37e1b090facb9d071b15f3a88ef8d8904e90c7e1453290b76cffa05e33b983c223d6a726c2d36319b7cb37a62e4856cc7e59612e09e5760a643e696fb9f951ab69fe4703f5db6aa81e5e27e64b62b79d2ce9e8f59d6b214ecd6ea0769f57071f508017905b8abb9a99f548ceaded03ed9f67e4a0c76d9969edb6cedaae7aac0521849f33bb895dd970b824c71b9242a320fcdb965093e7450d3f7a03ae0b484425346855de7f584bebd0423a5ff97da7f7d0573f008412c974312c5967b1e4c6a1b6f95f8d15b5da52652a8d3fc9bce16c0adf039ee922dc6cb36d44a0158672e032dab78ef37b6dec9e652a84ab0539c7d3fc4af46920683bedc32c23df393029d194c7a9c0b2eb02a47a77f64662974f52347cc8ad13542a08b979b6b602e8c3baaef6a825623edc9ff1de5c43dae34db01201e35c0388812cd4932242eb82f717b0ab51c9944dc2b653c57f49750509141b410d1ff8eb88809ee22e2cfbe70d0d23506e555000fc11269628013ce5cba7d9a50d9efb67872d9ecfa41c3afd4dc68e4cc5709c3fd1d9f5b81d12366bdb90d393ad8013e3c55a5cc04ab9b1adfe6a79071fc382f28b36459327266c8080f89174b36e49488b30611eeaac67e06a206ce943f50901704fa08f0de2e40a04079cb1e80fa47c21d734524a9c647cd711f05afbaf3954c94d8d1499f0a8f2ade47bb0140c2c68cf765dc23332b16a8e658cc20d991bd4dd57958a91f8c021c4b8b6dbff0ce9f4dd3665d86c165593d743c989425ab667f6963e59bea1327e90aad69970b8e409923ed3fbbfef58def9d32e17629b13f65a3213e9e89408f05be3625131b8248d37b72f92f266c3323e3f43d43503386a525299a3d6794a616b8be26d08b3b16a14c80207ce229d79ecac9029823b2fa9261e5aed52f7feb80ac9de54be519486719ad2c11bccee9a4c449e7c13ea009d1ebfdd3022b62edd7ec5df6e1b3bd4cb96542a28a10bbf7da62ff436aeb9b12c825daad50f5fe1b70fa86c23e619791fca1f8fa42788170a42951ac01c504c40991f4a42e19a2030079a0edbe9928c6c57238b9d77eafe29679d99802556d8c0ac2e44e1600ef22facc24cfeea4f13998387a57b57da7cc25f6ff8e090745e9403b2201954645f9d4849aef4b2f8198e977466a690009abd7034bf472751e5a4e6d2675184436f602156bc250934a333da115487ea035f02e314771db09675d89db6a0f3b9542b617f12fffab6abbb709687f9842c8cd4790036a7c9f4ea16186f875366bb3f9a88aca5fed98306682d11fddd062042ff0b0ec3d7b5bbf6d14ca66d081252abc4beef36412b36edb352959c86768cceea9e57e28f48cd61ad5c888f485f4640b2e98fed51599807a2c7688b7e3a39d79d1b21bc58eaf1d4b3305e169ab55ea76bf2d5f3b5c971fd4074d2f34dbcef061e6bb579b903f1f7eab06f29877c6388f7c20970f5e5897692411dfc7962750377745b403bfb93715ac505f1961c1e8a5f40738b9a14a71ba2178fcd8c969575b0205a39643ba0eb0b5566964ffd5456aa535a6d2bbd959477dc728f0ebf1504cf56fc8dbf29df0c0649db3f3a87d094e0e5083e304a1988197a0c698544be59defe8764d12e1a7d528aac14e02359eb0addc3253222fca091d7a6847b8f581ce4ff44e524b317e5f7ff213ec837b6032f22d44ab8ad0583e6dd87087707abf5ea437b393f1e9fedfe8f82e57a2db085579e83d64a53fed92072f91c02147e8af7b17487aad87a3d6e2416307f6b0f198a3817f1707cad4c488042e8a31e9b86133176f8ee4a707c4fa526485e5b9b66d1d8dac2b390dc8264eeab95e28dc88f46abcab492db4952a9f9fa559f631b15336612729af751237fa47c4b47cace4d9907b9e21ef2cc69850ada7ecbed59cdb9cb0828ae19d5d89e8afd315b3b756a132d89ab1af9c366eabf0eeb69601b376aed040d755f2f49cb887670a549848bbd0682360f57f4f4e100ddc501242afb4eb54d49792f291d0d862e2fad5cc55a9d78eeac857427b971b591341ba14ec06d0b2d0342f289fc6d6c6e97fcddfe7fbe6c133402b8265da2c6705f403f4e2ed0d8ea6456111890f1227ed01932f8a225cb3bf7108bb8897c1cc36ff766f4ee7e02cd933ff29e7ea390a6018cc57b6adc7bac1c3655a4e5089dd18bc97e06a87f731eb885ffe3718b9f2e53defd4f7fea66aeccfd7eb3184d32e4dc3def4d03dc1125fdae9fae0fb0355ded96413b8a57728b2feb6eadcb53c4428adb2191a89cc62a884439c8001a3b7cf73cfb08332b89896c8a8d5aa5233934bc2b7c9a3fc7797481cf37faedd19ad39b59927e216f92f2ac6244814fc47b029ab6692f6f66124c11d5000a7ede38aaff746e65fd2b750c16f9f9ec703e70f44d034e1ae07f78fa60e0fcc05a48ff494b15f7856e651a20d3d91561b19a0252fd9a94213b1d95c6957793e62a17d6f7a8c495a61d2769750a8a01a9badf1863d104a40d284550f7d8232d429553a4fe3e0ab08fb1784a55e492248c32159970f2c8e4a54e0191d7536a329e440553a4346eb009d361a506897be5d1c1456fc1886ebeb3565840ac2bd70ac0de35ca32b279fddc2be862d120cdae421076819eba7f99fde75346bf8bef1d700ce9f1efe2b1ffd0601869fed10ee6a5c7b0b74fb082dd4daaf88958993cd478762bb025eaa01eac1bf6a2d5c6c8f4c38efbf91b93fff1694751f2bf7d45959fc302ef1af2cec33a038cf59e248242640b602f4c8b7e3f196566693b6092c9ac9c77961823c25440c1e14ba016c5374fe9e64128bf882e27ac3dd7156aacd968a8908d65a4043b87fcfd8a24c2183b4ece9617f858a42659c6e02296c21a2b9b9057bc499e1cded75de99c725003b62763769a037d5b8f79585734039164290cfbd40c0e3993a7f88cac67d2e90e10a34f91b0935734d24d0da8f3a7fe133a85920e63fa9ceffdb1304ed58cb5c2b28a3aec42ee0eb7559e8add49c932aec5ccfd0dab57f0bb47cbf1d8cde7dba602a4ce91395dc96c81337afe1db054bd34abe3d9ca6b5c7cef0f7951362c834369b9b0877b28b0dcbed6831156a58dc8eee7ec7baa7f09bc5c426dc1faa4d71f50908bd6f297ec8e754d4d20def005585b4bc1fa31da1f02f62f78300094fcc41df2058784f2a50a0c6181329cd9e3f4e39e0a5e49c6c5d7259d40a730471dba3aa7c6a01b8002d9eddac75078a85025eea76eafd98923c251536d2d720460870d77772c9e8a2c827e80c6815aa47372e42f96f6c86c624be21aa8cbae12edb5002ac030f5584b8d291b27cd2a1675632dfe2a3b00b7aafa40ba9988ccbaedcc798748b6083286835572913cd0467d8031dcd18468b22258d3fbe76cb4448852e257b8c5bf6005eb694eadd7357cb452599133dbe8a5f3ec04a53a7f4ff8e5d1a262b7660229f14dcf7723a53f0041600be4f94768d7443e397f3cca831d2dd02170eea0b9d77231eeb59aaed7194d32b09fcbd1d0913ed7db52935439fd87d8c749057bccb0af202ed0bbfbe6110051b188507f081b093e53e6e7255ade1c70fcb4a3ff23fdd1a2f78f2d93522e81447468315bf9bdccfb0803e146cc091420287b7035a605ff3db1bb987f05b9f9936306166d89bfb097617128be79c69670b36416243e12627bc41ca6c5e5f98fc7e52ca5bca168dc99c8f76595475dcae905383ad4d16a22e3997afd6fdc386c6761a089502a017889a45f40d42015d91286be874e485666fd969d4584d1bcb7fb412b68310ad2aef05c6f6082eb37eb739fc0a2998c1e5652244d270dabcf990cec95eb682b23a4555af06ea7900b2795b60436bd840a1b69ac514a1154b73a21caa6b9fb733c824005a9114cf6d9b6ff3554e9c31762ddca94725898b3bcc1c243267bf5cc647139c5',
+ '9ad432e59a7f71adefb66e0c10e1873b5ab91c65624f8ac38a505d06d288c1f5f1a63a57a53f951347151f96a298147505ad5a5397af6f06ebb3a1f5d4117dc47b208934ae4036447b1e109dfe33382c778e14119fd445b83d85d945f480c2365fca8743608b3a89b459aae8cbb9d9aad7e3b16524c6f222a74c6fbed9df7a91c62c9d4e60278b2a1a4f5541b233e1354e359918cb8e608c5292ca282c358c1ed7cf311b591c0f6fd4877a6e5ba83716040b33f23e33753d65de524b948be025bb3aad74f173b0a00b897f386d20c39520ede9ba25bd1b7f09b96fefbae85eee99f5771fd0335404e6dece6791a46df9cb63223a2735399866aa89db4554eec09a89f9e49f64e5e48e0dcdc36e3a1d8c2cf64738eda2b7d1a33908d8ded878e5e67d998d060e4a882a9ee613adedbb946c2dbe7d1f0c7c72e9ee54ae2d7ae4a3a459c1e0ac3a6b38e31a8021f5c22f5ab291f0d1647b72c35f52d525d9441a43fca6d8a73af0303ce10802b3efc3612627a945fb64f8800c2eecf4048b3e020c17ea46a8573681db4bf0d69242f73a40f2fd26c5c88a8e947d441715ea6f85481db072acac16465f495a63869766a0ef3d15f9f5383a85a475e3a81e9fdf893d367dc67ae197670e05cf115796197c7c2d7a27545b0f4b843e500de85196f73588dba9dc9dc1cc31a4d648cce617b72fecb319aada11c97cff13b03ba99db8763e518398889d5e0f51f870ae30683750a24836bf5c48e7d4e0b5f7df4ffbb2487e68bd774b3203f232bcf1c51b15e62776c1e55a8abd8ed30abd4c9beab8cff570a6bd418e89a4206faa34d95025abfc91a790450c77a4c2a5b3163822ddf6c43e96ecbef8a82ae2314a9fb276a06d161b829e46897e12e9d820bc7fa1700ffc0dbdb2b532997b80a0259b17368f16be3bd87726aadbc190cc8ba8350c7c01e608a578f0e4648142e3c291d238f98d3c193383ac169598ffa97c41250e06b6ca54d5a435b50f227023a9e7a923e6bad8de1a29a275b47e7d967bef164d1815f01cd5a04d4da4485187630765a05e85dab216d4ce71415d54bb111acf71b9069f862ed200552ada2e387757ce566ad689bcaee9fab0421cca41c52a1923f27120ba67a41575aac04f5d6d41abee11952e256ad1ecdac2a328502087bc0bca3ebce1087d56542be2fc1fce2bc60f5caa1114d2f46d98c6dab60fa99a80d04956b82399c4899bb5287da6217fe56251fd7ab26fba449258a9bba7e8c92d1a779f5fa7a3e377f1507a1919eea4d18efb77b127c88c3b6f7ff88140657d8a935d02f896ae41e8ff05c01aa0be02523c5ffefd9a65d018d744af4e00a91f60e10267ca174046a46ddbe2c66517012f14877ba833cff0a474adb66b123b1577ac6eb71e53e35a72e2dbc8668d840932bd7ad7f81c8d52a7ebc5f5209ca3c9979daad83c721ee51b060c5a41438a8221e040f8367a2760e9e79154b4c76aa1885bbdb46c9b794f68827681a1adb3d4c524e2c8a9782680310e1bbc71ba1707118faf32f6f67d001efe2123ccf38667e632672e9b3b111c48086a139d9e3262fce1893921acb161082116290b3e8ba44cb1d71152ee709bf77e8643819a431a0ff852b337e59ed8ed945c7ca6b64bf1be4a3a5b17b7cc650418783265d7d397137d12877ec8cace94fffb02e5824db705a599f332beabe2377d47ca907c6940cf17d19d3f7ebfacc608fca510cb195fe969fbc1eb2c987a5a56fbf14cb28e7f0d6f98ddc12e05d84b5a664c6eb8384f323f69ad8a291895334a9d91dfa1c9bb932d6e2f4e0122bceb9b41df487231f1a8ceea9f56dcb59b8c0233919270a25d2af85c7b2c1fa0be6e749545e6208cac13bf867f986ef6f42b25c8d9ad48a1cb9a7869a9a4af07489b8c6b6890ebd2e73ab9453599076308d85c615178504f6eab569da1fedaddc13b8cca2f8efb8a0ce66530a99ecd853f5d4f08e13bb133ae253a004b82c7ac91161bbb8227773c82df6a4ec4f2ebe30281a5c4713d92d6f4cfcb9b8f789ffb1ff4e24549a58771b1ed72a86cdc8706b70b079ee9c71cbea672b27556278953d59d88706c4e55af01d822448aa3ca74c8c41c9c312176752ed6979686d8ae3b1ad8b644d46b0681da67242c0790dbb79b748dc93193ca83f2c3200709d3353b566f14ca6743c56ca4642068c9ebe2579ae3012ad2653d6e5c01f8cfc56047dbf22849090e20b8fac795bdfebafc09da2bd821c9fadef9c0d257c5f6a4c70ca454ccfe09b24807b2abc2a4f8c10a76cab81c95ad92472600be8f30858d4fab1de523dad33904db1cb8b57d5dc55f51aea87804de83e8ebb7876fe08367414dcf0df4866e8d9c5cc15735ef36b041c30f63b11665b309716c95c07ef81ce519403509a9e29458b128ee09a28a69f9f474519274490cf2e0a75049b1ed938338fac328ec38388003dae7fa3f61d8ce0b65dae2c69275eb5ff120d42268b463d185211af7775f5b795da8d06ec4e50a306a6668b348d53b16d06d27787467cd0d67b5a671a7f3323c3b9b53d6b978f38d0c5dede474162b2ece9f0c5c169408142ae99603d1db4d73fb264a204b79d347d22739011e1f03cf731e487658235d0d5524b154fcfff44726b37ff37fd0f089452c14b14cd80b204652a66d41fc142071cdbe0d30476fe043a9b8f85f659379ebd4469c08298a4acfa4edead9087577054e86d5759b0565da70bed7f220033f4f88966b59faca74967ae494292dc737990ed155d4e300fe7470593740ea8a04f6aecec6483311c7243d55143854548bb67566345407a6d5981050c052a96ef06ab0b454dbbca86b005606110f666299e3eb0f1bfeef400fcebb6d1b2f47f82a32f411be1c7dd787b22bbf34b4493a89a8d892b2d88415d79ed676f0dfaa70dbd6e4acab135fe544c3264a2531e724848da4b8a0edac6f5391ca34e66bbc1e2adee64fc4c7481cc9baae6e8b8e2667b21bb7498e425094ab2eff6fa6da634940d364c0b14021c23f1f2af20ed1f04d3ad97a0d23e159cd08fdddefd834893b443dc5c81986d320cd7b049f57042c1c444f53cc08d1ad629287cd7b2382153a5fa2712fa5a2457e9c54a33d0e2ec21dfa06ab4de41a369b705335dd2fcbef8fb98cd0bdfc9b5e24d3356f94db5899988285eb5960c5dac0cbea7edba517ff8279824a3ac67908599ba3d06b64899bd5045479b824d88318cad2113e2d6e2d5ac80d476dad442a661c282e8b54c9af1ba9fcbf32d75e18ec969554cd6ed96161cdb42f1f5705ab937d4bf1bd5120bdb49804238e215c818456bab152115f83ddb1ff6a186cc47528e79581c70397aee6faf40f96acdca62833ca8f93bd097b179b76602095876f6d818c4771143568faf755ea101de56f20554c565a1bde3157b4a279ff7a5881aa74baa9852903d438b6ae1cc51f80c4b3e3c86d6dd3e68de5230a1e7cc23e9326e7a710c9ed07dae760870d7d58b48d2b05c73e948c2978081136b56115428d2a03d9e507054b63501c6825007089b2cee5d03099eeb0d809a6afa8741d6a57feefda8e051cd755bc13dacc1510071be5a86bab43a382973009fc5d7e7fc0daead432758cd8b19a716d646a86e037b0b77920175950a79bc563a53e46e64cfc36da670063e1e318b8518f671987205a8e7e78dda4a028ce10db33b196f815a62928039954e075b9d8e7e22b97aecaffb0dffa63ec380c16599a2d20345d23c10ce78ea7257d6d631b58e51ec7fc3e8866c52f12f8c35b5de7c81bf2e9be7e33273e8e929f5fd500c7cc20a8731a83def8589d5292e71d8a038c858f7dc32fe6c2568dc18a2fb477256a1c3261afa1e7aebfce8c804c5b85f50ef45fb8133dc02557129eae9413b07d5b60225a64a18dd0234a268b9b1d360577f0ca6257b0ab9868a690d237f99317c774e226e18ab7a5ab978a7b7c69857befff384236908e62b1d0ffc0bf6e083ad5e1d88dcd644d76803f1e1896495a9e9dab112159e650cc5b18533ed57bf72fad2e6e57d038368bc9ca4c2a92c49db7aa7bbdc767b9db1e8e06f5152126483f78e30b4b930d5c2cafbe7b7817e2360e3df7a4fc091216a071234b0d4a79e3af5e891633beeba6a15aa512cbceac0deb7fa82e88e7f8e9992df6beedf97cb0ca86a7f9d5f9dafc31f1067beb5f290a969815c432a8733c53e907b77fd53698d719c51bf9eae346269c6a1da071621656afc7ccc3f58bf714cedf9c8987af811d3e6be4693c0d6cc6855860b2ac5d1775ec44b004754903250405d6d4b6ef55e51e22bcec9561575ad158ace8ba24f7397816d6bad1b446ceb201ce280bfffa77e0710b499ee5ed29abf2690b40bf9ceb7bc4a8b0b0d5a956015ceb4c2ee65a1baee13949fea3177d39afffe3e934f0d21cd78cadbbb21407189d940ac6ac5b19adf9aeb45da3be9ca316d7385a8dd93884d6ec789a557027204c33287b5ae7b80741d35265ecc8cf12b057d23ed24163db492fdaf4c6a3ff40f22f6ff7f6e542e4410eeabc851f5eae03e8793b090f9c48b9bd9b710b97d7a116f0a7df8b3cfd6b82c8460a79a9919e99f3f4f93c2b2bf3adfe83ba2e32f76a40de98de1ed632c58b6a2cf8cd50b00bfade0c21727c5805ad5a5977375ea6c4f861099ab99a2819e45a652d8bfa021cac12895bb4ec6ba64139e74f2c022dd7c7e1c4bb637029901602b952bb91d0fa39f8334e0962abfdf203f3eb1706d4ca34c68fea25407b52a8c9ef0f63fc62cc29f7d1a379c76233472046f3ff51e3c5c87833cf06f267862216d10b8d2300bb02762d020c01d66fc9f8a80eedcda2f2095f78110e6f35550d5325ef40eef2ef4edf0d11cbf94fdafef3e1e4e861597781e32558459d2de6efe7b46285457f8f94993a7bbd97179c707ab81ef8f54f7cb86d044eb4659762533a460d6310f1c35d0c7ce6eac475dca7f3f6642572cd2a3563db9962afbaf0beb4a398e2378eb530a09afb5b66b0c22ac8635d78be1ba8802f73e15764dfb9babd92aa0aa29f8a95bd5e6117661178815fc7b2f8c2811f213794cf9c6a4ee4cebddcebe43a6b6d14cb8ec026ade0233422ea0bae6647b7a0e5050a38ab4f9a831c490577e53c843632f3c2912265eecb77940ea093b49786a0903b330e2a035f42a19dcb8c58f7825a550d5face7ffcfc88c66d82f11e85bcffb0a967a2646ec97ac5ee91808ef81ac7c499835b07ec87c9bb95e23a6d1a5dc0f1e68d98c8cb28432edd86f9494b98e2f1f45a406943cfb89b03858bb7bee2e9420b112a4fc386ffaf5a07b440db46938366de13269b5ad1ef270b5980ebd2d52b7790db6bf06c1cd1c4a226083faa65d3818d37b24bafa21280e2185b19b41118cc9b20afca4e3730add4e2f1d11ab67ca4e642bedd44a5cf91886c98c0fd29847a8e9e8dc9b3bb46861e6fa0483e21d96187b89ee8905950e98cbadc8a148b08f1f9a9787713358dedfd2095fd8b149447d00dc4c6493950e95d8798dd90b210c35ad6024b13226135dfaa4579682b17c86dc6d32ea5c24a2189575024083b367f20a8bdbe09fe7b0e646131447bc1ee53af584c6a5c9097228eae8505d192d485e9960d688e4635c7f9e9dbd72c75927a13468301c400e25cdbf1c9eb83359568243263a306862c032ea8c7aa00b27ebc3816399d72af9630ffc5715da3bfd3a65ee27326193ac840612267558f053d9a9c5bb295ff093d6f789e2dd4a97e29c0f83a9e3a2cd084f04feb4d322dea3985ae6b9073bf8a4248c4e051d90b1d02289ebf5787b7e40c932967dbf863de1d1decea55cf3acf4f5d73307fe35ef8f77da0c5317740e1a9b9ac3cb5f0d75d32b3e63c74f10734af2de2132a7c0bbe6410ad00ae916e65f6d446be4db3eee424f818f660919b470c2abfecc40b97c9e29221e6415e86cd7e63677115ca210f5e4af39ccb196d92c0e46b41ef3a9997fd629dda7c3730949af7ad09a0abf44b693d1493f700f49477eb52970e6177c51f127f71c1d3d257e70185cc70c20f04b04eda6086e0d6c89b902470a418b8cdc2125530b48d0293fd01605e08b4a7e7dde0e3a6fd217aab96835f4332d8d31e80f29d9bc6fb47b52777eb22caec75414c939ecbbc5b66ba132541a34d2a6d33c623e7176b86f857f0e584bf49e1ddcd86f78a4366e711707c069a055575ddaa1c36903834b8bae903daab78082d77c9175d24a26f6d016b4b97b6edee43bafdfdab4772951905d4bab7ee018837a9e068650c7d4845bd070c6936c17a3c7b8be4e26b5b1f204fc7a01dbad04c990a90048f80188419bae028fb88248ba895e06c8c7a6635571f2e3e6ca068b7ba1054763d4e18a54171634bbf298b85109097eefa03086a35ea74f0351d3f7c13319a380ec4f21e65771ccf34996b091d022ef6cb83c6403548385007bfc8ec4d04fdc474634961fe42893dec66478a1650f21e618b3439edaee4f844d6a99acff0eb95fec76312645a1512570ea58aa503adc06c67b6c9c78507337d1035fa149bf0371e6ff3a240246ce6f501198d41a09e874cc7e2724b611bac20eb02aca34c882243ab5f940a47ed1656cbf7f464ae60cd732a2bb5e1d99eccd0c5a404f4a92fe21f282b6a3b2b024afcedd5629683811d7fed172733450d1ffd4e7ea5913853f0f164db874b4468df47e5465a4fc67c01d3af2928b839f30016d41701016090c97acfe48dc33a7d5dc820af4e08fdbdf51eded64cd93ecd37adf4e1a9bdb872f61be7cae03b63bc411e4e94b05a8fb361b20aa3062eba0801333f83022ea656e1453b13210c56a2138acd8b23ad0318f21da103e72142400747025f9cfcc0d925874841c2ce89cb6fce0be70a78ee5b00e2309d52494df1b449ebfbec808e563d728ddb37ea83949028a85ce756a7d6288037d3ea0f538982cc6adc7352657a83677a4402f8ca9a3f5b11414ccec626d378352c20fbe9941d3eed75c3faeca2b2069c10b661d548c7b5e538ad39dfc99c5aa71a7997869dce22125c50e29a6b23b071d5c4ce1a3cb3c982a77b304b3aed781c23565aa0f3200647f49c91f52062f589e7b0962fc2ae267812593aaf073180e2db69cdcf50bd6c1cd32981638efa5642dafc428c86f12d340da9c1519b12d5b9b706597822f0b3ff7c6a498bf344534b342a5b9706376e54fdff6cf9830c170f2ace9611e6548e6e54e152c4f9fb6cf167ad59f5aceb6a4967cc860d3b87a531cb24fc5317635bf8011135b50f6a13d40a07c62f0787a19fef83a4e3411000effcac048232b79d1ae59c5ab2a02ad8717fbc1889928694a6d9d76232102fca9853c64745d4abd25586c53a6468b83b485d5cd9bbca82b41ccb1a1660455162a954f62d0459ba8c16793e6d40a59cacc7174c823c3be6906047de6a044d0f59b164de3e444e8e3afc116a6bcdf332bd8c221d9a61533cb9ffb496b58493c4203f27c0e39c3f715f7503dbae62ee24edf622428ae1acef8169b5d58167b60a46b10250c562891e79ffa504ada5d2fdae938c5dec23a599973cb00d6634206c4da588f04c3dc7e01b1a9968021d6df78ff2c4c236bdd9a55bc727b0dc506f44958b2041f0948860a3444588242ffbdcf2726001e2f6b5bd5fb7a1624c62ff3dcee06ca85afd371ab31b3de78c54290886b0e2bf8994c62c037ca1943ee25cb25a23c2a5d3de4068bafde708b33061f4ad3cc13d82ee877bf794acc94c45044cb7e3c6ccf3ce50e53b6ad56e212b233be664900e778a8647ac8e2773cd01926778aeed805333d52aa4f08d7a7edb0948b2c6b3c4dfef2f0982c7a61669ae638d0cd3bb624aa54973980d73dff49670a5a2d1b0e31482fe2c2adfad338ab20437f4f094d572992a8a75302ce14b03f5dd37242bdbbdfc8039f544a15da8a300f2b1842e6c4395f4c9dd071d30ea9a0549d02c692154a231bd828536f75bf7c647d31ccc99361234ac3fe0c9315bdf2b961e591d56411aaf21431fb2931d36e0a1da1913eed2a466bc0e5bc584f729d52c62489ced3bdc44ffc782b8a354d6dc8b270778dfa1b30773d8d6768e75309e875c698c487d5d8fb3704ccdbaab5e068e4a668fde1bc4936e1fff60c03e59f4215d3a501abe150bf6edec465b79431b05d4c4bd7cb95fa6f5542528cccb2c52a4f5497cb65699361490cfd6d8570c769c26a0764df2fa9ec405e61306941e466cb50586bddf609a96f985d3e3cd40a5bbe0686e94611c0734b5c0d40021a65bf30cfcf293d0f1a618989ce1f0345624df72aafb127c3a5cd1e433d03c1c6aefd27d9e44caa3d2e4f3ee83757024d370815dd6a03abcec2c2601bd9c2cccc29e857777f1e4e07ad3d37bc7f2f6273f155c1289f26f9b97d19b9ecc8c54bb43d4769b088e551f5fff11c0d90ef4b3ff8faa31136633b0c409cd3bff454670751e4048de7eadb8f8c3394e451dfe43ab5bf62a3180296507211539b44b7474bcf85d1148575125ebdcd4748aa4656eb8e6ea6e32b4b340c7a41e489a035150b1ef3774f48cd21e9f885de41836ec8dbeccd19db58853dc8c2f42c90f018f6cca6f69f46193c2eb8a62501d7c49d639038a6619288fad90cb1f1d3b81ca61418cf55f100e108625430735713561c4f94d8bf2610a1f02e61af0282090d28977601da1485867ae444fc3889fc1f33b36f36e0115e8cb0674e24ede18ca9e5a76fa44bb1ddf2dadd10743b3e9a0829b7a7b8d3c9833282aa5c787b9748d9276a8a20716f110b707441ff461ff6f94885c6c85ff7877aad1f1114744d4586340b4fdd14f727bb83d25e041fd417dbd64254cd4b43734b7bf0f85ea0aa8c9656b04644fcf02ae85d1eefed8f0406941c19d72f60544e8f324296bfc75724f3d282f8bbf0031f7c44817d215e57c90e6230d95566d3',
+ '37ebe98ef52bfb240b9ad369153afe081bbcf9d7ae43e8ba336b8ac57e8a6da0a3365e3008072473bf9d6eac13e509c1619956e12a06fc696512da091a7d40232c675e737713fcf51aea6c0316c3bdbe196132b0943df2b013860105ce676fce7b88d0a167d7ec72c588b7b6465a83c9ea1d748d15713455e5d0e901c3cf646a38a09b0002dc5ab1687f350dca35c1a87cd404c0d529292082f77844203d86be0bb8a9d970a9af7baad8d050cbd9e024788eca91fbed39db930398180e393d949ad7e173d9c65498339a6ec670d049058653ad48af45cc4cbffd30c3b54cf1b290052b1864bcafd0accdf9b8e2a163134d2c982c1bba4a3dafec288e3cfd0ae1934a6f0e39122aebbd7a586e48d495167620708664d31c740bd868c1ccd5f0e94baf959e81502cb00da87330cbf149d5a8381e9eb519a8b97acad7a48c5b0c92623b861064ff1ce8455f32469381e6198c7b8abc341357d6a4c85f7fa517c4a47df728ac09a6645b0ca77df7c70cd4aacaf19c280949919132dde7993e9181e647e964ba99cd6bd10b893c8d90187a5009a23d295d43bfb4cc0e583b8052ac21651b23813bfc9912ea0c574e152f42d3f1975309588a4705196598ad93e1ab1d82954b4a18bc56e55039b6837fd893fa2bd7c70e21a5934dc2e990379ec6e8a2445dc55d57940a14e5164273f59cd58e5f6a8281e11c09536ea22821c98ac978537d7a02220d1d6552aee168a001715834596baabf7813e1c69949b23eb4b86658fd51819eadf8a13f067ca8a791cd1d53ab69d0e43f18bd72d5d93322cc1c36fbe33121f5ff01905328fc7c33d452a86468663c77fc80b0195ec1eca05a5daee339042b4f88a1f9371b472c6c5168c00e984937a134b282633dea25dde7e397b907b1e7d3d240a593e747007990782cf944fa078a7118fbfa793b2604fa15b82453209daa64475d0e95e2408319e8b5ce7460f4593a19e3831a9b363b1c5ddbcd273995fbc61ce7502b0233b1752223352e654837181d01a929f49faad422c65b8ae416ef81290b02b48e222c2b8c3ed57cf0494b928c1e11ad2da77baacd427785096aae1cd593cc356e551bc390cd5765ea41be30cf0266ae2e97d326c417c91e90d75f1f874555b88a14a7c5959a62f23976b77a4c754e35dfb7ddd1700df85f61a62b12a9eb4644caa7f8ba036b9f29c6315ff96c3f7148284ebe3239ecad50641f397ea24b46e21655352a4109b61479b9dd34972779f2f1a6a1d2887b8ff88289b2ebda2efe995668879bb93c4ebb3a585ab336f70b382205ac37c383475fa12ebddfb95b157172261597d2cb0f24f254feffaf75d224a3b407eb54cc7c8daa5483e4a79c347252d808a5f480a35987f6f09f6c6a73bd5cfbdb76a11ed78b86442b810cb703a5dec5874e8721af62e386591bd39d990b3521505e144100601b46de3f50752911ff37bb18f377de45ec4c60fc4ed8ea1717708d2d13fc9e1453a1c4a4db9e4fbe9b74cb8da14ad50c8c8f2ec944e10ee8e82ebb6a081959b0159f043a15fa1cb59bc5e035f7623fbfaa99ea0a1d81ae8692a4019e5a5edb3a4886c789675039fde87222975e86c2642eb0bd48408072fafb1a88507194c9bdd69f3418376a4d9e68c3b83b3f800605ff1dcf0917a6014b0dd77708b583ce3ea632746fee0e01a10500cba90016b4a9072847d809bb0481ae25f74f8ef290c7a087ae16f505fd0da670826a0b1174592d184e3a7e8622a5c84a30ab64aab75face50b96b217e8ea335c0605c638ed1c59370bb9ded004be428f49a79f74ec0fb296b3758f0b6b41930c7e029b55c8fa73cba7dc926151d4043c6bc8a716d7de9ae0cd3ef3ab2d19b0c813eaf12eacfb641d492b0001b2f0f699bd98e4581fd44c0c817646bdd77a71d8ed432f8d422812751a2f9178cf1800ee689ebf046cf9b161f9a7ef0a106cbe833398bf383288661b426fad8d4f570a8293629ee06856af295a58585a81f87f130e6e08f723234856e874bd0adbb2fc9e676deab6b9f22faacf12e875d1259ccea54f7294be02a16f34c427b51a33be8a0c460c4c07d51a2e7d5c0722a9fcfefd21c265d5aa2c57ae4fe95556b5e1388ea9756a6afb0856fb8fbe1d2bb1838be7a95049848fa9545b616badb753c453f266836eda3c92cd592bc0925690c42cd6667f866717827ebe91d0999f9de5f5fd6cf77f63737b65927aebcf6cefc7ca107fda8447e8bebf1f08a280d53a4b07f8e35904cc48cc08eda3c63a3475924bde1de6acebaa65fec5ee68ca22d3fe722bf33267de628c9db1ceda3c78cb2f9988682d641d068023f96aabde4e10071cdec2080f616ac30c2725ad3efe98a69a56873615a3a3161503a4f22621986def597b66641d07793d97cdc9a68f85fd3890a38928462b2fbe2bc5c509631438d2e344d1ced9e2b71748f1b6ddf33a3e597de3af03ce43d305b9f5acefdb2b71acc645d3b55fa3848484b7fa4cf25e71e766702f1003950bd2f45b304052861f6748a8f38175f1e96c91471f5a54999cc9937191b6adc9de0d2520d86590cd4aeab292ba9ae474edb5b8caad6ee095c9e74c0f5e5c9387559f946b2dc45da7fa1d4c2dae6973d5984841682af25ff7ff29d9721d6c7e76776e8965b6c681bc38e85da15954ecbcf20d7448204d9a6a477781c1564d363e4c634c36fbd3c3b50b332f1643c415d004ec999316e75694a8b98e2591678388dc6624058454ec3a7ce608b3f222b8bad5cef77095285e1d2ad746c557222dfc30605bfadaafc4f292e931a0f0d49b226d99d708247879aed5b9f2ca2fe6fb414f37373f844e13865524f206c54487aed53781834b3f6eefb248d95ba21bb60041d501f90a97a19dcd80920df7d84309148e3d0892e50687c86a45a1372926e00f200053f5f436e003e35bdc10fa99d9328853bf82d2091f1f087cc37678138ac0027e73cbcc99f7fe37939c98114fc7380c0ad1a26e3f5ec00bc7eae77045a55c62c18117879389c662837415852e7a2d01ac667a226fedb2596e3e137a83daec2712a65e8cec3e644e738d11bdfe9b19517fa593546373fddcb9e681fc97d1763bb9092a456cc0dfe1aa0e132387d105e3ccb7746ee199aa7af00bb96047310585fed40219dab43f057220a41e90c5f89fdac4a5d6b207c01d5ad4440c5ca29eed292c6f7000c58da111eb4b16e31efa6df3f3aff69e6447ac406aa96a9ece4b5b813bf8b3a499d09cd0969073468513355d6c19346c58480feaf470e0d45a13b74f2925488fd810e0f74afb9e82a24cdf61586bfae68dc92ea09b22d8c8f1ffe9db1e7e98892b5554ce2e15fd5f1cac5347df2eafd2a8d5f1aa8746b9403915da6d418c0b5a3aa8e09d6b65f9a49c3b7a5728e9baf95471404fdf64eb05da5f704dbad60ac9ac106cab2873fb1bc9023ad95c24852337a703d9cc04d6df7de594c3b2e4fb9f2996e0418ec8698a4c087c14a2687717f97e228e75afe295caae2f16513f47a45b4124a7c5ebacbacc562951233bf89f43ff85b703ec77f168c2278fbe6e57a0e7192125f4642d73f2f227d806287081bd30149b9d44fdb90029667622f9925b7826bd0343bc537c66e660f174b447860e1bb8846c3edcb639ebd213a4695f9cb471e188db7a859fcf3abae49569e676dec857b897627cb0bc1155ad6d45282d430176fde4262da2d5f41ff890ceb319d73dda804738456f30a3d68da41554d4cede62aa8549b24e211e76768e6b17379f842a24a449a0ba3ea73cfc72624b5afd118fd7e76a7c6b5bbfa7a6b6c97b97dea52decd51cf35a8e277140ffb2748777a1e3cc3211f3c12be099d0316f45023da6cd200339a718c72a5ca172903922e59648d08dc67f173788363c26e5df406391f107552925ba91b9e569f38101f5eef9a52d201288372abf6532beb4af19fa6d81eaf473d40896dbf4deac0f35c63bd1e129147c76e7aa8d0ef921631f55a7436411079f1bcc7b98714ac2c13b5e7326e60d918db1f05ffb19da767a95bb141a84c4b73664ccebf844f3601f7c853f009b21becba11af3106f1de5827b14e9fac84b2cbf16d18c045622acb260024768e8acc4c0ae2c0bd5f60a98023828cdec18ed8dc298a306c38d1ece01509f3265b5f8cbf441f0525097e8b48234bf69f65cf402c7540a023ed231ef95b222a900ea4bfaeec02c6d8b3b01648ad7a165237ca6b557b1ce287b0ea137f4ef54534070ee793695a9078ec89bcea389956878614ccbf917b61f8427b7cda870fdd92d2d297154262fc65f28ff1a54b2651afff12d6f36ee8c906107bbda399ce5e2cf0a430ad0dd86520841757126bad725bf1593c7959f16221894f5852ddad3172fef866b3321755491fd44fba009b42ec0b6c4fb9e901d7eb3b8acf70e94911f54c538bd0559c5740042b6df4a07c3e00bba0934d92a684b39592a576331e5a44672a227ccef3e595ffa1146ac1dcee0a70baa9acfd5c132b361b5ceb519984b0ee00cd2124aa8acb50c9e574fb19bd99c8fef5407faeedb28b796848bb372beb3f5bde55ed2cb140b60a53bba2df471f330208b09ffb8eda04315a06d693aa53d9bff8939ef6f3a68de6e1975f79f50b3d484665e4ee71124ed794be3a2baa7b5b918e62a095bc5d46e401a0979641fe465640e8d4d43eeba9d0cac76c7b86d22375123b988585e58f86566fd190d868eca08aa1e66932d6d3b14ecad3efd9f8cfcf2696ed42eadfa642324d941602cbaebb8639a00a17542afda32117051e4fbf243dfd255a559c49ac37c265827ba70b0bc618882336f43e1a6a729c57be478008cae6c74840bbe828c976ac628d7b6015bcb705612c277bac0727da645480a0e14fdc497956aef05c89d30f22c2c96c6dfc9dae30617e6206fbd957975b8ba0524f563289e1f5f09bdb6fd46fa6117e78e854f91d71699fcfcadfaa7d4db8fcb04bed08d68d11677b5085b295c1d414cb12456c84c669737af6c33992a5a9149fc7f9330bb291d38f6bed10318081dde8fd178f02eb0e8b7d022c8b63fdcc867546035775fcf7b32c8fee83df7cbb28372b23c71459b9566a7f64165da0a3d0e538a3dcc1b6a384f75f0263dc10e0924a0ef2ab459d0a52b7c112710c58cf72442253396b8a25d7644be166c3e7828aa62b1ca1f32f620ed969b021ec609fe926958a03cff21f08f7c8d3d3235b219fb0020a51b97b60f963ebb58f7a62a5b41104c0b28b58cfc81668825f87064e401c263421152b8790dbc99b3032c9615187f29fcc1a58e86364ad45524b5358fa2f0a3296729a3663a585e9aa922f534fefd16fb6f96cd9895709c5520cdcd24c8d107e387e520de055a3296544ef1c1ddd43b919a4ff139861f06ae5280d5aa5aaeb8f7d74ed6ea56093c2e697a30c29c4ac145aa99a372f1a03ae72495f52a40cfddedc12b6e9115aea5ea516c5a4223a8d0a0073c8b4abe3c6188fdd6d4ab627c9f4eab468fdc2a91945274ed18465a368f291a0050c9d638a31944091b35a8fd26a1ff65e2d17dfa32ef3ac412d8293b276849ad9af71fdf272363f771d0fa99996e24510e7bf731a7480cbbefff7801c0e5fd0a13dd8278162ec1687f85409a203e82d2bcdf7e7d1ae5509857c42fce80299fe06182e74a97c0c624ed5b6246e59781af9407fb28b34f7024f42d36eb92bb95f72cee379ed363daf2625b48e60d0489b23dfa57789c0dd2276b4575a01c2349171d2a58bcf29e659b868cdac1c30a02a160c078b6faa7e0696711d43447ea2108db3d34ec1bf9cfe802f601212d335445a4624829f8a600b18e9b3cf13a9787910f2fb27676fd809e7ea1a34c7306e766b2e7ae1bbb919cc888ea931d1eb2e27c6109b9a12c31e188a196a98bbe0b24cc315791d26ef01b77fe06c3011ac39a8f78d233b7651e586d14dcfc2636cb713ecabadb97374ce58498f8b2e557531793fd9207fe484a4e147f7b826502cd3785251973b23e2b62b7fdc74a10fce9c04f97511dbffe3f2c46887c25904b99df69e97b416bac18fadad67b71cc320eff8def185d41ae8558cbdae6ccee38b8cfb2bfe92d0aa99815b3ca1d115f21493b13adeeafce81a23c6b1bc15fc8f2b171284e6a1fd65c351b0c82b31112f022ddaa78dcfbac9f203eeef415c566a00c2c933f06ff18ee7674aba548592dc8214b1af8e929242f87c81b0cebe8106b5267ba39c5b51987e38858dce1d1f8d0cfee2bd61d217e5a5d41bb0c4aaf0e7b0a8c66e5b0291e4d05bfddcf8861bb31b32ea5ba80cb02472c11969b3b02a7f7bc025feace44726b6382012544f1bd1256744f4b1b0ff81f7b9f7462c5c92507f1316df228ec5c0786378b871e69479c3e26f232f5d6a709d3551d08f0ecced52f8158a2c40a234af448449c1cb1a1f6f5ae56171606582ebb9a5836c454eb86015ae7a4ac87105b371bf40d49b1134a037243a0878953b5bbd6ef944ae7c345ec24e4a0e8496b62d71a6381aa52e5bdeedc81784f45e0c75b72a8c9898ea0387a47153d7e3a7c895aab58a1497a5e794052d7457624478c24d44c7e8932c887322b422478418af64a389c152d12c7a6803e0fb0050dcf2b9d65a35a53b9845b9c3835fddd45dfd12e28f8845e03686b3707ef6003e7c1cd4f8d7406ee0d1cdc41d7b56fb630c1438fe33196e53389f1ec1540fe789c6599c0b589296214d831a86e89220ae97974f4d112f4c98c726027d0c9316d1303b87a43a86cb8b800835a677abfe1584e8be55a624612f56bdf71a054a2e834e35105a19a77f7dfdbf9dd2850ee44658ab0eae6e833c855bb9650eda7f8f4e74d8de73526f12773b2bcbb1bd35639f8730d8cdd6d64f496abae4e1f8cdc96148894aa691683515bcdf37ba6caa0cbf953c752a7b9819e9f834ff39ec8f6d8a3dd8dd5a431d47c7f74c7a633ff73ff507009c5ac9431cc588ba0c6d226edc17c94a0f14d3e8db0c7ef60c3293878dfe513f96b54c61c88a90aca4f246d6a5988f5f785ce0655f51b85e55af03e5772a083bfcf0816ebd97a4af416fa6414a9ad47b7198e51d55463807ef4f0d9b7c06a0a84762e4e46c8b39147a4bdd594b8d4d40b36f5e6b4d48726551890d040d229ee70ea3034d45b3c28eb80d686918fe6e219636b8f9b7e6fc08f4e3bed9bafc778aab274913e9cfd570732ab3fb434c9ba0928581232580495571e56f6705f2af05b56642c2b93df65c443a6caa5b167a4040d2438206d2cefd3114ab466eb3c9eaa5e66cf4447c89c493a2eee0b0ea6e7329b37c90ec2d0142bae7fef265ae3c9c053e44031c0a142bf9faa728e5170cdba59fa8da361d94d887d5d6f58b409bbc4bd4548990653a04dfb841fd784ac9cc4cfd34c88512de212074dfba30295badf22f1af2522c5fe1cd423bd8eae429d7a862bcd649ab61bf0d3b55daf4b6f0f390c503d7c1bdea453b5ef145bd8191802056bd9e0455a404b6afe5b25977f02f902caba46f988d91b2350ebe4091b5584d4f938a45803984a5291beadeeadda488dc7ed2dc4aae69ca8ae0bd4492f9b297c3fb257de986c1615d44dee59e1e14d34af9fd7852b13fdcb713dd1a03d341884a30ea1dc0104d63a31d291df035d317fea98ec44f5a86715014783172e667a748f162c5c26a8b34a0f133d89fb971bf6e0a01507efed010cc7f194b5e87a77d56a909d65efa0d5ccd6da9b5eb1d73422f97ffad8012af43a2905a98354b8362e9c459f0044336348eded53660d65a38a9efc42be13a6672790496d875a67e0078dfdd8340dab8547be140ca9f88891b635e195c20daa8359658785cbe3d09ce8a580f009324e6550b0196e305889262f28f49dead77e6f5a0e859c57d53c935a4c9590879b6528eb2bc3230217b0897cddfeff405a6a54b2f50c58311af1ede4ea0660b73037f9a097d9d0271b45e325bec666cc7cb65ae780e361639838d10fe79907a0da0efef85d2420a84e905bb33116789526a9a88319d460f539586762ab172e4a7f305f7ae36cb88c96d91aada0b4dda3418c670e27a5fdede39bd8659e477cbe08e645af927843dbdd67489b72693efeb3a7be0e121fdf5580474ca028f39a035e78d81dd212679d0a830c050ffd43af6642d60d410aaf34f7a5ea9cb2e12f21672e3f4e0c00ccdb05758e74df3893bd40a5d7921e2e149330fddbe0a2dae4210d50a3caa60b1b9db685f7704ae2d7302b18e8261052b779139747f462a6610a37252b170afbfce905fb6f7fb8c2b6100ee231507f403fee88ba5561580d4de4cdf600bf9e9816c9da1e1d2b91a1d966d04cdb98d3be55fb77af2daeeed750b8b60b494accaa12441d372afb3d47e7395b9e0e867595a1a6c8bff8638bcb138ddcac2f3efbf89762b68ebd77247c89929620f1a3cb8dcaf9632fde0996b33e6b2621da25924b4e2c8d6bff28ae0867786919ad763e6d79fc304a06277955795a7cb17186fb6bdfa98a16189544b228f3bcd3698737ff55b6185799459b796a63c6a61cea9d20f1e296d62f474c43750b77944e5f1c09072f019dbeeb64e9bc8dec4605d8e0322cdd97f56cc43084f5c983a584855654366fd5659ea23c6c15e1d7da51d82c683aa477b9f896563a5134c64e32814ea88b7f7af760f18bc91e656da92b72e98bc03f1c6bfb442830305529d681dc6bccae66da9b2e61b9c97e2397fdb92f7f6369b470529c570c2d3b329487981d148a462cdb992d792e34dd233e1c239657b8da0d59b804566cf81ad5f0a7a0ccb3a8fbda673887c153d2e56c484f9230d752be52c1e35bc9af5a7446237fc072afef777665c264c18e6a3c059fde2e8368f9bb898f1cc8393d1bf18b1757219670275f0bbc7deb0248c68af929111e19737479bcabab732d7e033aaeb277eac05e185e9e56b2450beaac784dd0308b7a5e8ca1f2fcd8852ddad9f7b7de264478e1891a391aa89964dae5ad0b7a829c2c9209db346ceb26c1b967cfac82ad574761443be3f0a910968239d23b11507ab978b3ce89e22b7d7283736b9786544ab4460f',
+];
+
+const List<String> _digests = [
+ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
+ '28969cdfa74a12c82f3bad960b0b000aca2ac329deea5c2328ebc6f2ba9802c1',
+ '5ca7133fa735326081558ac312c620eeca9970d1e70a4b95533d956f072d1f98',
+ 'dff2e73091f6c05e528896c4c831b9448653dc2ff043528f6769437bc7b975c2',
+ 'b16aa56be3880d18cd41e68384cf1ec8c17680c45a02b1575dc1518923ae8b0e',
+ 'f0887fe961c9cd3beab957e8222494abb969b1ce4c6557976df8b0f6d20e9166',
+ 'eca0a060b489636225b4fa64d267dabbe44273067ac679f20820bddc6b6a90ac',
+ '3fd877e27450e6bbd5d74bb82f9870c64c66e109418baa8e6bbcff355e287926',
+ '963bb88f27f512777aab6c8b1a02c70ec0ad651d428f870036e1917120fb48bf',
+ '078da3d77ed43bd3037a433fd0341855023793f9afd08b4b08ea1e5597ceef20',
+ '73d6fad1caaa75b43b21733561fd3958bdc555194a037c2addec19dc2d7a52bd',
+ '044cef802901932e46dc46b2545e6c99c0fc323a0ed99b081bda4216857f38ac',
+ 'fe56287cd657e4afc50dba7a3a54c2a6324b886becdcd1fae473b769e551a09b',
+ 'af53430466715e99a602fc9f5945719b04dd24267e6a98471f7a7869bd3b4313',
+ 'd189498a3463b18e846b8ab1b41583b0b7efc789dad8a7fb885bbf8fb5b45c5c',
+ 'dcbaf335360de853b9cddfdafb90fa75567d0d3d58af8db9d764113aef570125',
+ '80c25ec1600587e7f28b18b1b18e3cdc89928e39cab3bc25e4d4a4c139bcedc4',
+ 'd5c30315f72ed05fe519a1bf75ab5fd0ffec5ac1acb0daf66b6b769598594509',
+ '32c38c54189f2357e96bd77eb00c2b9c341ebebacc2945f97804f59a93238288',
+ '9b5b37816de8fcdf3ec10b745428708df8f391c550ea6746b2cafe019c2b6ace',
+ '6dd52b0d8b48cc8146cebd0216fbf5f6ef7eeafc0ff2ff9d1422d6345555a142',
+ '44d34809fc60d1fcafa7f37b794d1d3a765dd0d23194ebbe340f013f0c39b613',
+ '9df5c16a3f580406f07d96149303d8c408869b32053b726cf3defd241e484957',
+ '672b54e43f41ee77584bdf8bf854d97b6252c918f7ea2d26bc4097ea53a88f10',
+ 'feeb4b2b59fec8fdb1e55194a493d8c871757b5723675e93d3ac034b380b7fc9',
+ '76e3acbc718836f2df8ad2d0d2d76f0cfa5fea0986be918f10bcee730df441b9',
+ '6733809c73e53666c735b3bd3daf87ebc77c72756150a616a194108d71231272',
+ '0e6e3c143c3a5f7f38505ed6adc9b48c18edf6dedf11635f6e8f9ac73c39fe9e',
+ 'ffb4fc03e054f8ecbc31470fc023bedcd4a406b9dd56c71da1b660dcc4842c65',
+ 'c644612cd326b38b1c6813b1daded34448805aef317c35f548dfb4a0d74b8106',
+ 'c0e29eeeb0d3a7707947e623cdc7d1899adc70dd7861205ea5e5813954fb7957',
+ 'a4139b74b102cf1e2fce229a6cd84c87501f50afa4c80feacf7d8cf5ed94f042',
+ '4f44c1c7fbebb6f9601829f3897bfd650c56fa07844be76489076356ac1886a4',
+ 'b31ad3cd02b10db282b3576c059b746fb24ca6f09fef69402dc90ece7421cbb7',
+ '1c38bf6bbfd32292d67d1d651fd9d5b623b6ec1e854406223f51d0df46968712',
+ 'c2684c0dbb85c232b6da4fb5147dd0624429ec7e657991edd95eda37a587269e',
+ 'bf9d5e5b5393053f055b380baed7e792ae85ad37c0ada5fd4519542ccc461cf3',
+ 'd1f8bd684001ac5a4b67bbf79f87de524d2da99ac014dec3e4187728f4557471',
+ '49ba38db85c2796f85ffd57dd5ec337007414528ae33935b102d16a6b91ba6c1',
+ '725e6f8d888ebaf908b7692259ab8839c3248edd22ca115bb13e025808654700',
+ '32caef024f84e97c30b4a7b9d04b678b3d8a6eb2259dff5b7f7c011f090845f8',
+ '4bb33e7c6916e08a9b3ed6bcef790aaaee0dcf2e7a01afb056182dea2dad7d63',
+ '3ac7ac6bed82fdc8cd15b746f0ee7489158192c238f371c1883c9fe90b3e2831',
+ 'bfce809534eefe871273964d32f091fe756c71a7f512ef5f2300bcd57f699e74',
+ '1d26f3e04f89b4eaa9dbed9231bb051eef2e8311ad26fe53d0bf0b821eaf7567',
+ '0ffeb644a49e787ccc6970fe29705a4f4c2bfcfe7d19741c158333ff6982cc9c',
+ 'd048ee1524014adf9a56e60a388277de194c694cc787fc5a1b554ea9f07abfdf',
+ '50dbf40066f8d270484ee2ef6632282dfa300a85a8530eceeb0e04275e1c1efd',
+ '7c5d14ed83dab875ac25ce7feed6ef837d58e79dc601fb3c1fca48d4464e8b83',
+ '7d53eccd03da37bf58c1962a8f0f708a5c5c447f6a7e9e26137c169d5bdd82e4',
+ '99dc772e91ea02d9e421d552d61901016b9fd4ad2df4a8212c1ec5ba13893ab2',
+ 'cefdae1a3d75e792e8698d5e71f177cc761314e9ad5df9602c6e60ae65c4c267',
+ 'c99d64fa4dadd4bc8a389531c68b4590c6df0b9099c4d583bc00889fb7b98008',
+ '4d12a849047c6acd4b2eee6be35fa9051b02d21d50d419543008c1d82c427072',
+ 'f8e4ccab6c979229f6066cc0cb0cfa81bb21447c16c68773be7e558e9f9d798d',
+ '6595a2ef537a69ba8583dfbf7f5bec0ab1f93ce4c8ee1916eff44a93af5749c4',
+ 'cfb88d6faf2de3a69d36195acec2e255e2af2b7d933997f348e09f6ce5758360',
+ '4d54b2d284a6794581224e08f675541c8feab6eefa3ac1cfe5da4e03e62f72e4',
+ 'dba490256c9720c54c612a5bd1ef573cd51dc12b3e7bd8c6db2eabe0aacb846b',
+ '02804978eba6e1de65afdbc6a6091ed6b1ecee51e8bff40646a251de6678b7ef',
+ '0b66c8b4fefebc8dc7da0bbedc1114f228aa63c37d5c30e91ab500f3eadfcec5',
+ 'c464a7bf6d180de4f744bb2fe5dc27a3f681334ffd54a9814650e60260a478e3',
+ 'd6859c0b5a0b66376a24f56b2ab104286ed0078634ba19112ace0d6d60a9c1ae',
+ '18041bd4665083001fba8c5411d2d748e8abbfdcdfd9218cb02b68a78e7d4c23',
+ '42e61e174fbb3897d6dd6cef3dd2802fe67b331953b06114a65c772859dfc1aa',
+ '3c593aa539fdcdae516cdf2f15000f6634185c88f505b39775fb9ab137a10aa2',
+ '46500b6ae1ab40bde097ef168b0f3199049b55545a1588792d39d594f493dca7',
+ '5f4e16a72d6c9857da0ba009ccacd4f26d7f6bf6c1b78a2ed35e68fcb15b8e40',
+ '044d823532092c22a4b48181cfb2c796e1f5b98bcd713a21f70b5afcceef1d73',
+ 'db593a375cb27df689cd78b5154949e5bc30094a05d704c0295d547385176662',
+ '0599f88c429a3d4fcbb0206fa57e344121afdf8e56f78e3f5e61ba3bcf134ec6',
+ '6c83f9b69754facc3155da93261ed99c38e4225e748e8ebcd04ed62719fa56db',
+ 'f574ac85532bc0c6c4e7614a2e084dbc49fbc474cda593144af28c5cc5f293f8',
+ '19636dfc80fef6d47c7ab8fa620909ccc387126cec56415c9a898f64be728515',
+ '3380c8dae5c0b68bb264b757e2451c21cbe2b899fe7a871ab1bae6041f48e7ad',
+ 'c31bc10bed1384826cc30369b2d0b5880422e1a34d0eea0b67f29f40de17ba46',
+ 'c3cd7be2de832774c614ccf60d030d75dfacf3cc7e49a37af349a4c3c196b106',
+ '888e223d5a497fc679c3ecfe98bf7dc531a4adf3ccf0e6d586c8912ebf781af1',
+ 'e65812200409ad7e1684a2df8e15685dfab7079449c52d032870d80acceab3f6',
+ '2916d4595a3ede77f4165357977cf3529c672dcf4c39e76ec3aa848dba6ff4f6',
+ 'df5f9f0898e0fa1bd9c3d3196fa8f7e6b01331d11eb214f7e5629bb7a1b7eb20',
+ '46d6071814544b8de5db52d4b4d22023ba2e630146ef4e47b9b280341985f189',
+ '0a147f33ab036e8ae148061028c6a557e2eeb1a6ea71b3760548734942743557',
+ '07ddd5dafcf04956cc36c1ff290f07c1c0e5832cc8dd9aea502da677ea04fe64',
+ '2ace8ba5195f54a7c501234431e99232dbb1d1365edbb593a3dd3b5810326570',
+ '4c7118050c64cb293f54c5cc199e99aa87c0a7aaeb7256af498e82d535b994c7',
+ '906c5b84ec1e480195860d89f859fc7d3c5f67f585ef8b738ffec08a3c07a98b',
+ '09247dc00c0060232407a4e69050b5112c9f72a65d7b0ff077f6be180c482cdb',
+ '7b2e8b28951a825924aed1b26e9c197ec080558a97120f34d6e22e341a90c978',
+ 'a5e93544e86eb9960dcfcebb6bcc461d82f119cae1947e340c7cea1c7f351c0b',
+ 'c525eef8b2ca56547565c947bb7e964e2ecae7c9c82c29228b6c932d2ace181c',
+ '31600a05842b12ea927796eafa30e6b1634a97f9bb41a2f75abbb2aa921c17c3',
+ '7ce7f53dc2287da4cf28c9fe64d5515e484c9cc57fd81ec76e66fa38b760565e',
+ 'e026d0e1228ef882d093fe4dbb2ec5134dd122877ac2b380d399bff447fc9fa1',
+ 'cd26132e2c223d19d3a75ae0664f7475b478695d7824dad856c19417ea0b3794',
+ '176b0c71e213031a29f56009aac7ebec591ba24a8b162d80506b2df8f59e11a2',
+ '36423179904261f57bf7405853a319058065857e67a510128baf09a68c30b987',
+ '54290349fbb1e8327a65b871f3fc2c6d3975775e48dd1d7b2c368142bcfc8c27',
+ '683712362407cefd2968ce6373cbd86c1a6170493c84025be740129120d327bc',
+ '76e3a0221b6d29a43a0c2929baaf46ab00b85571d59ef2b3f0facb315621f4ec',
+ 'a7c4cff2f73c911d7e3f2f82b20adb9cf2caafc9254cf5997215a11046846d0e',
+ '977495dc59e74389b65ee1a7a33295014abdcf7916f9e0d1ca39a7cd395e6c41',
+ '6a5f09b3e0a8ae5d795f2dbed00ee521aed5b0875d2e487a82b2c687b527c278',
+ '5ba431851b1e2be373d818c3c6884e53d82273219c3f1c36c9aae099fa6690e1',
+ 'd305c4ce0161386004c267eaa17180eb2433280716c894ed4094c2597a582da1',
+ 'f98918c63e3a9238e78dbd5bebe4e47eaeec0ae1627387dcd2a5ae4725f7e47c',
+ 'cf17b0770212e87516c080aad008d50cb5481044626a325be730d54a66f66662',
+ '10e88348b55c5c0683f4d4d3ef56c485be9888bf00806040de25204d25df4ea6',
+ 'd46ef45eb47aa54032fc8ea47150d10334b208dc6b7ac5e09e8718231e87cd1c',
+ '982c20c2493fc9ae405b74b65a022662c014a38ef3d707217e56e57afac05994',
+ '8e28867538bc2c6c94d3428f05b1458f428d3f950430b09238209efe6bb267d9',
+ '022aa46f368252ce0a7b2431d55ac4767455865dfe65d2e372f4e82691a14cb2',
+ '5d1f1f7c14e34f79468bb5de195a60f3b422c4e48757facf1df01d1b022e6764',
+ '6025dc79681355ec9f3886a74b39dc4d1d2e6c77180080e9d296e5ca7742d04e',
+ 'f52b3c537f28d89f0df1efee21c70f74df186f3928296d19582d5c51286e98bc',
+ '1ca0be9286023fb0b947f07cad056e59cff9d2d16c7cefdbedc33950a9312685',
+ 'd8101ed4097b4bde7abbc16cd854e4c122460dbbabf08a9f56f4f2b882f59b00',
+ '9570f18459f97be85bfc8fca837e0891ef248ba6295119679280a136d60e57f2',
+ '8ff4c479d1230d8dc53493395e89ca712533b80e1b97cb5af448e0e78fab0f7a',
+ 'c4558c7ec68df61d6bb65238397d49cc320a8c213f7bffdd4a397552d83ec20e',
+ '7ebc665ab5e5a1babbbae9e86bd00a09bfe68c4ca91b9f0da092c853c7732c3f',
+ 'cecddb12b508e6cddcf3e96635abec8bc6031d588b21a4a4859cbdd79aaee47a',
+ '03deb53fbacc9e3701311efbff2ee0566c27355b6f30a22848a5b8618f6c0d63',
+ '0b6180f72608560023802ef42e0d80f862759a2a6b107667d7819e07bef00b08',
+ '71b950c0085388ddf90444c0918d72aa700319e789441fcd2da539c12a32ee19',
+ 'd5ebd0d3d544e46023979d06b666f35758b69628d95abb808fa65f51f03b81bf',
+ '740e25c81e510d27735af90e3f8091596092c8136edb60f4df910f7204c289d5',
+ '90df9cc3a3b904415331eba9cd52750c2c5cb73cb91b42caca7eee3788fc2b30',
+ '33b6229592ca719e4e46f35b287617fedadd3b7c38be3c8c1c9f446d2d9085b3',
+];
diff --git a/pkgs/crypto/test/sha512_test.dart b/pkgs/crypto/test/sha512_test.dart
new file mode 100644
index 0000000..07ad788
--- /dev/null
+++ b/pkgs/crypto/test/sha512_test.dart
@@ -0,0 +1,104 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:crypto/crypto.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('SHA2-384', () {
+ group('with a chunked converter', () {
+ test('add may not be called after close', () {
+ var sink =
+ sha384.startChunkedConversion(StreamController<Digest>().sink);
+ sink.close();
+ expect(() => sink.add([0]), throwsStateError);
+ });
+
+ test('close may be called multiple times', () {
+ var sink =
+ sha384.startChunkedConversion(StreamController<Digest>().sink);
+ sink.close();
+ sink.close();
+ sink.close();
+ sink.close();
+ });
+
+ test('close closes the underlying sink', () {
+ var inner = ChunkedConversionSink<Digest>.withCallback(
+ expectAsync1((accumulated) {
+ expect(accumulated.length, equals(1));
+ expect(
+ accumulated.first.toString(),
+ equals(
+ '38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b'),
+ );
+ }));
+
+ var outer = sha384.startChunkedConversion(inner);
+ outer.close();
+ });
+ });
+ });
+
+ group('SHA2-512', () {
+ group('with a chunked converter', () {
+ test('add may not be called after close', () {
+ var sink =
+ sha512.startChunkedConversion(StreamController<Digest>().sink);
+ sink.close();
+ expect(() => sink.add([0]), throwsStateError);
+ });
+
+ test('close may be called multiple times', () {
+ var sink =
+ sha512.startChunkedConversion(StreamController<Digest>().sink);
+ sink.close();
+ sink.close();
+ sink.close();
+ sink.close();
+ });
+
+ test('close closes the underlying sink', () {
+ var inner = ChunkedConversionSink<Digest>.withCallback(
+ expectAsync1((accumulated) {
+ expect(accumulated.length, equals(1));
+ expect(
+ accumulated.first.toString(),
+ equals(
+ 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e'));
+ }));
+
+ var outer = sha512.startChunkedConversion(inner);
+ outer.close();
+ });
+ });
+
+ test('128 bit padding', () {
+ final salts = [
+ 'AAAA{3FXhiiyc5gGWlRrVQ2RlJ.6xj.DKvf6l0bJxqh0BzA}'.codeUnits,
+ 'AAAA{3FXhiiyc5gGWlRrVQ.2RlJ6xj.DKvf6l0bJxqh0BzA}'.codeUnits,
+ 'AAAA{rFXhiiyc5gGWlVQ.2RlJ6xj.DKvf6lFXhiiyc5gGWl0}'.codeUnits,
+ ];
+
+ const results = [
+ 'nYg7eEsF/P7/l1AO0w8JFNNomS1gC76VE7Eg7Dpet+Dh6XiScDntYEU4tVItXp67evaLFvtMpW2uVJBZVKrBPw==',
+ 'TXNM4uk1Iwr2cYisWSdFifXdjfNiJTGEmNaMtqYrwJoS3JXpL1rebPKPfKudbFQGpcgJkLLhhpfnLzULBqq8KA==',
+ 'ckPYMDuPJjc73qHXQZiJgCskNG8mj9cPqFNsqYqxcBbQESgkWChoibAN7ssJrnoMFIpz9HwsBwMtt3z/KDUh9w==',
+ ];
+
+ for (var i = 0; i < salts.length; i++) {
+ var digest = <int>[];
+ for (var run = 0; run < 2000; run++) {
+ digest = sha512.convert([...digest, ...salts[i]]).bytes;
+ }
+ expect(base64.encode(digest), results[i]);
+ }
+ });
+ });
+}
diff --git a/pkgs/crypto/test/sha_monte_test.dart b/pkgs/crypto/test/sha_monte_test.dart
new file mode 100644
index 0000000..ae830a9
--- /dev/null
+++ b/pkgs/crypto/test/sha_monte_test.dart
@@ -0,0 +1,119 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'dart:typed_data';
+
+import 'package:crypto/crypto.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+// See https://csrc.nist.gov/Projects/cryptographic-algorithm-validation-program/Secure-Hashing
+
+void main() {
+ group('Monte Vectors', () {
+ monteTest(
+ 'sha224',
+ sha224,
+ 'ed2b70d575d9d0b4196ae84a03eed940057ea89cdd729b95b7d4e6a5',
+ [
+ 'cd94d7da13c030208b2d0d78fcfe9ea22fa8906df66aa9a1f42afa70',
+ '555846e884633639565d5e0c01dd93ba58edb01ee18e68ccca28f7b8',
+ '44d5f4a179b33231f24cc209ed2542ddb931391f2a2d604f80ed460b',
+ '18678e3c151f05f92a89fc5b2ec56bfc6fafa66d73ffc1937fcab4d0',
+ 'b285f829b0499ff45f8454eda2d4e0997b3f438c2728f1a25cfbb05a',
+ ],
+ );
+ monteTest(
+ 'sha256',
+ sha256,
+ '6d1e72ad03ddeb5de891e572e2396f8da015d899ef0e79503152d6010a3fe691',
+ [
+ 'e93c330ae5447738c8aa85d71a6c80f2a58381d05872d26bdd39f1fcd4f2b788',
+ '2e78f8c8772ea7c9331d41ed3f9cdf27d8f514a99342ee766ee3b8b0d0b121c0',
+ 'd6a23dff1b7f2eddc1a212f8a218397523a799b07386a30692fd6fe9d2bf0944',
+ 'fb0099a964fad5a88cf12952f2991ce256a4ac3049f3d389c3b9e6c00e585db4',
+ 'f9eba2a4cf6263826beaf6150057849eb975a9513c0b76ecad0f1c19ebbad89b',
+ ],
+ );
+ monteTest(
+ 'sha384',
+ sha384,
+ 'edff07255c71b54a9beae52cdfa083569a08be89949cbba73ddc8acf429359ca5e5be7a673633ca0d9709848f522a9df',
+ [
+ 'e81b86c49a38feddfd185f71ca7da6732a053ed4a2640d52d27f53f9f76422650b0e93645301ac99f8295d6f820f1035',
+ '1d6bd21713bffd50946a10c39a7742d740e8f271f0c8f643d4c95375094fd9bf29d89ee61a76053f22e44a4b058a64ed',
+ '425167b66ae965bd7d68515b54ebfa16f33d2bdb2147a4eac515a75224cd19cea564d692017d2a1c41c1a3f68bb5a209',
+ '9e7477ffd4baad1fcca035f4687b35ed47a57832fb27d131eb8018fcb41edf4d5e25874466d2e2d61ae3accdfc7aa364',
+ 'd7b4d4e779ca70c8d065630db1f9128ee43b4bde08a81bce13d48659b6ef47b6cfc802af6d8756f6cd43c709bb445bab',
+ ],
+ );
+ monteTest(
+ 'sha512',
+ sha512,
+ '5c337de5caf35d18ed90b5cddfce001ca1b8ee8602f367e7c24ccca6f893802fb1aca7a3dae32dcd60800a59959bc540d63237876b799229ae71a2526fbc52cd',
+ [
+ 'ada69add0071b794463c8806a177326735fa624b68ab7bcab2388b9276c036e4eaaff87333e83c81c0bca0359d4aeebcbcfd314c0630e0c2af68c1fb19cc470e',
+ 'ef219b37c24ae507a2b2b26d1add51b31fb5327eb8c3b19b882fe38049433dbeccd63b3d5b99ba2398920bcefb8aca98cd28a1ee5d2aaf139ce58a15d71b06b4',
+ 'c3d5087a62db0e5c6f5755c417f69037308cbce0e54519ea5be8171496cc6d18023ba15768153cfd74c7e7dc103227e9eed4b0f82233362b2a7b1a2cbcda9daf',
+ 'bb3a58f71148116e377505461d65d6c89906481fedfbcfe481b7aa8ceb977d252b3fe21bfff6e7fbf7575ceecf5936bd635e1cf52698c36ef6908ddbd5b6ae05',
+ 'b68f0cd2d63566b3934a50666dec6d62ca1db98e49d7733084c1f86d91a8a08c756fa7ece815e20930dd7cb66351bad8c087c2f94e8757cb98e7f4b86b21a8a8',
+ ],
+ );
+
+ monteTest(
+ 'sha512/224',
+ sha512224,
+ '2e325bf8c98c0be54493d04c329e706343aebe4968fdd33b37da9c0a',
+ [
+ '9ee006873962aa0842d636c759646a4ef4b65bcbebcc35430b20f7f4',
+ '87726eda4570734b396f4c253146ecb9770b8591739240f02a4f2a02',
+ '7be0871653db5fa514b4ec1a0363df004657155575b0383bc9fdec35',
+ '7a794a3a1ae255e67ffbf688a05b6aba7f231cebec64b4fc75092d49',
+ 'aaf5d4ecaf9426149821b15821b41c49e3900c0fc91664fb294216ea',
+ ],
+ );
+
+ monteTest(
+ 'sha512/256',
+ sha512256,
+ 'f41ece2613e4573915696b5adcd51ca328be3bf566a9ca99c9ceb0279c1cb0a7',
+ [
+ 'b1d97a6536896aa01098fb2b9e15d8692621c84077051fc1f70a8a48baa6dfaf',
+ 'a008d2c5adce31a95b30397ac691d8606c6769a47b801441ba3afb7f727c8a9c',
+ '8eb896cb2b309db019121eb72564b89c1a59f74d4e2f2f6773c87b98c1997d77',
+ 'ac71b694438cc300dde0f6f9f548d2304e2bdb6ea45e2b305af5fb3e4ec27761',
+ 'd47cca4ae027778fc285bc78fb2a9c1cc7cde498267c35157e86b05fc58e698d',
+ ],
+ );
+ });
+}
+
+void monteTest(String name, Hash hash, String seed, List<String> expected) {
+ test(name, () {
+ Iterable<String> run() sync* {
+ var seedBytes = bytesFromHexString(seed);
+ for (var j = 0; j < expected.length; j++) {
+ Uint8List md0, md1, md2;
+ md0 = Uint8List.fromList(seedBytes);
+ md1 = Uint8List.fromList(seedBytes);
+ md2 = Uint8List.fromList(seedBytes);
+ late Digest mdI;
+ for (var i = 3; i < 1003; i++) {
+ var mI = [...md0, ...md1, ...md2];
+ mdI = hash.convert(mI);
+ md0.setAll(0, md1);
+ md1.setAll(0, md2);
+ md2.setAll(0, mdI.bytes);
+ }
+ yield '$mdI';
+ seedBytes.setAll(0, md2);
+ }
+ }
+
+ expect(run().toList(), expected);
+ });
+}
diff --git a/pkgs/crypto/test/utils.dart b/pkgs/crypto/test/utils.dart
new file mode 100644
index 0000000..2e46d40
--- /dev/null
+++ b/pkgs/crypto/test/utils.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:typed_data';
+
+import 'package:crypto/crypto.dart';
+import 'package:test/test.dart';
+
+/// Asserts that an HMAC using [hash] returns [mac] for [input] and [key].
+void expectHmacEquals(Hash hash, List<int> input, List<int> key, String mac) {
+ var hmac = Hmac(hash, key);
+ expect(hmac.convert(input).toString(), startsWith(mac));
+}
+
+final toupleMatch = RegExp('([0-9a-fA-F]{2})');
+
+Uint8List bytesFromHexString(String message) {
+ var bytes = <int>[];
+ for (var match in toupleMatch.allMatches(message)) {
+ bytes.add(int.parse(match.group(0)!, radix: 16));
+ }
+ return Uint8List.fromList(bytes);
+}
diff --git a/pkgs/fixnum/.gitignore b/pkgs/fixnum/.gitignore
new file mode 100644
index 0000000..49ce72d
--- /dev/null
+++ b/pkgs/fixnum/.gitignore
@@ -0,0 +1,3 @@
+.dart_tool/
+.packages
+pubspec.lock
diff --git a/pkgs/fixnum/AUTHORS b/pkgs/fixnum/AUTHORS
new file mode 100644
index 0000000..3d772aa
--- /dev/null
+++ b/pkgs/fixnum/AUTHORS
@@ -0,0 +1,9 @@
+# Names should be added to this file with this pattern:
+#
+# For individuals:
+# Name <email address>
+#
+# For organizations:
+# Organization <fnmatch pattern>
+#
+Google Inc. <*@google.com>
\ No newline at end of file
diff --git a/pkgs/fixnum/CHANGELOG.md b/pkgs/fixnum/CHANGELOG.md
new file mode 100644
index 0000000..f3e5c01
--- /dev/null
+++ b/pkgs/fixnum/CHANGELOG.md
@@ -0,0 +1,69 @@
+## 1.1.1
+
+* Require Dart `^3.1.0`
+* Move to `dart-lang/core` monorepo.
+
+## 1.1.0
+
+* Add `tryParseRadix`, `tryParseInt` and `tryParseHex` static methods
+ to both `Int32` and `Int64`.
+* Document exception and overflow behavior of parse functions,
+ and of `toHexString`.
+* Make `Int32` parse functions consistent with documentation (accept
+ leading minus sign, do not accept empty inputs).
+* Update the minimum SDK constraint to 2.19.
+* Update to package:lints 2.0.0.
+
+## 1.0.1
+
+* Switch to using `package:lints`.
+* Populate the pubspec `repository` field.
+
+## 1.0.0
+
+* Stable null safety release.
+
+## 1.0.0-nullsafety.0
+
+* Migrate to null safety.
+ * This is meant to be mostly non-breaking, for opted in users runtime errors
+ will be promoted to static errors. For non-opted in users the runtime
+ errors are still present in their original form.
+
+## 0.10.11
+
+* Update minimum SDK constraint to version 2.1.1.
+
+## 0.10.10
+
+* Fix `Int64` parsing to throw `FormatException` on an empty string or single
+ minus sign. Previous incorrect behaviour was to throw a `RangeError` or
+ silently return zero.
+
+## 0.10.9
+
+* Add `Int64.toStringUnsigned()` and `Int64.toRadixStringUnsigned()` functions.
+
+## 0.10.8
+
+* Set SDK version constraint to `>=2.0.0-dev.65 <3.0.0`.
+
+## 0.10.7
+
+* Bug fix: Make bit shifts work at bitwidth boundaries. Previously,
+ `new Int64(3) << 64 == Int64(3)`. This ensures that the result is 0 in such
+ cases.
+* Updated maximum SDK constraint from 2.0.0-dev.infinity to 2.0.0.
+
+## 0.10.6
+
+* Fix `Int64([int value])` constructor to avoid rounding error on intermediate
+ results for large negative inputs when compiled to JavaScript. `new
+ Int64(-1000000000000000000)` used to produce the same value as
+ `Int64.parseInt("-1000000000000000001")`
+
+## 0.10.5
+
+* Fix strong mode warning in overridden `compareTo()` methods.
+
+*No changelog entries for previous versions...*
diff --git a/pkgs/fixnum/LICENSE b/pkgs/fixnum/LICENSE
new file mode 100644
index 0000000..162572a
--- /dev/null
+++ b/pkgs/fixnum/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/fixnum/README.md b/pkgs/fixnum/README.md
new file mode 100644
index 0000000..47aac7b
--- /dev/null
+++ b/pkgs/fixnum/README.md
@@ -0,0 +1,9 @@
+[](https://github.com/dart-lang/core/actions/workflows/fixnum.yaml)
+[](https://pub.dev/packages/fixnum)
+[](https://pub.dev/packages/fixnum/publisher)
+
+A fixed-width 32- and 64- bit integer library for Dart.
+
+Provides data types for signed 32- and 64-bit integers.
+The integer implementations in this library are designed to work identically
+whether executed on the Dart VM or compiled to JavaScript.
diff --git a/pkgs/fixnum/analysis_options.yaml b/pkgs/fixnum/analysis_options.yaml
new file mode 100644
index 0000000..e53700b
--- /dev/null
+++ b/pkgs/fixnum/analysis_options.yaml
@@ -0,0 +1,20 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+
+linter:
+ rules:
+ - avoid_private_typedef_functions
+ - avoid_unused_constructor_parameters
+ - cancel_subscriptions
+ - cascade_invocations
+ - join_return_with_assignment
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - unnecessary_raw_strings
+ - use_string_buffers
diff --git a/pkgs/fixnum/lib/fixnum.dart b/pkgs/fixnum/lib/fixnum.dart
new file mode 100644
index 0000000..6991645
--- /dev/null
+++ b/pkgs/fixnum/lib/fixnum.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Signed 32- and 64-bit integer support.
+///
+/// The integer implementations in this library are designed to work
+/// identically whether executed on the Dart VM or compiled to JavaScript.
+library;
+
+export 'src/int32.dart';
+export 'src/int64.dart';
+export 'src/intx.dart';
diff --git a/pkgs/fixnum/lib/src/int32.dart b/pkgs/fixnum/lib/src/int32.dart
new file mode 100644
index 0000000..8045bc1
--- /dev/null
+++ b/pkgs/fixnum/lib/src/int32.dart
@@ -0,0 +1,467 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: constant_identifier_names
+
+import 'int64.dart';
+import 'intx.dart';
+import 'utilities.dart' as u;
+
+/// An immutable 32-bit signed integer, in the range [-2^31, 2^31 - 1].
+/// Arithmetic operations may overflow in order to maintain this range.
+class Int32 implements IntX {
+ /// The maximum positive value attainable by an [Int32], namely
+ /// 2147483647.
+ static const Int32 MAX_VALUE = Int32._internal(0x7FFFFFFF);
+
+ /// The minimum positive value attainable by an [Int32], namely
+ /// -2147483648.
+ static const Int32 MIN_VALUE = Int32._internal(-0x80000000);
+
+ /// An [Int32] constant equal to 0.
+ static const Int32 ZERO = Int32._internal(0);
+
+ /// An [Int32] constant equal to 1.
+ static const Int32 ONE = Int32._internal(1);
+
+ /// An [Int32] constant equal to 2.
+ static const Int32 TWO = Int32._internal(2);
+
+ // Mask to 32-bits.
+ static const int _MASK_U32 = 0xFFFFFFFF;
+
+ /// Parses [source] in a given [radix] between 2 and 36.
+ ///
+ /// Returns an [Int32] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 32 bit integer,
+ /// the numerical value is truncated to the lowest 32 bits
+ /// of the value's binary representation,
+ /// interpreted as a 32-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of base-[radix]
+ /// digits (using letters from `a` to `z` as digits with values 10 through
+ /// 25 for radixes above 10), possibly prefixed by a `-` sign.
+ ///
+ /// Throws a [FormatException] if the input is not a valid
+ /// integer numeral in base [radix].
+ static Int32 parseRadix(String source, int radix) =>
+ _parseRadix(source, u.validateRadix(radix), true)!;
+
+ /// Parses [source] in a given [radix] between 2 and 36.
+ ///
+ /// Returns an [Int32] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 32 bit integer,
+ /// the numerical value is truncated to the lowest 32 bits
+ /// of the value's binary representation,
+ /// interpreted as a 32-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of base-[radix]
+ /// digits (using letters from `a` to `z` as digits with values 10 through
+ /// 25 for radixes above 10), possibly prefixed by a `-` sign.
+ ///
+ /// Throws a [FormatException] if the input is not a valid
+ /// integer numeral in base [radix].
+ static Int32? tryParseRadix(String source, int radix) =>
+ _parseRadix(source, u.validateRadix(radix), false);
+
+ // TODO(rice) - Make this faster by converting several digits at once.
+ static Int32? _parseRadix(String s, int radix, bool throwOnError) {
+ var index = 0;
+ var negative = false;
+ if (s.startsWith('-')) {
+ negative = true;
+ index = 1;
+ }
+ if (index == s.length) {
+ if (!throwOnError) return null;
+ throw FormatException('No digits', s, index);
+ }
+ var result = 0;
+ for (; index < s.length; index++) {
+ var c = s.codeUnitAt(index);
+ var digit = u.decodeDigit(c);
+ if (digit < radix) {
+ /// Doesn't matter whether the result is unsigned
+ /// or signed (as on the web), only the bits matter
+ /// to the [Int32.new] constructor.
+ result = (result * radix + digit) & _MASK_U32;
+ } else {
+ if (!throwOnError) return null;
+ throw FormatException('Non radix code unit', s, index);
+ }
+ }
+ if (negative) result = -result;
+ return Int32(result);
+ }
+
+ /// Parses [source] as a decimal numeral.
+ ///
+ /// Returns an [Int32] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 32 bit integer,
+ /// the numerical value is truncated to the lowest 32 bits
+ /// of the value's binary representation,
+ /// interpreted as a 32-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of digits (`0`-`9`),
+ /// possibly prefixed by a `-` sign.
+ ///
+ /// Throws a [FormatException] if the input is not a valid
+ /// decimal integer numeral.
+ static Int32 parseInt(String source) => _parseRadix(source, 10, true)!;
+
+ /// Parses [source] as a decimal numeral.
+ ///
+ /// Returns an [Int32] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 32 bit integer,
+ /// the numerical value is truncated to the lowest 32 bits
+ /// of the value's binary representation,
+ /// interpreted as a 32-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of digits (`0`-`9`),
+ /// possibly prefixed by a `-` sign.
+ ///
+ /// Throws a [FormatException] if the input is not a valid
+ /// decimal integer numeral.
+ static Int32? tryParseInt(String source) => _parseRadix(source, 10, false);
+
+ /// Parses [source] as a hexadecimal numeral.
+ ///
+ /// Returns an [Int32] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 32 bit integer,
+ /// the numerical value is truncated to the lowest 32 bits
+ /// of the value's binary representation,
+ /// interpreted as a 32-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of hexadecimal
+ /// digits (`0`-`9`, `a`-`f` or `A`-`F`), possibly prefixed by a `-` sign.
+ ///
+ /// Returns `null` if the input is not a valid
+ /// hexadecimal integer numeral.
+ static Int32 parseHex(String source) => _parseRadix(source, 16, true)!;
+
+ /// Parses [source] as a hexadecimal numeral.
+ ///
+ /// Returns an [Int32] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 32 bit integer,
+ /// the numerical value is truncated to the lowest 32 bits
+ /// of the value's binary representation,
+ /// interpreted as a 32-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of hexadecimal
+ /// digits (`0`-`9`, `a`-`f` or `A`-`F`), possibly prefixed by a `-` sign.
+ ///
+ /// Returns `null` if the input is not a valid
+ /// hexadecimal integer numeral.
+ static Int32? tryParseHex(String source) => _parseRadix(source, 16, false);
+
+ // The internal value, kept in the range [MIN_VALUE, MAX_VALUE].
+ final int _i;
+
+ const Int32._internal(int i) : _i = i;
+
+ /// Constructs an [Int32] from an [int]. Only the low 32 bits of the input
+ /// are used.
+ Int32([int i = 0]) : _i = (i & 0x7fffffff) - (i & 0x80000000);
+
+ // Returns the [int] representation of the specified value. Throws
+ // [ArgumentError] for non-integer arguments.
+ int _toInt(Object val) {
+ if (val is Int32) {
+ return val._i;
+ } else if (val is int) {
+ return val;
+ }
+ throw ArgumentError.value(val, 'other', 'Not an int, Int32 or Int64');
+ }
+
+ // The +, -, * , &, |, and ^ operaters deal with types as follows:
+ //
+ // Int32 + int => Int32
+ // Int32 + Int32 => Int32
+ // Int32 + Int64 => Int64
+ //
+ // The %, ~/ and remainder operators return an Int32 even with an Int64
+ // argument, since the result cannot be greater than the value on the
+ // left-hand side:
+ //
+ // Int32 % int => Int32
+ // Int32 % Int32 => Int32
+ // Int32 % Int64 => Int32
+
+ @override
+ IntX operator +(Object other) {
+ if (other is Int64) {
+ return toInt64() + other;
+ }
+ return Int32(_i + _toInt(other));
+ }
+
+ @override
+ IntX operator -(Object other) {
+ if (other is Int64) {
+ return toInt64() - other;
+ }
+ return Int32(_i - _toInt(other));
+ }
+
+ @override
+ Int32 operator -() => Int32(-_i);
+
+ @override
+ IntX operator *(Object other) {
+ if (other is Int64) {
+ return toInt64() * other;
+ }
+ // TODO(rice) - optimize
+ return (toInt64() * other).toInt32();
+ }
+
+ @override
+ Int32 operator %(Object other) {
+ if (other is Int64) {
+ // Result will be Int32
+ return (toInt64() % other).toInt32();
+ }
+ return Int32(_i % _toInt(other));
+ }
+
+ @override
+ Int32 operator ~/(Object other) {
+ if (other is Int64) {
+ return (toInt64() ~/ other).toInt32();
+ }
+ return Int32(_i ~/ _toInt(other));
+ }
+
+ @override
+ Int32 remainder(Object other) {
+ if (other is Int64) {
+ var t = toInt64();
+ return (t - (t ~/ other) * other).toInt32();
+ }
+ return this - (this ~/ other) * other as Int32;
+ }
+
+ @override
+ Int32 operator &(Object other) {
+ if (other is Int64) {
+ return (toInt64() & other).toInt32();
+ }
+ return Int32(_i & _toInt(other));
+ }
+
+ @override
+ Int32 operator |(Object other) {
+ if (other is Int64) {
+ return (toInt64() | other).toInt32();
+ }
+ return Int32(_i | _toInt(other));
+ }
+
+ @override
+ Int32 operator ^(Object other) {
+ if (other is Int64) {
+ return (toInt64() ^ other).toInt32();
+ }
+ return Int32(_i ^ _toInt(other));
+ }
+
+ @override
+ Int32 operator ~() => Int32(~_i);
+
+ @override
+ Int32 operator <<(int n) {
+ if (n < 0) {
+ throw ArgumentError(n);
+ }
+ if (n >= 32) {
+ return ZERO;
+ }
+ return Int32(_i << n);
+ }
+
+ @override
+ Int32 operator >>(int n) {
+ if (n < 0) {
+ throw ArgumentError(n);
+ }
+ if (n >= 32) {
+ return isNegative ? const Int32._internal(-1) : ZERO;
+ }
+ int value;
+ if (_i >= 0) {
+ value = _i >> n;
+ } else {
+ value = (_i >> n) | (0xffffffff << (32 - n));
+ }
+ return Int32(value);
+ }
+
+ @override
+ Int32 shiftRightUnsigned(int n) {
+ if (n < 0) {
+ throw ArgumentError(n);
+ }
+ if (n >= 32) {
+ return ZERO;
+ }
+ int value;
+ if (_i >= 0) {
+ value = _i >> n;
+ } else {
+ value = (_i >> n) & ((1 << (32 - n)) - 1);
+ }
+ return Int32(value);
+ }
+
+ /// Returns [:true:] if this [Int32] has the same numeric value as the
+ /// given object. The argument may be an [int] or an [IntX].
+ @override
+ bool operator ==(Object other) {
+ if (other is Int32) {
+ return _i == other._i;
+ } else if (other is Int64) {
+ return toInt64() == other;
+ } else if (other is int) {
+ return _i == other;
+ }
+ return false;
+ }
+
+ @override
+ int compareTo(Object other) {
+ if (other is Int64) {
+ return toInt64().compareTo(other);
+ }
+ return _i.compareTo(_toInt(other));
+ }
+
+ @override
+ bool operator <(Object other) {
+ if (other is Int64) {
+ return toInt64() < other;
+ }
+ return _i < _toInt(other);
+ }
+
+ @override
+ bool operator <=(Object other) {
+ if (other is Int64) {
+ return toInt64() <= other;
+ }
+ return _i <= _toInt(other);
+ }
+
+ @override
+ bool operator >(Object other) {
+ if (other is Int64) {
+ return toInt64() > other;
+ }
+ return _i > _toInt(other);
+ }
+
+ @override
+ bool operator >=(Object other) {
+ if (other is Int64) {
+ return toInt64() >= other;
+ }
+ return _i >= _toInt(other);
+ }
+
+ @override
+ bool get isEven => (_i & 0x1) == 0;
+
+ @override
+ bool get isMaxValue => _i == 2147483647;
+
+ @override
+ bool get isMinValue => _i == -2147483648;
+
+ @override
+ bool get isNegative => _i < 0;
+
+ @override
+ bool get isOdd => (_i & 0x1) == 1;
+
+ @override
+ bool get isZero => _i == 0;
+
+ @override
+ int get bitLength => _i.bitLength;
+
+ @override
+ int get hashCode => _i;
+
+ @override
+ Int32 abs() => _i < 0 ? Int32(-_i) : this;
+
+ @override
+ Int32 clamp(Object lowerLimit, Object upperLimit) {
+ if (this < lowerLimit) {
+ if (lowerLimit is IntX) return lowerLimit.toInt32();
+ if (lowerLimit is int) return Int32(lowerLimit);
+ throw ArgumentError(lowerLimit);
+ } else if (this > upperLimit) {
+ if (upperLimit is IntX) return upperLimit.toInt32();
+ if (upperLimit is int) return Int32(upperLimit);
+ throw ArgumentError(upperLimit);
+ }
+ return this;
+ }
+
+ @override
+ int numberOfLeadingZeros() => u.numberOfLeadingZeros(_i);
+
+ @override
+ int numberOfTrailingZeros() => u.numberOfTrailingZeros(_i);
+
+ @override
+ Int32 toSigned(int width) {
+ if (width < 1 || width > 32) throw RangeError.range(width, 1, 32);
+ return Int32(_i.toSigned(width));
+ }
+
+ @override
+ Int32 toUnsigned(int width) {
+ if (width < 0 || width > 32) throw RangeError.range(width, 0, 32);
+ return Int32(_i.toUnsigned(width));
+ }
+
+ @override
+ List<int> toBytes() {
+ var result = List<int>.filled(4, 0);
+ result[0] = _i & 0xff;
+ result[1] = (_i >> 8) & 0xff;
+ result[2] = (_i >> 16) & 0xff;
+ result[3] = (_i >> 24) & 0xff;
+ return result;
+ }
+
+ @override
+ double toDouble() => _i.toDouble();
+
+ @override
+ int toInt() => _i;
+
+ @override
+ Int32 toInt32() => this;
+
+ @override
+ Int64 toInt64() => Int64(_i);
+
+ @override
+ String toString() => _i.toString();
+
+ @override
+ String toHexString() => _i.toRadixString(16);
+
+ @override
+ String toRadixString(int radix) => _i.toRadixString(radix);
+}
diff --git a/pkgs/fixnum/lib/src/int64.dart b/pkgs/fixnum/lib/src/int64.dart
new file mode 100644
index 0000000..bbfcbd9
--- /dev/null
+++ b/pkgs/fixnum/lib/src/int64.dart
@@ -0,0 +1,1128 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: constant_identifier_names
+
+// Many locals are declared as `int` or `double`. We keep local variable types
+// because the types are critical to the efficiency of many operations.
+//
+// ignore_for_file: omit_local_variable_types
+
+import 'int32.dart';
+import 'intx.dart';
+import 'utilities.dart' as u;
+
+/// An immutable 64-bit signed integer, in the range [-2^63, 2^63 - 1].
+/// Arithmetic operations may overflow in order to maintain this range.
+class Int64 implements IntX {
+ // A 64-bit integer is represented internally as three non-negative
+ // integers, storing the 22 low, 22 middle, and 20 high bits of the
+ // 64-bit value. _l (low) and _m (middle) are in the range
+ // [0, 2^22 - 1] and _h (high) is in the range [0, 2^20 - 1].
+ //
+ // The values being assigned to _l, _m and _h in initialization are masked to
+ // force them into the above ranges. Sometimes we know that the value is a
+ // small non-negative integer but the dart2js compiler can't infer that, so a
+ // few of the masking operations are not needed for correctness but are
+ // helpful for dart2js code quality.
+
+ final int _l, _m, _h;
+
+ // Note: several functions require _BITS == 22 -- do not change this value.
+ static const int _BITS = 22;
+ static const int _BITS01 = 44; // 2 * _BITS
+ static const int _BITS2 = 20; // 64 - _BITS01
+ static const int _MASK = 4194303; // (1 << _BITS) - 1
+ static const int _MASK2 = 1048575; // (1 << _BITS2) - 1
+ static const int _SIGN_BIT = 19; // _BITS2 - 1
+ static const int _SIGN_BIT_MASK = 1 << _SIGN_BIT;
+
+ /// The maximum positive value attainable by an [Int64], namely
+ /// 9,223,372,036,854,775,807.
+ static const Int64 MAX_VALUE = Int64._bits(_MASK, _MASK, _MASK2 >> 1);
+
+ /// The minimum positive value attainable by an [Int64], namely
+ /// -9,223,372,036,854,775,808.
+ static const Int64 MIN_VALUE = Int64._bits(0, 0, _SIGN_BIT_MASK);
+
+ /// An [Int64] constant equal to 0.
+ static const Int64 ZERO = Int64._bits(0, 0, 0);
+
+ /// An [Int64] constant equal to 1.
+ static const Int64 ONE = Int64._bits(1, 0, 0);
+
+ /// An [Int64] constant equal to 2.
+ static const Int64 TWO = Int64._bits(2, 0, 0);
+
+ /// Constructs an [Int64] with a given bitwise representation. No validation
+ /// is performed.
+ const Int64._bits(this._l, this._m, this._h);
+
+ /// Parses [source] in a given [radix] between 2 and 36.
+ ///
+ /// Returns an [Int64] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 64 bit integer,
+ /// the numerical value is truncated to the lowest 64 bits
+ /// of the value's binary representation,
+ /// interpreted as a 64-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of base-[radix]
+ /// digits (using letters from `a` to `z` as digits with values 10 through
+ /// 25 for radixes above 10), possibly prefixed by a `-` sign.
+ ///
+ /// Throws a [FormatException] if the input is not recognized as a valid
+ /// integer numeral.
+ static Int64 parseRadix(String source, int radix) =>
+ _parseRadix(source, u.validateRadix(radix), true)!;
+
+ /// Parses [source] in a given [radix] between 2 and 36.
+ ///
+ /// Returns an [Int64] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 64 bit integer,
+ /// the numerical value is truncated to the lowest 64 bits
+ /// of the value's binary representation,
+ /// interpreted as a 64-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of base-[radix]
+ /// digits (using letters from `a` to `z` as digits with values 10 through
+ /// 25 for radixes above 10), possibly prefixed by a `-` sign.
+ ///
+ /// Returns `null` if the input is not recognized as a valid
+ /// integer numeral.
+ static Int64? tryParseRadix(String source, int radix) =>
+ _parseRadix(source, u.validateRadix(radix), false);
+
+ static Int64? _parseRadix(String s, int radix, bool throwOnError) {
+ int i = 0;
+ bool negative = false;
+ if (s.startsWith('-')) {
+ negative = true;
+ i++;
+ }
+
+ if (i >= s.length) {
+ if (!throwOnError) return null;
+ throw FormatException('No digits', s, i);
+ }
+
+ int d0 = 0, d1 = 0, d2 = 0; // low, middle, high components.
+ for (; i < s.length; i++) {
+ int c = s.codeUnitAt(i);
+ int digit = u.decodeDigit(c);
+ if (digit < radix) {
+ // [radix] and [digit] are at most 6 bits, component is 22, so we can
+ // multiply and add within 30 bit temporary values.
+ d0 = d0 * radix + digit;
+ int carry = d0 >> _BITS;
+ d0 = _MASK & d0;
+
+ d1 = d1 * radix + carry;
+ carry = d1 >> _BITS;
+ d1 = _MASK & d1;
+
+ d2 = d2 * radix + carry;
+ d2 = _MASK2 & d2;
+ } else {
+ if (!throwOnError) return null;
+ throw FormatException('Not radix digit', s, i);
+ }
+ }
+
+ if (negative) return _negate(d0, d1, d2);
+
+ return Int64._masked(d0, d1, d2);
+ }
+
+ /// Parses [source] as a decimal numeral.
+ ///
+ /// Returns an [Int64] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 64 bit integer,
+ /// the numerical value is truncated to the lowest 64 bits
+ /// of the value's binary representation,
+ /// interpreted as a 64-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of digits (`0`-`9`),
+ /// possibly prefixed by a `-` sign.
+ ///
+ /// Throws a [FormatException] if the input is not a valid
+ /// decimal integer numeral.
+ static Int64 parseInt(String source) => _parseRadix(source, 10, true)!;
+
+ /// Parses [source] as a decimal numeral.
+ ///
+ /// Returns an [Int64] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 64 bit integer,
+ /// the numerical value is truncated to the lowest 64 bits
+ /// of the value's binary representation,
+ /// interpreted as a 64-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of digits (`0`-`9`),
+ /// possibly prefixed by a `-` sign.
+ ///
+ /// Returns `null` if the input is not a valid
+ /// decimal integer numeral.
+ static Int64? tryParseInt(String source) => _parseRadix(source, 10, false);
+
+ /// Parses [source] as a hexadecimal numeral.
+ ///
+ /// Returns an [Int64] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 64 bit integer,
+ /// the numerical value is truncated to the lowest 64 bits
+ /// of the value's binary representation,
+ /// interpreted as a 64-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of hexadecimal
+ /// digits (`0`-`9`, `a`-`f` or `A`-`F`), possibly prefixed by a `-` sign.
+ ///
+ /// Throws a [FormatException] if the input is not a valid
+ /// hexadecimal integer numeral.
+ static Int64 parseHex(String source) => _parseRadix(source, 16, true)!;
+
+ /// Parses [source] as a hexadecimal numeral.
+ ///
+ /// Returns an [Int64] with the numerical value of [source].
+ /// If the numerical value of [source] does not fit
+ /// in a signed 64 bit integer,
+ /// the numerical value is truncated to the lowest 64 bits
+ /// of the value's binary representation,
+ /// interpreted as a 64-bit two's complement integer.
+ ///
+ /// The [source] string must contain a sequence of hexadecimal
+ /// digits (`0`-`9`, `a`-`f` or `A`-`F`), possibly prefixed by a `-` sign.
+ ///
+ /// Returns `null` if the input is not a valid
+ /// hexadecimal integer numeral.
+ static Int64? tryParseHex(String source) => _parseRadix(source, 16, false);
+
+ //
+ // Public constructors
+ //
+
+ /// Constructs an [Int64] with a given [int] value; zero by default.
+ factory Int64([int value = 0]) {
+ int v0 = 0, v1 = 0, v2 = 0;
+ bool negative = false;
+ if (value < 0) {
+ negative = true;
+ value = -value;
+ }
+ // Avoid using bitwise operations that in JavaScript coerce their input to
+ // 32 bits.
+ v2 = value ~/ 17592186044416; // 2^44
+ value -= v2 * 17592186044416;
+ v1 = value ~/ 4194304; // 2^22
+ value -= v1 * 4194304;
+ v0 = value;
+
+ return negative
+ ? Int64._negate(_MASK & v0, _MASK & v1, _MASK2 & v2)
+ : Int64._masked(v0, v1, v2);
+ }
+
+ factory Int64.fromBytes(List<int> bytes) {
+ // 20 bits into top, 22 into middle and bottom.
+ var split1 = bytes[5] & 0xFF;
+ var high =
+ ((bytes[7] & 0xFF) << 12) | ((bytes[6] & 0xFF) << 4) | (split1 >> 4);
+ var split2 = bytes[2] & 0xFF;
+ var middle = (split1 << 18) |
+ ((bytes[4] & 0xFF) << 10) |
+ ((bytes[3] & 0xFF) << 2) |
+ (split2 >> 6);
+ var low = (split2 << 16) | ((bytes[1] & 0xFF) << 8) | (bytes[0] & 0xFF);
+ // Top bits from above will be masked off here.
+ return Int64._masked(low, middle, high);
+ }
+
+ factory Int64.fromBytesBigEndian(List<int> bytes) {
+ var split1 = bytes[2] & 0xFF;
+ var high =
+ ((bytes[0] & 0xFF) << 12) | ((bytes[1] & 0xFF) << 4) | (split1 >> 4);
+ var split2 = bytes[5] & 0xFF;
+ var middle = (split1 << 18) |
+ ((bytes[3] & 0xFF) << 10) |
+ ((bytes[4] & 0xFF) << 2) |
+ (split2 >> 6);
+ var low = (split2 << 16) | ((bytes[6] & 0xFF) << 8) | (bytes[7] & 0xFF);
+ // Top bits from above will be masked off here.
+ return Int64._masked(low, middle, high);
+ }
+
+ /// Constructs an [Int64] from a pair of 32-bit integers having the value
+ /// [:((top & 0xffffffff) << 32) | (bottom & 0xffffffff):].
+ factory Int64.fromInts(int top, int bottom) {
+ top &= 0xffffffff;
+ bottom &= 0xffffffff;
+ int d0 = _MASK & bottom;
+ int d1 = ((0xfff & top) << 10) | (0x3ff & (bottom >> _BITS));
+ int d2 = _MASK2 & (top >> 12);
+ return Int64._masked(d0, d1, d2);
+ }
+
+ // Returns the [Int64] representation of the specified value. Throws
+ // [ArgumentError] for non-integer arguments.
+ static Int64 _promote(Object value) {
+ if (value is Int64) {
+ return value;
+ } else if (value is int) {
+ return Int64(value);
+ } else if (value is Int32) {
+ return value.toInt64();
+ }
+ throw ArgumentError.value(value, 'other', 'not an int, Int32 or Int64');
+ }
+
+ @override
+ Int64 operator +(Object other) {
+ Int64 o = _promote(other);
+ int sum0 = _l + o._l;
+ int sum1 = _m + o._m + (sum0 >> _BITS);
+ int sum2 = _h + o._h + (sum1 >> _BITS);
+ return Int64._masked(sum0, sum1, sum2);
+ }
+
+ @override
+ Int64 operator -(Object other) {
+ Int64 o = _promote(other);
+ return _sub(_l, _m, _h, o._l, o._m, o._h);
+ }
+
+ @override
+ Int64 operator -() => _negate(_l, _m, _h);
+
+ @override
+ Int64 operator *(Object other) {
+ Int64 o = _promote(other);
+
+ // Grab 13-bit chunks.
+ int a0 = _l & 0x1fff;
+ int a1 = (_l >> 13) | ((_m & 0xf) << 9);
+ int a2 = (_m >> 4) & 0x1fff;
+ int a3 = (_m >> 17) | ((_h & 0xff) << 5);
+ int a4 = (_h & 0xfff00) >> 8;
+
+ int b0 = o._l & 0x1fff;
+ int b1 = (o._l >> 13) | ((o._m & 0xf) << 9);
+ int b2 = (o._m >> 4) & 0x1fff;
+ int b3 = (o._m >> 17) | ((o._h & 0xff) << 5);
+ int b4 = (o._h & 0xfff00) >> 8;
+
+ // Compute partial products.
+ // Optimization: if b is small, avoid multiplying by parts that are 0.
+ int p0 = a0 * b0; // << 0
+ int p1 = a1 * b0; // << 13
+ int p2 = a2 * b0; // << 26
+ int p3 = a3 * b0; // << 39
+ int p4 = a4 * b0; // << 52
+
+ if (b1 != 0) {
+ p1 += a0 * b1;
+ p2 += a1 * b1;
+ p3 += a2 * b1;
+ p4 += a3 * b1;
+ }
+ if (b2 != 0) {
+ p2 += a0 * b2;
+ p3 += a1 * b2;
+ p4 += a2 * b2;
+ }
+ if (b3 != 0) {
+ p3 += a0 * b3;
+ p4 += a1 * b3;
+ }
+ if (b4 != 0) {
+ p4 += a0 * b4;
+ }
+
+ // Accumulate into 22-bit chunks:
+ // .........................................c10|...................c00|
+ // |....................|..................xxxx|xxxxxxxxxxxxxxxxxxxxxx| p0
+ // |....................|......................|......................|
+ // |....................|...................c11|......c01.............|
+ // |....................|....xxxxxxxxxxxxxxxxxx|xxxxxxxxx.............| p1
+ // |....................|......................|......................|
+ // |.................c22|...............c12....|......................|
+ // |..........xxxxxxxxxx|xxxxxxxxxxxxxxxxxx....|......................| p2
+ // |....................|......................|......................|
+ // |.................c23|..c13.................|......................|
+ // |xxxxxxxxxxxxxxxxxxxx|xxxxx.................|......................| p3
+ // |....................|......................|......................|
+ // |.........c24........|......................|......................|
+ // |xxxxxxxxxxxx........|......................|......................| p4
+
+ int c00 = p0 & 0x3fffff;
+ int c01 = (p1 & 0x1ff) << 13;
+ int c0 = c00 + c01;
+
+ int c10 = p0 >> 22;
+ int c11 = p1 >> 9;
+ int c12 = (p2 & 0x3ffff) << 4;
+ int c13 = (p3 & 0x1f) << 17;
+ int c1 = c10 + c11 + c12 + c13;
+
+ int c22 = p2 >> 18;
+ int c23 = p3 >> 5;
+ int c24 = (p4 & 0xfff) << 8;
+ int c2 = c22 + c23 + c24;
+
+ // Propagate high bits from c0 -> c1, c1 -> c2.
+ c1 += c0 >> _BITS;
+ c2 += c1 >> _BITS;
+
+ return Int64._masked(c0, c1, c2);
+ }
+
+ @override
+ Int64 operator %(Object other) => _divide(this, other, _RETURN_MOD);
+
+ @override
+ Int64 operator ~/(Object other) => _divide(this, other, _RETURN_DIV);
+
+ @override
+ Int64 remainder(Object other) => _divide(this, other, _RETURN_REM);
+
+ @override
+ Int64 operator &(Object other) {
+ Int64 o = _promote(other);
+ int a0 = _l & o._l;
+ int a1 = _m & o._m;
+ int a2 = _h & o._h;
+ return Int64._masked(a0, a1, a2);
+ }
+
+ @override
+ Int64 operator |(Object other) {
+ Int64 o = _promote(other);
+ int a0 = _l | o._l;
+ int a1 = _m | o._m;
+ int a2 = _h | o._h;
+ return Int64._masked(a0, a1, a2);
+ }
+
+ @override
+ Int64 operator ^(Object other) {
+ Int64 o = _promote(other);
+ int a0 = _l ^ o._l;
+ int a1 = _m ^ o._m;
+ int a2 = _h ^ o._h;
+ return Int64._masked(a0, a1, a2);
+ }
+
+ @override
+ Int64 operator ~() => Int64._masked(~_l, ~_m, ~_h);
+
+ @override
+ Int64 operator <<(int n) {
+ if (n < 0) {
+ throw ArgumentError.value(n);
+ }
+ if (n >= 64) {
+ return ZERO;
+ }
+
+ int res0, res1, res2;
+ if (n < _BITS) {
+ res0 = _l << n;
+ res1 = (_m << n) | (_l >> (_BITS - n));
+ res2 = (_h << n) | (_m >> (_BITS - n));
+ } else if (n < _BITS01) {
+ res0 = 0;
+ res1 = _l << (n - _BITS);
+ res2 = (_m << (n - _BITS)) | (_l >> (_BITS01 - n));
+ } else {
+ res0 = 0;
+ res1 = 0;
+ res2 = _l << (n - _BITS01);
+ }
+
+ return Int64._masked(res0, res1, res2);
+ }
+
+ @override
+ Int64 operator >>(int n) {
+ if (n < 0) {
+ throw ArgumentError.value(n);
+ }
+ if (n >= 64) {
+ return isNegative ? const Int64._bits(_MASK, _MASK, _MASK2) : ZERO;
+ }
+
+ int res0, res1, res2;
+
+ // Sign extend h(a).
+ int a2 = _h;
+ bool negative = (a2 & _SIGN_BIT_MASK) != 0;
+ if (negative && _MASK > _MASK2) {
+ // Add extra one bits on the left so the sign gets shifted into the wider
+ // lower words.
+ a2 += _MASK - _MASK2;
+ }
+
+ if (n < _BITS) {
+ res2 = _shiftRight(a2, n);
+ if (negative) {
+ res2 |= _MASK2 & ~(_MASK2 >> n);
+ }
+ res1 = _shiftRight(_m, n) | (a2 << (_BITS - n));
+ res0 = _shiftRight(_l, n) | (_m << (_BITS - n));
+ } else if (n < _BITS01) {
+ res2 = negative ? _MASK2 : 0;
+ res1 = _shiftRight(a2, n - _BITS);
+ if (negative) {
+ res1 |= _MASK & ~(_MASK >> (n - _BITS));
+ }
+ res0 = _shiftRight(_m, n - _BITS) | (a2 << (_BITS01 - n));
+ } else {
+ res2 = negative ? _MASK2 : 0;
+ res1 = negative ? _MASK : 0;
+ res0 = _shiftRight(a2, n - _BITS01);
+ if (negative) {
+ res0 |= _MASK & ~(_MASK >> (n - _BITS01));
+ }
+ }
+
+ return Int64._masked(res0, res1, res2);
+ }
+
+ @override
+ Int64 shiftRightUnsigned(int n) {
+ if (n < 0) {
+ throw ArgumentError.value(n);
+ }
+ if (n >= 64) {
+ return ZERO;
+ }
+
+ int res0, res1, res2;
+ int a2 = _MASK2 & _h; // Ensure a2 is positive.
+ if (n < _BITS) {
+ res2 = a2 >> n;
+ res1 = (_m >> n) | (a2 << (_BITS - n));
+ res0 = (_l >> n) | (_m << (_BITS - n));
+ } else if (n < _BITS01) {
+ res2 = 0;
+ res1 = a2 >> (n - _BITS);
+ res0 = (_m >> (n - _BITS)) | (_h << (_BITS01 - n));
+ } else {
+ res2 = 0;
+ res1 = 0;
+ res0 = a2 >> (n - _BITS01);
+ }
+
+ return Int64._masked(res0, res1, res2);
+ }
+
+ /// Returns [:true:] if this [Int64] has the same numeric value as the
+ /// given object. The argument may be an [int] or an [IntX].
+ @override
+ bool operator ==(Object other) {
+ Int64? o;
+ if (other is Int64) {
+ o = other;
+ } else if (other is int) {
+ if (_h == 0 && _m == 0) return _l == other;
+ // Since we know one of [_h] or [_m] is non-zero, if [other] fits in the
+ // low word then it can't be numerically equal.
+ if ((_MASK & other) == other) return false;
+ o = Int64(other);
+ } else if (other is Int32) {
+ o = other.toInt64();
+ }
+ if (o != null) {
+ return _l == o._l && _m == o._m && _h == o._h;
+ }
+ return false;
+ }
+
+ @override
+ int compareTo(Object other) => _compareTo(other);
+
+ int _compareTo(Object other) {
+ Int64 o = _promote(other);
+ int signa = _h >> (_BITS2 - 1);
+ int signb = o._h >> (_BITS2 - 1);
+ if (signa != signb) {
+ return signa == 0 ? 1 : -1;
+ }
+ if (_h > o._h) {
+ return 1;
+ } else if (_h < o._h) {
+ return -1;
+ }
+ if (_m > o._m) {
+ return 1;
+ } else if (_m < o._m) {
+ return -1;
+ }
+ if (_l > o._l) {
+ return 1;
+ } else if (_l < o._l) {
+ return -1;
+ }
+ return 0;
+ }
+
+ @override
+ bool operator <(Object other) => _compareTo(other) < 0;
+
+ @override
+ bool operator <=(Object other) => _compareTo(other) <= 0;
+
+ @override
+ bool operator >(Object other) => _compareTo(other) > 0;
+
+ @override
+ bool operator >=(Object other) => _compareTo(other) >= 0;
+
+ @override
+ bool get isEven => (_l & 0x1) == 0;
+
+ @override
+ bool get isMaxValue => (_h == _MASK2 >> 1) && _m == _MASK && _l == _MASK;
+
+ @override
+ bool get isMinValue => _h == _SIGN_BIT_MASK && _m == 0 && _l == 0;
+
+ @override
+ bool get isNegative => (_h & _SIGN_BIT_MASK) != 0;
+
+ @override
+ bool get isOdd => (_l & 0x1) == 1;
+
+ @override
+ bool get isZero => _h == 0 && _m == 0 && _l == 0;
+
+ @override
+ int get bitLength {
+ if (isZero) return 0;
+ int a0 = _l, a1 = _m, a2 = _h;
+ if (isNegative) {
+ a0 = _MASK & ~a0;
+ a1 = _MASK & ~a1;
+ a2 = _MASK2 & ~a2;
+ }
+ if (a2 != 0) return _BITS01 + a2.bitLength;
+ if (a1 != 0) return _BITS + a1.bitLength;
+ return a0.bitLength;
+ }
+
+ /// Returns a hash code based on all the bits of this [Int64].
+ @override
+ int get hashCode {
+ // TODO(sra): Should we ensure that hashCode values match corresponding int?
+ // i.e. should `new Int64(x).hashCode == x.hashCode`?
+ int bottom = ((_m & 0x3ff) << _BITS) | _l;
+ int top = (_h << 12) | ((_m >> 10) & 0xfff);
+ return bottom ^ top;
+ }
+
+ @override
+ Int64 abs() => isNegative ? -this : this;
+
+ @override
+ Int64 clamp(Object lowerLimit, Object upperLimit) {
+ Int64 lower = _promote(lowerLimit);
+ Int64 upper = _promote(upperLimit);
+ if (this < lower) return lower;
+ if (this > upper) return upper;
+ return this;
+ }
+
+ /// Returns the number of leading zeros in this [Int64] as an [int]
+ /// between 0 and 64.
+ @override
+ int numberOfLeadingZeros() {
+ int b2 = u.numberOfLeadingZeros(_h);
+ if (b2 == 32) {
+ int b1 = u.numberOfLeadingZeros(_m);
+ if (b1 == 32) {
+ return u.numberOfLeadingZeros(_l) + 32;
+ } else {
+ return b1 + _BITS2 - (32 - _BITS);
+ }
+ } else {
+ return b2 - (32 - _BITS2);
+ }
+ }
+
+ /// Returns the number of trailing zeros in this [Int64] as an [int]
+ /// between 0 and 64.
+ @override
+ int numberOfTrailingZeros() {
+ int zeros = u.numberOfTrailingZeros(_l);
+ if (zeros < 32) {
+ return zeros;
+ }
+
+ zeros = u.numberOfTrailingZeros(_m);
+ if (zeros < 32) {
+ return _BITS + zeros;
+ }
+
+ zeros = u.numberOfTrailingZeros(_h);
+ if (zeros < 32) {
+ return _BITS01 + zeros;
+ }
+ // All zeros
+ return 64;
+ }
+
+ @override
+ Int64 toSigned(int width) {
+ if (width < 1 || width > 64) throw RangeError.range(width, 1, 64);
+ if (width > _BITS01) {
+ return Int64._masked(_l, _m, _h.toSigned(width - _BITS01));
+ } else if (width > _BITS) {
+ int m = _m.toSigned(width - _BITS);
+ return m.isNegative
+ ? Int64._masked(_l, m, _MASK2)
+ : Int64._masked(_l, m, 0); // Masking for type inferrer.
+ } else {
+ int l = _l.toSigned(width);
+ return l.isNegative
+ ? Int64._masked(l, _MASK, _MASK2)
+ : Int64._masked(l, 0, 0); // Masking for type inferrer.
+ }
+ }
+
+ @override
+ Int64 toUnsigned(int width) {
+ if (width < 0 || width > 64) throw RangeError.range(width, 0, 64);
+ if (width > _BITS01) {
+ int h = _h.toUnsigned(width - _BITS01);
+ return Int64._masked(_l, _m, h);
+ } else if (width > _BITS) {
+ int m = _m.toUnsigned(width - _BITS);
+ return Int64._masked(_l, m, 0);
+ } else {
+ int l = _l.toUnsigned(width);
+ return Int64._masked(l, 0, 0);
+ }
+ }
+
+ @override
+ List<int> toBytes() {
+ var result = List<int>.filled(8, 0);
+ result[0] = _l & 0xff;
+ result[1] = (_l >> 8) & 0xff;
+ result[2] = ((_m << 6) & 0xfc) | ((_l >> 16) & 0x3f);
+ result[3] = (_m >> 2) & 0xff;
+ result[4] = (_m >> 10) & 0xff;
+ result[5] = ((_h << 4) & 0xf0) | ((_m >> 18) & 0xf);
+ result[6] = (_h >> 4) & 0xff;
+ result[7] = (_h >> 12) & 0xff;
+ return result;
+ }
+
+ @override
+ double toDouble() => toInt().toDouble();
+
+ @override
+ int toInt() {
+ int l = _l;
+ int m = _m;
+ int h = _h;
+ // In the sum we add least significant to most significant so that in
+ // JavaScript double arithmetic rounding occurs on only the last addition.
+ if ((_h & _SIGN_BIT_MASK) != 0) {
+ l = _MASK & ~_l;
+ m = _MASK & ~_m;
+ h = _MASK2 & ~_h;
+ return -((1 + l) + (4194304 * m) + (17592186044416 * h));
+ } else {
+ return l + (4194304 * m) + (17592186044416 * h);
+ }
+ }
+
+ /// Returns an [Int32] containing the low 32 bits of this [Int64].
+ @override
+ Int32 toInt32() => Int32(((_m & 0x3ff) << _BITS) | _l);
+
+ /// Returns `this`.
+ @override
+ Int64 toInt64() => this;
+
+ /// Returns the value of this [Int64] as a decimal [String].
+ @override
+ String toString() => _toRadixString(10);
+
+ @override
+ String toHexString() {
+ if (isZero) return '0';
+ Int64 x = this;
+ String hexStr = '';
+ while (!x.isZero) {
+ int digit = x._l & 0xf;
+ hexStr = '${_hexDigit(digit)}$hexStr';
+ x = x.shiftRightUnsigned(4);
+ }
+ return hexStr;
+ }
+
+ /// Returns the digits of `this` when interpreted as an unsigned 64-bit value.
+ @pragma('dart2js:noInline')
+ String toStringUnsigned() => _toRadixStringUnsigned(10, _l, _m, _h, '');
+
+ @pragma('dart2js:noInline')
+ String toRadixStringUnsigned(int radix) =>
+ _toRadixStringUnsigned(u.validateRadix(radix), _l, _m, _h, '');
+
+ @override
+ String toRadixString(int radix) => _toRadixString(u.validateRadix(radix));
+
+ String _toRadixString(int radix) {
+ int d0 = _l;
+ int d1 = _m;
+ int d2 = _h;
+
+ String sign = '';
+ if ((d2 & _SIGN_BIT_MASK) != 0) {
+ sign = '-';
+
+ // Negate in-place.
+ d0 = 0 - d0;
+ int borrow = (d0 >> _BITS) & 1;
+ d0 &= _MASK;
+ d1 = 0 - d1 - borrow;
+ borrow = (d1 >> _BITS) & 1;
+ d1 &= _MASK;
+ d2 = 0 - d2 - borrow;
+ d2 &= _MASK2;
+ // d2, d1, d0 now are an unsigned 64 bit integer for MIN_VALUE and an
+ // unsigned 63 bit integer for other values.
+ }
+ return _toRadixStringUnsigned(radix, d0, d1, d2, sign);
+ }
+
+ static String _toRadixStringUnsigned(
+ int radix, int d0, int d1, int d2, String sign) {
+ if (d0 == 0 && d1 == 0 && d2 == 0) return '0';
+
+ // Rearrange components into five components where all but the most
+ // significant are 10 bits wide.
+ //
+ // d4, d3, d4, d1, d0: 24 + 10 + 10 + 10 + 10 bits
+ //
+ // The choice of 10 bits allows a remainder of 20 bits to be scaled by 10
+ // bits and added during division while keeping all intermediate values
+ // within 30 bits (unsigned small integer range for 32 bit implementations
+ // of Dart VM and V8).
+ //
+ // 6 6 5 4 3 2 1
+ // 3210987654321098765432109876543210987654321098765432109876543210
+ // [--------d2--------][---------d1---------][---------d0---------]
+ // -->
+ // [----------d4----------][---d3---][---d2---][---d1---][---d0---]
+
+ int d4 = (d2 << 4) | (d1 >> 18);
+ int d3 = (d1 >> 8) & 0x3ff;
+ d2 = ((d1 << 2) | (d0 >> 20)) & 0x3ff;
+ d1 = (d0 >> 10) & 0x3ff;
+ d0 = d0 & 0x3ff;
+
+ int fatRadix = _fatRadixTable[radix];
+
+ // Generate chunks of digits. In radix 10, generate 6 digits per chunk.
+ //
+ // This loop generates at most 3 chunks, so we store the chunks in locals
+ // rather than a list. We are trying to generate digits 20 bits at a time
+ // until we have only 30 bits left. 20 + 20 + 30 > 64 would imply that we
+ // need only two chunks, but radix values 17-19 and 33-36 generate only 15
+ // or 16 bits per iteration, so sometimes the third chunk is needed.
+
+ String chunk1 = '', chunk2 = '', chunk3 = '';
+
+ while (!(d4 == 0 && d3 == 0)) {
+ int q = d4 ~/ fatRadix;
+ int r = d4 - q * fatRadix;
+ d4 = q;
+ d3 += r << 10;
+
+ q = d3 ~/ fatRadix;
+ r = d3 - q * fatRadix;
+ d3 = q;
+ d2 += r << 10;
+
+ q = d2 ~/ fatRadix;
+ r = d2 - q * fatRadix;
+ d2 = q;
+ d1 += r << 10;
+
+ q = d1 ~/ fatRadix;
+ r = d1 - q * fatRadix;
+ d1 = q;
+ d0 += r << 10;
+
+ q = d0 ~/ fatRadix;
+ r = d0 - q * fatRadix;
+ d0 = q;
+
+ assert(chunk3 == '');
+ chunk3 = chunk2;
+ chunk2 = chunk1;
+ // Adding [fatRadix] Forces an extra digit which we discard to get a fixed
+ // width. E.g. (1000000 + 123) -> "1000123" -> "000123". An alternative
+ // would be to pad to the left with zeroes.
+ chunk1 = (fatRadix + r).toRadixString(radix).substring(1);
+ }
+ int residue = (d2 << 20) + (d1 << 10) + d0;
+ String leadingDigits = residue == 0 ? '' : residue.toRadixString(radix);
+ return '$sign$leadingDigits$chunk1$chunk2$chunk3';
+ }
+
+ // Table of 'fat' radix values. Each entry for index `i` is the largest power
+ // of `i` whose remainder fits in 20 bits.
+ static const _fatRadixTable = <int>[
+ 0,
+ 0,
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2 *
+ 2,
+ 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3,
+ 4 * 4 * 4 * 4 * 4 * 4 * 4 * 4 * 4 * 4,
+ 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5,
+ 6 * 6 * 6 * 6 * 6 * 6 * 6,
+ 7 * 7 * 7 * 7 * 7 * 7 * 7,
+ 8 * 8 * 8 * 8 * 8 * 8,
+ 9 * 9 * 9 * 9 * 9 * 9,
+ 10 * 10 * 10 * 10 * 10 * 10,
+ 11 * 11 * 11 * 11 * 11,
+ 12 * 12 * 12 * 12 * 12,
+ 13 * 13 * 13 * 13 * 13,
+ 14 * 14 * 14 * 14 * 14,
+ 15 * 15 * 15 * 15 * 15,
+ 16 * 16 * 16 * 16 * 16,
+ 17 * 17 * 17 * 17,
+ 18 * 18 * 18 * 18,
+ 19 * 19 * 19 * 19,
+ 20 * 20 * 20 * 20,
+ 21 * 21 * 21 * 21,
+ 22 * 22 * 22 * 22,
+ 23 * 23 * 23 * 23,
+ 24 * 24 * 24 * 24,
+ 25 * 25 * 25 * 25,
+ 26 * 26 * 26 * 26,
+ 27 * 27 * 27 * 27,
+ 28 * 28 * 28 * 28,
+ 29 * 29 * 29 * 29,
+ 30 * 30 * 30 * 30,
+ 31 * 31 * 31 * 31,
+ 32 * 32 * 32 * 32,
+ 33 * 33 * 33,
+ 34 * 34 * 34,
+ 35 * 35 * 35,
+ 36 * 36 * 36
+ ];
+
+ String toDebugString() => 'Int64[_l=$_l, _m=$_m, _h=$_h]';
+
+ static Int64 _masked(int low, int medium, int high) =>
+ Int64._bits(_MASK & low, _MASK & medium, _MASK2 & high);
+
+ static Int64 _sub(int a0, int a1, int a2, int b0, int b1, int b2) {
+ int diff0 = a0 - b0;
+ int diff1 = a1 - b1 - ((diff0 >> _BITS) & 1);
+ int diff2 = a2 - b2 - ((diff1 >> _BITS) & 1);
+ return _masked(diff0, diff1, diff2);
+ }
+
+ static Int64 _negate(int b0, int b1, int b2) => _sub(0, 0, 0, b0, b1, b2);
+
+ String _hexDigit(int digit) => '0123456789ABCDEF'[digit];
+
+ // Work around dart2js bugs with negative arguments to '>>' operator.
+ static int _shiftRight(int x, int n) {
+ if (x >= 0) {
+ return x >> n;
+ } else {
+ int shifted = x >> n;
+ if (shifted >= 0x80000000) {
+ shifted -= 4294967296;
+ }
+ return shifted;
+ }
+ }
+
+ // Implementation of '~/', '%' and 'remainder'.
+
+ static Int64 _divide(Int64 a, Object other, int what) {
+ Int64 b = _promote(other);
+ if (b.isZero) {
+ throw UnsupportedError('Division by zero');
+ }
+ if (a.isZero) return ZERO;
+
+ bool aNeg = a.isNegative;
+ bool bNeg = b.isNegative;
+ a = a.abs();
+ b = b.abs();
+
+ int a0 = a._l;
+ int a1 = a._m;
+ int a2 = a._h;
+
+ int b0 = b._l;
+ int b1 = b._m;
+ int b2 = b._h;
+ return _divideHelper(a0, a1, a2, aNeg, b0, b1, b2, bNeg, what);
+ }
+
+ static const _RETURN_DIV = 1;
+ static const _RETURN_REM = 2;
+ static const _RETURN_MOD = 3;
+
+ static Int64 _divideHelper(
+ // up to 64 bits unsigned in a2/a1/a0 and b2/b1/b0
+ int a0,
+ int a1,
+ int a2,
+ bool aNeg, // input A.
+ int b0,
+ int b1,
+ int b2,
+ bool bNeg, // input B.
+ int what) {
+ int q0 = 0, q1 = 0, q2 = 0; // result Q.
+ int r0 = 0, r1 = 0, r2 = 0; // result R.
+
+ if (b2 == 0 && b1 == 0 && b0 < (1 << (30 - _BITS))) {
+ // Small divisor can be handled by single-digit division within Smi range.
+ //
+ // Handling small divisors here helps the estimate version below by
+ // handling cases where the estimate is off by more than a small amount.
+
+ q2 = a2 ~/ b0;
+ int carry = a2 - q2 * b0;
+ int d1 = a1 + (carry << _BITS);
+ q1 = d1 ~/ b0;
+ carry = d1 - q1 * b0;
+ int d0 = a0 + (carry << _BITS);
+ q0 = d0 ~/ b0;
+ r0 = d0 - q0 * b0;
+ } else {
+ // Approximate Q = A ~/ B and R = A - Q * B using doubles.
+
+ // The floating point approximation is very close to the correct value
+ // when floor(A/B) fits in fewer that 53 bits.
+
+ // We use double arithmetic for intermediate values. Double arithmetic on
+ // non-negative values is exact under the following conditions:
+ //
+ // - The values are integer values that fit in 53 bits.
+ // - Dividing by powers of two (adjusts exponent only).
+ // - Floor (zeroes bits with fractional weight).
+
+ const double K2 = 17592186044416.0; // 2^44
+ const double K1 = 4194304.0; // 2^22
+
+ // Approximate double values for [a] and [b].
+ double ad = a0 + K1 * a1 + K2 * a2;
+ double bd = b0 + K1 * b1 + K2 * b2;
+ // Approximate quotient.
+ double qd = (ad / bd).floorToDouble();
+
+ // Extract components of [qd] using double arithmetic.
+ double q2d = (qd / K2).floorToDouble();
+ qd = qd - K2 * q2d;
+ double q1d = (qd / K1).floorToDouble();
+ double q0d = qd - K1 * q1d;
+ q2 = q2d.toInt();
+ q1 = q1d.toInt();
+ q0 = q0d.toInt();
+
+ assert(q0 + K1 * q1 + K2 * q2 == (ad / bd).floorToDouble());
+ assert(q2 == 0 || b2 == 0); // Q and B can't both be big since Q*B <= A.
+
+ // P = Q * B, using doubles to hold intermediates.
+ // We don't need all partial sums since Q*B <= A.
+ double p0d = q0d * b0;
+ double p0carry = (p0d / K1).floorToDouble();
+ p0d = p0d - p0carry * K1;
+ double p1d = q1d * b0 + q0d * b1 + p0carry;
+ double p1carry = (p1d / K1).floorToDouble();
+ p1d = p1d - p1carry * K1;
+ double p2d = q2d * b0 + q1d * b1 + q0d * b2 + p1carry;
+ assert(p2d <= _MASK2); // No partial sum overflow.
+
+ // R = A - P
+ int diff0 = a0 - p0d.toInt();
+ int diff1 = a1 - p1d.toInt() - ((diff0 >> _BITS) & 1);
+ int diff2 = a2 - p2d.toInt() - ((diff1 >> _BITS) & 1);
+ r0 = _MASK & diff0;
+ r1 = _MASK & diff1;
+ r2 = _MASK2 & diff2;
+
+ // while (R < 0 || R >= B)
+ // adjust R towards [0, B)
+ while (r2 >= _SIGN_BIT_MASK ||
+ r2 > b2 ||
+ (r2 == b2 && (r1 > b1 || (r1 == b1 && r0 >= b0)))) {
+ // Direction multiplier for adjustment.
+ int m = (r2 & _SIGN_BIT_MASK) == 0 ? 1 : -1;
+ // R = R - B or R = R + B
+ int d0 = r0 - m * b0;
+ int d1 = r1 - m * (b1 + ((d0 >> _BITS) & 1));
+ int d2 = r2 - m * (b2 + ((d1 >> _BITS) & 1));
+ r0 = _MASK & d0;
+ r1 = _MASK & d1;
+ r2 = _MASK2 & d2;
+
+ // Q = Q + 1 or Q = Q - 1
+ d0 = q0 + m;
+ d1 = q1 + m * ((d0 >> _BITS) & 1);
+ d2 = q2 + m * ((d1 >> _BITS) & 1);
+ q0 = _MASK & d0;
+ q1 = _MASK & d1;
+ q2 = _MASK2 & d2;
+ }
+ }
+
+ // 0 <= R < B
+ assert(Int64.ZERO <= Int64._bits(r0, r1, r2));
+ assert(r2 < b2 || // Handles case where B = -(MIN_VALUE)
+ Int64._bits(r0, r1, r2) < Int64._bits(b0, b1, b2));
+
+ assert(what == _RETURN_DIV || what == _RETURN_MOD || what == _RETURN_REM);
+ if (what == _RETURN_DIV) {
+ if (aNeg != bNeg) return _negate(q0, q1, q2);
+ return Int64._masked(q0, q1, q2); // Masking for type inferrer.
+ }
+
+ if (!aNeg) {
+ return Int64._masked(r0, r1, r2); // Masking for type inferrer.
+ }
+
+ if (what == _RETURN_MOD) {
+ if (r0 == 0 && r1 == 0 && r2 == 0) {
+ return ZERO;
+ } else {
+ return _sub(b0, b1, b2, r0, r1, r2);
+ }
+ } else {
+ return _negate(r0, r1, r2);
+ }
+ }
+}
diff --git a/pkgs/fixnum/lib/src/intx.dart b/pkgs/fixnum/lib/src/intx.dart
new file mode 100644
index 0000000..d51a583
--- /dev/null
+++ b/pkgs/fixnum/lib/src/intx.dart
@@ -0,0 +1,207 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'int32.dart';
+import 'int64.dart';
+
+/// A fixed-precision integer.
+abstract class IntX implements Comparable<Object> {
+ /// Addition operator.
+ IntX operator +(Object other);
+
+ /// Subtraction operator.
+ IntX operator -(Object other);
+
+ /// Negate operator.
+ ///
+ /// Note that `-MIN_VALUE` is equal to `MIN_VALUE` due to overflow.
+ IntX operator -();
+
+ /// Multiplication operator.
+ IntX operator *(Object other);
+
+ /// Euclidean modulo operator.
+ ///
+ /// Returns the remainder of the euclidean division. The euclidean division
+ /// of two integers `a` and `b` yields two integers `q` and `r` such that
+ /// `a == b * q + r` and `0 <= r < a.abs()`.
+ IntX operator %(Object other);
+
+ /// Truncating division operator.
+ IntX operator ~/(Object other);
+
+ /// Returns the remainder of the truncating division of this integer by
+ /// [other].
+ IntX remainder(Object other);
+
+ /// Bitwise and operator.
+ IntX operator &(Object other);
+
+ /// Bitwise or operator.
+ IntX operator |(Object other);
+
+ /// Bitwise xor operator.
+ IntX operator ^(Object other);
+
+ /// Bitwise negate operator.
+ IntX operator ~();
+
+ /// Left bit-shift operator.
+ ///
+ /// Returns the result of shifting the bits of this integer by [shiftAmount]
+ /// bits to the left. Low-order bits are filled with zeros.
+ IntX operator <<(int shiftAmount);
+
+ /// Right bit-shift operator.
+ ///
+ /// Returns the result of shifting the bits of this integer by [shiftAmount]
+ /// bits to the right. High-order bits are filled with zero in the case where
+ /// this integer is positive, or one in the case where it is negative.
+ IntX operator >>(int shiftAmount);
+
+ /// Unsigned right-shift operator.
+ ///
+ /// Returns the result of shifting the bits of this integer by [shiftAmount]
+ /// bits to the right. High-order bits are filled with zeros.
+ IntX shiftRightUnsigned(int shiftAmount);
+
+ @override
+ int compareTo(Object other);
+
+ /// Returns `true` if and only if [other] is an int or IntX equal in
+ /// value to this integer.
+ @override
+ bool operator ==(Object other);
+
+ /// Relational less than operator.
+ bool operator <(Object other);
+
+ /// Relational less than or equal to operator.
+ bool operator <=(Object other);
+
+ /// Relational greater than operator.
+ bool operator >(Object other);
+
+ /// Relational greater than or equal to operator.
+ bool operator >=(Object other);
+
+ /// Returns `true` if and only if this integer is even.
+ bool get isEven;
+
+ /// Returns `true` if and only if this integer is the maximum signed value
+ /// that can be represented within its bit size.
+ bool get isMaxValue;
+
+ /// Returns `true` if and only if this integer is the minimum signed value
+ /// that can be represented within its bit size.
+ bool get isMinValue;
+
+ /// Returns `true` if and only if this integer is less than zero.
+ bool get isNegative;
+
+ /// Returns `true` if and only if this integer is odd.
+ bool get isOdd;
+
+ /// Returns `true` if and only if this integer is zero.
+ bool get isZero;
+
+ @override
+ int get hashCode;
+
+ /// Returns the absolute value of this integer.
+ IntX abs();
+
+ /// Clamps this integer to be in the range [lowerLimit] - [upperLimit].
+ IntX clamp(Object lowerLimit, Object upperLimit);
+
+ /// Returns the minimum number of bits required to store this integer.
+ ///
+ /// The number of bits excludes the sign bit, which gives the natural length
+ /// for non-negative (unsigned) values. Negative values are complemented to
+ /// return the bit position of the first bit that differs from the sign bit.
+ ///
+ /// To find the the number of bits needed to store the value as a signed
+ /// value, add one, i.e. use `x.bitLength + 1`.
+ int get bitLength;
+
+ /// Returns the number of high-order zeros in this integer's bit
+ /// representation.
+ int numberOfLeadingZeros();
+
+ /// Returns the number of low-order zeros in this integer's bit
+ /// representation.
+ int numberOfTrailingZeros();
+
+ /// Returns the least significant [width] bits of this integer, extending the
+ /// highest retained bit to the sign. This is the same as truncating the
+ /// value to fit in [width] bits using an signed 2-s complement
+ /// representation. The returned value has the same bit value in all positions
+ /// higher than [width].
+ ///
+ /// If the input value fits in [width] bits without truncation, the result is
+ /// the same as the input. The minimum width needed to avoid truncation of
+ /// `x` is `x.bitLength + 1`, i.e.
+ ///
+ /// x == x.toSigned(x.bitLength + 1);
+ IntX toSigned(int width);
+
+ /// Returns the least significant [width] bits of this integer as a
+ /// non-negative number (i.e. unsigned representation). The returned value has
+ /// zeros in all bit positions higher than [width].
+ ///
+ /// If the input fits in [width] bits without truncation, the result is the
+ /// same as the input. The minimum width needed to avoid truncation of `x` is
+ /// given by `x.bitLength`, i.e.
+ ///
+ /// x == x.toUnsigned(x.bitLength);
+ IntX toUnsigned(int width);
+
+ /// Returns a byte-sequence representation of this integer.
+ ///
+ /// Returns a list of int, starting with the least significant byte.
+ List<int> toBytes();
+
+ /// Returns the double representation of this integer.
+ ///
+ /// On some platforms, inputs with large absolute values (i.e., > 2^52) may
+ /// lose some of their low-order bits.
+ double toDouble();
+
+ /// Returns the int representation of this integer.
+ ///
+ /// On some platforms, inputs with large absolute values (i.e., > 2^52) may
+ /// lose some of their low-order bits.
+ int toInt();
+
+ /// Returns an Int32 representation of this integer.
+ ///
+ /// Narrower values are sign-extended and wider values have their high bits
+ /// truncated.
+ Int32 toInt32();
+
+ /// Returns an Int64 representation of this integer.
+ Int64 toInt64();
+
+ /// Returns a string representing the value of this integer in decimal
+ /// notation; example: `'13'`.
+ @override
+ String toString();
+
+ /// Returns a string representing the value of this integer in hexadecimal
+ /// notation.
+ ///
+ /// Example: `Int64(0xf01d).toHexString()` returns `'F01D'`.
+ ///
+ /// The string may interprets the number as *unsigned*, and has no leading
+ /// minus, even if the value [isNegative].
+ ///
+ /// Example: `Int64(-1).toHexString()` returns `'FFFFFFFFFFFFFFFF'`.
+ String toHexString();
+
+ /// Returns a string representing the value of this integer in the given
+ /// radix.
+ ///
+ /// [radix] must be an integer in the range 2 .. 16, inclusive.
+ String toRadixString(int radix);
+}
diff --git a/pkgs/fixnum/lib/src/utilities.dart b/pkgs/fixnum/lib/src/utilities.dart
new file mode 100644
index 0000000..8b3957c
--- /dev/null
+++ b/pkgs/fixnum/lib/src/utilities.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Shared functionality used by multiple classes and their implementations.
+
+int validateRadix(int radix) =>
+ RangeError.checkValueInInterval(radix, 2, 36, 'radix');
+
+/// Converts radix digits into their numeric values.
+///
+/// Converts the characters `0`-`9` into the values 0 through 9,
+/// and the letters `a`-`z` or `A`-`Z` into values 10 through 35,
+/// and return that value.
+/// Any other character returns a value above 35, which means it's
+/// not a valid digit in any radix in the range 2 through 36.
+int decodeDigit(int c) {
+ // Hex digit char codes
+ const c0 = 48; // '0'.codeUnitAt(0)
+ const ca = 97; // 'a'.codeUnitAt(0)
+
+ var digit = c ^ c0;
+ if (digit < 10) return digit;
+ var letter = (c | 0x20) - ca;
+ if (letter >= 0) {
+ // Returns values above 36 for invalid digits.
+ // The value is checked against the actual radix where the return
+ // value is used, so this is safe.
+ return letter + 10;
+ } else {
+ return 255; // Never a valid radix.
+ }
+}
+
+// Assumes i is <= 32-bit
+int numberOfLeadingZeros(int i) {
+ i |= i >> 1;
+ i |= i >> 2;
+ i |= i >> 4;
+ i |= i >> 8;
+ i |= i >> 16;
+ return bitCount(~i);
+}
+
+int numberOfTrailingZeros(int i) => bitCount((i & -i) - 1);
+
+// Assumes i is <= 32-bit.
+int bitCount(int i) {
+ // See "Hacker's Delight", section 5-1, "Counting 1-Bits".
+
+ // The basic strategy is to use "divide and conquer" to
+ // add pairs (then quads, etc.) of bits together to obtain
+ // sub-counts.
+ //
+ // A straightforward approach would look like:
+ //
+ // i = (i & 0x55555555) + ((i >> 1) & 0x55555555);
+ // i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
+ // i = (i & 0x0F0F0F0F) + ((i >> 4) & 0x0F0F0F0F);
+ // i = (i & 0x00FF00FF) + ((i >> 8) & 0x00FF00FF);
+ // i = (i & 0x0000FFFF) + ((i >> 16) & 0x0000FFFF);
+ //
+ // The code below removes unnecessary &'s and uses a
+ // trick to remove one instruction in the first line.
+
+ i -= (i >> 1) & 0x55555555;
+ i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
+ i = (i + (i >> 4)) & 0x0F0F0F0F;
+ i += i >> 8;
+ i += i >> 16;
+ return i & 0x0000003F;
+}
diff --git a/pkgs/fixnum/pubspec.yaml b/pkgs/fixnum/pubspec.yaml
new file mode 100644
index 0000000..123a4b7
--- /dev/null
+++ b/pkgs/fixnum/pubspec.yaml
@@ -0,0 +1,14 @@
+name: fixnum
+version: 1.1.1
+description: >-
+ Library for 32- and 64-bit signed fixed-width integers with consistent
+ behavior between native and JS runtimes.
+repository: https://github.com/dart-lang/core/tree/main/pkgs/fixnum
+issue_tracker: https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Afixnum
+
+environment:
+ sdk: ^3.1.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
diff --git a/pkgs/fixnum/test/all_tests.dart b/pkgs/fixnum/test/all_tests.dart
new file mode 100644
index 0000000..a4e2e74
--- /dev/null
+++ b/pkgs/fixnum/test/all_tests.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'int32_test.dart' as int32_test;
+import 'int64_test.dart' as int64_test;
+
+void main() {
+ int32_test.main();
+ int64_test.main();
+}
diff --git a/pkgs/fixnum/test/int32_test.dart b/pkgs/fixnum/test/int32_test.dart
new file mode 100644
index 0000000..5e6c36a
--- /dev/null
+++ b/pkgs/fixnum/test/int32_test.dart
@@ -0,0 +1,413 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:fixnum/fixnum.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('isX tests', () {
+ test('isEven', () {
+ expect((-Int32.ONE).isEven, false);
+ expect(Int32.ZERO.isEven, true);
+ expect(Int32.ONE.isEven, false);
+ expect(Int32.TWO.isEven, true);
+ });
+ test('isMaxValue', () {
+ expect(Int32.MIN_VALUE.isMaxValue, false);
+ expect(Int32.ZERO.isMaxValue, false);
+ expect(Int32.MAX_VALUE.isMaxValue, true);
+ });
+ test('isMinValue', () {
+ expect(Int32.MIN_VALUE.isMinValue, true);
+ expect(Int32.ZERO.isMinValue, false);
+ expect(Int32.MAX_VALUE.isMinValue, false);
+ });
+ test('isNegative', () {
+ expect(Int32.MIN_VALUE.isNegative, true);
+ expect(Int32.ZERO.isNegative, false);
+ expect(Int32.ONE.isNegative, false);
+ });
+ test('isOdd', () {
+ expect((-Int32.ONE).isOdd, true);
+ expect(Int32.ZERO.isOdd, false);
+ expect(Int32.ONE.isOdd, true);
+ expect(Int32.TWO.isOdd, false);
+ });
+ test('isZero', () {
+ expect(Int32.MIN_VALUE.isZero, false);
+ expect(Int32.ZERO.isZero, true);
+ expect(Int32.MAX_VALUE.isZero, false);
+ });
+ test('bitLength', () {
+ expect(Int32(-2).bitLength, 1);
+ expect((-Int32.ONE).bitLength, 0);
+ expect(Int32.ZERO.bitLength, 0);
+ expect(Int32.ONE.bitLength, 1);
+ expect(Int32(2).bitLength, 2);
+ expect(Int32.MAX_VALUE.bitLength, 31);
+ expect(Int32.MIN_VALUE.bitLength, 31);
+ });
+ });
+
+ group('arithmetic operators', () {
+ var n1 = Int32(1234);
+ var n2 = Int32(9876);
+ var n3 = Int32(-1234);
+ var n4 = Int32(-9876);
+
+ test('+', () {
+ expect(n1 + n2, Int32(11110));
+ expect(n3 + n2, Int32(8642));
+ expect(n3 + n4, Int32(-11110));
+ expect(n3 + Int64(1), Int64(-1233));
+ expect(Int32.MAX_VALUE + 1, Int32.MIN_VALUE);
+ });
+
+ test('-', () {
+ expect(n1 - n2, Int32(-8642));
+ expect(n3 - n2, Int32(-11110));
+ expect(n3 - n4, Int32(8642));
+ expect(n3 - Int64(1), Int64(-1235));
+ expect(Int32.MIN_VALUE - 1, Int32.MAX_VALUE);
+ });
+
+ test('unary -', () {
+ expect(-n1, Int32(-1234));
+ expect(-Int32.ZERO, Int32.ZERO);
+ });
+
+ test('*', () {
+ expect(n1 * n2, Int32(12186984));
+ expect(n2 * n3, Int32(-12186984));
+ expect(n3 * n3, Int32(1522756));
+ expect(n3 * n2, Int32(-12186984));
+ expect(Int32(0x12345678) * Int32(0x22222222), Int32(-899716112));
+ expect(Int32(123456789) * Int32(987654321), Int32(-67153019));
+ expect(Int32(0x12345678) * Int64(0x22222222),
+ Int64.fromInts(0x026D60DC, 0xCA5F6BF0));
+ expect(Int32(123456789) * 987654321, Int32(-67153019));
+ });
+
+ test('~/', () {
+ expect(Int32(829893893) ~/ Int32(1919), Int32(432461));
+ expect(Int32(0x12345678) ~/ Int32(0x22), Int32(0x12345678 ~/ 0x22));
+ expect(Int32(829893893) ~/ Int64(1919), Int32(432461));
+ expect(Int32(0x12345678) ~/ Int64(0x22), Int32(0x12345678 ~/ 0x22));
+ expect(Int32(829893893) ~/ 1919, Int32(432461));
+ expect(() => Int32(17) ~/ Int32.ZERO, throwsA(isUnsupportedError));
+ });
+
+ test('%', () {
+ expect(Int32(0x12345678) % Int32(0x22), Int32(0x12345678 % 0x22));
+ expect(Int32(0x12345678) % Int64(0x22), Int32(0x12345678 % 0x22));
+ });
+
+ test('remainder', () {
+ expect(Int32(0x12345678).remainder(Int32(0x22)),
+ Int32(0x12345678.remainder(0x22)));
+ expect(Int32(0x12345678).remainder(Int32(-0x22)),
+ Int32(0x12345678.remainder(-0x22)));
+ expect(Int32(-0x12345678).remainder(Int32(-0x22)),
+ Int32(-0x12345678.remainder(-0x22)));
+ expect(Int32(-0x12345678).remainder(Int32(0x22)),
+ Int32(-0x12345678.remainder(0x22)));
+ expect(Int32(0x12345678).remainder(Int64(0x22)),
+ Int32(0x12345678.remainder(0x22)));
+ });
+
+ test('abs', () {
+ // NOTE: Int32.MIN_VALUE.abs() is undefined
+ expect((Int32.MIN_VALUE + 1).abs(), Int32.MAX_VALUE);
+ expect(Int32(-1).abs(), Int32(1));
+ expect(Int32(0).abs(), Int32(0));
+ expect(Int32(1).abs(), Int32(1));
+ expect(Int32.MAX_VALUE.abs(), Int32.MAX_VALUE);
+ });
+
+ test('clamp', () {
+ var val = Int32(17);
+ expect(val.clamp(20, 30), Int32(20));
+ expect(val.clamp(10, 20), Int32(17));
+ expect(val.clamp(10, 15), Int32(15));
+
+ expect(val.clamp(Int32(20), Int32(30)), Int32(20));
+ expect(val.clamp(Int32(10), Int32(20)), Int32(17));
+ expect(val.clamp(Int32(10), Int32(15)), Int32(15));
+
+ expect(val.clamp(Int64(20), Int64(30)), Int32(20));
+ expect(val.clamp(Int64(10), Int64(20)), Int32(17));
+ expect(val.clamp(Int64(10), Int64(15)), Int32(15));
+ expect(val.clamp(Int64.MIN_VALUE, Int64(30)), Int32(17));
+ expect(val.clamp(Int64(10), Int64.MAX_VALUE), Int32(17));
+
+ expect(() => val.clamp(30.5, 40.5), throwsArgumentError);
+ expect(() => val.clamp(5.5, 10.5), throwsArgumentError);
+ expect(() => val.clamp('a', 1), throwsArgumentError);
+ expect(() => val.clamp(1, 'b'), throwsArgumentError);
+ expect(() => val.clamp('a', 1), throwsArgumentError);
+ });
+ });
+
+ group('leading/trailing zeros', () {
+ test('numberOfLeadingZeros', () {
+ expect(Int32(0).numberOfLeadingZeros(), 32);
+ expect(Int32(1).numberOfLeadingZeros(), 31);
+ expect(Int32(0xffff).numberOfLeadingZeros(), 16);
+ expect(Int32(-1).numberOfLeadingZeros(), 0);
+ });
+ test('numberOfTrailingZeros', () {
+ expect(Int32(0).numberOfTrailingZeros(), 32);
+ expect(Int32(0x80000000).numberOfTrailingZeros(), 31);
+ expect(Int32(1).numberOfTrailingZeros(), 0);
+ expect(Int32(0x10000).numberOfTrailingZeros(), 16);
+ });
+ });
+
+ group('comparison operators', () {
+ test('compareTo', () {
+ expect(Int32(0).compareTo(-1), 1);
+ expect(Int32(0).compareTo(0), 0);
+ expect(Int32(0).compareTo(1), -1);
+ expect(Int32(0).compareTo(Int32(-1)), 1);
+ expect(Int32(0).compareTo(Int32(0)), 0);
+ expect(Int32(0).compareTo(Int32(1)), -1);
+ expect(Int32(0).compareTo(Int64(-1)), 1);
+ expect(Int32(0).compareTo(Int64(0)), 0);
+ expect(Int32(0).compareTo(Int64(1)), -1);
+ });
+
+ test('<', () {
+ expect(Int32(17) < Int32(18), true);
+ expect(Int32(17) < Int32(17), false);
+ expect(Int32(17) < Int32(16), false);
+ expect(Int32(17) < Int64(18), true);
+ expect(Int32(17) < Int64(17), false);
+ expect(Int32(17) < Int64(16), false);
+ expect(Int32.MIN_VALUE < Int32.MAX_VALUE, true);
+ expect(Int32.MAX_VALUE < Int32.MIN_VALUE, false);
+ });
+
+ test('<=', () {
+ expect(Int32(17) <= Int32(18), true);
+ expect(Int32(17) <= Int32(17), true);
+ expect(Int32(17) <= Int32(16), false);
+ expect(Int32(17) <= Int64(18), true);
+ expect(Int32(17) <= Int64(17), true);
+ expect(Int32(17) <= Int64(16), false);
+ expect(Int32.MIN_VALUE <= Int32.MAX_VALUE, true);
+ expect(Int32.MAX_VALUE <= Int32.MIN_VALUE, false);
+ });
+
+ test('==', () {
+ expect(Int32(17), isNot(equals(Int32(18))));
+ expect(Int32(17), equals(Int32(17)));
+ expect(Int32(17), isNot(equals(Int32(16))));
+ expect(Int32(17), isNot(equals(Int64(18))));
+ expect(Int32(17), equals(Int64(17)));
+ expect(Int32(17), isNot(equals(Int64(16))));
+ expect(Int32.MIN_VALUE, isNot(equals(Int32.MAX_VALUE)));
+ expect(Int32(17), isNot(equals(18)));
+ expect(Int32(17) == 17, isTrue); // ignore: unrelated_type_equality_checks
+ expect(Int32(17), isNot(equals(16)));
+ expect(Int32(17), isNot(equals(Object())));
+ expect(Int32(17), isNot(equals(null)));
+ });
+
+ test('>=', () {
+ expect(Int32(17) >= Int32(18), false);
+ expect(Int32(17) >= Int32(17), true);
+ expect(Int32(17) >= Int32(16), true);
+ expect(Int32(17) >= Int64(18), false);
+ expect(Int32(17) >= Int64(17), true);
+ expect(Int32(17) >= Int64(16), true);
+ expect(Int32.MIN_VALUE >= Int32.MAX_VALUE, false);
+ expect(Int32.MAX_VALUE >= Int32.MIN_VALUE, true);
+ });
+
+ test('>', () {
+ expect(Int32(17) > Int32(18), false);
+ expect(Int32(17) > Int32(17), false);
+ expect(Int32(17) > Int32(16), true);
+ expect(Int32(17) > Int64(18), false);
+ expect(Int32(17) > Int64(17), false);
+ expect(Int32(17) > Int64(16), true);
+ expect(Int32.MIN_VALUE > Int32.MAX_VALUE, false);
+ expect(Int32.MAX_VALUE > Int32.MIN_VALUE, true);
+ });
+ });
+
+ group('bitwise operators', () {
+ test('&', () {
+ expect(Int32(0x12345678) & Int32(0x22222222),
+ Int32(0x12345678 & 0x22222222));
+ expect(Int32(0x12345678) & Int64(0x22222222),
+ Int64(0x12345678 & 0x22222222));
+ });
+
+ test('|', () {
+ expect(Int32(0x12345678) | Int32(0x22222222),
+ Int32(0x12345678 | 0x22222222));
+ expect(Int32(0x12345678) | Int64(0x22222222),
+ Int64(0x12345678 | 0x22222222));
+ });
+
+ test('^', () {
+ expect(Int32(0x12345678) ^ Int32(0x22222222),
+ Int32(0x12345678 ^ 0x22222222));
+ expect(Int32(0x12345678) ^ Int64(0x22222222),
+ Int64(0x12345678 ^ 0x22222222));
+ });
+
+ test('~', () {
+ expect(~Int32(0x12345678), Int32(~0x12345678));
+ expect(-Int32(0x12345678), Int64(-0x12345678));
+ });
+ });
+
+ group('bitshift operators', () {
+ test('<<', () {
+ expect(Int32(0x12345678) << 7, Int32(0x12345678 << 7));
+ expect(Int32(0x12345678) << 32, Int32.ZERO);
+ expect(Int32(0x12345678) << 33, Int32.ZERO);
+ expect(() => Int32(17) << -1, throwsArgumentError);
+ });
+
+ test('>>', () {
+ expect(Int32(0x12345678) >> 7, Int32(0x12345678 >> 7));
+ expect(Int32(0x12345678) >> 32, Int32.ZERO);
+ expect(Int32(0x12345678) >> 33, Int32.ZERO);
+ expect(Int32(-42) >> 32, Int32(-1));
+ expect(Int32(-42) >> 33, Int32(-1));
+ expect(() => Int32(17) >> -1, throwsArgumentError);
+ });
+
+ test('shiftRightUnsigned', () {
+ expect(Int32(0x12345678).shiftRightUnsigned(7), Int32(0x12345678 >> 7));
+ expect(Int32(0x12345678).shiftRightUnsigned(32), Int32.ZERO);
+ expect(Int32(0x12345678).shiftRightUnsigned(33), Int32.ZERO);
+ expect(Int32(-42).shiftRightUnsigned(32), Int32.ZERO);
+ expect(Int32(-42).shiftRightUnsigned(33), Int32.ZERO);
+ expect(() => Int32(17).shiftRightUnsigned(-1), throwsArgumentError);
+ });
+ });
+
+ group('conversions', () {
+ test('toSigned', () {
+ expect(Int32.ONE.toSigned(2), Int32.ONE);
+ expect(Int32.ONE.toSigned(1), -Int32.ONE);
+ expect(Int32.MAX_VALUE.toSigned(32), Int32.MAX_VALUE);
+ expect(Int32.MIN_VALUE.toSigned(32), Int32.MIN_VALUE);
+ expect(Int32.MAX_VALUE.toSigned(31), -Int32.ONE);
+ expect(Int32.MIN_VALUE.toSigned(31), Int32.ZERO);
+ expect(() => Int32.ONE.toSigned(0), throwsRangeError);
+ expect(() => Int32.ONE.toSigned(33), throwsRangeError);
+ });
+ test('toUnsigned', () {
+ expect(Int32.ONE.toUnsigned(1), Int32.ONE);
+ expect(Int32.ONE.toUnsigned(0), Int32.ZERO);
+ expect(Int32.MAX_VALUE.toUnsigned(32), Int32.MAX_VALUE);
+ expect(Int32.MIN_VALUE.toUnsigned(32), Int32.MIN_VALUE);
+ expect(Int32.MAX_VALUE.toUnsigned(31), Int32.MAX_VALUE);
+ expect(Int32.MIN_VALUE.toUnsigned(31), Int32.ZERO);
+ expect(() => Int32.ONE.toUnsigned(-1), throwsRangeError);
+ expect(() => Int32.ONE.toUnsigned(33), throwsRangeError);
+ });
+ test('toDouble', () {
+ expect(Int32(17).toDouble(), same(17.0));
+ expect(Int32(-17).toDouble(), same(-17.0));
+ });
+ test('toInt', () {
+ expect(Int32(17).toInt(), 17);
+ expect(Int32(-17).toInt(), -17);
+ });
+ test('toInt32', () {
+ expect(Int32(17).toInt32(), Int32(17));
+ expect(Int32(-17).toInt32(), Int32(-17));
+ });
+ test('toInt64', () {
+ expect(Int32(17).toInt64(), Int64(17));
+ expect(Int32(-17).toInt64(), Int64(-17));
+ });
+ test('toBytes', () {
+ expect(Int32(0).toBytes(), [0, 0, 0, 0]);
+ expect(Int32(0x01020304).toBytes(), [4, 3, 2, 1]);
+ expect(Int32(0x04030201).toBytes(), [1, 2, 3, 4]);
+ expect(Int32(-1).toBytes(), [0xff, 0xff, 0xff, 0xff]);
+ });
+ });
+
+ group('parse', () {
+ test('base 10', () {
+ void checkInt(int x) {
+ expect(Int32.parseRadix('$x', 10), Int32(x));
+ }
+
+ checkInt(0);
+ checkInt(1);
+ checkInt(1000);
+ checkInt(12345678);
+ checkInt(2147483647);
+ checkInt(2147483648);
+ checkInt(4294967295);
+ checkInt(4294967296);
+ expect(() => Int32.parseRadix('xyzzy', -1), throwsArgumentError);
+ expect(() => Int32.parseRadix('plugh', 10), throwsFormatException);
+ });
+
+ test('parseRadix', () {
+ void check(String s, int r, String x) {
+ expect(Int32.parseRadix(s, r).toString(), x);
+ }
+
+ check('deadbeef', 16, '-559038737');
+ check('95', 12, '113');
+ });
+
+ test('parseInt', () {
+ expect(Int32.parseInt('0'), Int32(0));
+ expect(Int32.parseInt('1000'), Int32(1000));
+ expect(Int32.parseInt('4294967296'), Int32(4294967296));
+ });
+
+ test('parseHex', () {
+ expect(Int32.parseHex('deadbeef'), Int32(0xdeadbeef));
+ expect(Int32.parseHex('cafebabe'), Int32(0xcafebabe));
+ expect(Int32.parseHex('8badf00d'), Int32(0x8badf00d));
+ });
+ });
+
+ group('string representation', () {
+ test('toString', () {
+ expect(Int32(0).toString(), '0');
+ expect(Int32(1).toString(), '1');
+ expect(Int32(-1).toString(), '-1');
+ expect(Int32(1000).toString(), '1000');
+ expect(Int32(-1000).toString(), '-1000');
+ expect(Int32(123456789).toString(), '123456789');
+ expect(Int32(2147483647).toString(), '2147483647');
+ expect(Int32(2147483648).toString(), '-2147483648');
+ expect(Int32(2147483649).toString(), '-2147483647');
+ expect(Int32(2147483650).toString(), '-2147483646');
+ expect(Int32(-2147483648).toString(), '-2147483648');
+ expect(Int32(-2147483649).toString(), '2147483647');
+ expect(Int32(-2147483650).toString(), '2147483646');
+ });
+ });
+
+ group('toHexString', () {
+ test('returns hexadecimal string representation', () {
+ expect(Int32(-1).toHexString(), '-1');
+ expect((Int32(-1) >> 8).toHexString(), '-1');
+ expect((Int32(-1) << 8).toHexString(), '-100');
+ expect(Int32(123456789).toHexString(), '75bcd15');
+ expect(Int32(-1).shiftRightUnsigned(8).toHexString(), 'ffffff');
+ });
+ });
+
+ group('toRadixString', () {
+ test('returns base n string representation', () {
+ expect(Int32(123456789).toRadixString(5), '223101104124');
+ });
+ });
+}
diff --git a/pkgs/fixnum/test/int64_test.dart b/pkgs/fixnum/test/int64_test.dart
new file mode 100644
index 0000000..e74d893
--- /dev/null
+++ b/pkgs/fixnum/test/int64_test.dart
@@ -0,0 +1,934 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// We permit local variable types in this test because they statically 'assert'
+// that the operations have an expected type.
+//
+// ignore_for_file: omit_local_variable_types
+
+import 'package:fixnum/fixnum.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('fromBytes', () {
+ test('fromBytes', () {
+ void checkBytes(List<int> bytes, int h, int l) {
+ expect(Int64.fromBytes(bytes), Int64.fromInts(h, l));
+ }
+
+ checkBytes([0, 0, 0, 0, 0, 0, 0, 0], 0, 0);
+ checkBytes([1, 0, 0, 0, 0, 0, 0, 0], 0, 1);
+ checkBytes([1, 2, 3, 4, 5, 6, 7, 8], 0x08070605, 0x04030201);
+ checkBytes([0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], 0xffffffff,
+ 0xfffffffe);
+ checkBytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], 0xffffffff,
+ 0xffffffff);
+ });
+ test('fromBytesBigEndian', () {
+ void checkBytes(List<int> bytes, int h, int l) {
+ expect(Int64.fromBytesBigEndian(bytes), Int64.fromInts(h, l));
+ }
+
+ checkBytes([0, 0, 0, 0, 0, 0, 0, 0], 0, 0);
+ checkBytes([0, 0, 0, 0, 0, 0, 0, 1], 0, 1);
+ checkBytes([8, 7, 6, 5, 4, 3, 2, 1], 0x08070605, 0x04030201);
+ checkBytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe], 0xffffffff,
+ 0xfffffffe);
+ checkBytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], 0xffffffff,
+ 0xffffffff);
+ });
+ });
+
+ void argumentErrorTest(
+ Object Function(Int64, Object) op, [
+ Int64 receiver = Int64.ONE,
+ ]) {
+ expect(
+ () => op(receiver, 'foo'),
+ throwsA(
+ isA<ArgumentError>()
+ .having((p0) => p0.toString(), 'toString', contains('"foo"')),
+ ),
+ );
+ }
+
+ group('is-tests', () {
+ test('isEven', () {
+ expect((-Int64.ONE).isEven, false);
+ expect(Int64.ZERO.isEven, true);
+ expect(Int64.ONE.isEven, false);
+ expect(Int64.TWO.isEven, true);
+ });
+ test('isMaxValue', () {
+ expect(Int64.MIN_VALUE.isMaxValue, false);
+ expect(Int64.ZERO.isMaxValue, false);
+ expect(Int64.MAX_VALUE.isMaxValue, true);
+ });
+ test('isMinValue', () {
+ expect(Int64.MIN_VALUE.isMinValue, true);
+ expect(Int64.ZERO.isMinValue, false);
+ expect(Int64.MAX_VALUE.isMinValue, false);
+ });
+ test('isNegative', () {
+ expect(Int64.MIN_VALUE.isNegative, true);
+ expect(Int64.ZERO.isNegative, false);
+ expect(Int64.ONE.isNegative, false);
+ });
+ test('isOdd', () {
+ expect((-Int64.ONE).isOdd, true);
+ expect(Int64.ZERO.isOdd, false);
+ expect(Int64.ONE.isOdd, true);
+ expect(Int64.TWO.isOdd, false);
+ });
+ test('isZero', () {
+ expect(Int64.MIN_VALUE.isZero, false);
+ expect(Int64.ZERO.isZero, true);
+ expect(Int64.MAX_VALUE.isZero, false);
+ });
+ test('bitLength', () {
+ expect(Int64(-2).bitLength, 1);
+ expect((-Int64.ONE).bitLength, 0);
+ expect(Int64.ZERO.bitLength, 0);
+ expect((Int64.ONE << 21).bitLength, 22);
+ expect((Int64.ONE << 22).bitLength, 23);
+ expect((Int64.ONE << 43).bitLength, 44);
+ expect((Int64.ONE << 44).bitLength, 45);
+ expect(Int64(2).bitLength, 2);
+ expect(Int64.MAX_VALUE.bitLength, 63);
+ expect(Int64.MIN_VALUE.bitLength, 63);
+ });
+ });
+
+ group('arithmetic operators', () {
+ Int64 n1 = Int64(1234);
+ Int64 n2 = Int64(9876);
+ Int64 n3 = Int64(-1234);
+ Int64 n4 = Int64(-9876);
+ Int64 n5 = Int64.fromInts(0x12345678, 0xabcdabcd);
+ Int64 n6 = Int64.fromInts(0x77773333, 0x22224444);
+
+ test('+', () {
+ expect(n1 + n2, Int64(11110));
+ expect(n3 + n2, Int64(8642));
+ expect(n3 + n4, Int64(-11110));
+ expect(n5 + n6, Int64.fromInts(0x89ab89ab, 0xcdeff011));
+ expect(Int64.MAX_VALUE + 1, Int64.MIN_VALUE);
+ argumentErrorTest((a, b) => a + b);
+ });
+
+ test('-', () {
+ expect(n1 - n2, Int64(-8642));
+ expect(n3 - n2, Int64(-11110));
+ expect(n3 - n4, Int64(8642));
+ expect(n5 - n6, Int64.fromInts(0x9abd2345, 0x89ab6789));
+ expect(Int64.MIN_VALUE - 1, Int64.MAX_VALUE);
+ argumentErrorTest((a, b) => a - b);
+ });
+
+ test('unary -', () {
+ expect(-n1, Int64(-1234));
+ expect(-Int64.ZERO, Int64.ZERO);
+ });
+
+ test('*', () {
+ expect(Int64(1111) * Int64(3), Int64(3333));
+ expect(Int64(1111) * Int64(-3), Int64(-3333));
+ expect(Int64(-1111) * Int64(3), Int64(-3333));
+ expect(Int64(-1111) * Int64(-3), Int64(3333));
+ expect(Int64(100) * Int64.ZERO, Int64.ZERO);
+
+ expect(
+ Int64.fromInts(0x12345678, 0x12345678) *
+ Int64.fromInts(0x1234, 0x12345678),
+ Int64.fromInts(0x7ff63f7c, 0x1df4d840));
+ expect(
+ Int64.fromInts(0xf2345678, 0x12345678) *
+ Int64.fromInts(0x1234, 0x12345678),
+ Int64.fromInts(0x7ff63f7c, 0x1df4d840));
+ expect(
+ Int64.fromInts(0xf2345678, 0x12345678) *
+ Int64.fromInts(0xffff1234, 0x12345678),
+ Int64.fromInts(0x297e3f7c, 0x1df4d840));
+
+ // RHS Int32
+ expect(Int64(123456789) * Int32(987654321),
+ Int64.fromInts(0x1b13114, 0xfbff5385));
+ expect(Int64(123456789) * Int32(987654321),
+ Int64.fromInts(0x1b13114, 0xfbff5385));
+
+ // Wraparound
+ expect(Int64(123456789) * Int64(987654321),
+ Int64.fromInts(0x1b13114, 0xfbff5385));
+
+ expect(Int64.MIN_VALUE * Int64(2), Int64.ZERO);
+ expect(Int64.MIN_VALUE * Int64(1), Int64.MIN_VALUE);
+ expect(Int64.MIN_VALUE * Int64(-1), Int64.MIN_VALUE);
+ argumentErrorTest((a, b) => a * b);
+ });
+
+ test('~/', () {
+ Int64 deadBeef = Int64.fromInts(0xDEADBEEF, 0xDEADBEEF);
+ Int64 ten = Int64(10);
+
+ expect(deadBeef ~/ ten, Int64.fromInts(0xfcaaf97e, 0x63115fe5));
+ expect(Int64.ONE ~/ Int64.TWO, Int64.ZERO);
+ expect(
+ Int64.MAX_VALUE ~/ Int64.TWO, Int64.fromInts(0x3fffffff, 0xffffffff));
+ expect(Int64.ZERO ~/ Int64(1000), Int64.ZERO);
+ expect(Int64.MIN_VALUE ~/ Int64.MIN_VALUE, Int64.ONE);
+ expect(Int64(1000) ~/ Int64.MIN_VALUE, Int64.ZERO);
+ expect(Int64.MIN_VALUE ~/ Int64(8192), Int64(-1125899906842624));
+ expect(Int64.MIN_VALUE ~/ Int64(8193), Int64(-1125762484664320));
+ expect(Int64(-1000) ~/ Int64(8192), Int64.ZERO);
+ expect(Int64(-1000) ~/ Int64(8193), Int64.ZERO);
+ expect(Int64(-1000000000) ~/ Int64(8192), Int64(-122070));
+ expect(Int64(-1000000000) ~/ Int64(8193), Int64(-122055));
+ expect(Int64(1000000000) ~/ Int64(8192), Int64(122070));
+ expect(Int64(1000000000) ~/ Int64(8193), Int64(122055));
+ expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00000000, 0x00000400),
+ Int64.fromInts(0x1fffff, 0xffffffff));
+ expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00000000, 0x00040000),
+ Int64.fromInts(0x1fff, 0xffffffff));
+ expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00000000, 0x04000000),
+ Int64.fromInts(0x1f, 0xffffffff));
+ expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00000004, 0x00000000),
+ Int64(536870911));
+ expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00000400, 0x00000000),
+ Int64(2097151));
+ expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00040000, 0x00000000),
+ Int64(8191));
+ expect(
+ Int64.MAX_VALUE ~/ Int64.fromInts(0x04000000, 0x00000000), Int64(31));
+ expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00000000, 0x00000300),
+ Int64.fromInts(0x2AAAAA, 0xAAAAAAAA));
+ expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00000000, 0x30000000),
+ Int64.fromInts(0x2, 0xAAAAAAAA));
+ expect(Int64.MAX_VALUE ~/ Int64.fromInts(0x00300000, 0x00000000),
+ Int64(0x2AA));
+ expect(Int64.MAX_VALUE ~/ Int64(0x123456),
+ Int64.fromInts(0x708, 0x002E9501));
+ expect(Int64.MAX_VALUE % Int64(0x123456), Int64(0x3BDA9));
+ expect(Int64(5) ~/ Int64(5), Int64.ONE);
+ expect(Int64(1000) ~/ Int64(3), Int64(333));
+ expect(Int64(1000) ~/ Int64(-3), Int64(-333));
+ expect(Int64(-1000) ~/ Int64(3), Int64(-333));
+ expect(Int64(-1000) ~/ Int64(-3), Int64(333));
+ expect(Int64(3) ~/ Int64(1000), Int64.ZERO);
+ expect(
+ Int64.fromInts(0x12345678, 0x12345678) ~/ Int64.fromInts(0x0, 0x123),
+ Int64.fromInts(0x1003d0, 0xe84f5ae8));
+ expect(
+ Int64.fromInts(0x12345678, 0x12345678) ~/
+ Int64.fromInts(0x1234, 0x12345678),
+ Int64.fromInts(0x0, 0x10003));
+ expect(
+ Int64.fromInts(0xf2345678, 0x12345678) ~/
+ Int64.fromInts(0x1234, 0x12345678),
+ Int64.fromInts(0xffffffff, 0xffff3dfe));
+ expect(
+ Int64.fromInts(0xf2345678, 0x12345678) ~/
+ Int64.fromInts(0xffff1234, 0x12345678),
+ Int64.fromInts(0x0, 0xeda));
+ expect(Int64(829893893) ~/ Int32(1919), Int32(432461));
+ expect(Int64(829893893) ~/ Int64(1919), Int32(432461));
+ expect(Int64(829893893) ~/ 1919, Int32(432461));
+ expect(() => Int64(1) ~/ Int64.ZERO, throwsA(isUnsupportedError));
+ expect(
+ Int64.MIN_VALUE ~/ Int64(2), Int64.fromInts(0xc0000000, 0x00000000));
+ expect(Int64.MIN_VALUE ~/ Int64(1), Int64.MIN_VALUE);
+ expect(Int64.MIN_VALUE ~/ Int64(-1), Int64.MIN_VALUE);
+ expect(() => Int64(17) ~/ Int64.ZERO, throwsA(isUnsupportedError));
+ argumentErrorTest((a, b) => a ~/ b);
+ });
+
+ test('%', () {
+ // Define % as Euclidean mod, with positive result for all arguments
+ expect(Int64.ZERO % Int64(1000), Int64.ZERO);
+ expect(Int64.MIN_VALUE % Int64.MIN_VALUE, Int64.ZERO);
+ expect(Int64(1000) % Int64.MIN_VALUE, Int64(1000));
+ expect(Int64.MIN_VALUE % Int64(8192), Int64.ZERO);
+ expect(Int64.MIN_VALUE % Int64(8193), Int64(6145));
+ expect(Int64(-1000) % Int64(8192), Int64(7192));
+ expect(Int64(-1000) % Int64(8193), Int64(7193));
+ expect(Int64(-1000000000) % Int64(8192), Int64(5632));
+ expect(Int64(-1000000000) % Int64(8193), Int64(4808));
+ expect(Int64(1000000000) % Int64(8192), Int64(2560));
+ expect(Int64(1000000000) % Int64(8193), Int64(3385));
+ expect(Int64.MAX_VALUE % Int64.fromInts(0x00000000, 0x00000400),
+ Int64.fromInts(0x0, 0x3ff));
+ expect(Int64.MAX_VALUE % Int64.fromInts(0x00000000, 0x00040000),
+ Int64.fromInts(0x0, 0x3ffff));
+ expect(Int64.MAX_VALUE % Int64.fromInts(0x00000000, 0x04000000),
+ Int64.fromInts(0x0, 0x3ffffff));
+ expect(Int64.MAX_VALUE % Int64.fromInts(0x00000004, 0x00000000),
+ Int64.fromInts(0x3, 0xffffffff));
+ expect(Int64.MAX_VALUE % Int64.fromInts(0x00000400, 0x00000000),
+ Int64.fromInts(0x3ff, 0xffffffff));
+ expect(Int64.MAX_VALUE % Int64.fromInts(0x00040000, 0x00000000),
+ Int64.fromInts(0x3ffff, 0xffffffff));
+ expect(Int64.MAX_VALUE % Int64.fromInts(0x04000000, 0x00000000),
+ Int64.fromInts(0x3ffffff, 0xffffffff));
+ expect(Int64(0x12345678).remainder(Int64(0x22)),
+ Int64(0x12345678.remainder(0x22)));
+ expect(Int64(0x12345678).remainder(Int64(-0x22)),
+ Int64(0x12345678.remainder(-0x22)));
+ expect(Int64(-0x12345678).remainder(Int64(-0x22)),
+ Int64(-0x12345678.remainder(-0x22)));
+ expect(Int64(-0x12345678).remainder(Int64(0x22)),
+ Int64(-0x12345678.remainder(0x22)));
+ expect(Int32(0x12345678).remainder(Int64(0x22)),
+ Int64(0x12345678.remainder(0x22)));
+ argumentErrorTest((a, b) => a % b);
+ });
+
+ test('clamp', () {
+ Int64 val = Int64(17);
+ expect(val.clamp(20, 30), Int64(20));
+ expect(val.clamp(10, 20), Int64(17));
+ expect(val.clamp(10, 15), Int64(15));
+
+ expect(val.clamp(Int32(20), Int32(30)), Int64(20));
+ expect(val.clamp(Int32(10), Int32(20)), Int64(17));
+ expect(val.clamp(Int32(10), Int32(15)), Int64(15));
+
+ expect(val.clamp(Int64(20), Int64(30)), Int64(20));
+ expect(val.clamp(Int64(10), Int64(20)), Int64(17));
+ expect(val.clamp(Int64(10), Int64(15)), Int64(15));
+ expect(val.clamp(Int64.MIN_VALUE, Int64(30)), Int64(17));
+ expect(val.clamp(Int64(10), Int64.MAX_VALUE), Int64(17));
+
+ expect(() => val.clamp(1, 'b'), throwsA(isArgumentError));
+ expect(() => val.clamp('a', 1), throwsA(isArgumentError));
+ });
+ });
+
+ group('leading/trailing zeros', () {
+ test('numberOfLeadingZeros', () {
+ void checkZeros(Int64 value, int zeros) {
+ expect(value.numberOfLeadingZeros(), zeros);
+ }
+
+ checkZeros(Int64(0), 64);
+ checkZeros(Int64(1), 63);
+ checkZeros(Int64.fromInts(0x00000000, 0x003fffff), 42);
+ checkZeros(Int64.fromInts(0x00000000, 0x00400000), 41);
+ checkZeros(Int64.fromInts(0x00000fff, 0xffffffff), 20);
+ checkZeros(Int64.fromInts(0x00001000, 0x00000000), 19);
+ checkZeros(Int64.fromInts(0x7fffffff, 0xffffffff), 1);
+ checkZeros(Int64(-1), 0);
+ });
+
+ test('numberOfTrailingZeros', () {
+ void checkZeros(Int64 value, int zeros) {
+ expect(value.numberOfTrailingZeros(), zeros);
+ }
+
+ checkZeros(Int64(-1), 0);
+ checkZeros(Int64(1), 0);
+ checkZeros(Int64(2), 1);
+ checkZeros(Int64.fromInts(0x00000000, 0x00200000), 21);
+ checkZeros(Int64.fromInts(0x00000000, 0x00400000), 22);
+ checkZeros(Int64.fromInts(0x00000800, 0x00000000), 43);
+ checkZeros(Int64.fromInts(0x00001000, 0x00000000), 44);
+ checkZeros(Int64.fromInts(0x80000000, 0x00000000), 63);
+ checkZeros(Int64(0), 64);
+ });
+ });
+
+ group('comparison operators', () {
+ Int64 largeNeg = Int64.fromInts(0x82341234, 0x0);
+ Int64 largePos = Int64.fromInts(0x12341234, 0x0);
+ Int64 largePosPlusOne = largePos + Int64(1);
+
+ test('<', () {
+ expect(Int64(10) < Int64(11), true);
+ expect(Int64(10) < Int64(10), false);
+ expect(Int64(10) < Int64(9), false);
+ expect(Int64(10) < Int32(11), true);
+ expect(Int64(10) < Int32(10), false);
+ expect(Int64(10) < Int32(9), false);
+ expect(Int64(-10) < Int64(-11), false);
+ expect(Int64.MIN_VALUE < Int64.ZERO, true);
+ expect(largeNeg < largePos, true);
+ expect(largePos < largePosPlusOne, true);
+ expect(largePos < largePos, false);
+ expect(largePosPlusOne < largePos, false);
+ expect(Int64.MIN_VALUE < Int64.MAX_VALUE, true);
+ expect(Int64.MAX_VALUE < Int64.MIN_VALUE, false);
+ argumentErrorTest((a, b) => a < b);
+ });
+
+ test('<=', () {
+ expect(Int64(10) <= Int64(11), true);
+ expect(Int64(10) <= Int64(10), true);
+ expect(Int64(10) <= Int64(9), false);
+ expect(Int64(10) <= Int32(11), true);
+ expect(Int64(10) <= Int32(10), true);
+ expect(Int64(10) <= Int64(9), false);
+ expect(Int64(-10) <= Int64(-11), false);
+ expect(Int64(-10) <= Int64(-10), true);
+ expect(largeNeg <= largePos, true);
+ expect(largePos <= largeNeg, false);
+ expect(largePos <= largePosPlusOne, true);
+ expect(largePos <= largePos, true);
+ expect(largePosPlusOne <= largePos, false);
+ expect(Int64.MIN_VALUE <= Int64.MAX_VALUE, true);
+ expect(Int64.MAX_VALUE <= Int64.MIN_VALUE, false);
+ argumentErrorTest((a, b) => a <= b);
+ });
+
+ test('==', () {
+ expect(Int64(0), equals(Int64(0)));
+ expect(Int64(0), isNot(equals(Int64(1))));
+ expect(Int64(0), equals(Int32(0)));
+ expect(Int64(0), isNot(equals(Int32(1))));
+ expect(Int64(0) == 0, isTrue); // ignore: unrelated_type_equality_checks
+ expect(Int64(0), isNot(equals(1)));
+ expect(Int64(10), isNot(equals(Int64(11))));
+ expect(Int64(10), equals(Int64(10)));
+ expect(Int64(10), isNot(equals(Int64(9))));
+ expect(Int64(10), isNot(equals(Int32(11))));
+ expect(Int64(10), equals(Int32(10)));
+ expect(Int64(10), isNot(equals(Int32(9))));
+ expect(Int64(10), isNot(equals(11)));
+ expect(Int64(10) == 10, isTrue); // ignore: unrelated_type_equality_checks
+ expect(Int64(10), isNot(equals(9)));
+ expect(Int64(-10), equals(Int64(-10)));
+ expect(Int64(-10) != Int64(-10), false);
+ expect(
+ Int64(-10) == -10, isTrue); // ignore: unrelated_type_equality_checks
+ expect(Int64(-10), isNot(equals(-9)));
+ expect(largePos, equals(largePos));
+ expect(largePos, isNot(equals(largePosPlusOne)));
+ expect(largePosPlusOne, isNot(equals(largePos)));
+ expect(Int64.MIN_VALUE, isNot(equals(Int64.MAX_VALUE)));
+ expect(Int64(17), isNot(equals(Object())));
+ expect(Int64(17), isNot(equals(null)));
+ });
+
+ test('>=', () {
+ expect(Int64(10) >= Int64(11), false);
+ expect(Int64(10) >= Int64(10), true);
+ expect(Int64(10) >= Int64(9), true);
+ expect(Int64(10) >= Int32(11), false);
+ expect(Int64(10) >= Int32(10), true);
+ expect(Int64(10) >= Int32(9), true);
+ expect(Int64(-10) >= Int64(-11), true);
+ expect(Int64(-10) >= Int64(-10), true);
+ expect(largePos >= largeNeg, true);
+ expect(largeNeg >= largePos, false);
+ expect(largePos >= largePosPlusOne, false);
+ expect(largePos >= largePos, true);
+ expect(largePosPlusOne >= largePos, true);
+ expect(Int64.MIN_VALUE >= Int64.MAX_VALUE, false);
+ expect(Int64.MAX_VALUE >= Int64.MIN_VALUE, true);
+ argumentErrorTest((a, b) => a >= b);
+ });
+
+ test('>', () {
+ expect(Int64(10) > Int64(11), false);
+ expect(Int64(10) > Int64(10), false);
+ expect(Int64(10) > Int64(9), true);
+ expect(Int64(10) > Int32(11), false);
+ expect(Int64(10) > Int32(10), false);
+ expect(Int64(10) > Int32(9), true);
+ expect(Int64(-10) > Int64(-11), true);
+ expect(Int64(10) > Int64(-11), true);
+ expect(Int64(-10) > Int64(11), false);
+ expect(largePos > largeNeg, true);
+ expect(largeNeg > largePos, false);
+ expect(largePos > largePosPlusOne, false);
+ expect(largePos > largePos, false);
+ expect(largePosPlusOne > largePos, true);
+ expect(Int64.ZERO > Int64.MIN_VALUE, true);
+ expect(Int64.MIN_VALUE > Int64.MAX_VALUE, false);
+ expect(Int64.MAX_VALUE > Int64.MIN_VALUE, true);
+ argumentErrorTest((a, b) => a > b);
+ });
+ });
+
+ group('bitwise operators', () {
+ Int64 n1 = Int64(1234);
+ Int64 n2 = Int64(9876);
+ Int64 n3 = Int64(-1234);
+ Int64 n4 = Int64(0x1234) << 32;
+ Int64 n5 = Int64(0x9876) << 32;
+
+ test('&', () {
+ expect(n1 & n2, Int64(1168));
+ expect(n3 & n2, Int64(8708));
+ expect(n4 & n5, Int64(0x1034) << 32);
+ argumentErrorTest((a, b) => a & b);
+ });
+
+ test('|', () {
+ expect(n1 | n2, Int64(9942));
+ expect(n3 | n2, Int64(-66));
+ expect(n4 | n5, Int64(0x9a76) << 32);
+ argumentErrorTest((a, b) => a | b);
+ });
+
+ test('^', () {
+ expect(n1 ^ n2, Int64(8774));
+ expect(n3 ^ n2, Int64(-8774));
+ expect(n4 ^ n5, Int64(0x8a42) << 32);
+ argumentErrorTest((a, b) => a ^ b);
+ });
+
+ test('~', () {
+ expect(-Int64(1), Int64(-1));
+ expect(-Int64(-1), Int64(1));
+ expect(-Int64.MIN_VALUE, Int64.MIN_VALUE);
+
+ expect(~n1, Int64(-1235));
+ expect(~n2, Int64(-9877));
+ expect(~n3, Int64(1233));
+ expect(~n4, Int64.fromInts(0xffffedcb, 0xffffffff));
+ expect(~n5, Int64.fromInts(0xffff6789, 0xffffffff));
+ });
+ });
+
+ group('bitshift operators', () {
+ test('<<', () {
+ expect(Int64.fromInts(0x12341234, 0x45674567) << 10,
+ Int64.fromInts(0xd048d115, 0x9d159c00));
+ expect(Int64.fromInts(0x92341234, 0x45674567) << 10,
+ Int64.fromInts(0xd048d115, 0x9d159c00));
+ expect(Int64(-1) << 5, Int64(-32));
+ expect(Int64(-1) << 0, Int64(-1));
+ expect(Int64(42) << 64, Int64.ZERO);
+ expect(Int64(42) << 65, Int64.ZERO);
+ expect(() => Int64(17) << -1, throwsArgumentError);
+ });
+
+ test('>>', () {
+ expect((Int64.MIN_VALUE >> 13).toString(), '-1125899906842624');
+ expect(Int64.fromInts(0x12341234, 0x45674567) >> 10,
+ Int64.fromInts(0x48d04, 0x8d1159d1));
+ expect(Int64.fromInts(0x92341234, 0x45674567) >> 10,
+ Int64.fromInts(0xffe48d04, 0x8d1159d1));
+ expect(Int64.fromInts(0xFFFFFFF, 0xFFFFFFFF) >> 34, Int64(67108863));
+ expect(Int64(42) >> 64, Int64.ZERO);
+ expect(Int64(42) >> 65, Int64.ZERO);
+ for (int n = 0; n <= 66; n++) {
+ expect(Int64(-1) >> n, Int64(-1));
+ }
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 8,
+ Int64.fromInts(0x00723456, 0x789abcde));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 16,
+ Int64.fromInts(0x00007234, 0x56789abc));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 24,
+ Int64.fromInts(0x00000072, 0x3456789a));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 28,
+ Int64.fromInts(0x00000007, 0x23456789));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 32,
+ Int64.fromInts(0x00000000, 0x72345678));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 36,
+ Int64.fromInts(0x00000000, 0x07234567));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 40,
+ Int64.fromInts(0x00000000, 0x00723456));
+ expect(Int64.fromInts(0x72345678, 0x9abcde00) >> 44,
+ Int64.fromInts(0x00000000, 0x00072345));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0) >> 48,
+ Int64.fromInts(0x00000000, 0x00007234));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 8,
+ Int64.fromInts(0xff923456, 0x789abcde));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 16,
+ Int64.fromInts(0xffff9234, 0x56789abc));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 24,
+ Int64.fromInts(0xffffff92, 0x3456789a));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 28,
+ Int64.fromInts(0xfffffff9, 0x23456789));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 32,
+ Int64.fromInts(0xffffffff, 0x92345678));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 36,
+ Int64.fromInts(0xffffffff, 0xf9234567));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 40,
+ Int64.fromInts(0xffffffff, 0xff923456));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 44,
+ Int64.fromInts(0xffffffff, 0xfff92345));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0) >> 48,
+ Int64.fromInts(0xffffffff, 0xffff9234));
+ expect(() => Int64(17) >> -1, throwsArgumentError);
+ });
+
+ test('shiftRightUnsigned', () {
+ expect(Int64.fromInts(0x12341234, 0x45674567).shiftRightUnsigned(10),
+ Int64.fromInts(0x48d04, 0x8d1159d1));
+ expect(Int64.fromInts(0x92341234, 0x45674567).shiftRightUnsigned(10),
+ Int64.fromInts(0x248d04, 0x8d1159d1));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(8),
+ Int64.fromInts(0x00723456, 0x789abcde));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(16),
+ Int64.fromInts(0x00007234, 0x56789abc));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(24),
+ Int64.fromInts(0x00000072, 0x3456789a));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(28),
+ Int64.fromInts(0x00000007, 0x23456789));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(32),
+ Int64.fromInts(0x00000000, 0x72345678));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(36),
+ Int64.fromInts(0x00000000, 0x07234567));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(40),
+ Int64.fromInts(0x00000000, 0x00723456));
+ expect(Int64.fromInts(0x72345678, 0x9abcde00).shiftRightUnsigned(44),
+ Int64.fromInts(0x00000000, 0x00072345));
+ expect(Int64.fromInts(0x72345678, 0x9abcdef0).shiftRightUnsigned(48),
+ Int64.fromInts(0x00000000, 0x00007234));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(8),
+ Int64.fromInts(0x00923456, 0x789abcde));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(16),
+ Int64.fromInts(0x00009234, 0x56789abc));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(24),
+ Int64.fromInts(0x00000092, 0x3456789a));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(28),
+ Int64.fromInts(0x00000009, 0x23456789));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(32),
+ Int64.fromInts(0x00000000, 0x92345678));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(36),
+ Int64.fromInts(0x00000000, 0x09234567));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(40),
+ Int64.fromInts(0x00000000, 0x00923456));
+ expect(Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(44),
+ Int64.fromInts(0x00000000, 0x00092345));
+ expect(Int64.fromInts(0x00000000, 0x00009234),
+ Int64.fromInts(0x92345678, 0x9abcdef0).shiftRightUnsigned(48));
+ expect(Int64(-1).shiftRightUnsigned(64), Int64.ZERO);
+ expect(Int64(1).shiftRightUnsigned(64), Int64.ZERO);
+ expect(() => Int64(17).shiftRightUnsigned(-1), throwsArgumentError);
+ });
+
+ test('overflow', () {
+ expect((Int64(1) << 63) >> 1, -Int64.fromInts(0x40000000, 0x00000000));
+ expect((Int64(-1) << 32) << 32, Int64(0));
+ expect(Int64.MIN_VALUE << 0, Int64.MIN_VALUE);
+ expect(Int64.MIN_VALUE << 1, Int64(0));
+ expect(
+ (-Int64.fromInts(8, 0)) >> 1, Int64.fromInts(0xfffffffc, 0x00000000));
+ expect((-Int64.fromInts(8, 0)).shiftRightUnsigned(1),
+ Int64.fromInts(0x7ffffffc, 0x0));
+ });
+ });
+
+ group('conversions', () {
+ test('toSigned', () {
+ expect((Int64.ONE << 44).toSigned(46), Int64.ONE << 44);
+ expect((Int64.ONE << 44).toSigned(45), -(Int64.ONE << 44));
+ expect((Int64.ONE << 22).toSigned(24), Int64.ONE << 22);
+ expect((Int64.ONE << 22).toSigned(23), -(Int64.ONE << 22));
+ expect(Int64.ONE.toSigned(2), Int64.ONE);
+ expect(Int64.ONE.toSigned(1), -Int64.ONE);
+ expect(Int64.MAX_VALUE.toSigned(64), Int64.MAX_VALUE);
+ expect(Int64.MIN_VALUE.toSigned(64), Int64.MIN_VALUE);
+ expect(Int64.MAX_VALUE.toSigned(63), -Int64.ONE);
+ expect(Int64.MIN_VALUE.toSigned(63), Int64.ZERO);
+ expect(() => Int64.ONE.toSigned(0), throwsRangeError);
+ expect(() => Int64.ONE.toSigned(65), throwsRangeError);
+ });
+ test('toUnsigned', () {
+ expect((Int64.ONE << 44).toUnsigned(45), Int64.ONE << 44);
+ expect((Int64.ONE << 44).toUnsigned(44), Int64.ZERO);
+ expect((Int64.ONE << 22).toUnsigned(23), Int64.ONE << 22);
+ expect((Int64.ONE << 22).toUnsigned(22), Int64.ZERO);
+ expect(Int64.ONE.toUnsigned(1), Int64.ONE);
+ expect(Int64.ONE.toUnsigned(0), Int64.ZERO);
+ expect(Int64.MAX_VALUE.toUnsigned(64), Int64.MAX_VALUE);
+ expect(Int64.MIN_VALUE.toUnsigned(64), Int64.MIN_VALUE);
+ expect(Int64.MAX_VALUE.toUnsigned(63), Int64.MAX_VALUE);
+ expect(Int64.MIN_VALUE.toUnsigned(63), Int64.ZERO);
+ expect(() => Int64.ONE.toUnsigned(-1), throwsRangeError);
+ expect(() => Int64.ONE.toUnsigned(65), throwsRangeError);
+ });
+ test('toDouble', () {
+ expect(Int64(0).toDouble(), same(0.0));
+ expect(Int64(100).toDouble(), same(100.0));
+ expect(Int64(-100).toDouble(), same(-100.0));
+ expect(Int64(2147483647).toDouble(), same(2147483647.0));
+ expect(Int64(2147483648).toDouble(), same(2147483648.0));
+ expect(Int64(-2147483647).toDouble(), same(-2147483647.0));
+ expect(Int64(-2147483648).toDouble(), same(-2147483648.0));
+ expect(Int64(4503599627370495).toDouble(), same(4503599627370495.0));
+ expect(Int64(4503599627370496).toDouble(), same(4503599627370496.0));
+ expect(Int64(-4503599627370495).toDouble(), same(-4503599627370495.0));
+ expect(Int64(-4503599627370496).toDouble(), same(-4503599627370496.0));
+ expect(Int64.parseInt('-10000000000000000').toDouble().toStringAsFixed(1),
+ '-10000000000000000.0');
+ expect(Int64.parseInt('-10000000000000001').toDouble().toStringAsFixed(1),
+ '-10000000000000000.0');
+ expect(Int64.parseInt('-10000000000000002').toDouble().toStringAsFixed(1),
+ '-10000000000000002.0');
+ expect(Int64.parseInt('-10000000000000003').toDouble().toStringAsFixed(1),
+ '-10000000000000004.0');
+ expect(Int64.parseInt('-10000000000000004').toDouble().toStringAsFixed(1),
+ '-10000000000000004.0');
+ expect(Int64.parseInt('-10000000000000005').toDouble().toStringAsFixed(1),
+ '-10000000000000004.0');
+ expect(Int64.parseInt('-10000000000000006').toDouble().toStringAsFixed(1),
+ '-10000000000000006.0');
+ expect(Int64.parseInt('-10000000000000007').toDouble().toStringAsFixed(1),
+ '-10000000000000008.0');
+ expect(Int64.parseInt('-10000000000000008').toDouble().toStringAsFixed(1),
+ '-10000000000000008.0');
+ });
+
+ test('toInt', () {
+ expect(Int64(0).toInt(), 0);
+ expect(Int64(100).toInt(), 100);
+ expect(Int64(-100).toInt(), -100);
+ expect(Int64(2147483647).toInt(), 2147483647);
+ expect(Int64(2147483648).toInt(), 2147483648);
+ expect(Int64(-2147483647).toInt(), -2147483647);
+ expect(Int64(-2147483648).toInt(), -2147483648);
+ expect(Int64(4503599627370495).toInt(), 4503599627370495);
+ expect(Int64(4503599627370496).toInt(), 4503599627370496);
+ expect(Int64(-4503599627370495).toInt(), -4503599627370495);
+ expect(Int64(-4503599627370496).toInt(), -4503599627370496);
+ });
+
+ test('toInt32', () {
+ expect(Int64(0).toInt32(), Int32(0));
+ expect(Int64(1).toInt32(), Int32(1));
+ expect(Int64(-1).toInt32(), Int32(-1));
+ expect(Int64(2147483647).toInt32(), Int32(2147483647));
+ expect(Int64(2147483648).toInt32(), Int32(-2147483648));
+ expect(Int64(2147483649).toInt32(), Int32(-2147483647));
+ expect(Int64(2147483650).toInt32(), Int32(-2147483646));
+ expect(Int64(-2147483648).toInt32(), Int32(-2147483648));
+ expect(Int64(-2147483649).toInt32(), Int32(2147483647));
+ expect(Int64(-2147483650).toInt32(), Int32(2147483646));
+ expect(Int64(-2147483651).toInt32(), Int32(2147483645));
+ });
+
+ test('toBytes', () {
+ expect(Int64(0).toBytes(), [0, 0, 0, 0, 0, 0, 0, 0]);
+ expect(Int64.fromInts(0x08070605, 0x04030201).toBytes(),
+ [1, 2, 3, 4, 5, 6, 7, 8]);
+ expect(Int64.fromInts(0x01020304, 0x05060708).toBytes(),
+ [8, 7, 6, 5, 4, 3, 2, 1]);
+ expect(Int64(-1).toBytes(),
+ [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
+ });
+ });
+
+ test('JavaScript 53-bit integer boundary', () {
+ Int64 factorial(Int64 n) {
+ if (n.isZero) {
+ return Int64(1);
+ } else {
+ return n * factorial(n - Int64(1));
+ }
+ }
+
+ Int64 fact18 = factorial(Int64(18));
+ Int64 fact17 = factorial(Int64(17));
+ expect(fact18 ~/ fact17, Int64(18));
+ });
+
+ test('min, max values', () {
+ expect(Int64(1) << 63, Int64.MIN_VALUE);
+ expect(-(Int64.MIN_VALUE + Int64(1)), Int64.MAX_VALUE);
+ });
+
+ test('negation', () {
+ void check(int n) {
+ // Sign change should commute with conversion.
+ expect(-Int64(-n), Int64(n));
+ expect(Int64(-n), -Int64(n));
+ }
+
+ check(10);
+ check(1000000000000000000);
+ check(9223372036854774784); // Near Int64.MAX_VALUE with exact double value.
+ check(-9223372036854775808); // Int64.MIN_VALUE. Is exact double.
+ });
+
+ group('parse', () {
+ test('parseRadix10', () {
+ void checkInt(int x) {
+ expect(Int64.parseRadix('$x', 10), Int64(x));
+ expect(Int64.tryParseRadix('$x', 10), Int64(x));
+ }
+
+ checkInt(0);
+ checkInt(1);
+ checkInt(-1);
+ checkInt(1000);
+ checkInt(12345678);
+ checkInt(-12345678);
+ checkInt(2147483647);
+ checkInt(2147483648);
+ checkInt(-2147483647);
+ checkInt(-2147483648);
+ checkInt(4294967295);
+ checkInt(4294967296);
+ checkInt(-4294967295);
+ checkInt(-4294967296);
+
+ expect(() => Int64.parseRadix('xyzzy', -1), throwsArgumentError);
+ expect(() => Int64.parseRadix('plugh', 10), throwsFormatException);
+ expect(() => Int64.parseRadix('', 10), throwsFormatException);
+ expect(() => Int64.parseRadix('-', 10), throwsFormatException);
+
+ expect(() => Int64.tryParseRadix('xyzzy', -1), throwsArgumentError);
+ expect(Int64.tryParseRadix('plugh', 10), null);
+ expect(Int64.tryParseRadix('', 10), null);
+ expect(Int64.tryParseRadix('-', 10), null);
+ });
+
+ test('parseHex', () {
+ void checkHex(String hexStr, int h, int l) {
+ expect(Int64.parseHex(hexStr), Int64.fromInts(h, l));
+ expect(Int64.tryParseHex(hexStr), Int64.fromInts(h, l));
+ }
+
+ checkHex('0', 0, 0);
+ checkHex('-0', 0, 0);
+ checkHex('00', 0, 0);
+ checkHex('01', 0, 1);
+ checkHex('-01', 0xffffffff, 0xffffffff);
+ checkHex('0a', 0, 10);
+ checkHex('0A', 0, 10);
+ checkHex('10', 0, 16);
+ checkHex('3FFFFF', 0, 0x3fffff);
+ checkHex('400000', 0, 0x400000);
+ checkHex('FFFFFFFFFFF', 0xfff, 0xffffffff);
+ checkHex('FFFFFFFFFFFFFFFE', 0xffffffff, 0xfffffffe);
+ checkHex('FFFFFFFFFFFFFFFF', 0xffffffff, 0xffffffff);
+
+ expect(() => Int64.parseHex(''), throwsFormatException);
+ expect(() => Int64.parseHex('-'), throwsFormatException);
+
+ expect(Int64.tryParseHex(''), null);
+ expect(Int64.tryParseHex('-'), null);
+ });
+
+ test('parseRadix', () {
+ void check(String s, int r, String x) {
+ expect(Int64.parseRadix(s, r).toString(), x);
+ expect(Int64.tryParseRadix(s, r).toString(), x);
+ }
+
+ check('ghoul', 36, '27699213');
+ check('ghoul', 35, '24769346');
+ // Min and max value.
+ check('-9223372036854775808', 10, '-9223372036854775808');
+ check('9223372036854775807', 10, '9223372036854775807');
+ // Overflow during parsing.
+ check('9223372036854775808', 10, '-9223372036854775808');
+
+ expect(() => Int64.parseRadix('0', 1), throwsRangeError);
+ expect(() => Int64.parseRadix('0', 37), throwsRangeError);
+ expect(() => Int64.parseRadix('xyzzy', -1), throwsRangeError);
+ expect(() => Int64.parseRadix('xyzzy', 10), throwsFormatException);
+
+ expect(() => Int64.tryParseRadix('0', 1), throwsRangeError);
+ expect(() => Int64.tryParseRadix('0', 37), throwsRangeError);
+ expect(() => Int64.tryParseRadix('xyzzy', -1), throwsRangeError);
+ expect(Int64.tryParseRadix('xyzzy', 10), null);
+ });
+
+ test('parseRadixN', () {
+ void check(String s, int r) {
+ expect(Int64.parseRadix(s, r).toRadixString(r), s);
+ expect(Int64.tryParseRadix(s, r)!.toRadixString(r), s);
+ }
+
+ check('2ppp111222333', 33); // This value & radix requires three chunks.
+ });
+ });
+
+ group('string representation', () {
+ test('toString', () {
+ expect(Int64(0).toString(), '0');
+ expect(Int64(1).toString(), '1');
+ expect(Int64(-1).toString(), '-1');
+ expect(Int64(-10).toString(), '-10');
+ expect(Int64.MIN_VALUE.toString(), '-9223372036854775808');
+ expect(Int64.MAX_VALUE.toString(), '9223372036854775807');
+
+ int top = 922337201;
+ int bottom = 967490662;
+ Int64 fullnum = (Int64(1000000000) * Int64(top)) + Int64(bottom);
+ expect(fullnum.toString(), '922337201967490662');
+ expect((-fullnum).toString(), '-922337201967490662');
+ expect(Int64(123456789).toString(), '123456789');
+ });
+
+ test('toHexString', () {
+ Int64 deadbeef12341234 = Int64.fromInts(0xDEADBEEF, 0x12341234);
+ expect(Int64.ZERO.toHexString(), '0');
+ expect(deadbeef12341234.toHexString(), 'DEADBEEF12341234');
+ expect(Int64.fromInts(0x17678A7, 0xDEF01234).toHexString(),
+ '17678A7DEF01234');
+ expect(Int64(123456789).toHexString(), '75BCD15');
+ });
+
+ test('toRadixString', () {
+ expect(Int64(123456789).toRadixString(5), '223101104124');
+ expect(Int64.MIN_VALUE.toRadixString(2),
+ '-1000000000000000000000000000000000000000000000000000000000000000');
+ expect(Int64.MIN_VALUE.toRadixString(3),
+ '-2021110011022210012102010021220101220222');
+ expect(Int64.MIN_VALUE.toRadixString(4),
+ '-20000000000000000000000000000000');
+ expect(Int64.MIN_VALUE.toRadixString(5), '-1104332401304422434310311213');
+ expect(Int64.MIN_VALUE.toRadixString(6), '-1540241003031030222122212');
+ expect(Int64.MIN_VALUE.toRadixString(7), '-22341010611245052052301');
+ expect(Int64.MIN_VALUE.toRadixString(8), '-1000000000000000000000');
+ expect(Int64.MIN_VALUE.toRadixString(9), '-67404283172107811828');
+ expect(Int64.MIN_VALUE.toRadixString(10), '-9223372036854775808');
+ expect(Int64.MIN_VALUE.toRadixString(11), '-1728002635214590698');
+ expect(Int64.MIN_VALUE.toRadixString(12), '-41a792678515120368');
+ expect(Int64.MIN_VALUE.toRadixString(13), '-10b269549075433c38');
+ expect(Int64.MIN_VALUE.toRadixString(14), '-4340724c6c71dc7a8');
+ expect(Int64.MIN_VALUE.toRadixString(15), '-160e2ad3246366808');
+ expect(Int64.MIN_VALUE.toRadixString(16), '-8000000000000000');
+ expect(Int64.MAX_VALUE.toRadixString(2),
+ '111111111111111111111111111111111111111111111111111111111111111');
+ expect(Int64.MAX_VALUE.toRadixString(3),
+ '2021110011022210012102010021220101220221');
+ expect(
+ Int64.MAX_VALUE.toRadixString(4), '13333333333333333333333333333333');
+ expect(Int64.MAX_VALUE.toRadixString(5), '1104332401304422434310311212');
+ expect(Int64.MAX_VALUE.toRadixString(6), '1540241003031030222122211');
+ expect(Int64.MAX_VALUE.toRadixString(7), '22341010611245052052300');
+ expect(Int64.MAX_VALUE.toRadixString(8), '777777777777777777777');
+ expect(Int64.MAX_VALUE.toRadixString(9), '67404283172107811827');
+ expect(Int64.MAX_VALUE.toRadixString(10), '9223372036854775807');
+ expect(Int64.MAX_VALUE.toRadixString(11), '1728002635214590697');
+ expect(Int64.MAX_VALUE.toRadixString(12), '41a792678515120367');
+ expect(Int64.MAX_VALUE.toRadixString(13), '10b269549075433c37');
+ expect(Int64.MAX_VALUE.toRadixString(14), '4340724c6c71dc7a7');
+ expect(Int64.MAX_VALUE.toRadixString(15), '160e2ad3246366807');
+ expect(Int64.MAX_VALUE.toRadixString(16), '7fffffffffffffff');
+ expect(() => Int64(42).toRadixString(-1), throwsArgumentError);
+ expect(() => Int64(42).toRadixString(0), throwsArgumentError);
+ expect(() => Int64(42).toRadixString(37), throwsArgumentError);
+ });
+
+ test('toStringUnsigned', () {
+ List<Int64> values = [];
+ for (int high = 0; high < 16; high++) {
+ for (int low = -2; low <= 2; low++) {
+ values.add((Int64(high) << (64 - 4)) + Int64(low));
+ }
+ }
+
+ for (Int64 value in values) {
+ for (int radix = 2; radix <= 36; radix++) {
+ String s1 = value.toRadixStringUnsigned(radix);
+ Int64 v2 = Int64.parseRadix(s1, radix);
+ expect(v2, value);
+ String s2 = v2.toRadixStringUnsigned(radix);
+ expect(s2, s1);
+ }
+ String s3 = value.toStringUnsigned();
+ Int64 v4 = Int64.parseInt(s3);
+ expect(v4, value);
+ String s4 = v4.toStringUnsigned();
+ expect(s4, s3);
+ }
+ });
+ });
+}
diff --git a/pkgs/fixnum/test/int_64_vm_test.dart b/pkgs/fixnum/test/int_64_vm_test.dart
new file mode 100644
index 0000000..2d28da3
--- /dev/null
+++ b/pkgs/fixnum/test/int_64_vm_test.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@TestOn('vm')
+library;
+
+import 'package:fixnum/fixnum.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('conversions', () {
+ test('toInt', () {
+ expect(Int64.parseInt('-10000000000000000').toInt(),
+ same(-10000000000000000));
+ expect(Int64.parseInt('-10000000000000001').toInt(),
+ same(-10000000000000001));
+ expect(Int64.parseInt('-10000000000000002').toInt(),
+ same(-10000000000000002));
+ expect(Int64.parseInt('-10000000000000003').toInt(),
+ same(-10000000000000003));
+ expect(Int64.parseInt('-10000000000000004').toInt(),
+ same(-10000000000000004));
+ expect(Int64.parseInt('-10000000000000005').toInt(),
+ same(-10000000000000005));
+ expect(Int64.parseInt('-10000000000000006').toInt(),
+ same(-10000000000000006));
+ expect(Int64.parseInt('-10000000000000007').toInt(),
+ same(-10000000000000007));
+ expect(Int64.parseInt('-10000000000000008').toInt(),
+ same(-10000000000000008));
+ });
+ });
+
+ test('', () {
+ void check(int n) {
+ // Sign change should commute with conversion.
+ expect(-Int64(-n), Int64(n));
+ expect(Int64(-n), -Int64(n));
+ }
+
+ check(10);
+ check(1000000000000000000);
+ check(9223372000000000000); // near Int64.MAX_VALUE, has exact double value
+ check(9223372036854775807); // Int64.MAX_VALUE, rounds up to -MIN_VALUE
+ check(-9223372036854775808); // Int64.MIN_VALUE
+ });
+}
diff --git a/pkgs/logging/.gitignore b/pkgs/logging/.gitignore
new file mode 100644
index 0000000..79f51c3
--- /dev/null
+++ b/pkgs/logging/.gitignore
@@ -0,0 +1,3 @@
+.dart_tool
+.packages
+pubspec.lock
diff --git a/pkgs/logging/AUTHORS b/pkgs/logging/AUTHORS
new file mode 100644
index 0000000..630ab0e
--- /dev/null
+++ b/pkgs/logging/AUTHORS
@@ -0,0 +1,10 @@
+# Names should be added to this file with this pattern:
+#
+# For individuals:
+# Name <email address>
+#
+# For organizations:
+# Organization <fnmatch pattern>
+#
+Google Inc. <*@google.com>
+Anton Astashov <anton.astashov@gmail.com>
diff --git a/pkgs/logging/CHANGELOG.md b/pkgs/logging/CHANGELOG.md
new file mode 100644
index 0000000..088183c
--- /dev/null
+++ b/pkgs/logging/CHANGELOG.md
@@ -0,0 +1,92 @@
+## 1.3.0
+
+* Override empty stack traces for trace level events.
+* Require Dart 3.4
+* Move to `dart-lang/core` monorepo.
+
+## 1.2.0
+
+* Add notification when the log level is changed. Logger `onLevelChanged` broadcasts a stream of level values.
+* Require Dart 2.19.
+
+## 1.1.1
+
+* Add a check that throws if a logger name ends with '.'.
+* Require Dart 2.18
+
+## 1.1.0
+
+* Add `Logger.attachedLoggers` which exposes all loggers created with the
+ default constructor.
+* Enable the `avoid_dynamic_calls` lint.
+
+## 1.0.2
+
+* Update description.
+* Add example.
+
+## 1.0.1
+
+* List log levels in README.
+
+## 1.0.0
+
+* Stable null safety release.
+
+## 1.0.0-nullsafety.0
+
+* Migrate to null safety.
+* Removed the deprecated `LoggerHandler` typedef.
+
+## 0.11.4
+
+* Add top level `defaultLevel`.
+* Require Dart `>=2.0.0`.
+* Make detached loggers work regardless of `hierarchicalLoggingEnabled`.
+
+## 0.11.3+2
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 0.11.3+1
+
+* Fixed several documentation comments.
+
+## 0.11.3
+
+* Added optional `LogRecord.object` field.
+
+* `Logger.log` sets `LogRecord.object` if the message is not a string or a
+ function that returns a string. So that a handler can access the original
+ object instead of just its `toString()`.
+
+## 0.11.2
+
+* Added `Logger.detached` - a convenience factory to obtain a logger that is not
+ attached to this library's logger hierarchy.
+
+## 0.11.1+1
+
+* Include default error with the auto-generated stack traces.
+
+## 0.11.1
+
+* Add support for automatically logging the stack trace on error messages. Note
+ this can be expensive, so it is off by default.
+
+## 0.11.0
+
+* Revert change in `0.10.0`. `stackTrace` must be an instance of `StackTrace`.
+ Use the `Trace` class from the [stack_trace package][] to convert strings.
+
+[stack_trace package]: https://pub.dev/packages/stack_trace
+
+## 0.10.0
+
+* Change type of `stackTrace` from `StackTrace` to `Object`.
+
+## 0.9.3
+
+* Added optional `LogRecord.zone` field.
+
+* Record current zone (or user specified zone) when creating new `LogRecord`s.
diff --git a/pkgs/logging/LICENSE b/pkgs/logging/LICENSE
new file mode 100644
index 0000000..ab3bfa0
--- /dev/null
+++ b/pkgs/logging/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2013, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/logging/README.md b/pkgs/logging/README.md
new file mode 100644
index 0000000..6623145
--- /dev/null
+++ b/pkgs/logging/README.md
@@ -0,0 +1,141 @@
+[](https://github.com/dart-lang/core/actions/workflows/logging.yaml)
+[](https://pub.dev/packages/logging)
+[](https://pub.dev/packages/logging/publisher)
+
+## Initializing
+
+By default, the logging package does not do anything useful with the log
+messages. You must configure the logging level and add a handler for the log
+messages.
+
+Here is a simple logging configuration that logs all messages via `print`.
+
+```dart
+Logger.root.level = Level.ALL; // defaults to Level.INFO
+Logger.root.onRecord.listen((record) {
+ print('${record.level.name}: ${record.time}: ${record.message}');
+});
+```
+
+First, set the root `Level`. All messages at or above the current level are sent to the
+`onRecord` stream. Available levels are:
+
++ `Level.OFF`
++ `Level.SHOUT`
++ `Level.SEVERE`
++ `Level.WARNING`
++ `Level.INFO`
++ `Level.CONFIG`
++ `Level.FINE`
++ `Level.FINER`
++ `Level.FINEST`
+
+Then, listen on the `onRecord` stream for `LogRecord` events. The `LogRecord`
+class has various properties for the message, error, logger name, and more.
+
+To listen for changed level notifications use:
+
+```dart
+Logger.root.onLevelChanged.listen((level) {
+ print('The new log level is $level');
+});
+```
+
+## Logging messages
+
+Create a `Logger` with a unique name to easily identify the source of the log
+messages.
+
+```dart
+final log = Logger('MyClassName');
+```
+
+Here is an example of logging a debug message and an error:
+
+```dart
+var future = doSomethingAsync().then((result) {
+ log.fine('Got the result: $result');
+ processResult(result);
+}).catchError((e, stackTrace) => log.severe('Oh noes!', e, stackTrace));
+```
+
+When logging more complex messages, you can pass a closure instead that will be
+evaluated only if the message is actually logged:
+
+```dart
+log.fine(() => [1, 2, 3, 4, 5].map((e) => e * 4).join("-"));
+```
+
+Available logging methods are:
+
++ `log.shout(logged_content);`
++ `log.severe(logged_content);`
++ `log.warning(logged_content);`
++ `log.info(logged_content);`
++ `log.config(logged_content);`
++ `log.fine(logged_content);`
++ `log.finer(logged_content);`
++ `log.finest(logged_content);`
+
+## Configuration
+
+Loggers can be individually configured and listened to. When an individual logger has no
+specific configuration, it uses the configuration and any listeners found at `Logger.root`.
+
+To begin, set the global boolean `hierarchicalLoggingEnabled` to `true`.
+
+Then, create unique loggers and configure their `level` attributes and assign any listeners to
+their `onRecord` streams.
+
+
+```dart
+ hierarchicalLoggingEnabled = true;
+ Logger.root.level = Level.WARNING;
+ Logger.root.onRecord.listen((record) {
+ print('[ROOT][WARNING+] ${record.message}');
+ });
+
+ final log1 = Logger('FINE+');
+ log1.level = Level.FINE;
+ log1.onRecord.listen((record) {
+ print('[LOG1][FINE+] ${record.message}');
+ });
+
+ // log2 inherits LEVEL value of WARNING from `Logger.root`
+ final log2 = Logger('WARNING+');
+ log2.onRecord.listen((record) {
+ print('[LOG2][WARNING+] ${record.message}');
+ });
+
+
+ // Will NOT print because FINER is too low level for `Logger.root`.
+ log1.finer('LOG_01 FINER (X)');
+
+ // Will print twice ([LOG1] & [ROOT])
+ log1.fine('LOG_01 FINE (√√)');
+
+ // Will print ONCE because `log1` only uses root listener.
+ log1.warning('LOG_01 WARNING (√)');
+
+ // Will never print because FINE is too low level.
+ log2.fine('LOG_02 FINE (X)');
+
+ // Will print twice ([LOG2] & [ROOT]) because warning is sufficient for all
+ // loggers' levels.
+ log2.warning('LOG_02 WARNING (√√)');
+
+ // Will never print because `info` is filtered by `Logger.root.level` of
+ // `Level.WARNING`.
+ log2.info('INFO (X)');
+```
+
+Results in:
+
+```
+[LOG1][FINE+] LOG_01 FINE (√√)
+[ROOT][WARNING+] LOG_01 FINE (√√)
+[LOG1][FINE+] LOG_01 WARNING (√)
+[ROOT][WARNING+] LOG_01 WARNING (√)
+[LOG2][WARNING+] LOG_02 WARNING (√√)
+[ROOT][WARNING+] LOG_02 WARNING (√√)
+```
diff --git a/pkgs/logging/analysis_options.yaml b/pkgs/logging/analysis_options.yaml
new file mode 100644
index 0000000..307a6a7
--- /dev/null
+++ b/pkgs/logging/analysis_options.yaml
@@ -0,0 +1,30 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_classes_with_only_static_members
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_returning_this
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - join_return_with_assignment
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - prefer_final_locals
+ - unnecessary_await_in_return
+ - unnecessary_raw_strings
+ - use_if_null_to_convert_nulls_to_bools
+ - use_raw_strings
+ - use_string_buffers
diff --git a/pkgs/logging/example/main.dart b/pkgs/logging/example/main.dart
new file mode 100644
index 0000000..b565c3c
--- /dev/null
+++ b/pkgs/logging/example/main.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:logging/logging.dart';
+
+final log = Logger('ExampleLogger');
+
+/// Example of configuring a logger to print to stdout.
+///
+/// This example will print:
+///
+/// INFO: 2021-09-13 15:35:10.703401: recursion: n = 4
+/// INFO: 2021-09-13 15:35:10.707974: recursion: n = 3
+/// Fibonacci(4) is: 3
+/// Fibonacci(5) is: 5
+/// SHOUT: 2021-09-13 15:35:10.708087: Unexpected negative n: -42
+/// Fibonacci(-42) is: 1
+void main() {
+ Logger.root.level = Level.ALL; // defaults to Level.INFO
+ Logger.root.onRecord.listen((record) {
+ print('${record.level.name}: ${record.time}: ${record.message}');
+ });
+
+ print('Fibonacci(4) is: ${fibonacci(4)}');
+
+ Logger.root.level = Level.SEVERE; // skip logs less then severe.
+ print('Fibonacci(5) is: ${fibonacci(5)}');
+
+ print('Fibonacci(-42) is: ${fibonacci(-42)}');
+}
+
+int fibonacci(int n) {
+ if (n <= 2) {
+ if (n < 0) log.shout('Unexpected negative n: $n');
+ return 1;
+ } else {
+ log.info('recursion: n = $n');
+ return fibonacci(n - 2) + fibonacci(n - 1);
+ }
+}
diff --git a/pkgs/logging/lib/logging.dart b/pkgs/logging/lib/logging.dart
new file mode 100644
index 0000000..6f447e7
--- /dev/null
+++ b/pkgs/logging/lib/logging.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'src/level.dart';
+export 'src/log_record.dart';
+export 'src/logger.dart';
diff --git a/pkgs/logging/lib/src/level.dart b/pkgs/logging/lib/src/level.dart
new file mode 100644
index 0000000..3669433
--- /dev/null
+++ b/pkgs/logging/lib/src/level.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: constant_identifier_names
+
+/// [Level]s to control logging output. Logging can be enabled to include all
+/// levels above certain [Level]. [Level]s are ordered using an integer
+/// value [Level.value]. The predefined [Level] constants below are sorted as
+/// follows (in descending order): [Level.SHOUT], [Level.SEVERE],
+/// [Level.WARNING], [Level.INFO], [Level.CONFIG], [Level.FINE], [Level.FINER],
+/// [Level.FINEST], and [Level.ALL].
+///
+/// We recommend using one of the predefined logging levels. If you define your
+/// own level, make sure you use a value between those used in [Level.ALL] and
+/// [Level.OFF].
+class Level implements Comparable<Level> {
+ final String name;
+
+ /// Unique value for this level. Used to order levels, so filtering can
+ /// exclude messages whose level is under certain value.
+ final int value;
+
+ const Level(this.name, this.value);
+
+ /// Special key to turn on logging for all levels ([value] = 0).
+ static const Level ALL = Level('ALL', 0);
+
+ /// Special key to turn off all logging ([value] = 2000).
+ static const Level OFF = Level('OFF', 2000);
+
+ /// Key for highly detailed tracing ([value] = 300).
+ static const Level FINEST = Level('FINEST', 300);
+
+ /// Key for fairly detailed tracing ([value] = 400).
+ static const Level FINER = Level('FINER', 400);
+
+ /// Key for tracing information ([value] = 500).
+ static const Level FINE = Level('FINE', 500);
+
+ /// Key for static configuration messages ([value] = 700).
+ static const Level CONFIG = Level('CONFIG', 700);
+
+ /// Key for informational messages ([value] = 800).
+ static const Level INFO = Level('INFO', 800);
+
+ /// Key for potential problems ([value] = 900).
+ static const Level WARNING = Level('WARNING', 900);
+
+ /// Key for serious failures ([value] = 1000).
+ static const Level SEVERE = Level('SEVERE', 1000);
+
+ /// Key for extra debugging loudness ([value] = 1200).
+ static const Level SHOUT = Level('SHOUT', 1200);
+
+ static const List<Level> LEVELS = [
+ ALL,
+ FINEST,
+ FINER,
+ FINE,
+ CONFIG,
+ INFO,
+ WARNING,
+ SEVERE,
+ SHOUT,
+ OFF
+ ];
+
+ @override
+ bool operator ==(Object other) => other is Level && value == other.value;
+
+ bool operator <(Level other) => value < other.value;
+
+ bool operator <=(Level other) => value <= other.value;
+
+ bool operator >(Level other) => value > other.value;
+
+ bool operator >=(Level other) => value >= other.value;
+
+ @override
+ int compareTo(Level other) => value - other.value;
+
+ @override
+ int get hashCode => value;
+
+ @override
+ String toString() => name;
+}
diff --git a/pkgs/logging/lib/src/log_record.dart b/pkgs/logging/lib/src/log_record.dart
new file mode 100644
index 0000000..8a0ee61
--- /dev/null
+++ b/pkgs/logging/lib/src/log_record.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'level.dart';
+import 'logger.dart';
+
+/// A log entry representation used to propagate information from [Logger] to
+/// individual handlers.
+class LogRecord {
+ final Level level;
+ final String message;
+
+ /// Non-string message passed to Logger.
+ final Object? object;
+
+ /// Logger where this record is stored.
+ final String loggerName;
+
+ /// Time when this record was created.
+ final DateTime time;
+
+ /// Unique sequence number greater than all log records created before it.
+ final int sequenceNumber;
+
+ static int _nextNumber = 0;
+
+ /// Associated error (if any) when recording errors messages.
+ final Object? error;
+
+ /// Associated stackTrace (if any) when recording errors messages.
+ final StackTrace? stackTrace;
+
+ /// Zone of the calling code which resulted in this LogRecord.
+ final Zone? zone;
+
+ LogRecord(this.level, this.message, this.loggerName,
+ [this.error, this.stackTrace, this.zone, this.object])
+ : time = DateTime.now(),
+ sequenceNumber = LogRecord._nextNumber++;
+
+ @override
+ String toString() => '[${level.name}] $loggerName: $message';
+}
diff --git a/pkgs/logging/lib/src/logger.dart b/pkgs/logging/lib/src/logger.dart
new file mode 100644
index 0000000..d9fa254
--- /dev/null
+++ b/pkgs/logging/lib/src/logger.dart
@@ -0,0 +1,326 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'level.dart';
+import 'log_record.dart';
+
+/// Whether to allow fine-grain logging and configuration of loggers in a
+/// hierarchy.
+///
+/// When false, all hierarchical logging instead is merged in the root logger.
+bool hierarchicalLoggingEnabled = false;
+
+/// Automatically record stack traces for any message of this level or above.
+///
+/// Because this is expensive, this is off by default.
+Level recordStackTraceAtLevel = Level.OFF;
+
+/// The default [Level].
+const defaultLevel = Level.INFO;
+
+/// Use a [Logger] to log debug messages.
+///
+/// [Logger]s are named using a hierarchical dot-separated name convention.
+class Logger {
+ /// Simple name of this logger.
+ final String name;
+
+ /// The full name of this logger, which includes the parent's full name.
+ String get fullName =>
+ parent?.name.isNotEmpty ?? false ? '${parent!.fullName}.$name' : name;
+
+ /// Parent of this logger in the hierarchy of loggers.
+ final Logger? parent;
+
+ /// Logging [Level] used for entries generated on this logger.
+ ///
+ /// Only the root logger is guaranteed to have a non-null [Level].
+ Level? _level;
+
+ /// Private modifiable map of child loggers, indexed by their simple names.
+ final Map<String, Logger> _children;
+
+ /// Children in the hierarchy of loggers, indexed by their simple names.
+ ///
+ /// This is an unmodifiable map.
+ final Map<String, Logger> children;
+
+ /// Controller used to notify when log entries are added to this logger.
+ ///
+ /// If hierarchical logging is disabled then this is `null` for all but the
+ /// root [Logger].
+ StreamController<LogRecord>? _controller;
+
+ /// Controller used to notify when the log level of this logger is changed.
+ StreamController<Level?>? _levelChangedController;
+
+ /// Create or find a Logger by name.
+ ///
+ /// Calling `Logger(name)` will return the same instance whenever it is called
+ /// with the same string name. Loggers created with this constructor are
+ /// retained indefinitely and available through [attachedLoggers].
+ factory Logger(String name) =>
+ _loggers.putIfAbsent(name, () => Logger._named(name));
+
+ /// Creates a new detached [Logger].
+ ///
+ /// Returns a new [Logger] instance (unlike `new Logger`, which returns a
+ /// [Logger] singleton), which doesn't have any parent or children,
+ /// and is not a part of the global hierarchical loggers structure.
+ ///
+ /// It can be useful when you just need a local short-living logger,
+ /// which you'd like to be garbage-collected later.
+ factory Logger.detached(String name) =>
+ Logger._internal(name, null, <String, Logger>{});
+
+ factory Logger._named(String name) {
+ if (name.startsWith('.')) {
+ throw ArgumentError("name shouldn't start with a '.'");
+ }
+ if (name.endsWith('.')) {
+ throw ArgumentError("name shouldn't end with a '.'");
+ }
+
+ // Split hierarchical names (separated with '.').
+ final dot = name.lastIndexOf('.');
+ Logger? parent;
+ String thisName;
+ if (dot == -1) {
+ if (name != '') parent = Logger('');
+ thisName = name;
+ } else {
+ parent = Logger(name.substring(0, dot));
+ thisName = name.substring(dot + 1);
+ }
+ return Logger._internal(thisName, parent, <String, Logger>{});
+ }
+
+ Logger._internal(this.name, this.parent, Map<String, Logger> children)
+ : _children = children,
+ children = UnmodifiableMapView(children) {
+ if (parent == null) {
+ _level = defaultLevel;
+ } else {
+ parent!._children[name] = this;
+ }
+ }
+
+ /// Effective level considering the levels established in this logger's
+ /// parents (when [hierarchicalLoggingEnabled] is true).
+ Level get level {
+ Level effectiveLevel;
+
+ if (parent == null) {
+ // We're either the root logger or a detached logger. Return our own
+ // level.
+ effectiveLevel = _level!;
+ } else if (!hierarchicalLoggingEnabled) {
+ effectiveLevel = root._level!;
+ } else {
+ effectiveLevel = _level ?? parent!.level;
+ }
+
+ // ignore: unnecessary_null_comparison
+ assert(effectiveLevel != null);
+ return effectiveLevel;
+ }
+
+ /// Override the level for this particular [Logger] and its children.
+ ///
+ /// Setting this to `null` makes it inherit the [parent]s level.
+ set level(Level? value) {
+ if (!hierarchicalLoggingEnabled && parent != null) {
+ throw UnsupportedError(
+ 'Please set "hierarchicalLoggingEnabled" to true if you want to '
+ 'change the level on a non-root logger.');
+ }
+ if (parent == null && value == null) {
+ throw UnsupportedError(
+ 'Cannot set the level to `null` on a logger with no parent.');
+ }
+ final isLevelChanged = _level != value;
+ _level = value;
+ if (isLevelChanged) {
+ _levelChangedController?.add(value);
+ }
+ }
+
+ /// Returns a stream of level values set to this [Logger].
+ ///
+ /// You can listen for set levels using the standard stream APIs,
+ /// for instance:
+ ///
+ /// ```dart
+ /// logger.onLevelChanged.listen((level) { ... });
+ /// ```
+ /// A state error will be thrown if the level is changed
+ /// inside the callback.
+ Stream<Level?> get onLevelChanged {
+ _levelChangedController ??= StreamController<Level?>.broadcast(sync: true);
+ return _levelChangedController!.stream;
+ }
+
+ /// Returns a stream of messages added to this [Logger].
+ ///
+ /// You can listen for messages using the standard stream APIs, for instance:
+ ///
+ /// ```dart
+ /// logger.onRecord.listen((record) { ... });
+ /// ```
+ Stream<LogRecord> get onRecord => _getStream();
+
+ void clearListeners() {
+ if (hierarchicalLoggingEnabled || parent == null) {
+ _controller?.close();
+ _controller = null;
+ } else {
+ root.clearListeners();
+ }
+ }
+
+ /// Whether a message for [value]'s level is loggable in this logger.
+ bool isLoggable(Level value) => value >= level;
+
+ /// Adds a log record for a [message] at a particular [logLevel] if
+ /// `isLoggable(logLevel)` is true.
+ ///
+ /// Use this method to create log entries for user-defined levels. To record a
+ /// message at a predefined level (e.g. [Level.INFO], [Level.WARNING], etc)
+ /// you can use their specialized methods instead (e.g. [info], [warning],
+ /// etc).
+ ///
+ /// If [message] is a [Function], it will be lazy evaluated. Additionally, if
+ /// [message] or its evaluated value is not a [String], then 'toString()' will
+ /// be called on the object and the result will be logged. The log record will
+ /// contain a field holding the original object.
+ ///
+ /// The log record will also contain a field for the zone in which this call
+ /// was made. This can be advantageous if a log listener wants to handler
+ /// records of different zones differently (e.g. group log records by HTTP
+ /// request if each HTTP request handler runs in it's own zone).
+ ///
+ /// If this record is logged at a level equal to or higher than
+ /// [recordStackTraceAtLevel] and [stackTrace] is `null` or [StackTrace.empty]
+ /// it will be defaulted to the current stack trace for this call.
+ void log(Level logLevel, Object? message,
+ [Object? error, StackTrace? stackTrace, Zone? zone]) {
+ Object? object;
+ if (isLoggable(logLevel)) {
+ if (message is Function) {
+ message = (message as Object? Function())();
+ }
+
+ String msg;
+ if (message is String) {
+ msg = message;
+ } else {
+ msg = message.toString();
+ object = message;
+ }
+
+ if ((stackTrace == null || stackTrace == StackTrace.empty) &&
+ logLevel >= recordStackTraceAtLevel) {
+ stackTrace = StackTrace.current;
+ error ??= 'autogenerated stack trace for $logLevel $msg';
+ }
+ zone ??= Zone.current;
+
+ final record =
+ LogRecord(logLevel, msg, fullName, error, stackTrace, zone, object);
+
+ if (parent == null) {
+ _publish(record);
+ } else if (!hierarchicalLoggingEnabled) {
+ root._publish(record);
+ } else {
+ Logger? target = this;
+ while (target != null) {
+ target._publish(record);
+ target = target.parent;
+ }
+ }
+ }
+ }
+
+ /// Log message at level [Level.FINEST].
+ ///
+ /// See [log] for information on how non-String [message] arguments are
+ /// handled.
+ void finest(Object? message, [Object? error, StackTrace? stackTrace]) =>
+ log(Level.FINEST, message, error, stackTrace);
+
+ /// Log message at level [Level.FINER].
+ ///
+ /// See [log] for information on how non-String [message] arguments are
+ /// handled.
+ void finer(Object? message, [Object? error, StackTrace? stackTrace]) =>
+ log(Level.FINER, message, error, stackTrace);
+
+ /// Log message at level [Level.FINE].
+ ///
+ /// See [log] for information on how non-String [message] arguments are
+ /// handled.
+ void fine(Object? message, [Object? error, StackTrace? stackTrace]) =>
+ log(Level.FINE, message, error, stackTrace);
+
+ /// Log message at level [Level.CONFIG].
+ ///
+ /// See [log] for information on how non-String [message] arguments are
+ /// handled.
+ void config(Object? message, [Object? error, StackTrace? stackTrace]) =>
+ log(Level.CONFIG, message, error, stackTrace);
+
+ /// Log message at level [Level.INFO].
+ ///
+ /// See [log] for information on how non-String [message] arguments are
+ /// handled.
+ void info(Object? message, [Object? error, StackTrace? stackTrace]) =>
+ log(Level.INFO, message, error, stackTrace);
+
+ /// Log message at level [Level.WARNING].
+ ///
+ /// See [log] for information on how non-String [message] arguments are
+ /// handled.
+ void warning(Object? message, [Object? error, StackTrace? stackTrace]) =>
+ log(Level.WARNING, message, error, stackTrace);
+
+ /// Log message at level [Level.SEVERE].
+ ///
+ /// See [log] for information on how non-String [message] arguments are
+ /// handled.
+ void severe(Object? message, [Object? error, StackTrace? stackTrace]) =>
+ log(Level.SEVERE, message, error, stackTrace);
+
+ /// Log message at level [Level.SHOUT].
+ ///
+ /// See [log] for information on how non-String [message] arguments are
+ /// handled.
+ void shout(Object? message, [Object? error, StackTrace? stackTrace]) =>
+ log(Level.SHOUT, message, error, stackTrace);
+
+ Stream<LogRecord> _getStream() {
+ if (hierarchicalLoggingEnabled || parent == null) {
+ return (_controller ??= StreamController<LogRecord>.broadcast(sync: true))
+ .stream;
+ } else {
+ return root._getStream();
+ }
+ }
+
+ void _publish(LogRecord record) => _controller?.add(record);
+
+ /// Top-level root [Logger].
+ static final Logger root = Logger('');
+
+ /// All attached [Logger]s in the system.
+ static final Map<String, Logger> _loggers = <String, Logger>{};
+
+ /// All attached [Logger]s in the system.
+ ///
+ /// Loggers created with [Logger.detached] are not included.
+ static Iterable<Logger> get attachedLoggers => _loggers.values;
+}
diff --git a/pkgs/logging/pubspec.yaml b/pkgs/logging/pubspec.yaml
new file mode 100644
index 0000000..18bdacf
--- /dev/null
+++ b/pkgs/logging/pubspec.yaml
@@ -0,0 +1,18 @@
+name: logging
+version: 1.3.0
+description: >-
+ Provides APIs for debugging and error logging, similar to loggers in other
+ languages, such as the Closure JS Logger and java.util.logging.Logger.
+repository: https://github.com/dart-lang/core/tree/main/pkgs/logging
+issue_tracker: https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Alogging
+
+topics:
+ - logging
+ - debugging
+
+environment:
+ sdk: ^3.4.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
diff --git a/pkgs/logging/test/logging_test.dart b/pkgs/logging/test/logging_test.dart
new file mode 100644
index 0000000..6bff3d8
--- /dev/null
+++ b/pkgs/logging/test/logging_test.dart
@@ -0,0 +1,761 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:logging/logging.dart';
+import 'package:test/test.dart';
+
+void main() {
+ final hierarchicalLoggingEnabledDefault = hierarchicalLoggingEnabled;
+
+ test('level comparison is a valid comparator', () {
+ const level1 = Level('NOT_REAL1', 253);
+ expect(level1 == level1, isTrue);
+ expect(level1 <= level1, isTrue);
+ expect(level1 >= level1, isTrue);
+ expect(level1 < level1, isFalse);
+ expect(level1 > level1, isFalse);
+
+ const level2 = Level('NOT_REAL2', 455);
+ expect(level1 <= level2, isTrue);
+ expect(level1 < level2, isTrue);
+ expect(level2 >= level1, isTrue);
+ expect(level2 > level1, isTrue);
+
+ const level3 = Level('NOT_REAL3', 253);
+ expect(level1, isNot(same(level3))); // different instances
+ expect(level1, equals(level3)); // same value.
+ });
+
+ test('default levels are in order', () {
+ const levels = Level.LEVELS;
+
+ for (var i = 0; i < levels.length; i++) {
+ for (var j = i + 1; j < levels.length; j++) {
+ expect(levels[i] < levels[j], isTrue);
+ }
+ }
+ });
+
+ test('levels are comparable', () {
+ final unsorted = [
+ Level.INFO,
+ Level.CONFIG,
+ Level.FINE,
+ Level.SHOUT,
+ Level.OFF,
+ Level.FINER,
+ Level.ALL,
+ Level.WARNING,
+ Level.FINEST,
+ Level.SEVERE,
+ ];
+
+ const sorted = Level.LEVELS;
+
+ expect(unsorted, isNot(orderedEquals(sorted)));
+
+ unsorted.sort();
+ expect(unsorted, orderedEquals(sorted));
+ });
+
+ test('levels are hashable', () {
+ final map = <Level, String>{};
+ map[Level.INFO] = 'info';
+ map[Level.SHOUT] = 'shout';
+ expect(map[Level.INFO], same('info'));
+ expect(map[Level.SHOUT], same('shout'));
+ });
+
+ test('logger name cannot start with a "." ', () {
+ expect(() => Logger('.c'), throwsArgumentError);
+ });
+
+ test('logger name cannot end with a "."', () {
+ expect(() => Logger('a.'), throwsArgumentError);
+ expect(() => Logger('a..d'), throwsArgumentError);
+ });
+
+ test('root level has proper defaults', () {
+ expect(Logger.root, isNotNull);
+ expect(Logger.root.parent, null);
+ expect(Logger.root.level, defaultLevel);
+ });
+
+ test('logger naming is hierarchical', () {
+ final c = Logger('a.b.c');
+ expect(c.name, equals('c'));
+ expect(c.parent!.name, equals('b'));
+ expect(c.parent!.parent!.name, equals('a'));
+ expect(c.parent!.parent!.parent!.name, equals(''));
+ expect(c.parent!.parent!.parent!.parent, isNull);
+ });
+
+ test('logger full name', () {
+ final c = Logger('a.b.c');
+ expect(c.fullName, equals('a.b.c'));
+ expect(c.parent!.fullName, equals('a.b'));
+ expect(c.parent!.parent!.fullName, equals('a'));
+ expect(c.parent!.parent!.parent!.fullName, equals(''));
+ expect(c.parent!.parent!.parent!.parent, isNull);
+ });
+
+ test('logger parent-child links are correct', () {
+ final a = Logger('a');
+ final b = Logger('a.b');
+ final c = Logger('a.c');
+ expect(a, same(b.parent));
+ expect(a, same(c.parent));
+ expect(a.children['b'], same(b));
+ expect(a.children['c'], same(c));
+ });
+
+ test('loggers are singletons', () {
+ final a1 = Logger('a');
+ final a2 = Logger('a');
+ final b = Logger('a.b');
+ final root = Logger.root;
+ expect(a1, same(a2));
+ expect(a1, same(b.parent));
+ expect(root, same(a1.parent));
+ expect(root, same(Logger('')));
+ });
+
+ test('cannot directly manipulate Logger.children', () {
+ final loggerAB = Logger('a.b');
+ final loggerA = loggerAB.parent!;
+
+ expect(loggerA.children['b'], same(loggerAB), reason: 'can read Children');
+
+ expect(() {
+ loggerAB.children['test'] = Logger('Fake1234');
+ }, throwsUnsupportedError, reason: 'Children is read-only');
+ });
+
+ test('stackTrace gets throw to LogRecord', () {
+ Logger.root.level = Level.INFO;
+
+ final records = <LogRecord>[];
+
+ final sub = Logger.root.onRecord.listen(records.add);
+
+ try {
+ throw UnsupportedError('test exception');
+ } catch (error, stack) {
+ Logger.root.log(Level.SEVERE, 'severe', error, stack);
+ Logger.root.warning('warning', error, stack);
+ }
+
+ Logger.root.log(Level.SHOUT, 'shout');
+
+ sub.cancel();
+
+ expect(records, hasLength(3));
+
+ final severe = records[0];
+ expect(severe.message, 'severe');
+ expect(severe.error is UnsupportedError, isTrue);
+ expect(severe.stackTrace is StackTrace, isTrue);
+
+ final warning = records[1];
+ expect(warning.message, 'warning');
+ expect(warning.error is UnsupportedError, isTrue);
+ expect(warning.stackTrace is StackTrace, isTrue);
+
+ final shout = records[2];
+ expect(shout.message, 'shout');
+ expect(shout.error, isNull);
+ expect(shout.stackTrace, isNull);
+ });
+
+ group('zone gets recorded to LogRecord', () {
+ test('root zone', () {
+ final root = Logger.root;
+
+ final recordingZone = Zone.current;
+ final records = <LogRecord>[];
+ root.onRecord.listen(records.add);
+ root.info('hello');
+
+ expect(records, hasLength(1));
+ expect(records.first.zone, equals(recordingZone));
+ });
+
+ test('child zone', () {
+ final root = Logger.root;
+
+ late Zone recordingZone;
+ final records = <LogRecord>[];
+ root.onRecord.listen(records.add);
+
+ runZoned(() {
+ recordingZone = Zone.current;
+ root.info('hello');
+ });
+
+ expect(records, hasLength(1));
+ expect(records.first.zone, equals(recordingZone));
+ });
+
+ test('custom zone', () {
+ final root = Logger.root;
+
+ late Zone recordingZone;
+ final records = <LogRecord>[];
+ root.onRecord.listen(records.add);
+
+ runZoned(() {
+ recordingZone = Zone.current;
+ });
+
+ runZoned(() => root.log(Level.INFO, 'hello', null, null, recordingZone));
+
+ expect(records, hasLength(1));
+ expect(records.first.zone, equals(recordingZone));
+ });
+ });
+
+ group('detached loggers', () {
+ tearDown(() {
+ hierarchicalLoggingEnabled = hierarchicalLoggingEnabledDefault;
+ Logger.root.level = defaultLevel;
+ });
+
+ test('create new instances of Logger', () {
+ final a1 = Logger.detached('a');
+ final a2 = Logger.detached('a');
+ final a = Logger('a');
+
+ expect(a1, isNot(a2));
+ expect(a1, isNot(a));
+ expect(a2, isNot(a));
+ });
+
+ test('parent is null', () {
+ final a = Logger.detached('a');
+ expect(a.parent, null);
+ });
+
+ test('children is empty', () {
+ final a = Logger.detached('a');
+ expect(a.children, <Object, Object>{});
+ });
+
+ test('have levels independent of the root level', () {
+ void testDetachedLoggerLevel(bool withHierarchy) {
+ hierarchicalLoggingEnabled = withHierarchy;
+
+ const newRootLevel = Level.ALL;
+ const newDetachedLevel = Level.OFF;
+
+ Logger.root.level = newRootLevel;
+
+ final detached = Logger.detached('a');
+ expect(detached.level, defaultLevel);
+ expect(Logger.root.level, newRootLevel);
+
+ detached.level = newDetachedLevel;
+ expect(detached.level, newDetachedLevel);
+ expect(Logger.root.level, newRootLevel);
+ }
+
+ testDetachedLoggerLevel(false);
+ testDetachedLoggerLevel(true);
+ });
+
+ test('log messages regardless of hierarchy', () {
+ void testDetachedLoggerOnRecord(bool withHierarchy) {
+ var calls = 0;
+ void handler(_) => calls += 1;
+
+ hierarchicalLoggingEnabled = withHierarchy;
+
+ final detached = Logger.detached('a');
+ detached.level = Level.ALL;
+ detached.onRecord.listen(handler);
+
+ Logger.root.info('foo');
+ expect(calls, 0);
+
+ detached.info('foo');
+ detached.info('foo');
+ expect(calls, 2);
+ }
+
+ testDetachedLoggerOnRecord(false);
+ testDetachedLoggerOnRecord(true);
+ });
+ });
+
+ group('mutating levels', () {
+ final root = Logger.root;
+ final a = Logger('a');
+ final b = Logger('a.b');
+ final c = Logger('a.b.c');
+ final d = Logger('a.b.c.d');
+ final e = Logger('a.b.c.d.e');
+
+ setUp(() {
+ hierarchicalLoggingEnabled = true;
+ root.level = Level.INFO;
+ a.level = null;
+ b.level = null;
+ c.level = null;
+ d.level = null;
+ e.level = null;
+ root.clearListeners();
+ a.clearListeners();
+ b.clearListeners();
+ c.clearListeners();
+ d.clearListeners();
+ e.clearListeners();
+ hierarchicalLoggingEnabled = false;
+ root.level = Level.INFO;
+ });
+
+ test('cannot set level if hierarchy is disabled', () {
+ expect(() => a.level = Level.FINE, throwsUnsupportedError);
+ });
+
+ test('cannot set the level to null on the root logger', () {
+ expect(() => root.level = null, throwsUnsupportedError);
+ });
+
+ test('cannot set the level to null on a detached logger', () {
+ expect(() => Logger.detached('l').level = null, throwsUnsupportedError);
+ });
+
+ test('loggers effective level - no hierarchy', () {
+ expect(root.level, equals(Level.INFO));
+ expect(a.level, equals(Level.INFO));
+ expect(b.level, equals(Level.INFO));
+
+ root.level = Level.SHOUT;
+
+ expect(root.level, equals(Level.SHOUT));
+ expect(a.level, equals(Level.SHOUT));
+ expect(b.level, equals(Level.SHOUT));
+ });
+
+ test('loggers effective level - with hierarchy', () {
+ hierarchicalLoggingEnabled = true;
+ expect(root.level, equals(Level.INFO));
+ expect(a.level, equals(Level.INFO));
+ expect(b.level, equals(Level.INFO));
+ expect(c.level, equals(Level.INFO));
+
+ root.level = Level.SHOUT;
+ b.level = Level.FINE;
+
+ expect(root.level, equals(Level.SHOUT));
+ expect(a.level, equals(Level.SHOUT));
+ expect(b.level, equals(Level.FINE));
+ expect(c.level, equals(Level.FINE));
+ });
+
+ test('loggers effective level - with changing hierarchy', () {
+ hierarchicalLoggingEnabled = true;
+ d.level = Level.SHOUT;
+ hierarchicalLoggingEnabled = false;
+
+ expect(root.level, Level.INFO);
+ expect(d.level, root.level);
+ expect(e.level, root.level);
+ });
+
+ test('isLoggable is appropriate', () {
+ hierarchicalLoggingEnabled = true;
+ root.level = Level.SEVERE;
+ c.level = Level.ALL;
+ e.level = Level.OFF;
+
+ expect(root.isLoggable(Level.SHOUT), isTrue);
+ expect(root.isLoggable(Level.SEVERE), isTrue);
+ expect(root.isLoggable(Level.WARNING), isFalse);
+ expect(c.isLoggable(Level.FINEST), isTrue);
+ expect(c.isLoggable(Level.FINE), isTrue);
+ expect(e.isLoggable(Level.SHOUT), isFalse);
+ });
+
+ test('add/remove handlers - no hierarchy', () {
+ var calls = 0;
+ void handler(_) {
+ calls++;
+ }
+
+ final sub = c.onRecord.listen(handler);
+ root.info('foo');
+ root.info('foo');
+ expect(calls, equals(2));
+ sub.cancel();
+ root.info('foo');
+ expect(calls, equals(2));
+ });
+
+ test('add/remove handlers - with hierarchy', () {
+ hierarchicalLoggingEnabled = true;
+ var calls = 0;
+ void handler(_) {
+ calls++;
+ }
+
+ c.onRecord.listen(handler);
+ root.info('foo');
+ root.info('foo');
+ expect(calls, equals(0));
+ });
+
+ test('logging methods store appropriate level', () {
+ root.level = Level.ALL;
+ final rootMessages = <String>[];
+ root.onRecord.listen((record) {
+ rootMessages.add('${record.level}: ${record.message}');
+ });
+
+ root.finest('1');
+ root.finer('2');
+ root.fine('3');
+ root.config('4');
+ root.info('5');
+ root.warning('6');
+ root.severe('7');
+ root.shout('8');
+
+ expect(
+ rootMessages,
+ equals([
+ 'FINEST: 1',
+ 'FINER: 2',
+ 'FINE: 3',
+ 'CONFIG: 4',
+ 'INFO: 5',
+ 'WARNING: 6',
+ 'SEVERE: 7',
+ 'SHOUT: 8'
+ ]));
+ });
+
+ test('logging methods store exception', () {
+ root.level = Level.ALL;
+ final rootMessages = <String>[];
+ root.onRecord.listen((r) {
+ rootMessages.add('${r.level}: ${r.message} ${r.error}');
+ });
+
+ root.finest('1');
+ root.finer('2');
+ root.fine('3');
+ root.config('4');
+ root.info('5');
+ root.warning('6');
+ root.severe('7');
+ root.shout('8');
+ root.finest('1', 'a');
+ root.finer('2', 'b');
+ root.fine('3', ['c']);
+ root.config('4', 'd');
+ root.info('5', 'e');
+ root.warning('6', 'f');
+ root.severe('7', 'g');
+ root.shout('8', 'h');
+
+ expect(
+ rootMessages,
+ equals([
+ 'FINEST: 1 null',
+ 'FINER: 2 null',
+ 'FINE: 3 null',
+ 'CONFIG: 4 null',
+ 'INFO: 5 null',
+ 'WARNING: 6 null',
+ 'SEVERE: 7 null',
+ 'SHOUT: 8 null',
+ 'FINEST: 1 a',
+ 'FINER: 2 b',
+ 'FINE: 3 [c]',
+ 'CONFIG: 4 d',
+ 'INFO: 5 e',
+ 'WARNING: 6 f',
+ 'SEVERE: 7 g',
+ 'SHOUT: 8 h'
+ ]));
+ });
+
+ test('message logging - no hierarchy', () {
+ root.level = Level.WARNING;
+ final rootMessages = <String>[];
+ final aMessages = <String>[];
+ final cMessages = <String>[];
+ c.onRecord.listen((record) {
+ cMessages.add('${record.level}: ${record.message}');
+ });
+ a.onRecord.listen((record) {
+ aMessages.add('${record.level}: ${record.message}');
+ });
+ root.onRecord.listen((record) {
+ rootMessages.add('${record.level}: ${record.message}');
+ });
+
+ root.info('1');
+ root.fine('2');
+ root.shout('3');
+
+ b.info('4');
+ b.severe('5');
+ b.warning('6');
+ b.fine('7');
+
+ c.fine('8');
+ c.warning('9');
+ c.shout('10');
+
+ expect(
+ rootMessages,
+ equals([
+ // 'INFO: 1' is not loggable
+ // 'FINE: 2' is not loggable
+ 'SHOUT: 3',
+ // 'INFO: 4' is not loggable
+ 'SEVERE: 5',
+ 'WARNING: 6',
+ // 'FINE: 7' is not loggable
+ // 'FINE: 8' is not loggable
+ 'WARNING: 9',
+ 'SHOUT: 10'
+ ]));
+
+ // no hierarchy means we all hear the same thing.
+ expect(aMessages, equals(rootMessages));
+ expect(cMessages, equals(rootMessages));
+ });
+
+ test('message logging - with hierarchy', () {
+ hierarchicalLoggingEnabled = true;
+
+ b.level = Level.WARNING;
+
+ final rootMessages = <String>[];
+ final aMessages = <String>[];
+ final cMessages = <String>[];
+ c.onRecord.listen((record) {
+ cMessages.add('${record.level}: ${record.message}');
+ });
+ a.onRecord.listen((record) {
+ aMessages.add('${record.level}: ${record.message}');
+ });
+ root.onRecord.listen((record) {
+ rootMessages.add('${record.level}: ${record.message}');
+ });
+
+ root.info('1');
+ root.fine('2');
+ root.shout('3');
+
+ b.info('4');
+ b.severe('5');
+ b.warning('6');
+ b.fine('7');
+
+ c.fine('8');
+ c.warning('9');
+ c.shout('10');
+
+ expect(
+ rootMessages,
+ equals([
+ 'INFO: 1',
+ // 'FINE: 2' is not loggable
+ 'SHOUT: 3',
+ // 'INFO: 4' is not loggable
+ 'SEVERE: 5',
+ 'WARNING: 6',
+ // 'FINE: 7' is not loggable
+ // 'FINE: 8' is not loggable
+ 'WARNING: 9',
+ 'SHOUT: 10'
+ ]));
+
+ expect(
+ aMessages,
+ equals([
+ // 1,2 and 3 are lower in the hierarchy
+ // 'INFO: 4' is not loggable
+ 'SEVERE: 5',
+ 'WARNING: 6',
+ // 'FINE: 7' is not loggable
+ // 'FINE: 8' is not loggable
+ 'WARNING: 9',
+ 'SHOUT: 10'
+ ]));
+
+ expect(
+ cMessages,
+ equals([
+ // 1 - 7 are lower in the hierarchy
+ // 'FINE: 8' is not loggable
+ 'WARNING: 9',
+ 'SHOUT: 10'
+ ]));
+ });
+
+ test('message logging - lazy functions', () {
+ root.level = Level.INFO;
+ final messages = <String>[];
+ root.onRecord.listen((record) {
+ messages.add('${record.level}: ${record.message}');
+ });
+
+ var callCount = 0;
+ String myClosure() => '${++callCount}';
+
+ root.info(myClosure);
+ root.finer(myClosure); // Should not get evaluated.
+ root.warning(myClosure);
+
+ expect(
+ messages,
+ equals([
+ 'INFO: 1',
+ 'WARNING: 2',
+ ]));
+ });
+
+ test('message logging - calls toString', () {
+ root.level = Level.INFO;
+ final messages = <String>[];
+ final objects = <Object?>[];
+ final object = Object();
+ root.onRecord.listen((record) {
+ messages.add('${record.level}: ${record.message}');
+ objects.add(record.object);
+ });
+
+ root.info(5);
+ root.info(false);
+ root.info([1, 2, 3]);
+ root.info(() => 10);
+ root.info(object);
+
+ expect(
+ messages,
+ equals([
+ 'INFO: 5',
+ 'INFO: false',
+ 'INFO: [1, 2, 3]',
+ 'INFO: 10',
+ "INFO: Instance of 'Object'"
+ ]));
+
+ expect(objects, [
+ 5,
+ false,
+ [1, 2, 3],
+ 10,
+ object
+ ]);
+ });
+ });
+
+ group('recordStackTraceAtLevel', () {
+ final root = Logger.root;
+ tearDown(() {
+ recordStackTraceAtLevel = Level.OFF;
+ root.clearListeners();
+ });
+
+ test('no stack trace by default', () {
+ final records = <LogRecord>[];
+ root.onRecord.listen(records.add);
+ root.severe('hello');
+ root.warning('hello');
+ root.info('hello');
+ expect(records, hasLength(3));
+ expect(records[0].stackTrace, isNull);
+ expect(records[1].stackTrace, isNull);
+ expect(records[2].stackTrace, isNull);
+ });
+
+ test('trace recorded only on requested levels', () {
+ final records = <LogRecord>[];
+ recordStackTraceAtLevel = Level.WARNING;
+ root.onRecord.listen(records.add);
+ root.severe('hello');
+ root.warning('hello');
+ root.info('hello');
+ expect(records, hasLength(3));
+ expect(records[0].stackTrace, isNotNull);
+ expect(records[1].stackTrace, isNotNull);
+ expect(records[2].stackTrace, isNull);
+ });
+
+ test('defaults a missing trace', () {
+ final records = <LogRecord>[];
+ recordStackTraceAtLevel = Level.SEVERE;
+ root.onRecord.listen(records.add);
+ root.severe('hello');
+ expect(records.single.stackTrace, isNotNull);
+ });
+
+ test('defaults an empty trace', () {
+ final records = <LogRecord>[];
+ recordStackTraceAtLevel = Level.SEVERE;
+ root.onRecord.listen(records.add);
+ root.severe('hello', 'error', StackTrace.empty);
+ expect(records.single.stackTrace, isNot(StackTrace.empty));
+ });
+
+ test('provided trace is used if given', () {
+ final trace = StackTrace.current;
+ final records = <LogRecord>[];
+ recordStackTraceAtLevel = Level.WARNING;
+ root.onRecord.listen(records.add);
+ root.severe('hello');
+ root.warning('hello', 'a', trace);
+ expect(records, hasLength(2));
+ expect(records[0].stackTrace, isNot(equals(trace)));
+ expect(records[1].stackTrace, trace);
+ });
+
+ test('error also generated when generating a trace', () {
+ final records = <LogRecord>[];
+ recordStackTraceAtLevel = Level.WARNING;
+ root.onRecord.listen(records.add);
+ root.severe('hello');
+ root.warning('hello');
+ root.info('hello');
+ expect(records, hasLength(3));
+ expect(records[0].error, isNotNull);
+ expect(records[1].error, isNotNull);
+ expect(records[2].error, isNull);
+ });
+
+ test('listen for level changed', () {
+ final levels = <Level?>[];
+ root.level = Level.ALL;
+ root.onLevelChanged.listen(levels.add);
+ root.level = Level.SEVERE;
+ root.level = Level.WARNING;
+ expect(levels, hasLength(2));
+ });
+
+ test('onLevelChanged is not emited if set the level to the same value', () {
+ final levels = <Level?>[];
+ root.level = Level.ALL;
+ root.onLevelChanged.listen(levels.add);
+ root.level = Level.ALL;
+ expect(levels, hasLength(0));
+ });
+
+ test('setting level in a loop throws state error', () {
+ root.level = Level.ALL;
+ root.onLevelChanged.listen((event) {
+ // Cannot fire new event. Controller is already firing an event
+ expect(() => root.level = Level.SEVERE, throwsStateError);
+ });
+ root.level = Level.WARNING;
+ expect(root.level, Level.SEVERE);
+ });
+ });
+}
diff --git a/pkgs/os_detect/.gitignore b/pkgs/os_detect/.gitignore
new file mode 100644
index 0000000..49ce72d
--- /dev/null
+++ b/pkgs/os_detect/.gitignore
@@ -0,0 +1,3 @@
+.dart_tool/
+.packages
+pubspec.lock
diff --git a/pkgs/os_detect/AUTHORS b/pkgs/os_detect/AUTHORS
new file mode 100644
index 0000000..846e4a1
--- /dev/null
+++ b/pkgs/os_detect/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the Dart project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google LLC
diff --git a/pkgs/os_detect/CHANGELOG.md b/pkgs/os_detect/CHANGELOG.md
new file mode 100644
index 0000000..9511042
--- /dev/null
+++ b/pkgs/os_detect/CHANGELOG.md
@@ -0,0 +1,21 @@
+## 2.0.3-wip
+
+- Require Dart 3.5
+
+## 2.0.2
+
+- Require Dart 3.0
+- Make work with VM's platform-constants.
+- Move to `dart-lang/core` monorepo.
+
+## 2.0.1
+
+- Populate the pubspec `repository` field.
+
+## 2.0.0
+
+- Stable null safety release.
+
+## 1.0.0
+
+- Initial release
diff --git a/pkgs/os_detect/LICENSE b/pkgs/os_detect/LICENSE
new file mode 100644
index 0000000..ed0a350
--- /dev/null
+++ b/pkgs/os_detect/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2020, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/os_detect/README.md b/pkgs/os_detect/README.md
new file mode 100644
index 0000000..cb931b1
--- /dev/null
+++ b/pkgs/os_detect/README.md
@@ -0,0 +1,42 @@
+[](https://github.com/dart-lang/core/actions/workflows/os_detect.yaml)
+[](https://pub.dev/packages/os_detect)
+[](https://pub.dev/packages/os_detect/publisher)
+
+Platform independent access to information about the current operating system.
+
+## Querying the current OS
+
+Exposes `operatingSystem` and `operatingSystemVersion` strings similar to those
+of the `Platform` class in `dart:io`, but also works on the web. The
+`operatingSystem` of a browser is the string "browser". Also exposes convenience
+getters like `isLinux`, `isAndroid` and `isBrowser` based on the
+`operatingSystem` string.
+
+To use this package instead of `dart:io`, replace the import of `dart:io` with:
+
+```dart
+import 'package:os_detect/os_detect.dart' as os_detect;
+```
+
+That should keep the code working if the only functionality used from `dart:io`
+is operating system detection. You should then use your IDE to rename the import
+prefix from `Platform` to something lower-cased which follows the style guide
+for import prefixes.
+
+Any new platform which supports neither `dart:io` nor `dart:html` can make
+itself recognizable by configuring the `dart.os.name` and `dart.os.version`
+environment settings, so that `const String.fromEnvironment` can access them.
+
+## Overriding the current OS string
+
+It's possible to override the current operating system string, as exposed by
+`operatingSystem` and `operatingSystemVersion` in
+`package:os_detect/os_detect.dart`. To do so, import the
+`package:os_detect/override.dart` library and use the `overrideOperatingSystem`
+function to run code in a zone where the operating system and version values are
+set to whatever values are desired.
+
+The class `OperatingSystemID` can also be used directly to abstract over the
+operating system name and version. The `OperatingSystemID.current` defaults to
+the values provided by the platform when not overridden using
+`overrideOperatingSystem`.
diff --git a/pkgs/os_detect/analysis_options.yaml b/pkgs/os_detect/analysis_options.yaml
new file mode 100644
index 0000000..da31799
--- /dev/null
+++ b/pkgs/os_detect/analysis_options.yaml
@@ -0,0 +1,26 @@
+# https://dart.dev/tools/analysis#the-analysis-options-file
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_classes_with_only_static_members
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_returning_this
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - use_raw_strings
+
+
diff --git a/pkgs/os_detect/bin/os_detect.dart b/pkgs/os_detect/bin/os_detect.dart
new file mode 100644
index 0000000..d886a4f
--- /dev/null
+++ b/pkgs/os_detect/bin/os_detect.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Prints the operating system detected by the current compilation environment.
+library;
+
+import 'package:os_detect/os_detect.dart' as os_detect;
+
+void main() {
+ final knownName = knownOSName();
+ print('OS name : ${os_detect.operatingSystem} '
+ '${knownName != null ? '($knownName)' : ''}');
+ print('OS version : ${os_detect.operatingSystemVersion}');
+}
+
+String? knownOSName() {
+ if (os_detect.isAndroid) {
+ return 'Android';
+ }
+ if (os_detect.isBrowser) {
+ return 'Browser';
+ }
+ if (os_detect.isFuchsia) {
+ return 'Fuchsia';
+ }
+ if (os_detect.isIOS) {
+ return 'iOS';
+ }
+ if (os_detect.isLinux) {
+ return 'Linux';
+ }
+ if (os_detect.isMacOS) {
+ return 'MacOS';
+ }
+ if (os_detect.isWindows) {
+ return 'Windows';
+ }
+ return null;
+}
diff --git a/pkgs/os_detect/example/example.dart b/pkgs/os_detect/example/example.dart
new file mode 100644
index 0000000..4a159d8
--- /dev/null
+++ b/pkgs/os_detect/example/example.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:os_detect/os_detect.dart' as os_detect;
+
+void main() {
+ print('''
+ OS ID: ${os_detect.operatingSystem}
+OS Version: ${os_detect.operatingSystemVersion}''');
+ if (os_detect.isAndroid) {
+ print(' OS Type: Android');
+ } else if (os_detect.isBrowser) {
+ print(' OS Type: Browser');
+ } else if (os_detect.isFuchsia) {
+ print(' OS Type: Fuchsia');
+ } else if (os_detect.isIOS) {
+ print(' OS Type: iOS');
+ } else if (os_detect.isLinux) {
+ print(' OS Type: Linux');
+ } else if (os_detect.isMacOS) {
+ print(' OS Type: MacOS');
+ } else if (os_detect.isWindows) {
+ print(' OS Type: Windows');
+ }
+}
diff --git a/pkgs/os_detect/example/tree_shaking.dart b/pkgs/os_detect/example/tree_shaking.dart
new file mode 100644
index 0000000..987f3dd
--- /dev/null
+++ b/pkgs/os_detect/example/tree_shaking.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Try compiling this example with (if on Linux):
+//
+// dart compile exe --target-os=linux tree_shaking.dart
+//
+// then check that "SOMETHING ELSE" does not occur in the
+// output `tree_shaking.exe` program, e.g.:
+//
+// strings tree_shaking.exe | grep SOMETHING
+//
+// which shows no matches.
+
+import 'package:os_detect/os_detect.dart' as platform;
+
+void main() {
+ if (platform.isLinux) {
+ print('Is Linux');
+ } else {
+ print('SOMETHING ELSE');
+ }
+ if (platform.operatingSystem == 'linux') {
+ print('Is Linux');
+ } else {
+ print('SOMETHING ELSE');
+ }
+}
diff --git a/pkgs/os_detect/lib/os_detect.dart b/pkgs/os_detect/lib/os_detect.dart
new file mode 100644
index 0000000..7279d2f
--- /dev/null
+++ b/pkgs/os_detect/lib/os_detect.dart
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Information about the current operating system.
+library;
+
+import 'src/os_override.dart';
+
+/// Identification of the current operating system or platform.
+///
+/// Specific known operating systems are reported by a unique known string,
+/// and all the `is<Name>` values are computed by comparing the
+/// [operatingSystem] string against those known strings.
+/// That means that *at most* one of those value can be `true`,
+/// and usually precisely one will be `true`.
+///
+/// **Notice:** Programs running in a browser will report their
+/// operating system as `"browser"`, not the operating system
+/// that browser is running on. See [isBrowser].
+String get operatingSystem => OperatingSystem.current.id;
+
+/// Representation of the version of the current operating system or platform.
+///
+/// May be empty if no version is known or available.
+String get operatingSystemVersion => OperatingSystem.current.version;
+
+/// Whether the current operating system is a version of
+/// [Linux](https://en.wikipedia.org/wiki/Linux).
+///
+/// Identified by [operatingSystem] being the string `linux`.
+///
+/// This value is `false` if the operating system is a specialized
+/// version of Linux that identifies itself by a different name,
+/// for example Android (see [isAndroid]),
+/// or if the code is running inside a browser (see [isBrowser]).
+@pragma('vm:prefer-inline')
+bool get isLinux => OperatingSystem.current.isLinux;
+
+/// Whether the current operating system is a version of
+/// [macOS](https://en.wikipedia.org/wiki/MacOS).
+///
+/// Identified by [operatingSystem] being the string `macos`.
+///
+/// The value is `false` if the code is running inside a browser,
+/// even if that browser is running on MacOS (see [isBrowser]).
+@pragma('vm:prefer-inline')
+bool get isMacOS => OperatingSystem.current.isMacOS;
+
+/// Whether the current operating system is a version of
+/// [Microsoft Windows](https://en.wikipedia.org/wiki/Microsoft_Windows).
+///
+/// Identified by [operatingSystem] being the string `windows`.
+///
+/// The value is `false` if the code is running inside a browser,
+/// even if that browser is running on Windows (see [isBrowser]).
+@pragma('vm:prefer-inline')
+bool get isWindows => OperatingSystem.current.isWindows;
+
+/// Whether the current operating system is a version of
+/// [Android](https://en.wikipedia.org/wiki/Android_%28operating_system%29).
+///
+/// Identified by [operatingSystem] being the string `android`.
+///
+/// The value is `false` if the code is running inside a browser,
+/// even if that browser is running on Android (see [isBrowser]).
+@pragma('vm:prefer-inline')
+bool get isAndroid => OperatingSystem.current.isAndroid;
+
+/// Whether the current operating system is a version of
+/// [iOS](https://en.wikipedia.org/wiki/IOS).
+///
+/// Identified by [operatingSystem] being the string `ios`.
+///
+/// The value is `false` if the code is running inside a browser,
+/// even if that browser is running on iOS (see [isBrowser]).
+@pragma('vm:prefer-inline')
+bool get isIOS => OperatingSystem.current.isIOS;
+
+/// Whether the current operating system is a version of
+/// [Fuchsia](https://en.wikipedia.org/wiki/Google_Fuchsia).
+///
+/// Identified by [operatingSystem] being the string `fuchsia`.
+///
+/// The value is `false` if the code is running inside a browser,
+/// even if that browser is running on Fuchsia (see [isBrowser]).
+@pragma('vm:prefer-inline')
+bool get isFuchsia => OperatingSystem.current.isFuchsia;
+
+/// Whether running in a web browser.
+///
+/// Identified by [operatingSystem] being the string `browser`.
+///
+/// If so, the [operatingSystemVersion] is the string made available
+/// through `window.navigator.appVersion`.
+///
+/// The value is `true` when the code is running inside a browser,
+/// no matter which operating system the browser is itself running on.
+/// No attempt is made to detect the underlying operating system.
+/// That information *may* be derived from [operatingSystemVersion],
+/// but browsers are able to lie in the app-version/user-agent
+/// string.
+@pragma('vm:prefer-inline')
+bool get isBrowser => OperatingSystem.current.isBrowser;
diff --git a/pkgs/os_detect/lib/override.dart b/pkgs/os_detect/lib/override.dart
new file mode 100644
index 0000000..cc3e918
--- /dev/null
+++ b/pkgs/os_detect/lib/override.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Functionality to override information about the current platform.
+library;
+
+export 'src/os_override.dart' show OperatingSystem, overrideOperatingSystem;
diff --git a/pkgs/os_detect/lib/src/os_kind.dart b/pkgs/os_detect/lib/src/os_kind.dart
new file mode 100644
index 0000000..7f4ee85
--- /dev/null
+++ b/pkgs/os_detect/lib/src/os_kind.dart
@@ -0,0 +1,101 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Shared constants and classes used to represent a recongized OS type
+///
+/// Not exported in the public API, but used to communicate between
+/// `override.dart` and the conditionally imported `osid_X.dart` files.
+///
+/// When the platform is statically known, all but one of the subclasses
+/// should be tree-shaken, so an `os is AndroidOS` can be resolved to
+/// a constant true/false depending on whether the class is the retained one
+/// or not.
+library;
+
+/// Operating identity object.
+///
+/// By only instantiating these subtypes guarded by target-OS guarded
+/// checks, unless using the "for testing" `OperatingSystem` constructor,
+/// all but one of the subclasses should be tree-shaken,
+/// and, e.g., the `_isId is IOS` test above should become tree-shakable
+/// on all other platforms.
+sealed class RecognizedOS {
+ // The recognized OS identifier strings recognized.
+ static const androidId = 'android';
+ static const browserId = 'browser';
+ static const fuchsiaId = 'fuchsia';
+ static const iOSId = 'ios';
+ static const linuxId = 'linux';
+ static const macOSId = 'macos';
+ static const windowsId = 'windows';
+
+ abstract final String id;
+ const RecognizedOS();
+}
+
+/// Operations system object for Android.
+class AndroidOS extends RecognizedOS {
+ @override
+ final String id = RecognizedOS.androidId;
+ const AndroidOS();
+}
+
+/// Operations system object for browsers.
+class BrowserOS extends RecognizedOS {
+ @override
+ final String id = RecognizedOS.browserId;
+ const BrowserOS();
+}
+
+/// Operations system object for Fuchsia.
+class FuchsiaOS extends RecognizedOS {
+ @override
+ final String id = RecognizedOS.fuchsiaId;
+ const FuchsiaOS();
+}
+
+/// Operations system object for iOS.
+class IOS extends RecognizedOS {
+ @override
+ final String id = RecognizedOS.iOSId;
+ const IOS();
+}
+
+/// Operations system object for Linux.
+class LinuxOS extends RecognizedOS {
+ @override
+ final String id = RecognizedOS.linuxId;
+ const LinuxOS();
+}
+
+/// Operations system object for MacOS.
+class MacOS extends RecognizedOS {
+ @override
+ final String id = RecognizedOS.macOSId;
+ const MacOS();
+}
+
+/// Operations system object for Windows.
+class WindowsOS extends RecognizedOS {
+ @override
+ final String id = RecognizedOS.windowsId;
+ const WindowsOS();
+}
+
+/// Fallback to represent unknown operating system.
+///
+/// Do not use for one of the recognized operating
+/// systems
+class UnknownOS extends RecognizedOS {
+ @override
+ final String id;
+ const UnknownOS(this.id)
+ : assert(id != RecognizedOS.linuxId),
+ assert(id != RecognizedOS.macOSId),
+ assert(id != RecognizedOS.windowsId),
+ assert(id != RecognizedOS.androidId),
+ assert(id != RecognizedOS.iOSId),
+ assert(id != RecognizedOS.fuchsiaId),
+ assert(id != RecognizedOS.browserId);
+}
diff --git a/pkgs/os_detect/lib/src/os_override.dart b/pkgs/os_detect/lib/src/os_override.dart
new file mode 100644
index 0000000..6f31bfd
--- /dev/null
+++ b/pkgs/os_detect/lib/src/os_override.dart
@@ -0,0 +1,180 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async' show Zone, runZoned;
+
+import 'package:meta/meta.dart';
+
+import 'os_kind.dart';
+import 'osid_unknown.dart'
+ if (dart.library.io) 'osid_io.dart'
+ if (dart.library.html) 'osid_html.dart';
+
+/// The name and version of an operating system.
+final class OperatingSystem {
+ // The recognized OS identifier strings.
+
+ /// The operating system ID string for Linux.
+ ///
+ /// Compare against [id] or the `operatingSystem` of `os_detect.dart`,
+ /// or use as argument to [OperatingSystem.new].
+ static const androidId = RecognizedOS.androidId;
+
+ /// The operating system ID string for browsers.
+ ///
+ /// Compare against [id] or the `operatingSystem` of `os_detect.dart`,
+ /// or use as argument to [OperatingSystem.new].
+ static const browserId = RecognizedOS.browserId;
+
+ /// The operating system ID string for Fuchsia.
+ ///
+ /// Compare against [id] or the `operatingSystem` of `os_detect.dart`,
+ /// or use as argument to [OperatingSystem.new].
+ static const fuchsiaId = RecognizedOS.fuchsiaId;
+
+ /// The operating system ID string for iOS.
+ ///
+ /// Compare against [id] or the `operatingSystem` of `os_detect.dart`,
+ /// or use as argument to [OperatingSystem.new].
+ static const iOSId = RecognizedOS.iOSId;
+
+ /// The operating system ID string for Linux.
+ ///
+ /// Compare against [id] or the `operatingSystem` of `os_detect.dart`,
+ /// or use as argument to [OperatingSystem.new].
+ static const linuxId = RecognizedOS.linuxId;
+
+ /// The operating system ID string for macOS.
+ ///
+ /// Compare against [id] or the `operatingSystem` of `os_detect.dart`,
+ /// or use as argument to [OperatingSystem.new].
+ static const macOSId = RecognizedOS.macOSId;
+
+ /// The operating system ID string for Windows.
+ ///
+ /// Compare against [id] or the `operatingSystem` of `os_detect.dart`,
+ /// or use as argument to [OperatingSystem.new].
+ static const windowsId = RecognizedOS.windowsId;
+
+ /// The current operating system ID.
+ ///
+ /// Defaults to what information is available
+ /// from known platform specific libraries,
+ /// but can be overridden using functionality from the
+ /// `osid_override.dart` library.
+ @pragma('vm:try-inline')
+ static OperatingSystem get current =>
+ Zone.current[#_os] as OperatingSystem? ?? platformOS;
+
+ /// A string representing the operating system or platform.
+ String get id => _osId.id;
+
+ // Operating system ID object.
+ final RecognizedOS _osId;
+
+ /// A string representing the version of the operating system or platform.
+ ///
+ /// May be empty if no version is known or available.
+ final String version;
+
+ /// Creates a new operating system object for testing.
+ ///
+ /// Can be used with [overrideOperatingSystem] to selectively
+ /// change the value returned by [current].
+ ///
+ /// **Notice:** Using this constructor may reduce the efficiency
+ /// of compilers recognizing code that isn't needed when compiling
+ /// for a particular platform (aka. "tree-shaking" of unreachable code).
+ // Uses chained conditionals to allow back-ends to constant fold when they
+ // know what `id` is, which they'd usually know for a specific operation.
+ // That can avoid retaining *all* the subclasses of `OS`.
+ @visibleForTesting
+ @pragma('vm:prefer-inline')
+ OperatingSystem(String id, String version)
+ : this._(
+ id == linuxId
+ ? const LinuxOS()
+ : id == macOSId
+ ? const MacOS()
+ : id == windowsId
+ ? const WindowsOS()
+ : id == androidId
+ ? const AndroidOS()
+ : id == iOSId
+ ? const IOS()
+ : id == fuchsiaId
+ ? const FuchsiaOS()
+ : id == browserId
+ ? const BrowserOS()
+ : UnknownOS(id),
+ version);
+
+ /// Used by platforms which know the ID object.
+ const OperatingSystem._(this._osId, this.version);
+
+ /// Whether the operating system is a version of
+ /// [Linux](https://en.wikipedia.org/wiki/Linux).
+ ///
+ /// Identified by [id] being the string `linux`.
+ ///
+ /// This value is `false` if the operating system is a specialized
+ /// version of Linux that identifies itself by a different name,
+ /// for example Android (see [isAndroid]).
+ bool get isLinux => _osId is LinuxOS;
+
+ /// Whether the operating system is a version of
+ /// [macOS](https://en.wikipedia.org/wiki/MacOS).
+ ///
+ /// Identified by [id] being the string `macos`.
+ bool get isMacOS => _osId is MacOS;
+
+ /// Whether the operating system is a version of
+ /// [Microsoft Windows](https://en.wikipedia.org/wiki/Microsoft_Windows).
+ ///
+ /// Identified by [id] being the string `windows`.
+ bool get isWindows => _osId is WindowsOS;
+
+ /// Whether the operating system is a version of
+ /// [Android](https://en.wikipedia.org/wiki/Android_%28operating_system%29).
+ ///
+ /// Identified by [id] being the string `android`.
+ bool get isAndroid => _osId is AndroidOS;
+
+ /// Whether the operating system is a version of
+ /// [iOS](https://en.wikipedia.org/wiki/IOS).
+ ///
+ /// Identified by [id] being the string `ios`.
+ bool get isIOS => _osId is IOS;
+
+ /// Whether the operating system is a version of
+ /// [Fuchsia](https://en.wikipedia.org/wiki/Google_Fuchsia).
+ ///
+ /// Identified by [id] being the string `fuchsia`.
+ bool get isFuchsia => _osId is FuchsiaOS;
+
+ /// Whether running in a web browser.
+ ///
+ /// Identified by [id] being the string `browser`.
+ ///
+ /// If so, the [version] is the string made available
+ /// through `window.navigator.appVersion`.
+ bool get isBrowser => _osId is BrowserOS;
+}
+
+/// Run [body] in a zone with platform overrides.
+///
+/// Overrides [OperatingSystem.current] with the supplied [operatingSystem]
+/// value while running in a new zone, and then runs [body] in that zone.
+///
+/// This override affects the `operatingSystem` and `version`
+/// exported by `package:osid/osid.dart`.
+R overrideOperatingSystem<R>(
+ OperatingSystem operatingSystem, R Function() body) =>
+ runZoned(body, zoneValues: {#_os: operatingSystem});
+
+// Exposes the `OperatingSystem._` constructor to the conditionally imported
+// libraries. Not exported by `../override.dart'.
+final class OperatingSystemInternal extends OperatingSystem {
+ const OperatingSystemInternal(super.id, super.version) : super._();
+}
diff --git a/pkgs/os_detect/lib/src/osid_html.dart b/pkgs/os_detect/lib/src/osid_html.dart
new file mode 100644
index 0000000..a5d896f
--- /dev/null
+++ b/pkgs/os_detect/lib/src/osid_html.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:html';
+
+import 'os_kind.dart' show BrowserOS;
+import 'os_override.dart';
+
+String get _osVersion => window.navigator.appVersion;
+
+final OperatingSystem platformOS =
+ OperatingSystemInternal(const BrowserOS(), _osVersion);
diff --git a/pkgs/os_detect/lib/src/osid_io.dart b/pkgs/os_detect/lib/src/osid_io.dart
new file mode 100644
index 0000000..56b45eb
--- /dev/null
+++ b/pkgs/os_detect/lib/src/osid_io.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'os_kind.dart';
+import 'os_override.dart';
+
+// Uses VM platform-constant functionality to constant fold this expression
+// when `Platform.operatingSystem` is known at compile-time.
+// Uses a valid "potentially constant" expression for this, instead of, e.g.,
+// a `switch` expression.
+@pragma('vm:platform-const')
+final RecognizedOS? _osType = Platform.operatingSystem == RecognizedOS.linuxId
+ ? const LinuxOS()
+ : Platform.operatingSystem == RecognizedOS.macOSId
+ ? const MacOS()
+ : Platform.operatingSystem == RecognizedOS.windowsId
+ ? const WindowsOS()
+ : Platform.operatingSystem == RecognizedOS.androidId
+ ? const AndroidOS()
+ : Platform.operatingSystem == RecognizedOS.iOSId
+ ? const IOS()
+ : Platform.operatingSystem == RecognizedOS.fuchsiaId
+ ? const FuchsiaOS()
+ : Platform.operatingSystem == RecognizedOS.browserId
+ ? const BrowserOS()
+ : null;
+
+final OperatingSystem platformOS = OperatingSystemInternal(
+ _osType ?? UnknownOS(Platform.operatingSystem),
+ Platform.operatingSystemVersion);
diff --git a/pkgs/os_detect/lib/src/osid_unknown.dart b/pkgs/os_detect/lib/src/osid_unknown.dart
new file mode 100644
index 0000000..2e6798e
--- /dev/null
+++ b/pkgs/os_detect/lib/src/osid_unknown.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'os_kind.dart';
+import 'os_override.dart';
+
+@pragma('vm:platform-const')
+const String _os =
+ String.fromEnvironment('dart.os.name', defaultValue: 'unknown');
+const String _osVersion = String.fromEnvironment('dart.os.version');
+
+const OperatingSystem platformOS = OperatingSystemInternal(
+ _os == RecognizedOS.linuxId
+ ? LinuxOS()
+ : _os == RecognizedOS.macOSId
+ ? MacOS()
+ : _os == RecognizedOS.windowsId
+ ? WindowsOS()
+ : _os == RecognizedOS.androidId
+ ? AndroidOS()
+ : _os == RecognizedOS.iOSId
+ ? IOS()
+ : _os == RecognizedOS.fuchsiaId
+ ? FuchsiaOS()
+ : _os == RecognizedOS.browserId
+ ? BrowserOS()
+ : UnknownOS(_os),
+ _osVersion);
diff --git a/pkgs/os_detect/pubspec.yaml b/pkgs/os_detect/pubspec.yaml
new file mode 100644
index 0000000..77281cd
--- /dev/null
+++ b/pkgs/os_detect/pubspec.yaml
@@ -0,0 +1,15 @@
+name: os_detect
+version: 2.0.3-wip
+description: Platform independent OS detection.
+repository: https://github.com/dart-lang/core/tree/main/pkgs/os_detect
+issue_tracker: https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aos_detect
+
+environment:
+ sdk: ^3.5.0
+
+dependencies:
+ meta: ^1.9.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.24.0
diff --git a/pkgs/os_detect/test/osid_test.dart b/pkgs/os_detect/test/osid_test.dart
new file mode 100644
index 0000000..862d937
--- /dev/null
+++ b/pkgs/os_detect/test/osid_test.dart
@@ -0,0 +1,73 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:os_detect/os_detect.dart';
+import 'package:os_detect/override.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('Exists and is consistent', () {
+ expect(operatingSystem, isNotNull);
+ expect(operatingSystemVersion, isNotNull);
+
+ expect(isLinux, operatingSystem == OperatingSystem.linuxId);
+ expect(isAndroid, operatingSystem == OperatingSystem.androidId);
+ expect(isMacOS, operatingSystem == OperatingSystem.macOSId);
+ expect(isWindows, operatingSystem == OperatingSystem.windowsId);
+ expect(isIOS, operatingSystem == OperatingSystem.iOSId);
+ expect(isFuchsia, operatingSystem == OperatingSystem.fuchsiaId);
+ expect(isBrowser, operatingSystem == OperatingSystem.browserId);
+ });
+
+ test('Override', () {
+ const overrideName = 'argle-bargle';
+ const overrideVersion = 'glop-glyf';
+ final overrideOS = OperatingSystem(overrideName, overrideVersion);
+ Zone? overrideZone;
+
+ final originalName = operatingSystem;
+ final originalVersion = operatingSystemVersion;
+ final originalID = OperatingSystem.current;
+ final originalZone = Zone.current;
+ expect(originalName, isNot(overrideName));
+ expect(originalVersion, isNot(overrideVersion));
+
+ // Override OS ID.
+ overrideOperatingSystem(overrideOS, () {
+ overrideZone = Zone.current;
+ expect(operatingSystem, overrideName);
+ expect(operatingSystemVersion, overrideVersion);
+ expect(OperatingSystem.current, same(overrideOS));
+ // Nested override.
+ overrideOperatingSystem(originalID, () {
+ expect(operatingSystem, originalName);
+ expect(operatingSystemVersion, originalVersion);
+ expect(OperatingSystem.current, same(originalID));
+ });
+ expect(operatingSystem, overrideName);
+ expect(operatingSystemVersion, overrideVersion);
+ expect(OperatingSystem.current, same(overrideOS));
+ // Captured parent zone does not have override.
+ originalZone.run(() {
+ expect(operatingSystem, originalName);
+ expect(operatingSystemVersion, originalVersion);
+ });
+ expect(operatingSystem, overrideName);
+ expect(operatingSystemVersion, overrideVersion);
+ expect(OperatingSystem.current, same(overrideOS));
+ });
+
+ expect(operatingSystem, originalName);
+ expect(operatingSystemVersion, originalVersion);
+
+ // A captured override zone retains the override.
+ overrideZone!.run(() {
+ expect(operatingSystem, overrideName);
+ expect(operatingSystemVersion, overrideVersion);
+ expect(OperatingSystem.current, same(overrideOS));
+ });
+ });
+}
diff --git a/pkgs/path/.gitignore b/pkgs/path/.gitignore
new file mode 100644
index 0000000..8608a82
--- /dev/null
+++ b/pkgs/path/.gitignore
@@ -0,0 +1,16 @@
+# Don’t commit the following directories created by pub.
+.buildlog
+.pub/
+.dart_tool/
+.idea/
+build/
+.packages
+
+# Or the files created by dart2js.
+*.dart.js
+*.js_
+*.js.deps
+*.js.map
+
+# Include when developing application packages.
+pubspec.lock
diff --git a/pkgs/path/CHANGELOG.md b/pkgs/path/CHANGELOG.md
new file mode 100644
index 0000000..c91afe1
--- /dev/null
+++ b/pkgs/path/CHANGELOG.md
@@ -0,0 +1,186 @@
+## 1.9.1
+
+- Require Dart 3.4
+- Move to `dart-lang/core` monorepo.
+
+## 1.9.0
+
+* Allow percent-encoded colons (`%3a`) in drive letters in `fromUri`.
+* Fixed an issue with the `split` method doc comment.
+* Require Dart 3.0
+
+## 1.8.3
+
+* Support up to 16 arguments in join function and up to 15 arguments in absolute function.
+
+## 1.8.2
+
+* Enable the `avoid_dynamic_calls` lint.
+* Populate the pubspec `repository` field.
+
+## 1.8.1
+
+* Don't crash when an empty string is passed to `toUri()`.
+
+## 1.8.0
+
+* Stable release for null safety.
+
+## 1.8.0-nullsafety.3
+
+* Update SDK constraints to `>=2.12.0-0 <3.0.0` based on beta release
+ guidelines.
+
+## 1.8.0-nullsafety.2
+
+* Allow prerelease versions of the 2.12 sdk.
+
+## 1.8.0-nullsafety.1
+
+* Allow 2.10 stable and 2.11.0 dev SDK versions.
+
+## 1.8.0-nullsafety
+
+* Migrate to null safety.
+
+## 1.7.0
+
+* Add support for multiple extension in `context.extension()`.
+
+## 1.6.4
+
+* Fixed a number of lints that affect the package health score.
+
+* Added an example.
+
+## 1.6.3
+
+* Don't throw a FileSystemException from `current` if the working directory has
+ been deleted, but we have a cached one we can use.
+
+## 1.6.2
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 1.6.1
+
+* Drop the `retype` implementation for compatibility with the latest SDK.
+
+## 1.6.0
+
+* Add a `PathMap` class that uses path equality for its keys.
+
+* Add a `PathSet` class that uses path equality for its contents.
+
+## 1.5.1
+
+* Fix a number of bugs that occurred when the current working directory was `/`
+ on Linux or Mac OS.
+
+## 1.5.0
+
+* Add a `setExtension()` top-level function and `Context` method.
+
+## 1.4.2
+
+* Treat `package:` URLs as absolute.
+
+* Normalize `c:\foo\.` to `c:\foo`.
+
+## 1.4.1
+
+* Root-relative URLs like `/foo` are now resolved relative to the drive letter
+ for `file` URLs that begin with a Windows-style drive letter. This matches the
+ [WHATWG URL specification][].
+
+[WHATWG URL specification]: https://url.spec.whatwg.org/#file-slash-state
+
+* When a root-relative URLs like `/foo` is converted to a Windows path using
+ `fromUrl()`, it is now resolved relative to the drive letter. This matches
+ IE's behavior.
+
+## 1.4.0
+
+* Add `equals()`, `hash()` and `canonicalize()` top-level functions and
+ `Context` methods. These make it easier to treat paths as map keys.
+
+* Properly compare Windows paths case-insensitively.
+
+* Further improve the performance of `isWithin()`.
+
+## 1.3.9
+
+* Further improve the performance of `isWithin()` when paths contain `/.`
+ sequences that aren't `/../`.
+
+## 1.3.8
+
+* Improve the performance of `isWithin()` when the paths don't contain
+ asymmetrical `.` or `..` components.
+
+* Improve the performance of `relative()` when `from` is `null` and the path is
+ already relative.
+
+* Improve the performance of `current` when the current directory hasn't
+ changed.
+
+## 1.3.7
+
+* Improve the performance of `absolute()` and `normalize()`.
+
+## 1.3.6
+
+* Ensure that `path.toUri` preserves trailing slashes for relative paths.
+
+## 1.3.5
+
+* Added type annotations to top-level and static fields.
+
+## 1.3.4
+
+* Fix dev_compiler warnings.
+
+## 1.3.3
+
+* Performance improvement in `Context.relative` - don't call `current` if `from`
+ is not relative.
+
+## 1.3.2
+
+* Fix some analyzer hints.
+
+## 1.3.1
+
+* Add a number of performance improvements.
+
+## 1.3.0
+
+* Expose a top-level `context` field that provides access to a `Context` object
+ for the current system.
+
+## 1.2.3
+
+* Don't cache path Context based on cwd, as cwd involves a system-call to
+ compute.
+
+## 1.2.2
+
+* Remove the documentation link from the pubspec so this is linked to
+ pub.dev by default.
+
+# 1.2.1
+
+* Many members on `Style` that provided access to patterns and functions used
+ internally for parsing paths have been deprecated.
+
+* Manually parse paths (rather than using RegExps to do so) for better
+ performance.
+
+# 1.2.0
+
+* Added `path.prettyUri`, which produces a human-readable representation of a
+ URI.
+
+# 1.1.0
+
+* `path.fromUri` now accepts strings as well as `Uri` objects.
diff --git a/pkgs/path/LICENSE b/pkgs/path/LICENSE
new file mode 100644
index 0000000..000cd7b
--- /dev/null
+++ b/pkgs/path/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/path/README.md b/pkgs/path/README.md
new file mode 100644
index 0000000..53ec889
--- /dev/null
+++ b/pkgs/path/README.md
@@ -0,0 +1,120 @@
+[](https://github.com/dart-lang/core/actions/workflows/path.yaml)
+[](https://pub.dev/packages/path)
+[](https://pub.dev/packages/path/publisher)
+
+A comprehensive, cross-platform path manipulation library for Dart.
+
+The path package provides common operations for manipulating paths:
+joining, splitting, normalizing, etc.
+
+We've tried very hard to make this library do the "right" thing on whatever
+platform you run it on, including in the browser. When you use the top-level
+functions, it will assume the current platform's path style and work with
+that. If you want to explicitly work with paths of a specific style, you can
+construct a [`p.Context`][Context] for that style.
+
+[Context]: https://pub.dev/documentation/path/latest/path/Context-class.html
+
+## Using
+
+The path library was designed to be imported with a prefix, though you don't
+have to if you don't want to:
+
+```dart
+import 'package:path/path.dart' as p;
+```
+
+The most common way to use the library is through the top-level functions.
+These manipulate path strings based on your current working directory and
+the path style (POSIX, Windows, or URLs) of the host platform. For example:
+
+```dart
+p.join('directory', 'file.txt');
+```
+
+This calls the top-level `join()` function to join the "directory" and
+"file.txt" using the current platform's directory separator.
+
+If you want to work with paths for a specific platform regardless of the underlying platform that the program is running on, you can create a
+[Context] and give it an explicit [Style]:
+
+```dart
+var context = p.Context(style: Style.windows);
+context.join('directory', 'file.txt');
+```
+
+This will join "directory" and "file.txt" using the Windows path separator,
+even when the program is run on a POSIX machine.
+
+## Stability
+
+The `path` package is used by many Dart packages, and as such it strives for a
+very high degree of stability. For the same reason, though, releasing a new
+major version would probably cause a lot of versioning pain, so some flexibility
+is necessary.
+
+We try to guarantee that **operations with valid inputs and correct output will
+not change**. Operations, where one or more inputs are invalid according to the
+semantics of the corresponding platform, may produce different outputs over time.
+Operations for which `path` produces incorrect output will also change so that
+we can fix bugs.
+
+Also, the `path` package's URL handling is based on [the WHATWG URL spec][].
+This is a living standard, and some parts of it haven't yet been entirely
+solidified by vendor support. The `path` package reserves the right to change
+its URL behavior if the underlying specification changes, although if the change
+is big enough to break many valid uses we may elect to treat it as a breaking
+change anyway.
+
+[the WHATWG URL spec]: https://url.spec.whatwg.org/
+
+## FAQ
+
+### Where can I use this?
+
+The `path` package runs on the Dart VM and in the browser under both dart2js and
+Dartium. On the browser, `window.location.href` is used as the current path.
+
+### Why doesn't this make paths first-class objects?
+
+When you have path *objects*, then every API that takes a path has to decide if
+it accepts strings, path objects, or both.
+
+ * Accepting strings is the most convenient, but then it seems weird to have
+ these path objects that aren't actually accepted by anything that needs a
+ path. Once you've created a path, you have to always call `.toString()` on
+ it before you can do anything useful with it.
+
+ * Requiring objects forces users to wrap path strings in these objects, which
+ is tedious. It also means coupling that API to whatever library defines this
+ path class. If there are multiple "path" libraries that each define their
+ own path types, then any library that works with paths has to pick which one
+ it uses.
+
+ * Taking both means you can't type your API. That defeats the purpose of
+ having a path type: why have a type if your APIs can't annotate that they
+ expect it?
+
+Given that, we've decided this library should simply treat paths as strings.
+
+### How cross-platform is this?
+
+We believe this library handles most of the corner cases of Windows paths
+(POSIX paths are generally pretty straightforward):
+
+ * It understands that *both* "/" and "\\" are valid path separators, not just
+ "\\".
+
+ * It can accurately tell if a path is absolute based on drive-letters or UNC
+ prefix.
+
+ * It understands that "/foo" is not an absolute path on Windows.
+
+ * It knows that "C:\foo\one.txt" and "c:/foo\two.txt" are two files in the
+ same directory.
+
+### What is a "path" in the browser?
+
+If you use this package in a browser, then it considers the "platform" to be
+the browser itself and uses URL strings to represent "browser paths".
+
diff --git a/pkgs/path/analysis_options.yaml b/pkgs/path/analysis_options.yaml
new file mode 100644
index 0000000..6c71296
--- /dev/null
+++ b/pkgs/path/analysis_options.yaml
@@ -0,0 +1,16 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+linter:
+ rules:
+ - avoid_private_typedef_functions
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - join_return_with_assignment
+ - missing_whitespace_between_adjacent_strings
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - prefer_final_locals
+ - unnecessary_breaks
+ - use_string_buffers
diff --git a/pkgs/path/benchmark/benchmark.dart b/pkgs/path/benchmark/benchmark.dart
new file mode 100644
index 0000000..b6647bb
--- /dev/null
+++ b/pkgs/path/benchmark/benchmark.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:path/path.dart' as p;
+
+/// Some hopefully real-world representative platform-independent paths.
+const genericPaths = [
+ '.',
+ '..',
+ 'out/ReleaseIA32/packages',
+ 'lib',
+ 'lib/src/',
+ 'lib/src/style/url.dart',
+ 'test/./not/.././normalized',
+ 'benchmark/really/long/path/with/many/components.dart',
+];
+
+/// Some platform-specific paths.
+final platformPaths = {
+ p.Style.posix: [
+ '/',
+ '/home/user/dart/sdk/lib/indexed_db/dart2js/indexed_db_dart2js.dart',
+ ],
+ p.Style.url: [
+ 'https://example.server.org/443643002/path?top=yes#fragment',
+ ],
+ p.Style.windows: [
+ r'C:\User\me\',
+ r'\\server\share\my\folders\some\file.data',
+ ],
+};
+
+/// The command line arguments passed to this script.
+late final List<String> arguments;
+
+void main(List<String> args) {
+ arguments = args;
+
+ for (var style in [p.Style.posix, p.Style.url, p.Style.windows]) {
+ final context = p.Context(style: style);
+ final files = <String>[...genericPaths, ...platformPaths[style]!];
+
+ void benchmark(String name, void Function(String) function) {
+ runBenchmark('${style.name}-$name', 100000, () {
+ for (var file in files) {
+ function(file);
+ }
+ });
+ }
+
+ void benchmarkPairs(String name, void Function(String, String) function) {
+ runBenchmark('${style.name}-$name', 1000, () {
+ for (var file1 in files) {
+ for (var file2 in files) {
+ function(file1, file2);
+ }
+ }
+ });
+ }
+
+ benchmark('absolute', context.absolute);
+ benchmark('basename', context.basename);
+ benchmark('basenameWithoutExtension', context.basenameWithoutExtension);
+ benchmark('dirname', context.dirname);
+ benchmark('extension', context.extension);
+ benchmark('rootPrefix', context.rootPrefix);
+ benchmark('isAbsolute', context.isAbsolute);
+ benchmark('isRelative', context.isRelative);
+ benchmark('isRootRelative', context.isRootRelative);
+ benchmark('normalize', context.normalize);
+ benchmark('relative', context.relative);
+ benchmarkPairs('relative from', (String file, String from) {
+ try {
+ context.relative(file, from: from);
+ } on p.PathException {
+ // Do nothing.
+ }
+ });
+ benchmark('toUri', context.toUri);
+ benchmark('prettyUri', context.prettyUri);
+ benchmarkPairs('isWithin', context.isWithin);
+ }
+
+ runBenchmark('current', 100000, () => p.current);
+}
+
+void runBenchmark(String name, int count, void Function() function) {
+ // If names are passed on the command-line, they select which benchmarks are
+ // run.
+ if (arguments.isNotEmpty && !arguments.contains(name)) return;
+
+ // Warmup.
+ for (var i = 0; i < 10000; i++) {
+ function();
+ }
+
+ final stopwatch = Stopwatch()..start();
+ for (var i = 0; i < count; i++) {
+ function();
+ }
+
+ final rate =
+ (count / stopwatch.elapsedMicroseconds).toStringAsFixed(5).padLeft(9);
+ print('${name.padLeft(32)}: $rate iter/us (${stopwatch.elapsed})');
+}
diff --git a/pkgs/path/example/example.dart b/pkgs/path/example/example.dart
new file mode 100644
index 0000000..89271fd
--- /dev/null
+++ b/pkgs/path/example/example.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:path/path.dart' as p;
+
+void main() {
+ print('Current path style: ${p.style}');
+
+ print('Current process path: ${p.current}');
+
+ print('Separators');
+ for (var entry in [p.posix, p.windows, p.url]) {
+ print(' ${entry.style.toString().padRight(7)}: ${entry.separator}');
+ }
+}
diff --git a/pkgs/path/lib/path.dart b/pkgs/path/lib/path.dart
new file mode 100644
index 0000000..40f1ea3
--- /dev/null
+++ b/pkgs/path/lib/path.dart
@@ -0,0 +1,481 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A comprehensive, cross-platform path manipulation library.
+///
+/// The path library was designed to be imported with a prefix, though you don't
+/// have to if you don't want to:
+///
+/// import 'package:path/path.dart' as p;
+///
+/// The most common way to use the library is through the top-level functions.
+/// These manipulate path strings based on your current working directory and
+/// the path style (POSIX, Windows, or URLs) of the host platform. For example:
+///
+/// p.join('directory', 'file.txt');
+///
+/// This calls the top-level [join] function to join "directory" and "file.txt"
+/// using the current platform's directory separator.
+///
+/// If you want to work with paths for a specific platform regardless of the
+/// underlying platform that the program is running on, you can create a
+/// [Context] and give it an explicit [Style]:
+///
+/// var context = p.Context(style: Style.windows);
+/// context.join('directory', 'file.txt');
+///
+/// This will join "directory" and "file.txt" using the Windows path separator,
+/// even when the program is run on a POSIX machine.
+library;
+
+import 'src/context.dart';
+import 'src/style.dart';
+
+export 'src/context.dart' hide createInternal;
+export 'src/path_exception.dart';
+export 'src/path_map.dart';
+export 'src/path_set.dart';
+export 'src/style.dart';
+
+/// A default context for manipulating POSIX paths.
+final Context posix = Context(style: Style.posix);
+
+/// A default context for manipulating Windows paths.
+final Context windows = Context(style: Style.windows);
+
+/// A default context for manipulating URLs.
+///
+/// URL path equality is undefined for paths that differ only in their
+/// percent-encoding or only in the case of their host segment.
+final Context url = Context(style: Style.url);
+
+/// The system path context.
+///
+/// This differs from a context created with [Context.new] in that its
+/// [Context.current] is always the current working directory, rather than being
+/// set once when the context is created.
+final Context context = createInternal();
+
+/// Returns the [Style] of the current context.
+///
+/// This is the style that all top-level path functions will use.
+Style get style => context.style;
+
+/// Gets the path to the current working directory.
+///
+/// In the browser, this means the current URL, without the last file segment.
+String get current {
+ // If the current working directory gets deleted out from under the program,
+ // accessing it will throw an IO exception. In order to avoid transient
+ // errors, if we already have a cached working directory, catch the error and
+ // use that.
+ Uri uri;
+ try {
+ uri = Uri.base;
+ } on Exception {
+ if (_current != null) return _current!;
+ rethrow;
+ }
+
+ // Converting the base URI to a file path is pretty slow, and the base URI
+ // rarely changes in practice, so we cache the result here.
+ if (uri == _currentUriBase) return _current!;
+ _currentUriBase = uri;
+
+ if (Style.platform == Style.url) {
+ _current = uri.resolve('.').toString();
+ } else {
+ final path = uri.toFilePath();
+ // Remove trailing '/' or '\' unless it is the only thing left
+ // (for instance the root on Linux).
+ final lastIndex = path.length - 1;
+ assert(path[lastIndex] == '/' || path[lastIndex] == '\\');
+ _current = lastIndex == 0 ? path : path.substring(0, lastIndex);
+ }
+ return _current!;
+}
+
+/// The last value returned by [Uri.base].
+///
+/// This is used to cache the current working directory.
+Uri? _currentUriBase;
+
+/// The last known value of the current working directory.
+///
+/// This is cached because [current] is called frequently but rarely actually
+/// changes.
+String? _current;
+
+/// Gets the path separator for the current platform. This is `\` on Windows
+/// and `/` on other platforms (including the browser).
+String get separator => context.separator;
+
+/// Returns a new path with the given path parts appended to [current].
+///
+/// Equivalent to [join()] with [current] as the first argument. Example:
+///
+/// p.absolute('path', 'to/foo'); // -> '/your/current/dir/path/to/foo'
+///
+/// Does not [normalize] or [canonicalize] paths.
+String absolute(String part1,
+ [String? part2,
+ String? part3,
+ String? part4,
+ String? part5,
+ String? part6,
+ String? part7,
+ String? part8,
+ String? part9,
+ String? part10,
+ String? part11,
+ String? part12,
+ String? part13,
+ String? part14,
+ String? part15]) =>
+ context.absolute(part1, part2, part3, part4, part5, part6, part7, part8,
+ part9, part10, part11, part12, part13, part14, part15);
+
+/// Gets the part of [path] after the last separator.
+///
+/// p.basename('path/to/foo.dart'); // -> 'foo.dart'
+/// p.basename('path/to'); // -> 'to'
+///
+/// Trailing separators are ignored.
+///
+/// p.basename('path/to/'); // -> 'to'
+String basename(String path) => context.basename(path);
+
+/// Gets the part of [path] after the last separator, and without any trailing
+/// file extension.
+///
+/// p.basenameWithoutExtension('path/to/foo.dart'); // -> 'foo'
+///
+/// Trailing separators are ignored.
+///
+/// p.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo'
+String basenameWithoutExtension(String path) =>
+ context.basenameWithoutExtension(path);
+
+/// Gets the part of [path] before the last separator.
+///
+/// p.dirname('path/to/foo.dart'); // -> 'path/to'
+/// p.dirname('path/to'); // -> 'path'
+///
+/// Trailing separators are ignored.
+///
+/// p.dirname('path/to/'); // -> 'path'
+///
+/// If an absolute path contains no directories, only a root, then the root
+/// is returned.
+///
+/// p.dirname('/'); // -> '/' (posix)
+/// p.dirname('c:\'); // -> 'c:\' (windows)
+///
+/// If a relative path has no directories, then '.' is returned.
+///
+/// p.dirname('foo'); // -> '.'
+/// p.dirname(''); // -> '.'
+String dirname(String path) => context.dirname(path);
+
+/// Gets the file extension of [path]: the portion of [basename] from the last
+/// `.` to the end (including the `.` itself).
+///
+/// p.extension('path/to/foo.dart'); // -> '.dart'
+/// p.extension('path/to/foo'); // -> ''
+/// p.extension('path.to/foo'); // -> ''
+/// p.extension('path/to/foo.dart.js'); // -> '.js'
+///
+/// If the file name starts with a `.`, then that is not considered the
+/// extension:
+///
+/// p.extension('~/.bashrc'); // -> ''
+/// p.extension('~/.notes.txt'); // -> '.txt'
+///
+/// Takes an optional parameter `level` which makes possible to return
+/// multiple extensions having `level` number of dots. If `level` exceeds the
+/// number of dots, the full extension is returned. The value of `level` must
+/// be greater than 0, else `RangeError` is thrown.
+///
+/// p.extension('foo.bar.dart.js', 2); // -> '.dart.js
+/// p.extension('foo.bar.dart.js', 3); // -> '.bar.dart.js'
+/// p.extension('foo.bar.dart.js', 10); // -> '.bar.dart.js'
+/// p.extension('path/to/foo.bar.dart.js', 2); // -> '.dart.js'
+String extension(String path, [int level = 1]) =>
+ context.extension(path, level);
+
+/// Returns the root of [path], if it's absolute, or the empty string if it's
+/// relative.
+///
+/// // Unix
+/// p.rootPrefix('path/to/foo'); // -> ''
+/// p.rootPrefix('/path/to/foo'); // -> '/'
+///
+/// // Windows
+/// p.rootPrefix(r'path\to\foo'); // -> ''
+/// p.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
+/// p.rootPrefix(r'\\server\share\a\b'); // -> r'\\server\share'
+///
+/// // URL
+/// p.rootPrefix('path/to/foo'); // -> ''
+/// p.rootPrefix('https://dart.dev/path/to/foo');
+/// // -> 'https://dart.dev'
+String rootPrefix(String path) => context.rootPrefix(path);
+
+/// Returns `true` if [path] is an absolute path and `false` if it is a
+/// relative path.
+///
+/// On POSIX systems, absolute paths start with a `/` (forward slash). On
+/// Windows, an absolute path starts with `\\`, or a drive letter followed by
+/// `:/` or `:\`. For URLs, absolute paths either start with a protocol and
+/// optional hostname (e.g. `https://dart.dev`, `file://`) or with a `/`.
+///
+/// URLs that start with `/` are known as "root-relative", since they're
+/// relative to the root of the current URL. Since root-relative paths are still
+/// absolute in every other sense, [isAbsolute] will return true for them. They
+/// can be detected using [isRootRelative].
+bool isAbsolute(String path) => context.isAbsolute(path);
+
+/// Returns `true` if [path] is a relative path and `false` if it is absolute.
+/// On POSIX systems, absolute paths start with a `/` (forward slash). On
+/// Windows, an absolute path starts with `\\`, or a drive letter followed by
+/// `:/` or `:\`.
+bool isRelative(String path) => context.isRelative(path);
+
+/// Returns `true` if [path] is a root-relative path and `false` if it's not.
+///
+/// URLs that start with `/` are known as "root-relative", since they're
+/// relative to the root of the current URL. Since root-relative paths are still
+/// absolute in every other sense, [isAbsolute] will return true for them. They
+/// can be detected using [isRootRelative].
+///
+/// No POSIX and Windows paths are root-relative.
+bool isRootRelative(String path) => context.isRootRelative(path);
+
+/// Joins the given path parts into a single path using the current platform's
+/// [separator]. Example:
+///
+/// p.join('path', 'to', 'foo'); // -> 'path/to/foo'
+///
+/// If any part ends in a path separator, then a redundant separator will not
+/// be added:
+///
+/// p.join('path/', 'to', 'foo'); // -> 'path/to/foo'
+///
+/// If a part is an absolute path, then anything before that will be ignored:
+///
+/// p.join('path', '/to', 'foo'); // -> '/to/foo'
+String join(String part1,
+ [String? part2,
+ String? part3,
+ String? part4,
+ String? part5,
+ String? part6,
+ String? part7,
+ String? part8,
+ String? part9,
+ String? part10,
+ String? part11,
+ String? part12,
+ String? part13,
+ String? part14,
+ String? part15,
+ String? part16]) =>
+ context.join(part1, part2, part3, part4, part5, part6, part7, part8, part9,
+ part10, part11, part12, part13, part14, part15, part16);
+
+/// Joins the given path parts into a single path using the current platform's
+/// [separator]. Example:
+///
+/// p.joinAll(['path', 'to', 'foo']); // -> 'path/to/foo'
+///
+/// If any part ends in a path separator, then a redundant separator will not
+/// be added:
+///
+/// p.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo'
+///
+/// If a part is an absolute path, then anything before that will be ignored:
+///
+/// p.joinAll(['path', '/to', 'foo']); // -> '/to/foo'
+///
+/// For a fixed number of parts, [join] is usually terser.
+String joinAll(Iterable<String> parts) => context.joinAll(parts);
+
+/// Splits [path] into its components using the current platform's [separator].
+///
+/// p.split('path/to/foo'); // -> ['path', 'to', 'foo']
+///
+/// The path will *not* be normalized before splitting.
+///
+/// p.split('path/../foo'); // -> ['path', '..', 'foo']
+///
+/// If [path] is absolute, the root directory will be the first element in the
+/// array. Example:
+///
+/// // Unix
+/// p.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo']
+///
+/// // Windows
+/// p.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo']
+/// p.split(r'\\server\share\path\to\foo');
+/// // -> [r'\\server\share', 'foo', 'bar', 'baz']
+///
+/// // Browser
+/// p.split('https://dart.dev/path/to/foo');
+/// // -> ['https://dart.dev', 'path', 'to', 'foo']
+List<String> split(String path) => context.split(path);
+
+/// Canonicalizes [path].
+///
+/// This is guaranteed to return the same path for two different input paths
+/// if and only if both input paths point to the same location. Unlike
+/// [normalize], it returns absolute paths when possible and canonicalizes
+/// ASCII case on Windows.
+///
+/// Note that this does not resolve symlinks.
+///
+/// If you want a map that uses path keys, it's probably more efficient to use a
+/// Map with [equals] and [hash] specified as the callbacks to use for keys than
+/// it is to canonicalize every key.
+String canonicalize(String path) => context.canonicalize(path);
+
+/// Normalizes [path], simplifying it by handling `..`, and `.`, and
+/// removing redundant path separators whenever possible.
+///
+/// Note that this is *not* guaranteed to return the same result for two
+/// equivalent input paths. For that, see [canonicalize]. Or, if you're using
+/// paths as map keys use [equals] and [hash] as the key callbacks.
+///
+/// p.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
+String normalize(String path) => context.normalize(path);
+
+/// Attempts to convert [path] to an equivalent relative path from the current
+/// directory.
+///
+/// // Given current directory is /root/path:
+/// p.relative('/root/path/a/b.dart'); // -> 'a/b.dart'
+/// p.relative('/root/other.dart'); // -> '../other.dart'
+///
+/// If the [from] argument is passed, [path] is made relative to that instead.
+///
+/// p.relative('/root/path/a/b.dart', from: '/root/path'); // -> 'a/b.dart'
+/// p.relative('/root/other.dart', from: '/root/path');
+/// // -> '../other.dart'
+///
+/// If [path] and/or [from] are relative paths, they are assumed to be relative
+/// to the current directory.
+///
+/// Since there is no relative path from one drive letter to another on Windows,
+/// or from one hostname to another for URLs, this will return an absolute path
+/// in those cases.
+///
+/// // Windows
+/// p.relative(r'D:\other', from: r'C:\home'); // -> 'D:\other'
+///
+/// // URL
+/// p.relative('https://dart.dev', from: 'https://pub.dev');
+/// // -> 'https://dart.dev'
+String relative(String path, {String? from}) =>
+ context.relative(path, from: from);
+
+/// Returns `true` if [child] is a path beneath `parent`, and `false` otherwise.
+///
+/// p.isWithin('/root/path', '/root/path/a'); // -> true
+/// p.isWithin('/root/path', '/root/other'); // -> false
+/// p.isWithin('/root/path', '/root/path') // -> false
+bool isWithin(String parent, String child) => context.isWithin(parent, child);
+
+/// Returns `true` if [path1] points to the same location as [path2], and
+/// `false` otherwise.
+///
+/// The [hash] function returns a hash code that matches these equality
+/// semantics.
+bool equals(String path1, String path2) => context.equals(path1, path2);
+
+/// Returns a hash code for [path] such that, if [equals] returns `true` for two
+/// paths, their hash codes are the same.
+///
+/// Note that the same path may have different hash codes on different platforms
+/// or with different [current] directories.
+int hash(String path) => context.hash(path);
+
+/// Removes a trailing extension from the last part of [path].
+///
+/// p.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
+String withoutExtension(String path) => context.withoutExtension(path);
+
+/// Returns [path] with the trailing extension set to [extension].
+///
+/// If [path] doesn't have a trailing extension, this just adds [extension] to
+/// the end.
+///
+/// p.setExtension('path/to/foo.dart', '.js') // -> 'path/to/foo.js'
+/// p.setExtension('path/to/foo.dart.js', '.map')
+/// // -> 'path/to/foo.dart.map'
+/// p.setExtension('path/to/foo', '.js') // -> 'path/to/foo.js'
+String setExtension(String path, String extension) =>
+ context.setExtension(path, extension);
+
+/// Returns the path represented by [uri], which may be a [String] or a [Uri].
+///
+/// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL
+/// style, this will just convert [uri] to a string.
+///
+/// // POSIX
+/// p.fromUri('file:///path/to/foo') // -> '/path/to/foo'
+///
+/// // Windows
+/// p.fromUri('file:///C:/path/to/foo') // -> r'C:\path\to\foo'
+///
+/// // URL
+/// p.fromUri('https://dart.dev/path/to/foo')
+/// // -> 'https://dart.dev/path/to/foo'
+///
+/// If [uri] is relative, a relative path will be returned.
+///
+/// p.fromUri('path/to/foo'); // -> 'path/to/foo'
+String fromUri(Object? uri) => context.fromUri(uri!);
+
+/// Returns the URI that represents [path].
+///
+/// For POSIX and Windows styles, this will return a `file:` URI. For the URL
+/// style, this will just convert [path] to a [Uri].
+///
+/// // POSIX
+/// p.toUri('/path/to/foo')
+/// // -> Uri.parse('file:///path/to/foo')
+///
+/// // Windows
+/// p.toUri(r'C:\path\to\foo')
+/// // -> Uri.parse('file:///C:/path/to/foo')
+///
+/// // URL
+/// p.toUri('https://dart.dev/path/to/foo')
+/// // -> Uri.parse('https://dart.dev/path/to/foo')
+///
+/// If [path] is relative, a relative URI will be returned.
+///
+/// p.toUri('path/to/foo') // -> Uri.parse('path/to/foo')
+Uri toUri(String path) => context.toUri(path);
+
+/// Returns a terse, human-readable representation of [uri].
+///
+/// [uri] can be a [String] or a [Uri]. If it can be made relative to the
+/// current working directory, that's done. Otherwise, it's returned as-is. This
+/// gracefully handles non-`file:` URIs for [Style.posix] and [Style.windows].
+///
+/// The returned value is meant for human consumption, and may be either URI-
+/// or path-formatted.
+///
+/// // POSIX at "/root/path"
+/// p.prettyUri('file:///root/path/a/b.dart'); // -> 'a/b.dart'
+/// p.prettyUri('https://dart.dev/'); // -> 'https://dart.dev'
+///
+/// // Windows at "C:\root\path"
+/// p.prettyUri('file:///C:/root/path/a/b.dart'); // -> r'a\b.dart'
+/// p.prettyUri('https://dart.dev/'); // -> 'https://dart.dev'
+///
+/// // URL at "https://dart.dev/root/path"
+/// p.prettyUri('https://dart.dev/root/path/a/b.dart'); // -> r'a/b.dart'
+/// p.prettyUri('file:///root/path'); // -> 'file:///root/path'
+String prettyUri(Object? uri) => context.prettyUri(uri!);
diff --git a/pkgs/path/lib/src/characters.dart b/pkgs/path/lib/src/characters.dart
new file mode 100644
index 0000000..a083543
--- /dev/null
+++ b/pkgs/path/lib/src/characters.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This library contains character-code definitions.
+const plus = 0x2b;
+const minus = 0x2d;
+const period = 0x2e;
+const slash = 0x2f;
+const zero = 0x30;
+const nine = 0x39;
+const colon = 0x3a;
+const upperA = 0x41;
+const upperZ = 0x5a;
+const lowerA = 0x61;
+const lowerZ = 0x7a;
+const backslash = 0x5c;
diff --git a/pkgs/path/lib/src/context.dart b/pkgs/path/lib/src/context.dart
new file mode 100644
index 0000000..4523332
--- /dev/null
+++ b/pkgs/path/lib/src/context.dart
@@ -0,0 +1,1201 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:math' as math;
+
+import '../path.dart' as p;
+import 'characters.dart' as chars;
+import 'internal_style.dart';
+import 'parsed_path.dart';
+import 'path_exception.dart';
+import 'style.dart';
+
+Context createInternal() => Context._internal();
+
+/// An instantiable class for manipulating paths. Unlike the top-level
+/// functions, this lets you explicitly select what platform the paths will use.
+class Context {
+ /// Creates a new path context for the given style and current directory.
+ ///
+ /// If [style] is omitted, it uses the host operating system's path style. If
+ /// only [current] is omitted, it defaults ".". If *both* [style] and
+ /// [current] are omitted, [current] defaults to the real current working
+ /// directory.
+ ///
+ /// On the browser, [style] defaults to [Style.url] and [current] defaults to
+ /// the current URL.
+ factory Context({Style? style, String? current}) {
+ if (current == null) {
+ if (style == null) {
+ current = p.current;
+ } else {
+ current = '.';
+ }
+ }
+
+ if (style == null) {
+ style = Style.platform;
+ } else if (style is! InternalStyle) {
+ throw ArgumentError('Only styles defined by the path package are '
+ 'allowed.');
+ }
+
+ return Context._(style as InternalStyle, current);
+ }
+
+ /// Create a [Context] to be used internally within path.
+ Context._internal()
+ : style = Style.platform as InternalStyle,
+ _current = null;
+
+ Context._(this.style, this._current);
+
+ /// The style of path that this context works with.
+ final InternalStyle style;
+
+ /// The current directory given when Context was created. If null, current
+ /// directory is evaluated from 'p.current'.
+ final String? _current;
+
+ /// The current directory that relative paths are relative to.
+ String get current => _current ?? p.current;
+
+ /// Gets the path separator for the context's [style]. On Mac and Linux,
+ /// this is `/`. On Windows, it's `\`.
+ String get separator => style.separator;
+
+ /// Returns a new path with the given path parts appended to [current].
+ ///
+ /// Equivalent to [join()] with [current] as the first argument. Example:
+ ///
+ /// var context = Context(current: '/root');
+ /// context.absolute('path', 'to', 'foo'); // -> '/root/path/to/foo'
+ ///
+ /// If [current] isn't absolute, this won't return an absolute path. Does not
+ /// [normalize] or [canonicalize] paths.
+ String absolute(String part1,
+ [String? part2,
+ String? part3,
+ String? part4,
+ String? part5,
+ String? part6,
+ String? part7,
+ String? part8,
+ String? part9,
+ String? part10,
+ String? part11,
+ String? part12,
+ String? part13,
+ String? part14,
+ String? part15]) {
+ _validateArgList('absolute', [
+ part1,
+ part2,
+ part3,
+ part4,
+ part5,
+ part6,
+ part7,
+ part8,
+ part9,
+ part10,
+ part11,
+ part12,
+ part13,
+ part14,
+ part15
+ ]);
+
+ // If there's a single absolute path, just return it. This is a lot faster
+ // for the common case of `p.absolute(path)`.
+ if (part2 == null && isAbsolute(part1) && !isRootRelative(part1)) {
+ return part1;
+ }
+
+ return join(current, part1, part2, part3, part4, part5, part6, part7, part8,
+ part9, part10, part11, part12, part13, part14, part15);
+ }
+
+ /// Gets the part of [path] after the last separator on the context's
+ /// platform.
+ ///
+ /// context.basename('path/to/foo.dart'); // -> 'foo.dart'
+ /// context.basename('path/to'); // -> 'to'
+ ///
+ /// Trailing separators are ignored.
+ ///
+ /// context.basename('path/to/'); // -> 'to'
+ String basename(String path) => _parse(path).basename;
+
+ /// Gets the part of [path] after the last separator on the context's
+ /// platform, and without any trailing file extension.
+ ///
+ /// context.basenameWithoutExtension('path/to/foo.dart'); // -> 'foo'
+ ///
+ /// Trailing separators are ignored.
+ ///
+ /// context.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo'
+ String basenameWithoutExtension(String path) =>
+ _parse(path).basenameWithoutExtension;
+
+ /// Gets the part of [path] before the last separator.
+ ///
+ /// context.dirname('path/to/foo.dart'); // -> 'path/to'
+ /// context.dirname('path/to'); // -> 'path'
+ ///
+ /// Trailing separators are ignored.
+ ///
+ /// context.dirname('path/to/'); // -> 'path'
+ String dirname(String path) {
+ final parsed = _parse(path);
+ parsed.removeTrailingSeparators();
+ if (parsed.parts.isEmpty) return parsed.root ?? '.';
+ if (parsed.parts.length == 1) return parsed.root ?? '.';
+ parsed.parts.removeLast();
+ parsed.separators.removeLast();
+ parsed.removeTrailingSeparators();
+ return parsed.toString();
+ }
+
+ /// Gets the file extension of [path]: the portion of [basename] from the last
+ /// `.` to the end (including the `.` itself).
+ ///
+ /// context.extension('path/to/foo.dart'); // -> '.dart'
+ /// context.extension('path/to/foo'); // -> ''
+ /// context.extension('path.to/foo'); // -> ''
+ /// context.extension('path/to/foo.dart.js'); // -> '.js'
+ ///
+ /// If the file name starts with a `.`, then it is not considered an
+ /// extension:
+ ///
+ /// context.extension('~/.bashrc'); // -> ''
+ /// context.extension('~/.notes.txt'); // -> '.txt'
+ ///
+ /// Takes an optional parameter `level` which makes possible to return
+ /// multiple extensions having `level` number of dots. If `level` exceeds the
+ /// number of dots, the full extension is returned. The value of `level` must
+ /// be greater than 0, else `RangeError` is thrown.
+ ///
+ /// context.extension('foo.bar.dart.js', 2); // -> '.dart.js
+ /// context.extension('foo.bar.dart.js', 3); // -> '.bar.dart.js'
+ /// context.extension('foo.bar.dart.js', 10); // -> '.bar.dart.js'
+ /// context.extension('path/to/foo.bar.dart.js', 2); // -> '.dart.js'
+ String extension(String path, [int level = 1]) =>
+ _parse(path).extension(level);
+
+ /// Returns the root of [path] if it's absolute, or an empty string if it's
+ /// relative.
+ ///
+ /// // Unix
+ /// context.rootPrefix('path/to/foo'); // -> ''
+ /// context.rootPrefix('/path/to/foo'); // -> '/'
+ ///
+ /// // Windows
+ /// context.rootPrefix(r'path\to\foo'); // -> ''
+ /// context.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
+ /// context.rootPrefix(r'\\server\share\a\b'); // -> r'\\server\share'
+ ///
+ /// // URL
+ /// context.rootPrefix('path/to/foo'); // -> ''
+ /// context.rootPrefix('https://dart.dev/path/to/foo');
+ /// // -> 'https://dart.dev'
+ String rootPrefix(String path) => path.substring(0, style.rootLength(path));
+
+ /// Returns `true` if [path] is an absolute path and `false` if it is a
+ /// relative path.
+ ///
+ /// On POSIX systems, absolute paths start with a `/` (forward slash). On
+ /// Windows, an absolute path starts with `\\`, or a drive letter followed by
+ /// `:/` or `:\`. For URLs, absolute paths either start with a protocol and
+ /// optional hostname (e.g. `https://dart.dev`, `file://`) or with a `/`.
+ ///
+ /// URLs that start with `/` are known as "root-relative", since they're
+ /// relative to the root of the current URL. Since root-relative paths are
+ /// still absolute in every other sense, [isAbsolute] will return true for
+ /// them. They can be detected using [isRootRelative].
+ bool isAbsolute(String path) => style.rootLength(path) > 0;
+
+ /// Returns `true` if [path] is a relative path and `false` if it is absolute.
+ /// On POSIX systems, absolute paths start with a `/` (forward slash). On
+ /// Windows, an absolute path starts with `\\`, or a drive letter followed by
+ /// `:/` or `:\`.
+ bool isRelative(String path) => !isAbsolute(path);
+
+ /// Returns `true` if [path] is a root-relative path and `false` if it's not.
+ ///
+ /// URLs that start with `/` are known as "root-relative", since they're
+ /// relative to the root of the current URL. Since root-relative paths are
+ /// still absolute in every other sense, [isAbsolute] will return true for
+ /// them. They can be detected using [isRootRelative].
+ ///
+ /// No POSIX and Windows paths are root-relative.
+ bool isRootRelative(String path) => style.isRootRelative(path);
+
+ /// Joins the given path parts into a single path. Example:
+ ///
+ /// context.join('path', 'to', 'foo'); // -> 'path/to/foo'
+ ///
+ /// If any part ends in a path separator, then a redundant separator will not
+ /// be added:
+ ///
+ /// context.join('path/', 'to', 'foo'); // -> 'path/to/foo'
+ ///
+ /// If a part is an absolute path, then anything before that will be ignored:
+ ///
+ /// context.join('path', '/to', 'foo'); // -> '/to/foo'
+ ///
+ String join(String part1,
+ [String? part2,
+ String? part3,
+ String? part4,
+ String? part5,
+ String? part6,
+ String? part7,
+ String? part8,
+ String? part9,
+ String? part10,
+ String? part11,
+ String? part12,
+ String? part13,
+ String? part14,
+ String? part15,
+ String? part16]) {
+ final parts = <String?>[
+ part1,
+ part2,
+ part3,
+ part4,
+ part5,
+ part6,
+ part7,
+ part8,
+ part9,
+ part10,
+ part11,
+ part12,
+ part13,
+ part14,
+ part15,
+ part16,
+ ];
+ _validateArgList('join', parts);
+ return joinAll(parts.whereType<String>());
+ }
+
+ /// Joins the given path parts into a single path. Example:
+ ///
+ /// context.joinAll(['path', 'to', 'foo']); // -> 'path/to/foo'
+ ///
+ /// If any part ends in a path separator, then a redundant separator will not
+ /// be added:
+ ///
+ /// context.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo'
+ ///
+ /// If a part is an absolute path, then anything before that will be ignored:
+ ///
+ /// context.joinAll(['path', '/to', 'foo']); // -> '/to/foo'
+ ///
+ /// For a fixed number of parts, [join] is usually terser.
+ String joinAll(Iterable<String> parts) {
+ final buffer = StringBuffer();
+ var needsSeparator = false;
+ var isAbsoluteAndNotRootRelative = false;
+
+ for (var part in parts.where((part) => part != '')) {
+ if (isRootRelative(part) && isAbsoluteAndNotRootRelative) {
+ // If the new part is root-relative, it preserves the previous root but
+ // replaces the path after it.
+ final parsed = _parse(part);
+ final path = buffer.toString();
+ parsed.root =
+ path.substring(0, style.rootLength(path, withDrive: true));
+ if (style.needsSeparator(parsed.root!)) {
+ parsed.separators[0] = style.separator;
+ }
+ buffer.clear();
+ buffer.write(parsed.toString());
+ } else if (isAbsolute(part)) {
+ isAbsoluteAndNotRootRelative = !isRootRelative(part);
+ // An absolute path discards everything before it.
+ buffer.clear();
+ buffer.write(part);
+ } else {
+ if (part.isNotEmpty && style.containsSeparator(part[0])) {
+ // The part starts with a separator, so we don't need to add one.
+ } else if (needsSeparator) {
+ buffer.write(separator);
+ }
+
+ buffer.write(part);
+ }
+
+ // Unless this part ends with a separator, we'll need to add one before
+ // the next part.
+ needsSeparator = style.needsSeparator(part);
+ }
+
+ return buffer.toString();
+ }
+
+ /// Splits [path] into its components using the current platform's
+ /// [separator]. Example:
+ ///
+ /// context.split('path/to/foo'); // -> ['path', 'to', 'foo']
+ ///
+ /// The path will *not* be normalized before splitting.
+ ///
+ /// context.split('path/../foo'); // -> ['path', '..', 'foo']
+ ///
+ /// If [path] is absolute, the root directory will be the first element in the
+ /// array. Example:
+ ///
+ /// // Unix
+ /// context.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo']
+ ///
+ /// // Windows
+ /// context.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo']
+ /// context.split(r'\\server\share\path\to\foo');
+ /// // -> [r'\\server\share', 'path', 'to', 'foo']
+ ///
+ /// // Browser
+ /// context.split('https://dart.dev/path/to/foo');
+ /// // -> ['https://dart.dev', 'path', 'to', 'foo']
+ List<String> split(String path) {
+ final parsed = _parse(path);
+ // Filter out empty parts that exist due to multiple separators in a row.
+ parsed.parts = parsed.parts.where((part) => part.isNotEmpty).toList();
+ if (parsed.root != null) parsed.parts.insert(0, parsed.root!);
+ return parsed.parts;
+ }
+
+ /// Canonicalizes [path].
+ ///
+ /// This is guaranteed to return the same path for two different input paths
+ /// if and only if both input paths point to the same location. Unlike
+ /// [normalize], it returns absolute paths when possible and canonicalizes
+ /// ASCII case on Windows.
+ ///
+ /// Note that this does not resolve symlinks.
+ ///
+ /// If you want a map that uses path keys, it's probably more efficient to use
+ /// a Map with [equals] and [hash] specified as the callbacks to use for keys
+ /// than it is to canonicalize every key.
+ String canonicalize(String path) {
+ path = absolute(path);
+ if (style != Style.windows && !_needsNormalization(path)) return path;
+
+ final parsed = _parse(path);
+ parsed.normalize(canonicalize: true);
+ return parsed.toString();
+ }
+
+ /// Normalizes [path], simplifying it by handling `..`, and `.`, and
+ /// removing redundant path separators whenever possible.
+ ///
+ /// Note that this is *not* guaranteed to return the same result for two
+ /// equivalent input paths. For that, see [canonicalize]. Or, if you're using
+ /// paths as map keys use [equals] and [hash] as the key callbacks.
+ ///
+ /// context.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
+ String normalize(String path) {
+ if (!_needsNormalization(path)) return path;
+
+ final parsed = _parse(path);
+ parsed.normalize();
+ return parsed.toString();
+ }
+
+ /// Returns whether [path] needs to be normalized.
+ bool _needsNormalization(String path) {
+ var start = 0;
+ final codeUnits = path.codeUnits;
+ int? previousPrevious;
+ int? previous;
+
+ // Skip past the root before we start looking for snippets that need
+ // normalization. We want to normalize "//", but not when it's part of
+ // "http://".
+ final root = style.rootLength(path);
+ if (root != 0) {
+ start = root;
+ previous = chars.slash;
+
+ // On Windows, the root still needs to be normalized if it contains a
+ // forward slash.
+ if (style == Style.windows) {
+ for (var i = 0; i < root; i++) {
+ if (codeUnits[i] == chars.slash) return true;
+ }
+ }
+ }
+
+ for (var i = start; i < codeUnits.length; i++) {
+ final codeUnit = codeUnits[i];
+ if (style.isSeparator(codeUnit)) {
+ // Forward slashes in Windows paths are normalized to backslashes.
+ if (style == Style.windows && codeUnit == chars.slash) return true;
+
+ // Multiple separators are normalized to single separators.
+ if (previous != null && style.isSeparator(previous)) return true;
+
+ // Single dots and double dots are normalized to directory traversals.
+ //
+ // This can return false positives for ".../", but that's unlikely
+ // enough that it's probably not going to cause performance issues.
+ if (previous == chars.period &&
+ (previousPrevious == null ||
+ previousPrevious == chars.period ||
+ style.isSeparator(previousPrevious))) {
+ return true;
+ }
+ }
+
+ previousPrevious = previous;
+ previous = codeUnit;
+ }
+
+ // Empty paths are normalized to ".".
+ if (previous == null) return true;
+
+ // Trailing separators are removed.
+ if (style.isSeparator(previous)) return true;
+
+ // Single dots and double dots are normalized to directory traversals.
+ if (previous == chars.period &&
+ (previousPrevious == null ||
+ style.isSeparator(previousPrevious) ||
+ previousPrevious == chars.period)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /// Attempts to convert [path] to an equivalent relative path relative to
+ /// [current].
+ ///
+ /// var context = Context(current: '/root/path');
+ /// context.relative('/root/path/a/b.dart'); // -> 'a/b.dart'
+ /// context.relative('/root/other.dart'); // -> '../other.dart'
+ ///
+ /// If the [from] argument is passed, [path] is made relative to that instead.
+ ///
+ /// context.relative('/root/path/a/b.dart',
+ /// from: '/root/path'); // -> 'a/b.dart'
+ /// context.relative('/root/other.dart',
+ /// from: '/root/path'); // -> '../other.dart'
+ ///
+ /// If [path] and/or [from] are relative paths, they are assumed to be
+ /// relative to [current].
+ ///
+ /// Since there is no relative path from one drive letter to another on
+ /// Windows, this will return an absolute path in that case.
+ ///
+ /// context.relative(r'D:\other', from: r'C:\other'); // -> 'D:\other'
+ ///
+ /// This will also return an absolute path if an absolute [path] is passed to
+ /// a context with a relative path for [current].
+ ///
+ /// var context = Context(r'some/relative/path');
+ /// context.relative(r'/absolute/path'); // -> '/absolute/path'
+ ///
+ /// If [current] is relative, it may be impossible to determine a path from
+ /// [from] to [path]. For example, if [current] and [path] are "." and [from]
+ /// is "/", no path can be determined. In this case, a [PathException] will be
+ /// thrown.
+ String relative(String path, {String? from}) {
+ // Avoid expensive computation if the path is already relative.
+ if (from == null && isRelative(path)) return normalize(path);
+
+ from = from == null ? current : absolute(from);
+
+ // We can't determine the path from a relative path to an absolute path.
+ if (isRelative(from) && isAbsolute(path)) {
+ return normalize(path);
+ }
+
+ // If the given path is relative, resolve it relative to the context's
+ // current directory.
+ if (isRelative(path) || isRootRelative(path)) {
+ path = absolute(path);
+ }
+
+ // If the path is still relative and `from` is absolute, we're unable to
+ // find a path from `from` to `path`.
+ if (isRelative(path) && isAbsolute(from)) {
+ throw PathException('Unable to find a path to "$path" from "$from".');
+ }
+
+ final fromParsed = _parse(from)..normalize();
+ final pathParsed = _parse(path)..normalize();
+
+ if (fromParsed.parts.isNotEmpty && fromParsed.parts[0] == '.') {
+ return pathParsed.toString();
+ }
+
+ // If the root prefixes don't match (for example, different drive letters
+ // on Windows), then there is no relative path, so just return the absolute
+ // one. In Windows, drive letters are case-insenstive and we allow
+ // calculation of relative paths, even if a path has not been normalized.
+ if (fromParsed.root != pathParsed.root &&
+ ((fromParsed.root == null || pathParsed.root == null) ||
+ !style.pathsEqual(fromParsed.root!, pathParsed.root!))) {
+ return pathParsed.toString();
+ }
+
+ // Strip off their common prefix.
+ while (fromParsed.parts.isNotEmpty &&
+ pathParsed.parts.isNotEmpty &&
+ style.pathsEqual(fromParsed.parts[0], pathParsed.parts[0])) {
+ fromParsed.parts.removeAt(0);
+ fromParsed.separators.removeAt(1);
+ pathParsed.parts.removeAt(0);
+ pathParsed.separators.removeAt(1);
+ }
+
+ // If there are any directories left in the from path, we need to walk up
+ // out of them. If a directory left in the from path is '..', it cannot
+ // be cancelled by adding a '..'.
+ if (fromParsed.parts.isNotEmpty && fromParsed.parts[0] == '..') {
+ throw PathException('Unable to find a path to "$path" from "$from".');
+ }
+ pathParsed.parts.insertAll(0, List.filled(fromParsed.parts.length, '..'));
+ pathParsed.separators[0] = '';
+ pathParsed.separators
+ .insertAll(1, List.filled(fromParsed.parts.length, style.separator));
+
+ // Corner case: the paths completely collapsed.
+ if (pathParsed.parts.isEmpty) return '.';
+
+ // Corner case: path was '.' and some '..' directories were added in front.
+ // Don't add a final '/.' in that case.
+ if (pathParsed.parts.length > 1 && pathParsed.parts.last == '.') {
+ pathParsed.parts.removeLast();
+ pathParsed.separators
+ ..removeLast()
+ ..removeLast()
+ ..add('');
+ }
+
+ // Make it relative.
+ pathParsed.root = '';
+ pathParsed.removeTrailingSeparators();
+
+ return pathParsed.toString();
+ }
+
+ /// Returns `true` if [child] is a path beneath `parent`, and `false`
+ /// otherwise.
+ ///
+ /// path.isWithin('/root/path', '/root/path/a'); // -> true
+ /// path.isWithin('/root/path', '/root/other'); // -> false
+ /// path.isWithin('/root/path', '/root/path'); // -> false
+ bool isWithin(String parent, String child) =>
+ _isWithinOrEquals(parent, child) == _PathRelation.within;
+
+ /// Returns `true` if [path1] points to the same location as [path2], and
+ /// `false` otherwise.
+ ///
+ /// The [hash] function returns a hash code that matches these equality
+ /// semantics.
+ bool equals(String path1, String path2) =>
+ _isWithinOrEquals(path1, path2) == _PathRelation.equal;
+
+ /// Compares two paths and returns an enum value indicating their relationship
+ /// to one another.
+ ///
+ /// This never returns [_PathRelation.inconclusive].
+ _PathRelation _isWithinOrEquals(String parent, String child) {
+ // Make both paths the same level of relative. We're only able to do the
+ // quick comparison if both paths are in the same format, and making a path
+ // absolute is faster than making it relative.
+ final parentIsAbsolute = isAbsolute(parent);
+ final childIsAbsolute = isAbsolute(child);
+ if (parentIsAbsolute && !childIsAbsolute) {
+ child = absolute(child);
+ if (style.isRootRelative(parent)) parent = absolute(parent);
+ } else if (childIsAbsolute && !parentIsAbsolute) {
+ parent = absolute(parent);
+ if (style.isRootRelative(child)) child = absolute(child);
+ } else if (childIsAbsolute && parentIsAbsolute) {
+ final childIsRootRelative = style.isRootRelative(child);
+ final parentIsRootRelative = style.isRootRelative(parent);
+
+ if (childIsRootRelative && !parentIsRootRelative) {
+ child = absolute(child);
+ } else if (parentIsRootRelative && !childIsRootRelative) {
+ parent = absolute(parent);
+ }
+ }
+
+ final result = _isWithinOrEqualsFast(parent, child);
+ if (result != _PathRelation.inconclusive) return result;
+
+ String relative;
+ try {
+ relative = this.relative(child, from: parent);
+ } on PathException catch (_) {
+ // If no relative path from [parent] to [child] is found, [child]
+ // definitely isn't a child of [parent].
+ return _PathRelation.different;
+ }
+
+ if (!isRelative(relative)) return _PathRelation.different;
+ if (relative == '.') return _PathRelation.equal;
+ if (relative == '..') return _PathRelation.different;
+ return (relative.length >= 3 &&
+ relative.startsWith('..') &&
+ style.isSeparator(relative.codeUnitAt(2)))
+ ? _PathRelation.different
+ : _PathRelation.within;
+ }
+
+ /// An optimized implementation of [_isWithinOrEquals] that doesn't handle a
+ /// few complex cases.
+ _PathRelation _isWithinOrEqualsFast(String parent, String child) {
+ // Normally we just bail when we see "." path components, but we can handle
+ // a single dot easily enough.
+ if (parent == '.') parent = '';
+
+ final parentRootLength = style.rootLength(parent);
+ final childRootLength = style.rootLength(child);
+
+ // If the roots aren't the same length, we know both paths are absolute or
+ // both are root-relative, and thus that the roots are meaningfully
+ // different.
+ //
+ // isWithin("C:/bar", "//foo/bar/baz") //=> false
+ // isWithin("http://example.com/", "http://google.com/bar") //=> false
+ if (parentRootLength != childRootLength) return _PathRelation.different;
+
+ // Make sure that the roots are textually the same as well.
+ //
+ // isWithin("C:/bar", "D:/bar/baz") //=> false
+ // isWithin("http://example.com/", "http://example.org/bar") //=> false
+ for (var i = 0; i < parentRootLength; i++) {
+ final parentCodeUnit = parent.codeUnitAt(i);
+ final childCodeUnit = child.codeUnitAt(i);
+ if (!style.codeUnitsEqual(parentCodeUnit, childCodeUnit)) {
+ return _PathRelation.different;
+ }
+ }
+
+ // Start by considering the last code unit as a separator, since
+ // semantically we're starting at a new path component even if we're
+ // comparing relative paths.
+ var lastCodeUnit = chars.slash;
+
+ /// The index of the last separator in [parent].
+ int? lastParentSeparator;
+
+ // Iterate through both paths as long as they're semantically identical.
+ var parentIndex = parentRootLength;
+ var childIndex = childRootLength;
+ while (parentIndex < parent.length && childIndex < child.length) {
+ var parentCodeUnit = parent.codeUnitAt(parentIndex);
+ var childCodeUnit = child.codeUnitAt(childIndex);
+ if (style.codeUnitsEqual(parentCodeUnit, childCodeUnit)) {
+ if (style.isSeparator(parentCodeUnit)) {
+ lastParentSeparator = parentIndex;
+ }
+
+ lastCodeUnit = parentCodeUnit;
+ parentIndex++;
+ childIndex++;
+ continue;
+ }
+
+ // Ignore multiple separators in a row.
+ if (style.isSeparator(parentCodeUnit) &&
+ style.isSeparator(lastCodeUnit)) {
+ lastParentSeparator = parentIndex;
+ parentIndex++;
+ continue;
+ } else if (style.isSeparator(childCodeUnit) &&
+ style.isSeparator(lastCodeUnit)) {
+ childIndex++;
+ continue;
+ }
+
+ // If a dot comes after a separator, it may be a directory traversal
+ // operator. To check that, we need to know if it's followed by either
+ // "/" or "./". Otherwise, it's just a normal non-matching character.
+ //
+ // isWithin("foo/./bar", "foo/bar/baz") //=> true
+ // isWithin("foo/bar/../baz", "foo/bar/.foo") //=> false
+ if (parentCodeUnit == chars.period && style.isSeparator(lastCodeUnit)) {
+ parentIndex++;
+
+ // We've hit "/." at the end of the parent path, which we can ignore,
+ // since the paths were equivalent up to this point.
+ if (parentIndex == parent.length) break;
+ parentCodeUnit = parent.codeUnitAt(parentIndex);
+
+ // We've hit "/./", which we can ignore.
+ if (style.isSeparator(parentCodeUnit)) {
+ lastParentSeparator = parentIndex;
+ parentIndex++;
+ continue;
+ }
+
+ // We've hit "/..", which may be a directory traversal operator that
+ // we can't handle on the fast track.
+ if (parentCodeUnit == chars.period) {
+ parentIndex++;
+ if (parentIndex == parent.length ||
+ style.isSeparator(parent.codeUnitAt(parentIndex))) {
+ return _PathRelation.inconclusive;
+ }
+ }
+
+ // If this isn't a directory traversal, fall through so we hit the
+ // normal handling for mismatched paths.
+ }
+
+ // This is the same logic as above, but for the child path instead of the
+ // parent.
+ if (childCodeUnit == chars.period && style.isSeparator(lastCodeUnit)) {
+ childIndex++;
+ if (childIndex == child.length) break;
+ childCodeUnit = child.codeUnitAt(childIndex);
+
+ if (style.isSeparator(childCodeUnit)) {
+ childIndex++;
+ continue;
+ }
+
+ if (childCodeUnit == chars.period) {
+ childIndex++;
+ if (childIndex == child.length ||
+ style.isSeparator(child.codeUnitAt(childIndex))) {
+ return _PathRelation.inconclusive;
+ }
+ }
+ }
+
+ // If we're here, we've hit two non-matching, non-significant characters.
+ // As long as the remainders of the two paths don't have any unresolved
+ // ".." components, we can be confident that [child] is not within
+ // [parent].
+ final childDirection = _pathDirection(child, childIndex);
+ if (childDirection != _PathDirection.belowRoot) {
+ return _PathRelation.inconclusive;
+ }
+
+ final parentDirection = _pathDirection(parent, parentIndex);
+ if (parentDirection != _PathDirection.belowRoot) {
+ return _PathRelation.inconclusive;
+ }
+
+ return _PathRelation.different;
+ }
+
+ // If the child is shorter than the parent, it's probably not within the
+ // parent. The only exception is if the parent has some weird ".." stuff
+ // going on, in which case we do the slow check.
+ //
+ // isWithin("foo/bar/baz", "foo/bar") //=> false
+ // isWithin("foo/bar/baz/../..", "foo/bar") //=> true
+ if (childIndex == child.length) {
+ if (parentIndex == parent.length ||
+ style.isSeparator(parent.codeUnitAt(parentIndex))) {
+ lastParentSeparator = parentIndex;
+ } else {
+ lastParentSeparator ??= math.max(0, parentRootLength - 1);
+ }
+
+ final direction = _pathDirection(parent, lastParentSeparator);
+ if (direction == _PathDirection.atRoot) return _PathRelation.equal;
+ return direction == _PathDirection.aboveRoot
+ ? _PathRelation.inconclusive
+ : _PathRelation.different;
+ }
+
+ // We've reached the end of the parent path, which means it's time to make a
+ // decision. Before we do, though, we'll check the rest of the child to see
+ // what that tells us.
+ final direction = _pathDirection(child, childIndex);
+
+ // If there are no more components in the child, then it's the same as
+ // the parent.
+ //
+ // isWithin("foo/bar", "foo/bar") //=> false
+ // isWithin("foo/bar", "foo/bar//") //=> false
+ // equals("foo/bar", "foo/bar") //=> true
+ // equals("foo/bar", "foo/bar//") //=> true
+ if (direction == _PathDirection.atRoot) return _PathRelation.equal;
+
+ // If there are unresolved ".." components in the child, no decision we make
+ // will be valid. We'll abort and do the slow check instead.
+ //
+ // isWithin("foo/bar", "foo/bar/..") //=> false
+ // isWithin("foo/bar", "foo/bar/baz/bang/../../..") //=> false
+ // isWithin("foo/bar", "foo/bar/baz/bang/../../../bar/baz") //=> true
+ if (direction == _PathDirection.aboveRoot) {
+ return _PathRelation.inconclusive;
+ }
+
+ // The child is within the parent if and only if we're on a separator
+ // boundary.
+ //
+ // isWithin("foo/bar", "foo/bar/baz") //=> true
+ // isWithin("foo/bar/", "foo/bar/baz") //=> true
+ // isWithin("foo/bar", "foo/barbaz") //=> false
+ return (style.isSeparator(child.codeUnitAt(childIndex)) ||
+ style.isSeparator(lastCodeUnit))
+ ? _PathRelation.within
+ : _PathRelation.different;
+ }
+
+ // Returns a [_PathDirection] describing the path represented by [codeUnits]
+ // starting at [index].
+ //
+ // This ignores leading separators.
+ //
+ // pathDirection("foo") //=> below root
+ // pathDirection("foo/bar/../baz") //=> below root
+ // pathDirection("//foo/bar/baz") //=> below root
+ // pathDirection("/") //=> at root
+ // pathDirection("foo/..") //=> at root
+ // pathDirection("foo/../baz") //=> reaches root
+ // pathDirection("foo/../..") //=> above root
+ // pathDirection("foo/../../foo/bar/baz") //=> above root
+ _PathDirection _pathDirection(String path, int index) {
+ var depth = 0;
+ var reachedRoot = false;
+ var i = index;
+ while (i < path.length) {
+ // Ignore initial separators or doubled separators.
+ while (i < path.length && style.isSeparator(path.codeUnitAt(i))) {
+ i++;
+ }
+
+ // If we're at the end, stop.
+ if (i == path.length) break;
+
+ // Move through the path component to the next separator.
+ final start = i;
+ while (i < path.length && !style.isSeparator(path.codeUnitAt(i))) {
+ i++;
+ }
+
+ // See if the path component is ".", "..", or a name.
+ if (i - start == 1 && path.codeUnitAt(start) == chars.period) {
+ // Don't change the depth.
+ } else if (i - start == 2 &&
+ path.codeUnitAt(start) == chars.period &&
+ path.codeUnitAt(start + 1) == chars.period) {
+ // ".." backs out a directory.
+ depth--;
+
+ // If we work back beyond the root, stop.
+ if (depth < 0) break;
+
+ // Record that we reached the root so we don't return
+ // [_PathDirection.belowRoot].
+ if (depth == 0) reachedRoot = true;
+ } else {
+ // Step inside a directory.
+ depth++;
+ }
+
+ // If we're at the end, stop.
+ if (i == path.length) break;
+
+ // Move past the separator.
+ i++;
+ }
+
+ if (depth < 0) return _PathDirection.aboveRoot;
+ if (depth == 0) return _PathDirection.atRoot;
+ if (reachedRoot) return _PathDirection.reachesRoot;
+ return _PathDirection.belowRoot;
+ }
+
+ /// Returns a hash code for [path] that matches the semantics of [equals].
+ ///
+ /// Note that the same path may have different hash codes in different
+ /// [Context]s.
+ int hash(String path) {
+ // Make [path] absolute to ensure that equivalent relative and absolute
+ // paths have the same hash code.
+ path = absolute(path);
+
+ final result = _hashFast(path);
+ if (result != null) return result;
+
+ final parsed = _parse(path);
+ parsed.normalize();
+ return _hashFast(parsed.toString())!;
+ }
+
+ /// An optimized implementation of [hash] that doesn't handle internal `..`
+ /// components.
+ ///
+ /// This will handle `..` components that appear at the beginning of the path.
+ int? _hashFast(String path) {
+ var hash = 4603;
+ var beginning = true;
+ var wasSeparator = true;
+ for (var i = 0; i < path.length; i++) {
+ final codeUnit = style.canonicalizeCodeUnit(path.codeUnitAt(i));
+
+ // Take advantage of the fact that collisions are allowed to ignore
+ // separators entirely. This lets us avoid worrying about cases like
+ // multiple trailing slashes.
+ if (style.isSeparator(codeUnit)) {
+ wasSeparator = true;
+ continue;
+ }
+
+ if (codeUnit == chars.period && wasSeparator) {
+ // If a dot comes after a separator, it may be a directory traversal
+ // operator. To check that, we need to know if it's followed by either
+ // "/" or "./". Otherwise, it's just a normal character.
+ //
+ // hash("foo/./bar") == hash("foo/bar")
+
+ // We've hit "/." at the end of the path, which we can ignore.
+ if (i + 1 == path.length) break;
+
+ final next = path.codeUnitAt(i + 1);
+
+ // We can just ignore "/./", since they don't affect the semantics of
+ // the path.
+ if (style.isSeparator(next)) continue;
+
+ // If the path ends with "/.." or contains "/../", we need to
+ // canonicalize it before we can hash it. We make an exception for ".."s
+ // at the beginning of the path, since those may appear even in a
+ // canonicalized path.
+ if (!beginning &&
+ next == chars.period &&
+ (i + 2 == path.length ||
+ style.isSeparator(path.codeUnitAt(i + 2)))) {
+ return null;
+ }
+ }
+
+ // Make sure [hash] stays under 32 bits even after multiplication.
+ hash &= 0x3FFFFFF;
+ hash *= 33;
+ hash ^= codeUnit;
+ wasSeparator = false;
+ beginning = false;
+ }
+ return hash;
+ }
+
+ /// Removes a trailing extension from the last part of [path].
+ ///
+ /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
+ String withoutExtension(String path) {
+ final parsed = _parse(path);
+
+ for (var i = parsed.parts.length - 1; i >= 0; i--) {
+ if (parsed.parts[i].isNotEmpty) {
+ parsed.parts[i] = parsed.basenameWithoutExtension;
+ break;
+ }
+ }
+
+ return parsed.toString();
+ }
+
+ /// Returns [path] with the trailing extension set to [extension].
+ ///
+ /// If [path] doesn't have a trailing extension, this just adds [extension] to
+ /// the end.
+ ///
+ /// context.setExtension('path/to/foo.dart', '.js')
+ /// // -> 'path/to/foo.js'
+ /// context.setExtension('path/to/foo.dart.js', '.map')
+ /// // -> 'path/to/foo.dart.map'
+ /// context.setExtension('path/to/foo', '.js')
+ /// // -> 'path/to/foo.js'
+ String setExtension(String path, String extension) =>
+ withoutExtension(path) + extension;
+
+ /// Returns the path represented by [uri], which may be a [String] or a [Uri].
+ ///
+ /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL
+ /// style, this will just convert [uri] to a string.
+ ///
+ /// // POSIX
+ /// context.fromUri('file:///path/to/foo')
+ /// // -> '/path/to/foo'
+ ///
+ /// // Windows
+ /// context.fromUri('file:///C:/path/to/foo')
+ /// // -> r'C:\path\to\foo'
+ ///
+ /// // URL
+ /// context.fromUri('https://dart.dev/path/to/foo')
+ /// // -> 'https://dart.dev/path/to/foo'
+ ///
+ /// If [uri] is relative, a relative path will be returned.
+ ///
+ /// path.fromUri('path/to/foo'); // -> 'path/to/foo'
+ String fromUri(Object? uri) => style.pathFromUri(_parseUri(uri!));
+
+ /// Returns the URI that represents [path].
+ ///
+ /// For POSIX and Windows styles, this will return a `file:` URI. For the URL
+ /// style, this will just convert [path] to a [Uri].
+ ///
+ /// // POSIX
+ /// context.toUri('/path/to/foo')
+ /// // -> Uri.parse('file:///path/to/foo')
+ ///
+ /// // Windows
+ /// context.toUri(r'C:\path\to\foo')
+ /// // -> Uri.parse('file:///C:/path/to/foo')
+ ///
+ /// // URL
+ /// context.toUri('https://dart.dev/path/to/foo')
+ /// // -> Uri.parse('https://dart.dev/path/to/foo')
+ Uri toUri(String path) {
+ if (isRelative(path)) {
+ return style.relativePathToUri(path);
+ } else {
+ return style.absolutePathToUri(join(current, path));
+ }
+ }
+
+ /// Returns a terse, human-readable representation of [uri].
+ ///
+ /// [uri] can be a [String] or a [Uri]. If it can be made relative to the
+ /// current working directory, that's done. Otherwise, it's returned as-is.
+ /// This gracefully handles non-`file:` URIs for [Style.posix] and
+ /// [Style.windows].
+ ///
+ /// The returned value is meant for human consumption, and may be either URI-
+ /// or path-formatted.
+ ///
+ /// // POSIX
+ /// var context = Context(current: '/root/path');
+ /// context.prettyUri('file:///root/path/a/b.dart'); // -> 'a/b.dart'
+ /// context.prettyUri('https://dart.dev/'); // -> 'https://dart.dev'
+ ///
+ /// // Windows
+ /// var context = Context(current: r'C:\root\path');
+ /// context.prettyUri('file:///C:/root/path/a/b.dart'); // -> r'a\b.dart'
+ /// context.prettyUri('https://dart.dev/'); // -> 'https://dart.dev'
+ ///
+ /// // URL
+ /// var context = Context(current: 'https://dart.dev/root/path');
+ /// context.prettyUri('https://dart.dev/root/path/a/b.dart');
+ /// // -> r'a/b.dart'
+ /// context.prettyUri('file:///root/path'); // -> 'file:///root/path'
+ String prettyUri(Object? uri) {
+ final typedUri = _parseUri(uri!);
+ if (typedUri.scheme == 'file' && style == Style.url) {
+ return typedUri.toString();
+ } else if (typedUri.scheme != 'file' &&
+ typedUri.scheme != '' &&
+ style != Style.url) {
+ return typedUri.toString();
+ }
+
+ final path = normalize(fromUri(typedUri));
+ final rel = relative(path);
+
+ // Only return a relative path if it's actually shorter than the absolute
+ // path. This avoids ugly things like long "../" chains to get to the root
+ // and then go back down.
+ return split(rel).length > split(path).length ? path : rel;
+ }
+
+ ParsedPath _parse(String path) => ParsedPath.parse(path, style);
+}
+
+/// Parses argument if it's a [String] or returns it intact if it's a [Uri].
+///
+/// Throws an [ArgumentError] otherwise.
+Uri _parseUri(Object uri) {
+ if (uri is String) return Uri.parse(uri);
+ if (uri is Uri) return uri;
+ throw ArgumentError.value(uri, 'uri', 'Value must be a String or a Uri');
+}
+
+/// Validates that there are no non-null arguments following a null one and
+/// throws an appropriate [ArgumentError] on failure.
+void _validateArgList(String method, List<String?> args) {
+ for (var i = 1; i < args.length; i++) {
+ // Ignore nulls hanging off the end.
+ if (args[i] == null || args[i - 1] != null) continue;
+
+ int numArgs;
+ for (numArgs = args.length; numArgs >= 1; numArgs--) {
+ if (args[numArgs - 1] != null) break;
+ }
+
+ // Show the arguments.
+ final message = StringBuffer();
+ message.write('$method(');
+ message.write(args
+ .take(numArgs)
+ .map((arg) => arg == null ? 'null' : '"$arg"')
+ .join(', '));
+ message.write('): part ${i - 1} was null, but part $i was not.');
+ throw ArgumentError(message.toString());
+ }
+}
+
+/// An enum of possible return values for [Context._pathDirection].
+class _PathDirection {
+ /// The path contains enough ".." components that at some point it reaches
+ /// above its original root.
+ ///
+ /// Note that this applies even if the path ends beneath its original root. It
+ /// takes precendence over any other return values that may apple.
+ static const aboveRoot = _PathDirection('above root');
+
+ /// The path contains enough ".." components that it ends at its original
+ /// root.
+ static const atRoot = _PathDirection('at root');
+
+ /// The path contains enough ".." components that at some point it reaches its
+ /// original root, but it ends beneath that root.
+ static const reachesRoot = _PathDirection('reaches root');
+
+ /// The path never reaches to or above its original root.
+ static const belowRoot = _PathDirection('below root');
+
+ final String name;
+
+ const _PathDirection(this.name);
+
+ @override
+ String toString() => name;
+}
+
+/// An enum of possible return values for [Context._isWithinOrEquals].
+class _PathRelation {
+ /// The first path is a proper parent of the second.
+ ///
+ /// For example, `foo` is a proper parent of `foo/bar`, but not of `foo`.
+ static const within = _PathRelation('within');
+
+ /// The two paths are equivalent.
+ ///
+ /// For example, `foo//bar` is equivalent to `foo/bar`.
+ static const equal = _PathRelation('equal');
+
+ /// The first path is neither a parent of nor equal to the second.
+ static const different = _PathRelation('different');
+
+ /// We couldn't quickly determine any information about the paths'
+ /// relationship to each other.
+ ///
+ /// Only returned by [Context._isWithinOrEqualsFast].
+ static const inconclusive = _PathRelation('inconclusive');
+
+ final String name;
+
+ const _PathRelation(this.name);
+
+ @override
+ String toString() => name;
+}
diff --git a/pkgs/path/lib/src/internal_style.dart b/pkgs/path/lib/src/internal_style.dart
new file mode 100644
index 0000000..71762c1
--- /dev/null
+++ b/pkgs/path/lib/src/internal_style.dart
@@ -0,0 +1,90 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'context.dart';
+import 'style.dart';
+
+/// The internal interface for the [Style] type.
+///
+/// Users should be able to pass around instances of [Style] like an enum, but
+/// the members that [Context] uses should be hidden from them. Those members
+/// are defined on this class instead.
+abstract class InternalStyle extends Style {
+ /// The default path separator for this style.
+ ///
+ /// On POSIX, this is `/`. On Windows, it's `\`.
+ @override
+ String get separator;
+
+ /// Returns whether [path] contains a separator.
+ bool containsSeparator(String path);
+
+ /// Returns whether [codeUnit] is the character code of a separator.
+ bool isSeparator(int codeUnit);
+
+ /// Returns whether this path component needs a separator after it.
+ ///
+ /// Windows and POSIX styles just need separators when the previous component
+ /// doesn't already end in a separator, but the URL always needs to place a
+ /// separator between the root and the first component, even if the root
+ /// already ends in a separator character. For example, to join "file://" and
+ /// "usr", an additional "/" is needed (making "file:///usr").
+ bool needsSeparator(String path);
+
+ /// Returns the number of characters of the root part.
+ ///
+ /// Returns 0 if the path is relative and 1 if the path is root-relative.
+ ///
+ /// If [withDrive] is `true`, this should include the drive letter for `file:`
+ /// URLs. Non-URL styles may ignore the parameter.
+ int rootLength(String path, {bool withDrive = false});
+
+ /// Gets the root prefix of [path] if path is absolute. If [path] is relative,
+ /// returns `null`.
+ @override
+ String? getRoot(String path) {
+ final length = rootLength(path);
+ if (length > 0) return path.substring(0, length);
+ return isRootRelative(path) ? path[0] : null;
+ }
+
+ /// Returns whether [path] is root-relative.
+ ///
+ /// If [path] is relative or absolute and not root-relative, returns `false`.
+ bool isRootRelative(String path);
+
+ /// Returns the path represented by [uri] in this style.
+ @override
+ String pathFromUri(Uri uri);
+
+ /// Returns the URI that represents a relative path.
+ @override
+ Uri relativePathToUri(String path) {
+ if (path.isEmpty) return Uri();
+ final segments = context.split(path);
+
+ // Ensure that a trailing slash in the path produces a trailing slash in the
+ // URL.
+ if (isSeparator(path.codeUnitAt(path.length - 1))) segments.add('');
+ return Uri(pathSegments: segments);
+ }
+
+ /// Returns the URI that represents [path], which is assumed to be absolute.
+ @override
+ Uri absolutePathToUri(String path);
+
+ /// Returns whether [codeUnit1] and [codeUnit2] are considered equivalent for
+ /// this style.
+ bool codeUnitsEqual(int codeUnit1, int codeUnit2) => codeUnit1 == codeUnit2;
+
+ /// Returns whether [path1] and [path2] are equivalent.
+ ///
+ /// This only needs to handle character-by-character comparison; it can assume
+ /// the paths are normalized and contain no `..` components.
+ bool pathsEqual(String path1, String path2) => path1 == path2;
+
+ int canonicalizeCodeUnit(int codeUnit) => codeUnit;
+
+ String canonicalizePart(String part) => part;
+}
diff --git a/pkgs/path/lib/src/parsed_path.dart b/pkgs/path/lib/src/parsed_path.dart
new file mode 100644
index 0000000..60fa849
--- /dev/null
+++ b/pkgs/path/lib/src/parsed_path.dart
@@ -0,0 +1,210 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'internal_style.dart';
+import 'style.dart';
+
+class ParsedPath {
+ /// The [InternalStyle] that was used to parse this path.
+ InternalStyle style;
+
+ /// The absolute root portion of the path, or `null` if the path is relative.
+ /// On POSIX systems, this will be `null` or "/". On Windows, it can be
+ /// `null`, "//" for a UNC path, or something like "C:\" for paths with drive
+ /// letters.
+ String? root;
+
+ /// Whether this path is root-relative.
+ ///
+ /// See `Context.isRootRelative`.
+ bool isRootRelative;
+
+ /// The path-separated parts of the path. All but the last will be
+ /// directories.
+ List<String> parts;
+
+ /// The path separators preceding each part.
+ ///
+ /// The first one will be an empty string unless the root requires a separator
+ /// between it and the path. The last one will be an empty string unless the
+ /// path ends with a trailing separator.
+ List<String> separators;
+
+ /// The file extension of the last non-empty part, or "" if it doesn't have
+ /// one.
+ String extension([int level = 1]) => _splitExtension(level)[1];
+
+ /// `true` if this is an absolute path.
+ bool get isAbsolute => root != null;
+
+ factory ParsedPath.parse(String path, InternalStyle style) {
+ // Remove the root prefix, if any.
+ final root = style.getRoot(path);
+ final isRootRelative = style.isRootRelative(path);
+ if (root != null) path = path.substring(root.length);
+
+ // Split the parts on path separators.
+ final parts = <String>[];
+ final separators = <String>[];
+
+ var start = 0;
+
+ if (path.isNotEmpty && style.isSeparator(path.codeUnitAt(0))) {
+ separators.add(path[0]);
+ start = 1;
+ } else {
+ separators.add('');
+ }
+
+ for (var i = start; i < path.length; i++) {
+ if (style.isSeparator(path.codeUnitAt(i))) {
+ parts.add(path.substring(start, i));
+ separators.add(path[i]);
+ start = i + 1;
+ }
+ }
+
+ // Add the final part, if any.
+ if (start < path.length) {
+ parts.add(path.substring(start));
+ separators.add('');
+ }
+
+ return ParsedPath._(style, root, isRootRelative, parts, separators);
+ }
+
+ ParsedPath._(
+ this.style, this.root, this.isRootRelative, this.parts, this.separators);
+
+ String get basename {
+ final copy = clone();
+ copy.removeTrailingSeparators();
+ if (copy.parts.isEmpty) return root ?? '';
+ return copy.parts.last;
+ }
+
+ String get basenameWithoutExtension => _splitExtension()[0];
+
+ bool get hasTrailingSeparator =>
+ parts.isNotEmpty && (parts.last == '' || separators.last != '');
+
+ void removeTrailingSeparators() {
+ while (parts.isNotEmpty && parts.last == '') {
+ parts.removeLast();
+ separators.removeLast();
+ }
+ if (separators.isNotEmpty) separators[separators.length - 1] = '';
+ }
+
+ void normalize({bool canonicalize = false}) {
+ // Handle '.', '..', and empty parts.
+ var leadingDoubles = 0;
+ final newParts = <String>[];
+ for (var part in parts) {
+ if (part == '.' || part == '') {
+ // Do nothing. Ignore it.
+ } else if (part == '..') {
+ // Pop the last part off.
+ if (newParts.isNotEmpty) {
+ newParts.removeLast();
+ } else {
+ // Backed out past the beginning, so preserve the "..".
+ leadingDoubles++;
+ }
+ } else {
+ newParts.add(canonicalize ? style.canonicalizePart(part) : part);
+ }
+ }
+
+ // A relative path can back out from the start directory.
+ if (!isAbsolute) {
+ newParts.insertAll(0, List.filled(leadingDoubles, '..'));
+ }
+
+ // If we collapsed down to nothing, do ".".
+ if (newParts.isEmpty && !isAbsolute) {
+ newParts.add('.');
+ }
+
+ // Canonicalize separators.
+ parts = newParts;
+ separators =
+ List.filled(newParts.length + 1, style.separator, growable: true);
+ if (!isAbsolute || newParts.isEmpty || !style.needsSeparator(root!)) {
+ separators[0] = '';
+ }
+
+ // Normalize the Windows root if needed.
+ if (root != null && style == Style.windows) {
+ if (canonicalize) root = root!.toLowerCase();
+ root = root!.replaceAll('/', '\\');
+ }
+ removeTrailingSeparators();
+ }
+
+ @override
+ String toString() {
+ final builder = StringBuffer();
+ if (root != null) builder.write(root);
+ for (var i = 0; i < parts.length; i++) {
+ builder.write(separators[i]);
+ builder.write(parts[i]);
+ }
+ builder.write(separators.last);
+
+ return builder.toString();
+ }
+
+ /// Returns k-th last index of the `character` in the `path`.
+ ///
+ /// If `k` exceeds the count of `character`s in `path`, the left most index
+ /// of the `character` is returned.
+ int _kthLastIndexOf(String path, String character, int k) {
+ var count = 0, leftMostIndexedCharacter = 0;
+ for (var index = path.length - 1; index >= 0; --index) {
+ if (path[index] == character) {
+ leftMostIndexedCharacter = index;
+ ++count;
+ if (count == k) {
+ return index;
+ }
+ }
+ }
+ return leftMostIndexedCharacter;
+ }
+
+ /// Splits the last non-empty part of the path into a `[basename, extension]`
+ /// pair.
+ ///
+ /// Takes an optional parameter `level` which makes possible to return
+ /// multiple extensions having `level` number of dots. If `level` exceeds the
+ /// number of dots, the path is split at the first most dot. The value of
+ /// `level` must be greater than 0, else `RangeError` is thrown.
+ ///
+ /// Returns a two-element list. The first is the name of the file without any
+ /// extension. The second is the extension or "" if it has none.
+ List<String> _splitExtension([int level = 1]) {
+ if (level <= 0) {
+ throw RangeError.value(
+ level, 'level', "level's value must be greater than 0");
+ }
+
+ final file =
+ parts.cast<String?>().lastWhere((p) => p != '', orElse: () => null);
+
+ if (file == null) return ['', ''];
+ if (file == '..') return ['..', ''];
+
+ final lastDot = _kthLastIndexOf(file, '.', level);
+
+ // If there is no dot, or it's the first character, like '.bashrc', it
+ // doesn't count.
+ if (lastDot <= 0) return [file, ''];
+
+ return [file.substring(0, lastDot), file.substring(lastDot)];
+ }
+
+ ParsedPath clone() => ParsedPath._(
+ style, root, isRootRelative, List.from(parts), List.from(separators));
+}
diff --git a/pkgs/path/lib/src/path_exception.dart b/pkgs/path/lib/src/path_exception.dart
new file mode 100644
index 0000000..12a8432
--- /dev/null
+++ b/pkgs/path/lib/src/path_exception.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// An exception class that's thrown when a path operation is unable to be
+/// computed accurately.
+class PathException implements Exception {
+ String message;
+
+ PathException(this.message);
+
+ @override
+ String toString() => 'PathException: $message';
+}
diff --git a/pkgs/path/lib/src/path_map.dart b/pkgs/path/lib/src/path_map.dart
new file mode 100644
index 0000000..50f4d7e
--- /dev/null
+++ b/pkgs/path/lib/src/path_map.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import '../path.dart' as p;
+
+/// A map whose keys are paths, compared using [p.equals] and [p.hash].
+class PathMap<V> extends MapView<String?, V> {
+ /// Creates an empty [PathMap] whose keys are compared using `context.equals`
+ /// and `context.hash`.
+ ///
+ /// The [context] defaults to the current path context.
+ PathMap({p.Context? context}) : super(_create(context));
+
+ /// Creates a [PathMap] with the same keys and values as [other] whose keys
+ /// are compared using `context.equals` and `context.hash`.
+ ///
+ /// The [context] defaults to the current path context. If multiple keys in
+ /// [other] represent the same logical path, the last key's value will be
+ /// used.
+ PathMap.of(Map<String, V> other, {p.Context? context})
+ : super(_create(context)..addAll(other));
+
+ /// Creates a map that uses [context] for equality and hashing.
+ static Map<String?, V> _create<V>(p.Context? context) {
+ context ??= p.context;
+ return LinkedHashMap(
+ equals: (path1, path2) {
+ if (path1 == null) return path2 == null;
+ if (path2 == null) return false;
+ return context!.equals(path1, path2);
+ },
+ hashCode: (path) => path == null ? 0 : context!.hash(path),
+ isValidKey: (path) => path is String || path == null);
+ }
+}
diff --git a/pkgs/path/lib/src/path_set.dart b/pkgs/path/lib/src/path_set.dart
new file mode 100644
index 0000000..424c8a1
--- /dev/null
+++ b/pkgs/path/lib/src/path_set.dart
@@ -0,0 +1,99 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import '../path.dart' as p;
+
+/// A set containing paths, compared using [p.equals] and [p.hash].
+class PathSet extends IterableBase<String?> implements Set<String?> {
+ /// The set to which we forward implementation methods.
+ final Set<String?> _inner;
+
+ /// Creates an empty [PathSet] whose contents are compared using
+ /// `context.equals` and `context.hash`.
+ ///
+ /// The [context] defaults to the current path context.
+ PathSet({p.Context? context}) : _inner = _create(context);
+
+ /// Creates a [PathSet] with the same contents as [other] whose elements are
+ /// compared using `context.equals` and `context.hash`.
+ ///
+ /// The [context] defaults to the current path context. If multiple elements
+ /// in [other] represent the same logical path, the first value will be
+ /// used.
+ PathSet.of(Iterable<String> other, {p.Context? context})
+ : _inner = _create(context)..addAll(other);
+
+ /// Creates a set that uses [context] for equality and hashing.
+ static Set<String?> _create(p.Context? context) {
+ context ??= p.context;
+ return LinkedHashSet(
+ equals: (path1, path2) {
+ if (path1 == null) return path2 == null;
+ if (path2 == null) return false;
+ return context!.equals(path1, path2);
+ },
+ hashCode: (path) => path == null ? 0 : context!.hash(path),
+ isValidKey: (path) => path is String || path == null);
+ }
+
+ // Normally we'd use DelegatingSetView from the collection package to
+ // implement these, but we want to avoid adding dependencies from path because
+ // it's so widely used that even brief version skew can be very painful.
+
+ @override
+ Iterator<String?> get iterator => _inner.iterator;
+
+ @override
+ int get length => _inner.length;
+
+ @override
+ bool add(String? value) => _inner.add(value);
+
+ @override
+ void addAll(Iterable<String?> elements) => _inner.addAll(elements);
+
+ @override
+ Set<T> cast<T>() => _inner.cast<T>();
+
+ @override
+ void clear() => _inner.clear();
+
+ @override
+ bool contains(Object? element) => _inner.contains(element);
+
+ @override
+ bool containsAll(Iterable<Object?> other) => _inner.containsAll(other);
+
+ @override
+ Set<String?> difference(Set<Object?> other) => _inner.difference(other);
+
+ @override
+ Set<String?> intersection(Set<Object?> other) => _inner.intersection(other);
+
+ @override
+ String? lookup(Object? element) => _inner.lookup(element);
+
+ @override
+ bool remove(Object? value) => _inner.remove(value);
+
+ @override
+ void removeAll(Iterable<Object?> elements) => _inner.removeAll(elements);
+
+ @override
+ void removeWhere(bool Function(String?) test) => _inner.removeWhere(test);
+
+ @override
+ void retainAll(Iterable<Object?> elements) => _inner.retainAll(elements);
+
+ @override
+ void retainWhere(bool Function(String?) test) => _inner.retainWhere(test);
+
+ @override
+ Set<String?> union(Set<String?> other) => _inner.union(other);
+
+ @override
+ Set<String?> toSet() => _inner.toSet();
+}
diff --git a/pkgs/path/lib/src/style.dart b/pkgs/path/lib/src/style.dart
new file mode 100644
index 0000000..e1b4fec
--- /dev/null
+++ b/pkgs/path/lib/src/style.dart
@@ -0,0 +1,85 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'context.dart';
+import 'style/posix.dart';
+import 'style/url.dart';
+import 'style/windows.dart';
+
+/// An enum type describing a "flavor" of path.
+abstract class Style {
+ /// POSIX-style paths use "/" (forward slash) as separators. Absolute paths
+ /// start with "/". Used by UNIX, Linux, Mac OS X, and others.
+ static final Style posix = PosixStyle();
+
+ /// Windows paths use `\` (backslash) as separators. Absolute paths start with
+ /// a drive letter followed by a colon (example, `C:`) or two backslashes
+ /// (`\\`) for UNC paths.
+ static final Style windows = WindowsStyle();
+
+ /// URLs aren't filesystem paths, but they're supported to make it easier to
+ /// manipulate URL paths in the browser.
+ ///
+ /// URLs use "/" (forward slash) as separators. Absolute paths either start
+ /// with a protocol and optional hostname (e.g. `https://dart.dev`,
+ /// `file://`) or with "/".
+ static final Style url = UrlStyle();
+
+ /// The style of the host platform.
+ ///
+ /// When running on the command line, this will be [windows] or [posix] based
+ /// on the host operating system. On a browser, this will be [url].
+ static final Style platform = _getPlatformStyle();
+
+ /// Gets the type of the host platform.
+ static Style _getPlatformStyle() {
+ // If we're running a Dart file in the browser from a `file:` URI,
+ // [Uri.base] will point to a file. If we're running on the standalone,
+ // it will point to a directory. We can use that fact to determine which
+ // style to use.
+ if (Uri.base.scheme != 'file') return Style.url;
+ if (!Uri.base.path.endsWith('/')) return Style.url;
+ if (Uri(path: 'a/b').toFilePath() == 'a\\b') return Style.windows;
+ return Style.posix;
+ }
+
+ /// The name of this path style. Will be "posix" or "windows".
+ String get name;
+
+ /// A [Context] that uses this style.
+ Context get context => Context(style: this);
+
+ @Deprecated('Most Style members will be removed in path 2.0.')
+ String get separator;
+
+ @Deprecated('Most Style members will be removed in path 2.0.')
+ Pattern get separatorPattern;
+
+ @Deprecated('Most Style members will be removed in path 2.0.')
+ Pattern get needsSeparatorPattern;
+
+ @Deprecated('Most Style members will be removed in path 2.0.')
+ Pattern get rootPattern;
+
+ @Deprecated('Most Style members will be removed in path 2.0.')
+ Pattern? get relativeRootPattern;
+
+ @Deprecated('Most style members will be removed in path 2.0.')
+ String? getRoot(String path);
+
+ @Deprecated('Most style members will be removed in path 2.0.')
+ String? getRelativeRoot(String path);
+
+ @Deprecated('Most style members will be removed in path 2.0.')
+ String pathFromUri(Uri uri);
+
+ @Deprecated('Most style members will be removed in path 2.0.')
+ Uri relativePathToUri(String path);
+
+ @Deprecated('Most style members will be removed in path 2.0.')
+ Uri absolutePathToUri(String path);
+
+ @override
+ String toString() => name;
+}
diff --git a/pkgs/path/lib/src/style/posix.dart b/pkgs/path/lib/src/style/posix.dart
new file mode 100644
index 0000000..f88e335
--- /dev/null
+++ b/pkgs/path/lib/src/style/posix.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../characters.dart' as chars;
+import '../internal_style.dart';
+import '../parsed_path.dart';
+
+/// The style for POSIX paths.
+class PosixStyle extends InternalStyle {
+ @override
+ final name = 'posix';
+ @override
+ final separator = '/';
+ final separators = const ['/'];
+
+ // Deprecated properties.
+
+ @override
+ final separatorPattern = RegExp(r'/');
+ @override
+ final needsSeparatorPattern = RegExp(r'[^/]$');
+ @override
+ final rootPattern = RegExp(r'^/');
+ @override
+ Pattern? get relativeRootPattern => null;
+
+ @override
+ bool containsSeparator(String path) => path.contains('/');
+
+ @override
+ bool isSeparator(int codeUnit) => codeUnit == chars.slash;
+
+ @override
+ bool needsSeparator(String path) =>
+ path.isNotEmpty && !isSeparator(path.codeUnitAt(path.length - 1));
+
+ @override
+ int rootLength(String path, {bool withDrive = false}) {
+ if (path.isNotEmpty && isSeparator(path.codeUnitAt(0))) return 1;
+ return 0;
+ }
+
+ @override
+ bool isRootRelative(String path) => false;
+
+ @override
+ String? getRelativeRoot(String path) => null;
+
+ @override
+ String pathFromUri(Uri uri) {
+ if (uri.scheme == '' || uri.scheme == 'file') {
+ return Uri.decodeComponent(uri.path);
+ }
+ throw ArgumentError("Uri $uri must have scheme 'file:'.");
+ }
+
+ @override
+ Uri absolutePathToUri(String path) {
+ final parsed = ParsedPath.parse(path, this);
+ if (parsed.parts.isEmpty) {
+ // If the path is a bare root (e.g. "/"), [components] will
+ // currently be empty. We add two empty components so the URL constructor
+ // produces "file:///", with a trailing slash.
+ parsed.parts.addAll(['', '']);
+ } else if (parsed.hasTrailingSeparator) {
+ // If the path has a trailing slash, add a single empty component so the
+ // URI has a trailing slash as well.
+ parsed.parts.add('');
+ }
+
+ return Uri(scheme: 'file', pathSegments: parsed.parts);
+ }
+}
diff --git a/pkgs/path/lib/src/style/url.dart b/pkgs/path/lib/src/style/url.dart
new file mode 100644
index 0000000..a2d3b0c
--- /dev/null
+++ b/pkgs/path/lib/src/style/url.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../characters.dart' as chars;
+import '../internal_style.dart';
+import '../utils.dart';
+
+/// The style for URL paths.
+class UrlStyle extends InternalStyle {
+ @override
+ final name = 'url';
+ @override
+ final separator = '/';
+ final separators = const ['/'];
+
+ // Deprecated properties.
+
+ @override
+ final separatorPattern = RegExp(r'/');
+ @override
+ final needsSeparatorPattern = RegExp(r'(^[a-zA-Z][-+.a-zA-Z\d]*://|[^/])$');
+ @override
+ final rootPattern = RegExp(r'[a-zA-Z][-+.a-zA-Z\d]*://[^/]*');
+ @override
+ final relativeRootPattern = RegExp(r'^/');
+
+ @override
+ bool containsSeparator(String path) => path.contains('/');
+
+ @override
+ bool isSeparator(int codeUnit) => codeUnit == chars.slash;
+
+ @override
+ bool needsSeparator(String path) {
+ if (path.isEmpty) return false;
+
+ // A URL that doesn't end in "/" always needs a separator.
+ if (!isSeparator(path.codeUnitAt(path.length - 1))) return true;
+
+ // A URI that's just "scheme://" needs an extra separator, despite ending
+ // with "/".
+ return path.endsWith('://') && rootLength(path) == path.length;
+ }
+
+ @override
+ int rootLength(String path, {bool withDrive = false}) {
+ if (path.isEmpty) return 0;
+ if (isSeparator(path.codeUnitAt(0))) return 1;
+
+ for (var i = 0; i < path.length; i++) {
+ final codeUnit = path.codeUnitAt(i);
+ if (isSeparator(codeUnit)) return 0;
+ if (codeUnit == chars.colon) {
+ if (i == 0) return 0;
+
+ // The root part is up until the next '/', or the full path. Skip ':'
+ // (and '//' if it exists) and search for '/' after that.
+ if (path.startsWith('//', i + 1)) i += 3;
+ final index = path.indexOf('/', i);
+ if (index <= 0) return path.length;
+
+ // file: URLs sometimes consider Windows drive letters part of the root.
+ // See https://url.spec.whatwg.org/#file-slash-state.
+ if (!withDrive || path.length < index + 3) return index;
+ if (!path.startsWith('file://')) return index;
+ return driveLetterEnd(path, index + 1) ?? index;
+ }
+ }
+
+ return 0;
+ }
+
+ @override
+ bool isRootRelative(String path) =>
+ path.isNotEmpty && isSeparator(path.codeUnitAt(0));
+
+ @override
+ String? getRelativeRoot(String path) => isRootRelative(path) ? '/' : null;
+
+ @override
+ String pathFromUri(Uri uri) => uri.toString();
+
+ @override
+ Uri relativePathToUri(String path) => Uri.parse(path);
+ @override
+ Uri absolutePathToUri(String path) => Uri.parse(path);
+}
diff --git a/pkgs/path/lib/src/style/windows.dart b/pkgs/path/lib/src/style/windows.dart
new file mode 100644
index 0000000..b7542c6
--- /dev/null
+++ b/pkgs/path/lib/src/style/windows.dart
@@ -0,0 +1,182 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../characters.dart' as chars;
+import '../internal_style.dart';
+import '../parsed_path.dart';
+import '../utils.dart';
+
+// `0b100000` can be bitwise-ORed with uppercase ASCII letters to get their
+// lowercase equivalents.
+const _asciiCaseBit = 0x20;
+
+/// The style for Windows paths.
+class WindowsStyle extends InternalStyle {
+ @override
+ final name = 'windows';
+ @override
+ final separator = '\\';
+ final separators = const ['/', '\\'];
+
+ // Deprecated properties.
+
+ @override
+ final separatorPattern = RegExp(r'[/\\]');
+ @override
+ final needsSeparatorPattern = RegExp(r'[^/\\]$');
+ @override
+ final rootPattern = RegExp(r'^(\\\\[^\\]+\\[^\\/]+|[a-zA-Z]:[/\\])');
+ @override
+ final relativeRootPattern = RegExp(r'^[/\\](?![/\\])');
+
+ @override
+ bool containsSeparator(String path) => path.contains('/');
+
+ @override
+ bool isSeparator(int codeUnit) =>
+ codeUnit == chars.slash || codeUnit == chars.backslash;
+
+ @override
+ bool needsSeparator(String path) {
+ if (path.isEmpty) return false;
+ return !isSeparator(path.codeUnitAt(path.length - 1));
+ }
+
+ @override
+ int rootLength(String path, {bool withDrive = false}) {
+ if (path.isEmpty) return 0;
+ if (path.codeUnitAt(0) == chars.slash) return 1;
+ if (path.codeUnitAt(0) == chars.backslash) {
+ if (path.length < 2 || path.codeUnitAt(1) != chars.backslash) return 1;
+ // The path is a network share. Search for up to two '\'s, as they are
+ // the server and share - and part of the root part.
+ var index = path.indexOf('\\', 2);
+ if (index > 0) {
+ index = path.indexOf('\\', index + 1);
+ if (index > 0) return index;
+ }
+ return path.length;
+ }
+ // If the path is of the form 'C:/' or 'C:\', with C being any letter, it's
+ // a root part.
+ if (path.length < 3) return 0;
+ // Check for the letter.
+ if (!isAlphabetic(path.codeUnitAt(0))) return 0;
+ // Check for the ':'.
+ if (path.codeUnitAt(1) != chars.colon) return 0;
+ // Check for either '/' or '\'.
+ if (!isSeparator(path.codeUnitAt(2))) return 0;
+ return 3;
+ }
+
+ @override
+ bool isRootRelative(String path) => rootLength(path) == 1;
+
+ @override
+ String? getRelativeRoot(String path) {
+ final length = rootLength(path);
+ if (length == 1) return path[0];
+ return null;
+ }
+
+ @override
+ String pathFromUri(Uri uri) {
+ if (uri.scheme != '' && uri.scheme != 'file') {
+ throw ArgumentError("Uri $uri must have scheme 'file:'.");
+ }
+
+ var path = uri.path;
+ if (uri.host == '') {
+ // Drive-letter paths look like "file:///C:/path/to/file". The
+ // replaceFirst removes the extra initial slash. Otherwise, leave the
+ // slash to match IE's interpretation of "/foo" as a root-relative path.
+ if (path.length >= 3 && path.startsWith('/') && isDriveLetter(path, 1)) {
+ path = path.replaceFirst('/', '');
+ }
+ } else {
+ // Network paths look like "file://hostname/path/to/file".
+ path = '\\\\${uri.host}$path';
+ }
+ return Uri.decodeComponent(path.replaceAll('/', '\\'));
+ }
+
+ @override
+ Uri absolutePathToUri(String path) {
+ final parsed = ParsedPath.parse(path, this);
+ if (parsed.root!.startsWith(r'\\')) {
+ // Network paths become "file://server/share/path/to/file".
+
+ // The root is of the form "\\server\share". We want "server" to be the
+ // URI host, and "share" to be the first element of the path.
+ final rootParts = parsed.root!.split('\\').where((part) => part != '');
+ parsed.parts.insert(0, rootParts.last);
+
+ if (parsed.hasTrailingSeparator) {
+ // If the path has a trailing slash, add a single empty component so the
+ // URI has a trailing slash as well.
+ parsed.parts.add('');
+ }
+
+ return Uri(
+ scheme: 'file', host: rootParts.first, pathSegments: parsed.parts);
+ } else {
+ // Drive-letter paths become "file:///C:/path/to/file".
+
+ // If the path is a bare root (e.g. "C:\"), [parsed.parts] will currently
+ // be empty. We add an empty component so the URL constructor produces
+ // "file:///C:/", with a trailing slash. We also add an empty component if
+ // the URL otherwise has a trailing slash.
+ if (parsed.parts.isEmpty || parsed.hasTrailingSeparator) {
+ parsed.parts.add('');
+ }
+
+ // Get rid of the trailing "\" in "C:\" because the URI constructor will
+ // add a separator on its own.
+ parsed.parts
+ .insert(0, parsed.root!.replaceAll('/', '').replaceAll('\\', ''));
+
+ return Uri(scheme: 'file', pathSegments: parsed.parts);
+ }
+ }
+
+ @override
+ bool codeUnitsEqual(int codeUnit1, int codeUnit2) {
+ if (codeUnit1 == codeUnit2) return true;
+
+ /// Forward slashes and backslashes are equivalent on Windows.
+ if (codeUnit1 == chars.slash) return codeUnit2 == chars.backslash;
+ if (codeUnit1 == chars.backslash) return codeUnit2 == chars.slash;
+
+ // If this check fails, the code units are definitely different. If it
+ // succeeds *and* either codeUnit is an ASCII letter, they're equivalent.
+ if (codeUnit1 ^ codeUnit2 != _asciiCaseBit) return false;
+
+ // Now we just need to verify that one of the code units is an ASCII letter.
+ final upperCase1 = codeUnit1 | _asciiCaseBit;
+ return upperCase1 >= chars.lowerA && upperCase1 <= chars.lowerZ;
+ }
+
+ @override
+ bool pathsEqual(String path1, String path2) {
+ if (identical(path1, path2)) return true;
+ if (path1.length != path2.length) return false;
+ for (var i = 0; i < path1.length; i++) {
+ if (!codeUnitsEqual(path1.codeUnitAt(i), path2.codeUnitAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @override
+ int canonicalizeCodeUnit(int codeUnit) {
+ if (codeUnit == chars.slash) return chars.backslash;
+ if (codeUnit < chars.upperA) return codeUnit;
+ if (codeUnit > chars.upperZ) return codeUnit;
+ return codeUnit | _asciiCaseBit;
+ }
+
+ @override
+ String canonicalizePart(String part) => part.toLowerCase();
+}
diff --git a/pkgs/path/lib/src/utils.dart b/pkgs/path/lib/src/utils.dart
new file mode 100644
index 0000000..7c01312
--- /dev/null
+++ b/pkgs/path/lib/src/utils.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'characters.dart' as chars;
+
+/// Returns whether [char] is the code for an ASCII letter (uppercase or
+/// lowercase).
+bool isAlphabetic(int char) =>
+ (char >= chars.upperA && char <= chars.upperZ) ||
+ (char >= chars.lowerA && char <= chars.lowerZ);
+
+/// Returns whether [char] is the code for an ASCII digit.
+bool isNumeric(int char) => char >= chars.zero && char <= chars.nine;
+
+/// Returns whether [path] has a URL-formatted Windows drive letter beginning at
+/// [index].
+bool isDriveLetter(String path, int index) =>
+ driveLetterEnd(path, index) != null;
+
+/// Returns the index of the first character after the drive letter or a
+/// URL-formatted path, or `null` if [index] is not the start of a drive letter.
+/// A valid drive letter must be followed by a colon and then either a `/` or
+/// the end of string.
+///
+/// ```
+/// d:/abc => 3
+/// d:/ => 3
+/// d: => 2
+/// d => null
+/// ```
+int? driveLetterEnd(String path, int index) {
+ if (path.length < index + 2) return null;
+ if (!isAlphabetic(path.codeUnitAt(index))) return null;
+ if (path.codeUnitAt(index + 1) != chars.colon) {
+ // If not a raw colon, check for escaped colon
+ if (path.length < index + 4) return null;
+ if (path.substring(index + 1, index + 4).toLowerCase() != '%3a') {
+ return null;
+ }
+ // Offset the index to account for the extra 2 characters from the
+ // colon encoding.
+ index += 2;
+ }
+ if (path.length == index + 2) return index + 2;
+ if (path.codeUnitAt(index + 2) != chars.slash) return null;
+ return index + 3;
+}
diff --git a/pkgs/path/pubspec.yaml b/pkgs/path/pubspec.yaml
new file mode 100644
index 0000000..d988127
--- /dev/null
+++ b/pkgs/path/pubspec.yaml
@@ -0,0 +1,15 @@
+name: path
+version: 1.9.1
+description: A string-based path manipulation library.
+repository: https://github.com/dart-lang/core/tree/main/pkgs/path
+issue_tracker: https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Apath
+
+topics:
+ - file-system
+
+environment:
+ sdk: ^3.4.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
diff --git a/pkgs/path/test/browser_test.dart b/pkgs/path/test/browser_test.dart
new file mode 100644
index 0000000..cddd846
--- /dev/null
+++ b/pkgs/path/test/browser_test.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@TestOn('browser')
+library;
+
+import 'dart:html';
+
+import 'package:path/path.dart' as path;
+import 'package:test/test.dart';
+
+void main() {
+ group('new Context()', () {
+ test('uses the window location if root and style are omitted', () {
+ final context = path.Context();
+ expect(context.current,
+ Uri.parse(window.location.href).resolve('.').toString());
+ });
+
+ test('uses "." if root is omitted', () {
+ final context = path.Context(style: path.Style.platform);
+ expect(context.current, '.');
+ });
+
+ test('uses the host platform if style is omitted', () {
+ final context = path.Context();
+ expect(context.style, path.Style.platform);
+ });
+ });
+
+ test('Style.platform is url', () {
+ expect(path.Style.platform, path.Style.url);
+ });
+
+ test('current', () {
+ expect(
+ path.current, Uri.parse(window.location.href).resolve('.').toString());
+ });
+}
diff --git a/pkgs/path/test/io_test.dart b/pkgs/path/test/io_test.dart
new file mode 100644
index 0000000..de22caf
--- /dev/null
+++ b/pkgs/path/test/io_test.dart
@@ -0,0 +1,105 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@TestOn('vm')
+library;
+
+import 'dart:io' as io;
+
+import 'package:path/path.dart' as path;
+import 'package:test/test.dart';
+
+void main() {
+ group('new Context()', () {
+ test('uses the current directory if root and style are omitted', () {
+ final context = path.Context();
+ expect(context.current, io.Directory.current.path);
+ });
+
+ test('uses "." if root is omitted', () {
+ final context = path.Context(style: path.Style.platform);
+ expect(context.current, '.');
+ });
+
+ test('uses the host platform if style is omitted', () {
+ final context = path.Context();
+ expect(context.style, path.Style.platform);
+ });
+ });
+
+ test('Style.platform returns the host platform style', () {
+ if (io.Platform.operatingSystem == 'windows') {
+ expect(path.Style.platform, path.Style.windows);
+ } else {
+ expect(path.Style.platform, path.Style.posix);
+ }
+ });
+
+ group('current', () {
+ test('returns the current working directory', () {
+ expect(path.current, io.Directory.current.path);
+ });
+
+ test('uses the previous working directory if deleted', () {
+ final dir = io.Directory.current.path;
+ try {
+ final temp = io.Directory.systemTemp.createTempSync('path_test');
+ final tempPath = temp.resolveSymbolicLinksSync();
+ io.Directory.current = temp;
+
+ // Call "current" once so that it can be cached.
+ expect(path.normalize(path.absolute(path.current)), equals(tempPath));
+
+ temp.deleteSync();
+
+ // Even though the directory no longer exists, no exception is thrown.
+ expect(path.normalize(path.absolute(path.current)), equals(tempPath));
+ } finally {
+ io.Directory.current = dir;
+ }
+ },
+ //TODO: Figure out why this is failing on windows and fix!
+ skip: io.Platform.isWindows ? 'Untriaged failure on Windows' : false);
+ });
+
+ test('registers changes to the working directory', () {
+ final dir = io.Directory.current.path;
+ try {
+ expect(path.absolute('foo/bar'), equals(path.join(dir, 'foo/bar')));
+ expect(
+ path.absolute('foo/bar'), equals(path.context.join(dir, 'foo/bar')));
+
+ io.Directory.current = path.dirname(dir);
+ expect(path.normalize(path.absolute('foo/bar')),
+ equals(path.normalize(path.join(dir, '../foo/bar'))));
+ expect(path.normalize(path.absolute('foo/bar')),
+ equals(path.normalize(path.context.join(dir, '../foo/bar'))));
+ } finally {
+ io.Directory.current = dir;
+ }
+ });
+
+ // Regression test for #35. This tests against the *actual* working directory
+ // rather than just a custom context because we do some processing in
+ // [path.current] that has clobbered the root in the past.
+ test('absolute works on root working directory', () {
+ final dir = path.current;
+ try {
+ io.Directory.current = path.rootPrefix(path.current);
+
+ expect(path.relative(path.absolute('foo/bar'), from: path.current),
+ path.relative(path.absolute('foo/bar')));
+
+ expect(path.normalize(path.absolute('foo/bar')),
+ equals(path.normalize(path.join(path.current, '../foo/bar'))));
+
+ expect(path.normalize(path.absolute('foo/bar')),
+ equals(path.normalize(path.join(path.current, '../foo/bar'))));
+ } finally {
+ io.Directory.current = dir;
+ }
+ },
+ //TODO(kevmoo): figure out why this is failing on windows and fix!
+ skip: io.Platform.isWindows ? 'Untriaged failure on Windows' : null);
+}
diff --git a/pkgs/path/test/path_map_test.dart b/pkgs/path/test/path_map_test.dart
new file mode 100644
index 0000000..11c4a3e
--- /dev/null
+++ b/pkgs/path/test/path_map_test.dart
@@ -0,0 +1,79 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:path/path.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('considers equal', () {
+ test('two identical paths', () {
+ final map = PathMap<int>();
+ map[join('foo', 'bar')] = 1;
+ map[join('foo', 'bar')] = 2;
+ expect(map, hasLength(1));
+ expect(map, containsPair(join('foo', 'bar'), 2));
+ });
+
+ test('two logically equivalent paths', () {
+ final map = PathMap<int>();
+ map['foo'] = 1;
+ map[absolute('foo')] = 2;
+ expect(map, hasLength(1));
+ expect(map, containsPair('foo', 2));
+ expect(map, containsPair(absolute('foo'), 2));
+ });
+
+ test('two nulls', () {
+ final map = PathMap<int>();
+ map[null] = 1;
+ map[null] = 2;
+ expect(map, hasLength(1));
+ expect(map, containsPair(null, 2));
+ });
+ });
+
+ group('considers unequal', () {
+ test('two distinct paths', () {
+ final map = PathMap<int>();
+ map['foo'] = 1;
+ map['bar'] = 2;
+ expect(map, hasLength(2));
+ expect(map, containsPair('foo', 1));
+ expect(map, containsPair('bar', 2));
+ });
+
+ test('a path and null', () {
+ final map = PathMap<int>();
+ map['foo'] = 1;
+ map[null] = 2;
+ expect(map, hasLength(2));
+ expect(map, containsPair('foo', 1));
+ expect(map, containsPair(null, 2));
+ });
+ });
+
+ test('uses the custom context', () {
+ final map = PathMap<int>(context: windows);
+ map['FOO'] = 1;
+ map['foo'] = 2;
+ expect(map, hasLength(1));
+ expect(map, containsPair('fOo', 2));
+ });
+
+ group('.of()', () {
+ test("copies the existing map's keys", () {
+ final map = PathMap.of({'foo': 1, 'bar': 2});
+ expect(map, hasLength(2));
+ expect(map, containsPair('foo', 1));
+ expect(map, containsPair('bar', 2));
+ });
+
+ test('uses the second value in the case of duplicates', () {
+ final map = PathMap.of({'foo': 1, absolute('foo'): 2});
+ expect(map, hasLength(1));
+ expect(map, containsPair('foo', 2));
+ expect(map, containsPair(absolute('foo'), 2));
+ });
+ });
+}
diff --git a/pkgs/path/test/path_set_test.dart b/pkgs/path/test/path_set_test.dart
new file mode 100644
index 0000000..135f9de
--- /dev/null
+++ b/pkgs/path/test/path_set_test.dart
@@ -0,0 +1,80 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:path/path.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('considers equal', () {
+ test('two identical paths', () {
+ final set = PathSet();
+ expect(set.add(join('foo', 'bar')), isTrue);
+ expect(set.add(join('foo', 'bar')), isFalse);
+ expect(set, hasLength(1));
+ expect(set, contains(join('foo', 'bar')));
+ });
+
+ test('two logically equivalent paths', () {
+ final set = PathSet();
+ expect(set.add('foo'), isTrue);
+ expect(set.add(absolute('foo')), isFalse);
+ expect(set, hasLength(1));
+ expect(set, contains('foo'));
+ expect(set, contains(absolute('foo')));
+ });
+
+ test('two nulls', () {
+ final set = PathSet();
+ expect(set.add(null), isTrue);
+ expect(set.add(null), isFalse);
+ expect(set, hasLength(1));
+ expect(set, contains(null));
+ });
+ });
+
+ group('considers unequal', () {
+ test('two distinct paths', () {
+ final set = PathSet();
+ expect(set.add('foo'), isTrue);
+ expect(set.add('bar'), isTrue);
+ expect(set, hasLength(2));
+ expect(set, contains('foo'));
+ expect(set, contains('bar'));
+ });
+
+ test('a path and null', () {
+ final set = PathSet();
+ expect(set.add('foo'), isTrue);
+ expect(set.add(null), isTrue);
+ expect(set, hasLength(2));
+ expect(set, contains('foo'));
+ expect(set, contains(null));
+ });
+ });
+
+ test('uses the custom context', () {
+ final set = PathSet(context: windows);
+ expect(set.add('FOO'), isTrue);
+ expect(set.add('foo'), isFalse);
+ expect(set, hasLength(1));
+ expect(set, contains('fOo'));
+ });
+
+ group('.of()', () {
+ test("copies the existing set's keys", () {
+ final set = PathSet.of(['foo', 'bar']);
+ expect(set, hasLength(2));
+ expect(set, contains('foo'));
+ expect(set, contains('bar'));
+ });
+
+ test('uses the first value in the case of duplicates', () {
+ final set = PathSet.of(['foo', absolute('foo')]);
+ expect(set, hasLength(1));
+ expect(set, contains('foo'));
+ expect(set, contains(absolute('foo')));
+ expect(set.first, 'foo');
+ });
+ });
+}
diff --git a/pkgs/path/test/path_test.dart b/pkgs/path/test/path_test.dart
new file mode 100644
index 0000000..b99b78b
--- /dev/null
+++ b/pkgs/path/test/path_test.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:path/path.dart' as path;
+import 'package:test/test.dart';
+
+void main() {
+ group('path.Style', () {
+ test('name', () {
+ expect(path.Style.posix.name, 'posix');
+ expect(path.Style.windows.name, 'windows');
+ });
+
+ test('separator', () {
+ // ignore: deprecated_member_use_from_same_package
+ expect(path.Style.posix.separator, '/');
+ // ignore: deprecated_member_use_from_same_package
+ expect(path.Style.windows.separator, '\\');
+ });
+
+ test('toString()', () {
+ expect(path.Style.posix.toString(), 'posix');
+ expect(path.Style.windows.toString(), 'windows');
+ });
+ });
+
+ group('new Context()', () {
+ test('uses the given current directory', () {
+ final context = path.Context(current: '/a/b/c');
+ expect(context.current, '/a/b/c');
+ });
+
+ test('uses the given style', () {
+ final context = path.Context(style: path.Style.windows);
+ expect(context.style, path.Style.windows);
+ });
+ });
+
+ test('posix is a default Context for the POSIX style', () {
+ expect(path.posix.style, path.Style.posix);
+ expect(path.posix.current, '.');
+ });
+
+ test('windows is a default Context for the Windows style', () {
+ expect(path.windows.style, path.Style.windows);
+ expect(path.windows.current, '.');
+ });
+
+ test('url is a default Context for the URL style', () {
+ expect(path.url.style, path.Style.url);
+ expect(path.url.current, '.');
+ });
+}
diff --git a/pkgs/path/test/posix_test.dart b/pkgs/path/test/posix_test.dart
new file mode 100644
index 0000000..121b4e3
--- /dev/null
+++ b/pkgs/path/test/posix_test.dart
@@ -0,0 +1,692 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:path/path.dart' as path;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ final context = path.Context(style: path.Style.posix, current: '/root/path');
+
+ test('separator', () {
+ expect(context.separator, '/');
+ });
+
+ test('extension', () {
+ expect(context.extension(''), '');
+ expect(context.extension('.'), '');
+ expect(context.extension('..'), '');
+ expect(context.extension('foo.dart'), '.dart');
+ expect(context.extension('foo.dart.js'), '.js');
+ expect(context.extension('a.b/c'), '');
+ expect(context.extension('a.b/c.d'), '.d');
+ expect(context.extension('~/.bashrc'), '');
+ expect(context.extension(r'a.b\c'), r'.b\c');
+ expect(context.extension('foo.dart/'), '.dart');
+ expect(context.extension('foo.dart//'), '.dart');
+ expect(context.extension('foo.bar.dart.js', 2), '.dart.js');
+ expect(context.extension(r'foo.bar.dart.js', 3), '.bar.dart.js');
+ expect(context.extension(r'foo.bar.dart.js', 10), '.bar.dart.js');
+ expect(context.extension('a.b/c.d', 2), '.d');
+ expect(() => context.extension(r'foo.bar.dart.js', 0), throwsRangeError);
+ expect(() => context.extension(r'foo.bar.dart.js', -1), throwsRangeError);
+ });
+
+ test('rootPrefix', () {
+ expect(context.rootPrefix(''), '');
+ expect(context.rootPrefix('a'), '');
+ expect(context.rootPrefix('a/b'), '');
+ expect(context.rootPrefix('/a/c'), '/');
+ expect(context.rootPrefix('/'), '/');
+ });
+
+ test('dirname', () {
+ expect(context.dirname(''), '.');
+ expect(context.dirname('.'), '.');
+ expect(context.dirname('..'), '.');
+ expect(context.dirname('../..'), '..');
+ expect(context.dirname('a'), '.');
+ expect(context.dirname('a/b'), 'a');
+ expect(context.dirname('a/b/c'), 'a/b');
+ expect(context.dirname('a/b.c'), 'a');
+ expect(context.dirname('a/'), '.');
+ expect(context.dirname('a/.'), 'a');
+ expect(context.dirname('a/..'), 'a');
+ expect(context.dirname(r'a\b/c'), r'a\b');
+ expect(context.dirname('/a'), '/');
+ expect(context.dirname('///a'), '/');
+ expect(context.dirname('/'), '/');
+ expect(context.dirname('///'), '/');
+ expect(context.dirname('a/b/'), 'a');
+ expect(context.dirname(r'a/b\c'), 'a');
+ expect(context.dirname('a//'), '.');
+ expect(context.dirname('a/b//'), 'a');
+ expect(context.dirname('a//b'), 'a');
+ });
+
+ test('basename', () {
+ expect(context.basename(''), '');
+ expect(context.basename('.'), '.');
+ expect(context.basename('..'), '..');
+ expect(context.basename('.foo'), '.foo');
+ expect(context.basename('a'), 'a');
+ expect(context.basename('a/b'), 'b');
+ expect(context.basename('a/b/c'), 'c');
+ expect(context.basename('a/b.c'), 'b.c');
+ expect(context.basename('a/'), 'a');
+ expect(context.basename('a/.'), '.');
+ expect(context.basename('a/..'), '..');
+ expect(context.basename(r'a\b/c'), 'c');
+ expect(context.basename('/a'), 'a');
+ expect(context.basename('/'), '/');
+ expect(context.basename('a/b/'), 'b');
+ expect(context.basename(r'a/b\c'), r'b\c');
+ expect(context.basename('a//'), 'a');
+ expect(context.basename('a/b//'), 'b');
+ expect(context.basename('a//b'), 'b');
+ });
+
+ test('basenameWithoutExtension', () {
+ expect(context.basenameWithoutExtension(''), '');
+ expect(context.basenameWithoutExtension('.'), '.');
+ expect(context.basenameWithoutExtension('..'), '..');
+ expect(context.basenameWithoutExtension('a'), 'a');
+ expect(context.basenameWithoutExtension('a/b'), 'b');
+ expect(context.basenameWithoutExtension('a/b/c'), 'c');
+ expect(context.basenameWithoutExtension('a/b.c'), 'b');
+ expect(context.basenameWithoutExtension('a/'), 'a');
+ expect(context.basenameWithoutExtension('a/.'), '.');
+ expect(context.basenameWithoutExtension(r'a/b\c'), r'b\c');
+ expect(context.basenameWithoutExtension('a/.bashrc'), '.bashrc');
+ expect(context.basenameWithoutExtension('a/b/c.d.e'), 'c.d');
+ expect(context.basenameWithoutExtension('a//'), 'a');
+ expect(context.basenameWithoutExtension('a/b//'), 'b');
+ expect(context.basenameWithoutExtension('a//b'), 'b');
+ expect(context.basenameWithoutExtension('a/b.c/'), 'b');
+ expect(context.basenameWithoutExtension('a/b.c//'), 'b');
+ expect(context.basenameWithoutExtension('a/b c.d e'), 'b c');
+ });
+
+ test('isAbsolute', () {
+ expect(context.isAbsolute(''), false);
+ expect(context.isAbsolute('a'), false);
+ expect(context.isAbsolute('a/b'), false);
+ expect(context.isAbsolute('/a'), true);
+ expect(context.isAbsolute('/a/b'), true);
+ expect(context.isAbsolute('~'), false);
+ expect(context.isAbsolute('.'), false);
+ expect(context.isAbsolute('..'), false);
+ expect(context.isAbsolute('.foo'), false);
+ expect(context.isAbsolute('../a'), false);
+ expect(context.isAbsolute('C:/a'), false);
+ expect(context.isAbsolute(r'C:\a'), false);
+ expect(context.isAbsolute(r'\\a'), false);
+ });
+
+ test('isRelative', () {
+ expect(context.isRelative(''), true);
+ expect(context.isRelative('a'), true);
+ expect(context.isRelative('a/b'), true);
+ expect(context.isRelative('/a'), false);
+ expect(context.isRelative('/a/b'), false);
+ expect(context.isRelative('~'), true);
+ expect(context.isRelative('.'), true);
+ expect(context.isRelative('..'), true);
+ expect(context.isRelative('.foo'), true);
+ expect(context.isRelative('../a'), true);
+ expect(context.isRelative('C:/a'), true);
+ expect(context.isRelative(r'C:\a'), true);
+ expect(context.isRelative(r'\\a'), true);
+ });
+
+ group('join', () {
+ test('allows up to sixteen parts', () {
+ expect(context.join('a'), 'a');
+ expect(context.join('a', 'b'), 'a/b');
+ expect(context.join('a', 'b', 'c'), 'a/b/c');
+ expect(context.join('a', 'b', 'c', 'd'), 'a/b/c/d');
+ expect(context.join('a', 'b', 'c', 'd', 'e'), 'a/b/c/d/e');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f'), 'a/b/c/d/e/f');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f', 'g'), 'a/b/c/d/e/f/g');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'),
+ 'a/b/c/d/e/f/g/h');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'),
+ 'a/b/c/d/e/f/g/h/i');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'),
+ 'a/b/c/d/e/f/g/h/i/j');
+ expect(
+ context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'),
+ 'a/b/c/d/e/f/g/h/i/j/k');
+ expect(
+ context.join(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'),
+ 'a/b/c/d/e/f/g/h/i/j/k/l');
+ expect(
+ context.join(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'),
+ 'a/b/c/d/e/f/g/h/i/j/k/l/m');
+ expect(
+ context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
+ 'l', 'm', 'n'),
+ 'a/b/c/d/e/f/g/h/i/j/k/l/m/n');
+ expect(
+ context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
+ 'l', 'm', 'n', 'o'),
+ 'a/b/c/d/e/f/g/h/i/j/k/l/m/n/o');
+ expect(
+ context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
+ 'l', 'm', 'n', 'o', 'p'),
+ 'a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p');
+ });
+
+ test('does not add separator if a part ends in one', () {
+ expect(context.join('a/', 'b', 'c/', 'd'), 'a/b/c/d');
+ expect(context.join('a\\', 'b'), r'a\/b');
+ });
+
+ test('ignores parts before an absolute path', () {
+ expect(context.join('a', '/', 'b', 'c'), '/b/c');
+ expect(context.join('a', '/b', '/c', 'd'), '/c/d');
+ expect(context.join('a', r'c:\b', 'c', 'd'), r'a/c:\b/c/d');
+ expect(context.join('a', r'\\b', 'c', 'd'), r'a/\\b/c/d');
+ });
+
+ test('ignores trailing nulls', () {
+ expect(context.join('a', null), equals('a'));
+ expect(context.join('a', 'b', 'c', null, null), equals('a/b/c'));
+ });
+
+ test('ignores empty strings', () {
+ expect(context.join(''), '');
+ expect(context.join('', ''), '');
+ expect(context.join('', 'a'), 'a');
+ expect(context.join('a', '', 'b', '', '', '', 'c'), 'a/b/c');
+ expect(context.join('a', 'b', ''), 'a/b');
+ });
+
+ test('disallows intermediate nulls', () {
+ expect(() => context.join('a', null, 'b'), throwsArgumentError);
+ });
+
+ test('join does not modify internal ., .., or trailing separators', () {
+ expect(context.join('a/', 'b/c/'), 'a/b/c/');
+ expect(context.join('a/b/./c/..//', 'd/.././..//e/f//'),
+ 'a/b/./c/..//d/.././..//e/f//');
+ expect(context.join('a/b', 'c/../../../..'), 'a/b/c/../../../..');
+ expect(context.join('a', 'b${context.separator}'), 'a/b/');
+ });
+ });
+
+ group('joinAll', () {
+ test('allows more than sixteen parts', () {
+ expect(
+ context.joinAll([
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i',
+ 'j',
+ 'k',
+ 'l',
+ 'm',
+ 'n',
+ 'o',
+ 'p',
+ 'q'
+ ]),
+ 'a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q');
+ });
+
+ test('does not add separator if a part ends in one', () {
+ expect(context.joinAll(['a/', 'b', 'c/', 'd']), 'a/b/c/d');
+ expect(context.joinAll(['a\\', 'b']), r'a\/b');
+ });
+
+ test('ignores parts before an absolute path', () {
+ expect(context.joinAll(['a', '/', 'b', 'c']), '/b/c');
+ expect(context.joinAll(['a', '/b', '/c', 'd']), '/c/d');
+ expect(context.joinAll(['a', r'c:\b', 'c', 'd']), r'a/c:\b/c/d');
+ expect(context.joinAll(['a', r'\\b', 'c', 'd']), r'a/\\b/c/d');
+ });
+ });
+
+ group('split', () {
+ test('simple cases', () {
+ expect(context.split(''), <String>[]);
+ expect(context.split('.'), ['.']);
+ expect(context.split('..'), ['..']);
+ expect(context.split('foo'), equals(['foo']));
+ expect(context.split('foo/bar.txt'), equals(['foo', 'bar.txt']));
+ expect(context.split('foo/bar/baz'), equals(['foo', 'bar', 'baz']));
+ expect(context.split('foo/../bar/./baz'),
+ equals(['foo', '..', 'bar', '.', 'baz']));
+ expect(context.split('foo//bar///baz'), equals(['foo', 'bar', 'baz']));
+ expect(context.split('foo/\\/baz'), equals(['foo', '\\', 'baz']));
+ expect(context.split('.'), equals(['.']));
+ expect(context.split(''), equals([]));
+ expect(context.split('foo/'), equals(['foo']));
+ expect(context.split('//'), equals(['/']));
+ });
+
+ test('includes the root for absolute paths', () {
+ expect(context.split('/foo/bar/baz'), equals(['/', 'foo', 'bar', 'baz']));
+ expect(context.split('/'), equals(['/']));
+ });
+ });
+
+ group('normalize', () {
+ test('simple cases', () {
+ expect(context.normalize(''), '.');
+ expect(context.normalize('.'), '.');
+ expect(context.normalize('..'), '..');
+ expect(context.normalize('a'), 'a');
+ expect(context.normalize('/'), '/');
+ expect(context.normalize(r'\'), r'\');
+ expect(context.normalize('C:/'), 'C:');
+ expect(context.normalize(r'C:\'), r'C:\');
+ expect(context.normalize(r'\\'), r'\\');
+ expect(context.normalize('a/./\xc5\u0bf8-;\u{1f085}\u{00}/c/d/../'),
+ 'a/\xc5\u0bf8-;\u{1f085}\u{00}/c');
+ });
+
+ test('collapses redundant separators', () {
+ expect(context.normalize(r'a/b/c'), r'a/b/c');
+ expect(context.normalize(r'a//b///c////d'), r'a/b/c/d');
+ });
+
+ test('does not collapse separators for other platform', () {
+ expect(context.normalize(r'a\\b\\\c'), r'a\\b\\\c');
+ });
+
+ test('eliminates "." parts', () {
+ expect(context.normalize('./'), '.');
+ expect(context.normalize('/.'), '/');
+ expect(context.normalize('/./'), '/');
+ expect(context.normalize('./.'), '.');
+ expect(context.normalize('a/./b'), 'a/b');
+ expect(context.normalize('a/.b/c'), 'a/.b/c');
+ expect(context.normalize('a/././b/./c'), 'a/b/c');
+ expect(context.normalize('././a'), 'a');
+ expect(context.normalize('a/./.'), 'a');
+ });
+
+ test('eliminates ".." parts', () {
+ expect(context.normalize('..'), '..');
+ expect(context.normalize('../'), '..');
+ expect(context.normalize('../../..'), '../../..');
+ expect(context.normalize('../../../'), '../../..');
+ expect(context.normalize('/..'), '/');
+ expect(context.normalize('/../../..'), '/');
+ expect(context.normalize('/../../../a'), '/a');
+ expect(context.normalize('c:/..'), '.');
+ expect(context.normalize('A:/../../..'), '../..');
+ expect(context.normalize('a/..'), '.');
+ expect(context.normalize('a/b/..'), 'a');
+ expect(context.normalize('a/../b'), 'b');
+ expect(context.normalize('a/./../b'), 'b');
+ expect(context.normalize('a/b/c/../../d/e/..'), 'a/d');
+ expect(context.normalize('a/b/../../../../c'), '../../c');
+ expect(context.normalize(r'z/a/b/../../..\../c'), r'z/..\../c');
+ expect(context.normalize(r'a/b\c/../d'), 'a/d');
+ });
+
+ test('does not walk before root on absolute paths', () {
+ expect(context.normalize('..'), '..');
+ expect(context.normalize('../'), '..');
+ expect(context.normalize('https://dart.dev/..'), 'https:');
+ expect(context.normalize('https://dart.dev/../../a'), 'a');
+ expect(context.normalize('file:///..'), '.');
+ expect(context.normalize('file:///../../a'), '../a');
+ expect(context.normalize('/..'), '/');
+ expect(context.normalize('a/..'), '.');
+ expect(context.normalize('../a'), '../a');
+ expect(context.normalize('/../a'), '/a');
+ expect(context.normalize('c:/../a'), 'a');
+ expect(context.normalize('/../a'), '/a');
+ expect(context.normalize('a/b/..'), 'a');
+ expect(context.normalize('../a/b/..'), '../a');
+ expect(context.normalize('a/../b'), 'b');
+ expect(context.normalize('a/./../b'), 'b');
+ expect(context.normalize('a/b/c/../../d/e/..'), 'a/d');
+ expect(context.normalize('a/b/../../../../c'), '../../c');
+ expect(context.normalize('a/b/c/../../..d/./.e/f././'), 'a/..d/.e/f.');
+ });
+
+ test('removes trailing separators', () {
+ expect(context.normalize('./'), '.');
+ expect(context.normalize('.//'), '.');
+ expect(context.normalize('a/'), 'a');
+ expect(context.normalize('a/b/'), 'a/b');
+ expect(context.normalize(r'a/b\'), r'a/b\');
+ expect(context.normalize('a/b///'), 'a/b');
+ });
+
+ test('when canonicalizing', () {
+ expect(context.canonicalize('.'), '/root/path');
+ expect(context.canonicalize('foo/bar'), '/root/path/foo/bar');
+ expect(context.canonicalize('FoO'), '/root/path/FoO');
+ });
+ });
+
+ group('relative', () {
+ group('from absolute root', () {
+ test('given absolute path in root', () {
+ expect(context.relative('/'), '../..');
+ expect(context.relative('/root'), '..');
+ expect(context.relative('/root/path'), '.');
+ expect(context.relative('/root/path/a'), 'a');
+ expect(context.relative('/root/path/a/b.txt'), 'a/b.txt');
+ expect(context.relative('/root/a/b.txt'), '../a/b.txt');
+ });
+
+ test('given absolute path outside of root', () {
+ expect(context.relative('/a/b'), '../../a/b');
+ expect(context.relative('/root/path/a'), 'a');
+ expect(context.relative('/root/path/a/b.txt'), 'a/b.txt');
+ expect(context.relative('/root/a/b.txt'), '../a/b.txt');
+ });
+
+ test('given relative path', () {
+ // The path is considered relative to the root, so it basically just
+ // normalizes.
+ expect(context.relative(''), '.');
+ expect(context.relative('.'), '.');
+ expect(context.relative('a'), 'a');
+ expect(context.relative('a/b.txt'), 'a/b.txt');
+ expect(context.relative('../a/b.txt'), '../a/b.txt');
+ expect(context.relative('a/./b/../c.txt'), 'a/c.txt');
+ });
+
+ test('is case-sensitive', () {
+ expect(context.relative('/RoOt'), '../../RoOt');
+ expect(context.relative('/rOoT/pAtH/a'), '../../rOoT/pAtH/a');
+ });
+
+ // Regression
+ test('from root-only path', () {
+ expect(context.relative('/', from: '/'), '.');
+ expect(context.relative('/root/path', from: '/'), 'root/path');
+ });
+ });
+
+ group('from relative root', () {
+ final r = path.Context(style: path.Style.posix, current: 'foo/bar');
+
+ test('given absolute path', () {
+ expect(r.relative('/'), equals('/'));
+ expect(r.relative('/a/b'), equals('/a/b'));
+ });
+
+ test('given relative path', () {
+ // The path is considered relative to the root, so it basically just
+ // normalizes.
+ expect(r.relative(''), '.');
+ expect(r.relative('.'), '.');
+ expect(r.relative('..'), '..');
+ expect(r.relative('a'), 'a');
+ expect(r.relative('a/b.txt'), 'a/b.txt');
+ expect(r.relative('../a/b.txt'), '../a/b.txt');
+ expect(r.relative('a/./b/../c.txt'), 'a/c.txt');
+ });
+ });
+
+ test('from a root with extension', () {
+ final r = path.Context(style: path.Style.posix, current: '/dir.ext');
+ expect(r.relative('/dir.ext/file'), 'file');
+ });
+
+ test('with a root parameter', () {
+ expect(context.relative('/foo/bar/baz', from: '/foo/bar'), equals('baz'));
+ expect(context.relative('..', from: '/foo/bar'), equals('../../root'));
+ expect(context.relative('/foo/bar/baz', from: 'foo/bar'),
+ equals('../../../../foo/bar/baz'));
+ expect(context.relative('..', from: 'foo/bar'), equals('../../..'));
+ });
+
+ test('with a root parameter and a relative root', () {
+ final r = path.Context(style: path.Style.posix, current: 'relative/root');
+ expect(r.relative('/foo/bar/baz', from: '/foo/bar'), equals('baz'));
+ expect(() => r.relative('..', from: '/foo/bar'), throwsPathException);
+ expect(
+ r.relative('/foo/bar/baz', from: 'foo/bar'), equals('/foo/bar/baz'));
+ expect(r.relative('..', from: 'foo/bar'), equals('../../..'));
+ });
+
+ test('from a . root', () {
+ final r = path.Context(style: path.Style.posix, current: '.');
+ expect(r.relative('/foo/bar/baz'), equals('/foo/bar/baz'));
+ expect(r.relative('foo/bar/baz'), equals('foo/bar/baz'));
+ });
+ });
+
+ group('isWithin', () {
+ test('simple cases', () {
+ expect(context.isWithin('foo/bar', 'foo/bar'), isFalse);
+ expect(context.isWithin('foo/bar', 'foo/bar/baz'), isTrue);
+ expect(context.isWithin('foo/bar', 'foo/baz'), isFalse);
+ expect(context.isWithin('foo/bar', '../path/foo/bar/baz'), isTrue);
+ expect(context.isWithin('/', '/foo/bar'), isTrue);
+ expect(context.isWithin('baz', '/root/path/baz/bang'), isTrue);
+ expect(context.isWithin('baz', '/root/path/bang/baz'), isFalse);
+ });
+
+ test('complex cases', () {
+ expect(context.isWithin('foo/./bar', 'foo/bar/baz'), isTrue);
+ expect(context.isWithin('foo//bar', 'foo/bar/baz'), isTrue);
+ expect(context.isWithin('foo/qux/../bar', 'foo/bar/baz'), isTrue);
+ expect(context.isWithin('foo/bar', 'foo/bar/baz/../..'), isFalse);
+ expect(context.isWithin('foo/bar', 'foo/bar///'), isFalse);
+ expect(context.isWithin('foo/.bar', 'foo/.bar/baz'), isTrue);
+ expect(context.isWithin('foo/./bar', 'foo/.bar/baz'), isFalse);
+ expect(context.isWithin('foo/..bar', 'foo/..bar/baz'), isTrue);
+ expect(context.isWithin('foo/bar', 'foo/bar/baz/..'), isFalse);
+ expect(context.isWithin('foo/bar', 'foo/bar/baz/../qux'), isTrue);
+ });
+
+ test('from a relative root', () {
+ final r = path.Context(style: path.Style.posix, current: 'foo/bar');
+ expect(r.isWithin('.', 'a/b/c'), isTrue);
+ expect(r.isWithin('.', '../a/b/c'), isFalse);
+ expect(r.isWithin('.', '../../a/foo/b/c'), isFalse);
+ expect(r.isWithin('/', '/baz/bang'), isTrue);
+ expect(r.isWithin('.', '/baz/bang'), isFalse);
+ });
+ });
+
+ group('equals and hash', () {
+ test('simple cases', () {
+ expectEquals(context, 'foo/bar', 'foo/bar');
+ expectNotEquals(context, 'foo/bar', 'foo/bar/baz');
+ expectNotEquals(context, 'foo/bar', 'foo');
+ expectNotEquals(context, 'foo/bar', 'foo/baz');
+ expectEquals(context, 'foo/bar', '../path/foo/bar');
+ expectEquals(context, '/', '/');
+ expectEquals(context, '/', '../..');
+ expectEquals(context, 'baz', '/root/path/baz');
+ });
+
+ test('complex cases', () {
+ expectEquals(context, 'foo/./bar', 'foo/bar');
+ expectEquals(context, 'foo//bar', 'foo/bar');
+ expectEquals(context, 'foo/qux/../bar', 'foo/bar');
+ expectNotEquals(context, 'foo/qux/../bar', 'foo/qux');
+ expectNotEquals(context, 'foo/bar', 'foo/bar/baz/../..');
+ expectEquals(context, 'foo/bar', 'foo/bar///');
+ expectEquals(context, 'foo/.bar', 'foo/.bar');
+ expectNotEquals(context, 'foo/./bar', 'foo/.bar');
+ expectEquals(context, 'foo/..bar', 'foo/..bar');
+ expectNotEquals(context, 'foo/../bar', 'foo/..bar');
+ expectEquals(context, 'foo/bar', 'foo/bar/baz/..');
+ expectNotEquals(context, 'FoO/bAr', 'foo/bar');
+ });
+
+ test('from a relative root', () {
+ final r = path.Context(style: path.Style.posix, current: 'foo/bar');
+ expectEquals(r, 'a/b', 'a/b');
+ expectNotEquals(r, '.', 'foo/bar');
+ expectNotEquals(r, '.', '../a/b');
+ expectEquals(r, '.', '../bar');
+ expectEquals(r, '/baz/bang', '/baz/bang');
+ expectNotEquals(r, 'baz/bang', '/baz/bang');
+ });
+ });
+
+ group('absolute', () {
+ test('allows up to fifteen parts', () {
+ expect(context.absolute('a'), '/root/path/a');
+ expect(context.absolute('a', 'b'), '/root/path/a/b');
+ expect(context.absolute('a', 'b', 'c'), '/root/path/a/b/c');
+ expect(context.absolute('a', 'b', 'c', 'd'), '/root/path/a/b/c/d');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e'), '/root/path/a/b/c/d/e');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f'),
+ '/root/path/a/b/c/d/e/f');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g'),
+ '/root/path/a/b/c/d/e/f/g');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'),
+ '/root/path/a/b/c/d/e/f/g/h');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'),
+ '/root/path/a/b/c/d/e/f/g/h/i');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'),
+ '/root/path/a/b/c/d/e/f/g/h/i/j');
+ expect(
+ context.absolute(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'),
+ '/root/path/a/b/c/d/e/f/g/h/i/j/k');
+ expect(
+ context.absolute(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'),
+ '/root/path/a/b/c/d/e/f/g/h/i/j/k/l');
+ expect(
+ context.absolute(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'),
+ '/root/path/a/b/c/d/e/f/g/h/i/j/k/l/m');
+ expect(
+ context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n'),
+ '/root/path/a/b/c/d/e/f/g/h/i/j/k/l/m/n');
+ expect(
+ context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n', 'o'),
+ '/root/path/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o');
+ });
+
+ test('does not add separator if a part ends in one', () {
+ expect(context.absolute('a/', 'b', 'c/', 'd'), '/root/path/a/b/c/d');
+ expect(context.absolute(r'a\', 'b'), r'/root/path/a\/b');
+ });
+
+ test('ignores parts before an absolute path', () {
+ expect(context.absolute('a', '/b', '/c', 'd'), '/c/d');
+ expect(
+ context.absolute('a', r'c:\b', 'c', 'd'), r'/root/path/a/c:\b/c/d');
+ expect(context.absolute('a', r'\\b', 'c', 'd'), r'/root/path/a/\\b/c/d');
+ });
+ });
+
+ test('withoutExtension', () {
+ expect(context.withoutExtension(''), '');
+ expect(context.withoutExtension('a'), 'a');
+ expect(context.withoutExtension('.a'), '.a');
+ expect(context.withoutExtension('a.b'), 'a');
+ expect(context.withoutExtension('a/b.c'), 'a/b');
+ expect(context.withoutExtension('a/b.c.d'), 'a/b.c');
+ expect(context.withoutExtension('a/'), 'a/');
+ expect(context.withoutExtension('a/b/'), 'a/b/');
+ expect(context.withoutExtension('a/.'), 'a/.');
+ expect(context.withoutExtension('a/.b'), 'a/.b');
+ expect(context.withoutExtension('a.b/c'), 'a.b/c');
+ expect(context.withoutExtension(r'a.b\c'), r'a');
+ expect(context.withoutExtension(r'a/b\c'), r'a/b\c');
+ expect(context.withoutExtension(r'a/b\c.d'), r'a/b\c');
+ expect(context.withoutExtension('a/b.c/'), 'a/b/');
+ expect(context.withoutExtension('a/b.c//'), 'a/b//');
+ });
+
+ test('setExtension', () {
+ expect(context.setExtension('', '.x'), '.x');
+ expect(context.setExtension('a', '.x'), 'a.x');
+ expect(context.setExtension('.a', '.x'), '.a.x');
+ expect(context.setExtension('a.b', '.x'), 'a.x');
+ expect(context.setExtension('a/b.c', '.x'), 'a/b.x');
+ expect(context.setExtension('a/b.c.d', '.x'), 'a/b.c.x');
+ expect(context.setExtension('a/', '.x'), 'a/.x');
+ expect(context.setExtension('a/b/', '.x'), 'a/b/.x');
+ expect(context.setExtension('a/.', '.x'), 'a/..x');
+ expect(context.setExtension('a/.b', '.x'), 'a/.b.x');
+ expect(context.setExtension('a.b/c', '.x'), 'a.b/c.x');
+ expect(context.setExtension(r'a.b\c', '.x'), r'a.x');
+ expect(context.setExtension(r'a/b\c', '.x'), r'a/b\c.x');
+ expect(context.setExtension(r'a/b\c.d', '.x'), r'a/b\c.x');
+ expect(context.setExtension('a/b.c/', '.x'), 'a/b/.x');
+ expect(context.setExtension('a/b.c//', '.x'), 'a/b//.x');
+ });
+
+ group('fromUri', () {
+ test('with a URI', () {
+ expect(context.fromUri(Uri.parse('file:///path/to/foo')), '/path/to/foo');
+ expect(
+ context.fromUri(Uri.parse('file:///path/to/foo/')), '/path/to/foo/');
+ expect(context.fromUri(Uri.parse('file:///')), '/');
+ expect(context.fromUri(Uri.parse('foo/bar')), 'foo/bar');
+ expect(context.fromUri(Uri.parse('/path/to/foo')), '/path/to/foo');
+ expect(context.fromUri(Uri.parse('///path/to/foo')), '/path/to/foo');
+ expect(context.fromUri(Uri.parse('file:///path/to/foo%23bar')),
+ '/path/to/foo#bar');
+ expect(context.fromUri(Uri.parse('_%7B_%7D_%60_%5E_%20_%22_%25_')),
+ r'_{_}_`_^_ _"_%_');
+ expect(() => context.fromUri(Uri.parse('https://dart.dev')),
+ throwsArgumentError);
+ });
+
+ test('with a string', () {
+ expect(context.fromUri('file:///path/to/foo'), '/path/to/foo');
+ });
+ });
+
+ test('toUri', () {
+ expect(context.toUri('/path/to/foo'), Uri.parse('file:///path/to/foo'));
+ expect(context.toUri('/path/to/foo/'), Uri.parse('file:///path/to/foo/'));
+ expect(context.toUri('path/to/foo/'), Uri.parse('path/to/foo/'));
+ expect(context.toUri('/'), Uri.parse('file:///'));
+ expect(context.toUri('foo/bar'), Uri.parse('foo/bar'));
+ expect(context.toUri('/path/to/foo#bar'),
+ Uri.parse('file:///path/to/foo%23bar'));
+ expect(context.toUri(r'/_{_}_`_^_ _"_%_'),
+ Uri.parse('file:///_%7B_%7D_%60_%5E_%20_%22_%25_'));
+ expect(context.toUri(r'_{_}_`_^_ _"_%_'),
+ Uri.parse('_%7B_%7D_%60_%5E_%20_%22_%25_'));
+ expect(context.toUri(''), Uri.parse(''));
+ });
+
+ group('prettyUri', () {
+ test('with a file: URI', () {
+ expect(context.prettyUri('file:///root/path/a/b'), 'a/b');
+ expect(context.prettyUri('file:///root/path/a/../b'), 'b');
+ expect(context.prettyUri('file:///other/path/a/b'), '/other/path/a/b');
+ expect(context.prettyUri('file:///root/other'), '../other');
+ });
+
+ test('with an http: URI', () {
+ expect(context.prettyUri('https://dart.dev/a/b'), 'https://dart.dev/a/b');
+ });
+
+ test('with a relative URI', () {
+ expect(context.prettyUri('a/b'), 'a/b');
+ });
+
+ test('with a root-relative URI', () {
+ expect(context.prettyUri('/a/b'), '/a/b');
+ });
+
+ test('with a Uri object', () {
+ expect(context.prettyUri(Uri.parse('a/b')), 'a/b');
+ });
+ });
+}
diff --git a/pkgs/path/test/relative_test.dart b/pkgs/path/test/relative_test.dart
new file mode 100644
index 0000000..189af73
--- /dev/null
+++ b/pkgs/path/test/relative_test.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+//
+// Test "relative" on all styles of path.Context, on all platforms.
+
+import 'package:path/path.dart' as path;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ relativeTest(path.Context(style: path.Style.posix, current: '.'), '/');
+ relativeTest(path.Context(style: path.Style.posix, current: '/'), '/');
+ relativeTest(
+ path.Context(style: path.Style.windows, current: r'd:\'),
+ r'c:\',
+ );
+ relativeTest(path.Context(style: path.Style.windows, current: '.'), r'c:\');
+ relativeTest(
+ path.Context(style: path.Style.url, current: 'file:///'),
+ 'http://myserver/',
+ );
+ relativeTest(
+ path.Context(style: path.Style.url, current: '.'),
+ 'http://myserver/',
+ );
+ relativeTest(path.Context(style: path.Style.url, current: 'file:///'), '/');
+ relativeTest(path.Context(style: path.Style.url, current: '.'), '/');
+}
+
+void relativeTest(path.Context context, String prefix) {
+ // Cases where the arguments are absolute paths.
+ void expectRelative(String result, String pathArg, String fromArg) {
+ test('relative $pathArg from $fromArg', () {
+ expect(
+ context.relative(pathArg, from: fromArg),
+ context.normalize(result),
+ );
+ });
+ }
+
+ group('${context.style}', () {
+ expectRelative('c/d', '${prefix}a/b/c/d', '${prefix}a/b');
+ expectRelative('c/d', '${prefix}a/b/c/d', '${prefix}a/b/');
+ expectRelative('.', '${prefix}a', '${prefix}a');
+ // Trailing slashes in the inputs have no effect.
+ expectRelative('../../z/x/y', '${prefix}a/b/z/x/y', '${prefix}a/b/c/d/');
+ expectRelative('../../z/x/y', '${prefix}a/b/z/x/y', '${prefix}a/b/c/d');
+ expectRelative('../../z/x/y', '${prefix}a/b/z/x/y/', '${prefix}a/b/c/d');
+ expectRelative('../../../z/x/y', '${prefix}z/x/y', '${prefix}a/b/c');
+ expectRelative('../../../z/x/y', '${prefix}z/x/y', '${prefix}a/b/c/');
+
+ // Cases where the arguments are relative paths.
+ expectRelative('c/d', 'a/b/c/d', 'a/b');
+ expectRelative('.', 'a/b/c', 'a/b/c');
+ expectRelative('.', 'a/d/../b/c', 'a/b/c/');
+ expectRelative('.', '', '');
+ expectRelative('.', '.', '');
+ expectRelative('.', '', '.');
+ expectRelative('.', '.', '.');
+ expectRelative('.', '..', '..');
+ expectRelative('a', 'a', '');
+ expectRelative('a', 'a', '.');
+ expectRelative('..', '.', 'a');
+ expectRelative('.', 'a/b/f/../c', 'a/e/../b/c');
+ expectRelative('d', 'a/b/f/../c/d', 'a/e/../b/c');
+ expectRelative('..', 'a/b/f/../c', 'a/e/../b/c/e/');
+ expectRelative('../..', '', 'a/b/');
+ expectRelative('../b/c/d', 'b/c/d/', 'a/');
+ expectRelative('../a/b/c', 'x/y/a//b/./f/../c', 'x//y/z');
+
+ // Case where from is an exact substring of path.
+ expectRelative('a/b', '${prefix}x/y//a/b', '${prefix}x/y/');
+ expectRelative('a/b', 'x/y//a/b', 'x/y/');
+ expectRelative('../ya/b', '${prefix}x/ya/b', '${prefix}x/y');
+ expectRelative('../ya/b', 'x/ya/b', 'x/y');
+ expectRelative('../b', 'x/y/../b', 'x/y/.');
+ expectRelative('a/b/c', 'x/y/a//b/./f/../c', 'x/y');
+ expectRelative('.', '${prefix}x/y//', '${prefix}x/y/');
+ expectRelative('.', '${prefix}x/y/', '${prefix}x/y');
+
+ if (context.current == '.') {
+ group('current directory', () {
+ // Should always throw - no relative path can be constructed.
+ test('throws', () {
+ expect(() => context.relative('.', from: '..'), throwsPathException);
+ expect(
+ () => context.relative('a/b', from: '../../d'),
+ throwsPathException,
+ );
+ expect(
+ () => context.relative('a/b', from: '${prefix}a/b'),
+ throwsPathException,
+ );
+ });
+
+ expectRelative('..', '..', '.');
+ expectRelative('../../..', '..', 'a/b/');
+
+ // absolute path relative from a relative path returns the absolute path
+ expectRelative('${prefix}a/b', '${prefix}a/b', 'c/d');
+ });
+ }
+ });
+}
diff --git a/pkgs/path/test/url_test.dart b/pkgs/path/test/url_test.dart
new file mode 100644
index 0000000..df4e582
--- /dev/null
+++ b/pkgs/path/test/url_test.dart
@@ -0,0 +1,990 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:path/path.dart' as path;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ final context = path.Context(
+ style: path.Style.url, current: 'https://dart.dev/root/path');
+
+ test('separator', () {
+ expect(context.separator, '/');
+ });
+
+ test('extension', () {
+ expect(context.extension(''), '');
+ expect(context.extension('foo.dart'), '.dart');
+ expect(context.extension('foo.dart.js'), '.js');
+ expect(context.extension('a.b/c'), '');
+ expect(context.extension('a.b/c.d'), '.d');
+ expect(context.extension(r'a.b\c'), r'.b\c');
+ expect(context.extension('foo.dart/'), '.dart');
+ expect(context.extension('foo.dart//'), '.dart');
+ });
+
+ test('rootPrefix', () {
+ expect(context.rootPrefix(''), '');
+ expect(context.rootPrefix('a'), '');
+ expect(context.rootPrefix('a/b'), '');
+ expect(context.rootPrefix('https://dart.dev/a/c'), 'https://dart.dev');
+ expect(context.rootPrefix('file:///a/c'), 'file://');
+ expect(context.rootPrefix('/a/c'), '/');
+ expect(context.rootPrefix('https://dart.dev/'), 'https://dart.dev');
+ expect(context.rootPrefix('file:///'), 'file://');
+ expect(context.rootPrefix('https://dart.dev'), 'https://dart.dev');
+ expect(context.rootPrefix('file://'), 'file://');
+ expect(context.rootPrefix('/'), '/');
+ expect(context.rootPrefix('foo/bar://'), '');
+ expect(context.rootPrefix('package:foo/bar.dart'), 'package:foo');
+ expect(context.rootPrefix('foo/bar:baz/qux'), '');
+ });
+
+ test('dirname', () {
+ expect(context.dirname(''), '.');
+ expect(context.dirname('a'), '.');
+ expect(context.dirname('a/b'), 'a');
+ expect(context.dirname('a/b/c'), 'a/b');
+ expect(context.dirname('a/b.c'), 'a');
+ expect(context.dirname('a/'), '.');
+ expect(context.dirname('a/.'), 'a');
+ expect(context.dirname(r'a\b/c'), r'a\b');
+ expect(context.dirname('https://dart.dev/a'), 'https://dart.dev');
+ expect(context.dirname('file:///a'), 'file://');
+ expect(context.dirname('/a'), '/');
+ expect(context.dirname('https://dart.dev///a'), 'https://dart.dev');
+ expect(context.dirname('file://///a'), 'file://');
+ expect(context.dirname('///a'), '/');
+ expect(context.dirname('https://dart.dev/'), 'https://dart.dev');
+ expect(context.dirname('https://dart.dev'), 'https://dart.dev');
+ expect(context.dirname('file:///'), 'file://');
+ expect(context.dirname('file://'), 'file://');
+ expect(context.dirname('/'), '/');
+ expect(context.dirname('https://dart.dev///'), 'https://dart.dev');
+ expect(context.dirname('file://///'), 'file://');
+ expect(context.dirname('///'), '/');
+ expect(context.dirname('a/b/'), 'a');
+ expect(context.dirname(r'a/b\c'), 'a');
+ expect(context.dirname('a//'), '.');
+ expect(context.dirname('a/b//'), 'a');
+ expect(context.dirname('a//b'), 'a');
+ });
+
+ test('basename', () {
+ expect(context.basename(''), '');
+ expect(context.basename('a'), 'a');
+ expect(context.basename('a/b'), 'b');
+ expect(context.basename('a/b/c'), 'c');
+ expect(context.basename('a/b.c'), 'b.c');
+ expect(context.basename('a/'), 'a');
+ expect(context.basename('a/.'), '.');
+ expect(context.basename(r'a\b/c'), 'c');
+ expect(context.basename('https://dart.dev/a'), 'a');
+ expect(context.basename('file:///a'), 'a');
+ expect(context.basename('/a'), 'a');
+ expect(context.basename('https://dart.dev/'), 'https://dart.dev');
+ expect(context.basename('https://dart.dev'), 'https://dart.dev');
+ expect(context.basename('file:///'), 'file://');
+ expect(context.basename('file://'), 'file://');
+ expect(context.basename('/'), '/');
+ expect(context.basename('a/b/'), 'b');
+ expect(context.basename(r'a/b\c'), r'b\c');
+ expect(context.basename('a//'), 'a');
+ expect(context.basename('a/b//'), 'b');
+ expect(context.basename('a//b'), 'b');
+ expect(context.basename('a b/c d.e f'), 'c d.e f');
+ });
+
+ test('basenameWithoutExtension', () {
+ expect(context.basenameWithoutExtension(''), '');
+ expect(context.basenameWithoutExtension('.'), '.');
+ expect(context.basenameWithoutExtension('..'), '..');
+ expect(context.basenameWithoutExtension('a'), 'a');
+ expect(context.basenameWithoutExtension('a/b'), 'b');
+ expect(context.basenameWithoutExtension('a/b/c'), 'c');
+ expect(context.basenameWithoutExtension('a/b.c'), 'b');
+ expect(context.basenameWithoutExtension('a/'), 'a');
+ expect(context.basenameWithoutExtension('a/.'), '.');
+ expect(context.basenameWithoutExtension(r'a/b\c'), r'b\c');
+ expect(context.basenameWithoutExtension('a/.bashrc'), '.bashrc');
+ expect(context.basenameWithoutExtension('a/b/c.d.e'), 'c.d');
+ expect(context.basenameWithoutExtension('a//'), 'a');
+ expect(context.basenameWithoutExtension('a/b//'), 'b');
+ expect(context.basenameWithoutExtension('a//b'), 'b');
+ expect(context.basenameWithoutExtension('a/b.c/'), 'b');
+ expect(context.basenameWithoutExtension('a/b.c//'), 'b');
+ expect(context.basenameWithoutExtension('a/b c.d e.f g'), 'b c.d e');
+ });
+
+ test('isAbsolute', () {
+ expect(context.isAbsolute(''), false);
+ expect(context.isAbsolute('a'), false);
+ expect(context.isAbsolute('a/b'), false);
+ expect(context.isAbsolute('https://dart.dev/a'), true);
+ expect(context.isAbsolute('file:///a'), true);
+ expect(context.isAbsolute('/a'), true);
+ expect(context.isAbsolute('https://dart.dev/a/b'), true);
+ expect(context.isAbsolute('file:///a/b'), true);
+ expect(context.isAbsolute('/a/b'), true);
+ expect(context.isAbsolute('https://dart.dev/'), true);
+ expect(context.isAbsolute('file:///'), true);
+ expect(context.isAbsolute('https://dart.dev'), true);
+ expect(context.isAbsolute('file://'), true);
+ expect(context.isAbsolute('/'), true);
+ expect(context.isAbsolute('~'), false);
+ expect(context.isAbsolute('.'), false);
+ expect(context.isAbsolute('../a'), false);
+ expect(context.isAbsolute('C:/a'), true);
+ expect(context.isAbsolute(r'C:\a'), true);
+ expect(context.isAbsolute('package:foo/bar.dart'), true);
+ expect(context.isAbsolute('foo/bar:baz/qux'), false);
+ expect(context.isAbsolute(r'\\a'), false);
+ });
+
+ test('isRelative', () {
+ expect(context.isRelative(''), true);
+ expect(context.isRelative('a'), true);
+ expect(context.isRelative('a/b'), true);
+ expect(context.isRelative('https://dart.dev/a'), false);
+ expect(context.isRelative('file:///a'), false);
+ expect(context.isRelative('/a'), false);
+ expect(context.isRelative('https://dart.dev/a/b'), false);
+ expect(context.isRelative('file:///a/b'), false);
+ expect(context.isRelative('/a/b'), false);
+ expect(context.isRelative('https://dart.dev/'), false);
+ expect(context.isRelative('file:///'), false);
+ expect(context.isRelative('https://dart.dev'), false);
+ expect(context.isRelative('file://'), false);
+ expect(context.isRelative('/'), false);
+ expect(context.isRelative('~'), true);
+ expect(context.isRelative('.'), true);
+ expect(context.isRelative('../a'), true);
+ expect(context.isRelative('C:/a'), false);
+ expect(context.isRelative(r'C:\a'), false);
+ expect(context.isRelative(r'package:foo/bar.dart'), false);
+ expect(context.isRelative('foo/bar:baz/qux'), true);
+ expect(context.isRelative(r'\\a'), true);
+ });
+
+ test('isRootRelative', () {
+ expect(context.isRootRelative(''), false);
+ expect(context.isRootRelative('a'), false);
+ expect(context.isRootRelative('a/b'), false);
+ expect(context.isRootRelative('https://dart.dev/a'), false);
+ expect(context.isRootRelative('file:///a'), false);
+ expect(context.isRootRelative('/a'), true);
+ expect(context.isRootRelative('https://dart.dev/a/b'), false);
+ expect(context.isRootRelative('file:///a/b'), false);
+ expect(context.isRootRelative('/a/b'), true);
+ expect(context.isRootRelative('https://dart.dev/'), false);
+ expect(context.isRootRelative('file:///'), false);
+ expect(context.isRootRelative('https://dart.dev'), false);
+ expect(context.isRootRelative('file://'), false);
+ expect(context.isRootRelative('/'), true);
+ expect(context.isRootRelative('~'), false);
+ expect(context.isRootRelative('.'), false);
+ expect(context.isRootRelative('../a'), false);
+ expect(context.isRootRelative('C:/a'), false);
+ expect(context.isRootRelative(r'C:\a'), false);
+ expect(context.isRootRelative(r'package:foo/bar.dart'), false);
+ expect(context.isRootRelative('foo/bar:baz/qux'), false);
+ expect(context.isRootRelative(r'\\a'), false);
+ });
+
+ group('join', () {
+ test('allows up to sixteen parts', () {
+ expect(context.join('a'), 'a');
+ expect(context.join('a', 'b'), 'a/b');
+ expect(context.join('a', 'b', 'c'), 'a/b/c');
+ expect(context.join('a', 'b', 'c', 'd'), 'a/b/c/d');
+ expect(context.join('a', 'b', 'c', 'd', 'e'), 'a/b/c/d/e');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f'), 'a/b/c/d/e/f');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f', 'g'), 'a/b/c/d/e/f/g');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'),
+ 'a/b/c/d/e/f/g/h');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'),
+ 'a/b/c/d/e/f/g/h/i');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'),
+ 'a/b/c/d/e/f/g/h/i/j');
+ expect(
+ context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'),
+ 'a/b/c/d/e/f/g/h/i/j/k');
+ expect(
+ context.join(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'),
+ 'a/b/c/d/e/f/g/h/i/j/k/l');
+ expect(
+ context.join(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'),
+ 'a/b/c/d/e/f/g/h/i/j/k/l/m');
+ expect(
+ context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
+ 'l', 'm', 'n'),
+ 'a/b/c/d/e/f/g/h/i/j/k/l/m/n');
+ expect(
+ context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
+ 'l', 'm', 'n', 'o'),
+ 'a/b/c/d/e/f/g/h/i/j/k/l/m/n/o');
+ expect(
+ context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
+ 'l', 'm', 'n', 'o', 'p'),
+ 'a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p');
+ });
+
+ test('does not add separator if a part ends in one', () {
+ expect(context.join('a/', 'b', 'c/', 'd'), 'a/b/c/d');
+ expect(context.join('a\\', 'b'), r'a\/b');
+ });
+
+ test('ignores parts before an absolute path', () {
+ expect(context.join('a', 'https://dart.dev', 'b', 'c'),
+ 'https://dart.dev/b/c');
+ expect(context.join('a', 'file://', 'b', 'c'), 'file:///b/c');
+ expect(context.join('a', '/', 'b', 'c'), '/b/c');
+ expect(context.join('a', '/b', 'https://dart.dev/c', 'd'),
+ 'https://dart.dev/c/d');
+ expect(
+ context.join('a', 'http://google.com/b', 'https://dart.dev/c', 'd'),
+ 'https://dart.dev/c/d');
+ expect(context.join('a', '/b', '/c', 'd'), '/c/d');
+ expect(context.join('a', r'c:\b', 'c', 'd'), r'c:\b/c/d');
+ expect(context.join('a', 'package:foo/bar', 'c', 'd'),
+ r'package:foo/bar/c/d');
+ expect(context.join('a', r'\\b', 'c', 'd'), r'a/\\b/c/d');
+ });
+
+ test('preserves roots before a root-relative path', () {
+ expect(context.join('https://dart.dev', 'a', '/b', 'c'),
+ 'https://dart.dev/b/c');
+ expect(context.join('file://', 'a', '/b', 'c'), 'file:///b/c');
+ expect(context.join('file://', 'a', '/b', 'c', '/d'), 'file:///d');
+ expect(context.join('package:foo/bar.dart', '/baz.dart'),
+ 'package:foo/baz.dart');
+ });
+
+ test('ignores trailing nulls', () {
+ expect(context.join('a', null), equals('a'));
+ expect(context.join('a', 'b', 'c', null, null), equals('a/b/c'));
+ });
+
+ test('ignores empty strings', () {
+ expect(context.join(''), '');
+ expect(context.join('', ''), '');
+ expect(context.join('', 'a'), 'a');
+ expect(context.join('a', '', 'b', '', '', '', 'c'), 'a/b/c');
+ expect(context.join('a', 'b', ''), 'a/b');
+ });
+
+ test('disallows intermediate nulls', () {
+ expect(() => context.join('a', null, 'b'), throwsArgumentError);
+ });
+
+ test('does not modify internal ., .., or trailing separators', () {
+ expect(context.join('a/', 'b/c/'), 'a/b/c/');
+ expect(context.join('a/b/./c/..//', 'd/.././..//e/f//'),
+ 'a/b/./c/..//d/.././..//e/f//');
+ expect(context.join('a/b', 'c/../../../..'), 'a/b/c/../../../..');
+ expect(context.join('a', 'b${context.separator}'), 'a/b/');
+ });
+
+ test('treats drive letters as part of the root for file: URLs', () {
+ expect(
+ context.join('file:///c:/foo/bar', '/baz/qux'), 'file:///c:/baz/qux');
+ expect(
+ context.join('file:///D:/foo/bar', '/baz/qux'), 'file:///D:/baz/qux');
+ expect(context.join('file:///c:/', '/baz/qux'), 'file:///c:/baz/qux');
+ expect(context.join('file:///c:', '/baz/qux'), 'file:///c:/baz/qux');
+ expect(context.join('file://host/c:/foo/bar', '/baz/qux'),
+ 'file://host/c:/baz/qux');
+ });
+
+ test(
+ 'treats drive letters as part of the root for file: URLs '
+ 'with encoded colons', () {
+ expect(context.join('file:///c%3A/foo/bar', '/baz/qux'),
+ 'file:///c%3A/baz/qux');
+ expect(context.join('file:///D%3A/foo/bar', '/baz/qux'),
+ 'file:///D%3A/baz/qux');
+ expect(context.join('file:///c%3A/', '/baz/qux'), 'file:///c%3A/baz/qux');
+ expect(context.join('file:///c%3A', '/baz/qux'), 'file:///c%3A/baz/qux');
+ expect(context.join('file://host/c%3A/foo/bar', '/baz/qux'),
+ 'file://host/c%3A/baz/qux');
+ });
+
+ test('treats drive letters as normal components for non-file: URLs', () {
+ expect(context.join('http://foo.com/c:/foo/bar', '/baz/qux'),
+ 'http://foo.com/baz/qux');
+ expect(context.join('misfile:///c:/foo/bar', '/baz/qux'),
+ 'misfile:///baz/qux');
+ expect(
+ context.join('filer:///c:/foo/bar', '/baz/qux'), 'filer:///baz/qux');
+ });
+ });
+
+ group('joinAll', () {
+ test('allows more than sixteen parts', () {
+ expect(
+ context.joinAll([
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i',
+ 'j',
+ 'k',
+ 'l',
+ 'm',
+ 'n',
+ 'o',
+ 'p',
+ 'q'
+ ]),
+ 'a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q');
+ });
+
+ test('ignores parts before an absolute path', () {
+ expect(context.joinAll(['a', 'https://dart.dev', 'b', 'c']),
+ 'https://dart.dev/b/c');
+ expect(context.joinAll(['a', 'file://', 'b', 'c']), 'file:///b/c');
+ expect(context.joinAll(['a', '/', 'b', 'c']), '/b/c');
+ expect(context.joinAll(['a', '/b', 'https://dart.dev/c', 'd']),
+ 'https://dart.dev/c/d');
+ expect(
+ context
+ .joinAll(['a', 'http://google.com/b', 'https://dart.dev/c', 'd']),
+ 'https://dart.dev/c/d');
+ expect(context.joinAll(['a', '/b', '/c', 'd']), '/c/d');
+ expect(context.joinAll(['a', r'c:\b', 'c', 'd']), r'c:\b/c/d');
+ expect(context.joinAll(['a', 'package:foo/bar', 'c', 'd']),
+ r'package:foo/bar/c/d');
+ expect(context.joinAll(['a', r'\\b', 'c', 'd']), r'a/\\b/c/d');
+ });
+
+ test('preserves roots before a root-relative path', () {
+ expect(context.joinAll(['https://dart.dev', 'a', '/b', 'c']),
+ 'https://dart.dev/b/c');
+ expect(context.joinAll(['file://', 'a', '/b', 'c']), 'file:///b/c');
+ expect(context.joinAll(['file://', 'a', '/b', 'c', '/d']), 'file:///d');
+ });
+ });
+
+ group('split', () {
+ test('simple cases', () {
+ expect(context.split(''), <String>[]);
+ expect(context.split('.'), ['.']);
+ expect(context.split('..'), ['..']);
+ expect(context.split('foo'), equals(['foo']));
+ expect(context.split('foo/bar.txt'), equals(['foo', 'bar.txt']));
+ expect(context.split('foo/bar/baz'), equals(['foo', 'bar', 'baz']));
+ expect(context.split('foo/../bar/./baz'),
+ equals(['foo', '..', 'bar', '.', 'baz']));
+ expect(context.split('foo//bar///baz'), equals(['foo', 'bar', 'baz']));
+ expect(context.split('foo/\\/baz'), equals(['foo', '\\', 'baz']));
+ expect(context.split('.'), equals(['.']));
+ expect(context.split(''), equals([]));
+ expect(context.split('foo/'), equals(['foo']));
+ expect(context.split('https://dart.dev//'), equals(['https://dart.dev']));
+ expect(context.split('file:////'), equals(['file://']));
+ expect(context.split('//'), equals(['/']));
+ });
+
+ test('includes the root for absolute paths', () {
+ expect(context.split('https://dart.dev/foo/bar/baz'),
+ equals(['https://dart.dev', 'foo', 'bar', 'baz']));
+ expect(context.split('file:///foo/bar/baz'),
+ equals(['file://', 'foo', 'bar', 'baz']));
+ expect(context.split('/foo/bar/baz'), equals(['/', 'foo', 'bar', 'baz']));
+ expect(context.split('https://dart.dev/'), equals(['https://dart.dev']));
+ expect(context.split('https://dart.dev'), equals(['https://dart.dev']));
+ expect(context.split('file:///'), equals(['file://']));
+ expect(context.split('file://'), equals(['file://']));
+ expect(context.split('/'), equals(['/']));
+ });
+ });
+
+ group('normalize', () {
+ test('simple cases', () {
+ expect(context.normalize(''), '.');
+ expect(context.normalize('.'), '.');
+ expect(context.normalize('..'), '..');
+ expect(context.normalize('a'), 'a');
+ expect(context.normalize('https://dart.dev/'), 'https://dart.dev');
+ expect(context.normalize('https://dart.dev'), 'https://dart.dev');
+ expect(context.normalize('file://'), 'file://');
+ expect(context.normalize('file:///'), 'file://');
+ expect(context.normalize('/'), '/');
+ expect(context.normalize(r'\'), r'\');
+ expect(context.normalize('C:/'), 'C:');
+ expect(context.normalize(r'C:\'), r'C:\');
+ expect(context.normalize(r'\\'), r'\\');
+ expect(context.normalize('a/./\xc5\u0bf8-;\u{1f085}\u{00}/c/d/../'),
+ 'a/\xc5\u0bf8-;\u{1f085}\u{00}/c');
+ });
+
+ test('collapses redundant separators', () {
+ expect(context.normalize(r'a/b/c'), r'a/b/c');
+ expect(context.normalize(r'a//b///c////d'), r'a/b/c/d');
+ });
+
+ test('does not collapse separators for other platform', () {
+ expect(context.normalize(r'a\\b\\\c'), r'a\\b\\\c');
+ });
+
+ test('eliminates "." parts', () {
+ expect(context.normalize('./'), '.');
+ expect(context.normalize('https://dart.dev/.'), 'https://dart.dev');
+ expect(context.normalize('file:///.'), 'file://');
+ expect(context.normalize('/.'), '/');
+ expect(context.normalize('https://dart.dev/./'), 'https://dart.dev');
+ expect(context.normalize('file:///./'), 'file://');
+ expect(context.normalize('/./'), '/');
+ expect(context.normalize('./.'), '.');
+ expect(context.normalize('a/./b'), 'a/b');
+ expect(context.normalize('a/.b/c'), 'a/.b/c');
+ expect(context.normalize('a/././b/./c'), 'a/b/c');
+ expect(context.normalize('././a'), 'a');
+ expect(context.normalize('a/./.'), 'a');
+ });
+
+ test('eliminates ".." parts', () {
+ expect(context.normalize('..'), '..');
+ expect(context.normalize('../'), '..');
+ expect(context.normalize('../../..'), '../../..');
+ expect(context.normalize('../../../'), '../../..');
+ expect(context.normalize('https://dart.dev/..'), 'https://dart.dev');
+ expect(context.normalize('file:///..'), 'file://');
+ expect(context.normalize('/..'), '/');
+ expect(
+ context.normalize('https://dart.dev/../../..'), 'https://dart.dev');
+ expect(context.normalize('file:///../../..'), 'file://');
+ expect(context.normalize('/../../..'), '/');
+ expect(context.normalize('https://dart.dev/../../../a'),
+ 'https://dart.dev/a');
+ expect(context.normalize('file:///../../../a'), 'file:///a');
+ expect(context.normalize('/../../../a'), '/a');
+ expect(context.normalize('c:/..'), 'c:');
+ expect(context.normalize('package:foo/..'), 'package:foo');
+ expect(context.normalize('A:/../../..'), 'A:');
+ expect(context.normalize('a/..'), '.');
+ expect(context.normalize('a/b/..'), 'a');
+ expect(context.normalize('a/../b'), 'b');
+ expect(context.normalize('a/./../b'), 'b');
+ expect(context.normalize('a/b/c/../../d/e/..'), 'a/d');
+ expect(context.normalize('a/b/../../../../c'), '../../c');
+ expect(context.normalize('z/a/b/../../..../c'), 'z/..../c');
+ expect(context.normalize('a/bc/../d'), 'a/d');
+ });
+
+ test('does not walk before root on absolute paths', () {
+ expect(context.normalize('..'), '..');
+ expect(context.normalize('../'), '..');
+ expect(context.normalize('https://dart.dev/..'), 'https://dart.dev');
+ expect(context.normalize('https://dart.dev/../a'), 'https://dart.dev/a');
+ expect(context.normalize('file:///..'), 'file://');
+ expect(context.normalize('file:///../a'), 'file:///a');
+ expect(context.normalize('/..'), '/');
+ expect(context.normalize('a/..'), '.');
+ expect(context.normalize('../a'), '../a');
+ expect(context.normalize('/../a'), '/a');
+ expect(context.normalize('c:/../a'), 'c:/a');
+ expect(context.normalize('package:foo/../a'), 'package:foo/a');
+ expect(context.normalize('/../a'), '/a');
+ expect(context.normalize('a/b/..'), 'a');
+ expect(context.normalize('../a/b/..'), '../a');
+ expect(context.normalize('a/../b'), 'b');
+ expect(context.normalize('a/./../b'), 'b');
+ expect(context.normalize('a/b/c/../../d/e/..'), 'a/d');
+ expect(context.normalize('a/b/../../../../c'), '../../c');
+ expect(context.normalize('a/b/c/../../..d/./.e/f././'), 'a/..d/.e/f.');
+ });
+
+ test('removes trailing separators', () {
+ expect(context.normalize('./'), '.');
+ expect(context.normalize('.//'), '.');
+ expect(context.normalize('a/'), 'a');
+ expect(context.normalize('a/b/'), 'a/b');
+ expect(context.normalize(r'a/b\'), r'a/b\');
+ expect(context.normalize('a/b///'), 'a/b');
+ });
+
+ test('when canonicalizing', () {
+ expect(context.canonicalize('.'), 'https://dart.dev/root/path');
+ expect(context.canonicalize('foo/bar'),
+ 'https://dart.dev/root/path/foo/bar');
+ expect(context.canonicalize('FoO'), 'https://dart.dev/root/path/FoO');
+ expect(context.canonicalize('/foo'), 'https://dart.dev/foo');
+ expect(context.canonicalize('http://google.com/foo'),
+ 'http://google.com/foo');
+ });
+ });
+
+ group('relative', () {
+ group('from absolute root', () {
+ test('given absolute path in root', () {
+ expect(context.relative('https://dart.dev'), '../..');
+ expect(context.relative('https://dart.dev/'), '../..');
+ expect(context.relative('/'), '../..');
+ expect(context.relative('https://dart.dev/root'), '..');
+ expect(context.relative('/root'), '..');
+ expect(context.relative('https://dart.dev/root/path'), '.');
+ expect(context.relative('/root/path'), '.');
+ expect(context.relative('https://dart.dev/root/path/a'), 'a');
+ expect(context.relative('/root/path/a'), 'a');
+ expect(
+ context.relative('https://dart.dev/root/path/a/b.txt'), 'a/b.txt');
+ expect(context.relative('/root/path/a/b.txt'), 'a/b.txt');
+ expect(context.relative('https://dart.dev/root/a/b.txt'), '../a/b.txt');
+ expect(context.relative('/root/a/b.txt'), '../a/b.txt');
+ });
+
+ test('given absolute path outside of root', () {
+ expect(context.relative('https://dart.dev/a/b'), '../../a/b');
+ expect(context.relative('/a/b'), '../../a/b');
+ expect(context.relative('https://dart.dev/root/path/a'), 'a');
+ expect(context.relative('/root/path/a'), 'a');
+ expect(
+ context.relative('https://dart.dev/root/path/a/b.txt'), 'a/b.txt');
+ expect(
+ context.relative('https://dart.dev/root/path/a/b.txt'), 'a/b.txt');
+ expect(context.relative('https://dart.dev/root/a/b.txt'), '../a/b.txt');
+ });
+
+ test('given absolute path with different hostname/protocol', () {
+ expect(context.relative(r'http://google.com/a/b'),
+ r'http://google.com/a/b');
+ expect(context.relative(r'file:///a/b'), r'file:///a/b');
+ });
+
+ test('given relative path', () {
+ // The path is considered relative to the root, so it basically just
+ // normalizes.
+ expect(context.relative(''), '.');
+ expect(context.relative('.'), '.');
+ expect(context.relative('a'), 'a');
+ expect(context.relative('a/b.txt'), 'a/b.txt');
+ expect(context.relative('../a/b.txt'), '../a/b.txt');
+ expect(context.relative('a/./b/../c.txt'), 'a/c.txt');
+ });
+
+ test('is case-sensitive', () {
+ expect(
+ context.relative('HtTps://dart.dev/root'), 'HtTps://dart.dev/root');
+ expect(
+ context.relative('https://DaRt.DeV/root'), 'https://DaRt.DeV/root');
+ expect(context.relative('/RoOt'), '../../RoOt');
+ expect(context.relative('/rOoT/pAtH/a'), '../../rOoT/pAtH/a');
+ });
+
+ // Regression
+ test('from root-only path', () {
+ expect(context.relative('https://dart.dev', from: 'https://dart.dev'),
+ '.');
+ expect(
+ context.relative('https://dart.dev/root/path',
+ from: 'https://dart.dev'),
+ 'root/path');
+ });
+ });
+
+ group('from relative root', () {
+ final r = path.Context(style: path.Style.url, current: 'foo/bar');
+
+ test('given absolute path', () {
+ expect(r.relative('http://google.com/'), equals('http://google.com'));
+ expect(r.relative('http://google.com'), equals('http://google.com'));
+ expect(r.relative('file:///'), equals('file://'));
+ expect(r.relative('file://'), equals('file://'));
+ expect(r.relative('/'), equals('/'));
+ expect(r.relative('/a/b'), equals('/a/b'));
+ });
+
+ test('given relative path', () {
+ // The path is considered relative to the root, so it basically just
+ // normalizes.
+ expect(r.relative(''), '.');
+ expect(r.relative('.'), '.');
+ expect(r.relative('..'), '..');
+ expect(r.relative('a'), 'a');
+ expect(r.relative('a/b.txt'), 'a/b.txt');
+ expect(r.relative('../a/b.txt'), '../a/b.txt');
+ expect(r.relative('a/./b/../c.txt'), 'a/c.txt');
+ });
+ });
+
+ group('from root-relative root', () {
+ final r = path.Context(style: path.Style.url, current: '/foo/bar');
+
+ test('given absolute path', () {
+ expect(r.relative('http://google.com/'), equals('http://google.com'));
+ expect(r.relative('http://google.com'), equals('http://google.com'));
+ expect(r.relative('file:///'), equals('file://'));
+ expect(r.relative('file://'), equals('file://'));
+ expect(r.relative('/'), equals('../..'));
+ expect(r.relative('/a/b'), equals('../../a/b'));
+ });
+
+ test('given relative path', () {
+ // The path is considered relative to the root, so it basically just
+ // normalizes.
+ expect(r.relative(''), '.');
+ expect(r.relative('.'), '.');
+ expect(r.relative('..'), '..');
+ expect(r.relative('a'), 'a');
+ expect(r.relative('a/b.txt'), 'a/b.txt');
+ expect(r.relative('../a/b.txt'), '../a/b.txt');
+ expect(r.relative('a/./b/../c.txt'), 'a/c.txt');
+ });
+ });
+
+ test('from a root with extension', () {
+ final r = path.Context(style: path.Style.url, current: '/dir.ext');
+ expect(r.relative('/dir.ext/file'), 'file');
+ });
+
+ test('with a root parameter', () {
+ expect(context.relative('/foo/bar/baz', from: '/foo/bar'), equals('baz'));
+ expect(context.relative('/foo/bar/baz', from: 'https://dart.dev/foo/bar'),
+ equals('baz'));
+ expect(context.relative('https://dart.dev/foo/bar/baz', from: '/foo/bar'),
+ equals('baz'));
+ expect(
+ context.relative('https://dart.dev/foo/bar/baz',
+ from: 'file:///foo/bar'),
+ equals('https://dart.dev/foo/bar/baz'));
+ expect(
+ context.relative('https://dart.dev/foo/bar/baz',
+ from: 'https://dart.dev/foo/bar'),
+ equals('baz'));
+ expect(context.relative('/foo/bar/baz', from: 'file:///foo/bar'),
+ equals('https://dart.dev/foo/bar/baz'));
+ expect(context.relative('file:///foo/bar/baz', from: '/foo/bar'),
+ equals('file:///foo/bar/baz'));
+
+ expect(context.relative('..', from: '/foo/bar'), equals('../../root'));
+ expect(context.relative('..', from: 'https://dart.dev/foo/bar'),
+ equals('../../root'));
+ expect(context.relative('..', from: 'file:///foo/bar'),
+ equals('https://dart.dev/root'));
+ expect(context.relative('..', from: '/foo/bar'), equals('../../root'));
+
+ expect(context.relative('https://dart.dev/foo/bar/baz', from: 'foo/bar'),
+ equals('../../../../foo/bar/baz'));
+ expect(context.relative('file:///foo/bar/baz', from: 'foo/bar'),
+ equals('file:///foo/bar/baz'));
+ expect(context.relative('/foo/bar/baz', from: 'foo/bar'),
+ equals('../../../../foo/bar/baz'));
+
+ expect(context.relative('..', from: 'foo/bar'), equals('../../..'));
+ });
+
+ test('with a root parameter and a relative root', () {
+ final r = path.Context(style: path.Style.url, current: 'relative/root');
+ expect(r.relative('/foo/bar/baz', from: '/foo/bar'), equals('baz'));
+ expect(r.relative('/foo/bar/baz', from: 'https://dart.dev/foo/bar'),
+ equals('/foo/bar/baz'));
+ expect(r.relative('https://dart.dev/foo/bar/baz', from: '/foo/bar'),
+ equals('https://dart.dev/foo/bar/baz'));
+ expect(
+ r.relative('https://dart.dev/foo/bar/baz', from: 'file:///foo/bar'),
+ equals('https://dart.dev/foo/bar/baz'));
+ expect(
+ r.relative('https://dart.dev/foo/bar/baz',
+ from: 'https://dart.dev/foo/bar'),
+ equals('baz'));
+
+ expect(r.relative('https://dart.dev/foo/bar/baz', from: 'foo/bar'),
+ equals('https://dart.dev/foo/bar/baz'));
+ expect(r.relative('file:///foo/bar/baz', from: 'foo/bar'),
+ equals('file:///foo/bar/baz'));
+ expect(
+ r.relative('/foo/bar/baz', from: 'foo/bar'), equals('/foo/bar/baz'));
+
+ expect(r.relative('..', from: 'foo/bar'), equals('../../..'));
+ });
+
+ test('from a . root', () {
+ final r = path.Context(style: path.Style.url, current: '.');
+ expect(r.relative('https://dart.dev/foo/bar/baz'),
+ equals('https://dart.dev/foo/bar/baz'));
+ expect(r.relative('file:///foo/bar/baz'), equals('file:///foo/bar/baz'));
+ expect(r.relative('/foo/bar/baz'), equals('/foo/bar/baz'));
+ expect(r.relative('foo/bar/baz'), equals('foo/bar/baz'));
+ });
+ });
+
+ group('isWithin', () {
+ test('simple cases', () {
+ expect(context.isWithin('foo/bar', 'foo/bar'), isFalse);
+ expect(context.isWithin('foo/bar', 'foo/bar/baz'), isTrue);
+ expect(context.isWithin('foo/bar', 'foo/baz'), isFalse);
+ expect(context.isWithin('foo/bar', '../path/foo/bar/baz'), isTrue);
+ expect(context.isWithin('https://dart.dev', 'https://dart.dev/foo/bar'),
+ isTrue);
+ expect(
+ context.isWithin('https://dart.dev', 'http://psub.dart.dev/foo/bar'),
+ isFalse);
+ expect(context.isWithin('https://dart.dev', '/foo/bar'), isTrue);
+ expect(context.isWithin('https://dart.dev/foo', '/foo/bar'), isTrue);
+ expect(context.isWithin('https://dart.dev/foo', '/bar/baz'), isFalse);
+ expect(context.isWithin('baz', 'https://dart.dev/root/path/baz/bang'),
+ isTrue);
+ expect(context.isWithin('baz', 'https://dart.dev/root/path/bang/baz'),
+ isFalse);
+ });
+
+ test('complex cases', () {
+ expect(context.isWithin('foo/./bar', 'foo/bar/baz'), isTrue);
+ expect(context.isWithin('foo//bar', 'foo/bar/baz'), isTrue);
+ expect(context.isWithin('foo/qux/../bar', 'foo/bar/baz'), isTrue);
+ expect(context.isWithin('foo/bar', 'foo/bar/baz/../..'), isFalse);
+ expect(context.isWithin('foo/bar', 'foo/bar///'), isFalse);
+ expect(context.isWithin('foo/.bar', 'foo/.bar/baz'), isTrue);
+ expect(context.isWithin('foo/./bar', 'foo/.bar/baz'), isFalse);
+ expect(context.isWithin('foo/..bar', 'foo/..bar/baz'), isTrue);
+ expect(context.isWithin('foo/bar', 'foo/bar/baz/..'), isFalse);
+ expect(context.isWithin('foo/bar', 'foo/bar/baz/../qux'), isTrue);
+ expect(context.isWithin('http://example.org/', 'http://example.com/foo'),
+ isFalse);
+ expect(context.isWithin('http://example.org/', 'https://dart.dev/foo'),
+ isFalse);
+ });
+
+ test('with root-relative paths', () {
+ expect(context.isWithin('/foo', 'https://dart.dev/foo/bar'), isTrue);
+ expect(context.isWithin('https://dart.dev/foo', '/foo/bar'), isTrue);
+ expect(context.isWithin('/root', 'foo/bar'), isTrue);
+ expect(context.isWithin('foo', '/root/path/foo/bar'), isTrue);
+ expect(context.isWithin('/foo', '/foo/bar'), isTrue);
+ });
+
+ test('from a relative root', () {
+ final r = path.Context(style: path.Style.url, current: 'foo/bar');
+ expect(r.isWithin('.', 'a/b/c'), isTrue);
+ expect(r.isWithin('.', '../a/b/c'), isFalse);
+ expect(r.isWithin('.', '../../a/foo/b/c'), isFalse);
+ expect(
+ r.isWithin('https://dart.dev/', 'https://dart.dev/baz/bang'), isTrue);
+ expect(r.isWithin('.', 'https://dart.dev/baz/bang'), isFalse);
+ });
+ });
+
+ group('equals and hash', () {
+ test('simple cases', () {
+ expectEquals(context, 'foo/bar', 'foo/bar');
+ expectNotEquals(context, 'foo/bar', 'foo/bar/baz');
+ expectNotEquals(context, 'foo/bar', 'foo');
+ expectNotEquals(context, 'foo/bar', 'foo/baz');
+ expectEquals(context, 'foo/bar', '../path/foo/bar');
+ expectEquals(context, 'http://google.com', 'http://google.com');
+ expectEquals(context, 'https://dart.dev', '../..');
+ expectEquals(context, 'baz', '/root/path/baz');
+ });
+
+ test('complex cases', () {
+ expectEquals(context, 'foo/./bar', 'foo/bar');
+ expectEquals(context, 'foo//bar', 'foo/bar');
+ expectEquals(context, 'foo/qux/../bar', 'foo/bar');
+ expectNotEquals(context, 'foo/qux/../bar', 'foo/qux');
+ expectNotEquals(context, 'foo/bar', 'foo/bar/baz/../..');
+ expectEquals(context, 'foo/bar', 'foo/bar///');
+ expectEquals(context, 'foo/.bar', 'foo/.bar');
+ expectNotEquals(context, 'foo/./bar', 'foo/.bar');
+ expectEquals(context, 'foo/..bar', 'foo/..bar');
+ expectNotEquals(context, 'foo/../bar', 'foo/..bar');
+ expectEquals(context, 'foo/bar', 'foo/bar/baz/..');
+ expectNotEquals(context, 'FoO/bAr', 'foo/bar');
+ expectEquals(context, 'http://google.com', 'http://google.com/');
+ expectEquals(context, 'https://dart.dev/root', '..');
+ });
+
+ test('with root-relative paths', () {
+ expectEquals(context, '/foo', 'https://dart.dev/foo');
+ expectNotEquals(context, '/foo', 'http://google.com/foo');
+ expectEquals(context, '/root/path/foo/bar', 'foo/bar');
+ });
+
+ test('from a relative root', () {
+ final r = path.Context(style: path.Style.posix, current: 'foo/bar');
+ expectEquals(r, 'a/b', 'a/b');
+ expectNotEquals(r, '.', 'foo/bar');
+ expectNotEquals(r, '.', '../a/b');
+ expectEquals(r, '.', '../bar');
+ expectEquals(r, '/baz/bang', '/baz/bang');
+ expectNotEquals(r, 'baz/bang', '/baz/bang');
+ });
+ });
+
+ group('absolute', () {
+ test('allows up to fifteen parts', () {
+ expect(context.absolute('a'), 'https://dart.dev/root/path/a');
+ expect(context.absolute('a', 'b'), 'https://dart.dev/root/path/a/b');
+ expect(
+ context.absolute('a', 'b', 'c'), 'https://dart.dev/root/path/a/b/c');
+ expect(context.absolute('a', 'b', 'c', 'd'),
+ 'https://dart.dev/root/path/a/b/c/d');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e'),
+ 'https://dart.dev/root/path/a/b/c/d/e');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f'),
+ 'https://dart.dev/root/path/a/b/c/d/e/f');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g'),
+ 'https://dart.dev/root/path/a/b/c/d/e/f/g');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'),
+ 'https://dart.dev/root/path/a/b/c/d/e/f/g/h');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'),
+ 'https://dart.dev/root/path/a/b/c/d/e/f/g/h/i');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'),
+ 'https://dart.dev/root/path/a/b/c/d/e/f/g/h/i/j');
+ expect(
+ context.absolute(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'),
+ 'https://dart.dev/root/path/a/b/c/d/e/f/g/h/i/j/k');
+ expect(
+ context.absolute(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'),
+ 'https://dart.dev/root/path/a/b/c/d/e/f/g/h/i/j/k/l');
+ expect(
+ context.absolute(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'),
+ 'https://dart.dev/root/path/a/b/c/d/e/f/g/h/i/j/k/l/m');
+ expect(
+ context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n'),
+ 'https://dart.dev/root/path/a/b/c/d/e/f/g/h/i/j/k/l/m/n');
+ expect(
+ context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n', 'o'),
+ 'https://dart.dev/root/path/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o');
+ });
+
+ test('does not add separator if a part ends in one', () {
+ expect(context.absolute('a/', 'b', 'c/', 'd'),
+ 'https://dart.dev/root/path/a/b/c/d');
+ expect(context.absolute(r'a\', 'b'), r'https://dart.dev/root/path/a\/b');
+ });
+
+ test('ignores parts before an absolute path', () {
+ expect(context.absolute('a', '/b', '/c', 'd'), 'https://dart.dev/c/d');
+ expect(context.absolute('a', '/b', 'file:///c', 'd'), 'file:///c/d');
+ expect(context.absolute('a', r'c:\b', 'c', 'd'), r'c:\b/c/d');
+ expect(context.absolute('a', r'\\b', 'c', 'd'),
+ r'https://dart.dev/root/path/a/\\b/c/d');
+ });
+ });
+
+ test('withoutExtension', () {
+ expect(context.withoutExtension(''), '');
+ expect(context.withoutExtension('a'), 'a');
+ expect(context.withoutExtension('.a'), '.a');
+ expect(context.withoutExtension('a.b'), 'a');
+ expect(context.withoutExtension('a/b.c'), 'a/b');
+ expect(context.withoutExtension('a/b.c.d'), 'a/b.c');
+ expect(context.withoutExtension('a/'), 'a/');
+ expect(context.withoutExtension('a/b/'), 'a/b/');
+ expect(context.withoutExtension('a/.'), 'a/.');
+ expect(context.withoutExtension('a/.b'), 'a/.b');
+ expect(context.withoutExtension('a.b/c'), 'a.b/c');
+ expect(context.withoutExtension(r'a.b\c'), r'a');
+ expect(context.withoutExtension(r'a/b\c'), r'a/b\c');
+ expect(context.withoutExtension(r'a/b\c.d'), r'a/b\c');
+ expect(context.withoutExtension('a/b.c/'), 'a/b/');
+ expect(context.withoutExtension('a/b.c//'), 'a/b//');
+ });
+
+ test('setExtension', () {
+ expect(context.setExtension('', '.x'), '.x');
+ expect(context.setExtension('a', '.x'), 'a.x');
+ expect(context.setExtension('.a', '.x'), '.a.x');
+ expect(context.setExtension('a.b', '.x'), 'a.x');
+ expect(context.setExtension('a/b.c', '.x'), 'a/b.x');
+ expect(context.setExtension('a/b.c.d', '.x'), 'a/b.c.x');
+ expect(context.setExtension('a/', '.x'), 'a/.x');
+ expect(context.setExtension('a/b/', '.x'), 'a/b/.x');
+ expect(context.setExtension('a/.', '.x'), 'a/..x');
+ expect(context.setExtension('a/.b', '.x'), 'a/.b.x');
+ expect(context.setExtension('a.b/c', '.x'), 'a.b/c.x');
+ expect(context.setExtension(r'a.b\c', '.x'), r'a.x');
+ expect(context.setExtension(r'a/b\c', '.x'), r'a/b\c.x');
+ expect(context.setExtension(r'a/b\c.d', '.x'), r'a/b\c.x');
+ expect(context.setExtension('a/b.c/', '.x'), 'a/b/.x');
+ expect(context.setExtension('a/b.c//', '.x'), 'a/b//.x');
+ });
+
+ group('fromUri', () {
+ test('with a URI', () {
+ expect(context.fromUri(Uri.parse('https://dart.dev/path/to/foo')),
+ 'https://dart.dev/path/to/foo');
+ expect(context.fromUri(Uri.parse('https://dart.dev/path/to/foo/')),
+ 'https://dart.dev/path/to/foo/');
+ expect(context.fromUri(Uri.parse('file:///path/to/foo')),
+ 'file:///path/to/foo');
+ expect(context.fromUri(Uri.parse('foo/bar')), 'foo/bar');
+ expect(context.fromUri(Uri.parse('https://dart.dev/path/to/foo%23bar')),
+ 'https://dart.dev/path/to/foo%23bar');
+ // Since the resulting "path" is also a URL, special characters should
+ // remain percent-encoded in the result.
+ expect(context.fromUri(Uri.parse('_%7B_%7D_%60_%5E_%20_%22_%25_')),
+ r'_%7B_%7D_%60_%5E_%20_%22_%25_');
+ });
+
+ test('with a string', () {
+ expect(context.fromUri('https://dart.dev/path/to/foo'),
+ 'https://dart.dev/path/to/foo');
+ });
+ });
+
+ test('toUri', () {
+ expect(context.toUri('https://dart.dev/path/to/foo'),
+ Uri.parse('https://dart.dev/path/to/foo'));
+ expect(context.toUri('https://dart.dev/path/to/foo/'),
+ Uri.parse('https://dart.dev/path/to/foo/'));
+ expect(context.toUri('path/to/foo/'), Uri.parse('path/to/foo/'));
+ expect(
+ context.toUri('file:///path/to/foo'), Uri.parse('file:///path/to/foo'));
+ expect(context.toUri('foo/bar'), Uri.parse('foo/bar'));
+ expect(context.toUri('https://dart.dev/path/to/foo%23bar'),
+ Uri.parse('https://dart.dev/path/to/foo%23bar'));
+ // Since the input path is also a URI, special characters should already
+ // be percent encoded there too.
+ expect(context.toUri(r'http://foo.com/_%7B_%7D_%60_%5E_%20_%22_%25_'),
+ Uri.parse('http://foo.com/_%7B_%7D_%60_%5E_%20_%22_%25_'));
+ expect(context.toUri(r'_%7B_%7D_%60_%5E_%20_%22_%25_'),
+ Uri.parse('_%7B_%7D_%60_%5E_%20_%22_%25_'));
+ expect(context.toUri(''), Uri.parse(''));
+ });
+
+ group('prettyUri', () {
+ test('with a file: URI', () {
+ expect(context.prettyUri(Uri.parse('file:///root/path/a/b')),
+ 'file:///root/path/a/b');
+ });
+
+ test('with an http: URI', () {
+ expect(context.prettyUri('https://dart.dev/root/path/a/b'), 'a/b');
+ expect(context.prettyUri('https://dart.dev/root/path/a/../b'), 'b');
+ expect(context.prettyUri('https://dart.dev/other/path/a/b'),
+ 'https://dart.dev/other/path/a/b');
+ expect(context.prettyUri('http://psub.dart.dev/root/path'),
+ 'http://psub.dart.dev/root/path');
+ expect(context.prettyUri('https://dart.dev/root/other'), '../other');
+ });
+
+ test('with a relative URI', () {
+ expect(context.prettyUri('a/b'), 'a/b');
+ });
+
+ test('with a root-relative URI', () {
+ expect(context.prettyUri('/a/b'), '/a/b');
+ });
+
+ test('with a Uri object', () {
+ expect(context.prettyUri(Uri.parse('a/b')), 'a/b');
+ });
+ });
+}
diff --git a/pkgs/path/test/utils.dart b/pkgs/path/test/utils.dart
new file mode 100644
index 0000000..08d4a52
--- /dev/null
+++ b/pkgs/path/test/utils.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+/// A matcher for a closure that throws a [p.PathException].
+final throwsPathException = throwsA(const TypeMatcher<p.PathException>());
+
+void expectEquals(p.Context context, String path1, String path2) {
+ expect(context.equals(path1, path2), isTrue,
+ reason: 'Expected "$path1" to equal "$path2".');
+ expect(context.equals(path2, path1), isTrue,
+ reason: 'Expected "$path2" to equal "$path1".');
+ expect(context.hash(path1), equals(context.hash(path2)),
+ reason: 'Expected "$path1" to hash the same as "$path2".');
+}
+
+void expectNotEquals(p.Context context, String path1, String path2,
+ {bool allowSameHash = false}) {
+ expect(context.equals(path1, path2), isFalse,
+ reason: 'Expected "$path1" not to equal "$path2".');
+ expect(context.equals(path2, path1), isFalse,
+ reason: 'Expected "$path2" not to equal "$path1".');
+
+ // Hash collisions are allowed, but the test author should be explicitly aware
+ // when they occur.
+ if (allowSameHash) return;
+ expect(context.hash(path1), isNot(equals(context.hash(path2))),
+ reason: 'Expected "$path1" not to hash the same as "$path2".');
+}
diff --git a/pkgs/path/test/windows_test.dart b/pkgs/path/test/windows_test.dart
new file mode 100644
index 0000000..180f560
--- /dev/null
+++ b/pkgs/path/test/windows_test.dart
@@ -0,0 +1,906 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:path/path.dart' as path;
+import 'package:path/src/utils.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ final context =
+ path.Context(style: path.Style.windows, current: r'C:\root\path');
+
+ test('separator', () {
+ expect(context.separator, '\\');
+ });
+
+ test('extension', () {
+ expect(context.extension(''), '');
+ expect(context.extension('.'), '');
+ expect(context.extension('..'), '');
+ expect(context.extension('a/..'), '');
+ expect(context.extension('foo.dart'), '.dart');
+ expect(context.extension('foo.dart.js'), '.js');
+ expect(context.extension('foo bargule fisk.dart.js'), '.js');
+ expect(context.extension(r'a.b\c'), '');
+ expect(context.extension('a.b/c.d'), '.d');
+ expect(context.extension(r'~\.bashrc'), '');
+ expect(context.extension(r'a.b/c'), r'');
+ expect(context.extension(r'foo.dart\'), '.dart');
+ expect(context.extension(r'foo.dart\\'), '.dart');
+ expect(context.extension('a.b/..', 2), '');
+ expect(context.extension('foo.bar.dart.js', 2), '.dart.js');
+ expect(context.extension(r'foo.bar.dart.js', 3), '.bar.dart.js');
+ expect(context.extension(r'foo.bar.dart.js', 10), '.bar.dart.js');
+ expect(context.extension('a.b/c.d', 2), '.d');
+ expect(() => context.extension(r'foo.bar.dart.js', 0), throwsRangeError);
+ expect(() => context.extension(r'foo.bar.dart.js', -1), throwsRangeError);
+ });
+
+ test('rootPrefix', () {
+ expect(context.rootPrefix(''), '');
+ expect(context.rootPrefix('a'), '');
+ expect(context.rootPrefix(r'a\b'), '');
+ expect(context.rootPrefix(r'C:\a\c'), r'C:\');
+ expect(context.rootPrefix(r'C:\'), r'C:\');
+ expect(context.rootPrefix('C:/'), 'C:/');
+ expect(context.rootPrefix(r'\\server\share\a\b'), r'\\server\share');
+ expect(context.rootPrefix(r'\\server\share'), r'\\server\share');
+ expect(context.rootPrefix(r'\\server\'), r'\\server\');
+ expect(context.rootPrefix(r'\\server'), r'\\server');
+ expect(context.rootPrefix(r'\a\b'), r'\');
+ expect(context.rootPrefix(r'/a/b'), r'/');
+ expect(context.rootPrefix(r'\'), r'\');
+ expect(context.rootPrefix(r'/'), r'/');
+ });
+
+ test('dirname', () {
+ expect(context.dirname(r''), '.');
+ expect(context.dirname(r'a'), '.');
+ expect(context.dirname(r'a\b'), 'a');
+ expect(context.dirname(r'a\b\c'), r'a\b');
+ expect(context.dirname(r'a\b.c'), 'a');
+ expect(context.dirname(r'a\'), '.');
+ expect(context.dirname('a/'), '.');
+ expect(context.dirname(r'a\.'), 'a');
+ expect(context.dirname(r'a\b/c'), r'a\b');
+ expect(context.dirname(r'C:\a'), r'C:\');
+ expect(context.dirname(r'C:\\\a'), r'C:\');
+ expect(context.dirname(r'C:\'), r'C:\');
+ expect(context.dirname(r'C:\\\'), r'C:\');
+ expect(context.dirname(r'a\b\'), r'a');
+ expect(context.dirname(r'a/b\c'), 'a/b');
+ expect(context.dirname(r'a\\'), r'.');
+ expect(context.dirname(r'a\b\\'), 'a');
+ expect(context.dirname(r'a\\b'), 'a');
+ expect(context.dirname(r'foo bar\gule fisk'), 'foo bar');
+ expect(context.dirname(r'\\server\share'), r'\\server\share');
+ expect(context.dirname(r'\\server\share\dir'), r'\\server\share');
+ expect(context.dirname(r'\a'), r'\');
+ expect(context.dirname(r'/a'), r'/');
+ expect(context.dirname(r'\'), r'\');
+ expect(context.dirname(r'/'), r'/');
+ });
+
+ test('basename', () {
+ expect(context.basename(r''), '');
+ expect(context.basename(r'.'), '.');
+ expect(context.basename(r'..'), '..');
+ expect(context.basename(r'.hest'), '.hest');
+ expect(context.basename(r'a'), 'a');
+ expect(context.basename(r'a\b'), 'b');
+ expect(context.basename(r'a\b\c'), 'c');
+ expect(context.basename(r'a\b.c'), 'b.c');
+ expect(context.basename(r'a\'), 'a');
+ expect(context.basename(r'a/'), 'a');
+ expect(context.basename(r'a\.'), '.');
+ expect(context.basename(r'a\b/c'), r'c');
+ expect(context.basename(r'C:\a'), 'a');
+ expect(context.basename(r'C:\'), r'C:\');
+ expect(context.basename(r'a\b\'), 'b');
+ expect(context.basename(r'a/b\c'), 'c');
+ expect(context.basename(r'a\\'), 'a');
+ expect(context.basename(r'a\b\\'), 'b');
+ expect(context.basename(r'a\\b'), 'b');
+ expect(context.basename(r'a\\b'), 'b');
+ expect(context.basename(r'a\fisk hest.ma pa'), 'fisk hest.ma pa');
+ expect(context.basename(r'\\server\share'), r'\\server\share');
+ expect(context.basename(r'\\server\share\dir'), r'dir');
+ expect(context.basename(r'\a'), r'a');
+ expect(context.basename(r'/a'), r'a');
+ expect(context.basename(r'\'), r'\');
+ expect(context.basename(r'/'), r'/');
+ });
+
+ test('basenameWithoutExtension', () {
+ expect(context.basenameWithoutExtension(''), '');
+ expect(context.basenameWithoutExtension('.'), '.');
+ expect(context.basenameWithoutExtension('..'), '..');
+ expect(context.basenameWithoutExtension('.hest'), '.hest');
+ expect(context.basenameWithoutExtension('a'), 'a');
+ expect(context.basenameWithoutExtension(r'a\b'), 'b');
+ expect(context.basenameWithoutExtension(r'a\b\c'), 'c');
+ expect(context.basenameWithoutExtension(r'a\b.c'), 'b');
+ expect(context.basenameWithoutExtension(r'a\'), 'a');
+ expect(context.basenameWithoutExtension(r'a\.'), '.');
+ expect(context.basenameWithoutExtension(r'a\b/c'), r'c');
+ expect(context.basenameWithoutExtension(r'a\.bashrc'), '.bashrc');
+ expect(context.basenameWithoutExtension(r'a\b\c.d.e'), 'c.d');
+ expect(context.basenameWithoutExtension(r'a\\'), 'a');
+ expect(context.basenameWithoutExtension(r'a\b\\'), 'b');
+ expect(context.basenameWithoutExtension(r'a\\b'), 'b');
+ expect(context.basenameWithoutExtension(r'a\b.c\'), 'b');
+ expect(context.basenameWithoutExtension(r'a\b.c\\'), 'b');
+ expect(context.basenameWithoutExtension(r'C:\f h.ma pa.f s'), 'f h.ma pa');
+ });
+
+ test('isAbsolute', () {
+ expect(context.isAbsolute(''), false);
+ expect(context.isAbsolute('.'), false);
+ expect(context.isAbsolute('..'), false);
+ expect(context.isAbsolute('a'), false);
+ expect(context.isAbsolute(r'a\b'), false);
+ expect(context.isAbsolute(r'\a\b'), true);
+ expect(context.isAbsolute(r'\'), true);
+ expect(context.isAbsolute(r'/a/b'), true);
+ expect(context.isAbsolute(r'/'), true);
+ expect(context.isAbsolute('~'), false);
+ expect(context.isAbsolute('.'), false);
+ expect(context.isAbsolute(r'..\a'), false);
+ expect(context.isAbsolute(r'a:/a\b'), true);
+ expect(context.isAbsolute(r'D:/a/b'), true);
+ expect(context.isAbsolute(r'c:\'), true);
+ expect(context.isAbsolute(r'B:\'), true);
+ expect(context.isAbsolute(r'c:\a'), true);
+ expect(context.isAbsolute(r'C:\a'), true);
+ expect(context.isAbsolute(r'\\server\share'), true);
+ expect(context.isAbsolute(r'\\server\share\path'), true);
+ });
+
+ test('isRelative', () {
+ expect(context.isRelative(''), true);
+ expect(context.isRelative('.'), true);
+ expect(context.isRelative('..'), true);
+ expect(context.isRelative('a'), true);
+ expect(context.isRelative(r'a\b'), true);
+ expect(context.isRelative(r'\a\b'), false);
+ expect(context.isRelative(r'\'), false);
+ expect(context.isRelative(r'/a/b'), false);
+ expect(context.isRelative(r'/'), false);
+ expect(context.isRelative('~'), true);
+ expect(context.isRelative('.'), true);
+ expect(context.isRelative(r'..\a'), true);
+ expect(context.isRelative(r'a:/a\b'), false);
+ expect(context.isRelative(r'D:/a/b'), false);
+ expect(context.isRelative(r'c:\'), false);
+ expect(context.isRelative(r'B:\'), false);
+ expect(context.isRelative(r'c:\a'), false);
+ expect(context.isRelative(r'C:\a'), false);
+ expect(context.isRelative(r'\\server\share'), false);
+ expect(context.isRelative(r'\\server\share\path'), false);
+ });
+
+ test('isRootRelative', () {
+ expect(context.isRootRelative(''), false);
+ expect(context.isRootRelative('.'), false);
+ expect(context.isRootRelative('..'), false);
+ expect(context.isRootRelative('a'), false);
+ expect(context.isRootRelative(r'a\b'), false);
+ expect(context.isRootRelative(r'\a\b'), true);
+ expect(context.isRootRelative(r'\'), true);
+ expect(context.isRootRelative(r'/a/b'), true);
+ expect(context.isRootRelative(r'/'), true);
+ expect(context.isRootRelative('~'), false);
+ expect(context.isRootRelative('.'), false);
+ expect(context.isRootRelative(r'..\a'), false);
+ expect(context.isRootRelative(r'a:/a\b'), false);
+ expect(context.isRootRelative(r'D:/a/b'), false);
+ expect(context.isRootRelative(r'c:\'), false);
+ expect(context.isRootRelative(r'B:\'), false);
+ expect(context.isRootRelative(r'c:\a'), false);
+ expect(context.isRootRelative(r'C:\a'), false);
+ expect(context.isRootRelative(r'\\server\share'), false);
+ expect(context.isRootRelative(r'\\server\share\path'), false);
+ });
+
+ group('join', () {
+ test('allows up to sixteen parts', () {
+ expect(context.join('a'), 'a');
+ expect(context.join('a', 'b'), r'a\b');
+ expect(context.join('a', 'b', 'c'), r'a\b\c');
+ expect(context.join('a', 'b', 'c', 'd'), r'a\b\c\d');
+ expect(context.join('a', 'b', 'c', 'd', 'e'), r'a\b\c\d\e');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f'), r'a\b\c\d\e\f');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f', 'g'), r'a\b\c\d\e\f\g');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'),
+ r'a\b\c\d\e\f\g\h');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'),
+ r'a\b\c\d\e\f\g\h\i');
+ expect(context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'),
+ r'a\b\c\d\e\f\g\h\i\j');
+ expect(
+ context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'),
+ r'a\b\c\d\e\f\g\h\i\j\k');
+ expect(
+ context.join(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'),
+ r'a\b\c\d\e\f\g\h\i\j\k\l');
+ expect(
+ context.join(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'),
+ r'a\b\c\d\e\f\g\h\i\j\k\l\m');
+ expect(
+ context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
+ 'l', 'm', 'n'),
+ r'a\b\c\d\e\f\g\h\i\j\k\l\m\n');
+ expect(
+ context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
+ 'l', 'm', 'n', 'o'),
+ r'a\b\c\d\e\f\g\h\i\j\k\l\m\n\o');
+ expect(
+ context.join('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
+ 'l', 'm', 'n', 'o', 'p'),
+ r'a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p');
+ });
+
+ test('does not add separator if a part ends or begins in one', () {
+ expect(context.join(r'a\', 'b', r'c\', 'd'), r'a\b\c\d');
+ expect(context.join('a/', 'b'), r'a/b');
+ });
+
+ test('ignores parts before an absolute path', () {
+ expect(context.join('a', r'\b', r'\c', 'd'), r'\c\d');
+ expect(context.join('a', '/b', '/c', 'd'), r'/c\d');
+ expect(context.join('a', r'c:\b', 'c', 'd'), r'c:\b\c\d');
+ expect(context.join('a', r'\\b\c', r'\\d\e', 'f'), r'\\d\e\f');
+ expect(context.join('a', r'c:\b', r'\c', 'd'), r'c:\c\d');
+ expect(context.join('a', r'\\b\c\d', r'\e', 'f'), r'\\b\c\e\f');
+ });
+
+ test('ignores trailing nulls', () {
+ expect(context.join('a', null), equals('a'));
+ expect(context.join('a', 'b', 'c', null, null), equals(r'a\b\c'));
+ });
+
+ test('ignores empty strings', () {
+ expect(context.join(''), '');
+ expect(context.join('', ''), '');
+ expect(context.join('', 'a'), 'a');
+ expect(context.join('a', '', 'b', '', '', '', 'c'), r'a\b\c');
+ expect(context.join('a', 'b', ''), r'a\b');
+ });
+
+ test('disallows intermediate nulls', () {
+ expect(() => context.join('a', null, 'b'), throwsArgumentError);
+ });
+
+ test('join does not modify internal ., .., or trailing separators', () {
+ expect(context.join('a/', 'b/c/'), 'a/b/c/');
+ expect(context.join(r'a\b\./c\..\\', r'd\..\.\..\\e\f\\'),
+ r'a\b\./c\..\\d\..\.\..\\e\f\\');
+ expect(context.join(r'a\b', r'c\..\..\..\..'), r'a\b\c\..\..\..\..');
+ expect(context.join(r'a', 'b${context.separator}'), r'a\b\');
+ });
+ });
+
+ group('joinAll', () {
+ test('allows more than sixteen parts', () {
+ expect(
+ context.joinAll([
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i',
+ 'j',
+ 'k',
+ 'l',
+ 'm',
+ 'n',
+ 'o',
+ 'p',
+ 'q'
+ ]),
+ r'a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q');
+ });
+
+ test('does not add separator if a part ends or begins in one', () {
+ expect(context.joinAll([r'a\', 'b', r'c\', 'd']), r'a\b\c\d');
+ expect(context.joinAll(['a/', 'b']), r'a/b');
+ });
+
+ test('ignores parts before an absolute path', () {
+ expect(context.joinAll(['a', r'\b', r'\c', 'd']), r'\c\d');
+ expect(context.joinAll(['a', '/b', '/c', 'd']), r'/c\d');
+ expect(context.joinAll(['a', r'c:\b', 'c', 'd']), r'c:\b\c\d');
+ expect(context.joinAll(['a', r'\\b\c', r'\\d\e', 'f']), r'\\d\e\f');
+ expect(context.joinAll(['a', r'c:\b', r'\c', 'd']), r'c:\c\d');
+ expect(context.joinAll(['a', r'\\b\c\d', r'\e', 'f']), r'\\b\c\e\f');
+ });
+ });
+
+ group('split', () {
+ test('simple cases', () {
+ expect(context.split(''), <String>[]);
+ expect(context.split('.'), ['.']);
+ expect(context.split('..'), ['..']);
+ expect(context.split('foo'), equals(['foo']));
+ expect(context.split(r'foo\bar.txt'), equals(['foo', 'bar.txt']));
+ expect(context.split(r'foo\bar/baz'), equals(['foo', 'bar', 'baz']));
+ expect(context.split(r'foo\..\bar\.\baz'),
+ equals(['foo', '..', 'bar', '.', 'baz']));
+ expect(context.split(r'foo\\bar\\\baz'), equals(['foo', 'bar', 'baz']));
+ expect(context.split(r'foo\/\baz'), equals(['foo', 'baz']));
+ expect(context.split('.'), equals(['.']));
+ expect(context.split(''), equals([]));
+ expect(context.split('foo/'), equals(['foo']));
+ expect(context.split(r'C:\'), equals([r'C:\']));
+ });
+
+ test('includes the root for absolute paths', () {
+ expect(context.split(r'C:\foo\bar\baz'),
+ equals([r'C:\', 'foo', 'bar', 'baz']));
+ expect(context.split(r'C:\\'), equals([r'C:\']));
+
+ expect(context.split(r'\\server\share\foo\bar\baz'),
+ equals([r'\\server\share', 'foo', 'bar', 'baz']));
+ expect(context.split(r'\\server\share'), equals([r'\\server\share']));
+
+ expect(
+ context.split(r'\foo\bar\baz'), equals([r'\', 'foo', 'bar', 'baz']));
+ expect(context.split(r'\'), equals([r'\']));
+ });
+ });
+
+ group('normalize', () {
+ test('simple cases', () {
+ expect(context.normalize(''), '.');
+ expect(context.normalize('.'), '.');
+ expect(context.normalize('..'), '..');
+ expect(context.normalize('a'), 'a');
+ expect(context.normalize('/a/b'), r'\a\b');
+ expect(context.normalize(r'\'), r'\');
+ expect(context.normalize(r'\a\b'), r'\a\b');
+ expect(context.normalize('/'), r'\');
+ expect(context.normalize('C:/'), r'C:\');
+ expect(context.normalize(r'C:\'), r'C:\');
+ expect(context.normalize(r'\\server\share'), r'\\server\share');
+ expect(context.normalize('a\\.\\\xc5\u0bf8-;\u{1f085}\u{00}\\c\\d\\..\\'),
+ 'a\\\xc5\u0bf8-;\u{1f085}\u{00}\x5cc');
+ });
+
+ test('collapses redundant separators', () {
+ expect(context.normalize(r'a\b\c'), r'a\b\c');
+ expect(context.normalize(r'a\\b\\\c\\\\d'), r'a\b\c\d');
+ });
+
+ test('eliminates "." parts', () {
+ expect(context.normalize(r'.\'), '.');
+ expect(context.normalize(r'c:\.'), r'c:\');
+ expect(context.normalize(r'c:\foo\.'), r'c:\foo');
+ expect(context.normalize(r'B:\.\'), r'B:\');
+ expect(context.normalize(r'\\server\share\.'), r'\\server\share');
+ expect(context.normalize(r'.\.'), '.');
+ expect(context.normalize(r'a\.\b'), r'a\b');
+ expect(context.normalize(r'a\.b\c'), r'a\.b\c');
+ expect(context.normalize(r'a\./.\b\.\c'), r'a\b\c');
+ expect(context.normalize(r'.\./a'), 'a');
+ expect(context.normalize(r'a/.\.'), 'a');
+ expect(context.normalize(r'\.'), r'\');
+ expect(context.normalize('/.'), r'\');
+ });
+
+ test('eliminates ".." parts', () {
+ expect(context.normalize('..'), '..');
+ expect(context.normalize(r'..\'), '..');
+ expect(context.normalize(r'..\..\..'), r'..\..\..');
+ expect(context.normalize(r'../..\..\'), r'..\..\..');
+ expect(context.normalize(r'\\server\share\..'), r'\\server\share');
+ expect(
+ context.normalize(r'\\server\share\..\../..\a'), r'\\server\share\a');
+ expect(context.normalize(r'c:\..'), r'c:\');
+ expect(context.normalize(r'c:\foo\..'), r'c:\');
+ expect(context.normalize(r'A:/..\..\..'), r'A:\');
+ expect(context.normalize(r'b:\..\..\..\a'), r'b:\a');
+ expect(context.normalize(r'b:\r\..\..\..\a\c\.\..'), r'b:\a');
+ expect(context.normalize(r'a\..'), '.');
+ expect(context.normalize(r'..\a'), r'..\a');
+ expect(context.normalize(r'c:\..\a'), r'c:\a');
+ expect(context.normalize(r'\..\a'), r'\a');
+ expect(context.normalize(r'a\b\..'), 'a');
+ expect(context.normalize(r'..\a\b\..'), r'..\a');
+ expect(context.normalize(r'a\..\b'), 'b');
+ expect(context.normalize(r'a\.\..\b'), 'b');
+ expect(context.normalize(r'a\b\c\..\..\d\e\..'), r'a\d');
+ expect(context.normalize(r'a\b\..\..\..\..\c'), r'..\..\c');
+ expect(context.normalize(r'a/b/c/../../..d/./.e/f././'), r'a\..d\.e\f.');
+ });
+
+ test('removes trailing separators', () {
+ expect(context.normalize(r'.\'), '.');
+ expect(context.normalize(r'.\\'), '.');
+ expect(context.normalize(r'a/'), 'a');
+ expect(context.normalize(r'a\b\'), r'a\b');
+ expect(context.normalize(r'a\b\\\'), r'a\b');
+ });
+
+ test('normalizes separators', () {
+ expect(context.normalize(r'a/b\c'), r'a\b\c');
+ });
+
+ test('when canonicalizing', () {
+ expect(context.canonicalize('.'), r'c:\root\path');
+ expect(context.canonicalize('foo/bar'), r'c:\root\path\foo\bar');
+ expect(context.canonicalize('FoO'), r'c:\root\path\foo');
+ expect(context.canonicalize('/foo'), r'c:\foo');
+ expect(context.canonicalize('D:/foo'), r'd:\foo');
+ });
+ });
+
+ group('relative', () {
+ group('from absolute root', () {
+ test('given absolute path in root', () {
+ expect(context.relative(r'C:\'), r'..\..');
+ expect(context.relative(r'C:\root'), '..');
+ expect(context.relative(r'\root'), '..');
+ expect(context.relative(r'C:\root\path'), '.');
+ expect(context.relative(r'\root\path'), '.');
+ expect(context.relative(r'C:\root\path\a'), 'a');
+ expect(context.relative(r'\root\path\a'), 'a');
+ expect(context.relative(r'C:\root\path\a\b.txt'), r'a\b.txt');
+ expect(context.relative(r'C:\root\a\b.txt'), r'..\a\b.txt');
+ expect(context.relative(r'C:/'), r'..\..');
+ expect(context.relative(r'C:/root'), '..');
+ expect(context.relative(r'c:\'), r'..\..');
+ expect(context.relative(r'c:\root'), '..');
+ });
+
+ test('given absolute path outside of root', () {
+ expect(context.relative(r'C:\a\b'), r'..\..\a\b');
+ expect(context.relative(r'\a\b'), r'..\..\a\b');
+ expect(context.relative(r'C:\root\path\a'), 'a');
+ expect(context.relative(r'C:\root\path\a\b.txt'), r'a\b.txt');
+ expect(context.relative(r'C:\root\a\b.txt'), r'..\a\b.txt');
+ expect(context.relative(r'C:/a/b'), r'..\..\a\b');
+ expect(context.relative(r'C:/root/path/a'), 'a');
+ expect(context.relative(r'c:\a\b'), r'..\..\a\b');
+ expect(context.relative(r'c:\root\path\a'), 'a');
+ });
+
+ test('given absolute path on different drive', () {
+ expect(context.relative(r'D:\a\b'), r'D:\a\b');
+ });
+
+ test('given relative path', () {
+ // The path is considered relative to the root, so it basically just
+ // normalizes.
+ expect(context.relative(''), '.');
+ expect(context.relative('.'), '.');
+ expect(context.relative('a'), 'a');
+ expect(context.relative(r'a\b.txt'), r'a\b.txt');
+ expect(context.relative(r'..\a\b.txt'), r'..\a\b.txt');
+ expect(context.relative(r'a\.\b\..\c.txt'), r'a\c.txt');
+ });
+
+ test('is case-insensitive', () {
+ expect(context.relative(r'c:\'), r'..\..');
+ expect(context.relative(r'c:\RoOt'), r'..');
+ expect(context.relative(r'c:\rOoT\pAtH\a'), r'a');
+ });
+
+ // Regression
+ test('from root-only path', () {
+ expect(context.relative(r'C:\', from: r'C:\'), '.');
+ expect(context.relative(r'C:\root\path', from: r'C:\'), r'root\path');
+ });
+ });
+
+ group('from relative root', () {
+ final r = path.Context(style: path.Style.windows, current: r'foo\bar');
+
+ test('given absolute path', () {
+ expect(r.relative(r'C:\'), equals(r'C:\'));
+ expect(r.relative(r'C:\a\b'), equals(r'C:\a\b'));
+ expect(r.relative(r'\'), equals(r'\'));
+ expect(r.relative(r'\a\b'), equals(r'\a\b'));
+ });
+
+ test('given relative path', () {
+ // The path is considered relative to the root, so it basically just
+ // normalizes.
+ expect(r.relative(''), '.');
+ expect(r.relative('.'), '.');
+ expect(r.relative('..'), '..');
+ expect(r.relative('a'), 'a');
+ expect(r.relative(r'a\b.txt'), r'a\b.txt');
+ expect(r.relative(r'..\a/b.txt'), r'..\a\b.txt');
+ expect(r.relative(r'a\./b\../c.txt'), r'a\c.txt');
+ });
+ });
+
+ group('from root-relative root', () {
+ final r = path.Context(style: path.Style.windows, current: r'\foo\bar');
+
+ test('given absolute path', () {
+ expect(r.relative(r'C:\'), equals(r'C:\'));
+ expect(r.relative(r'C:\a\b'), equals(r'C:\a\b'));
+ expect(r.relative(r'\'), equals(r'..\..'));
+ expect(r.relative(r'\a\b'), equals(r'..\..\a\b'));
+ expect(r.relative('/'), equals(r'..\..'));
+ expect(r.relative('/a/b'), equals(r'..\..\a\b'));
+ });
+
+ test('given relative path', () {
+ // The path is considered relative to the root, so it basically just
+ // normalizes.
+ expect(r.relative(''), '.');
+ expect(r.relative('.'), '.');
+ expect(r.relative('..'), '..');
+ expect(r.relative('a'), 'a');
+ expect(r.relative(r'a\b.txt'), r'a\b.txt');
+ expect(r.relative(r'..\a/b.txt'), r'..\a\b.txt');
+ expect(r.relative(r'a\./b\../c.txt'), r'a\c.txt');
+ });
+ });
+
+ test('from a root with extension', () {
+ final r = path.Context(style: path.Style.windows, current: r'C:\dir.ext');
+ expect(r.relative(r'C:\dir.ext\file'), 'file');
+ });
+
+ test('with a root parameter', () {
+ expect(context.relative(r'C:\foo\bar\baz', from: r'C:\foo\bar'),
+ equals('baz'));
+ expect(
+ context.relative('..', from: r'C:\foo\bar'), equals(r'..\..\root'));
+ expect(context.relative('..', from: r'D:\foo\bar'), equals(r'C:\root'));
+ expect(context.relative(r'C:\foo\bar\baz', from: r'foo\bar'),
+ equals(r'..\..\..\..\foo\bar\baz'));
+ expect(context.relative('..', from: r'foo\bar'), equals(r'..\..\..'));
+ });
+
+ test('with a root parameter and a relative root', () {
+ final r =
+ path.Context(style: path.Style.windows, current: r'relative\root');
+ expect(r.relative(r'C:\foo\bar\baz', from: r'C:\foo\bar'), equals('baz'));
+ expect(() => r.relative('..', from: r'C:\foo\bar'), throwsPathException);
+ expect(r.relative(r'C:\foo\bar\baz', from: r'foo\bar'),
+ equals(r'C:\foo\bar\baz'));
+ expect(r.relative('..', from: r'foo\bar'), equals(r'..\..\..'));
+ });
+
+ test('given absolute with different root prefix', () {
+ expect(context.relative(r'D:\a\b'), r'D:\a\b');
+ expect(context.relative(r'\\server\share\a\b'), r'\\server\share\a\b');
+ });
+
+ test('from a . root', () {
+ final r = path.Context(style: path.Style.windows, current: '.');
+ expect(r.relative(r'C:\foo\bar\baz'), equals(r'C:\foo\bar\baz'));
+ expect(r.relative(r'foo\bar\baz'), equals(r'foo\bar\baz'));
+ expect(r.relative(r'\foo\bar\baz'), equals(r'\foo\bar\baz'));
+ });
+ });
+
+ group('isWithin', () {
+ test('simple cases', () {
+ expect(context.isWithin(r'foo\bar', r'foo\bar'), isFalse);
+ expect(context.isWithin(r'foo\bar', r'foo\bar\baz'), isTrue);
+ expect(context.isWithin(r'foo\bar', r'foo\baz'), isFalse);
+ expect(context.isWithin(r'foo\bar', r'..\path\foo\bar\baz'), isTrue);
+ expect(context.isWithin(r'C:\', r'C:\foo\bar'), isTrue);
+ expect(context.isWithin(r'C:\', r'D:\foo\bar'), isFalse);
+ expect(context.isWithin(r'C:\', r'\foo\bar'), isTrue);
+ expect(context.isWithin(r'C:\foo', r'\foo\bar'), isTrue);
+ expect(context.isWithin(r'C:\foo', r'\bar\baz'), isFalse);
+ expect(context.isWithin(r'baz', r'C:\root\path\baz\bang'), isTrue);
+ expect(context.isWithin(r'baz', r'C:\root\path\bang\baz'), isFalse);
+ });
+
+ test('complex cases', () {
+ expect(context.isWithin(r'foo\.\bar', r'foo\bar\baz'), isTrue);
+ expect(context.isWithin(r'foo\\bar', r'foo\bar\baz'), isTrue);
+ expect(context.isWithin(r'foo\qux\..\bar', r'foo\bar\baz'), isTrue);
+ expect(context.isWithin(r'foo\bar', r'foo\bar\baz\..\..'), isFalse);
+ expect(context.isWithin(r'foo\bar', r'foo\bar\\\'), isFalse);
+ expect(context.isWithin(r'foo\.bar', r'foo\.bar\baz'), isTrue);
+ expect(context.isWithin(r'foo\.\bar', r'foo\.bar\baz'), isFalse);
+ expect(context.isWithin(r'foo\..bar', r'foo\..bar\baz'), isTrue);
+ expect(context.isWithin(r'foo\bar', r'foo\bar\baz\..'), isFalse);
+ expect(context.isWithin(r'foo\bar', r'foo\bar\baz\..\qux'), isTrue);
+ expect(context.isWithin(r'C:\', 'C:/foo'), isTrue);
+ expect(context.isWithin(r'C:\', r'D:\foo'), isFalse);
+ expect(context.isWithin(r'C:\', r'\\foo\bar'), isFalse);
+ });
+
+ test('with root-relative paths', () {
+ expect(context.isWithin(r'\foo', r'C:\foo\bar'), isTrue);
+ expect(context.isWithin(r'C:\foo', r'\foo\bar'), isTrue);
+ expect(context.isWithin(r'\root', r'foo\bar'), isTrue);
+ expect(context.isWithin(r'foo', r'\root\path\foo\bar'), isTrue);
+ expect(context.isWithin(r'\foo', r'\foo\bar'), isTrue);
+ });
+
+ test('from a relative root', () {
+ final r = path.Context(style: path.Style.windows, current: r'foo\bar');
+ expect(r.isWithin('.', r'a\b\c'), isTrue);
+ expect(r.isWithin('.', r'..\a\b\c'), isFalse);
+ expect(r.isWithin('.', r'..\..\a\foo\b\c'), isFalse);
+ expect(r.isWithin(r'C:\', r'C:\baz\bang'), isTrue);
+ expect(r.isWithin('.', r'C:\baz\bang'), isFalse);
+ });
+
+ test('is case-insensitive', () {
+ expect(context.isWithin(r'FoO', r'fOo\bar'), isTrue);
+ expect(context.isWithin(r'C:\', r'c:\foo'), isTrue);
+ expect(context.isWithin(r'fOo\qux\..\BaR', r'FoO\bAr\baz'), isTrue);
+ });
+ });
+
+ group('equals and hash', () {
+ test('simple cases', () {
+ expectEquals(context, r'foo\bar', r'foo\bar');
+ expectNotEquals(context, r'foo\bar', r'foo\bar\baz');
+ expectNotEquals(context, r'foo\bar', r'foo');
+ expectNotEquals(context, r'foo\bar', r'foo\baz');
+ expectEquals(context, r'foo\bar', r'..\path\foo\bar');
+ expectEquals(context, r'D:\', r'D:\');
+ expectEquals(context, r'C:\', r'..\..');
+ expectEquals(context, r'baz', r'C:\root\path\baz');
+ });
+
+ test('complex cases', () {
+ expectEquals(context, r'foo\.\bar', r'foo\bar');
+ expectEquals(context, r'foo\\bar', r'foo\bar');
+ expectEquals(context, r'foo\qux\..\bar', r'foo\bar');
+ expectNotEquals(context, r'foo\qux\..\bar', r'foo\qux');
+ expectNotEquals(context, r'foo\bar', r'foo\bar\baz\..\..');
+ expectEquals(context, r'foo\bar', r'foo\bar\\\');
+ expectEquals(context, r'foo\.bar', r'foo\.bar');
+ expectNotEquals(context, r'foo\.\bar', r'foo\.bar');
+ expectEquals(context, r'foo\..bar', r'foo\..bar');
+ expectNotEquals(context, r'foo\..\bar', r'foo\..bar');
+ expectEquals(context, r'foo\bar', r'foo\bar\baz\..');
+ expectEquals(context, r'FoO\bAr', r'foo\bar');
+ expectEquals(context, r'foo/\bar', r'foo\/bar');
+ expectEquals(context, r'c:\', r'C:\');
+ expectEquals(context, r'C:\root', r'..');
+ });
+
+ test('with root-relative paths', () {
+ expectEquals(context, r'\foo', r'C:\foo');
+ expectNotEquals(context, r'\foo', 'http://google.com/foo');
+ expectEquals(context, r'C:\root\path\foo\bar', r'foo\bar');
+ });
+
+ test('from a relative root', () {
+ final r = path.Context(style: path.Style.windows, current: r'foo\bar');
+ expectEquals(r, r'a\b', r'a\b');
+ expectNotEquals(r, '.', r'foo\bar');
+ expectNotEquals(r, '.', r'..\a\b');
+ expectEquals(r, '.', r'..\bar');
+ expectEquals(r, r'C:\baz\bang', r'C:\baz\bang');
+ expectNotEquals(r, r'baz\bang', r'C:\baz\bang');
+ });
+ });
+
+ group('absolute', () {
+ test('allows up to fifteen parts', () {
+ expect(context.absolute('a'), r'C:\root\path\a');
+ expect(context.absolute('a', 'b'), r'C:\root\path\a\b');
+ expect(context.absolute('a', 'b', 'c'), r'C:\root\path\a\b\c');
+ expect(context.absolute('a', 'b', 'c', 'd'), r'C:\root\path\a\b\c\d');
+ expect(
+ context.absolute('a', 'b', 'c', 'd', 'e'), r'C:\root\path\a\b\c\d\e');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f'),
+ r'C:\root\path\a\b\c\d\e\f');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g'),
+ r'C:\root\path\a\b\c\d\e\f\g');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'),
+ r'C:\root\path\a\b\c\d\e\f\g\h');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'),
+ r'C:\root\path\a\b\c\d\e\f\g\h\i');
+ expect(context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'),
+ r'C:\root\path\a\b\c\d\e\f\g\h\i\j');
+ expect(
+ context.absolute(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'),
+ r'C:\root\path\a\b\c\d\e\f\g\h\i\j\k');
+ expect(
+ context.absolute(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'),
+ r'C:\root\path\a\b\c\d\e\f\g\h\i\j\k\l');
+ expect(
+ context.absolute(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'),
+ r'C:\root\path\a\b\c\d\e\f\g\h\i\j\k\l\m');
+ expect(
+ context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n'),
+ r'C:\root\path\a\b\c\d\e\f\g\h\i\j\k\l\m\n');
+ expect(
+ context.absolute('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n', 'o'),
+ r'C:\root\path\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o');
+ });
+
+ test('does not add separator if a part ends in one', () {
+ expect(context.absolute(r'a\', 'b', r'c\', 'd'), r'C:\root\path\a\b\c\d');
+ expect(context.absolute('a/', 'b'), r'C:\root\path\a/b');
+ });
+
+ test('ignores parts before an absolute path', () {
+ expect(context.absolute('a', '/b', '/c', 'd'), r'C:\c\d');
+ expect(context.absolute('a', r'\b', r'\c', 'd'), r'C:\c\d');
+ expect(context.absolute('a', r'c:\b', 'c', 'd'), r'c:\b\c\d');
+ expect(context.absolute('a', r'\\b\c', r'\\d\e', 'f'), r'\\d\e\f');
+ });
+ });
+
+ test('withoutExtension', () {
+ expect(context.withoutExtension(''), '');
+ expect(context.withoutExtension('a'), 'a');
+ expect(context.withoutExtension('.a'), '.a');
+ expect(context.withoutExtension('a.b'), 'a');
+ expect(context.withoutExtension(r'a\b.c'), r'a\b');
+ expect(context.withoutExtension(r'a\b.c.d'), r'a\b.c');
+ expect(context.withoutExtension(r'a\'), r'a\');
+ expect(context.withoutExtension(r'a\b\'), r'a\b\');
+ expect(context.withoutExtension(r'a\.'), r'a\.');
+ expect(context.withoutExtension(r'a\.b'), r'a\.b');
+ expect(context.withoutExtension(r'a.b\c'), r'a.b\c');
+ expect(context.withoutExtension(r'a/b.c/d'), r'a/b.c/d');
+ expect(context.withoutExtension(r'a\b/c'), r'a\b/c');
+ expect(context.withoutExtension(r'a\b/c.d'), r'a\b/c');
+ expect(context.withoutExtension(r'a.b/c'), r'a.b/c');
+ expect(context.withoutExtension(r'a\b.c\'), r'a\b\');
+ });
+
+ test('setExtension', () {
+ expect(context.setExtension('', '.x'), '.x');
+ expect(context.setExtension('a', '.x'), 'a.x');
+ expect(context.setExtension('.a', '.x'), '.a.x');
+ expect(context.setExtension('a.b', '.x'), 'a.x');
+ expect(context.setExtension(r'a\b.c', '.x'), r'a\b.x');
+ expect(context.setExtension(r'a\b.c.d', '.x'), r'a\b.c.x');
+ expect(context.setExtension(r'a\', '.x'), r'a\.x');
+ expect(context.setExtension(r'a\b\', '.x'), r'a\b\.x');
+ expect(context.setExtension(r'a\.', '.x'), r'a\..x');
+ expect(context.setExtension(r'a\.b', '.x'), r'a\.b.x');
+ expect(context.setExtension(r'a.b\c', '.x'), r'a.b\c.x');
+ expect(context.setExtension(r'a/b.c/d', '.x'), r'a/b.c/d.x');
+ expect(context.setExtension(r'a\b/c', '.x'), r'a\b/c.x');
+ expect(context.setExtension(r'a\b/c.d', '.x'), r'a\b/c.x');
+ expect(context.setExtension(r'a.b/c', '.x'), r'a.b/c.x');
+ expect(context.setExtension(r'a\b.c\', '.x'), r'a\b\.x');
+ });
+
+ group('fromUri', () {
+ test('with a URI', () {
+ expect(context.fromUri(Uri.parse('file:///C:/path/to/foo')),
+ r'C:\path\to\foo');
+ expect(context.fromUri(Uri.parse('file:///C%3A/path/to/foo')),
+ r'C:\path\to\foo');
+ expect(context.fromUri(Uri.parse('file://server/share/path/to/foo')),
+ r'\\server\share\path\to\foo');
+ expect(context.fromUri(Uri.parse('file:///C:/')), r'C:\');
+ expect(context.fromUri(Uri.parse('file:///C%3A/')), r'C:\');
+ expect(
+ context.fromUri(Uri.parse('file://server/share')), r'\\server\share');
+ expect(context.fromUri(Uri.parse('foo/bar')), r'foo\bar');
+ expect(context.fromUri(Uri.parse('/C:/path/to/foo')), r'C:\path\to\foo');
+ expect(
+ context.fromUri(Uri.parse('///C:/path/to/foo')), r'C:\path\to\foo');
+ expect(context.fromUri(Uri.parse('//server/share/path/to/foo')),
+ r'\\server\share\path\to\foo');
+ expect(context.fromUri(Uri.parse('file:///C:/path/to/foo%23bar')),
+ r'C:\path\to\foo#bar');
+ expect(context.fromUri(Uri.parse('file:///C%3A/path/to/foo%23bar')),
+ r'C:\path\to\foo#bar');
+ expect(
+ context.fromUri(Uri.parse('file://server/share/path/to/foo%23bar')),
+ r'\\server\share\path\to\foo#bar');
+ expect(context.fromUri(Uri.parse('_%7B_%7D_%60_%5E_%20_%22_%25_')),
+ r'_{_}_`_^_ _"_%_');
+ expect(context.fromUri(Uri.parse('/foo')), r'\foo');
+ expect(() => context.fromUri(Uri.parse('https://dart.dev')),
+ throwsArgumentError);
+ });
+
+ test('with a string', () {
+ expect(context.fromUri('file:///C:/path/to/foo'), r'C:\path\to\foo');
+ expect(context.fromUri('file:///C%3A/path/to/foo'), r'C:\path\to\foo');
+ });
+ });
+
+ test('toUri', () {
+ expect(
+ context.toUri(r'C:\path\to\foo'), Uri.parse('file:///C:/path/to/foo'));
+ expect(context.toUri(r'C:\path\to\foo\'),
+ Uri.parse('file:///C:/path/to/foo/'));
+ expect(context.toUri(r'path\to\foo\'), Uri.parse('path/to/foo/'));
+ expect(context.toUri(r'C:\'), Uri.parse('file:///C:/'));
+ expect(context.toUri(r'\\server\share'), Uri.parse('file://server/share'));
+ expect(
+ context.toUri(r'\\server\share\'), Uri.parse('file://server/share/'));
+ expect(context.toUri(r'foo\bar'), Uri.parse('foo/bar'));
+ expect(context.toUri(r'C:\path\to\foo#bar'),
+ Uri.parse('file:///C:/path/to/foo%23bar'));
+ expect(context.toUri(r'\\server\share\path\to\foo#bar'),
+ Uri.parse('file://server/share/path/to/foo%23bar'));
+ expect(context.toUri(r'C:\_{_}_`_^_ _"_%_'),
+ Uri.parse('file:///C:/_%7B_%7D_%60_%5E_%20_%22_%25_'));
+ expect(context.toUri(r'_{_}_`_^_ _"_%_'),
+ Uri.parse('_%7B_%7D_%60_%5E_%20_%22_%25_'));
+ expect(context.toUri(''), Uri.parse(''));
+ });
+
+ group('prettyUri', () {
+ test('with a file: URI', () {
+ expect(context.prettyUri('file:///C:/root/path/a/b'), r'a\b');
+ expect(context.prettyUri('file:///C:/root/path/a/../b'), r'b');
+ expect(
+ context.prettyUri('file:///C:/other/path/a/b'), r'C:\other\path\a\b');
+ expect(
+ context.prettyUri('file:///D:/root/path/a/b'), r'D:\root\path\a\b');
+ expect(context.prettyUri('file:///C:/root/other'), r'..\other');
+ });
+
+ test('with a file: URI with encoded colons', () {
+ expect(context.prettyUri('file:///C%3A/root/path/a/b'), r'a\b');
+ expect(context.prettyUri('file:///C%3A/root/path/a/../b'), r'b');
+ expect(context.prettyUri('file:///C%3A/other/path/a/b'),
+ r'C:\other\path\a\b');
+ expect(
+ context.prettyUri('file:///D%3A/root/path/a/b'), r'D:\root\path\a\b');
+ expect(context.prettyUri('file:///C%3A/root/other'), r'..\other');
+ });
+
+ test('with an http: URI', () {
+ expect(context.prettyUri('https://dart.dev/a/b'), 'https://dart.dev/a/b');
+ });
+
+ test('with a relative URI', () {
+ expect(context.prettyUri('a/b'), r'a\b');
+ });
+
+ test('with a root-relative URI', () {
+ expect(context.prettyUri('/D:/a/b'), r'D:\a\b');
+ });
+
+ test('with a Uri object', () {
+ expect(context.prettyUri(Uri.parse('a/b')), r'a\b');
+ });
+ });
+
+ test('driveLetterEnd', () {
+ expect(driveLetterEnd('', 0), null);
+ expect(driveLetterEnd('foo.dart', 0), null);
+ expect(driveLetterEnd('@', 0), null);
+
+ expect(driveLetterEnd('c:', 0), 2);
+
+ // colons
+ expect(driveLetterEnd('c:/', 0), 3);
+ expect(driveLetterEnd('c:/a', 0), 3);
+
+ // escaped colons lowercase
+ expect(driveLetterEnd('c%3a/', 0), 5);
+ expect(driveLetterEnd('c%3a/a', 0), 5);
+
+ // escaped colons uppercase
+ expect(driveLetterEnd('c%3A/', 0), 5);
+ expect(driveLetterEnd('c%3A/a', 0), 5);
+
+ // non-drive letter
+ expect(driveLetterEnd('ab:/c', 0), null);
+ expect(driveLetterEnd('ab%3a/c', 0), null);
+ expect(driveLetterEnd('ab%3A/c', 0), null);
+ });
+}
diff --git a/pkgs/platform/.gitignore b/pkgs/platform/.gitignore
new file mode 100644
index 0000000..dfbf1b4
--- /dev/null
+++ b/pkgs/platform/.gitignore
@@ -0,0 +1,17 @@
+### Dart template
+# Don’t commit the following directories created by pub.
+.buildlog
+.dart_tool/
+.pub/
+build/
+packages
+.packages
+
+# Include when developing application packages.
+pubspec.lock
+
+# IDE
+.project
+.settings
+.idea
+.c9
diff --git a/pkgs/platform/AUTHORS b/pkgs/platform/AUTHORS
new file mode 100644
index 0000000..ad59f11
--- /dev/null
+++ b/pkgs/platform/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the Process project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/platform/CHANGELOG.md b/pkgs/platform/CHANGELOG.md
new file mode 100644
index 0000000..1e8c4d5
--- /dev/null
+++ b/pkgs/platform/CHANGELOG.md
@@ -0,0 +1,111 @@
+## 3.1.6
+
+* Move to `dart-lang/core` monorepo.
+
+## 3.1.5
+
+* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2.
+* Transfers the package source from https://github.com/flutter/packages
+ to https://github.com/dart-lang/platform.
+
+## 3.1.4
+
+* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
+* Fixes new lint warnings.
+
+## 3.1.3
+
+* Adds example app.
+
+## 3.1.2
+
+* Adds pub topics to package metadata.
+* Updates minimum supported SDK version to Flutter 3.7/Dart 2.19.
+
+## 3.1.1
+
+* Transfers the package source from https://github.com/google/platform.dart to
+ https://github.com/flutter/packages.
+
+## 3.1.0
+
+* Removed `Platform.packageRoot`, which was already marked deprecated, and which
+ didn't work in Dart 2.
+
+## 3.0.2
+
+* Added `FakePlatform.copyWith` function.
+
+## 3.0.1
+
+* Added string constants for each of the supported platforms for use in switch
+ statements.
+
+## 3.0.0
+
+* First stable null safe release.
+
+## 3.0.0-nullsafety.4
+
+* Update supported SDK range.
+
+## 3.0.0-nullsafety.3
+
+* Update supported SDK range.
+
+## 3.0.0-nullsafety.2
+
+* Update supported SDK range.
+
+## 3.0.0-nullsafety.1
+
+* Migrate package to null-safe dart.
+
+## 2.2.1
+
+* Add `operatingSystemVersion`
+
+## 2.2.0
+
+* Declare compatibility with Dart 2 stable
+* Update dependency on `package:test` to 1.0
+
+## 2.1.2
+
+* Relax sdk upper bound constraint to '<2.0.0' to allow 'edge' dart sdk use.
+
+## 2.1.1
+
+* Bumped maximum Dart SDK version to 2.0.0-dev.infinity
+
+## 2.1.0
+
+* Added `localeName`
+* Bumped minimum Dart SDK version to 1.24.0-dev.0.0
+
+## 2.0.0
+
+* Added `stdinSupportsAnsi` and `stdinSupportsAnsi`
+* Removed `ansiSupported`
+
+## 1.1.1
+
+* Updated `LocalPlatform` to use new `dart.io` API for ansi color support queries
+* Bumped minimum Dart SDK version to 1.23.0-dev.10.0
+
+## 1.1.0
+
+* Added `ansiSupported`
+* Bumped minimum Dart SDK version to 1.23.0-dev.9.0
+
+## 1.0.2
+
+* Minor doc updates
+
+## 1.0.1
+
+* Added const constructors for `Platform` and `LocalPlatform`
+
+## 1.0.0
+
+* Initial version
diff --git a/pkgs/platform/LICENSE b/pkgs/platform/LICENSE
new file mode 100644
index 0000000..389ce98
--- /dev/null
+++ b/pkgs/platform/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2017, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/platform/README.md b/pkgs/platform/README.md
new file mode 100644
index 0000000..44932a6
--- /dev/null
+++ b/pkgs/platform/README.md
@@ -0,0 +1,10 @@
+[](https://pub.dartlang.org/packages/platform)
+
+A generic platform abstraction for Dart.
+
+Like `dart:io`, `package:platform` supplies a rich, Dart-idiomatic API for
+accessing platform-specific information.
+
+`package:platform` provides a lightweight wrapper around the static `Platform`
+properties that exist in `dart:io`. However, it uses instance properties rather
+than static properties, making it possible to mock out in tests.
diff --git a/pkgs/platform/analysis_options.yaml b/pkgs/platform/analysis_options.yaml
new file mode 100644
index 0000000..4544442
--- /dev/null
+++ b/pkgs/platform/analysis_options.yaml
@@ -0,0 +1,6 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ errors:
+ # Allow having TODOs in the code
+ todo: ignore
diff --git a/pkgs/platform/example/.gitignore b/pkgs/platform/example/.gitignore
new file mode 100644
index 0000000..3a85790
--- /dev/null
+++ b/pkgs/platform/example/.gitignore
@@ -0,0 +1,3 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/
diff --git a/pkgs/platform/example/README.md b/pkgs/platform/example/README.md
new file mode 100644
index 0000000..b0bf20b
--- /dev/null
+++ b/pkgs/platform/example/README.md
@@ -0,0 +1,2 @@
+A small example application demonstrating how to use the platform information
+APIs in `package:platform`.
\ No newline at end of file
diff --git a/pkgs/platform/example/analysis_options.yaml b/pkgs/platform/example/analysis_options.yaml
new file mode 100644
index 0000000..dee8927
--- /dev/null
+++ b/pkgs/platform/example/analysis_options.yaml
@@ -0,0 +1,30 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+# Uncomment the following section to specify additional rules.
+
+# linter:
+# rules:
+# - camel_case_types
+
+# analyzer:
+# exclude:
+# - path/to/excluded/files/**
+
+# For more information about the core and recommended set of lints, see
+# https://dart.dev/go/core-lints
+
+# For additional information about configuring this file, see
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/platform/example/bin/example.dart b/pkgs/platform/example/bin/example.dart
new file mode 100644
index 0000000..59693e3
--- /dev/null
+++ b/pkgs/platform/example/bin/example.dart
@@ -0,0 +1,15 @@
+import 'package:platform/platform.dart';
+
+void main(List<String> arguments) {
+ const LocalPlatform platform = LocalPlatform();
+
+ print('Operating System: ${platform.operatingSystem}.');
+ print('Local Hostname: ${platform.localHostname}.');
+ print('Number of Processors: ${platform.numberOfProcessors}.');
+ print('Path Separator: ${platform.pathSeparator}.');
+ print('Locale Name: ${platform.localeName}.');
+ print('Stdin Supports ANSI: ${platform.stdinSupportsAnsi}.');
+ print('Stdout Supports ANSI: ${platform.stdoutSupportsAnsi}.');
+ print('Executable Arguments: ${platform.executableArguments}.');
+ print('Dart Version: ${platform.version}.');
+}
diff --git a/pkgs/platform/example/pubspec.yaml b/pkgs/platform/example/pubspec.yaml
new file mode 100644
index 0000000..fb2f5b0
--- /dev/null
+++ b/pkgs/platform/example/pubspec.yaml
@@ -0,0 +1,14 @@
+name: example
+description: Demonstrates how to use the platform api.
+version: 1.0.0
+publish_to: none
+
+environment:
+ sdk: ^3.2.0
+
+dependencies:
+ platform:
+ path: ../
+
+dev_dependencies:
+ lints: ^5.0.0
diff --git a/pkgs/platform/lib/platform.dart b/pkgs/platform/lib/platform.dart
new file mode 100644
index 0000000..f096dcb
--- /dev/null
+++ b/pkgs/platform/lib/platform.dart
@@ -0,0 +1,8 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Core interfaces & classes.
+export 'src/interface/local_platform.dart';
+export 'src/interface/platform.dart';
+export 'src/testing/fake_platform.dart';
diff --git a/pkgs/platform/lib/src/interface/local_platform.dart b/pkgs/platform/lib/src/interface/local_platform.dart
new file mode 100644
index 0000000..c24c01b
--- /dev/null
+++ b/pkgs/platform/lib/src/interface/local_platform.dart
@@ -0,0 +1,58 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:io' as io show Platform, stdin, stdout;
+
+import 'platform.dart';
+
+/// `Platform` implementation that delegates directly to `dart:io`.
+class LocalPlatform extends Platform {
+ /// Creates a new [LocalPlatform].
+ const LocalPlatform();
+
+ @override
+ int get numberOfProcessors => io.Platform.numberOfProcessors;
+
+ @override
+ String get pathSeparator => io.Platform.pathSeparator;
+
+ @override
+ String get operatingSystem => io.Platform.operatingSystem;
+
+ @override
+ String get operatingSystemVersion => io.Platform.operatingSystemVersion;
+
+ @override
+ String get localHostname => io.Platform.localHostname;
+
+ @override
+ Map<String, String> get environment => io.Platform.environment;
+
+ @override
+ String get executable => io.Platform.executable;
+
+ @override
+ String get resolvedExecutable => io.Platform.resolvedExecutable;
+
+ @override
+ Uri get script => io.Platform.script;
+
+ @override
+ List<String> get executableArguments => io.Platform.executableArguments;
+
+ @override
+ String? get packageConfig => io.Platform.packageConfig;
+
+ @override
+ String get version => io.Platform.version;
+
+ @override
+ bool get stdinSupportsAnsi => io.stdin.supportsAnsiEscapes;
+
+ @override
+ bool get stdoutSupportsAnsi => io.stdout.supportsAnsiEscapes;
+
+ @override
+ String get localeName => io.Platform.localeName;
+}
diff --git a/pkgs/platform/lib/src/interface/platform.dart b/pkgs/platform/lib/src/interface/platform.dart
new file mode 100644
index 0000000..dcc47be
--- /dev/null
+++ b/pkgs/platform/lib/src/interface/platform.dart
@@ -0,0 +1,205 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:convert';
+
+/// Provides API parity with the `Platform` class in `dart:io`, but using
+/// instance properties rather than static properties. This difference enables
+/// the use of these APIs in tests, where you can provide mock implementations.
+abstract class Platform {
+ /// Creates a new [Platform].
+ const Platform();
+
+ /// A string constant to compare with [operatingSystem] to see if the platform
+ /// is Linux.
+ ///
+ /// Useful in case statements when switching on [operatingSystem].
+ ///
+ /// To just check if the platform is Linux, use [isLinux].
+ static const String linux = 'linux';
+
+ /// A string constant to compare with [operatingSystem] to see if the platform
+ /// is Windows.
+ ///
+ /// Useful in case statements when switching on [operatingSystem].
+ ///
+ /// To just check if the platform is Windows, use [isWindows].
+ static const String windows = 'windows';
+
+ /// A string constant to compare with [operatingSystem] to see if the platform
+ /// is macOS.
+ ///
+ /// Useful in case statements when switching on [operatingSystem].
+ ///
+ /// To just check if the platform is macOS, use [isMacOS].
+ static const String macOS = 'macos';
+
+ /// A string constant to compare with [operatingSystem] to see if the platform
+ /// is Android.
+ ///
+ /// Useful in case statements when switching on [operatingSystem].
+ ///
+ /// To just check if the platform is Android, use [isAndroid].
+ static const String android = 'android';
+
+ /// A string constant to compare with [operatingSystem] to see if the platform
+ /// is iOS.
+ ///
+ /// Useful in case statements when switching on [operatingSystem].
+ ///
+ /// To just check if the platform is iOS, use [isIOS].
+ static const String iOS = 'ios';
+
+ /// A string constant to compare with [operatingSystem] to see if the platform
+ /// is Fuchsia.
+ ///
+ /// Useful in case statements when switching on [operatingSystem].
+ ///
+ /// To just check if the platform is Fuchsia, use [isFuchsia].
+ static const String fuchsia = 'fuchsia';
+
+ /// A list of the possible values that [operatingSystem] can return.
+ static const List<String> operatingSystemValues = <String>[
+ linux,
+ macOS,
+ windows,
+ android,
+ iOS,
+ fuchsia,
+ ];
+
+ /// The number of processors of the machine.
+ int get numberOfProcessors;
+
+ /// The path separator used by the operating system to separate
+ /// components in file paths.
+ String get pathSeparator;
+
+ /// A string (`linux`, `macos`, `windows`, `android`, `ios`, or `fuchsia`)
+ /// representing the operating system.
+ ///
+ /// The possible return values are available from [operatingSystemValues], and
+ /// there are constants for each of the platforms to use in switch statements
+ /// or conditionals (See [linux], [macOS], [windows], [android], [iOS], and
+ /// [fuchsia]).
+ String get operatingSystem;
+
+ /// A string representing the version of the operating system or platform.
+ String get operatingSystemVersion;
+
+ /// Get the local hostname for the system.
+ String get localHostname;
+
+ /// True if the operating system is Linux.
+ bool get isLinux => operatingSystem == linux;
+
+ /// True if the operating system is OS X.
+ bool get isMacOS => operatingSystem == macOS;
+
+ /// True if the operating system is Windows.
+ bool get isWindows => operatingSystem == windows;
+
+ /// True if the operating system is Android.
+ bool get isAndroid => operatingSystem == android;
+
+ /// True if the operating system is iOS.
+ bool get isIOS => operatingSystem == iOS;
+
+ /// True if the operating system is Fuchsia
+ bool get isFuchsia => operatingSystem == fuchsia;
+
+ /// The environment for this process.
+ ///
+ /// The returned environment is an unmodifiable map whose content is
+ /// retrieved from the operating system on its first use.
+ ///
+ /// Environment variables on Windows are case-insensitive. The map
+ /// returned on Windows is therefore case-insensitive and will convert
+ /// all keys to upper case. On other platforms the returned map is
+ /// a standard case-sensitive map.
+ Map<String, String> get environment;
+
+ /// The path of the executable used to run the script in this isolate.
+ ///
+ /// The path returned is the literal path used to run the script. This
+ /// path might be relative or just be a name from which the executable
+ /// was found by searching the `PATH`.
+ ///
+ /// To get the absolute path to the resolved executable use
+ /// [resolvedExecutable].
+ String get executable;
+
+ /// The path of the executable used to run the script in this
+ /// isolate after it has been resolved by the OS.
+ ///
+ /// This is the absolute path, with all symlinks resolved, to the
+ /// executable used to run the script.
+ String get resolvedExecutable;
+
+ /// The absolute URI of the script being run in this
+ /// isolate.
+ ///
+ /// If the script argument on the command line is relative,
+ /// it is resolved to an absolute URI before fetching the script, and
+ /// this absolute URI is returned.
+ ///
+ /// URI resolution only does string manipulation on the script path, and this
+ /// may be different from the file system's path resolution behavior. For
+ /// example, a symbolic link immediately followed by '..' will not be
+ /// looked up.
+ ///
+ /// If the executable environment does not support [script] an empty
+ /// [Uri] is returned.
+ Uri get script;
+
+ /// The flags passed to the executable used to run the script in this
+ /// isolate. These are the command-line flags between the executable name
+ /// and the script name. Each fetch of `executableArguments` returns a new
+ /// list containing the flags passed to the executable.
+ List<String> get executableArguments;
+
+ /// The value of the `--packages` flag passed to the executable
+ /// used to run the script in this isolate. This is the configuration which
+ /// specifies how Dart packages are looked up.
+ ///
+ /// If there is no `--packages` flag, `null` is returned.
+ String? get packageConfig;
+
+ /// The version of the current Dart runtime.
+ ///
+ /// The returned `String` is formatted as the [semver](http://semver.org)
+ /// version string of the current dart runtime, possibly followed by
+ /// whitespace and other version and build details.
+ String get version;
+
+ /// When stdin is connected to a terminal, whether ANSI codes are supported.
+ bool get stdinSupportsAnsi;
+
+ /// When stdout is connected to a terminal, whether ANSI codes are supported.
+ bool get stdoutSupportsAnsi;
+
+ /// Get the name of the current locale.
+ String get localeName;
+
+ /// Returns a JSON-encoded representation of this platform.
+ String toJson() {
+ return const JsonEncoder.withIndent(' ').convert(<String, dynamic>{
+ 'numberOfProcessors': numberOfProcessors,
+ 'pathSeparator': pathSeparator,
+ 'operatingSystem': operatingSystem,
+ 'operatingSystemVersion': operatingSystemVersion,
+ 'localHostname': localHostname,
+ 'environment': environment,
+ 'executable': executable,
+ 'resolvedExecutable': resolvedExecutable,
+ 'script': script.toString(),
+ 'executableArguments': executableArguments,
+ 'packageConfig': packageConfig,
+ 'version': version,
+ 'stdinSupportsAnsi': stdinSupportsAnsi,
+ 'stdoutSupportsAnsi': stdoutSupportsAnsi,
+ 'localeName': localeName,
+ });
+ }
+}
diff --git a/pkgs/platform/lib/src/testing/fake_platform.dart b/pkgs/platform/lib/src/testing/fake_platform.dart
new file mode 100644
index 0000000..0c028ca
--- /dev/null
+++ b/pkgs/platform/lib/src/testing/fake_platform.dart
@@ -0,0 +1,199 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:convert';
+
+import '../interface/platform.dart';
+
+/// Provides a mutable implementation of the [Platform] interface.
+class FakePlatform extends Platform {
+ /// Creates a new [FakePlatform] with the specified properties.
+ ///
+ /// Unspecified properties will *not* be assigned default values (they will
+ /// remain `null`). If an unset non-null value is read, a [StateError] will
+ /// be thrown instead of returning `null`.
+ FakePlatform({
+ int? numberOfProcessors,
+ String? pathSeparator,
+ String? operatingSystem,
+ String? operatingSystemVersion,
+ String? localHostname,
+ Map<String, String>? environment,
+ String? executable,
+ String? resolvedExecutable,
+ Uri? script,
+ List<String>? executableArguments,
+ this.packageConfig,
+ String? version,
+ bool? stdinSupportsAnsi,
+ bool? stdoutSupportsAnsi,
+ String? localeName,
+ }) : _numberOfProcessors = numberOfProcessors,
+ _pathSeparator = pathSeparator,
+ _operatingSystem = operatingSystem,
+ _operatingSystemVersion = operatingSystemVersion,
+ _localHostname = localHostname,
+ _environment = environment,
+ _executable = executable,
+ _resolvedExecutable = resolvedExecutable,
+ _script = script,
+ _executableArguments = executableArguments,
+ _version = version,
+ _stdinSupportsAnsi = stdinSupportsAnsi,
+ _stdoutSupportsAnsi = stdoutSupportsAnsi,
+ _localeName = localeName;
+
+ /// Creates a new [FakePlatform] with properties whose initial values mirror
+ /// the specified [platform].
+ FakePlatform.fromPlatform(Platform platform)
+ : _numberOfProcessors = platform.numberOfProcessors,
+ _pathSeparator = platform.pathSeparator,
+ _operatingSystem = platform.operatingSystem,
+ _operatingSystemVersion = platform.operatingSystemVersion,
+ _localHostname = platform.localHostname,
+ _environment = Map<String, String>.from(platform.environment),
+ _executable = platform.executable,
+ _resolvedExecutable = platform.resolvedExecutable,
+ _script = platform.script,
+ _executableArguments = List<String>.from(platform.executableArguments),
+ packageConfig = platform.packageConfig,
+ _version = platform.version,
+ _stdinSupportsAnsi = platform.stdinSupportsAnsi,
+ _stdoutSupportsAnsi = platform.stdoutSupportsAnsi,
+ _localeName = platform.localeName;
+
+ /// Creates a new [FakePlatform] with properties extracted from the encoded
+ /// JSON string.
+ ///
+ /// [json] must be a JSON string that matches the encoding produced by
+ /// [toJson].
+ factory FakePlatform.fromJson(String json) {
+ final map = const JsonDecoder().convert(json) as Map<String, dynamic>;
+ return FakePlatform(
+ numberOfProcessors: map['numberOfProcessors'] as int?,
+ pathSeparator: map['pathSeparator'] as String?,
+ operatingSystem: map['operatingSystem'] as String?,
+ operatingSystemVersion: map['operatingSystemVersion'] as String?,
+ localHostname: map['localHostname'] as String?,
+ environment:
+ (map['environment'] as Map<Object?, Object?>).cast<String, String>(),
+ executable: map['executable'] as String?,
+ resolvedExecutable: map['resolvedExecutable'] as String?,
+ script: Uri.parse(map['script'] as String),
+ executableArguments:
+ (map['executableArguments'] as List<Object?>).cast<String>(),
+ packageConfig: map['packageConfig'] as String?,
+ version: map['version'] as String?,
+ stdinSupportsAnsi: map['stdinSupportsAnsi'] as bool?,
+ stdoutSupportsAnsi: map['stdoutSupportsAnsi'] as bool?,
+ localeName: map['localeName'] as String?,
+ );
+ }
+
+ /// Creates a new [FakePlatform] from this one, with some properties replaced
+ /// by the given properties.
+ FakePlatform copyWith({
+ int? numberOfProcessors,
+ String? pathSeparator,
+ String? operatingSystem,
+ String? operatingSystemVersion,
+ String? localHostname,
+ Map<String, String>? environment,
+ String? executable,
+ String? resolvedExecutable,
+ Uri? script,
+ List<String>? executableArguments,
+ String? packageConfig,
+ String? version,
+ bool? stdinSupportsAnsi,
+ bool? stdoutSupportsAnsi,
+ String? localeName,
+ }) {
+ return FakePlatform(
+ numberOfProcessors: numberOfProcessors ?? this.numberOfProcessors,
+ pathSeparator: pathSeparator ?? this.pathSeparator,
+ operatingSystem: operatingSystem ?? this.operatingSystem,
+ operatingSystemVersion:
+ operatingSystemVersion ?? this.operatingSystemVersion,
+ localHostname: localHostname ?? this.localHostname,
+ environment: environment ?? this.environment,
+ executable: executable ?? this.executable,
+ resolvedExecutable: resolvedExecutable ?? this.resolvedExecutable,
+ script: script ?? this.script,
+ executableArguments: executableArguments ?? this.executableArguments,
+ packageConfig: packageConfig ?? this.packageConfig,
+ version: version ?? this.version,
+ stdinSupportsAnsi: stdinSupportsAnsi ?? this.stdinSupportsAnsi,
+ stdoutSupportsAnsi: stdoutSupportsAnsi ?? this.stdoutSupportsAnsi,
+ localeName: localeName ?? this.localeName,
+ );
+ }
+
+ @override
+ int get numberOfProcessors => _throwIfNull(_numberOfProcessors);
+ int? _numberOfProcessors;
+
+ @override
+ String get pathSeparator => _throwIfNull(_pathSeparator);
+ String? _pathSeparator;
+
+ @override
+ String get operatingSystem => _throwIfNull(_operatingSystem);
+ String? _operatingSystem;
+
+ @override
+ String get operatingSystemVersion => _throwIfNull(_operatingSystemVersion);
+ String? _operatingSystemVersion;
+
+ @override
+ String get localHostname => _throwIfNull(_localHostname);
+ String? _localHostname;
+
+ @override
+ Map<String, String> get environment => _throwIfNull(_environment);
+ Map<String, String>? _environment;
+
+ @override
+ String get executable => _throwIfNull(_executable);
+ String? _executable;
+
+ @override
+ String get resolvedExecutable => _throwIfNull(_resolvedExecutable);
+ String? _resolvedExecutable;
+
+ @override
+ Uri get script => _throwIfNull(_script);
+ Uri? _script;
+
+ @override
+ List<String> get executableArguments => _throwIfNull(_executableArguments);
+ List<String>? _executableArguments;
+
+ @override
+ String? packageConfig;
+
+ @override
+ String get version => _throwIfNull(_version);
+ String? _version;
+
+ @override
+ bool get stdinSupportsAnsi => _throwIfNull(_stdinSupportsAnsi);
+ bool? _stdinSupportsAnsi;
+
+ @override
+ bool get stdoutSupportsAnsi => _throwIfNull(_stdoutSupportsAnsi);
+ bool? _stdoutSupportsAnsi;
+
+ @override
+ String get localeName => _throwIfNull(_localeName);
+ String? _localeName;
+
+ T _throwIfNull<T>(T? value) {
+ if (value == null) {
+ throw StateError(
+ 'Tried to read property of FakePlatform but it was unset.');
+ }
+ return value;
+ }
+}
diff --git a/pkgs/platform/pubspec.yaml b/pkgs/platform/pubspec.yaml
new file mode 100644
index 0000000..2507672
--- /dev/null
+++ b/pkgs/platform/pubspec.yaml
@@ -0,0 +1,17 @@
+name: platform
+description: A pluggable, mockable platform information abstraction for Dart.
+repository: https://github.com/dart-lang/core/tree/main/pkgs/platform
+issue_tracker: https://github.com/dart-lang/core/issues
+version: 3.1.6
+
+topics:
+ - information
+ - platform
+
+environment:
+ sdk: ^3.2.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.1.0
+ test: ^1.16.8
+
diff --git a/pkgs/platform/test/fake_platform_test.dart b/pkgs/platform/test/fake_platform_test.dart
new file mode 100644
index 0000000..1aa7cbf
--- /dev/null
+++ b/pkgs/platform/test/fake_platform_test.dart
@@ -0,0 +1,165 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:io' as io;
+
+import 'package:platform/platform.dart';
+import 'package:test/test.dart';
+
+void _expectPlatformsEqual(Platform actual, Platform expected) {
+ expect(actual.numberOfProcessors, expected.numberOfProcessors);
+ expect(actual.pathSeparator, expected.pathSeparator);
+ expect(actual.operatingSystem, expected.operatingSystem);
+ expect(actual.operatingSystemVersion, expected.operatingSystemVersion);
+ expect(actual.localHostname, expected.localHostname);
+ expect(actual.environment, expected.environment);
+ expect(actual.executable, expected.executable);
+ expect(actual.resolvedExecutable, expected.resolvedExecutable);
+ expect(actual.script, expected.script);
+ expect(actual.executableArguments, expected.executableArguments);
+ expect(actual.packageConfig, expected.packageConfig);
+ expect(actual.version, expected.version);
+ expect(actual.localeName, expected.localeName);
+}
+
+void main() {
+ group('FakePlatform', () {
+ late FakePlatform fake;
+ late LocalPlatform local;
+
+ setUp(() {
+ fake = FakePlatform();
+ local = const LocalPlatform();
+ });
+
+ group('fromPlatform', () {
+ setUp(() {
+ fake = FakePlatform.fromPlatform(local);
+ });
+
+ test('copiesAllProperties', () {
+ _expectPlatformsEqual(fake, local);
+ });
+
+ test('convertsPropertiesToMutable', () {
+ final key = fake.environment.keys.first;
+
+ expect(fake.environment[key], local.environment[key]);
+ fake.environment[key] = 'FAKE';
+ expect(fake.environment[key], 'FAKE');
+
+ expect(
+ fake.executableArguments.length, local.executableArguments.length);
+ fake.executableArguments.add('ARG');
+ expect(fake.executableArguments.last, 'ARG');
+ });
+ });
+
+ group('copyWith', () {
+ setUp(() {
+ fake = FakePlatform.fromPlatform(local);
+ });
+
+ test('overrides a value, but leaves others intact', () {
+ final copy = fake.copyWith(
+ numberOfProcessors: -1,
+ );
+ expect(copy.numberOfProcessors, equals(-1));
+ expect(copy.pathSeparator, local.pathSeparator);
+ expect(copy.operatingSystem, local.operatingSystem);
+ expect(copy.operatingSystemVersion, local.operatingSystemVersion);
+ expect(copy.localHostname, local.localHostname);
+ expect(copy.environment, local.environment);
+ expect(copy.executable, local.executable);
+ expect(copy.resolvedExecutable, local.resolvedExecutable);
+ expect(copy.script, local.script);
+ expect(copy.executableArguments, local.executableArguments);
+ expect(copy.packageConfig, local.packageConfig);
+ expect(copy.version, local.version);
+ expect(copy.localeName, local.localeName);
+ });
+ test('can override all values', () {
+ fake = FakePlatform(
+ numberOfProcessors: 8,
+ pathSeparator: ':',
+ operatingSystem: 'fake',
+ operatingSystemVersion: '0.1.0',
+ localHostname: 'host',
+ environment: <String, String>{'PATH': '.'},
+ executable: 'executable',
+ resolvedExecutable: '/executable',
+ script: Uri.file('/platform/test/fake_platform_test.dart'),
+ executableArguments: <String>['scriptarg'],
+ version: '0.1.1',
+ stdinSupportsAnsi: false,
+ stdoutSupportsAnsi: true,
+ localeName: 'local',
+ );
+ final copy = fake.copyWith(
+ numberOfProcessors: local.numberOfProcessors,
+ pathSeparator: local.pathSeparator,
+ operatingSystem: local.operatingSystem,
+ operatingSystemVersion: local.operatingSystemVersion,
+ localHostname: local.localHostname,
+ environment: local.environment,
+ executable: local.executable,
+ resolvedExecutable: local.resolvedExecutable,
+ script: local.script,
+ executableArguments: local.executableArguments,
+ packageConfig: local.packageConfig,
+ version: local.version,
+ stdinSupportsAnsi: local.stdinSupportsAnsi,
+ stdoutSupportsAnsi: local.stdoutSupportsAnsi,
+ localeName: local.localeName,
+ );
+ _expectPlatformsEqual(copy, local);
+ });
+ });
+
+ group('json', () {
+ test('fromJson', () {
+ final json = io.File('test/platform.json').readAsStringSync();
+ fake = FakePlatform.fromJson(json);
+ expect(fake.numberOfProcessors, 8);
+ expect(fake.pathSeparator, '/');
+ expect(fake.operatingSystem, 'macos');
+ expect(fake.operatingSystemVersion, '10.14.5');
+ expect(fake.localHostname, 'platform.test.org');
+ expect(fake.environment, <String, String>{
+ 'PATH': '/bin',
+ 'PWD': '/platform',
+ });
+ expect(fake.executable, '/bin/dart');
+ expect(fake.resolvedExecutable, '/bin/dart');
+ expect(fake.script, Uri.file('/platform/test/fake_platform_test.dart'));
+ expect(fake.executableArguments, <String>['--checked']);
+ expect(fake.packageConfig, null);
+ expect(fake.version, '1.22.0');
+ expect(fake.localeName, 'de/de');
+ });
+
+ test('fromJsonToJson', () {
+ fake = FakePlatform.fromJson(local.toJson());
+ _expectPlatformsEqual(fake, local);
+ });
+ });
+ });
+
+ test('Throws when unset non-null values are read', () {
+ final platform = FakePlatform();
+
+ expect(() => platform.numberOfProcessors, throwsA(isStateError));
+ expect(() => platform.pathSeparator, throwsA(isStateError));
+ expect(() => platform.operatingSystem, throwsA(isStateError));
+ expect(() => platform.operatingSystemVersion, throwsA(isStateError));
+ expect(() => platform.localHostname, throwsA(isStateError));
+ expect(() => platform.environment, throwsA(isStateError));
+ expect(() => platform.executable, throwsA(isStateError));
+ expect(() => platform.resolvedExecutable, throwsA(isStateError));
+ expect(() => platform.script, throwsA(isStateError));
+ expect(() => platform.executableArguments, throwsA(isStateError));
+ expect(() => platform.version, throwsA(isStateError));
+ expect(() => platform.localeName, throwsA(isStateError));
+ });
+}
diff --git a/pkgs/platform/test/platform.json b/pkgs/platform/test/platform.json
new file mode 100644
index 0000000..60b7c13
--- /dev/null
+++ b/pkgs/platform/test/platform.json
@@ -0,0 +1,20 @@
+{
+ "numberOfProcessors": 8,
+ "pathSeparator": "/",
+ "operatingSystem": "macos",
+ "operatingSystemVersion": "10.14.5",
+ "localHostname": "platform.test.org",
+ "environment": {
+ "PATH": "/bin",
+ "PWD": "/platform"
+ },
+ "executable": "/bin/dart",
+ "resolvedExecutable": "/bin/dart",
+ "script": "file:///platform/test/fake_platform_test.dart",
+ "executableArguments": [
+ "--checked"
+ ],
+ "packageConfig": null,
+ "version": "1.22.0",
+ "localeName": "de/de"
+}
\ No newline at end of file
diff --git a/pkgs/typed_data/.gitignore b/pkgs/typed_data/.gitignore
new file mode 100644
index 0000000..efbbce1
--- /dev/null
+++ b/pkgs/typed_data/.gitignore
@@ -0,0 +1,10 @@
+.buildlog
+.DS_Store
+.idea
+.dart_tool/
+.pub/
+.settings/
+build/
+packages
+.packages
+pubspec.lock
diff --git a/pkgs/typed_data/AUTHORS b/pkgs/typed_data/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/typed_data/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/typed_data/CHANGELOG.md b/pkgs/typed_data/CHANGELOG.md
new file mode 100644
index 0000000..8763a87
--- /dev/null
+++ b/pkgs/typed_data/CHANGELOG.md
@@ -0,0 +1,69 @@
+## 1.4.0
+
+* The type of the `buffer` constructor argument to `TypedDataBuffer` is now
+ `TypeDataList<E>` (instead of `List<E>`). While this is breaking change
+ statically there was a runtime cast that makes this change a no-op in
+ practice.
+* Require Dart 3.5
+* Move to `dart-lang/core` monorepo.
+
+## 1.3.2
+
+* Added package topics to the pubspec file.
+* Require Dart 2.17.
+
+## 1.3.1
+
+* Switch to using `package:lints`.
+* Populate the pubspec `repository` field.
+
+## 1.3.0
+
+* Stable release for null safety.
+* Update SDK constraints to `>=2.12.0-0 <3.0.0` based on beta release
+ guidelines.
+
+## 1.2.0
+
+* Add typed queue classes such as `Uint8Queue`. These classes implement both
+ `Queue` and `List` with a highly-efficient typed-data-backed implementation.
+ Their `sublist()` methods also return typed data classes.
+* Update min Dart SDK to `2.4.0`.
+
+## 1.1.6
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 1.1.5
+
+* Undo unnecessary SDK version constraint tweak.
+
+## 1.1.4
+
+* Expand the SDK version constraint to include `<2.0.0-dev.infinity`.
+
+## 1.1.3
+
+* Fix all strong-mode warnings.
+
+## 1.1.2
+
+* Fix a bug where `TypedDataBuffer.insertAll` could fail to insert some elements
+ of an `Iterable`.
+
+## 1.1.1
+
+* Optimize `insertAll` with an `Iterable` argument and no end-point.
+
+## 1.1.0
+
+* Add `start` and `end` parameters to the `addAll()` and `insertAll()` methods
+ for the typed data buffer classes. These allow efficient concatenation of
+ slices of existing typed data.
+
+* Make `addAll()` for typed data buffer classes more efficient for lists,
+ especially typed data lists.
+
+## 1.0.0
+
+* ChangeLog starts here
diff --git a/pkgs/typed_data/LICENSE b/pkgs/typed_data/LICENSE
new file mode 100644
index 0000000..dbd2843
--- /dev/null
+++ b/pkgs/typed_data/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2015, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/typed_data/README.md b/pkgs/typed_data/README.md
new file mode 100644
index 0000000..512f43b
--- /dev/null
+++ b/pkgs/typed_data/README.md
@@ -0,0 +1,21 @@
+[](https://github.com/dart-lang/core/actions/workflows/typed_data.yaml)
+[](https://pub.dev/packages/typed_data)
+[](https://pub.dev/packages/typed_data/publisher)
+
+Helper libraries for working with typed data lists.
+
+The `typed_data` package contains utility functions and classes that makes working with typed data lists easier.
+
+## Using
+
+The `typed_data` package can be imported using:
+
+```dart
+import 'package:typed_data/typed_data.dart';
+```
+
+## Typed buffers
+
+Typed buffers are growable lists backed by typed arrays. These are similar to
+the growable lists created by `<int>[]` or `<double>[]`, but store typed data
+like a typed data list.
diff --git a/pkgs/typed_data/analysis_options.yaml b/pkgs/typed_data/analysis_options.yaml
new file mode 100644
index 0000000..3806e36
--- /dev/null
+++ b/pkgs/typed_data/analysis_options.yaml
@@ -0,0 +1,12 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+
+linter:
+ rules:
+ - avoid_unused_constructor_parameters
+ - cancel_subscriptions
+ - no_adjacent_strings_in_list
diff --git a/pkgs/typed_data/lib/src/typed_buffer.dart b/pkgs/typed_data/lib/src/typed_buffer.dart
new file mode 100644
index 0000000..dcb2c58
--- /dev/null
+++ b/pkgs/typed_data/lib/src/typed_buffer.dart
@@ -0,0 +1,417 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection' show ListBase;
+import 'dart:typed_data';
+
+abstract class TypedDataBuffer<E> extends ListBase<E> {
+ static const int _initialLength = 8;
+
+ /// The underlying data buffer.
+ TypedDataList<E> _buffer;
+
+ /// The length of the list being built.
+ int _length;
+
+ TypedDataBuffer(TypedDataList<E> buffer)
+ : _buffer = buffer,
+ _length = buffer.length;
+
+ @override
+ int get length => _length;
+
+ @override
+ E operator [](int index) {
+ if (index >= length) throw RangeError.index(index, this);
+ return _buffer[index];
+ }
+
+ @override
+ void operator []=(int index, E value) {
+ if (index >= length) throw RangeError.index(index, this);
+ _buffer[index] = value;
+ }
+
+ @override
+ set length(int newLength) {
+ if (newLength < _length) {
+ var defaultValue = _defaultValue;
+ for (var i = newLength; i < _length; i++) {
+ _buffer[i] = defaultValue;
+ }
+ } else if (newLength > _buffer.length) {
+ TypedDataList<E> newBuffer;
+ if (_buffer.isEmpty) {
+ newBuffer = _createBuffer(newLength);
+ } else {
+ newBuffer = _createBiggerBuffer(newLength);
+ }
+ newBuffer.setRange(0, _length, _buffer);
+ _buffer = newBuffer;
+ }
+ _length = newLength;
+ }
+
+ void _add(E value) {
+ if (_length == _buffer.length) _grow(_length);
+ _buffer[_length++] = value;
+ }
+
+ // We override the default implementation of `add` because it grows the list
+ // by setting the length in increments of one. We want to grow by doubling
+ // capacity in most cases.
+ @override
+ void add(E element) {
+ _add(element);
+ }
+
+ /// Appends all objects of [values] to the end of this buffer.
+ ///
+ /// This adds values from [start] (inclusive) to [end] (exclusive) in
+ /// [values]. If [end] is omitted, it defaults to adding all elements of
+ /// [values] after [start].
+ ///
+ /// The [start] value must be non-negative. The [values] iterable must have at
+ /// least [start] elements, and if [end] is specified, it must be greater than
+ /// or equal to [start] and [values] must have at least [end] elements.
+ @override
+ void addAll(Iterable<E> values, [int start = 0, int? end]) {
+ RangeError.checkNotNegative(start, 'start');
+ if (end != null && start > end) {
+ throw RangeError.range(end, start, null, 'end');
+ }
+
+ _addAll(values, start, end);
+ }
+
+ /// Inserts all objects of [values] at position [index] in this list.
+ ///
+ /// This adds values from [start] (inclusive) to [end] (exclusive) in
+ /// [values]. If [end] is omitted, it defaults to adding all elements of
+ /// [values] after [start].
+ ///
+ /// The [start] value must be non-negative. The [values] iterable must have at
+ /// least [start] elements, and if [end] is specified, it must be greater than
+ /// or equal to [start] and [values] must have at least [end] elements.
+ @override
+ void insertAll(int index, Iterable<E> values, [int start = 0, int? end]) {
+ RangeError.checkValidIndex(index, this, 'index', _length + 1);
+ RangeError.checkNotNegative(start, 'start');
+ if (end != null) {
+ if (start > end) {
+ throw RangeError.range(end, start, null, 'end');
+ }
+ if (start == end) return;
+ }
+
+ // If we're adding to the end of the list anyway, use [_addAll]. This lets
+ // us avoid converting [values] into a list even if [end] is null, since we
+ // can add values iteratively to the end of the list. We can't do so in the
+ // center because copying the trailing elements every time is non-linear.
+ if (index == _length) {
+ _addAll(values, start, end);
+ return;
+ }
+
+ if (end == null && values is List) {
+ end = values.length;
+ }
+ if (end != null) {
+ _insertKnownLength(index, values, start, end);
+ return;
+ }
+
+ // Add elements at end, growing as appropriate, then put them back at
+ // position [index] using flip-by-double-reverse.
+ var writeIndex = _length;
+ var skipCount = start;
+ for (var value in values) {
+ if (skipCount > 0) {
+ skipCount--;
+ continue;
+ }
+ if (writeIndex == _buffer.length) {
+ _grow(writeIndex);
+ }
+ _buffer[writeIndex++] = value;
+ }
+
+ if (skipCount > 0) {
+ throw StateError('Too few elements');
+ }
+ if (end != null && writeIndex < end) {
+ throw RangeError.range(end, start, writeIndex, 'end');
+ }
+
+ // Swap [index.._length) and [_length..writeIndex) by double-reversing.
+ _reverse(_buffer, index, _length);
+ _reverse(_buffer, _length, writeIndex);
+ _reverse(_buffer, index, writeIndex);
+ _length = writeIndex;
+ return;
+ }
+
+ // Reverses the range [start..end) of buffer.
+ static void _reverse(List buffer, int start, int end) {
+ end--; // Point to last element, not after last element.
+ while (start < end) {
+ var first = buffer[start];
+ var last = buffer[end];
+ buffer[end] = first;
+ buffer[start] = last;
+ start++;
+ end--;
+ }
+ }
+
+ /// Does the same thing as [addAll].
+ ///
+ /// This allows [addAll] and [insertAll] to share implementation without a
+ /// subclass unexpectedly overriding both when it intended to only override
+ /// [addAll].
+ void _addAll(Iterable<E> values, [int start = 0, int? end]) {
+ if (values is List) end ??= values.length;
+
+ // If we know the length of the segment to add, do so with [addRange]. This
+ // way we know how much to grow the buffer in advance, and it may be even
+ // more efficient for typed data input.
+ if (end != null) {
+ _insertKnownLength(_length, values, start, end);
+ return;
+ }
+
+ // Otherwise, just add values one at a time.
+ var i = 0;
+ for (var value in values) {
+ if (i >= start) add(value);
+ i++;
+ }
+ if (i < start) throw StateError('Too few elements');
+ }
+
+ /// Like [insertAll], but with a guaranteed non-`null` [start] and [end].
+ void _insertKnownLength(int index, Iterable<E> values, int start, int end) {
+ if (values is List) {
+ if (start > values.length || end > values.length) {
+ throw StateError('Too few elements');
+ }
+ }
+
+ var valuesLength = end - start;
+ var newLength = _length + valuesLength;
+ _ensureCapacity(newLength);
+
+ _buffer.setRange(
+ index + valuesLength, _length + valuesLength, _buffer, index);
+ _buffer.setRange(index, index + valuesLength, values, start);
+ _length = newLength;
+ }
+
+ @override
+ void insert(int index, E element) {
+ if (index < 0 || index > _length) {
+ throw RangeError.range(index, 0, _length);
+ }
+ if (_length < _buffer.length) {
+ _buffer.setRange(index + 1, _length + 1, _buffer, index);
+ _buffer[index] = element;
+ _length++;
+ return;
+ }
+ var newBuffer = _createBiggerBuffer(null);
+ newBuffer.setRange(0, index, _buffer);
+ newBuffer.setRange(index + 1, _length + 1, _buffer, index);
+ newBuffer[index] = element;
+ _length++;
+ _buffer = newBuffer;
+ }
+
+ /// Ensures that [_buffer] is at least [requiredCapacity] long,
+ ///
+ /// Grows the buffer if necessary, preserving existing data.
+ void _ensureCapacity(int requiredCapacity) {
+ if (requiredCapacity <= _buffer.length) return;
+ var newBuffer = _createBiggerBuffer(requiredCapacity);
+ newBuffer.setRange(0, _length, _buffer);
+ _buffer = newBuffer;
+ }
+
+ /// Create a bigger buffer.
+ ///
+ /// This method determines how much bigger a bigger buffer should
+ /// be. If [requiredCapacity] is not null, it will be at least that
+ /// size. It will always have at least have double the capacity of
+ /// the current buffer.
+ TypedDataList<E> _createBiggerBuffer(int? requiredCapacity) {
+ var newLength = _buffer.length * 2;
+ if (requiredCapacity != null && newLength < requiredCapacity) {
+ newLength = requiredCapacity;
+ } else if (newLength < _initialLength) {
+ newLength = _initialLength;
+ }
+ return _createBuffer(newLength);
+ }
+
+ /// Grows the buffer.
+ ///
+ /// This copies the first [length] elements into the new buffer.
+ void _grow(int length) {
+ _buffer = _createBiggerBuffer(null)..setRange(0, length, _buffer);
+ }
+
+ @override
+ void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
+ if (end > _length) throw RangeError.range(end, 0, _length);
+ _setRange(start, end, iterable, skipCount);
+ }
+
+ /// Like [setRange], but with no bounds checking.
+ void _setRange(int start, int end, Iterable<E> source, int skipCount) {
+ if (source is TypedDataBuffer<E>) {
+ _buffer.setRange(start, end, source._buffer, skipCount);
+ } else {
+ _buffer.setRange(start, end, source, skipCount);
+ }
+ }
+
+ // TypedData.
+
+ int get elementSizeInBytes => _buffer.elementSizeInBytes;
+
+ int get lengthInBytes => _length * _buffer.elementSizeInBytes;
+
+ int get offsetInBytes => _buffer.offsetInBytes;
+
+ /// Returns the underlying [ByteBuffer].
+ ///
+ /// The returned buffer may be replaced by operations that change the [length]
+ /// of this list.
+ ///
+ /// The buffer may be larger than [lengthInBytes] bytes, but never smaller.
+ ByteBuffer get buffer => _buffer.buffer;
+
+ // Specialization for the specific type.
+
+ // Return zero for integers, 0.0 for floats, etc.
+ // Used to fill buffer when changing length.
+ E get _defaultValue;
+
+ // Create a new typed list to use as buffer.
+ TypedDataList<E> _createBuffer(int size);
+}
+
+abstract class _IntBuffer extends TypedDataBuffer<int> {
+ _IntBuffer(super.buffer);
+
+ @override
+ int get _defaultValue => 0;
+}
+
+abstract class _FloatBuffer extends TypedDataBuffer<double> {
+ _FloatBuffer(super.buffer);
+
+ @override
+ double get _defaultValue => 0.0;
+}
+
+class Uint8Buffer extends _IntBuffer {
+ Uint8Buffer([int initialLength = 0]) : super(Uint8List(initialLength));
+
+ @override
+ Uint8List _createBuffer(int size) => Uint8List(size);
+}
+
+class Int8Buffer extends _IntBuffer {
+ Int8Buffer([int initialLength = 0]) : super(Int8List(initialLength));
+
+ @override
+ Int8List _createBuffer(int size) => Int8List(size);
+}
+
+class Uint8ClampedBuffer extends _IntBuffer {
+ Uint8ClampedBuffer([int initialLength = 0])
+ : super(Uint8ClampedList(initialLength));
+
+ @override
+ Uint8ClampedList _createBuffer(int size) => Uint8ClampedList(size);
+}
+
+class Uint16Buffer extends _IntBuffer {
+ Uint16Buffer([int initialLength = 0]) : super(Uint16List(initialLength));
+
+ @override
+ Uint16List _createBuffer(int size) => Uint16List(size);
+}
+
+class Int16Buffer extends _IntBuffer {
+ Int16Buffer([int initialLength = 0]) : super(Int16List(initialLength));
+
+ @override
+ Int16List _createBuffer(int size) => Int16List(size);
+}
+
+class Uint32Buffer extends _IntBuffer {
+ Uint32Buffer([int initialLength = 0]) : super(Uint32List(initialLength));
+
+ @override
+ Uint32List _createBuffer(int size) => Uint32List(size);
+}
+
+class Int32Buffer extends _IntBuffer {
+ Int32Buffer([int initialLength = 0]) : super(Int32List(initialLength));
+
+ @override
+ Int32List _createBuffer(int size) => Int32List(size);
+}
+
+class Uint64Buffer extends _IntBuffer {
+ Uint64Buffer([int initialLength = 0]) : super(Uint64List(initialLength));
+
+ @override
+ Uint64List _createBuffer(int size) => Uint64List(size);
+}
+
+class Int64Buffer extends _IntBuffer {
+ Int64Buffer([int initialLength = 0]) : super(Int64List(initialLength));
+
+ @override
+ Int64List _createBuffer(int size) => Int64List(size);
+}
+
+class Float32Buffer extends _FloatBuffer {
+ Float32Buffer([int initialLength = 0]) : super(Float32List(initialLength));
+
+ @override
+ Float32List _createBuffer(int size) => Float32List(size);
+}
+
+class Float64Buffer extends _FloatBuffer {
+ Float64Buffer([int initialLength = 0]) : super(Float64List(initialLength));
+
+ @override
+ Float64List _createBuffer(int size) => Float64List(size);
+}
+
+class Int32x4Buffer extends TypedDataBuffer<Int32x4> {
+ static final Int32x4 _zero = Int32x4(0, 0, 0, 0);
+
+ Int32x4Buffer([int initialLength = 0]) : super(Int32x4List(initialLength));
+
+ @override
+ Int32x4 get _defaultValue => _zero;
+
+ @override
+ Int32x4List _createBuffer(int size) => Int32x4List(size);
+}
+
+class Float32x4Buffer extends TypedDataBuffer<Float32x4> {
+ Float32x4Buffer([int initialLength = 0])
+ : super(Float32x4List(initialLength));
+
+ @override
+ Float32x4 get _defaultValue => Float32x4.zero();
+
+ @override
+ Float32x4List _createBuffer(int size) => Float32x4List(size);
+}
diff --git a/pkgs/typed_data/lib/src/typed_queue.dart b/pkgs/typed_data/lib/src/typed_queue.dart
new file mode 100644
index 0000000..84ce529
--- /dev/null
+++ b/pkgs/typed_data/lib/src/typed_queue.dart
@@ -0,0 +1,695 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+import 'dart:typed_data';
+
+import 'package:collection/collection.dart';
+
+import 'typed_buffer.dart';
+
+/// The shared superclass of all the typed queue subclasses.
+abstract class _TypedQueue<E, L extends TypedDataList<E>> with ListMixin<E> {
+ /// The underlying data buffer.
+ TypedDataList<E> _table;
+
+ int _head;
+ int _tail;
+
+ /// Create an empty queue.
+ _TypedQueue(this._table)
+ : _head = 0,
+ _tail = 0;
+
+ // Iterable interface.
+
+ @override
+ int get length => (_tail - _head) & (_table.length - 1);
+
+ @override
+ List<E> toList({bool growable = true}) {
+ var list = growable ? _createBuffer(length) : _createList(length);
+ _writeToList(list);
+ return list;
+ }
+
+ @override
+ QueueList<T> cast<T>() {
+ if (this is QueueList<T>) return this as QueueList<T>;
+ throw UnsupportedError('$this cannot be cast to the desired type.');
+ }
+
+ @Deprecated('Use `cast` instead')
+ QueueList<T> retype<T>() => cast<T>();
+
+ // Queue interface.
+
+ void addLast(E value) {
+ _table[_tail] = value;
+ _tail = (_tail + 1) & (_table.length - 1);
+ if (_head == _tail) _growAtCapacity();
+ }
+
+ void addFirst(E value) {
+ _head = (_head - 1) & (_table.length - 1);
+ _table[_head] = value;
+ if (_head == _tail) _growAtCapacity();
+ }
+
+ E removeFirst() {
+ if (_head == _tail) throw StateError('No element');
+ var result = _table[_head];
+ _head = (_head + 1) & (_table.length - 1);
+ return result;
+ }
+
+ @override
+ E removeLast() {
+ if (_head == _tail) throw StateError('No element');
+ _tail = (_tail - 1) & (_table.length - 1);
+ return _table[_tail];
+ }
+
+ // List interface.
+
+ @override
+ void add(E value) => addLast(value);
+
+ @override
+ set length(int value) {
+ RangeError.checkNotNegative(value, 'length');
+
+ var delta = value - length;
+ if (delta >= 0) {
+ var needsToGrow = _table.length <= value;
+ if (needsToGrow) _growTo(value);
+ _tail = (_tail + delta) & (_table.length - 1);
+
+ // If we didn't copy into a new table, make sure that we overwrite the
+ // existing data so that users don't accidentally depend on it still
+ // existing.
+ if (!needsToGrow) fillRange(value - delta, value, _defaultValue);
+ } else {
+ removeRange(value, length);
+ }
+ }
+
+ @override
+ E operator [](int index) {
+ RangeError.checkValidIndex(index, this, null, length);
+ return _table[(_head + index) & (_table.length - 1)];
+ }
+
+ @override
+ void operator []=(int index, E value) {
+ RangeError.checkValidIndex(index, this);
+ _table[(_head + index) & (_table.length - 1)] = value;
+ }
+
+ @override
+ void removeRange(int start, int end) {
+ var length = this.length;
+ RangeError.checkValidRange(start, end, length);
+
+ // Special-case removing an initial or final range because we can do it very
+ // efficiently by adjusting `_head` or `_tail`.
+ if (start == 0) {
+ _head = (_head + end) & (_table.length - 1);
+ return;
+ }
+
+ var elementsAfter = length - end;
+ if (elementsAfter == 0) {
+ _tail = (_head + start) & (_table.length - 1);
+ return;
+ }
+
+ // Choose whether to copy from the beginning of the end of the queue based
+ // on which will require fewer copied elements.
+ var removedElements = end - start;
+ if (start < elementsAfter) {
+ setRange(removedElements, end, this);
+ _head = (_head + removedElements) & (_table.length - 1);
+ } else {
+ setRange(start, length - removedElements, this, end);
+ _tail = (_tail - removedElements) & (_table.length - 1);
+ }
+ }
+
+ @override
+ void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
+ RangeError.checkValidRange(start, end, length);
+ if (start == end) return;
+
+ var targetStart = (_head + start) & (_table.length - 1);
+ var targetEnd = (_head + end) & (_table.length - 1);
+ var targetIsContiguous = targetStart < targetEnd;
+ if (identical(iterable, this)) {
+ // If we're copying this queue to itself, we can copy [_table] in directly
+ // which requires some annoying case analysis but in return bottoms out on
+ // an extremely efficient `memmove` call. However, we may need to do three
+ // copies to avoid overwriting data we'll need to use later.
+ var sourceStart = (_head + skipCount) & (_table.length - 1);
+ var sourceEnd = (sourceStart + (end - start)) & (_table.length - 1);
+ if (sourceStart == targetStart) return;
+
+ var sourceIsContiguous = sourceStart < sourceEnd;
+ if (targetIsContiguous && sourceIsContiguous) {
+ // If both the source and destination ranges are contiguous, we can
+ // do a single [setRange]. Hooray!
+ _table.setRange(targetStart, targetEnd, _table, sourceStart);
+ } else if (!targetIsContiguous && !sourceIsContiguous) {
+ // If neither range is contiguous, we need to do three copies.
+ if (sourceStart > targetStart) {
+ // [=====| targetEnd targetStart |======]
+ // [========| sourceEnd sourceStart |===]
+
+ // Copy front to back.
+ var startGap = sourceStart - targetStart;
+ var firstEnd = _table.length - startGap;
+ _table.setRange(targetStart, firstEnd, _table, sourceStart);
+ _table.setRange(firstEnd, _table.length, _table);
+ _table.setRange(0, targetEnd, _table, startGap);
+ } else if (sourceEnd < targetEnd) {
+ // [=====| targetEnd targetStart |======]
+ // [==| sourceEnd sourceStart |=========]
+
+ // Copy back to front.
+ var firstStart = targetEnd - sourceEnd;
+ _table.setRange(firstStart, targetEnd, _table);
+ _table.setRange(0, firstStart, _table, _table.length - firstStart);
+ _table.setRange(targetStart, _table.length, _table, sourceStart);
+ }
+ } else if (sourceStart < targetEnd) {
+ // Copying twice is safe here as long as we copy front to back.
+ if (sourceIsContiguous) {
+ // [=====| targetEnd targetStart |======]
+ // [ |===========| sourceEnd ]
+ // sourceStart
+ _table.setRange(targetStart, _table.length, _table, sourceStart);
+ _table.setRange(0, targetEnd, _table,
+ sourceStart + (_table.length - targetStart));
+ } else {
+ // targetEnd
+ // [ targetStart |===========| ]
+ // [=====| sourceEnd sourceStart |======]
+ var firstEnd = _table.length - sourceStart;
+ _table.setRange(targetStart, firstEnd, _table, sourceStart);
+ _table.setRange(firstEnd, targetEnd, _table);
+ }
+ } else {
+ // Copying twice is safe here as long as we copy back to front. This
+ // also covers the case where there's no overlap between the source and
+ // target ranges, in which case the direction doesn't matter.
+ if (sourceIsContiguous) {
+ // [=====| targetEnd targetStart |======]
+ // [ sourceStart |===========| ]
+ // sourceEnd
+ _table.setRange(0, targetEnd, _table,
+ sourceStart + (_table.length - targetStart));
+ _table.setRange(targetStart, _table.length, _table, sourceStart);
+ } else {
+ // targetStart
+ // [ |===========| targetEnd ]
+ // [=====| sourceEnd sourceStart |======]
+ var firstStart = targetEnd - sourceEnd;
+ _table.setRange(firstStart, targetEnd, _table);
+ _table.setRange(targetStart, firstStart, _table, sourceStart);
+ }
+ }
+ } else if (targetIsContiguous) {
+ // If the range is contiguous within the table, we can set it with a
+ // single underlying [setRange] call.
+ _table.setRange(targetStart, targetEnd, iterable, skipCount);
+ } else if (iterable is List<E>) {
+ // If the range isn't contiguous and [iterable] is actually a [List] (but
+ // not this queue), set it with two underlying [setRange] calls.
+ _table.setRange(targetStart, _table.length, iterable, skipCount);
+ _table.setRange(
+ 0, targetEnd, iterable, skipCount + (_table.length - targetStart));
+ } else {
+ // If [iterable] isn't a [List], we don't want to make two different
+ // [setRange] calls because it could materialize a lazy iterable twice.
+ // Instead we just fall back to the default iteration-based
+ // implementation.
+ super.setRange(start, end, iterable, skipCount);
+ }
+ }
+
+ @override
+ void fillRange(int start, int end, [E? value]) {
+ var startInTable = (_head + start) & (_table.length - 1);
+ var endInTable = (_head + end) & (_table.length - 1);
+ if (startInTable <= endInTable) {
+ _table.fillRange(startInTable, endInTable, value);
+ } else {
+ _table.fillRange(startInTable, _table.length, value);
+ _table.fillRange(0, endInTable, value);
+ }
+ }
+
+ @override
+ L sublist(int start, [int? end]) {
+ var length = this.length;
+ var nonNullEnd = RangeError.checkValidRange(start, end, length);
+
+ var list = _createList(nonNullEnd - start);
+ _writeToList(list, start, nonNullEnd);
+ return list;
+ }
+
+ // Internal helper functions.
+
+ /// Writes the contents of `this` between [start] (which defaults to 0) and
+ /// [end] (which defaults to [length]) to the beginning of [target].
+ ///
+ /// This is functionally identical to `target.setRange(0, end - start, this,
+ /// start)`, but it's more efficient when [target] is typed data.
+ ///
+ /// Returns the number of elements written to [target].
+ int _writeToList(List<E> target, [int? start, int? end]) {
+ start ??= 0;
+ end ??= length;
+ assert(target.length >= end - start);
+ assert(start <= end);
+
+ var elementsToWrite = end - start;
+ var startInTable = (_head + start) & (_table.length - 1);
+ var endInTable = (_head + end) & (_table.length - 1);
+ if (startInTable <= endInTable) {
+ target.setRange(0, elementsToWrite, _table, startInTable);
+ } else {
+ var firstPartSize = _table.length - startInTable;
+ target.setRange(0, firstPartSize, _table, startInTable);
+ target.setRange(firstPartSize, firstPartSize + endInTable, _table, 0);
+ }
+ return elementsToWrite;
+ }
+
+ /// Assumes the table is currently full to capacity, and grows it to the next
+ /// power of two.
+ void _growAtCapacity() {
+ assert(_head == _tail);
+
+ var newTable = _createList(_table.length * 2);
+
+ // We can't use [_writeToList] here because when `_head == _tail` it thinks
+ // the queue is empty rather than full.
+ var partitionPoint = _table.length - _head;
+ newTable.setRange(0, partitionPoint, _table, _head);
+ if (partitionPoint != _table.length) {
+ newTable.setRange(partitionPoint, _table.length, _table);
+ }
+ _head = 0;
+ _tail = _table.length;
+ _table = newTable;
+ }
+
+ /// Grows the tableso it's at least large enough size to include that many
+ /// elements.
+ void _growTo(int newElementCount) {
+ assert(newElementCount >= length);
+
+ // Add some extra room to ensure that there's room for more elements after
+ // expansion.
+ newElementCount += newElementCount >> 1;
+ var newTable = _createList(_nextPowerOf2(newElementCount));
+ _tail = _writeToList(newTable);
+ _table = newTable;
+ _head = 0;
+ }
+
+ // Specialization for the specific type.
+
+ // Create a new typed list.
+ L _createList(int size);
+
+ // Create a new typed buffer of the given type.
+ TypedDataBuffer<E> _createBuffer(int size);
+
+ /// The default value used to fill the queue when changing length.
+ E get _defaultValue;
+}
+
+abstract class _IntQueue<L extends TypedDataList<int>>
+ extends _TypedQueue<int, L> {
+ _IntQueue(super.queue);
+
+ @override
+ int get _defaultValue => 0;
+}
+
+abstract class _FloatQueue<L extends TypedDataList<double>>
+ extends _TypedQueue<double, L> {
+ _FloatQueue(super.queue);
+
+ @override
+ double get _defaultValue => 0.0;
+}
+
+/// A [QueueList] that efficiently stores 8-bit unsigned integers.
+///
+/// For long queues, this implementation can be considerably more space- and
+/// time-efficient than a default [QueueList] implementation.
+///
+/// Integers stored in this are truncated to their low eight bits, interpreted
+/// as an unsigned 8-bit integer with values in the range 0 to 255.
+class Uint8Queue extends _IntQueue<Uint8List> implements QueueList<int> {
+ /// Creates an empty [Uint8Queue] with the given initial internal capacity (in
+ /// elements).
+ Uint8Queue([int? initialCapacity])
+ : super(Uint8List(_chooseRealInitialCapacity(initialCapacity)));
+
+ /// Creates a [Uint8Queue] with the same length and contents as [elements].
+ factory Uint8Queue.fromList(List<int> elements) =>
+ Uint8Queue(elements.length)..addAll(elements);
+
+ @override
+ Uint8List _createList(int size) => Uint8List(size);
+ @override
+ Uint8Buffer _createBuffer(int size) => Uint8Buffer(size);
+}
+
+/// A [QueueList] that efficiently stores 8-bit signed integers.
+///
+/// For long queues, this implementation can be considerably more space- and
+/// time-efficient than a default [QueueList] implementation.
+///
+/// Integers stored in this are truncated to their low eight bits, interpreted
+/// as a signed 8-bit two's complement integer with values in the range -128 to
+/// +127.
+class Int8Queue extends _IntQueue<Int8List> implements QueueList<int> {
+ /// Creates an empty [Int8Queue] with the given initial internal capacity (in
+ /// elements).
+ Int8Queue([int? initialCapacity])
+ : super(Int8List(_chooseRealInitialCapacity(initialCapacity)));
+
+ /// Creates a [Int8Queue] with the same length and contents as [elements].
+ factory Int8Queue.fromList(List<int> elements) =>
+ Int8Queue(elements.length)..addAll(elements);
+
+ @override
+ Int8List _createList(int size) => Int8List(size);
+ @override
+ Int8Buffer _createBuffer(int size) => Int8Buffer(size);
+}
+
+/// A [QueueList] that efficiently stores 8-bit unsigned integers.
+///
+/// For long queues, this implementation can be considerably more space- and
+/// time-efficient than a default [QueueList] implementation.
+///
+/// Integers stored in this are clamped to an unsigned eight bit value. That is,
+/// all values below zero are stored as zero and all values above 255 are stored
+/// as 255.
+class Uint8ClampedQueue extends _IntQueue<Uint8ClampedList>
+ implements QueueList<int> {
+ /// Creates an empty [Uint8ClampedQueue] with the given initial internal
+ /// capacity (in elements).
+ Uint8ClampedQueue([int? initialCapacity])
+ : super(Uint8ClampedList(_chooseRealInitialCapacity(initialCapacity)));
+
+ /// Creates a [Uint8ClampedQueue] with the same length and contents as
+ /// [elements].
+ factory Uint8ClampedQueue.fromList(List<int> elements) =>
+ Uint8ClampedQueue(elements.length)..addAll(elements);
+
+ @override
+ Uint8ClampedList _createList(int size) => Uint8ClampedList(size);
+ @override
+ Uint8ClampedBuffer _createBuffer(int size) => Uint8ClampedBuffer(size);
+}
+
+/// A [QueueList] that efficiently stores 16-bit unsigned integers.
+///
+/// For long queues, this implementation can be considerably more space- and
+/// time-efficient than a default [QueueList] implementation.
+///
+/// Integers stored in this are truncated to their low 16 bits, interpreted as
+/// an unsigned 16-bit integer with values in the range 0 to 65535.
+class Uint16Queue extends _IntQueue<Uint16List> implements QueueList<int> {
+ /// Creates an empty [Uint16Queue] with the given initial internal capacity
+ /// (in elements).
+ Uint16Queue([int? initialCapacity])
+ : super(Uint16List(_chooseRealInitialCapacity(initialCapacity)));
+
+ /// Creates a [Uint16Queue] with the same length and contents as [elements].
+ factory Uint16Queue.fromList(List<int> elements) =>
+ Uint16Queue(elements.length)..addAll(elements);
+
+ @override
+ Uint16List _createList(int size) => Uint16List(size);
+ @override
+ Uint16Buffer _createBuffer(int size) => Uint16Buffer(size);
+}
+
+/// A [QueueList] that efficiently stores 16-bit signed integers.
+///
+/// For long queues, this implementation can be considerably more space- and
+/// time-efficient than a default [QueueList] implementation.
+///
+/// Integers stored in this are truncated to their low 16 bits, interpreted as a
+/// signed 16-bit two's complement integer with values in the range -32768 to
+/// +32767.
+class Int16Queue extends _IntQueue<Int16List> implements QueueList<int> {
+ /// Creates an empty [Int16Queue] with the given initial internal capacity (in
+ /// elements).
+ Int16Queue([int? initialCapacity])
+ : super(Int16List(_chooseRealInitialCapacity(initialCapacity)));
+
+ /// Creates a [Int16Queue] with the same length and contents as [elements].
+ factory Int16Queue.fromList(List<int> elements) =>
+ Int16Queue(elements.length)..addAll(elements);
+
+ @override
+ Int16List _createList(int size) => Int16List(size);
+ @override
+ Int16Buffer _createBuffer(int size) => Int16Buffer(size);
+}
+
+/// A [QueueList] that efficiently stores 32-bit unsigned integers.
+///
+/// For long queues, this implementation can be considerably more space- and
+/// time-efficient than a default [QueueList] implementation.
+///
+/// Integers stored in this are truncated to their low 32 bits, interpreted as
+/// an unsigned 32-bit integer with values in the range 0 to 4294967295.
+class Uint32Queue extends _IntQueue<Uint32List> implements QueueList<int> {
+ /// Creates an empty [Uint32Queue] with the given initial internal capacity
+ /// (in elements).
+ Uint32Queue([int? initialCapacity])
+ : super(Uint32List(_chooseRealInitialCapacity(initialCapacity)));
+
+ /// Creates a [Uint32Queue] with the same length and contents as [elements].
+ factory Uint32Queue.fromList(List<int> elements) =>
+ Uint32Queue(elements.length)..addAll(elements);
+
+ @override
+ Uint32List _createList(int size) => Uint32List(size);
+ @override
+ Uint32Buffer _createBuffer(int size) => Uint32Buffer(size);
+}
+
+/// A [QueueList] that efficiently stores 32-bit signed integers.
+///
+/// For long queues, this implementation can be considerably more space- and
+/// time-efficient than a default [QueueList] implementation.
+///
+/// Integers stored in this are truncated to their low 32 bits, interpreted as a
+/// signed 32-bit two's complement integer with values in the range -2147483648
+/// to 2147483647.
+class Int32Queue extends _IntQueue<Int32List> implements QueueList<int> {
+ /// Creates an empty [Int32Queue] with the given initial internal capacity (in
+ /// elements).
+ Int32Queue([int? initialCapacity])
+ : super(Int32List(_chooseRealInitialCapacity(initialCapacity)));
+
+ /// Creates a [Int32Queue] with the same length and contents as [elements].
+ factory Int32Queue.fromList(List<int> elements) =>
+ Int32Queue(elements.length)..addAll(elements);
+
+ @override
+ Int32List _createList(int size) => Int32List(size);
+ @override
+ Int32Buffer _createBuffer(int size) => Int32Buffer(size);
+}
+
+/// A [QueueList] that efficiently stores 64-bit unsigned integers.
+///
+/// For long queues, this implementation can be considerably more space- and
+/// time-efficient than a default [QueueList] implementation.
+///
+/// Integers stored in this are truncated to their low 64 bits, interpreted as
+/// an unsigned 64-bit integer with values in the range 0 to
+/// 18446744073709551615.
+class Uint64Queue extends _IntQueue<Uint64List> implements QueueList<int> {
+ /// Creates an empty [Uint64Queue] with the given initial internal capacity
+ /// (in elements).
+ Uint64Queue([int? initialCapacity])
+ : super(Uint64List(_chooseRealInitialCapacity(initialCapacity)));
+
+ /// Creates a [Uint64Queue] with the same length and contents as [elements].
+ factory Uint64Queue.fromList(List<int> elements) =>
+ Uint64Queue(elements.length)..addAll(elements);
+
+ @override
+ Uint64List _createList(int size) => Uint64List(size);
+ @override
+ Uint64Buffer _createBuffer(int size) => Uint64Buffer(size);
+}
+
+/// A [QueueList] that efficiently stores 64-bit signed integers.
+///
+/// For long queues, this implementation can be considerably more space- and
+/// time-efficient than a default [QueueList] implementation.
+///
+/// Integers stored in this are truncated to their low 64 bits, interpreted as a
+/// signed 64-bit two's complement integer with values in the range
+/// -9223372036854775808 to +9223372036854775807.
+class Int64Queue extends _IntQueue<Int64List> implements QueueList<int> {
+ /// Creates an empty [Int64Queue] with the given initial internal capacity (in
+ /// elements).
+ Int64Queue([int? initialCapacity])
+ : super(Int64List(_chooseRealInitialCapacity(initialCapacity)));
+
+ /// Creates a [Int64Queue] with the same length and contents as [elements].
+ factory Int64Queue.fromList(List<int> elements) =>
+ Int64Queue(elements.length)..addAll(elements);
+
+ @override
+ Int64List _createList(int size) => Int64List(size);
+ @override
+ Int64Buffer _createBuffer(int size) => Int64Buffer(size);
+}
+
+/// A [QueueList] that efficiently stores IEEE 754 single-precision binary
+/// floating-point numbers.
+///
+/// For long queues, this implementation can be considerably more space- and
+/// time-efficient than a default [QueueList] implementation.
+///
+/// Doubles stored in this are converted to the nearest single-precision value.
+/// Values read are converted to a double value with the same value.
+class Float32Queue extends _FloatQueue<Float32List>
+ implements QueueList<double> {
+ /// Creates an empty [Float32Queue] with the given initial internal capacity
+ /// (in elements).
+ Float32Queue([int? initialCapacity])
+ : super(Float32List(_chooseRealInitialCapacity(initialCapacity)));
+
+ /// Creates a [Float32Queue] with the same length and contents as [elements].
+ factory Float32Queue.fromList(List<double> elements) =>
+ Float32Queue(elements.length)..addAll(elements);
+
+ @override
+ Float32List _createList(int size) => Float32List(size);
+ @override
+ Float32Buffer _createBuffer(int size) => Float32Buffer(size);
+}
+
+/// A [QueueList] that efficiently stores IEEE 754 double-precision binary
+/// floating-point numbers.
+///
+/// For long queues, this implementation can be considerably more space- and
+/// time-efficient than a default [QueueList] implementation.
+class Float64Queue extends _FloatQueue<Float64List>
+ implements QueueList<double> {
+ /// Creates an empty [Float64Queue] with the given initial internal capacity
+ /// (in elements).
+ Float64Queue([int? initialCapacity])
+ : super(Float64List(_chooseRealInitialCapacity(initialCapacity)));
+
+ /// Creates a [Float64Queue] with the same length and contents as [elements].
+ factory Float64Queue.fromList(List<double> elements) =>
+ Float64Queue(elements.length)..addAll(elements);
+
+ @override
+ Float64List _createList(int size) => Float64List(size);
+ @override
+ Float64Buffer _createBuffer(int size) => Float64Buffer(size);
+}
+
+/// A [QueueList] that efficiently stores [Int32x4] numbers.
+///
+/// For long queues, this implementation can be considerably more space- and
+/// time-efficient than a default [QueueList] implementation.
+class Int32x4Queue extends _TypedQueue<Int32x4, Int32x4List>
+ implements QueueList<Int32x4> {
+ static final Int32x4 _zero = Int32x4(0, 0, 0, 0);
+
+ /// Creates an empty [Int32x4Queue] with the given initial internal capacity
+ /// (in elements).
+ Int32x4Queue([int? initialCapacity])
+ : super(Int32x4List(_chooseRealInitialCapacity(initialCapacity)));
+
+ /// Creates a [Int32x4Queue] with the same length and contents as [elements].
+ factory Int32x4Queue.fromList(List<Int32x4> elements) =>
+ Int32x4Queue(elements.length)..addAll(elements);
+
+ @override
+ Int32x4List _createList(int size) => Int32x4List(size);
+ @override
+ Int32x4Buffer _createBuffer(int size) => Int32x4Buffer(size);
+ @override
+ Int32x4 get _defaultValue => _zero;
+}
+
+/// A [QueueList] that efficiently stores [Float32x4] numbers.
+///
+/// For long queues, this implementation can be considerably more space- and
+/// time-efficient than a default [QueueList] implementation.
+class Float32x4Queue extends _TypedQueue<Float32x4, Float32x4List>
+ implements QueueList<Float32x4> {
+ /// Creates an empty [Float32x4Queue] with the given initial internal capacity
+ /// (in elements).
+ Float32x4Queue([int? initialCapacity])
+ : super(Float32x4List(_chooseRealInitialCapacity(initialCapacity)));
+
+ /// Creates a [Float32x4Queue] with the same length and contents as
+ /// [elements].
+ factory Float32x4Queue.fromList(List<Float32x4> elements) =>
+ Float32x4Queue(elements.length)..addAll(elements);
+
+ @override
+ Float32x4List _createList(int size) => Float32x4List(size);
+ @override
+ Float32x4Buffer _createBuffer(int size) => Float32x4Buffer(size);
+ @override
+ Float32x4 get _defaultValue => Float32x4.zero();
+}
+
+/// The initial capacity of queues if the user doesn't specify one.
+const _defaultInitialCapacity = 16;
+
+/// Choose the next-highest power of two given a user-specified
+/// [initialCapacity] for a queue.
+int _chooseRealInitialCapacity(int? initialCapacity) {
+ if (initialCapacity == null || initialCapacity < _defaultInitialCapacity) {
+ return _defaultInitialCapacity;
+ } else if (!_isPowerOf2(initialCapacity)) {
+ return _nextPowerOf2(initialCapacity);
+ } else {
+ return initialCapacity;
+ }
+}
+
+/// Whether [number] is a power of two.
+///
+/// Only works for positive numbers.
+bool _isPowerOf2(int number) => (number & (number - 1)) == 0;
+
+/// Rounds [number] up to the nearest power of 2.
+///
+/// If [number] is a power of 2 already, it is returned.
+///
+/// Only works for positive numbers.
+int _nextPowerOf2(int number) {
+ assert(number > 0);
+ number = (number << 1) - 1;
+ for (;;) {
+ var nextNumber = number & (number - 1);
+ if (nextNumber == 0) return number;
+ number = nextNumber;
+ }
+}
diff --git a/pkgs/typed_data/lib/typed_buffers.dart b/pkgs/typed_data/lib/typed_buffers.dart
new file mode 100644
index 0000000..05c8993
--- /dev/null
+++ b/pkgs/typed_data/lib/typed_buffers.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Growable typed-data lists.
+///
+/// These lists works just as a typed-data list, except that they are growable.
+/// They use an underlying buffer, and when that buffer becomes too small, it
+/// is replaced by a new buffer.
+///
+/// That means that using the `buffer` getter is not guaranteed
+/// to return the same result each time it is used, and that the buffer may
+/// be larger than what the list is using.
+library;
+
+export 'src/typed_buffer.dart' hide TypedDataBuffer;
diff --git a/pkgs/typed_data/lib/typed_data.dart b/pkgs/typed_data/lib/typed_data.dart
new file mode 100644
index 0000000..cc94c32
--- /dev/null
+++ b/pkgs/typed_data/lib/typed_data.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Utilities and functionality related to the "dart:typed_data" library.
+library;
+
+export 'src/typed_queue.dart';
+export 'typed_buffers.dart';
diff --git a/pkgs/typed_data/pubspec.yaml b/pkgs/typed_data/pubspec.yaml
new file mode 100644
index 0000000..6c93be6
--- /dev/null
+++ b/pkgs/typed_data/pubspec.yaml
@@ -0,0 +1,19 @@
+name: typed_data
+version: 1.4.0
+description: >-
+ Utility functions and classes related to the dart:typed_data library.
+repository: https://github.com/dart-lang/core/tree/main/pkgs/typed_data
+issue_tracker: https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Atyped_data
+
+topics:
+ - data-structures
+
+environment:
+ sdk: ^3.5.0
+
+dependencies:
+ collection: ^1.15.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
diff --git a/pkgs/typed_data/test/queue_test.dart b/pkgs/typed_data/test/queue_test.dart
new file mode 100644
index 0000000..c0b1368
--- /dev/null
+++ b/pkgs/typed_data/test/queue_test.dart
@@ -0,0 +1,315 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: avoid_function_literals_in_foreach_calls
+
+import 'package:test/test.dart';
+import 'package:typed_data/typed_data.dart';
+
+/// The initial capacity of queues if the user doesn't specify one.
+const capacity = 16;
+
+void main() {
+ group('Uint8Queue()', () {
+ test('creates an empty Uint8Queue', () {
+ expect(Uint8Queue(), isEmpty);
+ });
+
+ test('takes an initial capacity', () {
+ expect(Uint8Queue(100), isEmpty);
+ });
+ });
+
+ group('add() adds an element to the end', () {
+ forEachInternalRepresentation((queue) {
+ queue.add(16);
+ expect(queue, equals(oneThrough(capacity)));
+ });
+ });
+
+ group('addFirst() adds an element to the beginning', () {
+ forEachInternalRepresentation((queue) {
+ queue.addFirst(0);
+ expect(queue, equals([0, ...oneThrough(capacity - 1)]));
+ });
+ });
+
+ group('removeFirst() removes an element from the beginning', () {
+ forEachInternalRepresentation((queue) {
+ expect(queue.removeFirst(), equals(1));
+ expect(queue, equals(oneThrough(capacity - 1).skip(1)));
+ });
+
+ test('throws a StateError for an empty queue', () {
+ expect(Uint8Queue().removeFirst, throwsStateError);
+ });
+ });
+
+ group('removeLast() removes an element from the end', () {
+ forEachInternalRepresentation((queue) {
+ expect(queue.removeLast(), equals(15));
+ expect(queue, equals(oneThrough(capacity - 2)));
+ });
+
+ test('throws a StateError for an empty queue', () {
+ expect(Uint8Queue().removeLast, throwsStateError);
+ });
+ });
+
+ group('removeRange()', () {
+ group('removes a prefix', () {
+ forEachInternalRepresentation((queue) {
+ queue.removeRange(0, 5);
+ expect(queue, equals(oneThrough(capacity - 1).skip(5)));
+ });
+ });
+
+ group('removes a suffix', () {
+ forEachInternalRepresentation((queue) {
+ queue.removeRange(10, 15);
+ expect(queue, equals(oneThrough(capacity - 6)));
+ });
+ });
+
+ group('removes from the middle', () {
+ forEachInternalRepresentation((queue) {
+ queue.removeRange(5, 10);
+ expect(queue, equals([1, 2, 3, 4, 5, 11, 12, 13, 14, 15]));
+ });
+ });
+
+ group('removes everything', () {
+ forEachInternalRepresentation((queue) {
+ queue.removeRange(0, 15);
+ expect(queue, isEmpty);
+ });
+ });
+
+ test('throws a RangeError for an invalid range', () {
+ expect(() => Uint8Queue().removeRange(0, 1), throwsRangeError);
+ });
+ });
+
+ group('setRange()', () {
+ group('sets a range to the contents of an iterable', () {
+ forEachInternalRepresentation((queue) {
+ queue.setRange(5, 10, oneThrough(10).map((n) => 100 + n), 2);
+ expect(queue,
+ [1, 2, 3, 4, 5, 103, 104, 105, 106, 107, 11, 12, 13, 14, 15]);
+ });
+ });
+
+ group('sets a range to the contents of a list', () {
+ forEachInternalRepresentation((queue) {
+ queue.setRange(5, 10, oneThrough(10).map((n) => 100 + n).toList(), 2);
+ expect(queue,
+ [1, 2, 3, 4, 5, 103, 104, 105, 106, 107, 11, 12, 13, 14, 15]);
+ });
+ });
+
+ group(
+ 'sets a range to a section of the same queue overlapping at the '
+ 'beginning', () {
+ forEachInternalRepresentation((queue) {
+ queue.setRange(5, 10, queue, 2);
+ expect(queue, [1, 2, 3, 4, 5, 3, 4, 5, 6, 7, 11, 12, 13, 14, 15]);
+ });
+ });
+
+ group('sets a range to a section of the same queue overlapping at the end',
+ () {
+ forEachInternalRepresentation((queue) {
+ queue.setRange(5, 10, queue, 6);
+ expect(queue, [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 11, 12, 13, 14, 15]);
+ });
+ });
+
+ test('throws a RangeError for an invalid range', () {
+ expect(() => Uint8Queue().setRange(0, 1, [1]), throwsRangeError);
+ });
+ });
+
+ group('length returns the length', () {
+ forEachInternalRepresentation((queue) {
+ expect(queue.length, equals(15));
+ });
+ });
+
+ group('length=', () {
+ group('empties', () {
+ forEachInternalRepresentation((queue) {
+ queue.length = 0;
+ expect(queue, isEmpty);
+ });
+ });
+
+ group('shrinks', () {
+ forEachInternalRepresentation((queue) {
+ queue.length = 5;
+ expect(queue, equals([1, 2, 3, 4, 5]));
+ });
+ });
+
+ group('grows', () {
+ forEachInternalRepresentation((queue) {
+ queue.length = 20;
+ expect(
+ queue,
+ equals(oneThrough(capacity - 1) +
+ List.filled(20 - (capacity - 1), 0)));
+ });
+ });
+
+ group('zeroes out existing data', () {
+ forEachInternalRepresentation((queue) {
+ queue.length = 0;
+ queue.length = 15;
+ expect(queue, equals([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
+ });
+ });
+
+ test('throws a RangeError if length is less than 0', () {
+ expect(() => Uint8Queue().length = -1, throwsRangeError);
+ });
+ });
+
+ group('[]', () {
+ group('returns individual entries', () {
+ forEachInternalRepresentation((queue) {
+ for (var i = 0; i < capacity - 1; i++) {
+ expect(queue[i], equals(i + 1));
+ }
+ });
+ });
+
+ test('throws a RangeError if the index is less than 0', () {
+ var queue = Uint8Queue.fromList([1, 2, 3]);
+ expect(() => queue[-1], throwsRangeError);
+ });
+
+ test(
+ 'throws a RangeError if the index is greater than or equal to the '
+ 'length', () {
+ var queue = Uint8Queue.fromList([1, 2, 3]);
+ expect(() => queue[3], throwsRangeError);
+ });
+ });
+
+ group('[]=', () {
+ group('sets individual entries', () {
+ forEachInternalRepresentation((queue) {
+ for (var i = 0; i < capacity - 1; i++) {
+ queue[i] = 100 + i;
+ }
+ expect(queue, equals(List.generate(capacity - 1, (i) => 100 + i)));
+ });
+ });
+
+ test('throws a RangeError if the index is less than 0', () {
+ var queue = Uint8Queue.fromList([1, 2, 3]);
+ expect(() {
+ queue[-1] = 0;
+ }, throwsRangeError);
+ });
+
+ test(
+ 'throws a RangeError if the index is greater than or equal to the '
+ 'length', () {
+ var queue = Uint8Queue.fromList([1, 2, 3]);
+ expect(() {
+ queue[3] = 4;
+ }, throwsRangeError);
+ });
+ });
+
+ group('throws a modification error for', () {
+ late Uint8Queue queue;
+ setUp(() {
+ queue = Uint8Queue.fromList([1, 2, 3]);
+ });
+
+ test('add', () {
+ expect(() => queue.forEach((_) => queue.add(4)),
+ throwsConcurrentModificationError);
+ });
+
+ test('addAll', () {
+ expect(() => queue.forEach((_) => queue.addAll([4, 5, 6])),
+ throwsConcurrentModificationError);
+ });
+
+ test('addFirst', () {
+ expect(() => queue.forEach((_) => queue.addFirst(0)),
+ throwsConcurrentModificationError);
+ });
+
+ test('removeFirst', () {
+ expect(() => queue.forEach((_) => queue.removeFirst()),
+ throwsConcurrentModificationError);
+ });
+
+ test('removeLast', () {
+ expect(() => queue.forEach((_) => queue.removeLast()),
+ throwsConcurrentModificationError);
+ });
+
+ test('length=', () {
+ expect(() => queue.forEach((_) => queue.length = 1),
+ throwsConcurrentModificationError);
+ });
+ });
+}
+
+/// Runs [callback] in multiple tests, all with queues containing numbers from
+/// one through 15 in various different internal states.
+void forEachInternalRepresentation(void Function(Uint8Queue queue) callback) {
+ // Test with a queue whose internal table has plenty of room.
+ group("for a queue that's below capacity", () {
+ // Test with a queue whose elements are in one contiguous block, so `_head <
+ // _tail`.
+ test('with contiguous elements', () {
+ callback(Uint8Queue(capacity * 2)..addAll(oneThrough(capacity - 1)));
+ });
+
+ // Test with a queue whose elements are split across the ends of the table,
+ // so `_head > _tail`.
+ test('with an internal gap', () {
+ var queue = Uint8Queue(capacity * 2);
+ for (var i = capacity ~/ 2; i < capacity; i++) {
+ queue.add(i);
+ }
+ for (var i = capacity ~/ 2 - 1; i > 0; i--) {
+ queue.addFirst(i);
+ }
+ callback(queue);
+ });
+ });
+
+ // Test with a queue whose internal table will need to expand if one more
+ // element is added.
+ group("for a queue that's at capacity", () {
+ test('with contiguous elements', () {
+ callback(Uint8Queue()..addAll(oneThrough(capacity - 1)));
+ });
+
+ test('with an internal gap', () {
+ var queue = Uint8Queue();
+ for (var i = capacity ~/ 2; i < capacity; i++) {
+ queue.add(i);
+ }
+ for (var i = capacity ~/ 2 - 1; i > 0; i--) {
+ queue.addFirst(i);
+ }
+ callback(queue);
+ });
+ });
+}
+
+/// Returns a list containing the integers from one through [n].
+List<int> oneThrough(int n) => List.generate(n, (i) => i + 1);
+
+/// Returns a matcher that expects that a closure throws a
+/// [ConcurrentModificationError].
+final throwsConcurrentModificationError =
+ throwsA(const TypeMatcher<ConcurrentModificationError>());
diff --git a/pkgs/typed_data/test/typed_buffers_test.dart b/pkgs/typed_data/test/typed_buffers_test.dart
new file mode 100644
index 0000000..9d73040
--- /dev/null
+++ b/pkgs/typed_data/test/typed_buffers_test.dart
@@ -0,0 +1,567 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@TestOn('!vm')
+library;
+
+import 'dart:typed_data';
+
+import 'package:test/test.dart';
+import 'package:typed_data/src/typed_buffer.dart';
+
+const List<int> browserSafeIntSamples = [
+ 0x8000000000000000, // 2^63
+ 0x100000001,
+ 0x100000000, // 2^32
+ 0x0ffffffff,
+ 0xaaaaaaaa,
+ 0x80000001,
+ 0x80000000, // 2^31
+ 0x7fffffff,
+ 0x55555555,
+ 0x10001,
+ 0x10000, // 2^16
+ 0x0ffff,
+ 0xaaaa,
+ 0x8001,
+ 0x8000, // 2^15
+ 0x7fff,
+ 0x5555,
+ 0x101,
+ 0x100, // 2^8
+ 0x0ff,
+ 0xaa,
+ 0x81,
+ 0x80, // 2^7
+ 0x7f,
+ 0x55,
+ 0x02,
+ 0x01,
+ 0x00
+];
+
+void main() {
+ initTests(browserSafeIntSamples);
+}
+
+void initTests(List<int> intSamples) {
+ testUint(intSamples, 8, Uint8Buffer.new);
+ testInt(intSamples, 8, Int8Buffer.new);
+ test('Uint8ClampedBuffer', () {
+ testIntBuffer(intSamples, 8, 0, 255, Uint8ClampedBuffer.new, clampUint8);
+ });
+ testUint(intSamples, 16, Uint16Buffer.new);
+ testInt(intSamples, 16, Int16Buffer.new);
+ testUint(intSamples, 32, Uint32Buffer.new);
+
+ testInt(intSamples, 32, Int32Buffer.new);
+
+ testUint(intSamples, 64, Uint64Buffer.new,
+ // JS doesn't support 64-bit ints, so only test this on the VM.
+ testOn: 'dart-vm');
+ testInt(intSamples, 64, Int64Buffer.new,
+ // JS doesn't support 64-bit ints, so only test this on the VM.
+ testOn: 'dart-vm');
+
+ testInt32x4Buffer(intSamples);
+
+ var roundedFloatSamples = floatSamples.map(roundToFloat).toList();
+ testFloatBuffer(32, roundedFloatSamples, Float32Buffer.new, roundToFloat);
+ testFloatBuffer(64, doubleSamples, Float64Buffer.new, (x) => x);
+
+ testFloat32x4Buffer(roundedFloatSamples);
+
+ group('addAll', () {
+ for (var type in ['a list', 'an iterable']) {
+ group('with $type', () {
+ late Iterable<int> source;
+ late Uint8Buffer buffer;
+ setUp(() {
+ source = [1, 2, 3, 4, 5];
+ if (type == 'an iterable') {
+ source = (source as List<int>).reversed.toList().reversed;
+ }
+ buffer = Uint8Buffer();
+ });
+
+ test('adds values to the buffer', () {
+ buffer.addAll(source, 1, 4);
+ expect(buffer, equals([2, 3, 4]));
+
+ buffer.addAll(source, 4);
+ expect(buffer, equals([2, 3, 4, 5]));
+
+ buffer.addAll(source, 0, 1);
+ expect(buffer, equals([2, 3, 4, 5, 1]));
+ });
+
+ test('does nothing for empty slices', () {
+ buffer.addAll([6, 7, 8, 9, 10]);
+
+ buffer.addAll(source, 0, 0);
+ expect(buffer, equals([6, 7, 8, 9, 10]));
+
+ buffer.addAll(source, 3, 3);
+ expect(buffer, equals([6, 7, 8, 9, 10]));
+
+ buffer.addAll(source, 5);
+ expect(buffer, equals([6, 7, 8, 9, 10]));
+
+ buffer.addAll(source, 5, 5);
+ expect(buffer, equals([6, 7, 8, 9, 10]));
+ });
+
+ test('throws errors for invalid start and end', () {
+ expect(() => buffer.addAll(source, -1), throwsRangeError);
+ expect(() => buffer.addAll(source, -1, 2), throwsRangeError);
+ expect(() => buffer.addAll(source, 10), throwsStateError);
+ expect(() => buffer.addAll(source, 10, 11), throwsStateError);
+ expect(() => buffer.addAll(source, 3, 2), throwsRangeError);
+ expect(() => buffer.addAll(source, 3, 10), throwsStateError);
+ expect(() => buffer.addAll(source, 3, -1), throwsRangeError);
+ });
+ });
+ }
+ });
+
+ group('insertAll', () {
+ for (var type in ['a list', 'an iterable']) {
+ group('with $type', () {
+ late Iterable<int> source;
+ late Uint8Buffer buffer;
+ setUp(() {
+ source = [1, 2, 3, 4, 5];
+ if (type == 'an iterable') {
+ source = (source as List<int>).reversed.toList().reversed;
+ }
+ buffer = Uint8Buffer()..addAll([6, 7, 8, 9, 10]);
+ });
+
+ test('inserts values into the buffer', () {
+ buffer.insertAll(0, source, 1, 4);
+ expect(buffer, equals([2, 3, 4, 6, 7, 8, 9, 10]));
+
+ buffer.insertAll(3, source, 4);
+ expect(buffer, equals([2, 3, 4, 5, 6, 7, 8, 9, 10]));
+
+ buffer.insertAll(5, source, 0, 1);
+ expect(buffer, equals([2, 3, 4, 5, 6, 1, 7, 8, 9, 10]));
+ });
+
+ // Regression test for #1.
+ test('inserts values into the buffer after removeRange()', () {
+ buffer.removeRange(1, 4);
+ buffer.insertAll(1, source);
+ expect(buffer, equals([6, 1, 2, 3, 4, 5, 10]));
+ });
+
+ test('does nothing for empty slices', () {
+ buffer.insertAll(1, source, 0, 0);
+ expect(buffer, equals([6, 7, 8, 9, 10]));
+
+ buffer.insertAll(2, source, 3, 3);
+ expect(buffer, equals([6, 7, 8, 9, 10]));
+
+ buffer.insertAll(3, source, 5);
+ expect(buffer, equals([6, 7, 8, 9, 10]));
+
+ buffer.insertAll(4, source, 5, 5);
+ expect(buffer, equals([6, 7, 8, 9, 10]));
+ });
+
+ test('throws errors for invalid start and end', () {
+ expect(() => buffer.insertAll(-1, source), throwsRangeError);
+ expect(() => buffer.insertAll(6, source), throwsRangeError);
+ expect(() => buffer.insertAll(1, source, -1), throwsRangeError);
+ expect(() => buffer.insertAll(2, source, -1, 2), throwsRangeError);
+ expect(() => buffer.insertAll(3, source, 10), throwsStateError);
+ expect(() => buffer.insertAll(4, source, 10, 11), throwsStateError);
+ expect(() => buffer.insertAll(5, source, 3, 2), throwsRangeError);
+ expect(() => buffer.insertAll(1, source, 3, 10), throwsStateError);
+ expect(() => buffer.insertAll(2, source, 3, -1), throwsRangeError);
+ });
+ });
+ }
+ });
+}
+
+const doubleSamples = [
+ 0.0,
+ 5e-324, // Minimal denormal value.
+ 2.225073858507201e-308, // Maximal denormal value.
+ 2.2250738585072014e-308, // Minimal normal value.
+ 0.9999999999999999, // Maximum value < 1.
+ 1.0,
+ 1.0000000000000002, // Minimum value > 1.
+ 4294967295.0, // 2^32 -1.
+ 4294967296.0, // 2^32.
+ 4503599627370495.5, // Maximal fractional value.
+ 9007199254740992.0, // Maximal exact value (adding one gets lost).
+ 1.7976931348623157e+308, // Maximal value.
+ 1.0 / 0.0, // Infinity.
+ 0.0 / 0.0, // NaN.
+ 0.49999999999999994, // Round-traps 1-3 (adding 0.5 and rounding towards
+ 4503599627370497.0, // minus infinity will not be the same as rounding
+ 9007199254740991.0 // to nearest with 0.5 rounding up).
+];
+
+const floatSamples = [
+ 0.0,
+ 1.4e-45, // Minimal denormal value.
+ 1.1754942E-38, // Maximal denormal value.
+ 1.17549435E-38, // Minimal normal value.
+ 0.99999994, // Maximal value < 1.
+ 1.0,
+ 1.0000001, // Minimal value > 1.
+ 8388607.5, // Maximal fractional value.
+ 16777216.0, // Maximal exact value.
+ 3.4028235e+38, // Maximal value.
+ 1.0 / 0.0, // Infinity.
+ 0.0 / 0.0, // NaN.
+ 0.99999994, // Round traps 1-3.
+ 8388609.0,
+ 16777215.0
+];
+
+int clampUint8(int x) => x < 0
+ ? 0
+ : x > 255
+ ? 255
+ : x;
+
+void doubleEqual(num x, num y) {
+ if (y.isNaN) {
+ expect(x.isNaN, isTrue);
+ } else {
+ expect(x, equals(y));
+ }
+}
+
+Rounder intRounder(int bits) {
+ var highBit = 1 << (bits - 1);
+ var mask = highBit - 1;
+ return (int x) => (x & mask) - (x & highBit);
+}
+
+double roundToFloat(double value) {
+ return (Float32List(1)..[0] = value)[0];
+}
+
+void testFloat32x4Buffer(List<double> floatSamples) {
+ var float4Samples = <Float32x4>[];
+ for (var i = 0; i < floatSamples.length - 3; i++) {
+ float4Samples.add(Float32x4(floatSamples[i], floatSamples[i + 1],
+ floatSamples[i + 2], floatSamples[i + 3]));
+ }
+
+ void floatEquals(num x, num y) {
+ if (y.isNaN) {
+ expect(x.isNaN, isTrue);
+ } else {
+ expect(x, equals(y));
+ }
+ }
+
+ void x4Equals(Float32x4 x, Float32x4 y) {
+ floatEquals(x.x, y.x);
+ floatEquals(x.y, y.y);
+ floatEquals(x.z, y.z);
+ floatEquals(x.w, y.w);
+ }
+
+ test('Float32x4Buffer', () {
+ var buffer = Float32x4Buffer(5);
+ expect(buffer, const TypeMatcher<List<Float32x4>>());
+
+ expect(buffer.length, equals(5));
+ expect(buffer.elementSizeInBytes, equals(128 ~/ 8));
+ expect(buffer.lengthInBytes, equals(5 * 128 ~/ 8));
+ expect(buffer.offsetInBytes, equals(0));
+
+ x4Equals(buffer[0], Float32x4.zero());
+ buffer.length = 0;
+ expect(buffer.length, equals(0));
+
+ for (var sample in float4Samples) {
+ buffer.add(sample);
+ x4Equals(buffer[buffer.length - 1], sample);
+ }
+ expect(buffer.length, equals(float4Samples.length));
+
+ buffer.addAll(float4Samples);
+ expect(buffer.length, equals(float4Samples.length * 2));
+ for (var i = 0; i < float4Samples.length; i++) {
+ x4Equals(buffer[i], buffer[float4Samples.length + i]);
+ }
+
+ buffer.removeRange(4, 4 + float4Samples.length);
+ for (var i = 0; i < float4Samples.length; i++) {
+ x4Equals(buffer[i], float4Samples[i]);
+ }
+
+ // Test underlying buffer.
+ buffer.length = 1;
+ buffer[0] = float4Samples[0]; // Does not contain NaN.
+
+ var floats = Float32List.view(buffer.buffer);
+ expect(floats[0], equals(buffer[0].x));
+ expect(floats[1], equals(buffer[0].y));
+ expect(floats[2], equals(buffer[0].z));
+ expect(floats[3], equals(buffer[0].w));
+ });
+}
+
+// Takes bit-size, min value, max value, function to create a buffer, and
+// the rounding that is applied when storing values outside the valid range
+// into the buffer.
+void testFloatBuffer(
+ int bitSize,
+ List<double> samples,
+ TypedDataBuffer<double> Function() create,
+ double Function(double v) round,
+) {
+ test('Float${bitSize}Buffer', () {
+ var buffer = create();
+ expect(buffer, const TypeMatcher<List<double>>());
+ var byteSize = bitSize ~/ 8;
+
+ expect(buffer.length, equals(0));
+ buffer.add(0.0);
+ expect(buffer.length, equals(1));
+ expect(buffer.removeLast(), equals(0.0));
+ expect(buffer.length, equals(0));
+
+ for (var value in samples) {
+ buffer.add(value);
+ doubleEqual(buffer[buffer.length - 1], round(value));
+ }
+ expect(buffer.length, equals(samples.length));
+
+ buffer.addAll(samples);
+ expect(buffer.length, equals(samples.length * 2));
+ for (var i = 0; i < samples.length; i++) {
+ doubleEqual(buffer[i], buffer[samples.length + i]);
+ }
+
+ buffer.removeRange(samples.length, buffer.length);
+ expect(buffer.length, equals(samples.length));
+
+ buffer.insertAll(0, samples);
+ expect(buffer.length, equals(samples.length * 2));
+ for (var i = 0; i < samples.length; i++) {
+ doubleEqual(buffer[i], buffer[samples.length + i]);
+ }
+
+ buffer.length = samples.length;
+ expect(buffer.length, equals(samples.length));
+
+ // TypedData.
+ expect(buffer.elementSizeInBytes, equals(byteSize));
+ expect(buffer.lengthInBytes, equals(byteSize * buffer.length));
+ expect(buffer.offsetInBytes, equals(0));
+
+ // Accessing the buffer works.
+ // Accessing the underlying buffer works.
+ buffer.length = 2;
+ buffer[0] = samples[0];
+ buffer[1] = samples[1];
+ var bytes = Uint8List.view(buffer.buffer);
+ for (var i = 0; i < byteSize; i++) {
+ var tmp = bytes[i];
+ bytes[i] = bytes[byteSize + i];
+ bytes[byteSize + i] = tmp;
+ }
+ doubleEqual(buffer[0], round(samples[1]));
+ doubleEqual(buffer[1], round(samples[0]));
+ });
+}
+
+void testInt(
+ List<int> intSamples,
+ int bits,
+ TypedDataBuffer<int> Function(int length) buffer, {
+ String? testOn,
+}) {
+ var min = -(1 << (bits - 1));
+ var max = -(min + 1);
+ test('Int${bits}Buffer', () {
+ testIntBuffer(intSamples, bits, min, max, buffer, intRounder(bits));
+ }, testOn: testOn);
+}
+
+void testInt32x4Buffer(List<int> intSamples) {
+ test('Int32x4Buffer', () {
+ var bytes = 128 ~/ 8;
+ Matcher equals32x4(Int32x4 expected) => MatchesInt32x4(expected);
+
+ var buffer = Int32x4Buffer(0);
+ expect(buffer, const TypeMatcher<List<Int32x4>>());
+ expect(buffer.length, equals(0));
+
+ expect(buffer.elementSizeInBytes, equals(bytes));
+ expect(buffer.lengthInBytes, equals(0));
+ expect(buffer.offsetInBytes, equals(0));
+
+ var sample = Int32x4(-0x80000000, -1, 0, 0x7fffffff);
+ buffer.add(sample);
+ expect(buffer.length, equals(1));
+ expect(buffer[0], equals32x4(sample));
+
+ expect(buffer.elementSizeInBytes, equals(bytes));
+ expect(buffer.lengthInBytes, equals(bytes));
+ expect(buffer.offsetInBytes, equals(0));
+
+ buffer.length = 0;
+ expect(buffer.length, equals(0));
+
+ var samples = intSamples
+ .map((value) => Int32x4(value, -value, ~value, ~ -value))
+ .toList();
+ for (var value in samples) {
+ var length = buffer.length;
+ buffer.add(value);
+ expect(buffer.length, equals(length + 1));
+ expect(buffer[length], equals32x4(value));
+ }
+
+ buffer.addAll(samples); // Add all the values at once.
+ for (var i = 0; i < samples.length; i++) {
+ expect(buffer[samples.length + i], equals32x4(buffer[i]));
+ }
+
+ // Remove range works and changes length.
+ buffer.removeRange(samples.length, buffer.length);
+ expect(buffer.length, equals(samples.length));
+
+ // Accessing the underlying buffer works.
+ buffer.length = 2;
+ buffer[0] = Int32x4(-80000000, 0x7fffffff, 0, -1);
+ var byteBuffer = Uint8List.view(buffer.buffer);
+ var halfBytes = bytes ~/ 2;
+ for (var i = 0; i < halfBytes; i++) {
+ var tmp = byteBuffer[i];
+ byteBuffer[i] = byteBuffer[halfBytes + i];
+ byteBuffer[halfBytes + i] = tmp;
+ }
+ var result = Int32x4(0, -1, -80000000, 0x7fffffff);
+ expect(buffer[0], equals32x4(result));
+ });
+}
+
+void testIntBuffer(
+ List<int> intSamples,
+ int bits,
+ int min,
+ int max,
+ TypedDataBuffer<int> Function(int length) create,
+ int Function(int val) round,
+) {
+ assert(round(min) == min);
+ assert(round(max) == max);
+ // All int buffers default to the value 0.
+ var buffer = create(0);
+ expect(buffer, const TypeMatcher<List<int>>());
+ expect(buffer.length, equals(0));
+ var bytes = bits ~/ 8;
+
+ expect(buffer.elementSizeInBytes, equals(bytes));
+ expect(buffer.lengthInBytes, equals(0));
+ expect(buffer.offsetInBytes, equals(0));
+
+ buffer.add(min);
+ expect(buffer.length, equals(1));
+ expect(buffer[0], equals(min));
+
+ expect(buffer.elementSizeInBytes, equals(bytes));
+ expect(buffer.lengthInBytes, equals(bytes));
+ expect(buffer.offsetInBytes, equals(0));
+
+ buffer.length = 0;
+ expect(buffer.length, equals(0));
+
+ var samples = intSamples.toList()..addAll(intSamples.map((x) => -x));
+ for (var value in samples) {
+ var length = buffer.length;
+ buffer.add(value);
+ expect(buffer.length, equals(length + 1));
+ expect(buffer[length], equals(round(value)));
+ }
+ buffer.addAll(samples); // Add all the values at once.
+ for (var i = 0; i < samples.length; i++) {
+ expect(buffer[samples.length + i], equals(buffer[i]));
+ }
+
+ // Remove range works and changes length.
+ buffer.removeRange(samples.length, buffer.length);
+ expect(buffer.length, equals(samples.length));
+
+ // Both values are in `samples`, but equality is performed without rounding.
+ // For signed 64 bit ints, min and max wrap around, min-1=max and max+1=min
+ if (bits == 64) {
+ // TODO(keertip): fix tests for Uint64 / Int64 as now Uints are represented
+ // as signed ints.
+ expect(buffer.contains(min - 1), isTrue);
+ expect(buffer.contains(max + 1), isTrue);
+ } else {
+ // Both values are in `samples`, but equality is performed without rounding.
+ expect(buffer.contains(min - 1), isFalse);
+ expect(buffer.contains(max + 1), isFalse);
+ }
+ expect(buffer.contains(round(min - 1)), isTrue);
+ expect(buffer.contains(round(max + 1)), isTrue);
+
+ // Accessing the underlying buffer works.
+ buffer.length = 2;
+ buffer[0] = min;
+ buffer[1] = max;
+ var byteBuffer = Uint8List.view(buffer.buffer);
+ var byteSize = buffer.elementSizeInBytes;
+ for (var i = 0; i < byteSize; i++) {
+ var tmp = byteBuffer[i];
+ byteBuffer[i] = byteBuffer[byteSize + i];
+ byteBuffer[byteSize + i] = tmp;
+ }
+ expect(buffer[0], equals(max));
+ expect(buffer[1], equals(min));
+}
+
+void testUint(
+ List<int> intSamples,
+ int bits,
+ TypedDataBuffer<int> Function(int length) buffer, {
+ String? testOn,
+}) {
+ var min = 0;
+ var rounder = uintRounder(bits);
+ var max = rounder(-1);
+ test('Uint${bits}Buffer', () {
+ testIntBuffer(intSamples, bits, min, max, buffer, rounder);
+ }, testOn: testOn);
+}
+
+Rounder uintRounder(int bits) {
+ var halfbits = (1 << (bits ~/ 2)) - 1;
+ var mask = halfbits | (halfbits << (bits ~/ 2));
+ return (int x) => x & mask;
+}
+
+typedef Rounder = int Function(int value);
+
+class MatchesInt32x4 extends Matcher {
+ Int32x4 result;
+
+ MatchesInt32x4(this.result);
+
+ @override
+ Description describe(Description description) =>
+ description.add('Int32x4.==');
+
+ @override
+ bool matches(Object? item, Map matchState) =>
+ item is Int32x4 &&
+ result.x == item.x &&
+ result.y == item.y &&
+ result.z == item.z &&
+ result.w == item.w;
+}
diff --git a/pkgs/typed_data/test/typed_buffers_vm_test.dart b/pkgs/typed_data/test/typed_buffers_vm_test.dart
new file mode 100644
index 0000000..62afcef
--- /dev/null
+++ b/pkgs/typed_data/test/typed_buffers_vm_test.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@TestOn('vm')
+library;
+
+import 'package:test/test.dart';
+
+import 'typed_buffers_test.dart';
+
+void main() {
+ var browserUnsafe = [
+ 0x0ffffffffffffffff,
+ 0xaaaaaaaaaaaaaaaa,
+ 0x8000000000000001,
+ 0x7fffffffffffffff,
+ 0x5555555555555555,
+ ];
+ initTests(<int>[
+ ...browserSafeIntSamples,
+ ...browserUnsafe,
+ ]);
+}