diff options
Diffstat (limited to '')
-rw-r--r-- | svx/source/svdraw/svdpdf.cxx | 1151 |
1 files changed, 1151 insertions, 0 deletions
diff --git a/svx/source/svdraw/svdpdf.cxx b/svx/source/svdraw/svdpdf.cxx new file mode 100644 index 000000000..188651be3 --- /dev/null +++ b/svx/source/svdraw/svdpdf.cxx @@ -0,0 +1,1151 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <svdpdf.hxx> + +#include <config_features.h> + +#if HAVE_FEATURE_PDFIUM +#include <fpdfview.h> +#include <fpdf_edit.h> +#include <fpdf_text.h> + +#include <vcl/graph.hxx> +#include <vcl/vectorgraphicdata.hxx> + +#include <math.h> +#include <editeng/eeitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/shdditem.hxx> +#include <svx/xlnclit.hxx> +#include <svx/xlncapit.hxx> +#include <svx/xlnwtit.hxx> +#include <svx/xflclit.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <vcl/metric.hxx> +#include <editeng/charscaleitem.hxx> +#include <svx/sdtditm.hxx> +#include <svx/sdtagitm.hxx> +#include <svx/sdtfsitm.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdorect.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdetc.hxx> +#include <svl/itemset.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <tools/helpers.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <svx/xlinjoit.hxx> +#include <svx/xlndsit.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <svx/xbtmpit.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflbmtit.hxx> +#include <svx/xflbstit.hxx> +#include <svx/xlineit0.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <svx/svditer.hxx> +#include <svx/svdogrp.hxx> +#include <vcl/dibtools.hxx> +#include <sal/log.hxx> + +namespace +{ +double sqrt2(double a, double b) { return sqrt(a * a + b * b); } + +struct FPDFBitmapDeleter +{ + void operator()(FPDF_BITMAP bitmap) { FPDFBitmap_Destroy(bitmap); } +}; +} + +using namespace com::sun::star; + +ImpSdrPdfImport::ImpSdrPdfImport(SdrModel& rModel, SdrLayerID nLay, const tools::Rectangle& rRect, + Graphic const& rGraphic) + : maTmpList() + , mpVD(VclPtr<VirtualDevice>::Create()) + , maScaleRect(rRect) + , mnMapScalingOfs(0) + , mpModel(&rModel) + , mnLayer(nLay) + , maOldLineColor() + , mnLineWidth(0) + , maDash(css::drawing::DashStyle_RECT, 0, 0, 0, 0, 0) + , mbMov(false) + , mbSize(false) + , maOfs(0, 0) + , mfScaleX(1.0) + , mfScaleY(1.0) + , maScaleX(1.0) + , maScaleY(1.0) + , mbFntDirty(true) + , mbLastObjWasPolyWithoutLine(false) + , mbNoLine(false) + , mbNoFill(false) + , maClip() + , mpPdfDocument(nullptr) + , mnPageCount(0) + , mdPageWidthPts(0) + , mdPageHeightPts(0) + , mpPDFium(vcl::pdf::PDFiumLibrary::get()) +{ + mpVD->EnableOutput(false); + mpVD->SetLineColor(); + mpVD->SetFillColor(); + maOldLineColor.SetRed(mpVD->GetLineColor().GetRed() + 1); + mpLineAttr = std::make_unique<SfxItemSet>(rModel.GetItemPool(), + svl::Items<XATTR_LINE_FIRST, XATTR_LINE_LAST>{}); + mpFillAttr = std::make_unique<SfxItemSet>(rModel.GetItemPool(), + svl::Items<XATTR_FILL_FIRST, XATTR_FILL_LAST>{}); + mpTextAttr = std::make_unique<SfxItemSet>(rModel.GetItemPool(), + svl::Items<EE_ITEMS_START, EE_ITEMS_END>{}); + checkClip(); + + // Load the buffer using pdfium. + auto const& rVectorGraphicData = rGraphic.getVectorGraphicData(); + mpPdfDocument = FPDF_LoadMemDocument( + rVectorGraphicData->getVectorGraphicDataArray().getConstArray(), + rVectorGraphicData->getVectorGraphicDataArrayLength(), /*password=*/nullptr); + if (!mpPdfDocument) + { + //TODO: Handle failure to load. + switch (FPDF_GetLastError()) + { + case FPDF_ERR_SUCCESS: + break; + case FPDF_ERR_UNKNOWN: + break; + case FPDF_ERR_FILE: + break; + case FPDF_ERR_FORMAT: + break; + case FPDF_ERR_PASSWORD: + break; + case FPDF_ERR_SECURITY: + break; + case FPDF_ERR_PAGE: + break; + default: + break; + } + + return; + } + + mnPageCount = FPDF_GetPageCount(mpPdfDocument); +} + +ImpSdrPdfImport::~ImpSdrPdfImport() { FPDF_CloseDocument(mpPdfDocument); } + +void ImpSdrPdfImport::DoObjects(SvdProgressInfo* pProgrInfo, sal_uInt32* pActionsToReport, + int nPageIndex) +{ + const int nPageCount = FPDF_GetPageCount(mpPdfDocument); + if (nPageCount > 0 && nPageIndex >= 0 && nPageIndex < nPageCount) + { + // Render next page. + FPDF_PAGE pPdfPage = FPDF_LoadPage(mpPdfDocument, nPageIndex); + if (pPdfPage == nullptr) + return; + + const double dPageWidth = FPDF_GetPageWidth(pPdfPage); + const double dPageHeight = FPDF_GetPageHeight(pPdfPage); + SetupPageScale(dPageWidth, dPageHeight); + + // Load the page text to extract it when we get text elements. + FPDF_TEXTPAGE pTextPage = FPDFText_LoadPage(pPdfPage); + + const int nPageObjectCount = FPDFPage_CountObjects(pPdfPage); + if (pProgrInfo) + pProgrInfo->SetActionCount(nPageObjectCount); + + for (int nPageObjectIndex = 0; nPageObjectIndex < nPageObjectCount; ++nPageObjectIndex) + { + FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage, nPageObjectIndex); + ImportPdfObject(pPageObject, pTextPage, nPageObjectIndex); + if (pProgrInfo && pActionsToReport) + { + (*pActionsToReport)++; + + if (*pActionsToReport >= 16) + { + if (!pProgrInfo->ReportActions(*pActionsToReport)) + break; + + *pActionsToReport = 0; + } + } + } + + FPDFText_ClosePage(pTextPage); + FPDF_ClosePage(pPdfPage); + } +} + +void ImpSdrPdfImport::SetupPageScale(const double dPageWidth, const double dPageHeight) +{ + mfScaleX = mfScaleY = 1.0; + + // Store the page dimensions in Points. + mdPageWidthPts = dPageWidth; + mdPageHeightPts = dPageHeight; + + Size aPageSize(convertPointToMm100(dPageWidth), convertPointToMm100(dPageHeight)); + + if (aPageSize.Width() && aPageSize.Height() && (!maScaleRect.IsEmpty())) + { + maOfs = maScaleRect.TopLeft(); + + if (aPageSize.Width() != (maScaleRect.GetWidth() - 1)) + { + mfScaleX = static_cast<double>(maScaleRect.GetWidth() - 1) + / static_cast<double>(aPageSize.Width()); + } + + if (aPageSize.Height() != (maScaleRect.GetHeight() - 1)) + { + mfScaleY = static_cast<double>(maScaleRect.GetHeight() - 1) + / static_cast<double>(aPageSize.Height()); + } + } + + mbMov = maOfs.X() != 0 || maOfs.Y() != 0; + mbSize = false; + maScaleX = Fraction(1, 1); + maScaleY = Fraction(1, 1); + + if (aPageSize.Width() != (maScaleRect.GetWidth() - 1)) + { + maScaleX = Fraction(maScaleRect.GetWidth() - 1, aPageSize.Width()); + mbSize = true; + } + + if (aPageSize.Height() != (maScaleRect.GetHeight() - 1)) + { + maScaleY = Fraction(maScaleRect.GetHeight() - 1, aPageSize.Height()); + mbSize = true; + } +} + +size_t ImpSdrPdfImport::DoImport(SdrObjList& rOL, size_t nInsPos, int nPageNumber, + SvdProgressInfo* pProgrInfo) +{ + sal_uInt32 nActionsToReport(0); + + // execute + DoObjects(pProgrInfo, &nActionsToReport, nPageNumber); + + if (pProgrInfo) + { + pProgrInfo->ReportActions(nActionsToReport); + nActionsToReport = 0; + } + + // MapMode scaling + MapScaling(); + + // To calculate the progress meter, we use GetActionSize()*3. + // However, maTmpList has a lower entry count limit than GetActionSize(), + // so the actions that were assumed were too much have to be re-added. + // nActionsToReport = (rMtf.GetActionSize() - maTmpList.size()) * 2; + + // announce all currently unannounced rescales + if (pProgrInfo) + { + pProgrInfo->ReportRescales(nActionsToReport); + pProgrInfo->SetInsertCount(maTmpList.size()); + } + + nActionsToReport = 0; + + // insert all objects cached in aTmpList now into rOL from nInsPos + nInsPos = std::min(nInsPos, rOL.GetObjCount()); + + for (SdrObject* pObj : maTmpList) + { + rOL.NbcInsertObject(pObj, nInsPos); + nInsPos++; + + if (pProgrInfo) + { + nActionsToReport++; + + if (nActionsToReport >= 32) // update all 32 actions + { + pProgrInfo->ReportInserts(nActionsToReport); + nActionsToReport = 0; + } + } + } + + // report all remaining inserts for the last time + if (pProgrInfo) + { + pProgrInfo->ReportInserts(nActionsToReport); + } + + return maTmpList.size(); +} + +void ImpSdrPdfImport::SetAttributes(SdrObject* pObj, bool bForceTextAttr) +{ + mbNoLine = false; + mbNoFill = false; + bool bLine(!bForceTextAttr); + bool bFill(!pObj || (pObj->IsClosedObj() && !bForceTextAttr)); + bool bText(bForceTextAttr || (pObj && pObj->GetOutlinerParaObject())); + + if (bLine) + { + if (mnLineWidth) + { + mpLineAttr->Put(XLineWidthItem(mnLineWidth)); + } + else + { + mpLineAttr->Put(XLineWidthItem(0)); + } + + maOldLineColor = mpVD->GetLineColor(); + + if (mpVD->IsLineColor()) + { + mpLineAttr->Put(XLineStyleItem(drawing::LineStyle_SOLID)); //TODO support dashed lines. + mpLineAttr->Put(XLineColorItem(OUString(), mpVD->GetLineColor())); + } + else + { + mpLineAttr->Put(XLineStyleItem(drawing::LineStyle_NONE)); + } + + mpLineAttr->Put(XLineJointItem(css::drawing::LineJoint_NONE)); + + // Add LineCap support + mpLineAttr->Put(XLineCapItem(gaLineCap)); + + if (((maDash.GetDots() && maDash.GetDotLen()) + || (maDash.GetDashes() && maDash.GetDashLen())) + && maDash.GetDistance()) + { + mpLineAttr->Put(XLineDashItem(OUString(), maDash)); + } + else + { + mpLineAttr->Put(XLineDashItem(OUString(), XDash(css::drawing::DashStyle_RECT))); + } + } + else + { + mbNoLine = true; + } + + if (bFill) + { + if (mpVD->IsFillColor()) + { + mpFillAttr->Put(XFillStyleItem(drawing::FillStyle_SOLID)); + mpFillAttr->Put(XFillColorItem(OUString(), mpVD->GetFillColor())); + } + else + { + mpFillAttr->Put(XFillStyleItem(drawing::FillStyle_NONE)); + } + } + else + { + mbNoFill = true; + } + + if (bText && mbFntDirty) + { + vcl::Font aFnt(mpVD->GetFont()); + const sal_uInt32 nHeight(FRound(aFnt.GetFontSize().Height() * mfScaleY)); + + mpTextAttr->Put(SvxFontItem(aFnt.GetFamilyType(), aFnt.GetFamilyName(), aFnt.GetStyleName(), + aFnt.GetPitch(), aFnt.GetCharSet(), EE_CHAR_FONTINFO)); + mpTextAttr->Put(SvxFontItem(aFnt.GetFamilyType(), aFnt.GetFamilyName(), aFnt.GetStyleName(), + aFnt.GetPitch(), aFnt.GetCharSet(), EE_CHAR_FONTINFO_CJK)); + mpTextAttr->Put(SvxFontItem(aFnt.GetFamilyType(), aFnt.GetFamilyName(), aFnt.GetStyleName(), + aFnt.GetPitch(), aFnt.GetCharSet(), EE_CHAR_FONTINFO_CTL)); + mpTextAttr->Put(SvxPostureItem(aFnt.GetItalic(), EE_CHAR_ITALIC)); + mpTextAttr->Put(SvxWeightItem(aFnt.GetWeight(), EE_CHAR_WEIGHT)); + mpTextAttr->Put(SvxFontHeightItem(nHeight, 100, EE_CHAR_FONTHEIGHT)); + mpTextAttr->Put(SvxFontHeightItem(nHeight, 100, EE_CHAR_FONTHEIGHT_CJK)); + mpTextAttr->Put(SvxFontHeightItem(nHeight, 100, EE_CHAR_FONTHEIGHT_CTL)); + mpTextAttr->Put(SvxCharScaleWidthItem(100, EE_CHAR_FONTWIDTH)); + mpTextAttr->Put(SvxUnderlineItem(aFnt.GetUnderline(), EE_CHAR_UNDERLINE)); + mpTextAttr->Put(SvxOverlineItem(aFnt.GetOverline(), EE_CHAR_OVERLINE)); + mpTextAttr->Put(SvxCrossedOutItem(aFnt.GetStrikeout(), EE_CHAR_STRIKEOUT)); + mpTextAttr->Put(SvxShadowedItem(aFnt.IsShadow(), EE_CHAR_SHADOW)); + + // #i118485# Setting this item leads to problems (written #i118498# for this) + // mpTextAttr->Put(SvxAutoKernItem(aFnt.IsKerning(), EE_CHAR_KERNING)); + + mpTextAttr->Put(SvxWordLineModeItem(aFnt.IsWordLineMode(), EE_CHAR_WLM)); + mpTextAttr->Put(SvxContourItem(aFnt.IsOutline(), EE_CHAR_OUTLINE)); + mpTextAttr->Put(SvxColorItem(mpVD->GetTextColor(), EE_CHAR_COLOR)); + //... svxfont textitem svditext + mbFntDirty = false; + } + + if (pObj) + { + pObj->SetLayer(mnLayer); + + if (bLine) + { + pObj->SetMergedItemSet(*mpLineAttr); + } + + if (bFill) + { + pObj->SetMergedItemSet(*mpFillAttr); + } + + if (bText) + { + pObj->SetMergedItemSet(*mpTextAttr); + pObj->SetMergedItem(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_LEFT)); + } + } +} + +void ImpSdrPdfImport::InsertObj(SdrObject* pObj, bool bScale) +{ + if (bScale && !maScaleRect.IsEmpty()) + { + if (mbSize) + { + pObj->NbcResize(Point(), maScaleX, maScaleY); + } + + if (mbMov) + { + pObj->NbcMove(Size(maOfs.X(), maOfs.Y())); + } + } + + if (isClip()) + { + const basegfx::B2DPolyPolygon aPoly(pObj->TakeXorPoly()); + const basegfx::B2DRange aOldRange(aPoly.getB2DRange()); + const SdrLayerID aOldLayer(pObj->GetLayer()); + const SfxItemSet aOldItemSet(pObj->GetMergedItemSet()); + const SdrGrafObj* pSdrGrafObj = dynamic_cast<SdrGrafObj*>(pObj); + const SdrTextObj* pSdrTextObj = dynamic_cast<SdrTextObj*>(pObj); + + if (pSdrTextObj && pSdrTextObj->HasText()) + { + // all text objects are created from ImportText and have no line or fill attributes, so + // it is okay to concentrate on the text itself + while (true) + { + const basegfx::B2DPolyPolygon aTextContour(pSdrTextObj->TakeContour()); + const basegfx::B2DRange aTextRange(aTextContour.getB2DRange()); + const basegfx::B2DRange aClipRange(maClip.getB2DRange()); + + // no overlap -> completely outside + if (!aClipRange.overlaps(aTextRange)) + { + SdrObject::Free(pObj); + break; + } + + // when the clip is a rectangle fast check for inside is possible + if (basegfx::utils::isRectangle(maClip) && aClipRange.isInside(aTextRange)) + { + // completely inside ClipRect + break; + } + + // here text needs to be clipped; to do so, convert to SdrObjects with polygons + // and add these recursively. Delete original object, do not add in this run + SdrObjectUniquePtr pConverted = pSdrTextObj->ConvertToPolyObj(true, true); + SdrObject::Free(pObj); + + if (pConverted) + { + // recursively add created conversion; per definition this shall not + // contain further SdrTextObjs. Visit only non-group objects + SdrObjListIter aIter(*pConverted, SdrIterMode::DeepNoGroups); + + // work with clones; the created conversion may contain group objects + // and when working with the original objects the loop itself could + // break and the cleanup later would be pretty complicated (only delete group + // objects, are these empty, ...?) + while (aIter.IsMore()) + { + SdrObject* pCandidate = aIter.Next(); + OSL_ENSURE(pCandidate && dynamic_cast<SdrObjGroup*>(pCandidate) == nullptr, + "SdrObjListIter with SdrIterMode::DeepNoGroups error (!)"); + SdrObject* pNewClone( + pCandidate->CloneSdrObject(pCandidate->getSdrModelFromSdrObject())); + + if (pNewClone) + { + InsertObj(pNewClone, false); + } + else + { + OSL_ENSURE(false, "SdrObject::Clone() failed (!)"); + } + } + } + + break; + } + } + else + { + BitmapEx aBitmapEx; + + if (pSdrGrafObj) + { + aBitmapEx = pSdrGrafObj->GetGraphic().GetBitmapEx(); + } + + SdrObject::Free(pObj); + + if (!aOldRange.isEmpty()) + { + // clip against ClipRegion + const basegfx::B2DPolyPolygon aNewPoly(basegfx::utils::clipPolyPolygonOnPolyPolygon( + aPoly, maClip, true, !aPoly.isClosed())); + const basegfx::B2DRange aNewRange(aNewPoly.getB2DRange()); + + if (!aNewRange.isEmpty()) + { + pObj = new SdrPathObj(*mpModel, aNewPoly.isClosed() ? OBJ_POLY : OBJ_PLIN, + aNewPoly); + + pObj->SetLayer(aOldLayer); + pObj->SetMergedItemSet(aOldItemSet); + + if (!!aBitmapEx) + { + // aNewRange is inside of aOldRange and defines which part of aBitmapEx is used + const double fScaleX(aBitmapEx.GetSizePixel().Width() + / (aOldRange.getWidth() ? aOldRange.getWidth() : 1.0)); + const double fScaleY( + aBitmapEx.GetSizePixel().Height() + / (aOldRange.getHeight() ? aOldRange.getHeight() : 1.0)); + basegfx::B2DRange aPixel(aNewRange); + basegfx::B2DHomMatrix aTrans; + + aTrans.translate(-aOldRange.getMinX(), -aOldRange.getMinY()); + aTrans.scale(fScaleX, fScaleY); + aPixel.transform(aTrans); + + const Size aOrigSizePixel(aBitmapEx.GetSizePixel()); + const Point aClipTopLeft( + basegfx::fround(floor(std::max(0.0, aPixel.getMinX()))), + basegfx::fround(floor(std::max(0.0, aPixel.getMinY())))); + const Size aClipSize( + basegfx::fround(ceil(std::min( + static_cast<double>(aOrigSizePixel.Width()), aPixel.getWidth()))), + basegfx::fround( + ceil(std::min(static_cast<double>(aOrigSizePixel.Height()), + aPixel.getHeight())))); + const BitmapEx aClippedBitmap(aBitmapEx, aClipTopLeft, aClipSize); + + pObj->SetMergedItem(XFillStyleItem(drawing::FillStyle_BITMAP)); + pObj->SetMergedItem(XFillBitmapItem(OUString(), Graphic(aClippedBitmap))); + pObj->SetMergedItem(XFillBmpTileItem(false)); + pObj->SetMergedItem(XFillBmpStretchItem(true)); + } + } + } + } + } + + if (pObj) + { + // #i111954# check object for visibility + // used are SdrPathObj, SdrRectObj, SdrCircObj, SdrGrafObj + bool bVisible(false); + + if (pObj->HasLineStyle()) + { + bVisible = true; + } + + if (!bVisible && pObj->HasFillStyle()) + { + bVisible = true; + } + + if (!bVisible) + { + SdrTextObj* pTextObj = dynamic_cast<SdrTextObj*>(pObj); + + if (pTextObj && pTextObj->HasText()) + { + bVisible = true; + } + } + + if (!bVisible) + { + SdrGrafObj* pGrafObj = dynamic_cast<SdrGrafObj*>(pObj); + + if (pGrafObj) + { + // this may be refined to check if the graphic really is visible. It + // is here to ensure that graphic objects without fill, line and text + // get created + bVisible = true; + } + } + + if (!bVisible) + { + SdrObject::Free(pObj); + } + else + { + maTmpList.push_back(pObj); + + if (dynamic_cast<SdrPathObj*>(pObj)) + { + const bool bClosed(pObj->IsClosedObj()); + + mbLastObjWasPolyWithoutLine = mbNoLine && bClosed; + } + else + { + mbLastObjWasPolyWithoutLine = false; + } + } + } +} + +bool ImpSdrPdfImport::CheckLastPolyLineAndFillMerge(const basegfx::B2DPolyPolygon& rPolyPolygon) +{ + // #i73407# reformulation to use new B2DPolygon classes + if (mbLastObjWasPolyWithoutLine) + { + SdrObject* pTmpObj = !maTmpList.empty() ? maTmpList[maTmpList.size() - 1] : nullptr; + SdrPathObj* pLastPoly = dynamic_cast<SdrPathObj*>(pTmpObj); + + if (pLastPoly) + { + if (pLastPoly->GetPathPoly() == rPolyPolygon) + { + SetAttributes(nullptr); + + if (!mbNoLine && mbNoFill) + { + pLastPoly->SetMergedItemSet(*mpLineAttr); + + return true; + } + } + } + } + + return false; +} + +void ImpSdrPdfImport::checkClip() +{ + if (mpVD->IsClipRegion()) + { + maClip = mpVD->GetClipRegion().GetAsB2DPolyPolygon(); + + if (isClip()) + { + const basegfx::B2DHomMatrix aTransform(basegfx::utils::createScaleTranslateB2DHomMatrix( + mfScaleX, mfScaleY, maOfs.X(), maOfs.Y())); + + maClip.transform(aTransform); + } + } +} + +bool ImpSdrPdfImport::isClip() const { return !maClip.getB2DRange().isEmpty(); } +void ImpSdrPdfImport::ImportPdfObject(FPDF_PAGEOBJECT pPageObject, FPDF_TEXTPAGE pTextPage, + int nPageObjectIndex) +{ + if (pPageObject == nullptr) + return; + + const int nPageObjectType = FPDFPageObj_GetType(pPageObject); + switch (nPageObjectType) + { + case FPDF_PAGEOBJ_TEXT: + ImportText(pPageObject, pTextPage, nPageObjectIndex); + break; + case FPDF_PAGEOBJ_PATH: + ImportPath(pPageObject, nPageObjectIndex); + break; + case FPDF_PAGEOBJ_IMAGE: + ImportImage(pPageObject, nPageObjectIndex); + break; + case FPDF_PAGEOBJ_SHADING: + SAL_WARN("sd.filter", "Got page object SHADING: " << nPageObjectIndex); + break; + case FPDF_PAGEOBJ_FORM: + ImportForm(pPageObject, pTextPage, nPageObjectIndex); + break; + default: + SAL_WARN("sd.filter", "Unknown PDF page object #" << nPageObjectIndex + << " of type: " << nPageObjectType); + break; + } +} + +void ImpSdrPdfImport::ImportForm(FPDF_PAGEOBJECT pPageObject, FPDF_TEXTPAGE pTextPage, + int /*nPageObjectIndex*/) +{ + // Get the form matrix to perform correct translation/scaling of the form sub-objects. + const basegfx::B2DHomMatrix aOldMatrix = maCurrentMatrix; + + FS_MATRIX matrix; + FPDFFormObj_GetMatrix(pPageObject, &matrix); + maCurrentMatrix + = basegfx::B2DHomMatrix::abcdef(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f); + + const int nCount = FPDFFormObj_CountObjects(pPageObject); + for (int nIndex = 0; nIndex < nCount; ++nIndex) + { + FPDF_PAGEOBJECT pFormObject = FPDFFormObj_GetObject(pPageObject, nIndex); + ImportPdfObject(pFormObject, pTextPage, -1); + } + + // Restore the old one. + maCurrentMatrix = aOldMatrix; +} + +void ImpSdrPdfImport::ImportText(FPDF_PAGEOBJECT pPageObject, FPDF_TEXTPAGE pTextPage, + int /*nPageObjectIndex*/) +{ + float left; + float bottom; + float right; + float top; + if (!FPDFPageObj_GetBounds(pPageObject, &left, &bottom, &right, &top)) + { + SAL_WARN("sd.filter", "FAILED to get TEXT bounds"); + } + + if (left == right || top == bottom) + return; + + FS_MATRIX matrix; + FPDFTextObj_GetMatrix(pPageObject, &matrix); + basegfx::B2DHomMatrix aTextMatrix(maCurrentMatrix); + basegfx::B2DRange aTextRect(left, top, right, bottom); + aTextRect *= aTextMatrix; + const tools::Rectangle aRect = PointsToLogic(aTextRect.getMinX(), aTextRect.getMaxX(), + aTextRect.getMinY(), aTextRect.getMaxY()); + + const int nBytes = FPDFTextObj_GetText(pPageObject, pTextPage, nullptr, 0); + std::unique_ptr<sal_Unicode[]> pText(new sal_Unicode[nBytes]); + + const int nActualBytes = FPDFTextObj_GetText(pPageObject, pTextPage, pText.get(), nBytes); + if (nActualBytes <= 0) + { + return; + } + + // Let's rely on null-termination for the length of the string. We + // just know the number of bytes the string takes, but in OUString + // needs the number of characters. + OUString sText(pText.get()); + + const double dFontSize = FPDFTextObj_GetFontSize(pPageObject); + double dFontSizeH = fabs(sqrt2(matrix.a, matrix.c) * dFontSize); + double dFontSizeV = fabs(sqrt2(matrix.b, matrix.d) * dFontSize); + + dFontSizeH = convertPointToMm100(dFontSizeH); + dFontSizeV = convertPointToMm100(dFontSizeV); + + const Size aFontSize(dFontSizeH, dFontSizeV); + vcl::Font aFnt = mpVD->GetFont(); + if (aFontSize != aFnt.GetFontSize()) + { + aFnt.SetFontSize(aFontSize); + mpVD->SetFont(aFnt); + mbFntDirty = true; + } + + const int nFontName = 80 + 1; + std::unique_ptr<char[]> pFontName(new char[nFontName]); // + terminating null + char* pCharFontName = reinterpret_cast<char*>(pFontName.get()); + int nFontNameChars = FPDFTextObj_GetFontName(pPageObject, pCharFontName, nFontName); + if (nFontName >= nFontNameChars) + { + OUString sFontName = OUString::createFromAscii(pFontName.get()); + if (sFontName != aFnt.GetFamilyName()) + { + aFnt.SetFamilyName(sFontName); + mpVD->SetFont(aFnt); + mbFntDirty = true; + } + } + + Color aTextColor(COL_TRANSPARENT); + bool bFill = false; + bool bUse = true; + switch (FPDFTextObj_GetTextRenderMode(pPageObject)) + { + case FPDF_TEXTRENDERMODE_FILL: + case FPDF_TEXTRENDERMODE_FILL_CLIP: + case FPDF_TEXTRENDERMODE_FILL_STROKE: + case FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP: + bFill = true; + break; + case FPDF_TEXTRENDERMODE_STROKE: + case FPDF_TEXTRENDERMODE_STROKE_CLIP: + case FPDF_TEXTRENDERMODE_UNKNOWN: + break; + case FPDF_TEXTRENDERMODE_INVISIBLE: + case FPDF_TEXTRENDERMODE_CLIP: + bUse = false; + break; + } + if (bUse) + { + unsigned int nR, nG, nB, nA; + bool bRet = bFill ? FPDFPageObj_GetFillColor(pPageObject, &nR, &nG, &nB, &nA) + : FPDFPageObj_GetStrokeColor(pPageObject, &nR, &nG, &nB, &nA); + if (bRet) + aTextColor = Color(nR, nG, nB); + } + + if (aTextColor != mpVD->GetTextColor()) + { + mpVD->SetTextColor(aTextColor); + mbFntDirty = true; + } + + InsertTextObject(aRect.TopLeft(), aRect.GetSize(), sText); +} + +void ImpSdrPdfImport::InsertTextObject(const Point& rPos, const Size& rSize, const OUString& rStr) +{ + // calc text box size, add 5% to make it fit safely + + FontMetric aFontMetric(mpVD->GetFontMetric()); + vcl::Font aFont(mpVD->GetFont()); + FontAlign eAlignment(aFont.GetAlignment()); + + // sal_Int32 nTextWidth = static_cast<sal_Int32>(mpVD->GetTextWidth(rStr) * mfScaleX); + sal_Int32 nTextHeight = static_cast<sal_Int32>(mpVD->GetTextHeight() * mfScaleY); + + Point aPosition(FRound(rPos.X() * mfScaleX + maOfs.X()), + FRound(rPos.Y() * mfScaleY + maOfs.Y())); + Size aSize(FRound(rSize.Width() * mfScaleX), FRound(rSize.Height() * mfScaleY)); + + if (eAlignment == ALIGN_BASELINE) + aPosition.AdjustY(-FRound(aFontMetric.GetAscent() * mfScaleY)); + else if (eAlignment == ALIGN_BOTTOM) + aPosition.AdjustY(-nTextHeight); + + tools::Rectangle aTextRect(aPosition, aSize); + SdrRectObj* pText = new SdrRectObj(*mpModel, OBJ_TEXT, aTextRect); + + pText->SetMergedItem(makeSdrTextUpperDistItem(0)); + pText->SetMergedItem(makeSdrTextLowerDistItem(0)); + pText->SetMergedItem(makeSdrTextRightDistItem(0)); + pText->SetMergedItem(makeSdrTextLeftDistItem(0)); + + if (aFont.GetAverageFontWidth()) + { + pText->ClearMergedItem(SDRATTR_TEXT_AUTOGROWWIDTH); + pText->SetMergedItem(makeSdrTextAutoGrowHeightItem(false)); + // don't let the margins eat the space needed for the text + pText->SetMergedItem(SdrTextFitToSizeTypeItem(drawing::TextFitToSizeType_ALLLINES)); + } + else + { + pText->SetMergedItem(makeSdrTextAutoGrowWidthItem(true)); + } + + pText->SetLayer(mnLayer); + pText->NbcSetText(rStr); + SetAttributes(pText, true); + pText->SetSnapRect(aTextRect); + + if (!aFont.IsTransparent()) + { + SfxItemSet aAttr(*mpFillAttr->GetPool(), svl::Items<XATTR_FILL_FIRST, XATTR_FILL_LAST>{}); + aAttr.Put(XFillStyleItem(drawing::FillStyle_SOLID)); + aAttr.Put(XFillColorItem(OUString(), aFont.GetFillColor())); + pText->SetMergedItemSet(aAttr); + } + sal_uInt32 nAngle = aFont.GetOrientation(); + if (nAngle) + { + nAngle *= 10; + double a = nAngle * F_PI18000; + double nSin = sin(a); + double nCos = cos(a); + pText->NbcRotate(aPosition, nAngle, nSin, nCos); + } + InsertObj(pText, false); +} + +void ImpSdrPdfImport::MapScaling() +{ + const size_t nCount(maTmpList.size()); + const MapMode& rMap = mpVD->GetMapMode(); + Point aMapOrg(rMap.GetOrigin()); + bool bMov2(aMapOrg.X() != 0 || aMapOrg.Y() != 0); + + if (bMov2) + { + for (size_t i = mnMapScalingOfs; i < nCount; i++) + { + SdrObject* pObj = maTmpList[i]; + + pObj->NbcMove(Size(aMapOrg.X(), aMapOrg.Y())); + } + } + + mnMapScalingOfs = nCount; +} + +void ImpSdrPdfImport::ImportImage(FPDF_PAGEOBJECT pPageObject, int /*nPageObjectIndex*/) +{ + std::unique_ptr<std::remove_pointer<FPDF_BITMAP>::type, FPDFBitmapDeleter> bitmap( + FPDFImageObj_GetBitmap(pPageObject)); + if (!bitmap) + { + SAL_WARN("sd.filter", "Failed to get IMAGE"); + return; + } + + const int format = FPDFBitmap_GetFormat(bitmap.get()); + if (format == FPDFBitmap_Unknown) + { + SAL_WARN("sd.filter", "Failed to get IMAGE format"); + return; + } + + const unsigned char* pBuf + = static_cast<const unsigned char*>(FPDFBitmap_GetBuffer(bitmap.get())); + const int nWidth = FPDFBitmap_GetWidth(bitmap.get()); + const int nHeight = FPDFBitmap_GetHeight(bitmap.get()); + const int nStride = FPDFBitmap_GetStride(bitmap.get()); + BitmapEx aBitmap(Size(nWidth, nHeight), 24); + + switch (format) + { + case FPDFBitmap_BGR: + ReadRawDIB(aBitmap, pBuf, ScanlineFormat::N24BitTcBgr, nHeight, nStride); + break; + case FPDFBitmap_BGRx: + ReadRawDIB(aBitmap, pBuf, ScanlineFormat::N32BitTcRgba, nHeight, nStride); + break; + case FPDFBitmap_BGRA: + ReadRawDIB(aBitmap, pBuf, ScanlineFormat::N32BitTcBgra, nHeight, nStride); + break; + default: + SAL_WARN("sd.filter", "Got IMAGE width: " << nWidth << ", height: " << nHeight + << ", stride: " << nStride + << ", format: " << format); + break; + } + + float left; + float bottom; + float right; + float top; + if (!FPDFPageObj_GetBounds(pPageObject, &left, &bottom, &right, &top)) + { + SAL_WARN("sd.filter", "FAILED to get image bounds"); + } + + tools::Rectangle aRect = PointsToLogic(left, right, top, bottom); + aRect.AdjustRight(1); + aRect.AdjustBottom(1); + + SdrGrafObj* pGraf = new SdrGrafObj(*mpModel, Graphic(aBitmap), aRect); + + // This action is not creating line and fill, set directly, do not use SetAttributes(..) + pGraf->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE)); + pGraf->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE)); + InsertObj(pGraf); +} + +void ImpSdrPdfImport::ImportPath(FPDF_PAGEOBJECT pPageObject, int /*nPageObjectIndex*/) +{ + FS_MATRIX matrix; + FPDFPath_GetMatrix(pPageObject, &matrix); + + auto aPathMatrix + = basegfx::B2DHomMatrix::abcdef(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f); + aPathMatrix *= maCurrentMatrix; + + basegfx::B2DPolyPolygon aPolyPoly; + basegfx::B2DPolygon aPoly; + std::vector<basegfx::B2DPoint> aBezier; + + const int nSegments = FPDFPath_CountSegments(pPageObject); + for (int nSegmentIndex = 0; nSegmentIndex < nSegments; ++nSegmentIndex) + { + FPDF_PATHSEGMENT pPathSegment = FPDFPath_GetPathSegment(pPageObject, nSegmentIndex); + if (pPathSegment != nullptr) + { + float fx, fy; + if (!FPDFPathSegment_GetPoint(pPathSegment, &fx, &fy)) + { + SAL_WARN("sd.filter", "Failed to get PDF path segment point"); + continue; + } + + basegfx::B2DPoint aB2DPoint(fx, fy); + aB2DPoint *= aPathMatrix; + + const bool bClose = FPDFPathSegment_GetClose(pPathSegment); + if (bClose) + aPoly.setClosed(bClose); // TODO: Review + + Point aPoint = PointsToLogic(aB2DPoint.getX(), aB2DPoint.getY()); + aB2DPoint.setX(aPoint.X()); + aB2DPoint.setY(aPoint.Y()); + + const int nSegmentType = FPDFPathSegment_GetType(pPathSegment); + switch (nSegmentType) + { + case FPDF_SEGMENT_LINETO: + aPoly.append(aB2DPoint); + break; + + case FPDF_SEGMENT_BEZIERTO: + aBezier.emplace_back(aB2DPoint.getX(), aB2DPoint.getY()); + if (aBezier.size() == 3) + { + aPoly.appendBezierSegment(aBezier[0], aBezier[1], aBezier[2]); + aBezier.clear(); + } + break; + + case FPDF_SEGMENT_MOVETO: + // New Poly. + if (aPoly.count() > 0) + { + aPolyPoly.append(aPoly, 1); + aPoly.clear(); + } + + aPoly.append(aB2DPoint); + break; + + case FPDF_SEGMENT_UNKNOWN: + default: + SAL_WARN("sd.filter", "Unknown path segment type in PDF: " << nSegmentType); + break; + } + } + } + + if (aBezier.size() == 3) + { + aPoly.appendBezierSegment(aBezier[0], aBezier[1], aBezier[2]); + aBezier.clear(); + } + + if (aPoly.count() > 0) + { + aPolyPoly.append(aPoly, 1); + aPoly.clear(); + } + + const basegfx::B2DHomMatrix aTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix(mfScaleX, mfScaleY, maOfs.X(), maOfs.Y())); + aPolyPoly.transform(aTransform); + + float fWidth = 1; + FPDFPageObj_GetStrokeWidth(pPageObject, &fWidth); + const double dWidth = 0.5 * fabs(sqrt2(aPathMatrix.a(), aPathMatrix.c()) * fWidth); + mnLineWidth = convertPointToMm100(dWidth); + + int nFillMode = FPDF_FILLMODE_ALTERNATE; + FPDF_BOOL bStroke = 1; // Assume we have to draw, unless told otherwise. + if (FPDFPath_GetDrawMode(pPageObject, &nFillMode, &bStroke)) + { + if (nFillMode == FPDF_FILLMODE_ALTERNATE) + mpVD->SetDrawMode(DrawModeFlags::Default); + else if (nFillMode == FPDF_FILLMODE_WINDING) + mpVD->SetDrawMode(DrawModeFlags::Default); + else + mpVD->SetDrawMode(DrawModeFlags::NoFill); + } + + unsigned int nR; + unsigned int nG; + unsigned int nB; + unsigned int nA; + FPDFPageObj_GetFillColor(pPageObject, &nR, &nG, &nB, &nA); + mpVD->SetFillColor(Color(nR, nG, nB)); + + if (bStroke) + { + FPDFPageObj_GetStrokeColor(pPageObject, &nR, &nG, &nB, &nA); + mpVD->SetLineColor(Color(nR, nG, nB)); + } + else + mpVD->SetLineColor(COL_TRANSPARENT); + + if (!mbLastObjWasPolyWithoutLine || !CheckLastPolyLineAndFillMerge(aPolyPoly)) + { + SdrPathObj* pPath = new SdrPathObj(*mpModel, OBJ_POLY, aPolyPoly); + SetAttributes(pPath); + InsertObj(pPath, false); + } +} + +Point ImpSdrPdfImport::PointsToLogic(double x, double y) const +{ + y = correctVertOrigin(y); + + Point aPos(convertPointToMm100(x), convertPointToMm100(y)); + return aPos; +} + +tools::Rectangle ImpSdrPdfImport::PointsToLogic(double left, double right, double top, + double bottom) const +{ + top = correctVertOrigin(top); + bottom = correctVertOrigin(bottom); + + Point aPos(convertPointToMm100(left), convertPointToMm100(top)); + Size aSize(convertPointToMm100(right - left), convertPointToMm100(bottom - top)); + + return tools::Rectangle(aPos, aSize); +} + +#endif // HAVE_FEATURE_PDFIUM + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |