Jump To …

psdfile.coffee

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

  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"