diff --git a/src/utils/dicom-character-set.js b/src/utils/dicom-character-set.js new file mode 100644 index 00000000..1469945b --- /dev/null +++ b/src/utils/dicom-character-set.js @@ -0,0 +1,608 @@ +/*! dicom-character-set - 1.0.5 - 2024-01-10 | (c) 2018 Radialogica, LLC | https://github.com/radialogica/dicom-character-set */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["dicom-character-set"] = factory(); + else + root["dicom-character-set"] = factory(); +})(typeof self !== "undefined" ? self : this, () => { +return /******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({ + +/***/ "./character-sets.js": +/*!***************************!*\ + !*** ./character-sets.js ***! + \***************************/ +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { + +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ characterSets: () => (/* binding */ characterSets) +/* harmony export */ }); +const asciiElement = { + codeElement: 'G0', + escapeSequence: [0x1B, 0x28, 0x42], + encoding: 'windows-1252', + isASCII: true, + bytesPerCodePoint: 1 +}; +const characterSets = { + /** ******************************** + * Single-byte without extensions * + **********************************/ + + // Default + 'ISO_IR 6': { + encoding: 'utf-8' + }, + // Latin alphabet No. 1 + 'ISO_IR 100': { + encoding: 'windows-1252' + }, + // Latin alphabet No. 2 + 'ISO_IR 101': { + encoding: 'iso-8859-2' + }, + // Latin alphabet No. 3 + 'ISO_IR 109': { + encoding: 'iso-8859-3' + }, + // Latin alphabet No. 4 + 'ISO_IR 110': { + encoding: 'iso-8859-4' + }, + // Cyrillic + 'ISO_IR 144': { + encoding: 'iso-8859-5' + }, + // Arabic + 'ISO_IR 127': { + encoding: 'iso-8859-6' + }, + // Greek + 'ISO_IR 126': { + encoding: 'iso-8859-7' + }, + // Hebrew + 'ISO_IR 138': { + encoding: 'iso-8859-8' + }, + // Latin alphabet No. 5 + 'ISO_IR 148': { + encoding: 'windows-1254' + }, + // Japanese + 'ISO_IR 13': { + encoding: 'shift-jis' + }, + // Thai + 'ISO_IR 166': { + encoding: 'tis-620' + }, + /** ***************************** + * Single-byte with extensions * + *******************************/ + + // Default + 'ISO 2022 IR 6': { + extension: true, + elements: [asciiElement] + }, + // Latin alphabet No. 1 + 'ISO 2022 IR 100': { + extension: true, + elements: [asciiElement, { + codeElement: 'G1', + escapeSequence: [0x1B, 0x2D, 0x41], + encoding: 'windows-1252', + bytesPerCodePoint: 1 + }] + }, + // Latin alphabet No. 2 + 'ISO 2022 IR 101': { + extension: true, + elements: [asciiElement, { + codeElement: 'G1', + escapeSequence: [0x1B, 0x2D, 0x42], + encoding: 'iso-8859-2', + bytesPerCodePoint: 1 + }] + }, + // Latin alphabet No. 3 + 'ISO 2022 IR 109': { + extension: true, + elements: [asciiElement, { + codeElement: 'G1', + escapeSequence: [0x1B, 0x2D, 0x43], + encoding: 'iso-8859-3', + bytesPerCodePoint: 1 + }] + }, + // Latin alphabet No. 4 + 'ISO 2022 IR 110': { + extension: true, + elements: [asciiElement, { + codeElement: 'G1', + escapeSequence: [0x1B, 0x2D, 0x44], + encoding: 'iso-8859-4', + bytesPerCodePoint: 1 + }] + }, + // Cyrillic + 'ISO 2022 IR 144': { + extension: true, + elements: [asciiElement, { + codeElement: 'G1', + escapeSequence: [0x1B, 0x2D, 0x4C], + encoding: 'iso-8859-5', + bytesPerCodePoint: 1 + }] + }, + // Arabic + 'ISO 2022 IR 127': { + extension: true, + elements: [asciiElement, { + codeElement: 'G1', + escapeSequence: [0x1B, 0x2D, 0x47], + encoding: 'iso-8859-6', + bytesPerCodePoint: 1 + }] + }, + // Greek + 'ISO 2022 IR 126': { + extension: true, + elements: [asciiElement, { + codeElement: 'G1', + escapeSequence: [0x1B, 0x2D, 0x46], + encoding: 'iso-8859-7', + bytesPerCodePoint: 1 + }] + }, + // Hebrew + 'ISO 2022 IR 138': { + extension: true, + elements: [asciiElement, { + codeElement: 'G1', + escapeSequence: [0x1B, 0x2D, 0x48], + encoding: 'iso-8859-8', + bytesPerCodePoint: 1 + }] + }, + // Latin alphabet No. 5 + 'ISO 2022 IR 148': { + extension: true, + elements: [asciiElement, { + codeElement: 'G1', + escapeSequence: [0x1B, 0x2D, 0x4D], + encoding: 'windows-1254', + bytesPerCodePoint: 1 + }] + }, + // Japanese + 'ISO 2022 IR 13': { + extension: true, + elements: [{ + codeElement: 'G0', + escapeSequence: [0x1B, 0x28, 0x4A], + encoding: 'shift-jis', + bytesPerCodePoint: 1 + }, { + codeElement: 'G1', + escapeSequence: [0x1B, 0x29, 0x49], + encoding: 'shift-jis', + bytesPerCodePoint: 1 + }] + }, + // Thai + 'ISO 2022 IR 166': { + extension: true, + elements: [asciiElement, { + codeElement: 'G1', + escapeSequence: [0x1B, 0x2D, 0x54], + encoding: 'tis-620', + bytesPerCodePoint: 1 + }] + }, + /** **************************** + * Multi-byte with extensions * + ******************************/ + + // Japanese + 'ISO 2022 IR 87': { + extension: true, + multiByte: true, + elements: [{ + codeElement: 'G0', + escapeSequence: [0x1B, 0x24, 0x42], + encoding: 'euc-jp', + setHighBit: true, + bytesPerCodePoint: 2 + }] + }, + 'ISO 2022 IR 159': { + extension: true, + multiByte: true, + elements: [{ + codeElement: 'G0', + escapeSequence: [0x1B, 0x24, 0x28, 0x44], + encoding: 'euc-jp', + isJISX0212: true, + bytesPerCodePoint: 2 + }] + }, + // Korean + 'ISO 2022 IR 149': { + extension: true, + multiByte: true, + elements: [{ + codeElement: 'G1', + escapeSequence: [0x1B, 0x24, 0x29, 0x43], + encoding: 'euc-kr', + bytesPerCodePoint: 2 + }] + }, + // Simplified Chinese + 'ISO 2022 IR 58': { + extension: true, + multiByte: true, + elements: [{ + codeElement: 'G1', + escapeSequence: [0x1B, 0x24, 0x29, 0x41], + encoding: 'gb18030', + bytesPerCodePoint: 2 + }] + }, + /** ******************************* + * Multi-byte without extensions * + *********************************/ + + 'ISO_IR 192': { + encoding: 'utf-8', + multiByte: true + }, + GB18030: { + encoding: 'gb18030', + multiByte: true + }, + GBK: { + encoding: 'gbk', + multiByte: true + } +}; + +/***/ }), + +/***/ "./convert-bytes.js": +/*!**************************!*\ + !*** ./convert-bytes.js ***! + \**************************/ +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { + +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ convertBytes: () => (/* binding */ convertBytes), +/* harmony export */ convertBytesPromise: () => (/* binding */ convertBytesPromise) +/* harmony export */ }); +/* harmony import */ var _character_sets_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./character-sets.js */ "./character-sets.js"); + +const ESCAPE_BYTE = 0x1B; +const CARRIAGE_RETURN = 0xA; +const LINE_FEED = 0xC; +const FORM_FEED = 0xD; +const TAB = 0x9; +const BACKSLASH = 0x5C; // Aka yen symbol in Romaji +const EQUAL_SIGN = 0x3D; +const CARET = 0x5E; +function adjustShiftJISResult(delimiters, str) { + // browsers do strict ASCII for \ and ~, so to be compliant with Shift JIS we replace them + const fixedTildes = str.replace(/~/g, '‾'); + // If 0x5c is being used as a multi-value separator, we must not replace it so it stays as U+005c + return delimiters.includes(BACKSLASH) ? fixedTildes : fixedTildes.replace(/\\/g, '¥'); +} +function appendRunWithoutPromise(output, byteRunCharacterSet, delimiters, bytes, byteRunStart, byteRunEnd) { + const oneRunBytes = preprocessBytes(byteRunCharacterSet, bytes, byteRunStart, byteRunEnd); + return output + convertWithoutExtensions(byteRunCharacterSet.encoding, delimiters, oneRunBytes); +} +function appendRunWithPromise(output, byteRunCharacterSet, delimiters, bytes, byteRunStart, byteRunEnd) { + const oneRunBytes = preprocessBytes(byteRunCharacterSet, bytes, byteRunStart, byteRunEnd); + return (output === '' ? Promise.resolve('') : output).then(lhs => convertWithoutExtensionsPromise(byteRunCharacterSet.encoding, delimiters, oneRunBytes).then(rhs => lhs + rhs)); +} +function checkParameters(specificCharacterSet, bytes) { + if (bytes && !(bytes instanceof Uint8Array)) { + throw new Error('bytes must be a Uint8Array'); + } + if (specificCharacterSet && typeof specificCharacterSet !== 'string') { + throw new Error('specificCharacterSet must be a string'); + } +} +function convertBytesCore(withoutExtensionsFunc, appendFunc, specificCharacterSet, bytes, options) { + checkParameters(specificCharacterSet, bytes); + const characterSetStrings = getCharacterSetStrings(specificCharacterSet); + const checkedOptions = options || {}; + const delimiters = getDelimitersForVR(checkedOptions.vr); + if (characterSetStrings.length === 1 && !characterSetStrings[0].startsWith('ISO 2022')) { + return withoutExtensionsFunc(_character_sets_js__WEBPACK_IMPORTED_MODULE_0__.characterSets[characterSetStrings[0]].encoding, delimiters, bytes); + } + return convertWithExtensions(characterSetStrings.map(characterSet => _character_sets_js__WEBPACK_IMPORTED_MODULE_0__.characterSets[characterSet]), bytes, delimiters, appendFunc); +} +function convertWithExtensions(allowedCharacterSets, bytes, delimiters, appendRun) { + let output = ''; + if (!bytes || bytes.length === 0) { + return output; + } + const initialCharacterSets = { + G0: allowedCharacterSets[0].elements.find(element => element.codeElement === 'G0'), + G1: allowedCharacterSets[0].elements.find(element => element.codeElement === 'G1') + }; + const activeCharacterSets = Object.assign({}, initialCharacterSets); + let byteRunStart = 0; + let byteRunCharacterSet; + let nextSetIndex = 0; + + // Group bytes into runs based on their encoding so we don't have to use a different + // decoder for each character. Note that G0 and G1 planes can be different encodings, + // so we can't just group by character set. + + while (nextSetIndex < bytes.length) { + if (!byteRunCharacterSet) { + byteRunCharacterSet = getCharacterSet(bytes[byteRunStart], activeCharacterSets); + } + const next = findNextCharacterSet(bytes, byteRunStart, byteRunCharacterSet, activeCharacterSets, initialCharacterSets, delimiters); + nextSetIndex = next.index; + if (nextSetIndex > byteRunStart) { + output = appendRun(output, byteRunCharacterSet, delimiters, bytes, byteRunStart, nextSetIndex); + } + byteRunStart = nextSetIndex; + byteRunCharacterSet = next.characterSet; + if (next.escapeSequence) { + const nextCharacterSet = readEscapeSequence(bytes, nextSetIndex, allowedCharacterSets); + activeCharacterSets[nextCharacterSet.codeElement] = nextCharacterSet; + byteRunStart += nextCharacterSet.escapeSequence.length; + } + } + return output; +} +function convertWithoutExtensions(encoding, delimiters, bytes) { + const retVal = new TextDecoder(encoding).decode(bytes); + return encoding === 'shift-jis' ? adjustShiftJISResult(delimiters, retVal) : retVal; +} +function convertWithoutExtensionsPromise(encoding, delimiters, bytes) { + return new Promise(resolve => { + const fileReader = new FileReader(); + if (encoding === 'shift-jis') { + fileReader.onload = () => resolve(adjustShiftJISResult(delimiters, fileReader.result)); + } else { + fileReader.onload = () => resolve(fileReader.result); + } + const blob = new Blob([bytes]); + fileReader.readAsText(blob, encoding); + }); +} + +// Multibyte non-extension character sets must stand on their own or else be ignored. This method enforces that. +function filterMultiByteCharacterSetStrings(characterSetStrings) { + const initialCharacterSet = _character_sets_js__WEBPACK_IMPORTED_MODULE_0__.characterSets[characterSetStrings[0]]; + if (initialCharacterSet.multiByte && !initialCharacterSet.extension) { + return [characterSetStrings[0]]; + } + return characterSetStrings.filter(str => !_character_sets_js__WEBPACK_IMPORTED_MODULE_0__.characterSets[str].multiByte || _character_sets_js__WEBPACK_IMPORTED_MODULE_0__.characterSets[str].extension); +} +function findNextCharacterSet(bytes, start, currentCodeElement, activeCodeElements, initialCharacterSets, delimiters) { + for (let i = start; i < bytes.length; i += currentCodeElement.bytesPerCodePoint) { + if (bytes[i] === ESCAPE_BYTE) { + return { + escapeSequence: true, + index: i + }; + } + if (currentCodeElement.bytesPerCodePoint === 1 && delimiters.includes(bytes[i])) { + Object.assign(activeCodeElements, initialCharacterSets); + } + const nextCodeElement = getCharacterSet(bytes[i], activeCodeElements); + if (currentCodeElement && nextCodeElement !== currentCodeElement) { + return { + characterSet: nextCodeElement, + index: i + }; + } + } + return { + index: bytes.length + }; +} +function forceExtensionsIfApplicable(characterSetStrings) { + const forceExtensions = characterSetStrings.length > 1; + const returnValue = []; + for (let i = 0; i < characterSetStrings.length; i++) { + const characterSetString = characterSetStrings[i]; + if (!returnValue.includes(characterSetString)) { + returnValue.push(forceExtensions ? characterSetString.replace('ISO_IR', 'ISO 2022 IR') : characterSetString); + } + } + return returnValue; +} +function getCharacterSet(byte, activeCharacterSets) { + if (byte > 0x7F && activeCharacterSets.G1) { + return activeCharacterSets.G1; + } + if (activeCharacterSets.G0) { + return activeCharacterSets.G0; + } + // for robustness if byte <= 0x7F, try to output using G1 if no G0 is selected + if (activeCharacterSets.G1 && activeCharacterSets.G1.bytesPerCodePoint === 1) { + return activeCharacterSets.G1; + } + // If G1 is multibyte, default to ASCII + + return _character_sets_js__WEBPACK_IMPORTED_MODULE_0__.characterSets['ISO 2022 IR 6'].elements[0]; +} +function getCharacterSetStrings(specificCharacterSet) { + let characterSetStrings = specificCharacterSet ? specificCharacterSet.split('\\').map(characterSet => characterSet.trim().toUpperCase()) : ['']; + if (characterSetStrings[0] === '') { + characterSetStrings[0] = characterSetStrings.length > 1 ? 'ISO 2022 IR 6' : 'ISO_IR 6'; + } + if (characterSetStrings.some(characterSet => _character_sets_js__WEBPACK_IMPORTED_MODULE_0__.characterSets[characterSet] === undefined)) { + throw new Error('Invalid specific character set specified.'); + } + characterSetStrings = filterMultiByteCharacterSetStrings(characterSetStrings); + return forceExtensionsIfApplicable(characterSetStrings); +} +function getDelimitersForVR(incomingVR) { + const vr = (incomingVR || '').trim().toUpperCase(); + const delimiters = [CARRIAGE_RETURN, LINE_FEED, FORM_FEED, TAB]; + if (!['UT', 'ST', 'LT'].includes(vr)) { + // for delimiting multi-valued items + delimiters.push(BACKSLASH); + } + if (vr === 'PN') { + delimiters.push(EQUAL_SIGN); + delimiters.push(CARET); + } + return delimiters; +} +function preprocessBytes(characterSet, bytes, byteStart, byteEnd) { + let oneEncodingBytes; + if (characterSet.isJISX0212) { + oneEncodingBytes = processJISX0212(bytes, byteStart, byteEnd); + } else { + oneEncodingBytes = new Uint8Array(byteEnd - byteStart); + oneEncodingBytes.set(new Uint8Array(bytes.buffer, bytes.byteOffset + byteStart, byteEnd - byteStart)); + if (characterSet.setHighBit) { + setHighBit(oneEncodingBytes); + } + } + return oneEncodingBytes; +} +function processJISX0212(bytes, bytesStart, bytesEnd) { + const length = bytesEnd - bytesStart; + if (length % 2 !== 0) { + throw new Error('JIS X string with a character not having exactly two bytes!'); + } + const processedBytes = new Uint8Array(length + length / 2); + let outIndex = 0; + for (let i = bytesStart; i < bytesEnd; i += 2) { + processedBytes[outIndex++] = 0x8F; + processedBytes[outIndex++] = bytes[i] | 0x80; + processedBytes[outIndex++] = bytes[i + 1] | 0x80; + } + return processedBytes; +} +function escapeSequenceMatches(escapeSequence, bytes, startIndex) { + for (let escapeByteIndex = 0; escapeByteIndex < escapeSequence.length; escapeByteIndex++) { + if (startIndex + escapeByteIndex >= bytes.length) { + return false; + } else if (bytes[startIndex + escapeByteIndex] !== escapeSequence[escapeByteIndex]) { + return false; + } + } + return true; +} +function readEscapeSequence(bytes, start, extensionSets) { + for (let setIndex = 0; setIndex < extensionSets.length; setIndex++) { + const extensionSet = extensionSets[setIndex]; + for (let elementIndex = 0; elementIndex < extensionSet.elements.length; elementIndex++) { + const element = extensionSet.elements[elementIndex]; + if (escapeSequenceMatches(element.escapeSequence, bytes, start)) { + return element; + } + } + } + throw new Error(`Unknown escape sequence encountered at byte ${start}`); +} +function setHighBit(bytes) { + for (let i = 0; i < bytes.length; i++) { + bytes[i] |= 0x80; + } +} +function convertBytes(specificCharacterSet, bytes, options) { + return convertBytesCore(convertWithoutExtensions, appendRunWithoutPromise, specificCharacterSet, bytes, options); +} +function convertBytesPromise(specificCharacterSet, bytes, options) { + return convertBytesCore(convertWithoutExtensionsPromise, appendRunWithPromise, specificCharacterSet, bytes, options); +} + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ (() => { +/******/ // define getter functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ (() => { +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ })(); +/******/ +/************************************************************************/ +var __webpack_exports__ = {}; +// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk. +(() => { +/*!******************!*\ + !*** ./index.js ***! + \******************/ +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ characterSets: () => (/* reexport safe */ _character_sets_js__WEBPACK_IMPORTED_MODULE_1__.characterSets), +/* harmony export */ convertBytes: () => (/* reexport safe */ _convert_bytes_js__WEBPACK_IMPORTED_MODULE_0__.convertBytes), +/* harmony export */ convertBytesPromise: () => (/* reexport safe */ _convert_bytes_js__WEBPACK_IMPORTED_MODULE_0__.convertBytesPromise) +/* harmony export */ }); +/* harmony import */ var _convert_bytes_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./convert-bytes.js */ "./convert-bytes.js"); +/* harmony import */ var _character_sets_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./character-sets.js */ "./character-sets.js"); + + +})(); + +/******/ return __webpack_exports__; +/******/ })() +; +}); +//# sourceMappingURL=dicom-character-set.js.map \ No newline at end of file