diff options
-rw-r--r-- | emfio/inc/mtftools.hxx | 24 | ||||
-rw-r--r-- | emfio/source/reader/emfreader.cxx | 4 | ||||
-rw-r--r-- | emfio/source/reader/mtftools.cxx | 211 | ||||
-rw-r--r-- | emfio/source/reader/wmfreader.cxx | 3 | ||||
-rw-r--r-- | include/vcl/metaact.hxx | 8 | ||||
-rw-r--r-- | vcl/inc/impfont.hxx | 6 | ||||
-rw-r--r-- | vcl/source/filter/wmf/emfwr.cxx | 2 | ||||
-rw-r--r-- | vcl/source/font/font.cxx | 64 |
8 files changed, 286 insertions, 36 deletions
diff --git a/emfio/inc/mtftools.hxx b/emfio/inc/mtftools.hxx index 70471b66ea21..286442767d3a 100644 --- a/emfio/inc/mtftools.hxx +++ b/emfio/inc/mtftools.hxx @@ -27,6 +27,7 @@ #include <vcl/bitmapex.hxx> #include <vcl/lineinfo.hxx> #include <vcl/outdevstate.hxx> +#include <rtl/ref.hxx> #include "emfiodllapi.h" @@ -259,6 +260,8 @@ namespace emfio #define HUNDREDTH_MILLIMETERS_PER_MILLIINCH 2.54 #define MILLIINCH_PER_TWIPS 1.44 +class MetaFontAction; + //============================ WmfReader ================================== namespace emfio @@ -458,6 +461,24 @@ namespace emfio {} }; + // tdf#127471 implement detection and correction of wrongly scaled + // fonts in own-written, old (before this fix) EMF/WMF files + class ScaledFontDetectCorrectHelper + { + private: + rtl::Reference<MetaFontAction> maCurrentMetaFontAction; + std::vector<double> maAlternativeFontScales; + std::vector<std::pair<rtl::Reference<MetaFontAction>, double>> maPositiveIdentifiedCases; + std::vector<std::pair<rtl::Reference<MetaFontAction>, double>> maNegativeIdentifiedCases; + + public: + ScaledFontDetectCorrectHelper(); + void endCurrentMetaFontAction(); + void newCurrentMetaFontAction(rtl::Reference<MetaFontAction>& rNewMetaFontAction); + void evaluateAlternativeFontScale(OUString const & rText, tools::Long nImportedTextLength); + void applyAlternativeFontScale(); + }; + class MtfTools { MtfTools(MtfTools const &) = delete; @@ -521,6 +542,9 @@ namespace emfio sal_uInt32 mnEndPos; std::vector<std::unique_ptr<BSaveStruct>> maBmpSaveList; + // tdf#127471 always try to detect - only used with ScaledText + ScaledFontDetectCorrectHelper maScaledFontHelper; + bool mbNopMode : 1; bool mbFillStyleSelected : 1; bool mbClipNeedsUpdate : 1; diff --git a/emfio/source/reader/emfreader.cxx b/emfio/source/reader/emfreader.cxx index c2240ed612e5..30730725a182 100644 --- a/emfio/source/reader/emfreader.cxx +++ b/emfio/source/reader/emfreader.cxx @@ -2043,6 +2043,10 @@ namespace emfio } mpInputStream->Seek( nNextPos ); } + + // tdf#127471 + maScaledFontHelper.applyAlternativeFontScale(); + if( !maBmpSaveList.empty() ) ResolveBitmapActions( maBmpSaveList ); diff --git a/emfio/source/reader/mtftools.cxx b/emfio/source/reader/mtftools.cxx index 6064ca937a16..e64bb488a458 100644 --- a/emfio/source/reader/mtftools.cxx +++ b/emfio/source/reader/mtftools.cxx @@ -299,6 +299,184 @@ namespace emfio #endif }; + // tdf#127471 + ScaledFontDetectCorrectHelper::ScaledFontDetectCorrectHelper() + : maCurrentMetaFontAction(), + maAlternativeFontScales(), + maPositiveIdentifiedCases(), + maNegativeIdentifiedCases() + { + } + + void ScaledFontDetectCorrectHelper::endCurrentMetaFontAction() + { + if(maCurrentMetaFontAction.is() && !maAlternativeFontScales.empty()) + { + // create average corrected FontScale value and count + // positive/negative hits + sal_uInt32 nPositive(0); + sal_uInt32 nNegative(0); + double fAverage(0.0); + + for(double fPart : maAlternativeFontScales) + { + if(fPart < 0.0) + { + nNegative++; + fAverage += -fPart; + } + else + { + nPositive++; + fAverage += fPart; + } + } + + fAverage /= static_cast<double>(maAlternativeFontScales.size()); + + if(nPositive >= nNegative) + { + // correction intended, it is probably an old imported file + maPositiveIdentifiedCases.push_back(std::pair<rtl::Reference<MetaFontAction>, double>(maCurrentMetaFontAction, fAverage)); + } + else + { + // correction not favorable in the majority of cases for this Font, still + // remember to have a weight in the last decision for correction + maNegativeIdentifiedCases.push_back(std::pair<rtl::Reference<MetaFontAction>, double>(maCurrentMetaFontAction, fAverage)); + } + } + + maCurrentMetaFontAction.clear(); + maAlternativeFontScales.clear(); + } + + void ScaledFontDetectCorrectHelper::newCurrentMetaFontAction(rtl::Reference<MetaFontAction>& rNewMetaFontAction) + { + maCurrentMetaFontAction.clear(); + maAlternativeFontScales.clear(); + + if(rNewMetaFontAction.is()) + { + // check 1st criteria for FontScale active. We usually write this, + // so this will already sort out most situations + const vcl::Font& rCandidate(rNewMetaFontAction->GetFont()); + + if(0 != rCandidate.GetAverageFontWidth()) + { + const tools::Long nUnscaledAverageFontWidth(rCandidate.GetOrCalculateAverageFontWidth()); + + // check 2nd (system-dependent) criteria for FontScale + if(nUnscaledAverageFontWidth != rCandidate.GetFontHeight()) + { + // FontScale is active, remrmber and use as current + maCurrentMetaFontAction = rNewMetaFontAction; + } + } + } + } + + void ScaledFontDetectCorrectHelper::evaluateAlternativeFontScale(OUString const & rText, tools::Long nImportedTextLength) + { + if(maCurrentMetaFontAction.is()) + { + SolarMutexGuard aGuard; // VirtualDevice is not thread-safe + ScopedVclPtrInstance< VirtualDevice > pTempVirtualDevice; + + // calculate measured TextLength + const vcl::Font& rFontCandidate(maCurrentMetaFontAction->GetFont()); + pTempVirtualDevice->SetFont(rFontCandidate); + const tools::Long nMeasuredTextLength(pTempVirtualDevice->GetTextWidth(rText)); + + // compare expected and imported TextLengths + if(nImportedTextLength != nMeasuredTextLength) + { + const double fFactorText(static_cast<double>(nImportedTextLength) / static_cast<double>(nMeasuredTextLength)); + const double fFactorTextPercent(fabs(1.0 - fFactorText) * 100.0); + + // if we assume that loaded file was written on old linux, we have to + // back-convert the scale value depending on which system we run +#ifdef _WIN32 + // When running on Windows the value was not adapted at font import (see WinMtfFontStyle + // constructor), so it is still NormedFontScaling and we need to convert to Windows-style + // scaling +#else + // When running on unx (non-Windows) the value was already adapted at font import (see WinMtfFontStyle + // constructor). It was wrongly assumed to be Windows-style FontScaling, so we need to revert that + // to get back to the needed unx-style FontScale +#endif + // Interestingly this leads to the *same* correction, so no need to make this + // system-dependent (!) + const tools::Long nUnscaledAverageFontWidth(rFontCandidate.GetOrCalculateAverageFontWidth()); + const tools::Long nScaledAverageFontWidth(rFontCandidate.GetAverageFontWidth()); + const double fScaleFactor(static_cast<double>(nUnscaledAverageFontWidth) / static_cast<double>(rFontCandidate.GetFontHeight())); + const double fCorrectedAverageFontWidth(static_cast<double>(nScaledAverageFontWidth) * fScaleFactor); + tools::Long nCorrectedTextLength(0); + + { // do in own scope, only need nUnscaledAverageFontWidth + vcl::Font rFontCandidate2(rFontCandidate); + rFontCandidate2.SetAverageFontWidth(static_cast<tools::Long>(fCorrectedAverageFontWidth)); + pTempVirtualDevice->SetFont(rFontCandidate2); + nCorrectedTextLength = pTempVirtualDevice->GetTextWidth(rText); + } + + const double fFactorCorrectedText(static_cast<double>(nImportedTextLength) / static_cast<double>(nCorrectedTextLength)); + const double fFactorCorrectedTextPercent(fabs(1.0 - fFactorCorrectedText) * 100.0); + + // If FactorCorrectedText fits better than FactorText this is probably + // an import of an old EMF/WMF written by LibreOffice on a non-Windows (unx) system + // and should be corrected. + // Usually in tested cases this lies inside 5% of range, so detecting this just using + // fFactorTextPercent indside 5% -> no old file + // fFactorCorrectedTextPercent indside 5% -> is old file + // works not too bad, but there are some strange not so often used fonts where that + // values do deviate, so better just compare if old corrected would fit better than + // the uncorrected case, that is usually safe. + if(fFactorCorrectedTextPercent < fFactorTextPercent) + { + maAlternativeFontScales.push_back(fCorrectedAverageFontWidth); + } + else + { + // also push, but negative to remember non-fitting case + maAlternativeFontScales.push_back(-fCorrectedAverageFontWidth); + } + } + } + } + + void ScaledFontDetectCorrectHelper::applyAlternativeFontScale() + { + // make sure last evtl. detected current FontAction gets added to identified cases + endCurrentMetaFontAction(); + + // Take final decision to correct FontScaling for this imported Metafile or not. + // It is possible to weight positive against negative cases, so to only finally + // correct when more positive cases were detected. + // But that would be inconsequent and wrong. *If* the detected case is an old import + // the whole file was written with wrong FontScale values and all Font actions + // need to be corrected. Thus, for now, correct all when there are/is positive + // cases detected. + // On the other hand it *may* be that for some strange fonts there is a false-positive + // in the positive cases, so at least insist on positive cases being more than negative. + // Still, do then correct *all* cases. + if(!maPositiveIdentifiedCases.empty() + && maPositiveIdentifiedCases.size() >= maNegativeIdentifiedCases.size()) + { + for(std::pair<rtl::Reference<MetaFontAction>, double>& rCandidate : maPositiveIdentifiedCases) + { + rCandidate.first->correctFontScale(static_cast<tools::Long>(rCandidate.second)); + } + for(std::pair<rtl::Reference<MetaFontAction>, double>& rCandidate : maNegativeIdentifiedCases) + { + rCandidate.first->correctFontScale(static_cast<tools::Long>(rCandidate.second)); + } + } + + maPositiveIdentifiedCases.clear(); + maNegativeIdentifiedCases.clear(); + } + Color MtfTools::ReadColor() { sal_uInt32 nColor; @@ -927,6 +1105,7 @@ namespace emfio mnStartPos(0), mnEndPos(0), maBmpSaveList(), + maScaledFontHelper(), mbNopMode(false), mbFillStyleSelected(false), mbClipNeedsUpdate(true), @@ -1617,14 +1796,29 @@ namespace emfio maActPos = rPosition + aActPosDelta; } } - if ( bChangeFont || ( maLatestFont != aTmp ) ) + + if(bChangeFont || (maLatestFont != aTmp)) { maLatestFont = aTmp; - mpGDIMetaFile->AddAction( new MetaFontAction( aTmp ) ); + rtl::Reference<MetaFontAction> aNewMetaFontAction(new MetaFontAction(aTmp)); + + // tdf#127471 end evtl active MetaFontAction scale corrector detector/collector + maScaledFontHelper.endCurrentMetaFontAction(); + + // !bRecordPath: else no MetaTextArrayAction will be created + // nullptr != pDXArry: detection only possible when text size is given + // rText.getLength(): no useful check without text + if(!bRecordPath && nullptr != pDXArry && 0 != rText.getLength()) + { + maScaledFontHelper.newCurrentMetaFontAction(aNewMetaFontAction); + } + + mpGDIMetaFile->AddAction( aNewMetaFontAction ); mpGDIMetaFile->AddAction( new MetaTextAlignAction( aTmp.GetAlignment() ) ); mpGDIMetaFile->AddAction( new MetaTextColorAction( aTmp.GetColor() ) ); mpGDIMetaFile->AddAction( new MetaTextFillColorAction( aTmp.GetFillColor(), !aTmp.IsTransparent() ) ); } + if ( bRecordPath ) { // TODO @@ -1645,7 +1839,18 @@ namespace emfio /* because text without dx array is badly scaled, we will create such an array if necessary */ tools::Long* pDX = pDXArry; - if (!pDXArry) + if (pDXArry) + { + // only useful when we have an imported DXArray + if(!rText.isEmpty()) + { + maScaledFontHelper.evaluateAlternativeFontScale( + rText, + pDXArry[rText.getLength() - 1] // extract imported TextLength + ); + } + } + else { // #i117968# VirtualDevice is not thread safe, but filter is used in multithreading SolarMutexGuard aGuard; diff --git a/emfio/source/reader/wmfreader.cxx b/emfio/source/reader/wmfreader.cxx index 999584b02294..f285cacc1674 100644 --- a/emfio/source/reader/wmfreader.cxx +++ b/emfio/source/reader/wmfreader.cxx @@ -1234,6 +1234,9 @@ namespace emfio case W_META_ENDDOC: break; } + + // tdf#127471 + maScaledFontHelper.applyAlternativeFontScale(); } const tools::Long aMaxWidth = 1024; diff --git a/include/vcl/metaact.hxx b/include/vcl/metaact.hxx index 5ed09a9c5315..69552a2bbf95 100644 --- a/include/vcl/metaact.hxx +++ b/include/vcl/metaact.hxx @@ -1410,12 +1410,20 @@ public: const MapMode& GetMapMode() const { return maMapMode; } }; +// tdf#127471 decl for friend below +namespace emfio { class ScaledFontDetectCorrectHelper; } + class UNLESS_MERGELIBS(VCL_DLLPUBLIC) MetaFontAction final : public MetaAction { private: vcl::Font maFont; + // tdf#127471 for import correction of own wrong written EMF/WMF files allow correction + // of FontScale after import. Only from there, so use a friend here and keep the method private + friend class emfio::ScaledFontDetectCorrectHelper; + void correctFontScale(tools::Long nNewFontScale) { maFont.SetAverageFontWidth(nNewFontScale); } + public: MetaFontAction(); MetaFontAction(MetaFontAction const &) = default; diff --git a/vcl/inc/impfont.hxx b/vcl/inc/impfont.hxx index 14a2189d3921..35150abc87c0 100644 --- a/vcl/inc/impfont.hxx +++ b/vcl/inc/impfont.hxx @@ -64,14 +64,12 @@ public: void SetCharSet( const rtl_TextEncoding eCharSet ) { meCharSet = eCharSet; } void SetFontSize( const Size& rSize ) { -#ifndef _WIN32 if(rSize.Height() != maAverageFontSize.Height()) { // reset evtl. buffered calculated AverageFontSize, it depends // on Font::Height mnCalculatedAverageFontWidth = 0; } -#endif maAverageFontSize = rSize; } @@ -91,10 +89,8 @@ public: void IncreaseQualityBy( int nQualityAmount ) { mnQuality += nQualityAmount; } void DecreaseQualityBy( int nQualityAmount ) { mnQuality -= nQualityAmount; } -#ifndef _WIN32 tools::Long GetCalculatedAverageFontWidth() const { return mnCalculatedAverageFontWidth; } void SetCalculatedAverageFontWidth(tools::Long nNew) { mnCalculatedAverageFontWidth = nNew; } -#endif bool operator==( const ImplFont& ) const; @@ -146,9 +142,7 @@ private: int mnQuality; -#ifndef _WIN32 tools::Long mnCalculatedAverageFontWidth; -#endif }; #endif // INCLUDED_VCL_INC_IMPFONT_HXX diff --git a/vcl/source/filter/wmf/emfwr.cxx b/vcl/source/filter/wmf/emfwr.cxx index 51e24469e3d7..9f0ab33993f6 100644 --- a/vcl/source/filter/wmf/emfwr.cxx +++ b/vcl/source/filter/wmf/emfwr.cxx @@ -474,7 +474,7 @@ void EMFWriter::ImplCheckTextAttr() // specific formats. const tools::Long nAverageFontWidth(rFont.GetOrCalculateAverageFontWidth()); - if(nAverageFontWidth > 0) + if(nFontHeight > 0) { const double fScaleFactor(static_cast<double>(nAverageFontWidth) / static_cast<double>(nFontHeight)); nFontWidth = static_cast<tools::Long>(static_cast<double>(nFontWidth) * fScaleFactor); diff --git a/vcl/source/font/font.cxx b/vcl/source/font/font.cxx index ed12ff7e0d6c..9c5204d190f5 100644 --- a/vcl/source/font/font.cxx +++ b/vcl/source/font/font.cxx @@ -36,6 +36,7 @@ #include <rtl/instance.hxx> #include <vcl/TypeSerializer.hxx> +#include <vcl/svapp.hxx> #ifdef _WIN32 #include <vcl/metric.hxx> @@ -369,34 +370,49 @@ void Font::GetFontAttributes( FontAttributes& rAttrs ) const // tdf#127471 for corrections on EMF/WMF we need the AvgFontWidth in Windows-specific notation tools::Long Font::GetOrCalculateAverageFontWidth() const { -#ifdef _WIN32 - // on windows we just have it available - return GetAverageFontWidth(); -#else - // On non-Windows systems we need to calculate AvgFontWidth - // as close as possible (discussion see documentation in task) if(0 == mpImplFont->GetCalculatedAverageFontWidth()) { - // calculate it. For discussion of method used, see task - const std::size_t nSize(127 - 32); - std::array<sal_Unicode, nSize> aArray; + // VirtualDevice is not thread safe + SolarMutexGuard aGuard; + + // create unscaled copy of font (this), a VirtualDevice and set it there + vcl::Font aUnscaledFont(*this); + ScopedVclPtr<VirtualDevice> pTempVirtualDevice(VclPtr<VirtualDevice>::Create()); + aUnscaledFont.SetAverageFontWidth(0); + pTempVirtualDevice->SetFont(aUnscaledFont); - for(sal_Unicode a(0); a < nSize; a++) +#ifdef _WIN32 + // on Windows systems use FontMetric to get/create AverageFontWidth from system + const FontMetric aMetric(pTempVirtualDevice->GetFontMetric()); + const_cast<Font*>(this)->mpImplFont->SetCalculatedAverageFontWidth(aMetric.GetAverageFontWidth()); +#else + // On non-Windows systems we need to calculate AvgFontWidth + // as close as possible (discussion see documentation in task), + // so calculate it. For discussion of method used, see task + // buffer measure string creation, will always use the same + static OUString aMeasureString; + + if(!aMeasureString.getLength()) { - aArray[a] = a + 32; + const std::size_t nSize(127 - 32); + std::array<sal_Unicode, nSize> aArray; + + for(sal_Unicode a(0); a < nSize; a++) + { + aArray[a] = a + 32; + } + + aMeasureString = OUString(aArray.data()); } - vcl::Font aUnscaledFont(*this); - ScopedVclPtr<VirtualDevice> pVirDev(VclPtr<VirtualDevice>::Create()); - aUnscaledFont.SetAverageFontWidth(0); - pVirDev->SetFont(aUnscaledFont); const double fAverageFontWidth( - pVirDev->GetTextWidth(OUString(aArray.data(), nSize)) / static_cast<double>(nSize)); + pTempVirtualDevice->GetTextWidth(aMeasureString, aMeasureString.getLength()) / + static_cast<double>(aMeasureString.getLength())); const_cast<Font*>(this)->mpImplFont->SetCalculatedAverageFontWidth(basegfx::fround(fAverageFontWidth)); +#endif } return mpImplFont->GetCalculatedAverageFontWidth(); -#endif } SvStream& ReadImplFont( SvStream& rIStm, ImplFont& rImplFont, tools::Long& rnNormedFontScaling ) @@ -521,7 +537,7 @@ SvStream& ReadFont( SvStream& rIStm, vcl::Font& rFont ) aUnscaledFont.SetAverageFontWidth(0); const FontMetric aUnscaledFontMetric(Application::GetDefaultDevice()->GetFontMetric(aUnscaledFont)); - if (aUnscaledFontMetric.GetAverageFontWidth() > 0) + if (nHeight > 0) { const double fScaleFactor(static_cast<double>(nNormedFontScaling) / static_cast<double>(nHeight)); nScaledWidth = basegfx::fround(static_cast<double>(aUnscaledFontMetric.GetAverageFontWidth()) * fScaleFactor); @@ -885,10 +901,8 @@ ImplFont::ImplFont() : maFillColor( COL_TRANSPARENT ), mbWordLine( false ), mnOrientation( 0 ), - mnQuality( 0 ) -#ifndef _WIN32 - , mnCalculatedAverageFontWidth(0) -#endif + mnQuality( 0 ), + mnCalculatedAverageFontWidth( 0 ) {} ImplFont::ImplFont( const ImplFont& rImplFont ) : @@ -920,10 +934,8 @@ ImplFont::ImplFont( const ImplFont& rImplFont ) : maFillColor( rImplFont.maFillColor ), mbWordLine( rImplFont.mbWordLine ), mnOrientation( rImplFont.mnOrientation ), - mnQuality( rImplFont.mnQuality ) -#ifndef _WIN32 - , mnCalculatedAverageFontWidth(rImplFont.mnCalculatedAverageFontWidth) -#endif + mnQuality( rImplFont.mnQuality ), + mnCalculatedAverageFontWidth( rImplFont.mnCalculatedAverageFontWidth ) {} bool ImplFont::operator==( const ImplFont& rOther ) const |