Logo Search packages:      
Sourcecode: scummvm version File versions

themeparser.py

#!/usr/bin/env python
# encoding: utf-8

"""
 " ScummVM - Graphic Adventure Engine
 "
 " ScummVM is the legal property of its developers, whose names
 " are too numerous to list here. Please refer to the COPYRIGHT
 " file distributed with this source distribution.
 "
 " This program is free software; you can redistribute it and/or
 " modify it under the terms of the GNU General Public License
 " as published by the Free Software Foundation; either version 2
 " of the License, or (at your option) any later version.
 "
 " This program is distributed in the hope that it will be useful,
 " but WITHOUT ANY WARRANTY; without even the implied warranty of
 " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 " GNU General Public License for more details.
 "
 " You should have received a copy of the GNU General Public License
 " along with this program; if not, write to the Free Software
 " Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 "
 " $URL$
 " $Id$
"""

from __future__ import with_statement
import os
import xml.dom.minidom as DOM
import struct

FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)])

# adapted from Activestate Snippet Cookbook
def printBinaryDump(src, length=16):
    N=0; result=''
    while src:
       s,src = src[:length],src[length:]
       hexa = ' '.join(["%02X"%ord(x) for x in s])
       s = s.translate(FILTER)
       result += "%04X   %-*s   %s\n" % (N, length*3, hexa, s)
       N+=length
    print (result)

def pbin(data):
      return str(map(lambda c: hex(ord(c)), data))
#     return " ".join(["%0.2X" % ord(c) for c in data])

00051 class STXBinaryFile(object):
00052       class InvalidRGBColor(Exception):
            pass

00055       class InvalidResolution(Exception):
            pass

00058       class InvalidFontIdentifier(Exception):
            pass

00061       class InvalidBitmapName(Exception):
            pass

00064       class InvalidDialogOverlay(Exception):
            pass

00067       class DrawStepData(object):
            def __init__(self, isDefault, packFormat, function):
                  self.isDefault = isDefault
                  self.packFormat = packFormat
                  self.parse = function

      BYTEORDER = '>' # big-endian format by default, platform independent

      TRUTH_VALUES = {"" : 0, "true" : 1, "false" : 0, "True" : 1, "False" : 0}

      BLOCK_HEADERS = {
            "bitmaps"   : 0x0100000A,
            "fonts"           : 0x0100000B,
            "cursor"    : 0x0100000C,
            "drawdata"  : 0x0100000D,

            "globals"   : 0x0200000A,
            "dialog"    : 0x0200000B,
      }

      DIALOG_shading = {"none" : 0x0, "dim" : 0x1, "luminance" : 0x2}

      DS_triangleOrientations = {"top" : 0x1, "bottom" : 0x2, "left" : 0x3, "right" : 0x4}
      DS_fillModes = {"none" : 0x0, "foreground" : 0x1, "background" : 0x2, "gradient" : 0x3}
      DS_vectorAlign = {"left" : 0x1, "right" : 0x2, "bottom" : 0x3, "top" : 0x4, "center" : 0x5}

      DS_functions = {
            "void" : 0x0, "circle" : 0x1, "square" : 0x2, "roundedsq" : 0x3, "bevelsq" : 0x4,
            "line" : 0x5, "triangle" : 0x6, "fill" : 0x7, "tab" : 0x8, "bitmap" : 0x9, "cross" : 0xA
      }

      DS_fonts = {
            "text_default" : 0x0, "text_hover" : 0x1, "text_disabled" : 0x2,
            "text_inverted" : 0x3, "text_button" : 0x4, "text_button_hover" : 0x5, "text_normal" : 0x6
      }

      DS_text_alignV = {"bottom" : 0x0, "center" : 0x1, "top" : 0x2}
      DS_text_alignH = {"left" : 0x0, "center" : 0x1, "right" : 0x2}

      DS_list = [
            "func", "fill", "stroke", "gradient_factor",
            "width", "height", "xpos", "ypos",
            "radius", "bevel", "shadow", "orientation", "file",
            "fg_color", "bg_color", "gradient_start", "gradient_end", "bevel_color"
      ]

      def __init__(self, themeName, autoLoad = True, verbose = False):
            self._themeName = themeName
            self._stxFiles = []
            self._verbose = verbose

            self.DS_data = {
            #     attribute name                                  isDefault   pack  parse function
                  "func"                        : self.DrawStepData(False,    "B",  lambda f: self.DS_functions[f]),
                  "fill"                        : self.DrawStepData(True,     "B",  lambda f: self.DS_fillModes[f]),
                  "stroke"                : self.DrawStepData(True,     "B",  int),
                  "gradient_factor"       : self.DrawStepData(True,     "B",  int),
                  "width"                       : self.DrawStepData(False,    "i",  lambda w: -1 if w == 'height' else 0 if w == 'auto' else int(w)),
                  "height"                : self.DrawStepData(False,    "i",  lambda h: -1 if h == 'width' else 0 if h == 'auto' else int(h)),
                  "xpos"                        : self.DrawStepData(False,    "i",  lambda pos: self.DS_vectorAlign[pos] if pos in self.DS_vectorAlign else int(pos)),
                  "ypos"                        : self.DrawStepData(False,    "i",  lambda pos: self.DS_vectorAlign[pos] if pos in self.DS_vectorAlign else int(pos)),
                  "radius"                : self.DrawStepData(False,    "i",  lambda r: 0xFF if r == 'auto' else int(r)),
                  "bevel"                 : self.DrawStepData(True,     "B",  int),
                  "shadow"                : self.DrawStepData(True,     "B",  int),
                  "orientation"           : self.DrawStepData(False,    "B",  lambda o: self.DS_triangleOrientations[o]),
                  "file"                        : self.DrawStepData(False,    "B",  self.__getBitmap),
                  "fg_color"              : self.DrawStepData(True,     "4s", self.__parseColor),
                  "bg_color"              : self.DrawStepData(True,     "4s", self.__parseColor),
                  "gradient_start"  : self.DrawStepData(True,     "4s", self.__parseColor),
                  "gradient_end"          : self.DrawStepData(True,     "4s", self.__parseColor),
                  "bevel_color"           : self.DrawStepData(True,     "4s", self.__parseColor)
            }

            if autoLoad:
                  if not os.path.isdir(themeName) or not os.path.isfile(os.path.join(themeName, "THEMERC")):
                        raise IOError

                  for filename in os.listdir(themeName):
                        filename = os.path.join(themeName, filename)
                        if os.path.isfile(filename) and filename.endswith('.stx'):
                              self._stxFiles.append(filename)

      def debug(self, text):
            if self._verbose: print (text)

      def debugBinary(self, data):
            if self._verbose:
                  print ("BINARY OUTPUT (%d bytes): %s" % (len(data), " ".join(["%0.2X" % ord(c) for c in data])))

      def addSTXFile(self, filename):
            if not os.path.isfile(filename):
                  raise IOError
            else:
                  self._stxFiles.append(filename)

      def parse(self):
            if not self._stxFiles:
                  self.debug("No files have been loaded for parsing on the theme.")
                  raise IOError

            for f in self._stxFiles:
                  self.debug("Parsing %s." % f)
                  with open(f) as stxFile:
                        self.__parseFile(stxFile)

      def __parseFile(self, xmlFile):
            stxDom = DOM.parse(xmlFile)

            for layout in stxDom.getElementsByTagName("layout_info"):
                  self.__parseLayout(layout)

            for render in stxDom.getElementsByTagName("render_info"):
                  self.__parseRender(render)

            stxDom.unlink()

      def __getBitmap(self, bmp):
            bmp = str(bmp)

            if bmp == "":
                  return 0x0
            if bmp not in self._bitmaps:
                  raise self.InvalidBitmapName

            return self._bitmaps[bmp]

      def __parseDrawStep(self, drawstepDom, localDefaults = {}):

            dstable = {}

            if drawstepDom.tagName == "defaults":
                  isGlobal = drawstepDom.parentNode.tagName == "render_info"

                  for ds in self.DS_list:
                        if self.DS_data[ds].isDefault and drawstepDom.hasAttribute(ds):
                              dstable[ds] = self.DS_data[ds].parse(drawstepDom.getAttribute(ds))

                        elif isGlobal:
                              dstable[ds] = None

            else:
                  for ds in self.DS_data:
                        if drawstepDom.hasAttribute(ds):
                              dstable[ds] = self.DS_data[ds].parse(drawstepDom.getAttribute(ds))
                        elif self.DS_data[ds].isDefault:
                              dstable[ds] = localDefaults[ds] if ds in localDefaults else self._globalDefaults[ds]
                        else:
                              dstable[ds] = None

            return dstable


00219       def __parseDrawStepToBin(self, stepDict):
            """
                  /BBBBiiiiiBBB4s4s4s4s4sB/ ==
                  function          (byte)
                  fill              (byte)
                  stroke                  (byte)
                  gradient_factor   (byte)
                  width             (int32)
                  height                  (int32)
                  xpos              (int32)
                  ypos              (int32)
                  radius                  (int32)
                  bevel             (byte)
                  shadow                  (byte)
                  orientation       (byte)
                  file              (byte)
                  fg_color          (4 byte)
                  bg_color          (4 byte)
                  gradient_start    (4 byte)
                  gradient_end      (4 byte)
                  bevel_color       (4 byte)
            """

            packLayout = ""
            packData = []

            for ds in self.DS_list:
                  layout = self.DS_data[ds].packFormat
                  data = stepDict[ds]

                  if not data:
                        size = struct.calcsize(layout)
                        packLayout += "B" * size

                        for d in range(size):
                              packData.append(0)
                  else:
                        packLayout += layout
                        packData.append(data)


            stepBin = struct.pack(self.BYTEORDER + packLayout, *tuple(packData))
            return stepBin


00264       def __parseResolutionToBin(self, resString):
            """
                  /B bHH bHH bHH/ == 1 byte + x * 9 bytes
                  number of resolution sections (byte)
                  exclude resolution (byte)
                  resolution X (half)
                  resolution Y (half)
            """

            if resString == "":
                  return struct.pack(self.BYTEORDER + "BbHH", 1, 0, 0, 0)

            resolutions = resString.split(", ")
            packFormat = "B" + "bHH" * len(resolutions)
            packData = [len(resolutions)]

            for res in resolutions:
                  exclude = 0
                  if res[0] == '-':
                        exclude = 1
                        res = res[1:]

                  try:
                        x, y = res.split('x')
                        x = 0 if x == 'X' else int(x)
                        y = 0 if y == 'Y' else int(y)
                  except ValueError:
                        raise InvalidResolution

                  packData.append(exclude)
                  packData.append(x)
                  packData.append(y)

            buff = struct.pack(self.BYTEORDER + packFormat, *tuple(packData))
            return buff

00300       def __parseRGBToBin(self, color):
            """
                  /xBBB/ == 32 bits
                  padding (byte)
                  red color (byte)
                  green color (byte)
                  blue color (byte)
            """

            try:
                  rgb = tuple(map(int, color.split(", ")))
            except ValueError:
                  raise self.InvalidRGBColor

            if len(rgb) != 3:
                  raise self.InvalidRGBColor

            for c in rgb:
                  if c < 0 or c > 255:
                        raise self.InvalidRGBColor

            rgb = struct.pack(self.BYTEORDER + "xBBB", *tuple(rgb))

#           self.debugBinary(rgb)
            return rgb

      def __parseColor(self, color):
            try:
                  color = self.__parseRGBToBin(color)
            except self.InvalidRGBColor:
                  if color not in self._colors:
                        raise self.InvalidRGBColor
                  color = self._colors[color]

            return color


      def __parsePalette(self, paletteDom):
            self._colors = {}

            for color in paletteDom.getElementsByTagName("color"):
                  color_name = color.getAttribute('name')
                  color_rgb = self.__parseRGBToBin(color.getAttribute('rgb'))

                  self._colors[color_name] = color_rgb
#                 self.debug("COLOR: %s" % (color_name))


      def __parseBitmaps(self, bitmapsDom):
            self._bitmaps = {}
            idCount = 0xA0
            packLayout = ""
            packData = []

            for bitmap in bitmapsDom.getElementsByTagName("bitmap"):
                  bmpName = str(bitmap.getAttribute("filename"))
                  self._bitmaps[bmpName] = idCount

                  packLayout += "B%ds" % (len(bmpName) + 1)
                  packData.append(idCount)
                  packData.append(bmpName)
                  idCount += 1


            bitmapBinary = struct.pack(
                  self.BYTEORDER + "IB" + packLayout,
                  self.BLOCK_HEADERS['bitmaps'],
                  len(bitmapsDom.getElementsByTagName("bitmap")),
                  *tuple(packData)
            )

#           self.debug("BITMAPS:\n%s\n\n" % pbin(bitmapBinary))
            return bitmapBinary

00374       def __parseFonts(self, fontsDom):
            """
                  /IB Bs4ss .../
                  section header (uint32)
                  number of font definitions (byte)

                  id for font definition (byte)
                  resolution for font (byte array)
                  color for font (4 bytes)
                  font filename (byte array, null terminated)
            """

            packLayout = ""
            packData = []

            for font in fontsDom.getElementsByTagName("font"):
                  ident = font.getAttribute("id")

                  if ident not in self.DS_fonts:
                        raise self.InvalidFontIdentifier

                  color = self.__parseColor(font.getAttribute("color"))
                  filename = str(font.getAttribute("file"))

                  if filename == 'default':
                        filename = ''

                  resolution = self.__parseResolutionToBin(font.getAttribute("resolution"))

                  packLayout += "B%ds4s%ds" % (len(resolution), len(filename) + 1)
                  packData.append(self.DS_fonts[ident])
                  packData.append(resolution)
                  packData.append(color)
                  packData.append(filename)

            fontsBinary = struct.pack(self.BYTEORDER + \
                  "IB" + packLayout,
                  self.BLOCK_HEADERS['fonts'],
                  len(fontsDom.getElementsByTagName("font")),
                  *tuple(packData)
            )


#           self.debug("FONTS DATA:\n%s\n\n" % pbin(fontsBinary))
            return fontsBinary

00420       def __parseTextToBin(self, textDom):
            """
                  /BBBx/
                  font identifier (byte)
                  vertical alignment (byte)
                  horizontal alignment (byte)
                  padding until word (byte)
            """

            font = textDom.getAttribute("font")
            if font not in self.DS_fonts:
                  raise self.InvalidFontIdentifier

            textBin = struct.pack(self.BYTEORDER + "BBBx",
                  self.DS_fonts[font],
                  self.DS_text_alignV[textDom.getAttribute("vertical_align")],
                  self.DS_text_alignH[textDom.getAttribute("horizontal_align")]
            )

            return textBin

00441       def __parseDrawData(self, ddDom):
            """
                  /IsIBBHss/
                        Section Header (uint32)
                        Resolution (byte array, word-aligned)
                        DrawData id hash (uint32)
                        Cached (byte)
                        has text section? (byte)
                        number of DD sections (uint16)
                        ** text segment (4 bytes)
                        drawstep segments (byte array)
            """


            localDefaults = ddDom.getElementsByTagName("defaults")
            localDefaults = localDefaults[0] if localDefaults else {}

            stepList = []

            for ds in ddDom.getElementsByTagName("drawstep"):
                  dstable = self.__parseDrawStep(ds, localDefaults)
                  dsbinary = self.__parseDrawStepToBin(dstable)

                  stepList.append(dsbinary)

            stepByteArray = "".join(stepList)

            resolution = self.__parseResolutionToBin(ddDom.getAttribute("resolution"))

            text = ddDom.getElementsByTagName("text")
            text = self.__parseTextToBin(text[0]) if text else ""

            id_hash = str.__hash__(str(ddDom.getAttribute("id"))) & 0xFFFFFFFF
            cached = self.TRUTH_VALUES[ddDom.getAttribute("cached")]

            ddBinary = struct.pack(self.BYTEORDER + \
                  "I%dsIBBH4s%ds" % (len(resolution), len(stepByteArray)),

                  self.BLOCK_HEADERS['drawdata'], # Section Header (uint32)
                  resolution,                               # Resolution (byte array, word-aligned)
                  id_hash,                                  # DrawData id hash (uint32)
                  cached,                                         # Cached (byte)
                  0x1 if text else 0x0,               # has text section? (byte)
                  len(stepList),                            # number of DD sections (uint16)
                  text,                                     # ** text segment (byte array)
                  stepByteArray                             # drawstep segments (byte array)
            )

#           self.debug("DRAW DATA %s (%X): \n" % (ddDom.getAttribute("id"), id_hash) + pbin(ddBinary) + "\n\n")
            return ddBinary

00492       def __parseCursor(self, cursorDom):
            """
                  /IsBBhh/
                  section header (uint32)
                  resolution string (byte array)
                  bitmap id (byte)
                  scale (byte)
                  hotspot X (half)
                  hotspot Y (half)
            """

            resolution = self.__parseResolutionToBin(cursorDom.getAttribute("resolution"))
            scale = int(cursorDom.getAttribute("scale"))
            hsX, hsY = cursorDom.getAttribute("hotspot").split(", ")

            cursorBin = struct.pack(self.BYTEORDER + "I%dsBBhh" % len(resolution),
                  self.BLOCK_HEADERS['cursor'],
                  resolution,
                  self.__getBitmap(cursorDom.getAttribute("file")),
                  scale,
                  int(hsX),
                  int(hsY)
            )

#           self.debug("CURSOR:\n%s\n\n" % pbin(cursorBin))
            return cursorBin

      def __parseDialog(self, dialogDom):

            dialog_id = str(dialogDom.getAttribute("name"))
            resolution = self.__parseResolutionToBin(dialogDom.getAttribute("resolution"))

            overlays = str(dialogDom.getAttribute("overlays"))
            overlay_type = 0x0
            overlay_parent = ""

            if overlays == "screen":
                  overlay_type = 0x1
            elif overlays == "screen_center":
                  overlay_type = 0x2
            else:
                  overlay_type = 0x3
                  overlay_parent = str(overlays)

            dialog_enabled = 0x1
            if dialogDom.hasAttribute("enabled"):
                  dialog_enabled = self.TRUTH_VALUES[dialogDom.getAttribute("enabled")]

            dialog_shading = 0x0
            if dialogDom.hasAttribute("shading"):
                  dialog_shading = self.DIALOG_shading[dialogDom.getAttribute("shading")]

            dialog_inset = 0
            if dialogDom.hasAttribute("inset"):
                  dialog_inset = int(dialogDom.getAttribute("inset"))

            dialogBin = struct.pack(self.BYTEORDER + \
                  "I%ds%dsBBBB%ds" % (len(resolution), len(dialog_id) + 1, len(overlay_parent) + 1),
                  self.BLOCK_HEADERS['dialog'],
                  resolution,
                  dialog_id,
                  dialog_enabled,
                  dialog_shading,
                  dialog_inset,
                  overlay_type,
                  overlay_parent,
            )

            return dialogBin

      def __parseLayout(self, layoutDom):
            self.debug("GLOBAL SECTION: LAYOUT INFO.")

            dialogBIN = ""

            for dialog in layoutDom.getElementsByTagName("dialog"):
                  dialogBIN += self.__parseDialog(dialog)


            printBinaryDump(dialogBIN)

            return dialogBIN

      def __parseRender(self, renderDom):
            self.debug("GLOBAL SECTION: RENDER INFO.")

            bitmapBIN = ""
            fontsBIN = ""
            cursorBIN   = ""
            drawdataBIN = ""

            # parse color palettes
            paletteDom = renderDom.getElementsByTagName("palette")
            if paletteDom:
                  self.__parsePalette(paletteDom[0])

            # parse bitmaps
            bitmapsDom = renderDom.getElementsByTagName("bitmaps")
            if bitmapsDom:
                  bitmapBIN = self.__parseBitmaps(bitmapsDom[0])

            # parse fonts
            fontsDom = renderDom.getElementsByTagName("fonts")[0]
            fontsBIN = self.__parseFonts(fontsDom)

            # parse defaults
            defaultsDom = renderDom.getElementsByTagName("defaults")
            if defaultsDom:
                  self._globalDefaults = self.__parseDrawStep(defaultsDom[0])
            else:
                  self._globalDefaults = {}

            # parse cursors
            for cur in renderDom.getElementsByTagName("cursor"):
                  cursorBIN += self.__parseCursor(cur)

            # parse drawdata sets
            for dd in renderDom.getElementsByTagName("drawdata"):
                  drawdataBIN += self.__parseDrawData(dd)


            renderInfoBIN = bitmapBIN + fontsBIN + cursorBIN + drawdataBIN
            printBinaryDump(renderInfoBIN)

            return renderInfoBIN

if __name__ == '__main__':
      bin = STXBinaryFile('../gui/themes/scummclassic', True, True)
      bin.parse()


Generated by  Doxygen 1.6.0   Back to index