├── .browserslistrc ├── .prettierrc.json ├── .npmignore ├── .gitignore ├── babel.config.json ├── tsconfig.eslint.json ├── src ├── utils │ ├── crc32.ts │ ├── dummy-cue.ts │ ├── binary.ts │ ├── crc16-ccitt.ts │ ├── high-res-texttrack.ts │ └── md5.ts ├── constants │ ├── mapping │ │ ├── ascii.ts │ │ ├── drcs-MSZ.ts │ │ ├── hiragana.ts │ │ ├── katakana.ts │ │ ├── additional-symbols-set.ts │ │ ├── additional-symbols-pua.ts │ │ ├── additional-symbols-unicode.ts │ │ ├── drcs-NSZ.ts │ │ └── kanji.ts │ ├── color-table.ts │ └── jis8.ts ├── embedded.ts ├── index.ts ├── svg-renderer.ts ├── html-renderer-experimental.ts └── canvas-renderer.ts ├── .eslintrc.js ├── webpack.config.js ├── LICENSE ├── package.json ├── tsconfig.json └── README.md /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 0.25% 2 | last 2 versions 3 | not ie <= 8 4 | ie 11 5 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "tabWidth": 2 5 | } 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | .node-version 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | d.ts/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | .node-version 9 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "useBuiltIns": "usage", 7 | "corejs": 3 8 | } 9 | ] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "src/**/*.ts", 5 | ".eslint.js" 6 | ], 7 | "exclude": [ 8 | "node_modules", 9 | "dist" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/crc32.ts: -------------------------------------------------------------------------------- 1 | export default function CRC32(buffer: Uint8Array, begin: number, end: number) { 2 | let crc = -1; 3 | for (let i = begin; i < end; i++) { 4 | crc ^= buffer[i]; 5 | for (let j = 0; j < 8; j++) { 6 | if (crc & 1) { 7 | crc = (crc >>> 1) ^ 0xEDB88320; 8 | } else { 9 | crc >>>= 1; 10 | } 11 | } 12 | } 13 | return ~crc; 14 | } 15 | -------------------------------------------------------------------------------- /src/constants/mapping/ascii.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | '!','"','#','$','%','&',''','(',')','*','+',',','-','.','/','0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?','@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','[','¥',']','^','_','`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','{','|','}','~', 3 | ] 4 | -------------------------------------------------------------------------------- /src/constants/mapping/drcs-MSZ.ts: -------------------------------------------------------------------------------- 1 | export default new Map([ 2 | ['3439edd634ee82c577fe22dc90a4376f', 'ℓ'], 3 | ['874d052afe768e004e79a8521ef142f8', 'ℓ'], 4 | ['2503c22edb76a7a2bb415d04e03c1a79', 'ℓ'], 5 | 6 | ['26cec7eafe6c4dee1b739e11136535a2', '“'], 7 | 8 | ['2cd70fbda60617181afc8184161fe9e4', '”'], 9 | ['56417ea1a9f3d3899e6e62692f4558da', '”'], 10 | 11 | ['86c18677563a6c62a1beb5bdbbdb6eba', 'β'], 12 | ]) 13 | -------------------------------------------------------------------------------- /src/constants/mapping/hiragana.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | 'ぁ','あ','ぃ','い','ぅ','う','ぇ','え','ぉ','お','か','が','き','ぎ','く','ぐ','け','げ','こ','ご','さ','ざ','し','じ','す','ず','せ','ぜ','そ','ぞ','た','だ','ち','ぢ','っ','つ','づ','て','で','と','ど','な','に','ぬ','ね','の','は','ば','ぱ','ひ','び','ぴ','ふ','ぶ','ぷ','へ','べ','ぺ','ほ','ぼ','ぽ','ま','み','む','め','も','ゃ','や','ゅ','ゆ','ょ','よ','ら','り','る','れ','ろ','ゎ','わ','ゐ','ゑ','を','ん','','','','ゝ','ゞ','ー','。','「','」','、','・' 3 | ] 4 | -------------------------------------------------------------------------------- /src/constants/mapping/katakana.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | 'ァ','ア','ィ','イ','ゥ','ウ','ェ','エ','ォ','オ','カ','ガ','キ','ギ','ク','グ','ケ','ゲ','コ','ゴ','サ','ザ','シ','ジ','ス','ズ','セ','ゼ','ソ','ゾ','タ','ダ','チ','ヂ','ッ','ツ','ヅ','テ','デ','ト','ド','ナ','ニ','ヌ','ネ','ノ','ハ','バ','パ','ヒ','ビ','ピ','フ','ブ','プ','ヘ','ベ','ペ','ホ','ボ','ポ','マ','ミ','ム','メ','モ','ャ','ヤ','ュ','ユ','ョ','ヨ','ラ','リ','ル','レ','ロ','ヮ','ワ','ヰ','ヱ','ヲ','ン','ヴ','ヵ','ヶ','ヽ','ヾ','ー','。','「','」','、','・' 3 | ] 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | 'es2020': true 5 | }, 6 | parser: '@typescript-eslint/parser', 7 | 'parserOptions': { 8 | sourceType: "module", 9 | ecmaVersion: 2019, 10 | tsconfigRootDir: __dirname, 11 | project: ['./tsconfig.eslint.json'] 12 | }, 13 | plugins: [ 14 | '@typescript-eslint' 15 | ], 16 | extends: [ 17 | 'eslint:recommended', 18 | 'plugin:@typescript-eslint/recommended', 19 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 20 | 'prettier', 21 | 'prettier/@typescript-eslint' 22 | ], 23 | rules: { 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/embedded.ts: -------------------------------------------------------------------------------- 1 | import CanvasRenderer from './canvas-renderer' 2 | import CanvasProvider from './canvas-provider' 3 | import SVGRenderer from './svg-renderer' 4 | import SVGProvider from './svg-provider' 5 | import HTMLRenderer from './html-renderer-experimental' 6 | import HTMLProvider from './html-provider-experimental' 7 | 8 | import EmbeddedGlyph from './constants/mapping/additional-symbols-glyph' 9 | CanvasProvider.setEmbeddedGlyph(EmbeddedGlyph); 10 | SVGProvider.setEmbeddedGlyph(EmbeddedGlyph); 11 | HTMLProvider.setEmbeddedGlyph(EmbeddedGlyph); 12 | 13 | export { 14 | CanvasRenderer, CanvasProvider, 15 | SVGRenderer, SVGProvider, 16 | HTMLRenderer, HTMLProvider 17 | } 18 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CanvasRenderer, RendererOption as CanvasRendererOption } from './canvas-renderer' 2 | export { default as CanvasProvider, ProviderOption as CanvasProviderOption, ProviderResult as CanvasProviderResult } from './canvas-provider' 3 | 4 | export { default as SVGRenderer, RendererOption as SVGRendererOption } from './svg-renderer' 5 | export { default as SVGProvider, ProviderOption as SVGProviderOption, ProviderResult as SVGProviderResult } from './svg-provider' 6 | 7 | export { default as HTMLRenderer, RendererOption as HTMLRendererOption } from './html-renderer-experimental' 8 | export { default as HTMLProvider, ProviderOption as HTMLProviderOption, ProviderResult as HTMLProviderResult } from './html-provider-experimental' 9 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = { 4 | entry: { 5 | "aribb24": { 6 | import: path.resolve(__dirname, 'src', 'index.ts') 7 | }, 8 | "aribb24-embedded": { 9 | import: path.resolve(__dirname, 'src', 'embedded.ts') 10 | } 11 | }, 12 | output: { 13 | filename: '[name].js', 14 | path: path.resolve(__dirname, 'dist'), 15 | library: 'aribb24js', 16 | libraryTarget: 'umd', 17 | globalObject: 'this' 18 | }, 19 | 20 | resolve: { 21 | extensions: ['.ts', '.js', '.json'] 22 | }, 23 | 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.ts$/, 28 | use: [ 29 | { loader: 'babel-loader'}, 30 | { loader: 'ts-loader' }, 31 | ], 32 | } 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/dummy-cue.ts: -------------------------------------------------------------------------------- 1 | export default class DummyCue implements TextTrackCue { 2 | public startTime: number; 3 | public endTime: number; 4 | 5 | public id: string = ''; 6 | public pauseOnExit: boolean = false; 7 | 8 | public onenter: ((this: TextTrackCue, ev: Event) => any) | null = null; 9 | public onexit: ((this: TextTrackCue, ev: Event) => any) | null = null ; 10 | 11 | public readonly track: TextTrack | null = null; 12 | 13 | public constructor(startTime: number, endTime: number) { 14 | this.startTime = startTime; 15 | this.endTime = endTime; 16 | } 17 | 18 | // for ie11 (EventTarget を継承できないため) 19 | public addEventListener(type: K, listener: (this: TextTrackCue, ev: TextTrackCueEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void {} 20 | public removeEventListener(type: K, listener: (this: TextTrackCue, ev: TextTrackCueEventMap[K]) => any, options?: boolean | EventListenerOptions): void {} 21 | public dispatchEvent(ev: Event): boolean { 22 | return false; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 もにょ~ん 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/utils/binary.ts: -------------------------------------------------------------------------------- 1 | export function readID3Size (binary: Uint8Array, begin: number, end: number): number { 2 | let result = 0; 3 | for (let i = begin; i < end; i++) { 4 | result <<= 7; 5 | result |= (binary[i] & 0x7F); 6 | } 7 | return result; 8 | } 9 | 10 | export function binaryToPercentString(binary: Uint8Array, begin: number, end: number): string { 11 | let result = ''; 12 | for (let i = begin; i < end; i++) { 13 | result += `%${binary[i].toString(16).padStart(2, '0')}`; 14 | } 15 | return result; 16 | } 17 | 18 | export function binaryUTF8ToString(binary: Uint8Array, begin: number, end: number): string { 19 | if (window.TextDecoder) { 20 | const decoder = new TextDecoder('utf-8'); 21 | const array: Uint8Array = new Uint8Array(Array.prototype.slice.call(binary, begin, end)); 22 | 23 | return decoder.decode(array); 24 | } else { 25 | return window.decodeURIComponent(binaryToPercentString(binary, begin, end)); 26 | } 27 | } 28 | 29 | export function binaryISO85591ToString(binary: Uint8Array, begin: number, end: number): string { 30 | if (window.TextDecoder) { 31 | const decoder = new TextDecoder('iso-8859-1'); 32 | const array: Uint8Array = new Uint8Array(Array.prototype.slice.call(binary, begin, end)); 33 | 34 | return decoder.decode(array); 35 | } else { 36 | return window.unescape(binaryToPercentString(binary, begin, end)); 37 | } 38 | } 39 | 40 | export function base64ToUint8Array(base64: string): Uint8Array { 41 | const binary = window.atob(base64); 42 | const result = new Uint8Array(binary.length); 43 | for (let i = 0; i < binary.length; i++) { result[i] = binary.charCodeAt(i); } 44 | return result; 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aribb24.js", 3 | "version": "1.10.9", 4 | "main": "dist/aribb24.js", 5 | "types": "d.ts/index.d.ts", 6 | "files": [ 7 | "dist/**/*", 8 | "d.ts/**/*", 9 | "src/**/*", 10 | "tsconfig.json" 11 | ], 12 | "author": "monyone ", 13 | "description": "an alternative implementation for b24.js", 14 | "keywords": [ 15 | "html5", 16 | "arib", 17 | "b24", 18 | "subtitle" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/monyone/aribb24.js" 23 | }, 24 | "license": "MIT", 25 | "scripts": { 26 | "build": "webpack --mode production", 27 | "build:dev": "webpack --mode development", 28 | "tsc": "tsc --noEmit", 29 | "eslint": "eslint src/**/*.ts", 30 | "prettier": "prettier --write src/**/*.ts" 31 | }, 32 | "husky": { 33 | "hook": { 34 | "pre-commit": "lint-staged" 35 | } 36 | }, 37 | "lint-staged": { 38 | "*.ts": [ 39 | "tsc --noEmit", 40 | "eslint --fix", 41 | "prettier --write" 42 | ] 43 | }, 44 | "devDependencies": { 45 | "@babel/core": "^7.12.10", 46 | "@babel/preset-env": "^7.12.11", 47 | "@types/resize-observer-browser": "^0.1.5", 48 | "@types/spark-md5": "^3.0.2", 49 | "@typescript-eslint/eslint-plugin": "^4.14.0", 50 | "@typescript-eslint/parser": "^4.14.0", 51 | "babel-loader": "^8.2.2", 52 | "core-js": "^3.8.3", 53 | "eslint": "^7.18.0", 54 | "eslint-config-prettier": "^7.2.0", 55 | "husky": "^4.3.8", 56 | "lint-staged": "^10.5.3", 57 | "prettier": "^2.2.1", 58 | "ts-loader": "^8.0.14", 59 | "typescript": "^4.1.3", 60 | "webpack": "^5.16.0", 61 | "webpack-cli": "^4.4.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/utils/crc16-ccitt.ts: -------------------------------------------------------------------------------- 1 | const CRC16_CCITT_table = [ 2 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 3 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 4 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 5 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 6 | 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 7 | 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 8 | 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 9 | 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 10 | 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 11 | 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 12 | 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 13 | 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 14 | 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 15 | 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 16 | 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 17 | 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0, 18 | ]; 19 | 20 | const CRC16_CCITT = (buffer: Uint8Array, begin:number = 0, end?: number): number => { 21 | if (end == null) { end = buffer.length; } 22 | 23 | let crc = 0; 24 | 25 | for (let i = begin; i < end; i++) { 26 | crc = ((crc << 8) ^ CRC16_CCITT_table[((crc >> 8) ^ buffer[i]) & 0xFF]) & 0xFFFF; 27 | } 28 | 29 | return crc; 30 | } 31 | 32 | export default CRC16_CCITT; 33 | -------------------------------------------------------------------------------- /src/constants/mapping/additional-symbols-set.ts: -------------------------------------------------------------------------------- 1 | export default new Set([ 2 | '⛌','⛍','❗','⛏','⛐','⛑','⛒','⛕','⛓','⛔','','','⛖','⛗','⛘','⛙','⛚','⛛','⛜','⛝','⛞','⛟','⛠','⛡','⭕','㉈','㉉','㉊','㉋','㉌','㉍','㉎','㉏','⒑','⒒','⒓','','','','','','','','','','','','','','','','','⬛','⬤','','','','','','⚿','','','','','','','','','','','','㊙','','⛣','⭖','⭗','⭘','⭙','☓','㊋','〒','⛨','㉆','㉅','⛩','࿖','⛪','⛫','⛬','♨','⛭','⛮','⛯','⚓','✈','⛰','⛱','⛲','⛳','⛴','⛵','','Ⓓ','Ⓢ','⛶','','','','','','⛷','⛸','⛹','⛺','','☎','⛻','⛼','⛽','⛾','','⛿','➡','⬅','⬆','⬇','⬯','⬮','','','','','㎡','㎥','㎝','㎠','㎤','','⒈','⒉','⒊','⒋','⒌','⒍','⒎','⒏','⒐','','','','','','','','','','','','','','','','','㈳','㈶','㈲','㈱','㈹','㉄','▶','◀','〖','〗','⟐','²','³','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','㉇','','','℻','㈪','㈫','㈬','㈭','㈮','㈯','㈰','㈷','㍾','㍽','㍼','㍻','№','℡','〶','⚾','','','','','','','','','','','','','','','','','','','','','','','ℓ','㎏','㎐','㏊','㎞','㎢','㍱','½','↉','⅓','⅔','¼','¾','⅕','⅖','⅗','⅘','⅙','⅚','⅐','⅛','⅑','⅒','☀','☁','☂','⛄','☖','☗','⛉','⛊','♦','♥','♣','♠','⛋','⨀','‼','⁉','⛅','☔','⛆','☃','⛇','⚡','⛈','⚞','⚟','♬','','Ⅰ','Ⅱ','Ⅲ','Ⅳ','Ⅴ','Ⅵ','Ⅶ','Ⅷ','Ⅸ','Ⅹ','Ⅺ','Ⅻ','⑰','⑱','⑲','⑳','⑴','⑵','⑶','⑷','⑸','⑹','⑺','⑻','⑼','⑽','⑾','⑿','㉑','㉒','㉓','㉔','','','','','','','','','','','','','','','','','','','','','','','','','','','㉕','㉖','㉗','㉘','㉙','㉚','①','②','③','④','⑤','⑥','⑦','⑧','⑨','⑩','⑪','⑫','⑬','⑭','⑮','⑯','❶','❷','❸','❹','❺','❻','❼','❽','❾','❿','⓫','⓬','㉛','☎','⛌','⛍','❗','⛏','⛐','⛑','⛒','⛕','⛓','⛔','🅿','🆊','⛖','⛗','⛘','⛙','⛚','⛛','⛜','⛝','⛞','⛟','⛠','⛡','⭕','㉈','㉉','㉊','㉋','㉌','㉍','㉎','㉏','⒑','⒒','⒓','🅊','🅌','🄿','🅆','🅋','🈐','🈑','🈒','🈓','🅂','🈔','🈕','🈖','🅍','🄱','🄽','⬛','⬤','🈗','🈘','🈙','🈚','🈛','⚿','🈜','🈝','🈞','🈟','🈠','🈡','🈢','🈣','🈤','🈥','🅎','㊙','🈀','⛣','⭖','⭗','⭘','⭙','☓','㊋','⛨','㉆','㉅','⛩','࿖','⛪','⛫','⛬','⛭','⛮','⛯','⚓','✈','⛰','⛱','⛲','⛳','⛴','⛵','🅗','Ⓓ','Ⓢ','⛶','🅟','🆋','🆍','🆌','🅹','⛷','⛸','⛹','⛺','🅻','⛻','⛼','⛽','⛾','🅼','⛿','➡','⬅','⬆','⬇','⬯','⬮','㎥','㎠','㎤','🄀','⒈','⒉','⒊','⒋','⒌','⒍','⒎','⒏','⒐','🄁','🄂','🄃','🄄','🄅','🄆','🄇','🄈','🄉','🄊','㈳','㈶','㉄','⟐','🄭','🄬','🄫','㉇','🆐','🈦','℻','㈪','㈫','㈬','㈭','㈮','㈯','㈰','㈷','〶','⚾','🉀','🉁','🉂','🉃','🉄','🉅','🉆','🉇','🉈','🄪','🈧','🈨','🈩','🈔','🈪','🈫','🈬','🈭','🈮','🈯','🈰','🈱','㎐','㏊','㎢','㍱','↉','⅖','⅗','⅘','⅙','⅚','⅐','⅛','⅑','⅒','⛄','⛉','⛊','⛋','⨀','⛅','☔','⛆','⛇','⚡','⛈','⚞','⚟','⑴','⑵','⑶','⑷','⑸','⑹','⑺','⑻','⑼','⑽','⑾','⑿','🄐','🄑','🄒','🄓','🄔','🄕','🄖','🄗','🄘','🄙','🄚','🄛','🄜','🄝','🄞','🄟','🄠','🄡','🄢','🄣','🄤','🄥','🄦','🄧','🄨','🄩' 3 | ]) 4 | -------------------------------------------------------------------------------- /src/constants/color-table.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | [ 3 | '#000000FF', 4 | '#FF0000FF', 5 | '#00FF00FF', 6 | '#FFFF00FF', 7 | '#0000FFFF', 8 | '#FF00FFFF', 9 | '#00FFFFFF', 10 | '#FFFFFFFF', 11 | '#00000000', 12 | '#AA0000FF', 13 | '#00AA00FF', 14 | '#AAAA00FF', 15 | '#0000AAFF', 16 | '#AA00AAFF', 17 | '#00AAAAFF', 18 | '#AAAAAAFF', 19 | ], 20 | [ 21 | '#000055FF', 22 | '#005500FF', 23 | '#005555FF', 24 | '#0055AAFF', 25 | '#0055FFFF', 26 | '#00AA55FF', 27 | '#00AAFFFF', 28 | '#00FF55FF', 29 | '#00FFAAFF', 30 | '#550000FF', 31 | '#550055FF', 32 | '#5500AAFF', 33 | '#5500FFFF', 34 | '#555500FF', 35 | '#555555FF', 36 | '#5555AAFF', 37 | ], 38 | [ 39 | '#5555FFFF', 40 | '#55AA00FF', 41 | '#55AA55FF', 42 | '#55AAAAFF', 43 | '#55AAFFFF', 44 | '#55FF00FF', 45 | '#55FF55FF', 46 | '#55FFAAFF', 47 | '#55FFFFFF', 48 | '#AA0055FF', 49 | '#AA00FFFF', 50 | '#AA5500FF', 51 | '#AA5555FF', 52 | '#AA55AAFF', 53 | '#AA55FFFF', 54 | '#AAAA55FF', 55 | ], 56 | [ 57 | '#AAAAFFFF', 58 | '#AAFF00FF', 59 | '#AAFF55FF', 60 | '#AAFFAAFF', 61 | '#AAFFFFFF', 62 | '#FF0055FF', 63 | '#FF00AAFF', 64 | '#FF5500FF', 65 | '#FF5555FF', 66 | '#FF55AAFF', 67 | '#FF55FFFF', 68 | '#FFAA00FF', 69 | '#FFAA55FF', 70 | '#FFAAAAFF', 71 | '#FFAAFFFF', 72 | '#FFFF55FF', 73 | ], 74 | [ 75 | '#FFFFAAFF', 76 | '#00000080', 77 | '#FF000080', 78 | '#00FF0080', 79 | '#FFFF0080', 80 | '#0000FF80', 81 | '#FF00FF80', 82 | '#00FFFF80', 83 | '#FFFFFF80', 84 | '#AA000080', 85 | '#00AA0080', 86 | '#AAAA0080', 87 | '#0000AA80', 88 | '#AA00AA80', 89 | '#00AAAA80', 90 | '#AAAAAA80', 91 | ], 92 | [ 93 | '#00005580', 94 | '#00550080', 95 | '#00555580', 96 | '#0055AA80', 97 | '#0055FF80', 98 | '#00AA5580', 99 | '#00AAFF80', 100 | '#00FF5580', 101 | '#00FFAA80', 102 | '#55000080', 103 | '#55005580', 104 | '#5500AA80', 105 | '#5500FF80', 106 | '#55550080', 107 | '#55555580', 108 | '#5555AA80', 109 | ], 110 | [ 111 | '#5555FF80', 112 | '#55AA0080', 113 | '#55AA5580', 114 | '#55AAAA80', 115 | '#55AAFF80', 116 | '#55FF0080', 117 | '#55FF5580', 118 | '#55FFAA80', 119 | '#55FFFF80', 120 | '#AA005580', 121 | '#AA00FF80', 122 | '#AA550080', 123 | '#AA555580', 124 | '#AA55AA80', 125 | '#AA55FF80', 126 | '#AAAA5580', 127 | ], 128 | [ 129 | '#AAAAFF80', 130 | '#AAFF0080', 131 | '#AAFF5580', 132 | '#AAFFAA80', 133 | '#AAFFFF80', 134 | '#FF005580', 135 | '#FF00AA80', 136 | '#FF550080', 137 | '#FF555580', 138 | '#FF55AA80', 139 | '#FF55FF80', 140 | '#FFAA0080', 141 | '#FFAA5580', 142 | '#FFAAAA80', 143 | '#FFAAFF80', 144 | '#FFFF5580', 145 | ], 146 | ] 147 | -------------------------------------------------------------------------------- /src/constants/mapping/additional-symbols-pua.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | '㐂','','份','仿','侚','俉','傜','儞','冼','㔟','匇','卡','卬','詹','','呍','咖','咜','咩','唎','啊','噲','囤','圳','圴','塚','墀','姤','娣','婕','寬','﨑','㟢','庬','弴','彅','德','怗','恵','愰','昤','曈','曙','曺','曻','桒','鿄','椑','椻','橅','檑','櫛','','','','毱','泠','洮','海','涿','淊','淸','渚','潞','濹','灤','𤋮','','煇','燁','爀','玟','玨','珉','珖','琛','琡','琢','琦','琪','琬','琹','瑋','㻚','畵','疁','睲','䂓','磈','磠','祇','禮','鿆','䄃','鿅','秚','稞','筿','簱','䉤','綋','羡','脘','脺','舘','芮','葛','蓜','蓬','蕙','藎','蝕','蟬','蠋','裵','角','諶','跎','辻','迶','郝','鄧','鄭','醲','鈳','銈','錡','鍈','閒','雞','餃','饀','髙','鯖','鷗','麴','麵','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','⛌','⛍','❗','⛏','⛐','⛑','','⛒','⛕','⛓','⛔','','','','','','','','','⛖','⛗','⛘','⛙','⛚','⛛','⛜','⛝','⛞','⛟','⛠','⛡','⭕','㉈','㉉','㉊','㉋','㉌','㉍','㉎','㉏','','','','','⒑','⒒','⒓','','','','','','','','','','','','','','','','','⬛','⬤','','','','','','⚿','','','','','','','','','','','','㊙','','','','','','','','','','','','⛣','⭖','⭗','⭘','⭙','☓','㊋','〒','⛨','㉆','㉅','⛩','࿖','⛪','⛫','⛬','♨','⛭','⛮','⛯','⚓','✈','⛰','⛱','⛲','⛳','⛴','⛵','','Ⓓ','Ⓢ','⛶','','','','','','⛷','⛸','⛹','⛺','','☎','⛻','⛼','⛽','⛾','','⛿','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','➡','⬅','⬆','⬇','⬯','⬮','年','月','日','円','㎡','㎥','㎝','㎠','㎤','','⒈','⒉','⒊','⒋','⒌','⒍','⒎','⒏','⒐','','','','','','','','','','','','','','','','','㈳','㈶','㈲','㈱','㈹','㉄','▶','◀','〖','〗','⟐','²','³','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','㉇','','','℻','','','','㈪','㈫','㈬','㈭','㈮','㈯','㈰','㈷','㍾','㍽','㍼','㍻','№','℡','〶','⚾','','','','','','','','','','','','','','','','','','','','','','','ℓ','㎏','㎐','㏊','㎞','㎢','㍱','','','½','↉','⅓','⅔','¼','¾','⅕','⅖','⅗','⅘','⅙','⅚','⅐','⅛','⅑','⅒','☀','☁','☂','⛄','☖','☗','⛉','⛊','♦','♥','♣','♠','⛋','⨀','‼','⁉','⛅','☔','⛆','☃','⛇','⚡','⛈','','⚞','⚟','♬','☎','','','','Ⅰ','Ⅱ','Ⅲ','Ⅳ','Ⅴ','Ⅵ','Ⅶ','Ⅷ','Ⅸ','Ⅹ','Ⅺ','Ⅻ','⑰','⑱','⑲','⑳','⑴','⑵','⑶','⑷','⑸','⑹','⑺','⑻','⑼','⑽','⑾','⑿','㉑','㉒','㉓','㉔','','','','','','','','','','','','','','','','','','','','','','','','','','','㉕','㉖','㉗','㉘','㉙','㉚','①','②','③','④','⑤','⑥','⑦','⑧','⑨','⑩','⑪','⑫','⑬','⑭','⑮','⑯','❶','❷','❸','❹','❺','❻','❼','❽','❾','❿','⓫','⓬','㉛','' 3 | ] 4 | 5 | -------------------------------------------------------------------------------- /src/constants/mapping/additional-symbols-unicode.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | '㐂','𠅘','份','仿','侚','俉','傜','儞','冼','㔟','匇','卡','卬','詹','𠮷','呍','咖','咜','咩','唎','啊','噲','囤','圳','圴','塚','墀','姤','娣','婕','寬','﨑','㟢','庬','弴','彅','德','怗','恵','愰','昤','曈','曙','曺','曻','桒','鿄','椑','椻','橅','檑','櫛','𣏌','𣏾','𣗄','毱','泠','洮','海','涿','淊','淸','渚','潞','濹','灤','𤋮','𤋮','煇','燁','爀','玟','玨','珉','珖','琛','琡','琢','琦','琪','琬','琹','瑋','㻚','畵','疁','睲','䂓','磈','磠','祇','禮','鿆','䄃','鿅','秚','稞','筿','簱','䉤','綋','羡','脘','脺','舘','芮','葛','蓜','蓬','蕙','藎','蝕','蟬','蠋','裵','角','諶','跎','辻','迶','郝','鄧','鄭','醲','鈳','銈','錡','鍈','閒','雞','餃','饀','髙','鯖','鷗','麴','麵','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','⛌','⛍','❗','⛏','⛐','⛑','','⛒','⛕','⛓','⛔','','','','','🅿','🆊','','','⛖','⛗','⛘','⛙','⛚','⛛','⛜','⛝','⛞','⛟','⛠','⛡','⭕','㉈','㉉','㉊','㉋','㉌','㉍','㉎','㉏','','','','','⒑','⒒','⒓','🅊','🅌','🄿','🅆','🅋','🈐','🈑','🈒','🈓','🅂','🈔','🈕','🈖','🅍','🄱','🄽','⬛','⬤','🈗','🈘','🈙','🈚','🈛','⚿','🈜','🈝','🈞','🈟','🈠','🈡','🈢','🈣','🈤','🈥','🅎','㊙','🈀','','','','','','','','','','','⛣','⭖','⭗','⭘','⭙','☓','㊋','〒','⛨','㉆','㉅','⛩','࿖','⛪','⛫','⛬','♨','⛭','⛮','⛯','⚓','✈','⛰','⛱','⛲','⛳','⛴','⛵','🅗','Ⓓ','Ⓢ','⛶','🅟','🆋','🆍','🆌','🅹','⛷','⛸','⛹','⛺','🅻','☎','⛻','⛼','⛽','⛾','🅼','⛿','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','➡','⬅','⬆','⬇','⬯','⬮','年','月','日','円','㎡','㎥','㎝','㎠','㎤','🄀','⒈','⒉','⒊','⒋','⒌','⒍','⒎','⒏','⒐','','','','','','','🄁','🄂','🄃','🄄','🄅','🄆','🄇','🄈','🄉','🄊','㈳','㈶','㈲','㈱','㈹','㉄','▶','◀','〖','〗','⟐','²','³','🄭','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','🄬','🄫','㉇','🆐','🈦','℻','','','','㈪','㈫','㈬','㈭','㈮','㈯','㈰','㈷','㍾','㍽','㍼','㍻','№','℡','〶','⚾','🉀','🉁','🉂','🉃','🉄','🉅','🉆','🉇','🉈','🄪','🈧','🈨','🈩','🈔','🈪','🈫','🈬','🈭','🈮','🈯','🈰','🈱','ℓ','㎏','㎐','㏊','㎞','㎢','㍱','','','½','↉','⅓','⅔','¼','¾','⅕','⅖','⅗','⅘','⅙','⅚','⅐','⅛','⅑','⅒','☀','☁','☂','⛄','☖','☗','⛉','⛊','♦','♥','♣','♠','⛋','⨀','‼','⁉','⛅','☔','⛆','☃','⛇','⚡','⛈','','⚞','⚟','♬','☎','','','','Ⅰ','Ⅱ','Ⅲ','Ⅳ','Ⅴ','Ⅵ','Ⅶ','Ⅷ','Ⅸ','Ⅹ','Ⅺ','Ⅻ','⑰','⑱','⑲','⑳','⑴','⑵','⑶','⑷','⑸','⑹','⑺','⑻','⑼','⑽','⑾','⑿','㉑','㉒','㉓','㉔','🄐','🄑','🄒','🄓','🄔','🄕','🄖','🄗','🄘','🄙','🄚','🄛','🄜','🄝','🄞','🄟','🄠','🄡','🄢','🄣','🄤','🄥','🄦','🄧','🄨','🄩','㉕','㉖','㉗','㉘','㉙','㉚','①','②','③','④','⑤','⑥','⑦','⑧','⑨','⑩','⑪','⑫','⑬','⑭','⑮','⑯','❶','❷','❸','❹','❺','❻','❼','❽','❾','❿','⓫','⓬','㉛','' 3 | ] 4 | -------------------------------------------------------------------------------- /src/utils/high-res-texttrack.ts: -------------------------------------------------------------------------------- 1 | class HighResMetadataTextTrackCueList extends Array implements TextTrackCueList{ 2 | public addCue(cue: TextTrackCue) { 3 | this.push(cue); 4 | } 5 | 6 | public removeCue(cue: TextTrackCue) { 7 | const index = this.findIndex((c) => c === cue); 8 | if (index < 0) { return; } 9 | 10 | this.splice(index, 1); 11 | } 12 | 13 | public getCueById(id :string) { 14 | return this.find((c) => c.id === id) ?? null; 15 | } 16 | } 17 | 18 | export default class HighResMetadataTextTrack implements TextTrack { 19 | 20 | private media: HTMLMediaElement; 21 | private all: HighResMetadataTextTrackCueList = new HighResMetadataTextTrackCueList(); 22 | private active: HighResMetadataTextTrackCueList = new HighResMetadataTextTrackCueList(); 23 | 24 | private readonly polling_handler: (() => any) = this.polling.bind(this); 25 | private polling_id: number | null = null; 26 | 27 | public constructor(media: HTMLMediaElement) { 28 | this.media = media; 29 | } 30 | 31 | public startPolling() { 32 | this.polling_id = window.requestAnimationFrame(this.polling_handler); 33 | } 34 | 35 | public stopPolling() { 36 | if (this.polling_id == null) { return; } 37 | window.cancelAnimationFrame(this.polling_id); 38 | this.polling_id = null; 39 | } 40 | 41 | private polling() { 42 | const old_active = this.active; 43 | const new_active = this.activeCues; 44 | 45 | if (old_active.length !== new_active.length) { 46 | let event: CustomEvent | null = null; 47 | try { 48 | event = new CustomEvent('cuechange'); 49 | } catch { // for IE11 50 | event = document.createEvent('CustomEvent'); 51 | event.initCustomEvent('cuechange', false, false, {}); 52 | } 53 | 54 | if (event != null) { 55 | this.dispatchEvent(event); 56 | if (this.oncuechange) { this.oncuechange.call(this, event); } 57 | } 58 | } else { 59 | for (let i = 0; i < new_active.length; i++) { 60 | if (old_active[i] !== new_active[i]) { 61 | let event: CustomEvent | null = null; 62 | try { 63 | event = new CustomEvent('cuechange'); 64 | } catch { // for IE11 65 | event = document.createEvent('CustomEvent'); 66 | event.initCustomEvent('cuechange', false, false, {}); 67 | } 68 | 69 | if (event != null) { 70 | this.dispatchEvent(event); 71 | if (this.oncuechange) { this.oncuechange.call(this, event); } 72 | break; 73 | } 74 | } 75 | } 76 | } 77 | 78 | this.polling_id = window.requestAnimationFrame(this.polling_handler); 79 | } 80 | 81 | public readonly cues: TextTrackCueList = this.all; 82 | public get activeCues(): TextTrackCueList { 83 | const in_range_cues = new HighResMetadataTextTrackCueList(... this.all.filter((cue) => { 84 | return (cue.startTime <= this.media.currentTime && this.media.currentTime <= cue.endTime); 85 | })) 86 | 87 | in_range_cues.sort((a, b) => { 88 | if (a.startTime === b.startTime) { 89 | return -(a.endTime - b.endTime); 90 | } else { 91 | return (a.startTime - b.startTime); 92 | } 93 | }) 94 | 95 | this.active = in_range_cues; 96 | return this.active; 97 | } 98 | 99 | public getCueById(id: string) { 100 | return this.all.getCueById(id); 101 | } 102 | 103 | public addCue(cue: TextTrackCue) { 104 | this.all.addCue(cue); 105 | } 106 | public removeCue(cue: TextTrackCue) { 107 | this.all.removeCue(cue); 108 | } 109 | 110 | public oncuechange: ((this: TextTrack, ev: Event) => any) | null = null; 111 | 112 | public readonly id: string = ""; 113 | public readonly kind: TextTrackKind = "metadata"; 114 | public readonly label: string = ""; 115 | public readonly language: string = "ja-JP"; 116 | public readonly mode: TextTrackMode = "hidden"; 117 | public readonly inBandMetadataTrackDispatchType: string = ""; 118 | public readonly sourceBuffer = null; 119 | 120 | // for ie11 (EventTarget を継承できないため) 121 | private listeners: ((this: TextTrack, ev: Event) => any)[] = []; 122 | public addEventListener(type: 'cuechange', listener: (this: TextTrack, ev: Event) => any) { 123 | this.listeners.push(listener); 124 | } 125 | public removeEventListener(type: 'cuechange', listener: (this: TextTrack, ev: Event) => any) { 126 | const index = this.listeners.findIndex((cb) => cb === listener); 127 | if (index < 0) { return; } 128 | this.listeners.splice(index, 1); 129 | } 130 | public dispatchEvent(ev: Event): boolean { 131 | if (ev.type !== 'cuechange') { return true; } 132 | this.listeners.forEach((listener) => listener.call(this, ev)); 133 | return true; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | "declarationDir": "./d.ts", /* Redirect output structure to the directory. */ 16 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 17 | // "outFile": "./", /* Concatenate and emit output to single file. */ 18 | // "outDir": "./d" /* Redirect output structure to the directory. */ 19 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 20 | // "composite": true, /* Enable project compilation */ 21 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 22 | // "removeComments": true, /* Do not emit comments to output. */ 23 | // "noEmit": true, /* Do not emit outputs. */ 24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 25 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 27 | 28 | /* Strict Type-Checking Options */ 29 | "strict": true, /* Enable all strict type-checking options. */ 30 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 31 | // "strictNullChecks": true, /* Enable strict null checks. */ 32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 33 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 34 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 35 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 36 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 37 | 38 | /* Additional Checks */ 39 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 40 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 41 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 42 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 43 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 44 | 45 | /* Module Resolution Options */ 46 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 47 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 48 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 50 | // "typeRoots": [], /* List of folders to include type definitions from. */ 51 | // "types": [], /* Type declaration files to be included in compilation. */ 52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 53 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 56 | 57 | /* Source Map Options */ 58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 62 | 63 | /* Experimental Options */ 64 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 65 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 66 | 67 | /* Advanced Options */ 68 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 69 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 70 | }, 71 | "include": [ 72 | "src/**/*" 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /src/constants/jis8.ts: -------------------------------------------------------------------------------- 1 | export const enum JIS8 { 2 | NUL = 0x00, 3 | BEL = 0x07, 4 | APB = 0x08, 5 | APF = 0x09, 6 | APD = 0x0a, 7 | APU = 0x0b, 8 | CS = 0x0c, 9 | APR = 0x0d, 10 | LS1 = 0x0e, 11 | LS0 = 0x0f, 12 | PAPF = 0x16, 13 | CAN = 0x18, 14 | SS2 = 0x19, 15 | ESC = 0x1b, 16 | APS = 0x1c, 17 | SS3 = 0x1d, 18 | RS = 0x1e, 19 | US = 0x1f, 20 | SP = 0x20, 21 | DEL = 0x7f, 22 | BKF = 0x80, 23 | RDF = 0x81, 24 | GRF = 0x82, 25 | YLF = 0x83, 26 | BLF = 0x84, 27 | MGF = 0x85, 28 | CNF = 0x86, 29 | WHF = 0x87, 30 | SSZ = 0x88, 31 | MSZ = 0x89, 32 | NSZ = 0x8a, 33 | SZX = 0x8b, 34 | COL = 0x90, 35 | FLC = 0x91, 36 | CDC = 0x92, 37 | POL = 0x93, 38 | WMM = 0x94, 39 | MACRO = 0x95, 40 | HLC = 0x97, 41 | RPC = 0x98, 42 | SPL = 0x99, 43 | STL = 0x9a, 44 | CSI = 0x9b, 45 | TIME = 0x9d, 46 | } 47 | 48 | export const enum CSI { 49 | GSM = 0x42, 50 | SWF = 0x53, 51 | CCC = 0x54, 52 | SDF = 0x56, 53 | SSM = 0x57, 54 | SHS = 0x58, 55 | SVS = 0x59, 56 | PLD = 0x5b, 57 | PLU = 0x5c, 58 | GAA = 0x5d, 59 | SRC = 0x5e, 60 | SDP = 0x5f, 61 | ACPS = 0x61, 62 | TCC = 0x62, 63 | ORN = 0x63, 64 | MDF = 0x64, 65 | CFS = 0x65, 66 | XCS = 0x66, 67 | SCR = 0x67, 68 | PRA = 0x68, 69 | ACS = 0x69, 70 | UED = 0x6a, 71 | RCS = 0x6e, 72 | SCS = 0x6f, 73 | } 74 | 75 | export const enum ESC { 76 | LS2 = 0x6e, 77 | LS3 = 0x6f, 78 | LS1R = 0x7e, 79 | LS2R = 0x7d, 80 | LS3R = 0x7c, 81 | } 82 | 83 | const enum ALPHABETS { 84 | KANJI, 85 | ASCII, 86 | HIRAGANA, 87 | KATAKANA, 88 | MOSAIC_A, 89 | MOSAIC_B, 90 | MOSAIC_C, 91 | MOSAIC_D, 92 | P_ASCII, 93 | P_HIRAGANA, 94 | P_KATAKANA, 95 | JIS_X0201_KATAKANA, 96 | JIS_X0213_2004_KANJI_1, 97 | JIS_X0213_2004_KANJI_2, 98 | ADDITIONAL_SYMBOLS, 99 | 100 | DRCS_0, 101 | DRCS_1, 102 | DRCS_2, 103 | DRCS_3, 104 | DRCS_4, 105 | DRCS_5, 106 | DRCS_6, 107 | DRCS_7, 108 | DRCS_8, 109 | DRCS_9, 110 | DRCS_10, 111 | DRCS_11, 112 | DRCS_12, 113 | DRCS_13, 114 | DRCS_14, 115 | DRCS_15, 116 | MACRO, 117 | } 118 | export { ALPHABETS } 119 | 120 | export interface ALPHABET_ENTRY { 121 | bytes: number 122 | alphabet: ALPHABETS 123 | } 124 | 125 | export const G_SET_BY_ALPHABET = new Map([ 126 | [ALPHABETS.KANJI, { bytes: 2, alphabet: ALPHABETS.KANJI }], 127 | [ALPHABETS.ASCII, { bytes: 1, alphabet: ALPHABETS.ASCII }], 128 | [ALPHABETS.HIRAGANA, { bytes: 1, alphabet: ALPHABETS.HIRAGANA }], 129 | [ALPHABETS.KATAKANA, { bytes: 1, alphabet: ALPHABETS.KATAKANA }], 130 | [ALPHABETS.MOSAIC_A, { bytes: 1, alphabet: ALPHABETS.MOSAIC_A }], 131 | [ALPHABETS.MOSAIC_B, { bytes: 1, alphabet: ALPHABETS.MOSAIC_B }], 132 | [ALPHABETS.MOSAIC_C, { bytes: 1, alphabet: ALPHABETS.MOSAIC_C }], 133 | [ALPHABETS.MOSAIC_D, { bytes: 1, alphabet: ALPHABETS.MOSAIC_D }], 134 | [ALPHABETS.P_ASCII, { bytes: 1, alphabet: ALPHABETS.P_ASCII }], 135 | [ALPHABETS.P_HIRAGANA, { bytes: 1, alphabet: ALPHABETS.P_HIRAGANA }], 136 | [ALPHABETS.P_KATAKANA, { bytes: 1, alphabet: ALPHABETS.P_KATAKANA }], 137 | [ 138 | ALPHABETS.JIS_X0201_KATAKANA, 139 | { bytes: 1, alphabet: ALPHABETS.JIS_X0201_KATAKANA }, 140 | ], 141 | [ 142 | ALPHABETS.JIS_X0213_2004_KANJI_1, 143 | { bytes: 2, alphabet: ALPHABETS.JIS_X0213_2004_KANJI_1 }, 144 | ], 145 | [ 146 | ALPHABETS.JIS_X0213_2004_KANJI_2, 147 | { bytes: 2, alphabet: ALPHABETS.JIS_X0213_2004_KANJI_2 }, 148 | ], 149 | [ 150 | ALPHABETS.ADDITIONAL_SYMBOLS, 151 | { bytes: 2, alphabet: ALPHABETS.ADDITIONAL_SYMBOLS }, 152 | ], 153 | ]) 154 | export const G_SET_BY_F = new Map([ 155 | [0x42, { bytes: 2, alphabet: ALPHABETS.KANJI }], 156 | [0x4a, { bytes: 1, alphabet: ALPHABETS.ASCII }], 157 | [0x30, { bytes: 1, alphabet: ALPHABETS.HIRAGANA }], 158 | [0x31, { bytes: 1, alphabet: ALPHABETS.KATAKANA }], 159 | [0x32, { bytes: 1, alphabet: ALPHABETS.MOSAIC_A }], 160 | [0x33, { bytes: 1, alphabet: ALPHABETS.MOSAIC_B }], 161 | [0x34, { bytes: 1, alphabet: ALPHABETS.MOSAIC_C }], 162 | [0x35, { bytes: 1, alphabet: ALPHABETS.MOSAIC_D }], 163 | [0x36, { bytes: 1, alphabet: ALPHABETS.P_ASCII }], 164 | [0x37, { bytes: 1, alphabet: ALPHABETS.P_HIRAGANA }], 165 | [0x38, { bytes: 1, alphabet: ALPHABETS.P_KATAKANA }], 166 | [0x49, { bytes: 1, alphabet: ALPHABETS.JIS_X0201_KATAKANA }], 167 | [0x39, { bytes: 2, alphabet: ALPHABETS.JIS_X0213_2004_KANJI_1 }], 168 | [0x3a, { bytes: 2, alphabet: ALPHABETS.JIS_X0213_2004_KANJI_2 }], 169 | [0x3b, { bytes: 2, alphabet: ALPHABETS.ADDITIONAL_SYMBOLS }], 170 | ]) 171 | 172 | export const G_DRCS_BY_ALPHABET = new Map([ 173 | [ALPHABETS.DRCS_0, { bytes: 2, alphabet: ALPHABETS.DRCS_0 }], 174 | [ALPHABETS.DRCS_1, { bytes: 1, alphabet: ALPHABETS.DRCS_1 }], 175 | [ALPHABETS.DRCS_2, { bytes: 1, alphabet: ALPHABETS.DRCS_2 }], 176 | [ALPHABETS.DRCS_3, { bytes: 1, alphabet: ALPHABETS.DRCS_3 }], 177 | [ALPHABETS.DRCS_4, { bytes: 1, alphabet: ALPHABETS.DRCS_4 }], 178 | [ALPHABETS.DRCS_5, { bytes: 1, alphabet: ALPHABETS.DRCS_5 }], 179 | [ALPHABETS.DRCS_6, { bytes: 1, alphabet: ALPHABETS.DRCS_6 }], 180 | [ALPHABETS.DRCS_7, { bytes: 1, alphabet: ALPHABETS.DRCS_7 }], 181 | [ALPHABETS.DRCS_8, { bytes: 1, alphabet: ALPHABETS.DRCS_8 }], 182 | [ALPHABETS.DRCS_9, { bytes: 1, alphabet: ALPHABETS.DRCS_9 }], 183 | [ALPHABETS.DRCS_10, { bytes: 1, alphabet: ALPHABETS.DRCS_10 }], 184 | [ALPHABETS.DRCS_11, { bytes: 1, alphabet: ALPHABETS.DRCS_11 }], 185 | [ALPHABETS.DRCS_12, { bytes: 1, alphabet: ALPHABETS.DRCS_12 }], 186 | [ALPHABETS.DRCS_13, { bytes: 1, alphabet: ALPHABETS.DRCS_13 }], 187 | [ALPHABETS.DRCS_14, { bytes: 1, alphabet: ALPHABETS.DRCS_14 }], 188 | [ALPHABETS.DRCS_15, { bytes: 1, alphabet: ALPHABETS.DRCS_15 }], 189 | [ALPHABETS.MACRO, { bytes: 1, alphabet: ALPHABETS.MACRO }], 190 | ]) 191 | 192 | export const G_DRCS_BY_F = new Map([ 193 | [0x40, { bytes: 2, alphabet: ALPHABETS.DRCS_0 }], 194 | [0x41, { bytes: 1, alphabet: ALPHABETS.DRCS_1 }], 195 | [0x42, { bytes: 1, alphabet: ALPHABETS.DRCS_2 }], 196 | [0x43, { bytes: 1, alphabet: ALPHABETS.DRCS_3 }], 197 | [0x44, { bytes: 1, alphabet: ALPHABETS.DRCS_4 }], 198 | [0x45, { bytes: 1, alphabet: ALPHABETS.DRCS_5 }], 199 | [0x46, { bytes: 1, alphabet: ALPHABETS.DRCS_6 }], 200 | [0x47, { bytes: 1, alphabet: ALPHABETS.DRCS_7 }], 201 | [0x48, { bytes: 1, alphabet: ALPHABETS.DRCS_8 }], 202 | [0x49, { bytes: 1, alphabet: ALPHABETS.DRCS_9 }], 203 | [0x4a, { bytes: 1, alphabet: ALPHABETS.DRCS_10 }], 204 | [0x4b, { bytes: 1, alphabet: ALPHABETS.DRCS_11 }], 205 | [0x4c, { bytes: 1, alphabet: ALPHABETS.DRCS_12 }], 206 | [0x4d, { bytes: 1, alphabet: ALPHABETS.DRCS_13 }], 207 | [0x4e, { bytes: 1, alphabet: ALPHABETS.DRCS_14 }], 208 | [0x4f, { bytes: 1, alphabet: ALPHABETS.DRCS_15 }], 209 | [0x70, { bytes: 1, alphabet: ALPHABETS.MACRO }], 210 | ]) 211 | -------------------------------------------------------------------------------- /src/utils/md5.ts: -------------------------------------------------------------------------------- 1 | const L = (x: number, n: number) => { 2 | return (x << n) | (x >>> (32 - n)) 3 | } 4 | 5 | const X = (Y: (b: number, c: number, d: number) => number, a: number, b: number, c: number, d: number, m: number, k: number, s: number) => { 6 | return (L((a + Y(b, c, d) + m + k) | 0, s) + b) | 0 7 | } 8 | 9 | const F = (b: number, c: number, d: number) => { 10 | return (b & c) | ((~b) & d); 11 | } 12 | 13 | const G = (b: number, c: number, d: number) => { 14 | return (b & d) | (c & (~d)); 15 | } 16 | 17 | const H = (b: number, c: number, d: number) => { 18 | return b ^ c ^ d; 19 | } 20 | 21 | const I = (b: number, c: number, d: number) => { 22 | return c ^ (b | (~d)); 23 | } 24 | 25 | const byteToHex = (byte: number) => { 26 | const upper = (byte & 0xF0) >> 4; 27 | const lower = (byte & 0x0F) >> 0; 28 | return `${upper.toString(16)}${lower.toString(16)}` 29 | } 30 | 31 | export default (buffer: ArrayBuffer): string => { 32 | const dataLength = Math.floor((buffer.byteLength + 8) / 64 + 1) * 64; 33 | 34 | const padding = new Uint8Array(dataLength); 35 | padding.set(new Uint8Array(buffer), 0) 36 | 37 | const view = new DataView(padding.buffer); 38 | view.setUint8(buffer.byteLength, 0x80); 39 | view.setUint32(dataLength - 8, (buffer.byteLength * 8) % (2 ** 32), true); 40 | view.setUint32(dataLength - 4, (buffer.byteLength * 8) / (2 ** 32), true); 41 | 42 | let a = 0x67452301 | 0; 43 | let b = 0xefcdab89 | 0; 44 | let c = 0x98badcfe | 0; 45 | let d = 0x10325476 | 0; 46 | 47 | for (let i = 0; i < dataLength; i += 64) { 48 | let aa = a, bb = b, cc = c, dd = d; 49 | 50 | a = X(F, a, b, c, d, view.getUint32(i + 4 * 0, true), 0xd76aa478, 7); 51 | d = X(F, d, a, b, c, view.getUint32(i + 4 * 1, true), 0xe8c7b756, 12); 52 | c = X(F, c, d, a, b, view.getUint32(i + 4 * 2, true), 0x242070db, 17); 53 | b = X(F, b, c, d, a, view.getUint32(i + 4 * 3, true), 0xc1bdceee, 22); 54 | a = X(F, a, b, c, d, view.getUint32(i + 4 * 4, true), 0xf57c0faf, 7); 55 | d = X(F, d, a, b, c, view.getUint32(i + 4 * 5, true), 0x4787c62a, 12); 56 | c = X(F, c, d, a, b, view.getUint32(i + 4 * 6, true), 0xa8304613, 17); 57 | b = X(F, b, c, d, a, view.getUint32(i + 4 * 7, true), 0xfd469501, 22); 58 | a = X(F, a, b, c, d, view.getUint32(i + 4 * 8, true), 0x698098d8, 7); 59 | d = X(F, d, a, b, c, view.getUint32(i + 4 * 9, true), 0x8b44f7af, 12); 60 | c = X(F, c, d, a, b, view.getUint32(i + 4 * 10, true), 0xffff5bb1, 17); 61 | b = X(F, b, c, d, a, view.getUint32(i + 4 * 11, true), 0x895cd7be, 22); 62 | a = X(F, a, b, c, d, view.getUint32(i + 4 * 12, true), 0x6b901122, 7); 63 | d = X(F, d, a, b, c, view.getUint32(i + 4 * 13, true), 0xfd987193, 12); 64 | c = X(F, c, d, a, b, view.getUint32(i + 4 * 14, true), 0xa679438e, 17); 65 | b = X(F, b, c, d, a, view.getUint32(i + 4 * 15, true), 0x49b40821, 22); 66 | 67 | a = X(G, a, b, c, d, view.getUint32(i + 4 * 1, true), 0xf61e2562, 5); 68 | d = X(G, d, a, b, c, view.getUint32(i + 4 * 6, true), 0xc040b340, 9); 69 | c = X(G, c, d, a, b, view.getUint32(i + 4 * 11, true), 0x265e5a51, 14); 70 | b = X(G, b, c, d, a, view.getUint32(i + 4 * 0, true), 0xe9b6c7aa, 20); 71 | a = X(G, a, b, c, d, view.getUint32(i + 4 * 5, true), 0xd62f105d, 5); 72 | d = X(G, d, a, b, c, view.getUint32(i + 4 * 10, true), 0x02441453, 9); 73 | c = X(G, c, d, a, b, view.getUint32(i + 4 * 15, true), 0xd8a1e681, 14); 74 | b = X(G, b, c, d, a, view.getUint32(i + 4 * 4, true), 0xe7d3fbc8, 20); 75 | a = X(G, a, b, c, d, view.getUint32(i + 4 * 9, true), 0x21e1cde6, 5); 76 | d = X(G, d, a, b, c, view.getUint32(i + 4 * 14, true), 0xc33707d6, 9); 77 | c = X(G, c, d, a, b, view.getUint32(i + 4 * 3, true), 0xf4d50d87, 14); 78 | b = X(G, b, c, d, a, view.getUint32(i + 4 * 8, true), 0x455a14ed, 20); 79 | a = X(G, a, b, c, d, view.getUint32(i + 4 * 13, true), 0xa9e3e905, 5); 80 | d = X(G, d, a, b, c, view.getUint32(i + 4 * 2, true), 0xfcefa3f8, 9); 81 | c = X(G, c, d, a, b, view.getUint32(i + 4 * 7, true), 0x676f02d9, 14); 82 | b = X(G, b, c, d, a, view.getUint32(i + 4 * 12, true), 0x8d2a4c8a, 20); 83 | 84 | a = X(H, a, b, c, d, view.getUint32(i + 4 * 5, true), 0xfffa3942, 4); 85 | d = X(H, d, a, b, c, view.getUint32(i + 4 * 8, true), 0x8771f681, 11); 86 | c = X(H, c, d, a, b, view.getUint32(i + 4 * 11, true), 0x6d9d6122, 16); 87 | b = X(H, b, c, d, a, view.getUint32(i + 4 * 14, true), 0xfde5380c, 23); 88 | a = X(H, a, b, c, d, view.getUint32(i + 4 * 1, true), 0xa4beea44, 4); 89 | d = X(H, d, a, b, c, view.getUint32(i + 4 * 4, true), 0x4bdecfa9, 11); 90 | c = X(H, c, d, a, b, view.getUint32(i + 4 * 7, true), 0xf6bb4b60, 16); 91 | b = X(H, b, c, d, a, view.getUint32(i + 4 * 10, true), 0xbebfbc70, 23); 92 | a = X(H, a, b, c, d, view.getUint32(i + 4 * 13, true), 0x289b7ec6, 4); 93 | d = X(H, d, a, b, c, view.getUint32(i + 4 * 0, true), 0xeaa127fa, 11); 94 | c = X(H, c, d, a, b, view.getUint32(i + 4 * 3, true), 0xd4ef3085, 16); 95 | b = X(H, b, c, d, a, view.getUint32(i + 4 * 6, true), 0x04881d05, 23); 96 | a = X(H, a, b, c, d, view.getUint32(i + 4 * 9, true), 0xd9d4d039, 4); 97 | d = X(H, d, a, b, c, view.getUint32(i + 4 * 12, true), 0xe6db99e5, 11); 98 | c = X(H, c, d, a, b, view.getUint32(i + 4 * 15, true), 0x1fa27cf8, 16); 99 | b = X(H, b, c, d, a, view.getUint32(i + 4 * 2, true), 0xc4ac5665, 23); 100 | 101 | a = X(I, a, b, c, d, view.getUint32(i + 4 * 0, true), 0xf4292244, 6); 102 | d = X(I, d, a, b, c, view.getUint32(i + 4 * 7, true), 0x432aff97, 10); 103 | c = X(I, c, d, a, b, view.getUint32(i + 4 * 14, true), 0xab9423a7, 15); 104 | b = X(I, b, c, d, a, view.getUint32(i + 4 * 5, true), 0xfc93a039, 21); 105 | a = X(I, a, b, c, d, view.getUint32(i + 4 * 12, true), 0x655b59c3, 6); 106 | d = X(I, d, a, b, c, view.getUint32(i + 4 * 3, true), 0x8f0ccc92, 10); 107 | c = X(I, c, d, a, b, view.getUint32(i + 4 * 10, true), 0xffeff47d, 15); 108 | b = X(I, b, c, d, a, view.getUint32(i + 4 * 1, true), 0x85845dd1, 21); 109 | a = X(I, a, b, c, d, view.getUint32(i + 4 * 8, true), 0x6fa87e4f, 6); 110 | d = X(I, d, a, b, c, view.getUint32(i + 4 * 15, true), 0xfe2ce6e0, 10); 111 | c = X(I, c, d, a, b, view.getUint32(i + 4 * 6, true), 0xa3014314, 15); 112 | b = X(I, b, c, d, a, view.getUint32(i + 4 * 13, true), 0x4e0811a1, 21); 113 | a = X(I, a, b, c, d, view.getUint32(i + 4 * 4, true), 0xf7537e82, 6); 114 | d = X(I, d, a, b, c, view.getUint32(i + 4 * 11, true), 0xbd3af235, 10); 115 | c = X(I, c, d, a, b, view.getUint32(i + 4 * 2, true), 0x2ad7d2bb, 15); 116 | b = X(I, b, c, d, a, view.getUint32(i + 4 * 9, true), 0xeb86d391, 21); 117 | 118 | a = (aa + a) | 0 119 | b = (bb + b) | 0 120 | c = (cc + c) | 0 121 | d = (dd + d) | 0 122 | } 123 | 124 | let result = ''; 125 | result += byteToHex((a & 0x000000FF) >>> 0); 126 | result += byteToHex((a & 0x0000FF00) >>> 8); 127 | result += byteToHex((a & 0x00FF0000) >>> 16); 128 | result += byteToHex((a & 0xFF000000) >>> 24); 129 | result += byteToHex((b & 0x000000FF) >>> 0); 130 | result += byteToHex((b & 0x0000FF00) >>> 8); 131 | result += byteToHex((b & 0x00FF0000) >>> 16); 132 | result += byteToHex((b & 0xFF000000) >>> 24); 133 | result += byteToHex((c & 0x000000FF) >>> 0); 134 | result += byteToHex((c & 0x0000FF00) >>> 8); 135 | result += byteToHex((c & 0x00FF0000) >>> 16); 136 | result += byteToHex((c & 0xFF000000) >>> 24); 137 | result += byteToHex((d & 0x000000FF) >>> 0); 138 | result += byteToHex((d & 0x0000FF00) >>> 8); 139 | result += byteToHex((d & 0x00FF0000) >>> 16); 140 | result += byteToHex((d & 0xFF000000) >>> 24); 141 | 142 | return result; 143 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aribb24.js [![npm](https://img.shields.io/npm/v/aribb24.js.svg?style=flat)](https://www.npmjs.com/package/aribb24.js) 2 | 3 | An HTML5 subtitle renderer. 4 | It is alternative implementation for [b24.js](https://github.com/xqq/b24.js). 5 | 6 | ## Feature 7 | 8 | * HTML5 Canvas based dot by dot subtitle rendering 9 | * Fully compatible of [b24.js](https://github.com/xqq/b24.js) API 10 | * Colored rendering with font color and background color specified by data packet 11 | 12 | ## Special Thanks 13 | 14 | * Use glyph data from [和田研中丸ゴシック2004ARIB](https://ja.osdn.net/projects/jis2004/wiki/FrontPage) for ARIB additional symbol rendering. 15 | * aribb24-embedded.js utilizes embedded glyph data exported from the font which is released under public domain license. 16 | * Inspired by [b24.js](https://github.com/xqq/b24.js). 17 | * The pioneer of ARIB caption rendering on Web. 18 | * Influenced by [TVCaptionMod2](https://github.com/xtne6f/TVCaptionMod2). 19 | * Got lots of feedback form the project author and heavily inspired by it. 20 | 21 | ## Options 22 | 23 | * data_identifier: Specify number 0x80 (caption) or 0x81 (superimpose). default: 0x80 (caption) 24 | * data_group_id: Specify number 0x01 (1st language) or 0x02 (2nd language). default: 0x01 (1st language) 25 | * forceStrokeColor: Specify a color or true for always drawing character's stroke. 26 | * forceBackgroundColor: Specify a color for always drawing character's background 27 | * normalFont: Specify a font for drawing normal characters 28 | * gainiFont: Specify a font for drawing ARIB gaiji characters 29 | * drcsReplacement: Replace DRCS to text if possible 30 | * drcsReplaceMapping: add more DRCS Mapping by Object (ex. { md5: character }) 31 | * currently, replace to full-width character only supported. 32 | * renderedTextCallback: specify rendered text callback 33 | * Type Definition is renderedText (renderedText: string) => unknown 34 | * PRACallback: specify PRA callback for SuperImpose 35 | * Type Definition is PRA (index: number) => unknown 36 | * keepAspectRatio: keep caption's aspect ratio in any container. (default: true) 37 | * enableRawCanvas: enable raw video resolution canvas. it can get getRawCanvas method. 38 | * useStroke: use render outer stroke by strokeText or stroke (strokeText or stroke) API. (default: true) 39 | * usePUA: use PUA (Private Use Area) instead of unicode 5.2 (for Windows TV Gothic) 40 | * useHighResTextTrack: use polling instead of native cuechange event for b24 TextTrackCue handling. 41 | * useHighResTimeupdate: use polling instead of native timeupdate event for id3 TextTrackCue handling. 42 | * enableAutoInBandMetadataTextTrackDetection: enable InBand Metadata (id3) TextTrack auto detection. (default: true) 43 | * Recommended enableAutoInBandMetadataTextTrackDetection Settings 44 | * Safari (iOS, iPadOS, Mac OS) Native HLS Player: true 45 | * Legacy Edge Native HLS Player: true 46 | * hls.js : false (Please use FRAG_PARSING_METADATA event instead of this option) 47 | * video.js : false (Please set video.js's Timed Metadata TextTrack manually, not supported auto detection.) 48 | 49 | ## Build 50 | 51 | ### Preparing 52 | 53 | ```bash 54 | git clone https://github.com/monyone/aribb24.js 55 | cd aribb24.js 56 | yarn 57 | ``` 58 | 59 | ### Compiling aribb24.js library 60 | 61 | ```bash 62 | yarn run build 63 | ``` 64 | 65 | ## Getting Started 66 | 67 | ### with native player and hls.js (for id3 timed-metadata inserted stream) 68 | 69 | ```html 70 | 71 | 72 | 73 | 106 | ``` 107 | 108 | ### with video.js (for id3 timed-metadata inserted stream) 109 | 110 | ```html 111 | 112 | 113 | 114 | 115 | 158 | ``` 159 | 160 | ### with shaka-player (for id3 timed-metadata inserted stream) 161 | 162 | ```html 163 | 164 | 165 | 166 | 167 | 202 | ``` 203 | 204 | ### with hls-b24.js (for private_stream_1 inserted stream) 205 | 206 | ```html 207 | 208 | 209 | 210 | 235 | ``` 236 | 237 | ## Limitations 238 | 239 | * CanvasRenderer in Android Chrome with native HLS player dose not work 240 | * Because not support id3 timedmetadata in Android Chrome 241 | * aribb24-embedded.js on IE11 requires [path2d-polyfill](https://github.com/nilzona/path2d-polyfill). 242 | * Embedded glyph rendering use Path2D. IE11 does not support this feature. 243 | -------------------------------------------------------------------------------- /src/constants/mapping/drcs-NSZ.ts: -------------------------------------------------------------------------------- 1 | export default new Map([ 2 | ['022b6f43e2a414fd68f172da202bac9a', '⚞'], // gaiji 3 | ['94fb7be756372db6b62e3e0a119083d5', '⚞'], 4 | 5 | ['12aecdea283e4d07f88b9f2b740e4f86', '⚟'], // gaiji 6 | ['1a563501affbf7f5baec350a108d5505', '⚟'], 7 | ['556971570f40044fa4520df3289a1cf2', '⚟'], 8 | ['65b042886a563a771aa389b12af7bca7', '⚟'], 9 | 10 | ['bbda644d17efd3c020635ee3d90968a5', '⦅'], 11 | ['5063561406195ca45f5992e3f7ad77d2', '⦅'], 12 | ['9d15c0395a4738936af34308acf2d032', '⦅'], 13 | ['2a063edc4770b3403f060b38166a0d4d', '⦅'], 14 | ['54479aa90145b4713134b78d4fb98aa5', '⦅'], 15 | ['12a2c7156da32fc972b5a451bb87b813', '⦅'], 16 | ['c8d428ead557285b0b7088388b22519c', '⦅'], 17 | ['7160f7419cba7acdacd23cbeb4834dbe', '⦅'], 18 | ['3c49616fb9bf0b9052b30e118f8857ea', '⦅'], 19 | ['71c94bb6d963e47443eac448a09d22ce', '⦅'], 20 | ['7ec2179107ba4c58abb6ef92e7781365', '⦅'], 21 | ['2eb49bd25d7eeada006afc0864350da4', '⦅'], 22 | ['a341ee7fe8a368c9737a3341f016ac70', '⦅'], 23 | ['f47048d669ac8d84eeb62477e8420f89', '⦅'], 24 | 25 | ['38566b372f4c5a1aead4efa20decd079', '⦆'], 26 | ['d84fc83615b75802ed422eda4ba39465', '⦆'], 27 | ['5bb8b7731d9473ebd7c842334dfa24f2', '⦆'], 28 | ['9ffa7e00cfc7e807a161ada460b8060c', '⦆'], 29 | ['a58dc0e1271b03a5981b57a83271afa7', '⦆'], 30 | ['e67210b0da0161d36b79e8c9be6a9d0c', '⦆'], 31 | ['08de4be9569ebd6ac01709f552ae8a65', '⦆'], 32 | ['e214599903c94c532684bdf54b62df61', '⦆'], 33 | ['2a83209f8a7489081890c277397df425', '⦆'], 34 | ['0294d50cea5197c8c4646d2cace3e78d', '⦆'], 35 | ['0b808509e4d89a2b9d02252ca85f2e34', '⦆'], 36 | ['2a74d4ad7292c858dc2bb559de67f2d9', '⦆'], 37 | ['8c810b8cbe6159e837a88575bb4e6033', '⦆'], 38 | ['dc66317cd6fff4f4221069a20f321fce', '⦆'], 39 | 40 | ['563e1633d226c10ef4ec80638997e4a9', '『'], 41 | ['0993d5cdf910f481eeefa19e4f09d77c', '『'], 42 | ['7b80a8345c16e2d4f8ff2691e245c2b1', '『'], 43 | ['016669fa94786f9581342d47f317c02c', '『'], 44 | ['01d3eb52ab29f0eecc62ff74224fffd4', '『'], 45 | ['089aa1d87915ef8ad3c43982ac657c8c', '『'], 46 | ['30e8cb69cda3ad84e87943c4351c24b7', '『'], 47 | ['385927959c2621acf57f8d40140924f8', '『'], 48 | ['2c256506f406bac4c214318f196ad5db', '『'], 49 | ['2d3912e10113e5c7bef33df3249af4a7', '『'], 50 | ['8b6444be18f269ac615643b26f9e3041', '『'], 51 | ['9c8c1ff659b439f73c65cf4766ab2f14', '『'], 52 | ['e4caa1628ad6878f14be986761e06aaa', '『'], 53 | 54 | ['e702912587801d73d58cdb30e48debed', '』'], 55 | ['d70bb2b097f44c1ddefb93bf92bbb5cd', '』'], 56 | ['f2b927267947a75b891403f95db72005', '』'], 57 | ['4ec38a1d8d22e4df6c359f00f7ad8662', '』'], 58 | ['55c9ea9aa8eb630e5ecb793b2f85c927', '』'], 59 | ['70376e1ea05a3438a19c062ad49a7960', '』'], 60 | ['8fe7cb78ca24d1973419eecf99252a88', '』'], 61 | ['9ee59c7d2c202e0214836a0138f59e24', '』'], 62 | ['b56aaf7fc68c5e206ccbc2ee1442b3af', '』'], 63 | ['ba37f6b56d8fc8980c8236de9894fa61', '』'], 64 | ['cc9fde9238a2bf78fd1c13f65b098e77', '』'], 65 | ['f02e3e84dcd71c5d3bab2b7b4b99bd7e', '』'], 66 | ['f686e0b742abe806fccbd4d9b3fcc4cd', '』'], 67 | 68 | ['37f6ecf37a0a3ef8dff083ccc8754f81', '♬'], 69 | ['3336f18e849144658f212bd9399bec5f', '♬'], 70 | ['93efdc18683d8ecacb0a920d5f2fffb3', '♬'], 71 | ['9b8325b71aa6a000d24f88c4d7ec730d', '♬'], 72 | ['ab791ef796e6b5d66f13ed9aea3e8ab2', '♬'], 73 | 74 | ['6eb29f1917caea1cadf94f5496a4c374', '↱'], 75 | 76 | ['583134b86e7d90960f64c5b863196978', '➡'], 77 | ['4ba716a88c003ca0a069392be3b63951', '➡'], 78 | ['4e0fbe47e3ba0fd5949bda53f11b16a5', '➡'], 79 | ['61ec226a927ee80fffa12db219a43233', '➡'], 80 | 81 | ['8e5b873ac8e1bf84246b281b3548c2ff', '↴'], 82 | ['a78d9b65f46654601ce0145622164b47', '↴'], 83 | ['5d01e6804b9aaec0c276f77306888c54', '↴'], 84 | ['6168af1e81b6497fccb6b8d3226a8016', '↴'], 85 | 86 | ['4360c0b7364802b680f5a65fa415bdd6', '↗'], 87 | ['4dab788480bb9ac50d2454b58438e407', '↗'], 88 | ['bfb2d58ab8c469d2b8b5c42d81e4e3b7', '↗'], 89 | 90 | ['4c503a0873195bfe8d71c9d55669781b', '→'], 91 | ['caf36eff2cf3580cd66c5cd021ee4c09', '→'], 92 | 93 | ['f00be20caf0aaef3a6fbec90a0e71852', 'Ⅰ'], 94 | 95 | ['e660e1e23a6ddc9a5d2e0e1ef7ac5b86', 'Ⅱ'], 96 | ['a62583f621fb5405add08e8f0beb6db4', 'Ⅱ'], 97 | ['0e761ebb18b9870383725b3712f5c8d4', 'Ⅱ'], 98 | ['1f65debfbf9df96de52c6f80922b012b', 'Ⅱ'], 99 | ['5c13facf2da9f38922a9419061771ed0', 'Ⅱ'], 100 | ['75a65cc3171c4c7ca0141042846ab91a', 'Ⅱ'], 101 | ['eae94a6301787ff7bf77786ae4424601', 'Ⅱ'], 102 | 103 | ['f1add7809e18e064e4609783211c9815', 'Ⅲ'], 104 | 105 | ['a7ee6f7f63d348e2b8fb7ee9503f3c5c', '♡'], 106 | 107 | ['9c8cfb5e9349b06f0939605638896f4e', '♥'], 108 | 109 | ['45ce7d6d5c779136d32d3e60e13e10cd', '⅕'], 110 | ['51f5fe58aaf460263b766e990fdbe979', '⅕'], 111 | ['db40b0a65939e462396822d5ab3c6d9c', '⅕'], 112 | 113 | ['d9e3a48d5a7c6ba6f8db18f56cf91f92', '⅛'], 114 | 115 | ['86586bcdf8f14883f846849e93ca274c', '⅜'], 116 | 117 | ['9d81f46e134081d56bc92f69eebfabd9', '⅒'], 118 | ['18dddb04a4fe9b3f5c7b79e68fb8ab4b', '⅒'], 119 | ['eff8659a150859b7b69682a023b283c1', '⅒'], 120 | 121 | ['6e5ccf08b2bc815b0923df83cf9fafa1', '㎠'], 122 | ['8a77e56517a074d3d2ba426b84a07bf4', '㎠'], 123 | 124 | ['2c381a0eab014487d50f6f8bae8f0b71', '㎢'], 125 | ['9d1a36a1bec1cd2b0b0765f93c1e4f3c', '㎢'], 126 | ['e03eb00c54de790d8cc9997527fde905', '㎢'], 127 | 128 | ['3bce2a06a6a8557082543a6c90a42fe0', '〽'], 129 | ['4898c7d9fe3a8a6f9859b0e6f85a4327', '〽'], 130 | ['98ab18764756c8ca7608e17f562b21ce', '〽'], 131 | 132 | ['3037aad230d8cdae3df6e0ebedc0db79', '⁉'], 133 | ['66e3474e6cbd8e817ba0a1f8920bf4e7', '⁉'], 134 | 135 | ['4360dd96063ce1a9660cc8437e8238e3', '⁈'], 136 | ['6ce68b7e389c5169309ee956ed0c98a8', '⁈'], 137 | ['737a19289d25d963e255f3692ded6536', '⁈'], 138 | 139 | ['14b18199bbc3f4bf65b72e316bc41d3c', '!'], 140 | ['6bf58c146b692aeb403ed1f7618a060a', '!'], 141 | ['7f12b67caaf7c8c5075b444bb2a16c70', '!'], 142 | 143 | ['bfd55f4031ad80cb7401d65937b1d5d9', '〜'], 144 | ['4a61f6f7da9e6c8e373f4112cbd453cf', '〜'], 145 | ['882ded8f0bb4cdfa4ce28a0b64056d2a', '〜'], 146 | ['7726ffbf3a6e953affe6353c24ffb085', '〜'], 147 | 148 | ['0e290ec6542b5d52c972775e3d7cfeaf', '-'], 149 | ['420f1d27972d7cc83929307fbbb6dd50', '-'], 150 | ['4aa0e459273a2fe3012d7b3d2e14e07e', '-'], 151 | 152 | ['030b487ae68da1f4da98046f4fed390f', '一'], 153 | ['21699fa18fd14735a312512dfea2bff4', '一'], 154 | ['559fc240f4efe5a1e64714ce09217a3e', '一'], 155 | ['b7352c3f33a77bc9d3fbf693efbb8095', '一'], 156 | ['fcdb30a244fb6aad5255ee2d32fdf7fc', '一'], 157 | 158 | ['15a0a0fb33aacd4ce730a9503c46df5f', '㊙'], 159 | ['9dad4982bd65fbf21525261a7efdf669', '㊙'], 160 | 161 | ['c3e68e6d08d5429e28ffd6592acf4519', 'ゔ'], 162 | 163 | ['f022cfe594d6f6930d7a5b994e1a0b71', '凜'], 164 | ['407057c7b7b1a91d058d572d9a9d3aa5', '凜'], 165 | ['fc85b0622183795f89111219dfbc6281', '凜'], 166 | ['9707099e5828d97eb12ff2e6ba438558', '凜'], 167 | ['987c829b62eb31f467165827766c410d', '凜'], 168 | ['58371bb195aaa7a468c5c508351ac383', '凜'], 169 | 170 | ['32324012ed7274a15002b66ed1e464f8', '蜻'], 171 | ['0ffb731db8d4a6b711f97bbb08ed8819', '蜻'], 172 | 173 | ['d90aae9a752e9b61662a9cafa837961f', '祓'], 174 | ['23d6c6f231ac5d51f4cdaaaa26701956', '祓'], 175 | ['81cbedabd8f88d4494255b0631820dfd', '祓'], 176 | 177 | ['4185f93a5571e49433ca9c13ae588f96', '魎'], 178 | ['9ab74d6e8bda8723614017a7fce587fe', '魎'], 179 | 180 | ['e96a39a050b694e5f8aadb111420b698', '𠮷'], 181 | ['08c5eb5fac4f1d362b946689eb2e4edf', '𠮷'], 182 | ['2cef7e443c22f5835658e67749ae52d1', '𠮷'], 183 | ['4ab0dd1578c8c5fa25f45938ff0f8575', '𠮷'], 184 | ['4c392bb90a1f62796f8fba2c19b4a7de', '𠮷'], 185 | 186 | ['43856fd7c04a779e571fe24c47f02a6c', '髙'], 187 | ['265efc2a174c73ea229f9ffefa703f32', '髙'], 188 | ['5a7af09cce6b3005355e1c6c82df8858', '髙'], 189 | ['808e9b858294184933f8bf45d6291572', '髙'], 190 | ['27f0c69a76bf571d6dc25db389d20779', '髙'], 191 | ['46fb250f60436fd5f33808343893ca12', '髙'], 192 | ['8a8c4c67a6094d4dc6039e5fe931159c', '髙'], 193 | ['9257f3792fcfcd21b85524d5f86f624e', '髙'], 194 | ['d502a276d6f311449597ee9e576d9217', '髙'], 195 | ['eaa49075e50fbe1fa4b7f593dfd95620', '髙'], 196 | 197 | ['c01d2bafce469da1abbb612fdb16c1e3', '元'], 198 | ['e1ce03321fdb4eaca026a49a43e521a5', '元'], 199 | ['0cfa6c95283a90eff3733db1ac80f58a', '元'], 200 | ['52c1ad5b834821dc6b85ec27bdea1f76', '元'], 201 | ['ad088cffd260c1fccb655cae17b14803', '元'], 202 | 203 | ['a9ee52eaa5b4cc32d1891d540bfe93cc', '塚'], 204 | ['a00182f1de36aaee28cac80a3c89d067', '塚'], 205 | ['b03d44ca831a0c995116056ce23f82c5', '塚'], 206 | 207 | ['2d6b7d3b5ca6c02d94c5b48661045b7a', '﨑'], 208 | ['f5c6e02e235abd23a87f48ed6a64cdcc', '﨑'], 209 | ['e9a3b055bda7b9ae70bde4003a4c5885', '﨑'], 210 | ['cb17df533b4ebd698a038defeddecf8a', '﨑'], 211 | ['0ea39c05c35f96d5b5a48e9815974132', '﨑'], 212 | ['3f642f3778827e651c8b82a4e9f06fd3', '﨑'], 213 | ['447d8358f482a4e1d9495902ebe269b1', '﨑'], 214 | ['ca59a20f1e0ee55b74db34697f961385', '﨑'], 215 | ['d2eae5651260b39c4239bcf00c8a76c5', '﨑'], 216 | ['f55eb365a9ded45d1e620f83d9f9de26', '﨑'], 217 | 218 | ['2b385c2642704e44347f2f4db147c8fa', '葛'], 219 | ['5c3a8c3a891386a771ff8f00a239b4ba', '葛'], 220 | 221 | ['e8caa78518e2d690af54e2206c9538f8', '彅'], 222 | ['2e8659ae5e220240c5f8a97147d09df6', '彅'], 223 | ['7592e633260537c1dfa7e5af1000752a', '彅'], 224 | ['a57d3f7684c28d2a901fe6020145de32', '彅'], 225 | ['da3ab2d5da4d69c7d312c7d819e45856', '彅'], 226 | 227 | ['918e84ed41c2157aa5f5bbf9aa60514c', '塡'], 228 | ['b1e889986beb3a6518d8c2ea53547b7c', '塡'], 229 | ['d449ab392afa98c27eb817c40e2eb7ce', '塡'], 230 | 231 | ['e7158075f2976c353e4cf9247aae3abc', '遁'], 232 | ['f1a6fbb17f041cc15148163da34f541f', '遁'], 233 | 234 | ['a1779a3aaf215916fd0d8fbbb5bf5925', '蟬'], 235 | ['a3c09b57be535c0f5618d72f95884c50', '蟬'], 236 | 237 | ['4b9401a9f9a58c7d0f9c86120aa2dd23', '鏢'], 238 | ['fe00b640a48dd341573cafa94afeafa2', '鏢'], 239 | 240 | ['f4e1d8b42e3c49ea7c896049186d74bd', '蟜'], 241 | ['640130a634bd2a0f4347f933a8c5d6d6', '蟜'], 242 | 243 | ['62985aeebaec69314f03ff9d3080ada2', '鷗'], 244 | ['1bd027207977c585c5889a1e24cae94e', '鷗'], 245 | ['5c8022286d3bc941c12e9bbc475255dd', '鷗'], 246 | 247 | ['e4a837fe20dfa091e03afe4857e2482e', '剝'], 248 | ['bf2cccb40b985fe3af04281944beac1a', '剝'], 249 | ['d5451a035c4e516e5ccb9372cd533d81', '剝'], 250 | ['e13ae32f28d840df74a88432df9b122e', '剝'], 251 | 252 | ['41637d181cd99088e2120a4ec6fc18aa', '嬴'], 253 | ['de63abb1aaa44e6ab8a11470103377d5', '嬴'], 254 | ['3d32b12254e01c701c195412cb8ef37c', '嬴'], 255 | ['f67bc6318ccf43e7902df9a6f9622932', '嬴'], 256 | ['d4ce6847d78fc2f8241088b5c0be795c', '嬴'], 257 | 258 | ['c472e6ade04610e67904aca1b1fa1468', '麃'], 259 | ['db3d060943fbf888eb2fa7fd87340cba', '麃'], 260 | 261 | ['509cff0edcba46d5db30b2f2f45c49c9', '瘣'], 262 | ['4862270872e35184aab420c4d38169ad', '瘣'], 263 | 264 | ['def4d364d00d0f78577987eaebd42aef', '齕'], 265 | ['3cc113a87b49ce231a7b2ffbca4c1e18', '齕'], 266 | 267 | ['03dddff25be65f7c284ef8addb8a0a8b', '驁'], 268 | ['52aa815a5a57aff03085d31acd5afbc4', '驁'], 269 | 270 | ['4f0431c4c63a6a362646758e62521df8', '煖'], 271 | ['dbf1ab17c746c48d474b3730064ba6f2', '煖'], 272 | ['790c6b4da6a88f7f4fdb6fdab77fe045', '煖'], 273 | ['4d7ae77f2bbf9c8af03d49d466f74058', '煖'], 274 | 275 | ['0335ba124be8a9e0c501f4051ac5fcf5', '龐'], 276 | ['01d7892b430fd4362c8917ad921199b2', '龐'], 277 | ['e866fd7e605c8b7c8bf718c45a5438cf', '龐'], 278 | 279 | ['5df7d88e1e15018b3bce73e765ef72d6', '槌'], 280 | ['48478e1f69ea50c6f7709d47f15b4007', '槌'], 281 | 282 | ['4d7d276f23c92f94056b292e295ebd78', '神'], 283 | ['87d2b97034cf680cd86bc7fe7c500d93', '神'], 284 | 285 | ['f6300abbfcd6bd0db3abd41041499aaa', '邂'], 286 | ['26c476496eb73e15285527ab7c635f0b', '邂'], 287 | 288 | ['be33b9008a58bab485e17de9b2ab2626', '逅'], 289 | ['3a9b8b576fe8efca2dedc957732afa37', '逅'], 290 | ['b798637262a0c1a29c8de602d4b688c6', '逅'], 291 | 292 | ['a3785fd94f13646623554b180d08ac77', '德'], 293 | ['1f81885b0996be70410e5aa3e4aab3c6', '德'], 294 | ['88425dfcbd96fcb6d77ebb76f834d986', '德'], 295 | ['9f993f913cd0614a3a965d74e0f4c8d1', '德'], 296 | ['a8bb5f2f83d975edfc951a1e461befdc', '德'], 297 | ['cd2eadbb87d0aadf1d1cd71fed0ab02f', '德'], 298 | ['d22feeb00ace0a632e1a780682f937e8', '德'], 299 | 300 | ['c9f2fda15b722253c625aebe73f4b1d9', '辻'], 301 | ['04556b37bff1ccc2f3b395232e104934', '辻'], 302 | ['117bacaeb67e3508d23a650b98f3c143', '辻'], 303 | ['211d70374c1787c4bc62df15794a4692', '辻'], 304 | ['7ba50856c59d1de19cc9c88caaced915', '辻'], 305 | 306 | ['0d627ebf7693b13645336a88813fb7e3', '祀'], 307 | ['2c3c032660b20a485575c2d8c7d47956', '祀'], 308 | 309 | ['1aaec04e53f2978bdf0a127c01b34e9a', '遼'], 310 | ['e2c3bf09b755b0d59a8a25cba6dda273', '遼'], 311 | ['fb13879ba2f93a8b0a28b2cd5358d1ee', '遼'], 312 | 313 | ['20eff1fff8d986496b949efa604ec402', '誾'], 314 | ['8742940fcbdbd65aeff1566c1889ece7', '誾'], 315 | 316 | ['23e6ef0ecc7bbe8e9465b0b40e901c0d', '厓'], 317 | ['5a69785acb47d746fd1ae98bd511db81', '厓'], 318 | 319 | ['392b8afa18046fc06398b32a42641889', '你'], 320 | ['7ff2c821d31ef0ca7e9c430f3e659d46', '你'], 321 | 322 | ['44d8b7aacbfc1fc4c32d6526ab8012ee', '祠'], 323 | ['c9486b883ab870fc02e7a1f189454f49', '祠'], 324 | 325 | ['62e7447a02f797cf287a7a758d66563b', '擲'], 326 | ['7d767d2518431dd61e631941dea6bb5e', '擲'], 327 | 328 | ['914fa35485d5016adc8b799b0cb5e978', '口'], 329 | ['d50802fc331261feed1a140f3b70c4b3', '口'], 330 | ['eeff4833bdfc34b1cbfe6a9d98f38cb5', '口'], 331 | 332 | ['a6d6aaeaf5505676111390a52fa6be51', '暲'], 333 | ['bc534a1accc68d8876e9d47ad8d4b489', '暲'], 334 | 335 | ['b5e8cb114ccad281bcb4d86768d509df', '銈'], 336 | ['d2c0ab0242ae4ad8a08bffa71613a1a7', '銈'], 337 | 338 | ['f09031463933b2892be7ebbc501269d0', '蕙'], 339 | ['f1378529fe66a7f655031d7f5b8c4eb5', '蕙'], 340 | 341 | ['6b696a5ae7634c454aaa7dd833fdfaf9', '眷'], 342 | ['5012d099f110e5e7c0df78528686ae07', '榮'], 343 | ['bf27e95238dd789b05e38d56dc41cbf7', '嵓'], 344 | ['1d2eafa6be36dc6152cb1917cd2ac486', '氏'], 345 | ['8b1bd5636f709dfd6a95da9f463729c3', '柀'], 346 | ['0b49a77f459cf3783c5bac37a80518c5', '份'], 347 | ['8d1ba0e24b619cb4d377ddb7adb3e6fa', '喼'], 348 | ['dab4c329f3c540192f758a2e0008d275', '鎚'], 349 | ['5b6c90ad3012bfbbc2450b5ab930484d', '翟'], 350 | ['7eb78d5654f8335d0b1cf4cf78872097', '鄭'], 351 | ['c3852ea003683f2866abd56140fb5d84', '錆'], 352 | ['a78b8a79d8a32c925776c82955d168cc', '郭'], 353 | ['b6e773b060fdd575bc965369d509f4e0', '煉'], 354 | ['6d981a3b846347e2b3c9ca4d13794834', '桒'], 355 | ['d0ed8ffbc229f84dd796cdd6de36d2e4', '䃯'], 356 | ['d9aff359058ab474d552ce52e5a71ec8', '卿'], 357 | ['5417381484172c1607d7ca60765b62d2', '蝕'], 358 | ['b309cd2c649ce3ef6ea0ad2f5fc655cc', '這'], 359 | ['2a349ac3d6b94a8a64d904083fdd5c02', '辿'], 360 | ['0632283bfd909ef205b1f950e2b00f16', '靑'], 361 | ['d91c5a40619510b21610f523f9434269', '淸'], 362 | ['8dc47c6e65beb788da7ed9efd59f0934', '買'], 363 | ['e28d4c57d97fbe4a0d67aec2cc92e7c8', '捥'], 364 | ['9374173a2e4b7f1dcac75eccd5ee7e7f', '榊'], 365 | ]); 366 | -------------------------------------------------------------------------------- /src/svg-renderer.ts: -------------------------------------------------------------------------------- 1 | import SVGProvider from './svg-provider' 2 | import HighResTextTrack from './utils/high-res-texttrack' 3 | import DummyCue from './utils/dummy-cue' 4 | import { readID3Size, binaryISO85591ToString, binaryUTF8ToString, base64ToUint8Array} from './utils/binary' 5 | 6 | const DETECT_TIMEUPDATE_SEEKING_RANGE = 1; 7 | 8 | export interface RendererOption { 9 | data_identifier?: number, 10 | data_group_id?: number, 11 | forceStrokeColor?: boolean | string, 12 | forceBackgroundColor?: string, 13 | normalFont?: string, 14 | gaijiFont?: string, 15 | drcsReplacement?: boolean, 16 | drcsReplaceMapping?: Record, 17 | renderedTextCallback?: (renderedText: string) => unknown, 18 | PRACallback?: (index: number) => unknown, 19 | keepAspectRatio?: boolean, 20 | enableAutoInBandMetadataTextTrackDetection?: boolean, 21 | useHighResTextTrack?: boolean, 22 | useHighResTimeupdate?: boolean, 23 | usePUA?: boolean, 24 | } 25 | 26 | export default class SVGRenderer { 27 | 28 | private media: HTMLVideoElement | null = null 29 | private id3Track: TextTrack | null = null 30 | private b24Track: TextTrack | null = null 31 | private subtitleElement: HTMLElement | null = null 32 | private svg: SVGElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 33 | private prevCurrentTime: number | null = null 34 | private highResTimeupdatePollingId: number | null = null 35 | private isShowing: boolean = true 36 | private isOnSeeking: boolean = false 37 | private onB24CueChangeDrawed: boolean = false 38 | 39 | private readonly onID3AddtrackHandler: ((event: TrackEvent) => void) = this.onID3Addtrack.bind(this); 40 | private readonly onID3CueChangeHandler: (() => void) = this.onID3CueChange.bind(this); 41 | private readonly onB24CueChangeHandler: (() => void) = this.onB24CueChange.bind(this); 42 | 43 | private readonly onHighResTimeupdateHandler: (() => void) =this.onHighResTimeupdate.bind(this); 44 | private readonly onTimeupdateHandler: (() => void) = this.onTimeupdate.bind(this); 45 | private readonly onCanplayHandler: (() => void) = this.onCanplay.bind(this); 46 | private readonly onPlayHandler: (() => void) = this.onPlay.bind(this); 47 | private readonly onPauseHandler: (() => void) = this.onPause.bind(this); 48 | private readonly onPauseAnimationHandler: (() => void) = this.onPauseAnimation.bind(this); 49 | private readonly onResumeAnimationHandler: (() => void) = this.onResumeAnimation.bind(this); 50 | private readonly onSeekingHandler: (() => void) = this.onSeeking.bind(this); 51 | private readonly onSeekedHandler: (() => void) = this.onSeeked.bind(this); 52 | 53 | private rendererOption: RendererOption | undefined 54 | private data_identifier: number 55 | private data_group_id: number 56 | 57 | public constructor(option?: RendererOption) { 58 | this.data_identifier = option?.data_identifier ?? 0x80 // default: caption 59 | this.data_group_id = option?.data_group_id ?? 0x01 // default: 1st language 60 | this.rendererOption = { 61 | ... option, 62 | data_identifier: this.data_identifier, 63 | data_group_id: this.data_group_id, 64 | keepAspectRatio: option?.keepAspectRatio ?? true, // default: true 65 | enableAutoInBandMetadataTextTrackDetection: option?.enableAutoInBandMetadataTextTrackDetection ?? true, // default: true 66 | } 67 | } 68 | 69 | public attachMedia(media: HTMLVideoElement, subtitleElement?: HTMLElement): void { 70 | this.detachMedia() 71 | this.media = media 72 | this.subtitleElement = subtitleElement ?? media.parentElement 73 | 74 | this.media.addEventListener('canplay', this.onCanplayHandler) 75 | this.media.addEventListener('play', this.onResumeAnimationHandler) 76 | this.media.addEventListener('pause', this.onPauseAnimationHandler) 77 | 78 | if (this.rendererOption?.useHighResTimeupdate) { 79 | this.media.addEventListener('play', this.onPlayHandler) 80 | this.media.addEventListener('pause', this.onPauseHandler) 81 | } else { 82 | this.media.addEventListener('timeupdate', this.onTimeupdateHandler) 83 | } 84 | this.prevCurrentTime = null; 85 | 86 | this.setupTrack() 87 | this.setupSVG() 88 | } 89 | 90 | public detachMedia(): void { 91 | this.cleanupSVG() 92 | this.cleanupTrack() 93 | 94 | this.media?.removeEventListener('canplay', this.onCanplayHandler) 95 | this.media?.removeEventListener('play', this.onPlayHandler) 96 | this.media?.removeEventListener('pause', this.onPauseHandler) 97 | this.media?.removeEventListener('play', this.onResumeAnimationHandler) 98 | this.media?.removeEventListener('pause', this.onPauseAnimationHandler) 99 | this.onPause(); 100 | this.media?.removeEventListener('timeupdate', this.onTimeupdateHandler) 101 | this.prevCurrentTime = null; 102 | 103 | this.media = this.subtitleElement = null 104 | } 105 | 106 | public dispose(): void { 107 | this.detachMedia() 108 | } 109 | 110 | public getSVG(): SVGElement { 111 | return this.svg 112 | } 113 | 114 | public show(): void { 115 | this.isShowing = true 116 | 117 | this.svg.style.visibility = 'visible'; 118 | } 119 | 120 | public hide(): void { 121 | this.isShowing = false 122 | 123 | this.svg.style.visibility = 'hidden'; 124 | } 125 | 126 | public isPresent() { 127 | return this.onB24CueChangeDrawed 128 | } 129 | 130 | public pushRawData(pts: number, data: Uint8Array): boolean { 131 | const provider: SVGProvider = new SVGProvider(data, pts); 132 | const estimate = provider.render({ 133 | ... this.rendererOption, 134 | }) 135 | if (estimate == null) { return false; } 136 | 137 | const end_time = Number.isFinite(estimate.endTime) ? estimate.endTime : Number.MAX_SAFE_INTEGER; 138 | return this.addB24Cue(pts, end_time, data) 139 | } 140 | 141 | public pushBase64Data(pts: number, base64: string): boolean { 142 | const data = base64ToUint8Array(base64); 143 | return this.pushRawData(pts, data); 144 | } 145 | 146 | // for b24.js compatibility 147 | public pushData(pid: number, uint8array: Uint8Array, pts: number): boolean { 148 | return this.pushRawData(pts, uint8array); 149 | } 150 | 151 | public pushID3v2PRIVData(pts: number, owner: string, data: Uint8Array): boolean { 152 | if (owner !== 'aribb24.js') { return false; } 153 | return this.pushRawData(pts, data); 154 | } 155 | 156 | public pushID3v2TXXXData(pts: number, description: string, text: string): boolean { 157 | if (description !== 'aribb24.js') { return false; } 158 | return this.pushBase64Data(pts, text); 159 | } 160 | 161 | public pushID3v2Data(pts: number, data: Uint8Array): boolean { 162 | let result = false; 163 | 164 | for (let begin = 0; begin < data.length;) { 165 | const id3_start = begin; 166 | 167 | if (begin + 3 > data.length) { break; } 168 | if (!(data[begin + 0] === 0x49 && data[begin + 1] === 0x44 && data[begin + 2] === 0x33)) { break; } 169 | begin += 3 + 2 /* version */ + 1 /* flag */; 170 | 171 | if (begin + 4 > data.length) { break; } 172 | const id3_size = readID3Size(data, begin + 0, begin + 4); 173 | begin += 4; 174 | 175 | const id3_end = id3_start + 3 + 2 + 1 + 4 + id3_size; 176 | if (id3_end > data.length) { break; } 177 | 178 | for (let frame = begin; frame < id3_end;) { 179 | const frame_begin = frame; 180 | 181 | if (frame + 4 > data.length) { break; } 182 | const frame_name = binaryISO85591ToString(data, frame + 0, frame + 4); 183 | frame += 4; 184 | 185 | if (frame + 4 > data.length) { break; } 186 | const frame_size = readID3Size(data, frame + 0, frame + 4); 187 | frame += 4 + 2 /* flag */; 188 | 189 | const frame_end = frame_begin + 4 + 4 + 2 + frame_size; 190 | if (frame_end > data.length) { break; } 191 | 192 | if (frame_name === 'PRIV') { 193 | const PRIV_begin = frame; 194 | const PRIV_end = frame_end; 195 | 196 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 197 | 198 | const owner = binaryISO85591ToString(data, PRIV_begin, frame); 199 | const pes = new Uint8Array(Array.prototype.slice.call(data, frame + 1, PRIV_end)); 200 | 201 | if (this.pushID3v2PRIVData(pts, owner, pes)) { result = true; } 202 | } else if (frame_name === 'TXXX') { 203 | const encoding = data[frame + 0]; 204 | const description_begin = frame + 1; 205 | 206 | if (encoding === 0x03) { // UTF-8 207 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 208 | const description_end = frame; 209 | frame += 1; 210 | 211 | const data_begin = frame; 212 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 213 | const data_end = frame; 214 | 215 | const description = binaryUTF8ToString(data, description_begin, description_end); 216 | const text = binaryUTF8ToString(data, data_begin, data_end); 217 | 218 | if (this.pushID3v2TXXXData(pts, description, text)) { result = true; } 219 | } else if(encoding === 0x00) { // Laten-1 220 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 221 | const description_end = frame; 222 | frame += 1; 223 | 224 | const data_begin = frame; 225 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 226 | const data_end = frame; 227 | 228 | const description = binaryISO85591ToString(data, description_begin, description_end); 229 | const text = binaryISO85591ToString(data, data_begin, data_end); 230 | 231 | if (this.pushID3v2TXXXData(pts, description, text)) { result = true; } 232 | } 233 | } 234 | 235 | frame = frame_end; 236 | } 237 | 238 | begin = id3_start + 3 + 2 + 1 + 4 + id3_size; 239 | if (begin + 3 > data.length) { continue; } 240 | // id3 footer 241 | if (!(data[begin + 0] === 0x33 && data[begin + 1] === 0x44 && data[begin + 2] === 0x49)) { continue; } 242 | begin += 3 + 2 /* version */ + 1 /* flags */ + 4 /* size */; 243 | } 244 | 245 | return result; 246 | } 247 | 248 | public setInBandMetadataTextTrack(track: TextTrack): void { 249 | this.id3Track?.removeEventListener('cuechange', this.onID3CueChangeHandler) 250 | 251 | this.id3Track = track 252 | this.id3Track.mode = 'hidden' 253 | 254 | this.id3Track.addEventListener('cuechange', this.onID3CueChangeHandler) 255 | } 256 | 257 | private pushID3v2Cue(cue: TextTrackCue): boolean { 258 | if (!this.id3Track) { return false; } 259 | 260 | const start_time = cue.startTime; 261 | const id3_cue = cue as any; 262 | 263 | if (this.id3Track.inBandMetadataTrackDispatchType === '15260DFFFF49443320FF49443320000F'){ // Legacy Edge 264 | return this.pushID3v2Data(start_time, new Uint8Array(id3_cue.data)); 265 | } else if (this.id3Track.inBandMetadataTrackDispatchType === 'com.apple.streaming') { // Safari 266 | if (id3_cue.value.key === 'PRIV') { 267 | return this.pushID3v2PRIVData(start_time, id3_cue.value.info, new Uint8Array(id3_cue.value.data)); 268 | } else if (id3_cue.value.key === 'TXXX') { 269 | return this.pushID3v2TXXXData(start_time, id3_cue.value.info, id3_cue.value.data); 270 | } 271 | } else if (this.id3Track.label === 'id3') { // hls.js 272 | if (id3_cue.value.key === 'PRIV') { 273 | return this.pushID3v2PRIVData(start_time, id3_cue.value.info, new Uint8Array(id3_cue.value.data)); 274 | } else if (id3_cue.value.key === 'TXXX') { 275 | return this.pushID3v2TXXXData(start_time, id3_cue.value.info, id3_cue.value.data); 276 | } 277 | } else if (this.id3Track.label === 'Timed Metadata') { // video.js 278 | if (id3_cue.frame.key === 'PRIV') { 279 | return this.pushID3v2PRIVData(start_time, id3_cue.frame.owner, new Uint8Array(id3_cue.frame.data)); 280 | } else if (id3_cue.frame.key === 'TXXX') { 281 | return this.pushID3v2TXXXData(start_time, id3_cue.frame.description, id3_cue.frame.data); 282 | } 283 | } 284 | 285 | return false; 286 | } 287 | 288 | private onID3CueChange() { 289 | if (!this.id3Track) { return } 290 | 291 | if (this.isOnSeeking) { return } 292 | 293 | /* 294 | const activeCues = this.id3Track.activeCues ?? [] 295 | for (let i = activeCues.length - 1; i >= 0; i--) { 296 | if (this.pushID3v2Cue(activeCues[i])) { break; } 297 | } 298 | */ 299 | this.onTimeupdate(); 300 | } 301 | 302 | private addB24Cue (start_time: number, end_time: number, data: Uint8Array): boolean { 303 | if (!this.b24Track) { return false; } 304 | if (!SVGProvider.detect(data, this.rendererOption)) { return false; } 305 | 306 | const CueClass = window.VTTCue ?? window.TextTrackCue 307 | 308 | const b24_cue = new CueClass(start_time, end_time, ''); 309 | (b24_cue as any).data = data; 310 | 311 | if (window.VTTCue) { 312 | this.b24Track.addCue(b24_cue) 313 | } else if (window.TextTrackCue) { 314 | const hasCue = Array.prototype.some.call(this.b24Track.cues ?? [], (target) => { 315 | return target.startTime === start_time 316 | }) 317 | if (hasCue) { return false; } 318 | 319 | if (this.b24Track.cues) { 320 | const removed_cues: TextTrackCue[] = []; 321 | for (let i = this.b24Track.cues.length - 1; i >= 0; i--) { 322 | if (this.b24Track.cues[i].startTime >= start_time) { 323 | removed_cues.push(this.b24Track.cues[i]) 324 | this.b24Track.removeCue(this.b24Track.cues[i]) 325 | } 326 | } 327 | this.b24Track.addCue(b24_cue) 328 | for (let i = removed_cues.length - 1; i >= 0; i--) { 329 | this.b24Track.addCue(removed_cues[i]) 330 | } 331 | } 332 | } 333 | 334 | return true; 335 | } 336 | 337 | private onB24CueChange() { 338 | if (!this.media || !this.b24Track) { 339 | this.onB24CueChangeDrawed = false 340 | return 341 | } 342 | 343 | while (this.svg.firstChild) { 344 | this.svg.removeChild(this.svg.firstChild); 345 | } 346 | 347 | if (this.b24Track.activeCues && this.b24Track.activeCues.length > 0) { 348 | const lastCue = this.b24Track.activeCues[this.b24Track.activeCues.length - 1] as any 349 | 350 | if ((lastCue.startTime <= this.media.currentTime && this.media.currentTime <= lastCue.endTime) && !this.isOnSeeking) { 351 | // なんか Win Firefox で Cue が endTime 過ぎても activeCues から消えない場合があった、バグ? 352 | 353 | const provider: SVGProvider = new SVGProvider(lastCue.data, lastCue.startTime); 354 | let rendered = false 355 | 356 | if (this.isShowing) { 357 | const result = provider.render({ 358 | ... this.rendererOption, 359 | svg: this.svg 360 | }) 361 | 362 | if (result?.renderedText != null) { 363 | this.rendererOption?.renderedTextCallback?.(result?.renderedText); 364 | } 365 | 366 | if (result?.PRA != null) { 367 | this.rendererOption?.PRACallback?.(result.PRA); 368 | } 369 | 370 | rendered = result?.rendered ?? false 371 | } 372 | 373 | this.onB24CueChangeDrawed = true 374 | } else { 375 | this.onB24CueChangeDrawed = false 376 | } 377 | 378 | for (let i = this.b24Track.activeCues.length - 2; i >= 0; i--) { 379 | const cue = this.b24Track.activeCues[i] 380 | cue.endTime = Math.min(cue.endTime, lastCue.startTime) 381 | if (cue.startTime === cue.endTime) { // .. if duplicate subtitle appeared 382 | this.b24Track.removeCue(cue); 383 | } 384 | } 385 | } else{ 386 | this.onB24CueChangeDrawed = false 387 | } 388 | } 389 | 390 | private onHighResTimeupdate() { 391 | this.onTimeupdate(); 392 | this.highResTimeupdatePollingId = window.requestAnimationFrame(this.onHighResTimeupdateHandler); 393 | } 394 | 395 | private onTimeupdate() { 396 | if (!this.media) { return; } 397 | if (this.prevCurrentTime == null) { 398 | this.prevCurrentTime = this.media.currentTime; 399 | return; 400 | } 401 | 402 | if (!this.id3Track || !this.id3Track.cues || this.id3Track.cues.length === 0) { 403 | this.prevCurrentTime = this.media.currentTime; 404 | return; 405 | } 406 | 407 | if (this.isOnSeeking) { 408 | this.prevCurrentTime = this.media.currentTime; 409 | return; 410 | } 411 | if (Math.abs(this.media.currentTime - this.prevCurrentTime) > DETECT_TIMEUPDATE_SEEKING_RANGE) { 412 | this.prevCurrentTime = this.media.currentTime; 413 | return; 414 | } 415 | 416 | const dummyCue = new DummyCue(Number.NEGATIVE_INFINITY, this.id3Track.cues[0].startTime); 417 | let prevIndex: number | null = null; 418 | let currIndex: number | null = null; 419 | 420 | const cues: TextTrackCue[] = [ dummyCue ]; // ... this.id3Track.cues 421 | for (let i = 0; i < this.id3Track.cues.length; i++) { 422 | cues.push(this.id3Track.cues[i]); 423 | } 424 | 425 | { 426 | let begin = 0, end = cues.length; 427 | while (begin + 1 < end) { 428 | const currentTime = this.prevCurrentTime; 429 | const middle = Math.floor((begin + end) / 2); 430 | const startTime = cues[middle].startTime; 431 | 432 | if (currentTime < startTime) { 433 | end = middle; 434 | } else { 435 | begin = middle; 436 | } 437 | } 438 | prevIndex = begin; 439 | } 440 | { 441 | let begin = 0, end = cues.length; 442 | while (begin + 1 < end) { 443 | const currentTime = this.media.currentTime; 444 | const middle = Math.floor((begin + end) / 2); 445 | const startTime = cues[middle].startTime; 446 | 447 | if (currentTime < startTime) { 448 | end = middle; 449 | } else { 450 | begin = middle; 451 | } 452 | } 453 | currIndex = begin; 454 | } 455 | 456 | if (prevIndex === null || currIndex === null || prevIndex === currIndex){ 457 | this.prevCurrentTime = this.media.currentTime; 458 | return; 459 | } 460 | 461 | if (prevIndex < currIndex) { 462 | for (let index = currIndex; index > prevIndex; index--) { 463 | const cue = cues[index]; 464 | if (cue === dummyCue) { continue; } 465 | 466 | if (this.pushID3v2Cue(cue)) { break; } 467 | } 468 | } else { 469 | for (let index = prevIndex; index < currIndex; index++) { 470 | const cue = cues[index]; 471 | if (cue === dummyCue) { continue; } 472 | 473 | if (this.pushID3v2Cue(cue)) { break; } 474 | } 475 | } 476 | 477 | this.prevCurrentTime = this.media.currentTime; 478 | } 479 | 480 | private onCanplay() { 481 | if (this.id3Track) { 482 | this.id3Track.mode = 'hidden'; 483 | } 484 | if (this.b24Track) { 485 | this.b24Track.mode = 'hidden'; 486 | } 487 | 488 | if (this.media != null && this.prevCurrentTime == null) { 489 | this.prevCurrentTime = this.media.currentTime - Number.MIN_VALUE; 490 | } 491 | } 492 | 493 | private onPlay() { 494 | if (this.highResTimeupdatePollingId == null) { 495 | this.onHighResTimeupdate(); 496 | } 497 | } 498 | 499 | private onPause() { 500 | if (this.highResTimeupdatePollingId != null) { 501 | window.cancelAnimationFrame(this.highResTimeupdatePollingId); 502 | this.highResTimeupdatePollingId = null; 503 | } 504 | } 505 | 506 | private onPauseAnimation() { 507 | (this.svg as unknown as SVGSVGElement).pauseAnimations(); 508 | } 509 | 510 | private onResumeAnimation() { 511 | (this.svg as unknown as SVGSVGElement).unpauseAnimations(); 512 | } 513 | 514 | private onSeeking() { 515 | this.isOnSeeking = true 516 | this.onB24CueChange() 517 | } 518 | 519 | private onSeeked() { 520 | this.isOnSeeking = false 521 | } 522 | 523 | private onID3Addtrack(event: TrackEvent): void { 524 | if (!this.media) { 525 | return; 526 | } 527 | 528 | const textTrack = event.track!; 529 | if (textTrack.kind !== 'metadata') { return; } 530 | 531 | if ( textTrack.inBandMetadataTrackDispatchType === '15260DFFFF49443320FF49443320000F' // Legacy Edge 532 | || textTrack.inBandMetadataTrackDispatchType === 'com.apple.streaming' // Safari 533 | || textTrack.label === 'id3' // hls.js 534 | ) { 535 | this.setInBandMetadataTextTrack(textTrack); 536 | } 537 | } 538 | 539 | private setupTrack(): void { 540 | if (!this.media) { 541 | return 542 | } 543 | 544 | if (this.rendererOption?.useHighResTextTrack) { 545 | this.b24Track = new HighResTextTrack(this.media); 546 | (this.b24Track as HighResTextTrack).startPolling(); 547 | } else { 548 | const aribb24js_label = `ARIB B24 Japanese SVG (data_identifier=0x${this.data_identifier.toString(16)}, data_group_id=${this.data_group_id})` 549 | for (let i = 0; i < this.media.textTracks.length; i++) { 550 | const track = this.media.textTracks[i] 551 | if (track.label === aribb24js_label) { 552 | this.b24Track = track 553 | break 554 | } 555 | } 556 | if (!this.b24Track) { 557 | this.b24Track = this.media.addTextTrack('metadata', aribb24js_label, 'ja') 558 | this.b24Track.mode = 'hidden' 559 | } 560 | } 561 | 562 | this.b24Track.addEventListener('cuechange', this.onB24CueChangeHandler) 563 | 564 | if (this.rendererOption?.enableAutoInBandMetadataTextTrackDetection) { 565 | for (let i = 0; i < this.media.textTracks.length; i++) { 566 | const track = this.media.textTracks[i]; 567 | 568 | if (track.kind !== 'metadata') { continue; } 569 | 570 | if ( track.inBandMetadataTrackDispatchType === '15260DFFFF49443320FF49443320000F' // Legacy Edge 571 | || track.inBandMetadataTrackDispatchType === 'com.apple.streaming' // Safari 572 | || track.label === 'id3' // hls.js 573 | ) { 574 | this.setInBandMetadataTextTrack(track); 575 | break; 576 | } 577 | } 578 | 579 | this.media.textTracks.addEventListener('addtrack', this.onID3AddtrackHandler) 580 | } 581 | 582 | this.media.addEventListener('seeking', this.onSeekingHandler) 583 | this.media.addEventListener('seeked', this.onSeekedHandler) 584 | } 585 | 586 | private setupSVG(): void { 587 | if (!this.media || !this.subtitleElement){ 588 | return 589 | } 590 | this.svg.style.position = 'absolute' 591 | this.svg.style.top = this.svg.style.left = '0' 592 | this.svg.style.pointerEvents = 'none' 593 | this.svg.style.width = '100%' 594 | this.svg.style.height = '100%' 595 | 596 | this.subtitleElement.appendChild(this.svg) 597 | } 598 | 599 | private cleanupTrack(): void { 600 | if (this.b24Track) { 601 | if (this.rendererOption?.useHighResTextTrack) { 602 | (this.b24Track as HighResTextTrack).stopPolling(); 603 | } else { 604 | if (this.b24Track.cues) { 605 | for (let i = this.b24Track.cues.length - 1; i >= 0; i--) { 606 | this.b24Track.removeCue(this.b24Track.cues[i]) 607 | } 608 | } 609 | } 610 | } 611 | 612 | this.b24Track?.removeEventListener('cuechange', this.onB24CueChangeHandler) 613 | 614 | this.id3Track?.removeEventListener('cuechange', this.onID3CueChangeHandler) 615 | 616 | this.media?.removeEventListener('seeking', this.onSeekingHandler) 617 | this.media?.removeEventListener('seeked', this.onSeekedHandler) 618 | this.media?.textTracks.removeEventListener('addtrack', this.onID3AddtrackHandler) 619 | 620 | this.b24Track = this.id3Track = null 621 | } 622 | 623 | private cleanupSVG(): void { 624 | while (this.svg.firstChild) { 625 | this.svg.removeChild(this.svg.firstChild); 626 | } 627 | } 628 | } 629 | -------------------------------------------------------------------------------- /src/html-renderer-experimental.ts: -------------------------------------------------------------------------------- 1 | import HTMLProvider from './html-provider-experimental' 2 | import HighResTextTrack from './utils/high-res-texttrack' 3 | import DummyCue from './utils/dummy-cue' 4 | import { readID3Size, binaryISO85591ToString, binaryUTF8ToString, base64ToUint8Array} from './utils/binary' 5 | 6 | const DETECT_TIMEUPDATE_SEEKING_RANGE = 1; 7 | 8 | export interface RendererOption { 9 | data_identifier?: number, 10 | data_group_id?: number, 11 | forceStrokeColor?: boolean | string, 12 | forceBackgroundColor?: string, 13 | normalFont?: string, 14 | gaijiFont?: string, 15 | drcsReplacement?: boolean, 16 | drcsReplaceMapping?: Record, 17 | renderedTextCallback?: (renderedText: string) => unknown, 18 | PRACallback?: (index: number) => unknown, 19 | keepAspectRatio?: boolean, 20 | enableAutoInBandMetadataTextTrackDetection?: boolean, 21 | useStroke?: boolean, 22 | useHighResTextTrack?: boolean, 23 | useHighResTimeupdate?: boolean, 24 | usePUA?: boolean, 25 | } 26 | 27 | export default class HTMLID3Renderer { 28 | 29 | private media: HTMLVideoElement | null = null 30 | private id3Track: TextTrack | null = null 31 | private b24Track: TextTrack | null = null 32 | private subtitleElement: HTMLElement | null = null 33 | private table: HTMLTableElement | null = null; 34 | private wrapper: HTMLDivElement | null = null; 35 | private resizeObserver: ResizeObserver | null = null 36 | private mutationObserver: MutationObserver | null = null 37 | private prevCurrentTime: number | null = null 38 | private highResTimeupdatePollingId: number | null = null 39 | private isShowing: boolean = true 40 | private isOnSeeking: boolean = false 41 | private onB24CueChangeDrawed: boolean = false 42 | 43 | private readonly onID3AddtrackHandler: ((event: TrackEvent) => void) = this.onID3Addtrack.bind(this); 44 | private readonly onID3CueChangeHandler: (() => void) = this.onID3CueChange.bind(this); 45 | private readonly onB24CueChangeHandler: (() => void) = this.onB24CueChange.bind(this); 46 | 47 | private readonly onHighResTimeupdateHandler: (() => void) =this.onHighResTimeupdate.bind(this); 48 | private readonly onTimeupdateHandler: (() => void) = this.onTimeupdate.bind(this); 49 | private readonly onCanplayHandler: (() => void) = this.onCanplay.bind(this); 50 | private readonly onPlayHandler: (() => void) = this.onPlay.bind(this); 51 | private readonly onPauseHandler: (() => void) = this.onPause.bind(this); 52 | private readonly onSeekingHandler: (() => void) = this.onSeeking.bind(this); 53 | private readonly onSeekedHandler: (() => void) = this.onSeeked.bind(this); 54 | private readonly onResizeHandler: (() => void) = this.onResize.bind(this); 55 | 56 | private rendererOption: RendererOption | undefined 57 | private data_identifier: number 58 | private data_group_id: number 59 | 60 | public constructor(option?: RendererOption) { 61 | this.data_identifier = option?.data_identifier ?? 0x80 // default: caption 62 | this.data_group_id = option?.data_group_id ?? 0x01 // default: 1st language 63 | this.rendererOption = { 64 | ... option, 65 | data_identifier: this.data_identifier, 66 | data_group_id: this.data_group_id, 67 | keepAspectRatio: option?.keepAspectRatio ?? true, // default: true 68 | enableAutoInBandMetadataTextTrackDetection: option?.enableAutoInBandMetadataTextTrackDetection ?? true, // default: true 69 | useStroke: option?.useStroke ?? true, // default: true 70 | } 71 | } 72 | 73 | public attachMedia(media: HTMLVideoElement, subtitleElement?: HTMLElement): void { 74 | this.detachMedia() 75 | this.media = media 76 | this.subtitleElement = subtitleElement ?? media.parentElement 77 | 78 | this.media.addEventListener('canplay', this.onCanplayHandler) 79 | if (this.rendererOption?.useHighResTimeupdate) { 80 | this.media.addEventListener('play', this.onPlayHandler) 81 | this.media.addEventListener('pause', this.onPauseHandler) 82 | } else { 83 | this.media.addEventListener('timeupdate', this.onTimeupdateHandler) 84 | } 85 | this.prevCurrentTime = null; 86 | 87 | this.setupTrack() 88 | this.setupTable() 89 | } 90 | 91 | public detachMedia(): void { 92 | this.cleanupTable() 93 | this.cleanupTrack() 94 | 95 | this.media?.removeEventListener('canplay', this.onCanplayHandler) 96 | this.media?.removeEventListener('play', this.onPlayHandler) 97 | this.media?.removeEventListener('pause', this.onPauseHandler) 98 | this.onPause(); 99 | this.media?.removeEventListener('timeupdate', this.onTimeupdateHandler) 100 | this.prevCurrentTime = null; 101 | 102 | this.media = this.subtitleElement = null 103 | } 104 | 105 | public dispose(): void { 106 | this.detachMedia() 107 | } 108 | 109 | public refresh(): void { 110 | this.onResize() 111 | } 112 | 113 | public show(): void { 114 | this.isShowing = true 115 | 116 | if (this.table) { 117 | this.table.style.visibility = 'visible'; 118 | } 119 | } 120 | 121 | public hide(): void { 122 | this.isShowing = false 123 | 124 | if (this.table) { 125 | this.table.style.visibility = 'hidden'; 126 | } 127 | } 128 | 129 | public isPresent() { 130 | return this.onB24CueChangeDrawed 131 | } 132 | 133 | public pushRawData(pts: number, data: Uint8Array): boolean { 134 | const provider: HTMLProvider = new HTMLProvider(data, pts); 135 | const estimate = provider.render(this.rendererOption) 136 | if (estimate == null) { return false; } 137 | 138 | const end_time = Number.isFinite(estimate.endTime) ? estimate.endTime : Number.MAX_SAFE_INTEGER; 139 | return this.addB24Cue(pts, end_time, data) 140 | } 141 | 142 | public pushBase64Data(pts: number, base64: string): boolean { 143 | const data = base64ToUint8Array(base64); 144 | return this.pushRawData(pts, data); 145 | } 146 | 147 | // for b24.js compatibility 148 | public pushData(pid: number, uint8array: Uint8Array, pts: number): boolean { 149 | return this.pushRawData(pts, uint8array); 150 | } 151 | 152 | public pushID3v2PRIVData(pts: number, owner: string, data: Uint8Array): boolean { 153 | if (owner !== 'aribb24.js') { return false; } 154 | return this.pushRawData(pts, data); 155 | } 156 | 157 | public pushID3v2TXXXData(pts: number, description: string, text: string): boolean { 158 | if (description !== 'aribb24.js') { return false; } 159 | return this.pushBase64Data(pts, text); 160 | } 161 | 162 | public pushID3v2Data(pts: number, data: Uint8Array): boolean { 163 | let result = false; 164 | 165 | for (let begin = 0; begin < data.length;) { 166 | const id3_start = begin; 167 | 168 | if (begin + 3 > data.length) { break; } 169 | if (!(data[begin + 0] === 0x49 && data[begin + 1] === 0x44 && data[begin + 2] === 0x33)) { break; } 170 | begin += 3 + 2 /* version */ + 1 /* flag */; 171 | 172 | if (begin + 4 > data.length) { break; } 173 | const id3_size = readID3Size(data, begin + 0, begin + 4); 174 | begin += 4; 175 | 176 | const id3_end = id3_start + 3 + 2 + 1 + 4 + id3_size; 177 | if (id3_end > data.length) { break; } 178 | 179 | for (let frame = begin; frame < id3_end;) { 180 | const frame_begin = frame; 181 | 182 | if (frame + 4 > data.length) { break; } 183 | const frame_name = binaryISO85591ToString(data, frame + 0, frame + 4); 184 | frame += 4; 185 | 186 | if (frame + 4 > data.length) { break; } 187 | const frame_size = readID3Size(data, frame + 0, frame + 4); 188 | frame += 4 + 2 /* flag */; 189 | 190 | const frame_end = frame_begin + 4 + 4 + 2 + frame_size; 191 | if (frame_end > data.length) { break; } 192 | 193 | if (frame_name === 'PRIV') { 194 | const PRIV_begin = frame; 195 | const PRIV_end = frame_end; 196 | 197 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 198 | 199 | const owner = binaryISO85591ToString(data, PRIV_begin, frame); 200 | const pes = new Uint8Array(Array.prototype.slice.call(data, frame + 1, PRIV_end)); 201 | 202 | if (this.pushID3v2PRIVData(pts, owner, pes)) { result = true; } 203 | } else if (frame_name === 'TXXX') { 204 | const encoding = data[frame + 0]; 205 | const description_begin = frame + 1; 206 | 207 | if (encoding === 0x03) { // UTF-8 208 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 209 | const description_end = frame; 210 | frame += 1; 211 | 212 | const data_begin = frame; 213 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 214 | const data_end = frame; 215 | 216 | const description = binaryUTF8ToString(data, description_begin, description_end); 217 | const text = binaryUTF8ToString(data, data_begin, data_end); 218 | 219 | if (this.pushID3v2TXXXData(pts, description, text)) { result = true; } 220 | } else if(encoding === 0x00) { // Laten-1 221 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 222 | const description_end = frame; 223 | frame += 1; 224 | 225 | const data_begin = frame; 226 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 227 | const data_end = frame; 228 | 229 | const description = binaryISO85591ToString(data, description_begin, description_end); 230 | const text = binaryISO85591ToString(data, data_begin, data_end); 231 | 232 | if (this.pushID3v2TXXXData(pts, description, text)) { result = true; } 233 | } 234 | } 235 | 236 | frame = frame_end; 237 | } 238 | 239 | begin = id3_start + 3 + 2 + 1 + 4 + id3_size; 240 | if (begin + 3 > data.length) { continue; } 241 | // id3 footer 242 | if (!(data[begin + 0] === 0x33 && data[begin + 1] === 0x44 && data[begin + 2] === 0x49)) { continue; } 243 | begin += 3 + 2 /* version */ + 1 /* flags */ + 4 /* size */; 244 | } 245 | 246 | return result; 247 | } 248 | 249 | public setInBandMetadataTextTrack(track: TextTrack): void { 250 | this.id3Track?.removeEventListener('cuechange', this.onID3CueChangeHandler) 251 | 252 | this.id3Track = track 253 | this.id3Track.mode = 'hidden' 254 | 255 | this.id3Track.addEventListener('cuechange', this.onID3CueChangeHandler) 256 | } 257 | 258 | private pushID3v2Cue(cue: TextTrackCue): boolean { 259 | if (!this.id3Track) { return false; } 260 | 261 | const start_time = cue.startTime; 262 | const id3_cue = cue as any; 263 | 264 | if (this.id3Track.inBandMetadataTrackDispatchType === '15260DFFFF49443320FF49443320000F'){ // Legacy Edge 265 | return this.pushID3v2Data(start_time, new Uint8Array(id3_cue.data)); 266 | } else if (this.id3Track.inBandMetadataTrackDispatchType === 'com.apple.streaming') { // Safari 267 | if (id3_cue.value.key === 'PRIV') { 268 | return this.pushID3v2PRIVData(start_time, id3_cue.value.info, new Uint8Array(id3_cue.value.data)); 269 | } else if (id3_cue.value.key === 'TXXX') { 270 | return this.pushID3v2TXXXData(start_time, id3_cue.value.info, id3_cue.value.data); 271 | } 272 | } else if (this.id3Track.label === 'id3') { // hls.js 273 | if (id3_cue.value.key === 'PRIV') { 274 | return this.pushID3v2PRIVData(start_time, id3_cue.value.info, new Uint8Array(id3_cue.value.data)); 275 | } else if (id3_cue.value.key === 'TXXX') { 276 | return this.pushID3v2TXXXData(start_time, id3_cue.value.info, id3_cue.value.data); 277 | } 278 | } else if (this.id3Track.label === 'Timed Metadata') { // video.js 279 | if (id3_cue.frame.key === 'PRIV') { 280 | return this.pushID3v2PRIVData(start_time, id3_cue.frame.owner, new Uint8Array(id3_cue.frame.data)); 281 | } else if (id3_cue.frame.key === 'TXXX') { 282 | return this.pushID3v2TXXXData(start_time, id3_cue.frame.description, id3_cue.frame.data); 283 | } 284 | } 285 | 286 | return false; 287 | } 288 | 289 | private onID3CueChange() { 290 | if (!this.id3Track) { return } 291 | 292 | if (this.isOnSeeking) { return } 293 | 294 | /* 295 | const activeCues = this.id3Track.activeCues ?? [] 296 | for (let i = activeCues.length - 1; i >= 0; i--) { 297 | if (this.pushID3v2Cue(activeCues[i])) { break; } 298 | } 299 | */ 300 | this.onTimeupdate(); 301 | } 302 | 303 | private addB24Cue (start_time: number, end_time: number, data: Uint8Array): boolean { 304 | if (!this.b24Track) { return false; } 305 | if (!HTMLProvider.detect(data, this.rendererOption)) { return false; } 306 | 307 | const CueClass = window.VTTCue ?? window.TextTrackCue 308 | 309 | const b24_cue = new CueClass(start_time, end_time, ''); 310 | (b24_cue as any).data = data; 311 | 312 | if (window.VTTCue) { 313 | this.b24Track.addCue(b24_cue) 314 | } else if (window.TextTrackCue) { 315 | const hasCue = Array.prototype.some.call(this.b24Track.cues ?? [], (target) => { 316 | return target.startTime === start_time 317 | }) 318 | if (hasCue) { return false; } 319 | 320 | if (this.b24Track.cues) { 321 | const removed_cues: TextTrackCue[] = []; 322 | for (let i = this.b24Track.cues.length - 1; i >= 0; i--) { 323 | if (this.b24Track.cues[i].startTime >= start_time) { 324 | removed_cues.push(this.b24Track.cues[i]) 325 | this.b24Track.removeCue(this.b24Track.cues[i]) 326 | } 327 | } 328 | this.b24Track.addCue(b24_cue) 329 | for (let i = removed_cues.length - 1; i >= 0; i--) { 330 | this.b24Track.addCue(removed_cues[i]) 331 | } 332 | } 333 | } 334 | 335 | return true; 336 | } 337 | 338 | private onB24CueChange() { 339 | if (!this.media || !this.b24Track) { 340 | this.onB24CueChangeDrawed = false 341 | return 342 | } 343 | 344 | if (this.table) { 345 | while (this.table.firstChild) { 346 | this.table.removeChild(this.table.firstChild); 347 | } 348 | } 349 | 350 | if (this.b24Track.activeCues && this.b24Track.activeCues.length > 0) { 351 | const lastCue = this.b24Track.activeCues[this.b24Track.activeCues.length - 1] as any 352 | 353 | if ((lastCue.startTime <= this.media.currentTime && this.media.currentTime <= lastCue.endTime) && !this.isOnSeeking) { 354 | // なんか Win Firefox で Cue が endTime 過ぎても activeCues から消えない場合があった、バグ? 355 | 356 | const provider: HTMLProvider = new HTMLProvider(lastCue.data, lastCue.startTime); 357 | let rendered = false 358 | 359 | const result = provider.render({ 360 | ... this.rendererOption, 361 | table: this.table ?? undefined, 362 | }) 363 | 364 | if (result?.renderedText != null) { 365 | this.rendererOption?.renderedTextCallback?.(result?.renderedText); 366 | } 367 | 368 | if (result?.PRA != null) { 369 | this.rendererOption?.PRACallback?.(result.PRA); 370 | } 371 | 372 | if (result?.rendered) { this.onResize(); } 373 | this.onB24CueChangeDrawed = result?.rendered ?? false; 374 | } else { 375 | this.onB24CueChangeDrawed = false 376 | } 377 | 378 | for (let i = this.b24Track.activeCues.length - 2; i >= 0; i--) { 379 | const cue = this.b24Track.activeCues[i] 380 | cue.endTime = Math.min(cue.endTime, lastCue.startTime) 381 | if (cue.startTime === cue.endTime) { // .. if duplicate subtitle appeared 382 | this.b24Track.removeCue(cue); 383 | } 384 | } 385 | } else{ 386 | this.onB24CueChangeDrawed = false 387 | } 388 | } 389 | 390 | private onHighResTimeupdate() { 391 | this.onTimeupdate(); 392 | this.highResTimeupdatePollingId = window.requestAnimationFrame(this.onHighResTimeupdateHandler); 393 | } 394 | 395 | private onTimeupdate() { 396 | if (!this.media) { return; } 397 | if (this.prevCurrentTime == null) { 398 | this.prevCurrentTime = this.media.currentTime; 399 | return; 400 | } 401 | 402 | if (!this.id3Track || !this.id3Track.cues || this.id3Track.cues.length === 0) { 403 | this.prevCurrentTime = this.media.currentTime; 404 | return; 405 | } 406 | 407 | if (this.isOnSeeking) { 408 | this.prevCurrentTime = this.media.currentTime; 409 | return; 410 | } 411 | if (Math.abs(this.media.currentTime - this.prevCurrentTime) > DETECT_TIMEUPDATE_SEEKING_RANGE) { 412 | this.prevCurrentTime = this.media.currentTime; 413 | return; 414 | } 415 | 416 | const dummyCue = new DummyCue(Number.NEGATIVE_INFINITY, this.id3Track.cues[0].startTime); 417 | let prevIndex: number | null = null; 418 | let currIndex: number | null = null; 419 | 420 | const cues: TextTrackCue[] = [ dummyCue ]; // ... this.id3Track.cues 421 | for (let i = 0; i < this.id3Track.cues.length; i++) { 422 | cues.push(this.id3Track.cues[i]); 423 | } 424 | 425 | { 426 | let begin = 0, end = cues.length; 427 | while (begin + 1 < end) { 428 | const currentTime = this.prevCurrentTime; 429 | const middle = Math.floor((begin + end) / 2); 430 | const startTime = cues[middle].startTime; 431 | 432 | if (currentTime < startTime) { 433 | end = middle; 434 | } else { 435 | begin = middle; 436 | } 437 | } 438 | prevIndex = begin; 439 | } 440 | { 441 | let begin = 0, end = cues.length; 442 | while (begin + 1 < end) { 443 | const currentTime = this.media.currentTime; 444 | const middle = Math.floor((begin + end) / 2); 445 | const startTime = cues[middle].startTime; 446 | 447 | if (currentTime < startTime) { 448 | end = middle; 449 | } else { 450 | begin = middle; 451 | } 452 | } 453 | currIndex = begin; 454 | } 455 | 456 | if (prevIndex === null || currIndex === null || prevIndex === currIndex){ 457 | this.prevCurrentTime = this.media.currentTime; 458 | return; 459 | } 460 | 461 | if (prevIndex < currIndex) { 462 | for (let index = currIndex; index > prevIndex; index--) { 463 | const cue = cues[index]; 464 | if (cue === dummyCue) { continue; } 465 | 466 | if (this.pushID3v2Cue(cue)) { break; } 467 | } 468 | } else { 469 | for (let index = prevIndex; index < currIndex; index++) { 470 | const cue = cues[index]; 471 | if (cue === dummyCue) { continue; } 472 | 473 | if (this.pushID3v2Cue(cue)) { break; } 474 | } 475 | } 476 | 477 | this.prevCurrentTime = this.media.currentTime; 478 | } 479 | 480 | private onCanplay() { 481 | if (this.id3Track) { 482 | this.id3Track.mode = 'hidden'; 483 | } 484 | if (this.b24Track) { 485 | this.b24Track.mode = 'hidden'; 486 | } 487 | 488 | if (this.media != null && this.prevCurrentTime == null) { 489 | this.prevCurrentTime = this.media.currentTime - Number.MIN_VALUE; 490 | } 491 | } 492 | 493 | private onPlay() { 494 | if (this.highResTimeupdatePollingId == null) { 495 | this.onHighResTimeupdate(); 496 | } 497 | } 498 | 499 | private onPause() { 500 | if (this.highResTimeupdatePollingId != null) { 501 | window.cancelAnimationFrame(this.highResTimeupdatePollingId); 502 | this.highResTimeupdatePollingId = null; 503 | } 504 | } 505 | 506 | private onSeeking() { 507 | this.isOnSeeking = true 508 | this.onB24CueChange() 509 | } 510 | 511 | private onSeeked() { 512 | this.isOnSeeking = false 513 | } 514 | 515 | private onResize() { 516 | if (!this.media) { return } 517 | if (!this.wrapper) { return; } 518 | 519 | const style = window.getComputedStyle(this.media) 520 | const media_width = Number.parseInt(style.width) 521 | const media_height = Number.parseInt(style.height) 522 | 523 | // TODO; 524 | const caption_width = this.wrapper.offsetWidth 525 | const caption_height = this.wrapper.offsetHeight 526 | 527 | const scale_x = media_width / caption_width 528 | const scale_y = media_height / caption_height 529 | 530 | if (this.rendererOption?.keepAspectRatio) { 531 | const scale = Math.min(scale_x, scale_y); 532 | 533 | this.wrapper.style.top = `${(media_height - (caption_height * scale)) / 2}px`; 534 | this.wrapper.style.left = `${(media_width - (caption_width * scale)) / 2}px`; 535 | this.wrapper.style.transform = `scale(${scale})` 536 | this.wrapper.style.transformOrigin = '0 0'; 537 | } else { 538 | this.wrapper.style.top = '0px'; 539 | this.wrapper.style.left = '0px'; 540 | this.wrapper.style.transform = `scale(${scale_x}, ${scale_y})` 541 | this.wrapper.style.transformOrigin = '0 0'; 542 | } 543 | 544 | } 545 | 546 | private onID3Addtrack(event: TrackEvent): void { 547 | if (!this.media) { 548 | return; 549 | } 550 | 551 | const textTrack = event.track!; 552 | if (textTrack.kind !== 'metadata') { return; } 553 | 554 | if ( textTrack.inBandMetadataTrackDispatchType === '15260DFFFF49443320FF49443320000F' // Legacy Edge 555 | || textTrack.inBandMetadataTrackDispatchType === 'com.apple.streaming' // Safari 556 | || textTrack.label === 'id3' // hls.js 557 | ) { 558 | this.setInBandMetadataTextTrack(textTrack); 559 | } 560 | } 561 | 562 | private setupTrack(): void { 563 | if (!this.media) { 564 | return 565 | } 566 | 567 | if (this.rendererOption?.useHighResTextTrack) { 568 | this.b24Track = new HighResTextTrack(this.media); 569 | (this.b24Track as HighResTextTrack).startPolling(); 570 | } else { 571 | const aribb24js_label = `ARIB B24 Japanese (data_identifier=0x${this.data_identifier.toString(16)}, data_group_id=${this.data_group_id})` 572 | for (let i = 0; i < this.media.textTracks.length; i++) { 573 | const track = this.media.textTracks[i] 574 | if (track.label === aribb24js_label) { 575 | this.b24Track = track 576 | break 577 | } 578 | } 579 | if (!this.b24Track) { 580 | this.b24Track = this.media.addTextTrack('metadata', aribb24js_label, 'ja') 581 | this.b24Track.mode = 'hidden' 582 | } 583 | } 584 | 585 | this.b24Track.addEventListener('cuechange', this.onB24CueChangeHandler) 586 | 587 | if (this.rendererOption?.enableAutoInBandMetadataTextTrackDetection) { 588 | for (let i = 0; i < this.media.textTracks.length; i++) { 589 | const track = this.media.textTracks[i]; 590 | 591 | if (track.kind !== 'metadata') { continue; } 592 | 593 | if ( track.inBandMetadataTrackDispatchType === '15260DFFFF49443320FF49443320000F' // Legacy Edge 594 | || track.inBandMetadataTrackDispatchType === 'com.apple.streaming' // Safari 595 | || track.label === 'id3' // hls.js 596 | ) { 597 | this.setInBandMetadataTextTrack(track); 598 | break; 599 | } 600 | } 601 | 602 | this.media.textTracks.addEventListener('addtrack', this.onID3AddtrackHandler) 603 | } 604 | 605 | this.media.addEventListener('seeking', this.onSeekingHandler) 606 | this.media.addEventListener('seeked', this.onSeekedHandler) 607 | } 608 | 609 | private setupTable(): void { 610 | if (!this.media || !this.subtitleElement){ 611 | return 612 | } 613 | this.wrapper = document.createElement('div'); 614 | this.table = document.createElement('table'); 615 | 616 | this.wrapper.style.pointerEvents = 'none'; 617 | 618 | this.wrapper.appendChild(this.table); 619 | this.subtitleElement.appendChild(this.wrapper) 620 | this.media.addEventListener('resize', this.onResizeHandler) 621 | 622 | if (window.ResizeObserver) { 623 | this.resizeObserver = new ResizeObserver(() => { 624 | this.onResize() 625 | }) 626 | this.resizeObserver.observe(this.media) 627 | } else { 628 | window.addEventListener('resize', this.onResizeHandler) 629 | 630 | if (window.MutationObserver) { 631 | this.mutationObserver = new MutationObserver(() => { 632 | this.onResize() 633 | }) 634 | this.mutationObserver.observe(this.media, { 635 | attributes: true, 636 | attributeFilter: ['class', 'style'] 637 | }) 638 | } 639 | } 640 | } 641 | 642 | private cleanupTrack(): void { 643 | if (this.b24Track) { 644 | if (this.rendererOption?.useHighResTextTrack) { 645 | (this.b24Track as HighResTextTrack).stopPolling(); 646 | } else { 647 | if (this.b24Track.cues) { 648 | for (let i = this.b24Track.cues.length - 1; i >= 0; i--) { 649 | this.b24Track.removeCue(this.b24Track.cues[i]) 650 | } 651 | } 652 | } 653 | } 654 | 655 | this.b24Track?.removeEventListener('cuechange', this.onB24CueChangeHandler) 656 | 657 | this.id3Track?.removeEventListener('cuechange', this.onID3CueChangeHandler) 658 | 659 | this.media?.removeEventListener('seeking', this.onSeekingHandler) 660 | this.media?.removeEventListener('seeked', this.onSeekedHandler) 661 | this.media?.textTracks.removeEventListener('addtrack', this.onID3AddtrackHandler) 662 | 663 | this.b24Track = this.id3Track = null 664 | } 665 | 666 | private cleanupTable(): void { 667 | window.removeEventListener('resize', this.onResizeHandler) 668 | this.media?.removeEventListener('resize', this.onResizeHandler) 669 | 670 | if (this.resizeObserver) { 671 | this.resizeObserver.disconnect() 672 | this.resizeObserver = null 673 | } 674 | 675 | if (this.mutationObserver) { 676 | this.mutationObserver.disconnect() 677 | this.mutationObserver = null 678 | } 679 | 680 | if (this.table && this.wrapper) { 681 | this.wrapper.removeChild(this.table) 682 | } 683 | if (this.wrapper && this.subtitleElement) { 684 | this.subtitleElement.removeChild(this.wrapper) 685 | } 686 | this.wrapper = this.table = null 687 | } 688 | } 689 | -------------------------------------------------------------------------------- /src/canvas-renderer.ts: -------------------------------------------------------------------------------- 1 | import CanvasProvider from './canvas-provider' 2 | import HighResTextTrack from './utils/high-res-texttrack' 3 | import DummyCue from './utils/dummy-cue' 4 | import { readID3Size, binaryISO85591ToString, binaryUTF8ToString, base64ToUint8Array} from './utils/binary' 5 | 6 | const DETECT_TIMEUPDATE_SEEKING_RANGE = 1; 7 | 8 | export interface RendererOption { 9 | width?: number, 10 | height?: number, 11 | data_identifier?: number, 12 | data_group_id?: number, 13 | forceStrokeColor?: boolean | string, 14 | forceBackgroundColor?: string, 15 | normalFont?: string, 16 | gaijiFont?: string, 17 | drcsReplacement?: boolean, 18 | drcsReplaceMapping?: Record, 19 | renderedTextCallback?: (renderedText: string) => unknown, 20 | PRACallback?: (index: number) => unknown, 21 | keepAspectRatio?: boolean, 22 | enableRawCanvas?: boolean, 23 | enableAutoInBandMetadataTextTrackDetection?: boolean, 24 | useStroke?: boolean, 25 | useHighResTextTrack?: boolean, 26 | useHighResTimeupdate?: boolean, 27 | usePUA?: boolean, 28 | } 29 | 30 | export default class CanvasID3Renderer { 31 | 32 | private media: HTMLVideoElement | null = null 33 | private id3Track: TextTrack | null = null 34 | private b24Track: TextTrack | null = null 35 | private subtitleElement: HTMLElement | null = null 36 | private viewCanvas: HTMLCanvasElement | null = null 37 | private rawCanvas: HTMLCanvasElement | null = null 38 | private resizeObserver: ResizeObserver | null = null 39 | private mutationObserver: MutationObserver | null = null 40 | private prevCurrentTime: number | null = null 41 | private highResTimeupdatePollingId: number | null = null 42 | private isShowing: boolean = true 43 | private isOnSeeking: boolean = false 44 | private onB24CueChangeDrawed: boolean = false 45 | 46 | private readonly onID3AddtrackHandler: ((event: TrackEvent) => void) = this.onID3Addtrack.bind(this); 47 | private readonly onID3CueChangeHandler: (() => void) = this.onID3CueChange.bind(this); 48 | private readonly onB24CueChangeHandler: (() => void) = this.onB24CueChange.bind(this); 49 | 50 | private readonly onHighResTimeupdateHandler: (() => void) =this.onHighResTimeupdate.bind(this); 51 | private readonly onTimeupdateHandler: (() => void) = this.onTimeupdate.bind(this); 52 | private readonly onCanplayHandler: (() => void) = this.onCanplay.bind(this); 53 | private readonly onPlayHandler: (() => void) = this.onPlay.bind(this); 54 | private readonly onPauseHandler: (() => void) = this.onPause.bind(this); 55 | private readonly onSeekingHandler: (() => void) = this.onSeeking.bind(this); 56 | private readonly onSeekedHandler: (() => void) = this.onSeeked.bind(this); 57 | private readonly onResizeHandler: (() => void) = this.onResize.bind(this); 58 | 59 | private rendererOption: RendererOption | undefined 60 | private data_identifier: number 61 | private data_group_id: number 62 | 63 | public constructor(option?: RendererOption) { 64 | this.data_identifier = option?.data_identifier ?? 0x80 // default: caption 65 | this.data_group_id = option?.data_group_id ?? 0x01 // default: 1st language 66 | this.rendererOption = { 67 | ... option, 68 | data_identifier: this.data_identifier, 69 | data_group_id: this.data_group_id, 70 | keepAspectRatio: option?.keepAspectRatio ?? true, // default: true 71 | enableAutoInBandMetadataTextTrackDetection: option?.enableAutoInBandMetadataTextTrackDetection ?? true, // default: true 72 | useStroke: option?.useStroke ?? true, // default: true 73 | } 74 | } 75 | 76 | public attachMedia(media: HTMLVideoElement, subtitleElement?: HTMLElement): void { 77 | this.detachMedia() 78 | this.media = media 79 | this.subtitleElement = subtitleElement ?? media.parentElement 80 | 81 | this.media.addEventListener('canplay', this.onCanplayHandler) 82 | if (this.rendererOption?.useHighResTimeupdate) { 83 | this.media.addEventListener('play', this.onPlayHandler) 84 | this.media.addEventListener('pause', this.onPauseHandler) 85 | } else { 86 | this.media.addEventListener('timeupdate', this.onTimeupdateHandler) 87 | } 88 | this.prevCurrentTime = null; 89 | 90 | this.setupTrack() 91 | this.setupCanvas() 92 | } 93 | 94 | public detachMedia(): void { 95 | this.cleanupCanvas() 96 | this.cleanupTrack() 97 | 98 | this.media?.removeEventListener('canplay', this.onCanplayHandler) 99 | this.media?.removeEventListener('play', this.onPlayHandler) 100 | this.media?.removeEventListener('pause', this.onPauseHandler) 101 | this.onPause(); 102 | this.media?.removeEventListener('timeupdate', this.onTimeupdateHandler) 103 | this.prevCurrentTime = null; 104 | 105 | this.media = this.subtitleElement = null 106 | } 107 | 108 | public dispose(): void { 109 | this.detachMedia() 110 | } 111 | 112 | public getViewCanvas(): HTMLCanvasElement | null { 113 | return this.viewCanvas 114 | } 115 | 116 | public getRawCanvas(): HTMLCanvasElement | null { 117 | return this.rawCanvas 118 | } 119 | 120 | public refresh(): void { 121 | this.onResize() 122 | } 123 | 124 | public show(): void { 125 | this.isShowing = true 126 | this.onResize() 127 | } 128 | 129 | public hide(): void { 130 | this.isShowing = false 131 | 132 | if (this.viewCanvas) { 133 | const viewContext = this.viewCanvas.getContext('2d') 134 | if (viewContext) { 135 | viewContext.clearRect(0, 0, this.viewCanvas.width, this.viewCanvas.height); 136 | } 137 | } 138 | 139 | if (this.rawCanvas) { 140 | const rawContext = this.rawCanvas.getContext('2d') 141 | if (rawContext) { 142 | rawContext.clearRect(0, 0, this.rawCanvas.width, this.rawCanvas.height); 143 | } 144 | } 145 | } 146 | 147 | public isPresent() { 148 | return this.onB24CueChangeDrawed 149 | } 150 | 151 | public pushRawData(pts: number, data: Uint8Array): boolean { 152 | const provider: CanvasProvider = new CanvasProvider(data, pts); 153 | const estimate = provider.render({ 154 | ... this.rendererOption, 155 | width: undefined, // ここはデフォルト値で負荷を軽くする 156 | height: undefined, // ここはデフォルト値で負荷を軽くする 157 | }) 158 | if (estimate == null) { return false; } 159 | 160 | const end_time = Number.isFinite(estimate.endTime) ? estimate.endTime : Number.MAX_SAFE_INTEGER; 161 | return this.addB24Cue(pts, end_time, data) 162 | } 163 | 164 | public pushBase64Data(pts: number, base64: string): boolean { 165 | const data = base64ToUint8Array(base64); 166 | return this.pushRawData(pts, data); 167 | } 168 | 169 | // for b24.js compatibility 170 | public pushData(pid: number, uint8array: Uint8Array, pts: number): boolean { 171 | return this.pushRawData(pts, uint8array); 172 | } 173 | 174 | public pushID3v2PRIVData(pts: number, owner: string, data: Uint8Array): boolean { 175 | if (owner !== 'aribb24.js') { return false; } 176 | return this.pushRawData(pts, data); 177 | } 178 | 179 | public pushID3v2TXXXData(pts: number, description: string, text: string): boolean { 180 | if (description !== 'aribb24.js') { return false; } 181 | return this.pushBase64Data(pts, text); 182 | } 183 | 184 | public pushID3v2Data(pts: number, data: Uint8Array): boolean { 185 | let result = false; 186 | 187 | for (let begin = 0; begin < data.length;) { 188 | const id3_start = begin; 189 | 190 | if (begin + 3 > data.length) { break; } 191 | if (!(data[begin + 0] === 0x49 && data[begin + 1] === 0x44 && data[begin + 2] === 0x33)) { break; } 192 | begin += 3 + 2 /* version */ + 1 /* flag */; 193 | 194 | if (begin + 4 > data.length) { break; } 195 | const id3_size = readID3Size(data, begin + 0, begin + 4); 196 | begin += 4; 197 | 198 | const id3_end = id3_start + 3 + 2 + 1 + 4 + id3_size; 199 | if (id3_end > data.length) { break; } 200 | 201 | for (let frame = begin; frame < id3_end;) { 202 | const frame_begin = frame; 203 | 204 | if (frame + 4 > data.length) { break; } 205 | const frame_name = binaryISO85591ToString(data, frame + 0, frame + 4); 206 | frame += 4; 207 | 208 | if (frame + 4 > data.length) { break; } 209 | const frame_size = readID3Size(data, frame + 0, frame + 4); 210 | frame += 4 + 2 /* flag */; 211 | 212 | const frame_end = frame_begin + 4 + 4 + 2 + frame_size; 213 | if (frame_end > data.length) { break; } 214 | 215 | if (frame_name === 'PRIV') { 216 | const PRIV_begin = frame; 217 | const PRIV_end = frame_end; 218 | 219 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 220 | 221 | const owner = binaryISO85591ToString(data, PRIV_begin, frame); 222 | const pes = new Uint8Array(Array.prototype.slice.call(data, frame + 1, PRIV_end)); 223 | 224 | if (this.pushID3v2PRIVData(pts, owner, pes)) { result = true; } 225 | } else if (frame_name === 'TXXX') { 226 | const encoding = data[frame + 0]; 227 | const description_begin = frame + 1; 228 | 229 | if (encoding === 0x03) { // UTF-8 230 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 231 | const description_end = frame; 232 | frame += 1; 233 | 234 | const data_begin = frame; 235 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 236 | const data_end = frame; 237 | 238 | const description = binaryUTF8ToString(data, description_begin, description_end); 239 | const text = binaryUTF8ToString(data, data_begin, data_end); 240 | 241 | if (this.pushID3v2TXXXData(pts, description, text)) { result = true; } 242 | } else if(encoding === 0x00) { // Laten-1 243 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 244 | const description_end = frame; 245 | frame += 1; 246 | 247 | const data_begin = frame; 248 | while (data[frame] !== 0 && frame < frame_end) { frame++; } 249 | const data_end = frame; 250 | 251 | const description = binaryISO85591ToString(data, description_begin, description_end); 252 | const text = binaryISO85591ToString(data, data_begin, data_end); 253 | 254 | if (this.pushID3v2TXXXData(pts, description, text)) { result = true; } 255 | } 256 | } 257 | 258 | frame = frame_end; 259 | } 260 | 261 | begin = id3_start + 3 + 2 + 1 + 4 + id3_size; 262 | if (begin + 3 > data.length) { continue; } 263 | // id3 footer 264 | if (!(data[begin + 0] === 0x33 && data[begin + 1] === 0x44 && data[begin + 2] === 0x49)) { continue; } 265 | begin += 3 + 2 /* version */ + 1 /* flags */ + 4 /* size */; 266 | } 267 | 268 | return result; 269 | } 270 | 271 | public setInBandMetadataTextTrack(track: TextTrack): void { 272 | this.id3Track?.removeEventListener('cuechange', this.onID3CueChangeHandler) 273 | 274 | this.id3Track = track 275 | this.id3Track.mode = 'hidden' 276 | 277 | this.id3Track.addEventListener('cuechange', this.onID3CueChangeHandler) 278 | } 279 | 280 | private pushID3v2Cue(cue: TextTrackCue): boolean { 281 | if (!this.id3Track) { return false; } 282 | 283 | const start_time = cue.startTime; 284 | const id3_cue = cue as any; 285 | 286 | if (this.id3Track.inBandMetadataTrackDispatchType === '15260DFFFF49443320FF49443320000F'){ // Legacy Edge 287 | return this.pushID3v2Data(start_time, new Uint8Array(id3_cue.data)); 288 | } else if (this.id3Track.inBandMetadataTrackDispatchType === 'com.apple.streaming') { // Safari 289 | if (id3_cue.value.key === 'PRIV') { 290 | return this.pushID3v2PRIVData(start_time, id3_cue.value.info, new Uint8Array(id3_cue.value.data)); 291 | } else if (id3_cue.value.key === 'TXXX') { 292 | return this.pushID3v2TXXXData(start_time, id3_cue.value.info, id3_cue.value.data); 293 | } 294 | } else if (this.id3Track.label === 'id3') { // hls.js 295 | if (id3_cue.value.key === 'PRIV') { 296 | return this.pushID3v2PRIVData(start_time, id3_cue.value.info, new Uint8Array(id3_cue.value.data)); 297 | } else if (id3_cue.value.key === 'TXXX') { 298 | return this.pushID3v2TXXXData(start_time, id3_cue.value.info, id3_cue.value.data); 299 | } 300 | } else if (this.id3Track.label === 'Timed Metadata') { // video.js 301 | if (id3_cue.frame.key === 'PRIV') { 302 | return this.pushID3v2PRIVData(start_time, id3_cue.frame.owner, new Uint8Array(id3_cue.frame.data)); 303 | } else if (id3_cue.frame.key === 'TXXX') { 304 | return this.pushID3v2TXXXData(start_time, id3_cue.frame.description, id3_cue.frame.data); 305 | } 306 | } 307 | 308 | return false; 309 | } 310 | 311 | private onID3CueChange() { 312 | if (!this.id3Track) { return } 313 | 314 | if (this.isOnSeeking) { return } 315 | 316 | /* 317 | const activeCues = this.id3Track.activeCues ?? [] 318 | for (let i = activeCues.length - 1; i >= 0; i--) { 319 | if (this.pushID3v2Cue(activeCues[i])) { break; } 320 | } 321 | */ 322 | this.onTimeupdate(); 323 | } 324 | 325 | private addB24Cue (start_time: number, end_time: number, data: Uint8Array): boolean { 326 | if (!this.b24Track) { return false; } 327 | if (!CanvasProvider.detect(data, this.rendererOption)) { return false; } 328 | 329 | const CueClass = window.VTTCue ?? window.TextTrackCue 330 | 331 | const b24_cue = new CueClass(start_time, end_time, ''); 332 | (b24_cue as any).data = data; 333 | 334 | if (window.VTTCue) { 335 | this.b24Track.addCue(b24_cue) 336 | } else if (window.TextTrackCue) { 337 | const hasCue = Array.prototype.some.call(this.b24Track.cues ?? [], (target) => { 338 | return target.startTime === start_time 339 | }) 340 | if (hasCue) { return false; } 341 | 342 | if (this.b24Track.cues) { 343 | const removed_cues: TextTrackCue[] = []; 344 | for (let i = this.b24Track.cues.length - 1; i >= 0; i--) { 345 | if (this.b24Track.cues[i].startTime >= start_time) { 346 | removed_cues.push(this.b24Track.cues[i]) 347 | this.b24Track.removeCue(this.b24Track.cues[i]) 348 | } 349 | } 350 | this.b24Track.addCue(b24_cue) 351 | for (let i = removed_cues.length - 1; i >= 0; i--) { 352 | this.b24Track.addCue(removed_cues[i]) 353 | } 354 | } 355 | } 356 | 357 | return true; 358 | } 359 | 360 | private onB24CueChange() { 361 | if (!this.media || !this.b24Track) { 362 | this.onB24CueChangeDrawed = false 363 | return 364 | } 365 | 366 | if (this.viewCanvas) { 367 | const viewContext = this.viewCanvas.getContext('2d') 368 | if (viewContext) { 369 | viewContext.clearRect(0, 0, this.viewCanvas.width, this.viewCanvas.height); 370 | } 371 | } 372 | 373 | if (this.rawCanvas) { 374 | const rawContext = this.rawCanvas.getContext('2d') 375 | if (rawContext) { 376 | rawContext.clearRect(0, 0, this.rawCanvas.width, this.rawCanvas.height); 377 | } 378 | } 379 | 380 | if (this.b24Track.activeCues && this.b24Track.activeCues.length > 0) { 381 | const lastCue = this.b24Track.activeCues[this.b24Track.activeCues.length - 1] as any 382 | 383 | if ((lastCue.startTime <= this.media.currentTime && this.media.currentTime <= lastCue.endTime) && !this.isOnSeeking) { 384 | // なんか Win Firefox で Cue が endTime 過ぎても activeCues から消えない場合があった、バグ? 385 | 386 | const provider: CanvasProvider = new CanvasProvider(lastCue.data, lastCue.startTime); 387 | let rendered = false 388 | 389 | if (this.isShowing && this.viewCanvas) { 390 | const result = provider.render({ 391 | ... this.rendererOption, 392 | canvas: this.viewCanvas, 393 | width: this.rendererOption?.width ?? this.viewCanvas.width, 394 | height: this.rendererOption?.height ?? this.viewCanvas.height, 395 | }) 396 | 397 | if (result?.renderedText != null) { 398 | this.rendererOption?.renderedTextCallback?.(result?.renderedText); 399 | } 400 | 401 | if (result?.PRA != null) { 402 | this.rendererOption?.PRACallback?.(result.PRA); 403 | } 404 | 405 | rendered = result?.rendered ?? false; 406 | } 407 | 408 | if (this.isShowing && this.rawCanvas) { 409 | provider.render({ 410 | ... this.rendererOption, 411 | canvas: this.rawCanvas, 412 | width: this.rawCanvas.width, 413 | height: this.rawCanvas.height, 414 | keepAspectRatio: true, 415 | }) 416 | } 417 | 418 | this.onB24CueChangeDrawed = rendered 419 | } else { 420 | this.onB24CueChangeDrawed = false 421 | } 422 | 423 | for (let i = this.b24Track.activeCues.length - 2; i >= 0; i--) { 424 | const cue = this.b24Track.activeCues[i] 425 | cue.endTime = Math.min(cue.endTime, lastCue.startTime) 426 | if (cue.startTime === cue.endTime) { // .. if duplicate subtitle appeared 427 | this.b24Track.removeCue(cue); 428 | } 429 | } 430 | } else{ 431 | this.onB24CueChangeDrawed = false 432 | } 433 | } 434 | 435 | private onHighResTimeupdate() { 436 | this.onTimeupdate(); 437 | this.highResTimeupdatePollingId = window.requestAnimationFrame(this.onHighResTimeupdateHandler); 438 | } 439 | 440 | private onTimeupdate() { 441 | if (!this.media) { return; } 442 | if (this.prevCurrentTime == null) { 443 | this.prevCurrentTime = this.media.currentTime; 444 | return; 445 | } 446 | 447 | if (!this.id3Track || !this.id3Track.cues || this.id3Track.cues.length === 0) { 448 | this.prevCurrentTime = this.media.currentTime; 449 | return; 450 | } 451 | 452 | if (this.isOnSeeking) { 453 | this.prevCurrentTime = this.media.currentTime; 454 | return; 455 | } 456 | if (Math.abs(this.media.currentTime - this.prevCurrentTime) > DETECT_TIMEUPDATE_SEEKING_RANGE) { 457 | this.prevCurrentTime = this.media.currentTime; 458 | return; 459 | } 460 | 461 | const dummyCue = new DummyCue(Number.NEGATIVE_INFINITY, this.id3Track.cues[0].startTime); 462 | let prevIndex: number | null = null; 463 | let currIndex: number | null = null; 464 | 465 | const cues: TextTrackCue[] = [ dummyCue ]; // ... this.id3Track.cues 466 | for (let i = 0; i < this.id3Track.cues.length; i++) { 467 | cues.push(this.id3Track.cues[i]); 468 | } 469 | 470 | { 471 | let begin = 0, end = cues.length; 472 | while (begin + 1 < end) { 473 | const currentTime = this.prevCurrentTime; 474 | const middle = Math.floor((begin + end) / 2); 475 | const startTime = cues[middle].startTime; 476 | 477 | if (currentTime < startTime) { 478 | end = middle; 479 | } else { 480 | begin = middle; 481 | } 482 | } 483 | prevIndex = begin; 484 | } 485 | { 486 | let begin = 0, end = cues.length; 487 | while (begin + 1 < end) { 488 | const currentTime = this.media.currentTime; 489 | const middle = Math.floor((begin + end) / 2); 490 | const startTime = cues[middle].startTime; 491 | 492 | if (currentTime < startTime) { 493 | end = middle; 494 | } else { 495 | begin = middle; 496 | } 497 | } 498 | currIndex = begin; 499 | } 500 | 501 | if (prevIndex === null || currIndex === null || prevIndex === currIndex){ 502 | this.prevCurrentTime = this.media.currentTime; 503 | return; 504 | } 505 | 506 | if (prevIndex < currIndex) { 507 | for (let index = currIndex; index > prevIndex; index--) { 508 | const cue = cues[index]; 509 | if (cue === dummyCue) { continue; } 510 | 511 | if (this.pushID3v2Cue(cue)) { break; } 512 | } 513 | } else { 514 | for (let index = prevIndex; index < currIndex; index++) { 515 | const cue = cues[index]; 516 | if (cue === dummyCue) { continue; } 517 | 518 | if (this.pushID3v2Cue(cue)) { break; } 519 | } 520 | } 521 | 522 | this.prevCurrentTime = this.media.currentTime; 523 | } 524 | 525 | private onCanplay() { 526 | if (this.id3Track) { 527 | this.id3Track.mode = 'hidden'; 528 | } 529 | if (this.b24Track) { 530 | this.b24Track.mode = 'hidden'; 531 | } 532 | 533 | if (this.media != null && this.prevCurrentTime == null) { 534 | this.prevCurrentTime = this.media.currentTime - Number.MIN_VALUE; 535 | } 536 | } 537 | 538 | private onPlay() { 539 | if (this.highResTimeupdatePollingId == null) { 540 | this.onHighResTimeupdate(); 541 | } 542 | } 543 | 544 | private onPause() { 545 | if (this.highResTimeupdatePollingId != null) { 546 | window.cancelAnimationFrame(this.highResTimeupdatePollingId); 547 | this.highResTimeupdatePollingId = null; 548 | } 549 | } 550 | 551 | private onSeeking() { 552 | this.isOnSeeking = true 553 | this.onB24CueChange() 554 | } 555 | 556 | private onSeeked() { 557 | this.isOnSeeking = false 558 | } 559 | 560 | private onResize() { 561 | if (!this.media) { 562 | return 563 | } 564 | 565 | const style = window.getComputedStyle(this.media) 566 | const media_width = Number.parseInt(style.width) * window.devicePixelRatio 567 | const media_height = Number.parseInt(style.height) * window.devicePixelRatio 568 | const video_width = this.media.videoWidth 569 | const video_height = this.media.videoHeight 570 | 571 | if (this.viewCanvas) { 572 | this.viewCanvas.width = Math.round(media_width) 573 | this.viewCanvas.height = Math.round(media_height) 574 | } 575 | if (this.rawCanvas) { 576 | this.rawCanvas.width = video_width 577 | this.rawCanvas.height = video_height 578 | } 579 | 580 | if (!this.b24Track) { 581 | return; 582 | } 583 | 584 | if (this.viewCanvas) { 585 | const viewContext = this.viewCanvas.getContext('2d') 586 | if (viewContext) { 587 | viewContext.clearRect(0, 0, this.viewCanvas.width, this.viewCanvas.height); 588 | } 589 | } 590 | 591 | if (this.rawCanvas) { 592 | const rawContext = this.rawCanvas.getContext('2d') 593 | if (rawContext) { 594 | rawContext.clearRect(0, 0, this.rawCanvas.width, this.rawCanvas.height); 595 | } 596 | } 597 | 598 | if (!this.onB24CueChangeDrawed) { return } 599 | 600 | // onB24CueChange とほぼ同じだが、this.onB24CueChangeDrawed を変更しない 601 | if (this.b24Track.activeCues && this.b24Track.activeCues.length > 0) { 602 | const lastCue = this.b24Track.activeCues[this.b24Track.activeCues.length - 1] as any 603 | 604 | if ((lastCue.startTime <= this.media.currentTime && this.media.currentTime <= lastCue.endTime) && !this.isOnSeeking) { 605 | // なんか Win Firefox で Cue が endTime 過ぎても activeCues から消えない場合があった、バグ? 606 | 607 | const provider: CanvasProvider = new CanvasProvider(lastCue.data, lastCue.startTime); 608 | 609 | if (this.isShowing && this.viewCanvas) { 610 | provider.render({ 611 | ... this.rendererOption, 612 | canvas: this.viewCanvas, 613 | width: this.rendererOption?.width ?? this.viewCanvas.width, 614 | height: this.rendererOption?.height ?? this.viewCanvas.height, 615 | }) 616 | } 617 | 618 | if (this.isShowing && this.rawCanvas) { 619 | provider.render({ 620 | ... this.rendererOption, 621 | canvas: this.rawCanvas, 622 | width: this.rawCanvas.width, 623 | height: this.rawCanvas.height, 624 | keepAspectRatio: true, 625 | }) 626 | } 627 | } 628 | } 629 | } 630 | 631 | private onID3Addtrack(event: TrackEvent): void { 632 | if (!this.media) { 633 | return; 634 | } 635 | 636 | const textTrack = event.track!; 637 | if (textTrack.kind !== 'metadata') { return; } 638 | 639 | if ( textTrack.inBandMetadataTrackDispatchType === '15260DFFFF49443320FF49443320000F' // Legacy Edge 640 | || textTrack.inBandMetadataTrackDispatchType === 'com.apple.streaming' // Safari 641 | || textTrack.label === 'id3' // hls.js 642 | ) { 643 | this.setInBandMetadataTextTrack(textTrack); 644 | } 645 | } 646 | 647 | private setupTrack(): void { 648 | if (!this.media) { 649 | return 650 | } 651 | 652 | if (this.rendererOption?.useHighResTextTrack) { 653 | this.b24Track = new HighResTextTrack(this.media); 654 | (this.b24Track as HighResTextTrack).startPolling(); 655 | } else { 656 | const aribb24js_label = `ARIB B24 Japanese (data_identifier=0x${this.data_identifier.toString(16)}, data_group_id=${this.data_group_id})` 657 | for (let i = 0; i < this.media.textTracks.length; i++) { 658 | const track = this.media.textTracks[i] 659 | if (track.label === aribb24js_label) { 660 | this.b24Track = track 661 | break 662 | } 663 | } 664 | if (!this.b24Track) { 665 | this.b24Track = this.media.addTextTrack('metadata', aribb24js_label, 'ja') 666 | this.b24Track.mode = 'hidden' 667 | } 668 | } 669 | 670 | this.b24Track.addEventListener('cuechange', this.onB24CueChangeHandler) 671 | 672 | if (this.rendererOption?.enableAutoInBandMetadataTextTrackDetection) { 673 | for (let i = 0; i < this.media.textTracks.length; i++) { 674 | const track = this.media.textTracks[i]; 675 | 676 | if (track.kind !== 'metadata') { continue; } 677 | 678 | if ( track.inBandMetadataTrackDispatchType === '15260DFFFF49443320FF49443320000F' // Legacy Edge 679 | || track.inBandMetadataTrackDispatchType === 'com.apple.streaming' // Safari 680 | || track.label === 'id3' // hls.js 681 | ) { 682 | this.setInBandMetadataTextTrack(track); 683 | break; 684 | } 685 | } 686 | 687 | this.media.textTracks.addEventListener('addtrack', this.onID3AddtrackHandler) 688 | } 689 | 690 | this.media.addEventListener('seeking', this.onSeekingHandler) 691 | this.media.addEventListener('seeked', this.onSeekedHandler) 692 | } 693 | 694 | private setupCanvas(): void { 695 | if (!this.media || !this.subtitleElement){ 696 | return 697 | } 698 | this.viewCanvas = document.createElement('canvas') 699 | this.viewCanvas.style.position = 'absolute' 700 | this.viewCanvas.style.top = this.viewCanvas.style.left = '0' 701 | this.viewCanvas.style.pointerEvents = 'none' 702 | this.viewCanvas.style.width = '100%' 703 | this.viewCanvas.style.height = '100%' 704 | 705 | if (this.rendererOption?.enableRawCanvas) { 706 | this.rawCanvas = document.createElement('canvas') 707 | } 708 | 709 | this.onResize() 710 | 711 | this.subtitleElement.appendChild(this.viewCanvas) 712 | 713 | this.media.addEventListener('resize', this.onResizeHandler) 714 | 715 | if (window.ResizeObserver) { 716 | this.resizeObserver = new ResizeObserver(() => { 717 | this.onResize() 718 | }) 719 | this.resizeObserver.observe(this.media) 720 | } else { 721 | window.addEventListener('resize', this.onResizeHandler) 722 | 723 | if (window.MutationObserver) { 724 | this.mutationObserver = new MutationObserver(() => { 725 | this.onResize() 726 | }) 727 | this.mutationObserver.observe(this.media, { 728 | attributes: true, 729 | attributeFilter: ['class', 'style'] 730 | }) 731 | } 732 | } 733 | } 734 | 735 | private cleanupTrack(): void { 736 | if (this.b24Track) { 737 | if (this.rendererOption?.useHighResTextTrack) { 738 | (this.b24Track as HighResTextTrack).stopPolling(); 739 | } else { 740 | if (this.b24Track.cues) { 741 | for (let i = this.b24Track.cues.length - 1; i >= 0; i--) { 742 | this.b24Track.removeCue(this.b24Track.cues[i]) 743 | } 744 | } 745 | } 746 | } 747 | 748 | this.b24Track?.removeEventListener('cuechange', this.onB24CueChangeHandler) 749 | 750 | this.id3Track?.removeEventListener('cuechange', this.onID3CueChangeHandler) 751 | 752 | this.media?.removeEventListener('seeking', this.onSeekingHandler) 753 | this.media?.removeEventListener('seeked', this.onSeekedHandler) 754 | this.media?.textTracks.removeEventListener('addtrack', this.onID3AddtrackHandler) 755 | 756 | this.b24Track = this.id3Track = null 757 | } 758 | 759 | private cleanupCanvas(): void { 760 | window.removeEventListener('resize', this.onResizeHandler) 761 | this.media?.removeEventListener('resize', this.onResizeHandler) 762 | 763 | if (this.resizeObserver) { 764 | this.resizeObserver.disconnect() 765 | this.resizeObserver = null 766 | } 767 | 768 | if (this.mutationObserver) { 769 | this.mutationObserver.disconnect() 770 | this.mutationObserver = null 771 | } 772 | 773 | if (this.viewCanvas && this.subtitleElement) { 774 | this.subtitleElement.removeChild(this.viewCanvas) 775 | } 776 | 777 | if (this.viewCanvas) { 778 | this.viewCanvas.width = this.viewCanvas.height = 0 779 | } 780 | 781 | if (this.rawCanvas) { 782 | this.rawCanvas.width = this.rawCanvas.height = 0 783 | } 784 | 785 | this.viewCanvas = this.rawCanvas = null 786 | } 787 | } 788 | -------------------------------------------------------------------------------- /src/constants/mapping/kanji.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | ' ','、','。',',','.','・',':',';','?','!','゛','゜','´','`','¨','^',' ̄','_','ヽ','ヾ','ゝ','ゞ','〃','仝','々','〆','〇','ー','―','‐','/','\','〜','‖','|','…','‥','‘','’','“','”','(',')','〔','〕','[',']','{','}','〈','〉','《','》','「','」','『','』','【','】','+','−','±','×','÷','=','≠','<','>','≦','≧','∞','∴','♂','♀','°','′','″','℃','¥','$','¢','£','%','#','&','*','@','§','☆','★','○','●','◎','◇','◆','□','■','△','▲','▽','▼','※','〒','→','←','↑','↓','〓',''','"','-','~','〳','〴','〵','〻','〼','ヿ','ゟ','∈','∋','⊆','⊇','⊂','⊃','∪','∩','⊄','⊅','⊊','⊋','∉','∅','⌅','⌆','∧','∨','¬','⇒','⇔','∀','∃','⊕','⊖','⊗','∥','∦','⦅','⦆','〘','〙','〖','〗','∠','⊥','⌒','∂','∇','≡','≒','≪','≫','√','∽','∝','∵','∫','∬','≢','≃','≅','≈','≶','≷','↔','Å','‰','♯','♭','♪','†','‡','¶','♮','♫','♬','♩','◯','▷','▶','◁','◀','↗','↘','↖','↙','⇄','⇨','⇦','⇧','⇩','⤴','⤵','0','1','2','3','4','5','6','7','8','9','⦿','◉','〽','﹆','﹅','◦','•','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','∓','ℵ','ℏ','㏋','ℓ','℧','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','゠','–','⧺','⧻','ぁ','あ','ぃ','い','ぅ','う','ぇ','え','ぉ','お','か','が','き','ぎ','く','ぐ','け','げ','こ','ご','さ','ざ','し','じ','す','ず','せ','ぜ','そ','ぞ','た','だ','ち','ぢ','っ','つ','づ','て','で','と','ど','な','に','ぬ','ね','の','は','ば','ぱ','ひ','び','ぴ','ふ','ぶ','ぷ','へ','べ','ぺ','ほ','ぼ','ぽ','ま','み','む','め','も','ゃ','や','ゅ','ゆ','ょ','よ','ら','り','る','れ','ろ','ゎ','わ','ゐ','ゑ','を','ん','ゔ','ゕ','ゖ','か゚','き゚','く゚','け゚','こ゚','','','','ァ','ア','ィ','イ','ゥ','ウ','ェ','エ','ォ','オ','カ','ガ','キ','ギ','ク','グ','ケ','ゲ','コ','ゴ','サ','ザ','シ','ジ','ス','ズ','セ','ゼ','ソ','ゾ','タ','ダ','チ','ヂ','ッ','ツ','ヅ','テ','デ','ト','ド','ナ','ニ','ヌ','ネ','ノ','ハ','バ','パ','ヒ','ビ','ピ','フ','ブ','プ','ヘ','ベ','ペ','ホ','ボ','ポ','マ','ミ','ム','メ','モ','ャ','ヤ','ュ','ユ','ョ','ヨ','ラ','リ','ル','レ','ロ','ヮ','ワ','ヰ','ヱ','ヲ','ン','ヴ','ヵ','ヶ','カ゚','キ゚','ク゚','ケ゚','コ゚','セ゚','ツ゚','ト゚','Α','Β','Γ','Δ','Ε','Ζ','Η','Θ','Ι','Κ','Λ','Μ','Ν','Ξ','Ο','Π','Ρ','Σ','Τ','Υ','Φ','Χ','Ψ','Ω','♤','♠','♢','♦','♡','♥','♧','♣','α','β','γ','δ','ε','ζ','η','θ','ι','κ','λ','μ','ν','ξ','ο','π','ρ','σ','τ','υ','φ','χ','ψ','ω','ς','⓵','⓶','⓷','⓸','⓹','⓺','⓻','⓼','⓽','⓾','☖','☗','〠','☎','☀','☁','☂','☃','♨','▱','ㇰ','ㇱ','ㇲ','ㇳ','ㇴ','ㇵ','ㇶ','ㇷ','ㇸ','ㇹ','ㇷ゚','ㇺ','ㇻ','ㇼ','ㇽ','ㇾ','ㇿ','А','Б','В','Г','Д','Е','Ё','Ж','З','И','Й','К','Л','М','Н','О','П','Р','С','Т','У','Ф','Х','Ц','Ч','Ш','Щ','Ъ','Ы','Ь','Э','Ю','Я','⎾','⎿','⏀','⏁','⏂','⏃','⏄','⏅','⏆','⏇','⏈','⏉','⏊','⏋','⏌','а','б','в','г','д','е','ё','ж','з','и','й','к','л','м','н','о','п','р','с','т','у','ф','х','ц','ч','ш','щ','ъ','ы','ь','э','ю','я','ヷ','ヸ','ヹ','ヺ','⋚','⋛','⅓','⅔','⅕','✓','⌘','␣','⏎','─','│','┌','┐','┘','└','├','┬','┤','┴','┼','━','┃','┏','┓','┛','┗','┣','┳','┫','┻','╋','┠','┯','┨','┷','┿','┝','┰','┥','┸','╂','㉑','㉒','㉓','㉔','㉕','㉖','㉗','㉘','㉙','㉚','㉛','㉜','㉝','㉞','㉟','㊱','㊲','㊳','㊴','㊵','㊶','㊷','㊸','㊹','㊺','㊻','㊼','㊽','㊾','㊿','','','','','','','','','◐','◑','◒','◓','‼','⁇','⁈','⁉','Ǎ','ǎ','ǐ','Ḿ','ḿ','Ǹ','ǹ','Ǒ','ǒ','ǔ','ǖ','ǘ','ǚ','ǜ','','','€',' ','¡','¤','¦','©','ª','«','­','®','¯','²','³','·','¸','¹','º','»','¼','½','¾','¿','À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','Ø','Ù','Ú','Û','Ü','Ý','Þ','ß','à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ð','ñ','ò','ó','ô','õ','ö','ø','ù','ú','û','ü','ý','þ','ÿ','Ā','Ī','Ū','Ē','Ō','ā','ī','ū','ē','ō','Ą','˘','Ł','Ľ','Ś','Š','Ş','Ť','Ź','Ž','Ż','ą','˛','ł','ľ','ś','ˇ','š','ş','ť','ź','˝','ž','ż','Ŕ','Ă','Ĺ','Ć','Č','Ę','Ě','Ď','Ń','Ň','Ő','Ř','Ů','Ű','Ţ','ŕ','ă','ĺ','ć','č','ę','ě','ď','đ','ń','ň','ő','ř','ů','ű','ţ','˙','Ĉ','Ĝ','Ĥ','Ĵ','Ŝ','Ŭ','ĉ','ĝ','ĥ','ĵ','ŝ','ŭ','ɱ','ʋ','ɾ','ʃ','ʒ','ɬ','ɮ','ɹ','ʈ','ɖ','ɳ','ɽ','ʂ','ʐ','ɻ','ɭ','ɟ','ɲ','ʝ','ʎ','ɡ','ŋ','ɰ','ʁ','ħ','ʕ','ʔ','ɦ','ʘ','ǂ','ɓ','ɗ','ʄ','ɠ','Ɠ','œ','Œ','ɨ','ʉ','ɘ','ɵ','ə','ɜ','ɞ','ɐ','ɯ','ʊ','ɤ','ʌ','ɔ','ɑ','ɒ','ʍ','ɥ','ʢ','ʡ','ɕ','ʑ','ɺ','ɧ','ɚ','æ̀','ǽ','ὰ','ά','ɔ̀','ɔ́','ʌ̀','ʌ́','ə̀','ə́','ɚ̀','ɚ́','ὲ','έ','͡','ˈ','ˌ','ː','ˑ','̆','‿','̋','́','̄','̀','̏','̌','̂','˥','˦','˧','˨','˩','˩˥','˥˩','̥','̬','̹','̜','̟','̠','̈','̽','̩','̯','˞','̤','̰','̼','̴','̝','̞','̘','̙','̪','̺','̻','̃','̚','❶','❷','❸','❹','❺','❻','❼','❽','❾','❿','⓫','⓬','⓭','⓮','⓯','⓰','⓱','⓲','⓳','⓴','ⅰ','ⅱ','ⅲ','ⅳ','ⅴ','ⅵ','ⅶ','ⅷ','ⅸ','ⅹ','ⅺ','ⅻ','ⓐ','ⓑ','ⓒ','ⓓ','ⓔ','ⓕ','ⓖ','ⓗ','ⓘ','ⓙ','ⓚ','ⓛ','ⓜ','ⓝ','ⓞ','ⓟ','ⓠ','ⓡ','ⓢ','ⓣ','ⓤ','ⓥ','ⓦ','ⓧ','ⓨ','ⓩ','㋐','㋑','㋒','㋓','㋔','㋕','㋖','㋗','㋘','㋙','㋚','㋛','㋜','㋝','㋞','㋟','㋠','㋡','㋢','㋣','㋺','㋩','㋥','㋭','㋬','','','','','','','','','','⁑','⁂','①','②','③','④','⑤','⑥','⑦','⑧','⑨','⑩','⑪','⑫','⑬','⑭','⑮','⑯','⑰','⑱','⑲','⑳','Ⅰ','Ⅱ','Ⅲ','Ⅳ','Ⅴ','Ⅵ','Ⅶ','Ⅷ','Ⅸ','Ⅹ','Ⅺ','㍉','㌔','㌢','㍍','㌘','㌧','㌃','㌶','㍑','㍗','㌍','㌦','㌣','㌫','㍊','㌻','㎜','㎝','㎞','㎎','㎏','㏄','㎡','Ⅻ','','','','','','','','㍻','〝','〟','№','㏍','℡','㊤','㊥','㊦','㊧','㊨','㈱','㈲','㈹','㍾','㍽','㍼','','','','∮','','','','','∟','⊿','','','','❖','☞','俱','𠀋','㐂','丨','丯','丰','亍','仡','份','仿','伃','伋','你','佈','佉','佖','佟','佪','佬','佾','侊','侔','侗','侮','俉','俠','倁','倂','倎','倘','倧','倮','偀','倻','偁','傔','僌','僲','僐','僦','僧','儆','儃','儋','儞','儵','兊','免','兕','兗','㒵','冝','凃','凊','凞','凢','凮','刁','㓛','刓','刕','剉','剗','剡','劓','勈','勉','勌','勐','勖','勛','勤','勰','勻','匀','匇','匜','卑','卡','卣','卽','厓','厝','厲','吒','吧','呍','咜','呫','呴','呿','咈','咖','咡','咩','哆','哿','唎','唫','唵','啐','啞','喁','喆','喎','喝','喭','嗎','嘆','嘈','嘎','嘻','噉','噶','噦','器','噯','噱','噲','嚙','嚞','嚩','嚬','嚳','囉','囊','圊','𡈽','圡','圯','圳','圴','坰','坷','坼','垜','﨏','𡌛','垸','埇','埈','埏','埤','埭','埵','埶','埿','堉','塚','塡','塤','塀','塼','墉','增','墨','墩','𡑮','壒','壎','壔','壚','壠','壩','夌','虁','奝','奭','妋','妒','妤','姃','姒','姝','娓','娣','婧','婭','婷','婾','媄','媞','媧','嫄','𡢽','嬙','嬥','剝','亜','唖','娃','阿','哀','愛','挨','姶','逢','葵','茜','穐','悪','握','渥','旭','葦','芦','鯵','梓','圧','斡','扱','宛','姐','虻','飴','絢','綾','鮎','或','粟','袷','安','庵','按','暗','案','闇','鞍','杏','以','伊','位','依','偉','囲','夷','委','威','尉','惟','意','慰','易','椅','為','畏','異','移','維','緯','胃','萎','衣','謂','違','遺','医','井','亥','域','育','郁','磯','一','壱','溢','逸','稲','茨','芋','鰯','允','印','咽','員','因','姻','引','飲','淫','胤','蔭','院','陰','隠','韻','吋','右','宇','烏','羽','迂','雨','卯','鵜','窺','丑','碓','臼','渦','嘘','唄','欝','蔚','鰻','姥','厩','浦','瓜','閏','噂','云','運','雲','荏','餌','叡','営','嬰','影','映','曳','栄','永','泳','洩','瑛','盈','穎','頴','英','衛','詠','鋭','液','疫','益','駅','悦','謁','越','閲','榎','厭','円','園','堰','奄','宴','延','怨','掩','援','沿','演','炎','焔','煙','燕','猿','縁','艶','苑','薗','遠','鉛','鴛','塩','於','汚','甥','凹','央','奥','往','応','押','旺','横','欧','殴','王','翁','襖','鴬','鴎','黄','岡','沖','荻','億','屋','憶','臆','桶','牡','乙','俺','卸','恩','温','穏','音','下','化','仮','何','伽','価','佳','加','可','嘉','夏','嫁','家','寡','科','暇','果','架','歌','河','火','珂','禍','禾','稼','箇','花','苛','茄','荷','華','菓','蝦','課','嘩','貨','迦','過','霞','蚊','俄','峨','我','牙','画','臥','芽','蛾','賀','雅','餓','駕','介','会','解','回','塊','壊','廻','快','怪','悔','恢','懐','戒','拐','改','魁','晦','械','海','灰','界','皆','絵','芥','蟹','開','階','貝','凱','劾','外','咳','害','崖','慨','概','涯','碍','蓋','街','該','鎧','骸','浬','馨','蛙','垣','柿','蛎','鈎','劃','嚇','各','廓','拡','撹','格','核','殻','獲','確','穫','覚','角','赫','較','郭','閣','隔','革','学','岳','楽','額','顎','掛','笠','樫','橿','梶','鰍','潟','割','喝','恰','括','活','渇','滑','葛','褐','轄','且','鰹','叶','椛','樺','鞄','株','兜','竃','蒲','釜','鎌','噛','鴨','栢','茅','萱','粥','刈','苅','瓦','乾','侃','冠','寒','刊','勘','勧','巻','喚','堪','姦','完','官','寛','干','幹','患','感','慣','憾','換','敢','柑','桓','棺','款','歓','汗','漢','澗','潅','環','甘','監','看','竿','管','簡','緩','缶','翰','肝','艦','莞','観','諌','貫','還','鑑','間','閑','関','陥','韓','館','舘','丸','含','岸','巌','玩','癌','眼','岩','翫','贋','雁','頑','顔','願','企','伎','危','喜','器','基','奇','嬉','寄','岐','希','幾','忌','揮','机','旗','既','期','棋','棄','機','帰','毅','気','汽','畿','祈','季','稀','紀','徽','規','記','貴','起','軌','輝','飢','騎','鬼','亀','偽','儀','妓','宜','戯','技','擬','欺','犠','疑','祇','義','蟻','誼','議','掬','菊','鞠','吉','吃','喫','桔','橘','詰','砧','杵','黍','却','客','脚','虐','逆','丘','久','仇','休','及','吸','宮','弓','急','救','朽','求','汲','泣','灸','球','究','窮','笈','級','糾','給','旧','牛','去','居','巨','拒','拠','挙','渠','虚','許','距','鋸','漁','禦','魚','亨','享','京','供','侠','僑','兇','競','共','凶','協','匡','卿','叫','喬','境','峡','強','彊','怯','恐','恭','挟','教','橋','況','狂','狭','矯','胸','脅','興','蕎','郷','鏡','響','饗','驚','仰','凝','尭','暁','業','局','曲','極','玉','桐','粁','僅','勤','均','巾','錦','斤','欣','欽','琴','禁','禽','筋','緊','芹','菌','衿','襟','謹','近','金','吟','銀','九','倶','句','区','狗','玖','矩','苦','躯','駆','駈','駒','具','愚','虞','喰','空','偶','寓','遇','隅','串','櫛','釧','屑','屈','掘','窟','沓','靴','轡','窪','熊','隈','粂','栗','繰','桑','鍬','勲','君','薫','訓','群','軍','郡','卦','袈','祁','係','傾','刑','兄','啓','圭','珪','型','契','形','径','恵','慶','慧','憩','掲','携','敬','景','桂','渓','畦','稽','系','経','継','繋','罫','茎','荊','蛍','計','詣','警','軽','頚','鶏','芸','迎','鯨','劇','戟','撃','激','隙','桁','傑','欠','決','潔','穴','結','血','訣','月','件','倹','倦','健','兼','券','剣','喧','圏','堅','嫌','建','憲','懸','拳','捲','検','権','牽','犬','献','研','硯','絹','県','肩','見','謙','賢','軒','遣','鍵','険','顕','験','鹸','元','原','厳','幻','弦','減','源','玄','現','絃','舷','言','諺','限','乎','個','古','呼','固','姑','孤','己','庫','弧','戸','故','枯','湖','狐','糊','袴','股','胡','菰','虎','誇','跨','鈷','雇','顧','鼓','五','互','伍','午','呉','吾','娯','後','御','悟','梧','檎','瑚','碁','語','誤','護','醐','乞','鯉','交','佼','侯','候','倖','光','公','功','効','勾','厚','口','向','后','喉','坑','垢','好','孔','孝','宏','工','巧','巷','幸','広','庚','康','弘','恒','慌','抗','拘','控','攻','昂','晃','更','杭','校','梗','構','江','洪','浩','港','溝','甲','皇','硬','稿','糠','紅','紘','絞','綱','耕','考','肯','肱','腔','膏','航','荒','行','衡','講','貢','購','郊','酵','鉱','砿','鋼','閤','降','項','香','高','鴻','剛','劫','号','合','壕','拷','濠','豪','轟','麹','克','刻','告','国','穀','酷','鵠','黒','獄','漉','腰','甑','忽','惚','骨','狛','込','此','頃','今','困','坤','墾','婚','恨','懇','昏','昆','根','梱','混','痕','紺','艮','魂','些','佐','叉','唆','嵯','左','差','査','沙','瑳','砂','詐','鎖','裟','坐','座','挫','債','催','再','最','哉','塞','妻','宰','彩','才','採','栽','歳','済','災','采','犀','砕','砦','祭','斎','細','菜','裁','載','際','剤','在','材','罪','財','冴','坂','阪','堺','榊','肴','咲','崎','埼','碕','鷺','作','削','咋','搾','昨','朔','柵','窄','策','索','錯','桜','鮭','笹','匙','冊','刷','察','拶','撮','擦','札','殺','薩','雑','皐','鯖','捌','錆','鮫','皿','晒','三','傘','参','山','惨','撒','散','桟','燦','珊','産','算','纂','蚕','讃','賛','酸','餐','斬','暫','残','仕','仔','伺','使','刺','司','史','嗣','四','士','始','姉','姿','子','屍','市','師','志','思','指','支','孜','斯','施','旨','枝','止','死','氏','獅','祉','私','糸','紙','紫','肢','脂','至','視','詞','詩','試','誌','諮','資','賜','雌','飼','歯','事','似','侍','児','字','寺','慈','持','時','次','滋','治','爾','璽','痔','磁','示','而','耳','自','蒔','辞','汐','鹿','式','識','鴫','竺','軸','宍','雫','七','叱','執','失','嫉','室','悉','湿','漆','疾','質','実','蔀','篠','偲','柴','芝','屡','蕊','縞','舎','写','射','捨','赦','斜','煮','社','紗','者','謝','車','遮','蛇','邪','借','勺','尺','杓','灼','爵','酌','釈','錫','若','寂','弱','惹','主','取','守','手','朱','殊','狩','珠','種','腫','趣','酒','首','儒','受','呪','寿','授','樹','綬','需','囚','収','周','宗','就','州','修','愁','拾','洲','秀','秋','終','繍','習','臭','舟','蒐','衆','襲','讐','蹴','輯','週','酋','酬','集','醜','什','住','充','十','従','戎','柔','汁','渋','獣','縦','重','銃','叔','夙','宿','淑','祝','縮','粛','塾','熟','出','術','述','俊','峻','春','瞬','竣','舜','駿','准','循','旬','楯','殉','淳','準','潤','盾','純','巡','遵','醇','順','処','初','所','暑','曙','渚','庶','緒','署','書','薯','藷','諸','助','叙','女','序','徐','恕','鋤','除','傷','償','勝','匠','升','召','哨','商','唱','嘗','奨','妾','娼','宵','将','小','少','尚','庄','床','廠','彰','承','抄','招','掌','捷','昇','昌','昭','晶','松','梢','樟','樵','沼','消','渉','湘','焼','焦','照','症','省','硝','礁','祥','称','章','笑','粧','紹','肖','菖','蒋','蕉','衝','裳','訟','証','詔','詳','象','賞','醤','鉦','鍾','鐘','障','鞘','上','丈','丞','乗','冗','剰','城','場','壌','嬢','常','情','擾','条','杖','浄','状','畳','穣','蒸','譲','醸','錠','嘱','埴','飾','拭','植','殖','燭','織','職','色','触','食','蝕','辱','尻','伸','信','侵','唇','娠','寝','審','心','慎','振','新','晋','森','榛','浸','深','申','疹','真','神','秦','紳','臣','芯','薪','親','診','身','辛','進','針','震','人','仁','刃','塵','壬','尋','甚','尽','腎','訊','迅','陣','靭','笥','諏','須','酢','図','厨','逗','吹','垂','帥','推','水','炊','睡','粋','翠','衰','遂','酔','錐','錘','随','瑞','髄','崇','嵩','数','枢','趨','雛','据','杉','椙','菅','頗','雀','裾','澄','摺','寸','世','瀬','畝','是','凄','制','勢','姓','征','性','成','政','整','星','晴','棲','栖','正','清','牲','生','盛','精','聖','声','製','西','誠','誓','請','逝','醒','青','静','斉','税','脆','隻','席','惜','戚','斥','昔','析','石','積','籍','績','脊','責','赤','跡','蹟','碩','切','拙','接','摂','折','設','窃','節','説','雪','絶','舌','蝉','仙','先','千','占','宣','専','尖','川','戦','扇','撰','栓','栴','泉','浅','洗','染','潜','煎','煽','旋','穿','箭','線','繊','羨','腺','舛','船','薦','詮','賎','践','選','遷','銭','銑','閃','鮮','前','善','漸','然','全','禅','繕','膳','糎','噌','塑','岨','措','曾','曽','楚','狙','疏','疎','礎','祖','租','粗','素','組','蘇','訴','阻','遡','鼠','僧','創','双','叢','倉','喪','壮','奏','爽','宋','層','匝','惣','想','捜','掃','挿','掻','操','早','曹','巣','槍','槽','漕','燥','争','痩','相','窓','糟','総','綜','聡','草','荘','葬','蒼','藻','装','走','送','遭','鎗','霜','騒','像','増','憎','臓','蔵','贈','造','促','側','則','即','息','捉','束','測','足','速','俗','属','賊','族','続','卒','袖','其','揃','存','孫','尊','損','村','遜','他','多','太','汰','詑','唾','堕','妥','惰','打','柁','舵','楕','陀','駄','騨','体','堆','対','耐','岱','帯','待','怠','態','戴','替','泰','滞','胎','腿','苔','袋','貸','退','逮','隊','黛','鯛','代','台','大','第','醍','題','鷹','滝','瀧','卓','啄','宅','托','択','拓','沢','濯','琢','託','鐸','濁','諾','茸','凧','蛸','只','叩','但','達','辰','奪','脱','巽','竪','辿','棚','谷','狸','鱈','樽','誰','丹','単','嘆','坦','担','探','旦','歎','淡','湛','炭','短','端','箪','綻','耽','胆','蛋','誕','鍛','団','壇','弾','断','暖','檀','段','男','談','値','知','地','弛','恥','智','池','痴','稚','置','致','蜘','遅','馳','築','畜','竹','筑','蓄','逐','秩','窒','茶','嫡','着','中','仲','宙','忠','抽','昼','柱','注','虫','衷','註','酎','鋳','駐','樗','瀦','猪','苧','著','貯','丁','兆','凋','喋','寵','帖','帳','庁','弔','張','彫','徴','懲','挑','暢','朝','潮','牒','町','眺','聴','脹','腸','蝶','調','諜','超','跳','銚','長','頂','鳥','勅','捗','直','朕','沈','珍','賃','鎮','陳','津','墜','椎','槌','追','鎚','痛','通','塚','栂','掴','槻','佃','漬','柘','辻','蔦','綴','鍔','椿','潰','坪','壷','嬬','紬','爪','吊','釣','鶴','亭','低','停','偵','剃','貞','呈','堤','定','帝','底','庭','廷','弟','悌','抵','挺','提','梯','汀','碇','禎','程','締','艇','訂','諦','蹄','逓','邸','鄭','釘','鼎','泥','摘','擢','敵','滴','的','笛','適','鏑','溺','哲','徹','撤','轍','迭','鉄','典','填','天','展','店','添','纏','甜','貼','転','顛','点','伝','殿','澱','田','電','兎','吐','堵','塗','妬','屠','徒','斗','杜','渡','登','菟','賭','途','都','鍍','砥','砺','努','度','土','奴','怒','倒','党','冬','凍','刀','唐','塔','塘','套','宕','島','嶋','悼','投','搭','東','桃','梼','棟','盗','淘','湯','涛','灯','燈','当','痘','祷','等','答','筒','糖','統','到','董','蕩','藤','討','謄','豆','踏','逃','透','鐙','陶','頭','騰','闘','働','動','同','堂','導','憧','撞','洞','瞳','童','胴','萄','道','銅','峠','鴇','匿','得','徳','涜','特','督','禿','篤','毒','独','読','栃','橡','凸','突','椴','届','鳶','苫','寅','酉','瀞','噸','屯','惇','敦','沌','豚','遁','頓','呑','曇','鈍','奈','那','内','乍','凪','薙','謎','灘','捺','鍋','楢','馴','縄','畷','南','楠','軟','難','汝','二','尼','弐','迩','匂','賑','肉','虹','廿','日','乳','入','如','尿','韮','任','妊','忍','認','濡','禰','祢','寧','葱','猫','熱','年','念','捻','撚','燃','粘','乃','廼','之','埜','嚢','悩','濃','納','能','脳','膿','農','覗','蚤','巴','把','播','覇','杷','波','派','琶','破','婆','罵','芭','馬','俳','廃','拝','排','敗','杯','盃','牌','背','肺','輩','配','倍','培','媒','梅','楳','煤','狽','買','売','賠','陪','這','蝿','秤','矧','萩','伯','剥','博','拍','柏','泊','白','箔','粕','舶','薄','迫','曝','漠','爆','縛','莫','駁','麦','函','箱','硲','箸','肇','筈','櫨','幡','肌','畑','畠','八','鉢','溌','発','醗','髪','伐','罰','抜','筏','閥','鳩','噺','塙','蛤','隼','伴','判','半','反','叛','帆','搬','斑','板','氾','汎','版','犯','班','畔','繁','般','藩','販','範','釆','煩','頒','飯','挽','晩','番','盤','磐','蕃','蛮','匪','卑','否','妃','庇','彼','悲','扉','批','披','斐','比','泌','疲','皮','碑','秘','緋','罷','肥','被','誹','費','避','非','飛','樋','簸','備','尾','微','枇','毘','琵','眉','美','鼻','柊','稗','匹','疋','髭','彦','膝','菱','肘','弼','必','畢','筆','逼','桧','姫','媛','紐','百','謬','俵','彪','標','氷','漂','瓢','票','表','評','豹','廟','描','病','秒','苗','錨','鋲','蒜','蛭','鰭','品','彬','斌','浜','瀕','貧','賓','頻','敏','瓶','不','付','埠','夫','婦','富','冨','布','府','怖','扶','敷','斧','普','浮','父','符','腐','膚','芙','譜','負','賦','赴','阜','附','侮','撫','武','舞','葡','蕪','部','封','楓','風','葺','蕗','伏','副','復','幅','服','福','腹','複','覆','淵','弗','払','沸','仏','物','鮒','分','吻','噴','墳','憤','扮','焚','奮','粉','糞','紛','雰','文','聞','丙','併','兵','塀','幣','平','弊','柄','並','蔽','閉','陛','米','頁','僻','壁','癖','碧','別','瞥','蔑','箆','偏','変','片','篇','編','辺','返','遍','便','勉','娩','弁','鞭','保','舗','鋪','圃','捕','歩','甫','補','輔','穂','募','墓','慕','戊','暮','母','簿','菩','倣','俸','包','呆','報','奉','宝','峰','峯','崩','庖','抱','捧','放','方','朋','法','泡','烹','砲','縫','胞','芳','萌','蓬','蜂','褒','訪','豊','邦','鋒','飽','鳳','鵬','乏','亡','傍','剖','坊','妨','帽','忘','忙','房','暴','望','某','棒','冒','紡','肪','膨','謀','貌','貿','鉾','防','吠','頬','北','僕','卜','墨','撲','朴','牧','睦','穆','釦','勃','没','殆','堀','幌','奔','本','翻','凡','盆','摩','磨','魔','麻','埋','妹','昧','枚','毎','哩','槙','幕','膜','枕','鮪','柾','鱒','桝','亦','俣','又','抹','末','沫','迄','侭','繭','麿','万','慢','満','漫','蔓','味','未','魅','巳','箕','岬','密','蜜','湊','蓑','稔','脈','妙','粍','民','眠','務','夢','無','牟','矛','霧','鵡','椋','婿','娘','冥','名','命','明','盟','迷','銘','鳴','姪','牝','滅','免','棉','綿','緬','面','麺','摸','模','茂','妄','孟','毛','猛','盲','網','耗','蒙','儲','木','黙','目','杢','勿','餅','尤','戻','籾','貰','問','悶','紋','門','匁','也','冶','夜','爺','耶','野','弥','矢','厄','役','約','薬','訳','躍','靖','柳','薮','鑓','愉','愈','油','癒','諭','輸','唯','佑','優','勇','友','宥','幽','悠','憂','揖','有','柚','湧','涌','猶','猷','由','祐','裕','誘','遊','邑','郵','雄','融','夕','予','余','与','誉','輿','預','傭','幼','妖','容','庸','揚','揺','擁','曜','楊','様','洋','溶','熔','用','窯','羊','耀','葉','蓉','要','謡','踊','遥','陽','養','慾','抑','欲','沃','浴','翌','翼','淀','羅','螺','裸','来','莱','頼','雷','洛','絡','落','酪','乱','卵','嵐','欄','濫','藍','蘭','覧','利','吏','履','李','梨','理','璃','痢','裏','裡','里','離','陸','律','率','立','葎','掠','略','劉','流','溜','琉','留','硫','粒','隆','竜','龍','侶','慮','旅','虜','了','亮','僚','両','凌','寮','料','梁','涼','猟','療','瞭','稜','糧','良','諒','遼','量','陵','領','力','緑','倫','厘','林','淋','燐','琳','臨','輪','隣','鱗','麟','瑠','塁','涙','累','類','令','伶','例','冷','励','嶺','怜','玲','礼','苓','鈴','隷','零','霊','麗','齢','暦','歴','列','劣','烈','裂','廉','恋','憐','漣','煉','簾','練','聯','蓮','連','錬','呂','魯','櫓','炉','賂','路','露','労','婁','廊','弄','朗','楼','榔','浪','漏','牢','狼','篭','老','聾','蝋','郎','六','麓','禄','肋','録','論','倭','和','話','歪','賄','脇','惑','枠','鷲','亙','亘','鰐','詫','藁','蕨','椀','湾','碗','腕','𠮟','孁','孖','孽','宓','寘','寬','尒','尞','尣','尫','㞍','屢','層','屮','𡚴','屺','岏','岟','岣','岪','岺','峋','峐','峒','峴','𡸴','㟢','崍','崧','﨑','嵆','嵇','嵓','嵊','嵭','嶁','嶠','嶤','嶧','嶸','巋','吞','弌','丐','丕','个','丱','丶','丼','丿','乂','乖','乘','亂','亅','豫','亊','舒','弍','于','亞','亟','亠','亢','亰','亳','亶','从','仍','仄','仆','仂','仗','仞','仭','仟','价','伉','佚','估','佛','佝','佗','佇','佶','侈','侏','侘','佻','佩','佰','侑','佯','來','侖','儘','俔','俟','俎','俘','俛','俑','俚','俐','俤','俥','倚','倨','倔','倪','倥','倅','伜','俶','倡','倩','倬','俾','俯','們','倆','偃','假','會','偕','偐','偈','做','偖','偬','偸','傀','傚','傅','傴','傲','僉','僊','傳','僂','僖','僞','僥','僭','僣','僮','價','僵','儉','儁','儂','儖','儕','儔','儚','儡','儺','儷','儼','儻','儿','兀','兒','兌','兔','兢','竸','兩','兪','兮','冀','冂','囘','册','冉','冏','冑','冓','冕','冖','冤','冦','冢','冩','冪','冫','决','冱','冲','冰','况','冽','凅','凉','凛','几','處','凩','凭','凰','凵','凾','刄','刋','刔','刎','刧','刪','刮','刳','刹','剏','剄','剋','剌','剞','剔','剪','剴','剩','剳','剿','剽','劍','劔','劒','剱','劈','劑','辨','辧','劬','劭','劼','劵','勁','勍','勗','勞','勣','勦','飭','勠','勳','勵','勸','勹','匆','匈','甸','匍','匐','匏','匕','匚','匣','匯','匱','匳','匸','區','卆','卅','丗','卉','卍','凖','卞','卩','卮','夘','卻','卷','厂','厖','厠','厦','厥','厮','厰','厶','參','簒','雙','叟','曼','燮','叮','叨','叭','叺','吁','吽','呀','听','吭','吼','吮','吶','吩','吝','呎','咏','呵','咎','呟','呱','呷','呰','咒','呻','咀','呶','咄','咐','咆','哇','咢','咸','咥','咬','哄','哈','咨','咫','哂','咤','咾','咼','哘','哥','哦','唏','唔','哽','哮','哭','哺','哢','唹','啀','啣','啌','售','啜','啅','啖','啗','唸','唳','啝','喙','喀','咯','喊','喟','啻','啾','喘','喞','單','啼','喃','喩','喇','喨','嗚','嗅','嗟','嗄','嗜','嗤','嗔','嘔','嗷','嘖','嗾','嗽','嘛','嗹','噎','噐','營','嘴','嘶','嘲','嘸','噫','噤','嘯','噬','噪','嚆','嚀','嚊','嚠','嚔','嚏','嚥','嚮','嚶','嚴','囂','嚼','囁','囃','囀','囈','囎','囑','囓','囗','囮','囹','圀','囿','圄','圉','圈','國','圍','圓','團','圖','嗇','圜','圦','圷','圸','坎','圻','址','坏','坩','埀','垈','坡','坿','垉','垓','垠','垳','垤','垪','垰','埃','埆','埔','埒','埓','堊','埖','埣','堋','堙','堝','塲','堡','塢','塋','塰','毀','塒','堽','塹','墅','墹','墟','墫','墺','壞','墻','墸','墮','壅','壓','壑','壗','壙','壘','壥','壜','壤','壟','壯','壺','壹','壻','壼','壽','夂','夊','夐','夛','梦','夥','夬','夭','夲','夸','夾','竒','奕','奐','奎','奚','奘','奢','奠','奧','奬','奩','奸','妁','妝','佞','侫','妣','妲','姆','姨','姜','妍','姙','姚','娥','娟','娑','娜','娉','娚','婀','婬','婉','娵','娶','婢','婪','媚','媼','媾','嫋','嫂','媽','嫣','嫗','嫦','嫩','嫖','嫺','嫻','嬌','嬋','嬖','嬲','嫐','嬪','嬶','嬾','孃','孅','孀','孑','孕','孚','孛','孥','孩','孰','孳','孵','學','斈','孺','宀','它','宦','宸','寃','寇','寉','寔','寐','寤','實','寢','寞','寥','寫','寰','寶','寳','尅','將','專','對','尓','尠','尢','尨','尸','尹','屁','屆','屎','屓','屐','屏','孱','屬','屮','乢','屶','屹','岌','岑','岔','妛','岫','岻','岶','岼','岷','峅','岾','峇','峙','峩','峽','峺','峭','嶌','峪','崋','崕','崗','嵜','崟','崛','崑','崔','崢','崚','崙','崘','嵌','嵒','嵎','嵋','嵬','嵳','嵶','嶇','嶄','嶂','嶢','嶝','嶬','嶮','嶽','嶐','嶷','嶼','巉','巍','巓','巒','巖','巛','巫','已','巵','帋','帚','帙','帑','帛','帶','帷','幄','幃','幀','幎','幗','幔','幟','幢','幤','幇','幵','并','幺','麼','广','庠','廁','廂','廈','廐','廏','廖','廣','廝','廚','廛','廢','廡','廨','廩','廬','廱','廳','廰','廴','廸','廾','弃','弉','彝','彜','弋','弑','弖','弩','弭','弸','彁','彈','彌','彎','弯','彑','彖','彗','彙','彡','彭','彳','彷','徃','徂','彿','徊','很','徑','徇','從','徙','徘','徠','徨','徭','徼','忖','忻','忤','忸','忱','忝','悳','忿','怡','恠','怙','怐','怩','怎','怱','怛','怕','怫','怦','怏','怺','恚','恁','恪','恷','恟','恊','恆','恍','恣','恃','恤','恂','恬','恫','恙','悁','悍','惧','悃','悚','悄','悛','悖','悗','悒','悧','悋','惡','悸','惠','惓','悴','忰','悽','惆','悵','惘','慍','愕','愆','惶','惷','愀','惴','惺','愃','愡','惻','惱','愍','愎','慇','愾','愨','愧','慊','愿','愼','愬','愴','愽','慂','慄','慳','慷','慘','慙','慚','慫','慴','慯','慥','慱','慟','慝','慓','慵','憙','憖','憇','憬','憔','憚','憊','憑','憫','憮','懌','懊','應','懷','懈','懃','懆','憺','懋','罹','懍','懦','懣','懶','懺','懴','懿','懽','懼','懾','戀','戈','戉','戍','戌','戔','戛','戞','戡','截','戮','戰','戲','戳','扁','扎','扞','扣','扛','扠','扨','扼','抂','抉','找','抒','抓','抖','拔','抃','抔','拗','拑','抻','拏','拿','拆','擔','拈','拜','拌','拊','拂','拇','抛','拉','挌','拮','拱','挧','挂','挈','拯','拵','捐','挾','捍','搜','捏','掖','掎','掀','掫','捶','掣','掏','掉','掟','掵','捫','捩','掾','揩','揀','揆','揣','揉','插','揶','揄','搖','搴','搆','搓','搦','搶','攝','搗','搨','搏','摧','摯','摶','摎','攪','撕','撓','撥','撩','撈','撼','據','擒','擅','擇','撻','擘','擂','擱','擧','舉','擠','擡','抬','擣','擯','攬','擶','擴','擲','擺','攀','擽','攘','攜','攅','攤','攣','攫','攴','攵','攷','收','攸','畋','效','敖','敕','敍','敘','敞','敝','敲','數','斂','斃','變','斛','斟','斫','斷','旃','旆','旁','旄','旌','旒','旛','旙','无','旡','旱','杲','昊','昃','旻','杳','昵','昶','昴','昜','晏','晄','晉','晁','晞','晝','晤','晧','晨','晟','晢','晰','暃','暈','暎','暉','暄','暘','暝','曁','暹','曉','暾','暼','曄','暸','曖','曚','曠','昿','曦','曩','曰','曵','曷','朏','朖','朞','朦','朧','霸','朮','朿','朶','杁','朸','朷','杆','杞','杠','杙','杣','杤','枉','杰','枩','杼','杪','枌','枋','枦','枡','枅','枷','柯','枴','柬','枳','柩','枸','柤','柞','柝','柢','柮','枹','柎','柆','柧','檜','栞','框','栩','桀','桍','栲','桎','梳','栫','桙','档','桷','桿','梟','梏','梭','梔','條','梛','梃','檮','梹','桴','梵','梠','梺','椏','梍','桾','椁','棊','椈','棘','椢','椦','棡','椌','棍','棔','棧','棕','椶','椒','椄','棗','棣','椥','棹','棠','棯','椨','椪','椚','椣','椡','棆','楹','楷','楜','楸','楫','楔','楾','楮','椹','楴','椽','楙','椰','楡','楞','楝','榁','楪','榲','榮','槐','榿','槁','槓','榾','槎','寨','槊','槝','榻','槃','榧','樮','榑','榠','榜','榕','榴','槞','槨','樂','樛','槿','權','槹','槲','槧','樅','榱','樞','槭','樔','槫','樊','樒','櫁','樣','樓','橄','樌','橲','樶','橸','橇','橢','橙','橦','橈','樸','樢','檐','檍','檠','檄','檢','檣','檗','蘗','檻','櫃','櫂','檸','檳','檬','櫞','櫑','櫟','檪','櫚','櫪','櫻','欅','蘖','櫺','欒','欖','鬱','欟','欸','欷','盜','欹','飮','歇','歃','歉','歐','歙','歔','歛','歟','歡','歸','歹','歿','殀','殄','殃','殍','殘','殕','殞','殤','殪','殫','殯','殲','殱','殳','殷','殼','毆','毋','毓','毟','毬','毫','毳','毯','麾','氈','氓','气','氛','氤','氣','汞','汕','汢','汪','沂','沍','沚','沁','沛','汾','汨','汳','沒','沐','泄','泱','泓','沽','泗','泅','泝','沮','沱','沾','沺','泛','泯','泙','泪','洟','衍','洶','洫','洽','洸','洙','洵','洳','洒','洌','浣','涓','浤','浚','浹','浙','涎','涕','濤','涅','淹','渕','渊','涵','淇','淦','涸','淆','淬','淞','淌','淨','淒','淅','淺','淙','淤','淕','淪','淮','渭','湮','渮','渙','湲','湟','渾','渣','湫','渫','湶','湍','渟','湃','渺','湎','渤','滿','渝','游','溂','溪','溘','滉','溷','滓','溽','溯','滄','溲','滔','滕','溏','溥','滂','溟','潁','漑','灌','滬','滸','滾','漿','滲','漱','滯','漲','滌','漾','漓','滷','澆','潺','潸','澁','澀','潯','潛','濳','潭','澂','潼','潘','澎','澑','濂','潦','澳','澣','澡','澤','澹','濆','澪','濟','濕','濬','濔','濘','濱','濮','濛','瀉','瀋','濺','瀑','瀁','瀏','濾','瀛','瀚','潴','瀝','瀘','瀟','瀰','瀾','瀲','灑','灣','炙','炒','炯','烱','炬','炸','炳','炮','烟','烋','烝','烙','焉','烽','焜','焙','煥','煕','熈','煦','煢','煌','煖','煬','熏','燻','熄','熕','熨','熬','燗','熹','熾','燒','燉','燔','燎','燠','燬','燧','燵','燼','燹','燿','爍','爐','爛','爨','爭','爬','爰','爲','爻','爼','爿','牀','牆','牋','牘','牴','牾','犂','犁','犇','犒','犖','犢','犧','犹','犲','狃','狆','狄','狎','狒','狢','狠','狡','狹','狷','倏','猗','猊','猜','猖','猝','猴','猯','猩','猥','猾','獎','獏','默','獗','獪','獨','獰','獸','獵','獻','獺','珈','玳','珎','玻','珀','珥','珮','珞','璢','琅','瑯','琥','珸','琲','琺','瑕','琿','瑟','瑙','瑁','瑜','瑩','瑰','瑣','瑪','瑶','瑾','璋','璞','璧','瓊','瓏','瓔','珱','瓠','瓣','瓧','瓩','瓮','瓲','瓰','瓱','瓸','瓷','甄','甃','甅','甌','甎','甍','甕','甓','甞','甦','甬','甼','畄','畍','畊','畉','畛','畆','畚','畩','畤','畧','畫','畭','畸','當','疆','疇','畴','疊','疉','疂','疔','疚','疝','疥','疣','痂','疳','痃','疵','疽','疸','疼','疱','痍','痊','痒','痙','痣','痞','痾','痿','痼','瘁','痰','痺','痲','痳','瘋','瘍','瘉','瘟','瘧','瘠','瘡','瘢','瘤','瘴','瘰','瘻','癇','癈','癆','癜','癘','癡','癢','癨','癩','癪','癧','癬','癰','癲','癶','癸','發','皀','皃','皈','皋','皎','皖','皓','皙','皚','皰','皴','皸','皹','皺','盂','盍','盖','盒','盞','盡','盥','盧','盪','蘯','盻','眈','眇','眄','眩','眤','眞','眥','眦','眛','眷','眸','睇','睚','睨','睫','睛','睥','睿','睾','睹','瞎','瞋','瞑','瞠','瞞','瞰','瞶','瞹','瞿','瞼','瞽','瞻','矇','矍','矗','矚','矜','矣','矮','矼','砌','砒','礦','砠','礪','硅','碎','硴','碆','硼','碚','碌','碣','碵','碪','碯','磑','磆','磋','磔','碾','碼','磅','磊','磬','磧','磚','磽','磴','礇','礒','礑','礙','礬','礫','祀','祠','祗','祟','祚','祕','祓','祺','祿','禊','禝','禧','齋','禪','禮','禳','禹','禺','秉','秕','秧','秬','秡','秣','稈','稍','稘','稙','稠','稟','禀','稱','稻','稾','稷','穃','穗','穉','穡','穢','穩','龝','穰','穹','穽','窈','窗','窕','窘','窖','窩','竈','窰','窶','竅','竄','窿','邃','竇','竊','竍','竏','竕','竓','站','竚','竝','竡','竢','竦','竭','竰','笂','笏','笊','笆','笳','笘','笙','笞','笵','笨','笶','筐','筺','笄','筍','笋','筌','筅','筵','筥','筴','筧','筰','筱','筬','筮','箝','箘','箟','箍','箜','箚','箋','箒','箏','筝','箙','篋','篁','篌','篏','箴','篆','篝','篩','簑','簔','篦','篥','籠','簀','簇','簓','篳','篷','簗','簍','篶','簣','簧','簪','簟','簷','簫','簽','籌','籃','籔','籏','籀','籐','籘','籟','籤','籖','籥','籬','籵','粃','粐','粤','粭','粢','粫','粡','粨','粳','粲','粱','粮','粹','粽','糀','糅','糂','糘','糒','糜','糢','鬻','糯','糲','糴','糶','糺','紆','紂','紜','紕','紊','絅','絋','紮','紲','紿','紵','絆','絳','絖','絎','絲','絨','絮','絏','絣','經','綉','絛','綏','絽','綛','綺','綮','綣','綵','緇','綽','綫','總','綢','綯','緜','綸','綟','綰','緘','緝','緤','緞','緻','緲','緡','縅','縊','縣','縡','縒','縱','縟','縉','縋','縢','繆','繦','縻','縵','縹','繃','縷','縲','縺','繧','繝','繖','繞','繙','繚','繹','繪','繩','繼','繻','纃','緕','繽','辮','繿','纈','纉','續','纒','纐','纓','纔','纖','纎','纛','纜','缸','缺','罅','罌','罍','罎','罐','网','罕','罔','罘','罟','罠','罨','罩','罧','罸','羂','羆','羃','羈','羇','羌','羔','羞','羝','羚','羣','羯','羲','羹','羮','羶','羸','譱','翅','翆','翊','翕','翔','翡','翦','翩','翳','翹','飜','耆','耄','耋','耒','耘','耙','耜','耡','耨','耿','耻','聊','聆','聒','聘','聚','聟','聢','聨','聳','聲','聰','聶','聹','聽','聿','肄','肆','肅','肛','肓','肚','肭','冐','肬','胛','胥','胙','胝','胄','胚','胖','脉','胯','胱','脛','脩','脣','脯','腋','隋','腆','脾','腓','腑','胼','腱','腮','腥','腦','腴','膃','膈','膊','膀','膂','膠','膕','膤','膣','腟','膓','膩','膰','膵','膾','膸','膽','臀','臂','膺','臉','臍','臑','臙','臘','臈','臚','臟','臠','臧','臺','臻','臾','舁','舂','舅','與','舊','舍','舐','舖','舩','舫','舸','舳','艀','艙','艘','艝','艚','艟','艤','艢','艨','艪','艫','舮','艱','艷','艸','艾','芍','芒','芫','芟','芻','芬','苡','苣','苟','苒','苴','苳','苺','莓','范','苻','苹','苞','茆','苜','茉','苙','茵','茴','茖','茲','茱','荀','茹','荐','荅','茯','茫','茗','茘','莅','莚','莪','莟','莢','莖','茣','莎','莇','莊','荼','莵','荳','荵','莠','莉','莨','菴','萓','菫','菎','菽','萃','菘','萋','菁','菷','萇','菠','菲','萍','萢','萠','莽','萸','蔆','菻','葭','萪','萼','蕚','蒄','葷','葫','蒭','葮','蒂','葩','葆','萬','葯','葹','萵','蓊','葢','蒹','蒿','蒟','蓙','蓍','蒻','蓚','蓐','蓁','蓆','蓖','蒡','蔡','蓿','蓴','蔗','蔘','蔬','蔟','蔕','蔔','蓼','蕀','蕣','蕘','蕈','蕁','蘂','蕋','蕕','薀','薤','薈','薑','薊','薨','蕭','薔','薛','藪','薇','薜','蕷','蕾','薐','藉','薺','藏','薹','藐','藕','藝','藥','藜','藹','蘊','蘓','蘋','藾','藺','蘆','蘢','蘚','蘰','蘿','虍','乕','虔','號','虧','虱','蚓','蚣','蚩','蚪','蚋','蚌','蚶','蚯','蛄','蛆','蚰','蛉','蠣','蚫','蛔','蛞','蛩','蛬','蛟','蛛','蛯','蜒','蜆','蜈','蜀','蜃','蛻','蜑','蜉','蜍','蛹','蜊','蜴','蜿','蜷','蜻','蜥','蜩','蜚','蝠','蝟','蝸','蝌','蝎','蝴','蝗','蝨','蝮','蝙','蝓','蝣','蝪','蠅','螢','螟','螂','螯','蟋','螽','蟀','蟐','雖','螫','蟄','螳','蟇','蟆','螻','蟯','蟲','蟠','蠏','蠍','蟾','蟶','蟷','蠎','蟒','蠑','蠖','蠕','蠢','蠡','蠱','蠶','蠹','蠧','蠻','衄','衂','衒','衙','衞','衢','衫','袁','衾','袞','衵','衽','袵','衲','袂','袗','袒','袮','袙','袢','袍','袤','袰','袿','袱','裃','裄','裔','裘','裙','裝','裹','褂','裼','裴','裨','裲','褄','褌','褊','褓','襃','褞','褥','褪','褫','襁','襄','褻','褶','褸','襌','褝','襠','襞','襦','襤','襭','襪','襯','襴','襷','襾','覃','覈','覊','覓','覘','覡','覩','覦','覬','覯','覲','覺','覽','覿','觀','觚','觜','觝','觧','觴','觸','訃','訖','訐','訌','訛','訝','訥','訶','詁','詛','詒','詆','詈','詼','詭','詬','詢','誅','誂','誄','誨','誡','誑','誥','誦','誚','誣','諄','諍','諂','諚','諫','諳','諧','諤','諱','謔','諠','諢','諷','諞','諛','謌','謇','謚','諡','謖','謐','謗','謠','謳','鞫','謦','謫','謾','謨','譁','譌','譏','譎','證','譖','譛','譚','譫','譟','譬','譯','譴','譽','讀','讌','讎','讒','讓','讖','讙','讚','谺','豁','谿','豈','豌','豎','豐','豕','豢','豬','豸','豺','貂','貉','貅','貊','貍','貎','貔','豼','貘','戝','貭','貪','貽','貲','貳','貮','貶','賈','賁','賤','賣','賚','賽','賺','賻','贄','贅','贊','贇','贏','贍','贐','齎','贓','賍','贔','贖','赧','赭','赱','赳','趁','趙','跂','趾','趺','跏','跚','跖','跌','跛','跋','跪','跫','跟','跣','跼','踈','踉','跿','踝','踞','踐','踟','蹂','踵','踰','踴','蹊','蹇','蹉','蹌','蹐','蹈','蹙','蹤','蹠','踪','蹣','蹕','蹶','蹲','蹼','躁','躇','躅','躄','躋','躊','躓','躑','躔','躙','躪','躡','躬','躰','軆','躱','躾','軅','軈','軋','軛','軣','軼','軻','軫','軾','輊','輅','輕','輒','輙','輓','輜','輟','輛','輌','輦','輳','輻','輹','轅','轂','輾','轌','轉','轆','轎','轗','轜','轢','轣','轤','辜','辟','辣','辭','辯','辷','迚','迥','迢','迪','迯','邇','迴','逅','迹','迺','逑','逕','逡','逍','逞','逖','逋','逧','逶','逵','逹','迸','遏','遐','遑','遒','逎','遉','逾','遖','遘','遞','遨','遯','遶','隨','遲','邂','遽','邁','邀','邊','邉','邏','邨','邯','邱','邵','郢','郤','扈','郛','鄂','鄒','鄙','鄲','鄰','酊','酖','酘','酣','酥','酩','酳','酲','醋','醉','醂','醢','醫','醯','醪','醵','醴','醺','釀','釁','釉','釋','釐','釖','釟','釡','釛','釼','釵','釶','鈞','釿','鈔','鈬','鈕','鈑','鉞','鉗','鉅','鉉','鉤','鉈','銕','鈿','鉋','鉐','銜','銖','銓','銛','鉚','鋏','銹','銷','鋩','錏','鋺','鍄','錮','錙','錢','錚','錣','錺','錵','錻','鍜','鍠','鍼','鍮','鍖','鎰','鎬','鎭','鎔','鎹','鏖','鏗','鏨','鏥','鏘','鏃','鏝','鏐','鏈','鏤','鐚','鐔','鐓','鐃','鐇','鐐','鐶','鐫','鐵','鐡','鐺','鑁','鑒','鑄','鑛','鑠','鑢','鑞','鑪','鈩','鑰','鑵','鑷','鑽','鑚','鑼','鑾','钁','鑿','閂','閇','閊','閔','閖','閘','閙','閠','閨','閧','閭','閼','閻','閹','閾','闊','濶','闃','闍','闌','闕','闔','闖','關','闡','闥','闢','阡','阨','阮','阯','陂','陌','陏','陋','陷','陜','陞','陝','陟','陦','陲','陬','隍','隘','隕','隗','險','隧','隱','隲','隰','隴','隶','隸','隹','雎','雋','雉','雍','襍','雜','霍','雕','雹','霄','霆','霈','霓','霎','霑','霏','霖','霙','霤','霪','霰','霹','霽','霾','靄','靆','靈','靂','靉','靜','靠','靤','靦','靨','勒','靫','靱','靹','鞅','靼','鞁','靺','鞆','鞋','鞏','鞐','鞜','鞨','鞦','鞣','鞳','鞴','韃','韆','韈','韋','韜','韭','齏','韲','竟','韶','韵','頏','頌','頸','頤','頡','頷','頽','顆','顏','顋','顫','顯','顰','顱','顴','顳','颪','颯','颱','颶','飄','飃','飆','飩','飫','餃','餉','餒','餔','餘','餡','餝','餞','餤','餠','餬','餮','餽','餾','饂','饉','饅','饐','饋','饑','饒','饌','饕','馗','馘','馥','馭','馮','馼','駟','駛','駝','駘','駑','駭','駮','駱','駲','駻','駸','騁','騏','騅','駢','騙','騫','騷','驅','驂','驀','驃','騾','驕','驍','驛','驗','驟','驢','驥','驤','驩','驫','驪','骭','骰','骼','髀','髏','髑','髓','體','髞','髟','髢','髣','髦','髯','髫','髮','髴','髱','髷','髻','鬆','鬘','鬚','鬟','鬢','鬣','鬥','鬧','鬨','鬩','鬪','鬮','鬯','鬲','魄','魃','魏','魍','魎','魑','魘','魴','鮓','鮃','鮑','鮖','鮗','鮟','鮠','鮨','鮴','鯀','鯊','鮹','鯆','鯏','鯑','鯒','鯣','鯢','鯤','鯔','鯡','鰺','鯲','鯱','鯰','鰕','鰔','鰉','鰓','鰌','鰆','鰈','鰒','鰊','鰄','鰮','鰛','鰥','鰤','鰡','鰰','鱇','鰲','鱆','鰾','鱚','鱠','鱧','鱶','鱸','鳧','鳬','鳰','鴉','鴈','鳫','鴃','鴆','鴪','鴦','鶯','鴣','鴟','鵄','鴕','鴒','鵁','鴿','鴾','鵆','鵈','鵝','鵞','鵤','鵑','鵐','鵙','鵲','鶉','鶇','鶫','鵯','鵺','鶚','鶤','鶩','鶲','鷄','鷁','鶻','鶸','鶺','鷆','鷏','鷂','鷙','鷓','鷸','鷦','鷭','鷯','鷽','鸚','鸛','鸞','鹵','鹹','鹽','麁','麈','麋','麌','麒','麕','麑','麝','麥','麩','麸','麪','麭','靡','黌','黎','黏','黐','黔','黜','點','黝','黠','黥','黨','黯','黴','黶','黷','黹','黻','黼','黽','鼇','鼈','皷','鼕','鼡','鼬','鼾','齊','齒','齔','齣','齟','齠','齡','齦','齧','齬','齪','齷','齲','齶','龕','龜','龠','堯','槇','遙','瑤','凜','熙','噓','巢','帔','帘','幘','幞','庾','廊','廋','廹','开','异','弇','弝','弣','弴','弶','弽','彀','彅','彔','彘','彤','彧','彽','徉','徜','徧','徯','徵','德','忉','忞','忡','忩','怍','怔','怘','怳','怵','恇','悔','悝','悞','惋','惔','惕','惝','惸','愜','愫','愰','愷','慨','憍','憎','憼','憹','懲','戢','戾','扃','扖','扚','扯','抅','拄','拖','拼','挊','挘','挹','捃','捥','捼','揥','揭','揵','搐','搔','搢','摹','摑','摠','摭','擎','撾','撿','㐂','𠅘','份','仿','侚','俉','傜','儞','冼','㔟','匇','卡','卬','詹','𠮷','呍','咖','咜','咩','唎','啊','噲','囤','圳','圴','塚','墀','姤','娣','婕','寬','﨑','㟢','庬','弴','彅','德','怗','恵','愰','昤','曈','曙','曺','曻','桒','鿄','椑','椻','橅','檑','櫛','𣏌','𣏾','𣗄','毱','泠','洮','海','涿','淊','淸','渚','潞','濹','灤','𤋮','𤋮','煇','燁','爀','玟','玨','珉','珖','琛','琡','琢','琦','琪','琬','琹','瑋','㻚','畵','疁','睲','䂓','磈','磠','祇','禮','鿆','䄃','鿅','秚','稞','筿','簱','䉤','綋','羡','脘','脺','舘','芮','葛','蓜','蓬','蕙','藎','蝕','蟬','蠋','裵','角','諶','跎','辻','迶','郝','鄧','鄭','醲','鈳','銈','錡','鍈','閒','雞','餃','饀','髙','鯖','鷗','麴','麵','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','⛌','⛍','❗','⛏','⛐','⛑','','⛒','⛕','⛓','⛔','','','','','','','','','⛖','⛗','⛘','⛙','⛚','⛛','⛜','⛝','⛞','⛟','⛠','⛡','⭕','㉈','㉉','㉊','㉋','㉌','㉍','㉎','㉏','','','','','⒑','⒒','⒓','','','','','','','','','','','','','','','','','⬛','⬤','','','','','','⚿','','','','','','','','','','','','㊙','','','','','','','','','','','','⛣','⭖','⭗','⭘','⭙','☓','㊋','〒','⛨','㉆','㉅','⛩','࿖','⛪','⛫','⛬','♨','⛭','⛮','⛯','⚓','✈','⛰','⛱','⛲','⛳','⛴','⛵','','Ⓓ','Ⓢ','⛶','','','','','','⛷','⛸','⛹','⛺','','☎','⛻','⛼','⛽','⛾','','⛿','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','➡','⬅','⬆','⬇','⬯','⬮','年','月','日','円','㎡','㎥','㎝','㎠','㎤','','⒈','⒉','⒊','⒋','⒌','⒍','⒎','⒏','⒐','','','','','','','','','','','','','','','','','㈳','㈶','㈲','㈱','㈹','㉄','▶','◀','〖','〗','⟐','²','³','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','㉇','','','℻','','','','㈪','㈫','㈬','㈭','㈮','㈯','㈰','㈷','㍾','㍽','㍼','㍻','№','℡','〶','⚾','','','','','','','','','','','','','','','','','','','','','','','ℓ','㎏','㎐','㏊','㎞','㎢','㍱','','','½','↉','⅓','⅔','¼','¾','⅕','⅖','⅗','⅘','⅙','⅚','⅐','⅛','⅑','⅒','☀','☁','☂','⛄','☖','☗','⛉','⛊','♦','♥','♣','♠','⛋','⨀','‼','⁉','⛅','☔','⛆','☃','⛇','⚡','⛈','','⚞','⚟','♬','☎','','','','Ⅰ','Ⅱ','Ⅲ','Ⅳ','Ⅴ','Ⅵ','Ⅶ','Ⅷ','Ⅸ','Ⅹ','Ⅺ','Ⅻ','⑰','⑱','⑲','⑳','⑴','⑵','⑶','⑷','⑸','⑹','⑺','⑻','⑼','⑽','⑾','⑿','㉑','㉒','㉓','㉔','','','','','','','','','','','','','','','','','','','','','','','','','','','㉕','㉖','㉗','㉘','㉙','㉚','①','②','③','④','⑤','⑥','⑦','⑧','⑨','⑩','⑪','⑫','⑬','⑭','⑮','⑯','❶','❷','❸','❹','❺','❻','❼','❽','❾','❿','⓫','⓬','㉛','' 3 | ] 4 | --------------------------------------------------------------------------------