#!/usr/bin/env python
# Portions Copyright (C) 2001-2018 Artifex Software Inc.
#   
#   This software is distributed under license and may not be copied, modified
#   or distributed except as expressly authorized under the terms of that
#   license.  Refer to licensing information at http://www.artifex.com/ or
#   contact Artifex Software, Inc., 1305 Grant Avenue - Suite 200,
#   Novato, CA 94945, (415)492-9861, for further information.
#
# TODO
# array data should be wrapped.
#
# text data should be printed as a string not an array of ascii values.
#
# enumerations should printed we now print the ordinal value of the enumeration.
#
# make self.unpack endian like with binding

# DIFFS between HP
# Artifex reports the file offset of each operator HP does not.

#Python 3 compat mode for Python2 must be the first line (without conditionals):
# 'from __future__ imports must occur at the beginning of the file'
from __future__ import print_function, unicode_literals

import sys

if sys.version < '3':
    def chr_(x):
        return x
    def ord_(x):
        return ord(x)
    def unpackB(x):
        return unpack(b'B', x)[0]
    def b(x):
        return x.encode()
else:
    def chr_(x):
        return chr(x)
    def ord_(x):
        return x
    def unpackB(x):
        return x
    def b(x):
        return x

import string 

# for packing and unpacking binary data
from struct import *

# tags
pxl_tags_dict = {                                  
    'ArcPath' :                 0x91,                  
    'BeginChar' :               0x52,
    'BeginFontHeader' :         0x4f,
    'BeginImage' :              0xb0,
    'BeginPage' :               0x43,
    'BeginRastPattern' :        0xb3,
    'BeginScan' :               0xb6,
    'BeginSession' :            0x41,
    'BeginStream' :             0x5b,
    'BeginUserDefinedLineCap' : 0x82,
    'BezierPath' :              0x93,
    'BezierRelPath' :           0x95,
    'Chord' :                   0x96,
    'ChordPath' :               0x97,
    'CloseDataSource' :         0x49,
    'CloseSubPath' :            0x84,
    'Comment' :                 0x47,
    'Ellipse' :                 0x98,
    'EllipsePath' :             0x99,
    'EndChar' :                 0x54,
    'EndFontHeader' :           0x51,
    'EndImage' :                0xb2,
    'EndPage' :                 0x44,
    'EndRastPattern' :          0xb5,
    'EndScan' :                 0xb8,
    'EndSession' :              0x42,
    'EndStream' :               0x5d,
    'EndUserDefinedLineCaps' :  0x83,
    'ExecStream' :              0x5e,
    'LinePath' :                0x9b,
    'LineRelPath' :             0x9d,
    'NewPath' :                 0x85,
    'OpenDataSource' :          0x48,
    'PaintPath' :               0x86,
    'Passthrough' :             0xbf,
    'Pie' :                     0x9e,
    'PiePath' :                 0x9f,
    'PopGS' :                   0x60,
    'PushGS' :                  0x61,
    'ReadChar' :                0x53,
    'ReadFontHeader' :          0x50,
    'ReadImage' :               0xb1,
    'ReadRastPattern' :         0xb4,
    'ReadStream' :              0x5c,
    'Rectangle' :               0xa0,
    'RectanglePath' :           0xa1,
    'RemoveFont' :              0x55,
    'RemoveStream' :            0x5f,
    'RoundRectangle' :          0xa2,
    'RoundRectanglePath' :      0xa3,
    'ScanLineRel' :             0xb9,
    'SetAdaptiveHalftoning' :   0x94,
    'SetBrushSource' :          0x63,
    'SetCharAttributes' :       0x56,
    'SetCharAngle' :            0x64,
    'SetCharBoldValue' :        0x7d,
    'SetCharScale' :            0x65,
    'SetCharShear' :            0x66,
    'SetCharSubMode' :          0x81,
    'SetClipIntersect' :        0x67,
    'SetClipMode' :             0x7f,
    'SetClipRectangle' :        0x68,
    'SetClipReplace' :          0x62,
    'SetClipToPage' :           0x69,
    'SetColorSpace' :           0x6a,
    'SetColorTrapping' :        0x92,
    'SetColorTreatment' :       0x58,
    'SetCursor' :               0x6b,
    'SetCursorRel' :            0x6c,
    'SetDefaultGS' :            0x57,
    'SetHalftoneMethod' :       0x6d,
    'SetFillMode' :             0x6e,
    'SetFont' :                 0x6f,
    'SetHalftoneMethod' :       0x6d,
    'SetLineCap' :              0x71,
    'SetLineDash' :             0x70,
    'SetLineJoin' :             0x72,
    'SetMiterLimit' :           0x73,
    'SetNeutralAxis' :          0x7e,
    'SetPageDefaultCTM' :       0x74,
    'SetPageOrigin' :           0x75,
    'SetPageRotation' :         0x76,
    'SetPageScale' :            0x77,
    'SetPathToClip' :           0x80,
    'SetPatternTxMode' :        0x78,
    'SetPenSource' :            0x79,
    'SetPenWidth' :             0x7a,
    'SetROP' :                  0x7b,
    'SetSourceTxMode' :         0x7c,
    'Text' :                    0xa8,
    'TextPath' :                0xa9,
    'VendorUnique' :            0x46,
    'attr_ubyte' :              0xf8,
    'attr_uint16' :             0xf9,
    'embedded_data' :           0xfa,
    'embedded_data_byte' :      0xfb,
    'real32' :                  0xc5,
    'real32_array' :            0xcd,
    'real32_box' :              0xe5,
    'real32_xy' :               0xd5,
    'sint16' :                  0xc3,
    'sint16_array' :            0xcb,
    'sint16_box' :              0xe3,
    'sint16_xy' :               0xd3,
    'sint32' :                  0xc4,
    'sint32_array' :            0xcc,
    'sint32_box' :              0xe4,
    'sint32_xy' :               0xd4,
    'ubyte' :                   0xc0,
    'ubyte_array' :             0xc8,
    'ubyte_box' :               0xe0,
    'ubyte_xy' :                0xd0,
    'uint16' :                  0xc1,
    'uint16_array' :            0xc9,
    'uint16_box' :              0xe1,
    'uint16_xy' :               0xd1,
    'uint32' :                  0xc2,
    'uint32_array' :            0xca,
    'uint32_box' :              0xe2,
    'uint32_xy' :               0xd2
}

pxl_enumerations_dict = {
    'ArcDirection' : [ 'eClockWise=0',  'eCounterClockWise=1' ],
    'BackCh' : ['eErrorPage=0'], # deprecated.
    'CharSubModeArray' : [ 'eNoSubstitution=0', 'eVerticalSubstitution=1' ],
    'ClipMode' : ['eNonZeroWinding=0', 'eEvenOdd=1' ],
    'ClipRegion' : ['eInterior=0', 'eExterior=1'],
    'ColorDepth' : [ 'e1Bit=0', 'e4Bit=1', 'e8Bit=2' ],
    'ColorMapping' : [ 'eDirectPixel=0', 'eIndexedPixel=1' ],
    'ColorSpace' : [ 'eGray=1', 'eRGB=2', 'eSRGB=6', 'eGraySub=7'], # srgb deprecated
    'ColorTreatment' : [ 'eNoTreatment=0', 'eScreenMatch=1', 'eVivid=2' ],
    'CompressMode' : [ 'eNoCompression=0', 'eRLECompression=1',
                       'eJPEGCompression=2', 'eDeltaRowCompression=3' ],
    'DataOrg' : [ 'eBinaryHighByteFirst=0', 'eBinaryLowByteFirst=1' ],
    'DataSource' : [ 'eDefault=0' ],
    'DataType' : [ 'eUByte=0', 'eSByte=1', 'eUint16=2', 'eSint16=3' ],
    'DitherMatrix' : [ 'eDeviceBest=0' ],
    'DuplexPageMode' : [ 'eDuplexHorizontalBinding=0', 'eDuplexVerticalBinding=1' ],
    'DuplexPageSide' : [ 'eFrontMediaSide=0', 'eBackMediaSide=1' ],
    'ErrorReport' : ['eNoReporting=0', 'eBackChannel=1', 'eErrorPage=2',
                     'eBackChAndErrPage=3', 'eNWBackChannel=4', 'eNWErrorPage=5',
                     'eNWBackChAndErrPage=6' ],
    'FillMode' : ['eNonZeroWinding=0', 'eEvenOdd=1' ],
    'LineJoineMiterJoin' : [ 'eRoundJoin=0', 'eBevelJoin=1', 'eNoJoin=2' ],
    'MediaSource' : [ 'eDefaultSource=0', 'eAutoSelect=1', 'eManualFeed=2',
                      'eMultiPurposeTray=3', 'eUpperCassette=4', 'eLowerCassette=5',
                      'eEnvelopeTray=6', 'eThirdCassette=7', 'External Trays=8-255' ],
    'MediaDestination' :  [ 'eDefaultDestination=0', 'eFaceDownBin=1', 'eFaceUpBin=2',
                            'eJobOffsetBin=3', 'External Bins=5-255' ],
    'LineCapStyle' : [ 'eButtCap=0', 'eRoundCap=1', 'eSquareCap=2', 'eTriangleCap=3' ],
    'LineJoin' : [ 'eMiterJoin=0', 'eRoundJoin=1', 'eBevelJoin=2', 'eNoJoin=3' ],
    'Measure' : [ 'eInch=0', 'eMillimeter=1', 'eTenthsOfAMillimeter=2' ],
    'MediaSize' : [ 'eDefault=96', 'eLetterPaper=0', 'eLegalPaper=1', 'eA4Paper=2',
                    'eExecPaper=3', 'eLedgerPaper=4', 'eA3Paper=5',
                    'eCOM10Envelope=6', 'eMonarchEnvelope=7', 'eC5Envelope=8',
                    'eDLEnvelope=9', 'eJB4Paper=10', 'eJB5Paper=11', 'eB5Paper=13',
                    'eB5Envelope=12', 'eJPostcard=14', 'eJDoublePostcard=15',
                    'eA5Paper=16', 'eA6Paper=17', 'eJBPaper=18', 'JIS8K=19',
                    'JIS16K=20', 'JISExec=21' ],
    'Orientation' : ['ePortraitOrientation=0', 'eLandscapeOrientation=1',
                     'eReversePortrait=2', 'eReverseLandscape=3',
                     'eDefaultOrientation=4' ],
    'PatternPersistence' : [ 'eTempPattern=0', 'ePagePattern=1', 'eSessionPattern=2'],
    'SimplexPageMode' : ['eSimplexFrontSide=0'],
    'TxMode' : [ 'eOpaque=0', 'eTransparent=1' ],
    'VUExtension' : [
        'HP_ColorSmartRGB=68186216',           #0x04107068
        'HP_ColorSmartRGB=1752174596',         #0x68701004
        'HP_SelectTrayBinByString=1752178693', #0x68702005
        'EnterFRMode=1752178694',              #0x68702006
        'FRExtension=1752178705',              #0x68702011
        'JR3BeginImage=1752186880',            #0x68704000
        'JR3ReadImage=1752186881' ,            #0x68704001
        'JR3EndImage=1752186882'  ,            #0x68704002
        'JR3ExecStream=1752186883',            #0x68704003
        ],
    'WritingMode' : [ 'eHorizontal=0', 'eVertical=1' ]
}

# see appendix F
pxl_attribute_name_to_attribute_number_dict = { 
    'AllObjectTypes' : 29,
    'ArcDirection' : 65,
    'BlockByteLength' : 111,
    'BlockHeight' : 99,
    'BoundingBox' : 66,
    'ColorimetricColorSpace': 17, # deprecated
    'CharAngle' : 161,
    'CharBoldValue' : 177,
    'CharCode' : 162,
    'CharDataSize' : 163,
    'CharScale' : 164,
    'CharShear' : 165,
    'CharSize' : 166,
    'CharSubModeArray' : 172,
    'ClipMode' : 84,
    'ClipRegion' : 83,
    'ColorDepth' : 98,
    'ColorMapping' : 100,
    'ColorSpace' : 3,
    'ColorTreatment' : 120,
    'CommentData' : 129,
    'CompressMode' : 101,
    'ControlPoint1' : 81,
    'ControlPoint2' : 82,
    'CRGBMinMax' : 20, # deprecated
    'CustomMediaSize' : 47,
    'CustomMediaSizeUnits' : 48,
    'DashOffset' : 67,
    'DataOrg' : 130,
    'DestinationBox' : 102,
    'DestinationSize' : 103,
    'DeviceMatrix' : 33,
    'DitherMatrixDataType' : 34,
    'DitherMatrixDepth' : 51,
    'DitherMatrixSize' : 50,
    'DitherOrigin' : 35,
    'DuplexPageMode' : 53,
    'DuplexPageSide' : 54,
    'EllipseDimension' : 68,
    'EndPoint' : 69,
    'ErrorReport' : 143,
    'FillMode' : 70,
    'FontFormat' : 169,
    'FontHeaderLength' : 167,
    'FontName' : 168,
    'GammaGain' : 21, # deprecated.
    'GrayLevel' : 9,
    'LineCapStyle' : 71,
    'LineDashStyle' : 74,
    'LineJoinStyle' : 72,
    'Measure' : 134,
    'MediaDestination' : 36,
    'MediaSize' : 37,
    'MediaSource' : 38,
    'MediaType' : 39,
    'MiterLength' : 73,
    'NewDestinationSize' : 13,
    'NullBrush' : 4,
    'NullPen' : 5,
    'NumberOfPoints' : 77,
    'NumberOfScanLines' : 115,
    'Orientation' : 40,
    'PCLSelectFont' : 141,
    'PadBytesMultiple' : 110,
    'PageAngle' : 41,
    'PageCopies' : 49,
    'PageOrigin' : 42,
    'PageScale' : 43,
    'PaletteData' : 6,
    'PaletteDepth' : 2,
    'PatternDefineID' : 105,
    'PatternOrigin' : 12,
    'PatternPersistence' : 104,
    'PatternSelectID' : 8,
    'PenWidth' : 75,
    'Point' : 76,
    'PointType' : 80,
    'PrimaryArray' : 14,
    'PrimaryDepth' : 15,
    'PrintableArea' : 116,
    'RGBColor' : 11,
    'ROP3' : 44,
    'RasterObjects' : 32,
    'SimplexPageMode' : 52,
    'SolidLine' : 78,
    'SourceHeight' : 107,
    'SourceType' : 136,
    'SourceWidth' : 108,
    'StartLine' : 109,
    'StartPoint' : 79,
    'StreamDataLength' : 140,
    'StreamName' : 139,
    'SymbolSet' : 170,
    'TextData' : 171,
    'TextObjects' : 30,
    'TxMode' : 45,
    'UnitsPerMeasure' : 137,
    'VectorObjects' : 31,
    'VUExtension' : 145,
    'VUDataLength' : 146,
    'VUAttr1' : 147,
    'VUAttr2' : 148,
    'VUAttr3' : 149,
    'VUAttr4' : 150,
    'VUAttr5' : 151,
    'VUAttr6' : 152,
    'WhiteReferencePoint' : 19, # deprecated.
    'WritingMode' : 173, # deprecated.
    'XSpacingData' : 175, 
    'XYChromaticities' : 18, # deprecated.
    'YSpacingData' : 176,
}

printables = ''.join([(len(repr(chr(x)))==3) and (x != 47) and (x < 128) and chr(x) or '.' for x in range(256)])

class pxl_dis:

    def __init__(self, data):

        # the class does not handle pjl, work around that here.  NB
        # should check for little endian protocol but we haven't seen
        # it used yet.
        index = data.index(b") HP-PCL XL;")
        
        # copy of the data without the PJL
        data = data[index:]
        self.data = data
        
        # parse out data order and protocol
        self.binding = chr_(data[0])
        self.protocol = chr_(data[12])
        self.revision = chr_(data[14])

        # check binding NB - should check other stuff too:
        # example: )<SP>HP-PCL XL;2;0<CR><LF>
        if self.binding not in ['(', ')']:
            if (self.binding == '`'):
                print("The PXL code is already ascii encoded\n", file=sys.stderr)
            raise(SyntaxError)

        # replace with python's struct endian flag.
        if (self.binding == ')'):
            # little endian
            self.binding = '<'
        else:
            self.binding = '>'
        
        # save the what we skipped over so we can record file offsets
        self.skipped_over = index
        # pointer to data
        self.index = 0
        # graphic state number of pushes - number of pops
        self.graphics_state_level = 0
        # print out ascii protocol and revision.  NB should check
        # revisions are the same.
        print("` HP-PCL XL;" + self.protocol + ";" + self.revision)
        # saved size of last array parsed
        self.size_of_element = -1;
        self.size_of_array = -1;
        self.unpack_string = ""

        # skip over protocol and revision
        while( chr_(data[self.index]) != '\n' ):
            self.index = self.index + 1
        self.index = self.index + 1

        # dictionary of streams keyed by stream name
        self.user_defined_streams = {}

        # the n'th operator in the stream
        self.operator_position = 0

        # The VendorUnique operator can have an optional embedded payload
        # where the length is stored in the VUDataLength attribute.
        # We need to cache the value of that atttribute to skip over
        # that many number of bytes when it is encountered.
        self.VUDataLength = 0 

        # true if we get UEL
        self.endjob = 0
        
    def nullAttributeList(self):
        return 0
    
    # redefine unpack to handle endiannes
    def unpack(self, format, data):

        # prepend the binding to specify the endianness of the XL
        # stream and standard format, not native. (see struct.py docs)
        return unpack((b(self.binding) + b(format)), data)

    # implicitly read when parsing the tag
    def attributeIDValue(self):
        return 1

    def findTagKey(self, tag):
        for key in pxl_tags_dict:
            if ( pxl_tags_dict[key] == tag ):
                return key
        return 0

    # get the next operator
    def operatorTag(self):
        tag = unpackB(self.data[self.index])
        key = self.findTagKey(tag)
        if (not key):
            return 0
        if ( key == "PopGS" ):
            self.graphics_state_level = self.graphics_state_level - 1
        if ( key == "PushGS" ):
            self.graphics_state_level = self.graphics_state_level + 1
               
        if (not self.isEmbedded(key)):
            self.operator_position = self.operator_position + 1
        print("// Op Pos: %d " % self.operator_position, end=' ')
        print("Op fOff: %d " % (self.index + self.skipped_over), end=' ')
        print("Op Hex: %X " % tag, end=' ')
        print("Level: %d" % self.graphics_state_level)
        print(key)
        self.index = self.index + 1
                # handle special cases
        if (self.isEmbedded(key) or (key == 'VendorUnique')):
            self.process_EmbeddedInfo(key)
        return 1

    def Tag_ubyte(self):
        new_tag = unpackB(self.data[self.index])
        if ( new_tag == pxl_tags_dict['ubyte'] ):
            self.index = self.index + 1
            print("ubyte", end=' ')
            self.unpack_string = 'B'
            self.size_of_element = 1
            return 1
        return 0

    def Tag_sint16(self):
        new_tag = unpackB(self.data[self.index])
        if ( new_tag == pxl_tags_dict['sint16'] ):
            self.index = self.index + 1
            print("sint16", end=' ')
            self.unpack_string = 'h'
            self.size_of_element = 2
            return 1
        return 0

    def Tag_uint16(self):
        new_tag = unpackB(self.data[self.index])
        if ( new_tag == pxl_tags_dict['uint16'] ):
            self.index = self.index + 1
            print("uint16", end=' ')
            self.unpack_string = 'H'
            self.size_of_element = 2
            return 1
        return 0

    def Tag_sint32(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['sint32'] ):
            self.index = self.index + 1
            print("sint32", end=' ')
            self.unpack_string = 'i'
            self.size_of_element = 4
            return 1
        return 0

    def Tag_uint32(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['uint32'] ):
            self.index = self.index + 1

            print("uint32", end=' ')
            self.unpack_string = 'I'
            self.size_of_element = 4
            return 1
        return 0

    def Tag_real32(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['real32'] ):
            self.index = self.index + 1
            print("real32", end=' ')
            self.unpack_string = 'f'
            self.size_of_element = 4
            return 1
        return 0

    def Tag_ubyte_array(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['ubyte_array'] ):
            self.index = self.index + 1
            self.unpack_string = 'B'
            self.size_of_element = 1
            print("ubyte_array [", end=' ')
            return 1
        return 0

    def Tag_uint16_array(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['uint16_array'] ):
            self.index = self.index + 1
            self.unpack_string = 'H'
            self.size_of_element = 2
            print("uint16_array [", end=' ')
            return 1
        return 0

    def Tag_sint16_array(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['sint16_array'] ):
            self.index = self.index + 1
            self.unpack_string = 'h'
            self.size_of_element = 2
            print("sint16_array [", end=' ')
            return 1
        return 0

    def Tag_uint32_array(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['uint32_array'] ):
            self.index = self.index + 1
            self.unpack_string = 'I'
            self.size_of_element = 4
            print("uint32_array [", end=' ')
            return 1
        return 0

    def Tag_sint32_array(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['sint32_array'] ):
            self.index = self.index + 1
            self.unpack_string = 'i'
            self.size_of_element = 4
            print("sint32_array [", end=' ')
            return 1
        return 0

    def Tag_real32_array(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['real32_array'] ):
            self.index = self.index + 1
            self.unpack_string = 'f'
            self.size_of_element = 4
            print("real32_array [", end=' ')
            return 1
        return 0
    
    def Tag_ubyte_xy(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['ubyte_xy'] ):
            self.index = self.index + 1

            print("ubyte_xy %d %d" % \
                  self.unpack('BB', self.data[self.index:self.index+2]), end=' ')
            self.index = self.index + 2
            return 1
        return 0

    def Tag_uint16_xy(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['uint16_xy'] ):
            self.index = self.index + 1

            print("uint16_xy %d %d" % \
                  self.unpack('HH', self.data[self.index:self.index+4]), end=' ')
            self.index = self.index + 4
            return 1
        return 0

    def Tag_sint16_xy(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['sint16_xy'] ):
            self.index = self.index + 1

            print("sint16_xy %d %d" % \
                  self.unpack('hh', self.data[self.index:self.index+4]), end=' ')
            self.index = self.index + 4
            return 1
        return 0

    def Tag_uint32_xy(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['uint32_xy'] ):
            self.index = self.index + 1

            print("uint32_xy" % \
                  self.unpack('II', self.data[self.index:self.index+8]), end=' ')
            self.index = self.index + 8
            return 1
        return 0

    def Tag_sint32_xy(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['sint32_xy'] ):
            self.index = self.index + 1

            print("sint32_xy %d %d" % \
                  self.unpack('ii', self.data[self.index:self.index+8]), end=' ')
            self.index = self.index + 8
            return 1
        return 0

    def Tag_real32_xy(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['real32_xy'] ):
            self.index = self.index + 1

            print("real32_xy %f %f" % \
                  self.unpack('ff', self.data[self.index:self.index+8]), end=' ')
            self.index = self.index + 8
            return 1
        return 0
    
    def Tag_ubyte_box(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['ubyte_box'] ):
            self.index = self.index + 1

            print("ubyte_box %d %d %d %d" % \
                  self.unpack('BBBB', self.data[self.index:self.index+4]), end=' ')
            self.index = self.index + 4
            return 1
        return 0

    def Tag_uint16_box(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['uint16_box'] ):
            self.index = self.index + 1
            print("uint16_box %d %d %d %d" % \
                  self.unpack('hhhh', self.data[self.index:self.index+8]), end=' ')
            self.index = self.index + 8
            return 1
        return 0

    def Tag_sint16_box(self):
        new_tag = unpackB(self.data[self.index])
        if ( new_tag == pxl_tags_dict['sint16_box'] ):
            self.index = self.index + 1
            print("sint16_box %d %d %d %d" % \
                  self.unpack('hhhh', self.data[self.index:self.index+8]))
            self.index = self.index + 8
            return 1
        return 0

    def Tag_uint32_box(self):
        new_tag = unpackB(self.data[self.index])
        if ( new_tag == pxl_tags_dict['uint32_box'] ):
            self.index = self.index + 1
            print("uint32_box %d %d %d %d" % \
                  self.unpack('IIII', self.data[self.index:self.index+16]))
            self.index = self.index + 32
            return 1
        return 0

    def Tag_sint32_box(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['sint32_box'] ):
            self.index = self.index + 1

            print("sint32_box %d %d %d %d" % \
                  self.unpack('iiii', self.data[self.index:self.index+16]))
            self.index = self.index + 16
            return 1
        return 0

    def Tag_real32_box(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['real32_box'] ):
            self.index = self.index + 1

            print("real32_box %f %f %f %f" % \
                  self.unpack('ffff', self.data[self.index:self.index+16]))
            self.index = self.index + 16
            return 1
        return 0
    

    def dump(self, src):
        N=0
        result=[]
        while src:
            s,src = src[:16],src[16:]
            hexa = ' '.join(["%02X"% ord_(x) for x in s])
            s = ''.join(printables[ord_(x)] for x in s)
            result += "%04X   %-*s   %s\n" % (N, 16*3, hexa, s)
            N+=16
        return ''.join(result)


    # check for embedded tags.
    def isEmbedded(self, name):
        return ( name == 'embedded_data' or name == 'embedded_data_byte')

    def process_EmbeddedInfo(self, name):
        if ( name == 'embedded_data' ):
            length = self.unpack('I', self.data[self.index:self.index+4])[0]
            self.index = self.index + 4
        if ( name == 'embedded_data_byte' ):
            length = int(self.unpack('B', self.data[self.index:self.index+1])[0])
            self.index = self.index + 1
        if ( name == 'VendorUnique' ):
            length = self.VUDataLength
            self.VUDataLength = 0
        print("length:", end=' ')
        print(length)
        print("[")
        print(self.dump(self.data[self.index:self.index+length]))
        print("]")
        self.index = self.index + length

    def Tag_attr_ubyte(self):
        new_tag = unpackB(self.data[self.index])

        if ( new_tag == pxl_tags_dict['attr_ubyte'] ):
            self.index = self.index + 1

            tag = unpackB(self.data[self.index])
            tag_not_listed = 1
            for k in pxl_attribute_name_to_attribute_number_dict:
                if ( pxl_attribute_name_to_attribute_number_dict[k] == tag ):
                    tag_not_listed = 0
                    if (k == 'VUDataLength'):
                        # The VUDataLength is only ever seen with the CLJ3500/3550/3600
                        # as part of an optional VendorUnque payload; it is possible
                        # that its value type can be something else other than uint32.
                        # We'll just backtrack to get a uint32 value, and raise an error 
                        # if it isn't uint32.
                        vu_tag = unpackB(self.data[self.index-6])
                        if ( vu_tag == pxl_tags_dict['uint32'] ):
                            self.VUDataLength = int(self.unpack('I', self.data[self.index-5:self.index-1])[0])
                        else:
                            raise(SyntaxError)("VUDataLength not uint32")
                    print(k, end='')
                    if (k in pxl_enumerations_dict):
                        print(" //", end=' ')
                        searchstr = "=" + str(self.saved_value)
                        enum = pxl_enumerations_dict[k]
                        enumeration_not_found = 1
                        for value in enum:
                            if ( value[value.index('='):] == searchstr ):
                                enumeration_not_found = 0
                                print(value)
                                break
                        if (enumeration_not_found):
                            # we print here mainly to to avoid side effect 
                            # of the // above if no enumeration is found.
                            print('Unknown enumeration')
                    else:
                        print()
                    self.index = self.index + 1
                    # handle special cases
                    if ( self.isEmbedded(k) ):
                        self.process_EmbeddedInfo(k)
                    return 1
            if (tag_not_listed):
                print("Unlisted attribute number:", tag, file=sys.stderr)
                raise(SyntaxError)("Unlisted attribute number in PXL stream")
        return 0

    def Tag_attr_uint16(self):
        new_tag = unpackB(self.data[self.index])
        if ( new_tag == pxl_tags_dict['attr_uint16'] ):
            self.index = self.index + 1
            print("Attribute tag uint16 # NOT IMPLEMENTED #", self.unpack('HH', self.data[self.index] ))
            self.index = self.index + 2
            return 1
        return 0

    def attributeID(self):
        return (self.Tag_attr_ubyte() or self.Tag_attr_uint16()) and self.attributeIDValue()

    def singleValueType(self):
        if ( self.Tag_ubyte() or self.Tag_uint16() or self.Tag_uint32() or \
             self.Tag_sint16() or self.Tag_sint32() or self.Tag_real32() ):
            size = self.size_of_element
            if ( self.unpack_string == 'f' ):
                print("%f" % self.unpack(self.unpack_string, self.data[self.index:self.index+size]), end=' ')
            else:
                print("%d" % self.unpack(self.unpack_string, self.data[self.index:self.index+size]), end=' ')
            if (( self.unpack_string == 'B' and size == 1 ) or \
                ( self.unpack_string == 'I' and size == 4 )):
                self.saved_value = self.unpack(self.unpack_string, self.data[self.index:self.index+size])[0]
            self.index = self.index + self.size_of_element
            return 1
        return 0

    def xyValueType(self):
        return self.Tag_ubyte_xy() or self.Tag_uint16_xy() or self.Tag_uint32_xy() or \
               self.Tag_sint16_xy() or self.Tag_sint32_xy() or self.Tag_real32_xy()
        
    def boxValueType(self):
        return self.Tag_ubyte_box() or self.Tag_uint16_box() or self.Tag_uint32_box() or \
               self.Tag_sint16_box() or self.Tag_sint32_box() or self.Tag_real32_box()
        
    def valueType(self):
        return self.singleValueType() or self.xyValueType() or self.boxValueType()

    def arraySizeType(self):
        return self.Tag_ubyte() or self.Tag_uint16()

    def arraySize(self):
        # save the old unpack string for the type of the array, the data type for the size
        # will replace it.
        unpack_string = self.unpack_string
        size_of_element = self.size_of_element
        if ( self.arraySizeType() ):
            self.size_of_array = self.unpack( self.unpack_string, \
                                         self.data[self.index:self.index+self.size_of_element] )[0]
            print(self.size_of_array, end='')
            self.index = self.index + self.size_of_element
            # restore the unpack string
            self.unpack_string = unpack_string
            self.size_of_element = size_of_element
            return 1
        return 0
        
    def singleValueArrayType(self):
        return self.Tag_ubyte_array() or self.Tag_uint16_array() or \
               self.Tag_uint32_array() or self.Tag_sint16_array() or \
               self.Tag_sint32_array() or self.Tag_real32_array()
        
    def arrayType(self):
        if (self.singleValueArrayType() and self.arraySize()):
            array_size = self.size_of_element * self.size_of_array
            array_elements = self.unpack( str(self.size_of_array) + self.unpack_string, \
                                     self.data[self.index:self.index+array_size] )
            # hex dump byte arrays
            if (self.size_of_element == 1):
                print()
                print(self.dump(self.data[self.index:self.index+array_size]))
                print("]", end=' ')
            else:
                print(end=' ')
                for num in array_elements:
                    print(num, end=' ')
                print("]", end=' ')
            self.index = self.index + array_size
            return 1
        return 0

    def dataType(self):
        return( self.valueType() or self.arrayType() or self.boxValueType() )

    # these get parsed when doing the tags
    def numericValue(self):
        return 1;

    def attributeValue(self):
        return( self.dataType() and self.numericValue() )

    def singleAttributePair(self):
        return( self.attributeValue() and self.attributeID() )
    
    def multiAttributeList(self):
        # NB should be many 1+ not sure how this get handled yet
        return( self.singleAttributePair() )
    
    def nullAttributeList(self):
        # NB not sure
        return 0
    
    def attributeList(self):
        return (self.singleAttributePair() or self.multiAttributeList() or self.nullAttributeList())

    def attributeLists(self):
        # save the beginning of the attribute list even if it is
        # empty.  So we can report the position of the command.
        self.begin_attribute_pos = self.index
        # 0 or more attribute lists
        while( self.attributeList() ):
            continue
        return 1

    def UEL(self):
        if ( self.data[self.index:self.index+9] == b"\033%-12345X" ):
            print('string* \\x1B%-12345X')
            self.index = self.index + 9
            self.endjob = 1;
            return 1
        return 0
        
    def operatorSequences(self):
        while ( self.attributeLists() and self.operatorTag() ) or self.UEL():
            print()
            if ( self.endjob == 1 ):
                raise IndexError # hack see below
            else:
                continue
        
    def disassemble(self):
        try:
            self.operatorSequences()
        # assume an index error means we have processed everything - ugly
        except IndexError:
            return
        except KeyboardInterrupt:
            print("^C pressed. Terminating...", file=sys.stderr)
            return
        except IOError as msg:  # broken pipe when user quits a downsream pager
            if msg.args == (32,'Broken pipe'):
                print("Broken pipe...", file=sys.stderr)
                return
            else:
                raise
        else:
            print("dissassemble failed at file position %d" % self.index, file=sys.stderr)
            endpos = min(len(self.data), self.index + 25)
            for byte in self.data[self.index:endpos]:
                print(hex(ord(byte)), end=' ', file=sys.stderr)
            print()

if __name__ == '__main__':
    import sys

    if not sys.argv[1:]:
        print("Usage: %s pxl files" % sys.argv[0])

    files = sys.argv[1:]

    for file in files:
        try:
            fp = open(file, 'rb')
        except:
            print("Cannot find file %s" % file)
            continue
        # read the whole damn thing.  If this get too cumbersome we
        # can switch to string i/o which will use a disk
        pxl_code = fp.read()
        fp.close()
    
        # initialize and disassemble.
        pxl_stream = pxl_dis(pxl_code)
        pxl_stream.disassemble()
