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')
11 * Returns UTF8 byte length
13 * @param {String} str Input string
14 * @return {Number} Number of byte
16 function getStringByteLength (str) {
17 return unescape(encodeURIComponent(str)).length
21 * Get a list of segments of the specified mode
24 * @param {Mode} mode Segment mode
25 * @param {String} str String to process
26 * @return {Array} Array of object with segments data
28 function getSegments (regex, mode, str) {
32 while ((result = regex.exec(str)) !== null) {
37 length: result[0].length
45 * Extracts a series of segments with the appropriate
48 * @param {String} dataStr Input string
49 * @return {Array} Array of object with segments data
51 function getSegmentsFromString (dataStr) {
52 var numSegs = getSegments(Regex.NUMERIC, Mode.NUMERIC, dataStr)
53 var alphaNumSegs = getSegments(Regex.ALPHANUMERIC, Mode.ALPHANUMERIC, dataStr)
57 if (Utils.isKanjiModeEnabled()) {
58 byteSegs = getSegments(Regex.BYTE, Mode.BYTE, dataStr)
59 kanjiSegs = getSegments(Regex.KANJI, Mode.KANJI, dataStr)
61 byteSegs = getSegments(Regex.BYTE_KANJI, Mode.BYTE, dataStr)
65 var segs = numSegs.concat(alphaNumSegs, byteSegs, kanjiSegs)
68 .sort(function (s1, s2) {
69 return s1.index - s2.index
81 * Returns how many bits are needed to encode a string of
82 * specified length with the specified mode
84 * @param {Number} length String length
85 * @param {Mode} mode Segment mode
86 * @return {Number} Bit length
88 function getSegmentBitsLength (length, mode) {
91 return NumericData.getBitsLength(length)
92 case Mode.ALPHANUMERIC:
93 return AlphanumericData.getBitsLength(length)
95 return KanjiData.getBitsLength(length)
97 return ByteData.getBitsLength(length)
102 * Merges adjacent segments which have the same mode
104 * @param {Array} segs Array of object with segments data
105 * @return {Array} Array of object with segments data
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
121 * Generates a list of all possible nodes combination which
122 * will be used to build a segments graph.
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.
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.
131 * Each node represents a possible segment.
133 * @param {Array} segs Array of object with segments data
134 * @return {Array} Array of object with segments data
136 function buildNodes (segs) {
138 for (var i = 0; i < segs.length; i++) {
144 { data: seg.data, mode: Mode.ALPHANUMERIC, length: seg.length },
145 { data: seg.data, mode: Mode.BYTE, length: seg.length }
148 case Mode.ALPHANUMERIC:
150 { data: seg.data, mode: Mode.BYTE, length: seg.length }
155 { data: seg.data, mode: Mode.BYTE, length: getStringByteLength(seg.data) }
160 { data: seg.data, mode: Mode.BYTE, length: getStringByteLength(seg.data) }
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.
173 * At each connection will be assigned a weight depending on the
174 * segment's byte length.
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
180 function buildGraph (nodes, version) {
182 var graph = {'start': {}}
183 var prevNodeIds = ['start']
185 for (var i = 0; i < nodes.length; i++) {
186 var nodeGroup = nodes[i]
187 var currentNodeIds = []
189 for (var j = 0; j < nodeGroup.length; j++) {
190 var node = nodeGroup[j]
193 currentNodeIds.push(key)
194 table[key] = { node: node, lastCount: 0 }
197 for (var n = 0; n < prevNodeIds.length; n++) {
198 var prevNodeId = prevNodeIds[n]
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)
205 table[prevNodeId].lastCount += node.length
207 if (table[prevNodeId]) table[prevNodeId].lastCount = node.length
209 graph[prevNodeId][key] = getSegmentBitsLength(node.length, node.mode) +
210 4 + Mode.getCharCountIndicator(node.mode, version) // switch cost
215 prevNodeIds = currentNodeIds
218 for (n = 0; n < prevNodeIds.length; n++) {
219 graph[prevNodeIds[n]]['end'] = 0
222 return { map: graph, table: table }
226 * Builds a segment from a specified data and mode.
227 * If a mode is not specified, the more suitable will be used.
229 * @param {String} data Input data
230 * @param {Mode | String} modesHint Data mode
231 * @return {Segment} Segment
233 function buildSingleSegment (data, modesHint) {
235 var bestMode = Mode.getBestModeForData(data)
237 mode = Mode.from(modesHint, bestMode)
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))
246 // Use Mode.BYTE if Kanji support is disabled
247 if (mode === Mode.KANJI && !Utils.isKanjiModeEnabled()) {
253 return new NumericData(data)
255 case Mode.ALPHANUMERIC:
256 return new AlphanumericData(data)
259 return new KanjiData(data)
262 return new ByteData(data)
267 * Builds a list of segments from an array.
268 * Array can contain Strings or Objects with segment's info.
270 * For each item which is a string, will be generated a segment with the given
271 * string and the more appropriate encoding mode.
273 * For each item which is an object, will be generated a segment with the given
275 * Objects must contain at least the property "data".
276 * If property "mode" is not present, the more suitable mode will be used.
278 * @param {Array} array Array of objects with segments data
279 * @return {Array} Array of Segments
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))
294 * Builds an optimized sequence of segments from a string,
295 * which will produce the shortest possible bitstream.
297 * @param {String} data Input string
298 * @param {Number} version QR Code version
299 * @return {Array} Array of segments
301 exports.fromString = function fromString (data, version) {
302 var segs = getSegmentsFromString(data, Utils.isKanjiModeEnabled())
304 var nodes = buildNodes(segs)
305 var graph = buildGraph(nodes, version)
306 var path = dijkstra.find_path(graph.map, 'start', 'end')
308 var optimizedSegs = []
309 for (var i = 1; i < path.length - 1; i++) {
310 optimizedSegs.push(graph.table[path[i]].node)
313 return exports.fromArray(mergeSegments(optimizedSegs))
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.
323 * @param {string} data Input string
324 * @return {Array} Array of segments
326 exports.rawSplit = function rawSplit (data) {
327 return exports.fromArray(
328 getSegmentsFromString(data, Utils.isKanjiModeEnabled())