Why I Built It
When I was studying programming by following along with YouTube tutorials, there was one small but surprisingly annoying task I kept running into.
The person in the video would say:
"Okay, let's move forward with this!"
But then I realized that none of the following were provided anywhere:
- Code
- System prompts
- Other related resources
"こういうやつ" means "this".
So basically, I was thinking:
"Come on, let me copy and paste it, teacher..."
So, I had no choice but to repeat the following process over and over:
- Take a screenshot of the relevant part of the video.
- Upload it to Claude.
- Ask, "Can you transcribe the text in this image?"
- Copy and paste the result into VS Code.
...and I had to do this again and again.
Doing this every single time was just way too much of a hassle!
I found myself thinking:
"It would be nice if I could just select part of the screen and copy the text right there."
That was what gave me the idea to try building this Chrome extension.
What I Built
It's a Chrome extension called "ShoText!".
Here's how it works:
- Press
Alt+Shift+Dand drag to select any area of the screen. - The text within the selected area is automatically extracted using OCR.
- The recognized text is automatically copied to the clipboard.
That's it.
For OCR, I use Tesseract.js, the WebAssembly version of Tesseract, and everything is processed locally in the browser. Neither the image nor the OCR results are sent anywhere.
Oh, and about the name "ShoText!":
- Taking a screenshot = sounds kind of like "shot!"
- 文字 (text) = "text" in English, so I went with "Text"
Put them together, and you get "ShoText!"
I hope you'll give it some love.
Born from a One-Shot Prompt by Gemini
Tech Stack
I built it as a monorepo using a pnpm workspace, with two packages:
-
packages/core- Common logic that is independent of the browser extension
- Region selection UI
- Image cropping
- Toast notifications
- And more
-
packages/local-ocr- The extension itself, using Tesseract.js for local OCR
For the build setup, I use Vite + @crxjs/vite-plugin.
In the following sections, I'd like to share a few "aha!" moments I ran into while building it.
The Problem of Extra Spaces Between Japanese Characters in OCR
When I ran OCR on Japanese text, I ran into a strange problem: a half-width space was inserted between every character.
シ ョ ッ テ キ !
English worked just fine, so I wondered: why was this happening only with Japanese?
After looking through issues in the official Tesseract repository and asking Claude about it, I found that the preserve_interword_spaces setting was related to the problem.
// Changed from 0 to 1
await worker.setParameters({ preserve_interword_spaces: "1" });
But what exactly is preserve_interword_spaces?
I took a look at the Tesseract source code, and found this:
int numSpaces = preserve_interword_spaces_ ? it_->word()->word->space() : (words_appended > 0);
Hmm... I have absolutely no idea what that means.
So, after digging through issues in the official Tesseract repository and asking Claude to explain it to me, I learned that this setting determines how many spaces should be inserted before a word when constructing the OCR result.
With the default value (0), Tesseract doesn't take into account how much actual space there was between words in the image. Instead, it simply inserts one space between each word.
Internally, Tesseract treats individual characters (or groups of characters) as a single "word." In Japanese text, the gap between adjacent characters is usually close to zero. However, with the default setting, Tesseract ignores the actual spacing and unconditionally inserts one space between each "word."
That's what was causing the extra space between every Japanese character.
By setting preserve_interword_spaces: "1", numSpaces becomes it_->word()->word->space(), which means Tesseract uses the actual amount of spacing detected in the image.
Since the spacing between Japanese characters is usually close to zero, this prevents the unwanted spaces from being inserted.
There might still be some spacing depending on the font—for example, with a monospaced font, extra spaces might appear.
Conclusion
I originally built this Chrome extension to eliminate the small but tedious task of taking screenshots of text from videos and asking Claude to transcribe them. But along the way, I learned a lot about things like the performance characteristics of different libraries, and the differences between OCR models.
I'll keep working to improve my skills.
If you get a chance, give it a try!



Top comments (0)