// $Id: kerneldc.cpp 1432 2006-07-11 15:46:55Z alex $ /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE ================================XARAHEADERSTART=========================== Xara LX, a vector drawing and manipulation program. Copyright (C) 1993-2006 Xara Group Ltd. Copyright on certain contributions may be held in joint with their respective authors. See AUTHORS file for details. LICENSE TO USE AND MODIFY SOFTWARE ---------------------------------- This file is part of Xara LX. Xara LX is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. Xara LX and its component source files are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Xara LX (see the file GPL in the root directory of the distribution); if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ADDITIONAL RIGHTS ----------------- Conditional upon your continuing compliance with the GNU General Public License described above, Xara Group Ltd grants to you certain additional rights. The additional rights are to use, modify, and distribute the software together with the wxWidgets library, the wxXtra library, and the "CDraw" library and any other such library that any version of Xara LX relased by Xara Group Ltd requires in order to compile and execute, including the static linking of that library to XaraLX. In the case of the "CDraw" library, you may satisfy obligation under the GNU General Public License to provide source code by providing a binary copy of the library concerned and a copy of the license accompanying it. Nothing in this section restricts any of the rights you have under the GNU General Public License. SCOPE OF LICENSE ---------------- This license applies to this program (XaraLX) and its constituent source files only, and does not necessarily apply to other Xara products which may in part share the same code base, and are subject to their own licensing terms. This license does not apply to files in the wxXtra directory, which are built into a separate library, and are subject to the wxWindows license contained within that directory in the file "WXXTRA-LICENSE". This license does not apply to the binary libraries (if any) within the "libs" directory, which are subject to a separate license contained within that directory in the file "LIBS-LICENSE". ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS ---------------------------------------------- Subject to the terms of the GNU Public License (see above), you are free to do whatever you like with your modifications. However, you may (at your option) wish contribute them to Xara's source tree. You can find details of how to do this at: http://www.xaraxtreme.org/developers/ Prior to contributing your modifications, you will need to complete our contributor agreement. This can be found at: http://www.xaraxtreme.org/developers/contribute/ Please note that Xara will not accept modifications which modify any of the text between the start and end of this header (marked XARAHEADERSTART and XARAHEADEREND). MARKS ----- Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara designs are registered or unregistered trademarks, design-marks, and/or service marks of Xara Group Ltd. All rights in these marks are reserved. Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. http://www.xara.com/ =================================XARAHEADEREND============================ */ // Device Contexts that are kernel-safe. /* */ #include "camtypes.h" #include "kerneldc.h" #include "colourix.h" //#include "fixmem.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "epsfiltr.h" - in camtypes.h [AUTOMATICALLY REMOVED] DECLARE_SOURCE("$Revision: 1432 $"); #define new CAM_DEBUG_NEW CC_IMPLEMENT_DYNAMIC(KernelDC, CCDummyDC); /******************************************************************************************** > KernelDC::KernelDC(RenderType rType) Author: Tim_Browse (Xara Group Ltd) Created: 23/04/95 Inputs: The type of DC being made. Purpose: Initialise a kernel DC. This initialises the user space origin to (0,0) by default (only relevant for DCs that use the PostScript stream functions). SeeAlso: KernelDC::SetOrigin; KernelDC::OutputUserSpaceValue; KernelDC::OutputCoord ********************************************************************************************/ KernelDC::KernelDC(RenderType rType) : CCDummyDC(rType) { // Initialise other fields. LineWidth = 0; Origin.x = 0; Origin.y = 0; // Default to 2dp accuracy for user space values. FullAccuracy = FALSE; // No ASCII85 conversion by default. RawBuf = NULL; A85Buf = NULL; RawBufSize = 0; RLEtheASCII85Data = FALSE; } /******************************************************************************************** > KernelDC::KernelDC(CDC *pDC, RenderType rType) Author: Tim_Browse (Xara Group Ltd) Created: 23/04/95 Inputs: The OIL DC pointer, and the type of DC being made. Purpose: Initialise a kernel DC. This initialises the user space origin to (0,0) by default (only relevant for DCs that use the PostScript stream functions). SeeAlso: KernelDC::SetOrigin; KernelDC::OutputUserSpaceValue; KernelDC::OutputCoord ********************************************************************************************/ KernelDC::KernelDC(CNativeDC *pDC, RenderType rType) : CCDummyDC(pDC, rType) { // Initialise other fields. LineWidth = 0; Origin.x = 0; Origin.y = 0; // Default to 2dp accuracy for user space values. FullAccuracy = FALSE; // No ASCII85 conversion by default. RawBuf = NULL; A85Buf = NULL; RawBufSize = 0; RLEtheASCII85Data = FALSE; } /******************************************************************************************** > void KernelDC::SetOrigin(DocCoord &NewOrigin) Author: Tim_Browse (Xara Group Ltd) Created: 28/03/94 Inputs: NewOrigin - the desired origin (in spread coords) Purpose: Set the user space origin to the specified position in spread coordinates. SeeAlso: KernelDC::OutputUserSpaceValue; KernelDC::OutputCoord ********************************************************************************************/ void KernelDC::SetOrigin(DocCoord &NewOrigin) { Origin = NewOrigin; } /******************************************************************************************** > BOOL KernelDC::OutputCoord(DocCoord& Coord, EPSAccuracy Accuracy = ACCURACY_NORMAL) Author: Tim_Browse (Xara Group Ltd) Created: 28/03/94 Inputs: Coord - the coordinate to write out to the file. Accuracy - optional parameter - specifies how numbers should be output: ACCURACY_NORMAL: 2dp ACCURACY_ROUNDUP: 0dp, rounded up ACCURACY_ROUNDDOWN: 0dp, rounded down. Returns: TRUE if the data was written ok; FALSE if not => ERROR1 Purpose: Write out a coordinate x,y pair to the export file, automatically converting from spread coordinates to the EPS user space coordinate system, including adjusting the origin. SeeAlso: KernelDC::OutputUserSpaceValue Errors: File/disk error => ERROR1 ********************************************************************************************/ BOOL KernelDC::OutputCoord(DocCoord& Coord, EPSAccuracy Accuracy) { BOOL Ok = (OutputUserSpaceValue(Coord.x - Origin.x, Accuracy) && OutputUserSpaceValue(Coord.y - Origin.y, Accuracy)); return Ok; } /******************************************************************************************** > BOOL KernelDC::OutputUserSpaceValue(MILLIPOINT n, EPSAccuracy Accuracy = ACCURACY_NORMAL) Author: Tim_Browse (Xara Group Ltd) Created: 28/03/94 Inputs: n - the value (in millipoints) to output. Accuracy - optional parameter - specifies how numbers should be output: ACCURACY_NORMAL: 2dp, or 3dp (see KernelDC::SetFullAccuracy) ACCURACY_ROUNDUP: 0dp, rounded up ACCURACY_ROUNDDOWN: 0dp, rounded down. Returns: TRUE if the data was written ok; FALSE if not => ERROR1 Purpose: Output a 'user space' value to the export file. User space is the coordinate system used for EPS files (and more generally, PostScript programs). For Illustrator-based EPS, the user space is expressed in units of points, and usually has the origin at (0.0). By default, we follow the Illustrator convention of truncating values to 2dp (so we can generate files compatible with Illustrator etc), but Camelot EPS uses the full 3dp accuracy (we don't store more any accuracy than this internally). The accuracy can be changed by calling KernelDC::SetFullAccuracy Note that this routine does not do origin translation, because it may be used for dimensions such as line widths, which have no earthly use for origins (and anyway, it's a bit tricky to do origin translation with only one dimension!). e.g. OutputUserSpaceValue(31465) will output "3.147" to the export file. SeeAlso: KernelDC::OutputCoord; KernelDC::OutputToken; KernelDC::SetFullAccuracy Errors: Disk/file error => ERROR1 ********************************************************************************************/ BOOL KernelDC::OutputUserSpaceValue(MILLIPOINT n, EPSAccuracy Accuracy) { TCHAR Buf[20]; INT32 Integer; INT32 Fraction; const TCHAR * negative=_T("-"); const TCHAR * positive=_T(""); const TCHAR * sign = positive; // Convert millipoint to EPS user space value in desired format. switch (Accuracy) { case ACCURACY_NORMAL: // Convert to points, getting integer and fractional parts // invert the sign if necessary if (n<0) { sign=negative; n=-n; } // as n is positive (or zero), this works in the way expected. Both // Integer and fraction are >=0 too. Integer = n / 1000; Fraction = n % 1000; // Output to string (accurate to 2/3dp) // If fraction is 0, just output integer value. if (Fraction == 0) { camSprintf(Buf, _T("%d"), Integer); } else if (FullAccuracy) { camSprintf(Buf, _T("%s%d.%.3d"), sign, Integer, Fraction); } else { // Normal 2dp accuracy Fraction=(Fraction+5)/10; camSprintf(Buf, _T("%s%d.%.2d"), sign, Integer, Fraction); } break; case ACCURACY_ROUNDDOWN: // Convert to points, getting integer part. if (n < 0) Integer = (n - 999)/ 1000; else Integer = (n) / 1000; // Output to string (accurate to 0dp) camSprintf(Buf, _T("%d"), Integer); break; case ACCURACY_ROUNDUP: // Convert to points, getting integer part. if (n < 0) Integer = (n) / 1000; else Integer = (n + 999) / 1000; // Output to string (accurate to 0dp) camSprintf(Buf, _T("%d"), Integer); break; } return OutputToken(Buf); } /******************************************************************************************** > BOOL KernelDC::OutputColourValue(UINT32 n) Author: Tim_Browse (Xara Group Ltd) Created: 28/03/94 Inputs: n - the colour value (range 0-255) to write to the EPS file. Returns: TRUE if the data was written ok; FALSE if not => ERROR1 Purpose: Output a colour value to the export file. A 'colour value' is a value as used in the Camelot 'Colour' class, i.e. in the range 0 to 255. This range is converted to the range 0.0 to 1.0, and output to the file. SeeAlso: KernelDC::OutputColour Errors: Disk/file error => ERROR1 ********************************************************************************************/ BOOL KernelDC::OutputColourValue(UINT32 n) { // Convert to points, getting integer and fractional parts INT32 Integer = n / 255; INT32 Fraction = ((n % 255) * 100) / 255; // Output to string TCHAR Buf[20]; // Ensure we always get at least 2 decimal figures with %.2d camSprintf(Buf, _T("%d.%.2d"), Integer, Abs(Fraction)); return OutputToken(Buf); } /******************************************************************************************** > BOOL KernelDC::OutputColour(PColourCMYK *pCol) Author: Tim_Browse (Xara Group Ltd) Created: 28/03/94 Inputs: Col - the CMYK colour values to be output. Returns: TRUE if the data was written ok; FALSE if not => ERROR1 Purpose: Output a colour, as expressed by the Camelot 'Colour' class. This function takes the CMYK variation, is this is most appropriate to EPS files. Camelot colour values are converted from the 0-255 range to the 0.0-1.0 range before being output. e.g. MonoOn PColourCMYK Col = { 255, 255, 128, 0 }; pDC->OutputColour(&Col); MonoOff will give the following output: MonoOn 1.0 1.0 0.5 0.0 MonoOff SeeAlso: KernelDC::OutputColourValue Errors: Disk/file error => ERROR1 ********************************************************************************************/ BOOL KernelDC::OutputColour(PColourCMYK *pCol) { // Output each of the colour values. BOOL Ok = (OutputColourValue(pCol->Cyan) && OutputColourValue(pCol->Magenta) && OutputColourValue(pCol->Yellow) && OutputColourValue(pCol->Key)); // Return success or failure. return Ok; } /******************************************************************************************** > BOOL KernelDC::OutputNamedColour(DocColour *pCol, ColourContext* pContext = NULL) Author: Tim_Browse (Xara Group Ltd) Created: 09/08/94 Inputs: pCol - the colour to output. pContext - the context to use to convert the colour before output. if this is NULL the default CMYK context will be used. Returns: TRUE if the data was written ok; FALSE if not => ERROR1 Purpose: Similar to OutputColour(), except it outputs the colour name and tint of the colour as well. If pCol does not reference an indexed colour, then the name "NoName" is used. SeeAlso: KernelDC::OutputColour Errors: Disk/file error => ERROR1 ********************************************************************************************/ BOOL KernelDC::OutputNamedColour(DocColour *pCol, ColourContext* pContext) { // Get CMYK version of this colour. PColourCMYK CMYK; pCol->GetCMYKValue(pContext, &CMYK); // Output CMYK version if (!OutputColour(&CMYK)) return FALSE; // Get the indexed colour from the DocColour. IndexedColour *pIndCol = pCol->FindParentIndexedColour(); // Cope with the unexpected! // ENSURE(pIndCol != NULL, "Named colour has no index colour!"); if (pIndCol == NULL) { if (pCol->IsTransparent()) { // This is a 'no colour' type colour, so output a zero-length colour name, // as this is the only way we can handle this at the moment. if (!OutputString(_T(""))) return FALSE; } else { // Otherwise make up a colour name (see epsfiltr.h). if (!OutputString(ImmediateColourFudgeyBodgeName)) return FALSE; } } else { // Got an indexed colour - output its name // (Pass in TRUE to get a unique-identifier for local colours rather than "Local colour") String_64 *ColName = pIndCol->GetName(TRUE); if (!OutputString((TCHAR *) (*ColName))) return FALSE; } // Always tint 0 return OutputToken(_T("0")); } /******************************************************************************************** > BOOL KernelDC::OutputColourName(DocColour *pCol) Author: Chris_Gallimore (Xara Group Ltd) Created: 29/11/00 Inputs: pCol - the colour to output. Returns: TRUE if the data was written ok; FALSE if not => ERROR1 Purpose: Outputs a named colour's name as a string. If pCol does not reference an indexed colour, then the name "NoName" is used. This was seperated from OutputNamedColour so that the DeviceContext will not need to worry about which ColourModel to use (CMYK, RGB, etc...), as this will be determined by the RenderRegion. SeeAlso: KernelDC::OutputNamedColour Errors: Disk/file error => ERROR1 ********************************************************************************************/ BOOL KernelDC::OutputColourName(DocColour *pCol) { // Success / Failure flag for the export of this colour's name. BOOL ok = TRUE; // Get the indexed colour from the DocColour. IndexedColour *pIndCol = pCol->FindParentIndexedColour(); if (pIndCol == NULL) { if (pCol->IsTransparent()) { // This is a 'no colour' type colour, so output a zero-length colour name, // as this is the only way we can handle this at the moment. ok = OutputString(_T("")); } else { // Otherwise make up a colour name (see epsfiltr.h). ok = OutputString(ImmediateColourFudgeyBodgeName); } } else { // Got an indexed colour - output its name // (Pass in TRUE to get a unique-identifier for local colours rather than "Local colour") String_64 *ColName = pIndCol->GetName(TRUE); ok = OutputString((TCHAR *) (*ColName)); } return ok; } /******************************************************************************************** > BOOL KernelDC::OutputString(TCHAR *pString) Author: Tim_Browse (Xara Group Ltd) Created: 10/08/94 Inputs: pString - the string token to output. Returns: TRUE if the data was written ok; FALSE if not => ERROR1 Purpose: Output a string in PostScript format, i.e. delimited by '(' and ')', and escaping any parentheses so the string is syntactically correct. e.g. "String" is output as "(String)", and "Hello (there)" is output as "(Hello \(there\))" and so on. SeeAlso: KernelDC Errors: Disk/file error => ERROR1 ********************************************************************************************/ BOOL KernelDC::OutputString(TCHAR *pString) { TCHAR Buf[128]; // Open string delimiter Buf[0] = '('; // Copy the string, looking for embedded delimiters. INT32 src = 0, dst = 1; while ( (pString[src]) && (dst < 120 ) ) //while ((pString[src] != 0) && (dst < 120))-adapted for DBCS { if( (pString[src]== _T('(')) || (pString[src]==_T(')')) || (pString[src]== _T('\\')) ) //if( (pString[src] == '(') || (pString[src] == ')') || (pString[src] == '\\') )-adapted for DBCS { // escape this delimiter Buf[dst++] = '\\'; } // Copy this character Buf[dst++] = pString[src++]; } ERROR3IF(dst > 120, "String too long in KernelDC::OutputString"); // Terminate the string token Buf[dst++] = ')'; Buf[dst] = 0; // Output it return OutputToken(Buf); } /******************************************************************************************** > BOOL KernelDC::OutputMatrix(Matrix * Mat) Author: Chris_Snook (Xara Group Ltd) Created: 22/08/94 Inputs: M- pointer to the matrix to output to the EPS stream. Returns: TRUE if the data was written ok; FALSE if not => ERROR1 Purpose: Output a Matrix to the EPS stream. Errors: Disk/file error => ERROR1 ********************************************************************************************/ BOOL KernelDC::OutputMatrix(Matrix * M) { FIXED16 abcd[4]; INT32 ef[2]; M->GetComponents(abcd, ef); if(!OutputReal(abcd[0].MakeDouble())) return FALSE; if(!OutputReal(abcd[1].MakeDouble())) return FALSE; if(!OutputReal(abcd[2].MakeDouble())) return FALSE; if(!OutputReal(abcd[3].MakeDouble())) return FALSE; if(!OutputUserSpaceValue(ef[0])) return FALSE; if(!OutputUserSpaceValue(ef[1])) return FALSE; return TRUE; } /******************************************************************************************** > BOOL KernelDC::OutputValue(INT32 Value) Author: Tim_Browse (Xara Group Ltd) Created: 22/08/94 Inputs: Value - the number to write to the EPS stream. Returns: TRUE if the data was written ok; FALSE if not => ERROR1 Purpose: Output a signed integer to the EPS stream. Errors: Disk/file error => ERROR1 ********************************************************************************************/ BOOL KernelDC::OutputValue(INT32 Value) { TCHAR buf[30]; camSprintf(buf, _T("%d"), Value); return OutputToken(buf); } /******************************************************************************************** > BOOL KernelDC::OutputValue(UINT32 Value) Author: Tim_Browse (Xara Group Ltd) Created: 22/08/94 Inputs: Value - the number to write to the EPS stream. Returns: TRUE if the data was written ok; FALSE if not => ERROR1 Purpose: Output an unsigned integer to the EPS stream. Errors: Disk/file error => ERROR1 ********************************************************************************************/ BOOL KernelDC::OutputValue(UINT32 Value) { TCHAR buf[30]; camSprintf(buf, _T("%u"), Value); return OutputToken(buf); } /******************************************************************************************** > BOOL KernelDC::OutputReal(double Value) Author: Tim_Browse (Xara Group Ltd) Created: 07/11/94 Inputs: Value - the number to write to the EPS stream. Returns: TRUE if the data was written ok; FALSE if not => ERROR1 Purpose: Output a real (floating point) number to the EPS stream. Errors: Disk/file error => ERROR1 ********************************************************************************************/ BOOL KernelDC::OutputReal(double Value) { TCHAR buf[100]; // 100 should be enough for a floating point number! // Save floating point in scientific notation (because otherwise the number output // could be very long, e.g. 6.2e66 or similar would be very long if we didn't use // the exponent syntax). camSprintf(buf, _T("%g"), Value); return OutputToken(buf); } /******************************************************************************************** > BOOL KernelDC::OutputFloat(const double Value, const UINT32 DecPl) Author: Mike_Kenny (Xara Group Ltd) Created: 20/04/95 Inputs: Value - the number to write to the EPS stream. DecPl - the number of decimal places to output. (1..8) Returns: TRUE if the data was written ok; FALSE if not => ERROR1 Purpose: Output a real (floating point) number to the EPS stream. Errors: Disk/file error => ERROR1 ********************************************************************************************/ BOOL KernelDC::OutputFloat(const double Value, const UINT32 DecPl) { TCHAR buf[100]; TCHAR *pch=buf; INT32 len=0, dp=-1; switch (DecPl) { case 1: camSprintf(buf,_T("%.1f"),Value); break; case 2: camSprintf(buf,_T("%.2f"),Value); break; case 4: camSprintf(buf,_T("%.4f"),Value); break; case 5: camSprintf(buf,_T("%.5f"),Value); break; case 6: camSprintf(buf,_T("%.6f"),Value); break; case 7: camSprintf(buf,_T("%.7f"),Value); break; case 8: camSprintf(buf,_T("%.8f"),Value); break; default:camSprintf(buf,_T("%.3f"),Value); break; } // supress a few trailing zero's while (*pch != '\0') { if (*pch=='.') dp=len; len++; pch++; } // check no decimal place if (dp==-1) return OutputToken(buf); pch--; while ((len>0) && (*pch=='0')) { pch--; len--; } if ((len==0) || (*pch=='.')) pch++; pch++; *pch='\0'; return OutputToken(buf); } /******************************************************************************************** > BOOL KernelDC::OutputArray(INT32* Array, INT32 ArraySize) Author: Will_Cowling (Xara Group Ltd) Created: 15/04/95 Inputs: Array, pointer to the array data. ArraySize, the number of elements in the array. Returns: TRUE if the data was written ok; FALSE if not => ERROR1 Purpose: Output an Array to the EPS stream. Errors: Disk/file error => ERROR1 ********************************************************************************************/ BOOL KernelDC::OutputArray(INT32* Array, INT32 ArraySize) { // Output the 'ArrayStart' if (!OutputToken(_T("["))) return FALSE; // Output the actual array elements for (INT32 el = 0; el < ArraySize; el++) { if(!OutputUserSpaceValue(Array[el])) return FALSE; } // Output the 'ArrayEnd' if (!OutputToken(_T("]"))) return FALSE; return TRUE; } /******************************************************************************************** > INT32 KernelDC::OutputRawBinary(BYTE *Data, UINT32 Length, UINT32 Alignment = 1) Author: Tim_Browse (Xara Group Ltd) Created: 22/08/94 Inputs: Data - pointer to the data to output. Length - the number of bytes to output. Alignment - the byte alignment to use - it will pad the end of the data with 0 bytes if Length is not divisible by this parameter. Hence use 1 for no padding. Returns: The number of bytes output to the file, in terms of the source data, not in terms of the number of ASCII characters used to represent them. -1 => error occured while writing => ERROR1 Purpose: Outputs a sequence of bytes as raw hex data, as used by PostScript operators such as readhexstring. Errors: Disk/file error => ERROR1 ********************************************************************************************/ INT32 KernelDC::OutputRawBinary(BYTE *Data, UINT32 Length, UINT32 Alignment) { INT32 nBytes = 0; // Do 30 bytes at a time so we get lines with 60 characters on each. if (LineWidth > 0) { if (!OutputNewLine()) // Error encountered return -1; } // Work out the padding needed ENSURE(Alignment != 0, "Bad alignment in OutputRawBinary!"); UINT32 Padding = Alignment - (Length % Alignment); if (Padding == Alignment) Padding = 0; while (Length > 0) { // Work out how much data to output UINT32 ChunkLength = 32; if (Length < ChunkLength) ChunkLength = Length; // Convert the next chunk to hex and write it out TCHAR HexBuf[80]; ConvertToHex(Data, ChunkLength, HexBuf); if (!OutputTCHARAsChar(HexBuf, ChunkLength * 2)) { // Error return -1; } nBytes += ChunkLength; // Adjust length and check for padding requirements Length -= ChunkLength; Data += ChunkLength; if ((Length == 0) && (Padding > 0)) { // Put the string "00" into HexBuf HexBuf[0] = '0'; HexBuf[1] = '0'; HexBuf[2] = 0; // Output it however many times we need to while (Padding > 0) { if (!OutputTCHARAsChar(HexBuf, 2)) // Error return -1; nBytes++; Padding--; } } if (!OutputNewLine()) // Error encountered return -1; } // All done return nBytes; } /******************************************************************************************** > void KernelDC::ConvertToHex(BYTE *Data, UINT32 Length, TCHAR *Buf) Author: Tim_Browse (Xara Group Ltd) Created: 22/08/94 Inputs: Data - pointer to the data to convert Length - the number of bytes to convert. Outputs: Buf - the buffer to place the hex string into. NB. must be big enough to hold the 0-terminator! Purpose: Convert a block of bytes to a hexadecimal ASCII stroing representation of the data. SeeAlso: KernelDC::OutputRawBinary ********************************************************************************************/ void KernelDC::ConvertToHex(BYTE *Data, UINT32 Length, TCHAR *Buf) { UINT32 DataOfs = 0, DestOfs = 0; while (DataOfs < Length) { // Extract each 4-bit set and encode as hex INT32 Nybble = (Data[DataOfs] & 0xF0) >> 4; ENSURE(Nybble < 16, "Bad hex digit in KernelDC::ConvertToHex"); TCHAR Ch; if (Nybble < 10) Ch = '0' + (TCHAR) Nybble; else Ch = 'A' + (TCHAR) (Nybble - 10); Buf[DestOfs++] = Ch; Nybble = Data[DataOfs] & 0xF; ENSURE(Nybble < 16, "Bad hex digit in KernelDC::ConvertToHex"); if (Nybble < 10) Ch = '0' + (TCHAR) Nybble; else Ch = 'A' + (TCHAR) (Nybble - 10); Buf[DestOfs++] = Ch; // Do the next byte... DataOfs++; } // Terminate the string Buf[DestOfs] = 0; } // By default, enough room to convert 100 quadruples at once. const static INT32 A85DataSize = 100; BOOL KernelDC::StartASCII85Output(BOOL RunLengthEncode) { // Sanity check ERROR2IF(A85Buf != NULL, FALSE, "Unfinished ASCII85 output!"); // Remember whether or not we need to RLE the data. RLEtheASCII85Data = RunLengthEncode; // Allocate buffers // (Jason here - NOTE that I add a 4 byte padding to the end of both buffers in case // of small overflow. Otherwise, the ConvertToASCII85 routine fills the buffer to the // brim and then slaps a 0 terminator on the end, corrupting the heap) RawBuf = (LPBYTE) CCMalloc((A85DataSize * 4) + 4); if (RawBuf == NULL) return(FALSE); A85Buf = (LPBYTE) CCMalloc((A85DataSize * 5) + 4); if (A85Buf == NULL) { CCFree(RawBuf); return(FALSE); } // No characters in buffer yet. RawBufSize = 0; // Ok return TRUE; } INT32 KernelDC::EndASCII85Output() { // Are we useing RLE compression? if (RLEtheASCII85Data) { // Ok - end of data, so we add the RLE EOD marker to the buffer (byte value 128) BYTE EOD[2]; EOD[0] = (BYTE) 128; if (!QueueASCII85Data(EOD, 1)) // An error occured return -1; } // Flush out the buffer, padding with zeroes if necessary. INT32 nBytes = FlushASCII85Buffer(); // End of ASCII85 data, so output the ASCII85 EOD marker. TCHAR EOD[] = _T("~>"); OutputTCHARAsChar(EOD, 2); OutputNewLine(); // Free up the buffers CCFree(RawBuf); CCFree(A85Buf); RawBuf = NULL; A85Buf = NULL; RawBufSize = 0; RLEtheASCII85Data = FALSE; // Tell caller how many byts we saved return nBytes; } /******************************************************************************************** > INT32 KernelDC::OutputASCII85(BYTE *Data, UINT32 Length) Author: Tim_Browse (Xara Group Ltd) Created: 3/7/95 Inputs: Data - pointer to the data to output. Length - the number of bytes to output. RunLengthEncode - if TRUE then run length encode the data before encoding it to ASCII85 format (see Red Book for RLE spec). Returns: The number of bytes output to the file, in terms of the source data, not in terms of the number of ASCII characters used to represent them. -1 => error occured while writing => ERROR1 Purpose: Outputs a sequence of bytes as ASCII85 hex data, as used by PostScript Level 2. (This encoding should not be used when generating PostScript that must be compatible with Level 1 interpreters). Errors: Disk/file error => ERROR1 ********************************************************************************************/ INT32 KernelDC::OutputASCII85(BYTE *Data, UINT32 Length) { // Sanity check ERROR2IF(RawBuf == NULL, FALSE, "ASCII85 output not initialised correctly!"); // Run length encode the data first if required by the caller. BYTE *pRLLBuf = NULL; if (RLEtheASCII85Data) { // Encode this data using run length encoding. INT32 NewLength = Length; pRLLBuf = KernelDC::RunLengthEncode(Data, &NewLength); if (pRLLBuf == NULL) // Error encoding data return -1; #ifdef _DEBUG // Debug check - let's just decompress that data to make sure it was encoded ok! INT32 DecodedLength = NewLength; BYTE *Decoded = RunLengthDecode(pRLLBuf, &DecodedLength); // Check for data equality BOOL ValidEncoding = (DecodedLength == (INT32) Length); if (ValidEncoding) // Perform byte-wise comparison of data ValidEncoding = (memcmp(Data, Decoded, Length) == 0); // De-allocate the decoding buffer CCFree(Decoded); ERROR2IF(!ValidEncoding, -1, "Error in RunLengthEncoding integrity check!"); #endif // Point ASCII85 routine to the RLL encoded data Data = pRLLBuf; Length = NewLength; } INT32 nBytes = -1; // Start this chunk on a new line. if (LineWidth > 0) { if (!OutputNewLine()) { // Error encountered if (RLEtheASCII85Data) CCFree(pRLLBuf); return -1; } } // Queue up the data (which may or may not be RLL encoded). if (QueueASCII85Data(Data, Length)) // Data queued successfully. nBytes = Length; // Free up the buffer used for run length encoding. if (RLEtheASCII85Data) CCFree(pRLLBuf); // All done return nBytes; } BOOL KernelDC::QueueASCII85Data(BYTE *Data, UINT32 Length) { INT32 MaxData = A85DataSize * 4; // Keep filling and flushing the buffer until we are out of data to send. INT32 nBytes = Length; while (nBytes > 0) { ERROR2IF((INT32) RawBufSize > MaxData, FALSE, "ASCII85 buffer overflow!"); if ((INT32) RawBufSize == MaxData) { // Buffer is full - encode it and write it to disk. if (FlushASCII85Buffer() == -1) return FALSE; } // Put as much data into the buffer as possible INT32 SpaceLeft = MaxData - RawBufSize; if (nBytes > SpaceLeft) { // Not enough room to put it all in, so fill up the buffer memcpy(RawBuf + RawBufSize, Data, SpaceLeft); RawBufSize += SpaceLeft; Data += SpaceLeft; nBytes -= SpaceLeft; } else { // Enough room in buffer to put all the data in, so do it. memcpy(RawBuf + RawBufSize, Data, nBytes); RawBufSize += nBytes; nBytes = 0; } } // All ok return TRUE; } INT32 KernelDC::ConvertToASCII85(BYTE *Src, UINT32 Length, BYTE *Dest) { // Precompute the powers of 85 to use when encoding. /* static const INT32 Power85[5] = { 1, 85, 85 * 85, 85 * 85 * 85, 85 * 85 * 85 * 85 }; */ // Start at the beginning of the buffer // INT32 SrcOfs = 0; INT32 DstOfs = 0; while (Length > 0) { // Encode four bytes as five characters. UINT32 n=0; if (Length >= 4) { // Normal 4 bytes of data n = Src[0]; n = (n << 8) | Src[1]; n = (n << 8) | Src[2]; n = (n << 8) | Src[3]; } else { // Pad with zeros... switch (Length) { case 3: n = Src[0]; n = (n << 8) | Src[1]; n = (n << 8) | Src[2]; n <<= 8; break; case 2: n = Src[0]; n = (n << 8) | Src[1]; n <<= 16; break; case 1: n = Src[0]; n <<= 24; break; } } if ((n == 0) && (Length >= 4)) { // Special case - use "z" instead of "!!!!!" Dest[DstOfs++] = 'z'; } else { #if 1 register UINT32 q = n/7225; register unsigned r = (unsigned)n-7225*(unsigned)q; register unsigned t; Dest[DstOfs+3] = (t = r/85) + '!'; Dest[DstOfs+4] = r - 85*t + '!'; Dest[DstOfs+0] = (t = q/7225) + '!'; r = (unsigned)q - 7225*t; Dest[DstOfs+1] = (t = r/85) + '!'; Dest[DstOfs+2] = r - 85*t + '!'; if (Length >= 4) DstOfs += 5; else // Special case for last 5-tuple - see Red Book, bottom of p129 DstOfs += (Length + 1); #else /* for (INT32 i = 4; i >= 0; i--) { // Reduce the number to a 5 digit base-85 number. UINT32 m = n / Power85[i]; n -= m * Power85[i]; Dest[DstOfs++] = (TCHAR) (m + 33); } */ #endif } // Move on to the next four bytes Src += 4; if (Length < 4) Length = 0; else Length -= 4; } // Finished encoding - terminate the string (not strictly necessary but what the hell). // (Jason here... What do you mean "What the hell?" - you've just written one byte off the // end of the buffer, and corrupted the heap! I've now added 4 bytes padding // on our buffers (in StartASCII85Output) to make sure this is safe) Dest[DstOfs] = 0; // Return how many bytes this ASCII85 data takes up return DstOfs; } INT32 KernelDC::ConvertFromASCII85(BYTE *Src, UINT32 Length, BYTE *Dest) { // Precompute the powers of 85 to use when encoding. static const INT32 Power85[5] = { 1, 85, 85 * 85, 85 * 85 * 85, 85 * 85 * 85 * 85 }; // Start at the beginning of the buffer // INT32 SrcOfs = 0; INT32 DstOfs = 0; BOOL FoundEOD = FALSE; while ((Length > 0) && (!FoundEOD)) { // Decode five characters into four bytes. UINT32 n = 0; UINT32 TupleLength = (Length >= 5) ? 5 : Length; // Look for the EOD marker for (UINT32 i = 0; i < 5; i++) { if ((Src[i] == '~') && ((i + 1) < Length) && (Src[i + 1] == '>')) { // Found the EOD marker in the data FoundEOD = TRUE; // Shorten the tuple TupleLength = i; } } if (Src[0] == 'z') { // Skip the 'z' character TupleLength = 1; } else { // Decode base-85 numbers. for (UINT32 i = 0; i < 5; i++) { if (i < TupleLength) n += ((Src[i] - '!') * Power85[4 - i]); else if (i == TupleLength) // Round up fractional part n += 0x80 << ((4 - i) * 8); // 'z' is not allowed within a tuple ERROR2IF(Src[i] == 'z', -1, "Illegal 'z' in ASCII85 stream"); } } // Find out how many bytes we just decoded. UINT32 nBytesDecoded = TupleLength - 1; // Copy the decoded data into the buffer. UINT32 Shift = 24; while (nBytesDecoded > 0) { Dest[DstOfs++] = (BYTE) ((n >> Shift) & 0xFF); nBytesDecoded--; Shift -= 8; } // Move on to the next tuple. Src += TupleLength; Length -= TupleLength; } // Check the lengths match ERROR2IF(Length != 0, -1, "Incorrect length while decoding ASCII85 data"); //ERROR2IF(!FoundEOD, -1, "No EOD found while decoding ASCII85 data"); // Finished encoding - terminate the string (not strictly necessary but what the hell). Dest[DstOfs] = 0; // Return how many bytes this decoded ASCII85 data takes up return DstOfs; } INT32 KernelDC::FlushASCII85Buffer() { INT32 ConvertedLength = ConvertToASCII85(RawBuf, RawBufSize, A85Buf); #ifdef _DEBUG // Let's just check that it was encoded corrrectly. // (NOTE: I allocate 4 bytes more than necessary because ConvertFromASCII85 may // write an extra terminator byte past the end of the buffer memory) BYTE *Dest = (BYTE *) CCMalloc(RawBufSize + 4); if (Dest == NULL) // Error - no more memory return -1; INT32 nBytes = KernelDC::ConvertFromASCII85(A85Buf, ConvertedLength, Dest); ERROR2IF(nBytes < 0, -1, "Error in decoding ASCII85 data"); // Length check if (nBytes != (INT32) RawBufSize) ERROR3("Length of decoded ASCII85 data does not match original! Output may be corrupt"); // Binary comparison if (memcmp(RawBuf, Dest, RawBufSize) != 0) ERROR3("Decoded ASCII85 data does not match original! Output may be corrupt"); // Clean up. CCFree(Dest); #endif RawBufSize = 0; if (!OutputDirect(A85Buf, ConvertedLength)) { // Error return -1; } // All ok return ConvertedLength; } BYTE *KernelDC::RunLengthEncode(BYTE *Data, INT32 *pLength) { // Run length encode the data first if required by the caller. BYTE *pRLLBuf = NULL; // Work out how much space we will need (i.e. work out the worst case overhead for // run length encoding). // This is based on no repetition at all, i.e. no opportunity for compression. // This results in a one byte overhead per 128 bytes. INT32 Length = *pLength; INT32 Overhead = (Length / 128) + 10; // Add 10 for good measure! No GPFs here... // Get a buffer to convert the data in. Allocate 4 byes extra just to be safe INT32 RLLSize = Length + Overhead; pRLLBuf = (LPBYTE) CCMalloc(RLLSize + 4); if (pRLLBuf == NULL) // Error; return NULL; // Ok - we've got the buffer, so encode the data. // We don't bother to RLL unless we get a gain, i.e. 3 or more repeating characters. // (2 repeating bytes gives the same space usage, and can interrupt the stream so // actually uses more space). INT32 SeqLength = 0; INT32 DestOfs = 0; INT32 SrcOfs = 0; INT32 Ofs = 0; INT32 Len = Length; while (Len > 0) { // Check for limit on sequence length. ERROR2IF(SeqLength > 128, NULL, "Sequence too long in RLE encoding!"); if (SeqLength == 128) { // Got a full sequence - copy it to the destination buffer. ERROR2IF(DestOfs + SeqLength >= RLLSize, NULL, "RLE buffer over-run!"); pRLLBuf[DestOfs++] = SeqLength - 1; memcpy(pRLLBuf + DestOfs, Data + SrcOfs, SeqLength); DestOfs += SeqLength; SrcOfs += SeqLength; SeqLength = 0; } // Look for repeating characters if ((Len > 2) && (Data[Ofs] == Data[Ofs + 1]) && (Data[Ofs] == Data[Ofs + 2])) { // Ooh - found 3 repeating characters - see if there are any more. // First flush out any previous bytes. if (SeqLength > 0) { ERROR2IF(DestOfs + SeqLength >= RLLSize, NULL, "RLE buffer over-run!"); pRLLBuf[DestOfs++] = SeqLength - 1; memcpy(pRLLBuf + DestOfs, Data + SrcOfs, SeqLength); DestOfs += SeqLength; } // Sequence starts at this character. SrcOfs = Ofs; SeqLength = 0; while ((Len > 0) && (SeqLength < 128) && (Data[Ofs] == Data[SrcOfs])) { // Try next character Len--; Ofs++; SeqLength++; } // End of sequence of repeating characters - place it into buffer. ERROR2IF(DestOfs + 2 > RLLSize, NULL, "RLE buffer over-run!"); pRLLBuf[DestOfs++] = 257 - SeqLength; pRLLBuf[DestOfs++] = Data[SrcOfs]; // Move to next character //Ofs++; SeqLength = 0; SrcOfs = Ofs; } else { // Add this character to the current sequence. Len--; Ofs++; SeqLength++; } } // Flush remaining sequence, if there is one. if (SeqLength > 0) { ERROR2IF(DestOfs + SeqLength >= RLLSize, NULL, "RLE buffer over-run!"); pRLLBuf[DestOfs++] = SeqLength - 1; memcpy(pRLLBuf + DestOfs, Data + SrcOfs, SeqLength); DestOfs += SeqLength; } // Return the pointer to the RLL encoded data, and tell caller how long the data is. *pLength = DestOfs; return pRLLBuf; } BYTE *KernelDC::RunLengthDecode(BYTE *Data, INT32 *pLength) { // Ok, scan the data to see how large it will expand to. INT32 i = 0; INT32 Length = *pLength; INT32 Size = 0; BOOL FoundEOD = FALSE; while (i < Length) { if (Data[i] <= 127) { // Straight copying of data Size += (Data[i] + 1); // Skip past the data i += (Data[i] + 2); } else if (Data[i] > 128) { // Repeated data Size += (257 - Data[i]); // Skip past repeat count and byte to repeat. i += 2; } else { // 128 == End of stream FoundEOD = TRUE; break; } } // Got the size of data stream - allocate a buffer. if (Size == 0) { // Something not right here - cope with it gracefully *pLength = 0; return NULL; } // Allocate a buffer. Allocate 4 bytes extra just in case of slight buffer overruns BYTE *pRLLBuf = (BYTE *) CCMalloc(Size + 1 + 4); if (pRLLBuf == NULL) { // Error - no memory *pLength = 0; return NULL; } // Ok, actually decode it now. INT32 j = 0; i = 0; FoundEOD = FALSE; while (i < Length) { if (Data[i] <= 127) { // Straight copying of data memcpy(pRLLBuf + j, Data + i + 1, Data[i] + 1); // Skip past the data j += (Data[i] + 1); i += (Data[i] + 2); } else if (Data[i] > 128) { // Repeated data memset(pRLLBuf + j, Data[i + 1], 257 - Data[i]); // Skip past repeat count and byte to repeat. j += (257 - Data[i]); i += 2; } else { // 128 == End of stream FoundEOD = TRUE; break; } } // Sanity checks if (j!=Size) { *pLength=0; ERROR2(NULL, "Incorrect decoding of Run length data"); } // Ok, we've decoded it. *pLength = j; return pRLLBuf; } /******************************************************************************************** > void KernelDC::SetFullAccuracy(BOOL Full) Author: Tim_Browse (Xara Group Ltd) Created: 21/11/94 Inputs: Full: TRUE => Use 3dp when saving user space values FALSE => Use 2dp when saving user space values Purpose: Change the accuracy used when saving out user space values to the EPS file. This defaults to 2dp, but can be enabled to full accuracy (3dp) by passing in TRUE. We still do 2dp because that is what Illustrator and most of the other mediocre programs use and we don't want to generate files that might upset them. Camelot EPS uses the full 3dp accuracy though. ********************************************************************************************/ void KernelDC::SetFullAccuracy(BOOL Full) { FullAccuracy = Full; } /******************************************************************************************** > BOOL KernelDC::OutputNewLine() Author: Tim_Browse (Xara Group Ltd) Created: 28/03/94 Returns: FALSE (see Purpose) Purpose: Causes a new line to be started in the EPS/PS output. This is used to give a pleasant appearance to the file - most EPS/PS commands (as opposed to operands) are followed by a new line. NB. This base class version will always raise an ERROR2 and return FALSE because this function must be over-ridden in order to use the output functions. SeeAlso: KernelDC; KernelDC::OutputToken Errors: Always => ERROR2 ********************************************************************************************/ BOOL KernelDC::OutputNewLine() { ERROR2(FALSE, "OutputNewLine() called for base class KernelDC"); } /******************************************************************************************** > BOOL KernelDC::OutputToken(TCHAR *Str) Author: Tim_Browse (Xara Group Ltd) Created: 28/03/94 Inputs: Str - the character string to write to the stream. Returns: FALSE (see Purpose) Purpose: Outputs a string token to the EPS/PS stream. This is the central routine through which the other high-level routines eventually come. The other routines convert their output to a string, which they then pass on to this routine. A record is kept of the current line width - if it is over 70 characters wide before the token is output, then a new line is output to keep the lines in the EPS file reasonably short. For this reason, it is important not to output strings that contain newline characters, because this routine will not notice, and hence the LineWidth count will be wrong. This routine also ensures that tokens are separated by either a space or a newline, so it is not necessary to output spaces directly to keep the tokens separate - it happens automatically. NB. This base class version will always raise an ERROR2 and return FALSE because this function must be over-ridden in order to use the output functions. SeeAlso: KernelDC; KernelDC::OutputNewLine; KernelDC::OutputDirect Errors: Always => ERROR2 ********************************************************************************************/ BOOL KernelDC::OutputToken(TCHAR *Str) { ERROR2(FALSE, "OutputToken() called for base class KernelDC"); } /******************************************************************************************** > BOOL KernelDC::OutputDirect(BYTE *Buf, INT32 nBytes) Author: Tim_Browse (Xara Group Ltd) Created: 04/23/95 Inputs: Buf - the bytes to send to the stream. nBytes - the number of bytes to send to the stream. Returns: FALSE (see Purpose) Purpose: Send bytes directly to the PostScript stream with no alteration or padding. Used for sending binary/hex data to stream. NB. This base class version will always raise an ERROR2 and return FALSE because this function must be over-ridden in order to use the output functions. SeeAlso: KernelDC::OutputNewLine; KernelDC::OutputToken Errors: Always => ERROR2 ********************************************************************************************/ BOOL KernelDC::OutputDirect(BYTE *, INT32) { ERROR2(FALSE, "OutputDirect() called for base class KernelDC"); } /******************************************************************************************** > BOOL KernelDC::OutputTCHARAsChar(TCHAR *Buf, INT32 nBytes) Author: Alex Bligh Created: 13/06/2006 Inputs: Buf - the bytes to send to the stream. nBytes - the number of bytes to send to the stream (i.e the number of TCHARs to read) Returns: TRUE if all the bytes were sent ok; FALSE if not. Purpose: Send bytes directly to the PostScript stream with no alteration or padding. Used for sending binary/hex data to stream. We send the BYTE equal to the TCHAR with no conversion SeeAlso: KernelDC::OutputNewLine; KernelDC::OutputToken ********************************************************************************************/ BOOL KernelDC::OutputTCHARAsChar(TCHAR *Buf, INT32 nBytes) { if (sizeof(TCHAR) == sizeof(char)) return OutputDirect((BYTE *)Buf, nBytes); BYTE * pByte=new BYTE[nBytes]; if (!pByte) return FALSE; INT32 i; for (i=0; i ExportDC::ExportDC(Filter *Parent) Author: Tim_Browse (Xara Group Ltd) Created: 28/03/94 Purpose: Creates a DC of the correct type (RENDERTYPE_FILE). SeeAlso: EPSExportDC::EPSExportDC ********************************************************************************************/ ExportDC::ExportDC(Filter *Parent) : KernelDC(RENDERTYPE_FILE) { // Sanity check ENSURE(Parent != NULL, "NULL parent filter in ExportDC creation!"); // Install parent ParentFilter = Parent; ExportFile = NULL; } /******************************************************************************************** > BOOL ExportDC::Init(CCLexFile* pFile) Author: Tim_Browse (Xara Group Ltd) Created: 28/03/94 Inputs: pFile - the file to connect this DC to. Returns: TRUE if DC connected to file successfully, FALSE if an error occured. Purpose: Initialise an export DC by connecting it to a named file. Errors: Unable to open the file. SeeAlso: ExportDC ********************************************************************************************/ BOOL ExportDC::Init(CCLexFile* pFile) { // This file is already open when we get it ExportFile = pFile; // Make sure that the file is open ERROR2IF(!ExportFile->isOpen(), FALSE, "File was not open in ExportDC::Init()"); // File opened ok. return TRUE; } /******************************************************************************************** > ExportDC::~ExportDC() Author: Tim_Browse (Xara Group Ltd) Created: 28/03/94 Purpose: Deinitialise an Export device context. If the file connected to this DC is still open, the file is closed. SeeAlso: ExportDC ********************************************************************************************/ ExportDC::~ExportDC() { // This is no longer needed as the file is closed by the caller /* if (ExportFile.isOpen()) { if (IsUserName("Tim")) TRACE( _T("File still open in CCExportDC dtor")); ExportFile.close(); } */ } /******************************************************************************************** > Filter *ExportDC::GetParentFilter() Author: Tim_Browse (Xara Group Ltd) Created: 05/08/94 Returns: Pointer to the parent filter object. Purpose: Get a pointer to the filter object associated with this export device context. SeeAlso: Filter ********************************************************************************************/ Filter *ExportDC::GetParentFilter() { return ParentFilter; }