1cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved.
2cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// found in the LICENSE file.
4cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
5cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
6cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @fileoverview JavaScript shim for the liblouis Native Client wrapper.
7cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
8cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
9cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.provide('cvox.LibLouis');
10cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
11cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
12cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
13cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Encapsulates a liblouis Native Client instance in the page.
14cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @constructor
15cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string} nmfPath Path to .nmf file for the module.
16cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string=} opt_tablesDir Path to tables directory.
17cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
18cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.LibLouis = function(nmfPath, opt_tablesDir) {
19cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
20cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * Path to .nmf file for the module.
21cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private {string}
22cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
23cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.nmfPath_ = nmfPath;
24cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
25cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
26cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * Path to translation tables.
27cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private {?string}
28cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
29cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.tablesDir_ = goog.isDef(opt_tablesDir) ? opt_tablesDir : null;
30cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
31cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
32cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * Native Client <embed> element.
33cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * {@code null} when no <embed> is attached to the DOM.
34cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private {NaClEmbedElement}
35cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
36cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.embedElement_ = null;
37cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
38cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
39cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * The state of the instance.
40cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private {cvox.LibLouis.InstanceState}
41cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
42cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.instanceState_ =
43cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      cvox.LibLouis.InstanceState.NOT_LOADED;
44cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
45cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
46cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * Pending requests to construct translators.
47cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private {!Array.<{tableName: string,
48cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   *   callback: function(cvox.LibLouis.Translator)}>}
49cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
50cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.pendingTranslators_ = [];
51cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
52cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
53cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * Pending RPC callbacks. Maps from message IDs to callbacks.
54cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private {!Object.<string, function(!Object)>}
55cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
56cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.pendingRpcCallbacks_ = {};
57cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
58cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
59cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * Next message ID to be used. Incremented with each sent message.
60cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private {number}
61cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
62cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.nextMessageId_ = 1;
63cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
64cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
65cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
66cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
67cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Describes the loading state of the instance.
68cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @enum {number}
69cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
70cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.LibLouis.InstanceState = {
71cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  NOT_LOADED: 0,
72cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  LOADING: 1,
73cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  LOADED: 2,
74cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  ERROR: -1
75cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
76cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
77cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
78cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
79cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Attaches the Native Client wrapper to the DOM as a child of the provided
80cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * element, assumed to already be in the document.
81cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Element} elem Desired parent element of the instance.
82cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
83cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.LibLouis.prototype.attachToElement = function(elem) {
84cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (this.isAttached()) {
85cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    throw Error('instance already attached');
86cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
87cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
88cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var embed = document.createElement('embed');
89cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  embed.src = this.nmfPath_;
90cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  embed.type = 'application/x-nacl';
91cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  embed.width = 0;
92cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  embed.height = 0;
93cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!goog.isNull(this.tablesDir_)) {
94cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    embed.setAttribute('tablesdir', this.tablesDir_);
95cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
96cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  embed.addEventListener('load', goog.bind(this.onInstanceLoad_, this),
97cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      false /* useCapture */);
98cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  embed.addEventListener('error', goog.bind(this.onInstanceError_, this),
99cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      false /* useCapture */);
100cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  embed.addEventListener('message', goog.bind(this.onInstanceMessage_, this),
101cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      false /* useCapture */);
102cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  elem.appendChild(embed);
103cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
104cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.embedElement_ = /** @type {!NaClEmbedElement} */ (embed);
105cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.instanceState_ = cvox.LibLouis.InstanceState.LOADING;
106cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
107cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
108cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
109cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
110cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Detaches the Native Client instance from the DOM.
111cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
112cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.LibLouis.prototype.detach = function() {
113cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!this.isAttached()) {
114cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    throw Error('cannot detach unattached instance');
115cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
116cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
117cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.embedElement_.parentNode.removeChild(this.embedElement_);
118cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.embedElement_ = null;
119cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.instanceState_ =
120cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      cvox.LibLouis.InstanceState.NOT_LOADED;
121cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
122cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
123cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
124cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
125cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Determines whether the Native Client instance is attached.
126cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} {@code true} if the <embed> element is attached to the DOM.
127cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
128cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.LibLouis.prototype.isAttached = function() {
129cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return this.embedElement_ !== null;
130cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
131cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
132cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
133cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
134cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Returns a translator for the desired table, asynchronously.
1351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {string} tableNames Comma separated list of braille table names for
1361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci *     liblouis.
137cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {function(cvox.LibLouis.Translator)} callback
138cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     Callback which will receive the translator, or {@code null} on failure.
139cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
140cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.LibLouis.prototype.getTranslator =
1411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    function(tableNames, callback) {
142cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  switch (this.instanceState_) {
143cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    case cvox.LibLouis.InstanceState.NOT_LOADED:
144cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    case cvox.LibLouis.InstanceState.LOADING:
145cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.pendingTranslators_.push(
1461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          { tableNames: tableNames, callback: callback });
147cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      return;
148cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    case cvox.LibLouis.InstanceState.ERROR:
149cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      callback(null /* translator */);
150cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      return;
151cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    case cvox.LibLouis.InstanceState.LOADED:
1521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      this.rpc_('CheckTable', { 'table_names': tableNames },
153cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          goog.bind(function(reply) {
154cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (reply['success']) {
155cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          var translator = new cvox.LibLouis.Translator(
1561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci              this, tableNames);
157cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          callback(translator);
158cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        } else {
159cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          callback(null /* translator */);
160cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
161cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }, this));
162cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      return;
163cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
164cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
165cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
166cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
167cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
168cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Dispatches a message to the remote end and returns the reply asynchronously.
169cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * A message ID will be automatically assigned (as a side-effect).
170cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string} command Command name to be sent.
171cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Object} message JSONable message to be sent.
172cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {function(!Object)} callback Callback to receive the reply.
173cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
174cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
175cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.LibLouis.prototype.rpc_ =
176cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    function(command, message, callback) {
177cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (this.instanceState_ !==
178cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      cvox.LibLouis.InstanceState.LOADED) {
179cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    throw Error('cannot send RPC: liblouis instance not loaded');
180cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
181cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var messageId = '' + this.nextMessageId_++;
182cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  message['message_id'] = messageId;
183cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  message['command'] = command;
184cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var json = JSON.stringify(message);
185cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (goog.DEBUG) {
186cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    window.console.debug('RPC -> ' + json);
187cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
188cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.embedElement_.postMessage(json);
189cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.pendingRpcCallbacks_[messageId] = callback;
190cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
191cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
192cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
193cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
194cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Invoked when the Native Client instance successfully loads.
195cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Event} e Event dispatched after loading.
196cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
197cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
198cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.LibLouis.prototype.onInstanceLoad_ = function(e) {
199cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  window.console.info('loaded liblouis Native Client instance');
200cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.instanceState_ = cvox.LibLouis.InstanceState.LOADED;
201cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.pendingTranslators_.forEach(goog.bind(function(record) {
2021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    this.getTranslator(record.tableNames, record.callback);
203cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }, this));
204cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.pendingTranslators_.length = 0;
205cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
206cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
207cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
208cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
209cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Invoked when the Native Client instance fails to load.
210cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Event} e Event dispatched after loading failure.
211cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
212cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
213cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.LibLouis.prototype.onInstanceError_ = function(e) {
214cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  window.console.error('failed to load liblouis Native Client instance');
215cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.instanceState_ = cvox.LibLouis.InstanceState.ERROR;
216cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.pendingTranslators_.forEach(goog.bind(function(record) {
2171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    this.getTranslator(record.tableNames, record.callback);
218cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }, this));
219cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.pendingTranslators_.length = 0;
220cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
221cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
222cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
223cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
224cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Invoked when the Native Client instance posts a message.
225cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Event} e Event dispatched after the message was posted.
226cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
227cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
228cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.LibLouis.prototype.onInstanceMessage_ = function(e) {
229cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (goog.DEBUG) {
230cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    window.console.debug('RPC <- ' + e.data);
231cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
232cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var message = /** @type {!Object} */ (JSON.parse(e.data));
233cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var messageId = message['in_reply_to'];
234cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!goog.isDef(messageId)) {
235cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    window.console.warn('liblouis Native Client module sent message with no ID',
236cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        message);
237cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
238cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
239cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (goog.isDef(message['error'])) {
240cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    window.console.error('liblouis Native Client error', message['error']);
241cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
242cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var callback = this.pendingRpcCallbacks_[messageId];
243cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (goog.isDef(callback)) {
244cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    delete this.pendingRpcCallbacks_[messageId];
245cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    callback(message);
246cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
247cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
248cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
249cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
250cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
251cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Braille translator which uses a Native Client instance of liblouis.
252cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @constructor
253cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!cvox.LibLouis} instance The instance wrapper.
2541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {string} tableNames Comma separated list of Table names to be passed
2551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci *     to liblouis.
256cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
2571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccicvox.LibLouis.Translator = function(instance, tableNames) {
258cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
259cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * The instance wrapper.
260cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private {!cvox.LibLouis}
261cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
262cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.instance_ = instance;
263cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
264cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
265cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * The table name.
266cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private {string}
267cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
2681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  this.tableNames_ = tableNames;
269cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
270cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
271cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
272cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
273cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Translates text into braille cells.
274cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string} text Text to be translated.
275cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {function(ArrayBuffer, Array.<number>, Array.<number>)} callback
276cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     Callback for result.  Takes 3 parameters: the resulting cells,
277cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     mapping from text to braille positions and mapping from braille to
278cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     text positions.  If translation fails for any reason, all parameters are
279cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     {@code null}.
280cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
281cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.LibLouis.Translator.prototype.translate = function(text, callback) {
2821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  var message = { 'table_names': this.tableNames_, 'text': text };
283cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.instance_.rpc_('Translate', message, function(reply) {
284cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var cells = null;
285cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var textToBraille = null;
286cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var brailleToText = null;
287cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (reply['success'] && goog.isString(reply['cells'])) {
288cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      cells = cvox.LibLouis.Translator.decodeHexString_(reply['cells']);
289cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (goog.isDef(reply['text_to_braille'])) {
290cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        textToBraille = reply['text_to_braille'];
291cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
292cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (goog.isDef(reply['braille_to_text'])) {
293cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        brailleToText = reply['braille_to_text'];
294cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
295cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else if (text.length > 0) {
296cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // TODO(plundblad): The nacl wrapper currently returns an error
297cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // when translating an empty string.  Address that and always log here.
298cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      console.error('Braille translation error for ' + JSON.stringify(message));
299cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
300cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    callback(cells, textToBraille, brailleToText);
301cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  });
302cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
303cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
304cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
305cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
306cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Translates braille cells into text.
307cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!ArrayBuffer} cells Cells to be translated.
308cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {function(?string)} callback Callback for result.
309cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
310cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.LibLouis.Translator.prototype.backTranslate =
311cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    function(cells, callback) {
312116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  if (cells.byteLength == 0) {
313116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    // liblouis doesn't handle empty input, so handle that trivially
314116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    // here.
315116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    callback('');
316116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    return;
317116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  }
318cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var message = {
3191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    'table_names': this.tableNames_,
320cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    'cells': cvox.LibLouis.Translator.encodeHexString_(cells)
321cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  };
322cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.instance_.rpc_('BackTranslate', message, function(reply) {
323cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (reply['success'] && goog.isString(reply['text'])) {
324cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      callback(reply['text']);
325cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else {
326cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      callback(null /* text */);
327cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
328cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  });
329cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
330cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
331cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
332cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
333cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Decodes a hexadecimal string to an {@code ArrayBuffer}.
334cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string} hex Hexadecimal string.
335cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {!ArrayBuffer} Decoded binary data.
336cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
337cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
338cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.LibLouis.Translator.decodeHexString_ = function(hex) {
339cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!/^([0-9a-f]{2})*$/i.test(hex)) {
340cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    throw Error('invalid hexadecimal string');
341cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
342cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var array = new Uint8Array(hex.length / 2);
343cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var idx = 0;
344cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (var i = 0; i < hex.length; i += 2) {
345cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    array[idx++] = parseInt(hex.substring(i, i + 2), 16);
346cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
347cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return array.buffer;
348cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
349cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
350cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
351cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
352cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Encodes an {@code ArrayBuffer} in hexadecimal.
353cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!ArrayBuffer} arrayBuffer Binary data.
354cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {string} Hexadecimal string.
355cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
356cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
357cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.LibLouis.Translator.encodeHexString_ = function(arrayBuffer) {
358cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var array = new Uint8Array(arrayBuffer);
359cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var hex = '';
360cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (var i = 0; i < array.length; i++) {
361cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var b = array[i];
362cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    hex += (b < 0x10 ? '0' : '') + b.toString(16);
363cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
364cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return hex;
365cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
366