Electron Adventures: Episode 62: Hex Editor Displaying Data
Tomasz Wegrzanowski
Posted on September 26, 2021
Hex editor is not a very complicated project, but to keep the posts manageable let's do it one step at a time.
Let's start by displaying data in the MainView
.
fast-printf
One function that most languages have but browser-side JavaScript somehow lacks is anything like printf
.
In pretty much any other language, to get a 2 digit hex number you can do printf("%02x", i)
or something like that.
Fortunately there's a lot of npm packages for it, but many are called "printf" but do not implement even basic printf functionality.
After a few tries with other packages I found that fast-printf
does everything I need.
src/App.svelte
To start displaying data, first we need to generate some. And to we can just throw some numbers into an array in a loop. We'll actually want to use Buffer
or Uint8Array
for this eventually, but one thing at a time.
<script>
import MainView from "./MainView.svelte"
import Decodings from "./Decodings.svelte"
import StatusBar from "./StatusBar.svelte"
let data = []
let offset = 1234
for (let i=0; i<10010; i++) {
data.push(i & 0xFF)
}
</script>
<div class="editor">
<MainView {data} />
<Decodings {data} {offset} />
<StatusBar {offset} />
</div>
<svelte:head>
<title>fancy-data.bin</title>
</svelte:head>
src/StatusBar.svelte
For hex files there's situations where we want to dispaly offset as decimal, and situations where we want to display offset as hex. Since we have a lot of space on the status bar, we can do both.
printf
from fast-printf
package will handle the formatting.
<script>
import { printf } from "fast-printf"
export let offset
$: hexOffset = printf("%x", offset)
</script>
<div>
Offset: {offset} ({hexOffset})
</div>
<style>
div {
margin-top: 8px;
}
</style>
src/MainView.svelte
Svelte doesn't have {#while}
or {#for}
loops, just {#each}
, so we need to convert data to slices.
We can put the slicing in $:
block so it happens automtically whenever data
changes.
<script>
import Slice from "./Slice.svelte"
export let data
let slices
$: {
slices = []
for (let i=0; i<data.length; i+=16) {
slices.push({
offset: i,
data: data.slice(i, i+16),
})
}
}
</script>
<div class="main">
<table>
{#each slices as slice}
<Slice {...slice} />
{/each}
</table>
</div>
<style>
.main {
flex: 1 1 auto;
overflow-y: auto;
}
table {
width: 100%;
}
</style>
src/Slice.svelte
This component represents one row of the main view. It needs to display offset, hex data, and ascii data.
We sometimes want to display decimal and sometimes hex offset, but there's definitely no space for both. It would be nice to have some shortcut to switch between the modes.
<script>
import { printf } from "fast-printf"
import HexGroup from "./HexGroup.svelte"
import AsciiSlice from "./AsciiSlice.svelte"
export let offset
export let data
</script>
<tr>
<td class="offset">{printf("%06d", offset)}</td>
<HexGroup data={data.slice(0, 4)} />
<HexGroup data={data.slice(4, 8)} />
<HexGroup data={data.slice(8, 12)} />
<HexGroup data={data.slice(12, 16)} />
<AsciiSlice {data} />
</tr>
<style>
tr:nth-child(even) {
background-color: #555;
}
</style>
src/HexGroup.svelte
For now this component is very simple for now, thanks to printf
. We'll need to modify it so it tells us which exact cell is being hovered.
<script>
import { printf } from "fast-printf"
export let data
</script>
<td class="hex">
<span>
{data[0] !== undefined ? printf("%02x", data[0]) : ""}
</span>
<span>
{data[1] !== undefined ? printf("%02x", data[1]) : ""}
</span>
<span>
{data[2] !== undefined ? printf("%02x", data[2]) : ""}
</span>
<span>
{data[3] !== undefined ? printf("%02x", data[3]) : ""}
</span>
</td>
src/AsciiSlice.svelte
And finally, the ASCII preview of the data of the slice.
There are three cases here:
- it's a printable ASCII character - then we print it
- it's space - then we print it as
to keep data aligned - it's anything else - then we put a gray dot instead, to keep other data aligned
An obvious question is why don't we print UTF8 characters. This is mainly because that complicate data alignment a lot. And what if start of a character is on one line, but the rest of it is on the next? Or when there are combining characters? Binary data rarely has enough complex UTF8 to justify this. And the table under the code should handle such cases well enough.
<script>
export let data
</script>
<td class="ascii">
{#each data as d}
{#if d >= 33 && d <= 126}
{String.fromCharCode(d)}
{:else if d == 32}
{:else}
<span class="unprintable">.</span>
{/if}
{/each}
</td>
<style>
.unprintable {
color: #aaa;
}
</style>
Result
Here's the results:
In the next episode, we'll make data decoding table work.
As usual, all the code for the episode is here.
Posted on September 26, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.