Add look-up table for JSON ASCII escapes

On a mid-range x86_64 laptop, processing the 181 MiB citylots.json file
from github.com/zemirco/sf-city-lots-json:

$ time gen/bin/example-jsonptr < citylots.json > /dev/null
Before this commit:
real    0m1.799s
After:
real    0m1.765s
Ratio: 1.02x
diff --git a/example/cbor-to-json/cbor-to-json.cc b/example/cbor-to-json/cbor-to-json.cc
index da9df53..e08e64c 100644
--- a/example/cbor-to-json/cbor-to-json.cc
+++ b/example/cbor-to-json/cbor-to-json.cc
@@ -120,6 +120,138 @@
 
 // ----
 
+// ascii_escapes was created by script/print-json-ascii-escapes.go.
+const uint8_t ascii_escapes[1024] = {
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x30, 0x00,  // 0x00: "\\u0000"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x31, 0x00,  // 0x01: "\\u0001"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x32, 0x00,  // 0x02: "\\u0002"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x33, 0x00,  // 0x03: "\\u0003"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x34, 0x00,  // 0x04: "\\u0004"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x35, 0x00,  // 0x05: "\\u0005"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x36, 0x00,  // 0x06: "\\u0006"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x37, 0x00,  // 0x07: "\\u0007"
+    0x02, 0x5C, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x08: "\\b"
+    0x02, 0x5C, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x09: "\\t"
+    0x02, 0x5C, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x0A: "\\n"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x42, 0x00,  // 0x0B: "\\u000B"
+    0x02, 0x5C, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x0C: "\\f"
+    0x02, 0x5C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x0D: "\\r"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x45, 0x00,  // 0x0E: "\\u000E"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x46, 0x00,  // 0x0F: "\\u000F"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x30, 0x00,  // 0x10: "\\u0010"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x31, 0x00,  // 0x11: "\\u0011"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x32, 0x00,  // 0x12: "\\u0012"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x33, 0x00,  // 0x13: "\\u0013"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x34, 0x00,  // 0x14: "\\u0014"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x35, 0x00,  // 0x15: "\\u0015"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x36, 0x00,  // 0x16: "\\u0016"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x37, 0x00,  // 0x17: "\\u0017"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x38, 0x00,  // 0x18: "\\u0018"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x39, 0x00,  // 0x19: "\\u0019"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x41, 0x00,  // 0x1A: "\\u001A"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x42, 0x00,  // 0x1B: "\\u001B"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x43, 0x00,  // 0x1C: "\\u001C"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x44, 0x00,  // 0x1D: "\\u001D"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x45, 0x00,  // 0x1E: "\\u001E"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x46, 0x00,  // 0x1F: "\\u001F"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x32, 0x30, 0x00,  // 0x20: "\\u0020"
+    0x01, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x21: "!"
+    0x02, 0x5C, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x22: "\\\""
+    0x01, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x23: "#"
+    0x01, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x24: "$"
+    0x01, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x25: "%"
+    0x01, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x26: "&"
+    0x01, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x27: "'"
+    0x01, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x28: "("
+    0x01, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x29: ")"
+    0x01, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x2A: "*"
+    0x01, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x2B: "+"
+    0x01, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x2C: ","
+    0x01, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x2D: "-"
+    0x01, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x2E: "."
+    0x01, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x2F: "/"
+    0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x30: "0"
+    0x01, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x31: "1"
+    0x01, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x32: "2"
+    0x01, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x33: "3"
+    0x01, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x34: "4"
+    0x01, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x35: "5"
+    0x01, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x36: "6"
+    0x01, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x37: "7"
+    0x01, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x38: "8"
+    0x01, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x39: "9"
+    0x01, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x3A: ":"
+    0x01, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x3B: ";"
+    0x01, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x3C: "<"
+    0x01, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x3D: "="
+    0x01, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x3E: ">"
+    0x01, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x3F: "?"
+    0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x40: "@"
+    0x01, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x41: "A"
+    0x01, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x42: "B"
+    0x01, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x43: "C"
+    0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x44: "D"
+    0x01, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x45: "E"
+    0x01, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x46: "F"
+    0x01, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x47: "G"
+    0x01, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x48: "H"
+    0x01, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x49: "I"
+    0x01, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x4A: "J"
+    0x01, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x4B: "K"
+    0x01, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x4C: "L"
+    0x01, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x4D: "M"
+    0x01, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x4E: "N"
+    0x01, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x4F: "O"
+    0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x50: "P"
+    0x01, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x51: "Q"
+    0x01, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x52: "R"
+    0x01, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x53: "S"
+    0x01, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x54: "T"
+    0x01, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x55: "U"
+    0x01, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x56: "V"
+    0x01, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x57: "W"
+    0x01, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x58: "X"
+    0x01, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x59: "Y"
+    0x01, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x5A: "Z"
+    0x01, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x5B: "["
+    0x02, 0x5C, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x5C: "\\\\"
+    0x01, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x5D: "]"
+    0x01, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x5E: "^"
+    0x01, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x5F: "_"
+    0x01, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x60: "`"
+    0x01, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x61: "a"
+    0x01, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x62: "b"
+    0x01, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x63: "c"
+    0x01, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x64: "d"
+    0x01, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x65: "e"
+    0x01, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x66: "f"
+    0x01, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x67: "g"
+    0x01, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x68: "h"
+    0x01, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x69: "i"
+    0x01, 0x6A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x6A: "j"
+    0x01, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x6B: "k"
+    0x01, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x6C: "l"
+    0x01, 0x6D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x6D: "m"
+    0x01, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x6E: "n"
+    0x01, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x6F: "o"
+    0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x70: "p"
+    0x01, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x71: "q"
+    0x01, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x72: "r"
+    0x01, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x73: "s"
+    0x01, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x74: "t"
+    0x01, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x75: "u"
+    0x01, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x76: "v"
+    0x01, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x77: "w"
+    0x01, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x78: "x"
+    0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x79: "y"
+    0x01, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x7A: "z"
+    0x01, 0x7B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x7B: "{"
+    0x01, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x7C: "|"
+    0x01, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x7D: "}"
+    0x01, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x7E: "~"
+    0x01, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x7F: "<DEL>"
+};
+
 #define NEW_LINE_THEN_256_SPACES                                               \
   "\n                                                                        " \
   "                                                                          " \
@@ -491,7 +623,8 @@
         uint8_t c = ptr[i];
         if ((c == '"') || (c == '\\') || (c < 0x20)) {
           TRY(write_dst(ptr, i));
-          TRY(AppendAsciiByte(c));
+          TRY(write_dst(&ascii_escapes[8 * static_cast<size_t>(c) + 1],
+                        ascii_escapes[8 * static_cast<size_t>(c)]));
           ptr += i + 1;
           len -= i + 1;
           goto loop;
@@ -502,34 +635,6 @@
     return write_dst("\"", 1);
   }
 
-  std::string AppendAsciiByte(uint8_t c) {
-    switch (c) {
-      case '\b':
-        return write_dst("\\b", 2);
-      case '\f':
-        return write_dst("\\f", 2);
-      case '\n':
-        return write_dst("\\n", 2);
-      case '\r':
-        return write_dst("\\r", 2);
-      case '\t':
-        return write_dst("\\t", 2);
-      case '\"':
-        return write_dst("\\\"", 2);
-      case '\\':
-        return write_dst("\\\\", 2);
-    }
-    static const char* hex_digits = "0123456789ABCDEF";
-    uint8_t esc6[6];
-    esc6[0] = '\\';
-    esc6[1] = 'u';
-    esc6[2] = '0';
-    esc6[3] = '0';
-    esc6[4] = hex_digits[c >> 4];
-    esc6[5] = hex_digits[c & 0x1F];
-    return write_dst(&esc6[0], 6);
-  }
-
   std::string AppendMinus1MinusX(uint64_t val) override {
     TRY(WritePreambleAndUpdateContext());
     if (g_ctx == context::in_dict_after_key) {
diff --git a/example/jsonptr/jsonptr.cc b/example/jsonptr/jsonptr.cc
index bf44149..baa03b1 100644
--- a/example/jsonptr/jsonptr.cc
+++ b/example/jsonptr/jsonptr.cc
@@ -269,6 +269,138 @@
 
 // ----
 
+// ascii_escapes was created by script/print-json-ascii-escapes.go.
+const uint8_t ascii_escapes[1024] = {
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x30, 0x00,  // 0x00: "\\u0000"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x31, 0x00,  // 0x01: "\\u0001"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x32, 0x00,  // 0x02: "\\u0002"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x33, 0x00,  // 0x03: "\\u0003"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x34, 0x00,  // 0x04: "\\u0004"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x35, 0x00,  // 0x05: "\\u0005"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x36, 0x00,  // 0x06: "\\u0006"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x37, 0x00,  // 0x07: "\\u0007"
+    0x02, 0x5C, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x08: "\\b"
+    0x02, 0x5C, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x09: "\\t"
+    0x02, 0x5C, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x0A: "\\n"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x42, 0x00,  // 0x0B: "\\u000B"
+    0x02, 0x5C, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x0C: "\\f"
+    0x02, 0x5C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x0D: "\\r"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x45, 0x00,  // 0x0E: "\\u000E"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x30, 0x46, 0x00,  // 0x0F: "\\u000F"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x30, 0x00,  // 0x10: "\\u0010"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x31, 0x00,  // 0x11: "\\u0011"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x32, 0x00,  // 0x12: "\\u0012"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x33, 0x00,  // 0x13: "\\u0013"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x34, 0x00,  // 0x14: "\\u0014"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x35, 0x00,  // 0x15: "\\u0015"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x36, 0x00,  // 0x16: "\\u0016"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x37, 0x00,  // 0x17: "\\u0017"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x38, 0x00,  // 0x18: "\\u0018"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x39, 0x00,  // 0x19: "\\u0019"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x41, 0x00,  // 0x1A: "\\u001A"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x42, 0x00,  // 0x1B: "\\u001B"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x43, 0x00,  // 0x1C: "\\u001C"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x44, 0x00,  // 0x1D: "\\u001D"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x45, 0x00,  // 0x1E: "\\u001E"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x31, 0x46, 0x00,  // 0x1F: "\\u001F"
+    0x06, 0x5C, 0x75, 0x30, 0x30, 0x32, 0x30, 0x00,  // 0x20: "\\u0020"
+    0x01, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x21: "!"
+    0x02, 0x5C, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x22: "\\\""
+    0x01, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x23: "#"
+    0x01, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x24: "$"
+    0x01, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x25: "%"
+    0x01, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x26: "&"
+    0x01, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x27: "'"
+    0x01, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x28: "("
+    0x01, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x29: ")"
+    0x01, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x2A: "*"
+    0x01, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x2B: "+"
+    0x01, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x2C: ","
+    0x01, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x2D: "-"
+    0x01, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x2E: "."
+    0x01, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x2F: "/"
+    0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x30: "0"
+    0x01, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x31: "1"
+    0x01, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x32: "2"
+    0x01, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x33: "3"
+    0x01, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x34: "4"
+    0x01, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x35: "5"
+    0x01, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x36: "6"
+    0x01, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x37: "7"
+    0x01, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x38: "8"
+    0x01, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x39: "9"
+    0x01, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x3A: ":"
+    0x01, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x3B: ";"
+    0x01, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x3C: "<"
+    0x01, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x3D: "="
+    0x01, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x3E: ">"
+    0x01, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x3F: "?"
+    0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x40: "@"
+    0x01, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x41: "A"
+    0x01, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x42: "B"
+    0x01, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x43: "C"
+    0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x44: "D"
+    0x01, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x45: "E"
+    0x01, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x46: "F"
+    0x01, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x47: "G"
+    0x01, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x48: "H"
+    0x01, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x49: "I"
+    0x01, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x4A: "J"
+    0x01, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x4B: "K"
+    0x01, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x4C: "L"
+    0x01, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x4D: "M"
+    0x01, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x4E: "N"
+    0x01, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x4F: "O"
+    0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x50: "P"
+    0x01, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x51: "Q"
+    0x01, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x52: "R"
+    0x01, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x53: "S"
+    0x01, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x54: "T"
+    0x01, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x55: "U"
+    0x01, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x56: "V"
+    0x01, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x57: "W"
+    0x01, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x58: "X"
+    0x01, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x59: "Y"
+    0x01, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x5A: "Z"
+    0x01, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x5B: "["
+    0x02, 0x5C, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x5C: "\\\\"
+    0x01, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x5D: "]"
+    0x01, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x5E: "^"
+    0x01, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x5F: "_"
+    0x01, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x60: "`"
+    0x01, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x61: "a"
+    0x01, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x62: "b"
+    0x01, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x63: "c"
+    0x01, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x64: "d"
+    0x01, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x65: "e"
+    0x01, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x66: "f"
+    0x01, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x67: "g"
+    0x01, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x68: "h"
+    0x01, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x69: "i"
+    0x01, 0x6A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x6A: "j"
+    0x01, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x6B: "k"
+    0x01, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x6C: "l"
+    0x01, 0x6D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x6D: "m"
+    0x01, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x6E: "n"
+    0x01, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x6F: "o"
+    0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x70: "p"
+    0x01, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x71: "q"
+    0x01, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x72: "r"
+    0x01, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x73: "s"
+    0x01, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x74: "t"
+    0x01, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x75: "u"
+    0x01, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x76: "v"
+    0x01, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x77: "w"
+    0x01, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x78: "x"
+    0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x79: "y"
+    0x01, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x7A: "z"
+    0x01, 0x7B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x7B: "{"
+    0x01, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x7C: "|"
+    0x01, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x7D: "}"
+    0x01, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x7E: "~"
+    0x01, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0x7F: "<DEL>"
+};
+
 // Wuffs allows either statically or dynamically allocated work buffers. This
 // program exercises static allocation.
 #define WORK_BUFFER_ARRAY_SIZE \
@@ -915,49 +1047,11 @@
 
 // ----
 
-uint8_t  //
-hex_digit(uint8_t nibble) {
-  nibble &= 0x0F;
-  if (nibble <= 9) {
-    return '0' + nibble;
-  }
-  return ('A' - 10) + nibble;
-}
-
 const char*  //
 handle_unicode_code_point(uint32_t ucp) {
-  if (ucp < 0x0020) {
-    switch (ucp) {
-      case '\b':
-        return write_dst("\\b", 2);
-      case '\f':
-        return write_dst("\\f", 2);
-      case '\n':
-        return write_dst("\\n", 2);
-      case '\r':
-        return write_dst("\\r", 2);
-      case '\t':
-        return write_dst("\\t", 2);
-    }
-
-    // Other bytes less than 0x0020 are valid UTF-8 but not valid in a
-    // JSON string. They need to remain escaped.
-    uint8_t esc6[6];
-    esc6[0] = '\\';
-    esc6[1] = 'u';
-    esc6[2] = '0';
-    esc6[3] = '0';
-    esc6[4] = hex_digit(ucp >> 4);
-    esc6[5] = hex_digit(ucp >> 0);
-    return write_dst(&esc6[0], 6);
-
-  } else if (ucp == '\"') {
-    return write_dst("\\\"", 2);
-
-  } else if (ucp == '\\') {
-    return write_dst("\\\\", 2);
+  if (ucp < 0x80) {
+    return write_dst(&ascii_escapes[8 * ucp + 1], ascii_escapes[8 * ucp]);
   }
-
   uint8_t u[WUFFS_BASE__UTF_8__BYTE_LENGTH__MAX_INCL];
   size_t n = wuffs_base__utf_8__encode(
       wuffs_base__make_slice_u8(&u[0],
diff --git a/script/print-json-ascii-escapes.go b/script/print-json-ascii-escapes.go
new file mode 100644
index 0000000..40ed2a8
--- /dev/null
+++ b/script/print-json-ascii-escapes.go
@@ -0,0 +1,83 @@
+// Copyright 2020 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build ignore
+
+package main
+
+// print-json-ascii-escapes.go prints the JSON escapes for ASCII characters.
+// For example, the JSON escapes for the ASCII "horizontal tab" and
+// "substitute" control characters are "\t" and "\u001A".
+//
+// Usage: go run print-json-ascii-escapes.go
+
+import (
+	"fmt"
+	"os"
+)
+
+func main() {
+	if err := main1(); err != nil {
+		os.Stderr.WriteString(err.Error() + "\n")
+		os.Exit(1)
+	}
+}
+
+func main1() error {
+	for c := 0; c < 0x80; c++ {
+		s := ""
+		switch c {
+		case '\b':
+			s = "\\b"
+		case '\f':
+			s = "\\f"
+		case '\n':
+			s = "\\n"
+		case '\r':
+			s = "\\r"
+		case '\t':
+			s = "\\t"
+		case '"':
+			s = "\\\""
+		case '\\':
+			s = "\\\\"
+		default:
+			if c > 0x20 {
+				s = string(c)
+			} else {
+				const hex = "0123456789ABCDEF"
+				s = string([]byte{
+					'\\',
+					'u',
+					'0',
+					'0',
+					hex[c>>4],
+					hex[c&0x0F],
+				})
+			}
+		}
+		b := make([]byte, 8)
+		b[0] = byte(len(s))
+		copy(b[1:], s)
+		description := s
+		if c == 0x7F {
+			description = "<DEL>"
+		}
+		for _, x := range b {
+			fmt.Printf("0x%02X, ", x)
+		}
+		fmt.Printf("// 0x%02X: %q\n", c, description)
+	}
+	return nil
+}