You'll want to get rid of glTexImage2D completely except for application startup (allocate a pool of N images up front, then re-use them and update with glTexSubImage2D). And short of being able to optimize the text render, which seems to be awfully stupid, you'll want to render offscreen to those textures ahead of time before you need to render them on-screen.
To be fair, you're crazy CPU-bound. This workload is peanuts for a modern GPU and there's no excuse for it not running at 500+ fps. But that's just how JS goes. You'd probably have better luck with C/wasm for this kind of thing if the web is your target.