Skip to content

Commit

Permalink
Merge branch 'ocr'
Browse files Browse the repository at this point in the history
  • Loading branch information
wrbl606 committed Mar 11, 2023
2 parents 44e9c4e + 30fc1c4 commit 9ccc83b
Show file tree
Hide file tree
Showing 22 changed files with 537 additions and 3,268 deletions.
Binary file not shown.
285 changes: 285 additions & 0 deletions extension/firefox/content/deps/tesseract-core@4.0.2.wasm.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions extension/firefox/content/deps/tesseract.min.js.map

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions extension/firefox/content/deps/tesseract@4.0.2.min.js

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions extension/firefox/content/deps/worker.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions extension/firefox/content/deps/worker.min.js.map

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions extension/firefox/content/deps/worker@4.0.2.min.js

Large diffs are not rendered by default.

116 changes: 109 additions & 7 deletions extension/firefox/content/page.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
--background: #E8F2EB;
--content-width: 50vw;
--spacing: 1em;
--spacing-large: 2em;
--spacing-large: 1.4em;
--border-radius: .7em;
}

Expand Down Expand Up @@ -131,7 +131,8 @@
overflow-x: hidden;
}

.export {
.export,
.ocr-info {
scroll-snap-align: start;
}

Expand Down Expand Up @@ -175,18 +176,27 @@
min-height: 100svh;
}

.ocr-info>.ocr-loading-indicator {
display: flex;
gap: var(--spacing);
align-items: center;
}

.export,
.ocr-info,
header {
display: flex;
}

.export,
.ocr-info,
header h1 {
width: var(--content-width);
margin: 0 auto;
}

.export {
.export,
.ocr-info {
box-sizing: border-box;
margin-bottom: var(--spacing);
}
Expand Down Expand Up @@ -223,8 +233,8 @@

.steps>.step {
box-sizing: border-box;
padding: var(--spacing-large);
margin-top: var(--spacing-large);
padding: var(--spacing) var(--spacing-large);
margin-top: var(--spacing);
counter-increment: step;
background-color: var(--background);
border-radius: var(--border-radius);
Expand Down Expand Up @@ -257,14 +267,46 @@
display: grid;
}

.step-image>picture>.screenshot {
.step-image>picture>.screenshot,
.step-image>picture>.scrub-overlay,
.step-image>picture>.loading-overlay {
width: 100%;
border-radius: calc(var(--border-radius) - .1em);
flex: 1;
grid-column: 1;
grid-row: 1;
}

.step-image>picture>.scrub-overlay {
position: relative;
}

.step-image>picture>.scrub-overlay>.scrub-element {
position: absolute;
box-sizing: border-box;
background-color: transparent;
border: none;
cursor: pointer;
transition: .1s;
}

.step-image>picture>.scrub-overlay>.scrub-element:hover,
.step-image>picture>.scrub-overlay>.scrub-element.scrubbed:hover {
background-color: var(--background);
border: 2px dotted var(--foreground);
opacity: .5;
}

.step-image>picture>.scrub-overlay>.scrub-element.scrubbed:hover {
opacity: .3;
border: 2px solid var(--foreground);
}

.step-image>picture>.scrub-overlay>.scrub-element.scrubbed {
background-color: var(--background);
opacity: 1;
}

.step-image>picture>.cursor {
grid-column: 1;
grid-row: 1;
Expand Down Expand Up @@ -324,6 +366,49 @@
box-sizing: border-box;
margin-top: var(--spacing-large);
text-align: end;
display: flex;
gap: 1em;
justify-content: space-between;
}

.loading-inline:after {
content: '';
box-sizing: border-box;
display: inline-block;
width: 2rem;
height: 2rem;
border-radius: 2rem;
background-color: var(--background);
border: 5px solid var(--background);
border-top-color: var(--foreground);
animation: fullSpin 1.6s ease-in-out infinite;
box-shadow: 0px 4px 0px -6px var(--foreground);
}

.loading:after {
content: '';
position: absolute;
box-sizing: border-box;
margin-top: var(--spacing);
margin-left: var(--spacing);
width: 2rem;
height: 2rem;
border-radius: 2rem;
background-color: var(--background);
border: 5px solid var(--background);
border-top-color: var(--foreground);
animation: fullSpin 1.6s ease-in-out infinite;
box-shadow: 0px 0px 12px -6px var(--foreground);
}

@keyframes fullSpin {
0% {
transform: rotate(0deg);
}

100% {
transform: rotate(360deg);
}
}
</style>
</head>
Expand All @@ -332,7 +417,18 @@
<section wtc-editor class="export hidden" aria-label="Export">
<button id="exportPdf">Export PDF</button>
<button id="exportMd">Export Markdown</button>
<button id="saveHtml">Save HTML</button>
<button id="saveHtml"
title="HTML files are editable and contain all available information, even scrubbed words. Export a PDF or Markdown file to get words scrubbed in an unrecoverable way.">
Save HTML (editable scrubs)
</button>
</section>
<section wtc-editor class="ocr-info">
<p class="ocr-loading-indicator hidden">
<span class="loading-inline"></span> Offline image analysis in progress
</p>
<p class="scrub-info">
ℹ️ After the offline image analysis is complete, you can click on a word in each screenshot to get it scrubbed.
</p>
</section>
<header>
<h1 wtc-editable autofocus>What to click:</h1>
Expand All @@ -341,6 +437,12 @@ <h1 wtc-editable autofocus>What to click:</h1>
<section class="steps"></section>
</main>
<footer>
<a wtc-editor target="blank" href="https://github.com/wrbl606/what-to-click/issues">Report an issue</a>
<span wtc-editor>
Built with
<a href="https://github.com/localForage/localForage" target="blank">localforage</a> and
<a href="https://github.com/naptha/tesseract.js" target="tesseract.js">tesseract.js</a>
</span>
<time datetime=""></time>
</footer>
<script src="page.js" type="module"></script>
Expand Down
5 changes: 5 additions & 0 deletions extension/firefox/content/page.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { updateMeta } from './page/dom/seo.js';
import { savePdf, saveMarkdown, saveHtml } from './page/export.js';
import { main } from './page/dom/init.js';
import { attachOcrInfo } from './page/ocr/worker.js';
import { attachScrubs } from './page/dom/editor.js';

window.addEventListener('load', async () => {
await main();
Expand All @@ -9,4 +11,7 @@ window.addEventListener('load', async () => {
document.getElementById('saveHtml').addEventListener('click', saveHtml);
document.querySelector('[autofocus]').focus();
document.querySelector('h1').addEventListener('keyup', updateMeta);

attachScrubs(document.querySelectorAll('.screenshot'));
attachOcrInfo(document.querySelectorAll('.screenshot'));
});
40 changes: 39 additions & 1 deletion extension/firefox/content/page/dom/editor.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,42 @@
export function deleteStep(index = -1) {
const step = document.querySelector(`[wtc-step-index="${index}"]`);
step.remove();
}
}

export function attachScrubs(screenshots = []) {
for (const screenshot of screenshots) {
const observer = new MutationObserver((mutations, observer) => {
observer.disconnect();
const words = JSON.parse(mutations[0].target.getAttribute('wtc-ocr'));
const overlay = screenshot.parentNode.querySelector('.scrub-overlay');
const parser = new DOMParser();
const sizeRatio = screenshot.clientHeight / screenshot.naturalHeight;
const clientSize = { width: screenshot.clientWidth, height: screenshot.clientHeight };
for (const { word, box } of words) {
const width = ((box.x1 - box.x0) * sizeRatio / clientSize.width) * 100;
const height = ((box.y1 - box.y0) * sizeRatio / clientSize.height) * 100;
const top = ((box.y0 * sizeRatio) / clientSize.height) * 100;
const left = ((box.x0 * sizeRatio) / clientSize.width) * 100;
const scrubElementHtml = `
<div
class="scrub-element"
wtc-word="${encodeURIComponent(JSON.stringify({ word, box }))}"
style="width: ${width}%; height: ${height}%; top: ${top}%; left: ${left}%"
></div>
`;
const scrubElement = parser.parseFromString(scrubElementHtml, 'text/html').querySelector('.scrub-element');
scrubElement.addEventListener('click', () => {
scrubElement.classList.toggle('scrubbed');
scrubElement.c
});
overlay.appendChild(scrubElement);
}
});
observer.observe(screenshot, {
childList: false,
subtree: false,
attributes: true,
attributeFilter: ['wtc-ocr']
});
}
}
4 changes: 3 additions & 1 deletion extension/firefox/content/page/dom/init.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { tagToName } from "../tagToName.js";
import { deleteStep } from "./editor.js";

async function loadImages(sessionId = new URLSearchParams(window.location.href.split('?')[1]).get('s')) {
export async function loadImages(sessionId = new URLSearchParams(window.location.href.split('?')[1]).get('s')) {
return browser.runtime.sendMessage({ type: 'fetchImages', data: { session: sessionId } });
}

Expand All @@ -18,6 +18,8 @@ function createStep({ image, target }, index) {
<div class="step-image">
<picture>
<img class="screenshot" src="${image}">
<div class="scrub-overlay"></div>
<div class="loading-overlay" wtc-editor></div>
<img class="cursor" src="${cursorPng}">
</picture>
</div>
Expand Down
34 changes: 33 additions & 1 deletion extension/firefox/content/page/export.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { loadImages } from './dom/init.js';

export async function savePdf() {
document.querySelectorAll('.screenshot').forEach(applyScrubs);
document.querySelectorAll('[wtc-editor]').forEach((element) => element.classList.add('hidden'));
const textareas = [];
document.querySelectorAll('[wtc-textarea]').forEach((textarea) => {
Expand All @@ -17,6 +20,7 @@ export async function savePdf() {
textarea.style.height = `${textarea.scrollHeight}px`;
});
document.querySelectorAll('[wtc-editor]').forEach((element) => element.classList.remove('hidden'));
await removeScrubs();
}

export function saveHtml() {
Expand All @@ -38,7 +42,8 @@ export function saveHtml() {
`;
}

export function saveMarkdown() {
export async function saveMarkdown() {
document.querySelectorAll('.screenshot').forEach(applyScrubs);
const title = document.querySelector('h1').innerText;
const descriptions = [];
document.querySelectorAll('.step-description .content').forEach((el) => descriptions.push(el.value));
Expand All @@ -50,6 +55,7 @@ export function saveMarkdown() {
${descriptions.map((content, index) => `${index + 1}. ${content} \n ![${content}](${screenshots[index]})`).join('\n\n')}
`;
download(`What to click ${new Date().toDateString()}.md`, markdown, { type: 'text/markdown' });
await removeScrubs();
}

export function download(filename, data, options = { type: 'text/html' }) {
Expand All @@ -63,3 +69,29 @@ export function download(filename, data, options = { type: 'text/html' }) {
document.body.removeChild(el);
URL.revokeObjectURL(url);
}

function applyScrubs(screenshot) {
const canvas = document.createElement('canvas');
canvas.style = "width: 100%";
const context = canvas.getContext('2d');
canvas.width = screenshot.naturalWidth;
canvas.height = screenshot.naturalHeight;
context.drawImage(screenshot, 0, 0, screenshot.naturalWidth, screenshot.naturalHeight);
const scrubs = screenshot.parentNode.querySelectorAll('.scrub-overlay>.scrubbed');
for (const scrub of scrubs) {
const { box } = JSON.parse(decodeURIComponent(scrub.getAttribute('wtc-word')));
const { x0, y0, x1, y1 } = box;
context.beginPath();
context.rect(x0, y0, x1 - x0, y1 - y0);
context.fill();
}
screenshot.src = canvas.toDataURL('image/jpeg');
}

async function removeScrubs() {
const steps = await loadImages();
const screenshots = document.querySelectorAll('.screenshot');
steps.forEach(({ image }, index) => {
screenshots[index].src = image;
});
}
50 changes: 50 additions & 0 deletions extension/firefox/content/page/ocr/worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as ocr from '../../deps/tesseract@4.0.2.min.js';

let worker;

async function initWorker() {
const w = await Tesseract.createWorker({
workerPath: './deps/worker@4.0.2.min.js',
workerBlobURL: false,
langPath: './deps',
corePath: './deps/tesseract-core@4.0.2.wasm.js',
tessedit_create_hocr: '0',
tessedit_create_tsv: '0',
tessedit_create_box: '0',
tessedit_create_unlv: '0',
tessedit_create_osd: '0',
});
await w.loadLanguage('eng-fast');
await w.initialize('eng-fast');
worker = w;
return w;
}

async function recognizeWords(element) {
const result = await worker.recognize(element);
return result.data.paragraphs.map(({ lines }) => {
return lines.map((line) => line.words.map((word) => {
return {
word: word.choices[0],
box: word.bbox,
};
})).flat();
}).flat();
}


export async function attachOcrInfo(screenshots) {
document.querySelector('.ocr-loading-indicator').classList.toggle('hidden');
if (worker == null) {
await initWorker();
}
for (const screenshot of screenshots) {
const overlay = screenshot.parentNode.querySelector('.loading-overlay');
overlay.classList.toggle('loading');
const words = await recognizeWords(screenshot);
const serialized = JSON.stringify(words);
screenshot.setAttribute('wtc-ocr', serialized);
overlay.classList.toggle('loading');
}
document.querySelector('.ocr-loading-indicator').classList.toggle('hidden');
}
4 changes: 2 additions & 2 deletions extension/firefox/manifest.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"manifest_version": 2,
"name": "What-to-click",
"version": "1.5.1",
"description": "Create how-to documentation in seconds.",
"version": "1.6.0",
"description": "AI-powered how-to documentation creator.",
"icons": {
"48": "icons/record.png"
},
Expand Down
Loading

0 comments on commit 9ccc83b

Please sign in to comment.