/*
For general Scribus (>=1.3.2) copyright and licensing information please refer
to the COPYING file provided with the program. Following this notice may exist
a copyright and/or license notice that predates the release of Scribus 1.3.2
for which a new license (GPL+exception) is in place.
*/

/* Code inspired by KOffice libwmf and adapted for Scribus by Jean Ghali */

#include "scconfig.h"
#include "wmfimport.h"

#include <QBuffer>
#include <QCursor>
#include <QDataStream>
#include <QDrag>
#include <QFile>
#include <QMimeData>
#include <QPainterPath>
#include <QRegExp>
#include <QScopedArrayPointer>
#include <QTextCodec>

#include "commonstrings.h"
#include "fpointarray.h"
#include "loadsaveplugin.h"
#include "pageitem.h"
#include "prefsfile.h"
#include "prefsmanager.h"
#include "scmimedata.h"
#include "scraction.h"
#include "scribusXml.h"
#include "scribuscore.h"
#include "scribusdoc.h"
#include "scribusview.h"
#include "selection.h"
#include "ui/customfdialog.h"
#include "ui/propertiespalette.h"
#include "ui/scmwmenumanager.h"
#include "undomanager.h"
#include "util.h"
#include "util_math.h"

#include "wmfstruct.h"
#include "wmfcmd.h"
#include "wmfcontext.h"
#include "wmfhandle.h"
#include "metafuncs.h"

using namespace std;

#define WMFIMPORT_DEBUG 0
#define MAX_OBJHANDLE   128

#define ANSI_CHARSET			0		// ANSI charset (Windows-1252)
#define DEFAULT_CHARSET			1
#define SYMBOL_CHARSET			2
#define MAC_CHARSET				77
#define SHIFTJIS_CHARSET		128		// Shift JIS charset    (Windows-932)
#define HANGEUL_CHARSET			129		// Hangeul charset      (Windows-949)
#define JOHAB_CHARSET			130		// Johab charset        (Windows-1361)
#define GB2312_CHARSET			134		// GB2312 charset       (Windows-936)
#define CHINESEBIG5_CHARSET		136		// Chinese Big5 charset (Windows-950)
#define GREEK_CHARSET			161		// Greek charset        (Windows-1253)
#define TURKISH_CHARSET			162		// Turkish charset      (Windows-1254)
#define VIETNAMESE_CHARSET		163		// Vietnamese charset   (Windows-1258)
#define HEBREW_CHARSET			177		// Hebrew charset       (Windows-1255)
#define ARABIC_CHARSET			178		// Arabic charset       (Windows-1256)
#define BALTIC_CHARSET			186		// Baltic charset       (Windows-1257)
#define RUSSIAN_CHARSET			204		// Cyrillic charset     (Windows-1251)
#define THAI_CHARSET			222		// Thai charset         (Windows-874)
#define EASTEUROPE_CHARSET		238		// Eastern european charset (Windows-1250)
#define OEM_CHARSET				255

/* Symbol to unicode conversion table copied from http://root.cern.ch/viewvc/trunk/qt/src/TQtSymbolCodec.cxx
This code is placed under GPL licence (http://root.cern.ch/viewvc/trunk/LICENSE) */
static const ushort greek_symbol_to_unicode[64] = {
//
//  upper case letters:
//  ----------------
//   Alpha   Beta     Chi   Delta   Epsilon   Phi    Gamma    Eta
	0x0391, 0x0392, 0x03A7, 0x0394, 0x0395, 0x03A6, 0x0393, 0x0397,
//   Iota   Theta(S) Kappa  Lambda    Mu      Nu    Omicron   Pi
	0x0399, 0x03D1, 0x039A, 0x039B, 0x039C, 0x039D, 0x039F, 0x03A0,
//  Theta    Rho    Sigma    Tau    Upsilon Stigma   Omega     Xi
	0x0398, 0x03A1, 0x03A3, 0x03A4, 0x03A5, 0x03DB, 0x03A9, 0x039E,
//   Psi     Zeta   Sigma    Tau    Upsilon  ????   Omega     Xi
	0x03A8, 0x0396, 0x03EA, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD,
//
//  lower case letters:
//  ----------------
//   Alpha   Beta    Chi    Delta   Epsilon   Phi    Gamma    Eta
	0x03B1, 0x03B2, 0x03C7, 0x03B4, 0x03B5, 0x03D5, 0x03B3, 0x03B7,
//   Iota   Phi (2) Kappa   Lambda    Mu      Nu    Omicron   Pi
	0x03B9, 0x03C6, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BF, 0x03C0,
//  Theta    Rho    Sigma    Tau    Upsilon OmegaPi Omega     Xi
	0x03B8, 0x03C1, 0x03C3, 0x03C4, 0x03C5, 0x03D6, 0x03B9, 0x03BE,
//   Psi     Zeta   Sigma    Tau    Upsilon  ????   Omega     Xi
	0x03C8, 0x03B6, 0x03EA, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD
};

/* Symbol to unicode conversion table copied from http://root.cern.ch/viewvc/trunk/qt/src/TQtSymbolCodec.cxx
This code is placed under GPL licence (http://root.cern.ch/viewvc/trunk/LICENSE) */
static const ushort symbol_to_unicode[96] = {
//
//  upper case letters:
//  ----------------
// UPSILON  NUMBER  LessEq   Div   INFINITY ???????  CLUB   DIAMOND     
	0x03D2, 0x0374, 0x2264, 0x2215, 0x221E, 0x0395, 0x2663, 0x2666,
//   HEART  SPADE   <--->   <---             -->             RING
	0x2665, 0x2660, 0x2194, 0x2190, 0x2191, 0x2192, 0x2193, 0x2218,
//  Theta    Rho    Great    ????   Propor  diff    bullet   ????
	0x0398, 0x03A1, 0x2265, 0x03A4, 0x221D, 0x2202, 0x2219, 0x03A8,
//   NonEq  Ident    ????   ellips   div     minus  ?????   aleph
	0x2260, 0x2261, 0xFFFD, 0x22EF, 0x2223, 0x2212, 0xFFFD, 0xFFFD,
//
//  lower case letters:
//  ----------------
//   Alpha   Beta    Chi    Delta   Epsilon   Phi    Gamma    Eta
	0x03B1, 0x03B2, 0x03C7, 0x03B4, 0x03B5, 0x03D5, 0x03B3, 0x03B7,
//   Iota   Phi (2) Kappa   Lambda    Mu      Nu    Omicron   Pi
	0x03B9, 0x03C6, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BF, 0x03C0,
//  Theta    Rho    Sigma    Tau    Upsilon OmegaPi Omega     Xi
	0x03B8, 0x03C1, 0x03C3, 0x03C4, 0x03C5, 0x03D6, 0x03B9, 0x03BE,
//   Psi     Zeta   arrow    Tau    Upsilon  ????   Omega     Xi
	0x03C8, 0x03B6, 0x21D4, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD,
//  lower case letters:
//  ----------------
//   Alpha   Beta    Chi    Delta   Sigma     Phi    Gamma    Eta
	0x03B1, 0x03B2, 0x03C7, 0x03B4, 0x2211, 0x03D5, 0x03B3, 0x03B7,
//   Iota   Phi (2) Kappa   Lambda    Mu      Nu    Omicron   Pi
	0x03B9, 0x03C6, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BF, 0x03C0,
//  Theta   Integr  Sigma    Tau    Upsilon OmegaPi Omega     Xi
	0x03B8, 0x222b, 0x03C3, 0x03C4, 0x03C5, 0x03D6, 0x03B9, 0x03BE,
//   Psi     Zeta   Sigma    Tau    Upsilon  ????   Omega     Xi
	0x03C8, 0x03B6, 0x03EA, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD
};

WMFImport::WMFImport(ScribusDoc* doc, int flags)
         : interactive(flags & LoadSavePlugin::lfInteractive),
           m_Doc(doc)
{	
	m_tmpSel = new Selection(this, false);
}

WMFImport::~WMFImport()
{
	qDeleteAll(m_commands);
	m_commands.clear();
	delete m_tmpSel;
	if (m_ObjHandleTab)
	{
		for (int i = 0; i < MAX_OBJHANDLE; ++i)
		{
			if (m_ObjHandleTab[i])
				delete m_ObjHandleTab[i];
		}
		delete[] m_ObjHandleTab;
	}
}

QString WMFImport::importColor(const QColor& color)
{
	ScColor tmp;
	tmp.fromQColor(color);
	tmp.setSpotColor(false);
	tmp.setRegistrationColor(false);
	QString pNam = "FromWMF"+color.name();
	QString fNam = m_Doc->PageColors.tryAddColor(pNam, tmp);
	if (fNam == pNam)
		importedColors.append(pNam);
	return fNam;
}

QColor WMFImport::colorFromParam(const short* params )
{
	unsigned int colorRef = toDWord(params) & 0xffffff;
	int red      = colorRef & 255;
	int green    = (colorRef >> 8) & 255;
	int blue     = (colorRef >> 16) & 255;

	return QColor(red, green, blue);
}

QTextCodec* WMFImport::codecFromCharset(int charset)
{
	QTextCodec* codec = nullptr;
	if (charset == DEFAULT_CHARSET || charset == OEM_CHARSET)
		codec = QTextCodec::codecForLocale();
	else if (charset == ANSI_CHARSET)
		codec = QTextCodec::codecForName("windows-1252");
	/*else if (charset == SYMBOL_CHARSET)
		codec = QTextCodec::codecForLocale();// Not supported yet*/
	else if (charset == MAC_CHARSET)
		codec = QTextCodec::codecForName("Apple Roman");
	else if (charset == SHIFTJIS_CHARSET)
		codec = QTextCodec::codecForName("Shift-JIS");
	else if (charset == HANGEUL_CHARSET)
		codec = QTextCodec::codecForName("CP949");
	else if (charset == GB2312_CHARSET)
		codec = QTextCodec::codecForName("CP936");
	else if (charset == CHINESEBIG5_CHARSET)
		codec = QTextCodec::codecForName("Big5");
	else if (charset == GREEK_CHARSET)
		codec = QTextCodec::codecForName("windows-1253");
	else if (charset == TURKISH_CHARSET)
		codec = QTextCodec::codecForName("windows-1254");
	else if (charset == VIETNAMESE_CHARSET)
		codec = QTextCodec::codecForName("windows-1258");
	else if (charset == HEBREW_CHARSET)
		codec = QTextCodec::codecForName("windows-1255");
	else if (charset == ARABIC_CHARSET)
		codec = QTextCodec::codecForName("windows-1256");
	else if (charset == BALTIC_CHARSET)
		codec = QTextCodec::codecForName("windows-1257");
	else if (charset == RUSSIAN_CHARSET)
		codec = QTextCodec::codecForName("windows-1251");
	else if (charset == THAI_CHARSET)
		codec = QTextCodec::codecForName("CP874");
	else if (charset == EASTEUROPE_CHARSET)
		codec = QTextCodec::codecForName("windows-1250");
	return codec;
}

QString WMFImport::symbolToUnicode(const QByteArray& chars) const
{
	/*Code under GPL licence (http://root.cern.ch/viewvc/trunk/LICENSE)
	taken from http://root.cern.ch/viewvc/trunk/qt/src/TQtSymbolCodec.cxx*/
	QString r;
	const auto* c = (const unsigned char*) chars.data();
	
	if (chars.isEmpty())
		return r;
	
	for (int i = 0; i < chars.size(); ++i) 
	{
		if (64 < c[i] && c[i] <= 64 + 32)
			r.append(QChar(greek_symbol_to_unicode[c[i] - 65]));
		else if (64 + 32 + 1 <= c[i] && c[i] < 64 + 32 + 32 + 1)
			r.append(QChar(greek_symbol_to_unicode[c[i] - 65 - 32] + 32));
		else if (161 <= c[i])
			r.append(QChar(symbol_to_unicode[c[i] - 161]));
		else
			r.append(QChar(c[i]));
	}
	return r;
}

FPointArray WMFImport::pointsFromParam(short num, const short* params) const
{
	FPointArray points;
	points.resize(num);

	for (int i = 0; i < num; i++, params += 2)
		points.setPoint(i, params[0], params[1]);

	return points;
}

FPointArray WMFImport::pointsToPolyline(const FPointArray& points, bool closePath) const
{
	bool bFirst = true;
	double x = 0.0;
	double y = 0.0;
	FPointArray polyline;
	polyline.svgInit();

	for (int i = 0; i < points.size(); ++i )
	{
		const FPoint& point = points.point(i);
		x = point.x();
		y = point.y();
		if (bFirst)
		{
			polyline.svgMoveTo(x, y);
			bFirst = false;
		}
		else
		{
			polyline.svgLineTo(x, y);
		}
	}
	if (closePath && (points.size() > 4))
		polyline.svgClosePath();
	return polyline;
}

void WMFImport::pointsToAngle(double xStart, double yStart, double xEnd, double yEnd, double& angleStart, double& angleLength) const
{
	double aStart  = atan2(yStart, xStart);
	double aLength = atan2(yEnd, xEnd) - aStart;

	angleStart  = (int)(aStart * 180.0 / 3.14166);
	angleLength = (int)(aLength * 180.0 / 3.14166);
	if (angleLength < 0)
		angleLength = 360.0 + angleLength;
}

QImage WMFImport::readThumbnail(const QString& fname)
{
	if (!loadWMF(fname))
	{
		importFailed = true;
		return QImage();
	}
	QString CurDirP = QDir::currentPath();
	QFileInfo efp(fname);
	QDir::setCurrent(efp.path());
	double scale  = (m_Dpi > 0) ? 72.0 / m_Dpi : 0.05;
	double width  = m_BBox.width() * scale;
	double height = m_BBox.height() * scale;
	m_Doc = new ScribusDoc();
	m_Doc->setup(0, 1, 1, 1, 1, "Custom", "Custom");
	m_Doc->setPage(width, height, 0, 0, 0, 0, 0, 0, false, false);
	m_Doc->addPage(0);
	m_Doc->setGUI(false, ScCore->primaryMainWindow(), nullptr);
	m_Doc->setLoading(true);
	m_Doc->DoDrawing = false;
	m_Doc->scMW()->setScriptRunning(true);
	m_Doc->PageColors.ensureDefaultColors();
	QList<PageItem*> Elements = parseWmfCommands();
	m_tmpSel->clear();
	QImage tmpImage;
	if (!Elements.isEmpty())
	{
		if (Elements.count() > 1)
			m_Doc->groupObjectsList(Elements);
		m_Doc->DoDrawing = true;
		m_Doc->m_Selection->delaySignalsOn();
		m_tmpSel->addItems(Elements);
		m_tmpSel->setGroupRect();
		double xs = m_tmpSel->width();
		double ys = m_tmpSel->height();
		tmpImage = Elements.at(0)->DrawObj_toImage(500);
		tmpImage.setText("XSize", QString("%1").arg(xs));
		tmpImage.setText("YSize", QString("%1").arg(ys));
		m_Doc->m_Selection->delaySignalsOff();
		m_Doc->setLoading(false);
	}
	m_Doc->scMW()->setScriptRunning(false);
	delete m_Doc;
	m_Doc = nullptr;
	QDir::setCurrent(CurDirP);
	return tmpImage;
}

bool WMFImport::importFile(const QString& fname, const TransactionSettings& trSettings, int flags)
{
	if (!loadWMF(fname))
	{
		importFailed = true;
		return false;
	}
	QString CurDirP = QDir::currentPath();
	QFileInfo efp(fname);
	QDir::setCurrent(efp.path());
	bool success = importWMF(trSettings, flags);
	QDir::setCurrent(CurDirP);
	return success;
}

bool WMFImport::loadWMF(const QString& fileName)
{
	QFile file(fileName);
	if (!file.exists())
	{
		cerr << "File " << QFile::encodeName(fileName).data() << " does not exist" << std::endl;
		return false;
	}
	if (!file.open(QIODevice::ReadOnly))
	{
		cerr << "Cannot open file " << QFile::encodeName(fileName).data() << std::endl;
		return false;
	}

	QByteArray ba = file.readAll();
	file.close();

	QBuffer buffer(&ba);
	buffer.open(QIODevice::ReadOnly);
	return loadWMF(buffer);
}

bool WMFImport::loadWMF(QBuffer& buffer)
{
	QDataStream st;
	WmfEnhMetaHeader eheader;
	WmfMetaHeader header;
	WmfPlaceableHeader pheader;
	WORD16 checksum;
	int i, idx;
	qint64 filePos;
	WmfCmd *cmd;
	WORD32 rdSize;
	WORD16 rdFunc;

	header.mtSize = 0;
	header.mtHeaderSize = 0;
	header.mtNoParameters = 0;

	qDeleteAll(m_commands);
	m_commands.clear();

	st.setDevice(&buffer);
	st.setByteOrder(QDataStream::LittleEndian); // Great, I love Qt !

	//----- Read placeable metafile header
	st >> pheader.key;
	m_IsPlaceable = (pheader.key == (WORD32) APMHEADER_KEY);
	if (m_IsPlaceable)
	{
		st >> pheader.hmf;
		st >> pheader.bbox.left;
		st >> pheader.bbox.top;
		st >> pheader.bbox.right;
		st >> pheader.bbox.bottom;
		st >> pheader.inch;
		st >> pheader.reserved;
		st >> pheader.checksum;
		checksum = calcCheckSum(&pheader);
		if (pheader.checksum != checksum)
			m_IsPlaceable = false;

		m_Dpi = pheader.inch;
		m_BBox.setLeft(pheader.bbox.left);
		m_BBox.setTop(pheader.bbox.top);
		m_BBox.setRight(pheader.bbox.right);
		m_BBox.setBottom(pheader.bbox.bottom);
		m_HeaderBoundingBox = m_BBox;
		if (WMFIMPORT_DEBUG)
		{
			cerr << endl << "-------------------------------------------------" << endl;
			cerr << "WMF Placeable Header ( " << static_cast<int>(sizeof( pheader ) ) << "):" << endl;
			cerr << "  bbox=( " << m_BBox.left() << "; " << m_BBox.top() << "; " << m_BBox.width() << "; " << m_BBox.height() << ")" << endl;
			cerr << "  inch=" << pheader.inch << endl;
			cerr << "  checksum=" << pheader.checksum << "( " << (pheader.checksum == checksum ? "ok" : "wrong") << " )" << endl;
		}
	}
	else buffer.seek(0);

	//----- Read as enhanced metafile header
	filePos = buffer.pos();
	st >> eheader.iType;
	st >> eheader.nSize;
	st >> eheader.rclBounds.left;
	st >> eheader.rclBounds.top;
	st >> eheader.rclBounds.right;
	st >> eheader.rclBounds.bottom;
	st >> eheader.rclFrame.left;
	st >> eheader.rclFrame.top;
	st >> eheader.rclFrame.right;
	st >> eheader.rclFrame.bottom;
	st >> eheader.dSignature;
	m_IsEnhanced = (eheader.dSignature == ENHMETA_SIGNATURE);
	if (m_IsEnhanced) // is it really enhanced ?
	{
		st >> eheader.nVersion;
		st >> eheader.nBytes;
		st >> eheader.nRecords;
		st >> eheader.nHandles;
		st >> eheader.sReserved;
		st >> eheader.nDescription;
		st >> eheader.offDescription;
		st >> eheader.nPalEntries;
		st >> eheader.szlDevice.width;
		st >> eheader.szlDevice.height;
		st >> eheader.szlMillimeters.width;
		st >> eheader.szlMillimeters.height;

		if (WMFIMPORT_DEBUG)
		{
			cerr << endl << "-------------------------------------------------" << endl;
			cerr << "WMF Extended Header:" << endl;
			cerr << "  iType=" << eheader.iType << endl;
			cerr << "  nSize=" << eheader.nSize << endl;
			cerr << "  rclBounds=( " << eheader.rclBounds.left << "; " << eheader.rclBounds.top << "; "
					  << eheader.rclBounds.right << "; " << eheader.rclBounds.bottom << ")" << endl;
			cerr << "  rclFrame=( " << eheader.rclFrame.left << "; " << eheader.rclFrame.top << "; "
					  << eheader.rclFrame.right << "; " << eheader.rclFrame.bottom << ")" << endl;
			cerr << "  nBytes=" << eheader.nBytes << endl;
			cerr << "\nNOT YET IMPLEMENTED, SORRY." << endl;
		}
	}
	else // no, not enhanced
	{
		//----- Read as standard metafile header
		buffer.seek(filePos);
		st >> header.mtType;
		st >> header.mtHeaderSize;
		st >> header.mtVersion;
		st >> header.mtSize;
		st >> header.mtNoObjects;
		st >> header.mtMaxRecord;
		st >> header.mtNoParameters;
		if (WMFIMPORT_DEBUG)
			cerr << "WMF Header: " <<  "mtSize=" << header.mtSize << endl;
	}

	//----- Test header validity
	m_Valid = ((header.mtHeaderSize == 9) && (header.mtNoParameters == 0)) || m_IsEnhanced || m_IsPlaceable;
	if (m_Valid && !m_IsEnhanced)
	{
		//----- Read Metafile Records
		rdFunc = -1;
		while (!st.atEnd() && (rdFunc != 0))
		{
			st >> rdSize;
			st >> rdFunc;

			idx = findFunc(rdFunc);
			rdSize -= 3;

			cmd = new WmfCmd;
			m_commands.append(cmd);

			cmd->funcIndex = idx;
			cmd->numParam = rdSize;
			cmd->params = new WORD16[ rdSize ];

			for (i = 0; i < rdSize && !st.atEnd(); i++)
				st >> cmd->params[i];

			if (rdFunc == 0x020B && !m_IsPlaceable)
			{
				// SETWINDOWORG: dimensions
				m_BBox.setLeft(qMin((int) cmd->params[1], m_BBox.left()));
				m_BBox.setTop(qMin((int) cmd->params[0], m_BBox.top()));
			}
			if (rdFunc == 0x020C && !m_IsPlaceable)
			{
				// SETWINDOWEXT: dimensions
				m_BBox.setWidth((int) cmd->params[1]);
				m_BBox.setHeight((int) cmd->params[0]);
			}

			if (i < rdSize)
			{
				cerr << "WMF : file truncated !" << endl;
				return false;
			}
		}
		m_BBox = m_BBox.normalized();
		//----- Test records validities
		m_Valid = (rdFunc == 0) && (m_BBox.width() != 0) && (m_BBox.height() != 0);
		if (!m_Valid)
			cerr << "WMF : incorrect file format !" << endl;
	}
	else if (m_IsEnhanced)
	{
		cerr << "WMF : unsupported Enhanced Metafile !" << endl;
	}
	else
	{
		cerr << "WMF Header : incorrect header !" << endl;
	}

	buffer.close();
	return (m_Valid && !m_IsEnhanced);
}

bool WMFImport::importWMF(const TransactionSettings& trSettings, int flags)
{
	bool ret = false;
	//double scale = (m_Dpi > 288) ? 288.0 / m_Dpi : 1.0;
	double scale  = (m_Dpi > 0) ? 72.0 / m_Dpi : 0.05;
	double width  = m_BBox.width() * scale;
	double height = m_BBox.height() * scale;
	if (m_Doc && (!interactive || (flags & LoadSavePlugin::lfInsertPage)))
	{
		m_Doc->setPage(fabs(width), fabs(height), 0, 0, 0, 0, 0, 0, false, false);
		m_Doc->addPage(0);
		m_Doc->view()->addPage(0);
	}
	else if (!m_Doc || (flags & LoadSavePlugin::lfCreateDoc))
	{
		m_Doc = ScCore->primaryMainWindow()->doFileNew(fabs(width), fabs(height), 0, 0, 0, 0, 0, 0, false, false, 0, false, 0, 1, "Custom", true);
		ScCore->primaryMainWindow()->HaveNewDoc();
		ret = true;
	}
	if (ret || !interactive)
	{
		if (width > height)
			m_Doc->setPageOrientation(1);
		else
			m_Doc->setPageOrientation(0);
		m_Doc->setPageSize("Custom");
	}
	if ((!(flags & LoadSavePlugin::lfLoadAsPattern)) && (m_Doc->view() != nullptr))
		m_Doc->view()->deselectItems();
	m_Doc->setLoading(true);
	m_Doc->DoDrawing = false;
	if ((!(flags & LoadSavePlugin::lfLoadAsPattern)) && (m_Doc->view() != nullptr))
		m_Doc->view()->updatesOn(false);
	m_Doc->scMW()->setScriptRunning(true);

	QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
	m_Doc->PageColors.ensureDefaultColors();
	QList<PageItem*> Elements = parseWmfCommands();
	QGuiApplication::restoreOverrideCursor();

	if (flags & LoadSavePlugin::lfCreateDoc)
	{
		m_Doc->documentInfo().setTitle(m_docTitle);
		m_Doc->documentInfo().setComments(m_docDesc);
	}
	m_tmpSel->clear();
	if (Elements.isEmpty())
	{
		importFailed = true;
		for (const auto& importedColor : importedColors)
			m_Doc->PageColors.remove(importedColor);
	}
	if (Elements.count() > 1)
		m_Doc->groupObjectsList(Elements);
	m_Doc->DoDrawing = true;
	m_Doc->scMW()->setScriptRunning(false);
	if (interactive)
		m_Doc->setLoading(false);
	if (!Elements.isEmpty() && !ret && interactive)
	{
		if (flags & LoadSavePlugin::lfScripted)
		{
			bool loadF = m_Doc->isLoading();
			m_Doc->setLoading(false);
			m_Doc->changed();
			m_Doc->setLoading(loadF);
			if (!(flags & LoadSavePlugin::lfLoadAsPattern))
			{
				m_Doc->m_Selection->delaySignalsOn();
				m_Doc->m_Selection->addItems(Elements);
				m_Doc->m_Selection->delaySignalsOff();
				m_Doc->m_Selection->setGroupRect();
				if (m_Doc->view() != nullptr)
					m_Doc->view()->updatesOn(true);
			}
			importCanceled = false;
		}
		else
		{
			m_Doc->DragP = true;
			m_Doc->DraggedElem = nullptr;
			m_Doc->DragElements.clear();
			m_Doc->m_Selection->delaySignalsOn();
			m_tmpSel->addItems(Elements);
			m_tmpSel->setGroupRect();
			ScElemMimeData* md = ScriXmlDoc::writeToMimeData(m_Doc, m_tmpSel);
/*#ifndef QT_MAC*/
// see #2526
			m_Doc->itemSelection_DeleteItem(m_tmpSel);
/*#endif*/
			m_Doc->view()->updatesOn(true);
			m_Doc->m_Selection->delaySignalsOff();
			// We must copy the TransationSettings object as it is owned
			// by handleObjectImport method afterwards
			auto* transacSettings = new TransactionSettings(trSettings);
			m_Doc->view()->handleObjectImport(md, transacSettings);
			m_Doc->DragP = false;
			m_Doc->DraggedElem = nullptr;
			m_Doc->DragElements.clear();
		}
	}
	else
	{
		bool loadF = m_Doc->isLoading();
		m_Doc->setLoading(false);
		m_Doc->changed();
		m_Doc->reformPages();
		if ((!(flags & LoadSavePlugin::lfLoadAsPattern)) && (m_Doc->view() != nullptr))
			m_Doc->view()->updatesOn(true);
		m_Doc->setLoading(loadF);
	}
	return true;
}

QList<PageItem*> WMFImport::parseWmfCommands()
{
	int idx;
	const WmfCmd* cmd;
	QList<PageItem*> elements;

	m_context.reset();

	if (!m_Valid)
		return elements;

	delete[] m_ObjHandleTab;
	m_ObjHandleTab = new WmfObjHandle* [ MAX_OBJHANDLE ];
	for (int i = MAX_OBJHANDLE - 1; i >= 0; i--)
		m_ObjHandleTab[i] = nullptr;

	if (WMFIMPORT_DEBUG)
		cerr << "Bounding box : " << m_BBox.left() << " " << m_BBox.top() << " " << m_BBox.right() << " " << m_BBox.bottom() << endl;

	double scale = (m_Dpi > 0) ? 72.0 / m_Dpi : 0.05;
	m_context.setViewportOrg(0, 0);
	m_context.setViewportExt(m_BBox.width() * scale, m_BBox.height() * scale);
	m_context.setWindowOrg(m_BBox.left(), m_BBox.bottom());
	m_context.setWindowExt(m_BBox.width(), m_BBox.height());

	for (int index = 0; index < m_commands.count(); ++index)
	{
		cmd = m_commands.at(index);
		idx = cmd->funcIndex;
		(this->*metaFuncTab[idx].method)(elements, cmd->numParam, cmd->params);

		if (WMFIMPORT_DEBUG)
		{
			QString str;
			if (metaFuncTab[idx].name == nullptr)
				str += "UNKNOWN ";
			if (metaFuncTab[idx].method == &WMFImport::noop)
				str += "UNIMPLEMENTED ";
			str += metaFuncTab[idx].name;
			str += " : ";

			QString param;
			for (int i = 0; i < cmd->numParam; i++)
			{
				param.setNum(cmd->params[i]);
				str += param;
				str += " ";
			}
			cerr << str.toLatin1().data() << endl;
		}
	}
	return elements;
}

void WMFImport::finishCmdParsing(PageItem* item)
{
	QTransform gcm  = m_context.worldMatrix();
	double coeff1 = sqrt(gcm.m11() * gcm.m11() + gcm.m12() * gcm.m12());
	double coeff2 = sqrt(gcm.m21() * gcm.m21() + gcm.m22() * gcm.m22());
	if (item->isImageFrame())
	{
		item->moveBy(gcm.dx(), gcm.dy());
		item->setWidthHeight(item->width() * gcm.m11(), item->height() * gcm.m22());
		item->setLineWidth(qMax(item->lineWidth() * (coeff1 + coeff2) / 2.0, 0.25));
		if (item->imageIsAvailable)
			item->setImageXYScale(item->width() / item->pixm.width(), item->height() / item->pixm.height());
	}
	else if (item->isTextFrame())
	{
		item->setLineWidth(qMax(item->lineWidth() * (coeff1 + coeff2) / 2.0, 0.25));
	}
	else
	{
		item->ClipEdited = true;
		item->FrameType = 3;
		item->PoLine.map(gcm);
		item->setLineWidth(qMax(item->lineWidth() * (coeff1 + coeff2) / 2.0, 0.25));
		FPoint wh = getMaxClipF(&item->PoLine);
		item->setWidthHeight(wh.x(), wh.y());
		m_Doc->adjustItemSize(item);
	}
	item->setRedrawBounding();
	item->OwnPage   = m_Doc->OnPage(item);
	item->PLineEnd  = m_context.pen().capStyle();
	item->PLineJoin = m_context.pen().joinStyle();
	item->setTextFlowMode(PageItem::TextFlowDisabled);
	//item->DashOffset = gc->dashOffset;
	//item->DashValues = gc->dashArray;
}

void WMFImport::setWindowOrg(QList<PageItem*>& /*items*/, long, const short* params)
{
	m_context.setWindowOrg(params[1], params[0]);
}

void WMFImport::setWindowExt(QList<PageItem*>& /*items*/, long, const short* params)
{
	m_context.setWindowExt(params[1], params[0]);
}

void WMFImport::lineTo(QList<PageItem*>& items, long, const short* params)
{
	double BaseX = m_Doc->currentPage()->xOffset();
	double BaseY = m_Doc->currentPage()->yOffset();
	QPoint currentPos = m_context.position();
	bool   doStroke = (m_context.pen().style() != Qt::NoPen);
	if (doStroke)
	{
		double x1 = currentPos.x();
		double y1 = currentPos.y();
		double x2 = params[1];
		double y2 = params[0];
		double  lineWidth = m_context.pen().width();
		QString lineColor = importColor(m_context.pen().color());
		int z = m_Doc->itemAdd(PageItem::PolyLine, PageItem::Unspecified, BaseX, BaseY, 10, 10, lineWidth, CommonStrings::None, lineColor);
		PageItem* ite = m_Doc->Items->at(z);
		ite->PoLine.resize(4);
		ite->PoLine.setPoint(0, FPoint(x1, y1));
		ite->PoLine.setPoint(1, FPoint(x1, y1));
		ite->PoLine.setPoint(2, FPoint(x2, y2));
		ite->PoLine.setPoint(3, FPoint(x2, y2));
		finishCmdParsing(ite);
		items.append(ite);
	}
	m_context.setPosition(QPoint(params[1], params[0]));
}

void WMFImport::moveTo(QList<PageItem*>& /*items*/, long, const short* params)
{
	m_context.setPosition(QPoint(params[1], params[0]));
}

void WMFImport::ellipse(QList<PageItem*>& items, long, const short* params)
{
	double BaseX = m_Doc->currentPage()->xOffset();
	double BaseY = m_Doc->currentPage()->yOffset();
	double rx = (params[1] - params[3]) / 2.0;
	double ry = (params[0] - params[2]) / 2.0;
	double px = (params[1] + params[3]) / 2.0 - rx;
	double py = (params[0] + params[2]) / 2.0 - ry;
	bool   doFill = m_context.brush().style() != Qt::NoBrush;
	bool   doStroke = m_context.pen().style() != Qt::NoPen;
	QString fillColor   = doFill ? importColor(m_context.brush().color()) : CommonStrings::None;
	QString strokeColor = doStroke ? importColor(m_context.pen().color()) : CommonStrings::None;
	double  lineWidth   = m_context.pen().width();
	int z = m_Doc->itemAdd(PageItem::Polygon, PageItem::Ellipse, BaseX, BaseY, rx * 2.0, ry * 2.0, lineWidth, fillColor, strokeColor);
	PageItem* ite = m_Doc->Items->at(z);
	QTransform mm(1.0, 0.0, 0.0, 1.0, px, py);
	ite->PoLine.map(mm);
	finishCmdParsing(ite);
	items.append(ite);
}

void WMFImport::polygon(QList<PageItem*>& items, long, const short* params)
{
	//cerr << "WMFImport::polygon unimplemented" << endl;
	double  BaseX = m_Doc->currentPage()->xOffset();
	double  BaseY = m_Doc->currentPage()->yOffset();
	bool    doFill = m_context.brush().style() != Qt::NoBrush;
	bool    doStroke = m_context.pen().style() != Qt::NoPen;
	QString fillColor   = doFill ? importColor(m_context.brush().color()) : CommonStrings::None;
	QString strokeColor = doStroke ? importColor(m_context.pen().color()) : CommonStrings::None;
	double  lineWidth   = m_context.pen().width();
	if (doStroke && lineWidth <= 0.0 )
		lineWidth = 1.0;
	FPointArray paramPoints = pointsFromParam(params[0], &params[1]);
	FPointArray points      = pointsToPolyline(paramPoints, true);
	if (!paramPoints.empty())
	{
		int z = m_Doc->itemAdd(PageItem::Polygon, PageItem::Unspecified, BaseX, BaseY, 10, 10, lineWidth, fillColor, strokeColor);
		PageItem* ite = m_Doc->Items->at(z);
		ite->PoLine = points;
		ite->fillRule = !m_context.windingFill();
		finishCmdParsing(ite);
		items.append(ite);
	}
}

void WMFImport::polyPolygon(QList<PageItem*>& items, long /*num*/, const short* params)
{
	int numPolys   = params[0];
	int pointIndex = params[0] + 1;
	FPointArray pointsPoly;
	for (int i = 0; i < numPolys; ++i)
	{
		short  numPoints  = params[i + 1];
		QScopedArrayPointer<short> paramArray(new short[1 + 2 * numPoints]);
		paramArray[0] = numPoints;
		memcpy(&paramArray[1], &params[pointIndex], 2 * numPoints * sizeof(short));
		FPointArray paramPoints = pointsFromParam(numPoints, &paramArray[1]);
		FPointArray points      = pointsToPolyline(paramPoints, true);
		pointsPoly += points;
		if (numPolys > 1)
			pointsPoly.setMarker();
		pointIndex += (2 * numPoints);
	}
	double  BaseX = m_Doc->currentPage()->xOffset();
	double  BaseY = m_Doc->currentPage()->yOffset();
	bool    doFill = m_context.brush().style() != Qt::NoBrush;
	bool    doStroke = m_context.pen().style() != Qt::NoPen;
	QString fillColor   = doFill ? importColor(m_context.brush().color()) : CommonStrings::None;
	QString strokeColor = doStroke ? importColor(m_context.pen().color()) : CommonStrings::None;
	double  lineWidth   = m_context.pen().width();
	if (doStroke && lineWidth <= 0.0 )
		lineWidth = 1.0;
	if (!pointsPoly.empty())
	{
		int z = m_Doc->itemAdd(PageItem::Polygon, PageItem::Unspecified, BaseX, BaseY, 10, 10, lineWidth, fillColor, strokeColor);
		PageItem* ite = m_Doc->Items->at(z);
		ite->PoLine = pointsPoly;
		ite->fillRule = !m_context.windingFill();
		finishCmdParsing(ite);
		items.append(ite);
	}
}

void WMFImport::polyline(QList<PageItem*>& items, long, const short* params)
{
	double  BaseX = m_Doc->currentPage()->xOffset();
	double  BaseY = m_Doc->currentPage()->yOffset();
	bool    doStroke = m_context.pen().style() != Qt::NoPen;
	QString fillColor   = CommonStrings::None;
	QString strokeColor = doStroke ? importColor(m_context.pen().color()) : CommonStrings::None;
	double  lineWidth   = m_context.pen().width();
	if (doStroke && lineWidth <= 0.0 )
		lineWidth = 1.0;
	FPointArray paramPoints = pointsFromParam(params[0], &params[1]);
	FPointArray points      = pointsToPolyline(paramPoints, false);
	if (!paramPoints.empty())
	{
		int z = m_Doc->itemAdd(PageItem::PolyLine, PageItem::Unspecified, BaseX, BaseY, 10, 10, lineWidth, fillColor, strokeColor);
		PageItem* ite = m_Doc->Items->at(z);
		ite->PoLine = points;
		finishCmdParsing(ite);
		items.append(ite);
	}
}

void WMFImport::rectangle(QList<PageItem*>& items, long, const short* params)
{
	//cerr << "WMFImport::rectangle unimplemented" << endl;
	double  BaseX = m_Doc->currentPage()->xOffset();
	double  BaseY = m_Doc->currentPage()->yOffset();
	bool    doFill = m_context.brush().style() != Qt::NoBrush;
	bool    doStroke = m_context.pen().style() != Qt::NoPen;
	QString fillColor   = doFill ? importColor(m_context.brush().color()) : CommonStrings::None;
	QString strokeColor = doStroke ? importColor(m_context.pen().color()) : CommonStrings::None;
	double  lineWidth   = m_context.pen().width();
	if (doStroke && lineWidth <= 0.0 )
		lineWidth = 1.0;
	double x = ((params[3] - params[1]) > 0) ? params[1] : params[3];
	double y = ((params[2] - params[0]) > 0) ? params[0] : params[2];
	double width  = fabs((double) params[3] - params[1]);
	double height = fabs((double) params[2] - params[0]);
	int z = m_Doc->itemAdd(PageItem::Polygon, PageItem::Rectangle, BaseX, BaseY, width, height, lineWidth, fillColor, strokeColor);
	PageItem* ite = m_Doc->Items->at(z);
	QTransform mm(1.0, 0.0, 0.0, 1.0, x, y);
	ite->PoLine.map(mm);
	finishCmdParsing(ite);
	items.append(ite);
}

void WMFImport::roundRect(QList<PageItem*>& items, long, const short* params)
{
	double  BaseX = m_Doc->currentPage()->xOffset();
	double  BaseY = m_Doc->currentPage()->yOffset();
	bool    doFill = m_context.brush().style() != Qt::NoBrush;
	bool    doStroke = m_context.pen().style() != Qt::NoPen;
	QString fillColor   = doFill ? importColor(m_context.brush().color()) : CommonStrings::None;
	QString strokeColor = doStroke ? importColor(m_context.pen().color()) : CommonStrings::None;
	double  lineWidth   = m_context.pen().width();
	if (doStroke && lineWidth <= 0.0)
		lineWidth = 1.0;
	double x = ((params[5] - params[3]) > 0) ? params[3] : params[5];
	double y = ((params[4] - params[2]) > 0) ? params[2] : params[4];
	double width  = fabs((double) params[5] - params[3]);
	double height = fabs((double) params[4] - params[2]);
	double rx     = params[1] / 2.0;
	double ry     = params[0] / 2.0;
	int z = m_Doc->itemAdd(PageItem::Polygon, PageItem::Rectangle, BaseX, BaseY, width, height, lineWidth, fillColor, strokeColor);
	PageItem* ite = m_Doc->Items->at(z);
	if ((rx != 0.0) || (ry != 0.0))
	{
		ite->setCornerRadius(qMax(rx, ry));
		ite->SetFrameRound();
		m_Doc->setRedrawBounding(ite);
	}
	QTransform mm(1.0, 0.0, 0.0, 1.0, x, y);
	ite->PoLine.map(mm);
	finishCmdParsing(ite);
	items.append(ite);
}

void WMFImport::arc(QList<PageItem*>& items, long, const short* params)
{
	FPointArray  pointArray;
	QPainterPath painterPath;
	double  BaseX = m_Doc->currentPage()->xOffset();
	double  BaseY = m_Doc->currentPage()->yOffset();
	bool    doStroke = m_context.pen().style() != Qt::NoPen;
	QString fillColor   = CommonStrings::None;
	QString strokeColor = doStroke ? importColor(m_context.pen().color()) : CommonStrings::None;
	double  lineWidth   = m_context.pen().width();
	if (doStroke && lineWidth <= 0.0)
		lineWidth = 1.0;
	double  angleStart, angleLength;
	double  x = ((params[7] - params[5]) > 0) ? params[5] : params[7];
	double  y = ((params[6] - params[4]) > 0) ? params[4] : params[6];
	double  xCenter = (params[5] + params[7]) / 2.0;
	double  yCenter = (params[4] + params[6]) / 2.0;
	double  xWidth  =  fabs((double) params[5] - params[7]);
	double  yHeight =  fabs((double) params[4] - params[6]);
	pointsToAngle(params[3] - xCenter, yCenter - params[2], params[1] - xCenter, yCenter - params[0], angleStart, angleLength);
	painterPath.arcMoveTo(x, y, xWidth, yHeight, angleStart);
	painterPath.arcTo(x, y, xWidth, yHeight, angleStart, angleLength);
	pointArray.fromQPainterPath(painterPath);
	if (!pointArray.empty())
	{
		int z = m_Doc->itemAdd(PageItem::PolyLine, PageItem::Unspecified, BaseX, BaseY, 10, 10, lineWidth, fillColor, strokeColor);
		PageItem* ite = m_Doc->Items->at(z);
		ite->PoLine = pointArray;
		finishCmdParsing(ite);
		items.append(ite);
	}
}

void WMFImport::chord(QList<PageItem*>& items, long, const short* params)
{
	QPointF      firstPoint;
	FPointArray  pointArray;
	QPainterPath painterPath;
	double  BaseX = m_Doc->currentPage()->xOffset();
	double  BaseY = m_Doc->currentPage()->yOffset();
	bool    doFill   = m_context.brush().style() != Qt::NoBrush;
	bool    doStroke = m_context.pen().style() != Qt::NoPen;
	QString fillColor   = doFill ? importColor(m_context.brush().color()) : CommonStrings::None;
	QString strokeColor = doStroke ? importColor(m_context.pen().color()) : CommonStrings::None;
	double  lineWidth   = m_context.pen().width();
	if (doStroke && lineWidth <= 0.0 )
		lineWidth = 1.0;
	double  angleStart, angleLength;
	double  x = ((params[7] - params[5]) > 0) ? params[5] : params[7];
	double  y = ((params[6] - params[4]) > 0) ? params[4] : params[6];
	double  xCenter = (params[5] + params[7]) / 2.0;
	double  yCenter = (params[4] + params[6]) / 2.0;
	double  xWidth  =  fabs((double) params[5] - params[7]);
	double  yHeight =  fabs((double) params[4] - params[6]);
	pointsToAngle(params[3] - xCenter, yCenter - params[2], params[1] - xCenter, yCenter - params[0], angleStart, angleLength);
	painterPath.arcMoveTo(x, y, xWidth, yHeight, angleStart);
	firstPoint = painterPath.currentPosition();
	painterPath.arcTo (x, y, xWidth, yHeight, angleStart, angleLength);
	painterPath.lineTo(firstPoint);
	pointArray.fromQPainterPath(painterPath);
	if (!pointArray.empty())
	{
		int z = m_Doc->itemAdd(PageItem::Polygon, PageItem::Unspecified, BaseX, BaseY, 10, 10, lineWidth, fillColor, strokeColor);
		PageItem* ite = m_Doc->Items->at(z);
		ite->PoLine = pointArray;
		finishCmdParsing(ite);
		items.append(ite);
	}
}

void WMFImport::pie(QList<PageItem*>& items, long, const short* params)
{
	QPointF      firstPoint;
	FPointArray  pointArray;
	QPainterPath painterPath;
	double  BaseX = m_Doc->currentPage()->xOffset();
	double  BaseY = m_Doc->currentPage()->yOffset();
	bool    doFill   = m_context.brush().style() != Qt::NoBrush;
	bool    doStroke = m_context.pen().style() != Qt::NoPen;
	QString fillColor   = doFill ? importColor(m_context.brush().color()) : CommonStrings::None;
	QString strokeColor = doStroke ? importColor(m_context.pen().color()) : CommonStrings::None;
	double  lineWidth   = m_context.pen().width();
	if (doStroke && lineWidth <= 0.0)
		lineWidth = 1.0;
	double  angleStart, angleLength;
	double  x = ((params[7] - params[5]) > 0) ? params[5] : params[7];
	double  y = ((params[6] - params[4]) > 0) ? params[4] : params[6];
	double  xCenter = (params[5] + params[7]) / 2.0;
	double  yCenter = (params[4] + params[6]) / 2.0;
	double  xWidth  =  fabs((double) params[5] - params[7]);
	double  yHeight =  fabs((double) params[4] - params[6]);
	pointsToAngle(params[3] - xCenter, yCenter - params[2], params[1] - xCenter, yCenter - params[0], angleStart, angleLength);
	painterPath.arcMoveTo(x,y, xWidth, yHeight, angleStart);
	firstPoint = painterPath.currentPosition();
	painterPath.arcTo (x, y, xWidth, yHeight, angleStart, angleLength);
	painterPath.lineTo(xCenter, yCenter);
	painterPath.lineTo(firstPoint);
	pointArray.fromQPainterPath(painterPath);
	if (!pointArray.empty())
	{
		int z = m_Doc->itemAdd(PageItem::Polygon, PageItem::Unspecified, BaseX, BaseY, 10, 10, lineWidth, fillColor, strokeColor);
		PageItem* ite = m_Doc->Items->at(z);
		ite->PoLine = pointArray;
		finishCmdParsing(ite);
		items.append(ite);
	}
}

void WMFImport::setPolyFillMode(QList<PageItem*>& /*items*/, long, const short* params)
{
	m_context.setWindingFill(params[0]);
}

void WMFImport::setBkColor(QList<PageItem*>& /*items*/, long, const short* params)
{
	m_context.setBackgroundColor(colorFromParam(params));
}

void WMFImport::setBkMode(QList<PageItem*>& /*items*/, long, const short* params )
{
	if (params[0] == 1)
		m_context.setBackgroundMode(Qt::TransparentMode);
	else 
		m_context.setBackgroundMode(Qt::OpaqueMode);
}

void WMFImport::saveDC(QList<PageItem*>& /*items*/, long, const short*)
{
	m_context.save();
}

void WMFImport::restoreDC(QList<PageItem*>& /*items*/, long, const short* params)
{
	for (int i = 0; i > params[0]; i--)
		m_context.restore();
}

void WMFImport::intersectClipRect(QList<PageItem*>& /*items*/, long, const short* /*params*/)
{
	cerr << "WMFImport::intersectClipRect unimplemented" << endl;
}

void WMFImport::excludeClipRect(QList<PageItem*>& /*items*/, long, const short* /*params*/)
{
	cerr << "WMFImport::excludeClipRect unimplemented" << endl;
}

void WMFImport::setTextColor(QList<PageItem*>& /*items*/, long, const short* params)
{
	m_context.setTextColor(colorFromParam(params));
}

void WMFImport::setTextAlign(QList<PageItem*>& /*items*/, long, const short* params)
{
	m_context.setTextAlign(params[0]);
}

void WMFImport::textOut(QList<PageItem*>& items, long num, const short* params)
{
	QScopedArrayPointer<short> copyParm(new short[num + 1]);

	// re-order parameters
	int idxOffset = (params[0] / 2) + 1 + (params[0] & 1);
	copyParm[0] = params[idxOffset];
	copyParm[1] = params[idxOffset + 1];
	copyParm[2] = params[0];
	copyParm[3] = 0;
	memcpy(&copyParm[4], &params[1], params[0]);

	extTextOut(items, num + 1, copyParm.get());
}

void WMFImport::extTextOut(QList<PageItem*>& items, long num, const short* params)
{
	QString textString;
	double  BaseX = m_Doc->currentPage()->xOffset();
	double  BaseY = m_Doc->currentPage()->yOffset();

	if (params[3] & 0x0010) // ETO_GLYPH_INDEX
	{
		cerr << "WMFImport::extTextOut: ETO_GLYPH_INDEX option not supported " << endl;
		unsupported = true;
		return;
	}

	// ETO_CLIPPED flag add 4 parameters
	const auto* ptStr = (const char*) ((params[3] & 0x0004) ? (&params[8]) : (&params[4]));
	QByteArray textArray(ptStr, params[2]);
	
	const QTextCodec* codec = codecFromCharset(m_context.textCharSet());
	if (codec)
		textString = codec->toUnicode(textArray);
	else if (m_context.textCharSet() == SYMBOL_CHARSET)
		textString = symbolToUnicode(textArray);
	else
		textString = QString::fromLocal8Bit(textArray.data());

	QFontMetrics fm(m_context.font());
	int width  = fm.horizontalAdvance(textString) + fm.descent();  // because fm.width(text) isn't right with Italic text
	/*int height = fm.height();
	int ascent = fm.ascent();*/

	double startX = params[1];
	double startY = params[0];
	int    textAlign    = m_context.textAlign();
	double textRotation = m_context.textRotation();
	if (textAlign & 0x01)
	{
		// (left, top) position = current logical position
		QPoint pos = m_context.position();
		startX = pos.x();
		startY = pos.y();
	}

	m_context.save();
	// A bit hacky but should be sufficient to have text not flipped
	// top down in some wmfs (see cell.wmf sample from libwmf)
	if (m_context.worldMatrix().m22() < 0.0)
	{
		m_context.translate(startX, startY);
		m_context.scale(1.0, -1.0);
		m_context.translate(-startX, -startY);
	}
	if (textRotation != 0.0)
	{
		m_context.translate(startX, startY);
		m_context.rotate(textRotation);
		m_context.translate(-startX, -startY);
	}

	if ((textAlign & 0x04) && (textAlign & 0x02)) // TA_CENTER 0x06
		startX -= (width / 2);
	else if (textAlign & 0x02) // TA_RIGHT 0x02
		startX -= width;
	if (textAlign == 0) // TA_TOP                       
		startY += fm.ascent();

	bool eto_empty = (params[3] == 0);
	bool eto_clipped_set = (params[3] & 0x0004);
	bool eto_pdy_set     = (params[3] & 0x2000);

	int  idxOffset = (params[2] / 2) + 4 + (params[2] & 1) + (eto_clipped_set ? 4 : 0);
	int  minParams = eto_pdy_set ? (idxOffset + 2 * (params[2] - 1)) : (idxOffset + params[2]);
	bool useCharInterdistances = (num >= minParams) && (eto_empty || eto_pdy_set);
	if ((params[2] > 1) && useCharInterdistances) 
	{
		double xpos  = startX;
		double ypos  = startY;
		double lineWidth = 0.0;
		FPointArray textPath;
		QString textColor = importColor(m_context.textColor());
		for (int index = 0; (index < params[2] && index < textString.length()); ++index) 
		{
			QPainterPath painterPath;
			if (index > 0 && eto_pdy_set)
			{
				xpos += params[idxOffset + index * 2 - 1];
				ypos += params[idxOffset + index * 2];
			}
			else if (index > 0)
				xpos += params[idxOffset + index - 1];
			painterPath.addText(xpos, ypos, m_context.font(), textString.at(index));
			textPath.fromQPainterPath(painterPath);
			if (!textPath.empty())
			{
				int z = m_Doc->itemAdd(PageItem::Polygon, PageItem::Unspecified, BaseX, BaseY, 10, 10, lineWidth, textColor, CommonStrings::None);
				PageItem* ite = m_Doc->Items->at(z);
				ite->PoLine = textPath;
				finishCmdParsing(ite);
				items.append(ite);
			}
		}
	}
	else 
	{
		FPointArray textPath;
		QString textColor = importColor(m_context.textColor());
		QPainterPath painterPath;
		painterPath.addText(startX, startY, m_context.font(), textString);
		textPath.fromQPainterPath(painterPath);
		if (!textPath.empty())
		{
			double  lineWidth = 0.0;
			int z = m_Doc->itemAdd(PageItem::Polygon, PageItem::Unspecified, BaseX, BaseY, 10, 10, lineWidth, textColor, CommonStrings::None);
			PageItem* ite = m_Doc->Items->at(z);
			ite->PoLine = textPath;
			finishCmdParsing(ite);
			items.append(ite);
		}
	}
	m_context.restore();
}

void WMFImport::selectObject(QList<PageItem*>& /*items*/, long, const short* params )
{
	int idx = params[ 0 ];
	if (idx >= 0 && idx < MAX_OBJHANDLE && m_ObjHandleTab[idx])
		m_ObjHandleTab[idx]->apply(m_context);
}

void WMFImport::deleteObject(QList<PageItem*>& /*items*/, long, const short* params)
{
	deleteHandle(params[0]);
}

void WMFImport::createEmptyObject(QList<PageItem*>& /*items*/, long, const short*)
{
	// allocation of an empty object (to keep object counting in sync)
	auto* handle = new WmfObjPenHandle();
	addHandle(handle);
	cerr << "WMFImport: unimplemented createObject " << endl;
}

void WMFImport::createBrushIndirect(QList<PageItem*>& /*items*/, long, const short* params)
{
	static Qt::BrushStyle hatchedStyleTab[] =
	{
		Qt::HorPattern,
		Qt::FDiagPattern,
		Qt::BDiagPattern,
		Qt::CrossPattern,
		Qt::DiagCrossPattern
	};
	static Qt::BrushStyle styleTab[] =
	{ Qt::SolidPattern,
	  Qt::NoBrush,
	  Qt::FDiagPattern,   /* hatched */
	  Qt::Dense4Pattern,  /* should be custom bitmap pattern */
	  Qt::HorPattern,     /* should be BS_INDEXED (?) */
	  Qt::VerPattern,     /* should be device-independent bitmap */
	  Qt::Dense6Pattern,  /* should be device-independent packed-bitmap */
	  Qt::Dense2Pattern,  /* should be BS_PATTERN8x8 */
	  Qt::Dense3Pattern   /* should be device-independent BS_DIBPATTERN8x8 */
	};
	Qt::BrushStyle style;
	auto* handle = new WmfObjBrushHandle();
	addHandle(handle);

	short arg = params[0];
	if (arg == 2)
	{
		arg = params[3];
		if (arg >= 0 && arg < 5)
			style = hatchedStyleTab[arg];
		else
		{
			cerr << "WMFImport::createBrushIndirect: invalid hatched brush " << arg << endl;
			style = Qt::SolidPattern;
		}
	}
	else if (arg >= 0 && arg < 9)
		style = styleTab[arg];
	else
	{
		cerr << "WMFImport::createBrushIndirect: invalid brush " << arg << endl;
		style = Qt::SolidPattern;
	}
	handle->brush.setStyle(style);
	handle->brush.setColor(colorFromParam(params + 1));
}

void WMFImport::createPenIndirect(QList<PageItem*>& /*items*/, long, const short* params)
{
	static Qt::PenStyle styleTab[] = { Qt::SolidLine, Qt::DashLine, Qt::DotLine, Qt::DashDotLine, Qt::DashDotDotLine,
	  Qt::NoPen, Qt::SolidLine };
	Qt::PenStyle style;
	auto* handle = new WmfObjPenHandle();
	addHandle(handle);

	if (params[0] >= 0 && params[0] < 6)
		style = styleTab[params[0]];
	else
	{
		cerr << "WMFImport::createPenIndirect: invalid pen " << params[0] << endl;
		style = Qt::SolidLine;
	}

	handle->pen.setStyle(style);
	handle->pen.setWidth(params[1]);
	handle->pen.setColor(colorFromParam(params + 3));
	handle->pen.setCapStyle(Qt::RoundCap);
}

void WMFImport::createFontIndirect(QList<PageItem*>& /*items*/, long, const short* params)
{
	auto* handle = new WmfObjFontHandle();
	addHandle(handle);

	QString family((const char*) &params[9]);

	handle->rotation = -params[2] / 10; // text rotation (in 1/10 degree)
	handle->font.setFamily(family);
	handle->font.setStyleStrategy(QFont::PreferOutline);
	handle->font.setFixedPitch((params[8] & 0x01) == 0);
	// TODO: investigation why some test case need -2. (size of font in logical point)
	int fontSize = (params[0] != 0) ? (qAbs(params[0]) - 2) : 12;
	handle->font.setPixelSize(fontSize);
	handle->font.setWeight((params[4] >> 3));
	handle->font.setItalic(params[5] & 0x01);
	handle->font.setUnderline(params[5] & 0x100);
	handle->font.setStrikeOut(params[6] & 0x01);
	handle->charset = (params[6] & 0xFF00) >> 8;
}

void WMFImport::noop(QList<PageItem*>& /*items*/, long, const short*)
{
}

void WMFImport::end(QList<PageItem*>& /*items*/, long, const short*)
{
	//cerr << "WMFImport::end unimplemented" << endl;
}

void WMFImport::addHandle(WmfObjHandle* handle)
{
	int idx;

	for (idx = 0; idx < MAX_OBJHANDLE; idx++)
	{
		if (m_ObjHandleTab[idx] == nullptr)
			break;
	}

	if (idx < MAX_OBJHANDLE)
		m_ObjHandleTab[idx] = handle;
	else
		cerr << "WMFImport error: handle table full !" << endl;
}

void WMFImport::deleteHandle(int idx)
{
	if (idx >= 0 && idx < MAX_OBJHANDLE && m_ObjHandleTab[idx])
	{
		delete m_ObjHandleTab[idx];
		m_ObjHandleTab[idx] = nullptr;
	}
}

unsigned short WMFImport::calcCheckSum(WmfPlaceableHeader* apmfh)
{
	WORD16*  lpWord;
	WORD16   wResult;

	// Start with the first word
	wResult = *(lpWord = (WORD16*) (apmfh));
	// XOR in each of the other 9 words
	for (int i = 1; i <= 9; i++)
	{
		wResult ^= lpWord[i];
	}
	return wResult;
}

int WMFImport::findFunc(unsigned short aFunc) const
{
	int i = 0;

	for (i = 0; metaFuncTab[i].name; i++)
	{
		if (metaFuncTab[i].func == aFunc)
			return i;
	}

	// here : unknown function
	return i;
}

unsigned int WMFImport::toDWord(const short* params)
{
	unsigned int l;
#if !defined(WORDS_BIGENDIAN)
	l = *(const unsigned int*) (params);
#else
	char* bytes;
	char swap[4];
	bytes = (char*) params;
	swap[0] = bytes[2];
	swap[1] = bytes[3];
	swap[2] = bytes[0];
	swap[3] = bytes[1];
	l = *(unsigned int*) (swap);
#endif
	return l;
}
