/* -*- 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 #if HAVE_FEATURE_PDFIUM #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { /// Convert from DPI to pixels. /// PDFs don't have resolution, rather, /// dimensions are in inches, with 72 points / inch. /// Here we effectively render at 96 DPI (to match /// the image rendered in vcl::ImportPDF in pdfread.cxx). static inline double lcl_PointToPixel(double fPoint) { return fPoint * 96. / 72.; } /// Convert from pixels to logic (twips). static inline long lcl_ToLogic(double value) { // Convert to integral preserving two dp. const long in = static_cast(value * 100.); const long out = OutputDevice::LogicToLogic(in, MapUnit::MapPixel, MapUnit::Map100thMM); return out / 100; } static inline double sqrt2(double a, double b) { return sqrt(a * a + b * b); } } struct FPDFBitmapDeleter { inline void operator()(FPDF_BITMAP bitmap) { FPDFBitmap_Destroy(bitmap); } }; using namespace com::sun::star; ImpSdrPdfImport::ImpSdrPdfImport(SdrModel& rModel, SdrLayerID nLay, const tools::Rectangle& rRect, const std::shared_ptr>& pPdfData) : maTmpList() , mpVD(VclPtr::Create()) , maScaleRect(rRect) , mpPdfData(pPdfData) , mnMapScalingOfs(0) , mpLineAttr(nullptr) , mpFillAttr(nullptr) , mpTextAttr(nullptr) , mpModel(&rModel) , mnLayer(nLay) , maOldLineColor() , mnLineWidth(0) , maLineJoin(basegfx::B2DLineJoin::NONE) , maLineCap(css::drawing::LineCap_BUTT) , 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) , mbLastObjWasLine(false) , maClip() , mpPdfDocument(nullptr) , mnPageCount(0) , mdPageWidthPts(0) , mdPageHeightPts(0) { mpVD->EnableOutput(false); mpVD->SetLineColor(); mpVD->SetFillColor(); maOldLineColor.SetRed(mpVD->GetLineColor().GetRed() + 1); mpLineAttr = o3tl::make_unique(rModel.GetItemPool(), svl::Items{}); mpFillAttr = o3tl::make_unique(rModel.GetItemPool(), svl::Items{}); mpTextAttr = o3tl::make_unique(rModel.GetItemPool(), svl::Items{}); checkClip(); FPDF_LIBRARY_CONFIG aConfig; aConfig.version = 2; aConfig.m_pUserFontPaths = nullptr; aConfig.m_pIsolate = nullptr; aConfig.m_v8EmbedderSlot = 0; FPDF_InitLibraryWithConfig(&aConfig); // Load the buffer using pdfium. mpPdfDocument = FPDF_LoadMemDocument(mpPdfData->getConstArray(), mpPdfData->getLength(), /*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); FPDF_DestroyLibrary(); } 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_CountObject(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(lcl_ToLogic(lcl_PointToPixel(dPageWidth)), lcl_ToLogic(lcl_PointToPixel(dPageHeight))); if (aPageSize.Width() && aPageSize.Height() && (!maScaleRect.IsEmpty())) { maOfs = maScaleRect.TopLeft(); if (aPageSize.Width() != (maScaleRect.GetWidth() - 1)) { mfScaleX = static_cast(maScaleRect.GetWidth() - 1) / static_cast(aPageSize.Width()); } if (aPageSize.Height() != (maScaleRect.GetHeight() - 1)) { mfScaleY = static_cast(maScaleRect.GetHeight() - 1) / static_cast(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)); } switch (maLineJoin) { case basegfx::B2DLineJoin::NONE: mpLineAttr->Put(XLineJointItem(css::drawing::LineJoint_NONE)); break; case basegfx::B2DLineJoin::Bevel: mpLineAttr->Put(XLineJointItem(css::drawing::LineJoint_BEVEL)); break; case basegfx::B2DLineJoin::Miter: mpLineAttr->Put(XLineJointItem(css::drawing::LineJoint_MITER)); break; case basegfx::B2DLineJoin::Round: mpLineAttr->Put(XLineJointItem(css::drawing::LineJoint_ROUND)); break; } // Add LineCap support mpLineAttr->Put(XLineCapItem(maLineCap)); 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(pObj); const SdrTextObj* pSdrTextObj = dynamic_cast(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 SdrObject* 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(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 (!)"); } } // cleanup temporary conversion objects SdrObject::Free(pConverted); } 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(aOrigSizePixel.Width()), aPixel.getWidth()))), basegfx::fround( ceil(std::min(static_cast(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(pObj); if (pTextObj && pTextObj->HasText()) { bVisible = true; } } if (!bVisible) { SdrGrafObj* pGrafObj = dynamic_cast(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(pObj)) { const bool bClosed(pObj->IsClosedObj()); mbLastObjWasPolyWithoutLine = mbNoLine && bClosed; mbLastObjWasLine = !bClosed; } else { mbLastObjWasPolyWithoutLine = false; mbLastObjWasLine = false; } } } } bool ImpSdrPdfImport::CheckLastLineMerge(const basegfx::B2DPolygon& rSrcPoly) { // #i102706# Do not merge closed polygons if (rSrcPoly.isClosed()) { return false; } // #i73407# reformulation to use new B2DPolygon classes if (mbLastObjWasLine && (maOldLineColor == mpVD->GetLineColor()) && rSrcPoly.count()) { SdrObject* pTmpObj = maTmpList.size() ? maTmpList[maTmpList.size() - 1] : nullptr; SdrPathObj* pLastPoly = dynamic_cast(pTmpObj); if (pLastPoly) { if (1 == pLastPoly->GetPathPoly().count()) { bool bOk(false); basegfx::B2DPolygon aDstPoly(pLastPoly->GetPathPoly().getB2DPolygon(0)); // #i102706# Do not merge closed polygons if (aDstPoly.isClosed()) { return false; } if (aDstPoly.count()) { const sal_uInt32 nMaxDstPnt(aDstPoly.count() - 1); const sal_uInt32 nMaxSrcPnt(rSrcPoly.count() - 1); if (aDstPoly.getB2DPoint(nMaxDstPnt) == rSrcPoly.getB2DPoint(0)) { aDstPoly.append(rSrcPoly, 1, rSrcPoly.count() - 1); bOk = true; } else if (aDstPoly.getB2DPoint(0) == rSrcPoly.getB2DPoint(nMaxSrcPnt)) { basegfx::B2DPolygon aNew(rSrcPoly); aNew.append(aDstPoly, 1, aDstPoly.count() - 1); aDstPoly = aNew; bOk = true; } else if (aDstPoly.getB2DPoint(0) == rSrcPoly.getB2DPoint(0)) { aDstPoly.flip(); aDstPoly.append(rSrcPoly, 1, rSrcPoly.count() - 1); bOk = true; } else if (aDstPoly.getB2DPoint(nMaxDstPnt) == rSrcPoly.getB2DPoint(nMaxSrcPnt)) { basegfx::B2DPolygon aNew(rSrcPoly); aNew.flip(); aDstPoly.append(aNew, 1, aNew.count() - 1); bOk = true; } } if (bOk) { pLastPoly->NbcSetPathPoly(basegfx::B2DPolyPolygon(aDstPoly)); } return bOk; } } } return false; } bool ImpSdrPdfImport::CheckLastPolyLineAndFillMerge(const basegfx::B2DPolyPolygon& rPolyPolygon) { // #i73407# reformulation to use new B2DPolygon classes if (mbLastObjWasPolyWithoutLine) { SdrObject* pTmpObj = maTmpList.size() ? maTmpList[maTmpList.size() - 1] : nullptr; SdrPathObj* pLastPoly = dynamic_cast(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 Matrix aOldMatrix = mCurMatrix; double a, b, c, d, e, f; FPDFFormObj_GetMatrix(pPageObject, &a, &b, &c, &d, &e, &f); mCurMatrix = Matrix(a, b, c, d, e, f); const int nCount = FPDFFormObj_CountSubObjects(pPageObject); for (int nIndex = 0; nIndex < nCount; ++nIndex) { FPDF_PAGEOBJECT pFormObject = FPDFFormObj_GetSubObject(pPageObject, nIndex); ImportPdfObject(pFormObject, pTextPage, -1); } // Restore the old one. mCurMatrix = 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; double a, b, c, d, e, f; FPDFTextObj_GetMatrix(pPageObject, &a, &b, &c, &d, &e, &f); Matrix aTextMatrix(mCurMatrix); aTextMatrix.Transform(left, right, top, bottom); const tools::Rectangle aRect = PointsToLogic(left, right, top, bottom); const int nChars = FPDFTextObj_CountChars(pPageObject) * 2; std::unique_ptr pText(new sal_Unicode[nChars + 1]); // + terminating null unsigned short* pShortText = reinterpret_cast(pText.get()); const int nActualChars = FPDFTextObj_GetTextProcessed(pPageObject, pTextPage, 0, nChars, pShortText); if (nActualChars <= 0) { return; } OUString sText(pText.get(), nActualChars); const double dFontSize = FPDFTextObj_GetFontSize(pPageObject); double dFontSizeH = fabs(sqrt2(a, c) * dFontSize); double dFontSizeV = fabs(sqrt2(b, d) * dFontSize); dFontSizeH = lcl_PointToPixel(dFontSizeH); dFontSizeV = lcl_PointToPixel(dFontSizeV); dFontSizeH = lcl_ToLogic(dFontSizeH); dFontSizeV = lcl_ToLogic(dFontSizeV); const Size aFontSize(dFontSizeH, dFontSizeV); vcl::Font aFnt = mpVD->GetFont(); if (aFontSize != aFnt.GetFontSize()) { aFnt.SetFontSize(aFontSize); mpVD->SetFont(aFnt); mbFntDirty = true; } std::unique_ptr pFontName(new char[80 + 1]); // + terminating null char* pCharFontName = reinterpret_cast(pFontName.get()); int nFontNameChars = FPDFTextObj_GetFontName(pPageObject, pCharFontName); if (nFontNameChars > 0) { OUString sFontName = OUString::createFromAscii(pFontName.get()); if (sFontName != aFnt.GetFamilyName()) { aFnt.SetFamilyName(sFontName); mpVD->SetFont(aFnt); mbFntDirty = true; } } Color aTextColor(COL_TRANSPARENT); unsigned int nR, nG, nB, nA; if (FPDFTextObj_GetColor(pPageObject, &nR, &nG, &nB, &nA)) aTextColor = Color(nR, nG, nB); if (aTextColor != mpVD->GetTextColor()) { mpVD->SetTextColor(aTextColor); mbFntDirty = true; } ImportText(aRect.TopLeft(), aRect.GetSize(), sText); } void ImpSdrPdfImport::ImportText(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 aFnt(mpVD->GetFont()); FontAlign eAlg(aFnt.GetAlignment()); // sal_Int32 nTextWidth = static_cast(mpVD->GetTextWidth(rStr) * mfScaleX); sal_Int32 nTextHeight = static_cast(mpVD->GetTextHeight() * mfScaleY); Point aPos(FRound(rPos.X() * mfScaleX + maOfs.X()), FRound(rPos.Y() * mfScaleY + maOfs.Y())); Size aSize(FRound(rSize.Width() * mfScaleX), FRound(rSize.Height() * mfScaleY)); if (eAlg == ALIGN_BASELINE) aPos.AdjustY(-FRound(aFontMetric.GetAscent() * mfScaleY)); else if (eAlg == ALIGN_BOTTOM) aPos.AdjustY(-nTextHeight); tools::Rectangle aTextRect(aPos, 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 (aFnt.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 (!aFnt.IsTransparent()) { SfxItemSet aAttr(*mpFillAttr->GetPool(), svl::Items{}); aAttr.Put(XFillStyleItem(drawing::FillStyle_SOLID)); aAttr.Put(XFillColorItem(OUString(), aFnt.GetFillColor())); pText->SetMergedItemSet(aAttr); } sal_uInt32 nAngle = aFnt.GetOrientation(); if (nAngle) { nAngle *= 10; double a = nAngle * nPi180; double nSin = sin(a); double nCos = cos(a); pText->NbcRotate(aPos, 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::type, FPDFBitmapDeleter> bitmap( FPDFImageObj_GetBitmapBgra(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(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()); Bitmap 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*/) { double a, b, c, d, e, f; FPDFPath_GetMatrix(pPageObject, &a, &b, &c, &d, &e, &f); Matrix aPathMatrix(a, b, c, d, e, f); aPathMatrix.Concatinate(mCurMatrix); basegfx::B2DPolyPolygon aPolyPoly; basegfx::B2DPolygon aPoly; std::vector 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 segement point"); continue; } double x = fx; double y = fy; aPathMatrix.Transform(x, y); const bool bClose = FPDFPathSegment_GetClose(pPathSegment); if (bClose) aPoly.setClosed(bClose); // TODO: Review Point aPoint = PointsToLogic(x, y); x = aPoint.X(); y = aPoint.Y(); const int nSegmentType = FPDFPathSegment_GetType(pPathSegment); switch (nSegmentType) { case FPDF_SEGMENT_LINETO: aPoly.append(basegfx::B2DPoint(x, y)); break; case FPDF_SEGMENT_BEZIERTO: aBezier.emplace_back(x, y); 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(basegfx::B2DPoint(x, y)); 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; FPDFPath_GetStrokeWidth(pPageObject, &fWidth); const double dWidth = 0.5 * fabs(sqrt2(aPathMatrix.a(), aPathMatrix.c()) * fWidth); mnLineWidth = lcl_ToLogic(lcl_PointToPixel(dWidth)); int nFillMode = FPDF_FILLMODE_ALTERNATE; FPDF_BOOL bStroke = true; 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; FPDFPath_GetFillColor(pPageObject, &nR, &nG, &nB, &nA); mpVD->SetFillColor(Color(nR, nG, nB)); if (bStroke) { FPDFPath_GetStrokeColor(pPageObject, &nR, &nG, &nB, &nA); mpVD->SetLineColor(Color(nR, nG, nB)); } else mpVD->SetLineColor(COL_TRANSPARENT); if (!mbLastObjWasPolyWithoutLine || !CheckLastPolyLineAndFillMerge(basegfx::B2DPolyPolygon(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); x = lcl_PointToPixel(x); y = lcl_PointToPixel(y); Point aPos(lcl_ToLogic(x), lcl_ToLogic(y)); return aPos; } tools::Rectangle ImpSdrPdfImport::PointsToLogic(double left, double right, double top, double bottom) const { top = correctVertOrigin(top); bottom = correctVertOrigin(bottom); left = lcl_PointToPixel(left); right = lcl_PointToPixel(right); top = lcl_PointToPixel(top); bottom = lcl_PointToPixel(bottom); Point aPos(lcl_ToLogic(left), lcl_ToLogic(top)); Size aSize(lcl_ToLogic(right - left), lcl_ToLogic(bottom - top)); tools::Rectangle aRect(aPos, aSize); return aRect; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */