/* Copyright (C) 2000, S.R.Haque . This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. DESCRIPTION This is based on code originally written by Stefan Taferner (taferner@kde.org) and also borrows from libwmf (by Martin Vermeer and Caolan McNamara). */ #include #include #include #include #include #include #define PI (3.14159265358979323846) const int KWmf::s_area = 30504; const int KWmf::s_maxHandles = 64; KWmf::KWmf( unsigned dpi) { m_dpi = dpi; m_objectHandles = new WinObjHandle*[s_maxHandles]; } KWmf::~KWmf() { delete[] m_objectHandles; } // // // void KWmf::brushSet( unsigned colour, unsigned style) { m_dc.m_brushColour = colour; m_dc.m_brushStyle = style; } //----------------------------------------------------------------------------- unsigned KWmf::getColour( S32 colour) { unsigned red, green, blue; red = colour & 255; green = (colour >> 8) & 255; blue = (colour >> 16) & 255; return (red << 16) + (green << 8) + blue; } void KWmf::genericArc( TQString type, TQDataStream &operands) { TQPoint topLeft; TQPoint bottomRight; TQPoint start; TQPoint end; topLeft = normalisePoint(operands); bottomRight = normalisePoint(operands); start = normalisePoint(operands); end = normalisePoint(operands); // WMF defines arcs with the major and minor axes of an ellipse, and two points. // From each point draw a line to the center of the ellipse: the intercepts define // the ends of the arc. TQRect ellipse(topLeft, bottomRight); TQPoint centre = ellipse.center(); double startAngle = atan2((double)(centre.y() - start.y()), (double)(centre.x() - start.x())); double stopAngle = atan2((double)(centre.y() - end.y()), (double)(centre.x() - end.x())); startAngle = 180 * startAngle / PI; stopAngle = 180 * stopAngle / PI; gotEllipse(m_dc, type, centre, ellipse.size() / 2, static_cast(startAngle), static_cast(stopAngle)); } int KWmf::handleIndex(void) const { int i; for (i = 0; i < s_maxHandles; i++) { if (!m_objectHandles[i]) return i; } kdError(s_area) << "handle table full !" << endl; return -1; } //----------------------------------------------------------------------------- KWmf::WinObjPenHandle *KWmf::handleCreatePen(void) { WinObjPenHandle *handle = new WinObjPenHandle; int idx = handleIndex(); if (idx >= 0) m_objectHandles[idx] = handle; return handle; } //----------------------------------------------------------------------------- KWmf::WinObjBrushHandle *KWmf::handleCreateBrush(void) { WinObjBrushHandle *handle = new WinObjBrushHandle; int idx = handleIndex(); if (idx >= 0) m_objectHandles[idx] = handle; return handle; } //----------------------------------------------------------------------------- void KWmf::handleDelete(int idx) { if (idx >= 0 && idx < s_maxHandles && m_objectHandles[idx]) { delete m_objectHandles[idx]; m_objectHandles[idx] = NULL; } } // // // void KWmf::invokeHandler( S16 opcode, U32 words, TQDataStream &operands) { typedef void (KWmf::*method)(U32 words, TQDataStream &operands); typedef struct { const char *name; unsigned short opcode; method handler; } opcodeEntry; static const opcodeEntry funcTab[] = { { "ANIMATEPALETTE", 0x0436, 0 }, { "ARC", 0x0817, &KWmf::opArc }, { "BITBLT", 0x0922, 0 }, { "CHORD", 0x0830, 0 }, { "CREATEBRUSHINDIRECT", 0x02FC, &KWmf::opBrushCreateIndirect }, { "CREATEFONTINDIRECT", 0x02FB, 0 }, { "CREATEPALETTE", 0x00F7, 0 }, { "CREATEPATTERNBRUSH", 0x01F9, 0 }, { "CREATEPENINDIRECT", 0x02FA, &KWmf::opPenCreateIndirect }, { "CREATEREGION", 0x06FF, 0 }, { "DELETEOBJECT", 0x01F0, &KWmf::opObjectDelete }, { "DIBBITBLT", 0x0940, 0 }, { "DIBCREATEPATTERNBRUSH",0x0142, 0 }, { "DIBSTRETCHBLT", 0x0b41, 0 }, { "ELLIPSE", 0x0418, &KWmf::opEllipse }, { "ESCAPE", 0x0626, &KWmf::opNoop }, { "EXCLUDECLIPRECT", 0x0415, 0 }, { "EXTFLOODFILL", 0x0548, 0 }, { "EXTTEXTOUT", 0x0a32, 0 }, { "FILLREGION", 0x0228, 0 }, { "FLOODFILL", 0x0419, 0 }, { "FRAMEREGION", 0x0429, 0 }, { "INTERSECTCLIPRECT", 0x0416, 0 }, { "INVERTREGION", 0x012A, 0 }, { "LINETO", 0x0213, &KWmf::opLineTo }, { "MOVETO", 0x0214, &KWmf::opMoveTo }, { "OFFSETCLIPRGN", 0x0220, 0 }, { "OFFSETVIEWPORTORG", 0x0211, 0 }, { "OFFSETWINDOWORG", 0x020F, 0 }, { "PAINTREGION", 0x012B, 0 }, { "PATBLT", 0x061D, 0 }, { "PIE", 0x081A, &KWmf::opPie }, { "POLYGON", 0x0324, &KWmf::opPolygon }, { "POLYLINE", 0x0325, &KWmf::opPolyline }, { "POLYPOLYGON", 0x0538, 0 }, { "REALIZEPALETTE", 0x0035, 0 }, { "RECTANGLE", 0x041B, &KWmf::opRectangle }, { "RESIZEPALETTE", 0x0139, 0 }, { "RESTOREDC", 0x0127, &KWmf::opRestoreDc }, { "ROUNDRECT", 0x061C, 0 }, { "SAVEDC", 0x001E, &KWmf::opSaveDc }, { "SCALEVIEWPORTEXT", 0x0412, 0 }, { "SCALEWINDOWEXT", 0x0410, 0 }, { "SELECTCLIPREGION", 0x012C, 0 }, { "SELECTOBJECT", 0x012D, &KWmf::opObjectSelect }, { "SELECTPALETTE", 0x0234, 0 }, { "SETBKCOLOR", 0x0201, 0 }, { "SETBKMODE", 0x0102, 0 }, { "SETDIBTODEV", 0x0d33, 0 }, { "SETMAPMODE", 0x0103, 0 }, { "SETMAPPERFLAGS", 0x0231, 0 }, { "SETPALENTRIES", 0x0037, 0 }, { "SETPIXEL", 0x041F, 0 }, { "SETPOLYFILLMODE", 0x0106, &KWmf::opPolygonSetFillMode }, { "SETRELABS", 0x0105, 0 }, { "SETROP2", 0x0104, 0 }, { "SETSTRETCHBLTMODE", 0x0107, 0 }, { "SETTEXTALIGN", 0x012E, 0 }, { "SETTEXTCHAREXTRA", 0x0108, 0 }, { "SETTEXTCOLOR", 0x0209, 0 }, { "SETTEXTJUSTIFICATION", 0x020A, 0 }, { "SETVIEWPORTEXT", 0x020E, 0 }, { "SETVIEWPORTORG", 0x020D, 0 }, { "SETWINDOWEXT", 0x020C, &KWmf::opWindowSetExt }, { "SETWINDOWORG", 0x020B, &KWmf::opWindowSetOrg }, { "STRETCHBLT", 0x0B23, 0 }, { "STRETCHDIB", 0x0f43, 0 }, { "TEXTOUT", 0x0521, 0 }, { NULL, 0, 0 } }; unsigned i; method result; // Scan lookup table for operation. for (i = 0; funcTab[i].name; i++) { if (funcTab[i].opcode == opcode) { break; } } // Invoke handler. result = funcTab[i].handler; if (!result) { if (funcTab[i].name) kdError(s_area) << "invokeHandler: unsupported opcode: " << funcTab[i].name << " operands: " << words << endl; else kdError(s_area) << "invokeHandler: unsupported opcode: 0x" << TQString::number(opcode, 16) << " operands: " << words << endl; // Skip data we cannot use. for (i = 0; i < words; i++) { S16 discard; operands >> discard; } } else { kdDebug(s_area) << "invokeHandler: opcode: " << funcTab[i].name << " operands: " << words << endl; // We don't invoke the handler directly on the incoming operands, but // via a temporary datastream. This adds overhead, but eliminates the // need for the individual handlers to read *exactly* the right amount // of data (thus speeding development, and possibly adding some // future-proofing). if (words) { TQByteArray *record = new TQByteArray(words * 2); TQDataStream *body; operands.readRawBytes(record->data(), words * 2); body = new TQDataStream(*record, IO_ReadOnly); body->setByteOrder(TQDataStream::LittleEndian); (this->*result)(words, *body); delete body; delete record; } else { TQDataStream *body = new TQDataStream(); (this->*result)(words, *body); delete body; } } } TQPoint KWmf::normalisePoint( TQDataStream &operands) { S16 x; S16 y; operands >> x >> y; return TQPoint((x - m_windowOrgX) * m_windowFlipX / m_dpi, (y - m_windowOrgY) * m_windowFlipY / m_dpi); } TQSize KWmf::normaliseSize( TQDataStream &operands) { S16 width; S16 height; operands >> width >> height; return TQSize(width / m_dpi, height / m_dpi); } bool KWmf::parse( const TQString &file) { TQFile in(file); if (!in.open(IO_ReadOnly)) { kdError(s_area) << "Unable to open input file!" << endl; in.close(); return false; } TQDataStream stream(&in); bool result = parse(stream, in.size()); in.close(); return result; } bool KWmf::parse( TQDataStream &stream, unsigned size) { int startedAt; bool isPlaceable; bool isEnhanced; startedAt = stream.device()->at(); stream.setByteOrder(TQDataStream::LittleEndian); // Great, I love TQt ! for (int i = 0; i < s_maxHandles; i++) m_objectHandles[i] = NULL; struct RECT { S16 left; S16 top; S16 right; S16 bottom; }; struct RECTL { S32 left; S32 top; S32 right; S32 bottom; }; struct SIZE { S16 width; S16 height; }; struct SIZEL { S32 width; S32 height; }; struct WmfEnhMetaHeader { S32 iType; // Record type EMR_HEADER S32 nSize; // Record size in bytes. This may be greater // than the sizeof(ENHMETAHEADER). RECTL rclBounds; // Inclusive-inclusive bounds in device units RECTL rclFrame; // Inclusive-inclusive Picture Frame of metafile // in .01 mm units S32 dSignature; // Signature. Must be ENHMETA_SIGNATURE. S32 nVersion; // Version number S32 nBytes; // Size of the metafile in bytes S32 nRecords; // Number of records in the metafile S16 nHandles; // Number of handles in the handle table // Handle index zero is reserved. S16 sReserved; // Reserved. Must be zero. S32 nDescription; // Number of chars in the unicode description string // This is 0 if there is no description string S32 offDescription; // Offset to the metafile description record. // This is 0 if there is no description string S32 nPalEntries; // Number of entries in the metafile palette. SIZEL szlDevice; // Size of the reference device in pels SIZEL szlMillimeters; // Size of the reference device in millimeters }; #define ENHMETA_SIGNATURE 0x464D4520 struct WmfMetaHeader { S16 mtType; S16 mtHeaderSize; S16 mtVersion; S32 mtSize; S16 mtNoObjects; S32 mtMaxRecord; S16 mtNoParameters; }; struct WmfPlaceableHeader { S32 key; S16 hmf; RECT bbox; S16 inch; S32 reserved; S16 checksum; }; #define APMHEADER_KEY 0x9AC6CDD7L WmfPlaceableHeader pheader; WmfEnhMetaHeader eheader; WmfMetaHeader header; S16 checksum; int fileAt; //----- Read placeable metafile header stream >> pheader.key; isPlaceable = (pheader.key == (S32)APMHEADER_KEY); if (isPlaceable) { stream >> pheader.hmf; stream >> pheader.bbox.left; stream >> pheader.bbox.top; stream >> pheader.bbox.right; stream >> pheader.bbox.bottom; stream >> pheader.inch; stream >> pheader.reserved; stream >> pheader.checksum; checksum = 0; S16 *ptr = (S16 *)&pheader; // XOR in each of the S16s. for (unsigned i = 0; i < sizeof(WmfPlaceableHeader)/sizeof(S16); i++) { checksum ^= ptr[i]; } if (pheader.checksum != checksum) isPlaceable = false; m_dpi = (unsigned)((double)pheader.inch / m_dpi); m_windowOrgX = pheader.bbox.left; m_windowOrgY = pheader.bbox.top; if (pheader.bbox.right > pheader.bbox.left) m_windowFlipX = 1; else m_windowFlipX = -1; if (pheader.bbox.bottom > pheader.bbox.top) m_windowFlipY = 1; else m_windowFlipY = -1; } else { stream.device()->at(startedAt); m_dpi = (unsigned)((double)576 / m_dpi); m_windowOrgX = 0; m_windowOrgY = 0; m_windowFlipX = 1; m_windowFlipY = 1; } //----- Read as enhanced metafile header fileAt = stream.device()->at(); stream >> eheader.iType; stream >> eheader.nSize; stream >> eheader.rclBounds.left; stream >> eheader.rclBounds.top; stream >> eheader.rclBounds.right; stream >> eheader.rclBounds.bottom; stream >> eheader.rclFrame.left; stream >> eheader.rclFrame.top; stream >> eheader.rclFrame.right; stream >> eheader.rclFrame.bottom; stream >> eheader.dSignature; isEnhanced = (eheader.dSignature == ENHMETA_SIGNATURE); if (isEnhanced) // is it really enhanced ? { stream >> eheader.nVersion; stream >> eheader.nBytes; stream >> eheader.nRecords; stream >> eheader.nHandles; stream >> eheader.sReserved; stream >> eheader.nDescription; stream >> eheader.offDescription; stream >> eheader.nPalEntries; stream >> eheader.szlDevice.width; stream >> eheader.szlDevice.height; stream >> eheader.szlMillimeters.width; stream >> eheader.szlMillimeters.height; kdError(s_area) << "WMF Extended Header NOT YET IMPLEMENTED, SORRY." << endl; /* if (mSingleStep) { debug(" iType=%d", eheader.iType); debug(" nSize=%d", eheader.nSize); debug(" rclBounds=(%ld;%ld;%ld;%ld)", eheader.rclBounds.left, eheader.rclBounds.top, eheader.rclBounds.right, eheader.rclBounds.bottom); debug(" rclFrame=(%ld;%ld;%ld;%ld)", eheader.rclFrame.left, eheader.rclFrame.top, eheader.rclFrame.right, eheader.rclFrame.bottom); debug(" dSignature=%d", eheader.dSignature); debug(" nVersion=%d", eheader.nVersion); debug(" nBytes=%d", eheader.nBytes); } debug("NOT YET IMPLEMENTED, SORRY."); */ return false; } else // no, not enhanced { // debug("WMF Header"); //----- Read as standard metafile header stream.device()->at(fileAt); stream >> header.mtType; stream >> header.mtHeaderSize; stream >> header.mtVersion; stream >> header.mtSize; stream >> header.mtNoObjects; stream >> header.mtMaxRecord; stream >> header.mtNoParameters; /* if (mSingleStep) { debug(" mtType=%u", header.mtType); debug(" mtHeaderSize=%u", header.mtHeaderSize); debug(" mtVersion=%u", header.mtVersion); debug(" mtSize=%ld", header.mtSize); } */ } walk((size - (stream.device()->at() - startedAt)) / 2, stream); return true; } void KWmf::opArc( U32 /*words*/, TQDataStream &operands) { genericArc("arc", operands); } void KWmf::opBrushCreateIndirect( U32 /*words*/, TQDataStream &operands) { static TQt::BrushStyle hatchedStyleTab[] = { TQt::HorPattern, TQt::FDiagPattern, TQt::BDiagPattern, TQt::CrossPattern, TQt::DiagCrossPattern }; static TQt::BrushStyle styleTab[] = { TQt::SolidPattern, TQt::NoBrush, TQt::FDiagPattern, // hatched TQt::Dense4Pattern, // should be custom bitmap pattern TQt::HorPattern, // should be BS_INDEXED (?) TQt::VerPattern, // should be device-independent bitmap TQt::Dense6Pattern, // should be device-independent packed-bitmap TQt::Dense2Pattern, // should be BS_PATTERN8x8 TQt::Dense3Pattern // should be device-independent BS_DIBPATTERN8x8 }; TQt::BrushStyle style; WinObjBrushHandle *handle = handleCreateBrush(); S16 arg; S32 colour; S16 discard; operands >> arg >> colour; handle->m_colour = getColour(colour); if (arg == 2) { operands >> arg; if (arg >= 0 && arg < 6) { style = hatchedStyleTab[arg]; } else { kdError(s_area) << "createBrushIndirect: invalid hatched brush " << arg << endl; style = TQt::SolidPattern; } } else if (arg >= 0 && arg < 9) { style = styleTab[arg]; operands >> discard; } else { kdError(s_area) << "createBrushIndirect: invalid brush " << arg << endl; style = TQt::SolidPattern; operands >> discard; } handle->m_style = style; } void KWmf::opEllipse( U32 /*words*/, TQDataStream &operands) { TQPoint topLeft; TQPoint bottomRight; topLeft = normalisePoint(operands); bottomRight = normalisePoint(operands); TQRect ellipse(topLeft, bottomRight); gotEllipse(m_dc, "full", ellipse.center(), ellipse.size() / 2, 0, 0); } void KWmf::opLineTo( U32 /*words*/, TQDataStream &operands) { TQPoint lineTo; lineTo = normalisePoint(operands); TQPointArray points(2); points.setPoint(0, m_lineFrom); points.setPoint(1, lineTo); gotPolyline(m_dc, points); // Remember this point for next time. m_lineFrom = lineTo; } void KWmf::opMoveTo( U32 /*words*/, TQDataStream &operands) { m_lineFrom = normalisePoint(operands); } void KWmf::opNoop( U32 words, TQDataStream &operands) { skip(words, operands); } //----------------------------------------------------------------------------- void KWmf::opObjectDelete( U32 /*words*/, TQDataStream &operands) { S16 idx; operands >> idx; handleDelete(idx); } //----------------------------------------------------------------------------- void KWmf::opObjectSelect( U32 /*words*/, TQDataStream &operands) { S16 idx; operands >> idx; if (idx >= 0 && idx < s_maxHandles && m_objectHandles[idx]) m_objectHandles[idx]->apply(*this); } // // // void KWmf::opPenCreateIndirect( U32 /*words*/, TQDataStream &operands) { static TQt::PenStyle styleTab[] = { TQt::SolidLine, TQt::DashLine, TQt::DotLine, TQt::DashDotLine, TQt::DashDotDotLine, TQt::NoPen, TQt::SolidLine, // PS_INSIDEFRAME TQt::SolidLine, // PS_USERSTYLE TQt::SolidLine // PS_ALTERNATE }; WinObjPenHandle *handle = handleCreatePen(); S16 arg; S32 colour; operands >> arg; if (arg >= 0 && arg < 8) { handle->m_style = styleTab[arg]; } else { kdError(s_area) << "createPenIndirect: invalid pen " << arg << endl; handle->m_style = TQt::SolidLine; } operands >> arg; handle->m_width = arg; operands >> arg >> colour; handle->m_colour = getColour(colour); } void KWmf::opPie( U32 /*words*/, TQDataStream &operands) { genericArc("pie", operands); } void KWmf::opPolygonSetFillMode( U32 /*words*/, TQDataStream &operands) { S16 tmp; operands >> tmp; m_dc.m_winding = tmp != 0; } void KWmf::opPolygon( U32 /*words*/, TQDataStream &operands) { S16 tmp; operands >> tmp; TQPointArray points(tmp); for (int i = 0; i < tmp; i++) { points.setPoint(i, normalisePoint(operands)); } gotPolygon(m_dc, points); } void KWmf::opPolyline( U32 /*words*/, TQDataStream &operands) { S16 tmp; operands >> tmp; TQPointArray points(tmp); for (int i = 0; i < tmp; i++) { points.setPoint(i, normalisePoint(operands)); } gotPolyline(m_dc, points); } void KWmf::opRectangle( U32 /*words*/, TQDataStream &operands) { TQPoint topLeft; TQSize size; topLeft = normalisePoint(operands); size = normaliseSize(operands); TQRect rect(topLeft, size); TQPointArray points(4); points.setPoint(0, topLeft); points.setPoint(1, rect.topRight()); points.setPoint(2, rect.bottomRight()); points.setPoint(3, rect.bottomLeft()); gotRectangle(m_dc, points); } void KWmf::opRestoreDc( U32 /*words*/, TQDataStream &operands) { S16 pop; S16 i; operands >> pop; for (i = 0; i < pop; i++) { m_dc = m_savedDcs.pop(); } } void KWmf::opSaveDc( U32 /*words*/, TQDataStream &/*operands*/) { m_savedDcs.push(m_dc); // TBD: reinitialise m_dc. } void KWmf::opWindowSetOrg( U32 /*words*/, TQDataStream &operands) { S16 top; S16 left; operands >> top >> left; m_windowOrgX = left; m_windowOrgY = top; } void KWmf::opWindowSetExt( U32 /*words*/, TQDataStream &operands) { S16 height; S16 width; operands >> height >> width; if (width > 0) m_windowFlipX = 1; else m_windowFlipX = -1; if (height > 0) m_windowFlipY = 1; else m_windowFlipY = -1; } void KWmf::penSet( unsigned colour, unsigned style, unsigned width) { m_dc.m_penColour = colour; m_dc.m_penStyle = style; m_dc.m_penWidth = width; } void KWmf::skip( U32 words, TQDataStream &operands) { if ((int)words < 0) { kdError(s_area) << "skip: " << (int)words << endl; return; } if (words) { U32 i; S16 discard; kdDebug(s_area) << "skip: " << words << endl; for (i = 0; i < words; i++) { operands >> discard; } } } void KWmf::walk( U32 words, TQDataStream &operands) { // Read bits: // // struct WmfMetaRecord // { // S32 rdSize; // Record size (in words) of the function // S16 rdFunction; // Record function number // S16 rdParm[1]; // WORD array of parameters // }; // // struct WmfEnhMetaRecord // { // S32 iType; // Record type EMR_xxx // S32 nSize; // Record size in bytes // S32 dParm[1]; // DWORD array of parameters // }; S32 wordCount; S16 opcode; U32 length = 0; while (length < words) { operands >> wordCount; operands >> opcode; // If we get some duff data, protect ourselves. if (length + wordCount > words) { wordCount = words - length; } length += wordCount; if (opcode == 0) { // This appears to be an EOF marker. break; } // Package the arguments... invokeHandler(opcode, wordCount - 3, operands); } // Eat unexpected data that the caller may expect us to consume. skip(words - length, operands); } KWmf::DrawContext::DrawContext() { // TBD: initalise with proper values. m_brushColour = 0x808080; m_brushStyle = 1; m_penColour = 0x808080; m_penStyle = 1; m_penWidth = 1; } void KWmf::WinObjBrushHandle::apply( KWmf &p) { p.brushSet(m_colour, m_style); } void KWmf::WinObjPenHandle::apply( KWmf &p) { p.penSet(m_colour, m_style, m_width); }