Commits

Mikhail Korobov committed aaad8c7 Merge

Merge pull request #2 from oliverzheng/master

Adding descriptor parsing

Comments (0)

Files changed (4)

+*.pyc

src/psd_tools/decoder/actions.py

 
 from psd_tools.utils import read_unicode_string, read_fmt
 
-Descriptor = collections.namedtuple('Descriptor', 'name classID item_count items')
+Descriptor = collections.namedtuple('Descriptor', 'name classID items')
+Reference = collections.namedtuple('Descriptor', 'items')
+Property = collections.namedtuple('Property', 'name classID keyID')
+UnitFloat = collections.namedtuple('UnitFloat', 'unit value')
+Double = collections.namedtuple('Double', 'value')
+Class = collections.namedtuple('Class', 'name classID')
+String = collections.namedtuple('String', 'value')
+EnumReference = collections.namedtuple('String', 'name classID typeID enum')
+Boolean = collections.namedtuple('Boolean', 'value')
+Offset = collections.namedtuple('Offset', 'name classID value')
+Alias = collections.namedtuple('Alias', 'value')
+List = collections.namedtuple('List', 'items')
+Integer = collections.namedtuple('Integer', 'value')
+Enum = collections.namedtuple('Enum', 'type enum')
+EngineData = collections.namedtuple('EngineData', 'value')
 
 
-def decode_descriptor(data):
-    fp = io.BytesIO(data)
+def get_ostype(ostype):
+    return {
+        'obj ': decode_ref,
+        'ObjC': decode_descriptor,
+        'VlLs': decode_list,
+        'doub': decode_double,
+        'UntF': decode_unit_float,
+        'TEXT': decode_string,
+        'enum': decode_enum,
+        'long': decode_integer,
+        'bool': decode_bool,
+        'GlbO': decode_descriptor,
+        'type': decode_class,
+        'GlbC': decode_class,
+        'alis': decode_alias,
+        'tdta': decode_raw,
+    }.get(ostype, None)
+
+
+def decode_descriptor(fp):
     name = read_unicode_string(fp)
-
     classID_length = read_fmt("I", fp)[0]
     classID = fp.read(classID_length or 4)
 
+    items = []
     item_count = read_fmt("I", fp)[0]
-    items = fp.read() # TODO: detailed parsing
+    for n in xrange(item_count):
+        item_length = read_fmt("I", fp)[0]
+        key = fp.read(item_length or 4)
+        ostype = fp.read(4)
 
-    return Descriptor(name, classID, item_count, items)
+        decode_ostype = get_ostype(ostype)
+        if decode_ostype:
+            value = decode_ostype(key, fp)
+            if value is not None:
+                items.append((key, value))
+
+    return Descriptor(name, classID, items)
+
+def decode_ref(key, fp):
+    item_count = read_fmt("I", fp)[0]
+    items = []
+    for _ in xrange(item_count):
+        ostype = read_fmt("I", fp)
+
+        decode_ostype = {
+            'prop': decode_prop,
+            'Clss': decode_class,
+            'Enmr': decode_enum_ref,
+            'rele': decode_offset,
+            'Idnt': decode_identifier,
+            'indx': decode_index,
+            'name': decode_name,
+        }.get(ostype, None)
+        if decode_ostype:
+            value = decode_ostype(key, fp)
+            if value is not None:
+                items.append(value)
+    return Reference(items)
+
+def decode_prop(key, fp):
+    name = read_unicode_string(fp)
+    classID_length = read_fmt("I", fp)[0]
+    classID = fp.read(classID_length or 4)
+    keyID_length = read_fmt("I", fp)[0]
+    keyID = fp.read(keyID_length or 4)
+    return Property(name, classID, keyID)
+
+def decode_unit_float(key, fp):
+    unit_key = read_fmt("I", fp)
+    unit = {
+        '#Ang', 'angle',
+        '#Rsl', 'density',
+        '#Rlt', 'distance',
+        '#Nne', 'none',
+        '#Prc', 'percent',
+        '#Pxl', 'pixels',
+    }.get(unit_key, None)
+    if unit:
+        value = read_fmt("d", fp)
+        return UnitFloat(unit, value)
+
+def decode_double(key, fp):
+    return Double(read_fmt("d", fp))
+
+def decode_class(key, fp):
+    name = read_unicode_string(fp)
+    classID_length = read_fmt("I", fp)[0]
+    classID = fp.read(classID_length or 4)
+    return Class(name, classID)
+
+def decode_string(key, fp):
+    value = read_unicode_string(fp)
+    return String(value)
+
+def decode_enum_ref(key, fp):
+    name = read_unicode_string(fp)
+    classID_length = read_fmt("I", fp)[0]
+    classID = fp.read(classID_length or 4)
+    typeID_length = read_fmt("I", fp)[0]
+    typeID = fp.read(typeID_length or 4)
+    enum_length = read_fmt("I", fp)[0]
+    enum = fp.read(enum_length or 4)
+    return EnumReference(name, classID, typeID, enum)
+
+def decode_offset(key, fp):
+    name = read_unicode_string(fp)
+    classID_length = read_fmt("I", fp)[0]
+    classID = fp.read(classID_length or 4)
+    offset = read_fmt("I", fp)[0]
+    return Offset(name, classID, offset)
+
+def decode_bool(key, fp):
+    return Boolean(read_fmt("?", fp))
+
+def decode_alias(key, fp):
+    length = read_fmt("I", fp)[0]
+    value = fp.read(length)
+    return Alias(value)
+
+def decode_list(key, fp):
+    items_count = read_fmt("I", fp)[0]
+    items = []
+    for _ in xrange(items_count):
+        ostype = read_fmt("I", fp)
+
+        decode_ostype = get_ostype(ostype)
+        if decode_ostype:
+            value = decode_ostype(fp)
+            if value is not None:
+                items.append(value)
+
+    return List(items)
+
+def decode_integer(key, fp):
+    return Integer(read_fmt("I", fp)[0])
+
+def decode_enum(key, fp):
+    type_length = read_fmt("I", fp)[0]
+    type_ = fp.read(type_length or 4)
+    enum_length = read_fmt("I", fp)[0]
+    enum = fp.read(enum_length or 4)
+    return Enum(type_, enum)
+
+
+class UnknownOSType(ValueError):
+    pass
+
+# These need to raise exceptions - they are actually show stoppers. Without
+# knowing how much to read, the rest of the descriptor cannot be parsed. It's
+# probably better to not know any known descriptors unless all are present.
+# Tagged blocks can eat the exception since they know their total length.
+
+def decode_raw(key, fp):
+    # This is the only thing we know about.
+    if key == 'EngineData':
+        raw = fp.read()
+        return decode_enginedata(raw)
+
+    # XXX: The spec says variable data without a length ._.
+    raise UnknownOSType('Cannot decode raw descriptor data')
+
+def decode_identifier(key, fp):
+    # XXX: The spec says nothing about this.
+    raise UnknownOSType('Cannot decode identifier descriptor')
+
+def decode_index(key, fp):
+    # XXX: The spec says nothing about this.
+    raise UnknownOSType('Cannot decode index descriptor')
+
+def decode_name(key, fp):
+    # XXX: The spec says nothing about this.
+    raise UnknownOSType('Cannot decode name descriptor')
+
+
+def decode_enginedata(data):
+    # XXX: This is some kind of dictionary that have magical values.
+    # See java-psd-library for parsing info; the meaning of parsed data is still
+    # unknown.
+    return EngineData(data)

src/psd_tools/decoder/tagged_blocks.py

 import io
 
 from psd_tools.constants import TaggedBlock, SectionDivider
-from psd_tools.decoder.actions import decode_descriptor
+from psd_tools.decoder.actions import decode_descriptor, UnknownOSType
 from psd_tools.utils import read_fmt, read_unicode_string, unpack, debug_view
 from psd_tools.decoder import decoders
 from psd_tools.reader.layers import Block
 SolidColorSettings = collections.namedtuple('SolidColorSettings', 'version data')
 MetadataItem = collections.namedtuple('MetadataItem', 'sig key copy_on_sheet_duplication data')
 ProtectedSetting = collections.namedtuple('ProtectedSetting', 'transparency, composite, position')
+TypeToolObjectSetting = collections.namedtuple('TypeToolObjectSetting',
+                        'version xx xy yx yy tx ty text_version descriptor_version1 text_data')
+                        #'warp_version descriptor_version2 warp_data'
+                        #'left top right bottom')
+
 
 class Divider(collections.namedtuple('Divider', 'type key')):
     def __repr__(self):
 def _decode_soco(data):
     fp = io.BytesIO(data)
     version = read_fmt("I", fp)
-    data = decode_descriptor(fp.read())
-    return SolidColorSettings(version, data)
+    try:
+        data = decode_descriptor(fp)
+        return SolidColorSettings(version, data)
+    except UnknownOSType as e:
+        warnings.warn("Ignoring solid color tagged block (%s)" % e)
+
 
 @register(TaggedBlock.REFERENCE_POINT)
 def _decode_reference_point(data):
     fp = io.BytesIO(data)
     layers = layers._read_layers(fp, 'latin1', 16, length=len(data))
     return decode_layers(layers)
+
+@register(TaggedBlock.TYPE_TOOL_OBJECT_SETTING)
+def _decode_type_tool_object_setting(data):
+    fp = io.BytesIO(data)
+    ver, xx, xy, yx, yy, tx, ty, txt_ver, desc_ver1 = read_fmt("H 6Q H I", fp)
+
+    # This decoder needs to be updated if we have new formats.
+    if ver != 1 or txt_ver != 50 or desc_ver1 != 16:
+        warnings.warn("Ignoring type setting tagged block due to old versions")
+        return
+
+    try:
+        text_data = decode_descriptor(fp)
+    except UnknownOSType as e:
+        warnings.warn("Ignoring type setting tagged block (%s)" % e)
+        return
+
+    # XXX: Until Engine Data is parsed properly, the following cannot be parsed.
+    # The end of the engine data dictates where this starts.
+    return TypeToolObjectSetting(ver, xx, xy, yx, yy, tx, ty, txt_ver, desc_ver1, text_data)
+
+    warp_ver, desc_ver2 = read_fmt("H I", fp)
+    if warp_ver != 1 or desc_ver2 != 16:
+        warnings.warn("Ignoring type setting tagged block due to old versions")
+        return
+
+    try:
+        warp_data = decode_descriptor(fp)
+    except UnknownOSType as e:
+        warnings.warn("Ignoring type setting tagged block (%s)" % e)
+        return
+
+    left, top, right, bottom = read_fmt("4Q", fp)
+    return TypeToolObjectSetting(ver, xx, xy, yx, yy, tx, ty, txt_ver, desc_ver1,
+                                 text_data, warp_ver, desc_ver2, warp_data,
+                                 left, top, right, bottom)

src/psd_tools/user_api/psd_image.py

         return self.y2-self.y1
 
 
+class Text(object):
+	pass
+
 class _RawLayer(object):
     """
     Layer groups and layers are internally both 'layers' in PSD;
         info = self._info
         return BBox(info.left, info.top, info.right, info.bottom)
 
+    @property
+    def text(self):
+        if self._tagged_blocks.get(TaggedBlock.TYPE_TOOL_OBJECT_SETTING):
+            return Text()
+
     def __repr__(self):
         bbox = self.bbox
         return "<psd_tools.Layer: %r, size=%dx%d, x=%d, y=%d>" % (