// $Id: epsfiltr.cpp 1668 2006-08-04 11:45:17Z 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============================ */ // An EPS import/export filter system for Camelot. #include "camtypes.h" //#include "epsfiltr.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include //#include "attrmgr.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "bitfilt.h" #include "ccdc.h" #include "colcomp.h" #include "csrstack.h" //#include "doccomp.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "errors.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "ensure.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "prvwflt.h" #ifndef WEBSTER //#include "extfilts.h" #endif //WEBSTER //#include "fixmem.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "layer.h" #include "lineattr.h" #include "nativeps.h" // old native filter based on eps, used for version 1.1 #include "nodeelip.h" #include "nodepath.h" #include "noderect.h" //#include "oilfltrs.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "page.h" #include "paper.h" //#include "pathname.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "paths.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "progress.h" //#include "resource.h" //#include "rik.h" #include "saveeps.h" #include "sglayer.h" #include "sprdmsg.h" //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "tim.h" //#include "trans2d.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "gclips.h" #include "nodetxts.h" #include "nodetxtl.h" #include "nodershp.h" //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "insertnd.h" #include "fontman.h" #include "guides.h" //#include "nev.h" // _R(IDN_USER_CANCELLED) //#include "will2.h" #include "moldshap.h" #include "ai_bmp.h" //#include "epsclist.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "epscdef.h" #include "nodetext.h" DECLARE_SOURCE("$Revision: 1668 $"); CC_IMPLEMENT_DYNAMIC(EPSFilter, VectorFilter); CC_IMPLEMENT_MEMDUMP(RangeList, List); CC_IMPLEMENT_MEMDUMP(EPSClipContext, List); CC_IMPLEMENT_MEMDUMP(EPSClipContextItem, ListItem); #define new CAM_DEBUG_NEW // Job 10463: remove PS Level bits - default to Level 2 INT32 EPSFilter::XSEPSExportPSType = 2; INT32 EPSFilter::XSEPSExportDPI = 200; BOOL EPSFilter::XSEPSExportTextAsCurves = FALSE; /******************************************************************************************** > class RangeListItem : public ListItem Author: Tim_Browse (Xara Group Ltd) Created: 28/09/94 Purpose: Hold a range that SeeAlso: ********************************************************************************************/ class RangeListItem : public ListItem { CC_DECLARE_MEMDUMP(RangeListItem) public: RangeListItem(Node *pFirst, Node *pLast); BOOL IsLayer; Range TheRange; Layer *TheLayer; }; CC_IMPLEMENT_MEMDUMP(RangeListItem, ListItem) /******************************************************************************************** > RangeListItem::RangeListItem(Node *pFirst, Node *pLast) Author: Tim_Browse (Xara Group Ltd) Created: 29/09/94 Inputs: pFirst, pLast - the first and last nodes in the range. Purpose: Create a range list item using the specified node range. SeeAlso: RangeListItem; RangeList ********************************************************************************************/ RangeListItem::RangeListItem(Node *pFirst, Node *pLast) { // range or layer? if(pFirst == pLast && IS_A(pFirst, Layer)) { // just a layer, so make a layer variant TheLayer = (Layer *)pFirst; IsLayer = TRUE; } else { // it's more than one, or not a layer // We want all nodes to be included in the range RangeControl Flags; Flags.Selected = TRUE; Flags.Unselected = TRUE; // Make the range and copy to where we want it (spot slight omission from Range class!) Range Tmp(pFirst, pLast, Flags); TheRange = Tmp; IsLayer = FALSE; } } /******************************************************************************************** > BOOL RangeList::AddRange(Node *pFirst, Node *pLast) Author: Tim_Browse (Xara Group Ltd) Created: 28/09/94 Inputs: pFirst, pLast - the first and last nodes in the range. Returns: TRUE if the range was added ok; FALSE if not. Purpose: Adds another range to the range list. All nodes from pFirst to pLast are inclusively added to the range. SeeAlso: Range ********************************************************************************************/ BOOL RangeList::AddRange(Node *pFirst, Node *pLast) { // Get a new range RangeListItem *pItem = new RangeListItem(pFirst, pLast); if (pItem == NULL) // Out of memory return FALSE; // Add it to the list and return success AddTail(pItem); return TRUE; } /******************************************************************************************** > DocRect RangeList::GetBoundingRect() Author: Tim_Browse (Xara Group Ltd) Created: 29/09/94 Returns: The union of the bounding boxes of all nodes in this range list. Purpose: Goes through all nodes in all the ranges in this RangeList, and unions all the bounding boxes together. It uses the simple algorithm, which does not take account of the attributes applied to the nodes - i.e. it only gives an approximation (hence this function can result in bounding boxes being in the tree which are not entirely accurate). SeeAlso: RangeList ********************************************************************************************/ DocRect RangeList::GetBoundingRect() { // Start off with empty rectangle DocRect RangeBBox; // Do each range in turn RangeListItem *pItem = (RangeListItem *) GetHead(); while (pItem != NULL) { // Do each node in turn if(pItem->IsLayer) { // layer based calcuations // see long note below RangeBBox = RangeBBox.Union(pItem->TheLayer->GetBoundingRect(TRUE)); } else { // range based calculations Node *pNode = pItem->TheRange.FindFirst(); while (pNode != NULL) { // Does this node have a bounding rectangle? if (pNode->IsBounded()) { // Yes - add it to our union NodeRenderableBounded *pNodeBounded = (NodeRenderableBounded *) pNode; // Pass in TRUE so that we don't do a 'proper' attribute scan for the // bounding box - we only need an approximate bounding rectangle // for centering it anyway. // NB. this results in inaccurate bounding boxes in the tree, but when // we call this we're about to transform them anyway, so they'll // be updated correctly afterwards. RangeBBox = RangeBBox.Union(pNodeBounded->GetBoundingRect(TRUE)); } else { ENSURE(FALSE, "Found a non-renderable node in the import range"); } // Try the next node. pNode = pItem->TheRange.FindNext(pNode); } } // Try the next range pItem = (RangeListItem *) GetNext(pItem); } // Return the accumulated bounds return RangeBBox; } /******************************************************************************************** > void RangeList::Transform(TransformBase& Trans) Author: Tim_Browse (Xara Group Ltd) Created: 29/09/94 Inputs: Trans - the transform object to transform all the nodes by. Purpose: Transforms all the nodes in all the ranges of this range list using the transform object given. SeeAlso: Range::Transform; TransformBase ********************************************************************************************/ void RangeList::Transform(TransformBase& Trans) { // Do each range in turn RangeListItem *pItem = (RangeListItem *) GetHead(); while (pItem != NULL) { if(pItem->IsLayer) { pItem->TheLayer->Transform(Trans); } else { // Transform all nodes in this range Node *pNode = pItem->TheRange.FindFirst(); while (pNode != NULL) { // Is this node renderable? if (pNode->IS_KIND_OF(NodeRenderable)) { // Yes - transform it. NodeRenderable *pNodeRenderable = (NodeRenderable *) pNode; pNodeRenderable->Transform(Trans); } else { ENSURE(FALSE, "Found a non-renderable node in the import range"); } // Try the next node. pNode = pItem->TheRange.FindNext(pNode); } } // Try the next range pItem = (RangeListItem *) GetNext(pItem); } } /******************************************************************************************** > EPSClipContextItem::EPSClipContextItem(INT32 TheContextLevel, InkPath *pNewClipPath) Author: Tim_Browse (Xara Group Ltd) Created: 05/22/95 Inputs: TheContextLevel - the context level for this clip region. Purpose: Initialise the clip region item to sensible values. SeeAlso: EPSClipContext ********************************************************************************************/ EPSClipContextItem::EPSClipContextItem(INT32 TheContextLevel, Path *pNewClipPath) { ContextLevel = TheContextLevel; pClipPath = pNewClipPath; // Defaults to not a complex region. ComplexRegionLevel = 0; } /******************************************************************************************** > EPSClipContextItem::~EPSClipContextItem() Author: Tim_Browse (Xara Group Ltd) Created: 05/22/95 Purpose: Clean up the context item - this includes deleting the clipping path held in the region item. SeeAlso: EPSClipContext ********************************************************************************************/ EPSClipContextItem::~EPSClipContextItem() { delete pClipPath; } /******************************************************************************************** > EPSClipContext::EPSClipContext() Author: Tim_Browse (Xara Group Ltd) Created: 05/22/95 Purpose: Initialise the context to a sensible state, i.e. a zero context level. ********************************************************************************************/ EPSClipContext::EPSClipContext() { ContextLevel = 0; ComplexRegionLevel = 0; // Initially there is no clip region. pCachedClipPath = NULL; CacheIsValid = TRUE; // set default clipping flags ClippingFlags = 2 | CLIPPING_CLIP_WINDING; } /******************************************************************************************** > void EPSClipContext::SaveContext() Author: Tim_Browse (Xara Group Ltd) Created: 05/22/95 Purpose: Save the current clipping state for future use - no matter how many clip regions are added to the current context, the exact state of the current clipping context can be restore with a (balanced) call to EPSClipContext::RestoreContext(). SeeAlso: EPSClipContext::RestoreContext ********************************************************************************************/ void EPSClipContext::SaveContext() { ContextLevel++; } /******************************************************************************************** > BOOL EPSClipContext::RestoreContext() Author: Tim_Browse (Xara Group Ltd) Created: 05/22/95 Purpose: Restores the clipping context to the state it was in the last time SaveContext() was called on this clip context. Errors: No call to SaveContext() has been made => ERROR2 SeeAlso: EPSClipContext::SaveContext ********************************************************************************************/ BOOL EPSClipContext::RestoreContext() { // Invalidate cache CacheIsValid = FALSE; // Sanity check ERROR2IF(ContextLevel == 0, FALSE, "Unbalanced call to EPSClipContext::RestoreContext()"); // Decrement context level ContextLevel--; // Delete all items added since save context was last called... EPSClipContextItem *pItem = (EPSClipContextItem *) GetTail(); while ((pItem != NULL) && (pItem->ContextLevel > ContextLevel)) { delete RemoveTail(); // Get the next item pItem = (EPSClipContextItem *) GetTail(); } // If we get here then it's ok return TRUE; } /******************************************************************************************** > BOOL EPSClipContext::StartComplexClipRegion() Author: Tim_Browse (Xara Group Ltd) Created: 05/22/95 Returns: TRUE if ok; FALSE if out of memory. Purpose: Indicates that we are about to start constructing a complex clipping region. All future calls to AddNewClippingPath() will be joined into the current clipping region until EndComplexRegion is called. Note that you cannot interleave these calls with Save/Restore context calls; complex regions cannot be unbalanced across context levels. Errors: Out of memory => ERROR1 SeeAlso: EPSClipContext::EndComplexClipRegion() ********************************************************************************************/ BOOL EPSClipContext::StartComplexClipRegion() { // Update complex region count ComplexRegionLevel++; // We need to create a record to hold this complex region // (it has a NULL clipping region initially) EPSClipContextItem *pItem = new EPSClipContextItem(ContextLevel, NULL); if (pItem == NULL) // Out of memory return FALSE; // It's a complex region so save the level pItem->ComplexRegionLevel = ComplexRegionLevel; // Add to end of list AddTail(pItem); // All ok return TRUE; } /******************************************************************************************** > BOOL EPSClipContext::EndComplexClipRegion() Author: Tim_Browse (Xara Group Ltd) Created: 05/22/95 Returns: TRUE if ok; FALSE if no corresponding call to StartComplexClipRegion() has been encountered => ERROR2 Purpose: See StartComplexClipRegion(). Errors: If unbalanced call is made => ERROR2 SeeAlso: EPSClipContext::StartComplexClipRegion ********************************************************************************************/ BOOL EPSClipContext::EndComplexClipRegion() { ERROR2IF(ComplexRegionLevel == 0, FALSE, "Unbalanced call to EPSClipContext::EndComplexClipRegion()"); // Adjust complex region level. ComplexRegionLevel--; // All ok return TRUE; } /******************************************************************************************** > BOOL EPSClipContext::AddNewClippingPath(Path *pNewClipPath) Author: Tim_Browse (Xara Group Ltd) Created: 05/22/95 Inputs: The path to augment the current clipping region with. Returns: TRUE if clipping region updated ok; FALSE if not, i.e. out of memory => ERROR1 Purpose: Takes a copy of the path passed in and merges it with the current clipping context. Errors: Out of memory => ERROR1 ********************************************************************************************/ BOOL EPSClipContext::AddNewClippingPath(Path *pNewClipPath) { // Invalidate cache CacheIsValid = FALSE; // Flag to indicate if we just do a straightforward copy BOOL SimpleCopy = FALSE; EPSClipContextItem *pItem = NULL; if (ComplexRegionLevel > 0) { // We are building a complex region, so join the new path with the existing one. // First, make sure we have a clip path already? pItem = (EPSClipContextItem *) GetTail(); if (pItem->pClipPath == NULL) { // No clip path - just copy the new path and use that. SimpleCopy = TRUE; } else { // Already have a clip path - join with new one to form a complex path. if (!pItem->pClipPath->MergeTwoPaths(*pNewClipPath)) // Error occured (i.e. out of memory) return FALSE; } } else { // Just a simple clipping region pItem = new EPSClipContextItem(ContextLevel, NULL); if (pItem == NULL) // Out of memory return FALSE; // Add to end of list AddTail(pItem); // Just copy the path passed in SimpleCopy = TRUE; } // Should we just copy the path into the list item? if (SimpleCopy) { // Sanity check ERROR2IF(pItem == NULL, FALSE, "No clipping item found!"); // (yes, this code is pretty tedious!) pItem->pClipPath = new Path; if (pItem->pClipPath == NULL) // Out of memory return FALSE; // Graeme (26/7/00) - Added a test for a NULL pointer to prevent access violations. if ( pNewClipPath != NULL && !pItem->pClipPath->Initialise ( pNewClipPath->GetNumCoords () + 3, 12 ) ) { // Failed to initialise - out of memory return FALSE; } if (!pItem->pClipPath->CopyPathDataFrom(pNewClipPath)) // Failed to copy - out of memory - but this shouldn't happen as // we made sure we had enough space with the initialise call! ERROR2(FALSE, "Unexpected out-of-space error when copying clipping path"); } // If we've got here, it must have worked... return TRUE; } /******************************************************************************************** > BOOL EPSClipContext::ClipPathToRegion(Path *pPathToClip) Author: Tim_Browse (Xara Group Ltd) Created: 05/22/95 Inputs: pPathToClip - the path object that we want to clip Outputs: pPathToClip - the clipped version of the path Returns: TRUE if clipped ok; FALSE if not. Purpose: Given a path, clip it to the current clipping region specified by this object. SeeAlso: EPSClipContext::GetClipRegion ********************************************************************************************/ BOOL EPSClipContext::ClipPathToRegion(Path *pPathToClip) { // Cope with NULL clip region first if (GetClipRegion() == NULL) // Path is already 'clipped' return TRUE; // Ensure GetClipRegion validated the cache ERROR2IF(!CacheIsValid, FALSE, "Could not get valid clipping path in EPSClipContext::ClipPathToRegion()"); // We now have a valid region, so use it to clip the given path // NB. we do an intersection using the cliping path's winding, with a tolerance of 0, // and flatness of 128 millipoints (arbitrary). if (pCachedClipPath->ClipPathToPath(*pPathToClip, pPathToClip, ClippingFlags, 128, 256, 256) == -1) { // An error occured... return FALSE; } // Initialise flags so the bezier tool is happy // [see comment for Path::ClipPathToPath()]. pPathToClip->InitialiseFlags(); // If we get this far, it worked! return TRUE; } /******************************************************************************************** > Path *EPSClipContext::GetClipRegion() Author: Tim_Browse (Xara Group Ltd) Created: 05/22/95 Returns: Pointer to the clipping path, or NULL if no clipping region is specified by this object. Purpose: Get the path which defines the current clipping region ofheld by this object. SeeAlso: EPSClipContext::ClipPathToRegion ********************************************************************************************/ Path *EPSClipContext::GetClipRegion() { if (!CacheIsValid) { // Cache is invalid - if there is no path in the cache, get a new one, otherwise // empty the one in the cache and use that. if (pCachedClipPath == NULL) { pCachedClipPath = new Path; if (pCachedClipPath == NULL) // Out of memory return NULL; if (!pCachedClipPath->Initialise(12, 12)) // Failed to initialise - out of memory return NULL; } else { // Just clear this path out so we can use it to build a clipping region. pCachedClipPath->ClearPath(); } // Calculate the current clipping path - we do this by going along the region // list and combining the clip regions. EPSClipContextItem *pItem = (EPSClipContextItem *) GetHead(); while (pItem != NULL) { if (pItem->pClipPath != NULL) { // Do we already have a clipping region? if (pCachedClipPath->GetNumCoords() > 0) { // Combine this clipping region with the region being constructed... if (pItem->pClipPath->ClipPathToPath(*pCachedClipPath, pCachedClipPath, 2 | CLIPPING_CLIP_WINDING, 128, 256, 256) == -1) { // An error occured... return FALSE; } } else { // No clipping region yet - just copy the data from this path if (!pCachedClipPath->MakeSpaceInPath(pItem->pClipPath->GetNumCoords() + 3)) // Failed to initialise - out of memory return FALSE; if (!pCachedClipPath->CopyPathDataFrom(pItem->pClipPath)) // Failed to copy - out of memory - but this shouldn't happen as // we made sure we had enough space with the initialise call! ERROR2(FALSE, "Unexpected out-of-space error when copying clipping path"); } } // Get the next item pItem = (EPSClipContextItem *) GetNext(pItem); } // Did we get any clipping at all? if (pCachedClipPath->GetNumCoords() == 0) { // No clip region - delete and use NULL to indicate this. delete pCachedClipPath; pCachedClipPath = NULL; } // Validate cache CacheIsValid = TRUE; } // Cache must be valid, so return the contents of the cache. return pCachedClipPath; } // This is the array of ArtWorks EPS command/keyword names. CommandMap EPSFilter::Commands[] = { // Path Construction operators { EPSC_l, _T("l") }, { EPSC_c, _T("c") }, { EPSC_m, _T("m") }, { EPSC_L, _T("L") }, { EPSC_C, _T("C") }, { EPSC_v, _T("v") }, { EPSC_V, _T("V") }, { EPSC_y, _T("y") }, { EPSC_Y, _T("Y") }, // Colour operators { EPSC_g, _T("g") }, { EPSC_G, _T("G") }, { EPSC_k, _T("k") }, { EPSC_K, _T("K") }, { EPSC_x, _T("x") }, { EPSC_X, _T("X") }, { EPSC_p, _T("p") }, { EPSC_P, _T("P") }, // Path Painting operators { EPSC_N, _T("N") }, { EPSC_n, _T("n") }, { EPSC_F, _T("F") }, { EPSC_f, _T("f") }, { EPSC_S, _T("S") }, { EPSC_s, _T("s") }, { EPSC_B, _T("B") }, { EPSC_b, _T("b") }, { EPSC__u, _T("*u") }, { EPSC__U, _T("*U") }, // Group operators { EPSC_u, _T("u") }, { EPSC_U, _T("U") }, // Graphics state operators { EPSC_A, _T("A") }, { EPSC_d, _T("d") }, { EPSC_i, _T("i") }, { EPSC_D, _T("D") }, { EPSC_j, _T("j") }, { EPSC_J, _T("J") }, { EPSC_M, _T("M") }, { EPSC_w, _T("w") }, // Special operators { EPSC_ArrayStart,_T("[") }, { EPSC_ArrayEnd,_T("]") }, { EPSC_Slash, _T("/") }, // Overprint operators { EPSC_O, _T("O") }, { EPSC_R, _T("R") }, // Clipping operators { EPSC_q, _T("q") }, { EPSC_Q, _T("Q") }, { EPSC_H, _T("H") }, { EPSC_h, _T("h") }, { EPSC_W, _T("W") }, // Text operators { EPSC_To, _T("To") }, { EPSC_TO, _T("TO") }, { EPSC_Tp, _T("Tp") }, { EPSC_TP, _T("TP") }, // Matrix operators { EPSC_Tm, _T("Tm") }, { EPSC_Td, _T("Td") }, { EPSC_T_, _T("T*") }, { EPSC_TR, _T("TR") }, // Text Attribute operators { EPSC_Tr, _T("Tr") }, { EPSC_Tf, _T("Tf") }, { EPSC_Ta, _T("Ta") }, { EPSC_Tl, _T("Tl") }, { EPSC_Tt, _T("Tt") }, { EPSC_TW, _T("TW") }, { EPSC_Tw, _T("Tw") }, { EPSC_TC, _T("TC") }, { EPSC_Tc, _T("Tc") }, { EPSC_Ts, _T("Ts") }, { EPSC_Ti, _T("Ti") }, { EPSC_Tz, _T("Tz") }, { EPSC_TA, _T("TA") }, { EPSC_Tq, _T("Tq") }, // Text Body operators { EPSC_Tx, _T("Tx") }, { EPSC_Tj, _T("Tj") }, { EPSC_TX, _T("TX") }, { EPSC_Tk, _T("Tk") }, { EPSC_TK, _T("TK") }, { EPSC_Tplus, _T("T+") }, { EPSC_Tminus, _T("T-") }, // Misc { EPSC_showpage,_T("showpage") }, { EPSC_end, _T("end") }, { EPSC_ctx, _T("ctx") }, { EPSC_ctex, _T("ctex") }, { EPSC_Invalid, _T("Invalid") }, { EPSC_Name, _T("Name") }, { EPSC_Integer, _T("Integer") }, { EPSC_Double, _T("Double") }, { EPSC_FixedPoint,_T("FixedPt") }, { EPSC_String, _T("String") }, { EPSC_Comment, _T("Comment") }, { EPSC_EOL, _T("EOL") }, { EPSC_EOF, _T("EOF") } }; RangeList EPSFilter::ImportRange; /******************************************************************************************** > EPSFilter::EPSFilter() Author: Tim_Browse (Xara Group Ltd) Created: 28/10/93 Purpose: Constructor for an EPSFilter object. The object should be initialised before use. SeeAlso: EPSFilter::Init ********************************************************************************************/ EPSFilter::EPSFilter() { ImportMsgID = _R(IDT_IMPORTMSG_AI); Flags.CanImport = TRUE; //WEBSTER-Martin-27/01/97 #ifdef WEBSTER Flags.CanExport = FALSE; #else Flags.CanExport = TRUE; #endif //WEBSTER FilterID = FILTERID_AIEPS; #ifndef DO_EXPORT Flags.CanExport = FALSE; #endif EPSFile = NULL; TokenBuf = NULL; pColours = NULL; ExportDCPtr = NULL; ExportRegion = NULL; ExportMsgID = _R(IDT_EXPORTMSG_AI); AdjustOrigin = TRUE; SupportsLayers = TRUE; // The offsets into the file for the Preview thing EPSStart = 0; EPSEnd = 0; PreviewStart = 0; PreviewEnd = 0; TextComment[0] = 0; TextComment[1] = 0; TextComment[2] = 1; // set the font extras block vars. These indicate to the // filter to do something special with the next set of font attributes FontFlags.Bold = FALSE; FontFlags.Italic = FALSE; // Set the font type to assume we are loading true type fonts unless // told otherwise. ClassOfFont = FC_TRUETYPE; // Define the mould threshold default value. NewMouldThreshold = MOULD_V1THRESHOLD; // We're not in a group at first. EPS_Group = FALSE; // Clear the length of the file to the end of the EPS code. EndOfEPS = 0; // Set up the comment list. These are the known DSC comments, and how to handle them. EPSComments.Add ( _T("%%BeginProlog"), _T("%%EndProlog"), TRUE, 0 ); EPSComments.Add ( _T("%%BeginSetup"), _T("%%EndSetup"), TRUE, 0 ); EPSComments.Add ( _T("%%BeginDocument"), _T("%%EndDocument"), FALSE, 0 ); EPSComments.Add ( _T("%%BeginPageSetup"), _T("%%EndPageSetup"), FALSE, 0 ); EPSComments.Add ( _T("%%BeginDefaults"), _T("%%EndDefaults"), FALSE, 0 ); EPSComments.Add ( _T("%%BeginEmulation"), _T("%%EndEmulation"), FALSE, 0 ); EPSComments.Add ( _T("%%BeginPreview"), _T("%%EndPreview"), FALSE, 0 ); EPSComments.Add ( _T("%%BeginFeature"), _T("%%EndFeature"), FALSE, 0 ); EPSComments.Add ( _T("%%BeginFile"), _T("%%EndFile"), FALSE, 0 ); EPSComments.Add ( _T("%%BeginFont"), _T("%%EndFont"), FALSE, 0 ); EPSComments.Add ( _T("%%BeginProcSet"), _T("%%EndProcSet"), FALSE, 0 ); EPSComments.Add ( _T("%%BeginResource"), _T("%%EndResource"), FALSE, 0 ); EPSComments.Add ( _T("%%BeginCustomColor"), _T("%%EndCustomColor"), FALSE, 0 ); EPSComments.Add ( _T("%%BeginProcessColor"), _T("%%EndProcessColor"), FALSE, 0 ); } /******************************************************************************************** > virtual BOOL EPSFilter::Init() Author: Tim_Browse (Xara Group Ltd) Created: 28/10/93 Returns: FALSE => don't use this class for real filters! Purpose: Initialise an EPSFilter object. This is a base class and so should never actually be initialised so this function will always return FALSE, and also ENSURE in debug builds. ********************************************************************************************/ BOOL EPSFilter::Init() { // This class is now used only as basis for deriving EPS filters - it should not // actually be used as a real filter. ENSURE(FALSE, "Base EPSFilter class called! Do not use this!"); return FALSE; } /******************************************************************************************** > BOOL EPSFilter::InitPrefs() Author: Tim_Browse (Xara Group Ltd) Created: 06/01/95 Purpose: Initialise preferences for EPS formats. This is used by the Camelot EPS export filter, and includes font mappings, DPI, PostScript language level control, and exporting text as curves. ********************************************************************************************/ BOOL EPSFilter::InitPrefs() { #ifndef STANDALONE // Register preferences for EPS output. Camelot.DeclareSection(_T("EPS"), 5); Camelot.DeclarePref(NULL, _T("ExportPSType"), &XSEPSExportPSType, 0, 2); Camelot.DeclarePref(NULL, _T("ExportDPI"), &XSEPSExportDPI, 10, 600); Camelot.DeclarePref(NULL, _T("ExportTextAsCurves"), &XSEPSExportTextAsCurves); // Register preferences for font mappings from TrueType to PostScript names. // This is used by Camelot EPS (or any other filter or class that wants to use it). Camelot.DeclareSection( _T("EPSFontMapping"), 50); static const TCHAR FontMappings[][2][30] = { // Standard PostScript font mappings // // TrueType name => PostScript name // { _T("Times-New-Roman"), _T("Times-Roman") }, { _T("Times-New-Roman-Bold"), _T("Times-Bold") }, { _T("Times-New-Roman-Italic"), _T("Times-Italic") }, { _T("Times-New-Roman-BoldItalic"), _T("Times-BoldItalic") }, { _T("Arial"), _T("Helvetica") }, { _T("Arial-Bold"), _T("Helvetica-Bold") }, { _T("Arial-Italic"), _T("Helvetica-Oblique") }, { _T("Arial-BoldItalic"), _T("Helvetica-BoldOblique") }, { _T("Courier-New"), _T("Courier") }, { _T("Courier-New-Bold"), _T("Courier-Bold") }, { _T("Courier-New-Italic"), _T("Courier-Oblique") }, { _T("Courier-New-BoldItalic"), _T("Courier-BoldOblique") }, { _T("Michael"), _T("Palatino-Roman") }, { _T("Michael-Bold"), _T("Palatino-Bold") }, { _T("Michael-Italic"), _T("Palatino-Italic") }, { _T("Michael-BoldItalic"), _T("Palatino-BoldItalic") }, { _T("NewSchbook"), _T("NewCenturySchlbk-Roman") }, { _T("NewSchbook-Bold"), _T("NewCenturySchlbk-Bold") }, { _T("NewSchbook-Italic"), _T("NewCenturySchlbk-Italic") }, { _T("NewSchbook-BoldItalic"), _T("NewCenturySchlbk-BoldItalic") }, { _T("ZapfDingbatsBT"), _T("ZapfDingbats") }, { _T("Symbol"), _T("Symbol") }, #if 0 // Currently we have no TrueType equivalents for these fonts. { _T(""), _T("AvantGarde-Book") }, { _T(""), _T("AvantGarde-Demi") }, { _T(""), _T("AvantGarde-BookOblique") }, { _T(""), _T("AvantGarde-DemiOblique") }, { _T(""), _T("Bookman-Light") }, { _T(""), _T("Bookman-Demi") }, { _T(""), _T("Bookman-LightItalic") }, { _T(""), _T("Bookman-DemiItalic") }, { _T(""), _T("ZapfChancery-MediumItalic") }, #endif // Terminator { _T(""), _T("") } }; // Loop to declare all of these font mappings... INT32 i = 0; while (camStrclen(FontMappings[i][0]) > 0) { Camelot.SetPrefDirect( _T("EPSFontMapping"), (TCHAR *) &FontMappings[i][0][0], &FontMappings[i][1][0]); i++; } #endif // All ok return TRUE; } // The size of the buffer used to hold tokens const INT32 TokenBufSize = 256; /******************************************************************************************** > BOOL EPSFilter::PrepareToImport() Author: Tim_Browse (Xara Group Ltd) Created: 22/02/94 Returns: TRUE if succeeded, FALSE if not (e.g. no memory for EPS stack) Purpose: Prepare to import EPS data using this filter. This sets up the filter to a sensible state for reading. Errors: Out of memory. SeeAlso: EPSFilter::DoImport; EPSFilter::CleanUpAfterImport Scope: Private ********************************************************************************************/ BOOL EPSFilter::PrepareToImport() { if (!SetUpCurrentAttrs()) return FALSE; // Start the selection operation. if (!ImportInfo.pOp->DoStartSelOp(FALSE)) return FALSE; // Error if no file object. if (EPSFile == NULL) return FALSE; // Get ready to parse EPS tokens. EPSFile->InitLexer(); EPSFile->SetDelimiters("()<>[]{}/%"); EPSFile->SetCommentMarker('%'); EPSFile->SetStringDelimiters("()"); TokenBuf = EPSFile->GetTokenBuf(); Token = EPSC_Invalid; EPSFlags.ComplexPath = 0;//FALSE; EPSFlags.StrokeComplexPath = FALSE; EPSFlags.NoAttributes = FALSE; EPSFlags.EPSErrorEncountered = FALSE; EPSFlags.AddToNewLayer = FALSE; EPSFlags.PathIsHidden = FALSE; ThePathType = PATH_NORMAL; // Decide whether we need to use layers or not // Neville 7/8/97 - Layers are bad in Webster as they conflict with frames // Now the same is true in Camelot #ifndef WEBSTER // If the format supports layers and we are importing then we need to make a decision // based on whether the present document has frames in or not // Otherwise, just use the old code if (SupportsLayers && TheDocument->IsImporting()) { // Otherwise if we are in:- // - a framed document then remove all layers from importing // - a layered document then import with layers ERROR2IF(ImportInfo.pSpread == NULL,FALSE,"EPSFilter::PrepareToImport No spread"); Layer * pFrame = ImportInfo.pSpread->FindFirstFrameLayer(); if (pFrame == NULL) UseLayers = TRUE; // Layered = allow layers else UseLayers = FALSE; // Framed = disallow layers } else UseLayers = SupportsLayers && Filter::ImportWithLayers; #else // Do we want to import with layers? Use the preference flags to determine the actions if ( SupportsLayers && (!TheDocument->IsImporting() && Filter::OpenWithLayers) || (TheDocument->IsImporting() && Filter::ImportWithLayers) ) UseLayers = TRUE; else UseLayers = FALSE; #endif // WEBSTER // No path is being constructed pPath = NULL; pInkPath = NULL; // No nodes imported yet. ImportRange.DeleteAll(); pFirstNodeInRange = NULL; pLastNodeInRange = NULL; // No groups found yet. GroupNesting = 0; MaskedGroupNesting = 0; // No invalid paths found yet InvalidPathFound = FALSE; // Inform all the document components that we are about to import TheDocument->EPSStartImport(this); DocComponent *pComponent = TheDocument->EnumerateDocComponents(NULL); while (pComponent != NULL) { // Inform this document component that we are about to start an EPS import. pComponent->EPSStartImport(this); // If this is the colour component, remember it if (pComponent->GetRuntimeClass() == CC_RUNTIME_CLASS(ColourListComponent)) pColours = (ColourListComponent *) pComponent; // Look for next doc component pComponent = TheDocument->EnumerateDocComponents(pComponent); } ENSURE(pColours != NULL, "Didn't find a colour list component!"); if (pColours == NULL) // Tricky to import if we can't find the colours! return FALSE; // All ok return TRUE; } /******************************************************************************************** > void EPSFilter::CleanUpAfterImport(BOOL Successful) Author: Tim_Browse (Xara Group Ltd) Created: 24/02/94 Inputs: Successful: TRUE => the import succeeded FALSE => something went wrong with the import, so abandon all changes made. Purpose: Cleans up the memory allocated by EPSFilter::PrepareToImport() - used when the import process ends, either normally or abnormally. SeeAlso: EPSFilter::PrepareToImport; EPSFilter::DoImport Scope: Private ********************************************************************************************/ void EPSFilter::CleanUpAfterImport(BOOL Successful) { DeleteCurrentAttrs(); // Get rid of file object EPSFile->DeinitLexer(); EPSFile = NULL; TokenBuf = NULL; // get rid of anything on the graphic state stack TRACEUSER( "Ben", _T("Entries on GraphicStateStack = %d\n"), GraphicStateStack.GetCount()); GraphicStateStack.DeleteAll(); // Throw away handle to colour list component. pColours = NULL; // Inform all the document components that we have finished importing TheDocument->EPSEndImport(this, Successful); DocComponent *pComponent = TheDocument->EnumerateDocComponents(NULL); while (pComponent != NULL) { // Inform this document component that we have finished importing pComponent->EPSEndImport(this, Successful); // Look for next doc component pComponent = TheDocument->EnumerateDocComponents(pComponent); } // If imported with layers, make sure the top layer becomes the active one. if (Successful) // && UseLayers) { LayerSGallery::MakeTopLayerActive(ImportInfo.pSpread); BROADCAST_TO_ALL(SpreadMsg(ImportInfo.pSpread,SpreadMsg::LAYERCHANGES)); } // Inform the user if we found any bad paths // BODGE: This is disabled while we are doing the clipart so the batch converter // can run unattended. if (0 && InvalidPathFound) InformWarning(_R(IDT_IMPORT_BAD_PATH_FOUND)); // Clean up import range items. ImportRange.DeleteAll(); TheDocument->PostImport(); } /******************************************************************************************** > INT32 EPSFilter::HowCompatible(PathName& Filename, ADDR HeaderStart, UINT32 HeaderSize, UINT32 FileSize) Author: Rik_Heywood (Xara Group Ltd) Created: 1/2/95 Inputs: Filename - The name of the file to get bits of HeaderStart - The block of mem that holds the first part of the file to be read in HeaderSize - The size of the bit of memory FileSize - The size of the file Returns: 0 to 10. 0 means I know nothing about this file. 10 means its definatly mine Purpose: Looks at the start of the file to see if it recognises the file as belonging to it. ********************************************************************************************/ INT32 EPSFilter::HowCompatible(PathName& Filename, ADDR HeaderStart, UINT32 HeaderSize, UINT32 FileSize) { PORTNOTE("byteorder", "TODO: Check byte ordering") return EPSHeaderIsOk(HeaderStart, HeaderSize); } /******************************************************************************************** > virtual BOOL EPSFilter::IsDefaultDocRequired(const TCHAR* pcszPathName) Author: Neville_Humphrys (Xara Group Ltd) Created: 9/10/95 Inputs: pcszPathName pointer to the pathname to check Returns: TRUE if the filter requires a default document, FALSE if not. Purpose: Works out if opening a file of this type requires a default document to be loaded. If the file format supplies the document then return FALSE otherwise return TRUE. An example would be opening a bitmap file. This has no document defined in the file format and so we need to laod the default document before importing the bitmap into this file. In this baseclass version return FALSE and hence assume that the filters that need to will override this function to return TRUE. SeeAlso: Filter; Filter::IsDefaultDocRequired; CCamDoc::OnOpenDocument; SeeAlso: FilterFamily::DoImport; Filter::DoImport; ********************************************************************************************/ BOOL EPSFilter::IsDefaultDocRequired(const TCHAR* pcszPathName) { return TRUE; } /******************************************************************************************** > INT32 EPSFilter::EPSHeaderIsOk(ADDR pFileHeader, UINT32 HeaderSize) Author: Tim_Browse (Xara Group Ltd) Created: 28/02/94 Inputs: pFileHeader - pointer to the first few bytes of the file. HeaderSize - the number of bytes pointed to by pFileHeader. Returns: A number between 0 and 10 indicating how much it likes this file - see Filter::HowCompatible for details. Purpose: Checks to see if the EPS comment headers specify that this is an Illustrator generated EPS file, as required. SeeAlso: Filter::HowCompatible ********************************************************************************************/ INT32 EPSFilter::EPSHeaderIsOk(ADDR pFileHeader, UINT32 HeaderSize) { // This function is not unicode // Check the first line in EPS file if (strncmp((char *) pFileHeader, "%!PS-Adobe", 10) != 0) { // Incorrect version of EPS header line - we don't want this return 0; } // !PS-Adobe line is ok - check creator line... std::istringstream HeaderFile((char *) pFileHeader, ios_base::in /*HeaderSize*/); char Buffer[200]; UINT32 Lines = 0; while ((Lines < 20) && !HeaderFile.eof()) { HeaderFile.getline(Buffer, 200); Lines++; // Return TRUE if this file was created by Illustrator, or has been exported in // Illustrator format. if (strncmp(Buffer, "%%Creator: Adobe Illustrator", 28) == 0) // We definitely want this. return 8; if (strncmp(Buffer, "%%Creator:", 10) == 0) { // Found the creator line - does it contain the word Illustrator? if (strstr(Buffer, "Illustrator") != NULL) return 9; else break; } // If we find the compression token then stop the search as we don't want to start // looking in the compressed data! if (strncmp(Buffer, "%%Compression:", 14)==0) break; } // Didn't find a suitable Creator line, but the EPS line was ok, so return // that we're interested, but not sure. return 5; } /******************************************************************************************** > BOOL EPSFilter::DoImport(SelOperation *Op, CCLexFile* pFile, Document* DestDoc, BOOL AutoChosen, ImportPosition* Pos, KernelBitmap** ppImportedBitmap, DocCoord* pPosTranslate, String_256* URL) Author: Tim_Browse (Xara Group Ltd) Created: 28/10/93 Inputs: Op - pointer to the operation that this input process is associated with. pFile - the EPS file to be loaded. DestDoc - The Document object which should hold the data read in from the EPS file. Pos - ppImportedBitmap - this is used mainly in the bitfltr.cpp for the HTML import filter. HTMLFilter::DoImport() needs a pointer to a kernel bitmap to set the background bitmap up into Camelot. pPosTranslate - This is used too by the HTMLFilter in order to do a formatting. URL - URL of the original imported file Returns: TRUE if the input operation worked ok, FALSE if not. Purpose: Read EPS data from an EPS file, and convert it into Camelot tree data structures. When the file reading is complete, a forced redraw is issued for the area affected by the new data. At the moment, the data is just put on the first layer of the first spread of the first chapter in the document, and this is a bodge. The operation is terminated (i.e. its End() function is called) if the operation completed succesfully. Errors: Fails (returns FALSE) if the document structure is incorrect, if the EPS file contains a syntax error, if the EPS file cannot be found/opened, or if the EPS file is not actually an EPS file. SeeAlso: EPSFilter::PrepareToImport; EPSFilter::CleanUpAfterImport ********************************************************************************************/ BOOL EPSFilter::DoImport(SelOperation *Op, CCLexFile* pFile, Document* DestDoc, BOOL AutoChosen, ImportPosition* Pos, KernelBitmap** ppImportedBitmap, DocCoord* pPosTranslate, String_256* URL) { // Set up document pointer TheDocument = DestDoc; // Remember operation and position so we can use them to add nodes in other functions. ImportInfo.pOp = Op; ImportInfo.Pos = Pos; ImportInfo.pSpread = NULL; // Get ready to read some EPS data EPSFile = pFile; // See if we are capable of having a Preview bitmap attached. if ( pFile->IS_KIND_OF ( CCStreamFile ) ) { // OK, we will have to check for a preview bmp and skip to the Postscript if there is one // read the first 4 bytes and see if they match the magic PostScript word // Note this works in non-unicode mode char Buf[4]; // clear the buffer memset(Buf, 0, 4); // read the bytes in pFile->read(Buf, 4); // see if its a match if ((Buf[0]=='\xC5') && (Buf[1]=='\xD0') && (Buf[2]=='\xD3') && (Buf[3]=='\xC6')) { // Yes, this is a binary EPS file that has a TIFF attached, so find the EPS FilePos StartOfEPS = 0; EndOfEPS = 0; pFile->read(&StartOfEPS, 4); TRACEUSER( "Rik", _T("EPS Starts at %ld\n"), StartOfEPS); // Now try to find a pointer to the end of the EPS stuff. pFile->read ( &EndOfEPS, 4 ); EndOfEPS += StartOfEPS; // Seek to the start of the EPS ready for the importer to read it. pFile->seekIn(StartOfEPS, ios::beg); } else { // Nope, this must be any ordinary EPS file, so go back to the start of the file pFile->seekIn(0, ios::beg); EndOfEPS = pFile->Size (); } } // Get the right spread... Spread *pSpread; if (Pos == NULL) { // Find the layer on the first page of this document... pSpread = GetFirstSpread(DestDoc); } else { // Use the spread provided pSpread = Pos->pSpread; } // Remember it ImportInfo.pSpread = pSpread; // Let the filter set up itself ready to import if (!PrepareToImport()) { // Didn't work (no memory) CleanUpAfterImport(FALSE); return FALSE; } pLayer = pSpread->FindActiveLayer(); // Get the spread's bounding rectangle and convert it to spread coords. SpreadRect = pSpread->GetPasteboardRect(); pSpread->DocCoordToSpreadCoord(&SpreadRect); // pNode is where new objects will go - they all go into a group so the user // can easily drag the imported EPS file around. They can then ungroup it afterwards // if they want. // If we're ignoring layers, load it all in as a group on the active layer; // otherwise default to adding to the active layer. NodeGroup *pEPSGroup = NULL; if (UseLayers) { EPSFlags.AddToNewLayer = FALSE; pNode = (Node *) pLayer; } else { pEPSGroup = new NodeGroup; ERRORIF(pEPSGroup == NULL, _R(IDT_EPS_NOMEMORY), FALSE); pNode = pEPSGroup; } // Scaling factor is 1000 for ArtWorks EPS files Stack.SetCoordScaleFactor(EPSScaleFactor); if (Pos == NULL) { // For now, position EPS objects on 1st page of spread 1 Page *pPage = pSpread->FindFirstPageInSpread(); ERROR3IF(pPage == NULL, "Spread has no pages"); // Use bottom left of page as origin if (pPage) { DocRect PageRect = pPage->GetPageRect(); Stack.SetCoordOrigin(PageRect.lo); } } else { // Use position provided Stack.SetCoordOrigin(Pos->Position); } // No complex path initially EPSFlags.ComplexPath = 0;//FALSE; // Try to open the file // The file is already open when it is passed in // Find out the size of the file, in bytes. INT32 filesize = EPSFile->Size(); if (filesize == -1) { TRACEUSER("JustinF", _T("Couldn't take size of the EPS file\n")); return FALSE; } // Initialise LastProgressUpdate = 0; // Set the progress indicator, this next bit might take a while. //String_64 ImportMessage(ImportMsgID); String_64 ImportMessage = GetImportProgressString(EPSFile, GetImportMsgID()); BeginSlowJob(filesize, TRUE, &ImportMessage); // Process each token in the file do { GetToken(); } while (HandleToken() && (!EPSFlags.EPSErrorEncountered) && (!EPSFile->eof())); // File loaded ok if we reached eof (and no outstanding groups left) BOOL Ok = (GroupNesting == 0) && (MaskedGroupNesting == 0) && (EPSFile->eof() && !EPSFile->fail()); #if _DEBUG Stack.Dump(this); #endif // If the load failed for any reason, delete the subtree we have created; otherwise // graft it into the tree (currently bodged to be on the first layer of the second page. if (!Ok) { // Failed - delete the sub-tree we just created, and report error. if (UseLayers) { // Clean up if we need to if (pNode!=NULL) { // If UseLayers is set, the pNode usually refers to a node in the tree // So the last thing we want to do is start // deleting it. The operation unwinding will do that for us. //pNode->CascadeDelete(); //delete pNode; pNode = NULL; } } else { // Clean up the group if we need to if (pEPSGroup!=NULL) { pEPSGroup->CascadeDelete(); //delete pEPSGroup; pEPSGroup = NULL; } } // Tidy up after ourselves. // NB. we must delete the imported group before telling the document components, // otherwise, eg. the colour list component will try to delete indexed colours // when they are still referenced by nodes in the group. CleanUpAfterImport(FALSE); // Detach document. TheDocument = NULL; Op->FailAndExecute(); // All work has been completed. EndSlowJob(); #if !defined(EXCLUDE_FROM_RALPH) DialogBarOp::UpdateStateOfAllBars(); #endif return FALSE; } // Try to attach any outstanding nodes BOOL Success = AttachAllNodes(); // (Chris G 17/11/00) - the following fix was removed, as it was causing a crash when // new clipart indices were created from old .xar files (by selecting a new folder with // at least one old (CorelXara 2) .xar file in the file dialog which appears after the // "DiscClipart" button is pressed (in the Clipart gallery)). Since the old bug report // numbers are no longer valid (this was added to fix bug nos. 1469 and 3262 in 1995 - // checked into sourcesafe database "VSS", epsfiltr.cpp, checkin 82/83 by Ben Summers // on 8/7/95 and 12/7/95) it was impossible to find out why this fix was added, but it // appears to attempt to avoid name-clashing of the layers. // This fix: Sourcesafe DB: "Camelot2000", epsfiltr.cpp, ChrisG, no. 28, 17/11/00 // nasty thing: // if importing with layers, and there's only one layer, make it pretend it was // importing without layers. ie, completely ignore the user's preferences if we // just happen to feel like it. // Tidy up after ourselves CleanUpAfterImport(TRUE); // Detach document. TheDocument = NULL; // End job and inform caller of the success or failure of the venture. EndSlowJob(); #if !defined(EXCLUDE_FROM_RALPH) DialogBarOp::UpdateStateOfAllBars(); #endif return Success; } /******************************************************************************************** > BOOL EPSFilter::HandleToken() Author: Tim_Browse (Xara Group Ltd) Created: 25/02/94 Inputs: - Outputs: - Returns: TRUE if the token was processed ok, FALSE if not. Purpose: 'Handles' the current EPS token - if it's an EOL or EOF token, it updates the progress display, otherwise it just calls ProcessToken(); Errors: Syntax error in EPS file, User interrupted with Ctrl-C, Out of memory. SeeAlso: EPSFilter::GetToken; EPSFilter::ProcessToken ********************************************************************************************/ BOOL EPSFilter::HandleToken() { // BOOL Error = FALSE; // At the end of a line or the file? if ((Token != EPSC_EOL) && (Token != EPSC_EOF)) { // No, so try to process this token - if it fails, we have encountered an // error, so quit the loop. if (!ProcessToken()) return FALSE; } else { // Update the progress counter at the end of every 2kbytes, and break out of // the loop if the user has hit Crtl-C etc. INT32 CharsRead = EPSFile->GetCharsRead(); if (CharsRead > (LastProgressUpdate + 2048)) { if (!ContinueSlowJob(CharsRead)) { // Abort operation - make sure nodes are deleted and not added to the tree. ERROR(_R(IDT_IMPORT_USERABORT), FALSE); } // Update the progress counter LastProgressUpdate = CharsRead; } } // All ok return TRUE; } /******************************************************************************************** > void EPSFilter::DecodeToken() Author: Tim_Browse (Xara Group Ltd) Created: 28/10/93 Purpose: Decode the PostScript token that is contained in the 'TokenBuf' buffer. This is not used for strings, comments, or array brackets, but is used to detect tokens which are floating point numbers, integers, EPS keywords, or PostScript 'names' (e.g. font names). In other words, any token which cannot be identified solely by its first character. SeeAlso: EPSFilter::GetToken Scope: Private ********************************************************************************************/ void EPSFilter::DecodeToken() { // // Determine what we just read into TokenBuf. // ENSURE(camStrcmp(TokenBuf,_T("}bd")) != 0, "Found a }bd token!"); // Not interested in comments, unless it's actually a strange token if (Token == EPSC_Comment) { LookUpToken(); return; } // i is the index if the character we are currently examining. INT32 i = 0; // FoundDot is TRUE if we have already encountered a '.' character in this token. BOOL FoundDot = FALSE; // Number is TRUE if we haven't found any characters (or combination of characters, // eg. a token with two '.' characters in it) which mean that this token cannot be a // number (either floating point ot integer). BOOL Number = TRUE; // FoundFloatingPoint is TRUE if we have found a number that cannot be represented // by an integer or fixed point, i.e. it is in scientific notation (with exponent), or // the number has more than 3dp of precision. BOOL FoundFloatingPoint = FALSE; INT32 Precision = 0; // Try a floating point number first... while (TokenBuf[i] != 0) { TCHAR c = TokenBuf[i]; if (c == '.') { if (FoundDot) { // Found more than one dot - not a number Number = FALSE; break; } else { // Found the first '.' in this token FoundDot = TRUE; } } else { if (FoundDot) { // If it's a number, check the precision needed to represent it if (isdigit(c)) { Precision++; if (Precision > 3) FoundFloatingPoint = TRUE; } else { // Check for illegal characters if ((c == 'e') || (c == 'E')) { // Found an exponent FoundFloatingPoint = TRUE; } else if ((c != '-') && (c != '+')) { // Illegal character - e.g. alphanumeric, etc Number = FALSE; break; } } } else if (!isdigit(c) && (c != '-') && (c != '+')) { // Illegal character - e.g. alphanumeric, etc Number = FALSE; break; } } i++; } if (Number) { // It's a number - is it integer, fixed point, or floating point? if (FoundFloatingPoint) { // Floating point Token = EPSC_Double; TokenData.Double=0.0; camSscanf(TokenBuf,_T("%lf"),&TokenData.Double); } else if (FoundDot) { // Fixed point Token = EPSC_FixedPoint; TokenData.FixedPoint.FromAscii(TokenBuf); } else { // Integer Token = EPSC_Integer; TokenData.Long=0; camSscanf(TokenBuf,_T("%d"),&TokenData.Long); } return; // All done - found a number } // Try to find this token in the set(s) of tokens understood by this filter. LookUpToken(); } /******************************************************************************************** > void EPSFilter::LookUpToken() Author: Tim_Browse (Xara Group Ltd) Created: 26/05/94 Purpose: Determines if the current token is one of the standard Illustrator tokens. The 'Token' variable is set accordingly. If the token is not recognised, it is assumed to be a PostScript 'name'. SeeAlso: EPSFilter::DecodeToken; EPSFilter::ProcessToken ********************************************************************************************/ void EPSFilter::LookUpToken() { // Not interested in comments if (Token == EPSC_Comment) return; // Check to see if it is a keyword - cycle through the array of keyword names and // compare against our token (could use a hash table?) INT32 i = 0; while (Commands[i].Cmd != EPSC_Invalid) { if (camStrcmp(TokenBuf, Commands[i].CmdStr) == 0) { // Found the token - set the token variable and exit. Token = Commands[i].Cmd; return; } // Try next command i++; } // Keyword not found - must be a PostScript 'name' Token = EPSC_Name; } /******************************************************************************************** > BOOL EPSFilter::GetToken() Author: Tim_Browse (Xara Group Ltd) Created: 28/10/93 Returns: TRUE if a token was obtained ok, FALSE if not. Purpose: Extract a token from the input stream. The Token variable holds the type of the token, and TokenBuf holds the characters that make up the token (be it a command, a string. a number, or whatever). Errors: Fails if EOF detected while parsing a token (e.g. in the middle of a PostScript string token). Scope: Private ********************************************************************************************/ BOOL EPSFilter::GetToken() { // Initialise the token type. Token = EPSC_Invalid; // Ensure that the file doesn't run on past the end of the EPS segment. if ( !EPSFile->IsCompressionSet () && EPSFile->tell () > EndOfEPS ) { // Skip to the end of the file. EPSFile->seekIn ( 0, ios::end ); } if (!EPSFile->GetToken()) return FALSE; // No effect if at EOF switch (EPSFile->GetTokenType()) { case TOKEN_EOF: return TRUE; // EOF does not count as an error - TOKEN_EOF is a valid token case TOKEN_EOL: Token = EPSC_EOL; return TRUE; case TOKEN_STRING: Token = EPSC_String; return TRUE; case TOKEN_COMMENT: Token = EPSC_Comment; break; // Comments may be tokens in some EPS formats. default: break; } ENSURE((EPSFile->GetTokenType() == TOKEN_NORMAL) || (EPSFile->GetTokenType() == TOKEN_COMMENT), "Bad token type in EPSFilter::GetToken()"); // Decode this token DecodeToken(); // Token extracted ok return TRUE; } /******************************************************************************************** > BOOL EPSFilter::GetLineToken() Author: Tim_Browse (Xara Group Ltd) Created: 28/10/93 Returns: TRUE if a token was obtained ok, FALSE if not. Purpose: Extract a token from the input stream in a line-based manner. The Token variable holds the type of the token, and TokenBuf holds the characters that make up the token (be it a command, a string. a number, or whatever). Errors: Fails if EOF detected while parsing a token (e.g. in the middle of a PostScript string token). Scope: Private SeeAlso: EPSFilter::GetToken; CCLexFile::GetLineToken ********************************************************************************************/ BOOL EPSFilter::GetLineToken() { Token = EPSC_Invalid; // Ensure that the file doesn't run on past the end of the EPS segment. if ( !EPSFile->IsCompressionSet () && EPSFile->tell () > EndOfEPS ) { // Skip to the end of the file. EPSFile->seekIn ( 0, ios::end ); } if (!EPSFile->GetLineToken()) return FALSE; // No effect if at EOF switch (EPSFile->GetTokenType()) { case TOKEN_EOF: return TRUE; // EOF does not count as an error - TOKEN_EOF is a valid token case TOKEN_EOL: Token = EPSC_EOL; return TRUE; case TOKEN_STRING: Token = EPSC_String; return TRUE; case TOKEN_COMMENT: // Give filters a chance to claim comments as tokens for some strange EPS // formats (e.g. Illustrator 5) Token = EPSC_Comment; DecodeToken(); return TRUE; default: break; } ENSURE((EPSFile->GetTokenType() == TOKEN_NORMAL) || (EPSFile->GetTokenType() == TOKEN_LINE), "Bad token type in EPSFilter::GetLineToken()"); // Decode this token DecodeToken(); // Token extracted ok return TRUE; } /******************************************************************************************** > BOOL EPSFilter::AddNewNode(Node *pNewNode) Author: Tim_Browse (Xara Group Ltd) Created: 16/08/94 Inputs: pNewNode - the new node to add Returns: TRUE if it worked, FALSE if not. Purpose: When a new node is created, this function puts it into the tree in the correct place. It may add it into a group, or a new layer which is currently being constructed, or onto an existing layer. Errors: Out of memory SeeAlso: EPSFilter::UseLayer ********************************************************************************************/ BOOL EPSFilter::AddNewNode(Node *pNewNode) { if ((EPSFlags.AddToNewLayer) || (!UseLayers)) { // Simple case - just add to the new layer node pNewNode->AttachNode(pNode, LASTCHILD); if (pNewNode->IsBounded()) { // Update the bounds for this object NodeRenderableBounded *pBounded = (NodeRenderableBounded *) pNewNode; pBounded->InvalidateBoundingRect(); } } else { // Tricky case - we have to add the node to the existing layer in the // document in an undoable way. return InsertNewNode(pNewNode); } // All went ok. return TRUE; } /******************************************************************************************** > BOOL EPSFilter::InsertNewNode(Node *pNewNode) Author: Tim_Browse (Xara Group Ltd) Created: 17/08/94 Inputs: pNewNode - the node to add. Returns: TRUE if the node is added to the tree successfully; FALSE if not Purpose: This function adds a node to the tree in an undoable way. It uses DoInsertNewNode() to do this. If it fails then 'pNewNode' and all its children are deleted. It adds the node as a child of the node pointed to by 'pNode'. Errors: Out of memory SeeAlso: UndoableOperation::DoInsertNewNode ********************************************************************************************/ BOOL EPSFilter::InsertNewNode(Node *pNewNode) { // We have to add a NodeRenderableBounded. ENSURE(pNewNode->IS_KIND_OF(NodeRenderableBounded), "Trying to add a non-bounded node while importing!"); NodeRenderableBounded *pNodeBounded = (NodeRenderableBounded *) pNewNode; // Add the node and invalidate the region... if (pNodeBounded == (NodeRenderableBounded *) pLayer) { // Adding a new layer - sanity check ENSURE(pNode == ImportInfo.pSpread, "Not adding layer to correct spread!"); } // Some flags to see if things worked BOOL Success = FALSE; BOOL ReadyToInsert = TRUE; // We will see if the new node is a layer. // if not we will have to check to see if there is a layer in the document if (!pNewNode->IS_KIND_OF(Layer)) { // Its not a layer, so check that there is a layer (and try and make one if not) if (!MakeSureLayerExists(TheDocument)) { // There is no layer and one could not be made, so we will have to fail ReadyToInsert = FALSE; } else { // There is a layer in the document, so change the insertion node for the // filter if we need to. if (pNode==NULL) { // Find the spread and the active layer on it Spread* pSpread = ImportInfo.pSpread; if (pSpread!=NULL) pNode = (Node*) pSpread->FindActiveLayer(); } } } // If we passed the layer test, then we can carry on and try and insert the new nodes if (ReadyToInsert) { // We don't invalidate the region because we do that at the end when the import // has finished. if (UseLayers) { // Importing with layers - don't select the imported objects. Success = ImportInfo.pOp->DoInsertNewNode(pNodeBounded, pNode, LASTCHILD, FALSE, FALSE, FALSE); // Update the range pointers if (pFirstNodeInRange == NULL) pFirstNodeInRange = pNodeBounded; // Only update if the node is a direct child of a layer, because node ranges // should be held in terms of pointers to children of layers, otherwise the // end of the range will not be found and will spill over. if (pNodeBounded->FindParent()->IS_KIND_OF(Layer)) pLastNodeInRange = pNodeBounded; } else { // This is without layers, i.e. as a group, so we want to add it to the correct // place on the active layer, so we use this form of DoInsertNewNode which uses // the 'InsertionNode' thingy to put it in the right place. Success = ImportInfo.pOp->DoInsertNewNode(pNodeBounded, ImportInfo.pSpread, FALSE); } } // If it worked, try to optimise attributes - this currently just factors out attributes // common to compound objects. if ((!Success) || (!pNewNode->OptimiseAttributes())) { // It didn't work - delete the sub-tree we've created pNewNode->CascadeDelete(); delete pNewNode; // Return failure ImportInfo.pOp->FailAndExecute(); return FALSE; } // It worked! return TRUE; } /******************************************************************************************** > BOOL EPSFilter::AttachAllNodes() Author: Tim_Browse (Xara Group Ltd) Created: 16/08/94 Returns: TRUE if it worked ok; FALSE if not. Purpose: Called at the end of an import - attaches any nodes to the tree that have not yet been attached. e.g. a new layer node, or a group node if layers are being ignored. Errors: Out of memory SeeAlso: EPSFilter::AddNewNode ********************************************************************************************/ BOOL EPSFilter::AttachAllNodes() { BOOL Success = TRUE; NodeGroup *pGroup=NULL; if (UseLayers) { // Check to see if there is an outstanding layer node to add... if (EPSFlags.AddToNewLayer) { // Yes - add it... // We're adding the layer to the spread now EPSFlags.AddToNewLayer = FALSE; Node *pLayer = pNode; pNode = ImportInfo.pSpread; // Do it. Success = AddNewNode(pLayer); // Remember it if (Success) ImportRange.AddRange(pLayer, pLayer); } else if (pFirstNodeInRange != NULL) { // We have just finished adding a range of nodes to an existing layer so let's // remember the nodes. ImportRange.AddRange(pFirstNodeInRange, pLastNodeInRange); pFirstNodeInRange = NULL; pLastNodeInRange = NULL; } } else { // No layers - always imported as a group, so add it into the tree. pGroup = (NodeGroup *) pNode; pNode = (Node *) pLayer; Success = InsertNewNode(pGroup); } if (!Success) return FALSE; // we need the bounding rect to do this (not the one with attributes) DocRect BoundsRect; if (UseLayers) BoundsRect = ImportRange.GetBoundingRect(); else BoundsRect = pGroup->GetBoundingRect(TRUE); // Translate objects to the drag and drop position if necessary... Coord Offset; if (GetDragAndDropTranslation(ImportInfo.Pos, BoundsRect, &Offset)) { Trans2DMatrix Xlate(Offset.x, Offset.y); if (UseLayers) { // Transform the range of nodes and then delete the range info. ImportRange.Transform(Xlate); // Transform the bounding rect too because we use it to invalidate the region. BoundsRect.Translate(Offset.x, Offset.y); } else { // Just transform the group node pGroup->Transform(Xlate); } } // Invalidate the region if (UseLayers) { // Importing with layers - invalidate the bounds of all the inserted nodes Success = ImportInfo.pOp->DoInvalidateRegion(ImportInfo.pSpread, BoundsRect); } else { // When importing as a group, we select the group node at the end, so we need to // invalidate the blob bounding rect to get the redraw correct. Success = ImportInfo.pOp->DoInvalidateNodeRegion(pGroup, TRUE, FALSE); } if (!Success) { // Need to add tidy up code here. return FALSE; } // Make sure the node insertion position is in the correct place TheDocument->ResetInsertionPosition(); return Success; } /******************************************************************************************** > BOOL EPSFilter::UseLayer ( String_256& LayerName, BOOL GuideLayer = FALSE ) Author: Graeme_Sutherland (Xara Group Ltd) Created: 17/4/00 Inputs: LayerName - The name of the new layer. GuideLayer - TRUE to create a guide layer, FALSE to create a standard layer. Returns: TRUE - Success. FALSE - Failure - the layer wasn't created. Purpose: This is a wrapper around the new AddLayer method, which now returns a pointer to the newly created layer, which is where all the work is done. I've done this to preserve the original interface. SeeAlso: EPSFilter::AddLayer () ********************************************************************************************/ BOOL EPSFilter::UseLayer ( String_256& LayerName, BOOL GuideLayer ) { // Create a new layer using the layer name information. if ( AddLayer ( LayerName, GuideLayer ) != NULL ) { // Things went OK. return TRUE; } else { // A problem occurred. return FALSE; } } /******************************************************************************************** > Layer* EPSFilter::AddLayer ( String_256& LayerName, BOOL GuideLayer ) Author: Graeme_Sutherland (Xara Group Ltd) (based on Tim's code) Created: 17/4/00 (originally 16/08/94) Inputs: LayerName - The name of the new layer. GuideLayer - TRUE to create a guide layer, FALSE to create a standard layer. Returns: pLayer - A pointer to the newly creaded layer. Purpose: Given a layer name, construct a new layer, or find an existing layer with the same name, and cause new nodes to be added to the resulting layer. Errors: An error is thrown if the layer is not created. SeeAlso: EPSFilter::AddNewNode ********************************************************************************************/ Layer* EPSFilter::AddLayer ( String_256& LayerName, BOOL GuideLayer ) { // Have we currently got a pending new layer to add to the tree? if (EPSFlags.AddToNewLayer) { // Yes - add the new layer to the tree. EPSFlags.AddToNewLayer = FALSE; pNode = ImportInfo.pSpread; if (!InsertNewNode((Node *) pLayer)) return FALSE; // Remember it ImportRange.AddRange(pLayer, pLayer); } else if (pFirstNodeInRange != NULL) { // We have just finished adding a range of nodes to an existing layer so let's // remember the nodes. ImportRange.AddRange(pFirstNodeInRange, pLastNodeInRange); pFirstNodeInRange = NULL; pLastNodeInRange = NULL; } // Find out if we need a new layer name, because there is already one with this name. String_256 NewLayerName = LayerName; MakeLayerNameUnique(NewLayerName, ImportInfo.pSpread); // Make a new layer using this name. if (GuideLayer) { #if !defined(EXCLUDE_FROM_RALPH) // WEBSTER - markn 15/1/97 // CreateGuideLayer() used to be part of OpGuideline class, now part of Layer class pLayer = Layer::CreateGuideLayer(); #else ERROR3("EPSFilter::UseLayer GuideLayer == TRUE - whoop whoop ! BAD !"); #endif } else pLayer = new Layer; ERRORIF(pLayer == NULL, _R(IDT_EPS_NOMEMORY), FALSE); pLayer->SetLayerID(NewLayerName); EPSFlags.AddToNewLayer = TRUE; // Make it the new destination node for all new nodes. pNode = (Node *) pLayer; // Pass the layer pointer out. return pLayer; } /******************************************************************************************** > void EPSFilter::MakeLayerNameUnique(String_256& LayerName, Spread *pSpread) Author: Tim_Browse (Xara Group Ltd) Created: 24/01/95 Inputs: - Outputs: - Returns: - Purpose: Given a layer name, ensure it is unique within the spread. If a clash occurs then the following layer names are tried until a unique one is found. (Assuming layer name is "Foreground" in this example) Imported Foreground Foreground 2 Foreground 3 Foreground 4 . . . Errors: - SeeAlso: EPSFilter::UseLayer ********************************************************************************************/ void EPSFilter::MakeLayerNameUnique(String_256& LayerName, Spread *pSpread) { BOOL TriedImported = FALSE; UINT32 Number = 2; String_256 NewName = LayerName; Layer *pFirstLayer = pSpread->FindFirstLayer(); Layer *pLayer = pFirstLayer; while (pLayer != NULL) { // Get info on this layer. LayerStatus Status = pLayer->GetLayerStatus(); // Does this layer have the same name as the proposed new one? if (Status.StringLayerID == NewName) { // Yes - try a new name if (TriedImported) { // Try adding a number instead of "Imported" TCHAR NumBuf[10]; camSprintf(NumBuf, TEXT(" %d"), Number); NewName = LayerName; NewName += NumBuf; // Make sure we try a different number next time Number++; } else { // Try prefixing the name with "Imported" NewName.Load(_R(IDS_K_EPSFILTER_IMPORTED)); NewName += LayerName; TriedImported = TRUE; } // Start from first layer again pLayer = pFirstLayer; } else { // Try next layer pLayer = pLayer->FindNextLayer(); } } // Found an acceptable name - use it LayerName = NewName; } /******************************************************************************************** > BOOL EPSFilter::StartGroup() Author: Tim_Browse (Xara Group Ltd) Created: 16/08/94 Returns: TRUE if the new group was created ok; FALSE if not. Purpose: Used when a group structure needs to be created - after this is called, all new nodes added with AddNewNode() will be added as children of this new group, until EndGroup is called. Errors: Out of memory SeeAlso: EPSFilter::EndGroup ********************************************************************************************/ BOOL EPSFilter::StartGroup() { // Make a new group node for this group NodeGroup *pGroup = new NodeGroup; ERRORIF(pGroup == NULL, _R(IDT_EPS_NOMEMORY), FALSE); // Add it into the tree if (!AddNewNode(pGroup)) return FALSE; // Make sure new objects are added as children of the node pNode = pGroup; // All ok return TRUE; } /******************************************************************************************** > BOOL EPSFilter::EndGroup(BOOL Masked = FALSE) Author: Tim_Browse (Xara Group Ltd) Created: 16/08/94 Inputs: Masked - TRUE if this was a masked group; FALSE if it was a normal one. Returns: TRUE if the group was ended ok; FALSE if not. Purpose: Used when a group has finished being constructed and we want to return to normal node positioning. The group is added to the document when this function is called (although that depending on the current filter mode, i.e. whether this is a new or existing layer, it may not be added directly to the documnent tree straight away - it may be deferred until the next layer is found, or the import has ended). Errors: Out of memory SeeAlso: EPSFilter::StartGroup ********************************************************************************************/ BOOL EPSFilter::EndGroup(BOOL Masked) { // Sanity check ENSURE(pNode->IsKindOf(CC_RUNTIME_CLASS(NodeGroup)), "No group in EPSFilter::EndGroup"); // Finishing the handling of the group, next we're not in a group anymore. // EPS_Group = FALSE; if(Masked) { // call the derived class if(!MaskedGroupEnding()) return FALSE; // error... } // Remember the group node Node *pGroup = pNode; // Get the parent of the group node, and use that to add new objects to pNode = pNode->FindParent(); ENSURE(pNode != NULL, "Group has no parent in ProcessGroup()"); // Check for bad groups, i.e. groups with no children Node *pChild = pGroup->FindFirstChild(); if (pChild == NULL) { // if pFirstNodeInRange is this node, set it to zero if(pFirstNodeInRange == pGroup) pFirstNodeInRange = 0; // No children! Remove this group from the tree - we must hide it like this // and not delete it because it may already have been put in the undo buffer // via DoInsertNewNode(), and so we will end up with a pointer to a deleted item. // Hence we simply Hide it now and it all comes out in the wash if the user does // undo/redo. if (!ImportInfo.pOp->DoHideNode(pGroup, TRUE)) return FALSE; } else { // it was OK - validate it... if(!ValidateGroup(pGroup)) return FALSE; } // Keep group nesting up to date, and restore clipping region if necessary if (Masked) { MaskedGroupNesting--; if (!ClipRegion.RestoreContext()) // Error in EPS return FALSE; } else GroupNesting--; return TRUE; } /******************************************************************************************** > BOOL EPSFilter::ValidateGroup(Node *pGroup) Author: Ben_Summers (Xara Group Ltd) Created: 02/06/95 Inputs: pGroup - the group to validate Returns: error flag Purpose: Validate a group. Can be overridden to, for example, ensure that groups have at least two children. SeeAlso: FreeHandEPSFilter::ValidateGroup ********************************************************************************************/ BOOL EPSFilter::ValidateGroup(Node *pGroup) { return TRUE; } /******************************************************************************************** > BOOL EPSFilter::MaskedGroupEnding() Author: Ben_Summers (Xara Group Ltd) Created: 02/06/95 Inputs: - Returns: error in EPS flag Purpose: Function to call when a masked group ends. Gives a chance to restore graphic states and the like. SeeAlso: FreeHandEPSFilter::MaskedGroupEnding ********************************************************************************************/ BOOL EPSFilter::MaskedGroupEnding() { EPS_Group = FALSE; return TRUE; } /******************************************************************************************** > BOOL EPSFilter::AddAttributes(NodeRenderableBounded *pNode, BOOL Stroked, BOOL Filled) Author: Tim_Browse (Xara Group Ltd) Created: 28/10/93 Inputs: pNode - the bounded renderable node to add atributes to. Stroked - TRUE if the object to add attributes to should be stroked, FALSE if not. Stroked - TRUE if the object to add attributes to should be filled, FALSE if not. Returns: TRUE if the attributes were added ok, FALSE if not. Purpose: Add attributes to an object being constructed. The attributes are optimised so that if they are the same as the document's default attributes, they are not applied. Errors: Fails (returns FALSE) if not enough memory to add attributes. Scope: Private ********************************************************************************************/ BOOL EPSFilter::AddAttributes(NodeRenderableBounded *pNode, BOOL Stroked, BOOL Filled) { DocColour OldCol; // If not stroked, temporarily set the ignore bit on the line colour attribute. if (!Stroked) { StrokeColourAttribute *pAttr = (StrokeColourAttribute *) CurrentAttrs[ATTR_STROKECOLOUR].pAttr; OldCol = pAttr->Colour; DocColour trans(COLOUR_TRANS); if (!SetLineColour(trans)) return FALSE; } // If not filled, then set the ignore bit on the fill attribute. if (!Filled) //pPath->InkPath.IsFilled) { CurrentAttrs[ATTR_FILLGEOMETRY].Ignore = TRUE; } // Add attributes to the path, if they are different from the default... BOOL Result = AttributeManager::ApplyBasedOnDefaults(pNode, CurrentAttrs); // If not stroked, reinstall old line colour. if (!Stroked) // This can't fail. (tempting fate or wot!) SetLineColour(OldCol); // Enable the fill attribute again CurrentAttrs[ATTR_FILLGEOMETRY].Ignore = FALSE; // Re-enable path-filling, in case it was disabled for this object SetPathFilled(TRUE); // Return success or failure return Result; } /******************************************************************************************** > void EPSFilter::GetEPSColour(DocColour *DestCol, PColourCMYK *pCMYK, TintType Tint, FIXEDPOINT TintVal, String_64 *ColName) Author: Tim_Browse (Xara Group Ltd) Created: 08/08/94 Inputs: pCMYK - the colour components read from the EPS file. Tint - the type of tint used for this colour (i.e. specifies whether or not this is a named/tinted colour, and so on). TintVal - the percentage tint for a tinted colour. (ignored if no tint) ColName - the name of the colour. (may be NULL if no tint) Outputs: DestCol - the resulting DocColour, which may be a reference to an indexed colour. Purpose: Used when an EPS colour definition has just been read off the stack. Provides a centralised way of interpreting the usual colour definitions and getting a valid DocColour out of it. This copes with all sorts of things such as creating an indexed colour for named colours, and even handles creation of special colours for tinted named colours. Basically, it's a fire-and-forget function :-). If all else fails, it falls back to an immediate DocColour. SeeAlso: EPSFilter::GetEPSColourRGB, ColourComponentList ********************************************************************************************/ void EPSFilter::GetEPSColour(DocColour *DestCol, PColourCMYK *pCMYK, TintType Tint, FIXEDPOINT TintVal, String_64 *ColName) { IndexedColour *pIndexed = NULL; // Set up the colour with what we have. DestCol->SetCMYKValue(pCMYK); // Nasty hack time: // When saving immediate colours in a context where only named colours are expected, // e.g. when saving a grad fill that has an immediate colour, we use the name defined // by the constant 'ImmediateColourFudgeyBodgeName' (which is defined in epsfiltr.h) // to indicate this, so when we encounter this name, we don't treat it as an indexed // (named) colour - we treat it as an immediate colour). -- Tim BOOL IsNamed = ColName ? (ColName->CompareTo(ImmediateColourFudgeyBodgeName) != 0) : FALSE; if (((Tint == TINT_ILLUSTRATOR) || (Tint == TINT_COREL)) && IsNamed) { if (ColName->Length() > 0) { // Named colour - try to find it. pIndexed = pColours->FindNamedColour((TCHAR *) (*ColName), DestCol, (TintVal * 100) / FixedPointScale); if (!pIndexed) { TRACEUSER("Tim", _T("Unable to find named colour: %s\n"), (TCHAR *) (*ColName)); } } else { // A zero-length colour name is how Camelot currently represents // 'no colour' in grad fill colours, e.g. fade from red to no colour, i.e. // fade to 100% transparent. // In future we should have a proper named colour in Camelot that is 100% // transparent and we can use that. Until then... DocColour TransCol(COLOUR_TRANS); (*DestCol) = TransCol; } } else { // Unnamed colour - make a new indexed colour for it, or look up a previous // definition if it exists. We don't bother if the user has disabled loading // of unnamed colours. // // NB. The TRUE value for the 'Strict' parameter - we may well have defined // a colour called this already but with different values, so we always want // to check the colour definition as well as the name. if (Filter::AddUnnamedColours) { pIndexed = pColours->FindNamedColour(String_64(_R(IDS_K_EPSFILTER_UNNAMED)), DestCol, 100, TRUE); if (!pIndexed) { TRACEUSER("Tim", _T("Unable to construct an unnamed colour\n")); } } } if (pIndexed != NULL) { // Found the indexed colour, so use it. DestCol->MakeRefToIndexedColour(pIndexed); } // If no indexed colour made, we're stuck with an immediate colour... } /******************************************************************************************** > void EPSFilter::GetEPSColourRGB (DocColour *DestCol, INT32 red, INT32 green, INT32 blue, TintType Tint, FIXEDPOINT TintVal, String_64 *ColName) Author: Chris_Gallimore (Xara Group Ltd) Created: 12/12/00 Inputs: red, green, blue - the RGB colour components read from the EPS file. NOTE: Tints aren't currently supported by the RGB import - all they really do is determine whether the colour is named. Tint - the type of tint used for this colour (i.e. specifies whether or not this is a named/tinted colour, and so on). TintVal - the percentage tint for a tinted colour. (ignored if no tint) ColName - the name of the colour. (may be NULL if no tint) Outputs: DestCol - the resulting DocColour, which may be a reference to an indexed colour. Purpose: Identical to EPSFilter::GetEPSColour, except that it deals with RGB colours instead of CMYK ones. SeeAlso: EPSFilter::GetEPSColour, ColourComponentList ********************************************************************************************/ void EPSFilter::GetEPSColourRGB (DocColour *DestCol, INT32 red, INT32 green, INT32 blue, TintType Tint, FIXEDPOINT TintVal, String_64 *ColName) { IndexedColour *pIndexed = NULL; // Set up the colour with what we have. DestCol->SetRGBValue(red, green, blue); // Nasty hack time: // When saving immediate colours in a context where only named colours are expected, // e.g. when saving a grad fill that has an immediate colour, we use the name defined // by the constant 'ImmediateColourFudgeyBodgeName' (which is defined in epsfiltr.h) // to indicate this, so when we encounter this name, we don't treat it as an indexed // (named) colour - we treat it as an immediate colour). -- Tim BOOL IsNamed = ColName ? (ColName->CompareTo(ImmediateColourFudgeyBodgeName) != 0) : FALSE; if (((Tint == TINT_ILLUSTRATOR) || (Tint == TINT_COREL)) && IsNamed) { if (ColName->Length() > 0) { // Named colour - try to find it. pIndexed = pColours->FindNamedColour((TCHAR *) (*ColName), DestCol, (TintVal * 100) / FixedPointScale); } else { // A zero-length colour name is how Camelot currently represents // 'no colour' in grad fill colours, e.g. fade from red to no colour, i.e. // fade to 100% transparent. // In future we should have a proper named colour in Camelot that is 100% // transparent and we can use that. Until then... DocColour TransCol(COLOUR_TRANS); (*DestCol) = TransCol; } } else { // Unnamed colour - make a new indexed colour for it, or look up a previous // definition if it exists. We don't bother if the user has disabled loading // of unnamed colours. // // NB. The TRUE value for the 'Strict' parameter - we may well have defined // a colour called this already but with different values, so we always want // to check the colour definition as well as the name. if (Filter::AddUnnamedColours) { pIndexed = pColours->FindNamedColour(String_64(_R(IDS_K_EPSFILTER_UNNAMED)), DestCol, 100, TRUE); } } if (pIndexed != NULL) { // Found the indexed colour, so use it. DestCol->MakeRefToIndexedColour(pIndexed); } // If no indexed colour made, we're stuck with an immediate colour... } /******************************************************************************************** > BOOL EPSFilter::ProcessToken() Author: Tim_Browse (Xara Group Ltd) Created: 28/10/93 Returns: TRUE if the token was processed ok, FALSE if an error occured. Purpose: Examines the current token and performs whatever actions are necessary to process its meaning. Operands are simply pushed onto the EPS stack, and keywords are either acted upon, or ignored if they describe something that Camelot does not yet support (in the latter case, the operands of the keyword, if any, are simply discarded from the stack). Errors: Fails (returns FALSE) if a syntax/error is detected in the EPS file, or if there is insufficient memory to complete the operation. SeeAlso: EPSFilter::GetToken Scope: Private ********************************************************************************************/ BOOL EPSFilter::ProcessToken() { // Variables used to extract operands from the stack DocCoord Coords[3]; PColourCMYK Col; DocColour DocCol; double Double; DocRect BoundingRect; PathFlags Flags; TintType Tint = TINT_NONE; FIXEDPOINT TintVal; LineCapType LineCap; JointType JoinType; INT32 ArraySize; INT32 Array[8]; DashRec Dash; INT32 Long; String_64 ColName; // Decode the command, and execute it... switch (Token) { case EPSC_showpage: // No action required break; case EPSC_end: // No action required break; // Graphics state operators case EPSC_A: TRACE(_T("Ignoring graphics state token\n")); if (!Stack.Discard()) goto EPSError; break; case EPSC_i: TRACE(_T("Ignoring token i\n")); if (!Stack.Discard()) goto EPSError; break; case EPSC_D: TRACE(_T("Ignoring token D\n")); if (!Stack.Discard()) goto EPSError; break; case EPSC_M: // Set current mitre limit (defined in points) if (Stack.Pop(&Double)) { // Scale mitre limit from points to millipoints, and remember for future // objects. if (!SetMitreLimit((MILLIPOINT) (Double * EPSScaleFactor))) goto NoMemory; } else goto EPSError; break; // Group related procedures // Start of group case EPSC_u: GroupNesting++; return ProcessGroup(); // End of group case EPSC_U: // We're not in a group at the moment - syntax error in EPS goto EPSError; // Colour operators case EPSC_g: if (Stack.PopGrayScale(&Col)) { // Remember this fill colour for future objects DocCol.SetCMYKValue(&Col); if (!SetFillColour(DocCol)) goto NoMemory; } else // Invalid colour operands goto EPSError; break; case EPSC_G: if (Stack.PopGrayScale(&Col)) { // Remember this line colour for future objects DocCol.SetCMYKValue(&Col); if (!SetLineColour(DocCol)) goto NoMemory; } else // Invalid colour operands goto EPSError; break; case EPSC_p: case EPSC_P: // pattern TRACE(_T("Ignoring token p(P)\n")); if ( !Stack.DiscardArray() || !Stack.Discard(10) ) goto EPSError; break; case EPSC_x: case EPSC_X: Tint = TINT_ILLUSTRATOR; case EPSC_k: case EPSC_K: // Set current line/fill colour (CMYK) if (Stack.PopColour(&Col, Tint, &TintVal, &ColName)) { GetEPSColour(&DocCol, &Col, Tint, TintVal, &ColName); // Remember this colour for future objects if ((Token == EPSC_x) || (Token == EPSC_k)) { if (!SetFillColour(DocCol)) goto NoMemory; } else { if (!SetLineColour(DocCol)) goto NoMemory; } } else // Invalid colour operands goto EPSError; break; // Overprint related procedures case EPSC_O: TRACE(_T("Ignoring token O\n")); if (!Stack.Discard()) goto EPSError; break; case EPSC_R: TRACE(_T("Ignoring token R\n")); if (!Stack.Discard()) goto EPSError; break; case EPSC_w: // Set current line width (defined in points) if (Stack.Pop(&Double)) { // Scale line width from points to millipoints, and remember for future // objects. if (!SetLineWidth((MILLIPOINT) (Double * EPSScaleFactor))) goto NoMemory; } else goto EPSError; break; case EPSC_j: // Line join style if (!Stack.Pop(&Long)) goto EPSError; // Decode line join style switch (Long) { case 0: JoinType = MitreJoin; break; case 1: JoinType = RoundJoin; break; case 2: JoinType = BevelledJoin; break; // Unknown join style. default: goto EPSError; } // Try to set this line join style if (!SetJoinType(JoinType)) goto NoMemory; break; case EPSC_J: // Line cap style if (!Stack.Pop(&Long)) goto EPSError; // Decode line cap style switch (Long) { case 0: LineCap = LineCapButt; break; case 1: LineCap = LineCapRound; break; case 2: LineCap = LineCapSquare; break; // Unknown cap style. default: goto EPSError; } // Try to set this line cap style if (!SetLineCap(LineCap)) goto NoMemory; break; case EPSC_d: // Read a DashPattern if (!Stack.Pop(&Double)) goto EPSError; TRACEUSER( "Will", _T("Importing dash pattern\n")); // Start offset into the Dash array Dash.DashStart = (MILLIPOINT) (Double * EPSScaleFactor); // Now get the actual dash elements ArraySize = 8; // Max number of elements if (!Stack.PopArray(Array, &ArraySize)) goto EPSError; Dash.Elements = ArraySize; Dash.ElementData = Array; // We've lost the scaling information, so we won't try to // scale this Dash Pattern. Dash.ScaleWithLineWidth = FALSE; // Now set the Dash Pattern if (!SetDashPattern(Dash)) goto NoMemory; break; // // Procedures that define a path object // // MoveTo case EPSC_m: // Get the co-ordinate from the stack if (Stack.PopCoordPair(&Coords[0])) { if ((ThePathType != PATH_DISCARD) && (ThePathType != PATH_DISCARD_STICKY)) { if ((pPath == NULL) && (pInkPath == NULL)) { // We need a new NodePath because there isn't an InkPath around to use. // Create a new NodePath object switch (ThePathType) { case PATH_NORMAL: pPath = new NodePath; break; case PATH_RECT: pPath = (NodePath *) new NodeRect; break; case PATH_ELLIPSE: pPath = (NodePath *) new NodeEllipse; break; case PATH_MANGLED: pPath = new NodePath; if (pPath) pPath->SetMangled(TRUE); break; default: ENSURE(FALSE, "Unknown path type in EPS filter!"); goto EPSError; } // No attributes on the path yet EPSFlags.NoAttributes = TRUE; if ((pPath == NULL) || (!pPath->SetUpPath())) // Not enough memory to initialise path goto NoMemory; // Position tag at start of path. pPath->InkPath.FindStartOfPath(); // Point inkpath pointer at the nodepath's path object. pInkPath = &pPath->InkPath; } else if (EPSFlags.PathIsHidden) { // The last path was handled by an 'H' or 'h' operator, so // it was not deleted, so we delete it now. // (This is so we cope with incorrect ArtWorks EPS!) pInkPath->ClearPath(); } // Error handling for "else if (EPSFlags.ComplexPath == 0)" to // go in here if necessary. The program seems to work better // without it. // Insert a moveto into the path if (!pInkPath->InsertMoveTo(Coords[0])) { // Not enough dynamic heap to insert the moveto command goto NoMemory; } } } else // Invalid number/type of coordinate operands goto EPSError; break; // LineTo case EPSC_l: case EPSC_L: // Get the co-ordinate from the stack if (Stack.PopCoordPair(&Coords[0])) { if ((ThePathType != PATH_DISCARD) && (ThePathType != PATH_DISCARD_STICKY)) { if (pInkPath == NULL) // Paths must start with a moveto goto EPSError; if (Token == EPSC_l) Flags.IsSmooth = TRUE; if (!pInkPath->InsertLineTo(Coords[0], &Flags)) // Not enough dynamic heap to insert the lineto command goto NoMemory; } } else // Invalid number/type of coordinate operands goto EPSError; break; // Curveto case EPSC_c: case EPSC_C: // Get the co-ordinate from the stack if (Stack.PopCoordPair(&Coords[2]) && Stack.PopCoordPair(&Coords[1]) && Stack.PopCoordPair(&Coords[0])) { if ((ThePathType != PATH_DISCARD) && (ThePathType != PATH_DISCARD_STICKY)) { if (pInkPath == NULL) // Paths must start with a moveto goto EPSError; if (Token == EPSC_c) Flags.IsSmooth = TRUE; if (!pInkPath->InsertCurveTo(Coords[0], Coords[1], Coords[2], &Flags)) // Not enough dynamic heap to insert the curveto command goto NoMemory; } } else // Invalid number/type of coordinate operands goto EPSError; break; // This curve token only has two coordinate pairs - the first // is implicitly the previous coordinate case EPSC_v: case EPSC_V: // Get the co-ordinate from the stack if (Stack.PopCoordPair(&Coords[2]) && Stack.PopCoordPair(&Coords[1])) { if ((ThePathType != PATH_DISCARD) && (ThePathType != PATH_DISCARD_STICKY)) { if (pInkPath == NULL) // Paths must start with a moveto goto EPSError; if (Token == EPSC_v) Flags.IsSmooth = TRUE; // Find out what the previous coordinate was: INT32 index = pInkPath->GetNumCoords(); DocCoord* pCoord = pInkPath->GetCoordArray(); Coords[0] = pCoord[index-1]; if (!pInkPath->InsertCurveTo(Coords[0], Coords[1], Coords[2], &Flags)) // Not enough dynamic heap to insert the curveto command goto NoMemory; } } else // Invalid number/type of coordinate operands goto EPSError; break; case EPSC_y: case EPSC_Y: // Get the co-ordinate from the stack if (Stack.PopCoordPair(&Coords[2]) && Stack.PopCoordPair(&Coords[0])) { if ((ThePathType != PATH_DISCARD) && (ThePathType != PATH_DISCARD_STICKY)) { if (pInkPath == NULL) // Paths must start with a moveto goto EPSError; if (Token == EPSC_y) Flags.IsSmooth = TRUE; if (!pInkPath->InsertCurveTo(Coords[0], Coords[2], Coords[2], &Flags)) // Not enough dynamic heap to insert the curveto command goto NoMemory; } } else // Invalid number/type of coordinate operands goto EPSError; break; case EPSC_S: case EPSC_b: case EPSC_B: case EPSC_f: case EPSC_F: case EPSC_s: // Process the end of path directive. if ( !ProcessEndOfPath () ) goto NoMemory; break; // Hidden path - not stroked or filled, or used up. case EPSC_h: case EPSC_H: // Mark as a hidden flag to get around ArtWorks EPS bug. EPSFlags.PathIsHidden = TRUE; // Terminate path if (Token == EPSC_h) { if (!pPath->InkPath.CloseSubPath()) // Not enough dynamic heap to insert the final element of the path goto NoMemory; // This path is closed - set the filled flag. pPath->InkPath.IsFilled = TRUE; } break; // Path is not stroked or filled, but it is used up. case EPSC_n: case EPSC_N: // Process the end of path directive. if ( !ProcessUnfilledPath () ) goto NoMemory; break; case EPSC__u: // *u if ((EPSFlags.ComplexPath != 0) && !EPS_Group) // Can't start a complex path if one is still being constructed. // Apart in a group where we're actually dealing with a clipping! goto EPSError; if(EPS_Group && (EPSFlags.ComplexPath == 0)) ClipRegion.StartComplexClipRegion(); // Set the flag so the moveto routine knows that a complex path is being // created. EPSFlags.ComplexPath++; // Default to not stroking paths. EPSFlags.StrokeComplexPath = FALSE; break; case EPSC__U: // *U if (EPSFlags.ComplexPath == 0) // Can't end a complex path if we're not currently constructing one goto EPSError; // EPSFlags.ComplexPath = FALSE; EPSFlags.ComplexPath--; if(EPSFlags.ComplexPath <= 0) { EPSFlags.ComplexPath = 0; if(EPS_Group) ClipRegion.EndComplexClipRegion(); if ((ThePathType != PATH_DISCARD) && (ThePathType != PATH_DISCARD_STICKY)) { if (pPath == NULL) // Paths must start with a moveto goto EPSError; // Make sure this path is valid if (!pPath->InkPath.EnsureValid()) { // Invalid path - throw it away delete pPath; InvalidPathFound = TRUE; } else { // First, we clip if necessary if (!ClipRegion.ClipPathToRegion(&pPath->InkPath)) // Error! goto NoMemory; // Make sure bounding box is up to date. pPath->InvalidateBoundingRect(); // Add the attributes, if we haven't done so for this path if (EPSFlags.NoAttributes) { if (!AddAttributes(pPath, EPSFlags.StrokeComplexPath, pPath->InkPath.IsFilled)) goto NoMemory; EPSFlags.NoAttributes = FALSE; } // Add this path into the tree if (!AddNewNode(pPath)) goto NoMemory; } // We've finished building this path - set to NULL so the other routines // know that we're not building a path any more. pPath = NULL; pInkPath = NULL; } } break; // Clipping operators case EPSC_q: // Starting a clipped (masked) group - save the clip context before we start. ClipRegion.SaveContext(); // And keep in mind we're clipping! EPS_Group = TRUE; // Start decoding the masked group... MaskedGroupNesting++; return (ProcessGroup()); case EPSC_Q: // We're not in a masked group at the moment - syntax error in EPS goto EPSError; case EPSC_W: // Use this path to augment the clipping region... if (pInkPath == NULL) // No path to use! goto EPSError; if (!ClipRegion.AddNewClippingPath(pInkPath)) // Error! return FALSE; break; // Text operators case EPSC_To: return ProcessText(); break; case EPSC_TO: TRACE(_T("Ignoring token TO\n")); break; case EPSC_Tp: TRACE(_T("Ignoring token Tp\n")); if (!Stack.Discard(7)) goto EPSError; break; case EPSC_TP: TRACE(_T("Ignoring token TP\n")); break; case EPSC_Tm: TRACE(_T("Ignoring token Tm\n")); if (!Stack.Discard(6)) goto EPSError; break; case EPSC_Td: TRACE(_T("Ignoring token Td\n")); if (!Stack.Discard(2)) goto EPSError; break; case EPSC_T_: TRACE(_T("Ignoring token T\n")); break; case EPSC_TR: TRACE(_T("Ignoring token TR\n")); if (!Stack.Discard(8)) goto EPSError; break; // Text Attribute operators case EPSC_Tr: case EPSC_Ta: case EPSC_Tt: case EPSC_Tw: case EPSC_Tc: case EPSC_Ts: case EPSC_Tz: case EPSC_TA: case EPSC_Tq: TRACE(_T("Ignoring text attribute token\n")); if (!Stack.Discard()) goto EPSError; break; case EPSC_Tf: case EPSC_Tl: TRACE(_T("Ignoring token Tf/Tl\n")); if (!Stack.Discard(2)) goto EPSError; break; case EPSC_TW: case EPSC_TC: case EPSC_Ti: TRACE(_T("Ignoring text token\n")); if (!Stack.Discard(3)) goto EPSError; break; // Text Body operators case EPSC_Tx: case EPSC_Tj: case EPSC_TX: TRACE(_T("Ignoring text body tokens\n")); if (!Stack.Discard()) goto EPSError; break; case EPSC_Tk: case EPSC_TK: TRACE(_T("Ignoring token Tk(K)\n")); if (!Stack.Discard(2)) goto EPSError; break; case EPSC_Tplus: case EPSC_Tminus: TRACE(_T("Ignoring token T+/T-\n")); break; // Special tokens case EPSC_Name: return Stack.Push(TokenBuf, TRUE); case EPSC_ArrayStart: case EPSC_ArrayEnd: return Stack.Push(Token); case EPSC_Slash: // Don't care about literal names break; case EPSC_Integer: return Stack.Push(TokenData.Long); case EPSC_Double: return Stack.Push(TokenData.Double); case EPSC_FixedPoint: return Stack.Push(TokenData.FixedPoint); case EPSC_String: return Stack.Push(TokenBuf); case EPSC_Comment: return ProcessComment(); default: // Token not understood goto EPSError; } // No errors encountered while parsing this token and its operands. return TRUE; // Error handlers: EPSError: HandleEPSError(); return FALSE; NoMemory: HandleNoMemory(); return FALSE; // Error - Out of memory while reading EPS file } /******************************************************************************************** > BOOL EPSFilter::ProcessEndOfPath ( void ) Author: Graeme_Sutherland (Xara Group Ltd) (From Tim's code.) Created: 08/04/94 Inputs: - Returns: TRUE - The path was terminated correctly. FALSE - Something went wrong. Purpose: Processes the end-of-path directive. ********************************************************************************************/ BOOL EPSFilter::ProcessEndOfPath ( void ) { // Not a 'hidden' path, so mark it as such. EPSFlags.PathIsHidden = FALSE; if (ThePathType == PATH_DISCARD) { // Discard this path, but try to use the next one. ThePathType = PATH_NORMAL; return TRUE; } if (ThePathType == PATH_DISCARD_STICKY) // Keep discarding paths until explicitly told not to. return TRUE; ENSURE((ThePathType == PATH_NORMAL) || (ThePathType == PATH_RECT) || (ThePathType == PATH_ELLIPSE) || (ThePathType == PATH_MANGLED), "No path in stroke!"); // If no path being constructed, probably just some strange masking going on // that we can't cope with yet, so ignore it. if (pPath == NULL) return TRUE; // Terminate path if ((Token == EPSC_s) || (Token == EPSC_b) || (Token == EPSC_f)) { if (!pPath->InkPath.CloseSubPath()) // Not enough dynamic heap to insert the final element of the path return FALSE; // This path is closed - set the filled flag. pPath->InkPath.IsFilled = TRUE; } // Make the path filled or not, depending on the EPS token if ((Token == EPSC_b) || (Token == EPSC_B) || (Token == EPSC_F) || (Token == EPSC_f)) { // Path is filled. pPath->InkPath.IsFilled = TRUE; SetPathFilled(TRUE); } else { SetPathFilled(FALSE); } // Do any extra things we need to complete the construction of // the pPath object // Do any extra things we need to complete the construction of // the pPath object BOOL InsertQuickShape = FALSE; NodeRegularShape* pQuickShape = NULL; switch (ThePathType) { case PATH_RECT: pQuickShape = new NodeRegularShape; if (pQuickShape != NULL) { if (ProcessRectangle((NodeRect*)pPath, pQuickShape)) InsertQuickShape = TRUE; else { // It's not really a rectangle after all - insert it as a path delete pQuickShape; NodeRect* OldRect = (NodeRect*)pPath; pPath = new NodePath(); if ((pPath == NULL) || !pPath->InkPath.Initialise(OldRect->InkPath.GetNumCoords())) return TRUE; pPath->InkPath.CopyPathDataFrom(&OldRect->InkPath); delete OldRect; } } else { return FALSE; } break; case PATH_ELLIPSE: pQuickShape = new NodeRegularShape; if (pQuickShape != NULL) { if (ProcessEllipse((NodeEllipse*)pPath, pQuickShape)) InsertQuickShape = TRUE; else { // It's not really an ellipse after all - insert it as a path delete pQuickShape; NodeEllipse* OldElip = (NodeEllipse*)pPath; pPath = new NodePath(); if ((pPath == NULL) || !pPath->InkPath.Initialise(OldElip->InkPath.GetNumCoords())) return FALSE; pPath->InkPath.CopyPathDataFrom(&OldElip->InkPath); delete OldElip; } } else { return FALSE; } break; default: break; } if (EPSFlags.ComplexPath == 0) { // Not a complex path, so we terminate properly... if (InsertQuickShape) { // Make sure the bbox is updated... pQuickShape->InvalidateBoundingRect(); pQuickShape->InvalidateCache(); // Add the attributes, if we haven't done so for this path if (EPSFlags.NoAttributes) { if (!AddAttributes(pQuickShape, (Token != EPSC_f) && (Token != EPSC_F), pPath->InkPath.IsFilled)) return FALSE; EPSFlags.NoAttributes = FALSE; } delete pPath; // Delete the old rect/ellipse; // Add this path into the tree if (!AddNewNode(pQuickShape)) return FALSE; } else { // Make sure this path is valid if (!pPath->InkPath.EnsureValid()) { // No - throw it away. delete pPath; InvalidPathFound = TRUE; } else { // It's valid so we finish normally. // First, we clip if necessary if (!ClipRegion.ClipPathToRegion(&pPath->InkPath)) // Error! return FALSE; // (Chris G 16/11/00) - Fix to make sure we don't attempt to write a path // that's been clipped into oblivion. i.e. has no points left. if (pPath->InkPath.EnsureValid ()) { // Make sure the bbox is updated... pPath->InvalidateBoundingRect(); // Add the attributes, if we haven't done so for this path if (EPSFlags.NoAttributes) { if (!AddAttributes(pPath, (Token != EPSC_f) && (Token != EPSC_F), pPath->InkPath.IsFilled)) return FALSE; EPSFlags.NoAttributes = FALSE; } // Add this path into the tree if (!AddNewNode(pPath)) return FALSE; } else { delete pPath; InvalidPathFound = TRUE; } } } // We've finished building this path - set to NULL so the other routines // know that we're not building a path any more. pPath = NULL; pInkPath = NULL; } else EPSFlags.StrokeComplexPath = (Token != EPSC_f) && (Token != EPSC_F); // Handle the next path normally ThePathType = PATH_NORMAL; // It worked! return TRUE; } /******************************************************************************************** > BOOL EPSFilter::ProcessUnfilledPath ( void ) Author: Graeme_Sutherland (Xara Group Ltd) (From Tim's code.) Created: 08/04/94 Inputs: - Returns: TRUE - The path was terminated correctly. FALSE - Something went wrong. Purpose: Processes the end-of-path directive of an unfilled or unstroked path. ********************************************************************************************/ BOOL EPSFilter::ProcessUnfilledPath ( void ) { // Not a 'hidden' path, so mark it as such. EPSFlags.PathIsHidden = FALSE; if (ThePathType == PATH_DISCARD) { // Discard this path, but try to use the next one. ThePathType = PATH_NORMAL; return TRUE; } if (ThePathType == PATH_DISCARD_STICKY) // Keep discarding paths until explicitly told not to. return TRUE; ENSURE((ThePathType == PATH_NORMAL) || (ThePathType == PATH_RECT) || (ThePathType == PATH_ELLIPSE) || (ThePathType == PATH_MANGLED), "No path in hide!"); // If no path being constructed, probably just some strange masking going on // that we can't cope with yet, so ignore it. if (pPath == NULL) return TRUE; // Terminate path if ((Token == EPSC_h) || (Token == EPSC_n)) { if (!pPath->InkPath.CloseSubPath()) // Not enough dynamic heap to insert the final element of the path return FALSE; // This path is closed - set the filled flag. pPath->InkPath.IsFilled = TRUE; } // Make the path not filled. SetPathFilled(FALSE); // Add the attributes, if we haven't done so for this path if (EPSFlags.NoAttributes) { // Add attributes (FALSE => not stroked) if (!AddAttributes(pPath, FALSE, pPath->InkPath.IsFilled)) return FALSE; EPSFlags.NoAttributes = FALSE; } // Do the wierdness required for NodeRectangles if (ThePathType == PATH_RECT) { // Make sure we have a semi-valid path for a rectangle NodeRect *pRect = (NodeRect *) pPath; ENSURE(pRect->InkPath.GetNumCoords() == 5, "Bad rectangle path in EPS import!"); // Copy the path coordinates into the parallelogram DocCoord *Points = pRect->InkPath.GetCoordArray(); for (INT32 i = 0; i <= 3; i++) pRect->Parallel[i] = Points[i]; } else if (ThePathType == PATH_ELLIPSE) { // Make sure we have a semi-valid path for an ellipse NodeEllipse *pEllipse = (NodeEllipse *) pPath; ENSURE(pEllipse->InkPath.GetNumCoords() == 13, "Bad ellipse path in EPS import!"); // Copy the path coordinates into the parallelogram for (INT32 i = 0; i <= 3; i++) pEllipse->Parallel[i] = ShapeBBox[i]; } if (EPSFlags.ComplexPath == 0) { // Not a complex path, so we terminate properly. // Make sure this path is valid if (!pPath->InkPath.EnsureValid()) { // Invalid path - discard it delete pPath; InvalidPathFound = TRUE; } else { // Path is ok so carry on. pPath->InvalidateBoundingRect(); // Add this path into the tree if (!AddNewNode(pPath)) return FALSE; } // We've finished building this path - set to NULL so the other routines // know that we're not building a path any more. pPath = NULL; pInkPath = NULL; } else EPSFlags.StrokeComplexPath = FALSE; // Handle the next path normally ThePathType = PATH_NORMAL; // Success! return TRUE; } /******************************************************************************************** > BOOL EPSFilter::ProcessComment ( BOOL BypassDocComponents = FALSE ) Author: Tim_Browse (Xara Group Ltd) Created: 08/04/94 Inputs: BypassDocComponents - TRUE => don't pass this component to all the document components for processing. FALSE => pass this comment around to all the doc components to see if they handle it. Returns: TRUE if the comment was processed ok, FALSE if not. Purpose: Decodes an EPS comment - usually starting with "%%", e.g. %%BeginProlog etc. ********************************************************************************************/ BOOL EPSFilter::ProcessComment(BOOL BypassDocComponents) { if ((camStrncmp(TokenBuf, _T("%%Trailer"), 9) == 0) || (camStrncmp(TokenBuf, _T("%%PageTrailer"), 13) == 0)) { ProcessEPSTrailer(); } else if (camStrncmp(TokenBuf, _T("%%BoundingBox:"), 14) == 0) { ProcessBoundingBox(); } else { // Look for our known EPS/DSC comments INT32 Index = 0; BOOL FoundComment = FALSE; EPSCommentDef Comments; for(;;) { Comments = EPSComments.ReturnElement ( Index ); INT32 StartLen = camStrclen( Comments.StartMarker ); if (StartLen == 0) { // No more comments to try break; } if (camStrncmp(TokenBuf, Comments.StartMarker, StartLen) == 0) { FoundComment = TRUE; break; } // Try the next comment Index++; } if (FoundComment) { // Look for the end of this comment block. INT32 EndLen = camStrclen(Comments.EndMarker); do { GetLineToken(); if (Token == EPSC_Comment) { if (camStrncmp(TokenBuf, Comments.EndMarker, EndLen) == 0) { // Found the end of the comment block break; } // Special case for brain-damaged formats that don't bother to // bracket prolog segments correctly if ((camStrncmp(TokenBuf, _T("%%EndProlog"), 11) == 0) || (camStrncmp(TokenBuf, _T("%%EndSetup"), 10) == 0)) { // Found the end of the prolog/setup - this means we're not in the // prolog or setup processing loop (otherwise the above test // would have caught it) - it must be the next loop out. // So we put the token back onto the stream for the outer loop to // find and recognise, and quit our loop. EPSFile->UngetToken(); break; } // This is a comment - should we decode it? if (Comments.ProcessContents) { // Yes - first try the filter itself, and if that doesn't // need it, then try the document components. ProcessEPSComment(); } else { // No - recurse to handle nested comment blocks, and make sure it // isn't passed to the doc components. ProcessComment(TRUE); } } if (EPSFlags.EPSErrorEncountered) return FALSE; } while (!EPSFile->eof()); } else if (!BypassDocComponents) { // We're all out of ideas, so let the document components have a bash ProcessEPSComment(); } } return TRUE; } /******************************************************************************************** > BOOL EPSFilter::ProcessFilterComment() Author: Tim_Browse (Xara Group Ltd) Created: 12/08/94 Returns: TRUE if the comment is used by the filter; FALSE if not. Purpose: Override this function to claim EPS comments for the filter to decode. This function is called after the usual comments are checked for such as %%BeginProlog etc, but *before* the doc components are called. If you want to claim the current token (i.e. as held in TokenBuf), return TRUE. SeeAlso: EPSFilter::ProcessEPSComment ********************************************************************************************/ BOOL EPSFilter::ProcessFilterComment() { // This is the base class - there are no EPS comments specific to this filter. if (camStrncmp(TokenBuf, _T("%%XSScript"), 10) == 0) { TextComment[0]=2; return TRUE; } return FALSE; } /******************************************************************************************** > BOOL EPSFilter::ProcessEPSComment() Author: Tim_Browse (Xara Group Ltd) & Rik Created: 9/2/95 Returns: TRUE if the comment was recognised Purpose: Tries to process the contents of a comment. First it asks the filter and then it tries the document and all the doc components to see if it is one of thiers. ********************************************************************************************/ BOOL EPSFilter::ProcessEPSComment() { // First try the filter to see if it understands this comment... if (ProcessFilterComment()) // Yes - we're done. return TRUE; // We will start off by assuming that there are no doc components DocComponent *pComponent = NULL; ProcessEPSResult Result = EPSCommentUnknown; // ask the document what it thinks about the comment Result = TheDocument->ProcessEPSComment(this, TokenBuf); // If the document did not like the comment, let the doc components have a go if (Result == EPSCommentUnknown) pComponent = TheDocument->EnumerateDocComponents(NULL); while (pComponent != NULL) { // Does this document component want this comment? Result = pComponent->ProcessEPSComment(this, TokenBuf); if (Result != EPSCommentUnknown) // The component recognised the EPS comment break; // Otherwise, not recognised - try the next document component. pComponent = TheDocument->EnumerateDocComponents(pComponent); } // Work out if the comment was known to the doc-component - if the comment is // not known to any of them, we return FALSE to let the filter proces it. BOOL Recognised = Result != EPSCommentUnknown; // There may be continuation comments, so check for them. do { // Get the next token GetToken(); if ((Token != EPSC_Comment) || (camStrncmp(TokenBuf, _T("%%+"), 3) != 0)) { // Not a continuation comment - so we're finished if (Result == EPSCommentOK) { // Tell the document component that this comment has finished. if (pComponent==NULL) TheDocument->EndOfEPSComment(this); else pComponent->EndOfEPSComment(this); } // Put this token back onto the stream as we don't want to use it EPSFile->UngetToken(); break; } // Found a continuation comment - work out what to do with it if (Result == EPSCommentOK) { // The component understands this comment, so pass it on if (pComponent==NULL) Result = TheDocument->ProcessEPSComment(this, TokenBuf); else Result = pComponent->ProcessEPSComment(this, TokenBuf); } else { // Either the comment was not understood by any of the document components, // or it contained an error - either way, discard the rest of this comment. // i.e. ignore it } } while (!EPSFile->eof()); // Tell the caller whether or not we understood this comment. return (Recognised); } /******************************************************************************************** > BOOL EPSFilter::ProcessEPSTrailer() Author: Tim_Browse (Xara Group Ltd) Created: 9/2/95 Returns: TRUE - Success! FALSE - An error occured. Purpose: Handles the end of the page (I think) ********************************************************************************************/ BOOL EPSFilter::ProcessEPSTrailer() { BOOL Result = FALSE; // Found the end of a page, skip lines until another DSC comment is found. // This is so we skip the optional procset tidyup code that Illustrator // puts in. do { GetToken(); // Is it a DSC comment? if ((Token == EPSC_Comment) && (TokenBuf[1] == '%')) { // Yes - quit the loop - this is the end of the trailer. Result = TRUE; break; } } while (!EPSFile->eof()); // Put this comment back onto the input stream so it is processed properly. EPSFile->UngetToken(); return Result; } /******************************************************************************************** > BOOL EPSFilter::ProcessBoundingBox() Author: Rik_Heywood (Xara Group Ltd) Created: 9/2/95 Returns: TRUE - Success! FALSE - An error occured whilst processing the file. Purpose: Tries to make use of the BoundingBox info in the EPS file ********************************************************************************************/ BOOL EPSFilter::ProcessBoundingBox() { // Does this filter care about the bounding box? if (!AdjustOrigin) // No - discard it return TRUE; // Found the image's bounding box - set the origin accordingly. DocRect BBox; // Take a copy of the bounding box line so we can decompose it. TCHAR BBoxStr[50]; camStrncpy(BBoxStr, TokenBuf + 14, 50); // Extract the minimum values (converting to millipoints) TCHAR *Next = BBoxStr; TCHAR *Num; Num=Next; while(*Next && (*Next!=_T(' '))) Next++; if (*Next) *(Next++)=0; if (!Num || !*Num) // Error encountered in bounding box data - ignore it. return TRUE; BBox.lo.x = 0; camSscanf(Num, _T("%d"), &BBox.lo.x); BBox.lo.x *= 1000; Num=Next; while(*Next && (*Next!=_T(' '))) Next++; if (*Next) *(Next++)=0; if (!Num || !*Num) // Error encountered in bounding box data - ignore it. return TRUE; BBox.lo.y = 0; camSscanf(Num, _T("%d"), &BBox.lo.y); BBox.lo.y *= 1000; Num=Next; while(*Next && (*Next!=_T(' '))) Next++; if (*Next) *(Next++)=0; if (!Num || !*Num) // Error encountered in bounding box data - ignore it. return TRUE; BBox.hi.x = 0; camSscanf(Num, _T("%d"), &BBox.hi.x); BBox.hi.x *= 1000; Num=Next; while(*Next && (*Next!=_T(' '))) Next++; if (*Next) *(Next++)=0; if (!Num || !*Num) // Error encountered in bounding box data - ignore it. return TRUE; BBox.hi.y = 0; camSscanf(Num, _T("%d"), &BBox.hi.y); BBox.hi.y *= 1000; // Is this bounding box within the spread area? if (!SpreadRect.ContainsRect(BBox)) // No - adjust the origin Stack.TranslateCoordOrigin(-BBox.lo.x, -BBox.lo.y); // It worked! return TRUE; } /******************************************************************************************** > void EPSFilter::HandleEPSError() Author: Tim_Browse (Xara Group Ltd) Created: 25/02/94 Purpose: Cleans up when a syntax error is encountered in the EPS file. Any objects constructed so far are deleted, and the error message ID is set. ********************************************************************************************/ void EPSFilter::HandleEPSError() { // Error - EPS contains a syntax error. TRACE( _T("EPS syntax error at token <%s> (line %ld, TCHAR %d)\n"), TokenBuf, EPSFile->GetLineNumber(), EPSFile->GetCharOffset()); // Destroy any half-created paths if (pPath != NULL) { pPath->CascadeDelete(); pPath = NULL; } else { delete pInkPath; } pInkPath = NULL; // We know we've found an error, so clear the stack before we delete it so we don't // set off the ENSURE in EPSStack::~EPSStack. Stack.DeleteAll (); // Set global flag - this is a bodge until I set up a proper C++ exception to do this. EPSFlags.EPSErrorEncountered = TRUE; // Set the error for the caller to report String_256 ErrMsg; ErrMsg.MakeMsg(_R(IDT_EPS_BADSYNTAX), EPSFile->GetLineNumber(), EPSFile->GetCharOffset()); Error::SetError(_R(IDT_EPS_BADSYNTAX), (TCHAR *) ErrMsg, 0); } /******************************************************************************************** > void EPSFilter::HandleNoMemory() Author: Tim_Browse (Xara Group Ltd) Created: 25/02/94 Purpose: Cleans up when the filter runs out of memory when importing a file. Any objects constructed so far are deleted, and the error message ID is set. ********************************************************************************************/ void EPSFilter::HandleNoMemory() { // Destroy any half-created paths if (pPath != NULL) { pPath->CascadeDelete(); pPath = NULL; } else { delete pInkPath; } pInkPath = NULL; // Set the error for the caller to report (if it was a memory problem) if (Error::GetErrorNumber() == _R(IDS_OUT_OF_MEMORY)) Error::SetError(_R(IDT_EPS_NOMEMORY)); else // Must be an error in the file - e.g. bad path data HandleEPSError(); } /******************************************************************************************** > BOOL EPSFilter::ProcessGroup() Author: Tim_Browse (Xara Group Ltd) Created: 25/02/94 Returns: TRUE if the group was processed correctly, FALSE if not. Purpose: Reads in all the elements of a group structure in the EPS file. Errors: Syntax error in EPS file, Out of memory ********************************************************************************************/ BOOL EPSFilter::ProcessGroup() { if (!StartGroup()) return FALSE; // Keep processing tokens until we find the end of the group do { GetToken(); // Look for the end of the group token(s)... if ((Token == EPSC_U) || (Token == EPSC_Q)) { return EndGroup(Token == EPSC_Q); } } // Otherwise keep going until an error or eof is encountered while (HandleToken() && (!EPSFile->eof())); if (EPSFile->eof()) { // Didn't find end of group - syntax error; deal with it HandleEPSError(); } // If we're here, something went wrong return FALSE; } /******************************************************************************************** > TCHAR *EPSFilter::GetEPSCommand(EPSCommand Cmd) Author: Tim_Browse (Xara Group Ltd) Created: 24/02/94 Inputs: Cmd - the EPS token, e.g. EPSC_showpage Returns: Pointer to the string representation of the token, e.g. "showpage" Purpose: Given an EPS token, return the string representation of it; mainly for debugging purposes. ********************************************************************************************/ TCHAR *EPSFilter::GetEPSCommand(EPSCommand Cmd) { INT32 i = 0; while (Commands[i].Cmd != EPSC_Invalid) { if (Commands[i].Cmd == Cmd) return Commands[i].CmdStr; // Try next command i++; } // Couldn't find it - just return "Invalid" return Commands[i].CmdStr; } /******************************************************************************************** > BOOL EPSFilter::ExportBinaryHeader(CCLexFile* pFile) Author: Rik_Heywood (Xara Group Ltd) Created: 2/2/95 Inputs: pFile - The file we are saving to Returns: TRUE if it worked, FALSE if there was an error Purpose: Outputs the initial binary Header for EPS files that contain a TIFF preview of the EPS. This format is described on page 729 of the Big Red Postscript book (PostScript Language Reference Manual Second Edition). The basic form is as follows :- MonoOn: Bytes 0-3 C5D0D3C6 Bytes 4-7 Byte pos of start of PostScript in file Bytes 8-11 Length of PostScript section in Bytes Bytes 12-15 Start of MetaFile (always 0 from Camelot) Bytes 16-19 Length of MetaFile (always 0 from Camelot) Bytes 20-23 Start of TIFF file Bytes 24-27 Length of TIFF file Bytes 28-29 Checksum or FFFF SeeAlso: EPSFilter::CorrectBinaryHeader ********************************************************************************************/ BOOL EPSFilter::ExportBinaryHeader(CCLexFile* pFile) { #ifdef DO_EXPORT // Output the header here TCHAR Buf[30]; // Clear it to zero memset(Buf, 0, 30); // Fill the buffer with the Magic number that must appear at the start of the file // acording to the PostScript Manual Buf[0] = '\xC5'; Buf[1] = '\xD0'; Buf[2] = '\xD3'; Buf[3] = '\xC6'; // Fill in the Checksum to FFFF (meaning ignore checksum) Buf[28] = '\xFF'; Buf[29] = '\xFF'; // Write out the Magic number (0-3) if (pFile->write(Buf, 30).fail()) return FALSE; #endif // all worked return TRUE; } /******************************************************************************************** > BOOL EPSFilter::CorrectBinaryHeader(CCLexFile* pFile) Author: Rik_Heywood (Xara Group Ltd) Created: 2/2/95 Inputs: pFile - The file we are saving to Returns: TRUE if it worked, FALSE if there was an error Purpose: Goes back to the binary header at the start of the file and fills in the correct values to all the fields. SeeAlso: EPSFilter::ExportBinaryHeader ********************************************************************************************/ BOOL EPSFilter::CorrectBinaryHeader(CCLexFile* pFile) { #ifdef DO_EXPORT INT32 Length = 0; INT32 Zero = 0; // Seek back to the first thing that needs changing // The Start of PostScript pFile->seek(4); // Write out the start of postscript (4-7) if (pFile->write(&EPSStart, 4).fail()) return FALSE; // Write out the length of postscript (8-11) Length = EPSEnd - EPSStart; if (pFile->write(&Length, 4).fail()) return FALSE; // Write out the Start of metafile (12-15) if (pFile->write(&Zero, 4).fail()) return FALSE; // Write out the length of metafile (16-19) if (pFile->write(&Zero, 4).fail()) return FALSE; // Write out the start of TIFF if (pFile->write(&PreviewStart, 4).fail()) return FALSE; // Write out the legth of TIFF Length = PreviewEnd - PreviewStart; if (pFile->write(&Length, 4).fail()) return FALSE; #endif // All seemed to work return TRUE; } /******************************************************************************************** > virtual BOOL EPSFilter::WriteEPSProlog() Author: Rik_Heywood (Xara Group Ltd) Created: 2/2/95 Returns: TRUE if it worked (even if it did nothing), FALSE if there was an error Purpose: Allows the filter a chance to write out something in the Prolog section ********************************************************************************************/ BOOL EPSFilter::WriteEPSProlog() { return TRUE; } /******************************************************************************************** > virtual BOOL EPSFilter::WriteEPSSetup() Author: Rik_Heywood (Xara Group Ltd) Created: 2/2/95 Returns: TRUE if it worked (even if it did nothing), FALSE if there was an error Purpose: Allows the filter a chance to write out something in the Prolog section ********************************************************************************************/ BOOL EPSFilter::WriteEPSSetup() { return TRUE; } /******************************************************************************************** > virtual BOOL EPSFilter::WriteEPSComments() Author: Rik_Heywood (Xara Group Ltd) Created: 2/2/95 Returns: TRUE if it worked (even if it did nothing), FALSE if there was an error Purpose: Allows the filter a chance to write out something in the Prolog section ********************************************************************************************/ BOOL EPSFilter::WriteEPSComments() { return TRUE; } /******************************************************************************************** > virtual BOOL EPSFilter::WriteScript() Author: Rik_Heywood (Xara Group Ltd) Created: 2/2/95 Returns: TRUE if it worked (even if it did nothing), FALSE if there was an error Purpose: Allows the filter a chance to write out something in the Prolog section ********************************************************************************************/ BOOL EPSFilter::WriteScript() { return TRUE; } /******************************************************************************************** > virtual BOOL EPSFilter::NeedsPrintComponents () Author: Graeme_Sutherland (Xara Group Ltd) Created: 23/5/00 Returns: TRUE - Print components are required for this EPS type. Purpose: Allows the program to determine whether print components are needed for this flavour of EPS. ********************************************************************************************/ BOOL EPSFilter::NeedsPrintComponents () { // Default behaviour - assume that they are needed. return TRUE; } /******************************************************************************************** > EPSExportDC* EPSFilter::CreateExportDC() Author: Rik_Heywood (Xara Group Ltd) Created: 19/12/94 Returns: pointer to EPSExportDC or NULL Purpose: This allows the EPS filter to choose which sort of EPSExportDC to use. Most filters will use this default one, but some (Native Save/Load eps) will make use of a different ExportDC derived from EPSExportDC. This function simply returns the result of a 'new' and does no checking on the returned parameter. It is perfectly valid for this function to return NULL (meaning no ExportDC could be created) and the calling function should handle this appropriatly. ********************************************************************************************/ EPSExportDC* EPSFilter::CreateExportDC() { #ifdef DO_EXPORT // Create and return a DC return new EPSExportDC(this); #else return NULL; #endif } /******************************************************************************************** > virtual BOOL EPSFilter::GetExportOptions( ) Author: Neville_Humphrys (Xara Group Ltd) HUmphrys Created: 22/12/95 Inputs: - Returns: TRUE if OK, FALSE if user pressed Cancel. Purpose: Allows the user to be prompted to get information for export. Base class returns True so that nothing happens by default. Scope: Protected. ********************************************************************************************/ BOOL EPSFilter::GetExportOptions( ) { // baseclass version does nothing, so just return TRUE return TRUE; } /******************************************************************************************** > BOOL EPSFilter::PrepareToExport(CCLexFile* pFile, Spread *pSpread) Author: Tim_Browse (Xara Group Ltd) Created: 22/02/94 Inputs: pFile - The file to export to pSpread - The spread to export Returns: TRUE if succeeded, FALSE if not (e.g. no memory for EPS stack) Purpose: Prepare to import EPS data using this filter. This sets up the filter to a sensible state for reading. Errors: Out of memory. SeeAlso: EPSFilter::DoImport; EPSFilter::CleanUpAfterImport Scope: Private ********************************************************************************************/ BOOL EPSFilter::PrepareToExport(CCLexFile* pFile, Spread* pSpread) { #ifdef DO_EXPORT // Created the 'file' DC for rendering and try to open the specified file. ExportDCPtr = CreateExportDC(); if (ExportDCPtr == NULL) return FALSE; try { ExportDCPtr->Init(pFile); } catch(CFileException) { // Could not open the export file - return error return FALSE; } // Get the position of the first page, and use this to set the origin. Page *pPage = pSpread->FindFirstPageInSpread(); ENSURE(pPage != NULL, "Spread has no pages"); ERRORIF(pPage == NULL, _R(IDT_DOC_BADSTRUCTURE), FALSE); // Use bottom left of page as origin DocRect PageRect = pPage->GetPageRect(); ExportDCPtr->SetOrigin(PageRect.lo); // Create a new render region to export to: // Don't care about clip regions when exporting - create a null region. DocRect NullClipRect; NullClipRect.MakeEmpty(); if (IS_A(this, EPSFilter)) { // Don't use rendering matrix when exporting EPS as it uses fractional coordinates. Matrix Identity; // Don't use view scale; set to 1 FIXED16 Scale(1); // Create the region ExportRegion = new EPSRenderRegion(NullClipRect, Identity, Scale); if (ExportRegion == NULL) return FALSE; // Attach to the right device. ExportRegion->AttachDevice(DocView::GetSelected(), ExportDCPtr->GetDC(), pSpread); } // Inform all the document components that we are about to export DocComponent *pComponent = TheDocument->EnumerateDocComponents(NULL); while (pComponent != NULL) { // Inform this document component that we are about to start an EPS export. pComponent->EPSStartExport(this); // Look for next doc component pComponent = TheDocument->EnumerateDocComponents(pComponent); } #endif // All ok return TRUE; }; /******************************************************************************************** > void EPSFilter::CleanUpAfterExport() Author: Tim_Browse (Xara Group Ltd) Created: 24/02/94 Purpose: Cleans up the memory allocated by EPSFilter::PrepareToImport() - used when the import process ends, either normally or abnormally. SeeAlso: EPSFilter::PrepareToImport; EPSFilter::DoImport Scope: Private ********************************************************************************************/ void EPSFilter::CleanUpAfterExport() { #ifdef DO_EXPORT // Get rid of our dynamic objects if (ExportDCPtr!=NULL) delete ExportDCPtr; ExportDCPtr = NULL; if (ExportRegion!=NULL) delete ExportRegion; ExportRegion = NULL; // Inform all the document components that we have finished exporting DocComponent *pComponent = TheDocument->EnumerateDocComponents(NULL); while (pComponent != NULL) { // Inform this document component that we have finished exporting pComponent->EPSEndExport(this); // Look for next doc component pComponent = TheDocument->EnumerateDocComponents(pComponent); } #endif } /******************************************************************************************** > BOOL EPSFilter::DoExport(Operation *pOp, CCLexFile* pFile, PathName * pPath, Document *pDoc, BOOL ShowOptions) Author: Rik_Heywood (Xara Group Ltd) Created: 1/2/95 Inputs: pOp - The calling operation pFile - The file to export to pPath - the pathname of the file to be exported to pDoc - The doc to export ShowOptions - Show the export options dialogue? Returns: TRUE if it worked, FALSE if not Purpose: Exports the File as EPS. The various forms of EPS are to be found in the classes derived from this one. ********************************************************************************************/ BOOL EPSFilter::DoExport(Operation* pOp, CCLexFile* pFile, PathName* pPath, Document* pDoc, BOOL ShowOptions) { #ifdef DO_EXPORT // See if the EPS filters need some exporting options or not BOOL ok = GetExportOptions(); if (!ok) { // User has cancelled the operation so set our error whcih will be suppressed // and return FALSE Error::SetError(_R(IDN_USER_CANCELLED),0); return FALSE; } // Used to open the file up before starting DoExport. But this meant a cancel on the export // options dialog had filled the file, if it was already present. So now up up here if // not open already. In the PreviewBitmap case the file will already be open. if (!pFile->isOpen()) { if (pFile->IsKindOf(CC_RUNTIME_CLASS(CCDiskFile))) { ok = OpenExportFile((CCDiskFile*) pFile, pPath); if (!ok) return FALSE; } else { TRACEUSER( "JustinF", _T("Tried to open non-CCDiskFile in EPSFilter::DoExport\n")); return FALSE; } } // Do we want a Preview Bitmap while we are here? Filter* pFilter = NULL; if (WantPreviewBmp) { // Graeme (23/5/00) - The preview bitmap was originally a TIFF, but for // Xara X I've changed it to be a PNG. The preview filter code in the // older versions of Camelot (CorelXARA 1 and 2) *SHOULD* work OK with // this. pFilter = Filter::GetFirst(); // (ChrisG 28/11/00) - The preview bitmap should be in TIFF format, // otherwise import into Quark Express, etc... doesn't show anything // but a block stating that this is Postscript data; while ((pFilter != NULL) && (pFilter->FilterID != FILTERID_PREVIEW_TIFF)) { // Try the next filter pFilter = Filter::GetNext(pFilter); } // If we've found a filter, set up the preview export. if ( pFilter!=NULL ) { // Get ready for the binary header EPSStart = 0; EPSEnd = 0; PreviewStart = 0; PreviewEnd = 0; // Save out the Binary header if (!ExportBinaryHeader(pFile)) { CleanUpAfterExport(); return FALSE; } // if this is the Native Exporter, then we want to force the Preview to a fixed size //WEBSTER-Martin-03/01/97 INT32 OldPreviewSize = PreviewFilter::PreviewBitmapSize; if (IS_KIND_OF(CamelotNativeEPSFilter)) { // Set the Preview to be just over an inch accross PreviewFilter::PreviewBitmapSize = 96000; } // Get as Bitmap Filter and set it up BaseBitmapFilter* pBitmapFilter = static_cast ( pFilter ); pBitmapFilter->SetPreviewBitmap(TRUE); PreviewStart = pFile->tell(); // Export the Preview to the file if (!pBitmapFilter->DoExport(pOp, pFile, pPath, pDoc, TRUE)) { // The Preview bitmap failed to export // Go back to where the bitmap started pFile->seek(PreviewStart); // mark the fact that no bitmap was saved PreviewStart = 0; PreviewEnd = 0; } else { // Mark where the tiff ends PreviewEnd = pFile->tell(); } // Find out where the EPS Starts EPSStart = pFile->tell(); // Set it back pBitmapFilter->SetPreviewBitmap(FALSE); //WEBSTER-Martin-03/01/97 if (IS_KIND_OF(CamelotNativeEPSFilter)) { // Set the Preview back to its default setting PreviewFilter::PreviewBitmapSize = OldPreviewSize; } } } // Set up document pointer TheDocument = pDoc; // Get pointer to the spread to export. PORTNOTE("spread", "Multi-spread warning!") Spread *pSpread = GetFirstSpread(pDoc); // Set up device context and render region for this export. if (!PrepareToExport(pFile, pSpread)) { CleanUpAfterExport(); return FALSE; } // Export the data to the file if (!ExportRender(ExportRegion)) { CleanUpAfterExport(); return FALSE; } // All done - deallocate dynamic objects and return success. CleanUpAfterExport(); // Detach document TheDocument = NULL; // Do we want a Preview Bitmap while we are here (and were we able to make one) if ((WantPreviewBmp) && (pFilter!=NULL)) { // Find out where the PostScript ended EPSEnd = pFile->tell(); // BODGE to make the viewer work. // The viewer will get a read error if the EPS data i.e. not including the EPS // header and preview bitmap, is less than 1k as this is the section loaded in // by Filters::LoadInitialSegment and it will error if less than 1k long. INT32 Length = EPSEnd - EPSStart; if (Length < 1024) { const TCHAR PadChar = '\n'; // Pad the file up the required size FilePos CurrentPosition = pFile->tell(); while ((CurrentPosition - EPSEnd) < 1024) { if (pFile->put(PadChar).fail()) break; CurrentPosition = pFile->tell(); } } // Correct the binary header, now we know where everything is if (!CorrectBinaryHeader(pFile)) { ERROR1(FALSE, _R(IDT_EXPORT_INTERNAL_ERR)); } // Go back and rub out the copyright in the middle of the file RemoveMessage(pFile); } #endif return TRUE; } /******************************************************************************************** > void EPSFilter::RemoveMessage(CCLexFile* pFile) Author: Rik_Heywood (Xara Group Ltd) Created: 28/6/95 Purpose: Removes the Copyright message that appears in the middle of our native file format. ********************************************************************************************/ void EPSFilter::RemoveMessage(CCLexFile* pFile) { #ifdef DO_EXPORT // What we are looking for TCHAR Message[] = _T("1992 ACCUSOFT INC, ALL RIGHTS RESERVED"); String_64 Replace(_T("Xara Studio, Xara Studio, Xara Studio.")); INT32 Length = camStrlen(Message); TCHAR Ch = 0; FilePos StartOfStr = 0; try { // Go to the start of the Preview bitmap if (pFile->seekIn(PreviewStart, ios::beg).fail()) return; // Start looking for the string INT32 CharsToSearch = PreviewEnd - PreviewStart; INT32 MatchedChars = 0; // Get the first TCHAR and start looking char cch; pFile->get(cch); Ch=TCHAR(cch); // convert using 1:1 map while ((!pFile->eof()) && (!pFile->fail()) && (MatchedChars0)) { // Count this TCHAR CharsToSearch--; // see if matches our string if (Ch==Message[MatchedChars]) { // see if it is the first TCHAR in the string if (MatchedChars==0) { // We have just read the first TCHAR so the start is // where we are now, minus 1 StartOfStr = pFile->tell() - 1; } // Count the TCHAR MatchedChars++; } else { // Mismatch, so start again MatchedChars=0; } // Get the next TCHAR in the file and return if there is a problem char cch; pFile->get(cch); Ch=TCHAR(cch); // convert using 1:1 map } // If we found the whole string, replace it if (MatchedChars==Length) { // Go back to the start of the string pFile->seek(StartOfStr); pFile->write(Replace); TRACEUSER( "Rik", _T("Replaced copyright\n")); } } catch(CFileException) { // There was a problem, so don't change the message return; } #endif } /******************************************************************************************** > BOOL EPSFilter::ImportBinary(ADDR pData, INT32 Length) Author: Tim_Browse (Xara Group Ltd) Created: 23/08/94 Inputs: Length - the number of bytes to read, in terms of binary data imported, as opposed to how many bytes the file format takes to represent this binary data. Outputs: pData - the buffer to put the data into. Returns: TRUE if it imported the specified number of bytes correctly; FALSE if not. Purpose: Converts EPS hex strings into binary data. Attempts to read the number of bytes specified from the file. Errors: File input errors; EOF SeeAlso: Filter::ImportBinary ********************************************************************************************/ INT32 EPSFilter::ImportBinary(ADDR pData, INT32 Length) { do { // Get the next token from the file if (!EPSFile->GetHexToken()) return FALSE; INT32 CharsRead = EPSFile->GetCharsRead(); if (CharsRead > (LastProgressUpdate + 2048)) { if (!ContinueSlowJob(CharsRead)) { // Abort operation - make sure nodes are deleted and not added to the tree. ERROR(_R(IDT_IMPORT_USERABORT), FALSE); } else { LastProgressUpdate = CharsRead; } } // Ignore whitespace if (EPSFile->GetTokenType() == TOKEN_STRING) // Error break; // Decode the token into hex data. INT32 nBytes = DecodeHexString(pData, Length); if (nBytes == -1) { // Error ENSURE(FALSE, "Error in EPS hex data"); break; } pData += nBytes; Length -= nBytes; } while ((Length > 0) && !EPSFile->eof()); if (Length == 0) // All ok return TRUE; // Error encountered ENSURE(FALSE, "Error importing binary data from EPS file"); return FALSE; } /******************************************************************************************** > INT32 EPSFilter::DecodeHexString(ADDR pData, INT32 Length, INT32 nStart) Author: Tim_Browse (Xara Group Ltd) Created: 23/08/94 Inputs: Length - the maximum number of bytes to read from the hex string. nStart - where in TokenBuf (the member that contains the semantic value of the token) to start the decoding Outputs: pData - the buffer to place the binary data into. Returns: The number of bytes read from the current token. If an error occurs (i.e. invalid hex data) then -1 is returned. Purpose: Treat the current token as a hex string, and decode it into binary data and place it in the buffer provided by the caller. No more than Length bytes should be placed in the buffer. SeeAlso: EPSFilter::ImportBinary ********************************************************************************************/ INT32 EPSFilter::DecodeHexString(ADDR pData, INT32 Length, INT32 nStart) { INT32 TokenLen = camStrlen(TokenBuf + nStart); INT32 Ofs = 0; // Assume hex strings are even-numbered in length for the moment ENSURE((TokenLen & 1) == 0, "Bad hex string length in DecodeHexString"); // Work out how much of the string we should decode INT32 MaxOfs = Length * 2; // Decode the string two characters at a time for (INT32 i = nStart; (i < TokenLen) && (i < MaxOfs); i += 2) { TCHAR Ch = camToupper(TokenBuf[i]); BYTE Byte; // Decode first digit. if ((Ch >= '0') && (Ch <= '9')) { Byte = (BYTE) (Ch - '0'); } else if ((Ch >= 'A') && (Ch <= 'F')) { Byte = (BYTE) (Ch - 'A') + 10; } else { // Error in hex data return -1; } Ch = camToupper(TokenBuf[i+1]); Byte <<= 4; // Decode swcond digit. if ((Ch >= '0') && (Ch <= '9')) { Byte |= (BYTE) (Ch - '0'); } else if ((Ch >= 'A') && (Ch <= 'F')) { Byte |= (BYTE) (Ch - 'A') + 10; } else { // Error in hex data return -1; } pData[Ofs++] = Byte; } // How much data did we read? return Ofs; } /******************************************************************************************** > BOOL EPSFilter::ResetImportOrigin() Author: Rik_Heywood (Xara Group Ltd) Created: 17/2/95 Returns: TRUE if it worked, FLASE if not Purpose: If the importer changes the pages in the document when loading, this allows it to reset the origin. ********************************************************************************************/ BOOL EPSFilter::ResetImportOrigin() { // For now, position EPS objects on 1st page of spread 1 Page *pPage = ImportInfo.pSpread->FindFirstPageInSpread(); ERROR2IF(pPage==NULL, FALSE, "Spread has no pages"); // Use bottom left of page as origin DocRect PageRect = pPage->GetPageRect(); Stack.SetCoordOrigin(PageRect.lo); // worked return TRUE; } /******************************************************************************************** > BOOL EPSFilter::ResetImportOrigin(DocCoord NewOrigin) Author: Tim_Browse (Xara Group Ltd) Created: 7/6/95 Returns: TRUE if it worked, FLASE if not Purpose: Instructs the filter to use the new origin, because we are importing and otherwise QuickShapes and Text Stories will be incorrectly positioned. ********************************************************************************************/ BOOL EPSFilter::ResetImportOrigin(DocCoord NewOrigin) { Stack.SetCoordOrigin(NewOrigin); // worked return TRUE; } /******************************************************************************************** > BOOL EPSFilter::ProcessText() Author: Chris_Snook (Xara Group Ltd) /Mike Created: 29/11/94 Returns: TRUE if the Text was processed correctly, FALSE if not. Purpose: Reads in all the elements of a Text structure in the EPS file. Errors: Syntax error in EPS file, Out of memory ********************************************************************************************/ BOOL EPSFilter::ProcessText() { INT32 Type; Stack.Pop(&Type); Node *OldPos = pNode; BOOL PastAnEOL = FALSE; // we don't support area text if ((Type!=0) && (Type!=2)) return TRUE; // Create a matrix to use Matrix StoryMat; if (!GetStoryMatrix(&StoryMat)) { HandleEPSError(); return FALSE; } // create the text story TextStory *TheStory = new TextStory(); AddNewNode(TheStory); // set the story matrix TheStory->SetStoryMatrix(StoryMat); // change insert position pNode = TheStory; while (!EPSFile->eof()) { GetToken(); switch (Token) { // Start of a new text object case EPSC_To: { ERROR2RAW("Bad Error in EPS .. TextObject inside TextObject !"); goto error; } // Found the end of text so return done. case EPSC_TO: { if (!TheStory->AttachCaretAttributes()) goto error; // (ChrisG 29/3/2001) - Make sure that the last line has an EOL node at the end. TextLine * pLine = TheStory->FindLastLine (); if (!pLine->FindEOLNode ()) { EOLNode* pEOL = new EOLNode(); if (!AddNewNode(pEOL)) goto error; } pNode = OldPos; return TRUE; } // text render type case EPSC_Tr: { INT32 Style; if (!Stack.Pop(&Style)) goto error; switch (Style) { case 0: // Fill text (create a transparent line colour) SetStrokeColourNone(); break; case 1: // stroke text (create a transparent fill colour) SetFillColourNone(); break; case 2: // fill and stroke text break; case 3: // text with no fill or stroke (create transparent fill and line) SetStrokeColourNone(); SetFillColourNone(); break; } break; } case EPSC_Tx: case EPSC_Tj: case EPSC_TX: // found some text { // we want to add text if (PastAnEOL) { MakeNewTextLine(TheStory); PastAnEOL = FALSE; } String_256 Text; if (!Stack.Pop(&Text)) goto error; TCHAR * buf = (TCHAR*)Text; INT32 len = Text.Length(); TextChar* pCh = NULL; // BODGE for ( INT32 i = 0; i < len; i++) { // check for a carriage return if (buf[i]==_T('\r')) { PastAnEOL = TRUE; EOLNode* pEOL = new EOLNode(); if (!AddNewNode(pEOL)) goto error; if (!AttributeManager::ApplyBasedOnDefaults(pEOL,CurrentAttrs)) goto error; } // (ChrisG 2/4/2001) Don't import overflow (invisible) text, unless it's a // newline. Technically speaking this should still be imported, but it is // redundant information that Xara X won't ever use, so why import it? else if (Token != EPSC_TX) { // create a TextChar node pCh = new TextChar(); if (pCh == NULL) goto error; pCh->SetUnicodeValue((WCHAR)buf[i]); // Make sure we actually fill our paths matey if (!SetPathFilled(TRUE)) goto error; if(!AddNewNode(pCh)) goto error; if (!AttributeManager::ApplyBasedOnDefaults(pCh,CurrentAttrs)) goto error; } } // now turn off single character attributes break; } case EPSC_T_: { // ok we've received a T* token so translate the text matrix by // (-lineleading, 0) according to the documentation. Is this correct? // What I should do for now is to check wether we have a return code // in the last line generated and if not create one break; } // end of text path - not sure about this !! case EPSC_TP: // create a text line { if (!MakeNewTextLine(TheStory)) goto error; CaretNode* pCaret = new CaretNode(); if (!AddNewNode(pCaret)) goto error; break; } // Aspect Ratio % case EPSC_Tz: { double Aspect; if (!Stack.Pop(&Aspect)) goto error; if (!SetTextAspectRatio(FIXED16(Aspect/100.0))) // convert back from % to ratio! goto error; break; } // Tracking case EPSC_Tt: { INT32 Tracking; if (!Stack.Pop(&Tracking)) goto error; if (!SetTextTracking(Tracking)) goto error; break; } case EPSC_Ta: // Justification { Justification JustVal=JLEFT; INT32 Justify; if (!Stack.Pop(&Justify)) goto error; switch (Justify) { case 0: JustVal = JLEFT; break; case 2: JustVal = JRIGHT; break; case 1: JustVal = JCENTRE; break; case 3: JustVal = JFULL; break; } if (!SetTextJustification(JustVal)) goto error; break; } // line spacing, format= linespace paraspace Tl case EPSC_Tl: { double lineLead, paraLead; if (!Stack.Pop(¶Lead)) goto error; if (!Stack.Pop(&lineLead)) goto error; // line lead measure in absolute pointsize INT32 lead = (INT32)(lineLead*1000); if (!SetTextLineSpacing(1,0,lead,0)) goto error; break; } // create a Kern Code. case EPSC_Tk: { // we want to add text if (PastAnEOL) { MakeNewTextLine(TheStory); PastAnEOL = FALSE; } INT32 AutoKern; DocCoord KernValue; KernCode* pKernCode = new KernCode(); if (!pKernCode) goto error; if (!Stack.Pop(&KernValue.x)) goto error; if (!Stack.Pop(&AutoKern)) goto error; pKernCode->SetValue(KernValue); if (!AddNewNode(pKernCode)) goto error; if (!AttributeManager::ApplyBasedOnDefaults(pKernCode,CurrentAttrs)) goto error; break; } // Baseline shift case EPSC_Ts: { double BaseShift; if (!Stack.Pop(&BaseShift)) goto error; INT32 RealShift = (INT32)(BaseShift*1000+0.5); if (TextComment[0]>0) { TextComment[1]=RealShift; TextComment[0]--; if (TextComment[0]==0) { if (!SetTextScript(TextComment[1], TextComment[2])) goto error; TextComment[1]=0; TextComment[2]=0; } } else { if (!SetTextBaseLine(RealShift)) goto error; } break; } // Font name, pointsize case EPSC_Tf: { String_64 EncodedName; double FSize; // Pop the font size. if (!Stack.Pop(&FSize)) goto error; // Graeme (12/6/00) - The font format has changed with AI7, which adds two // more operators to the definition. The AI5 operator expects a string here - // if it's not present (i.e. the Pop fails), then we guess that it's an AI7 // file, and make a second attempt to pull the font size and name from the // stack. if ( !Stack.Pop ( &EncodedName ) ) { // We're dealing with an AI7 font, which contains descent and spacing // information. Ignore the last item, and use the next two as the // size and font name fields. // Pull out the font size. if ( !Stack.Pop ( &FSize ) ) goto error; // And take a second stab at getting the font name. if ( !Stack.Pop ( &EncodedName ) ) goto error; } // Calculate the millipoint fontsize. INT32 MillFSize = (INT32)(FSize*1000+0.5); // check whether we're inside a sub/super script comment if (TextComment[0]>0) { TextComment[2]=MillFSize; TextComment[0]--; if (TextComment[0]==0) { if (!SetTextScript(TextComment[1], TextComment[2])) goto error; TextComment[1]=0; TextComment[2]=0; } } else { if (!SetTextFont(&EncodedName, MillFSize)) goto error; } break; } // Unicode character handling (only output in native documents) // This is inserted here rather than in the native filter due // to EOL characters and the way the old code generates new lines. // We keep a LOCAL PastAnEOL flag to let us know whether we need // to generate a new line for the next character or not. This avoids // dangling lines with no chars in. Unfortunately all vars are local // to this routine and hence the native class cant get at them. So for // this single token, the native class will pass it up here where we // can get at it. case EPSC_ctx: { INT32 numc=0, ch, i, buff[256]; TextChar* pCh = NULL; // we want to add text if (PastAnEOL) { MakeNewTextLine(TheStory); PastAnEOL = FALSE; } // get the number of unicode characters in this string if (!Stack.Pop(&numc)) goto error; if (numc>255) goto error; for (i=0; i=0; i--) { // create a TextChar node pCh = new TextChar(); if (pCh == NULL) goto error; pCh->SetUnicodeValue((WCHAR)buff[i]); // Make sure we actually fill our paths matey if (!SetPathFilled(TRUE)) goto error; if (!AddNewNode(pCh)) goto error; if (!AttributeManager::ApplyBasedOnDefaults(pCh,CurrentAttrs)) goto error; } break; } case EPSC_ctex: { // the text extras token INT32 version; if (!Stack.Pop(&version)) goto error; switch (version) { case 0: double Scale,Aspect,Rotation,Shear; INT32 AsShapes, WordWrapping; MILLIPOINT StoryWidth; if (!Stack.Pop(&AsShapes)) goto error; if (!Stack.Pop(&Shear)) goto error; if (!Stack.Pop(&Rotation)) goto error; if (!Stack.Pop(&Aspect)) goto error; if (!Stack.Pop(&Scale)) goto error; if (!Stack.Pop(&StoryWidth)) StoryWidth = 0; if (!Stack.Pop(&WordWrapping)) WordWrapping = FALSE; // Set values in story here.... TheStory->SetWordWrapping(WordWrapping); TheStory->SetStoryWidth(StoryWidth); TheStory->SetCharsScale(Scale); TheStory->SetCharsAspect(Aspect); TheStory->SetCharsRotation(Rotation); TheStory->SetCharsShear(Shear); TheStory->SetPrintingAsShapes(AsShapes); break; default: // don't understand this version so chuck it! if (!Stack.Discard()) goto error; } break; } default: if (!HandleToken()) goto error; break; } } error: pNode = OldPos; HandleEPSError(); return FALSE; } /******************************************************************************************** > BOOL EPSFilter::GetStoryMatrix(Matrix* pMatrix) Author: Chris_Snook (Xara Group Ltd) Created: 29/11/94 Outputs: pMatrix - holds the story matrix red Returns: TRUE if the matirx was read correctly, FALSE if not. Purpose: Reads in all the elements of a Text Story Matrix. Errors: Syntax error in EPS file, Out of memory ********************************************************************************************/ BOOL EPSFilter::GetStoryMatrix(Matrix* pMatrix) { while (!EPSFile->eof()) { GetToken(); // Look for the end of text token... switch (Token) { case EPSC_Tp: // found the start of a text path. { INT32 StartDist; if (!Stack.Pop(&StartDist)) return FALSE; if (!Stack.Pop(pMatrix, FALSE)) return FALSE; // if we've loaded an illustrator file we need to fix up // the text matrix position ProcessTextMatrix(pMatrix); return TRUE; } default: if(!HandleToken()) return FALSE; break; } } HandleEPSError(); return FALSE; } /******************************************************************************************** > virtual void EPSFilter::ProcessTextMatrix(Matrix* pMatrix) Author: Mike_Kenny (Xara Group Ltd) Created: 18/8/95 Returns: - Purpose: Having read a text object position matrix from the file, we may (dependent on the filter type), need to process it in some way, ie add in the spread origin. ********************************************************************************************/ void EPSFilter::ProcessTextMatrix(Matrix* pMatrix) { // Do nothing in this base class } /******************************************************************************************** > BOOL EPSFilter::MakeNewTextLine() Author: Chris_Snook (Xara Group Ltd) Created: 29/11/94 Returns: TRUE if the Text was processed correctly, FALSE if not. Purpose: Reads in all the elements of a Text structure in the EPS file. Errors: Syntax error in EPS file, Out of memory ********************************************************************************************/ BOOL EPSFilter::MakeNewTextLine(Node * pParent) { pNode = pParent; TextLine* pTextLine = new TextLine(); if(!AddNewNode(pTextLine)) return FALSE; // make all future chars etc.. children of this line pNode = pTextLine; return TRUE; } /******************************************************************************************** > BOOL EPSFilter::SetStrokeColourNone() Author: Mike_Kenny (Xara Group Ltd) Created: 13/04/94 Inputs: - Returns: TRUE if the attribute was changed ok; FALSE otherwise. Purpose: Updates the current attribute for StrokeColour with the value of COLOUR_NONE Errors: Out of memory. SeeAlso: Filter; Filter::Init ********************************************************************************************/ BOOL EPSFilter::SetStrokeColourNone() { // Sanity check ENSURE(CurrentAttrs != NULL, "No current attributes in filter!"); // If we haven't changed this attribute yet, then make a new attribute object for // our own personal use... if (!CurrentAttrs[ATTR_STROKECOLOUR].Temp) { // Make the correct attribute value and install it as the current one. CurrentAttrs[ATTR_STROKECOLOUR].pAttr = new StrokeColourAttribute(); if (CurrentAttrs[ATTR_STROKECOLOUR].pAttr == NULL) return FALSE; CurrentAttrs[ATTR_STROKECOLOUR].Temp = TRUE; } // Line Colour must be NONE. StrokeColourAttribute* pLineCol = (StrokeColourAttribute*)CurrentAttrs[ATTR_STROKECOLOUR].pAttr; DocColour none(COLOUR_NONE); pLineCol->SetStartColour(&none); // All worked ok. return TRUE; } /******************************************************************************************** > BOOL EPSFilter::SetFillColourNone() Author: Mike_Kenny (Xara Group Ltd) Created: 13/04/94 Inputs: - Returns: TRUE if the attribute was changed ok; FALSE otherwise. Purpose: Updates the current attribute for FillColour with the value of COLOUR_NONE Errors: Out of memory. SeeAlso: Filter; Filter::Init ********************************************************************************************/ BOOL EPSFilter::SetFillColourNone() { // Sanity check ENSURE(CurrentAttrs != NULL, "No current attributes in filter!"); // If we haven't changed this attribute yet, then make a new attribute object for // our own personal use... if (!CurrentAttrs[ATTR_FILLGEOMETRY].Temp) { // Make the correct attribute value and install it as the current one. CurrentAttrs[ATTR_FILLGEOMETRY].pAttr = new FlatFillAttribute(); if (CurrentAttrs[ATTR_FILLGEOMETRY].pAttr == NULL) return FALSE; CurrentAttrs[ATTR_FILLGEOMETRY].Temp = TRUE; } // Fill Colour must be NONE. FlatFillAttribute* pFillCol = (FlatFillAttribute*)CurrentAttrs[ATTR_FILLGEOMETRY].pAttr; DocColour none(COLOUR_NONE); pFillCol->SetStartColour(&none); // All worked ok. return TRUE; } /******************************************************************************************** > BOOL EPSFilter::ProcessRectangle(NodeRect* pRect, NodeRegularShape* pQuickShape) Author: Peter_Arnold (Xara Group Ltd) Created: 29/5/95 Inputs: pPath - the old style rectangle that has been read pQuickShape - points to an unitilialied QuickShape Outputs: pQuickShape will be a rectangle QuickShape Returns: TRUE if it worked, FALSE if not Purpose: Having read in a rectangle path, this function builds a QuickShape rectangle as defined by the path ********************************************************************************************/ BOOL EPSFilter::ProcessRectangle(NodeRect* pRect, NodeRegularShape* pQuickShape) { BOOL ok = TRUE; if ((pRect != NULL) && (pQuickShape != NULL) && (pRect->InkPath.GetNumCoords() == 5)) { if (ok) ok = pQuickShape->SetUpShape(); DocCoord *Points = pRect->InkPath.GetCoordArray(); if (ok && pQuickShape->MakeRectangle((INT32)(Points[0].Distance(Points[1])), (INT32)(Points[1].Distance(Points[2])),0)) { // Now skew, rotate and translate the rectangle as required // Get rotation DocCoord Centre = DocCoord::OneHalf(Points[0], Points[2]); DocCoord RenderMajor = DocCoord::OneHalf(Points[0], Points[1]); double RotAngle = atan2((double)RenderMajor.y-Centre.y,(double)RenderMajor.x-Centre.x) - (PI/2); //double SkewAngle= atan2(pQuickShape->GetUTMinorAxes().y,pQuickShape->GetUTMinorAxes().x) - (PI/2); Matrix mat( ANGLE( RotAngle * ( 180 / PI ) ) ); Trans2DMatrix TransMat( mat ); TransMat *= Trans2DMatrix(Centre.x,Centre.y); pQuickShape->Transform(TransMat); } else { ERROR3("MakeRectangle failed"); ok = FALSE; } } else { ERROR3("Bad rectangle path in EPS import!"); ok = FALSE; } return ok; } /******************************************************************************************** > BOOL EPSFilter::ProcessEllipse(NodeEllipse* pEllipse, NodeRegularShape* pQuickShape) Author: Peter_Arnold (Xara Group Ltd) Created: 29/5/95 Inputs: pEllipse - the old style ellipse that has been read pQuickShape - points to an unitilialied QuickShape Outputs: pQuickShape will be a ellipse QuickShape Returns: TRUE if it worked, FALSE if not Purpose: Having read in a ellipse path, this function builds a QuickShape ellipse as defined by the path ********************************************************************************************/ BOOL EPSFilter::ProcessEllipse(NodeEllipse* pEllipse, NodeRegularShape* pQuickShape) { BOOL ok = TRUE; if ((pEllipse != NULL) && (pQuickShape != NULL) && (pEllipse->InkPath.GetNumCoords() == 13)) { if (ok) ok = pQuickShape->SetUpShape(); const DocCoord Centre = DocCoord::OneHalf(ShapeBBox[0], ShapeBBox[2]); const double Height = DocCoord::DistanceFromLine(ShapeBBox[0], ShapeBBox[1], Centre); const double Width = DocCoord::DistanceFromLine(ShapeBBox[1], ShapeBBox[2], Centre); if (ok && pQuickShape->MakeEllipse((INT32)Width, (INT32)Height)) { // Now skew, rotate and translate the ellipse as required // Get rotation DocCoord RenderMajor = DocCoord::OneHalf(ShapeBBox[0], ShapeBBox[1]); double RotAngle = atan2((double)RenderMajor.y-Centre.y,(double)RenderMajor.x-Centre.x) - (PI/2); //double SkewAngle= atan2(pQuickShape->GetUTMinorAxes().y,pQuickShape->GetUTMinorAxes().x) - (PI/2); Matrix mat( ANGLE( RotAngle * ( 180 / PI ) ) ); Trans2DMatrix TransMat( mat ); TransMat *= Trans2DMatrix(Centre.x,Centre.y); pQuickShape->Transform(TransMat); } else { ERROR3("MakeEllipse failed"); ok = FALSE; } } else { ERROR3("Bad ellipse path in EPS import!"); ok = FALSE; } return ok; } /******************************************************************************************** > class EPSSavedGraphicState : class ListItem Author: Ben_Summers (Xara Group Ltd) Created: 02/06/95 Purpose: to store a graphic state for the EPS filter SeeAlso: EPSFilter ********************************************************************************************/ class EPSSavedGraphicState : public ListItem { CC_DECLARE_MEMDUMP(EPSSavedGraphicState); public: EPSSavedGraphicState(); ~EPSSavedGraphicState(); BOOL Setup(); public: NodePath *pPath; INT32 ThePathType; AttributeEntry *Attrs; EPSFlagsDefn EPSFlags; }; CC_IMPLEMENT_MEMDUMP(EPSSavedGraphicState, ListItem) /******************************************************************************************** > EPSSavedGraphicState::EPSSavedGraphicState() Author: Ben_Summers (Xara Group Ltd) Created: 02/06/95 Inputs: - Returns: - Purpose: constructor SeeAlso: EPSFilter ********************************************************************************************/ EPSSavedGraphicState::EPSSavedGraphicState() { pPath = 0; Attrs = 0; } /******************************************************************************************** > EPSSavedGraphicState::Setup() Author: Ben_Summers (Xara Group Ltd) Created: 02/06/95 Inputs: - Returns: - Purpose: set up the object SeeAlso: EPSFilter ********************************************************************************************/ BOOL EPSSavedGraphicState::Setup() { // number of attributes? INT32 NumAttrs = AttributeManager::GetNumAttributes(); // get some memory for the nice attribute values Attrs = new AttributeEntry[NumAttrs]; // set up the values for (INT32 i = 0; i < NumAttrs; i++) { Attrs[i].pAttr = 0; Attrs[i].Temp = FALSE; Attrs[i].Ignore = FALSE; } // All done return TRUE; } /******************************************************************************************** > EPSSavedGraphicState::~EPSSavedGraphicState() Author: Ben_Summers (Xara Group Ltd) Created: 02/06/95 Inputs: - Returns: - Purpose: destructor SeeAlso: EPSFilter ********************************************************************************************/ EPSSavedGraphicState::~EPSSavedGraphicState() { if(pPath != 0) { pPath->CascadeDelete(); delete pPath; } if(Attrs != 0) { // how many? INT32 NumAttrs = AttributeManager::GetNumAttributes(); // delete them... for (INT32 i = 0; i < NumAttrs; i++) { if((Attrs[i].pAttr != 0) && (Attrs[i].Temp == TRUE)) delete Attrs[i].pAttr; } // delete the attay delete [] Attrs; } } /******************************************************************************************** > BOOL EPSFilter::Import_gsave() Author: Ben_Summers (Xara Group Ltd) Created: 02/06/95 Inputs: - Returns: error flag - can be a memory error Purpose: stores the current path and attributes to emulate a gsave in the imported EPS file. All things are copied. States can be nested. SeeAlso: EPSFilter::Import_grestore ********************************************************************************************/ BOOL EPSFilter::Import_gsave() { // get a new item to put everything in EPSSavedGraphicState *State = new EPSSavedGraphicState; if(State == 0 || !State->Setup()) { delete State; return FALSE; } // copy the path into it if(pPath != 0) { if(!pPath->NodeCopy((Node **)&State->pPath)) { delete State; return FALSE; } } else { State->pPath = 0; } State->ThePathType = ThePathType; // save the flags State->EPSFlags = EPSFlags; // copy all the attributes into it // number of attributes? INT32 NumAttrs = AttributeManager::GetNumAttributes(); // copy the values in for (INT32 i = 0; i < NumAttrs; i++) { if(CurrentAttrs[i].pAttr != 0) { if(CurrentAttrs[i].Temp) { // copy this attribute CCRuntimeClass* ObjectType = CurrentAttrs[i].pAttr->GetRuntimeClass(); AttributeValue* AttrClone = (AttributeValue*)ObjectType->CreateObject(); if(AttrClone == 0) { delete State; return FALSE; } AttrClone->SimpleCopy(CurrentAttrs[i].pAttr); // pop a pointer to it in the state State->Attrs[i].pAttr = AttrClone; } else { State->Attrs[i].pAttr = CurrentAttrs[i].pAttr; } } else { State->Attrs[i].pAttr = 0; } State->Attrs[i].Temp = CurrentAttrs[i].Temp; State->Attrs[i].Ignore = CurrentAttrs[i].Ignore; } // add the state to the list GraphicStateStack.AddTail(State); return TRUE; } /******************************************************************************************** > BOOL EPSFilter::Import_grestore() Author: Ben_Summers (Xara Group Ltd) Created: 02/06/95 Inputs: - Returns: error flag - but only if there isn't an entry on the stack to restore Purpose: restores path and attributes saved by the matching Import_gsave. SeeAlso: EPSFilter::Import_gsave ********************************************************************************************/ BOOL EPSFilter::Import_grestore() { // find the graphics state to restore... EPSSavedGraphicState *State = (EPSSavedGraphicState *)GraphicStateStack.GetTail(); if(State == 0) { // no entry on stack - there's an error in the EPS return FALSE; } // swap the path over if(pPath != 0) { pPath->CascadeDelete(); delete pPath; pPath = 0; pInkPath = 0; } pPath = State->pPath; State->pPath = 0; ThePathType = (EPSFilter::PathType)State->ThePathType; if(pPath != 0) { pInkPath = &pPath->InkPath; } else { pInkPath = 0; } // restore the flags EPSFlags = State->EPSFlags; // move the attributes over // number of attributes? INT32 NumAttrs = AttributeManager::GetNumAttributes(); // move the attributes for (INT32 i = 0; i < NumAttrs; i++) { // if there's a temporary one there, delete it if(CurrentAttrs[i].Temp && CurrentAttrs[i].pAttr != 0) { delete CurrentAttrs[i].pAttr; CurrentAttrs[i].pAttr = 0; } // move this one over CurrentAttrs[i].pAttr = State->Attrs[i].pAttr; State->Attrs[i].pAttr = 0; CurrentAttrs[i].Temp = State->Attrs[i].Temp; CurrentAttrs[i].Ignore = State->Attrs[i].Ignore; } // delete this item delete GraphicStateStack.RemoveItem(State); return TRUE; }