]> git.ardis.lu - utf-8-visualizer/commitdiff
Initial commit
authorArdis Lu <ardisglu@gmail.com>
Fri, 30 Dec 2022 04:35:11 +0000 (20:35 -0800)
committerArdis Lu <ardisglu@gmail.com>
Fri, 30 Dec 2022 04:35:11 +0000 (20:35 -0800)
README.md [new file with mode: 0644]
index.html [new file with mode: 0644]
script.js [new file with mode: 0644]
style.css [new file with mode: 0644]

diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..2fe38dd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# utf-8-visualizer
+
+[WORK IN PROGRESS] A tool to visualize how Unicode code points get encoded into UTF-8.
diff --git a/index.html b/index.html
new file mode 100644 (file)
index 0000000..634d340
--- /dev/null
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <link rel="icon" href="data:,">
+  <link rel="stylesheet" href="/style.css">
+  <title>utf-8-visualizer</title>
+</head>
+
+<body>
+  <header>
+    <h1>utf-8-visualizer</h1>
+  </header>
+
+  <main>
+    <input value="🏴‍☠️"></input>
+
+    <div id="chars"></div>
+    <div id="points"></div>
+    <div id="points-binary"></div>
+    <div id="bytes"></div>
+  </main>
+
+  <div></div>
+
+  <footer>
+    <a href="https://github.com/ardislu/utf-8-visualizer">
+      <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+        <path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"></path>
+        <path d="M9 18c-4.51 2-5-2-7-2"></path>
+      </svg>
+    </a>
+  </footer>
+
+  <script type="module" src="/script.js"></script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/script.js b/script.js
new file mode 100644 (file)
index 0000000..732b3c1
--- /dev/null
+++ b/script.js
@@ -0,0 +1,48 @@
+const input = document.querySelector('input');
+const chars = document.getElementById('chars');
+const points = document.getElementById('points');
+const pointsBinary = document.getElementById('points-binary');
+const bytes = document.getElementById('bytes');
+
+const encoder = new TextEncoder();
+
+// All invisible Unicode characters. Copied from VSCode source code:
+// https://github.com/microsoft/vscode/blob/main/src/vs/base/common/strings.ts#L1192
+const invisibleCharacters = [9, 10, 11, 12, 13, 32, 127, 160, 173, 847, 1564, 4447, 4448, 6068, 6069, 6155, 6156, 6157, 6158, 7355, 7356, 8192, 8193, 8194, 8195, 8196, 8197, 8198, 8199, 8200, 8201, 8202, 8203, 8204, 8205, 8206, 8207, 8234, 8235, 8236, 8237, 8238, 8239, 8287, 8288, 8289, 8290, 8291, 8292, 8293, 8294, 8295, 8296, 8297, 8298, 8299, 8300, 8301, 8302, 8303, 10240, 12288, 12644, 65024, 65025, 65026, 65027, 65028, 65029, 65030, 65031, 65032, 65033, 65034, 65035, 65036, 65037, 65038, 65039, 65279, 65440, 65520, 65521, 65522, 65523, 65524, 65525, 65526, 65527, 65528, 65532, 78844, 119155, 119156, 119157, 119158, 119159, 119160, 119161, 119162, 917504, 917505, 917506, 917507, 917508, 917509, 917510, 917511, 917512, 917513, 917514, 917515, 917516, 917517, 917518, 917519, 917520, 917521, 917522, 917523, 917524, 917525, 917526, 917527, 917528, 917529, 917530, 917531, 917532, 917533, 917534, 917535, 917536, 917537, 917538, 917539, 917540, 917541, 917542, 917543, 917544, 917545, 917546, 917547, 917548, 917549, 917550, 917551, 917552, 917553, 917554, 917555, 917556, 917557, 917558, 917559, 917560, 917561, 917562, 917563, 917564, 917565, 917566, 917567, 917568, 917569, 917570, 917571, 917572, 917573, 917574, 917575, 917576, 917577, 917578, 917579, 917580, 917581, 917582, 917583, 917584, 917585, 917586, 917587, 917588, 917589, 917590, 917591, 917592, 917593, 917594, 917595, 917596, 917597, 917598, 917599, 917600, 917601, 917602, 917603, 917604, 917605, 917606, 917607, 917608, 917609, 917610, 917611, 917612, 917613, 917614, 917615, 917616, 917617, 917618, 917619, 917620, 917621, 917622, 917623, 917624, 917625, 917626, 917627, 917628, 917629, 917630, 917631, 917760, 917761, 917762, 917763, 917764, 917765, 917766, 917767, 917768, 917769, 917770, 917771, 917772, 917773, 917774, 917775, 917776, 917777, 917778, 917779, 917780, 917781, 917782, 917783, 917784, 917785, 917786, 917787, 917788, 917789, 917790, 917791, 917792, 917793, 917794, 917795, 917796, 917797, 917798, 917799, 917800, 917801, 917802, 917803, 917804, 917805, 917806, 917807, 917808, 917809, 917810, 917811, 917812, 917813, 917814, 917815, 917816, 917817, 917818, 917819, 917820, 917821, 917822, 917823, 917824, 917825, 917826, 917827, 917828, 917829, 917830, 917831, 917832, 917833, 917834, 917835, 917836, 917837, 917838, 917839, 917840, 917841, 917842, 917843, 917844, 917845, 917846, 917847, 917848, 917849, 917850, 917851, 917852, 917853, 917854, 917855, 917856, 917857, 917858, 917859, 917860, 917861, 917862, 917863, 917864, 917865, 917866, 917867, 917868, 917869, 917870, 917871, 917872, 917873, 917874, 917875, 917876, 917877, 917878, 917879, 917880, 917881, 917882, 917883, 917884, 917885, 917886, 917887, 917888, 917889, 917890, 917891, 917892, 917893, 917894, 917895, 917896, 917897, 917898, 917899, 917900, 917901, 917902, 917903, 917904, 917905, 917906, 917907, 917908, 917909, 917910, 917911, 917912, 917913, 917914, 917915, 917916, 917917, 917918, 917919, 917920, 917921, 917922, 917923, 917924, 917925, 917926, 917927, 917928, 917929, 917930, 917931, 917932, 917933, 917934, 917935, 917936, 917937, 917938, 917939, 917940, 917941, 917942, 917943, 917944, 917945, 917946, 917947, 917948, 917949, 917950, 917951, 917952, 917953, 917954, 917955, 917956, 917957, 917958, 917959, 917960, 917961, 917962, 917963, 917964, 917965, 917966, 917967, 917968, 917969, 917970, 917971, 917972, 917973, 917974, 917975, 917976, 917977, 917978, 917979, 917980, 917981, 917982, 917983, 917984, 917985, 917986, 917987, 917988, 917989, 917990, 917991, 917992, 917993, 917994, 917995, 917996, 917997, 917998, 917999];
+
+function calculateOutput(input) {
+  const output = {
+    chars: [], // Visual representation of the code point
+    points: [], // Unicode code point, in hex
+    pointsBinary: [], // Unicode code point, in binary
+    bytes: [] // UTF-8 encoding of the code point, in binary
+  };
+
+  for (const char of input) {
+    const point = char.codePointAt(0);
+    const pointHex = point.toString(16);
+    const pointBinary = point.toString(2);
+
+    output.chars.push(invisibleCharacters.includes(point) ? '◌' : char); // Replace whitespace with ◌
+    output.points.push(`U+${pointHex.toUpperCase()}`);
+    output.pointsBinary.push(pointBinary.padStart(8 * Math.ceil(pointBinary.length / 8), '0'));
+    output.bytes.push(...[...encoder.encode(char)].map(c => c.toString(2).padStart(8, '0')));
+  }
+
+  return output;
+}
+
+function render(output) {
+  chars.textContent = output.chars.join(' | ');
+  points.textContent = output.points.join(' | ');
+  pointsBinary.textContent = output.pointsBinary.join(' | ');
+  bytes.textContent = output.bytes.join(' | ');
+}
+
+function handleChange(value) {
+  const output = calculateOutput(value);
+  render(output);
+}
+
+input.addEventListener('input', e => handleChange(e.target.value));
+handleChange(input.value); // Render the default value on first load
diff --git a/style.css b/style.css
new file mode 100644 (file)
index 0000000..5f73b4e
--- /dev/null
+++ b/style.css
@@ -0,0 +1,68 @@
+body {
+  font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
+  background: radial-gradient(circle at 50% 150px, hsl(222deg 50% 25%) 5%, hsl(222deg 50% 10%));
+  color: hsl(222deg 50% 95%);
+  min-block-size: 100dvh;
+  margin: 0;
+  display: grid;
+  grid-template-rows: auto auto 1fr auto;
+}
+
+header,
+footer {
+  display: flex;
+  justify-content: center;
+}
+
+main {
+  inline-size: min(1080px, 100% - 16px);
+  margin-inline: auto;
+}
+
+input {
+  display: block;
+  background: hsl(222deg 50% 95%);
+  color: hsl(222deg 50% 10%);
+  inline-size: 4rem;
+  block-size: 4rem;
+  margin-inline: auto;
+  padding: 4px 8px 10px 7px;
+  border-radius: 4px;
+  border: none;
+  font-size: 3rem;
+  transition: filter 800ms;
+}
+
+input:hover,
+input:focus-within {
+  filter: drop-shadow(0 0 8px hsl(222deg 50% 50%));
+  transition: filter 400ms;
+}
+
+footer {
+  margin-block: 8px;
+}
+
+a {
+  line-height: 0;
+  background: hsl(222deg 50% 30%);
+  color: hsl(222deg 50% 95%);
+  padding: 12px;
+  border-radius: 24px;
+  transition: filter 800ms;
+}
+
+a:hover,
+a:focus-within {
+  filter: brightness(120%);
+  transition: filter 400ms;
+}
+
+a>svg {
+  transition: transform 600ms;
+}
+
+:is(a:hover, a:focus-within)>svg {
+  transform: translateY(-2px);
+  transition: transform 200ms;
+}
\ No newline at end of file
This page took 0.047979 seconds and 4 git commands to generate.