From a141873db816d874298288461d8c65d4b47899ad Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 16 Sep 2022 16:00:39 +0100 Subject: [PATCH] Highlighting now takes account of character set width --- src/core/lib/ChrEnc.mjs | 42 +++++++++++++++++++++++++++ src/web/waiters/HighlighterWaiter.mjs | 20 +++++++++---- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/core/lib/ChrEnc.mjs b/src/core/lib/ChrEnc.mjs index 8934d137..6879d736 100644 --- a/src/core/lib/ChrEnc.mjs +++ b/src/core/lib/ChrEnc.mjs @@ -6,6 +6,8 @@ * @license Apache-2.0 */ +import cptable from "codepage"; + /** * Character encoding format mappings. */ @@ -175,6 +177,46 @@ for (const name in CHR_ENC_CODE_PAGES) { CHR_ENC_SIMPLE_REVERSE_LOOKUP[CHR_ENC_CODE_PAGES[name]] = simpleName; } + +/** + * Returns the width of the character set for the given codepage. + * For example, UTF-8 is a Single Byte Character Set, whereas + * UTF-16 is a Double Byte Character Set. + * + * @param {number} page - The codepage number + * @returns {number} + */ +export function chrEncWidth(page) { + if (typeof page !== "number") return 0; + + // Raw Bytes have a width of 1 + if (page === 0) return 1; + + const pageStr = page.toString(); + // Confirm this page is legitimate + if (!Object.prototype.hasOwnProperty.call(CHR_ENC_SIMPLE_REVERSE_LOOKUP, pageStr)) + return 0; + + // Statically defined code pages + if (Object.prototype.hasOwnProperty.call(cptable, pageStr)) + return cptable[pageStr].dec.length > 256 ? 2 : 1; + + // Cached code pages + if (cptable.utils.cache.sbcs.includes(pageStr)) + return 1; + if (cptable.utils.cache.dbcs.includes(pageStr)) + return 2; + + // Dynamically generated code pages + if (Object.prototype.hasOwnProperty.call(cptable.utils.magic, pageStr)) { + // Generate a single character and measure it + const a = cptable.utils.encode(page, "a"); + return a.length; + } + + return 0; +} + /** * Unicode Normalisation Forms * diff --git a/src/web/waiters/HighlighterWaiter.mjs b/src/web/waiters/HighlighterWaiter.mjs index 189d3777..aca55e84 100755 --- a/src/web/waiters/HighlighterWaiter.mjs +++ b/src/web/waiters/HighlighterWaiter.mjs @@ -5,7 +5,7 @@ */ import {EditorSelection} from "@codemirror/state"; - +import {chrEncWidth} from "../../core/lib/ChrEnc.mjs"; /** * Waiter to handle events related to highlighting in CyberChef. @@ -50,12 +50,23 @@ class HighlighterWaiter { // Confirm some non-empty ranges are set const selectionRanges = e.state.selection.ranges; + // Adjust offsets based on the width of the character set + const inputCharacterWidth = chrEncWidth(this.manager.input.inputChrEnc); + const outputCharacterWidth = chrEncWidth(this.manager.output.outputChrEnc); + let ratio = 1; + if (inputCharacterWidth !== outputCharacterWidth && + inputCharacterWidth !== 0 && outputCharacterWidth !== 0) { + ratio = io === "input" ? + inputCharacterWidth / outputCharacterWidth : + outputCharacterWidth / inputCharacterWidth; + } + // Loop through ranges and send request for output offsets for each one const direction = io === "input" ? "forward" : "reverse"; for (const range of selectionRanges) { const pos = [{ - start: range.from, - end: range.to + start: Math.floor(range.from * ratio), + end: Math.floor(range.to * ratio) }]; this.manager.worker.highlight(this.app.getRecipeConfig(), direction, pos); } @@ -80,8 +91,7 @@ class HighlighterWaiter { /** - * Adds the relevant HTML to the specified highlight element such that highlighting appears - * underneath the correct offset. + * Sends selection updates to the relevant EditorView. * * @param {string} io - The input or output * @param {Object[]} ranges - An array of position objects to highlight