aboutsummaryrefslogtreecommitdiffstats
path: root/src/psdfile.coffee
blob: 083b0d45b69237c41f472749f78668146b86835b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# Simulation and abstraction of a disk-based file.
# Provides methods to read the raw binary file data, which is stored in a 
# variable instead of read from disk. A lot of these functions are from C,
# but some of them are helper functions to make things a bit easier to
# understand.
class PSDFile
  unicodeRegex: /\\u([\d\w]{4})/gi

  constructor: (@data) ->
    # Track our current position in the file data. This is analogous to the
    # file pointer in C.
    @pos = 0

  # Get the current position in the file. This is here to parallel the C method
  tell: -> @pos

  # Read one or more bytes from the file. Note that this moves the file pointer
  # forward the number of bytes specified.
  read: (bytes) -> (@data[@pos++] for i in [0...bytes])

  # Move the file pointer to a new position. By default, this is done relative
  # to the current file pointer position. Setting the 2nd argument to false
  # causes the file pointer to move to the absolute location specified.
  seek: (amount, rel = true) ->
    if rel then @pos += amount else @pos = amount

  #
  # Helper functions so we don't have to remember the unpack
  # format codes.
  #
  
  # 4 bytes / 32-bit
  readInt: ->
    int = @readUInt()
    if int >= 0x80000000 then int - 0x100000000 else int

  readUInt: ->
    b1 = @read(1)[0] << 24
    b2 = @read(1)[0] << 16
    b3 = @read(1)[0] << 8
    b4 = @read(1)[0]
    b1 | b2 | b3 | b4

  # 2 bytes
  readShortInt: ->
    int = @readShortUInt()
    if int >= 0x8000 then int - 0x10000 else int

  readShortUInt: ->
    b1 = @read(1)[0] << 8
    b2 = @read(1)[0]
    b1 | b2

  # 4 bytes
  readLongInt: -> @readf(">l")[0]
  readLongUInt: -> @readf(">L")[0]

  # 8 bytes
  readDouble: -> @readf(">d")[0]

  # 1 byte
  readBoolean: -> @read(1)[0] isnt 0

  # Unfortunately Javascript does not support 64-bit integers, so we
  # have a temporary solution for now. In the future, we can parse and
  # store the int either as an octet string, or something more useful.
  readLongLong: -> @read(8)
  readULongLong: -> @read(8)

  # Reads a string with the given length. Because some strings are also
  # null-byte padded, we strip out these null bytes since they are of no
  # use to us in Javascript.
  readString: (length) ->
    ret = String.fromCharCode.apply null, @read(length)
    ret.replace /\u0000/g, ""

  readUnicodeString: ->
    len = @readInt() * 2
    str = @readf(">#{len}s")[0]
    str = str.replace @unicodeRegex, (match, grp) ->
      String.fromCharCode parseInt(grp, 16)

    str.replace /\u0000/g, ""

  # Used for reading pascal strings, which are strings that have their length 
  # prepended to the chunk of character bytes. If a length isn't found, a 
  # string with the default length will be read instead.
  readLengthWithString: (defaultLen = 4) ->
    length = @read(1)[0]
    if length is 0
      str = @readString defaultLen
    else
      str = @readString length

    str

  # Reads a byte list
  readBytesList: (size) -> @read size

  readSpaceColor: ->
    colorSpace = @readShortInt()

    colorComponent = []
    colorComponent.push @readShortInt() >> 8 for i in [0...4]
    PSDColor.colorSpaceToARGB(colorSpace, colorComponent)
    
  
  # Reads from the file given the unpack format string. Format string codes 
  # can be easily referenced 
  # [from the Python docs](http://docs.python.org/library/struct.html#format-characters)
  readf: (format) -> jspack.Unpack format, @read(jspack.CalcLength(format))

  # Skips a block, assuming the next byte describes the size of the section.
  # An optional description is given to explain why we are skipping this block
  # instead of parsing it.
  skipBlock: (desc = "unknown") ->
    [n] = @readf('>L')
    @seek(n) if n # relative

    Log.debug "Skipped #{desc} with #{n} bytes"