//
//  Little cms
//  Copyright (C) 1998-2007 Marti Maria
//
// Permission is hereby granted, free of charge, to any person obtaining 
// a copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software 
// is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in 
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


// Postscript level 2 operators



#include "lcms.h"
#include <time.h>
#include <stdarg.h>

// PostScript ColorRenderingDictionary and ColorSpaceArray

LCMSAPI DWORD LCMSEXPORT cmsGetPostScriptCSA(cmsHPROFILE hProfile, int Intent, LPVOID Buffer, DWORD dwBufferLen);
LCMSAPI DWORD LCMSEXPORT cmsGetPostScriptCRD(cmsHPROFILE hProfile, int Intent, LPVOID Buffer, DWORD dwBufferLen);
LCMSAPI DWORD LCMSEXPORT cmsGetPostScriptCRDEx(cmsHPROFILE hProfile, int Intent, DWORD dwFlags, LPVOID Buffer, DWORD dwBufferLen);
// -------------------------------------------------------------------- Implementation

#define MAXPSCOLS   60      // Columns on tables

/*
    Implementation
    --------------

  PostScript does use XYZ as its internal PCS. But since PostScript 
  interpolation tables are limited to 8 bits, I use Lab as a way to 
  improve the accuracy, favoring perceptual results. So, for the creation 
  of each CRD, CSA the profiles are converted to Lab via a device 
  link between  profile -> Lab or Lab -> profile. The PS code necessary to
  convert Lab <-> XYZ is also included.



  Color Space Arrays (CSA) 
  ==================================================================================

  In order to obtain precission, code chooses between three ways to implement
  the device -> XYZ transform. These cases identifies monochrome profiles (often
  implemented as a set of curves), matrix-shaper and LUT-based.

  Monochrome 
  -----------

  This is implemented as /CIEBasedA CSA. The prelinearization curve is 
  placed into /DecodeA section, and matrix equals to D50. Since here is
  no interpolation tables, I do the conversion directly to XYZ

  NOTE: CLUT-based monochrome profiles are NOT supported. So, cmsFLAGS_MATRIXINPUT
  flag is forced on such profiles.

    [ /CIEBasedA
      <<            
            /DecodeA { transfer function } bind
            /MatrixA [D50]  
            /RangeLMN [ 0.0 D50X 0.0 D50Y 0.0 D50Z ]
            /WhitePoint [D50]
            /BlackPoint [BP]
            /RenderingIntent (intent)
      >>
    ] 

   On simpler profiles, the PCS is already XYZ, so no conversion is required.

 
   Matrix-shaper based
   -------------------

   This is implemented both with /CIEBasedABC or /CIEBasedDEF on dependig
   of profile implementation. Since here is no interpolation tables, I do 
   the conversion directly to XYZ



    [ /CIEBasedABC
            <<
                /DecodeABC [ {transfer1} {transfer2} {transfer3} ]
                /MatrixABC [Matrix]
                /RangeLMN [ 0.0 D50X 0.0 D50Y 0.0 D50Z ]
                /DecodeLMN [ { / 2} dup dup ]
                /WhitePoint [D50]
                /BlackPoint [BP]
                /RenderingIntent (intent)
            >>
    ] 


    CLUT based
    ----------

     Lab is used in such cases.

    [ /CIEBasedDEF
            <<
            /DecodeDEF [ <prelinearization> ]
            /Table [ p p p [<...>]]
            /RangeABC [ 0 1 0 1 0 1]
            /DecodeABC[ <postlinearization> ]
            /RangeLMN [ -0.236 1.254 0 1 -0.635 1.640 ] 
               % -128/500 1+127/500 0 1  -127/200 1+128/200 
            /MatrixABC [ 1 1 1 1 0 0 0 0 -1]
            /WhitePoint [D50]
            /BlackPoint [BP]
            /RenderingIntent (intent)
    ] 


  Color Rendering Dictionaries (CRD)
  ==================================
  These are always implemented as CLUT, and always are using Lab. Since CRD are expected to
  be used as resources, the code adds the definition as well.

  <<
    /ColorRenderingType 1
    /WhitePoint [ D50 ]
    /BlackPoint [BP]
    /MatrixPQR [ Bradford ]
    /RangePQR [-0.125 1.375 -0.125 1.375 -0.125 1.375 ]
    /TransformPQR [            
    {4 index 3 get div 2 index 3 get mul exch pop exch pop exch pop exch pop } bind
    {4 index 4 get div 2 index 4 get mul exch pop exch pop exch pop exch pop } bind
    {4 index 5 get div 2 index 5 get mul exch pop exch pop exch pop exch pop } bind
    ]
    /MatrixABC <...>
    /EncodeABC <...>
    /RangeABC  <.. used for  XYZ -> Lab>
    /EncodeLMN
    /RenderTable [ p p p [<...>]]   
    
    /RenderingIntent (Perceptual)
  >> 
  /Current exch /ColorRendering defineresource pop


  The following stages are used to convert from XYZ to Lab
  --------------------------------------------------------  

  Input is given at LMN stage on X, Y, Z

  Encode LMN gives us f(X/Xn), f(Y/Yn), f(Z/Zn)

  /EncodeLMN [

    { 0.964200  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind
    { 1.000000  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind
    { 0.824900  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind

    ]
    
      
  MatrixABC is used to compute f(Y/Yn), f(X/Xn) - f(Y/Yn), f(Y/Yn) - f(Z/Zn)

  | 0  1  0|
  | 1 -1  0|
  | 0  1 -1|

  /MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ]

 EncodeABC finally gives Lab values.

  /EncodeABC [
    { 116 mul  16 sub 100 div  } bind
    { 500 mul 128 add 255 div  } bind
    { 200 mul 128 add 255 div  } bind
    ]   
    
  The following stages are used to convert Lab to XYZ
  ----------------------------------------------------

    /RangeABC [ 0 1 0 1 0 1]
    /DecodeABC [ { 100 mul 16 add 116 div } bind
                 { 255 mul 128 sub 500 div } bind
                 { 255 mul 128 sub 200 div } bind 
               ]
    
    /MatrixABC [ 1 1 1 1 0 0 0 0 -1]
    /DecodeLMN [
                {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.964200 mul} bind
                {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse } bind
                {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.824900 mul} bind
                ]


*/

/*

 PostScript algorithms discussion.
 =========================================================================================================

  1D interpolation algorithm 


  1D interpolation (float)
  ------------------------
    
    val2 = Domain * Value;

    cell0 = (int) floor(val2);
    cell1 = (int) ceil(val2);

    rest = val2 - cell0;

    y0 = LutTable[cell0] ;
    y1 = LutTable[cell1] ;

    y = y0 + (y1 - y0) * rest;


  
  PostScript code                   Stack
  ================================================

  {                                 % v
    <check 0..1.0>
    [array]                         % v tab
    dup                             % v tab tab
    length 1 sub                    % v tab dom

    3 -1 roll                       % tab dom v

    mul                             % tab val2
    dup                             % tab val2 val2
    dup                             % tab val2 val2 val2
    floor cvi                       % tab val2 val2 cell0
    exch                            % tab val2 cell0 val2
    ceiling cvi                     % tab val2 cell0 cell1

    3 index                         % tab val2 cell0 cell1 tab  
    exch                            % tab val2 cell0 tab cell1
    get                             % tab val2 cell0 y1

    4 -1 roll                       % val2 cell0 y1 tab
    3 -1 roll                       % val2 y1 tab cell0 
    get                             % val2 y1 y0 

    dup                             % val2 y1 y0 y0
    3 1 roll                        % val2 y0 y1 y0 

    sub                             % val2 y0 (y1-y0)
    3 -1 roll                       % y0 (y1-y0) val2
    dup                             % y0 (y1-y0) val2 val2
    floor cvi                       % y0 (y1-y0) val2 floor(val2) 
    sub                             % y0 (y1-y0) rest
    mul                             % y0 t1
    add                             % y
    65535 div                       % result

  } bind


*/

static icTagSignature Device2PCSTab[] = {icSigAToB0Tag,       // Perceptual
                                         icSigAToB1Tag,       // Relative colorimetric
                                         icSigAToB2Tag,       // Saturation
                                         icSigAToB1Tag };     // Absolute colorimetric
                                                           // (Relative/WhitePoint)


// --------------------------------------------------------------- Memory Stream
//
// This struct holds the memory block currently being write
//

typedef struct {
                LPBYTE Block;
                LPBYTE Ptr;
                DWORD  dwMax;
                DWORD  dwUsed;          
                int    MaxCols;
                int    Col;
                int    HasError;
                
            } MEMSTREAM, FAR* LPMEMSTREAM;


typedef struct {
                LPLUT Lut;
                LPMEMSTREAM m;
                
                int FirstComponent;
                int SecondComponent;
                
                int   bps;
                const char* PreMaj;
                const char* PostMaj;
                const char* PreMin;
                const char* PostMin;

                int  lIsInput;    // Handle L* encoding
                int  FixWhite;    // Force mapping of pure white 

                icColorSpaceSignature  ColorSpace;  // ColorSpace of profile

                
            } SAMPLERCARGO, FAR* LPSAMPLERCARGO;


// Creates a ready to use memory stream
static
LPMEMSTREAM CreateMemStream(LPBYTE Buffer, DWORD dwMax, int MaxCols)
{
    LPMEMSTREAM m = (LPMEMSTREAM) _cmsMalloc(sizeof(MEMSTREAM));
    if (m == NULL) return NULL;

    ZeroMemory(m, sizeof(MEMSTREAM));

    m -> Block   = m -> Ptr = Buffer;
    m -> dwMax   = dwMax;
    m -> dwUsed  = 0;
    m -> MaxCols = MaxCols;
    m -> Col     = 0;
    m -> HasError = 0;

    return m;
}



// Convert to byte
static
BYTE Word2Byte(WORD w)
{
    return (BYTE) floor((double) w / 257.0 + 0.5);
}


// Convert to byte (using ICC2 notation)

static
BYTE L2Byte(WORD w)
{    
    int ww = w + 0x0080;

    if (ww > 0xFFFF) return 0xFF;

    return (BYTE) ((WORD) (ww >> 8) & 0xFF);
}

// Write a raw, uncooked byte. Check for space
static
void WriteRawByte(LPMEMSTREAM m, BYTE b)
{    
    if (m -> dwUsed + 1 > m -> dwMax) {
        m -> HasError = 1;      
    }

    if (!m ->HasError && m ->Block) {
        *m ->Ptr++ = b;     
    }
        
    m -> dwUsed++;
}

// Write a cooked byte
static
void WriteByte(LPMEMSTREAM m, BYTE b)
{
    static const BYTE Hex[] = "0123456789ABCDEF";
    BYTE c;

        c = Hex[(b >> 4) & 0x0f];
        WriteRawByte(m, c);
    
        c = Hex[b & 0x0f];
        WriteRawByte(m, c);

        m -> Col += 2;

        if (m -> Col > m -> MaxCols) {

            WriteRawByte(m, '\n');
            m -> Col = 0;           
        }       

}

// Does write a formatted string. Guaranteed to be 2048 bytes at most.
static
void Writef(LPMEMSTREAM m, const char *frm, ...)
{
        va_list args;
        LPBYTE pt;
        BYTE Buffer[2048];

        va_start(args, frm);

        vsnprintf((char*) Buffer, 2048, frm, args);

        for (pt = Buffer; *pt; pt++)  {

            WriteRawByte(m, *pt);               
        }

        va_end(args);       
}



// ----------------------------------------------------------------- PostScript generation


// Removes offending Carriage returns
static 
char* RemoveCR(const char* txt)
{
    static char Buffer[2048];
    char* pt;

    strncpy(Buffer, txt, 2047);
    Buffer[2047] = 0;
    for (pt = Buffer; *pt; pt++)
            if (*pt == '\n' || *pt == '\r') *pt = ' ';

    return Buffer;

}

static
void EmitHeader(LPMEMSTREAM m, const char* Title, cmsHPROFILE hProfile)
{

    time_t timer;
    
    time(&timer);

    Writef(m, "%%!PS-Adobe-3.0\n");
    Writef(m, "%%\n");
    Writef(m, "%% %s\n", Title);
    Writef(m, "%% Source: %s\n", RemoveCR(cmsTakeProductName(hProfile)));
    Writef(m, "%% Description: %s\n", RemoveCR(cmsTakeProductDesc(hProfile)));
    Writef(m, "%% Created: %s", ctime(&timer)); // ctime appends a \n!!!
    Writef(m, "%%\n");
    Writef(m, "%%%%BeginResource\n");

}


// Emits White & Black point. White point is always D50, Black point is the device 
// Black point adapted to D50. 

static
void EmitWhiteBlackD50(LPMEMSTREAM m, LPcmsCIEXYZ BlackPoint)
{

    Writef(m, "/BlackPoint [%f %f %f]\n", BlackPoint -> X,
                                          BlackPoint -> Y,
                                          BlackPoint -> Z);

    Writef(m, "/WhitePoint [%f %f %f]\n", cmsD50_XYZ()->X, 
                                          cmsD50_XYZ()->Y,
                                          cmsD50_XYZ()->Z);
}


static
void EmitRangeCheck(LPMEMSTREAM m)
{
    Writef(m, "dup 0.0 lt { pop 0.0 } if "
              "dup 1.0 gt { pop 1.0 } if ");

}

// Does write the intent

static
void EmitIntent(LPMEMSTREAM m, int RenderingIntent)
{
    const char *intent;

    switch (RenderingIntent) {

        case INTENT_PERCEPTUAL:            intent = "Perceptual"; break;
        case INTENT_RELATIVE_COLORIMETRIC: intent = "RelativeColorimetric"; break;
        case INTENT_ABSOLUTE_COLORIMETRIC: intent = "AbsoluteColorimetric"; break;
        case INTENT_SATURATION:            intent = "Saturation"; break;

        default: intent = "Undefined"; break;
    }

    Writef(m, "/RenderingIntent (%s)\n", intent );    
}

//
//  Convert L* to Y
//
//      Y = Yn*[ (L* + 16) / 116] ^ 3   if (L*) >= 6 / 29
//        = Yn*( L* / 116) / 7.787      if (L*) < 6 / 29
//

/*
static
void EmitL2Y(LPMEMSTREAM m)
{
    Writef(m, 
            "{ " 
                "100 mul 16 add 116 div "               // (L * 100 + 16) / 116
                 "dup 6 29 div ge "                     // >= 6 / 29 ?          
                 "{ dup dup mul mul } "                 // yes, ^3 and done
                 "{ 4 29 div sub 108 841 div mul } "    // no, slope limiting
            "ifelse } bind "); 
}
*/


// Lab -> XYZ, see the discussion above

static
void EmitLab2XYZ(LPMEMSTREAM m)
{
    Writef(m, "/RangeABC [ 0 1 0 1 0 1]\n");
    Writef(m, "/DecodeABC [\n");
    Writef(m, "{100 mul  16 add 116 div } bind\n");
    Writef(m, "{255 mul 128 sub 500 div } bind\n");
    Writef(m, "{255 mul 128 sub 200 div } bind\n");
    Writef(m, "]\n");
    Writef(m, "/MatrixABC [ 1 1 1 1 0 0 0 0 -1]\n");
    Writef(m, "/RangeLMN [ -0.236 1.254 0 1 -0.635 1.640 ]\n"); 
    Writef(m, "/DecodeLMN [\n");
    Writef(m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.964200 mul} bind\n");
    Writef(m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse } bind\n");
    Writef(m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.824900 mul} bind\n");
    Writef(m, "]\n");
}



// Outputs a table of words. It does use 16 bits

static
void Emit1Gamma(LPMEMSTREAM m, LPWORD Table, int nEntries)
{
    int i;
    double gamma;


    if (nEntries <= 0) return;  // Empty table

    // Suppress whole if identity
    if (cmsIsLinear(Table, nEntries)) {
            Writef(m, "{} ");
            return;
    }


    // Check if is really an exponential. If so, emit "exp"
     gamma = cmsEstimateGammaEx(Table, nEntries, 0.001);
     if (gamma > 0) {
            Writef(m, "{ %g exp } bind ", gamma);
            return;
     }

    Writef(m, "{ ");

    // Bounds check
    EmitRangeCheck(m);
    
    // Emit intepolation code

    // PostScript code                      Stack
    // ===============                      ========================
                                            // v
    Writef(m, " [");

    // TODO: Check for endianess!!!

    for (i=0; i < nEntries; i++) {
            Writef(m, "%d ", Table[i]);
    }

    Writef(m, "] ");                        // v tab

    Writef(m, "dup ");                      // v tab tab        
    Writef(m, "length 1 sub ");             // v tab dom
    Writef(m, "3 -1 roll ");                // tab dom v
    Writef(m, "mul ");                      // tab val2
    Writef(m, "dup ");                      // tab val2 val2
    Writef(m, "dup ");                      // tab val2 val2 val2
    Writef(m, "floor cvi ");                // tab val2 val2 cell0
    Writef(m, "exch ");                     // tab val2 cell0 val2
    Writef(m, "ceiling cvi ");              // tab val2 cell0 cell1
    Writef(m, "3 index ");                  // tab val2 cell0 cell1 tab 
    Writef(m, "exch ");                     // tab val2 cell0 tab cell1
    Writef(m, "get ");                      // tab val2 cell0 y1
    Writef(m, "4 -1 roll ");                // val2 cell0 y1 tab
    Writef(m, "3 -1 roll ");                // val2 y1 tab cell0 
    Writef(m, "get ");                      // val2 y1 y0 
    Writef(m, "dup ");                      // val2 y1 y0 y0
    Writef(m, "3 1 roll ");                 // val2 y0 y1 y0 
    Writef(m, "sub ");                      // val2 y0 (y1-y0)
    Writef(m, "3 -1 roll ");                // y0 (y1-y0) val2
    Writef(m, "dup ");                      // y0 (y1-y0) val2 val2
    Writef(m, "floor cvi ");                // y0 (y1-y0) val2 floor(val2) 
    Writef(m, "sub ");                      // y0 (y1-y0) rest
    Writef(m, "mul ");                      // y0 t1
    Writef(m, "add ");                      // y
    Writef(m, "65535 div ");                // result

    Writef(m, " } bind ");
}


// Compare gamma table

static
LCMSBOOL GammaTableEquals(LPWORD g1, LPWORD g2, int nEntries)
{    
    return memcmp(g1, g2, nEntries* sizeof(WORD)) == 0;
}


// Does write a set of gamma curves

static
void EmitNGamma(LPMEMSTREAM m, int n, LPWORD g[], int nEntries)                  
{
    int i;
    
    for( i=0; i < n; i++ )
    {                
        if (i > 0 && GammaTableEquals(g[i-1], g[i], nEntries)) {

            Writef(m, "dup ");
        }
        else {    
            Emit1Gamma(m, g[i], nEntries);
        }
    }
    
}


// Check whatever a profile has CLUT tables (only on input)

static
LCMSBOOL IsLUTbased(cmsHPROFILE hProfile, int Intent)
{
    icTagSignature Tag;

    // Check if adequate tag is present
    Tag = Device2PCSTab[Intent];
        
    if (cmsIsTag(hProfile, Tag)) return 1;

    // If not present, revert to default (perceptual)
    Tag = icSigAToB0Tag;
    
    // If no tag present, try matrix-shaper
    return cmsIsTag(hProfile, Tag);
}



// Following code dumps a LUT onto memory stream
        

// This is the sampler. Intended to work in SAMPLER_INSPECT mode,
// that is, the callback will be called for each knot with
//
//          In[]  The grid location coordinates, normalized to 0..ffff
//          Out[] The LUT values, normalized to 0..ffff
//
//  Returning a value other than 0 does terminate the sampling process
//
//  Each row contains LUT values for all but first component. So, I 
//  detect row changing by keeping a copy of last value of first 
//  component. -1 is used to mark begining of whole block.

static
int OutputValueSampler(register WORD In[], register WORD Out[], register LPVOID Cargo)
{
    LPSAMPLERCARGO sc = (LPSAMPLERCARGO) Cargo;
    unsigned int i;


    if (sc -> FixWhite) {

        if (In[0] == 0xFFFF) {  // Only in L* = 100, ab = [-8..8]

            if ((In[1] >= 0x7800 && In[1] <= 0x8800) &&
                (In[2] >= 0x7800 && In[2] <= 0x8800)) {

                WORD* Black;
                WORD* White;
                int nOutputs;

                if (!_cmsEndPointsBySpace(sc ->ColorSpace, &White, &Black, &nOutputs))
                        return 0;

                for (i=0; i < (unsigned int) nOutputs; i++)
                        Out[i] = White[i];
            }

             
        }
    }


    // Hadle the parenthesis on rows

    if (In[0] != sc ->FirstComponent) {
            
            if (sc ->FirstComponent != -1) {

                    Writef(sc ->m, sc ->PostMin);
                    sc ->SecondComponent = -1;
                    Writef(sc ->m, sc ->PostMaj);           
            }

            // Begin block  
            sc->m->Col = 0;
                    
            Writef(sc ->m, sc ->PreMaj);            
            sc ->FirstComponent = In[0]; 
    }


      if (In[1] != sc ->SecondComponent) {
            
            if (sc ->SecondComponent != -1) {

                    Writef(sc ->m, sc ->PostMin);           
            }
                    
            Writef(sc ->m, sc ->PreMin);            
            sc ->SecondComponent = In[1]; 
    }


    
    // Dump table. Could be Word or byte based on
    // depending on bps member (16 bps mode is not currently 
    // being used at all, but is here for future ampliations)

    for (i=0; i < sc -> Lut ->OutputChan; i++) {

        WORD wWordOut = Out[i];

        if (sc ->bps == 8) {

            // Value as byte
            BYTE wByteOut;
            
            // If is input, convert from Lab2 to Lab4 (just divide by 256)

            if (sc ->lIsInput) {

          
                wByteOut = L2Byte(wWordOut);
            }
            else
                wByteOut = Word2Byte(wWordOut);

            WriteByte(sc -> m, wByteOut);
        }
        else {

            // Value as word    
            WriteByte(sc -> m, (BYTE) (wWordOut & 0xFF));
            WriteByte(sc -> m, (BYTE) ((wWordOut >> 8) & 0xFF));
        }
     }

    return 1;
}

// Writes a LUT on memstream. Could be 8 or 16 bits based

static
void WriteCLUT(LPMEMSTREAM m, LPLUT Lut, int bps, const char* PreMaj, 
                                                  const char* PostMaj,
                                                  const char* PreMin,
                                                  const char* PostMin,
                                                  int lIsInput,
                                                  int FixWhite,
                                                  icColorSpaceSignature ColorSpace)
{
    unsigned int i;
    SAMPLERCARGO sc;

    sc.FirstComponent = -1;
    sc.SecondComponent = -1;
    sc.Lut = Lut;
    sc.m   = m;
    sc.bps = bps;
    sc.PreMaj = PreMaj;
    sc.PostMaj= PostMaj;

    sc.PreMin   = PreMin;
    sc.PostMin  = PostMin;
    sc.lIsInput = lIsInput;
    sc.FixWhite = FixWhite;
    sc.ColorSpace = ColorSpace;

    Writef(m, "[");

    for (i=0; i < Lut ->InputChan; i++)
            Writef(m, " %d ", Lut ->cLutPoints);

    Writef(m, " [\n");

  

    cmsSample3DGrid(Lut, OutputValueSampler, (LPVOID) &sc, SAMPLER_INSPECT);
    
    
    Writef(m, PostMin);
    Writef(m, PostMaj);
    Writef(m, "] ");

    

}


// Dumps CIEBasedA Color Space Array

static
int EmitCIEBasedA(LPMEMSTREAM m, LPWORD Tab, int nEntries, LPcmsCIEXYZ BlackPoint)
{
            
        Writef(m, "[ /CIEBasedA\n");
        Writef(m, "  <<\n");

        Writef(m, "/DecodeA ");

        Emit1Gamma(m,Tab, nEntries);

        Writef(m, " \n");

        Writef(m, "/MatrixA [ 0.9642 1.0000 0.8249 ]\n");
        Writef(m, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n");
        
        EmitWhiteBlackD50(m, BlackPoint);
        EmitIntent(m, INTENT_PERCEPTUAL);

        Writef(m, ">>\n");        
        Writef(m, "]\n");
        
        return 1;
}


// Dumps CIEBasedABC Color Space Array

static
int EmitCIEBasedABC(LPMEMSTREAM m, LPWORD L[], int nEntries, LPWMAT3 Matrix, LPcmsCIEXYZ BlackPoint)
{
    int i;

        Writef(m, "[ /CIEBasedABC\n");
        Writef(m, "<<\n");
        Writef(m, "/DecodeABC [ ");
 
        EmitNGamma(m, 3, L, nEntries);

        Writef(m, "]\n");
        
        Writef(m, "/MatrixABC [ " );

        for( i=0; i < 3; i++ ) {
            
            Writef(m, "%.6f %.6f %.6f ", 
                        FIXED_TO_DOUBLE(Matrix->v[0].n[i]),
                        FIXED_TO_DOUBLE(Matrix->v[1].n[i]),
                        FIXED_TO_DOUBLE(Matrix->v[2].n[i]));            
        }

    
        Writef(m, "]\n");

        Writef(m, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n");
        
        EmitWhiteBlackD50(m, BlackPoint);
        EmitIntent(m, INTENT_PERCEPTUAL);

        Writef(m, ">>\n");
        Writef(m, "]\n");
        

        return 1;
}


static
int EmitCIEBasedDEF(LPMEMSTREAM m, LPLUT Lut, int Intent, LPcmsCIEXYZ BlackPoint)
{
    const char* PreMaj;
    const char* PostMaj;
    const char* PreMin, *PostMin;

    switch (Lut ->InputChan) {
    case 3:

            Writef(m, "[ /CIEBasedDEF\n");
            PreMaj ="<"; 
            PostMaj= ">\n";
            PreMin = PostMin = "";
            break;
    case 4:
            Writef(m, "[ /CIEBasedDEFG\n");
            PreMaj = "[";
            PostMaj = "]\n";
            PreMin = "<";
            PostMin = ">\n";
            break;
    default:
            return 0;

    }

    Writef(m, "<<\n");

    if (Lut ->wFlags & LUT_HASTL1) {
    
        Writef(m, "/DecodeDEF [ ");
        EmitNGamma(m, Lut ->InputChan, Lut ->L1, Lut ->CLut16params.nSamples);
        Writef(m, "]\n");
    }



    if (Lut ->wFlags & LUT_HAS3DGRID) {

            Writef(m, "/Table ");    
            WriteCLUT(m, Lut, 8, PreMaj, PostMaj, PreMin, PostMin, TRUE, FALSE, (icColorSpaceSignature) 0);
            Writef(m, "]\n");
    }
       
    EmitLab2XYZ(m);
    EmitWhiteBlackD50(m, BlackPoint);
    EmitIntent(m, Intent);

    Writef(m, "   >>\n");       
    Writef(m, "]\n");
    

    return 1;
}

// Generates a curve from a gray profile

static
LPGAMMATABLE ExtractGray2Y(cmsHPROFILE hProfile, int Intent)
{
    LPGAMMATABLE Out = cmsAllocGamma(256);
    cmsHPROFILE hXYZ = cmsCreateXYZProfile();
    cmsHTRANSFORM xform = cmsCreateTransform(hProfile, TYPE_GRAY_8, hXYZ, TYPE_XYZ_DBL, Intent, cmsFLAGS_NOTPRECALC);
    int i;

    for (i=0; i < 256; i++) {
        
      BYTE Gray = (BYTE) i;
      cmsCIEXYZ XYZ;
      
        cmsDoTransform(xform, &Gray, &XYZ, 1);
        
        Out ->GammaTable[i] =_cmsClampWord((int) floor(XYZ.Y * 65535.0 + 0.5));
    }

    cmsDeleteTransform(xform);
    cmsCloseProfile(hXYZ);
    return Out;
}



// Because PostScrip has only 8 bits in /Table, we should use
// a more perceptually uniform space... I do choose Lab.

static
int WriteInputLUT(LPMEMSTREAM m, cmsHPROFILE hProfile, int Intent)
{
    cmsHPROFILE hLab;
    cmsHTRANSFORM xform;
    icColorSpaceSignature ColorSpace;
    int nChannels;
    DWORD InputFormat;
    int rc;
    cmsHPROFILE Profiles[2];
    cmsCIEXYZ BlackPointAdaptedToD50;

    // Does create a device-link based transform. 
    // The DeviceLink is next dumped as working CSA.
    
    hLab        = cmsCreateLabProfile(NULL);
    ColorSpace  =  cmsGetColorSpace(hProfile);
    nChannels   = _cmsChannelsOf(ColorSpace);
    InputFormat = CHANNELS_SH(nChannels) | BYTES_SH(2);

    cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent,LCMS_BPFLAGS_D50_ADAPTED);

    // Is a devicelink profile?
    if (cmsGetDeviceClass(hProfile) == icSigLinkClass) {

        // if devicelink output already Lab, use it directly

        if (cmsGetPCS(hProfile) == icSigLabData) {
            
            xform = cmsCreateTransform(hProfile, InputFormat, NULL, 
                            TYPE_Lab_DBL, Intent, 0);
        }
        else {

            // Nope, adjust output to Lab if possible

            Profiles[0] = hProfile;
            Profiles[1] = hLab;

            xform = cmsCreateMultiprofileTransform(Profiles, 2,  InputFormat, 
                                    TYPE_Lab_DBL, Intent, 0);
        }


    }
    else {

        // This is a normal profile
        xform = cmsCreateTransform(hProfile, InputFormat, hLab, 
                            TYPE_Lab_DBL, Intent, 0);
    }


    
    if (xform == NULL) {
                        
            cmsSignalError(LCMS_ERRC_ABORTED, "Cannot create transform Profile -> Lab");
            return 0;
    }

    // Only 1, 3 and 4 channels are allowed

    switch (nChannels) {

    case 1: {            
            LPGAMMATABLE Gray2Y = ExtractGray2Y(hProfile, Intent);
            EmitCIEBasedA(m, Gray2Y->GammaTable, Gray2Y ->nEntries, &BlackPointAdaptedToD50);            
            cmsFreeGamma(Gray2Y);            
            }
            break;

    case 3: 
    case 4: {
            LPLUT DeviceLink;
            _LPcmsTRANSFORM v = (_LPcmsTRANSFORM) xform;

            if (v ->DeviceLink) 
                rc = EmitCIEBasedDEF(m, v->DeviceLink, Intent, &BlackPointAdaptedToD50);
            else {
                DeviceLink = _cmsPrecalculateDeviceLink(xform, 0);
                rc = EmitCIEBasedDEF(m, DeviceLink, Intent, &BlackPointAdaptedToD50);
                cmsFreeLUT(DeviceLink);
            }
            }
            break;

    default:

            cmsSignalError(LCMS_ERRC_ABORTED, "Only 3, 4 channels supported for CSA. This profile has %d channels.", nChannels);
            return 0;
    }
    

    cmsDeleteTransform(xform);
    cmsCloseProfile(hLab);
    return 1;
}



// Does create CSA based on matrix-shaper. Allowed types are gray and RGB based

static
int WriteInputMatrixShaper(LPMEMSTREAM m, cmsHPROFILE hProfile)
{
    icColorSpaceSignature ColorSpace;
    LPMATSHAPER MatShaper;
    int rc;
    cmsCIEXYZ BlackPointAdaptedToD50;


    ColorSpace = cmsGetColorSpace(hProfile);
    MatShaper  = cmsBuildInputMatrixShaper(hProfile);

    cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, INTENT_RELATIVE_COLORIMETRIC, LCMS_BPFLAGS_D50_ADAPTED);

    if (MatShaper == NULL) {

                cmsSignalError(LCMS_ERRC_ABORTED, "This profile is not suitable for input");
                return 0;
    }

    if (ColorSpace == icSigGrayData) {
            
            rc = EmitCIEBasedA(m, MatShaper ->L[0], 
                                  MatShaper ->p16.nSamples,
                                  &BlackPointAdaptedToD50);
        
    }
    else
        if (ColorSpace == icSigRgbData) {

        
            rc = EmitCIEBasedABC(m, MatShaper->L, 
                                        MatShaper ->p16.nSamples, 
                                        &MatShaper ->Matrix,
                                        &BlackPointAdaptedToD50);      
        }
        else  {

            cmsSignalError(LCMS_ERRC_ABORTED, "Profile is not suitable for CSA. Unsupported colorspace.");
            return 0;
        }

    cmsFreeMatShaper(MatShaper);
    return rc;
}



// Creates a PostScript color list from a named profile data. 
// This is a HP extension, and it works in Lab instead of XYZ

static
int WriteNamedColorCSA(LPMEMSTREAM m, cmsHPROFILE hNamedColor, int Intent)
{
    cmsHTRANSFORM xform;
    cmsHPROFILE   hLab;
    int i, nColors;
    char ColorName[32];


    hLab  = cmsCreateLabProfile(NULL);
    xform = cmsCreateTransform(hNamedColor, TYPE_NAMED_COLOR_INDEX, 
                        hLab, TYPE_Lab_DBL, Intent, cmsFLAGS_NOTPRECALC);
    if (xform == NULL) return 0;


    Writef(m, "<<\n");
    Writef(m, "(colorlistcomment) (%s)\n", "Named color CSA");
    Writef(m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n");
    Writef(m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n");

    nColors   = cmsNamedColorCount(xform);


    for (i=0; i < nColors; i++) {
        
        WORD In[1];
        cmsCIELab Lab;

        In[0] = (WORD) i;

        if (!cmsNamedColorInfo(xform, i, ColorName, NULL, NULL))
                continue;

        cmsDoTransform(xform, In, &Lab, 1);     
        Writef(m, "  (%s) [ %.3f %.3f %.3f ]\n", ColorName, Lab.L, Lab.a, Lab.b);
    }


        
    Writef(m, ">>\n");

    cmsDeleteTransform(xform);
    cmsCloseProfile(hLab);
    return 1;
}


// Does create a Color Space Array on XYZ colorspace for PostScript usage

DWORD LCMSEXPORT cmsGetPostScriptCSA(cmsHPROFILE hProfile, 
                              int Intent, 
                              LPVOID Buffer, DWORD dwBufferLen)
{
    
    LPMEMSTREAM mem;
    DWORD dwBytesUsed;
    
    // Set up the serialization engine
    mem = CreateMemStream((LPBYTE) Buffer, dwBufferLen, MAXPSCOLS);
    if (!mem) return 0;

    
    // Is a named color profile?
    if (cmsGetDeviceClass(hProfile) == icSigNamedColorClass) {

        if (!WriteNamedColorCSA(mem, hProfile, Intent)) {

                    _cmsFree((void*) mem);
                    return 0;
        }
    }
    else {


    // Any profile class are allowed (including devicelink), but
    // output (PCS) colorspace must be XYZ or Lab
    icColorSpaceSignature ColorSpace = cmsGetPCS(hProfile);

    if (ColorSpace != icSigXYZData &&
        ColorSpace != icSigLabData) {

            cmsSignalError(LCMS_ERRC_ABORTED, "Invalid output color space");
            _cmsFree((void*) mem);
            return 0;
    }
    
    // Is there any CLUT?
    if (IsLUTbased(hProfile, Intent)) {

        // Yes, so handle as LUT-based
        if (!WriteInputLUT(mem, hProfile, Intent)) {

                    _cmsFree((void*) mem);
                    return 0;
        }
    }
    else {
        
        // No, try Matrix-shaper (this only works on XYZ)

        if (!WriteInputMatrixShaper(mem, hProfile)) {

                    _cmsFree((void*) mem);  // Something went wrong
                    return 0;
        }
    }
    }

    
    // Done, keep memory usage
    dwBytesUsed = mem ->dwUsed;

    // Get rid of memory stream
    _cmsFree((void*) mem);

    // Finally, return used byte count
    return dwBytesUsed;
}

// ------------------------------------------------------ Color Rendering Dictionary (CRD)



/*

  Black point compensation plus chromatic adaptation:

  Step 1 - Chromatic adaptation
  =============================

          WPout
    X = ------- PQR
          Wpin

  Step 2 - Black point compensation
  =================================

          (WPout - BPout)*X - WPout*(BPin - BPout)
    out = --------------------------------------- 
                        WPout - BPin


  Algorithm discussion
  ====================
      
  TransformPQR(WPin, BPin, WPout, BPout, PQR)

  Wpin,etc= { Xws Yws Zws Pws Qws Rws }


  Algorithm             Stack 0...n
  ===========================================================
                        PQR BPout WPout BPin WPin
  4 index 3 get         WPin PQR BPout WPout BPin WPin  
  div                   (PQR/WPin) BPout WPout BPin WPin   
  2 index 3 get         WPout (PQR/WPin) BPout WPout BPin WPin   
  mult                  WPout*(PQR/WPin) BPout WPout BPin WPin   
  
  2 index 3 get         WPout WPout*(PQR/WPin) BPout WPout BPin WPin   
  2 index 3 get         BPout WPout WPout*(PQR/WPin) BPout WPout BPin WPin     
  sub                   (WPout-BPout) WPout*(PQR/WPin) BPout WPout BPin WPin    
  mult                  (WPout-BPout)* WPout*(PQR/WPin) BPout WPout BPin WPin   
          
  2 index 3 get         WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin     
  4 index 3 get         BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin     
  3 index 3 get         BPout BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
  
  sub                   (BPin-BPout) WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin     
  mult                  (BPin-BPout)*WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin     
  sub                   (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin     

  3 index 3 get         BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin     
  3 index 3 get         WPout BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin     
  exch
  sub                   (WPout-BPin) (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin       
  div                
  
  exch pop 
  exch pop
  exch pop
  exch pop

*/


static
void EmitPQRStage(LPMEMSTREAM m, cmsHPROFILE hProfile, int DoBPC, int lIsAbsolute)
{

   
        if (lIsAbsolute) {

            // For absolute colorimetric intent, encode back to relative 
            // and generate a relative LUT

            // Relative encoding is obtained across XYZpcs*(D50/WhitePoint)

            cmsCIEXYZ White;

            cmsTakeMediaWhitePoint(&White, hProfile);

            Writef(m,"/MatrixPQR [1 0 0 0 1 0 0 0 1 ]\n");
            Writef(m,"/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n");

            Writef(m, "%% Absolute colorimetric -- encode to relative to maximize LUT usage\n"
                      "/TransformPQR [\n"
                      "{0.9642 mul %g div exch pop exch pop exch pop exch pop} bind\n"
                      "{1.0000 mul %g div exch pop exch pop exch pop exch pop} bind\n"
                      "{0.8249 mul %g div exch pop exch pop exch pop exch pop} bind\n]\n", 
                      White.X, White.Y, White.Z);
            return;
        }


        Writef(m,"%% Bradford Cone Space\n"
                 "/MatrixPQR [0.8951 -0.7502 0.0389 0.2664 1.7135 -0.0685 -0.1614 0.0367 1.0296 ] \n");

        Writef(m, "/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n");


        // No BPC

        if (!DoBPC) {

            Writef(m, "%% VonKries-like transform in Bradford Cone Space\n"
                      "/TransformPQR [\n"
                      "{exch pop exch 3 get mul exch pop exch 3 get div} bind\n"
                      "{exch pop exch 4 get mul exch pop exch 4 get div} bind\n"
                      "{exch pop exch 5 get mul exch pop exch 5 get div} bind\n]\n"); 
        } else {

            // BPC

            Writef(m, "%% VonKries-like transform in Bradford Cone Space plus BPC\n"
                      "/TransformPQR [\n");
                  
            Writef(m, "{4 index 3 get div 2 index 3 get mul "
                    "2 index 3 get 2 index 3 get sub mul "                          
                    "2 index 3 get 4 index 3 get 3 index 3 get sub mul sub "
                    "3 index 3 get 3 index 3 get exch sub div "
                    "exch pop exch pop exch pop exch pop } bind\n");

            Writef(m, "{4 index 4 get div 2 index 4 get mul "
                    "2 index 4 get 2 index 4 get sub mul "
                    "2 index 4 get 4 index 4 get 3 index 4 get sub mul sub "
                    "3 index 4 get 3 index 4 get exch sub div "
                    "exch pop exch pop exch pop exch pop } bind\n");

            Writef(m, "{4 index 5 get div 2 index 5 get mul "
                    "2 index 5 get 2 index 5 get sub mul "
                    "2 index 5 get 4 index 5 get 3 index 5 get sub mul sub "
                    "3 index 5 get 3 index 5 get exch sub div "
                    "exch pop exch pop exch pop exch pop } bind\n]\n");

        }
          
        
}


static
void EmitXYZ2Lab(LPMEMSTREAM m)
{
    Writef(m, "/RangeLMN [ -0.635 2.0 0 2 -0.635 2.0 ]\n"); 
    Writef(m, "/EncodeLMN [\n");
    Writef(m, "{ 0.964200  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
    Writef(m, "{ 1.000000  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
    Writef(m, "{ 0.824900  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
    Writef(m, "]\n");
    Writef(m, "/MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ]\n");
    Writef(m, "/EncodeABC [\n");
    
        
    Writef(m, "{ 116 mul  16 sub 100 div  } bind\n");
    Writef(m, "{ 500 mul 128 add 256 div  } bind\n");
    Writef(m, "{ 200 mul 128 add 256 div  } bind\n");
    
    
    Writef(m, "]\n");
    

}

// Due to impedance mismatch between XYZ and almost all RGB and CMYK spaces
// I choose to dump LUTS in Lab instead of XYZ. There is still a lot of wasted
// space on 3D CLUT, but since space seems not to be a problem here, 33 points
// would give a reasonable accurancy. Note also that CRD tables must operate in 
// 8 bits.

static
int WriteOutputLUT(LPMEMSTREAM m, cmsHPROFILE hProfile, int Intent, DWORD dwFlags)
{
    cmsHPROFILE hLab;
    cmsHTRANSFORM xform;
    icColorSpaceSignature ColorSpace;
    int i, nChannels;
    DWORD OutputFormat;
    _LPcmsTRANSFORM v;
    LPLUT DeviceLink;
    cmsHPROFILE Profiles[3];
    cmsCIEXYZ BlackPointAdaptedToD50;
    LCMSBOOL lFreeDeviceLink = FALSE;
    LCMSBOOL lDoBPC = (dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION);
    LCMSBOOL lFixWhite = !(dwFlags & cmsFLAGS_NOWHITEONWHITEFIXUP);
    int RelativeEncodingIntent;
    
    

    hLab = cmsCreateLabProfile(NULL);

    ColorSpace  =  cmsGetColorSpace(hProfile);
    nChannels   = _cmsChannelsOf(ColorSpace);
    OutputFormat = CHANNELS_SH(nChannels) | BYTES_SH(2);
    
    // For absolute colorimetric, the LUT is encoded as relative 
    // in order to preserve precission.

    RelativeEncodingIntent = Intent;
    if (RelativeEncodingIntent == INTENT_ABSOLUTE_COLORIMETRIC)
        RelativeEncodingIntent = INTENT_RELATIVE_COLORIMETRIC;


    // Is a devicelink profile?
    if (cmsGetDeviceClass(hProfile) == icSigLinkClass) {

        // if devicelink input already in Lab

        if (ColorSpace == icSigLabData) {

              // adjust input to Lab to our v4

            Profiles[0] = hLab;
            Profiles[1] = hProfile;

            xform = cmsCreateMultiprofileTransform(Profiles, 2, TYPE_Lab_DBL, 
                                                        OutputFormat, RelativeEncodingIntent, 
                                                        dwFlags|cmsFLAGS_NOWHITEONWHITEFIXUP|cmsFLAGS_NOPRELINEARIZATION);
            
        }
        else {
          cmsSignalError(LCMS_ERRC_ABORTED, "Cannot use devicelink profile for CRD creation");
          return 0;
        }


    }
    else {
        
        // This is a normal profile
        xform = cmsCreateTransform(hLab, TYPE_Lab_DBL, hProfile, 
                            OutputFormat, RelativeEncodingIntent, dwFlags|cmsFLAGS_NOWHITEONWHITEFIXUP|cmsFLAGS_NOPRELINEARIZATION);
    }

    if (xform == NULL) {
                        
            cmsSignalError(LCMS_ERRC_ABORTED, "Cannot create transform Lab -> Profile in CRD creation");
            return 0;
    }

    // Get the internal precalculated devicelink

    v = (_LPcmsTRANSFORM) xform;
    DeviceLink = v ->DeviceLink;
    
    if (!DeviceLink) {

        DeviceLink = _cmsPrecalculateDeviceLink(xform, cmsFLAGS_NOPRELINEARIZATION);
        lFreeDeviceLink = TRUE;
    }

    Writef(m, "<<\n");
    Writef(m, "/ColorRenderingType 1\n");


    cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent, LCMS_BPFLAGS_D50_ADAPTED);

    // Emit headers, etc.
    EmitWhiteBlackD50(m, &BlackPointAdaptedToD50);
    EmitPQRStage(m, hProfile, lDoBPC, Intent == INTENT_ABSOLUTE_COLORIMETRIC);
    EmitXYZ2Lab(m);
        
    if (DeviceLink ->wFlags & LUT_HASTL1) {

        // Shouldn't happen
        cmsSignalError(LCMS_ERRC_ABORTED, "Internal error (prelinearization on CRD)");
        return 0;
    }
    

    // FIXUP: map Lab (100, 0, 0) to perfect white, because the particular encoding for Lab 
    // does map a=b=0 not falling into any specific node. Since range a,b goes -128..127, 
    // zero is slightly moved towards right, so assure next node (in L=100 slice) is mapped to
    // zero. This would sacrifice a bit of highlights, but failure to do so would cause
    // scum dot. Ouch.
    
    if (Intent == INTENT_ABSOLUTE_COLORIMETRIC)
            lFixWhite = FALSE;

    Writef(m, "/RenderTable ");
    
    WriteCLUT(m, DeviceLink, 8, "<", ">\n", "", "", FALSE, 
                lFixWhite, ColorSpace);
    
    Writef(m, " %d {} bind ", nChannels);

    for (i=1; i < nChannels; i++)
            Writef(m, "dup ");

    Writef(m, "]\n");

        
    EmitIntent(m, Intent);

    Writef(m, ">>\n");

    if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {

        Writef(m, "/Current exch /ColorRendering defineresource pop\n");
    }

    if (lFreeDeviceLink) cmsFreeLUT(DeviceLink);
    cmsDeleteTransform(xform);
    cmsCloseProfile(hLab);

    return 1;   
}


// Builds a ASCII string containing colorant list in 0..1.0 range
static
void BuildColorantList(char *Colorant, int nColorant, WORD Out[])
{
    char Buff[32];
    int j;

    Colorant[0] = 0;
    if (nColorant > MAXCHANNELS)
        nColorant = MAXCHANNELS;

    for (j=0; j < nColorant; j++) {

                sprintf(Buff, "%.3f", Out[j] / 65535.0);
                strcat(Colorant, Buff);
                if (j < nColorant -1) 
                        strcat(Colorant, " ");

        }       
}


// Creates a PostScript color list from a named profile data. 
// This is a HP extension.

static
int WriteNamedColorCRD(LPMEMSTREAM m, cmsHPROFILE hNamedColor, int Intent, DWORD dwFlags)
{
    cmsHTRANSFORM xform;    
    int i, nColors, nColorant;
    DWORD OutputFormat;
    char ColorName[32];
    char Colorant[128];

    nColorant = _cmsChannelsOf(cmsGetColorSpace(hNamedColor));
    OutputFormat = CHANNELS_SH(nColorant) | BYTES_SH(2);

    xform = cmsCreateTransform(hNamedColor, TYPE_NAMED_COLOR_INDEX, 
                        NULL, OutputFormat, Intent, cmsFLAGS_NOTPRECALC);
    if (xform == NULL) return 0;


    Writef(m, "<<\n");
    Writef(m, "(colorlistcomment) (%s) \n", "Named profile");
    Writef(m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n");
    Writef(m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n");

    nColors   = cmsNamedColorCount(xform);
    

    for (i=0; i < nColors; i++) {
        
        WORD In[1];
        WORD Out[MAXCHANNELS];

        In[0] = (WORD) i;

        if (!cmsNamedColorInfo(xform, i, ColorName, NULL, NULL))
                continue;

        cmsDoTransform(xform, In, Out, 1);      
        BuildColorantList(Colorant, nColorant, Out);
        Writef(m, "  (%s) [ %s ]\n", ColorName, Colorant);
    }

    Writef(m, "   >>");

    if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {

    Writef(m, " /Current exch /HPSpotTable defineresource pop\n");
    }

    cmsDeleteTransform(xform);  
    return 1;
}



// This one does create a Color Rendering Dictionary. 
// CRD are always LUT-Based, no matter if profile is
// implemented as matrix-shaper.

DWORD LCMSEXPORT cmsGetPostScriptCRDEx(cmsHPROFILE hProfile, 
                              int Intent, DWORD dwFlags,
                              LPVOID Buffer, DWORD dwBufferLen)
{
    
    LPMEMSTREAM mem;
    DWORD dwBytesUsed;

    // Set up the serialization artifact
    mem = CreateMemStream((LPBYTE) Buffer, dwBufferLen, MAXPSCOLS);
    if (!mem) return 0;

    
    if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {

    EmitHeader(mem, "Color Rendering Dictionary (CRD)", hProfile);
    }


    // Is a named color profile?
    if (cmsGetDeviceClass(hProfile) == icSigNamedColorClass) {

        if (!WriteNamedColorCRD(mem, hProfile, Intent, dwFlags)) {

                    _cmsFree((void*) mem);
                    return 0;
        }
    }
    else {
        
    // CRD are always implemented as LUT. 


    if (!WriteOutputLUT(mem, hProfile, Intent, dwFlags)) {
        _cmsFree((void*) mem);
        return 0;
    }
    }
    
    if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {

    Writef(mem, "%%%%EndResource\n");
    Writef(mem, "\n%% CRD End\n");
    }

    // Done, keep memory usage
    dwBytesUsed = mem ->dwUsed;

    // Get rid of memory stream
    _cmsFree((void*) mem);

    // Finally, return used byte count
    return dwBytesUsed;
}


// For compatibility with previous versions

DWORD LCMSEXPORT cmsGetPostScriptCRD(cmsHPROFILE hProfile, 
                              int Intent, 
                              LPVOID Buffer, DWORD dwBufferLen)
{
    return cmsGetPostScriptCRDEx(hProfile, Intent, 0, Buffer, dwBufferLen);
}
