external/pdfium/UnpackedTarball_pdfium.mk | 1 external/pdfium/extractpatterns.patch | 183 ++++++++++++++++++++++++++++++ include/vcl/filter/PDFiumLibrary.hxx | 6 sd/qa/unit/data/pdf/pattern-fill.pdf |binary sd/qa/unit/data/pdf/pattern-stroke.pdf |binary sd/qa/unit/export-tests.cxx | 42 ++++++ svx/source/inc/svdpdf.hxx | 12 + svx/source/svdraw/svdpdf.cxx | 153 ++++++++++++++++++++----- vcl/source/pdf/PDFiumLibrary.cxx | 34 +++++ 9 files changed, 405 insertions(+), 26 deletions(-)
New commits: commit 4c1f88e1eaf030ff30184bc6191824c38eb8659d Author: Caolán McNamara <[email protected]> AuthorDate: Tue Oct 21 21:07:28 2025 +0100 Commit: Caolán McNamara <[email protected]> CommitDate: Sun Oct 26 12:07:44 2025 +0100 stash and elide global font matrix and overwrite local one with it rather than let both of them pass through, where conversion to otf of input with: /FontMatrix [0.0005 0 0 0.0005 0 0] def ... ... %ADOBeginFontDict ... /FontMatrix [1 0 0 1 0 0] def otherwise then results in output of otf with UnitsPerEm of 1 which then freetype rejects as invalid and so nothing is rendered. Change-Id: I037a2bea1b1caf5259af40c59cc529afe63db998 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192809 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Miklos Vajna <[email protected]> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192988 Tested-by: Jenkins Reviewed-by: Caolán McNamara <[email protected]> diff --git a/svx/source/svdraw/svdpdf.cxx b/svx/source/svdraw/svdpdf.cxx index 0b203f8d6a00..ff9346c005a3 100644 --- a/svx/source/svdraw/svdpdf.cxx +++ b/svx/source/svdraw/svdpdf.cxx @@ -969,8 +969,12 @@ static bool isSimpleFamilyName(std::string_view Weight) || Weight == "BoldItalic"; } -static void rewriteBrokenFontName(std::string_view brokenName, std::string_view brokenCIDName, - std::string_view fixedName, const OUString& pfaCIDUrl) +// a) change brokenName/brokenCIDName if present to fixedName +// b) remove preamble FontMatrix and overwrite following ones in the %ADOBeginFontDict +// section with that content instead to avoid generating fonts with unusual UnitsPerEm +// of 1 that freetype will reject +static void rewriteFont(std::string_view brokenName, std::string_view brokenCIDName, + std::string_view fixedName, const OUString& pfaCIDUrl) { OUString oldCIDUrl = pfaCIDUrl + ".broken"; if (osl::File::move(pfaCIDUrl, oldCIDUrl) != osl::File::E_None) @@ -979,6 +983,12 @@ static void rewriteBrokenFontName(std::string_view brokenName, std::string_view return; } + const bool rewriteNames = !brokenName.empty(); + + bool fontDict = false; + + OString sGlobalMatrix; + const OString sBrokenFontLine = "/FontName /"_ostr + brokenName + " def"_ostr; const OString sFixedFontLine = "/FontName /"_ostr + fixedName + " def"_ostr; @@ -990,16 +1000,40 @@ static void rewriteBrokenFontName(std::string_view brokenName, std::string_view OString sLine; while (input.ReadLine(sLine)) { - if (sLine == sBrokenFontLine) + if (rewriteNames) { - output.WriteLine(sFixedFontLine); - continue; + if (sLine == sBrokenFontLine) + { + output.WriteLine(sFixedFontLine); + continue; + } + else if (sLine == sBrokenCIDFontLine) + { + output.WriteLine(sFixedCIDFontLine); + continue; + } } - else if (sLine == sBrokenCIDFontLine) + + if (sLine.startsWith("%ADOBeginFontDict")) + fontDict = true; + else if (sLine.startsWith("/FontMatrix ")) { - output.WriteLine(sFixedCIDFontLine); - continue; + if (!fontDict) + { + // Global case, stash and don't emit the global matrix + sGlobalMatrix = sLine; + continue; + } + + if (!sGlobalMatrix.isEmpty()) + { + // Local case, emit the global matrix instead if + // there was one, otherwise emit the local matrix + output.WriteLine(sGlobalMatrix); + continue; + } } + output.WriteLine(sLine); if (sLine.startsWith("%%BeginData")) { @@ -1027,7 +1061,7 @@ static bool toPfaCID(SubSetInfo& rSubSetInfo, const OUString& fileUrl, OUString toMergedMapUrl = fileUrl + u".tomergedmap"; OString version, Notice, FullName, FamilyName, CIDFontName, CIDFontVersion, srcFontType, - glyphTag; + glyphTag, FontMatrix; OString brokenFontName; FontName = postScriptName.toUtf8(); std::map<sal_Int32, OString> glyphIndexToName; @@ -1067,6 +1101,8 @@ static bool toPfaCID(SubSetInfo& rSubSetInfo, const OUString& fileUrl, continue; if (extractEntry(sLine, "Weight", Weight)) continue; + if (extractEntry(sLine, "FontMatrix", FontMatrix)) + continue; if (extractEntry(sLine, "sup.srcFontType", srcFontType)) continue; if (extractEntry(sLine, "## glyph[tag]", glyphTag)) @@ -1218,8 +1254,12 @@ static bool toPfaCID(SubSetInfo& rSubSetInfo, const OUString& fileUrl, } } - if (!brokenFontName.isEmpty()) - rewriteBrokenFontName(brokenFontName, CIDFontName, FontName, pfaCIDUrl); + if (!brokenFontName.isEmpty() || !FontMatrix.isEmpty()) + { + // If the fontname isn't as expected, or if there is a + // font matrix present then we rewrite the font. + rewriteFont(brokenFontName, CIDFontName, FontName, pfaCIDUrl); + } return true; } commit cfb9b5eed006947caed0687798253c5b65c983ab Author: Caolán McNamara <[email protected]> AuthorDate: Thu Oct 16 16:06:38 2025 +0100 Commit: Caolán McNamara <[email protected]> CommitDate: Sun Oct 26 12:07:35 2025 +0100 extract fill/stroke patterns as bitmaps We can apply fill patterns as tiled bitmap fill. For strokes/text-color apply center color of pattern as the color to use. Change-Id: I123184d19363c0f0e0a2988108efdb71ff3d34f4 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192552 Reviewed-by: Miklos Vajna <[email protected]> Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192987 Tested-by: Caolán McNamara <[email protected]> Reviewed-by: Caolán McNamara <[email protected]> diff --git a/external/pdfium/UnpackedTarball_pdfium.mk b/external/pdfium/UnpackedTarball_pdfium.mk index a7c61a86a8a9..8df29bbf3e6f 100644 --- a/external/pdfium/UnpackedTarball_pdfium.mk +++ b/external/pdfium/UnpackedTarball_pdfium.mk @@ -28,6 +28,7 @@ endif # TODO, attempt upstream pdfium_patches += ofz451333752.patch +pdfium_patches += extractpatterns.patch $(eval $(call gb_UnpackedTarball_UnpackedTarball,pdfium)) diff --git a/external/pdfium/extractpatterns.patch b/external/pdfium/extractpatterns.patch new file mode 100644 index 000000000000..377ab0b0472a --- /dev/null +++ b/external/pdfium/extractpatterns.patch @@ -0,0 +1,183 @@ +--- core/fpdfapi/page/cpdf_pattern.h 2025-10-17 15:44:08.393532043 +0100 ++++ core/fpdfapi/page/cpdf_pattern.h 2025-10-17 15:44:12.020459390 +0100 +@@ -26,6 +26,7 @@ + virtual CPDF_ShadingPattern* AsShadingPattern(); + + const CFX_Matrix& pattern_to_form() const { return pattern_to_form_; } ++ RetainPtr<CPDF_Object> pattern_obj() const { return pattern_obj_; } + + protected: + CPDF_Pattern(CPDF_Document* pDoc, +@@ -35,7 +36,6 @@ + + // All the getters that return pointers return non-NULL pointers. + CPDF_Document* document() const { return document_; } +- RetainPtr<CPDF_Object> pattern_obj() const { return pattern_obj_; } + const CFX_Matrix& parent_matrix() const { return parent_matrix_; } + + void SetPatternToFormMatrix(); +--- fpdfsdk/fpdf_edittext.cpp 2025-10-17 15:44:08.410532151 +0100 ++++ fpdfsdk/fpdf_edittext.cpp 2025-10-17 15:55:18.804847559 +0100 +@@ -13,6 +13,9 @@ + #include "core/fpdfapi/font/cpdf_cidfont.h" + #include "core/fpdfapi/font/cpdf_font.h" + #include "core/fpdfapi/page/cpdf_docpagedata.h" ++#include "core/fpdfapi/page/cpdf_form.h" ++#include "core/fpdfapi/page/cpdf_pathobject.h" ++#include "core/fpdfapi/page/cpdf_tilingpattern.h" + #include "core/fpdfapi/page/cpdf_textobject.h" + #include "core/fpdfapi/page/cpdf_textstate.h" + #include "core/fpdfapi/parser/cpdf_array.h" +@@ -834,6 +835,108 @@ + return FPDFBitmapFromCFXDIBitmap(result_bitmap.Leak()); + } + ++FPDF_BITMAP ++FPDFPageObj_GetRenderedPattern(CPDF_Pattern& pattern, ++ CPDF_Document& doc, ++ CPDF_PageObject& pageObj, ++ CPDF_Page* optional_page) { ++ CPDF_TilingPattern* pTilingPattern = pattern.AsTilingPattern(); ++ if (!pTilingPattern) ++ return nullptr; ++ const std::unique_ptr<CPDF_Form> pPatternForm = pTilingPattern->Load(&pageObj); ++ if (!pPatternForm) ++ return nullptr; ++ RetainPtr<const CPDF_Dictionary> pDict = pattern.pattern_obj()->GetDict(); ++ CFX_FloatRect pattern_bbox = pDict->GetRectFor("BBox"); ++ ++ // `rect` has to use integer values. Round up as needed. ++ const FX_RECT rect = pattern_bbox.GetOuterRect(); ++ if (rect.IsEmpty()) ++ return nullptr; ++ ++ auto result_bitmap = pdfium::MakeRetain<CFX_DIBitmap>(); ++ if (!result_bitmap->Create(rect.Width(), rect.Height(), ++ FXDIB_Format::kBgra)) { ++ return nullptr; ++ } ++ CFX_FloatRect cell_bbox = pattern.pattern_to_form().TransformRect(pattern_bbox); ++ CFX_FloatRect bitmap_rect(0.0f, 0.0f, rect.Width(), rect.Height()); ++ CFX_Matrix mtPattern2Bitmap; ++ mtPattern2Bitmap.MatchRect(bitmap_rect, cell_bbox); ++ ++ CFX_DefaultRenderDevice bitmap_device; ++ bitmap_device.AttachWithBackdropAndGroupKnockout( ++ result_bitmap, /*pBackdropBitmap=*/nullptr, /*bGroupKnockout=*/true); ++ ++ CPDF_RenderOptions options; ++ if (!pTilingPattern->colored()) ++ options.SetColorMode(CPDF_RenderOptions::kAlpha); ++ options.GetOptions().bForceHalftone = true; ++ ++ CPDF_RenderContext context(&doc, nullptr, nullptr); ++ context.AppendLayer(pPatternForm.get(), mtPattern2Bitmap); ++ context.Render(&bitmap_device, nullptr, &options, nullptr); ++ ++ CHECK(!result_bitmap->IsPremultiplied()); ++ ++ // Caller takes ownership. ++ return FPDFBitmapFromCFXDIBitmap(result_bitmap.Leak()); ++} ++ ++FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV ++FPDFPageObj_GetRenderedStrokePattern(FPDF_DOCUMENT document, ++ FPDF_PAGE page, ++ FPDF_PAGEOBJECT page_object) { ++ CPDF_Document* doc = CPDFDocumentFromFPDFDocument(document); ++ if (!doc) ++ return nullptr; ++ ++ CPDF_Page* optional_page = CPDFPageFromFPDFPage(page); ++ if (optional_page && optional_page->GetDocument() != doc) ++ return nullptr; ++ ++ CPDF_PageObject* object = CPDFPageObjectFromFPDFPageObject(page_object); ++ if (!object) ++ return nullptr; ++ ++ const CPDF_Color* stroke = object->color_state().GetStrokeColor(); ++ if (!stroke || !stroke->IsPattern()) ++ return nullptr; ++ ++ RetainPtr<CPDF_Pattern> pattern = stroke->GetPattern(); ++ if (!pattern) ++ return nullptr; ++ ++ return FPDFPageObj_GetRenderedPattern(*pattern, *doc, *object, optional_page); ++} ++ ++FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV ++FPDFPageObj_GetRenderedFillPattern(FPDF_DOCUMENT document, ++ FPDF_PAGE page, ++ FPDF_PAGEOBJECT page_object) { ++ CPDF_Document* doc = CPDFDocumentFromFPDFDocument(document); ++ if (!doc) ++ return nullptr; ++ ++ CPDF_Page* optional_page = CPDFPageFromFPDFPage(page); ++ if (optional_page && optional_page->GetDocument() != doc) ++ return nullptr; ++ ++ CPDF_PageObject* object = CPDFPageObjectFromFPDFPageObject(page_object); ++ if (!object) ++ return nullptr; ++ ++ const CPDF_Color* fill = object->color_state().GetFillColor(); ++ if (!fill || !fill->IsPattern()) ++ return nullptr; ++ ++ RetainPtr<CPDF_Pattern> pattern = fill->GetPattern(); ++ if (!pattern) ++ return nullptr; ++ ++ return FPDFPageObj_GetRenderedPattern(*pattern, *doc, *object, optional_page); ++} ++ + FPDF_EXPORT void FPDF_CALLCONV FPDFFont_Close(FPDF_FONT font) { + // Take back ownership from caller and release. + RetainPtr<CPDF_Font>().Unleak(CPDFFontFromFPDFFont(font)); +--- public/fpdf_edit.h 2025-10-17 15:44:08.415888612 +0100 ++++ public/fpdf_edit.h 2025-10-17 15:44:12.022555158 +0100 +@@ -1417,6 +1417,41 @@ + float scale); + + // Experimental API. ++// Get a bitmap rasterization of the stroke pattern of |page object|. ++// To render correctly, the caller must provide the |document| associated with ++// |page_object|. If there is a |page| associated with |page_object|, the ++// caller should provide that as well. The returned bitmap will be owned by ++// the caller, and FPDFBitmap_Destroy() must be called on the returned bitmap ++// when it is no longer needed. ++// ++// document - handle to a document associated with |page_object|. ++// page - handle to an optional page associated with |page_object|. ++// page_object - handle to a page object. ++// ++// Returns the bitmap or NULL if the stroke is not a pattern or on failure. ++FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV ++FPDFPageObj_GetRenderedStrokePattern(FPDF_DOCUMENT document, ++ FPDF_PAGE page, ++ FPDF_PAGEOBJECT page_object); ++ ++// Get a bitmap rasterization of the fill pattern of |page object|. ++// To render correctly, the caller must provide the |document| associated with ++// |page_object|. If there is a |page| associated with |page_object|, the ++// caller should provide that as well. The returned bitmap will be owned by ++// the caller, and FPDFBitmap_Destroy() must be called on the returned bitmap ++// when it is no longer needed. ++// ++// document - handle to a document associated with |page_object|. ++// page - handle to an optional page associated with |page_object|. ++// page_object - handle to a page object. ++// ++// Returns the bitmap or NULL if the fill is not a pattern or on failure. ++FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV ++FPDFPageObj_GetRenderedFillPattern(FPDF_DOCUMENT document, ++ FPDF_PAGE page, ++ FPDF_PAGEOBJECT page_object); ++ ++// Experimental API. + // Get the font of a text object. + // + // text - the handle to the text object. diff --git a/include/vcl/filter/PDFiumLibrary.hxx b/include/vcl/filter/PDFiumLibrary.hxx index 699ef04ba2e9..36bfa8743ac7 100644 --- a/include/vcl/filter/PDFiumLibrary.hxx +++ b/include/vcl/filter/PDFiumLibrary.hxx @@ -165,8 +165,14 @@ public: virtual bool getFontProperties(FontWeight& weight) = 0; virtual PDFTextRenderMode getTextRenderMode() = 0; virtual Color getFillColor() = 0; + virtual std::unique_ptr<PDFiumBitmap> getRenderedFillPattern(PDFiumDocument& rDoc, + PDFiumPage& rPage) + = 0; virtual Color getStrokeColor() = 0; virtual double getStrokeWidth() = 0; + virtual std::unique_ptr<PDFiumBitmap> getRenderedStrokePattern(PDFiumDocument& rDoc, + PDFiumPage& rPage) + = 0; // Path virtual int getPathSegmentCount() = 0; virtual std::unique_ptr<PDFiumPathSegment> getPathSegment(int index) = 0; diff --git a/sd/qa/unit/data/pdf/pattern-fill.pdf b/sd/qa/unit/data/pdf/pattern-fill.pdf new file mode 100644 index 000000000000..d5c4f0acebf9 Binary files /dev/null and b/sd/qa/unit/data/pdf/pattern-fill.pdf differ diff --git a/sd/qa/unit/data/pdf/pattern-stroke.pdf b/sd/qa/unit/data/pdf/pattern-stroke.pdf new file mode 100644 index 000000000000..ce26b39e295c Binary files /dev/null and b/sd/qa/unit/data/pdf/pattern-stroke.pdf differ diff --git a/sd/qa/unit/export-tests.cxx b/sd/qa/unit/export-tests.cxx index 66d9e1cfbc57..1eb3b00f6adb 100644 --- a/sd/qa/unit/export-tests.cxx +++ b/sd/qa/unit/export-tests.cxx @@ -1235,6 +1235,48 @@ CPPUNIT_TEST_FIXTURE(SdExportTest, testExplodedPdfEmbeddedFonts) #endif } +CPPUNIT_TEST_FIXTURE(SdExportTest, testExplodedPdfPatternStroke) +{ + auto pPdfium = vcl::pdf::PDFiumLibrary::get(); + if (!pPdfium) + return; + UsePdfium aGuard; + + loadFromFile(u"pdf/pattern-stroke.pdf"); + + setFilterOptions("{\"DecomposePDF\":{\"type\":\"boolean\",\"value\":\"true\"}}"); + save(u"OpenDocument Drawing Flat XML"_ustr); + + xmlDocUniquePtr pXmlDoc = parseExportedFile(); + + // ensure the stroke color is this redish color, and not gray which is what it + // defaults to if the stroke pattern isn't taken into account. + assertXPath(pXmlDoc, "/office:document/office:automatic-styles/style:style[@style:name='gr1']/" + "style:graphic-properties[@svg:stroke-color='#ed1b2d']"); +} + +CPPUNIT_TEST_FIXTURE(SdExportTest, testExplodedPdfPatternFill) +{ + auto pPdfium = vcl::pdf::PDFiumLibrary::get(); + if (!pPdfium) + return; + UsePdfium aGuard; + + loadFromFile(u"pdf/pattern-fill.pdf"); + + setFilterOptions("{\"DecomposePDF\":{\"type\":\"boolean\",\"value\":\"true\"}}"); + save(u"OpenDocument Drawing Flat XML"_ustr); + + xmlDocUniquePtr pXmlDoc = parseExportedFile(); + + // ensure the stroke color is this redish color, and not gray which is what it + // defaults to if the stroke pattern isn't taken into account. + assertXPath(pXmlDoc, "/office:document/office:automatic-styles/style:style[@style:name='gr1']/" + "style:graphic-properties[@style:repeat='repeat' and " + "@draw:fill-image-width='1.27cm' and @draw:fill-image-height='1.27cm' and " + "@draw:fill-image-name='Bitmap_20_1']"); +} + CPPUNIT_TEST_FIXTURE(SdExportTest, testEmbeddedText) { createSdDrawDoc("objectwithtext.fodg"); diff --git a/svx/source/inc/svdpdf.hxx b/svx/source/inc/svdpdf.hxx index f215c1f05938..c6a331adf044 100644 --- a/svx/source/inc/svdpdf.hxx +++ b/svx/source/inc/svdpdf.hxx @@ -107,6 +107,8 @@ class ImpSdrPdfImport final sal_Int32 mnLineWidth; static constexpr css::drawing::LineCap gaLineCap = css::drawing::LineCap_BUTT; XDash maDash; + std::optional<Color> moFillColor; + std::optional<Bitmap> moFillPattern; bool mbMov; bool mbSize; @@ -145,17 +147,25 @@ class ImpSdrPdfImport final void checkClip(); bool isClip() const; + Color getStrokeColor(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage); + Color getFillColor(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage); + void ImportPdfObject(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage, std::unique_ptr<vcl::pdf::PDFiumTextPage> const& pTextPage, int nPageObjectIndex); void ImportForm(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage, std::unique_ptr<vcl::pdf::PDFiumTextPage> const& pTextPage, int nPageObjectIndex); void ImportImage(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, int nPageObjectIndex); void ImportPath(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, - int nPageObjectIndex); + std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage, int nPageObjectIndex); void ImportText(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage, std::unique_ptr<vcl::pdf::PDFiumTextPage> const& pTextPage, int nPageObjectIndex); void InsertTextObject(const Point& rPos, const Size& rSize, const OUString& rStr, diff --git a/svx/source/svdraw/svdpdf.cxx b/svx/source/svdraw/svdpdf.cxx index 65e108e9fd58..0b203f8d6a00 100644 --- a/svx/source/svdraw/svdpdf.cxx +++ b/svx/source/svdraw/svdpdf.cxx @@ -31,6 +31,7 @@ #include <editeng/udlnitem.hxx> #include <editeng/crossedoutitem.hxx> #include <editeng/shdditem.hxx> +#include <svx/xflbmsxy.hxx> #include <svx/xlnclit.hxx> #include <svx/xlncapit.hxx> #include <svx/xlnwtit.hxx> @@ -125,7 +126,6 @@ ImpSdrPdfImport::ImpSdrPdfImport(SdrModel& rModel, SdrLayerID nLay, const tools: mpVD->SetMapMode(MapMode(MapUnit::Map100thMM)); mpVD->EnableOutput(false); mpVD->SetLineColor(); - mpVD->SetFillColor(); // Get TextBounds relative to baseline vcl::Font aFnt = mpVD->GetFont(); @@ -379,7 +379,7 @@ void ImpSdrPdfImport::DoObjects(SvdProgressInfo* pProgrInfo, sal_uInt32* pAction for (int nPageObjectIndex = 0; nPageObjectIndex < nPageObjectCount; ++nPageObjectIndex) { auto pPageObject = pPdfPage->getObject(nPageObjectIndex); - ImportPdfObject(pPageObject, pTextPage, nPageObjectIndex); + ImportPdfObject(pPageObject, pPdfPage, pTextPage, nPageObjectIndex); if (pProgrInfo && pActionsToReport) { (*pActionsToReport)++; @@ -552,10 +552,29 @@ void ImpSdrPdfImport::SetAttributes(SdrObject* pObj, bool bForceTextAttr) if (bFill) { - if (mpVD->IsFillColor()) + bool doFill + = mpVD->GetDrawMode() != DrawModeFlags::NoFill && (moFillColor || moFillPattern); + if (doFill && moFillColor) + doFill = !moFillColor->IsTransparent(); + if (doFill) { - mpFillAttr->Put(XFillStyleItem(drawing::FillStyle_SOLID)); - mpFillAttr->Put(XFillColorItem(OUString(), mpVD->GetFillColor())); + if (moFillColor) + { + mpFillAttr->Put(XFillStyleItem(drawing::FillStyle_SOLID)); + mpFillAttr->Put(XFillColorItem(OUString(), *moFillColor)); + } + else + { + assert(moFillPattern && "pattern should exist"); + mpFillAttr->Put(XFillStyleItem(drawing::FillStyle_BITMAP)); + mpFillAttr->Put(XFillBitmapItem(OUString(), Graphic(*moFillPattern))); + mpFillAttr->Put(XFillBmpStretchItem(false)); + mpFillAttr->Put(XFillBmpTileItem(true)); + mpFillAttr->Put( + XFillBmpSizeXItem(convertPointToMm100(moFillPattern->GetSizePixel().Width()))); + mpFillAttr->Put( + XFillBmpSizeYItem(convertPointToMm100(moFillPattern->GetSizePixel().Height()))); + } } else { @@ -877,6 +896,7 @@ void ImpSdrPdfImport::checkClip() bool ImpSdrPdfImport::isClip() const { return !maClip.getB2DRange().isEmpty(); } void ImpSdrPdfImport::ImportPdfObject( std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage, std::unique_ptr<vcl::pdf::PDFiumTextPage> const& pTextPage, int nPageObjectIndex) { if (!pPageObject) @@ -886,10 +906,10 @@ void ImpSdrPdfImport::ImportPdfObject( switch (ePageObjectType) { case vcl::pdf::PDFPageObjectType::Text: - ImportText(pPageObject, pTextPage, nPageObjectIndex); + ImportText(pPageObject, pPage, pTextPage, nPageObjectIndex); break; case vcl::pdf::PDFPageObjectType::Path: - ImportPath(pPageObject, nPageObjectIndex); + ImportPath(pPageObject, pPage, nPageObjectIndex); break; case vcl::pdf::PDFPageObjectType::Image: ImportImage(pPageObject, nPageObjectIndex); @@ -898,7 +918,7 @@ void ImpSdrPdfImport::ImportPdfObject( SAL_WARN("sd.filter", "Got page object SHADING: " << nPageObjectIndex); break; case vcl::pdf::PDFPageObjectType::Form: - ImportForm(pPageObject, pTextPage, nPageObjectIndex); + ImportForm(pPageObject, pPage, pTextPage, nPageObjectIndex); break; default: SAL_WARN("sd.filter", "Unknown PDF page object #" << nPageObjectIndex << " of type: " @@ -908,6 +928,7 @@ void ImpSdrPdfImport::ImportPdfObject( } void ImpSdrPdfImport::ImportForm(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage, std::unique_ptr<vcl::pdf::PDFiumTextPage> const& pTextPage, int /*nPageObjectIndex*/) { @@ -921,7 +942,7 @@ void ImpSdrPdfImport::ImportForm(std::unique_ptr<vcl::pdf::PDFiumPageObject> con { auto pFormObject = pPageObject->getFormObject(nIndex); - ImportPdfObject(pFormObject, pTextPage, -1); + ImportPdfObject(pFormObject, pPage, pTextPage, -1); } // Restore the old one. @@ -1753,7 +1774,39 @@ EmbeddedFontInfo ImpSdrPdfImport::convertToOTF(sal_Int64 prefix, SubSetInfo& rSu return EmbeddedFontInfo(); } +// There isn't, as far as I know, a way to stroke with a pattern at the moment, +// so extract some sensible color if this is a stroke pattern +Color ImpSdrPdfImport::getStrokeColor( + std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage) +{ + if (std::unique_ptr<vcl::pdf::PDFiumBitmap> bitmap + = pPageObject->getRenderedStrokePattern(*mpPdfDocument, *pPage)) + { + Bitmap aBitmap(bitmap->createBitmapFromBuffer()); + return aBitmap.GetPixelColor(aBitmap.GetSizePixel().Width() / 2, + aBitmap.GetSizePixel().Height() / 2); + } + return pPageObject->getStrokeColor(); +} + +// Typically for a fill pattern you want to use some pattern fill equivalent +// but if that's not possible then this fallback can be useful +Color ImpSdrPdfImport::getFillColor(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage) +{ + if (std::unique_ptr<vcl::pdf::PDFiumBitmap> bitmap + = pPageObject->getRenderedFillPattern(*mpPdfDocument, *pPage)) + { + Bitmap aBitmap(bitmap->createBitmapFromBuffer()); + return aBitmap.GetPixelColor(aBitmap.GetSizePixel().Width() / 2, + aBitmap.GetSizePixel().Height() / 2); + } + return pPageObject->getFillColor(); +} + void ImpSdrPdfImport::ImportText(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage, std::unique_ptr<vcl::pdf::PDFiumTextPage> const& pTextPage, int /*nPageObjectIndex*/) { @@ -1843,7 +1896,8 @@ void ImpSdrPdfImport::ImportText(std::unique_ptr<vcl::pdf::PDFiumPageObject> con } if (bUse) { - Color aColor = bFill ? pPageObject->getFillColor() : pPageObject->getStrokeColor(); + Color aColor + = bFill ? getFillColor(pPageObject, pPage) : getStrokeColor(pPageObject, pPage); if (aColor != COL_TRANSPARENT) aTextColor = aColor.GetRGBColor(); } @@ -1979,6 +2033,7 @@ void ImpSdrPdfImport::ImportImage(std::unique_ptr<vcl::pdf::PDFiumPageObject> co } void ImpSdrPdfImport::ImportPath(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage, int /*nPageObjectIndex*/) { auto aPathMatrix = pPageObject->getMatrix(); @@ -2074,12 +2129,20 @@ void ImpSdrPdfImport::ImportPath(std::unique_ptr<vcl::pdf::PDFiumPageObject> con mpVD->SetDrawMode(DrawModeFlags::NoFill); } - mpVD->SetFillColor(pPageObject->getFillColor()); - - if (bStroke) + if (std::unique_ptr<vcl::pdf::PDFiumBitmap> bitmap + = pPageObject->getRenderedFillPattern(*mpPdfDocument, *pPage)) { - mpVD->SetLineColor(pPageObject->getStrokeColor()); + moFillPattern = bitmap->createBitmapFromBuffer(); + moFillColor.reset(); } + else + { + moFillColor = pPageObject->getFillColor(); + moFillPattern.reset(); + } + + if (bStroke) + mpVD->SetLineColor(getStrokeColor(pPageObject, pPage)); else mpVD->SetLineColor(COL_TRANSPARENT); diff --git a/vcl/source/pdf/PDFiumLibrary.cxx b/vcl/source/pdf/PDFiumLibrary.cxx index f1677461f17b..1a60815ccf98 100644 --- a/vcl/source/pdf/PDFiumLibrary.cxx +++ b/vcl/source/pdf/PDFiumLibrary.cxx @@ -425,8 +425,12 @@ public: bool getFontProperties(FontWeight& weight) override; PDFTextRenderMode getTextRenderMode() override; Color getFillColor() override; + std::unique_ptr<PDFiumBitmap> getRenderedFillPattern(PDFiumDocument& rDoc, + PDFiumPage& rPage) override; Color getStrokeColor() override; double getStrokeWidth() override; + std::unique_ptr<PDFiumBitmap> getRenderedStrokePattern(PDFiumDocument& rDoc, + PDFiumPage& rPage) override; // Path int getPathSegmentCount() override; std::unique_ptr<PDFiumPathSegment> getPathSegment(int index) override; @@ -1375,6 +1379,36 @@ std::unique_ptr<PDFiumBitmap> PDFiumPageObjectImpl::getImageBitmap() return pPDFiumBitmap; } +std::unique_ptr<PDFiumBitmap> PDFiumPageObjectImpl::getRenderedStrokePattern(PDFiumDocument& rDoc, + PDFiumPage& rPage) +{ + auto& rDocImpl = static_cast<PDFiumDocumentImpl&>(rDoc); + auto& rPageImpl = static_cast<PDFiumPageImpl&>(rPage); + std::unique_ptr<PDFiumBitmap> pPDFiumBitmap; + FPDF_BITMAP pBitmap = FPDFPageObj_GetRenderedStrokePattern( + rDocImpl.getPointer(), rPageImpl.getPointer(), mpPageObject); + if (pBitmap) + { + pPDFiumBitmap = std::make_unique<PDFiumBitmapImpl>(pBitmap); + } + return pPDFiumBitmap; +} + +std::unique_ptr<PDFiumBitmap> PDFiumPageObjectImpl::getRenderedFillPattern(PDFiumDocument& rDoc, + PDFiumPage& rPage) +{ + auto& rDocImpl = static_cast<PDFiumDocumentImpl&>(rDoc); + auto& rPageImpl = static_cast<PDFiumPageImpl&>(rPage); + std::unique_ptr<PDFiumBitmap> pPDFiumBitmap; + FPDF_BITMAP pBitmap = FPDFPageObj_GetRenderedFillPattern(rDocImpl.getPointer(), + rPageImpl.getPointer(), mpPageObject); + if (pBitmap) + { + pPDFiumBitmap = std::make_unique<PDFiumBitmapImpl>(pBitmap); + } + return pPDFiumBitmap; +} + bool PDFiumPageObjectImpl::getDrawMode(PDFFillMode& rFillMode, bool& rStroke) { auto nFillMode = static_cast<int>(rFillMode);
