segments.js 9.1 KB

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