summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormeltingice <meltingice8917@gmail.com>2012-07-17 20:15:56 -0400
committermeltingice <meltingice8917@gmail.com>2012-07-17 20:15:56 -0400
commit954702d7dc3e942735dc0fbdbde4da74a793957d (patch)
treeb7d126045d6b2f42b18194c195fe219433e258c8
parentCode recompile (diff)
downloadpsd.js-954702d7dc3e942735dc0fbdbde4da74a793957d.tar.xz
psd.js-954702d7dc3e942735dc0fbdbde4da74a793957d.zip
Fix layer name encoding and parse unicode layer name (Issue #27)
-rw-r--r--examples/images/test-specialchars.psdbin0 -> 24004 bytes
-rw-r--r--lib/psd.js34
-rw-r--r--lib/psd.min.js8
-rwxr-xr-xsrc/psdfile.coffee2
-rwxr-xr-xsrc/psdlayer.coffee15
-rwxr-xr-xsrc/util.coffee29
6 files changed, 76 insertions, 12 deletions
diff --git a/examples/images/test-specialchars.psd b/examples/images/test-specialchars.psd
new file mode 100644
index 0000000..1132d6d
--- /dev/null
+++ b/examples/images/test-specialchars.psd
Binary files differ
diff --git a/lib/psd.js b/lib/psd.js
index 34e1795..69b02c1 100644
--- a/lib/psd.js
+++ b/lib/psd.js
@@ -2240,8 +2240,11 @@ var jspack = new JSPack(); ;
return this.file.seek(this.layerEnd, false);
}
this.parseBlendingRanges();
- this.parseLayerName();
+ this.parseLegacyLayerName();
this.parseExtraData();
+ if (this.name == null) {
+ this.name = this.legacyName;
+ }
Log.debug("Layer " + layerIndex + ":", this);
return this.file.seek(extrastart + extralen, false);
};
@@ -2357,10 +2360,10 @@ var jspack = new JSPack(); ;
return _results;
};
- PSDLayer.prototype.parseLayerName = function() {
+ PSDLayer.prototype.parseLegacyLayerName = function() {
var namelen;
namelen = Util.pad4(this.file.read(1)[0]);
- this.name = this.file.readString(namelen);
+ this.legacyName = Util.decodeMacroman(this.file.read(namelen)).replace(/\u0000/g, '');
return Log.debug("Layer name: " + this.name);
};
@@ -2407,6 +2410,9 @@ var jspack = new JSPack(); ;
case "TySh":
this.adjustments.typeTool = (new PSDTypeTool(this, length)).parse();
break;
+ case "luni":
+ this.name = this.file.readUnicodeString();
+ break;
case "lyid":
this.layerId = this.file.readInt();
break;
@@ -3597,6 +3603,28 @@ var jspack = new JSPack(); ;
return num;
};
+ Util.decodeMacroman = (function() {
+ var high_chars_unicode;
+ high_chars_unicode = '\u00c4\u00c5\u00c7\u00c9\u00d1\u00d6\u00dc\u00e1\n\u00e0\u00e2\u00e4\u00e3\u00e5\u00e7\u00e9\u00e8\n\u00ea\u00eb\u00ed\u00ec\u00ee\u00ef\u00f1\u00f3\n\u00f2\u00f4\u00f6\u00f5\u00fa\u00f9\u00fb\u00fc\n\u2020\u00b0\u00a2\u00a3\u00a7\u2022\u00b6\u00df\n\u00ae\u00a9\u2122\u00b4\u00a8\u2260\u00c6\u00d8\n\u221e\u00b1\u2264\u2265\u00a5\u00b5\u2202\u2211\n\u220f\u03c0\u222b\u00aa\u00ba\u03a9\u00e6\u00f8\n\u00bf\u00a1\u00ac\u221a\u0192\u2248\u2206\u00ab\n\u00bb\u2026\u00a0\u00c0\u00c3\u00d5\u0152\u0153\n\u2013\u2014\u201c\u201d\u2018\u2019\u00f7\u25ca\n\u00ff\u0178\u2044\u20ac\u2039\u203a\ufb01\ufb02\n\u2021\u00b7\u201a\u201e\u2030\u00c2\u00ca\u00c1\n\u00cb\u00c8\u00cd\u00ce\u00cf\u00cc\u00d3\u00d4\n\uf8ff\u00d2\u00da\u00db\u00d9\u0131\u02c6\u02dc\n\u00af\u02d8\u02d9\u02da\u00b8\u02dd\u02db\u02c7'.replace(/\n/g, '');
+ return function(byte_array) {
+ var byte, char_array, idx;
+ char_array = (function() {
+ var _i, _len, _results;
+ _results = [];
+ for (idx = _i = 0, _len = byte_array.length; _i < _len; idx = ++_i) {
+ byte = byte_array[idx];
+ if (byte < 0x80) {
+ _results.push(String.fromCharCode(byte));
+ } else {
+ _results.push(high_chars_unicode.charAt(byte - 0x80));
+ }
+ }
+ return _results;
+ })();
+ return char_array.join('');
+ };
+ })();
+
return Util;
})();
diff --git a/lib/psd.min.js b/lib/psd.min.js
index 99ef661..a920e1a 100644
--- a/lib/psd.min.js
+++ b/lib/psd.min.js
@@ -211,13 +211,14 @@ this.processImageData();if(typeof exports!=="undefined"&&exports!==null){memusag
return this.chanPos+=this.chInfo.length-2;};PSDChannelImage.prototype.parseImageData=function(){this.compression=this.parseCompression();switch(this.compression){case 0:return this.parseRaw();case 1:return this.parseRLE();case 2:case 3:return this.parseZip();default:Log.debug("Unknown image compression. Attempting to skip.");return this.file.seek(this.endPos,false);}};PSDChannelImage.prototype.parseChannelData=function(){var lineIndex,_ref;lineIndex=0;Log.debug("Parsing layer channel #"+this.chInfo.id+", Start = "+(this.file.tell()));return _ref=this.decodeRLEChannel(this.chanPos,lineIndex),this.chanPos=_ref[0],lineIndex=_ref[1],_ref;};return PSDChannelImage;})(PSDImage);PSDLayer=(function(){var BLEND_FLAGS,BLEND_MODES,CHANNEL_SUFFIXES,MASK_FLAGS,SAFE_FONTS,SECTION_DIVIDER_TYPES;CHANNEL_SUFFIXES={'-2':'layer mask','-1':'A',0:'R',1:'G',2:'B',3:'RGB',4:'CMYK',5:'HSL',6:'HSB',9:'Lab',11:'RGB',12:'Lab',13:'CMYK'};SECTION_DIVIDER_TYPES={0:"other",1:"open folder",2:"closed folder",3:"bounding section divider"};BLEND_MODES={"norm":"normal","dark":"darken","lite":"lighten","hue":"hue","sat":"saturation","colr":"color","lum":"luminosity","mul":"multiply","scrn":"screen","diss":"dissolve","over":"overlay","hLit":"hard light","sLit":"soft light","diff":"difference","smud":"exclusion","div":"color dodge","idiv":"color burn","lbrn":"linear burn","lddg":"linear dodge","vLit":"vivid light","lLit":"linear light","pLit":"pin light","hMix":"hard mix"};BLEND_FLAGS={0:"transparency protected",1:"visible",2:"obsolete",3:"bit 4 useful",4:"pixel data irrelevant"};MASK_FLAGS={0:"position relative",1:"layer mask disabled",2:"invert layer mask"};SAFE_FONTS=["Arial","Courier New","Georgia","Times New Roman","Verdana","Trebuchet MS","Lucida Sans","Tahoma"];function PSDLayer(file,header){this.file=file;this.header=header!=null?header:null;this.image=null;this.mask={};this.blendingRanges={};this.adjustments={};this.layerType="normal";this.blendingMode="normal";this.opacity=255;this.fillOpacity=255;this.isFolder=false;this.isHidden=false;}
PSDLayer.prototype.parse=function(layerIndex){var extralen,extrastart,result;if(layerIndex==null){layerIndex=null;}
this.parseInfo(layerIndex);this.parseBlendModes();extralen=this.file.readInt();this.layerEnd=this.file.tell()+extralen;assert(extralen>0);extrastart=this.file.tell();result=this.parseMaskData();if(!result){Log.debug("Error parsing mask data for layer #"+layerIndex+". Skipping.");return this.file.seek(this.layerEnd,false);}
-this.parseBlendingRanges();this.parseLayerName();this.parseExtraData();Log.debug("Layer "+layerIndex+":",this);return this.file.seek(extrastart+extralen,false);};PSDLayer.prototype.parseInfo=function(layerIndex){var channelID,channelLength,i,_i,_ref,_ref1,_ref2,_ref3,_results;this.idx=layerIndex;_ref=this.file.readf(">iiiih"),this.top=_ref[0],this.left=_ref[1],this.bottom=_ref[2],this.right=_ref[3],this.channels=_ref[4];_ref1=[this.bottom-this.top,this.right-this.left],this.rows=_ref1[0],this.cols=_ref1[1];assert(this.channels>0);this.height=this.rows;this.width=this.cols;if(this.bottom<this.top||this.right<this.left||this.channels>64){Log.debug("Somethings not right, attempting to skip layer.");this.file.seek(6*this.channels+12);this.file.skipBlock("layer info: extra data");return;}
+this.parseBlendingRanges();this.parseLegacyLayerName();this.parseExtraData();if(this.name==null){this.name=this.legacyName;}
+Log.debug("Layer "+layerIndex+":",this);return this.file.seek(extrastart+extralen,false);};PSDLayer.prototype.parseInfo=function(layerIndex){var channelID,channelLength,i,_i,_ref,_ref1,_ref2,_ref3,_results;this.idx=layerIndex;_ref=this.file.readf(">iiiih"),this.top=_ref[0],this.left=_ref[1],this.bottom=_ref[2],this.right=_ref[3],this.channels=_ref[4];_ref1=[this.bottom-this.top,this.right-this.left],this.rows=_ref1[0],this.cols=_ref1[1];assert(this.channels>0);this.height=this.rows;this.width=this.cols;if(this.bottom<this.top||this.right<this.left||this.channels>64){Log.debug("Somethings not right, attempting to skip layer.");this.file.seek(6*this.channels+12);this.file.skipBlock("layer info: extra data");return;}
this.channelsInfo=[];_results=[];for(i=_i=0,_ref2=this.channels;0<=_ref2?_i<_ref2:_i>_ref2;i=0<=_ref2?++_i:--_i){_ref3=this.file.readf(">hi"),channelID=_ref3[0],channelLength=_ref3[1];Log.debug("Channel "+i+": id="+channelID+", "+channelLength+" bytes, type="+CHANNEL_SUFFIXES[channelID]);_results.push(this.channelsInfo.push({id:channelID,length:channelLength}));}
return _results;};PSDLayer.prototype.parseBlendModes=function(){var filler,flags,_ref;this.blendMode={};_ref=this.file.readf(">4s4sBBBB"),this.blendMode.sig=_ref[0],this.blendMode.key=_ref[1],this.blendMode.opacity=_ref[2],this.blendMode.clipping=_ref[3],flags=_ref[4],filler=_ref[5];assert(this.blendMode.sig==="8BIM");this.blendMode.key=this.blendMode.key.trim();this.blendMode.opacityPercentage=(this.blendMode.opacity*100)/255;this.blendMode.blender=BLEND_MODES[this.blendMode.key];this.blendMode.transparencyProtected=flags&0x01;this.blendMode.visible=(flags&(0x01<<1))>0;this.blendMode.visible=1-this.blendMode.visible;this.blendMode.obsolete=(flags&(0x01<<2))>0;if((flags&(0x01<<3))>0){this.blendMode.pixelDataIrrelevant=(flags&(0x01<<4))>0;}
this.blendingMode=this.blendMode.blender;this.opacity=this.blendMode.opacity;this.visible=this.blendMode.visible;return Log.debug("Blending mode:",this.blendMode);};PSDLayer.prototype.parseMaskData=function(){var flags,_ref,_ref1,_ref2,_ref3;this.mask.size=this.file.readInt();assert((_ref=this.mask.size)===36||_ref===20||_ref===0);if(this.mask.size===0){return true;}
_ref1=this.file.readf(">llllBB"),this.mask.top=_ref1[0],this.mask.left=_ref1[1],this.mask.bottom=_ref1[2],this.mask.right=_ref1[3],this.mask.defaultColor=_ref1[4],flags=_ref1[5];assert((_ref2=this.mask.defaultColor)===0||_ref2===255);this.mask.width=this.mask.right-this.mask.left;this.mask.height=this.mask.bottom-this.mask.top;this.mask.relative=flags&0x01;this.mask.disabled=(flags&(0x01<<1))>0;this.mask.invert=(flags&(0x01<<2))>0;if(this.mask.size===20){this.file.seek(2);}else{_ref3=this.file.readf(">BB"),flags=_ref3[0],this.mask.defaultColor=_ref3[1];this.mask.relative=flags&0x01;this.mask.disabled=(flags&(0x01<<1))>0;this.mask.invert=(flags&(0x01<<2))>0;this.file.seek(16);}
return true;};PSDLayer.prototype.parseBlendingRanges=function(){var i,length,pos,_i,_ref,_results;length=this.file.readInt();this.blendingRanges.grey={source:{black:this.file.readShortInt(),white:this.file.readShortInt()},dest:{black:this.file.readShortInt(),white:this.file.readShortInt()}};pos=this.file.tell();this.blendingRanges.numChannels=(length-8)/8;assert(this.blendingRanges.numChannels>0);this.blendingRanges.channels=[];_results=[];for(i=_i=0,_ref=this.blendingRanges.numChannels;0<=_ref?_i<_ref:_i>_ref;i=0<=_ref?++_i:--_i){_results.push(this.blendingRanges.channels.push({source:{black:this.file.readShortInt(),white:this.file.readShortInt()},dest:{black:this.file.readShortInt(),white:this.file.readShortInt()}}));}
-return _results;};PSDLayer.prototype.parseLayerName=function(){var namelen;namelen=Util.pad4(this.file.read(1)[0]);this.name=this.file.readString(namelen);return Log.debug("Layer name: "+this.name);};PSDLayer.prototype.parseExtraData=function(){var key,length,pos,signature,_ref,_results;_results=[];while(this.file.tell()<this.layerEnd){_ref=this.file.readf(">4s4s"),signature=_ref[0],key=_ref[1];assert.equal(signature,"8BIM");length=Util.pad2(this.file.readInt());pos=this.file.tell();Log.debug("Extra layer info: key = "+key+", length = "+length);switch(key){case"levl":this.adjustments.levels=(new PSDLevels(this,length)).parse();break;case"curv":this.adjustments.curves=(new PSDCurves(this,length)).parse();break;case"brit":this.adjustments.brightnessContrast=(new PSDBrightnessContrast(this,length)).parse();break;case"blnc":this.adjustments.colorBalance=(new PSDColorBalance(this,length)).parse();break;case"hue2":this.adjustments.hueSaturation=(new PSDHueSaturation(this,length)).parse();break;case"selc":this.adjustments.selectiveColor=(new PSDSelectiveColor(this,length)).parse();break;case"thrs":this.adjustments.threshold=(new PSDThreshold(this,length)).parse();break;case"nvrt":this.adjustments.invert=(new PSDInvert(this,length)).parse();break;case"post":this.adjustments.posterize=(new PSDPosterize(this,length)).parse();break;case"tySh":this.adjustments.typeTool=(new PSDTypeTool(this,length)).parse(true);break;case"TySh":this.adjustments.typeTool=(new PSDTypeTool(this,length)).parse();break;case"lyid":this.layerId=this.file.readInt();break;case"lsct":this.readLayerSectionDivider();break;case"lrFX":this.parseEffectsLayer();this.file.read(2);break;default:this.file.seek(length);Log.debug("Skipping additional layer info with key "+key);}
+return _results;};PSDLayer.prototype.parseLegacyLayerName=function(){var namelen;namelen=Util.pad4(this.file.read(1)[0]);this.legacyName=Util.decodeMacroman(this.file.read(namelen)).replace(/\u0000/g,'');return Log.debug("Layer name: "+this.name);};PSDLayer.prototype.parseExtraData=function(){var key,length,pos,signature,_ref,_results;_results=[];while(this.file.tell()<this.layerEnd){_ref=this.file.readf(">4s4s"),signature=_ref[0],key=_ref[1];assert.equal(signature,"8BIM");length=Util.pad2(this.file.readInt());pos=this.file.tell();Log.debug("Extra layer info: key = "+key+", length = "+length);switch(key){case"levl":this.adjustments.levels=(new PSDLevels(this,length)).parse();break;case"curv":this.adjustments.curves=(new PSDCurves(this,length)).parse();break;case"brit":this.adjustments.brightnessContrast=(new PSDBrightnessContrast(this,length)).parse();break;case"blnc":this.adjustments.colorBalance=(new PSDColorBalance(this,length)).parse();break;case"hue2":this.adjustments.hueSaturation=(new PSDHueSaturation(this,length)).parse();break;case"selc":this.adjustments.selectiveColor=(new PSDSelectiveColor(this,length)).parse();break;case"thrs":this.adjustments.threshold=(new PSDThreshold(this,length)).parse();break;case"nvrt":this.adjustments.invert=(new PSDInvert(this,length)).parse();break;case"post":this.adjustments.posterize=(new PSDPosterize(this,length)).parse();break;case"tySh":this.adjustments.typeTool=(new PSDTypeTool(this,length)).parse(true);break;case"TySh":this.adjustments.typeTool=(new PSDTypeTool(this,length)).parse();break;case"luni":this.name=this.file.readUnicodeString();break;case"lyid":this.layerId=this.file.readInt();break;case"lsct":this.readLayerSectionDivider();break;case"lrFX":this.parseEffectsLayer();this.file.read(2);break;default:this.file.seek(length);Log.debug("Skipping additional layer info with key "+key);}
if(this.file.tell()!==(pos+length)){Log.debug("Error parsing additional layer info with key "+key+" - unexpected end");_results.push(this.file.seek(pos+length,false));}else{_results.push(void 0);}}
return _results;};PSDLayer.prototype.parseEffectsLayer=function(){var count,data,effect,effects,left,pos,signature,size,type,v,_ref,_ref1;effects=[];_ref=this.file.readf(">HH"),v=_ref[0],count=_ref[1];while(count-->0){_ref1=this.file.readf(">4s4s"),signature=_ref1[0],type=_ref1[1];size=this.file.readf(">i")[0];pos=this.file.tell();Log.debug("Parsing effect layer with type "+type+" and size "+size);effect=(function(){switch(type){case"cmnS":return new PSDLayerEffectCommonStateInfo(this.file);case"dsdw":return new PSDDropDownLayerEffect(this.file);case"isdw":return new PSDDropDownLayerEffect(this.file,true);}}).call(this);data=effect!=null?effect.parse():void 0;left=(pos+size)-this.file.tell();if(left!==0){Log.debug("Failed to parse effect layer with type "+type);this.file.seek(left);}else{if(type!=="cmnS"){effects.push(data);}}}
return this.adjustments.effects=effects;};PSDLayer.prototype.readLayerSectionDivider=function(){var code;code=this.file.readInt();this.layerType=SECTION_DIVIDER_TYPES[code];Log.debug("Layer type:",this.layerType);switch(code){case 1:case 2:return this.isFolder=true;case 3:return this.isHidden=true;}};PSDLayer.prototype.toJSON=function(){var data,section,sections,_i,_len;sections=['name','top','left','bottom','right','channels','rows','cols','channelsInfo','mask','layerType','blendMode','adjustments','visible'];data={};for(_i=0,_len=sections.length;_i<_len;_i++){section=sections[_i];data[section]=this[section];}
@@ -279,5 +280,6 @@ if(sigFig===0){return Math.round(num);}
mult=Math.pow(10,sigFig);return Math.round(num*mult)/mult;};Util.clamp=function(num,min,max){var i,val,_i,_len;if(min==null){min=Number.MIN_VALUE;}
if(max==null){max=Number.MAX_VALUE;}
if(typeof num==="object"&&(num.length!=null)){for(i=_i=0,_len=num.length;_i<_len;i=++_i){val=num[i];num[i]=Math.max(Math.min(val,max),min);}}else if(typeof num==="object"){for(i in num){if(!__hasProp.call(num,i))continue;val=num[i];num[i]=Math.max(Math.min(val,max),min);}}else{num=Math.max(Math.min(num,max),min);}
-return num;};return Util;})();Log=(function(){function Log(){}
+return num;};Util.decodeMacroman=(function(){var high_chars_unicode;high_chars_unicode='\u00c4\u00c5\u00c7\u00c9\u00d1\u00d6\u00dc\u00e1\n\u00e0\u00e2\u00e4\u00e3\u00e5\u00e7\u00e9\u00e8\n\u00ea\u00eb\u00ed\u00ec\u00ee\u00ef\u00f1\u00f3\n\u00f2\u00f4\u00f6\u00f5\u00fa\u00f9\u00fb\u00fc\n\u2020\u00b0\u00a2\u00a3\u00a7\u2022\u00b6\u00df\n\u00ae\u00a9\u2122\u00b4\u00a8\u2260\u00c6\u00d8\n\u221e\u00b1\u2264\u2265\u00a5\u00b5\u2202\u2211\n\u220f\u03c0\u222b\u00aa\u00ba\u03a9\u00e6\u00f8\n\u00bf\u00a1\u00ac\u221a\u0192\u2248\u2206\u00ab\n\u00bb\u2026\u00a0\u00c0\u00c3\u00d5\u0152\u0153\n\u2013\u2014\u201c\u201d\u2018\u2019\u00f7\u25ca\n\u00ff\u0178\u2044\u20ac\u2039\u203a\ufb01\ufb02\n\u2021\u00b7\u201a\u201e\u2030\u00c2\u00ca\u00c1\n\u00cb\u00c8\u00cd\u00ce\u00cf\u00cc\u00d3\u00d4\n\uf8ff\u00d2\u00da\u00db\u00d9\u0131\u02c6\u02dc\n\u00af\u02d8\u02d9\u02da\u00b8\u02dd\u02db\u02c7'.replace(/\n/g,'');return function(byte_array){var byte,char_array,idx;char_array=(function(){var _i,_len,_results;_results=[];for(idx=_i=0,_len=byte_array.length;_i<_len;idx=++_i){byte=byte_array[idx];if(byte<0x80){_results.push(String.fromCharCode(byte));}else{_results.push(high_chars_unicode.charAt(byte-0x80));}}
+return _results;})();return char_array.join('');};})();return Util;})();Log=(function(){function Log(){}
Log.debug=Log.log=function(){return this.output("log",arguments);};Log.output=function(method,data){if(typeof exports!=="undefined"&&exports!==null){if(PSD.DEBUG){return console[method].apply(null,data);}}else{if(PSD.DEBUG){return console[method]("[PSD]",data);}}};return Log;})();}).call(this); \ No newline at end of file
diff --git a/src/psdfile.coffee b/src/psdfile.coffee
index e30d1b1..577a368 100755
--- a/src/psdfile.coffee
+++ b/src/psdfile.coffee
@@ -72,7 +72,7 @@ class PSDFile
# use to us in Javascript.
readString: (length) ->
ret = []
- ret[i] = String.fromCharCode @read(1)[0] for i in [0...length]
+ ret[i] = String.fromCharCode(@read(1)[0]) for i in [0...length]
ret.join('').replace /\u0000/g, ""
readUnicodeString: ->
diff --git a/src/psdlayer.coffee b/src/psdlayer.coffee
index 24629d5..5e582c0 100755
--- a/src/psdlayer.coffee
+++ b/src/psdlayer.coffee
@@ -103,9 +103,11 @@ class PSDLayer
return @file.seek @layerEnd, false
@parseBlendingRanges()
- @parseLayerName()
+ @parseLegacyLayerName()
@parseExtraData()
+ @name = @legacyName unless @name?
+
Log.debug "Layer #{layerIndex}:", @
# In case there are filler zeros
@@ -254,11 +256,14 @@ class PSDLayer
black: @file.readShortInt()
white: @file.readShortInt()
- # Parse the name of this layer
- parseLayerName: ->
+ # Parse the name of this layer. This is considered the "legacy"
+ # name because it is encoded with MacRoman encoding. PS >= 5.0
+ # includes a unicode version of the name, which is in the additional
+ # layer information section.
+ parseLegacyLayerName: ->
# Name length is padded in multiples of 4
namelen = Util.pad4 @file.read(1)[0]
- @name = @file.readString namelen
+ @legacyName = Util.decodeMacroman(@file.read(namelen)).replace /\u0000/g, ''
Log.debug "Layer name: #{@name}"
@@ -299,6 +304,8 @@ class PSDLayer
@adjustments.typeTool = (new PSDTypeTool(@, length)).parse(true)
when "TySh" # PS >= 6
@adjustments.typeTool = (new PSDTypeTool(@, length)).parse()
+ when "luni" # PS >= 5.0
+ @name = @file.readUnicodeString()
when "lyid"
@layerId = @file.readInt()
when "lsct"
diff --git a/src/util.coffee b/src/util.coffee
index 050f0de..316b410 100755
--- a/src/util.coffee
+++ b/src/util.coffee
@@ -1,7 +1,7 @@
# "Static" utility functions
class Util
@pad2: (i) -> Math.floor((i + 1) / 2) * 2
- @pad4: (i) -> (((i & 0xFF) + 1 + 3) & ~ 0x03) - 1
+ @pad4: (i) -> (((i & 0xFF) + 1 + 3) & ~0x03) - 1
@toUInt16: (b1, b2) -> (b1 << 8) | b2
@toInt16: (b1, b2) ->
@@ -24,3 +24,30 @@ class Util
num = Math.max(Math.min(num, max), min)
num
+
+ # Contributed by https://github.com/jrus
+ @decodeMacroman = do ->
+ high_chars_unicode = '''
+ \u00c4\u00c5\u00c7\u00c9\u00d1\u00d6\u00dc\u00e1
+ \u00e0\u00e2\u00e4\u00e3\u00e5\u00e7\u00e9\u00e8
+ \u00ea\u00eb\u00ed\u00ec\u00ee\u00ef\u00f1\u00f3
+ \u00f2\u00f4\u00f6\u00f5\u00fa\u00f9\u00fb\u00fc
+ \u2020\u00b0\u00a2\u00a3\u00a7\u2022\u00b6\u00df
+ \u00ae\u00a9\u2122\u00b4\u00a8\u2260\u00c6\u00d8
+ \u221e\u00b1\u2264\u2265\u00a5\u00b5\u2202\u2211
+ \u220f\u03c0\u222b\u00aa\u00ba\u03a9\u00e6\u00f8
+ \u00bf\u00a1\u00ac\u221a\u0192\u2248\u2206\u00ab
+ \u00bb\u2026\u00a0\u00c0\u00c3\u00d5\u0152\u0153
+ \u2013\u2014\u201c\u201d\u2018\u2019\u00f7\u25ca
+ \u00ff\u0178\u2044\u20ac\u2039\u203a\ufb01\ufb02
+ \u2021\u00b7\u201a\u201e\u2030\u00c2\u00ca\u00c1
+ \u00cb\u00c8\u00cd\u00ce\u00cf\u00cc\u00d3\u00d4
+ \uf8ff\u00d2\u00da\u00db\u00d9\u0131\u02c6\u02dc
+ \u00af\u02d8\u02d9\u02da\u00b8\u02dd\u02db\u02c7
+ '''.replace /\n/g, ''
+
+ (byte_array) ->
+ char_array = for byte, idx in byte_array
+ if byte < 0x80 then String.fromCharCode byte
+ else high_chars_unicode.charAt byte - 0x80
+ char_array.join ''