[Service] Integrate DeviceHome and SignalingServer
[platform/framework/web/wrtjs.git] / device_home / node_modules / qrcode / lib / core / segments.js
1 var Mode = require('./mode')
2 var NumericData = require('./numeric-data')
3 var AlphanumericData = require('./alphanumeric-data')
4 var ByteData = require('./byte-data')
5 var KanjiData = require('./kanji-data')
6 var Regex = require('./regex')
7 var Utils = require('./utils')
8 var dijkstra = require('dijkstrajs')
9
10 /**
11  * Returns UTF8 byte length
12  *
13  * @param  {String} str Input string
14  * @return {Number}     Number of byte
15  */
16 function getStringByteLength (str) {
17   return unescape(encodeURIComponent(str)).length
18 }
19
20 /**
21  * Get a list of segments of the specified mode
22  * from a string
23  *
24  * @param  {Mode}   mode Segment mode
25  * @param  {String} str  String to process
26  * @return {Array}       Array of object with segments data
27  */
28 function getSegments (regex, mode, str) {
29   var segments = []
30   var result
31
32   while ((result = regex.exec(str)) !== null) {
33     segments.push({
34       data: result[0],
35       index: result.index,
36       mode: mode,
37       length: result[0].length
38     })
39   }
40
41   return segments
42 }
43
44 /**
45  * Extracts a series of segments with the appropriate
46  * modes from a string
47  *
48  * @param  {String} dataStr Input string
49  * @return {Array}          Array of object with segments data
50  */
51 function getSegmentsFromString (dataStr) {
52   var numSegs = getSegments(Regex.NUMERIC, Mode.NUMERIC, dataStr)
53   var alphaNumSegs = getSegments(Regex.ALPHANUMERIC, Mode.ALPHANUMERIC, dataStr)
54   var byteSegs
55   var kanjiSegs
56
57   if (Utils.isKanjiModeEnabled()) {
58     byteSegs = getSegments(Regex.BYTE, Mode.BYTE, dataStr)
59     kanjiSegs = getSegments(Regex.KANJI, Mode.KANJI, dataStr)
60   } else {
61     byteSegs = getSegments(Regex.BYTE_KANJI, Mode.BYTE, dataStr)
62     kanjiSegs = []
63   }
64
65   var segs = numSegs.concat(alphaNumSegs, byteSegs, kanjiSegs)
66
67   return segs
68     .sort(function (s1, s2) {
69       return s1.index - s2.index
70     })
71     .map(function (obj) {
72       return {
73         data: obj.data,
74         mode: obj.mode,
75         length: obj.length
76       }
77     })
78 }
79
80 /**
81  * Returns how many bits are needed to encode a string of
82  * specified length with the specified mode
83  *
84  * @param  {Number} length String length
85  * @param  {Mode} mode     Segment mode
86  * @return {Number}        Bit length
87  */
88 function getSegmentBitsLength (length, mode) {
89   switch (mode) {
90     case Mode.NUMERIC:
91       return NumericData.getBitsLength(length)
92     case Mode.ALPHANUMERIC:
93       return AlphanumericData.getBitsLength(length)
94     case Mode.KANJI:
95       return KanjiData.getBitsLength(length)
96     case Mode.BYTE:
97       return ByteData.getBitsLength(length)
98   }
99 }
100
101 /**
102  * Merges adjacent segments which have the same mode
103  *
104  * @param  {Array} segs Array of object with segments data
105  * @return {Array}      Array of object with segments data
106  */
107 function mergeSegments (segs) {
108   return segs.reduce(function (acc, curr) {
109     var prevSeg = acc.length - 1 >= 0 ? acc[acc.length - 1] : null
110     if (prevSeg && prevSeg.mode === curr.mode) {
111       acc[acc.length - 1].data += curr.data
112       return acc
113     }
114
115     acc.push(curr)
116     return acc
117   }, [])
118 }
119
120 /**
121  * Generates a list of all possible nodes combination which
122  * will be used to build a segments graph.
123  *
124  * Nodes are divided by groups. Each group will contain a list of all the modes
125  * in which is possible to encode the given text.
126  *
127  * For example the text '12345' can be encoded as Numeric, Alphanumeric or Byte.
128  * The group for '12345' will contain then 3 objects, one for each
129  * possible encoding mode.
130  *
131  * Each node represents a possible segment.
132  *
133  * @param  {Array} segs Array of object with segments data
134  * @return {Array}      Array of object with segments data
135  */
136 function buildNodes (segs) {
137   var nodes = []
138   for (var i = 0; i < segs.length; i++) {
139     var seg = segs[i]
140
141     switch (seg.mode) {
142       case Mode.NUMERIC:
143         nodes.push([seg,
144           { data: seg.data, mode: Mode.ALPHANUMERIC, length: seg.length },
145           { data: seg.data, mode: Mode.BYTE, length: seg.length }
146         ])
147         break
148       case Mode.ALPHANUMERIC:
149         nodes.push([seg,
150           { data: seg.data, mode: Mode.BYTE, length: seg.length }
151         ])
152         break
153       case Mode.KANJI:
154         nodes.push([seg,
155           { data: seg.data, mode: Mode.BYTE, length: getStringByteLength(seg.data) }
156         ])
157         break
158       case Mode.BYTE:
159         nodes.push([
160           { data: seg.data, mode: Mode.BYTE, length: getStringByteLength(seg.data) }
161         ])
162     }
163   }
164
165   return nodes
166 }
167
168 /**
169  * Builds a graph from a list of nodes.
170  * All segments in each node group will be connected with all the segments of
171  * the next group and so on.
172  *
173  * At each connection will be assigned a weight depending on the
174  * segment's byte length.
175  *
176  * @param  {Array} nodes    Array of object with segments data
177  * @param  {Number} version QR Code version
178  * @return {Object}         Graph of all possible segments
179  */
180 function buildGraph (nodes, version) {
181   var table = {}
182   var graph = {'start': {}}
183   var prevNodeIds = ['start']
184
185   for (var i = 0; i < nodes.length; i++) {
186     var nodeGroup = nodes[i]
187     var currentNodeIds = []
188
189     for (var j = 0; j < nodeGroup.length; j++) {
190       var node = nodeGroup[j]
191       var key = '' + i + j
192
193       currentNodeIds.push(key)
194       table[key] = { node: node, lastCount: 0 }
195       graph[key] = {}
196
197       for (var n = 0; n < prevNodeIds.length; n++) {
198         var prevNodeId = prevNodeIds[n]
199
200         if (table[prevNodeId] && table[prevNodeId].node.mode === node.mode) {
201           graph[prevNodeId][key] =
202             getSegmentBitsLength(table[prevNodeId].lastCount + node.length, node.mode) -
203             getSegmentBitsLength(table[prevNodeId].lastCount, node.mode)
204
205           table[prevNodeId].lastCount += node.length
206         } else {
207           if (table[prevNodeId]) table[prevNodeId].lastCount = node.length
208
209           graph[prevNodeId][key] = getSegmentBitsLength(node.length, node.mode) +
210             4 + Mode.getCharCountIndicator(node.mode, version) // switch cost
211         }
212       }
213     }
214
215     prevNodeIds = currentNodeIds
216   }
217
218   for (n = 0; n < prevNodeIds.length; n++) {
219     graph[prevNodeIds[n]]['end'] = 0
220   }
221
222   return { map: graph, table: table }
223 }
224
225 /**
226  * Builds a segment from a specified data and mode.
227  * If a mode is not specified, the more suitable will be used.
228  *
229  * @param  {String} data             Input data
230  * @param  {Mode | String} modesHint Data mode
231  * @return {Segment}                 Segment
232  */
233 function buildSingleSegment (data, modesHint) {
234   var mode
235   var bestMode = Mode.getBestModeForData(data)
236
237   mode = Mode.from(modesHint, bestMode)
238
239   // Make sure data can be encoded
240   if (mode !== Mode.BYTE && mode.bit < bestMode.bit) {
241     throw new Error('"' + data + '"' +
242       ' cannot be encoded with mode ' + Mode.toString(mode) +
243       '.\n Suggested mode is: ' + Mode.toString(bestMode))
244   }
245
246   // Use Mode.BYTE if Kanji support is disabled
247   if (mode === Mode.KANJI && !Utils.isKanjiModeEnabled()) {
248     mode = Mode.BYTE
249   }
250
251   switch (mode) {
252     case Mode.NUMERIC:
253       return new NumericData(data)
254
255     case Mode.ALPHANUMERIC:
256       return new AlphanumericData(data)
257
258     case Mode.KANJI:
259       return new KanjiData(data)
260
261     case Mode.BYTE:
262       return new ByteData(data)
263   }
264 }
265
266 /**
267  * Builds a list of segments from an array.
268  * Array can contain Strings or Objects with segment's info.
269  *
270  * For each item which is a string, will be generated a segment with the given
271  * string and the more appropriate encoding mode.
272  *
273  * For each item which is an object, will be generated a segment with the given
274  * data and mode.
275  * Objects must contain at least the property "data".
276  * If property "mode" is not present, the more suitable mode will be used.
277  *
278  * @param  {Array} array Array of objects with segments data
279  * @return {Array}       Array of Segments
280  */
281 exports.fromArray = function fromArray (array) {
282   return array.reduce(function (acc, seg) {
283     if (typeof seg === 'string') {
284       acc.push(buildSingleSegment(seg, null))
285     } else if (seg.data) {
286       acc.push(buildSingleSegment(seg.data, seg.mode))
287     }
288
289     return acc
290   }, [])
291 }
292
293 /**
294  * Builds an optimized sequence of segments from a string,
295  * which will produce the shortest possible bitstream.
296  *
297  * @param  {String} data    Input string
298  * @param  {Number} version QR Code version
299  * @return {Array}          Array of segments
300  */
301 exports.fromString = function fromString (data, version) {
302   var segs = getSegmentsFromString(data, Utils.isKanjiModeEnabled())
303
304   var nodes = buildNodes(segs)
305   var graph = buildGraph(nodes, version)
306   var path = dijkstra.find_path(graph.map, 'start', 'end')
307
308   var optimizedSegs = []
309   for (var i = 1; i < path.length - 1; i++) {
310     optimizedSegs.push(graph.table[path[i]].node)
311   }
312
313   return exports.fromArray(mergeSegments(optimizedSegs))
314 }
315
316 /**
317  * Splits a string in various segments with the modes which
318  * best represent their content.
319  * The produced segments are far from being optimized.
320  * The output of this function is only used to estimate a QR Code version
321  * which may contain the data.
322  *
323  * @param  {string} data Input string
324  * @return {Array}       Array of segments
325  */
326 exports.rawSplit = function rawSplit (data) {
327   return exports.fromArray(
328     getSegmentsFromString(data, Utils.isKanjiModeEnabled())
329   )
330 }