This repository has been archived on 2026-05-12. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
Inkpad/program.js
2023-12-21 12:40:14 -03:00

1798 lines
60 KiB
JavaScript

//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//// PARTICULAR PURPOSE.
////
//// Copyright (c) Microsoft Corporation. All rights reserved
// Sample app demonstrating the use of Ink and Reco APIs in the modern SDK.
// We are using Windows.UI.Input.Inking.InkManager.
function showMessage(message, isError)
{
var status = document.getElementById("statusMessage");
if (status)
{
status.innerText = message;
status.style.color = isError ? "red" : "green";
}
}
function displayStatus(message)
{
showMessage(message, false);
}
function displayError(message)
{
showMessage(message, true);
}
// ISSUE: Uncomment-out this call to get status messages from handleSuspending and handleActivated
function displayDebug(message)
{
// showMessage(message, false);
}
window.onerror = function (msg, url, line) {displayError("Error: " + msg + " url = " + url + " line = " + line);};
// Functions to convert from and to the 32-bit int used to represent color in Windows.UI.Input.Inking.InkManager.
// Convenience function used by color converters.
// Assumes arg num is a number (0..255); we convert it into a 2-digit hex string.
function byteHex(num)
{
var hex = num.toString(16);
if (hex.length === 1)
{
hex = "0" + hex;
}
return hex;
}
// Convert from Windows.UI.Input.Inking's color code to html's color hex string.
// Note the little-endian representation.
function toColorString(num)
{
var R = (num & 0x0000FF);
var G = (num & 0x00FF00) >> 8;
var B = (num & 0xFF0000) >> 16;
var str = "#" + byteHex(R) + byteHex(G) + byteHex(B);
return str;
}
// Convert from the few color names used in this app to Windows.UI.Input.Inking's color code.
// If it isn't one of those, then decode the hex string. Otherwise return gray.
// This does not include any alpha component.
// Note the little-endian representation.
function toColorInt(color)
{
switch (color)
{
// Ink colors
case "Black":
return 0x000000;
case "Blue":
return 0xFF0000;
case "Red":
return 0x0000FF;
case "Green":
return 0x008000;
// Highlighting colors
case "Yellow":
return 0x00FFFF;
case "Aqua":
return 0xFFFF00;
case "Lime":
return 0x00FF00;
// Select colors
case "Gold":
return 0x00D7FF;
case "White":
return 0xFFFFFF;
}
if ((color.length === 7) && (color.charAt(0) === "#"))
{
var R = parseInt(color.substr(1, 2), 16);
var G = parseInt(color.substr(3, 2), 16);
var B = parseInt(color.substr(5, 2), 16);
return (B << 16) + (G << 8) + R;
}
return 0x808080; // Gray
}
// Global variables representing the ink interface.
// The usage of a global variable for drawingAttributes is not completely necessary,
// just a convenience. One could always re-fetch the current drawingAttributes
// from the inkManager.
var inkManager = new Windows.UI.Input.Inking.InkManager();
var drawingAttributes = new Windows.UI.Input.Inking.InkDrawingAttributes();
drawingAttributes.fitToCurve = true;
drawingAttributes.alpha = 255;
inkManager.setDefaultDrawingAttributes(drawingAttributes);
// These are the global canvases (and their 2D contexts) for highlighting, for drawing ink,
// and for lassoing (and erasing).
var hlCanvas;
var hlContext;
var inkCanvas;
var inkContext;
var selCanvas;
var selContext;
// The "mode" of whether we are highlighting, inking, lassoing, or erasing is controlled by this global variable,
// which should be pointing to either hlContext, inkContext, or selContext.
// In lassoing mode (when context points to selContext), we might also be in erasing mode;
// the state of lassoing vs. erasing is kept inside the ink manager, in attribute "mode", which will
// have a value from enum Windows.UI.Input.Inking.InkManipulationMode, one of either "selecting"
// or "erasing" (the other value being "inking" but in that case context will be pointing to one of the other
// 2 canvases).
var context;
// Three functions to save and restore the current mode, and to clear this state.
// Note that we can get into erasing mode in one of two ways: there is a eraser button in the toolbar,
// and some pens have an active back end that is meant to represent erasing. If we get into erasing
// mode via the button, we stay in that mode until another button is pushed. If we get into erasing
// mode via the eraser end of the stylus, we should switch out of it when the user switches to the ink
// end of the stylus. And we want to return to the mode we were in before this happened. Thus we
// maintain a shallow stack (depth 1) of "mode" info.
var savedContext = null;
var savedStyle = null;
var savedMode = null;
function clearMode()
{
savedContext = null;
savedStyle = null;
savedMode = null;
}
function saveMode()
{
if (!savedContext)
{
savedStyle = context.strokeStyle;
savedContext = context;
savedMode = inkManager.mode;
}
}
function restoreMode()
{
if (savedContext)
{
context = savedContext;
context.strokeStyle = savedStyle;
inkManager.mode = savedMode;
clearMode();
}
}
// Global variable representing the pattern used when in select mode. This is an 8*1 image with 4 bits set,
// then 4 bits cleared, to give us a dashed line when drawing a lasso.
var selPattern;
// Global variable representing the application toolbar at the bottom of the screen.
var appbar;
// Global pointers to the find, and ink and highlight colors and widths, flyouts.
var findFlyout;
var inkColorsFlyout;
var inkWidthsFlyout;
var hlColorsFlyout;
var hlWidthsFlyout;
// Global pointer to the "More" button's flyout. The "More" button shows as an ellipsis in the toolbar.
var moreFlyout;
// Global pointer to the dialog box used for displaying recognition results (top 5 alternates),
// and an array of buttons (one per alternate). Note that while we think of it as a dialog box,
// it is really an html form.
var recoFlyout;
var clipButtons;
// Global pointer to the text buffer inside the Find flyout.
var findText;
// Returns true if any strokes inside the ink manager are selected; false otherwise.
function anySelected()
{
var strokes = inkManager.getStrokes();
var len = strokes.length;
for (var i = 0; i < len; i++)
{
if (strokes[i].selected)
{
return true;
}
}
return false;
}
//Returns true if this stroke is a highlighting stroke.
function isHighlighting(stroke)
{
var att = stroke.drawingAttributes;
return att.alpha < 200;
}
// Makes all strokes a part of the selection.
function selectAll()
{
var strokes = inkManager.getStrokes();
var len = strokes.length;
for (var i = 0; i < len; i++)
{
strokes[i].selected = 1;
}
}
// Makes all non-highlight strokes a part of the selection.
function selectAllNoHighlight()
{
var strokes = inkManager.getStrokes();
var len = strokes.length;
for (var i = 0; i < len; i++)
{
if (!isHighlighting(strokes[i]))
{
strokes[i].selected = 1;
}
}
}
// Unselects any strokes which are highlighting.
function unselectHighlight()
{
var strokes = inkManager.getStrokes();
var len = strokes.length;
for (var i = 0; i < len; i++)
{
if (strokes[i].selected && isHighlighting(strokes[i]))
{
strokes[i].selected = 0;
}
}
}
// Returns true if the point represented by x,y is within the rect.
function inRect(x, y, rect)
{
return ((rect.x <= x) && (x < (rect.x + rect.width)) &&
(rect.y <= y) && (y < (rect.y + rect.height)));
}
// Tests the array of results bounding boxes (from the recognition results on the ink manager).
// If a hit is found, select that ink (otherwise make sure no strokes are selected).
// Returns an object representing the results, with the original touch coordinates, the bounding
// box, the index of the result, the array of strokes in that specific word, and the array of alternates (recognition strings).
// If the touch is outside of any ink bounding box, then returns null.
function hitTest(tx, ty)
{
var results = inkManager.getRecognitionResults();
var cWords = results.size;
// This will unselect any current selection.
var pt = {x:0.0, y:0.0};
inkManager.selectWithLine(pt, pt);
for (var i = 0; i < cWords; i++)
{
var rect = results[i].boundingRect;
if (inRect(tx, ty, rect))
{
var strokes = results[i].getStrokes();
var cStrokes = strokes.size;
for (var j = 0; j < cStrokes; j++)
{
strokes[j].selected = true;
}
return {index: i,
handleX: tx, // Original touch point
handleY: ty,
strokes: strokes,
rect: rect,
alternates: results[i].getTextCandidates()};
}
}
return null;
}
// Note that we cannot just set the width in stroke.drawingAttributes.size.width,
// or the color in stroke.drawingAttributes.color.
// The stroke API supports get and put operations for drawingAttributes,
// but we must execute those operations separately, and change any values
// inside drawingAttributes between those operations.
// Change the color and width in the default (used for new strokes) to the values
// currently set in the current context.
function setDefaults()
{
var strokeSize = drawingAttributes.size;
strokeSize.width = strokeSize.height = context.lineWidth;
drawingAttributes.size = strokeSize;
drawingAttributes.color = toColorInt(context.strokeStyle);
drawingAttributes.alpha = (context === hlContext) ? 128 : 255;
inkManager.setDefaultDrawingAttributes(drawingAttributes);
}
// Four functions to switch back and forth between ink mode, highlight mode, select mode, and erase mode.
// There is also a temp erase mode, which uses the saveMode()/restoreMode() functions to
// return us to our previous mode when done erasing. This is used for quick erasers using the back end
// of the pen (for those pens that have that).
// NOTE: The erase modes also attempt to set the mouse/pen cursor to the image of a chalkboard eraser
// (stored in images/erase.cur), but as of this writing cursor switching is not working.
function highlightMode()
{
clearMode();
context = hlContext;
inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.inking;
setDefaults();
selCanvas.style.cursor = "default";
}
function inkMode()
{
clearMode();
context = inkContext;
inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.inking;
setDefaults();
selCanvas.style.cursor = "default";
}
function selectMode()
{
clearMode();
selContext.strokeStyle = selPattern;
context = selContext;
inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.selecting;
}
function eraseMode()
{
clearMode();
selContext.strokeStyle = "rgba(255,255,255,0.0)";
context = selContext;
inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.erasing;
selCanvas.style.cursor = "url(images/erase.cur), auto";
}
function tempEraseMode()
{
saveMode();
selContext.strokeStyle = "rgba(255,255,255,0.0)";
context = selContext;
inkManager.mode = inkManager.mode = Windows.UI.Input.Inking.InkManipulationMode.erasing;
selCanvas.style.cursor = "url(images/erase.cur), auto";
}
// Set the width of a stroke. Return true if we actually changed it.
// Note that we cannot just set the width in stroke.drawingAttributes.size.width.
// The stroke API supports get and put operations for drawingAttributes,
// but we must execute those operations separately, and change any values
// inside drawingAttributes between those operations.
function shapeStroke(stroke, width)
{
var att = stroke.drawingAttributes;
var strokeSize = att.size;
if (strokeSize.width !== width)
{
strokeSize.width = strokeSize.height = width;
att.size = strokeSize;
stroke.drawingAttributes = att;
return true;
}
else
{
return false;
}
}
// Set the color (and alpha) of a stroke. Return true if we actually changed it.
// Note that we cannot just set the color in stroke.drawingAttributes.color.
// The stroke API supports get and put operations for drawingAttributes,
// but we must execute those operations separately, and change any values
// inside drawingAttributes between those operations.
function colorStroke(stroke, color)
{
var att = stroke.drawingAttributes;
var code = toColorInt(color);
if (att.color !== code)
{
att.color = code;
stroke.drawingAttributes = att;
return true;
}
else
{
return false;
}
}
// Global memory of the current pointID (for pen, and, separately, for touch).
// We ignore handlePointerMove() and handlePointerUp() calls that don't use the same
// pointID as the most recent handlePointerDown() call. This is because the user sometimes
// accidentally nudges the mouse while inking or touching. This can cause move events
// for that mouse that have different x,y coordinates than the ink trace or touch path
// we are currently handling.
// MSPointer* events maintain this pointId so that one can track individual fingers,
// the pen, and the mouse.
// Note that when the pen fails to leave the area where it can be sensed, it does NOT
// get a new ID; so it is possible for 2 or more consecutive strokes to have the same ID.
var penID = -1;
var touchID = -1;
// If we have touched the screen, the details will be inside global variable touchedResults.
// If we touched inside a recognized word, additional details about that word will be included.
// See hitTest() above.
var touchedResults = null;
// In pointer handlers, evt.pointerType will be one of these values:
// 2 Touch
// 3 Pen
// 4 Mouse
// We will accept pen down or mouse left down as the start of a stroke.
// We will accept touch down or mouse right down as the start of a touch.
function handlePointerDown(evt)
{
try
{
if ((evt.pointerType === 3) || ((evt.pointerType === 4) && (evt.button === 1)))
{
evt.preventManipulation();
var pt = evt.currentPoint;
if (pt.properties.isEraser) // the back side of a pen, which we treat as an eraser
{
tempEraseMode();
}
else
{
restoreMode();
}
if (inkManager.mode === Windows.UI.Input.Inking.InkManipulationMode.inking)
{
// Unselect all strokes.
var origin = {x:0.0, y:0.0};
inkManager.selectWithLine(origin, origin);
}
context.beginPath();
context.moveTo(pt.rawPosition.x, pt.rawPosition.y);
inkManager.processPointerDown(pt);
penID = evt.pointerId;
}
else if ((evt.pointerType === 2) || ((evt.pointerType === 4) && (evt.button === 2)))
{
// ISSUE: Optionally, one could remember the selected strokes in a global,
// before selecting the touched strokes (or all strokes), so that they can be restored after the move/pan.
touchedResults = hitTest(evt.offsetX, evt.offsetY);
if (touchedResults)
{
evt.preventManipulation();
touchID = evt.pointerId;
}
}
}
catch (e)
{
displayError("handlePointerDown " + e.toString());
}
}
function handlePointerMove(evt)
{
try
{
var pt = evt.currentPoint;
if (evt.pointerId === penID)
{
evt.preventManipulation();
context.lineTo(pt.rawPosition.x, pt.rawPosition.y);
context.stroke();
// Get all the points we missed and feed them to inkManager.
// The array pts includes (as the last one) the point in pt above (returned by evt.currentPoint).
// var pts = evt.intermediatePoints;
// var i;
// for (i = 0; i < pts.length; i++)
// {
// inkManager.processPointerUpdate(pts[i]);
// }
inkManager.processPointerUpdate(pt);
}
else if (evt.pointerId === touchID)
{
if (touchedResults)
{
evt.preventManipulation();
if ((touchedResults.x !== evt.offsetX) || (touchedResults.y !== evt.offsetY))
{
var shift = {x: evt.offsetX - touchedResults.handleX, y: evt.offsetY - touchedResults.handleY};
inkManager.moveSelected(shift);
renderAllStrokes("suppress");
touchedResults.handleX = evt.offsetX;
touchedResults.handleY = evt.offsetY;
}
}
}
}
catch (e)
{
displayError("handlePointerMove " + e.toString());
}
}
function handlePointerUp(evt)
{
try
{
var pt = evt.currentPoint;
if (evt.pointerId === penID)
{
evt.preventManipulation();
context.lineTo(pt.rawPosition.x, pt.rawPosition.y);
context.stroke();
context.closePath();
inkManager.processPointerUp(pt);
}
else if (evt.pointerId === touchID)
{
if (touchedResults)
{
evt.preventManipulation();
if ((touchedResults.x !== evt.offsetX) || (touchedResults.y !== evt.offsetY))
{
var shift = {x: pt.rawPosition.x - touchedResults.handleX, y: pt.rawPosition.y - touchedResults.handleY};
inkManager.moveSelected(shift);
}
// If we touched a single word, then either put up the recognitions (if we didn't move much),
// or re-run the recognizer (if we did move substantially).
if (inRect(pt.rawPosition.x, pt.rawPosition.y, touchedResults.rect))
{
evt.preventDefault();
touchWord(touchedResults);
}
else
{
recognize(evt);
}
}
}
touchedResults = null;
touchID = -1;
penID = -1;
renderAllStrokes();
}
catch (e)
{
displayError("handlePointerUp " + e.toString());
}
}
// We treat the event of the pen leaving the canvas as the same as the pen lifting;
// it completes the stroke.
function handlePointerOut(evt)
{
try
{
if (evt.pointerId === penID)
{
evt.preventManipulation();
var pt = evt.currentPoint;
context.lineTo(pt.rawPosition.x, pt.rawPosition.y);
context.stroke();
context.closePath();
inkManager.processPointerUp(pt);
touchedResults = null;
touchID = -1;
penID = -1;
renderAllStrokes();
}
}
catch (e)
{
displayError("handlePointerOut " + e.toString());
}
}
//Draws a single stroke into a specified canvas 2D context, with a specified color and width.
function renderStroke(stroke, color, width, ctx)
{
ctx.save();
try
{
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = width;
var first = true;
stroke.getRenderingSegments().forEach(function (segment)
{
if (first)
{
ctx.moveTo(segment.position.x, segment.position.y);
first = false;
}
else
{
ctx.bezierCurveTo(segment.bezierControlPoint1.x, segment.bezierControlPoint1.y,
segment.bezierControlPoint2.x, segment.bezierControlPoint2.y,
segment.position.x, segment.position.y);
}
});
ctx.stroke();
ctx.closePath();
ctx.restore();
}
catch (e)
{
ctx.restore();
displayError("renderStroke " + e.toString());
}
}
// This draws a basic notepaper pattern into the highlight canvas, which is the lowest canvas.
// It has a single vertical dark red line defining the left margin, and a series of horizontal blue lines.
function renderPaper()
{
var height = hlCanvas.height;
var bottom = height - 0.5;
var right = hlCanvas.width - 0.5;
hlContext.save();
try
{
hlContext.beginPath();
hlContext.strokeStyle = "FireBrick";
hlContext.lineWidth = 1;
hlContext.moveTo(120.5, 0.5);
hlContext.lineTo(120.5, bottom);
hlContext.stroke();
hlContext.closePath();
hlContext.beginPath();
hlContext.strokeStyle = "Blue";
hlContext.lineWidth = 1;
for (var y = 65.5; y < height; y += 55)
{
hlContext.moveTo(0.5, y);
hlContext.lineTo(right, y);
}
hlContext.stroke();
hlContext.closePath();
hlContext.restore();
}
catch(e)
{
hlContext.restore();
displayError("renderPaper " + e.toString());
}
}
// Redraws (from the beginning) all strokes in the canvases. All canvases are erased,
// then the paper is drawn, then all the strokes are drawn.
// Selected strokes glow unless the arg "suppress" is passed.
function renderAllStrokes(suppress)
{
var glow = !suppress || (suppress !== "suppress");
selContext.clearRect(0, 0, selCanvas.width, selCanvas.height);
inkContext.clearRect(0, 0, inkCanvas.width, inkCanvas.height);
hlContext.clearRect(0, 0, hlCanvas.width, hlCanvas.height);
renderPaper();
inkManager.getStrokes().forEach(function (stroke)
{
var att = stroke.drawingAttributes;
var color = toColorString(att.color);
var strokeSize = att.size;
var width = strokeSize.width;
var hl = isHighlighting(stroke);
var ctx = hl ? hlContext : inkContext;
if (glow && stroke.selected)
{
renderStroke(stroke, color, width * 2, ctx);
var stripe = (hl ? "Azure" : "White");
var w = width - (hl ? 3 : 1);
renderStroke(stroke, stripe, w, ctx);
}
else
{
renderStroke(stroke, color, width, ctx);
}
});
}
function clear()
{
try
{
WinJS.UI.getControl(moreFlyout).hide();
if (anySelected())
{
inkManager.deleteSelected();
}
else
{
var strokeView = inkManager.getStrokes();
var strokeViewSize = strokeView.size;
for (var i = 0; i < strokeViewSize; i++)
{
var stroke = strokeView.getAt(i);
stroke.selected = true;
}
inkManager.deleteSelected();
inkMode();
}
renderAllStrokes();
displayStatus("");
}
catch (e)
{
displayError("clear " + e.toString());
}
}
// A button handler which fetches the value from the button, which should
// be a number. We set the lineWidth of the inking canvas to this width,
// then set the system into ink mode (which will cause the ink manager
// to change its defaults for new strokes to match the ink canvas).
// If any ink strokes (not including highlight strokes) are currently selected,
// we also change their width to this value. If any strokes are changed
// we must re-render the entire ink display.
function setInkWidth(evt)
{
try
{
WinJS.UI.getControl(inkWidthsFlyout).hide();
inkContext.lineWidth = evt.srcElement.value;
inkMode();
// Change any selected strokes to the new width. If any strokes change,
// we must also re-render all the strokes.
var redraw = false;
inkManager.getStrokes().forEach(function (stroke)
{
if (stroke.selected && !isHighlighting(stroke))
{
if (shapeStroke(stroke, inkContext.lineWidth))
{
redraw = true;
}
}
});
if (redraw)
{
renderAllStrokes();
}
}
catch (e)
{
displayError("setInkWidth " + e.toString());
}
}
// A button handler which fetches the value from the button, which should
// be a number. We set the lineWidth of the highlight canvas to this width,
// then set the system into highlight mode (which will cause the ink manager
// to change its defaults for new strokes to match the highlight canvas).
// If any highlight strokes are currently selected, we also change their width
// to this value. If any strokes are changed we must re-render the entire ink display.
function setHighlightWidth(evt)
{
try
{
WinJS.UI.getControl(hlWidthsFlyout).hide();
hlContext.lineWidth = evt.srcElement.value;
highlightMode();
// Change any selected strokes to the new width. If any strokes change,
// we must also re-render all the strokes.
var redraw = false;
inkManager.getStrokes().forEach(function (stroke)
{
if (stroke.selected && isHighlighting(stroke))
{
if (shapeStroke(stroke, hlContext.lineWidth))
{
redraw = true;
}
}
});
if (redraw)
{
renderAllStrokes();
}
}
catch (e)
{
displayError("setInkWidth " + e.toString());
}
}
// A button handler which fetches the value from the button, which should
// be a color name. We set the strokeStyle of the inking canvas to this color,
// then set the system into ink mode (which will cause the ink manager
// to change its defaults for new strokes to match the ink canvas).
// If any ink strokes (not including highlight strokes) are currently selected,
// we also change their color to this value. If any strokes are changed
// we must re-render the entire ink display.
function inkColor(evt)
{
WinJS.UI.getControl(inkColorsFlyout).hide();
inkContext.strokeStyle = evt.srcElement.id;
inkMode();
// Change any selected strokes to the new color. If any strokes are selected,
// we must also re-render all the strokes.
var redraw = false;
inkManager.getStrokes().forEach(function (stroke)
{
if (stroke.selected && !isHighlighting(stroke))
{
if (colorStroke(stroke, inkContext.strokeStyle))
{
redraw = true;
}
}
});
if (redraw)
{
renderAllStrokes();
}
}
// A button handler which fetches the value from the button, which should
// be a color name. We set the strokeStyle of the highlight canvas to this color,
// then set the system into highlight mode (which will cause the ink manager
// to change its defaults for new strokes to match the highlight canvas).
// If any highlight strokes are currently selected, we also change their color
// to this value. If any strokes are changed we must re-render the entire ink display.
function highlightColor(evt)
{
WinJS.UI.getControl(hlColorsFlyout).hide();
hlContext.strokeStyle = evt.srcElement.id;
highlightMode();
// Change any selected high-lighting strokes to the new color. If any strokes change,
// we must also re-render all the strokes.
var redraw = false;
inkManager.getStrokes().forEach(function (stroke)
{
if (stroke.selected && isHighlighting(stroke))
{
if (colorStroke(stroke, hlContext.strokeStyle))
{
redraw = true;
}
}
});
if (redraw)
{
renderAllStrokes();
}
}
// Finds a specific recognizer, and sets the inkManager's default to that recognizer.
// Returns true if successful.
function setRecognizerByName(name)
{
try
{
// recognizerView is a normal JavaScript array.
var recognizerView = inkManager.getRecognizers();
for (var i = 0, len = recognizerView.length; i < len; i++)
{
if (name === recognizerView[i].name)
{
inkManager.setDefaultRecognizer(recognizerView[i]);
return true;
}
}
}
catch (e)
{
displayError("setRecognizerByName " + e.toString());
}
return false;
}
// A button handler which runs the currently-loaded handwriting recognizer over
// the selected ink (not counting highlight strokes). If no ink is selected, then it
// runs over all the ink (again, not counting highlight strokes).
// The recogntion results (a string) is displayed in the status window.
// The recognitino results are also stored within the ink manager itself, so that
// other commands can find the bounding boxes (or ink strokes) of any specific
// word of ink.
function recognize(evt)
{
try
{
var strokes = inkManager.getStrokes();
if (strokes.length === 0)
{
displayStatus("Must first write something");
return;
}
// The recognizeAsync() method has 3 modes: selected, remaining, and all.
// This particular app cannot use "all" mode because it supports highlighting.
// If the user has highlighted one or more words, and we recognize in "all" mode,
// we will recognize all strokes, including the highlight strokes. This usually
// results in a recognition string containing many asterisks.
// If we find that no strokes are selected, rather than running in "all" mode, we
// select all strokes that are not highlighting strokes, then run in "selected" mode.
// If some strokes were already selected, we just need to unselect any which are highlighting.
// If we DID originally find that no strokes were selected, we remember that fact, so that
// we can unselect them after the recognition.
var bSelected = false;
if (anySelected())
{
unselectHighlight();
}
else
{
selectAllNoHighlight();
bSelected = true;
}
// Note that the third mode in recognizeAsync(), "recent", can be very useful in certain situations,
// but we are not using it here. It will recognize all strokes that have been added since the last
// recognition. If we were assuming that all strokes were writing, and we were trying to keep
// recognition caught up with the user's writing at all times (that is, not using a Reco button),
// then "recent" would be the mode we would want.
// Because recognition is slower, we ask for it as an asynchronous operation.
// The anonymous function (the first arg to the "then" method) will be called
// as a callback when recognition has completed. If an error occurs, the second
// arg will be called.
inkManager.recognizeAsync(Windows.UI.Input.Inking.InkRecognitionTarget.selected).then
(
function (results)
{
// Doing a recognition does not update the storage of results (the results that are stored inside the ink manager).
// We do that ourselves by calling this method.
inkManager.updateRecognitionResults(results);
// The arg "results" is an array of result objects representing "words", where "words" means words of ink (not computer memory words).
// IE, if you write "this is a test" that is 4 words, and results will be an array of length 4.
// var resultsCount = results.size;
var alternates = ""; // will accumulate the result words, with spaces between
results.forEach(function (recognitionResult) // iterate over the words of ink
{
// Method getTextCandidates() returns an array of recognition alternates (different interpretations of the same word of ink).
// For this program we only use the first (top) alternate in our display.
// If we were doing search over this ink, we would want to search all alternates.
var alternateStringArray = recognitionResult.getTextCandidates();
alternates = alternates + " " + alternateStringArray[0];
// The specific strokes forming the current word of ink are available to us.
// This feature is not used here, but we could, if we chose, display the ink,
// with the recognition result for each word directly above the specific word of ink,
// by fetching the bounding box of the recognitionResult (via the boundingRect property).
// Or, if we needed to do something to each stroke in the recognized word, we could
// call recognitionResult.getStrokes(), then iterate over the individual strokes.
});//end recognitionResultView.forEach
displayStatus(alternates);
},
function (e)
{
displayError("InkManager::recognizeAsync " + e.toString());
}
);
if (bSelected)
{
// Unselect all strokes (if we originally had no selected strokes).
var pt = {x:0.0, y:0.0};
inkManager.selectWithLine(pt, pt);
}
}
catch (e)
{
displayError("recognize: " + e.toString());
}
}
// A utility function for findText() below. This takes a target string (typed in by the user)
// an an array of recognition results objects, and inspects the recognition alternates of each
// results object. If a match is found among the alternates, then all strokes in that results
// object are selected.
function findWord(target, results)
{
try
{
var cWords = results.size;
var count = 0;
for (var i = 0; i < cWords; i++)
{
var alternates = results[i].getTextCandidates();
var cAlts = alternates.size;
for (var j = 0; j < cAlts; j++)
{
if (alternates[j].toLowerCase() === target.toLowerCase())
{
var strokes = results[i].getStrokes();
var cStrokes = strokes.size;
for (var k = 0; k < cStrokes; k++)
{
strokes[k].selected = true;
}
count++;
break;
}
}
}
return count;
}
catch (e)
{
displayError("findWord: " + e.toString());
}
}
// A handler for the Find button in the Find flyout. We fetch the search string
// from the form, and the array of recognition results objects from the ink
// manager. We unselect any current selection, so that when we are done
// the selections will reflect the search results. We split the search string into
// individual words, since our recognition results objects each represent individual
// words. The actual matching is done by findWord(), defined above.
// Note that multiple instances of a target can be found; if the target is "this" and
// the ink contains "this is this is that", 2 instances of "this" will be found and all
// strokes in both words will be selected.
// Note that findWord() above searches all alternates. This means you might write
// "this", have it mis-recognized as "these", but the search feature MAY find it, if
// "this" appears in any of the other 4 recognition alternates for this ink.
function find(evt)
{
try
{
WinJS.UI.getControl(findFlyout).hide();
var str = findText.value;
var results = inkManager.getRecognitionResults();
// This will unselect any current selection.
var pt = {x:0.0, y:0.0};
inkManager.selectWithLine(pt, pt);
var count = 0;
var words = str.split(" ");
for (var i = 0; i < words.length; i++)
{
count += findWord(words[i], results);
}
if (0 < count)
{
displayStatus("Found " + count + " words");
renderAllStrokes();
}
else
{
displayStatus("Did not find " + str);
}
return false;
}
catch (e)
{
displayError("find: " + e.toString());
}
return false;
}
// A form submit handler for recognition results buttons in the "reco" dialog box.
// The "reco" dialog box shows the top 5 recognition results for a specific word, and
// is invoked by tapping likely on a word (after recognition has been run).
// The top 5 results are actually submit buttons in this dialog box.
// We fetch the recognition result (the value of the submit button, a string) and
// copy it to the clipboard.
function recoClipboard(evt)
{
try
{
WinJS.UI.getControl(recoFlyout).hide();
var word = evt.srcElement.innerHTML;
var dataPackage = new Windows.ApplicationModel.DataTransfer.DataPackage();
dataPackage.setText(word);
Windows.ApplicationModel.DataTransfer.Clipboard.setContent(dataPackage);
displayStatus("To clipboard: " + word);
}
catch (e)
{
displayError("recoClipboard " + e.toString());
}
}
// Brings up the "reco" dialog box, after first changing the values of the 5 submit buttons to be
// the top 5 recognition alternates of a single word.
function touchWord(touchedResults)
{
try
{
// The Windows.UI.Input.Inking.InkManager interface normally returns 5 alternates.
// We check just to be sure we are not given more alternates than the count of buttons.
var cAlts = touchedResults.alternates.size;
if (cAlts == 0)
{
return;
}
var cButs = clipButtons.length;
if (cButs < cAlts)
{
cAlts = cButs;
}
var i;
for (i = 0; i < cAlts; i++)
{
clipButtons[i].innerHTML = touchedResults.alternates[i];
}
for (; i < cButs; i++)
{
clipButtons[i].innerHTML = "";
}
// Compute a location for the reco results dialog box that will be just to the left of the left-top corner of the bounding rect of the ink.
// If the x value goes negative, move it up to 0 (and let it cover the ink if need be).
// If the y value would cause the bottom to go off the bottom of the screen, move it down (in value, or up in y).
// Note how we use the dialog's (form's) clientWidth, not its style.width; this gives us the output of the live computation of the form's size.
// And we compute the limit (to avoid going off the bottom of the screen) using window.innerHeight - recoFlyout.offsetHeight.
// We use window.innerHeight, and not document.body.offsetHeight (or clientHeight) because the body height does not include
// the fixed-position items (the canvases).
var wordLeft = touchedResults.rect.x;
var wordTop = touchedResults.rect.y;
wordTop += selCanvas.offsetTop;
var limit = window.innerHeight - recoFlyout.offsetHeight - 2;
if (limit < wordTop)
{
wordTop = limit;
}
wordLeft -= (recoFlyout.offsetWidth + 40);
if (wordLeft < 0)
{
wordLeft = 0;
}
recoFlyout.style.left = wordLeft + "px";
recoFlyout.style.top = wordTop + "px";
setTimeout(function() {WinJS.UI.getControl(recoFlyout).show();}, 1);
}
catch (e)
{
displayError("touchWord: " + e.toString());
}
}
// A button handler which copies the selected strokes (or all the strokes if none are selected)
// into the clipboard. The strokes can be pasted into any application that handles any of the
// ink clipboard formats, such as Windows Journal.
function copySelected(evt)
{
try
{
WinJS.UI.getControl(moreFlyout).hide();
if (anySelected())
{
displayStatus("Copying selected strokes ...");
inkManager.copySelectedToClipboard();
displayStatus("Copy Selected");
}
else
{
displayStatus("Copying all strokes ...");
selectAll();
inkManager.copySelectedToClipboard();
// Unselect all strokes.
var pt = {x:0.0, y:0.0};
inkManager.selectWithLine(pt, pt);
displayStatus("Copy All");
}
}
catch (e)
{
displayError("touchWord: " + e.toString());
}
}
// A button handler which copies any available strokes in the clipboard into this app.
function paste(evt)
{
WinJS.UI.getControl(moreFlyout).hide();
displayStatus("Pasting ...");
var insertionPoint = {x: 100, y: 60};
var canPaste = inkManager.canPasteFromClipboard();
if (canPaste)
{
inkManager.pasteFromClipboard(insertionPoint);
displayStatus("Pasted");
renderAllStrokes();
}
else
{
displayStatus("Cannot paste");
}
}
// A button handler which closes the program.
function closeProgram(evt)
{
displayStatus("Closing App ...");
window.close();
}
function readInk(file)
{
if (file)
{
file.openAsync(Windows.Storage.FileAccessMode.read).then
(
function(file)
{
var inputStream = file.getInputStreamAt(0);
inkManager.load(inputStream);
var strokeView = inkManager.getStrokes();
var c = strokeView.size;
displayStatus("Loaded " + c + " strokes.");
renderAllStrokes();
},
function(e)
{
displayError("file::openAsync " + e.toString());
}
);
}
}
function load(evt)
{
try
{
WinJS.UI.getControl(moreFlyout).hide();
// Open the WinRT file picker, set the input folder, and set the input extension.
var picker = new Windows.Storage.Pickers.FileOpenPicker();
picker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
picker.fileTypeFilter.replaceAll([".gif"]);
// Get the input file; file is of type Windows.Storage.StorageFile.
picker.pickSingleFileAsync().then
(
readInk,
function(e)
{
displayError("Picker::pickSingleFileAsync *.gif " + e.toString());
}
);
}
catch (e)
{
displayError("load " + e.toString());
}
}
function writeInk(file)
{
file.openAsync(Windows.Storage.FileAccessMode.readWrite).then
(
function(file)
{
var stream = file.getOutputStreamAt(0);
inkManager.saveAsync(stream).then
(
function()
{
stream.flushAsync().then
(
function()
{
GC.Collect();
// Print the size of the stream on the screen.
var size = stream.size;
displayStatus("Saved " + size.toString() + " bytes.");
},
function(e)
{
displayError("flushAsync " + e.toString());
}
);
},
function(e)
{
displayError("saveAsync " + e.toString());
}
);
},
function(e)
{
displayError("openAsync " + e.toString());
}
);
}
function save(evt)
{
try
{
WinJS.UI.getControl(moreFlyout).hide();
var picker = new Windows.Storage.Pickers.FileSavePicker();
picker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
picker.fileTypeChoices.insert("GIF file", [".gif"]);
picker.defaultFileExtension = ".gif";
picker.pickSaveFileAsync().then
(
writeInk,
function(e)
{
displayError("Windows::Storage::Picker::pickSaveFileAsync " + e.toString());
}
);
}
catch (e)
{
displayError("save " + e.toString());
}
}
// A keypress handler that only handles a few keys. This is registered on the entire body.
// Escape will:
// 1. If any dialog boxes are showing, hide them and do nothing else.
// 2. Otherwise, if any strokes are selected, unselect them and do nothing else.
// 3. Otherwise, change to ink mode.
// This sequence allows us to "unpeel the onion" (it is very fast to hit escape 3 times if needed).
// Certain control keys invoke handlers that are otherwise invoked via buttons:
// ^C Copy
// ^V Paste
// ^F Find
// ^O Load
// ^S Save
// ^R Recognize
// ^Q Quit (shuts down the sample app)
// Note that most of these keys have standardized normal uses, and there is system code to handle that
// without our code doing anything. That code sometimes interferes with our program. All the functions
// we call from here call evt.preventDefault(), which should stop the default processing, but sometimes we still
// cannot get this code to execute.
function keypress(evt)
{
if (evt.keyCode === 27) // escape
{
evt.preventDefault();
if (!WinJS.UI.getControl(recoFlyout).hidden)
{
WinJS.UI.getControl(recoFlyout).hide();
renderAllStrokes();
}
else if (anySelected())
{
// Unselect all strokes.
var pt = {x:0.0, y:0.0};
inkManager.selectWithLine(pt, pt);
renderAllStrokes();
}
else
{
inkMode();
}
}
else if (evt.keyCode === 3) // control c
{
copySelected(evt);
}
else if (evt.keyCode === 22) // control v
{
paste(evt);
}
else if (evt.keyCode === 15) // control o
{
load(evt);
}
else if (evt.keyCode === 19) // control s
{
save(evt);
}
else if (evt.keyCode === 18) // control r
{
recognize(evt);
}
else if (evt.keyCode === 17) // control q
{
closeProgram(evt);
}
else if (evt.keyCode === 1) // control a
{
displayDebug("SPY: button.hidden " + clipButtons[0].hidden + // property undefined
" button.style.hidden " + clipButtons[0].style.hidden + // property undefined
" WinJS.UI.getControl(button).hidden " + WinJS.UI.getControl(clipButtons[0]).hidden + // object is null or undefined
" WinJS.UI.getControl(button).style.hidden " + WinJS.UI.getControl(clipButtons[0]).style.hidden); // object is null or undefined
}
}
var suspendFlags = {ink: false,
inkFile: false,
inkMode: false,
inkColor: false,
inkWidth: false,
highlightColor: false,
highlightWidth: false};
// Because there are asynchronous operations during suspension, we signal the need to handle suspension
// of the application asynchronously (by calling getDeferral()). We then proclaim we are done by
// calling complete() on that deferral.
var deferral = null;
function checkDeferral()
{
// After all asynchronous operations are done the app must call complete() on the deferral object;
// otherwise the app will be terminated.
if (suspendFlags.ink && suspendFlags.inkFile && suspendFlags.inkMode &&
suspendFlags.inkColor && suspendFlags.inkWidth &&
suspendFlags.highlightColor && suspendFlags.highlightWidth)
{
deferral.complete();
}
}
function handleSuspending(eventArgs)
{
displayDebug("handleSuspending");
suspendFlags.ink = false;
suspendFlags.inkFile = false;
suspendFlags.inkMode = false;
suspendFlags.inkColor = false;
suspendFlags.inkWidth = false;
suspendFlags.highlightColor = false;
suspendFlags.highlightWidth = false;
// We will be doing asynchronous operations during suspension, so we signal
// the need to handle suspension of the application asynchronously.
// We do this by via the getDeferral() method, and later call its complete() method.
deferral = eventArgs.suspendingOperation.getDeferral();
displayDebug("handleSuspending obtained deferral");
// Obtain the inking mode.
// Note that these if statements are structured such that if anything in our current state is
// damaged, we will default to "inking".
var inkMode = "inking";
if (context === hlContext)
{
inkMode = "highlighting";
}
else if (context === selContext)
{
inkMode = "selecting";
if (inkManager.mode === Windows.UI.Input.Inking.InkManipulationMode.erasing)
{
inkMode = "erasing";
}
}
displayDebug("handleSuspending set inkMode to " + inkMode);
// If there is no ink we do not write this file, and we set the inkFile.text to be the empty string.
// Otherwise it contains the file name.
var strokes = inkManager.getStrokes();
var inkFile = "";
if (0 < strokes.length)
{
var inkFile = "InkPad.suspend.gif";
displayDebug("handleSuspending about to set saveCallback");
// Note how these Async calls have no error function (second arg to then()). If there are any errors we don't want to do anything; just continue.
WinJS.Application.local.folder.createFileAsync(inkFile, Windows.Storage.CreationCollisionOption.replaceExisting).then
(
function(file)
{
file.openAsync(Windows.Storage.FileAccessMode.readWrite).then
(
function(file)
{
var stream = file.getOutputStreamAt(0);
inkManager.saveAsync(stream).then
(
function()
{
stream.flushAsync().then(function() {suspendFlags.ink = true; checkDeferral();});
}
);
}
);
}
);
}
else
{
suspendFlags.ink = true;
}
// Note how these Async calls have no error function (second arg to then()). If there are any errors we don't want to do anything; just continue.
displayDebug("handleSuspending about to make calls to writeText()");
WinJS.Application.local.writeText("inkFile.text", inkFile).then(function() {suspendFlags.inkFile = true; checkDeferral();});
WinJS.Application.local.writeText("inkMode.text", inkMode).then(function() {suspendFlags.inkMode = true; checkDeferral();});
WinJS.Application.local.writeText("inkColor.text", inkContext.strokeStyle).then(function() {suspendFlags.inkColor = true; checkDeferral();});
WinJS.Application.local.writeText("inkWidth.text", inkContext.lineWidth).then(function() {suspendFlags.inkWidth = true; checkDeferral();});
WinJS.Application.local.writeText("highlightColor.text", hlContext.strokeStyle).then(function() {suspendFlags.highlightColor = true; checkDeferral();});
WinJS.Application.local.writeText("highlightWidth.text", hlContext.lineWidth).then(function() {suspendFlags.highlightWidth = true; checkDeferral();});
displayDebug("handleSuspending completed calls to writeText()");
}
function handleActivated(event)
{
displayDebug("handleActivated");
if (event.kind === Windows.ApplicationModel.Activation.ActivationKind.launch)
{
// Check the previousExecutionState; is this a re-activation after a suspension followed by a graceful termination?
// If it is then we must have saved the state in the suspending handler. Retrieve the persisted state.
// Note how these Async calls have no error function (second arg to then()). If there are any errors we don't want to do anything; just continue.
var reason = event.previousExecutionState;
if ((reason === Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) ||
(reason === Windows.ApplicationModel.Activation.ApplicationExecutionState.notRunning))
{
// Obtain ink mode.
displayDebug("handleActivated about to readText() on inkMode");
WinJS.Application.local.readText("inkMode.text", "inking").then
(
function(str)
{
switch(str)
{
case "highlighting":
highlightMode();
break;
case "selecting":
selectMode();
break;
case "erasing":
eraseMode();
break;
default:
inkMode();
}
}
);
// Obtain ink color.
displayDebug("handleActivated about to readText() on inkColor");
WinJS.Application.local.readText("inkColor.text", "Black").then
(
function(str)
{
inkContext.strokeStyle = str;
setDefaults();
}
);
// Obtain ink width.
displayDebug("handleActivated about to readText() on inkWidth");
WinJS.Application.local.readText("inkWidth.text", "2").then
(
function(str)
{
inkContext.lineWidth = str;
setDefaults();
}
);
// Obtain highlight color.
displayDebug("handleActivated about to readText() on highlightColor");
WinJS.Application.local.readText("highlightColor.text", "Yellow").then
(
function(str)
{
hlContext.strokeStyle = str;
setDefaults();
}
);
// Obtain highlight width.
displayDebug("handleActivated about to readText() on highlightWidth");
WinJS.Application.local.readText("highlightWidth.text", "10").then
(
function(str)
{
hlContext.lineWidth = str;
setDefaults();
}
);
// Obtain highlight width.
displayDebug("handleActivated about to readText() on inkFile");
WinJS.Application.local.readText("inkFile.text", "").then
(
function(str)
{
if (str !== "")
{
// Note how there is no error function (second arg to then()).
// If the file does not exist (or cannot be read), we do nothing.
displayDebug("handleActivated about to read file from " + str);
WinJS.Application.local.folder.getFileAsync(str).then(readInk);
}
}
);
}
}
}
// Utility to fetch elements by ID.
function id(elementId)
{
return document.getElementById(elementId);
}
function handleLayoutChange(event)
{
hlCanvas.setAttribute("width", hlCanvas.offsetWidth);
hlCanvas.setAttribute("height", hlCanvas.offsetHeight);
inkCanvas.setAttribute("width", inkCanvas.offsetWidth);
inkCanvas.setAttribute("height", inkCanvas.offsetHeight);
selCanvas.setAttribute("width", selCanvas.offsetWidth);
selCanvas.setAttribute("height", selCanvas.offsetHeight);
renderAllStrokes();
}
function inkInitialize()
{
try
{
WinJS.UI.processAll();
displayStatus("Verba volant ...");
appbar = WinJS.UI.getControl(id("bottomAppBar"));
findFlyout = id("FindFlyout");
inkColorsFlyout = id("InkColorFlyout");
inkWidthsFlyout = id("InkWidthFlyout");
hlColorsFlyout = id("HighlightColorFlyout");
hlWidthsFlyout = id("HighlightWidthFlyout");
moreFlyout = id("MoreFlyout");
id("Reco").addEventListener("click", recognize, false);
findText = id("FindString");
findFlyout.addEventListener("aftershow", function(evt) {findText.focus();}, false);
id("FindButton").addEventListener("click", find, false);
id("ModeSelect").addEventListener("click", selectMode, false);
id("ModeErase").addEventListener("click", eraseMode, false);
id("Black").addEventListener("click", inkColor, false);
id("Blue").addEventListener("click", inkColor, false);
id("Red").addEventListener("click", inkColor, false);
id("Green").addEventListener("click", inkColor, false);
id("Yellow").addEventListener("click", highlightColor, false);
id("Aqua").addEventListener("click", highlightColor, false);
id("Lime").addEventListener("click", highlightColor, false);
for (var i = 2; i < 11; i += 2)
{
id("IW" + i).addEventListener("click", setInkWidth, false);
}
for (var i = 10; i < 31; i += 10)
{
id("HW" + i).addEventListener("click", setHighlightWidth, false);
}
hlCanvas = id("HighlightCanvas");
hlCanvas.setAttribute("width", hlCanvas.offsetWidth);
hlCanvas.setAttribute("height", hlCanvas.offsetHeight);
hlContext = hlCanvas.getContext("2d");
hlContext.lineWidth = 10;
hlContext.strokeStyle = "Yellow";
hlContext.lineCap = "round";
hlContext.lineJoin = "round";
inkCanvas = id("InkCanvas");
inkCanvas.setAttribute("width", inkCanvas.offsetWidth);
inkCanvas.setAttribute("height", inkCanvas.offsetHeight);
inkContext = inkCanvas.getContext("2d");
inkContext.lineWidth = 2;
inkContext.strokeStyle = "Black";
inkContext.lineCap = "round";
inkContext.lineJoin = "round";
selCanvas = id("SelectCanvas");
selCanvas.setAttribute("width", selCanvas.offsetWidth);
selCanvas.setAttribute("height", selCanvas.offsetHeight);
selContext = selCanvas.getContext("2d");
selContext.lineWidth = 1;
selContext.strokeStyle = "Gold";
selContext.lineCap = "round";
selContext.lineJoin = "round";
// Note that we must set the event listeners on the top-most canvas.
selCanvas.addEventListener("MSPointerDown", handlePointerDown, false);
selCanvas.addEventListener("MSPointerUp", handlePointerUp, false);
selCanvas.addEventListener("MSPointerMove", handlePointerMove, false);
selCanvas.addEventListener("MSPointerOut", handlePointerOut, false);
var image = new Image();
image.onload = function() {selContext.strokeStyle = selPattern = selContext.createPattern(image, "repeat");};
image.src = "images/select.png";
recoFlyout = id("RecoFlyout");
clipButtons = new Array();
for (var i = 0; i < 5; i++)
{
var ID = "Reco" + i;
clipButtons[i] = id(ID);
clipButtons[i].addEventListener("click", recoClipboard, false);
}
id("CopySelected").addEventListener("click", copySelected, false);
id("Paste").addEventListener("click", paste, false);
id("Save").addEventListener("click", save, false);
id("Load").addEventListener("click", load, false);
id("Clear").addEventListener("click", clear, false);
document.body.addEventListener("keypress", keypress, false);
if (!setRecognizerByName("Microsoft English (US) Handwriting Recognizer"))
{
displayStatus("Failed to find English (US) recognizer");
}
else
{
displayStatus("Verba volant, scripta manent");
}
inkMode();
renderPaper();
var mqlFull = msMatchMedia("all and (-ms-view-state: full-screen)");
mqlFull.addListener(handleLayoutChange);
var mqlSnapped = msMatchMedia("all and (-ms-view-state: snapped)");
mqlSnapped.addListener(handleLayoutChange);
var mqlFill = msMatchMedia("all and (-ms-view-state: fill)");
mqlFill.addListener(handleLayoutChange);
var mqlPortrait = msMatchMedia("all and (-ms-view-state: device-portrait)");
mqlPortrait.addListener(handleLayoutChange);
Windows.UI.WebUI.WebUIApplication.addEventListener("activated", handleActivated, false);
Windows.UI.WebUI.WebUIApplication.addEventListener("suspending", handleSuspending, false);
}
catch (e)
{
displayError("inkInitialize " + e.toString() + "\n" + e.stack);
}
}
document.addEventListener("DOMContentLoaded", inkInitialize, false);