'+
46 | ' '+
49 | ' ';
50 | }
51 |
--------------------------------------------------------------------------------
/www/css/cordova-plugin-sketch.css:
--------------------------------------------------------------------------------
1 | *, *::after, *::before {
2 | box-sizing: inherit;
3 | }
4 |
5 | .cordova-plugin-sketch-layout-withOrientation {
6 | flex-direction: column;
7 | }
8 | .cordova-plugin-sketch-layout-oppositeOrientation {
9 | flex-direction: row;
10 | }
11 | @media (orientation: landscape) {
12 | .cordova-plugin-sketch-layout-withOrientation {
13 | flex-direction: row;
14 | }
15 | .cordova-plugin-sketch-layout-oppositeOrientation {
16 | flex-direction: column;
17 | }
18 | }
19 |
20 | .cordova-plugin-sketch-nativePopUp {
21 | background: #000;
22 | box-sizing: border-box;
23 | display: none;
24 | height: 100%;
25 | left: 0;
26 | margin: 0;
27 | position: fixed;
28 | top: 0;
29 | width: 100%;
30 | z-index: 99999;
31 | }
32 |
33 | .cordova-plugin-sketch-nativePopUp-open {
34 | display: flex;
35 | }
36 |
37 | @media (orientation: landscape) {}
38 |
39 | .cordova-plugin-sketch-nativePopUp__button,
40 | .cordova-plugin-sketch-nativePopUp__content {
41 | margin: 0px;
42 | }
43 |
44 | .cordova-plugin-sketch-nativePopUp__content {
45 | background: #000;
46 | flex-grow: 1;
47 | flex-shrink: 1;
48 | }
49 |
50 | .cordova-plugin-sketch-nativePopUp__buttons {
51 | display: flex;
52 | flex-grow: 0;
53 | flex-shrink: 1;
54 | }
55 |
56 | .cordova-plugin-sketch-nativePopUp__button {
57 | flex-grow: 1;
58 | flex-shrink: 1;
59 | min-height: 44px;
60 | min-width: 44px;
61 | }
62 |
63 | button[data-action=cancel] {
64 | }
65 | button[data-action=done] {
66 | }
67 |
--------------------------------------------------------------------------------
/www/css/program.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | *
4 | {
5 | overflow: hidden;
6 | -ms-content-zooming: none;
7 | }
8 |
9 | body
10 | {
11 | touch-action: none;
12 | }
13 |
14 | #canvasGroup
15 | {
16 | display: block;
17 | position: fixed;
18 | left: 0px;
19 | right: 0px;
20 | top: 0px;
21 | bottom: 0px;
22 | width: 100%;
23 | height: 100%;
24 | overflow: scroll;
25 | z-index: 0;
26 | }
27 |
28 | div.rectangle
29 | {
30 | display: block;
31 | position: absolute;
32 | transform-origin: 0% 0%;
33 | touch-action: none;
34 | }
35 |
36 | canvas.surface
37 | {
38 | width: 100%;
39 | height: 100%;
40 | display: block;
41 | position: absolute;
42 | }
43 |
44 | #HighlightCanvas
45 | {
46 | background-color: White;
47 | z-index: 1;
48 | }
49 |
50 | #InkCanvas
51 | {
52 | background-color: White;
53 | z-index: 2;
54 | }
55 |
56 | #SelectCanvas
57 | {
58 | background-color: rgba(0, 0, 0, 0.0);
59 | z-index: 3;
60 | }
61 |
62 | #SelectionBox
63 | {
64 | background-color: rgba(0, 0, 0, 0.0);
65 | z-index: 4;
66 | }
67 |
68 | #statusMessage
69 | {
70 | position: relative;
71 | left: 125px;
72 | top: 70px;
73 | z-index: 10;
74 | color: black;
75 | font-size: 16pt;
76 | height: 32px;
77 | padding: 0px;
78 | border: 0px;
79 | }
80 |
81 | #RecoFlyout
82 | {
83 | color: black;
84 | }
85 |
86 | #Word
87 | {
88 | position: absolute;
89 | z-index: 20;
90 | visibility: hidden;
91 | }
92 |
93 | #Black {color: Black;}
94 | #Blue {color: Blue; }
95 | #Red {color: Red; }
96 | #Green {color: Green;}
97 | #Yellow {color: Yellow;}
98 |
99 | #Aqua {background-color: Aqua; }
100 | #Lime {background-color: Lime; }
101 |
--------------------------------------------------------------------------------
/www/images/Erase.cur:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oneblink/cordova-plugin-sketch/b5c542d4597f2b0b572a1e986c5a38736ad0f32c/www/images/Erase.cur
--------------------------------------------------------------------------------
/www/images/Select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oneblink/cordova-plugin-sketch/b5c542d4597f2b0b572a1e986c5a38736ad0f32c/www/images/Select.png
--------------------------------------------------------------------------------
/www/program.js:
--------------------------------------------------------------------------------
1 | // We are using Windows.UI.Input.Inking.InkManager.
2 |
3 | function showMessage(message, isError)
4 | {
5 | var statusDiv = document.getElementById("statusMessage");
6 | if (statusDiv)
7 | {
8 | statusDiv.innerText = message;
9 | statusDiv.style.color = isError ? "blue" : "green";
10 | }
11 | }
12 |
13 | function displayStatus(message)
14 | {
15 | showMessage(message, false);
16 | }
17 |
18 | function displayError(message)
19 | {
20 | showMessage(message, true);
21 | }
22 |
23 | window.onerror = function (msg, url, line) { displayError("Error: " + msg + " url = " + url + " line = " + line); };
24 |
25 | // Functions to convert from and to the 32-bit int used to represent color in Windows.UI.Input.Inking.InkManager.
26 |
27 | // Convenience function used by color converters.
28 | // Assumes arg num is a number (0..255); we convert it into a 2-digit hex string.
29 |
30 | function byteHex(num)
31 | {
32 | var hex = num.toString(16);
33 | if (hex.length === 1)
34 | {
35 | hex = "0" + hex;
36 | }
37 | return hex;
38 | }
39 |
40 | // Convert from Windows.UI.Input.Inking's color code to html's color hex string.
41 |
42 | function toColorString(color)
43 | {
44 | return "#" + byteHex(color.r) + byteHex(color.g) + byteHex(color.b);
45 | }
46 |
47 | // Convert from the few color names used in this app to Windows.UI.Input.Inking's color code.
48 | // If it isn't one of those, then decode the hex string. Otherwise return gray.
49 | // The alpha component is always set to full (255).
50 | function toColorStruct(color)
51 | {
52 | switch (color)
53 | {
54 | // Ink colors
55 | case "Black":
56 | return Windows.UI.Colors.black;
57 | case "Blue":
58 | return Windows.UI.Colors.blue;
59 | case "Red":
60 | return Windows.UI.Colors.red;
61 | case "Yellow":
62 | return Windows.UI.Colors.yellow;
63 | case "Green":
64 | return Windows.UI.Colors.green;
65 |
66 | // Highlighting colors
67 | case "Aqua":
68 | return Windows.UI.Colors.aqua;
69 | case "Lime":
70 | return Windows.UI.Colors.lime;
71 |
72 | // Select colors
73 | case "Gold":
74 | return Windows.UI.Colors.gold;
75 |
76 | case "White":
77 | return Windows.UI.Colors.white;
78 | }
79 |
80 | if ((color.length === 7) && (color.charAt(0) === "#"))
81 | {
82 | var R = parseInt(color.substr(1, 2), 16);
83 | var G = parseInt(color.substr(3, 2), 16);
84 | var B = parseInt(color.substr(5, 2), 16);
85 | return Windows.UI.ColorHelper.fromArgb(255, R, G, B);
86 | }
87 |
88 | return Windows.UI.Colors.gray;
89 | }
90 |
91 | // Global variable representing the application.
92 | var app;
93 |
94 | // Global variables representing the ink interface.
95 | // The usage of a global variable for drawingAttributes is not completely necessary,
96 | // just a convenience. One could always re-fetch the current drawingAttributes
97 | // from the inkManager.
98 | var inkManager = new Windows.UI.Input.Inking.InkManager();
99 | var drawingAttributes = new Windows.UI.Input.Inking.InkDrawingAttributes();
100 | drawingAttributes.fitToCurve = true;
101 | inkManager.setDefaultDrawingAttributes(drawingAttributes);
102 |
103 | // These are the global canvases (and their 2D contexts) for highlighting, for drawing ink,
104 | // and for lassoing (and erasing).
105 | var hlCanvas;
106 | var hlContext;
107 | var inkCanvas;
108 | var inkContext;
109 | var selCanvas;
110 | var selContext;
111 |
112 | // The "mode" of whether we are highlighting, inking, lassoing, or erasing is controlled by this global variable,
113 | // which should be pointing to either hlContext, inkContext, or selContext.
114 | // In lassoing mode (when context points to selContext), we might also be in erasing mode;
115 | // the state of lassoing vs. erasing is kept inside the ink manager, in attribute "mode", which will
116 | // have a value from enum Windows.UI.Input.Inking.InkManipulationMode, one of either "selecting"
117 | // or "erasing" (the other value being "inking" but in that case context will be pointing to one of the other
118 | // 2 canvases).
119 | var context;
120 |
121 | // Three functions to save and restore the current mode, and to clear this state.
122 |
123 | // Note that we can get into erasing mode in one of two ways: there is a eraser button in the toolbar,
124 | // and some pens have an active back end that is meant to represent erasing. If we get into erasing
125 | // mode via the button, we stay in that mode until another button is pushed. If we get into erasing
126 | // mode via the eraser end of the stylus, we should switch out of it when the user switches to the ink
127 | // end of the stylus. And we want to return to the mode we were in before this happened. Thus we
128 | // maintain a shallow stack (depth 1) of "mode" info.
129 |
130 | var savedContext = null;
131 | var savedStyle = null;
132 | var savedCursor = null;
133 | var savedMode = null;
134 |
135 | function clearMode()
136 | {
137 | //appbar.hide();
138 | savedContext = null;
139 | savedStyle = null;
140 | savedCursor = null;
141 | savedMode = null;
142 | }
143 |
144 | function saveMode()
145 | {
146 | if (!savedContext)
147 | {
148 | savedStyle = context.strokeStyle;
149 | savedContext = context;
150 | savedCursor = selCanvas.style.cursor;
151 | savedMode = inkManager.mode;
152 | }
153 | }
154 |
155 | function restoreMode()
156 | {
157 | if (savedContext)
158 | {
159 | context = savedContext;
160 | context.strokeStyle = savedStyle;
161 | inkManager.mode = savedMode;
162 | selCanvas.style.cursor = savedCursor;
163 | clearMode();
164 | }
165 | }
166 |
167 | // Global variable representing the pattern used when in select mode. This is an 8*1 image with 4 bits set,
168 | // then 4 bits cleared, to give us a dashed line when drawing a lasso.
169 | var selPattern;
170 |
171 | // Global variable representing the application toolbar at the bottom of the screen.
172 | var appbar;
173 |
174 | // Global pointers to flyouts invoked by the appbar.
175 | var findFlyout;
176 | var inkColorsFlyout;
177 | var inkWidthsFlyout;
178 | var hlColorsFlyout;
179 | var hlWidthsFlyout;
180 | var moreFlyout;
181 |
182 | // Global pointer to the flyout used for displaying recognition results (top 5 alternates),
183 | // and an array of buttons (one per alternate).
184 | var recoFlyout;
185 | var clipButtons;
186 |
187 | // Global pointer to the invisible
that marks the location of the currently selected word.
188 | var wordDiv;
189 |
190 | // Global pointer to the text buffer inside the Find flyout.
191 | var findText;
192 |
193 | // Returns true if any strokes inside the ink manager are selected; false otherwise.
194 | function anySelected()
195 | {
196 | var strokes = inkManager.getStrokes();
197 | var len = strokes.length;
198 | for (var i = 0; i < len; i++)
199 | {
200 | if (strokes[i].selected)
201 | {
202 | return true;
203 | }
204 | }
205 | return false;
206 | }
207 |
208 | //Returns true if this stroke is a highlighting stroke.
209 | function isHighlighting(stroke)
210 | {
211 | var att = stroke.drawingAttributes;
212 | return att.color.a < 200;
213 | }
214 |
215 | // Makes all strokes a part of the selection.
216 | function selectAll()
217 | {
218 | inkManager.getStrokes().forEach(function (stroke) {
219 | stroke.selected = true;
220 | });
221 | }
222 |
223 | // Makes all non-highlight strokes a part of the selection.
224 | function selectAllNoHighlight()
225 | {
226 | inkManager.getStrokes().forEach(function (stroke) {
227 | if (!isHighlighting(stroke)) {
228 | stroke.selected = true;
229 | }
230 | });
231 | }
232 |
233 | // Unselects any strokes which are highlighting.
234 | function unselectHighlight()
235 | {
236 | inkManager.getStrokes().forEach(function (stroke) {
237 | if (stroke.selected && isHighlighting(stroke)) {
238 | stroke.selected = false;
239 | }
240 | });
241 | }
242 |
243 | // Returns true if the point represented by x,y is within the rect.
244 | function inRect(x, y, rect)
245 | {
246 | return ((rect.x <= x) && (x < (rect.x + rect.width)) &&
247 | (rect.y <= y) && (y < (rect.y + rect.height)));
248 | }
249 |
250 | // Tests the array of results bounding boxes (from the recognition results on the ink manager).
251 | // Returns an object representing the results, with the original touch coordinates, the bounding
252 | // box, the index of the result, the array of strokes, and the array of alternates (recognition strings).
253 | // If recognition has not run or touch is outside of any word bounding box, then returns null.
254 | function hitTest(tx, ty)
255 | {
256 | var results = inkManager.getRecognitionResults();
257 | var cWords = results.size;
258 |
259 | if (cWords === 0)
260 | {
261 | return null;
262 | }
263 |
264 | for (var i = 0; i < cWords; i++)
265 | {
266 | var rect = results[i].boundingRect;
267 | if (inRect(tx, ty, rect))
268 | {
269 | return {
270 | index: i,
271 | handleX: tx, // Original touch point
272 | handleY: ty,
273 | strokes: results[i].getStrokes(),
274 | rect: rect,
275 | alternates: results[i].getTextCandidates()
276 | };
277 | }
278 | }
279 | return null;
280 | }
281 |
282 | // Note that we cannot just set the width in stroke.drawingAttributes.size.width,
283 | // or the color in stroke.drawingAttributes.color.
284 | // The stroke API supports get and put operations for drawingAttributes,
285 | // but we must execute those operations separately, and change any values
286 | // inside drawingAttributes between those operations.
287 |
288 | // Change the color and width in the default (used for new strokes) to the values
289 | // currently set in the current context.
290 | function setDefaults()
291 | {
292 | var strokeSize = drawingAttributes.size;
293 | strokeSize.width = strokeSize.height = context.lineWidth;
294 | drawingAttributes.size = strokeSize;
295 |
296 | var color = toColorStruct(context.strokeStyle);
297 | color.a = (context === hlContext) ? 128 : 255;
298 | drawingAttributes.color = color;
299 | inkManager.setDefaultDrawingAttributes(drawingAttributes);
300 | }
301 |
302 | // Four functions to switch back and forth between ink mode, highlight mode, select mode, and erase mode.
303 | // There is also a temp erase mode, which uses the saveMode()/restoreMode() functions to
304 | // return us to our previous mode when done erasing. This is used for quick erasers using the back end
305 | // of the pen (for those pens that have that).
306 | // NOTE: The erase modes also attempt to set the mouse/pen cursor to the image of a chalkboard eraser
307 | // (stored in images/erase.cur), but as of this writing cursor switching is not working.
308 |
309 | function highlightMode()
310 | {
311 | clearMode();
312 | context = hlContext;
313 | inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.inking;
314 | setDefaults();
315 | selCanvas.style.cursor = "default";
316 | }
317 |
318 | function inkMode()
319 | {
320 | clearMode();
321 | context = inkContext;
322 | inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.inking;
323 | setDefaults();
324 | selCanvas.style.cursor = "default";
325 | }
326 |
327 | function selectMode()
328 | {
329 | clearMode();
330 | selContext.strokeStyle = selPattern;
331 | context = selContext;
332 | inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.selecting;
333 | selCanvas.style.cursor = "default";
334 | }
335 |
336 | function eraseMode()
337 | {
338 | clearMode();
339 | selContext.strokeStyle = "rgba(255,255,255,0.0)";
340 | context = selContext;
341 | inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.erasing;
342 | selCanvas.style.cursor = "url(/www/cordova-plugin-sketch/images/erase.cur), auto";
343 | }
344 |
345 | function tempEraseMode()
346 | {
347 | saveMode();
348 | selContext.strokeStyle = "rgba(255,255,255,0.0)";
349 | context = selContext;
350 | inkManager.mode = inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.erasing;
351 | selCanvas.style.cursor = "url(/www/cordova-plugin-sketch/images/erase.cur), auto";
352 | }
353 |
354 | // Set the width of a stroke. Return true if we actually changed it.
355 | // Note that we cannot just set the width in stroke.drawingAttributes.size.width.
356 | // The stroke API supports get and put operations for drawingAttributes,
357 | // but we must execute those operations separately, and change any values
358 | // inside drawingAttributes between those operations.
359 | function shapeStroke(stroke, width)
360 | {
361 | var att = stroke.drawingAttributes;
362 | var strokeSize = att.size;
363 | if (strokeSize.width !== width)
364 | {
365 | strokeSize.width = strokeSize.height = width;
366 | att.size = strokeSize;
367 | stroke.drawingAttributes = att;
368 | return true;
369 | }
370 | else
371 | {
372 | return false;
373 | }
374 | }
375 |
376 | // Set the color (and alpha) of a stroke. Return true if we actually changed it.
377 | // Note that we cannot just set the color in stroke.drawingAttributes.color.
378 | // The stroke API supports get and put operations for drawingAttributes,
379 | // but we must execute those operations separately, and change any values
380 | // inside drawingAttributes between those operations.
381 | function colorStroke(stroke, color)
382 | {
383 | var att = stroke.drawingAttributes;
384 | var clr = toColorStruct(color);
385 | if (att.color !== clr)
386 | {
387 | att.color = clr;
388 | stroke.drawingAttributes = att;
389 | return true;
390 | }
391 | else
392 | {
393 | return false;
394 | }
395 | }
396 |
397 | // Global memory of the current pointID (for pen, and, separately, for touch).
398 | // We ignore handlePointerMove() and handlePointerUp() calls that don't use the same
399 | // pointID as the most recent handlePointerDown() call. This is because the user sometimes
400 | // accidentally nudges the mouse while inking or touching. This can cause move events
401 | // for that mouse that have different x,y coordinates than the ink trace or touch path
402 | // we are currently handling.
403 |
404 | // pointer* events maintain this pointId so that one can track individual fingers,
405 | // the pen, and the mouse.
406 |
407 | // Note that when the pen fails to leave the area where it can be sensed, it does NOT
408 | // get a new ID; so it is possible for 2 or more consecutive strokes to have the same ID.
409 |
410 | var penID = -1;
411 |
412 | // This global variable holds a reference to the div that is imposed on top of selected ink.
413 | // It is used to register event handlers that allow the user to move around selected ink.
414 | var selBox;
415 |
416 | // Hides the (transparent) div that is used to capture events for moving selected ink
417 | function anchorSelection() {
418 | // Make selBox of size 0 and move it to the top-left corner
419 | selBox.style.left = "0px";
420 | selBox.style.top = "0px";
421 | selBox.style.width = "0px";
422 | selBox.style.height = "0px";
423 | }
424 |
425 | // Places the (transparent) div that is used to capture events for moving selected ink.
426 | // The assumption is that rect is the bounding box of the selected ink.
427 | function detachSelection(rect) {
428 | // Move and resize selBox so that it perfectly overlaps with rect
429 | selBox.rect = rect;
430 | selBox.style.left = selBox.rect.x + "px";
431 | selBox.style.top = selBox.rect.y + "px";
432 | selBox.style.width = selBox.rect.width + "px";
433 | selBox.style.height = selBox.rect.height + "px";
434 | }
435 |
436 | // We will accept pen down or mouse left down as the start of a stroke.
437 | // We will accept touch down or mouse right down as the start of a touch.
438 | function handlePointerDown(evt)
439 | {
440 | try
441 | {
442 | //appbar.hide();
443 |
444 | if ((evt.pointerType === "pen") || ((evt.pointerType === "mouse") && (evt.button === 0)))
445 | {
446 | // Anchor and clear any current selection.
447 | anchorSelection();
448 | var pt = { x: 0.0, y: 0.0 };
449 | inkManager.selectWithLine(pt, pt);
450 |
451 | pt = evt.currentPoint;
452 |
453 | if (pt.properties.isEraser) // the back side of a pen, which we treat as an eraser
454 | {
455 | tempEraseMode();
456 | }
457 | else
458 | {
459 | restoreMode();
460 | }
461 |
462 | context.beginPath();
463 | context.moveTo(pt.rawPosition.x, pt.rawPosition.y);
464 |
465 | inkManager.processPointerDown(pt);
466 | penID = evt.pointerId;
467 | }
468 | else if (evt.pointerType === "touch")
469 | {
470 | // Start the processing of events related to this pointer as part of a gesture.
471 | // In this sample we are interested in MSGestureTap event, which we use to show alternates. See handleTap event handler.
472 | selCanvas.gestureObject.addPointer(evt.pointerId);
473 | }
474 | }
475 | catch (e)
476 | {
477 | displayError("handlePointerDown " + e.toString());
478 | }
479 | }
480 |
481 | function handlePointerMove(evt)
482 | {
483 | try
484 | {
485 | if (evt.pointerId === penID)
486 | {
487 | var pt = evt.currentPoint;
488 | context.lineTo(pt.rawPosition.x, pt.rawPosition.y);
489 | context.stroke();
490 | // Get all the points we missed and feed them to inkManager.
491 | // The array pts has the oldest point in position length-1; the most recent point is in position 0.
492 | // Actually, the point in position 0 is the same as the point in pt above (returned by evt.currentPoint).
493 | var pts = evt.intermediatePoints;
494 | for (var i = pts.length - 1; i >= 0 ; i--)
495 | {
496 | inkManager.processPointerUpdate(pts[i]);
497 | }
498 | }
499 |
500 | // No need to process touch events - selCanvas.gestureObject takes care of them and triggers MSGesture* events.
501 | }
502 | catch (e)
503 | {
504 | displayError("handlePointerMove " + e.toString());
505 | }
506 | }
507 |
508 | function handlePointerUp(evt)
509 | {
510 | try
511 | {
512 | if (evt.pointerId === penID)
513 | {
514 | penID = -1;
515 | var pt = evt.currentPoint;
516 | context.lineTo(pt.rawPosition.x, pt.rawPosition.y);
517 | context.stroke();
518 | context.closePath();
519 |
520 | var rect = inkManager.processPointerUp(pt);
521 | if (inkManager.mode === Windows.UI.Input.Inking.InkManipulationMode.selecting)
522 | {
523 | detachSelection(rect);
524 | }
525 |
526 | renderAllStrokes();
527 | }
528 | }
529 | catch (e)
530 | {
531 | displayError("handlePointerUp " + e.toString());
532 | }
533 | }
534 |
535 | // We treat the event of the pen leaving the canvas as the same as the pen lifting;
536 | // it completes the stroke.
537 | function handlePointerOut(evt)
538 | {
539 | try
540 | {
541 | if (evt.pointerId === penID)
542 | {
543 | var pt = evt.currentPoint;
544 | context.lineTo(pt.rawPosition.x, pt.rawPosition.y);
545 | context.stroke();
546 | context.closePath();
547 | inkManager.processPointerUp(pt);
548 | penID = -1;
549 | renderAllStrokes();
550 | }
551 | }
552 | catch (e)
553 | {
554 | displayError("handlePointerOut " + e.toString());
555 | }
556 | }
557 |
558 | function handleTap(evt)
559 | {
560 | //appbar.hide();
561 |
562 | // Anchor and clear any current selection.
563 | if (anySelected())
564 | {
565 | anchorSelection();
566 | var pt = { x: 0.0, y: 0.0 };
567 | inkManager.selectWithLine(pt, pt);
568 | renderAllStrokes();
569 | }
570 |
571 | var touchedResults = hitTest(evt.offsetX, evt.offsetY);
572 | if (touchedResults)
573 | {
574 | touchWord(touchedResults);
575 | }
576 | }
577 |
578 | function handleSelectionBoxPointerDown(evt)
579 | {
580 | //appbar.hide();
581 |
582 | // Start the processing of events related to this pointer as part of a gesture.
583 | // In this sample we are interested in MSGestureChange event, which we use to move selected ink.
584 | // See handleSelectionBoxGestureChange event handler.
585 | selBox.gestureObject.addPointer(evt.pointerId);
586 | }
587 |
588 | function handleSelectionBoxGestureChange(evt)
589 | {
590 | // Move selection box
591 | selBox.rect.x += evt.translationX;
592 | selBox.rect.y += evt.translationY;
593 | selBox.style.left = selBox.rect.x + "px";
594 | selBox.style.top = selBox.rect.y + "px";
595 |
596 | // Move selected ink
597 | inkManager.moveSelected({x: evt.translationX, y: evt.translationY});
598 |
599 | renderAllStrokes();
600 | }
601 |
602 | //we may need figure out a way to show different commands according to different plugin
603 | function setView()
604 | {
605 | if (window.innerWidth <= 320) {
606 | appbar.showOnlyCommands(["Clear", "Done", "Cancel"], true);
607 | // } else if (window.innerWidth <= 768) {
608 | // appbar.showOnlyCommands(["Save", "Done", "Clear", "Cancel"], true);
609 | } else {
610 | appbar.showCommands(["InkColors", "InkWidth", "ModeErase", "Done", "Clear", "Cancel"], true);
611 | }
612 | }
613 |
614 | //Draws a single stroke into a specified canvas 2D context, with a specified color and width.
615 | function renderStroke(stroke, color, width, ctx)
616 | {
617 | ctx.save();
618 |
619 | try
620 | {
621 | ctx.beginPath();
622 | ctx.strokeStyle = color;
623 | ctx.lineWidth = width;
624 |
625 | var first = true;
626 | stroke.getRenderingSegments().forEach(function (segment)
627 | {
628 | if (first)
629 | {
630 | ctx.moveTo(segment.position.x, segment.position.y);
631 | first = false;
632 | }
633 | else
634 | {
635 | ctx.bezierCurveTo(segment.bezierControlPoint1.x, segment.bezierControlPoint1.y,
636 | segment.bezierControlPoint2.x, segment.bezierControlPoint2.y,
637 | segment.position.x, segment.position.y);
638 | }
639 | });
640 |
641 | ctx.stroke();
642 | ctx.closePath();
643 |
644 | ctx.restore();
645 | }
646 | catch (e)
647 | {
648 | ctx.restore();
649 | displayError("renderStroke " + e.toString());
650 | }
651 | }
652 |
653 | // This draws a basic notepaper pattern into the highlight canvas, which is the lowest canvas.
654 | // It has a single vertical dark red line defining the left margin, and a series of horizontal blue lines.
655 | function renderPaper()
656 | {
657 | var height = hlCanvas.height;
658 | var bottom = height - 0.5;
659 | var right = hlCanvas.width - 0.5;
660 |
661 | hlContext.save();
662 | inkContext.save();
663 | if(navigator.sketch.inputType > 0){
664 | var img = document.getElementById("canvasImg");
665 | hlContext.drawImage(img, 0, 0, inkCanvas.width, inkCanvas.height);
666 | inkContext.drawImage(img, 0, 0, inkCanvas.width, inkCanvas.height);
667 | }
668 | }
669 |
670 | // Redraws (from the beginning) all strokes in the canvases. All canvases are erased,
671 | // then the paper is drawn, then all the strokes are drawn.
672 | function renderAllStrokes()
673 | {
674 | selContext.clearRect(0, 0, selCanvas.width, selCanvas.height);
675 | inkContext.clearRect(0, 0, inkCanvas.width, inkCanvas.height);
676 | hlContext.clearRect(0, 0, hlCanvas.width, hlCanvas.height);
677 |
678 | renderPaper();
679 |
680 | inkManager.getStrokes().forEach(function (stroke)
681 | {
682 | var att = stroke.drawingAttributes;
683 | var color = toColorString(att.color);
684 | var strokeSize = att.size;
685 | var width = strokeSize.width;
686 | var hl = isHighlighting(stroke);
687 | var ctx = hl ? hlContext : inkContext;
688 |
689 | if (stroke.selected)
690 | {
691 | renderStroke(stroke, color, width * 2, ctx);
692 | var stripe = hl ? "Azure" : "White";
693 | var w = width - (hl ? 3 : 1);
694 | renderStroke(stroke, stripe, w, ctx);
695 | }
696 | else
697 | {
698 | renderStroke(stroke, color, width, ctx);
699 | }
700 | });
701 | }
702 |
703 | function clear()
704 | {
705 | try
706 | {
707 | //appbar.hide();
708 | if (anySelected())
709 | {
710 | inkManager.deleteSelected();
711 | }
712 | else
713 | {
714 | selectAll();
715 | inkManager.deleteSelected();
716 | inkMode();
717 | }
718 |
719 | renderAllStrokes();
720 | displayStatus("");
721 | displayError("");
722 | }
723 | catch (e)
724 | {
725 | displayError("clear: " + e.toString());
726 | }
727 | }
728 |
729 | // A generic function for use for any async error function (the second arg to a then() method).
730 | function asyncError(e)
731 | {
732 | displayError("Async error: " + e.toString());
733 | }
734 |
735 | function refresh()
736 | {
737 | try
738 | {
739 | //appbar.hide();
740 | renderAllStrokes();
741 | }
742 | catch (e)
743 | {
744 | displayError("clear " + e.toString());
745 | }
746 | }
747 |
748 | // A button handler which fetches the ID from the button, which should
749 | // be IW2, IW4, etc. We set the lineWidth of the inking canvas to the number part of this ID,
750 | // then set the system into ink mode (which will cause the ink manager
751 | // to change its defaults for new strokes to match the ink canvas).
752 | // If any ink strokes (not including highlight strokes) are currently selected,
753 | // we also change their width to this value. If any strokes are changed
754 | // we must re-render the entire ink display.
755 | function setInkWidth(evt)
756 | {
757 | try
758 | {
759 | //appbar.hide();
760 |
761 | var id = evt.srcElement.id;
762 | id = id.substr(2);
763 | inkContext.lineWidth = id;
764 | inkMode();
765 |
766 | var redraw = false;
767 | inkManager.getStrokes().forEach(function (stroke)
768 | {
769 | if (stroke.selected && !isHighlighting(stroke))
770 | {
771 | if (shapeStroke(stroke, inkContext.lineWidth))
772 | {
773 | redraw = true;
774 | }
775 | }
776 | });
777 | if (redraw)
778 | {
779 | renderAllStrokes();
780 | }
781 | }
782 | catch (e)
783 | {
784 | displayError("setInkWidth " + e.toString());
785 | }
786 | }
787 |
788 | // A button handler which fetches the ID from the button, which should
789 | // be HW10, HW20, or HW30. We set the lineWidth of the highlighting canvas to the number part of this ID,
790 | // then set the system into highlight mode (which will cause the ink manager
791 | // to change its defaults for new strokes to match the highlight canvas).
792 | // If any highlight strokes are currently selected, we also change their width
793 | // to this value. If any strokes are changed we must re-render the dirty areas.
794 | function setHighlightWidth(evt)
795 | {
796 | try
797 | {
798 | //appbar.hide();
799 |
800 | var id = evt.srcElement.id;
801 | id = id.substr(2);
802 | hlContext.lineWidth = id;
803 | highlightMode();
804 |
805 | var redraw = false;
806 | inkManager.getStrokes().forEach(function (stroke)
807 | {
808 | if (stroke.selected && isHighlighting(stroke))
809 | {
810 | if (shapeStroke(stroke, hlContext.lineWidth))
811 | {
812 | redraw = true;
813 | }
814 | }
815 | });
816 | if (redraw)
817 | {
818 | renderAllStrokes();
819 | }
820 | }
821 | catch (e)
822 | {
823 | displayError("setInkWidth " + e.toString());
824 | }
825 | }
826 |
827 | // A button handler which fetches the ID from the button, which should
828 | // be a color name. We set the strokeStyle of the inking canvas to this color,
829 | // then set the system into ink mode (which will cause the ink manager
830 | // to change its defaults for new strokes to match the ink canvas).
831 | // If any ink strokes (not including highlight strokes) are currently selected,
832 | // we also change their color to this value. If any strokes are changed
833 | // we must re-render the dirty areas.
834 | function inkColor(evt)
835 | {
836 | //appbar.hide();
837 | inkContext.strokeStyle = evt.srcElement.id;
838 | inkMode();
839 |
840 | var redraw = false;
841 | inkManager.getStrokes().forEach(function (stroke)
842 | {
843 | if (stroke.selected && !isHighlighting(stroke))
844 | {
845 | if (colorStroke(stroke, inkContext.strokeStyle))
846 | {
847 | redraw = true;
848 | }
849 | }
850 | });
851 | if (redraw)
852 | {
853 | renderAllStrokes();
854 | }
855 | }
856 |
857 | // A button handler which fetches the ID from the button, which should
858 | // be a color name. We set the strokeStyle of the highlight canvas to this color,
859 | // then set the system into highlight mode (which will cause the ink manager
860 | // to change its defaults for new strokes to match the highlight canvas).
861 | // If any highlight strokes are currently selected, we also change their color
862 | // to this value. If any strokes are changed we must re-render the dirty areas.
863 | function highlightColor(evt)
864 | {
865 | //appbar.hide();
866 | hlContext.strokeStyle = evt.srcElement.id;
867 | highlightMode();
868 |
869 | var redraw = false;
870 | inkManager.getStrokes().forEach(function (stroke)
871 | {
872 | if (stroke.selected && isHighlighting(stroke))
873 | {
874 | if (colorStroke(stroke, hlContext.strokeStyle))
875 | {
876 | redraw = true;
877 | }
878 | }
879 | });
880 | if (redraw)
881 | {
882 | renderAllStrokes();
883 | }
884 | }
885 |
886 | // Finds a specific recognizer, and sets the inkManager's default to that recognizer.
887 | // Returns true if successful.
888 | function setRecognizerByName(recname)
889 | {
890 | try
891 | {
892 | // recognizers is a normal JavaScript array
893 | var recognizers = inkManager.getRecognizers();
894 | for (var i = 0, len = recognizers.length; i < len; i++)
895 | {
896 | if (recname === recognizers[i].name)
897 | {
898 | inkManager.setDefaultRecognizer(recognizers[i]);
899 | return true;
900 | }
901 | }
902 | }
903 | catch (e)
904 | {
905 | displayError("setRecognizerByName: " + e.toString());
906 | }
907 | return false;
908 | }
909 |
910 | // A button handler which runs the currently-loaded handwriting recognizer over
911 | // the selected ink (not counting highlight strokes). If no ink is selected, then it
912 | // runs over all the ink (again, not counting highlight strokes).
913 | // The recognition results (a string) is displayed in the status window.
914 | // The recognition results are also stored within the ink manager itself, so that
915 | // other commands can find the bounding boxes (or ink strokes) of any specific
916 | // word of ink.
917 | function recognize(evt)
918 | {
919 | //appbar.hide();
920 |
921 | // The recognizeAsync() method has 3 modes: selected, remaining, and all.
922 | // This particular app cannot use "all" mode because it supports highlighting.
923 | // If the user has highlighted one or more words, and we recognize in "all" mode,
924 | // we will recognize all strokes, including the highlight strokes. This usually
925 | // results in a recognition string containing many asterisks.
926 | // If we find that no strokes are selected, rather than running in "all" mode, we
927 | // select all strokes that are not highlighting strokes, then run in "selected" mode.
928 | // If some strokes were already selected, we just need to unselect any which are highlighting.
929 |
930 | // If we DID originally find that no strokes were selected, we remember that fact, so that
931 | // we can unselect them after the recognition.
932 | var bSelected = false;
933 | if (anySelected())
934 | {
935 | unselectHighlight();
936 | }
937 | else
938 | {
939 | selectAllNoHighlight();
940 | bSelected = true;
941 | }
942 |
943 | // NOTE: check that we have some ink to recognize before calling RecognizerContainer::RecognizeAsync()
944 | if (anySelected())
945 | {
946 | // recognizeAsync below will fail if another recognition task is in progress
947 | try
948 | {
949 |
950 | // Note that the third mode in recognizeAsync(), "recent", can be very useful in certain situations,
951 | // but we are not using it here. It will recognize all strokes that have been added since the last
952 | // recognition. If we were assuming that all strokes were writing, and we were trying to keep
953 | // recognition caught up with the user's writing at all times (that is, not using a Reco button),
954 | // then "recent" would be the mode we would want.
955 |
956 | // Because recognition is slower, we ask for it as an asynchronous operation.
957 | // The anonymous function (the first arg to the "then" method) will be called
958 | // as a callback when recognition has completed. If an error occurs, the second
959 | // arg will be called.
960 | inkManager.recognizeAsync(Windows.UI.Input.Inking.InkRecognitionTarget.selected).done
961 | (
962 | function (results)
963 | {
964 | // Doing a recognition does not update the storage of results (the results that are stored inside the ink manager).
965 | // We do that ourselves by calling this method.
966 | inkManager.updateRecognitionResults(results);
967 |
968 | // The arg "results" is an array of result objects representing "words", where "words" means words of ink (not computer memory words).
969 | // IE, if you write "this is a test" that is 4 words, and results will be an array of length 4.
970 |
971 | var alternates = ""; // will accumulate the result words, with spaces between
972 | var c = results.length;
973 | for (var i = 0; i < c; i++)
974 | {
975 | // Method getTextCandidates() returns an array of recognition alternates (different interpretations of the same word of ink).
976 | // This is a standard JavaScript array of standard JavaScript strings.
977 | // For this program we only use the first (top) alternate in our display.
978 | // If we were doing search over this ink, we would want to search all alternates.
979 | var alts = results[i].getTextCandidates();
980 | alternates = alternates + " " + alts[0];
981 |
982 | // The specific strokes forming the current word of ink are available to us.
983 | // This feature is not used here, but we could, if we chose, display the ink,
984 | // with the recognition result for each word directly above the specific word of ink,
985 | // by fetching the bounding box of the recognitionResult (via the boundingRect property).
986 | // Or, if we needed to do something to each stroke in the recognized word, we could
987 | // call recognitionResult.getStrokes(), then iterate over the individual strokes.
988 | }
989 | displayStatus(alternates);
990 | },
991 | function (e)
992 | {
993 | displayError("InkManager::recognizeAsync: " + e.toString());
994 | }
995 | );
996 | if (bSelected)
997 | {
998 | // Unselect all strokes (if we originally had no selected strokes).
999 | var pt = { x: 0.0, y: 0.0 };
1000 | inkManager.selectWithLine(pt, pt);
1001 | }
1002 | }
1003 | catch (e)
1004 | {
1005 | displayError("recognize: " + e.toString());
1006 | }
1007 | }
1008 | else
1009 | {
1010 | displayStatus("Must first write something.");
1011 | }
1012 | }
1013 |
1014 | // A utility function for findText() below. This takes a target string (typed in by the user)
1015 | // an an array of recognition results objects, and inspects the recognition alternates of each
1016 | // results object. If a match is found among the alternates, then all strokes in that results
1017 | // object are selected. The match is case-insensitive.
1018 | function findWord(target, results)
1019 | {
1020 | target = target.toLowerCase();
1021 | var cWords = results.length;
1022 |
1023 | var count = 0;
1024 | for (var i = 0; i < cWords; i++)
1025 | {
1026 | var alternates = results[i].getTextCandidates();
1027 | var cAlts = alternates.length;
1028 | for (var j = 0; j < cAlts; j++)
1029 | {
1030 | if (alternates[j].toLowerCase() === target)
1031 | {
1032 | var strokes = results[i].getStrokes();
1033 | var cStrokes = strokes.length;
1034 | for (var k = 0; k < cStrokes; k++)
1035 | {
1036 | strokes[k].selected = true;
1037 | }
1038 | count++;
1039 | break;
1040 | }
1041 | }
1042 | }
1043 | return count;
1044 | }
1045 |
1046 | // A handler for the Find button in the Find flyout. We fetch the search string
1047 | // from the form, and the array of recognition results objects from the ink
1048 | // manager. We unselect any current selection, so that when we are done
1049 | // the selections will reflect the search results. We split the search string into
1050 | // individual words, since our recognition results objects each represent individual
1051 | // words. The actual matching is done by findWord(), defined above.
1052 |
1053 | // Note that multiple instances of a target can be found; if the target is "this" and
1054 | // the ink contains "this is this is that", 2 instances of "this" will be found and all
1055 | // strokes in both words will be selected.
1056 |
1057 | // Note that findWord() above searches all alternates. This means you might write
1058 | // "this", have it mis-recognized as "these", but the search feature MAY find it, if
1059 | // "this" appears in any of the other 4 recognition alternates for this ink.
1060 | function find(evt)
1061 | {
1062 | try
1063 | {
1064 | //appbar.hide();
1065 |
1066 | var str = findText.value;
1067 | var results = inkManager.getRecognitionResults();
1068 |
1069 | // This will unselect any current selection.
1070 | var pt = {x:0.0, y:0.0};
1071 | inkManager.selectWithLine(pt, pt);
1072 |
1073 | var count = 0;
1074 | var words = str.split(" ");
1075 | for (var i = 0; i < words.length; i++)
1076 | {
1077 | count += findWord(words[i], results);
1078 | }
1079 |
1080 | if (0 < count)
1081 | {
1082 | displayStatus("Found " + /*@static_cast(String)*/count + " words");
1083 | renderAllStrokes();
1084 | }
1085 | else
1086 | {
1087 | displayStatus("Did not find " + str);
1088 | }
1089 | return false;
1090 | }
1091 | catch (e)
1092 | {
1093 | displayError("find: " + e.toString());
1094 | }
1095 | return false;
1096 | }
1097 |
1098 | // A button click handler for recognition results buttons in the "reco" Flyout.
1099 | // The flyout shows the top 5 recognition results for a specific word, and
1100 | // is invoked by tapping (with finger) on a word (after recognition has been run).
1101 | // We fetch the recognition result (the innerHTML of the button, a string) and
1102 | // copy it to the clipboard.
1103 | function recoClipboard(evt)
1104 | {
1105 | try
1106 | {
1107 | recoFlyout.winControl.hide();
1108 | var alt = evt.srcElement.innerHTML;
1109 |
1110 | var dataPackage = new Windows.ApplicationModel.DataTransfer.DataPackage();
1111 | dataPackage.setText(alt);
1112 | Windows.ApplicationModel.DataTransfer.Clipboard.setContent(dataPackage);
1113 | displayStatus("To clipboard: " + alt);
1114 | }
1115 | catch (e)
1116 | {
1117 | displayError("recoClipboard: " + e.toString());
1118 | }
1119 | }
1120 |
1121 | // Brings up the "reco" Flyout, after first changing the values of the 5 buttons to be
1122 | // the top 5 recognition alternates of a single word.
1123 | function touchWord(touchedResults)
1124 | {
1125 | try
1126 | {
1127 | // The Windows.UI.Input.Inking.InkManager interface normally returns 5 alternates.
1128 | // We check just to be sure we are not given more alternates than the count of buttons.
1129 | var cAlts = touchedResults.alternates.size;
1130 | if (cAlts === 0)
1131 | {
1132 | return;
1133 | }
1134 | var cButs = clipButtons.length;
1135 | if (cButs < cAlts)
1136 | {
1137 | cAlts = cButs;
1138 | }
1139 | var i;
1140 | for (i = 0; i < cAlts; i++)
1141 | {
1142 | clipButtons[i].label = touchedResults.alternates[i];
1143 | }
1144 | for (; i < cButs; i++)
1145 | {
1146 | clipButtons[i].label = "";
1147 | }
1148 |
1149 | // Display the reco results menu just to the left of the left-top corner of the bounding rect of the ink.
1150 | var rect = touchedResults.rect;
1151 | wordDiv.style.left = /*@static_cast(String)*/rect.x + "px";
1152 | wordDiv.style.top = /*@static_cast(String)*/rect.y + "px";
1153 | wordDiv.style.width = /*@static_cast(String)*/rect.width + "px";
1154 | wordDiv.style.height = /*@static_cast(String)*/rect.height + "px";
1155 | recoFlyout.winControl.show(wordDiv, "left");
1156 | }
1157 | catch (e)
1158 | {
1159 | displayError("touchWord: " + e.toString());
1160 | }
1161 | }
1162 |
1163 | // A button handler which copies the selected strokes (or all the strokes if none are selected)
1164 | // into the clipboard. The strokes can be pasted into any application that handles any of the
1165 | // ink clipboard formats, such as Windows Journal.
1166 | function copySelected(evt)
1167 | {
1168 | try
1169 | {
1170 | //appbar.hide();
1171 | if (anySelected())
1172 | {
1173 | displayStatus("Copying selected strokes ...");
1174 | inkManager.copySelectedToClipboard();
1175 | displayStatus("Copy Selected");
1176 | }
1177 | else
1178 | {
1179 | displayStatus("Copying all strokes ...");
1180 | selectAll();
1181 | inkManager.copySelectedToClipboard();
1182 | // Unselect all strokes.
1183 | var pt = {x:0.0, y:0.0};
1184 | inkManager.selectWithLine(pt, pt);
1185 | displayStatus("Copy All");
1186 | }
1187 | }
1188 | catch (e)
1189 | {
1190 | displayError("copySelected: " + e.toString());
1191 | }
1192 | }
1193 |
1194 | // A button handler which copies any available strokes in the clipboard into this app.
1195 | function paste(evt)
1196 | {
1197 | //appbar.hide();
1198 |
1199 | displayStatus("Pasting ...");
1200 | var insertionPoint = {x: 100, y: 60};
1201 | var canPaste = inkManager.canPasteFromClipboard();
1202 | if (canPaste)
1203 | {
1204 | inkManager.pasteFromClipboard(insertionPoint);
1205 | displayStatus("Pasted");
1206 | renderAllStrokes();
1207 | }
1208 | else
1209 | {
1210 | displayStatus("Cannot paste");
1211 | }
1212 | }
1213 |
1214 | // A keypress handler which closes the program.
1215 | // A normal program should not have this, but it is very
1216 | // convenient for testing.
1217 | function closeProgram(evt)
1218 | {
1219 | displayStatus("Closing App ...");
1220 | window.close();
1221 | }
1222 |
1223 | // prevent two concurrent loadAsync() operations
1224 | var asyncFlag = false;
1225 |
1226 | // Reads a gif file which contains strokes as metadata.
1227 | function readInk(storageFile)
1228 | {
1229 | if (storageFile)
1230 | {
1231 | // closure variable, visible to all promises in the following chain
1232 | var loadStream = null;
1233 | storageFile.openAsync(Windows.Storage.FileAccessMode.read).then(
1234 | function (stream)
1235 | {
1236 | // about to call loadAsync()
1237 | // prevent future calls to this API until we are done with the first call
1238 | asyncFlag = true;
1239 | loadStream = stream;
1240 | return inkManager.loadAsync(loadStream); // since we return the promise, it will be executed before the following .done
1241 | }
1242 | ).done(
1243 | function ()
1244 | {
1245 | var strokes = inkManager.getStrokes();
1246 | var c = strokes.length;
1247 | if (c === 0)
1248 | {
1249 | displayStatus("File does not contain any ink strokes.");
1250 | }
1251 | else
1252 | {
1253 | displayStatus("Loaded " + c + " strokes.");
1254 | renderAllStrokes();
1255 | }
1256 |
1257 | // reset asyncFlag, can call loadAsync() once again
1258 | asyncFlag = false;
1259 |
1260 | // input stream is IClosable interface and requires explicit close
1261 | loadStream.close();
1262 | },
1263 | function (e)
1264 | {
1265 | displayError("Load failed. Make sure you tried to open a file that can be read by the InkManager.");
1266 |
1267 | // we still want to reset asyncFlag if an error occurs
1268 | asyncFlag = false;
1269 |
1270 | // if the error occurred after the stream was opened, close the stream
1271 | if (loadStream)
1272 | {
1273 | loadStream.close();
1274 | }
1275 | }
1276 | );
1277 | }
1278 | }
1279 |
1280 | // A button handler which fetches the file name via the file picker, then calls readInk() above.
1281 | function load(evt)
1282 | {
1283 | //appbar.hide();
1284 | if (asyncFlag)
1285 | {
1286 | return;
1287 | }
1288 |
1289 | // Open the WinRT file picker, set the input folder, and set the input extension.
1290 | var picker = new Windows.Storage.Pickers.FileOpenPicker();
1291 | picker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
1292 | picker.fileTypeFilter.replaceAll([".gif"]);
1293 | picker.pickSingleFileAsync().done(readInk, asyncError);
1294 | }
1295 |
1296 | var encodeToBase64String = function (buffer) {
1297 | return Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
1298 | };
1299 |
1300 | function done(){
1301 | if (inkManager.getStrokes().size > 0)
1302 | {
1303 | if(navigator.sketch != null && navigator.sketch != undefined)
1304 | if(navigator.sketch.destinationType == navigator.sketch.DestinationType.DATA_URL)
1305 | saveToStream();
1306 | else if(navigator.sketch.destinationType == navigator.sketch.DestinationType.FILE_URI)
1307 | saveToFile();
1308 | }
1309 | else
1310 | {
1311 | statusMessage.innerText = "Nothing to save yet.";
1312 | }
1313 | }
1314 |
1315 | var filetype = getFileType();
1316 |
1317 | function getFileType(){
1318 | var encodingType = 'PNG';
1319 | if(navigator.sketch != null && navigator.sketch != undefined)
1320 | {
1321 | if(navigator.sketch.encodingType == navigator.sketch.EncodingType.JPEG)
1322 | encodingType = 'JPEG';
1323 | else if(navigator.sketch.encodingType == navigator.sketch.EncodingType.PNG)
1324 | encodingType = 'PNG';
1325 | }
1326 | return encodingType;
1327 | }
1328 |
1329 | function saveToStream(){
1330 | var dataURL = inkCanvas.toDataURL(navigator.sketch.DataURLType[navigator.sketch.encodingType], 1.0);
1331 | callbackDone(dataURL);
1332 | // var fileName = 'cordova-plugin-sketch-temporary.'+filetype;
1333 | // var repExt = Windows.Storage.CreationCollisionOption.ReplaceExisting;
1334 | // var folder = Windows.Storage.ApplicationData.current.temporaryFolder;
1335 | // folder.createFileAsync(fileName, repExt)
1336 | // .then(
1337 | // function(tempFile) {
1338 | // writeInk(tempFile)
1339 | // })
1340 | // .done(
1341 | // function(){
1342 | // folder.getFileAsync(fileName).done(
1343 | // function(tempFile){
1344 | // Windows.Storage.FileIO.readBufferAsync(tempFile).done(
1345 | // function(buffer) {
1346 | // var strBase64 = encodeToBase64String(buffer);
1347 | // tempFile.deleteAsync().done(
1348 | // function() {
1349 | // callbackDone(strBase64);
1350 | // },
1351 | // function (e)
1352 | // {
1353 | // displayError("Done " + e.toString());
1354 | // }
1355 | // );
1356 | // },
1357 | // function(e) {
1358 | // displayError("Done " + e.toString());
1359 | // }
1360 | // );
1361 | // },
1362 | // function(e) {
1363 | // displayError("Done " + e.toString());
1364 | // }
1365 | // );
1366 | // },
1367 | // function(e) {
1368 | // displayError("Done " + e.toString());
1369 | // }
1370 | // );
1371 | }
1372 |
1373 | function saveToFile(){
1374 | var picker = new Windows.Storage.Pickers.FileSavePicker();
1375 | picker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
1376 | picker.fileTypeChoices.insert(filetype + " file", ["." + filetype + ""]);
1377 | picker.defaultFileExtension = "." + filetype;
1378 | picker.pickSaveFileAsync().done(writeInkToFile, asyncError);
1379 | }
1380 |
1381 | function writeInkToFile(storageFile)
1382 | {
1383 | if (storageFile)
1384 | {
1385 | // closure variable, visible to all promises in the following chain
1386 | var saveStream = null;
1387 | storageFile.openAsync(Windows.Storage.FileAccessMode.readWrite).then(
1388 | function (stream)
1389 | {
1390 | saveStream = stream;
1391 | return inkManager.saveAsync(saveStream).done(
1392 | function() {
1393 | callbackDone(storageFile.path);
1394 | },
1395 | function (e)
1396 | {
1397 | displayError("Done " + e.toString());
1398 | }
1399 | ); // since we return the promise, it will be executed before the following .then
1400 | }
1401 | ).done(
1402 | function (result)
1403 | {
1404 | // print the size of the stream on the screen
1405 | displayStatus("File saved!");
1406 |
1407 | // output stream is IClosable interface and requires explicit close
1408 | saveStream.close();
1409 | },
1410 | function (e)
1411 | {
1412 | displayError("Save " + e.toString());
1413 |
1414 | // if the error occurred after the stream was opened, close the stream
1415 | if (saveStream)
1416 | {
1417 | saveStream.close();
1418 | }
1419 | }
1420 | );
1421 | }
1422 | }
1423 |
1424 | function callbackDone(stream){
1425 | if(navigator.sketch != null && navigator.sketch != undefined)
1426 | navigator.sketch.done(stream);
1427 | }
1428 |
1429 | function cancel(){
1430 | if(navigator.sketch != null && navigator.sketch != undefined)
1431 | navigator.sketch.cancel();
1432 | }
1433 |
1434 | function writeInk(storageFile)
1435 | {
1436 | if (storageFile)
1437 | {
1438 | // closure variable, visible to all promises in the following chain
1439 | var saveStream = null;
1440 | storageFile.openAsync(Windows.Storage.FileAccessMode.readWrite).then(
1441 | function (stream)
1442 | {
1443 | saveStream = stream;
1444 | return inkManager.saveAsync(saveStream); // since we return the promise, it will be executed before the following .then
1445 | }
1446 | ).done(
1447 | function (result)
1448 | {
1449 | // print the size of the stream on the screen
1450 | displayStatus("File saved!");
1451 |
1452 | // output stream is IClosable interface and requires explicit close
1453 | saveStream.close();
1454 | },
1455 | function (e)
1456 | {
1457 | displayError("Save " + e.toString());
1458 |
1459 | // if the error occurred after the stream was opened, close the stream
1460 | if (saveStream)
1461 | {
1462 | saveStream.close();
1463 | }
1464 | }
1465 | );
1466 | }
1467 | }
1468 |
1469 | // Shows the create file dialog box. Submitting on that form will invoke saveFile() above.
1470 | function save(evt)
1471 | {
1472 | //appbar.hide();
1473 |
1474 | // NOTE: make sure that the inkManager has some strokes to save before calling inkManager.saveAsync
1475 | if (inkManager.getStrokes().size > 0)
1476 | {
1477 | var picker = new Windows.Storage.Pickers.FileSavePicker();
1478 | picker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
1479 | picker.fileTypeChoices.insert("PNG file", [".PNG"]);
1480 | picker.fileTypeChoices.insert("JPEG file", [".JPEG"]);
1481 | picker.defaultFileExtension = ".PNG";
1482 | picker.pickSaveFileAsync().done(writeInk, asyncError);
1483 | }
1484 | else
1485 | {
1486 | statusMessage.innerText = "The InkManager doesn't contain any strokes to save.";
1487 | }
1488 | }
1489 |
1490 | // A keypress handler that only handles a few keys. This is registered on the entire body.
1491 | // Escape will:
1492 | // 1. If any dialog boxes are showing, hide them and do nothing else.
1493 | // 2. Otherwise, if any strokes are selected, unselect them and do nothing else.
1494 | // 3. Otherwise, change to ink mode.
1495 | // This sequence allows us to "unpeel the onion" (it is very fast to hit escape 3 times if needed).
1496 |
1497 | // Certain control keys invoke handlers that are otherwise invoked via buttons:
1498 | // ^C Copy
1499 | // ^V Paste
1500 | // ^F Find
1501 | // ^O Load
1502 | // ^S Save
1503 | // ^R Recognize
1504 | // ^Q Quit (shuts down the sample app)
1505 |
1506 | // Note that most of these keys have standardized normal uses, and there is system code to handle that
1507 | // without our code doing anything. That code sometimes interferes with our program. All the functions
1508 | // we call from here call evt.preventDefault(), which should stop the default processing, but sometimes we still
1509 | // cannot get this code to execute.
1510 | function keypress(evt)
1511 | {
1512 | if (evt.keyCode === 27) // escape
1513 | {
1514 | evt.preventDefault();
1515 | if (!recoFlyout.winControl.hidden)
1516 | {
1517 | recoFlyout.winControl.hide();
1518 | renderAllStrokes();
1519 | }
1520 | else if (anySelected())
1521 | {
1522 | // Unselect all strokes.
1523 | var pt = {x:0.0, y:0.0};
1524 | inkManager.selectWithLine(pt, pt);
1525 | renderAllStrokes();
1526 | }
1527 | else
1528 | {
1529 | inkMode();
1530 | }
1531 | }
1532 | else if (evt.keyCode === 3) // control c
1533 | {
1534 | copySelected(evt);
1535 | }
1536 | else if (evt.keyCode === 22) // control v
1537 | {
1538 | paste(evt);
1539 | }
1540 | else if (evt.keyCode === 15) // control o
1541 | {
1542 | load(evt);
1543 | }
1544 | else if (evt.keyCode === 19) // control s
1545 | {
1546 | save(evt);
1547 | }
1548 | else if (evt.keyCode === 18) // control r
1549 | {
1550 | recognize(evt);
1551 | }
1552 | else if (evt.keyCode === 17) // control q
1553 | {
1554 | closeProgram(evt);
1555 | }
1556 | }
1557 |
1558 | function inkInitialize()
1559 | {
1560 | // Utility to fetch elements by ID.
1561 | function id(elementId)
1562 | {
1563 | return document.getElementById(elementId);
1564 | }
1565 |
1566 | WinJS.UI.processAll().then(
1567 | function ()
1568 | {
1569 | app = WinJS.Application;
1570 | appbar = id("bottomAppBar").winControl;
1571 |
1572 | findFlyout = id("FindFlyout");
1573 | inkColorsFlyout = id("InkColorFlyout");
1574 | inkWidthsFlyout = id("InkWidthFlyout");
1575 | hlColorsFlyout = id("HighlightColorFlyout");
1576 | hlWidthsFlyout = id("HighlightWidthFlyout");
1577 | moreFlyout = id("MoreFlyout");
1578 |
1579 | if(findFlyout){
1580 | findText = id("FindString");
1581 | findFlyout.addEventListener("aftershow", function (evt) { findText.focus(); }, false);
1582 | id("FindButton").addEventListener("click", find, false);
1583 | }
1584 |
1585 | hlCanvas = id("HighlightCanvas");
1586 | hlCanvas.setAttribute("width", hlCanvas.offsetWidth);
1587 | hlCanvas.setAttribute("height", hlCanvas.offsetHeight);
1588 | hlContext = hlCanvas.getContext("2d");
1589 | hlContext.lineWidth = 10;
1590 | hlContext.strokeStyle = "Yellow";
1591 | hlContext.lineCap = "round";
1592 | hlContext.lineJoin = "round";
1593 |
1594 | inkCanvas = id("InkCanvas");
1595 | inkCanvas.setAttribute("width", inkCanvas.offsetWidth);
1596 | inkCanvas.setAttribute("height", inkCanvas.offsetHeight);
1597 | inkContext = inkCanvas.getContext("2d");
1598 | inkContext.lineWidth = 2;
1599 | inkContext.strokeStyle = "Black";
1600 | inkContext.lineCap = "round";
1601 | inkContext.lineJoin = "round";
1602 |
1603 | selCanvas = id("SelectCanvas");
1604 | selCanvas.gestureObject = new MSGesture();
1605 | selCanvas.gestureObject.target = selCanvas;
1606 | selCanvas.setAttribute("width", selCanvas.offsetWidth);
1607 | selCanvas.setAttribute("height", selCanvas.offsetHeight);
1608 | selContext = selCanvas.getContext("2d");
1609 | selContext.lineWidth = 1;
1610 | selContext.strokeStyle = "Gold";
1611 | selContext.lineCap = "round";
1612 | selContext.lineJoin = "round";
1613 |
1614 | selBox = id("SelectionBox");
1615 | selBox.addEventListener("pointerdown", handleSelectionBoxPointerDown, false);
1616 | selBox.addEventListener("MSGestureChange", handleSelectionBoxGestureChange, false);
1617 | selBox.gestureObject = new MSGesture();
1618 | selBox.gestureObject.target = selBox;
1619 | selBox.style.left = "0px";
1620 | selBox.style.top = "0px";
1621 | selBox.style.width = "0px";
1622 | selBox.style.height = "0px";
1623 |
1624 | // Note that we must set the event listeners on the top-most canvas.
1625 |
1626 | selCanvas.addEventListener("pointerdown", handlePointerDown, false);
1627 | selCanvas.addEventListener("pointerup", handlePointerUp, false);
1628 | selCanvas.addEventListener("pointermove", handlePointerMove, false);
1629 | selCanvas.addEventListener("pointerout", handlePointerOut, false);
1630 | selCanvas.addEventListener("MSGestureTap", handleTap, false);
1631 |
1632 | window.addEventListener("resize", setView);
1633 |
1634 | setView();
1635 |
1636 | var image = new Image();
1637 | image.onload = function () { selContext.strokeStyle = selPattern = selContext.createPattern(image, "repeat"); };
1638 | image.src = "/www/cordova-plugin-sketch/images/select.png";
1639 |
1640 | recoFlyout = id("RecoFlyout");
1641 | if(recoFlyout){
1642 | clipButtons = new Array();
1643 | for (var i = 0; i < 5; i++)
1644 | {
1645 | var ID = "Reco" + i;
1646 | clipButtons[i] = recoFlyout.winControl.getCommandById(ID);
1647 | }
1648 | }
1649 | wordDiv = id("Word");
1650 |
1651 | //document.body.addEventListener("keypress", keypress, false);
1652 |
1653 | if (!setRecognizerByName("Microsoft English (US) Handwriting Recognizer"))
1654 | {
1655 | displayStatus("Failed to find English (US) recognizer");
1656 | }
1657 | else
1658 | {
1659 | //displayStatus("Verba volant, Scripta manet");
1660 | }
1661 |
1662 | inkMode();
1663 | appbar.show();
1664 | renderPaper();
1665 | }
1666 | ).done(
1667 | function ()
1668 | {
1669 | },
1670 | function (e)
1671 | {
1672 | displayError("inkInitialize " + e.toString());
1673 | }
1674 | );
1675 | }
1676 |
1677 | // Tag the event handlers of the AppBar so that they can be used in a declarative context.
1678 | // For security reasons WinJS.UI.processAll and WinJS.Binding.processAll (and related) functions allow only
1679 | // functions that are marked as being usable declaratively to be invoked through declarative processing.
1680 | WinJS.UI.eventHandler(selectMode);
1681 | WinJS.UI.eventHandler(eraseMode);
1682 | WinJS.UI.eventHandler(clear);
1683 | WinJS.UI.eventHandler(refresh);
1684 | WinJS.UI.eventHandler(setInkWidth);
1685 | WinJS.UI.eventHandler(setHighlightWidth);
1686 | WinJS.UI.eventHandler(inkColor);
1687 | WinJS.UI.eventHandler(highlightColor);
1688 | WinJS.UI.eventHandler(recognize);
1689 | WinJS.UI.eventHandler(recoClipboard);
1690 | WinJS.UI.eventHandler(copySelected);
1691 | WinJS.UI.eventHandler(paste);
1692 | WinJS.UI.eventHandler(load);
1693 | WinJS.UI.eventHandler(save);
1694 | WinJS.UI.eventHandler(done);
1695 | WinJS.UI.eventHandler(cancel);
1696 |
1697 |
1698 | inkInitialize();
1699 |
--------------------------------------------------------------------------------
/www/sketch.js:
--------------------------------------------------------------------------------
1 | var Sketch = function () {
2 | var sketch = {};
3 |
4 | var getSketch = function (successCallback, errorCallback, options) {
5 | var argsCheck = require('cordova/argscheck');
6 | var opts = options || {};
7 |
8 | argsCheck.checkArgs('fFO', 'Sketch.getSketch', arguments);
9 | if (typeof opts.destinationType === 'undefined' || opts.destinationType === null) {
10 | opts.destinationType = DestinationType.DATA_URL;
11 | }
12 |
13 | if (typeof opts.encodingType === 'undefined' || opts.encodingType === null) {
14 | opts.encodingType = EncodingType.PNG;
15 | }
16 |
17 | if (typeof opts.inputType === 'undefined' || opts.inputType === null) {
18 | opts.inputType = InputType.NO_INPUT;
19 | }
20 |
21 | cordova.exec(successCallback, errorCallback, "SketchPlugin", "getSketch", [opts]);
22 | };
23 |
24 | var InputType = {
25 | NO_INPUT: 0, // no input as background image, use as signature plugin
26 | DATA_URL: 1, // base64 encoded string stream
27 | FILE_URI: 2 // file uri (content://media/external/images/media/2 for Android)
28 | };
29 |
30 | var DestinationType = {
31 | DATA_URL: 0, // Return base64 encoded string
32 | FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android)
33 | };
34 |
35 | var EncodingType = {
36 | JPEG: 0, // Return JPEG encoded image
37 | PNG: 1 // Return PNG encoded image
38 | };
39 |
40 | sketch.getSketch = getSketch;
41 | sketch.InputType = InputType;
42 | sketch.DestinationType = DestinationType;
43 | sketch.EncodingType = EncodingType;
44 |
45 | return sketch;
46 | };
47 |
48 | module.exports = Sketch();
49 |
--------------------------------------------------------------------------------