diff options
Diffstat (limited to 'drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx')
-rw-r--r-- | drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx | 935 |
1 files changed, 560 insertions, 375 deletions
diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx index b679768c9c3b..d93d98fef51a 100644 --- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx @@ -24,27 +24,30 @@ #include <rtl/ustring.hxx> #include <tools/gen.hxx> #include <tools/stream.hxx> -#include <tools/diagnose_ex.h> +#include <comphelper/diagnose_ex.hxx> +#include <comphelper/flagguard.hxx> #include <comphelper/processfactory.hxx> #include <config_global.h> #include <basegfx/polygon/b2dpolygonclipper.hxx> #include <basegfx/polygon/b2dpolypolygontools.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <basegfx/polygon/b2dlinegeometry.hxx> +#include <basegfx/utils/gradienttools.hxx> #include <vcl/virdev.hxx> #include <vcl/gdimtf.hxx> #include <vcl/gradient.hxx> #include <vcl/graphictools.hxx> #include <vcl/metaact.hxx> #include <vcl/graph.hxx> // for PDFExtOutDevData Graphic support -#include <toolkit/helper/formpdfexport.hxx> // for PDFExtOutDevData Graphic support +#include <vcl/formpdfexport.hxx> // for PDFExtOutDevData Graphic support #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonHatchPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> #include <drawinglayer/primitive2d/maskprimitive2d.hxx> #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> @@ -61,6 +64,9 @@ #include <drawinglayer/primitive2d/epsprimitive2d.hxx> #include <drawinglayer/primitive2d/structuretagprimitive2d.hxx> #include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> // for Title/Description metadata +#include <drawinglayer/converters.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <tools/vcompat.hxx> #include <com/sun/star/awt/XControl.hpp> #include <com/sun/star/i18n/BreakIterator.hpp> @@ -220,8 +226,10 @@ VclMetafileProcessor2D::impDumpToMetaFile(const primitive2d::Primitive2DContaine aPrimitiveRange.transform(maCurrentTransformation); const tools::Rectangle aPrimitiveRectangle( - basegfx::fround(aPrimitiveRange.getMinX()), basegfx::fround(aPrimitiveRange.getMinY()), - basegfx::fround(aPrimitiveRange.getMaxX()), basegfx::fround(aPrimitiveRange.getMaxY())); + basegfx::fround<tools::Long>(aPrimitiveRange.getMinX()), + basegfx::fround<tools::Long>(aPrimitiveRange.getMinY()), + basegfx::fround<tools::Long>(aPrimitiveRange.getMaxX()), + basegfx::fround<tools::Long>(aPrimitiveRange.getMaxY())); ScopedVclPtrInstance<VirtualDevice> aContentVDev; MapMode aNewMapMode(pLastOutputDevice->GetMapMode()); @@ -256,19 +264,20 @@ void VclMetafileProcessor2D::impConvertFillGradientAttributeToVCLGradient( Gradient& o_rVCLGradient, const attribute::FillGradientAttribute& rFiGrAtt, bool bIsTransparenceGradient) const { + const basegfx::BColor aStartColor(rFiGrAtt.getColorStops().front().getStopColor()); + const basegfx::BColor aEndColor(rFiGrAtt.getColorStops().back().getStopColor()); + if (bIsTransparenceGradient) { // it's about transparence channel intensities (black/white), do not use color modifier - o_rVCLGradient.SetStartColor(Color(rFiGrAtt.getStartColor())); - o_rVCLGradient.SetEndColor(Color(rFiGrAtt.getEndColor())); + o_rVCLGradient.SetStartColor(Color(aStartColor)); + o_rVCLGradient.SetEndColor(Color(aEndColor)); } else { // use color modifier to influence start/end color of gradient - o_rVCLGradient.SetStartColor( - Color(maBColorModifierStack.getModifiedColor(rFiGrAtt.getStartColor()))); - o_rVCLGradient.SetEndColor( - Color(maBColorModifierStack.getModifiedColor(rFiGrAtt.getEndColor()))); + o_rVCLGradient.SetStartColor(Color(maBColorModifierStack.getModifiedColor(aStartColor))); + o_rVCLGradient.SetEndColor(Color(maBColorModifierStack.getModifiedColor(aEndColor))); } o_rVCLGradient.SetAngle( @@ -281,40 +290,7 @@ void VclMetafileProcessor2D::impConvertFillGradientAttributeToVCLGradient( // defaults for intensity; those were computed into the start/end colors already o_rVCLGradient.SetStartIntensity(100); o_rVCLGradient.SetEndIntensity(100); - - switch (rFiGrAtt.getStyle()) - { - default: // attribute::GradientStyle::Linear : - { - o_rVCLGradient.SetStyle(GradientStyle::Linear); - break; - } - case attribute::GradientStyle::Axial: - { - o_rVCLGradient.SetStyle(GradientStyle::Axial); - break; - } - case attribute::GradientStyle::Radial: - { - o_rVCLGradient.SetStyle(GradientStyle::Radial); - break; - } - case attribute::GradientStyle::Elliptical: - { - o_rVCLGradient.SetStyle(GradientStyle::Elliptical); - break; - } - case attribute::GradientStyle::Square: - { - o_rVCLGradient.SetStyle(GradientStyle::Square); - break; - } - case attribute::GradientStyle::Rect: - { - o_rVCLGradient.SetStyle(GradientStyle::Rect); - break; - } - } + o_rVCLGradient.SetStyle(rFiGrAtt.getStyle()); } void VclMetafileProcessor2D::impStartSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill) @@ -325,7 +301,7 @@ void VclMetafileProcessor2D::impStartSvtGraphicFill(SvtGraphicFill const* pSvtGr WriteSvtGraphicFill(aMemStm, *pSvtGraphicFill); mpMetaFile->AddAction(new MetaCommentAction( - "XPATHFILL_SEQ_BEGIN", 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), + "XPATHFILL_SEQ_BEGIN"_ostr, 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), aMemStm.TellEnd())); mnSvtGraphicFillCount++; } @@ -336,7 +312,7 @@ void VclMetafileProcessor2D::impEndSvtGraphicFill(SvtGraphicFill const* pSvtGrap if (pSvtGraphicFill && mnSvtGraphicFillCount) { mnSvtGraphicFillCount--; - mpMetaFile->AddAction(new MetaCommentAction("XPATHFILL_SEQ_END")); + mpMetaFile->AddAction(new MetaCommentAction("XPATHFILL_SEQ_END"_ostr)); } } @@ -509,7 +485,7 @@ void VclMetafileProcessor2D::impStartSvtGraphicStroke(SvtGraphicStroke const* pS WriteSvtGraphicStroke(aMemStm, *pSvtGraphicStroke); mpMetaFile->AddAction(new MetaCommentAction( - "XPATHSTROKE_SEQ_BEGIN", 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), + "XPATHSTROKE_SEQ_BEGIN"_ostr, 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), aMemStm.TellEnd())); mnSvtGraphicStrokeCount++; } @@ -520,7 +496,7 @@ void VclMetafileProcessor2D::impEndSvtGraphicStroke(SvtGraphicStroke const* pSvt if (pSvtGraphicStroke && mnSvtGraphicStrokeCount) { mnSvtGraphicStrokeCount--; - mpMetaFile->AddAction(new MetaCommentAction("XPATHSTROKE_SEQ_END")); + mpMetaFile->AddAction(new MetaCommentAction("XPATHSTROKE_SEQ_END"_ostr)); } } @@ -546,7 +522,8 @@ void VclMetafileProcessor2D::popList() } // init static break iterator -uno::Reference<css::i18n::XBreakIterator> VclMetafileProcessor2D::mxBreakIterator; +vcl::DeleteOnDeinit<uno::Reference<css::i18n::XBreakIterator>> + VclMetafileProcessor2D::mxBreakIterator; VclMetafileProcessor2D::VclMetafileProcessor2D(const geometry::ViewInformation2D& rViewInformation, OutputDevice& rOutDev) @@ -718,17 +695,7 @@ VclMetafileProcessor2D::~VclMetafileProcessor2D() - UnoControlPDFExportContact is only created when PDFExtOutDevData is used at the target and uno control data is created in UnoControlPDFExportContact::do_PaintObject. - This may be added in primitive MetaFile renderer. - Adding support... - OOps, the necessary helper stuff is in svx/source/form/formpdxexport.cxx in namespace - svxform. Have to talk to FS if this has to be like that. Especially since - vcl::PDFWriter::AnyWidget is filled out, which is already part of vcl. - Wrote an eMail to FS, he is on vacation currently. I see no reason why not to move - that stuff to somewhere else, maybe tools or svtools ?!? We will see... - Moved to toolkit, so I have to link against it. I tried VCL first, but it did - not work since VCLUnoHelper::CreateFont is unresolved in VCL (!). Other than the name - may imply, it is defined in toolkit (!). Since toolkit is linked against VCL itself, - the lowest movement plane is toolkit. + This was added in primitive MetaFile renderer. Checked form control export, it works well. Done. - In goodies, in GraphicObject::Draw, when the used Graphic is linked, infos are @@ -836,8 +803,17 @@ void VclMetafileProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimi } case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D: { - processPolyPolygonGraphicPrimitive2D( - static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D&>(rCandidate)); + if (maBColorModifierStack.count()) + { + // tdf#151104 unfortunately processPolyPolygonGraphicPrimitive2D below + // does not support an active BColorModifierStack, so use the default + process(rCandidate); + } + else + { + processPolyPolygonGraphicPrimitive2D( + static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D&>(rCandidate)); + } break; } case PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D: @@ -923,17 +899,10 @@ void VclMetafileProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimi } case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D: { - RenderObjectInfoPrimitive2D( + processObjectInfoPrimitive2D( static_cast<const primitive2d::ObjectInfoPrimitive2D&>(rCandidate)); break; } - case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D: - case PRIMITIVE2D_ID_GLOWPRIMITIVE2D: - case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D: - { - processPrimitive2DOnPixelProcessor(rCandidate); - break; - } default: { // process recursively @@ -943,6 +912,51 @@ void VclMetafileProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimi } } +void VclMetafileProcessor2D::processObjectInfoPrimitive2D( + primitive2d::ObjectInfoPrimitive2D const& rObjectInfoPrimitive2D) +{ + // tdf#154982 process content first, so this object overrides any nested one + process(rObjectInfoPrimitive2D.getChildren()); + + // currently StructureTagPrimitive2D is only used for SdrObjects - have to + // avoid adding Alt text if the SdrObject is not actually tagged, as it + // would then end up on an unrelated structure element. + if (mpCurrentStructureTag && mpCurrentStructureTag->isTaggedSdrObject()) + { + // Create image alternative description from ObjectInfoPrimitive2D info + // for PDF export, for the currently active SdrObject's structure element + if (mpPDFExtOutDevData->GetIsExportTaggedPDF()) + { + OUString aAlternateDescription; + + if (!rObjectInfoPrimitive2D.getTitle().isEmpty()) + { + aAlternateDescription += rObjectInfoPrimitive2D.getTitle(); + } + + if (!rObjectInfoPrimitive2D.getDesc().isEmpty()) + { + if (!aAlternateDescription.isEmpty()) + { + aAlternateDescription += " - "; + } + + aAlternateDescription += rObjectInfoPrimitive2D.getDesc(); + } + + // Use SetAlternateText to set it. This will work as long as some + // structure is used (see PDFWriterImpl::setAlternateText and + // m_nCurrentStructElement - tagged PDF export works with this in + // Draw/Impress/Writer, but not in Calc due to too less structure...) + //Z maybe add structure to Calc PDF export, may need some BeginGroup/EndGroup stuff ..? + if (!aAlternateDescription.isEmpty()) + { + mpPDFExtOutDevData->SetAlternateText(aAlternateDescription); + } + } + } +} + void VclMetafileProcessor2D::processGraphicPrimitive2D( const primitive2d::GraphicPrimitive2D& rGraphicPrimitive) { @@ -1034,38 +1048,6 @@ void VclMetafileProcessor2D::processGraphicPrimitive2D( sal_Int32(ceil(aCropRange.getMaxX())), sal_Int32(ceil(aCropRange.getMaxY()))); } - // Create image alternative description from ObjectInfoPrimitive2D info - // for PDF export - if (mpPDFExtOutDevData->GetIsExportTaggedPDF() && nullptr != getObjectInfoPrimitive2D()) - { - OUString aAlternateDescription; - - if (!getObjectInfoPrimitive2D()->getTitle().isEmpty()) - { - aAlternateDescription += getObjectInfoPrimitive2D()->getTitle(); - } - - if (!getObjectInfoPrimitive2D()->getDesc().isEmpty()) - { - if (!aAlternateDescription.isEmpty()) - { - aAlternateDescription += " - "; - } - - aAlternateDescription += getObjectInfoPrimitive2D()->getDesc(); - } - - // Use SetAlternateText to set it. This will work as long as some - // structure is used (see PDFWriterImpl::setAlternateText and - // m_nCurrentStructElement - tagged PDF export works with this in - // Draw/Impress/Writer, but not in Calc due to too less structure...) - //Z maybe add structure to Calc PDF export, may need some BeginGroup/EndGroup stuff ..? - if (!aAlternateDescription.isEmpty()) - { - mpPDFExtOutDevData->SetAlternateText(aAlternateDescription); - } - } - // #i123295# 3rd param is uncropped rect, 4th is cropped. The primitive has the cropped // object transformation, thus aCurrentRect *is* the clip region while aCropRect is the expanded, // uncropped region. Thus, correct order is aCropRect, aCurrentRect @@ -1089,7 +1071,7 @@ void VclMetafileProcessor2D::processControlPrimitive2D( uno::Reference<beans::XPropertySetInfo> xPropertyInfo( xModelProperties.is() ? xModelProperties->getPropertySetInfo() : uno::Reference<beans::XPropertySetInfo>()); - static const OUStringLiteral sPrintablePropertyName(u"Printable"); + static constexpr OUString sPrintablePropertyName(u"Printable"_ustr); if (xPropertyInfo.is() && xPropertyInfo->hasPropertyByName(sPrintablePropertyName)) { @@ -1108,14 +1090,25 @@ void VclMetafileProcessor2D::processControlPrimitive2D( if (!bIsPrintableControl) return; + ::std::optional<sal_Int32> oAnchorParent; + if (mpPDFExtOutDevData) + { + if (rControlPrimitive.GetAnchorStructureElementKey()) + { + sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement( + rControlPrimitive.GetAnchorStructureElementKey()); + oAnchorParent.emplace(mpPDFExtOutDevData->GetCurrentStructureElement()); + mpPDFExtOutDevData->SetCurrentStructureElement(id); + } + } + const bool bPDFExport(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportFormFields()); bool bDoProcessRecursively(true); + bool bDecorative = (mpCurrentStructureTag && mpCurrentStructureTag->isDecorative()); - if (bPDFExport) + if (bPDFExport && !bDecorative) { // PDF export. Emulate data handling from UnoControlPDFExportContact - // I have now moved describePDFControl to toolkit, thus i can implement the PDF - // form control support now as follows std::unique_ptr<vcl::PDFWriter::AnyWidget> pPDFControl( ::toolkitform::describePDFControl(rXControl, *mpPDFExtOutDevData)); @@ -1135,9 +1128,37 @@ void VclMetafileProcessor2D::processControlPrimitive2D( mpOutputDevice->GetMapMode()); pPDFControl->TextFont.SetFontSize(aFontSize); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Form); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Form); + vcl::PDFWriter::StructAttributeValue role; + switch (pPDFControl->Type) + { + case vcl::PDFWriter::PushButton: + role = vcl::PDFWriter::Pb; + break; + case vcl::PDFWriter::RadioButton: + role = vcl::PDFWriter::Rb; + break; + case vcl::PDFWriter::CheckBox: + role = vcl::PDFWriter::Cb; + break; + default: // there is a paucity of roles, tv is the catch-all one + role = vcl::PDFWriter::Tv; + break; + } + // ISO 14289-1:2014, Clause: 7.18.4 + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Role, role); + // ISO 14289-1:2014, Clause: 7.18.1 + OUString const& rAltText(rControlPrimitive.GetAltText()); + if (!rAltText.isEmpty()) + { + mpPDFExtOutDevData->SetAlternateText(rAltText); + } mpPDFExtOutDevData->CreateControl(*pPDFControl); mpPDFExtOutDevData->EndStructureElement(); + if (oAnchorParent) + { + mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent); + } // no normal paint needed (see original UnoControlPDFExportContact::do_PaintObject); // do not process recursively @@ -1151,6 +1172,31 @@ void VclMetafileProcessor2D::processControlPrimitive2D( } } + if (!bDoProcessRecursively) + { + return; + } + + if (mpPDFExtOutDevData) + { // no corresponding PDF Form, use Figure instead + if (!bDecorative) + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Figure); + else + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::NonStructElement); + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Placement, vcl::PDFWriter::Block); + auto const range(rControlPrimitive.getB2DRange(getViewInformation2D())); + tools::Rectangle const aLogicRect(basegfx::fround<tools::Long>(range.getMinX()), + basegfx::fround<tools::Long>(range.getMinY()), + basegfx::fround<tools::Long>(range.getMaxX()), + basegfx::fround<tools::Long>(range.getMaxY())); + mpPDFExtOutDevData->SetStructureBoundingBox(aLogicRect); + OUString const& rAltText(rControlPrimitive.GetAltText()); + if (!rAltText.isEmpty() && !bDecorative) + { + mpPDFExtOutDevData->SetAlternateText(rAltText); + } + } + // #i93169# used flag the wrong way; true means that nothing was done yet if (bDoProcessRecursively) { @@ -1195,6 +1241,15 @@ void VclMetafileProcessor2D::processControlPrimitive2D( { process(rControlPrimitive); } + + if (mpPDFExtOutDevData) + { + mpPDFExtOutDevData->EndStructureElement(); + if (oAnchorParent) + { + mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent); + } + } } void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D( @@ -1202,7 +1257,7 @@ void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D( { // support for FIELD_SEQ_BEGIN, FIELD_SEQ_END and URL. It wraps text primitives (but is not limited to) // thus do the MetafileAction embedding stuff but just handle recursively. - const OString aCommentStringCommon("FIELD_SEQ_BEGIN"); + static constexpr OString aCommentStringCommon("FIELD_SEQ_BEGIN"_ostr); OUString aURL; switch (rFieldPrimitive.getType()) @@ -1214,7 +1269,7 @@ void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D( } case drawinglayer::primitive2d::FIELD_TYPE_PAGE: { - mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_BEGIN;PageField")); + mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_BEGIN;PageField"_ostr)); break; } case drawinglayer::primitive2d::FIELD_TYPE_URL: @@ -1238,7 +1293,7 @@ void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D( process(rContent); // for the end comment the type is not relevant yet, they are all the same. Just add. - mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_END")); + mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_END"_ostr)); if (!(mpPDFExtOutDevData && drawinglayer::primitive2d::FIELD_TYPE_URL == rFieldPrimitive.getType())) @@ -1251,7 +1306,8 @@ void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D( static_cast<sal_Int32>(ceil(aViewRange.getMaxX())), static_cast<sal_Int32>(ceil(aViewRange.getMaxY()))); vcl::PDFExtOutDevBookmarkEntry aBookmark; - aBookmark.nLinkId = mpPDFExtOutDevData->CreateLink(aRectLogic); + OUString const content(rFieldPrimitive.getValue("Representation")); + aBookmark.nLinkId = mpPDFExtOutDevData->CreateLink(aRectLogic, content); aBookmark.aBookmark = aURL; std::vector<vcl::PDFExtOutDevBookmarkEntry>& rBookmarks = mpPDFExtOutDevData->GetBookmarks(); rBookmarks.push_back(aBookmark); @@ -1262,7 +1318,7 @@ void VclMetafileProcessor2D::processTextHierarchyLinePrimitive2D( { // process recursively and add MetaFile comment process(rLinePrimitive); - mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOL")); + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOL"_ostr)); } void VclMetafileProcessor2D::processTextHierarchyBulletPrimitive2D( @@ -1272,14 +1328,14 @@ void VclMetafileProcessor2D::processTextHierarchyBulletPrimitive2D( if (mbInListItem) { maListElements.push(vcl::PDFWriter::LILabel); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::LILabel); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::LILabel); } // process recursively and add MetaFile comment process(rBulletPrimitive); // in Outliner::PaintBullet(), a MetafileComment for bullets is added, too. The // "XTEXT_EOC" is used, use here, too. - mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOC")); + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOC"_ostr)); if (mbInListItem) { @@ -1295,7 +1351,7 @@ void VclMetafileProcessor2D::processTextHierarchyBulletPrimitive2D( void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D( const primitive2d::TextHierarchyParagraphPrimitive2D& rParagraphPrimitive) { - const OString aCommentString("XTEXT_EOP"); + static constexpr OString aCommentString("XTEXT_EOP"_ostr); static bool bSuppressPDFExtOutDevDataSupport(false); // loplugin:constvars:ignore if (nullptr == mpPDFExtOutDevData || bSuppressPDFExtOutDevDataSupport) @@ -1311,7 +1367,7 @@ void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D( { // No Tagged PDF -> Dump as Paragraph // Emulate data handling from old ImpEditEngine::Paint - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Paragraph); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Paragraph); // Process recursively and add MetaFile comment process(rParagraphPrimitive); @@ -1337,7 +1393,7 @@ void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D( for (sal_Int16 a(mnCurrentOutlineLevel); a != nNewOutlineLevel; ++a) { maListElements.push(vcl::PDFWriter::List); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::List); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::List); } } else // if(nNewOutlineLevel < mnCurrentOutlineLevel) @@ -1368,13 +1424,13 @@ void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D( { // Dump as ListItem maListElements.push(vcl::PDFWriter::ListItem); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::ListItem); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::ListItem); mbInListItem = true; } else { // Dump as Paragraph - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Paragraph); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Paragraph); } // Process recursively and add MetaFile comment @@ -1391,7 +1447,7 @@ void VclMetafileProcessor2D::processTextHierarchyBlockPrimitive2D( const primitive2d::TextHierarchyBlockPrimitive2D& rBlockPrimitive) { // add MetaFile comment, process recursively and add MetaFile comment - mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_BEGIN")); + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_BEGIN"_ostr)); process(rBlockPrimitive); if (mnCurrentOutlineLevel >= 0) @@ -1403,7 +1459,7 @@ void VclMetafileProcessor2D::processTextHierarchyBlockPrimitive2D( } } - mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_END")); + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_END"_ostr)); } void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D( @@ -1418,7 +1474,7 @@ void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D( if (mbInListItem && mbBulletPresent) { maListElements.push(vcl::PDFWriter::LIBody); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::LIBody); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::LIBody); } // directdraw of text simple portion; use default processing @@ -1433,12 +1489,13 @@ void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D( // #i101169# if(pTextDecoratedCandidate) { // support for TEXT_ MetaFile actions only for decorated texts - if (!mxBreakIterator.is()) + if (!mxBreakIterator.get() || !mxBreakIterator.get()->get()) { uno::Reference<uno::XComponentContext> xContext( ::comphelper::getProcessComponentContext()); - mxBreakIterator = i18n::BreakIterator::create(xContext); + mxBreakIterator.set(i18n::BreakIterator::create(xContext)); } + auto& rBreakIterator = *mxBreakIterator.get()->get(); const OUString& rTxt = rTextCandidate.getText(); const sal_Int32 nTextLength(rTextCandidate.getTextLength()); // rTxt.getLength()); @@ -1449,16 +1506,16 @@ void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D( const sal_Int32 nTextPosition(rTextCandidate.getTextPosition()); sal_Int32 nDone; - sal_Int32 nNextCellBreak(mxBreakIterator->nextCharacters( + sal_Int32 nNextCellBreak(rBreakIterator.nextCharacters( rTxt, nTextPosition, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 0, nDone)); - css::i18n::Boundary nNextWordBoundary(mxBreakIterator->getWordBoundary( + css::i18n::Boundary nNextWordBoundary(rBreakIterator.getWordBoundary( rTxt, nTextPosition, rLocale, css::i18n::WordType::ANY_WORD, true)); sal_Int32 nNextSentenceBreak( - mxBreakIterator->endOfSentence(rTxt, nTextPosition, rLocale)); - const OString aCommentStringA("XTEXT_EOC"); - const OString aCommentStringB("XTEXT_EOW"); - const OString aCommentStringC("XTEXT_EOS"); + rBreakIterator.endOfSentence(rTxt, nTextPosition, rLocale)); + static constexpr OStringLiteral aCommentStringA("XTEXT_EOC"); + static constexpr OStringLiteral aCommentStringB("XTEXT_EOW"); + static constexpr OStringLiteral aCommentStringC("XTEXT_EOS"); for (sal_Int32 i(nTextPosition); i < nTextPosition + nTextLength; i++) { @@ -1467,21 +1524,21 @@ void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D( { mpMetaFile->AddAction( new MetaCommentAction(aCommentStringA, i - nTextPosition)); - nNextCellBreak = mxBreakIterator->nextCharacters( + nNextCellBreak = rBreakIterator.nextCharacters( rTxt, i, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); } if (i == nNextWordBoundary.endPos) { mpMetaFile->AddAction( new MetaCommentAction(aCommentStringB, i - nTextPosition)); - nNextWordBoundary = mxBreakIterator->getWordBoundary( + nNextWordBoundary = rBreakIterator.getWordBoundary( rTxt, i + 1, rLocale, css::i18n::WordType::ANY_WORD, true); } if (i == nNextSentenceBreak) { mpMetaFile->AddAction( new MetaCommentAction(aCommentStringC, i - nTextPosition)); - nNextSentenceBreak = mxBreakIterator->endOfSentence(rTxt, i + 1, rLocale); + nNextSentenceBreak = rBreakIterator.endOfSentence(rTxt, i + 1, rLocale); } } } @@ -1500,9 +1557,11 @@ void VclMetafileProcessor2D::processPolygonHairlinePrimitive2D( basegfx::B2DPolygon aLeft, aRight; splitLinePolygon(rBasePolygon, aLeft, aRight); rtl::Reference<primitive2d::PolygonHairlinePrimitive2D> xPLeft( - new primitive2d::PolygonHairlinePrimitive2D(aLeft, rHairlinePrimitive.getBColor())); + new primitive2d::PolygonHairlinePrimitive2D(std::move(aLeft), + rHairlinePrimitive.getBColor())); rtl::Reference<primitive2d::PolygonHairlinePrimitive2D> xPRight( - new primitive2d::PolygonHairlinePrimitive2D(aRight, rHairlinePrimitive.getBColor())); + new primitive2d::PolygonHairlinePrimitive2D(std::move(aRight), + rHairlinePrimitive.getBColor())); processBasePrimitive2D(*xPLeft); processBasePrimitive2D(*xPRight); @@ -1550,10 +1609,12 @@ void VclMetafileProcessor2D::processPolygonStrokePrimitive2D( basegfx::B2DPolygon aLeft, aRight; splitLinePolygon(rBasePolygon, aLeft, aRight); rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xPLeft( - new primitive2d::PolygonStrokePrimitive2D(aLeft, rStrokePrimitive.getLineAttribute(), + new primitive2d::PolygonStrokePrimitive2D(std::move(aLeft), + rStrokePrimitive.getLineAttribute(), rStrokePrimitive.getStrokeAttribute())); rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xPRight( - new primitive2d::PolygonStrokePrimitive2D(aRight, rStrokePrimitive.getLineAttribute(), + new primitive2d::PolygonStrokePrimitive2D(std::move(aRight), + rStrokePrimitive.getLineAttribute(), rStrokePrimitive.getStrokeAttribute())); processBasePrimitive2D(*xPLeft); @@ -1572,7 +1633,7 @@ void VclMetafileProcessor2D::processPolygonStrokePrimitive2D( const attribute::LineAttribute& rLine = rStrokePrimitive.getLineAttribute(); // create MetaPolyLineActions, but without LineStyle::Dash - if (basegfx::fTools::more(rLine.getWidth(), 0.0)) + if (rLine.getWidth() > 0.0) { const attribute::StrokeAttribute& rStroke = rStrokePrimitive.getStrokeAttribute(); @@ -1583,7 +1644,7 @@ void VclMetafileProcessor2D::processPolygonStrokePrimitive2D( // use the transformed line width LineInfo aLineInfo(LineStyle::Solid, - basegfx::fround(getTransformedLineWidth(rLine.getWidth()))); + std::round(getTransformedLineWidth(rLine.getWidth()))); aLineInfo.SetLineJoin(rLine.getLineJoin()); aLineInfo.SetLineCap(rLine.getLineCap()); @@ -1753,6 +1814,16 @@ void VclMetafileProcessor2D::processPolyPolygonGraphicPrimitive2D( // need to handle PolyPolygonGraphicPrimitive2D here to support XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END basegfx::B2DPolyPolygon aLocalPolyPolygon(rBitmapCandidate.getB2DPolyPolygon()); + if (!rBitmapCandidate.getDefinitionRange().isEmpty() + && aLocalPolyPolygon.getB2DRange() != rBitmapCandidate.getDefinitionRange()) + { + // The range which defines the bitmap fill is defined and different from the + // range of the defining geometry (e.g. used for FillStyle UseSlideBackground). + // This cannot be done calling vcl, thus use decomposition here directly + process(rBitmapCandidate); + return; + } + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); std::unique_ptr<SvtGraphicFill> pSvtGraphicFill; @@ -1903,7 +1974,7 @@ void VclMetafileProcessor2D::processPolyPolygonHatchPrimitive2D( aToolsPolyPolygon, Hatch(aHatchStyle, Color(maBColorModifierStack.getModifiedColor(rFillHatchAttribute.getColor())), - basegfx::fround(rFillHatchAttribute.getDistance()), + basegfx::fround<tools::Long>(rFillHatchAttribute.getDistance()), Degree10(basegfx::fround(basegfx::rad2deg<10>(rFillHatchAttribute.getAngle()))))); impEndSvtGraphicFill(pSvtGraphicFill.get()); @@ -1912,42 +1983,126 @@ void VclMetafileProcessor2D::processPolyPolygonHatchPrimitive2D( void VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D( const primitive2d::PolyPolygonGradientPrimitive2D& rGradientCandidate) { - basegfx::B2DVector aScale, aTranslate; - double fRotate, fShearX; + bool useDecompose(false); - maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX); + if (!useDecompose) + { + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + + maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX); + + // detect if transformation is rotated, sheared or mirrored in X and/or Y + if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX) + || aScale.getX() < 0.0 || aScale.getY() < 0.0) + { + // #i121185# When rotation or shear is used, a VCL Gradient cannot be used directly. + // This is because VCL Gradient mechanism does *not* support to rotate the gradient + // with objects and this case is not expressible in a Metafile (and cannot be added + // since the FileFormats used, e.g. *.wmf, do not support it either). + // Such cases happen when a graphic object uses a Metafile as graphic information or + // a fill style definition uses a Metafile. In this cases the graphic content is + // rotated with the graphic or filled object; this is not supported by the target + // format of this conversion renderer - Metafiles. + // To solve this, not a Gradient is written, but the decomposition of this object + // is written to the Metafile. This is the PolyPolygons building the gradient fill. + // These will need more space and time, but the result will be as if the Gradient + // was rotated with the object. + // This mechanism is used by all exporters still not using Primitives (e.g. Print, + // Slideshow, Export rto PDF, export to Picture, ...) but relying on Metafile + // transfers. One more reason to *change* these to primitives. + // BTW: One more example how useful the principles of primitives are; the decomposition + // is by definition a simpler, maybe more expensive representation of the same content. + useDecompose = true; + } + } - if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX)) + // tdf#150551 for PDF export, use the decomposition for better gradient visualization + if (!useDecompose && nullptr != mpPDFExtOutDevData) { - // #i121185# When rotation or shear is used, a VCL Gradient cannot be used directly. - // This is because VCL Gradient mechanism does *not* support to rotate the gradient - // with objects and this case is not expressible in a Metafile (and cannot be added - // since the FileFormats used, e.g. *.wmf, do not support it either). - // Such cases happen when a graphic object uses a Metafile as graphic information or - // a fill style definition uses a Metafile. In this cases the graphic content is - // rotated with the graphic or filled object; this is not supported by the target - // format of this conversion renderer - Metafiles. - // To solve this, not a Gradient is written, but the decomposition of this object - // is written to the Metafile. This is the PolyPolygons building the gradient fill. - // These will need more space and time, but the result will be as if the Gradient - // was rotated with the object. - // This mechanism is used by all exporters still not using Primitives (e.g. Print, - // Slideshow, Export rto PDF, export to Picture, ...) but relying on Metafile - // transfers. One more reason to *change* these to primitives. - // BTW: One more example how useful the principles of primitives are; the decomposition - // is by definition a simpler, maybe more expensive representation of the same content. - process(rGradientCandidate); - return; + useDecompose = true; } basegfx::B2DPolyPolygon aLocalPolyPolygon(rGradientCandidate.getB2DPolyPolygon()); - if (aLocalPolyPolygon.getB2DRange() != rGradientCandidate.getDefinitionRange()) + if (!useDecompose && aLocalPolyPolygon.getB2DRange() != rGradientCandidate.getDefinitionRange()) { // the range which defines the gradient is different from the range of the // geometry (used for writer frames). This cannot be done calling vcl, thus use // decomposition here + useDecompose = true; + } + + const attribute::FillGradientAttribute& rFillGradient(rGradientCandidate.getFillGradient()); + + if (!useDecompose && rFillGradient.cannotBeHandledByVCL()) + { + // MCGR: if we have ColorStops, do not try to fallback to old VCL-Gradient, + // that will *not* be capable of representing this properly. Use the + // correct decomposition instead + useDecompose = true; + } + + if (useDecompose) + { + GDIMetaFile* pMetaFile(mpOutputDevice->GetConnectMetaFile()); + + // tdf#155479 only add 'BGRAD_SEQ_BEGIN' if SVG export + if (nullptr != pMetaFile && pMetaFile->getSVG()) + { + // write the color stops to a memory stream + SvMemoryStream aMemStm; + VersionCompatWrite aCompat(aMemStm, 1); + + const basegfx::BColorStops& rColorStops(rFillGradient.getColorStops()); + sal_uInt16 nTmp(sal::static_int_cast<sal_uInt16>(rColorStops.size())); + aMemStm.WriteUInt16(nTmp); + + for (auto const& rCand : rColorStops) + { + aMemStm.WriteDouble(rCand.getStopOffset()); + const basegfx::BColor& rColor(rCand.getStopColor()); + aMemStm.WriteDouble(rColor.getRed()); + aMemStm.WriteDouble(rColor.getGreen()); + aMemStm.WriteDouble(rColor.getBlue()); + } + + // Add a new MetaCommentAction section of type 'BGRAD_SEQ_BEGIN/BGRAD_SEQ_END' + // that is capable of holding the new color step information, plus the + // already used MetaActionType::GRADIENTEX. + // With that combination only places that know about that new BGRAD_SEQ_* will + // use it while all others will work on the created decomposition of the + // gradient for compatibility - which are single-color filled polygons + pMetaFile->AddAction(new MetaCommentAction( + "BGRAD_SEQ_BEGIN"_ostr, 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), + aMemStm.TellEnd())); + + // create MetaActionType::GRADIENTEX + // NOTE: with the new BGRAD_SEQ_* we could use basegfx::B2DPolygon and + // basegfx::BGradient here directly, but may have to add streaming OPs + // for these, so for now just go with what we use all the time. The real + // work for improvement should not go to this 'compromise' but to a real + // re-work of the SVG export (or/and others) to no longer work on metafiles + // but on UNO API or primitives (whatever fits best to the specific export) + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); + Gradient aVCLGradient; + impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rFillGradient, false); + aLocalPolyPolygon.transform(maCurrentTransformation); + const tools::PolyPolygon aToolsPolyPolygon( + getFillPolyPolygon(basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon))); + mpOutputDevice->DrawGradient(aToolsPolyPolygon, aVCLGradient); + } + + // use decompose to draw, will create PolyPolygon ColorFill actions process(rGradientCandidate); + + // tdf#155479 only add 'BGRAD_SEQ_END' if SVG export + if (nullptr != pMetaFile && pMetaFile->getSVG()) + { + // close the BGRAD_SEQ_* actions range + pMetaFile->AddAction(new MetaCommentAction("BGRAD_SEQ_END"_ostr)); + } + return; } @@ -1959,8 +2114,7 @@ void VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D( // it is safest to use the VCL OutputDevice::DrawGradient method which creates those. // re-create a VCL-gradient from FillGradientPrimitive2D and the needed tools PolyPolygon Gradient aVCLGradient; - impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rGradientCandidate.getFillGradient(), - false); + impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rFillGradient, false); aLocalPolyPolygon.transform(maCurrentTransformation); // #i82145# ATM VCL printing of gradients using curved shapes does not work, @@ -1981,16 +2135,16 @@ void VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D( switch (aVCLGradient.GetStyle()) { - default: // GradientStyle::Linear: - case GradientStyle::Axial: + default: // css::awt::GradientStyle_LINEAR: + case css::awt::GradientStyle_AXIAL: eGrad = SvtGraphicFill::GradientType::Linear; break; - case GradientStyle::Radial: - case GradientStyle::Elliptical: + case css::awt::GradientStyle_RADIAL: + case css::awt::GradientStyle_ELLIPTICAL: eGrad = SvtGraphicFill::GradientType::Radial; break; - case GradientStyle::Square: - case GradientStyle::Rect: + case css::awt::GradientStyle_SQUARE: + case css::awt::GradientStyle_RECT: eGrad = SvtGraphicFill::GradientType::Rectangular; break; } @@ -2115,7 +2269,7 @@ void VclMetafileProcessor2D::processUnifiedTransparencePrimitive2D( if (!bForceToMetafile && 1 == rContent.size()) { - const primitive2d::Primitive2DReference xReference(rContent[0]); + const primitive2d::Primitive2DReference xReference(rContent.front()); pPoPoColor = dynamic_cast<const primitive2d::PolyPolygonColorPrimitive2D*>( xReference.get()); } @@ -2159,6 +2313,11 @@ void VclMetafileProcessor2D::processUnifiedTransparencePrimitive2D( // various content, create content-metafile GDIMetaFile aContentMetafile; + + // tdf#155479 always forward propagate SVG flag for sub-content, + // it may contain cannotBeHandledByVCL gradients or transparencyGradients + aContentMetafile.setSVG(mpOutputDevice->GetConnectMetaFile()->getSVG()); + const tools::Rectangle aPrimitiveRectangle( impDumpToMetaFile(rContent, aContentMetafile)); @@ -2172,7 +2331,7 @@ void VclMetafileProcessor2D::processUnifiedTransparencePrimitive2D( basegfx::fround(rUniTransparenceCandidate.getTransparence() * 255.0))); const Color aTransColor(nTransPercentVcl, nTransPercentVcl, nTransPercentVcl); - aVCLGradient.SetStyle(GradientStyle::Linear); + aVCLGradient.SetStyle(css::awt::GradientStyle_LINEAR); aVCLGradient.SetStartColor(aTransColor); aVCLGradient.SetEndColor(aTransColor); aVCLGradient.SetAngle(0_deg10); @@ -2202,29 +2361,82 @@ void VclMetafileProcessor2D::processTransparencePrimitive2D( // FillGradientPrimitive2D and reconstruct the gradient. // If that detection goes wrong, I have to create a transparence-blended bitmap. Eventually // do that in stripes, else RenderTransparencePrimitive2D may just be used - const primitive2d::Primitive2DContainer& rContent = rTransparenceCandidate.getChildren(); - const primitive2d::Primitive2DContainer& rTransparence - = rTransparenceCandidate.getTransparence(); + const primitive2d::Primitive2DContainer& rContent(rTransparenceCandidate.getChildren()); + const primitive2d::Primitive2DContainer& rTransparence( + rTransparenceCandidate.getTransparence()); if (rContent.empty() || rTransparence.empty()) return; // try to identify a single FillGradientPrimitive2D in the - // transparence part of the primitive - const primitive2d::FillGradientPrimitive2D* pFiGradient = nullptr; + // transparence part of the primitive. The hope is to handle + // the more specific case in a better way than the general + // TransparencePrimitive2D which has strongly separated + // definitions for transparency and content, both completely + // free definable by primitives + const primitive2d::FillGradientPrimitive2D* pFiGradient(nullptr); static bool bForceToBigTransparentVDev(false); // loplugin:constvars:ignore + // check for single FillGradientPrimitive2D if (!bForceToBigTransparentVDev && 1 == rTransparence.size()) { - const primitive2d::Primitive2DReference xReference(rTransparence[0]); - pFiGradient = dynamic_cast<const primitive2d::FillGradientPrimitive2D*>(xReference.get()); + pFiGradient = dynamic_cast<const primitive2d::FillGradientPrimitive2D*>( + rTransparence.front().get()); + + // check also for correct ID to exclude derived implementations + if (pFiGradient + && PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D != pFiGradient->getPrimitive2DID()) + pFiGradient = nullptr; } - // Check also for correct ID to exclude derived implementations - if (pFiGradient && PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D == pFiGradient->getPrimitive2DID()) + // tdf#155479 preps for holding extra-MCGR infos + bool bSVGTransparencyColorStops(false); + basegfx::BColorStops aSVGTransparencyColorStops; + + // MCGR: tdf#155437 If we have identified a transparency gradient, + // check if VCL is able to handle it at all + if (nullptr != pFiGradient && pFiGradient->getFillGradient().cannotBeHandledByVCL()) { - // various content, create content-metafile + // If not, reset the pointer and do not make use of this special case. + // Adding a gradient in incomplete state that can not be handled by vcl + // makes no sense and will knowingly lead to errors, especially with + // MCGR extended possibilities. I checked what happens with the + // MetaFloatTransparentAction added by OutputDevice::DrawTransparent, but + // in most cases it gets converted to bitmap or even ignored, see e.g. + // - vcl/source/gdi/pdfwriter_impl2.cxx for PDF export + // - vcl/source/filter/wmf/wmfwr.cxx -> does ignore TransparenceGradient completely + // - vcl/source/filter/wmf/emfwr.cxx -> same + // - vcl/source/filter/eps/eps.cxx -> same + // NOTE: Theoretically it would be possible to make the new extended Gradient data + // available in metafiles, with the known limitations (not backward comp, all + // places using it would need adaption, ...), but combined with knowing that nearly + // all usages ignore or render it locally anyways makes that a non-option. + + // tdf#155479 Yepp, as already mentioned above we need to add + // some MCGR infos in case of SVG export, prepare that here + if (mpOutputDevice->GetConnectMetaFile()->getSVG()) + { + // for SVG, do not use decompose & prep extra data + bSVGTransparencyColorStops = true; + aSVGTransparencyColorStops = pFiGradient->getFillGradient().getColorStops(); + } + else + { + // use decomposition + pFiGradient = nullptr; + } + } + + if (nullptr != pFiGradient) + { + // this combination of Gradient can be expressed/handled by + // vcl/metafile, so add it directly. various content, create content-metafile GDIMetaFile aContentMetafile; + + // tdf#155479 always forward propagate SVG flag for sub-content, + // it may contain cannotBeHandledByVCL gradients or transparencyGradients + aContentMetafile.setSVG(mpOutputDevice->GetConnectMetaFile()->getSVG()); + const tools::Rectangle aPrimitiveRectangle(impDumpToMetaFile(rContent, aContentMetafile)); // re-create a VCL-gradient from FillGradientPrimitive2D @@ -2232,126 +2444,192 @@ void VclMetafileProcessor2D::processTransparencePrimitive2D( impConvertFillGradientAttributeToVCLGradient(aVCLGradient, pFiGradient->getFillGradient(), true); - // render it to VCL - mpOutputDevice->DrawTransparent(aContentMetafile, aPrimitiveRectangle.TopLeft(), - aPrimitiveRectangle.GetSize(), aVCLGradient); - } - else - { - // sub-transparence group. Draw to VDev first. - // this may get refined to tiling when resolution is too big here - - // need to avoid switching off MapMode stuff here; maybe need another - // tooling class, cannot just do the same as with the pixel renderer. - // Need to experiment... - - // Okay, basic implementation finished and tested. The DPI stuff was hard - // and not easy to find out that it's needed. - // Since this will not yet happen normally (as long as no one constructs - // transparence primitives with non-trivial transparence content) i will for now not - // refine to tiling here. - - basegfx::B2DRange aViewRange(rContent.getB2DRange(getViewInformation2D())); - aViewRange.transform(maCurrentTransformation); - const tools::Rectangle aRectLogic(static_cast<sal_Int32>(floor(aViewRange.getMinX())), - static_cast<sal_Int32>(floor(aViewRange.getMinY())), - static_cast<sal_Int32>(ceil(aViewRange.getMaxX())), - static_cast<sal_Int32>(ceil(aViewRange.getMaxY()))); - const tools::Rectangle aRectPixel(mpOutputDevice->LogicToPixel(aRectLogic)); - Size aSizePixel(aRectPixel.GetSize()); - ScopedVclPtrInstance<VirtualDevice> aBufferDevice; - const sal_uInt32 nMaxSquarePixels(500000); - const sal_uInt32 nViewVisibleArea(aSizePixel.getWidth() * aSizePixel.getHeight()); - double fReduceFactor(1.0); - - if (nViewVisibleArea > nMaxSquarePixels) - { - // reduce render size - fReduceFactor = sqrt(double(nMaxSquarePixels) / static_cast<double>(nViewVisibleArea)); - aSizePixel = Size( - basegfx::fround(static_cast<double>(aSizePixel.getWidth()) * fReduceFactor), - basegfx::fround(static_cast<double>(aSizePixel.getHeight()) * fReduceFactor)); - } - - if (aBufferDevice->SetOutputSizePixel(aSizePixel)) - { - // create and set MapModes for target devices - MapMode aNewMapMode(mpOutputDevice->GetMapMode()); - aNewMapMode.SetOrigin(Point(-aRectLogic.Left(), -aRectLogic.Top())); - aBufferDevice->SetMapMode(aNewMapMode); - - // prepare view transformation for target renderers - // ATTENTION! Need to apply another scaling because of the potential DPI differences - // between Printer and VDev (mpOutputDevice and aBufferDevice here). - // To get the DPI, LogicToPixel from (1,1) from MapUnit::MapInch needs to be used. - basegfx::B2DHomMatrix aViewTransform(aBufferDevice->GetViewTransformation()); - const Size aDPIOld(mpOutputDevice->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch))); - const Size aDPINew(aBufferDevice->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch))); - const double fDPIXChange(static_cast<double>(aDPIOld.getWidth()) - / static_cast<double>(aDPINew.getWidth())); - const double fDPIYChange(static_cast<double>(aDPIOld.getHeight()) - / static_cast<double>(aDPINew.getHeight())); - - if (!basegfx::fTools::equal(fDPIXChange, 1.0) - || !basegfx::fTools::equal(fDPIYChange, 1.0)) - { - aViewTransform.scale(fDPIXChange, fDPIYChange); - } - - // also take scaling from Size reduction into account - if (!basegfx::fTools::equal(fReduceFactor, 1.0)) - { - aViewTransform.scale(fReduceFactor, fReduceFactor); - } - - // create view information and pixel renderer. Reuse known ViewInformation - // except new transformation and range - const geometry::ViewInformation2D aViewInfo( - getViewInformation2D().getObjectTransformation(), aViewTransform, aViewRange, - getViewInformation2D().getVisualizedPage(), getViewInformation2D().getViewTime()); - - VclPixelProcessor2D aBufferProcessor(aViewInfo, *aBufferDevice); - - // draw content using pixel renderer - const Point aEmptyPoint; - aBufferProcessor.process(rContent); - const Bitmap aBmContent(aBufferDevice->GetBitmap(aEmptyPoint, aSizePixel)); - - // draw transparence using pixel renderer - aBufferDevice->Erase(); - aBufferProcessor.process(rTransparence); - const AlphaMask aBmAlpha(aBufferDevice->GetBitmap(aEmptyPoint, aSizePixel)); + if (bSVGTransparencyColorStops) + { + // tdf#155479 create action directly & add extra + // MCGR infos to the metafile, do that by adding - ONLY in + // case of SVG export - to the MetaFileAction. For that + // reason, do what OutputDevice::DrawTransparent will do, + // but locally. + // NOTE: That would be good for this whole + // VclMetafileProcessor2D anyways to allow to get it + // completely independent from OutputDevice in the long run + GDIMetaFile* pMetaFile(mpOutputDevice->GetConnectMetaFile()); + rtl::Reference<::MetaFloatTransparentAction> pAction( + new MetaFloatTransparentAction(aContentMetafile, aPrimitiveRectangle.TopLeft(), + aPrimitiveRectangle.GetSize(), aVCLGradient)); - // paint - mpOutputDevice->DrawBitmapEx(aRectLogic.TopLeft(), aRectLogic.GetSize(), - BitmapEx(aBmContent, aBmAlpha)); + pAction->addSVGTransparencyColorStops(aSVGTransparencyColorStops); + pMetaFile->AddAction(pAction); } + else + { + // render it to VCL (creates MetaFloatTransparentAction) + mpOutputDevice->DrawTransparent(aContentMetafile, aPrimitiveRectangle.TopLeft(), + aPrimitiveRectangle.GetSize(), aVCLGradient); + } + return; } + + // Here we need to create a correct replacement visualization for the + // TransparencePrimitive2D for the target metafile. + // I replaced the n'th iteration to convert-to-bitmap which was + // used here by using the existing tooling. The orig here was also producing + // transparency errors with test-file from tdf#155437 on the right part of the + // image. + // Just rely on existing tooling doing the right thing in one place, so also + // corrections/optimizations can be in one single place + + // Start by getting logic range of content, transform object-to-world, then world-to-view + // to get to discrete values ('pixels'). Matrix multiplication is right-to-left (and not + // commutative) + basegfx::B2DRange aLogicRange(rTransparenceCandidate.getB2DRange(getViewInformation2D())); + aLogicRange.transform(mpOutputDevice->GetViewTransformation() * maCurrentTransformation); + + // expand in discrete coordinates to next-bigger 'pixel' boundaries and remember + // created discrete range + aLogicRange.expand( + basegfx::B2DPoint(floor(aLogicRange.getMinX()), floor(aLogicRange.getMinY()))); + aLogicRange.expand(basegfx::B2DPoint(ceil(aLogicRange.getMaxX()), ceil(aLogicRange.getMaxY()))); + const basegfx::B2DRange aDiscreteRange(aLogicRange); + + // transform back from discrete to world coordinates: this creates the + // pixel-boundaries extended logic range we need to cover all content + // reliably + aLogicRange.transform(mpOutputDevice->GetInverseViewTransformation()); + + // create transform embedding for renderer. Goal is to translate what we + // want to paint to top/left 0/0 and the calculated discrete size + basegfx::B2DHomMatrix aEmbedding(basegfx::utils::createTranslateB2DHomMatrix( + -aLogicRange.getMinX(), -aLogicRange.getMinY())); + const double fLogicWidth( + basegfx::fTools::equalZero(aLogicRange.getWidth()) ? 1.0 : aLogicRange.getWidth()); + const double fLogicHeight( + basegfx::fTools::equalZero(aLogicRange.getHeight()) ? 1.0 : aLogicRange.getHeight()); + aEmbedding.scale(aDiscreteRange.getWidth() / fLogicWidth, + aDiscreteRange.getHeight() / fLogicHeight); + + // use the whole TransparencePrimitive2D as input (no need to create a new + // one with the sub-contents, these are ref-counted) and add to embedding + // primitive2d::TransparencePrimitive2D& rTrCand(); + primitive2d::Primitive2DContainer xEmbedSeq{ &const_cast<primitive2d::TransparencePrimitive2D&>( + rTransparenceCandidate) }; + + // tdf#158743 when embedding, do not forget to 1st apply the evtl. used + // CurrentTransformation (right-to-left, apply that 1st) + xEmbedSeq = primitive2d::Primitive2DContainer{ new primitive2d::TransformPrimitive2D( + aEmbedding * maCurrentTransformation, std::move(xEmbedSeq)) }; + + // use empty ViewInformation & a useful MaximumQuadraticPixels + // limitation to paint the content + const auto aViewInformation2D(geometry::createViewInformation2D({})); + const sal_uInt32 nMaximumQuadraticPixels(500000); + const BitmapEx aBitmapEx(convertToBitmapEx( + std::move(xEmbedSeq), aViewInformation2D, basegfx::fround(aDiscreteRange.getWidth()), + basegfx::fround(aDiscreteRange.getHeight()), nMaximumQuadraticPixels)); + + // add to target metafile (will create MetaFloatTransparentAction) + mpOutputDevice->DrawBitmapEx(Point(basegfx::fround<tools::Long>(aLogicRange.getMinX()), + basegfx::fround<tools::Long>(aLogicRange.getMinY())), + Size(basegfx::fround<tools::Long>(aLogicRange.getWidth()), + basegfx::fround<tools::Long>(aLogicRange.getHeight())), + aBitmapEx); } void VclMetafileProcessor2D::processStructureTagPrimitive2D( const primitive2d::StructureTagPrimitive2D& rStructureTagCandidate) { + ::comphelper::ValueRestorationGuard const g(mpCurrentStructureTag, &rStructureTagCandidate); + // structured tag primitive const vcl::PDFWriter::StructElement& rTagElement(rStructureTagCandidate.getStructureElement()); bool bTagUsed((vcl::PDFWriter::NonStructElement != rTagElement)); + ::std::optional<sal_Int32> oAnchorParent; + + if (!rStructureTagCandidate.isTaggedSdrObject()) + { + bTagUsed = false; + } if (mpPDFExtOutDevData && bTagUsed) { // foreground object: tag as regular structure element if (!rStructureTagCandidate.isBackground()) { - mpPDFExtOutDevData->BeginStructureElement(rTagElement); + if (rStructureTagCandidate.GetAnchorStructureElementKey() != nullptr) + { + sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement( + rStructureTagCandidate.GetAnchorStructureElementKey()); + oAnchorParent.emplace(mpPDFExtOutDevData->GetCurrentStructureElement()); + mpPDFExtOutDevData->SetCurrentStructureElement(id); + } + mpPDFExtOutDevData->WrapBeginStructureElement(rTagElement); + switch (rTagElement) + { + case vcl::PDFWriter::H1: + case vcl::PDFWriter::H2: + case vcl::PDFWriter::H3: + case vcl::PDFWriter::H4: + case vcl::PDFWriter::H5: + case vcl::PDFWriter::H6: + case vcl::PDFWriter::Paragraph: + case vcl::PDFWriter::Heading: + case vcl::PDFWriter::Caption: + case vcl::PDFWriter::BlockQuote: + case vcl::PDFWriter::Table: + case vcl::PDFWriter::TableRow: + case vcl::PDFWriter::Formula: + case vcl::PDFWriter::Figure: + case vcl::PDFWriter::Annot: + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Placement, + vcl::PDFWriter::Block); + break; + case vcl::PDFWriter::TableData: + case vcl::PDFWriter::TableHeader: + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Placement, + vcl::PDFWriter::Inline); + break; + default: + break; + } + switch (rTagElement) + { + case vcl::PDFWriter::Table: + case vcl::PDFWriter::Formula: + case vcl::PDFWriter::Figure: + case vcl::PDFWriter::Annot: + { + auto const range(rStructureTagCandidate.getB2DRange(getViewInformation2D())); + tools::Rectangle const aLogicRect( + basegfx::fround<tools::Long>(range.getMinX()), + basegfx::fround<tools::Long>(range.getMinY()), + basegfx::fround<tools::Long>(range.getMaxX()), + basegfx::fround<tools::Long>(range.getMaxY())); + mpPDFExtOutDevData->SetStructureBoundingBox(aLogicRect); + break; + } + default: + break; + } + if (rTagElement == vcl::PDFWriter::Annot) + { + mpPDFExtOutDevData->SetStructureAnnotIds(rStructureTagCandidate.GetAnnotIds()); + } + if (rTagElement == vcl::PDFWriter::TableHeader) + { + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Scope, + vcl::PDFWriter::Column); + } } // background object else { // background image: tag as artifact if (rStructureTagCandidate.isImage()) - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::NonStructElement); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::NonStructElement); // any other background object: do not tag else - bTagUsed = false; + assert(false); } } @@ -2362,104 +2640,11 @@ void VclMetafileProcessor2D::processStructureTagPrimitive2D( { // write end tag mpPDFExtOutDevData->EndStructureElement(); - } -} - -VclPtr<VirtualDevice> -VclMetafileProcessor2D::CreateBufferDevice(const basegfx::B2DRange& rCandidateRange, - geometry::ViewInformation2D& rViewInfo, - tools::Rectangle& rRectLogic, Size& rSizePixel) const -{ - constexpr double fMaxSquarePixels = 500000; - basegfx::B2DRange aViewRange(rCandidateRange); - aViewRange.transform(maCurrentTransformation); - rRectLogic = tools::Rectangle(static_cast<tools::Long>(std::floor(aViewRange.getMinX())), - static_cast<tools::Long>(std::floor(aViewRange.getMinY())), - static_cast<tools::Long>(std::ceil(aViewRange.getMaxX())), - static_cast<tools::Long>(std::ceil(aViewRange.getMaxY()))); - const tools::Rectangle aRectPixel(mpOutputDevice->LogicToPixel(rRectLogic)); - rSizePixel = aRectPixel.GetSize(); - const double fViewVisibleArea(rSizePixel.getWidth() * rSizePixel.getHeight()); - double fReduceFactor(1.0); - - if (fViewVisibleArea > fMaxSquarePixels) - { - // reduce render size - fReduceFactor = sqrt(fMaxSquarePixels / fViewVisibleArea); - rSizePixel = Size(basegfx::fround(rSizePixel.getWidth() * fReduceFactor), - basegfx::fround(rSizePixel.getHeight() * fReduceFactor)); - } - - VclPtrInstance<VirtualDevice> pBufferDevice(DeviceFormat::DEFAULT, DeviceFormat::DEFAULT); - if (pBufferDevice->SetOutputSizePixel(rSizePixel)) - { - // create and set MapModes for target devices - MapMode aNewMapMode(mpOutputDevice->GetMapMode()); - aNewMapMode.SetOrigin(Point(-rRectLogic.Left(), -rRectLogic.Top())); - pBufferDevice->SetMapMode(aNewMapMode); - - // prepare view transformation for target renderers - // ATTENTION! Need to apply another scaling because of the potential DPI differences - // between Printer and VDev (mpOutputDevice and pBufferDevice here). - // To get the DPI, LogicToPixel from (1,1) from MapUnit::MapInch needs to be used. - basegfx::B2DHomMatrix aViewTransform(pBufferDevice->GetViewTransformation()); - const Size aDPIOld(mpOutputDevice->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch))); - const Size aDPINew(pBufferDevice->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch))); - const double fDPIXChange(static_cast<double>(aDPIOld.getWidth()) - / static_cast<double>(aDPINew.getWidth())); - const double fDPIYChange(static_cast<double>(aDPIOld.getHeight()) - / static_cast<double>(aDPINew.getHeight())); - - if (!basegfx::fTools::equal(fDPIXChange, 1.0) || !basegfx::fTools::equal(fDPIYChange, 1.0)) - { - aViewTransform.scale(fDPIXChange, fDPIYChange); - } - - // also take scaling from Size reduction into account - if (!basegfx::fTools::equal(fReduceFactor, 1.0)) + if (oAnchorParent) { - aViewTransform.scale(fReduceFactor, fReduceFactor); + mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent); } - - // create view information and pixel renderer. Reuse known ViewInformation - // except new transformation and range - rViewInfo = geometry::ViewInformation2D( - getViewInformation2D().getObjectTransformation(), aViewTransform, aViewRange, - getViewInformation2D().getVisualizedPage(), getViewInformation2D().getViewTime()); - } - else - pBufferDevice.disposeAndClear(); - -#if HAVE_P1155R3 - return pBufferDevice; -#else - return std::move(pBufferDevice); -#endif -} - -void VclMetafileProcessor2D::processPrimitive2DOnPixelProcessor( - const primitive2d::BasePrimitive2D& rCandidate) -{ - basegfx::B2DRange aViewRange(rCandidate.getB2DRange(getViewInformation2D())); - geometry::ViewInformation2D aViewInfo; - tools::Rectangle aRectLogic; - Size aSizePixel; - auto pBufferDevice(CreateBufferDevice(aViewRange, aViewInfo, aRectLogic, aSizePixel)); - if (pBufferDevice) - { - VclPixelProcessor2D aBufferProcessor(aViewInfo, *pBufferDevice, maBColorModifierStack); - - // draw content using pixel renderer - primitive2d::Primitive2DReference aRef( - &const_cast<primitive2d::BasePrimitive2D&>(rCandidate)); - aBufferProcessor.process({ aRef }); - const BitmapEx aBmContent(pBufferDevice->GetBitmapEx(Point(), aSizePixel)); - mpOutputDevice->DrawBitmapEx(aRectLogic.TopLeft(), aRectLogic.GetSize(), aBmContent); - - // aBufferProcessor dtor pops state off pBufferDevice pushed on by its ctor, let - // pBufferDevice live past aBufferProcessor scope to avoid warnings } - pBufferDevice.disposeAndClear(); } } // end of namespace |