Add support for ETO_PDY in WMF/EMF

Currently it is implemented by making all characters different
text arrays.

Unit test included.

Change-Id: I850bf192cf5d978a126d3f37b1084021d37bdf30
Reviewed-on: https://gerrit.libreoffice.org/32490
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
This commit is contained in:
Mike Kaganski 2016-12-29 11:45:38 +03:00
parent 767ec2f138
commit fa96ffbc6b
8 changed files with 166 additions and 91 deletions

View File

@ -421,15 +421,18 @@ void MetafileXmlDump::writeXml(const GDIMetaFile& rMetaFile, XmlWriter& rWriter)
rWriter.attribute("index", aIndex);
rWriter.attribute("length", aLength);
rWriter.startElement("dxarray");
OUString sDxLengthString;
for (sal_Int32 i = 0; i < aLength; ++i)
if (pMetaTextArrayAction->GetDXArray())
{
sDxLengthString += OUString::number(pMetaTextArrayAction->GetDXArray()[aIndex+i]);
sDxLengthString += " ";
rWriter.startElement("dxarray");
OUString sDxLengthString;
for (sal_Int32 i = 0; i < aLength; ++i)
{
sDxLengthString += OUString::number(pMetaTextArrayAction->GetDXArray()[aIndex + i]);
sDxLengthString += " ";
}
rWriter.content(sDxLengthString);
rWriter.endElement();
}
rWriter.content(sDxLengthString);
rWriter.endElement();
rWriter.startElement("text");
rWriter.content(pMetaTextArrayAction->GetText());

Binary file not shown.

Binary file not shown.

View File

@ -60,6 +60,7 @@ public:
void testTdf93750();
void testTdf99402();
void testTdf39894();
void testETO_PDY();
CPPUNIT_TEST_SUITE(WmfTest);
CPPUNIT_TEST(globalSetUp);
@ -71,6 +72,7 @@ public:
CPPUNIT_TEST(testTdf93750);
CPPUNIT_TEST(testTdf99402);
CPPUNIT_TEST(testTdf39894);
CPPUNIT_TEST(testETO_PDY);
CPPUNIT_TEST_SUITE_END();
};
@ -289,6 +291,30 @@ void WmfTest::testTdf39894()
}
}
void WmfTest::testETO_PDY()
{
OUString files[] = { "ETO_PDY.wmf", "ETO_PDY.emf" };
for (const auto& file: files)
{
SvFileStream aFileStream(getFullUrl(file), StreamMode::READ);
GDIMetaFile aGDIMetaFile;
ReadWindowMetafile(aFileStream, aGDIMetaFile);
MetafileXmlDump dumper;
xmlDocPtr pDoc = dumper.dumpAndParse(aGDIMetaFile);
CPPUNIT_ASSERT(pDoc);
// The y position of following text
// must be smaller than that of previous
auto y1 = getXPath(pDoc, "/metafile/push[2]/textarray[1]", "y");
auto y2 = getXPath(pDoc, "/metafile/push[2]/textarray[2]", "y");
auto y3 = getXPath(pDoc, "/metafile/push[2]/textarray[3]", "y");
CPPUNIT_ASSERT_MESSAGE(file.toUtf8().getStr(), y2.toInt32() < y1.toInt32());
CPPUNIT_ASSERT_MESSAGE(file.toUtf8().getStr(), y3.toInt32() < y2.toInt32());
}
}
CPPUNIT_TEST_SUITE_REGISTRATION(WmfTest);
CPPUNIT_PLUGIN_IMPLEMENT();

View File

@ -1547,7 +1547,6 @@ bool EnhWMFReader::ReadEnhWMF()
sal_Int32 nLeft, nTop, nRight, nBottom, ptlReferenceX, ptlReferenceY, nGfxMode, nXScale, nYScale;
sal_uInt32 nOffString, nOptions, offDx;
sal_Int32 nLen;
std::vector<long> aDX;
nCurPos = pWMF->Tell() - 8;
@ -1568,23 +1567,6 @@ bool EnhWMFReader::ReadEnhWMF()
bool bOffStringSane = nOffString <= nEndPos - nCurPos;
if (bLenSane && bOffStringSane)
{
sal_Int32 nDxSize = nLen * ((nOptions & ETO_PDY) ? 8 : 4);
if ( offDx && (( nCurPos + offDx + nDxSize ) <= nNextPos ) && nNextPos <= nEndPos )
{
pWMF->Seek( nCurPos + offDx );
aDX.resize(nLen);
for (sal_Int32 i = 0; i < nLen; ++i)
{
sal_Int32 val(0);
pWMF->ReadInt32(val);
aDX[i] = val;
if (nOptions & ETO_PDY)
{
pWMF->ReadInt32(val);
// TODO: Use Dy value
}
}
}
pWMF->Seek( nCurPos + nOffString );
OUString aText;
if ( bFlag )
@ -1594,22 +1576,6 @@ bool EnhWMFReader::ReadEnhWMF()
std::unique_ptr<sal_Char[]> pBuf(new sal_Char[ nLen ]);
pWMF->ReadBytes(pBuf.get(), nLen);
aText = OUString(pBuf.get(), nLen, pOut->GetCharSet());
pBuf.reset();
if ( aText.getLength() != nLen )
{
std::vector<long> aOldDX(aText.getLength());
aOldDX.swap(aDX);
sal_Int32 nDXLen = std::min<sal_Int32>(nLen, aOldDX.size());
for (sal_Int32 i = 0, j = 0; i < aText.getLength(); ++i)
{
sal_Unicode cUniChar = aText[i];
OString aCharacter(&cUniChar, 1, pOut->GetCharSet());
aDX[i] = 0;
for (sal_Int32 k = 0; ( k < aCharacter.getLength() ) && ( j < nDXLen ) && ( i < aText.getLength() ); ++k)
aDX[ i ] += aOldDX[j++];
}
}
}
}
else
@ -1630,7 +1596,53 @@ bool EnhWMFReader::ReadEnhWMF()
aText = OUString(pBuf.get(), nLen);
}
}
pOut->DrawText(aPos, aText, aDX.data(), bRecordPath, nGfxMode);
std::unique_ptr<long[]> pDXAry, pDYAry;
sal_Int32 nDxSize = nLen * ((nOptions & ETO_PDY) ? 8 : 4);
if ( offDx && (( nCurPos + offDx + nDxSize ) <= nNextPos ) && nNextPos <= nEndPos )
{
pWMF->Seek( nCurPos + offDx );
pDXAry.reset( new long[aText.getLength()] );
if (nOptions & ETO_PDY)
{
pDYAry.reset( new long[aText.getLength()] );
}
for (sal_Int32 i = 0; i < aText.getLength(); ++i)
{
sal_Int32 nDxCount = 1;
if (aText.getLength() != nLen)
{
sal_Unicode cUniChar = aText[i];
OString aTmp(&cUniChar, 1, pOut->GetCharSet());
if (aTmp.getLength() > 1)
{
nDxCount = aTmp.getLength();
}
}
sal_Int32 nDx = 0, nDy = 0;
while (nDxCount--)
{
sal_Int32 nDxTmp = 0;
pWMF->ReadInt32(nDxTmp);
nDx += nDxTmp;
if (nOptions & ETO_PDY)
{
sal_Int32 nDyTmp = 0;
pWMF->ReadInt32(nDyTmp);
nDy += nDyTmp;
}
}
pDXAry[i] = nDx;
if (nOptions & ETO_PDY)
{
pDYAry[i] = nDy;
}
}
}
pOut->DrawText(aPos, aText, pDXAry.get(), pDYAry.get(), bRecordPath, nGfxMode);
}
}
break;

View File

@ -1348,7 +1348,7 @@ void WinMtfOutput::DrawPolyBezier( tools::Polygon& rPolygon, bool bTo, bool bRec
}
}
void WinMtfOutput::DrawText( Point& rPosition, OUString& rText, long* pDXArry, bool bRecordPath, sal_Int32 nGfxMode )
void WinMtfOutput::DrawText( Point& rPosition, OUString& rText, long* pDXArry, long* pDYArry, bool bRecordPath, sal_Int32 nGfxMode )
{
UpdateClipRegion();
rPosition = ImplMap( rPosition );
@ -1357,18 +1357,25 @@ void WinMtfOutput::DrawText( Point& rPosition, OUString& rText, long* pDXArry, b
if (pDXArry)
{
sal_Int32 i;
sal_Int32 nSum = 0;
sal_Int32 nLen = rText.getLength();
for (i = 0; i < nLen; i++ )
sal_Int32 nSumX = 0, nSumY = 0;
for (sal_Int32 i = 0; i < rText.getLength(); i++ )
{
nSum += pDXArry[i];
nSumX += pDXArry[i];
// #i121382# Map DXArray using WorldTransform
const Size aSize(ImplMap(Size(nSum, 0)));
const basegfx::B2DVector aVector(aSize.Width(), aSize.Height());
pDXArry[i] = basegfx::fround(aVector.getLength());
const Size aSizeX(ImplMap(Size(nSumX, 0)));
const basegfx::B2DVector aVectorX(aSizeX.Width(), aSizeX.Height());
pDXArry[i] = basegfx::fround(aVectorX.getLength()) * (nSumX >= 0 ? 1 : -1);
if (pDYArry)
{
nSumY += pDYArry[i];
const Size aSizeY(ImplMap(Size(0, nSumY)));
const basegfx::B2DVector aVectorY(aSizeY.Width(), aSizeY.Height());
// Reverse Y
pDYArry[i] = basegfx::fround(aVectorY.getLength()) * (nSumY >= 0 ? -1 : 1);
}
}
}
if ( mnLatestTextLayoutMode != mnTextLayoutMode )
@ -1377,18 +1384,18 @@ void WinMtfOutput::DrawText( Point& rPosition, OUString& rText, long* pDXArry, b
mpGDIMetaFile->AddAction( new MetaLayoutModeAction( mnTextLayoutMode ) );
}
SetGfxMode( nGfxMode );
TextAlign eTextAlign;
if ( ( mnTextAlign & TA_BASELINE) == TA_BASELINE )
eTextAlign = ALIGN_BASELINE;
else if( ( mnTextAlign & TA_BOTTOM) == TA_BOTTOM )
eTextAlign = ALIGN_BOTTOM;
else
eTextAlign = ALIGN_TOP;
bool bChangeFont = false;
if ( mnLatestTextAlign != mnTextAlign )
{
bChangeFont = true;
mnLatestTextAlign = mnTextAlign;
TextAlign eTextAlign;
if ( ( mnTextAlign & TA_BASELINE) == TA_BASELINE )
eTextAlign = ALIGN_BASELINE;
else if( ( mnTextAlign & TA_BOTTOM) == TA_BOTTOM )
eTextAlign = ALIGN_BOTTOM;
else
eTextAlign = ALIGN_TOP;
mpGDIMetaFile->AddAction( new MetaTextAlignAction( eTextAlign ) );
}
if ( maLatestTextColor != maTextColor )
@ -1422,12 +1429,7 @@ void WinMtfOutput::DrawText( Point& rPosition, OUString& rText, long* pDXArry, b
else
aTmp.SetTransparent( false );
if ( ( mnTextAlign & TA_BASELINE) == TA_BASELINE )
aTmp.SetAlignment( ALIGN_BASELINE );
else if( ( mnTextAlign & TA_BOTTOM) == TA_BOTTOM )
aTmp.SetAlignment( ALIGN_BOTTOM );
else
aTmp.SetAlignment( ALIGN_TOP );
aTmp.SetAlignment( eTextAlign );
if ( nGfxMode == GM_ADVANCED )
{
@ -1455,7 +1457,8 @@ void WinMtfOutput::DrawText( Point& rPosition, OUString& rText, long* pDXArry, b
// #i117968# VirtualDevice is not thread safe, but filter is used in multithreading
SolarMutexGuard aGuard;
ScopedVclPtrInstance< VirtualDevice > pVDev;
sal_Int32 nTextWidth, nActPosDeltaX = 0;
sal_Int32 nTextWidth;
Point aActPosDelta;
pVDev->SetMapMode( MapMode( MapUnit::Map100thMM ) );
pVDev->SetFont( maFont );
if( pDXArry )
@ -1465,23 +1468,33 @@ void WinMtfOutput::DrawText( Point& rPosition, OUString& rText, long* pDXArry, b
if( nLen > 1 )
nTextWidth += pDXArry[ nLen - 2 ];
// tdf#39894: We should consider the distance to next character cell origin
nActPosDeltaX = pDXArry[ nLen - 1 ];
aActPosDelta.X() = pDXArry[ nLen - 1 ];
if ( pDYArry )
{
aActPosDelta.Y() = pDYArry[ nLen - 1 ];
}
}
else
{
nTextWidth = pVDev->GetTextWidth( rText );
aActPosDelta.X() = nTextWidth;
}
if( mnTextAlign & TA_UPDATECP )
rPosition = maActPos;
if ( mnTextAlign & TA_RIGHT_CENTER )
{
double fLength = ( ( mnTextAlign & TA_RIGHT_CENTER ) == TA_RIGHT ) ? nTextWidth : nTextWidth >> 1;
rPosition.X() -= (sal_Int32)( fLength * cos( maFont.GetOrientation() * F_PI1800 ) );
rPosition.Y() -= (sal_Int32)(-( fLength * sin( maFont.GetOrientation() * F_PI1800 ) ) );
Point aDisplacement( ( ( mnTextAlign & TA_RIGHT_CENTER ) == TA_RIGHT ) ? nTextWidth : nTextWidth >> 1, 0 );
Point().RotateAround(aDisplacement.X(), aDisplacement.Y(), maFont.GetOrientation());
rPosition -= aDisplacement;
}
if( mnTextAlign & TA_UPDATECP )
maActPos.X() = rPosition.X() + (pDXArry ? nActPosDeltaX : nTextWidth);
{
Point().RotateAround(aActPosDelta.X(), aActPosDelta.Y(), maFont.GetOrientation());
maActPos = rPosition + aActPosDelta;
}
}
if ( bChangeFont || ( maLatestFont != aTmp ) )
{
@ -1497,22 +1510,34 @@ void WinMtfOutput::DrawText( Point& rPosition, OUString& rText, long* pDXArry, b
}
else
{
/* because text without dx array is badly scaled, we
will create such an array if necessary */
long* pDX = pDXArry;
if (!pDXArry)
if ( pDXArry && pDYArry )
{
// #i117968# VirtualDevice is not thread safe, but filter is used in multithreading
SolarMutexGuard aGuard;
ScopedVclPtrInstance< VirtualDevice > pVDev;
pDX = new long[ rText.getLength() ];
pVDev->SetMapMode( MapUnit::Map100thMM );
pVDev->SetFont( maLatestFont );
pVDev->GetTextArray( rText, pDX, 0, rText.getLength());
for (sal_Int32 i = 0; i < rText.getLength(); ++i)
{
Point aCharDisplacement( i ? pDXArry[i-1] : 0, i ? pDYArry[i-1] : 0 );
Point().RotateAround(aCharDisplacement.X(), aCharDisplacement.Y(), maFont.GetOrientation());
mpGDIMetaFile->AddAction( new MetaTextArrayAction( rPosition + aCharDisplacement, OUString( rText[i] ), nullptr, 0, 1 ) );
}
}
else
{
/* because text without dx array is badly scaled, we
will create such an array if necessary */
long* pDX = pDXArry;
if (!pDXArry)
{
// #i117968# VirtualDevice is not thread safe, but filter is used in multithreading
SolarMutexGuard aGuard;
ScopedVclPtrInstance< VirtualDevice > pVDev;
pDX = new long[ rText.getLength() ];
pVDev->SetMapMode( MapUnit::Map100thMM );
pVDev->SetFont( maLatestFont );
pVDev->GetTextArray( rText, pDX, 0, rText.getLength());
}
mpGDIMetaFile->AddAction( new MetaTextArrayAction( rPosition, rText, pDX, 0, rText.getLength() ) );
if ( !pDXArry ) // this means we have created our own array
delete[] pDX; // which must be deleted
}
mpGDIMetaFile->AddAction( new MetaTextArrayAction( rPosition, rText, pDX, 0, rText.getLength() ) );
if ( !pDXArry ) // this means we have created our own array
delete[] pDX; // which must be deleted
}
SetGfxMode( nOldGfxMode );
}

View File

@ -600,6 +600,7 @@ public:
void DrawText( Point& rPosition,
OUString& rString,
long* pDXArry = nullptr,
long* pDYArry = nullptr,
bool bRecordPath = false,
sal_Int32 nGraphicsMode = GM_COMPATIBLE);

View File

@ -551,7 +551,7 @@ void WMFReader::ReadRecordParams( sal_uInt16 nFunc )
// dxAry will not fit
if ( nNewTextLen )
{
std::unique_ptr<long[]> pDXAry;
std::unique_ptr<long[]> pDXAry, pDYAry;
sal_uInt32 nMaxStreamPos = nRecordPos + ( nRecordSize << 1 );
sal_Int32 nDxArySize = nMaxStreamPos - pWMF->Tell();
sal_Int32 nDxAryEntries = nDxArySize >> 1;
@ -561,6 +561,10 @@ void WMFReader::ReadRecordParams( sal_uInt16 nFunc )
{
sal_uInt16 i; // needed just outside the for
pDXAry.reset(new long[ nNewTextLen ]);
if ( nOptions & ETO_PDY )
{
pDYAry.reset(new long[ nNewTextLen ]);
}
for (i = 0; i < nNewTextLen; i++ )
{
if ( pWMF->Tell() >= nMaxStreamPos )
@ -568,15 +572,15 @@ void WMFReader::ReadRecordParams( sal_uInt16 nFunc )
sal_Int32 nDxCount = 1;
if ( nNewTextLen != nOriginalTextLen )
{
sal_Unicode nUniChar = aText[i];
OString aTmp(&nUniChar, 1, pOut->GetCharSet());
sal_Unicode cUniChar = aText[i];
OString aTmp(&cUniChar, 1, pOut->GetCharSet());
if ( aTmp.getLength() > 1 )
{
nDxCount = aTmp.getLength();
}
}
sal_Int16 nDx = 0;
sal_Int16 nDx = 0, nDy = 0;
while ( nDxCount-- )
{
if ( ( pWMF->Tell() + 2 ) > nMaxStreamPos )
@ -590,17 +594,21 @@ void WMFReader::ReadRecordParams( sal_uInt16 nFunc )
break;
sal_Int16 nDyTmp = 0;
pWMF->ReadInt16(nDyTmp);
// TODO: use Dy offset
nDy += nDyTmp;
}
}
pDXAry[ i ] = nDx;
if ( nOptions & ETO_PDY )
{
pDYAry[i] = nDy;
}
}
if ( i == nNewTextLen )
bUseDXAry = true;
}
if ( pDXAry && bUseDXAry )
pOut->DrawText( aPosition, aText, pDXAry.get() );
pOut->DrawText( aPosition, aText, pDXAry.get(), pDYAry.get() );
else
pOut->DrawText( aPosition, aText );
}