| /* | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| // This file is available under and governed by the GNU General Public | |
| // License version 2 only, as published by the Free Software Foundation. | |
| // However, the following notice accompanied the original version of this | |
| // file: | |
| // | |
| //--------------------------------------------------------------------------------- | |
| // | |
| // Little Color Management System | |
| // Copyright (c) 1998-2024 Marti Maria Saguer | |
| // | |
| // Permission is hereby granted, free of charge, to any person obtaining | |
| // a copy of this software and associated documentation files (the "Software"), | |
| // to deal in the Software without restriction, including without limitation | |
| // the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| // and/or sell copies of the Software, and to permit persons to whom the Software | |
| // is furnished to do so, subject to the following conditions: | |
| // | |
| // The above copyright notice and this permission notice shall be included in | |
| // all copies or substantial portions of the Software. | |
| // | |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO | |
| // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| // | |
| //--------------------------------------------------------------------------------- | |
| // | |
| #include "lcms2_internal.h" | |
| // IT8.7 / CGATS.17-200x handling ----------------------------------------------------------------------------- | |
| #define MAXID 128 // Max length of identifier | |
| #define MAXSTR 1024 // Max length of string | |
| #define MAXTABLES 255 // Max Number of tables in a single stream | |
| #define MAXINCLUDE 20 // Max number of nested includes | |
| #define DEFAULT_DBL_FORMAT "%.10g" // Double formatting | |
| #ifdef CMS_IS_WINDOWS_ | |
| # include <io.h> | |
| # define DIR_CHAR '\\' | |
| #else | |
| # define DIR_CHAR '/' | |
| #endif | |
| // Symbols | |
| typedef enum { | |
| SUNDEFINED, | |
| SINUM, // Integer | |
| SDNUM, // Real | |
| SIDENT, // Identifier | |
| SSTRING, // string | |
| SCOMMENT, // comment | |
| SEOLN, // End of line | |
| SEOF, // End of stream | |
| SSYNERROR, // Syntax error found on stream | |
| // IT8 symbols | |
| SBEGIN_DATA, | |
| SBEGIN_DATA_FORMAT, | |
| SEND_DATA, | |
| SEND_DATA_FORMAT, | |
| SKEYWORD, | |
| SDATA_FORMAT_ID, | |
| SINCLUDE, | |
| // Cube symbols | |
| SDOMAIN_MAX, | |
| SDOMAIN_MIN, | |
| S_LUT1D_SIZE, | |
| S_LUT1D_INPUT_RANGE, | |
| S_LUT3D_SIZE, | |
| S_LUT3D_INPUT_RANGE, | |
| S_LUT_IN_VIDEO_RANGE, | |
| S_LUT_OUT_VIDEO_RANGE, | |
| STITLE | |
| } SYMBOL; | |
| // How to write the value | |
| typedef enum { | |
| WRITE_UNCOOKED, | |
| WRITE_STRINGIFY, | |
| WRITE_HEXADECIMAL, | |
| WRITE_BINARY, | |
| WRITE_PAIR | |
| } WRITEMODE; | |
| // Linked list of variable names | |
| typedef struct _KeyVal { | |
| struct _KeyVal* Next; | |
| char* Keyword; // Name of variable | |
| struct _KeyVal* NextSubkey; // If key is a dictionary, points to the next item | |
| char* Subkey; // If key is a dictionary, points to the subkey name | |
| char* Value; // Points to value | |
| WRITEMODE WriteAs; // How to write the value | |
| } KEYVALUE; | |
| // Linked list of memory chunks (Memory sink) | |
| typedef struct _OwnedMem { | |
| struct _OwnedMem* Next; | |
| void * Ptr; // Point to value | |
| } OWNEDMEM; | |
| // Suballocator | |
| typedef struct _SubAllocator { | |
| cmsUInt8Number* Block; | |
| cmsUInt32Number BlockSize; | |
| cmsUInt32Number Used; | |
| } SUBALLOCATOR; | |
| // Table. Each individual table can hold properties and rows & cols | |
| typedef struct _Table { | |
| char SheetType[MAXSTR]; // The first row of the IT8 (the type) | |
| int nSamples, nPatches; // Cols, Rows | |
| int SampleID; // Pos of ID | |
| KEYVALUE* HeaderList; // The properties | |
| char** DataFormat; // The binary stream descriptor | |
| char** Data; // The binary stream | |
| } TABLE; | |
| // File stream being parsed | |
| typedef struct _FileContext { | |
| char FileName[cmsMAX_PATH]; // File name if being read from file | |
| FILE* Stream; // File stream or NULL if holded in memory | |
| } FILECTX; | |
| //Very simple string | |
| typedef struct { | |
| struct struct_it8* it8; | |
| cmsInt32Number max; | |
| cmsInt32Number len; | |
| char* begin; | |
| } string; | |
| // This struct hold all information about an open IT8 handler. | |
| typedef struct struct_it8 { | |
| cmsUInt32Number TablesCount; // How many tables in this stream | |
| cmsUInt32Number nTable; // The actual table | |
| // Partser type | |
| cmsBool IsCUBE; | |
| // Tables | |
| TABLE Tab[MAXTABLES]; | |
| // Memory management | |
| OWNEDMEM* MemorySink; // The storage backend | |
| SUBALLOCATOR Allocator; // String suballocator -- just to keep it fast | |
| // Parser state machine | |
| SYMBOL sy; // Current symbol | |
| int ch; // Current character | |
| cmsInt32Number inum; // integer value | |
| cmsFloat64Number dnum; // real value | |
| string* id; // identifier | |
| string* str; // string | |
| // Allowed keywords & datasets. They have visibility on whole stream | |
| KEYVALUE* ValidKeywords; | |
| KEYVALUE* ValidSampleID; | |
| char* Source; // Points to loc. being parsed | |
| cmsInt32Number lineno; // line counter for error reporting | |
| FILECTX* FileStack[MAXINCLUDE]; // Stack of files being parsed | |
| cmsInt32Number IncludeSP; // Include Stack Pointer | |
| char* MemoryBlock; // The stream if holded in memory | |
| char DoubleFormatter[MAXID];// Printf-like 'cmsFloat64Number' formatter | |
| cmsContext ContextID; // The threading context | |
| } cmsIT8; | |
| // The stream for save operations | |
| typedef struct { | |
| FILE* stream; // For save-to-file behaviour | |
| cmsUInt8Number* Base; | |
| cmsUInt8Number* Ptr; // For save-to-mem behaviour | |
| cmsUInt32Number Used; | |
| cmsUInt32Number Max; | |
| } SAVESTREAM; | |
| // ------------------------------------------------------ cmsIT8 parsing routines | |
| // A keyword | |
| typedef struct { | |
| const char *id; | |
| SYMBOL sy; | |
| } KEYWORD; | |
| // The keyword->symbol translation tables. Sorting is required. | |
| static const KEYWORD TabKeysIT8[] = { | |
| {"$INCLUDE", SINCLUDE}, // This is an extension! | |
| {".INCLUDE", SINCLUDE}, // This is an extension! | |
| {"BEGIN_DATA", SBEGIN_DATA }, | |
| {"BEGIN_DATA_FORMAT", SBEGIN_DATA_FORMAT }, | |
| {"DATA_FORMAT_IDENTIFIER", SDATA_FORMAT_ID}, | |
| {"END_DATA", SEND_DATA}, | |
| {"END_DATA_FORMAT", SEND_DATA_FORMAT}, | |
| {"KEYWORD", SKEYWORD} | |
| }; | |
| #define NUMKEYS_IT8 (sizeof(TabKeysIT8)/sizeof(KEYWORD)) | |
| static const KEYWORD TabKeysCUBE[] = { | |
| {"DOMAIN_MAX", SDOMAIN_MAX }, | |
| {"DOMAIN_MIN", SDOMAIN_MIN }, | |
| {"LUT_1D_SIZE", S_LUT1D_SIZE }, | |
| {"LUT_1D_INPUT_RANGE", S_LUT1D_INPUT_RANGE }, | |
| {"LUT_3D_SIZE", S_LUT3D_SIZE }, | |
| {"LUT_3D_INPUT_RANGE", S_LUT3D_INPUT_RANGE }, | |
| {"LUT_IN_VIDEO_RANGE", S_LUT_IN_VIDEO_RANGE }, | |
| {"LUT_OUT_VIDEO_RANGE", S_LUT_OUT_VIDEO_RANGE }, | |
| {"TITLE", STITLE } | |
| }; | |
| #define NUMKEYS_CUBE (sizeof(TabKeysCUBE)/sizeof(KEYWORD)) | |
| // Predefined properties | |
| // A property | |
| typedef struct { | |
| const char *id; // The identifier | |
| WRITEMODE as; // How is supposed to be written | |
| } PROPERTY; | |
| static PROPERTY PredefinedProperties[] = { | |
| {"NUMBER_OF_FIELDS", WRITE_UNCOOKED}, // Required - NUMBER OF FIELDS | |
| {"NUMBER_OF_SETS", WRITE_UNCOOKED}, // Required - NUMBER OF SETS | |
| {"ORIGINATOR", WRITE_STRINGIFY}, // Required - Identifies the specific system, organization or individual that created the data file. | |
| {"FILE_DESCRIPTOR", WRITE_STRINGIFY}, // Required - Describes the purpose or contents of the data file. | |
| {"CREATED", WRITE_STRINGIFY}, // Required - Indicates date of creation of the data file. | |
| {"DESCRIPTOR", WRITE_STRINGIFY}, // Required - Describes the purpose or contents of the data file. | |
| {"DIFFUSE_GEOMETRY", WRITE_STRINGIFY}, // The diffuse geometry used. Allowed values are "sphere" or "opal". | |
| {"MANUFACTURER", WRITE_STRINGIFY}, | |
| {"MANUFACTURE", WRITE_STRINGIFY}, // Some broken Fuji targets does store this value | |
| {"PROD_DATE", WRITE_STRINGIFY}, // Identifies year and month of production of the target in the form yyyy:mm. | |
| {"SERIAL", WRITE_STRINGIFY}, // Uniquely identifies individual physical target. | |
| {"MATERIAL", WRITE_STRINGIFY}, // Identifies the material on which the target was produced using a code | |
| // uniquely identifying th e material. This is intend ed to be used for IT8.7 | |
| // physical targets only (i.e . IT8.7/1 and IT8.7/2). | |
| {"INSTRUMENTATION", WRITE_STRINGIFY}, // Used to report the specific instrumentation used (manufacturer and | |
| // model number) to generate the data reported. This data will often | |
| // provide more information about the particular data collected than an | |
| // extensive list of specific details. This is particularly important for | |
| // spectral data or data derived from spectrophotometry. | |
| {"MEASUREMENT_SOURCE", WRITE_STRINGIFY}, // Illumination used for spectral measurements. This data helps provide | |
| // a guide to the potential for issues of paper fluorescence, etc. | |
| {"PRINT_CONDITIONS", WRITE_STRINGIFY}, // Used to define the characteristics of the printed sheet being reported. | |
| // Where standard conditions have been defined (e.g., SWOP at nominal) | |
| // named conditions may suffice. Otherwise, detailed information is | |
| // needed. | |
| {"SAMPLE_BACKING", WRITE_STRINGIFY}, // Identifies the backing material used behind the sample during | |
| // measurement. Allowed values are "black", "white", or {"na". | |
| {"CHISQ_DOF", WRITE_STRINGIFY}, // Degrees of freedom associated with the Chi squared statistic | |
| // below properties are new in recent specs: | |
| {"MEASUREMENT_GEOMETRY", WRITE_STRINGIFY}, // The type of measurement, either reflection or transmission, should be indicated | |
| // along with details of the geometry and the aperture size and shape. For example, | |
| // for transmission measurements it is important to identify 0/diffuse, diffuse/0, | |
| // opal or integrating sphere, etc. For reflection it is important to identify 0/45, | |
| // 45/0, sphere (specular included or excluded), etc. | |
| {"FILTER", WRITE_STRINGIFY}, // Identifies the use of physical filter(s) during measurement. Typically used to | |
| // denote the use of filters such as none, D65, Red, Green or Blue. | |
| {"POLARIZATION", WRITE_STRINGIFY}, // Identifies the use of a physical polarization filter during measurement. Allowed | |
| // values are {"yes", "white", "none" or "na". | |
| {"WEIGHTING_FUNCTION", WRITE_PAIR}, // Indicates such functions as: the CIE standard observer functions used in the | |
| // calculation of various data parameters (2 degree and 10 degree), CIE standard | |
| // illuminant functions used in the calculation of various data parameters (e.g., D50, | |
| // D65, etc.), density status response, etc. If used there shall be at least one | |
| // name-value pair following the WEIGHTING_FUNCTION tag/keyword. The first attribute | |
| // in the set shall be {"name" and shall identify the particular parameter used. | |
| // The second shall be {"value" and shall provide the value associated with that name. | |
| // For ASCII data, a string containing the Name and Value attribute pairs shall follow | |
| // the weighting function keyword. A semi-colon separates attribute pairs from each | |
| // other and within the attribute the name and value are separated by a comma. | |
| {"COMPUTATIONAL_PARAMETER", WRITE_PAIR}, // Parameter that is used in computing a value from measured data. Name is the name | |
| // of the calculation, parameter is the name of the parameter used in the calculation | |
| // and value is the value of the parameter. | |
| {"TARGET_TYPE", WRITE_STRINGIFY}, // The type of target being measured, e.g. IT8.7/1, IT8.7/3, user defined, etc. | |
| {"COLORANT", WRITE_STRINGIFY}, // Identifies the colorant(s) used in creating the target. | |
| {"TABLE_DESCRIPTOR", WRITE_STRINGIFY}, // Describes the purpose or contents of a data table. | |
| {"TABLE_NAME", WRITE_STRINGIFY} // Provides a short name for a data table. | |
| }; | |
| #define NUMPREDEFINEDPROPS (sizeof(PredefinedProperties)/sizeof(PROPERTY)) | |
| // Predefined sample types on dataset | |
| static const char* PredefinedSampleID[] = { | |
| "SAMPLE_ID", // Identifies sample that data represents | |
| "STRING", // Identifies label, or other non-machine readable value. | |
| // Value must begin and end with a " symbol | |
| "CMYK_C", // Cyan component of CMYK data expressed as a percentage | |
| "CMYK_M", // Magenta component of CMYK data expressed as a percentage | |
| "CMYK_Y", // Yellow component of CMYK data expressed as a percentage | |
| "CMYK_K", // Black component of CMYK data expressed as a percentage | |
| "D_RED", // Red filter density | |
| "D_GREEN", // Green filter density | |
| "D_BLUE", // Blue filter density | |
| "D_VIS", // Visual filter density | |
| "D_MAJOR_FILTER", // Major filter d ensity | |
| "RGB_R", // Red component of RGB data | |
| "RGB_G", // Green component of RGB data | |
| "RGB_B", // Blue com ponent of RGB data | |
| "SPECTRAL_NM", // Wavelength of measurement expressed in nanometers | |
| "SPECTRAL_PCT", // Percentage reflectance/transmittance | |
| "SPECTRAL_DEC", // Reflectance/transmittance | |
| "XYZ_X", // X component of tristimulus data | |
| "XYZ_Y", // Y component of tristimulus data | |
| "XYZ_Z", // Z component of tristimulus data | |
| "XYY_X", // x component of chromaticity data | |
| "XYY_Y", // y component of chromaticity data | |
| "XYY_CAPY", // Y component of tristimulus data | |
| "LAB_L", // L* component of Lab data | |
| "LAB_A", // a* component of Lab data | |
| "LAB_B", // b* component of Lab data | |
| "LAB_C", // C*ab component of Lab data | |
| "LAB_H", // hab component of Lab data | |
| "LAB_DE", // CIE dE | |
| "LAB_DE_94", // CIE dE using CIE 94 | |
| "LAB_DE_CMC", // dE using CMC | |
| "LAB_DE_2000", // CIE dE using CIE DE 2000 | |
| "MEAN_DE", // Mean Delta E (LAB_DE) of samples compared to batch average | |
| // (Used for data files for ANSI IT8.7/1 and IT8.7/2 targets) | |
| "STDEV_X", // Standard deviation of X (tristimulus data) | |
| "STDEV_Y", // Standard deviation of Y (tristimulus data) | |
| "STDEV_Z", // Standard deviation of Z (tristimulus data) | |
| "STDEV_L", // Standard deviation of L* | |
| "STDEV_A", // Standard deviation of a* | |
| "STDEV_B", // Standard deviation of b* | |
| "STDEV_DE", // Standard deviation of CIE dE | |
| "CHI_SQD_PAR"}; // The average of the standard deviations of L*, a* and b*. It is | |
| // used to derive an estimate of the chi-squared parameter which is | |
| // recommended as the predictor of the variability of dE | |
| #define NUMPREDEFINEDSAMPLEID (sizeof(PredefinedSampleID)/sizeof(char *)) | |
| //Forward declaration of some internal functions | |
| static void* AllocChunk(cmsIT8* it8, cmsUInt32Number size); | |
| static | |
| string* StringAlloc(cmsIT8* it8, int max) | |
| { | |
| string* s = (string*) AllocChunk(it8, sizeof(string)); | |
| if (s == NULL) return NULL; | |
| s->it8 = it8; | |
| s->max = max; | |
| s->len = 0; | |
| s->begin = (char*) AllocChunk(it8, s->max); | |
| return s; | |
| } | |
| static | |
| void StringClear(string* s) | |
| { | |
| s->len = 0; | |
| s->begin[0] = 0; | |
| } | |
| static | |
| cmsBool StringAppend(string* s, char c) | |
| { | |
| if (s->len + 1 >= s->max) | |
| { | |
| char* new_ptr; | |
| s->max *= 10; | |
| new_ptr = (char*) AllocChunk(s->it8, s->max); | |
| if (new_ptr == NULL) return FALSE; | |
| if (new_ptr != NULL && s->begin != NULL) | |
| memcpy(new_ptr, s->begin, s->len); | |
| s->begin = new_ptr; | |
| } | |
| if (s->begin != NULL) | |
| { | |
| s->begin[s->len++] = c; | |
| s->begin[s->len] = 0; | |
| } | |
| return TRUE; | |
| } | |
| static | |
| char* StringPtr(string* s) | |
| { | |
| return s->begin; | |
| } | |
| static | |
| cmsBool StringCat(string* s, const char* c) | |
| { | |
| while (*c) | |
| { | |
| if (!StringAppend(s, *c)) return FALSE; | |
| c++; | |
| } | |
| return TRUE; | |
| } | |
| // Checks whatever c is a separator | |
| static | |
| cmsBool isseparator(int c) | |
| { | |
| return (c == ' ') || (c == '\t'); | |
| } | |
| // Checks whatever c is a valid identifier char | |
| static | |
| cmsBool ismiddle(int c) | |
| { | |
| return (!isseparator(c) && (c != '#') && (c !='\"') && (c != '\'') && (c > 32) && (c < 127)); | |
| } | |
| // Checks whatsever c is a valid identifier middle char. | |
| static | |
| cmsBool isidchar(int c) | |
| { | |
| return isalnum(c) || ismiddle(c); | |
| } | |
| // Checks whatsever c is a valid identifier first char. | |
| static | |
| cmsBool isfirstidchar(int c) | |
| { | |
| return c != '-' && !isdigit(c) && ismiddle(c); | |
| } | |
| // Guess whether the supplied path looks like an absolute path | |
| static | |
| cmsBool isabsolutepath(const char *path) | |
| { | |
| char ThreeChars[4]; | |
| if(path == NULL) | |
| return FALSE; | |
| if (path[0] == 0) | |
| return FALSE; | |
| strncpy(ThreeChars, path, 3); | |
| ThreeChars[3] = 0; | |
| if(ThreeChars[0] == DIR_CHAR) | |
| return TRUE; | |
| #ifdef CMS_IS_WINDOWS_ | |
| if (isalpha((int) ThreeChars[0]) && ThreeChars[1] == ':') | |
| return TRUE; | |
| #endif | |
| return FALSE; | |
| } | |
| // Makes a file path based on a given reference path | |
| // NOTE: this function doesn't check if the path exists or even if it's legal | |
| static | |
| cmsBool BuildAbsolutePath(const char *relPath, const char *basePath, char *buffer, cmsUInt32Number MaxLen) | |
| { | |
| char *tail; | |
| cmsUInt32Number len; | |
| // Already absolute? | |
| if (isabsolutepath(relPath)) { | |
| memcpy(buffer, relPath, MaxLen); | |
| buffer[MaxLen-1] = 0; | |
| return TRUE; | |
| } | |
| // No, search for last | |
| memcpy(buffer, basePath, MaxLen); | |
| buffer[MaxLen-1] = 0; | |
| tail = strrchr(buffer, DIR_CHAR); | |
| if (tail == NULL) return FALSE; // Is not absolute and has no separators?? | |
| len = (cmsUInt32Number) (tail - buffer); | |
| if (len >= MaxLen) return FALSE; | |
| // No need to assure zero terminator over here | |
| strncpy(tail + 1, relPath, MaxLen - len); | |
| return TRUE; | |
| } | |
| // Make sure no exploit is being even tried | |
| static | |
| const char* NoMeta(const char* str) | |
| { | |
| if (strchr(str, '%') != NULL) | |
| return "**** CORRUPTED FORMAT STRING ***"; | |
| return str; | |
| } | |
| // Syntax error | |
| static | |
| cmsBool SynError(cmsIT8* it8, const char *Txt, ...) | |
| { | |
| char Buffer[256], ErrMsg[1024]; | |
| va_list args; | |
| va_start(args, Txt); | |
| vsnprintf(Buffer, 255, Txt, args); | |
| Buffer[255] = 0; | |
| va_end(args); | |
| snprintf(ErrMsg, 1023, "%s: Line %d, %s", it8->FileStack[it8 ->IncludeSP]->FileName, it8->lineno, Buffer); | |
| ErrMsg[1023] = 0; | |
| it8->sy = SSYNERROR; | |
| cmsSignalError(it8 ->ContextID, cmsERROR_CORRUPTION_DETECTED, "%s", ErrMsg); | |
| return FALSE; | |
| } | |
| // Check if current symbol is same as specified. issue an error else. | |
| static | |
| cmsBool Check(cmsIT8* it8, SYMBOL sy, const char* Err) | |
| { | |
| if (it8 -> sy != sy) | |
| return SynError(it8, NoMeta(Err)); | |
| return TRUE; | |
| } | |
| // Read Next character from stream | |
| static | |
| void NextCh(cmsIT8* it8) | |
| { | |
| if (it8 -> FileStack[it8 ->IncludeSP]->Stream) { | |
| it8 ->ch = fgetc(it8 ->FileStack[it8 ->IncludeSP]->Stream); | |
| if (feof(it8 -> FileStack[it8 ->IncludeSP]->Stream)) { | |
| if (it8 ->IncludeSP > 0) { | |
| fclose(it8 ->FileStack[it8->IncludeSP--]->Stream); | |
| it8 -> ch = ' '; // Whitespace to be ignored | |
| } else | |
| it8 ->ch = 0; // EOF | |
| } | |
| } | |
| else { | |
| it8->ch = *it8->Source; | |
| if (it8->ch) it8->Source++; | |
| } | |
| } | |
| // Try to see if current identifier is a keyword, if so return the referred symbol | |
| static | |
| SYMBOL BinSrchKey(const char *id, int NumKeys, const KEYWORD* TabKeys) | |
| { | |
| int l = 1; | |
| int r = NumKeys; | |
| int x, res; | |
| while (r >= l) | |
| { | |
| x = (l+r)/2; | |
| res = cmsstrcasecmp(id, TabKeys[x-1].id); | |
| if (res == 0) return TabKeys[x-1].sy; | |
| if (res < 0) r = x - 1; | |
| else l = x + 1; | |
| } | |
| return SUNDEFINED; | |
| } | |
| // 10 ^n | |
| static | |
| cmsFloat64Number xpow10(int n) | |
| { | |
| return pow(10, (cmsFloat64Number) n); | |
| } | |
| // Reads a Real number, tries to follow from integer number | |
| static | |
| void ReadReal(cmsIT8* it8, cmsInt32Number inum) | |
| { | |
| it8->dnum = (cmsFloat64Number)inum; | |
| while (isdigit(it8->ch)) { | |
| it8->dnum = (cmsFloat64Number)it8->dnum * 10.0 + (cmsFloat64Number)(it8->ch - '0'); | |
| NextCh(it8); | |
| } | |
| if (it8->ch == '.') { // Decimal point | |
| cmsFloat64Number frac = 0.0; // fraction | |
| int prec = 0; // precision | |
| NextCh(it8); // Eats dec. point | |
| while (isdigit(it8->ch)) { | |
| frac = frac * 10.0 + (cmsFloat64Number)(it8->ch - '0'); | |
| prec++; | |
| NextCh(it8); | |
| } | |
| it8->dnum = it8->dnum + (frac / xpow10(prec)); | |
| } | |
| // Exponent, example 34.00E+20 | |
| if (toupper(it8->ch) == 'E') { | |
| cmsInt32Number e; | |
| cmsInt32Number sgn; | |
| NextCh(it8); sgn = 1; | |
| if (it8->ch == '-') { | |
| sgn = -1; NextCh(it8); | |
| } | |
| else | |
| if (it8->ch == '+') { | |
| sgn = +1; | |
| NextCh(it8); | |
| } | |
| e = 0; | |
| while (isdigit(it8->ch)) { | |
| cmsInt32Number digit = (it8->ch - '0'); | |
| if ((cmsFloat64Number)e * 10.0 + (cmsFloat64Number)digit < (cmsFloat64Number)+2147483647.0) | |
| e = e * 10 + digit; | |
| NextCh(it8); | |
| } | |
| e = sgn*e; | |
| it8->dnum = it8->dnum * xpow10(e); | |
| } | |
| } | |
| // Parses a float number | |
| // This can not call directly atof because it uses locale dependent | |
| // parsing, while CCMX files always use . as decimal separator | |
| static | |
| cmsFloat64Number ParseFloatNumber(const char *Buffer) | |
| { | |
| cmsFloat64Number dnum = 0.0; | |
| int sign = 1; | |
| // keep safe | |
| if (Buffer == NULL) return 0.0; | |
| if (*Buffer == '-' || *Buffer == '+') { | |
| sign = (*Buffer == '-') ? -1 : 1; | |
| Buffer++; | |
| } | |
| while (*Buffer && isdigit((int)*Buffer)) { | |
| dnum = dnum * 10.0 + (*Buffer - '0'); | |
| if (*Buffer) Buffer++; | |
| } | |
| if (*Buffer == '.') { | |
| cmsFloat64Number frac = 0.0; // fraction | |
| int prec = 0; // precision | |
| if (*Buffer) Buffer++; | |
| while (*Buffer && isdigit((int)*Buffer)) { | |
| frac = frac * 10.0 + (*Buffer - '0'); | |
| prec++; | |
| if (*Buffer) Buffer++; | |
| } | |
| dnum = dnum + (frac / xpow10(prec)); | |
| } | |
| // Exponent, example 34.00E+20 | |
| if (*Buffer && toupper(*Buffer) == 'E') { | |
| int e; | |
| int sgn; | |
| if (*Buffer) Buffer++; | |
| sgn = 1; | |
| if (*Buffer == '-') { | |
| sgn = -1; | |
| if (*Buffer) Buffer++; | |
| } | |
| else | |
| if (*Buffer == '+') { | |
| sgn = +1; | |
| if (*Buffer) Buffer++; | |
| } | |
| e = 0; | |
| while (*Buffer && isdigit((int)*Buffer)) { | |
| cmsInt32Number digit = (*Buffer - '0'); | |
| if ((cmsFloat64Number)e * 10.0 + digit < (cmsFloat64Number)+2147483647.0) | |
| e = e * 10 + digit; | |
| if (*Buffer) Buffer++; | |
| } | |
| e = sgn*e; | |
| dnum = dnum * xpow10(e); | |
| } | |
| return sign * dnum; | |
| } | |
| // Reads a string, special case to avoid infinite recursion on .include | |
| static | |
| void InStringSymbol(cmsIT8* it8) | |
| { | |
| while (isseparator(it8->ch)) | |
| NextCh(it8); | |
| if (it8->ch == '\'' || it8->ch == '\"') | |
| { | |
| int sng; | |
| sng = it8->ch; | |
| StringClear(it8->str); | |
| NextCh(it8); | |
| while (it8->ch != sng) { | |
| if (it8->ch == '\n' || it8->ch == '\r' || it8->ch == 0) break; | |
| else { | |
| if (!StringAppend(it8->str, (char)it8->ch)) { | |
| SynError(it8, "Out of memory"); | |
| return; | |
| } | |
| NextCh(it8); | |
| } | |
| } | |
| it8->sy = SSTRING; | |
| NextCh(it8); | |
| } | |
| else | |
| SynError(it8, "String expected"); | |
| } | |
| // Reads next symbol | |
| static | |
| void InSymbol(cmsIT8* it8) | |
| { | |
| SYMBOL key; | |
| do { | |
| while (isseparator(it8->ch)) | |
| NextCh(it8); | |
| if (isfirstidchar(it8->ch)) { // Identifier | |
| StringClear(it8->id); | |
| do { | |
| if (!StringAppend(it8->id, (char)it8->ch)) { | |
| SynError(it8, "Out of memory"); | |
| return; | |
| } | |
| NextCh(it8); | |
| } while (isidchar(it8->ch)); | |
| key = BinSrchKey(StringPtr(it8->id), | |
| it8->IsCUBE ? NUMKEYS_CUBE : NUMKEYS_IT8, | |
| it8->IsCUBE ? TabKeysCUBE : TabKeysIT8); | |
| if (key == SUNDEFINED) it8->sy = SIDENT; | |
| else it8->sy = key; | |
| } | |
| else // Is a number? | |
| if (isdigit(it8->ch) || it8->ch == '.' || it8->ch == '-' || it8->ch == '+') | |
| { | |
| int sign = 1; | |
| if (it8->ch == '-') { | |
| sign = -1; | |
| NextCh(it8); | |
| } | |
| it8->inum = 0; | |
| it8->sy = SINUM; | |
| if (it8->ch == '0') { // 0xnnnn (Hexa) or 0bnnnn (Binary) | |
| NextCh(it8); | |
| if (toupper(it8->ch) == 'X') { | |
| int j; | |
| NextCh(it8); | |
| while (isxdigit(it8->ch)) | |
| { | |
| it8->ch = toupper(it8->ch); | |
| if (it8->ch >= 'A' && it8->ch <= 'F') j = it8->ch -'A'+10; | |
| else j = it8->ch - '0'; | |
| if ((cmsFloat64Number) it8->inum * 16.0 + (cmsFloat64Number) j > (cmsFloat64Number)+2147483647.0) | |
| { | |
| SynError(it8, "Invalid hexadecimal number"); | |
| return; | |
| } | |
| it8->inum = it8->inum * 16 + j; | |
| NextCh(it8); | |
| } | |
| return; | |
| } | |
| if (toupper(it8->ch) == 'B') { // Binary | |
| int j; | |
| NextCh(it8); | |
| while (it8->ch == '0' || it8->ch == '1') | |
| { | |
| j = it8->ch - '0'; | |
| if ((cmsFloat64Number) it8->inum * 2.0 + j > (cmsFloat64Number)+2147483647.0) | |
| { | |
| SynError(it8, "Invalid binary number"); | |
| return; | |
| } | |
| it8->inum = it8->inum * 2 + j; | |
| NextCh(it8); | |
| } | |
| return; | |
| } | |
| } | |
| while (isdigit(it8->ch)) { | |
| cmsInt32Number digit = (it8->ch - '0'); | |
| if ((cmsFloat64Number) it8->inum * 10.0 + (cmsFloat64Number) digit > (cmsFloat64Number) +2147483647.0) { | |
| ReadReal(it8, it8->inum); | |
| it8->sy = SDNUM; | |
| it8->dnum *= sign; | |
| return; | |
| } | |
| it8->inum = it8->inum * 10 + digit; | |
| NextCh(it8); | |
| } | |
| if (it8->ch == '.') { | |
| ReadReal(it8, it8->inum); | |
| it8->sy = SDNUM; | |
| it8->dnum *= sign; | |
| return; | |
| } | |
| it8 -> inum *= sign; | |
| // Special case. Numbers followed by letters are taken as identifiers | |
| if (isidchar(it8 ->ch)) { | |
| char buffer[127]; | |
| if (it8 ->sy == SINUM) { | |
| snprintf(buffer, sizeof(buffer), "%d", it8->inum); | |
| } | |
| else { | |
| snprintf(buffer, sizeof(buffer), it8 ->DoubleFormatter, it8->dnum); | |
| } | |
| StringClear(it8->id); | |
| if (!StringCat(it8->id, buffer)) { | |
| SynError(it8, "Out of memory"); | |
| return; | |
| } | |
| do { | |
| if (!StringAppend(it8->id, (char)it8->ch)) { | |
| SynError(it8, "Out of memory"); | |
| return; | |
| } | |
| NextCh(it8); | |
| } while (isidchar(it8->ch)); | |
| it8->sy = SIDENT; | |
| } | |
| return; | |
| } | |
| else | |
| switch ((int) it8->ch) { | |
| // Eof stream markers | |
| case '\x1a': | |
| case 0: | |
| case -1: | |
| it8->sy = SEOF; | |
| break; | |
| // Next line | |
| case '\r': | |
| NextCh(it8); | |
| if (it8->ch == '\n') | |
| NextCh(it8); | |
| it8->sy = SEOLN; | |
| it8->lineno++; | |
| break; | |
| case '\n': | |
| NextCh(it8); | |
| it8->sy = SEOLN; | |
| it8->lineno++; | |
| break; | |
| // Comment | |
| case '#': | |
| NextCh(it8); | |
| while (it8->ch && it8->ch != '\n' && it8->ch != '\r') | |
| NextCh(it8); | |
| it8->sy = SCOMMENT; | |
| break; | |
| // String. | |
| case '\'': | |
| case '\"': | |
| InStringSymbol(it8); | |
| break; | |
| default: | |
| SynError(it8, "Unrecognized character: 0x%x", it8 ->ch); | |
| return; | |
| } | |
| } while (it8->sy == SCOMMENT); | |
| // Handle the include special token | |
| if (it8 -> sy == SINCLUDE) { | |
| FILECTX* FileNest; | |
| if(it8 -> IncludeSP >= (MAXINCLUDE-1)) { | |
| SynError(it8, "Too many recursion levels"); | |
| return; | |
| } | |
| InStringSymbol(it8); | |
| if (!Check(it8, SSTRING, "Filename expected")) | |
| return; | |
| FileNest = it8 -> FileStack[it8 -> IncludeSP + 1]; | |
| if(FileNest == NULL) { | |
| FileNest = it8 ->FileStack[it8 -> IncludeSP + 1] = (FILECTX*)AllocChunk(it8, sizeof(FILECTX)); | |
| if (FileNest == NULL) { | |
| SynError(it8, "Out of memory"); | |
| return; | |
| } | |
| } | |
| if (BuildAbsolutePath(StringPtr(it8->str), | |
| it8->FileStack[it8->IncludeSP]->FileName, | |
| FileNest->FileName, cmsMAX_PATH-1) == FALSE) { | |
| SynError(it8, "File path too long"); | |
| return; | |
| } | |
| FileNest->Stream = fopen(FileNest->FileName, "rt"); | |
| if (FileNest->Stream == NULL) { | |
| SynError(it8, "File %s not found", FileNest->FileName); | |
| return; | |
| } | |
| it8->IncludeSP++; | |
| it8 ->ch = ' '; | |
| InSymbol(it8); | |
| } | |
| } | |
| // Checks end of line separator | |
| static | |
| cmsBool CheckEOLN(cmsIT8* it8) | |
| { | |
| if (!Check(it8, SEOLN, "Expected separator")) return FALSE; | |
| while (it8->sy == SEOLN) | |
| InSymbol(it8); | |
| return TRUE; | |
| } | |
| // Skip a symbol | |
| static | |
| void Skip(cmsIT8* it8, SYMBOL sy) | |
| { | |
| if (it8->sy == sy && it8->sy != SEOF && it8->sy != SSYNERROR) | |
| InSymbol(it8); | |
| } | |
| // Skip multiple EOLN | |
| static | |
| void SkipEOLN(cmsIT8* it8) | |
| { | |
| while (it8->sy == SEOLN) { | |
| InSymbol(it8); | |
| } | |
| } | |
| // Returns a string holding current value | |
| static | |
| cmsBool GetVal(cmsIT8* it8, char* Buffer, cmsUInt32Number max, const char* ErrorTitle) | |
| { | |
| switch (it8->sy) { | |
| case SEOLN: // Empty value | |
| Buffer[0]=0; | |
| break; | |
| case SIDENT: strncpy(Buffer, StringPtr(it8->id), max); | |
| Buffer[max-1]=0; | |
| break; | |
| case SINUM: snprintf(Buffer, max, "%d", it8 -> inum); break; | |
| case SDNUM: snprintf(Buffer, max, it8->DoubleFormatter, it8 -> dnum); break; | |
| case SSTRING: strncpy(Buffer, StringPtr(it8->str), max); | |
| Buffer[max-1] = 0; | |
| break; | |
| default: | |
| return SynError(it8, "%s", ErrorTitle); | |
| } | |
| Buffer[max] = 0; | |
| return TRUE; | |
| } | |
| // ---------------------------------------------------------- Table | |
| static | |
| TABLE* GetTable(cmsIT8* it8) | |
| { | |
| if ((it8 -> nTable >= it8 ->TablesCount)) { | |
| SynError(it8, "Table %d out of sequence", it8 -> nTable); | |
| return it8 -> Tab; | |
| } | |
| return it8 ->Tab + it8 ->nTable; | |
| } | |
| // ---------------------------------------------------------- Memory management | |
| // Frees an allocator and owned memory | |
| void CMSEXPORT cmsIT8Free(cmsHANDLE hIT8) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| if (it8 == NULL) | |
| return; | |
| if (it8->MemorySink) { | |
| OWNEDMEM* p; | |
| OWNEDMEM* n; | |
| for (p = it8->MemorySink; p != NULL; p = n) { | |
| n = p->Next; | |
| if (p->Ptr) _cmsFree(it8 ->ContextID, p->Ptr); | |
| _cmsFree(it8 ->ContextID, p); | |
| } | |
| } | |
| if (it8->MemoryBlock) | |
| _cmsFree(it8 ->ContextID, it8->MemoryBlock); | |
| _cmsFree(it8 ->ContextID, it8); | |
| } | |
| // Allocates a chunk of data, keep linked list | |
| static | |
| void* AllocBigBlock(cmsIT8* it8, cmsUInt32Number size) | |
| { | |
| OWNEDMEM* ptr1; | |
| void* ptr = _cmsMallocZero(it8->ContextID, size); | |
| if (ptr != NULL) { | |
| ptr1 = (OWNEDMEM*) _cmsMallocZero(it8 ->ContextID, sizeof(OWNEDMEM)); | |
| if (ptr1 == NULL) { | |
| _cmsFree(it8 ->ContextID, ptr); | |
| return NULL; | |
| } | |
| ptr1-> Ptr = ptr; | |
| ptr1-> Next = it8 -> MemorySink; | |
| it8 -> MemorySink = ptr1; | |
| } | |
| return ptr; | |
| } | |
| // Suballocator. | |
| static | |
| void* AllocChunk(cmsIT8* it8, cmsUInt32Number size) | |
| { | |
| cmsUInt32Number Free = it8 ->Allocator.BlockSize - it8 ->Allocator.Used; | |
| cmsUInt8Number* ptr; | |
| size = _cmsALIGNMEM(size); | |
| if (size == 0) return NULL; | |
| if (size > Free) { | |
| cmsUInt8Number* new_block; | |
| if (it8 -> Allocator.BlockSize == 0) | |
| it8 -> Allocator.BlockSize = 20*1024; | |
| else | |
| it8 ->Allocator.BlockSize *= 2; | |
| if (it8 ->Allocator.BlockSize < size) | |
| it8 ->Allocator.BlockSize = size; | |
| it8 ->Allocator.Used = 0; | |
| new_block = (cmsUInt8Number*)AllocBigBlock(it8, it8->Allocator.BlockSize); | |
| if (new_block == NULL) | |
| return NULL; | |
| it8->Allocator.Block = new_block; | |
| } | |
| if (it8->Allocator.Block == NULL) | |
| return NULL; | |
| ptr = it8 ->Allocator.Block + it8 ->Allocator.Used; | |
| it8 ->Allocator.Used += size; | |
| return (void*) ptr; | |
| } | |
| // Allocates a string | |
| static | |
| char *AllocString(cmsIT8* it8, const char* str) | |
| { | |
| cmsUInt32Number Size; | |
| char *ptr; | |
| if (str == NULL) return NULL; | |
| Size = (cmsUInt32Number)strlen(str) + 1; | |
| ptr = (char *) AllocChunk(it8, Size); | |
| if (ptr) memcpy(ptr, str, Size-1); | |
| return ptr; | |
| } | |
| // Searches through linked list | |
| static | |
| cmsBool IsAvailableOnList(KEYVALUE* p, const char* Key, const char* Subkey, KEYVALUE** LastPtr) | |
| { | |
| if (LastPtr) *LastPtr = p; | |
| for (; p != NULL; p = p->Next) { | |
| if (LastPtr) *LastPtr = p; | |
| if (*Key != '#') { // Comments are ignored | |
| if (cmsstrcasecmp(Key, p->Keyword) == 0) | |
| break; | |
| } | |
| } | |
| if (p == NULL) | |
| return FALSE; | |
| if (Subkey == 0) | |
| return TRUE; | |
| for (; p != NULL; p = p->NextSubkey) { | |
| if (p ->Subkey == NULL) continue; | |
| if (LastPtr) *LastPtr = p; | |
| if (cmsstrcasecmp(Subkey, p->Subkey) == 0) | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| // Add a property into a linked list | |
| static | |
| KEYVALUE* AddToList(cmsIT8* it8, KEYVALUE** Head, const char *Key, const char *Subkey, const char* xValue, WRITEMODE WriteAs) | |
| { | |
| KEYVALUE* p; | |
| KEYVALUE* last; | |
| // Check if property is already in list | |
| if (IsAvailableOnList(*Head, Key, Subkey, &p)) { | |
| // This may work for editing properties | |
| if (cmsstrcasecmp(Key, "NUMBER_OF_FIELDS") == 0 || | |
| cmsstrcasecmp(Key, "NUMBER_OF_SETS") == 0) { | |
| SynError(it8, "duplicate key <%s>", Key); | |
| return NULL; | |
| } | |
| } | |
| else { | |
| last = p; | |
| // Allocate the container | |
| p = (KEYVALUE*) AllocChunk(it8, sizeof(KEYVALUE)); | |
| if (p == NULL) | |
| { | |
| SynError(it8, "AddToList: out of memory"); | |
| return NULL; | |
| } | |
| // Store name and value | |
| p->Keyword = AllocString(it8, Key); | |
| p->Subkey = (Subkey == NULL) ? NULL : AllocString(it8, Subkey); | |
| // Keep the container in our list | |
| if (*Head == NULL) { | |
| *Head = p; | |
| } | |
| else | |
| { | |
| if (Subkey != NULL && last != NULL) { | |
| last->NextSubkey = p; | |
| // If Subkey is not null, then last is the last property with the same key, | |
| // but not necessarily is the last property in the list, so we need to move | |
| // to the actual list end | |
| while (last->Next != NULL) | |
| last = last->Next; | |
| } | |
| if (last != NULL) last->Next = p; | |
| } | |
| p->Next = NULL; | |
| p->NextSubkey = NULL; | |
| } | |
| p->WriteAs = WriteAs; | |
| if (xValue != NULL) { | |
| p->Value = AllocString(it8, xValue); | |
| } | |
| else { | |
| p->Value = NULL; | |
| } | |
| return p; | |
| } | |
| static | |
| KEYVALUE* AddAvailableProperty(cmsIT8* it8, const char* Key, WRITEMODE as) | |
| { | |
| return AddToList(it8, &it8->ValidKeywords, Key, NULL, NULL, as); | |
| } | |
| static | |
| KEYVALUE* AddAvailableSampleID(cmsIT8* it8, const char* Key) | |
| { | |
| return AddToList(it8, &it8->ValidSampleID, Key, NULL, NULL, WRITE_UNCOOKED); | |
| } | |
| static | |
| cmsBool AllocTable(cmsIT8* it8) | |
| { | |
| TABLE* t; | |
| if (it8->TablesCount >= (MAXTABLES-1)) | |
| return FALSE; | |
| t = it8 ->Tab + it8 ->TablesCount; | |
| t->HeaderList = NULL; | |
| t->DataFormat = NULL; | |
| t->Data = NULL; | |
| it8 ->TablesCount++; | |
| return TRUE; | |
| } | |
| cmsInt32Number CMSEXPORT cmsIT8SetTable(cmsHANDLE IT8, cmsUInt32Number nTable) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) IT8; | |
| if (nTable >= it8 ->TablesCount) { | |
| if (nTable == it8 ->TablesCount) { | |
| if (!AllocTable(it8)) { | |
| SynError(it8, "Too many tables"); | |
| return -1; | |
| } | |
| } | |
| else { | |
| SynError(it8, "Table %d is out of sequence", nTable); | |
| return -1; | |
| } | |
| } | |
| it8 ->nTable = nTable; | |
| return (cmsInt32Number) nTable; | |
| } | |
| // Init an empty container | |
| cmsHANDLE CMSEXPORT cmsIT8Alloc(cmsContext ContextID) | |
| { | |
| cmsIT8* it8; | |
| cmsUInt32Number i; | |
| it8 = (cmsIT8*) _cmsMallocZero(ContextID, sizeof(cmsIT8)); | |
| if (it8 == NULL) return NULL; | |
| AllocTable(it8); | |
| it8->MemoryBlock = NULL; | |
| it8->MemorySink = NULL; | |
| it8->IsCUBE = FALSE; | |
| it8 ->nTable = 0; | |
| it8->ContextID = ContextID; | |
| it8->Allocator.Used = 0; | |
| it8->Allocator.Block = NULL; | |
| it8->Allocator.BlockSize = 0; | |
| it8->ValidKeywords = NULL; | |
| it8->ValidSampleID = NULL; | |
| it8 -> sy = SUNDEFINED; | |
| it8 -> ch = ' '; | |
| it8 -> Source = NULL; | |
| it8 -> inum = 0; | |
| it8 -> dnum = 0.0; | |
| it8->FileStack[0] = (FILECTX*)AllocChunk(it8, sizeof(FILECTX)); | |
| it8->IncludeSP = 0; | |
| it8 -> lineno = 1; | |
| it8->id = StringAlloc(it8, MAXSTR); | |
| it8->str = StringAlloc(it8, MAXSTR); | |
| strcpy(it8->DoubleFormatter, DEFAULT_DBL_FORMAT); | |
| cmsIT8SetSheetType((cmsHANDLE) it8, "CGATS.17"); | |
| // Initialize predefined properties & data | |
| for (i=0; i < NUMPREDEFINEDPROPS; i++) | |
| AddAvailableProperty(it8, PredefinedProperties[i].id, PredefinedProperties[i].as); | |
| for (i=0; i < NUMPREDEFINEDSAMPLEID; i++) | |
| AddAvailableSampleID(it8, PredefinedSampleID[i]); | |
| return (cmsHANDLE) it8; | |
| } | |
| const char* CMSEXPORT cmsIT8GetSheetType(cmsHANDLE hIT8) | |
| { | |
| return GetTable((cmsIT8*) hIT8)->SheetType; | |
| } | |
| cmsBool CMSEXPORT cmsIT8SetSheetType(cmsHANDLE hIT8, const char* Type) | |
| { | |
| TABLE* t = GetTable((cmsIT8*) hIT8); | |
| strncpy(t ->SheetType, Type, MAXSTR-1); | |
| t ->SheetType[MAXSTR-1] = 0; | |
| return TRUE; | |
| } | |
| cmsBool CMSEXPORT cmsIT8SetComment(cmsHANDLE hIT8, const char* Val) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| if (!Val) return FALSE; | |
| if (!*Val) return FALSE; | |
| return AddToList(it8, &GetTable(it8)->HeaderList, "# ", NULL, Val, WRITE_UNCOOKED) != NULL; | |
| } | |
| // Sets a property | |
| cmsBool CMSEXPORT cmsIT8SetPropertyStr(cmsHANDLE hIT8, const char* Key, const char *Val) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| if (!Val) return FALSE; | |
| if (!*Val) return FALSE; | |
| return AddToList(it8, &GetTable(it8)->HeaderList, Key, NULL, Val, WRITE_STRINGIFY) != NULL; | |
| } | |
| cmsBool CMSEXPORT cmsIT8SetPropertyDbl(cmsHANDLE hIT8, const char* cProp, cmsFloat64Number Val) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| char Buffer[1024]; | |
| snprintf(Buffer, 1023, it8->DoubleFormatter, Val); | |
| return AddToList(it8, &GetTable(it8)->HeaderList, cProp, NULL, Buffer, WRITE_UNCOOKED) != NULL; | |
| } | |
| cmsBool CMSEXPORT cmsIT8SetPropertyHex(cmsHANDLE hIT8, const char* cProp, cmsUInt32Number Val) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| char Buffer[1024]; | |
| snprintf(Buffer, 1023, "%u", Val); | |
| return AddToList(it8, &GetTable(it8)->HeaderList, cProp, NULL, Buffer, WRITE_HEXADECIMAL) != NULL; | |
| } | |
| cmsBool CMSEXPORT cmsIT8SetPropertyUncooked(cmsHANDLE hIT8, const char* Key, const char* Buffer) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| return AddToList(it8, &GetTable(it8)->HeaderList, Key, NULL, Buffer, WRITE_UNCOOKED) != NULL; | |
| } | |
| cmsBool CMSEXPORT cmsIT8SetPropertyMulti(cmsHANDLE hIT8, const char* Key, const char* SubKey, const char *Buffer) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| return AddToList(it8, &GetTable(it8)->HeaderList, Key, SubKey, Buffer, WRITE_PAIR) != NULL; | |
| } | |
| // Gets a property | |
| const char* CMSEXPORT cmsIT8GetProperty(cmsHANDLE hIT8, const char* Key) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| KEYVALUE* p; | |
| if (IsAvailableOnList(GetTable(it8) -> HeaderList, Key, NULL, &p)) | |
| { | |
| return p -> Value; | |
| } | |
| return NULL; | |
| } | |
| cmsFloat64Number CMSEXPORT cmsIT8GetPropertyDbl(cmsHANDLE hIT8, const char* cProp) | |
| { | |
| const char *v = cmsIT8GetProperty(hIT8, cProp); | |
| if (v == NULL) return 0.0; | |
| return ParseFloatNumber(v); | |
| } | |
| const char* CMSEXPORT cmsIT8GetPropertyMulti(cmsHANDLE hIT8, const char* Key, const char *SubKey) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| KEYVALUE* p; | |
| if (IsAvailableOnList(GetTable(it8) -> HeaderList, Key, SubKey, &p)) { | |
| return p -> Value; | |
| } | |
| return NULL; | |
| } | |
| // ----------------------------------------------------------------- Datasets | |
| // A safe atoi that returns 0 when NULL input is given | |
| static | |
| cmsInt32Number satoi(const char* b) | |
| { | |
| int n; | |
| if (b == NULL) return 0; | |
| n = atoi(b); | |
| if (n > 0x7ffffff0L) return 0x7ffffff0L; | |
| if (n < -0x7ffffff0L) return -0x7ffffff0L; | |
| return (cmsInt32Number)n; | |
| } | |
| static | |
| cmsBool AllocateDataFormat(cmsIT8* it8) | |
| { | |
| cmsUInt32Number size; | |
| TABLE* t = GetTable(it8); | |
| if (t->DataFormat) return TRUE; // Already allocated | |
| t->nSamples = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_FIELDS")); | |
| if (t->nSamples <= 0 || t->nSamples > 0x7ffe) { | |
| SynError(it8, "Wrong NUMBER_OF_FIELDS"); | |
| return FALSE; | |
| } | |
| size = ((cmsUInt32Number)t->nSamples + 1) * sizeof(char*); | |
| t->DataFormat = (char**)AllocChunk(it8, size); | |
| if (t->DataFormat == NULL) { | |
| SynError(it8, "Unable to allocate dataFormat array"); | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| static | |
| const char *GetDataFormat(cmsIT8* it8, int n) | |
| { | |
| TABLE* t = GetTable(it8); | |
| if (t->DataFormat) | |
| return t->DataFormat[n]; | |
| return NULL; | |
| } | |
| static | |
| cmsBool SetDataFormat(cmsIT8* it8, int n, const char *label) | |
| { | |
| TABLE* t = GetTable(it8); | |
| if (!t->DataFormat) { | |
| if (!AllocateDataFormat(it8)) | |
| return FALSE; | |
| } | |
| if (n >= t -> nSamples) { | |
| SynError(it8, "More than NUMBER_OF_FIELDS fields."); | |
| return FALSE; | |
| } | |
| if (t->DataFormat) { | |
| t->DataFormat[n] = AllocString(it8, label); | |
| if (t->DataFormat[n] == NULL) return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| cmsBool CMSEXPORT cmsIT8SetDataFormat(cmsHANDLE h, int n, const char *Sample) | |
| { | |
| cmsIT8* it8 = (cmsIT8*)h; | |
| return SetDataFormat(it8, n, Sample); | |
| } | |
| // Convert to binary | |
| static | |
| const char* satob(const char* v) | |
| { | |
| cmsUInt32Number x; | |
| static char buf[33]; | |
| char *s = buf + 33; | |
| if (v == NULL) return "0"; | |
| x = atoi(v); | |
| *--s = 0; | |
| if (!x) *--s = '0'; | |
| for (; x; x /= 2) *--s = '0' + x%2; | |
| return s; | |
| } | |
| static | |
| cmsBool AllocateDataSet(cmsIT8* it8) | |
| { | |
| TABLE* t = GetTable(it8); | |
| if (t -> Data) return TRUE; // Already allocated | |
| t-> nSamples = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_FIELDS")); | |
| t-> nPatches = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_SETS")); | |
| if (t -> nSamples < 0 || t->nSamples > 0x7ffe || t->nPatches < 0 || t->nPatches > 0x7ffe || | |
| (t->nPatches * t->nSamples) > 200000) | |
| { | |
| SynError(it8, "AllocateDataSet: too much data"); | |
| return FALSE; | |
| } | |
| else { | |
| // Some dumb analyzers warns of possible overflow here, just take a look couple of lines above. | |
| t->Data = (char**)AllocChunk(it8, ((cmsUInt32Number)t->nSamples + 1) * ((cmsUInt32Number)t->nPatches + 1) * sizeof(char*)); | |
| if (t->Data == NULL) { | |
| SynError(it8, "AllocateDataSet: Unable to allocate data array"); | |
| return FALSE; | |
| } | |
| } | |
| return TRUE; | |
| } | |
| static | |
| char* GetData(cmsIT8* it8, int nSet, int nField) | |
| { | |
| TABLE* t = GetTable(it8); | |
| int nSamples = t -> nSamples; | |
| int nPatches = t -> nPatches; | |
| if (nSet < 0 || nSet >= nPatches || nField < 0 || nField >= nSamples) | |
| return NULL; | |
| if (!t->Data) return NULL; | |
| return t->Data [nSet * nSamples + nField]; | |
| } | |
| static | |
| cmsBool SetData(cmsIT8* it8, int nSet, int nField, const char *Val) | |
| { | |
| char* ptr; | |
| TABLE* t = GetTable(it8); | |
| if (!t->Data) { | |
| if (!AllocateDataSet(it8)) return FALSE; | |
| } | |
| if (!t->Data) return FALSE; | |
| if (nSet > t -> nPatches || nSet < 0) { | |
| return SynError(it8, "Patch %d out of range, there are %d patches", nSet, t -> nPatches); | |
| } | |
| if (nField > t ->nSamples || nField < 0) { | |
| return SynError(it8, "Sample %d out of range, there are %d samples", nField, t ->nSamples); | |
| } | |
| ptr = AllocString(it8, Val); | |
| if (ptr == NULL) | |
| return FALSE; | |
| t->Data [nSet * t -> nSamples + nField] = ptr; | |
| return TRUE; | |
| } | |
| // --------------------------------------------------------------- File I/O | |
| // Writes a string to file | |
| static | |
| void WriteStr(SAVESTREAM* f, const char *str) | |
| { | |
| cmsUInt32Number len; | |
| if (str == NULL) | |
| str = " "; | |
| // Length to write | |
| len = (cmsUInt32Number) strlen(str); | |
| f ->Used += len; | |
| if (f ->stream) { // Should I write it to a file? | |
| if (fwrite(str, 1, len, f->stream) != len) { | |
| cmsSignalError(0, cmsERROR_WRITE, "Write to file error in CGATS parser"); | |
| return; | |
| } | |
| } | |
| else { // Or to a memory block? | |
| if (f ->Base) { // Am I just counting the bytes? | |
| if (f ->Used > f ->Max) { | |
| cmsSignalError(0, cmsERROR_WRITE, "Write to memory overflows in CGATS parser"); | |
| return; | |
| } | |
| memmove(f ->Ptr, str, len); | |
| f->Ptr += len; | |
| } | |
| } | |
| } | |
| // Write formatted | |
| static | |
| void Writef(SAVESTREAM* f, const char* frm, ...) | |
| { | |
| char Buffer[4096]; | |
| va_list args; | |
| va_start(args, frm); | |
| vsnprintf(Buffer, 4095, frm, args); | |
| Buffer[4095] = 0; | |
| WriteStr(f, Buffer); | |
| va_end(args); | |
| } | |
| // Writes full header | |
| static | |
| void WriteHeader(cmsIT8* it8, SAVESTREAM* fp) | |
| { | |
| KEYVALUE* p; | |
| TABLE* t = GetTable(it8); | |
| // Writes the type | |
| WriteStr(fp, t->SheetType); | |
| WriteStr(fp, "\n"); | |
| for (p = t->HeaderList; (p != NULL); p = p->Next) | |
| { | |
| if (*p ->Keyword == '#') { | |
| char* Pt; | |
| WriteStr(fp, "#\n# "); | |
| for (Pt = p ->Value; *Pt; Pt++) { | |
| Writef(fp, "%c", *Pt); | |
| if (*Pt == '\n') { | |
| WriteStr(fp, "# "); | |
| } | |
| } | |
| WriteStr(fp, "\n#\n"); | |
| continue; | |
| } | |
| if (!IsAvailableOnList(it8-> ValidKeywords, p->Keyword, NULL, NULL)) { | |
| #ifdef CMS_STRICT_CGATS | |
| WriteStr(fp, "KEYWORD\t\""); | |
| WriteStr(fp, p->Keyword); | |
| WriteStr(fp, "\"\n"); | |
| #endif | |
| AddAvailableProperty(it8, p->Keyword, WRITE_UNCOOKED); | |
| } | |
| WriteStr(fp, p->Keyword); | |
| if (p->Value) { | |
| switch (p ->WriteAs) { | |
| case WRITE_UNCOOKED: | |
| Writef(fp, "\t%s", p ->Value); | |
| break; | |
| case WRITE_STRINGIFY: | |
| Writef(fp, "\t\"%s\"", p->Value ); | |
| break; | |
| case WRITE_HEXADECIMAL: | |
| Writef(fp, "\t0x%X", satoi(p ->Value)); | |
| break; | |
| case WRITE_BINARY: | |
| Writef(fp, "\t0b%s", satob(p ->Value)); | |
| break; | |
| case WRITE_PAIR: | |
| Writef(fp, "\t\"%s,%s\"", p->Subkey, p->Value); | |
| break; | |
| default: SynError(it8, "Unknown write mode %d", p ->WriteAs); | |
| return; | |
| } | |
| } | |
| WriteStr (fp, "\n"); | |
| } | |
| } | |
| // Writes the data format | |
| static | |
| void WriteDataFormat(SAVESTREAM* fp, cmsIT8* it8) | |
| { | |
| int i, nSamples; | |
| TABLE* t = GetTable(it8); | |
| if (!t -> DataFormat) return; | |
| WriteStr(fp, "BEGIN_DATA_FORMAT\n"); | |
| WriteStr(fp, " "); | |
| nSamples = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_FIELDS")); | |
| if (nSamples <= t->nSamples) { | |
| for (i = 0; i < nSamples; i++) { | |
| WriteStr(fp, t->DataFormat[i]); | |
| WriteStr(fp, ((i == (nSamples - 1)) ? "\n" : "\t")); | |
| } | |
| } | |
| WriteStr (fp, "END_DATA_FORMAT\n"); | |
| } | |
| // Writes data array | |
| static | |
| void WriteData(SAVESTREAM* fp, cmsIT8* it8) | |
| { | |
| int i, j, nPatches; | |
| TABLE* t = GetTable(it8); | |
| if (!t->Data) return; | |
| WriteStr (fp, "BEGIN_DATA\n"); | |
| nPatches = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_SETS")); | |
| if (nPatches <= t->nPatches) { | |
| for (i = 0; i < nPatches; i++) { | |
| WriteStr(fp, " "); | |
| for (j = 0; j < t->nSamples; j++) { | |
| char* ptr = t->Data[i * t->nSamples + j]; | |
| if (ptr == NULL) WriteStr(fp, "\"\""); | |
| else { | |
| // If value contains whitespace, enclose within quote | |
| if (strchr(ptr, ' ') != NULL) { | |
| WriteStr(fp, "\""); | |
| WriteStr(fp, ptr); | |
| WriteStr(fp, "\""); | |
| } | |
| else | |
| WriteStr(fp, ptr); | |
| } | |
| WriteStr(fp, ((j == (t->nSamples - 1)) ? "\n" : "\t")); | |
| } | |
| } | |
| } | |
| WriteStr (fp, "END_DATA\n"); | |
| } | |
| // Saves whole file | |
| cmsBool CMSEXPORT cmsIT8SaveToFile(cmsHANDLE hIT8, const char* cFileName) | |
| { | |
| SAVESTREAM sd; | |
| cmsUInt32Number i; | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| memset(&sd, 0, sizeof(sd)); | |
| sd.stream = fopen(cFileName, "wt"); | |
| if (!sd.stream) return FALSE; | |
| for (i=0; i < it8 ->TablesCount; i++) { | |
| TABLE* t; | |
| if (cmsIT8SetTable(hIT8, i) < 0) goto Error; | |
| /** | |
| * Check for wrong data | |
| */ | |
| t = GetTable(it8); | |
| if (t->Data == NULL) goto Error; | |
| if (t->DataFormat == NULL) goto Error; | |
| WriteHeader(it8, &sd); | |
| WriteDataFormat(&sd, it8); | |
| WriteData(&sd, it8); | |
| } | |
| if (fclose(sd.stream) != 0) return FALSE; | |
| return TRUE; | |
| Error: | |
| fclose(sd.stream); | |
| return FALSE; | |
| } | |
| // Saves to memory | |
| cmsBool CMSEXPORT cmsIT8SaveToMem(cmsHANDLE hIT8, void *MemPtr, cmsUInt32Number* BytesNeeded) | |
| { | |
| SAVESTREAM sd; | |
| cmsUInt32Number i; | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| memset(&sd, 0, sizeof(sd)); | |
| sd.stream = NULL; | |
| sd.Base = (cmsUInt8Number*) MemPtr; | |
| sd.Ptr = sd.Base; | |
| sd.Used = 0; | |
| if (sd.Base && (*BytesNeeded > 0)) { | |
| sd.Max = (*BytesNeeded) - 1; // Write to memory? | |
| } | |
| else | |
| sd.Max = 0; // Just counting the needed bytes | |
| for (i=0; i < it8 ->TablesCount; i++) { | |
| cmsIT8SetTable(hIT8, i); | |
| WriteHeader(it8, &sd); | |
| WriteDataFormat(&sd, it8); | |
| WriteData(&sd, it8); | |
| } | |
| sd.Used++; // The \0 at the very end | |
| if (sd.Base) | |
| *sd.Ptr = 0; | |
| *BytesNeeded = sd.Used; | |
| return TRUE; | |
| } | |
| // -------------------------------------------------------------- Higher level parsing | |
| static | |
| cmsBool DataFormatSection(cmsIT8* it8) | |
| { | |
| int iField = 0; | |
| TABLE* t = GetTable(it8); | |
| InSymbol(it8); // Eats "BEGIN_DATA_FORMAT" | |
| CheckEOLN(it8); | |
| while (it8->sy != SEND_DATA_FORMAT && | |
| it8->sy != SEOLN && | |
| it8->sy != SEOF && | |
| it8->sy != SSYNERROR) { | |
| if (it8->sy != SIDENT) { | |
| return SynError(it8, "Sample type expected"); | |
| } | |
| if (!SetDataFormat(it8, iField, StringPtr(it8->id))) return FALSE; | |
| iField++; | |
| InSymbol(it8); | |
| SkipEOLN(it8); | |
| } | |
| SkipEOLN(it8); | |
| Skip(it8, SEND_DATA_FORMAT); | |
| SkipEOLN(it8); | |
| if (iField != t ->nSamples) { | |
| SynError(it8, "Count mismatch. NUMBER_OF_FIELDS was %d, found %d\n", t ->nSamples, iField); | |
| } | |
| return TRUE; | |
| } | |
| static | |
| cmsBool DataSection (cmsIT8* it8) | |
| { | |
| int iField = 0; | |
| int iSet = 0; | |
| char Buffer[256]; | |
| TABLE* t = GetTable(it8); | |
| InSymbol(it8); // Eats "BEGIN_DATA" | |
| CheckEOLN(it8); | |
| if (!t->Data) { | |
| if (!AllocateDataSet(it8)) return FALSE; | |
| } | |
| while (it8->sy != SEND_DATA && it8->sy != SEOF && it8->sy != SSYNERROR) | |
| { | |
| if (iField >= t -> nSamples) { | |
| iField = 0; | |
| iSet++; | |
| } | |
| if (it8->sy != SEND_DATA && it8->sy != SEOF && it8->sy != SSYNERROR) { | |
| switch (it8->sy) | |
| { | |
| // To keep very long data | |
| case SIDENT: | |
| if (!SetData(it8, iSet, iField, StringPtr(it8->id))) | |
| return FALSE; | |
| break; | |
| case SSTRING: | |
| if (!SetData(it8, iSet, iField, StringPtr(it8->str))) | |
| return FALSE; | |
| break; | |
| default: | |
| if (!GetVal(it8, Buffer, 255, "Sample data expected")) | |
| return FALSE; | |
| if (!SetData(it8, iSet, iField, Buffer)) | |
| return FALSE; | |
| } | |
| iField++; | |
| InSymbol(it8); | |
| SkipEOLN(it8); | |
| } | |
| } | |
| SkipEOLN(it8); | |
| Skip(it8, SEND_DATA); | |
| SkipEOLN(it8); | |
| // Check for data completion. | |
| if ((iSet+1) != t -> nPatches) | |
| return SynError(it8, "Count mismatch. NUMBER_OF_SETS was %d, found %d\n", t ->nPatches, iSet+1); | |
| return TRUE; | |
| } | |
| static | |
| cmsBool HeaderSection(cmsIT8* it8) | |
| { | |
| char VarName[MAXID]; | |
| char Buffer[MAXSTR]; | |
| KEYVALUE* Key; | |
| while (it8->sy != SEOF && | |
| it8->sy != SSYNERROR && | |
| it8->sy != SBEGIN_DATA_FORMAT && | |
| it8->sy != SBEGIN_DATA) { | |
| switch (it8 -> sy) { | |
| case SKEYWORD: | |
| InSymbol(it8); | |
| if (!GetVal(it8, Buffer, MAXSTR-1, "Keyword expected")) return FALSE; | |
| if (!AddAvailableProperty(it8, Buffer, WRITE_UNCOOKED)) return FALSE; | |
| InSymbol(it8); | |
| break; | |
| case SDATA_FORMAT_ID: | |
| InSymbol(it8); | |
| if (!GetVal(it8, Buffer, MAXSTR-1, "Keyword expected")) return FALSE; | |
| if (!AddAvailableSampleID(it8, Buffer)) return FALSE; | |
| InSymbol(it8); | |
| break; | |
| case SIDENT: | |
| strncpy(VarName, StringPtr(it8->id), MAXID - 1); | |
| VarName[MAXID - 1] = 0; | |
| if (!IsAvailableOnList(it8->ValidKeywords, VarName, NULL, &Key)) { | |
| #ifdef CMS_STRICT_CGATS | |
| return SynError(it8, "Undefined keyword '%s'", VarName); | |
| #else | |
| Key = AddAvailableProperty(it8, VarName, WRITE_UNCOOKED); | |
| if (Key == NULL) return FALSE; | |
| #endif | |
| } | |
| InSymbol(it8); | |
| if (!GetVal(it8, Buffer, MAXSTR - 1, "Property data expected")) return FALSE; | |
| if (Key->WriteAs != WRITE_PAIR) { | |
| if (AddToList(it8, &GetTable(it8)->HeaderList, VarName, NULL, Buffer, | |
| (it8->sy == SSTRING) ? WRITE_STRINGIFY : WRITE_UNCOOKED) == NULL) return FALSE; | |
| } | |
| else { | |
| const char *Subkey; | |
| char *Nextkey; | |
| if (it8->sy != SSTRING) | |
| return SynError(it8, "Invalid value '%s' for property '%s'.", Buffer, VarName); | |
| // chop the string as a list of "subkey, value" pairs, using ';' as a separator | |
| for (Subkey = Buffer; Subkey != NULL; Subkey = Nextkey) | |
| { | |
| char *Value, *temp; | |
| // identify token pair boundary | |
| Nextkey = (char*)strchr(Subkey, ';'); | |
| if (Nextkey) | |
| *Nextkey++ = '\0'; | |
| // for each pair, split the subkey and the value | |
| Value = (char*)strrchr(Subkey, ','); | |
| if (Value == NULL) | |
| return SynError(it8, "Invalid value for property '%s'.", VarName); | |
| // gobble the spaces before the coma, and the coma itself | |
| temp = Value++; | |
| do *temp-- = '\0'; while (temp >= Subkey && *temp == ' '); | |
| // gobble any space at the right | |
| temp = Value + strlen(Value) - 1; | |
| while (*temp == ' ') *temp-- = '\0'; | |
| // trim the strings from the left | |
| Subkey += strspn(Subkey, " "); | |
| Value += strspn(Value, " "); | |
| if (Subkey[0] == 0 || Value[0] == 0) | |
| return SynError(it8, "Invalid value for property '%s'.", VarName); | |
| AddToList(it8, &GetTable(it8)->HeaderList, VarName, Subkey, Value, WRITE_PAIR); | |
| } | |
| } | |
| InSymbol(it8); | |
| break; | |
| case SEOLN: break; | |
| default: | |
| return SynError(it8, "expected keyword or identifier"); | |
| } | |
| SkipEOLN(it8); | |
| } | |
| return TRUE; | |
| } | |
| static | |
| void ReadType(cmsIT8* it8, char* SheetTypePtr) | |
| { | |
| cmsInt32Number cnt = 0; | |
| // First line is a very special case. | |
| while (isseparator(it8->ch)) | |
| NextCh(it8); | |
| while (it8->ch != '\r' && it8 ->ch != '\n' && it8->ch != '\t' && it8 -> ch != 0) { | |
| if (cnt++ < MAXSTR) | |
| *SheetTypePtr++= (char) it8 ->ch; | |
| NextCh(it8); | |
| } | |
| *SheetTypePtr = 0; | |
| } | |
| static | |
| cmsBool ParseIT8(cmsIT8* it8, cmsBool nosheet) | |
| { | |
| char* SheetTypePtr = it8 ->Tab[0].SheetType; | |
| if (nosheet == 0) { | |
| ReadType(it8, SheetTypePtr); | |
| } | |
| InSymbol(it8); | |
| SkipEOLN(it8); | |
| while (it8-> sy != SEOF && | |
| it8-> sy != SSYNERROR) { | |
| switch (it8 -> sy) { | |
| case SBEGIN_DATA_FORMAT: | |
| if (!DataFormatSection(it8)) return FALSE; | |
| break; | |
| case SBEGIN_DATA: | |
| if (!DataSection(it8)) return FALSE; | |
| if (it8 -> sy != SEOF && it8->sy != SSYNERROR) { | |
| if (!AllocTable(it8)) return FALSE; | |
| it8 ->nTable = it8 ->TablesCount - 1; | |
| // Read sheet type if present. We only support identifier and string. | |
| // <ident> <eoln> is a type string | |
| // anything else, is not a type string | |
| if (nosheet == 0) { | |
| if (it8 ->sy == SIDENT) { | |
| // May be a type sheet or may be a prop value statement. We cannot use insymbol in | |
| // this special case... | |
| while (isseparator(it8->ch)) | |
| NextCh(it8); | |
| // If a newline is found, then this is a type string | |
| if (it8 ->ch == '\n' || it8->ch == '\r') { | |
| cmsIT8SetSheetType(it8, StringPtr(it8 ->id)); | |
| InSymbol(it8); | |
| } | |
| else | |
| { | |
| // It is not. Just continue | |
| cmsIT8SetSheetType(it8, ""); | |
| } | |
| } | |
| else | |
| // Validate quoted strings | |
| if (it8 ->sy == SSTRING) { | |
| cmsIT8SetSheetType(it8, StringPtr(it8 ->str)); | |
| InSymbol(it8); | |
| } | |
| } | |
| } | |
| break; | |
| case SEOLN: | |
| SkipEOLN(it8); | |
| break; | |
| default: | |
| if (!HeaderSection(it8)) return FALSE; | |
| } | |
| } | |
| return (it8 -> sy != SSYNERROR); | |
| } | |
| // Init useful pointers | |
| static | |
| void CookPointers(cmsIT8* it8) | |
| { | |
| int idField, i; | |
| char* Fld; | |
| cmsUInt32Number j; | |
| cmsUInt32Number nOldTable = it8->nTable; | |
| for (j = 0; j < it8->TablesCount; j++) { | |
| TABLE* t = it8->Tab + j; | |
| t->SampleID = 0; | |
| it8->nTable = j; | |
| for (idField = 0; idField < t->nSamples; idField++) | |
| { | |
| if (t->DataFormat == NULL) { | |
| SynError(it8, "Undefined DATA_FORMAT"); | |
| return; | |
| } | |
| Fld = t->DataFormat[idField]; | |
| if (!Fld) continue; | |
| if (cmsstrcasecmp(Fld, "SAMPLE_ID") == 0) { | |
| t->SampleID = idField; | |
| } | |
| // "LABEL" is an extension. It keeps references to forward tables | |
| if ((cmsstrcasecmp(Fld, "LABEL") == 0) || Fld[0] == '$') { | |
| // Search for table references... | |
| for (i = 0; i < t->nPatches; i++) { | |
| char* Label = GetData(it8, i, idField); | |
| if (Label) { | |
| cmsUInt32Number k; | |
| // This is the label, search for a table containing | |
| // this property | |
| for (k = 0; k < it8->TablesCount; k++) { | |
| TABLE* Table = it8->Tab + k; | |
| KEYVALUE* p; | |
| if (IsAvailableOnList(Table->HeaderList, Label, NULL, &p)) { | |
| // Available, keep type and table | |
| char Buffer[256]; | |
| char* Type = p->Value; | |
| int nTable = (int)k; | |
| snprintf(Buffer, 255, "%s %d %s", Label, nTable, Type); | |
| SetData(it8, i, idField, Buffer); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| it8->nTable = nOldTable; | |
| } | |
| // Try to infere if the file is a CGATS/IT8 file at all. Read first line | |
| // that should be something like some printable characters plus a \n | |
| // returns 0 if this is not like a CGATS, or an integer otherwise. This integer is the number of words in first line? | |
| static | |
| int IsMyBlock(const cmsUInt8Number* Buffer, cmsUInt32Number n) | |
| { | |
| int words = 1, space = 0, quot = 0; | |
| cmsUInt32Number i; | |
| if (n < 10) return 0; // Too small | |
| if (n > 132) | |
| n = 132; | |
| for (i = 1; i < n; i++) { | |
| switch(Buffer[i]) | |
| { | |
| case '\n': | |
| case '\r': | |
| return ((quot == 1) || (words > 2)) ? 0 : words; | |
| case '\t': | |
| case ' ': | |
| if(!quot && !space) | |
| space = 1; | |
| break; | |
| case '\"': | |
| quot = !quot; | |
| break; | |
| default: | |
| if (Buffer[i] < 32) return 0; | |
| if (Buffer[i] > 127) return 0; | |
| words += space; | |
| space = 0; | |
| break; | |
| } | |
| } | |
| return 0; | |
| } | |
| static | |
| cmsBool IsMyFile(const char* FileName) | |
| { | |
| FILE *fp; | |
| cmsUInt32Number Size; | |
| cmsUInt8Number Ptr[133]; | |
| fp = fopen(FileName, "rt"); | |
| if (!fp) { | |
| cmsSignalError(0, cmsERROR_FILE, "File '%s' not found", FileName); | |
| return FALSE; | |
| } | |
| Size = (cmsUInt32Number) fread(Ptr, 1, 132, fp); | |
| if (fclose(fp) != 0) | |
| return FALSE; | |
| Ptr[Size] = '\0'; | |
| return IsMyBlock(Ptr, Size); | |
| } | |
| // ---------------------------------------------------------- Exported routines | |
| cmsHANDLE CMSEXPORT cmsIT8LoadFromMem(cmsContext ContextID, const void *Ptr, cmsUInt32Number len) | |
| { | |
| cmsHANDLE hIT8; | |
| cmsIT8* it8; | |
| int type; | |
| _cmsAssert(Ptr != NULL); | |
| _cmsAssert(len != 0); | |
| type = IsMyBlock((const cmsUInt8Number*)Ptr, len); | |
| if (type == 0) return NULL; | |
| hIT8 = cmsIT8Alloc(ContextID); | |
| if (!hIT8) return NULL; | |
| it8 = (cmsIT8*) hIT8; | |
| it8 ->MemoryBlock = (char*) _cmsMalloc(ContextID, len + 1); | |
| if (it8->MemoryBlock == NULL) | |
| { | |
| cmsIT8Free(hIT8); | |
| return NULL; | |
| } | |
| strncpy(it8 ->MemoryBlock, (const char*) Ptr, len); | |
| it8 ->MemoryBlock[len] = 0; | |
| strncpy(it8->FileStack[0]->FileName, "", cmsMAX_PATH-1); | |
| it8-> Source = it8 -> MemoryBlock; | |
| if (!ParseIT8(it8, type-1)) { | |
| cmsIT8Free(hIT8); | |
| return NULL; | |
| } | |
| CookPointers(it8); | |
| it8 ->nTable = 0; | |
| _cmsFree(ContextID, it8->MemoryBlock); | |
| it8 -> MemoryBlock = NULL; | |
| return hIT8; | |
| } | |
| cmsHANDLE CMSEXPORT cmsIT8LoadFromFile(cmsContext ContextID, const char* cFileName) | |
| { | |
| cmsHANDLE hIT8; | |
| cmsIT8* it8; | |
| int type; | |
| _cmsAssert(cFileName != NULL); | |
| type = IsMyFile(cFileName); | |
| if (type == 0) return NULL; | |
| hIT8 = cmsIT8Alloc(ContextID); | |
| it8 = (cmsIT8*) hIT8; | |
| if (!hIT8) return NULL; | |
| it8 ->FileStack[0]->Stream = fopen(cFileName, "rt"); | |
| if (!it8 ->FileStack[0]->Stream) { | |
| cmsIT8Free(hIT8); | |
| return NULL; | |
| } | |
| strncpy(it8->FileStack[0]->FileName, cFileName, cmsMAX_PATH-1); | |
| it8->FileStack[0]->FileName[cmsMAX_PATH-1] = 0; | |
| if (!ParseIT8(it8, type-1)) { | |
| fclose(it8 ->FileStack[0]->Stream); | |
| cmsIT8Free(hIT8); | |
| return NULL; | |
| } | |
| CookPointers(it8); | |
| it8 ->nTable = 0; | |
| if (fclose(it8 ->FileStack[0]->Stream)!= 0) { | |
| cmsIT8Free(hIT8); | |
| return NULL; | |
| } | |
| return hIT8; | |
| } | |
| int CMSEXPORT cmsIT8EnumDataFormat(cmsHANDLE hIT8, char ***SampleNames) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| TABLE* t; | |
| _cmsAssert(hIT8 != NULL); | |
| t = GetTable(it8); | |
| if (SampleNames) | |
| *SampleNames = t -> DataFormat; | |
| return t -> nSamples; | |
| } | |
| cmsUInt32Number CMSEXPORT cmsIT8EnumProperties(cmsHANDLE hIT8, char ***PropertyNames) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| KEYVALUE* p; | |
| cmsUInt32Number n; | |
| char **Props; | |
| TABLE* t; | |
| _cmsAssert(hIT8 != NULL); | |
| t = GetTable(it8); | |
| // Pass#1 - count properties | |
| n = 0; | |
| for (p = t -> HeaderList; p != NULL; p = p->Next) { | |
| n++; | |
| } | |
| Props = (char**)AllocChunk(it8, sizeof(char*) * n); | |
| if (Props != NULL) { | |
| // Pass#2 - Fill pointers | |
| n = 0; | |
| for (p = t->HeaderList; p != NULL; p = p->Next) { | |
| Props[n++] = p->Keyword; | |
| } | |
| } | |
| *PropertyNames = Props; | |
| return n; | |
| } | |
| cmsUInt32Number CMSEXPORT cmsIT8EnumPropertyMulti(cmsHANDLE hIT8, const char* cProp, const char ***SubpropertyNames) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| KEYVALUE *p, *tmp; | |
| cmsUInt32Number n; | |
| const char **Props; | |
| TABLE* t; | |
| _cmsAssert(hIT8 != NULL); | |
| t = GetTable(it8); | |
| if(!IsAvailableOnList(t->HeaderList, cProp, NULL, &p)) { | |
| *SubpropertyNames = 0; | |
| return 0; | |
| } | |
| // Pass#1 - count properties | |
| n = 0; | |
| for (tmp = p; tmp != NULL; tmp = tmp->NextSubkey) { | |
| if(tmp->Subkey != NULL) | |
| n++; | |
| } | |
| Props = (const char **) AllocChunk(it8, sizeof(char *) * n); | |
| if (Props != NULL) { | |
| // Pass#2 - Fill pointers | |
| n = 0; | |
| for (tmp = p; tmp != NULL; tmp = tmp->NextSubkey) { | |
| if (tmp->Subkey != NULL) | |
| Props[n++] = p->Subkey; | |
| } | |
| } | |
| *SubpropertyNames = Props; | |
| return n; | |
| } | |
| static | |
| int LocatePatch(cmsIT8* it8, const char* cPatch) | |
| { | |
| int i; | |
| const char *data; | |
| TABLE* t = GetTable(it8); | |
| for (i=0; i < t-> nPatches; i++) { | |
| data = GetData(it8, i, t->SampleID); | |
| if (data != NULL) { | |
| if (cmsstrcasecmp(data, cPatch) == 0) | |
| return i; | |
| } | |
| } | |
| // SynError(it8, "Couldn't find patch '%s'\n", cPatch); | |
| return -1; | |
| } | |
| static | |
| int LocateEmptyPatch(cmsIT8* it8) | |
| { | |
| int i; | |
| const char *data; | |
| TABLE* t = GetTable(it8); | |
| for (i=0; i < t-> nPatches; i++) { | |
| data = GetData(it8, i, t->SampleID); | |
| if (data == NULL) | |
| return i; | |
| } | |
| return -1; | |
| } | |
| static | |
| int LocateSample(cmsIT8* it8, const char* cSample) | |
| { | |
| int i; | |
| const char *fld; | |
| TABLE* t = GetTable(it8); | |
| for (i=0; i < t->nSamples; i++) { | |
| fld = GetDataFormat(it8, i); | |
| if (fld != NULL) { | |
| if (cmsstrcasecmp(fld, cSample) == 0) | |
| return i; | |
| } | |
| } | |
| return -1; | |
| } | |
| int CMSEXPORT cmsIT8FindDataFormat(cmsHANDLE hIT8, const char* cSample) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| _cmsAssert(hIT8 != NULL); | |
| return LocateSample(it8, cSample); | |
| } | |
| const char* CMSEXPORT cmsIT8GetDataRowCol(cmsHANDLE hIT8, int row, int col) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| _cmsAssert(hIT8 != NULL); | |
| return GetData(it8, row, col); | |
| } | |
| cmsFloat64Number CMSEXPORT cmsIT8GetDataRowColDbl(cmsHANDLE hIT8, int row, int col) | |
| { | |
| const char* Buffer; | |
| Buffer = cmsIT8GetDataRowCol(hIT8, row, col); | |
| if (Buffer == NULL) return 0.0; | |
| return ParseFloatNumber(Buffer); | |
| } | |
| cmsBool CMSEXPORT cmsIT8SetDataRowCol(cmsHANDLE hIT8, int row, int col, const char* Val) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| _cmsAssert(hIT8 != NULL); | |
| return SetData(it8, row, col, Val); | |
| } | |
| cmsBool CMSEXPORT cmsIT8SetDataRowColDbl(cmsHANDLE hIT8, int row, int col, cmsFloat64Number Val) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| char Buff[256]; | |
| _cmsAssert(hIT8 != NULL); | |
| snprintf(Buff, 255, it8->DoubleFormatter, Val); | |
| return SetData(it8, row, col, Buff); | |
| } | |
| const char* CMSEXPORT cmsIT8GetData(cmsHANDLE hIT8, const char* cPatch, const char* cSample) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| int iField, iSet; | |
| _cmsAssert(hIT8 != NULL); | |
| iField = LocateSample(it8, cSample); | |
| if (iField < 0) { | |
| return NULL; | |
| } | |
| iSet = LocatePatch(it8, cPatch); | |
| if (iSet < 0) { | |
| return NULL; | |
| } | |
| return GetData(it8, iSet, iField); | |
| } | |
| cmsFloat64Number CMSEXPORT cmsIT8GetDataDbl(cmsHANDLE it8, const char* cPatch, const char* cSample) | |
| { | |
| const char* Buffer; | |
| Buffer = cmsIT8GetData(it8, cPatch, cSample); | |
| return ParseFloatNumber(Buffer); | |
| } | |
| cmsBool CMSEXPORT cmsIT8SetData(cmsHANDLE hIT8, const char* cPatch, const char* cSample, const char *Val) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| int iField, iSet; | |
| TABLE* t; | |
| _cmsAssert(hIT8 != NULL); | |
| t = GetTable(it8); | |
| iField = LocateSample(it8, cSample); | |
| if (iField < 0) | |
| return FALSE; | |
| if (t-> nPatches == 0) { | |
| if (!AllocateDataFormat(it8)) | |
| return FALSE; | |
| if (!AllocateDataSet(it8)) | |
| return FALSE; | |
| CookPointers(it8); | |
| } | |
| if (cmsstrcasecmp(cSample, "SAMPLE_ID") == 0) { | |
| iSet = LocateEmptyPatch(it8); | |
| if (iSet < 0) { | |
| return SynError(it8, "Couldn't add more patches '%s'\n", cPatch); | |
| } | |
| iField = t -> SampleID; | |
| } | |
| else { | |
| iSet = LocatePatch(it8, cPatch); | |
| if (iSet < 0) { | |
| return FALSE; | |
| } | |
| } | |
| return SetData(it8, iSet, iField, Val); | |
| } | |
| cmsBool CMSEXPORT cmsIT8SetDataDbl(cmsHANDLE hIT8, const char* cPatch, | |
| const char* cSample, | |
| cmsFloat64Number Val) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| char Buff[256]; | |
| _cmsAssert(hIT8 != NULL); | |
| snprintf(Buff, 255, it8->DoubleFormatter, Val); | |
| return cmsIT8SetData(hIT8, cPatch, cSample, Buff); | |
| } | |
| // Buffer should get MAXSTR at least | |
| const char* CMSEXPORT cmsIT8GetPatchName(cmsHANDLE hIT8, int nPatch, char* buffer) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| TABLE* t; | |
| char* Data; | |
| _cmsAssert(hIT8 != NULL); | |
| t = GetTable(it8); | |
| Data = GetData(it8, nPatch, t->SampleID); | |
| if (!Data) return NULL; | |
| if (!buffer) return Data; | |
| strncpy(buffer, Data, MAXSTR-1); | |
| buffer[MAXSTR-1] = 0; | |
| return buffer; | |
| } | |
| int CMSEXPORT cmsIT8GetPatchByName(cmsHANDLE hIT8, const char *cPatch) | |
| { | |
| _cmsAssert(hIT8 != NULL); | |
| return LocatePatch((cmsIT8*)hIT8, cPatch); | |
| } | |
| cmsUInt32Number CMSEXPORT cmsIT8TableCount(cmsHANDLE hIT8) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| _cmsAssert(hIT8 != NULL); | |
| return it8 ->TablesCount; | |
| } | |
| // This handles the "LABEL" extension. | |
| // Label, nTable, Type | |
| int CMSEXPORT cmsIT8SetTableByLabel(cmsHANDLE hIT8, const char* cSet, const char* cField, const char* ExpectedType) | |
| { | |
| const char* cLabelFld; | |
| char Type[256], Label[256]; | |
| cmsUInt32Number nTable; | |
| _cmsAssert(hIT8 != NULL); | |
| if (cField != NULL && *cField == 0) | |
| cField = "LABEL"; | |
| if (cField == NULL) | |
| cField = "LABEL"; | |
| cLabelFld = cmsIT8GetData(hIT8, cSet, cField); | |
| if (!cLabelFld) return -1; | |
| if (sscanf(cLabelFld, "%255s %u %255s", Label, &nTable, Type) != 3) | |
| return -1; | |
| if (ExpectedType != NULL && *ExpectedType == 0) | |
| ExpectedType = NULL; | |
| if (ExpectedType) { | |
| if (cmsstrcasecmp(Type, ExpectedType) != 0) return -1; | |
| } | |
| return cmsIT8SetTable(hIT8, nTable); | |
| } | |
| cmsBool CMSEXPORT cmsIT8SetIndexColumn(cmsHANDLE hIT8, const char* cSample) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| int pos; | |
| _cmsAssert(hIT8 != NULL); | |
| pos = LocateSample(it8, cSample); | |
| if(pos == -1) | |
| return FALSE; | |
| it8->Tab[it8->nTable].SampleID = pos; | |
| return TRUE; | |
| } | |
| void CMSEXPORT cmsIT8DefineDblFormat(cmsHANDLE hIT8, const char* Formatter) | |
| { | |
| cmsIT8* it8 = (cmsIT8*) hIT8; | |
| _cmsAssert(hIT8 != NULL); | |
| if (Formatter == NULL) | |
| strcpy(it8->DoubleFormatter, DEFAULT_DBL_FORMAT); | |
| else | |
| strncpy(it8->DoubleFormatter, Formatter, sizeof(it8->DoubleFormatter)); | |
| it8 ->DoubleFormatter[sizeof(it8 ->DoubleFormatter)-1] = 0; | |
| } | |
| static | |
| cmsBool ReadNumbers(cmsIT8* cube, int n, cmsFloat64Number* arr) | |
| { | |
| int i; | |
| for (i = 0; i < n; i++) { | |
| if (cube->sy == SINUM) | |
| arr[i] = cube->inum; | |
| else | |
| if (cube->sy == SDNUM) | |
| arr[i] = cube->dnum; | |
| else | |
| return SynError(cube, "Number expected"); | |
| InSymbol(cube); | |
| } | |
| return CheckEOLN(cube); | |
| } | |
| static | |
| cmsBool ParseCube(cmsIT8* cube, cmsStage** Shaper, cmsStage** CLUT, char title[]) | |
| { | |
| cmsFloat64Number domain_min[3] = { 0, 0, 0 }; | |
| cmsFloat64Number domain_max[3] = { 1.0, 1.0, 1.0 }; | |
| cmsFloat64Number check_0_1[2] = { 0, 1.0 }; | |
| int shaper_size = 0; | |
| int lut_size = 0; | |
| int i; | |
| InSymbol(cube); | |
| while (cube->sy != SEOF && cube->sy != SSYNERROR) { | |
| switch (cube->sy) | |
| { | |
| // Set profile description | |
| case STITLE: | |
| InSymbol(cube); | |
| if (!Check(cube, SSTRING, "Title string expected")) return FALSE; | |
| memcpy(title, StringPtr(cube->str), MAXSTR); | |
| title[MAXSTR - 1] = 0; | |
| InSymbol(cube); | |
| break; | |
| // Define domain | |
| case SDOMAIN_MIN: | |
| InSymbol(cube); | |
| if (!ReadNumbers(cube, 3, domain_min)) return FALSE; | |
| break; | |
| case SDOMAIN_MAX: | |
| InSymbol(cube); | |
| if (!ReadNumbers(cube, 3, domain_max)) return FALSE; | |
| break; | |
| // Define shaper | |
| case S_LUT1D_SIZE: | |
| InSymbol(cube); | |
| if (!Check(cube, SINUM, "Shaper size expected")) return FALSE; | |
| shaper_size = cube->inum; | |
| InSymbol(cube); | |
| break; | |
| // Deefine CLUT | |
| case S_LUT3D_SIZE: | |
| InSymbol(cube); | |
| if (!Check(cube, SINUM, "LUT size expected")) return FALSE; | |
| lut_size = cube->inum; | |
| InSymbol(cube); | |
| break; | |
| // Range. If present, has to be 0..1.0 | |
| case S_LUT1D_INPUT_RANGE: | |
| case S_LUT3D_INPUT_RANGE: | |
| InSymbol(cube); | |
| if (!ReadNumbers(cube, 2, check_0_1)) return FALSE; | |
| if (check_0_1[0] != 0 || check_0_1[1] != 1.0) { | |
| return SynError(cube, "Unsupported format"); | |
| } | |
| break; | |
| case SEOLN: | |
| InSymbol(cube); | |
| break; | |
| default: | |
| case S_LUT_IN_VIDEO_RANGE: | |
| case S_LUT_OUT_VIDEO_RANGE: | |
| return SynError(cube, "Unsupported format"); | |
| // Read and create tables | |
| case SINUM: | |
| case SDNUM: | |
| if (shaper_size > 0) { | |
| cmsToneCurve* curves[3]; | |
| cmsFloat32Number* shapers = (cmsFloat32Number*)_cmsMalloc(cube->ContextID, 3 * shaper_size * sizeof(cmsFloat32Number)); | |
| if (shapers == NULL) return FALSE; | |
| for (i = 0; i < shaper_size; i++) { | |
| cmsFloat64Number nums[3]; | |
| if (!ReadNumbers(cube, 3, nums)) return FALSE; | |
| shapers[i + 0] = (cmsFloat32Number) ((nums[0] - domain_min[0]) / (domain_max[0] - domain_min[0])); | |
| shapers[i + 1 * shaper_size] = (cmsFloat32Number) ((nums[1] - domain_min[1]) / (domain_max[1] - domain_min[1])); | |
| shapers[i + 2 * shaper_size] = (cmsFloat32Number) ((nums[2] - domain_min[2]) / (domain_max[2] - domain_min[2])); | |
| } | |
| for (i = 0; i < 3; i++) { | |
| curves[i] = cmsBuildTabulatedToneCurveFloat(cube->ContextID, shaper_size, | |
| &shapers[i * shaper_size]); | |
| if (curves[i] == NULL) return FALSE; | |
| } | |
| *Shaper = cmsStageAllocToneCurves(cube->ContextID, 3, curves); | |
| cmsFreeToneCurveTriple(curves); | |
| } | |
| if (lut_size > 0) { | |
| int nodes = lut_size * lut_size * lut_size; | |
| cmsFloat32Number* lut_table = _cmsMalloc(cube->ContextID, nodes * 3 * sizeof(cmsFloat32Number)); | |
| if (lut_table == NULL) return FALSE; | |
| for (i = 0; i < nodes; i++) { | |
| cmsFloat64Number nums[3]; | |
| if (!ReadNumbers(cube, 3, nums)) return FALSE; | |
| lut_table[i * 3 + 2] = (cmsFloat32Number) ((nums[0] - domain_min[0]) / (domain_max[0] - domain_min[0])); | |
| lut_table[i * 3 + 1] = (cmsFloat32Number) ((nums[1] - domain_min[1]) / (domain_max[1] - domain_min[1])); | |
| lut_table[i * 3 + 0] = (cmsFloat32Number) ((nums[2] - domain_min[2]) / (domain_max[2] - domain_min[2])); | |
| } | |
| *CLUT = cmsStageAllocCLutFloat(cube->ContextID, lut_size, 3, 3, lut_table); | |
| _cmsFree(cube->ContextID, lut_table); | |
| } | |
| if (!Check(cube, SEOF, "Extra symbols found in file")) return FALSE; | |
| } | |
| } | |
| return TRUE; | |
| } | |
| // Share the parser to read .cube format and create RGB devicelink profiles | |
| cmsHPROFILE CMSEXPORT cmsCreateDeviceLinkFromCubeFileTHR(cmsContext ContextID, const char* cFileName) | |
| { | |
| cmsHPROFILE hProfile = NULL; | |
| cmsIT8* cube = NULL; | |
| cmsPipeline* Pipeline = NULL; | |
| cmsStage* CLUT = NULL; | |
| cmsStage* Shaper = NULL; | |
| cmsMLU* DescriptionMLU = NULL; | |
| char title[MAXSTR]; | |
| _cmsAssert(cFileName != NULL); | |
| cube = (cmsIT8*) cmsIT8Alloc(ContextID); | |
| if (!cube) return NULL; | |
| cube->IsCUBE = TRUE; | |
| cube->FileStack[0]->Stream = fopen(cFileName, "rt"); | |
| if (!cube->FileStack[0]->Stream) goto Done; | |
| strncpy(cube->FileStack[0]->FileName, cFileName, cmsMAX_PATH - 1); | |
| cube->FileStack[0]->FileName[cmsMAX_PATH - 1] = 0; | |
| if (!ParseCube(cube, &Shaper, &CLUT, title)) goto Done; | |
| // Success on parsing, let's create the profile | |
| hProfile = cmsCreateProfilePlaceholder(ContextID); | |
| if (!hProfile) goto Done; | |
| cmsSetProfileVersion(hProfile, 4.4); | |
| cmsSetDeviceClass(hProfile, cmsSigLinkClass); | |
| cmsSetColorSpace(hProfile, cmsSigRgbData); | |
| cmsSetPCS(hProfile, cmsSigRgbData); | |
| cmsSetHeaderRenderingIntent(hProfile, INTENT_PERCEPTUAL); | |
| // Creates a Pipeline to hold CLUT and shaper | |
| Pipeline = cmsPipelineAlloc(ContextID, 3, 3); | |
| if (Pipeline == NULL) goto Done; | |
| // Populates the pipeline | |
| if (Shaper != NULL) { | |
| if (!cmsPipelineInsertStage(Pipeline, cmsAT_BEGIN, Shaper)) | |
| goto Done; | |
| } | |
| if (CLUT != NULL) { | |
| if (!cmsPipelineInsertStage(Pipeline, cmsAT_END, CLUT)) | |
| goto Done; | |
| } | |
| // Propagate the description. We put no copyright because we know | |
| // nothing on the copyrighted state of the .cube | |
| DescriptionMLU = cmsMLUalloc(ContextID, 1); | |
| if (!cmsMLUsetUTF8(DescriptionMLU, cmsNoLanguage, cmsNoCountry, title)) goto Done; | |
| // Flush the tags | |
| if (!cmsWriteTag(hProfile, cmsSigProfileDescriptionTag, DescriptionMLU)) goto Done; | |
| if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, (void*)Pipeline)) goto Done; | |
| Done: | |
| if (DescriptionMLU != NULL) | |
| cmsMLUfree(DescriptionMLU); | |
| if (Pipeline != NULL) | |
| cmsPipelineFree(Pipeline); | |
| cmsIT8Free((cmsHANDLE) cube); | |
| return hProfile; | |
| } | |
| cmsHPROFILE CMSEXPORT cmsCreateDeviceLinkFromCubeFile(const char* cFileName) | |
| { | |
| return cmsCreateDeviceLinkFromCubeFileTHR(NULL, cFileName); | |
| } |