Skip to content

Commit c170ac4

Browse files
committed
add text
1 parent 51cb3f7 commit c170ac4

2 files changed

Lines changed: 127 additions & 16 deletions

File tree

js/annotation_editor.html

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,25 @@
9494
cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" fill="none" stroke="%23DC2626" stroke-width="2"/></svg>') 12 12, crosshair;
9595
}
9696

97+
#annotation-canvas.tool-text {
98+
cursor: text;
99+
}
100+
101+
#text-input {
102+
position: absolute;
103+
display: none;
104+
background: transparent;
105+
border: 1px dashed var(--color-accent);
106+
border-radius: 2px;
107+
color: var(--color-accent);
108+
font-family: var(--font-family);
109+
font-weight: bold;
110+
outline: none;
111+
padding: 2px 4px;
112+
min-width: 100px;
113+
z-index: 10;
114+
}
115+
97116
#toolbar {
98117
position: fixed;
99118
bottom: 32px;
@@ -321,6 +340,7 @@
321340
<div class="shortcuts">
322341
<span><span class="shortcut-key">A</span> Arrow</span>
323342
<span><span class="shortcut-key">R</span> Rectangle</span>
343+
<span><span class="shortcut-key">T</span> Text</span>
324344
<span><span class="shortcut-key">Ctrl+Z</span> Undo</span>
325345
<span><span class="shortcut-key">Esc</span> Cancel</span>
326346
<span><span class="shortcut-key">Ctrl+Enter</span> Save</span>
@@ -329,6 +349,7 @@
329349

330350
<div id="canvas-container">
331351
<canvas id="annotation-canvas"></canvas>
352+
<input type="text" id="text-input" placeholder="Type text..." />
332353
</div>
333354

334355
<div id="toolbar">
@@ -344,6 +365,13 @@
344365
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
345366
</svg>
346367
</button>
368+
<button class="tool-button" id="text-tool" data-tooltip="Text">
369+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
370+
<polyline points="4 7 4 4 20 4 20 7"></polyline>
371+
<line x1="9.5" y1="20" x2="14.5" y2="20"></line>
372+
<line x1="12" y1="4" x2="12" y2="20"></line>
373+
</svg>
374+
</button>
347375
<button class="tool-button" id="undo-button" data-tooltip="Undo (Ctrl+Z)" disabled>
348376
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
349377
<polyline points="1 4 1 10 7 10"></polyline>

js/annotation_editor.js

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
const canvas = document.getElementById('annotation-canvas');
66
const ctx = canvas.getContext('2d');
77

8-
let currentTool = 'arrow'; // 'arrow' or 'rectangle'
8+
let currentTool = 'arrow'; // 'arrow', 'rectangle', or 'text'
99
let isDrawing = false;
10+
let isEditingText = false;
1011
let startX, startY;
1112
let annotations = []; // Store all drawn annotations
1213
let baseImage = null; // Store the original screenshot
@@ -15,6 +16,11 @@
1516
// Drawing state
1617
const ANNOTATION_COLOR = '#DC2626';
1718
const LINE_WIDTH = 3;
19+
const TEXT_FONT_SIZE = 20;
20+
const TEXT_FONT = `bold ${TEXT_FONT_SIZE}px 'IBM Plex Sans', sans-serif`;
21+
22+
// Text input element
23+
const textInput = document.getElementById('text-input');
1824

1925
// Initialize editor with screenshot data
2026
function initEditor(screenshotDataUrl) {
@@ -76,6 +82,8 @@
7682
drawArrow(annotation.startX, annotation.startY, annotation.endX, annotation.endY);
7783
} else if (annotation.type === 'rectangle') {
7884
drawRectangle(annotation.startX, annotation.startY, annotation.width, annotation.height);
85+
} else if (annotation.type === 'text') {
86+
drawText(annotation.x, annotation.y, annotation.text);
7987
}
8088
}
8189

@@ -112,6 +120,13 @@
112120
ctx.stroke();
113121
}
114122

123+
// Draw text
124+
function drawText(x, y, text) {
125+
ctx.font = TEXT_FONT;
126+
ctx.fillStyle = ANNOTATION_COLOR;
127+
ctx.fillText(text, x, y);
128+
}
129+
115130
// Get mouse position relative to canvas
116131
function getMousePos(e) {
117132
const rect = canvas.getBoundingClientRect();
@@ -124,12 +139,76 @@
124139
};
125140
}
126141

142+
// Show text input at a given position on the canvas
143+
function showTextInput(canvasX, canvasY) {
144+
const rect = canvas.getBoundingClientRect();
145+
const scaleX = rect.width / canvas.width;
146+
const scaleY = rect.height / canvas.height;
147+
148+
// Position input in CSS coordinates, offset up by font size so text baseline aligns
149+
const displayFontSize = TEXT_FONT_SIZE * scaleY;
150+
textInput.style.left = (canvasX * scaleX) + 'px';
151+
textInput.style.top = (canvasY * scaleY - displayFontSize) + 'px';
152+
textInput.style.fontSize = displayFontSize + 'px';
153+
textInput.style.display = 'block';
154+
textInput.value = '';
155+
isEditingText = true;
156+
// Defer focus to next tick so the mousedown event finishes first
157+
setTimeout(() => textInput.focus(), 0);
158+
}
159+
160+
// Hide text input and commit or discard
161+
function hideTextInput(commit) {
162+
if (!isEditingText) return;
163+
const text = textInput.value.trim();
164+
textInput.style.display = 'none';
165+
textInput.value = '';
166+
isEditingText = false;
167+
168+
if (commit && text) {
169+
annotations.push({
170+
type: 'text',
171+
x: startX,
172+
y: startY,
173+
text: text
174+
});
175+
redrawCanvas();
176+
updateUndoButton();
177+
}
178+
}
179+
180+
// Text input event handlers
181+
textInput.addEventListener('keydown', (e) => {
182+
if (e.key === 'Enter') {
183+
e.preventDefault();
184+
hideTextInput(true);
185+
} else if (e.key === 'Escape') {
186+
e.preventDefault();
187+
hideTextInput(false);
188+
}
189+
e.stopPropagation(); // Prevent global shortcuts while typing
190+
});
191+
127192
// Mouse event handlers
128193
canvas.addEventListener('mousedown', (e) => {
129-
isDrawing = true;
194+
if (isEditingText) {
195+
// Clicking canvas while editing text commits it
196+
hideTextInput(true);
197+
return;
198+
}
199+
130200
const pos = getMousePos(e);
131201
startX = pos.x;
132202
startY = pos.y;
203+
204+
if (currentTool === 'text') {
205+
// Prevent canvas from stealing focus from the text input
206+
e.preventDefault();
207+
showTextInput(pos.x, pos.y);
208+
return;
209+
}
210+
211+
isDrawing = true;
133212
});
134213

135214
canvas.addEventListener('mousemove', (e) => {
@@ -184,20 +263,19 @@
184263
updateUndoButton();
185264
});
186265

187-
// Tool selection
188-
document.getElementById('arrow-tool').addEventListener('click', () => {
189-
currentTool = 'arrow';
190-
document.getElementById('arrow-tool').classList.add('active');
191-
document.getElementById('rectangle-tool').classList.remove('active');
192-
canvas.className = 'tool-arrow';
193-
});
266+
// Tool selection helper
267+
function setActiveTool(tool) {
268+
hideTextInput(false);
269+
currentTool = tool;
270+
document.getElementById('arrow-tool').classList.toggle('active', tool === 'arrow');
271+
document.getElementById('rectangle-tool').classList.toggle('active', tool === 'rectangle');
272+
document.getElementById('text-tool').classList.toggle('active', tool === 'text');
273+
canvas.className = 'tool-' + tool;
274+
}
194275

195-
document.getElementById('rectangle-tool').addEventListener('click', () => {
196-
currentTool = 'rectangle';
197-
document.getElementById('rectangle-tool').classList.add('active');
198-
document.getElementById('arrow-tool').classList.remove('active');
199-
canvas.className = 'tool-rectangle';
200-
});
276+
document.getElementById('arrow-tool').addEventListener('click', () => setActiveTool('arrow'));
277+
document.getElementById('rectangle-tool').addEventListener('click', () => setActiveTool('rectangle'));
278+
document.getElementById('text-tool').addEventListener('click', () => setActiveTool('text'));
201279

202280
// Undo button
203281
document.getElementById('undo-button').addEventListener('click', undo);
@@ -239,13 +317,18 @@
239317

240318
// Keyboard shortcuts
241319
document.addEventListener('keydown', (e) => {
320+
// Skip global shortcuts while editing text (handled by textInput's own keydown)
321+
if (isEditingText) return;
322+
242323
if (e.key === 'Escape') {
243324
document.getElementById('cancel-button').click();
244325
} else if (e.key === 'a' || e.key === 'A') {
245326
document.getElementById('arrow-tool').click();
246327
} else if (e.key === 'r' || e.key === 'R') {
247328
document.getElementById('rectangle-tool').click();
248-
} else if (e.key === 'z' && (e.ctrlKey || e.metaKey)) {
329+
} else if (e.key === 't' || e.key === 'T') {
330+
document.getElementById('text-tool').click();
331+
} else if (e.key === 'z' && (e.ctrlKey || e.metaKey)) {
249332
e.preventDefault();
250333
undo();
251334
} else if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {

0 commit comments

Comments
 (0)