1 //------------------------------------------------------------------------------
  2 // File: jamUtils.jsxinc
  3 // Version: 4.5
  4 // Release Date: 2016-09-29
  5 // Copyright: © 2011-2016 Michel MARIANI <http://www.tonton-pixel.com/blog/>
  6 // Licence: GPL <http://www.gnu.org/licenses/gpl.html>
  7 //------------------------------------------------------------------------------
  8 // This program is free software: you can redistribute it and/or modify
  9 // it under the terms of the GNU General Public License as published by
 10 // the Free Software Foundation, either version 3 of the License, or
 11 // (at your option) any later version.
 12 // 
 13 // This program is distributed in the hope that it will be useful,
 14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16 // GNU General Public License for more details.
 17 // 
 18 // You should have received a copy of the GNU General Public License
 19 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 20 //------------------------------------------------------------------------------
 21 // Version History:
 22 //  4.5:
 23 //  - Incremented version number to keep in sync with other modules.
 24 //  4.4:
 25 //  - Normalized error messages.
 26 //  4.0:
 27 //  - Removed reference to 'this' for main global object.
 28 //  3.6:
 29 //  - Incremented version number to keep in sync with other modules.
 30 //  3.5:
 31 //  - Added jamUtils.mergeData ().
 32 //  3.4:
 33 //  - Added jamUtils.readTextFile () and jamUtils.writeTextFile (), now used
 34 //    by jamUtils.readJsonFile () and jamUtils.writeJsonFile () respectively.
 35 //  - Added optional lowercase parameter to jamUtils.dataToHexaString ().
 36 //  3.3.1:
 37 //  - Added 'TEXT' Mac OS type to newly created JSON file in
 38 //    jamUtils.writeJsonFile ().
 39 //  3.3:
 40 //  - Added jamUtils.loadActionSet ().
 41 //  - Added extra file.writeln () when needed in jamUtils.writeJsonFile ().
 42 //  - Added proper handling of null values in jamUtils.cloneData () and
 43 //    jamUtils.getCustomOptions ().
 44 //  3.2:
 45 //  - Incremented version number to keep in sync with other modules.
 46 //  3.1:
 47 //  - Moved jamEngine.dataToHexaString () and jamEngine.hexaToDataString ()
 48 //    to jamUtils.dataToHexaString () and jamUtils.hexaToDataString ().
 49 //  3.0:
 50 //  - Applied the redefined JSON AM Reference format.
 51 //  2.0:
 52 //  - Initial release.
 53 //------------------------------------------------------------------------------
 54 
 55 /**
 56  * @fileOverview
 57  * @name jamUtils.jsxinc
 58  * @author Michel MARIANI
 59  */
 60 
 61 //------------------------------------------------------------------------------
 62 
 63 if (typeof jamUtils !== 'object')
 64 {
 65     /**
 66      * Global object (used to simulate a namespace in JavaScript) containing
 67      * a set of utility functions for scripts written with the
 68      * <a href="http://www.tonton-pixel.com/blog/json-photoshop-scripting/json-action-manager/">JSON Action Manager</a> engine.
 69      * @author Michel MARIANI
 70      * @version 4.5
 71      * @namespace
 72      */
 73     var jamUtils = { };
 74     //
 75     (function ()
 76     {
 77         /**
 78          * @description Convert a distance in pixels to "distanceUnit" units.
 79          * @param {Number} amount Distance in pixels
 80          * @param {Number} amountBasePerInch Resolution of document in pixels per inch
 81          * @returns {Number} Distance in "distanceUnit" units (special pixels units at an absolute 72 dpi)
 82          * @see jamUtils.fromDistanceUnit
 83          * @example
 84          * jamEngine.jsonPlay
 85          * (
 86          *     "make",
 87          *     {
 88          *         "new":
 89          *         {
 90          *             "<object>":
 91          *             {
 92          *                 "document":
 93          *                 {
 94          *                     "name": { "<string>": "Polaroid 4x5 Inch (Print)" },
 95          *                     "mode": { "<class>": "RGBColorMode" },
 96          *                     "width":
 97          *                     {
 98          *                         "<unitDouble>":
 99          *                         {
100          *                             "distanceUnit": jamUtils.<strong>toDistanceUnit</strong> (4 * 300, 300)
101          *                         }
102          *                     },
103          *                     "height":
104          *                     {
105          *                         "<unitDouble>":
106          *                         {
107          *                             "distanceUnit": jamUtils.<strong>toDistanceUnit</strong> (5 * 300, 300)
108          *                         }
109          *                     },
110          *                     "resolution": { "<unitDouble>": { "densityUnit": 300 } },
111          *                     "depth": { "<integer>": 16 },
112          *                     "fill": { "<enumerated>": { "fill": "white" } },
113          *                     "profile": { "<string>": "Adobe RGB (1998)" }
114          *                 }
115          *             }
116          *         }
117          *     }
118          * );
119          */
120         jamUtils.toDistanceUnit = function (amount, amountBasePerInch)
121         {
122             return (amount / amountBasePerInch) * 72.0;
123             // return (amount * 72.0) / amountBasePerInch;
124         };
125         //
126         /**
127          * @description Convert a distance from "distanceUnit" units to pixels.
128          * @param {Number} amount Distance in "distanceUnit" units (special pixels units at an absolute 72 dpi)
129          * @param {Number} amountBasePerInch Resolution of document in pixels per inch
130          * @returns {Number} Distance in pixels
131          * @see jamUtils.toDistanceUnit
132          * @example
133          * jamEngine.meaningfulIds = true;
134          * var resultDescObj = jamEngine.jsonGet ([ { "document": { "<enumerated>": { "ordinal": "first" } } } ]);
135          * var width = resultDescObj["width"]["<unitDouble>"]["distanceUnit"];
136          * var height = resultDescObj["height"]["<unitDouble>"]["distanceUnit"];
137          * var resolution = resultDescObj["resolution"]["<unitDouble>"]["densityUnit"];
138          * var pixelsWidth = jamUtils.<strong>fromDistanceUnit</strong> (width, resolution);
139          * var pixelsHeight = jamUtils.<strong>fromDistanceUnit</strong> (height, resolution);
140          */
141         jamUtils.fromDistanceUnit = function (amount, amountBasePerInch)
142         {
143             return (amount / 72.0) * amountBasePerInch;
144             // return (amount * amountBasePerInch) / 72.0;
145         };
146         //
147         /**
148          * @description Test if a required font is available.
149          * @param {String} fontPostScriptName Font PostScript name string
150          * @returns {Boolean} Font found boolean
151          * @example
152          * if (jamUtils.<strong>fontExists</strong> ("Apple-Chancery"))
153          * {
154          *     // Use the fancy Apple Chancery font...
155          * }
156          */
157         jamUtils.fontExists = function (fontPostScriptName)
158         {
159             var useDOM = true;
160             //
161             var found = false;
162             if (useDOM) // Much faster !!
163             {
164                 for (var i = 0; i < app.fonts.length; i++)
165                 {
166                     if (app.fonts[i].postScriptName === fontPostScriptName)
167                     {
168                         found = true;
169                         break;
170                     }
171                 }
172             }
173             else
174             {
175                 var saveMeaningfulIds = jamEngine.meaningfulIds;
176                 var saveParseFriendly = jamEngine.parseFriendly;
177                 jamEngine.meaningfulIds = true;
178                 jamEngine.parseFriendly = true;
179                 //
180                 var resultDescriptorObj = jamEngine.jsonGet
181                 (
182                     [
183                         [ "property", [ "<property>", "fontList" ] ],
184                         [ "application", [ "<enumerated>", [ "ordinal", "targetEnum" ] ] ]
185                     ]
186                 );
187                 var fontPostScriptNameArr = resultDescriptorObj["fontList"][1][1]["fontPostScriptName"][1];
188                 for (var i = 0; i < fontPostScriptNameArr.length; i++)
189                 {
190                     if (fontPostScriptNameArr[i][1] === fontPostScriptName)
191                     {
192                         found = true;
193                         break;
194                     }
195                 }
196                 //
197                 jamEngine.meaningfulIds = saveMeaningfulIds;
198                 jamEngine.parseFriendly = saveParseFriendly;
199             }
200             return found;
201         };
202         //
203         /**
204          * @description Load an action if not currently available.
205          * @param {String} action Action name string
206          * @param {String} actionSet Action set name string
207          * @param {String} actionsFilePath Actions file path string
208          * @example
209          * Folder.current = new Folder ("~/JSON Action Manager/tests/resources/");
210          * jamUtils.<strong>loadAction</strong> ("Cross Process 2", "Cross Processing", "Cross Processing.atn");
211          */
212         jamUtils.loadAction = function (action, actionSet, actionsFilePath)
213         {
214             try
215             {
216                 jamEngine.jsonGet ([ [ "action", [ "<name>", action ] ], [ "actionSet", [ "<name>", actionSet ] ] ]);
217                 var found = true;
218             }
219             catch (e)
220             {
221                 var found = false;
222             }
223             if (!found)
224             {
225                 jamEngine.jsonPlay ("open", { "target": [ "<path>", actionsFilePath ] });
226             }
227         };
228         //
229         /**
230          * @description Load an action set if not currently available.
231          * @param {String} actionSet Action set name string
232          * @param {String} actionsFilePath Actions file path string
233          * @example
234          * Folder.current = new Folder ("~/JSON Action Manager/tests/resources/");
235          * jamUtils.<strong>loadActionSet</strong> ("Cross Processing", "Cross Processing.atn");
236          */
237         jamUtils.loadActionSet = function (actionSet, actionsFilePath)
238         {
239             try
240             {
241                 jamEngine.jsonGet ([ [ "actionSet", [ "<name>", actionSet ] ] ]);
242                 var found = true;
243             }
244             catch (e)
245             {
246                 var found = false;
247             }
248             if (!found)
249             {
250                 jamEngine.jsonPlay ("open", { "target": [ "<path>", actionsFilePath ] });
251             }
252         };
253         //
254         /**
255          * @description Load a preset if not currently available.
256          * @param {String} presetProperty Preset property string:<br />
257          * <ul>
258          * <li>"brush"</li>
259          * <li>"colors"</li>
260          * <li>"gradientClassEvent"</li>
261          * <li>"style"</li>
262          * <li>"pattern"</li>
263          * <li>"shapingCurve"</li>
264          * <li>"customShape"</li>
265          * <li>"toolPreset"</li>
266          * </ul>
267          * @param {String} presetName Preset name string
268          * @param {String} presetFilePath Preset file path string
269          * @example
270          * Folder.current = new Folder ("~/JSON Action Manager/tests/resources/");
271          * jamUtils.<strong>loadPreset</strong> ("style", "Logo X-Aqua in Blue Glass (Button)", "Logo-X-Aqua.asl");
272          * jamUtils.<strong>loadPreset</strong> ("toolPreset", "Brush Tool Soft Round 35 Red", "Brush-35-Red.tpl");
273          */
274         jamUtils.loadPreset = function (presetProperty, presetName, presetFilePath)
275         {
276             var useDOM = false;
277             var useOpen = true;
278             //
279             var classes =
280             {
281                 // (property): (class)
282                 "brush": "brush",
283                 "colors": "color",
284                 "gradientClassEvent": "gradientClassEvent",
285                 "style": "styleClass",
286                 "pattern": "'PttR'",
287                 "shapingCurve": "shapingCurve",
288                 "customShape": "customShape",
289                 "toolPreset": "toolPreset"
290             };
291             var presetClass = classes[presetProperty];
292             //
293             var saveMeaningfulIds = jamEngine.meaningfulIds;
294             var saveParseFriendly = jamEngine.parseFriendly;
295             jamEngine.meaningfulIds = true;
296             jamEngine.parseFriendly = true;
297             //
298             var found = false;
299             var resultDescriptorObj = jamEngine.jsonGet
300             (
301                 [
302                     [ "property", [ "<property>", "presetManager" ] ],
303                     [ "application", [ "<enumerated>", [ "ordinal", "targetEnum" ] ] ]
304                 ]
305             );
306             var presetManagerArr = resultDescriptorObj["presetManager"][1];
307             for (var i = 0; i < presetManagerArr.length; i++)
308             {
309                 var presets = presetManagerArr[i][1];
310                 if (presets[0] === presetClass)
311                 {
312                     var presetsArr = presets[1]["name"][1];
313                     for (var j = 0; j < presetsArr.length; j++)
314                     {
315                         if (presetsArr[j][1] === presetName)
316                         {
317                             found = true;
318                             break;
319                         }
320                     }
321                     break;
322                 }
323             }
324             if (!found)
325             {
326                 if (useDOM)
327                 {
328                     app.load (new File (presetFilePath));
329                 }
330                 else if (useOpen)
331                 {
332                     jamEngine.jsonPlay ("open", { "target": [ "<path>", presetFilePath ] });
333                 }
334                 else
335                 {
336                     jamEngine.jsonPlay
337                     (
338                         "set",
339                         {
340                             "target":
341                             [
342                                 "<reference>",
343                                 [
344                                     [ "property", [ "<property>", presetProperty ] ],
345                                     [ "application", [ "<enumerated>", [ "ordinal", "targetEnum" ] ] ]
346                                 ]
347                             ],
348                             "to": [ "<path>", presetFilePath ],
349                             "append": [ "<boolean>", true ]
350                         }
351                     );
352                 }
353             }
354             //
355             jamEngine.meaningfulIds = saveMeaningfulIds;
356             jamEngine.parseFriendly = saveParseFriendly;
357         };
358         //
359         function getFileObject (file)
360         {
361             var fileObject;
362             if (file instanceof File)
363             {
364                 fileObject = file;
365             }
366             else if (typeof file === 'string')
367             {
368                 fileObject = new File (file);
369             }
370             else
371             {
372                 throw new Error ('[jamUtils getFileObject] Invalid argument');
373             }
374             return fileObject;
375         }
376         //
377         /**
378          * @description Read a text file in UTF-8 encoding.
379          * @param {Object|String} textFile Text file File object (or path string)
380          * @returns {String|Null} Text string, or null if error
381          * @see jamUtils.writeTextFile
382          * @example
383          * var text = jamUtils.<strong>readTextFile</strong> ("~/Desktop/test.txt");
384          */
385         jamUtils.readTextFile = function (textFile)
386         {
387             var text = null;
388             var file = getFileObject (textFile);
389             if (file.open ("r"))
390             {
391                 text = file.read ();
392                 file.close ();
393             }
394             return text;
395         };
396         //
397         /**
398          * @description Convert a JSON text file into a JavaScript data structure.
399          * @param {Object|String} jsonFile JSON text file File object (or path string)
400          * @returns {Object|Array|String|Number|Boolean|Null} JavaScript data structure (usually an object or array)
401          * @see jamUtils.writeJsonFile
402          * @example
403          * var data = jamUtils.<strong>readJsonFile</strong> ("~/Desktop/test-data.json");
404          * alert ("color: " + jamJSON.stringify (data["color"]));   // -> color: [ "RGBColor", [ 255, 0, 255 ] ]
405          */
406         jamUtils.readJsonFile = function (jsonFile)
407         {
408             return jamJSON.parse (this.readTextFile (jsonFile), true);
409         };
410         //
411         /**
412          * @description Write a text file in UTF-8 encoding.
413          * @param {Object|String} textFile Text file File object (or path string)
414          * @param {String} text Text string
415          * @see jamUtils.readTextFile
416          * @example
417          * jamUtils.<strong>writeTextFile</strong> ("~/Desktop/test.txt", "test");
418          */
419         jamUtils.writeTextFile = function (textFile, text)
420         {
421             var file = getFileObject (textFile);
422             if (file.open ('w', 'TEXT'))
423             {
424                 file.encoding = 'UTF-8';
425                 file.lineFeed = 'unix';
426                 file.write ('\uFEFF'); // Byte Order Mark
427                 file.write (text);
428                 file.close ();
429             }
430         };
431         //
432         /**
433          * @description Convert a JavaScript data structure into a JSON text file.
434          * @param {Object|String} jsonFile JSON text file File object (or path string)
435          * @param {Object|Array|String|Number|Boolean|Null} data JavaScript data structure (usually an object or array)
436          * @param {String|Number} [space] Indent space string (e.g. "\t") or number of spaces
437          * @see jamUtils.readJsonFile
438          * @example
439          * var magenta = [ "RGBColor", [ 255, 0, 255 ] ];
440          * var data =
441          * {
442          *     "width": 640,
443          *     "height": 480,
444          *     "color": magenta,
445          *     "title": "紅紫色" + "/" + "マゼンタ"
446          * };
447          * jamUtils.<strong>writeJsonFile</strong> ("~/Desktop/test-data.json", data);
448          */
449         jamUtils.writeJsonFile = function (jsonFile, data, space)
450         {
451             this.writeTextFile (jsonFile, jamJSON.stringify (data, space));
452         };
453         //
454         /**
455          * @description Get a clone (deep copy) of a Javascript data structure.
456          * @param {Object|Array|String|Number|Boolean|Null} data Javascript data structure
457          * @returns {Object|Array|String|Number|Boolean|Null} Clone (deep copy) of a Javascript data structure
458          * @example
459          * var customOptions;
460          * var defaultOptions =
461          * {
462          *     dialog:
463          *     {
464          *         x: 0,
465          *         y: 0,
466          *         width: 1024,
467          *         height: 768
468          *     }
469          * };
470          * //
471          * customOptions = jamUtils.<strong>cloneData</strong> (defaultOptions);
472          * customOptions.dialog.width = 512;
473          * alert (defaultOptions.dialog.width);   // -> 1024
474          * //
475          * customOptions = defaultOptions;
476          * customOptions.dialog.width = 512;
477          * alert (defaultOptions.dialog.width);   // -> 512
478          */
479         jamUtils.cloneData = function (data)
480         {
481             var clone;
482             if (data === null)   // No constructor for null
483             {
484                 clone = data;
485             }
486             else if (data.constructor === Object)
487             {
488                 clone = { };
489                 for (var k in data)
490                 {
491                     if (data.hasOwnProperty (k))
492                     {
493                         clone[k] = this.cloneData (data[k]);
494                     }
495                 }
496             }
497             else if (data.constructor === Array)
498             {
499                 clone = [ ];
500                 for (var i = 0; i < data.length; i++)
501                 {
502                     clone.push (this.cloneData (data[i]));
503                 }
504             }
505             else
506             {
507                 clone = data;
508             }
509             return clone;
510         };
511         //
512         /**
513          * @description Merge two Javascript literal object data structures, using the second one as default base.
514          * @param {Object} data Javascript literal object data structure
515          * @param {Object} defaultData Javascript literal object data structure
516          * @returns {Object} Merged Javascript literal object data structure
517          * @example
518          * var customOptions =
519          * {
520          *     name: "foobar",
521          *     active: true,
522          *     dialog:
523          *     {
524          *         width: 512
525          *     }
526          * };
527          * var defaultOptions =
528          * {
529          *     name: "untitled",
530          *     enabled: false,
531          *     active: false,
532          *     dialog:
533          *     {
534          *         x: 0,
535          *         y: 0,
536          *         width: 1024,
537          *         height: 768
538          *     }
539          * };
540          * //
541          * customOptions = jamUtils.<strong>mergeData</strong> (customOptions, defaultOptions);
542          * alert (jamJSON.stringify (customOptions, '\t'));
543          * // ->
544          * // {
545          * //     "name": "foobar",
546          * //     "active": true,
547          * //     "dialog":
548          * //     {
549          * //         "width": 512,
550          * //         "x": 0,
551          * //         "y": 0,
552          * //         "height": 768
553          * //     },
554          * //     "enabled": false
555          * // }
556          */
557         jamUtils.mergeData = function (data, defaultData)
558         {
559             for (var k in defaultData)
560             {
561                 if (defaultData.hasOwnProperty (k))
562                 {
563                     if (k in data)
564                     {
565                         if ((defaultData[k] !== null) && (defaultData[k].constructor === Object))
566                         {
567                             data[k] = this.mergeData (data[k], defaultData[k]);
568                         }
569                     }
570                     else
571                     {
572                         data[k] = this.cloneData (defaultData[k]);
573                     }
574                 }
575             }
576             return data;
577         };
578         //
579         var jsonCustomOptionsStringKey = "jsonCustomOptions";
580         //
581         /**
582          * @description Retrieve custom options associated with a unique signature [available in CS3 or later].
583          * @param {String} signature Unique signature string
584          * @param {Object} defaultOptions Default options as a JavaScript literal object
585          * @returns {Object} Custom options as a JavaScript literal object
586          * @see jamUtils.eraseCustomOptions
587          * @see jamUtils.putCustomOptions
588          * @example
589          * var signature = "unique-signature-custom-options";
590          * jamUtils.eraseCustomOptions (signature);
591          * var customOptions = { name: "Rectangle" };
592          * jamUtils.putCustomOptions (signature, customOptions, true);
593          * var defaultOptions = { width: 1024, height: 768, name: "Untitled" };
594          * customOptions = jamUtils.<strong>getCustomOptions</strong> (signature, defaultOptions);
595          * if (customOptions.name !== "Untitled")
596          * {
597          *     alert (jamJSON.stringify (customOptions.name + "-" + customOptions.width + "x" + customOptions.height));
598          *     // -> "Rectangle-1024x768"
599          * }
600          */
601         jamUtils.getCustomOptions = function (signature, defaultOptions)
602         {
603             var saveMeaningfulIds = jamEngine.meaningfulIds;
604             var saveParseFriendly = jamEngine.parseFriendly;
605             jamEngine.meaningfulIds = true;
606             jamEngine.parseFriendly = false;
607             try
608             {
609                 var resultObj = jamEngine.classIdAndActionDescriptorToJson (jamEngine.uniIdStrToId (signature), app.getCustomOptions (signature));
610                 var customOptions = jamJSON.parse (resultObj["<descriptor>"][jsonCustomOptionsStringKey]["<string>"], true)
611             }
612             catch (e)
613             {
614                 var customOptions = { };
615             }
616             jamEngine.meaningfulIds = saveMeaningfulIds;
617             jamEngine.parseFriendly = saveParseFriendly;
618             return this.mergeData (customOptions, defaultOptions);
619         };
620         //
621         /**
622          * @description Save custom options associated with a unique signature [available in CS3 or later].
623          * @param {String} signature Unique signature string
624          * @param {Object} customOptions Custom options as a JavaScript literal object
625          * @param {Boolean} [persistent] Whether the options should persist once the script has finished
626          * @see jamUtils.eraseCustomOptions
627          * @see jamUtils.getCustomOptions
628          * @example
629          * var signature = "unique-signature-custom-options";
630          * var customOptions = { width: 512, height: 512, name: "Square" };
631          * jamUtils.<strong>putCustomOptions</strong> (signature, customOptions, true);
632          */
633         jamUtils.putCustomOptions = function (signature, customOptions, persistent)
634         {
635             var descriptorObj = { };
636             descriptorObj[jsonCustomOptionsStringKey] = [ "<string>", jamJSON.stringify (customOptions) ];
637             app.putCustomOptions (signature, jamEngine.jsonToActionDescriptor (descriptorObj), persistent);
638         };
639         //
640         /**
641          * @description Erase custom options associated with a unique signature [available in CS3 or later].
642          * @param {String} signature Unique signature string
643          * @see jamUtils.getCustomOptions
644          * @see jamUtils.putCustomOptions
645          * @example
646          * var signature = "unique-signature-custom-options";
647          * jamUtils.<strong>eraseCustomOptions</strong> (signature);
648          */
649         jamUtils.eraseCustomOptions = function (signature)
650         {
651             app.eraseCustomOptions (signature);
652         };
653         //
654         /**
655          * @description Convert a raw byte data string into a hexadecimal string.
656          * @param {String} dataString Raw byte data string
657          * @param {Boolean} [lowercase] Use lowercase hexadecimal digits (false by default)
658          * @returns {String} Hexadecimal string
659          * @see jamUtils.hexaToDataString
660          * @example
661          * var hexaString = jamUtils.<strong>dataToHexaString</strong> ("«Íï"); // -> "ABCDEF"
662          */
663         jamUtils.dataToHexaString = function (dataString, lowercase)
664         {
665             var hexaDigits = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" ];
666             var hexaString = "";
667             var length = dataString.length;
668             for (var index = 0; index < length; index++)
669             {
670                 var dataByte = dataString.charCodeAt (index);
671                 if ((dataByte >= 0x00) && (dataByte <= 0xFF))
672                 {
673                     hexaString += hexaDigits[(dataByte & 0xF0) >> 4] + hexaDigits[dataByte & 0x0F];
674                 }
675                 else
676                 {
677                     throw new Error ("[jamUtils.dataToHexaString] Invalid data string");
678                 }
679             }
680             if (lowercase)
681             {
682                 hexaString = hexaString.toLowerCase ();
683             }
684             return hexaString;
685         };
686         //
687         /**
688          * @description Convert a hexadecimal string into a raw byte data string.
689          * @param {String} hexaString Hexadecimal string
690          * @returns {String} Raw byte data string
691          * @see jamUtils.dataToHexaString
692          * @example
693          * var dataString = jamUtils.<strong>hexaToDataString</strong> ("00011E1F20217E7F80819E9FA0A1FEFF");
694          * // -> "\u0000\u0001\u001E\u001F !~\u007F\u0080\u0081\u009E\u009F ¡þÿ"
695          */
696         jamUtils.hexaToDataString = function (hexaString)
697         {
698             var dataString = "";
699             var length = hexaString.length;
700             if (((length % 2) === 0) && (/^[0-9A-Fa-f]*$/.test (hexaString)))
701             {
702                 for (var index = 0; index < length; index += 2)
703                 {
704                     var byteString = hexaString.slice (index, index + 2);
705                     dataString += String.fromCharCode (parseInt (byteString, 16));
706                 }
707             }
708             else
709             {
710                 throw new Error ("[jamUtils.hexaToDataString] Invalid hexa string");
711             }
712             return dataString;
713         };
714     } ());
715 }
716 
717 //------------------------------------------------------------------------------
718 
719