1 /*
   2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   3  *
   4  * This code is free software; you can redistribute it and/or modify it
   5  * under the terms of the GNU General Public License version 2 only, as
   6  * published by the Free Software Foundation.  Oracle designates this
   7  * particular file as subject to the "Classpath" exception as provided
   8  * by Oracle in the LICENSE file that accompanied this code.
   9  *
  10  * This code is distributed in the hope that it will be useful, but WITHOUT
  11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  13  * version 2 for more details (a copy is included in the LICENSE file that
  14  * accompanied this code).
  15  *
  16  * You should have received a copy of the GNU General Public License version
  17  * 2 along with this work; if not, write to the Free Software Foundation,
  18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  19  *
  20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  21  * or visit www.oracle.com if you need additional information or have any
  22  * questions.
  23  */
  24 
  25 // This file is available under and governed by the GNU General Public
  26 // License version 2 only, as published by the Free Software Foundation.
  27 // However, the following notice accompanied the original version of this
  28 // file:
  29 //
  30 //---------------------------------------------------------------------------------
  31 //
  32 //  Little Color Management System
  33 //  Copyright (c) 1998-2023 Marti Maria Saguer
  34 //
  35 // Permission is hereby granted, free of charge, to any person obtaining
  36 // a copy of this software and associated documentation files (the "Software"),
  37 // to deal in the Software without restriction, including without limitation
  38 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
  39 // and/or sell copies of the Software, and to permit persons to whom the Software
  40 // is furnished to do so, subject to the following conditions:
  41 //
  42 // The above copyright notice and this permission notice shall be included in
  43 // all copies or substantial portions of the Software.
  44 //
  45 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  46 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
  47 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  48 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  49 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  50 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  51 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  52 //
  53 //---------------------------------------------------------------------------------
  54 //
  55 
  56 #include "lcms2_internal.h"
  57 
  58 
  59 // IT8.7 / CGATS.17-200x handling -----------------------------------------------------------------------------
  60 
  61 
  62 #define MAXID        128     // Max length of identifier
  63 #define MAXSTR      1024     // Max length of string
  64 #define MAXTABLES    255     // Max Number of tables in a single stream
  65 #define MAXINCLUDE    20     // Max number of nested includes
  66 
  67 #define DEFAULT_DBL_FORMAT  "%.10g" // Double formatting
  68 
  69 #ifdef CMS_IS_WINDOWS_
  70 #    include <io.h>
  71 #    define DIR_CHAR    '\\'
  72 #else
  73 #    define DIR_CHAR    '/'
  74 #endif
  75 
  76 
  77 // Symbols
  78 typedef enum {
  79 
  80         SUNDEFINED,
  81         SINUM,      // Integer
  82         SDNUM,      // Real
  83         SIDENT,     // Identifier
  84         SSTRING,    // string
  85         SCOMMENT,   // comment
  86         SEOLN,      // End of line
  87         SEOF,       // End of stream
  88         SSYNERROR,  // Syntax error found on stream
  89 
  90         // IT8 symbols
  91 
  92         SBEGIN_DATA,
  93         SBEGIN_DATA_FORMAT,
  94         SEND_DATA,
  95         SEND_DATA_FORMAT,
  96         SKEYWORD,
  97         SDATA_FORMAT_ID,
  98         SINCLUDE,
  99 
 100         // Cube symbols
 101 
 102         SDOMAIN_MAX,
 103         SDOMAIN_MIN,
 104         S_LUT1D_SIZE,
 105         S_LUT1D_INPUT_RANGE,
 106         S_LUT3D_SIZE,
 107         S_LUT3D_INPUT_RANGE,
 108         S_LUT_IN_VIDEO_RANGE,
 109         S_LUT_OUT_VIDEO_RANGE,
 110         STITLE
 111 
 112     } SYMBOL;
 113 
 114 
 115 // How to write the value
 116 typedef enum {
 117 
 118         WRITE_UNCOOKED,
 119         WRITE_STRINGIFY,
 120         WRITE_HEXADECIMAL,
 121         WRITE_BINARY,
 122         WRITE_PAIR
 123 
 124     } WRITEMODE;
 125 
 126 // Linked list of variable names
 127 typedef struct _KeyVal {
 128 
 129         struct _KeyVal*  Next;
 130         char*            Keyword;       // Name of variable
 131         struct _KeyVal*  NextSubkey;    // If key is a dictionary, points to the next item
 132         char*            Subkey;        // If key is a dictionary, points to the subkey name
 133         char*            Value;         // Points to value
 134         WRITEMODE        WriteAs;       // How to write the value
 135 
 136    } KEYVALUE;
 137 
 138 
 139 // Linked list of memory chunks (Memory sink)
 140 typedef struct _OwnedMem {
 141 
 142         struct _OwnedMem* Next;
 143         void *            Ptr;          // Point to value
 144 
 145    } OWNEDMEM;
 146 
 147 // Suballocator
 148 typedef struct _SubAllocator {
 149 
 150          cmsUInt8Number* Block;
 151          cmsUInt32Number BlockSize;
 152          cmsUInt32Number Used;
 153 
 154     } SUBALLOCATOR;
 155 
 156 // Table. Each individual table can hold properties and rows & cols
 157 typedef struct _Table {
 158 
 159         char SheetType[MAXSTR];               // The first row of the IT8 (the type)
 160 
 161         int            nSamples, nPatches;    // Cols, Rows
 162         int            SampleID;              // Pos of ID
 163 
 164         KEYVALUE*      HeaderList;            // The properties
 165 
 166         char**         DataFormat;            // The binary stream descriptor
 167         char**         Data;                  // The binary stream
 168 
 169     } TABLE;
 170 
 171 // File stream being parsed
 172 typedef struct _FileContext {
 173         char           FileName[cmsMAX_PATH];    // File name if being read from file
 174         FILE*          Stream;                   // File stream or NULL if holded in memory
 175     } FILECTX;
 176 
 177 //Very simple string
 178 typedef struct {
 179 
 180         struct struct_it8* it8;
 181         cmsInt32Number max;
 182         cmsInt32Number len;
 183         char* begin;
 184     } string;
 185 
 186 
 187 // This struct hold all information about an open IT8 handler.
 188 typedef struct struct_it8 {
 189 
 190         cmsUInt32Number  TablesCount;                     // How many tables in this stream
 191         cmsUInt32Number  nTable;                          // The actual table
 192 
 193         // Partser type
 194         cmsBool        IsCUBE;
 195 
 196         // Tables
 197         TABLE Tab[MAXTABLES];
 198 
 199         // Memory management
 200         OWNEDMEM*      MemorySink;            // The storage backend
 201         SUBALLOCATOR   Allocator;             // String suballocator -- just to keep it fast
 202 
 203         // Parser state machine
 204         SYMBOL             sy;                // Current symbol
 205         int                ch;                // Current character
 206 
 207         cmsInt32Number     inum;              // integer value
 208         cmsFloat64Number   dnum;              // real value
 209 
 210         string*        id;            // identifier
 211         string*        str;           // string
 212 
 213         // Allowed keywords & datasets. They have visibility on whole stream
 214         KEYVALUE*      ValidKeywords;
 215         KEYVALUE*      ValidSampleID;
 216 
 217         char*          Source;                // Points to loc. being parsed
 218         cmsInt32Number lineno;                // line counter for error reporting
 219 
 220         FILECTX*       FileStack[MAXINCLUDE]; // Stack of files being parsed
 221         cmsInt32Number IncludeSP;             // Include Stack Pointer
 222 
 223         char*          MemoryBlock;           // The stream if holded in memory
 224 
 225         char           DoubleFormatter[MAXID];// Printf-like 'cmsFloat64Number' formatter
 226 
 227         cmsContext    ContextID;              // The threading context
 228 
 229    } cmsIT8;
 230 
 231 
 232 // The stream for save operations
 233 typedef struct {
 234 
 235         FILE* stream;   // For save-to-file behaviour
 236 
 237         cmsUInt8Number* Base;
 238         cmsUInt8Number* Ptr;        // For save-to-mem behaviour
 239         cmsUInt32Number Used;
 240         cmsUInt32Number Max;
 241 
 242     } SAVESTREAM;
 243 
 244 
 245 // ------------------------------------------------------ cmsIT8 parsing routines
 246 
 247 
 248 // A keyword
 249 typedef struct {
 250 
 251         const char *id;
 252         SYMBOL sy;
 253 
 254    } KEYWORD;
 255 
 256 // The keyword->symbol translation tables. Sorting is required.
 257 static const KEYWORD TabKeysIT8[] = {
 258 
 259         {"$INCLUDE",               SINCLUDE},   // This is an extension!
 260         {".INCLUDE",               SINCLUDE},   // This is an extension!
 261 
 262         {"BEGIN_DATA",             SBEGIN_DATA },
 263         {"BEGIN_DATA_FORMAT",      SBEGIN_DATA_FORMAT },
 264         {"DATA_FORMAT_IDENTIFIER", SDATA_FORMAT_ID},
 265         {"END_DATA",               SEND_DATA},
 266         {"END_DATA_FORMAT",        SEND_DATA_FORMAT},
 267         {"KEYWORD",                SKEYWORD}
 268         };
 269 
 270 #define NUMKEYS_IT8 (sizeof(TabKeysIT8)/sizeof(KEYWORD))
 271 
 272 static const KEYWORD TabKeysCUBE[] = {
 273 
 274         {"DOMAIN_MAX",             SDOMAIN_MAX },
 275         {"DOMAIN_MIN",             SDOMAIN_MIN },
 276         {"LUT_1D_SIZE",            S_LUT1D_SIZE },
 277         {"LUT_1D_INPUT_RANGE",     S_LUT1D_INPUT_RANGE },
 278         {"LUT_3D_SIZE",            S_LUT3D_SIZE },
 279         {"LUT_3D_INPUT_RANGE",     S_LUT3D_INPUT_RANGE },
 280         {"LUT_IN_VIDEO_RANGE",     S_LUT_IN_VIDEO_RANGE },
 281         {"LUT_OUT_VIDEO_RANGE",    S_LUT_OUT_VIDEO_RANGE },
 282         {"TITLE",                  STITLE }
 283 
 284 };
 285 
 286 #define NUMKEYS_CUBE (sizeof(TabKeysCUBE)/sizeof(KEYWORD))
 287 
 288 
 289 
 290 // Predefined properties
 291 
 292 // A property
 293 typedef struct {
 294         const char *id;    // The identifier
 295         WRITEMODE as;      // How is supposed to be written
 296     } PROPERTY;
 297 
 298 static PROPERTY PredefinedProperties[] = {
 299 
 300         {"NUMBER_OF_FIELDS", WRITE_UNCOOKED},    // Required - NUMBER OF FIELDS
 301         {"NUMBER_OF_SETS",   WRITE_UNCOOKED},    // Required - NUMBER OF SETS
 302         {"ORIGINATOR",       WRITE_STRINGIFY},   // Required - Identifies the specific system, organization or individual that created the data file.
 303         {"FILE_DESCRIPTOR",  WRITE_STRINGIFY},   // Required - Describes the purpose or contents of the data file.
 304         {"CREATED",          WRITE_STRINGIFY},   // Required - Indicates date of creation of the data file.
 305         {"DESCRIPTOR",       WRITE_STRINGIFY},   // Required  - Describes the purpose or contents of the data file.
 306         {"DIFFUSE_GEOMETRY", WRITE_STRINGIFY},   // The diffuse geometry used. Allowed values are "sphere" or "opal".
 307         {"MANUFACTURER",     WRITE_STRINGIFY},
 308         {"MANUFACTURE",      WRITE_STRINGIFY},   // Some broken Fuji targets does store this value
 309         {"PROD_DATE",        WRITE_STRINGIFY},   // Identifies year and month of production of the target in the form yyyy:mm.
 310         {"SERIAL",           WRITE_STRINGIFY},   // Uniquely identifies individual physical target.
 311 
 312         {"MATERIAL",         WRITE_STRINGIFY},    // Identifies the material on which the target was produced using a code
 313                                                   // uniquely identifying th e material. This is intend ed to be used for IT8.7
 314                                                   // physical targets only (i.e . IT8.7/1 and IT8.7/2).
 315 
 316         {"INSTRUMENTATION",  WRITE_STRINGIFY},    // Used to report the specific instrumentation used (manufacturer and
 317                                                   // model number) to generate the data reported. This data will often
 318                                                   // provide more information about the particular data collected than an
 319                                                   // extensive list of specific details. This is particularly important for
 320                                                   // spectral data or data derived from spectrophotometry.
 321 
 322         {"MEASUREMENT_SOURCE", WRITE_STRINGIFY},  // Illumination used for spectral measurements. This data helps provide
 323                                                   // a guide to the potential for issues of paper fluorescence, etc.
 324 
 325         {"PRINT_CONDITIONS", WRITE_STRINGIFY},     // Used to define the characteristics of the printed sheet being reported.
 326                                                    // Where standard conditions have been defined (e.g., SWOP at nominal)
 327                                                    // named conditions may suffice. Otherwise, detailed information is
 328                                                    // needed.
 329 
 330         {"SAMPLE_BACKING",   WRITE_STRINGIFY},     // Identifies the backing material used behind the sample during
 331                                                    // measurement. Allowed values are "black", "white", or {"na".
 332 
 333         {"CHISQ_DOF",        WRITE_STRINGIFY},     // Degrees of freedom associated with the Chi squared statistic
 334                                                    // below properties are new in recent specs:
 335 
 336         {"MEASUREMENT_GEOMETRY", WRITE_STRINGIFY}, // The type of measurement, either reflection or transmission, should be indicated
 337                                                    // along with details of the geometry and the aperture size and shape. For example,
 338                                                    // for transmission measurements it is important to identify 0/diffuse, diffuse/0,
 339                                                    // opal or integrating sphere, etc. For reflection it is important to identify 0/45,
 340                                                    // 45/0, sphere (specular included or excluded), etc.
 341 
 342        {"FILTER",            WRITE_STRINGIFY},     // Identifies the use of physical filter(s) during measurement. Typically used to
 343                                                    // denote the use of filters such as none, D65, Red, Green or Blue.
 344 
 345        {"POLARIZATION",      WRITE_STRINGIFY},     // Identifies the use of a physical polarization filter during measurement. Allowed
 346                                                    // values are {"yes", "white", "none" or "na".
 347 
 348        {"WEIGHTING_FUNCTION", WRITE_PAIR},         // Indicates such functions as: the CIE standard observer functions used in the
 349                                                    // calculation of various data parameters (2 degree and 10 degree), CIE standard
 350                                                    // illuminant functions used in the calculation of various data parameters (e.g., D50,
 351                                                    // D65, etc.), density status response, etc. If used there shall be at least one
 352                                                    // name-value pair following the WEIGHTING_FUNCTION tag/keyword. The first attribute
 353                                                    // in the set shall be {"name" and shall identify the particular parameter used.
 354                                                    // The second shall be {"value" and shall provide the value associated with that name.
 355                                                    // For ASCII data, a string containing the Name and Value attribute pairs shall follow
 356                                                    // the weighting function keyword. A semi-colon separates attribute pairs from each
 357                                                    // other and within the attribute the name and value are separated by a comma.
 358 
 359        {"COMPUTATIONAL_PARAMETER", WRITE_PAIR},    // Parameter that is used in computing a value from measured data. Name is the name
 360                                                    // of the calculation, parameter is the name of the parameter used in the calculation
 361                                                    // and value is the value of the parameter.
 362 
 363        {"TARGET_TYPE",        WRITE_STRINGIFY},    // The type of target being measured, e.g. IT8.7/1, IT8.7/3, user defined, etc.
 364 
 365        {"COLORANT",           WRITE_STRINGIFY},    // Identifies the colorant(s) used in creating the target.
 366 
 367        {"TABLE_DESCRIPTOR",   WRITE_STRINGIFY},    // Describes the purpose or contents of a data table.
 368 
 369        {"TABLE_NAME",         WRITE_STRINGIFY}     // Provides a short name for a data table.
 370 };
 371 
 372 #define NUMPREDEFINEDPROPS (sizeof(PredefinedProperties)/sizeof(PROPERTY))
 373 
 374 
 375 // Predefined sample types on dataset
 376 static const char* PredefinedSampleID[] = {
 377         "SAMPLE_ID",      // Identifies sample that data represents
 378         "STRING",         // Identifies label, or other non-machine readable value.
 379                           // Value must begin and end with a " symbol
 380 
 381         "CMYK_C",         // Cyan component of CMYK data expressed as a percentage
 382         "CMYK_M",         // Magenta component of CMYK data expressed as a percentage
 383         "CMYK_Y",         // Yellow component of CMYK data expressed as a percentage
 384         "CMYK_K",         // Black component of CMYK data expressed as a percentage
 385         "D_RED",          // Red filter density
 386         "D_GREEN",        // Green filter density
 387         "D_BLUE",         // Blue filter density
 388         "D_VIS",          // Visual filter density
 389         "D_MAJOR_FILTER", // Major filter d ensity
 390         "RGB_R",          // Red component of RGB data
 391         "RGB_G",          // Green component of RGB data
 392         "RGB_B",          // Blue com ponent of RGB data
 393         "SPECTRAL_NM",    // Wavelength of measurement expressed in nanometers
 394         "SPECTRAL_PCT",   // Percentage reflectance/transmittance
 395         "SPECTRAL_DEC",   // Reflectance/transmittance
 396         "XYZ_X",          // X component of tristimulus data
 397         "XYZ_Y",          // Y component of tristimulus data
 398         "XYZ_Z",          // Z component of tristimulus data
 399         "XYY_X",          // x component of chromaticity data
 400         "XYY_Y",          // y component of chromaticity data
 401         "XYY_CAPY",       // Y component of tristimulus data
 402         "LAB_L",          // L* component of Lab data
 403         "LAB_A",          // a* component of Lab data
 404         "LAB_B",          // b* component of Lab data
 405         "LAB_C",          // C*ab component of Lab data
 406         "LAB_H",          // hab component of Lab data
 407         "LAB_DE",         // CIE dE
 408         "LAB_DE_94",      // CIE dE using CIE 94
 409         "LAB_DE_CMC",     // dE using CMC
 410         "LAB_DE_2000",    // CIE dE using CIE DE 2000
 411         "MEAN_DE",        // Mean Delta E (LAB_DE) of samples compared to batch average
 412                           // (Used for data files for ANSI IT8.7/1 and IT8.7/2 targets)
 413         "STDEV_X",        // Standard deviation of X (tristimulus data)
 414         "STDEV_Y",        // Standard deviation of Y (tristimulus data)
 415         "STDEV_Z",        // Standard deviation of Z (tristimulus data)
 416         "STDEV_L",        // Standard deviation of L*
 417         "STDEV_A",        // Standard deviation of a*
 418         "STDEV_B",        // Standard deviation of b*
 419         "STDEV_DE",       // Standard deviation of CIE dE
 420         "CHI_SQD_PAR"};   // The average of the standard deviations of L*, a* and b*. It is
 421                           // used to derive an estimate of the chi-squared parameter which is
 422                           // recommended as the predictor of the variability of dE
 423 
 424 #define NUMPREDEFINEDSAMPLEID (sizeof(PredefinedSampleID)/sizeof(char *))
 425 
 426 //Forward declaration of some internal functions
 427 static void* AllocChunk(cmsIT8* it8, cmsUInt32Number size);
 428 
 429 static
 430 string* StringAlloc(cmsIT8* it8, int max)
 431 {
 432     string* s = (string*) AllocChunk(it8, sizeof(string));
 433     if (s == NULL) return NULL;
 434 
 435     s->it8 = it8;
 436     s->max = max;
 437     s->len = 0;
 438     s->begin = (char*) AllocChunk(it8, s->max);
 439 
 440     return s;
 441 }
 442 
 443 static
 444 void StringClear(string* s)
 445 {
 446     s->len = 0;
 447 }
 448 
 449 static
 450 void StringAppend(string* s, char c)
 451 {
 452     if (s->len + 1 >= s->max)
 453     {
 454         char* new_ptr;
 455 
 456         s->max *= 10;
 457         new_ptr = (char*) AllocChunk(s->it8, s->max);
 458         if (new_ptr != NULL && s->begin != NULL)
 459             memcpy(new_ptr, s->begin, s->len);
 460 
 461         s->begin = new_ptr;
 462     }
 463 
 464     if (s->begin != NULL)
 465     {
 466         s->begin[s->len++] = c;
 467         s->begin[s->len] = 0;
 468     }
 469 }
 470 
 471 static
 472 char* StringPtr(string* s)
 473 {
 474     return s->begin;
 475 }
 476 
 477 static
 478 void StringCat(string* s, const char* c)
 479 {
 480     while (*c)
 481     {
 482         StringAppend(s, *c);
 483         c++;
 484     }
 485 }
 486 
 487 
 488 // Checks whatever c is a separator
 489 static
 490 cmsBool isseparator(int c)
 491 {
 492     return (c == ' ') || (c == '\t');
 493 }
 494 
 495 // Checks whatever c is a valid identifier char
 496 static
 497 cmsBool ismiddle(int c)
 498 {
 499    return (!isseparator(c) && (c != '#') && (c !='\"') && (c != '\'') && (c > 32) && (c < 127));
 500 }
 501 
 502 // Checks whatsever c is a valid identifier middle char.
 503 static
 504 cmsBool isidchar(int c)
 505 {
 506    return isalnum(c) || ismiddle(c);
 507 }
 508 
 509 // Checks whatsever c is a valid identifier first char.
 510 static
 511 cmsBool isfirstidchar(int c)
 512 {
 513      return c != '-' && !isdigit(c) && ismiddle(c);
 514 }
 515 
 516 // Guess whether the supplied path looks like an absolute path
 517 static
 518 cmsBool isabsolutepath(const char *path)
 519 {
 520     char ThreeChars[4];
 521 
 522     if(path == NULL)
 523         return FALSE;
 524     if (path[0] == 0)
 525         return FALSE;
 526 
 527     strncpy(ThreeChars, path, 3);
 528     ThreeChars[3] = 0;
 529 
 530     if(ThreeChars[0] == DIR_CHAR)
 531         return TRUE;
 532 
 533 #ifdef  CMS_IS_WINDOWS_
 534     if (isalpha((int) ThreeChars[0]) && ThreeChars[1] == ':')
 535         return TRUE;
 536 #endif
 537     return FALSE;
 538 }
 539 
 540 
 541 // Makes a file path based on a given reference path
 542 // NOTE: this function doesn't check if the path exists or even if it's legal
 543 static
 544 cmsBool BuildAbsolutePath(const char *relPath, const char *basePath, char *buffer, cmsUInt32Number MaxLen)
 545 {
 546     char *tail;
 547     cmsUInt32Number len;
 548 
 549     // Already absolute?
 550     if (isabsolutepath(relPath)) {
 551 
 552         memcpy(buffer, relPath, MaxLen);
 553         buffer[MaxLen-1] = 0;
 554         return TRUE;
 555     }
 556 
 557     // No, search for last
 558     memcpy(buffer, basePath, MaxLen);
 559     buffer[MaxLen-1] = 0;
 560 
 561     tail = strrchr(buffer, DIR_CHAR);
 562     if (tail == NULL) return FALSE;    // Is not absolute and has no separators??
 563 
 564     len = (cmsUInt32Number) (tail - buffer);
 565     if (len >= MaxLen) return FALSE;
 566 
 567     // No need to assure zero terminator over here
 568     strncpy(tail + 1, relPath, MaxLen - len);
 569 
 570     return TRUE;
 571 }
 572 
 573 
 574 // Make sure no exploit is being even tried
 575 static
 576 const char* NoMeta(const char* str)
 577 {
 578     if (strchr(str, '%') != NULL)
 579         return "**** CORRUPTED FORMAT STRING ***";
 580 
 581     return str;
 582 }
 583 
 584 // Syntax error
 585 static
 586 cmsBool SynError(cmsIT8* it8, const char *Txt, ...)
 587 {
 588     char Buffer[256], ErrMsg[1024];
 589     va_list args;
 590 
 591     va_start(args, Txt);
 592     vsnprintf(Buffer, 255, Txt, args);
 593     Buffer[255] = 0;
 594     va_end(args);
 595 
 596     snprintf(ErrMsg, 1023, "%s: Line %d, %s", it8->FileStack[it8 ->IncludeSP]->FileName, it8->lineno, Buffer);
 597     ErrMsg[1023] = 0;
 598     it8->sy = SSYNERROR;
 599     cmsSignalError(it8 ->ContextID, cmsERROR_CORRUPTION_DETECTED, "%s", ErrMsg);
 600     return FALSE;
 601 }
 602 
 603 // Check if current symbol is same as specified. issue an error else.
 604 static
 605 cmsBool Check(cmsIT8* it8, SYMBOL sy, const char* Err)
 606 {
 607         if (it8 -> sy != sy)
 608                 return SynError(it8, NoMeta(Err));
 609         return TRUE;
 610 }
 611 
 612 // Read Next character from stream
 613 static
 614 void NextCh(cmsIT8* it8)
 615 {
 616     if (it8 -> FileStack[it8 ->IncludeSP]->Stream) {
 617 
 618         it8 ->ch = fgetc(it8 ->FileStack[it8 ->IncludeSP]->Stream);
 619 
 620         if (feof(it8 -> FileStack[it8 ->IncludeSP]->Stream))  {
 621 
 622             if (it8 ->IncludeSP > 0) {
 623 
 624                 fclose(it8 ->FileStack[it8->IncludeSP--]->Stream);
 625                 it8 -> ch = ' ';                            // Whitespace to be ignored
 626 
 627             } else
 628                 it8 ->ch = 0;   // EOF
 629         }
 630     }
 631     else {
 632         it8->ch = *it8->Source;
 633         if (it8->ch) it8->Source++;
 634     }
 635 }
 636 
 637 
 638 // Try to see if current identifier is a keyword, if so return the referred symbol
 639 static
 640 SYMBOL BinSrchKey(const char *id, int NumKeys, const KEYWORD* TabKeys)
 641 {
 642     int l = 1;
 643     int r = NumKeys;
 644     int x, res;
 645 
 646     while (r >= l)
 647     {
 648         x = (l+r)/2;
 649         res = cmsstrcasecmp(id, TabKeys[x-1].id);
 650         if (res == 0) return TabKeys[x-1].sy;
 651         if (res < 0) r = x - 1;
 652         else l = x + 1;
 653     }
 654 
 655     return SUNDEFINED;
 656 }
 657 
 658 
 659 // 10 ^n
 660 static
 661 cmsFloat64Number xpow10(int n)
 662 {
 663     return pow(10, (cmsFloat64Number) n);
 664 }
 665 
 666 
 667 //  Reads a Real number, tries to follow from integer number
 668 static
 669 void ReadReal(cmsIT8* it8, cmsInt32Number inum)
 670 {
 671     it8->dnum = (cmsFloat64Number)inum;
 672 
 673     while (isdigit(it8->ch)) {
 674 
 675         it8->dnum = (cmsFloat64Number)it8->dnum * 10.0 + (cmsFloat64Number)(it8->ch - '0');
 676         NextCh(it8);
 677     }
 678 
 679     if (it8->ch == '.') {        // Decimal point
 680 
 681         cmsFloat64Number frac = 0.0;      // fraction
 682         int prec = 0;                     // precision
 683 
 684         NextCh(it8);               // Eats dec. point
 685 
 686         while (isdigit(it8->ch)) {
 687 
 688             frac = frac * 10.0 + (cmsFloat64Number)(it8->ch - '0');
 689             prec++;
 690             NextCh(it8);
 691         }
 692 
 693         it8->dnum = it8->dnum + (frac / xpow10(prec));
 694     }
 695 
 696     // Exponent, example 34.00E+20
 697     if (toupper(it8->ch) == 'E') {
 698 
 699         cmsInt32Number e;
 700         cmsInt32Number sgn;
 701 
 702         NextCh(it8); sgn = 1;
 703 
 704         if (it8->ch == '-') {
 705 
 706             sgn = -1; NextCh(it8);
 707         }
 708         else
 709             if (it8->ch == '+') {
 710 
 711                 sgn = +1;
 712                 NextCh(it8);
 713             }
 714 
 715         e = 0;
 716         while (isdigit(it8->ch)) {
 717 
 718             cmsInt32Number digit = (it8->ch - '0');
 719 
 720             if ((cmsFloat64Number)e * 10.0 + (cmsFloat64Number)digit < (cmsFloat64Number)+2147483647.0)
 721                 e = e * 10 + digit;
 722 
 723             NextCh(it8);
 724         }
 725 
 726         e = sgn*e;
 727         it8->dnum = it8->dnum * xpow10(e);
 728     }
 729 }
 730 
 731 // Parses a float number
 732 // This can not call directly atof because it uses locale dependent
 733 // parsing, while CCMX files always use . as decimal separator
 734 static
 735 cmsFloat64Number ParseFloatNumber(const char *Buffer)
 736 {
 737     cmsFloat64Number dnum = 0.0;
 738     int sign = 1;
 739 
 740     // keep safe
 741     if (Buffer == NULL) return 0.0;
 742 
 743     if (*Buffer == '-' || *Buffer == '+') {
 744 
 745         sign = (*Buffer == '-') ? -1 : 1;
 746         Buffer++;
 747     }
 748 
 749 
 750     while (*Buffer && isdigit((int)*Buffer)) {
 751 
 752         dnum = dnum * 10.0 + (*Buffer - '0');
 753         if (*Buffer) Buffer++;
 754     }
 755 
 756     if (*Buffer == '.') {
 757 
 758         cmsFloat64Number frac = 0.0;      // fraction
 759         int prec = 0;                     // precision
 760 
 761         if (*Buffer) Buffer++;
 762 
 763         while (*Buffer && isdigit((int)*Buffer)) {
 764 
 765             frac = frac * 10.0 + (*Buffer - '0');
 766             prec++;
 767             if (*Buffer) Buffer++;
 768         }
 769 
 770         dnum = dnum + (frac / xpow10(prec));
 771     }
 772 
 773     // Exponent, example 34.00E+20
 774     if (*Buffer && toupper(*Buffer) == 'E') {
 775 
 776         int e;
 777         int sgn;
 778 
 779         if (*Buffer) Buffer++;
 780         sgn = 1;
 781 
 782         if (*Buffer == '-') {
 783 
 784             sgn = -1;
 785             if (*Buffer) Buffer++;
 786         }
 787         else
 788             if (*Buffer == '+') {
 789 
 790                 sgn = +1;
 791                 if (*Buffer) Buffer++;
 792             }
 793 
 794         e = 0;
 795         while (*Buffer && isdigit((int)*Buffer)) {
 796 
 797             cmsInt32Number digit = (*Buffer - '0');
 798 
 799             if ((cmsFloat64Number)e * 10.0 + digit < (cmsFloat64Number)+2147483647.0)
 800                 e = e * 10 + digit;
 801 
 802             if (*Buffer) Buffer++;
 803         }
 804 
 805         e = sgn*e;
 806         dnum = dnum * xpow10(e);
 807     }
 808 
 809     return sign * dnum;
 810 }
 811 
 812 
 813 // Reads a string, special case to avoid infinite recursion on .include
 814 static
 815 void InStringSymbol(cmsIT8* it8)
 816 {
 817     while (isseparator(it8->ch))
 818         NextCh(it8);
 819 
 820     if (it8->ch == '\'' || it8->ch == '\"')
 821     {
 822         int sng;
 823 
 824         sng = it8->ch;
 825         StringClear(it8->str);
 826 
 827         NextCh(it8);
 828 
 829         while (it8->ch != sng) {
 830 
 831             if (it8->ch == '\n' || it8->ch == '\r' || it8->ch == 0) break;
 832             else {
 833                 StringAppend(it8->str, (char)it8->ch);
 834                 NextCh(it8);
 835             }
 836         }
 837 
 838         it8->sy = SSTRING;
 839         NextCh(it8);
 840     }
 841     else
 842         SynError(it8, "String expected");
 843 
 844 }
 845 
 846 // Reads next symbol
 847 static
 848 void InSymbol(cmsIT8* it8)
 849 {
 850     SYMBOL key;
 851 
 852     do {
 853 
 854         while (isseparator(it8->ch))
 855             NextCh(it8);
 856 
 857         if (isfirstidchar(it8->ch)) {          // Identifier
 858 
 859             StringClear(it8->id);
 860 
 861             do {
 862 
 863                 StringAppend(it8->id, (char) it8->ch);
 864 
 865                 NextCh(it8);
 866 
 867             } while (isidchar(it8->ch));
 868 
 869 
 870             key = BinSrchKey(StringPtr(it8->id),
 871                     it8->IsCUBE ? NUMKEYS_CUBE : NUMKEYS_IT8,
 872                     it8->IsCUBE ? TabKeysCUBE : TabKeysIT8);
 873             if (key == SUNDEFINED) it8->sy = SIDENT;
 874             else it8->sy = key;
 875 
 876         }
 877         else                         // Is a number?
 878             if (isdigit(it8->ch) || it8->ch == '.' || it8->ch == '-' || it8->ch == '+')
 879             {
 880                 int sign = 1;
 881 
 882                 if (it8->ch == '-') {
 883                     sign = -1;
 884                     NextCh(it8);
 885                 }
 886 
 887                 it8->inum = 0;
 888                 it8->sy   = SINUM;
 889 
 890                 if (it8->ch == '0') {          // 0xnnnn (Hexa) or 0bnnnn (Binary)
 891 
 892                     NextCh(it8);
 893                     if (toupper(it8->ch) == 'X') {
 894 
 895                         int j;
 896 
 897                         NextCh(it8);
 898                         while (isxdigit(it8->ch))
 899                         {
 900                             it8->ch = toupper(it8->ch);
 901                             if (it8->ch >= 'A' && it8->ch <= 'F')  j = it8->ch -'A'+10;
 902                             else j = it8->ch - '0';
 903 
 904                             if ((cmsFloat64Number) it8->inum * 16.0 + (cmsFloat64Number) j > (cmsFloat64Number)+2147483647.0)
 905                             {
 906                                 SynError(it8, "Invalid hexadecimal number");
 907                                 it8->sy = SEOF;
 908                                 return;
 909                             }
 910 
 911                             it8->inum = it8->inum * 16 + j;
 912                             NextCh(it8);
 913                         }
 914                         return;
 915                     }
 916 
 917                     if (toupper(it8->ch) == 'B') {  // Binary
 918 
 919                         int j;
 920 
 921                         NextCh(it8);
 922                         while (it8->ch == '0' || it8->ch == '1')
 923                         {
 924                             j = it8->ch - '0';
 925 
 926                             if ((cmsFloat64Number) it8->inum * 2.0 + j > (cmsFloat64Number)+2147483647.0)
 927                             {
 928                                 SynError(it8, "Invalid binary number");
 929                                 it8->sy = SEOF;
 930                                 return;
 931                             }
 932 
 933                             it8->inum = it8->inum * 2 + j;
 934                             NextCh(it8);
 935                         }
 936                         return;
 937                     }
 938                 }
 939 
 940 
 941                 while (isdigit(it8->ch)) {
 942 
 943                     cmsInt32Number digit = (it8->ch - '0');
 944 
 945                     if ((cmsFloat64Number) it8->inum * 10.0 + (cmsFloat64Number) digit > (cmsFloat64Number) +2147483647.0) {
 946                         ReadReal(it8, it8->inum);
 947                         it8->sy = SDNUM;
 948                         it8->dnum *= sign;
 949                         return;
 950                     }
 951 
 952                     it8->inum = it8->inum * 10 + digit;
 953                     NextCh(it8);
 954                 }
 955 
 956                 if (it8->ch == '.') {
 957 
 958                     ReadReal(it8, it8->inum);
 959                     it8->sy = SDNUM;
 960                     it8->dnum *= sign;
 961                     return;
 962                 }
 963 
 964                 it8 -> inum *= sign;
 965 
 966                 // Special case. Numbers followed by letters are taken as identifiers
 967 
 968                 if (isidchar(it8 ->ch)) {
 969 
 970                     char buffer[127];
 971 
 972                     if (it8 ->sy == SINUM) {
 973 
 974                         snprintf(buffer, sizeof(buffer), "%d", it8->inum);
 975                     }
 976                     else {
 977 
 978                         snprintf(buffer, sizeof(buffer), it8 ->DoubleFormatter, it8->dnum);
 979                     }
 980 
 981                     StringClear(it8->id);
 982                     StringCat(it8->id, buffer);
 983 
 984                     do {
 985 
 986                         StringAppend(it8->id, (char) it8->ch);
 987 
 988                         NextCh(it8);
 989 
 990                     } while (isidchar(it8->ch));
 991 
 992                     it8->sy = SIDENT;
 993                 }
 994                 return;
 995 
 996             }
 997             else
 998                 switch ((int) it8->ch) {
 999 
1000         // Eof stream markers
1001         case '\x1a':
1002         case 0:
1003         case -1:
1004             it8->sy = SEOF;
1005             break;
1006 
1007 
1008         // Next line
1009         case '\r':
1010             NextCh(it8);
1011             if (it8->ch == '\n')
1012                 NextCh(it8);
1013             it8->sy = SEOLN;
1014             it8->lineno++;
1015             break;
1016 
1017         case '\n':
1018             NextCh(it8);
1019             it8->sy = SEOLN;
1020             it8->lineno++;
1021             break;
1022 
1023         // Comment
1024         case '#':
1025             NextCh(it8);
1026             while (it8->ch && it8->ch != '\n' && it8->ch != '\r')
1027                 NextCh(it8);
1028 
1029             it8->sy = SCOMMENT;
1030             break;
1031 
1032         // String.
1033         case '\'':
1034         case '\"':
1035             InStringSymbol(it8);
1036             break;
1037 
1038 
1039         default:
1040             SynError(it8, "Unrecognized character: 0x%x", it8 ->ch);
1041             it8->sy = SEOF;
1042             return;
1043             }
1044 
1045     } while (it8->sy == SCOMMENT);
1046 
1047     // Handle the include special token
1048 
1049     if (it8 -> sy == SINCLUDE) {
1050 
1051                 FILECTX* FileNest;
1052 
1053                 if(it8 -> IncludeSP >= (MAXINCLUDE-1)) {
1054 
1055                     SynError(it8, "Too many recursion levels");
1056                     it8->sy = SEOF;
1057                     return;
1058                 }
1059 
1060                 InStringSymbol(it8);
1061                 if (!Check(it8, SSTRING, "Filename expected"))
1062                 {
1063                     it8->sy = SEOF;
1064                     return;
1065                 }
1066 
1067                 FileNest = it8 -> FileStack[it8 -> IncludeSP + 1];
1068                 if(FileNest == NULL) {
1069 
1070                     FileNest = it8 ->FileStack[it8 -> IncludeSP + 1] = (FILECTX*)AllocChunk(it8, sizeof(FILECTX));
1071                     if (FileNest == NULL) {
1072                         SynError(it8, "Out of memory");
1073                         it8->sy = SEOF;
1074                         return;
1075                     }
1076                 }
1077 
1078                 if (BuildAbsolutePath(StringPtr(it8->str),
1079                                       it8->FileStack[it8->IncludeSP]->FileName,
1080                                       FileNest->FileName, cmsMAX_PATH-1) == FALSE) {
1081                     SynError(it8, "File path too long");
1082                     it8->sy = SEOF;
1083                     return;
1084                 }
1085 
1086                 FileNest->Stream = fopen(FileNest->FileName, "rt");
1087                 if (FileNest->Stream == NULL) {
1088 
1089                         SynError(it8, "File %s not found", FileNest->FileName);
1090                         it8->sy = SEOF;
1091                         return;
1092                 }
1093                 it8->IncludeSP++;
1094 
1095                 it8 ->ch = ' ';
1096                 InSymbol(it8);
1097     }
1098 
1099 }
1100 
1101 // Checks end of line separator
1102 static
1103 cmsBool CheckEOLN(cmsIT8* it8)
1104 {
1105         if (!Check(it8, SEOLN, "Expected separator")) return FALSE;
1106         while (it8 -> sy == SEOLN)
1107                         InSymbol(it8);
1108         return TRUE;
1109 
1110 }
1111 
1112 // Skip a symbol
1113 
1114 static
1115 void Skip(cmsIT8* it8, SYMBOL sy)
1116 {
1117         if (it8->sy == sy && it8->sy != SEOF)
1118                         InSymbol(it8);
1119 }
1120 
1121 
1122 // Skip multiple EOLN
1123 static
1124 void SkipEOLN(cmsIT8* it8)
1125 {
1126     while (it8->sy == SEOLN) {
1127              InSymbol(it8);
1128     }
1129 }
1130 
1131 
1132 // Returns a string holding current value
1133 static
1134 cmsBool GetVal(cmsIT8* it8, char* Buffer, cmsUInt32Number max, const char* ErrorTitle)
1135 {
1136     switch (it8->sy) {
1137 
1138     case SEOLN:   // Empty value
1139                   Buffer[0]=0;
1140                   break;
1141     case SIDENT:  strncpy(Buffer, StringPtr(it8->id), max);
1142                   Buffer[max-1]=0;
1143                   break;
1144     case SINUM:   snprintf(Buffer, max, "%d", it8 -> inum); break;
1145     case SDNUM:   snprintf(Buffer, max, it8->DoubleFormatter, it8 -> dnum); break;
1146     case SSTRING: strncpy(Buffer, StringPtr(it8->str), max);
1147                   Buffer[max-1] = 0;
1148                   break;
1149 
1150 
1151     default:
1152          return SynError(it8, "%s", ErrorTitle);
1153     }
1154 
1155     Buffer[max] = 0;
1156     return TRUE;
1157 }
1158 
1159 // ---------------------------------------------------------- Table
1160 
1161 static
1162 TABLE* GetTable(cmsIT8* it8)
1163 {
1164    if ((it8 -> nTable >= it8 ->TablesCount)) {
1165 
1166            SynError(it8, "Table %d out of sequence", it8 -> nTable);
1167            return it8 -> Tab;
1168    }
1169 
1170    return it8 ->Tab + it8 ->nTable;
1171 }
1172 
1173 // ---------------------------------------------------------- Memory management
1174 
1175 
1176 // Frees an allocator and owned memory
1177 void CMSEXPORT cmsIT8Free(cmsHANDLE hIT8)
1178 {
1179    cmsIT8* it8 = (cmsIT8*) hIT8;
1180 
1181     if (it8 == NULL)
1182         return;
1183 
1184     if (it8->MemorySink) {
1185 
1186         OWNEDMEM* p;
1187         OWNEDMEM* n;
1188 
1189         for (p = it8->MemorySink; p != NULL; p = n) {
1190 
1191             n = p->Next;
1192             if (p->Ptr) _cmsFree(it8 ->ContextID, p->Ptr);
1193             _cmsFree(it8 ->ContextID, p);
1194         }
1195     }
1196 
1197     if (it8->MemoryBlock)
1198         _cmsFree(it8 ->ContextID, it8->MemoryBlock);
1199 
1200     _cmsFree(it8 ->ContextID, it8);
1201 }
1202 
1203 
1204 // Allocates a chunk of data, keep linked list
1205 static
1206 void* AllocBigBlock(cmsIT8* it8, cmsUInt32Number size)
1207 {
1208     OWNEDMEM* ptr1;
1209     void* ptr = _cmsMallocZero(it8->ContextID, size);
1210 
1211     if (ptr != NULL) {
1212 
1213         ptr1 = (OWNEDMEM*) _cmsMallocZero(it8 ->ContextID, sizeof(OWNEDMEM));
1214 
1215         if (ptr1 == NULL) {
1216 
1217             _cmsFree(it8 ->ContextID, ptr);
1218             return NULL;
1219         }
1220 
1221         ptr1-> Ptr        = ptr;
1222         ptr1-> Next       = it8 -> MemorySink;
1223         it8 -> MemorySink = ptr1;
1224     }
1225 
1226     return ptr;
1227 }
1228 
1229 
1230 // Suballocator.
1231 static
1232 void* AllocChunk(cmsIT8* it8, cmsUInt32Number size)
1233 {
1234     cmsUInt32Number Free = it8 ->Allocator.BlockSize - it8 ->Allocator.Used;
1235     cmsUInt8Number* ptr;
1236 
1237     size = _cmsALIGNMEM(size);
1238 
1239     if (size > Free) {
1240 
1241         if (it8 -> Allocator.BlockSize == 0)
1242 
1243                 it8 -> Allocator.BlockSize = 20*1024;
1244         else
1245                 it8 ->Allocator.BlockSize *= 2;
1246 
1247         if (it8 ->Allocator.BlockSize < size)
1248                 it8 ->Allocator.BlockSize = size;
1249 
1250         it8 ->Allocator.Used = 0;
1251         it8 ->Allocator.Block = (cmsUInt8Number*) AllocBigBlock(it8, it8 ->Allocator.BlockSize);
1252     }
1253 
1254     if (it8->Allocator.Block == NULL)
1255         return NULL;
1256 
1257     ptr = it8 ->Allocator.Block + it8 ->Allocator.Used;
1258     it8 ->Allocator.Used += size;
1259 
1260     return (void*) ptr;
1261 
1262 }
1263 
1264 
1265 // Allocates a string
1266 static
1267 char *AllocString(cmsIT8* it8, const char* str)
1268 {
1269     cmsUInt32Number Size = (cmsUInt32Number) strlen(str)+1;
1270     char *ptr;
1271 
1272 
1273     ptr = (char *) AllocChunk(it8, Size);
1274     if (ptr) memcpy(ptr, str, Size-1);
1275 
1276     return ptr;
1277 }
1278 
1279 // Searches through linked list
1280 
1281 static
1282 cmsBool IsAvailableOnList(KEYVALUE* p, const char* Key, const char* Subkey, KEYVALUE** LastPtr)
1283 {
1284     if (LastPtr) *LastPtr = p;
1285 
1286     for (;  p != NULL; p = p->Next) {
1287 
1288         if (LastPtr) *LastPtr = p;
1289 
1290         if (*Key != '#') { // Comments are ignored
1291 
1292             if (cmsstrcasecmp(Key, p->Keyword) == 0)
1293                 break;
1294         }
1295     }
1296 
1297     if (p == NULL)
1298         return FALSE;
1299 
1300     if (Subkey == 0)
1301         return TRUE;
1302 
1303     for (; p != NULL; p = p->NextSubkey) {
1304 
1305         if (p ->Subkey == NULL) continue;
1306 
1307         if (LastPtr) *LastPtr = p;
1308 
1309         if (cmsstrcasecmp(Subkey, p->Subkey) == 0)
1310             return TRUE;
1311     }
1312 
1313     return FALSE;
1314 }
1315 
1316 
1317 
1318 // Add a property into a linked list
1319 static
1320 KEYVALUE* AddToList(cmsIT8* it8, KEYVALUE** Head, const char *Key, const char *Subkey, const char* xValue, WRITEMODE WriteAs)
1321 {
1322     KEYVALUE* p;
1323     KEYVALUE* last;
1324 
1325 
1326     // Check if property is already in list
1327 
1328     if (IsAvailableOnList(*Head, Key, Subkey, &p)) {
1329 
1330         // This may work for editing properties
1331 
1332         if (cmsstrcasecmp(Key, "NUMBER_OF_FIELDS") == 0 ||
1333             cmsstrcasecmp(Key, "NUMBER_OF_SETS") == 0) {
1334 
1335             SynError(it8, "duplicate key <%s>", Key);
1336             return NULL;
1337         }
1338     }
1339     else {
1340 
1341         last = p;
1342 
1343         // Allocate the container
1344         p = (KEYVALUE*) AllocChunk(it8, sizeof(KEYVALUE));
1345         if (p == NULL)
1346         {
1347             SynError(it8, "AddToList: out of memory");
1348             return NULL;
1349         }
1350 
1351         // Store name and value
1352         p->Keyword = AllocString(it8, Key);
1353         p->Subkey = (Subkey == NULL) ? NULL : AllocString(it8, Subkey);
1354 
1355         // Keep the container in our list
1356         if (*Head == NULL) {
1357             *Head = p;
1358         }
1359         else
1360         {
1361             if (Subkey != NULL && last != NULL) {
1362 
1363                 last->NextSubkey = p;
1364 
1365                 // If Subkey is not null, then last is the last property with the same key,
1366                 // but not necessarily is the last property in the list, so we need to move
1367                 // to the actual list end
1368                 while (last->Next != NULL)
1369                          last = last->Next;
1370             }
1371 
1372             if (last != NULL) last->Next = p;
1373         }
1374 
1375         p->Next    = NULL;
1376         p->NextSubkey = NULL;
1377     }
1378 
1379     p->WriteAs = WriteAs;
1380 
1381     if (xValue != NULL) {
1382 
1383         p->Value   = AllocString(it8, xValue);
1384     }
1385     else {
1386         p->Value   = NULL;
1387     }
1388 
1389     return p;
1390 }
1391 
1392 static
1393 KEYVALUE* AddAvailableProperty(cmsIT8* it8, const char* Key, WRITEMODE as)
1394 {
1395     return AddToList(it8, &it8->ValidKeywords, Key, NULL, NULL, as);
1396 }
1397 
1398 
1399 static
1400 KEYVALUE* AddAvailableSampleID(cmsIT8* it8, const char* Key)
1401 {
1402     return AddToList(it8, &it8->ValidSampleID, Key, NULL, NULL, WRITE_UNCOOKED);
1403 }
1404 
1405 
1406 static
1407 void AllocTable(cmsIT8* it8)
1408 {
1409     TABLE* t;
1410 
1411     t = it8 ->Tab + it8 ->TablesCount;
1412 
1413     t->HeaderList = NULL;
1414     t->DataFormat = NULL;
1415     t->Data       = NULL;
1416 
1417     it8 ->TablesCount++;
1418 }
1419 
1420 
1421 cmsInt32Number CMSEXPORT cmsIT8SetTable(cmsHANDLE  IT8, cmsUInt32Number nTable)
1422 {
1423      cmsIT8* it8 = (cmsIT8*) IT8;
1424 
1425      if (nTable >= it8 ->TablesCount) {
1426 
1427          if (nTable == it8 ->TablesCount) {
1428 
1429              AllocTable(it8);
1430          }
1431          else {
1432              SynError(it8, "Table %d is out of sequence", nTable);
1433              return -1;
1434          }
1435      }
1436 
1437      it8 ->nTable = nTable;
1438 
1439      return (cmsInt32Number) nTable;
1440 }
1441 
1442 
1443 
1444 // Init an empty container
1445 cmsHANDLE  CMSEXPORT cmsIT8Alloc(cmsContext ContextID)
1446 {
1447     cmsIT8* it8;
1448     cmsUInt32Number i;
1449 
1450     it8 = (cmsIT8*) _cmsMallocZero(ContextID, sizeof(cmsIT8));
1451     if (it8 == NULL) return NULL;
1452 
1453     AllocTable(it8);
1454 
1455     it8->MemoryBlock = NULL;
1456     it8->MemorySink  = NULL;
1457 
1458     it8->IsCUBE = FALSE;
1459 
1460     it8 ->nTable = 0;
1461 
1462     it8->ContextID = ContextID;
1463     it8->Allocator.Used = 0;
1464     it8->Allocator.Block = NULL;
1465     it8->Allocator.BlockSize = 0;
1466 
1467     it8->ValidKeywords = NULL;
1468     it8->ValidSampleID = NULL;
1469 
1470     it8 -> sy = SUNDEFINED;
1471     it8 -> ch = ' ';
1472     it8 -> Source = NULL;
1473     it8 -> inum = 0;
1474     it8 -> dnum = 0.0;
1475 
1476     it8->FileStack[0] = (FILECTX*)AllocChunk(it8, sizeof(FILECTX));
1477     it8->IncludeSP   = 0;
1478     it8 -> lineno = 1;
1479 
1480     it8->id = StringAlloc(it8, MAXSTR);
1481     it8->str = StringAlloc(it8, MAXSTR);
1482 
1483     strcpy(it8->DoubleFormatter, DEFAULT_DBL_FORMAT);
1484     cmsIT8SetSheetType((cmsHANDLE) it8, "CGATS.17");
1485 
1486     // Initialize predefined properties & data
1487 
1488     for (i=0; i < NUMPREDEFINEDPROPS; i++)
1489             AddAvailableProperty(it8, PredefinedProperties[i].id, PredefinedProperties[i].as);
1490 
1491     for (i=0; i < NUMPREDEFINEDSAMPLEID; i++)
1492             AddAvailableSampleID(it8, PredefinedSampleID[i]);
1493 
1494 
1495    return (cmsHANDLE) it8;
1496 }
1497 
1498 
1499 const char* CMSEXPORT cmsIT8GetSheetType(cmsHANDLE hIT8)
1500 {
1501         return GetTable((cmsIT8*) hIT8)->SheetType;
1502 }
1503 
1504 cmsBool CMSEXPORT cmsIT8SetSheetType(cmsHANDLE hIT8, const char* Type)
1505 {
1506         TABLE* t = GetTable((cmsIT8*) hIT8);
1507 
1508         strncpy(t ->SheetType, Type, MAXSTR-1);
1509         t ->SheetType[MAXSTR-1] = 0;
1510         return TRUE;
1511 }
1512 
1513 cmsBool CMSEXPORT cmsIT8SetComment(cmsHANDLE hIT8, const char* Val)
1514 {
1515     cmsIT8* it8 = (cmsIT8*) hIT8;
1516 
1517     if (!Val) return FALSE;
1518     if (!*Val) return FALSE;
1519 
1520     return AddToList(it8, &GetTable(it8)->HeaderList, "# ", NULL, Val, WRITE_UNCOOKED) != NULL;
1521 }
1522 
1523 // Sets a property
1524 cmsBool CMSEXPORT cmsIT8SetPropertyStr(cmsHANDLE hIT8, const char* Key, const char *Val)
1525 {
1526     cmsIT8* it8 = (cmsIT8*) hIT8;
1527 
1528     if (!Val) return FALSE;
1529     if (!*Val) return FALSE;
1530 
1531     return AddToList(it8, &GetTable(it8)->HeaderList, Key, NULL, Val, WRITE_STRINGIFY) != NULL;
1532 }
1533 
1534 cmsBool CMSEXPORT cmsIT8SetPropertyDbl(cmsHANDLE hIT8, const char* cProp, cmsFloat64Number Val)
1535 {
1536     cmsIT8* it8 = (cmsIT8*) hIT8;
1537     char Buffer[1024];
1538 
1539     snprintf(Buffer, 1023, it8->DoubleFormatter, Val);
1540 
1541     return AddToList(it8, &GetTable(it8)->HeaderList, cProp, NULL, Buffer, WRITE_UNCOOKED) != NULL;
1542 }
1543 
1544 cmsBool CMSEXPORT cmsIT8SetPropertyHex(cmsHANDLE hIT8, const char* cProp, cmsUInt32Number Val)
1545 {
1546     cmsIT8* it8 = (cmsIT8*) hIT8;
1547     char Buffer[1024];
1548 
1549     snprintf(Buffer, 1023, "%u", Val);
1550 
1551     return AddToList(it8, &GetTable(it8)->HeaderList, cProp, NULL, Buffer, WRITE_HEXADECIMAL) != NULL;
1552 }
1553 
1554 cmsBool CMSEXPORT cmsIT8SetPropertyUncooked(cmsHANDLE hIT8, const char* Key, const char* Buffer)
1555 {
1556     cmsIT8* it8 = (cmsIT8*) hIT8;
1557 
1558     return AddToList(it8, &GetTable(it8)->HeaderList, Key, NULL, Buffer, WRITE_UNCOOKED) != NULL;
1559 }
1560 
1561 cmsBool CMSEXPORT cmsIT8SetPropertyMulti(cmsHANDLE hIT8, const char* Key, const char* SubKey, const char *Buffer)
1562 {
1563     cmsIT8* it8 = (cmsIT8*) hIT8;
1564 
1565     return AddToList(it8, &GetTable(it8)->HeaderList, Key, SubKey, Buffer, WRITE_PAIR) != NULL;
1566 }
1567 
1568 // Gets a property
1569 const char* CMSEXPORT cmsIT8GetProperty(cmsHANDLE hIT8, const char* Key)
1570 {
1571     cmsIT8* it8 = (cmsIT8*) hIT8;
1572     KEYVALUE* p;
1573 
1574     if (IsAvailableOnList(GetTable(it8) -> HeaderList, Key, NULL, &p))
1575     {
1576         return p -> Value;
1577     }
1578     return NULL;
1579 }
1580 
1581 
1582 cmsFloat64Number CMSEXPORT cmsIT8GetPropertyDbl(cmsHANDLE hIT8, const char* cProp)
1583 {
1584     const char *v = cmsIT8GetProperty(hIT8, cProp);
1585 
1586     if (v == NULL) return 0.0;
1587 
1588     return ParseFloatNumber(v);
1589 }
1590 
1591 const char* CMSEXPORT cmsIT8GetPropertyMulti(cmsHANDLE hIT8, const char* Key, const char *SubKey)
1592 {
1593     cmsIT8* it8 = (cmsIT8*) hIT8;
1594     KEYVALUE* p;
1595 
1596     if (IsAvailableOnList(GetTable(it8) -> HeaderList, Key, SubKey, &p)) {
1597         return p -> Value;
1598     }
1599     return NULL;
1600 }
1601 
1602 // ----------------------------------------------------------------- Datasets
1603 
1604 // A safe atoi that returns 0 when NULL input is given
1605 static
1606 cmsInt32Number satoi(const char* b)
1607 {
1608     int n;
1609 
1610     if (b == NULL) return 0;
1611 
1612     n = atoi(b);
1613     if (n > 0x7fffffffL) return 0x7fffffffL;
1614     if (n < -0x7ffffffeL) return -0x7ffffffeL;
1615 
1616     return (cmsInt32Number)n;
1617 }
1618 
1619 
1620 static
1621 cmsBool AllocateDataFormat(cmsIT8* it8)
1622 {
1623     TABLE* t = GetTable(it8);
1624 
1625     if (t -> DataFormat) return TRUE;    // Already allocated
1626 
1627     t -> nSamples  = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
1628 
1629     if (t -> nSamples <= 0) {
1630 
1631         SynError(it8, "AllocateDataFormat: Unknown NUMBER_OF_FIELDS");
1632         return FALSE;
1633         }
1634 
1635     t -> DataFormat = (char**) AllocChunk (it8, ((cmsUInt32Number) t->nSamples + 1) * sizeof(char *));
1636     if (t->DataFormat == NULL) {
1637 
1638         SynError(it8, "AllocateDataFormat: Unable to allocate dataFormat array");
1639         return FALSE;
1640     }
1641 
1642     return TRUE;
1643 }
1644 
1645 static
1646 const char *GetDataFormat(cmsIT8* it8, int n)
1647 {
1648     TABLE* t = GetTable(it8);
1649 
1650     if (t->DataFormat)
1651         return t->DataFormat[n];
1652 
1653     return NULL;
1654 }
1655 
1656 static
1657 cmsBool SetDataFormat(cmsIT8* it8, int n, const char *label)
1658 {
1659     TABLE* t = GetTable(it8);
1660 
1661     if (!t->DataFormat) {
1662 
1663         if (!AllocateDataFormat(it8))
1664             return FALSE;
1665     }
1666 
1667     if (n > t -> nSamples) {
1668         SynError(it8, "More than NUMBER_OF_FIELDS fields.");
1669         return FALSE;
1670     }
1671 
1672     if (t->DataFormat) {
1673         t->DataFormat[n] = AllocString(it8, label);
1674         if (t->DataFormat[n] == NULL) return FALSE;
1675     }
1676 
1677     return TRUE;
1678 }
1679 
1680 
1681 cmsBool CMSEXPORT cmsIT8SetDataFormat(cmsHANDLE  h, int n, const char *Sample)
1682 {
1683     cmsIT8* it8 = (cmsIT8*)h;
1684     return SetDataFormat(it8, n, Sample);
1685 }
1686 
1687 // Convert to binary
1688 static
1689 const char* satob(const char* v)
1690 {
1691     cmsUInt32Number x;
1692     static char buf[33];
1693     char *s = buf + 33;
1694 
1695     if (v == NULL) return "0";
1696 
1697     x = atoi(v);
1698     *--s = 0;
1699     if (!x) *--s = '0';
1700     for (; x; x /= 2) *--s = '0' + x%2;
1701 
1702     return s;
1703 }
1704 
1705 
1706 static
1707 cmsBool AllocateDataSet(cmsIT8* it8)
1708 {
1709     TABLE* t = GetTable(it8);
1710 
1711     if (t -> Data) return TRUE;    // Already allocated
1712 
1713     t-> nSamples   = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
1714     t-> nPatches   = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_SETS"));
1715 
1716     if (t -> nSamples < 0 || t->nSamples > 0x7ffe || t->nPatches < 0 || t->nPatches > 0x7ffe)
1717     {
1718         SynError(it8, "AllocateDataSet: too much data");
1719         return FALSE;
1720     }
1721     else {
1722         // Some dumb analizers warns of possible overflow here, just take a look couple of lines above.
1723         t->Data = (char**)AllocChunk(it8, ((cmsUInt32Number)t->nSamples + 1) * ((cmsUInt32Number)t->nPatches + 1) * sizeof(char*));
1724         if (t->Data == NULL) {
1725 
1726             SynError(it8, "AllocateDataSet: Unable to allocate data array");
1727             return FALSE;
1728         }
1729     }
1730 
1731     return TRUE;
1732 }
1733 
1734 static
1735 char* GetData(cmsIT8* it8, int nSet, int nField)
1736 {
1737     TABLE* t = GetTable(it8);
1738     int nSamples    = t -> nSamples;
1739     int nPatches    = t -> nPatches;
1740 
1741     if (nSet < 0 || nSet >= nPatches || nField < 0 || nField >= nSamples)
1742         return NULL;
1743 
1744     if (!t->Data) return NULL;
1745     return t->Data [nSet * nSamples + nField];
1746 }
1747 
1748 static
1749 cmsBool SetData(cmsIT8* it8, int nSet, int nField, const char *Val)
1750 {
1751     TABLE* t = GetTable(it8);
1752 
1753     if (!t->Data) {
1754         if (!AllocateDataSet(it8)) return FALSE;
1755     }
1756 
1757     if (!t->Data) return FALSE;
1758 
1759     if (nSet > t -> nPatches || nSet < 0) {
1760 
1761             return SynError(it8, "Patch %d out of range, there are %d patches", nSet, t -> nPatches);
1762     }
1763 
1764     if (nField > t ->nSamples || nField < 0) {
1765             return SynError(it8, "Sample %d out of range, there are %d samples", nField, t ->nSamples);
1766 
1767     }
1768 
1769     t->Data [nSet * t -> nSamples + nField] = AllocString(it8, Val);
1770     return TRUE;
1771 }
1772 
1773 
1774 // --------------------------------------------------------------- File I/O
1775 
1776 
1777 // Writes a string to file
1778 static
1779 void WriteStr(SAVESTREAM* f, const char *str)
1780 {
1781     cmsUInt32Number len;
1782 
1783     if (str == NULL)
1784         str = " ";
1785 
1786     // Length to write
1787     len = (cmsUInt32Number) strlen(str);
1788     f ->Used += len;
1789 
1790 
1791     if (f ->stream) {   // Should I write it to a file?
1792 
1793         if (fwrite(str, 1, len, f->stream) != len) {
1794             cmsSignalError(0, cmsERROR_WRITE, "Write to file error in CGATS parser");
1795             return;
1796         }
1797 
1798     }
1799     else {  // Or to a memory block?
1800 
1801         if (f ->Base) {   // Am I just counting the bytes?
1802 
1803             if (f ->Used > f ->Max) {
1804 
1805                  cmsSignalError(0, cmsERROR_WRITE, "Write to memory overflows in CGATS parser");
1806                  return;
1807             }
1808 
1809             memmove(f ->Ptr, str, len);
1810             f->Ptr += len;
1811         }
1812 
1813     }
1814 }
1815 
1816 
1817 // Write formatted
1818 
1819 static
1820 void Writef(SAVESTREAM* f, const char* frm, ...)
1821 {
1822     char Buffer[4096];
1823     va_list args;
1824 
1825     va_start(args, frm);
1826     vsnprintf(Buffer, 4095, frm, args);
1827     Buffer[4095] = 0;
1828     WriteStr(f, Buffer);
1829     va_end(args);
1830 
1831 }
1832 
1833 // Writes full header
1834 static
1835 void WriteHeader(cmsIT8* it8, SAVESTREAM* fp)
1836 {
1837     KEYVALUE* p;
1838     TABLE* t = GetTable(it8);
1839 
1840     // Writes the type
1841     WriteStr(fp, t->SheetType);
1842     WriteStr(fp, "\n");
1843 
1844     for (p = t->HeaderList; (p != NULL); p = p->Next)
1845     {
1846         if (*p ->Keyword == '#') {
1847 
1848             char* Pt;
1849 
1850             WriteStr(fp, "#\n# ");
1851             for (Pt = p ->Value; *Pt; Pt++) {
1852 
1853 
1854                 Writef(fp, "%c", *Pt);
1855 
1856                 if (*Pt == '\n') {
1857                     WriteStr(fp, "# ");
1858                 }
1859             }
1860 
1861             WriteStr(fp, "\n#\n");
1862             continue;
1863         }
1864 
1865 
1866         if (!IsAvailableOnList(it8-> ValidKeywords, p->Keyword, NULL, NULL)) {
1867 
1868 #ifdef CMS_STRICT_CGATS
1869             WriteStr(fp, "KEYWORD\t\"");
1870             WriteStr(fp, p->Keyword);
1871             WriteStr(fp, "\"\n");
1872 #endif
1873 
1874             AddAvailableProperty(it8, p->Keyword, WRITE_UNCOOKED);
1875         }
1876 
1877         WriteStr(fp, p->Keyword);
1878         if (p->Value) {
1879 
1880             switch (p ->WriteAs) {
1881 
1882             case WRITE_UNCOOKED:
1883                     Writef(fp, "\t%s", p ->Value);
1884                     break;
1885 
1886             case WRITE_STRINGIFY:
1887                     Writef(fp, "\t\"%s\"", p->Value );
1888                     break;
1889 
1890             case WRITE_HEXADECIMAL:
1891                     Writef(fp, "\t0x%X", satoi(p ->Value));
1892                     break;
1893 
1894             case WRITE_BINARY:
1895                     Writef(fp, "\t0b%s", satob(p ->Value));
1896                     break;
1897 
1898             case WRITE_PAIR:
1899                     Writef(fp, "\t\"%s,%s\"", p->Subkey, p->Value);
1900                     break;
1901 
1902             default: SynError(it8, "Unknown write mode %d", p ->WriteAs);
1903                      return;
1904             }
1905         }
1906 
1907         WriteStr (fp, "\n");
1908     }
1909 
1910 }
1911 
1912 
1913 // Writes the data format
1914 static
1915 void WriteDataFormat(SAVESTREAM* fp, cmsIT8* it8)
1916 {
1917     int i, nSamples;
1918     TABLE* t = GetTable(it8);
1919 
1920     if (!t -> DataFormat) return;
1921 
1922        WriteStr(fp, "BEGIN_DATA_FORMAT\n");
1923        WriteStr(fp, " ");
1924        nSamples = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
1925 
1926        if (nSamples <= t->nSamples) {
1927 
1928            for (i = 0; i < nSamples; i++) {
1929 
1930                WriteStr(fp, t->DataFormat[i]);
1931                WriteStr(fp, ((i == (nSamples - 1)) ? "\n" : "\t"));
1932            }
1933        }
1934 
1935        WriteStr (fp, "END_DATA_FORMAT\n");
1936 }
1937 
1938 
1939 // Writes data array
1940 static
1941 void WriteData(SAVESTREAM* fp, cmsIT8* it8)
1942 {
1943        int  i, j, nPatches;
1944        TABLE* t = GetTable(it8);
1945 
1946        if (!t->Data) return;
1947 
1948        WriteStr (fp, "BEGIN_DATA\n");
1949 
1950        nPatches = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_SETS"));
1951 
1952        if (nPatches <= t->nPatches) {
1953 
1954            for (i = 0; i < nPatches; i++) {
1955 
1956                WriteStr(fp, " ");
1957 
1958                for (j = 0; j < t->nSamples; j++) {
1959 
1960                    char* ptr = t->Data[i * t->nSamples + j];
1961 
1962                    if (ptr == NULL) WriteStr(fp, "\"\"");
1963                    else {
1964                        // If value contains whitespace, enclose within quote
1965 
1966                        if (strchr(ptr, ' ') != NULL) {
1967 
1968                            WriteStr(fp, "\"");
1969                            WriteStr(fp, ptr);
1970                            WriteStr(fp, "\"");
1971                        }
1972                        else
1973                            WriteStr(fp, ptr);
1974                    }
1975 
1976                    WriteStr(fp, ((j == (t->nSamples - 1)) ? "\n" : "\t"));
1977                }
1978            }
1979        }
1980        WriteStr (fp, "END_DATA\n");
1981 }
1982 
1983 
1984 
1985 // Saves whole file
1986 cmsBool CMSEXPORT cmsIT8SaveToFile(cmsHANDLE hIT8, const char* cFileName)
1987 {
1988     SAVESTREAM sd;
1989     cmsUInt32Number i;
1990     cmsIT8* it8 = (cmsIT8*) hIT8;
1991 
1992     memset(&sd, 0, sizeof(sd));
1993 
1994     sd.stream = fopen(cFileName, "wt");
1995     if (!sd.stream) return FALSE;
1996 
1997     for (i=0; i < it8 ->TablesCount; i++) {
1998 
1999         TABLE* t;
2000 
2001         if (cmsIT8SetTable(hIT8, i) < 0) goto Error;
2002 
2003         /**
2004         * Check for wrong data
2005         */
2006         t = GetTable(it8);
2007         if (t->Data == NULL) goto Error;
2008         if (t->DataFormat == NULL) goto Error;
2009 
2010         WriteHeader(it8, &sd);
2011         WriteDataFormat(&sd, it8);
2012         WriteData(&sd, it8);
2013     }
2014 
2015     if (fclose(sd.stream) != 0) return FALSE;
2016     return TRUE;
2017 
2018 Error:
2019     fclose(sd.stream);
2020     return FALSE;
2021 
2022 }
2023 
2024 
2025 // Saves to memory
2026 cmsBool CMSEXPORT cmsIT8SaveToMem(cmsHANDLE hIT8, void *MemPtr, cmsUInt32Number* BytesNeeded)
2027 {
2028     SAVESTREAM sd;
2029     cmsUInt32Number i;
2030     cmsIT8* it8 = (cmsIT8*) hIT8;
2031 
2032     memset(&sd, 0, sizeof(sd));
2033 
2034     sd.stream = NULL;
2035     sd.Base   = (cmsUInt8Number*) MemPtr;
2036     sd.Ptr    = sd.Base;
2037 
2038     sd.Used = 0;
2039 
2040     if (sd.Base && (*BytesNeeded > 0)) {
2041 
2042         sd.Max = (*BytesNeeded) - 1;     // Write to memory?
2043     }
2044     else
2045         sd.Max  = 0;                // Just counting the needed bytes
2046 
2047     for (i=0; i < it8 ->TablesCount; i++) {
2048 
2049         cmsIT8SetTable(hIT8, i);
2050         WriteHeader(it8, &sd);
2051         WriteDataFormat(&sd, it8);
2052         WriteData(&sd, it8);
2053     }
2054 
2055     sd.Used++;  // The \0 at the very end
2056 
2057     if (sd.Base)
2058         *sd.Ptr = 0;
2059 
2060     *BytesNeeded = sd.Used;
2061 
2062     return TRUE;
2063 }
2064 
2065 
2066 // -------------------------------------------------------------- Higher level parsing
2067 
2068 static
2069 cmsBool DataFormatSection(cmsIT8* it8)
2070 {
2071     int iField = 0;
2072     TABLE* t = GetTable(it8);
2073 
2074     InSymbol(it8);   // Eats "BEGIN_DATA_FORMAT"
2075     CheckEOLN(it8);
2076 
2077     while (it8->sy != SEND_DATA_FORMAT &&
2078         it8->sy != SEOLN &&
2079         it8->sy != SEOF &&
2080         it8->sy != SSYNERROR)  {
2081 
2082             if (it8->sy != SIDENT) {
2083 
2084                 return SynError(it8, "Sample type expected");
2085             }
2086 
2087             if (!SetDataFormat(it8, iField, StringPtr(it8->id))) return FALSE;
2088             iField++;
2089 
2090             InSymbol(it8);
2091             SkipEOLN(it8);
2092        }
2093 
2094        SkipEOLN(it8);
2095        Skip(it8, SEND_DATA_FORMAT);
2096        SkipEOLN(it8);
2097 
2098        if (iField != t ->nSamples) {
2099            SynError(it8, "Count mismatch. NUMBER_OF_FIELDS was %d, found %d\n", t ->nSamples, iField);
2100 
2101 
2102        }
2103 
2104        return TRUE;
2105 }
2106 
2107 
2108 
2109 static
2110 cmsBool DataSection (cmsIT8* it8)
2111 {
2112     int  iField = 0;
2113     int  iSet   = 0;
2114     char Buffer[256];
2115     TABLE* t = GetTable(it8);
2116 
2117     InSymbol(it8);   // Eats "BEGIN_DATA"
2118     CheckEOLN(it8);
2119 
2120     if (!t->Data) {
2121         if (!AllocateDataSet(it8)) return FALSE;
2122     }
2123 
2124     while (it8->sy != SEND_DATA && it8->sy != SEOF)
2125     {
2126         if (iField >= t -> nSamples) {
2127             iField = 0;
2128             iSet++;
2129 
2130         }
2131 
2132         if (it8->sy != SEND_DATA && it8->sy != SEOF) {
2133 
2134             switch (it8->sy)
2135             {
2136 
2137             // To keep very long data
2138             case SIDENT:
2139                 if (!SetData(it8, iSet, iField, StringPtr(it8->id)))
2140                     return FALSE;
2141                 break;
2142 
2143             case SSTRING:
2144                 if (!SetData(it8, iSet, iField, StringPtr(it8->str)))
2145                     return FALSE;
2146                 break;
2147 
2148             default:
2149 
2150             if (!GetVal(it8, Buffer, 255, "Sample data expected"))
2151                 return FALSE;
2152 
2153             if (!SetData(it8, iSet, iField, Buffer))
2154                 return FALSE;
2155             }
2156 
2157             iField++;
2158 
2159             InSymbol(it8);
2160             SkipEOLN(it8);
2161         }
2162     }
2163 
2164     SkipEOLN(it8);
2165     Skip(it8, SEND_DATA);
2166     SkipEOLN(it8);
2167 
2168     // Check for data completion.
2169 
2170     if ((iSet+1) != t -> nPatches)
2171         return SynError(it8, "Count mismatch. NUMBER_OF_SETS was %d, found %d\n", t ->nPatches, iSet+1);
2172 
2173     return TRUE;
2174 }
2175 
2176 
2177 
2178 
2179 static
2180 cmsBool HeaderSection(cmsIT8* it8)
2181 {
2182     char VarName[MAXID];
2183     char Buffer[MAXSTR];
2184     KEYVALUE* Key;
2185 
2186         while (it8->sy != SEOF &&
2187                it8->sy != SSYNERROR &&
2188                it8->sy != SBEGIN_DATA_FORMAT &&
2189                it8->sy != SBEGIN_DATA) {
2190 
2191 
2192         switch (it8 -> sy) {
2193 
2194         case SKEYWORD:
2195                 InSymbol(it8);
2196                 if (!GetVal(it8, Buffer, MAXSTR-1, "Keyword expected")) return FALSE;
2197                 if (!AddAvailableProperty(it8, Buffer, WRITE_UNCOOKED)) return FALSE;
2198                 InSymbol(it8);
2199                 break;
2200 
2201 
2202         case SDATA_FORMAT_ID:
2203                 InSymbol(it8);
2204                 if (!GetVal(it8, Buffer, MAXSTR-1, "Keyword expected")) return FALSE;
2205                 if (!AddAvailableSampleID(it8, Buffer)) return FALSE;
2206                 InSymbol(it8);
2207                 break;
2208 
2209 
2210         case SIDENT:
2211             strncpy(VarName, StringPtr(it8->id), MAXID - 1);
2212             VarName[MAXID - 1] = 0;
2213 
2214             if (!IsAvailableOnList(it8->ValidKeywords, VarName, NULL, &Key)) {
2215 
2216 #ifdef CMS_STRICT_CGATS
2217                 return SynError(it8, "Undefined keyword '%s'", VarName);
2218 #else
2219                 Key = AddAvailableProperty(it8, VarName, WRITE_UNCOOKED);
2220                 if (Key == NULL) return FALSE;
2221 #endif
2222             }
2223 
2224             InSymbol(it8);
2225             if (!GetVal(it8, Buffer, MAXSTR - 1, "Property data expected")) return FALSE;
2226 
2227             if (Key->WriteAs != WRITE_PAIR) {
2228                 AddToList(it8, &GetTable(it8)->HeaderList, VarName, NULL, Buffer,
2229                     (it8->sy == SSTRING) ? WRITE_STRINGIFY : WRITE_UNCOOKED);
2230             }
2231             else {
2232                 const char *Subkey;
2233                 char *Nextkey;
2234                 if (it8->sy != SSTRING)
2235                     return SynError(it8, "Invalid value '%s' for property '%s'.", Buffer, VarName);
2236 
2237                 // chop the string as a list of "subkey, value" pairs, using ';' as a separator
2238                 for (Subkey = Buffer; Subkey != NULL; Subkey = Nextkey)
2239                 {
2240                     char *Value, *temp;
2241 
2242                     //  identify token pair boundary
2243                     Nextkey = (char*)strchr(Subkey, ';');
2244                     if (Nextkey)
2245                         *Nextkey++ = '\0';
2246 
2247                     // for each pair, split the subkey and the value
2248                     Value = (char*)strrchr(Subkey, ',');
2249                     if (Value == NULL)
2250                         return SynError(it8, "Invalid value for property '%s'.", VarName);
2251 
2252                     // gobble the spaces before the coma, and the coma itself
2253                     temp = Value++;
2254                     do *temp-- = '\0'; while (temp >= Subkey && *temp == ' ');
2255 
2256                     // gobble any space at the right
2257                     temp = Value + strlen(Value) - 1;
2258                     while (*temp == ' ') *temp-- = '\0';
2259 
2260                     // trim the strings from the left
2261                     Subkey += strspn(Subkey, " ");
2262                     Value += strspn(Value, " ");
2263 
2264                     if (Subkey[0] == 0 || Value[0] == 0)
2265                         return SynError(it8, "Invalid value for property '%s'.", VarName);
2266                     AddToList(it8, &GetTable(it8)->HeaderList, VarName, Subkey, Value, WRITE_PAIR);
2267                 }
2268             }
2269 
2270             InSymbol(it8);
2271             break;
2272 
2273 
2274         case SEOLN: break;
2275 
2276         default:
2277                 return SynError(it8, "expected keyword or identifier");
2278         }
2279 
2280     SkipEOLN(it8);
2281     }
2282 
2283     return TRUE;
2284 
2285 }
2286 
2287 
2288 static
2289 void ReadType(cmsIT8* it8, char* SheetTypePtr)
2290 {
2291     cmsInt32Number cnt = 0;
2292 
2293     // First line is a very special case.
2294 
2295     while (isseparator(it8->ch))
2296             NextCh(it8);
2297 
2298     while (it8->ch != '\r' && it8 ->ch != '\n' && it8->ch != '\t' && it8 -> ch != 0) {
2299 
2300         if (cnt++ < MAXSTR)
2301             *SheetTypePtr++= (char) it8 ->ch;
2302         NextCh(it8);
2303     }
2304 
2305     *SheetTypePtr = 0;
2306 }
2307 
2308 
2309 static
2310 cmsBool ParseIT8(cmsIT8* it8, cmsBool nosheet)
2311 {
2312     char* SheetTypePtr = it8 ->Tab[0].SheetType;
2313 
2314     if (nosheet == 0) {
2315         ReadType(it8, SheetTypePtr);
2316     }
2317 
2318     InSymbol(it8);
2319 
2320     SkipEOLN(it8);
2321 
2322     while (it8-> sy != SEOF &&
2323            it8-> sy != SSYNERROR) {
2324 
2325             switch (it8 -> sy) {
2326 
2327             case SBEGIN_DATA_FORMAT:
2328                     if (!DataFormatSection(it8)) return FALSE;
2329                     break;
2330 
2331             case SBEGIN_DATA:
2332 
2333                     if (!DataSection(it8)) return FALSE;
2334 
2335                     if (it8 -> sy != SEOF) {
2336 
2337                             AllocTable(it8);
2338                             it8 ->nTable = it8 ->TablesCount - 1;
2339 
2340                             // Read sheet type if present. We only support identifier and string.
2341                             // <ident> <eoln> is a type string
2342                             // anything else, is not a type string
2343                             if (nosheet == 0) {
2344 
2345                                 if (it8 ->sy == SIDENT) {
2346 
2347                                     // May be a type sheet or may be a prop value statement. We cannot use insymbol in
2348                                     // this special case...
2349                                      while (isseparator(it8->ch))
2350                                          NextCh(it8);
2351 
2352                                      // If a newline is found, then this is a type string
2353                                     if (it8 ->ch == '\n' || it8->ch == '\r') {
2354 
2355                                          cmsIT8SetSheetType(it8, StringPtr(it8 ->id));
2356                                          InSymbol(it8);
2357                                     }
2358                                     else
2359                                     {
2360                                         // It is not. Just continue
2361                                         cmsIT8SetSheetType(it8, "");
2362                                     }
2363                                 }
2364                                 else
2365                                     // Validate quoted strings
2366                                     if (it8 ->sy == SSTRING) {
2367                                         cmsIT8SetSheetType(it8, StringPtr(it8 ->str));
2368                                         InSymbol(it8);
2369                                     }
2370                            }
2371 
2372                     }
2373                     break;
2374 
2375             case SEOLN:
2376                     SkipEOLN(it8);
2377                     break;
2378 
2379             default:
2380                     if (!HeaderSection(it8)) return FALSE;
2381            }
2382 
2383     }
2384 
2385     return (it8 -> sy != SSYNERROR);
2386 }
2387 
2388 
2389 
2390 // Init useful pointers
2391 
2392 static
2393 void CookPointers(cmsIT8* it8)
2394 {
2395     int idField, i;
2396     char* Fld;
2397     cmsUInt32Number j;
2398     cmsUInt32Number nOldTable = it8->nTable;
2399 
2400     for (j = 0; j < it8->TablesCount; j++) {
2401 
2402         TABLE* t = it8->Tab + j;
2403 
2404         t->SampleID = 0;
2405         it8->nTable = j;
2406 
2407         for (idField = 0; idField < t->nSamples; idField++)
2408         {
2409             if (t->DataFormat == NULL) {
2410                 SynError(it8, "Undefined DATA_FORMAT");
2411                 return;
2412             }
2413 
2414             Fld = t->DataFormat[idField];
2415             if (!Fld) continue;
2416 
2417 
2418             if (cmsstrcasecmp(Fld, "SAMPLE_ID") == 0) {
2419 
2420                 t->SampleID = idField;
2421             }
2422 
2423             // "LABEL" is an extension. It keeps references to forward tables
2424 
2425             if ((cmsstrcasecmp(Fld, "LABEL") == 0) || Fld[0] == '$') {
2426 
2427                 // Search for table references...
2428                 for (i = 0; i < t->nPatches; i++) {
2429 
2430                     char* Label = GetData(it8, i, idField);
2431 
2432                     if (Label) {
2433 
2434                         cmsUInt32Number k;
2435 
2436                         // This is the label, search for a table containing
2437                         // this property
2438 
2439                         for (k = 0; k < it8->TablesCount; k++) {
2440 
2441                             TABLE* Table = it8->Tab + k;
2442                             KEYVALUE* p;
2443 
2444                             if (IsAvailableOnList(Table->HeaderList, Label, NULL, &p)) {
2445 
2446                                 // Available, keep type and table
2447                                 char Buffer[256];
2448 
2449                                 char* Type = p->Value;
2450                                 int  nTable = (int)k;
2451 
2452                                 snprintf(Buffer, 255, "%s %d %s", Label, nTable, Type);
2453 
2454                                 SetData(it8, i, idField, Buffer);
2455                             }
2456                         }
2457                     }
2458                 }
2459             }
2460         }
2461     }
2462 
2463     it8->nTable = nOldTable;
2464 }
2465 
2466 // Try to infere if the file is a CGATS/IT8 file at all. Read first line
2467 // that should be something like some printable characters plus a \n
2468 // returns 0 if this is not like a CGATS, or an integer otherwise. This integer is the number of words in first line?
2469 static
2470 int IsMyBlock(const cmsUInt8Number* Buffer, cmsUInt32Number n)
2471 {
2472     int words = 1, space = 0, quot = 0;
2473     cmsUInt32Number i;
2474 
2475     if (n < 10) return 0;   // Too small
2476 
2477     if (n > 132)
2478         n = 132;
2479 
2480     for (i = 1; i < n; i++) {
2481 
2482         switch(Buffer[i])
2483         {
2484         case '\n':
2485         case '\r':
2486             return ((quot == 1) || (words > 2)) ? 0 : words;
2487         case '\t':
2488         case ' ':
2489             if(!quot && !space)
2490                 space = 1;
2491             break;
2492         case '\"':
2493             quot = !quot;
2494             break;
2495         default:
2496             if (Buffer[i] < 32) return 0;
2497             if (Buffer[i] > 127) return 0;
2498             words += space;
2499             space = 0;
2500             break;
2501         }
2502     }
2503 
2504     return 0;
2505 }
2506 
2507 
2508 static
2509 cmsBool IsMyFile(const char* FileName)
2510 {
2511    FILE *fp;
2512    cmsUInt32Number Size;
2513    cmsUInt8Number Ptr[133];
2514 
2515    fp = fopen(FileName, "rt");
2516    if (!fp) {
2517        cmsSignalError(0, cmsERROR_FILE, "File '%s' not found", FileName);
2518        return FALSE;
2519    }
2520 
2521    Size = (cmsUInt32Number) fread(Ptr, 1, 132, fp);
2522 
2523    if (fclose(fp) != 0)
2524        return FALSE;
2525 
2526    Ptr[Size] = '\0';
2527 
2528    return IsMyBlock(Ptr, Size);
2529 }
2530 
2531 // ---------------------------------------------------------- Exported routines
2532 
2533 
2534 cmsHANDLE  CMSEXPORT cmsIT8LoadFromMem(cmsContext ContextID, const void *Ptr, cmsUInt32Number len)
2535 {
2536     cmsHANDLE hIT8;
2537     cmsIT8*  it8;
2538     int type;
2539 
2540     _cmsAssert(Ptr != NULL);
2541     _cmsAssert(len != 0);
2542 
2543     type = IsMyBlock((const cmsUInt8Number*)Ptr, len);
2544     if (type == 0) return NULL;
2545 
2546     hIT8 = cmsIT8Alloc(ContextID);
2547     if (!hIT8) return NULL;
2548 
2549     it8 = (cmsIT8*) hIT8;
2550     it8 ->MemoryBlock = (char*) _cmsMalloc(ContextID, len + 1);
2551     if (it8->MemoryBlock == NULL)
2552     {
2553         cmsIT8Free(hIT8);
2554         return NULL;
2555     }
2556 
2557     strncpy(it8 ->MemoryBlock, (const char*) Ptr, len);
2558     it8 ->MemoryBlock[len] = 0;
2559 
2560     strncpy(it8->FileStack[0]->FileName, "", cmsMAX_PATH-1);
2561     it8-> Source = it8 -> MemoryBlock;
2562 
2563     if (!ParseIT8(it8, type-1)) {
2564 
2565         cmsIT8Free(hIT8);
2566         return NULL;
2567     }
2568 
2569     CookPointers(it8);
2570     it8 ->nTable = 0;
2571 
2572     _cmsFree(ContextID, it8->MemoryBlock);
2573     it8 -> MemoryBlock = NULL;
2574 
2575     return hIT8;
2576 
2577 
2578 }
2579 
2580 
2581 cmsHANDLE  CMSEXPORT cmsIT8LoadFromFile(cmsContext ContextID, const char* cFileName)
2582 {
2583 
2584      cmsHANDLE hIT8;
2585      cmsIT8*  it8;
2586      int type;
2587 
2588      _cmsAssert(cFileName != NULL);
2589 
2590      type = IsMyFile(cFileName);
2591      if (type == 0) return NULL;
2592 
2593      hIT8 = cmsIT8Alloc(ContextID);
2594      it8 = (cmsIT8*) hIT8;
2595      if (!hIT8) return NULL;
2596 
2597 
2598      it8 ->FileStack[0]->Stream = fopen(cFileName, "rt");
2599 
2600      if (!it8 ->FileStack[0]->Stream) {
2601          cmsIT8Free(hIT8);
2602          return NULL;
2603      }
2604 
2605 
2606     strncpy(it8->FileStack[0]->FileName, cFileName, cmsMAX_PATH-1);
2607     it8->FileStack[0]->FileName[cmsMAX_PATH-1] = 0;
2608 
2609     if (!ParseIT8(it8, type-1)) {
2610 
2611             fclose(it8 ->FileStack[0]->Stream);
2612             cmsIT8Free(hIT8);
2613             return NULL;
2614     }
2615 
2616     CookPointers(it8);
2617     it8 ->nTable = 0;
2618 
2619     if (fclose(it8 ->FileStack[0]->Stream)!= 0) {
2620             cmsIT8Free(hIT8);
2621             return NULL;
2622     }
2623 
2624     return hIT8;
2625 
2626 }
2627 
2628 int CMSEXPORT cmsIT8EnumDataFormat(cmsHANDLE hIT8, char ***SampleNames)
2629 {
2630     cmsIT8* it8 = (cmsIT8*) hIT8;
2631     TABLE* t;
2632 
2633     _cmsAssert(hIT8 != NULL);
2634 
2635     t = GetTable(it8);
2636 
2637     if (SampleNames)
2638         *SampleNames = t -> DataFormat;
2639     return t -> nSamples;
2640 }
2641 
2642 
2643 cmsUInt32Number CMSEXPORT cmsIT8EnumProperties(cmsHANDLE hIT8, char ***PropertyNames)
2644 {
2645     cmsIT8* it8 = (cmsIT8*) hIT8;
2646     KEYVALUE* p;
2647     cmsUInt32Number n;
2648     char **Props;
2649     TABLE* t;
2650 
2651     _cmsAssert(hIT8 != NULL);
2652 
2653     t = GetTable(it8);
2654 
2655     // Pass#1 - count properties
2656 
2657     n = 0;
2658     for (p = t -> HeaderList;  p != NULL; p = p->Next) {
2659         n++;
2660     }
2661 
2662 
2663     Props = (char**)AllocChunk(it8, sizeof(char*) * n);
2664     if (Props != NULL) {
2665 
2666         // Pass#2 - Fill pointers
2667         n = 0;
2668         for (p = t->HeaderList; p != NULL; p = p->Next) {
2669             Props[n++] = p->Keyword;
2670         }
2671 
2672     }
2673     *PropertyNames = Props;
2674 
2675     return n;
2676 }
2677 
2678 cmsUInt32Number CMSEXPORT cmsIT8EnumPropertyMulti(cmsHANDLE hIT8, const char* cProp, const char ***SubpropertyNames)
2679 {
2680     cmsIT8* it8 = (cmsIT8*) hIT8;
2681     KEYVALUE *p, *tmp;
2682     cmsUInt32Number n;
2683     const char **Props;
2684     TABLE* t;
2685 
2686     _cmsAssert(hIT8 != NULL);
2687 
2688 
2689     t = GetTable(it8);
2690 
2691     if(!IsAvailableOnList(t->HeaderList, cProp, NULL, &p)) {
2692         *SubpropertyNames = 0;
2693         return 0;
2694     }
2695 
2696     // Pass#1 - count properties
2697 
2698     n = 0;
2699     for (tmp = p;  tmp != NULL; tmp = tmp->NextSubkey) {
2700         if(tmp->Subkey != NULL)
2701             n++;
2702     }
2703 
2704 
2705     Props = (const char **) AllocChunk(it8, sizeof(char *) * n);
2706     if (Props != NULL) {
2707 
2708         // Pass#2 - Fill pointers
2709         n = 0;
2710         for (tmp = p; tmp != NULL; tmp = tmp->NextSubkey) {
2711             if (tmp->Subkey != NULL)
2712                 Props[n++] = p->Subkey;
2713         }
2714     }
2715 
2716     *SubpropertyNames = Props;
2717     return n;
2718 }
2719 
2720 static
2721 int LocatePatch(cmsIT8* it8, const char* cPatch)
2722 {
2723     int i;
2724     const char *data;
2725     TABLE* t = GetTable(it8);
2726 
2727     for (i=0; i < t-> nPatches; i++) {
2728 
2729         data = GetData(it8, i, t->SampleID);
2730 
2731         if (data != NULL) {
2732 
2733                 if (cmsstrcasecmp(data, cPatch) == 0)
2734                         return i;
2735                 }
2736         }
2737 
2738         // SynError(it8, "Couldn't find patch '%s'\n", cPatch);
2739         return -1;
2740 }
2741 
2742 
2743 static
2744 int LocateEmptyPatch(cmsIT8* it8)
2745 {
2746     int i;
2747     const char *data;
2748     TABLE* t = GetTable(it8);
2749 
2750     for (i=0; i < t-> nPatches; i++) {
2751 
2752         data = GetData(it8, i, t->SampleID);
2753 
2754         if (data == NULL)
2755             return i;
2756 
2757     }
2758 
2759     return -1;
2760 }
2761 
2762 static
2763 int LocateSample(cmsIT8* it8, const char* cSample)
2764 {
2765     int i;
2766     const char *fld;
2767     TABLE* t = GetTable(it8);
2768 
2769     for (i=0; i < t->nSamples; i++) {
2770 
2771         fld = GetDataFormat(it8, i);
2772         if (fld != NULL) {
2773             if (cmsstrcasecmp(fld, cSample) == 0)
2774                 return i;
2775         }
2776     }
2777 
2778     return -1;
2779 
2780 }
2781 
2782 
2783 int CMSEXPORT cmsIT8FindDataFormat(cmsHANDLE hIT8, const char* cSample)
2784 {
2785     cmsIT8* it8 = (cmsIT8*) hIT8;
2786 
2787     _cmsAssert(hIT8 != NULL);
2788 
2789     return LocateSample(it8, cSample);
2790 }
2791 
2792 
2793 
2794 const char* CMSEXPORT cmsIT8GetDataRowCol(cmsHANDLE hIT8, int row, int col)
2795 {
2796     cmsIT8* it8 = (cmsIT8*) hIT8;
2797 
2798     _cmsAssert(hIT8 != NULL);
2799 
2800     return GetData(it8, row, col);
2801 }
2802 
2803 
2804 cmsFloat64Number CMSEXPORT cmsIT8GetDataRowColDbl(cmsHANDLE hIT8, int row, int col)
2805 {
2806     const char* Buffer;
2807 
2808     Buffer = cmsIT8GetDataRowCol(hIT8, row, col);
2809 
2810     if (Buffer == NULL) return 0.0;
2811 
2812     return ParseFloatNumber(Buffer);
2813 }
2814 
2815 
2816 cmsBool CMSEXPORT cmsIT8SetDataRowCol(cmsHANDLE hIT8, int row, int col, const char* Val)
2817 {
2818     cmsIT8* it8 = (cmsIT8*) hIT8;
2819 
2820     _cmsAssert(hIT8 != NULL);
2821 
2822     return SetData(it8, row, col, Val);
2823 }
2824 
2825 
2826 cmsBool CMSEXPORT cmsIT8SetDataRowColDbl(cmsHANDLE hIT8, int row, int col, cmsFloat64Number Val)
2827 {
2828     cmsIT8* it8 = (cmsIT8*) hIT8;
2829     char Buff[256];
2830 
2831     _cmsAssert(hIT8 != NULL);
2832 
2833     snprintf(Buff, 255, it8->DoubleFormatter, Val);
2834 
2835     return SetData(it8, row, col, Buff);
2836 }
2837 
2838 
2839 
2840 const char* CMSEXPORT cmsIT8GetData(cmsHANDLE hIT8, const char* cPatch, const char* cSample)
2841 {
2842     cmsIT8* it8 = (cmsIT8*) hIT8;
2843     int iField, iSet;
2844 
2845     _cmsAssert(hIT8 != NULL);
2846 
2847     iField = LocateSample(it8, cSample);
2848     if (iField < 0) {
2849         return NULL;
2850     }
2851 
2852     iSet = LocatePatch(it8, cPatch);
2853     if (iSet < 0) {
2854             return NULL;
2855     }
2856 
2857     return GetData(it8, iSet, iField);
2858 }
2859 
2860 
2861 cmsFloat64Number CMSEXPORT cmsIT8GetDataDbl(cmsHANDLE  it8, const char* cPatch, const char* cSample)
2862 {
2863     const char* Buffer;
2864 
2865     Buffer = cmsIT8GetData(it8, cPatch, cSample);
2866 
2867     return ParseFloatNumber(Buffer);
2868 }
2869 
2870 
2871 
2872 cmsBool CMSEXPORT cmsIT8SetData(cmsHANDLE hIT8, const char* cPatch, const char* cSample, const char *Val)
2873 {
2874     cmsIT8* it8 = (cmsIT8*) hIT8;
2875     int iField, iSet;
2876     TABLE* t;
2877 
2878     _cmsAssert(hIT8 != NULL);
2879 
2880     t = GetTable(it8);
2881 
2882     iField = LocateSample(it8, cSample);
2883 
2884     if (iField < 0)
2885         return FALSE;
2886 
2887     if (t-> nPatches == 0) {
2888 
2889         if (!AllocateDataFormat(it8))
2890             return FALSE;
2891 
2892         if (!AllocateDataSet(it8))
2893             return FALSE;
2894 
2895         CookPointers(it8);
2896     }
2897 
2898     if (cmsstrcasecmp(cSample, "SAMPLE_ID") == 0) {
2899 
2900         iSet   = LocateEmptyPatch(it8);
2901         if (iSet < 0) {
2902             return SynError(it8, "Couldn't add more patches '%s'\n", cPatch);
2903         }
2904 
2905         iField = t -> SampleID;
2906     }
2907     else {
2908         iSet = LocatePatch(it8, cPatch);
2909         if (iSet < 0) {
2910             return FALSE;
2911         }
2912     }
2913 
2914     return SetData(it8, iSet, iField, Val);
2915 }
2916 
2917 
2918 cmsBool CMSEXPORT cmsIT8SetDataDbl(cmsHANDLE hIT8, const char* cPatch,
2919                                    const char* cSample,
2920                                    cmsFloat64Number Val)
2921 {
2922     cmsIT8* it8 = (cmsIT8*) hIT8;
2923     char Buff[256];
2924 
2925     _cmsAssert(hIT8 != NULL);
2926 
2927     snprintf(Buff, 255, it8->DoubleFormatter, Val);
2928     return cmsIT8SetData(hIT8, cPatch, cSample, Buff);
2929 }
2930 
2931 // Buffer should get MAXSTR at least
2932 
2933 const char* CMSEXPORT cmsIT8GetPatchName(cmsHANDLE hIT8, int nPatch, char* buffer)
2934 {
2935     cmsIT8* it8 = (cmsIT8*) hIT8;
2936     TABLE* t;
2937     char* Data;
2938 
2939     _cmsAssert(hIT8 != NULL);
2940 
2941     t = GetTable(it8);
2942     Data = GetData(it8, nPatch, t->SampleID);
2943 
2944     if (!Data) return NULL;
2945     if (!buffer) return Data;
2946 
2947     strncpy(buffer, Data, MAXSTR-1);
2948     buffer[MAXSTR-1] = 0;
2949     return buffer;
2950 }
2951 
2952 int CMSEXPORT cmsIT8GetPatchByName(cmsHANDLE hIT8, const char *cPatch)
2953 {
2954     _cmsAssert(hIT8 != NULL);
2955 
2956     return LocatePatch((cmsIT8*)hIT8, cPatch);
2957 }
2958 
2959 cmsUInt32Number CMSEXPORT cmsIT8TableCount(cmsHANDLE hIT8)
2960 {
2961     cmsIT8* it8 = (cmsIT8*) hIT8;
2962 
2963     _cmsAssert(hIT8 != NULL);
2964 
2965     return it8 ->TablesCount;
2966 }
2967 
2968 // This handles the "LABEL" extension.
2969 // Label, nTable, Type
2970 
2971 int CMSEXPORT cmsIT8SetTableByLabel(cmsHANDLE hIT8, const char* cSet, const char* cField, const char* ExpectedType)
2972 {
2973     const char* cLabelFld;
2974     char Type[256], Label[256];
2975     cmsUInt32Number nTable;
2976 
2977     _cmsAssert(hIT8 != NULL);
2978 
2979     if (cField != NULL && *cField == 0)
2980             cField = "LABEL";
2981 
2982     if (cField == NULL)
2983             cField = "LABEL";
2984 
2985     cLabelFld = cmsIT8GetData(hIT8, cSet, cField);
2986     if (!cLabelFld) return -1;
2987 
2988     if (sscanf(cLabelFld, "%255s %u %255s", Label, &nTable, Type) != 3)
2989             return -1;
2990 
2991     if (ExpectedType != NULL && *ExpectedType == 0)
2992         ExpectedType = NULL;
2993 
2994     if (ExpectedType) {
2995 
2996         if (cmsstrcasecmp(Type, ExpectedType) != 0) return -1;
2997     }
2998 
2999     return cmsIT8SetTable(hIT8, nTable);
3000 }
3001 
3002 
3003 cmsBool CMSEXPORT cmsIT8SetIndexColumn(cmsHANDLE hIT8, const char* cSample)
3004 {
3005     cmsIT8* it8 = (cmsIT8*) hIT8;
3006     int pos;
3007 
3008     _cmsAssert(hIT8 != NULL);
3009 
3010     pos = LocateSample(it8, cSample);
3011     if(pos == -1)
3012         return FALSE;
3013 
3014     it8->Tab[it8->nTable].SampleID = pos;
3015     return TRUE;
3016 }
3017 
3018 
3019 void CMSEXPORT cmsIT8DefineDblFormat(cmsHANDLE hIT8, const char* Formatter)
3020 {
3021     cmsIT8* it8 = (cmsIT8*) hIT8;
3022 
3023     _cmsAssert(hIT8 != NULL);
3024 
3025     if (Formatter == NULL)
3026         strcpy(it8->DoubleFormatter, DEFAULT_DBL_FORMAT);
3027     else
3028         strncpy(it8->DoubleFormatter, Formatter, sizeof(it8->DoubleFormatter));
3029 
3030     it8 ->DoubleFormatter[sizeof(it8 ->DoubleFormatter)-1] = 0;
3031 }
3032 
3033 
3034 static
3035 cmsBool ReadNumbers(cmsIT8* cube, int n, cmsFloat64Number* arr)
3036 {
3037     int i;
3038 
3039     for (i = 0; i < n; i++) {
3040 
3041         if (cube->sy == SINUM)
3042             arr[i] = cube->inum;
3043         else
3044             if (cube->sy == SDNUM)
3045                 arr[i] = cube->dnum;
3046             else
3047                 return SynError(cube, "Number expected");
3048 
3049         InSymbol(cube);
3050     }
3051 
3052     return CheckEOLN(cube);
3053 }
3054 
3055 static
3056 cmsBool ParseCube(cmsIT8* cube, cmsStage** Shaper, cmsStage** CLUT, char title[])
3057 {
3058     cmsFloat64Number domain_min[3] = { 0, 0, 0 };
3059     cmsFloat64Number domain_max[3] = { 1.0, 1.0, 1.0 };
3060     cmsFloat64Number check_0_1[2] = { 0, 1.0 };
3061     int shaper_size = 0;
3062     int lut_size = 0;
3063     int i;
3064 
3065     InSymbol(cube);
3066 
3067     while (cube->sy != SEOF) {
3068         switch (cube->sy)
3069         {
3070         // Set profile description
3071         case STITLE:
3072             InSymbol(cube);
3073             if (!Check(cube, SSTRING, "Title string expected")) return FALSE;
3074             memcpy(title, StringPtr(cube->str), MAXSTR);
3075             title[MAXSTR - 1] = 0;
3076             InSymbol(cube);
3077             break;
3078 
3079         // Define domain
3080         case SDOMAIN_MIN:
3081             InSymbol(cube);
3082             if (!ReadNumbers(cube, 3, domain_min)) return FALSE;
3083             break;
3084 
3085         case SDOMAIN_MAX:
3086             InSymbol(cube);
3087             if (!ReadNumbers(cube, 3, domain_max)) return FALSE;
3088             break;
3089 
3090         // Define shaper
3091         case S_LUT1D_SIZE:
3092             InSymbol(cube);
3093             if (!Check(cube, SINUM, "Shaper size expected")) return FALSE;
3094             shaper_size = cube->inum;
3095             InSymbol(cube);
3096             break;
3097 
3098         // Deefine CLUT
3099         case S_LUT3D_SIZE:
3100             InSymbol(cube);
3101             if (!Check(cube, SINUM, "LUT size expected")) return FALSE;
3102             lut_size = cube->inum;
3103             InSymbol(cube);
3104             break;
3105 
3106         // Range. If present, has to be 0..1.0
3107         case S_LUT1D_INPUT_RANGE:
3108         case S_LUT3D_INPUT_RANGE:
3109             InSymbol(cube);
3110             if (!ReadNumbers(cube, 2, check_0_1)) return FALSE;
3111             if (check_0_1[0] != 0 || check_0_1[1] != 1.0) {
3112                 return SynError(cube, "Unsupported format");
3113             }
3114             break;
3115 
3116         case SEOLN:
3117             InSymbol(cube);
3118             break;
3119 
3120         default:
3121         case S_LUT_IN_VIDEO_RANGE:
3122         case S_LUT_OUT_VIDEO_RANGE:
3123             return SynError(cube, "Unsupported format");
3124 
3125             // Read and create tables
3126         case SINUM:
3127         case SDNUM:
3128 
3129             if (shaper_size > 0) {
3130 
3131                 cmsToneCurve* curves[3];
3132                 cmsFloat32Number* shapers = (cmsFloat32Number*)_cmsMalloc(cube->ContextID, 3 * shaper_size * sizeof(cmsFloat32Number));
3133                 if (shapers == NULL) return FALSE;
3134 
3135                 for (i = 0; i < shaper_size; i++) {
3136 
3137                     cmsFloat64Number nums[3];
3138 
3139                     if (!ReadNumbers(cube, 3, nums)) return FALSE;
3140 
3141                     shapers[i + 0]               = (cmsFloat32Number) ((nums[0] - domain_min[0]) / (domain_max[0] - domain_min[0]));
3142                     shapers[i + 1 * shaper_size] = (cmsFloat32Number) ((nums[1] - domain_min[1]) / (domain_max[1] - domain_min[1]));
3143                     shapers[i + 2 * shaper_size] = (cmsFloat32Number) ((nums[2] - domain_min[2]) / (domain_max[2] - domain_min[2]));
3144                 }
3145 
3146                 for (i = 0; i < 3; i++) {
3147 
3148                     curves[i] = cmsBuildTabulatedToneCurveFloat(cube->ContextID, shaper_size,
3149                         &shapers[i * shaper_size]);
3150                     if (curves[i] == NULL) return FALSE;
3151                 }
3152 
3153                 *Shaper = cmsStageAllocToneCurves(cube->ContextID, 3, curves);
3154 
3155                 cmsFreeToneCurveTriple(curves);
3156             }
3157 
3158             if (lut_size > 0) {
3159 
3160                 int nodes = lut_size * lut_size * lut_size;
3161 
3162                 cmsFloat32Number* lut_table = _cmsMalloc(cube->ContextID, nodes * 3 * sizeof(cmsFloat32Number));
3163                 if (lut_table == NULL) return FALSE;
3164 
3165                 for (i = 0; i < nodes; i++) {
3166 
3167                     cmsFloat64Number nums[3];
3168 
3169                     if (!ReadNumbers(cube, 3, nums)) return FALSE;
3170 
3171                     lut_table[i * 3 + 2] = (cmsFloat32Number) ((nums[0] - domain_min[0]) / (domain_max[0] - domain_min[0]));
3172                     lut_table[i * 3 + 1] = (cmsFloat32Number) ((nums[1] - domain_min[1]) / (domain_max[1] - domain_min[1]));
3173                     lut_table[i * 3 + 0] = (cmsFloat32Number) ((nums[2] - domain_min[2]) / (domain_max[2] - domain_min[2]));
3174                 }
3175 
3176                 *CLUT = cmsStageAllocCLutFloat(cube->ContextID, lut_size, 3, 3, lut_table);
3177                 _cmsFree(cube->ContextID, lut_table);
3178             }
3179 
3180             if (!Check(cube, SEOF, "Extra symbols found in file")) return FALSE;
3181         }
3182     }
3183 
3184     return TRUE;
3185 }
3186 
3187 // Share the parser to read .cube format and create RGB devicelink profiles
3188 cmsHPROFILE CMSEXPORT cmsCreateDeviceLinkFromCubeFileTHR(cmsContext ContextID, const char* cFileName)
3189 {
3190     cmsHPROFILE hProfile = NULL;
3191     cmsIT8* cube = NULL;
3192     cmsPipeline* Pipeline = NULL;
3193     cmsStage* CLUT = NULL;
3194     cmsStage* Shaper = NULL;
3195     cmsMLU* DescriptionMLU = NULL;
3196     char title[MAXSTR];
3197 
3198     _cmsAssert(cFileName != NULL);
3199 
3200     cube = (cmsIT8*) cmsIT8Alloc(ContextID);
3201     if (!cube) return NULL;
3202 
3203     cube->IsCUBE = TRUE;
3204     cube->FileStack[0]->Stream = fopen(cFileName, "rt");
3205 
3206     if (!cube->FileStack[0]->Stream) goto Done;
3207 
3208     strncpy(cube->FileStack[0]->FileName, cFileName, cmsMAX_PATH - 1);
3209     cube->FileStack[0]->FileName[cmsMAX_PATH - 1] = 0;
3210 
3211     if (!ParseCube(cube, &Shaper, &CLUT, title)) goto Done;
3212 
3213     // Success on parsing, let's create the profile
3214     hProfile = cmsCreateProfilePlaceholder(ContextID);
3215     if (!hProfile) goto Done;
3216 
3217     cmsSetProfileVersion(hProfile, 4.4);
3218 
3219     cmsSetDeviceClass(hProfile, cmsSigLinkClass);
3220     cmsSetColorSpace(hProfile,  cmsSigRgbData);
3221     cmsSetPCS(hProfile,         cmsSigRgbData);
3222 
3223     cmsSetHeaderRenderingIntent(hProfile, INTENT_PERCEPTUAL);
3224 
3225     // Creates a Pipeline to hold CLUT and shaper
3226     Pipeline = cmsPipelineAlloc(ContextID, 3, 3);
3227     if (Pipeline == NULL) goto Done;
3228 
3229     // Populates the pipeline
3230     if (Shaper != NULL) {
3231         if (!cmsPipelineInsertStage(Pipeline, cmsAT_BEGIN, Shaper))
3232             goto Done;
3233     }
3234 
3235     if (CLUT != NULL) {
3236         if (!cmsPipelineInsertStage(Pipeline, cmsAT_END, CLUT))
3237             goto Done;
3238     }
3239 
3240     // Propagate the description. We put no copyright because we know
3241     // nothing on the copyrighted state of the .cube
3242     DescriptionMLU = cmsMLUalloc(ContextID, 1);
3243     if (!cmsMLUsetUTF8(DescriptionMLU, cmsNoLanguage, cmsNoCountry, title)) goto Done;
3244 
3245     // Flush the tags
3246     if (!cmsWriteTag(hProfile, cmsSigProfileDescriptionTag, DescriptionMLU)) goto Done;
3247     if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, (void*)Pipeline)) goto Done;
3248 
3249 Done:
3250 
3251     if (DescriptionMLU != NULL)
3252         cmsMLUfree(DescriptionMLU);
3253 
3254     if (Pipeline != NULL)
3255         cmsPipelineFree(Pipeline);
3256 
3257     cmsIT8Free((cmsHANDLE) cube);
3258 
3259     return hProfile;
3260 }
3261 
3262 cmsHPROFILE CMSEXPORT cmsCreateDeviceLinkFromCubeFile(const char* cFileName)
3263 {
3264     return cmsCreateDeviceLinkFromCubeFileTHR(NULL, cFileName);
3265 }