summaryrefslogtreecommitdiffstats
path: root/vcl/source/gdi
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/source/gdi')
-rw-r--r--vcl/source/gdi/CommonSalLayout.cxx850
-rw-r--r--vcl/source/gdi/FileDefinitionWidgetDraw.cxx1130
-rw-r--r--vcl/source/gdi/TypeSerializer.cxx483
-rw-r--r--vcl/source/gdi/WidgetDefinition.cxx186
-rw-r--r--vcl/source/gdi/WidgetDefinitionReader.cxx495
-rw-r--r--vcl/source/gdi/configsettings.cxx135
-rw-r--r--vcl/source/gdi/cvtgrf.cxx69
-rw-r--r--vcl/source/gdi/embeddedfontshelper.cxx363
-rw-r--r--vcl/source/gdi/extoutdevdata.cxx27
-rw-r--r--vcl/source/gdi/formpdfexport.cxx824
-rw-r--r--vcl/source/gdi/gdimetafiletools.cxx1086
-rw-r--r--vcl/source/gdi/gdimtf.cxx2357
-rw-r--r--vcl/source/gdi/gfxlink.cxx173
-rw-r--r--vcl/source/gdi/gradient.cxx675
-rw-r--r--vcl/source/gdi/graph.cxx560
-rw-r--r--vcl/source/gdi/graphictools.cxx289
-rw-r--r--vcl/source/gdi/hatch.cxx114
-rw-r--r--vcl/source/gdi/impglyphitem.cxx555
-rw-r--r--vcl/source/gdi/impgraph.cxx1775
-rw-r--r--vcl/source/gdi/jobset.cxx421
-rw-r--r--vcl/source/gdi/lineinfo.cxx293
-rw-r--r--vcl/source/gdi/mapmod.cxx191
-rw-r--r--vcl/source/gdi/metaact.cxx2208
-rw-r--r--vcl/source/gdi/mtfxmldump.cxx1568
-rw-r--r--vcl/source/gdi/oldprintadaptor.cxx109
-rw-r--r--vcl/source/gdi/pdfbuildin_fonts.cxx760
-rw-r--r--vcl/source/gdi/pdfextoutdevdata.cxx919
-rw-r--r--vcl/source/gdi/pdfobjectcopier.cxx346
-rw-r--r--vcl/source/gdi/pdfwriter.cxx485
-rw-r--r--vcl/source/gdi/pdfwriter_impl.cxx11934
-rw-r--r--vcl/source/gdi/pdfwriter_impl2.cxx2047
-rw-r--r--vcl/source/gdi/print.cxx1676
-rw-r--r--vcl/source/gdi/print2.cxx70
-rw-r--r--vcl/source/gdi/print3.cxx2156
-rw-r--r--vcl/source/gdi/regband.cxx886
-rw-r--r--vcl/source/gdi/region.cxx1793
-rw-r--r--vcl/source/gdi/regionband.cxx1365
-rw-r--r--vcl/source/gdi/salgdiimpl.cxx24
-rw-r--r--vcl/source/gdi/salgdilayout.cxx940
-rw-r--r--vcl/source/gdi/sallayout.cxx1182
-rw-r--r--vcl/source/gdi/salmisc.cxx433
-rw-r--r--vcl/source/gdi/scrptrun.cxx259
-rw-r--r--vcl/source/gdi/vectorgraphicdata.cxx357
-rw-r--r--vcl/source/gdi/virdev.cxx521
-rw-r--r--vcl/source/gdi/wall.cxx271
45 files changed, 45360 insertions, 0 deletions
diff --git a/vcl/source/gdi/CommonSalLayout.cxx b/vcl/source/gdi/CommonSalLayout.cxx
new file mode 100644
index 0000000000..bcf6f54639
--- /dev/null
+++ b/vcl/source/gdi/CommonSalLayout.cxx
@@ -0,0 +1,850 @@
+/* -*- 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 <sal/config.h>
+
+#include <sal/log.hxx>
+#include <unotools/configmgr.hxx>
+#include <o3tl/temporary.hxx>
+
+#include <vcl/unohelp.hxx>
+#include <vcl/font/Feature.hxx>
+#include <vcl/font/FeatureParser.hxx>
+#include <vcl/svapp.hxx>
+
+#include <ImplLayoutArgs.hxx>
+#include <TextLayoutCache.hxx>
+#include <font/FontSelectPattern.hxx>
+#include <salgdi.hxx>
+#include <sallayout.hxx>
+
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+
+#include <unicode/uchar.h>
+#include <hb-ot.h>
+#include <hb-graphite2.h>
+#include <hb-icu.h>
+
+#include <map>
+#include <memory>
+
+GenericSalLayout::GenericSalLayout(LogicalFontInstance &rFont)
+ : m_GlyphItems(rFont)
+ , mpVertGlyphs(nullptr)
+ , mbFuzzing(utl::ConfigManager::IsFuzzing())
+{
+}
+
+GenericSalLayout::~GenericSalLayout()
+{
+ if (mpVertGlyphs)
+ hb_set_destroy(mpVertGlyphs);
+}
+
+void GenericSalLayout::ParseFeatures(std::u16string_view aName)
+{
+ vcl::font::FeatureParser aParser(aName);
+ const OUString& sLanguage = aParser.getLanguage();
+ if (!sLanguage.isEmpty())
+ msLanguage = OUStringToOString(sLanguage, RTL_TEXTENCODING_ASCII_US);
+
+ for (auto const &rFeat : aParser.getFeatures())
+ {
+ hb_feature_t aFeature { rFeat.m_nTag, rFeat.m_nValue, rFeat.m_nStart, rFeat.m_nEnd };
+ maFeatures.push_back(aFeature);
+ }
+}
+
+namespace {
+
+struct SubRun
+{
+ int32_t mnMin;
+ int32_t mnEnd;
+ hb_script_t maScript;
+ hb_direction_t maDirection;
+};
+
+}
+
+namespace {
+ int32_t GetVerticalOrientation(sal_UCS4 cCh, const LanguageTag& rTag)
+ {
+ // Override orientation of fullwidth colon , semi-colon,
+ // and Bopomofo tonal marks.
+ if ((cCh == 0xff1a || cCh == 0xff1b
+ || cCh == 0x2ca || cCh == 0x2cb || cCh == 0x2c7 || cCh == 0x2d9)
+ && rTag.getLanguage() == "zh")
+ return U_VO_TRANSFORMED_UPRIGHT;
+
+ return u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION);
+ }
+} // namespace
+
+SalLayoutGlyphs GenericSalLayout::GetGlyphs() const
+{
+ SalLayoutGlyphs glyphs;
+ glyphs.AppendImpl(m_GlyphItems.clone());
+ return glyphs;
+}
+
+void GenericSalLayout::SetNeedFallback(vcl::text::ImplLayoutArgs& rArgs, sal_Int32 nCharPos, bool bRightToLeft)
+{
+ if (nCharPos < 0 || mbFuzzing)
+ return;
+
+ using namespace ::com::sun::star;
+
+ if (!mxBreak.is())
+ mxBreak = vcl::unohelper::CreateBreakIterator();
+
+ lang::Locale aLocale(rArgs.maLanguageTag.getLocale());
+
+ //if position nCharPos is missing in the font, grab the entire grapheme and
+ //mark all glyphs as missing so the whole thing is rendered with the same
+ //font
+ sal_Int32 nDone;
+ int nGraphemeEndPos =
+ mxBreak->nextCharacters(rArgs.mrStr, nCharPos, aLocale,
+ i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
+ // Safely advance nCharPos in case it is a non-BMP character.
+ rArgs.mrStr.iterateCodePoints(&nCharPos);
+ int nGraphemeStartPos =
+ mxBreak->previousCharacters(rArgs.mrStr, nCharPos, aLocale,
+ i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
+
+ // tdf#107612
+ // If the start of the fallback run is Mongolian character and the previous
+ // character is NNBSP, we want to include the NNBSP in the fallback since
+ // it has special uses in Mongolian and have to be in the same text run to
+ // work.
+ sal_Int32 nTempPos = nGraphemeStartPos;
+ if (nGraphemeStartPos > 0)
+ {
+ auto nCurrChar = rArgs.mrStr.iterateCodePoints(&nTempPos, 0);
+ auto nPrevChar = rArgs.mrStr.iterateCodePoints(&nTempPos, -1);
+ if (nPrevChar == 0x202F
+ && u_getIntPropertyValue(nCurrChar, UCHAR_SCRIPT) == USCRIPT_MONGOLIAN)
+ nGraphemeStartPos = nTempPos;
+ }
+
+ //stay inside the Layout range (e.g. with tdf124116-1.odt)
+ nGraphemeStartPos = std::max(rArgs.mnMinCharPos, nGraphemeStartPos);
+ nGraphemeEndPos = std::min(rArgs.mnEndCharPos, nGraphemeEndPos);
+
+ rArgs.AddFallbackRun(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
+}
+
+void GenericSalLayout::AdjustLayout(vcl::text::ImplLayoutArgs& rArgs)
+{
+ SalLayout::AdjustLayout(rArgs);
+
+ if (rArgs.mpDXArray)
+ ApplyDXArray(rArgs.mpDXArray, rArgs.mpKashidaArray);
+ else if (rArgs.mnLayoutWidth)
+ Justify(rArgs.mnLayoutWidth);
+ // apply asian kerning if the glyphs are not already formatted
+ else if ((rArgs.mnFlags & SalLayoutFlags::KerningAsian)
+ && !(rArgs.mnFlags & SalLayoutFlags::Vertical))
+ ApplyAsianKerning(rArgs.mrStr);
+}
+
+void GenericSalLayout::DrawText(SalGraphics& rSalGraphics) const
+{
+ //call platform dependent DrawText functions
+ rSalGraphics.DrawTextLayout( *this );
+}
+
+// Find if the nominal glyph of the character is an input to “vert” feature.
+// We don’t check for a specific script or language as it shouldn’t matter
+// here; if the glyph would be the result from applying “vert” for any
+// script/language then we want to always treat it as upright glyph.
+bool GenericSalLayout::HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aVariationSelector)
+{
+ sal_GlyphId nGlyphIndex = GetFont().GetGlyphIndex(aChar, aVariationSelector);
+ if (!nGlyphIndex)
+ return false;
+
+ if (!mpVertGlyphs)
+ {
+ hb_face_t* pHbFace = hb_font_get_face(GetFont().GetHbFont());
+ mpVertGlyphs = hb_set_create();
+
+ // Find all GSUB lookups for “vert” feature.
+ hb_set_t* pLookups = hb_set_create();
+ hb_tag_t const pFeatures[] = { HB_TAG('v','e','r','t'), HB_TAG_NONE };
+ hb_ot_layout_collect_lookups(pHbFace, HB_OT_TAG_GSUB, nullptr, nullptr, pFeatures, pLookups);
+ if (!hb_set_is_empty(pLookups))
+ {
+ // Find the input glyphs in each lookup (i.e. the glyphs that
+ // this lookup applies to).
+ hb_codepoint_t nIdx = HB_SET_VALUE_INVALID;
+ while (hb_set_next(pLookups, &nIdx))
+ {
+ hb_set_t* pGlyphs = hb_set_create();
+ hb_ot_layout_lookup_collect_glyphs(pHbFace, HB_OT_TAG_GSUB, nIdx,
+ nullptr, // glyphs before
+ pGlyphs, // glyphs input
+ nullptr, // glyphs after
+ nullptr); // glyphs out
+ hb_set_union(mpVertGlyphs, pGlyphs);
+ }
+ }
+ hb_set_destroy(pLookups);
+ }
+
+ return hb_set_has(mpVertGlyphs, nGlyphIndex) != 0;
+}
+
+bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* pGlyphs)
+{
+ // No need to touch m_GlyphItems at all for an empty string.
+ if (rArgs.mnEndCharPos - rArgs.mnMinCharPos <= 0)
+ return true;
+
+ if (pGlyphs)
+ {
+ // Work with pre-computed glyph items.
+ m_GlyphItems = *pGlyphs;
+ for(const GlyphItem& item : m_GlyphItems)
+ if(!item.glyphId())
+ SetNeedFallback(rArgs, item.charPos(), item.IsRTLGlyph());
+ // Some flags are set as a side effect of text layout, restore them here.
+ rArgs.mnFlags |= pGlyphs->GetFlags();
+ return true;
+ }
+
+ hb_font_t *pHbFont = GetFont().GetHbFont();
+ bool isGraphite = GetFont().IsGraphiteFont();
+
+ int nGlyphCapacity = 2 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos);
+ m_GlyphItems.reserve(nGlyphCapacity);
+
+ const int nLength = rArgs.mrStr.getLength();
+ const sal_Unicode *pStr = rArgs.mrStr.getStr();
+
+ std::optional<vcl::text::TextLayoutCache> oNewScriptRun;
+ vcl::text::TextLayoutCache const* pTextLayout;
+ if (rArgs.m_pTextLayoutCache)
+ {
+ pTextLayout = rArgs.m_pTextLayoutCache; // use cache!
+ }
+ else
+ {
+ oNewScriptRun.emplace(pStr, rArgs.mnEndCharPos);
+ pTextLayout = &*oNewScriptRun;
+ }
+
+ // nBaseOffset is used to align vertical text to the center of rotated
+ // horizontal text. That is the offset from original baseline to
+ // the center of EM box. Maybe we can use OpenType base table to improve this
+ // in the future.
+ double nBaseOffset = 0;
+ if (rArgs.mnFlags & SalLayoutFlags::Vertical)
+ {
+ hb_font_extents_t extents;
+ if (hb_font_get_h_extents(pHbFont, &extents))
+ nBaseOffset = ( extents.ascender + extents.descender ) / 2.0;
+ }
+
+ hb_buffer_t* pHbBuffer = hb_buffer_create();
+ hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity);
+
+ const vcl::font::FontSelectPattern& rFontSelData = GetFont().GetFontSelectPattern();
+ if (rArgs.mnFlags & SalLayoutFlags::DisableKerning)
+ {
+ SAL_INFO("vcl.harfbuzz", "Disabling kerning for font: " << rFontSelData.maTargetName);
+ maFeatures.push_back({ HB_TAG('k','e','r','n'), 0, 0, static_cast<unsigned int>(-1) });
+ }
+
+ if (rArgs.mnFlags & SalLayoutFlags::DisableLigatures)
+ {
+ SAL_INFO("vcl.harfbuzz", "Disabling ligatures for font: " << rFontSelData.maTargetName);
+
+ // Both of these are optional ligatures, enabled by default but not for
+ // orthographically-required ligatures.
+ maFeatures.push_back({ HB_TAG('l','i','g','a'), 0, 0, static_cast<unsigned int>(-1) });
+ maFeatures.push_back({ HB_TAG('c','l','i','g'), 0, 0, static_cast<unsigned int>(-1) });
+ }
+
+ ParseFeatures(rFontSelData.maTargetName);
+
+ double nXScale = 0;
+ double nYScale = 0;
+ GetFont().GetScale(&nXScale, &nYScale);
+
+ basegfx::B2DPoint aCurrPos(0, 0);
+ while (true)
+ {
+ int nBidiMinRunPos, nBidiEndRunPos;
+ bool bRightToLeft;
+ if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft))
+ break;
+
+ // Find script subruns.
+ std::vector<SubRun> aSubRuns;
+ int nCurrentPos = nBidiMinRunPos;
+ size_t k = 0;
+ for (; k < pTextLayout->runs.size(); ++k)
+ {
+ vcl::text::Run const& rRun(pTextLayout->runs[k]);
+ if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd)
+ {
+ break;
+ }
+ }
+
+ if (isGraphite)
+ {
+ hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
+ aSubRuns.push_back({ nBidiMinRunPos, nBidiEndRunPos, aScript, bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR });
+ }
+ else
+ {
+ while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size())
+ {
+ int32_t nMinRunPos = nCurrentPos;
+ int32_t nEndRunPos = std::min(pTextLayout->runs[k].nEnd, nBidiEndRunPos);
+ hb_direction_t aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
+ hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
+ // For vertical text, further divide the runs based on character
+ // orientation.
+ if (rArgs.mnFlags & SalLayoutFlags::Vertical)
+ {
+ sal_Int32 nIdx = nMinRunPos;
+ while (nIdx < nEndRunPos)
+ {
+ sal_Int32 nPrevIdx = nIdx;
+ sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&nIdx);
+ int32_t aVo = GetVerticalOrientation(aChar, rArgs.maLanguageTag);
+
+ sal_UCS4 aVariationSelector = 0;
+ if (nIdx < nEndRunPos)
+ {
+ sal_Int32 nNextIdx = nIdx;
+ sal_UCS4 aNextChar = rArgs.mrStr.iterateCodePoints(&nNextIdx);
+ if (u_hasBinaryProperty(aNextChar, UCHAR_VARIATION_SELECTOR))
+ {
+ nIdx = nNextIdx;
+ aVariationSelector = aNextChar;
+ }
+ }
+
+ // Characters with U and Tu vertical orientation should
+ // be shaped in vertical direction. But characters
+ // with Tr should be shaped in vertical direction
+ // only if they have vertical alternates, otherwise
+ // they should be shaped in horizontal direction
+ // and then rotated.
+ // See http://unicode.org/reports/tr50/#vo
+ if (aVo == U_VO_UPRIGHT || aVo == U_VO_TRANSFORMED_UPRIGHT ||
+ (aVo == U_VO_TRANSFORMED_ROTATED &&
+ HasVerticalAlternate(aChar, aVariationSelector)))
+ {
+ aDirection = HB_DIRECTION_TTB;
+ }
+ else
+ {
+ aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
+ }
+
+ if (aSubRuns.empty() || aSubRuns.back().maDirection != aDirection || aSubRuns.back().maScript != aScript)
+ aSubRuns.push_back({ nPrevIdx, nIdx, aScript, aDirection });
+ else
+ aSubRuns.back().mnEnd = nIdx;
+ }
+ }
+ else
+ {
+ aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript, aDirection });
+ }
+
+ nCurrentPos = nEndRunPos;
+ ++k;
+ }
+ }
+
+ // RTL subruns should be reversed to ensure that final glyph order is
+ // correct.
+ if (bRightToLeft)
+ std::reverse(aSubRuns.begin(), aSubRuns.end());
+
+ for (const auto& aSubRun : aSubRuns)
+ {
+ hb_buffer_clear_contents(pHbBuffer);
+
+ const int nMinRunPos = aSubRun.mnMin;
+ const int nEndRunPos = aSubRun.mnEnd;
+ const int nRunLen = nEndRunPos - nMinRunPos;
+
+ int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
+
+ // Produce HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL that we use below.
+ nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL;
+
+ if (nMinRunPos == 0)
+ nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */
+ if (nEndRunPos == nLength)
+ nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */
+
+ hb_buffer_set_direction(pHbBuffer, aSubRun.maDirection);
+ hb_buffer_set_script(pHbBuffer, aSubRun.maScript);
+ if (!msLanguage.isEmpty())
+ {
+ hb_buffer_set_language(pHbBuffer, hb_language_from_string(msLanguage.getStr(), msLanguage.getLength()));
+ }
+ else
+ {
+ OString sLanguage = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US);
+ hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), sLanguage.getLength()));
+ }
+ hb_buffer_set_flags(pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags));
+ hb_buffer_add_utf16(
+ pHbBuffer, reinterpret_cast<uint16_t const *>(pStr), nLength,
+ nMinRunPos, nRunLen);
+
+ // The shapers that we want HarfBuzz to use, in the order of
+ // preference.
+ const char*const pHbShapers[] = { "graphite2", "ot", "fallback", nullptr };
+ bool ok = hb_shape_full(pHbFont, pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers);
+ assert(ok);
+ (void) ok;
+
+ int nRunGlyphCount = hb_buffer_get_length(pHbBuffer);
+ hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer, nullptr);
+ hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer, nullptr);
+
+ for (int i = 0; i < nRunGlyphCount; ++i) {
+ int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
+ int32_t nCharPos = pHbGlyphInfos[i].cluster;
+ int32_t nCharCount = 0;
+ bool bInCluster = false;
+ bool bClusterStart = false;
+
+ // Find the number of characters that make up this glyph.
+ if (!bRightToLeft)
+ {
+ // If the cluster is the same as previous glyph, then this
+ // already consumed, skip.
+ if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster)
+ {
+ nCharCount = 0;
+ bInCluster = true;
+ }
+ else
+ {
+ // Find the next glyph with a different cluster, or the
+ // end of text.
+ int j = i;
+ int32_t nNextCharPos = nCharPos;
+ while (nNextCharPos == nCharPos && j < nRunGlyphCount)
+ nNextCharPos = pHbGlyphInfos[j++].cluster;
+
+ if (nNextCharPos == nCharPos)
+ nNextCharPos = nEndRunPos;
+ nCharCount = nNextCharPos - nCharPos;
+ if ((i == 0 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i - 1].cluster) &&
+ (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster))
+ bClusterStart = true;
+ }
+ }
+ else
+ {
+ // If the cluster is the same as previous glyph, then this
+ // will be consumed later, skip.
+ if (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster)
+ {
+ nCharCount = 0;
+ bInCluster = true;
+ }
+ else
+ {
+ // Find the previous glyph with a different cluster, or
+ // the end of text.
+ int j = i;
+ int32_t nNextCharPos = nCharPos;
+ while (nNextCharPos == nCharPos && j >= 0)
+ nNextCharPos = pHbGlyphInfos[j--].cluster;
+
+ if (nNextCharPos == nCharPos)
+ nNextCharPos = nEndRunPos;
+ nCharCount = nNextCharPos - nCharPos;
+ if ((i == nRunGlyphCount - 1 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i + 1].cluster) &&
+ (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster))
+ bClusterStart = true;
+ }
+ }
+
+ // if needed request glyph fallback by updating LayoutArgs
+ if (!nGlyphIndex)
+ {
+ SetNeedFallback(rArgs, nCharPos, bRightToLeft);
+ if (SalLayoutFlags::ForFallback & rArgs.mnFlags)
+ continue;
+ }
+
+ GlyphItemFlags nGlyphFlags = GlyphItemFlags::NONE;
+ if (bRightToLeft)
+ nGlyphFlags |= GlyphItemFlags::IS_RTL_GLYPH;
+
+ if (bClusterStart)
+ nGlyphFlags |= GlyphItemFlags::IS_CLUSTER_START;
+
+ if (bInCluster)
+ nGlyphFlags |= GlyphItemFlags::IS_IN_CLUSTER;
+
+ sal_UCS4 aChar
+ = rArgs.mrStr.iterateCodePoints(&o3tl::temporary(sal_Int32(nCharPos)), 0);
+
+ if (u_isUWhiteSpace(aChar))
+ nGlyphFlags |= GlyphItemFlags::IS_SPACING;
+
+ if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & HB_GLYPH_FLAG_UNSAFE_TO_BREAK)
+ nGlyphFlags |= GlyphItemFlags::IS_UNSAFE_TO_BREAK;
+
+ if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL)
+ nGlyphFlags |= GlyphItemFlags::IS_SAFE_TO_INSERT_KASHIDA;
+
+ double nAdvance, nXOffset, nYOffset;
+ if (aSubRun.maDirection == HB_DIRECTION_TTB)
+ {
+ nGlyphFlags |= GlyphItemFlags::IS_VERTICAL;
+
+ nAdvance = -pHbPositions[i].y_advance;
+ nXOffset = -pHbPositions[i].y_offset;
+ nYOffset = -pHbPositions[i].x_offset - nBaseOffset;
+
+ if (GetFont().NeedOffsetCorrection(pHbPositions[i].y_offset))
+ {
+ // We need glyph's advance, top bearing, and height to
+ // correct y offset.
+ tools::Rectangle aRect;
+ // Get cached bound rect value for the font,
+ GetFont().GetGlyphBoundRect(nGlyphIndex, aRect, true);
+
+ nXOffset = -(aRect.Top() / nXScale + ( pHbPositions[i].y_advance
+ + ( aRect.GetHeight() / nXScale ) ) / 2.0 );
+ }
+
+ }
+ else
+ {
+ nAdvance = pHbPositions[i].x_advance;
+ nXOffset = pHbPositions[i].x_offset;
+ nYOffset = -pHbPositions[i].y_offset;
+ }
+
+ nAdvance = nAdvance * nXScale;
+ nXOffset = nXOffset * nXScale;
+ nYOffset = nYOffset * nYScale;
+ if (!GetSubpixelPositioning())
+ {
+ nAdvance = std::lround(nAdvance);
+ nXOffset = std::lround(nXOffset);
+ nYOffset = std::lround(nYOffset);
+ }
+
+ basegfx::B2DPoint aNewPos(aCurrPos.getX() + nXOffset, aCurrPos.getY() + nYOffset);
+ const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags,
+ nAdvance, nXOffset, nYOffset);
+ m_GlyphItems.push_back(aGI);
+
+ aCurrPos.adjustX(nAdvance);
+ }
+ }
+ }
+
+ hb_buffer_destroy(pHbBuffer);
+
+ // Some flags are set as a side effect of text layout, save them here.
+ if (rArgs.mnFlags & SalLayoutFlags::GlyphItemsOnly)
+ m_GlyphItems.SetFlags(rArgs.mnFlags);
+
+ return true;
+}
+
+void GenericSalLayout::GetCharWidths(std::vector<double>& rCharWidths, const OUString& rStr) const
+{
+ const int nCharCount = mnEndCharPos - mnMinCharPos;
+
+ rCharWidths.clear();
+ rCharWidths.resize(nCharCount, 0);
+
+ css::uno::Reference<css::i18n::XBreakIterator> xBreak;
+ auto aLocale(maLanguageTag.getLocale());
+
+ for (auto const& aGlyphItem : m_GlyphItems)
+ {
+ if (aGlyphItem.charPos() >= mnEndCharPos)
+ continue;
+
+ unsigned int nGraphemeCount = 0;
+ if (aGlyphItem.charCount() > 1 && aGlyphItem.newWidth() != 0 && !rStr.isEmpty())
+ {
+ // We are calculating DX array for cursor positions and this is a
+ // ligature, find out how many grapheme clusters are in it.
+ if (!xBreak.is())
+ xBreak = mxBreak.is() ? mxBreak : vcl::unohelper::CreateBreakIterator();
+
+ // Count grapheme clusters in the ligature.
+ sal_Int32 nDone;
+ sal_Int32 nPos = aGlyphItem.charPos();
+ while (nPos < aGlyphItem.charPos() + aGlyphItem.charCount())
+ {
+ nPos = xBreak->nextCharacters(rStr, nPos, aLocale,
+ css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
+ nGraphemeCount++;
+ }
+ }
+
+ if (nGraphemeCount > 1)
+ {
+ // More than one grapheme cluster, we want to distribute the glyph
+ // width over them.
+ std::vector<double> aWidths(nGraphemeCount);
+
+ // Check if the glyph has ligature caret positions.
+ unsigned int nCarets = nGraphemeCount;
+ std::vector<hb_position_t> aCarets(nGraphemeCount);
+ hb_ot_layout_get_ligature_carets(GetFont().GetHbFont(),
+ aGlyphItem.IsRTLGlyph() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR,
+ aGlyphItem.glyphId(), 0, &nCarets, aCarets.data());
+
+ // Carets are 1-less than the grapheme count (since the last
+ // position is defined by glyph width), if the count does not
+ // match, ignore it.
+ if (nCarets == nGraphemeCount - 1)
+ {
+ // Scale the carets and apply glyph offset to them since they
+ // are based on the default glyph metrics.
+ double fScale = 0;
+ GetFont().GetScale(&fScale, nullptr);
+ for (size_t i = 0; i < nCarets; i++)
+ aCarets[i] = (aCarets[i] * fScale) + aGlyphItem.xOffset();
+
+ // Use the glyph width for the last caret.
+ aCarets[nCarets] = aGlyphItem.newWidth();
+
+ // Carets are absolute from the X origin of the glyph, turn
+ // them to relative widths that we need below.
+ for (size_t i = 0; i < nGraphemeCount; i++)
+ aWidths[i] = aCarets[i] - (i == 0 ? 0 : aCarets[i - 1]);
+
+ // Carets are in visual order, but we want widths in logical
+ // order.
+ if (aGlyphItem.IsRTLGlyph())
+ std::reverse(aWidths.begin(), aWidths.end());
+ }
+ else
+ {
+ // The glyph has no carets, distribute the width evenly.
+ auto nWidth = aGlyphItem.newWidth() / nGraphemeCount;
+ std::fill(aWidths.begin(), aWidths.end(), nWidth);
+
+ // Add rounding difference to the last component to maintain
+ // ligature width.
+ aWidths[nGraphemeCount - 1] += aGlyphItem.newWidth() - (nWidth * nGraphemeCount);
+ }
+
+ // Set the width of each grapheme cluster.
+ sal_Int32 nDone;
+ sal_Int32 nPos = aGlyphItem.charPos();
+ for (auto nWidth : aWidths)
+ {
+ rCharWidths[nPos - mnMinCharPos] += nWidth;
+ nPos = xBreak->nextCharacters(rStr, nPos, aLocale,
+ css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
+ }
+ }
+ else
+ rCharWidths[aGlyphItem.charPos() - mnMinCharPos] += aGlyphItem.newWidth();
+ }
+}
+
+// - pDXArray: is the adjustments to glyph advances (usually due to
+// justification).
+// - pKashidaArray: is the places where kashidas are inserted (for Arabic
+// justification). The number of kashidas is calculated from the pDXArray.
+void GenericSalLayout::ApplyDXArray(const double* pDXArray, const sal_Bool* pKashidaArray)
+{
+ int nCharCount = mnEndCharPos - mnMinCharPos;
+ std::vector<double> aOldCharWidths;
+ std::unique_ptr<double[]> const pNewCharWidths(new double[nCharCount]);
+
+ // Get the natural character widths (i.e. before applying DX adjustments).
+ GetCharWidths(aOldCharWidths, {});
+
+ // Calculate the character widths after DX adjustments.
+ for (int i = 0; i < nCharCount; ++i)
+ {
+ if (i == 0)
+ pNewCharWidths[i] = pDXArray[i];
+ else
+ pNewCharWidths[i] = pDXArray[i] - pDXArray[i - 1];
+ }
+
+ // Map of Kashida insertion points (in the glyph items vector) and the
+ // requested width.
+ std::map<size_t, std::pair<double, double>> pKashidas;
+
+ // The accumulated difference in X position.
+ double nDelta = 0;
+
+ // Apply the DX adjustments to glyph positions and widths.
+ size_t i = 0;
+ while (i < m_GlyphItems.size())
+ {
+ // Accumulate the width difference for all characters corresponding to
+ // this glyph.
+ int nCharPos = m_GlyphItems[i].charPos() - mnMinCharPos;
+ double nDiff = 0;
+ for (int j = 0; j < m_GlyphItems[i].charCount(); j++)
+ nDiff += pNewCharWidths[nCharPos + j] - aOldCharWidths[nCharPos + j];
+
+ if (!m_GlyphItems[i].IsRTLGlyph())
+ {
+ // Adjust the width and position of the first (leftmost) glyph in
+ // the cluster.
+ m_GlyphItems[i].addNewWidth(nDiff);
+ m_GlyphItems[i].adjustLinearPosX(nDelta);
+
+ // Adjust the position of the rest of the glyphs in the cluster.
+ while (++i < m_GlyphItems.size())
+ {
+ if (!m_GlyphItems[i].IsInCluster())
+ break;
+ m_GlyphItems[i].adjustLinearPosX(nDelta);
+ }
+ }
+ else if (m_GlyphItems[i].IsInCluster())
+ {
+ // RTL glyph in the middle of the cluster, will be handled in the
+ // loop below.
+ i++;
+ }
+ else // RTL
+ {
+ // Adjust the width and position of the first (rightmost) glyph in
+ // the cluster. This is RTL, so we put all the adjustment to the
+ // left of the glyph.
+ m_GlyphItems[i].addNewWidth(nDiff);
+ m_GlyphItems[i].adjustLinearPosX(nDelta + nDiff);
+
+ // Adjust the X position of the rest of the glyphs in the cluster.
+ // We iterate backwards since this is an RTL glyph.
+ for (int j = i - 1; j >= 0 && m_GlyphItems[j].IsInCluster(); j--)
+ m_GlyphItems[j].adjustLinearPosX(nDelta + nDiff);
+
+ // This is a Kashida insertion position, mark it. Kashida glyphs
+ // will be inserted below.
+ if (pKashidaArray && pKashidaArray[nCharPos])
+ pKashidas[i] = { nDiff, pNewCharWidths[nCharPos] };
+
+ i++;
+ }
+
+ // Increment the delta, the loop above makes sure we do so only once
+ // for every character (cluster) not for every glyph (otherwise we
+ // would apply it multiple times for each glyph belonging to the same
+ // character which is wrong as DX adjustments are character based).
+ nDelta += nDiff;
+ }
+
+ // Insert Kashida glyphs.
+ if (pKashidas.empty())
+ return;
+
+ // Find Kashida glyph width and index.
+ sal_GlyphId nKashidaIndex = GetFont().GetGlyphIndex(0x0640);
+ double nKashidaWidth = GetFont().GetKashidaWidth();
+ if (!GetSubpixelPositioning())
+ nKashidaWidth = std::ceil(nKashidaWidth);
+
+ if (nKashidaWidth <= 0)
+ {
+ SAL_WARN("vcl.gdi", "Asked to insert Kashidas in a font with bogus Kashida width");
+ return;
+ }
+
+ size_t nInserted = 0;
+ for (auto const& pKashida : pKashidas)
+ {
+ auto pGlyphIter = m_GlyphItems.begin() + nInserted + pKashida.first;
+
+ // The total Kashida width.
+ auto const& [nTotalWidth, nClusterWidth] = pKashida.second;
+
+ // Number of times to repeat each Kashida.
+ int nCopies = 1;
+ if (nTotalWidth > nKashidaWidth)
+ nCopies = nTotalWidth / nKashidaWidth;
+
+ // See if we can improve the fit by adding an extra Kashidas and
+ // squeezing them together a bit.
+ double nOverlap = 0;
+ double nShortfall = nTotalWidth - nKashidaWidth * nCopies;
+ if (nShortfall > 0)
+ {
+ ++nCopies;
+ double nExcess = nCopies * nKashidaWidth - nTotalWidth;
+ if (nExcess > 0)
+ nOverlap = nExcess / (nCopies - 1);
+ }
+
+ basegfx::B2DPoint aPos = pGlyphIter->linearPos();
+ int nCharPos = pGlyphIter->charPos();
+ GlyphItemFlags const nFlags = GlyphItemFlags::IS_IN_CLUSTER | GlyphItemFlags::IS_RTL_GLYPH;
+ // Move to the left side of the adjusted width and start inserting
+ // glyphs there.
+ aPos.adjustX(-nClusterWidth + pGlyphIter->origWidth());
+ while (nCopies--)
+ {
+ GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, 0, 0, 0);
+ pGlyphIter = m_GlyphItems.insert(pGlyphIter, aKashida);
+ aPos.adjustX(nKashidaWidth - nOverlap);
+ ++pGlyphIter;
+ ++nInserted;
+ }
+ }
+}
+
+// Kashida will be inserted between nCharPos and nNextCharPos.
+bool GenericSalLayout::IsKashidaPosValid(int nCharPos, int nNextCharPos) const
+{
+ // Search for glyph items corresponding to nCharPos and nNextCharPos.
+ auto const& rGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(),
+ [&](const GlyphItem& g) { return g.charPos() == nCharPos; });
+ auto const& rNextGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(),
+ [&](const GlyphItem& g) { return g.charPos() == nNextCharPos; });
+
+ // If either is not found then a ligature is created at this position, we
+ // can’t insert Kashida here.
+ if (rGlyph == m_GlyphItems.end() || rNextGlyph == m_GlyphItems.end())
+ return false;
+
+ // If the either character is not supported by this layout, return false so
+ // that fallback layouts would be checked for it.
+ if (rGlyph->glyphId() == 0 || rNextGlyph->glyphId() == 0)
+ return false;
+
+ // Lastly check if this position is kashida-safe.
+ return rNextGlyph->IsSafeToInsertKashida();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/FileDefinitionWidgetDraw.cxx b/vcl/source/gdi/FileDefinitionWidgetDraw.cxx
new file mode 100644
index 0000000000..0be4c829dd
--- /dev/null
+++ b/vcl/source/gdi/FileDefinitionWidgetDraw.cxx
@@ -0,0 +1,1130 @@
+/* -*- 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/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <FileDefinitionWidgetDraw.hxx>
+#include <widgetdraw/WidgetDefinitionReader.hxx>
+
+#include <svdata.hxx>
+#include <rtl/bootstrap.hxx>
+#include <config_folders.h>
+#include <osl/file.hxx>
+
+#include <basegfx/range/b2drectangle.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/tuple/b2dtuple.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <tools/stream.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <vcl/gradient.hxx>
+
+#include <comphelper/seqstream.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/string.hxx>
+
+#include <com/sun/star/graphic/SvgTools.hpp>
+#include <basegfx/DrawCommands.hxx>
+#include <o3tl/string_view.hxx>
+
+using namespace css;
+
+namespace vcl
+{
+namespace
+{
+OUString lcl_getThemeDefinitionPath()
+{
+ OUString sPath("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/theme_definitions/");
+ rtl::Bootstrap::expandMacros(sPath);
+ return sPath;
+}
+
+bool lcl_directoryExists(OUString const& sDirectory)
+{
+ osl::DirectoryItem aDirectoryItem;
+ osl::FileBase::RC eRes = osl::DirectoryItem::get(sDirectory, aDirectoryItem);
+ return eRes == osl::FileBase::E_None;
+}
+
+bool lcl_fileExists(OUString const& sFilename)
+{
+ osl::File aFile(sFilename);
+ osl::FileBase::RC eRC = aFile.open(osl_File_OpenFlag_Read);
+ return osl::FileBase::E_None == eRC;
+}
+
+std::shared_ptr<WidgetDefinition> getWidgetDefinition(OUString const& rDefinitionFile,
+ OUString const& rDefinitionResourcesPath)
+{
+ auto pWidgetDefinition = std::make_shared<WidgetDefinition>();
+ WidgetDefinitionReader aReader(rDefinitionFile, rDefinitionResourcesPath);
+ if (aReader.read(*pWidgetDefinition))
+ return pWidgetDefinition;
+ return std::shared_ptr<WidgetDefinition>();
+}
+
+std::shared_ptr<WidgetDefinition> const&
+getWidgetDefinitionForTheme(std::u16string_view rThemenName)
+{
+ static std::shared_ptr<WidgetDefinition> spDefinition;
+ if (!spDefinition)
+ {
+ OUString sSharedDefinitionBasePath = lcl_getThemeDefinitionPath();
+ OUString sThemeFolder = sSharedDefinitionBasePath + rThemenName + "/";
+ OUString sThemeDefinitionFile = sThemeFolder + "definition.xml";
+ if (lcl_directoryExists(sThemeFolder) && lcl_fileExists(sThemeDefinitionFile))
+ spDefinition = getWidgetDefinition(sThemeDefinitionFile, sThemeFolder);
+ }
+ return spDefinition;
+}
+
+int getSettingValueInteger(std::string_view rValue, int nDefault)
+{
+ if (rValue.empty())
+ return nDefault;
+ if (!comphelper::string::isdigitAsciiString(rValue))
+ return nDefault;
+ return o3tl::toInt32(rValue);
+}
+
+bool getSettingValueBool(std::string_view rValue, bool bDefault)
+{
+ if (rValue.empty())
+ return bDefault;
+ if (rValue == "true" || rValue == "false")
+ return rValue == "true";
+ return bDefault;
+}
+
+} // end anonymous namespace
+
+FileDefinitionWidgetDraw::FileDefinitionWidgetDraw(SalGraphics& rGraphics)
+ : m_rGraphics(rGraphics)
+ , m_bIsActive(false)
+{
+ m_pWidgetDefinition = getWidgetDefinitionForTheme(u"online");
+#ifdef IOS
+ if (!m_pWidgetDefinition)
+ m_pWidgetDefinition = getWidgetDefinitionForTheme(u"ios");
+#endif
+
+ if (!m_pWidgetDefinition)
+ return;
+
+ auto& pSettings = m_pWidgetDefinition->mpSettings;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maNWFData.mbNoFocusRects = true;
+ pSVData->maNWFData.mbNoFocusRectsForFlatButtons = true;
+ pSVData->maNWFData.mbNoActiveTabTextRaise
+ = getSettingValueBool(pSettings->msNoActiveTabTextRaise, true);
+ pSVData->maNWFData.mbCenteredTabs = getSettingValueBool(pSettings->msCenteredTabs, true);
+ pSVData->maNWFData.mbProgressNeedsErase = true;
+ pSVData->maNWFData.mnStatusBarLowerRightOffset = 10;
+ pSVData->maNWFData.mbCanDrawWidgetAnySize = true;
+
+ int nDefaultListboxEntryMargin = pSVData->maNWFData.mnListBoxEntryMargin;
+ pSVData->maNWFData.mnListBoxEntryMargin
+ = getSettingValueInteger(pSettings->msListBoxEntryMargin, nDefaultListboxEntryMargin);
+
+ m_bIsActive = true;
+}
+
+bool FileDefinitionWidgetDraw::isNativeControlSupported(ControlType eType, ControlPart ePart)
+{
+ switch (eType)
+ {
+ case ControlType::Generic:
+ case ControlType::Pushbutton:
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ return true;
+ case ControlType::Combobox:
+ if (ePart == ControlPart::HasBackgroundTexture)
+ return false;
+ return true;
+ case ControlType::Editbox:
+ case ControlType::EditboxNoBorder:
+ case ControlType::MultilineEditbox:
+ return true;
+ case ControlType::Listbox:
+ if (ePart == ControlPart::HasBackgroundTexture)
+ return false;
+ return true;
+ case ControlType::Spinbox:
+ if (ePart == ControlPart::AllButtons)
+ return false;
+ return true;
+ case ControlType::SpinButtons:
+ return false;
+ case ControlType::TabItem:
+ case ControlType::TabPane:
+ case ControlType::TabHeader:
+ case ControlType::TabBody:
+ return true;
+ case ControlType::Scrollbar:
+ if (ePart == ControlPart::DrawBackgroundHorz
+ || ePart == ControlPart::DrawBackgroundVert)
+ return false;
+ return true;
+ case ControlType::Slider:
+ case ControlType::Fixedline:
+ case ControlType::Toolbar:
+ return true;
+ case ControlType::Menubar:
+ case ControlType::MenuPopup:
+ return true;
+ case ControlType::Progress:
+ case ControlType::LevelBar:
+ return true;
+ case ControlType::IntroProgress:
+ return false;
+ case ControlType::Tooltip:
+ return true;
+ case ControlType::WindowBackground:
+ case ControlType::Frame:
+ case ControlType::ListNode:
+ case ControlType::ListNet:
+ case ControlType::ListHeader:
+ return true;
+ }
+
+ return false;
+}
+
+bool FileDefinitionWidgetDraw::hitTestNativeControl(
+ ControlType /*eType*/, ControlPart /*ePart*/,
+ const tools::Rectangle& /*rBoundingControlRegion*/, const Point& /*aPos*/, bool& /*rIsInside*/)
+{
+ return false;
+}
+
+void FileDefinitionWidgetDraw::drawPolyPolygon(SalGraphics& rGraphics,
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& i_rPolyPolygon,
+ double i_fTransparency)
+{
+ rGraphics.drawPolyPolygon(rObjectToDevice, i_rPolyPolygon, i_fTransparency);
+}
+
+void FileDefinitionWidgetDraw::drawPolyLine(
+ SalGraphics& rGraphics, const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& i_rPolygon, double i_fTransparency, double i_fLineWidth,
+ const std::vector<double>* i_pStroke, basegfx::B2DLineJoin i_eLineJoin,
+ css::drawing::LineCap i_eLineCap, double i_fMiterMinimumAngle, bool bPixelSnapHairline)
+{
+ rGraphics.drawPolyLine(rObjectToDevice, i_rPolygon, i_fTransparency, i_fLineWidth, i_pStroke,
+ i_eLineJoin, i_eLineCap, i_fMiterMinimumAngle, bPixelSnapHairline);
+}
+
+void FileDefinitionWidgetDraw::drawBitmap(SalGraphics& rGraphics, const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap)
+{
+ rGraphics.drawBitmap(rPosAry, rSalBitmap);
+}
+
+void FileDefinitionWidgetDraw::drawBitmap(SalGraphics& rGraphics, const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap)
+{
+ rGraphics.drawBitmap(rPosAry, rSalBitmap, rTransparentBitmap);
+}
+
+void FileDefinitionWidgetDraw::implDrawGradient(SalGraphics& rGraphics,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ const SalGradient& rGradient)
+{
+ rGraphics.implDrawGradient(rPolyPolygon, rGradient);
+}
+
+namespace
+{
+void drawFromDrawCommands(gfx::DrawRoot const& rDrawRoot, SalGraphics& rGraphics, tools::Long nX,
+ tools::Long nY, tools::Long nWidth, tools::Long nHeight)
+{
+ basegfx::B2DRectangle aSVGRect = rDrawRoot.maRectangle;
+
+ basegfx::B2DRange aTargetSurface(nX, nY, nX + nWidth + 1, nY + nHeight + 1);
+
+ for (std::shared_ptr<gfx::DrawBase> const& pDrawBase : rDrawRoot.maChildren)
+ {
+ switch (pDrawBase->getType())
+ {
+ case gfx::DrawCommandType::Rectangle:
+ {
+ auto const& rRectangle = static_cast<gfx::DrawRectangle const&>(*pDrawBase);
+
+ basegfx::B2DRange aInputRectangle(rRectangle.maRectangle);
+
+ double fDeltaX = aTargetSurface.getWidth() - aSVGRect.getWidth();
+ double fDeltaY = aTargetSurface.getHeight() - aSVGRect.getHeight();
+
+ basegfx::B2DRange aFinalRectangle(
+ aInputRectangle.getMinX(), aInputRectangle.getMinY(),
+ aInputRectangle.getMaxX() + fDeltaX, aInputRectangle.getMaxY() + fDeltaY);
+
+ aFinalRectangle.transform(basegfx::utils::createTranslateB2DHomMatrix(
+ aTargetSurface.getMinX() - 0.5, aTargetSurface.getMinY() - 0.5));
+
+ basegfx::B2DPolygon aB2DPolygon = basegfx::utils::createPolygonFromRect(
+ aFinalRectangle, rRectangle.mnRx / aFinalRectangle.getWidth() * 2.0,
+ rRectangle.mnRy / aFinalRectangle.getHeight() * 2.0);
+
+ if (rRectangle.mpFillColor)
+ {
+ rGraphics.SetLineColor();
+ rGraphics.SetFillColor(Color(*rRectangle.mpFillColor));
+ FileDefinitionWidgetDraw::drawPolyPolygon(rGraphics, basegfx::B2DHomMatrix(),
+ basegfx::B2DPolyPolygon(aB2DPolygon),
+ 1.0 - rRectangle.mnOpacity);
+ }
+ else if (rRectangle.mpFillGradient)
+ {
+ rGraphics.SetLineColor();
+ rGraphics.SetFillColor();
+ if (rRectangle.mpFillGradient->meType == gfx::GradientType::Linear)
+ {
+ auto* pLinearGradient = static_cast<gfx::LinearGradientInfo*>(
+ rRectangle.mpFillGradient.get());
+ SalGradient aGradient;
+ double x, y;
+
+ x = pLinearGradient->x1;
+ y = pLinearGradient->y1;
+
+ if (x > aSVGRect.getCenterX())
+ x = x + fDeltaX;
+ if (y > aSVGRect.getCenterY())
+ y = y + fDeltaY;
+
+ aGradient.maPoint1 = basegfx::B2DPoint(x, y);
+ aGradient.maPoint1 *= basegfx::utils::createTranslateB2DHomMatrix(
+ aTargetSurface.getMinX() - 0.5, aTargetSurface.getMinY() - 0.5);
+
+ x = pLinearGradient->x2;
+ y = pLinearGradient->y2;
+
+ if (x > aSVGRect.getCenterX())
+ x = x + fDeltaX;
+ if (y > aSVGRect.getCenterY())
+ y = y + fDeltaY;
+
+ aGradient.maPoint2 = basegfx::B2DPoint(x, y);
+ aGradient.maPoint2 *= basegfx::utils::createTranslateB2DHomMatrix(
+ aTargetSurface.getMinX() - 0.5, aTargetSurface.getMinY() - 0.5);
+
+ for (gfx::GradientStop const& rStop : pLinearGradient->maGradientStops)
+ {
+ Color aColor(rStop.maColor);
+ aColor.SetAlpha(255
+ - (rStop.mfOpacity * (1.0f - rRectangle.mnOpacity)));
+ aGradient.maStops.emplace_back(aColor, rStop.mfOffset);
+ }
+ FileDefinitionWidgetDraw::implDrawGradient(
+ rGraphics, basegfx::B2DPolyPolygon(aB2DPolygon), aGradient);
+ }
+ }
+ if (rRectangle.mpStrokeColor)
+ {
+ rGraphics.SetLineColor(Color(*rRectangle.mpStrokeColor));
+ rGraphics.SetFillColor();
+ FileDefinitionWidgetDraw::drawPolyLine(
+ rGraphics, basegfx::B2DHomMatrix(), aB2DPolygon, 1.0 - rRectangle.mnOpacity,
+ rRectangle.mnStrokeWidth,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false);
+ }
+ }
+ break;
+ case gfx::DrawCommandType::Path:
+ {
+ auto const& rPath = static_cast<gfx::DrawPath const&>(*pDrawBase);
+
+ double fDeltaX = aTargetSurface.getWidth() - aSVGRect.getWidth();
+ double fDeltaY = aTargetSurface.getHeight() - aSVGRect.getHeight();
+
+ basegfx::B2DPolyPolygon aPolyPolygon(rPath.maPolyPolygon);
+ for (auto& rPolygon : aPolyPolygon)
+ {
+ for (size_t i = 0; i < rPolygon.count(); ++i)
+ {
+ auto& rPoint = rPolygon.getB2DPoint(i);
+ double x = rPoint.getX();
+ double y = rPoint.getY();
+
+ if (x > aSVGRect.getCenterX())
+ x = x + fDeltaX;
+ if (y > aSVGRect.getCenterY())
+ y = y + fDeltaY;
+ rPolygon.setB2DPoint(i, basegfx::B2DPoint(x, y));
+ }
+ }
+ aPolyPolygon.transform(basegfx::utils::createTranslateB2DHomMatrix(
+ aTargetSurface.getMinX() - 0.5, aTargetSurface.getMinY() - 0.5));
+
+ if (rPath.mpFillColor)
+ {
+ rGraphics.SetLineColor();
+ rGraphics.SetFillColor(Color(*rPath.mpFillColor));
+ FileDefinitionWidgetDraw::drawPolyPolygon(rGraphics, basegfx::B2DHomMatrix(),
+ aPolyPolygon, 1.0 - rPath.mnOpacity);
+ }
+ if (rPath.mpStrokeColor)
+ {
+ rGraphics.SetLineColor(Color(*rPath.mpStrokeColor));
+ rGraphics.SetFillColor();
+ for (auto const& rPolygon : std::as_const(aPolyPolygon))
+ {
+ FileDefinitionWidgetDraw::drawPolyLine(
+ rGraphics, basegfx::B2DHomMatrix(), rPolygon, 1.0 - rPath.mnOpacity,
+ rPath.mnStrokeWidth,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false);
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void munchDrawCommands(std::vector<std::shared_ptr<WidgetDrawAction>> const& rDrawActions,
+ SalGraphics& rGraphics, tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ for (std::shared_ptr<WidgetDrawAction> const& pDrawAction : rDrawActions)
+ {
+ switch (pDrawAction->maType)
+ {
+ case WidgetDrawActionType::RECTANGLE:
+ {
+ auto const& rWidgetDraw
+ = static_cast<WidgetDrawActionRectangle const&>(*pDrawAction);
+
+ basegfx::B2DRectangle rRect(
+ nX + (nWidth * rWidgetDraw.mfX1), nY + (nHeight * rWidgetDraw.mfY1),
+ nX + (nWidth * rWidgetDraw.mfX2), nY + (nHeight * rWidgetDraw.mfY2));
+
+ basegfx::B2DPolygon aB2DPolygon = basegfx::utils::createPolygonFromRect(
+ rRect, rWidgetDraw.mnRx / rRect.getWidth() * 2.0,
+ rWidgetDraw.mnRy / rRect.getHeight() * 2.0);
+
+ rGraphics.SetLineColor();
+ rGraphics.SetFillColor(rWidgetDraw.maFillColor);
+ FileDefinitionWidgetDraw::drawPolyPolygon(
+ rGraphics, basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aB2DPolygon), 0.0f);
+ rGraphics.SetLineColor(rWidgetDraw.maStrokeColor);
+ rGraphics.SetFillColor();
+ FileDefinitionWidgetDraw::drawPolyLine(
+ rGraphics, basegfx::B2DHomMatrix(), aB2DPolygon, 0.0f,
+ rWidgetDraw.mnStrokeWidth, nullptr, // MM01
+ basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false);
+ }
+ break;
+ case WidgetDrawActionType::LINE:
+ {
+ auto const& rWidgetDraw = static_cast<WidgetDrawActionLine const&>(*pDrawAction);
+ Point aRectPoint(nX + 1, nY + 1);
+
+ Size aRectSize(nWidth - 1, nHeight - 1);
+
+ rGraphics.SetFillColor();
+ rGraphics.SetLineColor(rWidgetDraw.maStrokeColor);
+
+ basegfx::B2DPolygon aB2DPolygon{
+ { aRectPoint.X() + (aRectSize.Width() * rWidgetDraw.mfX1),
+ aRectPoint.Y() + (aRectSize.Height() * rWidgetDraw.mfY1) },
+ { aRectPoint.X() + (aRectSize.Width() * rWidgetDraw.mfX2),
+ aRectPoint.Y() + (aRectSize.Height() * rWidgetDraw.mfY2) },
+ };
+
+ FileDefinitionWidgetDraw::drawPolyLine(
+ rGraphics, basegfx::B2DHomMatrix(), aB2DPolygon, 0.0f,
+ rWidgetDraw.mnStrokeWidth, nullptr, // MM01
+ basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false);
+ }
+ break;
+ case WidgetDrawActionType::IMAGE:
+ {
+ double nScaleFactor = 1.0;
+ if (comphelper::LibreOfficeKit::isActive())
+ nScaleFactor = comphelper::LibreOfficeKit::getDPIScale();
+
+ auto const& rWidgetDraw = static_cast<WidgetDrawActionImage const&>(*pDrawAction);
+ auto& rCacheImages = ImplGetSVData()->maGDIData.maThemeImageCache;
+ OUString rCacheKey = rWidgetDraw.msSource + "@" + OUString::number(nScaleFactor);
+ auto aIterator = rCacheImages.find(rCacheKey);
+
+ BitmapEx aBitmap;
+ if (aIterator == rCacheImages.end())
+ {
+ SvFileStream aFileStream(rWidgetDraw.msSource, StreamMode::READ);
+
+ vcl::bitmap::loadFromSvg(aFileStream, "", aBitmap, nScaleFactor);
+ if (!aBitmap.IsEmpty())
+ {
+ rCacheImages.insert(std::make_pair(rCacheKey, aBitmap));
+ }
+ }
+ else
+ {
+ aBitmap = aIterator->second;
+ }
+
+ tools::Long nImageWidth = aBitmap.GetSizePixel().Width();
+ tools::Long nImageHeight = aBitmap.GetSizePixel().Height();
+ SalTwoRect aTR(0, 0, nImageWidth, nImageHeight, nX, nY, nImageWidth / nScaleFactor,
+ nImageHeight / nScaleFactor);
+ if (!aBitmap.IsEmpty())
+ {
+ const std::shared_ptr<SalBitmap> pSalBitmap
+ = aBitmap.GetBitmap().ImplGetSalBitmap();
+ if (aBitmap.IsAlpha())
+ {
+ const std::shared_ptr<SalBitmap> pSalBitmapAlpha
+ = aBitmap.GetAlphaMask().GetBitmap().ImplGetSalBitmap();
+ FileDefinitionWidgetDraw::drawBitmap(rGraphics, aTR, *pSalBitmap,
+ *pSalBitmapAlpha);
+ }
+ else
+ {
+ FileDefinitionWidgetDraw::drawBitmap(rGraphics, aTR, *pSalBitmap);
+ }
+ }
+ }
+ break;
+ case WidgetDrawActionType::EXTERNAL:
+ {
+ auto const& rWidgetDraw
+ = static_cast<WidgetDrawActionExternal const&>(*pDrawAction);
+
+ auto& rCacheDrawCommands = ImplGetSVData()->maGDIData.maThemeDrawCommandsCache;
+
+ auto aIterator = rCacheDrawCommands.find(rWidgetDraw.msSource);
+
+ if (aIterator == rCacheDrawCommands.end())
+ {
+ SvFileStream aFileStream(rWidgetDraw.msSource, StreamMode::READ);
+
+ uno::Reference<uno::XComponentContext> xContext(
+ comphelper::getProcessComponentContext());
+ const uno::Reference<graphic::XSvgParser> xSvgParser
+ = graphic::SvgTools::create(xContext);
+
+ std::size_t nSize = aFileStream.remainingSize();
+ std::vector<sal_Int8> aBuffer(nSize + 1);
+ aFileStream.ReadBytes(aBuffer.data(), nSize);
+ aBuffer[nSize] = 0;
+
+ uno::Sequence<sal_Int8> aData(aBuffer.data(), nSize + 1);
+ uno::Reference<io::XInputStream> aInputStream(
+ new comphelper::SequenceInputStream(aData));
+
+ uno::Any aAny = xSvgParser->getDrawCommands(aInputStream, "");
+ if (aAny.has<sal_uInt64>())
+ {
+ auto* pDrawRoot = reinterpret_cast<gfx::DrawRoot*>(aAny.get<sal_uInt64>());
+ if (pDrawRoot)
+ {
+ rCacheDrawCommands.insert(
+ std::make_pair(rWidgetDraw.msSource, *pDrawRoot));
+ drawFromDrawCommands(*pDrawRoot, rGraphics, nX, nY, nWidth, nHeight);
+ }
+ }
+ }
+ else
+ {
+ drawFromDrawCommands(aIterator->second, rGraphics, nX, nY, nWidth, nHeight);
+ }
+ }
+ break;
+ }
+ }
+}
+
+} // end anonymous namespace
+
+bool FileDefinitionWidgetDraw::resolveDefinition(ControlType eType, ControlPart ePart,
+ ControlState eState,
+ const ImplControlValue& rValue, tools::Long nX,
+ tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ bool bOK = false;
+ auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ePart);
+ if (pPart)
+ {
+ auto const& aStates = pPart->getStates(eType, ePart, eState, rValue);
+ if (!aStates.empty())
+ {
+ // use last defined state
+ auto const& pState = aStates.back();
+ {
+ munchDrawCommands(pState->mpWidgetDrawActions, m_rGraphics, nX, nY, nWidth,
+ nHeight);
+ bOK = true;
+ }
+ }
+ }
+ return bOK;
+}
+
+bool FileDefinitionWidgetDraw::drawNativeControl(ControlType eType, ControlPart ePart,
+ const tools::Rectangle& rControlRegion,
+ ControlState eState,
+ const ImplControlValue& rValue,
+ const OUString& /*aCaptions*/,
+ const Color& /*rBackgroundColor*/)
+{
+ bool bOldAA = m_rGraphics.getAntiAlias();
+ m_rGraphics.setAntiAlias(true);
+
+ tools::Long nWidth = rControlRegion.GetWidth() - 1;
+ tools::Long nHeight = rControlRegion.GetHeight() - 1;
+ tools::Long nX = rControlRegion.Left();
+ tools::Long nY = rControlRegion.Top();
+
+ bool bOK = false;
+
+ switch (eType)
+ {
+ case ControlType::Pushbutton:
+ {
+ /*bool bIsAction = false;
+ const PushButtonValue* pPushButtonValue = static_cast<const PushButtonValue*>(&rValue);
+ if (pPushButtonValue)
+ bIsAction = pPushButtonValue->mbIsAction;*/
+
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Radiobutton:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Checkbox:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Combobox:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Editbox:
+ case ControlType::EditboxNoBorder:
+ case ControlType::MultilineEditbox:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Listbox:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Spinbox:
+ {
+ if (rValue.getType() == ControlType::SpinButtons)
+ {
+ const SpinbuttonValue* pSpinVal = static_cast<const SpinbuttonValue*>(&rValue);
+
+ {
+ ControlPart eUpButtonPart = pSpinVal->mnUpperPart;
+ ControlState eUpButtonState = pSpinVal->mnUpperState;
+
+ tools::Long nUpperX = pSpinVal->maUpperRect.Left();
+ tools::Long nUpperY = pSpinVal->maUpperRect.Top();
+ tools::Long nUpperWidth = pSpinVal->maUpperRect.GetWidth() - 1;
+ tools::Long nUpperHeight = pSpinVal->maUpperRect.GetHeight() - 1;
+
+ bOK = resolveDefinition(eType, eUpButtonPart, eUpButtonState,
+ ImplControlValue(), nUpperX, nUpperY, nUpperWidth,
+ nUpperHeight);
+ }
+
+ if (bOK)
+ {
+ ControlPart eDownButtonPart = pSpinVal->mnLowerPart;
+ ControlState eDownButtonState = pSpinVal->mnLowerState;
+
+ tools::Long nLowerX = pSpinVal->maLowerRect.Left();
+ tools::Long nLowerY = pSpinVal->maLowerRect.Top();
+ tools::Long nLowerWidth = pSpinVal->maLowerRect.GetWidth() - 1;
+ tools::Long nLowerHeight = pSpinVal->maLowerRect.GetHeight() - 1;
+
+ bOK = resolveDefinition(eType, eDownButtonPart, eDownButtonState,
+ ImplControlValue(), nLowerX, nLowerY, nLowerWidth,
+ nLowerHeight);
+ }
+ }
+ else
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ }
+ break;
+ case ControlType::SpinButtons:
+ break;
+ case ControlType::TabItem:
+ case ControlType::TabHeader:
+ case ControlType::TabPane:
+ case ControlType::TabBody:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Slider:
+ {
+ const SliderValue* pSliderValue = static_cast<const SliderValue*>(&rValue);
+ tools::Long nThumbX = pSliderValue->maThumbRect.Left();
+ tools::Long nThumbY = pSliderValue->maThumbRect.Top();
+ tools::Long nThumbWidth = pSliderValue->maThumbRect.GetWidth() - 1;
+ tools::Long nThumbHeight = pSliderValue->maThumbRect.GetHeight() - 1;
+
+ if (ePart == ControlPart::TrackHorzArea)
+ {
+ tools::Long nCenterX = nThumbX + nThumbWidth / 2;
+
+ bOK = resolveDefinition(eType, ControlPart::TrackHorzLeft, eState, rValue, nX, nY,
+ nCenterX - nX, nHeight);
+ if (bOK)
+ bOK = resolveDefinition(eType, ControlPart::TrackHorzRight, eState, rValue,
+ nCenterX, nY, nX + nWidth - nCenterX, nHeight);
+ }
+ else if (ePart == ControlPart::TrackVertArea)
+ {
+ tools::Long nCenterY = nThumbY + nThumbHeight / 2;
+
+ bOK = resolveDefinition(eType, ControlPart::TrackVertUpper, eState, rValue, nX, nY,
+ nWidth, nCenterY - nY);
+ if (bOK)
+ bOK = resolveDefinition(eType, ControlPart::TrackVertLower, eState, rValue, nY,
+ nCenterY, nWidth, nY + nHeight - nCenterY);
+ }
+
+ if (bOK)
+ {
+ bOK = resolveDefinition(eType, ControlPart::Button,
+ eState | pSliderValue->mnThumbState, rValue, nThumbX,
+ nThumbY, nThumbWidth, nThumbHeight);
+ }
+ }
+ break;
+ case ControlType::Fixedline:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Toolbar:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Menubar:
+ case ControlType::MenuPopup:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::Progress:
+ case ControlType::LevelBar:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::IntroProgress:
+ break;
+ case ControlType::Tooltip:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::WindowBackground:
+ case ControlType::Frame:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case ControlType::ListNode:
+ case ControlType::ListNet:
+ case ControlType::ListHeader:
+ {
+ bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight);
+ }
+ break;
+ default:
+ break;
+ }
+
+ m_rGraphics.setAntiAlias(bOldAA);
+
+ return bOK;
+}
+
+bool FileDefinitionWidgetDraw::getNativeControlRegion(
+ ControlType eType, ControlPart ePart, const tools::Rectangle& rBoundingControlRegion,
+ ControlState /*eState*/, const ImplControlValue& /*aValue*/, const OUString& /*aCaption*/,
+ tools::Rectangle& rNativeBoundingRegion, tools::Rectangle& rNativeContentRegion)
+{
+ Point aLocation(rBoundingControlRegion.TopLeft());
+
+ switch (eType)
+ {
+ case ControlType::Spinbox:
+ {
+ auto const& pButtonUpPart
+ = m_pWidgetDefinition->getDefinition(eType, ControlPart::ButtonUp);
+ if (!pButtonUpPart)
+ return false;
+ Size aButtonSizeUp(pButtonUpPart->mnWidth, pButtonUpPart->mnHeight);
+
+ auto const& pButtonDownPart
+ = m_pWidgetDefinition->getDefinition(eType, ControlPart::ButtonDown);
+ if (!pButtonDownPart)
+ return false;
+ Size aButtonSizeDown(pButtonDownPart->mnWidth, pButtonDownPart->mnHeight);
+
+ auto const& pEntirePart
+ = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire);
+
+ OString sOrientation = pEntirePart->msOrientation;
+
+ if (sOrientation.isEmpty() || sOrientation == "stacked")
+ {
+ return false;
+ }
+ else if (sOrientation == "decrease-edit-increase")
+ {
+ if (ePart == ControlPart::ButtonUp)
+ {
+ Point aPoint(aLocation.X() + rBoundingControlRegion.GetWidth()
+ - aButtonSizeUp.Width(),
+ aLocation.Y());
+ rNativeContentRegion = tools::Rectangle(aPoint, aButtonSizeUp);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::ButtonDown)
+ {
+ rNativeContentRegion = tools::Rectangle(aLocation, aButtonSizeDown);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::SubEdit)
+ {
+ Point aPoint(aLocation.X() + aButtonSizeDown.Width(), aLocation.Y());
+ Size aSize(rBoundingControlRegion.GetWidth()
+ - (aButtonSizeDown.Width() + aButtonSizeUp.Width()),
+ std::max(aButtonSizeUp.Height(), aButtonSizeDown.Height()));
+ rNativeContentRegion = tools::Rectangle(aPoint, aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::Entire)
+ {
+ Size aSize(rBoundingControlRegion.GetWidth(),
+ std::max(aButtonSizeUp.Height(), aButtonSizeDown.Height()));
+ rNativeContentRegion = tools::Rectangle(aLocation, aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ }
+ else if (sOrientation == "edit-decrease-increase")
+ {
+ if (ePart == ControlPart::ButtonUp)
+ {
+ Point aPoint(aLocation.X() + rBoundingControlRegion.GetWidth()
+ - aButtonSizeUp.Width(),
+ aLocation.Y());
+ rNativeContentRegion = tools::Rectangle(aPoint, aButtonSizeUp);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::ButtonDown)
+ {
+ Point aPoint(aLocation.X() + rBoundingControlRegion.GetWidth()
+ - (aButtonSizeDown.Width() + aButtonSizeUp.Width()),
+ aLocation.Y());
+ rNativeContentRegion = tools::Rectangle(aPoint, aButtonSizeDown);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::SubEdit)
+ {
+ Size aSize(rBoundingControlRegion.GetWidth()
+ - (aButtonSizeDown.Width() + aButtonSizeUp.Width()),
+ std::max(aButtonSizeUp.Height(), aButtonSizeDown.Height()));
+ rNativeContentRegion = tools::Rectangle(aLocation, aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::Entire)
+ {
+ Size aSize(rBoundingControlRegion.GetWidth(),
+ std::max(aButtonSizeUp.Height(), aButtonSizeDown.Height()));
+ rNativeContentRegion = tools::Rectangle(aLocation, aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ }
+ }
+ break;
+ case ControlType::Checkbox:
+ {
+ auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire);
+ if (!pPart)
+ return false;
+
+ Size aSize(pPart->mnWidth, pPart->mnHeight);
+ rNativeContentRegion = tools::Rectangle(Point(), aSize);
+ return true;
+ }
+ case ControlType::Radiobutton:
+ {
+ auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire);
+ if (!pPart)
+ return false;
+
+ Size aSize(pPart->mnWidth, pPart->mnHeight);
+ rNativeContentRegion = tools::Rectangle(Point(), aSize);
+ return true;
+ }
+ case ControlType::TabItem:
+ {
+ auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire);
+ if (!pPart)
+ return false;
+
+ tools::Long nWidth = std::max(rBoundingControlRegion.GetWidth() + pPart->mnMarginWidth,
+ tools::Long(pPart->mnWidth));
+ tools::Long nHeight
+ = std::max(rBoundingControlRegion.GetHeight() + pPart->mnMarginHeight,
+ tools::Long(pPart->mnHeight));
+
+ rNativeBoundingRegion = tools::Rectangle(aLocation, Size(nWidth, nHeight));
+ rNativeContentRegion = rNativeBoundingRegion;
+ return true;
+ }
+ case ControlType::Editbox:
+ case ControlType::EditboxNoBorder:
+ case ControlType::MultilineEditbox:
+ {
+ sal_Int32 nHeight = rBoundingControlRegion.GetHeight();
+
+ auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire);
+ if (pPart)
+ nHeight = std::max(nHeight, pPart->mnHeight);
+
+ Size aSize(rBoundingControlRegion.GetWidth(), nHeight);
+ rNativeContentRegion = tools::Rectangle(aLocation, aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ rNativeBoundingRegion.expand(2);
+ return true;
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ if (ePart == ControlPart::ButtonUp || ePart == ControlPart::ButtonDown
+ || ePart == ControlPart::ButtonLeft || ePart == ControlPart::ButtonRight)
+ {
+ rNativeContentRegion = tools::Rectangle(aLocation, Size(0, 0));
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else
+ {
+ rNativeBoundingRegion = rBoundingControlRegion;
+ rNativeContentRegion = rNativeBoundingRegion;
+ return true;
+ }
+ }
+ break;
+ case ControlType::Combobox:
+ case ControlType::Listbox:
+ {
+ auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::ButtonDown);
+ Size aComboButtonSize(pPart->mnWidth, pPart->mnHeight);
+
+ if (ePart == ControlPart::ButtonDown)
+ {
+ Point aPoint(aLocation.X() + rBoundingControlRegion.GetWidth()
+ - aComboButtonSize.Width() - 1,
+ aLocation.Y());
+ rNativeContentRegion = tools::Rectangle(aPoint, aComboButtonSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::SubEdit)
+ {
+ Size aSize(rBoundingControlRegion.GetWidth() - aComboButtonSize.Width(),
+ aComboButtonSize.Height());
+ rNativeContentRegion = tools::Rectangle(aLocation + Point(1, 1), aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ else if (ePart == ControlPart::Entire)
+ {
+ Size aSize(rBoundingControlRegion.GetWidth(), aComboButtonSize.Height());
+ rNativeContentRegion = tools::Rectangle(aLocation, aSize);
+ rNativeBoundingRegion = rNativeContentRegion;
+ rNativeBoundingRegion.expand(2);
+ return true;
+ }
+ }
+ break;
+ case ControlType::Slider:
+ if (ePart == ControlPart::ThumbHorz || ePart == ControlPart::ThumbVert)
+ {
+ rNativeContentRegion = tools::Rectangle(aLocation, Size(28, 28));
+ rNativeBoundingRegion = rNativeContentRegion;
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool FileDefinitionWidgetDraw::updateSettings(AllSettings& rSettings)
+{
+ StyleSettings aStyleSet = rSettings.GetStyleSettings();
+
+ auto& pDefinitionStyle = m_pWidgetDefinition->mpStyle;
+
+ aStyleSet.SetFaceColor(pDefinitionStyle->maFaceColor);
+ aStyleSet.SetCheckedColor(pDefinitionStyle->maCheckedColor);
+ aStyleSet.SetLightColor(pDefinitionStyle->maLightColor);
+ aStyleSet.SetLightBorderColor(pDefinitionStyle->maLightBorderColor);
+ aStyleSet.SetShadowColor(pDefinitionStyle->maShadowColor);
+ aStyleSet.SetDarkShadowColor(pDefinitionStyle->maDarkShadowColor);
+ aStyleSet.SetDefaultButtonTextColor(pDefinitionStyle->maDefaultButtonTextColor);
+ aStyleSet.SetButtonTextColor(pDefinitionStyle->maButtonTextColor);
+ aStyleSet.SetDefaultActionButtonTextColor(pDefinitionStyle->maDefaultActionButtonTextColor);
+ aStyleSet.SetActionButtonTextColor(pDefinitionStyle->maActionButtonTextColor);
+ aStyleSet.SetFlatButtonTextColor(pDefinitionStyle->maFlatButtonTextColor);
+ aStyleSet.SetDefaultButtonRolloverTextColor(pDefinitionStyle->maDefaultButtonRolloverTextColor);
+ aStyleSet.SetButtonRolloverTextColor(pDefinitionStyle->maButtonRolloverTextColor);
+ aStyleSet.SetDefaultActionButtonRolloverTextColor(
+ pDefinitionStyle->maDefaultActionButtonRolloverTextColor);
+ aStyleSet.SetActionButtonRolloverTextColor(pDefinitionStyle->maActionButtonRolloverTextColor);
+ aStyleSet.SetFlatButtonRolloverTextColor(pDefinitionStyle->maFlatButtonRolloverTextColor);
+ aStyleSet.SetDefaultButtonPressedRolloverTextColor(
+ pDefinitionStyle->maDefaultButtonPressedRolloverTextColor);
+ aStyleSet.SetButtonPressedRolloverTextColor(pDefinitionStyle->maButtonPressedRolloverTextColor);
+ aStyleSet.SetDefaultActionButtonPressedRolloverTextColor(
+ pDefinitionStyle->maDefaultActionButtonPressedRolloverTextColor);
+ aStyleSet.SetActionButtonPressedRolloverTextColor(
+ pDefinitionStyle->maActionButtonPressedRolloverTextColor);
+ aStyleSet.SetFlatButtonPressedRolloverTextColor(
+ pDefinitionStyle->maFlatButtonPressedRolloverTextColor);
+ aStyleSet.SetRadioCheckTextColor(pDefinitionStyle->maRadioCheckTextColor);
+ aStyleSet.SetGroupTextColor(pDefinitionStyle->maGroupTextColor);
+ aStyleSet.SetLabelTextColor(pDefinitionStyle->maLabelTextColor);
+ aStyleSet.SetWindowColor(pDefinitionStyle->maWindowColor);
+ aStyleSet.SetWindowTextColor(pDefinitionStyle->maWindowTextColor);
+ aStyleSet.SetDialogColor(pDefinitionStyle->maDialogColor);
+ aStyleSet.SetDialogTextColor(pDefinitionStyle->maDialogTextColor);
+ aStyleSet.SetWorkspaceColor(pDefinitionStyle->maWorkspaceColor);
+ aStyleSet.SetMonoColor(pDefinitionStyle->maMonoColor);
+ aStyleSet.SetFieldColor(pDefinitionStyle->maFieldColor);
+ aStyleSet.SetFieldTextColor(pDefinitionStyle->maFieldTextColor);
+ aStyleSet.SetFieldRolloverTextColor(pDefinitionStyle->maFieldRolloverTextColor);
+ aStyleSet.SetActiveColor(pDefinitionStyle->maActiveColor);
+ aStyleSet.SetActiveTextColor(pDefinitionStyle->maActiveTextColor);
+ aStyleSet.SetActiveBorderColor(pDefinitionStyle->maActiveBorderColor);
+ aStyleSet.SetDeactiveColor(pDefinitionStyle->maDeactiveColor);
+ aStyleSet.SetDeactiveTextColor(pDefinitionStyle->maDeactiveTextColor);
+ aStyleSet.SetDeactiveBorderColor(pDefinitionStyle->maDeactiveBorderColor);
+ aStyleSet.SetMenuColor(pDefinitionStyle->maMenuColor);
+ aStyleSet.SetMenuBarColor(pDefinitionStyle->maMenuBarColor);
+ aStyleSet.SetMenuBarRolloverColor(pDefinitionStyle->maMenuBarRolloverColor);
+ aStyleSet.SetMenuBorderColor(pDefinitionStyle->maMenuBorderColor);
+ aStyleSet.SetMenuTextColor(pDefinitionStyle->maMenuTextColor);
+ aStyleSet.SetMenuBarTextColor(pDefinitionStyle->maMenuBarTextColor);
+ aStyleSet.SetMenuBarRolloverTextColor(pDefinitionStyle->maMenuBarRolloverTextColor);
+ aStyleSet.SetMenuBarHighlightTextColor(pDefinitionStyle->maMenuBarHighlightTextColor);
+ aStyleSet.SetMenuHighlightColor(pDefinitionStyle->maMenuHighlightColor);
+ aStyleSet.SetMenuHighlightTextColor(pDefinitionStyle->maMenuHighlightTextColor);
+ aStyleSet.SetHighlightColor(pDefinitionStyle->maHighlightColor);
+ aStyleSet.SetHighlightTextColor(pDefinitionStyle->maHighlightTextColor);
+ aStyleSet.SetActiveTabColor(pDefinitionStyle->maActiveTabColor);
+ aStyleSet.SetInactiveTabColor(pDefinitionStyle->maInactiveTabColor);
+ aStyleSet.SetTabTextColor(pDefinitionStyle->maTabTextColor);
+ aStyleSet.SetTabRolloverTextColor(pDefinitionStyle->maTabRolloverTextColor);
+ aStyleSet.SetTabHighlightTextColor(pDefinitionStyle->maTabHighlightTextColor);
+ aStyleSet.SetDisableColor(pDefinitionStyle->maDisableColor);
+ aStyleSet.SetHelpColor(pDefinitionStyle->maHelpColor);
+ aStyleSet.SetHelpTextColor(pDefinitionStyle->maHelpTextColor);
+ aStyleSet.SetLinkColor(pDefinitionStyle->maLinkColor);
+ aStyleSet.SetVisitedLinkColor(pDefinitionStyle->maVisitedLinkColor);
+ aStyleSet.SetToolTextColor(pDefinitionStyle->maToolTextColor);
+
+ auto& pSettings = m_pWidgetDefinition->mpSettings;
+
+ int nFontSize = getSettingValueInteger(pSettings->msDefaultFontSize, 10);
+ vcl::Font aFont(FAMILY_SWISS, Size(0, nFontSize));
+ aFont.SetCharSet(osl_getThreadTextEncoding());
+ aFont.SetWeight(WEIGHT_NORMAL);
+ aFont.SetFamilyName("Liberation Sans");
+ aStyleSet.SetAppFont(aFont);
+ aStyleSet.SetHelpFont(aFont);
+ aStyleSet.SetMenuFont(aFont);
+ aStyleSet.SetToolFont(aFont);
+ aStyleSet.SetGroupFont(aFont);
+ aStyleSet.SetLabelFont(aFont);
+ aStyleSet.SetRadioCheckFont(aFont);
+ aStyleSet.SetPushButtonFont(aFont);
+ aStyleSet.SetFieldFont(aFont);
+ aStyleSet.SetIconFont(aFont);
+ aStyleSet.SetTabFont(aFont);
+
+ aFont.SetWeight(WEIGHT_BOLD);
+ aStyleSet.SetFloatTitleFont(aFont);
+ aStyleSet.SetTitleFont(aFont);
+
+ int nTitleHeight = getSettingValueInteger(pSettings->msTitleHeight, aStyleSet.GetTitleHeight());
+ aStyleSet.SetTitleHeight(nTitleHeight);
+
+ int nFloatTitleHeight
+ = getSettingValueInteger(pSettings->msFloatTitleHeight, aStyleSet.GetFloatTitleHeight());
+ aStyleSet.SetFloatTitleHeight(nFloatTitleHeight);
+
+ int nLogicWidth = getSettingValueInteger(pSettings->msListBoxPreviewDefaultLogicWidth,
+ 15); // See vcl/source/app/settings.cxx
+ int nLogicHeight = getSettingValueInteger(pSettings->msListBoxPreviewDefaultLogicHeight, 7);
+ aStyleSet.SetListBoxPreviewDefaultLogicSize(Size(nLogicWidth, nLogicHeight));
+
+ rSettings.SetStyleSettings(aStyleSet);
+
+ return true;
+}
+
+} // end vcl namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/TypeSerializer.cxx b/vcl/source/gdi/TypeSerializer.cxx
new file mode 100644
index 0000000000..49890fc38e
--- /dev/null
+++ b/vcl/source/gdi/TypeSerializer.cxx
@@ -0,0 +1,483 @@
+/* -*- 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 <vcl/TypeSerializer.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/fract.hxx>
+#include <sal/log.hxx>
+#include <comphelper/fileformat.h>
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/filter/SvmWriter.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/dibtools.hxx>
+
+TypeSerializer::TypeSerializer(SvStream& rStream)
+ : GenericTypeSerializer(rStream)
+{
+}
+
+void TypeSerializer::readGradient(Gradient& rGradient)
+{
+ VersionCompatRead aCompat(mrStream);
+
+ sal_uInt16 nStyle = 0;
+ Color aStartColor;
+ Color aEndColor;
+ sal_uInt16 nAngle = 0;
+ sal_uInt16 nBorder = 0;
+ sal_uInt16 nOffsetX = 0;
+ sal_uInt16 nOffsetY = 0;
+ sal_uInt16 nIntensityStart = 0;
+ sal_uInt16 nIntensityEnd = 0;
+ sal_uInt16 nStepCount = 0;
+
+ mrStream.ReadUInt16(nStyle);
+ readColor(aStartColor);
+ readColor(aEndColor);
+ mrStream.ReadUInt16(nAngle);
+ mrStream.ReadUInt16(nBorder);
+ mrStream.ReadUInt16(nOffsetX);
+ mrStream.ReadUInt16(nOffsetY);
+ mrStream.ReadUInt16(nIntensityStart);
+ mrStream.ReadUInt16(nIntensityEnd);
+ mrStream.ReadUInt16(nStepCount);
+
+ rGradient.SetStyle(static_cast<css::awt::GradientStyle>(nStyle));
+ rGradient.SetStartColor(aStartColor);
+ rGradient.SetEndColor(aEndColor);
+ if (nAngle > 3600)
+ {
+ SAL_WARN("vcl", "angle out of range " << nAngle);
+ nAngle = 0;
+ }
+ rGradient.SetAngle(Degree10(nAngle));
+ rGradient.SetBorder(nBorder);
+ rGradient.SetOfsX(nOffsetX);
+ rGradient.SetOfsY(nOffsetY);
+ rGradient.SetStartIntensity(nIntensityStart);
+ rGradient.SetEndIntensity(nIntensityEnd);
+ rGradient.SetSteps(nStepCount);
+}
+
+void TypeSerializer::writeGradient(const Gradient& rGradient)
+{
+ VersionCompatWrite aCompat(mrStream, 1);
+
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(rGradient.GetStyle()));
+ writeColor(rGradient.GetStartColor());
+ writeColor(rGradient.GetEndColor());
+ mrStream.WriteUInt16(rGradient.GetAngle().get());
+ mrStream.WriteUInt16(rGradient.GetBorder());
+ mrStream.WriteUInt16(rGradient.GetOfsX());
+ mrStream.WriteUInt16(rGradient.GetOfsY());
+ mrStream.WriteUInt16(rGradient.GetStartIntensity());
+ mrStream.WriteUInt16(rGradient.GetEndIntensity());
+ mrStream.WriteUInt16(rGradient.GetSteps());
+}
+
+void TypeSerializer::readGfxLink(GfxLink& rGfxLink)
+{
+ sal_uInt16 nType = 0;
+ sal_uInt32 nDataSize = 0;
+ sal_uInt32 nUserId = 0;
+
+ Size aSize;
+ MapMode aMapMode;
+ bool bMapAndSizeValid = false;
+
+ {
+ VersionCompatRead aCompat(mrStream);
+
+ // Version 1
+ mrStream.ReadUInt16(nType);
+ mrStream.ReadUInt32(nDataSize);
+ mrStream.ReadUInt32(nUserId);
+
+ if (aCompat.GetVersion() >= 2)
+ {
+ readSize(aSize);
+ readMapMode(aMapMode);
+ bMapAndSizeValid = true;
+ }
+ }
+
+ auto nRemainingData = mrStream.remainingSize();
+ if (nDataSize > nRemainingData)
+ {
+ SAL_WARN("vcl", "graphic link stream is smaller than requested size");
+ nDataSize = nRemainingData;
+ }
+
+ rGfxLink = GfxLink(BinaryDataContainer(mrStream, nDataSize), static_cast<GfxLinkType>(nType));
+ rGfxLink.SetUserId(nUserId);
+
+ if (bMapAndSizeValid)
+ {
+ rGfxLink.SetPrefSize(aSize);
+ rGfxLink.SetPrefMapMode(aMapMode);
+ }
+}
+
+void TypeSerializer::writeGfxLink(const GfxLink& rGfxLink)
+{
+ {
+ VersionCompatWrite aCompat(mrStream, 2);
+
+ // Version 1
+ mrStream.WriteUInt16(sal_uInt16(rGfxLink.GetType()));
+ mrStream.WriteUInt32(rGfxLink.GetDataSize());
+ mrStream.WriteUInt32(rGfxLink.GetUserId());
+
+ // Version 2
+ writeSize(rGfxLink.GetPrefSize());
+ writeMapMode(rGfxLink.GetPrefMapMode());
+ }
+
+ if (rGfxLink.GetDataSize())
+ {
+ if (rGfxLink.GetData())
+ mrStream.WriteBytes(rGfxLink.GetData(), rGfxLink.GetDataSize());
+ }
+}
+
+namespace
+{
+#define NATIVE_FORMAT_50 COMPAT_FORMAT('N', 'A', 'T', '5')
+
+} // end anonymous namespace
+
+void TypeSerializer::readGraphic(Graphic& rGraphic)
+{
+ if (mrStream.GetError())
+ return;
+
+ const sal_uInt64 nInitialStreamPosition = mrStream.Tell();
+ sal_uInt32 nType;
+
+ // if there is no more data, avoid further expensive
+ // reading which will create VDevs and other stuff, just to
+ // read nothing.
+ if (mrStream.remainingSize() < 4)
+ return;
+
+ // read Id
+ mrStream.ReadUInt32(nType);
+
+ if (NATIVE_FORMAT_50 == nType)
+ {
+ Graphic aGraphic;
+ GfxLink aLink;
+
+ // read compat info, destructor writes stuff into the header
+ {
+ VersionCompatRead aCompat(mrStream);
+ }
+
+ readGfxLink(aLink);
+
+ if (!mrStream.GetError() && aLink.LoadNative(aGraphic))
+ {
+ if (aLink.IsPrefMapModeValid())
+ aGraphic.SetPrefMapMode(aLink.GetPrefMapMode());
+
+ if (aLink.IsPrefSizeValid())
+ aGraphic.SetPrefSize(aLink.GetPrefSize());
+ }
+ else
+ {
+ mrStream.Seek(nInitialStreamPosition);
+ mrStream.SetError(ERRCODE_IO_WRONGFORMAT);
+ }
+ rGraphic = aGraphic;
+ }
+ else
+ {
+ BitmapEx aBitmapEx;
+ const SvStreamEndian nOldFormat = mrStream.GetEndian();
+
+ mrStream.SeekRel(-4);
+ mrStream.SetEndian(SvStreamEndian::LITTLE);
+ ReadDIBBitmapEx(aBitmapEx, mrStream);
+
+ if (!mrStream.GetError())
+ {
+ sal_uInt32 nMagic1 = 0;
+ sal_uInt32 nMagic2 = 0;
+ if (mrStream.remainingSize() >= 8)
+ {
+ sal_uInt64 nBeginPosition = mrStream.Tell();
+ mrStream.ReadUInt32(nMagic1);
+ mrStream.ReadUInt32(nMagic2);
+ mrStream.Seek(nBeginPosition);
+ }
+ if (!mrStream.GetError())
+ {
+ if (nMagic1 == 0x5344414e && nMagic2 == 0x494d4931)
+ {
+ Animation aAnimation;
+ ReadAnimation(mrStream, aAnimation);
+
+ // #108077# manually set loaded BmpEx to Animation
+ // (which skips loading its BmpEx if already done)
+ aAnimation.SetBitmapEx(aBitmapEx);
+ rGraphic = Graphic(aAnimation);
+ }
+ else
+ {
+ rGraphic = Graphic(aBitmapEx);
+ }
+ }
+ else
+ {
+ mrStream.ResetError();
+ }
+ }
+ else
+ {
+ GDIMetaFile aMetaFile;
+
+ mrStream.Seek(nInitialStreamPosition);
+ mrStream.ResetError();
+ SvmReader aReader(mrStream);
+ aReader.Read(aMetaFile);
+
+ if (!mrStream.GetError())
+ {
+ rGraphic = Graphic(aMetaFile);
+ }
+ else
+ {
+ ErrCode nOriginalError = mrStream.GetErrorCode();
+ // try to stream in Svg defining data (length, byte array and evtl. path)
+ // See below (operator<<) for more information
+ sal_uInt32 nMagic;
+ mrStream.Seek(nInitialStreamPosition);
+ mrStream.ResetError();
+ mrStream.ReadUInt32(nMagic);
+
+ if (constSvgMagic == nMagic || constWmfMagic == nMagic || constEmfMagic == nMagic
+ || constPdfMagic == nMagic)
+ {
+ sal_uInt32 nLength = 0;
+ mrStream.ReadUInt32(nLength);
+
+ if (nLength)
+ {
+ BinaryDataContainer aDataContainer(mrStream, nLength);
+
+ if (!mrStream.GetError())
+ {
+ VectorGraphicDataType aDataType(VectorGraphicDataType::Svg);
+
+ switch (nMagic)
+ {
+ case constWmfMagic:
+ aDataType = VectorGraphicDataType::Wmf;
+ break;
+ case constEmfMagic:
+ aDataType = VectorGraphicDataType::Emf;
+ break;
+ case constPdfMagic:
+ aDataType = VectorGraphicDataType::Pdf;
+ break;
+ }
+
+ auto aVectorGraphicDataPtr
+ = std::make_shared<VectorGraphicData>(aDataContainer, aDataType);
+ rGraphic = Graphic(aVectorGraphicDataPtr);
+ }
+ }
+ }
+ else
+ {
+ mrStream.SetError(nOriginalError);
+ }
+
+ mrStream.Seek(nInitialStreamPosition);
+ }
+ }
+ mrStream.SetEndian(nOldFormat);
+ }
+}
+
+void TypeSerializer::writeGraphic(const Graphic& rGraphic)
+{
+ Graphic aGraphic(rGraphic);
+
+ if (!aGraphic.makeAvailable())
+ return;
+
+ auto pGfxLink = aGraphic.GetSharedGfxLink();
+
+ if (mrStream.GetVersion() >= SOFFICE_FILEFORMAT_50
+ && (mrStream.GetCompressMode() & SvStreamCompressFlags::NATIVE) && pGfxLink
+ && pGfxLink->IsNative())
+ {
+ // native format
+ mrStream.WriteUInt32(NATIVE_FORMAT_50);
+
+ // write compat info, destructor writes stuff into the header
+ {
+ VersionCompatWrite aCompat(mrStream, 1);
+ }
+ pGfxLink->SetPrefMapMode(aGraphic.GetPrefMapMode());
+ pGfxLink->SetPrefSize(aGraphic.GetPrefSize());
+ writeGfxLink(*pGfxLink);
+ }
+ else
+ {
+ // own format
+ const SvStreamEndian nOldFormat = mrStream.GetEndian();
+ mrStream.SetEndian(SvStreamEndian::LITTLE);
+
+ switch (aGraphic.GetType())
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ auto pVectorGraphicData = aGraphic.getVectorGraphicData();
+ if (pVectorGraphicData)
+ {
+ // stream out Vector Graphic defining data (length, byte array and evtl. path)
+ // this is used e.g. in swapping out graphic data and in transporting it over UNO API
+ // as sequence of bytes, but AFAIK not written anywhere to any kind of file, so it should be
+ // no problem to extend it; only used at runtime
+ switch (pVectorGraphicData->getType())
+ {
+ case VectorGraphicDataType::Wmf:
+ {
+ mrStream.WriteUInt32(constWmfMagic);
+ break;
+ }
+ case VectorGraphicDataType::Emf:
+ {
+ mrStream.WriteUInt32(constEmfMagic);
+ break;
+ }
+ case VectorGraphicDataType::Svg:
+ {
+ mrStream.WriteUInt32(constSvgMagic);
+ break;
+ }
+ case VectorGraphicDataType::Pdf:
+ {
+ mrStream.WriteUInt32(constPdfMagic);
+ break;
+ }
+ }
+
+ sal_uInt32 nSize = pVectorGraphicData->getBinaryDataContainer().getSize();
+ mrStream.WriteUInt32(nSize);
+ pVectorGraphicData->getBinaryDataContainer().writeToStream(mrStream);
+
+ // For backwards compatibility, used to serialize path
+ mrStream.WriteUniOrByteString(u"", mrStream.GetStreamCharSet());
+ }
+ else if (aGraphic.IsAnimated())
+ {
+ WriteAnimation(mrStream, aGraphic.GetAnimation());
+ }
+ else
+ {
+ WriteDIBBitmapEx(aGraphic.GetBitmapEx(), mrStream);
+ }
+ }
+ break;
+
+ default:
+ {
+ if (aGraphic.IsSupportedGraphic())
+ {
+ if (!mrStream.GetError())
+ {
+ SvmWriter aWriter(mrStream);
+ aWriter.Write(rGraphic.GetGDIMetaFile());
+ }
+ }
+ }
+ break;
+ }
+ mrStream.SetEndian(nOldFormat);
+ }
+}
+
+bool TooLargeScaleForMapMode(const Fraction& rScale, int nDPI)
+{
+ // ImplLogicToPixel will multiply its values by this numerator * dpi and then double that
+ // result before dividing
+ if (rScale.GetNumerator() > std::numeric_limits<sal_Int32>::max() / nDPI / 2)
+ return true;
+ if (rScale.GetNumerator() < std::numeric_limits<sal_Int32>::min() / nDPI / 2)
+ return true;
+ return false;
+}
+
+static bool UselessScaleForMapMode(const Fraction& rScale)
+{
+ if (!rScale.IsValid())
+ return true;
+ // ofz#62439 negative numbers are multiplied by -1, MIN_INT32 * -1
+ // cannot be expressed as an int
+ if (rScale.GetNumerator() == std::numeric_limits<sal_Int32>::min())
+ return true;
+ if (static_cast<double>(rScale) < 0.0)
+ return true;
+ return false;
+}
+
+void TypeSerializer::readMapMode(MapMode& rMapMode)
+{
+ VersionCompatRead aCompat(mrStream);
+ sal_uInt16 nTmp16(0);
+ Point aOrigin;
+ Fraction aScaleX;
+ Fraction aScaleY;
+ bool bSimple(true);
+
+ mrStream.ReadUInt16(nTmp16);
+ MapUnit eUnit = static_cast<MapUnit>(nTmp16);
+ readPoint(aOrigin);
+ readFraction(aScaleX);
+ readFraction(aScaleY);
+ mrStream.ReadCharAsBool(bSimple);
+
+ const bool bBogus = UselessScaleForMapMode(aScaleX) || UselessScaleForMapMode(aScaleY);
+ SAL_WARN_IF(bBogus, "vcl", "invalid scale");
+
+ if (bSimple || bBogus)
+ rMapMode = MapMode(eUnit);
+ else
+ rMapMode = MapMode(eUnit, aOrigin, aScaleX, aScaleY);
+}
+
+void TypeSerializer::writeMapMode(MapMode const& rMapMode)
+{
+ VersionCompatWrite aCompat(mrStream, 1);
+
+ mrStream.WriteUInt16(sal_uInt16(rMapMode.GetMapUnit()));
+ writePoint(rMapMode.GetOrigin());
+ writeFraction(rMapMode.GetScaleX());
+ writeFraction(rMapMode.GetScaleY());
+ mrStream.WriteBool(rMapMode.IsSimple());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/WidgetDefinition.cxx b/vcl/source/gdi/WidgetDefinition.cxx
new file mode 100644
index 0000000000..1c8fb1321e
--- /dev/null
+++ b/vcl/source/gdi/WidgetDefinition.cxx
@@ -0,0 +1,186 @@
+/* -*- 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/.
+ *
+ */
+
+#include <utility>
+#include <widgetdraw/WidgetDefinition.hxx>
+
+#include <sal/config.h>
+#include <unordered_map>
+
+namespace vcl
+{
+std::shared_ptr<WidgetDefinitionPart> WidgetDefinition::getDefinition(ControlType eType,
+ ControlPart ePart)
+{
+ auto aIterator = maDefinitions.find(ControlTypeAndPart(eType, ePart));
+
+ if (aIterator != maDefinitions.end())
+ return aIterator->second;
+ return std::shared_ptr<WidgetDefinitionPart>();
+}
+
+std::vector<std::shared_ptr<WidgetDefinitionState>>
+WidgetDefinitionPart::getStates(ControlType eType, ControlPart ePart, ControlState eState,
+ ImplControlValue const& rValue)
+{
+ std::vector<std::shared_ptr<WidgetDefinitionState>> aStatesToAdd;
+
+ for (const auto& state : maStates)
+ {
+ bool bAdd = true;
+
+ if (state->msEnabled != "any"
+ && !((state->msEnabled == "true" && eState & ControlState::ENABLED)
+ || (state->msEnabled == "false" && !(eState & ControlState::ENABLED))))
+ bAdd = false;
+ if (state->msFocused != "any"
+ && !((state->msFocused == "true" && eState & ControlState::FOCUSED)
+ || (state->msFocused == "false" && !(eState & ControlState::FOCUSED))))
+ bAdd = false;
+ if (state->msPressed != "any"
+ && !((state->msPressed == "true" && eState & ControlState::PRESSED)
+ || (state->msPressed == "false" && !(eState & ControlState::PRESSED))))
+ bAdd = false;
+ if (state->msRollover != "any"
+ && !((state->msRollover == "true" && eState & ControlState::ROLLOVER)
+ || (state->msRollover == "false" && !(eState & ControlState::ROLLOVER))))
+ bAdd = false;
+ if (state->msDefault != "any"
+ && !((state->msDefault == "true" && eState & ControlState::DEFAULT)
+ || (state->msDefault == "false" && !(eState & ControlState::DEFAULT))))
+ bAdd = false;
+ if (state->msSelected != "any"
+ && !((state->msSelected == "true" && eState & ControlState::SELECTED)
+ || (state->msSelected == "false" && !(eState & ControlState::SELECTED))))
+ bAdd = false;
+
+ ButtonValue eButtonValue = rValue.getTristateVal();
+
+ if (state->msButtonValue != "any"
+ && !((state->msButtonValue == "true" && eButtonValue == ButtonValue::On)
+ || (state->msButtonValue == "false" && eButtonValue == ButtonValue::Off)))
+ {
+ bAdd = false;
+ }
+
+ OString sExtra = "any"_ostr;
+
+ switch (eType)
+ {
+ case ControlType::TabItem:
+ {
+ auto const& rTabItemValue = static_cast<TabitemValue const&>(rValue);
+
+ if (rTabItemValue.isLeftAligned() && rTabItemValue.isRightAligned()
+ && rTabItemValue.isFirst() && rTabItemValue.isLast())
+ sExtra = "first_last"_ostr;
+ else if (rTabItemValue.isLeftAligned() || rTabItemValue.isFirst())
+ sExtra = "first"_ostr;
+ else if (rTabItemValue.isRightAligned() || rTabItemValue.isLast())
+ sExtra = "last"_ostr;
+ else
+ sExtra = "middle"_ostr;
+ }
+ break;
+ case ControlType::ListHeader:
+ {
+ if (ePart == ControlPart::Arrow)
+ {
+ if (rValue.getNumericVal() == 1)
+ sExtra = "down"_ostr;
+ else
+ sExtra = "up"_ostr;
+ }
+ }
+ break;
+ case ControlType::Pushbutton:
+ {
+ auto const& rPushButtonValue = static_cast<PushButtonValue const&>(rValue);
+ if (rPushButtonValue.mbIsAction)
+ sExtra = "action"_ostr;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (state->msExtra != "any" && state->msExtra != sExtra)
+ {
+ bAdd = false;
+ }
+
+ if (bAdd)
+ aStatesToAdd.push_back(state);
+ }
+
+ return aStatesToAdd;
+}
+
+WidgetDefinitionState::WidgetDefinitionState(OString sEnabled, OString sFocused, OString sPressed,
+ OString sRollover, OString sDefault, OString sSelected,
+ OString sButtonValue, OString sExtra)
+ : msEnabled(std::move(sEnabled))
+ , msFocused(std::move(sFocused))
+ , msPressed(std::move(sPressed))
+ , msRollover(std::move(sRollover))
+ , msDefault(std::move(sDefault))
+ , msSelected(std::move(sSelected))
+ , msButtonValue(std::move(sButtonValue))
+ , msExtra(std::move(sExtra))
+{
+}
+
+void WidgetDefinitionState::addDrawRectangle(Color aStrokeColor, sal_Int32 nStrokeWidth,
+ Color aFillColor, float fX1, float fY1, float fX2,
+ float fY2, sal_Int32 nRx, sal_Int32 nRy)
+{
+ auto pCommand(std::make_shared<WidgetDrawActionRectangle>());
+ pCommand->maStrokeColor = aStrokeColor;
+ pCommand->maFillColor = aFillColor;
+ pCommand->mnStrokeWidth = nStrokeWidth;
+ pCommand->mnRx = nRx;
+ pCommand->mnRy = nRy;
+ pCommand->mfX1 = fX1;
+ pCommand->mfY1 = fY1;
+ pCommand->mfX2 = fX2;
+ pCommand->mfY2 = fY2;
+ mpWidgetDrawActions.push_back(std::move(pCommand));
+}
+
+void WidgetDefinitionState::addDrawLine(Color aStrokeColor, sal_Int32 nStrokeWidth, float fX1,
+ float fY1, float fX2, float fY2)
+{
+ auto pCommand(std::make_shared<WidgetDrawActionLine>());
+ pCommand->maStrokeColor = aStrokeColor;
+ pCommand->mnStrokeWidth = nStrokeWidth;
+ pCommand->mfX1 = fX1;
+ pCommand->mfY1 = fY1;
+ pCommand->mfX2 = fX2;
+ pCommand->mfY2 = fY2;
+ mpWidgetDrawActions.push_back(std::move(pCommand));
+}
+
+void WidgetDefinitionState::addDrawImage(OUString const& sSource)
+{
+ auto pCommand(std::make_shared<WidgetDrawActionImage>());
+ pCommand->msSource = sSource;
+ mpWidgetDrawActions.push_back(std::move(pCommand));
+}
+
+void WidgetDefinitionState::addDrawExternal(OUString const& sSource)
+{
+ auto pCommand(std::make_shared<WidgetDrawActionExternal>());
+ pCommand->msSource = sSource;
+ mpWidgetDrawActions.push_back(std::move(pCommand));
+}
+
+} // end vcl namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/WidgetDefinitionReader.cxx b/vcl/source/gdi/WidgetDefinitionReader.cxx
new file mode 100644
index 0000000000..f5373b035c
--- /dev/null
+++ b/vcl/source/gdi/WidgetDefinitionReader.cxx
@@ -0,0 +1,495 @@
+/* -*- 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/.
+ *
+ */
+
+#include <utility>
+#include <widgetdraw/WidgetDefinitionReader.hxx>
+
+#include <sal/config.h>
+#include <osl/file.hxx>
+#include <tools/stream.hxx>
+#include <o3tl/string_view.hxx>
+#include <unordered_map>
+
+namespace vcl
+{
+namespace
+{
+bool lcl_fileExists(OUString const& sFilename)
+{
+ osl::File aFile(sFilename);
+ osl::FileBase::RC eRC = aFile.open(osl_File_OpenFlag_Read);
+ return osl::FileBase::E_None == eRC;
+}
+
+int lcl_gethex(char aChar)
+{
+ if (aChar >= '0' && aChar <= '9')
+ return aChar - '0';
+ else if (aChar >= 'a' && aChar <= 'f')
+ return aChar - 'a' + 10;
+ else if (aChar >= 'A' && aChar <= 'F')
+ return aChar - 'A' + 10;
+ else
+ return 0;
+}
+
+bool readColor(OString const& rString, Color& rColor)
+{
+ if (rString.getLength() != 7)
+ return false;
+
+ const char aChar(rString[0]);
+
+ if (aChar != '#')
+ return false;
+
+ rColor.SetRed((lcl_gethex(rString[1]) << 4) | lcl_gethex(rString[2]));
+ rColor.SetGreen((lcl_gethex(rString[3]) << 4) | lcl_gethex(rString[4]));
+ rColor.SetBlue((lcl_gethex(rString[5]) << 4) | lcl_gethex(rString[6]));
+
+ return true;
+}
+
+bool readSetting(OString const& rInputString, OString& rOutputString)
+{
+ if (!rInputString.isEmpty())
+ rOutputString = rInputString;
+ return true;
+}
+
+OString getValueOrAny(OString const& rInputString)
+{
+ if (rInputString.isEmpty())
+ return "any"_ostr;
+ return rInputString;
+}
+
+ControlPart xmlStringToControlPart(std::string_view sPart)
+{
+ if (o3tl::equalsIgnoreAsciiCase(sPart, "NONE"))
+ return ControlPart::NONE;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "Entire"))
+ return ControlPart::Entire;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "ListboxWindow"))
+ return ControlPart::ListboxWindow;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "Button"))
+ return ControlPart::Button;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "ButtonUp"))
+ return ControlPart::ButtonUp;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "ButtonDown"))
+ return ControlPart::ButtonDown;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "ButtonLeft"))
+ return ControlPart::ButtonLeft;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "ButtonRight"))
+ return ControlPart::ButtonRight;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "AllButtons"))
+ return ControlPart::AllButtons;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "SeparatorHorz"))
+ return ControlPart::SeparatorHorz;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "SeparatorVert"))
+ return ControlPart::SeparatorVert;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackHorzLeft"))
+ return ControlPart::TrackHorzLeft;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackVertUpper"))
+ return ControlPart::TrackVertUpper;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackHorzRight"))
+ return ControlPart::TrackHorzRight;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackVertLower"))
+ return ControlPart::TrackVertLower;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackHorzArea"))
+ return ControlPart::TrackHorzArea;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackVertArea"))
+ return ControlPart::TrackVertArea;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "Arrow"))
+ return ControlPart::Arrow;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "ThumbHorz"))
+ return ControlPart::ThumbHorz;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "ThumbVert"))
+ return ControlPart::ThumbVert;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "MenuItem"))
+ return ControlPart::MenuItem;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "MenuItemCheckMark"))
+ return ControlPart::MenuItemCheckMark;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "MenuItemRadioMark"))
+ return ControlPart::MenuItemRadioMark;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "Separator"))
+ return ControlPart::Separator;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "SubmenuArrow"))
+ return ControlPart::SubmenuArrow;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "SubEdit"))
+ return ControlPart::SubEdit;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "DrawBackgroundHorz"))
+ return ControlPart::DrawBackgroundHorz;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "DrawBackgroundVert"))
+ return ControlPart::DrawBackgroundVert;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "TabsDrawRtl"))
+ return ControlPart::TabsDrawRtl;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "HasBackgroundTexture"))
+ return ControlPart::HasBackgroundTexture;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "HasThreeButtons"))
+ return ControlPart::HasThreeButtons;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "BackgroundWindow"))
+ return ControlPart::BackgroundWindow;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "BackgroundDialog"))
+ return ControlPart::BackgroundDialog;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "Border"))
+ return ControlPart::Border;
+ else if (o3tl::equalsIgnoreAsciiCase(sPart, "Focus"))
+ return ControlPart::Focus;
+ return ControlPart::NONE;
+}
+
+bool getControlTypeForXmlString(OString const& rString, ControlType& reType)
+{
+ static std::unordered_map<OString, ControlType> aPartMap = {
+ { "pushbutton", ControlType::Pushbutton },
+ { "radiobutton", ControlType::Radiobutton },
+ { "checkbox", ControlType::Checkbox },
+ { "combobox", ControlType::Combobox },
+ { "editbox", ControlType::Editbox },
+ { "listbox", ControlType::Listbox },
+ { "scrollbar", ControlType::Scrollbar },
+ { "spinbox", ControlType::Spinbox },
+ { "slider", ControlType::Slider },
+ { "fixedline", ControlType::Fixedline },
+ { "progress", ControlType::Progress },
+ { "levelbar", ControlType::LevelBar },
+ { "tabitem", ControlType::TabItem },
+ { "tabheader", ControlType::TabHeader },
+ { "tabpane", ControlType::TabPane },
+ { "tabbody", ControlType::TabBody },
+ { "frame", ControlType::Frame },
+ { "windowbackground", ControlType::WindowBackground },
+ { "toolbar", ControlType::Toolbar },
+ { "listnode", ControlType::ListNode },
+ { "listnet", ControlType::ListNet },
+ { "listheader", ControlType::ListHeader },
+ { "menubar", ControlType::Menubar },
+ { "menupopup", ControlType::MenuPopup },
+ { "tooltip", ControlType::Tooltip },
+ };
+
+ auto const& rIterator = aPartMap.find(rString);
+ if (rIterator != aPartMap.end())
+ {
+ reType = rIterator->second;
+ return true;
+ }
+ return false;
+}
+
+} // end anonymous namespace
+
+WidgetDefinitionReader::WidgetDefinitionReader(OUString aDefinitionFile, OUString aResourcePath)
+ : m_rDefinitionFile(std::move(aDefinitionFile))
+ , m_rResourcePath(std::move(aResourcePath))
+{
+}
+
+void WidgetDefinitionReader::readDrawingDefinition(
+ tools::XmlWalker& rWalker, const std::shared_ptr<WidgetDefinitionState>& rpState)
+{
+ rWalker.children();
+ while (rWalker.isValid())
+ {
+ if (rWalker.name() == "rect")
+ {
+ Color aStrokeColor;
+ readColor(rWalker.attribute("stroke"_ostr), aStrokeColor);
+ Color aFillColor;
+ readColor(rWalker.attribute("fill"_ostr), aFillColor);
+ OString sStrokeWidth = rWalker.attribute("stroke-width"_ostr);
+ sal_Int32 nStrokeWidth = -1;
+ if (!sStrokeWidth.isEmpty())
+ nStrokeWidth = sStrokeWidth.toInt32();
+
+ sal_Int32 nRx = -1;
+ OString sRx = rWalker.attribute("rx"_ostr);
+ if (!sRx.isEmpty())
+ nRx = sRx.toInt32();
+
+ sal_Int32 nRy = -1;
+ OString sRy = rWalker.attribute("ry"_ostr);
+ if (!sRy.isEmpty())
+ nRy = sRy.toInt32();
+
+ OString sX1 = rWalker.attribute("x1"_ostr);
+ float fX1 = sX1.isEmpty() ? 0.0 : sX1.toFloat();
+
+ OString sY1 = rWalker.attribute("y1"_ostr);
+ float fY1 = sY1.isEmpty() ? 0.0 : sY1.toFloat();
+
+ OString sX2 = rWalker.attribute("x2"_ostr);
+ float fX2 = sX2.isEmpty() ? 1.0 : sX2.toFloat();
+
+ OString sY2 = rWalker.attribute("y2"_ostr);
+ float fY2 = sY2.isEmpty() ? 1.0 : sY2.toFloat();
+
+ rpState->addDrawRectangle(aStrokeColor, nStrokeWidth, aFillColor, fX1, fY1, fX2, fY2,
+ nRx, nRy);
+ }
+ else if (rWalker.name() == "line")
+ {
+ Color aStrokeColor;
+ readColor(rWalker.attribute("stroke"_ostr), aStrokeColor);
+
+ OString sStrokeWidth = rWalker.attribute("stroke-width"_ostr);
+ sal_Int32 nStrokeWidth = -1;
+ if (!sStrokeWidth.isEmpty())
+ nStrokeWidth = sStrokeWidth.toInt32();
+
+ OString sX1 = rWalker.attribute("x1"_ostr);
+ float fX1 = sX1.isEmpty() ? -1.0 : sX1.toFloat();
+
+ OString sY1 = rWalker.attribute("y1"_ostr);
+ float fY1 = sY1.isEmpty() ? -1.0 : sY1.toFloat();
+
+ OString sX2 = rWalker.attribute("x2"_ostr);
+ float fX2 = sX2.isEmpty() ? -1.0 : sX2.toFloat();
+
+ OString sY2 = rWalker.attribute("y2"_ostr);
+ float fY2 = sY2.isEmpty() ? -1.0 : sY2.toFloat();
+
+ rpState->addDrawLine(aStrokeColor, nStrokeWidth, fX1, fY1, fX2, fY2);
+ }
+ else if (rWalker.name() == "image")
+ {
+ OString sSource = rWalker.attribute("source"_ostr);
+ rpState->addDrawImage(m_rResourcePath
+ + OStringToOUString(sSource, RTL_TEXTENCODING_UTF8));
+ }
+ else if (rWalker.name() == "external")
+ {
+ OString sSource = rWalker.attribute("source"_ostr);
+ rpState->addDrawExternal(m_rResourcePath
+ + OStringToOUString(sSource, RTL_TEXTENCODING_UTF8));
+ }
+ rWalker.next();
+ }
+ rWalker.parent();
+}
+
+void WidgetDefinitionReader::readDefinition(tools::XmlWalker& rWalker,
+ WidgetDefinition& rWidgetDefinition, ControlType eType)
+{
+ rWalker.children();
+ while (rWalker.isValid())
+ {
+ if (rWalker.name() == "part")
+ {
+ OString sPart = rWalker.attribute("value"_ostr);
+ ControlPart ePart = xmlStringToControlPart(sPart);
+
+ std::shared_ptr<WidgetDefinitionPart> pPart = std::make_shared<WidgetDefinitionPart>();
+
+ OString sWidth = rWalker.attribute("width"_ostr);
+ if (!sWidth.isEmpty())
+ {
+ sal_Int32 nWidth = sWidth.isEmpty() ? 0 : sWidth.toInt32();
+ pPart->mnWidth = nWidth;
+ }
+
+ OString sHeight = rWalker.attribute("height"_ostr);
+ if (!sHeight.isEmpty())
+ {
+ sal_Int32 nHeight = sHeight.isEmpty() ? 0 : sHeight.toInt32();
+ pPart->mnHeight = nHeight;
+ }
+
+ OString sMarginHeight = rWalker.attribute("margin-height"_ostr);
+ if (!sMarginHeight.isEmpty())
+ {
+ sal_Int32 nMarginHeight = sMarginHeight.isEmpty() ? 0 : sMarginHeight.toInt32();
+ pPart->mnMarginHeight = nMarginHeight;
+ }
+
+ OString sMarginWidth = rWalker.attribute("margin-width"_ostr);
+ if (!sMarginWidth.isEmpty())
+ {
+ sal_Int32 nMarginWidth = sMarginWidth.isEmpty() ? 0 : sMarginWidth.toInt32();
+ pPart->mnMarginWidth = nMarginWidth;
+ }
+
+ OString sOrientation = rWalker.attribute("orientation"_ostr);
+ if (!sOrientation.isEmpty())
+ {
+ pPart->msOrientation = sOrientation;
+ }
+
+ rWidgetDefinition.maDefinitions.emplace(ControlTypeAndPart(eType, ePart), pPart);
+ readPart(rWalker, pPart);
+ }
+ rWalker.next();
+ }
+ rWalker.parent();
+}
+
+void WidgetDefinitionReader::readPart(tools::XmlWalker& rWalker,
+ std::shared_ptr<WidgetDefinitionPart> rpPart)
+{
+ rWalker.children();
+ while (rWalker.isValid())
+ {
+ if (rWalker.name() == "state")
+ {
+ OString sEnabled = getValueOrAny(rWalker.attribute("enabled"_ostr));
+ OString sFocused = getValueOrAny(rWalker.attribute("focused"_ostr));
+ OString sPressed = getValueOrAny(rWalker.attribute("pressed"_ostr));
+ OString sRollover = getValueOrAny(rWalker.attribute("rollover"_ostr));
+ OString sDefault = getValueOrAny(rWalker.attribute("default"_ostr));
+ OString sSelected = getValueOrAny(rWalker.attribute("selected"_ostr));
+ OString sButtonValue = getValueOrAny(rWalker.attribute("button-value"_ostr));
+ OString sExtra = getValueOrAny(rWalker.attribute("extra"_ostr));
+
+ std::shared_ptr<WidgetDefinitionState> pState = std::make_shared<WidgetDefinitionState>(
+ sEnabled, sFocused, sPressed, sRollover, sDefault, sSelected, sButtonValue, sExtra);
+
+ rpPart->maStates.push_back(pState);
+ readDrawingDefinition(rWalker, pState);
+ }
+ rWalker.next();
+ }
+ rWalker.parent();
+}
+
+bool WidgetDefinitionReader::read(WidgetDefinition& rWidgetDefinition)
+{
+ if (!lcl_fileExists(m_rDefinitionFile))
+ return false;
+
+ auto pStyle = std::make_shared<WidgetDefinitionStyle>();
+
+ std::unordered_map<OString, Color*> aStyleColorMap = {
+ { "faceColor", &pStyle->maFaceColor },
+ { "checkedColor", &pStyle->maCheckedColor },
+ { "lightColor", &pStyle->maLightColor },
+ { "lightBorderColor", &pStyle->maLightBorderColor },
+ { "shadowColor", &pStyle->maShadowColor },
+ { "darkShadowColor", &pStyle->maDarkShadowColor },
+ { "buttonTextColor", &pStyle->maButtonTextColor },
+ { "defaultActionButtonTextColor", &pStyle->maDefaultActionButtonTextColor },
+ { "actionButtonTextColor", &pStyle->maActionButtonTextColor },
+ { "actionButtonRolloverTextColor", &pStyle->maActionButtonRolloverTextColor },
+ { "buttonRolloverTextColor", &pStyle->maButtonRolloverTextColor },
+ { "radioCheckTextColor", &pStyle->maRadioCheckTextColor },
+ { "groupTextColor", &pStyle->maGroupTextColor },
+ { "labelTextColor", &pStyle->maLabelTextColor },
+ { "windowColor", &pStyle->maWindowColor },
+ { "windowTextColor", &pStyle->maWindowTextColor },
+ { "dialogColor", &pStyle->maDialogColor },
+ { "dialogTextColor", &pStyle->maDialogTextColor },
+ { "workspaceColor", &pStyle->maWorkspaceColor },
+ { "monoColor", &pStyle->maMonoColor },
+ { "fieldColor", &pStyle->maFieldColor },
+ { "fieldTextColor", &pStyle->maFieldTextColor },
+ { "fieldRolloverTextColor", &pStyle->maFieldRolloverTextColor },
+ { "activeColor", &pStyle->maActiveColor },
+ { "activeTextColor", &pStyle->maActiveTextColor },
+ { "activeBorderColor", &pStyle->maActiveBorderColor },
+ { "deactiveColor", &pStyle->maDeactiveColor },
+ { "deactiveTextColor", &pStyle->maDeactiveTextColor },
+ { "deactiveBorderColor", &pStyle->maDeactiveBorderColor },
+ { "menuColor", &pStyle->maMenuColor },
+ { "menuBarColor", &pStyle->maMenuBarColor },
+ { "menuBarRolloverColor", &pStyle->maMenuBarRolloverColor },
+ { "menuBorderColor", &pStyle->maMenuBorderColor },
+ { "menuTextColor", &pStyle->maMenuTextColor },
+ { "menuBarTextColor", &pStyle->maMenuBarTextColor },
+ { "menuBarRolloverTextColor", &pStyle->maMenuBarRolloverTextColor },
+ { "menuBarHighlightTextColor", &pStyle->maMenuBarHighlightTextColor },
+ { "menuHighlightColor", &pStyle->maMenuHighlightColor },
+ { "menuHighlightTextColor", &pStyle->maMenuHighlightTextColor },
+ { "highlightColor", &pStyle->maHighlightColor },
+ { "highlightTextColor", &pStyle->maHighlightTextColor },
+ { "activeTabColor", &pStyle->maActiveTabColor },
+ { "inactiveTabColor", &pStyle->maInactiveTabColor },
+ { "tabTextColor", &pStyle->maTabTextColor },
+ { "tabRolloverTextColor", &pStyle->maTabRolloverTextColor },
+ { "tabHighlightTextColor", &pStyle->maTabHighlightTextColor },
+ { "disableColor", &pStyle->maDisableColor },
+ { "helpColor", &pStyle->maHelpColor },
+ { "helpTextColor", &pStyle->maHelpTextColor },
+ { "linkColor", &pStyle->maLinkColor },
+ { "visitedLinkColor", &pStyle->maVisitedLinkColor },
+ { "toolTextColor", &pStyle->maToolTextColor },
+ };
+
+ rWidgetDefinition.mpStyle = pStyle;
+
+ auto pSettings = std::make_shared<WidgetDefinitionSettings>();
+
+ std::unordered_map<OString, OString*> aSettingMap = {
+ { "noActiveTabTextRaise", &pSettings->msNoActiveTabTextRaise },
+ { "centeredTabs", &pSettings->msCenteredTabs },
+ { "listBoxEntryMargin", &pSettings->msListBoxEntryMargin },
+ { "defaultFontSize", &pSettings->msDefaultFontSize },
+ { "titleHeight", &pSettings->msTitleHeight },
+ { "floatTitleHeight", &pSettings->msFloatTitleHeight },
+ { "listBoxPreviewDefaultLogicWidth", &pSettings->msListBoxPreviewDefaultLogicWidth },
+ { "listBoxPreviewDefaultLogicHeight", &pSettings->msListBoxPreviewDefaultLogicHeight },
+ };
+
+ rWidgetDefinition.mpSettings = pSettings;
+
+ SvFileStream aFileStream(m_rDefinitionFile, StreamMode::READ);
+
+ tools::XmlWalker aWalker;
+ if (!aWalker.open(&aFileStream))
+ return false;
+
+ if (aWalker.name() != "widgets")
+ return false;
+
+ aWalker.children();
+ while (aWalker.isValid())
+ {
+ ControlType eType;
+ if (aWalker.name() == "style")
+ {
+ aWalker.children();
+ while (aWalker.isValid())
+ {
+ auto pair = aStyleColorMap.find(aWalker.name());
+ if (pair != aStyleColorMap.end())
+ {
+ readColor(aWalker.attribute("value"_ostr), *pair->second);
+ }
+ aWalker.next();
+ }
+ aWalker.parent();
+ }
+ if (aWalker.name() == "settings")
+ {
+ aWalker.children();
+ while (aWalker.isValid())
+ {
+ auto pair = aSettingMap.find(aWalker.name());
+ if (pair != aSettingMap.end())
+ {
+ readSetting(aWalker.attribute("value"_ostr), *pair->second);
+ }
+ aWalker.next();
+ }
+ aWalker.parent();
+ }
+ else if (getControlTypeForXmlString(aWalker.name(), eType))
+ {
+ readDefinition(aWalker, rWidgetDefinition, eType);
+ }
+ aWalker.next();
+ }
+ aWalker.parent();
+
+ return true;
+}
+
+} // end vcl namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/configsettings.cxx b/vcl/source/gdi/configsettings.cxx
new file mode 100644
index 0000000000..5586f67a61
--- /dev/null
+++ b/vcl/source/gdi/configsettings.cxx
@@ -0,0 +1,135 @@
+/* -*- 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 <configsettings.hxx>
+
+#include <svdata.hxx>
+
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <o3tl/any.hxx>
+#include <sal/log.hxx>
+
+using namespace utl;
+using namespace vcl;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::container;
+
+constexpr OUStringLiteral SETTINGS_CONFIGNODE = u"VCL/Settings";
+
+SettingsConfigItem* SettingsConfigItem::get()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( ! pSVData->mpSettingsConfigItem )
+ pSVData->mpSettingsConfigItem.reset( new SettingsConfigItem() );
+ return pSVData->mpSettingsConfigItem.get();
+}
+
+SettingsConfigItem::SettingsConfigItem()
+ : ConfigItem( SETTINGS_CONFIGNODE, ConfigItemMode::NONE ),
+ m_aSettings( 0 )
+{
+ getValues();
+}
+
+SettingsConfigItem::~SettingsConfigItem()
+{
+ assert(!IsModified()); // should have been committed
+}
+
+void SettingsConfigItem::ImplCommit()
+{
+ for (auto const& setting : m_aSettings)
+ {
+ OUString aKeyName( setting.first );
+ /*bool bAdded =*/ AddNode( OUString(), aKeyName );
+ Sequence< PropertyValue > aValues( setting.second.size() );
+ PropertyValue* pValues = aValues.getArray();
+ int nIndex = 0;
+ for (auto const& elem : setting.second)
+ {
+ pValues[nIndex].Name = aKeyName + "/" + elem.first;
+ pValues[nIndex].Handle = 0;
+ pValues[nIndex].Value <<= elem.second;
+ pValues[nIndex].State = PropertyState_DIRECT_VALUE;
+ nIndex++;
+ }
+ ReplaceSetProperties( aKeyName, aValues );
+ }
+}
+
+void SettingsConfigItem::Notify( const Sequence< OUString >& )
+{
+ getValues();
+}
+
+void SettingsConfigItem::getValues()
+{
+ m_aSettings.clear();
+
+ const Sequence< OUString > aNames( GetNodeNames( OUString() ) );
+
+ for( const auto& aKeyName : aNames )
+ {
+#if OSL_DEBUG_LEVEL > 2
+ SAL_INFO( "vcl", "found settings data for " << aKeyName );
+#endif
+ const Sequence< OUString > aKeys( GetNodeNames( aKeyName ) );
+ Sequence< OUString > aSettingsKeys( aKeys.getLength() );
+ std::transform(aKeys.begin(), aKeys.end(), aSettingsKeys.getArray(),
+ [&aKeyName](const OUString& rKey) -> OUString { return aKeyName + "/" + rKey; });
+ const Sequence< Any > aValues( GetProperties( aSettingsKeys ) );
+ for( int i = 0; i < aValues.getLength(); i++ )
+ {
+ if( auto pLine = o3tl::tryAccess<OUString>(aValues[i]) )
+ {
+ if( !pLine->isEmpty() )
+ m_aSettings[ aKeyName ][ aKeys[i] ] = *pLine;
+#if OSL_DEBUG_LEVEL > 2
+ SAL_INFO( "vcl", " \"" << aKeys.getConstArray()[i] << "\"=\"" << *pLine << "\"" );
+#endif
+ }
+ }
+ }
+}
+
+OUString SettingsConfigItem::getValue( const OUString& rGroup, const OUString& rKey ) const
+{
+ std::unordered_map< OUString, SmallOUStrMap >::const_iterator group = m_aSettings.find( rGroup );
+ if( group == m_aSettings.end() || group->second.find( rKey ) == group->second.end() )
+ {
+ return OUString();
+ }
+ return group->second.find(rKey)->second;
+}
+
+void SettingsConfigItem::setValue( const OUString& rGroup, const OUString& rKey, const OUString& rValue )
+{
+ bool bModified = m_aSettings[ rGroup ][ rKey ] != rValue;
+ if( bModified )
+ {
+ m_aSettings[ rGroup ][ rKey ] = rValue;
+ SetModified();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/cvtgrf.cxx b/vcl/source/gdi/cvtgrf.cxx
new file mode 100644
index 0000000000..51fb6df10e
--- /dev/null
+++ b/vcl/source/gdi/cvtgrf.cxx
@@ -0,0 +1,69 @@
+/* -*- 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 <vcl/cvtgrf.hxx>
+#include <tools/stream.hxx>
+
+#include <svdata.hxx>
+
+// Callback
+GraphicConverter::GraphicConverter()
+{
+}
+
+ErrCode GraphicConverter::Import( SvStream& rIStm, Graphic& rGraphic, ConvertDataFormat nFormat )
+{
+ GraphicConverter* pCvt = ImplGetSVData()->maGDIData.mxGrfConverter.get();
+ ErrCode nRet = ERRCODE_IO_GENERAL;
+
+ if( pCvt && pCvt->GetFilterHdl().IsSet() )
+ {
+ ConvertData aData( rGraphic, rIStm, nFormat );
+
+ if( pCvt->GetFilterHdl().Call( aData ) )
+ {
+ rGraphic = aData.maGraphic;
+ nRet = ERRCODE_NONE;
+ }
+ else if( rIStm.GetError() )
+ nRet = rIStm.GetError();
+ }
+
+ return nRet;
+}
+
+ErrCode GraphicConverter::Export( SvStream& rOStm, const Graphic& rGraphic, ConvertDataFormat nFormat )
+{
+ GraphicConverter* pCvt = ImplGetSVData()->maGDIData.mxGrfConverter.get();
+ ErrCode nRet = ERRCODE_IO_GENERAL;
+
+ if( pCvt && pCvt->GetFilterHdl().IsSet() )
+ {
+ ConvertData aData( rGraphic, rOStm, nFormat );
+
+ if( pCvt->GetFilterHdl().Call( aData ) )
+ nRet = ERRCODE_NONE;
+ else if( rOStm.GetError() )
+ nRet = rOStm.GetError();
+ }
+
+ return nRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/embeddedfontshelper.cxx b/vcl/source/gdi/embeddedfontshelper.cxx
new file mode 100644
index 0000000000..a8a4f7e983
--- /dev/null
+++ b/vcl/source/gdi/embeddedfontshelper.cxx
@@ -0,0 +1,363 @@
+/* -*- 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/.
+ */
+
+#include <sal/config.h>
+
+#include <memory>
+#include <config_folders.h>
+#include <config_eot.h>
+
+#include <osl/file.hxx>
+#include <rtl/bootstrap.hxx>
+#include <sal/log.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/embeddedfontshelper.hxx>
+#include <com/sun/star/io/XInputStream.hpp>
+
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <salgdi.hxx>
+#include <sft.hxx>
+
+
+#if ENABLE_EOT
+extern "C"
+{
+namespace libeot
+{
+#include <libeot/libeot.h>
+} // namespace libeot
+} // extern "C"
+#endif
+
+using namespace com::sun::star;
+using namespace vcl;
+
+static void clearDir( const OUString& path )
+{
+ osl::Directory dir( path );
+ if( dir.reset() == osl::Directory::E_None )
+ {
+ for(;;)
+ {
+ osl::DirectoryItem item;
+ if( dir.getNextItem( item ) != osl::Directory::E_None )
+ break;
+ osl::FileStatus status( osl_FileStatus_Mask_FileURL );
+ if( item.getFileStatus( status ) == osl::File::E_None )
+ osl::File::remove( status.getFileURL());
+ }
+ }
+}
+
+void EmbeddedFontsHelper::clearTemporaryFontFiles()
+{
+ OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
+ rtl::Bootstrap::expandMacros( path );
+ path += "/user/temp/embeddedfonts/";
+ clearDir( path + "fromdocs/" );
+ clearDir( path + "fromsystem/" );
+}
+
+bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference< io::XInputStream >& stream, const OUString& fontName,
+ std::u16string_view extra, std::vector< unsigned char > const & key, bool eot )
+{
+ OUString fileUrl = EmbeddedFontsHelper::fileUrlForTemporaryFont( fontName, extra );
+ osl::File file( fileUrl );
+ switch( file.open( osl_File_OpenFlag_Create | osl_File_OpenFlag_Write ))
+ {
+ case osl::File::E_None:
+ break; // ok
+ case osl::File::E_EXIST:
+ return true; // Assume it's already been added correctly.
+ default:
+ SAL_WARN( "vcl.fonts", "Cannot open file for temporary font" );
+ return false;
+ }
+ size_t keyPos = 0;
+ std::vector< char > fontData;
+ fontData.reserve( 1000000 );
+ for(;;)
+ {
+ uno::Sequence< sal_Int8 > buffer;
+ sal_uInt64 read = stream->readBytes( buffer, 1024 );
+ auto bufferRange = asNonConstRange(buffer);
+ for( sal_uInt64 pos = 0;
+ pos < read && keyPos < key.size();
+ ++pos )
+ bufferRange[ pos ] ^= key[ keyPos++ ];
+ // if eot, don't write the file out yet, since we need to unpack it first.
+ if( !eot && read > 0 )
+ {
+ sal_uInt64 writtenTotal = 0;
+ while( writtenTotal < read )
+ {
+ sal_uInt64 written;
+ file.write( buffer.getConstArray(), read, written );
+ writtenTotal += written;
+ }
+ }
+ fontData.insert( fontData.end(), buffer.getConstArray(), buffer.getConstArray() + read );
+ if( read <= 0 )
+ break;
+ }
+ bool sufficientFontRights(false);
+#if ENABLE_EOT
+ if( eot )
+ {
+ unsigned uncompressedFontSize = 0;
+ unsigned char *nakedPointerToUncompressedFont = nullptr;
+ libeot::EOTMetadata eotMetadata;
+ libeot::EOTError uncompressError =
+ libeot::EOT2ttf_buffer( reinterpret_cast<unsigned char *>(fontData.data()), fontData.size(), &eotMetadata, &nakedPointerToUncompressedFont, &uncompressedFontSize );
+ std::shared_ptr<unsigned char> uncompressedFont( nakedPointerToUncompressedFont, libeot::EOTfreeBuffer );
+ if( uncompressError != libeot::EOT_SUCCESS )
+ {
+ SAL_WARN( "vcl.fonts", "Failed to uncompress font" );
+ osl::File::remove( fileUrl );
+ return false;
+ }
+ sal_uInt64 writtenTotal = 0;
+ while( writtenTotal < uncompressedFontSize )
+ {
+ sal_uInt64 written;
+ if( file.write( uncompressedFont.get() + writtenTotal, uncompressedFontSize - writtenTotal, written ) != osl::File::E_None )
+ {
+ SAL_WARN( "vcl.fonts", "Error writing temporary font file" );
+ osl::File::remove( fileUrl );
+ return false;
+ }
+ writtenTotal += written;
+ }
+ sufficientFontRights = libeot::EOTcanLegallyEdit( &eotMetadata );
+ libeot::EOTfreeMetadata( &eotMetadata );
+ }
+#endif
+
+ if( file.close() != osl::File::E_None )
+ {
+ SAL_WARN( "vcl.fonts", "Writing temporary font file failed" );
+ osl::File::remove( fileUrl );
+ return false;
+ }
+ if( !eot )
+ {
+ sufficientFontRights = sufficientTTFRights(fontData.data(), fontData.size(), FontRights::EditingAllowed);
+ }
+ if( !sufficientFontRights )
+ {
+ // It would be actually better to open the document in read-only mode in this case,
+ // warn the user about this, and provide a button to drop the font(s) in order
+ // to switch to editing.
+ SAL_INFO( "vcl.fonts", "Ignoring embedded font that is not usable for editing" );
+ osl::File::remove( fileUrl );
+ return false;
+ }
+ m_aAccumulatedFonts.emplace_back(std::make_pair(fontName, fileUrl));
+ return true;
+}
+
+namespace
+{
+ struct UpdateFontsGuard
+ {
+ UpdateFontsGuard()
+ {
+ OutputDevice::ImplClearAllFontData(true);
+ }
+
+ ~UpdateFontsGuard()
+ {
+ OutputDevice::ImplRefreshAllFontData(true);
+ }
+ };
+}
+
+void EmbeddedFontsHelper::activateFonts()
+{
+ if (m_aAccumulatedFonts.empty())
+ return;
+ UpdateFontsGuard aUpdateFontsGuard;
+ for (const auto& rEntry : m_aAccumulatedFonts)
+ EmbeddedFontsHelper::activateFont(rEntry.first, rEntry.second);
+ m_aAccumulatedFonts.clear();
+}
+
+OUString EmbeddedFontsHelper::fileUrlForTemporaryFont( const OUString& fontName, std::u16string_view extra )
+{
+ OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
+ rtl::Bootstrap::expandMacros( path );
+ path += "/user/temp/embeddedfonts/fromdocs/";
+ osl::Directory::createPath( path );
+ OUString filename = fontName;
+ static int uniqueCounter = 0;
+ if( extra == u"?" )
+ filename += OUString::number( uniqueCounter++ );
+ else
+ filename += extra;
+ filename += ".ttf"; // TODO is it always ttf?
+ return path + filename;
+}
+
+void EmbeddedFontsHelper::activateFont( const OUString& fontName, const OUString& fileUrl )
+{
+ OutputDevice *pDevice = Application::GetDefaultDevice();
+ pDevice->AddTempDevFont(fileUrl, fontName);
+}
+
+// Check if it's (legally) allowed to embed the font file into a document
+// (ttf has a flag allowing this). PhysicalFontFace::IsEmbeddable() appears
+// to have a different meaning (guessing from code, IsSubsettable() might
+// possibly mean it's ttf, while IsEmbeddable() might mean it's type1).
+// So just try to open the data as ttf and see.
+bool EmbeddedFontsHelper::sufficientTTFRights( const void* data, tools::Long size, FontRights rights )
+{
+ TrueTypeFont* font;
+ if( OpenTTFontBuffer( data, size, 0 /*TODO*/, &font ) == SFErrCodes::Ok )
+ {
+ TTGlobalFontInfo info;
+ GetTTGlobalFontInfo( font, &info );
+ CloseTTFont( font );
+ // https://www.microsoft.com/typography/otspec/os2.htm#fst
+ int copyright = info.typeFlags;
+ switch( rights )
+ {
+ case FontRights::ViewingAllowed:
+ // Embedding not restricted completely.
+ return ( copyright & 0x02 ) != 0x02;
+ case FontRights::EditingAllowed:
+ // Font is installable or editable.
+ return copyright == 0 || ( copyright & 0x08 );
+ }
+ }
+ return true; // no known restriction
+}
+
+OUString EmbeddedFontsHelper::fontFileUrl( std::u16string_view familyName, FontFamily family, FontItalic italic,
+ FontWeight weight, FontPitch pitch, FontRights rights )
+{
+ OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
+ rtl::Bootstrap::expandMacros( path );
+ path += "/user/temp/embeddedfonts/fromsystem/";
+ osl::Directory::createPath( path );
+ OUString filename = OUString::Concat(familyName) + "_" + OUString::number( family ) + "_" + OUString::number( italic )
+ + "_" + OUString::number( weight ) + "_" + OUString::number( pitch )
+ + ".ttf"; // TODO is it always ttf?
+ OUString url = path + filename;
+ if( osl::File( url ).open( osl_File_OpenFlag_Read ) == osl::File::E_None ) // = exists()
+ {
+ // File with contents of the font file already exists, assume it's been created by a previous call.
+ return url;
+ }
+ bool ok = false;
+ SalGraphics* graphics = Application::GetDefaultDevice()->GetGraphics();
+ vcl::font::PhysicalFontCollection fonts;
+ graphics->GetDevFontList( &fonts );
+ std::unique_ptr< vcl::font::PhysicalFontFaceCollection > fontInfo( fonts.GetFontFaceCollection());
+ vcl::font::PhysicalFontFace* selected = nullptr;
+
+ // Maybe we don't find the perfect match for the font. E.G. we have fonts with the same family name
+ // but not same bold or italic etc...
+ // In this case we add all the fonts having the family name of the used font:
+ // - we store all these fonts in familyNameFonts during loop
+ // - if we haven't found the perfect match we store all fonts in familyNameFonts
+ typedef std::vector<vcl::font::PhysicalFontFace*> FontList;
+ FontList familyNameFonts;
+
+ for( int i = 0;
+ i < fontInfo->Count();
+ ++i )
+ {
+ vcl::font::PhysicalFontFace* f = fontInfo->Get( i );
+ if( f->GetFamilyName() == familyName )
+ {
+ // Ignore comparing text encodings, at least for now. They cannot be trivially compared
+ // (e.g. UCS2 and UTF8 are technically the same characters, just have different encoding,
+ // and just having a unicode font doesn't say what glyphs it actually contains).
+ // It is possible that it still may be needed to do at least some checks here
+ // for some encodings (can one font have more font files for more encodings?).
+ if(( family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
+ && ( italic == ITALIC_DONTKNOW || f->GetItalic() == italic )
+ && ( weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
+ && ( pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
+ { // Exact match, return it immediately.
+ selected = f;
+ break;
+ }
+ if(( f->GetFamilyType() == FAMILY_DONTKNOW || family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
+ && ( f->GetItalic() == ITALIC_DONTKNOW || italic == ITALIC_DONTKNOW || f->GetItalic() == italic )
+ && ( f->GetWeight() == WEIGHT_DONTKNOW || weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
+ && ( f->GetPitch() == PITCH_DONTKNOW || pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
+ { // Some fonts specify 'DONTKNOW' for some things, still a good match, if we don't find a better one.
+ selected = f;
+ }
+ // adding "not perfect match" to familyNameFonts vector
+ familyNameFonts.push_back(f);
+
+ }
+ }
+
+ // if we have found a perfect match we will add only "selected", otherwise all familyNameFonts
+ FontList fontsToAdd = (selected ? FontList(1, selected) : std::move(familyNameFonts));
+
+ for (vcl::font::PhysicalFontFace* f : fontsToAdd)
+ {
+ if (!selected) { // recalculate file not for "not perfect match"
+ filename = OUString::Concat(familyName) + "_" + OUString::number(f->GetFamilyType()) + "_" +
+ OUString::number(f->GetItalic()) + "_" + OUString::number(f->GetWeight()) + "_" +
+ OUString::number(f->GetPitch()) + ".ttf"; // TODO is it always ttf?
+ url = path + filename;
+ if (osl::File(url).open(osl_File_OpenFlag_Read) == osl::File::E_None) // = exists()
+ {
+ // File with contents of the font file already exists, assume it's been created by a previous call.
+ continue;
+ }
+ }
+ auto aFontData(f->GetRawFontData(0));
+ if (!aFontData.empty())
+ {
+ auto data = aFontData.data();
+ auto size = aFontData.size();
+ if( sufficientTTFRights( data, size, rights ))
+ {
+ osl::File file( url );
+ if( file.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) == osl::File::E_None )
+ {
+ sal_uInt64 written = 0;
+ sal_uInt64 totalSize = size;
+ bool error = false;
+ while( written < totalSize && !error)
+ {
+ sal_uInt64 nowWritten;
+ switch( file.write( data + written, size - written, nowWritten ))
+ {
+ case osl::File::E_None:
+ written += nowWritten;
+ break;
+ case osl::File::E_AGAIN:
+ case osl::File::E_INTR:
+ break;
+ default:
+ error = true;
+ break;
+ }
+ }
+ file.close();
+ if( error )
+ osl::File::remove( url );
+ else
+ ok = true;
+ }
+ }
+ }
+ }
+ return ok ? url : "";
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/gdi/extoutdevdata.cxx b/vcl/source/gdi/extoutdevdata.cxx
new file mode 100644
index 0000000000..1bb84a6694
--- /dev/null
+++ b/vcl/source/gdi/extoutdevdata.cxx
@@ -0,0 +1,27 @@
+/* -*- 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 <vcl/extoutdevdata.hxx>
+
+namespace vcl
+{
+ExtOutDevData::~ExtOutDevData() {}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/formpdfexport.cxx b/vcl/source/gdi/formpdfexport.cxx
new file mode 100644
index 0000000000..ac952491a2
--- /dev/null
+++ b/vcl/source/gdi/formpdfexport.cxx
@@ -0,0 +1,824 @@
+/* -*- 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 <memory>
+#include <vcl/formpdfexport.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/lineend.hxx>
+#include <unordered_map>
+#include <sal/log.hxx>
+
+#include <com/sun/star/container/XIndexAccess.hpp>
+#include <com/sun/star/form/XForm.hpp>
+#include <com/sun/star/container/XChild.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/form/FormComponentType.hpp>
+#include <com/sun/star/awt/TextAlign.hpp>
+#include <com/sun/star/awt/XControl.hpp>
+#include <com/sun/star/style/VerticalAlignment.hpp>
+#include <com/sun/star/form/FormButtonType.hpp>
+#include <com/sun/star/form/FormSubmitMethod.hpp>
+
+#include <toolkit/helper/vclunohelper.hxx>
+#include <vcl/pdfextoutdevdata.hxx>
+#include <vcl/unohelp.hxx>
+
+#include <algorithm>
+#include <iterator>
+
+
+static vcl::Font CreateFont( const css::awt::FontDescriptor& rDescr )
+{
+ vcl::Font aFont;
+ if ( !rDescr.Name.isEmpty() )
+ aFont.SetFamilyName( rDescr.Name );
+ if ( !rDescr.StyleName.isEmpty() )
+ aFont.SetStyleName( rDescr.StyleName );
+ if ( rDescr.Height )
+ aFont.SetFontSize( Size( rDescr.Width, rDescr.Height ) );
+ if ( static_cast<FontFamily>(rDescr.Family) != FAMILY_DONTKNOW )
+ aFont.SetFamily( static_cast<FontFamily>(rDescr.Family) );
+ if ( static_cast<rtl_TextEncoding>(rDescr.CharSet) != RTL_TEXTENCODING_DONTKNOW )
+ aFont.SetCharSet( static_cast<rtl_TextEncoding>(rDescr.CharSet) );
+ if ( static_cast<FontPitch>(rDescr.Pitch) != PITCH_DONTKNOW )
+ aFont.SetPitch( static_cast<FontPitch>(rDescr.Pitch) );
+ if ( rDescr.CharacterWidth )
+ aFont.SetWidthType(vcl::unohelper::ConvertFontWidth(rDescr.CharacterWidth));
+ if ( rDescr.Weight )
+ aFont.SetWeight(vcl::unohelper::ConvertFontWeight(rDescr.Weight));
+ if ( rDescr.Slant != css::awt::FontSlant_DONTKNOW )
+ aFont.SetItalic(vcl::unohelper::ConvertFontSlant(rDescr.Slant));
+ if ( static_cast<FontLineStyle>(rDescr.Underline) != LINESTYLE_DONTKNOW )
+ aFont.SetUnderline( static_cast<FontLineStyle>(rDescr.Underline) );
+ if ( static_cast<FontStrikeout>(rDescr.Strikeout) != STRIKEOUT_DONTKNOW )
+ aFont.SetStrikeout( static_cast<FontStrikeout>(rDescr.Strikeout) );
+
+ // Not DONTKNOW
+ aFont.SetOrientation( Degree10(static_cast<sal_Int16>(rDescr.Orientation * 10)) );
+ aFont.SetKerning( static_cast<FontKerning>(rDescr.Kerning) );
+ aFont.SetWordLineMode( rDescr.WordLineMode );
+
+ return aFont;
+}
+
+namespace toolkitform
+{
+
+
+ using namespace ::com::sun::star;
+ using namespace ::com::sun::star::uno;
+ using namespace ::com::sun::star::awt;
+ using namespace ::com::sun::star::style;
+ using namespace ::com::sun::star::beans;
+ using namespace ::com::sun::star::form;
+ using namespace ::com::sun::star::lang;
+ using namespace ::com::sun::star::container;
+
+ constexpr OUString FM_PROP_NAME = u"Name"_ustr;
+
+ namespace
+ {
+
+ /** determines the FormComponentType of a form control
+ */
+ sal_Int16 classifyFormControl( const Reference< XPropertySet >& _rxModel )
+ {
+ static constexpr OUString FM_PROP_CLASSID = u"ClassId"_ustr;
+ sal_Int16 nControlType = FormComponentType::CONTROL;
+
+ Reference< XPropertySetInfo > xPSI;
+ if ( _rxModel.is() )
+ xPSI = _rxModel->getPropertySetInfo();
+ if ( xPSI.is() && xPSI->hasPropertyByName( FM_PROP_CLASSID ) )
+ {
+ if( ! (_rxModel->getPropertyValue( FM_PROP_CLASSID ) >>= nControlType) ) {
+ SAL_WARN("toolkit.helper", "classifyFormControl: unable to get property " << FM_PROP_CLASSID);
+ }
+ }
+
+ return nControlType;
+ }
+
+
+ /** (default-)creates a PDF widget according to a given FormComponentType
+ */
+ std::unique_ptr<vcl::PDFWriter::AnyWidget> createDefaultWidget( sal_Int16 _nFormComponentType )
+ {
+ switch ( _nFormComponentType )
+ {
+ case FormComponentType::COMMANDBUTTON:
+ return std::make_unique<vcl::PDFWriter::PushButtonWidget>();
+ case FormComponentType::CHECKBOX:
+ return std::make_unique<vcl::PDFWriter::CheckBoxWidget>();
+ case FormComponentType::RADIOBUTTON:
+ return std::make_unique<vcl::PDFWriter::RadioButtonWidget>();
+ case FormComponentType::LISTBOX:
+ return std::make_unique<vcl::PDFWriter::ListBoxWidget>();
+ case FormComponentType::COMBOBOX:
+ return std::make_unique<vcl::PDFWriter::ComboBoxWidget>();
+
+ case FormComponentType::TEXTFIELD:
+ case FormComponentType::FILECONTROL:
+ case FormComponentType::DATEFIELD:
+ case FormComponentType::TIMEFIELD:
+ case FormComponentType::NUMERICFIELD:
+ case FormComponentType::CURRENCYFIELD:
+ case FormComponentType::PATTERNFIELD:
+ return std::make_unique<vcl::PDFWriter::EditWidget>();
+ }
+ return nullptr;
+ }
+
+
+ /** determines a unique number for the radio group which the given radio
+ button model belongs to
+
+ The number is guaranteed to be
+ <ul><li>unique within the document in which the button lives</li>
+ <li>the same for subsequent calls with other radio button models,
+ which live in the same document, and belong to the same group</li>
+ </ul>
+
+ @precond
+ the model must be part of the form component hierarchy in a document
+ */
+ sal_Int32 determineRadioGroupId( const Reference< XPropertySet >& _rxRadioModel )
+ {
+ OSL_ENSURE( classifyFormControl( _rxRadioModel ) == FormComponentType::RADIOBUTTON,
+ "determineRadioGroupId: this *is* no radio button model!" );
+ // The fact that radio button groups need to be unique within the complete
+ // host document makes it somewhat difficult ...
+ // Problem is that two form radio buttons belong to the same group if
+ // - they have the same parent
+ // - AND they have the same name or group name
+ // This implies that we need some knowledge about (potentially) *all* radio button
+ // groups in the document.
+
+ // get the root-level container
+ Reference< XChild > xChild( _rxRadioModel, UNO_QUERY );
+ Reference< XForm > xParentForm( xChild.is() ? xChild->getParent() : Reference< XInterface >(), UNO_QUERY );
+ OSL_ENSURE( xParentForm.is(), "determineRadioGroupId: no parent form -> group id!" );
+ if ( !xParentForm.is() )
+ return -1;
+
+ while ( xParentForm.is() )
+ {
+ xChild = xParentForm.get();
+ xParentForm.set(xChild->getParent(), css::uno::UNO_QUERY);
+ }
+ Reference< XIndexAccess > xRoot( xChild->getParent(), UNO_QUERY );
+ OSL_ENSURE( xRoot.is(), "determineRadioGroupId: unable to determine the root of the form component hierarchy!" );
+ if ( !xRoot.is() )
+ return -1;
+
+ // count the leafs in the hierarchy, until we encounter radio button
+ ::std::vector< Reference< XIndexAccess > > aAncestors;
+ ::std::vector< sal_Int32 > aPath;
+
+ Reference< XInterface > xNormalizedLookup( _rxRadioModel, UNO_QUERY );
+ Reference< XIndexAccess > xCurrentContainer( xRoot );
+ sal_Int32 nStartWithChild = 0;
+ sal_Int32 nGroupsEncountered = 0;
+ do
+ {
+ std::unordered_map<OUString,sal_Int32> GroupNameMap;
+ std::unordered_map<OUString,sal_Int32> SharedNameMap;
+ sal_Int32 nCount = xCurrentContainer->getCount();
+ sal_Int32 i;
+ for ( i = nStartWithChild; i < nCount; ++i )
+ {
+ Reference< XInterface > xElement( xCurrentContainer->getByIndex( i ), UNO_QUERY );
+ if ( !xElement.is() )
+ {
+ OSL_FAIL( "determineRadioGroupId: very suspicious!" );
+ continue;
+ }
+
+ Reference< XIndexAccess > xNewContainer( xElement, UNO_QUERY );
+ if ( xNewContainer.is() )
+ {
+ // step down the hierarchy
+ aAncestors.push_back( xCurrentContainer );
+ xCurrentContainer = xNewContainer;
+ aPath.push_back( i );
+ nStartWithChild = 0;
+ break;
+ // out of the inner loop, but continue with the outer loop
+ }
+
+ if ( xElement.get() == xNormalizedLookup.get() )
+ {
+ // Our radio button is in this container.
+ // Now take the time to ID this container's groups and return the button's groupId
+ for ( i = 0; i < nCount; ++i )
+ {
+ try
+ {
+ xElement.set( xCurrentContainer->getByIndex( i ), UNO_QUERY_THROW );
+ Reference< XServiceInfo > xModelSI( xElement, UNO_QUERY_THROW );
+ if ( xModelSI->supportsService("com.sun.star.awt.UnoControlRadioButtonModel") )
+ {
+ Reference< XPropertySet > aProps( xElement, UNO_QUERY_THROW );
+
+ OUString sGroupName;
+ aProps->getPropertyValue("GroupName") >>= sGroupName;
+ if ( !sGroupName.isEmpty() )
+ {
+ // map: unique key is the group name, so attempts to add a different ID value
+ // for an existing group are ignored - keeping the first ID - perfect for this scenario.
+ GroupNameMap.emplace( sGroupName, nGroupsEncountered + i );
+
+ if ( xElement.get() == xNormalizedLookup.get() )
+ return GroupNameMap[sGroupName];
+ }
+ else
+ {
+ // Old implementation didn't have a GroupName, just identical Control names.
+ aProps->getPropertyValue( FM_PROP_NAME ) >>= sGroupName;
+ SharedNameMap.emplace( sGroupName, nGroupsEncountered + i );
+
+ if ( xElement.get() == xNormalizedLookup.get() )
+ return SharedNameMap[sGroupName];
+ }
+
+ }
+ }
+ catch( uno::Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("toolkit");
+ }
+ }
+ SAL_WARN("toolkit.helper","determineRadioGroupId: did not find the radios element's group!" );
+ }
+ }
+
+ // we encounter this container the first time. In particular, we did not just step up
+ if ( nStartWithChild == 0 )
+ {
+ // Our control wasn't in this container, so consider every item to be a possible unique group.
+ // This is way too much: Not all of the elements in the current container will form groups.
+ // But anyway, this number is sufficient for our purpose, since sequential group ids are not required.
+ // Ultimately, the container contains *at most* this many groups.
+ nGroupsEncountered += nCount;
+ }
+
+ if ( i >= nCount )
+ {
+ // the loop terminated because there were no more elements
+ // -> step up, if possible
+ if ( aAncestors.empty() )
+ break;
+
+ xCurrentContainer = aAncestors.back(); aAncestors.pop_back();
+ nStartWithChild = aPath.back() + 1; aPath.pop_back();
+ }
+ }
+ while ( true );
+ return -1;
+ }
+
+
+ /** copies a StringItemList to a PDF widget's list
+ */
+ void getStringItemVector( const Reference< XPropertySet >& _rxModel, ::std::vector< OUString >& _rVector )
+ {
+ Sequence< OUString > aListEntries;
+ if( ! (_rxModel->getPropertyValue( "StringItemList" ) >>= aListEntries) ) {
+ SAL_WARN("toolkit.helper", "getStringItemVector: unable to get property StringItemList");
+ }
+ _rVector.insert( _rVector.end(), std::cbegin(aListEntries), std::cend(aListEntries) );
+ }
+ }
+
+
+ /** creates a PDF compatible control descriptor for the given control
+ */
+ std::unique_ptr<vcl::PDFWriter::AnyWidget> describePDFControl( const Reference< XControl >& _rxControl,
+ vcl::PDFExtOutDevData& i_pdfExportData )
+ {
+ std::unique_ptr<vcl::PDFWriter::AnyWidget> Descriptor;
+ OSL_ENSURE( _rxControl.is(), "describePDFControl: invalid (NULL) control!" );
+ if ( !_rxControl.is() )
+ return Descriptor;
+
+ try
+ {
+ Reference< XPropertySet > xModelProps( _rxControl->getModel(), UNO_QUERY );
+ sal_Int16 nControlType = classifyFormControl( xModelProps );
+ Descriptor = createDefaultWidget( nControlType );
+ if (!Descriptor)
+ // no PDF widget available for this
+ return Descriptor;
+
+ Reference< XPropertySetInfo > xPSI( xModelProps->getPropertySetInfo() );
+ Reference< XServiceInfo > xSI( xModelProps, UNO_QUERY );
+ OSL_ENSURE( xSI.is(), "describePDFControl: no service info!" );
+ // if we survived classifyFormControl, then it's a real form control, and they all have
+ // service infos
+
+
+ // set the common widget properties
+
+
+ // Name, Description, Text
+ if( ! (xModelProps->getPropertyValue( FM_PROP_NAME ) >>= Descriptor->Name) ) {
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_NAME);
+ }
+ if( ! (xModelProps->getPropertyValue( "HelpText" ) >>= Descriptor->Description) ) {
+ SAL_INFO("toolkit.helper", "describePDFControl: unable to get property HelpText");
+ }
+ Any aText;
+ static constexpr OUString FM_PROP_TEXT = u"Text"_ustr;
+ static constexpr OUString FM_PROP_LABEL = u"Label"_ustr;
+ static constexpr OUString FM_PROP_VALUE = u"Value"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_TEXT ) )
+ aText = xModelProps->getPropertyValue( FM_PROP_TEXT );
+ else if ( xPSI->hasPropertyByName( FM_PROP_LABEL ) )
+ aText = xModelProps->getPropertyValue( FM_PROP_LABEL );
+ else if ( xPSI->hasPropertyByName( FM_PROP_VALUE ) )
+ {
+ double aValue;
+ if (xModelProps->getPropertyValue( FM_PROP_VALUE ) >>= aValue)
+ aText <<= OUString::number(aValue);
+ }
+
+ if ( aText.hasValue() ) {
+ if( ! (aText >>= Descriptor->Text) ) {
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to assign aText to Descriptor->Text");
+ }
+ }
+
+
+ // readonly
+ static constexpr OUString FM_PROP_READONLY = u"ReadOnly"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_READONLY ) )
+ if( ! (xModelProps->getPropertyValue( FM_PROP_READONLY ) >>= Descriptor->ReadOnly) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_READONLY);
+
+
+ // border
+ {
+ static constexpr OUString FM_PROP_BORDER = u"Border"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_BORDER ) )
+ {
+ sal_Int16 nBorderType = 0;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_BORDER ) >>= nBorderType) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_BORDER);
+ Descriptor->Border = ( nBorderType != 0 );
+
+ OUString sBorderColorPropertyName( "BorderColor" );
+ if ( xPSI->hasPropertyByName( sBorderColorPropertyName ) )
+ {
+ Color nBorderColor = COL_TRANSPARENT;
+ if ( xModelProps->getPropertyValue( sBorderColorPropertyName ) >>= nBorderColor )
+ Descriptor->BorderColor = nBorderColor;
+ else
+ Descriptor->BorderColor = COL_BLACK;
+ }
+ }
+ }
+
+
+ // background color
+ static constexpr OUString FM_PROP_BACKGROUNDCOLOR = u"BackgroundColor"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_BACKGROUNDCOLOR ) )
+ {
+ Color nBackColor = COL_TRANSPARENT;
+ xModelProps->getPropertyValue( FM_PROP_BACKGROUNDCOLOR ) >>= nBackColor;
+ Descriptor->Background = true;
+ Descriptor->BackgroundColor = nBackColor;
+ }
+
+
+ // text color
+ static constexpr OUString FM_PROP_TEXTCOLOR = u"TextColor"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_TEXTCOLOR ) )
+ {
+ Color nTextColor = COL_TRANSPARENT;
+ xModelProps->getPropertyValue( FM_PROP_TEXTCOLOR ) >>= nTextColor;
+ Descriptor->TextColor = nTextColor;
+ }
+
+
+ // text style
+ Descriptor->TextStyle = DrawTextFlags::NONE;
+
+ // multi line and word break
+ // The MultiLine property of the control is mapped to both the "MULTILINE" and
+ // "WORDBREAK" style flags
+ static constexpr OUString FM_PROP_MULTILINE = u"MultiLine"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_MULTILINE ) )
+ {
+ bool bMultiLine = false;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_MULTILINE ) >>= bMultiLine) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_MULTILINE);
+ if ( bMultiLine )
+ Descriptor->TextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
+ }
+
+ // horizontal alignment
+ static constexpr OUString FM_PROP_ALIGN = u"Align"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_ALIGN ) )
+ {
+ sal_Int16 nAlign = awt::TextAlign::LEFT;
+ xModelProps->getPropertyValue( FM_PROP_ALIGN ) >>= nAlign;
+ // TODO: when the property is VOID - are there situations/UIs where this
+ // means something else than LEFT?
+ switch ( nAlign )
+ {
+ case awt::TextAlign::LEFT: Descriptor->TextStyle |= DrawTextFlags::Left; break;
+ case awt::TextAlign::CENTER: Descriptor->TextStyle |= DrawTextFlags::Center; break;
+ case awt::TextAlign::RIGHT: Descriptor->TextStyle |= DrawTextFlags::Right; break;
+ default:
+ OSL_FAIL( "describePDFControl: invalid text align!" );
+ }
+ }
+
+ // vertical alignment
+ {
+ OUString sVertAlignPropertyName( "VerticalAlign" );
+ if ( xPSI->hasPropertyByName( sVertAlignPropertyName ) )
+ {
+ VerticalAlignment nAlign = VerticalAlignment_MIDDLE;
+ xModelProps->getPropertyValue( sVertAlignPropertyName ) >>= nAlign;
+ switch ( nAlign )
+ {
+ case VerticalAlignment_TOP: Descriptor->TextStyle |= DrawTextFlags::Top; break;
+ case VerticalAlignment_MIDDLE: Descriptor->TextStyle |= DrawTextFlags::VCenter; break;
+ case VerticalAlignment_BOTTOM: Descriptor->TextStyle |= DrawTextFlags::Bottom; break;
+ default:
+ OSL_FAIL( "describePDFControl: invalid vertical text align!" );
+ }
+ }
+ }
+
+ // font
+ static constexpr OUString FM_PROP_FONT = u"FontDescriptor"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_FONT ) )
+ {
+ FontDescriptor aUNOFont;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_FONT ) >>= aUNOFont) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_FONT);
+ Descriptor->TextFont = CreateFont( aUNOFont );
+ }
+
+ // tab order
+ OUString aTabIndexString( "TabIndex" );
+ if ( xPSI->hasPropertyByName( aTabIndexString ) )
+ {
+ sal_Int16 nIndex = -1;
+ if( ! (xModelProps->getPropertyValue( aTabIndexString ) >>= nIndex) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << aTabIndexString);
+ Descriptor->TabOrder = nIndex;
+ }
+
+
+ // special widget properties
+
+ // edits
+ if ( Descriptor->getType() == vcl::PDFWriter::Edit )
+ {
+ vcl::PDFWriter::EditWidget* pEditWidget = static_cast< vcl::PDFWriter::EditWidget* >( Descriptor.get() );
+
+ // multiline (already flagged in the TextStyle)
+ pEditWidget->MultiLine = bool( Descriptor->TextStyle & DrawTextFlags::MultiLine );
+
+ // password input
+ OUString sEchoCharPropName( "EchoChar" );
+ if ( xPSI->hasPropertyByName( sEchoCharPropName ) )
+ {
+ sal_Int16 nEchoChar = 0;
+ if ( ( xModelProps->getPropertyValue( sEchoCharPropName ) >>= nEchoChar ) && ( nEchoChar != 0 ) )
+ pEditWidget->Password = true;
+ }
+
+ // file select
+ if ( xSI->supportsService( "com.sun.star.form.component.FileControl" ) )
+ pEditWidget->FileSelect = true;
+
+ // maximum text length
+ static constexpr OUString FM_PROP_MAXTEXTLEN = u"MaxTextLen"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_MAXTEXTLEN ) )
+ {
+ sal_Int16 nMaxTextLength = 0;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_MAXTEXTLEN ) >>= nMaxTextLength) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_MAXTEXTLEN);
+ if ( nMaxTextLength <= 0 )
+ // "-1" has a special meaning for database-bound controls
+ nMaxTextLength = 0;
+ pEditWidget->MaxLen = nMaxTextLength;
+ }
+
+ switch ( nControlType )
+ {
+ case FormComponentType::CURRENCYFIELD:
+ case FormComponentType::NUMERICFIELD:
+ {
+
+ pEditWidget->Format = vcl::PDFWriter::Number;
+
+ static constexpr OUString FM_PROP_CURRENCYSYMBOL = u"CurrencySymbol"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_CURRENCYSYMBOL ) )
+ {
+ OUString sCurrencySymbol;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_CURRENCYSYMBOL ) >>= sCurrencySymbol) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_CURRENCYSYMBOL);
+ pEditWidget->CurrencySymbol = sCurrencySymbol;
+ }
+
+ static constexpr OUString FM_PROP_DECIMALACCURACY = u"DecimalAccuracy"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_DECIMALACCURACY ) )
+ {
+ sal_Int32 nDecimalAccuracy = 0;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_DECIMALACCURACY ) >>= nDecimalAccuracy) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_DECIMALACCURACY);
+ pEditWidget->DecimalAccuracy = nDecimalAccuracy;
+ }
+
+ static constexpr OUString FM_PROP_PREPENDCURRENCYSYMBOL = u"PrependCurrencySymbol"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_PREPENDCURRENCYSYMBOL ) )
+ {
+ bool bPrependCurrencySymbol = true;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_PREPENDCURRENCYSYMBOL ) >>= bPrependCurrencySymbol) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_PREPENDCURRENCYSYMBOL);
+ pEditWidget->PrependCurrencySymbol = bPrependCurrencySymbol;
+ }
+ } break;
+ case FormComponentType::TIMEFIELD:
+ {
+ pEditWidget->Format = vcl::PDFWriter::Time;
+
+ static constexpr OUString FM_PROP_TIMEFORMAT = u"TimeFormat"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_TIMEFORMAT ) )
+ {
+ sal_Int32 nTimeFormat = 0;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_TIMEFORMAT ) >>= nTimeFormat) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_TIMEFORMAT);
+
+ switch ( nTimeFormat )
+ {
+ case 0:
+ pEditWidget->TimeFormat = "HH:MM"; //13:45
+ break;
+ case 1:
+ pEditWidget->TimeFormat = "HH:MM:ss"; //13:45:00
+ break;
+ case 2:
+ pEditWidget->TimeFormat = "h:MMtt"; //01:45 PM
+ break;
+ case 3:
+ pEditWidget->TimeFormat = "h:MM:sstt"; //01:45:00 PM
+ break;
+ }
+ }
+ } break;
+ case FormComponentType::DATEFIELD:
+ {
+ pEditWidget->Format = vcl::PDFWriter::Date;
+
+ static constexpr OUString FM_PROP_DATEFORMAT = u"DateFormat"_ustr;
+ if ( xPSI->hasPropertyByName( FM_PROP_DATEFORMAT ) )
+ {
+ sal_Int32 nDateFormat = 0;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_DATEFORMAT ) >>= nDateFormat) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_DATEFORMAT);
+
+ switch ( nDateFormat )
+ {
+ case 0:
+ case 1:
+ pEditWidget->DateFormat = "mm/dd/yy"; // Standard (short)
+ break;
+ case 2:
+ case 3:
+ pEditWidget->DateFormat = "mm/dd/yyyy"; // Standard (long)
+ break;
+ case 4:
+ pEditWidget->DateFormat = "dd/mm/yy"; // DD/MM/YY
+ break;
+ case 5:
+ pEditWidget->DateFormat = "mm/dd/yy"; // MM/DD/YY
+ break;
+ case 6:
+ pEditWidget->DateFormat = "yy/mm/dd"; // YY/MM/DD
+ break;
+ case 7:
+ pEditWidget->DateFormat = "dd/mm/yyyy"; // DD/MM/YYYY
+ break;
+ case 8:
+ pEditWidget->DateFormat = "mm/dd/yyyy"; // MM/DD/YYYY
+ break;
+ case 9:
+ pEditWidget->DateFormat = "yyyy/mm/dd"; // YYYY/MM/DD
+ break;
+ case 10:
+ pEditWidget->DateFormat = "yy-mm-dd"; // YY-MM-DD
+ break;
+ case 11:
+ pEditWidget->DateFormat = "yyyy-mm-dd"; // YYYY-MM-DD
+ break;
+ }
+ }
+ } break;
+ }
+ }
+
+ // buttons
+ if ( Descriptor->getType() == vcl::PDFWriter::PushButton )
+ {
+ vcl::PDFWriter::PushButtonWidget* pButtonWidget = static_cast< vcl::PDFWriter::PushButtonWidget* >( Descriptor.get() );
+ FormButtonType eButtonType = FormButtonType_PUSH;
+ if( ! (xModelProps->getPropertyValue("ButtonType") >>= eButtonType) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property ButtonType");
+ static constexpr OUString FM_PROP_TARGET_URL = u"TargetURL"_ustr;
+ if ( eButtonType == FormButtonType_SUBMIT )
+ {
+ // if a button is a submit button, then it uses the URL at its parent form
+ Reference< XChild > xChild( xModelProps, UNO_QUERY );
+ Reference < XPropertySet > xParentProps;
+ if ( xChild.is() )
+ xParentProps.set(xChild->getParent(), css::uno::UNO_QUERY);
+ if ( xParentProps.is() )
+ {
+ Reference< XServiceInfo > xParentSI( xParentProps, UNO_QUERY );
+ if ( xParentSI.is() && xParentSI->supportsService("com.sun.star.form.component.HTMLForm") )
+ {
+ if( ! (xParentProps->getPropertyValue( FM_PROP_TARGET_URL ) >>= pButtonWidget->URL) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_TARGET_URL);
+ pButtonWidget->Submit = true;
+ FormSubmitMethod eMethod = FormSubmitMethod_POST;
+ if( ! (xParentProps->getPropertyValue("SubmitMethod") >>= eMethod) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_TARGET_URL);
+ pButtonWidget->SubmitGet = (eMethod == FormSubmitMethod_GET);
+ }
+ }
+ }
+ else if ( eButtonType == FormButtonType_URL )
+ {
+ OUString sURL;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_TARGET_URL ) >>= sURL) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_TARGET_URL);
+ const bool bDocumentLocalTarget = sURL.startsWith("#");
+ if ( bDocumentLocalTarget )
+ {
+ // Register the destination for future handling ...
+ pButtonWidget->Dest = i_pdfExportData.RegisterDest();
+
+ // and put it into the bookmarks, to ensure the future handling really happens
+ ::std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks( i_pdfExportData.GetBookmarks() );
+ vcl::PDFExtOutDevBookmarkEntry aBookmark;
+ aBookmark.nDestId = pButtonWidget->Dest;
+ aBookmark.aBookmark = sURL;
+ rBookmarks.push_back( aBookmark );
+ }
+ else
+ pButtonWidget->URL = sURL;
+
+ pButtonWidget->Submit = false;
+ }
+
+ // TODO:
+ // In PDF files, buttons are either reset, url or submit buttons. So if we have a simple PUSH button
+ // in a document, then this means that we do not export a SubmitToURL, which means that in PDF,
+ // the button is used as reset button.
+ // Is this desired? If no, we would have to reset Descriptor to NULL here, in case eButtonType
+ // != FormButtonType_SUBMIT && != FormButtonType_RESET
+
+ // the PDF exporter defaults the text style, if 0. To prevent this, we have to transfer the UNO
+ // defaults to the PDF widget
+ if ( pButtonWidget->TextStyle == DrawTextFlags::NONE )
+ pButtonWidget->TextStyle = DrawTextFlags::Center | DrawTextFlags::VCenter;
+ }
+
+
+ // check boxes
+ static constexpr OUString FM_PROP_STATE = u"State"_ustr;
+ if ( Descriptor->getType() == vcl::PDFWriter::CheckBox )
+ {
+ vcl::PDFWriter::CheckBoxWidget* pCheckBoxWidget = static_cast< vcl::PDFWriter::CheckBoxWidget* >( Descriptor.get() );
+ sal_Int16 nState = 0;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_STATE ) >>= nState) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_STATE);
+ pCheckBoxWidget->Checked = ( nState != 0 );
+
+ try
+ {
+ xModelProps->getPropertyValue( "RefValue" ) >>= pCheckBoxWidget->OnValue;
+ }
+ catch(...)
+ {
+ }
+
+ try
+ {
+ xModelProps->getPropertyValue( "SecondaryRefValue" ) >>= pCheckBoxWidget->OffValue;
+ }
+ catch(...)
+ {
+ }
+ }
+
+
+ // radio buttons
+ if ( Descriptor->getType() == vcl::PDFWriter::RadioButton )
+ {
+ vcl::PDFWriter::RadioButtonWidget* pRadioWidget = static_cast< vcl::PDFWriter::RadioButtonWidget* >( Descriptor.get() );
+ sal_Int16 nState = 0;
+ if( ! (xModelProps->getPropertyValue( FM_PROP_STATE ) >>= nState) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_STATE);
+ pRadioWidget->Selected = ( nState != 0 );
+ pRadioWidget->RadioGroup = determineRadioGroupId( xModelProps );
+ try
+ {
+ xModelProps->getPropertyValue( "RefValue" ) >>= pRadioWidget->OnValue;
+ }
+ catch(...)
+ {
+ }
+
+ try
+ {
+ xModelProps->getPropertyValue( "SecondaryRefValue" ) >>= pRadioWidget->OffValue;
+ }
+ catch(...)
+ {
+ }
+ }
+
+
+ // list boxes
+ if ( Descriptor->getType() == vcl::PDFWriter::ListBox )
+ {
+ vcl::PDFWriter::ListBoxWidget* pListWidget = static_cast< vcl::PDFWriter::ListBoxWidget* >( Descriptor.get() );
+
+ // drop down
+ if( ! (xModelProps->getPropertyValue( "Dropdown" ) >>= pListWidget->DropDown) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property Dropdown");
+
+ // multi selection
+ if( ! (xModelProps->getPropertyValue("MultiSelection") >>= pListWidget->MultiSelect) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property MultiSelection");
+
+ // entries
+ getStringItemVector( xModelProps, pListWidget->Entries );
+
+ // get selected items
+ Sequence< sal_Int16 > aSelectIndices;
+ if( ! (xModelProps->getPropertyValue("SelectedItems") >>= aSelectIndices) )
+ SAL_WARN("toolkit.helper", "describePDFControl: unable to get property SelectedItems");
+ if( aSelectIndices.hasElements() )
+ {
+ pListWidget->SelectedEntries.resize( 0 );
+ auto nEntriesSize = static_cast<sal_Int16>(pListWidget->Entries.size());
+ std::copy_if(std::cbegin(aSelectIndices), std::cend(aSelectIndices), std::back_inserter(pListWidget->SelectedEntries),
+ [&nEntriesSize](const sal_Int16 nIndex) { return nIndex >= 0 && nIndex < nEntriesSize; });
+ }
+ }
+
+
+ // combo boxes
+ if ( Descriptor->getType() == vcl::PDFWriter::ComboBox )
+ {
+ vcl::PDFWriter::ComboBoxWidget* pComboWidget = static_cast< vcl::PDFWriter::ComboBoxWidget* >( Descriptor.get() );
+
+ // entries
+ getStringItemVector( xModelProps, pComboWidget->Entries );
+ }
+
+
+ // some post-processing
+
+ // text line ends
+ // some controls may (always or dependent on other settings) return UNIX line ends
+ Descriptor->Text = convertLineEnd(Descriptor->Text, LINEEND_CRLF);
+ }
+ catch( const Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "toolkit", "describePDFControl" );
+ }
+ return Descriptor;
+ }
+
+
+} // namespace toolkitform
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/gdimetafiletools.cxx b/vcl/source/gdi/gdimetafiletools.cxx
new file mode 100644
index 0000000000..95c42c5df7
--- /dev/null
+++ b/vcl/source/gdi/gdimetafiletools.cxx
@@ -0,0 +1,1086 @@
+/* -*- 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 <vcl/gdimetafiletools.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/canvastools.hxx>
+#include <basegfx/polygon/b2dpolygonclipper.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/graphictools.hxx>
+#include <osl/diagnose.h>
+#include <tools/stream.hxx>
+
+// helpers
+
+namespace
+{
+ bool handleGeometricContent(
+ const basegfx::B2DPolyPolygon& rClip,
+ const basegfx::B2DPolyPolygon& rSource,
+ GDIMetaFile& rTarget,
+ bool bStroke)
+ {
+ if(rSource.count() && rClip.count())
+ {
+ const basegfx::B2DPolyPolygon aResult(
+ basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ rSource,
+ rClip,
+ true, // inside
+ bStroke));
+
+ if(aResult.count())
+ {
+ if(aResult == rSource)
+ {
+ // not clipped, but inside. Add original
+ return false;
+ }
+ else
+ {
+ // add clipped geometry
+ if(bStroke)
+ {
+ for(auto const& rB2DPolygon : aResult)
+ {
+ rTarget.AddAction(
+ new MetaPolyLineAction(
+ tools::Polygon(rB2DPolygon)));
+ }
+ }
+ else
+ {
+ rTarget.AddAction(
+ new MetaPolyPolygonAction(
+ tools::PolyPolygon(aResult)));
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ bool handleGradientContent(
+ const basegfx::B2DPolyPolygon& rClip,
+ const basegfx::B2DPolyPolygon& rSource,
+ const Gradient& rGradient,
+ GDIMetaFile& rTarget)
+ {
+ if(rSource.count() && rClip.count())
+ {
+ const basegfx::B2DPolyPolygon aResult(
+ basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ rSource,
+ rClip,
+ true, // inside
+ false)); // stroke
+
+ if(aResult.count())
+ {
+ if(aResult == rSource)
+ {
+ // not clipped, but inside. Add original
+ return false;
+ }
+ else
+ {
+ // add clipped geometry
+ rTarget.AddAction(
+ new MetaGradientExAction(
+ tools::PolyPolygon(aResult),
+ rGradient));
+ }
+ }
+ }
+
+ return true;
+ }
+
+ bool handleBitmapContent(
+ const basegfx::B2DPolyPolygon& rClip,
+ const Point& rPoint,
+ const Size& rSize,
+ const BitmapEx& rBitmapEx,
+ GDIMetaFile& rTarget)
+ {
+ if(!rSize.Width() || !rSize.Height() || rBitmapEx.IsEmpty())
+ {
+ // bitmap or size is empty
+ return true;
+ }
+
+ const basegfx::B2DRange aLogicBitmapRange(
+ rPoint.X(), rPoint.Y(),
+ rPoint.X() + rSize.Width(), rPoint.Y() + rSize.Height());
+ const basegfx::B2DPolyPolygon aClipOfBitmap(
+ basegfx::utils::clipPolyPolygonOnRange(
+ rClip,
+ aLogicBitmapRange,
+ true,
+ false)); // stroke
+
+ if(!aClipOfBitmap.count())
+ {
+ // outside clip region
+ return true;
+ }
+
+ // inside or overlapping. Use area to find out if it is completely
+ // covering (inside) or overlapping
+ const double fClipArea(basegfx::utils::getArea(aClipOfBitmap));
+ const double fBitmapArea(
+ aLogicBitmapRange.getWidth() * aLogicBitmapRange.getWidth() +
+ aLogicBitmapRange.getHeight() * aLogicBitmapRange.getHeight());
+ const double fFactor(fClipArea / fBitmapArea);
+
+ if(basegfx::fTools::more(fFactor, 1.0 - 0.001))
+ {
+ // completely covering (with 0.1% tolerance)
+ return false;
+ }
+
+ // needs clipping (with 0.1% tolerance). Prepare VirtualDevice
+ // in pixel mode for alpha channel painting (black is transparent,
+ // white to paint 100% opacity)
+ const Size aSizePixel(rBitmapEx.GetSizePixel());
+ ScopedVclPtrInstance< VirtualDevice > aVDev;
+
+ aVDev->SetOutputSizePixel(aSizePixel);
+ aVDev->EnableMapMode(false);
+ aVDev->SetFillColor( COL_WHITE);
+ aVDev->SetLineColor();
+
+ if(rBitmapEx.IsAlpha())
+ {
+ // use given alpha channel
+ aVDev->DrawBitmap(Point(0, 0), rBitmapEx.GetAlphaMask().GetBitmap());
+ }
+ else
+ {
+ // reset alpha channel
+ aVDev->SetBackground(Wallpaper(COL_BLACK));
+ aVDev->Erase();
+ }
+
+ // transform polygon from clipping to pixel coordinates
+ basegfx::B2DPolyPolygon aPixelPoly(aClipOfBitmap);
+ basegfx::B2DHomMatrix aTransform;
+
+ aTransform.translate(-aLogicBitmapRange.getMinX(), -aLogicBitmapRange.getMinY());
+ aTransform.scale(
+ static_cast< double >(aSizePixel.Width()) / aLogicBitmapRange.getWidth(),
+ static_cast< double >(aSizePixel.Height()) / aLogicBitmapRange.getHeight());
+ aPixelPoly.transform(aTransform);
+
+ // to fill the non-covered parts, use the Xor fill rule of
+ // tools::PolyPolygon painting. Start with an all-covering polygon and
+ // add the clip polygon one
+ basegfx::B2DPolyPolygon aInvertPixelPoly;
+
+ aInvertPixelPoly.append(
+ basegfx::utils::createPolygonFromRect(
+ basegfx::B2DRange(
+ 0.0, 0.0,
+ aSizePixel.Width(), aSizePixel.Height())));
+ aInvertPixelPoly.append(aPixelPoly);
+
+ // paint as alpha
+ aVDev->DrawPolyPolygon(aInvertPixelPoly);
+
+ // get created alpha mask and set defaults
+ AlphaMask aAlpha(
+ aVDev->GetBitmap(
+ Point(0, 0),
+ aSizePixel));
+
+ aAlpha.SetPrefSize(rBitmapEx.GetPrefSize());
+ aAlpha.SetPrefMapMode(rBitmapEx.GetPrefMapMode());
+
+ // add new action replacing the old one
+ rTarget.AddAction(
+ new MetaBmpExScaleAction(
+ Point(
+ basegfx::fround(aLogicBitmapRange.getMinX()),
+ basegfx::fround(aLogicBitmapRange.getMinY())),
+ Size(
+ basegfx::fround(aLogicBitmapRange.getWidth()),
+ basegfx::fround(aLogicBitmapRange.getHeight())),
+ BitmapEx(rBitmapEx.GetBitmap(), aAlpha)));
+
+ return true;
+ }
+
+ void addSvtGraphicStroke(const SvtGraphicStroke& rStroke, GDIMetaFile& rTarget)
+ {
+ // write SvtGraphicFill
+ SvMemoryStream aMemStm;
+ WriteSvtGraphicStroke( aMemStm, rStroke );
+ rTarget.AddAction(
+ new MetaCommentAction(
+ "XPATHSTROKE_SEQ_BEGIN"_ostr,
+ 0,
+ static_cast< const sal_uInt8* >(aMemStm.GetData()),
+ aMemStm.TellEnd()));
+ }
+
+ void addSvtGraphicFill(const SvtGraphicFill &rFilling, GDIMetaFile& rTarget)
+ {
+ // write SvtGraphicFill
+ SvMemoryStream aMemStm;
+ WriteSvtGraphicFill( aMemStm, rFilling );
+ rTarget.AddAction(
+ new MetaCommentAction(
+ "XPATHFILL_SEQ_BEGIN"_ostr,
+ 0,
+ static_cast< const sal_uInt8* >(aMemStm.GetData()),
+ aMemStm.TellEnd()));
+ }
+} // end of anonymous namespace
+
+// #i121267# Tooling to internally clip geometry against internal clip regions
+
+void clipMetafileContentAgainstOwnRegions(GDIMetaFile& rSource)
+{
+ const sal_uLong nObjCount(rSource.GetActionSize());
+
+ if(!nObjCount)
+ {
+ return;
+ }
+
+ // prepare target data container and push/pop stack data
+ GDIMetaFile aTarget;
+ bool bChanged(false);
+ std::vector< basegfx::B2DPolyPolygon > aClips;
+ std::vector< vcl::PushFlags > aPushFlags;
+ std::vector< MapMode > aMapModes;
+
+ // start with empty region
+ aClips.emplace_back();
+
+ // start with default MapMode (MapUnit::MapPixel)
+ aMapModes.emplace_back();
+
+ for(sal_uLong i(0); i < nObjCount; ++i)
+ {
+ const MetaAction* pAction(rSource.GetAction(i));
+ const MetaActionType nType(pAction->GetType());
+ bool bDone(false);
+
+ // basic operation takes care of clipregion actions (four) and push/pop of these
+ // to steer the currently set clip region. There *is* an active
+ // clip region when (aClips.size() && aClips.back().count()), see
+ // below
+ switch(nType)
+ {
+ case MetaActionType::CLIPREGION :
+ {
+ const MetaClipRegionAction* pA = static_cast< const MetaClipRegionAction* >(pAction);
+
+ if(pA->IsClipping())
+ {
+ const vcl::Region& rRegion = pA->GetRegion();
+ const basegfx::B2DPolyPolygon aNewClip(rRegion.GetAsB2DPolyPolygon());
+
+ aClips.back() = aNewClip;
+ }
+ else
+ {
+ aClips.back() = basegfx::B2DPolyPolygon();
+ }
+
+ break;
+ }
+
+ case MetaActionType::ISECTRECTCLIPREGION :
+ {
+ const MetaISectRectClipRegionAction* pA = static_cast< const MetaISectRectClipRegionAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(!rRect.IsEmpty() && !aClips.empty() && aClips.back().count())
+ {
+ const basegfx::B2DRange aClipRange(vcl::unotools::b2DRectangleFromRectangle(rRect));
+
+ aClips.back() = basegfx::utils::clipPolyPolygonOnRange(
+ aClips.back(),
+ aClipRange,
+ true, // inside
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::ISECTREGIONCLIPREGION :
+ {
+ const MetaISectRegionClipRegionAction* pA = static_cast< const MetaISectRegionClipRegionAction* >(pAction);
+ const vcl::Region& rRegion = pA->GetRegion();
+
+ if(!rRegion.IsEmpty() && !aClips.empty() && aClips.back().count())
+ {
+ const basegfx::B2DPolyPolygon aNewClip(rRegion.GetAsB2DPolyPolygon());
+
+ aClips.back() = basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ aClips.back(),
+ aNewClip,
+ true, // inside
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::MOVECLIPREGION :
+ {
+ const MetaMoveClipRegionAction* pA = static_cast< const MetaMoveClipRegionAction* >(pAction);
+ const tools::Long aHorMove(pA->GetHorzMove());
+ const tools::Long aVerMove(pA->GetVertMove());
+
+ if((aHorMove || aVerMove) && !aClips.empty() && aClips.back().count())
+ {
+ aClips.back().transform(
+ basegfx::utils::createTranslateB2DHomMatrix(
+ aHorMove,
+ aVerMove));
+ }
+ break;
+ }
+
+ case MetaActionType::PUSH :
+ {
+ const MetaPushAction* pA = static_cast< const MetaPushAction* >(pAction);
+ const vcl::PushFlags nFlags(pA->GetFlags());
+
+ aPushFlags.push_back(nFlags);
+
+ if(nFlags & vcl::PushFlags::CLIPREGION)
+ {
+ aClips.push_back(aClips.back());
+ }
+
+ if(nFlags & vcl::PushFlags::MAPMODE)
+ {
+ aMapModes.push_back(aMapModes.back());
+ }
+ break;
+ }
+
+ case MetaActionType::POP :
+ {
+
+ if(!aPushFlags.empty())
+ {
+ const vcl::PushFlags nFlags(aPushFlags.back());
+ aPushFlags.pop_back();
+
+ if(nFlags & vcl::PushFlags::CLIPREGION)
+ {
+ if(aClips.size() > 1)
+ {
+ aClips.pop_back();
+ }
+ else
+ {
+ OSL_ENSURE(false, "Wrong POP() in ClipRegions (!)");
+ }
+ }
+
+ if(nFlags & vcl::PushFlags::MAPMODE)
+ {
+ if(aMapModes.size() > 1)
+ {
+ aMapModes.pop_back();
+ }
+ else
+ {
+ OSL_ENSURE(false, "Wrong POP() in MapModes (!)");
+ }
+ }
+ }
+ else
+ {
+ OSL_ENSURE(false, "Invalid pop() without push() (!)");
+ }
+
+ break;
+ }
+
+ case MetaActionType::MAPMODE :
+ {
+ const MetaMapModeAction* pA = static_cast< const MetaMapModeAction* >(pAction);
+
+ aMapModes.back() = pA->GetMapMode();
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+
+ // this area contains all actions which could potentially be clipped. Since
+ // this tooling is only a fallback (see comments in header), only the needed
+ // actions will be implemented. Extend using the pattern for the already
+ // implemented actions.
+ if(!aClips.empty() && aClips.back().count())
+ {
+ switch(nType)
+ {
+
+ // pixel actions, just check on inside
+
+ case MetaActionType::PIXEL :
+ {
+ const MetaPixelAction* pA = static_cast< const MetaPixelAction* >(pAction);
+ const Point& rPoint = pA->GetPoint();
+
+ if(!basegfx::utils::isInside(
+ aClips.back(),
+ basegfx::B2DPoint(rPoint.X(), rPoint.Y())))
+ {
+ // when not inside, do not add original
+ bDone = true;
+ }
+ break;
+ }
+
+ case MetaActionType::POINT :
+ {
+ const MetaPointAction* pA = static_cast< const MetaPointAction* >(pAction);
+ const Point& rPoint = pA->GetPoint();
+
+ if(!basegfx::utils::isInside(
+ aClips.back(),
+ basegfx::B2DPoint(rPoint.X(), rPoint.Y())))
+ {
+ // when not inside, do not add original
+ bDone = true;
+ }
+ break;
+ }
+
+ // geometry actions
+
+ case MetaActionType::LINE :
+ {
+ const MetaLineAction* pA = static_cast< const MetaLineAction* >(pAction);
+ const Point& rStart(pA->GetStartPoint());
+ const Point& rEnd(pA->GetEndPoint());
+ basegfx::B2DPolygon aLine;
+
+ aLine.append(basegfx::B2DPoint(rStart.X(), rStart.Y()));
+ aLine.append(basegfx::B2DPoint(rEnd.X(), rEnd.Y()));
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(aLine),
+ aTarget,
+ true); // stroke
+ break;
+ }
+
+ case MetaActionType::RECT :
+ {
+ const MetaRectAction* pA = static_cast< const MetaRectAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect))),
+ aTarget,
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::ROUNDRECT :
+ {
+ const MetaRoundRectAction* pA = static_cast< const MetaRoundRectAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ const sal_uInt32 nHor(pA->GetHorzRound());
+ const sal_uInt32 nVer(pA->GetVertRound());
+ const basegfx::B2DRange aRange(vcl::unotools::b2DRectangleFromRectangle(rRect));
+ basegfx::B2DPolygon aOutline;
+
+ if(nHor || nVer)
+ {
+ double fRadiusX((nHor * 2.0) / (aRange.getWidth() > 0.0 ? aRange.getWidth() : 1.0));
+ double fRadiusY((nVer * 2.0) / (aRange.getHeight() > 0.0 ? aRange.getHeight() : 1.0));
+ fRadiusX = std::clamp(fRadiusX, 0.0, 1.0);
+ fRadiusY = std::clamp(fRadiusY, 0.0, 1.0);
+
+ aOutline = basegfx::utils::createPolygonFromRect(aRange, fRadiusX, fRadiusY);
+ }
+ else
+ {
+ aOutline = basegfx::utils::createPolygonFromRect(aRange);
+ }
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(aOutline),
+ aTarget,
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::ELLIPSE :
+ {
+ const MetaEllipseAction* pA = static_cast< const MetaEllipseAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ const basegfx::B2DRange aRange(vcl::unotools::b2DRectangleFromRectangle(rRect));
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromEllipse(
+ aRange.getCenter(),
+ aRange.getWidth() * 0.5,
+ aRange.getHeight() * 0.5)),
+ aTarget,
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::ARC :
+ {
+ const MetaArcAction* pA = static_cast< const MetaArcAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ const tools::Polygon aToolsPoly(
+ rRect,
+ pA->GetStartPoint(),
+ pA->GetEndPoint(),
+ PolyStyle::Arc);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()),
+ aTarget,
+ true); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::PIE :
+ {
+ const MetaPieAction* pA = static_cast< const MetaPieAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ const tools::Polygon aToolsPoly(
+ rRect,
+ pA->GetStartPoint(),
+ pA->GetEndPoint(),
+ PolyStyle::Pie);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()),
+ aTarget,
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::CHORD :
+ {
+ const MetaChordAction* pA = static_cast< const MetaChordAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ const tools::Polygon aToolsPoly(
+ rRect,
+ pA->GetStartPoint(),
+ pA->GetEndPoint(),
+ PolyStyle::Chord);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()),
+ aTarget,
+ false); // stroke
+ }
+ break;
+ }
+
+ case MetaActionType::POLYLINE :
+ {
+ const MetaPolyLineAction* pA = static_cast< const MetaPolyLineAction* >(pAction);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(pA->GetPolygon().getB2DPolygon()),
+ aTarget,
+ true); // stroke
+ break;
+ }
+
+ case MetaActionType::POLYGON :
+ {
+ const MetaPolygonAction* pA = static_cast< const MetaPolygonAction* >(pAction);
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(pA->GetPolygon().getB2DPolygon()),
+ aTarget,
+ false); // stroke
+ break;
+ }
+
+ case MetaActionType::POLYPOLYGON :
+ {
+ const MetaPolyPolygonAction* pA = static_cast< const MetaPolyPolygonAction* >(pAction);
+ const tools::PolyPolygon& rPoly = pA->GetPolyPolygon();
+
+ bDone = handleGeometricContent(
+ aClips.back(),
+ rPoly.getB2DPolyPolygon(),
+ aTarget,
+ false); // stroke
+ break;
+ }
+
+ // bitmap actions, create BitmapEx with alpha channel derived
+ // from clipping
+
+ case MetaActionType::BMPEX :
+ {
+ const MetaBmpExAction* pA = static_cast< const MetaBmpExAction* >(pAction);
+ const BitmapEx& rBitmapEx = pA->GetBitmapEx();
+
+ // the logical size depends on the PrefSize of the given bitmap in
+ // combination with the current MapMode
+ Size aLogicalSize(rBitmapEx.GetPrefSize());
+
+ if(MapUnit::MapPixel == rBitmapEx.GetPrefMapMode().GetMapUnit())
+ {
+ aLogicalSize = Application::GetDefaultDevice()->PixelToLogic(aLogicalSize, aMapModes.back());
+ }
+ else
+ {
+ aLogicalSize = OutputDevice::LogicToLogic(aLogicalSize, rBitmapEx.GetPrefMapMode(), aMapModes.back());
+ }
+
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetPoint(),
+ aLogicalSize,
+ rBitmapEx,
+ aTarget);
+ break;
+ }
+
+ case MetaActionType::BMP :
+ {
+ const MetaBmpAction* pA = static_cast< const MetaBmpAction* >(pAction);
+ const Bitmap& rBitmap = pA->GetBitmap();
+
+ // the logical size depends on the PrefSize of the given bitmap in
+ // combination with the current MapMode
+ Size aLogicalSize(rBitmap.GetPrefSize());
+
+ if(MapUnit::MapPixel == rBitmap.GetPrefMapMode().GetMapUnit())
+ {
+ aLogicalSize = Application::GetDefaultDevice()->PixelToLogic(aLogicalSize, aMapModes.back());
+ }
+ else
+ {
+ aLogicalSize = OutputDevice::LogicToLogic(aLogicalSize, rBitmap.GetPrefMapMode(), aMapModes.back());
+ }
+
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetPoint(),
+ aLogicalSize,
+ BitmapEx(rBitmap),
+ aTarget);
+ break;
+ }
+
+ case MetaActionType::BMPEXSCALE :
+ {
+ const MetaBmpExScaleAction* pA = static_cast< const MetaBmpExScaleAction* >(pAction);
+
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetPoint(),
+ pA->GetSize(),
+ pA->GetBitmapEx(),
+ aTarget);
+ break;
+ }
+
+ case MetaActionType::BMPSCALE :
+ {
+ const MetaBmpScaleAction* pA = static_cast< const MetaBmpScaleAction* >(pAction);
+
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetPoint(),
+ pA->GetSize(),
+ BitmapEx(pA->GetBitmap()),
+ aTarget);
+ break;
+ }
+
+ case MetaActionType::BMPEXSCALEPART :
+ {
+ const MetaBmpExScalePartAction* pA = static_cast< const MetaBmpExScalePartAction* >(pAction);
+ const BitmapEx& rBitmapEx = pA->GetBitmapEx();
+
+ if(rBitmapEx.IsEmpty())
+ {
+ // empty content
+ bDone = true;
+ }
+ else
+ {
+ BitmapEx aCroppedBitmapEx(rBitmapEx);
+ const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize());
+
+ if(aCropRectangle.IsEmpty())
+ {
+ // empty content
+ bDone = true;
+ }
+ else
+ {
+ aCroppedBitmapEx.Crop(aCropRectangle);
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetDestPoint(),
+ pA->GetDestSize(),
+ aCroppedBitmapEx,
+ aTarget);
+ }
+ }
+ break;
+ }
+
+ case MetaActionType::BMPSCALEPART :
+ {
+ const MetaBmpScalePartAction* pA = static_cast< const MetaBmpScalePartAction* >(pAction);
+ const Bitmap& rBitmap = pA->GetBitmap();
+
+ if(rBitmap.IsEmpty())
+ {
+ // empty content
+ bDone = true;
+ }
+ else
+ {
+ Bitmap aCroppedBitmap(rBitmap);
+ const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize());
+
+ if(aCropRectangle.IsEmpty())
+ {
+ // empty content
+ bDone = true;
+ }
+ else
+ {
+ aCroppedBitmap.Crop(aCropRectangle);
+ bDone = handleBitmapContent(
+ aClips.back(),
+ pA->GetDestPoint(),
+ pA->GetDestSize(),
+ BitmapEx(aCroppedBitmap),
+ aTarget);
+ }
+ }
+ break;
+ }
+
+ // need to handle all those 'hacks' which hide data in comments
+
+ case MetaActionType::COMMENT :
+ {
+ const MetaCommentAction* pA = static_cast< const MetaCommentAction* >(pAction);
+ const OString& rComment = pA->GetComment();
+
+ if(rComment.equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN"))
+ {
+ // nothing to do; this just means that between here and XGRAD_SEQ_END
+ // exists a MetaActionType::GRADIENTEX mixed with Xor-tricked painting
+ // commands. This comment is used to scan over these and filter for
+ // the gradient action. It is needed to support MetaActionType::GRADIENTEX
+ // in this processor to solve usages.
+ }
+ else if(rComment.equalsIgnoreAsciiCase("XPATHFILL_SEQ_BEGIN"))
+ {
+ SvtGraphicFill aFilling;
+ tools::PolyPolygon aPath;
+
+ { // read SvtGraphicFill
+ SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(),StreamMode::READ);
+ ReadSvtGraphicFill( aMemStm, aFilling );
+ }
+
+ aFilling.getPath(aPath);
+
+ if(aPath.Count())
+ {
+ const basegfx::B2DPolyPolygon aSource(aPath.getB2DPolyPolygon());
+ const basegfx::B2DPolyPolygon aResult(
+ basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ aSource,
+ aClips.back(),
+ true, // inside
+ false)); // stroke
+
+ if(aResult.count())
+ {
+ if(aResult != aSource)
+ {
+ // add clipped geometry
+ aFilling.setPath(tools::PolyPolygon(aResult));
+ addSvtGraphicFill(aFilling, aTarget);
+ bDone = true;
+ }
+ }
+ else
+ {
+ // exchange with empty polygon
+ aFilling.setPath(tools::PolyPolygon());
+ addSvtGraphicFill(aFilling, aTarget);
+ bDone = true;
+ }
+ }
+ }
+ else if(rComment.equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_BEGIN"))
+ {
+ SvtGraphicStroke aStroke;
+ tools::Polygon aPath;
+
+ { // read SvtGraphicFill
+ SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(),StreamMode::READ);
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+ }
+
+ aStroke.getPath(aPath);
+
+ if(aPath.GetSize())
+ {
+ const basegfx::B2DPolygon aSource(aPath.getB2DPolygon());
+ const basegfx::B2DPolyPolygon aResult(
+ basegfx::utils::clipPolygonOnPolyPolygon(
+ aSource,
+ aClips.back(),
+ true, // inside
+ true)); // stroke
+
+ if(aResult.count())
+ {
+ if(aResult.count() > 1 || aResult.getB2DPolygon(0) != aSource)
+ {
+ // add clipped geometry
+ for(auto const& rB2DPolygon : aResult)
+ {
+ aStroke.setPath(tools::Polygon(rB2DPolygon));
+ addSvtGraphicStroke(aStroke, aTarget);
+ }
+
+ bDone = true;
+ }
+ }
+ else
+ {
+ // exchange with empty polygon
+ aStroke.setPath(tools::Polygon());
+ addSvtGraphicStroke(aStroke, aTarget);
+ bDone = true;
+ }
+
+ }
+ }
+ break;
+ }
+
+ // need to handle gradient fills (hopefully only unrotated ones)
+
+ case MetaActionType::GRADIENT :
+ {
+ const MetaGradientAction* pA = static_cast< const MetaGradientAction* >(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ if(rRect.IsEmpty())
+ {
+ bDone = true;
+ }
+ else
+ {
+ bDone = handleGradientContent(
+ aClips.back(),
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect))),
+ pA->GetGradient(),
+ aTarget);
+ }
+
+ break;
+ }
+
+ case MetaActionType::GRADIENTEX :
+ {
+ const MetaGradientExAction* pA = static_cast< const MetaGradientExAction* >(pAction);
+ const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon();
+
+ bDone = handleGradientContent(
+ aClips.back(),
+ rPolyPoly.getB2DPolyPolygon(),
+ pA->GetGradient(),
+ aTarget);
+ break;
+ }
+
+ // not (yet) supported actions
+
+ // MetaActionType::NONE
+ // MetaActionType::TEXT
+ // MetaActionType::TEXTARRAY
+ // MetaActionType::STRETCHTEXT
+ // MetaActionType::TEXTRECT
+ // MetaActionType::MASK
+ // MetaActionType::MASKSCALE
+ // MetaActionType::MASKSCALEPART
+ // MetaActionType::HATCH
+ // MetaActionType::WALLPAPER
+ // MetaActionType::FILLCOLOR
+ // MetaActionType::TEXTCOLOR
+ // MetaActionType::TEXTFILLCOLOR
+ // MetaActionType::TEXTALIGN
+ // MetaActionType::MAPMODE
+ // MetaActionType::FONT
+ // MetaActionType::Transparent
+ // MetaActionType::EPS
+ // MetaActionType::REFPOINT
+ // MetaActionType::TEXTLINECOLOR
+ // MetaActionType::TEXTLINE
+ // MetaActionType::FLOATTRANSPARENT
+ // MetaActionType::LAYOUTMODE
+ // MetaActionType::TEXTLANGUAGE
+ // MetaActionType::OVERLINECOLOR
+
+ // if an action is not handled at all, it will simply get copied to the
+ // target (see below). This is the default for all non-implemented actions
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ if(bDone)
+ {
+ bChanged = true;
+ }
+ else
+ {
+ aTarget.AddAction(const_cast< MetaAction* >(pAction));
+ }
+ }
+
+ if(bChanged)
+ {
+ // when changed, copy back and do not forget to set MapMode
+ // and PrefSize
+ aTarget.SetPrefMapMode(rSource.GetPrefMapMode());
+ aTarget.SetPrefSize(rSource.GetPrefSize());
+ rSource = aTarget;
+ }
+}
+
+bool usesClipActions(const GDIMetaFile& rSource)
+{
+ const sal_uLong nObjCount(rSource.GetActionSize());
+
+ for(sal_uLong i(0); i < nObjCount; ++i)
+ {
+ const MetaAction* pAction(rSource.GetAction(i));
+ const MetaActionType nType(pAction->GetType());
+
+ switch(nType)
+ {
+ case MetaActionType::CLIPREGION :
+ case MetaActionType::ISECTRECTCLIPREGION :
+ case MetaActionType::ISECTREGIONCLIPREGION :
+ case MetaActionType::MOVECLIPREGION :
+ {
+ return true;
+ }
+
+ default: break;
+ }
+ }
+
+ return false;
+}
+
+MetafileAccessor::~MetafileAccessor()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/gdimtf.cxx b/vcl/source/gdi/gdimtf.cxx
new file mode 100644
index 0000000000..fb4ff37dfa
--- /dev/null
+++ b/vcl/source/gdi/gdimtf.cxx
@@ -0,0 +1,2357 @@
+/* -*- 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 <cstdlib>
+#include <memory>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/helpers.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/fract.hxx>
+#include <vcl/BitmapPalette.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/window.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/graphictools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/mtfxmldump.hxx>
+
+#include <vcl/TypeSerializer.hxx>
+
+#include <com/sun/star/beans/XFastPropertySet.hpp>
+#include <com/sun/star/rendering/MtfRenderer.hpp>
+#include <com/sun/star/rendering/XBitmapCanvas.hpp>
+#include <com/sun/star/rendering/XCanvas.hpp>
+#include <comphelper/processfactory.hxx>
+
+using namespace com::sun::star;
+
+namespace {
+
+struct ImplColAdjustParam
+{
+ std::unique_ptr<sal_uInt8[]> pMapR;
+ std::unique_ptr<sal_uInt8[]> pMapG;
+ std::unique_ptr<sal_uInt8[]> pMapB;
+};
+
+struct ImplBmpAdjustParam
+{
+ short nLuminancePercent;
+ short nContrastPercent;
+ short nChannelRPercent;
+ short nChannelGPercent;
+ short nChannelBPercent;
+ double fGamma;
+ bool bInvert;
+};
+
+struct ImplColConvertParam
+{
+ MtfConversion eConversion;
+};
+
+struct ImplBmpConvertParam
+{
+ BmpConversion eConversion;
+};
+
+struct ImplColMonoParam
+{
+ Color aColor;
+};
+
+struct ImplBmpMonoParam
+{
+ Color aColor;
+};
+
+struct ImplColReplaceParam
+{
+ std::unique_ptr<sal_uLong[]> pMinR;
+ std::unique_ptr<sal_uLong[]> pMaxR;
+ std::unique_ptr<sal_uLong[]> pMinG;
+ std::unique_ptr<sal_uLong[]> pMaxG;
+ std::unique_ptr<sal_uLong[]> pMinB;
+ std::unique_ptr<sal_uLong[]> pMaxB;
+ const Color * pDstCols;
+ sal_uLong nCount;
+};
+
+struct ImplBmpReplaceParam
+{
+ const Color* pSrcCols;
+ const Color* pDstCols;
+ sal_uLong nCount;
+};
+
+}
+
+GDIMetaFile::GDIMetaFile() :
+ m_nCurrentActionElement( 0 ),
+ m_aPrefSize ( 1, 1 ),
+ m_pPrev ( nullptr ),
+ m_pNext ( nullptr ),
+ m_pOutDev ( nullptr ),
+ m_bPause ( false ),
+ m_bRecord ( false ),
+ m_bUseCanvas ( false ),
+ m_bSVG ( false )
+{
+}
+
+GDIMetaFile::GDIMetaFile( const GDIMetaFile& rMtf ) :
+ m_nCurrentActionElement( rMtf.m_nCurrentActionElement ),
+ m_aPrefMapMode ( rMtf.m_aPrefMapMode ),
+ m_aPrefSize ( rMtf.m_aPrefSize ),
+ m_pPrev ( rMtf.m_pPrev ),
+ m_pNext ( rMtf.m_pNext ),
+ m_pOutDev ( nullptr ),
+ m_bPause ( false ),
+ m_bRecord ( false ),
+ m_bUseCanvas ( rMtf.m_bUseCanvas ),
+ m_bSVG ( rMtf.m_bSVG )
+{
+ for( size_t i = 0, n = rMtf.GetActionSize(); i < n; ++i )
+ {
+ m_aList.push_back( rMtf.GetAction( i ) );
+ }
+
+ if( rMtf.m_bRecord )
+ {
+ Record( rMtf.m_pOutDev );
+
+ if ( rMtf.m_bPause )
+ Pause( true );
+ }
+}
+
+GDIMetaFile::~GDIMetaFile()
+{
+ Clear();
+}
+
+bool GDIMetaFile::HasTransparentActions() const
+{
+ MetaAction* pCurrAct;
+
+ // watch for transparent drawing actions
+ for(pCurrAct = const_cast<GDIMetaFile*>(this)->FirstAction();
+ pCurrAct;
+ pCurrAct = const_cast<GDIMetaFile*>(this)->NextAction())
+ {
+ // #i10613# determine if the action is transparency capable
+
+ // #107169# Also examine metafiles with masked bitmaps in
+ // detail. Further down, this is optimized in such a way
+ // that there's no unnecessary painting of masked bitmaps
+ // (which are _always_ subdivided into rectangular regions
+ // of uniform opacity): if a masked bitmap is printed over
+ // empty background, we convert to a plain bitmap with
+ // white background.
+ if (pCurrAct->IsTransparent())
+ return true;
+ }
+
+ return false;
+}
+
+size_t GDIMetaFile::GetActionSize() const
+{
+ return m_aList.size();
+}
+
+MetaAction* GDIMetaFile::GetAction( size_t nAction ) const
+{
+ return (nAction < m_aList.size()) ? m_aList[ nAction ].get() : nullptr;
+}
+
+MetaAction* GDIMetaFile::FirstAction()
+{
+ m_nCurrentActionElement = 0;
+ return m_aList.empty() ? nullptr : m_aList[ 0 ].get();
+}
+
+MetaAction* GDIMetaFile::NextAction()
+{
+ return ( m_nCurrentActionElement + 1 < m_aList.size() ) ? m_aList[ ++m_nCurrentActionElement ].get() : nullptr;
+}
+
+void GDIMetaFile::ReplaceAction( rtl::Reference<MetaAction> pAction, size_t nAction )
+{
+ if ( nAction >= m_aList.size() )
+ {
+ return;
+ }
+ //fdo#39995 This doesn't increment the incoming action ref-count nor does it
+ //decrement the outgoing action ref-count
+ std::swap(pAction, m_aList[nAction]);
+}
+
+GDIMetaFile& GDIMetaFile::operator=( const GDIMetaFile& rMtf )
+{
+ if( this != &rMtf )
+ {
+ Clear();
+
+ // Increment RefCount of MetaActions
+ for( size_t i = 0, n = rMtf.GetActionSize(); i < n; ++i )
+ {
+ m_aList.push_back( rMtf.GetAction( i ) );
+ }
+
+ m_aPrefMapMode = rMtf.m_aPrefMapMode;
+ m_aPrefSize = rMtf.m_aPrefSize;
+ m_pPrev = rMtf.m_pPrev;
+ m_pNext = rMtf.m_pNext;
+ m_pOutDev = nullptr;
+ m_bPause = false;
+ m_bRecord = false;
+ m_bUseCanvas = rMtf.m_bUseCanvas;
+ m_bSVG = rMtf.m_bSVG;
+
+ if( rMtf.m_bRecord )
+ {
+ Record( rMtf.m_pOutDev );
+
+ if( rMtf.m_bPause )
+ Pause( true );
+ }
+ }
+
+ return *this;
+}
+
+bool GDIMetaFile::operator==( const GDIMetaFile& rMtf ) const
+{
+ const size_t nObjCount = m_aList.size();
+ bool bRet = false;
+
+ if( this == &rMtf )
+ bRet = true;
+ else if( rMtf.GetActionSize() == nObjCount &&
+ rMtf.GetPrefSize() == m_aPrefSize &&
+ rMtf.GetPrefMapMode() == m_aPrefMapMode )
+ {
+ bRet = true;
+
+ for( size_t n = 0; n < nObjCount; n++ )
+ {
+ if( m_aList[ n ] != rMtf.GetAction( n ) )
+ {
+ bRet = false;
+ break;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+void GDIMetaFile::Clear()
+{
+ if( m_bRecord )
+ Stop();
+
+ m_aList.clear();
+}
+
+void GDIMetaFile::Linker( OutputDevice* pOut, bool bLink )
+{
+ if( bLink )
+ {
+ m_pNext = nullptr;
+ m_pPrev = pOut->GetConnectMetaFile();
+ pOut->SetConnectMetaFile( this );
+
+ if( m_pPrev )
+ m_pPrev->m_pNext = this;
+ }
+ else
+ {
+ if( m_pNext )
+ {
+ m_pNext->m_pPrev = m_pPrev;
+
+ if( m_pPrev )
+ m_pPrev->m_pNext = m_pNext;
+ }
+ else
+ {
+ if( m_pPrev )
+ m_pPrev->m_pNext = nullptr;
+
+ pOut->SetConnectMetaFile( m_pPrev );
+ }
+
+ m_pPrev = nullptr;
+ m_pNext = nullptr;
+ }
+}
+
+void GDIMetaFile::Record( OutputDevice* pOut )
+{
+ if( m_bRecord )
+ Stop();
+
+ m_nCurrentActionElement = m_aList.empty() ? 0 : (m_aList.size() - 1);
+ m_pOutDev = pOut;
+ m_bRecord = true;
+ Linker( pOut, true );
+}
+
+void GDIMetaFile::Play( GDIMetaFile& rMtf )
+{
+ if (m_bRecord || rMtf.m_bRecord)
+ return;
+
+ MetaAction* pAction = GetCurAction();
+ const size_t nObjCount = m_aList.size();
+
+ rMtf.UseCanvas( rMtf.GetUseCanvas() || m_bUseCanvas );
+ rMtf.setSVG( rMtf.getSVG() || m_bSVG );
+
+ for( size_t nCurPos = m_nCurrentActionElement; nCurPos < nObjCount; nCurPos++ )
+ {
+ if( pAction )
+ {
+ rMtf.AddAction( pAction );
+ }
+
+ pAction = NextAction();
+ }
+}
+
+void GDIMetaFile::Play(OutputDevice& rOut, size_t nPos)
+{
+ if( m_bRecord )
+ return;
+
+ MetaAction* pAction = GetCurAction();
+ const size_t nObjCount = m_aList.size();
+ size_t nSyncCount = rOut.GetSyncCount();
+
+ if( nPos > nObjCount )
+ nPos = nObjCount;
+
+ // #i23407# Set backwards-compatible text language and layout mode
+ // This is necessary, since old metafiles don't even know of these
+ // recent add-ons. Newer metafiles must of course explicitly set
+ // those states.
+ rOut.Push(vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE);
+ rOut.SetLayoutMode(vcl::text::ComplexTextLayoutFlags::Default);
+ rOut.SetDigitLanguage(LANGUAGE_SYSTEM);
+
+ SAL_INFO( "vcl.gdi", "GDIMetaFile::Play on device of size: " << rOut.GetOutputSizePixel().Width() << " " << rOut.GetOutputSizePixel().Height());
+
+ if (!ImplPlayWithRenderer(rOut, Point(0,0), rOut.GetOutputSize())) {
+ size_t i = 0;
+ for( size_t nCurPos = m_nCurrentActionElement; nCurPos < nPos; nCurPos++ )
+ {
+ if( pAction )
+ {
+ pAction->Execute(&rOut);
+
+ // flush output from time to time
+ if( i++ > nSyncCount )
+ {
+ rOut.Flush();
+ i = 0;
+ }
+ }
+
+ pAction = NextAction();
+ }
+ }
+ rOut.Pop();
+}
+
+bool GDIMetaFile::ImplPlayWithRenderer(OutputDevice& rOut, const Point& rPos, Size rLogicDestSize)
+{
+ if (!m_bUseCanvas)
+ return false;
+
+ Size rDestSize(rOut.LogicToPixel(rLogicDestSize));
+
+ const vcl::Window* win = rOut.GetOwnerWindow();
+
+ if (!win)
+ win = Application::GetActiveTopWindow();
+ if (!win)
+ win = Application::GetFirstTopLevelWindow();
+
+ if (!win)
+ return false;
+
+ try
+ {
+ uno::Reference<rendering::XCanvas> xCanvas = win->GetOutDev()->GetCanvas ();
+
+ if (!xCanvas.is())
+ return false;
+
+ Size aSize (rDestSize.Width () + 1, rDestSize.Height () + 1);
+ uno::Reference<rendering::XBitmap> xBitmap = xCanvas->getDevice ()->createCompatibleAlphaBitmap (vcl::unotools::integerSize2DFromSize( aSize));
+ if( xBitmap.is () )
+ {
+ uno::Reference< rendering::XBitmapCanvas > xBitmapCanvas( xBitmap, uno::UNO_QUERY );
+ if( xBitmapCanvas.is() )
+ {
+ uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext();
+ uno::Reference< rendering::XMtfRenderer > xMtfRenderer = rendering::MtfRenderer::createWithBitmapCanvas( xContext, xBitmapCanvas );
+
+ xBitmapCanvas->clear();
+ uno::Reference< beans::XFastPropertySet > xMtfFastPropertySet( xMtfRenderer, uno::UNO_QUERY );
+ if( xMtfFastPropertySet.is() )
+ // set this metafile to the renderer to
+ // speedup things (instead of copying data to
+ // sequence of bytes passed to renderer)
+ xMtfFastPropertySet->setFastPropertyValue( 0, uno::Any( reinterpret_cast<sal_Int64>( this ) ) );
+
+ xMtfRenderer->draw( rDestSize.Width(), rDestSize.Height() );
+
+ BitmapEx aBitmapEx;
+ if( aBitmapEx.Create( xBitmapCanvas, aSize ) )
+ {
+ if (rOut.GetMapMode().GetMapUnit() == MapUnit::MapPixel)
+ rOut.DrawBitmapEx( rPos, aBitmapEx );
+ else
+ rOut.DrawBitmapEx( rPos, rLogicDestSize, aBitmapEx );
+ return true;
+ }
+ }
+ }
+ }
+ catch (const uno::RuntimeException& )
+ {
+ throw; // runtime errors are fatal
+ }
+ catch (const uno::Exception&)
+ {
+ // ignore errors, no way of reporting them here
+ TOOLS_WARN_EXCEPTION("vcl.gdi", "GDIMetaFile::ImplPlayWithRenderer");
+ }
+
+ return false;
+}
+
+void GDIMetaFile::Play(OutputDevice& rOut, const Point& rPos,
+ const Size& rSize)
+{
+ MapMode aDrawMap( GetPrefMapMode() );
+ Size aDestSize(rOut.LogicToPixel(rSize));
+
+ if (aDestSize.Width() <= 0 || aDestSize.Height() <= 0)
+ return;
+
+ if (aDestSize.Width() > std::numeric_limits<sal_Int32>::max() ||
+ aDestSize.Height() > std::numeric_limits<sal_Int32>::max())
+ return;
+
+ GDIMetaFile* pMtf = rOut.GetConnectMetaFile();
+
+ if (ImplPlayWithRenderer(rOut, rPos, rSize))
+ return;
+
+ Size aTmpPrefSize(rOut.LogicToPixel(GetPrefSize(), aDrawMap));
+
+ if( !aTmpPrefSize.Width() )
+ aTmpPrefSize.setWidth( aDestSize.Width() );
+
+ if( !aTmpPrefSize.Height() )
+ aTmpPrefSize.setHeight( aDestSize.Height() );
+
+ Fraction aScaleX( aDestSize.Width(), aTmpPrefSize.Width() );
+ Fraction aScaleY( aDestSize.Height(), aTmpPrefSize.Height() );
+
+ aScaleX *= aDrawMap.GetScaleX();
+ aScaleY *= aDrawMap.GetScaleY();
+ // try reducing inaccurary first and abandon if the scaling
+ // still cannot be achieved
+ if (TooLargeScaleForMapMode(aScaleX, rOut.GetDPIX()))
+ aScaleX.ReduceInaccurate(10);
+ if (TooLargeScaleForMapMode(aScaleY, rOut.GetDPIY()))
+ aScaleY.ReduceInaccurate(10);
+ if (TooLargeScaleForMapMode(aScaleX, rOut.GetDPIX()) ||
+ TooLargeScaleForMapMode(aScaleY, rOut.GetDPIY()))
+ {
+ SAL_WARN("vcl", "GDIMetaFile Scaling is too high");
+ return;
+ }
+
+ aDrawMap.SetScaleX(aScaleX);
+ aDrawMap.SetScaleY(aScaleY);
+
+ // #i47260# Convert logical output position to offset within
+ // the metafile's mapmode. Therefore, disable pixel offset on
+ // outdev, it's inverse mnOutOffLogicX/Y is calculated for a
+ // different mapmode (the one currently set on rOut, that is)
+ // - thus, aDrawMap's origin would generally be wrong. And
+ // even _if_ aDrawMap is similar to pOutDev's current mapmode,
+ // it's _still_ undesirable to have pixel offset unequal zero,
+ // because one would still get round-off errors (the
+ // round-trip error for LogicToPixel( PixelToLogic() ) was the
+ // reason for having pixel offset in the first place).
+ const Size& rOldOffset(rOut.GetPixelOffset());
+ const Size aEmptySize;
+ rOut.SetPixelOffset(aEmptySize);
+ aDrawMap.SetOrigin(rOut.PixelToLogic(rOut.LogicToPixel(rPos), aDrawMap));
+ rOut.SetPixelOffset(rOldOffset);
+
+ rOut.Push();
+
+ bool bIsRecord = (pMtf && pMtf->IsRecord());
+ rOut.SetMetafileMapMode(aDrawMap, bIsRecord);
+
+ // #i23407# Set backwards-compatible text language and layout mode
+ // This is necessary, since old metafiles don't even know of these
+ // recent add-ons. Newer metafiles must of course explicitly set
+ // those states.
+ rOut.SetLayoutMode(vcl::text::ComplexTextLayoutFlags::Default);
+ rOut.SetDigitLanguage(LANGUAGE_SYSTEM);
+
+ Play(rOut);
+
+ rOut.Pop();
+}
+
+void GDIMetaFile::Pause( bool _bPause )
+{
+ if( !m_bRecord )
+ return;
+
+ if( _bPause )
+ {
+ if( !m_bPause )
+ Linker( m_pOutDev, false );
+ }
+ else
+ {
+ if( m_bPause )
+ Linker( m_pOutDev, true );
+ }
+
+ m_bPause = _bPause;
+}
+
+void GDIMetaFile::Stop()
+{
+ if( m_bRecord )
+ {
+ m_bRecord = false;
+
+ if( !m_bPause )
+ Linker( m_pOutDev, false );
+ else
+ m_bPause = false;
+ }
+}
+
+void GDIMetaFile::WindStart()
+{
+ if( !m_bRecord )
+ m_nCurrentActionElement = 0;
+}
+
+void GDIMetaFile::WindPrev()
+{
+ if( !m_bRecord )
+ if ( m_nCurrentActionElement > 0 )
+ --m_nCurrentActionElement;
+}
+
+void GDIMetaFile::AddAction(const rtl::Reference<MetaAction>& pAction)
+{
+ m_aList.push_back( pAction );
+
+ if( m_pPrev )
+ {
+ m_pPrev->AddAction( pAction );
+ }
+}
+
+void GDIMetaFile::AddAction(const rtl::Reference<MetaAction>& pAction, size_t nPos)
+{
+ if ( nPos < m_aList.size() )
+ {
+ m_aList.insert( m_aList.begin() + nPos, pAction );
+ }
+ else
+ {
+ m_aList.push_back( pAction );
+ }
+
+ if( m_pPrev )
+ {
+ m_pPrev->AddAction( pAction, nPos );
+ }
+}
+
+void GDIMetaFile::push_back(const rtl::Reference<MetaAction>& pAction)
+{
+ m_aList.push_back( pAction );
+}
+
+void GDIMetaFile::Mirror( BmpMirrorFlags nMirrorFlags )
+{
+ const Size aOldPrefSize( GetPrefSize() );
+ tools::Long nMoveX, nMoveY;
+ double fScaleX, fScaleY;
+
+ if( nMirrorFlags & BmpMirrorFlags::Horizontal )
+ {
+ nMoveX = std::abs( aOldPrefSize.Width() ) - 1;
+ fScaleX = -1.0;
+ }
+ else
+ {
+ nMoveX = 0;
+ fScaleX = 1.0;
+ }
+
+ if( nMirrorFlags & BmpMirrorFlags::Vertical )
+ {
+ nMoveY = std::abs( aOldPrefSize.Height() ) - 1;
+ fScaleY = -1.0;
+ }
+ else
+ {
+ nMoveY = 0;
+ fScaleY = 1.0;
+ }
+
+ if( ( fScaleX != 1.0 ) || ( fScaleY != 1.0 ) )
+ {
+ Scale( fScaleX, fScaleY );
+ Move( nMoveX, nMoveY );
+ SetPrefSize( aOldPrefSize );
+ }
+}
+
+void GDIMetaFile::Move( tools::Long nX, tools::Long nY )
+{
+ const Size aBaseOffset( nX, nY );
+ Size aOffset( aBaseOffset );
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev;
+
+ aMapVDev->EnableOutput( false );
+ aMapVDev->SetMapMode( GetPrefMapMode() );
+
+ for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() )
+ {
+ const MetaActionType nType = pAct->GetType();
+ MetaAction* pModAct;
+
+ if( pAct->GetRefCount() > 1 )
+ {
+ m_aList[ m_nCurrentActionElement ] = pAct->Clone();
+ pModAct = m_aList[ m_nCurrentActionElement ].get();
+ }
+ else
+ pModAct = pAct;
+
+ if( ( MetaActionType::MAPMODE == nType ) ||
+ ( MetaActionType::PUSH == nType ) ||
+ ( MetaActionType::POP == nType ) )
+ {
+ pModAct->Execute( aMapVDev.get() );
+ aOffset = OutputDevice::LogicToLogic( aBaseOffset, GetPrefMapMode(), aMapVDev->GetMapMode() );
+ }
+
+ pModAct->Move( aOffset.Width(), aOffset.Height() );
+ }
+}
+
+void GDIMetaFile::Move( tools::Long nX, tools::Long nY, tools::Long nDPIX, tools::Long nDPIY )
+{
+ const Size aBaseOffset( nX, nY );
+ Size aOffset( aBaseOffset );
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev;
+
+ aMapVDev->EnableOutput( false );
+ aMapVDev->SetReferenceDevice( nDPIX, nDPIY );
+ aMapVDev->SetMapMode( GetPrefMapMode() );
+
+ for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() )
+ {
+ const MetaActionType nType = pAct->GetType();
+ MetaAction* pModAct;
+
+ if( pAct->GetRefCount() > 1 )
+ {
+ m_aList[ m_nCurrentActionElement ] = pAct->Clone();
+ pModAct = m_aList[ m_nCurrentActionElement ].get();
+ }
+ else
+ pModAct = pAct;
+
+ if( ( MetaActionType::MAPMODE == nType ) ||
+ ( MetaActionType::PUSH == nType ) ||
+ ( MetaActionType::POP == nType ) )
+ {
+ pModAct->Execute( aMapVDev.get() );
+ if( aMapVDev->GetMapMode().GetMapUnit() == MapUnit::MapPixel )
+ {
+ aOffset = aMapVDev->LogicToPixel( aBaseOffset, GetPrefMapMode() );
+ MapMode aMap( aMapVDev->GetMapMode() );
+ aOffset.setWidth( static_cast<tools::Long>(aOffset.Width() * static_cast<double>(aMap.GetScaleX())) );
+ aOffset.setHeight( static_cast<tools::Long>(aOffset.Height() * static_cast<double>(aMap.GetScaleY())) );
+ }
+ else
+ aOffset = OutputDevice::LogicToLogic( aBaseOffset, GetPrefMapMode(), aMapVDev->GetMapMode() );
+ }
+
+ pModAct->Move( aOffset.Width(), aOffset.Height() );
+ }
+}
+
+void GDIMetaFile::ScaleActions(double const fScaleX, double const fScaleY)
+{
+ for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() )
+ {
+ MetaAction* pModAct;
+
+ if( pAct->GetRefCount() > 1 )
+ {
+ m_aList[ m_nCurrentActionElement ] = pAct->Clone();
+ pModAct = m_aList[ m_nCurrentActionElement ].get();
+ }
+ else
+ pModAct = pAct;
+
+ pModAct->Scale( fScaleX, fScaleY );
+ }
+}
+
+void GDIMetaFile::Scale( double fScaleX, double fScaleY )
+{
+ ScaleActions(fScaleX, fScaleY);
+
+ m_aPrefSize.setWidth( FRound( m_aPrefSize.Width() * fScaleX ) );
+ m_aPrefSize.setHeight( FRound( m_aPrefSize.Height() * fScaleY ) );
+}
+
+void GDIMetaFile::Scale( const Fraction& rScaleX, const Fraction& rScaleY )
+{
+ Scale( static_cast<double>(rScaleX), static_cast<double>(rScaleY) );
+}
+
+void GDIMetaFile::Clip( const tools::Rectangle& i_rClipRect )
+{
+ tools::Rectangle aCurRect( i_rClipRect );
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev;
+
+ aMapVDev->EnableOutput( false );
+ aMapVDev->SetMapMode( GetPrefMapMode() );
+
+ for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() )
+ {
+ const MetaActionType nType = pAct->GetType();
+
+ if( ( MetaActionType::MAPMODE == nType ) ||
+ ( MetaActionType::PUSH == nType ) ||
+ ( MetaActionType::POP == nType ) )
+ {
+ pAct->Execute( aMapVDev.get() );
+ aCurRect = OutputDevice::LogicToLogic( i_rClipRect, GetPrefMapMode(), aMapVDev->GetMapMode() );
+ }
+ else if( nType == MetaActionType::CLIPREGION )
+ {
+ MetaClipRegionAction* pOldAct = static_cast<MetaClipRegionAction*>(pAct);
+ vcl::Region aNewReg( aCurRect );
+ if( pOldAct->IsClipping() )
+ aNewReg.Intersect( pOldAct->GetRegion() );
+ MetaClipRegionAction* pNewAct = new MetaClipRegionAction( std::move(aNewReg), true );
+ m_aList[ m_nCurrentActionElement ] = pNewAct;
+ }
+ }
+}
+
+Point GDIMetaFile::ImplGetRotatedPoint( const Point& rPt, const Point& rRotatePt,
+ const Size& rOffset, double fSin, double fCos )
+{
+ const tools::Long nX = rPt.X() - rRotatePt.X();
+ const tools::Long nY = rPt.Y() - rRotatePt.Y();
+
+ return Point( FRound( fCos * nX + fSin * nY ) + rRotatePt.X() + rOffset.Width(),
+ -FRound( fSin * nX - fCos * nY ) + rRotatePt.Y() + rOffset.Height() );
+}
+
+tools::Polygon GDIMetaFile::ImplGetRotatedPolygon( const tools::Polygon& rPoly, const Point& rRotatePt,
+ const Size& rOffset, double fSin, double fCos )
+{
+ tools::Polygon aRet( rPoly );
+
+ aRet.Rotate( rRotatePt, fSin, fCos );
+ aRet.Move( rOffset.Width(), rOffset.Height() );
+
+ return aRet;
+}
+
+tools::PolyPolygon GDIMetaFile::ImplGetRotatedPolyPolygon( const tools::PolyPolygon& rPolyPoly, const Point& rRotatePt,
+ const Size& rOffset, double fSin, double fCos )
+{
+ tools::PolyPolygon aRet( rPolyPoly );
+
+ aRet.Rotate( rRotatePt, fSin, fCos );
+ aRet.Move( rOffset.Width(), rOffset.Height() );
+
+ return aRet;
+}
+
+void GDIMetaFile::ImplAddGradientEx( GDIMetaFile& rMtf,
+ const OutputDevice& rMapDev,
+ const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGrad )
+{
+ // Generate comment, GradientEx and Gradient actions (within DrawGradient)
+ ScopedVclPtrInstance< VirtualDevice > aVDev(rMapDev, DeviceFormat::WITHOUT_ALPHA);
+ aVDev->EnableOutput( false );
+ GDIMetaFile aGradMtf;
+
+ aGradMtf.Record( aVDev.get() );
+ aVDev->DrawGradient( rPolyPoly, rGrad );
+ aGradMtf.Stop();
+
+ size_t i, nAct( aGradMtf.GetActionSize() );
+ for( i=0; i < nAct; ++i )
+ {
+ MetaAction* pMetaAct = aGradMtf.GetAction( i );
+ rMtf.AddAction( pMetaAct );
+ }
+}
+
+void GDIMetaFile::Rotate( Degree10 nAngle10 )
+{
+ nAngle10 %= 3600_deg10;
+ nAngle10 = ( nAngle10 < 0_deg10 ) ? ( Degree10(3599) + nAngle10 ) : nAngle10;
+
+ if( !nAngle10 )
+ return;
+
+ GDIMetaFile aMtf;
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev;
+ const double fAngle = toRadians(nAngle10);
+ const double fSin = sin( fAngle );
+ const double fCos = cos( fAngle );
+ tools::Rectangle aRect( Point(), GetPrefSize() );
+ tools::Polygon aPoly( aRect );
+
+ aPoly.Rotate( Point(), fSin, fCos );
+
+ aMapVDev->EnableOutput( false );
+ aMapVDev->SetMapMode( GetPrefMapMode() );
+
+ const tools::Rectangle aNewBound( aPoly.GetBoundRect() );
+
+ const Point aOrigin( GetPrefMapMode().GetOrigin().X(), GetPrefMapMode().GetOrigin().Y() );
+ const Size aOffset( -aNewBound.Left(), -aNewBound.Top() );
+
+ Point aRotAnchor( aOrigin );
+ Size aRotOffset( aOffset );
+
+ for( MetaAction* pAction = FirstAction(); pAction; pAction = NextAction() )
+ {
+ const MetaActionType nActionType = pAction->GetType();
+
+ switch( nActionType )
+ {
+ case MetaActionType::PIXEL:
+ {
+ MetaPixelAction* pAct = static_cast<MetaPixelAction*>(pAction);
+ aMtf.AddAction( new MetaPixelAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetColor() ) );
+ }
+ break;
+
+ case MetaActionType::POINT:
+ {
+ MetaPointAction* pAct = static_cast<MetaPointAction*>(pAction);
+ aMtf.AddAction( new MetaPointAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ MetaLineAction* pAct = static_cast<MetaLineAction*>(pAction);
+ aMtf.AddAction( new MetaLineAction( ImplGetRotatedPoint( pAct->GetStartPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ ImplGetRotatedPoint( pAct->GetEndPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetLineInfo() ) );
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ MetaRectAction* pAct = static_cast<MetaRectAction*>(pAction);
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( tools::Polygon(pAct->GetRect()), aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ MetaRoundRectAction* pAct = static_cast<MetaRoundRectAction*>(pAction);
+ const tools::Polygon aRoundRectPoly( pAct->GetRect(), pAct->GetHorzRound(), pAct->GetVertRound() );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aRoundRectPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ MetaEllipseAction* pAct = static_cast<MetaEllipseAction*>(pAction);
+ const tools::Polygon aEllipsePoly( pAct->GetRect().Center(), pAct->GetRect().GetWidth() >> 1, pAct->GetRect().GetHeight() >> 1 );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aEllipsePoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::ARC:
+ {
+ MetaArcAction* pAct = static_cast<MetaArcAction*>(pAction);
+ const tools::Polygon aArcPoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Arc );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aArcPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::PIE:
+ {
+ MetaPieAction* pAct = static_cast<MetaPieAction*>(pAction);
+ const tools::Polygon aPiePoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Pie );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aPiePoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::CHORD:
+ {
+ MetaChordAction* pAct = static_cast<MetaChordAction*>(pAction);
+ const tools::Polygon aChordPoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Chord );
+
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aChordPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ MetaPolyLineAction* pAct = static_cast<MetaPolyLineAction*>(pAction);
+ aMtf.AddAction( new MetaPolyLineAction( ImplGetRotatedPolygon( pAct->GetPolygon(), aRotAnchor, aRotOffset, fSin, fCos ), pAct->GetLineInfo() ) );
+ }
+ break;
+
+ case MetaActionType::POLYGON:
+ {
+ MetaPolygonAction* pAct = static_cast<MetaPolygonAction*>(pAction);
+ aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( pAct->GetPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ {
+ MetaPolyPolygonAction* pAct = static_cast<MetaPolyPolygonAction*>(pAction);
+ aMtf.AddAction( new MetaPolyPolygonAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) );
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ MetaTextAction* pAct = static_cast<MetaTextAction*>(pAction);
+ aMtf.AddAction( new MetaTextAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetText(), pAct->GetIndex(), pAct->GetLen() ) );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pAction);
+ aMtf.AddAction( new MetaTextArrayAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetText(), pAct->GetDXArray(), pAct->GetKashidaArray(), pAct->GetIndex(), pAct->GetLen() ) );
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ MetaStretchTextAction* pAct = static_cast<MetaStretchTextAction*>(pAction);
+ aMtf.AddAction( new MetaStretchTextAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetWidth(), pAct->GetText(), pAct->GetIndex(), pAct->GetLen() ) );
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ {
+ MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pAction);
+ aMtf.AddAction( new MetaTextLineAction( ImplGetRotatedPoint( pAct->GetStartPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetWidth(), pAct->GetStrikeout(), pAct->GetUnderline(), pAct->GetOverline() ) );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction);
+ tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetPoint(), pAct->GetSize() )), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() );
+ BitmapEx aBmpEx( pAct->GetBitmap() );
+
+ aBmpEx.Rotate( nAngle10, COL_TRANSPARENT );
+ aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(),
+ aBmpEx ) );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction);
+ tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetDestPoint(), pAct->GetDestSize() )), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() );
+ BitmapEx aBmpEx( pAct->GetBitmap() );
+
+ aBmpEx.Crop( tools::Rectangle( pAct->GetSrcPoint(), pAct->GetSrcSize() ) );
+ aBmpEx.Rotate( nAngle10, COL_TRANSPARENT );
+
+ aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction);
+ tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetPoint(), pAct->GetSize() )), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() );
+ BitmapEx aBmpEx( pAct->GetBitmapEx() );
+
+ aBmpEx.Rotate( nAngle10, COL_TRANSPARENT );
+
+ aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction);
+ tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetDestPoint(), pAct->GetDestSize() )), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() );
+ BitmapEx aBmpEx( pAct->GetBitmapEx() );
+
+ aBmpEx.Crop( tools::Rectangle( pAct->GetSrcPoint(), pAct->GetSrcSize() ) );
+ aBmpEx.Rotate( nAngle10, COL_TRANSPARENT );
+
+ aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction);
+
+ ImplAddGradientEx( aMtf, *aMapVDev,
+ tools::PolyPolygon(ImplGetRotatedPolygon( tools::Polygon(pAct->GetRect()), aRotAnchor, aRotOffset, fSin, fCos )),
+ pAct->GetGradient() );
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction);
+ aMtf.AddAction( new MetaGradientExAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetGradient() ) );
+ }
+ break;
+
+ // Handle gradientex comment block correctly
+ case MetaActionType::COMMENT:
+ {
+ MetaCommentAction* pCommentAct = static_cast<MetaCommentAction*>(pAction);
+ if( pCommentAct->GetComment() == "XGRAD_SEQ_BEGIN" )
+ {
+ int nBeginComments( 1 );
+ pAction = NextAction();
+
+ // skip everything, except gradientex action
+ while( pAction )
+ {
+ const MetaActionType nType = pAction->GetType();
+
+ if( MetaActionType::GRADIENTEX == nType )
+ {
+ // Add rotated gradientex
+ MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction);
+ ImplAddGradientEx( aMtf, *aMapVDev,
+ ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetGradient() );
+ }
+ else if( MetaActionType::COMMENT == nType)
+ {
+ MetaCommentAction* pAct = static_cast<MetaCommentAction*>(pAction);
+ if( pAct->GetComment() == "XGRAD_SEQ_END" )
+ {
+ // handle nested blocks
+ --nBeginComments;
+
+ // gradientex comment block: end reached, done.
+ if( !nBeginComments )
+ break;
+ }
+ else if( pAct->GetComment() == "XGRAD_SEQ_BEGIN" )
+ {
+ // handle nested blocks
+ ++nBeginComments;
+ }
+
+ }
+
+ pAction =NextAction();
+ }
+ }
+ else
+ {
+ bool bPathStroke = (pCommentAct->GetComment() == "XPATHSTROKE_SEQ_BEGIN");
+ if ( bPathStroke || pCommentAct->GetComment() == "XPATHFILL_SEQ_BEGIN" )
+ {
+ if ( pCommentAct->GetDataSize() )
+ {
+ SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pCommentAct->GetData()), pCommentAct->GetDataSize(), StreamMode::READ );
+ SvMemoryStream aDest;
+ if ( bPathStroke )
+ {
+ SvtGraphicStroke aStroke;
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+ tools::Polygon aPath;
+ aStroke.getPath( aPath );
+ aStroke.setPath( ImplGetRotatedPolygon( aPath, aRotAnchor, aRotOffset, fSin, fCos ) );
+ WriteSvtGraphicStroke( aDest, aStroke );
+ aMtf.AddAction( new MetaCommentAction( "XPATHSTROKE_SEQ_BEGIN"_ostr, 0,
+ static_cast<const sal_uInt8*>( aDest.GetData()), aDest.Tell() ) );
+ }
+ else
+ {
+ SvtGraphicFill aFill;
+ ReadSvtGraphicFill( aMemStm, aFill );
+ tools::PolyPolygon aPath;
+ aFill.getPath( aPath );
+ aFill.setPath( ImplGetRotatedPolyPolygon( aPath, aRotAnchor, aRotOffset, fSin, fCos ) );
+ WriteSvtGraphicFill( aDest, aFill );
+ aMtf.AddAction( new MetaCommentAction( "XPATHFILL_SEQ_BEGIN"_ostr, 0,
+ static_cast<const sal_uInt8*>( aDest.GetData()), aDest.Tell() ) );
+ }
+ }
+ }
+ else if ( pCommentAct->GetComment() == "XPATHSTROKE_SEQ_END"
+ || pCommentAct->GetComment() == "XPATHFILL_SEQ_END" )
+ {
+ pAction->Execute( aMapVDev.get() );
+ aMtf.AddAction( pAction );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ MetaHatchAction* pAct = static_cast<MetaHatchAction*>(pAction);
+ Hatch aHatch( pAct->GetHatch() );
+
+ aHatch.SetAngle( aHatch.GetAngle() + nAngle10 );
+ aMtf.AddAction( new MetaHatchAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ),
+ aHatch ) );
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ MetaTransparentAction* pAct = static_cast<MetaTransparentAction*>(pAction);
+ aMtf.AddAction( new MetaTransparentAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ),
+ pAct->GetTransparence() ) );
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pAction);
+ GDIMetaFile aTransMtf( pAct->GetGDIMetaFile() );
+ tools::Polygon aMtfPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetPoint(), pAct->GetSize() )), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aMtfRect( aMtfPoly.GetBoundRect() );
+
+ aTransMtf.Rotate( nAngle10 );
+ aMtf.AddAction( new MetaFloatTransparentAction( aTransMtf, aMtfRect.TopLeft(), aMtfRect.GetSize(),
+ pAct->GetGradient() ) );
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction);
+ GDIMetaFile aEPSMtf( pAct->GetSubstitute() );
+ tools::Polygon aEPSPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetPoint(), pAct->GetSize() )), aRotAnchor, aRotOffset, fSin, fCos ) );
+ tools::Rectangle aEPSRect( aEPSPoly.GetBoundRect() );
+
+ aEPSMtf.Rotate( nAngle10 );
+ aMtf.AddAction( new MetaEPSAction( aEPSRect.TopLeft(), aEPSRect.GetSize(),
+ pAct->GetLink(), aEPSMtf ) );
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ {
+ MetaClipRegionAction* pAct = static_cast<MetaClipRegionAction*>(pAction);
+
+ if( pAct->IsClipping() && pAct->GetRegion().HasPolyPolygonOrB2DPolyPolygon() )
+ aMtf.AddAction( new MetaClipRegionAction( vcl::Region( ImplGetRotatedPolyPolygon( pAct->GetRegion().GetAsPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ), true ) );
+ else
+ {
+ aMtf.AddAction( pAction );
+ }
+ }
+ break;
+
+ case MetaActionType::ISECTRECTCLIPREGION:
+ {
+ MetaISectRectClipRegionAction* pAct = static_cast<MetaISectRectClipRegionAction*>(pAction);
+ aMtf.AddAction( new MetaISectRegionClipRegionAction(vcl::Region(
+ ImplGetRotatedPolygon( tools::Polygon(pAct->GetRect()), aRotAnchor,
+ aRotOffset, fSin, fCos )) ) );
+ }
+ break;
+
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ {
+ MetaISectRegionClipRegionAction* pAct = static_cast<MetaISectRegionClipRegionAction*>(pAction);
+ const vcl::Region& rRegion = pAct->GetRegion();
+
+ if( rRegion.HasPolyPolygonOrB2DPolyPolygon() )
+ aMtf.AddAction( new MetaISectRegionClipRegionAction( vcl::Region( ImplGetRotatedPolyPolygon( rRegion.GetAsPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) ) );
+ else
+ {
+ aMtf.AddAction( pAction );
+ }
+ }
+ break;
+
+ case MetaActionType::REFPOINT:
+ {
+ MetaRefPointAction* pAct = static_cast<MetaRefPointAction*>(pAction);
+ aMtf.AddAction( new MetaRefPointAction( ImplGetRotatedPoint( pAct->GetRefPoint(), aRotAnchor, aRotOffset, fSin, fCos ), pAct->IsSetting() ) );
+ }
+ break;
+
+ case MetaActionType::FONT:
+ {
+ MetaFontAction* pAct = static_cast<MetaFontAction*>(pAction);
+ vcl::Font aFont( pAct->GetFont() );
+
+ aFont.SetOrientation( aFont.GetOrientation() + nAngle10 );
+ aMtf.AddAction( new MetaFontAction( std::move(aFont) ) );
+ }
+ break;
+
+ case MetaActionType::BMP:
+ case MetaActionType::BMPEX:
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ case MetaActionType::WALLPAPER:
+ case MetaActionType::TEXTRECT:
+ case MetaActionType::MOVECLIPREGION:
+ {
+ OSL_FAIL( "GDIMetaFile::Rotate(): unsupported action" );
+ }
+ break;
+
+ default:
+ {
+ pAction->Execute( aMapVDev.get() );
+ aMtf.AddAction( pAction );
+
+ // update rotation point and offset, if necessary
+ if( ( MetaActionType::MAPMODE == nActionType ) ||
+ ( MetaActionType::PUSH == nActionType ) ||
+ ( MetaActionType::POP == nActionType ) )
+ {
+ aRotAnchor = OutputDevice::LogicToLogic( aOrigin, m_aPrefMapMode, aMapVDev->GetMapMode() );
+ aRotOffset = OutputDevice::LogicToLogic( aOffset, m_aPrefMapMode, aMapVDev->GetMapMode() );
+ }
+ }
+ break;
+ }
+ }
+
+ aMtf.m_aPrefMapMode = m_aPrefMapMode;
+ aMtf.m_aPrefSize = aNewBound.GetSize();
+
+ *this = aMtf;
+
+}
+
+static void ImplActionBounds( tools::Rectangle& o_rOutBounds,
+ const tools::Rectangle& i_rInBounds,
+ const std::vector<tools::Rectangle>& i_rClipStack )
+{
+ tools::Rectangle aBounds( i_rInBounds );
+ if( ! i_rInBounds.IsEmpty() && ! i_rClipStack.empty() && ! i_rClipStack.back().IsEmpty() )
+ aBounds.Intersection( i_rClipStack.back() );
+ if( aBounds.IsEmpty() )
+ return;
+
+ if( ! o_rOutBounds.IsEmpty() )
+ o_rOutBounds.Union( aBounds );
+ else
+ o_rOutBounds = aBounds;
+}
+
+tools::Rectangle GDIMetaFile::GetBoundRect( OutputDevice& i_rReference ) const
+{
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev( i_rReference );
+
+ aMapVDev->EnableOutput( false );
+ aMapVDev->SetMapMode( GetPrefMapMode() );
+
+ std::vector<tools::Rectangle> aClipStack( 1, tools::Rectangle() );
+ std::vector<vcl::PushFlags> aPushFlagStack;
+
+ tools::Rectangle aBound;
+ const sal_uLong nCount(GetActionSize());
+
+ for(sal_uLong a(0); a < nCount; a++)
+ {
+ MetaAction* pAction = GetAction(a);
+ const MetaActionType nActionType = pAction->GetType();
+
+ switch( nActionType )
+ {
+ case MetaActionType::PIXEL:
+ {
+ MetaPixelAction* pAct = static_cast<MetaPixelAction*>(pAction);
+ ImplActionBounds( aBound,
+ tools::Rectangle( OutputDevice::LogicToLogic( pAct->GetPoint(), aMapVDev->GetMapMode(), GetPrefMapMode() ),
+ aMapVDev->PixelToLogic( Size( 1, 1 ), GetPrefMapMode() ) ),
+ aClipStack );
+ }
+ break;
+
+ case MetaActionType::POINT:
+ {
+ MetaPointAction* pAct = static_cast<MetaPointAction*>(pAction);
+ ImplActionBounds( aBound,
+ tools::Rectangle( OutputDevice::LogicToLogic( pAct->GetPoint(), aMapVDev->GetMapMode(), GetPrefMapMode() ),
+ aMapVDev->PixelToLogic( Size( 1, 1 ), GetPrefMapMode() ) ),
+ aClipStack );
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ MetaLineAction* pAct = static_cast<MetaLineAction*>(pAction);
+ Point aP1( pAct->GetStartPoint() ), aP2( pAct->GetEndPoint() );
+ tools::Rectangle aRect( aP1, aP2 );
+ aRect.Normalize();
+
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ MetaRectAction* pAct = static_cast<MetaRectAction*>(pAction);
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ MetaRoundRectAction* pAct = static_cast<MetaRoundRectAction*>(pAction);
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ MetaEllipseAction* pAct = static_cast<MetaEllipseAction*>(pAction);
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::ARC:
+ {
+ MetaArcAction* pAct = static_cast<MetaArcAction*>(pAction);
+ // FIXME: this is imprecise
+ // e.g. for small arcs the whole rectangle is WAY too large
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::PIE:
+ {
+ MetaPieAction* pAct = static_cast<MetaPieAction*>(pAction);
+ // FIXME: this is imprecise
+ // e.g. for small arcs the whole rectangle is WAY too large
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::CHORD:
+ {
+ MetaChordAction* pAct = static_cast<MetaChordAction*>(pAction);
+ // FIXME: this is imprecise
+ // e.g. for small arcs the whole rectangle is WAY too large
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ MetaPolyLineAction* pAct = static_cast<MetaPolyLineAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPolygon().GetBoundRect() );
+
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::POLYGON:
+ {
+ MetaPolygonAction* pAct = static_cast<MetaPolygonAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPolygon().GetBoundRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ {
+ MetaPolyPolygonAction* pAct = static_cast<MetaPolyPolygonAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ MetaTextAction* pAct = static_cast<MetaTextAction*>(pAction);
+ tools::Rectangle aRect;
+ // hdu said base = index
+ aMapVDev->GetTextBoundRect( aRect, pAct->GetText(), pAct->GetIndex(), pAct->GetIndex(), pAct->GetLen() );
+ Point aPt( pAct->GetPoint() );
+ aRect.Move( aPt.X(), aPt.Y() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pAction);
+ tools::Rectangle aRect;
+ // hdu said base = index
+ aMapVDev->GetTextBoundRect( aRect, pAct->GetText(), pAct->GetIndex(), pAct->GetIndex(), pAct->GetLen(),
+ 0, pAct->GetDXArray(), pAct->GetKashidaArray() );
+ Point aPt( pAct->GetPoint() );
+ aRect.Move( aPt.X(), aPt.Y() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ MetaStretchTextAction* pAct = static_cast<MetaStretchTextAction*>(pAction);
+ tools::Rectangle aRect;
+ // hdu said base = index
+ aMapVDev->GetTextBoundRect( aRect, pAct->GetText(), pAct->GetIndex(), pAct->GetIndex(), pAct->GetLen(),
+ pAct->GetWidth() );
+ Point aPt( pAct->GetPoint() );
+ aRect.Move( aPt.X(), aPt.Y() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ {
+ MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pAction);
+ // measure a test string to get ascend and descent right
+ static constexpr OUStringLiteral pStr = u"\u00c4g";
+ OUString aStr( pStr );
+
+ tools::Rectangle aRect;
+ aMapVDev->GetTextBoundRect( aRect, aStr, 0, 0, aStr.getLength() );
+ Point aPt( pAct->GetStartPoint() );
+ aRect.Move( aPt.X(), aPt.Y() );
+ aRect.SetRight( aRect.Left() + pAct->GetWidth() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::COMMENT:
+ {
+ // nothing to do
+ };
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ MetaHatchAction* pAct = static_cast<MetaHatchAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ MetaTransparentAction* pAct = static_cast<MetaTransparentAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pAction);
+ // MetaFloatTransparentAction is defined limiting its content Metafile
+ // to its geometry definition(Point, Size), so use these directly
+ const tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ {
+ MetaClipRegionAction* pAct = static_cast<MetaClipRegionAction*>(pAction);
+ if( pAct->IsClipping() )
+ aClipStack.back() = OutputDevice::LogicToLogic( pAct->GetRegion().GetBoundRect(), aMapVDev->GetMapMode(), GetPrefMapMode() );
+ else
+ aClipStack.back() = tools::Rectangle();
+ }
+ break;
+
+ case MetaActionType::ISECTRECTCLIPREGION:
+ {
+ MetaISectRectClipRegionAction* pAct = static_cast<MetaISectRectClipRegionAction*>(pAction);
+ tools::Rectangle aRect( OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ) );
+ if( aClipStack.back().IsEmpty() )
+ aClipStack.back() = aRect;
+ else
+ aClipStack.back().Intersection( aRect );
+ }
+ break;
+
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ {
+ MetaISectRegionClipRegionAction* pAct = static_cast<MetaISectRegionClipRegionAction*>(pAction);
+ tools::Rectangle aRect( OutputDevice::LogicToLogic( pAct->GetRegion().GetBoundRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ) );
+ if( aClipStack.back().IsEmpty() )
+ aClipStack.back() = aRect;
+ else
+ aClipStack.back().Intersection( aRect );
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ MetaBmpAction* pAct = static_cast<MetaBmpAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPoint(), aMapVDev->PixelToLogic( pAct->GetBitmap().GetSizePixel() ) );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ MetaBmpExAction* pAct = static_cast<MetaBmpExAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPoint(), aMapVDev->PixelToLogic( pAct->GetBitmapEx().GetSizePixel() ) );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::MASK:
+ {
+ MetaMaskAction* pAct = static_cast<MetaMaskAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPoint(), aMapVDev->PixelToLogic( pAct->GetBitmap().GetSizePixel() ) );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::MASKSCALE:
+ {
+ MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ {
+ MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::WALLPAPER:
+ {
+ MetaWallpaperAction* pAct = static_cast<MetaWallpaperAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ MetaTextRectAction* pAct = static_cast<MetaTextRectAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
+ }
+ break;
+
+ case MetaActionType::MOVECLIPREGION:
+ {
+ MetaMoveClipRegionAction* pAct = static_cast<MetaMoveClipRegionAction*>(pAction);
+ if( ! aClipStack.back().IsEmpty() )
+ {
+ Size aDelta( pAct->GetHorzMove(), pAct->GetVertMove() );
+ aDelta = OutputDevice::LogicToLogic( aDelta, aMapVDev->GetMapMode(), GetPrefMapMode() );
+ aClipStack.back().Move( aDelta.Width(), aDelta.Width() );
+ }
+ }
+ break;
+
+ default:
+ {
+ pAction->Execute( aMapVDev.get() );
+
+ if( nActionType == MetaActionType::PUSH )
+ {
+ MetaPushAction* pAct = static_cast<MetaPushAction*>(pAction);
+ aPushFlagStack.push_back( pAct->GetFlags() );
+ if( aPushFlagStack.back() & vcl::PushFlags::CLIPREGION )
+ {
+ tools::Rectangle aRect( aClipStack.back() );
+ aClipStack.push_back( aRect );
+ }
+ }
+ else if( nActionType == MetaActionType::POP )
+ {
+ // sanity check
+ if( ! aPushFlagStack.empty() )
+ {
+ if( aPushFlagStack.back() & vcl::PushFlags::CLIPREGION )
+ {
+ if( aClipStack.size() > 1 )
+ aClipStack.pop_back();
+ }
+ aPushFlagStack.pop_back();
+ }
+ }
+ }
+ break;
+ }
+ }
+ return aBound;
+}
+
+Color GDIMetaFile::ImplColAdjustFnc( const Color& rColor, const void* pColParam )
+{
+ return Color( ColorAlpha, rColor.GetAlpha(),
+ static_cast<const ImplColAdjustParam*>(pColParam)->pMapR[ rColor.GetRed() ],
+ static_cast<const ImplColAdjustParam*>(pColParam)->pMapG[ rColor.GetGreen() ],
+ static_cast<const ImplColAdjustParam*>(pColParam)->pMapB[ rColor.GetBlue() ] );
+
+}
+
+BitmapEx GDIMetaFile::ImplBmpAdjustFnc( const BitmapEx& rBmpEx, const void* pBmpParam )
+{
+ const ImplBmpAdjustParam* p = static_cast<const ImplBmpAdjustParam*>(pBmpParam);
+ BitmapEx aRet( rBmpEx );
+
+ aRet.Adjust( p->nLuminancePercent, p->nContrastPercent,
+ p->nChannelRPercent, p->nChannelGPercent, p->nChannelBPercent,
+ p->fGamma, p->bInvert );
+
+ return aRet;
+}
+
+Color GDIMetaFile::ImplColConvertFnc( const Color& rColor, const void* pColParam )
+{
+ sal_uInt8 cLum = rColor.GetLuminance();
+
+ if( MtfConversion::N1BitThreshold == static_cast<const ImplColConvertParam*>(pColParam)->eConversion )
+ cLum = ( cLum < 128 ) ? 0 : 255;
+
+ return Color( ColorAlpha, rColor.GetAlpha(), cLum, cLum, cLum );
+}
+
+BitmapEx GDIMetaFile::ImplBmpConvertFnc( const BitmapEx& rBmpEx, const void* pBmpParam )
+{
+ BitmapEx aRet( rBmpEx );
+
+ aRet.Convert( static_cast<const ImplBmpConvertParam*>(pBmpParam)->eConversion );
+
+ return aRet;
+}
+
+Color GDIMetaFile::ImplColMonoFnc( const Color&, const void* pColParam )
+{
+ return static_cast<const ImplColMonoParam*>(pColParam)->aColor;
+}
+
+BitmapEx GDIMetaFile::ImplBmpMonoFnc( const BitmapEx& rBmpEx, const void* pBmpParam )
+{
+ BitmapPalette aPal( 3 );
+ aPal[ 0 ] = COL_BLACK;
+ aPal[ 1 ] = COL_WHITE;
+ aPal[ 2 ] = static_cast<const ImplBmpMonoParam*>(pBmpParam)->aColor;
+
+ Bitmap aBmp(rBmpEx.GetSizePixel(), vcl::PixelFormat::N8_BPP, &aPal);
+ aBmp.Erase( static_cast<const ImplBmpMonoParam*>(pBmpParam)->aColor );
+
+ if( rBmpEx.IsAlpha() )
+ return BitmapEx( aBmp, rBmpEx.GetAlphaMask() );
+ else
+ return BitmapEx( aBmp );
+}
+
+Color GDIMetaFile::ImplColReplaceFnc( const Color& rColor, const void* pColParam )
+{
+ const sal_uLong nR = rColor.GetRed(), nG = rColor.GetGreen(), nB = rColor.GetBlue();
+
+ for( sal_uLong i = 0; i < static_cast<const ImplColReplaceParam*>(pColParam)->nCount; i++ )
+ {
+ if( ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinR[ i ] <= nR ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxR[ i ] >= nR ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinG[ i ] <= nG ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxG[ i ] >= nG ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinB[ i ] <= nB ) &&
+ ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxB[ i ] >= nB ) )
+ {
+ return static_cast<const ImplColReplaceParam*>(pColParam)->pDstCols[ i ];
+ }
+ }
+
+ return rColor;
+}
+
+BitmapEx GDIMetaFile::ImplBmpReplaceFnc( const BitmapEx& rBmpEx, const void* pBmpParam )
+{
+ const ImplBmpReplaceParam* p = static_cast<const ImplBmpReplaceParam*>(pBmpParam);
+ BitmapEx aRet( rBmpEx );
+
+ aRet.Replace( p->pSrcCols, p->pDstCols, p->nCount );
+
+ return aRet;
+}
+
+void GDIMetaFile::ImplExchangeColors( ColorExchangeFnc pFncCol, const void* pColParam,
+ BmpExchangeFnc pFncBmp, const void* pBmpParam )
+{
+ GDIMetaFile aMtf;
+
+ aMtf.m_aPrefSize = m_aPrefSize;
+ aMtf.m_aPrefMapMode = m_aPrefMapMode;
+ aMtf.m_bUseCanvas = m_bUseCanvas;
+ aMtf.m_bSVG = m_bSVG;
+
+ for( MetaAction* pAction = FirstAction(); pAction; pAction = NextAction() )
+ {
+ const MetaActionType nType = pAction->GetType();
+
+ switch( nType )
+ {
+ case MetaActionType::PIXEL:
+ {
+ MetaPixelAction* pAct = static_cast<MetaPixelAction*>(pAction);
+ aMtf.push_back( new MetaPixelAction( pAct->GetPoint(), pFncCol( pAct->GetColor(), pColParam ) ) );
+ }
+ break;
+
+ case MetaActionType::LINECOLOR:
+ {
+ MetaLineColorAction* pAct = static_cast<MetaLineColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaLineColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::FILLCOLOR:
+ {
+ MetaFillColorAction* pAct = static_cast<MetaFillColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaFillColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::TEXTCOLOR:
+ {
+ MetaTextColorAction* pAct = static_cast<MetaTextColorAction*>(pAction);
+ aMtf.push_back( new MetaTextColorAction( pFncCol( pAct->GetColor(), pColParam ) ) );
+ }
+ break;
+
+ case MetaActionType::TEXTFILLCOLOR:
+ {
+ MetaTextFillColorAction* pAct = static_cast<MetaTextFillColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaTextFillColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::TEXTLINECOLOR:
+ {
+ MetaTextLineColorAction* pAct = static_cast<MetaTextLineColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaTextLineColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::OVERLINECOLOR:
+ {
+ MetaOverlineColorAction* pAct = static_cast<MetaOverlineColorAction*>(pAction);
+
+ if( pAct->IsSetting() )
+ pAct = new MetaOverlineColorAction( pFncCol( pAct->GetColor(), pColParam ), true );
+
+ aMtf.push_back( pAct );
+ }
+ break;
+
+ case MetaActionType::FONT:
+ {
+ MetaFontAction* pAct = static_cast<MetaFontAction*>(pAction);
+ vcl::Font aFont( pAct->GetFont() );
+
+ aFont.SetColor( pFncCol( aFont.GetColor(), pColParam ) );
+ aFont.SetFillColor( pFncCol( aFont.GetFillColor(), pColParam ) );
+ aMtf.push_back( new MetaFontAction( std::move(aFont) ) );
+ }
+ break;
+
+ case MetaActionType::WALLPAPER:
+ {
+ MetaWallpaperAction* pAct = static_cast<MetaWallpaperAction*>(pAction);
+ Wallpaper aWall( pAct->GetWallpaper() );
+ const tools::Rectangle& rRect = pAct->GetRect();
+
+ aWall.SetColor( pFncCol( aWall.GetColor(), pColParam ) );
+
+ if( aWall.IsBitmap() )
+ aWall.SetBitmap( pFncBmp( aWall.GetBitmap(), pBmpParam ) );
+
+ if( aWall.IsGradient() )
+ {
+ Gradient aGradient( aWall.GetGradient() );
+
+ aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) );
+ aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) );
+ aWall.SetGradient( aGradient );
+ }
+
+ aMtf.push_back( new MetaWallpaperAction( rRect, std::move(aWall) ) );
+ }
+ break;
+
+ case MetaActionType::BMP:
+ case MetaActionType::BMPEX:
+ case MetaActionType::MASK:
+ {
+ OSL_FAIL( "Don't use bitmap actions of this type in metafiles!" );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction);
+ aMtf.push_back( new MetaBmpScaleAction( pAct->GetPoint(), pAct->GetSize(),
+ pFncBmp( BitmapEx(pAct->GetBitmap()), pBmpParam ).GetBitmap() ) );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction);
+ aMtf.push_back( new MetaBmpScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(),
+ pAct->GetSrcPoint(), pAct->GetSrcSize(),
+ pFncBmp( BitmapEx(pAct->GetBitmap()), pBmpParam ).GetBitmap() )
+ );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction);
+ aMtf.push_back( new MetaBmpExScaleAction( pAct->GetPoint(), pAct->GetSize(),
+ pFncBmp( pAct->GetBitmapEx(), pBmpParam ) )
+ );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction);
+ aMtf.push_back( new MetaBmpExScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(),
+ pAct->GetSrcPoint(), pAct->GetSrcSize(),
+ pFncBmp( pAct->GetBitmapEx(), pBmpParam ) )
+ );
+ }
+ break;
+
+ case MetaActionType::MASKSCALE:
+ {
+ MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pAction);
+ aMtf.push_back( new MetaMaskScaleAction( pAct->GetPoint(), pAct->GetSize(),
+ pAct->GetBitmap(),
+ pFncCol( pAct->GetColor(), pColParam ) )
+ );
+ }
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ {
+ MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction);
+ aMtf.push_back( new MetaMaskScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(),
+ pAct->GetSrcPoint(), pAct->GetSrcSize(),
+ pAct->GetBitmap(),
+ pFncCol( pAct->GetColor(), pColParam ) )
+ );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction);
+ Gradient aGradient( pAct->GetGradient() );
+
+ aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) );
+ aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) );
+ aMtf.push_back( new MetaGradientAction( pAct->GetRect(), std::move(aGradient) ) );
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction);
+ Gradient aGradient( pAct->GetGradient() );
+
+ aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) );
+ aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) );
+ aMtf.push_back( new MetaGradientExAction( pAct->GetPolyPolygon(), std::move(aGradient) ) );
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ MetaHatchAction* pAct = static_cast<MetaHatchAction*>(pAction);
+ Hatch aHatch( pAct->GetHatch() );
+
+ aHatch.SetColor( pFncCol( aHatch.GetColor(), pColParam ) );
+ aMtf.push_back( new MetaHatchAction( pAct->GetPolyPolygon(), aHatch ) );
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pAction);
+ GDIMetaFile aTransMtf( pAct->GetGDIMetaFile() );
+
+ aTransMtf.ImplExchangeColors( pFncCol, pColParam, pFncBmp, pBmpParam );
+ aMtf.push_back( new MetaFloatTransparentAction( aTransMtf,
+ pAct->GetPoint(), pAct->GetSize(),
+ pAct->GetGradient() )
+ );
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction);
+ GDIMetaFile aSubst( pAct->GetSubstitute() );
+
+ aSubst.ImplExchangeColors( pFncCol, pColParam, pFncBmp, pBmpParam );
+ aMtf.push_back( new MetaEPSAction( pAct->GetPoint(), pAct->GetSize(),
+ pAct->GetLink(), aSubst )
+ );
+ }
+ break;
+
+ default:
+ {
+ aMtf.push_back( pAction );
+ }
+ break;
+ }
+ }
+
+ *this = aMtf;
+}
+
+void GDIMetaFile::Adjust( short nLuminancePercent, short nContrastPercent,
+ short nChannelRPercent, short nChannelGPercent,
+ short nChannelBPercent, double fGamma, bool bInvert, bool msoBrightness )
+{
+ // nothing to do? => return quickly
+ if( !(nLuminancePercent || nContrastPercent ||
+ nChannelRPercent || nChannelGPercent || nChannelBPercent ||
+ ( fGamma != 1.0 ) || bInvert) )
+ return;
+
+ double fM, fROff, fGOff, fBOff, fOff;
+ ImplColAdjustParam aColParam;
+ ImplBmpAdjustParam aBmpParam;
+
+ aColParam.pMapR.reset(new sal_uInt8[ 256 ]);
+ aColParam.pMapG.reset(new sal_uInt8[ 256 ]);
+ aColParam.pMapB.reset(new sal_uInt8[ 256 ]);
+
+ // calculate slope
+ if( nContrastPercent >= 0 )
+ fM = 128.0 / ( 128.0 - 1.27 * std::clamp( nContrastPercent, short(0), short(100) ) );
+ else
+ fM = ( 128.0 + 1.27 * std::clamp( nContrastPercent, short(-100), short(0) ) ) / 128.0;
+
+ if(!msoBrightness)
+ // total offset = luminance offset + contrast offset
+ fOff = std::clamp( nLuminancePercent, short(-100), short(100) ) * 2.55 + 128.0 - fM * 128.0;
+ else
+ fOff = std::clamp( nLuminancePercent, short(-100), short(100) ) * 2.55;
+
+ // channel offset = channel offset + total offset
+ fROff = nChannelRPercent * 2.55 + fOff;
+ fGOff = nChannelGPercent * 2.55 + fOff;
+ fBOff = nChannelBPercent * 2.55 + fOff;
+
+ // calculate gamma value
+ fGamma = ( fGamma <= 0.0 || fGamma > 10.0 ) ? 1.0 : ( 1.0 / fGamma );
+ const bool bGamma = ( fGamma != 1.0 );
+
+ // create mapping table
+ for( tools::Long nX = 0; nX < 256; nX++ )
+ {
+ if(!msoBrightness)
+ {
+ aColParam.pMapR[ nX ] = FRound(std::clamp( nX * fM + fROff, 0.0, 255.0 ));
+ aColParam.pMapG[ nX ] = FRound(std::clamp( nX * fM + fGOff, 0.0, 255.0 ));
+ aColParam.pMapB[ nX ] = FRound(std::clamp( nX * fM + fBOff, 0.0, 255.0 ));
+ }
+ else
+ {
+ aColParam.pMapR[ nX ] = FRound(std::clamp( (nX+fROff/2-128) * fM + 128 + fROff/2, 0.0, 255.0 ));
+ aColParam.pMapG[ nX ] = FRound(std::clamp( (nX+fGOff/2-128) * fM + 128 + fGOff/2, 0.0, 255.0 ));
+ aColParam.pMapB[ nX ] = FRound(std::clamp( (nX+fBOff/2-128) * fM + 128 + fBOff/2, 0.0, 255.0 ));
+ }
+ if( bGamma )
+ {
+ aColParam.pMapR[ nX ] = GAMMA( aColParam.pMapR[ nX ], fGamma );
+ aColParam.pMapG[ nX ] = GAMMA( aColParam.pMapG[ nX ], fGamma );
+ aColParam.pMapB[ nX ] = GAMMA( aColParam.pMapB[ nX ], fGamma );
+ }
+
+ if( bInvert )
+ {
+ aColParam.pMapR[ nX ] = ~aColParam.pMapR[ nX ];
+ aColParam.pMapG[ nX ] = ~aColParam.pMapG[ nX ];
+ aColParam.pMapB[ nX ] = ~aColParam.pMapB[ nX ];
+ }
+ }
+
+ aBmpParam.nLuminancePercent = nLuminancePercent;
+ aBmpParam.nContrastPercent = nContrastPercent;
+ aBmpParam.nChannelRPercent = nChannelRPercent;
+ aBmpParam.nChannelGPercent = nChannelGPercent;
+ aBmpParam.nChannelBPercent = nChannelBPercent;
+ aBmpParam.fGamma = fGamma;
+ aBmpParam.bInvert = bInvert;
+
+ // do color adjustment
+ ImplExchangeColors( ImplColAdjustFnc, &aColParam, ImplBmpAdjustFnc, &aBmpParam );
+}
+
+void GDIMetaFile::Convert( MtfConversion eConversion )
+{
+ ImplColConvertParam aColParam;
+ ImplBmpConvertParam aBmpParam;
+
+ aColParam.eConversion = eConversion;
+ aBmpParam.eConversion = ( MtfConversion::N1BitThreshold == eConversion ) ? BmpConversion::N1BitThreshold : BmpConversion::N8BitGreys;
+
+ ImplExchangeColors( ImplColConvertFnc, &aColParam, ImplBmpConvertFnc, &aBmpParam );
+}
+
+void GDIMetaFile::ReplaceColors( const Color* pSearchColors, const Color* pReplaceColors, sal_uLong nColorCount )
+{
+ ImplColReplaceParam aColParam;
+ ImplBmpReplaceParam aBmpParam;
+
+ aColParam.pMinR.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMaxR.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMinG.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMaxG.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMinB.reset(new sal_uLong[ nColorCount ]);
+ aColParam.pMaxB.reset(new sal_uLong[ nColorCount ]);
+
+ for( sal_uLong i = 0; i < nColorCount; i++ )
+ {
+ tools::Long nVal;
+
+ nVal = pSearchColors[ i ].GetRed();
+ aColParam.pMinR[ i ] = static_cast<sal_uLong>(std::max( nVal, tools::Long(0) ));
+ aColParam.pMaxR[ i ] = static_cast<sal_uLong>(std::min( nVal, tools::Long(255) ));
+
+ nVal = pSearchColors[ i ].GetGreen();
+ aColParam.pMinG[ i ] = static_cast<sal_uLong>(std::max( nVal, tools::Long(0) ));
+ aColParam.pMaxG[ i ] = static_cast<sal_uLong>(std::min( nVal, tools::Long(255) ));
+
+ nVal = pSearchColors[ i ].GetBlue();
+ aColParam.pMinB[ i ] = static_cast<sal_uLong>(std::max( nVal, tools::Long(0) ));
+ aColParam.pMaxB[ i ] = static_cast<sal_uLong>(std::min( nVal, tools::Long(255) ));
+ }
+
+ aColParam.pDstCols = pReplaceColors;
+ aColParam.nCount = nColorCount;
+
+ aBmpParam.pSrcCols = pSearchColors;
+ aBmpParam.pDstCols = pReplaceColors;
+ aBmpParam.nCount = nColorCount;
+
+ ImplExchangeColors( ImplColReplaceFnc, &aColParam, ImplBmpReplaceFnc, &aBmpParam );
+};
+
+GDIMetaFile GDIMetaFile::GetMonochromeMtf( const Color& rColor ) const
+{
+ GDIMetaFile aRet( *this );
+
+ ImplColMonoParam aColParam;
+ ImplBmpMonoParam aBmpParam;
+
+ aColParam.aColor = rColor;
+ aBmpParam.aColor = rColor;
+
+ aRet.ImplExchangeColors( ImplColMonoFnc, &aColParam, ImplBmpMonoFnc, &aBmpParam );
+
+ return aRet;
+}
+
+sal_uLong GDIMetaFile::GetSizeBytes() const
+{
+ sal_uLong nSizeBytes = 0;
+
+ for( size_t i = 0, nObjCount = GetActionSize(); i < nObjCount; ++i )
+ {
+ MetaAction* pAction = GetAction( i );
+
+ // default action size is set to 32 (=> not the exact value)
+ nSizeBytes += 32;
+
+ // add sizes for large action content
+ switch( pAction->GetType() )
+ {
+ case MetaActionType::BMP: nSizeBytes += static_cast<MetaBmpAction*>( pAction )->GetBitmap().GetSizeBytes(); break;
+ case MetaActionType::BMPSCALE: nSizeBytes += static_cast<MetaBmpScaleAction*>( pAction )->GetBitmap().GetSizeBytes(); break;
+ case MetaActionType::BMPSCALEPART: nSizeBytes += static_cast<MetaBmpScalePartAction*>( pAction )->GetBitmap().GetSizeBytes(); break;
+
+ case MetaActionType::BMPEX: nSizeBytes += static_cast<MetaBmpExAction*>( pAction )->GetBitmapEx().GetSizeBytes(); break;
+ case MetaActionType::BMPEXSCALE: nSizeBytes += static_cast<MetaBmpExScaleAction*>( pAction )->GetBitmapEx().GetSizeBytes(); break;
+ case MetaActionType::BMPEXSCALEPART: nSizeBytes += static_cast<MetaBmpExScalePartAction*>( pAction )->GetBitmapEx().GetSizeBytes(); break;
+
+ case MetaActionType::MASK: nSizeBytes += static_cast<MetaMaskAction*>( pAction )->GetBitmap().GetSizeBytes(); break;
+ case MetaActionType::MASKSCALE: nSizeBytes += static_cast<MetaMaskScaleAction*>( pAction )->GetBitmap().GetSizeBytes(); break;
+ case MetaActionType::MASKSCALEPART: nSizeBytes += static_cast<MetaMaskScalePartAction*>( pAction )->GetBitmap().GetSizeBytes(); break;
+
+ case MetaActionType::POLYLINE: nSizeBytes += static_cast<MetaPolyLineAction*>( pAction )->GetPolygon().GetSize() * sizeof( Point ); break;
+ case MetaActionType::POLYGON: nSizeBytes += static_cast<MetaPolygonAction*>( pAction )->GetPolygon().GetSize() * sizeof( Point ); break;
+ case MetaActionType::POLYPOLYGON:
+ {
+ const tools::PolyPolygon& rPolyPoly = static_cast<MetaPolyPolygonAction*>( pAction )->GetPolyPolygon();
+
+ for( sal_uInt16 n = 0; n < rPolyPoly.Count(); ++n )
+ nSizeBytes += ( rPolyPoly[ n ].GetSize() * sizeof( Point ) );
+ }
+ break;
+
+ case MetaActionType::TEXT: nSizeBytes += static_cast<MetaTextAction*>( pAction )->GetText().getLength() * sizeof( sal_Unicode ); break;
+ case MetaActionType::STRETCHTEXT: nSizeBytes += static_cast<MetaStretchTextAction*>( pAction )->GetText().getLength() * sizeof( sal_Unicode ); break;
+ case MetaActionType::TEXTRECT: nSizeBytes += static_cast<MetaTextRectAction*>( pAction )->GetText().getLength() * sizeof( sal_Unicode ); break;
+ case MetaActionType::TEXTARRAY:
+ {
+ MetaTextArrayAction* pTextArrayAction = static_cast<MetaTextArrayAction*>(pAction);
+
+ nSizeBytes += ( pTextArrayAction->GetText().getLength() * sizeof( sal_Unicode ) );
+
+ if( !pTextArrayAction->GetDXArray().empty() )
+ nSizeBytes += ( pTextArrayAction->GetLen() << 2 );
+ }
+ break;
+ default: break;
+ }
+ }
+
+ return nSizeBytes;
+}
+
+bool GDIMetaFile::CreateThumbnail(BitmapEx& rBitmapEx, BmpConversion eColorConversion, BmpScaleFlag nScaleFlag) const
+{
+ // initialization seems to be complicated but is used to avoid rounding errors
+ ScopedVclPtrInstance< VirtualDevice > aVDev;
+ // set Enable to tease the rendering down the code paths which use B2DPolygon and
+ // avoid integer overflows on scaling tools::Polygon, e.g. moz1545040-1.svg
+ // note: this is similar to DocumentToGraphicRenderer::renderToGraphic
+ aVDev->SetAntialiasing(AntialiasingFlags::Enable | aVDev->GetAntialiasing());
+ const Point aNullPt;
+ const Point aTLPix( aVDev->LogicToPixel( aNullPt, GetPrefMapMode() ) );
+ const Point aBRPix( aVDev->LogicToPixel( Point( GetPrefSize().Width() - 1, GetPrefSize().Height() - 1 ), GetPrefMapMode() ) );
+ Size aDrawSize( aVDev->LogicToPixel( GetPrefSize(), GetPrefMapMode() ) );
+ Size aSizePix( std::abs( aBRPix.X() - aTLPix.X() ) + 1, std::abs( aBRPix.Y() - aTLPix.Y() ) + 1 );
+ sal_uInt32 nMaximumExtent = 512;
+
+ if (!rBitmapEx.IsEmpty())
+ rBitmapEx.SetEmpty();
+
+ // determine size that has the same aspect ratio as image size and
+ // fits into the rectangle determined by nMaximumExtent
+ if ( aSizePix.Width() && aSizePix.Height()
+ && ( sal::static_int_cast< tools::ULong >(aSizePix.Width()) >
+ nMaximumExtent ||
+ sal::static_int_cast< tools::ULong >(aSizePix.Height()) >
+ nMaximumExtent ) )
+ {
+ const Size aOldSizePix( aSizePix );
+ double fWH = static_cast< double >( aSizePix.Width() ) / aSizePix.Height();
+
+ if ( fWH <= 1.0 )
+ {
+ aSizePix.setWidth( FRound( nMaximumExtent * fWH ) );
+ aSizePix.setHeight( nMaximumExtent );
+ }
+ else
+ {
+ aSizePix.setWidth( nMaximumExtent );
+ aSizePix.setHeight( FRound( nMaximumExtent / fWH ) );
+ }
+
+ aDrawSize.setWidth( FRound( ( static_cast< double >( aDrawSize.Width() ) * aSizePix.Width() ) / aOldSizePix.Width() ) );
+ aDrawSize.setHeight( FRound( ( static_cast< double >( aDrawSize.Height() ) * aSizePix.Height() ) / aOldSizePix.Height() ) );
+ }
+
+ // draw image(s) into VDev and get resulting image
+ // do it 4x larger to be able to scale it down & get beautiful antialias
+ Size aAntialiasSize(aSizePix.Width() * 4, aSizePix.Height() * 4);
+ if (aVDev->SetOutputSizePixel(aAntialiasSize))
+ {
+ // antialias: provide 4x larger size, and then scale down the result
+ Size aAntialias(aDrawSize.Width() * 4, aDrawSize.Height() * 4);
+
+ // draw metafile into VDev
+ const_cast<GDIMetaFile *>(this)->WindStart();
+ const_cast<GDIMetaFile *>(this)->Play(*aVDev, Point(), aAntialias);
+
+ // get paint bitmap
+ BitmapEx aBitmap( aVDev->GetBitmapEx( aNullPt, aVDev->GetOutputSizePixel() ) );
+
+ // scale down the image to the desired size - use the input scaler for the scaling operation
+ aBitmap.Scale(aDrawSize, nScaleFlag);
+
+ // convert to desired bitmap color format
+ Size aSize(aBitmap.GetSizePixel());
+ if (aSize.Width() && aSize.Height())
+ aBitmap.Convert(eColorConversion);
+
+ rBitmapEx = aBitmap;
+ }
+
+ return !rBitmapEx.IsEmpty();
+}
+
+void GDIMetaFile::UseCanvas( bool _bUseCanvas )
+{
+ m_bUseCanvas = _bUseCanvas;
+}
+
+void GDIMetaFile::dumpAsXml(const char* pFileName) const
+{
+ SvFileStream aStream(pFileName ? OUString::fromUtf8(pFileName) : OUString("file:///tmp/metafile.xml"),
+ StreamMode::STD_READWRITE | StreamMode::TRUNC);
+ assert(aStream.good());
+ MetafileXmlDump aDumper;
+ aDumper.dump(*this, aStream);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/gfxlink.cxx b/vcl/source/gdi/gfxlink.cxx
new file mode 100644
index 0000000000..c6ca4678b0
--- /dev/null
+++ b/vcl/source/gdi/gfxlink.cxx
@@ -0,0 +1,173 @@
+/* -*- 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 <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <utility>
+#include <vcl/graph.hxx>
+#include <vcl/gfxlink.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <memory>
+#include <o3tl/hash_combine.hxx>
+
+GfxLink::GfxLink()
+ : meType(GfxLinkType::NONE)
+ , mnUserId(0)
+ , maHash(0)
+ , mbPrefMapModeValid(false)
+ , mbPrefSizeValid(false)
+{
+}
+
+GfxLink::GfxLink(BinaryDataContainer aDataConainer, GfxLinkType nType)
+ : meType(nType)
+ , mnUserId(0)
+ , maDataContainer(std::move(aDataConainer))
+ , maHash(0)
+ , mbPrefMapModeValid(false)
+ , mbPrefSizeValid(false)
+{
+}
+
+size_t GfxLink::GetHash() const
+{
+ if (!maHash)
+ {
+ std::size_t seed = maDataContainer.calculateHash();
+ o3tl::hash_combine(seed, meType);
+ maHash = seed;
+ }
+ return maHash;
+}
+
+bool GfxLink::operator==( const GfxLink& rGfxLink ) const
+{
+ if (GetDataSize() != rGfxLink.GetDataSize()
+ || meType != rGfxLink.meType
+ || GetHash() != rGfxLink.GetHash())
+ return false;
+
+ const sal_uInt8* pSource = GetData();
+ const sal_uInt8* pDestination = rGfxLink.GetData();
+
+ if (pSource == pDestination)
+ return true;
+
+ sal_uInt32 nSourceSize = GetDataSize();
+ sal_uInt32 nDestSize = rGfxLink.GetDataSize();
+
+ if (pSource && pDestination && (nSourceSize == nDestSize))
+ return memcmp(pSource, pDestination, nSourceSize) == 0;
+
+ return false;
+}
+
+bool GfxLink::IsNative() const
+{
+ return meType >= GfxLinkType::NativeFirst && meType <= GfxLinkType::NativeLast;
+}
+
+const sal_uInt8* GfxLink::GetData() const
+{
+ return maDataContainer.getData();
+}
+
+void GfxLink::SetPrefSize( const Size& rPrefSize )
+{
+ maPrefSize = rPrefSize;
+ mbPrefSizeValid = true;
+}
+
+void GfxLink::SetPrefMapMode( const MapMode& rPrefMapMode )
+{
+ maPrefMapMode = rPrefMapMode;
+ mbPrefMapModeValid = true;
+}
+
+bool GfxLink::LoadNative( Graphic& rGraphic ) const
+{
+ bool bRet = false;
+
+ if (IsNative() && !maDataContainer.isEmpty())
+ {
+ const sal_uInt8* pData = GetData();
+ if (pData)
+ {
+ SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(pData), GetDataSize(), StreamMode::READ | StreamMode::WRITE);
+ OUString aShortName;
+
+ switch (meType)
+ {
+ case GfxLinkType::NativeGif: aShortName = GIF_SHORTNAME; break;
+ case GfxLinkType::NativeJpg: aShortName = JPG_SHORTNAME; break;
+ case GfxLinkType::NativePng: aShortName = PNG_SHORTNAME; break;
+ case GfxLinkType::NativeTif: aShortName = TIF_SHORTNAME; break;
+ case GfxLinkType::NativeWmf: aShortName = WMF_SHORTNAME; break;
+ case GfxLinkType::NativeMet: aShortName = MET_SHORTNAME; break;
+ case GfxLinkType::NativePct: aShortName = PCT_SHORTNAME; break;
+ case GfxLinkType::NativeSvg: aShortName = SVG_SHORTNAME; break;
+ case GfxLinkType::NativeBmp: aShortName = BMP_SHORTNAME; break;
+ case GfxLinkType::NativePdf: aShortName = PDF_SHORTNAME; break;
+ case GfxLinkType::NativeWebp: aShortName = WEBP_SHORTNAME; break;
+ default: break;
+ }
+ if (!aShortName.isEmpty())
+ {
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ sal_uInt16 nFormat = rFilter.GetImportFormatNumberForShortName(aShortName);
+ ErrCode nResult = rFilter.ImportGraphic(rGraphic, u"", aMemoryStream, nFormat);
+ if (nResult == ERRCODE_NONE)
+ bRet = true;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+bool GfxLink::ExportNative( SvStream& rOStream ) const
+{
+ if( GetDataSize() )
+ {
+ auto pData = GetData();
+ if (pData)
+ rOStream.WriteBytes(pData, GetDataSize());
+ }
+
+ return ( rOStream.GetError() == ERRCODE_NONE );
+}
+
+bool GfxLink::IsEMF() const
+{
+ const sal_uInt8* pGraphicAry = GetData();
+ if ((GetType() == GfxLinkType::NativeWmf) && pGraphicAry && (GetDataSize() > 0x2c))
+ {
+ // check the magic number
+ if ((pGraphicAry[0x28] == 0x20) && (pGraphicAry[0x29] == 0x45)
+ && (pGraphicAry[0x2a] == 0x4d) && (pGraphicAry[0x2b] == 0x46))
+ {
+ //emf detected
+ return true;
+ }
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/gradient.cxx b/vcl/source/gdi/gradient.cxx
new file mode 100644
index 0000000000..75a53a2a93
--- /dev/null
+++ b/vcl/source/gdi/gradient.cxx
@@ -0,0 +1,675 @@
+/* -*- 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 <tools/gen.hxx>
+
+#include <vcl/gradient.hxx>
+#include <vcl/metaact.hxx>
+#include <cmath>
+
+class Gradient::Impl
+{
+public:
+ css::awt::GradientStyle meStyle;
+ Color maStartColor;
+ Color maEndColor;
+ Degree10 mnAngle;
+ sal_uInt16 mnBorder;
+ sal_uInt16 mnOfsX;
+ sal_uInt16 mnOfsY;
+ sal_uInt16 mnIntensityStart;
+ sal_uInt16 mnIntensityEnd;
+ sal_uInt16 mnStepCount;
+
+ Impl()
+ : meStyle (css::awt::GradientStyle_LINEAR)
+ , maStartColor(COL_BLACK)
+ , maEndColor(COL_WHITE)
+ , mnAngle(0)
+ , mnBorder(0)
+ , mnOfsX(50)
+ , mnOfsY(50)
+ , mnIntensityStart(100)
+ , mnIntensityEnd(100)
+ , mnStepCount(0)
+ {
+ }
+
+ Impl(const Impl& rImplGradient)
+ : meStyle (rImplGradient.meStyle)
+ , maStartColor(rImplGradient.maStartColor)
+ , maEndColor(rImplGradient.maEndColor)
+ , mnAngle(rImplGradient.mnAngle)
+ , mnBorder(rImplGradient.mnBorder)
+ , mnOfsX(rImplGradient.mnOfsX)
+ , mnOfsY(rImplGradient.mnOfsY)
+ , mnIntensityStart(rImplGradient.mnIntensityStart)
+ , mnIntensityEnd(rImplGradient.mnIntensityEnd)
+ , mnStepCount(rImplGradient.mnStepCount)
+ {
+ }
+
+ bool operator==(const Impl& rImpl_Gradient) const
+ {
+ return (meStyle == rImpl_Gradient.meStyle)
+ && (mnAngle == rImpl_Gradient.mnAngle)
+ && (mnBorder == rImpl_Gradient.mnBorder)
+ && (mnOfsX == rImpl_Gradient.mnOfsX)
+ && (mnOfsY == rImpl_Gradient.mnOfsY)
+ && (mnStepCount == rImpl_Gradient.mnStepCount)
+ && (mnIntensityStart == rImpl_Gradient.mnIntensityStart)
+ && (mnIntensityEnd == rImpl_Gradient.mnIntensityEnd)
+ && (maStartColor == rImpl_Gradient.maStartColor)
+ && (maEndColor == rImpl_Gradient.maEndColor);
+ }
+};
+
+Gradient::Gradient() = default;
+
+Gradient::Gradient( const Gradient& ) = default;
+
+Gradient::Gradient( Gradient&& ) = default;
+
+Gradient::Gradient( css::awt::GradientStyle eStyle,
+ const Color& rStartColor, const Color& rEndColor )
+{
+ mpImplGradient->meStyle = eStyle;
+ mpImplGradient->maStartColor = rStartColor;
+ mpImplGradient->maEndColor = rEndColor;
+}
+
+Gradient::~Gradient() = default;
+
+
+css::awt::GradientStyle Gradient::GetStyle() const
+{
+ return mpImplGradient->meStyle;
+}
+
+void Gradient::SetStyle( css::awt::GradientStyle eStyle )
+{
+ mpImplGradient->meStyle = eStyle;
+}
+
+const Color& Gradient::GetStartColor() const
+{
+ return mpImplGradient->maStartColor;
+}
+
+void Gradient::SetStartColor( const Color& rColor )
+{
+ mpImplGradient->maStartColor = rColor;
+}
+
+const Color& Gradient::GetEndColor() const
+{
+ return mpImplGradient->maEndColor;
+}
+
+void Gradient::SetEndColor( const Color& rColor )
+{
+ mpImplGradient->maEndColor = rColor;
+}
+
+Degree10 Gradient::GetAngle() const
+{
+ return mpImplGradient->mnAngle;
+}
+
+void Gradient::SetAngle( Degree10 nAngle )
+{
+ mpImplGradient->mnAngle = nAngle;
+}
+
+sal_uInt16 Gradient::GetBorder() const
+{
+ return mpImplGradient->mnBorder;
+}
+
+void Gradient::SetBorder( sal_uInt16 nBorder )
+{
+ mpImplGradient->mnBorder = nBorder;
+}
+
+sal_uInt16 Gradient::GetOfsX() const
+{
+ return mpImplGradient->mnOfsX;
+}
+
+void Gradient::SetOfsX( sal_uInt16 nOfsX )
+{
+ mpImplGradient->mnOfsX = nOfsX;
+}
+
+sal_uInt16 Gradient::GetOfsY() const
+{
+ return mpImplGradient->mnOfsY;
+}
+
+void Gradient::SetOfsY( sal_uInt16 nOfsY )
+{
+ mpImplGradient->mnOfsY = nOfsY;
+}
+
+sal_uInt16 Gradient::GetStartIntensity() const
+{
+ return mpImplGradient->mnIntensityStart;
+}
+
+void Gradient::SetStartIntensity( sal_uInt16 nIntens )
+{
+ mpImplGradient->mnIntensityStart = nIntens;
+}
+
+sal_uInt16 Gradient::GetEndIntensity() const
+{
+ return mpImplGradient->mnIntensityEnd;
+}
+
+void Gradient::SetEndIntensity( sal_uInt16 nIntens )
+{
+ mpImplGradient->mnIntensityEnd = nIntens;
+}
+
+sal_uInt16 Gradient::GetSteps() const
+{
+ return mpImplGradient->mnStepCount;
+}
+
+void Gradient::SetSteps( sal_uInt16 nSteps )
+{
+ mpImplGradient->mnStepCount = nSteps;
+}
+
+void Gradient::GetBoundRect( const tools::Rectangle& rRect, tools::Rectangle& rBoundRect, Point& rCenter ) const
+{
+ tools::Rectangle aRect( rRect );
+ Degree10 nAngle = GetAngle() % 3600_deg10;
+
+ if( GetStyle() == css::awt::GradientStyle_LINEAR || GetStyle() == css::awt::GradientStyle_AXIAL )
+ {
+ const double fAngle = toRadians(nAngle);
+ const double fWidth = aRect.GetWidth();
+ const double fHeight = aRect.GetHeight();
+ double fDX = fWidth * fabs( cos( fAngle ) ) +
+ fHeight * fabs( sin( fAngle ) );
+ double fDY = fHeight * fabs( cos( fAngle ) ) +
+ fWidth * fabs( sin( fAngle ) );
+ fDX = (fDX - fWidth) * 0.5 + 0.5;
+ fDY = (fDY - fHeight) * 0.5 + 0.5;
+ aRect.AdjustLeft( -static_cast<tools::Long>(fDX) );
+ aRect.AdjustRight(static_cast<tools::Long>(fDX) );
+ aRect.AdjustTop( -static_cast<tools::Long>(fDY) );
+ aRect.AdjustBottom(static_cast<tools::Long>(fDY) );
+
+ rBoundRect = aRect;
+ rCenter = rRect.Center();
+ }
+ else
+ {
+ if( GetStyle() == css::awt::GradientStyle_SQUARE || GetStyle() == css::awt::GradientStyle_RECT )
+ {
+ const double fAngle = toRadians(nAngle);
+ const double fWidth = aRect.GetWidth();
+ const double fHeight = aRect.GetHeight();
+ double fDX = fWidth * fabs( cos( fAngle ) ) + fHeight * fabs( sin( fAngle ) );
+ double fDY = fHeight * fabs( cos( fAngle ) ) + fWidth * fabs( sin( fAngle ) );
+
+ fDX = ( fDX - fWidth ) * 0.5 + 0.5;
+ fDY = ( fDY - fHeight ) * 0.5 + 0.5;
+
+ aRect.AdjustLeft( -static_cast<tools::Long>(fDX) );
+ aRect.AdjustRight(static_cast<tools::Long>(fDX) );
+ aRect.AdjustTop( -static_cast<tools::Long>(fDY) );
+ aRect.AdjustBottom(static_cast<tools::Long>(fDY) );
+ }
+
+ Size aSize( aRect.GetSize() );
+
+ if( GetStyle() == css::awt::GradientStyle_RADIAL )
+ {
+ // Calculation of radii for circle
+ aSize.setWidth( static_cast<tools::Long>(0.5 + std::hypot(aSize.Width(), aSize.Height())) );
+ aSize.setHeight( aSize.Width() );
+ }
+ else if( GetStyle() == css::awt::GradientStyle_ELLIPTICAL )
+ {
+ // Calculation of radii for ellipse
+ aSize.setWidth( static_cast<tools::Long>( 0.5 + static_cast<double>(aSize.Width()) * M_SQRT2 ) );
+ aSize.setHeight( static_cast<tools::Long>( 0.5 + static_cast<double>(aSize.Height()) * M_SQRT2) );
+ }
+
+ // Calculate new centers
+ tools::Long nZWidth = aRect.GetWidth() * static_cast<tools::Long>(GetOfsX()) / 100;
+ tools::Long nZHeight = aRect.GetHeight() * static_cast<tools::Long>(GetOfsY()) / 100;
+ tools::Long nBorderX = static_cast<tools::Long>(GetBorder()) * aSize.Width() / 100;
+ tools::Long nBorderY = static_cast<tools::Long>(GetBorder()) * aSize.Height() / 100;
+ rCenter = Point( aRect.Left() + nZWidth, aRect.Top() + nZHeight );
+
+ // Respect borders
+ aSize.AdjustWidth( -nBorderX );
+ aSize.AdjustHeight( -nBorderY );
+
+ // Recalculate output rectangle
+ aRect.SetLeft( rCenter.X() - ( aSize.Width() >> 1 ) );
+ aRect.SetTop( rCenter.Y() - ( aSize.Height() >> 1 ) );
+
+ aRect.SetSize( aSize );
+ rBoundRect = aRect;
+ }
+}
+
+void Gradient::MakeGrayscale()
+{
+ Color aStartCol(GetStartColor());
+ Color aEndCol(GetEndColor());
+ sal_uInt8 cStartLum = aStartCol.GetLuminance();
+ sal_uInt8 cEndLum = aEndCol.GetLuminance();
+
+ aStartCol = Color(cStartLum, cStartLum, cStartLum);
+ aEndCol = Color(cEndLum, cEndLum, cEndLum);
+
+ SetStartColor(aStartCol);
+ SetEndColor(aEndCol);
+}
+
+Gradient& Gradient::operator=( const Gradient& ) = default;
+
+Gradient& Gradient::operator=( Gradient&& ) = default;
+
+bool Gradient::operator==( const Gradient& rGradient ) const
+{
+ return mpImplGradient == rGradient.mpImplGradient;
+}
+
+const sal_uInt32 GRADIENT_DEFAULT_STEPCOUNT = 0;
+
+void Gradient::AddGradientActions(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile)
+{
+ tools::Rectangle aRect(rRect);
+ aRect.Normalize();
+
+ // do nothing if the rectangle is empty
+ if (aRect.IsEmpty())
+ return;
+
+ rMetaFile.AddAction(new MetaPushAction(vcl::PushFlags::ALL));
+ rMetaFile.AddAction(new MetaISectRectClipRegionAction( aRect));
+ rMetaFile.AddAction(new MetaLineColorAction(Color(), false));
+
+ // because we draw with no border line, we have to expand gradient
+ // rect to avoid missing lines on the right and bottom edge
+ aRect.AdjustLeft( -1 );
+ aRect.AdjustTop( -1 );
+ aRect.AdjustRight( 1 );
+ aRect.AdjustBottom( 1 );
+
+ // calculate step count if necessary
+ if (!GetSteps())
+ SetSteps(GRADIENT_DEFAULT_STEPCOUNT);
+
+ if (GetStyle() == css::awt::GradientStyle_LINEAR || GetStyle() == css::awt::GradientStyle_AXIAL)
+ DrawLinearGradientToMetafile(aRect, rMetaFile);
+ else
+ DrawComplexGradientToMetafile(aRect, rMetaFile);
+
+ rMetaFile.AddAction(new MetaPopAction());
+}
+
+tools::Long Gradient::GetMetafileSteps(tools::Rectangle const& rRect) const
+{
+ // calculate step count
+ tools::Long nStepCount = GetSteps();
+
+ if (nStepCount)
+ return nStepCount;
+
+ if (GetStyle() == css::awt::GradientStyle_LINEAR || GetStyle() == css::awt::GradientStyle_AXIAL)
+ return rRect.GetHeight();
+ else
+ return std::min(rRect.GetWidth(), rRect.GetHeight());
+}
+
+
+static sal_uInt8 GetGradientColorValue(tools::Long nValue)
+{
+ if ( nValue < 0 )
+ return 0;
+ else if ( nValue > 0xFF )
+ return 0xFF;
+ else
+ return static_cast<sal_uInt8>(nValue);
+}
+
+void Gradient::DrawLinearGradientToMetafile(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile) const
+{
+ // get BoundRect of rotated rectangle
+ tools::Rectangle aRect;
+ Point aCenter;
+ Degree10 nAngle = GetAngle() % 3600_deg10;
+
+ GetBoundRect(rRect, aRect, aCenter);
+
+ bool bLinear = (GetStyle() == css::awt::GradientStyle_LINEAR);
+ double fBorder = GetBorder() * aRect.GetHeight() / 100.0;
+ if ( !bLinear )
+ {
+ fBorder /= 2.0;
+ }
+ tools::Rectangle aMirrorRect = aRect; // used in style axial
+ aMirrorRect.SetTop( ( aRect.Top() + aRect.Bottom() ) / 2 );
+ if ( !bLinear )
+ {
+ aRect.SetBottom( aMirrorRect.Top() );
+ }
+
+ // colour-intensities of start- and finish; change if needed
+ tools::Long nFactor;
+ Color aStartCol = GetStartColor();
+ Color aEndCol = GetEndColor();
+ tools::Long nStartRed = aStartCol.GetRed();
+ tools::Long nStartGreen = aStartCol.GetGreen();
+ tools::Long nStartBlue = aStartCol.GetBlue();
+ tools::Long nEndRed = aEndCol.GetRed();
+ tools::Long nEndGreen = aEndCol.GetGreen();
+ tools::Long nEndBlue = aEndCol.GetBlue();
+ nFactor = GetStartIntensity();
+ nStartRed = (nStartRed * nFactor) / 100;
+ nStartGreen = (nStartGreen * nFactor) / 100;
+ nStartBlue = (nStartBlue * nFactor) / 100;
+ nFactor = GetEndIntensity();
+ nEndRed = (nEndRed * nFactor) / 100;
+ nEndGreen = (nEndGreen * nFactor) / 100;
+ nEndBlue = (nEndBlue * nFactor) / 100;
+
+ // gradient style axial has exchanged start and end colors
+ if ( !bLinear)
+ {
+ std::swap( nStartRed, nEndRed );
+ std::swap( nStartGreen, nEndGreen );
+ std::swap( nStartBlue, nEndBlue );
+ }
+
+ sal_uInt8 nRed;
+ sal_uInt8 nGreen;
+ sal_uInt8 nBlue;
+
+ // Create border
+ tools::Rectangle aBorderRect = aRect;
+ tools::Polygon aPoly( 4 );
+ if (fBorder > 0.0)
+ {
+ nRed = static_cast<sal_uInt8>(nStartRed);
+ nGreen = static_cast<sal_uInt8>(nStartGreen);
+ nBlue = static_cast<sal_uInt8>(nStartBlue);
+
+ rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ aBorderRect.SetBottom( static_cast<tools::Long>( aBorderRect.Top() + fBorder ) );
+ aRect.SetTop( aBorderRect.Bottom() );
+ aPoly[0] = aBorderRect.TopLeft();
+ aPoly[1] = aBorderRect.TopRight();
+ aPoly[2] = aBorderRect.BottomRight();
+ aPoly[3] = aBorderRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ rMetaFile.AddAction( new MetaPolygonAction( aPoly ) );
+
+ if ( !bLinear)
+ {
+ aBorderRect = aMirrorRect;
+ aBorderRect.SetTop( static_cast<tools::Long>( aBorderRect.Bottom() - fBorder ) );
+ aMirrorRect.SetBottom( aBorderRect.Top() );
+ aPoly[0] = aBorderRect.TopLeft();
+ aPoly[1] = aBorderRect.TopRight();
+ aPoly[2] = aBorderRect.BottomRight();
+ aPoly[3] = aBorderRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ rMetaFile.AddAction( new MetaPolygonAction( aPoly ) );
+ }
+ }
+
+ tools::Long nStepCount = GetMetafileSteps(aRect);
+
+ // minimal three steps and maximal as max color steps
+ tools::Long nAbsRedSteps = std::abs( nEndRed - nStartRed );
+ tools::Long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen );
+ tools::Long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue );
+ tools::Long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps );
+ nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps );
+ tools::Long nSteps = std::min( nStepCount, nMaxColorSteps );
+ if ( nSteps < 3)
+ {
+ nSteps = 3;
+ }
+
+ double fScanInc = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps);
+ double fGradientLine = static_cast<double>(aRect.Top());
+ double fMirrorGradientLine = static_cast<double>(aMirrorRect.Bottom());
+
+ const double fStepsMinus1 = static_cast<double>(nSteps) - 1.0;
+ if ( !bLinear)
+ {
+ nSteps -= 1; // draw middle polygons as one polygon after loop to avoid gap
+ }
+ for ( tools::Long i = 0; i < nSteps; i++ )
+ {
+ // linear interpolation of color
+ double fAlpha = static_cast<double>(i) / fStepsMinus1;
+ double fTempColor = static_cast<double>(nStartRed) * (1.0-fAlpha) + static_cast<double>(nEndRed) * fAlpha;
+ nRed = GetGradientColorValue(static_cast<tools::Long>(fTempColor));
+ fTempColor = static_cast<double>(nStartGreen) * (1.0-fAlpha) + static_cast<double>(nEndGreen) * fAlpha;
+ nGreen = GetGradientColorValue(static_cast<tools::Long>(fTempColor));
+ fTempColor = static_cast<double>(nStartBlue) * (1.0-fAlpha) + static_cast<double>(nEndBlue) * fAlpha;
+ nBlue = GetGradientColorValue(static_cast<tools::Long>(fTempColor));
+
+ rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ // Polygon for this color step
+ aRect.SetTop( static_cast<tools::Long>( fGradientLine + static_cast<double>(i) * fScanInc ) );
+ aRect.SetBottom( static_cast<tools::Long>( fGradientLine + ( static_cast<double>(i) + 1.0 ) * fScanInc ) );
+ aPoly[0] = aRect.TopLeft();
+ aPoly[1] = aRect.TopRight();
+ aPoly[2] = aRect.BottomRight();
+ aPoly[3] = aRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ rMetaFile.AddAction( new MetaPolygonAction( aPoly ) );
+
+ if ( !bLinear )
+ {
+ aMirrorRect.SetBottom( static_cast<tools::Long>( fMirrorGradientLine - static_cast<double>(i) * fScanInc ) );
+ aMirrorRect.SetTop( static_cast<tools::Long>( fMirrorGradientLine - (static_cast<double>(i) + 1.0)* fScanInc ) );
+ aPoly[0] = aMirrorRect.TopLeft();
+ aPoly[1] = aMirrorRect.TopRight();
+ aPoly[2] = aMirrorRect.BottomRight();
+ aPoly[3] = aMirrorRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ rMetaFile.AddAction( new MetaPolygonAction( aPoly ) );
+ }
+ }
+ if ( bLinear)
+ return;
+
+ // draw middle polygon with end color
+ nRed = GetGradientColorValue(nEndRed);
+ nGreen = GetGradientColorValue(nEndGreen);
+ nBlue = GetGradientColorValue(nEndBlue);
+
+ rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ aRect.SetTop( static_cast<tools::Long>( fGradientLine + static_cast<double>(nSteps) * fScanInc ) );
+ aRect.SetBottom( static_cast<tools::Long>( fMirrorGradientLine - static_cast<double>(nSteps) * fScanInc ) );
+ aPoly[0] = aRect.TopLeft();
+ aPoly[1] = aRect.TopRight();
+ aPoly[2] = aRect.BottomRight();
+ aPoly[3] = aRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ rMetaFile.AddAction( new MetaPolygonAction( std::move(aPoly) ) );
+
+}
+
+void Gradient::DrawComplexGradientToMetafile(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile) const
+{
+ // Determine if we output via Polygon or PolyPolygon
+ // For all rasteroperations other than Overpaint always use PolyPolygon,
+ // as we will get wrong results if we output multiple times on top of each other.
+ // Also for printers always use PolyPolygon, as not all printers
+ // can print polygons on top of each other.
+
+ tools::Rectangle aRect;
+ Point aCenter;
+ GetBoundRect(rRect, aRect, aCenter);
+
+ std::optional<tools::PolyPolygon> xPolyPoly;
+ xPolyPoly = tools::PolyPolygon( 2 );
+
+ // last parameter - true if complex gradient, false if linear
+ tools::Long nStepCount = GetMetafileSteps(rRect);
+
+ // at least three steps and at most the number of colour differences
+ tools::Long nSteps = std::max(nStepCount, tools::Long(2));
+
+ Color aStartCol(GetStartColor());
+ Color aEndCol(GetEndColor());
+
+ tools::Long nStartRed = (static_cast<tools::Long>(aStartCol.GetRed()) * GetStartIntensity()) / 100;
+ tools::Long nStartGreen = (static_cast<tools::Long>(aStartCol.GetGreen()) * GetStartIntensity()) / 100;
+ tools::Long nStartBlue = (static_cast<tools::Long>(aStartCol.GetBlue()) * GetStartIntensity()) / 100;
+
+ tools::Long nEndRed = (static_cast<tools::Long>(aEndCol.GetRed()) * GetEndIntensity()) / 100;
+ tools::Long nEndGreen = (static_cast<tools::Long>(aEndCol.GetGreen()) * GetEndIntensity()) / 100;
+ tools::Long nEndBlue = (static_cast<tools::Long>(aEndCol.GetBlue()) * GetEndIntensity()) / 100;
+
+ tools::Long nRedSteps = nEndRed - nStartRed;
+ tools::Long nGreenSteps = nEndGreen - nStartGreen;
+ tools::Long nBlueSteps = nEndBlue - nStartBlue;
+
+ tools::Long nCalcSteps = std::abs(nRedSteps);
+ tools::Long nTempSteps = std::abs(nGreenSteps);
+
+ if (nTempSteps > nCalcSteps)
+ nCalcSteps = nTempSteps;
+
+ nTempSteps = std::abs( nBlueSteps );
+
+ if (nTempSteps > nCalcSteps)
+ nCalcSteps = nTempSteps;
+
+ if (nCalcSteps < nSteps)
+ nSteps = nCalcSteps;
+
+ if ( !nSteps )
+ nSteps = 1;
+
+ // determine output limits and stepsizes for all directions
+ tools::Polygon aPoly;
+ double fScanLeft = aRect.Left();
+ double fScanTop = aRect.Top();
+ double fScanRight = aRect.Right();
+ double fScanBottom = aRect.Bottom();
+ double fScanIncX = static_cast<double>(aRect.GetWidth()) / static_cast<double>(nSteps) * 0.5;
+ double fScanIncY = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps) * 0.5;
+
+ // all gradients are rendered as nested rectangles which shrink
+ // equally in each dimension - except for 'square' gradients
+ // which shrink to a central vertex but are not per-se square.
+ if (GetStyle() != css::awt::GradientStyle_SQUARE)
+ {
+ fScanIncY = std::min( fScanIncY, fScanIncX );
+ fScanIncX = fScanIncY;
+ }
+ sal_uInt8 nRed = static_cast<sal_uInt8>(nStartRed), nGreen = static_cast<sal_uInt8>(nStartGreen), nBlue = static_cast<sal_uInt8>(nStartBlue);
+ bool bPaintLastPolygon( false ); // #107349# Paint last polygon only if loop has generated any output
+
+ rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ aPoly = tools::Polygon(rRect);
+ xPolyPoly->Insert( aPoly );
+ xPolyPoly->Insert( aPoly );
+
+ // loop to output Polygon/PolyPolygon sequentially
+ for( tools::Long i = 1; i < nSteps; i++ )
+ {
+ // calculate new Polygon
+ fScanLeft += fScanIncX;
+ aRect.SetLeft( static_cast<tools::Long>( fScanLeft ) );
+ fScanTop += fScanIncY;
+ aRect.SetTop( static_cast<tools::Long>( fScanTop ) );
+ fScanRight -= fScanIncX;
+ aRect.SetRight( static_cast<tools::Long>( fScanRight ) );
+ fScanBottom -= fScanIncY;
+ aRect.SetBottom( static_cast<tools::Long>( fScanBottom ) );
+
+ if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) )
+ break;
+
+ if (GetStyle() == css::awt::GradientStyle_RADIAL || GetStyle() == css::awt::GradientStyle_ELLIPTICAL)
+ aPoly = tools::Polygon( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
+ else
+ aPoly = tools::Polygon( aRect );
+
+ aPoly.Rotate(aCenter, GetAngle() % 3600_deg10);
+
+ // adapt colour accordingly
+ const tools::Long nStepIndex = ( xPolyPoly ? i : ( i + 1 ) );
+ nRed = GetGradientColorValue( nStartRed + ( ( nRedSteps * nStepIndex ) / nSteps ) );
+ nGreen = GetGradientColorValue( nStartGreen + ( ( nGreenSteps * nStepIndex ) / nSteps ) );
+ nBlue = GetGradientColorValue( nStartBlue + ( ( nBlueSteps * nStepIndex ) / nSteps ) );
+
+ bPaintLastPolygon = true; // #107349# Paint last polygon only if loop has generated any output
+
+ xPolyPoly->Replace( xPolyPoly->GetObject( 1 ), 0 );
+ xPolyPoly->Replace( aPoly, 1 );
+
+ rMetaFile.AddAction( new MetaPolyPolygonAction( *xPolyPoly ) );
+
+ // #107349# Set fill color _after_ geometry painting:
+ // xPolyPoly's geometry is the band from last iteration's
+ // aPoly to current iteration's aPoly. The window outdev
+ // path (see else below), on the other hand, paints the
+ // full aPoly. Thus, here, we're painting the band before
+ // the one painted in the window outdev path below. To get
+ // matching colors, have to delay color setting here.
+ rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+ }
+
+ const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 );
+
+ if( rPoly.GetBoundRect().IsEmpty() )
+ return;
+
+ // #107349# Paint last polygon with end color only if loop
+ // has generated output. Otherwise, the current
+ // (i.e. start) color is taken, to generate _any_ output.
+ if( bPaintLastPolygon )
+ {
+ nRed = GetGradientColorValue( nEndRed );
+ nGreen = GetGradientColorValue( nEndGreen );
+ nBlue = GetGradientColorValue( nEndBlue );
+ }
+
+ rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+ rMetaFile.AddAction( new MetaPolygonAction( rPoly ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/graph.cxx b/vcl/source/gdi/graph.cxx
new file mode 100644
index 0000000000..4c0efa56be
--- /dev/null
+++ b/vcl/source/gdi/graph.cxx
@@ -0,0 +1,560 @@
+/* -*- 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 <tools/fract.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/image.hxx>
+#include <impgraph.hxx>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <comphelper/servicehelper.hxx>
+#include <graphic/UnoGraphic.hxx>
+#include <vcl/GraphicExternalLink.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+
+void ImplDrawDefault(OutputDevice& rOutDev, const OUString* pText,
+ vcl::Font* pFont, const BitmapEx* pBitmapEx,
+ const Point& rDestPt, const Size& rDestSize)
+{
+ sal_uInt16 nPixel = static_cast<sal_uInt16>(rOutDev.PixelToLogic( Size( 1, 1 ) ).Width());
+ sal_uInt16 nPixelWidth = nPixel;
+ Point aPoint( rDestPt.X() + nPixelWidth, rDestPt.Y() + nPixelWidth );
+ Size aSize( rDestSize.Width() - ( nPixelWidth << 1 ), rDestSize.Height() - ( nPixelWidth << 1 ) );
+ bool bFilled = ( pBitmapEx != nullptr || pFont != nullptr );
+ tools::Rectangle aBorderRect( aPoint, aSize );
+
+ rOutDev.Push();
+
+ rOutDev.SetFillColor();
+
+ // On the printer a black rectangle and on the screen one with 3D effect
+ rOutDev.DrawBorder(aBorderRect);
+
+ aPoint.AdjustX(nPixelWidth + 2*nPixel );
+ aPoint.AdjustY(nPixelWidth + 2*nPixel );
+ aSize.AdjustWidth( -(2*nPixelWidth + 4*nPixel) );
+ aSize.AdjustHeight( -(2*nPixelWidth + 4*nPixel) );
+
+ if( !aSize.IsEmpty() && pBitmapEx && !pBitmapEx->IsEmpty() )
+ {
+ Size aBitmapSize( rOutDev.PixelToLogic( pBitmapEx->GetSizePixel() ) );
+
+ if( aSize.Height() > aBitmapSize.Height() && aSize.Width() > aBitmapSize.Width() )
+ {
+ rOutDev.DrawBitmapEx( aPoint, *pBitmapEx );
+ aPoint.AdjustX(aBitmapSize.Width() + 2*nPixel );
+ aSize.AdjustWidth( -(aBitmapSize.Width() + 2*nPixel) );
+ }
+ }
+
+ if ( !aSize.IsEmpty() && pFont && pText && pText->getLength() && rOutDev.IsOutputEnabled() )
+ {
+ MapMode aMapMode( MapUnit::MapPoint );
+ Size aSz = rOutDev.LogicToLogic( Size( 0, 12 ), &aMapMode, nullptr );
+ tools::Long nThreshold = aSz.Height() / 2;
+ tools::Long nStep = nThreshold / 3;
+
+ if ( !nStep )
+ nStep = aSz.Height() - nThreshold;
+
+ for(;; aSz.AdjustHeight( -nStep ) )
+ {
+ pFont->SetFontSize( aSz );
+ rOutDev.SetFont( *pFont );
+
+ tools::Long nTextHeight = rOutDev.GetTextHeight();
+ tools::Long nTextWidth = rOutDev.GetTextWidth( *pText );
+ if ( nTextHeight )
+ {
+ // The approximation does not respect imprecisions caused
+ // by word wraps
+ tools::Long nLines = aSize.Height() / nTextHeight;
+ tools::Long nWidth = aSize.Width() * nLines; // Approximation!!!
+
+ if ( nTextWidth <= nWidth || aSz.Height() <= nThreshold )
+ {
+ sal_Int32 nStart = 0;
+ sal_Int32 nLen = 0;
+
+ while( nStart < pText->getLength() && (*pText)[nStart] == ' ' )
+ nStart++;
+ while( nStart+nLen < pText->getLength() && (*pText)[nStart+nLen] != ' ' )
+ nLen++;
+ while( nStart < pText->getLength() && nLines-- )
+ {
+ sal_Int32 nNext = nLen;
+ do
+ {
+ while ( nStart+nNext < pText->getLength() && (*pText)[nStart+nNext] == ' ' )
+ nNext++;
+ while ( nStart+nNext < pText->getLength() && (*pText)[nStart+nNext] != ' ' )
+ nNext++;
+ nTextWidth = rOutDev.GetTextWidth( *pText, nStart, nNext );
+ if ( nTextWidth > aSize.Width() )
+ break;
+ nLen = nNext;
+ }
+ while ( nStart+nNext < pText->getLength() );
+
+ sal_Int32 n = nLen;
+ nTextWidth = rOutDev.GetTextWidth( *pText, nStart, n );
+ while( nTextWidth > aSize.Width() )
+ nTextWidth = rOutDev.GetTextWidth( *pText, nStart, --n );
+ rOutDev.DrawText( aPoint, *pText, nStart, n );
+
+ aPoint.AdjustY(nTextHeight );
+ nStart = sal::static_int_cast<sal_uInt16>(nStart + nLen);
+ nLen = nNext-nLen;
+ while( nStart < pText->getLength() && (*pText)[nStart] == ' ' )
+ {
+ nStart++;
+ nLen--;
+ }
+ }
+ break;
+ }
+ }
+ else
+ break;
+ }
+ }
+
+ // If the default graphic does not have content, we draw a red rectangle
+ if( !bFilled )
+ {
+ aBorderRect.AdjustLeft( 1 );
+ aBorderRect.AdjustTop( 1 );
+ aBorderRect.AdjustRight( -1 );
+ aBorderRect.AdjustBottom( -1 );
+
+ rOutDev.SetLineColor( COL_LIGHTRED );
+ rOutDev.DrawLine( aBorderRect.TopLeft(), aBorderRect.BottomRight() );
+ rOutDev.DrawLine( aBorderRect.TopRight(), aBorderRect.BottomLeft() );
+ }
+
+ rOutDev.Pop();
+}
+
+} // end anonymous namespace
+
+Graphic::Graphic()
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance())
+{
+}
+
+Graphic::Graphic(const Graphic& rGraphic)
+{
+ if( rGraphic.IsAnimated() )
+ mxImpGraphic = vcl::graphic::Manager::get().copy(rGraphic.mxImpGraphic);
+ else
+ mxImpGraphic = rGraphic.mxImpGraphic;
+}
+
+Graphic::Graphic(Graphic&& rGraphic) noexcept
+ : mxImpGraphic(std::move(rGraphic.mxImpGraphic))
+{
+}
+
+Graphic::Graphic(std::shared_ptr<GfxLink> const & rGfxLink, sal_Int32 nPageIndex)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rGfxLink, nPageIndex))
+{
+}
+
+Graphic::Graphic(GraphicExternalLink const & rGraphicExternalLink)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rGraphicExternalLink))
+{
+}
+
+Graphic::Graphic(const BitmapEx& rBmpEx)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rBmpEx))
+{
+}
+
+// We use XGraphic for passing toolbar images across app UNO aps
+// and we need to be able to see and preserve 'stock' images too.
+Graphic::Graphic(const Image& rImage)
+ // FIXME: should really defer the BitmapEx load.
+ : mxImpGraphic(std::make_shared<ImpGraphic>(rImage.GetBitmapEx()))
+{
+ OUString aStock = rImage.GetStock();
+ if (aStock.getLength())
+ mxImpGraphic->setOriginURL("private:graphicrepository/" + aStock);
+}
+
+Graphic::Graphic(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rVectorGraphicDataPtr))
+{
+}
+
+Graphic::Graphic(const Animation& rAnimation)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rAnimation))
+{
+}
+
+Graphic::Graphic(const GDIMetaFile& rMtf)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rMtf))
+{
+}
+
+Graphic::Graphic( const css::uno::Reference< css::graphic::XGraphic >& rxGraphic )
+{
+ const ::unographic::Graphic* pUnoGraphic = dynamic_cast<::unographic::Graphic*>(rxGraphic.get());
+ const ::Graphic* pGraphic = pUnoGraphic ? &pUnoGraphic->GetGraphic() : nullptr;
+
+ if( pGraphic )
+ {
+ if (pGraphic->IsAnimated())
+ mxImpGraphic = vcl::graphic::Manager::get().copy(pGraphic->mxImpGraphic);
+ else
+ mxImpGraphic = pGraphic->mxImpGraphic;
+ }
+ else
+ mxImpGraphic = vcl::graphic::Manager::get().newInstance();
+}
+
+void Graphic::ImplTestRefCount()
+{
+ if (mxImpGraphic.use_count() > 1)
+ {
+ mxImpGraphic = vcl::graphic::Manager::get().copy(mxImpGraphic);
+ }
+}
+
+bool Graphic::isAvailable() const
+{
+ return mxImpGraphic->isAvailable();
+}
+
+bool Graphic::makeAvailable()
+{
+ return mxImpGraphic->makeAvailable();
+}
+
+Graphic& Graphic::operator=( const Graphic& rGraphic )
+{
+ if( &rGraphic != this )
+ {
+ if( rGraphic.IsAnimated() )
+ mxImpGraphic = vcl::graphic::Manager::get().copy(rGraphic.mxImpGraphic);
+ else
+ mxImpGraphic = rGraphic.mxImpGraphic;
+ }
+
+ return *this;
+}
+
+Graphic& Graphic::operator=(Graphic&& rGraphic) noexcept
+{
+ mxImpGraphic = std::move(rGraphic.mxImpGraphic);
+ return *this;
+}
+
+bool Graphic::operator==( const Graphic& rGraphic ) const
+{
+ return (*mxImpGraphic == *rGraphic.mxImpGraphic);
+}
+
+bool Graphic::operator!=( const Graphic& rGraphic ) const
+{
+ return (*mxImpGraphic != *rGraphic.mxImpGraphic);
+}
+
+bool Graphic::IsNone() const
+{
+ return GraphicType::NONE == mxImpGraphic->getType();
+}
+
+void Graphic::Clear()
+{
+ ImplTestRefCount();
+ mxImpGraphic->clear();
+}
+
+GraphicType Graphic::GetType() const
+{
+ return mxImpGraphic->getType();
+}
+
+void Graphic::SetDefaultType()
+{
+ ImplTestRefCount();
+ mxImpGraphic->setDefaultType();
+}
+
+bool Graphic::IsSupportedGraphic() const
+{
+ return mxImpGraphic->isSupportedGraphic();
+}
+
+bool Graphic::IsTransparent() const
+{
+ return mxImpGraphic->isTransparent();
+}
+
+bool Graphic::IsAlpha() const
+{
+ return mxImpGraphic->isAlpha();
+}
+
+bool Graphic::IsAnimated() const
+{
+ return mxImpGraphic->isAnimated();
+}
+
+bool Graphic::IsEPS() const
+{
+ return mxImpGraphic->isEPS();
+}
+
+BitmapEx Graphic::GetBitmapEx(const GraphicConversionParameters& rParameters) const
+{
+ return mxImpGraphic->getBitmapEx(rParameters);
+}
+
+Animation Graphic::GetAnimation() const
+{
+ return mxImpGraphic->getAnimation();
+}
+
+const GDIMetaFile& Graphic::GetGDIMetaFile() const
+{
+ return mxImpGraphic->getGDIMetaFile();
+}
+
+const BitmapEx& Graphic::GetBitmapExRef() const
+{
+ return mxImpGraphic->getBitmapExRef();
+}
+
+uno::Reference<css::graphic::XGraphic> Graphic::GetXGraphic() const
+{
+ uno::Reference<css::graphic::XGraphic> xGraphic;
+
+ if (GetType() != GraphicType::NONE)
+ {
+ rtl::Reference<unographic::Graphic> pUnoGraphic = new unographic::Graphic;
+ pUnoGraphic->init(*this);
+ xGraphic = pUnoGraphic;
+ }
+
+ return xGraphic;
+}
+
+Size Graphic::GetPrefSize() const
+{
+ return mxImpGraphic->getPrefSize();
+}
+
+void Graphic::SetPrefSize( const Size& rPrefSize )
+{
+ ImplTestRefCount();
+ mxImpGraphic->setPrefSize( rPrefSize );
+}
+
+MapMode Graphic::GetPrefMapMode() const
+{
+ return mxImpGraphic->getPrefMapMode();
+}
+
+void Graphic::SetPrefMapMode( const MapMode& rPrefMapMode )
+{
+ ImplTestRefCount();
+ mxImpGraphic->setPrefMapMode( rPrefMapMode );
+}
+
+basegfx::B2DSize Graphic::GetPPI() const
+{
+ double nGrfDPIx;
+ double nGrfDPIy;
+
+ const MapMode aGrfMap(GetPrefMapMode());
+ const Size aGrfPixelSize(GetSizePixel());
+ const Size aGrfPrefMapModeSize(GetPrefSize());
+ if (aGrfMap.GetMapUnit() == MapUnit::MapInch)
+ {
+ nGrfDPIx = aGrfPixelSize.Width() / ( static_cast<double>(aGrfMap.GetScaleX()) * aGrfPrefMapModeSize.Width() );
+ nGrfDPIy = aGrfPixelSize.Height() / ( static_cast<double>(aGrfMap.GetScaleY()) * aGrfPrefMapModeSize.Height() );
+ }
+ else
+ {
+ const Size aGrf1000thInchSize = OutputDevice::LogicToLogic(
+ aGrfPrefMapModeSize, aGrfMap, MapMode(MapUnit::Map1000thInch));
+ nGrfDPIx = aGrf1000thInchSize.Width() == 0
+ ? 0.0 : 1000.0 * aGrfPixelSize.Width() / aGrf1000thInchSize.Width();
+ nGrfDPIy = aGrf1000thInchSize.Height() == 0
+ ? 0.0 : 1000.0 * aGrfPixelSize.Height() / aGrf1000thInchSize.Height();
+ }
+
+ return basegfx::B2DSize(nGrfDPIx, nGrfDPIy);
+}
+
+Size Graphic::GetSizePixel( const OutputDevice* pRefDevice ) const
+{
+ Size aRet;
+
+ if( GraphicType::Bitmap == mxImpGraphic->getType() )
+ aRet = mxImpGraphic->getSizePixel();
+ else
+ aRet = ( pRefDevice ? pRefDevice : Application::GetDefaultDevice() )->LogicToPixel( GetPrefSize(), GetPrefMapMode() );
+
+ return aRet;
+}
+
+sal_uLong Graphic::GetSizeBytes() const
+{
+ return mxImpGraphic->getSizeBytes();
+}
+
+void Graphic::Draw(OutputDevice& rOutDev, const Point& rDestPt) const
+{
+ mxImpGraphic->draw(rOutDev, rDestPt);
+}
+
+void Graphic::Draw(OutputDevice& rOutDev, const Point& rDestPt,
+ const Size& rDestSz) const
+{
+ if( GraphicType::Default == mxImpGraphic->getType() )
+ ImplDrawDefault(rOutDev, nullptr, nullptr, nullptr, rDestPt, rDestSz);
+ else
+ mxImpGraphic->draw(rOutDev, rDestPt, rDestSz);
+}
+
+void Graphic::DrawEx(OutputDevice& rOutDev, const OUString& rText,
+ vcl::Font& rFont, const BitmapEx& rBitmap,
+ const Point& rDestPt, const Size& rDestSz)
+{
+ ImplDrawDefault(rOutDev, &rText, &rFont, &rBitmap, rDestPt, rDestSz);
+}
+
+void Graphic::StartAnimation(OutputDevice& rOutDev, const Point& rDestPt,
+ const Size& rDestSz, tools::Long nRendererId,
+ OutputDevice* pFirstFrameOutDev)
+{
+ ImplTestRefCount();
+ mxImpGraphic->startAnimation(rOutDev, rDestPt, rDestSz, nRendererId, pFirstFrameOutDev);
+}
+
+void Graphic::StopAnimation( const OutputDevice* pOutDev, tools::Long nRendererId )
+{
+ ImplTestRefCount();
+ mxImpGraphic->stopAnimation( pOutDev, nRendererId );
+}
+
+void Graphic::SetAnimationNotifyHdl( const Link<Animation*,void>& rLink )
+{
+ mxImpGraphic->setAnimationNotifyHdl( rLink );
+}
+
+Link<Animation*,void> Graphic::GetAnimationNotifyHdl() const
+{
+ return mxImpGraphic->getAnimationNotifyHdl();
+}
+
+sal_uInt32 Graphic::GetAnimationLoopCount() const
+{
+ return mxImpGraphic->getAnimationLoopCount();
+}
+
+std::shared_ptr<GraphicReader>& Graphic::GetReaderContext()
+{
+ return mxImpGraphic->getContext();
+}
+
+void Graphic::SetReaderContext( const std::shared_ptr<GraphicReader> &pReader )
+{
+ mxImpGraphic->setContext( pReader );
+}
+
+void Graphic::SetDummyContext( bool value )
+{
+ mxImpGraphic->setDummyContext( value );
+}
+
+bool Graphic::IsDummyContext() const
+{
+ return mxImpGraphic->isDummyContext();
+}
+
+void Graphic::SetGfxLink( const std::shared_ptr<GfxLink>& rGfxLink )
+{
+ ImplTestRefCount();
+ mxImpGraphic->setGfxLink(rGfxLink);
+}
+
+const std::shared_ptr<GfxLink> & Graphic::GetSharedGfxLink() const
+{
+ return mxImpGraphic->getSharedGfxLink();
+}
+
+GfxLink Graphic::GetGfxLink() const
+{
+ return mxImpGraphic->getGfxLink();
+}
+
+bool Graphic::IsGfxLink() const
+{
+ return mxImpGraphic->isGfxLink();
+}
+
+BitmapChecksum Graphic::GetChecksum() const
+{
+ return mxImpGraphic->getChecksum();
+}
+
+const std::shared_ptr<VectorGraphicData>& Graphic::getVectorGraphicData() const
+{
+ return mxImpGraphic->getVectorGraphicData();
+}
+
+sal_Int32 Graphic::getPageNumber() const
+{
+ return mxImpGraphic->getPageNumber();
+}
+
+OUString Graphic::getOriginURL() const
+{
+ if (mxImpGraphic)
+ {
+ return mxImpGraphic->getOriginURL();
+ }
+ return OUString();
+}
+
+void Graphic::setOriginURL(OUString const & rOriginURL)
+{
+ if (mxImpGraphic)
+ {
+ mxImpGraphic->setOriginURL(rOriginURL);
+ }
+}
+
+OString Graphic::getUniqueID() const
+{
+ OString aUniqueString;
+ if (mxImpGraphic)
+ aUniqueString = mxImpGraphic->getUniqueID();
+ return aUniqueString;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/graphictools.cxx b/vcl/source/gdi/graphictools.cxx
new file mode 100644
index 0000000000..8308da0415
--- /dev/null
+++ b/vcl/source/gdi/graphictools.cxx
@@ -0,0 +1,289 @@
+/* -*- 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 <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <utility>
+#include <vcl/TypeSerializer.hxx>
+#include <vcl/graphictools.hxx>
+
+SvtGraphicFill::Transform::Transform()
+{
+ matrix[0] = 1.0; matrix[1] = 0.0; matrix[2] = 0.0;
+ matrix[3] = 0.0; matrix[4] = 1.0; matrix[5] = 0.0;
+}
+
+SvtGraphicStroke::SvtGraphicStroke() :
+ mfTransparency(),
+ mfStrokeWidth(),
+ maCapType(),
+ maJoinType(),
+ mfMiterLimit( 3.0 )
+{
+}
+
+SvtGraphicStroke::SvtGraphicStroke( tools::Polygon aPath,
+ tools::PolyPolygon aStartArrow,
+ tools::PolyPolygon aEndArrow,
+ double fTransparency,
+ double fStrokeWidth,
+ CapType aCap,
+ JoinType aJoin,
+ double fMiterLimit,
+ DashArray&& rDashArray ) :
+ maPath(std::move( aPath )),
+ maStartArrow(std::move( aStartArrow )),
+ maEndArrow(std::move( aEndArrow )),
+ mfTransparency( fTransparency ),
+ mfStrokeWidth( fStrokeWidth ),
+ maCapType( aCap ),
+ maJoinType( aJoin ),
+ mfMiterLimit( fMiterLimit ),
+ maDashArray( std::move(rDashArray) )
+{
+}
+
+void SvtGraphicStroke::getPath( tools::Polygon& rPath ) const
+{
+ rPath = maPath;
+}
+
+void SvtGraphicStroke::getStartArrow( tools::PolyPolygon& rPath ) const
+{
+ rPath = maStartArrow;
+}
+
+void SvtGraphicStroke::getEndArrow( tools::PolyPolygon& rPath ) const
+{
+ rPath = maEndArrow;
+}
+
+
+void SvtGraphicStroke::getDashArray( DashArray& rDashArray ) const
+{
+ rDashArray = maDashArray;
+}
+
+void SvtGraphicStroke::setPath( const tools::Polygon& rPoly )
+{
+ maPath = rPoly;
+}
+
+void SvtGraphicStroke::setStartArrow( const tools::PolyPolygon& rPoly )
+{
+ maStartArrow = rPoly;
+}
+
+void SvtGraphicStroke::setEndArrow( const tools::PolyPolygon& rPoly )
+{
+ maEndArrow = rPoly;
+}
+
+void SvtGraphicStroke::scale( double fXScale, double fYScale )
+{
+ // Clearly scaling stroke-width for fat lines is rather a problem
+ maPath.Scale( fXScale, fYScale );
+
+ double fScale = sqrt (fabs (fXScale * fYScale) ); // clearly not ideal.
+ mfStrokeWidth *= fScale;
+ mfMiterLimit *= fScale;
+
+ maStartArrow.Scale( fXScale, fYScale );
+ maEndArrow.Scale( fXScale, fYScale );
+}
+
+SvStream& WriteSvtGraphicStroke( SvStream& rOStm, const SvtGraphicStroke& rClass )
+{
+ VersionCompatWrite aCompat( rOStm, 1 );
+
+ rClass.maPath.Write( rOStm );
+ rClass.maStartArrow.Write( rOStm );
+ rClass.maEndArrow.Write( rOStm );
+ rOStm.WriteDouble( rClass.mfTransparency );
+ rOStm.WriteDouble( rClass.mfStrokeWidth );
+ sal_uInt16 nTmp = sal::static_int_cast<sal_uInt16>( rClass.maCapType );
+ rOStm.WriteUInt16( nTmp );
+ nTmp = sal::static_int_cast<sal_uInt16>( rClass.maJoinType );
+ rOStm.WriteUInt16( nTmp );
+ rOStm.WriteDouble( rClass.mfMiterLimit );
+
+ rOStm.WriteUInt32( rClass.maDashArray.size() );
+ size_t i;
+ for(i=0; i<rClass.maDashArray.size(); ++i)
+ rOStm.WriteDouble( rClass.maDashArray[i] );
+
+ return rOStm;
+}
+
+SvStream& ReadSvtGraphicStroke( SvStream& rIStm, SvtGraphicStroke& rClass )
+{
+ VersionCompatRead aCompat( rIStm );
+
+ rClass.maPath.Read( rIStm );
+ rClass.maStartArrow.Read( rIStm );
+ rClass.maEndArrow.Read( rIStm );
+ rIStm.ReadDouble( rClass.mfTransparency );
+ rIStm.ReadDouble( rClass.mfStrokeWidth );
+ sal_uInt16 nTmp;
+ rIStm.ReadUInt16( nTmp );
+ rClass.maCapType = SvtGraphicStroke::CapType(nTmp);
+ rIStm.ReadUInt16( nTmp );
+ rClass.maJoinType = SvtGraphicStroke::JoinType(nTmp);
+ rIStm.ReadDouble( rClass.mfMiterLimit );
+
+ sal_uInt32 nSize;
+ rIStm.ReadUInt32( nSize );
+ rClass.maDashArray.resize(nSize);
+ size_t i;
+ for(i=0; i<rClass.maDashArray.size(); ++i)
+ rIStm.ReadDouble( rClass.maDashArray[i] );
+
+ return rIStm;
+}
+
+SvtGraphicFill::SvtGraphicFill() :
+ maFillColor( COL_BLACK ),
+ mfTransparency(),
+ maFillRule(),
+ maFillType(),
+ mbTiling( false ),
+ maHatchType(),
+ maHatchColor( COL_BLACK ),
+ maGradientType(),
+ maGradient1stColor( COL_BLACK ),
+ maGradient2ndColor( COL_BLACK ),
+ maGradientStepCount( gradientStepsInfinite )
+{
+}
+
+SvtGraphicFill::SvtGraphicFill( tools::PolyPolygon aPath,
+ Color aFillColor,
+ double fTransparency,
+ FillRule aFillRule,
+ FillType aFillType,
+ const Transform& aFillTransform,
+ bool bTiling,
+ HatchType aHatchType,
+ Color aHatchColor,
+ GradientType aGradientType,
+ Color aGradient1stColor,
+ Color aGradient2ndColor,
+ sal_Int32 aGradientStepCount,
+ Graphic aFillGraphic ) :
+ maPath(std::move( aPath )),
+ maFillColor( aFillColor ),
+ mfTransparency( fTransparency ),
+ maFillRule( aFillRule ),
+ maFillType( aFillType ),
+ maFillTransform( aFillTransform ),
+ mbTiling( bTiling ),
+ maHatchType( aHatchType ),
+ maHatchColor( aHatchColor ),
+ maGradientType( aGradientType ),
+ maGradient1stColor( aGradient1stColor ),
+ maGradient2ndColor( aGradient2ndColor ),
+ maGradientStepCount( aGradientStepCount ),
+ maFillGraphic(std::move( aFillGraphic ))
+{
+}
+
+void SvtGraphicFill::getPath( tools::PolyPolygon& rPath ) const
+{
+ rPath = maPath;
+}
+
+
+void SvtGraphicFill::getTransform( Transform& rTrans ) const
+{
+ rTrans = maFillTransform;
+}
+
+
+void SvtGraphicFill::getGraphic( Graphic& rGraphic ) const
+{
+ rGraphic = maFillGraphic;
+}
+
+void SvtGraphicFill::setPath( const tools::PolyPolygon& rPath )
+{
+ maPath = rPath;
+}
+
+SvStream& WriteSvtGraphicFill( SvStream& rOStm, const SvtGraphicFill& rClass )
+{
+ VersionCompatWrite aCompat( rOStm, 1 );
+
+ rClass.maPath.Write( rOStm );
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeColor(rClass.maFillColor);
+ rOStm.WriteDouble( rClass.mfTransparency );
+ sal_uInt16 nTmp = sal::static_int_cast<sal_uInt16>( rClass.maFillRule );
+ rOStm.WriteUInt16( nTmp );
+ nTmp = sal::static_int_cast<sal_uInt16>( rClass.maFillType );
+ rOStm.WriteUInt16( nTmp );
+ int i;
+ for(i=0; i<SvtGraphicFill::Transform::MatrixSize; ++i)
+ rOStm.WriteDouble( rClass.maFillTransform.matrix[i] );
+ nTmp = sal_uInt16(rClass.mbTiling);
+ rOStm.WriteUInt16( nTmp );
+ nTmp = sal::static_int_cast<sal_uInt16>( rClass.maHatchType );
+ rOStm.WriteUInt16( nTmp );
+ aSerializer.writeColor(rClass.maHatchColor);
+ nTmp = sal::static_int_cast<sal_uInt16>( rClass.maGradientType );
+ rOStm.WriteUInt16( nTmp );
+ aSerializer.writeColor(rClass.maGradient1stColor);
+ aSerializer.writeColor(rClass.maGradient2ndColor);
+ rOStm.WriteInt32( rClass.maGradientStepCount );
+ aSerializer.writeGraphic(rClass.maFillGraphic);
+
+ return rOStm;
+}
+
+SvStream& ReadSvtGraphicFill( SvStream& rIStm, SvtGraphicFill& rClass )
+{
+ VersionCompatRead aCompat( rIStm );
+
+ rClass.maPath.Read( rIStm );
+
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readColor(rClass.maFillColor);
+ rIStm.ReadDouble( rClass.mfTransparency );
+ sal_uInt16 nTmp;
+ rIStm.ReadUInt16( nTmp );
+ rClass.maFillRule = SvtGraphicFill::FillRule( nTmp );
+ rIStm.ReadUInt16( nTmp );
+ rClass.maFillType = SvtGraphicFill::FillType( nTmp );
+ for (int i = 0; i < SvtGraphicFill::Transform::MatrixSize; ++i)
+ rIStm.ReadDouble( rClass.maFillTransform.matrix[i] );
+ rIStm.ReadUInt16( nTmp );
+ rClass.mbTiling = nTmp;
+ rIStm.ReadUInt16( nTmp );
+ rClass.maHatchType = SvtGraphicFill::HatchType( nTmp );
+ aSerializer.readColor(rClass.maHatchColor);
+ rIStm.ReadUInt16( nTmp );
+ rClass.maGradientType = SvtGraphicFill::GradientType( nTmp );
+ aSerializer.readColor(rClass.maGradient1stColor);
+ aSerializer.readColor(rClass.maGradient2ndColor);
+ rIStm.ReadInt32( rClass.maGradientStepCount );
+ aSerializer.readGraphic(rClass.maFillGraphic);
+
+ return rIStm;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/hatch.cxx b/vcl/source/gdi/hatch.cxx
new file mode 100644
index 0000000000..e097f2f36d
--- /dev/null
+++ b/vcl/source/gdi/hatch.cxx
@@ -0,0 +1,114 @@
+/* -*- 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 <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/GenericTypeSerializer.hxx>
+#include <vcl/hatch.hxx>
+
+ImplHatch::ImplHatch() :
+ maColor ( COL_BLACK ),
+ meStyle ( HatchStyle::Single ),
+ mnDistance ( 1 ),
+ mnAngle ( 0 )
+{
+}
+
+bool ImplHatch::operator==( const ImplHatch& rImplHatch ) const
+{
+ return maColor == rImplHatch.maColor &&
+ meStyle == rImplHatch.meStyle &&
+ mnDistance == rImplHatch.mnDistance &&
+ mnAngle == rImplHatch.mnAngle;
+}
+
+Hatch::Hatch() = default;
+
+Hatch::Hatch( const Hatch& ) = default;
+
+Hatch::Hatch( HatchStyle eStyle, const Color& rColor,
+ tools::Long nDistance, Degree10 nAngle10 )
+{
+ mpImplHatch->maColor = rColor;
+ mpImplHatch->meStyle = eStyle;
+ mpImplHatch->mnDistance = nDistance;
+ mpImplHatch->mnAngle = nAngle10;
+}
+
+Hatch::~Hatch() = default;
+
+Hatch& Hatch::operator=( const Hatch& ) = default;
+
+bool Hatch::operator==( const Hatch& rHatch ) const
+{
+ return mpImplHatch == rHatch.mpImplHatch;
+}
+
+
+void Hatch::SetColor( const Color& rColor )
+{
+ mpImplHatch->maColor = rColor;
+}
+
+void Hatch::SetDistance( tools::Long nDistance )
+{
+ mpImplHatch->mnDistance = nDistance;
+}
+
+void Hatch::SetAngle( Degree10 nAngle10 )
+{
+ mpImplHatch->mnAngle = nAngle10;
+}
+
+SvStream& ReadHatch( SvStream& rIStm, Hatch& rHatch )
+{
+ VersionCompatRead aCompat(rIStm);
+
+ sal_uInt16 nTmpStyle(0);
+ rIStm.ReadUInt16(nTmpStyle);
+ rHatch.mpImplHatch->meStyle = static_cast<HatchStyle>(nTmpStyle);
+
+ tools::GenericTypeSerializer aSerializer(rIStm);
+ aSerializer.readColor(rHatch.mpImplHatch->maColor);
+
+ sal_Int32 nTmp32(0);
+ rIStm.ReadInt32(nTmp32);
+ rHatch.mpImplHatch->mnDistance = nTmp32;
+
+ sal_Int16 nTmpAngle(0);
+ rIStm.ReadInt16(nTmpAngle);
+ rHatch.mpImplHatch->mnAngle = Degree10(nTmpAngle);
+
+ return rIStm;
+}
+
+SvStream& WriteHatch( SvStream& rOStm, const Hatch& rHatch )
+{
+ VersionCompatWrite aCompat(rOStm, 1);
+
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rHatch.mpImplHatch->meStyle) );
+
+ tools::GenericTypeSerializer aSerializer(rOStm);
+ aSerializer.writeColor(rHatch.mpImplHatch->maColor);
+ rOStm.WriteInt32( rHatch.mpImplHatch->mnDistance ).WriteInt16( rHatch.mpImplHatch->mnAngle.get() );
+
+ return rOStm;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/impglyphitem.cxx b/vcl/source/gdi/impglyphitem.cxx
new file mode 100644
index 0000000000..ca8016a192
--- /dev/null
+++ b/vcl/source/gdi/impglyphitem.cxx
@@ -0,0 +1,555 @@
+/* -*- 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 <impglyphitem.hxx>
+#include <utility>
+#include <vcl/glyphitemcache.hxx>
+#include <vcl/vcllayout.hxx>
+#include <vcl/lazydelete.hxx>
+#include <tools/stream.hxx>
+#include <unotools/configmgr.hxx>
+#include <TextLayoutCache.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <unicode/ubidi.h>
+#include <unicode/uchar.h>
+
+// These need being explicit because of SalLayoutGlyphsImpl being private in vcl.
+SalLayoutGlyphs::SalLayoutGlyphs() {}
+
+SalLayoutGlyphs::~SalLayoutGlyphs() {}
+
+SalLayoutGlyphs::SalLayoutGlyphs(SalLayoutGlyphs&& rOther) noexcept
+{
+ std::swap(m_pImpl, rOther.m_pImpl);
+ std::swap(m_pExtraImpls, rOther.m_pExtraImpls);
+}
+
+SalLayoutGlyphs& SalLayoutGlyphs::operator=(SalLayoutGlyphs&& rOther) noexcept
+{
+ if (this != &rOther)
+ {
+ std::swap(m_pImpl, rOther.m_pImpl);
+ std::swap(m_pExtraImpls, rOther.m_pExtraImpls);
+ }
+ return *this;
+}
+
+bool SalLayoutGlyphs::IsValid() const
+{
+ if (m_pImpl == nullptr)
+ return false;
+ if (!m_pImpl->IsValid())
+ return false;
+ if (m_pExtraImpls)
+ for (std::unique_ptr<SalLayoutGlyphsImpl> const& impl : *m_pExtraImpls)
+ if (!impl->IsValid())
+ return false;
+ return true;
+}
+
+void SalLayoutGlyphs::Invalidate()
+{
+ // Invalidating is in fact simply clearing.
+ m_pImpl.reset();
+ m_pExtraImpls.reset();
+}
+
+SalLayoutGlyphsImpl* SalLayoutGlyphs::Impl(unsigned int nLevel) const
+{
+ if (nLevel == 0)
+ return m_pImpl.get();
+ if (m_pExtraImpls != nullptr && nLevel - 1 < m_pExtraImpls->size())
+ return (*m_pExtraImpls)[nLevel - 1].get();
+ return nullptr;
+}
+
+void SalLayoutGlyphs::AppendImpl(SalLayoutGlyphsImpl* pImpl)
+{
+ if (!m_pImpl)
+ m_pImpl.reset(pImpl);
+ else
+ {
+ if (!m_pExtraImpls)
+ m_pExtraImpls.reset(new std::vector<std::unique_ptr<SalLayoutGlyphsImpl>>);
+ m_pExtraImpls->emplace_back(pImpl);
+ }
+}
+
+SalLayoutGlyphsImpl* SalLayoutGlyphsImpl::clone() const { return new SalLayoutGlyphsImpl(*this); }
+
+// Clone, but only glyphs in the given range in the original text string.
+// It is possible the given range may not be cloned, in which case this returns nullptr.
+SalLayoutGlyphsImpl* SalLayoutGlyphsImpl::cloneCharRange(sal_Int32 index, sal_Int32 length) const
+{
+ std::unique_ptr<SalLayoutGlyphsImpl> copy(new SalLayoutGlyphsImpl(*GetFont()));
+ copy->SetFlags(GetFlags());
+ if (empty())
+ return copy.release();
+ bool rtl = front().IsRTLGlyph();
+ // Avoid mixing LTR/RTL or layouts that do not have it set explicitly (BiDiStrong). Otherwise
+ // the subset may not quite match what would a real layout call give (e.g. some characters with neutral
+ // direction such as space might have different LTR/RTL flag). It seems bailing out here mostly
+ // avoid relatively rare corner cases and doesn't matter for performance.
+ // This is also checked in SalLayoutGlyphsCache::GetLayoutGlyphs() below.
+ if (!(GetFlags() & SalLayoutFlags::BiDiStrong)
+ || rtl != bool(GetFlags() & SalLayoutFlags::BiDiRtl))
+ return nullptr;
+ copy->reserve(std::min<size_t>(size(), length));
+ sal_Int32 beginPos = index;
+ sal_Int32 endPos = index + length;
+ const_iterator pos;
+ if (rtl)
+ {
+ // Glyphs are in reverse order for RTL.
+ beginPos = index + length - 1;
+ endPos = index - 1;
+ // Skip glyphs that are in the string after the given index, i.e. are before the glyphs
+ // we want.
+ pos = std::partition_point(
+ begin(), end(), [beginPos](const GlyphItem& it) { return it.charPos() > beginPos; });
+ }
+ else
+ {
+ // Skip glyphs that are in the string before the given index (glyphs are sorted by charPos()).
+ pos = std::partition_point(
+ begin(), end(), [beginPos](const GlyphItem& it) { return it.charPos() < beginPos; });
+ }
+ if (pos == end())
+ return nullptr;
+ // Require a start at the exact position given, otherwise bail out.
+ if (pos->charPos() != beginPos)
+ return nullptr;
+ // For RTL make sure we're not cutting in the middle of a multi-character glyph,
+ // or in the middle of a cluster
+ // (for non-RTL charPos is always the start of a multi-character glyph).
+ if (rtl && (pos->charPos() + pos->charCount() > beginPos + 1 || pos->IsInCluster()))
+ return nullptr;
+ if (!isSafeToBreak(pos, rtl))
+ return nullptr;
+ // LinearPos needs adjusting to start at xOffset/yOffset for the first item,
+ // that's how it's computed in GenericSalLayout::LayoutText().
+ basegfx::B2DPoint zeroPoint
+ = pos->linearPos() - basegfx::B2DPoint(pos->xOffset(), pos->yOffset());
+ // Add and adjust all glyphs until the given length.
+ // The check is written as 'charPos + charCount <= endPos' rather than 'charPos < endPos'
+ // (or similarly for RTL) to make sure we include complete glyphs. If a glyph is composed
+ // from several characters, we should not cut in the middle of those characters, so this
+ // checks the glyph is entirely in the given character range. If it is not, this will end
+ // the loop and the later 'pos->charPos() != endPos' check will fail and bail out.
+ // CppunitTest_sw_layoutwriter's testCombiningCharacterCursorPosition would fail without this.
+ while (pos != end()
+ && (rtl ? pos->charPos() - pos->charCount() >= endPos
+ : pos->charPos() + pos->charCount() <= endPos))
+ {
+ if (pos->IsRTLGlyph() != rtl)
+ return nullptr; // Don't mix RTL and non-RTL runs.
+ // HACK: When running CppunitTest_sw_uiwriter3's testTdf104649 on Mac there's glyph
+ // with id 1232 that has 0 charCount, 0 origWidth and inconsistent xOffset (sometimes 0,
+ // but sometimes not). Possibly font or Harfbuzz bug? It's extremely rare, so simply bail out.
+ if (pos->charCount() == 0 && pos->origWidth() == 0)
+ return nullptr;
+ copy->push_back(*pos);
+ copy->back().setLinearPos(copy->back().linearPos() - zeroPoint);
+ ++pos;
+ }
+ if (pos != end())
+ {
+ // Fail if the next character is not at the expected past-end position. For RTL check
+ // that we're not cutting in the middle of a multi-character glyph.
+ if (rtl ? pos->charPos() + pos->charCount() != endPos + 1 : pos->charPos() != endPos)
+ return nullptr;
+ if (!isSafeToBreak(pos, rtl))
+ return nullptr;
+ }
+ return copy.release();
+}
+
+bool SalLayoutGlyphsImpl::isSafeToBreak(const_iterator pos, bool rtl) const
+{
+ if (rtl)
+ {
+ // RTL is more complicated, because HB_GLYPH_FLAG_UNSAFE_TO_BREAK talks about beginning
+ // of a cluster, which refers to the text, not glyphs. This function is called
+ // for the first glyph of the subset and the first glyph after the subset, but since
+ // the glyphs are backwards, and we need the beginning of cluster at the start of the text
+ // and beginning of the cluster after the text, we need to check glyphs before this position.
+ if (pos == begin())
+ return true;
+ --pos;
+ }
+ // Don't create a subset if it's not safe to break at the beginning or end of the sequence
+ // (https://harfbuzz.github.io/harfbuzz-hb-buffer.html#hb-glyph-flags-t).
+ if (pos->IsUnsafeToBreak() || (pos->IsInCluster() && !pos->IsClusterStart()))
+ return false;
+ return true;
+}
+
+#ifdef DBG_UTIL
+bool SalLayoutGlyphsImpl::isLayoutEquivalent(const SalLayoutGlyphsImpl* other) const
+{
+ if (!GetFont()->mxFontMetric->CompareDeviceIndependentFontAttributes(
+ *other->GetFont()->mxFontMetric))
+ return false;
+ if (GetFlags() != other->GetFlags())
+ return false;
+ if (empty() || other->empty())
+ return empty() == other->empty();
+ if (size() != other->size())
+ return false;
+ for (size_t pos = 0; pos < size(); ++pos)
+ {
+ if (!(*this)[pos].isLayoutEquivalent((*other)[pos]))
+ return false;
+ }
+ return true;
+}
+#endif
+
+bool SalLayoutGlyphsImpl::IsValid() const
+{
+ if (!m_rFontInstance.is())
+ return false;
+ return true;
+}
+
+void SalLayoutGlyphsCache::clear() { mCachedGlyphs.clear(); }
+
+SalLayoutGlyphsCache* SalLayoutGlyphsCache::self()
+{
+ static vcl::DeleteOnDeinit<SalLayoutGlyphsCache> cache(
+ !utl::ConfigManager::IsFuzzing()
+ ? officecfg::Office::Common::Cache::Font::GlyphsCacheSize::get()
+ : 20000);
+ return cache.get();
+}
+
+static UBiDiDirection getBiDiDirection(std::u16string_view text, sal_Int32 index, sal_Int32 len)
+{
+ // Return whether all character are LTR, RTL, neutral or whether it's mixed.
+ // This is sort of ubidi_getBaseDirection() and ubidi_getDirection(),
+ // but it's meant to be fast but also check all characters.
+ sal_Int32 end = index + len;
+ UBiDiDirection direction = UBIDI_NEUTRAL;
+ while (index < end)
+ {
+ switch (u_charDirection(o3tl::iterateCodePoints(text, &index)))
+ {
+ // Only characters with strong direction.
+ case U_LEFT_TO_RIGHT:
+ if (direction == UBIDI_RTL)
+ return UBIDI_MIXED;
+ direction = UBIDI_LTR;
+ break;
+ case U_RIGHT_TO_LEFT:
+ case U_RIGHT_TO_LEFT_ARABIC:
+ if (direction == UBIDI_LTR)
+ return UBIDI_MIXED;
+ direction = UBIDI_RTL;
+ break;
+ default:
+ break;
+ }
+ }
+ return direction;
+}
+
+static SalLayoutGlyphs makeGlyphsSubset(const SalLayoutGlyphs& source,
+ const OutputDevice* outputDevice, std::u16string_view text,
+ sal_Int32 index, sal_Int32 len)
+{
+ // tdf#149264: We need to check if the text is LTR, RTL or mixed. Apparently
+ // harfbuzz doesn't give reproducible results (or possibly HB_GLYPH_FLAG_UNSAFE_TO_BREAK
+ // is not reliable?) when asked to lay out RTL text as LTR. So require that the whole
+ // subset ir either LTR or RTL.
+ UBiDiDirection direction = getBiDiDirection(text, index, len);
+ if (direction == UBIDI_MIXED)
+ return SalLayoutGlyphs();
+ SalLayoutGlyphs ret;
+ for (int level = 0;; ++level)
+ {
+ const SalLayoutGlyphsImpl* sourceLevel = source.Impl(level);
+ if (sourceLevel == nullptr)
+ break;
+ bool sourceRtl = bool(sourceLevel->GetFlags() & SalLayoutFlags::BiDiRtl);
+ if ((direction == UBIDI_LTR && sourceRtl) || (direction == UBIDI_RTL && !sourceRtl))
+ return SalLayoutGlyphs();
+ SalLayoutGlyphsImpl* cloned = sourceLevel->cloneCharRange(index, len);
+ // If the glyphs range cannot be cloned, bail out.
+ if (cloned == nullptr)
+ return SalLayoutGlyphs();
+ // If the entire string is mixed LTR/RTL but the subset is only LTR,
+ // then make sure the flags match that, otherwise checkGlyphsEqual()
+ // would assert on flags being different.
+ cloned->SetFlags(cloned->GetFlags()
+ | outputDevice->GetBiDiLayoutFlags(text, index, index + len));
+ ret.AppendImpl(cloned);
+ }
+ return ret;
+}
+
+#ifdef DBG_UTIL
+static void checkGlyphsEqual(const SalLayoutGlyphs& g1, const SalLayoutGlyphs& g2)
+{
+ for (int level = 0;; ++level)
+ {
+ const SalLayoutGlyphsImpl* l1 = g1.Impl(level);
+ const SalLayoutGlyphsImpl* l2 = g2.Impl(level);
+ if (l1 == nullptr || l2 == nullptr)
+ {
+ assert(l1 == l2);
+ break;
+ }
+ assert(l1->isLayoutEquivalent(l2));
+ }
+}
+#endif
+
+const SalLayoutGlyphs*
+SalLayoutGlyphsCache::GetLayoutGlyphs(VclPtr<const OutputDevice> outputDevice, const OUString& text,
+ sal_Int32 nIndex, sal_Int32 nLen, tools::Long nLogicWidth,
+ const vcl::text::TextLayoutCache* layoutCache)
+{
+ if (nLen == 0)
+ return nullptr;
+ const CachedGlyphsKey key(outputDevice, text, nIndex, nLen, nLogicWidth);
+ GlyphsCache::const_iterator it = mCachedGlyphs.find(key);
+ if (it != mCachedGlyphs.end())
+ {
+ if (it->second.IsValid())
+ return &it->second;
+ // Do not try to create the layout here. If a cache item exists, it's already
+ // been attempted and the layout was invalid (this happens with MultiSalLayout).
+ // So in that case this is a cached failure.
+ return nullptr;
+ }
+ bool resetLastSubstringKey = true;
+ const sal_Unicode nbSpace = 0xa0; // non-breaking space
+ // SalLayoutGlyphsImpl::cloneCharRange() requires BiDiStrong, so if not set, do not even try.
+ bool skipGlyphSubsets
+ = !(outputDevice->GetLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiStrong);
+ if ((nIndex != 0 || nLen != text.getLength()) && !skipGlyphSubsets)
+ {
+ // The glyphs functions are often called first for an entire string
+ // and then with an increasing starting index until the end of the string.
+ // Which means it's possible to get the glyphs faster by just copying
+ // a subset of the full glyphs and adjusting as necessary.
+ if (mLastTemporaryKey.has_value() && mLastTemporaryKey == key)
+ return &mLastTemporaryGlyphs;
+ const CachedGlyphsKey keyWhole(outputDevice, text, 0, text.getLength(), nLogicWidth);
+ GlyphsCache::const_iterator itWhole = mCachedGlyphs.find(keyWhole);
+ if (itWhole == mCachedGlyphs.end())
+ {
+ // This function may often be called repeatedly for segments of the same string,
+ // in which case it is more efficient to cache glyphs for the entire string
+ // and then return subsets of them. So if a second call either starts at the same
+ // position or starts at the end of the previous call, cache the entire string.
+ // This used to do this only for the first two segments of the string,
+ // but that missed the case when the font slightly changed e.g. because of the first
+ // part being underlined. Doing this for any two segments allows this optimization
+ // even when the prefix of the string would use a different font.
+ // TODO: Can those font differences be ignored?
+ // Writer layouts tests enable SAL_NON_APPLICATION_FONT_USE=abort in order
+ // to make PrintFontManager::Substitute() abort if font fallback happens. When
+ // laying out the entire string the chance this happens increases (e.g. testAbi11870
+ // normally calls this function only for a part of a string, but this optimization
+ // lays out the entire string and causes a fallback). Since this optimization
+ // does not change result of this function, simply disable it for those tests.
+ static const bool bAbortOnFontSubstitute = [] {
+ const char* pEnv = getenv("SAL_NON_APPLICATION_FONT_USE");
+ return pEnv && strcmp(pEnv, "abort") == 0;
+ }();
+ if (mLastSubstringKey.has_value() && !bAbortOnFontSubstitute)
+ {
+ sal_Int32 pos = nIndex;
+ if (mLastSubstringKey->len < pos && text[pos - 1] == nbSpace)
+ --pos; // Writer skips a non-breaking space, so skip that character too.
+ if ((mLastSubstringKey->len == pos || mLastSubstringKey->index == nIndex)
+ && mLastSubstringKey
+ == CachedGlyphsKey(outputDevice, text, mLastSubstringKey->index,
+ mLastSubstringKey->len, nLogicWidth))
+ {
+ GetLayoutGlyphs(outputDevice, text, 0, text.getLength(), nLogicWidth,
+ layoutCache);
+ itWhole = mCachedGlyphs.find(keyWhole);
+ }
+ else
+ mLastSubstringKey.reset();
+ }
+ if (!mLastSubstringKey.has_value())
+ {
+ mLastSubstringKey = key;
+ resetLastSubstringKey = false;
+ }
+ }
+ if (itWhole != mCachedGlyphs.end() && itWhole->second.IsValid())
+ {
+ mLastSubstringKey.reset();
+ mLastTemporaryGlyphs
+ = makeGlyphsSubset(itWhole->second, outputDevice, text, nIndex, nLen);
+ if (mLastTemporaryGlyphs.IsValid())
+ {
+ mLastTemporaryKey = key;
+#ifdef DBG_UTIL
+ std::shared_ptr<const vcl::text::TextLayoutCache> tmpLayoutCache;
+ if (layoutCache == nullptr)
+ {
+ tmpLayoutCache = vcl::text::TextLayoutCache::Create(text);
+ layoutCache = tmpLayoutCache.get();
+ }
+ // Check if the subset result really matches what we would get normally,
+ // to make sure corner cases are handled well (see SalLayoutGlyphsImpl::cloneCharRange()).
+ std::unique_ptr<SalLayout> layout
+ = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {}, {},
+ SalLayoutFlags::GlyphItemsOnly, layoutCache);
+ assert(layout);
+ checkGlyphsEqual(mLastTemporaryGlyphs, layout->GetGlyphs());
+#endif
+ return &mLastTemporaryGlyphs;
+ }
+ }
+ }
+ if (resetLastSubstringKey)
+ {
+ // Writer does non-breaking space differently (not as part of the string), so in that
+ // case ignore that call and still allow finding two adjacent substrings that have
+ // the non-breaking space between them.
+ if (nLen != 1 || text[nIndex] != nbSpace)
+ mLastSubstringKey.reset();
+ }
+
+ std::shared_ptr<const vcl::text::TextLayoutCache> tmpLayoutCache;
+ if (layoutCache == nullptr)
+ {
+ tmpLayoutCache = vcl::text::TextLayoutCache::Create(text);
+ layoutCache = tmpLayoutCache.get();
+ }
+ std::unique_ptr<SalLayout> layout
+ = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {}, {},
+ SalLayoutFlags::GlyphItemsOnly, layoutCache);
+ if (layout)
+ {
+ SalLayoutGlyphs glyphs = layout->GetGlyphs();
+ if (glyphs.IsValid())
+ {
+ // TODO: Fallbacks do not work reliably (fallback font not included in the key),
+ // so do not cache (but still return once, using the temporary without a key set).
+ if (!mbCacheGlyphsWhenDoingFallbackFonts && glyphs.Impl(1) != nullptr)
+ {
+ mLastTemporaryGlyphs = std::move(glyphs);
+ mLastTemporaryKey.reset();
+ return &mLastTemporaryGlyphs;
+ }
+ mCachedGlyphs.insert(std::make_pair(key, std::move(glyphs)));
+ assert(mCachedGlyphs.find(key)
+ == mCachedGlyphs.begin()); // newly inserted item is first
+ return &mCachedGlyphs.begin()->second;
+ }
+ }
+ // Failure, cache it too as invalid glyphs.
+ mCachedGlyphs.insert(std::make_pair(key, SalLayoutGlyphs()));
+ return nullptr;
+}
+
+void SalLayoutGlyphsCache::SetCacheGlyphsWhenDoingFallbackFonts(bool bOK)
+{
+ mbCacheGlyphsWhenDoingFallbackFonts = bOK;
+ if (!bOK)
+ clear();
+}
+
+SalLayoutGlyphsCache::CachedGlyphsKey::CachedGlyphsKey(
+ const VclPtr<const OutputDevice>& outputDevice, OUString t, sal_Int32 i, sal_Int32 l,
+ tools::Long w)
+ : text(std::move(t))
+ , index(i)
+ , len(l)
+ , logicWidth(w)
+ // we also need to save things used in OutputDevice::ImplPrepareLayoutArgs(), in case they
+ // change in the output device, plus mapMode affects the sizes.
+ , fontMetric(outputDevice->GetFontMetric())
+ // TODO It would be possible to get a better hit ratio if mapMode wasn't part of the key
+ // and results that differ only in mapmode would have coordinates adjusted based on that.
+ // That would occasionally lead to rounding errors (at least differences that would
+ // make checkGlyphsEqual() fail).
+ , mapMode(outputDevice->GetMapMode())
+ , digitLanguage(outputDevice->GetDigitLanguage())
+ , layoutMode(outputDevice->GetLayoutMode())
+ , rtl(outputDevice->IsRTLEnabled())
+{
+ const LogicalFontInstance* fi = outputDevice->GetFontInstance();
+ fi->GetScale(&fontScaleX, &fontScaleY);
+
+ const vcl::font::FontSelectPattern& rFSD = fi->GetFontSelectPattern();
+ disabledLigatures = rFSD.GetPitch() == PITCH_FIXED;
+ artificialItalic = fi->NeedsArtificialItalic();
+ artificialBold = fi->NeedsArtificialBold();
+
+ hashValue = 0;
+ o3tl::hash_combine(hashValue, vcl::text::FirstCharsStringHash()(text));
+ o3tl::hash_combine(hashValue, index);
+ o3tl::hash_combine(hashValue, len);
+ o3tl::hash_combine(hashValue, logicWidth);
+ o3tl::hash_combine(hashValue, outputDevice.get());
+ // Need to use IgnoreColor, because sometimes the color changes, but it's irrelevant
+ // for text layout (and also obsolete in vcl::Font).
+ o3tl::hash_combine(hashValue, fontMetric.GetHashValueIgnoreColor());
+ // For some reason font scale may differ even if vcl::Font is the same,
+ // so explicitly check it too.
+ o3tl::hash_combine(hashValue, fontScaleX);
+ o3tl::hash_combine(hashValue, fontScaleY);
+ o3tl::hash_combine(hashValue, mapMode.GetHashValue());
+ o3tl::hash_combine(hashValue, rtl);
+ o3tl::hash_combine(hashValue, disabledLigatures);
+ o3tl::hash_combine(hashValue, artificialItalic);
+ o3tl::hash_combine(hashValue, artificialBold);
+ o3tl::hash_combine(hashValue, layoutMode);
+ o3tl::hash_combine(hashValue, digitLanguage.get());
+}
+
+inline bool SalLayoutGlyphsCache::CachedGlyphsKey::operator==(const CachedGlyphsKey& other) const
+{
+ return hashValue == other.hashValue && index == other.index && len == other.len
+ && logicWidth == other.logicWidth && mapMode == other.mapMode && rtl == other.rtl
+ && disabledLigatures == other.disabledLigatures
+ && artificialItalic == other.artificialItalic && artificialBold == other.artificialBold
+ && layoutMode == other.layoutMode && digitLanguage == other.digitLanguage
+ && fontScaleX == other.fontScaleX && fontScaleY == other.fontScaleY
+ && fontMetric.EqualIgnoreColor(other.fontMetric)
+ && vcl::text::FastStringCompareEqual()(text, other.text);
+ // Slower things last in the comparison.
+}
+
+size_t SalLayoutGlyphsCache::GlyphsCost::operator()(const SalLayoutGlyphs& glyphs) const
+{
+ size_t cost = 0;
+ for (int level = 0;; ++level)
+ {
+ const SalLayoutGlyphsImpl* impl = glyphs.Impl(level);
+ if (impl == nullptr)
+ break;
+ // Count size in bytes, both the SalLayoutGlyphsImpl instance and contained GlyphItem's.
+ cost += sizeof(*impl);
+ cost += impl->size() * sizeof(impl->front());
+ }
+ return cost;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/impgraph.cxx b/vcl/source/gdi/impgraph.cxx
new file mode 100644
index 0000000000..ac36d2c72b
--- /dev/null
+++ b/vcl/source/gdi/impgraph.cxx
@@ -0,0 +1,1775 @@
+/* -*- 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 <sal/config.h>
+#include <sal/log.hxx>
+
+#include <comphelper/fileformat.h>
+#include <o3tl/make_shared.hxx>
+#include <tools/fract.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/stream.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <unotools/tempfile.hxx>
+#include <utility>
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/filter/SvmWriter.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/gfxlink.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/metaact.hxx>
+#include <impgraph.hxx>
+#include <com/sun/star/graphic/XPrimitive2D.hpp>
+#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
+#include <vcl/dibtools.hxx>
+#include <map>
+#include <memory>
+#include <vcl/gdimetafiletools.hxx>
+#include <vcl/TypeSerializer.hxx>
+#include <vcl/pdfread.hxx>
+#include <graphic/VectorGraphicLoader.hxx>
+
+#define GRAPHIC_MTFTOBMP_MAXEXT 2048
+#define GRAPHIC_STREAMBUFSIZE 8192UL
+
+#define SWAP_FORMAT_ID COMPAT_FORMAT( 'S', 'W', 'A', 'P' )
+
+using namespace com::sun::star;
+
+
+class ImpSwapFile
+{
+private:
+ utl::TempFileFast maTempFile;
+ OUString maOriginURL;
+
+public:
+ ImpSwapFile(OUString aOriginURL)
+ : maOriginURL(std::move(aOriginURL))
+ {
+ }
+
+ SvStream* getStream() { return maTempFile.GetStream(StreamMode::READWRITE); }
+ OUString const & getOriginURL() const { return maOriginURL; }
+};
+
+SvStream* ImpGraphic::getSwapFileStream() const
+{
+ if (mpSwapFile)
+ return mpSwapFile->getStream();
+ return nullptr;
+}
+
+ImpGraphic::ImpGraphic() :
+ meType ( GraphicType::NONE ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared ( false )
+{
+}
+
+ImpGraphic::ImpGraphic(const ImpGraphic& rImpGraphic)
+ : maMetaFile(rImpGraphic.maMetaFile)
+ , maBitmapEx(rImpGraphic.maBitmapEx)
+ , maSwapInfo(rImpGraphic.maSwapInfo)
+ , mpContext(rImpGraphic.mpContext)
+ , mpSwapFile(rImpGraphic.mpSwapFile)
+ , mpGfxLink(rImpGraphic.mpGfxLink)
+ , meType(rImpGraphic.meType)
+ , mnSizeBytes(rImpGraphic.mnSizeBytes)
+ , mbSwapOut(rImpGraphic.mbSwapOut)
+ , mbDummyContext(rImpGraphic.mbDummyContext)
+ , maVectorGraphicData(rImpGraphic.maVectorGraphicData)
+ , maGraphicExternalLink(rImpGraphic.maGraphicExternalLink)
+ , maLastUsed (std::chrono::high_resolution_clock::now())
+ , mbPrepared (rImpGraphic.mbPrepared)
+{
+ if( rImpGraphic.mpAnimation )
+ {
+ mpAnimation = std::make_unique<Animation>( *rImpGraphic.mpAnimation );
+ maBitmapEx = mpAnimation->GetBitmapEx();
+ }
+}
+
+ImpGraphic::ImpGraphic(ImpGraphic&& rImpGraphic) noexcept
+ : maMetaFile(std::move(rImpGraphic.maMetaFile))
+ , maBitmapEx(std::move(rImpGraphic.maBitmapEx))
+ , maSwapInfo(std::move(rImpGraphic.maSwapInfo))
+ , mpAnimation(std::move(rImpGraphic.mpAnimation))
+ , mpContext(std::move(rImpGraphic.mpContext))
+ , mpSwapFile(std::move(rImpGraphic.mpSwapFile))
+ , mpGfxLink(std::move(rImpGraphic.mpGfxLink))
+ , meType(rImpGraphic.meType)
+ , mnSizeBytes(rImpGraphic.mnSizeBytes)
+ , mbSwapOut(rImpGraphic.mbSwapOut)
+ , mbDummyContext(rImpGraphic.mbDummyContext)
+ , maVectorGraphicData(std::move(rImpGraphic.maVectorGraphicData))
+ , maGraphicExternalLink(rImpGraphic.maGraphicExternalLink)
+ , maLastUsed (std::chrono::high_resolution_clock::now())
+ , mbPrepared (rImpGraphic.mbPrepared)
+{
+ rImpGraphic.clear();
+ rImpGraphic.mbDummyContext = false;
+}
+
+ImpGraphic::ImpGraphic(std::shared_ptr<GfxLink> xGfxLink, sal_Int32 nPageIndex)
+ : mpGfxLink(std::move(xGfxLink))
+ , meType(GraphicType::Bitmap)
+ , mnSizeBytes(0)
+ , mbSwapOut(true)
+ , mbDummyContext(false)
+ , maLastUsed (std::chrono::high_resolution_clock::now())
+ , mbPrepared (false)
+{
+ maSwapInfo.mbIsTransparent = true;
+ maSwapInfo.mbIsAlpha = true;
+ maSwapInfo.mbIsEPS = false;
+ maSwapInfo.mbIsAnimated = false;
+ maSwapInfo.mnAnimationLoopCount = 0;
+ maSwapInfo.mnPageIndex = nPageIndex;
+}
+
+ImpGraphic::ImpGraphic(GraphicExternalLink aGraphicExternalLink) :
+ meType ( GraphicType::Default ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ maGraphicExternalLink(std::move(aGraphicExternalLink)),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared (false)
+{
+}
+
+ImpGraphic::ImpGraphic( const BitmapEx& rBitmapEx ) :
+ maBitmapEx ( rBitmapEx ),
+ meType ( !rBitmapEx.IsEmpty() ? GraphicType::Bitmap : GraphicType::NONE ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared (false)
+{
+}
+
+ImpGraphic::ImpGraphic(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr)
+: meType( rVectorGraphicDataPtr ? GraphicType::Bitmap : GraphicType::NONE ),
+ mnSizeBytes( 0 ),
+ mbSwapOut( false ),
+ mbDummyContext ( false ),
+ maVectorGraphicData(rVectorGraphicDataPtr),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared (false)
+{
+}
+
+ImpGraphic::ImpGraphic( const Animation& rAnimation ) :
+ maBitmapEx ( rAnimation.GetBitmapEx() ),
+ mpAnimation ( std::make_unique<Animation>( rAnimation ) ),
+ meType ( GraphicType::Bitmap ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared (false)
+{
+}
+
+ImpGraphic::ImpGraphic( const GDIMetaFile& rMtf ) :
+ maMetaFile ( rMtf ),
+ meType ( GraphicType::GdiMetafile ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared (false)
+{
+}
+
+ImpGraphic::~ImpGraphic()
+{
+ vcl::graphic::Manager::get().unregisterGraphic(this);
+}
+
+ImpGraphic& ImpGraphic::operator=( const ImpGraphic& rImpGraphic )
+{
+ if( &rImpGraphic != this )
+ {
+ sal_Int64 aOldSizeBytes = mnSizeBytes;
+
+ maMetaFile = rImpGraphic.maMetaFile;
+ meType = rImpGraphic.meType;
+ mnSizeBytes = rImpGraphic.mnSizeBytes;
+
+ maSwapInfo = rImpGraphic.maSwapInfo;
+ mpContext = rImpGraphic.mpContext;
+ mbDummyContext = rImpGraphic.mbDummyContext;
+ maGraphicExternalLink = rImpGraphic.maGraphicExternalLink;
+
+ mpAnimation.reset();
+
+ if ( rImpGraphic.mpAnimation )
+ {
+ mpAnimation = std::make_unique<Animation>( *rImpGraphic.mpAnimation );
+ maBitmapEx = mpAnimation->GetBitmapEx();
+ }
+ else
+ {
+ maBitmapEx = rImpGraphic.maBitmapEx;
+ }
+
+ mbSwapOut = rImpGraphic.mbSwapOut;
+ mpSwapFile = rImpGraphic.mpSwapFile;
+ mbPrepared = rImpGraphic.mbPrepared;
+
+ mpGfxLink = rImpGraphic.mpGfxLink;
+
+ maVectorGraphicData = rImpGraphic.maVectorGraphicData;
+ maLastUsed = std::chrono::high_resolution_clock::now();
+
+ vcl::graphic::Manager::get().changeExisting(this, aOldSizeBytes);
+ }
+
+ return *this;
+}
+
+ImpGraphic& ImpGraphic::operator=(ImpGraphic&& rImpGraphic)
+{
+ sal_Int64 aOldSizeBytes = mnSizeBytes;
+
+ maMetaFile = std::move(rImpGraphic.maMetaFile);
+ meType = rImpGraphic.meType;
+ mnSizeBytes = rImpGraphic.mnSizeBytes;
+ maSwapInfo = std::move(rImpGraphic.maSwapInfo);
+ mpContext = std::move(rImpGraphic.mpContext);
+ mbDummyContext = rImpGraphic.mbDummyContext;
+ mpAnimation = std::move(rImpGraphic.mpAnimation);
+ maBitmapEx = std::move(rImpGraphic.maBitmapEx);
+ mbSwapOut = rImpGraphic.mbSwapOut;
+ mpSwapFile = std::move(rImpGraphic.mpSwapFile);
+ mpGfxLink = std::move(rImpGraphic.mpGfxLink);
+ maVectorGraphicData = std::move(rImpGraphic.maVectorGraphicData);
+ maGraphicExternalLink = rImpGraphic.maGraphicExternalLink;
+ mbPrepared = rImpGraphic.mbPrepared;
+
+ rImpGraphic.clear();
+ rImpGraphic.mbDummyContext = false;
+ maLastUsed = std::chrono::high_resolution_clock::now();
+
+ vcl::graphic::Manager::get().changeExisting(this, aOldSizeBytes);
+
+ return *this;
+}
+
+bool ImpGraphic::operator==( const ImpGraphic& rOther ) const
+{
+ if( this == &rOther )
+ return true;
+
+ if (mbPrepared && rOther.mbPrepared)
+ return (*mpGfxLink == *rOther.mpGfxLink);
+
+ if (!isAvailable() || !rOther.isAvailable())
+ return false;
+
+ if ( meType != rOther.meType )
+ return false;
+
+ bool bRet = false;
+ switch( meType )
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ return true;
+
+ case GraphicType::GdiMetafile:
+ return ( rOther.maMetaFile == maMetaFile );
+
+ case GraphicType::Bitmap:
+ {
+ if(maVectorGraphicData)
+ {
+ if(maVectorGraphicData == rOther.maVectorGraphicData)
+ {
+ // equal instances
+ bRet = true;
+ }
+ else if(rOther.maVectorGraphicData)
+ {
+ // equal content
+ bRet = (*maVectorGraphicData) == (*rOther.maVectorGraphicData);
+ }
+ }
+ else if( mpAnimation )
+ {
+ if( rOther.mpAnimation && ( *rOther.mpAnimation == *mpAnimation ) )
+ bRet = true;
+ }
+ else if( !rOther.mpAnimation && ( rOther.maBitmapEx == maBitmapEx ) )
+ {
+ bRet = true;
+ }
+ }
+ break;
+ }
+
+ return bRet;
+}
+
+const std::shared_ptr<VectorGraphicData>& ImpGraphic::getVectorGraphicData() const
+{
+ ensureAvailable();
+
+ return maVectorGraphicData;
+}
+
+void ImpGraphic::createSwapInfo()
+{
+ if (isSwappedOut())
+ return;
+
+ if (!maBitmapEx.IsEmpty())
+ maSwapInfo.maSizePixel = maBitmapEx.GetSizePixel();
+ else
+ maSwapInfo.maSizePixel = Size();
+
+ maSwapInfo.maPrefMapMode = getPrefMapMode();
+ maSwapInfo.maPrefSize = getPrefSize();
+ maSwapInfo.mbIsAnimated = isAnimated();
+ maSwapInfo.mbIsEPS = isEPS();
+ maSwapInfo.mbIsTransparent = isTransparent();
+ maSwapInfo.mbIsAlpha = isAlpha();
+ maSwapInfo.mnAnimationLoopCount = getAnimationLoopCount();
+ maSwapInfo.mnPageIndex = getPageNumber();
+}
+
+void ImpGraphic::clearGraphics()
+{
+ maBitmapEx.Clear();
+ maMetaFile.Clear();
+ mpAnimation.reset();
+ maVectorGraphicData.reset();
+}
+
+void ImpGraphic::setPrepared(bool bAnimated, const Size* pSizeHint)
+{
+ mbPrepared = true;
+ mbSwapOut = true;
+ meType = GraphicType::Bitmap;
+
+ SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(mpGfxLink->GetData()), mpGfxLink->GetDataSize(), StreamMode::READ | StreamMode::WRITE);
+
+ if (pSizeHint)
+ {
+ maSwapInfo.maPrefSize = *pSizeHint;
+ maSwapInfo.maPrefMapMode = MapMode(MapUnit::Map100thMM);
+ }
+
+ GraphicDescriptor aDescriptor(aMemoryStream, nullptr);
+ if (aDescriptor.Detect(true))
+ {
+ if (!pSizeHint)
+ {
+ // If we have logic size, work with that, as later pixel -> logic
+ // conversion will work with the output device DPI, not the graphic
+ // DPI.
+ Size aLogSize = aDescriptor.GetSize_100TH_MM();
+ if (aDescriptor.GetPreferredLogSize() && aDescriptor.GetPreferredMapMode())
+ {
+ maSwapInfo.maPrefSize = *aDescriptor.GetPreferredLogSize();
+ maSwapInfo.maPrefMapMode = *aDescriptor.GetPreferredMapMode();
+ }
+ else if (aLogSize.getWidth() && aLogSize.getHeight())
+ {
+ maSwapInfo.maPrefSize = aLogSize;
+ maSwapInfo.maPrefMapMode = MapMode(MapUnit::Map100thMM);
+ }
+ else
+ {
+ maSwapInfo.maPrefSize = aDescriptor.GetSizePixel();
+ maSwapInfo.maPrefMapMode = MapMode(MapUnit::MapPixel);
+ }
+ }
+
+ maSwapInfo.maSizePixel = aDescriptor.GetSizePixel();
+ maSwapInfo.mbIsTransparent = aDescriptor.IsTransparent();
+ maSwapInfo.mbIsAlpha = aDescriptor.IsAlpha();
+ } else {
+ maSwapInfo.mbIsTransparent = false;
+ maSwapInfo.mbIsAlpha = false;
+ }
+
+ maSwapInfo.mnAnimationLoopCount = 0;
+ maSwapInfo.mbIsEPS = false;
+ maSwapInfo.mbIsAnimated = bAnimated;
+
+ if (maVectorGraphicData)
+ maSwapInfo.mnPageIndex = maVectorGraphicData->getPageIndex();
+}
+
+void ImpGraphic::clear()
+{
+ mpSwapFile.reset();
+ mbSwapOut = false;
+ mbPrepared = false;
+
+ // cleanup
+ clearGraphics();
+ meType = GraphicType::NONE;
+ sal_Int64 nOldSize = mnSizeBytes;
+ mnSizeBytes = 0;
+ vcl::graphic::Manager::get().changeExisting(this, nOldSize);
+ maGraphicExternalLink.msURL.clear();
+}
+
+void ImpGraphic::setDefaultType()
+{
+ clear();
+ meType = GraphicType::Default;
+}
+
+bool ImpGraphic::isSupportedGraphic() const
+{
+ return( meType != GraphicType::NONE );
+}
+
+bool ImpGraphic::isTransparent() const
+{
+ bool bRet(true);
+
+ if (mbSwapOut)
+ {
+ bRet = maSwapInfo.mbIsTransparent;
+ }
+ else if (meType == GraphicType::Bitmap && !maVectorGraphicData)
+ {
+ bRet = mpAnimation ? mpAnimation->IsTransparent() : maBitmapEx.IsAlpha();
+ }
+
+ return bRet;
+}
+
+bool ImpGraphic::isAlpha() const
+{
+ bool bRet(false);
+
+ if (mbSwapOut)
+ {
+ bRet = maSwapInfo.mbIsAlpha;
+ }
+ else if (maVectorGraphicData)
+ {
+ bRet = true;
+ }
+ else if (meType == GraphicType::Bitmap)
+ {
+ bRet = (nullptr == mpAnimation && maBitmapEx.IsAlpha());
+ }
+
+ return bRet;
+}
+
+bool ImpGraphic::isAnimated() const
+{
+ return mbSwapOut ? maSwapInfo.mbIsAnimated : mpAnimation != nullptr;
+}
+
+bool ImpGraphic::isEPS() const
+{
+ if (mbSwapOut)
+ return maSwapInfo.mbIsEPS;
+
+ return( ( meType == GraphicType::GdiMetafile ) &&
+ ( maMetaFile.GetActionSize() > 0 ) &&
+ ( maMetaFile.GetAction( 0 )->GetType() == MetaActionType::EPS ) );
+}
+
+bool ImpGraphic::isAvailable() const
+{
+ return !mbPrepared && !mbSwapOut;
+}
+
+bool ImpGraphic::makeAvailable()
+{
+ return ensureAvailable();
+}
+
+BitmapEx ImpGraphic::getVectorGraphicReplacement() const
+{
+ BitmapEx aRet = maVectorGraphicData->getReplacement();
+
+ if (maExPrefSize.getWidth() && maExPrefSize.getHeight())
+ {
+ aRet.SetPrefSize(maExPrefSize);
+ }
+
+ return aRet;
+}
+
+Bitmap ImpGraphic::getBitmap(const GraphicConversionParameters& rParameters) const
+{
+ Bitmap aRetBmp;
+
+ ensureAvailable();
+
+ if( meType == GraphicType::Bitmap )
+ {
+ if(maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // use maBitmapEx as local buffer for rendered svg
+ const_cast< ImpGraphic* >(this)->maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ const BitmapEx& rRetBmpEx = ( mpAnimation ? mpAnimation->GetBitmapEx() : maBitmapEx );
+
+ aRetBmp = rRetBmpEx.GetBitmap( COL_WHITE );
+
+ if(rParameters.getSizePixel().Width() || rParameters.getSizePixel().Height())
+ aRetBmp.Scale(rParameters.getSizePixel());
+ }
+ else if( ( meType != GraphicType::Default ) && isSupportedGraphic() )
+ {
+ if(maBitmapEx.IsEmpty())
+ {
+ // calculate size
+ ScopedVclPtrInstance< VirtualDevice > aVDev;
+ Size aDrawSize(aVDev->LogicToPixel(maMetaFile.GetPrefSize(), maMetaFile.GetPrefMapMode()));
+
+ if(rParameters.getSizePixel().Width() && rParameters.getSizePixel().Height())
+ {
+ // apply given size if exists
+ aDrawSize = rParameters.getSizePixel();
+ }
+
+ if(aDrawSize.Width() && aDrawSize.Height() && !rParameters.getUnlimitedSize()
+ && (aDrawSize.Width() > GRAPHIC_MTFTOBMP_MAXEXT || aDrawSize.Height() > GRAPHIC_MTFTOBMP_MAXEXT))
+ {
+ // limit bitmap size to a maximum of GRAPHIC_MTFTOBMP_MAXEXT x GRAPHIC_MTFTOBMP_MAXEXT
+ double fWH(static_cast<double>(aDrawSize.Width()) / static_cast<double>(aDrawSize.Height()));
+
+ if(fWH <= 1.0)
+ {
+ aDrawSize.setWidth(basegfx::fround(GRAPHIC_MTFTOBMP_MAXEXT * fWH));
+ aDrawSize.setHeight(GRAPHIC_MTFTOBMP_MAXEXT);
+ }
+ else
+ {
+ aDrawSize.setWidth(GRAPHIC_MTFTOBMP_MAXEXT);
+ aDrawSize.setHeight(basegfx::fround(GRAPHIC_MTFTOBMP_MAXEXT / fWH));
+ }
+ }
+
+ // calculate pixel size. Normally, it's the same as aDrawSize, but may
+ // need to be extended when hairlines are on the right or bottom edge
+ Size aPixelSize(aDrawSize);
+
+ if(GraphicType::GdiMetafile == getType())
+ {
+ // tdf#126319 Removed correction based on hairline-at-the-extremes of
+ // the metafile. The task shows that this is no longer sufficient since
+ // less hairlines get used in general - what is good, but breaks that
+ // old fix. Anyways, hairlines are a left-over from non-AA times
+ // when it was not possible to paint lines taller than one pixel.
+ // This might need to be corrected further using primitives and
+ // the possibility to get better-quality ranges for correction. For
+ // now, always add that one pixel.
+ aPixelSize.setWidth(aPixelSize.getWidth() + 1);
+ aPixelSize.setHeight(aPixelSize.getHeight() + 1);
+ }
+
+ if(aVDev->SetOutputSizePixel(aPixelSize))
+ {
+ if(rParameters.getAntiAliase())
+ {
+ aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::Enable);
+ }
+
+ if(rParameters.getSnapHorVerLines())
+ {
+ aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::PixelSnapHairline);
+ }
+
+ draw(*aVDev, Point(), aDrawSize);
+
+ // use maBitmapEx as local buffer for rendered metafile
+ const_cast< ImpGraphic* >(this)->maBitmapEx = aVDev->GetBitmapEx( Point(), aVDev->GetOutputSizePixel() );
+ }
+ }
+
+ aRetBmp = maBitmapEx.GetBitmap();
+ }
+
+ if( !aRetBmp.IsEmpty() )
+ {
+ aRetBmp.SetPrefMapMode(getPrefMapMode());
+ aRetBmp.SetPrefSize(getPrefSize());
+ }
+
+ return aRetBmp;
+}
+
+BitmapEx ImpGraphic::getBitmapEx(const GraphicConversionParameters& rParameters) const
+{
+ BitmapEx aRetBmpEx;
+
+ ensureAvailable();
+
+ if( meType == GraphicType::Bitmap )
+ {
+ if(maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // use maBitmapEx as local buffer for rendered svg
+ const_cast< ImpGraphic* >(this)->maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ aRetBmpEx = ( mpAnimation ? mpAnimation->GetBitmapEx() : maBitmapEx );
+
+ if(rParameters.getSizePixel().Width() || rParameters.getSizePixel().Height())
+ {
+ aRetBmpEx.Scale(
+ rParameters.getSizePixel(),
+ BmpScaleFlag::Fast);
+ }
+ }
+ else if( ( meType != GraphicType::Default ) && isSupportedGraphic() )
+ {
+ if(maBitmapEx.IsEmpty())
+ {
+ const ImpGraphic aMonoMask( maMetaFile.GetMonochromeMtf( COL_BLACK ) );
+
+ // use maBitmapEx as local buffer for rendered metafile
+ const_cast< ImpGraphic* >(this)->maBitmapEx = BitmapEx(getBitmap(rParameters), aMonoMask.getBitmap(rParameters));
+ }
+
+ aRetBmpEx = maBitmapEx;
+ }
+
+ return aRetBmpEx;
+}
+
+Animation ImpGraphic::getAnimation() const
+{
+ Animation aAnimation;
+
+ ensureAvailable();
+ if( mpAnimation )
+ aAnimation = *mpAnimation;
+
+ return aAnimation;
+}
+
+const BitmapEx& ImpGraphic::getBitmapExRef() const
+{
+ ensureAvailable();
+ return maBitmapEx;
+}
+
+const GDIMetaFile& ImpGraphic::getGDIMetaFile() const
+{
+ ensureAvailable();
+ if (!maMetaFile.GetActionSize()
+ && maVectorGraphicData
+ && (VectorGraphicDataType::Emf == maVectorGraphicData->getType()
+ || VectorGraphicDataType::Wmf == maVectorGraphicData->getType()))
+ {
+ // If we have a Emf/Wmf VectorGraphic object, we
+ // need a way to get the Metafile data out of the primitive
+ // representation. Use a strict virtual hook (MetafileAccessor)
+ // to access the MetafilePrimitive2D directly. Also see comments in
+ // XEmfParser about this.
+ const std::deque< css::uno::Reference< css::graphic::XPrimitive2D > > aSequence(maVectorGraphicData->getPrimitive2DSequence());
+
+ if (1 == aSequence.size())
+ {
+ // try to cast to MetafileAccessor implementation
+ const css::uno::Reference< css::graphic::XPrimitive2D > xReference(aSequence[0]);
+ auto pUnoPrimitive = static_cast< const drawinglayer::primitive2d::UnoPrimitive2D* >(xReference.get());
+ if (pUnoPrimitive)
+ {
+ const MetafileAccessor* pMetafileAccessor = dynamic_cast< const MetafileAccessor* >(pUnoPrimitive->getBasePrimitive2D().get());
+
+ if (pMetafileAccessor)
+ {
+ // it is a MetafileAccessor implementation, get Metafile
+ pMetafileAccessor->accessMetafile(const_cast< ImpGraphic* >(this)->maMetaFile);
+ }
+ }
+ }
+ }
+
+ if (GraphicType::Bitmap == meType && !maMetaFile.GetActionSize())
+ {
+ // #i119735#
+ // Use the local maMetaFile as container for a metafile-representation
+ // of the bitmap graphic. This will be done only once, thus be buffered.
+ // I checked all usages of maMetaFile, it is only used when type is not
+ // GraphicType::Bitmap. In operator= it will get copied, thus buffering will
+ // survive copying (change this if not wanted)
+ ImpGraphic* pThat = const_cast< ImpGraphic* >(this);
+
+ if(maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // use maBitmapEx as local buffer for rendered svg
+ pThat->maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ // #123983# directly create a metafile with the same PrefSize and PrefMapMode
+ // the bitmap has, this will be an always correct metafile
+ if(maBitmapEx.IsAlpha())
+ {
+ pThat->maMetaFile.AddAction(new MetaBmpExScaleAction(Point(), maBitmapEx.GetPrefSize(), maBitmapEx));
+ }
+ else
+ {
+ pThat->maMetaFile.AddAction(new MetaBmpScaleAction(Point(), maBitmapEx.GetPrefSize(), maBitmapEx.GetBitmap()));
+ }
+
+ pThat->maMetaFile.Stop();
+ pThat->maMetaFile.WindStart();
+ pThat->maMetaFile.SetPrefSize(maBitmapEx.GetPrefSize());
+ pThat->maMetaFile.SetPrefMapMode(maBitmapEx.GetPrefMapMode());
+ }
+
+ return maMetaFile;
+}
+
+Size ImpGraphic::getSizePixel() const
+{
+ Size aSize;
+
+ if (isSwappedOut())
+ aSize = maSwapInfo.maSizePixel;
+ else
+ aSize = getBitmapEx(GraphicConversionParameters()).GetSizePixel();
+
+ return aSize;
+}
+
+Size ImpGraphic::getPrefSize() const
+{
+ Size aSize;
+
+ if (isSwappedOut())
+ {
+ aSize = maSwapInfo.maPrefSize;
+ }
+ else
+ {
+ switch (meType)
+ {
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ if (!maExPrefSize.getWidth() || !maExPrefSize.getHeight())
+ {
+ // svg not yet buffered in maBitmapEx, return size derived from range
+ const basegfx::B2DRange& rRange = maVectorGraphicData->getRange();
+
+#ifdef MACOSX
+ // tdf#157680 scale down estimated size of embedded PDF
+ // For some unknown reason, the embedded PDF sizes
+ // are 20x larger than expected. This only occurs on
+ // macOS so possibly there is some special conversion
+ // from MapUnit::MapPoint to MapUnit::MapTwip elsewhere
+ // in the code.
+ if (maVectorGraphicData->getType() == VectorGraphicDataType::Pdf)
+ aSize = Size(basegfx::fround(rRange.getWidth() / 20.0f), basegfx::fround(rRange.getHeight() / 20.0f));
+ else
+#endif
+ aSize = Size(basegfx::fround(rRange.getWidth()), basegfx::fround(rRange.getHeight()));
+ }
+ else
+ {
+ aSize = maExPrefSize;
+ }
+ }
+ else
+ {
+ aSize = maBitmapEx.GetPrefSize();
+
+ if( !aSize.Width() || !aSize.Height() )
+ {
+ aSize = maBitmapEx.GetSizePixel();
+ }
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ aSize = maMetaFile.GetPrefSize();
+ }
+ break;
+
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+ }
+ }
+
+ return aSize;
+}
+
+void ImpGraphic::setValuesForPrefSize(const Size& rPrefSize)
+{
+ switch (meType)
+ {
+ case GraphicType::Bitmap:
+ {
+ // used when importing a writer FlyFrame with SVG as graphic, added conversion
+ // to allow setting the PrefSize at the BitmapEx to hold it
+ if (maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ maExPrefSize = rPrefSize;
+ }
+
+ // #108077# Push through pref size to animation object,
+ // will be lost on copy otherwise
+ if (mpAnimation)
+ {
+ const_cast< BitmapEx& >(mpAnimation->GetBitmapEx()).SetPrefSize(rPrefSize);
+ }
+
+ if (!maExPrefSize.getWidth() || !maExPrefSize.getHeight())
+ {
+ maBitmapEx.SetPrefSize(rPrefSize);
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ if (isSupportedGraphic())
+ maMetaFile.SetPrefSize(rPrefSize);
+ }
+ break;
+
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+ }
+}
+
+void ImpGraphic::setPrefSize(const Size& rPrefSize)
+{
+ ensureAvailable();
+ setValuesForPrefSize(rPrefSize);
+}
+
+MapMode ImpGraphic::getPrefMapMode() const
+{
+ MapMode aMapMode;
+
+ if (isSwappedOut())
+ {
+ aMapMode = maSwapInfo.maPrefMapMode;
+ }
+ else
+ {
+ switch (meType)
+ {
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // svg not yet buffered in maBitmapEx, return default PrefMapMode
+ aMapMode = MapMode(MapUnit::Map100thMM);
+ }
+ else
+ {
+ const Size aSize(maBitmapEx.GetPrefSize());
+
+ if (aSize.Width() && aSize.Height())
+ aMapMode = maBitmapEx.GetPrefMapMode();
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ return maMetaFile.GetPrefMapMode();
+ }
+ break;
+
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+ }
+ }
+
+ return aMapMode;
+}
+
+void ImpGraphic::setValuesForPrefMapMod(const MapMode& rPrefMapMode)
+{
+ switch (meType)
+ {
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData)
+ {
+ // ignore for Vector Graphic Data. If this is really used (except the grfcache)
+ // it can be extended by using maBitmapEx as buffer for getVectorGraphicReplacement()
+ }
+ else
+ {
+ // #108077# Push through pref mapmode to animation object,
+ // will be lost on copy otherwise
+ if (mpAnimation)
+ {
+ const_cast<BitmapEx&>(mpAnimation->GetBitmapEx()).SetPrefMapMode(rPrefMapMode);
+ }
+
+ maBitmapEx.SetPrefMapMode(rPrefMapMode);
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ maMetaFile.SetPrefMapMode(rPrefMapMode);
+ }
+ break;
+
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+ }
+}
+
+void ImpGraphic::setPrefMapMode(const MapMode& rPrefMapMode)
+{
+ ensureAvailable();
+ setValuesForPrefMapMod(rPrefMapMode);
+}
+
+sal_uLong ImpGraphic::getSizeBytes() const
+{
+ if (mnSizeBytes > 0)
+ return mnSizeBytes;
+
+ if (mbPrepared)
+ ensureAvailable();
+
+ switch (meType)
+ {
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData)
+ {
+ std::pair<VectorGraphicData::State, size_t> aPair(maVectorGraphicData->getSizeBytes());
+ if (VectorGraphicData::State::UNPARSED == aPair.first)
+ {
+ return aPair.second; // don't cache it until Vector Graphic Data is parsed
+ }
+ mnSizeBytes = aPair.second;
+ }
+ else
+ {
+ mnSizeBytes = mpAnimation ? mpAnimation->GetSizeBytes() : maBitmapEx.GetSizeBytes();
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ mnSizeBytes = maMetaFile.GetSizeBytes();
+ }
+ break;
+
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+ }
+
+ return mnSizeBytes;
+}
+
+void ImpGraphic::draw(OutputDevice& rOutDev, const Point& rDestPt) const
+{
+ ensureAvailable();
+
+ if (isSwappedOut())
+ return;
+
+ switch (meType)
+ {
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // use maBitmapEx as local buffer for rendered svg
+ const_cast<ImpGraphic*>(this)->maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ if (mpAnimation)
+ {
+ mpAnimation->Draw(rOutDev, rDestPt);
+ }
+ else
+ {
+ maBitmapEx.Draw(&rOutDev, rDestPt);
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ draw(rOutDev, rDestPt, maMetaFile.GetPrefSize());
+ }
+ break;
+
+ case GraphicType::Default:
+ case GraphicType::NONE:
+ break;
+ }
+}
+
+void ImpGraphic::draw(OutputDevice& rOutDev,
+ const Point& rDestPt, const Size& rDestSize) const
+{
+ ensureAvailable();
+
+ if (isSwappedOut())
+ return;
+
+ switch (meType)
+ {
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // use maBitmapEx as local buffer for rendered svg
+ const_cast<ImpGraphic*>(this)->maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ if (mpAnimation)
+ {
+ mpAnimation->Draw(rOutDev, rDestPt, rDestSize);
+ }
+ else
+ {
+ maBitmapEx.Draw(&rOutDev, rDestPt, rDestSize);
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ const_cast<ImpGraphic*>(this)->maMetaFile.WindStart();
+ const_cast<ImpGraphic*>(this)->maMetaFile.Play(rOutDev, rDestPt, rDestSize);
+ const_cast<ImpGraphic*>(this)->maMetaFile.WindStart();
+ }
+ break;
+
+ case GraphicType::Default:
+ case GraphicType::NONE:
+ break;
+ }
+}
+
+void ImpGraphic::startAnimation(OutputDevice& rOutDev, const Point& rDestPt,
+ const Size& rDestSize, tools::Long nRendererId,
+ OutputDevice* pFirstFrameOutDev )
+{
+ ensureAvailable();
+
+ if( isSupportedGraphic() && !isSwappedOut() && mpAnimation )
+ mpAnimation->Start(rOutDev, rDestPt, rDestSize, nRendererId, pFirstFrameOutDev);
+}
+
+void ImpGraphic::stopAnimation( const OutputDevice* pOutDev, tools::Long nRendererId )
+{
+ ensureAvailable();
+
+ if( isSupportedGraphic() && !isSwappedOut() && mpAnimation )
+ mpAnimation->Stop( pOutDev, nRendererId );
+}
+
+void ImpGraphic::setAnimationNotifyHdl( const Link<Animation*,void>& rLink )
+{
+ ensureAvailable();
+
+ if( mpAnimation )
+ mpAnimation->SetNotifyHdl( rLink );
+}
+
+Link<Animation*,void> ImpGraphic::getAnimationNotifyHdl() const
+{
+ Link<Animation*,void> aLink;
+
+ ensureAvailable();
+
+ if( mpAnimation )
+ aLink = mpAnimation->GetNotifyHdl();
+
+ return aLink;
+}
+
+sal_uInt32 ImpGraphic::getAnimationLoopCount() const
+{
+ if (mbSwapOut)
+ return maSwapInfo.mnAnimationLoopCount;
+
+ return mpAnimation ? mpAnimation->GetLoopCount() : 0;
+}
+
+void ImpGraphic::setContext( const std::shared_ptr<GraphicReader>& pReader )
+{
+ mpContext = pReader;
+ mbDummyContext = false;
+}
+
+bool ImpGraphic::swapInContent(SvStream& rStream)
+{
+ bool bRet = false;
+
+ sal_uInt32 nId;
+ sal_Int32 nType;
+ sal_Int32 nLength;
+
+ rStream.ReadUInt32(nId);
+
+ // check version
+ if (SWAP_FORMAT_ID != nId)
+ {
+ SAL_WARN("vcl", "Incompatible swap file!");
+ return false;
+ }
+
+ rStream.ReadInt32(nType);
+ rStream.ReadInt32(nLength);
+
+ meType = static_cast<GraphicType>(nType);
+
+ if (meType == GraphicType::NONE || meType == GraphicType::Default)
+ {
+ return true;
+ }
+ else
+ {
+ bRet = swapInGraphic(rStream);
+ }
+
+ return bRet;
+}
+
+bool ImpGraphic::swapOutGraphic(SvStream& rStream)
+{
+ if (rStream.GetError())
+ return false;
+
+ ensureAvailable();
+
+ if (isSwappedOut())
+ {
+ rStream.SetError(SVSTREAM_GENERALERROR);
+ return false;
+ }
+
+ switch (meType)
+ {
+ case GraphicType::GdiMetafile:
+ {
+ if(!rStream.GetError())
+ {
+ SvmWriter aWriter(rStream);
+ aWriter.Write(maMetaFile);
+ }
+ }
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData)
+ {
+ rStream.WriteInt32(sal_Int32(GraphicContentType::Vector));
+ // stream out Vector Graphic defining data (length, byte array and evtl. path)
+ // this is used e.g. in swapping out graphic data and in transporting it over UNO API
+ // as sequence of bytes, but AFAIK not written anywhere to any kind of file, so it should be
+ // no problem to extend it; only used at runtime
+ switch (maVectorGraphicData->getType())
+ {
+ case VectorGraphicDataType::Wmf:
+ {
+ rStream.WriteUInt32(constWmfMagic);
+ break;
+ }
+ case VectorGraphicDataType::Emf:
+ {
+ rStream.WriteUInt32(constEmfMagic);
+ break;
+ }
+ case VectorGraphicDataType::Svg:
+ {
+ rStream.WriteUInt32(constSvgMagic);
+ break;
+ }
+ case VectorGraphicDataType::Pdf:
+ {
+ rStream.WriteUInt32(constPdfMagic);
+ break;
+ }
+ }
+
+ rStream.WriteUInt32(maVectorGraphicData->getBinaryDataContainer().getSize());
+ maVectorGraphicData->getBinaryDataContainer().writeToStream(rStream);
+ }
+ else if (mpAnimation)
+ {
+ rStream.WriteInt32(sal_Int32(GraphicContentType::Animation));
+ WriteAnimation(rStream, *mpAnimation);
+ }
+ else
+ {
+ rStream.WriteInt32(sal_Int32(GraphicContentType::Bitmap));
+ WriteDIBBitmapEx(maBitmapEx, rStream);
+ }
+ }
+ break;
+
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+ }
+
+ if (mpGfxLink)
+ mpGfxLink->getDataContainer().swapOut();
+
+ return true;
+}
+
+bool ImpGraphic::swapOutContent(SvStream& rStream)
+{
+ ensureAvailable();
+
+ bool bRet = false;
+
+ if (meType == GraphicType::NONE || meType == GraphicType::Default || isSwappedOut())
+ return false;
+
+ sal_uLong nDataFieldPos;
+
+ // Write the SWAP ID
+ rStream.WriteUInt32(SWAP_FORMAT_ID);
+
+ rStream.WriteInt32(static_cast<sal_Int32>(meType));
+
+ // data size is updated later
+ nDataFieldPos = rStream.Tell();
+ rStream.WriteInt32(0);
+
+ // write data block
+ const sal_uInt64 nDataStart = rStream.Tell();
+
+ swapOutGraphic(rStream);
+
+ if (!rStream.GetError())
+ {
+ // Write the written length th the header
+ const sal_uInt64 nCurrentPosition = rStream.Tell();
+ rStream.Seek(nDataFieldPos);
+ rStream.WriteInt32(nCurrentPosition - nDataStart);
+ rStream.Seek(nCurrentPosition);
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool ImpGraphic::swapOut()
+{
+ if (isSwappedOut())
+ return false;
+
+ bool bResult = false;
+
+ sal_Int64 nByteSize = getSizeBytes();
+
+ // We have GfxLink so we have the source available
+ if (mpGfxLink && mpGfxLink->IsNative())
+ {
+ createSwapInfo();
+
+ clearGraphics();
+
+ // reset the swap file
+ mpSwapFile.reset();
+
+ mpGfxLink->getDataContainer().swapOut();
+
+ // mark as swapped out
+ mbSwapOut = true;
+
+ bResult = true;
+ }
+ else
+ {
+ // Create a swap file
+ auto pSwapFile = o3tl::make_shared<ImpSwapFile>(getOriginURL());
+
+ // Open a stream to write the swap file to
+ {
+ SvStream* pOutputStream = pSwapFile->getStream();
+
+ if (!pOutputStream)
+ return false;
+
+ // Write to stream
+ pOutputStream->SetVersion(SOFFICE_FILEFORMAT_50);
+ pOutputStream->SetCompressMode(SvStreamCompressFlags::NATIVE);
+ pOutputStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE);
+
+ if (!pOutputStream->GetError() && swapOutContent(*pOutputStream))
+ {
+ pOutputStream->FlushBuffer();
+ bResult = !pOutputStream->GetError();
+ }
+ }
+
+ // Check if writing was successful
+ if (bResult)
+ {
+ // We have swapped out, so can clean memory and prepare swap info
+ createSwapInfo();
+ clearGraphics();
+
+ mpSwapFile = std::move(pSwapFile);
+ mbSwapOut = true;
+ }
+ }
+
+ if (bResult)
+ {
+ // Signal to manager that we have swapped out
+ vcl::graphic::Manager::get().swappedOut(this, nByteSize);
+ }
+
+ return bResult;
+}
+
+bool ImpGraphic::ensureAvailable() const
+{
+ auto pThis = const_cast<ImpGraphic*>(this);
+
+ bool bResult = true;
+
+ if (isSwappedOut())
+ bResult = pThis->swapIn();
+
+ pThis->maLastUsed = std::chrono::high_resolution_clock::now();
+ return bResult;
+}
+
+void ImpGraphic::updateFromLoadedGraphic(const ImpGraphic* pGraphic)
+{
+ if (mbPrepared)
+ {
+ GraphicExternalLink aLink = maGraphicExternalLink;
+ Size aPrefSize = maSwapInfo.maPrefSize;
+ MapMode aPrefMapMode = maSwapInfo.maPrefMapMode;
+ *this = *pGraphic;
+ if (aPrefSize.getWidth() && aPrefSize.getHeight() && aPrefMapMode == getPrefMapMode())
+ {
+ // Use custom preferred size if it was set when the graphic was still unloaded.
+ // Only set the size in case the unloaded and loaded unit matches.
+ setPrefSize(aPrefSize);
+ }
+ maGraphicExternalLink = aLink;
+ }
+ else
+ {
+ // Move over only graphic content
+ mpAnimation.reset();
+ if (pGraphic->mpAnimation)
+ {
+ mpAnimation = std::make_unique<Animation>(*pGraphic->mpAnimation);
+ maBitmapEx = mpAnimation->GetBitmapEx();
+ }
+ else
+ {
+ maBitmapEx = pGraphic->maBitmapEx;
+ }
+
+ maMetaFile = pGraphic->maMetaFile;
+ maVectorGraphicData = pGraphic->maVectorGraphicData;
+
+ // Set to 0, to force recalculation
+ mnSizeBytes = 0;
+ mnChecksum = 0;
+
+ restoreFromSwapInfo();
+
+ mbSwapOut = false;
+ }
+}
+
+void ImpGraphic::dumpState(rtl::OStringBuffer &rState)
+{
+ if (meType == GraphicType::NONE && mnSizeBytes == 0)
+ return; // uninteresting.
+
+ rState.append("\n\t");
+
+ if (mbSwapOut)
+ rState.append("swapped\t");
+ else
+ rState.append("loaded\t");
+
+ rState.append(static_cast<sal_Int32>(meType));
+ rState.append("\tsize:\t");
+ rState.append(static_cast<sal_Int64>(mnSizeBytes));
+ rState.append("\tgfxl:\t");
+ rState.append(static_cast<sal_Int64>(mpGfxLink ? mpGfxLink->getSizeBytes() : -1));
+ rState.append("\t");
+ rState.append(static_cast<sal_Int32>(maSwapInfo.maSizePixel.Width()));
+ rState.append("x");
+ rState.append(static_cast<sal_Int32>(maSwapInfo.maSizePixel.Height()));
+ rState.append("\t");
+ rState.append(static_cast<sal_Int32>(maExPrefSize.Width()));
+ rState.append("x");
+ rState.append(static_cast<sal_Int32>(maExPrefSize.Height()));
+}
+
+void ImpGraphic::restoreFromSwapInfo()
+{
+ setValuesForPrefMapMod(maSwapInfo.maPrefMapMode);
+ setValuesForPrefSize(maSwapInfo.maPrefSize);
+
+ if (maVectorGraphicData)
+ {
+ maVectorGraphicData->setPageIndex(maSwapInfo.mnPageIndex);
+ }
+}
+
+namespace
+{
+
+std::optional<VectorGraphicDataType> lclConvertToVectorGraphicType(GfxLink const & rLink)
+{
+ switch(rLink.GetType())
+ {
+ case GfxLinkType::NativePdf:
+ return VectorGraphicDataType::Pdf;
+
+ case GfxLinkType::NativeWmf:
+ if (rLink.IsEMF())
+ return VectorGraphicDataType::Emf;
+ else
+ return VectorGraphicDataType::Wmf;
+
+ case GfxLinkType::NativeSvg:
+ return VectorGraphicDataType::Svg;
+
+ default:
+ break;
+ }
+ return std::optional<VectorGraphicDataType>();
+}
+
+} // end namespace
+
+bool ImpGraphic::swapIn()
+{
+ if (!isSwappedOut())
+ return false;
+
+ bool bReturn = false;
+
+ if (mbPrepared)
+ {
+ Graphic aGraphic;
+ if (!mpGfxLink->LoadNative(aGraphic))
+ return false;
+
+ updateFromLoadedGraphic(aGraphic.ImplGetImpGraphic());
+
+ maLastUsed = std::chrono::high_resolution_clock::now();
+ bReturn = true;
+ }
+ else if (mpGfxLink && mpGfxLink->IsNative())
+ {
+ std::optional<VectorGraphicDataType> oType = lclConvertToVectorGraphicType(*mpGfxLink);
+ if (oType)
+ {
+ maVectorGraphicData = vcl::loadVectorGraphic(mpGfxLink->getDataContainer(), *oType);
+
+ // Set to 0, to force recalculation
+ mnSizeBytes = 0;
+ mnChecksum = 0;
+
+ restoreFromSwapInfo();
+
+ mbSwapOut = false;
+ }
+ else
+ {
+ Graphic aGraphic;
+ if (!mpGfxLink->LoadNative(aGraphic))
+ return false;
+
+ ImpGraphic* pImpGraphic = aGraphic.ImplGetImpGraphic();
+ if (meType != pImpGraphic->meType)
+ return false;
+
+ updateFromLoadedGraphic(pImpGraphic);
+ }
+
+ maLastUsed = std::chrono::high_resolution_clock::now();
+ bReturn = true;
+ }
+ else
+ {
+ SvStream* pStream = nullptr;
+
+ if (mpSwapFile)
+ pStream = mpSwapFile->getStream();
+
+ if (pStream)
+ {
+ pStream->SetVersion(SOFFICE_FILEFORMAT_50);
+ pStream->SetCompressMode(SvStreamCompressFlags::NATIVE);
+ pStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE);
+ pStream->Seek(STREAM_SEEK_TO_BEGIN);
+
+ bReturn = swapInFromStream(*pStream);
+
+ restoreFromSwapInfo();
+
+ setOriginURL(mpSwapFile->getOriginURL());
+
+ mpSwapFile.reset();
+ }
+ }
+
+ if (bReturn)
+ {
+ vcl::graphic::Manager::get().swappedIn(this, getSizeBytes());
+ }
+
+ return bReturn;
+}
+
+bool ImpGraphic::swapInFromStream(SvStream& rStream)
+{
+ bool bRet = false;
+
+ if (rStream.GetError())
+ return false;
+
+ clearGraphics();
+ mnSizeBytes = 0;
+ mnChecksum = 0;
+
+ bRet = swapInContent(rStream);
+
+ if (!bRet)
+ {
+ //throw away swapfile, etc.
+ clear();
+ }
+
+ mbSwapOut = false;
+
+ return bRet;
+}
+
+bool ImpGraphic::swapInGraphic(SvStream& rStream)
+{
+ bool bReturn = false;
+
+ if (rStream.GetError())
+ return bReturn;
+
+ if (meType == GraphicType::Bitmap)
+ {
+ sal_Int32 nContentType = -1;
+ rStream.ReadInt32(nContentType);
+ if (nContentType < 0)
+ return false;
+
+ auto eContentType = static_cast<GraphicContentType>(nContentType);
+
+ switch (eContentType)
+ {
+ case GraphicContentType::Bitmap:
+ {
+ BitmapEx aBitmapEx;
+ ReadDIBBitmapEx(aBitmapEx, rStream);
+ if (!rStream.GetError())
+ {
+ maBitmapEx = aBitmapEx;
+ bReturn = true;
+ }
+ }
+ break;
+
+ case GraphicContentType::Animation:
+ {
+ auto pAnimation = std::make_unique<Animation>();
+ ReadAnimation(rStream, *pAnimation);
+ if (!rStream.GetError())
+ {
+ mpAnimation = std::move(pAnimation);
+ maBitmapEx = mpAnimation->GetBitmapEx();
+ bReturn = true;
+ }
+ }
+ break;
+
+ case GraphicContentType::Vector:
+ {
+ // try to stream in Svg defining data (length, byte array and evtl. path)
+ // See below (operator<<) for more information
+ sal_uInt32 nMagic;
+ rStream.ReadUInt32(nMagic);
+
+ if (constSvgMagic == nMagic || constWmfMagic == nMagic || constEmfMagic == nMagic || constPdfMagic == nMagic)
+ {
+ sal_uInt32 nVectorGraphicDataSize(0);
+ rStream.ReadUInt32(nVectorGraphicDataSize);
+
+ if (nVectorGraphicDataSize)
+ {
+ BinaryDataContainer aDataContainer(rStream, nVectorGraphicDataSize);
+
+ if (rStream.GetError())
+ return false;
+
+ VectorGraphicDataType aDataType;
+
+ switch (nMagic)
+ {
+ case constSvgMagic:
+ aDataType = VectorGraphicDataType::Svg;
+ break;
+ case constWmfMagic:
+ aDataType = VectorGraphicDataType::Wmf;
+ break;
+ case constEmfMagic:
+ aDataType = VectorGraphicDataType::Emf;
+ break;
+ case constPdfMagic:
+ aDataType = VectorGraphicDataType::Pdf;
+ break;
+ default:
+ return false;
+ }
+
+ auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aDataContainer, aDataType);
+
+ if (!rStream.GetError())
+ {
+ maVectorGraphicData = aVectorGraphicDataPtr;
+ bReturn = true;
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ else if (meType == GraphicType::GdiMetafile)
+ {
+ GDIMetaFile aMetaFile;
+ SvmReader aReader(rStream);
+ aReader.Read(aMetaFile);
+ if (!rStream.GetError())
+ {
+ maMetaFile = aMetaFile;
+ bReturn = true;
+ }
+ }
+ return bReturn;
+}
+
+void ImpGraphic::setGfxLink(const std::shared_ptr<GfxLink>& rGfxLink)
+{
+ ensureAvailable();
+
+ mpGfxLink = rGfxLink;
+}
+
+const std::shared_ptr<GfxLink> & ImpGraphic::getSharedGfxLink() const
+{
+ return mpGfxLink;
+}
+
+GfxLink ImpGraphic::getGfxLink() const
+{
+ ensureAvailable();
+
+ return( mpGfxLink ? *mpGfxLink : GfxLink() );
+}
+
+bool ImpGraphic::isGfxLink() const
+{
+ return ( bool(mpGfxLink) );
+}
+
+BitmapChecksum ImpGraphic::getChecksum() const
+{
+ if (mnChecksum != 0)
+ return mnChecksum;
+
+ ensureAvailable();
+
+ switch (meType)
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if (maVectorGraphicData)
+ mnChecksum = maVectorGraphicData->GetChecksum();
+ else if (mpAnimation)
+ mnChecksum = mpAnimation->GetChecksum();
+ else
+ mnChecksum = maBitmapEx.GetChecksum();
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ mnChecksum = SvmWriter::GetChecksum(maMetaFile);
+ }
+ break;
+ }
+ return mnChecksum;
+}
+
+sal_Int32 ImpGraphic::getPageNumber() const
+{
+ if (isSwappedOut())
+ return maSwapInfo.mnPageIndex;
+
+ if (maVectorGraphicData)
+ return maVectorGraphicData->getPageIndex();
+ return -1;
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/jobset.cxx b/vcl/source/gdi/jobset.cxx
new file mode 100644
index 0000000000..19fa712ea6
--- /dev/null
+++ b/vcl/source/gdi/jobset.cxx
@@ -0,0 +1,421 @@
+/* -*- 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 <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <tools/solar.h>
+#include <tools/stream.hxx>
+#include <vcl/jobset.hxx>
+#include <jobset.h>
+#include <memory>
+
+#define JOBSET_FILE364_SYSTEM (sal_uInt16(0xFFFF))
+#define JOBSET_FILE605_SYSTEM (sal_uInt16(0xFFFE))
+
+namespace {
+
+// used only for compatibility with older versions,
+// printer/driver name are truncated if too long,
+// device name and port name are completely unused
+struct ImplOldJobSetupData
+{
+ char cPrinterName[64];
+ char cDeviceName[32];
+ char cPortName[32];
+ char cDriverName[32];
+};
+
+struct Impl364JobSetupData
+{
+ SVBT16 nSize;
+ SVBT16 nSystem;
+ SVBT32 nDriverDataLen;
+ SVBT16 nOrientation;
+ SVBT16 nPaperBin;
+ SVBT16 nPaperFormat;
+ SVBT32 nPaperWidth;
+ SVBT32 nPaperHeight;
+};
+
+}
+
+ImplJobSetup::ImplJobSetup()
+{
+ mnSystem = 0;
+ meOrientation = Orientation::Portrait;
+ meDuplexMode = DuplexMode::Unknown;
+ mnPaperBin = 0;
+ mePaperFormat = PAPER_USER;
+ mnPaperWidth = 0;
+ mnPaperHeight = 0;
+ mnDriverDataLen = 0;
+ mbPapersizeFromSetup = false;
+ meSetupMode = PrinterSetupMode::DocumentGlobal;
+}
+
+ImplJobSetup::ImplJobSetup( const ImplJobSetup& rJobSetup ) :
+ mnSystem( rJobSetup.GetSystem() ),
+ maPrinterName( rJobSetup.GetPrinterName() ),
+ maDriver( rJobSetup.GetDriver() ),
+ meOrientation( rJobSetup.GetOrientation() ),
+ meDuplexMode( rJobSetup.GetDuplexMode() ),
+ mnPaperBin( rJobSetup.GetPaperBin() ),
+ mePaperFormat( rJobSetup.GetPaperFormat() ),
+ mnPaperWidth( rJobSetup.GetPaperWidth() ),
+ mnPaperHeight( rJobSetup.GetPaperHeight() ),
+ mnDriverDataLen( rJobSetup.GetDriverDataLen() ),
+ mpDriverData(),
+ mbPapersizeFromSetup( rJobSetup.GetPapersizeFromSetup() ),
+ meSetupMode( rJobSetup.GetPrinterSetupMode() ),
+ maValueMap( rJobSetup.GetValueMap() )
+ {
+ if ( rJobSetup.GetDriverData() )
+ {
+ mpDriverData.reset( new sal_uInt8[mnDriverDataLen] );
+ memcpy( mpDriverData.get(), rJobSetup.GetDriverData(), mnDriverDataLen );
+ }
+ else
+ mpDriverData = nullptr;
+}
+
+ImplJobSetup::~ImplJobSetup()
+{
+}
+
+void ImplJobSetup::SetSystem(sal_uInt16 nSystem)
+{
+ mnSystem = nSystem;
+}
+
+void ImplJobSetup::SetPrinterName(const OUString& rPrinterName)
+{
+ maPrinterName = rPrinterName;
+}
+
+void ImplJobSetup::SetDriver(const OUString& rDriver)
+{
+ maDriver = rDriver;
+}
+
+void ImplJobSetup::SetOrientation(Orientation eOrientation)
+{
+ meOrientation = eOrientation;
+}
+
+void ImplJobSetup::SetDuplexMode(DuplexMode eDuplexMode)
+{
+ meDuplexMode = eDuplexMode;
+}
+
+void ImplJobSetup::SetPaperBin(sal_uInt16 nPaperBin)
+{
+ mnPaperBin = nPaperBin;
+}
+
+void ImplJobSetup::SetPaperFormat(Paper ePaperFormat)
+{
+ mePaperFormat = ePaperFormat;
+}
+
+void ImplJobSetup::SetPaperWidth(tools::Long nPaperWidth)
+{
+ mnPaperWidth = nPaperWidth;
+}
+
+void ImplJobSetup::SetPaperHeight(tools::Long nPaperHeight)
+{
+ mnPaperHeight = nPaperHeight;
+}
+
+void ImplJobSetup::SetDriverData(std::unique_ptr<sal_uInt8[]> pDriverData, sal_uInt32 nDriverDataLen)
+{
+ mpDriverData = std::move(pDriverData);
+ mnDriverDataLen = nDriverDataLen;
+}
+
+void ImplJobSetup::SetPapersizeFromSetup(bool bPapersizeFromSetup)
+{
+ mbPapersizeFromSetup = bPapersizeFromSetup;
+}
+
+void ImplJobSetup::SetPrinterSetupMode(PrinterSetupMode eMode)
+{
+ meSetupMode = eMode;
+}
+
+void ImplJobSetup::SetValueMap( const OUString& rKey, const OUString& rValue )
+{
+ maValueMap [ rKey ] = rValue;
+}
+
+JobSetup& JobSetup::operator=( const JobSetup& ) = default;
+
+JobSetup& JobSetup::operator=( JobSetup&& ) = default;
+
+bool ImplJobSetup::operator==( const ImplJobSetup& rImplJobSetup ) const
+{
+ return mnSystem == rImplJobSetup.mnSystem &&
+ maPrinterName == rImplJobSetup.maPrinterName &&
+ maDriver == rImplJobSetup.maDriver &&
+ meOrientation == rImplJobSetup.meOrientation &&
+ meDuplexMode == rImplJobSetup.meDuplexMode &&
+ mnPaperBin == rImplJobSetup.mnPaperBin &&
+ mePaperFormat == rImplJobSetup.mePaperFormat &&
+ mnPaperWidth == rImplJobSetup.mnPaperWidth &&
+ mnPaperHeight == rImplJobSetup.mnPaperHeight &&
+ mbPapersizeFromSetup == rImplJobSetup.mbPapersizeFromSetup &&
+ mnDriverDataLen == rImplJobSetup.mnDriverDataLen &&
+ maValueMap == rImplJobSetup.maValueMap &&
+ memcmp( mpDriverData.get(),
+ rImplJobSetup.mpDriverData.get(),
+ std::min(mnDriverDataLen, rImplJobSetup.mnDriverDataLen)) == 0;
+}
+
+namespace
+{
+ JobSetup::ImplType& GetGlobalDefault()
+ {
+ static JobSetup::ImplType gDefault;
+ return gDefault;
+ }
+}
+
+JobSetup::JobSetup() : mpData(GetGlobalDefault())
+{
+}
+
+JobSetup::JobSetup( const JobSetup& ) = default;
+
+JobSetup::~JobSetup() = default;
+
+bool JobSetup::operator==( const JobSetup& rJobSetup ) const
+{
+ return mpData == rJobSetup.mpData;
+}
+
+const ImplJobSetup& JobSetup::ImplGetConstData() const
+{
+ return *mpData;
+}
+
+ImplJobSetup& JobSetup::ImplGetData()
+{
+ return *mpData;
+}
+
+OUString const & JobSetup::GetPrinterName() const
+{
+ return mpData->GetPrinterName();
+}
+
+bool JobSetup::IsDefault() const
+{
+ return mpData.same_object(GetGlobalDefault());
+}
+
+SvStream& ReadJobSetup( SvStream& rIStream, JobSetup& rJobSetup )
+{
+ {
+ sal_uInt16 nLen = 0;
+ rIStream.ReadUInt16( nLen );
+ if (nLen <= 4)
+ return rIStream;
+
+ sal_uInt16 nSystem = 0;
+ rIStream.ReadUInt16( nSystem );
+ size_t nRead = nLen - sizeof(nLen) - sizeof(nSystem);
+ if (nRead > rIStream.remainingSize())
+ {
+ SAL_WARN("vcl", "Parsing error: " << rIStream.remainingSize() <<
+ " max possible entries, but " << nRead << " claimed, truncating");
+ return rIStream;
+ }
+ sal_uInt64 const nFirstPos = rIStream.Tell();
+ std::unique_ptr<char[]> pTempBuf(new char[nRead]);
+ nRead = rIStream.ReadBytes(pTempBuf.get(), nRead);
+ if (nRead >= sizeof(ImplOldJobSetupData))
+ {
+ ImplOldJobSetupData* pData = reinterpret_cast<ImplOldJobSetupData*>(pTempBuf.get());
+
+ rtl_TextEncoding aStreamEncoding = RTL_TEXTENCODING_UTF8;
+ if( nSystem == JOBSET_FILE364_SYSTEM )
+ aStreamEncoding = rIStream.GetStreamCharSet();
+
+ ImplJobSetup& rJobData = rJobSetup.ImplGetData();
+
+ // use (potentially truncated) printer/driver name from ImplOldJobSetupData as fallback,
+ // gets overwritten below if PRINTER_NAME/DRIVER_NAME keys are set
+ pData->cPrinterName[std::size(pData->cPrinterName) - 1] = 0;
+ rJobData.SetPrinterName( OStringToOUString(pData->cPrinterName, aStreamEncoding) );
+ pData->cDriverName[std::size(pData->cDriverName) - 1] = 0;
+ rJobData.SetDriver( OStringToOUString(pData->cDriverName, aStreamEncoding) );
+
+ // Are these our new JobSetup files?
+ if ( nSystem == JOBSET_FILE364_SYSTEM ||
+ nSystem == JOBSET_FILE605_SYSTEM )
+ {
+ if (nRead < sizeof(ImplOldJobSetupData) + sizeof(Impl364JobSetupData))
+ {
+ SAL_WARN("vcl", "Parsing error: " << sizeof(ImplOldJobSetupData) + sizeof(Impl364JobSetupData) <<
+ " required, but " << nRead << " available");
+ return rIStream;
+ }
+
+ Impl364JobSetupData* pOldJobData = reinterpret_cast<Impl364JobSetupData*>(pTempBuf.get() + sizeof( ImplOldJobSetupData ));
+ sal_uInt16 nOldJobDataSize = SVBT16ToUInt16( pOldJobData->nSize );
+ rJobData.SetSystem( SVBT16ToUInt16( pOldJobData->nSystem ) );
+ const sal_uInt32 nDriverDataLen = SVBT32ToUInt32( pOldJobData->nDriverDataLen );
+ rJobData.SetOrientation( static_cast<Orientation>(SVBT16ToUInt16( pOldJobData->nOrientation )) );
+ rJobData.SetDuplexMode( DuplexMode::Unknown );
+ rJobData.SetPaperBin( SVBT16ToUInt16( pOldJobData->nPaperBin ) );
+ sal_uInt16 nPaperFormat = SVBT16ToUInt16(pOldJobData->nPaperFormat);
+ if (nPaperFormat < NUM_PAPER_ENTRIES)
+ rJobData.SetPaperFormat(static_cast<Paper>(nPaperFormat));
+ else
+ {
+ SAL_WARN("vcl", "Parsing error: " << nPaperFormat <<
+ " paper format, but legal max is " << NUM_PAPER_ENTRIES);
+ }
+ rJobData.SetPaperWidth( static_cast<tools::Long>(SVBT32ToUInt32( pOldJobData->nPaperWidth )) );
+ rJobData.SetPaperHeight( static_cast<tools::Long>(SVBT32ToUInt32( pOldJobData->nPaperHeight )) );
+ if ( nDriverDataLen )
+ {
+ const char* pDriverData = reinterpret_cast<const char*>(pOldJobData) + nOldJobDataSize;
+ const char* pDriverDataEnd = pDriverData + nDriverDataLen;
+ if (pDriverDataEnd > pTempBuf.get() + nRead)
+ {
+ SAL_WARN("vcl", "corrupted job setup");
+ }
+ else
+ {
+ auto pNewDriverData = std::make_unique<sal_uInt8[]>( nDriverDataLen );
+ memcpy( pNewDriverData.get(), pDriverData, nDriverDataLen );
+ rJobData.SetDriverData( std::move(pNewDriverData), nDriverDataLen );
+ }
+ }
+ if( nSystem == JOBSET_FILE605_SYSTEM )
+ {
+ rIStream.Seek( nFirstPos + sizeof( ImplOldJobSetupData ) +
+ sizeof( Impl364JobSetupData ) + rJobData.GetDriverDataLen() );
+ while( rIStream.Tell() < nFirstPos + nRead )
+ {
+ OUString aKey = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStream, RTL_TEXTENCODING_UTF8);
+ OUString aValue = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStream, RTL_TEXTENCODING_UTF8);
+ if( aKey == "COMPAT_DUPLEX_MODE" )
+ {
+ if( aValue == "DuplexMode::Unknown" )
+ rJobData.SetDuplexMode( DuplexMode::Unknown );
+ else if( aValue == "DuplexMode::Off" )
+ rJobData.SetDuplexMode( DuplexMode::Off );
+ else if( aValue == "DuplexMode::ShortEdge" )
+ rJobData.SetDuplexMode( DuplexMode::ShortEdge );
+ else if( aValue == "DuplexMode::LongEdge" )
+ rJobData.SetDuplexMode( DuplexMode::LongEdge );
+ }
+ else if (aKey == u"PRINTER_NAME")
+ rJobData.SetPrinterName(aValue);
+ else if (aKey == u"DRIVER_NAME")
+ rJobData.SetDriver(aValue);
+ else
+ rJobData.SetValueMap(aKey, aValue);
+ }
+ SAL_WARN_IF( rIStream.Tell() != nFirstPos+nRead, "vcl", "corrupted job setup" );
+ // ensure correct stream position
+ rIStream.Seek(nFirstPos + nRead);
+ }
+ }
+ }
+ }
+
+ return rIStream;
+}
+
+SvStream& WriteJobSetup( SvStream& rOStream, const JobSetup& rJobSetup )
+{
+ {
+ sal_uInt16 nLen = 0;
+ if ( rJobSetup.IsDefault() )
+ rOStream.WriteUInt16( nLen );
+ else
+ {
+ const ImplJobSetup& rJobData = rJobSetup.ImplGetConstData();
+ Impl364JobSetupData aOldJobData;
+ sal_uInt16 nOldJobDataSize = sizeof( aOldJobData );
+ ShortToSVBT16( nOldJobDataSize, aOldJobData.nSize );
+ ShortToSVBT16( rJobData.GetSystem(), aOldJobData.nSystem );
+ UInt32ToSVBT32( rJobData.GetDriverDataLen(), aOldJobData.nDriverDataLen );
+ ShortToSVBT16( static_cast<sal_uInt16>(rJobData.GetOrientation()), aOldJobData.nOrientation );
+ ShortToSVBT16( rJobData.GetPaperBin(), aOldJobData.nPaperBin );
+ ShortToSVBT16( static_cast<sal_uInt16>(rJobData.GetPaperFormat()), aOldJobData.nPaperFormat );
+ UInt32ToSVBT32( static_cast<sal_uLong>(rJobData.GetPaperWidth()), aOldJobData.nPaperWidth );
+ UInt32ToSVBT32( static_cast<sal_uLong>(rJobData.GetPaperHeight()), aOldJobData.nPaperHeight );
+
+ ImplOldJobSetupData aOldData = {};
+ OString aPrnByteName(OUStringToOString(rJobData.GetPrinterName(), RTL_TEXTENCODING_UTF8));
+ strncpy(aOldData.cPrinterName, aPrnByteName.getStr(), std::size(aOldData.cPrinterName) - 1);
+ OString aDriverByteName(OUStringToOString(rJobData.GetDriver(), RTL_TEXTENCODING_UTF8));
+ strncpy(aOldData.cDriverName, aDriverByteName.getStr(), std::size(aOldData.cDriverName) - 1);
+ int nPos = rOStream.Tell();
+ rOStream.WriteUInt16( 0 );
+ rOStream.WriteUInt16( JOBSET_FILE605_SYSTEM );
+ rOStream.WriteBytes( &aOldData, sizeof( aOldData ) );
+ rOStream.WriteBytes( &aOldJobData, nOldJobDataSize );
+ rOStream.WriteBytes( rJobData.GetDriverData(), rJobData.GetDriverDataLen() );
+
+ const std::unordered_map< OUString, OUString >& rValueMap(
+ rJobData.GetValueMap());
+
+ for (auto const& value : rValueMap)
+ {
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, value.first, RTL_TEXTENCODING_UTF8);
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, value.second, RTL_TEXTENCODING_UTF8);
+ }
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "COMPAT_DUPLEX_MODE");
+ switch( rJobData.GetDuplexMode() )
+ {
+ case DuplexMode::Unknown:
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::Unknown");
+ break;
+ case DuplexMode::Off:
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::Off");
+ break;
+ case DuplexMode::ShortEdge:
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::ShortEdge");
+ break;
+ case DuplexMode::LongEdge:
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::LongEdge");
+ break;
+ }
+ // write printer, driver name in full, the ones in aOldData may be truncated
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, u"PRINTER_NAME", RTL_TEXTENCODING_UTF8);
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, rJobData.GetPrinterName(), RTL_TEXTENCODING_UTF8);
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, u"DRIVER_NAME", RTL_TEXTENCODING_UTF8);
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, rJobData.GetDriver(), RTL_TEXTENCODING_UTF8);
+
+ nLen = sal::static_int_cast<sal_uInt16>(rOStream.Tell() - nPos);
+ rOStream.Seek( nPos );
+ rOStream.WriteUInt16( nLen );
+ rOStream.Seek( nPos + nLen );
+ }
+ }
+
+ return rOStream;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/lineinfo.cxx b/vcl/source/gdi/lineinfo.cxx
new file mode 100644
index 0000000000..490b1d920d
--- /dev/null
+++ b/vcl/source/gdi/lineinfo.cxx
@@ -0,0 +1,293 @@
+/* -*- 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 <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <vcl/lineinfo.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+#include <numeric>
+
+
+ImplLineInfo::ImplLineInfo()
+ : mnWidth(0)
+ , mnDashLen(0)
+ , mnDotLen(0)
+ , mnDistance(0)
+ , meLineJoin(basegfx::B2DLineJoin::Round)
+ , meLineCap(css::drawing::LineCap_BUTT)
+ , meStyle(LineStyle::Solid)
+ , mnDashCount(0)
+ , mnDotCount(0)
+{
+}
+
+inline bool ImplLineInfo::operator==( const ImplLineInfo& rB ) const
+{
+ return(meStyle == rB.meStyle
+ && mnWidth == rB.mnWidth
+ && mnDashCount == rB.mnDashCount
+ && mnDashLen == rB.mnDashLen
+ && mnDotCount == rB.mnDotCount
+ && mnDotLen == rB.mnDotLen
+ && mnDistance == rB.mnDistance
+ && meLineJoin == rB.meLineJoin
+ && meLineCap == rB.meLineCap);
+}
+
+
+LineInfo::LineInfo( LineStyle eStyle, double nWidth )
+{
+ mpImplLineInfo->meStyle = eStyle;
+ mpImplLineInfo->mnWidth = nWidth;
+}
+
+LineInfo::LineInfo( const LineInfo& ) = default;
+
+LineInfo::LineInfo( LineInfo&& ) = default;
+
+LineInfo::~LineInfo() = default;
+
+LineInfo& LineInfo::operator=( const LineInfo& ) = default;
+
+LineInfo& LineInfo::operator=( LineInfo&& ) = default;
+
+bool LineInfo::operator==( const LineInfo& rLineInfo ) const
+{
+ return mpImplLineInfo == rLineInfo.mpImplLineInfo;
+}
+
+void LineInfo::SetStyle( LineStyle eStyle )
+{
+ mpImplLineInfo->meStyle = eStyle;
+}
+
+void LineInfo::SetWidth( double nWidth )
+{
+ mpImplLineInfo->mnWidth = nWidth;
+}
+
+void LineInfo::SetDashCount( sal_uInt16 nDashCount )
+{
+ mpImplLineInfo->mnDashCount = nDashCount;
+}
+
+void LineInfo::SetDashLen( double nDashLen )
+{
+ mpImplLineInfo->mnDashLen = nDashLen;
+}
+
+void LineInfo::SetDotCount( sal_uInt16 nDotCount )
+{
+ mpImplLineInfo->mnDotCount = nDotCount;
+}
+
+void LineInfo::SetDotLen( double nDotLen )
+{
+ mpImplLineInfo->mnDotLen = nDotLen;
+}
+
+void LineInfo::SetDistance( double nDistance )
+{
+ mpImplLineInfo->mnDistance = nDistance;
+}
+
+void LineInfo::SetLineJoin(basegfx::B2DLineJoin eLineJoin)
+{
+ mpImplLineInfo->meLineJoin = eLineJoin;
+}
+
+void LineInfo::SetLineCap(css::drawing::LineCap eLineCap)
+{
+ mpImplLineInfo->meLineCap = eLineCap;
+}
+
+bool LineInfo::IsDefault() const
+{
+ return( !mpImplLineInfo->mnWidth
+ && ( LineStyle::Solid == mpImplLineInfo->meStyle )
+ && ( css::drawing::LineCap_BUTT == mpImplLineInfo->meLineCap));
+}
+
+static void ReadLimitedDouble(SvStream& rIStm, double &fDest)
+{
+ double fTmp(0.0);
+ rIStm.ReadDouble(fTmp);
+ if (!std::isfinite(fTmp) || fTmp < std::numeric_limits<sal_Int32>::min() || fTmp > std::numeric_limits<sal_Int32>::max())
+ {
+ SAL_WARN("vcl", "Parsing error: out of range double: " << fTmp);
+ return;
+ }
+ fDest = fTmp;
+}
+
+SvStream& ReadLineInfo( SvStream& rIStm, LineInfo& rLineInfo )
+{
+ VersionCompatRead aCompat( rIStm );
+ sal_uInt16 nTmp16(0);
+ sal_Int32 nTmp32(0);
+
+ rIStm.ReadUInt16( nTmp16 );
+ rLineInfo.mpImplLineInfo->meStyle = static_cast<LineStyle>(nTmp16);
+ rIStm.ReadInt32( nTmp32 );
+ rLineInfo.mpImplLineInfo->mnWidth = nTmp32;
+
+ if( aCompat.GetVersion() >= 2 )
+ {
+ // version 2
+ rIStm.ReadUInt16( rLineInfo.mpImplLineInfo->mnDashCount ).ReadInt32( nTmp32 );
+ rLineInfo.mpImplLineInfo->mnDashLen = nTmp32;
+ rIStm.ReadUInt16( rLineInfo.mpImplLineInfo->mnDotCount ).ReadInt32( nTmp32 );
+ rLineInfo.mpImplLineInfo->mnDotLen = nTmp32;
+ rIStm.ReadInt32( nTmp32 );
+ rLineInfo.mpImplLineInfo->mnDistance = nTmp32;
+ }
+
+ if( aCompat.GetVersion() >= 3 )
+ {
+ // version 3
+ rIStm.ReadUInt16( nTmp16 );
+ rLineInfo.mpImplLineInfo->meLineJoin = static_cast<basegfx::B2DLineJoin>(nTmp16);
+ }
+
+ if( aCompat.GetVersion() >= 4 )
+ {
+ // version 4
+ rIStm.ReadUInt16( nTmp16 );
+ rLineInfo.mpImplLineInfo->meLineCap = static_cast<css::drawing::LineCap>(nTmp16);
+ }
+
+ if( aCompat.GetVersion() >= 5 )
+ {
+ // version 5
+ ReadLimitedDouble(rIStm, rLineInfo.mpImplLineInfo->mnWidth);
+ ReadLimitedDouble(rIStm, rLineInfo.mpImplLineInfo->mnDashLen);
+ ReadLimitedDouble(rIStm, rLineInfo.mpImplLineInfo->mnDotLen);
+ ReadLimitedDouble(rIStm, rLineInfo.mpImplLineInfo->mnDistance);
+ }
+
+ return rIStm;
+}
+
+SvStream& WriteLineInfo( SvStream& rOStm, const LineInfo& rLineInfo )
+{
+ VersionCompatWrite aCompat( rOStm, 5 );
+
+ // version 1
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rLineInfo.mpImplLineInfo->meStyle) )
+ .WriteInt32( basegfx::fround( rLineInfo.mpImplLineInfo->mnWidth ));
+
+ // since version2
+ rOStm.WriteUInt16( rLineInfo.mpImplLineInfo->mnDashCount )
+ .WriteInt32( basegfx::fround( rLineInfo.mpImplLineInfo->mnDashLen ));
+ rOStm.WriteUInt16( rLineInfo.mpImplLineInfo->mnDotCount )
+ .WriteInt32( basegfx::fround( rLineInfo.mpImplLineInfo->mnDotLen ));
+ rOStm.WriteInt32( basegfx::fround( rLineInfo.mpImplLineInfo->mnDistance ));
+
+ // since version3
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rLineInfo.mpImplLineInfo->meLineJoin) );
+
+ // since version4
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rLineInfo.mpImplLineInfo->meLineCap) );
+
+ // since version5
+ rOStm.WriteDouble( rLineInfo.mpImplLineInfo->mnWidth );
+ rOStm.WriteDouble( rLineInfo.mpImplLineInfo->mnDashLen );
+ rOStm.WriteDouble( rLineInfo.mpImplLineInfo->mnDotLen );
+ rOStm.WriteDouble( rLineInfo.mpImplLineInfo->mnDistance );
+
+ return rOStm;
+}
+
+std::vector< double > LineInfo::GetDotDashArray() const
+{
+ ::std::vector< double > fDotDashArray;
+ if ( GetStyle() != LineStyle::Dash )
+ return fDotDashArray;
+
+ const double fDashLen(GetDashLen());
+ const double fDotLen(GetDotLen());
+ const double fDistance(GetDistance());
+
+ for(sal_uInt16 a(0); a < GetDashCount(); a++)
+ {
+ fDotDashArray.push_back(fDashLen);
+ fDotDashArray.push_back(fDistance);
+ }
+
+ for(sal_uInt16 b(0); b < GetDotCount(); b++)
+ {
+ fDotDashArray.push_back(fDotLen);
+ fDotDashArray.push_back(fDistance);
+ }
+ return fDotDashArray;
+}
+
+void LineInfo::applyToB2DPolyPolygon(
+ basegfx::B2DPolyPolygon& io_rLinePolyPolygon,
+ basegfx::B2DPolyPolygon& o_rFillPolyPolygon) const
+{
+ o_rFillPolyPolygon.clear();
+
+ if(!io_rLinePolyPolygon.count())
+ return;
+
+ if(LineStyle::Dash == GetStyle())
+ {
+ ::std::vector< double > fDotDashArray = GetDotDashArray();
+ const double fAccumulated(::std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0));
+
+ if(fAccumulated > 0.0)
+ {
+ basegfx::B2DPolyPolygon aResult;
+
+ for(auto const& rPolygon : std::as_const(io_rLinePolyPolygon))
+ {
+ basegfx::B2DPolyPolygon aLineTarget;
+ basegfx::utils::applyLineDashing(
+ rPolygon,
+ fDotDashArray,
+ &aLineTarget);
+ aResult.append(aLineTarget);
+ }
+
+ io_rLinePolyPolygon = aResult;
+ }
+ }
+
+ if(!(GetWidth() > 1 && io_rLinePolyPolygon.count()))
+ return;
+
+ const double fHalfLineWidth((GetWidth() * 0.5) + 0.5);
+
+ for(auto const& rPolygon : std::as_const(io_rLinePolyPolygon))
+ {
+ o_rFillPolyPolygon.append(basegfx::utils::createAreaGeometry(
+ rPolygon,
+ fHalfLineWidth,
+ GetLineJoin(),
+ GetLineCap()));
+ }
+
+ io_rLinePolyPolygon.clear();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/mapmod.cxx b/vcl/source/gdi/mapmod.cxx
new file mode 100644
index 0000000000..45c7a29a81
--- /dev/null
+++ b/vcl/source/gdi/mapmod.cxx
@@ -0,0 +1,191 @@
+/* -*- 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 <vcl/mapmod.hxx>
+
+#include <o3tl/hash_combine.hxx>
+#include <tools/gen.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <vcl/TypeSerializer.hxx>
+
+struct MapMode::ImplMapMode
+{
+ MapUnit meUnit;
+ bool mbSimple;
+ Point maOrigin;
+ // NOTE: these Fraction must NOT have more than 32 bits precision
+ // because ReadFraction / WriteFraction do only 32 bits, so more than
+ // that cannot be stored in MetaFiles!
+ // => call ReduceInaccurate whenever setting these
+ Fraction maScaleX;
+ Fraction maScaleY;
+
+ ImplMapMode();
+ ImplMapMode(MapUnit eMapUnit);
+ ImplMapMode(const ImplMapMode& rImpMapMode);
+
+ bool operator==( const ImplMapMode& rImpMapMode ) const;
+};
+
+MapMode::ImplMapMode::ImplMapMode() :
+ maOrigin( 0, 0 ),
+ maScaleX( 1, 1 ),
+ maScaleY( 1, 1 )
+{
+ meUnit = MapUnit::MapPixel;
+ mbSimple = true;
+}
+
+MapMode::ImplMapMode::ImplMapMode(MapUnit eMapUnit) :
+ maOrigin( 0, 0 ),
+ maScaleX( 1, 1 ),
+ maScaleY( 1, 1 )
+{
+ meUnit = eMapUnit;
+ mbSimple = true;
+}
+
+MapMode::ImplMapMode::ImplMapMode( const ImplMapMode& ) = default;
+
+bool MapMode::ImplMapMode::operator==( const ImplMapMode& rImpMapMode ) const
+{
+ return meUnit == rImpMapMode.meUnit
+ && maOrigin == rImpMapMode.maOrigin
+ && maScaleX == rImpMapMode.maScaleX
+ && maScaleY == rImpMapMode.maScaleY;
+}
+
+namespace
+{
+
+ MapMode::ImplType& GetGlobalDefault()
+ {
+ static MapMode::ImplType gDefault;
+ return gDefault;
+ }
+ MapMode::ImplType GetUnitDefault(MapUnit mapUnit)
+ {
+ static MapMode::ImplType defaults[]
+ {
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map100thMM) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map10thMM) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapMM) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapCM) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map1000thInch) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map100thInch) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map10thInch) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapInch) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapPoint) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapTwip) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapPixel) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapSysFont) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapAppFont) ),
+ MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapRelative) ),
+ };
+ if (mapUnit <= MapUnit::MapRelative) {
+ return MapMode::ImplType(defaults[static_cast<int>(mapUnit)]); // [-loplugin:redundantfcast] false positive
+ }
+ // sometimes the SvmReader stuff will generate a bogus mapunit value
+ return MapMode::ImplType(MapMode::ImplMapMode(mapUnit));
+ }
+}
+
+MapMode::MapMode() : mpImplMapMode(GetGlobalDefault())
+{
+}
+
+MapMode::MapMode( const MapMode& ) = default;
+
+MapMode::MapMode( MapUnit eUnit ) : mpImplMapMode(GetUnitDefault(eUnit))
+{
+}
+
+MapMode::MapMode( MapUnit eUnit, const Point& rLogicOrg,
+ const Fraction& rScaleX, const Fraction& rScaleY )
+{
+ mpImplMapMode->meUnit = eUnit;
+ mpImplMapMode->maOrigin = rLogicOrg;
+ mpImplMapMode->maScaleX = rScaleX;
+ mpImplMapMode->maScaleY = rScaleY;
+ mpImplMapMode->mbSimple = false;
+}
+
+MapMode::~MapMode() = default;
+
+void MapMode::SetMapUnit( MapUnit eUnit )
+{
+ mpImplMapMode->meUnit = eUnit;
+}
+
+void MapMode::SetOrigin( const Point& rLogicOrg )
+{
+ mpImplMapMode->maOrigin = rLogicOrg;
+ mpImplMapMode->mbSimple = false;
+}
+
+void MapMode::SetScaleX( const Fraction& rScaleX )
+{
+ mpImplMapMode->maScaleX = rScaleX;
+ mpImplMapMode->mbSimple = false;
+}
+
+void MapMode::SetScaleY( const Fraction& rScaleY )
+{
+ mpImplMapMode->maScaleY = rScaleY;
+ mpImplMapMode->mbSimple = false;
+}
+
+MapMode& MapMode::operator=( const MapMode& ) = default;
+
+MapMode& MapMode::operator=( MapMode&& ) = default;
+
+bool MapMode::operator==( const MapMode& rMapMode ) const
+{
+ return mpImplMapMode == rMapMode.mpImplMapMode;
+}
+
+bool MapMode::IsDefault() const
+{
+ return mpImplMapMode.same_object(GetGlobalDefault());
+}
+
+size_t MapMode::GetHashValue() const
+{
+ size_t hash = 0;
+ o3tl::hash_combine( hash, mpImplMapMode->meUnit );
+ o3tl::hash_combine( hash, mpImplMapMode->maOrigin.GetHashValue());
+ o3tl::hash_combine( hash, mpImplMapMode->maScaleX.GetHashValue());
+ o3tl::hash_combine( hash, mpImplMapMode->maScaleY.GetHashValue());
+ o3tl::hash_combine( hash, mpImplMapMode->mbSimple );
+ return hash;
+}
+
+MapUnit MapMode::GetMapUnit() const { return mpImplMapMode->meUnit; }
+
+const Point& MapMode::GetOrigin() const { return mpImplMapMode->maOrigin; }
+
+const Fraction& MapMode::GetScaleX() const { return mpImplMapMode->maScaleX; }
+
+const Fraction& MapMode::GetScaleY() const { return mpImplMapMode->maScaleY; }
+
+bool MapMode::IsSimple() const { return mpImplMapMode->mbSimple; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/metaact.cxx b/vcl/source/gdi/metaact.cxx
new file mode 100644
index 0000000000..2e1f3e4d84
--- /dev/null
+++ b/vcl/source/gdi/metaact.cxx
@@ -0,0 +1,2208 @@
+/* -*- 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 <stdio.h>
+#include <string.h>
+#include <osl/thread.h>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/helpers.hxx>
+#include <utility>
+#include <vcl/dibtools.hxx>
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/filter/SvmWriter.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/graphictools.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/fontdefs.hxx>
+#include <vcl/TypeSerializer.hxx>
+
+namespace
+{
+
+void ImplScalePoint( Point& rPt, double fScaleX, double fScaleY )
+{
+ rPt.setX( FRound( fScaleX * rPt.X() ) );
+ rPt.setY( FRound( fScaleY * rPt.Y() ) );
+}
+
+void ImplScaleRect( tools::Rectangle& rRect, double fScaleX, double fScaleY )
+{
+ Point aTL( rRect.TopLeft() );
+ Point aBR( rRect.BottomRight() );
+
+ ImplScalePoint( aTL, fScaleX, fScaleY );
+ ImplScalePoint( aBR, fScaleX, fScaleY );
+
+ rRect = tools::Rectangle( aTL, aBR );
+ rRect.Normalize();
+}
+
+void ImplScalePoly( tools::Polygon& rPoly, double fScaleX, double fScaleY )
+{
+ for( sal_uInt16 i = 0, nCount = rPoly.GetSize(); i < nCount; i++ )
+ ImplScalePoint( rPoly[ i ], fScaleX, fScaleY );
+}
+
+void ImplScaleLineInfo( LineInfo& rLineInfo, double fScaleX, double fScaleY )
+{
+ if( !rLineInfo.IsDefault() )
+ {
+ const double fScale = ( fabs(fScaleX) + fabs(fScaleY) ) * 0.5;
+
+ rLineInfo.SetWidth( FRound( fScale * rLineInfo.GetWidth() ) );
+ rLineInfo.SetDashLen( FRound( fScale * rLineInfo.GetDashLen() ) );
+ rLineInfo.SetDotLen( FRound( fScale * rLineInfo.GetDotLen() ) );
+ rLineInfo.SetDistance( FRound( fScale * rLineInfo.GetDistance() ) );
+ }
+}
+
+} //anonymous namespace
+
+MetaAction::MetaAction() :
+ mnType( MetaActionType::NONE )
+{
+}
+
+MetaAction::MetaAction( MetaActionType nType ) :
+ mnType( nType )
+{
+}
+
+MetaAction::MetaAction( MetaAction const & rOther ) :
+ SimpleReferenceObject(), mnType( rOther.mnType )
+{
+}
+
+MetaAction::~MetaAction()
+{
+}
+
+void MetaAction::Execute( OutputDevice* )
+{
+}
+
+rtl::Reference<MetaAction> MetaAction::Clone() const
+{
+ return new MetaAction;
+}
+
+void MetaAction::Move( tools::Long, tools::Long )
+{
+}
+
+void MetaAction::Scale( double, double )
+{
+}
+
+MetaPixelAction::MetaPixelAction() :
+ MetaAction(MetaActionType::PIXEL)
+{}
+
+MetaPixelAction::~MetaPixelAction()
+{}
+
+MetaPixelAction::MetaPixelAction( const Point& rPt, const Color& rColor ) :
+ MetaAction ( MetaActionType::PIXEL ),
+ maPt ( rPt ),
+ maColor ( rColor )
+{}
+
+void MetaPixelAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawPixel( maPt, maColor );
+}
+
+rtl::Reference<MetaAction> MetaPixelAction::Clone() const
+{
+ return new MetaPixelAction( *this );
+}
+
+void MetaPixelAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaPixelAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+MetaPointAction::MetaPointAction() :
+ MetaAction(MetaActionType::POINT)
+{}
+
+MetaPointAction::~MetaPointAction()
+{}
+
+MetaPointAction::MetaPointAction( const Point& rPt ) :
+ MetaAction ( MetaActionType::POINT ),
+ maPt ( rPt )
+{}
+
+void MetaPointAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawPixel( maPt );
+}
+
+rtl::Reference<MetaAction> MetaPointAction::Clone() const
+{
+ return new MetaPointAction( *this );
+}
+
+void MetaPointAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaPointAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+MetaLineAction::MetaLineAction() :
+ MetaAction(MetaActionType::LINE)
+{}
+
+MetaLineAction::~MetaLineAction()
+{}
+
+MetaLineAction::MetaLineAction( const Point& rStart, const Point& rEnd ) :
+ MetaAction ( MetaActionType::LINE ),
+ maStartPt ( rStart ),
+ maEndPt ( rEnd )
+{}
+
+MetaLineAction::MetaLineAction( const Point& rStart, const Point& rEnd,
+ LineInfo aLineInfo ) :
+ MetaAction ( MetaActionType::LINE ),
+ maLineInfo (std::move( aLineInfo )),
+ maStartPt ( rStart ),
+ maEndPt ( rEnd )
+{}
+
+void MetaLineAction::Execute( OutputDevice* pOut )
+{
+ if( maLineInfo.IsDefault() )
+ pOut->DrawLine( maStartPt, maEndPt );
+ else
+ pOut->DrawLine( maStartPt, maEndPt, maLineInfo );
+}
+
+rtl::Reference<MetaAction> MetaLineAction::Clone() const
+{
+ return new MetaLineAction( *this );
+}
+
+void MetaLineAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maStartPt.Move( nHorzMove, nVertMove );
+ maEndPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaLineAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maStartPt, fScaleX, fScaleY );
+ ImplScalePoint( maEndPt, fScaleX, fScaleY );
+ ImplScaleLineInfo( maLineInfo, fScaleX, fScaleY );
+}
+
+MetaRectAction::MetaRectAction() :
+ MetaAction(MetaActionType::RECT)
+{}
+
+MetaRectAction::~MetaRectAction()
+{}
+
+MetaRectAction::MetaRectAction( const tools::Rectangle& rRect ) :
+ MetaAction ( MetaActionType::RECT ),
+ maRect ( rRect )
+{}
+
+void MetaRectAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawRect( maRect );
+}
+
+rtl::Reference<MetaAction> MetaRectAction::Clone() const
+{
+ return new MetaRectAction( *this );
+}
+
+void MetaRectAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaRectAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+MetaRoundRectAction::MetaRoundRectAction() :
+ MetaAction ( MetaActionType::ROUNDRECT ),
+ mnHorzRound ( 0 ),
+ mnVertRound ( 0 )
+{}
+
+MetaRoundRectAction::~MetaRoundRectAction()
+{}
+
+MetaRoundRectAction::MetaRoundRectAction( const tools::Rectangle& rRect,
+ sal_uInt32 nHorzRound, sal_uInt32 nVertRound ) :
+ MetaAction ( MetaActionType::ROUNDRECT ),
+ maRect ( rRect ),
+ mnHorzRound ( nHorzRound ),
+ mnVertRound ( nVertRound )
+{}
+
+void MetaRoundRectAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawRect( maRect, mnHorzRound, mnVertRound );
+}
+
+rtl::Reference<MetaAction> MetaRoundRectAction::Clone() const
+{
+ return new MetaRoundRectAction( *this );
+}
+
+void MetaRoundRectAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaRoundRectAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+ mnHorzRound = FRound( mnHorzRound * fabs(fScaleX) );
+ mnVertRound = FRound( mnVertRound * fabs(fScaleY) );
+}
+
+MetaEllipseAction::MetaEllipseAction() :
+ MetaAction(MetaActionType::ELLIPSE)
+{}
+
+MetaEllipseAction::~MetaEllipseAction()
+{}
+
+MetaEllipseAction::MetaEllipseAction( const tools::Rectangle& rRect ) :
+ MetaAction ( MetaActionType::ELLIPSE ),
+ maRect ( rRect )
+{}
+
+void MetaEllipseAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawEllipse( maRect );
+}
+
+rtl::Reference<MetaAction> MetaEllipseAction::Clone() const
+{
+ return new MetaEllipseAction( *this );
+}
+
+void MetaEllipseAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaEllipseAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+MetaArcAction::MetaArcAction() :
+ MetaAction(MetaActionType::ARC)
+{}
+
+MetaArcAction::~MetaArcAction()
+{}
+
+MetaArcAction::MetaArcAction( const tools::Rectangle& rRect,
+ const Point& rStart, const Point& rEnd ) :
+ MetaAction ( MetaActionType::ARC ),
+ maRect ( rRect ),
+ maStartPt ( rStart ),
+ maEndPt ( rEnd )
+{}
+
+void MetaArcAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawArc( maRect, maStartPt, maEndPt );
+}
+
+rtl::Reference<MetaAction> MetaArcAction::Clone() const
+{
+ return new MetaArcAction( *this );
+}
+
+void MetaArcAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+ maStartPt.Move( nHorzMove, nVertMove );
+ maEndPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaArcAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+ ImplScalePoint( maStartPt, fScaleX, fScaleY );
+ ImplScalePoint( maEndPt, fScaleX, fScaleY );
+}
+
+MetaPieAction::MetaPieAction() :
+ MetaAction(MetaActionType::PIE)
+{}
+
+MetaPieAction::~MetaPieAction()
+{}
+
+MetaPieAction::MetaPieAction( const tools::Rectangle& rRect,
+ const Point& rStart, const Point& rEnd ) :
+ MetaAction ( MetaActionType::PIE ),
+ maRect ( rRect ),
+ maStartPt ( rStart ),
+ maEndPt ( rEnd )
+{}
+
+void MetaPieAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawPie( maRect, maStartPt, maEndPt );
+}
+
+rtl::Reference<MetaAction> MetaPieAction::Clone() const
+{
+ return new MetaPieAction( *this );
+}
+
+void MetaPieAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+ maStartPt.Move( nHorzMove, nVertMove );
+ maEndPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaPieAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+ ImplScalePoint( maStartPt, fScaleX, fScaleY );
+ ImplScalePoint( maEndPt, fScaleX, fScaleY );
+}
+
+MetaChordAction::MetaChordAction() :
+ MetaAction(MetaActionType::CHORD)
+{}
+
+MetaChordAction::~MetaChordAction()
+{}
+
+MetaChordAction::MetaChordAction( const tools::Rectangle& rRect,
+ const Point& rStart, const Point& rEnd ) :
+ MetaAction ( MetaActionType::CHORD ),
+ maRect ( rRect ),
+ maStartPt ( rStart ),
+ maEndPt ( rEnd )
+{}
+
+void MetaChordAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawChord( maRect, maStartPt, maEndPt );
+}
+
+rtl::Reference<MetaAction> MetaChordAction::Clone() const
+{
+ return new MetaChordAction( *this );
+}
+
+void MetaChordAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+ maStartPt.Move( nHorzMove, nVertMove );
+ maEndPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaChordAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+ ImplScalePoint( maStartPt, fScaleX, fScaleY );
+ ImplScalePoint( maEndPt, fScaleX, fScaleY );
+}
+
+MetaPolyLineAction::MetaPolyLineAction() :
+ MetaAction(MetaActionType::POLYLINE)
+{}
+
+MetaPolyLineAction::~MetaPolyLineAction()
+{}
+
+MetaPolyLineAction::MetaPolyLineAction( tools::Polygon aPoly ) :
+ MetaAction ( MetaActionType::POLYLINE ),
+ maPoly (std::move( aPoly ))
+{}
+
+MetaPolyLineAction::MetaPolyLineAction( tools::Polygon aPoly, LineInfo aLineInfo ) :
+ MetaAction ( MetaActionType::POLYLINE ),
+ maLineInfo (std::move( aLineInfo )),
+ maPoly (std::move( aPoly ))
+{}
+
+static bool AllowDim(tools::Long nDim)
+{
+ static bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ if (bFuzzing)
+ {
+ if (nDim > 0x20000000 || nDim < -0x20000000)
+ {
+ SAL_WARN("vcl", "skipping huge dimension: " << nDim);
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool AllowPoint(const Point& rPoint)
+{
+ return AllowDim(rPoint.X()) && AllowDim(rPoint.Y());
+}
+
+static bool AllowRect(const tools::Rectangle& rRect)
+{
+ return AllowPoint(rRect.TopLeft()) && AllowPoint(rRect.BottomRight());
+}
+
+void MetaPolyLineAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowRect(pOut->LogicToPixel(maPoly.GetBoundRect())))
+ return;
+
+ if( maLineInfo.IsDefault() )
+ pOut->DrawPolyLine( maPoly );
+ else
+ pOut->DrawPolyLine( maPoly, maLineInfo );
+}
+
+rtl::Reference<MetaAction> MetaPolyLineAction::Clone() const
+{
+ return new MetaPolyLineAction( *this );
+}
+
+void MetaPolyLineAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaPolyLineAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoly( maPoly, fScaleX, fScaleY );
+ ImplScaleLineInfo( maLineInfo, fScaleX, fScaleY );
+}
+
+MetaPolygonAction::MetaPolygonAction() :
+ MetaAction(MetaActionType::POLYGON)
+{}
+
+MetaPolygonAction::~MetaPolygonAction()
+{}
+
+MetaPolygonAction::MetaPolygonAction( tools::Polygon aPoly ) :
+ MetaAction ( MetaActionType::POLYGON ),
+ maPoly (std::move( aPoly ))
+{}
+
+void MetaPolygonAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawPolygon( maPoly );
+}
+
+rtl::Reference<MetaAction> MetaPolygonAction::Clone() const
+{
+ return new MetaPolygonAction( *this );
+}
+
+void MetaPolygonAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaPolygonAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoly( maPoly, fScaleX, fScaleY );
+}
+
+MetaPolyPolygonAction::MetaPolyPolygonAction() :
+ MetaAction(MetaActionType::POLYPOLYGON)
+{}
+
+MetaPolyPolygonAction::~MetaPolyPolygonAction()
+{}
+
+MetaPolyPolygonAction::MetaPolyPolygonAction( tools::PolyPolygon aPolyPoly ) :
+ MetaAction ( MetaActionType::POLYPOLYGON ),
+ maPolyPoly (std::move( aPolyPoly ))
+{}
+
+void MetaPolyPolygonAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawPolyPolygon( maPolyPoly );
+}
+
+rtl::Reference<MetaAction> MetaPolyPolygonAction::Clone() const
+{
+ return new MetaPolyPolygonAction( *this );
+}
+
+void MetaPolyPolygonAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPolyPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaPolyPolygonAction::Scale( double fScaleX, double fScaleY )
+{
+ for( sal_uInt16 i = 0, nCount = maPolyPoly.Count(); i < nCount; i++ )
+ ImplScalePoly( maPolyPoly[ i ], fScaleX, fScaleY );
+}
+
+MetaTextAction::MetaTextAction() :
+ MetaAction ( MetaActionType::TEXT ),
+ mnIndex ( 0 ),
+ mnLen ( 0 )
+{}
+
+MetaTextAction::~MetaTextAction()
+{}
+
+MetaTextAction::MetaTextAction( const Point& rPt, OUString aStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::TEXT ),
+ maPt ( rPt ),
+ maStr (std::move( aStr )),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{}
+
+void MetaTextAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowDim(pOut->LogicToPixel(maPt).Y()))
+ return;
+
+ pOut->DrawText( maPt, maStr, mnIndex, mnLen );
+}
+
+rtl::Reference<MetaAction> MetaTextAction::Clone() const
+{
+ return new MetaTextAction( *this );
+}
+
+void MetaTextAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+MetaTextArrayAction::MetaTextArrayAction() :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ mnIndex ( 0 ),
+ mnLen ( 0 )
+{}
+
+MetaTextArrayAction::MetaTextArrayAction( const MetaTextArrayAction& rAction ) :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ maStartPt ( rAction.maStartPt ),
+ maStr ( rAction.maStr ),
+ maDXAry ( rAction.maDXAry ),
+ maKashidaAry( rAction.maKashidaAry ),
+ mnIndex ( rAction.mnIndex ),
+ mnLen ( rAction.mnLen )
+{
+}
+
+MetaTextArrayAction::MetaTextArrayAction( const Point& rStartPt,
+ OUString aStr,
+ KernArray aDXAry,
+ std::vector<sal_Bool> aKashidaAry,
+ sal_Int32 nIndex,
+ sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ maStartPt ( rStartPt ),
+ maStr (std::move( aStr )),
+ maDXAry (std::move( aDXAry )),
+ maKashidaAry(std::move( aKashidaAry )),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{
+}
+
+MetaTextArrayAction::MetaTextArrayAction( const Point& rStartPt,
+ OUString aStr,
+ KernArraySpan pDXAry,
+ std::span<const sal_Bool> pKashidaAry,
+ sal_Int32 nIndex,
+ sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ maStartPt ( rStartPt ),
+ maStr (std::move( aStr )),
+ maKashidaAry( pKashidaAry.begin(), pKashidaAry.end() ),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{
+ maDXAry.assign(pDXAry);
+}
+
+MetaTextArrayAction::~MetaTextArrayAction()
+{
+}
+
+void MetaTextArrayAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTextArray( maStartPt, maStr, maDXAry, maKashidaAry, mnIndex, mnLen );
+}
+
+rtl::Reference<MetaAction> MetaTextArrayAction::Clone() const
+{
+ return new MetaTextArrayAction( *this );
+}
+
+void MetaTextArrayAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maStartPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextArrayAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maStartPt, fScaleX, fScaleY );
+
+ if ( !maDXAry.empty() && mnLen )
+ {
+ for ( sal_uInt16 i = 0, nCount = mnLen; i < nCount; i++ )
+ maDXAry.set(i, FRound(maDXAry[i] * fabs(fScaleX)));
+ }
+}
+
+void MetaTextArrayAction::SetDXArray(KernArray aArray)
+{
+ maDXAry = std::move(aArray);
+}
+
+void MetaTextArrayAction::SetKashidaArray(std::vector<sal_Bool> aArray)
+{
+ maKashidaAry = std::move(aArray);
+}
+
+MetaStretchTextAction::MetaStretchTextAction() :
+ MetaAction ( MetaActionType::STRETCHTEXT ),
+ mnWidth ( 0 ),
+ mnIndex ( 0 ),
+ mnLen ( 0 )
+{}
+
+MetaStretchTextAction::~MetaStretchTextAction()
+{}
+
+MetaStretchTextAction::MetaStretchTextAction( const Point& rPt, sal_uInt32 nWidth,
+ OUString aStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::STRETCHTEXT ),
+ maPt ( rPt ),
+ maStr (std::move( aStr )),
+ mnWidth ( nWidth ),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{}
+
+void MetaStretchTextAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowDim(pOut->LogicToPixel(maPt).Y()))
+ return;
+
+ pOut->DrawStretchText( maPt, mnWidth, maStr, mnIndex, mnLen );
+}
+
+rtl::Reference<MetaAction> MetaStretchTextAction::Clone() const
+{
+ return new MetaStretchTextAction( *this );
+}
+
+void MetaStretchTextAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaStretchTextAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+ mnWidth = static_cast<sal_uLong>(FRound( mnWidth * fabs(fScaleX) ));
+}
+MetaTextRectAction::MetaTextRectAction() :
+ MetaAction ( MetaActionType::TEXTRECT ),
+ mnStyle ( DrawTextFlags::NONE )
+{}
+
+MetaTextRectAction::~MetaTextRectAction()
+{}
+
+MetaTextRectAction::MetaTextRectAction( const tools::Rectangle& rRect,
+ OUString aStr, DrawTextFlags nStyle ) :
+ MetaAction ( MetaActionType::TEXTRECT ),
+ maRect ( rRect ),
+ maStr (std::move( aStr )),
+ mnStyle ( nStyle )
+{}
+
+void MetaTextRectAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowRect(pOut->LogicToPixel(maRect)))
+ return;
+
+ pOut->DrawText( maRect, maStr, mnStyle );
+}
+
+rtl::Reference<MetaAction> MetaTextRectAction::Clone() const
+{
+ return new MetaTextRectAction( *this );
+}
+
+void MetaTextRectAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextRectAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+MetaTextLineAction::MetaTextLineAction() :
+ MetaAction ( MetaActionType::TEXTLINE ),
+ mnWidth ( 0 ),
+ meStrikeout ( STRIKEOUT_NONE ),
+ meUnderline ( LINESTYLE_NONE ),
+ meOverline ( LINESTYLE_NONE )
+{}
+
+MetaTextLineAction::~MetaTextLineAction()
+{}
+
+MetaTextLineAction::MetaTextLineAction( const Point& rPos, tools::Long nWidth,
+ FontStrikeout eStrikeout,
+ FontLineStyle eUnderline,
+ FontLineStyle eOverline ) :
+ MetaAction ( MetaActionType::TEXTLINE ),
+ maPos ( rPos ),
+ mnWidth ( nWidth ),
+ meStrikeout ( eStrikeout ),
+ meUnderline ( eUnderline ),
+ meOverline ( eOverline )
+{}
+
+void MetaTextLineAction::Execute( OutputDevice* pOut )
+{
+ if (mnWidth < 0)
+ {
+ SAL_WARN("vcl", "skipping line with negative width: " << mnWidth);
+ return;
+ }
+ pOut->DrawTextLine( maPos, mnWidth, meStrikeout, meUnderline, meOverline );
+}
+
+rtl::Reference<MetaAction> MetaTextLineAction::Clone() const
+{
+ return new MetaTextLineAction( *this );
+}
+
+void MetaTextLineAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPos.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextLineAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPos, fScaleX, fScaleY );
+ mnWidth = FRound( mnWidth * fabs(fScaleX) );
+}
+
+MetaBmpAction::MetaBmpAction() :
+ MetaAction(MetaActionType::BMP)
+{}
+
+MetaBmpAction::~MetaBmpAction()
+{}
+
+MetaBmpAction::MetaBmpAction( const Point& rPt, const Bitmap& rBmp ) :
+ MetaAction ( MetaActionType::BMP ),
+ maBmp ( rBmp ),
+ maPt ( rPt )
+{}
+
+void MetaBmpAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawBitmap( maPt, maBmp );
+}
+
+rtl::Reference<MetaAction> MetaBmpAction::Clone() const
+{
+ return new MetaBmpAction( *this );
+}
+
+void MetaBmpAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+MetaBmpScaleAction::MetaBmpScaleAction() :
+ MetaAction(MetaActionType::BMPSCALE)
+{}
+
+MetaBmpScaleAction::~MetaBmpScaleAction()
+{}
+
+MetaBmpScaleAction::MetaBmpScaleAction( const Point& rPt, const Size& rSz,
+ const Bitmap& rBmp ) :
+ MetaAction ( MetaActionType::BMPSCALE ),
+ maBmp ( rBmp ),
+ maPt ( rPt ),
+ maSz ( rSz )
+{}
+
+static bool AllowScale(const Size& rSource, const Size& rDest)
+{
+ static bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ if (bFuzzing)
+ {
+ constexpr int nMaxScaleWhenFuzzing = 128;
+
+ auto nSourceHeight = rSource.Height();
+ auto nDestHeight = rDest.Height();
+ if (nSourceHeight && std::abs(nDestHeight / nSourceHeight) > nMaxScaleWhenFuzzing)
+ {
+ SAL_WARN("vcl", "skipping large vertical scaling: " << nSourceHeight << " to " << nDestHeight);
+ return false;
+ }
+
+ if (nDestHeight && std::abs(nSourceHeight / nDestHeight) > nMaxScaleWhenFuzzing)
+ {
+ SAL_WARN("vcl", "skipping large vertical scaling: " << nSourceHeight << " to " << nDestHeight);
+ return false;
+ }
+
+ auto nSourceWidth = rSource.Width();
+ auto nDestWidth = rDest.Width();
+ if (nSourceWidth && std::abs(nDestWidth / nSourceWidth) > nMaxScaleWhenFuzzing)
+ {
+ SAL_WARN("vcl", "skipping large horizontal scaling: " << nSourceWidth << " to " << nDestWidth);
+ return false;
+ }
+
+ if (nDestWidth && std::abs(nSourceWidth / nDestWidth) > nMaxScaleWhenFuzzing)
+ {
+ SAL_WARN("vcl", "skipping large horizontal scaling: " << nSourceWidth << " to " << nDestWidth);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void MetaBmpScaleAction::Execute( OutputDevice* pOut )
+{
+ Size aPixelSize(pOut->LogicToPixel(maSz));
+ if (!AllowRect(tools::Rectangle(pOut->LogicToPixel(maPt), aPixelSize)) ||
+ !AllowScale(maBmp.GetSizePixel(), aPixelSize))
+ {
+ return;
+ }
+
+ pOut->DrawBitmap( maPt, maSz, maBmp );
+}
+
+rtl::Reference<MetaAction> MetaBmpScaleAction::Clone() const
+{
+ return new MetaBmpScaleAction( *this );
+}
+
+void MetaBmpScaleAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpScaleAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maPt, maSz);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maPt = aRectangle.TopLeft();
+ maSz = aRectangle.GetSize();
+}
+
+MetaBmpScalePartAction::MetaBmpScalePartAction() :
+ MetaAction(MetaActionType::BMPSCALEPART)
+{}
+
+MetaBmpScalePartAction::~MetaBmpScalePartAction()
+{}
+
+MetaBmpScalePartAction::MetaBmpScalePartAction( const Point& rDstPt, const Size& rDstSz,
+ const Point& rSrcPt, const Size& rSrcSz,
+ const Bitmap& rBmp ) :
+ MetaAction ( MetaActionType::BMPSCALEPART ),
+ maBmp ( rBmp ),
+ maDstPt ( rDstPt ),
+ maDstSz ( rDstSz ),
+ maSrcPt ( rSrcPt ),
+ maSrcSz ( rSrcSz )
+{}
+
+void MetaBmpScalePartAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maDstPt, maDstSz))))
+ return;
+
+ pOut->DrawBitmap( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmp );
+}
+
+rtl::Reference<MetaAction> MetaBmpScalePartAction::Clone() const
+{
+ return new MetaBmpScalePartAction( *this );
+}
+
+void MetaBmpScalePartAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maDstPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpScalePartAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maDstPt, maDstSz);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maDstPt = aRectangle.TopLeft();
+ maDstSz = aRectangle.GetSize();
+}
+
+MetaBmpExAction::MetaBmpExAction() :
+ MetaAction(MetaActionType::BMPEX)
+{}
+
+MetaBmpExAction::~MetaBmpExAction()
+{}
+
+MetaBmpExAction::MetaBmpExAction( const Point& rPt, const BitmapEx& rBmpEx ) :
+ MetaAction ( MetaActionType::BMPEX ),
+ maBmpEx ( rBmpEx ),
+ maPt ( rPt )
+{}
+
+void MetaBmpExAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawBitmapEx( maPt, maBmpEx );
+}
+
+rtl::Reference<MetaAction> MetaBmpExAction::Clone() const
+{
+ return new MetaBmpExAction( *this );
+}
+
+void MetaBmpExAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpExAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+MetaBmpExScaleAction::MetaBmpExScaleAction() :
+ MetaAction(MetaActionType::BMPEXSCALE)
+{}
+
+MetaBmpExScaleAction::~MetaBmpExScaleAction()
+{}
+
+MetaBmpExScaleAction::MetaBmpExScaleAction( const Point& rPt, const Size& rSz,
+ const BitmapEx& rBmpEx ) :
+ MetaAction ( MetaActionType::BMPEXSCALE ),
+ maBmpEx ( rBmpEx ),
+ maPt ( rPt ),
+ maSz ( rSz )
+{}
+
+void MetaBmpExScaleAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowScale(maBmpEx.GetSizePixel(), pOut->LogicToPixel(maSz)))
+ return;
+ if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maPt, maSz))))
+ return;
+
+ pOut->DrawBitmapEx( maPt, maSz, maBmpEx );
+}
+
+rtl::Reference<MetaAction> MetaBmpExScaleAction::Clone() const
+{
+ return new MetaBmpExScaleAction( *this );
+}
+
+void MetaBmpExScaleAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpExScaleAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maPt, maSz);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maPt = aRectangle.TopLeft();
+ maSz = aRectangle.GetSize();
+}
+
+MetaBmpExScalePartAction::MetaBmpExScalePartAction() :
+ MetaAction(MetaActionType::BMPEXSCALEPART)
+{}
+
+MetaBmpExScalePartAction::~MetaBmpExScalePartAction()
+{}
+
+MetaBmpExScalePartAction::MetaBmpExScalePartAction( const Point& rDstPt, const Size& rDstSz,
+ const Point& rSrcPt, const Size& rSrcSz,
+ const BitmapEx& rBmpEx ) :
+ MetaAction ( MetaActionType::BMPEXSCALEPART ),
+ maBmpEx ( rBmpEx ),
+ maDstPt ( rDstPt ),
+ maDstSz ( rDstSz ),
+ maSrcPt ( rSrcPt ),
+ maSrcSz ( rSrcSz )
+{}
+
+void MetaBmpExScalePartAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maDstPt, maDstSz))))
+ return;
+
+ pOut->DrawBitmapEx( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmpEx );
+}
+
+rtl::Reference<MetaAction> MetaBmpExScalePartAction::Clone() const
+{
+ return new MetaBmpExScalePartAction( *this );
+}
+
+void MetaBmpExScalePartAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maDstPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpExScalePartAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maDstPt, maDstSz);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maDstPt = aRectangle.TopLeft();
+ maDstSz = aRectangle.GetSize();
+}
+
+MetaMaskAction::MetaMaskAction() :
+ MetaAction(MetaActionType::MASK)
+{}
+
+MetaMaskAction::~MetaMaskAction()
+{}
+
+MetaMaskAction::MetaMaskAction( const Point& rPt,
+ const Bitmap& rBmp,
+ const Color& rColor ) :
+ MetaAction ( MetaActionType::MASK ),
+ maBmp ( rBmp ),
+ maColor ( rColor ),
+ maPt ( rPt )
+{}
+
+void MetaMaskAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawMask( maPt, maBmp, maColor );
+}
+
+rtl::Reference<MetaAction> MetaMaskAction::Clone() const
+{
+ return new MetaMaskAction( *this );
+}
+
+void MetaMaskAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaMaskAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+MetaMaskScaleAction::MetaMaskScaleAction() :
+ MetaAction(MetaActionType::MASKSCALE)
+{}
+
+MetaMaskScaleAction::~MetaMaskScaleAction()
+{}
+
+MetaMaskScaleAction::MetaMaskScaleAction( const Point& rPt, const Size& rSz,
+ const Bitmap& rBmp,
+ const Color& rColor ) :
+ MetaAction ( MetaActionType::MASKSCALE ),
+ maBmp ( rBmp ),
+ maColor ( rColor ),
+ maPt ( rPt ),
+ maSz ( rSz )
+{}
+
+void MetaMaskScaleAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maPt, maSz))))
+ return;
+ pOut->DrawMask( maPt, maSz, maBmp, maColor );
+}
+
+rtl::Reference<MetaAction> MetaMaskScaleAction::Clone() const
+{
+ return new MetaMaskScaleAction( *this );
+}
+
+void MetaMaskScaleAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaMaskScaleAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maPt, maSz);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maPt = aRectangle.TopLeft();
+ maSz = aRectangle.GetSize();
+}
+
+MetaMaskScalePartAction::MetaMaskScalePartAction() :
+ MetaAction(MetaActionType::MASKSCALEPART)
+{}
+
+MetaMaskScalePartAction::~MetaMaskScalePartAction()
+{}
+
+MetaMaskScalePartAction::MetaMaskScalePartAction( const Point& rDstPt, const Size& rDstSz,
+ const Point& rSrcPt, const Size& rSrcSz,
+ const Bitmap& rBmp,
+ const Color& rColor ) :
+ MetaAction ( MetaActionType::MASKSCALEPART ),
+ maBmp ( rBmp ),
+ maColor ( rColor ),
+ maDstPt ( rDstPt ),
+ maDstSz ( rDstSz ),
+ maSrcPt ( rSrcPt ),
+ maSrcSz ( rSrcSz )
+{}
+
+void MetaMaskScalePartAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maDstPt, maDstSz))))
+ return;
+
+ pOut->DrawMask( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmp, maColor, MetaActionType::MASKSCALE );
+}
+
+rtl::Reference<MetaAction> MetaMaskScalePartAction::Clone() const
+{
+ return new MetaMaskScalePartAction( *this );
+}
+
+void MetaMaskScalePartAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maDstPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaMaskScalePartAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maDstPt, maDstSz);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maDstPt = aRectangle.TopLeft();
+ maDstSz = aRectangle.GetSize();
+}
+
+MetaGradientAction::MetaGradientAction() :
+ MetaAction(MetaActionType::GRADIENT)
+{}
+
+MetaGradientAction::~MetaGradientAction()
+{}
+
+MetaGradientAction::MetaGradientAction( const tools::Rectangle& rRect, Gradient aGradient ) :
+ MetaAction ( MetaActionType::GRADIENT ),
+ maRect ( rRect ),
+ maGradient (std::move( aGradient ))
+{}
+
+void MetaGradientAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawGradient( maRect, maGradient );
+}
+
+rtl::Reference<MetaAction> MetaGradientAction::Clone() const
+{
+ return new MetaGradientAction( *this );
+}
+
+void MetaGradientAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaGradientAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+MetaGradientExAction::MetaGradientExAction() :
+ MetaAction ( MetaActionType::GRADIENTEX )
+{}
+
+MetaGradientExAction::MetaGradientExAction( tools::PolyPolygon aPolyPoly, Gradient aGradient ) :
+ MetaAction ( MetaActionType::GRADIENTEX ),
+ maPolyPoly (std::move( aPolyPoly )),
+ maGradient (std::move( aGradient ))
+{}
+
+MetaGradientExAction::~MetaGradientExAction()
+{}
+
+void MetaGradientExAction::Execute( OutputDevice* pOut )
+{
+ if( pOut->GetConnectMetaFile() )
+ {
+ pOut->GetConnectMetaFile()->AddAction( this );
+ }
+}
+
+rtl::Reference<MetaAction> MetaGradientExAction::Clone() const
+{
+ return new MetaGradientExAction( *this );
+}
+
+void MetaGradientExAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPolyPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaGradientExAction::Scale( double fScaleX, double fScaleY )
+{
+ for( sal_uInt16 i = 0, nCount = maPolyPoly.Count(); i < nCount; i++ )
+ ImplScalePoly( maPolyPoly[ i ], fScaleX, fScaleY );
+}
+
+MetaHatchAction::MetaHatchAction() :
+ MetaAction(MetaActionType::HATCH)
+{}
+
+MetaHatchAction::~MetaHatchAction()
+{}
+
+MetaHatchAction::MetaHatchAction( tools::PolyPolygon aPolyPoly, const Hatch& rHatch ) :
+ MetaAction ( MetaActionType::HATCH ),
+ maPolyPoly (std::move( aPolyPoly )),
+ maHatch ( rHatch )
+{}
+
+void MetaHatchAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowRect(pOut->LogicToPixel(maPolyPoly.GetBoundRect())))
+ return;
+ if (!AllowDim(pOut->LogicToPixel(Point(maHatch.GetDistance(), 0)).X()))
+ return;
+
+ pOut->DrawHatch( maPolyPoly, maHatch );
+}
+
+rtl::Reference<MetaAction> MetaHatchAction::Clone() const
+{
+ return new MetaHatchAction( *this );
+}
+
+void MetaHatchAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPolyPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaHatchAction::Scale( double fScaleX, double fScaleY )
+{
+ for( sal_uInt16 i = 0, nCount = maPolyPoly.Count(); i < nCount; i++ )
+ ImplScalePoly( maPolyPoly[ i ], fScaleX, fScaleY );
+}
+
+MetaWallpaperAction::MetaWallpaperAction() :
+ MetaAction(MetaActionType::WALLPAPER)
+{}
+
+MetaWallpaperAction::~MetaWallpaperAction()
+{}
+
+MetaWallpaperAction::MetaWallpaperAction( const tools::Rectangle& rRect,
+ const Wallpaper& rPaper ) :
+ MetaAction ( MetaActionType::WALLPAPER ),
+ maRect ( rRect ),
+ maWallpaper ( rPaper )
+{}
+
+void MetaWallpaperAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawWallpaper( maRect, maWallpaper );
+}
+
+rtl::Reference<MetaAction> MetaWallpaperAction::Clone() const
+{
+ return new MetaWallpaperAction( *this );
+}
+
+void MetaWallpaperAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaWallpaperAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+MetaClipRegionAction::MetaClipRegionAction() :
+ MetaAction ( MetaActionType::CLIPREGION ),
+ mbClip ( false )
+{}
+
+MetaClipRegionAction::~MetaClipRegionAction()
+{}
+
+MetaClipRegionAction::MetaClipRegionAction( vcl::Region aRegion, bool bClip ) :
+ MetaAction ( MetaActionType::CLIPREGION ),
+ maRegion (std::move( aRegion )),
+ mbClip ( bClip )
+{}
+
+void MetaClipRegionAction::Execute( OutputDevice* pOut )
+{
+ if( mbClip )
+ pOut->SetClipRegion( maRegion );
+ else
+ pOut->SetClipRegion();
+}
+
+rtl::Reference<MetaAction> MetaClipRegionAction::Clone() const
+{
+ return new MetaClipRegionAction( *this );
+}
+
+void MetaClipRegionAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRegion.Move( nHorzMove, nVertMove );
+}
+
+void MetaClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ maRegion.Scale( fScaleX, fScaleY );
+}
+
+MetaISectRectClipRegionAction::MetaISectRectClipRegionAction() :
+ MetaAction(MetaActionType::ISECTRECTCLIPREGION)
+{}
+
+MetaISectRectClipRegionAction::~MetaISectRectClipRegionAction()
+{}
+
+MetaISectRectClipRegionAction::MetaISectRectClipRegionAction( const tools::Rectangle& rRect ) :
+ MetaAction ( MetaActionType::ISECTRECTCLIPREGION ),
+ maRect ( rRect )
+{}
+
+void MetaISectRectClipRegionAction::Execute( OutputDevice* pOut )
+{
+ pOut->IntersectClipRegion( maRect );
+}
+
+rtl::Reference<MetaAction> MetaISectRectClipRegionAction::Clone() const
+{
+ return new MetaISectRectClipRegionAction( *this );
+}
+
+void MetaISectRectClipRegionAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaISectRectClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+MetaISectRegionClipRegionAction::MetaISectRegionClipRegionAction() :
+ MetaAction(MetaActionType::ISECTREGIONCLIPREGION)
+{}
+
+MetaISectRegionClipRegionAction::~MetaISectRegionClipRegionAction()
+{}
+
+MetaISectRegionClipRegionAction::MetaISectRegionClipRegionAction( vcl::Region aRegion ) :
+ MetaAction ( MetaActionType::ISECTREGIONCLIPREGION ),
+ maRegion (std::move( aRegion ))
+{
+}
+
+void MetaISectRegionClipRegionAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowRect(pOut->LogicToPixel(maRegion.GetBoundRect())))
+ return;
+
+ pOut->IntersectClipRegion( maRegion );
+}
+
+rtl::Reference<MetaAction> MetaISectRegionClipRegionAction::Clone() const
+{
+ return new MetaISectRegionClipRegionAction( *this );
+}
+
+void MetaISectRegionClipRegionAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maRegion.Move( nHorzMove, nVertMove );
+}
+
+void MetaISectRegionClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ maRegion.Scale( fScaleX, fScaleY );
+}
+
+MetaMoveClipRegionAction::MetaMoveClipRegionAction() :
+ MetaAction ( MetaActionType::MOVECLIPREGION ),
+ mnHorzMove ( 0 ),
+ mnVertMove ( 0 )
+{}
+
+MetaMoveClipRegionAction::~MetaMoveClipRegionAction()
+{}
+
+MetaMoveClipRegionAction::MetaMoveClipRegionAction( tools::Long nHorzMove, tools::Long nVertMove ) :
+ MetaAction ( MetaActionType::MOVECLIPREGION ),
+ mnHorzMove ( nHorzMove ),
+ mnVertMove ( nVertMove )
+{}
+
+void MetaMoveClipRegionAction::Execute( OutputDevice* pOut )
+{
+ if (!AllowPoint(pOut->LogicToPixel(Point(mnHorzMove, mnVertMove))))
+ return;
+ pOut->MoveClipRegion( mnHorzMove, mnVertMove );
+}
+
+rtl::Reference<MetaAction> MetaMoveClipRegionAction::Clone() const
+{
+ return new MetaMoveClipRegionAction( *this );
+}
+
+void MetaMoveClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ mnHorzMove = FRound( mnHorzMove * fScaleX );
+ mnVertMove = FRound( mnVertMove * fScaleY );
+}
+
+MetaLineColorAction::MetaLineColorAction() :
+ MetaAction ( MetaActionType::LINECOLOR ),
+ mbSet ( false )
+{}
+
+MetaLineColorAction::~MetaLineColorAction()
+{}
+
+MetaLineColorAction::MetaLineColorAction( const Color& rColor, bool bSet ) :
+ MetaAction ( MetaActionType::LINECOLOR ),
+ maColor ( rColor ),
+ mbSet ( bSet )
+{}
+
+void MetaLineColorAction::Execute( OutputDevice* pOut )
+{
+ if( mbSet )
+ pOut->SetLineColor( maColor );
+ else
+ pOut->SetLineColor();
+}
+
+rtl::Reference<MetaAction> MetaLineColorAction::Clone() const
+{
+ return new MetaLineColorAction( *this );
+}
+
+MetaFillColorAction::MetaFillColorAction() :
+ MetaAction ( MetaActionType::FILLCOLOR ),
+ mbSet ( false )
+{}
+
+MetaFillColorAction::~MetaFillColorAction()
+{}
+
+MetaFillColorAction::MetaFillColorAction( const Color& rColor, bool bSet ) :
+ MetaAction ( MetaActionType::FILLCOLOR ),
+ maColor ( rColor ),
+ mbSet ( bSet )
+{}
+
+void MetaFillColorAction::Execute( OutputDevice* pOut )
+{
+ if( mbSet )
+ pOut->SetFillColor( maColor );
+ else
+ pOut->SetFillColor();
+}
+
+rtl::Reference<MetaAction> MetaFillColorAction::Clone() const
+{
+ return new MetaFillColorAction( *this );
+}
+
+MetaTextColorAction::MetaTextColorAction() :
+ MetaAction(MetaActionType::TEXTCOLOR)
+{}
+
+MetaTextColorAction::~MetaTextColorAction()
+{}
+
+MetaTextColorAction::MetaTextColorAction( const Color& rColor ) :
+ MetaAction ( MetaActionType::TEXTCOLOR ),
+ maColor ( rColor )
+{}
+
+void MetaTextColorAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetTextColor( maColor );
+}
+
+rtl::Reference<MetaAction> MetaTextColorAction::Clone() const
+{
+ return new MetaTextColorAction( *this );
+}
+
+MetaTextFillColorAction::MetaTextFillColorAction() :
+ MetaAction ( MetaActionType::TEXTFILLCOLOR ),
+ mbSet ( false )
+{}
+
+MetaTextFillColorAction::~MetaTextFillColorAction()
+{}
+
+MetaTextFillColorAction::MetaTextFillColorAction( const Color& rColor, bool bSet ) :
+ MetaAction ( MetaActionType::TEXTFILLCOLOR ),
+ maColor ( rColor ),
+ mbSet ( bSet )
+{}
+
+void MetaTextFillColorAction::Execute( OutputDevice* pOut )
+{
+ if( mbSet )
+ pOut->SetTextFillColor( maColor );
+ else
+ pOut->SetTextFillColor();
+}
+
+rtl::Reference<MetaAction> MetaTextFillColorAction::Clone() const
+{
+ return new MetaTextFillColorAction( *this );
+}
+
+MetaTextLineColorAction::MetaTextLineColorAction() :
+ MetaAction ( MetaActionType::TEXTLINECOLOR ),
+ mbSet ( false )
+{}
+
+MetaTextLineColorAction::~MetaTextLineColorAction()
+{}
+
+MetaTextLineColorAction::MetaTextLineColorAction( const Color& rColor, bool bSet ) :
+ MetaAction ( MetaActionType::TEXTLINECOLOR ),
+ maColor ( rColor ),
+ mbSet ( bSet )
+{}
+
+void MetaTextLineColorAction::Execute( OutputDevice* pOut )
+{
+ if( mbSet )
+ pOut->SetTextLineColor( maColor );
+ else
+ pOut->SetTextLineColor();
+}
+
+rtl::Reference<MetaAction> MetaTextLineColorAction::Clone() const
+{
+ return new MetaTextLineColorAction( *this );
+}
+
+MetaOverlineColorAction::MetaOverlineColorAction() :
+ MetaAction ( MetaActionType::OVERLINECOLOR ),
+ mbSet ( false )
+{}
+
+MetaOverlineColorAction::~MetaOverlineColorAction()
+{}
+
+MetaOverlineColorAction::MetaOverlineColorAction( const Color& rColor, bool bSet ) :
+ MetaAction ( MetaActionType::OVERLINECOLOR ),
+ maColor ( rColor ),
+ mbSet ( bSet )
+{}
+
+void MetaOverlineColorAction::Execute( OutputDevice* pOut )
+{
+ if( mbSet )
+ pOut->SetOverlineColor( maColor );
+ else
+ pOut->SetOverlineColor();
+}
+
+rtl::Reference<MetaAction> MetaOverlineColorAction::Clone() const
+{
+ return new MetaOverlineColorAction( *this );
+}
+
+MetaTextAlignAction::MetaTextAlignAction() :
+ MetaAction ( MetaActionType::TEXTALIGN ),
+ maAlign ( ALIGN_TOP )
+{}
+
+MetaTextAlignAction::~MetaTextAlignAction()
+{}
+
+MetaTextAlignAction::MetaTextAlignAction( TextAlign aAlign ) :
+ MetaAction ( MetaActionType::TEXTALIGN ),
+ maAlign ( aAlign )
+{}
+
+void MetaTextAlignAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetTextAlign( maAlign );
+}
+
+rtl::Reference<MetaAction> MetaTextAlignAction::Clone() const
+{
+ return new MetaTextAlignAction( *this );
+}
+
+MetaMapModeAction::MetaMapModeAction() :
+ MetaAction(MetaActionType::MAPMODE)
+{}
+
+MetaMapModeAction::~MetaMapModeAction()
+{}
+
+MetaMapModeAction::MetaMapModeAction( const MapMode& rMapMode ) :
+ MetaAction ( MetaActionType::MAPMODE ),
+ maMapMode ( rMapMode )
+{}
+
+void MetaMapModeAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetMapMode( maMapMode );
+}
+
+rtl::Reference<MetaAction> MetaMapModeAction::Clone() const
+{
+ return new MetaMapModeAction( *this );
+}
+
+void MetaMapModeAction::Scale( double fScaleX, double fScaleY )
+{
+ Point aPoint( maMapMode.GetOrigin() );
+
+ ImplScalePoint( aPoint, fScaleX, fScaleY );
+ maMapMode.SetOrigin( aPoint );
+}
+
+MetaFontAction::MetaFontAction() :
+ MetaAction(MetaActionType::FONT)
+{}
+
+MetaFontAction::~MetaFontAction()
+{}
+
+MetaFontAction::MetaFontAction( vcl::Font aFont ) :
+ MetaAction ( MetaActionType::FONT ),
+ maFont (std::move( aFont ))
+{
+ // #96876: because RTL_TEXTENCODING_SYMBOL is often set at the OpenSymbol font,
+ // we change the textencoding to RTL_TEXTENCODING_UNICODE here, which seems
+ // to be the right way; changing the textencoding at other sources
+ // is too dangerous at the moment
+ if ( IsOpenSymbol( maFont.GetFamilyName() )
+ && ( maFont.GetCharSet() != RTL_TEXTENCODING_UNICODE ) )
+ {
+ SAL_WARN_IF(maFont.GetCharSet() == RTL_TEXTENCODING_SYMBOL, "vcl", "OpenSymbol should not have charset of RTL_TEXTENCODING_SYMBOL in new documents");
+ maFont.SetCharSet( RTL_TEXTENCODING_UNICODE );
+ }
+}
+
+void MetaFontAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetFont( maFont );
+}
+
+rtl::Reference<MetaAction> MetaFontAction::Clone() const
+{
+ return new MetaFontAction( *this );
+}
+
+void MetaFontAction::Scale( double fScaleX, double fScaleY )
+{
+ const Size aSize(
+ FRound(maFont.GetFontSize().Width() * fabs(fScaleX)),
+ FRound(maFont.GetFontSize().Height() * fabs(fScaleY)));
+ maFont.SetFontSize( aSize );
+}
+
+MetaPushAction::MetaPushAction() :
+ MetaAction ( MetaActionType::PUSH ),
+ mnFlags ( vcl::PushFlags::NONE )
+{}
+
+MetaPushAction::~MetaPushAction()
+{}
+
+MetaPushAction::MetaPushAction( vcl::PushFlags nFlags ) :
+ MetaAction ( MetaActionType::PUSH ),
+ mnFlags ( nFlags )
+{}
+
+void MetaPushAction::Execute( OutputDevice* pOut )
+{
+ pOut->Push( mnFlags );
+}
+
+rtl::Reference<MetaAction> MetaPushAction::Clone() const
+{
+ return new MetaPushAction( *this );
+}
+
+MetaPopAction::MetaPopAction() :
+ MetaAction(MetaActionType::POP)
+{}
+
+MetaPopAction::~MetaPopAction()
+{}
+
+void MetaPopAction::Execute( OutputDevice* pOut )
+{
+ pOut->Pop();
+}
+
+rtl::Reference<MetaAction> MetaPopAction::Clone() const
+{
+ return new MetaPopAction( *this );
+}
+
+MetaRasterOpAction::MetaRasterOpAction() :
+ MetaAction ( MetaActionType::RASTEROP ),
+ meRasterOp ( RasterOp::OverPaint )
+{}
+
+MetaRasterOpAction::~MetaRasterOpAction()
+{}
+
+MetaRasterOpAction::MetaRasterOpAction( RasterOp eRasterOp ) :
+ MetaAction ( MetaActionType::RASTEROP ),
+ meRasterOp ( eRasterOp )
+{
+}
+
+void MetaRasterOpAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetRasterOp( meRasterOp );
+}
+
+rtl::Reference<MetaAction> MetaRasterOpAction::Clone() const
+{
+ return new MetaRasterOpAction( *this );
+}
+
+MetaTransparentAction::MetaTransparentAction() :
+ MetaAction ( MetaActionType::Transparent ),
+ mnTransPercent ( 0 )
+{}
+
+MetaTransparentAction::~MetaTransparentAction()
+{}
+
+MetaTransparentAction::MetaTransparentAction( tools::PolyPolygon aPolyPoly, sal_uInt16 nTransPercent ) :
+ MetaAction ( MetaActionType::Transparent ),
+ maPolyPoly (std::move( aPolyPoly )),
+ mnTransPercent ( nTransPercent )
+{}
+
+void MetaTransparentAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTransparent( maPolyPoly, mnTransPercent );
+}
+
+rtl::Reference<MetaAction> MetaTransparentAction::Clone() const
+{
+ return new MetaTransparentAction( *this );
+}
+
+void MetaTransparentAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPolyPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaTransparentAction::Scale( double fScaleX, double fScaleY )
+{
+ for( sal_uInt16 i = 0, nCount = maPolyPoly.Count(); i < nCount; i++ )
+ ImplScalePoly( maPolyPoly[ i ], fScaleX, fScaleY );
+}
+
+MetaFloatTransparentAction::MetaFloatTransparentAction() :
+ MetaAction(MetaActionType::FLOATTRANSPARENT)
+{}
+
+MetaFloatTransparentAction::~MetaFloatTransparentAction()
+{}
+
+MetaFloatTransparentAction::MetaFloatTransparentAction( const GDIMetaFile& rMtf, const Point& rPos,
+ const Size& rSize, Gradient aGradient ) :
+ MetaAction ( MetaActionType::FLOATTRANSPARENT ),
+ maMtf ( rMtf ),
+ maPoint ( rPos ),
+ maSize ( rSize ),
+ maGradient (std::move( aGradient ))
+{}
+
+void MetaFloatTransparentAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTransparent( maMtf, maPoint, maSize, maGradient );
+}
+
+rtl::Reference<MetaAction> MetaFloatTransparentAction::Clone() const
+{
+ return new MetaFloatTransparentAction( *this );
+}
+
+void MetaFloatTransparentAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPoint.Move( nHorzMove, nVertMove );
+}
+
+void MetaFloatTransparentAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maPoint, maSize);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maPoint = aRectangle.TopLeft();
+ maSize = aRectangle.GetSize();
+}
+
+void MetaFloatTransparentAction::addSVGTransparencyColorStops(const basegfx::BColorStops& rSVGTransparencyColorStops)
+{
+ maSVGTransparencyColorStops = rSVGTransparencyColorStops;
+}
+
+MetaEPSAction::MetaEPSAction() :
+ MetaAction(MetaActionType::EPS)
+{}
+
+MetaEPSAction::~MetaEPSAction()
+{}
+
+MetaEPSAction::MetaEPSAction( const Point& rPoint, const Size& rSize,
+ GfxLink aGfxLink, const GDIMetaFile& rSubst ) :
+ MetaAction ( MetaActionType::EPS ),
+ maGfxLink (std::move( aGfxLink )),
+ maSubst ( rSubst ),
+ maPoint ( rPoint ),
+ maSize ( rSize )
+{}
+
+void MetaEPSAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawEPS( maPoint, maSize, maGfxLink, &maSubst );
+}
+
+rtl::Reference<MetaAction> MetaEPSAction::Clone() const
+{
+ return new MetaEPSAction( *this );
+}
+
+void MetaEPSAction::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ maPoint.Move( nHorzMove, nVertMove );
+}
+
+void MetaEPSAction::Scale( double fScaleX, double fScaleY )
+{
+ tools::Rectangle aRectangle(maPoint, maSize);
+ ImplScaleRect( aRectangle, fScaleX, fScaleY );
+ maPoint = aRectangle.TopLeft();
+ maSize = aRectangle.GetSize();
+}
+
+MetaRefPointAction::MetaRefPointAction() :
+ MetaAction ( MetaActionType::REFPOINT ),
+ mbSet ( false )
+{}
+
+MetaRefPointAction::~MetaRefPointAction()
+{}
+
+MetaRefPointAction::MetaRefPointAction( const Point& rRefPoint, bool bSet ) :
+ MetaAction ( MetaActionType::REFPOINT ),
+ maRefPoint ( rRefPoint ),
+ mbSet ( bSet )
+{}
+
+void MetaRefPointAction::Execute( OutputDevice* pOut )
+{
+ if( mbSet )
+ pOut->SetRefPoint( maRefPoint );
+ else
+ pOut->SetRefPoint();
+}
+
+rtl::Reference<MetaAction> MetaRefPointAction::Clone() const
+{
+ return new MetaRefPointAction( *this );
+}
+
+MetaCommentAction::MetaCommentAction() :
+ MetaAction ( MetaActionType::COMMENT ),
+ mnValue ( 0 )
+{
+ ImplInitDynamicData( nullptr, 0UL );
+}
+
+MetaCommentAction::MetaCommentAction( const MetaCommentAction& rAct ) :
+ MetaAction ( MetaActionType::COMMENT ),
+ maComment ( rAct.maComment ),
+ mnValue ( rAct.mnValue )
+{
+ ImplInitDynamicData( rAct.mpData.get(), rAct.mnDataSize );
+}
+
+MetaCommentAction::MetaCommentAction( OString aComment, sal_Int32 nValue, const sal_uInt8* pData, sal_uInt32 nDataSize ) :
+ MetaAction ( MetaActionType::COMMENT ),
+ maComment (std::move( aComment )),
+ mnValue ( nValue )
+{
+ ImplInitDynamicData( pData, nDataSize );
+}
+
+MetaCommentAction::~MetaCommentAction()
+{
+}
+
+void MetaCommentAction::ImplInitDynamicData( const sal_uInt8* pData, sal_uInt32 nDataSize )
+{
+ if ( nDataSize && pData )
+ {
+ mnDataSize = nDataSize;
+ mpData.reset( new sal_uInt8[ mnDataSize ] );
+ memcpy( mpData.get(), pData, mnDataSize );
+ }
+ else
+ {
+ mnDataSize = 0;
+ mpData = nullptr;
+ }
+}
+
+void MetaCommentAction::Execute( OutputDevice* pOut )
+{
+ if ( pOut->GetConnectMetaFile() )
+ {
+ pOut->GetConnectMetaFile()->AddAction( this );
+ }
+}
+
+rtl::Reference<MetaAction> MetaCommentAction::Clone() const
+{
+ return new MetaCommentAction( *this );
+}
+
+void MetaCommentAction::Move( tools::Long nXMove, tools::Long nYMove )
+{
+ if ( !(nXMove || nYMove) )
+ return;
+
+ if ( !(mnDataSize && mpData) )
+ return;
+
+ bool bPathStroke = (maComment == "XPATHSTROKE_SEQ_BEGIN");
+ if ( !(bPathStroke || maComment == "XPATHFILL_SEQ_BEGIN") )
+ return;
+
+ SvMemoryStream aMemStm( static_cast<void*>(mpData.get()), mnDataSize, StreamMode::READ );
+ SvMemoryStream aDest;
+ if ( bPathStroke )
+ {
+ SvtGraphicStroke aStroke;
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+
+ tools::Polygon aPath;
+ aStroke.getPath( aPath );
+ aPath.Move( nXMove, nYMove );
+ aStroke.setPath( aPath );
+
+ tools::PolyPolygon aStartArrow;
+ aStroke.getStartArrow(aStartArrow);
+ aStartArrow.Move(nXMove, nYMove);
+ aStroke.setStartArrow(aStartArrow);
+
+ tools::PolyPolygon aEndArrow;
+ aStroke.getEndArrow(aEndArrow);
+ aEndArrow.Move(nXMove, nYMove);
+ aStroke.setEndArrow(aEndArrow);
+
+ WriteSvtGraphicStroke( aDest, aStroke );
+ }
+ else
+ {
+ SvtGraphicFill aFill;
+ ReadSvtGraphicFill( aMemStm, aFill );
+
+ tools::PolyPolygon aPath;
+ aFill.getPath( aPath );
+ aPath.Move( nXMove, nYMove );
+ aFill.setPath( aPath );
+
+ WriteSvtGraphicFill( aDest, aFill );
+ }
+ mpData.reset();
+ ImplInitDynamicData( static_cast<const sal_uInt8*>( aDest.GetData() ), aDest.Tell() );
+}
+
+// SJ: 25.07.06 #i56656# we are not able to mirror certain kind of
+// comments properly, especially the XPATHSTROKE and XPATHFILL lead to
+// problems, so it is better to remove these comments when mirroring
+// FIXME: fake comment to apply the next hunk in the right location
+void MetaCommentAction::Scale( double fXScale, double fYScale )
+{
+ if (( fXScale == 1.0 ) && ( fYScale == 1.0 ))
+ return;
+
+ if ( !(mnDataSize && mpData) )
+ return;
+
+ bool bPathStroke = (maComment == "XPATHSTROKE_SEQ_BEGIN");
+ if ( bPathStroke || maComment == "XPATHFILL_SEQ_BEGIN" )
+ {
+ SvMemoryStream aMemStm( static_cast<void*>(mpData.get()), mnDataSize, StreamMode::READ );
+ SvMemoryStream aDest;
+ if ( bPathStroke )
+ {
+ SvtGraphicStroke aStroke;
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+ aStroke.scale( fXScale, fYScale );
+ WriteSvtGraphicStroke( aDest, aStroke );
+ }
+ else
+ {
+ SvtGraphicFill aFill;
+ ReadSvtGraphicFill( aMemStm, aFill );
+ tools::PolyPolygon aPath;
+ aFill.getPath( aPath );
+ aPath.Scale( fXScale, fYScale );
+ aFill.setPath( aPath );
+ WriteSvtGraphicFill( aDest, aFill );
+ }
+ mpData.reset();
+ ImplInitDynamicData( static_cast<const sal_uInt8*>( aDest.GetData() ), aDest.Tell() );
+ } else if( maComment == "EMF_PLUS_HEADER_INFO" ){
+ SvMemoryStream aMemStm( static_cast<void*>(mpData.get()), mnDataSize, StreamMode::READ );
+ SvMemoryStream aDest;
+
+ sal_Int32 nLeft(0), nRight(0), nTop(0), nBottom(0);
+ sal_Int32 nPixX(0), nPixY(0), nMillX(0), nMillY(0);
+ float m11(0), m12(0), m21(0), m22(0), mdx(0), mdy(0);
+
+ // read data
+ aMemStm.ReadInt32( nLeft ).ReadInt32( nTop ).ReadInt32( nRight ).ReadInt32( nBottom );
+ aMemStm.ReadInt32( nPixX ).ReadInt32( nPixY ).ReadInt32( nMillX ).ReadInt32( nMillY );
+ aMemStm.ReadFloat( m11 ).ReadFloat( m12 ).ReadFloat( m21 ).ReadFloat( m22 ).ReadFloat( mdx ).ReadFloat( mdy );
+
+ // add scale to the transformation
+ m11 *= fXScale;
+ m12 *= fXScale;
+ m22 *= fYScale;
+ m21 *= fYScale;
+
+ // prepare new data
+ aDest.WriteInt32( nLeft ).WriteInt32( nTop ).WriteInt32( nRight ).WriteInt32( nBottom );
+ aDest.WriteInt32( nPixX ).WriteInt32( nPixY ).WriteInt32( nMillX ).WriteInt32( nMillY );
+ aDest.WriteFloat( m11 ).WriteFloat( m12 ).WriteFloat( m21 ).WriteFloat( m22 ).WriteFloat( mdx ).WriteFloat( mdy );
+
+ // save them
+ ImplInitDynamicData( static_cast<const sal_uInt8*>( aDest.GetData() ), aDest.Tell() );
+ }
+}
+
+MetaLayoutModeAction::MetaLayoutModeAction() :
+ MetaAction ( MetaActionType::LAYOUTMODE ),
+ mnLayoutMode( vcl::text::ComplexTextLayoutFlags::Default )
+{}
+
+MetaLayoutModeAction::~MetaLayoutModeAction()
+{}
+
+MetaLayoutModeAction::MetaLayoutModeAction( vcl::text::ComplexTextLayoutFlags nLayoutMode ) :
+ MetaAction ( MetaActionType::LAYOUTMODE ),
+ mnLayoutMode( nLayoutMode )
+{}
+
+void MetaLayoutModeAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetLayoutMode( mnLayoutMode );
+}
+
+rtl::Reference<MetaAction> MetaLayoutModeAction::Clone() const
+{
+ return new MetaLayoutModeAction( *this );
+}
+
+MetaTextLanguageAction::MetaTextLanguageAction() :
+ MetaAction ( MetaActionType::TEXTLANGUAGE ),
+ meTextLanguage( LANGUAGE_DONTKNOW )
+{}
+
+MetaTextLanguageAction::~MetaTextLanguageAction()
+{}
+
+MetaTextLanguageAction::MetaTextLanguageAction( LanguageType eTextLanguage ) :
+ MetaAction ( MetaActionType::TEXTLANGUAGE ),
+ meTextLanguage( eTextLanguage )
+{}
+
+void MetaTextLanguageAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetDigitLanguage( meTextLanguage );
+}
+
+rtl::Reference<MetaAction> MetaTextLanguageAction::Clone() const
+{
+ return new MetaTextLanguageAction( *this );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/mtfxmldump.cxx b/vcl/source/gdi/mtfxmldump.cxx
new file mode 100644
index 0000000000..e6b1adc80e
--- /dev/null
+++ b/vcl/source/gdi/mtfxmldump.cxx
@@ -0,0 +1,1568 @@
+/* -*- 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/.
+ */
+
+#include <vcl/mtfxmldump.hxx>
+#include <tools/XmlWriter.hxx>
+#include <tools/fract.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+
+#include <rtl/string.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <comphelper/hash.hxx>
+
+#include <sstream>
+
+namespace
+{
+
+OUString collectPushFlags(vcl::PushFlags nFlags)
+{
+ if ((nFlags & vcl::PushFlags::ALL) == vcl::PushFlags::ALL)
+ return "PushAll";
+ else if ((nFlags & PUSH_ALLFONT) == PUSH_ALLFONT)
+ return "PushAllFont";
+
+ std::vector<OUString> aStrings;
+
+ if (nFlags & vcl::PushFlags::LINECOLOR)
+ aStrings.emplace_back("PushLineColor");
+ if (nFlags & vcl::PushFlags::FILLCOLOR)
+ aStrings.emplace_back("PushFillColor");
+ if (nFlags & vcl::PushFlags::FONT)
+ aStrings.emplace_back("PushFont");
+ if (nFlags & vcl::PushFlags::TEXTCOLOR)
+ aStrings.emplace_back("PushTextColor");
+ if (nFlags & vcl::PushFlags::MAPMODE)
+ aStrings.emplace_back("PushMapMode");
+ if (nFlags & vcl::PushFlags::CLIPREGION)
+ aStrings.emplace_back("PushClipRegion");
+ if (nFlags & vcl::PushFlags::RASTEROP)
+ aStrings.emplace_back("PushRasterOp");
+ if (nFlags & vcl::PushFlags::TEXTFILLCOLOR)
+ aStrings.emplace_back("PushTextFillColor");
+ if (nFlags & vcl::PushFlags::TEXTALIGN)
+ aStrings.emplace_back("PushTextAlign");
+ if (nFlags & vcl::PushFlags::REFPOINT)
+ aStrings.emplace_back("PushRefPoint");
+ if (nFlags & vcl::PushFlags::TEXTLINECOLOR)
+ aStrings.emplace_back("PushTextLineColor");
+ if (nFlags & vcl::PushFlags::TEXTLAYOUTMODE)
+ aStrings.emplace_back("PushTextLayoutMode");
+ if (nFlags & vcl::PushFlags::TEXTLANGUAGE)
+ aStrings.emplace_back("PushTextLanguage");
+ if (nFlags & vcl::PushFlags::OVERLINECOLOR)
+ aStrings.emplace_back("PushOverlineColor");
+ if (nFlags & vcl::PushFlags::RTLENABLED)
+ aStrings.emplace_back("PushRTLEnabled");
+
+ OUString aString;
+
+ if (aStrings.empty())
+ return aString;
+
+ aString = aStrings[0];
+ for (size_t i = 1; i < aStrings.size(); ++i)
+ {
+ aString += ", " + aStrings[i];
+ }
+ return aString;
+}
+
+OUString convertDrawTextFlagsToString(DrawTextFlags eDrawTextFlags)
+{
+ std::vector<OUString> aStrings;
+ if (eDrawTextFlags & DrawTextFlags::Disable)
+ aStrings.emplace_back("Disable");
+ if (eDrawTextFlags & DrawTextFlags::Mnemonic)
+ aStrings.emplace_back("Mnemonic");
+ if (eDrawTextFlags & DrawTextFlags::Mono)
+ aStrings.emplace_back("Mono");
+ if (eDrawTextFlags & DrawTextFlags::Clip)
+ aStrings.emplace_back("Clip");
+ if (eDrawTextFlags & DrawTextFlags::Left)
+ aStrings.emplace_back("Left");
+ if (eDrawTextFlags & DrawTextFlags::Center)
+ aStrings.emplace_back("Center");
+ if (eDrawTextFlags & DrawTextFlags::Right)
+ aStrings.emplace_back("Right");
+ if (eDrawTextFlags & DrawTextFlags::Top)
+ aStrings.emplace_back("Top");
+ if (eDrawTextFlags & DrawTextFlags::VCenter)
+ aStrings.emplace_back("VCenter");
+ if (eDrawTextFlags & DrawTextFlags::Bottom)
+ aStrings.emplace_back("Bottom");
+ if (eDrawTextFlags & DrawTextFlags::EndEllipsis)
+ aStrings.emplace_back("EndEllipsis");
+ if (eDrawTextFlags & DrawTextFlags::PathEllipsis)
+ aStrings.emplace_back("PathEllipsis");
+ if (eDrawTextFlags & DrawTextFlags::MultiLine)
+ aStrings.emplace_back("MultiLine");
+ if (eDrawTextFlags & DrawTextFlags::WordBreak)
+ aStrings.emplace_back("WordBreak");
+ if (eDrawTextFlags & DrawTextFlags::NewsEllipsis)
+ aStrings.emplace_back("NewsEllipsis");
+ if (eDrawTextFlags & DrawTextFlags::WordBreakHyphenation)
+ aStrings.emplace_back("WordBreakHyphenation");
+ if (eDrawTextFlags & DrawTextFlags::CenterEllipsis)
+ aStrings.emplace_back("CenterEllipsis");
+
+ OUString aString;
+
+ if (aStrings.empty())
+ return "None";
+
+ aString = aStrings[0];
+ for (size_t i = 1; i < aStrings.size(); ++i)
+ {
+ aString += " " + aStrings[i];
+ }
+ return aString;
+};
+
+OUString convertRopToString(RasterOp eRop)
+{
+ switch (eRop)
+ {
+ case RasterOp::OverPaint: return "overpaint";
+ case RasterOp::Xor: return "xor";
+ case RasterOp::N0: return "0";
+ case RasterOp::N1: return "1";
+ case RasterOp::Invert: return "invert";
+ }
+ return OUString();
+}
+
+OUString convertTextAlignToString(TextAlign eAlign)
+{
+ switch (eAlign)
+ {
+ case ALIGN_BASELINE: return "baseline";
+ case ALIGN_BOTTOM: return "bottom";
+ case ALIGN_TOP: return "top";
+ case TextAlign_FORCE_EQUAL_SIZE: return "equalsize";
+ }
+ return OUString();
+}
+
+OUString convertColorToString(Color aColor)
+{
+ OUString aRGBString = aColor.AsRGBHexString();
+ return "#" + aRGBString;
+}
+
+OUString convertLineStyleToString(LineStyle eAlign)
+{
+ switch (eAlign)
+ {
+ case LineStyle::NONE: return "none";
+ case LineStyle::Solid: return "solid";
+ case LineStyle::Dash: return "dash";
+ default: break;
+ }
+ return OUString();
+}
+
+OUString convertLineJoinToString(basegfx::B2DLineJoin eJoin)
+{
+ switch (eJoin)
+ {
+ default:
+ case basegfx::B2DLineJoin::NONE: return "none";
+ case basegfx::B2DLineJoin::Bevel: return "bevel";
+ case basegfx::B2DLineJoin::Miter: return "miter";
+ case basegfx::B2DLineJoin::Round: return "round";
+ }
+}
+
+OUString convertLineCapToString(css::drawing::LineCap eCap)
+{
+ switch (eCap)
+ {
+ default:
+ case css::drawing::LineCap_BUTT: return "butt";
+ case css::drawing::LineCap_ROUND: return "round";
+ case css::drawing::LineCap_SQUARE: return "square";
+ }
+}
+
+OUString convertPolygonFlags(PolyFlags eFlags)
+{
+ switch (eFlags)
+ {
+ default:
+ case PolyFlags::Normal: return "normal";
+ case PolyFlags::Control: return "control";
+ case PolyFlags::Smooth: return "smooth";
+ case PolyFlags::Symmetric: return "symmetric";
+ }
+}
+
+OUString convertFontWeightToString(FontWeight eFontWeight)
+{
+ switch (eFontWeight)
+ {
+ case WEIGHT_DONTKNOW: return "unknown";
+ case WEIGHT_THIN: return "thin";
+ case WEIGHT_ULTRALIGHT: return "ultralight";
+ case WEIGHT_LIGHT: return "light";
+ case WEIGHT_SEMILIGHT: return "semilight";
+ case WEIGHT_NORMAL: return "normal";
+ case WEIGHT_MEDIUM: return "medium";
+ case WEIGHT_SEMIBOLD: return "semibold";
+ case WEIGHT_BOLD: return "bold";
+ case WEIGHT_ULTRABOLD: return "ultrabold";
+ case WEIGHT_BLACK: return "black";
+ case FontWeight_FORCE_EQUAL_SIZE: return "equalsize";
+ }
+ return OUString();
+}
+
+OUString convertFontStrikeoutToString(FontStrikeout eFontStrikeout)
+{
+ switch (eFontStrikeout)
+ {
+ case STRIKEOUT_NONE: return "none";
+ case STRIKEOUT_SINGLE: return "single";
+ case STRIKEOUT_DOUBLE: return "double";
+ case STRIKEOUT_DONTKNOW: return "dontknow";
+ case STRIKEOUT_BOLD: return "bold";
+ case STRIKEOUT_SLASH: return "slash";
+ case STRIKEOUT_X: return "x";
+ case FontStrikeout_FORCE_EQUAL_SIZE: return "equalsize";
+ }
+ return OUString();
+}
+
+OUString convertFontLineStyleToString(FontLineStyle eFontLineStyle)
+{
+ switch (eFontLineStyle)
+ {
+ case LINESTYLE_NONE: return "none";
+ case LINESTYLE_SINGLE: return "single";
+ case LINESTYLE_DOUBLE: return "double";
+ case LINESTYLE_DOTTED: return "dotted";
+ case LINESTYLE_DONTKNOW: return "dontknow";
+ case LINESTYLE_DASH: return "dash";
+ case LINESTYLE_LONGDASH: return "longdash";
+ case LINESTYLE_DASHDOT: return "dashdot";
+ case LINESTYLE_DASHDOTDOT: return "dashdotdot";
+ case LINESTYLE_SMALLWAVE: return "smallwave";
+ case LINESTYLE_WAVE: return "wave";
+ case LINESTYLE_DOUBLEWAVE: return "doublewave";
+ case LINESTYLE_BOLD: return "bold";
+ case LINESTYLE_BOLDDOTTED: return "bolddotted";
+ case LINESTYLE_BOLDDASH: return "bolddash";
+ case LINESTYLE_BOLDLONGDASH: return "boldlongdash";
+ case LINESTYLE_BOLDDASHDOT: return "bolddashdot";
+ case LINESTYLE_BOLDDASHDOTDOT: return "bolddashdotdot";
+ case LINESTYLE_BOLDWAVE: return "boldwave";
+ case FontLineStyle_FORCE_EQUAL_SIZE: return "equalsize";
+ }
+ return OUString();
+}
+
+OString convertLineStyleToString(const MetaActionType nActionType)
+{
+ switch (nActionType)
+ {
+ case MetaActionType::NONE: return "null"_ostr;
+ case MetaActionType::PIXEL: return "pixel"_ostr;
+ case MetaActionType::POINT: return "point"_ostr;
+ case MetaActionType::LINE: return "line"_ostr;
+ case MetaActionType::RECT: return "rect"_ostr;
+ case MetaActionType::ROUNDRECT: return "roundrect"_ostr;
+ case MetaActionType::ELLIPSE: return "ellipse"_ostr;
+ case MetaActionType::ARC: return "arc"_ostr;
+ case MetaActionType::PIE: return "pie"_ostr;
+ case MetaActionType::CHORD: return "chord"_ostr;
+ case MetaActionType::POLYLINE: return "polyline"_ostr;
+ case MetaActionType::POLYGON: return "polygon"_ostr;
+ case MetaActionType::POLYPOLYGON: return "polypolygon"_ostr;
+ case MetaActionType::TEXT: return "text"_ostr;
+ case MetaActionType::TEXTARRAY: return "textarray"_ostr;
+ case MetaActionType::STRETCHTEXT: return "stretchtext"_ostr;
+ case MetaActionType::TEXTRECT: return "textrect"_ostr;
+ case MetaActionType::TEXTLINE: return "textline"_ostr;
+ case MetaActionType::BMP: return "bmp"_ostr;
+ case MetaActionType::BMPSCALE: return "bmpscale"_ostr;
+ case MetaActionType::BMPSCALEPART: return "bmpscalepart"_ostr;
+ case MetaActionType::BMPEX: return "bmpex"_ostr;
+ case MetaActionType::BMPEXSCALE: return "bmpexscale"_ostr;
+ case MetaActionType::BMPEXSCALEPART: return "bmpexscalepart"_ostr;
+ case MetaActionType::MASK: return "mask"_ostr;
+ case MetaActionType::MASKSCALE: return "maskscale"_ostr;
+ case MetaActionType::MASKSCALEPART: return "maskscalepart"_ostr;
+ case MetaActionType::GRADIENT: return "gradient"_ostr;
+ case MetaActionType::GRADIENTEX: return "gradientex"_ostr;
+ case MetaActionType::HATCH: return "hatch"_ostr;
+ case MetaActionType::WALLPAPER: return "wallpaper"_ostr;
+ case MetaActionType::CLIPREGION: return "clipregion"_ostr;
+ case MetaActionType::ISECTRECTCLIPREGION: return "sectrectclipregion"_ostr;
+ case MetaActionType::ISECTREGIONCLIPREGION: return "sectregionclipregion"_ostr;
+ case MetaActionType::MOVECLIPREGION: return "moveclipregion"_ostr;
+ case MetaActionType::LINECOLOR: return "linecolor"_ostr;
+ case MetaActionType::FILLCOLOR: return "fillcolor"_ostr;
+ case MetaActionType::TEXTCOLOR: return "textcolor"_ostr;
+ case MetaActionType::TEXTFILLCOLOR: return "textfillcolor"_ostr;
+ case MetaActionType::TEXTLINECOLOR: return "textlinecolor"_ostr;
+ case MetaActionType::OVERLINECOLOR: return "overlinecolor"_ostr;
+ case MetaActionType::TEXTALIGN: return "textalign"_ostr;
+ case MetaActionType::MAPMODE: return "mapmode"_ostr;
+ case MetaActionType::FONT: return "font"_ostr;
+ case MetaActionType::PUSH: return "push"_ostr;
+ case MetaActionType::POP: return "pop"_ostr;
+ case MetaActionType::RASTEROP: return "rasterop"_ostr;
+ case MetaActionType::Transparent: return "transparent"_ostr;
+ case MetaActionType::FLOATTRANSPARENT: return "floattransparent"_ostr;
+ case MetaActionType::EPS: return "eps"_ostr;
+ case MetaActionType::REFPOINT: return "refpoint"_ostr;
+ case MetaActionType::COMMENT: return "comment"_ostr;
+ case MetaActionType::LAYOUTMODE: return "layoutmode"_ostr;
+ case MetaActionType::TEXTLANGUAGE: return "textlanguage"_ostr;
+ }
+ return ""_ostr;
+}
+
+OUString convertBitmapExTransparentType(BitmapEx const & rBitmapEx)
+{
+ if (rBitmapEx.IsAlpha())
+ return "bitmap";
+ else
+ return "none";
+}
+
+OUString convertMapUnitToString(MapUnit eUnit)
+{
+ switch (eUnit)
+ {
+ default:
+ case MapUnit::LASTENUMDUMMY: return "LASTENUMDUMMY";
+ case MapUnit::Map1000thInch: return "Map1000thInch";
+ case MapUnit::Map100thInch: return "Map100thInch";
+ case MapUnit::Map100thMM: return "Map100thMM";
+ case MapUnit::Map10thInch: return "Map10thInch";
+ case MapUnit::Map10thMM: return "Map10thMM";
+ case MapUnit::MapAppFont: return "MapAppFont";
+ case MapUnit::MapCM: return "MapCM";
+ case MapUnit::MapInch: return "MapInch";
+ case MapUnit::MapMM: return "MapMM";
+ case MapUnit::MapPixel: return "MapPixel";
+ case MapUnit::MapPoint: return "MapPoint";
+ case MapUnit::MapRelative: return "MapRelative";
+ case MapUnit::MapSysFont: return "MapSysFont";
+ case MapUnit::MapTwip: return "MapTwip";
+ }
+}
+
+OUString convertFractionToString(const Fraction& aFraction)
+{
+ std::stringstream ss;
+
+ ss << aFraction;
+
+ return OUString::createFromAscii(ss.str());
+}
+
+OUString convertGradientStyleToOUString(css::awt::GradientStyle eStyle)
+{
+ switch (eStyle)
+ {
+ case css::awt::GradientStyle_LINEAR: return "Linear";
+ case css::awt::GradientStyle_AXIAL: return "Axial";
+ case css::awt::GradientStyle_RADIAL: return "Radial";
+ case css::awt::GradientStyle_ELLIPTICAL: return "Elliptical";
+ case css::awt::GradientStyle_SQUARE: return "Square";
+ case css::awt::GradientStyle_RECT: return "Rect";
+ case css::awt::GradientStyle::GradientStyle_MAKE_FIXED_SIZE: return "ForceEqualSize";
+ }
+ return OUString();
+}
+
+OUString convertHatchStyle(HatchStyle eStyle)
+{
+ switch (eStyle)
+ {
+ case HatchStyle::Single: return "Single";
+ case HatchStyle::Double: return "Double";
+ case HatchStyle::Triple: return "Triple";
+ case HatchStyle::FORCE_EQUAL_SIZE: return "ForceEqualSize";
+ }
+ return OUString();
+}
+
+OUString convertLanguageTypeToString(LanguageType rLanguageType)
+{
+ std::stringstream ss;
+ ss << std::hex << std::setfill ('0') << std::setw(4) << rLanguageType.get();
+ return "#" + OUString::createFromAscii(ss.str());
+}
+
+OUString convertWallpaperStyleToString(WallpaperStyle eWallpaperStyle)
+{
+ switch (eWallpaperStyle)
+ {
+ case WallpaperStyle::NONE: return "NONE";
+ case WallpaperStyle::Tile: return "Tile";
+ case WallpaperStyle::Center: return "Center";
+ case WallpaperStyle::Scale: return "Scale";
+ case WallpaperStyle::TopLeft: return "TopLeft";
+ case WallpaperStyle::Top: return "Top";
+ case WallpaperStyle::TopRight: return "TopRight";
+ case WallpaperStyle::Left: return "Left";
+ case WallpaperStyle::Right: return "Right";
+ case WallpaperStyle::BottomLeft: return "BottomLeft";
+ case WallpaperStyle::Bottom: return "Bottom";
+ case WallpaperStyle::BottomRight: return "BottomRight";
+ case WallpaperStyle::ApplicationGradient: return "ApplicationGradient";
+ }
+ return OUString();
+}
+
+OUString convertPixelFormatToString(vcl::PixelFormat ePixelFormat)
+{
+ switch (ePixelFormat)
+ {
+ case vcl::PixelFormat::INVALID: return "INVALID";
+ case vcl::PixelFormat::N8_BPP: return "8BPP";
+ case vcl::PixelFormat::N24_BPP: return "24BPP";
+ case vcl::PixelFormat::N32_BPP: return "32BPP";
+ }
+ return OUString();
+}
+
+OUString convertComplexTestLayoutFlags(vcl::text::ComplexTextLayoutFlags nFlags)
+{
+ if (nFlags == vcl::text::ComplexTextLayoutFlags::Default)
+ return "Default";
+
+ std::vector<OUString> aStrings;
+
+ if (nFlags & vcl::text::ComplexTextLayoutFlags::BiDiRtl)
+ aStrings.emplace_back("BiDiRtl");
+ if (nFlags & vcl::text::ComplexTextLayoutFlags::BiDiStrong)
+ aStrings.emplace_back("BiDiStrong");
+ if (nFlags & vcl::text::ComplexTextLayoutFlags::TextOriginLeft)
+ aStrings.emplace_back("TextOriginLeft");
+ if (nFlags & vcl::text::ComplexTextLayoutFlags::TextOriginRight)
+ aStrings.emplace_back("TextOriginRight");
+
+ OUString aString;
+
+ if (aStrings.empty())
+ return aString;
+
+ aString = aStrings[0];
+ for (size_t i = 1; i < aStrings.size(); ++i)
+ {
+ aString += ", " + aStrings[i];
+ }
+ return aString;
+}
+
+OUString convertGfxLinkTypeToString(GfxLinkType eGfxLinkType)
+{
+ switch(eGfxLinkType)
+ {
+ case GfxLinkType::EpsBuffer: return "EpsBuffer";
+ case GfxLinkType::NativeBmp: return "NativeBmp";
+ case GfxLinkType::NativeGif: return "NativeGif";
+ case GfxLinkType::NativeJpg: return "NativeJpg";
+ case GfxLinkType::NativeMet: return "NativeMet";
+ case GfxLinkType::NativeMov: return "NativeMov";
+ case GfxLinkType::NativePct: return "NativePct";
+ case GfxLinkType::NativePdf: return "NativePdf";
+ case GfxLinkType::NativePng: return "NativePng";
+ case GfxLinkType::NativeSvg: return "NativeSvg";
+ case GfxLinkType::NativeTif: return "NativeTif";
+ case GfxLinkType::NativeWmf: return "NativeWmf";
+ case GfxLinkType::NativeWebp: return "NativeWebp";
+ case GfxLinkType::NONE: return "None";
+ }
+ return OUString();
+}
+
+OUString hex32(sal_uInt32 nNumber)
+{
+ std::stringstream ss;
+ ss << std::hex << std::setfill('0') << std::setw(8) << nNumber;
+ return OUString::createFromAscii(ss.str());
+}
+
+OUString toHexString(const sal_uInt8* nData, sal_uInt32 nDataSize){
+
+ std::stringstream aStrm;
+ for (sal_uInt32 i = 0; i < nDataSize; i++)
+ {
+ aStrm << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(nData[i]);
+ }
+
+ return OUString::createFromAscii(aStrm.str());
+}
+
+void writePoint(tools::XmlWriter& rWriter, Point const& rPoint)
+{
+ rWriter.attribute("x", rPoint.X());
+ rWriter.attribute("y", rPoint.Y());
+}
+
+void writeStartPoint(tools::XmlWriter& rWriter, Point const& rPoint)
+{
+ rWriter.attribute("startx", rPoint.X());
+ rWriter.attribute("starty", rPoint.Y());
+}
+
+void writeEndPoint(tools::XmlWriter& rWriter, Point const& rPoint)
+{
+ rWriter.attribute("endx", rPoint.X());
+ rWriter.attribute("endy", rPoint.Y());
+}
+
+void writeSize(tools::XmlWriter& rWriter, Size const& rSize)
+{
+ rWriter.attribute("width", rSize.Width());
+ rWriter.attribute("height", rSize.Height());
+}
+
+void writeRectangle(tools::XmlWriter& rWriter, tools::Rectangle const& rRectangle)
+{
+ rWriter.attribute("left", rRectangle.Left());
+ rWriter.attribute("top", rRectangle.Top());
+ if (rRectangle.IsWidthEmpty())
+ rWriter.attribute("right", "empty"_ostr);
+ else
+ rWriter.attribute("right", rRectangle.Right());
+ if (rRectangle.IsHeightEmpty())
+ rWriter.attribute("bottom", "empty"_ostr);
+ else
+ rWriter.attribute("bottom", rRectangle.Bottom());
+}
+
+void writeMapMode(tools::XmlWriter& rWriter, MapMode const& rMapMode)
+{
+ rWriter.attribute("mapunit", convertMapUnitToString( rMapMode.GetMapUnit() ));
+ writePoint(rWriter, rMapMode.GetOrigin());
+ rWriter.attribute("scalex", convertFractionToString(rMapMode.GetScaleX()));
+ rWriter.attribute("scaley", convertFractionToString(rMapMode.GetScaleY()));
+}
+
+void writeLineInfo(tools::XmlWriter& rWriter, LineInfo const& rLineInfo)
+{
+ rWriter.attribute("style", convertLineStyleToString(rLineInfo.GetStyle()));
+ rWriter.attribute("width", rLineInfo.GetWidth());
+ rWriter.attribute("dashlen", rLineInfo.GetDashLen());
+ rWriter.attribute("dashcount", rLineInfo.GetDashCount());
+ rWriter.attribute("dotlen", rLineInfo.GetDotLen());
+ rWriter.attribute("dotcount", rLineInfo.GetDotCount());
+ rWriter.attribute("distance", rLineInfo.GetDistance());
+ rWriter.attribute("join", convertLineJoinToString(rLineInfo.GetLineJoin()));
+ rWriter.attribute("cap", convertLineCapToString(rLineInfo.GetLineCap()));
+}
+
+void writeGradient(tools::XmlWriter& rWriter, Gradient const& rGradient)
+{
+ rWriter.attribute("style", convertGradientStyleToOUString(rGradient.GetStyle()));
+ rWriter.attribute("startcolor", convertColorToString(rGradient.GetStartColor()));
+ rWriter.attribute("endcolor", convertColorToString(rGradient.GetEndColor()));
+ rWriter.attribute("angle", rGradient.GetAngle().get());
+ rWriter.attribute("border", rGradient.GetBorder());
+ rWriter.attribute("offsetx", rGradient.GetOfsX());
+ rWriter.attribute("offsety", rGradient.GetOfsY());
+ rWriter.attribute("startintensity", rGradient.GetStartIntensity());
+ rWriter.attribute("endintensity", rGradient.GetEndIntensity());
+ rWriter.attribute("steps", rGradient.GetSteps());
+}
+
+OString toHexString(const std::vector<unsigned char>& a)
+{
+ std::stringstream aStrm;
+ for (auto& i : a)
+ {
+ aStrm << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(i);
+ }
+
+ return OString(aStrm.str());
+}
+
+void writeBitmapContentChecksum(tools::XmlWriter& rWriter, Bitmap const& rBitmap)
+{
+ Bitmap aBitmap(rBitmap);
+
+ comphelper::Hash aHashEngine(comphelper::HashType::SHA1);
+ BitmapScopedReadAccess pReadAccess(aBitmap);
+ assert(pReadAccess);
+
+ for (tools::Long y = 0 ; y < pReadAccess->Height() ; ++y)
+ {
+ for (tools::Long x = 0 ; x < pReadAccess->Width() ; ++x)
+ {
+ BitmapColor aColor = pReadAccess->GetColor(y, x);
+ sal_uInt8 r = aColor.GetRed();
+ sal_uInt8 g = aColor.GetGreen();
+ sal_uInt8 b = aColor.GetBlue();
+ sal_uInt8 a = aColor.GetAlpha();
+ aHashEngine.update(&r, 1);
+ aHashEngine.update(&g, 1);
+ aHashEngine.update(&b, 1);
+ aHashEngine.update(&a, 1);
+ }
+ }
+ std::vector<unsigned char> aVector = aHashEngine.finalize();
+ rWriter.attribute("contentchecksum", toHexString(aVector));
+}
+
+void writeBitmap(tools::XmlWriter& rWriter, Bitmap const& rBitmap)
+{
+ writeBitmapContentChecksum(rWriter, rBitmap);
+ rWriter.attribute("bitmapwidth", rBitmap.GetSizePixel().Width());
+ rWriter.attribute("bitmapheight", rBitmap.GetSizePixel().Height());
+ rWriter.attribute("pixelformat", convertPixelFormatToString(rBitmap.getPixelFormat()));
+ rWriter.attribute("crc", hex32(rBitmap.GetChecksum()));
+}
+
+} // anonymous namespace
+
+MetafileXmlDump::MetafileXmlDump()
+{
+ maFilter.fill(false);
+}
+
+void MetafileXmlDump::filterActionType(const MetaActionType nActionType, bool bShouldFilter)
+{
+ maFilter[nActionType] = bShouldFilter;
+}
+
+void MetafileXmlDump::filterAllActionTypes()
+{
+ maFilter.fill(true);
+}
+
+void MetafileXmlDump::dump(const GDIMetaFile& rMetaFile, SvStream& rStream)
+{
+ tools::XmlWriter aWriter(&rStream);
+ aWriter.startDocument();
+ aWriter.startElement("metafile");
+
+ writeXml(rMetaFile, aWriter);
+
+ aWriter.endElement();
+ aWriter.endDocument();
+}
+
+void MetafileXmlDump::writeXml(const GDIMetaFile& rMetaFile, tools::XmlWriter& rWriter)
+{
+ MapMode aMtfMapMode = rMetaFile.GetPrefMapMode();
+ rWriter.attribute("mapunit", convertMapUnitToString(aMtfMapMode.GetMapUnit()));
+ writePoint(rWriter, aMtfMapMode.GetOrigin());
+ rWriter.attribute("scalex", convertFractionToString(aMtfMapMode.GetScaleX()));
+ rWriter.attribute("scaley", convertFractionToString(aMtfMapMode.GetScaleY()));
+
+ Size aMtfSize = rMetaFile.GetPrefSize();
+ writeSize(rWriter, aMtfSize);
+
+ for(size_t nAction = 0; nAction < rMetaFile.GetActionSize(); ++nAction)
+ {
+ MetaAction* pAction = rMetaFile.GetAction(nAction);
+ const MetaActionType nActionType = pAction->GetType();
+ if (maFilter[nActionType])
+ continue;
+
+ OString sCurrentElementTag = convertLineStyleToString(nActionType);
+
+ switch (nActionType)
+ {
+ case MetaActionType::NONE:
+ {
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::PIXEL:
+ {
+ auto* pMetaAction = static_cast<MetaPixelAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMetaAction->GetPoint());
+ rWriter.attribute("color", convertColorToString(pMetaAction->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::POINT:
+ {
+ auto* pMetaAction = static_cast<MetaPointAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMetaAction->GetPoint());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ MetaLineAction* pMetaLineAction = static_cast<MetaLineAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeStartPoint(rWriter, pMetaLineAction->GetStartPoint());
+ writeEndPoint(rWriter, pMetaLineAction->GetEndPoint());
+
+ writeLineInfo(rWriter, pMetaLineAction->GetLineInfo());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ MetaRectAction* pMetaAction = static_cast<MetaRectAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMetaAction->GetRect());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ auto pMetaAction = static_cast<MetaRoundRectAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMetaAction->GetRect());
+ rWriter.attribute("horizontalround", pMetaAction->GetHorzRound());
+ rWriter.attribute("verticalround", pMetaAction->GetVertRound());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ auto pMetaAction = static_cast<MetaEllipseAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMetaAction->GetRect());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::ARC:
+ {
+ auto pMetaAction = static_cast<MetaArcAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMetaAction->GetRect());
+ writeStartPoint(rWriter, pMetaAction->GetStartPoint());
+ writeEndPoint(rWriter, pMetaAction->GetEndPoint());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::PIE:
+ {
+ auto pMetaAction = static_cast<MetaPieAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMetaAction->GetRect());
+ writeStartPoint(rWriter, pMetaAction->GetStartPoint());
+ writeEndPoint(rWriter, pMetaAction->GetEndPoint());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::CHORD:
+ {
+ auto pMetaAction = static_cast<MetaChordAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMetaAction->GetRect());
+ writeStartPoint(rWriter, pMetaAction->GetStartPoint());
+ writeEndPoint(rWriter, pMetaAction->GetEndPoint());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ MetaPolyLineAction* pMetaPolyLineAction = static_cast<MetaPolyLineAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ writeLineInfo(rWriter, pMetaPolyLineAction->GetLineInfo());
+
+ tools::Polygon aPolygon = pMetaPolyLineAction->GetPolygon();
+ bool bFlags = aPolygon.HasFlags();
+ for (sal_uInt16 i = 0; i < aPolygon.GetSize(); i++)
+ {
+ rWriter.startElement("point");
+ writePoint(rWriter, aPolygon[i]);
+ if (bFlags)
+ rWriter.attribute("flags", convertPolygonFlags(aPolygon.GetFlags(i)));
+ rWriter.endElement();
+ }
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::POLYGON:
+ {
+ MetaPolygonAction* pMetaPolygonAction = static_cast<MetaPolygonAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ tools::Polygon aPolygon = pMetaPolygonAction->GetPolygon();
+ bool bFlags = aPolygon.HasFlags();
+ for (sal_uInt16 i = 0; i < aPolygon.GetSize(); i++)
+ {
+ rWriter.startElement("point");
+ writePoint(rWriter, aPolygon[i]);
+ if (bFlags)
+ rWriter.attribute("flags", convertPolygonFlags(aPolygon.GetFlags(i)));
+ rWriter.endElement();
+ }
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ {
+ MetaPolyPolygonAction *const pMetaPolyPolygonAction = static_cast<MetaPolyPolygonAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ tools::PolyPolygon const& rPolyPolygon(pMetaPolyPolygonAction->GetPolyPolygon());
+
+ for (sal_uInt16 j = 0; j < rPolyPolygon.Count(); ++j)
+ {
+ rWriter.startElement("polygon");
+ tools::Polygon const& rPolygon = rPolyPolygon[j];
+ bool bFlags = rPolygon.HasFlags();
+ for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i)
+ {
+ rWriter.startElement("point");
+ writePoint(rWriter, rPolygon[i]);
+ if (bFlags)
+ rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i)));
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ auto* pMeta = static_cast<MetaTextAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ rWriter.attribute("index", pMeta->GetIndex());
+ rWriter.attribute("length", pMeta->GetLen());
+ rWriter.startElement("textcontent");
+ rWriter.content(pMeta->GetText());
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ MetaTextArrayAction* pMetaTextArrayAction = static_cast<MetaTextArrayAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ sal_Int32 aIndex = pMetaTextArrayAction->GetIndex();
+ sal_Int32 aLength = pMetaTextArrayAction->GetLen();
+
+ writePoint(rWriter, pMetaTextArrayAction->GetPoint());
+ rWriter.attribute("index", aIndex);
+ rWriter.attribute("length", aLength);
+
+ if (!pMetaTextArrayAction->GetDXArray().empty())
+ {
+ auto & rArray = pMetaTextArrayAction->GetDXArray();
+ rWriter.startElement("dxarray");
+ if (aIndex < o3tl::narrowing<sal_Int32>(rArray.size()))
+ rWriter.attribute("first", rArray[aIndex]);
+ if (aIndex + aLength - 1 < o3tl::narrowing<sal_Int32>(rArray.size()))
+ rWriter.attribute("last", rArray[aIndex + aLength - 1]);
+ OUStringBuffer sDxLengthString(std::max((aLength - aIndex) * 4, sal_Int32(0)));
+ for (sal_Int32 i = 0; i < aLength - aIndex; ++i)
+ {
+ sDxLengthString.append(OUString::number(rArray[aIndex + i]) + " ");
+ }
+ rWriter.content(sDxLengthString);
+ rWriter.endElement();
+ }
+
+ rWriter.startElement("text");
+
+ const OUString& rStr = pMetaTextArrayAction->GetText();
+ // fix bad XML dump by removing forbidden 0x01
+ // FIXME: expand footnote anchor point 0x01 instead of this
+ if ( rStr.indexOf(0x01) > -1 )
+ rWriter.content(rStr.replaceAll("\001", ""));
+ else
+ rWriter.content(rStr);
+
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ auto* pMeta = static_cast<MetaStretchTextAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ writePoint(rWriter, pMeta->GetPoint());
+ rWriter.attribute("index", pMeta->GetIndex());
+ rWriter.attribute("length", pMeta->GetLen());
+ rWriter.attribute("width", pMeta->GetWidth());
+
+ rWriter.startElement("textcontent");
+ rWriter.content(pMeta->GetText());
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ auto* pMeta = static_cast<MetaTextRectAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writeRectangle(rWriter, pMeta->GetRect());
+ rWriter.startElement("textcontent");
+ rWriter.content(pMeta->GetText());
+ rWriter.endElement();
+
+ rWriter.startElement("style");
+ rWriter.content(convertDrawTextFlagsToString(pMeta->GetStyle()));
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ auto pMeta = static_cast<MetaBmpAction*>(pAction);
+ Bitmap aBitmap = pMeta->GetBitmap();
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeBitmap(rWriter, aBitmap);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ auto pMeta = static_cast<MetaBmpScaleAction*>(pAction);
+ Bitmap aBitmap = pMeta->GetBitmap();
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeSize(rWriter, pMeta->GetSize());
+ writeBitmap(rWriter, aBitmap);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ auto pMeta = static_cast<MetaBmpScalePartAction*>(pAction);
+ Bitmap aBitmap = pMeta->GetBitmap();
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("destx", pMeta->GetDestPoint().X());
+ rWriter.attribute("desty", pMeta->GetDestPoint().Y());
+ rWriter.attribute("destwidth", pMeta->GetDestSize().Width());
+ rWriter.attribute("destheight", pMeta->GetDestSize().Height());
+ rWriter.attribute("srcx", pMeta->GetSrcPoint().X());
+ rWriter.attribute("srcy", pMeta->GetSrcPoint().Y());
+ rWriter.attribute("srcwidth", pMeta->GetSrcSize().Width());
+ rWriter.attribute("srcheight", pMeta->GetSrcSize().Height());
+ writeBitmap(rWriter, aBitmap);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ auto pMeta = static_cast<MetaBmpExAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ Bitmap aBitmap = pMeta->GetBitmapEx().GetBitmap();
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx()));
+ writeBitmap(rWriter, aBitmap);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ auto pMeta = static_cast<MetaBmpExScaleAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeSize(rWriter, pMeta->GetSize());
+ Bitmap aBitmap = pMeta->GetBitmapEx().GetBitmap();
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx()));
+ writeBitmap(rWriter, aBitmap);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ auto pMeta = static_cast<MetaBmpExScalePartAction*>(pAction);
+ Bitmap aBitmap = pMeta->GetBitmapEx().GetBitmap();
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("destx", pMeta->GetDestPoint().X());
+ rWriter.attribute("desty", pMeta->GetDestPoint().Y());
+ rWriter.attribute("destwidth", pMeta->GetDestSize().Width());
+ rWriter.attribute("destheight", pMeta->GetDestSize().Height());
+ rWriter.attribute("srcx", pMeta->GetSrcPoint().X());
+ rWriter.attribute("srcy", pMeta->GetSrcPoint().Y());
+ rWriter.attribute("srcwidth", pMeta->GetSrcSize().Width());
+ rWriter.attribute("srcheight", pMeta->GetSrcSize().Height());
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx()));
+ writeBitmap(rWriter, aBitmap);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::MASK:
+ {
+ auto pMeta = static_cast<MetaMaskAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum()));
+ rWriter.attribute("color", convertColorToString(pMeta->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::MASKSCALE:
+ {
+ auto pMeta = static_cast<MetaMaskScaleAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeSize(rWriter, pMeta->GetSize());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum()));
+ rWriter.attribute("color", convertColorToString(pMeta->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ {
+ auto pMeta = static_cast<MetaMaskScalePartAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("destx", pMeta->GetDestPoint().X());
+ rWriter.attribute("desty", pMeta->GetDestPoint().Y());
+ rWriter.attribute("destwidth", pMeta->GetDestSize().Width());
+ rWriter.attribute("destheight", pMeta->GetDestSize().Height());
+ rWriter.attribute("srcx", pMeta->GetSrcPoint().X());
+ rWriter.attribute("srcy", pMeta->GetSrcPoint().Y());
+ rWriter.attribute("srcwidth", pMeta->GetSrcSize().Width());
+ rWriter.attribute("srcheight", pMeta->GetSrcSize().Height());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum()));
+ rWriter.attribute("color", convertColorToString(pMeta->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ const MetaGradientAction* pMeta = static_cast<MetaGradientAction*>(pAction);
+
+ rWriter.startElement(sCurrentElementTag);
+ writeGradient(rWriter, pMeta->GetGradient());
+
+ rWriter.startElement("rectangle");
+ writeRectangle(rWriter, pMeta->GetRect());
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ auto* const pMetaHatchAction = static_cast<MetaHatchAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ tools::PolyPolygon const& rPolyPolygon(pMetaHatchAction->GetPolyPolygon());
+
+ for (sal_uInt16 j = 0; j < rPolyPolygon.Count(); ++j)
+ {
+ rWriter.startElement("polygon");
+ tools::Polygon const& rPolygon = rPolyPolygon[j];
+ bool bFlags = rPolygon.HasFlags();
+ for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i)
+ {
+ rWriter.startElement("point");
+ writePoint(rWriter, rPolygon[i]);
+ if (bFlags)
+ rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i)));
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+
+ rWriter.startElement("hatch");
+ const auto& rHatch = pMetaHatchAction->GetHatch();
+ rWriter.attribute("style", convertHatchStyle(rHatch.GetStyle()));
+ rWriter.attribute("color", convertColorToString(rHatch.GetColor()));
+ rWriter.attribute("distance", sal_Int32(rHatch.GetDistance()));
+ rWriter.attribute("angle", sal_Int32(rHatch.GetAngle().get()));
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::WALLPAPER:
+ {
+ const auto* pMetaAction = static_cast<const MetaWallpaperAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ writeRectangle(rWriter, pMetaAction->GetRect());
+
+ rWriter.startElement("wallpaper");
+ const auto& rWallpaper = pMetaAction->GetWallpaper();
+
+ rWriter.attribute("color", convertColorToString(rWallpaper.GetColor()));
+
+ WallpaperStyle eStyle = rWallpaper.GetStyle();
+ rWriter.attribute("style", convertWallpaperStyleToString(eStyle));
+
+ if (rWallpaper.IsBitmap())
+ {
+ rWriter.startElement("bitmap");
+ BitmapEx const & rBitmapEx = rWallpaper.GetBitmap();
+ rWriter.attribute("crc", hex32(rBitmapEx.GetChecksum()));
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(rBitmapEx));
+ rWriter.attribute("pixelformat", convertPixelFormatToString(rBitmapEx.GetBitmap().getPixelFormat()));
+ rWriter.attribute("width", hex32(rBitmapEx.GetSizePixel().Width()));
+ rWriter.attribute("height", hex32(rBitmapEx.GetSizePixel().Height()));
+ rWriter.endElement();
+ }
+
+ if (rWallpaper.IsGradient())
+ {
+ rWriter.startElement("gradient");
+ Gradient aGradient = rWallpaper.GetGradient();
+ writeGradient(rWriter, aGradient);
+ rWriter.endElement();
+ }
+
+ if (rWallpaper.IsRect())
+ {
+ tools::Rectangle aRect = rWallpaper.GetRect();
+ rWriter.startElement("rectangle");
+ writeRectangle(rWriter, aRect);
+ rWriter.endElement();
+ }
+
+ rWriter.attribute("fixed", rWallpaper.IsFixed() ? "true" : "false");
+ rWriter.attribute("scrollable", rWallpaper.IsScrollable() ? "true" : "false");
+
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ {
+ const auto* pMetaClipRegionAction = static_cast<const MetaClipRegionAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ tools::Rectangle aRectangle = pMetaClipRegionAction->GetRegion().GetBoundRect();
+ writeRectangle(rWriter, aRectangle);
+
+ vcl::Region aRegion = pMetaClipRegionAction->GetRegion();
+
+ if (aRegion.HasPolyPolygonOrB2DPolyPolygon())
+ {
+ tools::PolyPolygon aPolyPolygon = aRegion.GetAsPolyPolygon();
+
+ for (sal_uInt16 j = 0; j < aPolyPolygon.Count(); ++j)
+ {
+ rWriter.startElement("polygon");
+ tools::Polygon const& rPolygon = aPolyPolygon[j];
+ bool bFlags = rPolygon.HasFlags();
+ for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i)
+ {
+ rWriter.startElement("point");
+ writePoint(rWriter, rPolygon[i]);
+ if (bFlags)
+ rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i)));
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+ }
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::ISECTRECTCLIPREGION:
+ {
+ MetaISectRectClipRegionAction* pMetaISectRectClipRegionAction = static_cast<MetaISectRectClipRegionAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ tools::Rectangle aRectangle = pMetaISectRectClipRegionAction->GetRect();
+ writeRectangle(rWriter, aRectangle);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ {
+ MetaISectRegionClipRegionAction* pMetaISectRegionClipRegionAction = static_cast<MetaISectRegionClipRegionAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ // FIXME for now we dump only the bounding box; this is
+ // enough for the tests we have, but may need extending to
+ // dumping the real polypolygon in the future
+ tools::Rectangle aRectangle = pMetaISectRegionClipRegionAction->GetRegion().GetBoundRect();
+ writeRectangle(rWriter, aRectangle);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::MOVECLIPREGION:
+ {
+ const auto* pMetaMoveClipRegionAction = static_cast<MetaMoveClipRegionAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("horzmove", pMetaMoveClipRegionAction->GetHorzMove());
+ rWriter.attribute("vertmove", pMetaMoveClipRegionAction->GetVertMove());
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::LINECOLOR:
+ {
+ MetaLineColorAction* pMetaLineColorAction = static_cast<MetaLineColorAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("color", convertColorToString(pMetaLineColorAction->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::FILLCOLOR:
+ {
+ MetaFillColorAction* pMetaFillColorAction = static_cast<MetaFillColorAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("color", convertColorToString(pMetaFillColorAction->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTCOLOR:
+ {
+ MetaTextColorAction* pMetaTextColorAction = static_cast<MetaTextColorAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("color", convertColorToString(pMetaTextColorAction->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTFILLCOLOR:
+ {
+ MetaTextFillColorAction* pMetaTextFillColorAction = static_cast<MetaTextFillColorAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("color", convertColorToString(pMetaTextFillColorAction->GetColor()));
+
+ if (pMetaTextFillColorAction->IsSetting())
+ rWriter.attribute("setting", u"true");
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTALIGN:
+ {
+ MetaTextAlignAction* pMetaTextAlignAction = static_cast<MetaTextAlignAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ OUString sAlign = convertTextAlignToString(pMetaTextAlignAction->GetTextAlign());
+ if (!sAlign.isEmpty())
+ rWriter.attribute("align", sAlign);
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::MAPMODE:
+ {
+ const MetaMapModeAction* pMeta = static_cast<MetaMapModeAction*>(pAction);
+ MapMode aMapMode = pMeta->GetMapMode();
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("mapunit", convertMapUnitToString( aMapMode.GetMapUnit() ));
+ writePoint(rWriter, aMapMode.GetOrigin());
+ rWriter.attribute("scalex", convertFractionToString(aMapMode.GetScaleX()));
+ rWriter.attribute("scaley", convertFractionToString(aMapMode.GetScaleY()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::FONT:
+ {
+ MetaFontAction* pMetaFontAction = static_cast<MetaFontAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ vcl::Font aFont = pMetaFontAction->GetFont();
+
+ rWriter.attribute("color", convertColorToString(aFont.GetColor()));
+ rWriter.attribute("fillcolor", convertColorToString(aFont.GetFillColor()));
+ rWriter.attribute("name", aFont.GetFamilyName());
+ rWriter.attribute("stylename", aFont.GetStyleName());
+ rWriter.attribute("width", aFont.GetFontSize().Width());
+ rWriter.attribute("height", aFont.GetFontSize().Height());
+ rWriter.attribute("orientation", aFont.GetOrientation().get());
+ rWriter.attribute("weight", convertFontWeightToString(aFont.GetWeight()));
+ rWriter.attribute("vertical", aFont.IsVertical() ? "true" : "false");
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::PUSH:
+ {
+ MetaPushAction* pMetaPushAction = static_cast<MetaPushAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("flags", collectPushFlags(pMetaPushAction->GetFlags()));
+ }
+ break;
+
+ case MetaActionType::POP:
+ {
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::RASTEROP:
+ {
+ MetaRasterOpAction* pMetaRasterOpAction = static_cast<MetaRasterOpAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ if (pMetaRasterOpAction->GetRasterOp() != RasterOp::OverPaint)
+ {
+ rWriter.attribute("operation", convertRopToString(pMetaRasterOpAction->GetRasterOp()));
+ }
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ const MetaTransparentAction* pMeta = static_cast<MetaTransparentAction*>(pAction);
+
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("transparence", pMeta->GetTransparence());
+
+ tools::PolyPolygon const& rPolyPolygon(pMeta->GetPolyPolygon());
+
+ for (sal_uInt16 j = 0; j < rPolyPolygon.Count(); ++j)
+ {
+ rWriter.startElement("polygon");
+ tools::Polygon const& rPolygon = rPolyPolygon[j];
+ bool bFlags = rPolygon.HasFlags();
+ for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i)
+ {
+ rWriter.startElement("point");
+ writePoint(rWriter, rPolygon[i]);
+ if (bFlags)
+ rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i)));
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ MetaEPSAction* pMetaEPSAction = static_cast<MetaEPSAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ writePoint(rWriter, pMetaEPSAction->GetPoint());
+ writeSize(rWriter, pMetaEPSAction->GetSize());
+
+ rWriter.startElement("gfxlink");
+ writeSize(rWriter, pMetaEPSAction->GetLink().GetPrefSize());
+ rWriter.attribute("type", convertGfxLinkTypeToString(pMetaEPSAction->GetLink().GetType()));
+ rWriter.attribute("userid", pMetaEPSAction->GetLink().GetUserId());
+ rWriter.attribute("datasize", pMetaEPSAction->GetLink().GetDataSize());
+ rWriter.attribute("data", toHexString(pMetaEPSAction->GetLink().GetData(), pMetaEPSAction->GetLink().GetDataSize()));
+ rWriter.attribute("native", pMetaEPSAction->GetLink().IsNative() ? "true" : "false");
+ rWriter.attribute("emf", pMetaEPSAction->GetLink().IsEMF() ? "true" : "false");
+ rWriter.attribute("validmapmode", pMetaEPSAction->GetLink().IsPrefMapModeValid() ? "true" : "false");
+ rWriter.startElement("prefmapmode");
+ writeMapMode(rWriter, pMetaEPSAction->GetLink().GetPrefMapMode());
+ rWriter.endElement();
+ rWriter.endElement();
+
+ rWriter.startElement("metafile");
+ writeXml(pMetaEPSAction->GetSubstitute(), rWriter);
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::REFPOINT:
+ {
+ auto* pMeta = static_cast<MetaRefPointAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetRefPoint());
+ rWriter.attribute("set", pMeta->IsSetting() ? "true" : "false");
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTLINECOLOR:
+ {
+ auto* pMeta = static_cast<MetaTextLineColorAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("color", convertColorToString(pMeta->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ {
+ auto* pMeta = static_cast<MetaTextLineAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetStartPoint());
+ rWriter.attribute("width", pMeta->GetWidth());
+ rWriter.attribute("strikeout", convertFontStrikeoutToString(pMeta->GetStrikeout()));
+ rWriter.attribute("underline", convertFontLineStyleToString(pMeta->GetUnderline()));
+ rWriter.attribute("overline", convertFontLineStyleToString(pMeta->GetOverline()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ const auto* pMeta = static_cast<MetaFloatTransparentAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeSize(rWriter, pMeta->GetSize());
+ rWriter.attribute("transparent", pMeta->IsTransparent() ? "true" : "false");
+
+ rWriter.startElement("gradient");
+ writeGradient(rWriter, pMeta->GetGradient());
+ rWriter.endElement();
+
+ rWriter.startElement("metafile");
+ writeXml(pMeta->GetGDIMetaFile(), rWriter);
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ const MetaGradientExAction* pMetaGradientExAction = static_cast<MetaGradientExAction*>(pAction);
+
+ rWriter.startElement(sCurrentElementTag);
+ writeGradient(rWriter, pMetaGradientExAction->GetGradient());
+
+ tools::PolyPolygon const& rPolyPolygon(pMetaGradientExAction->GetPolyPolygon());
+ for (sal_uInt16 j = 0; j < rPolyPolygon.Count(); ++j)
+ {
+ rWriter.startElement("polygon");
+ tools::Polygon const& rPolygon = rPolyPolygon[j];
+ bool bFlags = rPolygon.HasFlags();
+ for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i)
+ {
+ rWriter.startElement("point");
+ writePoint(rWriter, rPolygon[i]);
+ if (bFlags)
+ rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i)));
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::LAYOUTMODE:
+ {
+ const MetaLayoutModeAction* pMetaLayoutModeAction = static_cast<MetaLayoutModeAction*>(pAction);
+
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("textlayout", convertComplexTestLayoutFlags(pMetaLayoutModeAction->GetLayoutMode()));
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::TEXTLANGUAGE:
+ {
+ const MetaTextLanguageAction* pMetaTextLanguageAction = static_cast<MetaTextLanguageAction*>(pAction);
+
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("language", convertLanguageTypeToString(pMetaTextLanguageAction->GetTextLanguage()));
+
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::OVERLINECOLOR:
+ {
+ const auto* pMetaAction = static_cast<MetaOverlineColorAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("color", convertColorToString(pMetaAction->GetColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::COMMENT:
+ {
+ MetaCommentAction* pMetaCommentAction = static_cast<MetaCommentAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ if (pMetaCommentAction->GetDataSize() > 0)
+ {
+ rWriter.attribute("datasize", pMetaCommentAction->GetDataSize());
+ rWriter.attribute("data", toHexString(pMetaCommentAction->GetData(), pMetaCommentAction->GetDataSize()));
+ }
+ rWriter.attribute("value", pMetaCommentAction->GetValue());
+
+ if (!pMetaCommentAction->GetComment().isEmpty())
+ {
+ rWriter.startElement("comment");
+ rWriter.content(pMetaCommentAction->GetComment());
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+ break;
+
+ default:
+ {
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("note", "not implemented in xml dump"_ostr);
+ rWriter.endElement();
+ }
+ break;
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/oldprintadaptor.cxx b/vcl/source/gdi/oldprintadaptor.cxx
new file mode 100644
index 0000000000..599e1a543f
--- /dev/null
+++ b/vcl/source/gdi/oldprintadaptor.cxx
@@ -0,0 +1,109 @@
+/* -*- 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 <sal/config.h>
+
+#include <comphelper/propertyvalue.hxx>
+#include <vcl/oldprintadaptor.hxx>
+#include <vcl/gdimtf.hxx>
+
+#include <com/sun/star/awt/Size.hpp>
+
+#include <vector>
+
+using namespace vcl;
+using namespace cppu;
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::beans;
+
+namespace vcl
+{
+ namespace {
+
+ struct AdaptorPage
+ {
+ GDIMetaFile maPage;
+ css::awt::Size maPageSize;
+ };
+
+ }
+
+ struct ImplOldStyleAdaptorData
+ {
+ std::vector< AdaptorPage > maPages;
+ };
+}
+
+OldStylePrintAdaptor::OldStylePrintAdaptor(const VclPtr<Printer>& i_xPrinter, weld::Window* i_pWindow)
+ : PrinterController(i_xPrinter, i_pWindow)
+ , mpData(new ImplOldStyleAdaptorData)
+{
+}
+
+OldStylePrintAdaptor::~OldStylePrintAdaptor()
+{
+}
+
+void OldStylePrintAdaptor::StartPage()
+{
+ Size aPaperSize( getPrinter()->PixelToLogic( getPrinter()->GetPaperSizePixel(), MapMode( MapUnit::Map100thMM ) ) );
+ mpData->maPages.emplace_back( );
+ mpData->maPages.back().maPageSize.Width = aPaperSize.getWidth();
+ mpData->maPages.back().maPageSize.Height = aPaperSize.getHeight();
+ getPrinter()->SetConnectMetaFile( &mpData->maPages.back().maPage );
+
+ // copy state into metafile
+ VclPtr<Printer> xPrinter( getPrinter() );
+ xPrinter->SetMapMode(xPrinter->GetMapMode());
+ xPrinter->SetFont(xPrinter->GetFont());
+ xPrinter->SetDrawMode(xPrinter->GetDrawMode());
+ xPrinter->SetLineColor(xPrinter->GetLineColor());
+ xPrinter->SetFillColor(xPrinter->GetFillColor());
+}
+
+void OldStylePrintAdaptor::EndPage()
+{
+ getPrinter()->SetConnectMetaFile( nullptr );
+ mpData->maPages.back().maPage.WindStart();
+}
+
+int OldStylePrintAdaptor::getPageCount() const
+{
+ return int(mpData->maPages.size());
+}
+
+Sequence< PropertyValue > OldStylePrintAdaptor::getPageParameters( int i_nPage ) const
+{
+ css::awt::Size aSize;
+ if( i_nPage < int(mpData->maPages.size() ) )
+ aSize = mpData->maPages[i_nPage].maPageSize;
+ return { comphelper::makePropertyValue("PageSize", css::uno::Any(aSize)) };
+}
+
+void OldStylePrintAdaptor::printPage( int i_nPage ) const
+{
+ if( i_nPage < int(mpData->maPages.size()) )
+ {
+ mpData->maPages[ i_nPage ].maPage.WindStart();
+ mpData->maPages[ i_nPage ].maPage.Play(*getPrinter());
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfbuildin_fonts.cxx b/vcl/source/gdi/pdfbuildin_fonts.cxx
new file mode 100644
index 0000000000..7f80bfdd03
--- /dev/null
+++ b/vcl/source/gdi/pdfbuildin_fonts.cxx
@@ -0,0 +1,760 @@
+/* -*- 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 <sal/config.h>
+
+#include <rtl/strbuf.hxx>
+
+#include <pdf/pdfbuildin_fonts.hxx>
+
+namespace vcl::pdf
+{
+OString BuildinFont::getNameObject() const
+{
+ OStringBuffer aBuf(16);
+ aBuf.append('/');
+ const char* pRun = m_pPSName;
+
+ unsigned int nCopied = 0;
+ while (*pRun)
+ {
+ if (*pRun >= 'A' && *pRun <= 'Z')
+ nCopied = 0;
+ if (nCopied++ < 2)
+ aBuf.append(*pRun);
+ pRun++;
+ }
+ return aBuf.makeStringAndClear();
+}
+
+const FontCharMapRef& BuildinFont::GetFontCharMap() const
+{
+ assert(false && "pdf::BuildinFont doesn't provide correct char maps!");
+ if (m_xFontCharMap.is())
+ return m_xFontCharMap;
+
+ m_xFontCharMap = FontCharMap::GetDefaultMap(m_eCharSet != RTL_TEXTENCODING_MS_1252);
+ return m_xFontCharMap;
+}
+
+FontAttributes BuildinFont::GetFontAttributes() const
+{
+ FontAttributes aDFA;
+ aDFA.SetFamilyName(OUString::createFromAscii(m_pName));
+ aDFA.SetStyleName(OUString::createFromAscii(m_pStyleName));
+ aDFA.SetFamilyType(m_eFamily);
+ // dubious, see BuildinFont::GetFontCharMap
+ aDFA.SetMicrosoftSymbolEncoded(m_eCharSet != RTL_TEXTENCODING_MS_1252);
+ aDFA.SetPitch(m_ePitch);
+ aDFA.SetWeight(m_eWeight);
+ aDFA.SetItalic(m_eItalic);
+ aDFA.SetWidthType(m_eWidthType);
+ aDFA.SetQuality(50000);
+ return aDFA;
+}
+
+const BuildinFont BuildinFontFace::m_aBuildinFonts[14]
+ = { { "Courier", // family name
+ "Normal", // style
+ "Courier", // PSName
+ 629,
+ -157, // ascend, descend
+ FAMILY_MODERN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_FIXED, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 600, 600, 600, 600, 600, 600, 600, 600, // 32 - 39
+ 600, 600, 600, 600, 600, 600, 600, 600, // 40 - 47
+ 600, 600, 600, 600, 600, 600, 600, 600, // 48 - 55
+ 600, 600, 600, 600, 600, 600, 600, 600, // 56 - 63
+ 600, 600, 600, 600, 600, 600, 600, 600, // 64 - 71
+ 600, 600, 600, 600, 600, 600, 600, 600, // 72 - 79
+ 600, 600, 600, 600, 600, 600, 600, 600, // 80 - 87
+ 600, 600, 600, 600, 600, 600, 600, 600, // 88 - 95
+ 600, 600, 600, 600, 600, 600, 600, 600, // 96 - 103
+ 600, 600, 600, 600, 600, 600, 600, 600, // 104 - 111
+ 600, 600, 600, 600, 600, 600, 600, 600, // 112 - 119
+ 600, 600, 600, 600, 600, 600, 600, 0, // 120 - 127
+ 600, 0, 600, 600, 600, 600, 600, 600, // 128 - 135
+ 600, 600, 600, 600, 600, 0, 600, 0, // 136 - 143
+ 0, 600, 600, 600, 600, 600, 600, 600, // 144 - 151
+ 600, 600, 600, 600, 600, 0, 600, 600, // 152 - 159
+ 600, 600, 600, 600, 600, 600, 600, 600, // 160 - 167
+ 600, 600, 600, 600, 600, 600, 600, 600, // 168 - 175
+ 600, 600, 600, 600, 600, 600, 600, 600, // 176 - 183
+ 600, 600, 600, 600, 600, 600, 600, 600, // 184 - 191
+ 600, 600, 600, 600, 600, 600, 600, 600, // 192 - 199
+ 600, 600, 600, 600, 600, 600, 600, 600, // 200 - 207
+ 600, 600, 600, 600, 600, 600, 600, 600, // 208 - 215
+ 600, 600, 600, 600, 600, 600, 600, 600, // 216 - 223
+ 600, 600, 600, 600, 600, 600, 600, 600, // 224 - 231
+ 600, 600, 600, 600, 600, 600, 600, 600, // 232 - 239
+ 600, 600, 600, 600, 600, 600, 600, 600, // 240 - 247
+ 600, 600, 600, 600, 600, 600, 600, 600 // 248 - 255
+ },
+ nullptr },
+
+ { "Courier", // family name
+ "Italic", // style
+ "Courier-Oblique", // PSName
+ 629,
+ -157, // ascend, descend
+ FAMILY_MODERN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_FIXED, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NORMAL, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 600, 600, 600, 600, 600, 600, 600, 600, // 32 - 39
+ 600, 600, 600, 600, 600, 600, 600, 600, // 40 - 47
+ 600, 600, 600, 600, 600, 600, 600, 600, // 48 - 55
+ 600, 600, 600, 600, 600, 600, 600, 600, // 56 - 63
+ 600, 600, 600, 600, 600, 600, 600, 600, // 64 - 71
+ 600, 600, 600, 600, 600, 600, 600, 600, // 72 - 79
+ 600, 600, 600, 600, 600, 600, 600, 600, // 80 - 87
+ 600, 600, 600, 600, 600, 600, 600, 600, // 88 - 95
+ 600, 600, 600, 600, 600, 600, 600, 600, // 96 - 103
+ 600, 600, 600, 600, 600, 600, 600, 600, // 104 - 111
+ 600, 600, 600, 600, 600, 600, 600, 600, // 112 - 119
+ 600, 600, 600, 600, 600, 600, 600, 0, // 120 - 127
+ 600, 0, 600, 600, 600, 600, 600, 600, // 128 - 135
+ 600, 600, 600, 600, 600, 0, 600, 0, // 136 - 143
+ 0, 600, 600, 600, 600, 600, 600, 600, // 144 - 151
+ 600, 600, 600, 600, 600, 0, 600, 600, // 152 - 159
+ 600, 600, 600, 600, 600, 600, 600, 600, // 160 - 167
+ 600, 600, 600, 600, 600, 600, 600, 600, // 168 - 175
+ 600, 600, 600, 600, 600, 600, 600, 600, // 176 - 183
+ 600, 600, 600, 600, 600, 600, 600, 600, // 184 - 191
+ 600, 600, 600, 600, 600, 600, 600, 600, // 192 - 199
+ 600, 600, 600, 600, 600, 600, 600, 600, // 200 - 207
+ 600, 600, 600, 600, 600, 600, 600, 600, // 208 - 215
+ 600, 600, 600, 600, 600, 600, 600, 600, // 216 - 223
+ 600, 600, 600, 600, 600, 600, 600, 600, // 224 - 231
+ 600, 600, 600, 600, 600, 600, 600, 600, // 232 - 239
+ 600, 600, 600, 600, 600, 600, 600, 600, // 240 - 247
+ 600, 600, 600, 600, 600, 600, 600, 600 // 248 - 255
+ },
+ nullptr },
+
+ { "Courier", // family name
+ "Bold", // style
+ "Courier-Bold", // PSName
+ 629,
+ -157, // ascend, descend
+ FAMILY_MODERN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_FIXED, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_BOLD, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 600, 600, 600, 600, 600, 600, 600, 600, // 32 - 39
+ 600, 600, 600, 600, 600, 600, 600, 600, // 40 - 47
+ 600, 600, 600, 600, 600, 600, 600, 600, // 48 - 55
+ 600, 600, 600, 600, 600, 600, 600, 600, // 56 - 63
+ 600, 600, 600, 600, 600, 600, 600, 600, // 64 - 71
+ 600, 600, 600, 600, 600, 600, 600, 600, // 72 - 79
+ 600, 600, 600, 600, 600, 600, 600, 600, // 80 - 87
+ 600, 600, 600, 600, 600, 600, 600, 600, // 88 - 95
+ 600, 600, 600, 600, 600, 600, 600, 600, // 96 - 103
+ 600, 600, 600, 600, 600, 600, 600, 600, // 104 - 111
+ 600, 600, 600, 600, 600, 600, 600, 600, // 112 - 119
+ 600, 600, 600, 600, 600, 600, 600, 0, // 120 - 127
+ 600, 0, 600, 600, 600, 600, 600, 600, // 128 - 135
+ 600, 600, 600, 600, 600, 0, 600, 0, // 136 - 143
+ 0, 600, 600, 600, 600, 600, 600, 600, // 144 - 151
+ 600, 600, 600, 600, 600, 0, 600, 600, // 152 - 159
+ 600, 600, 600, 600, 600, 600, 600, 600, // 160 - 167
+ 600, 600, 600, 600, 600, 600, 600, 600, // 168 - 175
+ 600, 600, 600, 600, 600, 600, 600, 600, // 176 - 183
+ 600, 600, 600, 600, 600, 600, 600, 600, // 184 - 191
+ 600, 600, 600, 600, 600, 600, 600, 600, // 192 - 199
+ 600, 600, 600, 600, 600, 600, 600, 600, // 200 - 207
+ 600, 600, 600, 600, 600, 600, 600, 600, // 208 - 215
+ 600, 600, 600, 600, 600, 600, 600, 600, // 216 - 223
+ 600, 600, 600, 600, 600, 600, 600, 600, // 224 - 231
+ 600, 600, 600, 600, 600, 600, 600, 600, // 232 - 239
+ 600, 600, 600, 600, 600, 600, 600, 600, // 240 - 247
+ 600, 600, 600, 600, 600, 600, 600, 600 // 248 - 255
+ },
+ nullptr },
+
+ { "Courier", // family name
+ "Bold Italic", // style
+ "Courier-BoldOblique", // PSName
+ 629,
+ -157, // ascend, descend
+ FAMILY_MODERN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_FIXED, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_BOLD, // weight type
+ ITALIC_NORMAL, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 600, 600, 600, 600, 600, 600, 600, 600, // 32 - 39
+ 600, 600, 600, 600, 600, 600, 600, 600, // 40 - 47
+ 600, 600, 600, 600, 600, 600, 600, 600, // 48 - 55
+ 600, 600, 600, 600, 600, 600, 600, 600, // 56 - 63
+ 600, 600, 600, 600, 600, 600, 600, 600, // 64 - 71
+ 600, 600, 600, 600, 600, 600, 600, 600, // 72 - 79
+ 600, 600, 600, 600, 600, 600, 600, 600, // 80 - 87
+ 600, 600, 600, 600, 600, 600, 600, 600, // 88 - 95
+ 600, 600, 600, 600, 600, 600, 600, 600, // 96 - 103
+ 600, 600, 600, 600, 600, 600, 600, 600, // 104 - 111
+ 600, 600, 600, 600, 600, 600, 600, 600, // 112 - 119
+ 600, 600, 600, 600, 600, 600, 600, 0, // 120 - 127
+ 600, 0, 600, 600, 600, 600, 600, 600, // 128 - 135
+ 600, 600, 600, 600, 600, 0, 600, 0, // 136 - 143
+ 0, 600, 600, 600, 600, 600, 600, 600, // 144 - 151
+ 600, 600, 600, 600, 600, 0, 600, 600, // 152 - 159
+ 600, 600, 600, 600, 600, 600, 600, 600, // 160 - 167
+ 600, 600, 600, 600, 600, 600, 600, 600, // 168 - 175
+ 600, 600, 600, 600, 600, 600, 600, 600, // 176 - 183
+ 600, 600, 600, 600, 600, 600, 600, 600, // 184 - 191
+ 600, 600, 600, 600, 600, 600, 600, 600, // 192 - 199
+ 600, 600, 600, 600, 600, 600, 600, 600, // 200 - 207
+ 600, 600, 600, 600, 600, 600, 600, 600, // 208 - 215
+ 600, 600, 600, 600, 600, 600, 600, 600, // 216 - 223
+ 600, 600, 600, 600, 600, 600, 600, 600, // 224 - 231
+ 600, 600, 600, 600, 600, 600, 600, 600, // 232 - 239
+ 600, 600, 600, 600, 600, 600, 600, 600, // 240 - 247
+ 600, 600, 600, 600, 600, 600, 600, 600 // 248 - 255
+ },
+ nullptr },
+
+ { "Helvetica", // family name
+ "Normal", // style
+ "Helvetica", // PSName
+ 718,
+ -207, // ascend, descend
+ FAMILY_SWISS, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 278, 278, 355, 556, 556, 889, 667, 191, // 32 - 39
+ 333, 333, 389, 584, 278, 333, 278, 278, // 40 - 47
+ 556, 556, 556, 556, 556, 556, 556, 556, // 48 - 55
+ 556, 556, 278, 278, 584, 584, 584, 556, // 56 - 63
+ 1015, 667, 667, 722, 722, 667, 611, 778, // 64 - 71
+ 722, 278, 500, 667, 556, 833, 722, 778, // 72 - 79
+ 667, 778, 722, 667, 611, 722, 667, 944, // 80 - 87
+ 667, 667, 611, 278, 278, 278, 469, 556, // 88 - 95
+ 333, 556, 556, 500, 556, 556, 278, 556, // 96 - 103
+ 556, 222, 222, 500, 222, 833, 556, 556, // 104 - 111
+ 556, 556, 333, 500, 278, 556, 500, 722, // 112 - 119
+ 500, 500, 500, 334, 260, 334, 584, 0, // 120 - 127
+ 556, 0, 222, 556, 333, 1000, 556, 556, // 128 - 135
+ 333, 1000, 667, 333, 1000, 0, 500, 0, // 136 - 143
+ 0, 222, 222, 333, 333, 350, 556, 1000, // 144 - 151
+ 333, 1000, 500, 333, 944, 0, 500, 667, // 152 - 159
+ 278, 333, 556, 556, 556, 556, 260, 556, // 160 - 167
+ 333, 737, 370, 556, 584, 333, 737, 333, // 168 - 175
+ 400, 584, 333, 333, 333, 556, 537, 278, // 176 - 183
+ 333, 333, 365, 556, 834, 834, 834, 611, // 184 - 191
+ 667, 667, 667, 667, 667, 667, 1000, 722, // 192 - 199
+ 667, 667, 667, 667, 278, 278, 278, 278, // 200 - 207
+ 722, 722, 778, 778, 778, 778, 778, 584, // 208 - 215
+ 778, 722, 722, 722, 722, 667, 667, 611, // 216 - 223
+ 556, 556, 556, 556, 556, 556, 889, 500, // 224 - 231
+ 556, 556, 556, 556, 278, 278, 278, 278, // 232 - 239
+ 556, 556, 556, 556, 556, 556, 556, 584, // 240 - 247
+ 611, 556, 556, 556, 556, 500, 556, 500 // 248 - 255
+ },
+ nullptr },
+
+ { "Helvetica", // family name
+ "Italic", // style
+ "Helvetica-Oblique", // PSName
+ 718,
+ -207, // ascend, descend
+ FAMILY_SWISS, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NORMAL, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 278, 278, 355, 556, 556, 889, 667, 191, // 32 - 39
+ 333, 333, 389, 584, 278, 333, 278, 278, // 40 - 47
+ 556, 556, 556, 556, 556, 556, 556, 556, // 48 - 55
+ 556, 556, 278, 278, 584, 584, 584, 556, // 56 - 63
+ 1015, 667, 667, 722, 722, 667, 611, 778, // 64 - 71
+ 722, 278, 500, 667, 556, 833, 722, 778, // 72 - 79
+ 667, 778, 722, 667, 611, 722, 667, 944, // 80 - 87
+ 667, 667, 611, 278, 278, 278, 469, 556, // 88 - 95
+ 333, 556, 556, 500, 556, 556, 278, 556, // 96 - 103
+ 556, 222, 222, 500, 222, 833, 556, 556, // 104 - 111
+ 556, 556, 333, 500, 278, 556, 500, 722, // 112 - 119
+ 500, 500, 500, 334, 260, 334, 584, 0, // 120 - 127
+ 556, 0, 222, 556, 333, 1000, 556, 556, // 128 - 135
+ 333, 1000, 667, 333, 1000, 0, 500, 0, // 136 - 143
+ 0, 222, 222, 333, 333, 350, 556, 1000, // 144 - 151
+ 333, 1000, 500, 333, 944, 0, 500, 667, // 152 - 159
+ 278, 333, 556, 556, 556, 556, 260, 556, // 160 - 167
+ 333, 737, 370, 556, 584, 333, 737, 333, // 168 - 175
+ 400, 584, 333, 333, 333, 556, 537, 278, // 176 - 183
+ 333, 333, 365, 556, 834, 834, 834, 611, // 184 - 191
+ 667, 667, 667, 667, 667, 667, 1000, 722, // 192 - 199
+ 667, 667, 667, 667, 278, 278, 278, 278, // 200 - 207
+ 722, 722, 778, 778, 778, 778, 778, 584, // 208 - 215
+ 778, 722, 722, 722, 722, 667, 667, 611, // 216 - 223
+ 556, 556, 556, 556, 556, 556, 889, 500, // 224 - 231
+ 556, 556, 556, 556, 278, 278, 278, 278, // 232 - 239
+ 556, 556, 556, 556, 556, 556, 556, 584, // 240 - 247
+ 611, 556, 556, 556, 556, 500, 556, 500 // 248 - 255
+ },
+ nullptr },
+
+ { "Helvetica", // family name
+ "Bold", // style
+ "Helvetica-Bold", // PSName
+ 718,
+ -207, // ascend, descend
+ FAMILY_SWISS, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_BOLD, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 278, 333, 474, 556, 556, 889, 722, 238, // 32 - 39
+ 333, 333, 389, 584, 278, 333, 278, 278, // 40 - 47
+ 556, 556, 556, 556, 556, 556, 556, 556, // 48 - 55
+ 556, 556, 333, 333, 584, 584, 584, 611, // 56 - 63
+ 975, 722, 722, 722, 722, 667, 611, 778, // 64 - 71
+ 722, 278, 556, 722, 611, 833, 722, 778, // 72 - 79
+ 667, 778, 722, 667, 611, 722, 667, 944, // 80 - 87
+ 667, 667, 611, 333, 278, 333, 584, 556, // 88 - 95
+ 333, 556, 611, 556, 611, 556, 333, 611, // 96 - 103
+ 611, 278, 278, 556, 278, 889, 611, 611, // 104 - 111
+ 611, 611, 389, 556, 333, 611, 556, 778, // 112 - 119
+ 556, 556, 500, 389, 280, 389, 584, 0, // 120 - 127
+ 556, 0, 278, 556, 500, 1000, 556, 556, // 128 - 135
+ 333, 1000, 667, 333, 1000, 0, 500, 0, // 136 - 143
+ 0, 278, 278, 500, 500, 350, 556, 1000, // 144 - 151
+ 333, 1000, 556, 333, 944, 0, 500, 667, // 152 - 159
+ 278, 333, 556, 556, 556, 556, 280, 556, // 160 - 167
+ 333, 737, 370, 556, 584, 333, 737, 333, // 168 - 175
+ 400, 584, 333, 333, 333, 611, 556, 278, // 176 - 183
+ 333, 333, 365, 556, 834, 834, 834, 611, // 184 - 191
+ 722, 722, 722, 722, 722, 722, 1000, 722, // 192 - 199
+ 667, 667, 667, 667, 278, 278, 278, 278, // 200 - 207
+ 722, 722, 778, 778, 778, 778, 778, 584, // 208 - 215
+ 778, 722, 722, 722, 722, 667, 667, 611, // 216 - 223
+ 556, 556, 556, 556, 556, 556, 889, 556, // 224 - 231
+ 556, 556, 556, 556, 278, 278, 278, 278, // 232 - 239
+ 611, 611, 611, 611, 611, 611, 611, 584, // 240 - 247
+ 611, 611, 611, 611, 611, 556, 611, 556 // 248 - 255
+ },
+ nullptr },
+
+ { "Helvetica", // family name
+ "Bold Italic", // style
+ "Helvetica-BoldOblique", // PSName
+ 718,
+ -207, // ascend, descend
+ FAMILY_SWISS, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_BOLD, // weight type
+ ITALIC_NORMAL, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 278, 333, 474, 556, 556, 889, 722, 238, // 32 - 39
+ 333, 333, 389, 584, 278, 333, 278, 278, // 40 - 47
+ 556, 556, 556, 556, 556, 556, 556, 556, // 48 - 55
+ 556, 556, 333, 333, 584, 584, 584, 611, // 56 - 63
+ 975, 722, 722, 722, 722, 667, 611, 778, // 64 - 71
+ 722, 278, 556, 722, 611, 833, 722, 778, // 72 - 79
+ 667, 778, 722, 667, 611, 722, 667, 944, // 80 - 87
+ 667, 667, 611, 333, 278, 333, 584, 556, // 88 - 95
+ 333, 556, 611, 556, 611, 556, 333, 611, // 96 - 103
+ 611, 278, 278, 556, 278, 889, 611, 611, // 104 - 111
+ 611, 611, 389, 556, 333, 611, 556, 778, // 112 - 119
+ 556, 556, 500, 389, 280, 389, 584, 0, // 120 - 127
+ 556, 0, 278, 556, 500, 1000, 556, 556, // 128 - 135
+ 333, 1000, 667, 333, 1000, 0, 500, 0, // 136 - 143
+ 0, 278, 278, 500, 500, 350, 556, 1000, // 144 - 151
+ 333, 1000, 556, 333, 944, 0, 500, 667, // 152 - 159
+ 278, 333, 556, 556, 556, 556, 280, 556, // 160 - 167
+ 333, 737, 370, 556, 584, 333, 737, 333, // 168 - 175
+ 400, 584, 333, 333, 333, 611, 556, 278, // 176 - 183
+ 333, 333, 365, 556, 834, 834, 834, 611, // 184 - 191
+ 722, 722, 722, 722, 722, 722, 1000, 722, // 192 - 199
+ 667, 667, 667, 667, 278, 278, 278, 278, // 200 - 207
+ 722, 722, 778, 778, 778, 778, 778, 584, // 208 - 215
+ 778, 722, 722, 722, 722, 667, 667, 611, // 216 - 223
+ 556, 556, 556, 556, 556, 556, 889, 556, // 224 - 231
+ 556, 556, 556, 556, 278, 278, 278, 278, // 232 - 239
+ 611, 611, 611, 611, 611, 611, 611, 584, // 240 - 247
+ 611, 611, 611, 611, 611, 556, 611, 556 // 248 - 255
+ },
+ nullptr },
+
+ { "Times", // family name
+ "Normal", // style
+ "Times-Roman", // PSName
+ 683,
+ -217, // ascend, descend
+ FAMILY_ROMAN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 250, 333, 408, 500, 500, 833, 778, 180, // 32 - 39
+ 333, 333, 500, 564, 250, 333, 250, 278, // 40 - 47
+ 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55
+ 500, 500, 278, 278, 564, 564, 564, 444, // 56 - 63
+ 921, 722, 667, 667, 722, 611, 556, 722, // 64 - 71
+ 722, 333, 389, 722, 611, 889, 722, 722, // 72 - 79
+ 556, 722, 667, 556, 611, 722, 722, 944, // 80 - 87
+ 722, 722, 611, 333, 278, 333, 469, 500, // 88 - 95
+ 333, 444, 500, 444, 500, 444, 333, 500, // 96 - 103
+ 500, 278, 278, 500, 278, 778, 500, 500, // 104 - 111
+ 500, 500, 333, 389, 278, 500, 500, 722, // 112 - 119
+ 500, 500, 444, 480, 200, 480, 541, 0, // 120 - 127
+ 500, 0, 333, 500, 444, 1000, 500, 500, // 128 - 135
+ 333, 1000, 556, 333, 889, 0, 444, 0, // 136 - 143
+ 0, 333, 333, 444, 444, 350, 500, 1000, // 144 - 151
+ 333, 980, 389, 333, 722, 0, 444, 722, // 152 - 159
+ 250, 333, 500, 500, 500, 500, 200, 500, // 160 - 167
+ 333, 760, 276, 500, 564, 333, 760, 333, // 168 - 175
+ 400, 564, 300, 300, 333, 500, 453, 250, // 176 - 183
+ 333, 300, 310, 500, 750, 750, 750, 444, // 184 - 191
+ 722, 722, 722, 722, 722, 722, 889, 667, // 192 - 199
+ 611, 611, 611, 611, 333, 333, 333, 333, // 200 - 207
+ 722, 722, 722, 722, 722, 722, 722, 564, // 208 - 215
+ 722, 722, 722, 722, 722, 722, 556, 500, // 216 - 223
+ 444, 444, 444, 444, 444, 444, 667, 444, // 224 - 231
+ 444, 444, 444, 444, 278, 278, 278, 278, // 232 - 239
+ 500, 500, 500, 500, 500, 500, 500, 564, // 240 - 247
+ 500, 500, 500, 500, 500, 500, 500, 500 // 248 - 255
+ },
+ nullptr },
+
+ { "Times", // family name
+ "Italic", // style
+ "Times-Italic", // PSName
+ 683,
+ -217, // ascend, descend
+ FAMILY_ROMAN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NORMAL, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 250, 333, 420, 500, 500, 833, 778, 214, // 32 - 39
+ 333, 333, 500, 675, 250, 333, 250, 278, // 40 - 47
+ 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55
+ 500, 500, 333, 333, 675, 675, 675, 500, // 56 - 63
+ 920, 611, 611, 667, 722, 611, 611, 722, // 64 - 71
+ 722, 333, 444, 667, 556, 833, 667, 722, // 72 - 79
+ 611, 722, 611, 500, 556, 722, 611, 833, // 80 - 87
+ 611, 556, 556, 389, 278, 389, 422, 500, // 88 - 95
+ 333, 500, 500, 444, 500, 444, 278, 500, // 96 - 103
+ 500, 278, 278, 444, 278, 722, 500, 500, // 104 - 111
+ 500, 500, 389, 389, 278, 500, 444, 667, // 112 - 119
+ 444, 444, 389, 400, 275, 400, 541, 0, // 120 - 127
+ 500, 0, 333, 500, 556, 889, 500, 500, // 128 - 135
+ 333, 1000, 500, 333, 944, 0, 389, 0, // 136 - 143
+ 0, 333, 333, 556, 556, 350, 500, 889, // 144 - 151
+ 333, 980, 389, 333, 667, 0, 389, 556, // 152 - 159
+ 250, 389, 500, 500, 500, 500, 275, 500, // 160 - 167
+ 333, 760, 276, 500, 675, 333, 760, 333, // 168 - 175
+ 400, 675, 300, 300, 333, 500, 523, 250, // 176 - 183
+ 333, 300, 310, 500, 750, 750, 750, 500, // 184 - 191
+ 611, 611, 611, 611, 611, 611, 889, 667, // 192 - 199
+ 611, 611, 611, 611, 333, 333, 333, 333, // 200 - 207
+ 722, 667, 722, 722, 722, 722, 722, 675, // 208 - 215
+ 722, 722, 722, 722, 722, 556, 611, 500, // 216 - 223
+ 500, 500, 500, 500, 500, 500, 667, 444, // 224 - 231
+ 444, 444, 444, 444, 278, 278, 278, 278, // 232 - 239
+ 500, 500, 500, 500, 500, 500, 500, 675, // 240 - 247
+ 500, 500, 500, 500, 500, 444, 500, 444 // 248 - 255
+ },
+ nullptr },
+
+ { "Times", // family name
+ "Bold", // style
+ "Times-Bold", // PSName
+ 683,
+ -217, // ascend, descend
+ FAMILY_ROMAN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_BOLD, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 250, 333, 555, 500, 500, 1000, 833, 278, // 32 - 39
+ 333, 333, 500, 570, 250, 333, 250, 278, // 40 - 47
+ 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55
+ 500, 500, 333, 333, 570, 570, 570, 500, // 56 - 63
+ 930, 722, 667, 722, 722, 667, 611, 778, // 64 - 71
+ 778, 389, 500, 778, 667, 944, 722, 778, // 72 - 79
+ 611, 778, 722, 556, 667, 722, 722, 1000, // 80 - 87
+ 722, 722, 667, 333, 278, 333, 581, 500, // 88 - 95
+ 333, 500, 556, 444, 556, 444, 333, 500, // 96 - 103
+ 556, 278, 333, 556, 278, 833, 556, 500, // 104 - 111
+ 556, 556, 444, 389, 333, 556, 500, 722, // 112 - 119
+ 500, 500, 444, 394, 220, 394, 520, 0, // 120 - 127
+ 500, 0, 333, 500, 500, 1000, 500, 500, // 128 - 135
+ 333, 1000, 556, 333, 1000, 0, 444, 0, // 136 - 143
+ 0, 333, 333, 500, 500, 350, 500, 1000, // 144 - 151
+ 333, 1000, 389, 333, 722, 0, 444, 722, // 152 - 159
+ 250, 333, 500, 500, 500, 500, 220, 500, // 160 - 167
+ 333, 747, 300, 500, 570, 333, 747, 333, // 168 - 175
+ 400, 570, 300, 300, 333, 556, 540, 250, // 176 - 183
+ 333, 300, 330, 500, 750, 750, 750, 500, // 184 - 191
+ 722, 722, 722, 722, 722, 722, 1000, 722, // 192 - 199
+ 667, 667, 667, 667, 389, 389, 389, 389, // 200 - 207
+ 722, 722, 778, 778, 778, 778, 778, 570, // 208 - 215
+ 778, 722, 722, 722, 722, 722, 611, 556, // 216 - 223
+ 500, 500, 500, 500, 500, 500, 722, 444, // 224 - 231
+ 444, 444, 444, 444, 278, 278, 278, 278, // 232 - 239
+ 500, 556, 500, 500, 500, 500, 500, 570, // 240 - 247
+ 500, 556, 556, 556, 556, 500, 556, 500 // 248 - 255
+ },
+ nullptr },
+
+ { "Times", // family name
+ "Bold Italic", // style
+ "Times-BoldItalic", // PSName
+ 683,
+ -217, // ascend, descend
+ FAMILY_ROMAN, // family style
+ RTL_TEXTENCODING_MS_1252, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_BOLD, // weight type
+ ITALIC_NORMAL, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 250, 389, 555, 500, 500, 833, 778, 278, // 32 - 39
+ 333, 333, 500, 570, 250, 333, 250, 278, // 40 - 47
+ 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55
+ 500, 500, 333, 333, 570, 570, 570, 500, // 56 - 63
+ 832, 667, 667, 667, 722, 667, 667, 722, // 64 - 71
+ 778, 389, 500, 667, 611, 889, 722, 722, // 72 - 79
+ 611, 722, 667, 556, 611, 722, 667, 889, // 80 - 87
+ 667, 611, 611, 333, 278, 333, 570, 500, // 88 - 95
+ 333, 500, 500, 444, 500, 444, 333, 500, // 96 - 103
+ 556, 278, 278, 500, 278, 778, 556, 500, // 104 - 111
+ 500, 500, 389, 389, 278, 556, 444, 667, // 112 - 119
+ 500, 444, 389, 348, 220, 348, 570, 0, // 120 - 127
+ 500, 0, 333, 500, 500, 1000, 500, 500, // 128 - 135
+ 333, 1000, 556, 333, 944, 0, 389, 0, // 136 - 143
+ 0, 333, 333, 500, 500, 350, 500, 1000, // 144 - 151
+ 333, 1000, 389, 333, 722, 0, 389, 611, // 152 - 159
+ 250, 389, 500, 500, 500, 500, 220, 500, // 160 - 167
+ 333, 747, 266, 500, 606, 333, 747, 333, // 168 - 175
+ 400, 570, 300, 300, 333, 576, 500, 250, // 176 - 183
+ 333, 300, 300, 500, 750, 750, 750, 500, // 184 - 191
+ 667, 667, 667, 667, 667, 667, 944, 667, // 192 - 199
+ 667, 667, 667, 667, 389, 389, 389, 389, // 200 - 207
+ 722, 722, 722, 722, 722, 722, 722, 570, // 208 - 215
+ 722, 722, 722, 722, 722, 611, 611, 500, // 216 - 223
+ 500, 500, 500, 500, 500, 500, 722, 444, // 224 - 231
+ 444, 444, 444, 444, 278, 278, 278, 278, // 232 - 239
+ 500, 556, 500, 500, 500, 500, 500, 570, // 240 - 247
+ 500, 556, 556, 556, 556, 444, 500, 444 // 248 - 255
+ },
+ nullptr },
+
+ // The font name "Symbol" is too generic and causes plenty of trouble.
+ // To ensure WYSIWIG the PDF-Base14 variant gets a not-confusable name
+ { "PDF_Base14_Symbol", // family name
+ "Normal", // style
+ "Symbol", // PSName
+ 1010,
+ -293, // ascend, descend
+ FAMILY_DONTKNOW, // family style
+ RTL_TEXTENCODING_ADOBE_SYMBOL, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 250, 333, 713, 500, 549, 833, 778, 439, // 32 - 39
+ 333, 333, 500, 549, 250, 549, 250, 278, // 40 - 47
+ 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55
+ 500, 500, 278, 278, 549, 549, 549, 444, // 56 - 63
+ 549, 722, 667, 722, 612, 611, 763, 603, // 64 - 71
+ 722, 333, 631, 722, 686, 889, 722, 722, // 72 - 79
+ 768, 741, 556, 592, 611, 690, 439, 768, // 80 - 87
+ 645, 795, 611, 333, 863, 333, 658, 500, // 88 - 95
+ 500, 631, 549, 549, 494, 439, 521, 411, // 96 - 103
+ 603, 329, 603, 549, 549, 576, 521, 549, // 104 - 111
+ 549, 521, 549, 603, 439, 576, 713, 686, // 112 - 119
+ 493, 686, 494, 480, 200, 480, 549, 0, // 120 - 127
+ 0, 0, 0, 0, 0, 0, 0, 0, // 128 - 135
+ 0, 0, 0, 0, 0, 0, 0, 0, // 136 - 143
+ 0, 0, 0, 0, 0, 0, 0, 0, // 144 - 151
+ 0, 0, 0, 0, 0, 0, 0, 0, // 152 - 159
+ 750, 620, 247, 549, 167, 713, 500, 753, // 160 - 167
+ 753, 753, 753, 1042, 987, 603, 987, 603, // 168 - 175
+ 400, 549, 411, 549, 549, 713, 494, 460, // 176 - 183
+ 549, 549, 549, 549, 1000, 603, 1000, 658, // 184 - 191
+ 823, 686, 795, 987, 768, 768, 823, 768, // 192 - 199
+ 768, 713, 713, 713, 713, 713, 713, 713, // 200 - 207
+ 768, 713, 790, 790, 890, 823, 549, 250, // 208 - 215
+ 713, 603, 603, 1042, 987, 603, 987, 603, // 216 - 223
+ 494, 329, 790, 790, 786, 713, 384, 384, // 224 - 231
+ 384, 384, 384, 384, 494, 494, 494, 494, // 232 - 239
+ 0, 329, 274, 686, 686, 686, 384, 384, // 240 - 247
+ 384, 384, 384, 384, 494, 494, 494, 0 // 248 - 255
+ },
+ nullptr },
+
+ { "ZapfDingbats", // family name
+ "Normal", // style
+ "ZapfDingbats", // PSName
+ 820,
+ -143, // ascend, descend
+ FAMILY_DONTKNOW, // family style
+ RTL_TEXTENCODING_ADOBE_DINGBATS, // charset
+ PITCH_VARIABLE, // pitch
+ WIDTH_NORMAL, // width type
+ WEIGHT_NORMAL, // weight type
+ ITALIC_NONE, // italic type
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31
+ 278, 974, 961, 974, 980, 719, 789, 790, // 32 - 39
+ 791, 690, 960, 939, 549, 855, 911, 933, // 40 - 47
+ 911, 945, 974, 755, 846, 762, 761, 571, // 48 - 55
+ 677, 763, 760, 759, 754, 494, 552, 537, // 56 - 63
+ 577, 692, 786, 788, 788, 790, 793, 794, // 64 - 71
+ 816, 823, 789, 841, 823, 833, 816, 831, // 72 - 79
+ 923, 744, 723, 749, 790, 792, 695, 776, // 80 - 87
+ 768, 792, 759, 707, 708, 682, 701, 826, // 88 - 95
+ 815, 789, 789, 707, 687, 696, 689, 786, // 96 - 103
+ 787, 713, 791, 785, 791, 873, 761, 762, // 104 - 111
+ 762, 759, 759, 892, 892, 788, 784, 438, // 112 - 119
+ 138, 277, 415, 392, 392, 668, 668, 0, // 120 - 127
+ 390, 390, 317, 317, 276, 276, 509, 509, // 128 - 135
+ 410, 410, 234, 234, 334, 334, 0, 0, // 136 - 143
+ 0, 0, 0, 0, 0, 0, 0, 0, // 144 - 151
+ 0, 0, 0, 0, 0, 0, 0, 0, // 152 - 159
+ 0, 732, 544, 544, 910, 667, 760, 760, // 160 - 167
+ 776, 595, 694, 626, 788, 788, 788, 788, // 168 - 175
+ 788, 788, 788, 788, 788, 788, 788, 788, // 176 - 183
+ 788, 788, 788, 788, 788, 788, 788, 788, // 184 - 191
+ 788, 788, 788, 788, 788, 788, 788, 788, // 192 - 199
+ 788, 788, 788, 788, 788, 788, 788, 788, // 200 - 207
+ 788, 788, 788, 788, 894, 838, 1016, 458, // 208 - 215
+ 748, 924, 748, 918, 927, 928, 928, 834, // 216 - 223
+ 873, 828, 924, 924, 917, 930, 931, 463, // 224 - 231
+ 883, 836, 836, 867, 867, 696, 696, 874, // 232 - 239
+ 0, 874, 760, 946, 771, 865, 771, 888, // 240 - 247
+ 967, 888, 831, 873, 927, 970, 918, 0 // 248 - 255
+ },
+ nullptr }
+
+ };
+
+BuildinFontInstance::BuildinFontInstance(const vcl::font::PhysicalFontFace& rFontFace,
+ const vcl::font::FontSelectPattern& rFSP)
+ : LogicalFontInstance(rFontFace, rFSP)
+{
+}
+
+bool BuildinFontInstance::GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const
+{
+ return false;
+}
+
+BuildinFontFace::BuildinFontFace(int nId)
+ : vcl::font::PhysicalFontFace(m_aBuildinFonts[nId].GetFontAttributes())
+ , mrBuildin(m_aBuildinFonts[nId])
+{
+}
+
+rtl::Reference<LogicalFontInstance>
+BuildinFontFace::CreateFontInstance(const vcl::font::FontSelectPattern& rFSP) const
+{
+ return new BuildinFontInstance(*this, rFSP);
+}
+
+} // namespace vcl::pdf
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfextoutdevdata.cxx b/vcl/source/gdi/pdfextoutdevdata.cxx
new file mode 100644
index 0000000000..c2c838d5db
--- /dev/null
+++ b/vcl/source/gdi/pdfextoutdevdata.cxx
@@ -0,0 +1,919 @@
+/* -*- 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 <vcl/canvastools.hxx>
+#include <vcl/pdfextoutdevdata.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/gfxlink.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <tools/stream.hxx>
+
+#include <memory>
+#include <map>
+#include <variant>
+
+namespace vcl
+{
+namespace {
+
+struct CreateNamedDest {
+ OUString maDestName;
+ MapMode maParaMapMode;
+ PDFWriter::DestAreaType mnParaDestAreaType;
+ tools::Rectangle maParaRect;
+ sal_Int32 mnPage;
+};
+struct CreateDest {
+ MapMode maParaMapMode;
+ PDFWriter::DestAreaType mnParaDestAreaType;
+ tools::Rectangle maParaRect;
+ sal_Int32 mnPage;
+};
+struct CreateControlLink { sal_Int32 mnControlId; };
+struct CreateLink {
+ OUString maAltText;
+ MapMode maParaMapMode;
+ tools::Rectangle maParaRect;
+ sal_Int32 mnPage;
+};
+struct CreateScreen {
+ OUString maAltText;
+ OUString maMimeType;
+ MapMode maParaMapMode;
+ tools::Rectangle maParaRect;
+ sal_Int32 mnPage;
+};
+struct SetLinkDest {
+ sal_Int32 mnLinkId;
+ sal_Int32 mnDestId;
+};
+struct SetLinkURL {
+ OUString maLinkURL;
+ sal_Int32 mnLinkId;
+};
+struct SetScreenURL {
+ OUString maScreenURL;
+ sal_Int32 mnScreenId;
+};
+struct SetScreenStream {
+ OUString maScreenStream;
+ sal_Int32 mnScreenId;
+};
+struct RegisterDest { sal_Int32 mnDestId; };
+struct CreateOutlineItem {
+ OUString maText;
+ sal_Int32 mnParent;
+ sal_Int32 mnDestID;
+};
+struct CreateNote {
+ MapMode maParaMapMode;
+ PDFNote maParaPDFNote;
+ tools::Rectangle maParaRect;
+ sal_Int32 mnPage;
+};
+struct SetPageTransition {
+ PDFWriter::PageTransition maParaPageTransition;
+ sal_uInt32 mnMilliSec;
+ sal_Int32 mnPage;
+};
+struct EnsureStructureElement { sal_Int32 mnId; };
+struct InitStructureElement {
+ PDFWriter::StructElement mParaStructElement;
+ OUString maAlias;
+ sal_Int32 mnId;
+};
+struct BeginStructureElement { sal_Int32 mnId; };
+struct EndStructureElement{};
+struct SetCurrentStructureElement { sal_Int32 mnStructId; };
+struct SetStructureAttribute {
+ PDFWriter::StructAttribute mParaStructAttribute;
+ PDFWriter::StructAttributeValue mParaStructAttributeValue;
+};
+struct SetStructureAttributeNumerical { PDFWriter::StructAttribute mParaStructAttribute; sal_Int32 mnId; };
+struct SetStructureBoundingBox { tools::Rectangle mRect; };
+struct SetStructureAnnotIds {
+ ::std::vector<sal_Int32> annotIds;
+};
+struct SetActualText { OUString maText; };
+struct SetAlternateText { OUString maText; };
+struct CreateControl {
+ std::shared_ptr< PDFWriter::AnyWidget > mxControl;
+};
+struct BeginGroup {};
+struct EndGroupGfxLink {
+ Graphic maGraphic;
+ tools::Rectangle maOutputRect, maVisibleOutputRect;
+ sal_Int32 mnTransparency;
+};
+
+typedef std::variant<CreateNamedDest,
+ CreateDest,
+ CreateControlLink,
+ CreateLink,
+ CreateScreen,
+ SetLinkDest,
+ SetLinkURL,
+ SetScreenURL,
+ SetScreenStream,
+ RegisterDest,
+ CreateOutlineItem,
+ CreateNote,
+ SetPageTransition> GlobalActionData;
+
+typedef std::variant<EnsureStructureElement,
+ InitStructureElement,
+ BeginStructureElement,
+ EndStructureElement,
+ SetCurrentStructureElement,
+ SetStructureAttribute,
+ SetStructureAttributeNumerical,
+ SetStructureBoundingBox,
+ SetStructureAnnotIds,
+ SetActualText,
+ SetAlternateText,
+ CreateControl,
+ BeginGroup,
+ EndGroupGfxLink> PageActionData;
+
+struct PDFExtOutDevDataSyncPage
+{
+ sal_uInt32 nIdx;
+ PageActionData eAct;
+};
+
+struct PDFLinkDestination
+{
+ tools::Rectangle mRect;
+ MapMode mMapMode;
+ sal_Int32 mPageNr;
+ PDFWriter::DestAreaType mAreaType;
+};
+}
+
+struct GlobalSyncData
+{
+ std::deque< GlobalActionData > mActions;
+ ::std::map< sal_Int32, PDFLinkDestination > mFutureDestinations;
+
+ sal_Int32 GetMappedId(sal_Int32 nLinkId);
+
+ /** the way this appears to work: (only) everything that increments mCurId
+ at recording time must put an item into mParaIds at playback time,
+ so that the mCurId becomes the eventual index into mParaIds.
+ */
+ sal_Int32 mCurId;
+ std::vector< sal_Int32 > mParaIds;
+ std::map<void const*, sal_Int32> mSEMap;
+
+ sal_Int32 mCurrentStructElement;
+ std::vector< sal_Int32 > mStructParents;
+ GlobalSyncData() :
+ mCurId ( 0 ),
+ mCurrentStructElement( 0 )
+ {
+ mStructParents.push_back(0); // because PDFWriterImpl has a dummy root
+ }
+ void PlayGlobalActions( PDFWriter& rWriter );
+};
+
+sal_Int32 GlobalSyncData::GetMappedId(sal_Int32 nLinkId)
+{
+ /* negative values are intentionally passed as invalid IDs
+ * e.g. to create a new top level outline item
+ */
+ if( nLinkId >= 0 )
+ {
+ if ( o3tl::make_unsigned(nLinkId) < mParaIds.size() )
+ nLinkId = mParaIds[ nLinkId ];
+ else
+ nLinkId = -1;
+
+ SAL_WARN_IF( nLinkId < 0, "vcl", "unmapped id in GlobalSyncData" );
+ }
+
+ return nLinkId;
+}
+
+void GlobalSyncData::PlayGlobalActions( PDFWriter& rWriter )
+{
+ for (auto const& action : mActions)
+ {
+ if (std::holds_alternative<CreateNamedDest>(action)) { //i56629
+ const vcl::CreateNamedDest& rCreateNamedDest = std::get<CreateNamedDest>(action);
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( rCreateNamedDest.maParaMapMode );
+ mParaIds.push_back( rWriter.CreateNamedDest( rCreateNamedDest.maDestName, rCreateNamedDest.maParaRect, rCreateNamedDest.mnPage, rCreateNamedDest.mnParaDestAreaType ) );
+ rWriter.Pop();
+ }
+ else if (std::holds_alternative<CreateDest>(action)) {
+ const vcl::CreateDest& rCreateDest = std::get<CreateDest>(action);
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( rCreateDest.maParaMapMode );
+ mParaIds.push_back( rWriter.CreateDest( rCreateDest.maParaRect, rCreateDest.mnPage, rCreateDest.mnParaDestAreaType ) );
+ rWriter.Pop();
+ }
+ else if (std::holds_alternative<CreateControlLink>(action)) {
+ const vcl::CreateControlLink& rCreateControlLink = std::get<CreateControlLink>(action);
+ // tdf#157397: this must be called *in order* with CreateLink etc.
+ rWriter.SetLinkPropertyID(rCreateControlLink.mnControlId, sal_Int32(mParaIds.size()));
+ mParaIds.push_back(rCreateControlLink.mnControlId);
+ }
+ else if (std::holds_alternative<CreateLink>(action)) {
+ const vcl::CreateLink& rCreateLink = std::get<CreateLink>(action);
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( rCreateLink.maParaMapMode );
+ mParaIds.push_back( rWriter.CreateLink(rCreateLink.maParaRect, rCreateLink.mnPage, rCreateLink.maAltText) );
+ // resolve LinkAnnotation structural attribute
+ rWriter.SetLinkPropertyID( mParaIds.back(), sal_Int32(mParaIds.size()-1) );
+ rWriter.Pop();
+ }
+ else if (std::holds_alternative<CreateScreen>(action)) {
+ const vcl::CreateScreen& rCreateScreen = std::get<CreateScreen>(action);
+ rWriter.Push(PushFlags::MAPMODE);
+ rWriter.SetMapMode(rCreateScreen.maParaMapMode);
+ mParaIds.push_back(rWriter.CreateScreen(rCreateScreen.maParaRect, rCreateScreen.mnPage, rCreateScreen.maAltText, rCreateScreen.maMimeType));
+ // resolve AnnotIds structural attribute
+ rWriter.SetLinkPropertyID(mParaIds.back(), sal_Int32(mParaIds.size()-1));
+ rWriter.Pop();
+ }
+ else if (std::holds_alternative<SetLinkDest>(action)) {
+ const vcl::SetLinkDest& rSetLinkDest = std::get<SetLinkDest>(action);
+ sal_Int32 nLinkId = GetMappedId(rSetLinkDest.mnLinkId);
+ sal_Int32 nDestId = GetMappedId(rSetLinkDest.mnDestId);
+ rWriter.SetLinkDest( nLinkId, nDestId );
+ }
+ else if (std::holds_alternative<SetLinkURL>(action)) {
+ const vcl::SetLinkURL& rSetLinkURL = std::get<SetLinkURL>(action);
+ sal_Int32 nLinkId = GetMappedId(rSetLinkURL.mnLinkId);
+ rWriter.SetLinkURL( nLinkId, rSetLinkURL.maLinkURL );
+ }
+ else if (std::holds_alternative<SetScreenURL>(action)) {
+ const vcl::SetScreenURL& rSetScreenURL = std::get<SetScreenURL>(action);
+ sal_Int32 nScreenId = GetMappedId(rSetScreenURL.mnScreenId);
+ rWriter.SetScreenURL(nScreenId, rSetScreenURL.maScreenURL);
+ }
+ else if (std::holds_alternative<SetScreenStream>(action)) {
+ const vcl::SetScreenStream& rSetScreenStream = std::get<SetScreenStream>(action);
+ sal_Int32 nScreenId = GetMappedId(rSetScreenStream.mnScreenId);
+ rWriter.SetScreenStream(nScreenId, rSetScreenStream.maScreenStream);
+ }
+ else if (std::holds_alternative<RegisterDest>(action)) {
+ const vcl::RegisterDest& rRegisterDest = std::get<RegisterDest>(action);
+ const sal_Int32 nDestId = rRegisterDest.mnDestId;
+ OSL_ENSURE( mFutureDestinations.find( nDestId ) != mFutureDestinations.end(),
+ "GlobalSyncData::PlayGlobalActions: DescribeRegisteredRequest has not been called for that destination!" );
+
+ PDFLinkDestination& rDest = mFutureDestinations[ nDestId ];
+
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( rDest.mMapMode );
+ mParaIds.push_back( rWriter.RegisterDestReference( nDestId, rDest.mRect, rDest.mPageNr, rDest.mAreaType ) );
+ rWriter.Pop();
+ }
+ else if (std::holds_alternative<CreateOutlineItem>(action)) {
+ const vcl::CreateOutlineItem& rCreateOutlineItem = std::get<CreateOutlineItem>(action);
+ sal_Int32 nParent = GetMappedId(rCreateOutlineItem.mnParent);
+ sal_Int32 nLinkId = GetMappedId(rCreateOutlineItem.mnDestID);
+ mParaIds.push_back( rWriter.CreateOutlineItem( nParent, rCreateOutlineItem.maText, nLinkId ) );
+ }
+ else if (std::holds_alternative<CreateNote>(action)) {
+ const vcl::CreateNote& rCreateNote = std::get<CreateNote>(action);
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( rCreateNote.maParaMapMode );
+ rWriter.CreateNote( rCreateNote.maParaRect, rCreateNote.maParaPDFNote, rCreateNote.mnPage );
+ }
+ else if (std::holds_alternative<SetPageTransition>(action)) {
+ const vcl::SetPageTransition& rSetPageTransition = std::get<SetPageTransition>(action);
+ rWriter.SetPageTransition( rSetPageTransition.maParaPageTransition, rSetPageTransition.mnMilliSec, rSetPageTransition.mnPage );
+ }
+ }
+}
+
+struct PageSyncData
+{
+ std::deque< PDFExtOutDevDataSyncPage > mActions;
+ Graphic mCurrentGraphic;
+ GlobalSyncData* mpGlobalData;
+
+ bool mbGroupIgnoreGDIMtfActions;
+
+
+ explicit PageSyncData( GlobalSyncData* pGlobal )
+ : mbGroupIgnoreGDIMtfActions ( false )
+ { mpGlobalData = pGlobal; }
+
+ void PushAction( const OutputDevice& rOutDev, PageActionData eAct );
+ bool PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAction, const GDIMetaFile& rMtf, const PDFExtOutDevData& rOutDevData );
+};
+
+void PageSyncData::PushAction( const OutputDevice& rOutDev, PageActionData eAct )
+{
+ GDIMetaFile* pMtf = rOutDev.GetConnectMetaFile();
+ SAL_WARN_IF( !pMtf, "vcl", "PageSyncData::PushAction -> no ConnectMetaFile !!!" );
+
+ PDFExtOutDevDataSyncPage aSync;
+ aSync.eAct = std::move(eAct);
+ if ( pMtf )
+ aSync.nIdx = pMtf->GetActionSize();
+ else
+ aSync.nIdx = 0x7fffffff; // sync not possible
+ mActions.emplace_back( std::move(aSync) );
+}
+bool PageSyncData::PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAction, const GDIMetaFile& rMtf, const PDFExtOutDevData& rOutDevData )
+{
+ bool bRet = false;
+ if ( !mActions.empty() && ( mActions.front().nIdx == rCurGDIMtfAction ) )
+ {
+ bRet = true;
+ PDFExtOutDevDataSyncPage aDataSync = std::move(mActions.front());
+ mActions.pop_front();
+ if (std::holds_alternative<EnsureStructureElement>(aDataSync.eAct)) {
+#ifndef NDEBUG
+ const vcl::EnsureStructureElement& rEnsureStructureElement = std::get<EnsureStructureElement>(aDataSync.eAct);
+ sal_Int32 const id =
+#endif
+ rWriter.EnsureStructureElement();
+ assert(id == -1 || id == rEnsureStructureElement.mnId); // identity mapping
+ }
+ else if (std::holds_alternative<InitStructureElement>(aDataSync.eAct)) {
+ const vcl::InitStructureElement& rInitStructureElement = std::get<InitStructureElement>(aDataSync.eAct);
+ rWriter.InitStructureElement(rInitStructureElement.mnId, rInitStructureElement.mParaStructElement, rInitStructureElement.maAlias);
+ }
+ else if (std::holds_alternative<BeginStructureElement>(aDataSync.eAct)) {
+ const vcl::BeginStructureElement& rBeginStructureElement = std::get<BeginStructureElement>(aDataSync.eAct);
+ rWriter.BeginStructureElement(rBeginStructureElement.mnId);
+ }
+ else if (std::holds_alternative<EndStructureElement>(aDataSync.eAct)) {
+ rWriter.EndStructureElement();
+ }
+ else if (std::holds_alternative<SetCurrentStructureElement>(aDataSync.eAct)) {
+ const vcl::SetCurrentStructureElement& rSetCurrentStructureElement = std::get<SetCurrentStructureElement>(aDataSync.eAct);
+ rWriter.SetCurrentStructureElement(rSetCurrentStructureElement.mnStructId);
+ }
+ else if (std::holds_alternative<SetStructureAttribute>(aDataSync.eAct)) {
+ const vcl::SetStructureAttribute& rSetStructureAttribute = std::get<SetStructureAttribute>(aDataSync.eAct);
+ rWriter.SetStructureAttribute( rSetStructureAttribute.mParaStructAttribute, rSetStructureAttribute.mParaStructAttributeValue );
+ }
+ else if (std::holds_alternative<SetStructureAttributeNumerical>(aDataSync.eAct)) {
+ const vcl::SetStructureAttributeNumerical& rSetStructureAttributeNumerical = std::get<SetStructureAttributeNumerical>(aDataSync.eAct);
+ rWriter.SetStructureAttributeNumerical( rSetStructureAttributeNumerical.mParaStructAttribute, rSetStructureAttributeNumerical.mnId );
+ }
+ else if (std::holds_alternative<SetStructureBoundingBox>(aDataSync.eAct)) {
+ const vcl::SetStructureBoundingBox& rSetStructureBoundingBox = std::get<SetStructureBoundingBox>(aDataSync.eAct);
+ rWriter.SetStructureBoundingBox( rSetStructureBoundingBox.mRect );
+ }
+ else if (std::holds_alternative<SetStructureAnnotIds>(aDataSync.eAct)) {
+ const vcl::SetStructureAnnotIds& rSetStructureAnnotIds = std::get<SetStructureAnnotIds>(aDataSync.eAct);
+ rWriter.SetStructureAnnotIds(rSetStructureAnnotIds.annotIds);
+ }
+ else if (std::holds_alternative<SetActualText>(aDataSync.eAct)) {
+ const vcl::SetActualText& rSetActualText = std::get<SetActualText>(aDataSync.eAct);
+ rWriter.SetActualText( rSetActualText.maText );
+ }
+ else if (std::holds_alternative<SetAlternateText>(aDataSync.eAct)) {
+ const vcl::SetAlternateText& rSetAlternateText = std::get<SetAlternateText>(aDataSync.eAct);
+ rWriter.SetAlternateText( rSetAlternateText.maText );
+ }
+ else if (std::holds_alternative<CreateControl>(aDataSync.eAct)) {
+ const vcl::CreateControl& rCreateControl = std::get<CreateControl>(aDataSync.eAct);
+ std::shared_ptr< PDFWriter::AnyWidget > pControl( rCreateControl.mxControl );
+ SAL_WARN_IF( !pControl, "vcl", "PageSyncData::PlaySyncPageAct: invalid widget!" );
+ if ( pControl )
+ {
+ sal_Int32 const n = rWriter.CreateControl(*pControl);
+ // resolve AnnotIds structural attribute
+ ::std::vector<sal_Int32> const annotIds{ sal_Int32(mpGlobalData->mCurId) };
+ rWriter.SetStructureAnnotIds(annotIds);
+ // tdf#157397: this must be called *in order* with CreateLink etc.
+ mpGlobalData->mActions.push_back(CreateControlLink{n});
+ mpGlobalData->mCurId++;
+ }
+ }
+ else if (std::holds_alternative<BeginGroup>(aDataSync.eAct)) {
+ /* first determining if this BeginGroup is starting a GfxLink,
+ by searching for an EndGroup or an EndGroupGfxLink */
+ mbGroupIgnoreGDIMtfActions = false;
+ auto itStartingGfxLink = std::find_if(mActions.begin(), mActions.end(),
+ [](const PDFExtOutDevDataSyncPage& rAction) { return std::holds_alternative<EndGroupGfxLink>(rAction.eAct); });
+ if ( itStartingGfxLink != mActions.end() )
+ {
+ EndGroupGfxLink& rEndGroup = std::get<EndGroupGfxLink>(itStartingGfxLink->eAct);
+ Graphic& rGraphic = rEndGroup.maGraphic;
+ if ( rGraphic.IsGfxLink() )
+ {
+ GfxLinkType eType = rGraphic.GetGfxLink().GetType();
+ if ( eType == GfxLinkType::NativeJpg )
+ {
+ mbGroupIgnoreGDIMtfActions = rOutDevData.HasAdequateCompression(rGraphic, rEndGroup.maOutputRect, rEndGroup.maVisibleOutputRect);
+ if ( !mbGroupIgnoreGDIMtfActions )
+ mCurrentGraphic = rGraphic;
+ }
+ else if ( eType == GfxLinkType::NativePng || eType == GfxLinkType::NativePdf )
+ {
+ if ( eType == GfxLinkType::NativePdf || rOutDevData.HasAdequateCompression(rGraphic, rEndGroup.maOutputRect, rEndGroup.maVisibleOutputRect) )
+ mCurrentGraphic = rGraphic;
+ }
+ }
+ }
+ }
+ else if (std::holds_alternative<EndGroupGfxLink>(aDataSync.eAct)) {
+ EndGroupGfxLink& rEndGroup = std::get<EndGroupGfxLink>(aDataSync.eAct);
+ tools::Rectangle aOutputRect, aVisibleOutputRect;
+ Graphic aGraphic( rEndGroup.maGraphic );
+
+ sal_Int32 nTransparency = rEndGroup.mnTransparency;
+ aOutputRect = rEndGroup.maOutputRect;
+ aVisibleOutputRect = rEndGroup.maVisibleOutputRect;
+
+ if ( mbGroupIgnoreGDIMtfActions )
+ {
+ bool bClippingNeeded = ( aOutputRect != aVisibleOutputRect ) && !aVisibleOutputRect.IsEmpty();
+
+ GfxLink aGfxLink( aGraphic.GetGfxLink() );
+ if ( aGfxLink.GetType() == GfxLinkType::NativeJpg )
+ {
+ if ( bClippingNeeded )
+ {
+ rWriter.Push();
+ basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(aVisibleOutputRect) ) );
+ rWriter.SetClipRegion( aRect);
+ }
+
+ AlphaMask aAlphaMask;
+ if (nTransparency)
+ {
+ aAlphaMask = AlphaMask(aGraphic.GetSizePixel());
+ aAlphaMask.Erase(nTransparency);
+ }
+
+ SvMemoryStream aTmp;
+ const sal_uInt8* pData = aGfxLink.GetData();
+ sal_uInt32 nBytes = aGfxLink.GetDataSize();
+ if( pData && nBytes )
+ {
+ aTmp.WriteBytes( pData, nBytes );
+
+ // Look up the output rectangle from the previous
+ // bitmap scale action if possible. This has the
+ // correct position and size for images with a
+ // custom translation (Writer header) or scaling
+ // (Impress notes page).
+ if (rCurGDIMtfAction > 0)
+ {
+ const MetaAction* pAction = rMtf.GetAction(rCurGDIMtfAction - 1);
+ if (pAction && pAction->GetType() == MetaActionType::BMPSCALE)
+ {
+ const MetaBmpScaleAction* pA
+ = static_cast<const MetaBmpScaleAction*>(pAction);
+ aOutputRect.SetPos(pA->GetPoint());
+ aOutputRect.SetSize(pA->GetSize());
+ }
+ }
+ auto ePixelFormat = aGraphic.GetBitmapEx().getPixelFormat();
+ rWriter.DrawJPGBitmap(aTmp, ePixelFormat > vcl::PixelFormat::N8_BPP, aGraphic.GetSizePixel(), aOutputRect, aAlphaMask, aGraphic);
+ }
+
+ if ( bClippingNeeded )
+ rWriter.Pop();
+ }
+ mbGroupIgnoreGDIMtfActions = false;
+ }
+ mCurrentGraphic.Clear();
+ }
+ }
+ else if ( mbGroupIgnoreGDIMtfActions )
+ {
+ rCurGDIMtfAction++;
+ bRet = true;
+ }
+ return bRet;
+}
+
+PDFExtOutDevData::PDFExtOutDevData( const OutputDevice& rOutDev ) :
+ mrOutDev ( rOutDev ),
+ mbTaggedPDF ( false ),
+ mbExportNotes ( true ),
+ mbExportNotesInMargin ( false ),
+ mbExportNotesPages ( false ),
+ mbTransitionEffects ( true ),
+ mbUseLosslessCompression( true ),
+ mbReduceImageResolution ( false ),
+ mbExportFormFields ( false ),
+ mbExportBookmarks ( false ),
+ mbExportHiddenSlides ( false ),
+ mbSinglePageSheets ( false ),
+ mbExportNDests ( false ),
+ mnPage ( -1 ),
+ mnCompressionQuality ( 90 ),
+ mpGlobalSyncData ( new GlobalSyncData() )
+{
+ mpPageSyncData.reset( new PageSyncData( mpGlobalSyncData.get() ) );
+}
+
+PDFExtOutDevData::~PDFExtOutDevData()
+{
+ mpPageSyncData.reset();
+ mpGlobalSyncData.reset();
+}
+
+const Graphic& PDFExtOutDevData::GetCurrentGraphic() const
+{
+ return mpPageSyncData->mCurrentGraphic;
+}
+
+void PDFExtOutDevData::SetDocumentLocale( const css::lang::Locale& rLoc )
+{
+ maDocLocale = rLoc;
+}
+void PDFExtOutDevData::SetCurrentPageNumber( const sal_Int32 nPage )
+{
+ mnPage = nPage;
+}
+void PDFExtOutDevData::SetIsLosslessCompression( const bool bUseLosslessCompression )
+{
+ mbUseLosslessCompression = bUseLosslessCompression;
+}
+void PDFExtOutDevData::SetCompressionQuality( const sal_Int32 nQuality )
+{
+ mnCompressionQuality = nQuality;
+}
+void PDFExtOutDevData::SetIsReduceImageResolution( const bool bReduceImageResolution )
+{
+ mbReduceImageResolution = bReduceImageResolution;
+}
+void PDFExtOutDevData::SetIsExportNotes( const bool bExportNotes )
+{
+ mbExportNotes = bExportNotes;
+}
+void PDFExtOutDevData::SetIsExportNotesInMargin( const bool bExportNotesInMargin )
+{
+ mbExportNotesInMargin = bExportNotesInMargin;
+}
+void PDFExtOutDevData::SetIsExportNotesPages( const bool bExportNotesPages )
+{
+ mbExportNotesPages = bExportNotesPages;
+}
+void PDFExtOutDevData::SetIsExportTaggedPDF( const bool bTaggedPDF )
+{
+ mbTaggedPDF = bTaggedPDF;
+}
+void PDFExtOutDevData::SetIsExportTransitionEffects( const bool bTransitionEffects )
+{
+ mbTransitionEffects = bTransitionEffects;
+}
+void PDFExtOutDevData::SetIsExportFormFields( const bool bExportFomtFields )
+{
+ mbExportFormFields = bExportFomtFields;
+}
+void PDFExtOutDevData::SetIsExportBookmarks( const bool bExportBookmarks )
+{
+ mbExportBookmarks = bExportBookmarks;
+}
+void PDFExtOutDevData::SetIsExportHiddenSlides( const bool bExportHiddenSlides )
+{
+ mbExportHiddenSlides = bExportHiddenSlides;
+}
+void PDFExtOutDevData::SetIsSinglePageSheets( const bool bSinglePageSheets )
+{
+ mbSinglePageSheets = bSinglePageSheets;
+}
+void PDFExtOutDevData::SetIsExportNamedDestinations( const bool bExportNDests )
+{
+ mbExportNDests = bExportNDests;
+}
+void PDFExtOutDevData::ResetSyncData(PDFWriter *const pWriter)
+{
+ if (pWriter != nullptr)
+ {
+ // tdf#157182 HACK: all PDF actions on this page will be deleted; to have
+ // matching SE IDs on the *next* page, replay EnsureStructureElement actions
+ for (PDFExtOutDevDataSyncPage const& rAction : mpPageSyncData->mActions)
+ {
+ if (std::holds_alternative<struct EnsureStructureElement>(rAction.eAct))
+ {
+ pWriter->EnsureStructureElement();
+ }
+ }
+ }
+ *mpPageSyncData = PageSyncData( mpGlobalSyncData.get() );
+}
+bool PDFExtOutDevData::PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rIdx, const GDIMetaFile& rMtf )
+{
+ return mpPageSyncData->PlaySyncPageAct( rWriter, rIdx, rMtf, *this );
+}
+void PDFExtOutDevData::PlayGlobalActions( PDFWriter& rWriter )
+{
+ mpGlobalSyncData->PlayGlobalActions( rWriter );
+}
+
+/* global actions, synchronisation to the recorded metafile isn't needed,
+ all actions will be played after the last page was recorded
+*/
+//--->i56629
+sal_Int32 PDFExtOutDevData::CreateNamedDest(const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr )
+{
+ mpGlobalSyncData->mActions.push_back(
+ vcl::CreateNamedDest{ sDestName, mrOutDev.GetMapMode(), PDFWriter::DestAreaType::XYZ, rRect, nPageNr == -1 ? mnPage : nPageNr } );
+
+ return mpGlobalSyncData->mCurId++;
+}
+//<---i56629
+sal_Int32 PDFExtOutDevData::RegisterDest()
+{
+ const sal_Int32 nLinkDestID = mpGlobalSyncData->mCurId++;
+ mpGlobalSyncData->mActions.push_back( vcl::RegisterDest{ nLinkDestID } );
+
+ return nLinkDestID;
+}
+void PDFExtOutDevData::DescribeRegisteredDest( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ OSL_PRECOND( nDestId != -1, "PDFExtOutDevData::DescribeRegisteredDest: invalid destination Id!" );
+ PDFLinkDestination aLinkDestination;
+ aLinkDestination.mRect = rRect;
+ aLinkDestination.mMapMode = mrOutDev.GetMapMode();
+ aLinkDestination.mPageNr = nPageNr == -1 ? mnPage : nPageNr;
+ aLinkDestination.mAreaType = eType;
+ mpGlobalSyncData->mFutureDestinations[ nDestId ] = aLinkDestination;
+}
+sal_Int32 PDFExtOutDevData::CreateDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ mpGlobalSyncData->mActions.push_back(
+ vcl::CreateDest{ mrOutDev.GetMapMode(), eType, rRect, nPageNr == -1 ? mnPage : nPageNr } );
+ return mpGlobalSyncData->mCurId++;
+}
+sal_Int32 PDFExtOutDevData::CreateLink(const tools::Rectangle& rRect, OUString const& rAltText, sal_Int32 nPageNr)
+{
+ mpGlobalSyncData->mActions.push_back(
+ vcl::CreateLink{rAltText, mrOutDev.GetMapMode(), rRect, nPageNr == -1 ? mnPage : nPageNr } );
+ return mpGlobalSyncData->mCurId++;
+}
+
+sal_Int32 PDFExtOutDevData::CreateScreen(const tools::Rectangle& rRect,
+ OUString const& rAltText, OUString const& rMimeType,
+ sal_Int32 nPageNr, SdrObject const*const pObj)
+{
+ mpGlobalSyncData->mActions.push_back(vcl::CreateScreen{ rAltText, rMimeType, mrOutDev.GetMapMode(), rRect, nPageNr });
+ auto const ret(mpGlobalSyncData->mCurId++);
+ m_ScreenAnnotations[pObj].push_back(ret);
+ return ret;
+}
+
+::std::vector<sal_Int32> const& PDFExtOutDevData::GetScreenAnnotIds(SdrObject const*const pObj) const
+{
+ auto const it(m_ScreenAnnotations.find(pObj));
+ if (it == m_ScreenAnnotations.end())
+ {
+ assert(false); // expected?
+ }
+ return it->second;
+}
+
+void PDFExtOutDevData::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
+{
+ mpGlobalSyncData->mActions.push_back( vcl::SetLinkDest{ nLinkId, nDestId } );
+}
+void PDFExtOutDevData::SetLinkURL( sal_Int32 nLinkId, const OUString& rURL )
+{
+ mpGlobalSyncData->mActions.push_back( vcl::SetLinkURL{ rURL, nLinkId } );
+}
+
+void PDFExtOutDevData::SetScreenURL(sal_Int32 nScreenId, const OUString& rURL)
+{
+ mpGlobalSyncData->mActions.push_back(vcl::SetScreenURL{ rURL, nScreenId });
+}
+
+void PDFExtOutDevData::SetScreenStream(sal_Int32 nScreenId, const OUString& rURL)
+{
+ mpGlobalSyncData->mActions.push_back(vcl::SetScreenStream{ rURL, nScreenId });
+}
+
+sal_Int32 PDFExtOutDevData::CreateOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID )
+{
+ if (nParent == -1)
+ // Has no parent, it's a chapter / heading 1.
+ maChapterNames.push_back(rText);
+
+ mpGlobalSyncData->mActions.push_back( vcl::CreateOutlineItem{ rText, nParent, nDestID } );
+ return mpGlobalSyncData->mCurId++;
+}
+void PDFExtOutDevData::CreateNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
+{
+ mpGlobalSyncData->mActions.push_back(
+ vcl::CreateNote{ mrOutDev.GetMapMode(), rNote, rRect, nPageNr == -1 ? mnPage : nPageNr } );
+}
+void PDFExtOutDevData::SetPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec )
+{
+ mpGlobalSyncData->mActions.push_back( vcl::SetPageTransition{ eType, nMilliSec, mnPage } );
+}
+
+/* local (page), actions have to be played synchronously to the actions of
+ of the recorded metafile (created by each xRenderable->render()) */
+
+sal_Int32 PDFExtOutDevData::EnsureStructureElement(void const*const key)
+{
+ sal_Int32 id(-1);
+ if (key != nullptr)
+ {
+ auto const it(mpGlobalSyncData->mSEMap.find(key));
+ if (it != mpGlobalSyncData->mSEMap.end())
+ {
+ id = it->second;
+ }
+ }
+ if (id == -1)
+ {
+ id = mpGlobalSyncData->mStructParents.size();
+ mpPageSyncData->PushAction(mrOutDev, vcl::EnsureStructureElement{ id });
+ mpGlobalSyncData->mStructParents.push_back(mpGlobalSyncData->mCurrentStructElement);
+ if (key != nullptr)
+ {
+ mpGlobalSyncData->mSEMap.emplace(key, id);
+ }
+ }
+ return id;
+}
+
+void PDFExtOutDevData::InitStructureElement(sal_Int32 const id,
+ PDFWriter::StructElement const eType, const OUString& rAlias)
+{
+ mpPageSyncData->PushAction(mrOutDev, vcl::InitStructureElement{ eType, rAlias, id });
+ // update parent: required for hell fly anchor frames in sw, so that on the actual
+ // anchor frame EndStructureElement() resets mCurrentStructElement properly.
+ mpGlobalSyncData->mStructParents[id] = mpGlobalSyncData->mCurrentStructElement;
+}
+
+void PDFExtOutDevData::BeginStructureElement(sal_Int32 const id)
+{
+ mpPageSyncData->PushAction( mrOutDev, vcl::BeginStructureElement{ id } );
+ mpGlobalSyncData->mCurrentStructElement = id;
+}
+
+sal_Int32 PDFExtOutDevData::WrapBeginStructureElement(
+ PDFWriter::StructElement const eType, const OUString& rAlias)
+{
+ sal_Int32 const id = EnsureStructureElement(nullptr);
+ InitStructureElement(id, eType, rAlias);
+ BeginStructureElement(id);
+ return id;
+}
+
+void PDFExtOutDevData::EndStructureElement()
+{
+ assert(mpGlobalSyncData->mCurrentStructElement != 0); // underflow?
+ mpPageSyncData->PushAction( mrOutDev, vcl::EndStructureElement{} );
+ mpGlobalSyncData->mCurrentStructElement = mpGlobalSyncData->mStructParents[ mpGlobalSyncData->mCurrentStructElement ];
+}
+
+void PDFExtOutDevData::SetCurrentStructureElement(sal_Int32 const nStructId)
+{
+ assert(o3tl::make_unsigned(nStructId) < mpGlobalSyncData->mStructParents.size());
+ mpGlobalSyncData->mCurrentStructElement = nStructId;
+ mpPageSyncData->PushAction( mrOutDev, vcl::SetCurrentStructureElement{ nStructId } );
+}
+
+sal_Int32 PDFExtOutDevData::GetCurrentStructureElement() const
+{
+ return mpGlobalSyncData->mCurrentStructElement;
+}
+
+void PDFExtOutDevData::SetStructureAttribute( PDFWriter::StructAttribute eAttr, PDFWriter::StructAttributeValue eVal )
+{
+ mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureAttribute{ eAttr, eVal } );
+}
+void PDFExtOutDevData::SetStructureAttributeNumerical( PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
+{
+ mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureAttributeNumerical { eAttr, nValue } );
+}
+void PDFExtOutDevData::SetStructureBoundingBox( const tools::Rectangle& rRect )
+{
+ mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureBoundingBox{ rRect } );
+}
+
+void PDFExtOutDevData::SetStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds)
+{
+ mpPageSyncData->PushAction(mrOutDev, vcl::SetStructureAnnotIds{ rAnnotIds });
+}
+
+void PDFExtOutDevData::SetActualText( const OUString& rText )
+{
+ mpPageSyncData->PushAction( mrOutDev, vcl::SetActualText{ rText } );
+}
+void PDFExtOutDevData::SetAlternateText( const OUString& rText )
+{
+ mpPageSyncData->PushAction( mrOutDev, vcl::SetAlternateText{ rText } );
+}
+
+void PDFExtOutDevData::CreateControl( const PDFWriter::AnyWidget& rControlType )
+{
+ std::shared_ptr< PDFWriter::AnyWidget > pClone( rControlType.Clone() );
+ mpPageSyncData->PushAction( mrOutDev, vcl::CreateControl{ pClone } );
+}
+
+void PDFExtOutDevData::BeginGroup()
+{
+ mpPageSyncData->PushAction( mrOutDev, vcl::BeginGroup{} );
+}
+
+void PDFExtOutDevData::EndGroup( const Graphic& rGraphic,
+ sal_uInt8 nTransparency,
+ const tools::Rectangle& rOutputRect,
+ const tools::Rectangle& rVisibleOutputRect )
+{
+ mpPageSyncData->PushAction( mrOutDev,
+ vcl::EndGroupGfxLink{rGraphic, rOutputRect, rVisibleOutputRect, nTransparency} );
+}
+
+// Avoids expensive de-compression and re-compression of large images.
+bool PDFExtOutDevData::HasAdequateCompression( const Graphic &rGraphic,
+ const tools::Rectangle & rOutputRect,
+ const tools::Rectangle & rVisibleOutputRect ) const
+{
+ assert(rGraphic.IsGfxLink() &&
+ (rGraphic.GetGfxLink().GetType() == GfxLinkType::NativeJpg ||
+ rGraphic.GetGfxLink().GetType() == GfxLinkType::NativePng ||
+ rGraphic.GetGfxLink().GetType() == GfxLinkType::NativePdf));
+
+ if (rOutputRect != rVisibleOutputRect)
+ // rOutputRect is the crop rectangle, re-compress cropped image.
+ return false;
+
+ if (mbReduceImageResolution)
+ // Reducing resolution was requested, implies that re-compressing is
+ // wanted.
+ return false;
+
+ auto nSize = rGraphic.GetGfxLink().GetDataSize();
+ if (nSize == 0)
+ return false;
+
+ GfxLink aLink = rGraphic.GetGfxLink();
+ SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(aLink.GetData()), aLink.GetDataSize(),
+ StreamMode::READ | StreamMode::WRITE);
+ GraphicDescriptor aDescriptor(aMemoryStream, nullptr);
+ if (aDescriptor.Detect(true) && aDescriptor.GetNumberOfImageComponents() == 4)
+ // 4 means CMYK, which is not handled.
+ return false;
+
+ const Size aSize = rGraphic.GetSizePixel();
+
+ // small items better off as PNG anyway
+ if ( aSize.Width() < 32 &&
+ aSize.Height() < 32 )
+ return false;
+
+ if (GetIsLosslessCompression())
+ return !GetIsReduceImageResolution();
+
+ // FIXME: ideally we'd also pre-empt the DPI related scaling too.
+ sal_Int32 nCurrentRatio = (100 * aSize.Width() * aSize.Height() * 4) /
+ nSize;
+
+ static const struct {
+ sal_Int32 mnQuality;
+ sal_Int32 mnRatio;
+ } aRatios[] = { // minimum tolerable compression ratios
+ { 100, 400 }, { 95, 700 }, { 90, 1000 }, { 85, 1200 },
+ { 80, 1500 }, { 75, 1700 }
+ };
+ sal_Int32 nTargetRatio = 10000;
+ bool bIsTargetRatioReached = false;
+ for (auto & rRatio : aRatios)
+ {
+ if ( mnCompressionQuality > rRatio.mnQuality )
+ {
+ bIsTargetRatioReached = true;
+ break;
+ }
+ nTargetRatio = rRatio.mnRatio;
+ }
+
+ return ((nCurrentRatio > nTargetRatio) && bIsTargetRatioReached);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfobjectcopier.cxx b/vcl/source/gdi/pdfobjectcopier.cxx
new file mode 100644
index 0000000000..0d07c0df7f
--- /dev/null
+++ b/vcl/source/gdi/pdfobjectcopier.cxx
@@ -0,0 +1,346 @@
+/* -*- 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/.
+ */
+
+#include <sal/log.hxx>
+#include <sal/types.h>
+#include <rtl/strbuf.hxx>
+#include <tools/stream.hxx>
+#include <tools/zcodec.hxx>
+
+#include <vcl/filter/pdfdocument.hxx>
+#include <vcl/filter/pdfobjectcontainer.hxx>
+
+#include <pdf/objectcopier.hxx>
+#include <pdf/pdfwriter_impl.hxx>
+
+namespace vcl
+{
+PDFObjectCopier::PDFObjectCopier(PDFObjectContainer& rContainer)
+ : m_rContainer(rContainer)
+{
+}
+
+void PDFObjectCopier::copyRecursively(OStringBuffer& rLine, filter::PDFElement& rInputElement,
+ SvMemoryStream& rDocBuffer,
+ std::map<sal_Int32, sal_Int32>& rCopiedResources)
+{
+ if (auto pReference = dynamic_cast<filter::PDFReferenceElement*>(&rInputElement))
+ {
+ filter::PDFObjectElement* pReferenced = pReference->LookupObject();
+ if (pReferenced)
+ {
+ // Copy the referenced object.
+ sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
+
+ // Write the updated reference.
+ rLine.append(nRef);
+ rLine.append(" 0 R");
+ }
+ }
+ else if (auto pInputArray = dynamic_cast<filter::PDFArrayElement*>(&rInputElement))
+ {
+ rLine.append("[ ");
+ for (auto const& pElement : pInputArray->GetElements())
+ {
+ copyRecursively(rLine, *pElement, rDocBuffer, rCopiedResources);
+ rLine.append(" ");
+ }
+ rLine.append("] ");
+ }
+ else if (auto pInputDictionary = dynamic_cast<filter::PDFDictionaryElement*>(&rInputElement))
+ {
+ rLine.append("<< ");
+ for (auto const& pPair : pInputDictionary->GetItems())
+ {
+ rLine.append("/");
+ rLine.append(pPair.first);
+ rLine.append(" ");
+ copyRecursively(rLine, *pPair.second, rDocBuffer, rCopiedResources);
+ rLine.append(" ");
+ }
+ rLine.append(">> ");
+ }
+ else
+ {
+ rInputElement.writeString(rLine);
+ }
+}
+
+sal_Int32 PDFObjectCopier::copyExternalResource(SvMemoryStream& rDocBuffer,
+ filter::PDFObjectElement& rObject,
+ std::map<sal_Int32, sal_Int32>& rCopiedResources)
+{
+ auto it = rCopiedResources.find(rObject.GetObjectValue());
+ if (it != rCopiedResources.end())
+ {
+ // This resource was already copied once, nothing to do.
+ return it->second;
+ }
+
+ sal_Int32 nObject = m_rContainer.createObject();
+ // Remember what is the ID of this object in our output.
+ rCopiedResources[rObject.GetObjectValue()] = nObject;
+ SAL_INFO("vcl.pdfwriter", "PDFObjectCopier::copyExternalResource: " << rObject.GetObjectValue()
+ << " -> " << nObject);
+
+ OStringBuffer aLine = OString::number(nObject) + " 0 obj\n";
+
+ if (rObject.GetDictionary())
+ {
+ aLine.append("<< ");
+ bool bFirst = true;
+ for (auto const& rPair : rObject.GetDictionaryItems())
+ {
+ if (bFirst)
+ bFirst = false;
+ else
+ aLine.append(" ");
+
+ aLine.append("/" + rPair.first + " ");
+ copyRecursively(aLine, *rPair.second, rDocBuffer, rCopiedResources);
+ }
+
+ aLine.append(" >>\n");
+ }
+
+ filter::PDFStreamElement* pStream = rObject.GetStream();
+ if (pStream)
+ {
+ aLine.append("stream\n");
+ }
+
+ if (filter::PDFArrayElement* pArray = rObject.GetArray())
+ {
+ aLine.append("[ ");
+
+ const std::vector<filter::PDFElement*>& rElements = pArray->GetElements();
+
+ bool bFirst = true;
+ for (auto const& pElement : rElements)
+ {
+ if (bFirst)
+ bFirst = false;
+ else
+ aLine.append(" ");
+ copyRecursively(aLine, *pElement, rDocBuffer, rCopiedResources);
+ }
+ aLine.append("]\n");
+ }
+
+ // If the object has a number element outside a dictionary or array, copy that.
+ if (filter::PDFNumberElement* pNumber = rObject.GetNumberElement())
+ {
+ pNumber->writeString(aLine);
+ aLine.append("\n");
+ }
+
+ // We have the whole object, now write it to the output.
+ if (!m_rContainer.updateObject(nObject))
+ return -1;
+ if (!m_rContainer.writeBuffer(aLine))
+ return -1;
+ aLine.setLength(0);
+
+ if (pStream)
+ {
+ SvMemoryStream& rStream = pStream->GetMemory();
+ m_rContainer.checkAndEnableStreamEncryption(nObject);
+ aLine.append(static_cast<const char*>(rStream.GetData()), rStream.GetSize());
+ if (!m_rContainer.writeBuffer(aLine))
+ return -1;
+ aLine.setLength(0);
+ m_rContainer.disableStreamEncryption();
+
+ aLine.append("\nendstream\n");
+ if (!m_rContainer.writeBuffer(aLine))
+ return -1;
+ aLine.setLength(0);
+ }
+
+ aLine.append("endobj\n\n");
+ if (!m_rContainer.writeBuffer(aLine))
+ return -1;
+
+ return nObject;
+}
+
+OString PDFObjectCopier::copyExternalResources(filter::PDFObjectElement& rPage,
+ const OString& rKind,
+ std::map<sal_Int32, sal_Int32>& rCopiedResources)
+{
+ // A name - object ID map, IDs as they appear in our output, not the
+ // original ones.
+ std::map<OString, sal_Int32> aRet;
+
+ // Get the rKind subset of the resource dictionary.
+ std::map<OString, filter::PDFElement*> aItems;
+ filter::PDFObjectElement* pKindObject = nullptr;
+ if (auto pResources
+ = dynamic_cast<filter::PDFDictionaryElement*>(rPage.Lookup("Resources"_ostr)))
+ {
+ // Resources is a direct dictionary.
+ filter::PDFElement* pLookup = pResources->LookupElement(rKind);
+ if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pLookup))
+ {
+ // rKind is an inline dictionary.
+ aItems = pDictionary->GetItems();
+ }
+ else if (auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pLookup))
+ {
+ // rKind refers to a dictionary.
+ filter::PDFObjectElement* pReferenced = pReference->LookupObject();
+ if (!pReferenced)
+ {
+ return {};
+ }
+
+ pKindObject = pReferenced;
+ aItems = pReferenced->GetDictionaryItems();
+ }
+ }
+ else if (filter::PDFObjectElement* pPageResources = rPage.LookupObject("Resources"_ostr))
+ {
+ // Resources is an indirect object.
+ filter::PDFElement* pValue = pPageResources->Lookup(rKind);
+ if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pValue))
+ {
+ // Kind is a direct dictionary.
+ aItems = pDictionary->GetItems();
+ }
+ else if (filter::PDFObjectElement* pObject = pPageResources->LookupObject(rKind))
+ {
+ // Kind is an indirect object.
+ aItems = pObject->GetDictionaryItems();
+ pKindObject = pObject;
+ }
+ }
+ if (aItems.empty())
+ return {};
+
+ SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer();
+ bool bHasDictValue = false;
+
+ for (const auto& rItem : aItems)
+ {
+ // For each item copy it over to our output then insert it into aRet.
+ auto pReference = dynamic_cast<filter::PDFReferenceElement*>(rItem.second);
+ if (!pReference)
+ {
+ if (pKindObject && dynamic_cast<filter::PDFDictionaryElement*>(rItem.second))
+ {
+ bHasDictValue = true;
+ break;
+ }
+
+ continue;
+ }
+
+ filter::PDFObjectElement* pValue = pReference->LookupObject();
+ if (!pValue)
+ continue;
+
+ // Then copying over an object copy its dictionary and its stream.
+ sal_Int32 nObject = copyExternalResource(rDocBuffer, *pValue, rCopiedResources);
+ aRet[rItem.first] = nObject;
+ }
+
+ if (bHasDictValue && pKindObject)
+ {
+ sal_Int32 nObject = copyExternalResource(rDocBuffer, *pKindObject, rCopiedResources);
+ return "/" + rKind + " " + OString::number(nObject) + " 0 R";
+ }
+
+ // Build the dictionary entry string.
+ OStringBuffer sRet("/" + rKind + "<<");
+ for (const auto& rPair : aRet)
+ {
+ sRet.append("/" + rPair.first + " " + OString::number(rPair.second) + " 0 R");
+ }
+ sRet.append(">>");
+
+ return sRet.makeStringAndClear();
+}
+
+void PDFObjectCopier::copyPageResources(filter::PDFObjectElement* pPage, OStringBuffer& rLine)
+{
+ // Maps from source object id (PDF image) to target object id (export result).
+ std::map<sal_Int32, sal_Int32> aCopiedResources;
+ copyPageResources(pPage, rLine, aCopiedResources);
+}
+
+void PDFObjectCopier::copyPageResources(filter::PDFObjectElement* pPage, OStringBuffer& rLine,
+ std::map<sal_Int32, sal_Int32>& rCopiedResources)
+{
+ rLine.append(" /Resources <<");
+ static const std::initializer_list<OString> aKeys
+ = { "ColorSpace"_ostr, "ExtGState"_ostr, "Font"_ostr,
+ "XObject"_ostr, "Shading"_ostr, "Pattern"_ostr };
+ for (const auto& rKey : aKeys)
+ {
+ rLine.append(copyExternalResources(*pPage, rKey, rCopiedResources));
+ }
+ rLine.append(">>");
+}
+
+sal_Int32 PDFObjectCopier::copyPageStreams(std::vector<filter::PDFObjectElement*>& rContentStreams,
+ SvMemoryStream& rStream, bool& rCompressed)
+{
+ for (auto pContent : rContentStreams)
+ {
+ filter::PDFStreamElement* pPageStream = pContent->GetStream();
+ if (!pPageStream)
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFObjectCopier::copyPageStreams: contents has no stream");
+ continue;
+ }
+
+ SvMemoryStream& rPageStream = pPageStream->GetMemory();
+
+ auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"_ostr));
+ auto pFilterArray = dynamic_cast<filter::PDFArrayElement*>(pContent->Lookup("Filter"_ostr));
+ if (!pFilter && pFilterArray)
+ {
+ auto& aElements = pFilterArray->GetElements();
+ if (!aElements.empty())
+ pFilter = dynamic_cast<filter::PDFNameElement*>(aElements[0]);
+ }
+
+ if (pFilter)
+ {
+ if (pFilter->GetValue() != "FlateDecode")
+ {
+ continue;
+ }
+
+ SvMemoryStream aMemoryStream;
+ ZCodec aZCodec;
+ rPageStream.Seek(0);
+ aZCodec.BeginCompression();
+ aZCodec.Decompress(rPageStream, aMemoryStream);
+ if (!aZCodec.EndCompression())
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFObjectCopier::copyPageStreams: decompression failed");
+ continue;
+ }
+
+ rStream.WriteBytes(aMemoryStream.GetData(), aMemoryStream.GetSize());
+ }
+ else
+ {
+ rStream.WriteBytes(rPageStream.GetData(), rPageStream.GetSize());
+ }
+ }
+
+ rCompressed = PDFWriterImpl::compressStream(&rStream);
+
+ return rStream.Tell();
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfwriter.cxx b/vcl/source/gdi/pdfwriter.cxx
new file mode 100644
index 0000000000..60437f55fe
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter.cxx
@@ -0,0 +1,485 @@
+/* -*- 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 <vcl/bitmapex.hxx>
+
+#include <pdf/pdfwriter_impl.hxx>
+
+using namespace vcl;
+
+PDFWriter::AnyWidget::~AnyWidget()
+{
+}
+
+PDFWriter::PDFWriter( const PDFWriter::PDFWriterContext& rContext, const css::uno::Reference< css::beans::XMaterialHolder >& xEnc )
+ :
+ xImplementation( VclPtr<PDFWriterImpl>::Create(rContext, xEnc, *this) )
+{
+}
+
+PDFWriter::~PDFWriter()
+{
+ xImplementation.disposeAndClear();
+}
+
+OutputDevice* PDFWriter::GetReferenceDevice()
+{
+ return xImplementation.get();
+}
+
+void PDFWriter::NewPage( double nPageWidth, double nPageHeight, Orientation eOrientation )
+{
+ xImplementation->newPage( nPageWidth, nPageHeight, eOrientation );
+}
+
+bool PDFWriter::Emit()
+{
+ return xImplementation->emit();
+}
+
+void PDFWriter::SetDocumentLocale( const css::lang::Locale& rLoc )
+{
+ xImplementation->setDocumentLocale( rLoc );
+}
+
+void PDFWriter::SetFont( const vcl::Font& rFont )
+{
+ xImplementation->setFont( rFont );
+}
+
+void PDFWriter::DrawText( const Point& rPos, const OUString& rText )
+{
+ xImplementation->drawText( rPos, rText, 0, rText.getLength() );
+}
+
+void PDFWriter::DrawTextLine(
+ const Point& rPos,
+ tools::Long nWidth,
+ FontStrikeout eStrikeout,
+ FontLineStyle eUnderline,
+ FontLineStyle eOverline )
+{
+ xImplementation->drawTextLine( rPos, nWidth, eStrikeout, eUnderline, eOverline, false/*bUnderlineAbove*/ );
+}
+
+void PDFWriter::DrawTextArray(
+ const Point& rStartPt,
+ const OUString& rStr,
+ KernArraySpan pDXAry,
+ std::span<const sal_Bool> pKashidaAry,
+ sal_Int32 nIndex,
+ sal_Int32 nLen )
+{
+ xImplementation->drawTextArray( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen );
+}
+
+void PDFWriter::DrawStretchText(
+ const Point& rStartPt,
+ sal_Int32 nWidth,
+ const OUString& rStr,
+ sal_Int32 nIndex,
+ sal_Int32 nLen )
+{
+ xImplementation->drawStretchText( rStartPt, nWidth, rStr, nIndex, nLen );
+}
+
+void PDFWriter::DrawText(
+ const tools::Rectangle& rRect,
+ const OUString& rStr,
+ DrawTextFlags nStyle )
+{
+ xImplementation->drawText( rRect, rStr, nStyle );
+}
+
+void PDFWriter::DrawLine( const Point& rStart, const Point& rStop )
+{
+ xImplementation->drawLine( rStart, rStop );
+}
+
+void PDFWriter::DrawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
+{
+ xImplementation->drawLine( rStart, rStop, rInfo );
+}
+
+void PDFWriter::DrawPolygon( const tools::Polygon& rPoly )
+{
+ xImplementation->drawPolygon( rPoly );
+}
+
+void PDFWriter::DrawPolyLine( const tools::Polygon& rPoly )
+{
+ xImplementation->drawPolyLine( rPoly );
+}
+
+void PDFWriter::DrawRect( const tools::Rectangle& rRect )
+{
+ xImplementation->drawRectangle( rRect );
+}
+
+void PDFWriter::DrawRect( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
+{
+ xImplementation->drawRectangle( rRect, nHorzRound, nVertRound );
+}
+
+void PDFWriter::DrawEllipse( const tools::Rectangle& rRect )
+{
+ xImplementation->drawEllipse( rRect );
+}
+
+void PDFWriter::DrawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop )
+{
+ xImplementation->drawArc( rRect, rStart, rStop, false, false );
+}
+
+void PDFWriter::DrawPie( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop )
+{
+ xImplementation->drawArc( rRect, rStart, rStop, true, false );
+}
+
+void PDFWriter::DrawChord( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop )
+{
+ xImplementation->drawArc( rRect, rStart, rStop, false, true );
+}
+
+void PDFWriter::DrawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
+{
+ xImplementation->drawPolyLine( rPoly, rInfo );
+}
+
+void PDFWriter::DrawPolyLine( const tools::Polygon& rPoly, const ExtLineInfo& rInfo )
+{
+ xImplementation->drawPolyLine( rPoly, rInfo );
+}
+
+void PDFWriter::DrawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
+{
+ xImplementation->drawPolyPolygon( rPolyPoly );
+}
+
+void PDFWriter::DrawPixel( const Point& rPos, const Color& rColor )
+{
+ xImplementation->drawPixel( rPos, rColor );
+}
+
+void PDFWriter::DrawBitmap( const Point& rDestPt, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
+{
+ xImplementation->drawBitmap( rDestPt, rDestSize, rBitmap, rGraphic );
+}
+
+void PDFWriter::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize, const BitmapEx& rBitmap )
+{
+ xImplementation->drawBitmap( rDestPt, rDestSize, rBitmap );
+}
+
+void PDFWriter::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
+{
+ xImplementation->drawHatch( rPolyPoly, rHatch );
+}
+
+void PDFWriter::DrawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
+{
+ xImplementation->drawGradient( rRect, rGradient );
+}
+
+void PDFWriter::DrawGradient( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient )
+{
+ xImplementation->push(PushFlags::CLIPREGION);
+ xImplementation->setClipRegion( rPolyPoly.getB2DPolyPolygon() );
+ xImplementation->drawGradient( rPolyPoly.GetBoundRect(), rGradient );
+ xImplementation->pop();
+}
+
+void PDFWriter::DrawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWallpaper )
+{
+ xImplementation->drawWallpaper( rRect, rWallpaper );
+}
+
+void PDFWriter::DrawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt16 nTransparencePercent )
+{
+ xImplementation->drawTransparent( rPolyPoly, nTransparencePercent );
+}
+
+void PDFWriter::BeginTransparencyGroup()
+{
+ xImplementation->beginTransparencyGroup();
+}
+
+void PDFWriter::EndTransparencyGroup( const tools::Rectangle& rRect, sal_uInt16 nTransparentPercent )
+{
+ xImplementation->endTransparencyGroup( rRect, nTransparentPercent );
+}
+
+void PDFWriter::Push( PushFlags nFlags )
+{
+ xImplementation->push( nFlags );
+}
+
+void PDFWriter::Pop()
+{
+ xImplementation->pop();
+}
+
+void PDFWriter::SetMapMode( const MapMode& rMapMode )
+{
+ xImplementation->setMapMode( rMapMode );
+}
+
+void PDFWriter::SetLineColor( const Color& rColor )
+{
+ xImplementation->setLineColor( rColor );
+}
+
+void PDFWriter::SetFillColor( const Color& rColor )
+{
+ xImplementation->setFillColor( rColor );
+}
+
+void PDFWriter::SetClipRegion()
+{
+ xImplementation->clearClipRegion();
+}
+
+void PDFWriter::SetClipRegion( const basegfx::B2DPolyPolygon& rRegion )
+{
+ xImplementation->setClipRegion( rRegion );
+}
+
+void PDFWriter::MoveClipRegion( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ xImplementation->moveClipRegion( nHorzMove, nVertMove );
+}
+
+void PDFWriter::IntersectClipRegion( const basegfx::B2DPolyPolygon& rRegion )
+{
+ xImplementation->intersectClipRegion( rRegion );
+}
+
+void PDFWriter::IntersectClipRegion( const tools::Rectangle& rRect )
+{
+ xImplementation->intersectClipRegion( rRect );
+}
+
+void PDFWriter::SetLayoutMode( vcl::text::ComplexTextLayoutFlags nMode )
+{
+ xImplementation->setLayoutMode( nMode );
+}
+
+void PDFWriter::SetDigitLanguage( LanguageType eLang )
+{
+ xImplementation->setDigitLanguage( eLang );
+}
+
+void PDFWriter::SetTextColor( const Color& rColor )
+{
+ xImplementation->setTextColor( rColor );
+}
+
+void PDFWriter::SetTextFillColor()
+{
+ xImplementation->setTextFillColor();
+}
+
+void PDFWriter::SetTextFillColor( const Color& rColor )
+{
+ xImplementation->setTextFillColor( rColor );
+}
+
+void PDFWriter::SetTextLineColor()
+{
+ xImplementation->setTextLineColor();
+}
+
+void PDFWriter::SetTextLineColor( const Color& rColor )
+{
+ xImplementation->setTextLineColor( rColor );
+}
+
+void PDFWriter::SetOverlineColor()
+{
+ xImplementation->setOverlineColor();
+}
+
+void PDFWriter::SetOverlineColor( const Color& rColor )
+{
+ xImplementation->setOverlineColor( rColor );
+}
+
+void PDFWriter::SetTextAlign( ::TextAlign eAlign )
+{
+ xImplementation->setTextAlign( eAlign );
+}
+
+void PDFWriter::DrawJPGBitmap( SvStream& rStreamData, bool bIsTrueColor, const Size& rSrcSizePixel, const tools::Rectangle& rTargetArea, const AlphaMask& rAlphaMask, const Graphic& rGraphic )
+{
+ xImplementation->drawJPGBitmap( rStreamData, bIsTrueColor, rSrcSizePixel, rTargetArea, rAlphaMask, rGraphic );
+}
+
+sal_Int32 PDFWriter::CreateLink(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText)
+{
+ return xImplementation->createLink(rRect, nPageNr, rAltText);
+}
+
+sal_Int32 PDFWriter::CreateScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText, OUString const& rMimeType)
+{
+ return xImplementation->createScreen(rRect, nPageNr, rAltText, rMimeType);
+}
+
+sal_Int32 PDFWriter::RegisterDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, DestAreaType eType )
+{
+ return xImplementation->registerDestReference( nDestId, rRect, nPageNr, eType );
+}
+//--->i56629
+sal_Int32 PDFWriter::CreateNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ return xImplementation->createNamedDest( sDestName, rRect, nPageNr, eType );
+}
+sal_Int32 PDFWriter::CreateDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ return xImplementation->createDest( rRect, nPageNr, eType );
+}
+
+void PDFWriter::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
+{
+ xImplementation->setLinkDest( nLinkId, nDestId );
+}
+
+void PDFWriter::SetLinkURL( sal_Int32 nLinkId, const OUString& rURL )
+{
+ xImplementation->setLinkURL( nLinkId, rURL );
+}
+
+void PDFWriter::SetScreenURL(sal_Int32 nScreenId, const OUString& rURL)
+{
+ xImplementation->setScreenURL(nScreenId, rURL);
+}
+
+void PDFWriter::SetScreenStream(sal_Int32 nScreenId, const OUString& rURL)
+{
+ xImplementation->setScreenStream(nScreenId, rURL);
+}
+
+void PDFWriter::SetLinkPropertyID( sal_Int32 nLinkId, sal_Int32 nPropertyId )
+{
+ xImplementation->setLinkPropertyId( nLinkId, nPropertyId );
+}
+
+sal_Int32 PDFWriter::CreateOutlineItem( sal_Int32 nParent, std::u16string_view rText, sal_Int32 nDestID )
+{
+ return xImplementation->createOutlineItem( nParent, rText, nDestID );
+}
+
+void PDFWriter::CreateNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
+{
+ xImplementation->createNote( rRect, rNote, nPageNr );
+}
+
+sal_Int32 PDFWriter::EnsureStructureElement()
+{
+ return xImplementation->ensureStructureElement();
+}
+
+void PDFWriter::InitStructureElement(sal_Int32 const id,
+ PDFWriter::StructElement const eType, std::u16string_view const rAlias)
+{
+ return xImplementation->initStructureElement(id, eType, rAlias);
+}
+
+void PDFWriter::BeginStructureElement(sal_Int32 const id)
+{
+ return xImplementation->beginStructureElement(id);
+}
+
+void PDFWriter::EndStructureElement()
+{
+ xImplementation->endStructureElement();
+}
+
+void PDFWriter::SetCurrentStructureElement( sal_Int32 nID )
+{
+ xImplementation->setCurrentStructureElement( nID );
+}
+
+void PDFWriter::SetStructureAttribute( enum StructAttribute eAttr, enum StructAttributeValue eVal )
+{
+ xImplementation->setStructureAttribute( eAttr, eVal );
+}
+
+void PDFWriter::SetStructureAttributeNumerical( enum StructAttribute eAttr, sal_Int32 nValue )
+{
+ xImplementation->setStructureAttributeNumerical( eAttr, nValue );
+}
+
+void PDFWriter::SetStructureBoundingBox( const tools::Rectangle& rRect )
+{
+ xImplementation->setStructureBoundingBox( rRect );
+}
+
+void PDFWriter::SetStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds)
+{
+ xImplementation->setStructureAnnotIds(rAnnotIds);
+}
+
+void PDFWriter::SetActualText( const OUString& rText )
+{
+ xImplementation->setActualText( rText );
+}
+
+void PDFWriter::SetAlternateText( const OUString& rText )
+{
+ xImplementation->setAlternateText( rText );
+}
+
+void PDFWriter::SetPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
+{
+ xImplementation->setPageTransition( eType, nMilliSec, nPageNr );
+}
+
+sal_Int32 PDFWriter::CreateControl( const PDFWriter::AnyWidget& rControl )
+{
+ return xImplementation->createControl( rControl );
+}
+
+PDFOutputStream::~PDFOutputStream()
+{
+}
+
+void PDFWriter::AddAttachedFile(OUString const& rFileName, OUString const& rMimeType, OUString const& rDescription, std::unique_ptr<PDFOutputStream> pStream)
+{
+ xImplementation->addDocumentAttachedFile(rFileName, rMimeType, rDescription, std::move(pStream));
+}
+
+std::set< PDFWriter::ErrorCode > const & PDFWriter::GetErrors() const
+{
+ return xImplementation->getErrors();
+}
+
+css::uno::Reference< css::beans::XMaterialHolder >
+PDFWriter::InitEncryption( const OUString& i_rOwnerPassword,
+ const OUString& i_rUserPassword
+ )
+{
+ return PDFWriterImpl::initEncryption( i_rOwnerPassword, i_rUserPassword );
+}
+
+void PDFWriter::PlayMetafile( const GDIMetaFile& i_rMTF, const vcl::PDFWriter::PlayMetafileContext& i_rPlayContext, PDFExtOutDevData* i_pData )
+{
+ xImplementation->playMetafile( i_rMTF, i_pData, i_rPlayContext );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
new file mode 100644
index 0000000000..c7ef07ff49
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -0,0 +1,11934 @@
+/* -*- 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 <sal/config.h>
+#include <config_crypto.h>
+
+#include <sal/types.h>
+
+#include <math.h>
+#include <algorithm>
+#include <string_view>
+
+#include <lcms2.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <memory>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/util/URL.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <comphelper/xmlencode.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <o3tl/numeric.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/temporary.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <osl/file.hxx>
+#include <osl/thread.h>
+#include <rtl/crc.h>
+#include <rtl/digest.h>
+#include <rtl/uri.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <svl/cryptosign.hxx>
+#include <sal/log.hxx>
+#include <svl/urihelper.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <tools/helpers.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/UnitConversion.hxx>
+#include <tools/zcodec.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <vcl/glyphitemcache.hxx>
+#include <vcl/kernarray.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/pdfread.hxx>
+#include <vcl/settings.hxx>
+#include <strhelper.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/filter/pdfdocument.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <comphelper/hash.hxx>
+
+#include <svdata.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <fontsubset.hxx>
+#include <font/EmphasisMark.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <salgdi.hxx>
+#include <textlayout.hxx>
+#include <textlineinfo.hxx>
+#include <impglyphitem.hxx>
+#include <pdf/XmpMetadata.hxx>
+#include <pdf/objectcopier.hxx>
+#include <pdf/pdfwriter_impl.hxx>
+#include <pdf/PdfConfig.hxx>
+#include <o3tl/sorted_vector.hxx>
+#include <frozen/bits/defines.h>
+#include <frozen/bits/elsa_std.h>
+#include <frozen/map.h>
+
+using namespace::com::sun::star;
+
+static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
+
+namespace
+{
+
+constexpr sal_Int32 nLog10Divisor = 3;
+constexpr double fDivisor = 1000.0;
+
+constexpr double pixelToPoint(double px)
+{
+ return px / fDivisor;
+}
+
+constexpr sal_Int32 pointToPixel(double pt)
+{
+ return sal_Int32(pt * fDivisor);
+}
+
+void appendObjectID(sal_Int32 nObjectID, OStringBuffer & aLine)
+{
+ aLine.append(nObjectID);
+ aLine.append(" 0 obj\n");
+}
+
+void appendObjectReference(sal_Int32 nObjectID, OStringBuffer & aLine)
+{
+ aLine.append(nObjectID);
+ aLine.append(" 0 R ");
+}
+
+void appendHex(sal_Int8 nInt, OStringBuffer& rBuffer)
+{
+ static const char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+ rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] );
+ rBuffer.append( pHexDigits[ nInt & 15 ] );
+}
+
+void appendName( std::u16string_view rStr, OStringBuffer& rBuffer )
+{
+// FIXME i59651 add a check for max length of 127 chars? Per PDF spec 1.4, appendix C.1
+// I guess than when reading the #xx sequence it will count for a single character.
+ OString aStr( OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ) );
+ int nLen = aStr.getLength();
+ for( int i = 0; i < nLen; i++ )
+ {
+ /* #i16920# PDF recommendation: output UTF8, any byte
+ * outside the interval [33(=ASCII'!');126(=ASCII'~')]
+ * should be escaped hexadecimal
+ * for the sake of ghostscript which also reads PDF
+ * but has a narrower acceptance rate we only pass
+ * alphanumerics and '-' literally.
+ */
+ if( (aStr[i] >= 'A' && aStr[i] <= 'Z' ) ||
+ (aStr[i] >= 'a' && aStr[i] <= 'z' ) ||
+ (aStr[i] >= '0' && aStr[i] <= '9' ) ||
+ aStr[i] == '-' )
+ {
+ rBuffer.append( aStr[i] );
+ }
+ else
+ {
+ rBuffer.append( '#' );
+ appendHex( static_cast<sal_Int8>(aStr[i]), rBuffer );
+ }
+ }
+}
+
+void appendName( const char* pStr, OStringBuffer& rBuffer )
+{
+ // FIXME i59651 see above
+ while( pStr && *pStr )
+ {
+ if( (*pStr >= 'A' && *pStr <= 'Z' ) ||
+ (*pStr >= 'a' && *pStr <= 'z' ) ||
+ (*pStr >= '0' && *pStr <= '9' ) ||
+ *pStr == '-' )
+ {
+ rBuffer.append( *pStr );
+ }
+ else
+ {
+ rBuffer.append( '#' );
+ appendHex( static_cast<sal_Int8>(*pStr), rBuffer );
+ }
+ pStr++;
+ }
+}
+
+//used only to emit encoded passwords
+void appendLiteralString( const char* pStr, sal_Int32 nLength, OStringBuffer& rBuffer )
+{
+ while( nLength )
+ {
+ switch( *pStr )
+ {
+ case '\n' :
+ rBuffer.append( "\\n" );
+ break;
+ case '\r' :
+ rBuffer.append( "\\r" );
+ break;
+ case '\t' :
+ rBuffer.append( "\\t" );
+ break;
+ case '\b' :
+ rBuffer.append( "\\b" );
+ break;
+ case '\f' :
+ rBuffer.append( "\\f" );
+ break;
+ case '(' :
+ case ')' :
+ case '\\' :
+ rBuffer.append( "\\" );
+ rBuffer.append( static_cast<char>(*pStr) );
+ break;
+ default:
+ rBuffer.append( static_cast<char>(*pStr) );
+ break;
+ }
+ pStr++;
+ nLength--;
+ }
+}
+
+/*
+ * Convert a string before using it.
+ *
+ * This string conversion function is needed because the destination name
+ * in a PDF file seen through an Internet browser should be
+ * specially crafted, in order to be used directly by the browser.
+ * In this way the fragment part of a hyperlink to a PDF file (e.g. something
+ * as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the
+ * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called
+ * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf
+ * and go to named destination thefragment using default zoom'.
+ * The conversion is needed because in case of a fragment in the form: Slide%201
+ * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201
+ * using this conversion, in both the generated named destinations, fragment and GoToR
+ * destination.
+ *
+ * The names for destinations are name objects and so they don't need to be encrypted
+ * even though they expose the content of PDF file (e.g. guessing the PDF content from the
+ * destination name).
+ *
+ * Further limitation: it is advisable to use standard ASCII characters for
+ * OOo bookmarks.
+*/
+void appendDestinationName( const OUString& rString, OStringBuffer& rBuffer )
+{
+ const sal_Unicode* pStr = rString.getStr();
+ sal_Int32 nLen = rString.getLength();
+ for( int i = 0; i < nLen; i++ )
+ {
+ sal_Unicode aChar = pStr[i];
+ if( (aChar >= '0' && aChar <= '9' ) ||
+ (aChar >= 'a' && aChar <= 'z' ) ||
+ (aChar >= 'A' && aChar <= 'Z' ) ||
+ aChar == '-' )
+ {
+ rBuffer.append(static_cast<char>(aChar));
+ }
+ else
+ {
+ sal_Int8 aValueHigh = sal_Int8(aChar >> 8);
+ if(aValueHigh > 0)
+ appendHex( aValueHigh, rBuffer );
+ appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
+ }
+ }
+}
+
+} // end anonymous namespace
+
+namespace vcl
+{
+const sal_uInt8 PDFWriterImpl::s_nPadString[32] =
+{
+ 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
+ 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A
+};
+
+namespace
+{
+
+template < class GEOMETRY >
+GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject )
+{
+ GEOMETRY aPoint;
+ if ( MapUnit::MapPixel == _rSource.GetMapUnit() )
+ {
+ aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest );
+ }
+ else
+ {
+ aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest );
+ }
+ return aPoint;
+}
+
+void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle);
+
+} // end anonymous namespace
+
+void PDFWriter::AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer)
+{
+ rBuffer.append( "FEFF" );
+ const sal_Unicode* pStr = rString.getStr();
+ sal_Int32 nLen = rString.getLength();
+ for( int i = 0; i < nLen; i++ )
+ {
+ sal_Unicode aChar = pStr[i];
+ appendHex( static_cast<sal_Int8>(aChar >> 8), rBuffer );
+ appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
+ }
+}
+
+void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl )
+{
+ /* #i80258# previously we use appendName here
+ however we need a slightly different coding scheme than the normal
+ name encoding for field names
+ */
+ const OUString& rName = i_rControl.Name;
+ OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
+ int nLen = aStr.getLength();
+
+ OStringBuffer aBuffer( rName.getLength()+64 );
+ for( int i = 0; i < nLen; i++ )
+ {
+ /* #i16920# PDF recommendation: output UTF8, any byte
+ * outside the interval [32(=ASCII' ');126(=ASCII'~')]
+ * should be escaped hexadecimal
+ */
+ if( aStr[i] >= 32 && aStr[i] <= 126 )
+ aBuffer.append( aStr[i] );
+ else
+ {
+ aBuffer.append( '#' );
+ appendHex( static_cast<sal_Int8>(aStr[i]), aBuffer );
+ }
+ }
+
+ OString aFullName( aBuffer.makeStringAndClear() );
+
+ /* #i82785# create hierarchical fields down to the for each dot in i_rName */
+ sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0;
+ OString aPartialName;
+ OString aDomain;
+ do
+ {
+ nLastTokenIndex = nTokenIndex;
+ aPartialName = aFullName.getToken( 0, '.', nTokenIndex );
+ if( nTokenIndex != -1 )
+ {
+ // find or create a hierarchical field
+ // first find the fully qualified name up to this field
+ aDomain = aFullName.copy( 0, nTokenIndex-1 );
+ std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
+ if( it == m_aFieldNameMap.end() )
+ {
+ // create new hierarchy field
+ sal_Int32 nNewWidget = m_aWidgets.size();
+ m_aWidgets.emplace_back( );
+ m_aWidgets[nNewWidget].m_nObject = createObject();
+ m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy;
+ m_aWidgets[nNewWidget].m_aName = aPartialName;
+ m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
+ m_aFieldNameMap[aDomain] = nNewWidget;
+ m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
+ if( nLastTokenIndex > 0 )
+ {
+ // this field is not a root field and
+ // needs to be inserted to its parent
+ OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) );
+ it = m_aFieldNameMap.find( aParentDomain );
+ OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" );
+ if( it != m_aFieldNameMap.end() )
+ {
+ OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" );
+ if( it->second < sal_Int32(m_aWidgets.size()) )
+ {
+ PDFWidget& rParentField( m_aWidgets[it->second] );
+ rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject );
+ rParentField.m_aKidsIndex.push_back( nNewWidget );
+ m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject;
+ }
+ }
+ }
+ }
+ else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy )
+ {
+ // this is invalid, someone tries to have a terminal field as parent
+ // example: a button with the name foo.bar exists and
+ // another button is named foo.bar.no
+ // workaround: put the second terminal field as much up in the hierarchy as
+ // necessary to have a non-terminal field as parent (or none at all)
+ // since it->second already is terminal, we just need to use its parent
+ aDomain.clear();
+ aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
+ if( nLastTokenIndex > 0 )
+ {
+ aDomain = aFullName.copy( 0, nLastTokenIndex-1 );
+ aFullName = aDomain + "." + aPartialName;
+ }
+ else
+ aFullName = aPartialName;
+ break;
+ }
+ }
+ } while( nTokenIndex != -1 );
+
+ // insert widget into its hierarchy field
+ if( !aDomain.isEmpty() )
+ {
+ std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
+ if( it != m_aFieldNameMap.end() )
+ {
+ OSL_ENSURE( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size(), "invalid field index" );
+ if( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size() )
+ {
+ m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject;
+ m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject);
+ m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex );
+ }
+ }
+ }
+
+ if( aPartialName.isEmpty() )
+ {
+ // how funny, an empty field name
+ if( i_rControl.getType() == PDFWriter::RadioButton )
+ {
+ aPartialName = "RadioGroup" +
+ OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup );
+ }
+ else
+ aPartialName = "Widget"_ostr;
+ }
+
+ if( ! m_aContext.AllowDuplicateFieldNames )
+ {
+ std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName );
+
+ if( it != m_aFieldNameMap.end() ) // not unique
+ {
+ std::unordered_map< OString, sal_Int32 >::const_iterator check_it;
+ OString aTry;
+ sal_Int32 nTry = 2;
+ do
+ {
+ aTry = aFullName + "_" + OString::number(nTry++);
+ check_it = m_aFieldNameMap.find( aTry );
+ } while( check_it != m_aFieldNameMap.end() );
+ aFullName = aTry;
+ m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
+ aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
+ }
+ else
+ m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
+ }
+
+ // finally
+ m_aWidgets[i_nWidgetIndex].m_aName = aPartialName;
+}
+
+namespace
+{
+
+void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer )
+{
+ if( nValue < 0 )
+ {
+ rBuffer.append( '-' );
+ nValue = -nValue;
+ }
+ sal_Int32 nFactor = 1, nDiv = nLog10Divisor;
+ while( nDiv-- )
+ nFactor *= 10;
+
+ sal_Int32 nInt = nValue / nFactor;
+ rBuffer.append( nInt );
+ if (nFactor > 1 && nValue % nFactor)
+ {
+ rBuffer.append( '.' );
+ do
+ {
+ nFactor /= 10;
+ rBuffer.append((nValue / nFactor) % 10);
+ }
+ while (nFactor > 1 && nValue % nFactor); // omit trailing zeros
+ }
+}
+
+// appends a double. PDF does not accept exponential format, only fixed point
+void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 10 )
+{
+ bool bNeg = false;
+ if( fValue < 0.0 )
+ {
+ bNeg = true;
+ fValue=-fValue;
+ }
+
+ sal_Int64 nInt = static_cast<sal_Int64>(fValue);
+ fValue -= static_cast<double>(nInt);
+ // optimizing hardware may lead to a value of 1.0 after the subtraction
+ if( rtl::math::approxEqual(fValue, 1.0) || log10( 1.0-fValue ) <= -nPrecision )
+ {
+ nInt++;
+ fValue = 0.0;
+ }
+ sal_Int64 nFrac = 0;
+ if( fValue )
+ {
+ fValue *= pow( 10.0, static_cast<double>(nPrecision) );
+ nFrac = static_cast<sal_Int64>(fValue);
+ }
+ if( bNeg && ( nInt || nFrac ) )
+ rBuffer.append( '-' );
+ rBuffer.append( nInt );
+ if( !nFrac )
+ return;
+
+ int i;
+ rBuffer.append( '.' );
+ sal_Int64 nBound = static_cast<sal_Int64>(pow( 10.0, nPrecision - 1.0 )+0.5);
+ for ( i = 0; ( i < nPrecision ) && nFrac; i++ )
+ {
+ sal_Int64 nNumb = nFrac / nBound;
+ nFrac -= nNumb * nBound;
+ rBuffer.append( nNumb );
+ nBound /= 10;
+ }
+}
+
+void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
+{
+
+ if( rColor == COL_TRANSPARENT )
+ return;
+
+ if( bConvertToGrey )
+ {
+ sal_uInt8 cByte = rColor.GetLuminance();
+ appendDouble( static_cast<double>(cByte) / 255.0, rBuffer );
+ }
+ else
+ {
+ appendDouble( static_cast<double>(rColor.GetRed()) / 255.0, rBuffer );
+ rBuffer.append( ' ' );
+ appendDouble( static_cast<double>(rColor.GetGreen()) / 255.0, rBuffer );
+ rBuffer.append( ' ' );
+ appendDouble( static_cast<double>(rColor.GetBlue()) / 255.0, rBuffer );
+ }
+}
+
+} // end anonymous namespace
+
+void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
+{
+ if( rColor != COL_TRANSPARENT )
+ {
+ bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
+ appendColor( rColor, rBuffer, bGrey );
+ rBuffer.append( bGrey ? " G" : " RG" );
+ }
+}
+
+void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
+{
+ if( rColor != COL_TRANSPARENT )
+ {
+ bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
+ appendColor( rColor, rBuffer, bGrey );
+ rBuffer.append( bGrey ? " g" : " rg" );
+ }
+}
+
+namespace
+{
+
+void appendPdfTimeDate(OStringBuffer & rBuffer,
+ sal_Int16 year, sal_uInt16 month, sal_uInt16 day, sal_uInt16 hours, sal_uInt16 minutes, sal_uInt16 seconds, sal_Int32 tzDelta)
+{
+ rBuffer.append("D:");
+ rBuffer.append(char('0' + ((year / 1000) % 10)));
+ rBuffer.append(char('0' + ((year / 100) % 10)));
+ rBuffer.append(char('0' + ((year / 10) % 10)));
+ rBuffer.append(char('0' + (year % 10)));
+ rBuffer.append(char('0' + ((month / 10) % 10)));
+ rBuffer.append(char('0' + (month % 10)));
+ rBuffer.append(char('0' + ((day / 10) % 10)));
+ rBuffer.append(char('0' + (day % 10)));
+ rBuffer.append(char('0' + ((hours / 10) % 10)));
+ rBuffer.append(char('0' + (hours % 10)));
+ rBuffer.append(char('0' + ((minutes / 10) % 10)));
+ rBuffer.append(char('0' + (minutes % 10)));
+ rBuffer.append(char('0' + ((seconds / 10) % 10)));
+ rBuffer.append(char('0' + (seconds % 10)));
+
+ if (tzDelta == 0)
+ {
+ rBuffer.append("Z");
+ }
+ else
+ {
+ if (tzDelta > 0 )
+ rBuffer.append("+");
+ else
+ {
+ rBuffer.append("-");
+ tzDelta = -tzDelta;
+ }
+
+ rBuffer.append(char('0' + ((tzDelta / 36000) % 10)));
+ rBuffer.append(char('0' + ((tzDelta / 3600) % 10)));
+ rBuffer.append("'");
+ rBuffer.append(char('0' + ((tzDelta / 600) % 6)));
+ rBuffer.append(char('0' + ((tzDelta / 60) % 10)));
+ }
+}
+
+const char* getPDFVersionStr(PDFWriter::PDFVersion ePDFVersion)
+{
+ switch (ePDFVersion)
+ {
+ case PDFWriter::PDFVersion::PDF_A_1:
+ case PDFWriter::PDFVersion::PDF_1_4:
+ return "1.4";
+ case PDFWriter::PDFVersion::PDF_1_5:
+ return "1.5";
+ case PDFWriter::PDFVersion::PDF_1_6:
+ return "1.6";
+ default:
+ case PDFWriter::PDFVersion::PDF_A_2:
+ case PDFWriter::PDFVersion::PDF_A_3:
+ case PDFWriter::PDFVersion::PDF_1_7:
+ return "1.7";
+ }
+}
+
+} // end namespace
+
+PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
+ :
+ m_pWriter( pWriter ),
+ m_nPageWidth( nPageWidth ),
+ m_nPageHeight( nPageHeight ),
+ m_nUserUnit( 1 ),
+ m_eOrientation( eOrientation ),
+ m_nPageObject( 0 ), // invalid object number
+ m_nStreamLengthObject( 0 ),
+ m_nBeginStreamPos( 0 ),
+ m_eTransition( PDFWriter::PageTransition::Regular ),
+ m_nTransTime( 0 )
+{
+ // object ref must be only ever updated in emit()
+ m_nPageObject = m_pWriter->createObject();
+
+ switch (m_pWriter->m_aContext.Version)
+ {
+ // 1.6 or later
+ default:
+ m_nUserUnit = std::ceil(std::max(nPageWidth, nPageHeight) / 14400.0);
+ break;
+ case PDFWriter::PDFVersion::PDF_1_4:
+ case PDFWriter::PDFVersion::PDF_1_5:
+ case PDFWriter::PDFVersion::PDF_A_1:
+ break;
+ }
+}
+
+void PDFPage::beginStream()
+{
+ if (g_bDebugDisableCompression)
+ {
+ m_pWriter->emitComment("PDFWriterImpl::PDFPage::beginStream, +");
+ }
+ m_aStreamObjects.push_back(m_pWriter->createObject());
+ if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) )
+ return;
+
+ m_nStreamLengthObject = m_pWriter->createObject();
+ // write content stream header
+ OStringBuffer aLine(
+ OString::number(m_aStreamObjects.back())
+ + " 0 obj\n<</Length "
+ + OString::number( m_nStreamLengthObject )
+ + " 0 R" );
+ if (!g_bDebugDisableCompression)
+ aLine.append( "/Filter/FlateDecode" );
+ aLine.append( ">>\nstream\n" );
+ if( ! m_pWriter->writeBuffer( aLine ) )
+ return;
+ if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos))
+ {
+ m_pWriter->m_aFile.close();
+ m_pWriter->m_bOpen = false;
+ }
+ if (!g_bDebugDisableCompression)
+ m_pWriter->beginCompression();
+ m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() );
+}
+
+void PDFPage::endStream()
+{
+ if (!g_bDebugDisableCompression)
+ m_pWriter->endCompression();
+ sal_uInt64 nEndStreamPos;
+ if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos))
+ {
+ m_pWriter->m_aFile.close();
+ m_pWriter->m_bOpen = false;
+ return;
+ }
+ m_pWriter->disableStreamEncryption();
+ if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n" ) )
+ return;
+ // emit stream length object
+ if( ! m_pWriter->updateObject( m_nStreamLengthObject ) )
+ return;
+ OString aLine =
+ OString::number( m_nStreamLengthObject ) +
+ " 0 obj\n" +
+ OString::number( static_cast<sal_Int64>(nEndStreamPos-m_nBeginStreamPos) ) +
+ "\nendobj\n\n";
+ m_pWriter->writeBuffer( aLine );
+}
+
+bool PDFPage::emit(sal_Int32 nParentObject )
+{
+ m_pWriter->MARK("PDFPage::emit");
+ // emit page object
+ if( ! m_pWriter->updateObject( m_nPageObject ) )
+ return false;
+ OStringBuffer aLine(
+ OString::number(m_nPageObject)
+ + " 0 obj\n"
+ "<</Type/Page/Parent "
+ + OString::number(nParentObject)
+ + " 0 R"
+ "/Resources "
+ + OString::number(m_pWriter->getResourceDictObj())
+ + " 0 R" );
+ if( m_nPageWidth && m_nPageHeight )
+ {
+ aLine.append( "/MediaBox[0 0 "
+ + OString::number(m_nPageWidth / m_nUserUnit)
+ + " "
+ + OString::number(m_nPageHeight / m_nUserUnit)
+ + "]" );
+ if (m_nUserUnit > 1)
+ {
+ aLine.append("\n/UserUnit " + OString::number(m_nUserUnit));
+ }
+ }
+ switch( m_eOrientation )
+ {
+ case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break;
+ case PDFWriter::Orientation::Inherit: break;
+ }
+ int nAnnots = m_aAnnotations.size();
+ if( nAnnots > 0 )
+ {
+ aLine.append( "/Annots[\n" );
+ for( int i = 0; i < nAnnots; i++ )
+ {
+ aLine.append( OString::number(m_aAnnotations[i])
+ + " 0 R" );
+ aLine.append( ((i+1)%15) ? " " : "\n" );
+ }
+ aLine.append( "]\n" );
+ if (PDFWriter::PDFVersion::PDF_1_5 <= m_pWriter->m_aContext.Version)
+ {
+ // ISO 14289-1:2014, Clause: 7.18.3
+ aLine.append( "/Tabs/S\n" );
+ }
+ }
+ if( !m_aMCIDParents.empty() )
+ {
+ OStringBuffer aStructParents( 1024 );
+ aStructParents.append( "[ " );
+ int nParents = m_aMCIDParents.size();
+ for( int i = 0; i < nParents; i++ )
+ {
+ aStructParents.append( OString::number(m_aMCIDParents[i])
+ + " 0 R" );
+ aStructParents.append( ((i%10) == 9) ? "\n" : " " );
+ }
+ aStructParents.append( "]" );
+ m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
+
+ aLine.append( "/StructParents "
+ + OString::number( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) )
+ + "\n" );
+ }
+ if( m_eTransition != PDFWriter::PageTransition::Regular && m_nTransTime > 0 )
+ {
+ // transition duration
+ aLine.append( "/Trans<</D " );
+ appendDouble( static_cast<double>(m_nTransTime)/1000.0, aLine, 3 );
+ aLine.append( "\n" );
+ const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr;
+ switch( m_eTransition )
+ {
+ case PDFWriter::PageTransition::SplitHorizontalInward:
+ pStyle = "Split"; pDm = "H"; pM = "I"; break;
+ case PDFWriter::PageTransition::SplitHorizontalOutward:
+ pStyle = "Split"; pDm = "H"; pM = "O"; break;
+ case PDFWriter::PageTransition::SplitVerticalInward:
+ pStyle = "Split"; pDm = "V"; pM = "I"; break;
+ case PDFWriter::PageTransition::SplitVerticalOutward:
+ pStyle = "Split"; pDm = "V"; pM = "O"; break;
+ case PDFWriter::PageTransition::BlindsHorizontal:
+ pStyle = "Blinds"; pDm = "H"; break;
+ case PDFWriter::PageTransition::BlindsVertical:
+ pStyle = "Blinds"; pDm = "V"; break;
+ case PDFWriter::PageTransition::BoxInward:
+ pStyle = "Box"; pM = "I"; break;
+ case PDFWriter::PageTransition::BoxOutward:
+ pStyle = "Box"; pM = "O"; break;
+ case PDFWriter::PageTransition::WipeLeftToRight:
+ pStyle = "Wipe"; pDi = "0"; break;
+ case PDFWriter::PageTransition::WipeBottomToTop:
+ pStyle = "Wipe"; pDi = "90"; break;
+ case PDFWriter::PageTransition::WipeRightToLeft:
+ pStyle = "Wipe"; pDi = "180"; break;
+ case PDFWriter::PageTransition::WipeTopToBottom:
+ pStyle = "Wipe"; pDi = "270"; break;
+ case PDFWriter::PageTransition::Dissolve:
+ pStyle = "Dissolve"; break;
+ case PDFWriter::PageTransition::Regular:
+ break;
+ }
+ // transition style
+ if( pStyle )
+ {
+ aLine.append( OString::Concat("/S/") + pStyle + "\n" );
+ }
+ if( pDm )
+ {
+ aLine.append( OString::Concat("/Dm/") + pDm + "\n" );
+ }
+ if( pM )
+ {
+ aLine.append( OString::Concat("/M/") + pM + "\n" );
+ }
+ if( pDi )
+ {
+ aLine.append( OString::Concat("/Di ") + pDi + "\n" );
+ }
+ aLine.append( ">>\n" );
+ }
+
+ aLine.append( "/Contents" );
+ unsigned int nStreamObjects = m_aStreamObjects.size();
+ if( nStreamObjects > 1 )
+ aLine.append( '[' );
+ for(sal_Int32 i : m_aStreamObjects)
+ {
+ aLine.append( " " + OString::number( i ) + " 0 R" );
+ }
+ if( nStreamObjects > 1 )
+ aLine.append( ']' );
+ aLine.append( ">>\nendobj\n\n" );
+ return m_pWriter->writeBuffer( aLine );
+}
+
+void PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const
+{
+ Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
+ m_pWriter->m_aMapMode,
+ m_pWriter,
+ rPoint ) );
+
+ sal_Int32 nValue = aPoint.X();
+
+ appendFixedInt( nValue, rBuffer );
+
+ rBuffer.append( ' ' );
+
+ nValue = pointToPixel(getHeight()) - aPoint.Y();
+
+ appendFixedInt( nValue, rBuffer );
+}
+
+void PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const
+{
+ double fValue = pixelToPoint(rPoint.getX());
+
+ appendDouble( fValue, rBuffer, nLog10Divisor );
+ rBuffer.append( ' ' );
+ fValue = getHeight() - pixelToPoint(rPoint.getY());
+ appendDouble( fValue, rBuffer, nLog10Divisor );
+}
+
+void PDFPage::appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const
+{
+ appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer );
+ rBuffer.append( ' ' );
+ appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), rBuffer, false );
+ rBuffer.append( ' ' );
+ appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), rBuffer );
+ rBuffer.append( " re" );
+}
+
+void PDFPage::convertRect( tools::Rectangle& rRect ) const
+{
+ Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
+ m_pWriter->m_aMapMode,
+ m_pWriter,
+ rRect.BottomLeft() + Point( 0, 1 )
+ );
+ Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
+ m_pWriter->m_aMapMode,
+ m_pWriter,
+ rRect.GetSize() );
+ rRect.SetLeft( aLL.X() );
+ rRect.SetRight( aLL.X() + aSize.Width() );
+ rRect.SetTop( pointToPixel(getHeight()) - aLL.Y() );
+ rRect.SetBottom( rRect.Top() + aSize.Height() );
+}
+
+void PDFPage::appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const
+{
+ sal_uInt16 nPoints = rPoly.GetSize();
+ /*
+ * #108582# applications do weird things
+ */
+ sal_uInt32 nBufLen = rBuffer.getLength();
+ if( nPoints <= 0 )
+ return;
+
+ const PolyFlags* pFlagArray = rPoly.GetConstFlagAry();
+ appendPoint( rPoly[0], rBuffer );
+ rBuffer.append( " m\n" );
+ for( sal_uInt16 i = 1; i < nPoints; i++ )
+ {
+ if( pFlagArray && pFlagArray[i] == PolyFlags::Control && nPoints-i > 2 )
+ {
+ // bezier
+ SAL_WARN_IF( pFlagArray[i+1] != PolyFlags::Control || pFlagArray[i+2] == PolyFlags::Control, "vcl.pdfwriter", "unexpected sequence of control points" );
+ appendPoint( rPoly[i], rBuffer );
+ rBuffer.append( " " );
+ appendPoint( rPoly[i+1], rBuffer );
+ rBuffer.append( " " );
+ appendPoint( rPoly[i+2], rBuffer );
+ rBuffer.append( " c" );
+ i += 2; // add additionally consumed points
+ }
+ else
+ {
+ // line
+ appendPoint( rPoly[i], rBuffer );
+ rBuffer.append( " l" );
+ }
+ if( (rBuffer.getLength() - nBufLen) > 65 )
+ {
+ rBuffer.append( "\n" );
+ nBufLen = rBuffer.getLength();
+ }
+ else
+ rBuffer.append( " " );
+ }
+ if( bClose )
+ rBuffer.append( "h\n" );
+}
+
+void PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const
+{
+ basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
+ m_pWriter->m_aMapMode,
+ m_pWriter,
+ rPoly ) );
+
+ if( basegfx::utils::isRectangle( aPoly ) )
+ {
+ basegfx::B2DRange aRange( aPoly.getB2DRange() );
+ basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() );
+ appendPixelPoint( aBL, rBuffer );
+ rBuffer.append( ' ' );
+ appendMappedLength( aRange.getWidth(), rBuffer, false, nLog10Divisor );
+ rBuffer.append( ' ' );
+ appendMappedLength( aRange.getHeight(), rBuffer, true, nLog10Divisor );
+ rBuffer.append( " re\n" );
+ return;
+ }
+ sal_uInt32 nPoints = aPoly.count();
+ if( nPoints <= 0 )
+ return;
+
+ sal_uInt32 nBufLen = rBuffer.getLength();
+ basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) );
+ appendPixelPoint( aLastPoint, rBuffer );
+ rBuffer.append( " m\n" );
+ for( sal_uInt32 i = 1; i <= nPoints; i++ )
+ {
+ if( i != nPoints || aPoly.isClosed() )
+ {
+ sal_uInt32 nCurPoint = i % nPoints;
+ sal_uInt32 nLastPoint = i-1;
+ basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) );
+ if( aPoly.isNextControlPointUsed( nLastPoint ) &&
+ aPoly.isPrevControlPointUsed( nCurPoint ) )
+ {
+ appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
+ rBuffer.append( ' ' );
+ appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
+ rBuffer.append( ' ' );
+ appendPixelPoint( aPoint, rBuffer );
+ rBuffer.append( " c" );
+ }
+ else if( aPoly.isNextControlPointUsed( nLastPoint ) )
+ {
+ appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
+ rBuffer.append( ' ' );
+ appendPixelPoint( aPoint, rBuffer );
+ rBuffer.append( " y" );
+ }
+ else if( aPoly.isPrevControlPointUsed( nCurPoint ) )
+ {
+ appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
+ rBuffer.append( ' ' );
+ appendPixelPoint( aPoint, rBuffer );
+ rBuffer.append( " v" );
+ }
+ else
+ {
+ appendPixelPoint( aPoint, rBuffer );
+ rBuffer.append( " l" );
+ }
+ if( (rBuffer.getLength() - nBufLen) > 65 )
+ {
+ rBuffer.append( "\n" );
+ nBufLen = rBuffer.getLength();
+ }
+ else
+ rBuffer.append( " " );
+ }
+ }
+ rBuffer.append( "h\n" );
+}
+
+void PDFPage::appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
+{
+ sal_uInt16 nPolygons = rPolyPoly.Count();
+ for( sal_uInt16 n = 0; n < nPolygons; n++ )
+ appendPolygon( rPolyPoly[n], rBuffer );
+}
+
+void PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
+{
+ for(auto const& rPolygon : rPolyPoly)
+ appendPolygon( rPolygon, rBuffer );
+}
+
+void PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const
+{
+ sal_Int32 nValue = nLength;
+ if ( nLength < 0 )
+ {
+ rBuffer.append( '-' );
+ nValue = -nLength;
+ }
+ Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
+ m_pWriter->m_aMapMode,
+ m_pWriter,
+ Size( nValue, nValue ) ) );
+ nValue = bVertical ? aSize.Height() : aSize.Width();
+ if( pOutLength )
+ *pOutLength = ((nLength < 0 ) ? -nValue : nValue);
+
+ appendFixedInt( nValue, rBuffer );
+}
+
+void PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32 nPrecision ) const
+{
+ Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
+ m_pWriter->m_aMapMode,
+ m_pWriter,
+ Size( 1000, 1000 ) ) );
+ fLength *= pixelToPoint(static_cast<double>(bVertical ? aSize.Height() : aSize.Width()) / 1000.0);
+ appendDouble( fLength, rBuffer, nPrecision );
+}
+
+bool PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const
+{
+ if(LineStyle::Dash == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen())
+ {
+ // dashed and non-degraded case, check for implementation limits of dash array
+ // in PDF reader apps (e.g. acroread)
+ if(2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10)
+ {
+ return false;
+ }
+ }
+
+ if(basegfx::B2DLineJoin::NONE != rInfo.GetLineJoin())
+ {
+ // LineJoin used, ExtLineInfo required
+ return false;
+ }
+
+ if(css::drawing::LineCap_BUTT != rInfo.GetLineCap())
+ {
+ // LineCap used, ExtLineInfo required
+ return false;
+ }
+
+ if( rInfo.GetStyle() == LineStyle::Dash )
+ {
+ rBuffer.append( "[ " );
+ if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case
+ {
+ appendMappedLength( rInfo.GetDashLen(), rBuffer );
+ rBuffer.append( ' ' );
+ appendMappedLength( rInfo.GetDistance(), rBuffer );
+ rBuffer.append( ' ' );
+ }
+ else
+ {
+ for( int n = 0; n < rInfo.GetDashCount(); n++ )
+ {
+ appendMappedLength( rInfo.GetDashLen(), rBuffer );
+ rBuffer.append( ' ' );
+ appendMappedLength( rInfo.GetDistance(), rBuffer );
+ rBuffer.append( ' ' );
+ }
+ for( int m = 0; m < rInfo.GetDotCount(); m++ )
+ {
+ appendMappedLength( rInfo.GetDotLen(), rBuffer );
+ rBuffer.append( ' ' );
+ appendMappedLength( rInfo.GetDistance(), rBuffer );
+ rBuffer.append( ' ' );
+ }
+ }
+ rBuffer.append( "] 0 d\n" );
+ }
+
+ if( rInfo.GetWidth() > 1 )
+ {
+ appendMappedLength( rInfo.GetWidth(), rBuffer );
+ rBuffer.append( " w\n" );
+ }
+ else if( rInfo.GetWidth() == 0 )
+ {
+ // "pixel" line
+ appendDouble( 72.0/double(m_pWriter->GetDPIX()), rBuffer );
+ rBuffer.append( " w\n" );
+ }
+
+ return true;
+}
+
+void PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const
+{
+ if( nWidth <= 0 )
+ return;
+ if( nDelta < 1 )
+ nDelta = 1;
+
+ rBuffer.append( "0 " );
+ appendMappedLength( nY, rBuffer );
+ rBuffer.append( " m\n" );
+ for( sal_Int32 n = 0; n < nWidth; )
+ {
+ n += nDelta;
+ appendMappedLength( n, rBuffer, false );
+ rBuffer.append( ' ' );
+ appendMappedLength( nDelta+nY, rBuffer );
+ rBuffer.append( ' ' );
+ n += nDelta;
+ appendMappedLength( n, rBuffer, false );
+ rBuffer.append( ' ' );
+ appendMappedLength( nY, rBuffer );
+ rBuffer.append( " v " );
+ if( n < nWidth )
+ {
+ n += nDelta;
+ appendMappedLength( n, rBuffer, false );
+ rBuffer.append( ' ' );
+ appendMappedLength( nY-nDelta, rBuffer );
+ rBuffer.append( ' ' );
+ n += nDelta;
+ appendMappedLength( n, rBuffer, false );
+ rBuffer.append( ' ' );
+ appendMappedLength( nY, rBuffer );
+ rBuffer.append( " v\n" );
+ }
+ }
+ rBuffer.append( "S\n" );
+}
+
+void PDFPage::appendMatrix3(Matrix3 const & rMatrix, OStringBuffer& rBuffer)
+{
+ appendDouble(rMatrix.get(0), rBuffer);
+ rBuffer.append(' ');
+ appendDouble(rMatrix.get(1), rBuffer);
+ rBuffer.append(' ');
+ appendDouble(rMatrix.get(2), rBuffer);
+ rBuffer.append(' ');
+ appendDouble(rMatrix.get(3), rBuffer);
+ rBuffer.append(' ');
+ appendPoint(Point(tools::Long(rMatrix.get(4)), tools::Long(rMatrix.get(5))), rBuffer);
+}
+
+double PDFPage::getHeight() const
+{
+ double fRet = m_nPageHeight ? m_nPageHeight : 842; // default A4 height in inch/72, OK to use hardcoded value here?
+
+ if (m_nUserUnit > 1)
+ {
+ fRet /= m_nUserUnit;
+ }
+
+ return fRet;
+}
+
+PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext,
+ const css::uno::Reference< css::beans::XMaterialHolder >& xEnc,
+ PDFWriter& i_rOuterFace)
+ : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA, OUTDEV_PDF),
+ m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ),
+ m_aWidgetStyleSettings(Application::GetSettings().GetStyleSettings()),
+ m_nCurrentStructElement( 0 ),
+ m_bEmitStructure( true ),
+ m_nNextFID( 1 ),
+ m_aPDFBmpCache(utl::ConfigManager::IsFuzzing() ? 15 :
+ officecfg::Office::Common::VCL::PDFExportImageCacheSize::get()),
+ m_nCurrentPage( -1 ),
+ m_nCatalogObject(0),
+ m_nSignatureObject( -1 ),
+ m_nSignatureContentOffset( 0 ),
+ m_nSignatureLastByteRangeNoOffset( 0 ),
+ m_nResourceDict( -1 ),
+ m_nFontDictObject( -1 ),
+ m_aContext(rContext),
+ m_aFile(m_aContext.URL),
+ m_bOpen(false),
+ m_DocDigest(::comphelper::HashType::MD5),
+ m_aCipher( nullptr ),
+ m_nKeyLength(0),
+ m_nRC4KeyLength(0),
+ m_bEncryptThisStream( false ),
+ m_nAccessPermissions(0),
+ m_bIsPDF_A1( false ),
+ m_bIsPDF_A2( false ),
+ m_bIsPDF_UA( false ),
+ m_bIsPDF_A3( false ),
+ m_rOuterFace( i_rOuterFace )
+{
+ m_aStructure.emplace_back( );
+ m_aStructure[0].m_nOwnElement = 0;
+ m_aStructure[0].m_nParentElement = 0;
+ //m_StructElementStack.push(0);
+
+ Font aFont;
+ aFont.SetFamilyName( "Times" );
+ aFont.SetFontSize( Size( 0, 12 ) );
+
+ // tdf#150786 use the same settings for widgets regardless of theme
+ m_aWidgetStyleSettings.SetStandardStyles();
+
+ GraphicsState aState;
+ aState.m_aMapMode = m_aMapMode;
+ aState.m_aFont = aFont;
+ m_aGraphicsStack.push_front( aState );
+
+ osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
+ if (aError != osl::File::E_None)
+ {
+ if (aError == osl::File::E_EXIST)
+ {
+ aError = m_aFile.open(osl_File_OpenFlag_Write);
+ if (aError == osl::File::E_None)
+ aError = m_aFile.setSize(0);
+ }
+ }
+ if (aError != osl::File::E_None)
+ return;
+
+ m_bOpen = true;
+
+ // setup DocInfo
+ setupDocInfo();
+
+ /* prepare the cypher engine, can be done in CTOR, free in DTOR */
+ m_aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
+
+ if( xEnc.is() )
+ prepareEncryption( xEnc );
+
+ if( m_aContext.Encryption.Encrypt() )
+ {
+ // sanity check
+ if( m_aContext.Encryption.OValue.size() != ENCRYPTED_PWD_SIZE ||
+ m_aContext.Encryption.UValue.size() != ENCRYPTED_PWD_SIZE ||
+ m_aContext.Encryption.EncryptionKey.size() != MAXIMUM_RC4_KEY_LENGTH
+ )
+ {
+ // the field lengths are invalid ? This was not setup by initEncryption.
+ // do not encrypt after all
+ m_aContext.Encryption.OValue.clear();
+ m_aContext.Encryption.UValue.clear();
+ OSL_ENSURE( false, "encryption data failed sanity check, encryption disabled" );
+ }
+ else // setup key lengths
+ m_nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, m_nKeyLength, m_nRC4KeyLength );
+ }
+
+ // write header
+ OStringBuffer aBuffer( 20 );
+ aBuffer.append( "%PDF-" );
+ aBuffer.append(getPDFVersionStr(m_aContext.Version));
+ // append something binary as comment (suggested in PDF Reference)
+ aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
+ if( !writeBuffer( aBuffer ) )
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ return;
+ }
+
+ // insert outline root
+ m_aOutline.emplace_back( );
+
+ m_bIsPDF_A1 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_1);
+ if( m_bIsPDF_A1 )
+ m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
+
+ m_bIsPDF_A2 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_2);
+ if( m_bIsPDF_A2 )
+ m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
+
+ m_bIsPDF_A3 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_3);
+ if( m_bIsPDF_A3 )
+ m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
+
+ if (m_aContext.UniversalAccessibilityCompliance)
+ {
+ m_bIsPDF_UA = true;
+ m_aContext.Tagged = true;
+ }
+
+ if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
+ SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
+ else
+ SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
+
+ SetOutputSizePixel( Size( 640, 480 ) );
+ SetMapMode(MapMode(MapUnit::MapMM));
+}
+
+PDFWriterImpl::~PDFWriterImpl()
+{
+ disposeOnce();
+}
+
+void PDFWriterImpl::dispose()
+{
+ if( m_aCipher )
+ rtl_cipher_destroyARCFOUR( m_aCipher );
+ m_aPages.clear();
+ VirtualDevice::dispose();
+}
+
+bool PDFWriterImpl::ImplNewFont() const
+{
+ const ImplSVData* pSVData = ImplGetSVData();
+
+ if( mxFontCollection == pSVData->maGDIData.mxScreenFontList
+ || mxFontCache == pSVData->maGDIData.mxScreenFontCache )
+ {
+ const_cast<vcl::PDFWriterImpl&>(*this).ImplUpdateFontData();
+ }
+
+ return OutputDevice::ImplNewFont();
+}
+
+void PDFWriterImpl::setupDocInfo()
+{
+ std::vector< sal_uInt8 > aId;
+ m_aCreationDateString = PDFWriter::GetDateTime();
+ computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aContext.DocumentInfo.ModificationDate, m_aCreationMetaDateString );
+ if( m_aContext.Encryption.DocumentIdentifier.empty() )
+ m_aContext.Encryption.DocumentIdentifier = aId;
+}
+
+OString PDFWriter::GetDateTime()
+{
+ OStringBuffer aRet;
+
+ TimeValue aTVal, aGMT;
+ oslDateTime aDT;
+ osl_getSystemTime(&aGMT);
+ osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
+ osl_getDateTimeFromTimeValue(&aTVal, &aDT);
+
+ sal_Int32 nDelta = aTVal.Seconds-aGMT.Seconds;
+
+ appendPdfTimeDate(aRet, aDT.Year, aDT.Month, aDT.Day, aDT.Hours, aDT.Minutes, aDT.Seconds, nDelta);
+
+ aRet.append("'");
+ return aRet.makeStringAndClear();
+}
+
+void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier,
+ const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
+ const OString& i_rCString1,
+ const css::util::DateTime& rCreationMetaDate,
+ OString& o_rCString2
+ )
+{
+ o_rIdentifier.clear();
+
+ //build the document id
+ OString aInfoValuesOut;
+ OStringBuffer aID( 1024 );
+ if( !i_rDocInfo.Title.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Title, aID);
+ if( !i_rDocInfo.Author.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Author, aID);
+ if( !i_rDocInfo.Subject.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Subject, aID);
+ if( !i_rDocInfo.Keywords.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Keywords, aID);
+ if( !i_rDocInfo.Creator.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Creator, aID);
+ if( !i_rDocInfo.Producer.isEmpty() )
+ PDFWriter::AppendUnicodeTextString(i_rDocInfo.Producer, aID);
+
+ TimeValue aTVal, aGMT;
+ oslDateTime aDT;
+ aDT.NanoSeconds = rCreationMetaDate.NanoSeconds;
+ aDT.Seconds = rCreationMetaDate.Seconds;
+ aDT.Minutes = rCreationMetaDate.Minutes;
+ aDT.Hours = rCreationMetaDate.Hours;
+ aDT.Day = rCreationMetaDate.Day;
+ aDT.Month = rCreationMetaDate.Month;
+ aDT.Year = rCreationMetaDate.Year;
+
+ osl_getSystemTime( &aGMT );
+ osl_getLocalTimeFromSystemTime( &aGMT, &aTVal );
+ OStringBuffer aCreationMetaDateString(64);
+
+ // i59651: we fill the Metadata date string as well, if PDF/A is requested
+ // according to ISO 19005-1:2005 6.7.3 the date is corrected for
+ // local time zone offset UTC only, whereas Acrobat 8 seems
+ // to use the localtime notation only
+ // according to a recommendation in XMP Specification (Jan 2004, page 75)
+ // the Acrobat way seems the right approach
+ aCreationMetaDateString.append(
+ OStringChar(static_cast<char>('0' + ((aDT.Year/1000)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Year/100)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Year/10)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Year)%10)) )
+ + "-"
+ + OStringChar(static_cast<char>('0' + ((aDT.Month/10)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Month)%10)) )
+ + "-"
+ + OStringChar(static_cast<char>('0' + ((aDT.Day/10)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Day)%10)) )
+ + "T"
+ + OStringChar(static_cast<char>('0' + ((aDT.Hours/10)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Hours)%10)) )
+ + ":"
+ + OStringChar(static_cast<char>('0' + ((aDT.Minutes/10)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Minutes)%10)) )
+ + ":"
+ + OStringChar(static_cast<char>('0' + ((aDT.Seconds/10)%10)) )
+ + OStringChar(static_cast<char>('0' + ((aDT.Seconds)%10)) ));
+
+ sal_uInt32 nDelta = 0;
+ if( aGMT.Seconds > aTVal.Seconds )
+ {
+ nDelta = aGMT.Seconds-aTVal.Seconds;
+ aCreationMetaDateString.append( "-" );
+ }
+ else if( aGMT.Seconds < aTVal.Seconds )
+ {
+ nDelta = aTVal.Seconds-aGMT.Seconds;
+ aCreationMetaDateString.append( "+" );
+ }
+ else
+ {
+ aCreationMetaDateString.append( "Z" );
+
+ }
+ if( nDelta )
+ {
+ aCreationMetaDateString.append(
+ OStringChar(static_cast<char>('0' + ((nDelta/36000)%10)) )
+ + OStringChar(static_cast<char>('0' + ((nDelta/3600)%10)) )
+ + ":"
+ + OStringChar(static_cast<char>('0' + ((nDelta/600)%6)) )
+ + OStringChar(static_cast<char>('0' + ((nDelta/60)%10)) ));
+ }
+ aID.append( i_rCString1.getStr(), i_rCString1.getLength() );
+
+ aInfoValuesOut = aID.makeStringAndClear();
+ o_rCString2 = aCreationMetaDateString.makeStringAndClear();
+
+ ::comphelper::Hash aDigest(::comphelper::HashType::MD5);
+ aDigest.update(reinterpret_cast<unsigned char const*>(&aGMT), sizeof(aGMT));
+ aDigest.update(reinterpret_cast<unsigned char const*>(aInfoValuesOut.getStr()), aInfoValuesOut.getLength());
+ //the binary form of the doc id is needed for encryption stuff
+ o_rIdentifier = aDigest.finalize();
+}
+
+/* i12626 methods */
+/*
+check if the Unicode string must be encrypted or not, perform the requested task,
+append the string as unicode hex, encrypted if needed
+ */
+inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
+{
+ rOutBuffer.append( "<" );
+ if( m_aContext.Encryption.Encrypt() )
+ {
+ const sal_Unicode* pStr = rInString.getStr();
+ sal_Int32 nLen = rInString.getLength();
+ //prepare a unicode string, encrypt it
+ enableStringEncryption( nInObjectNumber );
+ sal_uInt8 *pCopy = m_vEncryptionBuffer.data();
+ sal_Int32 nChars = 2 + (nLen * 2);
+ m_vEncryptionBuffer.resize(nChars);
+ *pCopy++ = 0xFE;
+ *pCopy++ = 0xFF;
+ // we need to prepare a byte stream from the unicode string buffer
+ for( int i = 0; i < nLen; i++ )
+ {
+ sal_Unicode aUnChar = pStr[i];
+ *pCopy++ = static_cast<sal_uInt8>( aUnChar >> 8 );
+ *pCopy++ = static_cast<sal_uInt8>( aUnChar & 255 );
+ }
+ //encrypt in place
+ rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChars, m_vEncryptionBuffer.data(), nChars );
+ //now append, hexadecimal (appendHex), the encrypted result
+ for(int i = 0; i < nChars; i++)
+ appendHex( m_vEncryptionBuffer[i], rOutBuffer );
+ }
+ else
+ PDFWriter::AppendUnicodeTextString(rInString, rOutBuffer);
+ rOutBuffer.append( ">" );
+}
+
+inline void PDFWriterImpl::appendLiteralStringEncrypt( std::string_view rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
+{
+ rOutBuffer.append( "(" );
+ sal_Int32 nChars = rInString.size();
+ //check for encryption, if ok, encrypt the string, then convert with appndLiteralString
+ if( m_aContext.Encryption.Encrypt() )
+ {
+ m_vEncryptionBuffer.resize(nChars);
+ //encrypt the string in a buffer, then append it
+ enableStringEncryption( nInObjectNumber );
+ rtl_cipher_encodeARCFOUR( m_aCipher, rInString.data(), nChars, m_vEncryptionBuffer.data(), nChars );
+ appendLiteralString( reinterpret_cast<char*>(m_vEncryptionBuffer.data()), nChars, rOutBuffer );
+ }
+ else
+ appendLiteralString( rInString.data(), nChars , rOutBuffer );
+ rOutBuffer.append( ")" );
+}
+
+void PDFWriterImpl::appendLiteralStringEncrypt( std::u16string_view rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc )
+{
+ OString aBufferString( OUStringToOString( rInString, nEnc ) );
+ sal_Int32 nLen = aBufferString.getLength();
+ OStringBuffer aBuf( nLen );
+ const char* pT = aBufferString.getStr();
+
+ for( sal_Int32 i = 0; i < nLen; i++, pT++ )
+ {
+ if( (*pT & 0x80) == 0 )
+ aBuf.append( *pT );
+ else
+ {
+ aBuf.append( '<' );
+ appendHex( *pT, aBuf );
+ aBuf.append( '>' );
+ }
+ }
+ aBufferString = aBuf.makeStringAndClear();
+ appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
+}
+
+/* end i12626 methods */
+
+void PDFWriterImpl::emitComment( const char* pComment )
+{
+ OString aLine = OString::Concat("% ") + pComment + "\n";
+ writeBuffer( aLine );
+}
+
+bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
+{
+ if (!g_bDebugDisableCompression)
+ {
+ sal_uInt64 nEndPos = pStream->TellEnd();
+ pStream->Seek( STREAM_SEEK_TO_BEGIN );
+ ZCodec aCodec( 0x4000, 0x4000 );
+ SvMemoryStream aStream;
+ aCodec.BeginCompression();
+ aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos );
+ aCodec.EndCompression();
+ nEndPos = aStream.Tell();
+ pStream->Seek( STREAM_SEEK_TO_BEGIN );
+ aStream.Seek( STREAM_SEEK_TO_BEGIN );
+ pStream->SetStreamSize( nEndPos );
+ pStream->WriteBytes( aStream.GetData(), nEndPos );
+ return true;
+ }
+ else
+ return false;
+}
+
+void PDFWriterImpl::beginCompression()
+{
+ if (!g_bDebugDisableCompression)
+ {
+ m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 );
+ m_pMemStream = std::make_unique<SvMemoryStream>();
+ m_pCodec->BeginCompression();
+ }
+}
+
+void PDFWriterImpl::endCompression()
+{
+ if (!g_bDebugDisableCompression && m_pCodec)
+ {
+ m_pCodec->EndCompression();
+ m_pCodec.reset();
+ sal_uInt64 nLen = m_pMemStream->Tell();
+ m_pMemStream->Seek( 0 );
+ writeBufferBytes( m_pMemStream->GetData(), nLen );
+ m_pMemStream.reset();
+ }
+}
+
+bool PDFWriterImpl::writeBufferBytes( const void* pBuffer, sal_uInt64 nBytes )
+{
+ if( ! m_bOpen ) // we are already down the drain
+ return false;
+
+ if( ! nBytes ) // huh ?
+ return true;
+
+ if( !m_aOutputStreams.empty() )
+ {
+ m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
+ m_aOutputStreams.front().m_pStream->WriteBytes(
+ pBuffer, sal::static_int_cast<std::size_t>(nBytes));
+ return true;
+ }
+
+ sal_uInt64 nWritten;
+ if( m_pCodec )
+ {
+ m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), static_cast<sal_uLong>(nBytes) );
+ nWritten = nBytes;
+ }
+ else
+ {
+ bool buffOK = true;
+ if( m_bEncryptThisStream )
+ {
+ /* implement the encryption part of the PDF spec encryption algorithm 3.1 */
+ m_vEncryptionBuffer.resize(nBytes);
+ if( buffOK )
+ rtl_cipher_encodeARCFOUR( m_aCipher,
+ pBuffer, static_cast<sal_Size>(nBytes),
+ m_vEncryptionBuffer.data(), static_cast<sal_Size>(nBytes) );
+ }
+
+ const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_vEncryptionBuffer.data() : pBuffer;
+ m_DocDigest.update(static_cast<unsigned char const*>(pWriteBuffer), static_cast<sal_uInt32>(nBytes));
+
+ if (m_aFile.write(pWriteBuffer, nBytes, nWritten) != osl::File::E_None)
+ nWritten = 0;
+
+ if( nWritten != nBytes )
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ }
+ }
+
+ return nWritten == nBytes;
+}
+
+void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
+{
+ endPage();
+ m_nCurrentPage = m_aPages.size();
+ m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation );
+
+ const Fraction frac(m_aPages.back().m_nUserUnit, pointToPixel(1));
+ m_aMapMode = MapMode(MapUnit::MapPoint, Point(), frac, frac);
+
+ m_aPages.back().beginStream();
+
+ // setup global graphics state
+ // linewidth is "1 pixel" by default
+ OStringBuffer aBuf( 16 );
+ appendDouble( 72.0/double(GetDPIX()), aBuf );
+ aBuf.append( " w\n" );
+ writeBuffer( aBuf );
+}
+
+void PDFWriterImpl::endPage()
+{
+ if( m_aPages.empty() )
+ return;
+
+ // close eventual MC sequence
+ endStructureElementMCSeq();
+
+ // sanity check
+ if( !m_aOutputStreams.empty() )
+ {
+ OSL_FAIL( "redirection across pages !!!" );
+ m_aOutputStreams.clear(); // leak !
+ m_aMapMode.SetOrigin( Point() );
+ }
+
+ m_aGraphicsStack.clear();
+ m_aGraphicsStack.emplace_back( );
+
+ // this should pop the PDF graphics stack if necessary
+ updateGraphicsState();
+
+ m_aPages.back().endStream();
+
+ // reset the default font
+ Font aFont;
+ aFont.SetFamilyName( "Times" );
+ aFont.SetFontSize( Size( 0, 12 ) );
+
+ m_aCurrentPDFState = m_aGraphicsStack.front();
+ m_aGraphicsStack.front().m_aFont = aFont;
+
+ for (auto & bitmap : m_aBitmaps)
+ {
+ if( ! bitmap.m_aBitmap.IsEmpty() )
+ {
+ writeBitmapObject(bitmap);
+ bitmap.m_aBitmap = BitmapEx();
+ }
+ }
+ for (auto & jpeg : m_aJPGs)
+ {
+ if( jpeg.m_pStream )
+ {
+ writeJPG( jpeg );
+ jpeg.m_pStream.reset();
+ jpeg.m_aAlphaMask = AlphaMask();
+ }
+ }
+ for (auto & item : m_aTransparentObjects)
+ {
+ if( item.m_pContentStream )
+ {
+ writeTransparentObject(item);
+ item.m_pContentStream.reset();
+ }
+ }
+
+}
+
+sal_Int32 PDFWriterImpl::createObject()
+{
+ m_aObjects.push_back( ~0U );
+ return m_aObjects.size();
+}
+
+bool PDFWriterImpl::updateObject( sal_Int32 n )
+{
+ if( ! m_bOpen )
+ return false;
+
+ sal_uInt64 nOffset = ~0U;
+ osl::File::RC aError = m_aFile.getPos(nOffset);
+ SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
+ if (aError != osl::File::E_None)
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ }
+ m_aObjects[ n-1 ] = nOffset;
+ return aError == osl::File::E_None;
+}
+
+#define CHECK_RETURN( x ) if( !(x) ) return 0
+#define CHECK_RETURN2( x ) if( !(x) ) return
+
+sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
+{
+ if( nObject > 0 )
+ {
+ OStringBuffer aLine( 1024 );
+
+ aLine.append( OString::number(nObject)
+ + " 0 obj\n"
+ "<</Nums[\n" );
+ sal_Int32 nTreeItems = m_aStructParentTree.size();
+ for( sal_Int32 n = 0; n < nTreeItems; n++ )
+ {
+ aLine.append( OString::number(n) + " "
+ + m_aStructParentTree[n]
+ + "\n" );
+ }
+ aLine.append( "]>>\nendobj\n\n" );
+ CHECK_RETURN( updateObject( nObject ) );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ }
+ return nObject;
+}
+
+// every structure element already has a unique object id - just use it for ID
+static OString GenerateID(sal_Int32 const nObjectId)
+{
+ return "id" + OString::number(nObjectId);
+}
+
+sal_Int32 PDFWriterImpl::emitStructIDTree(sal_Int32 const nObject)
+{
+ // loosely following PDF 1.7, 10.6.5 Example of Logical Structure, Example 10.15
+ if (nObject < 0)
+ {
+ return nObject;
+ }
+ // the name tree entries must be sorted lexicographically.
+ std::map<OString, sal_Int32> ids;
+ for (auto n : m_StructElemObjsWithID)
+ {
+ ids.emplace(GenerateID(n), n);
+ }
+ OStringBuffer buf;
+ appendObjectID(nObject, buf);
+ buf.append("<</Names [\n");
+ for (auto const& it : ids)
+ {
+ appendLiteralStringEncrypt(it.first, nObject, buf);
+ buf.append(" ");
+ appendObjectReference(it.second, buf);
+ buf.append("\n");
+ }
+ buf.append("] >>\nendobj\n\n");
+
+ CHECK_RETURN( updateObject(nObject) );
+ CHECK_RETURN( writeBuffer(buf) );
+
+ return nObject;
+}
+
+const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
+{
+ static constexpr auto aAttributeStrings = frozen::make_map<PDFWriter::StructAttribute, const char*>({
+ { PDFWriter::Placement, "Placement" },
+ { PDFWriter::WritingMode, "WritingMode" },
+ { PDFWriter::SpaceBefore, "SpaceBefore" },
+ { PDFWriter::SpaceAfter, "SpaceAfter" },
+ { PDFWriter::StartIndent, "StartIndent" },
+ { PDFWriter::EndIndent, "EndIndent" },
+ { PDFWriter::TextIndent, "TextIndent" },
+ { PDFWriter::TextAlign, "TextAlign" },
+ { PDFWriter::Width, "Width" },
+ { PDFWriter::Height, "Height" },
+ { PDFWriter::BlockAlign, "BlockAlign" },
+ { PDFWriter::InlineAlign, "InlineAlign" },
+ { PDFWriter::LineHeight, "LineHeight" },
+ { PDFWriter::BaselineShift, "BaselineShift" },
+ { PDFWriter::TextDecorationType,"TextDecorationType" },
+ { PDFWriter::ListNumbering, "ListNumbering" },
+ { PDFWriter::RowSpan, "RowSpan" },
+ { PDFWriter::ColSpan, "ColSpan" },
+ { PDFWriter::Scope, "Scope" },
+ { PDFWriter::Role, "Role" },
+ { PDFWriter::RubyAlign, "RubyAlign" },
+ { PDFWriter::RubyPosition, "RubyPosition" },
+ { PDFWriter::Type, "Type" },
+ { PDFWriter::Subtype, "Subtype" },
+ { PDFWriter::LinkAnnotation, "LinkAnnotation" }
+ });
+
+ auto it = aAttributeStrings.find( eAttr );
+
+ if( it == aAttributeStrings.end() )
+ SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr);
+
+ return it != aAttributeStrings.end() ? it->second : "";
+}
+
+const char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal )
+{
+ static constexpr auto aValueStrings = frozen::make_map<PDFWriter::StructAttributeValue, const char*>({
+ { PDFWriter::NONE, "None" },
+ { PDFWriter::Block, "Block" },
+ { PDFWriter::Inline, "Inline" },
+ { PDFWriter::Before, "Before" },
+ { PDFWriter::After, "After" },
+ { PDFWriter::Start, "Start" },
+ { PDFWriter::End, "End" },
+ { PDFWriter::LrTb, "LrTb" },
+ { PDFWriter::RlTb, "RlTb" },
+ { PDFWriter::TbRl, "TbRl" },
+ { PDFWriter::Center, "Center" },
+ { PDFWriter::Justify, "Justify" },
+ { PDFWriter::Auto, "Auto" },
+ { PDFWriter::Middle, "Middle" },
+ { PDFWriter::Normal, "Normal" },
+ { PDFWriter::Underline, "Underline" },
+ { PDFWriter::Overline, "Overline" },
+ { PDFWriter::LineThrough,"LineThrough" },
+ { PDFWriter::Row, "Row" },
+ { PDFWriter::Column, "Column" },
+ { PDFWriter::Both, "Both" },
+ { PDFWriter::Pagination, "Pagination" },
+ { PDFWriter::Layout, "Layout" },
+ { PDFWriter::Page, "Page" },
+ { PDFWriter::Background, "Background" },
+ { PDFWriter::Header, "Header" },
+ { PDFWriter::Footer, "Footer" },
+ { PDFWriter::Watermark, "Watermark" },
+ { PDFWriter::Rb, "rb" },
+ { PDFWriter::Cb, "cb" },
+ { PDFWriter::Pb, "pb" },
+ { PDFWriter::Tv, "tv" },
+ { PDFWriter::RStart, "Start" },
+ { PDFWriter::RCenter, "Center" },
+ { PDFWriter::REnd, "End" },
+ { PDFWriter::RJustify, "Justify" },
+ { PDFWriter::RDistribute,"Distribute" },
+ { PDFWriter::RBefore, "Before" },
+ { PDFWriter::RAfter, "After" },
+ { PDFWriter::RWarichu, "Warichu" },
+ { PDFWriter::RInline, "Inline" },
+ { PDFWriter::Disc, "Disc" },
+ { PDFWriter::Circle, "Circle" },
+ { PDFWriter::Square, "Square" },
+ { PDFWriter::Decimal, "Decimal" },
+ { PDFWriter::UpperRoman, "UpperRoman" },
+ { PDFWriter::LowerRoman, "LowerRoman" },
+ { PDFWriter::UpperAlpha, "UpperAlpha" },
+ { PDFWriter::LowerAlpha, "LowerAlpha" }
+ });
+
+ auto it = aValueStrings.find( eVal );
+
+ if( it == aValueStrings.end() )
+ SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal);
+
+ return it != aValueStrings.end() ? it->second : "";
+}
+
+static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
+{
+ o_rLine.append( "/" );
+ o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
+
+ if( i_rVal.eValue != PDFWriter::Invalid )
+ {
+ o_rLine.append( "/" );
+ o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
+ }
+ else
+ {
+ // numerical value
+ o_rLine.append( " " );
+ if( i_bIsFixedInt )
+ appendFixedInt( i_rVal.nValue, o_rLine );
+ else
+ o_rLine.append( i_rVal.nValue );
+ }
+ o_rLine.append( "\n" );
+}
+
+template<typename T>
+void PDFWriterImpl::AppendAnnotKid(PDFStructureElement& i_rEle, T & rAnnot)
+{
+ // update struct parent of link
+ OString const aStructParentEntry(OString::number(i_rEle.m_nObject) + " 0 R");
+ m_aStructParentTree.push_back( aStructParentEntry );
+ rAnnot.m_nStructParent = m_aStructParentTree.size()-1;
+ sal_Int32 const nAnnotObj(rAnnot.m_nObject);
+ i_rEle.m_aKids.emplace_back(ObjReferenceObj{nAnnotObj});
+}
+
+OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
+{
+ // create layout, list and table attribute sets
+ OStringBuffer aLayout(256), aList(64), aTable(64);
+ OStringBuffer aPrintField;
+ for (auto const& attribute : i_rEle.m_aAttributes)
+ {
+ if( attribute.first == PDFWriter::ListNumbering )
+ appendStructureAttributeLine( attribute.first, attribute.second, aList, true );
+ else if (attribute.first == PDFWriter::Role)
+ {
+ appendStructureAttributeLine(attribute.first, attribute.second, aPrintField, true);
+ }
+ else if( attribute.first == PDFWriter::RowSpan ||
+ attribute.first == PDFWriter::ColSpan ||
+ attribute.first == PDFWriter::Scope)
+ {
+ appendStructureAttributeLine( attribute.first, attribute.second, aTable, false );
+ }
+ else if( attribute.first == PDFWriter::LinkAnnotation )
+ {
+ sal_Int32 nLink = attribute.second.nValue;
+ std::map< sal_Int32, sal_Int32 >::const_iterator link_it =
+ m_aLinkPropertyMap.find( nLink );
+ if( link_it != m_aLinkPropertyMap.end() )
+ nLink = link_it->second;
+ if( nLink >= 0 && o3tl::make_unsigned(nLink) < m_aLinks.size() )
+ {
+ AppendAnnotKid(i_rEle, m_aLinks[nLink]);
+ }
+ else
+ {
+ OSL_FAIL( "unresolved link id for Link structure" );
+ SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
+ if (g_bDebugDisableCompression)
+ {
+ OString aLine = "unresolved link id " +
+ OString::number( nLink ) +
+ " for Link structure";
+ emitComment( aLine.getStr() );
+ }
+ }
+ }
+ else
+ appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true );
+ }
+ if( ! i_rEle.m_aBBox.IsEmpty() )
+ {
+ aLayout.append( "/BBox[" );
+ appendFixedInt( i_rEle.m_aBBox.Left(), aLayout );
+ aLayout.append( " " );
+ appendFixedInt( i_rEle.m_aBBox.Top(), aLayout );
+ aLayout.append( " " );
+ appendFixedInt( i_rEle.m_aBBox.Right(), aLayout );
+ aLayout.append( " " );
+ appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout );
+ aLayout.append( "]\n" );
+ }
+
+ OStringBuffer aRet(256);
+ bool isArray(false);
+ if (1 < (aLayout.isEmpty() ? 0 : 1) + (aList.isEmpty() ? 0 : 1)
+ + (aPrintField.isEmpty() ? 0 : 1) + (aTable.isEmpty() ? 0 : 1))
+ {
+ isArray = true;
+ aRet.append(" [");
+ }
+ auto const WriteAttrs = [&](char const*const pName, OStringBuffer & rBuf)
+ {
+ aRet.append(" <</O");
+ aRet.append(pName);
+ aRet.append(rBuf);
+ aRet.append(">>");
+ };
+ if( !aLayout.isEmpty() )
+ {
+ WriteAttrs("/Layout", aLayout);
+ }
+ if( !aList.isEmpty() )
+ {
+ WriteAttrs("/List", aList);
+ }
+ if (!aPrintField.isEmpty())
+ {
+ WriteAttrs("/PrintField", aPrintField);
+ }
+ if( !aTable.isEmpty() )
+ {
+ WriteAttrs("/Table", aTable);
+ }
+
+ if (isArray)
+ {
+ aRet.append( " ]" );
+ }
+ return aRet.makeStringAndClear();
+}
+
+sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
+{
+ assert(rEle.m_nOwnElement == 0 || rEle.m_oType);
+ if (rEle.m_nOwnElement != rEle.m_nParentElement // emit the struct tree root
+ // do not emit NonStruct and its children
+ && *rEle.m_oType == PDFWriter::NonStructElement)
+ {
+ return 0;
+ }
+
+ for (auto const& child : rEle.m_aChildren)
+ {
+ if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() )
+ {
+ PDFStructureElement& rChild = m_aStructure[ child ];
+ if (*rChild.m_oType != PDFWriter::NonStructElement)
+ {
+ if( rChild.m_nParentElement == rEle.m_nOwnElement )
+ emitStructure( rChild );
+ else
+ {
+ OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" );
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child);
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child);
+ }
+ }
+
+ OStringBuffer aLine( 512 );
+ aLine.append(
+ OString::number(rEle.m_nObject)
+ + " 0 obj\n"
+ "<</Type" );
+ sal_Int32 nParentTree = -1;
+ sal_Int32 nIDTree = -1;
+ if( rEle.m_nOwnElement == rEle.m_nParentElement )
+ {
+ nParentTree = createObject();
+ CHECK_RETURN( nParentTree );
+ aLine.append( "/StructTreeRoot\n"
+ "/ParentTree "
+ + OString::number(nParentTree)
+ + " 0 R\n" );
+ if( ! m_aRoleMap.empty() )
+ {
+ aLine.append( "/RoleMap<<" );
+ for (auto const& role : m_aRoleMap)
+ {
+ aLine.append( "/" + role.first + "/" + role.second + "\n" );
+ }
+ aLine.append( ">>\n" );
+ }
+ if (!m_StructElemObjsWithID.empty())
+ {
+ nIDTree = createObject();
+ aLine.append("/IDTree ");
+ appendObjectReference(nIDTree, aLine);
+ aLine.append("\n");
+ }
+ }
+ else
+ {
+ aLine.append( "/StructElem\n"
+ "/S/" );
+ if( !rEle.m_aAlias.isEmpty() )
+ aLine.append( rEle.m_aAlias );
+ else
+ aLine.append( getStructureTag(*rEle.m_oType) );
+ if (m_StructElemObjsWithID.find(rEle.m_nObject) != m_StructElemObjsWithID.end())
+ {
+ aLine.append("\n/ID ");
+ appendLiteralStringEncrypt(GenerateID(rEle.m_nObject), rEle.m_nObject, aLine);
+ }
+ aLine.append(
+ "\n"
+ "/P "
+ + OString::number(m_aStructure[ rEle.m_nParentElement ].m_nObject)
+ + " 0 R\n"
+ "/Pg "
+ + OString::number(rEle.m_nFirstPageObject)
+ + " 0 R\n" );
+ if( !rEle.m_aActualText.isEmpty() )
+ {
+ aLine.append( "/ActualText" );
+ appendUnicodeTextStringEncrypt( rEle.m_aActualText, rEle.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !rEle.m_aAltText.isEmpty() )
+ {
+ aLine.append( "/Alt" );
+ appendUnicodeTextStringEncrypt( rEle.m_aAltText, rEle.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+ }
+ if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) )
+ {
+ OString aAttribs = emitStructureAttributes( rEle );
+ if( !aAttribs.isEmpty() )
+ {
+ aLine.append( "/A" + aAttribs + "\n" );
+ }
+ }
+ if( !rEle.m_aLocale.Language.isEmpty() )
+ {
+ /* PDF allows only RFC 3066, which is only partly BCP 47 and does not
+ * include script tags and others.
+ * http://pdf.editme.com/pdfua-naturalLanguageSpecification
+ * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886
+ * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification
+ * */
+ LanguageTag aLanguageTag( rEle.m_aLocale);
+ OUString aLanguage, aScript, aCountry;
+ aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
+ if (!aLanguage.isEmpty())
+ {
+ OUStringBuffer aLocBuf( 16 );
+ aLocBuf.append( aLanguage );
+ if( !aCountry.isEmpty() )
+ {
+ aLocBuf.append( "-" + aCountry );
+ }
+ aLine.append( "/Lang" );
+ appendLiteralStringEncrypt( aLocBuf, rEle.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+ }
+ if (!rEle.m_AnnotIds.empty())
+ {
+ for (auto const id : rEle.m_AnnotIds)
+ {
+ auto const it(m_aLinkPropertyMap.find(id));
+ assert(it != m_aLinkPropertyMap.end());
+
+ if (*rEle.m_oType == PDFWriter::Form)
+ {
+ assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aWidgets.size());
+ AppendAnnotKid(rEle, m_aWidgets[it->second]);
+ }
+ else
+ {
+ assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aScreens.size());
+ AppendAnnotKid(rEle, m_aScreens[it->second]);
+ }
+ }
+ }
+ if( ! rEle.m_aKids.empty() )
+ {
+ unsigned int i = 0;
+ aLine.append( "/K[" );
+ for (auto const& rKid : rEle.m_aKids)
+ {
+ if (std::holds_alternative<ObjReference>(rKid))
+ {
+ ObjReference const& rObj(std::get<ObjReference>(rKid));
+ appendObjectReference(rObj.nObject, aLine);
+ aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
+ }
+ else if (std::holds_alternative<ObjReferenceObj>(rKid))
+ {
+ ObjReferenceObj const& rObj(std::get<ObjReferenceObj>(rKid));
+ aLine.append("<</Type/OBJR/Obj ");
+ appendObjectReference(rObj.nObject, aLine);
+ aLine.append(">>\n");
+ }
+ else
+ {
+ assert(std::holds_alternative<MCIDReference>(rKid));
+ MCIDReference const& rMCID(std::get<MCIDReference>(rKid));
+ if (rMCID.nPageObj == rEle.m_nFirstPageObject)
+ {
+ aLine.append(OString::number(rMCID.nMCID) + " ");
+ }
+ else
+ {
+ aLine.append("<</Type/MCR/Pg ");
+ appendObjectReference(rMCID.nPageObj, aLine);
+ aLine.append(" /MCID " + OString::number(rMCID.nMCID) + ">>\n");
+ }
+ }
+ ++i;
+ }
+ aLine.append( "]\n" );
+ }
+ aLine.append( ">>\nendobj\n\n" );
+
+ CHECK_RETURN( updateObject( rEle.m_nObject ) );
+ CHECK_RETURN( writeBuffer( aLine ) );
+
+ CHECK_RETURN( emitStructParentTree( nParentTree ) );
+ CHECK_RETURN( emitStructIDTree(nIDTree) );
+
+ return rEle.m_nObject;
+}
+
+bool PDFWriterImpl::emitGradients()
+{
+ for (auto const& gradient : m_aGradients)
+ {
+ if ( !writeGradientFunction( gradient ) ) return false;
+ }
+ return true;
+}
+
+bool PDFWriterImpl::emitTilings()
+{
+ OStringBuffer aTilingObj( 1024 );
+
+ for (auto & tiling : m_aTilings)
+ {
+ SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" );
+ if( ! tiling.m_pTilingStream )
+ continue;
+
+ aTilingObj.setLength( 0 );
+
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::emitTilings" );
+ }
+
+ sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left());
+ sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top());
+ sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth());
+ sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight());
+ if( tiling.m_aCellSize.Width() == 0 )
+ tiling.m_aCellSize.setWidth( nW );
+ if( tiling.m_aCellSize.Height() == 0 )
+ tiling.m_aCellSize.setHeight( nH );
+
+ bool bDeflate = compressStream( tiling.m_pTilingStream.get() );
+ sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->TellEnd();
+ tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN );
+
+ // write pattern object
+ aTilingObj.append(
+ OString::number(tiling.m_nObject)
+ + " 0 obj\n"
+ "<</Type/Pattern/PatternType 1\n"
+ "/PaintType 1\n"
+ "/TilingType 2\n"
+ "/BBox[" );
+ appendFixedInt( nX, aTilingObj );
+ aTilingObj.append( ' ' );
+ appendFixedInt( nY, aTilingObj );
+ aTilingObj.append( ' ' );
+ appendFixedInt( nX+nW, aTilingObj );
+ aTilingObj.append( ' ' );
+ appendFixedInt( nY+nH, aTilingObj );
+ aTilingObj.append( "]\n"
+ "/XStep " );
+ appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj );
+ aTilingObj.append( "\n"
+ "/YStep " );
+ appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj );
+ aTilingObj.append( "\n" );
+ if( tiling.m_aTransform.matrix[0] != 1.0 ||
+ tiling.m_aTransform.matrix[1] != 0.0 ||
+ tiling.m_aTransform.matrix[3] != 0.0 ||
+ tiling.m_aTransform.matrix[4] != 1.0 ||
+ tiling.m_aTransform.matrix[2] != 0.0 ||
+ tiling.m_aTransform.matrix[5] != 0.0 )
+ {
+ aTilingObj.append( "/Matrix [" );
+ // TODO: scaling, mirroring on y, etc
+ appendDouble( tiling.m_aTransform.matrix[0], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[1], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[3], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[4], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[2], aTilingObj );
+ aTilingObj.append( ' ' );
+ appendDouble( tiling.m_aTransform.matrix[5], aTilingObj );
+ aTilingObj.append( "]\n" );
+ }
+ aTilingObj.append( "/Resources" );
+ tiling.m_aResources.append( aTilingObj, getFontDictObject() );
+ if( bDeflate )
+ aTilingObj.append( "/Filter/FlateDecode" );
+ aTilingObj.append( "/Length "
+ + OString::number(static_cast<sal_Int32>(nTilingStreamSize))
+ + ">>\nstream\n" );
+ if ( !updateObject( tiling.m_nObject ) ) return false;
+ if ( !writeBuffer( aTilingObj ) ) return false;
+ checkAndEnableStreamEncryption( tiling.m_nObject );
+ bool written = writeBufferBytes( tiling.m_pTilingStream->GetData(), nTilingStreamSize );
+ tiling.m_pTilingStream.reset();
+ if( !written )
+ return false;
+ disableStreamEncryption();
+ aTilingObj.setLength( 0 );
+ aTilingObj.append( "\nendstream\nendobj\n\n" );
+ if ( !writeBuffer( aTilingObj ) ) return false;
+ }
+ return true;
+}
+
+sal_Int32 PDFWriterImpl::emitBuildinFont(const pdf::BuildinFontFace* pFD, sal_Int32 nFontObject)
+{
+ if( !pFD )
+ return 0;
+ const pdf::BuildinFont& rBuildinFont = pFD->GetBuildinFont();
+
+ OStringBuffer aLine( 1024 );
+
+ if( nFontObject <= 0 )
+ nFontObject = createObject();
+ CHECK_RETURN( updateObject( nFontObject ) );
+ aLine.append(
+ OString::number(nFontObject)
+ + " 0 obj\n"
+ "<</Type/Font/Subtype/Type1/BaseFont/" );
+ appendName( rBuildinFont.m_pPSName, aLine );
+ aLine.append( "\n" );
+ if( rBuildinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 )
+ aLine.append( "/Encoding/WinAnsiEncoding\n" );
+ aLine.append( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ return nFontObject;
+}
+
+namespace
+{
+// Translate units from TT to PS (standard 1/1000)
+int XUnits(int nUPEM, int n) { return (n * 1000) / nUPEM; }
+}
+
+std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const vcl::font::PhysicalFontFace* pFace, EmbedFont const & rEmbed )
+{
+ std::map< sal_Int32, sal_Int32 > aRet;
+
+ if (g_bDebugDisableCompression)
+ emitComment("PDFWriterImpl::emitSystemFont");
+
+ FontSubsetInfo aInfo;
+ // fill in dummy values
+ aInfo.m_nAscent = 1000;
+ aInfo.m_nDescent = 200;
+ aInfo.m_nCapHeight = 1000;
+ aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) );
+ aInfo.m_aPSName = pFace->GetFamilyName();
+
+ sal_Int32 pWidths[256] = { 0 };
+ const LogicalFontInstance* pFontInstance = rEmbed.m_pFontInstance;
+ auto nUPEM = pFace->UnitsPerEm();
+ for( sal_Ucs c = 32; c < 256; c++ )
+ {
+ sal_GlyphId nGlyph = pFontInstance->GetGlyphIndex(c);
+ pWidths[c] = XUnits(nUPEM, pFontInstance->GetGlyphWidth(nGlyph, false, false));
+ }
+
+ // We are interested only in filling aInfo
+ sal_GlyphId aGlyphIds[] = { 0 };
+ sal_uInt8 pEncoding[] = { 0 };
+ std::vector<sal_uInt8> aBuffer;
+ pFace->CreateFontSubset(aBuffer, aGlyphIds, pEncoding, 1, aInfo);
+
+ // write font descriptor
+ sal_Int32 nFontDescriptor = emitFontDescriptor( pFace, aInfo, 0, 0 );
+ if( nFontDescriptor )
+ {
+ // write font object
+ sal_Int32 nObject = createObject();
+ if( updateObject( nObject ) )
+ {
+ OStringBuffer aLine( 1024 );
+ aLine.append(
+ OString::number(nObject)
+ + " 0 obj\n"
+ "<</Type/Font/Subtype/TrueType"
+ "/BaseFont/" );
+ appendName( aInfo.m_aPSName, aLine );
+ aLine.append( "\n" );
+ if (!pFace->IsMicrosoftSymbolEncoded())
+ aLine.append( "/Encoding/WinAnsiEncoding\n" );
+ aLine.append( "/FirstChar 32 /LastChar 255\n"
+ "/Widths[" );
+ for( int i = 32; i < 256; i++ )
+ {
+ aLine.append( pWidths[i] );
+ aLine.append( ((i&15) == 15) ? "\n" : " " );
+ }
+ aLine.append( "]\n"
+ "/FontDescriptor "
+ + OString::number( nFontDescriptor )
+ + " 0 R>>\n"
+ "endobj\n\n" );
+ writeBuffer( aLine );
+
+ aRet[ rEmbed.m_nNormalFontID ] = nObject;
+ }
+ }
+
+ return aRet;
+}
+
+namespace
+{
+uint32_t fillSubsetArrays(const FontEmit& rSubset, sal_GlyphId* pGlyphIds, sal_Int32* pWidths,
+ sal_uInt8* pEncoding, sal_Int32* pEncToUnicodeIndex,
+ sal_Int32* pCodeUnitsPerGlyph, std::vector<sal_Ucs>& rCodeUnits,
+ sal_Int32& nToUnicodeStream)
+{
+ rCodeUnits.reserve(256);
+
+ // if it gets used then it will appear in s_subset.m_aMapping, otherwise 0 is fine
+ pWidths[0] = 0;
+
+ uint32_t nGlyphs = 1;
+ for (auto const& item : rSubset.m_aMapping)
+ {
+ sal_uInt8 nEnc = item.second.getGlyphId();
+
+ SAL_WARN_IF(pGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter",
+ "duplicate glyph");
+ SAL_WARN_IF(nEnc > rSubset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding");
+
+ pGlyphIds[nEnc] = item.first;
+ pEncoding[nEnc] = nEnc;
+ pEncToUnicodeIndex[nEnc] = static_cast<sal_Int32>(rCodeUnits.size());
+ pCodeUnitsPerGlyph[nEnc] = item.second.countCodes();
+ pWidths[nEnc] = item.second.getGlyphWidth();
+ for (sal_Int32 n = 0; n < pCodeUnitsPerGlyph[nEnc]; n++)
+ rCodeUnits.push_back(item.second.getCode(n));
+ if (item.second.getCode(0))
+ nToUnicodeStream = 1;
+ if (nGlyphs < 256)
+ nGlyphs++;
+ else
+ OSL_FAIL("too many glyphs for subset");
+ }
+
+ return nGlyphs;
+}
+}
+
+bool PDFWriterImpl::emitType3Font(const vcl::font::PhysicalFontFace* pFace,
+ const FontSubset& rType3Font,
+ std::map<sal_Int32, sal_Int32>& rFontIDToObject)
+{
+ if (g_bDebugDisableCompression)
+ emitComment("PDFWriterImpl::emitType3Font");
+
+ const auto& rColorPalettes = pFace->GetColorPalettes();
+
+ FontSubsetInfo aSubsetInfo;
+ sal_GlyphId pTempGlyphIds[] = { 0 };
+ sal_uInt8 pTempEncoding[] = { 0 };
+ std::vector<sal_uInt8> aBuffer;
+ pFace->CreateFontSubset(aBuffer, pTempGlyphIds, pTempEncoding, 1, aSubsetInfo);
+
+ for (auto& rSubset : rType3Font.m_aSubsets)
+ {
+ sal_GlyphId pGlyphIds[256] = {};
+ sal_Int32 pWidths[256];
+ sal_uInt8 pEncoding[256] = {};
+ sal_Int32 pEncToUnicodeIndex[256] = {};
+ sal_Int32 pCodeUnitsPerGlyph[256] = {};
+ std::vector<sal_Ucs> aCodeUnits;
+ sal_Int32 nToUnicodeStream = 0;
+
+ // fill arrays and prepare encoding index map
+ auto nGlyphs = fillSubsetArrays(rSubset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
+ pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
+
+ // write font descriptor
+ sal_Int32 nFontDescriptor = 0;
+ if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4)
+ nFontDescriptor = emitFontDescriptor(pFace, aSubsetInfo, rSubset.m_nFontID, 0);
+
+ if (nToUnicodeStream)
+ nToUnicodeStream = createToUnicodeCMap(pEncoding, aCodeUnits, pCodeUnitsPerGlyph,
+ pEncToUnicodeIndex, nGlyphs);
+
+ // write font object
+ sal_Int32 nFontObject = createObject();
+ if (!updateObject(nFontObject))
+ return false;
+
+ OStringBuffer aLine(1024);
+ aLine.append(
+ OString::number(nFontObject)
+ + " 0 obj\n"
+ "<</Type/Font/Subtype/Type3/Name/");
+ appendName(aSubsetInfo.m_aPSName, aLine);
+
+ aLine.append(
+ "\n/FontBBox["
+ // note: Top and Bottom are reversed in VCL and PDF rectangles
+ + OString::number(aSubsetInfo.m_aFontBBox.Left())
+ + " "
+ + OString::number(aSubsetInfo.m_aFontBBox.Top())
+ + " "
+ + OString::number(aSubsetInfo.m_aFontBBox.Right())
+ + " "
+ + OString::number(aSubsetInfo.m_aFontBBox.Bottom() + 1)
+ + "]\n");
+
+ // tdf#155610
+ // Adobe Acrobat does not seem to like certain UPEMs, so instead of
+ // setting the FontMatrix scale relative to the UPEM, we always set to
+ // 0.001 (1000 UPEM) and scale everything if the font’s UPEM is
+ // different.
+ double fScale = 1000. / pFace->UnitsPerEm();
+
+ aLine.append("/FontMatrix[0.001 0 0 0.001 0 0]\n");
+
+ sal_Int32 pGlyphStreams[256] = {};
+ aLine.append("/CharProcs<<\n");
+ for (auto i = 1u; i < nGlyphs; i++)
+ {
+ auto nStream = createObject();
+ aLine.append("/"
+ + pFace->GetGlyphName(pGlyphIds[i], true)
+ + " "
+ + OString::number(nStream)
+ + " 0 R\n");
+ pGlyphStreams[i] = nStream;
+ }
+ aLine.append(">>\n"
+
+ "/Encoding<</Type/Encoding/Differences[1");
+ for (auto i = 1u; i < nGlyphs; i++)
+ aLine.append(" /" + pFace->GetGlyphName(pGlyphIds[i], true));
+ aLine.append("]>>\n"
+
+ "/FirstChar 0\n"
+ "/LastChar "
+ + OString::number(nGlyphs - 1)
+ + "\n"
+
+ "/Widths[");
+ for (auto i = 0u; i < nGlyphs; i++)
+ {
+ appendDouble(pWidths[i] * fScale, aLine);
+ aLine.append(" ");
+ }
+ aLine.append("]\n");
+
+ if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4)
+ {
+ aLine.append("/FontDescriptor " + OString::number(nFontDescriptor) + " 0 R\n");
+ }
+
+ auto nResources = createObject();
+ aLine.append("/Resources " + OString::number(nResources) + " 0 R\n");
+
+ if (nToUnicodeStream)
+ {
+ aLine.append("/ToUnicode " + OString::number(nToUnicodeStream) + " 0 R\n");
+ }
+
+ aLine.append(">>\n"
+ "endobj\n\n");
+
+ if (!writeBuffer(aLine))
+ return false;
+
+ std::set<sal_Int32> aUsedFonts;
+ std::list<BitmapEmit> aUsedBitmaps;
+ std::map<sal_uInt8, sal_Int32> aUsedAlpha;
+ ResourceDict aResourceDict;
+ std::list<StreamRedirect> aOutputStreams;
+
+ // Scale for glyph outlines.
+ double fScaleX = (GetDPIX() / 72.) * fScale;
+ double fScaleY = (GetDPIY() / 72.) * fScale;
+
+ for (auto i = 1u; i < nGlyphs; i++)
+ {
+ auto nStream = pGlyphStreams[i];
+ if (!updateObject(nStream))
+ return false;
+ OStringBuffer aContents(1024);
+ appendDouble(pWidths[i] * fScale, aContents);
+ aContents.append(" 0 d0\n");
+
+ const auto& rGlyph = rSubset.m_aMapping.find(pGlyphIds[i])->second;
+ const auto& rLayers = rGlyph.getColorLayers();
+ for (const auto& rLayer : rLayers)
+ {
+ aUsedFonts.insert(rLayer.m_nFontID);
+
+ aContents.append("q ");
+ // 0xFFFF is a special value means foreground color.
+ if (rLayer.m_nColorIndex != 0xFFFF)
+ {
+ auto& rPalette = rColorPalettes[0];
+ auto aColor(rPalette[rLayer.m_nColorIndex]);
+ appendNonStrokingColor(aColor, aContents);
+ aContents.append(" ");
+ if (aColor.GetAlpha() != 0xFF
+ && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4)
+ {
+ auto nAlpha = aColor.GetAlpha();
+ OStringBuffer aName(16);
+ aName.append("GS");
+ appendHex(nAlpha, aName);
+
+ aContents.append("/" + aName + " gs ");
+
+ if (aUsedAlpha.find(nAlpha) == aUsedAlpha.end())
+ {
+ auto nObject = createObject();
+ aUsedAlpha[nAlpha] = nObject;
+ pushResource(ResourceKind::ExtGState, aName.makeStringAndClear(),
+ nObject, aResourceDict, aOutputStreams);
+ }
+ }
+ }
+ aContents.append(
+ "BT "
+ "/F" + OString::number(rLayer.m_nFontID) + " ");
+ appendDouble(pFace->UnitsPerEm() * fScale, aContents);
+ aContents.append(
+ " Tf "
+ "<");
+ appendHex(rLayer.m_nSubsetGlyphID, aContents);
+ aContents.append(
+ ">Tj "
+ "ET "
+ "Q\n");
+ }
+
+ tools::Rectangle aRect;
+ const auto& rBitmapData = rGlyph.getColorBitmap(aRect);
+ if (!rBitmapData.empty())
+ {
+ SvMemoryStream aStream(const_cast<uint8_t*>(rBitmapData.data()), rBitmapData.size(),
+ StreamMode::READ);
+ vcl::PngImageReader aReader(aStream);
+
+ // When rendering an image with an alpha mask during PDF
+ // export, the alpha mask needs to be inverted
+ BitmapEx aBitmapEx = aReader.read();
+ if ( aBitmapEx.IsAlpha())
+ {
+ AlphaMask aAlpha = aBitmapEx.GetAlphaMask();
+ aAlpha.Invert();
+ aBitmapEx = BitmapEx(aBitmapEx.GetBitmap(), aAlpha);
+ }
+
+ auto aBitmapEmit = createBitmapEmit(aBitmapEx, Graphic(),
+ aUsedBitmaps, aResourceDict, aOutputStreams);
+
+ auto nObject = aBitmapEmit.m_aReferenceXObject.getObject();
+ aContents.append("q ");
+ appendDouble(aRect.GetWidth() * fScale, aContents);
+ aContents.append(" 0 0 ");
+ appendDouble(aRect.GetHeight() * fScale, aContents);
+ aContents.append(
+ + " "
+ + OString::number(aRect.getX())
+ + " "
+ + OString::number(aRect.getY())
+ + " cm "
+ "/Im"
+ + OString::number(nObject)
+ + " Do Q\n");
+ }
+
+ const auto& rOutline = rGlyph.getOutline();
+ if (rOutline.count())
+ {
+ aContents.append("q ");
+ appendDouble(fScaleX, aContents);
+ aContents.append(" 0 0 ");
+ appendDouble(fScaleY, aContents);
+ aContents.append(" 0 ");
+ appendDouble(m_aPages.back().getHeight() * -fScaleY, aContents, 3);
+ aContents.append(" cm\n");
+ m_aPages.back().appendPolyPolygon(rOutline, aContents);
+ aContents.append("f\n"
+ "Q\n");
+ }
+
+ aLine.setLength(0);
+ aLine.append(OString::number(nStream)
+ + " 0 obj\n<</Length "
+ + OString::number(aContents.getLength())
+ + ">>\nstream\n");
+ if (!writeBuffer(aLine))
+ return false;
+ if (!writeBuffer(aContents))
+ return false;
+ aLine.setLength(0);
+ aLine.append("endstream\nendobj\n\n");
+ if (!writeBuffer(aLine))
+ return false;
+ }
+
+ // write font dict
+ sal_Int32 nFontDict = 0;
+ if (!aUsedFonts.empty())
+ {
+ nFontDict = createObject();
+ aLine.setLength(0);
+ aLine.append(OString::number(nFontDict) + " 0 obj\n<<");
+ for (auto nFontID : aUsedFonts)
+ {
+ aLine.append("/F"
+ + OString::number(nFontID)
+ + " "
+ + OString::number(rFontIDToObject[nFontID])
+ + " 0 R");
+ }
+ aLine.append(">>\nendobj\n\n");
+ if (!updateObject(nFontDict))
+ return false;
+ if (!writeBuffer(aLine))
+ return false;
+ }
+
+ // write ExtGState objects
+ if (!aUsedAlpha.empty())
+ {
+ for (const auto & [ nAlpha, nObject ] : aUsedAlpha)
+ {
+ aLine.setLength(0);
+ aLine.append(OString::number(nObject) + " 0 obj\n<<");
+ if (m_bIsPDF_A1)
+ {
+ aLine.append("/CA 1.0/ca 1.0");
+ m_aErrors.insert(PDFWriter::Warning_Transparency_Omitted_PDFA);
+ }
+ else
+ {
+ aLine.append("/CA ");
+ appendDouble(nAlpha / 255., aLine);
+ aLine.append("/ca ");
+ appendDouble(nAlpha / 255., aLine);
+ }
+ aLine.append(">>\nendobj\n\n");
+ if (!updateObject(nObject))
+ return false;
+ if (!writeBuffer(aLine))
+ return false;
+ }
+ }
+
+ // write bitmap objects
+ for (auto& aBitmap : aUsedBitmaps)
+ writeBitmapObject(aBitmap);
+
+ // write resources dict
+ aLine.setLength(0);
+ aLine.append(OString::number(nResources) + " 0 obj\n");
+ aResourceDict.append(aLine, nFontDict);
+ aLine.append("endobj\n\n");
+ if (!updateObject(nResources))
+ return false;
+ if (!writeBuffer(aLine))
+ return false;
+
+ rFontIDToObject[rSubset.m_nFontID] = nFontObject;
+ }
+
+ return true;
+}
+
+typedef int ThreeInts[3];
+static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen,
+ ThreeInts& rSegmentLengths )
+{
+ if( !pFontBytes || (nByteLen < 0) )
+ return false;
+ const unsigned char* pPtr = pFontBytes;
+ const unsigned char* pEnd = pFontBytes + nByteLen;
+
+ for(int & rSegmentLength : rSegmentLengths) {
+ // read segment1 header
+ if( pPtr+6 >= pEnd )
+ return false;
+ if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) )
+ return false;
+ const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2];
+ if( nLen <= 0)
+ return false;
+ rSegmentLength = nLen;
+ pPtr += nLen + 6;
+ }
+
+ // read segment-end header
+ if( pPtr+2 >= pEnd )
+ return false;
+ if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) )
+ return false;
+
+ return true;
+}
+
+static void appendSubsetName( int nSubsetID, std::u16string_view rPSName, OStringBuffer& rBuffer )
+{
+ if( nSubsetID )
+ {
+ for( int i = 0; i < 6; i++ )
+ {
+ int nOffset = nSubsetID % 26;
+ nSubsetID /= 26;
+ rBuffer.append( static_cast<char>('A'+nOffset) );
+ }
+ rBuffer.append( '+' );
+ }
+ appendName( rPSName, rBuffer );
+}
+
+sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8 const * pEncoding,
+ const std::vector<sal_Ucs>& rCodeUnits,
+ const sal_Int32* pCodeUnitsPerGlyph,
+ const sal_Int32* pEncToUnicodeIndex,
+ uint32_t nGlyphs )
+{
+ int nMapped = 0;
+ for (auto n = 0u; n < nGlyphs; ++n)
+ if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]])
+ nMapped++;
+
+ if( nMapped == 0 )
+ return 0;
+
+ sal_Int32 nStream = createObject();
+ CHECK_RETURN( updateObject( nStream ) );
+
+ OStringBuffer aContents( 1024 );
+ aContents.append(
+ "/CIDInit/ProcSet findresource begin\n"
+ "12 dict begin\n"
+ "begincmap\n"
+ "/CIDSystemInfo<<\n"
+ "/Registry (Adobe)\n"
+ "/Ordering (UCS)\n"
+ "/Supplement 0\n"
+ ">> def\n"
+ "/CMapName/Adobe-Identity-UCS def\n"
+ "/CMapType 2 def\n"
+ "1 begincodespacerange\n"
+ "<00> <FF>\n"
+ "endcodespacerange\n"
+ );
+ int nCount = 0;
+ for (auto n = 0u; n < nGlyphs; ++n)
+ {
+ if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]])
+ {
+ if( (nCount % 100) == 0 )
+ {
+ if( nCount )
+ aContents.append( "endbfchar\n" );
+ aContents.append( OString::number(static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) )
+ + " beginbfchar\n" );
+ }
+ aContents.append( '<' );
+ appendHex( static_cast<sal_Int8>(pEncoding[n]), aContents );
+ aContents.append( "> <" );
+ // TODO: handle code points>U+FFFF
+ sal_Int32 nIndex = pEncToUnicodeIndex[n];
+ for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ )
+ {
+ appendHex( static_cast<sal_Int8>(rCodeUnits[nIndex + j] / 256), aContents );
+ appendHex( static_cast<sal_Int8>(rCodeUnits[nIndex + j] & 255), aContents );
+ }
+ aContents.append( ">\n" );
+ nCount++;
+ }
+ }
+ aContents.append( "endbfchar\n"
+ "endcmap\n"
+ "CMapName currentdict /CMap defineresource pop\n"
+ "end\n"
+ "end\n" );
+ SvMemoryStream aStream;
+ if (!g_bDebugDisableCompression)
+ {
+ ZCodec aCodec( 0x4000, 0x4000 );
+ aCodec.BeginCompression();
+ aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() );
+ aCodec.EndCompression();
+ }
+
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::createToUnicodeCMap" );
+ }
+ OStringBuffer aLine( 40 );
+
+ aLine.append( OString::number(nStream ) + " 0 obj\n<</Length " );
+ sal_uInt64 nLen = 0;
+ if (!g_bDebugDisableCompression)
+ {
+ nLen = aStream.Tell();
+ aStream.Seek( 0 );
+ aLine.append( OString::number(nLen) + "/Filter/FlateDecode" );
+ }
+ else
+ aLine.append( aContents.getLength() );
+ aLine.append( ">>\nstream\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ checkAndEnableStreamEncryption( nStream );
+ if (!g_bDebugDisableCompression)
+ {
+ CHECK_RETURN( writeBufferBytes( aStream.GetData(), nLen ) );
+ }
+ else
+ {
+ CHECK_RETURN( writeBuffer( aContents ) );
+ }
+ disableStreamEncryption();
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\n"
+ "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ return nStream;
+}
+
+sal_Int32 PDFWriterImpl::emitFontDescriptor( const vcl::font::PhysicalFontFace* pFace, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream )
+{
+ OStringBuffer aLine( 1024 );
+ // get font flags, see PDF reference 1.4 p. 358
+ // possibly characters outside Adobe standard encoding
+ // so set Symbolic flag
+ sal_Int32 nFontFlags = (1<<2);
+ if( pFace->GetItalic() == ITALIC_NORMAL || pFace->GetItalic() == ITALIC_OBLIQUE )
+ nFontFlags |= (1 << 6);
+ if( pFace->GetPitch() == PITCH_FIXED )
+ nFontFlags |= 1;
+ if( pFace->GetFamilyType() == FAMILY_SCRIPT )
+ nFontFlags |= (1 << 3);
+ else if( pFace->GetFamilyType() == FAMILY_ROMAN )
+ nFontFlags |= (1 << 1);
+
+ sal_Int32 nFontDescriptor = createObject();
+ CHECK_RETURN( updateObject( nFontDescriptor ) );
+ aLine.setLength( 0 );
+ aLine.append(
+ OString::number(nFontDescriptor)
+ + " 0 obj\n"
+ "<</Type/FontDescriptor/FontName/" );
+ appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
+ aLine.append( "\n"
+ "/Flags "
+ + OString::number( nFontFlags )
+ + "\n"
+ "/FontBBox["
+ // note: Top and Bottom are reversed in VCL and PDF rectangles
+ + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Left()) )
+ + " "
+ + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Top()) )
+ + " "
+ + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Right()) )
+ + " "
+ + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Bottom()+1) )
+ + "]/ItalicAngle " );
+ if( pFace->GetItalic() == ITALIC_OBLIQUE || pFace->GetItalic() == ITALIC_NORMAL )
+ aLine.append( "-30" );
+ else
+ aLine.append( "0" );
+ aLine.append( "\n"
+ "/Ascent "
+ + OString::number( static_cast<sal_Int32>(rInfo.m_nAscent) )
+ + "\n"
+ "/Descent "
+ + OString::number( static_cast<sal_Int32>(-rInfo.m_nDescent) )
+ + "\n"
+ "/CapHeight "
+ + OString::number( static_cast<sal_Int32>(rInfo.m_nCapHeight) )
+ // According to PDF reference 1.4 StemV is required
+ // seems a tad strange to me, but well ...
+ + "\n"
+ "/StemV 80\n" );
+ if( nFontStream )
+ {
+ aLine.append( "/FontFile" );
+ switch( rInfo.m_nFontType )
+ {
+ case FontType::SFNT_TTF:
+ aLine.append( '2' );
+ break;
+ case FontType::TYPE1_PFA:
+ case FontType::TYPE1_PFB:
+ case FontType::ANY_TYPE1:
+ break;
+ default:
+ OSL_FAIL( "unknown fonttype in PDF font descriptor" );
+ return 0;
+ }
+ aLine.append( " " + OString::number(nFontStream) + " 0 R\n" );
+ }
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+
+ return nFontDescriptor;
+}
+
+void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const
+{
+ for (auto const& item : m_aBuildinFontToObjectMap)
+ {
+ rDict.append( pdf::BuildinFontFace::Get(item.first).getNameObject() );
+ rDict.append( ' ' );
+ rDict.append( item.second );
+ rDict.append( " 0 R" );
+ }
+}
+
+bool PDFWriterImpl::emitFonts()
+{
+ OStringBuffer aLine( 1024 );
+
+ std::map< sal_Int32, sal_Int32 > aFontIDToObject;
+
+ for (const auto & subset : m_aSubsets)
+ {
+ for (auto & s_subset :subset.second.m_aSubsets)
+ {
+ sal_GlyphId pGlyphIds[ 256 ] = {};
+ sal_Int32 pWidths[ 256 ];
+ sal_uInt8 pEncoding[ 256 ] = {};
+ sal_Int32 pEncToUnicodeIndex[ 256 ] = {};
+ sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {};
+ std::vector<sal_Ucs> aCodeUnits;
+ sal_Int32 nToUnicodeStream = 0;
+
+ // fill arrays and prepare encoding index map
+ auto nGlyphs = fillSubsetArrays(s_subset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
+ pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
+
+ std::vector<sal_uInt8> aBuffer;
+ FontSubsetInfo aSubsetInfo;
+ const auto* pFace = subset.first;
+ if (pFace->CreateFontSubset(aBuffer, pGlyphIds, pEncoding, nGlyphs, aSubsetInfo))
+ {
+ // create font stream
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::emitFonts" );
+ }
+ sal_Int32 nFontStream = createObject();
+ sal_Int32 nStreamLengthObject = createObject();
+ if ( !updateObject( nFontStream ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( OString::number(nFontStream)
+ + " 0 obj\n"
+ "<</Length "
+ + OString::number( nStreamLengthObject ) );
+ if (!g_bDebugDisableCompression)
+ aLine.append( " 0 R"
+ "/Filter/FlateDecode"
+ "/Length1 " );
+ else
+ aLine.append( " 0 R"
+ "/Length1 " );
+
+ sal_uInt64 nStartPos = 0;
+ if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF )
+ {
+ aLine.append( OString::number(static_cast<sal_Int32>(aBuffer.size()))
+ + ">>\n"
+ "stream\n" );
+ if ( !writeBuffer( aLine ) ) return false;
+ if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
+
+ // copy font file
+ beginCompression();
+ checkAndEnableStreamEncryption( nFontStream );
+ if (!writeBufferBytes(aBuffer.data(), aBuffer.size()))
+ return false;
+ }
+ else if( aSubsetInfo.m_nFontType & FontType::CFF_FONT)
+ {
+ // TODO: implement
+ OSL_FAIL( "PDFWriterImpl does not support CFF-font subsets yet!" );
+ }
+ else if( aSubsetInfo.m_nFontType & FontType::TYPE1_PFB) // TODO: also support PFA?
+ {
+ // get the PFB-segment lengths
+ ThreeInts aSegmentLengths = {0,0,0};
+ getPfbSegmentLengths(aBuffer.data(), static_cast<int>(aBuffer.size()), aSegmentLengths);
+ // the lengths below are mandatory for PDF-exported Type1 fonts
+ // because the PFB segment headers get stripped! WhyOhWhy.
+ aLine.append( OString::number(static_cast<sal_Int32>(aSegmentLengths[0]) )
+ + "/Length2 "
+ + OString::number( static_cast<sal_Int32>(aSegmentLengths[1]) )
+ + "/Length3 "
+ + OString::number( static_cast<sal_Int32>(aSegmentLengths[2]) )
+ + ">>\n"
+ "stream\n" );
+ if ( !writeBuffer( aLine ) ) return false;
+ if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
+
+ // emit PFB-sections without section headers
+ beginCompression();
+ checkAndEnableStreamEncryption( nFontStream );
+ if ( !writeBufferBytes( &aBuffer[6], aSegmentLengths[0] ) ) return false;
+ if ( !writeBufferBytes( &aBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false;
+ if ( !writeBufferBytes( &aBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ) return false;
+ }
+ else
+ {
+ SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType));
+ aLine.append( "0 >>\nstream\n" );
+ }
+
+ endCompression();
+ disableStreamEncryption();
+
+ sal_uInt64 nEndPos = 0;
+ if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false;
+ // end the stream
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\nendobj\n\n" );
+ if ( !writeBuffer( aLine ) ) return false;
+
+ // emit stream length object
+ if ( !updateObject( nStreamLengthObject ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( OString::number(nStreamLengthObject)
+ + " 0 obj\n"
+ + OString::number( static_cast<sal_Int64>(nEndPos-nStartPos) )
+ + "\nendobj\n\n" );
+ if ( !writeBuffer( aLine ) ) return false;
+
+ // write font descriptor
+ sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream );
+
+ if( nToUnicodeStream )
+ nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits, pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
+
+ sal_Int32 nFontObject = createObject();
+ if ( !updateObject( nFontObject ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( OString::number(nFontObject) + " 0 obj\n" );
+ aLine.append( (aSubsetInfo.m_nFontType & FontType::ANY_TYPE1) ?
+ "<</Type/Font/Subtype/Type1/BaseFont/" :
+ "<</Type/Font/Subtype/TrueType/BaseFont/" );
+ appendSubsetName( s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine );
+ aLine.append( "\n"
+ "/FirstChar 0\n"
+ "/LastChar "
+ + OString::number( static_cast<sal_Int32>(nGlyphs-1) )
+ + "\n"
+ "/Widths[" );
+ for (auto i = 0u; i < nGlyphs; i++)
+ {
+ aLine.append( pWidths[ i ] );
+ aLine.append( ((i & 15) == 15) ? "\n" : " " );
+ }
+ aLine.append( "]\n"
+ "/FontDescriptor "
+ + OString::number( nFontDescriptor )
+ + " 0 R\n" );
+ if( nToUnicodeStream )
+ {
+ aLine.append( "/ToUnicode "
+ + OString::number( nToUnicodeStream )
+ + " 0 R\n" );
+ }
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ if ( !writeBuffer( aLine ) ) return false;
+
+ aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
+ }
+ else
+ {
+ OStringBuffer aErrorComment( 256 );
+ aErrorComment.append( "CreateFontSubset failed for font \""
+ + OUStringToOString( pFace->GetFamilyName(), RTL_TEXTENCODING_UTF8 )
+ + "\"" );
+ if( pFace->GetItalic() == ITALIC_NORMAL )
+ aErrorComment.append( " italic" );
+ else if( pFace->GetItalic() == ITALIC_OBLIQUE )
+ aErrorComment.append( " oblique" );
+ aErrorComment.append( " weight=" + OString::number( sal_Int32(pFace->GetWeight()) ) );
+ emitComment( aErrorComment.getStr() );
+ }
+ }
+ }
+
+ // emit system fonts
+ for (auto const& systemFont : m_aSystemFonts)
+ {
+ std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second );
+ for (auto const& item : aObjects)
+ {
+ if ( !item.second ) return false;
+ aFontIDToObject[ item.first ] = item.second;
+ }
+ }
+
+ // emit Type3 fonts
+ for (auto const& it : m_aType3Fonts)
+ {
+ if (!emitType3Font(it.first, it.second, aFontIDToObject))
+ return false;
+ }
+
+ OStringBuffer aFontDict( 1024 );
+ aFontDict.append( OString::number(getFontDictObject())
+ + " 0 obj\n"
+ "<<" );
+ int ni = 0;
+ for (auto const& itemMap : aFontIDToObject)
+ {
+ aFontDict.append( "/F"
+ + OString::number( itemMap.first )
+ + " "
+ + OString::number( itemMap.second )
+ + " 0 R" );
+ if( ((++ni) & 7) == 0 )
+ aFontDict.append( '\n' );
+ }
+ // emit builtin font for widget appearances / variable text
+ for (auto & item : m_aBuildinFontToObjectMap)
+ {
+ rtl::Reference<pdf::BuildinFontFace> aData(new pdf::BuildinFontFace(item.first));
+ item.second = emitBuildinFont( aData.get(), item.second );
+ }
+
+ appendBuildinFontsToDict(aFontDict);
+ aFontDict.append( "\n>>\nendobj\n\n" );
+
+ if ( !updateObject( getFontDictObject() ) ) return false;
+ if ( !writeBuffer( aFontDict ) ) return false;
+ return true;
+}
+
+sal_Int32 PDFWriterImpl::emitResources()
+{
+ // emit shadings
+ if( ! m_aGradients.empty() )
+ CHECK_RETURN( emitGradients() );
+ // emit tilings
+ if( ! m_aTilings.empty() )
+ CHECK_RETURN( emitTilings() );
+
+ // emit font dict
+ CHECK_RETURN( emitFonts() );
+
+ // emit Resource dict
+ OStringBuffer aLine( 512 );
+ sal_Int32 nResourceDict = getResourceDictObj();
+ CHECK_RETURN( updateObject( nResourceDict ) );
+ aLine.setLength( 0 );
+ aLine.append( OString::number(nResourceDict)
+ + " 0 obj\n" );
+ m_aGlobalResourceDict.append( aLine, getFontDictObject() );
+ aLine.append( "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ return nResourceDict;
+}
+
+sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
+ sal_Int32 nItemLevel,
+ sal_Int32 nCurrentItemId )
+{
+ /* The /Count number of an item is
+ positive: the number of visible subitems
+ negative: the negative number of subitems that will become visible if
+ the item gets opened
+ see PDF ref 1.4 p 478
+ */
+
+ sal_Int32 nCount = 0;
+
+ if( m_aContext.OpenBookmarkLevels < 0 || // all levels are visible
+ m_aContext.OpenBookmarkLevels >= nItemLevel // this level is visible
+ )
+ {
+ PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
+ sal_Int32 nChildren = rItem.m_aChildren.size();
+ for( sal_Int32 i = 0; i < nChildren; i++ )
+ nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
+ rCounts[nCurrentItemId] = nCount;
+ // return 1 (this item) + visible sub items
+ if( nCount < 0 )
+ nCount = 0;
+ nCount++;
+ }
+ else
+ {
+ // this bookmark level is invisible
+ PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
+ sal_Int32 nChildren = rItem.m_aChildren.size();
+ rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size());
+ for( sal_Int32 i = 0; i < nChildren; i++ )
+ updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
+ nCount = -1;
+ }
+
+ return nCount;
+}
+
+sal_Int32 PDFWriterImpl::emitOutline()
+{
+ int i, nItems = m_aOutline.size();
+
+ // do we have an outline at all ?
+ if( nItems < 2 )
+ return 0;
+
+ // reserve object numbers for all outline items
+ for( i = 0; i < nItems; ++i )
+ m_aOutline[i].m_nObject = createObject();
+
+ // update all parent, next and prev object ids
+ for( i = 0; i < nItems; ++i )
+ {
+ PDFOutlineEntry& rItem = m_aOutline[i];
+ int nChildren = rItem.m_aChildren.size();
+
+ if( nChildren )
+ {
+ for( int n = 0; n < nChildren; ++n )
+ {
+ PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
+
+ rChild.m_nParentObject = rItem.m_nObject;
+ rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
+ rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
+ }
+
+ }
+ }
+
+ // calculate Count entries for all items
+ std::vector< sal_Int32 > aCounts( nItems );
+ updateOutlineItemCount( aCounts, 0, 0 );
+
+ // emit hierarchy
+ for( i = 0; i < nItems; ++i )
+ {
+ PDFOutlineEntry& rItem = m_aOutline[i];
+ OStringBuffer aLine( 1024 );
+
+ CHECK_RETURN( updateObject( rItem.m_nObject ) );
+ aLine.append( OString::number(rItem.m_nObject)
+ + " 0 obj\n"
+ "<<" );
+ // number of visible children (all levels)
+ if( i > 0 || aCounts[0] > 0 )
+ {
+ aLine.append( "/Count " + OString::number( aCounts[i] ) );
+ }
+ if( ! rItem.m_aChildren.empty() )
+ {
+ // children list: First, Last
+ aLine.append( "/First "
+ + OString::number( m_aOutline[rItem.m_aChildren.front()].m_nObject )
+ + " 0 R/Last "
+ + OString::number( m_aOutline[rItem.m_aChildren.back()].m_nObject )
+ + " 0 R\n" );
+ }
+ if( i > 0 )
+ {
+ // Title, Dest, Parent, Prev, Next
+ aLine.append( "/Title" );
+ appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine );
+ aLine.append( "\n" );
+ // Dest is not required
+ if( rItem.m_nDestID >= 0 && o3tl::make_unsigned(rItem.m_nDestID) < m_aDests.size() )
+ {
+ aLine.append( "/Dest" );
+ appendDest( rItem.m_nDestID, aLine );
+ }
+ aLine.append( "/Parent "
+ + OString::number( rItem.m_nParentObject )
+ + " 0 R" );
+ if( rItem.m_nPrevObject )
+ {
+ aLine.append( "/Prev "
+ + OString::number( rItem.m_nPrevObject )
+ + " 0 R" );
+ }
+ if( rItem.m_nNextObject )
+ {
+ aLine.append( "/Next "
+ + OString::number( rItem.m_nNextObject )
+ + " 0 R" );
+ }
+ }
+ aLine.append( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ }
+
+ return m_aOutline[0].m_nObject;
+}
+
+#undef CHECK_RETURN
+#define CHECK_RETURN( x ) if( !x ) return false
+
+bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
+{
+ if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() )
+ {
+ SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << static_cast<int>(nDestID) << " requested");
+ return false;
+ }
+
+ const PDFDest& rDest = m_aDests[ nDestID ];
+ const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
+
+ rBuffer.append( '[' );
+ rBuffer.append( rDestPage.m_nPageObject );
+ rBuffer.append( " 0 R" );
+
+ switch( rDest.m_eType )
+ {
+ case PDFWriter::DestAreaType::XYZ:
+ default:
+ rBuffer.append( "/XYZ " );
+ appendFixedInt( rDest.m_aRect.Left(), rBuffer );
+ rBuffer.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
+ rBuffer.append( " 0" );
+ break;
+ case PDFWriter::DestAreaType::FitRectangle:
+ rBuffer.append( "/FitR " );
+ appendFixedInt( rDest.m_aRect.Left(), rBuffer );
+ rBuffer.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Top(), rBuffer );
+ rBuffer.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Right(), rBuffer );
+ rBuffer.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
+ break;
+ }
+ rBuffer.append( ']' );
+
+ return true;
+}
+
+void PDFWriterImpl::addDocumentAttachedFile(OUString const& rFileName, OUString const& rMimeType, OUString const& rDescription, std::unique_ptr<PDFOutputStream> rStream)
+{
+ sal_Int32 nObjectID = addEmbeddedFile(std::move(rStream), rMimeType);
+ auto& rAttachedFile = m_aDocumentAttachedFiles.emplace_back();
+ rAttachedFile.maFilename = rFileName;
+ rAttachedFile.maMimeType = rMimeType;
+ rAttachedFile.maDescription = rDescription;
+ rAttachedFile.mnEmbeddedFileObjectId = nObjectID;
+ rAttachedFile.mnObjectId = createObject();
+}
+
+sal_Int32 PDFWriterImpl::addEmbeddedFile(std::unique_ptr<PDFOutputStream> rStream, OUString const& rMimeType)
+{
+ sal_Int32 aObjectID = createObject();
+ auto& rEmbedded = m_aEmbeddedFiles.emplace_back();
+ rEmbedded.m_nObject = aObjectID;
+ rEmbedded.m_aSubType = rMimeType;
+ rEmbedded.m_pStream = std::move(rStream);
+ return aObjectID;
+}
+
+sal_Int32 PDFWriterImpl::addEmbeddedFile(BinaryDataContainer const & rDataContainer)
+{
+ sal_Int32 aObjectID = createObject();
+ m_aEmbeddedFiles.emplace_back();
+ m_aEmbeddedFiles.back().m_nObject = aObjectID;
+ m_aEmbeddedFiles.back().m_aDataContainer = rDataContainer;
+ return aObjectID;
+}
+
+bool PDFWriterImpl::emitScreenAnnotations()
+{
+ int nAnnots = m_aScreens.size();
+ for (int i = 0; i < nAnnots; i++)
+ {
+ const PDFScreen& rScreen = m_aScreens[i];
+
+ OStringBuffer aLine;
+ bool bEmbed = false;
+ if (!rScreen.m_aTempFileURL.isEmpty())
+ {
+ bEmbed = true;
+ if (!updateObject(rScreen.m_nTempFileObject))
+ continue;
+
+ SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ);
+ SvMemoryStream aMemoryStream;
+ aMemoryStream.WriteStream(aFileStream);
+
+ aLine.append(rScreen.m_nTempFileObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /EmbeddedFile /Length ");
+ aLine.append(static_cast<sal_Int64>(aMemoryStream.GetSize()));
+ aLine.append(" >>\nstream\n");
+ CHECK_RETURN(writeBuffer(aLine));
+ aLine.setLength(0);
+
+ CHECK_RETURN(writeBufferBytes(aMemoryStream.GetData(), aMemoryStream.GetSize()));
+
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine));
+ aLine.setLength(0);
+ }
+
+ if (!updateObject(rScreen.m_nObject))
+ continue;
+
+ // Annot dictionary.
+ aLine.append(OString::number(rScreen.m_nObject)
+ + " 0 obj\n"
+ "<</Type/Annot"
+ "/Subtype/Screen/Rect[");
+ appendFixedInt(rScreen.m_aRect.Left(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rScreen.m_aRect.Top(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rScreen.m_aRect.Right(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rScreen.m_aRect.Bottom(), aLine);
+ aLine.append("]");
+
+ // Action dictionary.
+ aLine.append("/A<</Type/Action /S/Rendition /AN "
+ + OString::number(rScreen.m_nObject)
+ + " 0 R ");
+
+ // Rendition dictionary.
+ aLine.append("/R<</Type/Rendition /S/MR ");
+
+ // MediaClip dictionary.
+ aLine.append("/C<</Type/MediaClip /S/MCD ");
+ if (bEmbed)
+ {
+ aLine.append("\n/D << /Type /Filespec /F (<embedded file>) ");
+ if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
+ { // ISO 14289-1:2014, Clause: 7.11
+ aLine.append("/UF (<embedded file>) ");
+ }
+ aLine.append("/EF << /F ");
+ aLine.append(rScreen.m_nTempFileObject);
+ aLine.append(" 0 R >>");
+ }
+ else
+ {
+ // Linked.
+ aLine.append("\n/D << /Type /Filespec /FS /URL /F ");
+ appendLiteralStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine, osl_getThreadTextEncoding());
+ if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
+ { // ISO 14289-1:2014, Clause: 7.11
+ aLine.append("/UF ");
+ appendUnicodeTextStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine);
+ }
+ }
+ if (PDFWriter::PDFVersion::PDF_1_6 <= m_aContext.Version
+ && !rScreen.m_AltText.isEmpty())
+ { // ISO 14289-1:2014, Clause: 7.11
+ aLine.append("/Desc ");
+ appendUnicodeTextStringEncrypt(rScreen.m_AltText, rScreen.m_nObject, aLine);
+ }
+ aLine.append(" >>\n"); // end of /D
+ // Allow playing the video via a tempfile.
+ aLine.append("/P <</TF (TEMPACCESS)>>");
+ // ISO 14289-1:2014, Clause: 7.18.6.2
+ aLine.append("/CT ");
+ appendLiteralStringEncrypt(rScreen.m_MimeType, rScreen.m_nObject, aLine);
+ // ISO 14289-1:2014, Clause: 7.18.6.2
+ // Alt text is a "Multi-language Text Array"
+ aLine.append(" /Alt [ () ");
+ appendUnicodeTextStringEncrypt(rScreen.m_AltText, rScreen.m_nObject, aLine);
+ aLine.append(" ] "
+ ">>");
+
+ // End Rendition dictionary by requesting play/pause/stop controls.
+ aLine.append("/P<</BE<</C true >>>>"
+ ">>");
+
+ // End Action dictionary.
+ aLine.append("/OP 0 >>");
+
+ if (-1 != rScreen.m_nStructParent)
+ {
+ aLine.append("\n/StructParent "
+ + OString::number(rScreen.m_nStructParent)
+ + "\n");
+ }
+
+ // End Annot dictionary.
+ aLine.append("/P "
+ + OString::number(m_aPages[rScreen.m_nPage].m_nPageObject)
+ + " 0 R\n>>\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine));
+ }
+
+ return true;
+}
+
+bool PDFWriterImpl::emitLinkAnnotations()
+{
+ MARK("PDFWriterImpl::emitLinkAnnotations");
+ int nAnnots = m_aLinks.size();
+ for( int i = 0; i < nAnnots; i++ )
+ {
+ const PDFLink& rLink = m_aLinks[i];
+ if( ! updateObject( rLink.m_nObject ) )
+ continue;
+
+ OStringBuffer aLine( 1024 );
+ aLine.append( rLink.m_nObject );
+ aLine.append( " 0 obj\n" );
+// i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
+// see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
+ aLine.append( "<</Type/Annot" );
+ if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3)
+ aLine.append( "/F 4" );
+ aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );
+
+ appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
+ aLine.append( ' ' );
+ appendFixedInt( rLink.m_aRect.Top(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
+ aLine.append( ' ' );
+ appendFixedInt( rLink.m_aRect.Bottom(), aLine );
+ aLine.append( "]" );
+ // ISO 14289-1:2014, Clause: 7.18.5
+ aLine.append("/Contents");
+ appendUnicodeTextStringEncrypt(rLink.m_AltText, rLink.m_nObject, aLine);
+ if( rLink.m_nDest >= 0 )
+ {
+ aLine.append( "/Dest" );
+ appendDest( rLink.m_nDest, aLine );
+ }
+ else
+ {
+/*
+destination is external to the document, so
+we check in the following sequence:
+
+ if target type is neither .pdf, nor .od[tpgs], then
+ check if relative or absolute and act accordingly (use URI or 'launch application' as requested)
+ end processing
+ else if target is .od[tpgs]: then
+ if conversion of type from od[tpgs] to pdf is requested, convert it and this becomes the new target file
+ processing continue
+
+ if (new)target is .pdf : then
+ if GotToR is requested, then
+ convert the target in GoToR where the fragment of the URI is
+ considered the named destination in the target file, set relative or absolute as requested
+ else strip the fragment from URL and then set URI or 'launch application' as requested
+*/
+
+// FIXME: check if the decode mechanisms for URL processing throughout this implementation
+// are the correct one!!
+
+// extract target file type
+ auto url(URIHelper::resolveIdnaHost(rLink.m_aURL));
+
+ INetURLObject aDocumentURL( m_aContext.BaseURL );
+ INetURLObject aTargetURL( url );
+ bool bSetGoToRMode = false;
+ bool bTargetHasPDFExtension = false;
+ INetProtocol eTargetProtocol = aTargetURL.GetProtocol();
+ bool bIsUNCPath = false;
+ bool bUnparsedURI = false;
+
+ // check if the protocol is a known one, or if there is no protocol at all (on target only)
+ // if there is no protocol, make the target relative to the current document directory
+ // getting the needed URL information from the current document path
+ if( eTargetProtocol == INetProtocol::NotValid )
+ {
+ if( url.getLength() > 4 && url.startsWith("\\\\\\\\"))
+ {
+ bIsUNCPath = true;
+ }
+ else
+ {
+ INetURLObject aNewURL(rtl::Uri::convertRelToAbs(
+ (m_aContext.BaseURL.getLength() > 0 ?
+ m_aContext.BaseURL :
+ //use dummy location if empty
+ u"http://ahost.ax"_ustr),
+ url));
+ aTargetURL = aNewURL; //reassign the new target URL
+
+ //recompute the target protocol, with the new URL
+ //normal URL processing resumes
+ eTargetProtocol = aTargetURL.GetProtocol();
+
+ bUnparsedURI = eTargetProtocol == INetProtocol::NotValid;
+ }
+ }
+
+ OUString aFileExtension = aTargetURL.GetFileExtension();
+
+ // Check if the URL ends in '/': if yes it's a directory,
+ // it will be forced to a URI link.
+ // possibly a malformed URI, leave it as it is, force as URI
+ if( aTargetURL.hasFinalSlash() )
+ m_aContext.DefaultLinkAction = PDFWriter::URIAction;
+
+ if( !aFileExtension.isEmpty() )
+ {
+ if( m_aContext.ConvertOOoTargetToPDFTarget )
+ {
+ bool bChangeFileExtensionToPDF = false;
+ //examine the file type (.odm .odt. .odp, odg, ods)
+ if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) )
+ bChangeFileExtensionToPDF = true;
+ if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) )
+ bChangeFileExtensionToPDF = true;
+ else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) )
+ bChangeFileExtensionToPDF = true;
+ else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) )
+ bChangeFileExtensionToPDF = true;
+ else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) )
+ bChangeFileExtensionToPDF = true;
+ if( bChangeFileExtensionToPDF )
+ aTargetURL.setExtension(u"pdf" );
+ }
+ //check if extension is pdf, see if GoToR should be forced
+ bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" );
+ if( m_aContext.ForcePDFAction && bTargetHasPDFExtension )
+ bSetGoToRMode = true;
+ }
+ //prepare the URL, if relative or not
+ INetProtocol eBaseProtocol = aDocumentURL.GetProtocol();
+ //queue the string common to all types of actions
+ aLine.append( "/A<</Type/Action/S");
+ if( bIsUNCPath ) // handle Win UNC paths
+ {
+ aLine.append( "/Launch/Win<</F" );
+ // INetURLObject is not good with UNC paths, use original path
+ appendLiteralStringEncrypt( url, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
+ aLine.append( ">>" );
+ }
+ else
+ {
+ bool bSetRelative = false;
+ bool bFileSpec = false;
+ //check if relative file link is requested and if the protocol is 'file://'
+ if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File )
+ bSetRelative = true;
+
+ OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is,
+ if( !bSetGoToRMode )
+ {
+ switch( m_aContext.DefaultLinkAction )
+ {
+ default:
+ case PDFWriter::URIAction :
+ case PDFWriter::URIActionDestination :
+ aLine.append( "/URI/URI" );
+ break;
+ case PDFWriter::LaunchAction:
+ // now:
+ // if a launch action is requested and the hyperlink target has a fragment
+ // and the target file does not have a pdf extension, or it's not a 'file:://'
+ // protocol then force the uri action on it
+ // This code will permit the correct opening of application on web pages,
+ // the one that normally have fragments (but I may be wrong...)
+ // and will force the use of URI when the protocol is not file:
+ if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) ||
+ eTargetProtocol != INetProtocol::File )
+ {
+ aLine.append( "/URI/URI" );
+ }
+ else
+ {
+ aLine.append( "/Launch/F" );
+ bFileSpec = true;
+ }
+ break;
+ }
+ }
+
+ //fragment are encoded in the same way as in the named destination processing
+ if( bSetGoToRMode )
+ {
+ //add the fragment
+ OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset );
+ aLine.append("/GoToR");
+ aLine.append("/F");
+ appendLiteralStringEncrypt( bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark,
+ INetURLObject::EncodeMechanism::WasEncoded,
+ INetURLObject::DecodeMechanism::WithCharset ) :
+ aURLNoMark, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
+ if( !aFragment.isEmpty() )
+ {
+ aLine.append("/D/");
+ appendDestinationName( aFragment , aLine );
+ }
+ }
+ else
+ {
+ // change the fragment to accommodate the bookmark (only if the file extension
+ // is PDF and the requested action is of the correct type)
+ if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination &&
+ bTargetHasPDFExtension && !aFragment.isEmpty() )
+ {
+ OStringBuffer aLineLoc( 1024 );
+ appendDestinationName( aFragment , aLineLoc );
+ //substitute the fragment
+ aTargetURL.SetMark( OStringToOUString(aLineLoc, RTL_TEXTENCODING_ASCII_US) );
+ }
+ OUString aURL = bUnparsedURI ? url :
+ aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset :
+ INetURLObject::DecodeMechanism::NONE );
+ appendLiteralStringEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL,
+ INetURLObject::EncodeMechanism::WasEncoded,
+ bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE
+ ) :
+ aURL , rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
+ }
+ }
+ aLine.append( ">>\n" );
+ }
+ if (rLink.m_nStructParent != -1)
+ {
+ aLine.append( "/StructParent " );
+ aLine.append( rLink.m_nStructParent );
+ }
+ aLine.append( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ }
+
+ return true;
+}
+
+namespace
+{
+
+void appendAnnotationRect(tools::Rectangle const & rRectangle, OStringBuffer & aLine)
+{
+ aLine.append("/Rect[");
+ appendFixedInt(rRectangle.Left(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rRectangle.Top(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rRectangle.Right(), aLine);
+ aLine.append(' ');
+ appendFixedInt(rRectangle.Bottom(), aLine);
+ aLine.append("] ");
+}
+
+} // end anonymous namespace
+
+void PDFWriterImpl::emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry const & rNote)
+{
+ appendObjectID(rNote.m_nObject, aLine);
+
+ aLine.append("<</Type /Annot /Subtype ");
+ if (rNote.m_aContents.maPolygons.size() == 1)
+ {
+ auto const& rPolygon = rNote.m_aContents.maPolygons[0];
+ aLine.append(rPolygon.isClosed() ? "/Polygon " : "/Polyline ");
+ aLine.append("/Vertices [");
+ for (sal_uInt32 i = 0; i < rPolygon.count(); ++i)
+ {
+ appendDouble(convertMm100ToPoint(rPolygon.getB2DPoint(i).getX()), aLine, nLog10Divisor);
+ aLine.append(" ");
+ appendDouble(m_aPages[rNote.m_nPage].getHeight()
+ - convertMm100ToPoint(rPolygon.getB2DPoint(i).getY()),
+ aLine, nLog10Divisor);
+ aLine.append(" ");
+ }
+ aLine.append("] ");
+ aLine.append("/C [");
+ appendColor(rNote.m_aContents.annotColor, aLine, false);
+ aLine.append("] ");
+ if (rPolygon.isClosed())
+ {
+ aLine.append("/IC [");
+ appendColor(rNote.m_aContents.interiorColor, aLine, false);
+ aLine.append("] ");
+ }
+ }
+ else if (rNote.m_aContents.maPolygons.size() > 1)
+ {
+ aLine.append("/Ink /InkList [");
+ for (auto const& rPolygon : rNote.m_aContents.maPolygons)
+ {
+ aLine.append("[");
+ for (sal_uInt32 i = 0; i < rPolygon.count(); ++i)
+ {
+ appendDouble(convertMm100ToPoint(rPolygon.getB2DPoint(i).getX()), aLine,
+ nLog10Divisor);
+ aLine.append(" ");
+ appendDouble(m_aPages[rNote.m_nPage].getHeight()
+ - convertMm100ToPoint(rPolygon.getB2DPoint(i).getY()),
+ aLine, nLog10Divisor);
+ aLine.append(" ");
+ }
+ aLine.append("]");
+ aLine.append("/C [");
+ appendColor(rNote.m_aContents.annotColor, aLine, false);
+ aLine.append("] ");
+ }
+ aLine.append("] ");
+ }
+ else if (rNote.m_aContents.isFreeText)
+ aLine.append("/FreeText ");
+ else
+ aLine.append("/Text ");
+
+ aLine.append("/BS<</W 0>>");
+
+ // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
+ // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
+ if (m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3)
+ aLine.append("/F 4 ");
+
+ appendAnnotationRect(rNote.m_aRect, aLine);
+
+ aLine.append("/Popup ");
+ appendObjectReference(rNote.m_aPopUpAnnotation.m_nObject, aLine);
+
+ auto & rDateTime = rNote.m_aContents.maModificationDate;
+
+ aLine.append("/M (");
+ appendPdfTimeDate(aLine, rDateTime.Year, rDateTime.Month, rDateTime.Day, rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds, 0);
+ aLine.append(") ");
+
+ // contents of the note (type text string)
+ aLine.append("/Contents ");
+ appendUnicodeTextStringEncrypt(rNote.m_aContents.Contents, rNote.m_nObject, aLine);
+ aLine.append("\n");
+
+ // optional title
+ if (!rNote.m_aContents.Title.isEmpty())
+ {
+ aLine.append("/T ");
+ appendUnicodeTextStringEncrypt(rNote.m_aContents.Title, rNote.m_nObject, aLine);
+ aLine.append("\n");
+ }
+ aLine.append(">>\n");
+ aLine.append("endobj\n\n");
+}
+
+void PDFWriterImpl::emitPopupAnnotationLine(OStringBuffer & aLine, PDFPopupAnnotation const & rPopUp)
+{
+ appendObjectID(rPopUp.m_nObject, aLine);
+ aLine.append("<</Type /Annot /Subtype /Popup ");
+ aLine.append("/Parent ");
+ appendObjectReference(rPopUp.m_nParentObject, aLine);
+ aLine.append(">>\n");
+ aLine.append("endobj\n\n");
+}
+
+bool PDFWriterImpl::emitNoteAnnotations()
+{
+ // emit note annotations
+ int nAnnots = m_aNotes.size();
+ for( int i = 0; i < nAnnots; i++ )
+ {
+ const PDFNoteEntry& rNote = m_aNotes[i];
+ const PDFPopupAnnotation& rPopUp = rNote.m_aPopUpAnnotation;
+
+ {
+ if (!updateObject(rNote.m_nObject))
+ return false;
+
+ OStringBuffer aLine(1024);
+
+ emitTextAnnotationLine(aLine, rNote);
+
+ if (!writeBuffer(aLine))
+ return false;
+ }
+
+ {
+
+ if (!updateObject(rPopUp.m_nObject))
+ return false;
+
+ OStringBuffer aLine(1024);
+
+ emitPopupAnnotationLine(aLine, rPopUp);
+
+ if (!writeBuffer(aLine))
+ return false;
+ }
+ }
+ return true;
+}
+
+Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font& rAppSetFont )
+{
+ bool bAdjustSize = false;
+
+ Font aFont( rControlFont );
+ if( aFont.GetFamilyName().isEmpty() )
+ {
+ aFont = rAppSetFont;
+ if( rControlFont.GetFontHeight() )
+ aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) );
+ else
+ bAdjustSize = true;
+ if( rControlFont.GetItalic() != ITALIC_DONTKNOW )
+ aFont.SetItalic( rControlFont.GetItalic() );
+ if( rControlFont.GetWeight() != WEIGHT_DONTKNOW )
+ aFont.SetWeight( rControlFont.GetWeight() );
+ }
+ else if( ! aFont.GetFontHeight() )
+ {
+ aFont.SetFontSize( rAppSetFont.GetFontSize() );
+ bAdjustSize = true;
+ }
+ if( bAdjustSize )
+ {
+ Size aFontSize = aFont.GetFontSize();
+ OutputDevice* pDefDev = Application::GetDefaultDevice();
+ aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() );
+ aFont.SetFontSize( aFontSize );
+ }
+ return aFont;
+}
+
+sal_Int32 PDFWriterImpl::getBestBuildinFont( const vcl::Font& rFont )
+{
+ sal_Int32 nBest = 4; // default to Helvetica
+
+ if (rFont.GetFamilyType() == FAMILY_ROMAN)
+ {
+ // Serif: default to Times-Roman.
+ nBest = 8;
+ }
+
+ OUString aFontName( rFont.GetFamilyName() );
+ aFontName = aFontName.toAsciiLowerCase();
+
+ if( aFontName.indexOf( "times" ) != -1 )
+ nBest = 8;
+ else if( aFontName.indexOf( "courier" ) != -1 )
+ nBest = 0;
+ else if( aFontName.indexOf( "dingbats" ) != -1 )
+ nBest = 13;
+ else if( aFontName.indexOf( "symbol" ) != -1 )
+ nBest = 12;
+ if( nBest < 12 )
+ {
+ if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL )
+ nBest += 1;
+ if( rFont.GetWeight() > WEIGHT_MEDIUM )
+ nBest += 2;
+ }
+
+ if( m_aBuildinFontToObjectMap.find( nBest ) == m_aBuildinFontToObjectMap.end() )
+ m_aBuildinFontToObjectMap[ nBest ] = createObject();
+
+ return nBest;
+}
+
+static const Color& replaceColor( const Color& rCol1, const Color& rCol2 )
+{
+ return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1;
+}
+
+void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget )
+{
+ const StyleSettings& rSettings = m_aWidgetStyleSettings;
+
+ // save graphics state
+ push( PushFlags::ALL );
+
+ // transform relative to control's coordinates since an
+ // appearance stream is a form XObject
+ // this relies on the m_aRect member of rButton NOT already being transformed
+ // to default user space
+ if( rWidget.Background || rWidget.Border )
+ {
+ setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT );
+ setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT );
+ drawRectangle( rWidget.Location );
+ }
+ // prepare font to use
+ Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
+ setFont( aFont );
+ setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
+
+ drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
+
+ // create DA string while local mapmode is still in place
+ // (that is before endRedirect())
+ OStringBuffer aDA( 256 );
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
+ Font aDummyFont( "Helvetica", aFont.GetFontSize() );
+ sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont );
+ aDA.append( ' ' );
+ aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject());
+ aDA.append( ' ' );
+ m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
+ aDA.append( " Tf" );
+ rButton.m_aDAString = aDA.makeStringAndClear();
+
+ pop();
+
+ rButton.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = new SvMemoryStream();
+
+ /* seems like a bad hack but at least works in both AR5 and 6:
+ we draw the button ourselves and tell AR
+ the button would be totally transparent with no text
+
+ One would expect that simply setting a normal appearance
+ should suffice, but no, as soon as the user actually presses
+ the button and an action is tied to it (gasp! a button that
+ does something) the appearance gets replaced by some crap that AR
+ creates on the fly even if no DA or MK is given. On AR6 at least
+ the DA and MK work as expected, but on AR5 this creates a region
+ filled with the background color but nor text. Urgh.
+ */
+ rButton.m_aMKDict = "/BC [] /BG [] /CA"_ostr;
+ rButton.m_aMKDictCAString = ""_ostr;
+}
+
+Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern,
+ const PDFWriter::AnyWidget& rWidget,
+ const StyleSettings& rSettings )
+{
+ Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
+
+ if( rWidget.Background || rWidget.Border )
+ {
+ if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT )
+ {
+ sal_Int32 nDelta = GetDPIX() / 500;
+ if( nDelta < 1 )
+ nDelta = 1;
+ setLineColor( COL_TRANSPARENT );
+ tools::Rectangle aRect = rIntern.m_aRect;
+ setFillColor( rSettings.GetLightBorderColor() );
+ drawRectangle( aRect );
+ aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta );
+ aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta );
+ setFillColor( rSettings.GetFieldColor() );
+ drawRectangle( aRect );
+ setFillColor( rSettings.GetLightColor() );
+ drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
+ drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
+ setFillColor( rSettings.GetDarkShadowColor() );
+ drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
+ drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
+ }
+ else
+ {
+ setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT );
+ setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
+ drawRectangle( rIntern.m_aRect );
+ }
+
+ if( rWidget.Border )
+ {
+ // adjust edit area accounting for border
+ sal_Int32 nDelta = aFont.GetFontHeight()/4;
+ if( nDelta < 1 )
+ nDelta = 1;
+ rIntern.m_aRect.AdjustLeft(nDelta );
+ rIntern.m_aRect.AdjustTop(nDelta );
+ rIntern.m_aRect.AdjustRight( -nDelta );
+ rIntern.m_aRect.AdjustBottom( -nDelta );
+ }
+ }
+ return aFont;
+}
+
+void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget )
+{
+ const StyleSettings& rSettings = m_aWidgetStyleSettings;
+ SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 );
+
+ push( PushFlags::ALL );
+
+ // prepare font to use, draw field border
+ Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
+ // Get the built-in font which is closest to aFont.
+ sal_Int32 nBest = getBestBuildinFont(aFont);
+
+ // prepare DA string
+ OStringBuffer aDA( 32 );
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
+ aDA.append( ' ' );
+ aDA.append(pdf::BuildinFontFace::Get(nBest).getNameObject());
+
+ OStringBuffer aDR( 32 );
+ aDR.append( "/Font " );
+ aDR.append( getFontDictObject() );
+ aDR.append( " 0 R" );
+ rEdit.m_aDRDict = aDR.makeStringAndClear();
+ aDA.append( ' ' );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
+ aDA.append( " Tf" );
+
+ /* create an empty appearance stream, let the viewer create
+ the appearance at runtime. This is because AR5 seems to
+ paint the widget appearance always, and a dynamically created
+ appearance on top of it. AR6 is well behaved in that regard, so
+ that behaviour seems to be a bug. Anyway this empty appearance
+ relies on /NeedAppearances in the AcroForm dictionary set to "true"
+ */
+ beginRedirect( pEditStream, rEdit.m_aRect );
+ writeBuffer( "/Tx BMC\nEMC\n" );
+
+ endRedirect();
+ pop();
+
+ rEdit.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = pEditStream;
+
+ rEdit.m_aDAString = aDA.makeStringAndClear();
+}
+
+void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
+{
+ const StyleSettings& rSettings = m_aWidgetStyleSettings;
+ SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 );
+
+ push( PushFlags::ALL );
+
+ // prepare font to use, draw field border
+ Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
+ sal_Int32 nBest = getSystemFont( aFont );
+
+ beginRedirect( pListBoxStream, rBox.m_aRect );
+
+ setLineColor( COL_TRANSPARENT );
+ setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
+ drawRectangle( rBox.m_aRect );
+
+ // empty appearance, see createDefaultEditAppearance for reference
+ writeBuffer( "/Tx BMC\nEMC\n" );
+
+ endRedirect();
+ pop();
+
+ rBox.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = pListBoxStream;
+
+ // prepare DA string
+ OStringBuffer aDA( 256 );
+ // prepare DA string
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
+ aDA.append( ' ' );
+ aDA.append( "/F" );
+ aDA.append( nBest );
+
+ OStringBuffer aDR( 32 );
+ aDR.append( "/Font " );
+ aDR.append( getFontDictObject() );
+ aDR.append( " 0 R" );
+ rBox.m_aDRDict = aDR.makeStringAndClear();
+ aDA.append( ' ' );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
+ aDA.append( " Tf" );
+ rBox.m_aDAString = aDA.makeStringAndClear();
+}
+
+void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget )
+{
+ const StyleSettings& rSettings = m_aWidgetStyleSettings;
+
+ // save graphics state
+ push( PushFlags::ALL );
+
+ if( rWidget.Background || rWidget.Border )
+ {
+ setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
+ setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
+ drawRectangle( rBox.m_aRect );
+ }
+
+ Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
+ setFont( aFont );
+ Size aFontSize = aFont.GetFontSize();
+ if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
+ aFontSize.setHeight( rBox.m_aRect.GetHeight() );
+ sal_Int32 nDelta = aFontSize.Height()/10;
+ if( nDelta < 1 )
+ nDelta = 1;
+
+ tools::Rectangle aCheckRect, aTextRect;
+ {
+ aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
+ aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
+ aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
+ aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
+
+ // #i74206# handle small controls without text area
+ while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
+ {
+ aCheckRect.AdjustRight( -nDelta );
+ aCheckRect.AdjustTop(nDelta/2 );
+ aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
+ }
+
+ aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
+ aTextRect.SetTop( rBox.m_aRect.Top() );
+ aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
+ aTextRect.SetBottom( rBox.m_aRect.Bottom() );
+ }
+ setLineColor( COL_BLACK );
+ setFillColor( COL_TRANSPARENT );
+ OStringBuffer aLW( 32 );
+ aLW.append( "q " );
+ m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
+ aLW.append( " w " );
+ writeBuffer( aLW );
+ drawRectangle( aCheckRect );
+ writeBuffer( " Q\n" );
+ setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
+ drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
+
+ pop();
+
+ OStringBuffer aDA( 256 );
+
+ // tdf#93853 don't rely on Zapf (or any other 'standard' font)
+ // being present, but our own OpenSymbol - N.B. PDF/A for good
+ // reasons require even the standard PS fonts to be embedded!
+ Push();
+ SetFont( Font( OUString( "OpenSymbol" ), aFont.GetFontSize() ) );
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace();
+ Pop();
+
+ // make sure OpenSymbol is embedded, and includes our checkmark
+ const sal_Unicode cMark=0x2713;
+ const auto nGlyphId = pFontInstance->GetGlyphIndex(cMark);
+ const auto nGlyphWidth = pFontInstance->GetGlyphWidth(nGlyphId, false, false);
+
+ sal_uInt8 nMappedGlyph;
+ sal_Int32 nMappedFontObject;
+ registerGlyph(nGlyphId, pFace, pFontInstance, { cMark }, nGlyphWidth, nMappedGlyph, nMappedFontObject);
+
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
+ aDA.append( ' ' );
+ aDA.append( "/F" );
+ aDA.append( nMappedFontObject );
+ aDA.append( " 0 Tf" );
+
+ OStringBuffer aDR( 32 );
+ aDR.append( "/Font " );
+ aDR.append( getFontDictObject() );
+ aDR.append( " 0 R" );
+ rBox.m_aDRDict = aDR.makeStringAndClear();
+ rBox.m_aDAString = aDA.makeStringAndClear();
+ rBox.m_aMKDict = "/CA"_ostr;
+ rBox.m_aMKDictCAString = "8"_ostr;
+ rBox.m_aRect = aCheckRect;
+
+ // create appearance streams
+ sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol
+ nCharXOffset *= aCheckRect.GetHeight();
+ nCharXOffset /= 2000;
+ sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf
+ nCharYOffset *= aCheckRect.GetHeight();
+ nCharYOffset /= 2000;
+
+ // write 'checked' appearance stream
+ SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
+ beginRedirect( pCheckStream, aCheckRect );
+ aDA.append( "/Tx BMC\nq BT\n" );
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
+ aDA.append( ' ' );
+ aDA.append( "/F" );
+ aDA.append( nMappedFontObject );
+ aDA.append( ' ' );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
+ aDA.append( " Tf\n" );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
+ aDA.append( " " );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
+ aDA.append( " Td <" );
+ appendHex( nMappedGlyph, aDA );
+ aDA.append( "> Tj\nET\nQ\nEMC\n" );
+ writeBuffer( aDA );
+ endRedirect();
+ rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream;
+
+ // write 'unchecked' appearance stream
+ SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
+ beginRedirect( pUncheckStream, aCheckRect );
+ writeBuffer( "/Tx BMC\nEMC\n" );
+ endRedirect();
+ rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = pUncheckStream;
+}
+
+void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
+{
+ const StyleSettings& rSettings = m_aWidgetStyleSettings;
+
+ // save graphics state
+ push( PushFlags::ALL );
+
+ if( rWidget.Background || rWidget.Border )
+ {
+ setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
+ setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
+ drawRectangle( rBox.m_aRect );
+ }
+
+ Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
+ setFont( aFont );
+ Size aFontSize = aFont.GetFontSize();
+ if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
+ aFontSize.setHeight( rBox.m_aRect.GetHeight() );
+ sal_Int32 nDelta = aFontSize.Height()/10;
+ if( nDelta < 1 )
+ nDelta = 1;
+
+ tools::Rectangle aCheckRect, aTextRect;
+ {
+ aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
+ aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
+ aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
+ aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
+
+ // #i74206# handle small controls without text area
+ while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
+ {
+ aCheckRect.AdjustRight( -nDelta );
+ aCheckRect.AdjustTop(nDelta/2 );
+ aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
+ }
+
+ aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
+ aTextRect.SetTop( rBox.m_aRect.Top() );
+ aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
+ aTextRect.SetBottom( rBox.m_aRect.Bottom() );
+ }
+ setLineColor( COL_BLACK );
+ setFillColor( COL_TRANSPARENT );
+ OStringBuffer aLW( 32 );
+ aLW.append( "q " );
+ m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
+ aLW.append( " w " );
+ writeBuffer( aLW );
+ drawEllipse( aCheckRect );
+ writeBuffer( " Q\n" );
+ setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
+ drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
+
+ pop();
+
+ //to encrypt this (el)
+ rBox.m_aMKDict = "/CA"_ostr;
+ //after this assignment, to m_aMKDic cannot be added anything
+ rBox.m_aMKDictCAString = "l"_ostr;
+
+ rBox.m_aRect = aCheckRect;
+
+ // create appearance streams
+ push( PushFlags::ALL);
+ SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
+
+ beginRedirect( pCheckStream, aCheckRect );
+ OStringBuffer aDA( 256 );
+ aDA.append( "/Tx BMC\nq BT\n" );
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
+ aDA.append( ' ' );
+ m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
+ aDA.append( " 0 0 Td\nET\nQ\n" );
+ writeBuffer( aDA );
+ setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
+ setLineColor( COL_TRANSPARENT );
+ aCheckRect.AdjustLeft(3*nDelta );
+ aCheckRect.AdjustTop(3*nDelta );
+ aCheckRect.AdjustBottom( -(3*nDelta) );
+ aCheckRect.AdjustRight( -(3*nDelta) );
+ drawEllipse( aCheckRect );
+ writeBuffer( "\nEMC\n" );
+ endRedirect();
+
+ pop();
+ rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream;
+
+ SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
+ beginRedirect( pUncheckStream, aCheckRect );
+ writeBuffer( "/Tx BMC\nEMC\n" );
+ endRedirect();
+ rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = pUncheckStream;
+}
+
+bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
+{
+ // TODO: check and insert default streams
+ OString aStandardAppearance;
+ switch( rWidget.m_eType )
+ {
+ case PDFWriter::CheckBox:
+ aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
+ break;
+ default:
+ break;
+ }
+
+ if( !rWidget.m_aAppearances.empty() )
+ {
+ rAnnotDict.append( "/AP<<\n" );
+ for (auto & dict_item : rWidget.m_aAppearances)
+ {
+ rAnnotDict.append( "/" );
+ rAnnotDict.append( dict_item.first );
+ bool bUseSubDict = (dict_item.second.size() > 1);
+
+ // PDF/A requires sub-dicts for /FT/Btn objects (clause
+ // 6.3.3)
+ if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3)
+ {
+ if( rWidget.m_eType == PDFWriter::RadioButton ||
+ rWidget.m_eType == PDFWriter::CheckBox ||
+ rWidget.m_eType == PDFWriter::PushButton )
+ {
+ bUseSubDict = true;
+ }
+ }
+
+ rAnnotDict.append( bUseSubDict ? "<<" : " " );
+
+ for (auto const& stream_item : dict_item.second)
+ {
+ SvMemoryStream* pAppearanceStream = stream_item.second;
+ dict_item.second[ stream_item.first ] = nullptr;
+
+ bool bDeflate = compressStream( pAppearanceStream );
+
+ sal_Int64 nStreamLen = pAppearanceStream->TellEnd();
+ pAppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
+ sal_Int32 nObject = createObject();
+ CHECK_RETURN( updateObject( nObject ) );
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::emitAppearances" );
+ }
+ OStringBuffer aLine;
+ aLine.append( nObject );
+
+ aLine.append( " 0 obj\n"
+ "<</Type/XObject\n"
+ "/Subtype/Form\n"
+ "/BBox[0 0 " );
+ appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
+ aLine.append( " " );
+ appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
+ aLine.append( "]\n"
+ "/Resources " );
+ aLine.append( getResourceDictObj() );
+ aLine.append( " 0 R\n"
+ "/Length " );
+ aLine.append( nStreamLen );
+ aLine.append( "\n" );
+ if( bDeflate )
+ aLine.append( "/Filter/FlateDecode\n" );
+ aLine.append( ">>\nstream\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ checkAndEnableStreamEncryption( nObject );
+ CHECK_RETURN( writeBufferBytes( pAppearanceStream->GetData(), nStreamLen ) );
+ disableStreamEncryption();
+ CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n" ) );
+
+ if( bUseSubDict )
+ {
+ rAnnotDict.append( " /" );
+ rAnnotDict.append( stream_item.first );
+ rAnnotDict.append( " " );
+ }
+ rAnnotDict.append( nObject );
+ rAnnotDict.append( " 0 R" );
+
+ delete pAppearanceStream;
+ }
+
+ rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" );
+ }
+ rAnnotDict.append( ">>\n" );
+ if( !aStandardAppearance.isEmpty() )
+ {
+ rAnnotDict.append( "/AS /" );
+ rAnnotDict.append( aStandardAppearance );
+ rAnnotDict.append( "\n" );
+ }
+ }
+
+ return true;
+}
+
+bool PDFWriterImpl::emitWidgetAnnotations()
+{
+ ensureUniqueRadioOnValues();
+
+ int nAnnots = m_aWidgets.size();
+ for( int a = 0; a < nAnnots; a++ )
+ {
+ PDFWidget& rWidget = m_aWidgets[a];
+
+ if( rWidget.m_eType == PDFWriter::CheckBox )
+ {
+ if ( !rWidget.m_aOnValue.isEmpty() )
+ {
+ auto app_it = rWidget.m_aAppearances.find( "N"_ostr );
+ if( app_it != rWidget.m_aAppearances.end() )
+ {
+ auto stream_it = app_it->second.find( "Yes"_ostr );
+ if( stream_it != app_it->second.end() )
+ {
+ SvMemoryStream* pStream = stream_it->second;
+ app_it->second.erase( stream_it );
+ OStringBuffer aBuf( rWidget.m_aOnValue.getLength()*2 );
+ appendName( rWidget.m_aOnValue, aBuf );
+ (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
+ }
+ else
+ SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Yes\" stream" );
+ }
+ }
+
+ if ( !rWidget.m_aOffValue.isEmpty() )
+ {
+ auto app_it = rWidget.m_aAppearances.find( "N"_ostr );
+ if( app_it != rWidget.m_aAppearances.end() )
+ {
+ auto stream_it = app_it->second.find( "Off"_ostr );
+ if( stream_it != app_it->second.end() )
+ {
+ SvMemoryStream* pStream = stream_it->second;
+ app_it->second.erase( stream_it );
+ OStringBuffer aBuf( rWidget.m_aOffValue.getLength()*2 );
+ appendName( rWidget.m_aOffValue, aBuf );
+ (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
+ }
+ else
+ SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Off\" stream" );
+ }
+ }
+ }
+
+ OStringBuffer aLine( 1024 );
+ OStringBuffer aValue( 256 );
+ aLine.append( rWidget.m_nObject );
+ aLine.append( " 0 obj\n"
+ "<<" );
+ if( rWidget.m_eType != PDFWriter::Hierarchy )
+ {
+ // emit widget annotation only for terminal fields
+ if( rWidget.m_aKids.empty() )
+ {
+ int iRectMargin;
+
+ aLine.append( "/Type/Annot/Subtype/Widget/F " );
+
+ if (rWidget.m_eType == PDFWriter::Signature)
+ {
+ aLine.append( "132\n" ); // Print & Locked
+ iRectMargin = 0;
+ }
+ else
+ {
+ aLine.append( "4\n" );
+ iRectMargin = 1;
+ }
+
+ if (-1 != rWidget.m_nStructParent)
+ {
+ aLine.append("/StructParent ");
+ aLine.append(rWidget.m_nStructParent);
+ aLine.append("\n");
+ }
+
+ aLine.append("/Rect[" );
+ appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine );
+ aLine.append( "]\n" );
+ }
+ aLine.append( "/FT/" );
+ switch( rWidget.m_eType )
+ {
+ case PDFWriter::RadioButton:
+ case PDFWriter::CheckBox:
+ // for radio buttons only the RadioButton field, not the
+ // CheckBox children should have a value, else acrobat reader
+ // does not always check the right button
+ // of course real check boxes (not belonging to a radio group)
+ // need their values, too
+ if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
+ {
+ aValue.append( "/" );
+ // check for radio group with all buttons unpressed
+ if( rWidget.m_aValue.isEmpty() )
+ aValue.append( "Off" );
+ else
+ appendName( rWidget.m_aValue, aValue );
+ }
+ [[fallthrough]];
+ case PDFWriter::PushButton:
+ aLine.append( "Btn" );
+ break;
+ case PDFWriter::ListBox:
+ if( rWidget.m_nFlags & 0x200000 ) // multiselect
+ {
+ aValue.append( "[" );
+ for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ )
+ {
+ sal_Int32 nEntry = rWidget.m_aSelectedEntries[i];
+ if( nEntry >= 0
+ && o3tl::make_unsigned(nEntry) < rWidget.m_aListEntries.size() )
+ appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject, aValue );
+ }
+ aValue.append( "]" );
+ }
+ else if( !rWidget.m_aSelectedEntries.empty() &&
+ rWidget.m_aSelectedEntries[0] >= 0 &&
+ o3tl::make_unsigned(rWidget.m_aSelectedEntries[0]) < rWidget.m_aListEntries.size() )
+ {
+ appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject, aValue );
+ }
+ else
+ appendUnicodeTextStringEncrypt( OUString(), rWidget.m_nObject, aValue );
+ aLine.append( "Ch" );
+ break;
+ case PDFWriter::ComboBox:
+ appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
+ aLine.append( "Ch" );
+ break;
+ case PDFWriter::Edit:
+ aLine.append( "Tx" );
+ appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
+ break;
+ case PDFWriter::Signature:
+ aLine.append( "Sig" );
+ aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US));
+ break;
+ case PDFWriter::Hierarchy: // make the compiler happy
+ break;
+ }
+ aLine.append( "\n" );
+ aLine.append( "/P " );
+ aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
+ aLine.append( " 0 R\n" );
+ }
+ if( rWidget.m_nParent )
+ {
+ aLine.append( "/Parent " );
+ aLine.append( rWidget.m_nParent );
+ aLine.append( " 0 R\n" );
+ }
+ if( !rWidget.m_aKids.empty() )
+ {
+ aLine.append( "/Kids[" );
+ for( size_t i = 0; i < rWidget.m_aKids.size(); i++ )
+ {
+ aLine.append( rWidget.m_aKids[i] );
+ aLine.append( " 0 R" );
+ aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
+ }
+ aLine.append( "]\n" );
+ }
+ if( !rWidget.m_aName.isEmpty() )
+ {
+ aLine.append( "/T" );
+ appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if (!rWidget.m_aDescription.isEmpty())
+ {
+ // the alternate field name should be unicode able since it is
+ // supposed to be used in UI
+ aLine.append( "/TU" );
+ appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+
+ if( rWidget.m_nFlags )
+ {
+ aLine.append( "/Ff " );
+ aLine.append( rWidget.m_nFlags );
+ aLine.append( "\n" );
+ }
+ if( !aValue.isEmpty() )
+ {
+ OString aVal = aValue.makeStringAndClear();
+ aLine.append( "/V " );
+ aLine.append( aVal );
+ aLine.append( "\n"
+ "/DV " );
+ aLine.append( aVal );
+ aLine.append( "\n" );
+ }
+ if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
+ {
+ sal_Int32 nTI = -1;
+ aLine.append( "/Opt[\n" );
+ sal_Int32 i = 0;
+ for (auto const& entry : rWidget.m_aListEntries)
+ {
+ appendUnicodeTextStringEncrypt( entry, rWidget.m_nObject, aLine );
+ aLine.append( "\n" );
+ if( entry == rWidget.m_aValue )
+ nTI = i;
+ ++i;
+ }
+ aLine.append( "]\n" );
+ if( nTI > 0 )
+ {
+ aLine.append( "/TI " );
+ aLine.append( nTI );
+ aLine.append( "\n" );
+ if( rWidget.m_nFlags & 0x200000 ) // Multiselect
+ {
+ aLine.append( "/I [" );
+ aLine.append( nTI );
+ aLine.append( "]\n" );
+ }
+ }
+ }
+ if( rWidget.m_eType == PDFWriter::Edit )
+ {
+ if ( rWidget.m_nMaxLen > 0 )
+ {
+ aLine.append( "/MaxLen " );
+ aLine.append( rWidget.m_nMaxLen );
+ aLine.append( "\n" );
+ }
+
+ if ( rWidget.m_nFormat == PDFWriter::Number )
+ {
+ OString aHexText;
+
+ if ( !rWidget.m_aCurrencySymbol.isEmpty() )
+ {
+ // Get the hexadecimal code
+ sal_UCS4 cChar = rWidget.m_aCurrencySymbol.iterateCodePoints(&o3tl::temporary(sal_Int32(1)), -1);
+ aHexText = "\\\\u" + OString::number(cChar, 16);
+ }
+
+ aLine.append("/AA<<\n");
+ aLine.append("/F<</JS(AFNumber_Format\\(");
+ aLine.append(OString::number(rWidget.m_nDecimalAccuracy));
+ aLine.append(", 0, 0, 0, \"");
+ aLine.append( aHexText );
+ aLine.append("\",");
+ aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol));
+ aLine.append("\\);)");
+ aLine.append("/S/JavaScript>>\n");
+ aLine.append("/K<</JS(AFNumber_Keystroke\\(");
+ aLine.append(OString::number(rWidget.m_nDecimalAccuracy));
+ aLine.append(", 0, 0, 0, \"");
+ aLine.append( aHexText );
+ aLine.append("\",");
+ aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol));
+ aLine.append("\\);)");
+ aLine.append("/S/JavaScript>>\n");
+ aLine.append(">>\n");
+ }
+ else if ( rWidget.m_nFormat == PDFWriter::Time )
+ {
+ aLine.append("/AA<<\n");
+ aLine.append("/F<</JS(AFTime_FormatEx\\(\"");
+ aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US));
+ aLine.append("\"\\);)");
+ aLine.append("/S/JavaScript>>\n");
+ aLine.append("/K<</JS(AFTime_KeystrokeEx\\(\"");
+ aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US));
+ aLine.append("\"\\);)");
+ aLine.append("/S/JavaScript>>\n");
+ aLine.append(">>\n");
+ }
+ else if ( rWidget.m_nFormat == PDFWriter::Date )
+ {
+ aLine.append("/AA<<\n");
+ aLine.append("/F<</JS(AFDate_FormatEx\\(\"");
+ aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US));
+ aLine.append("\"\\);)");
+ aLine.append("/S/JavaScript>>\n");
+ aLine.append("/K<</JS(AFDate_KeystrokeEx\\(\"");
+ aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US));
+ aLine.append("\"\\);)");
+ aLine.append("/S/JavaScript>>\n");
+ aLine.append(">>\n");
+ }
+ }
+ if( rWidget.m_eType == PDFWriter::PushButton )
+ {
+ if(!m_bIsPDF_A1)
+ {
+ OStringBuffer aDest;
+ if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) )
+ {
+ aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " );
+ aLine.append( aDest );
+ aLine.append( ">>>>\n" );
+ }
+ else if( rWidget.m_aListEntries.empty() )
+ {
+ if( !m_bIsPDF_A2 && !m_bIsPDF_A3 )
+ {
+ // create a reset form action
+ aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
+ }
+ }
+ else if( rWidget.m_bSubmit )
+ {
+ // create a submit form action
+ aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
+ appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine, osl_getThreadTextEncoding() );
+ aLine.append( "/Flags " );
+
+ sal_Int32 nFlags = 0;
+ switch( m_aContext.SubmitFormat )
+ {
+ case PDFWriter::HTML:
+ nFlags |= 4;
+ break;
+ case PDFWriter::XML:
+ nFlags |= 32;
+ break;
+ case PDFWriter::PDF:
+ nFlags |= 256;
+ break;
+ case PDFWriter::FDF:
+ default:
+ break;
+ }
+ if( rWidget.m_bSubmitGet )
+ nFlags |= 8;
+ aLine.append( nFlags );
+ aLine.append( ">>>>\n" );
+ }
+ else
+ {
+ // create a URI action
+ aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" );
+ aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) );
+ aLine.append( ")>>>>\n" );
+ }
+ }
+ else
+ m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA );
+ }
+ if( !rWidget.m_aDAString.isEmpty() )
+ {
+ if( !rWidget.m_aDRDict.isEmpty() )
+ {
+ aLine.append( "/DR<<" );
+ aLine.append( rWidget.m_aDRDict );
+ aLine.append( ">>\n" );
+ }
+ else
+ {
+ aLine.append( "/DR<</Font<<" );
+ appendBuildinFontsToDict( aLine );
+ aLine.append( ">>>>\n" );
+ }
+ aLine.append( "/DA" );
+ appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine );
+ aLine.append( "\n" );
+ if( rWidget.m_nTextStyle & DrawTextFlags::Center )
+ aLine.append( "/Q 1\n" );
+ else if( rWidget.m_nTextStyle & DrawTextFlags::Right )
+ aLine.append( "/Q 2\n" );
+ }
+ // appearance characteristics for terminal fields
+ // which are supposed to have an appearance constructed
+ // by the viewer application
+ if( !rWidget.m_aMKDict.isEmpty() )
+ {
+ aLine.append( "/MK<<" );
+ aLine.append( rWidget.m_aMKDict );
+ //add the CA string, encrypting it
+ appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine);
+ aLine.append( ">>\n" );
+ }
+
+ CHECK_RETURN( emitAppearances( rWidget, aLine ) );
+
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN( updateObject( rWidget.m_nObject ) );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ }
+ return true;
+}
+
+bool PDFWriterImpl::emitAnnotations()
+{
+ if( m_aPages.empty() )
+ return false;
+
+ CHECK_RETURN( emitLinkAnnotations() );
+ CHECK_RETURN(emitScreenAnnotations());
+ CHECK_RETURN( emitNoteAnnotations() );
+ CHECK_RETURN( emitWidgetAnnotations() );
+
+ return true;
+}
+
+class PDFStreamIf : public cppu::WeakImplHelper< css::io::XOutputStream >
+{
+ VclPtr<PDFWriterImpl> m_pWriter;
+ bool m_bWrite;
+ public:
+ explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {}
+
+ virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override
+ {
+ if( m_bWrite && aData.hasElements() )
+ {
+ sal_Int32 nBytes = aData.getLength();
+ m_pWriter->writeBufferBytes( aData.getConstArray(), nBytes );
+ }
+ }
+ virtual void SAL_CALL flush() override {}
+ virtual void SAL_CALL closeOutput() override
+ {
+ m_bWrite = false;
+ }
+};
+
+bool PDFWriterImpl::emitEmbeddedFiles()
+{
+ for (auto& rEmbeddedFile : m_aEmbeddedFiles)
+ {
+ if (!updateObject(rEmbeddedFile.m_nObject))
+ continue;
+
+ sal_Int32 nSizeObject = createObject();
+ sal_Int32 nParamsObject = createObject();
+
+ OStringBuffer aLine;
+ aLine.append(rEmbeddedFile.m_nObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /EmbeddedFile");
+ if (!rEmbeddedFile.m_aSubType.isEmpty())
+ {
+ aLine.append("/Subtype /");
+ appendName(rEmbeddedFile.m_aSubType, aLine);
+ }
+ aLine.append(" /Length ");
+ appendObjectReference(nSizeObject, aLine);
+ aLine.append(" /Params ");
+ appendObjectReference(nParamsObject, aLine);
+ aLine.append(">>\nstream\n");
+ checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject);
+ CHECK_RETURN(writeBuffer(aLine));
+ disableStreamEncryption();
+ aLine.setLength(0);
+
+ sal_Int64 nSize{};
+ if (!rEmbeddedFile.m_aDataContainer.isEmpty())
+ {
+ nSize = rEmbeddedFile.m_aDataContainer.getSize();
+ CHECK_RETURN(writeBufferBytes(rEmbeddedFile.m_aDataContainer.getData(), rEmbeddedFile.m_aDataContainer.getSize()));
+ }
+ else if (rEmbeddedFile.m_pStream)
+ {
+ sal_uInt64 nBegin = getCurrentFilePosition();
+ css::uno::Reference<css::io::XOutputStream> xStream(new PDFStreamIf(this));
+ rEmbeddedFile.m_pStream->write(xStream);
+ rEmbeddedFile.m_pStream.reset();
+ xStream.clear();
+ nSize = sal_Int64(getCurrentFilePosition() - nBegin);
+ }
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine));
+ aLine.setLength(0);
+
+ if (!updateObject(nSizeObject))
+ return false;
+ aLine.append(nSizeObject);
+ aLine.append(" 0 obj\n");
+ aLine.append(nSize);
+ aLine.append("\nendobj\n\n");
+ if (!writeBuffer(aLine))
+ return false;
+ aLine.setLength(0);
+
+ if (!updateObject(nParamsObject))
+ return false;
+ aLine.append(nParamsObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<<");
+ aLine.append("/Size ");
+ aLine.append(nSize);
+ aLine.append(">>");
+ aLine.append("\nendobj\n\n");
+ if (!writeBuffer(aLine))
+ return false;
+ }
+ return true;
+}
+
+#undef CHECK_RETURN
+#define CHECK_RETURN( x ) if( !x ) return false
+
+bool PDFWriterImpl::emitCatalog()
+{
+ // build page tree
+ // currently there is only one node that contains all leaves
+
+ // first create a page tree node id
+ sal_Int32 nTreeNode = createObject();
+
+ // emit global resource dictionary (page emit needs it)
+ CHECK_RETURN( emitResources() );
+
+ // emit all pages
+ for (auto & page : m_aPages)
+ if( ! page.emit( nTreeNode ) )
+ return false;
+
+ sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations();
+
+ sal_Int32 nOutlineDict = emitOutline();
+
+ // emit Output intent
+ sal_Int32 nOutputIntentObject = emitOutputIntent();
+
+ // emit metadata
+ sal_Int32 nMetadataObject = emitDocumentMetadata();
+
+ sal_Int32 nStructureDict = 0;
+ if(m_aStructure.size() > 1)
+ {
+ removePlaceholderSE(m_aStructure, m_aStructure[0]);
+ // check if dummy structure containers are needed
+ addInternalStructureContainer(m_aStructure[0]);
+ nStructureDict = m_aStructure[0].m_nObject = createObject();
+ emitStructure( m_aStructure[ 0 ] );
+ }
+
+ // adjust tree node file offset
+ if( ! updateObject( nTreeNode ) )
+ return false;
+
+ // emit tree node
+ OStringBuffer aLine( 2048 );
+ aLine.append( nTreeNode );
+ aLine.append( " 0 obj\n" );
+ aLine.append( "<</Type/Pages\n" );
+ aLine.append( "/Resources " );
+ aLine.append( getResourceDictObj() );
+ aLine.append( " 0 R\n" );
+
+ if( m_aPages.empty() ) // sanity check, this should not happen
+ aLine.append( "/MediaBox[0 0 595 842]\n" ); // default A4 size in pt
+
+ aLine.append("/Kids[ ");
+ unsigned int i = 0;
+ for (const auto & page : m_aPages)
+ {
+ aLine.append( page.m_nPageObject );
+ aLine.append( " 0 R" );
+ aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
+ ++i;
+ }
+ aLine.append( "]\n"
+ "/Count " );
+ aLine.append( static_cast<sal_Int32>(m_aPages.size()) );
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+
+ // emit annotation objects
+ CHECK_RETURN( emitAnnotations() );
+ CHECK_RETURN( emitEmbeddedFiles() );
+
+ // emit attached files
+ for (auto & rAttachedFile : m_aDocumentAttachedFiles)
+ {
+ if (!updateObject(rAttachedFile.mnObjectId))
+ return false;
+ aLine.setLength( 0 );
+
+ appendObjectID(rAttachedFile.mnObjectId, aLine);
+ aLine.append("<</Type /Filespec");
+ aLine.append("/F<");
+ PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine);
+ aLine.append("> ");
+ if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
+ {
+ aLine.append("/UF<");
+ PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine);
+ aLine.append("> ");
+ }
+ if (!rAttachedFile.maDescription.isEmpty())
+ {
+ aLine.append("/Desc <");
+ PDFWriter::AppendUnicodeTextString(rAttachedFile.maDescription, aLine);
+ aLine.append("> ");
+ }
+ aLine.append("/EF <</F ");
+ appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine);
+ aLine.append(">>");
+ aLine.append(">>\nendobj\n\n");
+ CHECK_RETURN( writeBuffer( aLine ) );
+ }
+
+ // emit Catalog
+ m_nCatalogObject = createObject();
+ if( ! updateObject( m_nCatalogObject ) )
+ return false;
+ aLine.setLength( 0 );
+ aLine.append( m_nCatalogObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/Catalog/Pages " );
+ aLine.append( nTreeNode );
+ aLine.append( " 0 R\n" );
+
+ // check if there are named destinations to emit (root must be inside the catalog)
+ if( nNamedDestinationsDictionary )
+ {
+ aLine.append("/Dests ");
+ aLine.append( nNamedDestinationsDictionary );
+ aLine.append( " 0 R\n" );
+ }
+
+ if (!m_aDocumentAttachedFiles.empty())
+ {
+ aLine.append("/Names ");
+ aLine.append("<</EmbeddedFiles <</Names [");
+ for (auto & rAttachedFile : m_aDocumentAttachedFiles)
+ {
+ aLine.append('<');
+ PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine);
+ aLine.append('>');
+ aLine.append(' ');
+ appendObjectReference(rAttachedFile.mnObjectId, aLine);
+ }
+ aLine.append("]>>>>");
+ aLine.append("\n" );
+ }
+
+ if( m_aContext.PageLayout != PDFWriter::DefaultLayout )
+ switch( m_aContext.PageLayout )
+ {
+ default :
+ case PDFWriter::SinglePage :
+ aLine.append( "/PageLayout/SinglePage\n" );
+ break;
+ case PDFWriter::Continuous :
+ aLine.append( "/PageLayout/OneColumn\n" );
+ break;
+ case PDFWriter::ContinuousFacing :
+ // the flag m_aContext.FirstPageLeft below is used to set the page on the left side
+ aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
+ break;
+ }
+ if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode )
+ switch( m_aContext.PDFDocumentMode )
+ {
+ default :
+ aLine.append( "/PageMode/UseNone\n" );
+ break;
+ case PDFWriter::UseOutlines :
+ aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
+ break;
+ case PDFWriter::UseThumbs :
+ aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
+ break;
+ }
+ else if( m_aContext.OpenInFullScreenMode )
+ aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
+
+ OStringBuffer aInitPageRef;
+ if( m_aContext.InitialPage >= 0 && o3tl::make_unsigned(m_aContext.InitialPage) < m_aPages.size() )
+ {
+ aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject );
+ aInitPageRef.append( " 0 R" );
+ }
+ else
+ aInitPageRef.append( "0" );
+
+ switch( m_aContext.PDFDocumentAction )
+ {
+ case PDFWriter::ActionDefault : //do nothing, this is the Acrobat default
+ default:
+ if( aInitPageRef.getLength() > 1 )
+ {
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef );
+ aLine.append( " /XYZ null null 0]\n" );
+ }
+ break;
+ case PDFWriter::FitInWindow :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef );
+ aLine.append( " /Fit]\n" ); //Open fit page
+ break;
+ case PDFWriter::FitWidth :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef );
+ aLine.append( " /FitH 842]\n" ); //Open fit width, default A4 height in pt, OK to use hardcoded value here?
+ break;
+ case PDFWriter::FitVisible :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef );
+ aLine.append( " /FitBH 842]\n" ); //Open fit visible, , default A4 height in pt, OK to use hardcoded value here?
+ break;
+ case PDFWriter::ActionZoom :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef );
+ aLine.append( " /XYZ null null " );
+ if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 )
+ aLine.append( static_cast<double>(m_aContext.Zoom)/100.0 );
+ else
+ aLine.append( "0" );
+ aLine.append( "]\n" );
+ break;
+ }
+
+ // viewer preferences, if we had some, then emit
+ if( m_aContext.HideViewerToolbar ||
+ (!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) ||
+ m_aContext.HideViewerMenubar ||
+ m_aContext.HideViewerWindowControls || m_aContext.FitWindow ||
+ m_aContext.CenterWindow || (m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) ||
+ m_aContext.OpenInFullScreenMode )
+ {
+ aLine.append( "/ViewerPreferences<<" );
+ if( m_aContext.HideViewerToolbar )
+ aLine.append( "/HideToolbar true\n" );
+ if( m_aContext.HideViewerMenubar )
+ aLine.append( "/HideMenubar true\n" );
+ if( m_aContext.HideViewerWindowControls )
+ aLine.append( "/HideWindowUI true\n" );
+ if( m_aContext.FitWindow )
+ aLine.append( "/FitWindow true\n" );
+ if( m_aContext.CenterWindow )
+ aLine.append( "/CenterWindow true\n" );
+ if (!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle)
+ aLine.append( "/DisplayDocTitle true\n" );
+ if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing )
+ aLine.append( "/Direction/R2L\n" );
+ if( m_aContext.OpenInFullScreenMode )
+ switch( m_aContext.PDFDocumentMode )
+ {
+ default :
+ case PDFWriter::ModeDefault :
+ aLine.append( "/NonFullScreenPageMode/UseNone\n" );
+ break;
+ case PDFWriter::UseOutlines :
+ aLine.append( "/NonFullScreenPageMode/UseOutlines\n" );
+ break;
+ case PDFWriter::UseThumbs :
+ aLine.append( "/NonFullScreenPageMode/UseThumbs\n" );
+ break;
+ }
+ aLine.append( ">>\n" );
+ }
+
+ if( nOutlineDict )
+ {
+ aLine.append( "/Outlines " );
+ aLine.append( nOutlineDict );
+ aLine.append( " 0 R\n" );
+ }
+ if( nStructureDict )
+ {
+ aLine.append( "/StructTreeRoot " );
+ aLine.append( nStructureDict );
+ aLine.append( " 0 R\n" );
+ }
+ if( !m_aContext.DocumentLocale.Language.isEmpty() )
+ {
+ /* PDF allows only RFC 3066, see above in emitStructure(). */
+ LanguageTag aLanguageTag( m_aContext.DocumentLocale);
+ OUString aLanguage, aScript, aCountry;
+ aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
+ if (!aLanguage.isEmpty())
+ {
+ OUStringBuffer aLocBuf( 16 );
+ aLocBuf.append( aLanguage );
+ if( !aCountry.isEmpty() )
+ {
+ aLocBuf.append( '-' );
+ aLocBuf.append( aCountry );
+ }
+ aLine.append( "/Lang" );
+ appendLiteralStringEncrypt( aLocBuf, m_nCatalogObject, aLine );
+ aLine.append( "\n" );
+ }
+ }
+ if (m_aContext.Tagged)
+ {
+ aLine.append( "/MarkInfo<</Marked true>>\n" );
+ }
+ if( !m_aWidgets.empty() )
+ {
+ aLine.append( "/AcroForm<</Fields[\n" );
+ int nWidgets = m_aWidgets.size();
+ int nOut = 0;
+ for( int j = 0; j < nWidgets; j++ )
+ {
+ // output only root fields
+ if( m_aWidgets[j].m_nParent < 1 )
+ {
+ aLine.append( m_aWidgets[j].m_nObject );
+ aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " );
+ }
+ }
+ aLine.append( "\n]" );
+
+#if HAVE_FEATURE_NSS
+ if (m_nSignatureObject != -1)
+ aLine.append( "/SigFlags 3");
+#endif
+
+ aLine.append( "/DR " );
+ aLine.append( getResourceDictObj() );
+ aLine.append( " 0 R" );
+ // NeedAppearances must not be used if PDF is signed
+ if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3
+#if HAVE_FEATURE_NSS
+ || ( m_nSignatureObject != -1 )
+#endif
+ )
+ aLine.append( ">>\n" );
+ else
+ aLine.append( "/NeedAppearances true>>\n" );
+ }
+
+ //check if there is a Metadata object
+ if( nOutputIntentObject )
+ {
+ aLine.append("/OutputIntents[");
+ aLine.append( nOutputIntentObject );
+ aLine.append( " 0 R]" );
+ }
+
+ if( nMetadataObject )
+ {
+ aLine.append("/Metadata ");
+ aLine.append( nMetadataObject );
+ aLine.append( " 0 R" );
+ }
+
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ return writeBuffer( aLine );
+}
+
+#if HAVE_FEATURE_NSS
+
+bool PDFWriterImpl::emitSignature()
+{
+ if( !updateObject( m_nSignatureObject ) )
+ return false;
+
+ OStringBuffer aLine( 0x5000 );
+ aLine.append( m_nSignatureObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append("<</Contents <" );
+
+ sal_uInt64 nOffset = ~0U;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
+
+ m_nSignatureContentOffset = nOffset + aLine.getLength();
+
+ // reserve some space for the PKCS#7 object
+ OStringBuffer aContentFiller( MAX_SIGNATURE_CONTENT_LENGTH );
+ comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0');
+ aLine.append( aContentFiller );
+ aLine.append( ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached");
+
+ if( !m_aContext.DocumentInfo.Author.isEmpty() )
+ {
+ aLine.append( "/Name" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, m_nSignatureObject, aLine );
+ }
+
+ aLine.append( " /M ");
+ appendLiteralStringEncrypt( m_aCreationDateString, m_nSignatureObject, aLine );
+
+ aLine.append( " /ByteRange [ 0 ");
+ aLine.append( m_nSignatureContentOffset - 1 );
+ aLine.append( " " );
+ aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 );
+ aLine.append( " " );
+
+ m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength();
+
+ // mark the last ByteRange no and add some space. Now, we don't know
+ // how many bytes we need for this ByteRange value
+ // The real value will be overwritten in the finalizeSignature method
+ OStringBuffer aByteRangeFiller( 100 );
+ comphelper::string::padToLength(aByteRangeFiller, 100, ' ');
+ aLine.append( aByteRangeFiller );
+ aLine.append(" /Filter/Adobe.PPKMS");
+
+ //emit reason, location and contactinfo
+ if ( !m_aContext.SignReason.isEmpty() )
+ {
+ aLine.append("/Reason");
+ appendUnicodeTextStringEncrypt( m_aContext.SignReason, m_nSignatureObject, aLine );
+ }
+
+ if ( !m_aContext.SignLocation.isEmpty() )
+ {
+ aLine.append("/Location");
+ appendUnicodeTextStringEncrypt( m_aContext.SignLocation, m_nSignatureObject, aLine );
+ }
+
+ if ( !m_aContext.SignContact.isEmpty() )
+ {
+ aLine.append("/ContactInfo");
+ appendUnicodeTextStringEncrypt( m_aContext.SignContact, m_nSignatureObject, aLine );
+ }
+
+ aLine.append(" >>\nendobj\n\n" );
+
+ return writeBuffer( aLine );
+}
+
+bool PDFWriterImpl::finalizeSignature()
+{
+ if (!m_aContext.SignCertificate.is())
+ return false;
+
+ // 1- calculate last ByteRange value
+ sal_uInt64 nOffset = ~0U;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
+
+ sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
+
+ // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position
+ sal_uInt64 nWritten = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset) ) );
+ OString aByteRangeNo = OString::number( nLastByteRangeNo ) + " ]";
+
+ if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None)
+ {
+ CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset)) );
+ return false;
+ }
+
+ // 3- create the PKCS#7 object using NSS
+
+ // Prepare buffer and calculate PDF file digest
+ CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, 0)) );
+
+ std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]);
+ sal_uInt64 bytesRead1;
+
+ //FIXME: Check if hash is calculated from the correct byterange
+ if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) ||
+ bytesRead1 != static_cast<sal_uInt64>(m_nSignatureContentOffset) - 1)
+ {
+ SAL_WARN("vcl.pdfwriter", "First buffer read failed");
+ return false;
+ }
+
+ std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]);
+ sal_uInt64 bytesRead2;
+
+ if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) ||
+ osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) ||
+ bytesRead2 != static_cast<sal_uInt64>(nLastByteRangeNo))
+ {
+ SAL_WARN("vcl.pdfwriter", "Second buffer read failed");
+ return false;
+ }
+
+ OStringBuffer aCMSHexBuffer;
+ svl::crypto::Signing aSigning(m_aContext.SignCertificate);
+ aSigning.AddDataRange(buffer1.get(), bytesRead1);
+ aSigning.AddDataRange(buffer2.get(), bytesRead2);
+ aSigning.SetSignTSA(m_aContext.SignTSA);
+ aSigning.SetSignPassword(m_aContext.SignPassword);
+ if (!aSigning.Sign(aCMSHexBuffer))
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed");
+ return false;
+ }
+
+ assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
+
+ // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object
+ nWritten = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset)) );
+ m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten);
+
+ return osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset);
+}
+
+#endif //HAVE_FEATURE_NSS
+
+sal_Int32 PDFWriterImpl::emitInfoDict( )
+{
+ sal_Int32 nObject = createObject();
+
+ if( updateObject( nObject ) )
+ {
+ OStringBuffer aLine( 1024 );
+ aLine.append( nObject );
+ aLine.append( " 0 obj\n"
+ "<<" );
+ if( !m_aContext.DocumentInfo.Title.isEmpty() )
+ {
+ aLine.append( "/Title" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Title, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Author.isEmpty() )
+ {
+ aLine.append( "/Author" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Subject.isEmpty() )
+ {
+ aLine.append( "/Subject" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Subject, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Keywords.isEmpty() )
+ {
+ aLine.append( "/Keywords" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Keywords, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Creator.isEmpty() )
+ {
+ aLine.append( "/Creator" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Creator, nObject, aLine );
+ aLine.append( "\n" );
+ }
+ if( !m_aContext.DocumentInfo.Producer.isEmpty() )
+ {
+ aLine.append( "/Producer" );
+ appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Producer, nObject, aLine );
+ aLine.append( "\n" );
+ }
+
+ aLine.append( "/CreationDate" );
+ appendLiteralStringEncrypt( m_aCreationDateString, nObject, aLine );
+ aLine.append( ">>\nendobj\n\n" );
+ if( ! writeBuffer( aLine ) )
+ nObject = 0;
+ }
+ else
+ nObject = 0;
+
+ return nObject;
+}
+
+// Part of this function may be shared with method appendDest.
+sal_Int32 PDFWriterImpl::emitNamedDestinations()
+{
+ sal_Int32 nCount = m_aNamedDests.size();
+ if( nCount <= 0 )
+ return 0;//define internal error
+
+ //get the object number for all the destinations
+ sal_Int32 nObject = createObject();
+
+ if( updateObject( nObject ) )
+ {
+ //emit the dictionary
+ OStringBuffer aLine( 1024 );
+ aLine.append( nObject );
+ aLine.append( " 0 obj\n"
+ "<<" );
+
+ sal_Int32 nDestID;
+ for( nDestID = 0; nDestID < nCount; nDestID++ )
+ {
+ const PDFNamedDest& rDest = m_aNamedDests[ nDestID ];
+ // In order to correctly function both under an Internet browser and
+ // directly with a reader (provided the reader has the feature) we
+ // need to set the name of the destination the same way it will be encoded
+ // in an Internet link
+ INetURLObject aLocalURL( u"http://ahost.ax" ); //dummy location, won't be used
+ aLocalURL.SetMark( rDest.m_aDestName );
+
+ const OUString aName = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as
+ // in link creation ( see PDFWriterImpl::emitLinkAnnotations )
+ const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
+
+ aLine.append( '/' );
+ appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog )
+ aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function
+ //maps the preceding character properly
+ aLine.append( rDestPage.m_nPageObject );
+ aLine.append( " 0 R" );
+
+ switch( rDest.m_eType )
+ {
+ case PDFWriter::DestAreaType::XYZ:
+ default:
+ aLine.append( "/XYZ " );
+ appendFixedInt( rDest.m_aRect.Left(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Bottom(), aLine );
+ aLine.append( " 0" );
+ break;
+ case PDFWriter::DestAreaType::FitRectangle:
+ aLine.append( "/FitR " );
+ appendFixedInt( rDest.m_aRect.Left(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Top(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Right(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rDest.m_aRect.Bottom(), aLine );
+ break;
+ }
+ aLine.append( "]\n" );
+ }
+
+ //close
+ aLine.append( ">>\nendobj\n\n" );
+ if( ! writeBuffer( aLine ) )
+ nObject = 0;
+ }
+ else
+ nObject = 0;
+
+ return nObject;
+}
+
+// emits the output intent dictionary
+sal_Int32 PDFWriterImpl::emitOutputIntent()
+{
+ if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 )
+ return 0;
+
+ //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1
+
+ OStringBuffer aLine( 1024 );
+ sal_Int32 nICCObject = createObject();
+ sal_Int32 nStreamLengthObject = createObject();
+
+ aLine.append( nICCObject );
+// sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16)
+ aLine.append( " 0 obj\n<</N 3/Length " );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 R" );
+ if (!g_bDebugDisableCompression)
+ aLine.append( "/Filter/FlateDecode" );
+ aLine.append( ">>\nstream\n" );
+ if ( !updateObject( nICCObject ) ) return 0;
+ if ( !writeBuffer( aLine ) ) return 0;
+ //get file position
+ sal_uInt64 nBeginStreamPos = 0;
+ if (osl::File::E_None != m_aFile.getPos(nBeginStreamPos))
+ return 0;
+ beginCompression();
+ checkAndEnableStreamEncryption( nICCObject );
+ cmsHPROFILE hProfile = cmsCreate_sRGBProfile();
+ //force ICC profile version 2.1
+ cmsSetProfileVersion(hProfile, 2.1);
+ cmsUInt32Number nBytesNeeded = 0;
+ cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded);
+ if (!nBytesNeeded)
+ return 0;
+ std::vector<unsigned char> aBuffer(nBytesNeeded);
+ cmsSaveProfileToMem(hProfile, aBuffer.data(), &nBytesNeeded);
+ cmsCloseProfile(hProfile);
+ bool written = writeBufferBytes( aBuffer.data(), static_cast<sal_Int32>(aBuffer.size()) );
+ disableStreamEncryption();
+ endCompression();
+
+ sal_uInt64 nEndStreamPos = 0;
+ if (m_aFile.getPos(nEndStreamPos) != osl::File::E_None)
+ return 0;
+
+ if( !written )
+ return 0;
+ if( ! writeBuffer( "\nendstream\nendobj\n\n" ) )
+ return 0 ;
+ aLine.setLength( 0 );
+
+ //emit the stream length object
+ if ( !updateObject( nStreamLengthObject ) ) return 0;
+ aLine.setLength( 0 );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
+ aLine.append( "\nendobj\n\n" );
+ if ( !writeBuffer( aLine ) ) return 0;
+ aLine.setLength( 0 );
+
+ //emit the OutputIntent dictionary
+ sal_Int32 nOIObject = createObject();
+ if ( !updateObject( nOIObject ) ) return 0;
+ aLine.append( nOIObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier");
+
+ appendLiteralStringEncrypt( std::string_view("sRGB IEC61966-2.1") ,nOIObject, aLine );
+ aLine.append("/DestOutputProfile ");
+ aLine.append( nICCObject );
+ aLine.append( " 0 R>>\nendobj\n\n" );
+ if ( !writeBuffer( aLine ) ) return 0;
+
+ return nOIObject;
+}
+
+static void lcl_assignMeta(std::u16string_view aValue, OString& aMeta)
+{
+ if (!aValue.empty())
+ {
+ aMeta = OUStringToOString(comphelper::string::encodeForXml(aValue), RTL_TEXTENCODING_UTF8);
+ }
+}
+
+static void lcl_assignMeta(const css::uno::Sequence<OUString>& rValues, std::vector<OString>& rMeta)
+{
+ if (!rValues.hasElements())
+ return;
+
+ std::vector<OString> aNewMetaVector;
+ aNewMetaVector.reserve(rValues.getLength());
+
+ for (const OUString& rValue : rValues)
+ {
+ aNewMetaVector.emplace_back(
+ OUStringToOString(comphelper::string::encodeForXml(rValue), RTL_TEXTENCODING_UTF8));
+ }
+
+ rMeta = std::move(aNewMetaVector);
+}
+
+// emits the document metadata
+sal_Int32 PDFWriterImpl::emitDocumentMetadata()
+{
+ if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 && !m_bIsPDF_UA)
+ return 0;
+
+ //get the object number for all the destinations
+ sal_Int32 nObject = createObject();
+
+ if( updateObject( nObject ) )
+ {
+ pdf::XmpMetadata aMetadata;
+
+ if (m_bIsPDF_A1)
+ aMetadata.mnPDF_A = 1;
+ else if (m_bIsPDF_A2)
+ aMetadata.mnPDF_A = 2;
+ else if (m_bIsPDF_A3)
+ aMetadata.mnPDF_A = 3;
+
+ aMetadata.mbPDF_UA = m_bIsPDF_UA;
+
+ lcl_assignMeta(m_aContext.DocumentInfo.Title, aMetadata.msTitle);
+ lcl_assignMeta(m_aContext.DocumentInfo.Author, aMetadata.msAuthor);
+ lcl_assignMeta(m_aContext.DocumentInfo.Subject, aMetadata.msSubject);
+ lcl_assignMeta(m_aContext.DocumentInfo.Producer, aMetadata.msProducer);
+ aMetadata.msPDFVersion = getPDFVersionStr(m_aContext.Version);
+ lcl_assignMeta(m_aContext.DocumentInfo.Keywords, aMetadata.msKeywords);
+ lcl_assignMeta(m_aContext.DocumentInfo.Contributor, aMetadata.maContributor);
+ lcl_assignMeta(m_aContext.DocumentInfo.Coverage, aMetadata.msCoverage);
+ lcl_assignMeta(m_aContext.DocumentInfo.Identifier, aMetadata.msIdentifier);
+ lcl_assignMeta(m_aContext.DocumentInfo.Publisher, aMetadata.maPublisher);
+ lcl_assignMeta(m_aContext.DocumentInfo.Relation, aMetadata.maRelation);
+ lcl_assignMeta(m_aContext.DocumentInfo.Rights, aMetadata.msRights);
+ lcl_assignMeta(m_aContext.DocumentInfo.Source, aMetadata.msSource);
+ lcl_assignMeta(m_aContext.DocumentInfo.Type, aMetadata.msType);
+ lcl_assignMeta(m_aContext.DocumentInfo.Creator, aMetadata.m_sCreatorTool);
+ aMetadata.m_sCreateDate = m_aCreationMetaDateString;
+
+ OStringBuffer aMetadataObj( 1024 );
+
+ aMetadataObj.append( nObject );
+ aMetadataObj.append( " 0 obj\n" );
+
+ aMetadataObj.append( "<</Type/Metadata/Subtype/XML/Length " );
+
+ aMetadataObj.append( sal_Int32(aMetadata.getSize()) );
+ aMetadataObj.append( ">>\nstream\n" );
+ if ( !writeBuffer( aMetadataObj ) )
+ return 0;
+ //emit the stream
+ if ( !writeBufferBytes( aMetadata.getData(), aMetadata.getSize() ) )
+ return 0;
+
+ if( ! writeBuffer( "\nendstream\nendobj\n\n" ) )
+ nObject = 0;
+ }
+ else
+ nObject = 0;
+
+ return nObject;
+}
+
+bool PDFWriterImpl::emitTrailer()
+{
+ // emit doc info
+ sal_Int32 nDocInfoObject = emitInfoDict( );
+
+ sal_Int32 nSecObject = 0;
+
+ if( m_aContext.Encryption.Encrypt() )
+ {
+ //emit the security information
+ //must be emitted as indirect dictionary object, since
+ //Acrobat Reader 5 works only with this kind of implementation
+ nSecObject = createObject();
+
+ if( updateObject( nSecObject ) )
+ {
+ OStringBuffer aLineS( 1024 );
+ aLineS.append( nSecObject );
+ aLineS.append( " 0 obj\n"
+ "<</Filter/Standard/V " );
+ // check the version
+ aLineS.append( "2/Length 128/R 3" );
+
+ // emit the owner password, must not be encrypted
+ aLineS.append( "/O(" );
+ appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.OValue.data()), sal_Int32(m_aContext.Encryption.OValue.size()), aLineS );
+ aLineS.append( ")/U(" );
+ appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.UValue.data()), sal_Int32(m_aContext.Encryption.UValue.size()), aLineS );
+ aLineS.append( ")/P " );// the permission set
+ aLineS.append( m_nAccessPermissions );
+ aLineS.append( ">>\nendobj\n\n" );
+ if( !writeBuffer( aLineS ) )
+ nSecObject = 0;
+ }
+ else
+ nSecObject = 0;
+ }
+ // emit xref table
+ // remember start
+ sal_uInt64 nXRefOffset = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nXRefOffset )) );
+ CHECK_RETURN( writeBuffer( "xref\n" ) );
+
+ sal_Int32 nObjects = m_aObjects.size();
+ OStringBuffer aLine;
+ aLine.append( "0 " );
+ aLine.append( static_cast<sal_Int32>(nObjects+1) );
+ aLine.append( "\n" );
+ aLine.append( "0000000000 65535 f \n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+
+ for( sal_Int32 i = 0; i < nObjects; i++ )
+ {
+ aLine.setLength( 0 );
+ OString aOffset = OString::number( m_aObjects[i] );
+ for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ )
+ aLine.append( '0' );
+ aLine.append( aOffset );
+ aLine.append( " 00000 n \n" );
+ SAL_WARN_IF( aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ }
+
+ // prepare document checksum
+ OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 );
+ ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize());
+ for (sal_uInt8 i : nMD5Sum)
+ appendHex( i, aDocChecksum );
+ // document id set in setDocInfo method
+ // emit trailer
+ aLine.setLength( 0 );
+ aLine.append( "trailer\n"
+ "<</Size " );
+ aLine.append( static_cast<sal_Int32>(nObjects+1) );
+ aLine.append( "/Root " );
+ aLine.append( m_nCatalogObject );
+ aLine.append( " 0 R\n" );
+ if( nSecObject )
+ {
+ aLine.append( "/Encrypt ");
+ aLine.append( nSecObject );
+ aLine.append( " 0 R\n" );
+ }
+ if( nDocInfoObject )
+ {
+ aLine.append( "/Info " );
+ aLine.append( nDocInfoObject );
+ aLine.append( " 0 R\n" );
+ }
+ if( ! m_aContext.Encryption.DocumentIdentifier.empty() )
+ {
+ aLine.append( "/ID [ <" );
+ for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
+ {
+ appendHex( sal_Int8(item), aLine );
+ }
+ aLine.append( ">\n"
+ "<" );
+ for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
+ {
+ appendHex( sal_Int8(item), aLine );
+ }
+ aLine.append( "> ]\n" );
+ }
+ if( !aDocChecksum.isEmpty() )
+ {
+ aLine.append( "/DocChecksum /" );
+ aLine.append( aDocChecksum );
+ aLine.append( "\n" );
+ }
+
+ if (!m_aDocumentAttachedFiles.empty())
+ {
+ aLine.append( "/AdditionalStreams [" );
+ for (auto const& rAttachedFile : m_aDocumentAttachedFiles)
+ {
+ aLine.append( "/" );
+ appendName(rAttachedFile.maMimeType, aLine);
+ aLine.append(" ");
+ appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine);
+ aLine.append("\n");
+ }
+ aLine.append( "]\n" );
+ }
+
+ aLine.append( ">>\n"
+ "startxref\n" );
+ aLine.append( static_cast<sal_Int64>(nXRefOffset) );
+ aLine.append( "\n"
+ "%%EOF\n" );
+ return writeBuffer( aLine );
+}
+
+namespace {
+
+struct AnnotationSortEntry
+{
+ sal_Int32 nTabOrder;
+ sal_Int32 nObject;
+ sal_Int32 nWidgetIndex;
+
+ AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) :
+ nTabOrder( nTab ),
+ nObject( nObj ),
+ nWidgetIndex( nI )
+ {}
+};
+
+struct AnnotSortContainer
+{
+ o3tl::sorted_vector< sal_Int32 > aObjects;
+ std::vector< AnnotationSortEntry > aSortedAnnots;
+};
+
+struct AnnotSorterLess
+{
+ std::vector<PDFWidget>& m_rWidgets;
+
+ explicit AnnotSorterLess( std::vector<PDFWidget>& rWidgets ) : m_rWidgets( rWidgets ) {}
+
+ bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight )
+ {
+ if( rLeft.nTabOrder < rRight.nTabOrder )
+ return true;
+ if( rRight.nTabOrder < rLeft.nTabOrder )
+ return false;
+ if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 )
+ return false;
+ if( rRight.nWidgetIndex < 0 )
+ return true;
+ if( rLeft.nWidgetIndex < 0 )
+ return false;
+ // remember: widget rects are in PDF coordinates, so they are ordered down up
+ if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() >
+ m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() )
+ return true;
+ if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() >
+ m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() )
+ return false;
+ if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() <
+ m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() )
+ return true;
+ return false;
+ }
+};
+
+}
+
+void PDFWriterImpl::sortWidgets()
+{
+ // sort widget annotations on each page as per their
+ // TabOrder attribute
+ std::unordered_map< sal_Int32, AnnotSortContainer > sorted;
+ int nWidgets = m_aWidgets.size();
+ for( int nW = 0; nW < nWidgets; nW++ )
+ {
+ const PDFWidget& rWidget = m_aWidgets[nW];
+ if( rWidget.m_nPage >= 0 )
+ {
+ AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ];
+ // optimize vector allocation
+ if( rCont.aSortedAnnots.empty() )
+ rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() );
+ // insert widget to tab sorter
+ // RadioButtons are not page annotations, only their individual check boxes are
+ if( rWidget.m_eType != PDFWriter::RadioButton )
+ {
+ rCont.aObjects.insert( rWidget.m_nObject );
+ rCont.aSortedAnnots.emplace_back( rWidget.m_nTabOrder, rWidget.m_nObject, nW );
+ }
+ }
+ }
+ for (auto & item : sorted)
+ {
+ // append entries for non widget annotations
+ PDFPage& rPage = m_aPages[ item.first ];
+ unsigned int nAnnots = rPage.m_aAnnotations.size();
+ for( unsigned int nA = 0; nA < nAnnots; nA++ )
+ if( item.second.aObjects.find( rPage.m_aAnnotations[nA] ) == item.second.aObjects.end())
+ item.second.aSortedAnnots.emplace_back( 10000, rPage.m_aAnnotations[nA], -1 );
+
+ AnnotSorterLess aLess( m_aWidgets );
+ std::stable_sort( item.second.aSortedAnnots.begin(), item.second.aSortedAnnots.end(), aLess );
+ // sanity check
+ if( item.second.aSortedAnnots.size() == nAnnots)
+ {
+ for( unsigned int nA = 0; nA < nAnnots; nA++ )
+ rPage.m_aAnnotations[nA] = item.second.aSortedAnnots[nA].nObject;
+ }
+ else
+ {
+ SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" );
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions "
+ "on page nr " << item.first << ", " <<
+ static_cast<tools::Long>(item.second.aSortedAnnots.size()) << " sorted and " <<
+ static_cast<tools::Long>(nAnnots) << " unsorted");
+ }
+ }
+
+ // FIXME: implement tab order in structure tree for PDF 1.5
+}
+
+bool PDFWriterImpl::emit()
+{
+ endPage();
+
+ // resort structure tree and annotations if necessary
+ // needed for widget tab order
+ sortWidgets();
+
+#if HAVE_FEATURE_NSS
+ if( m_aContext.SignPDF )
+ {
+ // sign the document
+ PDFWriter::SignatureWidget aSignature;
+ aSignature.Name = "Signature1";
+ createControl( aSignature, 0 );
+ }
+#endif
+
+ // emit catalog
+ CHECK_RETURN( emitCatalog() );
+
+#if HAVE_FEATURE_NSS
+ if (m_nSignatureObject != -1) // if document is signed, emit sigdict
+ {
+ if( !emitSignature() )
+ {
+ m_aErrors.insert( PDFWriter::Error_Signature_Failed );
+ return false;
+ }
+ }
+#endif
+
+ // emit trailer
+ CHECK_RETURN( emitTrailer() );
+
+#if HAVE_FEATURE_NSS
+ if (m_nSignatureObject != -1) // finalize the signature
+ {
+ if( !finalizeSignature() )
+ {
+ m_aErrors.insert( PDFWriter::Error_Signature_Failed );
+ return false;
+ }
+ }
+#endif
+
+ m_aFile.close();
+ m_bOpen = false;
+
+ return true;
+}
+
+
+sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont )
+{
+ Push();
+
+ SetFont( i_rFont );
+
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace();
+ sal_Int32 nFontID = 0;
+ auto it = m_aSystemFonts.find( pFace );
+ if( it != m_aSystemFonts.end() )
+ nFontID = it->second.m_nNormalFontID;
+ else
+ {
+ nFontID = m_nNextFID++;
+ m_aSystemFonts[ pFace ] = EmbedFont();
+ m_aSystemFonts[ pFace ].m_pFontInstance = const_cast<LogicalFontInstance*>(pFontInstance);
+ m_aSystemFonts[ pFace ].m_nNormalFontID = nFontID;
+ }
+
+ Pop();
+ return nFontID;
+}
+
+void PDFWriterImpl::registerSimpleGlyph(const sal_GlyphId nFontGlyphId,
+ const vcl::font::PhysicalFontFace* pFace,
+ const std::vector<sal_Ucs>& rCodeUnits,
+ sal_Int32 nGlyphWidth,
+ sal_uInt8& nMappedGlyph,
+ sal_Int32& nMappedFontObject)
+{
+ FontSubset& rSubset = m_aSubsets[ pFace ];
+ // search for font specific glyphID
+ auto it = rSubset.m_aMapping.find( nFontGlyphId );
+ if( it != rSubset.m_aMapping.end() )
+ {
+ nMappedFontObject = it->second.m_nFontID;
+ nMappedGlyph = it->second.m_nSubsetGlyphID;
+ }
+ else
+ {
+ // create new subset if necessary
+ if( rSubset.m_aSubsets.empty()
+ || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) )
+ {
+ rSubset.m_aSubsets.emplace_back( m_nNextFID++ );
+ }
+
+ // copy font id
+ nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
+ // create new glyph in subset
+ sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1);
+ nMappedGlyph = nNewId;
+
+ // add new glyph to emitted font subset
+ GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ];
+ rNewGlyphEmit.setGlyphId( nNewId );
+ rNewGlyphEmit.setGlyphWidth(XUnits(pFace->UnitsPerEm(), nGlyphWidth));
+ for (const auto nCode : rCodeUnits)
+ rNewGlyphEmit.addCode(nCode);
+
+ // add new glyph to font mapping
+ Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ];
+ rNewGlyph.m_nFontID = nMappedFontObject;
+ rNewGlyph.m_nSubsetGlyphID = nNewId;
+ }
+}
+
+void PDFWriterImpl::registerGlyph(const sal_GlyphId nFontGlyphId,
+ const vcl::font::PhysicalFontFace* pFace,
+ const LogicalFontInstance* pFont,
+ const std::vector<sal_Ucs>& rCodeUnits, sal_Int32 nGlyphWidth,
+ sal_uInt8& nMappedGlyph, sal_Int32& nMappedFontObject)
+{
+ auto bVariations = !pFace->GetVariations(*pFont).empty();
+ // tdf#155161
+ // PDF doesn’t support CFF2 table and we currently don’t convert them to
+ // Type 1 (like we do with CFF table), so treat it like fonts with
+ // variations and embed as Type 3 fonts.
+ if (!pFace->GetRawFontData(HB_TAG('C', 'F', 'F', '2')).empty())
+ bVariations = true;
+
+ if (pFace->IsColorFont() || bVariations)
+ {
+ // Font has colors, check if this glyph has color layers or bitmap.
+ tools::Rectangle aRect;
+ auto aLayers = pFace->GetGlyphColorLayers(nFontGlyphId);
+ auto aBitmap = pFace->GetGlyphColorBitmap(nFontGlyphId, aRect);
+ if (!aLayers.empty() || !aBitmap.empty() || bVariations)
+ {
+ auto& rSubset = m_aType3Fonts[pFace];
+ auto it = rSubset.m_aMapping.find(nFontGlyphId);
+ if (it != rSubset.m_aMapping.end())
+ {
+ nMappedFontObject = it->second.m_nFontID;
+ nMappedGlyph = it->second.m_nSubsetGlyphID;
+ }
+ else
+ {
+ // create new subset if necessary
+ if (rSubset.m_aSubsets.empty()
+ || (rSubset.m_aSubsets.back().m_aMapping.size() > 254))
+ {
+ rSubset.m_aSubsets.emplace_back(m_nNextFID++);
+ }
+
+ // copy font id
+ nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
+ // create new glyph in subset
+ sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(
+ rSubset.m_aSubsets.back().m_aMapping.size() + 1);
+ nMappedGlyph = nNewId;
+
+ // add new glyph to emitted font subset
+ auto& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[nFontGlyphId];
+ rNewGlyphEmit.setGlyphId(nNewId);
+ rNewGlyphEmit.setGlyphWidth(nGlyphWidth);
+ for (const auto nCode : rCodeUnits)
+ rNewGlyphEmit.addCode(nCode);
+
+ // add color layers to the glyphs
+ if (!aLayers.empty())
+ {
+ for (const auto& aLayer : aLayers)
+ {
+ sal_uInt8 nLayerGlyph;
+ sal_Int32 nLayerFontID;
+ registerSimpleGlyph(aLayer.nGlyphIndex, pFace, rCodeUnits, nGlyphWidth,
+ nLayerGlyph, nLayerFontID);
+
+ rNewGlyphEmit.addColorLayer(
+ { nLayerFontID, nLayerGlyph, aLayer.nColorIndex });
+ }
+ }
+ else if (!aBitmap.empty())
+ rNewGlyphEmit.setColorBitmap(aBitmap, aRect);
+ else if (bVariations)
+ rNewGlyphEmit.setOutline(pFont->GetGlyphOutlineUntransformed(nFontGlyphId));
+
+ // add new glyph to font mapping
+ Glyph& rNewGlyph = rSubset.m_aMapping[nFontGlyphId];
+ rNewGlyph.m_nFontID = nMappedFontObject;
+ rNewGlyph.m_nSubsetGlyphID = nNewId;
+ }
+ return;
+ }
+ }
+
+ // If we reach here then the glyph has no color layers.
+ registerSimpleGlyph(nFontGlyphId, pFace, rCodeUnits, nGlyphWidth, nMappedGlyph,
+ nMappedFontObject);
+}
+
+void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines )
+{
+ push( PushFlags::ALL );
+
+ FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief();
+
+ Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor();
+ Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
+ Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
+ Color aReliefColor( COL_LIGHTGRAY );
+ if( aTextColor == COL_BLACK )
+ aTextColor = COL_WHITE;
+ if( aTextLineColor == COL_BLACK )
+ aTextLineColor = COL_WHITE;
+ if( aOverlineColor == COL_BLACK )
+ aOverlineColor = COL_WHITE;
+ // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct
+ if( aTextColor == COL_WHITE )
+ aReliefColor = COL_BLACK;
+
+ Font aSetFont = m_aCurrentPDFState.m_aFont;
+ aSetFont.SetRelief( FontRelief::NONE );
+ aSetFont.SetShadow( false );
+
+ aSetFont.SetColor( aReliefColor );
+ setTextLineColor( aReliefColor );
+ setOverlineColor( aReliefColor );
+ setFont( aSetFont );
+ tools::Long nOff = 1 + GetDPIX()/300;
+ if( eRelief == FontRelief::Engraved )
+ nOff = -nOff;
+
+ rLayout.DrawOffset() += Point( nOff, nOff );
+ updateGraphicsState();
+ drawLayout( rLayout, rText, bTextLines );
+
+ rLayout.DrawOffset() -= Point( nOff, nOff );
+ setTextLineColor( aTextLineColor );
+ setOverlineColor( aOverlineColor );
+ aSetFont.SetColor( aTextColor );
+ setFont( aSetFont );
+ updateGraphicsState();
+ drawLayout( rLayout, rText, bTextLines );
+
+ // clean up the mess
+ pop();
+}
+
+void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines )
+{
+ Font aSaveFont = m_aCurrentPDFState.m_aFont;
+ Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
+ Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
+
+ Font& rFont = m_aCurrentPDFState.m_aFont;
+ if( rFont.GetColor() == COL_BLACK || rFont.GetColor().GetLuminance() < 8 )
+ rFont.SetColor( COL_LIGHTGRAY );
+ else
+ rFont.SetColor( COL_BLACK );
+ rFont.SetShadow( false );
+ rFont.SetOutline( false );
+ setFont( rFont );
+ setTextLineColor( rFont.GetColor() );
+ setOverlineColor( rFont.GetColor() );
+ updateGraphicsState();
+
+ tools::Long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24);
+ if( rFont.IsOutline() )
+ nOff++;
+ rLayout.DrawBase() += basegfx::B2DPoint(nOff, nOff);
+ drawLayout( rLayout, rText, bTextLines );
+ rLayout.DrawBase() -= basegfx::B2DPoint(nOff, nOff);
+
+ setFont( aSaveFont );
+ setTextLineColor( aSaveTextLineColor );
+ setOverlineColor( aSaveOverlineColor );
+ updateGraphicsState();
+}
+
+void PDFWriterImpl::drawVerticalGlyphs(
+ const std::vector<PDFGlyph>& rGlyphs,
+ OStringBuffer& rLine,
+ const Point& rAlignOffset,
+ const Matrix3& rRotScale,
+ double fAngle,
+ double fXScale,
+ sal_Int32 nFontHeight)
+{
+ double nXOffset = 0;
+ Point aCurPos(SubPixelToLogic(rGlyphs[0].m_aPos));
+ aCurPos += rAlignOffset;
+ for( size_t i = 0; i < rGlyphs.size(); i++ )
+ {
+ // have to emit each glyph on its own
+ double fDeltaAngle = 0.0;
+ double fYScale = 1.0;
+ double fTempXScale = fXScale;
+
+ // perform artificial italics if necessary
+ double fSkew = 0.0;
+ if (rGlyphs[i].m_pFont->NeedsArtificialItalic())
+ fSkew = ARTIFICIAL_ITALIC_SKEW;
+
+ double fSkewB = fSkew;
+ double fSkewA = 0.0;
+
+ Point aDeltaPos;
+ if (rGlyphs[i].m_pGlyph->IsVertical())
+ {
+ fDeltaAngle = M_PI/2.0;
+ fYScale = fXScale;
+ fTempXScale = 1.0;
+ fSkewA = -fSkewB;
+ fSkewB = 0.0;
+ }
+ aDeltaPos += SubPixelToLogic(basegfx::B2DPoint(nXOffset / fXScale, 0)) - SubPixelToLogic(basegfx::B2DPoint());
+ if( i < rGlyphs.size()-1 )
+ // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
+ {
+ double nOffsetX = rGlyphs[i+1].m_aPos.getX() - rGlyphs[i].m_aPos.getX();
+ double nOffsetY = rGlyphs[i+1].m_aPos.getY() - rGlyphs[i].m_aPos.getY();
+ nXOffset += std::hypot(nOffsetX, nOffsetY);
+ }
+ if (!rGlyphs[i].m_pGlyph->glyphId())
+ continue;
+
+ aDeltaPos = rRotScale.transform( aDeltaPos );
+
+ Matrix3 aMat;
+ if( fSkewB != 0.0 || fSkewA != 0.0 )
+ aMat.skew( fSkewA, fSkewB );
+ aMat.scale( fTempXScale, fYScale );
+ aMat.rotate( fAngle+fDeltaAngle );
+ aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() );
+ m_aPages.back().appendMatrix3(aMat, rLine);
+ rLine.append( " Tm" );
+ if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId )
+ {
+ rLine.append( " /F" );
+ rLine.append( rGlyphs[i].m_nMappedFontId );
+ rLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nFontHeight, rLine );
+ rLine.append( " Tf" );
+ }
+ rLine.append( "<" );
+ appendHex( rGlyphs[i].m_nMappedGlyphId, rLine );
+ rLine.append( ">Tj\n" );
+ }
+}
+
+void PDFWriterImpl::drawHorizontalGlyphs(
+ const std::vector<PDFGlyph>& rGlyphs,
+ OStringBuffer& rLine,
+ const Point& rAlignOffset,
+ bool bFirst,
+ double fAngle,
+ double fXScale,
+ sal_Int32 nFontHeight,
+ sal_Int32 nPixelFontHeight)
+{
+ // horizontal (= normal) case
+
+ // fill in run end indices
+ // end is marked by index of the first glyph of the next run
+ // a run is marked by same mapped font id and same Y position
+ std::vector< sal_uInt32 > aRunEnds;
+ aRunEnds.reserve( rGlyphs.size() );
+ for( size_t i = 1; i < rGlyphs.size(); i++ )
+ {
+ if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId ||
+ rGlyphs[i].m_pFont != rGlyphs[i-1].m_pFont ||
+ rGlyphs[i].m_aPos.getY() != rGlyphs[i-1].m_aPos.getY() )
+ {
+ aRunEnds.push_back(i);
+ }
+ }
+ // last run ends at last glyph
+ aRunEnds.push_back( rGlyphs.size() );
+
+ // loop over runs of the same font
+ sal_uInt32 nBeginRun = 0;
+ for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ )
+ {
+ // setup text matrix back transformed to current coordinate system
+ Point aCurPos(SubPixelToLogic(rGlyphs[nBeginRun].m_aPos));
+ aCurPos += rAlignOffset;
+
+ // perform artificial italics if necessary
+ double fSkew = 0.0;
+ if (rGlyphs[nBeginRun].m_pFont->NeedsArtificialItalic())
+ fSkew = ARTIFICIAL_ITALIC_SKEW;
+
+ // the first run can be set with "Td" operator
+ // subsequent use of that operator would move
+ // the textline matrix relative to what was set before
+ // making use of that would drive us into rounding issues
+ Matrix3 aMat;
+ if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 )
+ {
+ m_aPages.back().appendPoint( aCurPos, rLine );
+ rLine.append( " Td " );
+ }
+ else
+ {
+ if( fSkew != 0.0 )
+ aMat.skew( 0.0, fSkew );
+ aMat.scale( fXScale, 1.0 );
+ aMat.rotate( fAngle );
+ aMat.translate( aCurPos.X(), aCurPos.Y() );
+ m_aPages.back().appendMatrix3(aMat, rLine);
+ rLine.append( " Tm\n" );
+ }
+ // set up correct font
+ rLine.append( "/F" );
+ rLine.append( rGlyphs[nBeginRun].m_nMappedFontId );
+ rLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nFontHeight, rLine );
+ rLine.append( " Tf" );
+
+ // output glyphs using Tj or TJ
+ OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 );
+ aKernedLine.append( "[<" );
+ aUnkernedLine.append( '<' );
+ appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine );
+ appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine );
+
+ aMat.invert();
+ bool bNeedKern = false;
+ for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ )
+ {
+ appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine );
+ // check if default glyph positioning is sufficient
+ const basegfx::B2DPoint aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
+ const basegfx::B2DPoint aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
+ double fAdvance = aThisPos.getX() - aPrevPos.getX();
+ fAdvance *= 1000.0 / nPixelFontHeight;
+ const double fAdjustment = rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5;
+ SAL_WARN_IF(
+ fAdjustment < SAL_MIN_INT32 || fAdjustment > SAL_MAX_INT32, "vcl.pdfwriter",
+ "adjustment " << fAdjustment << " outside 32-bit int");
+ const sal_Int32 nAdjustment = static_cast<sal_Int32>(
+ std::clamp(fAdjustment, double(SAL_MIN_INT32), double(SAL_MAX_INT32)));
+ if( nAdjustment != 0 )
+ {
+ // apply individual glyph positioning
+ bNeedKern = true;
+ aKernedLine.append( ">" );
+ aKernedLine.append( nAdjustment );
+ aKernedLine.append( "<" );
+ }
+ appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine );
+ }
+ aKernedLine.append( ">]TJ\n" );
+ aUnkernedLine.append( ">Tj\n" );
+ rLine.append( bNeedKern ? aKernedLine : aUnkernedLine );
+
+ // set beginning of next run
+ nBeginRun = aRunEnds[nRun];
+ }
+}
+
+void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines )
+{
+ // relief takes precedence over shadow (see outdev3.cxx)
+ if( m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE )
+ {
+ drawRelief( rLayout, rText, bTextLines );
+ return;
+ }
+ else if( m_aCurrentPDFState.m_aFont.IsShadow() )
+ drawShadow( rLayout, rText, bTextLines );
+
+ OStringBuffer aLine( 512 );
+
+ const int nMaxGlyphs = 256;
+
+ std::vector<sal_Ucs> aCodeUnits;
+ bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical();
+ int nIndex = 0;
+ double fXScale = 1.0;
+ sal_Int32 nPixelFontHeight = GetFontInstance()->GetFontSelectPattern().mnHeight;
+ TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
+
+ // transform font height back to current units
+ // note: the layout calculates in outdevs device pixel !!
+ sal_Int32 nFontHeight = ImplDevicePixelToLogicHeight( nPixelFontHeight );
+ if( m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
+ {
+ Font aFont( m_aCurrentPDFState.m_aFont );
+ aFont.SetAverageFontWidth( 0 );
+ FontMetric aMetric = GetFontMetric( aFont );
+ if( aMetric.GetAverageFontWidth() != m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
+ {
+ fXScale =
+ static_cast<double>(m_aCurrentPDFState.m_aFont.GetAverageFontWidth()) /
+ static_cast<double>(aMetric.GetAverageFontWidth());
+ }
+ }
+
+ // if the mapmode is distorted we need to adjust for that also
+ if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() )
+ {
+ fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY());
+ }
+
+ Degree10 nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
+ // normalize angles
+ while( nAngle < 0_deg10 )
+ nAngle += 3600_deg10;
+ nAngle = nAngle % 3600_deg10;
+ double fAngle = toRadians(nAngle);
+
+ Matrix3 aRotScale;
+ aRotScale.scale( fXScale, 1.0 );
+ if( fAngle != 0.0 )
+ aRotScale.rotate( -fAngle );
+
+ bool bPop = false;
+ bool bABold = false;
+ // artificial bold necessary ?
+ if (GetFontInstance()->NeedsArtificialBold())
+ {
+ aLine.append("q ");
+ bPop = true;
+ bABold = true;
+ }
+ // setup text colors (if necessary)
+ Color aStrokeColor( COL_TRANSPARENT );
+ Color aNonStrokeColor( COL_TRANSPARENT );
+
+ if( m_aCurrentPDFState.m_aFont.IsOutline() )
+ {
+ aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
+ aNonStrokeColor = COL_WHITE;
+ }
+ else
+ aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
+ if( bABold )
+ aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
+
+ if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor )
+ {
+ if( ! bPop )
+ aLine.append( "q " );
+ bPop = true;
+ appendStrokingColor( aStrokeColor, aLine );
+ aLine.append( "\n" );
+ }
+ if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor )
+ {
+ if( ! bPop )
+ aLine.append( "q " );
+ bPop = true;
+ appendNonStrokingColor( aNonStrokeColor, aLine );
+ aLine.append( "\n" );
+ }
+
+ // begin text object
+ aLine.append( "BT\n" );
+ // outline attribute ?
+ if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold )
+ {
+ // set correct text mode, set stroke width
+ aLine.append( "2 Tr " ); // fill, then stroke
+
+ if( m_aCurrentPDFState.m_aFont.IsOutline() )
+ {
+ // unclear what to do in case of outline and artificial bold
+ // for the time being outline wins
+ aLine.append( "0.25 w \n" );
+ }
+ else
+ {
+ double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0;
+ m_aPages.back().appendMappedLength( fW, aLine );
+ aLine.append ( " w\n" );
+ }
+ }
+
+ FontMetric aRefDevFontMetric = GetFontMetric();
+ const GlyphItem* pGlyph = nullptr;
+ const LogicalFontInstance* pGlyphFont = nullptr;
+
+ // collect the glyphs into a single array
+ std::vector< PDFGlyph > aGlyphs;
+ aGlyphs.reserve( nMaxGlyphs );
+ // first get all the glyphs and register them; coordinates still in Pixel
+ basegfx::B2DPoint aPos;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
+ {
+ const auto* pFace = pGlyphFont->GetFontFace();
+
+ aCodeUnits.clear();
+
+ // tdf#66597, tdf#115117
+ //
+ // Here is how we embed textual content in PDF files, to allow for
+ // better text extraction for complex and typography-rich text.
+ //
+ // * If there is many to one or many to many mapping, use an
+ // ActualText span embedding the original string, since ToUnicode
+ // can't handle these.
+ // * If the one glyph is used for several Unicode code points, also
+ // use ActualText since ToUnicode can map each glyph in the font
+ // only once.
+ // * Limit ActualText to single cluster at a time, since using it
+ // for whole words or sentences breaks text selection and
+ // highlighting in PDF viewers (there will be no way to tell
+ // which glyphs belong to which characters).
+ // * Keep generating (now) redundant ToUnicode entries for
+ // compatibility with old tools not supporting ActualText.
+
+ assert(pGlyph->charCount() >= 0);
+ for (int n = 0; n < pGlyph->charCount(); n++)
+ aCodeUnits.push_back(rText[pGlyph->charPos() + n]);
+
+ bool bUseActualText = false;
+
+ // If this is a start of complex cluster, use ActualText.
+ if (pGlyph->IsClusterStart())
+ bUseActualText = true;
+
+ const auto nGlyphId = pGlyph->glyphId();
+
+ // A glyph can't have more than one ToUnicode entry, use ActualText
+ // instead.
+ if (!aCodeUnits.empty() && !bUseActualText)
+ {
+ for (const auto& rSubset : m_aSubsets[pFace].m_aSubsets)
+ {
+ const auto& it = rSubset.m_aMapping.find(nGlyphId);
+ if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
+ {
+ bUseActualText = true;
+ aCodeUnits.clear();
+ }
+ }
+ }
+
+ auto nGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, pGlyph->IsVertical(), false);
+
+ sal_uInt8 nMappedGlyph;
+ sal_Int32 nMappedFontObject;
+ registerGlyph(nGlyphId, pFace, pGlyphFont, aCodeUnits, nGlyphWidth, nMappedGlyph, nMappedFontObject);
+
+ int nCharPos = -1;
+ if (bUseActualText || pGlyph->IsInCluster())
+ nCharPos = pGlyph->charPos();
+
+ aGlyphs.emplace_back(aPos,
+ pGlyph,
+ pGlyphFont,
+ XUnits(pFace->UnitsPerEm(), nGlyphWidth),
+ nMappedFontObject,
+ nMappedGlyph,
+ nCharPos);
+ }
+
+ // Avoid fill color when map mode is in pixels, the below code assumes
+ // logic map mode.
+ bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel;
+ if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel)
+ {
+ // PDF doesn't have a text fill color, so draw a rectangle before
+ // drawing the actual text.
+ push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
+ setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor());
+ // Avoid border around the rectangle for Writer shape text.
+ setLineColor(COL_TRANSPARENT);
+
+ // The rectangle is the bounding box of the text, but also includes
+ // ascent / descent to match the on-screen rendering.
+ // This is the top left of the text without ascent / descent.
+ basegfx::B2DPoint aDrawPosition(rLayout.GetDrawPosition());
+ tools::Rectangle aRectangle(SubPixelToLogic(aDrawPosition),
+ Size(ImplDevicePixelToLogicWidth(rLayout.GetTextWidth()), 0));
+ aRectangle.AdjustTop(-aRefDevFontMetric.GetAscent());
+ // This includes ascent / descent.
+ aRectangle.setHeight(aRefDevFontMetric.GetLineHeight());
+
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ if (pFontInstance->mnOrientation)
+ {
+ // Adapt rectangle for rotated text.
+ tools::Polygon aPolygon(aRectangle);
+ aPolygon.Rotate(SubPixelToLogic(aDrawPosition), pFontInstance->mnOrientation);
+ drawPolygon(aPolygon);
+ }
+ else
+ drawRectangle(aRectangle);
+
+ pop();
+ }
+
+ Point aAlignOffset;
+ if ( eAlign == ALIGN_BOTTOM )
+ aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) );
+ else if ( eAlign == ALIGN_TOP )
+ aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() );
+ if( aAlignOffset.X() || aAlignOffset.Y() )
+ aAlignOffset = aRotScale.transform( aAlignOffset );
+
+ /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original
+ string contained only one of the UTF16 BOMs
+ */
+ if( ! aGlyphs.empty() )
+ {
+ size_t nStart = 0;
+ size_t nEnd = 0;
+ while (nStart < aGlyphs.size())
+ {
+ while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos)
+ nEnd++;
+
+ std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd);
+
+ int nCharPos, nCharCount;
+ if (!aRun.front().m_pGlyph->IsRTLGlyph())
+ {
+ nCharPos = aRun.front().m_nCharPos;
+ nCharCount = aRun.front().m_pGlyph->charCount();
+ }
+ else
+ {
+ nCharPos = aRun.back().m_nCharPos;
+ nCharCount = aRun.back().m_pGlyph->charCount();
+ }
+
+ if (nCharPos >= 0 && nCharCount)
+ {
+ aLine.append("/Span<</ActualText<FEFF");
+ for (int i = 0; i < nCharCount; i++)
+ {
+ sal_Unicode aChar = rText[nCharPos + i];
+ appendHex(static_cast<sal_Int8>(aChar >> 8), aLine);
+ appendHex(static_cast<sal_Int8>(aChar & 255), aLine);
+ }
+ aLine.append( ">>>\nBDC\n" );
+ }
+
+ if (bVertical)
+ drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, nFontHeight);
+ else
+ drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, nFontHeight, nPixelFontHeight);
+
+ if (nCharPos >= 0 && nCharCount)
+ aLine.append( "EMC\n" );
+
+ nStart = nEnd;
+ }
+ }
+
+ // end textobject
+ aLine.append( "ET\n" );
+ if( bPop )
+ aLine.append( "Q\n" );
+
+ writeBuffer( aLine );
+
+ // draw eventual textlines
+ FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout();
+ FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline();
+ FontLineStyle eOverline = m_aCurrentPDFState.m_aFont.GetOverline();
+ if( bTextLines &&
+ (
+ ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) ||
+ ( eOverline != LINESTYLE_NONE && eOverline != LINESTYLE_DONTKNOW ) ||
+ ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW )
+ )
+ )
+ {
+ bool bUnderlineAbove = m_aCurrentPDFState.m_aFont.IsUnderlineAbove();
+ if( m_aCurrentPDFState.m_aFont.IsWordLineMode() )
+ {
+ basegfx::B2DPoint aStartPt;
+ double nWidth = 0;
+ nIndex = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
+ {
+ if (!pGlyph->IsSpacing())
+ {
+ if( !nWidth )
+ aStartPt = aPos;
+
+ nWidth += pGlyph->newWidth();
+ }
+ else if( nWidth > 0 )
+ {
+ drawTextLine( SubPixelToLogic(aStartPt),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ nWidth = 0;
+ }
+ }
+
+ if( nWidth > 0 )
+ {
+ drawTextLine( SubPixelToLogic(aStartPt),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+ }
+ else
+ {
+ basegfx::B2DPoint aStartPt = rLayout.GetDrawPosition();
+ int nWidth = rLayout.GetTextWidth();
+ drawTextLine( SubPixelToLogic(aStartPt),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+ }
+
+ // write eventual emphasis marks
+ if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) )
+ return;
+
+ push( PushFlags::ALL );
+
+ aLine.setLength( 0 );
+ aLine.append( "q\n" );
+
+ FontEmphasisMark nEmphMark = m_aCurrentPDFState.m_aFont.GetEmphasisMarkStyle();
+
+ tools::Long nEmphHeight;
+ if ( nEmphMark & FontEmphasisMark::PosBelow )
+ nEmphHeight = GetEmphasisDescent();
+ else
+ nEmphHeight = GetEmphasisAscent();
+
+ vcl::font::EmphasisMark aEmphasisMark(nEmphMark, ImplDevicePixelToLogicWidth(nEmphHeight), GetDPIY());
+ if ( aEmphasisMark.IsShapePolyLine() )
+ {
+ setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
+ setFillColor( COL_TRANSPARENT );
+ }
+ else
+ {
+ setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
+ setLineColor( COL_TRANSPARENT );
+ }
+
+ writeBuffer( aLine );
+
+ Point aOffset(0,0);
+ Point aOffsetVert(0,0);
+
+ if ( nEmphMark & FontEmphasisMark::PosBelow )
+ {
+ aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset() );
+ aOffsetVert = aOffset;
+ }
+ else
+ {
+ aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + aEmphasisMark.GetYOffset()) );
+ // Todo: use ideographic em-box or ideographic character face information.
+ aOffsetVert.AdjustY(-(GetFontInstance()->mxFontMetric->GetAscent() +
+ GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset()));
+ }
+
+ tools::Long nEmphWidth2 = aEmphasisMark.GetWidth() / 2;
+ tools::Long nEmphHeight2 = nEmphHeight / 2;
+ aOffset += Point( nEmphWidth2, nEmphHeight2 );
+
+ if ( eAlign == ALIGN_BOTTOM )
+ aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetDescent()) );
+ else if ( eAlign == ALIGN_TOP )
+ aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() );
+
+ tools::Rectangle aRectangle;
+ nIndex = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
+ {
+ if (!pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle))
+ continue;
+
+ if (!pGlyph->IsSpacing())
+ {
+ basegfx::B2DPoint aAdjOffset;
+ if (pGlyph->IsVertical())
+ {
+ aAdjOffset = basegfx::B2DPoint(aOffsetVert.X(), aOffsetVert.Y());
+ aAdjOffset.adjustX((-pGlyph->origWidth() + aEmphasisMark.GetWidth()) / 2);
+ }
+ else
+ {
+ aAdjOffset = basegfx::B2DPoint(aOffset.X(), aOffset.Y());
+ aAdjOffset.adjustX(aRectangle.Left() + (aRectangle.GetWidth() - aEmphasisMark.GetWidth()) / 2 );
+ }
+
+ aAdjOffset = aRotScale.transform( aAdjOffset );
+
+ aAdjOffset -= basegfx::B2DPoint(nEmphWidth2, nEmphHeight2);
+
+ basegfx::B2DPoint aMarkDevPos(aPos);
+ aMarkDevPos += aAdjOffset;
+ Point aMarkPos = SubPixelToLogic(aMarkDevPos);
+ drawEmphasisMark( aMarkPos.X(), aMarkPos.Y(),
+ aEmphasisMark.GetShape(), aEmphasisMark.IsShapePolyLine(),
+ aEmphasisMark.GetRect1(), aEmphasisMark.GetRect2() );
+ }
+ }
+
+ writeBuffer( "Q\n" );
+ pop();
+
+}
+
+void PDFWriterImpl::drawEmphasisMark( tools::Long nX, tools::Long nY,
+ const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
+ const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
+{
+ // TODO: pass nWidth as width of this mark
+ // long nWidth = 0;
+
+ if ( rPolyPoly.Count() )
+ {
+ if ( bPolyLine )
+ {
+ tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
+ aPoly.Move( nX, nY );
+ drawPolyLine( aPoly );
+ }
+ else
+ {
+ tools::PolyPolygon aPolyPoly = rPolyPoly;
+ aPolyPoly.Move( nX, nY );
+ drawPolyPolygon( aPolyPoly );
+ }
+ }
+
+ if ( !rRect1.IsEmpty() )
+ {
+ tools::Rectangle aRect( Point( nX+rRect1.Left(),
+ nY+rRect1.Top() ), rRect1.GetSize() );
+ drawRectangle( aRect );
+ }
+
+ if ( !rRect2.IsEmpty() )
+ {
+ tools::Rectangle aRect( Point( nX+rRect2.Left(),
+ nY+rRect2.Top() ), rRect2.GetSize() );
+
+ drawRectangle( aRect );
+ }
+}
+
+void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines )
+{
+ MARK( "drawText" );
+
+ updateGraphicsState();
+
+ // get a layout from the OutputDevice's SalGraphics
+ // this also enforces font substitution and sets the font on SalGraphics
+ const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
+ GetLayoutGlyphs( this, rText, nIndex, nLen );
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos,
+ 0, {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
+ if( pLayout )
+ {
+ drawLayout( *pLayout, rText, bTextLines );
+ }
+}
+
+void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, KernArraySpan pDXArray, std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex, sal_Int32 nLen )
+{
+ MARK( "drawText with array" );
+
+ updateGraphicsState();
+
+ // get a layout from the OutputDevice's SalGraphics
+ // this also enforces font substitution and sets the font on SalGraphics
+ const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
+ GetLayoutGlyphs( this, rText, nIndex, nLen );
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray, pKashidaArray,
+ SalLayoutFlags::NONE, nullptr, layoutGlyphs );
+ if( pLayout )
+ {
+ drawLayout( *pLayout, rText, true );
+ }
+}
+
+void PDFWriterImpl::drawStretchText( const Point& rPos, sal_Int32 nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen )
+{
+ MARK( "drawStretchText" );
+
+ updateGraphicsState();
+
+ // get a layout from the OutputDevice's SalGraphics
+ // this also enforces font substitution and sets the font on SalGraphics
+ const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
+ GetLayoutGlyphs( this, rText, nIndex, nLen, nWidth );
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth,
+ {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
+ if( pLayout )
+ {
+ drawLayout( *pLayout, rText, true );
+ }
+}
+
+void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle )
+{
+ tools::Long nWidth = rRect.GetWidth();
+ tools::Long nHeight = rRect.GetHeight();
+
+ if ( nWidth <= 0 || nHeight <= 0 )
+ return;
+
+ MARK( "drawText with rectangle" );
+
+ updateGraphicsState();
+
+ // clip with rectangle
+ OStringBuffer aLine;
+ aLine.append( "q " );
+ m_aPages.back().appendRect( rRect, aLine );
+ aLine.append( " W* n\n" );
+ writeBuffer( aLine );
+
+ // if disabled text is needed, put in here
+
+ Point aPos = rRect.TopLeft();
+
+ tools::Long nTextHeight = GetTextHeight();
+ sal_Int32 nMnemonicPos = -1;
+
+ OUString aStr = rOrigStr;
+ if ( nStyle & DrawTextFlags::Mnemonic )
+ aStr = removeMnemonicFromString( aStr, nMnemonicPos );
+
+ // multiline text
+ if ( nStyle & DrawTextFlags::MultiLine )
+ {
+ ImplMultiTextLineInfo aMultiLineInfo;
+ sal_Int32 i;
+ sal_Int32 nFormatLines;
+
+ if ( nTextHeight )
+ {
+ vcl::DefaultTextLayout aLayout( *this );
+ OUString aLastLine;
+ aLayout.GetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle );
+ sal_Int32 nLines = nHeight/nTextHeight;
+ nFormatLines = aMultiLineInfo.Count();
+ if ( !nLines )
+ nLines = 1;
+ if ( nFormatLines > nLines )
+ {
+ if ( nStyle & DrawTextFlags::EndEllipsis )
+ {
+ // handle last line
+ nFormatLines = nLines-1;
+
+ ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines );
+ aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF);
+ // replace line feed by space
+ aLastLine = aLastLine.replace('\n', ' ');
+ aLastLine = GetEllipsisString( aLastLine, nWidth, nStyle );
+ nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
+ nStyle |= DrawTextFlags::Top;
+ }
+ }
+
+ // vertical alignment
+ if ( nStyle & DrawTextFlags::Bottom )
+ aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
+ else if ( nStyle & DrawTextFlags::VCenter )
+ aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );
+
+ // draw all lines excluding the last
+ for ( i = 0; i < nFormatLines; i++ )
+ {
+ ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
+ if ( nStyle & DrawTextFlags::Right )
+ aPos.AdjustX(nWidth-rLineInfo.GetWidth() );
+ else if ( nStyle & DrawTextFlags::Center )
+ aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 );
+ sal_Int32 nIndex = rLineInfo.GetIndex();
+ sal_Int32 nLineLen = rLineInfo.GetLen();
+ drawText( aPos, aStr, nIndex, nLineLen );
+ // mnemonics should not appear in documents,
+ // if the need arises, put them in here
+ aPos.AdjustY(nTextHeight );
+ aPos.setX( rRect.Left() );
+ }
+
+ // output last line left adjusted since it was shortened
+ if (!aLastLine.isEmpty())
+ drawText( aPos, aLastLine, 0, aLastLine.getLength() );
+ }
+ }
+ else
+ {
+ tools::Long nTextWidth = GetTextWidth( aStr );
+
+ // Evt. Text kuerzen
+ if ( nTextWidth > nWidth )
+ {
+ if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) )
+ {
+ aStr = GetEllipsisString( aStr, nWidth, nStyle );
+ nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
+ nStyle |= DrawTextFlags::Left;
+ nTextWidth = GetTextWidth( aStr );
+ }
+ }
+
+ // vertical alignment
+ if ( nStyle & DrawTextFlags::Right )
+ aPos.AdjustX(nWidth-nTextWidth );
+ else if ( nStyle & DrawTextFlags::Center )
+ aPos.AdjustX((nWidth-nTextWidth)/2 );
+
+ if ( nStyle & DrawTextFlags::Bottom )
+ aPos.AdjustY(nHeight-nTextHeight );
+ else if ( nStyle & DrawTextFlags::VCenter )
+ aPos.AdjustY((nHeight-nTextHeight)/2 );
+
+ // mnemonics should be inserted here if the need arises
+
+ // draw the actual text
+ drawText( aPos, aStr, 0, aStr.getLength() );
+ }
+
+ // reset clip region to original value
+ aLine.setLength( 0 );
+ aLine.append( "Q\n" );
+ writeBuffer( aLine );
+}
+
+void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop )
+{
+ MARK( "drawLine" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ OStringBuffer aLine;
+ m_aPages.back().appendPoint( rStart, aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( rStop, aLine );
+ aLine.append( " l S\n" );
+
+ writeBuffer( aLine );
+}
+
+void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
+{
+ MARK( "drawLine with LineInfo" );
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 )
+ {
+ drawLine( rStart, rStop );
+ return;
+ }
+
+ OStringBuffer aLine;
+
+ aLine.append( "q " );
+ if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
+ {
+ m_aPages.back().appendPoint( rStart, aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( rStop, aLine );
+ aLine.append( " l S Q\n" );
+
+ writeBuffer( aLine );
+ }
+ else
+ {
+ PDFWriter::ExtLineInfo aInfo;
+ convertLineInfoToExtLineInfo( rInfo, aInfo );
+ Point aPolyPoints[2] = { rStart, rStop };
+ tools::Polygon aPoly( 2, aPolyPoints );
+ drawPolyLine( aPoly, aInfo );
+ }
+}
+
+#define HCONV( x ) ImplDevicePixelToLogicHeight( x )
+
+void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
+{
+ // note: units in pFontInstance are ref device pixel
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ tools::Long nLineHeight = 0;
+ tools::Long nLinePos = 0;
+
+ appendStrokingColor( aColor, aLine );
+ aLine.append( "\n" );
+
+ if ( bIsAbove )
+ {
+ if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() )
+ ImplInitAboveTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() );
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() );
+ }
+ if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
+ nLineHeight = 3;
+
+ tools::Long nLineWidth = GetDPIX()/450;
+ if ( ! nLineWidth )
+ nLineWidth = 1;
+
+ if ( eTextLine == LINESTYLE_BOLDWAVE )
+ nLineWidth = 3*nLineWidth;
+
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineWidth), aLine );
+ aLine.append( " w " );
+
+ if ( eTextLine == LINESTYLE_DOUBLEWAVE )
+ {
+ tools::Long nOrgLineHeight = nLineHeight;
+ nLineHeight /= 3;
+ if ( nLineHeight < 2 )
+ {
+ if ( nOrgLineHeight > 1 )
+ nLineHeight = 2;
+ else
+ nLineHeight = 1;
+ }
+ tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2);
+ if ( nLineDY < nLineWidth )
+ nLineDY = nLineWidth;
+ tools::Long nLineDY2 = nLineDY/2;
+ if ( !nLineDY2 )
+ nLineDY2 = 1;
+
+ nLinePos -= nLineWidth-nLineDY2;
+
+ m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
+
+ nLinePos += nLineWidth+nLineDY;
+ m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
+ }
+ else
+ {
+ if ( eTextLine != LINESTYLE_BOLDWAVE )
+ nLinePos -= nLineWidth/2;
+ m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine );
+ }
+}
+
+void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
+{
+ // note: units in pFontInstance are ref device pixel
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ tools::Long nLineHeight = 0;
+ tools::Long nLinePos = 0;
+ tools::Long nLinePos2 = 0;
+
+ if ( eTextLine > LINESTYLE_BOLDWAVE )
+ eTextLine = LINESTYLE_SINGLE;
+
+ switch ( eTextLine )
+ {
+ case LINESTYLE_SINGLE:
+ case LINESTYLE_DOTTED:
+ case LINESTYLE_DASH:
+ case LINESTYLE_LONGDASH:
+ case LINESTYLE_DASHDOT:
+ case LINESTYLE_DASHDOTDOT:
+ if ( bIsAbove )
+ {
+ if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() )
+ ImplInitAboveTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetAboveUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetAboveUnderlineOffset();
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetUnderlineOffset();
+ }
+ break;
+ case LINESTYLE_BOLD:
+ case LINESTYLE_BOLDDOTTED:
+ case LINESTYLE_BOLDDASH:
+ case LINESTYLE_BOLDLONGDASH:
+ case LINESTYLE_BOLDDASHDOT:
+ case LINESTYLE_BOLDDASHDOTDOT:
+ if ( bIsAbove )
+ {
+ if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() )
+ ImplInitAboveTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset();
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetBoldUnderlineOffset();
+ }
+ break;
+ case LINESTYLE_DOUBLE:
+ if ( bIsAbove )
+ {
+ if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
+ ImplInitAboveTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1();
+ nLinePos2 = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2();
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1();
+ nLinePos2 = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2();
+ }
+ break;
+ default:
+ break;
+ }
+
+ if ( !nLineHeight )
+ return;
+
+ // tdf#154235
+ // nLinePos/nLinePos2 is the distance from baseline to the top of the line,
+ // while in PDF we stroke the line so the position is to the middle of the
+ // line, we add half of nLineHeight to account for that.
+ auto nOffset = nLineHeight / 2;
+ if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
+ {
+ // Except when outlining, as now we are drawing a rectangle, so
+ // nLinePos is the bottom of the rectangle, so need to add nLineHeight
+ // to it.
+ nOffset = nLineHeight;
+ }
+
+ nLineHeight = HCONV(nLineHeight);
+ nLinePos = HCONV(nLinePos + nOffset);
+ nLinePos2 = HCONV(nLinePos2 + nOffset);
+
+ // outline attribute ?
+ if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
+ {
+ appendStrokingColor(aColor, aLine); // stroke with text color
+ aLine.append( " " );
+ appendNonStrokingColor(COL_WHITE, aLine); // fill with white
+ aLine.append( "\n" );
+ aLine.append( "0.25 w \n" ); // same line thickness as in drawLayout
+
+ // draw rectangle instead
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
+ aLine.append( " " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
+ aLine.append( " re h B\n" );
+ return;
+ }
+
+
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
+ aLine.append( " w " );
+ appendStrokingColor( aColor, aLine );
+ aLine.append( "\n" );
+
+ switch ( eTextLine )
+ {
+ case LINESTYLE_DOTTED:
+ case LINESTYLE_BOLDDOTTED:
+ aLine.append( "[ " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
+ aLine.append( " ] 0 d\n" );
+ break;
+ case LINESTYLE_DASH:
+ case LINESTYLE_LONGDASH:
+ case LINESTYLE_BOLDDASH:
+ case LINESTYLE_BOLDLONGDASH:
+ {
+ sal_Int32 nDashLength = 4*nLineHeight;
+ sal_Int32 nVoidLength = 2*nLineHeight;
+ if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) )
+ nDashLength = 8*nLineHeight;
+
+ aLine.append( "[ " );
+ m_aPages.back().appendMappedLength( nDashLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( " ] 0 d\n" );
+ }
+ break;
+ case LINESTYLE_DASHDOT:
+ case LINESTYLE_BOLDDASHDOT:
+ {
+ sal_Int32 nDashLength = 4*nLineHeight;
+ sal_Int32 nVoidLength = 2*nLineHeight;
+ aLine.append( "[ " );
+ m_aPages.back().appendMappedLength( nDashLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( " ] 0 d\n" );
+ }
+ break;
+ case LINESTYLE_DASHDOTDOT:
+ case LINESTYLE_BOLDDASHDOTDOT:
+ {
+ sal_Int32 nDashLength = 4*nLineHeight;
+ sal_Int32 nVoidLength = 2*nLineHeight;
+ aLine.append( "[ " );
+ m_aPages.back().appendMappedLength( nDashLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
+ aLine.append( " ] 0 d\n" );
+ }
+ break;
+ default:
+ break;
+ }
+
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
+ aLine.append( " l S\n" );
+ if ( eTextLine == LINESTYLE_DOUBLE )
+ {
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
+ aLine.append( " l S\n" );
+ }
+
+}
+
+void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, tools::Long nWidth, FontStrikeout eStrikeout, Color aColor )
+{
+ // note: units in pFontInstance are ref device pixel
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ tools::Long nLineHeight = 0;
+ tools::Long nLinePos = 0;
+ tools::Long nLinePos2 = 0;
+
+ if ( eStrikeout > STRIKEOUT_X )
+ eStrikeout = STRIKEOUT_SINGLE;
+
+ switch ( eStrikeout )
+ {
+ case STRIKEOUT_SINGLE:
+ if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() )
+ ImplInitTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetStrikeoutSize();
+ nLinePos = pFontInstance->mxFontMetric->GetStrikeoutOffset();
+ break;
+ case STRIKEOUT_BOLD:
+ if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
+ ImplInitTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize();
+ nLinePos = pFontInstance->mxFontMetric->GetBoldStrikeoutOffset();
+ break;
+ case STRIKEOUT_DOUBLE:
+ if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
+ ImplInitTextLineSize();
+ nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize();
+ nLinePos = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1();
+ nLinePos2 = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2();
+ break;
+ default:
+ break;
+ }
+
+ if ( !nLineHeight )
+ return;
+
+ // tdf#154235
+ // nLinePos/nLinePos2 is the distance from baseline to the bottom of the line,
+ // while in PDF we stroke the line so the position is to the middle of the
+ // line, we add half of nLineHeight to account for that.
+ nLinePos = HCONV(nLinePos + nLineHeight / 2);
+ nLinePos2 = HCONV(nLinePos2 + nLineHeight / 2);
+ nLineHeight = HCONV(nLineHeight);
+
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
+ aLine.append( " w " );
+ appendStrokingColor( aColor, aLine );
+ aLine.append( "\n" );
+
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
+ aLine.append( " l S\n" );
+
+ if ( eStrikeout == STRIKEOUT_DOUBLE )
+ {
+ aLine.append( "0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
+ aLine.append( " l S\n" );
+ }
+
+}
+
+void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout )
+{
+ //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
+ //to tweak this
+
+ OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? OUString( "/" ) : OUString( "X" );
+ OUString aStrikeout = aStrikeoutChar;
+ while( GetTextWidth( aStrikeout ) < nWidth )
+ aStrikeout += aStrikeout;
+
+ // do not get broader than nWidth modulo 1 character
+ while( GetTextWidth( aStrikeout ) >= nWidth )
+ aStrikeout = aStrikeout.copy(1);
+ aStrikeout += aStrikeoutChar;
+ bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow();
+ if ( bShadow )
+ {
+ Font aFont = m_aCurrentPDFState.m_aFont;
+ aFont.SetShadow( false );
+ setFont( aFont );
+ updateGraphicsState();
+ }
+
+ // strikeout string is left aligned non-CTL text
+ vcl::text::ComplexTextLayoutFlags nOrigTLM = GetLayoutMode();
+ SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiStrong);
+
+ push( PushFlags::CLIPREGION );
+ FontMetric aRefDevFontMetric = GetFontMetric();
+ tools::Rectangle aRect;
+ aRect.SetLeft( rPos.X() );
+ aRect.SetRight( aRect.Left()+nWidth );
+ aRect.SetBottom( rPos.Y()+aRefDevFontMetric.GetDescent() );
+ aRect.SetTop( rPos.Y()-aRefDevFontMetric.GetAscent() );
+
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ if (pFontInstance->mnOrientation)
+ {
+ tools::Polygon aPoly( aRect );
+ aPoly.Rotate( rPos, pFontInstance->mnOrientation);
+ aRect = aPoly.GetBoundRect();
+ }
+
+ intersectClipRegion( aRect );
+ drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false );
+ pop();
+
+ SetLayoutMode( nOrigTLM );
+
+ if ( bShadow )
+ {
+ Font aFont = m_aCurrentPDFState.m_aFont;
+ aFont.SetShadow( true );
+ setFont( aFont );
+ updateGraphicsState();
+ }
+}
+
+void PDFWriterImpl::drawTextLine( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove )
+{
+ if ( !nWidth ||
+ ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) &&
+ ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) &&
+ ((eOverline == LINESTYLE_NONE)||(eOverline == LINESTYLE_DONTKNOW)) ) )
+ return;
+
+ MARK( "drawTextLine" );
+ updateGraphicsState();
+
+ // note: units in pFontInstance are ref device pixel
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ Color aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor;
+ Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
+ Color aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor();
+ bool bStrikeoutDone = false;
+ bool bUnderlineDone = false;
+ bool bOverlineDone = false;
+
+ if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) )
+ {
+ drawStrikeoutChar( rPos, nWidth, eStrikeout );
+ bStrikeoutDone = true;
+ }
+
+ Point aPos( rPos );
+ TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
+ if( eAlign == ALIGN_TOP )
+ aPos.AdjustY(HCONV( pFontInstance->mxFontMetric->GetAscent() ));
+ else if( eAlign == ALIGN_BOTTOM )
+ aPos.AdjustY( -HCONV( pFontInstance->mxFontMetric->GetDescent() ) );
+
+ OStringBuffer aLine( 512 );
+ // save GS
+ aLine.append( "q " );
+
+ // rotate and translate matrix
+ double fAngle = toRadians(m_aCurrentPDFState.m_aFont.GetOrientation());
+ Matrix3 aMat;
+ aMat.rotate( fAngle );
+ aMat.translate( aPos.X(), aPos.Y() );
+ m_aPages.back().appendMatrix3(aMat, aLine);
+ aLine.append( " cm\n" );
+
+ if ( aUnderlineColor.IsTransparent() )
+ aUnderlineColor = aStrikeoutColor;
+
+ if ( aOverlineColor.IsTransparent() )
+ aOverlineColor = aStrikeoutColor;
+
+ if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
+ (eUnderline == LINESTYLE_WAVE) ||
+ (eUnderline == LINESTYLE_DOUBLEWAVE) ||
+ (eUnderline == LINESTYLE_BOLDWAVE) )
+ {
+ drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
+ bUnderlineDone = true;
+ }
+
+ if ( (eOverline == LINESTYLE_SMALLWAVE) ||
+ (eOverline == LINESTYLE_WAVE) ||
+ (eOverline == LINESTYLE_DOUBLEWAVE) ||
+ (eOverline == LINESTYLE_BOLDWAVE) )
+ {
+ drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
+ bOverlineDone = true;
+ }
+
+ if ( !bUnderlineDone )
+ {
+ drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
+ }
+
+ if ( !bOverlineDone )
+ {
+ drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
+ }
+
+ if ( !bStrikeoutDone )
+ {
+ drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor );
+ }
+
+ aLine.append( "Q\n" );
+ writeBuffer( aLine );
+}
+
+void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly )
+{
+ MARK( "drawPolygon" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ int nPoints = rPoly.GetSize();
+ OStringBuffer aLine( 20 * nPoints );
+ m_aPages.back().appendPolygon( rPoly, aLine );
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "B*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "S\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine );
+}
+
+void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
+{
+ MARK( "drawPolyPolygon" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ int nPolygons = rPolyPoly.Count();
+
+ OStringBuffer aLine( 40 * nPolygons );
+ m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "B*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "S\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine );
+}
+
+void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent )
+{
+ SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
+ nTransparentPercent = nTransparentPercent % 100;
+
+ MARK( "drawTransparent" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
+ {
+ m_aErrors.insert( m_bIsPDF_A1 ?
+ PDFWriter::Warning_Transparency_Omitted_PDFA :
+ PDFWriter::Warning_Transparency_Omitted_PDF13 );
+
+ drawPolyPolygon( rPolyPoly );
+ return;
+ }
+
+ // create XObject
+ m_aTransparentObjects.emplace_back( );
+ // FIXME: polygons with beziers may yield incorrect bound rect
+ m_aTransparentObjects.back().m_aBoundRect = rPolyPoly.GetBoundRect();
+ // convert rectangle to default user space
+ m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
+ m_aTransparentObjects.back().m_nObject = createObject();
+ m_aTransparentObjects.back().m_nExtGStateObject = createObject();
+ m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0;
+ m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 ));
+ // create XObject's content stream
+ OStringBuffer aContent( 256 );
+ m_aPages.back().appendPolyPolygon( rPolyPoly, aContent );
+ if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT &&
+ m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT )
+ aContent.append( " B*\n" );
+ else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT )
+ aContent.append( " S\n" );
+ else
+ aContent.append( " f*\n" );
+ m_aTransparentObjects.back().m_pContentStream->WriteBytes(
+ aContent.getStr(), aContent.getLength() );
+
+ OStringBuffer aObjName( 16 );
+ aObjName.append( "Tr" );
+ aObjName.append( m_aTransparentObjects.back().m_nObject );
+ OString aTrName( aObjName.makeStringAndClear() );
+ aObjName.append( "EGS" );
+ aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
+ OString aExtName( aObjName.makeStringAndClear() );
+
+ OString aLine =
+ // insert XObject
+ "q /" +
+ aExtName +
+ " gs /" +
+ aTrName +
+ " Do Q\n";
+ writeBuffer( aLine );
+
+ pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
+ pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
+}
+
+void PDFWriterImpl::pushResource(ResourceKind eKind, const OString& rResource, sal_Int32 nObject, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams)
+{
+ if( nObject < 0 )
+ return;
+
+ switch( eKind )
+ {
+ case ResourceKind::XObject:
+ rResourceDict.m_aXObjects[rResource] = nObject;
+ if (!rOutputStreams.empty())
+ rOutputStreams.front().m_aResourceDict.m_aXObjects[rResource] = nObject;
+ break;
+ case ResourceKind::ExtGState:
+ rResourceDict.m_aExtGStates[rResource] = nObject;
+ if (!rOutputStreams.empty())
+ rOutputStreams.front().m_aResourceDict.m_aExtGStates[rResource] = nObject;
+ break;
+ case ResourceKind::Shading:
+ rResourceDict.m_aShadings[rResource] = nObject;
+ if (!rOutputStreams.empty())
+ rOutputStreams.front().m_aResourceDict.m_aShadings[rResource] = nObject;
+ break;
+ case ResourceKind::Pattern:
+ rResourceDict.m_aPatterns[rResource] = nObject;
+ if (!rOutputStreams.empty())
+ rOutputStreams.front().m_aResourceDict.m_aPatterns[rResource] = nObject;
+ break;
+ }
+}
+
+void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject )
+{
+ pushResource(eKind, rResource, nObject, m_aGlobalResourceDict, m_aOutputStreams);
+}
+
+void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect )
+{
+ push( PushFlags::ALL );
+
+ // force reemitting clip region inside the new stream, and
+ // prevent emitting an unbalanced "Q" at the start
+ clearClipRegion();
+ // this is needed to point m_aCurrentPDFState at the pushed state
+ // ... but it's pointless to actually write into the "outer" stream here!
+ updateGraphicsState(Mode::NOWRITE);
+
+ m_aOutputStreams.push_front( StreamRedirect() );
+ m_aOutputStreams.front().m_pStream = pStream;
+ m_aOutputStreams.front().m_aMapMode = m_aMapMode;
+
+ if( !rTargetRect.IsEmpty() )
+ {
+ m_aOutputStreams.front().m_aTargetRect =
+ lcl_convert( m_aGraphicsStack.front().m_aMapMode,
+ m_aMapMode,
+ this,
+ rTargetRect );
+ Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft();
+ tools::Long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight());
+ aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) );
+ m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta );
+ }
+
+ // setup graphics state for independent object stream
+
+ // force reemitting colors
+ m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
+ m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
+}
+
+SvStream* PDFWriterImpl::endRedirect()
+{
+ SvStream* pStream = nullptr;
+ if( ! m_aOutputStreams.empty() )
+ {
+ pStream = m_aOutputStreams.front().m_pStream;
+ m_aMapMode = m_aOutputStreams.front().m_aMapMode;
+ m_aOutputStreams.pop_front();
+ }
+
+ pop();
+
+ m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
+ m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
+
+ // needed after pop() to set m_aCurrentPDFState
+ updateGraphicsState(Mode::NOWRITE);
+
+ return pStream;
+}
+
+void PDFWriterImpl::beginTransparencyGroup()
+{
+ updateGraphicsState();
+ if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
+ beginRedirect( new SvMemoryStream( 1024, 1024 ), tools::Rectangle() );
+}
+
+void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent )
+{
+ SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
+ nTransparentPercent = nTransparentPercent % 100;
+
+ if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
+ return;
+
+ // create XObject
+ m_aTransparentObjects.emplace_back( );
+ m_aTransparentObjects.back().m_aBoundRect = rBoundingBox;
+ // convert rectangle to default user space
+ m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
+ m_aTransparentObjects.back().m_nObject = createObject();
+ m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0;
+ // get XObject's content stream
+ m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) );
+ m_aTransparentObjects.back().m_nExtGStateObject = createObject();
+
+ OStringBuffer aObjName( 16 );
+ aObjName.append( "Tr" );
+ aObjName.append( m_aTransparentObjects.back().m_nObject );
+ OString aTrName( aObjName.makeStringAndClear() );
+ aObjName.append( "EGS" );
+ aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
+ OString aExtName( aObjName.makeStringAndClear() );
+
+ OString aLine =
+ // insert XObject
+ "q /" +
+ aExtName +
+ " gs /" +
+ aTrName +
+ " Do Q\n";
+ writeBuffer( aLine );
+
+ pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
+ pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
+
+}
+
+void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect )
+{
+ MARK( "drawRectangle" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ OStringBuffer aLine( 40 );
+ m_aPages.back().appendRect( rRect, aLine );
+
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( " B*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( " S\n" );
+ else
+ aLine.append( " f*\n" );
+
+ writeBuffer( aLine );
+}
+
+void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
+{
+ MARK( "drawRectangle with rounded edges" );
+
+ if( !nHorzRound && !nVertRound )
+ drawRectangle( rRect );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ if( nHorzRound > static_cast<sal_uInt32>(rRect.GetWidth())/2 )
+ nHorzRound = rRect.GetWidth()/2;
+ if( nVertRound > static_cast<sal_uInt32>(rRect.GetHeight())/2 )
+ nVertRound = rRect.GetHeight()/2;
+
+ Point aPoints[16];
+ const double kappa = 0.5522847498;
+ const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(nHorzRound))+0.5);
+ const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(nVertRound))+0.5);
+
+ aPoints[1] = Point( rRect.Left() + nHorzRound, rRect.Top() );
+ aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
+ aPoints[2] = Point( rRect.Right()+1 - nHorzRound, aPoints[1].Y() );
+ aPoints[3] = Point( aPoints[2].X()+kx, aPoints[2].Y() );
+
+ aPoints[5] = Point( rRect.Right()+1, rRect.Top()+nVertRound );
+ aPoints[4] = Point( aPoints[5].X(), aPoints[5].Y()-ky );
+ aPoints[6] = Point( aPoints[5].X(), rRect.Bottom()+1 - nVertRound );
+ aPoints[7] = Point( aPoints[6].X(), aPoints[6].Y()+ky );
+
+ aPoints[9] = Point( rRect.Right()+1-nHorzRound, rRect.Bottom()+1 );
+ aPoints[8] = Point( aPoints[9].X()+kx, aPoints[9].Y() );
+ aPoints[10] = Point( rRect.Left() + nHorzRound, aPoints[9].Y() );
+ aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
+
+ aPoints[13] = Point( rRect.Left(), rRect.Bottom()+1-nVertRound );
+ aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
+ aPoints[14] = Point( rRect.Left(), rRect.Top()+nVertRound );
+ aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky );
+
+ OStringBuffer aLine( 80 );
+ m_aPages.back().appendPoint( aPoints[1], aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( aPoints[2], aLine );
+ aLine.append( " l " );
+ m_aPages.back().appendPoint( aPoints[3], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[4], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[5], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[6], aLine );
+ aLine.append( " l " );
+ m_aPages.back().appendPoint( aPoints[7], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[8], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[9], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[10], aLine );
+ aLine.append( " l " );
+ m_aPages.back().appendPoint( aPoints[11], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[12], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[13], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[14], aLine );
+ aLine.append( " l " );
+ m_aPages.back().appendPoint( aPoints[15], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[0], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[1], aLine );
+ aLine.append( " c " );
+
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "b*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "s\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine );
+}
+
+void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect )
+{
+ MARK( "drawEllipse" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ Point aPoints[12];
+ const double kappa = 0.5522847498;
+ const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5);
+ const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5);
+
+ aPoints[1] = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Top() );
+ aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
+ aPoints[2] = Point( aPoints[1].X() + kx, aPoints[1].Y() );
+
+ aPoints[4] = Point( rRect.Right()+1, rRect.Top() + rRect.GetHeight()/2 );
+ aPoints[3] = Point( aPoints[4].X(), aPoints[4].Y() - ky );
+ aPoints[5] = Point( aPoints[4].X(), aPoints[4].Y() + ky );
+
+ aPoints[7] = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Bottom()+1 );
+ aPoints[6] = Point( aPoints[7].X() + kx, aPoints[7].Y() );
+ aPoints[8] = Point( aPoints[7].X() - kx, aPoints[7].Y() );
+
+ aPoints[10] = Point( rRect.Left(), rRect.Top() + rRect.GetHeight()/2 );
+ aPoints[9] = Point( aPoints[10].X(), aPoints[10].Y() + ky );
+ aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky );
+
+ OStringBuffer aLine( 80 );
+ m_aPages.back().appendPoint( aPoints[1], aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( aPoints[2], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[3], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[4], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[5], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[6], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[7], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[8], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[9], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[10], aLine );
+ aLine.append( " c\n" );
+ m_aPages.back().appendPoint( aPoints[11], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[0], aLine );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( aPoints[1], aLine );
+ aLine.append( " c " );
+
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "b*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "s\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine );
+}
+
+static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint )
+{
+ Point aOrigin((rRect.Left()+rRect.Right()+1)/2,
+ (rRect.Top()+rRect.Bottom()+1)/2);
+ Point aPoint = rPoint - aOrigin;
+
+ double fX = static_cast<double>(aPoint.X());
+ double fY = static_cast<double>(-aPoint.Y());
+
+ if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0))
+ throw o3tl::divide_by_zero();
+
+ if( rRect.GetWidth() > rRect.GetHeight() )
+ fY = fY*(static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight()));
+ else if( rRect.GetHeight() > rRect.GetWidth() )
+ fX = fX*(static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth()));
+ return atan2( fY, fX );
+}
+
+void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord )
+{
+ MARK( "drawArc" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
+ return;
+
+ // calculate start and stop angles
+ const double fStartAngle = calcAngle( rRect, rStart );
+ double fStopAngle = calcAngle( rRect, rStop );
+ while( fStopAngle < fStartAngle )
+ fStopAngle += 2.0*M_PI;
+ const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1;
+ const double fFragmentDelta = (fStopAngle-fStartAngle)/static_cast<double>(nFragments);
+ const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0);
+ const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0;
+ const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0;
+
+ const Point aCenter( (rRect.Left()+rRect.Right()+1)/2,
+ (rRect.Top()+rRect.Bottom()+1)/2 );
+
+ OStringBuffer aLine( 30*nFragments );
+ Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ),
+ -static_cast<int>(halfHeight * sin(fStartAngle) ) );
+ aPoint += aCenter;
+ m_aPages.back().appendPoint( aPoint, aLine );
+ aLine.append( " m " );
+ if( !basegfx::fTools::equal(fStartAngle, fStopAngle) )
+ {
+ for( int i = 0; i < nFragments; i++ )
+ {
+ const double fStartFragment = fStartAngle + static_cast<double>(i)*fFragmentDelta;
+ const double fStopFragment = fStartFragment + fFragmentDelta;
+ aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ),
+ -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) );
+ aPoint += aCenter;
+ m_aPages.back().appendPoint( aPoint, aLine );
+ aLine.append( ' ' );
+
+ aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ),
+ -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) );
+ aPoint += aCenter;
+ m_aPages.back().appendPoint( aPoint, aLine );
+ aLine.append( ' ' );
+
+ aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ),
+ -static_cast<int>(halfHeight * sin(fStopFragment) ) );
+ aPoint += aCenter;
+ m_aPages.back().appendPoint( aPoint, aLine );
+ aLine.append( " c\n" );
+ }
+ }
+ if( bWithChord || bWithPie )
+ {
+ if( bWithPie )
+ {
+ m_aPages.back().appendPoint( aCenter, aLine );
+ aLine.append( " l " );
+ }
+ aLine.append( "h " );
+ }
+ if( ! bWithChord && ! bWithPie )
+ aLine.append( "S\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
+ m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
+ aLine.append( "B*\n" );
+ else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "S\n" );
+ else
+ aLine.append( "f*\n" );
+
+ writeBuffer( aLine );
+}
+
+void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly )
+{
+ MARK( "drawPolyLine" );
+
+ sal_uInt16 nPoints = rPoly.GetSize();
+ if( nPoints < 2 )
+ return;
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ OStringBuffer aLine( 20 * nPoints );
+ m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] );
+ aLine.append( "S\n" );
+
+ writeBuffer( aLine );
+}
+
+void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
+{
+ MARK( "drawPolyLine with LineInfo" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ OStringBuffer aLine;
+ aLine.append( "q " );
+ if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
+ {
+ writeBuffer( aLine );
+ drawPolyLine( rPoly );
+ writeBuffer( "Q\n" );
+ }
+ else
+ {
+ PDFWriter::ExtLineInfo aInfo;
+ convertLineInfoToExtLineInfo( rInfo, aInfo );
+ drawPolyLine( rPoly, aInfo );
+ }
+}
+
+void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut )
+{
+ SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" );
+ rOut.m_fLineWidth = rIn.GetWidth();
+ rOut.m_fTransparency = 0.0;
+ rOut.m_eCap = PDFWriter::capButt;
+ rOut.m_eJoin = PDFWriter::joinMiter;
+ rOut.m_fMiterLimit = 10;
+ rOut.m_aDashArray = rIn.GetDotDashArray();
+
+ // add LineJoin
+ switch(rIn.GetLineJoin())
+ {
+ case basegfx::B2DLineJoin::Bevel :
+ {
+ rOut.m_eJoin = PDFWriter::joinBevel;
+ break;
+ }
+ // Pdf has no 'none' lineJoin, default is miter
+ case basegfx::B2DLineJoin::NONE :
+ case basegfx::B2DLineJoin::Miter :
+ {
+ rOut.m_eJoin = PDFWriter::joinMiter;
+ break;
+ }
+ case basegfx::B2DLineJoin::Round :
+ {
+ rOut.m_eJoin = PDFWriter::joinRound;
+ break;
+ }
+ }
+
+ // add LineCap
+ switch(rIn.GetLineCap())
+ {
+ default: /* css::drawing::LineCap_BUTT */
+ {
+ rOut.m_eCap = PDFWriter::capButt;
+ break;
+ }
+ case css::drawing::LineCap_ROUND:
+ {
+ rOut.m_eCap = PDFWriter::capRound;
+ break;
+ }
+ case css::drawing::LineCap_SQUARE:
+ {
+ rOut.m_eCap = PDFWriter::capSquare;
+ break;
+ }
+ }
+}
+
+void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo )
+{
+ MARK( "drawPolyLine with ExtLineInfo" );
+
+ updateGraphicsState();
+
+ if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
+ return;
+
+ if( rInfo.m_fTransparency >= 1.0 )
+ return;
+
+ if( rInfo.m_fTransparency != 0.0 )
+ beginTransparencyGroup();
+
+ OStringBuffer aLine;
+ aLine.append( "q " );
+ m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine );
+ aLine.append( " w" );
+ if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader
+ {
+ switch( rInfo.m_eCap )
+ {
+ default:
+ case PDFWriter::capButt: aLine.append( " 0 J" );break;
+ case PDFWriter::capRound: aLine.append( " 1 J" );break;
+ case PDFWriter::capSquare: aLine.append( " 2 J" );break;
+ }
+ switch( rInfo.m_eJoin )
+ {
+ default:
+ case PDFWriter::joinMiter:
+ {
+ double fLimit = rInfo.m_fMiterLimit;
+ if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit )
+ fLimit = fLimit / rInfo.m_fLineWidth;
+ if( fLimit < 1.0 )
+ fLimit = 1.0;
+ aLine.append( " 0 j " );
+ appendDouble( fLimit, aLine );
+ aLine.append( " M" );
+ }
+ break;
+ case PDFWriter::joinRound: aLine.append( " 1 j" );break;
+ case PDFWriter::joinBevel: aLine.append( " 2 j" );break;
+ }
+ if( !rInfo.m_aDashArray.empty() )
+ {
+ aLine.append( " [ " );
+ for (auto const& dash : rInfo.m_aDashArray)
+ {
+ m_aPages.back().appendMappedLength( dash, aLine );
+ aLine.append( ' ' );
+ }
+ aLine.append( "] 0 d" );
+ }
+ aLine.append( "\n" );
+ writeBuffer( aLine );
+ drawPolyLine( rPoly );
+ }
+ else
+ {
+ basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon());
+ basegfx::B2DPolyPolygon aPolyPoly;
+
+ basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly);
+
+ // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments.
+ // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality)
+ // this line needs to be removed and the loop below adapted accordingly
+ aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly);
+
+ const sal_uInt32 nPolygonCount(aPolyPoly.count());
+
+ for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ )
+ {
+ aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " );
+ aPoly = aPolyPoly.getB2DPolygon( nPoly );
+ const sal_uInt32 nPointCount(aPoly.count());
+
+ if(nPointCount)
+ {
+ const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1);
+ basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ if( a > 0 )
+ aLine.append( " " );
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex));
+
+ m_aPages.back().appendPoint( Point( FRound(aCurrent.getX()),
+ FRound(aCurrent.getY()) ),
+ aLine );
+ aLine.append( " m " );
+ m_aPages.back().appendPoint( Point( FRound(aNext.getX()),
+ FRound(aNext.getY()) ),
+ aLine );
+ aLine.append( " l" );
+
+ // prepare next edge
+ aCurrent = aNext;
+ }
+ }
+ }
+ aLine.append( " S " );
+ writeBuffer( aLine );
+ }
+ writeBuffer( "Q\n" );
+
+ if( rInfo.m_fTransparency == 0.0 )
+ return;
+
+ // FIXME: actually this may be incorrect with bezier polygons
+ tools::Rectangle aBoundRect( rPoly.GetBoundRect() );
+ // avoid clipping with thick lines
+ if( rInfo.m_fLineWidth > 0.0 )
+ {
+ sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth);
+ aBoundRect.AdjustTop( -nLW );
+ aBoundRect.AdjustLeft( -nLW );
+ aBoundRect.AdjustRight(nLW );
+ aBoundRect.AdjustBottom(nLW );
+ }
+ endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) );
+}
+
+void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor )
+{
+ MARK( "drawPixel" );
+
+ Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor );
+
+ if( aColor == COL_TRANSPARENT )
+ return;
+
+ // pixels are drawn in line color, so have to set
+ // the nonstroking color to line color
+ Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
+ setFillColor( aColor );
+
+ updateGraphicsState();
+
+ OStringBuffer aLine( 20 );
+ m_aPages.back().appendPoint( rPoint, aLine );
+ aLine.append( ' ' );
+ appendDouble( 1.0/double(GetDPIX()), aLine );
+ aLine.append( ' ' );
+ appendDouble( 1.0/double(GetDPIY()), aLine );
+ aLine.append( " re f\n" );
+ writeBuffer( aLine );
+
+ setFillColor( aOldFillColor );
+}
+
+void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
+{
+ CHECK_RETURN2( updateObject( rObject.m_nObject ) );
+
+ bool bFlateFilter = compressStream( rObject.m_pContentStream.get() );
+ sal_uInt64 nSize = rObject.m_pContentStream->TellEnd();
+ rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN );
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::writeTransparentObject" );
+ }
+ OStringBuffer aLine( 512 );
+ CHECK_RETURN2( updateObject( rObject.m_nObject ) );
+ aLine.append( rObject.m_nObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/XObject\n"
+ "/Subtype/Form\n"
+ "/BBox[ " );
+ appendFixedInt( rObject.m_aBoundRect.Left(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rObject.m_aBoundRect.Top(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rObject.m_aBoundRect.Right(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine );
+ aLine.append( " ]\n" );
+ if( ! m_bIsPDF_A1 )
+ {
+ // 7.8.3 Resource dicts are required for content streams
+ aLine.append( "/Resources " );
+ aLine.append( getResourceDictObj() );
+ aLine.append( " 0 R\n" );
+
+ aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" );
+ }
+
+ aLine.append( "/Length " );
+ aLine.append( static_cast<sal_Int32>(nSize) );
+ aLine.append( "\n" );
+ if( bFlateFilter )
+ aLine.append( "/Filter/FlateDecode\n" );
+ aLine.append( ">>\n"
+ "stream\n" );
+ CHECK_RETURN2( writeBuffer( aLine ) );
+ checkAndEnableStreamEncryption( rObject.m_nObject );
+ CHECK_RETURN2( writeBufferBytes( rObject.m_pContentStream->GetData(), nSize ) );
+ disableStreamEncryption();
+ aLine.setLength( 0 );
+ aLine.append( "\n"
+ "endstream\n"
+ "endobj\n\n" );
+ CHECK_RETURN2( writeBuffer( aLine ) );
+
+ // write ExtGState dict for this XObject
+ aLine.setLength( 0 );
+ aLine.append( rObject.m_nExtGStateObject );
+ aLine.append( " 0 obj\n"
+ "<<" );
+
+ if( m_bIsPDF_A1 )
+ {
+ aLine.append( "/CA 1.0/ca 1.0" );
+ m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
+ }
+ else
+ {
+ aLine.append( "/CA " );
+ appendDouble( rObject.m_fAlpha, aLine );
+ aLine.append( "\n"
+ " /ca " );
+ appendDouble( rObject.m_fAlpha, aLine );
+ }
+ aLine.append( "\n" );
+
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN2( updateObject( rObject.m_nExtGStateObject ) );
+ CHECK_RETURN2( writeBuffer( aLine ) );
+}
+
+bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject )
+{
+ // LO internal gradient -> PDF shading type:
+ // * css::awt::GradientStyle_LINEAR: axial shading, using sampled-function with 2 samples
+ // [t=0:colorStart, t=1:colorEnd]
+ // * css::awt::GradientStyle_AXIAL: axial shading, using sampled-function with 3 samples
+ // [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd]
+ // * other styles: function shading with aSize.Width() * aSize.Height() samples
+ sal_Int32 nFunctionObject = createObject();
+ CHECK_RETURN( updateObject( nFunctionObject ) );
+
+ ScopedVclPtrInstance< VirtualDevice > aDev;
+ aDev->SetOutputSizePixel( rObject.m_aSize );
+ aDev->SetMapMode( MapMode( MapUnit::MapPixel ) );
+ if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
+ aDev->SetDrawMode( aDev->GetDrawMode() |
+ ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
+ DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
+ aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient );
+
+ Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize );
+ BitmapScopedReadAccess pAccess(aSample);
+
+ Size aSize = aSample.GetSizePixel();
+
+ sal_Int32 nStreamLengthObject = createObject();
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::writeGradientFunction" );
+ }
+ OStringBuffer aLine( 120 );
+ aLine.append( nFunctionObject );
+ aLine.append( " 0 obj\n"
+ "<</FunctionType 0\n");
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case css::awt::GradientStyle_LINEAR:
+ case css::awt::GradientStyle_AXIAL:
+ aLine.append("/Domain[ 0 1]\n");
+ break;
+ default:
+ aLine.append("/Domain[ 0 1 0 1]\n");
+ }
+ aLine.append("/Size[ " );
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case css::awt::GradientStyle_LINEAR:
+ aLine.append('2');
+ break;
+ case css::awt::GradientStyle_AXIAL:
+ aLine.append('3');
+ break;
+ default:
+ aLine.append( static_cast<sal_Int32>(aSize.Width()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(aSize.Height()) );
+ }
+ aLine.append( " ]\n"
+ "/BitsPerSample 8\n"
+ "/Range[ 0 1 0 1 0 1 ]\n"
+ "/Order 3\n"
+ "/Length " );
+ aLine.append( nStreamLengthObject );
+ if (!g_bDebugDisableCompression)
+ aLine.append( " 0 R\n"
+ "/Filter/FlateDecode"
+ ">>\n"
+ "stream\n" );
+ else
+ aLine.append( " 0 R\n"
+ ">>\n"
+ "stream\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+
+ sal_uInt64 nStartStreamPos = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartStreamPos)) );
+
+ checkAndEnableStreamEncryption( nFunctionObject );
+ beginCompression();
+ sal_uInt8 aCol[3];
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case css::awt::GradientStyle_AXIAL:
+ aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
+ aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
+ aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
+ CHECK_RETURN( writeBufferBytes( aCol, 3 ) );
+ [[fallthrough]];
+ case css::awt::GradientStyle_LINEAR:
+ {
+ aCol[0] = rObject.m_aGradient.GetStartColor().GetRed();
+ aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen();
+ aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue();
+ CHECK_RETURN( writeBufferBytes( aCol, 3 ) );
+
+ aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
+ aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
+ aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
+ CHECK_RETURN( writeBufferBytes( aCol, 3 ) );
+ break;
+ }
+ default:
+ for( int y = aSize.Height()-1; y >= 0; y-- )
+ {
+ for( tools::Long x = 0; x < aSize.Width(); x++ )
+ {
+ BitmapColor aColor = pAccess->GetColor( y, x );
+ aCol[0] = aColor.GetRed();
+ aCol[1] = aColor.GetGreen();
+ aCol[2] = aColor.GetBlue();
+ CHECK_RETURN( writeBufferBytes( aCol, 3 ) );
+ }
+ }
+ }
+ endCompression();
+ disableStreamEncryption();
+
+ sal_uInt64 nEndStreamPos = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndStreamPos)) );
+
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+
+ // write stream length
+ CHECK_RETURN( updateObject( nStreamLengthObject ) );
+ aLine.setLength( 0 );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) );
+ aLine.append( "\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+
+ CHECK_RETURN( updateObject( rObject.m_nObject ) );
+ aLine.setLength( 0 );
+ aLine.append( rObject.m_nObject );
+ aLine.append( " 0 obj\n");
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case css::awt::GradientStyle_LINEAR:
+ case css::awt::GradientStyle_AXIAL:
+ aLine.append("<</ShadingType 2\n");
+ break;
+ default:
+ aLine.append("<</ShadingType 1\n");
+ }
+ aLine.append("/ColorSpace/DeviceRGB\n"
+ "/AntiAlias true\n");
+
+ // Determination of shading axis
+ // See: OutputDevice::ImplDrawLinearGradient for reference
+ tools::Rectangle aRect;
+ aRect.SetLeft(0);
+ aRect.SetTop(0);
+ aRect.SetRight( aSize.Width() );
+ aRect.SetBottom( aSize.Height() );
+
+ tools::Rectangle aBoundRect;
+ Point aCenter;
+ Degree10 nAngle = rObject.m_aGradient.GetAngle() % 3600_deg10;
+ rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter );
+
+ const bool bLinear = (rObject.m_aGradient.GetStyle() == css::awt::GradientStyle_LINEAR);
+ double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0;
+ if ( !bLinear )
+ {
+ fBorder /= 2.0;
+ }
+
+ aBoundRect.AdjustBottom( -fBorder );
+ if (!bLinear)
+ {
+ aBoundRect.AdjustTop(fBorder );
+ }
+
+ switch (rObject.m_aGradient.GetStyle())
+ {
+ case css::awt::GradientStyle_LINEAR:
+ case css::awt::GradientStyle_AXIAL:
+ {
+ aLine.append("/Domain[ 0 1 ]\n"
+ "/Coords[ " );
+ tools::Polygon aPoly( 2 );
+ aPoly[0] = aBoundRect.BottomCenter();
+ aPoly[1] = aBoundRect.TopCenter();
+ aPoly.Rotate( aCenter, 3600_deg10 - nAngle );
+
+ aLine.append( static_cast<sal_Int32>(aPoly[0].X()) );
+ aLine.append( " " );
+ aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) );
+ aLine.append( " " );
+ aLine.append( static_cast<sal_Int32>(aPoly[1].X()));
+ aLine.append( " ");
+ aLine.append( static_cast<sal_Int32>(aPoly[1].Y()));
+ aLine.append( " ]\n");
+ aLine.append("/Extend [true true]\n");
+ break;
+ }
+ default:
+ aLine.append("/Domain[ 0 1 0 1 ]\n"
+ "/Matrix[ " );
+ aLine.append( static_cast<sal_Int32>(aSize.Width()) );
+ aLine.append( " 0 0 " );
+ aLine.append( static_cast<sal_Int32>(aSize.Height()) );
+ aLine.append( " 0 0 ]\n");
+ }
+ aLine.append("/Function " );
+ aLine.append( nFunctionObject );
+ aLine.append( " 0 R\n"
+ ">>\n"
+ "endobj\n\n" );
+ return writeBuffer( aLine );
+}
+
+void PDFWriterImpl::writeJPG( const JPGEmit& rObject )
+{
+ if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject)
+ {
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+ return;
+ }
+
+ CHECK_RETURN2( rObject.m_pStream );
+ CHECK_RETURN2( updateObject( rObject.m_nObject ) );
+
+ sal_uInt64 nLength = rObject.m_pStream->TellEnd();
+ rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
+
+ sal_Int32 nMaskObject = 0;
+ if( !rObject.m_aAlphaMask.IsEmpty() )
+ {
+ if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4
+ && !m_bIsPDF_A1)
+ {
+ nMaskObject = createObject();
+ }
+ else if( m_bIsPDF_A1 )
+ m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
+ else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
+ m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 );
+
+ }
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::writeJPG" );
+ }
+
+ OStringBuffer aLine(200);
+ aLine.append( rObject.m_nObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/XObject/Subtype/Image/Width " );
+ aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) );
+ aLine.append( " /Height " );
+ aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) );
+ aLine.append( " /BitsPerComponent 8 " );
+ if( rObject.m_bTrueColor )
+ aLine.append( "/ColorSpace/DeviceRGB" );
+ else
+ aLine.append( "/ColorSpace/DeviceGray" );
+ aLine.append( "/Filter/DCTDecode/Length " );
+ aLine.append( static_cast<sal_Int64>(nLength) );
+ if( nMaskObject )
+ {
+ aLine.append(" /SMask ");
+ aLine.append( nMaskObject );
+ aLine.append( " 0 R " );
+ }
+ aLine.append( ">>\nstream\n" );
+ CHECK_RETURN2( writeBuffer( aLine ) );
+
+ checkAndEnableStreamEncryption( rObject.m_nObject );
+ CHECK_RETURN2( writeBufferBytes( rObject.m_pStream->GetData(), nLength ) );
+ disableStreamEncryption();
+
+ aLine.setLength( 0 );
+ CHECK_RETURN2( writeBuffer( "\nendstream\nendobj\n\n" ) );
+
+ if( nMaskObject )
+ {
+ BitmapEmit aEmit;
+ aEmit.m_nObject = nMaskObject;
+ aEmit.m_aBitmap = BitmapEx( rObject.m_aAlphaMask.GetBitmap(), rObject.m_aAlphaMask );
+ writeBitmapObject( aEmit, true );
+ }
+
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+}
+
+void PDFWriterImpl::writeReferenceXObject(const ReferenceXObjectEmit& rEmit)
+{
+ if (rEmit.m_nFormObject <= 0)
+ return;
+
+ // Count /Matrix and /BBox.
+ // vcl::ImportPDF() uses getDefaultPdfResolutionDpi to set the desired
+ // rendering DPI so we have to take into account that here too.
+ static const double fResolutionDPI = vcl::pdf::getDefaultPdfResolutionDpi();
+ static const double fMagicScaleFactor = PDF_INSERT_MAGIC_SCALE_FACTOR;
+
+ sal_Int32 nOldDPIX = GetDPIX();
+ sal_Int32 nOldDPIY = GetDPIY();
+ SetDPIX(fResolutionDPI);
+ SetDPIY(fResolutionDPI);
+ Size aSize = PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit()));
+ SetDPIX(nOldDPIX);
+ SetDPIY(nOldDPIY);
+ double fScaleX = 1.0 / aSize.Width();
+ double fScaleY = 1.0 / aSize.Height();
+
+ sal_Int32 nWrappedFormObject = 0;
+ if (!m_aContext.UseReferenceXObject)
+ {
+ // tdf#156842 increase scale for external PDF data
+ // Multiply PDF_INSERT_MAGIC_SCALE_FACTOR for platforms like macOS
+ // that scale all images by this number.
+ // This fix also allows the CppunitTest_vcl_pdfexport to run
+ // successfully on macOS.
+ fScaleX = fMagicScaleFactor / aSize.Width();
+ fScaleY = fMagicScaleFactor / aSize.Height();
+
+ // Parse the PDF data, we need that to write the PDF dictionary of our
+ // object.
+ if (rEmit.m_nExternalPDFDataIndex < 0)
+ return;
+ auto& rExternalPDFStream = m_aExternalPDFStreams.get(rEmit.m_nExternalPDFDataIndex);
+ auto& pPDFDocument = rExternalPDFStream.getPDFDocument();
+ if (!pPDFDocument)
+ {
+ // Couldn't parse the document and can't continue
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: failed to parse the document");
+ return;
+ }
+
+ std::vector<filter::PDFObjectElement*> aPages = pPDFDocument->GetPages();
+ if (aPages.empty())
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages");
+ return;
+ }
+
+ size_t nPageIndex = rEmit.m_nExternalPDFPageIndex >= 0 ? rEmit.m_nExternalPDFPageIndex : 0;
+
+ filter::PDFObjectElement* pPage = aPages[nPageIndex];
+ if (!pPage)
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page");
+ return;
+ }
+
+ double aOrigin[2] = { 0.0, 0.0 };
+ if (auto* pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("MediaBox"_ostr)))
+ {
+ const auto& rElements = pArray->GetElements();
+ if (rElements.size() >= 4)
+ {
+ // get x1, y1 of the rectangle.
+ for (sal_Int32 nIdx = 0; nIdx < 2; ++nIdx)
+ {
+ if (const auto* pNumElement = dynamic_cast<filter::PDFNumberElement*>(rElements[nIdx]))
+ aOrigin[nIdx] = pNumElement->GetValue();
+ }
+ }
+ }
+
+ std::vector<filter::PDFObjectElement*> aContentStreams;
+ if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"_ostr))
+ aContentStreams.push_back(pContentStream);
+ else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents"_ostr)))
+ {
+ for (const auto pElement : pArray->GetElements())
+ {
+ auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
+ if (!pReference)
+ continue;
+
+ filter::PDFObjectElement* pObject = pReference->LookupObject();
+ if (!pObject)
+ continue;
+
+ aContentStreams.push_back(pObject);
+ }
+ }
+
+ if (aContentStreams.empty())
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream");
+ return;
+ }
+
+ // Merge link annotations from pPage to our page.
+ std::vector<filter::PDFObjectElement*> aAnnots;
+ if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Annots"_ostr)))
+ {
+ for (const auto pElement : pArray->GetElements())
+ {
+ auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
+ if (!pReference)
+ {
+ continue;
+ }
+
+ filter::PDFObjectElement* pObject = pReference->LookupObject();
+ if (!pObject)
+ {
+ continue;
+ }
+
+ auto pType = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
+ if (!pType || pType->GetValue() != "Annot")
+ {
+ continue;
+ }
+
+ auto pSubtype = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr));
+ if (!pSubtype || pSubtype->GetValue() != "Link")
+ {
+ continue;
+ }
+
+ // Reference to a link annotation object, remember it.
+ aAnnots.push_back(pObject);
+ }
+ }
+ if (!aAnnots.empty())
+ {
+ PDFObjectCopier aCopier(*this);
+ SvMemoryStream& rDocBuffer = pPage->GetDocument().GetEditBuffer();
+ std::map<sal_Int32, sal_Int32> aMap;
+ for (const auto& pAnnot : aAnnots)
+ {
+ // Copy over the annotation and refer to its new id.
+ sal_Int32 nNewId = aCopier.copyExternalResource(rDocBuffer, *pAnnot, aMap);
+ m_aPages.back().m_aAnnotations.push_back(nNewId);
+ }
+ }
+
+ nWrappedFormObject = createObject();
+ // Write the form XObject wrapped below. This is a separate object from
+ // the wrapper, this way there is no need to alter the stream contents.
+
+ OStringBuffer aLine;
+ aLine.append(nWrappedFormObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /XObject");
+ aLine.append(" /Subtype /Form");
+
+ tools::Long nWidth = aSize.Width();
+ tools::Long nHeight = aSize.Height();
+ basegfx::B2DRange aBBox(0, 0, aSize.Width(), aSize.Height());
+ if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate"_ostr)))
+ {
+ // The original page was rotated, then construct a transformation matrix which does the
+ // same with our form object.
+ sal_Int32 nRotAngle = static_cast<sal_Int32>(pRotate->GetValue()) % 360;
+ // /Rotate is clockwise, matrix rotate is counter-clockwise.
+ sal_Int32 nAngle = -1 * nRotAngle;
+
+ // The bounding box just rotates.
+ basegfx::B2DHomMatrix aBBoxMat;
+ aBBoxMat.rotate(basegfx::deg2rad(pRotate->GetValue()));
+ aBBox.transform(aBBoxMat);
+
+ // Now transform the object: rotate around the center and make sure that the rotation
+ // doesn't affect the aspect ratio.
+ basegfx::B2DHomMatrix aMat;
+ aMat.translate(-0.5 * aBBox.getWidth() - aOrigin[0], -0.5 * aBBox.getHeight() - aOrigin[1]);
+ aMat.rotate(basegfx::deg2rad(nAngle));
+ aMat.translate(0.5 * nWidth, 0.5 * nHeight);
+
+ aLine.append(" /Matrix [ ");
+ aLine.append(aMat.a());
+ aLine.append(" ");
+ aLine.append(aMat.b());
+ aLine.append(" ");
+ aLine.append(aMat.c());
+ aLine.append(" ");
+ aLine.append(aMat.d());
+ aLine.append(" ");
+ aLine.append(aMat.e());
+ aLine.append(" ");
+ aLine.append(aMat.f());
+ aLine.append(" ] ");
+ }
+
+ PDFObjectCopier aCopier(*this);
+ auto & rResources = rExternalPDFStream.getCopiedResources();
+ aCopier.copyPageResources(pPage, aLine, rResources);
+
+ aLine.append(" /BBox [ ");
+ aLine.append(aOrigin[0]);
+ aLine.append(' ');
+ aLine.append(aOrigin[1]);
+ aLine.append(' ');
+ aLine.append(aBBox.getWidth() + aOrigin[0]);
+ aLine.append(' ');
+ aLine.append(aBBox.getHeight() + aOrigin[1]);
+ aLine.append(" ]");
+
+ if (!g_bDebugDisableCompression)
+ aLine.append(" /Filter/FlateDecode");
+ aLine.append(" /Length ");
+
+ SvMemoryStream aStream;
+ bool bCompressed = false;
+ sal_Int32 nLength = PDFObjectCopier::copyPageStreams(aContentStreams, aStream, bCompressed);
+ aLine.append(nLength);
+
+ aLine.append(">>\nstream\n");
+ if (g_bDebugDisableCompression)
+ {
+ emitComment("PDFWriterImpl::writeReferenceXObject, WrappedFormObject");
+ }
+ if (!updateObject(nWrappedFormObject))
+ return;
+ if (!writeBuffer(aLine))
+ return;
+ aLine.setLength(0);
+
+ checkAndEnableStreamEncryption(nWrappedFormObject);
+ // Copy the original page streams to the form XObject stream.
+ aLine.append(static_cast<const char*>(aStream.GetData()), aStream.GetSize());
+ if (!writeBuffer(aLine))
+ return;
+ aLine.setLength(0);
+ disableStreamEncryption();
+
+ aLine.append("\nendstream\nendobj\n\n");
+ if (!writeBuffer(aLine))
+ return;
+ }
+
+ OStringBuffer aLine;
+ if (g_bDebugDisableCompression)
+ {
+ emitComment("PDFWriterImpl::writeReferenceXObject, FormObject");
+ }
+ if (!updateObject(rEmit.m_nFormObject))
+ return;
+
+ // Now have all the info to write the form XObject.
+ aLine.append(rEmit.m_nFormObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /XObject");
+ aLine.append(" /Subtype /Form");
+ aLine.append(" /Resources << /XObject<<");
+
+ sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
+ aLine.append(" /Im");
+ aLine.append(nObject);
+ aLine.append(" ");
+ aLine.append(nObject);
+ aLine.append(" 0 R");
+
+ aLine.append(">> >>");
+ aLine.append(" /Matrix [ ");
+ appendDouble(fScaleX, aLine);
+ aLine.append(" 0 0 ");
+ appendDouble(fScaleY, aLine);
+ aLine.append(" 0 0 ]");
+ aLine.append(" /BBox [ 0 0 ");
+ // tdf#157680 reduce size by magic scale factor in /BBox
+ aLine.append(aSize.Width() / fMagicScaleFactor);
+ aLine.append(" ");
+ // tdf#157680 reduce size by magic scale factor in /BBox
+ aLine.append(aSize.Height() / fMagicScaleFactor);
+ aLine.append(" ]\n");
+
+ if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
+ {
+ // Write the reference dictionary.
+ aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) ");
+ if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
+ { // ISO 14289-1:2014, Clause: 7.11
+ aLine.append("/UF (<embedded file>) ");
+ }
+ aLine.append("/EF << /F ");
+ aLine.append(rEmit.m_nEmbeddedObject);
+ aLine.append(" 0 R >> >> /Page 0 >>\n");
+ }
+
+ aLine.append("/Length ");
+
+ OStringBuffer aStream;
+ aStream.append("q ");
+ if (m_aContext.UseReferenceXObject)
+ {
+ // Reference XObject markup is used, just refer to the fallback bitmap
+ // here.
+ aStream.append(aSize.Width());
+ aStream.append(" 0 0 ");
+ aStream.append(aSize.Height());
+ aStream.append(" 0 0 cm\n");
+ aStream.append("/Im");
+ aStream.append(rEmit.m_nBitmapObject);
+ aStream.append(" Do\n");
+ }
+ else
+ {
+ // Reset line width to the default.
+ aStream.append(" 1 w\n");
+
+ // vcl::RenderPDFBitmaps() effectively renders a white background for transparent input, be
+ // consistent with that.
+ aStream.append("1 1 1 rg\n");
+ aStream.append("0 0 ");
+ aStream.append(aSize.Width());
+ aStream.append(" ");
+ aStream.append(aSize.Height());
+ aStream.append(" re\n");
+ aStream.append("f*\n");
+
+ // Reset non-stroking color in case the XObject uses the default
+ aStream.append("0 0 0 rg\n");
+ // No reference XObject, draw the form XObject containing the original
+ // page streams.
+ aStream.append("/Im");
+ aStream.append(nWrappedFormObject);
+ aStream.append(" Do\n");
+ }
+ aStream.append("Q");
+ aLine.append(aStream.getLength());
+
+ aLine.append(">>\nstream\n");
+ if (!writeBuffer(aLine))
+ return;
+ aLine.setLength(0);
+
+ checkAndEnableStreamEncryption(rEmit.m_nFormObject);
+ aLine.append(aStream.getStr());
+ if (!writeBuffer(aLine))
+ return;
+ aLine.setLength(0);
+ disableStreamEncryption();
+
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN2(writeBuffer(aLine));
+}
+
+bool PDFWriterImpl::writeBitmapObject( const BitmapEmit& rObject, bool bMask )
+{
+ if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject)
+ {
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+ return true;
+ }
+
+ CHECK_RETURN( updateObject( rObject.m_nObject ) );
+
+ Bitmap aBitmap;
+ bool bWriteMask = false;
+ if( ! bMask )
+ {
+ aBitmap = rObject.m_aBitmap.GetBitmap();
+ if( rObject.m_aBitmap.IsAlpha() )
+ {
+ if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
+ bWriteMask = true;
+ // else draw without alpha channel
+ }
+ }
+ else
+ {
+ if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() )
+ {
+ if( rObject.m_aBitmap.IsAlpha() )
+ {
+ aBitmap = rObject.m_aBitmap.GetAlphaMask().GetBitmap();
+ aBitmap.Convert( BmpConversion::N1BitThreshold );
+ SAL_WARN_IF(aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP, "vcl.pdfwriter", "mask conversion failed" );
+ }
+ }
+ else if (aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP)
+ {
+ aBitmap = rObject.m_aBitmap.GetAlphaMask().GetBitmap();
+ aBitmap.Convert( BmpConversion::N8BitGreys );
+ SAL_WARN_IF(aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP, "vcl.pdfwriter", "alpha mask conversion failed" );
+ }
+ }
+
+ BitmapScopedReadAccess pAccess(aBitmap);
+
+ bool bTrueColor = true;
+ sal_Int32 nBitsPerComponent = 0;
+ auto const ePixelFormat = aBitmap.getPixelFormat();
+ switch (ePixelFormat)
+ {
+ case vcl::PixelFormat::N8_BPP:
+ bTrueColor = false;
+ nBitsPerComponent = vcl::pixelFormatBitCount(ePixelFormat);
+ break;
+ case vcl::PixelFormat::N24_BPP:
+ case vcl::PixelFormat::N32_BPP:
+ bTrueColor = true;
+ nBitsPerComponent = 8;
+ break;
+ case vcl::PixelFormat::INVALID:
+ return false;
+ }
+
+ sal_Int32 nStreamLengthObject = createObject();
+ sal_Int32 nMaskObject = 0;
+
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::writeBitmapObject" );
+ }
+ OStringBuffer aLine(1024);
+ aLine.append( rObject.m_nObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/XObject/Subtype/Image/Width " );
+ aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
+ aLine.append( "/Height " );
+ aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Height()) );
+ aLine.append( "/BitsPerComponent " );
+ aLine.append( nBitsPerComponent );
+ aLine.append( "/Length " );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 R\n" );
+ if (!g_bDebugDisableCompression)
+ {
+ if( nBitsPerComponent != 1 )
+ {
+ aLine.append( "/Filter/FlateDecode" );
+ }
+ else
+ {
+ aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " );
+ aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
+ aLine.append( ">>\n" );
+ }
+ }
+ if( ! bMask )
+ {
+ aLine.append( "/ColorSpace" );
+ if( bTrueColor )
+ aLine.append( "/DeviceRGB\n" );
+ else if( aBitmap.HasGreyPaletteAny() )
+ {
+ aLine.append( "/DeviceGray\n" );
+ if (aBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP)
+ {
+ // #i47395# 1 bit bitmaps occasionally have an inverted grey palette
+ sal_uInt16 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
+ assert( nBlackIndex == 0 || nBlackIndex == 1);
+ sal_uInt16 nWhiteIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_WHITE ) );
+ if( pAccess->GetPalette()[nBlackIndex] == BitmapColor( COL_BLACK ) &&
+ pAccess->GetPalette()[nWhiteIndex] == BitmapColor( COL_WHITE ) )
+ {
+ // It is black and white
+ if( nBlackIndex == 1 )
+ aLine.append( "/Decode[1 0]\n" );
+ }
+ else
+ {
+ // It is two levels of grey
+ aLine.append( "/Decode[" );
+ assert( pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetGreen() &&
+ pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetBlue() &&
+ pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetGreen() &&
+ pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetBlue() );
+ aLine.append( pAccess->GetPalette()[0].GetRed() / 255.0 );
+ aLine.append( " " );
+ aLine.append( pAccess->GetPalette()[1].GetRed() / 255.0 );
+ aLine.append( "]\n" );
+ }
+ }
+ }
+ else
+ {
+ aLine.append( "[ /Indexed/DeviceRGB " );
+ aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) );
+ aLine.append( "\n<" );
+ if( m_aContext.Encryption.Encrypt() )
+ {
+ enableStringEncryption( rObject.m_nObject );
+ //check encryption buffer size
+ m_vEncryptionBuffer.resize(pAccess->GetPaletteEntryCount()*3);
+ int nChar = 0;
+ //fill the encryption buffer
+ for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
+ {
+ const BitmapColor& rColor = pAccess->GetPaletteColor( i );
+ m_vEncryptionBuffer[nChar++] = rColor.GetRed();
+ m_vEncryptionBuffer[nChar++] = rColor.GetGreen();
+ m_vEncryptionBuffer[nChar++] = rColor.GetBlue();
+ }
+ //encrypt the colorspace lookup table
+ rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChar, m_vEncryptionBuffer.data(), nChar );
+ //now queue the data for output
+ nChar = 0;
+ for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
+ {
+ appendHex(m_vEncryptionBuffer[nChar++], aLine );
+ appendHex(m_vEncryptionBuffer[nChar++], aLine );
+ appendHex(m_vEncryptionBuffer[nChar++], aLine );
+ }
+ }
+ else //no encryption requested (PDF/A-1a program flow drops here)
+ {
+ for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
+ {
+ const BitmapColor& rColor = pAccess->GetPaletteColor( i );
+ appendHex( rColor.GetRed(), aLine );
+ appendHex( rColor.GetGreen(), aLine );
+ appendHex( rColor.GetBlue(), aLine );
+ }
+ }
+ aLine.append( ">\n]\n" );
+ }
+ }
+ else
+ {
+ aLine.append( "/ColorSpace/DeviceGray\n"
+ "/Decode [ 1 0 ]\n" );
+ }
+
+ if (!bMask && !m_bIsPDF_A1)
+ {
+ if( bWriteMask )
+ {
+ nMaskObject = createObject();
+ if (rObject.m_aBitmap.IsAlpha())
+ aLine.append( "/SMask " );
+ else
+ aLine.append( "/Mask " );
+ aLine.append( nMaskObject );
+ aLine.append( " 0 R\n" );
+ }
+ }
+ else if( m_bIsPDF_A1 && bWriteMask )
+ m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
+
+ aLine.append( ">>\n"
+ "stream\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ sal_uInt64 nStartPos = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartPos)) );
+
+ checkAndEnableStreamEncryption( rObject.m_nObject );
+ if (!g_bDebugDisableCompression && nBitsPerComponent == 1)
+ {
+ writeG4Stream(pAccess.get());
+ }
+ else
+ {
+ beginCompression();
+ if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
+ {
+ //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
+ const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
+
+ for( tools::Long i = 0; i < pAccess->Height(); i++ )
+ {
+ CHECK_RETURN( writeBufferBytes( pAccess->GetScanline( i ), nScanLineBytes ) );
+ }
+ }
+ else
+ {
+ const int nScanLineBytes = pAccess->Width()*3;
+ std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]);
+ for( tools::Long y = 0; y < pAccess->Height(); y++ )
+ {
+ for( tools::Long x = 0; x < pAccess->Width(); x++ )
+ {
+ BitmapColor aColor = pAccess->GetColor( y, x );
+ xCol[3*x+0] = aColor.GetRed();
+ xCol[3*x+1] = aColor.GetGreen();
+ xCol[3*x+2] = aColor.GetBlue();
+ }
+ CHECK_RETURN(writeBufferBytes(xCol.get(), nScanLineBytes));
+ }
+ }
+ endCompression();
+ }
+ disableStreamEncryption();
+
+ sal_uInt64 nEndPos = 0;
+ CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndPos)) );
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+ CHECK_RETURN( updateObject( nStreamLengthObject ) );
+ aLine.setLength( 0 );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
+ aLine.append( "\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine ) );
+
+ if( nMaskObject )
+ {
+ BitmapEmit aEmit;
+ aEmit.m_nObject = nMaskObject;
+ aEmit.m_aBitmap = rObject.m_aBitmap;
+ return writeBitmapObject( aEmit, true );
+ }
+
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+
+ return true;
+}
+
+void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject)
+{
+ // The bitmap object is always a valid identifier, even if the graphic has
+ // no pdf data.
+ rEmit.m_nBitmapObject = nBitmapObject;
+
+ if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf)
+ return;
+
+ BinaryDataContainer const & rDataContainer = rGraphic.getVectorGraphicData()->getBinaryDataContainer();
+
+ if (m_aContext.UseReferenceXObject)
+ {
+ // Store the original PDF data as an embedded file.
+ auto nObjectID = addEmbeddedFile(rDataContainer);
+ rEmit.m_nEmbeddedObject = nObjectID;
+ }
+ else
+ {
+ sal_Int32 aIndex = m_aExternalPDFStreams.store(rDataContainer);
+ rEmit.m_nExternalPDFPageIndex = rGraphic.getVectorGraphicData()->getPageIndex();
+ rEmit.m_nExternalPDFDataIndex = aIndex;
+ }
+
+ rEmit.m_nFormObject = createObject();
+ rEmit.m_aPixelSize = rGraphic.GetPrefSize();
+}
+
+void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const AlphaMask& rAlphaMask, const Graphic& rGraphic )
+{
+ MARK( "drawJPGBitmap" );
+
+ OStringBuffer aLine( 80 );
+ updateGraphicsState();
+
+ // #i40055# sanity check
+ if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) )
+ return;
+ if( ! (rSizePixel.Width() && rSizePixel.Height()) )
+ return;
+
+ rDCTData.Seek( 0 );
+ if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale )
+ {
+ // need to convert to grayscale;
+ // load stream to bitmap and draw the bitmap instead
+ Graphic aGraphic;
+ GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG );
+ if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == aGraphic.GetSizePixel() )
+ {
+ Bitmap aBmp( aGraphic.GetBitmapEx().GetBitmap() );
+ BitmapEx aBmpEx( aBmp, rAlphaMask );
+ drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmpEx );
+ }
+ else
+ drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmapEx() );
+ return;
+ }
+
+ std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream);
+ pStream->WriteStream( rDCTData );
+ pStream->Seek( STREAM_SEEK_TO_END );
+
+ BitmapID aID;
+ aID.m_aPixelSize = rSizePixel;
+ aID.m_nSize = pStream->Tell();
+ pStream->Seek( STREAM_SEEK_TO_BEGIN );
+ aID.m_nChecksum = rtl_crc32( 0, pStream->GetData(), aID.m_nSize );
+ if( ! rAlphaMask.IsEmpty() )
+ aID.m_nMaskChecksum = rAlphaMask.GetChecksum();
+
+ std::vector< JPGEmit >::const_iterator it = std::find_if(m_aJPGs.begin(), m_aJPGs.end(),
+ [&](const JPGEmit& arg) { return aID == arg.m_aID; });
+ if( it == m_aJPGs.end() )
+ {
+ m_aJPGs.emplace( m_aJPGs.begin() );
+ JPGEmit& rEmit = m_aJPGs.front();
+ if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
+ rEmit.m_nObject = createObject();
+ rEmit.m_aID = aID;
+ rEmit.m_pStream = std::move( pStream );
+ rEmit.m_bTrueColor = bIsTrueColor;
+ if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == rSizePixel )
+ rEmit.m_aAlphaMask = rAlphaMask;
+ createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject);
+
+ it = m_aJPGs.begin();
+ }
+
+ aLine.append( "q " );
+ sal_Int32 nCheckWidth = 0;
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth );
+ aLine.append( " 0 0 " );
+ sal_Int32 nCheckHeight = 0;
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine );
+ aLine.append( " cm\n/Im" );
+ sal_Int32 nObject = it->m_aReferenceXObject.getObject();
+ aLine.append(nObject);
+ aLine.append( " Do Q\n" );
+ if( nCheckWidth == 0 || nCheckHeight == 0 )
+ {
+ // #i97512# avoid invalid current matrix
+ aLine.setLength( 0 );
+ aLine.append( "\n%jpeg image /Im" );
+ aLine.append( it->m_nObject );
+ aLine.append( " scaled to zero size, omitted\n" );
+ }
+ writeBuffer( aLine );
+
+ OString aObjName = "Im" + OString::number(nObject);
+ pushResource( ResourceKind::XObject, aObjName, nObject );
+
+}
+
+void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor )
+{
+ OStringBuffer aLine( 80 );
+ updateGraphicsState();
+
+ aLine.append( "q " );
+ if( rFillColor != COL_TRANSPARENT )
+ {
+ appendNonStrokingColor( rFillColor, aLine );
+ aLine.append( ' ' );
+ }
+ sal_Int32 nCheckWidth = 0;
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), aLine, false, &nCheckWidth );
+ aLine.append( " 0 0 " );
+ sal_Int32 nCheckHeight = 0;
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), aLine, true, &nCheckHeight );
+ aLine.append( ' ' );
+ m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), aLine );
+ aLine.append( " cm\n/Im" );
+ sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject();
+ aLine.append(nObject);
+ aLine.append( " Do Q\n" );
+ if( nCheckWidth == 0 || nCheckHeight == 0 )
+ {
+ // #i97512# avoid invalid current matrix
+ aLine.setLength( 0 );
+ aLine.append( "\n%bitmap image /Im" );
+ aLine.append( rBitmap.m_nObject );
+ aLine.append( " scaled to zero size, omitted\n" );
+ }
+ writeBuffer( aLine );
+}
+
+const BitmapEmit& PDFWriterImpl::createBitmapEmit(const BitmapEx& i_rBitmap, const Graphic& rGraphic, std::list<BitmapEmit>& rBitmaps, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams)
+{
+ BitmapEx aBitmap( i_rBitmap );
+ auto ePixelFormat = aBitmap.GetBitmap().getPixelFormat();
+ if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
+ aBitmap.Convert(BmpConversion::N8BitGreys);
+ BitmapID aID;
+ aID.m_aPixelSize = aBitmap.GetSizePixel();
+ aID.m_nSize = vcl::pixelFormatBitCount(ePixelFormat);
+ aID.m_nChecksum = aBitmap.GetBitmap().GetChecksum();
+ aID.m_nMaskChecksum = 0;
+ if( aBitmap.IsAlpha() )
+ aID.m_nMaskChecksum = aBitmap.GetAlphaMask().GetChecksum();
+ std::list<BitmapEmit>::const_iterator it = std::find_if(rBitmaps.begin(), rBitmaps.end(),
+ [&](const BitmapEmit& arg) { return aID == arg.m_aID; });
+ if (it == rBitmaps.end())
+ {
+ rBitmaps.push_front(BitmapEmit());
+ rBitmaps.front().m_aID = aID;
+ rBitmaps.front().m_aBitmap = aBitmap;
+ if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
+ rBitmaps.front().m_nObject = createObject();
+ createEmbeddedFile(rGraphic, rBitmaps.front().m_aReferenceXObject, rBitmaps.front().m_nObject);
+ it = rBitmaps.begin();
+ }
+
+ sal_Int32 nObject = it->m_aReferenceXObject.getObject();
+ OString aObjName = "Im" + OString::number(nObject);
+ pushResource(ResourceKind::XObject, aObjName, nObject, rResourceDict, rOutputStreams);
+
+ return *it;
+}
+
+const BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& i_rBitmap, const Graphic& rGraphic )
+{
+ return createBitmapEmit(i_rBitmap, rGraphic, m_aBitmaps, m_aGlobalResourceDict, m_aOutputStreams);
+}
+
+void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
+{
+ MARK( "drawBitmap (Bitmap)" );
+
+ // #i40055# sanity check
+ if( ! (rDestSize.Width() && rDestSize.Height()) )
+ return;
+
+ const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( rBitmap ), rGraphic );
+ drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
+}
+
+void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap )
+{
+ MARK( "drawBitmap (BitmapEx)" );
+
+ // #i40055# sanity check
+ if( ! (rDestSize.Width() && rDestSize.Height()) )
+ return;
+
+ const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() );
+ drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
+}
+
+sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize )
+{
+ Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
+ MapMode( MapUnit::MapPoint ),
+ this,
+ rSize ) );
+ // check if we already have this gradient
+ // rounding to point will generally lose some pixels
+ // round up to point boundary
+ aPtSize.AdjustWidth( 1 );
+ aPtSize.AdjustHeight( 1 );
+ std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(),
+ [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); });
+
+ if( it == m_aGradients.end() )
+ {
+ m_aGradients.push_front( GradientEmit() );
+ m_aGradients.front().m_aGradient = rGradient;
+ m_aGradients.front().m_nObject = createObject();
+ m_aGradients.front().m_aSize = aPtSize;
+ it = m_aGradients.begin();
+ }
+
+ OStringBuffer aObjName( 16 );
+ aObjName.append( 'P' );
+ aObjName.append( it->m_nObject );
+ pushResource( ResourceKind::Shading, aObjName.makeStringAndClear(), it->m_nObject );
+
+ return it->m_nObject;
+}
+
+void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
+{
+ MARK( "drawGradient (Rectangle)" );
+
+ sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() );
+
+ Point aTranslate( rRect.BottomLeft() );
+ aTranslate += Point( 0, 1 );
+
+ updateGraphicsState();
+
+ OStringBuffer aLine( 80 );
+ aLine.append( "q 1 0 0 1 " );
+ m_aPages.back().appendPoint( aTranslate, aLine );
+ aLine.append( " cm " );
+ // if a stroke is appended reset the clip region before stroke
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ aLine.append( "q " );
+ aLine.append( "0 0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
+ aLine.append( " re W n\n" );
+
+ aLine.append( "/P" );
+ aLine.append( nGradient );
+ aLine.append( " sh " );
+ if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
+ {
+ aLine.append( "Q 0 0 " );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
+ aLine.append( ' ' );
+ m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
+ aLine.append( " re S " );
+ }
+ aLine.append( "Q\n" );
+ writeBuffer( aLine );
+}
+
+void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
+{
+ MARK( "drawHatch" );
+
+ updateGraphicsState();
+
+ if( rPolyPoly.Count() )
+ {
+ tools::PolyPolygon aPolyPoly( rPolyPoly );
+
+ aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
+ push( PushFlags::LINECOLOR );
+ setLineColor( rHatch.GetColor() );
+ DrawHatch( aPolyPoly, rHatch, false );
+ pop();
+ }
+}
+
+void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall )
+{
+ MARK( "drawWallpaper" );
+
+ bool bDrawColor = false;
+ bool bDrawGradient = false;
+ bool bDrawBitmap = false;
+
+ BitmapEx aBitmap;
+ Point aBmpPos = rRect.TopLeft();
+ Size aBmpSize;
+ if( rWall.IsBitmap() )
+ {
+ aBitmap = rWall.GetBitmap();
+ aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(),
+ getMapMode(),
+ this,
+ aBitmap.GetPrefSize() );
+ tools::Rectangle aRect( rRect );
+ if( rWall.IsRect() )
+ {
+ aRect = rWall.GetRect();
+ aBmpPos = aRect.TopLeft();
+ aBmpSize = aRect.GetSize();
+ }
+ if( rWall.GetStyle() != WallpaperStyle::Scale )
+ {
+ if( rWall.GetStyle() != WallpaperStyle::Tile )
+ {
+ bDrawBitmap = true;
+ if( rWall.IsGradient() )
+ bDrawGradient = true;
+ else
+ bDrawColor = true;
+ switch( rWall.GetStyle() )
+ {
+ case WallpaperStyle::TopLeft:
+ break;
+ case WallpaperStyle::Top:
+ aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
+ break;
+ case WallpaperStyle::Left:
+ aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
+ break;
+ case WallpaperStyle::TopRight:
+ aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
+ break;
+ case WallpaperStyle::Center:
+ aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
+ aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
+ break;
+ case WallpaperStyle::Right:
+ aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
+ aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
+ break;
+ case WallpaperStyle::BottomLeft:
+ aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
+ break;
+ case WallpaperStyle::Bottom:
+ aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
+ aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
+ break;
+ case WallpaperStyle::BottomRight:
+ aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
+ aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
+ break;
+ default: ;
+ }
+ }
+ else
+ {
+ // push the bitmap
+ const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() );
+
+ // convert to page coordinates; this needs to be done here
+ // since the emit does not know the page anymore
+ tools::Rectangle aConvertRect( aBmpPos, aBmpSize );
+ m_aPages.back().convertRect( aConvertRect );
+
+ OString aImageName = "Im" + OString::number( rEmit.m_nObject );
+
+ // push the pattern
+ OStringBuffer aTilingStream( 32 );
+ appendFixedInt( aConvertRect.GetWidth(), aTilingStream );
+ aTilingStream.append( " 0 0 " );
+ appendFixedInt( aConvertRect.GetHeight(), aTilingStream );
+ aTilingStream.append( " 0 0 cm\n/" );
+ aTilingStream.append( aImageName );
+ aTilingStream.append( " Do\n" );
+
+ m_aTilings.emplace_back( );
+ m_aTilings.back().m_nObject = createObject();
+ m_aTilings.back().m_aRectangle = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() );
+ m_aTilings.back().m_pTilingStream.reset(new SvMemoryStream());
+ m_aTilings.back().m_pTilingStream->WriteBytes(
+ aTilingStream.getStr(), aTilingStream.getLength() );
+ // phase the tiling so wallpaper begins on upper left
+ if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0))
+ throw o3tl::divide_by_zero();
+ m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor;
+ m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor;
+ m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject;
+
+ updateGraphicsState();
+
+ OStringBuffer aObjName( 16 );
+ aObjName.append( 'P' );
+ aObjName.append( m_aTilings.back().m_nObject );
+ OString aPatternName( aObjName.makeStringAndClear() );
+ pushResource( ResourceKind::Pattern, aPatternName, m_aTilings.back().m_nObject );
+
+ // fill a rRect with the pattern
+ OStringBuffer aLine( 100 );
+ aLine.append( "q /Pattern cs /" );
+ aLine.append( aPatternName );
+ aLine.append( " scn " );
+ m_aPages.back().appendRect( rRect, aLine );
+ aLine.append( " f Q\n" );
+ writeBuffer( aLine );
+ }
+ }
+ else
+ {
+ aBmpPos = aRect.TopLeft();
+ aBmpSize = aRect.GetSize();
+ bDrawBitmap = true;
+ }
+
+ if( aBitmap.IsAlpha() )
+ {
+ if( rWall.IsGradient() )
+ bDrawGradient = true;
+ else
+ bDrawColor = true;
+ }
+ }
+ else if( rWall.IsGradient() )
+ bDrawGradient = true;
+ else
+ bDrawColor = true;
+
+ if( bDrawGradient )
+ {
+ drawGradient( rRect, rWall.GetGradient() );
+ }
+ if( bDrawColor )
+ {
+ Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor;
+ Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
+ setLineColor( COL_TRANSPARENT );
+ setFillColor( rWall.GetColor() );
+ drawRectangle( rRect );
+ setLineColor( aOldLineColor );
+ setFillColor( aOldFillColor );
+ }
+ if( bDrawBitmap )
+ {
+ // set temporary clip region since aBmpPos and aBmpSize
+ // may be outside rRect
+ OStringBuffer aLine( 20 );
+ aLine.append( "q " );
+ m_aPages.back().appendRect( rRect, aLine );
+ aLine.append( " W n\n" );
+ writeBuffer( aLine );
+ drawBitmap( aBmpPos, aBmpSize, aBitmap );
+ writeBuffer( "Q\n" );
+ }
+}
+
+void PDFWriterImpl::updateGraphicsState(Mode const mode)
+{
+ OStringBuffer aLine( 256 );
+ GraphicsState& rNewState = m_aGraphicsStack.front();
+ // first set clip region since it might invalidate everything else
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion;
+
+ if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion ||
+ ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) )
+ {
+ if( m_aCurrentPDFState.m_bClipRegion )
+ {
+ aLine.append( "Q " );
+ // invalidate everything but the clip region
+ m_aCurrentPDFState = GraphicsState();
+ rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion;
+ }
+ if( rNewState.m_bClipRegion )
+ {
+ // clip region is always stored in private PDF mapmode
+ MapMode aNewMapMode = rNewState.m_aMapMode;
+ rNewState.m_aMapMode = m_aMapMode;
+ SetMapMode( rNewState.m_aMapMode );
+ m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
+
+ aLine.append("q ");
+ if ( rNewState.m_aClipRegion.count() )
+ {
+ m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, aLine );
+ }
+ else
+ {
+ // tdf#130150 Need to revert tdf#99680, that breaks the
+ // rule that an set but empty clip-region clips everything
+ // aka draws nothing -> nothing is in an empty clip-region
+ aLine.append( "0 0 m h " ); // NULL clip, i.e. nothing visible
+ }
+ aLine.append( "W* n\n" );
+
+ rNewState.m_aMapMode = aNewMapMode;
+ SetMapMode( rNewState.m_aMapMode );
+ m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
+ }
+ }
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode;
+ SetMapMode( rNewState.m_aMapMode );
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font;
+ SetFont( rNewState.m_aFont );
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode;
+ SetLayoutMode( rNewState.m_nLayoutMode );
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage;
+ SetDigitLanguage( rNewState.m_aDigitLanguage );
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor;
+ if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor &&
+ rNewState.m_aLineColor != COL_TRANSPARENT )
+ {
+ appendStrokingColor( rNewState.m_aLineColor, aLine );
+ aLine.append( "\n" );
+ }
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor;
+ if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor &&
+ rNewState.m_aFillColor != COL_TRANSPARENT )
+ {
+ appendNonStrokingColor( rNewState.m_aFillColor, aLine );
+ aLine.append( "\n" );
+ }
+ }
+
+ if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent )
+ {
+ rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent;
+ if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
+ {
+ // TODO: switch extended graphicsstate
+ }
+ }
+
+ // everything is up to date now
+ m_aCurrentPDFState = m_aGraphicsStack.front();
+ if ((mode != Mode::NOWRITE) && !aLine.isEmpty())
+ writeBuffer( aLine );
+}
+
+/* #i47544# imitate OutputDevice behaviour:
+* if a font with a nontransparent color is set, it overwrites the current
+* text color. OTOH setting the text color will overwrite the color of the font.
+*/
+void PDFWriterImpl::setFont( const vcl::Font& rFont )
+{
+ Color aColor = rFont.GetColor();
+ if( aColor == COL_TRANSPARENT )
+ aColor = m_aGraphicsStack.front().m_aFont.GetColor();
+ m_aGraphicsStack.front().m_aFont = rFont;
+ m_aGraphicsStack.front().m_aFont.SetColor( aColor );
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
+}
+
+void PDFWriterImpl::push( PushFlags nFlags )
+{
+ OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" );
+ m_aGraphicsStack.push_front( m_aGraphicsStack.front() );
+ m_aGraphicsStack.front().m_nFlags = nFlags;
+}
+
+void PDFWriterImpl::pop()
+{
+ OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" );
+ if( m_aGraphicsStack.size() < 2 )
+ return;
+
+ GraphicsState aState = m_aGraphicsStack.front();
+ m_aGraphicsStack.pop_front();
+ GraphicsState& rOld = m_aGraphicsStack.front();
+
+ // move those parameters back that were not pushed
+ // in the first place
+ if( ! (aState.m_nFlags & PushFlags::LINECOLOR) )
+ setLineColor( aState.m_aLineColor );
+ if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) )
+ setFillColor( aState.m_aFillColor );
+ if( ! (aState.m_nFlags & PushFlags::FONT) )
+ setFont( aState.m_aFont );
+ if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) )
+ setTextColor( aState.m_aFont.GetColor() );
+ if( ! (aState.m_nFlags & PushFlags::MAPMODE) )
+ setMapMode( aState.m_aMapMode );
+ if( ! (aState.m_nFlags & PushFlags::CLIPREGION) )
+ {
+ // do not use setClipRegion here
+ // it would convert again assuming the current mapmode
+ rOld.m_aClipRegion = aState.m_aClipRegion;
+ rOld.m_bClipRegion = aState.m_bClipRegion;
+ }
+ if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) )
+ setTextLineColor( aState.m_aTextLineColor );
+ if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) )
+ setOverlineColor( aState.m_aOverlineColor );
+ if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) )
+ setTextAlign( aState.m_aFont.GetAlignment() );
+ if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) )
+ setTextFillColor( aState.m_aFont.GetFillColor() );
+ if( ! (aState.m_nFlags & PushFlags::REFPOINT) )
+ {
+ // what ?
+ }
+ // invalidate graphics state
+ m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All;
+}
+
+void PDFWriterImpl::setMapMode( const MapMode& rMapMode )
+{
+ m_aGraphicsStack.front().m_aMapMode = rMapMode;
+ SetMapMode( rMapMode );
+ m_aCurrentPDFState.m_aMapMode = rMapMode;
+}
+
+void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion )
+{
+ // tdf#130150 improve coordinate manipulations to double precision transformations
+ const basegfx::B2DHomMatrix aCurrentTransform(
+ GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
+ basegfx::B2DPolyPolygon aRegion(rRegion);
+
+ aRegion.transform(aCurrentTransform);
+ m_aGraphicsStack.front().m_aClipRegion = aRegion;
+ m_aGraphicsStack.front().m_bClipRegion = true;
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
+}
+
+void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY )
+{
+ if( !(m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count()) )
+ return;
+
+ // tdf#130150 improve coordinate manipulations to double precision transformations
+ basegfx::B2DHomMatrix aConvertA;
+
+ if(MapUnit::MapPixel == m_aGraphicsStack.front().m_aMapMode.GetMapUnit())
+ {
+ aConvertA = GetInverseViewTransformation(m_aMapMode);
+ }
+ else
+ {
+ aConvertA = LogicToLogic(m_aGraphicsStack.front().m_aMapMode, m_aMapMode);
+ }
+
+ basegfx::B2DPoint aB2DPointA(nX, nY);
+ basegfx::B2DPoint aB2DPointB(0.0, 0.0);
+ aB2DPointA *= aConvertA;
+ aB2DPointB *= aConvertA;
+ aB2DPointA -= aB2DPointB;
+ basegfx::B2DHomMatrix aMat;
+
+ aMat.translate(aB2DPointA.getX(), aB2DPointA.getY());
+ m_aGraphicsStack.front().m_aClipRegion.transform( aMat );
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
+}
+
+void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect )
+{
+ basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect) ) );
+ intersectClipRegion( aRect );
+}
+
+void PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion )
+{
+ // tdf#130150 improve coordinate manipulations to double precision transformations
+ const basegfx::B2DHomMatrix aCurrentTransform(
+ GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
+ basegfx::B2DPolyPolygon aRegion(rRegion);
+
+ aRegion.transform(aCurrentTransform);
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
+
+ if( m_aGraphicsStack.front().m_bClipRegion )
+ {
+ basegfx::B2DPolyPolygon aOld( basegfx::utils::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) );
+ aRegion = basegfx::utils::prepareForPolygonOperation( aRegion );
+ m_aGraphicsStack.front().m_aClipRegion = basegfx::utils::solvePolygonOperationAnd( aOld, aRegion );
+ }
+ else
+ {
+ m_aGraphicsStack.front().m_aClipRegion = aRegion;
+ m_aGraphicsStack.front().m_bClipRegion = true;
+ }
+}
+
+void PDFWriterImpl::createNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
+{
+ if (nPageNr < 0)
+ nPageNr = m_nCurrentPage;
+
+ if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size())
+ return;
+
+ m_aNotes.emplace_back();
+ auto & rNoteEntry = m_aNotes.back();
+ rNoteEntry.m_nObject = createObject();
+ rNoteEntry.m_aPopUpAnnotation.m_nObject = createObject();
+ rNoteEntry.m_aPopUpAnnotation.m_nParentObject = rNoteEntry.m_nObject;
+ rNoteEntry.m_aContents = rNote;
+ rNoteEntry.m_aRect = rRect;
+ rNoteEntry.m_nPage = nPageNr;
+ // convert to default user space now, since the mapmode may change
+ m_aPages[nPageNr].convertRect(rNoteEntry.m_aRect);
+
+ // insert note to page's annotation list
+ m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_nObject);
+ m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_aPopUpAnnotation.m_nObject);
+}
+
+sal_Int32 PDFWriterImpl::createLink(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText)
+{
+ if( nPageNr < 0 )
+ nPageNr = m_nCurrentPage;
+
+ if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
+ return -1;
+
+ sal_Int32 nRet = m_aLinks.size();
+
+ m_aLinks.emplace_back(rAltText);
+ m_aLinks.back().m_nObject = createObject();
+ m_aLinks.back().m_nPage = nPageNr;
+ m_aLinks.back().m_aRect = rRect;
+ // convert to default user space now, since the mapmode may change
+ m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect );
+
+ // insert link to page's annotation list
+ m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject );
+
+ return nRet;
+}
+
+sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText, OUString const& rMimeType)
+{
+ if (nPageNr < 0)
+ nPageNr = m_nCurrentPage;
+
+ if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size())
+ return -1;
+
+ sal_Int32 nRet = m_aScreens.size();
+
+ m_aScreens.emplace_back(rAltText, rMimeType);
+ m_aScreens.back().m_nObject = createObject();
+ m_aScreens.back().m_nPage = nPageNr;
+ m_aScreens.back().m_aRect = rRect;
+ // Convert to default user space now, since the mapmode may change.
+ m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect);
+
+ // Insert link to page's annotation list.
+ m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject);
+
+ return nRet;
+}
+
+sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ if( nPageNr < 0 )
+ nPageNr = m_nCurrentPage;
+
+ if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
+ return -1;
+
+ sal_Int32 nRet = m_aNamedDests.size();
+
+ m_aNamedDests.emplace_back( );
+ m_aNamedDests.back().m_aDestName = sDestName;
+ m_aNamedDests.back().m_nPage = nPageNr;
+ m_aNamedDests.back().m_eType = eType;
+ m_aNamedDests.back().m_aRect = rRect;
+ // convert to default user space now, since the mapmode may change
+ m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect );
+
+ return nRet;
+}
+
+sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ if( nPageNr < 0 )
+ nPageNr = m_nCurrentPage;
+
+ if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
+ return -1;
+
+ sal_Int32 nRet = m_aDests.size();
+
+ m_aDests.emplace_back( );
+ m_aDests.back().m_nPage = nPageNr;
+ m_aDests.back().m_eType = eType;
+ m_aDests.back().m_aRect = rRect;
+ // convert to default user space now, since the mapmode may change
+ m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect );
+
+ return nRet;
+}
+
+sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
+{
+ m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType );
+ return m_aDestinationIdTranslation[ nDestId ];
+}
+
+void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
+{
+ if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
+ return;
+ if( nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size() )
+ return;
+
+ m_aLinks[ nLinkId ].m_nDest = nDestId;
+}
+
+void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
+{
+ if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
+ return;
+
+ m_aLinks[ nLinkId ].m_nDest = -1;
+
+ using namespace ::com::sun::star;
+
+ if (!m_xTrans.is())
+ {
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ m_xTrans = util::URLTransformer::create(xContext);
+ }
+
+ util::URL aURL;
+ aURL.Complete = rURL;
+
+ m_xTrans->parseStrict( aURL );
+
+ m_aLinks[ nLinkId ].m_aURL = aURL.Complete;
+}
+
+void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL)
+{
+ if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size())
+ return;
+
+ m_aScreens[nScreenId].m_aURL = rURL;
+}
+
+void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL)
+{
+ if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size())
+ return;
+
+ m_aScreens[nScreenId].m_aTempFileURL = rURL;
+ m_aScreens[nScreenId].m_nTempFileObject = createObject();
+}
+
+void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId )
+{
+ m_aLinkPropertyMap[ nPropertyId ] = nLinkId;
+}
+
+sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, std::u16string_view rText, sal_Int32 nDestID )
+{
+ // create new item
+ sal_Int32 nNewItem = m_aOutline.size();
+ m_aOutline.emplace_back( );
+
+ // set item attributes
+ setOutlineItemParent( nNewItem, nParent );
+ setOutlineItemText( nNewItem, rText );
+ setOutlineItemDest( nNewItem, nDestID );
+
+ return nNewItem;
+}
+
+void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent )
+{
+ if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() )
+ return;
+
+ if( nNewParent < 0 || o3tl::make_unsigned(nNewParent) >= m_aOutline.size() || nNewParent == nItem )
+ {
+ nNewParent = 0;
+ }
+ // insert item to new parent's list of children
+ m_aOutline[ nNewParent ].m_aChildren.push_back( nItem );
+}
+
+void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, std::u16string_view rText )
+{
+ if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() )
+ return;
+
+ m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText );
+}
+
+void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
+{
+ if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) // item does not exist
+ return;
+ if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() ) // dest does not exist
+ return;
+ m_aOutline[nItem].m_nDestID = nDestID;
+}
+
+const char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType )
+{
+ static constexpr auto aTagStrings = frozen::make_map<PDFWriter::StructElement, const char*>({
+ { PDFWriter::NonStructElement, "NonStruct" },
+ { PDFWriter::Document, "Document" },
+ { PDFWriter::Part, "Part" },
+ { PDFWriter::Article, "Art" },
+ { PDFWriter::Section, "Sect" },
+ { PDFWriter::Division, "Div" },
+ { PDFWriter::BlockQuote, "BlockQuote" },
+ { PDFWriter::Caption, "Caption" },
+ { PDFWriter::TOC, "TOC" },
+ { PDFWriter::TOCI, "TOCI" },
+ { PDFWriter::Index, "Index" },
+ { PDFWriter::Paragraph, "P" },
+ { PDFWriter::Heading, "H" },
+ { PDFWriter::H1, "H1" },
+ { PDFWriter::H2, "H2" },
+ { PDFWriter::H3, "H3" },
+ { PDFWriter::H4, "H4" },
+ { PDFWriter::H5, "H5" },
+ { PDFWriter::H6, "H6" },
+ { PDFWriter::List, "L" },
+ { PDFWriter::ListItem, "LI" },
+ { PDFWriter::LILabel, "Lbl" },
+ { PDFWriter::LIBody, "LBody" },
+ { PDFWriter::Table, "Table" },
+ { PDFWriter::TableRow, "TR" },
+ { PDFWriter::TableHeader, "TH" },
+ { PDFWriter::TableData, "TD" },
+ { PDFWriter::Span, "Span" },
+ { PDFWriter::Quote, "Quote" },
+ { PDFWriter::Note, "Note" },
+ { PDFWriter::Reference, "Reference" },
+ { PDFWriter::BibEntry, "BibEntry" },
+ { PDFWriter::Code, "Code" },
+ { PDFWriter::Link, "Link" },
+ { PDFWriter::Annot, "Annot" },
+ { PDFWriter::Ruby, "Ruby" },
+ { PDFWriter::RB, "RB" },
+ { PDFWriter::RT, "RT" },
+ { PDFWriter::RP, "RP" },
+ { PDFWriter::Warichu, "Warichu" },
+ { PDFWriter::WT, "WT" },
+ { PDFWriter::WP, "WP" },
+ { PDFWriter::Figure, "Figure" },
+ { PDFWriter::Formula, "Formula"},
+ { PDFWriter::Form, "Form" }
+ });
+
+ if (eType == PDFWriter::Annot
+ && m_aContext.Version < PDFWriter::PDFVersion::PDF_1_5)
+ {
+ return "Figure"; // fallback
+ }
+
+ auto it = aTagStrings.find( eType );
+
+ return it != aTagStrings.end() ? it->second : "Div";
+}
+
+void PDFWriterImpl::addRoleMap(OString aAlias, PDFWriter::StructElement eType)
+{
+ OString aTag = getStructureTag(eType);
+ // For PDF/UA it's not allowed to map an alias with the same name.
+ // Not aware of a reason for doing it in any case, so just don't do it.
+ if (aAlias != aTag)
+ m_aRoleMap[aAlias] = aTag;
+}
+
+void PDFWriterImpl::beginStructureElementMCSeq()
+{
+ assert(m_nCurrentStructElement == 0 || m_aStructure[m_nCurrentStructElement].m_oType);
+ if( m_bEmitStructure &&
+ m_nCurrentStructElement > 0 && // StructTreeRoot
+ // Document = SwPageFrame => this is not *inside* the page content
+ // stream so do not emit MCID!
+ m_aStructure[m_nCurrentStructElement].m_oType &&
+ *m_aStructure[m_nCurrentStructElement].m_oType != PDFWriter::Document &&
+ ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
+ )
+ {
+ PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ];
+ OStringBuffer aLine( 128 );
+ sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size();
+ aLine.append( "/" );
+ if( !rEle.m_aAlias.isEmpty() )
+ aLine.append( rEle.m_aAlias );
+ else
+ aLine.append( getStructureTag(*rEle.m_oType) );
+ aLine.append( "<</MCID " );
+ aLine.append( nMCID );
+ aLine.append( ">>BDC\n" );
+ writeBuffer( aLine );
+
+ // update the element's content list
+ SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object "
+ << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = "
+ << rEle.m_nFirstPageObject);
+ rEle.m_aKids.emplace_back(MCIDReference{m_aPages[m_nCurrentPage].m_nPageObject, nMCID});
+ // update the page's mcid parent list
+ m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject );
+ // mark element MC sequence as open
+ rEle.m_bOpenMCSeq = true;
+ }
+ // handle artifacts
+ else if( ! m_bEmitStructure && m_aContext.Tagged &&
+ m_nCurrentStructElement > 0 &&
+ m_aStructure[m_nCurrentStructElement].m_oType &&
+ *m_aStructure[m_nCurrentStructElement].m_oType == PDFWriter::NonStructElement &&
+ ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
+ )
+ {
+ OString aLine = "/Artifact "_ostr;
+ writeBuffer( aLine );
+ // emit property list if requested
+ OStringBuffer buf;
+ for (auto const& rAttr : m_aStructure[m_nCurrentStructElement].m_aAttributes)
+ {
+ appendStructureAttributeLine(rAttr.first, rAttr.second, buf, false);
+ }
+ if (buf.isEmpty())
+ {
+ writeBuffer("BMC\n");
+ }
+ else
+ {
+ writeBuffer("<<");
+ writeBuffer(buf);
+ writeBuffer(">> BDC\n");
+ }
+ // mark element MC sequence as open
+ m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
+ }
+}
+
+void PDFWriterImpl::endStructureElementMCSeq(EndMode const endMode)
+{
+ if (m_nCurrentStructElement > 0 // not StructTreeRoot
+ && m_aStructure[m_nCurrentStructElement].m_oType
+ && (m_bEmitStructure
+ || (endMode != EndMode::OnlyStruct
+ && m_aStructure[m_nCurrentStructElement].m_oType == PDFWriter::NonStructElement))
+ && m_aStructure[m_nCurrentStructElement].m_bOpenMCSeq)
+ {
+ writeBuffer( "EMC\n" );
+ m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false;
+ }
+}
+
+bool PDFWriterImpl::checkEmitStructure()
+{
+ bool bEmit = false;
+ if( m_aContext.Tagged )
+ {
+ bEmit = true;
+ sal_Int32 nEle = m_nCurrentStructElement;
+ while( nEle > 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() )
+ {
+ if (m_aStructure[nEle].m_oType
+ && *m_aStructure[nEle].m_oType == PDFWriter::NonStructElement)
+ {
+ bEmit = false;
+ break;
+ }
+ nEle = m_aStructure[ nEle ].m_nParentElement;
+ }
+ }
+ return bEmit;
+}
+
+sal_Int32 PDFWriterImpl::ensureStructureElement()
+{
+ if( ! m_aContext.Tagged )
+ return -1;
+
+ sal_Int32 nNewId = sal_Int32(m_aStructure.size());
+ m_aStructure.emplace_back();
+ PDFStructureElement& rEle = m_aStructure.back();
+ // leave rEle.m_oType uninitialised
+ rEle.m_nOwnElement = nNewId;
+ // temporary parent
+ rEle.m_nParentElement = m_nCurrentStructElement;
+ rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
+ m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
+ return nNewId;
+}
+
+void PDFWriterImpl::initStructureElement(sal_Int32 const id,
+ PDFWriter::StructElement const eType, std::u16string_view const rAlias)
+{
+ if( ! m_aContext.Tagged )
+ return;
+
+ if( m_nCurrentStructElement == 0 &&
+ eType != PDFWriter::Document && eType != PDFWriter::NonStructElement )
+ {
+ // struct tree root hit, but not beginning document
+ // this might happen with setCurrentStructureElement
+ // silently insert structure into document again if one properly exists
+ if( ! m_aStructure[ 0 ].m_aChildren.empty() )
+ {
+ const std::vector< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren;
+ auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(),
+ [&](sal_Int32 nElement) {
+ return m_aStructure[nElement].m_oType
+ && *m_aStructure[nElement].m_oType == PDFWriter::Document; });
+ if( it != rRootChildren.end() )
+ {
+ m_nCurrentStructElement = *it;
+ SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" );
+ }
+ else {
+ OSL_FAIL( "document structure in disorder !" );
+ }
+ }
+ else {
+ OSL_FAIL( "PDF document structure MUST be contained in a Document element" );
+ }
+ }
+
+ PDFStructureElement& rEle = m_aStructure[id];
+ assert(!rEle.m_oType);
+ rEle.m_oType.emplace(eType);
+ // remove it from its possibly placeholder parent; append to real parent
+ auto const it(std::find(m_aStructure[rEle.m_nParentElement].m_aChildren.begin(),
+ m_aStructure[rEle.m_nParentElement].m_aChildren.end(), id));
+ assert(it != m_aStructure[rEle.m_nParentElement].m_aChildren.end());
+ m_aStructure[rEle.m_nParentElement].m_aChildren.erase(it);
+ rEle.m_nParentElement = m_nCurrentStructElement;
+ rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
+ m_aStructure[m_nCurrentStructElement].m_aChildren.push_back(id);
+
+ // handle alias names
+ if( !rAlias.empty() && eType != PDFWriter::NonStructElement )
+ {
+ OStringBuffer aNameBuf( rAlias.size() );
+ appendName( rAlias, aNameBuf );
+ OString aAliasName( aNameBuf.makeStringAndClear() );
+ rEle.m_aAlias = aAliasName;
+ addRoleMap(aAliasName, eType);
+ }
+
+ if (m_bEmitStructure && eType != PDFWriter::NonStructElement) // don't create nonexistent objects
+ {
+ rEle.m_nObject = createObject();
+ // update parent's kids list
+ m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(ObjReference{rEle.m_nObject});
+ // ISO 14289-1:2014, Clause: 7.9
+ if (*rEle.m_oType == PDFWriter::Note)
+ {
+ m_StructElemObjsWithID.insert(rEle.m_nObject);
+ }
+ }
+}
+
+void PDFWriterImpl::beginStructureElement(sal_Int32 const id)
+{
+ if( m_nCurrentPage < 0 )
+ return;
+
+ if( ! m_aContext.Tagged )
+ return;
+
+ assert(id != -1 && "cid#1538888 doesn't consider above m_aContext.Tagged");
+
+ // close eventual current MC sequence
+ endStructureElementMCSeq(EndMode::OnlyStruct);
+
+ PDFStructureElement& rEle = m_aStructure[id];
+ m_StructElementStack.push(m_nCurrentStructElement);
+ m_nCurrentStructElement = id;
+
+ if (g_bDebugDisableCompression)
+ {
+ OStringBuffer aLine( "beginStructureElement " );
+ aLine.append( m_nCurrentStructElement );
+ aLine.append( ": " );
+ aLine.append( rEle.m_oType
+ ? getStructureTag(*rEle.m_oType)
+ : "<placeholder>" );
+ if( !rEle.m_aAlias.isEmpty() )
+ {
+ aLine.append( " aliased as \"" );
+ aLine.append( rEle.m_aAlias );
+ aLine.append( '\"' );
+ }
+ emitComment( aLine.getStr() );
+ }
+
+ // check whether to emit structure henceforth
+ m_bEmitStructure = checkEmitStructure();
+}
+
+void PDFWriterImpl::endStructureElement()
+{
+ if( m_nCurrentPage < 0 )
+ return;
+
+ if( ! m_aContext.Tagged )
+ return;
+
+ if( m_nCurrentStructElement == 0 )
+ {
+ // hit the struct tree root, that means there is an endStructureElement
+ // without corresponding beginStructureElement
+ return;
+ }
+
+ // end the marked content sequence
+ endStructureElementMCSeq();
+
+ OStringBuffer aLine;
+ if (g_bDebugDisableCompression)
+ {
+ aLine.append( "endStructureElement " );
+ aLine.append( m_nCurrentStructElement );
+ aLine.append( ": " );
+ aLine.append( m_aStructure[m_nCurrentStructElement].m_oType
+ ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
+ : "<placeholder>" );
+ if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
+ {
+ aLine.append( " aliased as \"" );
+ aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
+ aLine.append( '\"' );
+ }
+ }
+
+ // "end" the structure element, the parent becomes current element
+ m_nCurrentStructElement = m_StructElementStack.top();
+ m_StructElementStack.pop();
+
+ // check whether to emit structure henceforth
+ m_bEmitStructure = checkEmitStructure();
+
+ if (g_bDebugDisableCompression && m_bEmitStructure)
+ {
+ emitComment( aLine.getStr() );
+ }
+}
+
+namespace {
+
+void removePlaceholderSEImpl(std::vector<PDFStructureElement> & rStructure,
+ std::vector<sal_Int32>::iterator & rParentIt)
+{
+ PDFStructureElement& rEle(rStructure[*rParentIt]);
+ removePlaceholderSE(rStructure, rEle);
+
+ if (!rEle.m_oType)
+ {
+ // Placeholder was not initialised - should not happen when printing
+ // a full page, but might if a selection is printed, which can be only
+ // a shape without its anchor.
+ // Handle this by moving the children to the parent SE.
+ PDFStructureElement & rParent(rStructure[rEle.m_nParentElement]);
+ rParentIt = rParent.m_aChildren.erase(rParentIt);
+ std::vector<sal_Int32> children;
+ for (auto const child : rEle.m_aChildren)
+ {
+ PDFStructureElement& rChild = rStructure[child];
+ rChild.m_nParentElement = rEle.m_nParentElement;
+ children.push_back(rChild.m_nOwnElement);
+ }
+ rParentIt = rParent.m_aChildren.insert(rParentIt, children.begin(), children.end())
+ + children.size();
+ }
+ else
+ {
+ ++rParentIt;
+ }
+
+}
+
+void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle)
+{
+ for (auto it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); )
+ {
+ removePlaceholderSEImpl(rStructure, it);
+ }
+}
+
+} // end anonymous namespace
+
+/*
+ * This function adds an internal structure list container to overcome the 8191 elements array limitation
+ * in kids element emission.
+ * Recursive function
+ *
+ */
+void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle )
+{
+ if (rEle.m_nOwnElement != rEle.m_nParentElement
+ && *rEle.m_oType == PDFWriter::NonStructElement)
+ {
+ return;
+ }
+
+ for (auto const& child : rEle.m_aChildren)
+ {
+ assert(child > 0 && o3tl::make_unsigned(child) < m_aStructure.size());
+ if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() )
+ {
+ PDFStructureElement& rChild = m_aStructure[ child ];
+ if (*rChild.m_oType != PDFWriter::NonStructElement)
+ {
+ //triggered when a child of the rEle element is found
+ assert(rChild.m_nParentElement == rEle.m_nOwnElement);
+ if( rChild.m_nParentElement == rEle.m_nOwnElement )
+ addInternalStructureContainer( rChild );//examine the child
+ else
+ {
+ OSL_FAIL( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" );
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure element with id " << child );
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " << child );
+ }
+ }
+
+ if( rEle.m_nOwnElement == rEle.m_nParentElement )
+ return;
+
+ if( rEle.m_aKids.empty() )
+ return;
+
+ if( rEle.m_aKids.size() <= ncMaxPDFArraySize ) return;
+
+ //then we need to add the containers for the kids elements
+ // a list to be used for the new kid element
+ std::list< PDFStructureElementKid > aNewKids;
+ std::vector< sal_Int32 > aNewChildren;
+
+ // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?)
+ OString aAliasName("Div"_ostr);
+ addRoleMap(aAliasName, PDFWriter::Division);
+
+ while( rEle.m_aKids.size() > ncMaxPDFArraySize )
+ {
+ sal_Int32 nCurrentStructElement = rEle.m_nOwnElement;
+ sal_Int32 nNewId = sal_Int32(m_aStructure.size());
+ m_aStructure.emplace_back( );
+ PDFStructureElement& rEleNew = m_aStructure.back();
+ rEleNew.m_aAlias = aAliasName;
+ rEleNew.m_oType.emplace(PDFWriter::Division); // a new Div type container
+ rEleNew.m_nOwnElement = nNewId;
+ rEleNew.m_nParentElement = nCurrentStructElement;
+ //inherit the same page as the first child to be reparented
+ rEleNew.m_nFirstPageObject = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject;
+ rEleNew.m_nObject = createObject();//assign a PDF object number
+ //add the object to the kid list of the parent
+ aNewKids.emplace_back(ObjReference{rEleNew.m_nObject});
+ aNewChildren.push_back( nNewId );
+
+ std::vector< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() );
+ std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() );
+ advance( aChildEndIt, ncMaxPDFArraySize );
+ advance( aKidEndIt, ncMaxPDFArraySize );
+
+ rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(),
+ rEle.m_aKids,
+ rEle.m_aKids.begin(),
+ aKidEndIt );
+ rEleNew.m_aChildren.insert( rEleNew.m_aChildren.begin(),
+ rEle.m_aChildren.begin(),
+ aChildEndIt );
+ rEle.m_aChildren.erase( rEle.m_aChildren.begin(), aChildEndIt );
+
+ // set the kid's new parent
+ for (auto const& child : rEleNew.m_aChildren)
+ {
+ m_aStructure[ child ].m_nParentElement = nNewId;
+ }
+ }
+ //finally add the new kids resulting from the container added
+ rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() );
+ rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() );
+}
+
+bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle )
+{
+ bool bSuccess = false;
+
+ if( m_aContext.Tagged && nEle >= 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() )
+ {
+ // end eventual previous marked content sequence
+ endStructureElementMCSeq();
+
+ m_nCurrentStructElement = nEle;
+ m_bEmitStructure = checkEmitStructure();
+ if (g_bDebugDisableCompression)
+ {
+ OStringBuffer aLine( "setCurrentStructureElement " );
+ aLine.append( m_nCurrentStructElement );
+ aLine.append( ": " );
+ aLine.append( m_aStructure[m_nCurrentStructElement].m_oType
+ ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
+ : "<placeholder>" );
+ if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
+ {
+ aLine.append( " aliased as \"" );
+ aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
+ aLine.append( '\"' );
+ }
+ if( ! m_bEmitStructure )
+ aLine.append( " (inside NonStruct)" );
+ emitComment( aLine.getStr() );
+ }
+ bSuccess = true;
+ }
+
+ return bSuccess;
+}
+
+bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal )
+{
+ if( !m_aContext.Tagged )
+ return false;
+
+ assert(m_aStructure[m_nCurrentStructElement].m_oType);
+ bool bInsert = false;
+ if (m_nCurrentStructElement > 0
+ && (m_bEmitStructure
+ // allow it for topmost non-structured element
+ || (m_aContext.Tagged
+ && (0 == m_aStructure[m_nCurrentStructElement].m_nParentElement
+ || !m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType
+ || *m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType != PDFWriter::NonStructElement))))
+ {
+ PDFWriter::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
+ switch( eAttr )
+ {
+ case PDFWriter::Placement:
+ if( eVal == PDFWriter::Block ||
+ eVal == PDFWriter::Inline ||
+ eVal == PDFWriter::Before ||
+ eVal == PDFWriter::Start ||
+ eVal == PDFWriter::End )
+ bInsert = true;
+ break;
+ case PDFWriter::WritingMode:
+ if( eVal == PDFWriter::LrTb ||
+ eVal == PDFWriter::RlTb ||
+ eVal == PDFWriter::TbRl )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::TextAlign:
+ if( eVal == PDFWriter::Start ||
+ eVal == PDFWriter::Center ||
+ eVal == PDFWriter::End ||
+ eVal == PDFWriter::Justify )
+ {
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::Width:
+ case PDFWriter::Height:
+ if( eVal == PDFWriter::Auto )
+ {
+ if( eType == PDFWriter::Figure ||
+ eType == PDFWriter::Formula ||
+ eType == PDFWriter::Form ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::BlockAlign:
+ if( eVal == PDFWriter::Before ||
+ eVal == PDFWriter::Middle ||
+ eVal == PDFWriter::After ||
+ eVal == PDFWriter::Justify )
+ {
+ if( eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::InlineAlign:
+ if( eVal == PDFWriter::Start ||
+ eVal == PDFWriter::Center ||
+ eVal == PDFWriter::End )
+ {
+ if( eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::LineHeight:
+ if( eVal == PDFWriter::Normal ||
+ eVal == PDFWriter::Auto )
+ {
+ // only for ILSE and BLSE
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData ||
+ eType == PDFWriter::Span ||
+ eType == PDFWriter::Quote ||
+ eType == PDFWriter::Note ||
+ eType == PDFWriter::Reference ||
+ eType == PDFWriter::BibEntry ||
+ eType == PDFWriter::Code ||
+ eType == PDFWriter::Link )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::TextDecorationType:
+ if( eVal == PDFWriter::NONE ||
+ eVal == PDFWriter::Underline ||
+ eVal == PDFWriter::Overline ||
+ eVal == PDFWriter::LineThrough )
+ {
+ // only for ILSE and BLSE
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData ||
+ eType == PDFWriter::Span ||
+ eType == PDFWriter::Quote ||
+ eType == PDFWriter::Note ||
+ eType == PDFWriter::Reference ||
+ eType == PDFWriter::BibEntry ||
+ eType == PDFWriter::Code ||
+ eType == PDFWriter::Link )
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::Scope:
+ if (eVal == PDFWriter::Row || eVal == PDFWriter::Column || eVal == PDFWriter::Both)
+ {
+ if (eType == PDFWriter::TableHeader
+ && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::Type:
+ if (eVal == PDFWriter::Pagination || eVal == PDFWriter::Layout || eVal == PDFWriter::Page)
+ // + Background for PDF >= 1.7
+ {
+ if (eType == PDFWriter::NonStructElement)
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::Subtype:
+ if (eVal == PDFWriter::Header || eVal == PDFWriter::Footer || eVal == PDFWriter::Watermark)
+ {
+ if (eType == PDFWriter::NonStructElement
+ && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::Role:
+ if (eVal == PDFWriter::Rb || eVal == PDFWriter::Cb || eVal == PDFWriter::Pb || eVal == PDFWriter::Tv)
+ {
+ if (eType == PDFWriter::Form
+ && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::RubyAlign:
+ if (eVal == PDFWriter::RStart || eVal == PDFWriter::RCenter || eVal == PDFWriter::REnd || eVal == PDFWriter::RJustify || eVal == PDFWriter::RDistribute)
+ {
+ if (eType == PDFWriter::RT
+ && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::RubyPosition:
+ if (eVal == PDFWriter::RBefore || eVal == PDFWriter::RAfter || eVal == PDFWriter::RWarichu || eVal == PDFWriter::RInline)
+ {
+ if (eType == PDFWriter::RT
+ && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
+ {
+ bInsert = true;
+ }
+ }
+ break;
+ case PDFWriter::ListNumbering:
+ if( eVal == PDFWriter::NONE ||
+ eVal == PDFWriter::Disc ||
+ eVal == PDFWriter::Circle ||
+ eVal == PDFWriter::Square ||
+ eVal == PDFWriter::Decimal ||
+ eVal == PDFWriter::UpperRoman ||
+ eVal == PDFWriter::LowerRoman ||
+ eVal == PDFWriter::UpperAlpha ||
+ eVal == PDFWriter::LowerAlpha )
+ {
+ if( eType == PDFWriter::List )
+ bInsert = true;
+ }
+ break;
+ default: break;
+ }
+ }
+
+ if( bInsert )
+ m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal );
+ else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
+ SAL_INFO("vcl.pdfwriter",
+ "rejecting setStructureAttribute( " << getAttributeTag( eAttr )
+ << ", " << getAttributeValueTag( eVal )
+ << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
+ << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
+ << ") element");
+
+ return bInsert;
+}
+
+bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
+{
+ if( ! m_aContext.Tagged )
+ return false;
+
+ assert(m_aStructure[m_nCurrentStructElement].m_oType);
+ bool bInsert = false;
+ if( m_nCurrentStructElement > 0 && m_bEmitStructure )
+ {
+ if( eAttr == PDFWriter::Language )
+ {
+ m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale();
+ return true;
+ }
+
+ PDFWriter::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
+ switch( eAttr )
+ {
+ case PDFWriter::SpaceBefore:
+ case PDFWriter::SpaceAfter:
+ case PDFWriter::StartIndent:
+ case PDFWriter::EndIndent:
+ // just for BLSE
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::TextIndent:
+ // paragraph like BLSE and additional elements
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::Width:
+ case PDFWriter::Height:
+ if( eType == PDFWriter::Figure ||
+ eType == PDFWriter::Formula ||
+ eType == PDFWriter::Form ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::LineHeight:
+ case PDFWriter::BaselineShift:
+ // only for ILSE and BLSE
+ if( eType == PDFWriter::Paragraph ||
+ eType == PDFWriter::Heading ||
+ eType == PDFWriter::H1 ||
+ eType == PDFWriter::H2 ||
+ eType == PDFWriter::H3 ||
+ eType == PDFWriter::H4 ||
+ eType == PDFWriter::H5 ||
+ eType == PDFWriter::H6 ||
+ eType == PDFWriter::List ||
+ eType == PDFWriter::ListItem ||
+ eType == PDFWriter::LILabel ||
+ eType == PDFWriter::LIBody ||
+ eType == PDFWriter::Table ||
+ eType == PDFWriter::TableRow ||
+ eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData ||
+ eType == PDFWriter::Span ||
+ eType == PDFWriter::Quote ||
+ eType == PDFWriter::Note ||
+ eType == PDFWriter::Reference ||
+ eType == PDFWriter::BibEntry ||
+ eType == PDFWriter::Code ||
+ eType == PDFWriter::Link )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::RowSpan:
+ case PDFWriter::ColSpan:
+ // only for table cells
+ if( eType == PDFWriter::TableHeader ||
+ eType == PDFWriter::TableData )
+ {
+ bInsert = true;
+ }
+ break;
+ case PDFWriter::LinkAnnotation:
+ if( eType == PDFWriter::Link )
+ bInsert = true;
+ break;
+ default: break;
+ }
+ }
+
+ if( bInsert )
+ m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue );
+ else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
+ SAL_INFO("vcl.pdfwriter",
+ "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )
+ << ", " << static_cast<int>(nValue)
+ << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
+ << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
+ << ") element");
+
+ return bInsert;
+}
+
+void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect )
+{
+ sal_Int32 nPageNr = m_nCurrentPage;
+ if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() || !m_aContext.Tagged )
+ return;
+
+ if( !(m_nCurrentStructElement > 0 && m_bEmitStructure) )
+ return;
+
+ assert(m_aStructure[m_nCurrentStructElement].m_oType);
+ PDFWriter::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
+ if( eType == PDFWriter::Figure ||
+ eType == PDFWriter::Formula ||
+ eType == PDFWriter::Form ||
+ eType == PDFWriter::Division ||
+ eType == PDFWriter::Table )
+ {
+ m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect;
+ // convert to default user space now, since the mapmode may change
+ m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox );
+ }
+}
+
+void PDFWriterImpl::setStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds)
+{
+ assert(!(m_nCurrentPage < 0 || m_aPages.size() <= o3tl::make_unsigned(m_nCurrentPage)));
+
+ if (!m_aContext.Tagged || m_nCurrentStructElement <= 0 || !m_bEmitStructure)
+ {
+ return;
+ }
+
+ m_aStructure[m_nCurrentStructElement].m_AnnotIds = rAnnotIds;
+}
+
+void PDFWriterImpl::setActualText( const OUString& rText )
+{
+ if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
+ {
+ m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText;
+ }
+}
+
+void PDFWriterImpl::setAlternateText( const OUString& rText )
+{
+ if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
+ {
+ m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText;
+ }
+}
+
+void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
+{
+ if( nPageNr < 0 )
+ nPageNr = m_nCurrentPage;
+
+ if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
+ return;
+
+ m_aPages[ nPageNr ].m_eTransition = eType;
+ m_aPages[ nPageNr ].m_nTransTime = nMilliSec;
+}
+
+void PDFWriterImpl::ensureUniqueRadioOnValues()
+{
+ // loop over radio groups
+ for (auto const& group : m_aRadioGroupWidgets)
+ {
+ PDFWidget& rGroupWidget = m_aWidgets[ group.second ];
+ // check whether all kids have a unique OnValue
+ std::unordered_map< OUString, sal_Int32 > aOnValues;
+ bool bIsUnique = true;
+ for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
+ {
+ const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue;
+ SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal);
+ if( aOnValues.find( rVal ) == aOnValues.end() )
+ {
+ aOnValues[ rVal ] = 1;
+ }
+ else
+ {
+ bIsUnique = false;
+ break;
+ }
+ }
+ if( ! bIsUnique )
+ {
+ SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" );
+ // make unique by using ascending OnValues
+ int nKid = 0;
+ for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
+ {
+ PDFWidget& rKid = m_aWidgets[nKidIndex];
+ rKid.m_aOnValue = OUString::number( nKid+1 );
+ if( rKid.m_aValue != "Off" )
+ rKid.m_aValue = rKid.m_aOnValue;
+ ++nKid;
+ }
+ }
+ // finally move the "Yes" appearance to the OnValue appearance
+ for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
+ {
+ PDFWidget& rKid = m_aWidgets[nKidIndex];
+ if ( !rKid.m_aOnValue.isEmpty() )
+ {
+ auto app_it = rKid.m_aAppearances.find( "N"_ostr );
+ if( app_it != rKid.m_aAppearances.end() )
+ {
+ auto stream_it = app_it->second.find( "Yes"_ostr );
+ if( stream_it != app_it->second.end() )
+ {
+ SvMemoryStream* pStream = stream_it->second;
+ app_it->second.erase( stream_it );
+ OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 );
+ appendName( rKid.m_aOnValue, aBuf );
+ (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
+ }
+ else
+ SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" );
+ }
+ }
+
+ if ( !rKid.m_aOffValue.isEmpty() )
+ {
+ auto app_it = rKid.m_aAppearances.find( "N"_ostr );
+ if( app_it != rKid.m_aAppearances.end() )
+ {
+ auto stream_it = app_it->second.find( "Off"_ostr );
+ if( stream_it != app_it->second.end() )
+ {
+ SvMemoryStream* pStream = stream_it->second;
+ app_it->second.erase( stream_it );
+ OStringBuffer aBuf( rKid.m_aOffValue.getLength()*2 );
+ appendName( rKid.m_aOffValue, aBuf );
+ (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
+ }
+ else
+ SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Off\" stream" );
+ }
+ }
+
+ // update selected radio button
+ if( rKid.m_aValue != "Off" )
+ {
+ rGroupWidget.m_aValue = rKid.m_aValue;
+ }
+ }
+ }
+}
+
+sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn )
+{
+ sal_Int32 nRadioGroupWidget = -1;
+
+ std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup );
+
+ if( it == m_aRadioGroupWidgets.end() )
+ {
+ m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget =
+ sal_Int32(m_aWidgets.size());
+
+ // new group, insert the radiobutton
+ m_aWidgets.emplace_back( );
+ m_aWidgets.back().m_nObject = createObject();
+ m_aWidgets.back().m_nPage = m_nCurrentPage;
+ m_aWidgets.back().m_eType = PDFWriter::RadioButton;
+ m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup;
+ m_aWidgets.back().m_nFlags |= 0x0000C000; // NoToggleToOff and Radio bits
+
+ createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn );
+ }
+ else
+ nRadioGroupWidget = it->second;
+
+ return nRadioGroupWidget;
+}
+
+sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr )
+{
+ if( nPageNr < 0 )
+ nPageNr = m_nCurrentPage;
+
+ if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
+ return -1;
+
+ bool sigHidden(true);
+ sal_Int32 nNewWidget = m_aWidgets.size();
+ m_aWidgets.emplace_back( );
+
+ m_aWidgets.back().m_nObject = createObject();
+ m_aWidgets.back().m_aRect = rControl.Location;
+ m_aWidgets.back().m_nPage = nPageNr;
+ m_aWidgets.back().m_eType = rControl.getType();
+
+ sal_Int32 nRadioGroupWidget = -1;
+ // for unknown reasons the radio buttons of a radio group must not have a
+ // field name, else the buttons are in fact check boxes -
+ // that is multiple buttons of the radio group can be selected
+ if( rControl.getType() == PDFWriter::RadioButton )
+ nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) );
+ else
+ {
+ createWidgetFieldName( nNewWidget, rControl );
+ }
+
+ // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid
+ PDFWidget& rNewWidget = m_aWidgets[nNewWidget];
+ rNewWidget.m_aDescription = rControl.Description;
+ rNewWidget.m_aText = rControl.Text;
+ rNewWidget.m_nTextStyle = rControl.TextStyle &
+ ( DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top |
+ DrawTextFlags::VCenter | DrawTextFlags::Bottom |
+ DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
+ rNewWidget.m_nTabOrder = rControl.TabOrder;
+
+ // various properties are set via the flags (/Ff) property of the field dict
+ if( rControl.ReadOnly )
+ rNewWidget.m_nFlags |= 1;
+ if( rControl.getType() == PDFWriter::PushButton )
+ {
+ const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl);
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle =
+ DrawTextFlags::Center | DrawTextFlags::VCenter |
+ DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
+
+ rNewWidget.m_nFlags |= 0x00010000;
+ if( !rBtn.URL.isEmpty() )
+ rNewWidget.m_aListEntries.push_back( rBtn.URL );
+ rNewWidget.m_bSubmit = rBtn.Submit;
+ rNewWidget.m_bSubmitGet = rBtn.SubmitGet;
+ rNewWidget.m_nDest = rBtn.Dest;
+ createDefaultPushButtonAppearance( rNewWidget, rBtn );
+ }
+ else if( rControl.getType() == PDFWriter::RadioButton )
+ {
+ const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl);
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle =
+ DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
+ /* PDF sees a RadioButton group as one radio button with
+ * children which are in turn check boxes
+ *
+ * so we need to create a radio button on demand for a new group
+ * and insert a checkbox for each RadioButtonWidget as its child
+ */
+ rNewWidget.m_eType = PDFWriter::CheckBox;
+ rNewWidget.m_nRadioGroup = rBtn.RadioGroup;
+
+ SAL_WARN_IF( nRadioGroupWidget < 0 || o3tl::make_unsigned(nRadioGroupWidget) >= m_aWidgets.size(), "vcl.pdfwriter", "no radio group parent" );
+
+ PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget];
+ rRadioButton.m_aKids.push_back( rNewWidget.m_nObject );
+ rRadioButton.m_aKidsIndex.push_back( nNewWidget );
+ rNewWidget.m_nParent = rRadioButton.m_nObject;
+
+ rNewWidget.m_aValue = "Off";
+ rNewWidget.m_aOnValue = rBtn.OnValue;
+ rNewWidget.m_aOffValue = rBtn.OffValue;
+ if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected )
+ {
+ rNewWidget.m_aValue = rNewWidget.m_aOnValue;
+ rRadioButton.m_aValue = rNewWidget.m_aOnValue;
+ }
+ createDefaultRadioButtonAppearance( rNewWidget, rBtn );
+
+ // union rect of radio group
+ tools::Rectangle aRect = rNewWidget.m_aRect;
+ m_aPages[ nPageNr ].convertRect( aRect );
+ rRadioButton.m_aRect.Union( aRect );
+ }
+ else if( rControl.getType() == PDFWriter::CheckBox )
+ {
+ const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl);
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle =
+ DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
+
+ rNewWidget.m_aValue
+ = rBox.Checked ? std::u16string_view(u"Yes") : std::u16string_view(u"Off" );
+ rNewWidget.m_aOnValue = rBox.OnValue;
+ rNewWidget.m_aOffValue = rBox.OffValue;
+ // create default appearance before m_aRect gets transformed
+ createDefaultCheckBoxAppearance( rNewWidget, rBox );
+ }
+ else if( rControl.getType() == PDFWriter::ListBox )
+ {
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
+
+ const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl);
+ rNewWidget.m_aListEntries = rLstBox.Entries;
+ rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries;
+ rNewWidget.m_aValue = rLstBox.Text;
+ if( rLstBox.DropDown )
+ rNewWidget.m_nFlags |= 0x00020000;
+ if (rLstBox.MultiSelect && !rLstBox.DropDown)
+ rNewWidget.m_nFlags |= 0x00200000;
+
+ createDefaultListBoxAppearance( rNewWidget, rLstBox );
+ }
+ else if( rControl.getType() == PDFWriter::ComboBox )
+ {
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
+
+ const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl);
+ rNewWidget.m_aValue = rBox.Text;
+ rNewWidget.m_aListEntries = rBox.Entries;
+ rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag
+
+ PDFWriter::ListBoxWidget aLBox;
+ aLBox.Name = rBox.Name;
+ aLBox.Description = rBox.Description;
+ aLBox.Text = rBox.Text;
+ aLBox.TextStyle = rBox.TextStyle;
+ aLBox.ReadOnly = rBox.ReadOnly;
+ aLBox.Border = rBox.Border;
+ aLBox.BorderColor = rBox.BorderColor;
+ aLBox.Background = rBox.Background;
+ aLBox.BackgroundColor = rBox.BackgroundColor;
+ aLBox.TextFont = rBox.TextFont;
+ aLBox.TextColor = rBox.TextColor;
+ aLBox.DropDown = true;
+ aLBox.MultiSelect = false;
+ aLBox.Entries = rBox.Entries;
+
+ createDefaultListBoxAppearance( rNewWidget, aLBox );
+ }
+ else if( rControl.getType() == PDFWriter::Edit )
+ {
+ if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
+ rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
+
+ const PDFWriter::EditWidget& rEdit = static_cast<const PDFWriter::EditWidget&>(rControl);
+ if( rEdit.MultiLine )
+ {
+ rNewWidget.m_nFlags |= 0x00001000;
+ rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
+ }
+ if( rEdit.Password )
+ rNewWidget.m_nFlags |= 0x00002000;
+ if (rEdit.FileSelect)
+ rNewWidget.m_nFlags |= 0x00100000;
+ rNewWidget.m_nMaxLen = rEdit.MaxLen;
+ rNewWidget.m_nFormat = rEdit.Format;
+ rNewWidget.m_aCurrencySymbol = rEdit.CurrencySymbol;
+ rNewWidget.m_nDecimalAccuracy = rEdit.DecimalAccuracy;
+ rNewWidget.m_bPrependCurrencySymbol = rEdit.PrependCurrencySymbol;
+ rNewWidget.m_aTimeFormat = rEdit.TimeFormat;
+ rNewWidget.m_aDateFormat = rEdit.DateFormat;
+ rNewWidget.m_aValue = rEdit.Text;
+
+ createDefaultEditAppearance( rNewWidget, rEdit );
+ }
+#if HAVE_FEATURE_NSS
+ else if( rControl.getType() == PDFWriter::Signature)
+ {
+ sigHidden = true;
+
+ rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0);
+
+ m_nSignatureObject = createObject();
+ rNewWidget.m_aValue = OUString::number( m_nSignatureObject );
+ rNewWidget.m_aValue += " 0 R";
+ // let's add a fake appearance
+ rNewWidget.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = new SvMemoryStream();
+ }
+#endif
+
+ // if control is a hidden signature, do not convert coordinates since we
+ // need /Rect [ 0 0 0 0 ]
+ if ( ! ( ( rControl.getType() == PDFWriter::Signature ) && sigHidden ) )
+ {
+ // convert to default user space now, since the mapmode may change
+ // note: create default appearances before m_aRect gets transformed
+ m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect );
+ }
+
+ // insert widget to page's annotation list
+ m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject );
+
+ return nNewWidget;
+}
+
+void PDFWriterImpl::MARK( const char* pString )
+{
+ beginStructureElementMCSeq();
+ if (g_bDebugDisableCompression)
+ emitComment( pString );
+}
+
+sal_Int32 ReferenceXObjectEmit::getObject() const
+{
+ if (m_nFormObject > 0)
+ return m_nFormObject;
+ else
+ return m_nBitmapObject;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfwriter_impl2.cxx b/vcl/source/gdi/pdfwriter_impl2.cxx
new file mode 100644
index 0000000000..58fc31aafd
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter_impl2.cxx
@@ -0,0 +1,2047 @@
+/* -*- 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 <pdf/pdfwriter_impl.hxx>
+
+#include <vcl/pdfextoutdevdata.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/graph.hxx>
+
+#include <unotools/streamwrap.hxx>
+
+#include <tools/helpers.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+
+#include <comphelper/fileformat.h>
+#include <comphelper/hash.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <com/sun/star/graphic/GraphicProvider.hpp>
+#include <com/sun/star/graphic/XGraphicProvider.hpp>
+#include <com/sun/star/beans/XMaterialHolder.hpp>
+
+#include <cppuhelper/implbase.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+
+#include <sal/log.hxx>
+#include <memory>
+
+using namespace vcl;
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::beans;
+
+static bool lcl_canUsePDFAxialShading(const Gradient& rGradient);
+
+void PDFWriterImpl::implWriteGradient( const tools::PolyPolygon& i_rPolyPoly, const Gradient& i_rGradient,
+ VirtualDevice* i_pDummyVDev, const vcl::PDFWriter::PlayMetafileContext& i_rContext )
+{
+ GDIMetaFile aTmpMtf;
+ Gradient aGradient(i_rGradient);
+
+ aGradient.AddGradientActions( i_rPolyPoly.GetBoundRect(), aTmpMtf );
+
+ m_rOuterFace.Push();
+ m_rOuterFace.IntersectClipRegion( i_rPolyPoly.getB2DPolyPolygon() );
+ playMetafile( aTmpMtf, nullptr, i_rContext, i_pDummyVDev );
+ m_rOuterFace.Pop();
+}
+
+void PDFWriterImpl::implWriteBitmapEx( const Point& i_rPoint, const Size& i_rSize, const BitmapEx& i_rBitmapEx, const Graphic& i_Graphic,
+ VirtualDevice const * i_pDummyVDev, const vcl::PDFWriter::PlayMetafileContext& i_rContext )
+{
+ if ( i_rBitmapEx.IsEmpty() || !i_rSize.Width() || !i_rSize.Height() )
+ return;
+
+ BitmapEx aBitmapEx( i_rBitmapEx );
+ Point aPoint( i_rPoint );
+ Size aSize( i_rSize );
+
+ // #i19065# Negative sizes have mirror semantics on
+ // OutputDevice. BitmapEx and co. have no idea about that, so
+ // perform that _before_ doing anything with aBitmapEx.
+ BmpMirrorFlags nMirrorFlags(BmpMirrorFlags::NONE);
+ if( aSize.Width() < 0 )
+ {
+ aSize.setWidth( aSize.Width() * -1 );
+ aPoint.AdjustX( -(aSize.Width()) );
+ nMirrorFlags |= BmpMirrorFlags::Horizontal;
+ }
+ if( aSize.Height() < 0 )
+ {
+ aSize.setHeight( aSize.Height() * -1 );
+ aPoint.AdjustY( -(aSize.Height()) );
+ nMirrorFlags |= BmpMirrorFlags::Vertical;
+ }
+
+ if( nMirrorFlags != BmpMirrorFlags::NONE )
+ {
+ aBitmapEx.Mirror( nMirrorFlags );
+ }
+
+ bool bIsJpeg = false, bIsPng = false;
+ if( i_Graphic.GetType() != GraphicType::NONE && i_Graphic.GetBitmapEx() == aBitmapEx )
+ {
+ GfxLinkType eType = i_Graphic.GetGfxLink().GetType();
+ bIsJpeg = (eType == GfxLinkType::NativeJpg);
+ bIsPng = (eType == GfxLinkType::NativePng);
+ }
+
+ // Do not downsample images smaller than 50x50px.
+ const Size aBmpSize(aBitmapEx.GetSizePixel());
+ if (i_rContext.m_nMaxImageResolution > 50 && aBmpSize.getWidth() > 50
+ && aBmpSize.getHeight() > 50)
+ {
+ // do downsampling if necessary
+ const Size aDstSizeTwip( i_pDummyVDev->PixelToLogic(i_pDummyVDev->LogicToPixel(aSize), MapMode(MapUnit::MapTwip)) );
+ const double fBmpPixelX = aBmpSize.Width();
+ const double fBmpPixelY = aBmpSize.Height();
+ const double fMaxPixelX
+ = o3tl::convert<double>(aDstSizeTwip.Width(), o3tl::Length::twip, o3tl::Length::in)
+ * i_rContext.m_nMaxImageResolution;
+ const double fMaxPixelY
+ = o3tl::convert<double>(aDstSizeTwip.Height(), o3tl::Length::twip, o3tl::Length::in)
+ * i_rContext.m_nMaxImageResolution;
+
+ // check, if the bitmap DPI exceeds the maximum DPI (allow 4 pixel rounding tolerance)
+ if( ( ( fBmpPixelX > ( fMaxPixelX + 4 ) ) ||
+ ( fBmpPixelY > ( fMaxPixelY + 4 ) ) ) &&
+ ( fBmpPixelY > 0.0 ) && ( fMaxPixelY > 0.0 ) )
+ {
+ // do scaling
+ Size aNewBmpSize;
+ const double fBmpWH = fBmpPixelX / fBmpPixelY;
+ const double fMaxWH = fMaxPixelX / fMaxPixelY;
+
+ if( fBmpWH < fMaxWH )
+ {
+ aNewBmpSize.setWidth( FRound( fMaxPixelY * fBmpWH ) );
+ aNewBmpSize.setHeight( FRound( fMaxPixelY ) );
+ }
+ else if( fBmpWH > 0.0 )
+ {
+ aNewBmpSize.setWidth( FRound( fMaxPixelX ) );
+ aNewBmpSize.setHeight( FRound( fMaxPixelX / fBmpWH) );
+ }
+
+ if( aNewBmpSize.Width() && aNewBmpSize.Height() )
+ {
+ // #i121233# Use best quality for PDF exports
+ aBitmapEx.Scale( aNewBmpSize, BmpScaleFlag::BestQuality );
+ }
+ else
+ {
+ aBitmapEx.SetEmpty();
+ }
+ }
+ }
+
+ const Size aSizePixel( aBitmapEx.GetSizePixel() );
+ if ( !(aSizePixel.Width() && aSizePixel.Height()) )
+ return;
+
+ if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
+ aBitmapEx.Convert(BmpConversion::N8BitGreys);
+ bool bUseJPGCompression = !i_rContext.m_bOnlyLosslessCompression;
+ if ( bIsPng || ( aSizePixel.Width() < 32 ) || ( aSizePixel.Height() < 32 ) )
+ bUseJPGCompression = false;
+
+ auto pStrm=std::make_shared<SvMemoryStream>();
+ AlphaMask aAlphaMask;
+
+ bool bTrueColorJPG = true;
+ if ( bUseJPGCompression )
+ {
+ // TODO this checks could be done much earlier, saving us
+ // from trying conversion & stores before...
+ if ( !aBitmapEx.IsAlpha() )
+ {
+ const auto& rCacheEntry=m_aPDFBmpCache.find(
+ aBitmapEx.GetChecksum());
+ if ( rCacheEntry != m_aPDFBmpCache.end() )
+ {
+ m_rOuterFace.DrawJPGBitmap( *rCacheEntry->second, true, aSizePixel,
+ tools::Rectangle( aPoint, aSize ), aAlphaMask, i_Graphic );
+ return;
+ }
+ }
+ sal_uInt32 nZippedFileSize = 0; // sj: we will calculate the filesize of a zipped bitmap
+ if ( !bIsJpeg ) // to determine if jpeg compression is useful
+ {
+ SvMemoryStream aTemp;
+ aTemp.SetCompressMode( aTemp.GetCompressMode() | SvStreamCompressFlags::ZBITMAP );
+ aTemp.SetVersion( SOFFICE_FILEFORMAT_40 ); // sj: up from version 40 our bitmap stream operator
+ WriteDIBBitmapEx(aBitmapEx, aTemp); // is capable of zlib stream compression
+ nZippedFileSize = aTemp.TellEnd();
+ }
+ if ( aBitmapEx.IsAlpha() )
+ aAlphaMask = aBitmapEx.GetAlphaMask();
+ Graphic aGraphic(BitmapEx(aBitmapEx.GetBitmap()));
+
+ Sequence< PropertyValue > aFilterData{
+ comphelper::makePropertyValue("Quality", sal_Int32(i_rContext.m_nJPEGQuality)),
+ comphelper::makePropertyValue("ColorMode", sal_Int32(0))
+ };
+
+ try
+ {
+ uno::Reference < io::XStream > xStream = new utl::OStreamWrapper( *pStrm );
+ uno::Reference< io::XSeekable > xSeekable( xStream, UNO_QUERY_THROW );
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ uno::Reference< graphic::XGraphicProvider > xGraphicProvider( graphic::GraphicProvider::create(xContext) );
+ uno::Reference< graphic::XGraphic > xGraphic( aGraphic.GetXGraphic() );
+ uno::Reference < io::XOutputStream > xOut( xStream->getOutputStream() );
+ uno::Sequence< beans::PropertyValue > aOutMediaProperties{
+ comphelper::makePropertyValue("OutputStream", xOut),
+ comphelper::makePropertyValue("MimeType", OUString("image/jpeg")),
+ comphelper::makePropertyValue("FilterData", aFilterData)
+ };
+ xGraphicProvider->storeGraphic( xGraphic, aOutMediaProperties );
+ xOut->flush();
+ if ( !bIsJpeg && xSeekable->getLength() > nZippedFileSize )
+ {
+ bUseJPGCompression = false;
+ }
+ else
+ {
+ pStrm->Seek( STREAM_SEEK_TO_END );
+
+ xSeekable->seek( 0 );
+ Sequence< PropertyValue > aArgs{ comphelper::makePropertyValue("InputStream",
+ xStream) };
+ uno::Reference< XPropertySet > xPropSet( xGraphicProvider->queryGraphicDescriptor( aArgs ) );
+ if ( xPropSet.is() )
+ {
+ sal_Int16 nBitsPerPixel = 24;
+ if ( xPropSet->getPropertyValue("BitsPerPixel") >>= nBitsPerPixel )
+ {
+ bTrueColorJPG = nBitsPerPixel != 8;
+ }
+ }
+ }
+ }
+ catch( uno::Exception& )
+ {
+ bUseJPGCompression = false;
+ }
+ }
+ if ( bUseJPGCompression )
+ {
+ m_rOuterFace.DrawJPGBitmap( *pStrm, bTrueColorJPG, aSizePixel, tools::Rectangle( aPoint, aSize ), aAlphaMask, i_Graphic );
+ if (!aBitmapEx.IsAlpha() && bTrueColorJPG)
+ {
+ // Cache last jpeg export
+ m_aPDFBmpCache.insert(
+ {aBitmapEx.GetChecksum(), pStrm});
+ }
+ }
+ else if ( aBitmapEx.IsAlpha() )
+ m_rOuterFace.DrawBitmapEx( aPoint, aSize, aBitmapEx );
+ else
+ m_rOuterFace.DrawBitmap( aPoint, aSize, aBitmapEx.GetBitmap(), i_Graphic );
+
+}
+
+void PDFWriterImpl::playMetafile( const GDIMetaFile& i_rMtf, vcl::PDFExtOutDevData* i_pOutDevData, const vcl::PDFWriter::PlayMetafileContext& i_rContext, VirtualDevice* pDummyVDev )
+{
+ bool bAssertionFired( false );
+
+ ScopedVclPtr<VirtualDevice> xPrivateDevice;
+ if( ! pDummyVDev )
+ {
+ xPrivateDevice.disposeAndReset(VclPtr<VirtualDevice>::Create());
+ pDummyVDev = xPrivateDevice.get();
+ pDummyVDev->EnableOutput( false );
+ pDummyVDev->SetMapMode( i_rMtf.GetPrefMapMode() );
+ }
+ const GDIMetaFile& aMtf( i_rMtf );
+
+ for( sal_uInt32 i = 0, nCount = aMtf.GetActionSize(); i < nCount; )
+ {
+ if ( !i_pOutDevData || !i_pOutDevData->PlaySyncPageAct( m_rOuterFace, i, aMtf ) )
+ {
+ const MetaAction* pAction = aMtf.GetAction( i );
+ const MetaActionType nType = pAction->GetType();
+
+ switch( nType )
+ {
+ case MetaActionType::PIXEL:
+ {
+ const MetaPixelAction* pA = static_cast<const MetaPixelAction*>(pAction);
+ m_rOuterFace.DrawPixel( pA->GetPoint(), pA->GetColor() );
+ }
+ break;
+
+ case MetaActionType::POINT:
+ {
+ const MetaPointAction* pA = static_cast<const MetaPointAction*>(pAction);
+ m_rOuterFace.DrawPixel( pA->GetPoint() );
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ const MetaLineAction* pA = static_cast<const MetaLineAction*>(pAction);
+ if ( pA->GetLineInfo().IsDefault() )
+ m_rOuterFace.DrawLine( pA->GetStartPoint(), pA->GetEndPoint() );
+ else
+ m_rOuterFace.DrawLine( pA->GetStartPoint(), pA->GetEndPoint(), pA->GetLineInfo() );
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ const MetaRectAction* pA = static_cast<const MetaRectAction*>(pAction);
+ m_rOuterFace.DrawRect( pA->GetRect() );
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pAction);
+ m_rOuterFace.DrawRect( pA->GetRect(), pA->GetHorzRound(), pA->GetVertRound() );
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pAction);
+ m_rOuterFace.DrawEllipse( pA->GetRect() );
+ }
+ break;
+
+ case MetaActionType::ARC:
+ {
+ const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction);
+ m_rOuterFace.DrawArc( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() );
+ }
+ break;
+
+ case MetaActionType::PIE:
+ {
+ const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction);
+ m_rOuterFace.DrawPie( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() );
+ }
+ break;
+
+ case MetaActionType::CHORD:
+ {
+ const MetaChordAction* pA = static_cast<const MetaChordAction*>(pAction);
+ m_rOuterFace.DrawChord( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() );
+ }
+ break;
+
+ case MetaActionType::POLYGON:
+ {
+ const MetaPolygonAction* pA = static_cast<const MetaPolygonAction*>(pAction);
+ m_rOuterFace.DrawPolygon( pA->GetPolygon() );
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pAction);
+ if ( pA->GetLineInfo().IsDefault() )
+ m_rOuterFace.DrawPolyLine( pA->GetPolygon() );
+ else
+ m_rOuterFace.DrawPolyLine( pA->GetPolygon(), pA->GetLineInfo() );
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ {
+ const MetaPolyPolygonAction* pA = static_cast<const MetaPolyPolygonAction*>(pAction);
+ m_rOuterFace.DrawPolyPolygon( pA->GetPolyPolygon() );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pAction);
+ const Gradient& rGradient = pA->GetGradient();
+ if (lcl_canUsePDFAxialShading(rGradient))
+ {
+ m_rOuterFace.DrawGradient( pA->GetRect(), rGradient );
+ }
+ else
+ {
+ const tools::PolyPolygon aPolyPoly( pA->GetRect() );
+ implWriteGradient( aPolyPoly, rGradient, pDummyVDev, i_rContext );
+ }
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ const MetaGradientExAction* pA = static_cast<const MetaGradientExAction*>(pAction);
+ const Gradient& rGradient = pA->GetGradient();
+
+ if (lcl_canUsePDFAxialShading(rGradient))
+ m_rOuterFace.DrawGradient( pA->GetPolyPolygon(), rGradient );
+ else
+ implWriteGradient( pA->GetPolyPolygon(), rGradient, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pAction);
+ m_rOuterFace.DrawHatch( pA->GetPolyPolygon(), pA->GetHatch() );
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ const MetaTransparentAction* pA = static_cast<const MetaTransparentAction*>(pAction);
+ m_rOuterFace.DrawTransparent( pA->GetPolyPolygon(), pA->GetTransparence() );
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pAction);
+
+ GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() );
+ const Point& rPos = pA->GetPoint();
+ const Size& rSize= pA->GetSize();
+ const Gradient& rTransparenceGradient = pA->GetGradient();
+
+ // special case constant alpha value
+ if( rTransparenceGradient.GetStartColor() == rTransparenceGradient.GetEndColor() )
+ {
+ const Color aTransCol( rTransparenceGradient.GetStartColor() );
+ const sal_uInt16 nTransPercent = aTransCol.GetLuminance() * 100 / 255;
+ m_rOuterFace.BeginTransparencyGroup();
+
+ // tdf#138826 adjust the aTmpMtf to start at rPos (see also #i112076#)
+ Point aMtfOrigin(aTmpMtf.GetPrefMapMode().GetOrigin());
+ if (rPos != aMtfOrigin)
+ aTmpMtf.Move(rPos.X() - aMtfOrigin.X(), rPos.Y() - aMtfOrigin.Y());
+
+ playMetafile( aTmpMtf, nullptr, i_rContext, pDummyVDev );
+ m_rOuterFace.EndTransparencyGroup( tools::Rectangle( rPos, rSize ), nTransPercent );
+ }
+ else
+ {
+ const Size aDstSizeTwip( pDummyVDev->PixelToLogic(pDummyVDev->LogicToPixel(rSize), MapMode(MapUnit::MapTwip)) );
+
+ // i#115962# Always use at least 300 DPI for bitmap conversion of transparence gradients,
+ // else the quality is not acceptable (see bugdoc as example)
+ sal_Int32 nMaxBmpDPI(300);
+
+ if( i_rContext.m_nMaxImageResolution > 50 )
+ {
+ if ( nMaxBmpDPI > i_rContext.m_nMaxImageResolution )
+ nMaxBmpDPI = i_rContext.m_nMaxImageResolution;
+ }
+ const sal_Int32 nPixelX = o3tl::convert<double>(aDstSizeTwip.Width(), o3tl::Length::twip, o3tl::Length::in) * nMaxBmpDPI;
+ const sal_Int32 nPixelY = o3tl::convert<double>(aDstSizeTwip.Height(), o3tl::Length::twip, o3tl::Length::in) * nMaxBmpDPI;
+ if ( nPixelX && nPixelY )
+ {
+ Size aDstSizePixel( nPixelX, nPixelY );
+ ScopedVclPtrInstance<VirtualDevice> xVDev(DeviceFormat::WITH_ALPHA);
+ if( xVDev->SetOutputSizePixel( aDstSizePixel, true, true ) )
+ {
+ Point aPoint;
+
+ MapMode aMapMode( pDummyVDev->GetMapMode() );
+ aMapMode.SetOrigin( aPoint );
+ xVDev->SetMapMode( aMapMode );
+ const bool bVDevOldMap = xVDev->IsMapModeEnabled();
+ Size aDstSize( xVDev->PixelToLogic( aDstSizePixel ) );
+
+ Point aMtfOrigin( aTmpMtf.GetPrefMapMode().GetOrigin() );
+ if ( aMtfOrigin.X() || aMtfOrigin.Y() )
+ aTmpMtf.Move( -aMtfOrigin.X(), -aMtfOrigin.Y() );
+ double fScaleX = static_cast<double>(aDstSize.Width()) / static_cast<double>(aTmpMtf.GetPrefSize().Width());
+ double fScaleY = static_cast<double>(aDstSize.Height()) / static_cast<double>(aTmpMtf.GetPrefSize().Height());
+ if( fScaleX != 1.0 || fScaleY != 1.0 )
+ aTmpMtf.Scale( fScaleX, fScaleY );
+ aTmpMtf.SetPrefMapMode( aMapMode );
+
+ // create paint bitmap
+ aTmpMtf.WindStart();
+ aTmpMtf.Play(*xVDev, aPoint, aDstSize);
+ aTmpMtf.WindStart();
+ xVDev->EnableMapMode( false );
+ BitmapEx aPaint = xVDev->GetBitmapEx(aPoint, xVDev->GetOutputSizePixel());
+ xVDev->EnableMapMode( bVDevOldMap ); // #i35331#: MUST NOT use EnableMapMode( sal_True ) here!
+
+ // create alpha mask from gradient
+ xVDev->SetDrawMode( DrawModeFlags::GrayGradient );
+ xVDev->DrawGradient( tools::Rectangle( aPoint, aDstSize ), rTransparenceGradient );
+ xVDev->SetDrawMode( DrawModeFlags::Default );
+ xVDev->EnableMapMode( false );
+
+ AlphaMask aAlpha(xVDev->GetBitmap(Point(), xVDev->GetOutputSizePixel()));
+ AlphaMask aPaintAlpha(aPaint.GetAlphaMask());
+ // The alpha mask is inverted from what is
+ // expected so invert it again. To test this
+ // code, export to PDF the transparent shapes,
+ // gradients, and images in the documents
+ // attached to the following bug reports:
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=155912
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=156630
+ aAlpha.Invert(); // convert to alpha
+ aAlpha.BlendWith(aPaintAlpha);
+#if HAVE_FEATURE_SKIA
+#if OSL_DEBUG_LEVEL > 0
+ // In release builds, we always invert
+ // regardless of whether Skia is enabled or not.
+ // But in debug builds, we can't invert when
+ // Skia is enabled.
+ if ( !SkiaHelper::isVCLSkiaEnabled() )
+#endif
+#endif
+ {
+ // When Skia is disabled, the alpha mask
+ // must be inverted a second time. To test
+ // this code, export the following
+ // document to PDF:
+ // https://bugs.documentfoundation.org/attachment.cgi?id=188084
+ aAlpha.Invert(); // convert to alpha
+ }
+
+ xVDev.disposeAndClear();
+
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( rPos, rSize, BitmapEx( aPaint.GetBitmap(), aAlpha ), aGraphic, pDummyVDev, i_rContext );
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction);
+ const GDIMetaFile& aSubstitute( pA->GetSubstitute() );
+
+ m_rOuterFace.Push();
+ pDummyVDev->Push();
+
+ MapMode aMapMode( aSubstitute.GetPrefMapMode() );
+ Size aOutSize( OutputDevice::LogicToLogic( pA->GetSize(), pDummyVDev->GetMapMode(), aMapMode ) );
+ aMapMode.SetScaleX( Fraction( aOutSize.Width(), aSubstitute.GetPrefSize().Width() ) );
+ aMapMode.SetScaleY( Fraction( aOutSize.Height(), aSubstitute.GetPrefSize().Height() ) );
+ aMapMode.SetOrigin( OutputDevice::LogicToLogic( pA->GetPoint(), pDummyVDev->GetMapMode(), aMapMode ) );
+
+ m_rOuterFace.SetMapMode( aMapMode );
+ pDummyVDev->SetMapMode( aMapMode );
+ playMetafile( aSubstitute, nullptr, i_rContext, pDummyVDev );
+ pDummyVDev->Pop();
+ m_rOuterFace.Pop();
+ }
+ break;
+
+ case MetaActionType::COMMENT:
+ if( ! i_rContext.m_bTransparenciesWereRemoved )
+ {
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
+
+ if( pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN"))
+ {
+ const MetaGradientExAction* pGradAction = nullptr;
+ bool bDone = false;
+
+ while( !bDone && ( ++i < nCount ) )
+ {
+ pAction = aMtf.GetAction( i );
+
+ if( pAction->GetType() == MetaActionType::GRADIENTEX )
+ pGradAction = static_cast<const MetaGradientExAction*>(pAction);
+ else if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END")) )
+ {
+ bDone = true;
+ }
+ }
+
+ if( pGradAction )
+ {
+ if (lcl_canUsePDFAxialShading(pGradAction->GetGradient()))
+ {
+ m_rOuterFace.DrawGradient( pGradAction->GetPolyPolygon(), pGradAction->GetGradient() );
+ }
+ else
+ {
+ implWriteGradient( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), pDummyVDev, i_rContext );
+ }
+ }
+ }
+ else
+ {
+ const sal_uInt8* pData = pA->GetData();
+ if ( pData )
+ {
+ SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pData), pA->GetDataSize(), StreamMode::READ );
+ bool bSkipSequence = false;
+ OString sSeqEnd;
+
+ if( pA->GetComment() == "XPATHSTROKE_SEQ_BEGIN" )
+ {
+ sSeqEnd = "XPATHSTROKE_SEQ_END"_ostr;
+ SvtGraphicStroke aStroke;
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+
+ tools::Polygon aPath;
+ aStroke.getPath( aPath );
+
+ tools::PolyPolygon aStartArrow;
+ tools::PolyPolygon aEndArrow;
+ double fTransparency( aStroke.getTransparency() );
+ double fStrokeWidth( aStroke.getStrokeWidth() );
+ SvtGraphicStroke::DashArray aDashArray;
+
+ aStroke.getStartArrow( aStartArrow );
+ aStroke.getEndArrow( aEndArrow );
+ aStroke.getDashArray( aDashArray );
+
+ bSkipSequence = true;
+ if ( aStartArrow.Count() || aEndArrow.Count() )
+ bSkipSequence = false;
+ if ( !aDashArray.empty() && ( fStrokeWidth != 0.0 ) && ( fTransparency == 0.0 ) )
+ bSkipSequence = false;
+ if ( bSkipSequence )
+ {
+ PDFWriter::ExtLineInfo aInfo;
+ aInfo.m_fLineWidth = fStrokeWidth;
+ aInfo.m_fTransparency = fTransparency;
+ aInfo.m_fMiterLimit = aStroke.getMiterLimit();
+ switch( aStroke.getCapType() )
+ {
+ default:
+ case SvtGraphicStroke::capButt: aInfo.m_eCap = PDFWriter::capButt;break;
+ case SvtGraphicStroke::capRound: aInfo.m_eCap = PDFWriter::capRound;break;
+ case SvtGraphicStroke::capSquare: aInfo.m_eCap = PDFWriter::capSquare;break;
+ }
+ switch( aStroke.getJoinType() )
+ {
+ default:
+ case SvtGraphicStroke::joinMiter: aInfo.m_eJoin = PDFWriter::joinMiter;break;
+ case SvtGraphicStroke::joinRound: aInfo.m_eJoin = PDFWriter::joinRound;break;
+ case SvtGraphicStroke::joinBevel: aInfo.m_eJoin = PDFWriter::joinBevel;break;
+ case SvtGraphicStroke::joinNone:
+ aInfo.m_eJoin = PDFWriter::joinMiter;
+ aInfo.m_fMiterLimit = 0.0;
+ break;
+ }
+ aInfo.m_aDashArray = aDashArray;
+
+ if(SvtGraphicStroke::joinNone == aStroke.getJoinType()
+ && fStrokeWidth > 0.0)
+ {
+ // emulate no edge rounding by handling single edges
+ const sal_uInt16 nPoints(aPath.GetSize());
+ const bool bCurve(aPath.HasFlags());
+
+ for(sal_uInt16 a(0); a + 1 < nPoints; a++)
+ {
+ if(bCurve
+ && PolyFlags::Normal != aPath.GetFlags(a + 1)
+ && a + 2 < nPoints
+ && PolyFlags::Normal != aPath.GetFlags(a + 2)
+ && a + 3 < nPoints)
+ {
+ const tools::Polygon aSnippet(4,
+ aPath.GetConstPointAry() + a,
+ aPath.GetConstFlagAry() + a);
+ m_rOuterFace.DrawPolyLine( aSnippet, aInfo );
+ a += 2;
+ }
+ else
+ {
+ const tools::Polygon aSnippet(2,
+ aPath.GetConstPointAry() + a);
+ m_rOuterFace.DrawPolyLine( aSnippet, aInfo );
+ }
+ }
+ }
+ else
+ {
+ m_rOuterFace.DrawPolyLine( aPath, aInfo );
+ }
+ }
+ }
+ else if ( pA->GetComment() == "XPATHFILL_SEQ_BEGIN" )
+ {
+ sSeqEnd = "XPATHFILL_SEQ_END"_ostr;
+ SvtGraphicFill aFill;
+ ReadSvtGraphicFill( aMemStm, aFill );
+
+ if ( ( aFill.getFillType() == SvtGraphicFill::fillSolid ) && ( aFill.getFillRule() == SvtGraphicFill::fillEvenOdd ) )
+ {
+ double fTransparency = aFill.getTransparency();
+ if ( fTransparency == 0.0 )
+ {
+ tools::PolyPolygon aPath;
+ aFill.getPath( aPath );
+
+ bSkipSequence = true;
+ m_rOuterFace.DrawPolyPolygon( aPath );
+ }
+ else if ( fTransparency == 1.0 )
+ bSkipSequence = true;
+ }
+ }
+ if ( bSkipSequence )
+ {
+ while( ++i < nCount )
+ {
+ pAction = aMtf.GetAction( i );
+ if ( pAction->GetType() == MetaActionType::COMMENT )
+ {
+ OString sComment( static_cast<const MetaCommentAction*>(pAction)->GetComment() );
+ if (sComment == sSeqEnd)
+ break;
+ }
+ // #i44496#
+ // the replacement action for stroke is a filled rectangle
+ // the set fillcolor of the replacement is part of the graphics
+ // state and must not be skipped
+ else if( pAction->GetType() == MetaActionType::FILLCOLOR )
+ {
+ const MetaFillColorAction* pMA = static_cast<const MetaFillColorAction*>(pAction);
+ if( pMA->IsSetting() )
+ m_rOuterFace.SetFillColor( pMA->GetColor() );
+ else
+ m_rOuterFace.SetFillColor();
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ const MetaBmpAction* pA = static_cast<const MetaBmpAction*>(pAction);
+ BitmapEx aBitmapEx( pA->GetBitmap() );
+ Size aSize( OutputDevice::LogicToLogic( aBitmapEx.GetPrefSize(),
+ aBitmapEx.GetPrefMapMode(), pDummyVDev->GetMapMode() ) );
+ if( ! ( aSize.Width() && aSize.Height() ) )
+ aSize = pDummyVDev->PixelToLogic( aBitmapEx.GetSizePixel() );
+
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetPoint(), aSize, aBitmapEx, aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetPoint(), pA->GetSize(), BitmapEx( pA->GetBitmap() ), aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pAction);
+ BitmapEx aBitmapEx( pA->GetBitmap() );
+ aBitmapEx.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) );
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetDestPoint(), pA->GetDestSize(), aBitmapEx, aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ const MetaBmpExAction* pA = static_cast<const MetaBmpExAction*>(pAction);
+
+ // The alpha mask is inverted from what is
+ // expected so invert it again. To test this
+ // code, export to PDF the transparent shapes,
+ // gradients, and images in the documents
+ // attached to the following bug reports:
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=155912
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=156630
+ BitmapEx aBitmapEx( pA->GetBitmapEx() );
+ if ( aBitmapEx.IsAlpha())
+ {
+ AlphaMask aAlpha = aBitmapEx.GetAlphaMask();
+ aAlpha.Invert();
+ aBitmapEx = BitmapEx(aBitmapEx.GetBitmap(), aAlpha);
+ }
+
+ Size aSize( OutputDevice::LogicToLogic( aBitmapEx.GetPrefSize(),
+ aBitmapEx.GetPrefMapMode(), pDummyVDev->GetMapMode() ) );
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetPoint(), aSize, aBitmapEx, aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+
+ // The alpha mask is inverted from what is
+ // expected so invert it again. To test this
+ // code, export to PDF the transparent shapes,
+ // gradients, and images in the documents
+ // attached to the following bug reports:
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=155912
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=156630
+ BitmapEx aBitmapEx( pA->GetBitmapEx() );
+ if ( aBitmapEx.IsAlpha())
+ {
+ AlphaMask aAlpha = aBitmapEx.GetAlphaMask();
+ aAlpha.Invert();
+ aBitmapEx = BitmapEx(aBitmapEx.GetBitmap(), aAlpha);
+ }
+
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetPoint(), pA->GetSize(), aBitmapEx, aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction);
+
+ // The alpha mask is inverted from what is
+ // expected so invert it again. To test this
+ // code, export to PDF the transparent shapes,
+ // gradients, and images in the documents
+ // attached to the following bug reports:
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=155912
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=156630
+ BitmapEx aBitmapEx( pA->GetBitmapEx() );
+ if ( aBitmapEx.IsAlpha())
+ {
+ AlphaMask aAlpha = aBitmapEx.GetAlphaMask();
+ aAlpha.Invert();
+ aBitmapEx = BitmapEx(aBitmapEx.GetBitmap(), aAlpha);
+ }
+
+ aBitmapEx.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) );
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetDestPoint(), pA->GetDestSize(), aBitmapEx, aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ {
+ SAL_WARN( "vcl", "MetaMask...Action not supported yet" );
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction);
+ m_rOuterFace.DrawText( pA->GetPoint(), pA->GetText().copy( pA->GetIndex(), std::min<sal_Int32>(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) ) );
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
+ m_rOuterFace.DrawText( pA->GetRect(), pA->GetText(), pA->GetStyle() );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction);
+ m_rOuterFace.DrawTextArray( pA->GetPoint(), pA->GetText(), pA->GetDXArray(), pA->GetKashidaArray(), pA->GetIndex(), pA->GetLen() );
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction);
+ m_rOuterFace.DrawStretchText( pA->GetPoint(), pA->GetWidth(), pA->GetText(), pA->GetIndex(), pA->GetLen() );
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ {
+ const MetaTextLineAction* pA = static_cast<const MetaTextLineAction*>(pAction);
+ m_rOuterFace.DrawTextLine( pA->GetStartPoint(), pA->GetWidth(), pA->GetStrikeout(), pA->GetUnderline(), pA->GetOverline() );
+
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ {
+ const MetaClipRegionAction* pA = static_cast<const MetaClipRegionAction*>(pAction);
+
+ if( pA->IsClipping() )
+ {
+ if( pA->GetRegion().IsEmpty() )
+ m_rOuterFace.SetClipRegion( basegfx::B2DPolyPolygon() );
+ else
+ {
+ const vcl::Region& aReg( pA->GetRegion() );
+ m_rOuterFace.SetClipRegion( aReg.GetAsB2DPolyPolygon() );
+ }
+ }
+ else
+ m_rOuterFace.SetClipRegion();
+ }
+ break;
+
+ case MetaActionType::ISECTRECTCLIPREGION:
+ {
+ const MetaISectRectClipRegionAction* pA = static_cast<const MetaISectRectClipRegionAction*>(pAction);
+ m_rOuterFace.IntersectClipRegion( pA->GetRect() );
+ }
+ break;
+
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ {
+ const MetaISectRegionClipRegionAction* pA = static_cast<const MetaISectRegionClipRegionAction*>(pAction);
+ const vcl::Region& aReg( pA->GetRegion() );
+ m_rOuterFace.IntersectClipRegion( aReg.GetAsB2DPolyPolygon() );
+ }
+ break;
+
+ case MetaActionType::MOVECLIPREGION:
+ {
+ const MetaMoveClipRegionAction* pA = static_cast<const MetaMoveClipRegionAction*>(pAction);
+ m_rOuterFace.MoveClipRegion( pA->GetHorzMove(), pA->GetVertMove() );
+ }
+ break;
+
+ case MetaActionType::MAPMODE:
+ {
+ const_cast< MetaAction* >( pAction )->Execute( pDummyVDev );
+ m_rOuterFace.SetMapMode( pDummyVDev->GetMapMode() );
+ }
+ break;
+
+ case MetaActionType::LINECOLOR:
+ {
+ const MetaLineColorAction* pA = static_cast<const MetaLineColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetLineColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetLineColor();
+ }
+ break;
+
+ case MetaActionType::FILLCOLOR:
+ {
+ const MetaFillColorAction* pA = static_cast<const MetaFillColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetFillColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetFillColor();
+ }
+ break;
+
+ case MetaActionType::TEXTLINECOLOR:
+ {
+ const MetaTextLineColorAction* pA = static_cast<const MetaTextLineColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetTextLineColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetTextLineColor();
+ }
+ break;
+
+ case MetaActionType::OVERLINECOLOR:
+ {
+ const MetaOverlineColorAction* pA = static_cast<const MetaOverlineColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetOverlineColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetOverlineColor();
+ }
+ break;
+
+ case MetaActionType::TEXTFILLCOLOR:
+ {
+ const MetaTextFillColorAction* pA = static_cast<const MetaTextFillColorAction*>(pAction);
+
+ if( pA->IsSetting() )
+ m_rOuterFace.SetTextFillColor( pA->GetColor() );
+ else
+ m_rOuterFace.SetTextFillColor();
+ }
+ break;
+
+ case MetaActionType::TEXTCOLOR:
+ {
+ const MetaTextColorAction* pA = static_cast<const MetaTextColorAction*>(pAction);
+ m_rOuterFace.SetTextColor( pA->GetColor() );
+ }
+ break;
+
+ case MetaActionType::TEXTALIGN:
+ {
+ const MetaTextAlignAction* pA = static_cast<const MetaTextAlignAction*>(pAction);
+ m_rOuterFace.SetTextAlign( pA->GetTextAlign() );
+ }
+ break;
+
+ case MetaActionType::FONT:
+ {
+ const MetaFontAction* pA = static_cast<const MetaFontAction*>(pAction);
+ m_rOuterFace.SetFont( pA->GetFont() );
+ }
+ break;
+
+ case MetaActionType::PUSH:
+ {
+ const MetaPushAction* pA = static_cast<const MetaPushAction*>(pAction);
+
+ pDummyVDev->Push( pA->GetFlags() );
+ m_rOuterFace.Push( pA->GetFlags() );
+ }
+ break;
+
+ case MetaActionType::POP:
+ {
+ pDummyVDev->Pop();
+ m_rOuterFace.Pop();
+ }
+ break;
+
+ case MetaActionType::LAYOUTMODE:
+ {
+ const MetaLayoutModeAction* pA = static_cast<const MetaLayoutModeAction*>(pAction);
+ m_rOuterFace.SetLayoutMode( pA->GetLayoutMode() );
+ }
+ break;
+
+ case MetaActionType::TEXTLANGUAGE:
+ {
+ const MetaTextLanguageAction* pA = static_cast<const MetaTextLanguageAction*>(pAction);
+ m_rOuterFace.SetDigitLanguage( pA->GetTextLanguage() );
+ }
+ break;
+
+ case MetaActionType::WALLPAPER:
+ {
+ const MetaWallpaperAction* pA = static_cast<const MetaWallpaperAction*>(pAction);
+ m_rOuterFace.DrawWallpaper( pA->GetRect(), pA->GetWallpaper() );
+ }
+ break;
+
+ case MetaActionType::RASTEROP:
+ {
+ // !!! >>> we don't want to support this actions
+ }
+ break;
+
+ case MetaActionType::REFPOINT:
+ {
+ // !!! >>> we don't want to support this actions
+ }
+ break;
+
+ default:
+ // #i24604# Made assertion fire only once per
+ // metafile. The asserted actions here are all
+ // deprecated
+ if( !bAssertionFired )
+ {
+ bAssertionFired = true;
+ SAL_WARN( "vcl", "PDFExport::ImplWriteActions: deprecated and unsupported MetaAction encountered " << static_cast<int>(nType) );
+ }
+ break;
+ }
+ i++;
+ }
+ }
+}
+
+// Encryption methods
+
+/* a crutch to transport a ::comphelper::Hash safely though UNO API
+ this is needed for the PDF export dialog, which otherwise would have to pass
+ clear text passwords down till they can be used in PDFWriter. Unfortunately
+ the MD5 sum of the password (which is needed to create the PDF encryption key)
+ is not sufficient, since an MD5 digest cannot be created in an arbitrary state
+ which would be needed in PDFWriterImpl::computeEncryptionKey.
+*/
+class EncHashTransporter : public cppu::WeakImplHelper < css::beans::XMaterialHolder >
+{
+ ::std::unique_ptr<::comphelper::Hash> m_pDigest;
+ sal_IntPtr maID;
+ std::vector< sal_uInt8 > maOValue;
+
+ static std::map< sal_IntPtr, EncHashTransporter* > sTransporters;
+public:
+ EncHashTransporter()
+ : m_pDigest(new ::comphelper::Hash(::comphelper::HashType::MD5))
+ {
+ maID = reinterpret_cast< sal_IntPtr >(this);
+ while( sTransporters.find( maID ) != sTransporters.end() ) // paranoia mode
+ maID++;
+ sTransporters[ maID ] = this;
+ }
+
+ virtual ~EncHashTransporter() override
+ {
+ sTransporters.erase( maID );
+ SAL_INFO( "vcl", "EncHashTransporter freed" );
+ }
+
+ ::comphelper::Hash* getUDigest() { return m_pDigest.get(); };
+ std::vector< sal_uInt8 >& getOValue() { return maOValue; }
+ void invalidate()
+ {
+ m_pDigest.reset();
+ }
+
+ // XMaterialHolder
+ virtual uno::Any SAL_CALL getMaterial() override
+ {
+ return uno::Any( sal_Int64(maID) );
+ }
+
+ static EncHashTransporter* getEncHashTransporter( const uno::Reference< beans::XMaterialHolder >& );
+
+};
+
+std::map< sal_IntPtr, EncHashTransporter* > EncHashTransporter::sTransporters;
+
+EncHashTransporter* EncHashTransporter::getEncHashTransporter( const uno::Reference< beans::XMaterialHolder >& xRef )
+{
+ EncHashTransporter* pResult = nullptr;
+ if( xRef.is() )
+ {
+ uno::Any aMat( xRef->getMaterial() );
+ sal_Int64 nMat = 0;
+ if( aMat >>= nMat )
+ {
+ std::map< sal_IntPtr, EncHashTransporter* >::iterator it = sTransporters.find( static_cast<sal_IntPtr>(nMat) );
+ if( it != sTransporters.end() )
+ pResult = it->second;
+ }
+ }
+ return pResult;
+}
+
+void PDFWriterImpl::checkAndEnableStreamEncryption( sal_Int32 nObject )
+{
+ if( !m_aContext.Encryption.Encrypt() )
+ return;
+
+ m_bEncryptThisStream = true;
+ sal_Int32 i = m_nKeyLength;
+ m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>(nObject);
+ m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 8 );
+ m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 16 );
+ // the other location of m_nEncryptionKey is already set to 0, our fixed generation number
+ // do the MD5 hash
+ ::std::vector<unsigned char> const nMD5Sum(::comphelper::Hash::calculateHash(
+ m_aContext.Encryption.EncryptionKey.data(), i+2, ::comphelper::HashType::MD5));
+ // the i+2 to take into account the generation number, always zero
+ // initialize the RC4 with the key
+ // key length: see algorithm 3.1, step 4: (N+5) max 16
+ rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, nMD5Sum.data(), m_nRC4KeyLength, nullptr, 0 );
+}
+
+void PDFWriterImpl::enableStringEncryption( sal_Int32 nObject )
+{
+ if( !m_aContext.Encryption.Encrypt() )
+ return;
+
+ sal_Int32 i = m_nKeyLength;
+ m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>(nObject);
+ m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 8 );
+ m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 16 );
+ // the other location of m_nEncryptionKey is already set to 0, our fixed generation number
+ // do the MD5 hash
+ // the i+2 to take into account the generation number, always zero
+ ::std::vector<unsigned char> const nMD5Sum(::comphelper::Hash::calculateHash(
+ m_aContext.Encryption.EncryptionKey.data(), i+2, ::comphelper::HashType::MD5));
+ // initialize the RC4 with the key
+ // key length: see algorithm 3.1, step 4: (N+5) max 16
+ rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, nMD5Sum.data(), m_nRC4KeyLength, nullptr, 0 );
+}
+
+/* init the encryption engine
+1. init the document id, used both for building the document id and for building the encryption key(s)
+2. build the encryption key following algorithms described in the PDF specification
+ */
+uno::Reference< beans::XMaterialHolder > PDFWriterImpl::initEncryption( const OUString& i_rOwnerPassword,
+ const OUString& i_rUserPassword
+ )
+{
+ uno::Reference< beans::XMaterialHolder > xResult;
+ if( !i_rOwnerPassword.isEmpty() || !i_rUserPassword.isEmpty() )
+ {
+ rtl::Reference<EncHashTransporter> pTransporter = new EncHashTransporter;
+ xResult = pTransporter;
+
+ // get padded passwords
+ sal_uInt8 aPadUPW[ENCRYPTED_PWD_SIZE], aPadOPW[ENCRYPTED_PWD_SIZE];
+ padPassword( i_rOwnerPassword.isEmpty() ? i_rUserPassword : i_rOwnerPassword, aPadOPW );
+ padPassword( i_rUserPassword, aPadUPW );
+
+ if( computeODictionaryValue( aPadOPW, aPadUPW, pTransporter->getOValue(), SECUR_128BIT_KEY ) )
+ {
+ pTransporter->getUDigest()->update(aPadUPW, ENCRYPTED_PWD_SIZE);
+ }
+ else
+ xResult.clear();
+
+ // trash temporary padded cleartext PWDs
+ rtl_secureZeroMemory (aPadOPW, sizeof(aPadOPW));
+ rtl_secureZeroMemory (aPadUPW, sizeof(aPadUPW));
+ }
+ return xResult;
+}
+
+bool PDFWriterImpl::prepareEncryption( const uno::Reference< beans::XMaterialHolder >& xEnc )
+{
+ bool bSuccess = false;
+ EncHashTransporter* pTransporter = EncHashTransporter::getEncHashTransporter( xEnc );
+ if( pTransporter )
+ {
+ sal_Int32 nKeyLength = 0, nRC4KeyLength = 0;
+ sal_Int32 nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, nKeyLength, nRC4KeyLength );
+ m_aContext.Encryption.OValue = pTransporter->getOValue();
+ bSuccess = computeUDictionaryValue( pTransporter, m_aContext.Encryption, nKeyLength, nAccessPermissions );
+ }
+ if( ! bSuccess )
+ {
+ m_aContext.Encryption.OValue.clear();
+ m_aContext.Encryption.UValue.clear();
+ m_aContext.Encryption.EncryptionKey.clear();
+ }
+ return bSuccess;
+}
+
+sal_Int32 PDFWriterImpl::computeAccessPermissions( const vcl::PDFWriter::PDFEncryptionProperties& i_rProperties,
+ sal_Int32& o_rKeyLength, sal_Int32& o_rRC4KeyLength )
+{
+ /*
+ 2) compute the access permissions, in numerical form
+
+ the default value depends on the revision 2 (40 bit) or 3 (128 bit security):
+ - for 40 bit security the unused bit must be set to 1, since they are not used
+ - for 128 bit security the same bit must be preset to 0 and set later if needed
+ according to the table 3.15, pdf v 1.4 */
+ sal_Int32 nAccessPermissions = 0xfffff0c0;
+
+ o_rKeyLength = SECUR_128BIT_KEY;
+ o_rRC4KeyLength = 16; // for this value see PDF spec v 1.4, algorithm 3.1 step 4, where n is 16,
+ // thus maximum permitted value is 16
+
+ nAccessPermissions |= ( i_rProperties.CanPrintTheDocument ) ? 1 << 2 : 0;
+ nAccessPermissions |= ( i_rProperties.CanModifyTheContent ) ? 1 << 3 : 0;
+ nAccessPermissions |= ( i_rProperties.CanCopyOrExtract ) ? 1 << 4 : 0;
+ nAccessPermissions |= ( i_rProperties.CanAddOrModify ) ? 1 << 5 : 0;
+ nAccessPermissions |= ( i_rProperties.CanFillInteractive ) ? 1 << 8 : 0;
+ nAccessPermissions |= ( i_rProperties.CanExtractForAccessibility ) ? 1 << 9 : 0;
+ nAccessPermissions |= ( i_rProperties.CanAssemble ) ? 1 << 10 : 0;
+ nAccessPermissions |= ( i_rProperties.CanPrintFull ) ? 1 << 11 : 0;
+ return nAccessPermissions;
+}
+
+/*************************************************************
+begin i12626 methods
+
+Implements Algorithm 3.2, step 1 only
+*/
+void PDFWriterImpl::padPassword( std::u16string_view i_rPassword, sal_uInt8* o_pPaddedPW )
+{
+ // get ansi-1252 version of the password string CHECKIT ! i12626
+ OString aString( OUStringToOString( i_rPassword, RTL_TEXTENCODING_MS_1252 ) );
+
+ //copy the string to the target
+ sal_Int32 nToCopy = ( aString.getLength() < ENCRYPTED_PWD_SIZE ) ? aString.getLength() : ENCRYPTED_PWD_SIZE;
+ sal_Int32 nCurrentChar;
+
+ for( nCurrentChar = 0; nCurrentChar < nToCopy; nCurrentChar++ )
+ o_pPaddedPW[nCurrentChar] = static_cast<sal_uInt8>( aString[nCurrentChar] );
+
+ //pad it with standard byte string
+ sal_Int32 i,y;
+ for( i = nCurrentChar, y = 0 ; i < ENCRYPTED_PWD_SIZE; i++, y++ )
+ o_pPaddedPW[i] = s_nPadString[y];
+}
+
+/**********************************
+Algorithm 3.2 Compute the encryption key used
+
+step 1 should already be done before calling, the paThePaddedPassword parameter should contain
+the padded password and must be 32 byte long, the encryption key is returned into the paEncryptionKey parameter,
+it will be 16 byte long for 128 bit security; for 40 bit security only the first 5 bytes are used
+
+TODO: in pdf ver 1.5 and 1.6 the step 6 is different, should be implemented. See spec.
+
+*/
+bool PDFWriterImpl::computeEncryptionKey( EncHashTransporter* i_pTransporter, vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, sal_Int32 i_nAccessPermissions )
+{
+ bool bSuccess = true;
+ ::std::vector<unsigned char> nMD5Sum;
+
+ // transporter contains an MD5 digest with the padded user password already
+ ::comphelper::Hash *const pDigest = i_pTransporter->getUDigest();
+ if (pDigest)
+ {
+ //step 3
+ if( ! io_rProperties.OValue.empty() )
+ pDigest->update(io_rProperties.OValue.data(), io_rProperties.OValue.size());
+ else
+ bSuccess = false;
+ //Step 4
+ sal_uInt8 nPerm[4];
+
+ nPerm[0] = static_cast<sal_uInt8>(i_nAccessPermissions);
+ nPerm[1] = static_cast<sal_uInt8>( i_nAccessPermissions >> 8 );
+ nPerm[2] = static_cast<sal_uInt8>( i_nAccessPermissions >> 16 );
+ nPerm[3] = static_cast<sal_uInt8>( i_nAccessPermissions >> 24 );
+
+ pDigest->update(nPerm, sizeof(nPerm));
+
+ //step 5, get the document ID, binary form
+ pDigest->update(io_rProperties.DocumentIdentifier.data(), io_rProperties.DocumentIdentifier.size());
+ //get the digest
+ nMD5Sum = pDigest->finalize();
+
+ //step 6, only if 128 bit
+ for (sal_Int32 i = 0; i < 50; i++)
+ {
+ nMD5Sum = ::comphelper::Hash::calculateHash(nMD5Sum.data(), nMD5Sum.size(), ::comphelper::HashType::MD5);
+ }
+ }
+ else
+ bSuccess = false;
+
+ i_pTransporter->invalidate();
+
+ //Step 7
+ if( bSuccess )
+ {
+ io_rProperties.EncryptionKey.resize( MAXIMUM_RC4_KEY_LENGTH );
+ for( sal_Int32 i = 0; i < MD5_DIGEST_SIZE; i++ )
+ io_rProperties.EncryptionKey[i] = nMD5Sum[i];
+ }
+ else
+ io_rProperties.EncryptionKey.clear();
+
+ return bSuccess;
+}
+
+/**********************************
+Algorithm 3.3 Compute the encryption dictionary /O value, save into the class data member
+the step numbers down here correspond to the ones in PDF v.1.4 specification
+*/
+bool PDFWriterImpl::computeODictionaryValue( const sal_uInt8* i_pPaddedOwnerPassword,
+ const sal_uInt8* i_pPaddedUserPassword,
+ std::vector< sal_uInt8 >& io_rOValue,
+ sal_Int32 i_nKeyLength
+ )
+{
+ bool bSuccess = true;
+
+ io_rOValue.resize( ENCRYPTED_PWD_SIZE );
+
+ rtlCipher aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
+ if (aCipher)
+ {
+ //step 1 already done, data is in i_pPaddedOwnerPassword
+ //step 2
+
+ ::std::vector<unsigned char> nMD5Sum(::comphelper::Hash::calculateHash(
+ i_pPaddedOwnerPassword, ENCRYPTED_PWD_SIZE, ::comphelper::HashType::MD5));
+ //step 3, only if 128 bit
+ if (i_nKeyLength == SECUR_128BIT_KEY)
+ {
+ sal_Int32 i;
+ for (i = 0; i < 50; i++)
+ {
+ nMD5Sum = ::comphelper::Hash::calculateHash(nMD5Sum.data(), nMD5Sum.size(), ::comphelper::HashType::MD5);
+ }
+ }
+ //Step 4, the key is in nMD5Sum
+ //step 5 already done, data is in i_pPaddedUserPassword
+ //step 6
+ if (rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode,
+ nMD5Sum.data(), i_nKeyLength , nullptr, 0 )
+ == rtl_Cipher_E_None)
+ {
+ // encrypt the user password using the key set above
+ rtl_cipher_encodeARCFOUR( aCipher, i_pPaddedUserPassword, ENCRYPTED_PWD_SIZE, // the data to be encrypted
+ io_rOValue.data(), sal_Int32(io_rOValue.size()) ); //encrypted data
+ //Step 7, only if 128 bit
+ if( i_nKeyLength == SECUR_128BIT_KEY )
+ {
+ sal_uInt32 i;
+ size_t y;
+ sal_uInt8 nLocalKey[ SECUR_128BIT_KEY ]; // 16 = 128 bit key
+
+ for( i = 1; i <= 19; i++ ) // do it 19 times, start with 1
+ {
+ for( y = 0; y < sizeof( nLocalKey ); y++ )
+ nLocalKey[y] = static_cast<sal_uInt8>( nMD5Sum[y] ^ i );
+
+ if (rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode,
+ nLocalKey, SECUR_128BIT_KEY, nullptr, 0 ) //destination data area, on init can be NULL
+ != rtl_Cipher_E_None)
+ {
+ bSuccess = false;
+ break;
+ }
+ rtl_cipher_encodeARCFOUR( aCipher, io_rOValue.data(), sal_Int32(io_rOValue.size()), // the data to be encrypted
+ io_rOValue.data(), sal_Int32(io_rOValue.size()) ); // encrypted data, can be the same as the input, encrypt "in place"
+ //step 8, store in class data member
+ }
+ }
+ }
+ else
+ bSuccess = false;
+ }
+ else
+ bSuccess = false;
+
+ if( aCipher )
+ rtl_cipher_destroyARCFOUR( aCipher );
+
+ if( ! bSuccess )
+ io_rOValue.clear();
+ return bSuccess;
+}
+
+/**********************************
+Algorithms 3.4 and 3.5 Compute the encryption dictionary /U value, save into the class data member, revision 2 (40 bit) or 3 (128 bit)
+*/
+bool PDFWriterImpl::computeUDictionaryValue( EncHashTransporter* i_pTransporter,
+ vcl::PDFWriter::PDFEncryptionProperties& io_rProperties,
+ sal_Int32 i_nKeyLength,
+ sal_Int32 i_nAccessPermissions
+ )
+{
+ bool bSuccess = true;
+
+ io_rProperties.UValue.resize( ENCRYPTED_PWD_SIZE );
+
+ ::comphelper::Hash aDigest(::comphelper::HashType::MD5);
+ rtlCipher aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
+ if (aCipher)
+ {
+ //step 1, common to both 3.4 and 3.5
+ if( computeEncryptionKey( i_pTransporter, io_rProperties, i_nAccessPermissions ) )
+ {
+ // prepare encryption key for object
+ for( sal_Int32 i = i_nKeyLength, y = 0; y < 5 ; y++ )
+ io_rProperties.EncryptionKey[i++] = 0;
+
+ //or 3.5, for 128 bit security
+ //step6, initialize the last 16 bytes of the encrypted user password to 0
+ for(sal_uInt32 i = MD5_DIGEST_SIZE; i < sal_uInt32(io_rProperties.UValue.size()); i++)
+ io_rProperties.UValue[i] = 0;
+ //steps 2 and 3
+ aDigest.update(s_nPadString, sizeof(s_nPadString));
+ aDigest.update(io_rProperties.DocumentIdentifier.data(), io_rProperties.DocumentIdentifier.size());
+
+ ::std::vector<unsigned char> const nMD5Sum(aDigest.finalize());
+ //Step 4
+ rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode,
+ io_rProperties.EncryptionKey.data(), SECUR_128BIT_KEY, nullptr, 0 ); //destination data area
+ rtl_cipher_encodeARCFOUR( aCipher, nMD5Sum.data(), nMD5Sum.size(), // the data to be encrypted
+ io_rProperties.UValue.data(), SECUR_128BIT_KEY ); //encrypted data, stored in class data member
+ //step 5
+ sal_uInt32 i;
+ size_t y;
+ sal_uInt8 nLocalKey[SECUR_128BIT_KEY];
+
+ for( i = 1; i <= 19; i++ ) // do it 19 times, start with 1
+ {
+ for( y = 0; y < sizeof( nLocalKey ) ; y++ )
+ nLocalKey[y] = static_cast<sal_uInt8>( io_rProperties.EncryptionKey[y] ^ i );
+
+ rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode,
+ nLocalKey, SECUR_128BIT_KEY, // key and key length
+ nullptr, 0 ); //destination data area, on init can be NULL
+ rtl_cipher_encodeARCFOUR( aCipher, io_rProperties.UValue.data(), SECUR_128BIT_KEY, // the data to be encrypted
+ io_rProperties.UValue.data(), SECUR_128BIT_KEY ); // encrypted data, can be the same as the input, encrypt "in place"
+ }
+ }
+ else
+ bSuccess = false;
+ }
+ else
+ bSuccess = false;
+
+ if( aCipher )
+ rtl_cipher_destroyARCFOUR( aCipher );
+
+ if( ! bSuccess )
+ io_rProperties.UValue.clear();
+ return bSuccess;
+}
+
+/* end i12626 methods */
+
+const tools::Long unsetRun[256] =
+{
+ 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, /* 0x00 - 0x0f */
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 - 0x1f */
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x20 - 0x2f */
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x30 - 0x3f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 - 0x4f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x50 - 0x5f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 - 0x6f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x70 - 0x7f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf0 - 0xff */
+};
+
+const tools::Long setRun[256] =
+{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 - 0x0f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x6f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80 - 0x8f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x90 - 0x9f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xa0 - 0xaf */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xb0 - 0xbf */
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xc0 - 0xcf */
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xd0 - 0xdf */
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xe0 - 0xef */
+ 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, /* 0xf0 - 0xff */
+};
+
+static bool isSet( const Scanline i_pLine, tools::Long i_nIndex )
+{
+ return (i_pLine[ i_nIndex/8 ] & (0x80 >> (i_nIndex&7))) != 0;
+}
+
+static tools::Long findBitRunImpl( const Scanline i_pLine, tools::Long i_nStartIndex, tools::Long i_nW, bool i_bSet )
+{
+ tools::Long nIndex = i_nStartIndex;
+ if( nIndex < i_nW )
+ {
+ const sal_uInt8 * pByte = i_pLine + (nIndex/8);
+ sal_uInt8 nByte = *pByte;
+
+ // run up to byte boundary
+ tools::Long nBitInByte = (nIndex & 7);
+ if( nBitInByte )
+ {
+ sal_uInt8 nMask = 0x80 >> nBitInByte;
+ while( nBitInByte != 8 )
+ {
+ if( (nByte & nMask) != (i_bSet ? nMask : 0) )
+ return std::min(nIndex, i_nW);
+ nMask = nMask >> 1;
+ nBitInByte++;
+ nIndex++;
+ }
+ if( nIndex < i_nW )
+ {
+ pByte++;
+ nByte = *pByte;
+ }
+ }
+
+ sal_uInt8 nRunByte;
+ const tools::Long* pRunTable;
+ if( i_bSet )
+ {
+ nRunByte = 0xff;
+ pRunTable = setRun;
+ }
+ else
+ {
+ nRunByte = 0;
+ pRunTable = unsetRun;
+ }
+
+ if( nIndex < i_nW )
+ {
+ while( nByte == nRunByte )
+ {
+ nIndex += 8;
+
+ if (nIndex >= i_nW)
+ break;
+
+ pByte++;
+ nByte = *pByte;
+ }
+ }
+
+ if( nIndex < i_nW )
+ {
+ nIndex += pRunTable[nByte];
+ }
+ }
+ return std::min(nIndex, i_nW);
+}
+
+static tools::Long findBitRun(const Scanline i_pLine, tools::Long i_nStartIndex, tools::Long i_nW, bool i_bSet)
+{
+ if (i_nStartIndex < 0)
+ return i_nW;
+
+ return findBitRunImpl(i_pLine, i_nStartIndex, i_nW, i_bSet);
+}
+
+static tools::Long findBitRun(const Scanline i_pLine, tools::Long i_nStartIndex, tools::Long i_nW)
+{
+ if (i_nStartIndex < 0)
+ return i_nW;
+
+ const bool bSet = i_nStartIndex < i_nW && isSet(i_pLine, i_nStartIndex);
+
+ return findBitRunImpl(i_pLine, i_nStartIndex, i_nW, bSet);
+}
+
+struct BitStreamState
+{
+ sal_uInt8 mnBuffer;
+ sal_uInt32 mnNextBitPos;
+
+ BitStreamState()
+ : mnBuffer( 0 )
+ , mnNextBitPos( 8 )
+ {
+ }
+
+ const sal_uInt8& getByte() const { return mnBuffer; }
+ void flush() { mnNextBitPos = 8; mnBuffer = 0; }
+};
+
+void PDFWriterImpl::putG4Bits( sal_uInt32 i_nLength, sal_uInt32 i_nCode, BitStreamState& io_rState )
+{
+ while( i_nLength > io_rState.mnNextBitPos )
+ {
+ io_rState.mnBuffer |= static_cast<sal_uInt8>( i_nCode >> (i_nLength - io_rState.mnNextBitPos) );
+ i_nLength -= io_rState.mnNextBitPos;
+ writeBufferBytes( &io_rState.getByte(), 1 );
+ io_rState.flush();
+ }
+ assert(i_nLength < 9);
+ static const unsigned int msbmask[9] = { 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff };
+ io_rState.mnBuffer |= static_cast<sal_uInt8>( (i_nCode & msbmask[i_nLength]) << (io_rState.mnNextBitPos - i_nLength) );
+ io_rState.mnNextBitPos -= i_nLength;
+ if( io_rState.mnNextBitPos == 0 )
+ {
+ writeBufferBytes( &io_rState.getByte(), 1 );
+ io_rState.flush();
+ }
+}
+
+namespace {
+
+struct PixelCode
+{
+ sal_uInt32 mnEncodedPixels;
+ sal_uInt32 mnCodeBits;
+ sal_uInt32 mnCode;
+};
+
+}
+
+const PixelCode WhitePixelCodes[] =
+{
+ { 0, 8, 0x35 }, // 0011 0101
+ { 1, 6, 0x7 }, // 0001 11
+ { 2, 4, 0x7 }, // 0111
+ { 3, 4, 0x8 }, // 1000
+ { 4, 4, 0xB }, // 1011
+ { 5, 4, 0xC }, // 1100
+ { 6, 4, 0xE }, // 1110
+ { 7, 4, 0xF }, // 1111
+ { 8, 5, 0x13 }, // 1001 1
+ { 9, 5, 0x14 }, // 1010 0
+ { 10, 5, 0x7 }, // 0011 1
+ { 11, 5, 0x8 }, // 0100 0
+ { 12, 6, 0x8 }, // 0010 00
+ { 13, 6, 0x3 }, // 0000 11
+ { 14, 6, 0x34 }, // 1101 00
+ { 15, 6, 0x35 }, // 1101 01
+ { 16, 6, 0x2A }, // 1010 10
+ { 17, 6, 0x2B }, // 1010 11
+ { 18, 7, 0x27 }, // 0100 111
+ { 19, 7, 0xC }, // 0001 100
+ { 20, 7, 0x8 }, // 0001 000
+ { 21, 7, 0x17 }, // 0010 111
+ { 22, 7, 0x3 }, // 0000 011
+ { 23, 7, 0x4 }, // 0000 100
+ { 24, 7, 0x28 }, // 0101 000
+ { 25, 7, 0x2B }, // 0101 011
+ { 26, 7, 0x13 }, // 0010 011
+ { 27, 7, 0x24 }, // 0100 100
+ { 28, 7, 0x18 }, // 0011 000
+ { 29, 8, 0x2 }, // 0000 0010
+ { 30, 8, 0x3 }, // 0000 0011
+ { 31, 8, 0x1A }, // 0001 1010
+ { 32, 8, 0x1B }, // 0001 1011
+ { 33, 8, 0x12 }, // 0001 0010
+ { 34, 8, 0x13 }, // 0001 0011
+ { 35, 8, 0x14 }, // 0001 0100
+ { 36, 8, 0x15 }, // 0001 0101
+ { 37, 8, 0x16 }, // 0001 0110
+ { 38, 8, 0x17 }, // 0001 0111
+ { 39, 8, 0x28 }, // 0010 1000
+ { 40, 8, 0x29 }, // 0010 1001
+ { 41, 8, 0x2A }, // 0010 1010
+ { 42, 8, 0x2B }, // 0010 1011
+ { 43, 8, 0x2C }, // 0010 1100
+ { 44, 8, 0x2D }, // 0010 1101
+ { 45, 8, 0x4 }, // 0000 0100
+ { 46, 8, 0x5 }, // 0000 0101
+ { 47, 8, 0xA }, // 0000 1010
+ { 48, 8, 0xB }, // 0000 1011
+ { 49, 8, 0x52 }, // 0101 0010
+ { 50, 8, 0x53 }, // 0101 0011
+ { 51, 8, 0x54 }, // 0101 0100
+ { 52, 8, 0x55 }, // 0101 0101
+ { 53, 8, 0x24 }, // 0010 0100
+ { 54, 8, 0x25 }, // 0010 0101
+ { 55, 8, 0x58 }, // 0101 1000
+ { 56, 8, 0x59 }, // 0101 1001
+ { 57, 8, 0x5A }, // 0101 1010
+ { 58, 8, 0x5B }, // 0101 1011
+ { 59, 8, 0x4A }, // 0100 1010
+ { 60, 8, 0x4B }, // 0100 1011
+ { 61, 8, 0x32 }, // 0011 0010
+ { 62, 8, 0x33 }, // 0011 0011
+ { 63, 8, 0x34 }, // 0011 0100
+ { 64, 5, 0x1B }, // 1101 1
+ { 128, 5, 0x12 }, // 1001 0
+ { 192, 6, 0x17 }, // 0101 11
+ { 256, 7, 0x37 }, // 0110 111
+ { 320, 8, 0x36 }, // 0011 0110
+ { 384, 8, 0x37 }, // 0011 0111
+ { 448, 8, 0x64 }, // 0110 0100
+ { 512, 8, 0x65 }, // 0110 0101
+ { 576, 8, 0x68 }, // 0110 1000
+ { 640, 8, 0x67 }, // 0110 0111
+ { 704, 9, 0xCC }, // 0110 0110 0
+ { 768, 9, 0xCD }, // 0110 0110 1
+ { 832, 9, 0xD2 }, // 0110 1001 0
+ { 896, 9, 0xD3 }, // 0110 1001 1
+ { 960, 9, 0xD4 }, // 0110 1010 0
+ { 1024, 9, 0xD5 }, // 0110 1010 1
+ { 1088, 9, 0xD6 }, // 0110 1011 0
+ { 1152, 9, 0xD7 }, // 0110 1011 1
+ { 1216, 9, 0xD8 }, // 0110 1100 0
+ { 1280, 9, 0xD9 }, // 0110 1100 1
+ { 1344, 9, 0xDA }, // 0110 1101 0
+ { 1408, 9, 0xDB }, // 0110 1101 1
+ { 1472, 9, 0x98 }, // 0100 1100 0
+ { 1536, 9, 0x99 }, // 0100 1100 1
+ { 1600, 9, 0x9A }, // 0100 1101 0
+ { 1664, 6, 0x18 }, // 0110 00
+ { 1728, 9, 0x9B }, // 0100 1101 1
+ { 1792, 11, 0x8 }, // 0000 0001 000
+ { 1856, 11, 0xC }, // 0000 0001 100
+ { 1920, 11, 0xD }, // 0000 0001 101
+ { 1984, 12, 0x12 }, // 0000 0001 0010
+ { 2048, 12, 0x13 }, // 0000 0001 0011
+ { 2112, 12, 0x14 }, // 0000 0001 0100
+ { 2176, 12, 0x15 }, // 0000 0001 0101
+ { 2240, 12, 0x16 }, // 0000 0001 0110
+ { 2304, 12, 0x17 }, // 0000 0001 0111
+ { 2368, 12, 0x1C }, // 0000 0001 1100
+ { 2432, 12, 0x1D }, // 0000 0001 1101
+ { 2496, 12, 0x1E }, // 0000 0001 1110
+ { 2560, 12, 0x1F } // 0000 0001 1111
+};
+
+const PixelCode BlackPixelCodes[] =
+{
+ { 0, 10, 0x37 }, // 0000 1101 11
+ { 1, 3, 0x2 }, // 010
+ { 2, 2, 0x3 }, // 11
+ { 3, 2, 0x2 }, // 10
+ { 4, 3, 0x3 }, // 011
+ { 5, 4, 0x3 }, // 0011
+ { 6, 4, 0x2 }, // 0010
+ { 7, 5, 0x3 }, // 0001 1
+ { 8, 6, 0x5 }, // 0001 01
+ { 9, 6, 0x4 }, // 0001 00
+ { 10, 7, 0x4 }, // 0000 100
+ { 11, 7, 0x5 }, // 0000 101
+ { 12, 7, 0x7 }, // 0000 111
+ { 13, 8, 0x4 }, // 0000 0100
+ { 14, 8, 0x7 }, // 0000 0111
+ { 15, 9, 0x18 }, // 0000 1100 0
+ { 16, 10, 0x17 }, // 0000 0101 11
+ { 17, 10, 0x18 }, // 0000 0110 00
+ { 18, 10, 0x8 }, // 0000 0010 00
+ { 19, 11, 0x67 }, // 0000 1100 111
+ { 20, 11, 0x68 }, // 0000 1101 000
+ { 21, 11, 0x6C }, // 0000 1101 100
+ { 22, 11, 0x37 }, // 0000 0110 111
+ { 23, 11, 0x28 }, // 0000 0101 000
+ { 24, 11, 0x17 }, // 0000 0010 111
+ { 25, 11, 0x18 }, // 0000 0011 000
+ { 26, 12, 0xCA }, // 0000 1100 1010
+ { 27, 12, 0xCB }, // 0000 1100 1011
+ { 28, 12, 0xCC }, // 0000 1100 1100
+ { 29, 12, 0xCD }, // 0000 1100 1101
+ { 30, 12, 0x68 }, // 0000 0110 1000
+ { 31, 12, 0x69 }, // 0000 0110 1001
+ { 32, 12, 0x6A }, // 0000 0110 1010
+ { 33, 12, 0x6B }, // 0000 0110 1011
+ { 34, 12, 0xD2 }, // 0000 1101 0010
+ { 35, 12, 0xD3 }, // 0000 1101 0011
+ { 36, 12, 0xD4 }, // 0000 1101 0100
+ { 37, 12, 0xD5 }, // 0000 1101 0101
+ { 38, 12, 0xD6 }, // 0000 1101 0110
+ { 39, 12, 0xD7 }, // 0000 1101 0111
+ { 40, 12, 0x6C }, // 0000 0110 1100
+ { 41, 12, 0x6D }, // 0000 0110 1101
+ { 42, 12, 0xDA }, // 0000 1101 1010
+ { 43, 12, 0xDB }, // 0000 1101 1011
+ { 44, 12, 0x54 }, // 0000 0101 0100
+ { 45, 12, 0x55 }, // 0000 0101 0101
+ { 46, 12, 0x56 }, // 0000 0101 0110
+ { 47, 12, 0x57 }, // 0000 0101 0111
+ { 48, 12, 0x64 }, // 0000 0110 0100
+ { 49, 12, 0x65 }, // 0000 0110 0101
+ { 50, 12, 0x52 }, // 0000 0101 0010
+ { 51, 12, 0x53 }, // 0000 0101 0011
+ { 52, 12, 0x24 }, // 0000 0010 0100
+ { 53, 12, 0x37 }, // 0000 0011 0111
+ { 54, 12, 0x38 }, // 0000 0011 1000
+ { 55, 12, 0x27 }, // 0000 0010 0111
+ { 56, 12, 0x28 }, // 0000 0010 1000
+ { 57, 12, 0x58 }, // 0000 0101 1000
+ { 58, 12, 0x59 }, // 0000 0101 1001
+ { 59, 12, 0x2B }, // 0000 0010 1011
+ { 60, 12, 0x2C }, // 0000 0010 1100
+ { 61, 12, 0x5A }, // 0000 0101 1010
+ { 62, 12, 0x66 }, // 0000 0110 0110
+ { 63, 12, 0x67 }, // 0000 0110 0111
+ { 64, 10, 0xF }, // 0000 0011 11
+ { 128, 12, 0xC8 }, // 0000 1100 1000
+ { 192, 12, 0xC9 }, // 0000 1100 1001
+ { 256, 12, 0x5B }, // 0000 0101 1011
+ { 320, 12, 0x33 }, // 0000 0011 0011
+ { 384, 12, 0x34 }, // 0000 0011 0100
+ { 448, 12, 0x35 }, // 0000 0011 0101
+ { 512, 13, 0x6C }, // 0000 0011 0110 0
+ { 576, 13, 0x6D }, // 0000 0011 0110 1
+ { 640, 13, 0x4A }, // 0000 0010 0101 0
+ { 704, 13, 0x4B }, // 0000 0010 0101 1
+ { 768, 13, 0x4C }, // 0000 0010 0110 0
+ { 832, 13, 0x4D }, // 0000 0010 0110 1
+ { 896, 13, 0x72 }, // 0000 0011 1001 0
+ { 960, 13, 0x73 }, // 0000 0011 1001 1
+ { 1024, 13, 0x74 }, // 0000 0011 1010 0
+ { 1088, 13, 0x75 }, // 0000 0011 1010 1
+ { 1152, 13, 0x76 }, // 0000 0011 1011 0
+ { 1216, 13, 0x77 }, // 0000 0011 1011 1
+ { 1280, 13, 0x52 }, // 0000 0010 1001 0
+ { 1344, 13, 0x53 }, // 0000 0010 1001 1
+ { 1408, 13, 0x54 }, // 0000 0010 1010 0
+ { 1472, 13, 0x55 }, // 0000 0010 1010 1
+ { 1536, 13, 0x5A }, // 0000 0010 1101 0
+ { 1600, 13, 0x5B }, // 0000 0010 1101 1
+ { 1664, 13, 0x64 }, // 0000 0011 0010 0
+ { 1728, 13, 0x65 }, // 0000 0011 0010 1
+ { 1792, 11, 0x8 }, // 0000 0001 000
+ { 1856, 11, 0xC }, // 0000 0001 100
+ { 1920, 11, 0xD }, // 0000 0001 101
+ { 1984, 12, 0x12 }, // 0000 0001 0010
+ { 2048, 12, 0x13 }, // 0000 0001 0011
+ { 2112, 12, 0x14 }, // 0000 0001 0100
+ { 2176, 12, 0x15 }, // 0000 0001 0101
+ { 2240, 12, 0x16 }, // 0000 0001 0110
+ { 2304, 12, 0x17 }, // 0000 0001 0111
+ { 2368, 12, 0x1C }, // 0000 0001 1100
+ { 2432, 12, 0x1D }, // 0000 0001 1101
+ { 2496, 12, 0x1E }, // 0000 0001 1110
+ { 2560, 12, 0x1F } // 0000 0001 1111
+};
+
+void PDFWriterImpl::putG4Span( tools::Long i_nSpan, bool i_bWhitePixel, BitStreamState& io_rState )
+{
+ const PixelCode* pTable = i_bWhitePixel ? WhitePixelCodes : BlackPixelCodes;
+ // maximum encoded span is 2560 consecutive pixels
+ while( i_nSpan > 2623 )
+ {
+ // write 2560 bits, that is entry (63 + (2560 >> 6)) == 103 in the appropriate table
+ putG4Bits( pTable[103].mnCodeBits, pTable[103].mnCode, io_rState );
+ i_nSpan -= pTable[103].mnEncodedPixels;
+ }
+ // write multiples of 64 pixels up to 2560
+ if( i_nSpan > 63 )
+ {
+ sal_uInt32 nTabIndex = 63 + (i_nSpan >> 6);
+ OSL_ASSERT( pTable[nTabIndex].mnEncodedPixels == static_cast<sal_uInt32>(64*(i_nSpan >> 6)) );
+ putG4Bits( pTable[nTabIndex].mnCodeBits, pTable[nTabIndex].mnCode, io_rState );
+ i_nSpan -= pTable[nTabIndex].mnEncodedPixels;
+ }
+ putG4Bits( pTable[i_nSpan].mnCodeBits, pTable[i_nSpan].mnCode, io_rState );
+}
+
+void PDFWriterImpl::writeG4Stream( BitmapReadAccess const * i_pBitmap )
+{
+ tools::Long nW = i_pBitmap->Width();
+ tools::Long nH = i_pBitmap->Height();
+ if( nW <= 0 || nH <= 0 )
+ return;
+ if( i_pBitmap->GetBitCount() != 1 )
+ return;
+
+ BitStreamState aBitState;
+
+ // the first reference line is virtual and completely empty
+ std::unique_ptr<sal_uInt8[]> pFirstRefLine(new sal_uInt8[nW/8 + 1]);
+ memset(pFirstRefLine.get(), 0, nW/8 + 1);
+ Scanline pRefLine = pFirstRefLine.get();
+ for( tools::Long nY = 0; nY < nH; nY++ )
+ {
+ const Scanline pCurLine = i_pBitmap->GetScanline( nY );
+ tools::Long nLineIndex = 0;
+ bool bRunSet = (*pCurLine & 0x80) != 0;
+ bool bRefSet = (*pRefLine & 0x80) != 0;
+ tools::Long nRunIndex1 = bRunSet ? 0 : findBitRun( pCurLine, 0, nW, bRunSet );
+ tools::Long nRefIndex1 = bRefSet ? 0 : findBitRun( pRefLine, 0, nW, bRefSet );
+ for( ; nLineIndex < nW; )
+ {
+ tools::Long nRefIndex2 = findBitRun( pRefLine, nRefIndex1, nW );
+ if( nRefIndex2 >= nRunIndex1 )
+ {
+ tools::Long nDiff = nRefIndex1 - nRunIndex1;
+ if( -3 <= nDiff && nDiff <= 3 )
+ { // vertical coding
+ static const struct
+ {
+ sal_uInt32 mnCodeBits;
+ sal_uInt32 mnCode;
+ } VerticalCodes[7] = {
+ { 7, 0x03 }, // 0000 011
+ { 6, 0x03 }, // 0000 11
+ { 3, 0x03 }, // 011
+ { 1, 0x1 }, // 1
+ { 3, 0x2 }, // 010
+ { 6, 0x02 }, // 0000 10
+ { 7, 0x02 } // 0000 010
+ };
+ // convert to index
+ nDiff += 3;
+
+ // emit diff code
+ putG4Bits( VerticalCodes[nDiff].mnCodeBits, VerticalCodes[nDiff].mnCode, aBitState );
+ nLineIndex = nRunIndex1;
+ }
+ else
+ { // difference too large, horizontal coding
+ // emit horz code 001
+ putG4Bits( 3, 0x1, aBitState );
+ tools::Long nRunIndex2 = findBitRun( pCurLine, nRunIndex1, nW );
+ bool bWhiteFirst = ( nLineIndex + nRunIndex1 == 0 || ! isSet( pCurLine, nLineIndex ) );
+ putG4Span( nRunIndex1 - nLineIndex, bWhiteFirst, aBitState );
+ putG4Span( nRunIndex2 - nRunIndex1, ! bWhiteFirst, aBitState );
+ nLineIndex = nRunIndex2;
+ }
+ }
+ else
+ { // emit pass code 0001
+ putG4Bits( 4, 0x1, aBitState );
+ nLineIndex = nRefIndex2;
+ }
+ if( nLineIndex < nW )
+ {
+ bool bSet = isSet( pCurLine, nLineIndex );
+ nRunIndex1 = findBitRun( pCurLine, nLineIndex, nW, bSet );
+ nRefIndex1 = findBitRun( pRefLine, nLineIndex, nW, ! bSet );
+ nRefIndex1 = findBitRun( pRefLine, nRefIndex1, nW, bSet );
+ }
+ }
+
+ // the current line is the reference for the next line
+ pRefLine = pCurLine;
+ }
+ // terminate strip with EOFB
+ putG4Bits( 12, 1, aBitState );
+ putG4Bits( 12, 1, aBitState );
+ if( aBitState.mnNextBitPos != 8 )
+ {
+ writeBufferBytes( &aBitState.getByte(), 1 );
+ aBitState.flush();
+ }
+}
+
+void PDFWriterImpl::DrawHatchLine_DrawLine(const Point& rStartPoint, const Point& rEndPoint)
+{
+ drawLine(rStartPoint, rEndPoint);
+}
+
+static bool lcl_canUsePDFAxialShading(const Gradient& rGradient) {
+ switch (rGradient.GetStyle())
+ {
+ case css::awt::GradientStyle_LINEAR:
+ case css::awt::GradientStyle_AXIAL:
+ break;
+ default:
+ return false;
+ }
+
+ // TODO: handle step count
+ return rGradient.GetSteps() <= 0;
+}
+
+void PDFWriterImpl::ImplClearFontData(bool bNewFontLists)
+{
+ VirtualDevice::ImplClearFontData(bNewFontLists);
+ if (bNewFontLists && AcquireGraphics())
+ {
+ ReleaseFontCollection();
+ ReleaseFontCache();
+ }
+}
+
+void PDFWriterImpl::ImplRefreshFontData(bool bNewFontLists)
+{
+ if (bNewFontLists && AcquireGraphics())
+ {
+ SetFontCollectionFromSVData();
+ ResetNewFontCache();
+ }
+}
+
+vcl::Region PDFWriterImpl::ClipToDeviceBounds(vcl::Region aRegion) const
+{
+ return aRegion;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/print.cxx b/vcl/source/gdi/print.cxx
new file mode 100644
index 0000000000..eff94a9211
--- /dev/null
+++ b/vcl/source/gdi/print.cxx
@@ -0,0 +1,1676 @@
+/* -*- 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 <sal/config.h>
+
+#include <sal/types.h>
+#include <sal/log.hxx>
+#include <comphelper/processfactory.hxx>
+#include <o3tl/safeint.hxx>
+#include <tools/debug.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/QueueInfo.hxx>
+#include <vcl/event.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/print.hxx>
+#include <vcl/printer/Options.hxx>
+
+#include <jobset.h>
+#include <print.h>
+#include <ImplOutDevData.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <font/fontsubstitution.hxx>
+#include <impfontcache.hxx>
+#include <print.hrc>
+#include <salgdi.hxx>
+#include <salinst.hxx>
+#include <salprn.hxx>
+#include <salptype.hxx>
+#include <salvd.hxx>
+#include <svdata.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/uno/Sequence.h>
+
+int nImplSysDialog = 0;
+
+namespace
+{
+ Paper ImplGetPaperFormat( tools::Long nWidth100thMM, tools::Long nHeight100thMM )
+ {
+ PaperInfo aInfo(nWidth100thMM, nHeight100thMM);
+ aInfo.doSloppyFit();
+ return aInfo.getPaper();
+ }
+
+ const PaperInfo& ImplGetEmptyPaper()
+ {
+ static PaperInfo aInfo(PAPER_USER);
+ return aInfo;
+ }
+}
+
+void ImplUpdateJobSetupPaper( JobSetup& rJobSetup )
+{
+ const ImplJobSetup& rConstData = rJobSetup.ImplGetConstData();
+
+ if ( !rConstData.GetPaperWidth() || !rConstData.GetPaperHeight() )
+ {
+ if ( rConstData.GetPaperFormat() != PAPER_USER )
+ {
+ PaperInfo aInfo(rConstData.GetPaperFormat());
+
+ ImplJobSetup& rData = rJobSetup.ImplGetData();
+ rData.SetPaperWidth( aInfo.getWidth() );
+ rData.SetPaperHeight( aInfo.getHeight() );
+ }
+ }
+ else if ( rConstData.GetPaperFormat() == PAPER_USER )
+ {
+ Paper ePaper = ImplGetPaperFormat( rConstData.GetPaperWidth(), rConstData.GetPaperHeight() );
+ if ( ePaper != PAPER_USER )
+ rJobSetup.ImplGetData().SetPaperFormat(ePaper);
+ }
+}
+
+void Printer::ImplPrintTransparent( const Bitmap& rBmp,
+ const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel )
+{
+ Point aDestPt( LogicToPixel( rDestPt ) );
+ Size aDestSz( LogicToPixel( rDestSize ) );
+ tools::Rectangle aSrcRect( rSrcPtPixel, rSrcSizePixel );
+
+ aSrcRect.Normalize();
+
+ if( rBmp.IsEmpty() || !aSrcRect.GetWidth() || !aSrcRect.GetHeight() || !aDestSz.Width() || !aDestSz.Height() )
+ return;
+
+ Bitmap aPaint( rBmp );
+ BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE;
+
+ // mirrored horizontally
+ if( aDestSz.Width() < 0 )
+ {
+ aDestSz.setWidth( -aDestSz.Width() );
+ aDestPt.AdjustX( -( aDestSz.Width() - 1 ) );
+ nMirrFlags |= BmpMirrorFlags::Horizontal;
+ }
+
+ // mirrored vertically
+ if( aDestSz.Height() < 0 )
+ {
+ aDestSz.setHeight( -aDestSz.Height() );
+ aDestPt.AdjustY( -( aDestSz.Height() - 1 ) );
+ nMirrFlags |= BmpMirrorFlags::Vertical;
+ }
+
+ // source cropped?
+ if( aSrcRect != tools::Rectangle( Point(), aPaint.GetSizePixel() ) )
+ {
+ aPaint.Crop( aSrcRect );
+ }
+
+ // destination mirrored
+ if( nMirrFlags != BmpMirrorFlags::NONE )
+ {
+ aPaint.Mirror( nMirrFlags );
+ }
+
+ // we always want to have a mask
+ AlphaMask aAlphaMask(aSrcRect.GetSize());
+ aAlphaMask.Erase( 0 );
+
+ // do painting
+ const tools::Long nSrcWidth = aSrcRect.GetWidth(), nSrcHeight = aSrcRect.GetHeight();
+ tools::Long nX, nY; // , nWorkX, nWorkY, nWorkWidth, nWorkHeight;
+ std::unique_ptr<tools::Long[]> pMapX(new tools::Long[ nSrcWidth + 1 ]);
+ std::unique_ptr<tools::Long[]> pMapY(new tools::Long[ nSrcHeight + 1 ]);
+ const bool bOldMap = mbMap;
+
+ mbMap = false;
+
+ // create forward mapping tables
+ for( nX = 0; nX <= nSrcWidth; nX++ )
+ pMapX[ nX ] = aDestPt.X() + FRound( static_cast<double>(aDestSz.Width()) * nX / nSrcWidth );
+
+ for( nY = 0; nY <= nSrcHeight; nY++ )
+ pMapY[ nY ] = aDestPt.Y() + FRound( static_cast<double>(aDestSz.Height()) * nY / nSrcHeight );
+
+ tools::Rectangle rectangle { Point(0,0), aSrcRect.GetSize() };
+ const Point aMapPt(pMapX[rectangle.Left()], pMapY[rectangle.Top()]);
+ const Size aMapSz( pMapX[rectangle.Right() + 1] - aMapPt.X(), // pMapX[L + W] -> L + ((R - L) + 1) -> R + 1
+ pMapY[rectangle.Bottom() + 1] - aMapPt.Y()); // same for Y
+ Bitmap aBandBmp(aPaint);
+
+ DrawBitmap(aMapPt, aMapSz, Point(), aBandBmp.GetSizePixel(), aBandBmp);
+
+ mbMap = bOldMap;
+}
+
+bool Printer::DrawTransformBitmapExDirect(
+ const basegfx::B2DHomMatrix& /*aFullTransform*/,
+ const BitmapEx& /*rBitmapEx*/,
+ double /*fAlpha*/)
+{
+ // printers can't draw bitmaps directly
+ return false;
+}
+
+bool Printer::TransformAndReduceBitmapExToTargetRange(
+ const basegfx::B2DHomMatrix& /*aFullTransform*/,
+ basegfx::B2DRange& /*aVisibleRange*/,
+ double& /*fMaximumArea*/)
+{
+ // deliberately do nothing - you can't reduce the
+ // target range for a printer at all
+ return true;
+}
+
+void Printer::DrawDeviceBitmapEx( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ BitmapEx& rBmpEx )
+{
+ if( rBmpEx.IsAlpha() )
+ {
+ // #107169# For true alpha bitmaps, no longer masking the
+ // bitmap, but perform a full alpha blend against a white
+ // background here.
+ Bitmap aBmp( rBmpEx.GetBitmap() );
+ aBmp.Blend( rBmpEx.GetAlphaMask(), COL_WHITE );
+ DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmp );
+ }
+ else
+ {
+ Bitmap aBmp( rBmpEx.GetBitmap() );
+ ImplPrintTransparent( aBmp, rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel );
+ }
+}
+
+void Printer::EmulateDrawTransparent ( const tools::PolyPolygon& rPolyPoly,
+ sal_uInt16 nTransparencePercent )
+{
+ // #110958# Disable alpha VDev, we perform the necessary
+ VirtualDevice* pOldAlphaVDev = mpAlphaVDev;
+
+ // operation explicitly further below.
+ if( mpAlphaVDev )
+ mpAlphaVDev = nullptr;
+
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ mpMetaFile = nullptr;
+
+ mpMetaFile = pOldMetaFile;
+
+ // #110958# Restore disabled alpha VDev
+ mpAlphaVDev = pOldAlphaVDev;
+
+ tools::Rectangle aPolyRect( LogicToPixel( rPolyPoly ).GetBoundRect() );
+ const Size aDPISize( LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)) );
+ const tools::Long nBaseExtent = std::max<tools::Long>( FRound( aDPISize.Width() / 300. ), 1 );
+ tools::Long nMove;
+ const sal_uInt16 nTrans = ( nTransparencePercent < 13 ) ? 0 :
+ ( nTransparencePercent < 38 ) ? 25 :
+ ( nTransparencePercent < 63 ) ? 50 :
+ ( nTransparencePercent < 88 ) ? 75 : 100;
+
+ switch( nTrans )
+ {
+ case 25: nMove = nBaseExtent * 3; break;
+ case 50: nMove = nBaseExtent * 4; break;
+ case 75: nMove = nBaseExtent * 6; break;
+
+ // #i112959# very transparent (88 < nTransparencePercent <= 99)
+ case 100: nMove = nBaseExtent * 8; break;
+
+ // #i112959# not transparent (nTransparencePercent < 13)
+ default: nMove = 0; break;
+ }
+
+ Push( vcl::PushFlags::CLIPREGION | vcl::PushFlags::LINECOLOR );
+ IntersectClipRegion(vcl::Region(rPolyPoly));
+ SetLineColor( GetFillColor() );
+ const bool bOldMap = mbMap;
+ EnableMapMode( false );
+
+ if(nMove)
+ {
+ tools::Rectangle aRect( aPolyRect.TopLeft(), Size( aPolyRect.GetWidth(), nBaseExtent ) );
+ while( aRect.Top() <= aPolyRect.Bottom() )
+ {
+ DrawRect( aRect );
+ aRect.Move( 0, nMove );
+ }
+
+ aRect = tools::Rectangle( aPolyRect.TopLeft(), Size( nBaseExtent, aPolyRect.GetHeight() ) );
+ while( aRect.Left() <= aPolyRect.Right() )
+ {
+ DrawRect( aRect );
+ aRect.Move( nMove, 0 );
+ }
+ }
+ else
+ {
+ // #i112959# if not transparent, draw full rectangle in clip region
+ DrawRect( aPolyRect );
+ }
+
+ EnableMapMode( bOldMap );
+ Pop();
+
+ mpMetaFile = pOldMetaFile;
+
+ // #110958# Restore disabled alpha VDev
+ mpAlphaVDev = pOldAlphaVDev;
+}
+
+void Printer::DrawOutDev( const Point& /*rDestPt*/, const Size& /*rDestSize*/,
+ const Point& /*rSrcPt*/, const Size& /*rSrcSize*/ )
+{
+ SAL_WARN( "vcl.gdi", "Don't use OutputDevice::DrawOutDev(...) with printer devices!" );
+}
+
+void Printer::DrawOutDev( const Point& /*rDestPt*/, const Size& /*rDestSize*/,
+ const Point& /*rSrcPt*/, const Size& /*rSrcSize*/,
+ const OutputDevice& /*rOutDev*/ )
+{
+ SAL_WARN( "vcl.gdi", "Don't use OutputDevice::DrawOutDev(...) with printer devices!" );
+}
+
+void Printer::CopyArea( const Point& /*rDestPt*/,
+ const Point& /*rSrcPt*/, const Size& /*rSrcSize*/,
+ bool /*bWindowInvalidate*/ )
+{
+ SAL_WARN( "vcl.gdi", "Don't use OutputDevice::CopyArea(...) with printer devices!" );
+}
+
+tools::Rectangle Printer::GetBackgroundComponentBounds() const
+{
+ Point aPageOffset = Point( 0, 0 ) - this->GetPageOffsetPixel();
+ Size aSize = this->GetPaperSizePixel();
+ return tools::Rectangle( aPageOffset, aSize );
+}
+
+void Printer::SetPrinterOptions( const vcl::printer::Options& i_rOptions )
+{
+ *mpPrinterOptions = i_rOptions;
+}
+
+bool Printer::HasMirroredGraphics() const
+{
+ // due to a "hotfix" for AOO bug i55719, this needs to return false
+ return false;
+}
+
+SalPrinterQueueInfo::SalPrinterQueueInfo()
+{
+ mnStatus = PrintQueueFlags::NONE;
+ mnJobs = QUEUE_JOBS_DONTKNOW;
+}
+
+SalPrinterQueueInfo::~SalPrinterQueueInfo()
+{
+}
+
+ImplPrnQueueList::~ImplPrnQueueList()
+{
+}
+
+void ImplPrnQueueList::Add( std::unique_ptr<SalPrinterQueueInfo> pData )
+{
+ std::unordered_map< OUString, sal_Int32 >::iterator it =
+ m_aNameToIndex.find( pData->maPrinterName );
+ if( it == m_aNameToIndex.end() )
+ {
+ m_aNameToIndex[ pData->maPrinterName ] = m_aQueueInfos.size();
+ m_aPrinterList.push_back( pData->maPrinterName );
+ m_aQueueInfos.push_back( ImplPrnQueueData() );
+ m_aQueueInfos.back().mpQueueInfo = nullptr;
+ m_aQueueInfos.back().mpSalQueueInfo = std::move(pData);
+ }
+ else // this should not happen, but ...
+ {
+ ImplPrnQueueData& rData = m_aQueueInfos[ it->second ];
+ rData.mpQueueInfo.reset();
+ rData.mpSalQueueInfo = std::move(pData);
+ }
+}
+
+ImplPrnQueueData* ImplPrnQueueList::Get( const OUString& rPrinter )
+{
+ ImplPrnQueueData* pData = nullptr;
+ std::unordered_map<OUString,sal_Int32>::iterator it =
+ m_aNameToIndex.find( rPrinter );
+ if( it != m_aNameToIndex.end() )
+ pData = &m_aQueueInfos[it->second];
+ return pData;
+}
+
+static void ImplInitPrnQueueList()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ pSVData->maGDIData.mpPrinterQueueList.reset(new ImplPrnQueueList);
+
+ static const char* pEnv = getenv( "SAL_DISABLE_PRINTERLIST" );
+ if( !pEnv || !*pEnv )
+ pSVData->mpDefInst->GetPrinterQueueInfo( pSVData->maGDIData.mpPrinterQueueList.get() );
+}
+
+void ImplDeletePrnQueueList()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maGDIData.mpPrinterQueueList.reset();
+}
+
+const std::vector<OUString>& Printer::GetPrinterQueues()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->maGDIData.mpPrinterQueueList )
+ ImplInitPrnQueueList();
+ assert(pSVData->maGDIData.mpPrinterQueueList && "mpPrinterQueueList exists by now");
+ return pSVData->maGDIData.mpPrinterQueueList->m_aPrinterList;
+}
+
+const QueueInfo* Printer::GetQueueInfo( const OUString& rPrinterName, bool bStatusUpdate )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( !pSVData->maGDIData.mpPrinterQueueList )
+ ImplInitPrnQueueList();
+
+ if ( !pSVData->maGDIData.mpPrinterQueueList )
+ return nullptr;
+
+ ImplPrnQueueData* pInfo = pSVData->maGDIData.mpPrinterQueueList->Get( rPrinterName );
+ if( pInfo )
+ {
+ if( !pInfo->mpQueueInfo || bStatusUpdate )
+ pSVData->mpDefInst->GetPrinterQueueState( pInfo->mpSalQueueInfo.get() );
+
+ if ( !pInfo->mpQueueInfo )
+ pInfo->mpQueueInfo.reset(new QueueInfo);
+
+ pInfo->mpQueueInfo->maPrinterName = pInfo->mpSalQueueInfo->maPrinterName;
+ pInfo->mpQueueInfo->maDriver = pInfo->mpSalQueueInfo->maDriver;
+ pInfo->mpQueueInfo->maLocation = pInfo->mpSalQueueInfo->maLocation;
+ pInfo->mpQueueInfo->maComment = pInfo->mpSalQueueInfo->maComment;
+ pInfo->mpQueueInfo->mnStatus = pInfo->mpSalQueueInfo->mnStatus;
+ pInfo->mpQueueInfo->mnJobs = pInfo->mpSalQueueInfo->mnJobs;
+ return pInfo->mpQueueInfo.get();
+ }
+ return nullptr;
+}
+
+OUString Printer::GetDefaultPrinterName()
+{
+ static const char* pEnv = getenv( "SAL_DISABLE_DEFAULTPRINTER" );
+ if( !pEnv || !*pEnv )
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+
+ return pSVData->mpDefInst->GetDefaultPrinter();
+ }
+ return OUString();
+}
+
+void Printer::ImplInitData()
+{
+ mbDevOutput = false;
+ mbDefPrinter = false;
+ mnError = ERRCODE_NONE;
+ mnPageQueueSize = 0;
+ mnCopyCount = 1;
+ mbCollateCopy = false;
+ mbPrinting = false;
+ mbJobActive = false;
+ mbPrintFile = false;
+ mbInPrintPage = false;
+ mbNewJobSetup = false;
+ mbSinglePrintJobs = false;
+ mpInfoPrinter = nullptr;
+ mpPrinter = nullptr;
+ mpDisplayDev = nullptr;
+ mpPrinterOptions.reset(new vcl::printer::Options);
+
+ // Add printer to the list
+ ImplSVData* pSVData = ImplGetSVData();
+ mpNext = pSVData->maGDIData.mpFirstPrinter;
+ mpPrev = nullptr;
+ if ( mpNext )
+ mpNext->mpPrev = this;
+ pSVData->maGDIData.mpFirstPrinter = this;
+}
+
+bool Printer::AcquireGraphics() const
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( mpGraphics )
+ return true;
+
+ mbInitLineColor = true;
+ mbInitFillColor = true;
+ mbInitFont = true;
+ mbInitTextColor = true;
+ mbInitClipRegion = true;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( mpJobGraphics )
+ mpGraphics = mpJobGraphics;
+ else if ( mpDisplayDev )
+ {
+ const VirtualDevice* pVirDev = mpDisplayDev;
+ mpGraphics = pVirDev->mpVirDev->AcquireGraphics();
+ // if needed retry after releasing least recently used virtual device graphics
+ while ( !mpGraphics )
+ {
+ if ( !pSVData->maGDIData.mpLastVirGraphics )
+ break;
+ pSVData->maGDIData.mpLastVirGraphics->ReleaseGraphics();
+ mpGraphics = pVirDev->mpVirDev->AcquireGraphics();
+ }
+ // update global LRU list of virtual device graphics
+ if ( mpGraphics )
+ {
+ mpNextGraphics = pSVData->maGDIData.mpFirstVirGraphics;
+ pSVData->maGDIData.mpFirstVirGraphics = const_cast<Printer*>(this);
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = const_cast<Printer*>(this);
+ if ( !pSVData->maGDIData.mpLastVirGraphics )
+ pSVData->maGDIData.mpLastVirGraphics = const_cast<Printer*>(this);
+ }
+ }
+ else
+ {
+ mpGraphics = mpInfoPrinter->AcquireGraphics();
+ // if needed retry after releasing least recently used printer graphics
+ while ( !mpGraphics )
+ {
+ if ( !pSVData->maGDIData.mpLastPrnGraphics )
+ break;
+ pSVData->maGDIData.mpLastPrnGraphics->ReleaseGraphics();
+ mpGraphics = mpInfoPrinter->AcquireGraphics();
+ }
+ // update global LRU list of printer graphics
+ if ( mpGraphics )
+ {
+ mpNextGraphics = pSVData->maGDIData.mpFirstPrnGraphics;
+ pSVData->maGDIData.mpFirstPrnGraphics = const_cast<Printer*>(this);
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = const_cast<Printer*>(this);
+ if ( !pSVData->maGDIData.mpLastPrnGraphics )
+ pSVData->maGDIData.mpLastPrnGraphics = const_cast<Printer*>(this);
+ }
+ }
+
+ if ( mpGraphics )
+ {
+ mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp );
+ mpGraphics->setAntiAlias(bool(mnAntialiasing & AntialiasingFlags::Enable));
+ }
+
+ return mpGraphics != nullptr;
+}
+
+void Printer::ImplReleaseFonts()
+{
+ mpGraphics->ReleaseFonts();
+ mbNewFont = true;
+ mbInitFont = true;
+
+ mpFontInstance.clear();
+ mpFontFaceCollection.reset();
+}
+
+void Printer::ImplReleaseGraphics(bool bRelease)
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( !mpGraphics )
+ return;
+
+ // release the fonts of the physically released graphics device
+ if( bRelease )
+ ImplReleaseFonts();
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ Printer* pPrinter = this;
+
+ if ( !pPrinter->mpJobGraphics )
+ {
+ if ( pPrinter->mpDisplayDev )
+ {
+ VirtualDevice* pVirDev = pPrinter->mpDisplayDev;
+ if ( bRelease )
+ pVirDev->mpVirDev->ReleaseGraphics( mpGraphics );
+ // remove from global LRU list of virtual device graphics
+ if ( mpPrevGraphics )
+ mpPrevGraphics->mpNextGraphics = mpNextGraphics;
+ else
+ pSVData->maGDIData.mpFirstVirGraphics = mpNextGraphics;
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = mpPrevGraphics;
+ else
+ pSVData->maGDIData.mpLastVirGraphics = mpPrevGraphics;
+ }
+ else
+ {
+ if ( bRelease )
+ pPrinter->mpInfoPrinter->ReleaseGraphics( mpGraphics );
+ // remove from global LRU list of printer graphics
+ if ( mpPrevGraphics )
+ mpPrevGraphics->mpNextGraphics = mpNextGraphics;
+ else
+ pSVData->maGDIData.mpFirstPrnGraphics = static_cast<Printer*>(mpNextGraphics.get());
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = mpPrevGraphics;
+ else
+ pSVData->maGDIData.mpLastPrnGraphics = static_cast<Printer*>(mpPrevGraphics.get());
+ }
+ }
+
+ mpGraphics = nullptr;
+ mpPrevGraphics = nullptr;
+ mpNextGraphics = nullptr;
+}
+
+void Printer::ReleaseGraphics(bool bRelease)
+{
+ ImplReleaseGraphics(bRelease);
+}
+
+void Printer::ImplInit( SalPrinterQueueInfo* pInfo )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ // #i74084# update info for this specific SalPrinterQueueInfo
+ pSVData->mpDefInst->GetPrinterQueueState( pInfo );
+
+ // Test whether the driver actually matches the JobSetup
+ ImplJobSetup& rData = maJobSetup.ImplGetData();
+ if ( rData.GetDriverData() )
+ {
+ if ( rData.GetPrinterName() != pInfo->maPrinterName ||
+ rData.GetDriver() != pInfo->maDriver )
+ {
+ rData.SetDriverData(nullptr, 0);
+ }
+ }
+
+ // Remember printer name
+ maPrinterName = pInfo->maPrinterName;
+ maDriver = pInfo->maDriver;
+
+ // Add printer name to JobSetup
+ rData.SetPrinterName( maPrinterName );
+ rData.SetDriver( maDriver );
+
+ mpInfoPrinter = pSVData->mpDefInst->CreateInfoPrinter( pInfo, &rData );
+ mpPrinter = nullptr;
+ mpJobGraphics = nullptr;
+ ImplUpdateJobSetupPaper( maJobSetup );
+
+ if ( !mpInfoPrinter )
+ {
+ ImplInitDisplay();
+ return;
+ }
+
+ // we need a graphics
+ if ( !AcquireGraphics() )
+ {
+ ImplInitDisplay();
+ return;
+ }
+
+ // Init data
+ ImplUpdatePageData();
+ mxFontCollection = std::make_shared<vcl::font::PhysicalFontCollection>();
+ mxFontCache = std::make_shared<ImplFontCache>();
+ mpGraphics->GetDevFontList(mxFontCollection.get());
+}
+
+void Printer::ImplInitDisplay()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ mpInfoPrinter = nullptr;
+ mpPrinter = nullptr;
+ mpJobGraphics = nullptr;
+
+ mpDisplayDev = VclPtr<VirtualDevice>::Create();
+ mxFontCollection = pSVData->maGDIData.mxScreenFontList;
+ mxFontCache = pSVData->maGDIData.mxScreenFontCache;
+ mnDPIX = mpDisplayDev->mnDPIX;
+ mnDPIY = mpDisplayDev->mnDPIY;
+}
+
+void Printer::DrawDeviceMask( const Bitmap& rMask, const Color& rMaskColor,
+ const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel )
+{
+ Point aDestPt( LogicToPixel( rDestPt ) );
+ Size aDestSz( LogicToPixel( rDestSize ) );
+ tools::Rectangle aSrcRect( rSrcPtPixel, rSrcSizePixel );
+
+ aSrcRect.Normalize();
+
+ if( !(!rMask.IsEmpty() && aSrcRect.GetWidth() && aSrcRect.GetHeight() && aDestSz.Width() && aDestSz.Height()) )
+ return;
+
+ Bitmap aMask( rMask );
+ BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE;
+
+ if (aMask.getPixelFormat() >= vcl::PixelFormat::N8_BPP)
+ aMask.Convert( BmpConversion::N1BitThreshold );
+
+ // mirrored horizontally
+ if( aDestSz.Width() < 0 )
+ {
+ aDestSz.setWidth( -aDestSz.Width() );
+ aDestPt.AdjustX( -( aDestSz.Width() - 1 ) );
+ nMirrFlags |= BmpMirrorFlags::Horizontal;
+ }
+
+ // mirrored vertically
+ if( aDestSz.Height() < 0 )
+ {
+ aDestSz.setHeight( -aDestSz.Height() );
+ aDestPt.AdjustY( -( aDestSz.Height() - 1 ) );
+ nMirrFlags |= BmpMirrorFlags::Vertical;
+ }
+
+ // source cropped?
+ if( aSrcRect != tools::Rectangle( Point(), aMask.GetSizePixel() ) )
+ aMask.Crop( aSrcRect );
+
+ // destination mirrored
+ if( nMirrFlags != BmpMirrorFlags::NONE)
+ aMask.Mirror( nMirrFlags );
+
+ // do painting
+ const tools::Long nSrcWidth = aSrcRect.GetWidth(), nSrcHeight = aSrcRect.GetHeight();
+ tools::Long nX, nY; //, nWorkX, nWorkY, nWorkWidth, nWorkHeight;
+ std::unique_ptr<tools::Long[]> pMapX( new tools::Long[ nSrcWidth + 1 ] );
+ std::unique_ptr<tools::Long[]> pMapY( new tools::Long[ nSrcHeight + 1 ] );
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ const bool bOldMap = mbMap;
+
+ mpMetaFile = nullptr;
+ mbMap = false;
+ Push( vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR );
+ SetLineColor( rMaskColor );
+ SetFillColor( rMaskColor );
+ InitLineColor();
+ InitFillColor();
+
+ // create forward mapping tables
+ for( nX = 0; nX <= nSrcWidth; nX++ )
+ pMapX[ nX ] = aDestPt.X() + FRound( static_cast<double>(aDestSz.Width()) * nX / nSrcWidth );
+
+ for( nY = 0; nY <= nSrcHeight; nY++ )
+ pMapY[ nY ] = aDestPt.Y() + FRound( static_cast<double>(aDestSz.Height()) * nY / nSrcHeight );
+
+ // walk through all rectangles of mask
+ const vcl::Region aWorkRgn(aMask.CreateRegion(COL_BLACK, tools::Rectangle(Point(), aMask.GetSizePixel())));
+ RectangleVector aRectangles;
+ aWorkRgn.GetRegionRectangles(aRectangles);
+
+ for (auto const& rectangle : aRectangles)
+ {
+ const Point aMapPt(pMapX[rectangle.Left()], pMapY[rectangle.Top()]);
+ const Size aMapSz(
+ pMapX[rectangle.Right() + 1] - aMapPt.X(), // pMapX[L + W] -> L + ((R - L) + 1) -> R + 1
+ pMapY[rectangle.Bottom() + 1] - aMapPt.Y()); // same for Y
+
+ DrawRect(tools::Rectangle(aMapPt, aMapSz));
+ }
+
+ Pop();
+ mbMap = bOldMap;
+ mpMetaFile = pOldMetaFile;
+}
+
+SalPrinterQueueInfo* Printer::ImplGetQueueInfo( const OUString& rPrinterName,
+ const OUString* pDriver )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->maGDIData.mpPrinterQueueList )
+ ImplInitPrnQueueList();
+
+ ImplPrnQueueList* pPrnList = pSVData->maGDIData.mpPrinterQueueList.get();
+ if ( pPrnList && !pPrnList->m_aQueueInfos.empty() )
+ {
+ // first search for the printer name directly
+ ImplPrnQueueData* pInfo = pPrnList->Get( rPrinterName );
+ if( pInfo )
+ return pInfo->mpSalQueueInfo.get();
+
+ // then search case insensitive
+ for(const ImplPrnQueueData & rQueueInfo : pPrnList->m_aQueueInfos)
+ {
+ if( rQueueInfo.mpSalQueueInfo->maPrinterName.equalsIgnoreAsciiCase( rPrinterName ) )
+ return rQueueInfo.mpSalQueueInfo.get();
+ }
+
+ // then search for driver name
+ if ( pDriver )
+ {
+ for(const ImplPrnQueueData & rQueueInfo : pPrnList->m_aQueueInfos)
+ {
+ if( rQueueInfo.mpSalQueueInfo->maDriver == *pDriver )
+ return rQueueInfo.mpSalQueueInfo.get();
+ }
+ }
+
+ // then the default printer
+ pInfo = pPrnList->Get( GetDefaultPrinterName() );
+ if( pInfo )
+ return pInfo->mpSalQueueInfo.get();
+
+ // last chance: the first available printer
+ return pPrnList->m_aQueueInfos[0].mpSalQueueInfo.get();
+ }
+
+ return nullptr;
+}
+
+void Printer::ImplUpdatePageData()
+{
+ // we need a graphics
+ if ( !AcquireGraphics() )
+ return;
+
+ mpGraphics->GetResolution( mnDPIX, mnDPIY );
+ mpInfoPrinter->GetPageInfo( &maJobSetup.ImplGetConstData(),
+ mnOutWidth, mnOutHeight,
+ maPageOffset,
+ maPaperSize );
+}
+
+void Printer::ImplUpdateFontList()
+{
+ ImplUpdateFontData();
+}
+
+tools::Long Printer::GetGradientStepCount( tools::Long nMinRect )
+{
+ // use display-equivalent step size calculation
+ tools::Long nInc = (nMinRect < 800) ? 10 : 20;
+
+ return nInc;
+}
+
+Printer::Printer()
+ : OutputDevice(OUTDEV_PRINTER)
+{
+ ImplInitData();
+ SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( GetDefaultPrinterName(), nullptr );
+ if ( pInfo )
+ {
+ ImplInit( pInfo );
+ if ( !IsDisplayPrinter() )
+ mbDefPrinter = true;
+ }
+ else
+ ImplInitDisplay();
+}
+
+Printer::Printer( const JobSetup& rJobSetup )
+ : OutputDevice(OUTDEV_PRINTER)
+ , maJobSetup(rJobSetup)
+{
+ ImplInitData();
+ const ImplJobSetup& rConstData = rJobSetup.ImplGetConstData();
+ OUString aDriver = rConstData.GetDriver();
+ SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( rConstData.GetPrinterName(),
+ &aDriver );
+ if ( pInfo )
+ {
+ ImplInit( pInfo );
+ SetJobSetup( rJobSetup );
+ }
+ else
+ {
+ ImplInitDisplay();
+ maJobSetup = JobSetup();
+ }
+}
+
+Printer::Printer( const QueueInfo& rQueueInfo )
+ : OutputDevice(OUTDEV_PRINTER)
+{
+ ImplInitData();
+ SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( rQueueInfo.GetPrinterName(),
+ &rQueueInfo.GetDriver() );
+ if ( pInfo )
+ ImplInit( pInfo );
+ else
+ ImplInitDisplay();
+}
+
+Printer::Printer( const OUString& rPrinterName )
+ : OutputDevice(OUTDEV_PRINTER)
+{
+ ImplInitData();
+ SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( rPrinterName, nullptr );
+ if ( pInfo )
+ ImplInit( pInfo );
+ else
+ ImplInitDisplay();
+}
+
+Printer::~Printer()
+{
+ disposeOnce();
+}
+
+void Printer::dispose()
+{
+ SAL_WARN_IF( IsPrinting(), "vcl.gdi", "Printer::~Printer() - Job is printing" );
+ SAL_WARN_IF( IsJobActive(), "vcl.gdi", "Printer::~Printer() - Job is active" );
+
+ mpPrinterOptions.reset();
+
+ ImplReleaseGraphics();
+ if ( mpInfoPrinter )
+ ImplGetSVData()->mpDefInst->DestroyInfoPrinter( mpInfoPrinter );
+ if ( mpDisplayDev )
+ mpDisplayDev.disposeAndClear();
+ else
+ {
+ // OutputDevice Dtor is trying the same thing; that why we need to set
+ // the FontEntry to NULL here
+ // TODO: consolidate duplicate cleanup by Printer and OutputDevice
+ mpFontInstance.clear();
+ mpFontFaceCollection.reset();
+ mxFontCache.reset();
+ // font list deleted by OutputDevice dtor
+ }
+
+ // Add printer from the list
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( mpPrev )
+ mpPrev->mpNext = mpNext;
+ else
+ pSVData->maGDIData.mpFirstPrinter = mpNext;
+ if ( mpNext )
+ mpNext->mpPrev = mpPrev;
+
+ mpPrev.clear();
+ mpNext.clear();
+ OutputDevice::dispose();
+}
+
+Size Printer::GetButtonBorderSize()
+{
+ Size aBrdSize(LogicToPixel(Size(20, 20), MapMode(MapUnit::Map100thMM)));
+
+ if (!aBrdSize.Width())
+ aBrdSize.setWidth(1);
+
+ if (!aBrdSize.Height())
+ aBrdSize.setHeight(1);
+
+ return aBrdSize;
+}
+
+sal_uInt32 Printer::GetCapabilities( PrinterCapType nType ) const
+{
+ if ( IsDisplayPrinter() )
+ return 0;
+
+ if( mpInfoPrinter )
+ return mpInfoPrinter->GetCapabilities( &maJobSetup.ImplGetConstData(), nType );
+ else
+ return 0;
+}
+
+bool Printer::HasSupport( PrinterSupport eFeature ) const
+{
+ switch ( eFeature )
+ {
+ case PrinterSupport::SetOrientation:
+ return GetCapabilities( PrinterCapType::SetOrientation ) != 0;
+ case PrinterSupport::SetPaperSize:
+ return GetCapabilities( PrinterCapType::SetPaperSize ) != 0;
+ case PrinterSupport::SetPaper:
+ return GetCapabilities( PrinterCapType::SetPaper ) != 0;
+ case PrinterSupport::CollateCopy:
+ return (GetCapabilities( PrinterCapType::CollateCopies ) != 0);
+ case PrinterSupport::SetupDialog:
+ return GetCapabilities( PrinterCapType::SupportDialog ) != 0;
+ }
+
+ return true;
+}
+
+bool Printer::SetJobSetup( const JobSetup& rSetup )
+{
+ if ( IsDisplayPrinter() || mbInPrintPage )
+ return false;
+
+ JobSetup aJobSetup = rSetup;
+
+ ReleaseGraphics();
+ if ( mpInfoPrinter->SetPrinterData( &aJobSetup.ImplGetData() ) )
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ return true;
+ }
+
+ return false;
+}
+
+bool Printer::Setup(weld::Window* pWindow, PrinterSetupMode eMode)
+{
+ if ( IsDisplayPrinter() )
+ return false;
+
+ if ( IsJobActive() || IsPrinting() )
+ return false;
+
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+ rData.SetPrinterSetupMode( eMode );
+ // TODO: orig page size
+
+ if (!pWindow)
+ {
+ vcl::Window* pDefWin = ImplGetDefaultWindow();
+ pWindow = pDefWin ? pDefWin->GetFrameWeld() : nullptr;
+ }
+ if( !pWindow )
+ return false;
+
+ ReleaseGraphics();
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mnModalMode++;
+ nImplSysDialog++;
+ bool bSetup = mpInfoPrinter->Setup(pWindow, &rData);
+ pSVData->maAppData.mnModalMode--;
+ nImplSysDialog--;
+ if ( bSetup )
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ return true;
+ }
+ return false;
+}
+
+bool Printer::SetPrinterProps( const Printer* pPrinter )
+{
+ if ( IsJobActive() || IsPrinting() )
+ return false;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ mbDefPrinter = pPrinter->mbDefPrinter;
+ maPrintFile = pPrinter->maPrintFile;
+ mbPrintFile = pPrinter->mbPrintFile;
+ mnCopyCount = pPrinter->mnCopyCount;
+ mbCollateCopy = pPrinter->mbCollateCopy;
+ mnPageQueueSize = pPrinter->mnPageQueueSize;
+ *mpPrinterOptions = *pPrinter->mpPrinterOptions;
+
+ if ( pPrinter->IsDisplayPrinter() )
+ {
+ // Destroy old printer
+ if ( !IsDisplayPrinter() )
+ {
+ ReleaseGraphics();
+ pSVData->mpDefInst->DestroyInfoPrinter( mpInfoPrinter );
+ mpFontInstance.clear();
+ mpFontFaceCollection.reset();
+ // clean up font list
+ mxFontCache.reset();
+ mxFontCollection.reset();
+
+ mbInitFont = true;
+ mbNewFont = true;
+ mpInfoPrinter = nullptr;
+ }
+
+ // Construct new printer
+ ImplInitDisplay();
+ return true;
+ }
+
+ // Destroy old printer?
+ if ( GetName() != pPrinter->GetName() )
+ {
+ ReleaseGraphics();
+ if ( mpDisplayDev )
+ {
+ mpDisplayDev.disposeAndClear();
+ }
+ else
+ {
+ pSVData->mpDefInst->DestroyInfoPrinter( mpInfoPrinter );
+
+ mpFontInstance.clear();
+ mpFontFaceCollection.reset();
+ mxFontCache.reset();
+ mxFontCollection.reset();
+ mbInitFont = true;
+ mbNewFont = true;
+ mpInfoPrinter = nullptr;
+ }
+
+ // Construct new printer
+ OUString aDriver = pPrinter->GetDriverName();
+ SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( pPrinter->GetName(), &aDriver );
+ if ( pInfo )
+ {
+ ImplInit( pInfo );
+ SetJobSetup( pPrinter->GetJobSetup() );
+ }
+ else
+ ImplInitDisplay();
+ }
+ else
+ SetJobSetup( pPrinter->GetJobSetup() );
+
+ return false;
+}
+
+bool Printer::SetOrientation( Orientation eOrientation )
+{
+ if ( mbInPrintPage )
+ return false;
+
+ if ( maJobSetup.ImplGetConstData().GetOrientation() != eOrientation )
+ {
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+
+ rData.SetOrientation(eOrientation);
+
+ if ( IsDisplayPrinter() )
+ {
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ return true;
+ }
+
+ ReleaseGraphics();
+ if ( mpInfoPrinter->SetData( JobSetFlags::ORIENTATION, &rData ) )
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ return true;
+ }
+ else
+ return false;
+ }
+
+ return true;
+}
+
+Orientation Printer::GetOrientation() const
+{
+ return maJobSetup.ImplGetConstData().GetOrientation();
+}
+
+bool Printer::SetPaperBin( sal_uInt16 nPaperBin )
+{
+ if ( mbInPrintPage )
+ return false;
+
+ if ( maJobSetup.ImplGetConstData().GetPaperBin() != nPaperBin &&
+ nPaperBin < GetPaperBinCount() )
+ {
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+ rData.SetPaperBin(nPaperBin);
+
+ if ( IsDisplayPrinter() )
+ {
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ return true;
+ }
+
+ ReleaseGraphics();
+ if ( mpInfoPrinter->SetData( JobSetFlags::PAPERBIN, &rData ) )
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ return true;
+ }
+ else
+ return false;
+ }
+
+ return true;
+}
+
+sal_uInt16 Printer::GetPaperBin() const
+{
+ return maJobSetup.ImplGetConstData().GetPaperBin();
+}
+
+bool Printer::GetPrinterSettingsPreferred() const
+{
+ return maJobSetup.ImplGetConstData().GetPapersizeFromSetup();
+}
+
+// dear loplugins, DO NOT REMOVE this code
+// it will be used in follow-up commits
+void Printer::SetPrinterSettingsPreferred( bool bPaperSizeFromSetup)
+{
+ if ( maJobSetup.ImplGetConstData().GetPapersizeFromSetup() != bPaperSizeFromSetup )
+ {
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+ rData.SetPapersizeFromSetup(bPaperSizeFromSetup);
+
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ }
+}
+
+// Map user paper format to an available printer paper format
+void Printer::ImplFindPaperFormatForUserSize( JobSetup& aJobSetup )
+{
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+
+ // The angle that a landscape page will be turned counterclockwise wrt to portrait.
+ int nLandscapeAngle = mpInfoPrinter ? mpInfoPrinter->GetLandscapeAngle( &maJobSetup.ImplGetConstData() ) : 900;
+ int nPaperCount = GetPaperInfoCount();
+ PaperInfo aInfo(rData.GetPaperWidth(), rData.GetPaperHeight());
+
+ // Compare all paper formats and get the appropriate one
+ for ( int i = 0; i < nPaperCount; i++ )
+ {
+ const PaperInfo& rPaperInfo = GetPaperInfo( i );
+
+ if ( aInfo.sloppyEqual(rPaperInfo) )
+ {
+ rData.SetPaperFormat(
+ ImplGetPaperFormat( rPaperInfo.getWidth(),
+ rPaperInfo.getHeight() ));
+ rData.SetOrientation( Orientation::Portrait );
+ return;
+ }
+ }
+
+ // If the printer supports landscape orientation, check paper sizes again
+ // with landscape orientation. This is necessary as a printer driver provides
+ // all paper sizes with portrait orientation only!!
+ if ( !(rData.GetPaperFormat() == PAPER_USER &&
+ nLandscapeAngle != 0 &&
+ HasSupport( PrinterSupport::SetOrientation )))
+ return;
+
+ const tools::Long nRotatedWidth = rData.GetPaperHeight();
+ const tools::Long nRotatedHeight = rData.GetPaperWidth();
+ PaperInfo aRotatedInfo(nRotatedWidth, nRotatedHeight);
+
+ for ( int i = 0; i < nPaperCount; i++ )
+ {
+ const PaperInfo& rPaperInfo = GetPaperInfo( i );
+
+ if ( aRotatedInfo.sloppyEqual( rPaperInfo ) )
+ {
+ rData.SetPaperFormat(
+ ImplGetPaperFormat( rPaperInfo.getWidth(),
+ rPaperInfo.getHeight() ));
+ rData.SetOrientation( Orientation::Landscape );
+ return;
+ }
+ }
+}
+
+void Printer::SetPaper( Paper ePaper )
+{
+ if ( mbInPrintPage )
+ return;
+
+ if ( maJobSetup.ImplGetConstData().GetPaperFormat() == ePaper )
+ return;
+
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+
+ rData.SetPaperFormat( ePaper );
+ if ( ePaper != PAPER_USER )
+ {
+ PaperInfo aInfo(ePaper);
+ rData.SetPaperWidth( aInfo.getWidth() );
+ rData.SetPaperHeight( aInfo.getHeight() );
+ }
+
+ if ( IsDisplayPrinter() )
+ {
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ return;
+ }
+
+ ReleaseGraphics();
+ if ( ePaper == PAPER_USER )
+ ImplFindPaperFormatForUserSize( aJobSetup );
+ if ( mpInfoPrinter->SetData( JobSetFlags::PAPERSIZE | JobSetFlags::ORIENTATION, &rData ))
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ }
+}
+
+bool Printer::SetPaperSizeUser( const Size& rSize )
+{
+ if ( mbInPrintPage )
+ return false;
+
+ const Size aPixSize = LogicToPixel( rSize );
+ const Size aPageSize = PixelToLogic(aPixSize, MapMode(MapUnit::Map100thMM));
+ bool bNeedToChange(maJobSetup.ImplGetConstData().GetPaperWidth() != aPageSize.Width() ||
+ maJobSetup.ImplGetConstData().GetPaperHeight() != aPageSize.Height());
+
+ if(!bNeedToChange)
+ {
+ // #i122984# only need to change when Paper is different from PAPER_USER and
+ // the mapped Paper which will created below in the call to ImplFindPaperFormatForUserSize
+ // and will replace maJobSetup.ImplGetConstData()->GetPaperFormat(). This leads to
+ // unnecessary JobSetups, e.g. when printing a multi-page fax, but also with
+ // normal print
+ const Paper aPaper = ImplGetPaperFormat(aPageSize.Width(), aPageSize.Height());
+
+ bNeedToChange = maJobSetup.ImplGetConstData().GetPaperFormat() != PAPER_USER &&
+ maJobSetup.ImplGetConstData().GetPaperFormat() != aPaper;
+ }
+
+ if(bNeedToChange)
+ {
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+ rData.SetPaperFormat( PAPER_USER );
+ rData.SetPaperWidth( aPageSize.Width() );
+ rData.SetPaperHeight( aPageSize.Height() );
+ rData.SetOrientation( Orientation::Portrait );
+
+ if ( IsDisplayPrinter() )
+ {
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ return true;
+ }
+
+ ReleaseGraphics();
+ ImplFindPaperFormatForUserSize( aJobSetup );
+
+ // Changing the paper size can also change the orientation!
+ if ( mpInfoPrinter->SetData( JobSetFlags::PAPERSIZE | JobSetFlags::ORIENTATION, &rData ))
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ return true;
+ }
+ else
+ return false;
+ }
+
+ return true;
+}
+
+int Printer::GetPaperInfoCount() const
+{
+ if( ! mpInfoPrinter )
+ return 0;
+ if( ! mpInfoPrinter->m_bPapersInit )
+ mpInfoPrinter->InitPaperFormats( &maJobSetup.ImplGetConstData() );
+ return mpInfoPrinter->m_aPaperFormats.size();
+}
+
+OUString Printer::GetPaperName( Paper ePaper )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maPaperNames.empty() )
+ {
+ // This array must (probably) match exactly the enum Paper in <i18nutil/paper.hxx>
+ static const int PaperIndex[] =
+ {
+ PAPER_A0, PAPER_A1, PAPER_A2, PAPER_A3, PAPER_A4, PAPER_A5, PAPER_B4_ISO, PAPER_B5_ISO,
+ PAPER_LETTER, PAPER_LEGAL, PAPER_TABLOID, PAPER_USER, PAPER_B6_ISO, PAPER_ENV_C4, PAPER_ENV_C5,
+ PAPER_ENV_C6, PAPER_ENV_C65, PAPER_ENV_DL, PAPER_SLIDE_DIA, PAPER_SCREEN_4_3, PAPER_C, PAPER_D,
+ PAPER_E, PAPER_EXECUTIVE, PAPER_FANFOLD_LEGAL_DE, PAPER_ENV_MONARCH, PAPER_ENV_PERSONAL, PAPER_ENV_9,
+ PAPER_ENV_10, PAPER_ENV_11, PAPER_ENV_12, PAPER_KAI16, PAPER_KAI32, PAPER_KAI32BIG, PAPER_B4_JIS,
+ PAPER_B5_JIS, PAPER_B6_JIS, PAPER_LEDGER, PAPER_STATEMENT, PAPER_QUARTO, PAPER_10x14, PAPER_ENV_14,
+ PAPER_ENV_C3, PAPER_ENV_ITALY, PAPER_FANFOLD_US, PAPER_FANFOLD_DE, PAPER_POSTCARD_JP, PAPER_9x11,
+ PAPER_10x11, PAPER_15x11, PAPER_ENV_INVITE, PAPER_A_PLUS, PAPER_B_PLUS, PAPER_LETTER_PLUS, PAPER_A4_PLUS,
+ PAPER_DOUBLEPOSTCARD_JP, PAPER_A6, PAPER_12x11, PAPER_A7, PAPER_A8, PAPER_A9, PAPER_A10, PAPER_B0_ISO,
+ PAPER_B1_ISO, PAPER_B2_ISO, PAPER_B3_ISO, PAPER_B7_ISO, PAPER_B8_ISO, PAPER_B9_ISO, PAPER_B10_ISO,
+ PAPER_ENV_C2, PAPER_ENV_C7, PAPER_ENV_C8, PAPER_ARCHA, PAPER_ARCHB, PAPER_ARCHC, PAPER_ARCHD,
+ PAPER_ARCHE, PAPER_SCREEN_16_9, PAPER_SCREEN_16_10, PAPER_16K_195x270, PAPER_16K_197x273,
+ PAPER_WIDESCREEN, PAPER_ONSCREENSHOW_4_3, PAPER_ONSCREENSHOW_16_9, PAPER_ONSCREENSHOW_16_10
+ };
+ static_assert(SAL_N_ELEMENTS(PaperIndex) == SAL_N_ELEMENTS(RID_STR_PAPERNAMES), "localized paper name count wrong");
+ for (size_t i = 0; i < SAL_N_ELEMENTS(PaperIndex); ++i)
+ pSVData->maPaperNames[PaperIndex[i]] = VclResId(RID_STR_PAPERNAMES[i]);
+ }
+
+ std::unordered_map<int,OUString>::const_iterator it = pSVData->maPaperNames.find( static_cast<int>(ePaper) );
+ return (it != pSVData->maPaperNames.end()) ? it->second : OUString();
+}
+
+const PaperInfo& Printer::GetPaperInfo( int nPaper ) const
+{
+ if( ! mpInfoPrinter )
+ return ImplGetEmptyPaper();
+ if( ! mpInfoPrinter->m_bPapersInit )
+ mpInfoPrinter->InitPaperFormats( &maJobSetup.ImplGetConstData() );
+ if( mpInfoPrinter->m_aPaperFormats.empty() || nPaper < 0 || o3tl::make_unsigned(nPaper) >= mpInfoPrinter->m_aPaperFormats.size() )
+ return ImplGetEmptyPaper();
+ return mpInfoPrinter->m_aPaperFormats[nPaper];
+}
+
+Size Printer::GetPaperSize( int nPaper ) const
+{
+ PaperInfo aInfo = GetPaperInfo( nPaper );
+ return PixelToLogic( Size( aInfo.getWidth(), aInfo.getHeight() ) );
+}
+
+void Printer::SetDuplexMode( DuplexMode eDuplex )
+{
+ if ( mbInPrintPage )
+ return;
+
+ if ( maJobSetup.ImplGetConstData().GetDuplexMode() == eDuplex )
+ return;
+
+ JobSetup aJobSetup = maJobSetup;
+ ImplJobSetup& rData = aJobSetup.ImplGetData();
+
+ rData.SetDuplexMode( eDuplex );
+
+ if ( IsDisplayPrinter() )
+ {
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ return;
+ }
+
+ ReleaseGraphics();
+ if ( mpInfoPrinter->SetData( JobSetFlags::DUPLEXMODE, &rData ) )
+ {
+ ImplUpdateJobSetupPaper( aJobSetup );
+ mbNewJobSetup = true;
+ maJobSetup = aJobSetup;
+ ImplUpdatePageData();
+ ImplUpdateFontList();
+ }
+}
+
+DuplexMode Printer::GetDuplexMode() const
+{
+ return maJobSetup.ImplGetConstData().GetDuplexMode();
+}
+
+Paper Printer::GetPaper() const
+{
+ return maJobSetup.ImplGetConstData().GetPaperFormat();
+}
+
+Size Printer::GetSizeOfPaper() const
+{
+ return Size(maJobSetup.ImplGetConstData().GetPaperWidth(), maJobSetup.ImplGetConstData().GetPaperHeight());
+}
+
+sal_uInt16 Printer::GetPaperBinCount() const
+{
+ if ( IsDisplayPrinter() )
+ return 0;
+
+ return mpInfoPrinter->GetPaperBinCount( &maJobSetup.ImplGetConstData() );
+}
+
+OUString Printer::GetPaperBinName( sal_uInt16 nPaperBin ) const
+{
+ if ( IsDisplayPrinter() )
+ return OUString();
+
+ if ( nPaperBin < GetPaperBinCount() )
+ return mpInfoPrinter->GetPaperBinName( &maJobSetup.ImplGetConstData(), nPaperBin );
+ else
+ return OUString();
+}
+
+void Printer::SetCopyCount( sal_uInt16 nCopy, bool bCollate )
+{
+ mnCopyCount = nCopy;
+ mbCollateCopy = bCollate;
+}
+
+ErrCode Printer::ImplSalPrinterErrorCodeToVCL( SalPrinterError nError )
+{
+ ErrCode nVCLError;
+ switch ( nError )
+ {
+ case SalPrinterError::NONE:
+ nVCLError = ERRCODE_NONE;
+ break;
+ case SalPrinterError::Abort:
+ nVCLError = PRINTER_ABORT;
+ break;
+ default:
+ nVCLError = PRINTER_GENERALERROR;
+ break;
+ }
+
+ return nVCLError;
+}
+
+void Printer::EndJob()
+{
+ if ( !IsJobActive() )
+ return;
+
+ SAL_WARN_IF( mbInPrintPage, "vcl.gdi", "Printer::EndJob() - StartPage() without EndPage() called" );
+
+ mbJobActive = false;
+
+ if ( mpPrinter )
+ {
+ ReleaseGraphics();
+
+ mbPrinting = false;
+
+ mbDevOutput = false;
+ mpPrinter->EndJob();
+ mpPrinter.reset();
+ }
+}
+
+void Printer::ImplStartPage()
+{
+ if ( !IsJobActive() )
+ return;
+
+ if ( !mpPrinter )
+ return;
+
+ SalGraphics* pGraphics = mpPrinter->StartPage( &maJobSetup.ImplGetData(),
+ mbNewJobSetup );
+ if ( pGraphics )
+ {
+ ReleaseGraphics();
+ mpJobGraphics = pGraphics;
+ }
+ mbDevOutput = true;
+
+ // PrintJob not aborted ???
+ if ( IsJobActive() )
+ mbInPrintPage = true;
+}
+
+void Printer::ImplEndPage()
+{
+ if ( !IsJobActive() )
+ return;
+
+ mbInPrintPage = false;
+
+ if ( mpPrinter )
+ {
+ ReleaseGraphics();
+ mpPrinter->EndPage();
+ mbDevOutput = false;
+
+ mpJobGraphics = nullptr;
+ mbNewJobSetup = false;
+ }
+}
+
+void Printer::updatePrinters()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ ImplPrnQueueList* pPrnList = pSVData->maGDIData.mpPrinterQueueList.get();
+
+ if ( !pPrnList )
+ return;
+
+ std::unique_ptr<ImplPrnQueueList> pNewList(new ImplPrnQueueList);
+ pSVData->mpDefInst->GetPrinterQueueInfo( pNewList.get() );
+
+ bool bChanged = pPrnList->m_aQueueInfos.size() != pNewList->m_aQueueInfos.size();
+ for( decltype(pPrnList->m_aQueueInfos)::size_type i = 0; ! bChanged && i < pPrnList->m_aQueueInfos.size(); i++ )
+ {
+ ImplPrnQueueData& rInfo = pPrnList->m_aQueueInfos[i];
+ ImplPrnQueueData& rNewInfo = pNewList->m_aQueueInfos[i];
+ if( ! rInfo.mpSalQueueInfo || ! rNewInfo.mpSalQueueInfo || // sanity check
+ rInfo.mpSalQueueInfo->maPrinterName != rNewInfo.mpSalQueueInfo->maPrinterName )
+ {
+ bChanged = true;
+ }
+ }
+ if( !bChanged )
+ return;
+
+ ImplDeletePrnQueueList();
+ pSVData->maGDIData.mpPrinterQueueList = std::move(pNewList);
+
+ Application* pApp = GetpApp();
+ if( pApp )
+ {
+ DataChangedEvent aDCEvt( DataChangedEventType::PRINTER );
+ Application::ImplCallEventListenersApplicationDataChanged(&aDCEvt);
+ Application::NotifyAllWindows( aDCEvt );
+ }
+}
+
+bool Printer::UsePolyPolygonForComplexGradient()
+{
+ return true;
+}
+
+void Printer::ClipAndDrawGradientMetafile ( const Gradient &rGradient, const tools::PolyPolygon &rPolyPoly )
+{
+ const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
+
+ Push( vcl::PushFlags::CLIPREGION );
+ IntersectClipRegion(vcl::Region(rPolyPoly));
+ DrawGradient( aBoundRect, rGradient );
+ Pop();
+}
+
+void Printer::SetFontOrientation( LogicalFontInstance* const pFontEntry ) const
+{
+ pFontEntry->mnOrientation = pFontEntry->mxFontMetric->GetOrientation();
+}
+
+vcl::Region Printer::ClipToDeviceBounds(vcl::Region aRegion) const
+{
+ return aRegion;
+}
+
+Bitmap Printer::GetBitmap( const Point& rSrcPt, const Size& rSize ) const
+{
+ SAL_WARN("vcl.gdi", "GetBitmap(): This should never be called on by a Printer instance");
+
+ return OutputDevice::GetBitmap( rSrcPt, rSize );
+}
+
+css::awt::DeviceInfo Printer::GetDeviceInfo() const
+{
+ Size aDevSz = GetPaperSizePixel();
+ css::awt::DeviceInfo aInfo = GetCommonDeviceInfo(aDevSz);
+ Size aOutSz = GetOutputSizePixel();
+ Point aOffset = GetPageOffset();
+ aInfo.LeftInset = aOffset.X();
+ aInfo.TopInset = aOffset.Y();
+ aInfo.RightInset = aDevSz.Width() - aOutSz.Width() - aOffset.X();
+ aInfo.BottomInset = aDevSz.Height() - aOutSz.Height() - aOffset.Y();
+ aInfo.Capabilities = 0;
+
+ return aInfo;
+}
+
+void Printer::SetWaveLineColors(Color const& rColor, tools::Long)
+{
+ if (mbLineColor || mbInitLineColor)
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+
+ mpGraphics->SetFillColor(rColor);
+ mbInitFillColor = true;
+}
+
+Size Printer::GetWaveLineSize(tools::Long nLineWidth) const
+{
+ // FIXME - do we have a bug here? If the linewidth is 0, then we will return
+ // Size(0, 0) - is this correct?
+ return Size(nLineWidth, ((nLineWidth*mnDPIX)+(mnDPIY/2))/mnDPIY);
+}
+
+void Printer::SetSystemTextColor(SystemTextColorFlags, bool)
+{
+ SetTextColor(COL_BLACK);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/gdi/print2.cxx b/vcl/source/gdi/print2.cxx
new file mode 100644
index 0000000000..85b39c683d
--- /dev/null
+++ b/vcl/source/gdi/print2.cxx
@@ -0,0 +1,70 @@
+/* -*- 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 <sal/log.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/print.hxx>
+#include <vcl/printer/Options.hxx>
+
+#include <utility>
+#include <vector>
+
+void Printer::DrawGradientEx( OutputDevice* pOut, const tools::Rectangle& rRect, const Gradient& rGradient )
+{
+ const vcl::printer::Options& rPrinterOptions = GetPrinterOptions();
+
+ if( rPrinterOptions.IsReduceGradients() )
+ {
+ if( vcl::printer::GradientMode::Stripes == rPrinterOptions.GetReducedGradientMode() )
+ {
+ if( !rGradient.GetSteps() || ( rGradient.GetSteps() > rPrinterOptions.GetReducedGradientStepCount() ) )
+ {
+ Gradient aNewGradient( rGradient );
+
+ aNewGradient.SetSteps( rPrinterOptions.GetReducedGradientStepCount() );
+ pOut->DrawGradient( rRect, aNewGradient );
+ }
+ else
+ pOut->DrawGradient( rRect, rGradient );
+ }
+ else
+ {
+ const Color& rStartColor = rGradient.GetStartColor();
+ const Color& rEndColor = rGradient.GetEndColor();
+ const tools::Long nR = ( ( static_cast<tools::Long>(rStartColor.GetRed()) * rGradient.GetStartIntensity() ) / 100 +
+ ( static_cast<tools::Long>(rEndColor.GetRed()) * rGradient.GetEndIntensity() ) / 100 ) >> 1;
+ const tools::Long nG = ( ( static_cast<tools::Long>(rStartColor.GetGreen()) * rGradient.GetStartIntensity() ) / 100 +
+ ( static_cast<tools::Long>(rEndColor.GetGreen()) * rGradient.GetEndIntensity() ) / 100 ) >> 1;
+ const tools::Long nB = ( ( static_cast<tools::Long>(rStartColor.GetBlue()) * rGradient.GetStartIntensity() ) / 100 +
+ ( static_cast<tools::Long>(rEndColor.GetBlue()) * rGradient.GetEndIntensity() ) / 100 ) >> 1;
+ const Color aColor( static_cast<sal_uInt8>(nR), static_cast<sal_uInt8>(nG), static_cast<sal_uInt8>(nB) );
+
+ pOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
+ pOut->SetLineColor( aColor );
+ pOut->SetFillColor( aColor );
+ pOut->DrawRect( rRect );
+ pOut->Pop();
+ }
+ }
+ else
+ pOut->DrawGradient( rRect, rGradient );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/print3.cxx b/vcl/source/gdi/print3.cxx
new file mode 100644
index 0000000000..1ebab3b481
--- /dev/null
+++ b/vcl/source/gdi/print3.cxx
@@ -0,0 +1,2156 @@
+/* -*- 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 <sal/types.h>
+#include <sal/log.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/sequence.hxx>
+#include <o3tl/safeint.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/debug.hxx>
+#include <tools/urlobj.hxx>
+
+#include <utility>
+#include <vcl/metaact.hxx>
+#include <vcl/print.hxx>
+#include <vcl/printer/Options.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+
+#include <configsettings.hxx>
+#include <printdlg.hxx>
+#include <salinst.hxx>
+#include <salprn.hxx>
+#include <strings.hrc>
+#include <svdata.hxx>
+
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/ui/dialogs/FilePicker.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/view/DuplexMode.hpp>
+
+#include <unordered_map>
+#include <unordered_set>
+
+using namespace vcl;
+
+namespace {
+
+class ImplPageCache
+{
+ struct CacheEntry
+ {
+ GDIMetaFile aPage;
+ PrinterController::PageSize aSize;
+ };
+
+ std::vector< CacheEntry > maPages;
+ std::vector< sal_Int32 > maPageNumbers;
+ std::vector< sal_Int32 > maCacheRanking;
+
+ static const sal_Int32 nCacheSize = 6;
+
+ void updateRanking( sal_Int32 nLastHit )
+ {
+ if( maCacheRanking[0] != nLastHit )
+ {
+ for( sal_Int32 i = nCacheSize-1; i > 0; i-- )
+ maCacheRanking[i] = maCacheRanking[i-1];
+ maCacheRanking[0] = nLastHit;
+ }
+ }
+
+public:
+ ImplPageCache()
+ : maPages( nCacheSize )
+ , maPageNumbers( nCacheSize, -1 )
+ , maCacheRanking( nCacheSize )
+ {
+ for( sal_Int32 i = 0; i < nCacheSize; i++ )
+ maCacheRanking[i] = nCacheSize - i - 1;
+ }
+
+ // caution: does not ensure uniqueness
+ void insert( sal_Int32 i_nPageNo, const GDIMetaFile& i_rPage, const PrinterController::PageSize& i_rSize )
+ {
+ sal_Int32 nReplacePage = maCacheRanking.back();
+ maPages[ nReplacePage ].aPage = i_rPage;
+ maPages[ nReplacePage ].aSize = i_rSize;
+ maPageNumbers[ nReplacePage ] = i_nPageNo;
+ // cache insertion means in our case, the page was just queried
+ // so update the ranking
+ updateRanking( nReplacePage );
+ }
+
+ // caution: bad algorithm; should there ever be reason to increase the cache size beyond 6
+ // this needs to be urgently rewritten. However do NOT increase the cache size lightly,
+ // whole pages can be rather memory intensive
+ bool get( sal_Int32 i_nPageNo, GDIMetaFile& o_rPageFile, PrinterController::PageSize& o_rSize )
+ {
+ for( sal_Int32 i = 0; i < nCacheSize; ++i )
+ {
+ if( maPageNumbers[i] == i_nPageNo )
+ {
+ updateRanking( i );
+ o_rPageFile = maPages[i].aPage;
+ o_rSize = maPages[i].aSize;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void invalidate()
+ {
+ for( sal_Int32 i = 0; i < nCacheSize; ++i )
+ {
+ maPageNumbers[i] = -1;
+ maPages[i].aPage.Clear();
+ maCacheRanking[i] = nCacheSize - i - 1;
+ }
+ }
+};
+
+}
+
+class vcl::ImplPrinterControllerData
+{
+public:
+ struct ControlDependency
+ {
+ OUString maDependsOnName;
+ sal_Int32 mnDependsOnEntry;
+
+ ControlDependency() : mnDependsOnEntry( -1 ) {}
+ };
+
+ typedef std::unordered_map< OUString, size_t > PropertyToIndexMap;
+ typedef std::unordered_map< OUString, ControlDependency > ControlDependencyMap;
+ typedef std::unordered_map< OUString, css::uno::Sequence< sal_Bool > > ChoiceDisableMap;
+
+ VclPtr< Printer > mxPrinter;
+ weld::Window* mpWindow;
+ css::uno::Sequence< css::beans::PropertyValue > maUIOptions;
+ std::vector< css::beans::PropertyValue > maUIProperties;
+ std::vector< bool > maUIPropertyEnabled;
+ PropertyToIndexMap maPropertyToIndex;
+ ControlDependencyMap maControlDependencies;
+ ChoiceDisableMap maChoiceDisableMap;
+ bool mbFirstPage;
+ bool mbLastPage;
+ bool mbReversePageOrder;
+ bool mbPapersizeFromSetup;
+ bool mbPapersizeFromUser;
+ bool mbOrientationFromUser;
+ bool mbPrinterModified;
+ css::view::PrintableState meJobState;
+
+ vcl::PrinterController::MultiPageSetup maMultiPage;
+
+ std::shared_ptr<vcl::PrintProgressDialog> mxProgress;
+
+ ImplPageCache maPageCache;
+
+ // set by user through printer properties subdialog of printer settings dialog
+ Size maDefaultPageSize;
+ // set by user through print dialog
+ Size maUserPageSize;
+ // set by user through print dialog
+ Orientation meUserOrientation;
+ // set by user through printer properties subdialog of printer settings dialog
+ sal_Int32 mnDefaultPaperBin;
+ // Set by user through printer properties subdialog of print dialog.
+ // Overrides application-set tray for a page.
+ sal_Int32 mnFixedPaperBin;
+
+ // N.B. Apparently we have three levels of paper tray settings
+ // (latter overrides former):
+ // 1. default tray
+ // 2. tray set for a concrete page by an application, e.g., writer
+ // allows setting a printer tray (for the default printer) for a
+ // page style. This setting can be overridden by user by selecting
+ // "Use only paper tray from printer preferences" on the Options
+ // page in the print dialog, in which case the default tray is
+ // used for all pages.
+ // 3. tray set in printer properties the printer dialog
+ // I'm not quite sure why 1. and 3. are distinct, but the commit
+ // history suggests this is intentional...
+
+ ImplPrinterControllerData() :
+ mpWindow( nullptr ),
+ mbFirstPage( true ),
+ mbLastPage( false ),
+ mbReversePageOrder( false ),
+ mbPapersizeFromSetup( false ),
+ mbPapersizeFromUser( false ),
+ mbOrientationFromUser( false ),
+ mbPrinterModified( false ),
+ meJobState( css::view::PrintableState_JOB_STARTED ),
+ meUserOrientation( Orientation::Portrait ),
+ mnDefaultPaperBin( -1 ),
+ mnFixedPaperBin( -1 )
+ {}
+
+ ~ImplPrinterControllerData()
+ {
+ if (mxProgress)
+ {
+ mxProgress->response(RET_CANCEL);
+ mxProgress.reset();
+ }
+ }
+
+ Size getRealPaperSize( const Size& i_rPageSize, bool bNoNUP ) const
+ {
+ Size size;
+ if ( mbPapersizeFromUser )
+ size = maUserPageSize;
+ else if( mbPapersizeFromSetup )
+ size = maDefaultPageSize;
+ else if( maMultiPage.nRows * maMultiPage.nColumns > 1 && ! bNoNUP )
+ size = maMultiPage.aPaperSize;
+ else
+ size = i_rPageSize;
+ if(mbOrientationFromUser)
+ {
+ if ( (meUserOrientation == Orientation::Portrait && size.Width() > size.Height()) ||
+ (meUserOrientation == Orientation::Landscape && size.Width() < size.Height()) )
+ {
+ // coverity[swapped_arguments : FALSE] - this is in the correct order
+ size = Size( size.Height(), size.Width() );
+ }
+ }
+ return size;
+ }
+ PrinterController::PageSize modifyJobSetup( const css::uno::Sequence< css::beans::PropertyValue >& i_rProps );
+ void resetPaperToLastConfigured();
+};
+
+PrinterController::PrinterController(const VclPtr<Printer>& i_xPrinter, weld::Window* i_pWindow)
+ : mpImplData( new ImplPrinterControllerData )
+{
+ mpImplData->mxPrinter = i_xPrinter;
+ mpImplData->mpWindow = i_pWindow;
+}
+
+static OUString queryFile( Printer const * pPrinter )
+{
+ OUString aResult;
+
+ css::uno::Reference< css::uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+ css::uno::Reference< css::ui::dialogs::XFilePicker3 > xFilePicker = css::ui::dialogs::FilePicker::createWithMode(xContext, css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION);
+
+ try
+ {
+#ifdef UNX
+ // add PostScript and PDF
+ bool bPS = true, bPDF = true;
+ if( pPrinter )
+ {
+ if( pPrinter->GetCapabilities( PrinterCapType::PDF ) )
+ bPS = false;
+ else
+ bPDF = false;
+ }
+ if( bPS )
+ xFilePicker->appendFilter( "PostScript", "*.ps" );
+ if( bPDF )
+ xFilePicker->appendFilter( "Portable Document Format", "*.pdf" );
+#elif defined _WIN32
+ (void)pPrinter;
+ xFilePicker->appendFilter( "*.PRN", "*.prn" );
+#endif
+ // add arbitrary files
+ xFilePicker->appendFilter(VclResId(SV_STDTEXT_ALLFILETYPES), "*.*");
+ }
+ catch (const css::lang::IllegalArgumentException&)
+ {
+ TOOLS_WARN_EXCEPTION( "vcl.gdi", "caught IllegalArgumentException when registering filter" );
+ }
+
+ if( xFilePicker->execute() == css::ui::dialogs::ExecutableDialogResults::OK )
+ {
+ css::uno::Sequence< OUString > aPathSeq( xFilePicker->getSelectedFiles() );
+ INetURLObject aObj( aPathSeq[0] );
+ aResult = aObj.PathToFileName();
+ }
+ return aResult;
+}
+
+namespace {
+
+struct PrintJobAsync
+{
+ std::shared_ptr<PrinterController> mxController;
+ JobSetup maInitSetup;
+
+ PrintJobAsync(std::shared_ptr<PrinterController> i_xController,
+ const JobSetup& i_rInitSetup)
+ : mxController(std::move( i_xController )), maInitSetup( i_rInitSetup )
+ {}
+
+ DECL_LINK( ExecJob, void*, void );
+};
+
+}
+
+IMPL_LINK_NOARG(PrintJobAsync, ExecJob, void*, void)
+{
+ Printer::ImplPrintJob(mxController, maInitSetup);
+
+ // clean up, do not access members after this
+ delete this;
+}
+
+void Printer::PrintJob(const std::shared_ptr<PrinterController>& i_xController,
+ const JobSetup& i_rInitSetup)
+{
+ bool bSynchronous = false;
+ css::beans::PropertyValue* pVal = i_xController->getValue( "Wait" );
+ if( pVal )
+ pVal->Value >>= bSynchronous;
+
+ if( bSynchronous )
+ ImplPrintJob(i_xController, i_rInitSetup);
+ else
+ {
+ PrintJobAsync* pAsync = new PrintJobAsync(i_xController, i_rInitSetup);
+ Application::PostUserEvent( LINK( pAsync, PrintJobAsync, ExecJob ) );
+ }
+}
+
+bool Printer::PreparePrintJob(std::shared_ptr<PrinterController> xController,
+ const JobSetup& i_rInitSetup)
+{
+ // check if there is a default printer; if not, show an error box (if appropriate)
+ if( GetDefaultPrinterName().isEmpty() )
+ {
+ if (xController->isShowDialogs())
+ {
+ std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(xController->getWindow(), "vcl/ui/errornoprinterdialog.ui"));
+ std::unique_ptr<weld::MessageDialog> xBox(xBuilder->weld_message_dialog("ErrorNoPrinterDialog"));
+ xBox->run();
+ }
+ xController->setValue( "IsDirect",
+ css::uno::Any( false ) );
+ }
+
+ // setup printer
+
+ // #i114306# changed behavior back from persistence
+ // if no specific printer is already set, create the default printer
+ if (!xController->getPrinter())
+ {
+ OUString aPrinterName( i_rInitSetup.GetPrinterName() );
+ VclPtrInstance<Printer> xPrinter( aPrinterName );
+ xPrinter->SetJobSetup(i_rInitSetup);
+ xController->setPrinter(xPrinter);
+ xController->setPapersizeFromSetup(xPrinter->GetPrinterSettingsPreferred());
+ }
+
+ // reset last page property
+ xController->setLastPage(false);
+
+ // update "PageRange" property inferring from other properties:
+ // case 1: "Pages" set from UNO API ->
+ // setup "Print Selection" and insert "PageRange" attribute
+ // case 2: "All pages" is selected
+ // update "Page range" attribute to have a sensible default,
+ // but leave "All" as selected
+
+ // "Pages" attribute from API is now equivalent to "PageRange"
+ // AND "PrintContent" = 1 except calc where it is "PrintRange" = 1
+ // Argh ! That sure needs cleaning up
+ css::beans::PropertyValue* pContentVal = xController->getValue("PrintRange");
+ if( ! pContentVal )
+ pContentVal = xController->getValue("PrintContent");
+
+ // case 1: UNO API has set "Pages"
+ css::beans::PropertyValue* pPagesVal = xController->getValue("Pages");
+ if( pPagesVal )
+ {
+ OUString aPagesVal;
+ pPagesVal->Value >>= aPagesVal;
+ if( !aPagesVal.isEmpty() )
+ {
+ // "Pages" attribute from API is now equivalent to "PageRange"
+ // AND "PrintContent" = 1 except calc where it is "PrintRange" = 1
+ // Argh ! That sure needs cleaning up
+ if( pContentVal )
+ {
+ pContentVal->Value <<= sal_Int32( 1 );
+ xController->setValue("PageRange", pPagesVal->Value);
+ }
+ }
+ }
+ // case 2: is "All" selected ?
+ else if( pContentVal )
+ {
+ sal_Int32 nContent = -1;
+ if( pContentVal->Value >>= nContent )
+ {
+ if( nContent == 0 )
+ {
+ // do not overwrite PageRange if it is already set
+ css::beans::PropertyValue* pRangeVal = xController->getValue("PageRange");
+ OUString aRange;
+ if( pRangeVal )
+ pRangeVal->Value >>= aRange;
+ if( aRange.isEmpty() )
+ {
+ sal_Int32 nPages = xController->getPageCount();
+ if( nPages > 0 )
+ {
+ OUStringBuffer aBuf( 32 );
+ aBuf.append( "1" );
+ if( nPages > 1 )
+ {
+ aBuf.append( "-" + OUString::number( nPages ) );
+ }
+ xController->setValue("PageRange", css::uno::Any(aBuf.makeStringAndClear()));
+ }
+ }
+ }
+ }
+ }
+
+ css::beans::PropertyValue* pReverseVal = xController->getValue("PrintReverse");
+ if( pReverseVal )
+ {
+ bool bReverse = false;
+ pReverseVal->Value >>= bReverse;
+ xController->setReversePrint( bReverse );
+ }
+
+ css::beans::PropertyValue* pPapersizeFromSetupVal = xController->getValue("PapersizeFromSetup");
+ if( pPapersizeFromSetupVal )
+ {
+ bool bPapersizeFromSetup = false;
+ pPapersizeFromSetupVal->Value >>= bPapersizeFromSetup;
+ xController->setPapersizeFromSetup(bPapersizeFromSetup);
+ }
+
+ // setup NUp printing from properties
+ sal_Int32 nRows = xController->getIntProperty("NUpRows", 1);
+ sal_Int32 nCols = xController->getIntProperty("NUpColumns", 1);
+ if( nRows > 1 || nCols > 1 )
+ {
+ PrinterController::MultiPageSetup aMPS;
+ aMPS.nRows = std::max<sal_Int32>(nRows, 1);
+ aMPS.nColumns = std::max<sal_Int32>(nCols, 1);
+ sal_Int32 nValue = xController->getIntProperty("NUpPageMarginLeft", aMPS.nLeftMargin);
+ if( nValue >= 0 )
+ aMPS.nLeftMargin = nValue;
+ nValue = xController->getIntProperty("NUpPageMarginRight", aMPS.nRightMargin);
+ if( nValue >= 0 )
+ aMPS.nRightMargin = nValue;
+ nValue = xController->getIntProperty( "NUpPageMarginTop", aMPS.nTopMargin );
+ if( nValue >= 0 )
+ aMPS.nTopMargin = nValue;
+ nValue = xController->getIntProperty( "NUpPageMarginBottom", aMPS.nBottomMargin );
+ if( nValue >= 0 )
+ aMPS.nBottomMargin = nValue;
+ nValue = xController->getIntProperty( "NUpHorizontalSpacing", aMPS.nHorizontalSpacing );
+ if( nValue >= 0 )
+ aMPS.nHorizontalSpacing = nValue;
+ nValue = xController->getIntProperty( "NUpVerticalSpacing", aMPS.nVerticalSpacing );
+ if( nValue >= 0 )
+ aMPS.nVerticalSpacing = nValue;
+ aMPS.bDrawBorder = xController->getBoolProperty( "NUpDrawBorder", aMPS.bDrawBorder );
+ aMPS.nOrder = static_cast<NupOrderType>(xController->getIntProperty( "NUpSubPageOrder", static_cast<sal_Int32>(aMPS.nOrder) ));
+ aMPS.aPaperSize = xController->getPrinter()->PixelToLogic( xController->getPrinter()->GetPaperSizePixel(), MapMode( MapUnit::Map100thMM ) );
+ css::beans::PropertyValue* pPgSizeVal = xController->getValue( "NUpPaperSize" );
+ css::awt::Size aSizeVal;
+ if( pPgSizeVal && (pPgSizeVal->Value >>= aSizeVal) )
+ {
+ aMPS.aPaperSize.setWidth( aSizeVal.Width );
+ aMPS.aPaperSize.setHeight( aSizeVal.Height );
+ }
+
+ xController->setMultipage( aMPS );
+ }
+
+ // in direct print case check whether there is anything to print.
+ // if not, show an errorbox (if appropriate)
+ if( xController->isShowDialogs() && xController->isDirectPrint() )
+ {
+ if( xController->getFilteredPageCount() == 0 )
+ {
+ std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(xController->getWindow(), "vcl/ui/errornocontentdialog.ui"));
+ std::unique_ptr<weld::MessageDialog> xBox(xBuilder->weld_message_dialog("ErrorNoContentDialog"));
+ xBox->run();
+ return false;
+ }
+ }
+
+ // check if the printer brings up its own dialog
+ // in that case leave the work to that dialog
+ if( ! xController->getPrinter()->GetCapabilities( PrinterCapType::ExternalDialog ) &&
+ ! xController->isDirectPrint() &&
+ xController->isShowDialogs()
+ )
+ {
+ try
+ {
+ PrintDialog aDlg(xController->getWindow(), xController);
+ if (!aDlg.run())
+ {
+ xController->abortJob();
+ return false;
+ }
+ if (aDlg.isPrintToFile())
+ {
+ OUString aFile = queryFile( xController->getPrinter().get() );
+ if( aFile.isEmpty() )
+ {
+ xController->abortJob();
+ return false;
+ }
+ xController->setValue( "LocalFileName",
+ css::uno::Any( aFile ) );
+ }
+ else if (aDlg.isSingleJobs())
+ {
+ xController->getPrinter()->SetSinglePrintJobs(true);
+ }
+ }
+ catch (const std::bad_alloc&)
+ {
+ }
+ }
+#ifdef MACOSX
+ else
+ {
+ // The PrintDialog updates the printer list in its constructor so do
+ // the same for printers that bring up their own dialog since. Not
+ // sure if this is needed or not on Windows or X11, so limit only to
+ // macOS for now.
+ Printer::updatePrinters();
+ }
+#endif
+
+ xController->pushPropertiesToPrinter();
+ return true;
+}
+
+bool Printer::ExecutePrintJob(const std::shared_ptr<PrinterController>& xController)
+{
+ OUString aJobName;
+ css::beans::PropertyValue* pJobNameVal = xController->getValue( "JobName" );
+ if( pJobNameVal )
+ pJobNameVal->Value >>= aJobName;
+
+ return xController->getPrinter()->StartJob( aJobName, xController );
+}
+
+void Printer::FinishPrintJob(const std::shared_ptr<PrinterController>& xController)
+{
+ xController->resetPaperToLastConfigured();
+ xController->jobFinished( xController->getJobState() );
+}
+
+void Printer::ImplPrintJob(const std::shared_ptr<PrinterController>& xController,
+ const JobSetup& i_rInitSetup)
+{
+ if (PreparePrintJob(xController, i_rInitSetup))
+ {
+ ExecutePrintJob(xController);
+ }
+ FinishPrintJob(xController);
+}
+
+bool Printer::StartJob( const OUString& i_rJobName, std::shared_ptr<vcl::PrinterController> const & i_xController)
+{
+ mnError = ERRCODE_NONE;
+
+ if ( IsDisplayPrinter() )
+ return false;
+
+ if ( IsJobActive() || IsPrinting() )
+ return false;
+
+ sal_uInt32 nCopies = mnCopyCount;
+ bool bCollateCopy = mbCollateCopy;
+ bool bUserCopy = false;
+
+ if ( nCopies > 1 )
+ {
+ const sal_uInt32 nDevCopy = GetCapabilities( bCollateCopy
+ ? PrinterCapType::CollateCopies
+ : PrinterCapType::Copies );
+
+ // need to do copies by hand ?
+ if ( nCopies > nDevCopy )
+ {
+ bUserCopy = true;
+ nCopies = 1;
+ bCollateCopy = false;
+ }
+ }
+ else
+ bCollateCopy = false;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ mpPrinter = pSVData->mpDefInst->CreatePrinter( mpInfoPrinter );
+
+ if (!mpPrinter)
+ return false;
+
+ bool bSinglePrintJobs = i_xController->getPrinter()->IsSinglePrintJobs();
+
+ css::beans::PropertyValue* pFileValue = i_xController->getValue("LocalFileName");
+ if( pFileValue )
+ {
+ OUString aFile;
+ pFileValue->Value >>= aFile;
+ if( !aFile.isEmpty() )
+ {
+ mbPrintFile = true;
+ maPrintFile = aFile;
+ bSinglePrintJobs = false;
+ }
+ }
+
+ OUString* pPrintFile = nullptr;
+ if ( mbPrintFile )
+ pPrintFile = &maPrintFile;
+ mpPrinterOptions->ReadFromConfig( mbPrintFile );
+
+ mbPrinting = true;
+ if( GetCapabilities( PrinterCapType::UsePullModel ) )
+ {
+ mbJobActive = true;
+ // SAL layer does all necessary page printing
+ // and also handles showing a dialog
+ // that also means it must call jobStarted when the dialog is finished
+ // it also must set the JobState of the Controller
+ if( mpPrinter->StartJob( pPrintFile,
+ i_rJobName,
+ Application::GetDisplayName(),
+ &maJobSetup.ImplGetData(),
+ *i_xController) )
+ {
+ EndJob();
+ }
+ else
+ {
+ mnError = ImplSalPrinterErrorCodeToVCL(mpPrinter->GetErrorCode());
+ if ( !mnError )
+ mnError = PRINTER_GENERALERROR;
+ mbPrinting = false;
+ mpPrinter.reset();
+ mbJobActive = false;
+
+ GDIMetaFile aDummyFile;
+ i_xController->setLastPage(true);
+ i_xController->getFilteredPageFile(0, aDummyFile);
+
+ return false;
+ }
+ }
+ else
+ {
+ // possibly a dialog has been shown
+ // now the real job starts
+ i_xController->setJobState( css::view::PrintableState_JOB_STARTED );
+ i_xController->jobStarted();
+
+ int nJobs = 1;
+ int nOuterRepeatCount = 1;
+ int nInnerRepeatCount = 1;
+ if( bUserCopy )
+ {
+ if( mbCollateCopy )
+ nOuterRepeatCount = mnCopyCount;
+ else
+ nInnerRepeatCount = mnCopyCount;
+ }
+ if( bSinglePrintJobs )
+ {
+ nJobs = mnCopyCount;
+ nCopies = 1;
+ nOuterRepeatCount = nInnerRepeatCount = 1;
+ }
+
+ for( int nJobIteration = 0; nJobIteration < nJobs; nJobIteration++ )
+ {
+ bool bError = false;
+ if( mpPrinter->StartJob( pPrintFile,
+ i_rJobName,
+ Application::GetDisplayName(),
+ nCopies,
+ bCollateCopy,
+ i_xController->isDirectPrint(),
+ &maJobSetup.ImplGetData() ) )
+ {
+ bool bAborted = false;
+ mbJobActive = true;
+ i_xController->createProgressDialog();
+ const int nPages = i_xController->getFilteredPageCount();
+ // abort job, if no pages will be printed.
+ if ( nPages == 0 )
+ {
+ i_xController->abortJob();
+ bAborted = true;
+ }
+ for( int nOuterIteration = 0; nOuterIteration < nOuterRepeatCount && ! bAborted; nOuterIteration++ )
+ {
+ for( int nPage = 0; nPage < nPages && ! bAborted; nPage++ )
+ {
+ for( int nInnerIteration = 0; nInnerIteration < nInnerRepeatCount && ! bAborted; nInnerIteration++ )
+ {
+ if( nPage == nPages-1 &&
+ nOuterIteration == nOuterRepeatCount-1 &&
+ nInnerIteration == nInnerRepeatCount-1 &&
+ nJobIteration == nJobs-1 )
+ {
+ i_xController->setLastPage(true);
+ }
+ i_xController->printFilteredPage(nPage);
+ if (i_xController->isProgressCanceled())
+ {
+ i_xController->abortJob();
+ }
+ if (i_xController->getJobState() ==
+ css::view::PrintableState_JOB_ABORTED)
+ {
+ bAborted = true;
+ }
+ }
+ }
+ // FIXME: duplex ?
+ }
+ EndJob();
+
+ if( nJobIteration < nJobs-1 )
+ {
+ mpPrinter = pSVData->mpDefInst->CreatePrinter( mpInfoPrinter );
+
+ if ( mpPrinter )
+ mbPrinting = true;
+ else
+ bError = true;
+ }
+ }
+ else
+ bError = true;
+
+ if( bError )
+ {
+ mnError = mpPrinter ? ImplSalPrinterErrorCodeToVCL(mpPrinter->GetErrorCode()) : ERRCODE_NONE;
+ if ( !mnError )
+ mnError = PRINTER_GENERALERROR;
+ i_xController->setJobState( mnError == PRINTER_ABORT
+ ? css::view::PrintableState_JOB_ABORTED
+ : css::view::PrintableState_JOB_FAILED );
+ mbPrinting = false;
+ mpPrinter.reset();
+
+ return false;
+ }
+ }
+
+ if (i_xController->getJobState() == css::view::PrintableState_JOB_STARTED)
+ i_xController->setJobState(css::view::PrintableState_JOB_SPOOLED);
+ }
+
+ // make last used printer persistent for UI jobs
+ if (i_xController->isShowDialogs() && !i_xController->isDirectPrint())
+ {
+ SettingsConfigItem* pItem = SettingsConfigItem::get();
+ pItem->setValue( "PrintDialog",
+ "LastPrinterUsed",
+ GetName()
+ );
+ }
+
+ return true;
+}
+
+PrinterController::~PrinterController()
+{
+}
+
+css::view::PrintableState PrinterController::getJobState() const
+{
+ return mpImplData->meJobState;
+}
+
+void PrinterController::setJobState( css::view::PrintableState i_eState )
+{
+ mpImplData->meJobState = i_eState;
+}
+
+const VclPtr<Printer>& PrinterController::getPrinter() const
+{
+ return mpImplData->mxPrinter;
+}
+
+weld::Window* PrinterController::getWindow() const
+{
+ return mpImplData->mpWindow;
+}
+
+void PrinterController::dialogsParentClosing()
+{
+ mpImplData->mpWindow = nullptr;
+ if (mpImplData->mxProgress)
+ {
+ // close the dialog without doing anything, just get rid of it
+ mpImplData->mxProgress->response(RET_OK);
+ mpImplData->mxProgress.reset();
+ }
+}
+
+void PrinterController::setPrinter( const VclPtr<Printer>& i_rPrinter )
+{
+ VclPtr<Printer> xPrinter = mpImplData->mxPrinter;
+
+ Size aPaperSize; // Save current paper size
+ Orientation eOrientation = Orientation::Portrait; // Save current paper orientation
+ bool bSavedSizeOrientation = false;
+
+ // #tdf 126744 Transfer paper size and orientation settings to newly selected printer
+ if ( xPrinter )
+ {
+ aPaperSize = xPrinter->GetPaperSize();
+ eOrientation = xPrinter->GetOrientation();
+ bSavedSizeOrientation = true;
+ }
+
+ mpImplData->mxPrinter = i_rPrinter;
+ setValue( "Name",
+ css::uno::Any( i_rPrinter->GetName() ) );
+ mpImplData->mnDefaultPaperBin = mpImplData->mxPrinter->GetPaperBin();
+ mpImplData->mxPrinter->Push();
+ mpImplData->mxPrinter->SetMapMode(MapMode(MapUnit::Map100thMM));
+ mpImplData->maDefaultPageSize = mpImplData->mxPrinter->GetPaperSize();
+
+ if ( bSavedSizeOrientation )
+ {
+ mpImplData->mxPrinter->SetPaperSizeUser(aPaperSize);
+ mpImplData->mxPrinter->SetOrientation(eOrientation);
+ }
+
+ mpImplData->mbPapersizeFromUser = false;
+ mpImplData->mbOrientationFromUser = false;
+ mpImplData->mxPrinter->Pop();
+ mpImplData->mnFixedPaperBin = -1;
+}
+
+void PrinterController::resetPrinterOptions( bool i_bFileOutput )
+{
+ vcl::printer::Options aOpt;
+ aOpt.ReadFromConfig( i_bFileOutput );
+ mpImplData->mxPrinter->SetPrinterOptions( aOpt );
+}
+
+void PrinterController::setupPrinter( weld::Window* i_pParent )
+{
+ bool bRet = false;
+
+ // Important to hold printer alive while doing setup etc.
+ VclPtr< Printer > xPrinter = mpImplData->mxPrinter;
+
+ if( !xPrinter )
+ return;
+
+ xPrinter->Push();
+ xPrinter->SetMapMode(MapMode(MapUnit::Map100thMM));
+
+ // get current data
+ Size aPaperSize(xPrinter->GetPaperSize());
+ Orientation eOrientation = xPrinter->GetOrientation();
+ sal_uInt16 nPaperBin = xPrinter->GetPaperBin();
+
+ // reset paper size back to last configured size, not
+ // whatever happens to be the current page
+ // (but only if the printer config has changed, otherwise
+ // don't override printer page auto-detection - tdf#91362)
+ if (getPrinterModified() || getPapersizeFromSetup())
+ {
+ resetPaperToLastConfigured();
+ }
+
+ // call driver setup
+ bRet = xPrinter->Setup( i_pParent, PrinterSetupMode::SingleJob );
+ SAL_WARN_IF(xPrinter != mpImplData->mxPrinter, "vcl.gdi",
+ "Printer changed underneath us during setup");
+ xPrinter = mpImplData->mxPrinter;
+
+ Size aNewPaperSize(xPrinter->GetPaperSize());
+ if (bRet)
+ {
+ bool bInvalidateCache = false;
+ setPapersizeFromSetup(xPrinter->GetPrinterSettingsPreferred());
+
+ // was papersize overridden ? if so we need to take action if we're
+ // configured to use the driver papersize
+ if (aNewPaperSize != mpImplData->maDefaultPageSize)
+ {
+ mpImplData->maDefaultPageSize = aNewPaperSize;
+ bInvalidateCache = getPapersizeFromSetup();
+ }
+
+ // was bin overridden ? if so we need to take action
+ sal_uInt16 nNewPaperBin = xPrinter->GetPaperBin();
+ if (nNewPaperBin != nPaperBin)
+ {
+ mpImplData->mnFixedPaperBin = nNewPaperBin;
+ bInvalidateCache = true;
+ }
+
+ if (bInvalidateCache)
+ {
+ mpImplData->maPageCache.invalidate();
+ }
+ }
+ else
+ {
+ //restore to whatever it was before we entered this method
+ xPrinter->SetOrientation( eOrientation );
+ if (aPaperSize != aNewPaperSize)
+ xPrinter->SetPaperSizeUser(aPaperSize);
+ }
+ xPrinter->Pop();
+}
+
+PrinterController::PageSize vcl::ImplPrinterControllerData::modifyJobSetup( const css::uno::Sequence< css::beans::PropertyValue >& i_rProps )
+{
+ PrinterController::PageSize aPageSize;
+ aPageSize.aSize = mxPrinter->GetPaperSize();
+ css::awt::Size aSetSize, aIsSize;
+ sal_Int32 nPaperBin = mnDefaultPaperBin;
+ for( const auto& rProp : i_rProps )
+ {
+ if ( rProp.Name == "PreferredPageSize" )
+ {
+ rProp.Value >>= aSetSize;
+ }
+ else if ( rProp.Name == "PageSize" )
+ {
+ rProp.Value >>= aIsSize;
+ }
+ else if ( rProp.Name == "PageIncludesNonprintableArea" )
+ {
+ bool bVal = false;
+ rProp.Value >>= bVal;
+ aPageSize.bFullPaper = bVal;
+ }
+ else if ( rProp.Name == "PrinterPaperTray" )
+ {
+ sal_Int32 nBin = -1;
+ rProp.Value >>= nBin;
+ if( nBin >= 0 && o3tl::make_unsigned(nBin) < mxPrinter->GetPaperBinCount() )
+ nPaperBin = nBin;
+ }
+ }
+
+ Size aCurSize( mxPrinter->GetPaperSize() );
+ if( aSetSize.Width && aSetSize.Height )
+ {
+ Size aSetPaperSize( aSetSize.Width, aSetSize.Height );
+ Size aRealPaperSize( getRealPaperSize( aSetPaperSize, true/*bNoNUP*/ ) );
+ if( aRealPaperSize != aCurSize )
+ aIsSize = aSetSize;
+ }
+
+ if( aIsSize.Width && aIsSize.Height )
+ {
+ aPageSize.aSize.setWidth( aIsSize.Width );
+ aPageSize.aSize.setHeight( aIsSize.Height );
+
+ Size aRealPaperSize( getRealPaperSize( aPageSize.aSize, true/*bNoNUP*/ ) );
+ if( aRealPaperSize != aCurSize )
+ mxPrinter->SetPaperSizeUser( aRealPaperSize );
+ }
+
+ // paper bin set from properties in print dialog overrides
+ // application default for a page
+ if ( mnFixedPaperBin != -1 )
+ nPaperBin = mnFixedPaperBin;
+
+ if( nPaperBin != -1 && nPaperBin != mxPrinter->GetPaperBin() )
+ mxPrinter->SetPaperBin( nPaperBin );
+
+ return aPageSize;
+}
+
+//fdo#61886
+
+//when printing is finished, set the paper size of the printer to either what
+//the user explicitly set as the desired paper size, or fallback to whatever
+//the printer had before printing started. That way it doesn't contain the last
+//paper size of a multiple paper size using document when we are in our normal
+//auto accept document paper size mode and end up overwriting the original
+//paper size setting for file->printer_settings just by pressing "ok" in the
+//print dialog
+void vcl::ImplPrinterControllerData::resetPaperToLastConfigured()
+{
+ mxPrinter->Push();
+ mxPrinter->SetMapMode(MapMode(MapUnit::Map100thMM));
+ Size aCurSize(mxPrinter->GetPaperSize());
+ if (aCurSize != maDefaultPageSize)
+ mxPrinter->SetPaperSizeUser(maDefaultPageSize);
+ mxPrinter->Pop();
+}
+
+int PrinterController::getPageCountProtected() const
+{
+ const MapMode aMapMode( MapUnit::Map100thMM );
+
+ mpImplData->mxPrinter->Push();
+ mpImplData->mxPrinter->SetMapMode( aMapMode );
+ int nPages = getPageCount();
+ mpImplData->mxPrinter->Pop();
+ return nPages;
+}
+
+css::uno::Sequence< css::beans::PropertyValue > PrinterController::getPageParametersProtected( int i_nPage ) const
+{
+ const MapMode aMapMode( MapUnit::Map100thMM );
+
+ mpImplData->mxPrinter->Push();
+ mpImplData->mxPrinter->SetMapMode( aMapMode );
+ css::uno::Sequence< css::beans::PropertyValue > aResult( getPageParameters( i_nPage ) );
+ mpImplData->mxPrinter->Pop();
+ return aResult;
+}
+
+PrinterController::PageSize PrinterController::getPageFile( int i_nUnfilteredPage, GDIMetaFile& o_rMtf, bool i_bMayUseCache )
+{
+ // update progress if necessary
+ if( mpImplData->mxProgress )
+ {
+ // do nothing if printing is canceled
+ if( mpImplData->mxProgress->isCanceled() )
+ return PrinterController::PageSize();
+ mpImplData->mxProgress->tick();
+ Application::Reschedule( true );
+ }
+
+ if( i_bMayUseCache )
+ {
+ PrinterController::PageSize aPageSize;
+ if( mpImplData->maPageCache.get( i_nUnfilteredPage, o_rMtf, aPageSize ) )
+ {
+ return aPageSize;
+ }
+ }
+ else
+ mpImplData->maPageCache.invalidate();
+
+ o_rMtf.Clear();
+
+ // get page parameters
+ css::uno::Sequence< css::beans::PropertyValue > aPageParm( getPageParametersProtected( i_nUnfilteredPage ) );
+ const MapMode aMapMode( MapUnit::Map100thMM );
+
+ mpImplData->mxPrinter->Push();
+ mpImplData->mxPrinter->SetMapMode( aMapMode );
+
+ // modify job setup if necessary
+ PrinterController::PageSize aPageSize = mpImplData->modifyJobSetup( aPageParm );
+
+ o_rMtf.SetPrefSize( aPageSize.aSize );
+ o_rMtf.SetPrefMapMode( aMapMode );
+
+ mpImplData->mxPrinter->EnableOutput( false );
+
+ o_rMtf.Record( mpImplData->mxPrinter.get() );
+
+ printPage( i_nUnfilteredPage );
+
+ o_rMtf.Stop();
+ o_rMtf.WindStart();
+ mpImplData->mxPrinter->Pop();
+
+ if( i_bMayUseCache )
+ mpImplData->maPageCache.insert( i_nUnfilteredPage, o_rMtf, aPageSize );
+
+ // reset "FirstPage" property to false now we've gotten at least our first one
+ mpImplData->mbFirstPage = false;
+
+ return aPageSize;
+}
+
+static void appendSubPage( GDIMetaFile& o_rMtf, const tools::Rectangle& i_rClipRect, GDIMetaFile& io_rSubPage, bool i_bDrawBorder )
+{
+ // intersect all clipregion actions with our clip rect
+ io_rSubPage.WindStart();
+ io_rSubPage.Clip( i_rClipRect );
+
+ // save gstate
+ o_rMtf.AddAction( new MetaPushAction( PushFlags::ALL ) );
+
+ // clip to page rect
+ o_rMtf.AddAction( new MetaClipRegionAction( vcl::Region( i_rClipRect ), true ) );
+
+ // append the subpage
+ io_rSubPage.WindStart();
+ io_rSubPage.Play( o_rMtf );
+
+ // restore gstate
+ o_rMtf.AddAction( new MetaPopAction() );
+
+ // draw a border
+ if( !i_bDrawBorder )
+ return;
+
+ // save gstate
+ o_rMtf.AddAction( new MetaPushAction( PushFlags::LINECOLOR | PushFlags::FILLCOLOR | PushFlags::CLIPREGION | PushFlags::MAPMODE ) );
+ o_rMtf.AddAction( new MetaMapModeAction( MapMode( MapUnit::Map100thMM ) ) );
+
+ tools::Rectangle aBorderRect( i_rClipRect );
+ o_rMtf.AddAction( new MetaLineColorAction( COL_BLACK, true ) );
+ o_rMtf.AddAction( new MetaFillColorAction( COL_TRANSPARENT, false ) );
+ o_rMtf.AddAction( new MetaRectAction( aBorderRect ) );
+
+ // restore gstate
+ o_rMtf.AddAction( new MetaPopAction() );
+}
+
+PrinterController::PageSize PrinterController::getFilteredPageFile( int i_nFilteredPage, GDIMetaFile& o_rMtf, bool i_bMayUseCache )
+{
+ const MultiPageSetup& rMPS( mpImplData->maMultiPage );
+ int nSubPages = rMPS.nRows * rMPS.nColumns;
+ if( nSubPages < 1 )
+ nSubPages = 1;
+
+ // reverse sheet order
+ if( mpImplData->mbReversePageOrder )
+ {
+ int nDocPages = getFilteredPageCount();
+ i_nFilteredPage = nDocPages - 1 - i_nFilteredPage;
+ }
+
+ // there is no filtering to be done (and possibly the page size of the
+ // original page is to be set), when N-Up is "neutral" that is there is
+ // only one subpage and the margins are 0
+ if( nSubPages == 1 &&
+ rMPS.nLeftMargin == 0 && rMPS.nRightMargin == 0 &&
+ rMPS.nTopMargin == 0 && rMPS.nBottomMargin == 0 )
+ {
+ PrinterController::PageSize aPageSize = getPageFile( i_nFilteredPage, o_rMtf, i_bMayUseCache );
+ if (mpImplData->meJobState != css::view::PrintableState_JOB_STARTED)
+ { // rhbz#657394: check that we are still printing...
+ return PrinterController::PageSize();
+ }
+ Size aPaperSize = mpImplData->getRealPaperSize( aPageSize.aSize, true );
+ mpImplData->mxPrinter->SetMapMode( MapMode( MapUnit::Map100thMM ) );
+ mpImplData->mxPrinter->SetPaperSizeUser( aPaperSize );
+ if( aPaperSize != aPageSize.aSize )
+ {
+ // user overridden page size, center Metafile
+ o_rMtf.WindStart();
+ tools::Long nDX = (aPaperSize.Width() - aPageSize.aSize.Width()) / 2;
+ tools::Long nDY = (aPaperSize.Height() - aPageSize.aSize.Height()) / 2;
+ o_rMtf.Move( nDX, nDY, mpImplData->mxPrinter->GetDPIX(), mpImplData->mxPrinter->GetDPIY() );
+ o_rMtf.WindStart();
+ o_rMtf.SetPrefSize( aPaperSize );
+ aPageSize.aSize = aPaperSize;
+ }
+ return aPageSize;
+ }
+
+ // set last page property really only on the very last page to be rendered
+ // that is on the last subpage of a NUp run
+ bool bIsLastPage = mpImplData->mbLastPage;
+ mpImplData->mbLastPage = false;
+
+ Size aPaperSize( mpImplData->getRealPaperSize( mpImplData->maMultiPage.aPaperSize, false ) );
+
+ // multi page area: page size minus margins + one time spacing right and down
+ // the added spacing is so each subpage can be calculated including its spacing
+ Size aMPArea( aPaperSize );
+ aMPArea.AdjustWidth( -(rMPS.nLeftMargin + rMPS.nRightMargin) );
+ aMPArea.AdjustWidth(rMPS.nHorizontalSpacing );
+ aMPArea.AdjustHeight( -(rMPS.nTopMargin + rMPS.nBottomMargin) );
+ aMPArea.AdjustHeight(rMPS.nVerticalSpacing );
+
+ // determine offsets
+ tools::Long nAdvX = aMPArea.Width() / rMPS.nColumns;
+ tools::Long nAdvY = aMPArea.Height() / rMPS.nRows;
+
+ // determine size of a "cell" subpage, leave a little space around pages
+ Size aSubPageSize( nAdvX - rMPS.nHorizontalSpacing, nAdvY - rMPS.nVerticalSpacing );
+
+ o_rMtf.Clear();
+ o_rMtf.SetPrefSize( aPaperSize );
+ o_rMtf.SetPrefMapMode( MapMode( MapUnit::Map100thMM ) );
+ o_rMtf.AddAction( new MetaMapModeAction( MapMode( MapUnit::Map100thMM ) ) );
+
+ int nDocPages = getPageCountProtected();
+ if (mpImplData->meJobState != css::view::PrintableState_JOB_STARTED)
+ { // rhbz#657394: check that we are still printing...
+ return PrinterController::PageSize();
+ }
+ for( int nSubPage = 0; nSubPage < nSubPages; nSubPage++ )
+ {
+ // map current sub page to real page
+ int nPage = i_nFilteredPage * nSubPages + nSubPage;
+ if( nSubPage == nSubPages-1 ||
+ nPage == nDocPages-1 )
+ {
+ mpImplData->mbLastPage = bIsLastPage;
+ }
+ if( nPage >= 0 && nPage < nDocPages )
+ {
+ GDIMetaFile aPageFile;
+ PrinterController::PageSize aPageSize = getPageFile( nPage, aPageFile, i_bMayUseCache );
+ if( aPageSize.aSize.Width() && aPageSize.aSize.Height() )
+ {
+ tools::Long nCellX = 0, nCellY = 0;
+ switch( rMPS.nOrder )
+ {
+ case NupOrderType::LRTB:
+ nCellX = (nSubPage % rMPS.nColumns);
+ nCellY = (nSubPage / rMPS.nColumns);
+ break;
+ case NupOrderType::TBLR:
+ nCellX = (nSubPage / rMPS.nRows);
+ nCellY = (nSubPage % rMPS.nRows);
+ break;
+ case NupOrderType::RLTB:
+ nCellX = rMPS.nColumns - 1 - (nSubPage % rMPS.nColumns);
+ nCellY = (nSubPage / rMPS.nColumns);
+ break;
+ case NupOrderType::TBRL:
+ nCellX = rMPS.nColumns - 1 - (nSubPage / rMPS.nRows);
+ nCellY = (nSubPage % rMPS.nRows);
+ break;
+ }
+ // scale the metafile down to a sub page size
+ double fScaleX = double(aSubPageSize.Width())/double(aPageSize.aSize.Width());
+ double fScaleY = double(aSubPageSize.Height())/double(aPageSize.aSize.Height());
+ double fScale = std::min( fScaleX, fScaleY );
+ aPageFile.Scale( fScale, fScale );
+ aPageFile.WindStart();
+
+ // move the subpage so it is centered in its "cell"
+ tools::Long nOffX = (aSubPageSize.Width() - tools::Long(double(aPageSize.aSize.Width()) * fScale)) / 2;
+ tools::Long nOffY = (aSubPageSize.Height() - tools::Long(double(aPageSize.aSize.Height()) * fScale)) / 2;
+ tools::Long nX = rMPS.nLeftMargin + nOffX + nAdvX * nCellX;
+ tools::Long nY = rMPS.nTopMargin + nOffY + nAdvY * nCellY;
+ aPageFile.Move( nX, nY, mpImplData->mxPrinter->GetDPIX(), mpImplData->mxPrinter->GetDPIY() );
+ aPageFile.WindStart();
+ // calculate border rectangle
+ tools::Rectangle aSubPageRect( Point( nX, nY ),
+ Size( tools::Long(double(aPageSize.aSize.Width())*fScale),
+ tools::Long(double(aPageSize.aSize.Height())*fScale) ) );
+
+ // append subpage to page
+ appendSubPage( o_rMtf, aSubPageRect, aPageFile, rMPS.bDrawBorder );
+ }
+ }
+ }
+ o_rMtf.WindStart();
+
+ // subsequent getPageFile calls have changed the paper, reset it to current value
+ mpImplData->mxPrinter->SetMapMode( MapMode( MapUnit::Map100thMM ) );
+ mpImplData->mxPrinter->SetPaperSizeUser( aPaperSize );
+
+ return PrinterController::PageSize( aPaperSize, true );
+}
+
+int PrinterController::getFilteredPageCount() const
+{
+ int nDiv = mpImplData->maMultiPage.nRows * mpImplData->maMultiPage.nColumns;
+ if( nDiv < 1 )
+ nDiv = 1;
+ return (getPageCountProtected() + (nDiv-1)) / nDiv;
+}
+
+DrawModeFlags PrinterController::removeTransparencies( GDIMetaFile const & i_rIn, GDIMetaFile& o_rOut )
+{
+ DrawModeFlags nRestoreDrawMode = mpImplData->mxPrinter->GetDrawMode();
+ sal_Int32 nMaxBmpDPIX = mpImplData->mxPrinter->GetDPIX();
+ sal_Int32 nMaxBmpDPIY = mpImplData->mxPrinter->GetDPIY();
+
+ const vcl::printer::Options& rPrinterOptions = mpImplData->mxPrinter->GetPrinterOptions();
+
+ static const sal_Int32 OPTIMAL_BMP_RESOLUTION = 300;
+ static const sal_Int32 NORMAL_BMP_RESOLUTION = 200;
+
+ if( rPrinterOptions.IsReduceBitmaps() )
+ {
+ // calculate maximum resolution for bitmap graphics
+ if( printer::BitmapMode::Optimal == rPrinterOptions.GetReducedBitmapMode() )
+ {
+ nMaxBmpDPIX = std::min( sal_Int32(OPTIMAL_BMP_RESOLUTION), nMaxBmpDPIX );
+ nMaxBmpDPIY = std::min( sal_Int32(OPTIMAL_BMP_RESOLUTION), nMaxBmpDPIY );
+ }
+ else if( printer::BitmapMode::Normal == rPrinterOptions.GetReducedBitmapMode() )
+ {
+ nMaxBmpDPIX = std::min( sal_Int32(NORMAL_BMP_RESOLUTION), nMaxBmpDPIX );
+ nMaxBmpDPIY = std::min( sal_Int32(NORMAL_BMP_RESOLUTION), nMaxBmpDPIY );
+ }
+ else
+ {
+ nMaxBmpDPIX = std::min( sal_Int32(rPrinterOptions.GetReducedBitmapResolution()), nMaxBmpDPIX );
+ nMaxBmpDPIY = std::min( sal_Int32(rPrinterOptions.GetReducedBitmapResolution()), nMaxBmpDPIY );
+ }
+ }
+
+ // convert to greyscales
+ if( rPrinterOptions.IsConvertToGreyscales() )
+ {
+ mpImplData->mxPrinter->SetDrawMode( mpImplData->mxPrinter->GetDrawMode() |
+ ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
+ DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
+ }
+
+ // disable transparency output
+ if( rPrinterOptions.IsReduceTransparency() && ( vcl::printer::TransparencyMode::NONE == rPrinterOptions.GetReducedTransparencyMode() ) )
+ {
+ mpImplData->mxPrinter->SetDrawMode( mpImplData->mxPrinter->GetDrawMode() | DrawModeFlags::NoTransparency );
+ }
+
+ Color aBg( COL_TRANSPARENT ); // default: let RemoveTransparenciesFromMetaFile do its own background logic
+ if( mpImplData->maMultiPage.nRows * mpImplData->maMultiPage.nColumns > 1 )
+ {
+ // in N-Up printing we have no "page" background operation
+ // we also have no way to determine the paper color
+ // so let's go for white, which will kill 99.9% of the real cases
+ aBg = COL_WHITE;
+ }
+ mpImplData->mxPrinter->RemoveTransparenciesFromMetaFile( i_rIn, o_rOut, nMaxBmpDPIX, nMaxBmpDPIY,
+ rPrinterOptions.IsReduceTransparency(),
+ rPrinterOptions.GetReducedTransparencyMode() == vcl::printer::TransparencyMode::Auto,
+ rPrinterOptions.IsReduceBitmaps() && rPrinterOptions.IsReducedBitmapIncludesTransparency(),
+ aBg
+ );
+ return nRestoreDrawMode;
+}
+
+void PrinterController::printFilteredPage( int i_nPage )
+{
+ if( mpImplData->meJobState != css::view::PrintableState_JOB_STARTED )
+ return; // rhbz#657394: check that we are still printing...
+
+ GDIMetaFile aPageFile;
+ PrinterController::PageSize aPageSize = getFilteredPageFile( i_nPage, aPageFile );
+
+ if( mpImplData->mxProgress )
+ {
+ // do nothing if printing is canceled
+ if( mpImplData->mxProgress->isCanceled() )
+ {
+ setJobState( css::view::PrintableState_JOB_ABORTED );
+ return;
+ }
+ }
+
+ // in N-Up printing set the correct page size
+ mpImplData->mxPrinter->SetMapMode(MapMode(MapUnit::Map100thMM));
+ // aPageSize was filtered through mpImplData->getRealPaperSize already by getFilteredPageFile()
+ mpImplData->mxPrinter->SetPaperSizeUser( aPageSize.aSize );
+ if( mpImplData->mnFixedPaperBin != -1 &&
+ mpImplData->mxPrinter->GetPaperBin() != mpImplData->mnFixedPaperBin )
+ {
+ mpImplData->mxPrinter->SetPaperBin( mpImplData->mnFixedPaperBin );
+ }
+
+ // if full paper is meant to be used, move the output to accommodate for pageoffset
+ if( aPageSize.bFullPaper )
+ {
+ Point aPageOffset( mpImplData->mxPrinter->GetPageOffset() );
+ aPageFile.WindStart();
+ aPageFile.Move( -aPageOffset.X(), -aPageOffset.Y(), mpImplData->mxPrinter->GetDPIX(), mpImplData->mxPrinter->GetDPIY() );
+ }
+
+ GDIMetaFile aCleanedFile;
+ DrawModeFlags nRestoreDrawMode = removeTransparencies( aPageFile, aCleanedFile );
+
+ mpImplData->mxPrinter->EnableOutput();
+
+ // actually print the page
+ mpImplData->mxPrinter->ImplStartPage();
+
+ mpImplData->mxPrinter->Push();
+ aCleanedFile.WindStart();
+ aCleanedFile.Play(*mpImplData->mxPrinter);
+ mpImplData->mxPrinter->Pop();
+
+ mpImplData->mxPrinter->ImplEndPage();
+
+ mpImplData->mxPrinter->SetDrawMode( nRestoreDrawMode );
+}
+
+void PrinterController::jobStarted()
+{
+}
+
+void PrinterController::jobFinished( css::view::PrintableState )
+{
+}
+
+void PrinterController::abortJob()
+{
+ setJobState( css::view::PrintableState_JOB_ABORTED );
+ // applications (well, sw) depend on a page request with "IsLastPage" = true
+ // to free resources, else they (well, sw) will crash eventually
+ setLastPage( true );
+
+ if (mpImplData->mxProgress)
+ {
+ mpImplData->mxProgress->response(RET_CANCEL);
+ mpImplData->mxProgress.reset();
+ }
+
+ GDIMetaFile aMtf;
+ getPageFile( 0, aMtf );
+}
+
+void PrinterController::setLastPage( bool i_bLastPage )
+{
+ mpImplData->mbLastPage = i_bLastPage;
+}
+
+void PrinterController::setReversePrint( bool i_bReverse )
+{
+ mpImplData->mbReversePageOrder = i_bReverse;
+}
+
+void PrinterController::setPapersizeFromSetup( bool i_bPapersizeFromSetup )
+{
+ mpImplData->mbPapersizeFromSetup = i_bPapersizeFromSetup;
+ mpImplData->mxPrinter->SetPrinterSettingsPreferred( i_bPapersizeFromSetup );
+ if ( i_bPapersizeFromSetup )
+ {
+ mpImplData->mbPapersizeFromUser = false;
+ mpImplData->mbOrientationFromUser = false;
+ }
+}
+
+bool PrinterController::getPapersizeFromSetup() const
+{
+ return mpImplData->mbPapersizeFromSetup;
+}
+
+void PrinterController::setPaperSizeFromUser( Size i_aUserSize )
+{
+ mpImplData->mbPapersizeFromUser = true;
+ mpImplData->mbPapersizeFromSetup = false;
+ mpImplData->mxPrinter->SetPrinterSettingsPreferred( false );
+
+ mpImplData->maUserPageSize = i_aUserSize;
+}
+
+void PrinterController::setOrientationFromUser( Orientation eOrientation, bool set )
+{
+ mpImplData->mbOrientationFromUser = set;
+ mpImplData->meUserOrientation = eOrientation;
+}
+
+void PrinterController::setPrinterModified( bool i_bPrinterModified )
+{
+ mpImplData->mbPrinterModified = i_bPrinterModified;
+}
+
+bool PrinterController::getPrinterModified() const
+{
+ return mpImplData->mbPrinterModified;
+}
+
+css::uno::Sequence< css::beans::PropertyValue > PrinterController::getJobProperties( const css::uno::Sequence< css::beans::PropertyValue >& i_rMergeList ) const
+{
+ std::unordered_set< OUString > aMergeSet;
+ size_t nResultLen = size_t(i_rMergeList.getLength()) + mpImplData->maUIProperties.size() + 3;
+ for( const auto& rPropVal : i_rMergeList )
+ aMergeSet.insert( rPropVal.Name );
+
+ css::uno::Sequence< css::beans::PropertyValue > aResult( nResultLen );
+ auto pResult = aResult.getArray();
+ std::copy(i_rMergeList.begin(), i_rMergeList.end(), pResult);
+ int nCur = i_rMergeList.getLength();
+ for(const css::beans::PropertyValue & rPropVal : mpImplData->maUIProperties)
+ {
+ if( aMergeSet.find( rPropVal.Name ) == aMergeSet.end() )
+ pResult[nCur++] = rPropVal;
+ }
+ // append IsFirstPage
+ if( aMergeSet.find( "IsFirstPage" ) == aMergeSet.end() )
+ {
+ css::beans::PropertyValue aVal;
+ aVal.Name = "IsFirstPage";
+ aVal.Value <<= mpImplData->mbFirstPage;
+ pResult[nCur++] = aVal;
+ }
+ // append IsLastPage
+ if( aMergeSet.find( "IsLastPage" ) == aMergeSet.end() )
+ {
+ css::beans::PropertyValue aVal;
+ aVal.Name = "IsLastPage";
+ aVal.Value <<= mpImplData->mbLastPage;
+ pResult[nCur++] = aVal;
+ }
+ // append IsPrinter
+ if( aMergeSet.find( "IsPrinter" ) == aMergeSet.end() )
+ {
+ css::beans::PropertyValue aVal;
+ aVal.Name = "IsPrinter";
+ aVal.Value <<= true;
+ pResult[nCur++] = aVal;
+ }
+ aResult.realloc( nCur );
+ return aResult;
+}
+
+const css::uno::Sequence< css::beans::PropertyValue >& PrinterController::getUIOptions() const
+{
+ return mpImplData->maUIOptions;
+}
+
+css::beans::PropertyValue* PrinterController::getValue( const OUString& i_rProperty )
+{
+ std::unordered_map< OUString, size_t >::const_iterator it =
+ mpImplData->maPropertyToIndex.find( i_rProperty );
+ return it != mpImplData->maPropertyToIndex.end() ? &mpImplData->maUIProperties[it->second] : nullptr;
+}
+
+const css::beans::PropertyValue* PrinterController::getValue( const OUString& i_rProperty ) const
+{
+ std::unordered_map< OUString, size_t >::const_iterator it =
+ mpImplData->maPropertyToIndex.find( i_rProperty );
+ return it != mpImplData->maPropertyToIndex.end() ? &mpImplData->maUIProperties[it->second] : nullptr;
+}
+
+void PrinterController::setValue( const OUString& i_rPropertyName, const css::uno::Any& i_rValue )
+{
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rPropertyName;
+ aVal.Value = i_rValue;
+
+ setValue( aVal );
+}
+
+void PrinterController::setValue( const css::beans::PropertyValue& i_rPropertyValue )
+{
+ std::unordered_map< OUString, size_t >::const_iterator it =
+ mpImplData->maPropertyToIndex.find( i_rPropertyValue.Name );
+ if( it != mpImplData->maPropertyToIndex.end() )
+ mpImplData->maUIProperties[ it->second ] = i_rPropertyValue;
+ else
+ {
+ // insert correct index into property map
+ mpImplData->maPropertyToIndex[ i_rPropertyValue.Name ] = mpImplData->maUIProperties.size();
+ mpImplData->maUIProperties.push_back( i_rPropertyValue );
+ mpImplData->maUIPropertyEnabled.push_back( true );
+ }
+}
+
+void PrinterController::setUIOptions( const css::uno::Sequence< css::beans::PropertyValue >& i_rOptions )
+{
+ SAL_WARN_IF( mpImplData->maUIOptions.hasElements(), "vcl.gdi", "setUIOptions called twice !" );
+
+ mpImplData->maUIOptions = i_rOptions;
+
+ for( const auto& rOpt : i_rOptions )
+ {
+ css::uno::Sequence< css::beans::PropertyValue > aOptProp;
+ rOpt.Value >>= aOptProp;
+ bool bIsEnabled = true;
+ bool bHaveProperty = false;
+ OUString aPropName;
+ vcl::ImplPrinterControllerData::ControlDependency aDep;
+ css::uno::Sequence< sal_Bool > aChoicesDisabled;
+ for( const css::beans::PropertyValue& rEntry : std::as_const(aOptProp) )
+ {
+ if ( rEntry.Name == "Property" )
+ {
+ css::beans::PropertyValue aVal;
+ rEntry.Value >>= aVal;
+ DBG_ASSERT( mpImplData->maPropertyToIndex.find( aVal.Name )
+ == mpImplData->maPropertyToIndex.end(), "duplicate property entry" );
+ setValue( aVal );
+ aPropName = aVal.Name;
+ bHaveProperty = true;
+ }
+ else if ( rEntry.Name == "Enabled" )
+ {
+ bool bValue = true;
+ rEntry.Value >>= bValue;
+ bIsEnabled = bValue;
+ }
+ else if ( rEntry.Name == "DependsOnName" )
+ {
+ rEntry.Value >>= aDep.maDependsOnName;
+ }
+ else if ( rEntry.Name == "DependsOnEntry" )
+ {
+ rEntry.Value >>= aDep.mnDependsOnEntry;
+ }
+ else if ( rEntry.Name == "ChoicesDisabled" )
+ {
+ rEntry.Value >>= aChoicesDisabled;
+ }
+ }
+ if( bHaveProperty )
+ {
+ vcl::ImplPrinterControllerData::PropertyToIndexMap::const_iterator it =
+ mpImplData->maPropertyToIndex.find( aPropName );
+ // sanity check
+ if( it != mpImplData->maPropertyToIndex.end() )
+ {
+ mpImplData->maUIPropertyEnabled[ it->second ] = bIsEnabled;
+ }
+ if( !aDep.maDependsOnName.isEmpty() )
+ mpImplData->maControlDependencies[ aPropName ] = aDep;
+ if( aChoicesDisabled.hasElements() )
+ mpImplData->maChoiceDisableMap[ aPropName ] = aChoicesDisabled;
+ }
+ }
+}
+
+bool PrinterController::isUIOptionEnabled( const OUString& i_rProperty ) const
+{
+ bool bEnabled = false;
+ std::unordered_map< OUString, size_t >::const_iterator prop_it =
+ mpImplData->maPropertyToIndex.find( i_rProperty );
+ if( prop_it != mpImplData->maPropertyToIndex.end() )
+ {
+ bEnabled = mpImplData->maUIPropertyEnabled[prop_it->second];
+
+ if( bEnabled )
+ {
+ // check control dependencies
+ vcl::ImplPrinterControllerData::ControlDependencyMap::const_iterator it =
+ mpImplData->maControlDependencies.find( i_rProperty );
+ if( it != mpImplData->maControlDependencies.end() )
+ {
+ // check if the dependency is enabled
+ // if the dependency is disabled, we are too
+ bEnabled = isUIOptionEnabled( it->second.maDependsOnName );
+
+ if( bEnabled )
+ {
+ // does the dependency have the correct value ?
+ const css::beans::PropertyValue* pVal = getValue( it->second.maDependsOnName );
+ OSL_ENSURE( pVal, "unknown property in dependency" );
+ if( pVal )
+ {
+ sal_Int32 nDepVal = 0;
+ bool bDepVal = false;
+ if( pVal->Value >>= nDepVal )
+ {
+ bEnabled = (nDepVal == it->second.mnDependsOnEntry) || (it->second.mnDependsOnEntry == -1);
+ }
+ else if( pVal->Value >>= bDepVal )
+ {
+ // could be a dependency on a checked boolean
+ // in this case the dependency is on a non zero for checked value
+ bEnabled = ( bDepVal && it->second.mnDependsOnEntry != 0) ||
+ ( ! bDepVal && it->second.mnDependsOnEntry == 0);
+ }
+ else
+ {
+ // if the type does not match something is awry
+ OSL_FAIL( "strange type in control dependency" );
+ bEnabled = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ return bEnabled;
+}
+
+bool PrinterController::isUIChoiceEnabled( const OUString& i_rProperty, sal_Int32 i_nValue ) const
+{
+ bool bEnabled = true;
+ ImplPrinterControllerData::ChoiceDisableMap::const_iterator it =
+ mpImplData->maChoiceDisableMap.find( i_rProperty );
+ if(it != mpImplData->maChoiceDisableMap.end() )
+ {
+ const css::uno::Sequence< sal_Bool >& rDisabled( it->second );
+ if( i_nValue >= 0 && i_nValue < rDisabled.getLength() )
+ bEnabled = ! rDisabled[i_nValue];
+ }
+ return bEnabled;
+}
+
+OUString PrinterController::makeEnabled( const OUString& i_rProperty )
+{
+ OUString aDependency;
+
+ vcl::ImplPrinterControllerData::ControlDependencyMap::const_iterator it =
+ mpImplData->maControlDependencies.find( i_rProperty );
+ if( it != mpImplData->maControlDependencies.end() )
+ {
+ if( isUIOptionEnabled( it->second.maDependsOnName ) )
+ {
+ aDependency = it->second.maDependsOnName;
+ const css::beans::PropertyValue* pVal = getValue( aDependency );
+ OSL_ENSURE( pVal, "unknown property in dependency" );
+ if( pVal )
+ {
+ sal_Int32 nDepVal = 0;
+ bool bDepVal = false;
+ if( pVal->Value >>= nDepVal )
+ {
+ if( it->second.mnDependsOnEntry != -1 )
+ {
+ setValue( aDependency, css::uno::Any( sal_Int32( it->second.mnDependsOnEntry ) ) );
+ }
+ }
+ else if( pVal->Value >>= bDepVal )
+ {
+ setValue( aDependency, css::uno::Any( it->second.mnDependsOnEntry != 0 ) );
+ }
+ else
+ {
+ // if the type does not match something is awry
+ OSL_FAIL( "strange type in control dependency" );
+ }
+ }
+ }
+ }
+
+ return aDependency;
+}
+
+void PrinterController::createProgressDialog()
+{
+ if (!mpImplData->mxProgress)
+ {
+ bool bShow = true;
+ css::beans::PropertyValue* pMonitor = getValue( "MonitorVisible" );
+ if( pMonitor )
+ pMonitor->Value >>= bShow;
+ else
+ {
+ const css::beans::PropertyValue* pVal = getValue( "IsApi" );
+ if( pVal )
+ {
+ bool bApi = false;
+ pVal->Value >>= bApi;
+ bShow = ! bApi;
+ }
+ }
+
+ if( bShow && ! Application::IsHeadlessModeEnabled() )
+ {
+ mpImplData->mxProgress = std::make_shared<PrintProgressDialog>(getWindow(), getPageCountProtected());
+ weld::DialogController::runAsync(mpImplData->mxProgress, [](sal_Int32 /*nResult*/){});
+ }
+ }
+ else
+ {
+ mpImplData->mxProgress->response(RET_CANCEL);
+ mpImplData->mxProgress.reset();
+ }
+}
+
+bool PrinterController::isProgressCanceled() const
+{
+ return mpImplData->mxProgress && mpImplData->mxProgress->isCanceled();
+}
+
+void PrinterController::setMultipage( const MultiPageSetup& i_rMPS )
+{
+ mpImplData->maMultiPage = i_rMPS;
+}
+
+const PrinterController::MultiPageSetup& PrinterController::getMultipage() const
+{
+ return mpImplData->maMultiPage;
+}
+
+void PrinterController::resetPaperToLastConfigured()
+{
+ mpImplData->resetPaperToLastConfigured();
+}
+
+void PrinterController::pushPropertiesToPrinter()
+{
+ sal_Int32 nCopyCount = 1;
+ // set copycount and collate
+ const css::beans::PropertyValue* pVal = getValue( "CopyCount" );
+ if( pVal )
+ pVal->Value >>= nCopyCount;
+ bool bCollate = false;
+ pVal = getValue( "Collate" );
+ if( pVal )
+ pVal->Value >>= bCollate;
+ mpImplData->mxPrinter->SetCopyCount( static_cast<sal_uInt16>(nCopyCount), bCollate );
+
+ pVal = getValue("SinglePrintJobs");
+ bool bSinglePrintJobs = false;
+ if (pVal)
+ pVal->Value >>= bSinglePrintJobs;
+ mpImplData->mxPrinter->SetSinglePrintJobs(bSinglePrintJobs);
+
+ // duplex mode
+ pVal = getValue( "DuplexMode" );
+ if( pVal )
+ {
+ sal_Int16 nDuplex = css::view::DuplexMode::UNKNOWN;
+ pVal->Value >>= nDuplex;
+ switch( nDuplex )
+ {
+ case css::view::DuplexMode::OFF: mpImplData->mxPrinter->SetDuplexMode( DuplexMode::Off ); break;
+ case css::view::DuplexMode::LONGEDGE: mpImplData->mxPrinter->SetDuplexMode( DuplexMode::LongEdge ); break;
+ case css::view::DuplexMode::SHORTEDGE: mpImplData->mxPrinter->SetDuplexMode( DuplexMode::ShortEdge ); break;
+ }
+ }
+}
+
+bool PrinterController::isShowDialogs() const
+{
+ bool bApi = getBoolProperty( "IsApi", false );
+ return ! bApi && ! Application::IsHeadlessModeEnabled();
+}
+
+bool PrinterController::isDirectPrint() const
+{
+ bool bDirect = getBoolProperty( "IsDirect", false );
+ return bDirect;
+}
+
+bool PrinterController::getBoolProperty( const OUString& i_rProperty, bool i_bFallback ) const
+{
+ bool bRet = i_bFallback;
+ const css::beans::PropertyValue* pVal = getValue( i_rProperty );
+ if( pVal )
+ pVal->Value >>= bRet;
+ return bRet;
+}
+
+sal_Int32 PrinterController::getIntProperty( const OUString& i_rProperty, sal_Int32 i_nFallback ) const
+{
+ sal_Int32 nRet = i_nFallback;
+ const css::beans::PropertyValue* pVal = getValue( i_rProperty );
+ if( pVal )
+ pVal->Value >>= nRet;
+ return nRet;
+}
+
+/*
+ * PrinterOptionsHelper
+**/
+css::uno::Any PrinterOptionsHelper::getValue( const OUString& i_rPropertyName ) const
+{
+ css::uno::Any aRet;
+ std::unordered_map< OUString, css::uno::Any >::const_iterator it =
+ m_aPropertyMap.find( i_rPropertyName );
+ if( it != m_aPropertyMap.end() )
+ aRet = it->second;
+ return aRet;
+}
+
+bool PrinterOptionsHelper::getBoolValue( const OUString& i_rPropertyName, bool i_bDefault ) const
+{
+ bool bRet = false;
+ css::uno::Any aVal( getValue( i_rPropertyName ) );
+ return (aVal >>= bRet) ? bRet : i_bDefault;
+}
+
+sal_Int64 PrinterOptionsHelper::getIntValue( const OUString& i_rPropertyName, sal_Int64 i_nDefault ) const
+{
+ sal_Int64 nRet = 0;
+ css::uno::Any aVal( getValue( i_rPropertyName ) );
+ return (aVal >>= nRet) ? nRet : i_nDefault;
+}
+
+OUString PrinterOptionsHelper::getStringValue( const OUString& i_rPropertyName ) const
+{
+ OUString aRet;
+ css::uno::Any aVal( getValue( i_rPropertyName ) );
+ return (aVal >>= aRet) ? aRet : OUString();
+}
+
+bool PrinterOptionsHelper::processProperties( const css::uno::Sequence< css::beans::PropertyValue >& i_rNewProp )
+{
+ bool bChanged = false;
+
+ for( const auto& rVal : i_rNewProp )
+ {
+ std::unordered_map< OUString, css::uno::Any >::iterator it =
+ m_aPropertyMap.find( rVal.Name );
+
+ bool bElementChanged = (it == m_aPropertyMap.end()) || (it->second != rVal.Value);
+ if( bElementChanged )
+ {
+ m_aPropertyMap[ rVal.Name ] = rVal.Value;
+ bChanged = true;
+ }
+ }
+ return bChanged;
+}
+
+void PrinterOptionsHelper::appendPrintUIOptions( css::uno::Sequence< css::beans::PropertyValue >& io_rProps ) const
+{
+ if( !m_aUIProperties.empty() )
+ {
+ sal_Int32 nIndex = io_rProps.getLength();
+ io_rProps.realloc( nIndex+1 );
+ io_rProps.getArray()[ nIndex ] = comphelper::makePropertyValue(
+ "ExtraPrintUIOptions", comphelper::containerToSequence(m_aUIProperties));
+ }
+}
+
+css::uno::Any PrinterOptionsHelper::setUIControlOpt(const css::uno::Sequence< OUString >& i_rIDs,
+ const OUString& i_rTitle,
+ const css::uno::Sequence< OUString >& i_rHelpIds,
+ const OUString& i_rType,
+ const css::beans::PropertyValue* i_pVal,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ sal_Int32 nElements =
+ 2 // ControlType + ID
+ + (i_rTitle.isEmpty() ? 0 : 1) // Text
+ + (i_rHelpIds.hasElements() ? 1 : 0) // HelpId
+ + (i_pVal ? 1 : 0) // Property
+ + i_rControlOptions.maAddProps.size() // additional props
+ + (i_rControlOptions.maGroupHint.isEmpty() ? 0 : 1) // grouping
+ + (i_rControlOptions.mbInternalOnly ? 1 : 0) // internal hint
+ + (i_rControlOptions.mbEnabled ? 0 : 1) // enabled
+ ;
+ if( !i_rControlOptions.maDependsOnName.isEmpty() )
+ {
+ nElements += 1;
+ if( i_rControlOptions.mnDependsOnEntry != -1 )
+ nElements += 1;
+ if( i_rControlOptions.mbAttachToDependency )
+ nElements += 1;
+ }
+
+ css::uno::Sequence< css::beans::PropertyValue > aCtrl( nElements );
+ auto pCtrl = aCtrl.getArray();
+ sal_Int32 nUsed = 0;
+ if( !i_rTitle.isEmpty() )
+ {
+ pCtrl[nUsed ].Name = "Text";
+ pCtrl[nUsed++].Value <<= i_rTitle;
+ }
+ if( i_rHelpIds.hasElements() )
+ {
+ pCtrl[nUsed ].Name = "HelpId";
+ pCtrl[nUsed++].Value <<= i_rHelpIds;
+ }
+ pCtrl[nUsed ].Name = "ControlType";
+ pCtrl[nUsed++].Value <<= i_rType;
+ pCtrl[nUsed ].Name = "ID";
+ pCtrl[nUsed++].Value <<= i_rIDs;
+ if( i_pVal )
+ {
+ pCtrl[nUsed ].Name = "Property";
+ pCtrl[nUsed++].Value <<= *i_pVal;
+ }
+ if( !i_rControlOptions.maDependsOnName.isEmpty() )
+ {
+ pCtrl[nUsed ].Name = "DependsOnName";
+ pCtrl[nUsed++].Value <<= i_rControlOptions.maDependsOnName;
+ if( i_rControlOptions.mnDependsOnEntry != -1 )
+ {
+ pCtrl[nUsed ].Name = "DependsOnEntry";
+ pCtrl[nUsed++].Value <<= i_rControlOptions.mnDependsOnEntry;
+ }
+ if( i_rControlOptions.mbAttachToDependency )
+ {
+ pCtrl[nUsed ].Name = "AttachToDependency";
+ pCtrl[nUsed++].Value <<= i_rControlOptions.mbAttachToDependency;
+ }
+ }
+ if( !i_rControlOptions.maGroupHint.isEmpty() )
+ {
+ pCtrl[nUsed ].Name = "GroupingHint";
+ pCtrl[nUsed++].Value <<= i_rControlOptions.maGroupHint;
+ }
+ if( i_rControlOptions.mbInternalOnly )
+ {
+ pCtrl[nUsed ].Name = "InternalUIOnly";
+ pCtrl[nUsed++].Value <<= true;
+ }
+ if( ! i_rControlOptions.mbEnabled )
+ {
+ pCtrl[nUsed ].Name = "Enabled";
+ pCtrl[nUsed++].Value <<= false;
+ }
+
+ sal_Int32 nAddProps = i_rControlOptions.maAddProps.size();
+ for( sal_Int32 i = 0; i < nAddProps; i++ )
+ pCtrl[ nUsed++ ] = i_rControlOptions.maAddProps[i];
+
+ SAL_WARN_IF( nUsed != nElements, "vcl.gdi", "nUsed != nElements, probable heap corruption" );
+
+ return css::uno::Any( aCtrl );
+}
+
+css::uno::Any PrinterOptionsHelper::setGroupControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId)
+{
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Group");
+}
+
+css::uno::Any PrinterOptionsHelper::setSubgroupControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Subgroup", nullptr, i_rControlOptions);
+}
+
+css::uno::Any PrinterOptionsHelper::setBoolControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId,
+ const OUString& i_rProperty,
+ bool i_bValue,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_bValue;
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Bool", &aVal, i_rControlOptions);
+}
+
+css::uno::Any PrinterOptionsHelper::setChoiceRadiosControlOpt(const css::uno::Sequence< OUString >& i_rIDs,
+ const OUString& i_rTitle,
+ const css::uno::Sequence< OUString >& i_rHelpId,
+ const OUString& i_rProperty,
+ const css::uno::Sequence< OUString >& i_rChoices,
+ sal_Int32 i_nValue,
+ const css::uno::Sequence< sal_Bool >& i_rDisabledChoices,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ UIControlOptions aOpt( i_rControlOptions );
+ sal_Int32 nUsed = aOpt.maAddProps.size();
+ aOpt.maAddProps.resize( nUsed + 1 + (i_rDisabledChoices.hasElements() ? 1 : 0) );
+ aOpt.maAddProps[nUsed].Name = "Choices";
+ aOpt.maAddProps[nUsed].Value <<= i_rChoices;
+ if( i_rDisabledChoices.hasElements() )
+ {
+ aOpt.maAddProps[nUsed+1].Name = "ChoicesDisabled";
+ aOpt.maAddProps[nUsed+1].Value <<= i_rDisabledChoices;
+ }
+
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_nValue;
+ return setUIControlOpt(i_rIDs, i_rTitle, i_rHelpId, "Radio", &aVal, aOpt);
+}
+
+css::uno::Any PrinterOptionsHelper::setChoiceListControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const css::uno::Sequence< OUString >& i_rHelpId,
+ const OUString& i_rProperty,
+ const css::uno::Sequence< OUString >& i_rChoices,
+ sal_Int32 i_nValue,
+ const css::uno::Sequence< sal_Bool >& i_rDisabledChoices,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ UIControlOptions aOpt( i_rControlOptions );
+ sal_Int32 nUsed = aOpt.maAddProps.size();
+ aOpt.maAddProps.resize( nUsed + 1 + (i_rDisabledChoices.hasElements() ? 1 : 0) );
+ aOpt.maAddProps[nUsed].Name = "Choices";
+ aOpt.maAddProps[nUsed].Value <<= i_rChoices;
+ if( i_rDisabledChoices.hasElements() )
+ {
+ aOpt.maAddProps[nUsed+1].Name = "ChoicesDisabled";
+ aOpt.maAddProps[nUsed+1].Value <<= i_rDisabledChoices;
+ }
+
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_nValue;
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, i_rHelpId, "List", &aVal, aOpt);
+}
+
+css::uno::Any PrinterOptionsHelper::setRangeControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId,
+ const OUString& i_rProperty,
+ sal_Int32 i_nValue,
+ sal_Int32 i_nMinValue,
+ sal_Int32 i_nMaxValue,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ UIControlOptions aOpt( i_rControlOptions );
+ if( i_nMaxValue >= i_nMinValue )
+ {
+ sal_Int32 nUsed = aOpt.maAddProps.size();
+ aOpt.maAddProps.resize( nUsed + 2 );
+ aOpt.maAddProps[nUsed ].Name = "MinValue";
+ aOpt.maAddProps[nUsed++].Value <<= i_nMinValue;
+ aOpt.maAddProps[nUsed ].Name = "MaxValue";
+ aOpt.maAddProps[nUsed++].Value <<= i_nMaxValue;
+ }
+
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_nValue;
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Range", &aVal, aOpt);
+}
+
+css::uno::Any PrinterOptionsHelper::setEditControlOpt(const OUString& i_rID,
+ const OUString& i_rTitle,
+ const OUString& i_rHelpId,
+ const OUString& i_rProperty,
+ const OUString& i_rValue,
+ const PrinterOptionsHelper::UIControlOptions& i_rControlOptions)
+{
+ css::uno::Sequence< OUString > aHelpId;
+ if( !i_rHelpId.isEmpty() )
+ {
+ aHelpId.realloc( 1 );
+ *aHelpId.getArray() = i_rHelpId;
+ }
+ css::beans::PropertyValue aVal;
+ aVal.Name = i_rProperty;
+ aVal.Value <<= i_rValue;
+ css::uno::Sequence< OUString > aIds { i_rID };
+ return setUIControlOpt(aIds, i_rTitle, aHelpId, "Edit", &aVal, i_rControlOptions);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/regband.cxx b/vcl/source/gdi/regband.cxx
new file mode 100644
index 0000000000..91d292519f
--- /dev/null
+++ b/vcl/source/gdi/regband.cxx
@@ -0,0 +1,886 @@
+/* -*- 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 <tools/helpers.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include <regband.hxx>
+
+// ImplRegionBand
+
+// Each band contains all rectangles between upper and lower border.
+// For Union, Intersect, Xor and Exclude operations rectangles of
+// equal height are evaluated. The borders of the bands should always
+// be chosen such that this is possible.
+
+// If possible, rectangles within the bands are condensed.
+
+// When converting polygons all points of the polygon are registered
+// in the individual bands (for each band they are stored as
+// points in a list). After registration of these points they are
+// converted to rectangles and the points in the list are deleted.
+
+ImplRegionBand::ImplRegionBand( tools::Long nTop, tools::Long nBottom )
+{
+ // save boundaries
+ mnYTop = nTop;
+ mnYBottom = nBottom;
+
+ // initialize lists
+ mpNextBand = nullptr;
+ mpPrevBand = nullptr;
+ mpFirstSep = nullptr;
+ mpFirstBandPoint = nullptr;
+ mbTouched = false;
+}
+
+ImplRegionBand::ImplRegionBand(
+ const ImplRegionBand& rRegionBand,
+ const bool bIgnorePoints)
+{
+ // copy boundaries
+ mnYTop = rRegionBand.mnYTop;
+ mnYBottom = rRegionBand.mnYBottom;
+ mbTouched = rRegionBand.mbTouched;
+
+ // initialisation
+ mpNextBand = nullptr;
+ mpPrevBand = nullptr;
+ mpFirstSep = nullptr;
+ mpFirstBandPoint = nullptr;
+
+ // copy all elements of the list with separations
+ ImplRegionBandSep* pNewSep;
+ ImplRegionBandSep* pPrevSep = nullptr;
+ ImplRegionBandSep* pSep = rRegionBand.mpFirstSep;
+ while ( pSep )
+ {
+ // create new and copy data
+ pNewSep = new ImplRegionBandSep;
+ pNewSep->mnXLeft = pSep->mnXLeft;
+ pNewSep->mnXRight = pSep->mnXRight;
+ pNewSep->mbRemoved = pSep->mbRemoved;
+ pNewSep->mpNextSep = nullptr;
+ if ( pSep == rRegionBand.mpFirstSep )
+ mpFirstSep = pNewSep;
+ else
+ pPrevSep->mpNextSep = pNewSep;
+
+ pPrevSep = pNewSep;
+ pSep = pSep->mpNextSep;
+ }
+
+ if ( bIgnorePoints)
+ return;
+
+ // Copy points.
+ ImplRegionBandPoint* pPoint = rRegionBand.mpFirstBandPoint;
+ ImplRegionBandPoint* pPrevPointCopy = nullptr;
+ while (pPoint != nullptr)
+ {
+ ImplRegionBandPoint* pPointCopy = new ImplRegionBandPoint;
+ pPointCopy->mpNextBandPoint = nullptr;
+ pPointCopy->mnX = pPoint->mnX;
+ pPointCopy->mnLineId = pPoint->mnLineId;
+ pPointCopy->mbEndPoint = pPoint->mbEndPoint;
+ pPointCopy->meLineType = pPoint->meLineType;
+
+ if (pPrevPointCopy != nullptr)
+ pPrevPointCopy->mpNextBandPoint = pPointCopy;
+ else
+ mpFirstBandPoint = pPointCopy;
+
+ pPrevPointCopy = pPointCopy;
+ pPoint = pPoint->mpNextBandPoint;
+ }
+}
+
+ImplRegionBand::~ImplRegionBand()
+{
+ SAL_WARN_IF( mpFirstBandPoint != nullptr, "vcl", "ImplRegionBand::~ImplRegionBand -> pointlist not empty" );
+
+ // delete elements of the list
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ ImplRegionBandSep* pTempSep = pSep->mpNextSep;
+ delete pSep;
+ pSep = pTempSep;
+ }
+
+ // delete elements of the list
+ ImplRegionBandPoint* pPoint = mpFirstBandPoint;
+ while ( pPoint )
+ {
+ ImplRegionBandPoint* pTempPoint = pPoint->mpNextBandPoint;
+ delete pPoint;
+ pPoint = pTempPoint;
+ }
+}
+
+// generate separations from lines and process union with existing
+// separations
+
+void ImplRegionBand::ProcessPoints()
+{
+ // check Pointlist
+ ImplRegionBandPoint* pRegionBandPoint = mpFirstBandPoint;
+ while ( pRegionBandPoint )
+ {
+ // within list?
+ if ( pRegionBandPoint->mpNextBandPoint )
+ {
+ // start/stop?
+ if ( pRegionBandPoint->mbEndPoint && pRegionBandPoint->mpNextBandPoint->mbEndPoint )
+ {
+ // same direction? -> remove next point!
+ if ( pRegionBandPoint->meLineType == pRegionBandPoint->mpNextBandPoint->meLineType )
+ {
+ ImplRegionBandPoint* pSaveRegionBandPoint = pRegionBandPoint->mpNextBandPoint;
+ pRegionBandPoint->mpNextBandPoint = pRegionBandPoint->mpNextBandPoint->mpNextBandPoint;
+ delete pSaveRegionBandPoint;
+ }
+ }
+ }
+
+ // continue with next element in the list
+ pRegionBandPoint = pRegionBandPoint->mpNextBandPoint;
+ }
+
+ pRegionBandPoint = mpFirstBandPoint;
+ while ( pRegionBandPoint && pRegionBandPoint->mpNextBandPoint )
+ {
+ Union( pRegionBandPoint->mnX, pRegionBandPoint->mpNextBandPoint->mnX );
+
+ ImplRegionBandPoint* pNextBandPoint = pRegionBandPoint->mpNextBandPoint->mpNextBandPoint;
+
+ // remove already processed points
+ delete pRegionBandPoint->mpNextBandPoint;
+ delete pRegionBandPoint;
+
+ // continue with next element in the list
+ pRegionBandPoint = pNextBandPoint;
+ }
+
+ // remove last element if necessary
+ delete pRegionBandPoint;
+
+ // list is now empty
+ mpFirstBandPoint = nullptr;
+}
+
+// generate separations from lines and process union with existing
+// separations
+
+bool ImplRegionBand::InsertPoint( tools::Long nX, tools::Long nLineId,
+ bool bEndPoint, LineType eLineType )
+{
+ if ( !mpFirstBandPoint )
+ {
+ mpFirstBandPoint = new ImplRegionBandPoint;
+ mpFirstBandPoint->mnX = nX;
+ mpFirstBandPoint->mnLineId = nLineId;
+ mpFirstBandPoint->mbEndPoint = bEndPoint;
+ mpFirstBandPoint->meLineType = eLineType;
+ mpFirstBandPoint->mpNextBandPoint = nullptr;
+ return true;
+ }
+
+ // look if line already touched the band
+ ImplRegionBandPoint* pRegionBandPoint = mpFirstBandPoint;
+ ImplRegionBandPoint* pLastTestedRegionBandPoint = nullptr;
+ while( pRegionBandPoint )
+ {
+ if ( pRegionBandPoint->mnLineId == nLineId )
+ {
+ if ( bEndPoint )
+ {
+ if( !pRegionBandPoint->mbEndPoint )
+ {
+ // remove old band point
+ if( !mpFirstBandPoint->mpNextBandPoint )
+ {
+ // if we've only got one point => replace first point
+ pRegionBandPoint->mnX = nX;
+ pRegionBandPoint->mbEndPoint = true;
+ return true;
+ }
+ else
+ {
+ // remove current point
+ if( !pLastTestedRegionBandPoint )
+ {
+ // remove and delete old first point
+ ImplRegionBandPoint* pSaveBandPoint = mpFirstBandPoint;
+ mpFirstBandPoint = mpFirstBandPoint->mpNextBandPoint;
+ delete pSaveBandPoint;
+ }
+ else
+ {
+ // remove and delete current band point
+ pLastTestedRegionBandPoint->mpNextBandPoint = pRegionBandPoint->mpNextBandPoint;
+ delete pRegionBandPoint;
+ }
+
+ break;
+ }
+ }
+ }
+ else
+ return false;
+ }
+
+ // use next element
+ pLastTestedRegionBandPoint = pRegionBandPoint;
+ pRegionBandPoint = pRegionBandPoint->mpNextBandPoint;
+ }
+
+ // search appropriate position and insert point into the list
+ ImplRegionBandPoint* pNewRegionBandPoint;
+
+ pRegionBandPoint = mpFirstBandPoint;
+ pLastTestedRegionBandPoint = nullptr;
+ while ( pRegionBandPoint )
+ {
+ // new point completely left? -> insert as first point
+ if ( nX <= pRegionBandPoint->mnX )
+ {
+ pNewRegionBandPoint = new ImplRegionBandPoint;
+ pNewRegionBandPoint->mnX = nX;
+ pNewRegionBandPoint->mnLineId = nLineId;
+ pNewRegionBandPoint->mbEndPoint = bEndPoint;
+ pNewRegionBandPoint->meLineType = eLineType;
+ pNewRegionBandPoint->mpNextBandPoint = pRegionBandPoint;
+
+ // connections to the new point
+ if ( !pLastTestedRegionBandPoint )
+ mpFirstBandPoint = pNewRegionBandPoint;
+ else
+ pLastTestedRegionBandPoint->mpNextBandPoint = pNewRegionBandPoint;
+
+ return true;
+ }
+
+ // use next element
+ pLastTestedRegionBandPoint = pRegionBandPoint;
+ pRegionBandPoint = pRegionBandPoint->mpNextBandPoint;
+ }
+
+ // not inserted -> add to the end of the list
+ pNewRegionBandPoint = new ImplRegionBandPoint;
+ pNewRegionBandPoint->mnX = nX;
+ pNewRegionBandPoint->mnLineId = nLineId;
+ pNewRegionBandPoint->mbEndPoint = bEndPoint;
+ pNewRegionBandPoint->meLineType = eLineType;
+ pNewRegionBandPoint->mpNextBandPoint = nullptr;
+
+ // connections to the new point
+ pLastTestedRegionBandPoint->mpNextBandPoint = pNewRegionBandPoint;
+
+ return true;
+}
+
+void ImplRegionBand::MoveX( tools::Long nHorzMove )
+{
+ // move all x-separations
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ pSep->mnXLeft += nHorzMove;
+ pSep->mnXRight += nHorzMove;
+ pSep = pSep->mpNextSep;
+ }
+}
+
+void ImplRegionBand::ScaleX( double fHorzScale )
+{
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ pSep->mnXLeft = FRound( pSep->mnXLeft * fHorzScale );
+ pSep->mnXRight = FRound( pSep->mnXRight * fHorzScale );
+ pSep = pSep->mpNextSep;
+ }
+}
+
+// combine overlapping separations
+
+void ImplRegionBand::OptimizeBand()
+{
+ ImplRegionBandSep* pPrevSep = nullptr;
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ // remove?
+ if ( pSep->mbRemoved || (pSep->mnXRight < pSep->mnXLeft) )
+ {
+ ImplRegionBandSep* pOldSep = pSep;
+ if ( pSep == mpFirstSep )
+ mpFirstSep = pSep->mpNextSep;
+ else
+ pPrevSep->mpNextSep = pSep->mpNextSep;
+ pSep = pSep->mpNextSep;
+ delete pOldSep;
+ continue;
+ }
+
+ // overlapping separations? -> combine!
+ if ( pSep->mpNextSep )
+ {
+ if ( (pSep->mnXRight+1) >= pSep->mpNextSep->mnXLeft )
+ {
+ if ( pSep->mpNextSep->mnXRight > pSep->mnXRight )
+ pSep->mnXRight = pSep->mpNextSep->mnXRight;
+
+ ImplRegionBandSep* pOldSep = pSep->mpNextSep;
+ pSep->mpNextSep = pOldSep->mpNextSep;
+ delete pOldSep;
+ continue;
+ }
+ }
+
+ pPrevSep = pSep;
+ pSep = pSep->mpNextSep;
+ }
+}
+
+void ImplRegionBand::Union( tools::Long nXLeft, tools::Long nXRight )
+{
+ SAL_WARN_IF( nXLeft > nXRight, "vcl", "ImplRegionBand::Union(): nxLeft > nXRight" );
+
+ // band empty? -> add element
+ if ( !mpFirstSep )
+ {
+ mpFirstSep = new ImplRegionBandSep;
+ mpFirstSep->mnXLeft = nXLeft;
+ mpFirstSep->mnXRight = nXRight;
+ mpFirstSep->mbRemoved = false;
+ mpFirstSep->mpNextSep = nullptr;
+ return;
+ }
+
+ // process real union
+ ImplRegionBandSep* pNewSep;
+ ImplRegionBandSep* pPrevSep = nullptr;
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ // new separation completely inside? nothing to do!
+ if ( (nXLeft >= pSep->mnXLeft) && (nXRight <= pSep->mnXRight) )
+ return;
+
+ // new separation completely left? -> new separation!
+ if ( nXRight < pSep->mnXLeft )
+ {
+ pNewSep = new ImplRegionBandSep;
+ pNewSep->mnXLeft = nXLeft;
+ pNewSep->mnXRight = nXRight;
+ pNewSep->mbRemoved = false;
+
+ pNewSep->mpNextSep = pSep;
+ if ( pSep == mpFirstSep )
+ mpFirstSep = pNewSep;
+ else
+ pPrevSep->mpNextSep = pNewSep;
+ break;
+ }
+
+ // new separation overlapping from left? -> extend boundary
+ if ( (nXRight >= pSep->mnXLeft) && (nXLeft <= pSep->mnXLeft) )
+ pSep->mnXLeft = nXLeft;
+
+ // new separation overlapping from right? -> extend boundary
+ if ( (nXLeft <= pSep->mnXRight) && (nXRight > pSep->mnXRight) )
+ {
+ pSep->mnXRight = nXRight;
+ break;
+ }
+
+ // not inserted, but last element? -> add to the end of the list
+ if ( !pSep->mpNextSep && (nXLeft > pSep->mnXRight) )
+ {
+ pNewSep = new ImplRegionBandSep;
+ pNewSep->mnXLeft = nXLeft;
+ pNewSep->mnXRight = nXRight;
+ pNewSep->mbRemoved = false;
+
+ pSep->mpNextSep = pNewSep;
+ pNewSep->mpNextSep = nullptr;
+ break;
+ }
+
+ pPrevSep = pSep;
+ pSep = pSep->mpNextSep;
+ }
+
+ OptimizeBand();
+}
+
+void ImplRegionBand::Intersect( tools::Long nXLeft, tools::Long nXRight )
+{
+ SAL_WARN_IF( nXLeft > nXRight, "vcl", "ImplRegionBand::Intersect(): nxLeft > nXRight" );
+
+ // band has been touched
+ mbTouched = true;
+
+ // band empty? -> nothing to do
+ if ( !mpFirstSep )
+ return;
+
+ // process real intersection
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ // new separation completely outside? -> remove separation
+ if ( (nXRight < pSep->mnXLeft) || (nXLeft > pSep->mnXRight) )
+ // will be removed from the optimizer
+ pSep->mbRemoved = true;
+
+ // new separation overlapping from left? -> reduce right boundary
+ if ( (nXLeft <= pSep->mnXLeft) &&
+ (nXRight <= pSep->mnXRight) &&
+ (nXRight >= pSep->mnXLeft) )
+ pSep->mnXRight = nXRight;
+
+ // new separation overlapping from right? -> reduce right boundary
+ if ( (nXLeft >= pSep->mnXLeft) &&
+ (nXLeft <= pSep->mnXRight) &&
+ (nXRight >= pSep->mnXRight) )
+ pSep->mnXLeft = nXLeft;
+
+ // new separation within the actual one? -> reduce both boundaries
+ if ( (nXLeft >= pSep->mnXLeft) && (nXRight <= pSep->mnXRight) )
+ {
+ pSep->mnXRight = nXRight;
+ pSep->mnXLeft = nXLeft;
+ }
+
+ pSep = pSep->mpNextSep;
+ }
+
+ OptimizeBand();
+}
+
+void ImplRegionBand::Exclude( tools::Long nXLeft, tools::Long nXRight )
+{
+ SAL_WARN_IF( nXLeft > nXRight, "vcl", "ImplRegionBand::Exclude(): nxLeft > nXRight" );
+
+ // band has been touched
+ mbTouched = true;
+
+ // band empty? -> nothing to do
+ if ( !mpFirstSep )
+ return;
+
+ // process real exclusion
+ ImplRegionBandSep* pNewSep;
+ ImplRegionBandSep* pPrevSep = nullptr;
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ bool bSepProcessed = false;
+
+ // new separation completely overlapping? -> remove separation
+ if ( (nXLeft <= pSep->mnXLeft) && (nXRight >= pSep->mnXRight) )
+ {
+ // will be removed from the optimizer
+ pSep->mbRemoved = true;
+ bSepProcessed = true;
+ }
+
+ // new separation overlapping from left? -> reduce boundary
+ if ( !bSepProcessed )
+ {
+ if ( (nXRight >= pSep->mnXLeft) && (nXLeft <= pSep->mnXLeft) )
+ {
+ pSep->mnXLeft = nXRight+1;
+ bSepProcessed = true;
+ }
+ }
+
+ // new separation overlapping from right? -> reduce boundary
+ if ( !bSepProcessed )
+ {
+ if ( (nXLeft <= pSep->mnXRight) && (nXRight > pSep->mnXRight) )
+ {
+ pSep->mnXRight = nXLeft-1;
+ bSepProcessed = true;
+ }
+ }
+
+ // new separation within the actual one? -> reduce boundary
+ // and add new entry for reminder
+ if ( !bSepProcessed )
+ {
+ if ( (nXLeft >= pSep->mnXLeft) && (nXRight <= pSep->mnXRight) )
+ {
+ pNewSep = new ImplRegionBandSep;
+ pNewSep->mnXLeft = pSep->mnXLeft;
+ pNewSep->mnXRight = nXLeft-1;
+ pNewSep->mbRemoved = false;
+
+ pSep->mnXLeft = nXRight+1;
+
+ // connections from the new separation
+ pNewSep->mpNextSep = pSep;
+
+ // connections to the new separation
+ if ( pSep == mpFirstSep )
+ mpFirstSep = pNewSep;
+ else
+ pPrevSep->mpNextSep = pNewSep;
+ }
+ }
+
+ pPrevSep = pSep;
+ pSep = pSep->mpNextSep;
+ }
+
+ OptimizeBand();
+}
+
+void ImplRegionBand::XOr( tools::Long nXLeft, tools::Long nXRight )
+{
+ SAL_WARN_IF( nXLeft > nXRight, "vcl", "ImplRegionBand::XOr(): nxLeft > nXRight" );
+
+ // #i46602# Reworked rectangle Xor
+
+ // In general, we can distinguish 11 cases of intersection
+ // (details below). The old implementation explicitly handled 7
+ // cases (numbered in the order of appearance, use CVS to get your
+ // hands on the old version), therefore, I've sticked to that
+ // order, and added four more cases. The code below references
+ // those numbers via #1, #2, etc.
+
+ // Num Mnem newX:oldX newY:oldY Description Result Can quit?
+
+ // #1 Empty band - - The band is empty, thus, simply add new bandSep just add Yes
+
+ // #2 apart - - The rectangles are disjunct, add new one as is just add Yes
+
+ // #3 atop == == The rectangles are _exactly_ the same, remove existing just remove Yes
+
+ // #4 around < > The new rectangle extends the old to both sides intersect No
+
+ // #5 left < < The new rectangle is left of the old (but intersects) intersect Yes
+
+ // #5b left-atop < == The new is left of the old, and coincides on the right intersect Yes
+
+ // #6 right > > The new is right of the old (but intersects) intersect No
+
+ // #6b right-atop == > The new is right of the old, and coincides on the left intersect No
+
+ // #7 inside > < The new is fully inside the old intersect Yes
+
+ // #8 inside-right > == The new is fully inside the old, coincides on the right intersect Yes
+
+ // #9 inside-left == < The new is fully inside the old, coincides on the left intersect Yes
+
+ // Then, to correctly perform XOr, the segment that's switched off
+ // (i.e. the overlapping part of the old and the new segment) must
+ // be extended by one pixel value at each border:
+ // 1 1
+ // 0 4 0 4
+ // 111100000001111
+
+ // Clearly, the leading band sep now goes from 0 to 3, and the
+ // trailing band sep from 11 to 14. This mimics the xor look of a
+ // bitmap operation.
+
+ // band empty? -> add element
+ if ( !mpFirstSep )
+ {
+ mpFirstSep = new ImplRegionBandSep;
+ mpFirstSep->mnXLeft = nXLeft;
+ mpFirstSep->mnXRight = nXRight;
+ mpFirstSep->mbRemoved = false;
+ mpFirstSep->mpNextSep = nullptr;
+ return;
+ }
+
+ // process real xor
+ ImplRegionBandSep* pNewSep;
+ ImplRegionBandSep* pPrevSep = nullptr;
+ ImplRegionBandSep* pSep = mpFirstSep;
+
+ while ( pSep )
+ {
+ tools::Long nOldLeft( pSep->mnXLeft );
+ tools::Long nOldRight( pSep->mnXRight );
+
+ // did the current segment actually touch the new rect? If
+ // not, skip all comparisons, go on, loop and try to find
+ // intersecting bandSep
+ if( nXLeft <= nOldRight )
+ {
+ if( nXRight < nOldLeft )
+ {
+ // #2
+
+ // add _before_ current bandSep
+ pNewSep = new ImplRegionBandSep;
+ pNewSep->mnXLeft = nXLeft;
+ pNewSep->mnXRight = nXRight;
+ pNewSep->mpNextSep = pSep;
+ pNewSep->mbRemoved = false;
+
+ // connections from the new separation
+ pNewSep->mpNextSep = pSep;
+
+ // connections to the new separation
+ if ( pSep == mpFirstSep )
+ mpFirstSep = pNewSep;
+ else
+ pPrevSep->mpNextSep = pNewSep;
+ pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop
+ break;
+ }
+ else if( nXLeft == nOldLeft && nXRight == nOldRight )
+ {
+ // #3
+ pSep->mbRemoved = true;
+ pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop
+ break;
+ }
+ else if( nXLeft != nOldLeft && nXRight == nOldRight )
+ {
+ // # 5b, 8
+ if( nXLeft < nOldLeft )
+ {
+ nXRight = nOldLeft; // 5b
+ }
+ else
+ {
+ nXRight = nXLeft; // 8
+ nXLeft = nOldLeft;
+ }
+
+ pSep->mnXLeft = nXLeft;
+ pSep->mnXRight = nXRight-1;
+
+ pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop
+ break;
+ }
+ else if( nXLeft == nOldLeft && nXRight != nOldRight )
+ {
+ // # 6b, 9
+
+ if( nXRight > nOldRight )
+ {
+ nXLeft = nOldRight+1; // 6b
+
+ // cannot break here, simply mark segment as removed,
+ // and go on with adapted nXLeft/nXRight
+ pSep->mbRemoved = true;
+ }
+ else
+ {
+ pSep->mnXLeft = nXRight+1; // 9
+
+ pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop
+ break;
+ }
+ }
+ else // if( nXLeft != nOldLeft && nXRight != nOldRight ) follows automatically
+ {
+ // #4,5,6,7
+ SAL_WARN_IF( nXLeft == nOldLeft || nXRight == nOldRight, "vcl",
+ "ImplRegionBand::XOr(): Case 4,5,6,7 expected all coordinates to be not equal!" );
+
+ // The plain-jane check would look like this:
+
+ // if( nXLeft < nOldLeft )
+ // {
+ // // #4,5
+ // if( nXRight > nOldRight )
+ // {
+ // // #4
+ // }
+ // else
+ // {
+ // // #5 done!
+ // }
+ // }
+ // else
+ // {
+ // // #6,7
+ // if( nXRight > nOldRight )
+ // {
+ // // #6
+ // }
+ // else
+ // {
+ // // #7 done!
+ // }
+ // }
+
+ // but since we generally don't have to care whether
+ // it's 4 or 6 (only that we must not stop processing
+ // here), condensed that in such a way that only the
+ // coordinates get shuffled into correct ordering.
+
+ if( nXLeft < nOldLeft )
+ ::std::swap( nOldLeft, nXLeft );
+
+ bool bDone( false );
+
+ if( nXRight < nOldRight )
+ {
+ ::std::swap( nOldRight, nXRight );
+ bDone = true;
+ }
+
+ // now, nOldLeft<nXLeft<=nOldRight<nXRight always
+ // holds. Note that we need the nXLeft<=nOldRight here, as
+ // the intersection part might be only one pixel (original
+ // nXLeft==nXRight)
+ SAL_WARN_IF( nOldLeft==nXLeft || nXLeft>nOldRight || nOldRight>=nXRight, "vcl",
+ "ImplRegionBand::XOr(): Case 4,5,6,7 expected coordinates to be ordered now!" );
+
+ pSep->mnXLeft = nOldLeft;
+ pSep->mnXRight = nXLeft-1;
+
+ nXLeft = nOldRight+1;
+ // nxRight is already setup correctly
+
+ if( bDone )
+ {
+ // add behind current bandSep
+ pNewSep = new ImplRegionBandSep;
+
+ pNewSep->mnXLeft = nXLeft;
+ pNewSep->mnXRight = nXRight;
+ pNewSep->mpNextSep = pSep->mpNextSep;
+ pNewSep->mbRemoved = false;
+
+ // connections from the new separation
+ pSep->mpNextSep = pNewSep;
+
+ pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop
+ break;
+ }
+ }
+ }
+
+ pPrevSep = pSep;
+ pSep = pSep->mpNextSep;
+ }
+
+ // new separation completely right of existing bandSeps ?
+ if( pPrevSep && nXLeft >= pPrevSep->mnXRight )
+ {
+ pNewSep = new ImplRegionBandSep;
+ pNewSep->mnXLeft = nXLeft;
+ pNewSep->mnXRight = nXRight;
+ pNewSep->mpNextSep = nullptr;
+ pNewSep->mbRemoved = false;
+
+ // connections from the new separation
+ pPrevSep->mpNextSep = pNewSep;
+ }
+
+ OptimizeBand();
+}
+
+bool ImplRegionBand::Contains( tools::Long nX )
+{
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ if ( (pSep->mnXLeft <= nX) && (pSep->mnXRight >= nX) )
+ return true;
+
+ pSep = pSep->mpNextSep;
+ }
+
+ return false;
+}
+
+tools::Long ImplRegionBand::GetXLeftBoundary() const
+{
+ SAL_WARN_IF(mpFirstSep == nullptr, "vcl", "ImplRegionBand::XLeftBoundary -> no separation in band!");
+
+ return mpFirstSep ? mpFirstSep->mnXLeft : 0;
+}
+
+tools::Long ImplRegionBand::GetXRightBoundary() const
+{
+ SAL_WARN_IF( mpFirstSep == nullptr, "vcl", "ImplRegionBand::XRightBoundary -> no separation in band!" );
+ if (!mpFirstSep)
+ return 0;
+ // search last separation
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep->mpNextSep )
+ pSep = pSep->mpNextSep;
+ return pSep->mnXRight;
+}
+
+bool ImplRegionBand::operator==( const ImplRegionBand& rRegionBand ) const
+{
+ ImplRegionBandSep* pOwnRectBandSep = mpFirstSep;
+ ImplRegionBandSep* pSecondRectBandSep = rRegionBand.mpFirstSep;
+ while ( pOwnRectBandSep && pSecondRectBandSep )
+ {
+ // get boundaries of current rectangle
+ tools::Long nOwnXLeft = pOwnRectBandSep->mnXLeft;
+ tools::Long nSecondXLeft = pSecondRectBandSep->mnXLeft;
+ if ( nOwnXLeft != nSecondXLeft )
+ return false;
+
+ tools::Long nOwnXRight = pOwnRectBandSep->mnXRight;
+ tools::Long nSecondXRight = pSecondRectBandSep->mnXRight;
+ if ( nOwnXRight != nSecondXRight )
+ return false;
+
+ // get next separation from current band
+ pOwnRectBandSep = pOwnRectBandSep->mpNextSep;
+
+ // get next separation from current band
+ pSecondRectBandSep = pSecondRectBandSep->mpNextSep;
+ }
+
+ // different number of separations?
+ return !(pOwnRectBandSep || pSecondRectBandSep);
+}
+
+ImplRegionBand* ImplRegionBand::SplitBand (const sal_Int32 nY)
+{
+ OSL_ASSERT(nY>mnYTop);
+ OSL_ASSERT(nY<=mnYBottom);
+
+ // Create a copy of the given band (we tell the constructor to copy the points together
+ // with the seps.)
+ ImplRegionBand* pLowerBand = new ImplRegionBand(*this, false);
+
+ // Adapt vertical coordinates.
+ mnYBottom = nY-1;
+ pLowerBand->mnYTop = nY;
+
+ // Insert new band into list of bands.
+ pLowerBand->mpNextBand = mpNextBand;
+ mpNextBand = pLowerBand;
+ pLowerBand->mpPrevBand = this;
+ if (pLowerBand->mpNextBand != nullptr)
+ pLowerBand->mpNextBand->mpPrevBand = pLowerBand;
+
+ return pLowerBand;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/region.cxx b/vcl/source/gdi/region.cxx
new file mode 100644
index 0000000000..6665b5c48d
--- /dev/null
+++ b/vcl/source/gdi/region.cxx
@@ -0,0 +1,1793 @@
+/* -*- 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 <memory>
+#include <tools/vcompat.hxx>
+#include <tools/stream.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/region.hxx>
+#include <regionband.hxx>
+
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolygonclipper.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <tools/poly.hxx>
+#include <unotools/configmgr.hxx>
+
+namespace
+{
+ /** Return <TRUE/> when the given polygon is rectilinear and oriented so that
+ all sides are either horizontal or vertical.
+ */
+ bool ImplIsPolygonRectilinear (const tools::PolyPolygon& rPolyPoly)
+ {
+ // Iterate over all polygons.
+ const sal_uInt16 nPolyCount = rPolyPoly.Count();
+ for (sal_uInt16 nPoly = 0; nPoly < nPolyCount; ++nPoly)
+ {
+ const tools::Polygon& aPoly = rPolyPoly.GetObject(nPoly);
+
+ // Iterate over all edges of the current polygon.
+ const sal_uInt16 nSize = aPoly.GetSize();
+
+ if (nSize < 2)
+ continue;
+ Point aPoint (aPoly.GetPoint(0));
+ const Point aLastPoint (aPoint);
+ for (sal_uInt16 nPoint = 1; nPoint < nSize; ++nPoint)
+ {
+ const Point aNextPoint (aPoly.GetPoint(nPoint));
+ // When there is at least one edge that is neither vertical nor
+ // horizontal then the entire polygon is not rectilinear (and
+ // oriented along primary axes.)
+ if (aPoint.X() != aNextPoint.X() && aPoint.Y() != aNextPoint.Y())
+ return false;
+
+ aPoint = aNextPoint;
+ }
+ // Compare closing edge.
+ if (aLastPoint.X() != aPoint.X() && aLastPoint.Y() != aPoint.Y())
+ return false;
+ }
+ return true;
+ }
+
+ /** Convert a rectilinear polygon (that is oriented along the primary axes)
+ to a list of bands. For this special form of polygon we can use an
+ optimization that prevents the creation of one band per y value.
+ However, it still is possible that some temporary bands are created that
+ later can be optimized away.
+ @param rPolyPolygon
+ A set of zero, one, or more polygons, nested or not, that are
+ converted into a list of bands.
+ @return
+ A new RegionBand object is returned that contains the bands that
+ represent the given poly-polygon.
+ */
+ std::shared_ptr<RegionBand> ImplRectilinearPolygonToBands(const tools::PolyPolygon& rPolyPoly)
+ {
+ OSL_ASSERT(ImplIsPolygonRectilinear (rPolyPoly));
+
+ // Create a new RegionBand object as container of the bands.
+ std::shared_ptr<RegionBand> pRegionBand( std::make_shared<RegionBand>() );
+ tools::Long nLineId = 0;
+
+ // Iterate over all polygons.
+ const sal_uInt16 nPolyCount = rPolyPoly.Count();
+ for (sal_uInt16 nPoly = 0; nPoly < nPolyCount; ++nPoly)
+ {
+ const tools::Polygon& aPoly = rPolyPoly.GetObject(nPoly);
+
+ // Iterate over all edges of the current polygon.
+ const sal_uInt16 nSize = aPoly.GetSize();
+ if (nSize < 2)
+ continue;
+ // Avoid fetching every point twice (each point is the start point
+ // of one and the end point of another edge.)
+ Point aStart (aPoly.GetPoint(0));
+ Point aEnd;
+ for (sal_uInt16 nPoint = 1; nPoint <= nSize; ++nPoint, aStart=aEnd)
+ {
+ // We take the implicit closing edge into account by mapping
+ // index nSize to 0.
+ aEnd = aPoly.GetPoint(nPoint%nSize);
+ if (aStart.Y() == aEnd.Y())
+ {
+ // Horizontal lines are ignored.
+ continue;
+ }
+
+ // At this point the line has to be vertical.
+ OSL_ASSERT(aStart.X() == aEnd.X());
+
+ // Sort y-coordinates to simplify the algorithm and store the
+ // direction separately. The direction is calculated as it is
+ // in other places (but seems to be the wrong way.)
+ const tools::Long nTop (::std::min(aStart.Y(), aEnd.Y()));
+ const tools::Long nBottom (::std::max(aStart.Y(), aEnd.Y()));
+ const LineType eLineType (aStart.Y() > aEnd.Y() ? LineType::Descending : LineType::Ascending);
+
+ // Make sure that the current line is covered by bands.
+ pRegionBand->ImplAddMissingBands(nTop,nBottom);
+
+ // Find top-most band that may contain nTop.
+ ImplRegionBand* pBand = pRegionBand->ImplGetFirstRegionBand();
+ while (pBand!=nullptr && pBand->mnYBottom < nTop)
+ pBand = pBand->mpNextBand;
+ ImplRegionBand* pTopBand = pBand;
+ // If necessary split the band at nTop so that nTop is contained
+ // in the lower band.
+ if (pBand!=nullptr
+ // Prevent the current band from becoming 0 pixel high
+ && pBand->mnYTop<nTop
+ // this allows the lowest pixel of the band to be split off
+ && pBand->mnYBottom>=nTop
+ // do not split a band that is just one pixel high
+ && pBand->mnYTop<pBand->mnYBottom-1)
+ {
+ // Split the top band.
+ pTopBand = pBand->SplitBand(nTop);
+ }
+
+ // Advance to band that may contain nBottom.
+ while (pBand!=nullptr && pBand->mnYBottom < nBottom)
+ pBand = pBand->mpNextBand;
+ // The lowest band may have to be split at nBottom so that
+ // nBottom itself remains in the upper band.
+ if (pBand!=nullptr
+ // allow the current band becoming 1 pixel high
+ && pBand->mnYTop<=nBottom
+ // prevent splitting off a band that is 0 pixel high
+ && pBand->mnYBottom>nBottom
+ // do not split a band that is just one pixel high
+ && pBand->mnYTop<pBand->mnYBottom-1)
+ {
+ // Split the bottom band.
+ pBand->SplitBand(nBottom+1);
+ }
+
+ // Note that we remember the top band (in pTopBand) but not the
+ // bottom band. The later can be determined by comparing y
+ // coordinates.
+
+ // Add the x-value as point to all bands in the nTop->nBottom range.
+ for (pBand=pTopBand; pBand!=nullptr&&pBand->mnYTop<=nBottom; pBand=pBand->mpNextBand)
+ pBand->InsertPoint(aStart.X(), nLineId++, true, eLineType);
+ }
+ }
+
+ return pRegionBand;
+ }
+
+ /** Convert a general polygon (one for which ImplIsPolygonRectilinear()
+ returns <FALSE/>) to bands.
+ */
+ std::shared_ptr<RegionBand> ImplGeneralPolygonToBands(const tools::PolyPolygon& rPolyPoly, const tools::Rectangle& rPolygonBoundingBox)
+ {
+ tools::Long nLineID = 0;
+
+ // initialisation and creation of Bands
+ std::shared_ptr<RegionBand> pRegionBand( std::make_shared<RegionBand>() );
+ pRegionBand->CreateBandRange(rPolygonBoundingBox.Top(), rPolygonBoundingBox.Bottom());
+
+ // insert polygons
+ const sal_uInt16 nPolyCount = rPolyPoly.Count();
+
+ for ( sal_uInt16 nPoly = 0; nPoly < nPolyCount; nPoly++ )
+ {
+ // get reference to current polygon
+ const tools::Polygon& aPoly = rPolyPoly.GetObject( nPoly );
+ const sal_uInt16 nSize = aPoly.GetSize();
+
+ // not enough points ( <= 2 )? -> nothing to do!
+ if ( nSize <= 2 )
+ continue;
+
+ // band the polygon
+ for ( sal_uInt16 nPoint = 1; nPoint < nSize; nPoint++ )
+ {
+ pRegionBand->InsertLine( aPoly.GetPoint(nPoint-1), aPoly.GetPoint(nPoint), nLineID++ );
+ }
+
+ // close polygon with line from first point to last point, if necessary
+ const Point rLastPoint = aPoly.GetPoint(nSize-1);
+ const Point rFirstPoint = aPoly.GetPoint(0);
+
+ if ( rLastPoint != rFirstPoint )
+ {
+ pRegionBand->InsertLine( rLastPoint, rFirstPoint, nLineID++ );
+ }
+ }
+
+ return pRegionBand;
+ }
+} // end of anonymous namespace
+
+namespace vcl {
+
+bool vcl::Region::IsEmpty() const
+{
+ return !mbIsNull && !mpB2DPolyPolygon && !mpPolyPolygon && !mpRegionBand;
+}
+
+
+static std::shared_ptr<RegionBand> ImplCreateRegionBandFromPolyPolygon(const tools::PolyPolygon& rPolyPolygon)
+{
+ std::shared_ptr<RegionBand> pRetval;
+
+ if(rPolyPolygon.Count())
+ {
+ // ensure to subdivide when bezier segments are used, it's going to
+ // be expanded to rectangles
+ tools::PolyPolygon aPolyPolygon;
+
+ rPolyPolygon.AdaptiveSubdivide(aPolyPolygon);
+
+ if(aPolyPolygon.Count())
+ {
+ const tools::Rectangle aRect(aPolyPolygon.GetBoundRect());
+
+ if(!aRect.IsEmpty())
+ {
+ if(ImplIsPolygonRectilinear(aPolyPolygon))
+ {
+ // For rectilinear polygons there is an optimized band conversion.
+ pRetval = ImplRectilinearPolygonToBands(aPolyPolygon);
+ }
+ else
+ {
+ pRetval = ImplGeneralPolygonToBands(aPolyPolygon, aRect);
+ }
+
+ // Convert points into seps.
+ if(pRetval)
+ {
+ pRetval->processPoints();
+
+ // Optimize list of bands. Adjacent bands with identical lists
+ // of seps are joined.
+ if(!pRetval->OptimizeBandList())
+ {
+ pRetval.reset();
+ }
+ }
+ }
+ }
+ }
+
+ return pRetval;
+}
+
+tools::PolyPolygon vcl::Region::ImplCreatePolyPolygonFromRegionBand() const
+{
+ tools::PolyPolygon aRetval;
+
+ if(getRegionBand())
+ {
+ RectangleVector aRectangles;
+ GetRegionRectangles(aRectangles);
+
+ for (auto const& rectangle : aRectangles)
+ {
+ aRetval.Insert( tools::Polygon(rectangle) );
+ }
+ }
+ else
+ {
+ OSL_ENSURE(false, "Called with no local RegionBand (!)");
+ }
+
+ return aRetval;
+}
+
+basegfx::B2DPolyPolygon vcl::Region::ImplCreateB2DPolyPolygonFromRegionBand() const
+{
+ tools::PolyPolygon aPoly(ImplCreatePolyPolygonFromRegionBand());
+
+ return aPoly.getB2DPolyPolygon();
+}
+
+Region::Region(bool bIsNull)
+: mbIsNull(bIsNull)
+{
+}
+
+Region::Region(const tools::Rectangle& rRect)
+: mbIsNull(false)
+{
+ if (!rRect.IsEmpty())
+ mpRegionBand = std::make_shared<RegionBand>(rRect);
+}
+
+Region::Region(const tools::Polygon& rPolygon)
+: mbIsNull(false)
+{
+
+ if(rPolygon.GetSize())
+ {
+ ImplCreatePolyPolyRegion(tools::PolyPolygon(rPolygon));
+ }
+}
+
+Region::Region(const tools::PolyPolygon& rPolyPoly)
+: mbIsNull(false)
+{
+
+ if(rPolyPoly.Count())
+ {
+ ImplCreatePolyPolyRegion(rPolyPoly);
+ }
+}
+
+Region::Region(const basegfx::B2DPolyPolygon& rPolyPoly)
+: mbIsNull(false)
+{
+
+ if(rPolyPoly.count())
+ {
+ ImplCreatePolyPolyRegion(rPolyPoly);
+ }
+}
+
+Region::Region(const vcl::Region&) = default;
+
+Region::Region(vcl::Region&& rRegion) noexcept
+: mpB2DPolyPolygon(std::move(rRegion.mpB2DPolyPolygon)),
+ mpPolyPolygon(std::move(rRegion.mpPolyPolygon)),
+ mpRegionBand(std::move(rRegion.mpRegionBand)),
+ mbIsNull(rRegion.mbIsNull)
+{
+ rRegion.mbIsNull = true;
+}
+
+Region::~Region() = default;
+
+void vcl::Region::ImplCreatePolyPolyRegion( const tools::PolyPolygon& rPolyPoly )
+{
+ const sal_uInt16 nPolyCount = rPolyPoly.Count();
+
+ if(!nPolyCount)
+ return;
+
+ // polypolygon empty? -> empty region
+ const tools::Rectangle aRect(rPolyPoly.GetBoundRect());
+
+ if(aRect.IsEmpty())
+ return;
+
+ // width OR height == 1 ? => Rectangular region
+ if((1 == aRect.GetWidth()) || (1 == aRect.GetHeight()) || rPolyPoly.IsRect())
+ {
+ mpRegionBand = std::make_shared<RegionBand>(aRect);
+ }
+ else
+ {
+ mpPolyPolygon = rPolyPoly;
+ }
+
+ mbIsNull = false;
+}
+
+void vcl::Region::ImplCreatePolyPolyRegion( const basegfx::B2DPolyPolygon& rPolyPoly )
+{
+ if(rPolyPoly.count() && !rPolyPoly.getB2DRange().isEmpty())
+ {
+ mpB2DPolyPolygon = rPolyPoly;
+ mbIsNull = false;
+ }
+}
+
+void vcl::Region::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ if(IsNull() || IsEmpty())
+ {
+ // empty or null need no move
+ return;
+ }
+
+ if(!nHorzMove && !nVertMove)
+ {
+ // no move defined
+ return;
+ }
+
+ if(getB2DPolyPolygon())
+ {
+ basegfx::B2DPolyPolygon aPoly(*getB2DPolyPolygon());
+
+ aPoly.transform(basegfx::utils::createTranslateB2DHomMatrix(nHorzMove, nVertMove));
+ if (aPoly.count())
+ mpB2DPolyPolygon = aPoly;
+ else
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+ else if(getPolyPolygon())
+ {
+ tools::PolyPolygon aPoly(*getPolyPolygon());
+
+ aPoly.Move(nHorzMove, nVertMove);
+ mpB2DPolyPolygon.reset();
+ if (aPoly.Count())
+ mpPolyPolygon = aPoly;
+ else
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+ else if(getRegionBand())
+ {
+ std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*getRegionBand());
+
+ pNew->Move(nHorzMove, nVertMove);
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand = std::move(pNew);
+ }
+ else
+ {
+ OSL_ENSURE(false, "Region::Move error: impossible combination (!)");
+ }
+}
+
+void vcl::Region::Scale( double fScaleX, double fScaleY )
+{
+ if(IsNull() || IsEmpty())
+ {
+ // empty or null need no scale
+ return;
+ }
+
+ if(basegfx::fTools::equalZero(fScaleX) && basegfx::fTools::equalZero(fScaleY))
+ {
+ // no scale defined
+ return;
+ }
+
+ if(getB2DPolyPolygon())
+ {
+ basegfx::B2DPolyPolygon aPoly(*getB2DPolyPolygon());
+
+ aPoly.transform(basegfx::utils::createScaleB2DHomMatrix(fScaleX, fScaleY));
+ if (aPoly.count())
+ mpB2DPolyPolygon = aPoly;
+ else
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+ else if(getPolyPolygon())
+ {
+ tools::PolyPolygon aPoly(*getPolyPolygon());
+
+ aPoly.Scale(fScaleX, fScaleY);
+ mpB2DPolyPolygon.reset();
+ if (aPoly.Count())
+ mpPolyPolygon = aPoly;
+ else
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+ else if(getRegionBand())
+ {
+ std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*getRegionBand());
+
+ pNew->Scale(fScaleX, fScaleY);
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand = std::move(pNew);
+ }
+ else
+ {
+ OSL_ENSURE(false, "Region::Scale error: impossible combination (!)");
+ }
+}
+
+void vcl::Region::Union( const tools::Rectangle& rRect )
+{
+ if(rRect.IsEmpty())
+ {
+ // empty rectangle will not expand the existing union, nothing to do
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // no local data, the union will be equal to source. Create using rectangle
+ *this = rRect;
+ return;
+ }
+
+ if(HasPolyPolygonOrB2DPolyPolygon())
+ {
+ // get this B2DPolyPolygon, solve on polygon base
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly);
+
+ if(!aThisPolyPoly.count())
+ {
+ // no local polygon, use the rectangle as new region
+ *this = rRect;
+ }
+ else
+ {
+ // get the other B2DPolyPolygon and use logical Or-Operation
+ const basegfx::B2DPolygon aRectPoly(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect)));
+ const basegfx::B2DPolyPolygon aClip(
+ basegfx::utils::solvePolygonOperationOr(
+ aThisPolyPoly,
+ basegfx::B2DPolyPolygon(aRectPoly)));
+ *this = vcl::Region(aClip);
+ }
+
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // no region band, create using the rectangle
+ *this = rRect;
+ return;
+ }
+
+ std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*pCurrent);
+
+ // get justified rectangle
+ const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
+ const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));
+
+ // insert bands if the boundaries are not already in the list
+ pNew->InsertBands(nTop, nBottom);
+
+ // process union
+ pNew->Union(nLeft, nTop, nRight, nBottom);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+}
+
+void vcl::Region::Intersect( const tools::Rectangle& rRect )
+{
+ if ( rRect.IsEmpty() )
+ {
+ // empty rectangle will create empty region
+ SetEmpty();
+ return;
+ }
+
+ if(IsNull())
+ {
+ // null region (everything) intersect with rect will give rect
+ *this = rRect;
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // no content, cannot get more empty
+ return;
+ }
+
+ if(HasPolyPolygonOrB2DPolyPolygon())
+ {
+ // if polygon data prefer double precision, the other will be lost (if buffered)
+ if(getB2DPolyPolygon())
+ {
+ const basegfx::B2DPolyPolygon aPoly(
+ basegfx::utils::clipPolyPolygonOnRange(
+ *getB2DPolyPolygon(),
+ basegfx::B2DRange(
+ rRect.Left(),
+ rRect.Top(),
+ rRect.Right() + 1,
+ rRect.Bottom() + 1),
+ true,
+ false));
+
+ if (aPoly.count())
+ mpB2DPolyPolygon = aPoly;
+ else
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+ else // if(getPolyPolygon())
+ {
+ tools::PolyPolygon aPoly(*getPolyPolygon());
+
+ // use the PolyPolygon::Clip method for rectangles, this is
+ // fairly simple (does not even use GPC) and saves us from
+ // unnecessary banding
+ aPoly.Clip(rRect);
+
+ mpB2DPolyPolygon.reset();
+ if (aPoly.Count())
+ mpPolyPolygon = aPoly;
+ else
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // region is empty -> nothing to do!
+ return;
+ }
+
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
+
+ // get justified rectangle
+ const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
+ const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));
+
+ // insert bands if the boundaries are not already in the list
+ pNew->InsertBands(nTop, nBottom);
+
+ // process intersect
+ pNew->Intersect(nLeft, nTop, nRight, nBottom);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+}
+
+void vcl::Region::Exclude( const tools::Rectangle& rRect )
+{
+ if ( rRect.IsEmpty() )
+ {
+ // excluding nothing will do no change
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // cannot exclude from empty, done
+ return;
+ }
+
+ if(IsNull())
+ {
+ // error; cannot exclude from null region since this is not representable
+ // in the data
+ OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
+ return;
+ }
+
+ if( HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ // get this B2DPolyPolygon
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly);
+
+ if(!aThisPolyPoly.count())
+ {
+ // when local polygon is empty, nothing can be excluded
+ return;
+ }
+
+ // get the other B2DPolyPolygon
+ const basegfx::B2DPolygon aRectPoly(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect)));
+ const basegfx::B2DPolyPolygon aOtherPolyPoly(aRectPoly);
+ const basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationDiff(aThisPolyPoly, aOtherPolyPoly);
+
+ *this = vcl::Region(aClip);
+
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ if(!mpRegionBand)
+ {
+ // empty? -> done!
+ return;
+ }
+
+ std::shared_ptr<RegionBand>& pNew = mpRegionBand;
+ // only make a copy if someone else is also using it
+ if (pNew.use_count() > 1)
+ pNew = std::make_shared<RegionBand>(*pNew);
+
+ // get justified rectangle
+ const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
+ const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));
+
+ // insert bands if the boundaries are not already in the list
+ pNew->InsertBands(nTop, nBottom);
+
+ // process exclude
+ pNew->Exclude(nLeft, nTop, nRight, nBottom);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ pNew.reset();
+}
+
+void vcl::Region::XOr( const tools::Rectangle& rRect )
+{
+ if ( rRect.IsEmpty() )
+ {
+ // empty rectangle will not change local content
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // rRect will be the xored-form (local off, rect on)
+ *this = rRect;
+ return;
+ }
+
+ if(IsNull())
+ {
+ // error; cannot exclude from null region since this is not representable
+ // in the data
+ OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
+ return;
+ }
+
+ if( HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ // get this B2DPolyPolygon
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly );
+
+ if(!aThisPolyPoly.count())
+ {
+ // no local content, XOr will be equal to rectangle
+ *this = rRect;
+ return;
+ }
+
+ // get the other B2DPolyPolygon
+ const basegfx::B2DPolygon aRectPoly(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(rRect)));
+ const basegfx::B2DPolyPolygon aOtherPolyPoly(aRectPoly);
+ const basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationXor(aThisPolyPoly, aOtherPolyPoly);
+
+ *this = vcl::Region(aClip);
+
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // rRect will be the xored-form (local off, rect on)
+ *this = rRect;
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*getRegionBand()));
+
+ // get justified rectangle
+ const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
+ const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));
+
+ // insert bands if the boundaries are not already in the list
+ pNew->InsertBands(nTop, nBottom);
+
+ // process xor
+ pNew->XOr(nLeft, nTop, nRight, nBottom);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+}
+
+void vcl::Region::Union( const vcl::Region& rRegion )
+{
+ if(rRegion.IsEmpty())
+ {
+ // no extension at all
+ return;
+ }
+
+ if(rRegion.IsNull())
+ {
+ // extending with null region -> null region
+ *this = vcl::Region(true);
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // local is empty, union will give source region
+ *this = rRegion;
+ return;
+ }
+
+ if(IsNull())
+ {
+ // already fully expanded (is null region), cannot be extended
+ return;
+ }
+
+ if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ // get this B2DPolyPolygon
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly);
+
+ if(!aThisPolyPoly.count())
+ {
+ // when no local content, union will be equal to rRegion
+ *this = rRegion;
+ return;
+ }
+
+ // get the other B2DPolyPolygon
+ basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
+ aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation(aOtherPolyPoly);
+
+ // use logical OR operation
+ basegfx::B2DPolyPolygon aClip(basegfx::utils::solvePolygonOperationOr(aThisPolyPoly, aOtherPolyPoly));
+
+ *this = vcl::Region( aClip );
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // local is empty, union will give source region
+ *this = rRegion;
+ return;
+ }
+
+ const RegionBand* pSource = rRegion.getRegionBand();
+
+ if(!pSource)
+ {
+ // no extension at all
+ return;
+ }
+
+ // prepare source and target
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
+
+ // union with source
+ pNew->Union(*pSource);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+}
+
+void vcl::Region::Intersect( const vcl::Region& rRegion )
+{
+ // same instance data? -> nothing to do!
+ if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion.getB2DPolyPolygon())
+ {
+ return;
+ }
+
+ if(getPolyPolygon() && getPolyPolygon() == rRegion.getPolyPolygon())
+ {
+ return;
+ }
+
+ if(getRegionBand() && getRegionBand() == rRegion.getRegionBand())
+ {
+ return;
+ }
+
+ if(rRegion.IsNull())
+ {
+ // source region is null-region, intersect will not change local region
+ return;
+ }
+
+ if(IsNull())
+ {
+ // when local region is null-region, intersect will be equal to source
+ *this = rRegion;
+ return;
+ }
+
+ if(rRegion.IsEmpty())
+ {
+ // source region is empty, intersection will always be empty
+ SetEmpty();
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // local region is empty, cannot get more empty than that. Nothing to do
+ return;
+ }
+
+ if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ // get this B2DPolyPolygon
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ if(!aThisPolyPoly.count())
+ {
+ // local region is empty, cannot get more empty than that. Nothing to do
+ return;
+ }
+
+ // get the other B2DPolyPolygon
+ basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
+
+ if(!aOtherPolyPoly.count())
+ {
+ // source region is empty, intersection will always be empty
+ SetEmpty();
+ return;
+ }
+
+ static size_t gPointLimit = !utl::ConfigManager::IsFuzzing() ? SAL_MAX_SIZE : 8192;
+ size_t nPointLimit(gPointLimit);
+ const basegfx::B2DPolyPolygon aClip(
+ basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ aOtherPolyPoly,
+ aThisPolyPoly,
+ true,
+ false,
+ &nPointLimit));
+ *this = vcl::Region( aClip );
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // local region is empty, cannot get more empty than that. Nothing to do
+ return;
+ }
+
+ const RegionBand* pSource = rRegion.getRegionBand();
+
+ if(!pSource)
+ {
+ // source region is empty, intersection will always be empty
+ SetEmpty();
+ return;
+ }
+
+ // both RegionBands exist and are not empty
+ if(pCurrent->getRectangleCount() + 2 < pSource->getRectangleCount())
+ {
+ // when we have less rectangles, turn around the call
+ vcl::Region aTempRegion = rRegion;
+ aTempRegion.Intersect( *this );
+ *this = aTempRegion;
+ }
+ else
+ {
+ // prepare new regionBand
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
+
+ // intersect with source
+ pNew->Intersect(*pSource);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+ }
+}
+
+void vcl::Region::Exclude( const vcl::Region& rRegion )
+{
+ if ( rRegion.IsEmpty() )
+ {
+ // excluding nothing will do no change
+ return;
+ }
+
+ if ( rRegion.IsNull() )
+ {
+ // excluding everything will create empty region
+ SetEmpty();
+ return;
+ }
+
+ if(IsEmpty())
+ {
+ // cannot exclude from empty, done
+ return;
+ }
+
+ if(IsNull())
+ {
+ // error; cannot exclude from null region since this is not representable
+ // in the data
+ OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
+ return;
+ }
+
+ if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ // get this B2DPolyPolygon
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ if(!aThisPolyPoly.count())
+ {
+ // cannot exclude from empty, done
+ return;
+ }
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly );
+
+ // get the other B2DPolyPolygon
+ basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
+ aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation( aOtherPolyPoly );
+
+ basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationDiff( aThisPolyPoly, aOtherPolyPoly );
+ *this = vcl::Region( aClip );
+ return;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // cannot exclude from empty, done
+ return;
+ }
+
+ const RegionBand* pSource = rRegion.getRegionBand();
+
+ if(!pSource)
+ {
+ // excluding nothing will do no change
+ return;
+ }
+
+ // prepare source and target
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
+
+ // union with source
+ const bool bSuccess(pNew->Exclude(*pSource));
+
+ // cleanup
+ if(!bSuccess)
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+}
+
+bool vcl::Region::XOr( const vcl::Region& rRegion )
+{
+ if ( rRegion.IsEmpty() )
+ {
+ // empty region will not change local content
+ return true;
+ }
+
+ if ( rRegion.IsNull() )
+ {
+ // error; cannot exclude null region from local since this is not representable
+ // in the data
+ OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
+ return true;
+ }
+
+ if(IsEmpty())
+ {
+ // rRect will be the xored-form (local off, rect on)
+ *this = rRegion;
+ return true;
+ }
+
+ if(IsNull())
+ {
+ // error: cannot exclude from null region since this is not representable
+ // in the data
+ OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
+ return false;
+ }
+
+ if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ // get this B2DPolyPolygon
+ basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
+
+ if(!aThisPolyPoly.count())
+ {
+ // rRect will be the xored-form (local off, rect on)
+ *this = rRegion;
+ return true;
+ }
+
+ aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly );
+
+ // get the other B2DPolyPolygon
+ basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
+ aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation( aOtherPolyPoly );
+
+ basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationXor( aThisPolyPoly, aOtherPolyPoly );
+ *this = vcl::Region( aClip );
+ return true;
+ }
+
+ // only region band mode possibility left here or null/empty
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // rRect will be the xored-form (local off, rect on)
+ *this = rRegion;
+ return true;
+ }
+
+ const RegionBand* pSource = rRegion.getRegionBand();
+
+ if(!pSource)
+ {
+ // empty region will not change local content
+ return true;
+ }
+
+ // prepare source and target
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
+
+ // union with source
+ pNew->XOr(*pSource);
+
+ // cleanup
+ if(!pNew->OptimizeBandList())
+ {
+ pNew.reset();
+ }
+
+ mpRegionBand = std::move(pNew);
+
+ return true;
+}
+
+tools::Rectangle vcl::Region::GetBoundRect() const
+{
+ if(IsEmpty())
+ {
+ // no internal data? -> region is empty!
+ return tools::Rectangle();
+ }
+
+ if(IsNull())
+ {
+ // error; null region has no BoundRect
+ // OSL_ENSURE(false, "Region::GetBoundRect error: null region has unlimited bound rect, not representable (!)");
+ return tools::Rectangle();
+ }
+
+ // prefer double precision source
+ if(getB2DPolyPolygon())
+ {
+ const basegfx::B2DRange aRange(basegfx::utils::getRange(*getB2DPolyPolygon()));
+
+ if(aRange.isEmpty())
+ {
+ // emulate PolyPolygon::GetBoundRect() when empty polygon
+ return tools::Rectangle();
+ }
+ else
+ {
+ // #i122149# corrected rounding, no need for ceil() and floor() here
+ return tools::Rectangle(
+ basegfx::fround(aRange.getMinX()), basegfx::fround(aRange.getMinY()),
+ basegfx::fround(aRange.getMaxX()), basegfx::fround(aRange.getMaxY()));
+ }
+ }
+
+ if(getPolyPolygon())
+ {
+ return getPolyPolygon()->GetBoundRect();
+ }
+
+ if(getRegionBand())
+ {
+ return getRegionBand()->GetBoundRect();
+ }
+
+ return tools::Rectangle();
+}
+
+tools::PolyPolygon vcl::Region::GetAsPolyPolygon() const
+{
+ if(getPolyPolygon())
+ {
+ return *getPolyPolygon();
+ }
+
+ if(getB2DPolyPolygon())
+ {
+ // the polygon needs to be converted, buffer the down conversion
+ const tools::PolyPolygon aPolyPolgon(*getB2DPolyPolygon());
+ const_cast< vcl::Region* >(this)->mpPolyPolygon = aPolyPolgon;
+
+ return *getPolyPolygon();
+ }
+
+ if(getRegionBand())
+ {
+ // the BandRegion needs to be converted, buffer the conversion
+ const tools::PolyPolygon aPolyPolgon(ImplCreatePolyPolygonFromRegionBand());
+ const_cast< vcl::Region* >(this)->mpPolyPolygon = aPolyPolgon;
+
+ return *getPolyPolygon();
+ }
+
+ return tools::PolyPolygon();
+}
+
+basegfx::B2DPolyPolygon vcl::Region::GetAsB2DPolyPolygon() const
+{
+ if(getB2DPolyPolygon())
+ {
+ return *getB2DPolyPolygon();
+ }
+
+ if(getPolyPolygon())
+ {
+ // the polygon needs to be converted, buffer the up conversion. This will be preferred from now.
+ const basegfx::B2DPolyPolygon aB2DPolyPolygon(getPolyPolygon()->getB2DPolyPolygon());
+ const_cast< vcl::Region* >(this)->mpB2DPolyPolygon = aB2DPolyPolygon;
+
+ return *getB2DPolyPolygon();
+ }
+
+ if(getRegionBand())
+ {
+ // the BandRegion needs to be converted, buffer the conversion
+ const basegfx::B2DPolyPolygon aB2DPolyPolygon(ImplCreateB2DPolyPolygonFromRegionBand());
+ const_cast< vcl::Region* >(this)->mpB2DPolyPolygon = aB2DPolyPolygon;
+
+ return *getB2DPolyPolygon();
+ }
+
+ return basegfx::B2DPolyPolygon();
+}
+
+const RegionBand* vcl::Region::GetAsRegionBand() const
+{
+ if(!getRegionBand())
+ {
+ if(getB2DPolyPolygon())
+ {
+ // convert B2DPolyPolygon to RegionBand, buffer it and return it
+ const_cast< vcl::Region* >(this)->mpRegionBand = ImplCreateRegionBandFromPolyPolygon(tools::PolyPolygon(*getB2DPolyPolygon()));
+ }
+ else if(getPolyPolygon())
+ {
+ // convert B2DPolyPolygon to RegionBand, buffer it and return it
+ const_cast< vcl::Region* >(this)->mpRegionBand = ImplCreateRegionBandFromPolyPolygon(*getPolyPolygon());
+ }
+ }
+
+ return getRegionBand();
+}
+
+bool vcl::Region::Contains( const Point& rPoint ) const
+{
+ if(IsEmpty())
+ {
+ // no point can be in empty region
+ return false;
+ }
+
+ if(IsNull())
+ {
+ // all points are inside null-region
+ return true;
+ }
+
+ // Too expensive (?)
+ //if(mpImplRegion->getRegionPolyPoly())
+ //{
+ // return mpImplRegion->getRegionPolyPoly()->Contains( rPoint );
+ //}
+
+ // ensure RegionBand existence
+ const RegionBand* pRegionBand = GetAsRegionBand();
+
+ if(pRegionBand)
+ {
+ return pRegionBand->Contains(rPoint);
+ }
+
+ return false;
+}
+
+bool vcl::Region::Overlaps( const tools::Rectangle& rRect ) const
+{
+ if(IsEmpty())
+ {
+ // nothing can be over something empty
+ return false;
+ }
+
+ if(IsNull())
+ {
+ // everything is over null region
+ return true;
+ }
+
+ // Can we optimize this ??? - is used in StarDraw for brushes pointers
+ // Why we have no IsOver for Regions ???
+ // create region from rectangle and intersect own region
+ vcl::Region aRegion(rRect);
+ aRegion.Intersect( *this );
+
+ // rectangle is over if include is not empty
+ return !aRegion.IsEmpty();
+}
+
+bool vcl::Region::IsRectangle() const
+{
+ if( IsEmpty() || IsNull() )
+ return false;
+
+ if( getB2DPolyPolygon() )
+ return basegfx::utils::isRectangle( *getB2DPolyPolygon() );
+
+ if( getPolyPolygon() )
+ return getPolyPolygon()->IsRect();
+
+ if( getRegionBand() )
+ return (getRegionBand()->getRectangleCount() == 1);
+
+ return false;
+}
+
+void vcl::Region::SetNull()
+{
+ // reset all content
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ mbIsNull = true;
+}
+
+void vcl::Region::SetEmpty()
+{
+ // reset all content
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ mbIsNull = false;
+}
+
+Region& vcl::Region::operator=( const vcl::Region& ) = default;
+
+Region& vcl::Region::operator=( vcl::Region&& rRegion ) noexcept
+{
+ mpB2DPolyPolygon = std::move(rRegion.mpB2DPolyPolygon);
+ mpPolyPolygon = std::move(rRegion.mpPolyPolygon);
+ mpRegionBand = std::move(rRegion.mpRegionBand);
+ mbIsNull = rRegion.mbIsNull;
+ rRegion.mbIsNull = true;
+
+ return *this;
+}
+
+Region& vcl::Region::operator=( const tools::Rectangle& rRect )
+{
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ if (!rRect.IsEmpty())
+ mpRegionBand = std::make_shared<RegionBand>(rRect);
+ else
+ mpRegionBand.reset();
+ mbIsNull = false;
+
+ return *this;
+}
+
+bool vcl::Region::operator==( const vcl::Region& rRegion ) const
+{
+ if(IsNull() && rRegion.IsNull())
+ {
+ // both are null region
+ return true;
+ }
+
+ if(IsEmpty() && rRegion.IsEmpty())
+ {
+ // both are empty
+ return true;
+ }
+
+ if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion.getB2DPolyPolygon())
+ {
+ // same instance data? -> equal
+ return true;
+ }
+
+ if(getPolyPolygon() && getPolyPolygon() == rRegion.getPolyPolygon())
+ {
+ // same instance data? -> equal
+ return true;
+ }
+
+ if(getRegionBand() && getRegionBand() == rRegion.getRegionBand())
+ {
+ // same instance data? -> equal
+ return true;
+ }
+
+ if(IsNull() || IsEmpty())
+ {
+ return false;
+ }
+
+ if(rRegion.IsNull() || rRegion.IsEmpty())
+ {
+ return false;
+ }
+
+ if(rRegion.getB2DPolyPolygon() || getB2DPolyPolygon())
+ {
+ // one of both has a B2DPolyPolygon based region, ensure both have it
+ // by evtl. conversion
+ GetAsB2DPolyPolygon();
+ rRegion.GetAsB2DPolyPolygon();
+
+ return *rRegion.getB2DPolyPolygon() == *getB2DPolyPolygon();
+ }
+
+ if(rRegion.getPolyPolygon() || getPolyPolygon())
+ {
+ // one of both has a B2DPolyPolygon based region, ensure both have it
+ // by evtl. conversion
+ GetAsPolyPolygon();
+ rRegion.GetAsPolyPolygon();
+
+ return *rRegion.getPolyPolygon() == *getPolyPolygon();
+ }
+
+ // both are not empty or null (see above) and if content supported polygon
+ // data the comparison is already done. Only both on RegionBand base can be left,
+ // but better check
+ if(rRegion.getRegionBand() && getRegionBand())
+ {
+ return *rRegion.getRegionBand() == *getRegionBand();
+ }
+
+ // should not happen, but better deny equality
+ return false;
+}
+
+SvStream& ReadRegion(SvStream& rIStrm, vcl::Region& rRegion)
+{
+ VersionCompatRead aCompat(rIStrm);
+ sal_uInt16 nVersion(0);
+ sal_uInt16 nTmp16(0);
+
+ // clear region to be loaded
+ rRegion.SetEmpty();
+
+ // get version of streamed region
+ rIStrm.ReadUInt16( nVersion );
+
+ // get type of region
+ rIStrm.ReadUInt16( nTmp16 );
+
+ enum RegionType { REGION_NULL, REGION_EMPTY, REGION_RECTANGLE, REGION_COMPLEX };
+ auto eStreamedType = nTmp16;
+
+ switch (eStreamedType)
+ {
+ case REGION_NULL:
+ {
+ rRegion.SetNull();
+ break;
+ }
+
+ case REGION_EMPTY:
+ {
+ rRegion.SetEmpty();
+ break;
+ }
+
+ default:
+ {
+ std::shared_ptr<RegionBand> xNewRegionBand(std::make_shared<RegionBand>());
+ bool bSuccess = xNewRegionBand->load(rIStrm);
+ rRegion.mpRegionBand = xNewRegionBand;
+
+ bool bHasPolyPolygon(false);
+ if (aCompat.GetVersion() >= 2)
+ {
+ rIStrm.ReadCharAsBool( bHasPolyPolygon );
+
+ if (bHasPolyPolygon)
+ {
+ tools::PolyPolygon aNewPoly;
+ ReadPolyPolygon(rIStrm, aNewPoly);
+ const auto nPolygons = aNewPoly.Count();
+ if (nPolygons > 128)
+ {
+ SAL_WARN("vcl.gdi", "suspiciously high no of polygons in clip:" << nPolygons);
+ if (utl::ConfigManager::IsFuzzing())
+ aNewPoly.Clear();
+ }
+ rRegion.mpPolyPolygon = aNewPoly;
+ }
+ }
+
+ if (!bSuccess && !bHasPolyPolygon)
+ {
+ SAL_WARN("vcl.gdi", "bad region band:" << bHasPolyPolygon);
+ rRegion.SetNull();
+ }
+
+ break;
+ }
+ }
+
+ return rIStrm;
+}
+
+SvStream& WriteRegion( SvStream& rOStrm, const vcl::Region& rRegion )
+{
+ const sal_uInt16 nVersion(2);
+ VersionCompatWrite aCompat(rOStrm, nVersion);
+
+ // put version
+ rOStrm.WriteUInt16( nVersion );
+
+ // put type
+ enum RegionType { REGION_NULL, REGION_EMPTY, REGION_RECTANGLE, REGION_COMPLEX };
+ RegionType aRegionType(REGION_COMPLEX);
+ bool bEmpty(rRegion.IsEmpty());
+
+ if(!bEmpty && rRegion.getB2DPolyPolygon() && 0 == rRegion.getB2DPolyPolygon()->count())
+ {
+ OSL_ENSURE(false, "Region with empty B2DPolyPolygon, should not be created (!)");
+ bEmpty = true;
+ }
+
+ if(!bEmpty && rRegion.getPolyPolygon() && 0 == rRegion.getPolyPolygon()->Count())
+ {
+ OSL_ENSURE(false, "Region with empty PolyPolygon, should not be created (!)");
+ bEmpty = true;
+ }
+
+ if(bEmpty)
+ {
+ aRegionType = REGION_EMPTY;
+ }
+ else if(rRegion.IsNull())
+ {
+ aRegionType = REGION_NULL;
+ }
+ else if(rRegion.getRegionBand() && rRegion.getRegionBand()->isSingleRectangle())
+ {
+ aRegionType = REGION_RECTANGLE;
+ }
+
+ rOStrm.WriteUInt16( aRegionType );
+
+ // get RegionBand
+ const RegionBand* pRegionBand = rRegion.getRegionBand();
+
+ if(pRegionBand)
+ {
+ pRegionBand->save(rOStrm);
+ }
+ else
+ {
+ // for compatibility, write an empty RegionBand (will only write
+ // the end marker STREAMENTRY_END, but this *is* needed)
+ const RegionBand aRegionBand;
+
+ aRegionBand.save(rOStrm);
+ }
+
+ // write polypolygon if available
+ const bool bHasPolyPolygon(rRegion.HasPolyPolygonOrB2DPolyPolygon());
+ rOStrm.WriteBool( bHasPolyPolygon );
+
+ if(bHasPolyPolygon)
+ {
+ // #i105373#
+ tools::PolyPolygon aNoCurvePolyPolygon;
+ rRegion.GetAsPolyPolygon().AdaptiveSubdivide(aNoCurvePolyPolygon);
+
+ WritePolyPolygon( rOStrm, aNoCurvePolyPolygon );
+ }
+
+ return rOStrm;
+}
+
+void vcl::Region::GetRegionRectangles(RectangleVector& rTarget) const
+{
+ // clear returnvalues
+ rTarget.clear();
+
+ // ensure RegionBand existence
+ const RegionBand* pRegionBand = GetAsRegionBand();
+
+ if(pRegionBand)
+ {
+ pRegionBand->GetRegionRectangles(rTarget);
+ }
+}
+
+static bool ImplPolygonRectTest( const tools::Polygon& rPoly, tools::Rectangle* pRectOut = nullptr )
+{
+ bool bIsRect = false;
+ const Point* pPoints = rPoly.GetConstPointAry();
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ if( nPoints == 4 || (nPoints == 5 && pPoints[0] == pPoints[4]) )
+ {
+ tools::Long nX1 = pPoints[0].X(), nX2 = pPoints[2].X(), nY1 = pPoints[0].Y(), nY2 = pPoints[2].Y();
+
+ if( ( (pPoints[1].X() == nX1 && pPoints[3].X() == nX2) && (pPoints[1].Y() == nY2 && pPoints[3].Y() == nY1) )
+ || ( (pPoints[1].X() == nX2 && pPoints[3].X() == nX1) && (pPoints[1].Y() == nY1 && pPoints[3].Y() == nY2) ) )
+ {
+ bIsRect = true;
+
+ if( pRectOut )
+ {
+ tools::Long nSwap;
+
+ if( nX2 < nX1 )
+ {
+ nSwap = nX2;
+ nX2 = nX1;
+ nX1 = nSwap;
+ }
+
+ if( nY2 < nY1 )
+ {
+ nSwap = nY2;
+ nY2 = nY1;
+ nY1 = nSwap;
+ }
+
+ if( nX2 != nX1 )
+ {
+ nX2--;
+ }
+
+ if( nY2 != nY1 )
+ {
+ nY2--;
+ }
+
+ pRectOut->SetLeft( nX1 );
+ pRectOut->SetRight( nX2 );
+ pRectOut->SetTop( nY1 );
+ pRectOut->SetBottom( nY2 );
+ }
+ }
+ }
+
+ return bIsRect;
+}
+
+vcl::Region vcl::Region::GetRegionFromPolyPolygon( const tools::PolyPolygon& rPolyPoly )
+{
+ //return vcl::Region( rPolyPoly );
+
+ // check if it's worth extracting the XOr'ing the Rectangles
+ // empiricism shows that break even between XOr'ing rectangles separately
+ // and ImplCreateRegionBandFromPolyPolygon is at half rectangles/half polygons
+ int nPolygonRects = 0, nPolygonPolygons = 0;
+ int nPolygons = rPolyPoly.Count();
+
+ for( int i = 0; i < nPolygons; i++ )
+ {
+ const tools::Polygon& rPoly = rPolyPoly[i];
+
+ if( ImplPolygonRectTest( rPoly ) )
+ {
+ nPolygonRects++;
+ }
+ else
+ {
+ nPolygonPolygons++;
+ }
+ }
+
+ if( nPolygonPolygons > nPolygonRects )
+ {
+ return vcl::Region( rPolyPoly );
+ }
+
+ vcl::Region aResult;
+ tools::Rectangle aRect;
+
+ for( int i = 0; i < nPolygons; i++ )
+ {
+ const tools::Polygon& rPoly = rPolyPoly[i];
+
+ if( ImplPolygonRectTest( rPoly, &aRect ) )
+ {
+ aResult.XOr( aRect );
+ }
+ else
+ {
+ aResult.XOr( vcl::Region(rPoly) );
+ }
+ }
+
+ return aResult;
+}
+
+} /* namespace vcl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/regionband.cxx b/vcl/source/gdi/regionband.cxx
new file mode 100644
index 0000000000..7b48df7a09
--- /dev/null
+++ b/vcl/source/gdi/regionband.cxx
@@ -0,0 +1,1365 @@
+/* -*- 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 <sal/config.h>
+
+#include <cstdlib>
+
+#include <tools/stream.hxx>
+#include <regionband.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+RegionBand::RegionBand()
+: mpFirstBand(nullptr),
+ mpLastCheckedBand(nullptr)
+{
+}
+
+RegionBand::RegionBand(const RegionBand& rRef)
+: mpFirstBand(nullptr),
+ mpLastCheckedBand(nullptr)
+{
+ *this = rRef;
+}
+
+RegionBand& RegionBand::operator=(const RegionBand& rRef)
+{
+ if (this != &rRef)
+ {
+ ImplRegionBand* pPrevBand = nullptr;
+ ImplRegionBand* pBand = rRef.mpFirstBand;
+
+ while(pBand)
+ {
+ ImplRegionBand* pNewBand = new ImplRegionBand(*pBand);
+
+ // first element? -> set as first into the list
+ if(pBand == rRef.mpFirstBand)
+ {
+ mpFirstBand = pNewBand;
+ }
+ else
+ {
+ pPrevBand->mpNextBand = pNewBand;
+ }
+
+ pPrevBand = pNewBand;
+ pBand = pBand->mpNextBand;
+ }
+ }
+ return *this;
+}
+
+RegionBand::RegionBand(const tools::Rectangle& rRect)
+: mpFirstBand(nullptr),
+ mpLastCheckedBand(nullptr)
+{
+ const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));
+ const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
+
+ // add band with boundaries of the rectangle
+ mpFirstBand = new ImplRegionBand(nTop, nBottom);
+
+ // Set left and right boundaries of the band
+ mpFirstBand->Union(nLeft, nRight);
+
+}
+
+void RegionBand::implReset()
+{
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ ImplRegionBand* pTempBand = pBand->mpNextBand;
+ delete pBand;
+ pBand = pTempBand;
+ }
+
+ mpLastCheckedBand = nullptr;
+ mpFirstBand = nullptr;
+}
+
+RegionBand::~RegionBand()
+{
+ implReset();
+}
+
+bool RegionBand::operator==( const RegionBand& rRegionBand ) const
+{
+
+ // initialise pointers
+ ImplRegionBand* pOwnRectBand = mpFirstBand;
+ ImplRegionBandSep* pOwnRectBandSep = pOwnRectBand->mpFirstSep;
+ ImplRegionBand* pSecondRectBand = rRegionBand.mpFirstBand;
+ ImplRegionBandSep* pSecondRectBandSep = pSecondRectBand->mpFirstSep;
+
+ while ( pOwnRectBandSep && pSecondRectBandSep )
+ {
+ // get boundaries of current rectangle
+ tools::Long nOwnXLeft = pOwnRectBandSep->mnXLeft;
+ tools::Long nSecondXLeft = pSecondRectBandSep->mnXLeft;
+
+ if ( nOwnXLeft != nSecondXLeft )
+ {
+ return false;
+ }
+
+ tools::Long nOwnYTop = pOwnRectBand->mnYTop;
+ tools::Long nSecondYTop = pSecondRectBand->mnYTop;
+
+ if ( nOwnYTop != nSecondYTop )
+ {
+ return false;
+ }
+
+ tools::Long nOwnXRight = pOwnRectBandSep->mnXRight;
+ tools::Long nSecondXRight = pSecondRectBandSep->mnXRight;
+
+ if ( nOwnXRight != nSecondXRight )
+ {
+ return false;
+ }
+
+ tools::Long nOwnYBottom = pOwnRectBand->mnYBottom;
+ tools::Long nSecondYBottom = pSecondRectBand->mnYBottom;
+
+ if ( nOwnYBottom != nSecondYBottom )
+ {
+ return false;
+ }
+
+ // get next separation from current band
+ pOwnRectBandSep = pOwnRectBandSep->mpNextSep;
+
+ // no separation found? -> go to next band!
+ if ( !pOwnRectBandSep )
+ {
+ // get next band
+ pOwnRectBand = pOwnRectBand->mpNextBand;
+
+ // get first separation in current band
+ if( pOwnRectBand )
+ {
+ pOwnRectBandSep = pOwnRectBand->mpFirstSep;
+ }
+ }
+
+ // get next separation from current band
+ pSecondRectBandSep = pSecondRectBandSep->mpNextSep;
+
+ // no separation found? -> go to next band!
+ if ( !pSecondRectBandSep )
+ {
+ // get next band
+ pSecondRectBand = pSecondRectBand->mpNextBand;
+
+ // get first separation in current band
+ if( pSecondRectBand )
+ {
+ pSecondRectBandSep = pSecondRectBand->mpFirstSep;
+ }
+ }
+
+ if ( pOwnRectBandSep && !pSecondRectBandSep )
+ {
+ return false;
+ }
+
+ if ( !pOwnRectBandSep && pSecondRectBandSep )
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+namespace {
+
+enum StreamEntryType { STREAMENTRY_BANDHEADER, STREAMENTRY_SEPARATION, STREAMENTRY_END };
+
+}
+
+bool RegionBand::load(SvStream& rIStrm)
+{
+ // clear this instance data
+ implReset();
+
+ // get all bands
+ ImplRegionBand* pCurrBand = nullptr;
+
+ // get header from first element
+ sal_uInt16 nTmp16(STREAMENTRY_END);
+ rIStrm.ReadUInt16(nTmp16);
+
+ if (STREAMENTRY_END == static_cast<StreamEntryType>(nTmp16))
+ return false;
+
+ size_t nRecordsPossible = rIStrm.remainingSize() / (2*sizeof(sal_Int32));
+ if (!nRecordsPossible)
+ {
+ OSL_ENSURE(false, "premature end of region stream" );
+ implReset();
+ return false;
+ }
+
+ do
+ {
+ // insert new band or new separation?
+ if(STREAMENTRY_BANDHEADER == static_cast<StreamEntryType>(nTmp16))
+ {
+ sal_Int32 nYTop(0);
+ sal_Int32 nYBottom(0);
+
+ rIStrm.ReadInt32( nYTop );
+ rIStrm.ReadInt32( nYBottom );
+
+ // create band
+ ImplRegionBand* pNewBand = new ImplRegionBand( nYTop, nYBottom );
+
+ // first element? -> set as first into the list
+ if ( !pCurrBand )
+ {
+ mpFirstBand = pNewBand;
+ }
+ else
+ {
+ pCurrBand->mpNextBand = pNewBand;
+ }
+
+ // save pointer for next creation
+ pCurrBand = pNewBand;
+ }
+ else
+ {
+ sal_Int32 nXLeft(0);
+ sal_Int32 nXRight(0);
+
+ rIStrm.ReadInt32( nXLeft );
+ rIStrm.ReadInt32( nXRight );
+
+ // add separation
+ if ( pCurrBand )
+ {
+ pCurrBand->Union( nXLeft, nXRight );
+ }
+ }
+
+ if( rIStrm.eof() )
+ {
+ OSL_ENSURE(false, "premature end of region stream" );
+ implReset();
+ return false;
+ }
+
+ // get next header
+ rIStrm.ReadUInt16( nTmp16 );
+ }
+ while (STREAMENTRY_END != static_cast<StreamEntryType>(nTmp16) && rIStrm.good());
+ if (!CheckConsistency())
+ {
+ implReset();
+ return false;
+ }
+ return true;
+}
+
+void RegionBand::save(SvStream& rOStrm) const
+{
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ // put boundaries
+ rOStrm.WriteUInt16( STREAMENTRY_BANDHEADER );
+ rOStrm.WriteInt32( pBand->mnYTop );
+ rOStrm.WriteInt32( pBand->mnYBottom );
+
+ // put separations of current band
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while(pSep)
+ {
+ // put separation
+ rOStrm.WriteUInt16( STREAMENTRY_SEPARATION );
+ rOStrm.WriteInt32( pSep->mnXLeft );
+ rOStrm.WriteInt32( pSep->mnXRight );
+
+ // next separation from current band
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+ // put endmarker
+ rOStrm.WriteUInt16( STREAMENTRY_END );
+}
+
+bool RegionBand::isSingleRectangle() const
+{
+ // just one band?
+ if(mpFirstBand && !mpFirstBand->mpNextBand)
+ {
+ // just one sep?
+ if(mpFirstBand->mpFirstSep && !mpFirstBand->mpFirstSep->mpNextSep)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void RegionBand::InsertBand(ImplRegionBand* pPreviousBand, ImplRegionBand* pBandToInsert)
+{
+ OSL_ASSERT(pBandToInsert!=nullptr);
+
+ if(!pPreviousBand)
+ {
+ // Insert band before all others.
+ if(mpFirstBand)
+ {
+ mpFirstBand->mpPrevBand = pBandToInsert;
+ }
+
+ pBandToInsert->mpNextBand = mpFirstBand;
+ mpFirstBand = pBandToInsert;
+ }
+ else
+ {
+ // Insert band directly after pPreviousBand.
+ pBandToInsert->mpNextBand = pPreviousBand->mpNextBand;
+ pPreviousBand->mpNextBand = pBandToInsert;
+ pBandToInsert->mpPrevBand = pPreviousBand;
+ }
+
+}
+
+void RegionBand::processPoints()
+{
+ ImplRegionBand* pRegionBand = mpFirstBand;
+
+ while(pRegionBand)
+ {
+ // generate separations from the lines and process union
+ pRegionBand->ProcessPoints();
+ pRegionBand = pRegionBand->mpNextBand;
+ }
+
+}
+
+/** This function is similar to the RegionBand::InsertBands() method.
+ It creates a minimal set of missing bands so that the entire vertical
+ interval from nTop to nBottom is covered by bands.
+*/
+void RegionBand::ImplAddMissingBands(const tools::Long nTop, const tools::Long nBottom)
+{
+ // Iterate over already existing bands and add missing bands atop the
+ // first and between two bands.
+ ImplRegionBand* pPreviousBand = nullptr;
+ ImplRegionBand* pBand = ImplGetFirstRegionBand();
+ tools::Long nCurrentTop (nTop);
+
+ while (pBand != nullptr && nCurrentTop<nBottom)
+ {
+ if (nCurrentTop < pBand->mnYTop)
+ {
+ // Create new band above the current band.
+ ImplRegionBand* pAboveBand = new ImplRegionBand(
+ nCurrentTop,
+ ::std::min(nBottom,pBand->mnYTop-1));
+ InsertBand(pPreviousBand, pAboveBand);
+ }
+
+ // Adapt the top of the interval to prevent overlapping bands.
+ nCurrentTop = ::std::max(nTop, pBand->mnYBottom+1);
+
+ // Advance to next band.
+ pPreviousBand = pBand;
+ pBand = pBand->mpNextBand;
+ }
+
+ // We still have to cover two cases:
+ // 1. The region does not yet contain any bands.
+ // 2. The interval nTop->nBottom extends past the bottom most band.
+ if (nCurrentTop <= nBottom
+ && (pBand==nullptr || nBottom>pBand->mnYBottom))
+ {
+ // When there is no previous band then the new one will be the
+ // first. Otherwise the new band is inserted behind the last band.
+ InsertBand(
+ pPreviousBand,
+ new ImplRegionBand(
+ nCurrentTop,
+ nBottom));
+ }
+
+}
+
+void RegionBand::CreateBandRange(tools::Long nYTop, tools::Long nYBottom)
+{
+ // add top band
+ mpFirstBand = new ImplRegionBand( nYTop-1, nYTop-1 );
+
+ // begin first search from the first element
+ mpLastCheckedBand = mpFirstBand;
+ ImplRegionBand* pBand = mpFirstBand;
+
+ for ( tools::Long i = nYTop; i <= nYBottom+1; i++ )
+ {
+ // create new band
+ ImplRegionBand* pNewBand = new ImplRegionBand( i, i );
+ pBand->mpNextBand = pNewBand;
+
+ if ( pBand != mpFirstBand )
+ {
+ pNewBand->mpPrevBand = pBand;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::InsertLine(const Point& rStartPt, const Point& rEndPt, tools::Long nLineId)
+{
+ tools::Long nX, nY;
+
+ // lines consisting of a single point do not interest here
+ if ( rStartPt == rEndPt )
+ {
+ return;
+ }
+
+ LineType eLineType = (rStartPt.Y() > rEndPt.Y()) ? LineType::Descending : LineType::Ascending;
+ if ( rStartPt.X() == rEndPt.X() )
+ {
+ // vertical line
+ const tools::Long nEndY = rEndPt.Y();
+
+ nX = rStartPt.X();
+ nY = rStartPt.Y();
+
+ if( nEndY > nY )
+ {
+ for ( ; nY <= nEndY; nY++ )
+ {
+ Point aNewPoint( nX, nY );
+ InsertPoint( aNewPoint, nLineId,
+ (aNewPoint == rEndPt) || (aNewPoint == rStartPt),
+ eLineType );
+ }
+ }
+ else
+ {
+ for ( ; nY >= nEndY; nY-- )
+ {
+ Point aNewPoint( nX, nY );
+ InsertPoint( aNewPoint, nLineId,
+ (aNewPoint == rEndPt) || (aNewPoint == rStartPt),
+ eLineType );
+ }
+ }
+ }
+ else if ( rStartPt.Y() != rEndPt.Y() )
+ {
+ const tools::Long nDX = std::abs( rEndPt.X() - rStartPt.X() );
+ const tools::Long nDY = std::abs( rEndPt.Y() - rStartPt.Y() );
+ const tools::Long nStartX = rStartPt.X();
+ const tools::Long nStartY = rStartPt.Y();
+ const tools::Long nEndX = rEndPt.X();
+ const tools::Long nEndY = rEndPt.Y();
+ const tools::Long nXInc = ( nStartX < nEndX ) ? 1 : -1;
+ const tools::Long nYInc = ( nStartY < nEndY ) ? 1 : -1;
+
+ if ( nDX >= nDY )
+ {
+ const tools::Long nDYX = ( nDY - nDX ) * 2;
+ const tools::Long nDY2 = nDY << 1;
+ tools::Long nD = nDY2 - nDX;
+
+ for ( nX = nStartX, nY = nStartY; nX != nEndX; nX += nXInc )
+ {
+ InsertPoint( Point( nX, nY ), nLineId, nStartX == nX, eLineType );
+
+ if ( nD < 0 )
+ nD += nDY2;
+ else
+ {
+ nD += nDYX;
+ nY += nYInc;
+ }
+ }
+ }
+ else
+ {
+ const tools::Long nDYX = ( nDX - nDY ) * 2;
+ const tools::Long nDY2 = nDX << 1;
+ tools::Long nD = nDY2 - nDY;
+
+ for ( nX = nStartX, nY = nStartY; nY != nEndY; nY += nYInc )
+ {
+ InsertPoint( Point( nX, nY ), nLineId, nStartY == nY, eLineType );
+
+ if ( nD < 0 )
+ nD += nDY2;
+ else
+ {
+ nD += nDYX;
+ nX += nXInc;
+ }
+ }
+ }
+
+ // last point
+ InsertPoint( Point( nEndX, nEndY ), nLineId, true, eLineType );
+ }
+}
+
+void RegionBand::InsertPoint(const Point &rPoint, tools::Long nLineID, bool bEndPoint, LineType eLineType)
+{
+ SAL_WARN_IF( mpFirstBand == nullptr, "vcl", "RegionBand::InsertPoint - no bands available!" );
+
+ if ( rPoint.Y() == mpLastCheckedBand->mnYTop )
+ {
+ mpLastCheckedBand->InsertPoint( rPoint.X(), nLineID, bEndPoint, eLineType );
+ return;
+ }
+
+ if ( rPoint.Y() > mpLastCheckedBand->mnYTop )
+ {
+ // Search ascending
+ while ( mpLastCheckedBand )
+ {
+ // Insert point if possible
+ if ( rPoint.Y() == mpLastCheckedBand->mnYTop )
+ {
+ mpLastCheckedBand->InsertPoint( rPoint.X(), nLineID, bEndPoint, eLineType );
+ return;
+ }
+
+ mpLastCheckedBand = mpLastCheckedBand->mpNextBand;
+ }
+
+ OSL_ENSURE(false, "RegionBand::InsertPoint reached the end of the list!" );
+ }
+ else
+ {
+ // Search descending
+ while ( mpLastCheckedBand )
+ {
+ // Insert point if possible
+ if ( rPoint.Y() == mpLastCheckedBand->mnYTop )
+ {
+ mpLastCheckedBand->InsertPoint( rPoint.X(), nLineID, bEndPoint, eLineType );
+ return;
+ }
+
+ mpLastCheckedBand = mpLastCheckedBand->mpPrevBand;
+ }
+
+ OSL_ENSURE(false, "RegionBand::InsertPoint reached the beginning of the list!" );
+ }
+
+ OSL_ENSURE(false, "RegionBand::InsertPoint point not inserted!" );
+
+ // reinitialize pointer (should never be reached!)
+ mpLastCheckedBand = mpFirstBand;
+}
+
+bool RegionBand::OptimizeBandList()
+{
+ ImplRegionBand* pPrevBand = nullptr;
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while ( pBand )
+ {
+ const bool bBTEqual = pBand->mpNextBand && (pBand->mnYBottom == pBand->mpNextBand->mnYTop);
+
+ // no separation? -> remove!
+ if ( pBand->IsEmpty() || (bBTEqual && (pBand->mnYBottom == pBand->mnYTop)) )
+ {
+ // save pointer
+ ImplRegionBand* pOldBand = pBand;
+
+ // previous element of the list
+ if ( pBand == mpFirstBand )
+ mpFirstBand = pBand->mpNextBand;
+ else
+ pPrevBand->mpNextBand = pBand->mpNextBand;
+
+ pBand = pBand->mpNextBand;
+ delete pOldBand;
+ }
+ else
+ {
+ // fixup
+ if ( bBTEqual )
+ pBand->mnYBottom = pBand->mpNextBand->mnYTop-1;
+
+ // this and next band with equal separations? -> combine!
+ if ( pBand->mpNextBand &&
+ ((pBand->mnYBottom+1) == pBand->mpNextBand->mnYTop) &&
+ (*pBand == *pBand->mpNextBand) )
+ {
+ // expand current height
+ pBand->mnYBottom = pBand->mpNextBand->mnYBottom;
+
+ // remove next band from list
+ ImplRegionBand* pDeletedBand = pBand->mpNextBand;
+ pBand->mpNextBand = pDeletedBand->mpNextBand;
+ delete pDeletedBand;
+
+ // check band again!
+ }
+ else
+ {
+ // count rectangles within band
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+ while ( pSep )
+ {
+ pSep = pSep->mpNextSep;
+ }
+
+ pPrevBand = pBand;
+ pBand = pBand->mpNextBand;
+ }
+ }
+ }
+
+#ifdef DBG_UTIL
+ pBand = mpFirstBand;
+ while ( pBand )
+ {
+ SAL_WARN_IF( pBand->mpFirstSep == nullptr, "vcl", "Exiting RegionBand::OptimizeBandList(): empty band in region!" );
+
+ if ( pBand->mnYBottom < pBand->mnYTop )
+ OSL_ENSURE(false, "RegionBand::OptimizeBandList(): YBottomBoundary < YTopBoundary" );
+
+ if ( pBand->mpNextBand && pBand->mnYBottom >= pBand->mpNextBand->mnYTop )
+ OSL_ENSURE(false, "RegionBand::OptimizeBandList(): overlapping bands in region!" );
+
+ pBand = pBand->mpNextBand;
+ }
+#endif
+
+ return (nullptr != mpFirstBand);
+}
+
+void RegionBand::Move(tools::Long nHorzMove, tools::Long nVertMove)
+{
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ // process the vertical move
+ if(nVertMove)
+ {
+ pBand->mnYTop = o3tl::saturating_add(pBand->mnYTop, nVertMove);
+ pBand->mnYBottom = o3tl::saturating_add(pBand->mnYBottom, nVertMove);
+ }
+
+ // process the horizontal move
+ if(nHorzMove)
+ {
+ pBand->MoveX(nHorzMove);
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::Scale(double fScaleX, double fScaleY)
+{
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ // process the vertical move
+ if(0.0 != fScaleY)
+ {
+ pBand->mnYTop = basegfx::fround(pBand->mnYTop * fScaleY);
+ pBand->mnYBottom = basegfx::fround(pBand->mnYBottom * fScaleY);
+ }
+
+ // process the horizontal move
+ if(0.0 != fScaleX)
+ {
+ pBand->ScaleX(fScaleX);
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::InsertBands(tools::Long nTop, tools::Long nBottom)
+{
+ // region empty? -> set rectangle as first entry!
+ if ( !mpFirstBand )
+ {
+ // add band with boundaries of the rectangle
+ mpFirstBand = new ImplRegionBand( nTop, nBottom );
+ return;
+ }
+
+ // find/insert bands for the boundaries of the rectangle
+ bool bTopBoundaryInserted = false;
+ bool bTop2BoundaryInserted = false;
+ bool bBottomBoundaryInserted = false;
+
+ // special case: top boundary is above the first band
+ ImplRegionBand* pNewBand;
+
+ if ( nTop < mpFirstBand->mnYTop )
+ {
+ // create new band above the first in the list
+ pNewBand = new ImplRegionBand( nTop, mpFirstBand->mnYTop );
+
+ if ( nBottom < mpFirstBand->mnYTop )
+ {
+ pNewBand->mnYBottom = nBottom;
+ }
+
+ // insert band into the list
+ pNewBand->mpNextBand = mpFirstBand;
+ mpFirstBand = pNewBand;
+
+ bTopBoundaryInserted = true;
+ }
+
+ // insert band(s) into the list
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while ( pBand )
+ {
+ // Insert Bands if possible
+ if ( !bTopBoundaryInserted )
+ {
+ bTopBoundaryInserted = InsertSingleBand( pBand, nTop - 1 );
+ }
+
+ if ( !bTop2BoundaryInserted )
+ {
+ bTop2BoundaryInserted = InsertSingleBand( pBand, nTop );
+ }
+
+ if ( !bBottomBoundaryInserted && (nTop != nBottom) )
+ {
+ bBottomBoundaryInserted = InsertSingleBand( pBand, nBottom );
+ }
+
+ // both boundaries inserted? -> nothing more to do
+ if ( bTopBoundaryInserted && bTop2BoundaryInserted && bBottomBoundaryInserted )
+ {
+ break;
+ }
+
+ // insert bands between two bands if necessary
+ if ( pBand->mpNextBand )
+ {
+ if ( (pBand->mnYBottom + 1) < pBand->mpNextBand->mnYTop )
+ {
+ // copy band with list and set new boundary
+ pNewBand = new ImplRegionBand( pBand->mnYBottom+1, pBand->mpNextBand->mnYTop-1 );
+
+ // insert band into the list
+ pNewBand->mpNextBand = pBand->mpNextBand;
+ pBand->mpNextBand = pNewBand;
+ }
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+bool RegionBand::InsertSingleBand(ImplRegionBand* pBand, tools::Long nYBandPosition)
+{
+ // boundary already included in band with height 1? -> nothing to do!
+ if ( (pBand->mnYTop == pBand->mnYBottom) && (nYBandPosition == pBand->mnYTop) )
+ {
+ return true;
+ }
+
+ // insert single height band on top?
+ ImplRegionBand* pNewBand;
+
+ if ( nYBandPosition == pBand->mnYTop )
+ {
+ // copy band with list and set new boundary
+ pNewBand = new ImplRegionBand( *pBand );
+ pNewBand->mnYTop = nYBandPosition+1;
+
+ // insert band into the list
+ pNewBand->mpNextBand = pBand->mpNextBand;
+ pBand->mnYBottom = nYBandPosition;
+ pBand->mpNextBand = pNewBand;
+
+ return true;
+ }
+
+ // top of new rectangle within the current band? -> insert new band and copy data
+ if ( (nYBandPosition > pBand->mnYTop) && (nYBandPosition < pBand->mnYBottom) )
+ {
+ // copy band with list and set new boundary
+ pNewBand = new ImplRegionBand( *pBand );
+ pNewBand->mnYTop = nYBandPosition;
+
+ // insert band into the list
+ pNewBand->mpNextBand = pBand->mpNextBand;
+ pBand->mnYBottom = nYBandPosition;
+ pBand->mpNextBand = pNewBand;
+
+ // copy band with list and set new boundary
+ pNewBand = new ImplRegionBand( *pBand );
+ pNewBand->mnYTop = nYBandPosition;
+
+ // insert band into the list
+ pBand->mpNextBand->mnYTop = nYBandPosition+1;
+
+ pNewBand->mpNextBand = pBand->mpNextBand;
+ pBand->mnYBottom = nYBandPosition - 1;
+ pBand->mpNextBand = pNewBand;
+
+ return true;
+ }
+
+ // create new band behind the current in the list
+ if ( !pBand->mpNextBand )
+ {
+ if ( nYBandPosition == pBand->mnYBottom )
+ {
+ // copy band with list and set new boundary
+ pNewBand = new ImplRegionBand( *pBand );
+ pNewBand->mnYTop = pBand->mnYBottom;
+ pNewBand->mnYBottom = nYBandPosition;
+
+ pBand->mnYBottom = nYBandPosition-1;
+
+ // append band to the list
+ pBand->mpNextBand = pNewBand;
+ return true;
+ }
+
+ if ( nYBandPosition > pBand->mnYBottom )
+ {
+ // create new band
+ pNewBand = new ImplRegionBand( pBand->mnYBottom + 1, nYBandPosition );
+
+ // append band to the list
+ pBand->mpNextBand = pNewBand;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void RegionBand::Union(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom)
+{
+ SAL_WARN_IF( nLeft > nRight, "vcl", "RegionBand::Union() - nLeft > nRight" );
+ SAL_WARN_IF( nTop > nBottom, "vcl", "RegionBand::Union() - nTop > nBottom" );
+
+ // process union
+ ImplRegionBand* pBand = mpFirstBand;
+ while ( pBand )
+ {
+ if ( pBand->mnYTop >= nTop )
+ {
+ if ( pBand->mnYBottom <= nBottom )
+ pBand->Union( nLeft, nRight );
+ else
+ {
+#ifdef DBG_UTIL
+ tools::Long nCurY = pBand->mnYBottom;
+ pBand = pBand->mpNextBand;
+ while ( pBand )
+ {
+ if ( (pBand->mnYTop < nCurY) || (pBand->mnYBottom < nCurY) )
+ {
+ OSL_ENSURE(false, "RegionBand::Union() - Bands not sorted!" );
+ }
+ pBand = pBand->mpNextBand;
+ }
+#endif
+ break;
+ }
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::Intersect(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom)
+{
+ // process intersections
+ ImplRegionBand* pPrevBand = nullptr;
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ // band within intersection boundary? -> process. otherwise remove
+ if((pBand->mnYTop >= nTop) && (pBand->mnYBottom <= nBottom))
+ {
+ // process intersection
+ pBand->Intersect(nLeft, nRight);
+ pPrevBand = pBand;
+ pBand = pBand->mpNextBand;
+ }
+ else
+ {
+ ImplRegionBand* pOldBand = pBand;
+
+ if(pBand == mpFirstBand)
+ {
+ mpFirstBand = pBand->mpNextBand;
+ }
+ else
+ {
+ pPrevBand->mpNextBand = pBand->mpNextBand;
+ }
+
+ pBand = pBand->mpNextBand;
+ delete pOldBand;
+ }
+ }
+
+}
+
+void RegionBand::Union(const RegionBand& rSource)
+{
+ // apply all rectangles from rSource to this
+ ImplRegionBand* pBand = rSource.mpFirstBand;
+
+ while ( pBand )
+ {
+ // insert bands if the boundaries are not already in the list
+ InsertBands(pBand->mnYTop, pBand->mnYBottom);
+
+ // process all elements of the list
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while(pSep)
+ {
+ Union(pSep->mnXLeft, pBand->mnYTop, pSep->mnXRight, pBand->mnYBottom);
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::Exclude(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom)
+{
+ SAL_WARN_IF( nLeft > nRight, "vcl", "RegionBand::Exclude() - nLeft > nRight" );
+ SAL_WARN_IF( nTop > nBottom, "vcl", "RegionBand::Exclude() - nTop > nBottom" );
+
+ // process exclude
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ if(pBand->mnYTop >= nTop)
+ {
+ if(pBand->mnYBottom <= nBottom)
+ {
+ pBand->Exclude(nLeft, nRight);
+ }
+ else
+ {
+#ifdef DBG_UTIL
+ tools::Long nCurY = pBand->mnYBottom;
+ pBand = pBand->mpNextBand;
+
+ while(pBand)
+ {
+ if((pBand->mnYTop < nCurY) || (pBand->mnYBottom < nCurY))
+ {
+ OSL_ENSURE(false, "RegionBand::Exclude() - Bands not sorted!" );
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+#endif
+ break;
+ }
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::XOr(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom)
+{
+ SAL_WARN_IF( nLeft > nRight, "vcl", "RegionBand::Exclude() - nLeft > nRight" );
+ SAL_WARN_IF( nTop > nBottom, "vcl", "RegionBand::Exclude() - nTop > nBottom" );
+
+ // process xor
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ if(pBand->mnYTop >= nTop)
+ {
+ if(pBand->mnYBottom <= nBottom)
+ {
+ pBand->XOr(nLeft, nRight);
+ }
+ else
+ {
+#ifdef DBG_UTIL
+ tools::Long nCurY = pBand->mnYBottom;
+ pBand = pBand->mpNextBand;
+
+ while(pBand)
+ {
+ if((pBand->mnYTop < nCurY) || (pBand->mnYBottom < nCurY))
+ {
+ OSL_ENSURE(false, "RegionBand::XOr() - Bands not sorted!" );
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+#endif
+ break;
+ }
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+}
+
+void RegionBand::Intersect(const RegionBand& rSource)
+{
+ // mark all bands as untouched
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while ( pBand )
+ {
+ pBand->mbTouched = false;
+ pBand = pBand->mpNextBand;
+ }
+
+ pBand = rSource.mpFirstBand;
+
+ while ( pBand )
+ {
+ // insert bands if the boundaries are not already in the list
+ InsertBands( pBand->mnYTop, pBand->mnYBottom );
+
+ // process all elements of the list
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while ( pSep )
+ {
+ // left boundary?
+ if ( pSep == pBand->mpFirstSep )
+ {
+ // process intersection and do not remove untouched bands
+ Exclude( LONG_MIN+1, pBand->mnYTop, pSep->mnXLeft-1, pBand->mnYBottom );
+ }
+
+ // right boundary?
+ if ( pSep->mpNextSep == nullptr )
+ {
+ // process intersection and do not remove untouched bands
+ Exclude( pSep->mnXRight+1, pBand->mnYTop, LONG_MAX-1, pBand->mnYBottom );
+ }
+ else
+ {
+ // process intersection and do not remove untouched bands
+ Exclude( pSep->mnXRight+1, pBand->mnYTop, pSep->mpNextSep->mnXLeft-1, pBand->mnYBottom );
+ }
+
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+ // remove all untouched bands if bands already left
+ ImplRegionBand* pPrevBand = nullptr;
+ pBand = mpFirstBand;
+
+ while ( pBand )
+ {
+ if ( !pBand->mbTouched )
+ {
+ // save pointer
+ ImplRegionBand* pOldBand = pBand;
+
+ // previous element of the list
+ if ( pBand == mpFirstBand )
+ {
+ mpFirstBand = pBand->mpNextBand;
+ }
+ else
+ {
+ pPrevBand->mpNextBand = pBand->mpNextBand;
+ }
+
+ pBand = pBand->mpNextBand;
+ delete pOldBand;
+ }
+ else
+ {
+ pPrevBand = pBand;
+ pBand = pBand->mpNextBand;
+ }
+ }
+
+}
+
+bool RegionBand::Exclude(const RegionBand& rSource)
+{
+ // apply all rectangles to the region passed to this region
+ ImplRegionBand* pBand = rSource.mpFirstBand;
+
+ while ( pBand )
+ {
+ // insert bands if the boundaries are not already in the list
+ InsertBands( pBand->mnYTop, pBand->mnYBottom );
+
+ // process all elements of the list
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while ( pSep )
+ {
+ Exclude( pSep->mnXLeft, pBand->mnYTop, pSep->mnXRight, pBand->mnYBottom );
+ pSep = pSep->mpNextSep;
+ }
+
+ // to test less bands, already check in the loop
+ if ( !OptimizeBandList() )
+ {
+ return false;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+ return true;
+}
+
+bool RegionBand::CheckConsistency() const
+{
+ if (!mpFirstBand)
+ return true;
+ // look in the band list (don't test first band again!)
+ const ImplRegionBand* pBand = mpFirstBand->mpNextBand;
+ while (pBand)
+ {
+ if (!pBand->mpFirstSep)
+ return false;
+ pBand = pBand->mpNextBand;
+ }
+ return true;
+}
+
+tools::Rectangle RegionBand::GetBoundRect() const
+{
+ if (!mpFirstBand)
+ return tools::Rectangle();
+
+ // get the boundaries of the first band
+ tools::Long nYTop(mpFirstBand->mnYTop);
+ tools::Long nYBottom(mpFirstBand->mnYBottom);
+ tools::Long nXLeft(mpFirstBand->GetXLeftBoundary());
+ tools::Long nXRight(mpFirstBand->GetXRightBoundary());
+
+ // look in the band list (don't test first band again!)
+ ImplRegionBand* pBand = mpFirstBand->mpNextBand;
+
+ while ( pBand )
+ {
+ nYBottom = pBand->mnYBottom;
+ nXLeft = std::min( nXLeft, pBand->GetXLeftBoundary() );
+ nXRight = std::max( nXRight, pBand->GetXRightBoundary() );
+
+ pBand = pBand->mpNextBand;
+ }
+
+ return tools::Rectangle( nXLeft, nYTop, nXRight, nYBottom );
+}
+
+void RegionBand::XOr(const RegionBand& rSource)
+{
+ ImplRegionBand* pBand = rSource.mpFirstBand;
+
+ while ( pBand )
+ {
+ // insert bands if the boundaries are not already in the list
+ InsertBands( pBand->mnYTop, pBand->mnYBottom );
+
+ // process all elements of the list
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while ( pSep )
+ {
+ XOr( pSep->mnXLeft, pBand->mnYTop, pSep->mnXRight, pBand->mnYBottom );
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+}
+
+bool RegionBand::Contains(const Point& rPoint) const
+{
+
+ // search band list
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ // is point within band?
+ if((pBand->mnYTop <= rPoint.Y()) && (pBand->mnYBottom >= rPoint.Y()))
+ {
+ // is point within separation of the band?
+ return pBand->Contains(rPoint.X());
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+ return false;
+}
+
+void RegionBand::GetRegionRectangles(RectangleVector& rTarget) const
+{
+ // clear result vector
+ rTarget.clear();
+ ImplRegionBand* pCurrRectBand = mpFirstBand;
+ tools::Rectangle aRectangle;
+
+ while(pCurrRectBand)
+ {
+ ImplRegionBandSep* pCurrRectBandSep = pCurrRectBand->mpFirstSep;
+
+ aRectangle.SetTop( pCurrRectBand->mnYTop );
+ aRectangle.SetBottom( pCurrRectBand->mnYBottom );
+
+ while(pCurrRectBandSep)
+ {
+ aRectangle.SetLeft( pCurrRectBandSep->mnXLeft );
+ aRectangle.SetRight( pCurrRectBandSep->mnXRight );
+ rTarget.push_back(aRectangle);
+ pCurrRectBandSep = pCurrRectBandSep->mpNextSep;
+ }
+
+ pCurrRectBand = pCurrRectBand->mpNextBand;
+ }
+}
+
+sal_uInt32 RegionBand::getRectangleCount() const
+{
+ sal_uInt32 nCount = 0;
+ const ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while(pSep)
+ {
+ nCount++;
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+
+ return nCount;
+}
+
+#ifdef DBG_UTIL
+const char* ImplDbgTestRegionBand(const void* pObj)
+{
+ const RegionBand* pRegionBand = static_cast< const RegionBand* >(pObj);
+
+ if(pRegionBand)
+ {
+ const ImplRegionBand* pBand = pRegionBand->ImplGetFirstRegionBand();
+
+ while(pBand)
+ {
+ if(pBand->mnYBottom < pBand->mnYTop)
+ {
+ return "YBottom < YTop";
+ }
+
+ if(pBand->mpNextBand)
+ {
+ if(pBand->mnYBottom >= pBand->mpNextBand->mnYTop)
+ {
+ return "overlapping bands in region";
+ }
+ }
+
+ if(pBand->mbTouched)
+ {
+ return "Band-mbTouched overwrite";
+ }
+
+ ImplRegionBandSep* pSep = pBand->mpFirstSep;
+
+ while(pSep)
+ {
+ if(pSep->mnXRight < pSep->mnXLeft)
+ {
+ return "XLeft < XRight";
+ }
+
+ if(pSep->mpNextSep)
+ {
+ if(pSep->mnXRight >= pSep->mpNextSep->mnXLeft)
+ {
+ return "overlapping separations in region";
+ }
+ }
+
+ if ( pSep->mbRemoved )
+ {
+ return "Sep-mbRemoved overwrite";
+ }
+
+ pSep = pSep->mpNextSep;
+ }
+
+ pBand = pBand->mpNextBand;
+ }
+ }
+
+ return nullptr;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/salgdiimpl.cxx b/vcl/source/gdi/salgdiimpl.cxx
new file mode 100644
index 0000000000..622c86c395
--- /dev/null
+++ b/vcl/source/gdi/salgdiimpl.cxx
@@ -0,0 +1,24 @@
+/* -*- 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 <salgdiimpl.hxx>
+
+SalGraphicsImpl::~SalGraphicsImpl() {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/salgdilayout.cxx b/vcl/source/gdi/salgdilayout.cxx
new file mode 100644
index 0000000000..0a5eb5140a
--- /dev/null
+++ b/vcl/source/gdi/salgdilayout.cxx
@@ -0,0 +1,940 @@
+/* -*- 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 <sal/config.h>
+
+#include <memory>
+#include <config_features.h>
+#include <sal/log.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <salgdi.hxx>
+#include <salframe.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <FileDefinitionWidgetDraw.hxx>
+#include <rtl/math.hxx>
+#include <comphelper/lok.hxx>
+#include <toolbarvalue.hxx>
+
+// The only common SalFrame method
+
+SalFrameGeometry SalFrame::GetGeometry() const
+{
+ // mirror frame coordinates at parent
+ SalFrame *pParent = GetParent();
+ if( pParent && AllSettings::GetLayoutRTL() )
+ {
+ SalFrameGeometry aGeom = maGeometry;
+ const int nParentX = aGeom.x() - pParent->maGeometry.x();
+ aGeom.setX(pParent->maGeometry.x() + pParent->maGeometry.width() - maGeometry.width() - nParentX);
+ return aGeom;
+ }
+ else
+ return maGeometry;
+}
+
+SalGraphics::SalGraphics()
+: m_nLayout( SalLayoutFlags::NONE ),
+ m_eLastMirrorMode(MirrorMode::NONE),
+ m_nLastMirrorTranslation(0),
+ m_bAntiAlias(false)
+{
+ // read global RTL settings
+ if( AllSettings::GetLayoutRTL() )
+ m_nLayout = SalLayoutFlags::BiDiRtl;
+}
+
+bool SalGraphics::initWidgetDrawBackends(bool bForce)
+{
+ static bool bFileDefinitionsWidgetDraw = !!getenv("VCL_DRAW_WIDGETS_FROM_FILE");
+
+ if (bFileDefinitionsWidgetDraw || bForce)
+ {
+ m_pWidgetDraw.reset(new vcl::FileDefinitionWidgetDraw(*this));
+ auto pFileDefinitionWidgetDraw = static_cast<vcl::FileDefinitionWidgetDraw*>(m_pWidgetDraw.get());
+ if (!pFileDefinitionWidgetDraw->isActive())
+ {
+ m_pWidgetDraw.reset();
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+SalGraphics::~SalGraphics() COVERITY_NOEXCEPT_FALSE
+{
+ // can't call ReleaseFonts here, as the destructor just calls this classes SetFont (pure virtual)!
+}
+
+bool SalGraphics::drawTransformedBitmap(
+ const basegfx::B2DPoint& /* rNull */,
+ const basegfx::B2DPoint& /* rX */,
+ const basegfx::B2DPoint& /* rY */,
+ const SalBitmap& /* rSourceBitmap */,
+ const SalBitmap* /* pAlphaBitmap */,
+ double /* fAlpha */)
+{
+ // here direct support for transformed bitmaps can be implemented
+ return false;
+}
+
+tools::Long SalGraphics::mirror2( tools::Long x, const OutputDevice& rOutDev ) const
+{
+ mirror(x, rOutDev);
+ return x;
+}
+
+inline tools::Long SalGraphics::GetDeviceWidth(const OutputDevice& rOutDev) const
+{
+ return rOutDev.IsVirtual() ? rOutDev.GetOutputWidthPixel() : GetGraphicsWidth();
+}
+
+void SalGraphics::mirror( tools::Long& x, const OutputDevice& rOutDev ) const
+{
+ const tools::Long w = GetDeviceWidth(rOutDev);
+ if( !w )
+ return;
+
+ if (rOutDev.ImplIsAntiparallel() )
+ {
+ // mirror this window back
+ if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ tools::Long devX = w - rOutDev.GetOutputWidthPixel() - rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ x = devX + (x - rOutDev.GetOutOffXPixel());
+ }
+ else
+ {
+ tools::Long devX = rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ x = rOutDev.GetOutputWidthPixel() - (x - devX) + rOutDev.GetOutOffXPixel() - 1;
+ }
+ }
+ else if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ x = w-1-x;
+}
+
+void SalGraphics::mirror( tools::Long& x, tools::Long nWidth, const OutputDevice& rOutDev, bool bBack ) const
+{
+ const tools::Long w = GetDeviceWidth(rOutDev);
+ if( !w )
+ return;
+
+ if (rOutDev.ImplIsAntiparallel() )
+ {
+ // mirror this window back
+ if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ tools::Long devX = w - rOutDev.GetOutputWidthPixel() - rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ if( bBack )
+ x = x - devX + rOutDev.GetOutOffXPixel();
+ else
+ x = devX + (x - rOutDev.GetOutOffXPixel());
+ }
+ else
+ {
+ tools::Long devX = rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ if( bBack )
+ x = devX + (rOutDev.GetOutputWidthPixel() + devX) - (x + nWidth);
+ else
+ x = rOutDev.GetOutputWidthPixel() - (x - devX) + rOutDev.GetOutOffXPixel() - nWidth;
+ }
+ }
+ else if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ x = w-nWidth-x;
+}
+
+bool SalGraphics::mirror( sal_uInt32 nPoints, const Point *pPtAry, Point *pPtAry2, const OutputDevice& rOutDev ) const
+{
+ const tools::Long w = GetDeviceWidth(rOutDev);
+ if( w )
+ {
+ sal_uInt32 i, j;
+
+ if (rOutDev.ImplIsAntiparallel())
+ {
+ // mirror this window back
+ if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ tools::Long devX = w - rOutDev.GetOutputWidthPixel() - rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ for( i=0, j=nPoints-1; i<nPoints; i++,j-- )
+ {
+ pPtAry2[j].setX( devX + (pPtAry[i].getX() - rOutDev.GetOutOffXPixel()) );
+ pPtAry2[j].setY( pPtAry[i].getY() );
+ }
+ }
+ else
+ {
+ tools::Long devX = rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ for( i=0, j=nPoints-1; i<nPoints; i++,j-- )
+ {
+ pPtAry2[j].setX( rOutDev.GetOutputWidthPixel() - (pPtAry[i].getX() - devX) + rOutDev.GetOutOffXPixel() - 1 );
+ pPtAry2[j].setY( pPtAry[i].getY() );
+ }
+ }
+ }
+ else if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ for( i=0, j=nPoints-1; i<nPoints; i++,j-- )
+ {
+ pPtAry2[j].setX( w-1-pPtAry[i].getX() );
+ pPtAry2[j].setY( pPtAry[i].getY() );
+ }
+ }
+ return true;
+ }
+ else
+ return false;
+}
+
+void SalGraphics::mirror( vcl::Region& rRgn, const OutputDevice& rOutDev ) const
+{
+ if( rRgn.HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ const basegfx::B2DPolyPolygon aPolyPoly(mirror(rRgn.GetAsB2DPolyPolygon(), rOutDev));
+
+ rRgn = vcl::Region(aPolyPoly);
+ }
+ else
+ {
+ RectangleVector aRectangles;
+ rRgn.GetRegionRectangles(aRectangles);
+ rRgn.SetEmpty();
+
+ for (auto & rectangle : aRectangles)
+ {
+ mirror(rectangle, rOutDev);
+ rRgn.Union(rectangle);
+ }
+
+ //ImplRegionInfo aInfo;
+ //bool bRegionRect;
+ //Region aMirroredRegion;
+ //long nX, nY, nWidth, nHeight;
+
+ //bRegionRect = rRgn.ImplGetFirstRect( aInfo, nX, nY, nWidth, nHeight );
+ //while ( bRegionRect )
+ //{
+ // Rectangle aRect( Point(nX, nY), Size(nWidth, nHeight) );
+ // mirror( aRect, rOutDev, bBack );
+ // aMirroredRegion.Union( aRect );
+ // bRegionRect = rRgn.ImplGetNextRect( aInfo, nX, nY, nWidth, nHeight );
+ //}
+ //rRgn = aMirroredRegion;
+ }
+}
+
+void SalGraphics::mirror( tools::Rectangle& rRect, const OutputDevice& rOutDev, bool bBack ) const
+{
+ tools::Long nWidth = rRect.GetWidth();
+ tools::Long x = rRect.Left();
+ tools::Long x_org = x;
+
+ mirror( x, nWidth, rOutDev, bBack );
+ rRect.Move( x - x_org, 0 );
+}
+
+basegfx::B2DPolyPolygon SalGraphics::mirror( const basegfx::B2DPolyPolygon& i_rPoly, const OutputDevice& i_rOutDev ) const
+{
+ const basegfx::B2DHomMatrix& rMirror(getMirror(i_rOutDev));
+
+ if(rMirror.isIdentity())
+ {
+ return i_rPoly;
+ }
+ else
+ {
+ basegfx::B2DPolyPolygon aRet(i_rPoly);
+ aRet.transform(rMirror);
+ aRet.flip();
+ return aRet;
+ }
+}
+
+SalGraphics::MirrorMode SalGraphics::GetMirrorMode(const OutputDevice& rOutDev) const
+{
+ if (rOutDev.ImplIsAntiparallel())
+ {
+ if (m_nLayout & SalLayoutFlags::BiDiRtl)
+ return MirrorMode::AntiparallelBiDi;
+ else
+ return MirrorMode::Antiparallel;
+ }
+ else if (m_nLayout & SalLayoutFlags::BiDiRtl)
+ return MirrorMode::BiDi;
+ return MirrorMode::NONE;
+}
+
+const basegfx::B2DHomMatrix& SalGraphics::getMirror( const OutputDevice& i_rOutDev ) const
+{
+ // get mirroring transformation
+ MirrorMode eNewMirrorMode = GetMirrorMode(i_rOutDev);
+ tools::Long nTranslate(0);
+
+ switch (eNewMirrorMode)
+ {
+ case MirrorMode::AntiparallelBiDi:
+ {
+ const tools::Long w = GetDeviceWidth(i_rOutDev);
+ SAL_WARN_IF(!w, "vcl", "missing graphics width");
+ nTranslate = w - i_rOutDev.GetOutputWidthPixel() - (2 * i_rOutDev.GetOutOffXPixel());
+ break;
+ }
+ case MirrorMode::Antiparallel:
+ {
+ nTranslate = i_rOutDev.GetOutputWidthPixel() + (2 * i_rOutDev.GetOutOffXPixel()) - 1;
+ break;
+ }
+ case MirrorMode::BiDi:
+ {
+ const tools::Long w = GetDeviceWidth(i_rOutDev);
+ SAL_WARN_IF(!w, "vcl", "missing graphics width");
+ nTranslate = w - 1;
+ break;
+ }
+ case MirrorMode::NONE:
+ break;
+ }
+
+ // if the translation (due to device width), or mirror state of the device changed, then m_aLastMirror is invalid
+ bool bLastMirrorValid = eNewMirrorMode == m_eLastMirrorMode && nTranslate == m_nLastMirrorTranslation;
+ if (!bLastMirrorValid)
+ {
+ const_cast<SalGraphics*>(this)->m_nLastMirrorTranslation = nTranslate;
+ const_cast<SalGraphics*>(this)->m_eLastMirrorMode = eNewMirrorMode;
+
+ switch (eNewMirrorMode)
+ {
+ // mirror this window back
+ case MirrorMode::AntiparallelBiDi:
+ {
+ /* This path gets exercised in calc's RTL UI (e.g. SAL_RTL_ENABLED=1)
+ with its LTR horizontal scrollbar */
+
+ // Original code was:
+ // double devX = w-i_rOutDev.GetOutputWidthPixel()-i_rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ // aRet.setX( devX + (i_rPoint.getX() - i_rOutDev.GetOutOffXPixel()) );
+ const_cast<SalGraphics*>(this)->m_aLastMirror = basegfx::utils::createTranslateB2DHomMatrix(
+ nTranslate, 0.0);
+ break;
+ }
+ case MirrorMode::Antiparallel:
+ {
+ /* This path gets exercised in writers's LTR UI with a RTL horizontal
+ scrollbar, cross-reference dialog populated from contents from a
+ RTL document tdf#131725 */
+
+ // Original code was;
+ // tools::Long devX = rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX
+ // x = rOutDev.GetOutputWidthPixel() - (x - devX) + rOutDev.GetOutOffXPixel() - 1;
+ const_cast<SalGraphics*>(this)->m_aLastMirror = basegfx::utils::createScaleTranslateB2DHomMatrix(
+ -1.0,
+ 1.0,
+ nTranslate,
+ 0.0);
+ break;
+ }
+ case MirrorMode::BiDi:
+ {
+ // Original code was:
+ // aRet.setX( w-1-i_rPoint.getX() );
+ // -mirror X -> scale(-1.0, 1.0)
+ // -translate X -> translate(w-1, 0)
+ // Checked this one, works as expected.
+ const_cast<SalGraphics*>(this)->m_aLastMirror = basegfx::utils::createScaleTranslateB2DHomMatrix(
+ -1.0,
+ 1.0,
+ nTranslate,
+ 0.0);
+ break;
+ }
+ case MirrorMode::NONE:
+ const_cast<SalGraphics*>(this)->m_aLastMirror.identity();
+ break;
+ }
+ }
+
+ return m_aLastMirror;
+}
+
+void SalGraphics::SetClipRegion( const vcl::Region& i_rClip, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ vcl::Region aMirror( i_rClip );
+ mirror( aMirror, rOutDev );
+ setClipRegion( aMirror );
+ }
+ else
+ {
+ setClipRegion( i_rClip );
+ }
+}
+
+void SalGraphics::DrawPixel( tools::Long nX, tools::Long nY, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, rOutDev );
+ drawPixel( nX, nY );
+}
+
+void SalGraphics::DrawPixel( tools::Long nX, tools::Long nY, Color nColor, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, rOutDev );
+ drawPixel( nX, nY, nColor );
+}
+
+void SalGraphics::DrawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ mirror( nX1, rOutDev );
+ mirror( nX2, rOutDev );
+ }
+ drawLine( nX1, nY1, nX2, nY2 );
+}
+
+void SalGraphics::DrawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, nWidth, rOutDev );
+ drawRect( nX, nY, nWidth, nHeight );
+}
+
+void SalGraphics::DrawPolyLine( sal_uInt32 nPoints, Point const * pPtAry, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev );
+ drawPolyLine( nPoints, bCopied ? pPtAry2.get() : pPtAry );
+ }
+ else
+ drawPolyLine( nPoints, pPtAry );
+}
+
+void SalGraphics::DrawPolygon( sal_uInt32 nPoints, const Point* pPtAry, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev );
+ drawPolygon( nPoints, bCopied ? pPtAry2.get() : pPtAry );
+ }
+ else
+ drawPolygon( nPoints, pPtAry );
+}
+
+void SalGraphics::DrawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, const Point** pPtAry, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ // TODO: optimize, reduce new/delete calls
+ std::unique_ptr<Point*[]> pPtAry2( new Point*[nPoly] );
+ sal_uLong i;
+ for(i=0; i<nPoly; i++)
+ {
+ sal_uLong nPoints = pPoints[i];
+ pPtAry2[i] = new Point[ nPoints ];
+ mirror( nPoints, pPtAry[i], pPtAry2[i], rOutDev );
+ }
+
+ drawPolyPolygon( nPoly, pPoints, const_cast<const Point**>(pPtAry2.get()) );
+
+ for(i=0; i<nPoly; i++)
+ delete [] pPtAry2[i];
+ }
+ else
+ drawPolyPolygon( nPoly, pPoints, pPtAry );
+}
+
+namespace
+{
+ basegfx::B2DHomMatrix createTranslateToMirroredBounds(const basegfx::B2DRange &rBoundingBox, const basegfx::B2DHomMatrix& rMirror)
+ {
+ basegfx::B2DRange aRTLBoundingBox(rBoundingBox);
+ aRTLBoundingBox *= rMirror;
+ return basegfx::utils::createTranslateB2DHomMatrix(aRTLBoundingBox.getMinX() - rBoundingBox.getMinX(), 0);
+ }
+}
+
+void SalGraphics::DrawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& i_rPolyPolygon,
+ double i_fTransparency,
+ const OutputDevice& i_rOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || i_rOutDev.IsRTLEnabled() )
+ {
+ // mirroring set
+ const basegfx::B2DHomMatrix& rMirror(getMirror(i_rOutDev));
+ if(!rMirror.isIdentity())
+ {
+ drawPolyPolygon(
+ rMirror * rObjectToDevice,
+ i_rPolyPolygon,
+ i_fTransparency);
+ return;
+ }
+ }
+
+ drawPolyPolygon(
+ rObjectToDevice,
+ i_rPolyPolygon,
+ i_fTransparency);
+}
+
+bool SalGraphics::DrawPolyLineBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry, const OutputDevice& rOutDev )
+{
+ bool bResult = false;
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev );
+ bResult = drawPolyLineBezier( nPoints, bCopied ? pPtAry2.get() : pPtAry, pFlgAry );
+ }
+ else
+ bResult = drawPolyLineBezier( nPoints, pPtAry, pFlgAry );
+ return bResult;
+}
+
+bool SalGraphics::DrawPolygonBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry, const OutputDevice& rOutDev )
+{
+ bool bResult = false;
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev );
+ bResult = drawPolygonBezier( nPoints, bCopied ? pPtAry2.get() : pPtAry, pFlgAry );
+ }
+ else
+ bResult = drawPolygonBezier( nPoints, pPtAry, pFlgAry );
+ return bResult;
+}
+
+bool SalGraphics::DrawPolyPolygonBezier( sal_uInt32 i_nPoly, const sal_uInt32* i_pPoints,
+ const Point* const* i_pPtAry, const PolyFlags* const* i_pFlgAry, const OutputDevice& i_rOutDev )
+{
+ bool bRet = false;
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || i_rOutDev.IsRTLEnabled() )
+ {
+ // TODO: optimize, reduce new/delete calls
+ std::unique_ptr<Point*[]> pPtAry2( new Point*[i_nPoly] );
+ sal_uLong i;
+ for(i=0; i<i_nPoly; i++)
+ {
+ sal_uLong nPoints = i_pPoints[i];
+ pPtAry2[i] = new Point[ nPoints ];
+ mirror( nPoints, i_pPtAry[i], pPtAry2[i], i_rOutDev );
+ }
+
+ bRet = drawPolyPolygonBezier( i_nPoly, i_pPoints, const_cast<const Point* const *>(pPtAry2.get()), i_pFlgAry );
+
+ for(i=0; i<i_nPoly; i++)
+ delete [] pPtAry2[i];
+ }
+ else
+ bRet = drawPolyPolygonBezier( i_nPoly, i_pPoints, i_pPtAry, i_pFlgAry );
+ return bRet;
+}
+
+bool SalGraphics::DrawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& i_rPolygon,
+ double i_fTransparency,
+ double i_rLineWidth,
+ const std::vector< double >* i_pStroke, // MM01
+ basegfx::B2DLineJoin i_eLineJoin,
+ css::drawing::LineCap i_eLineCap,
+ double i_fMiterMinimumAngle,
+ bool bPixelSnapHairline,
+ const OutputDevice& i_rOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || i_rOutDev.IsRTLEnabled() )
+ {
+ // mirroring set
+ const basegfx::B2DHomMatrix& rMirror(getMirror(i_rOutDev));
+ if(!rMirror.isIdentity())
+ {
+ return drawPolyLine(
+ rMirror * rObjectToDevice,
+ i_rPolygon,
+ i_fTransparency,
+ i_rLineWidth,
+ i_pStroke, // MM01
+ i_eLineJoin,
+ i_eLineCap,
+ i_fMiterMinimumAngle,
+ bPixelSnapHairline);
+ }
+ }
+
+ // no mirroring set (or identity), use standard call
+ return drawPolyLine(
+ rObjectToDevice,
+ i_rPolygon,
+ i_fTransparency,
+ i_rLineWidth,
+ i_pStroke, // MM01
+ i_eLineJoin,
+ i_eLineCap,
+ i_fMiterMinimumAngle,
+ bPixelSnapHairline);
+}
+
+bool SalGraphics::DrawGradient(const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, const OutputDevice& rOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ tools::PolyPolygon aMirrored(mirror(rPolyPoly.getB2DPolyPolygon(), rOutDev));
+ return drawGradient(aMirrored, rGradient);
+ }
+
+ return drawGradient( rPolyPoly, rGradient );
+}
+
+void SalGraphics::CopyArea( tools::Long nDestX, tools::Long nDestY,
+ tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight,
+ const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ mirror( nDestX, nSrcWidth, rOutDev );
+ mirror( nSrcX, nSrcWidth, rOutDev );
+ }
+ copyArea( nDestX, nDestY, nSrcX, nSrcY, nSrcWidth, nSrcHeight, true/*bWindowInvalidate*/ );
+}
+
+void SalGraphics::CopyBits(const SalTwoRect& rPosAry, const OutputDevice& rOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ copyBits( aPosAry2, nullptr );
+ }
+ else
+ copyBits( rPosAry, nullptr );
+}
+
+void SalGraphics::CopyBits(const SalTwoRect& rPosAry, SalGraphics& rSrcGraphics,
+ const OutputDevice& rOutDev, const OutputDevice& rSrcOutDev)
+{
+ if( ( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) ||
+ ( (rSrcGraphics.GetLayout() & SalLayoutFlags::BiDiRtl) || rSrcOutDev.IsRTLEnabled()) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ if( (rSrcGraphics.GetLayout() & SalLayoutFlags::BiDiRtl) || rSrcOutDev.IsRTLEnabled() )
+ mirror( aPosAry2.mnSrcX, aPosAry2.mnSrcWidth, rSrcOutDev );
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ copyBits( aPosAry2, &rSrcGraphics );
+ }
+ else
+ copyBits( rPosAry, &rSrcGraphics );
+}
+
+void SalGraphics::DrawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ drawBitmap( aPosAry2, rSalBitmap );
+ }
+ else
+ drawBitmap( rPosAry, rSalBitmap );
+}
+
+void SalGraphics::DrawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ drawBitmap( aPosAry2, rSalBitmap, rTransparentBitmap );
+ }
+ else
+ drawBitmap( rPosAry, rSalBitmap, rTransparentBitmap );
+}
+
+void SalGraphics::DrawMask( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ Color nMaskColor, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ drawMask( aPosAry2, rSalBitmap, nMaskColor );
+ }
+ else
+ drawMask( rPosAry, rSalBitmap, nMaskColor );
+}
+
+std::shared_ptr<SalBitmap> SalGraphics::GetBitmap( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, nWidth, rOutDev );
+ return getBitmap( nX, nY, nWidth, nHeight );
+}
+
+Color SalGraphics::GetPixel( tools::Long nX, tools::Long nY, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, rOutDev );
+ return getPixel( nX, nY );
+}
+
+void SalGraphics::Invert( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, SalInvert nFlags, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, nWidth, rOutDev );
+ invert( nX, nY, nWidth, nHeight, nFlags );
+}
+
+void SalGraphics::Invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev );
+ invert( nPoints, bCopied ? pPtAry2.get() : pPtAry, nFlags );
+ }
+ else
+ invert( nPoints, pPtAry, nFlags );
+}
+
+bool SalGraphics::DrawEPS( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, void* pPtr, sal_uInt32 nSize, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, nWidth, rOutDev );
+ return drawEPS( nX, nY, nWidth, nHeight, pPtr, nSize );
+}
+
+bool SalGraphics::HitTestNativeScrollbar(ControlPart nPart, const tools::Rectangle& rControlRegion,
+ const Point& aPos, bool& rIsInside, const OutputDevice& rOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ Point pt( aPos );
+ tools::Rectangle rgn( rControlRegion );
+ pt.setX( mirror2( pt.X(), rOutDev ) );
+ mirror( rgn, rOutDev );
+ return forWidget()->hitTestNativeControl( ControlType::Scrollbar, nPart, rgn, pt, rIsInside );
+ }
+ else
+ return forWidget()->hitTestNativeControl( ControlType::Scrollbar, nPart, rControlRegion, aPos, rIsInside );
+}
+
+void SalGraphics::mirror( ImplControlValue& rVal, const OutputDevice& rOutDev ) const
+{
+ switch( rVal.getType() )
+ {
+ case ControlType::Slider:
+ {
+ SliderValue* pSlVal = static_cast<SliderValue*>(&rVal);
+ mirror(pSlVal->maThumbRect, rOutDev);
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ ScrollbarValue* pScVal = static_cast<ScrollbarValue*>(&rVal);
+ mirror(pScVal->maThumbRect, rOutDev);
+ mirror(pScVal->maButton1Rect, rOutDev);
+ mirror(pScVal->maButton2Rect, rOutDev);
+ }
+ break;
+ case ControlType::Spinbox:
+ case ControlType::SpinButtons:
+ {
+ SpinbuttonValue* pSpVal = static_cast<SpinbuttonValue*>(&rVal);
+ mirror(pSpVal->maUpperRect, rOutDev);
+ mirror(pSpVal->maLowerRect, rOutDev);
+ }
+ break;
+ case ControlType::Toolbar:
+ {
+ ToolbarValue* pTVal = static_cast<ToolbarValue*>(&rVal);
+ mirror(pTVal->maGripRect, rOutDev);
+ }
+ break;
+ default: break;
+ }
+}
+
+bool SalGraphics::DrawNativeControl( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion,
+ ControlState nState, const ImplControlValue& aValue,
+ const OUString& aCaption, const OutputDevice& rOutDev,
+ const Color& rBackgroundColor)
+{
+ bool bRet = false;
+ tools::Rectangle aControlRegion(rControlRegion);
+ if (aControlRegion.IsEmpty() || aControlRegion.GetWidth() <= 0 || aControlRegion.GetHeight() <= 0)
+ return bRet;
+
+ bool bLayoutRTL = true && (m_nLayout & SalLayoutFlags::BiDiRtl);
+ bool bDevRTL = rOutDev.IsRTLEnabled();
+ bool bIsLOK = comphelper::LibreOfficeKit::isActive();
+ if( (bLayoutRTL || bDevRTL) && !bIsLOK )
+ {
+ mirror(aControlRegion, rOutDev);
+ std::unique_ptr< ImplControlValue > mirrorValue( aValue.clone());
+ mirror( *mirrorValue, rOutDev );
+ bRet = forWidget()->drawNativeControl(nType, nPart, aControlRegion, nState, *mirrorValue, aCaption, rBackgroundColor);
+ }
+ else
+ bRet = forWidget()->drawNativeControl(nType, nPart, aControlRegion, nState, aValue, aCaption, rBackgroundColor);
+
+ if (bRet && m_pWidgetDraw)
+ handleDamage(aControlRegion);
+ return bRet;
+}
+
+bool SalGraphics::GetNativeControlRegion( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion, ControlState nState,
+ const ImplControlValue& aValue,
+ tools::Rectangle &rNativeBoundingRegion, tools::Rectangle &rNativeContentRegion, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ tools::Rectangle rgn( rControlRegion );
+ mirror( rgn, rOutDev );
+ std::unique_ptr< ImplControlValue > mirrorValue( aValue.clone());
+ mirror( *mirrorValue, rOutDev );
+ if (forWidget()->getNativeControlRegion(nType, nPart, rgn, nState, *mirrorValue, OUString(), rNativeBoundingRegion, rNativeContentRegion))
+ {
+ mirror( rNativeBoundingRegion, rOutDev, true );
+ mirror( rNativeContentRegion, rOutDev, true );
+ return true;
+ }
+ return false;
+ }
+ else
+ return forWidget()->getNativeControlRegion(nType, nPart, rControlRegion, nState, aValue, OUString(), rNativeBoundingRegion, rNativeContentRegion);
+}
+
+bool SalGraphics::BlendBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rBitmap,
+ const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ return blendBitmap( aPosAry2, rBitmap );
+ }
+ else
+ return blendBitmap( rPosAry, rBitmap );
+}
+
+bool SalGraphics::BlendAlphaBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap,
+ const SalBitmap& rAlphaBitmap,
+ const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ return blendAlphaBitmap( aPosAry2, rSrcBitmap, rMaskBitmap, rAlphaBitmap );
+ }
+ else
+ return blendAlphaBitmap( rPosAry, rSrcBitmap, rMaskBitmap, rAlphaBitmap );
+}
+
+bool SalGraphics::DrawAlphaBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap,
+ const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev );
+ return drawAlphaBitmap( aPosAry2, rSourceBitmap, rAlphaBitmap );
+ }
+ else
+ return drawAlphaBitmap( rPosAry, rSourceBitmap, rAlphaBitmap );
+}
+
+bool SalGraphics::DrawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap,
+ double fAlpha,
+ const OutputDevice& rOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ {
+ // mirroring set
+ const basegfx::B2DHomMatrix& rMirror(getMirror(rOutDev));
+ if (!rMirror.isIdentity())
+ {
+ basegfx::B2DPolygon aPoints({rNull, rX, rY});
+ basegfx::B2DRange aBoundingBox(aPoints.getB2DRange());
+ auto aTranslateToMirroredBounds = createTranslateToMirroredBounds(aBoundingBox, rMirror);
+
+ basegfx::B2DPoint aNull = aTranslateToMirroredBounds * rNull;
+ basegfx::B2DPoint aX = aTranslateToMirroredBounds * rX;
+ basegfx::B2DPoint aY = aTranslateToMirroredBounds * rY;
+
+ return drawTransformedBitmap(aNull, aX, aY, rSourceBitmap, pAlphaBitmap, fAlpha);
+ }
+ }
+
+ return drawTransformedBitmap(rNull, rX, rY, rSourceBitmap, pAlphaBitmap, fAlpha);
+}
+
+bool SalGraphics::HasFastDrawTransformedBitmap() const
+{
+ return hasFastDrawTransformedBitmap();
+}
+
+bool SalGraphics::DrawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ sal_uInt8 nTransparency, const OutputDevice& rOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() )
+ mirror( nX, nWidth, rOutDev );
+
+ return drawAlphaRect( nX, nY, nWidth, nHeight, nTransparency );
+}
+
+OUString SalGraphics::getRenderBackendName() const
+{
+ if (GetImpl())
+ return GetImpl()->getRenderBackendName();
+ return OUString();
+}
+
+bool SalGraphics::ShouldDownscaleIconsAtSurface(double* pScaleOut) const
+{
+ if (pScaleOut)
+ *pScaleOut = comphelper::LibreOfficeKit::getDPIScale();
+ return comphelper::LibreOfficeKit::isActive();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/sallayout.cxx b/vcl/source/gdi/sallayout.cxx
new file mode 100644
index 0000000000..af281127ba
--- /dev/null
+++ b/vcl/source/gdi/sallayout.cxx
@@ -0,0 +1,1182 @@
+/* -*- 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 <sal/config.h>
+
+#include <iostream>
+#include <iomanip>
+
+#include <sal/log.hxx>
+
+#include <cstdio>
+
+#include <math.h>
+
+#include <ImplLayoutArgs.hxx>
+#include <salgdi.hxx>
+#include <sallayout.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <i18nlangtag/lang.h>
+
+#include <vcl/svapp.hxx>
+
+#include <algorithm>
+#include <memory>
+
+#include <impglyphitem.hxx>
+
+// Glyph Flags
+#define GF_FONTMASK 0xF0000000
+#define GF_FONTSHIFT 28
+
+
+sal_UCS4 GetLocalizedChar( sal_UCS4 nChar, LanguageType eLang )
+{
+ // currently only conversion from ASCII digits is interesting
+ if( (nChar < '0') || ('9' < nChar) )
+ return nChar;
+
+ int nOffset;
+ // eLang & LANGUAGE_MASK_PRIMARY catches language independent of region.
+ // CAVEAT! To some like Mongolian MS assigned the same primary language
+ // although the script type is different!
+ LanguageType pri = primary(eLang);
+ if( pri == primary(LANGUAGE_ARABIC_SAUDI_ARABIA) )
+ nOffset = 0x0660 - '0'; // arabic-indic digits
+ else if ( pri.anyOf(
+ primary(LANGUAGE_FARSI),
+ primary(LANGUAGE_URDU_PAKISTAN),
+ primary(LANGUAGE_PUNJABI), //???
+ primary(LANGUAGE_SINDHI)))
+ nOffset = 0x06F0 - '0'; // eastern arabic-indic digits
+ else if ( pri == primary(LANGUAGE_BENGALI) )
+ nOffset = 0x09E6 - '0'; // bengali
+ else if ( pri == primary(LANGUAGE_HINDI) )
+ nOffset = 0x0966 - '0'; // devanagari
+ else if ( pri.anyOf(
+ primary(LANGUAGE_AMHARIC_ETHIOPIA),
+ primary(LANGUAGE_TIGRIGNA_ETHIOPIA)))
+ // TODO case:
+ nOffset = 0x1369 - '0'; // ethiopic
+ else if ( pri == primary(LANGUAGE_GUJARATI) )
+ nOffset = 0x0AE6 - '0'; // gujarati
+#ifdef LANGUAGE_GURMUKHI // TODO case:
+ else if ( pri == primary(LANGUAGE_GURMUKHI) )
+ nOffset = 0x0A66 - '0'; // gurmukhi
+#endif
+ else if ( pri == primary(LANGUAGE_KANNADA) )
+ nOffset = 0x0CE6 - '0'; // kannada
+ else if ( pri == primary(LANGUAGE_KHMER))
+ nOffset = 0x17E0 - '0'; // khmer
+ else if ( pri == primary(LANGUAGE_LAO) )
+ nOffset = 0x0ED0 - '0'; // lao
+ else if ( pri == primary(LANGUAGE_MALAYALAM) )
+ nOffset = 0x0D66 - '0'; // malayalam
+ else if ( pri == primary(LANGUAGE_MONGOLIAN_MONGOLIAN_LSO))
+ {
+ if (eLang.anyOf(
+ LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA,
+ LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA,
+ LANGUAGE_MONGOLIAN_MONGOLIAN_LSO))
+ nOffset = 0x1810 - '0'; // mongolian
+ else
+ nOffset = 0; // mongolian cyrillic
+ }
+ else if ( pri == primary(LANGUAGE_BURMESE) )
+ nOffset = 0x1040 - '0'; // myanmar
+ else if ( pri == primary(LANGUAGE_ODIA) )
+ nOffset = 0x0B66 - '0'; // odia
+ else if ( pri == primary(LANGUAGE_TAMIL) )
+ nOffset = 0x0BE7 - '0'; // tamil
+ else if ( pri == primary(LANGUAGE_TELUGU) )
+ nOffset = 0x0C66 - '0'; // telugu
+ else if ( pri == primary(LANGUAGE_THAI) )
+ nOffset = 0x0E50 - '0'; // thai
+ else if ( pri == primary(LANGUAGE_TIBETAN) )
+ nOffset = 0x0F20 - '0'; // tibetan
+ else
+ {
+ nOffset = 0;
+ }
+
+ nChar += nOffset;
+ return nChar;
+}
+
+SalLayout::SalLayout()
+: mnMinCharPos( -1 ),
+ mnEndCharPos( -1 ),
+ maLanguageTag( LANGUAGE_DONTKNOW ),
+ mnOrientation( 0 ),
+ maDrawOffset( 0, 0 ),
+ mbSubpixelPositioning(false)
+{}
+
+SalLayout::~SalLayout()
+{}
+
+void SalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs )
+{
+ mnMinCharPos = rArgs.mnMinCharPos;
+ mnEndCharPos = rArgs.mnEndCharPos;
+ mnOrientation = rArgs.mnOrientation;
+ maLanguageTag = rArgs.maLanguageTag;
+}
+
+basegfx::B2DPoint SalLayout::GetDrawPosition(const basegfx::B2DPoint& rRelative) const
+{
+ basegfx::B2DPoint aPos(maDrawBase);
+ basegfx::B2DPoint aOfs(rRelative.getX() + maDrawOffset.X(),
+ rRelative.getY() + maDrawOffset.Y());
+
+ if( mnOrientation == 0_deg10 )
+ aPos += aOfs;
+ else
+ {
+ // cache trigonometric results
+ static Degree10 nOldOrientation(0);
+ static double fCos = 1.0, fSin = 0.0;
+ if( nOldOrientation != mnOrientation )
+ {
+ nOldOrientation = mnOrientation;
+ double fRad = toRadians(mnOrientation);
+ fCos = cos( fRad );
+ fSin = sin( fRad );
+ }
+
+ double fX = aOfs.getX();
+ double fY = aOfs.getY();
+ if (mbSubpixelPositioning)
+ {
+ double nX = +fCos * fX + fSin * fY;
+ double nY = +fCos * fY - fSin * fX;
+ aPos += basegfx::B2DPoint(nX, nY);
+ }
+ else
+ {
+ tools::Long nX = static_cast<tools::Long>( +fCos * fX + fSin * fY );
+ tools::Long nY = static_cast<tools::Long>( +fCos * fY - fSin * fX );
+ aPos += basegfx::B2DPoint(nX, nY);
+ }
+ }
+
+ return aPos;
+}
+
+bool SalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rVector) const
+{
+ bool bAllOk = true;
+ bool bOneOk = false;
+
+ basegfx::B2DPolyPolygon aGlyphOutline;
+
+ basegfx::B2DPoint aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ const LogicalFontInstance* pGlyphFont;
+ while (GetNextGlyph(&pGlyph, aPos, nStart, &pGlyphFont))
+ {
+ // get outline of individual glyph, ignoring "empty" glyphs
+ bool bSuccess = pGlyph->GetGlyphOutline(pGlyphFont, aGlyphOutline);
+ bAllOk &= bSuccess;
+ bOneOk |= bSuccess;
+ // only add non-empty outlines
+ if( bSuccess && (aGlyphOutline.count() > 0) )
+ {
+ if( aPos.getX() || aPos.getY() )
+ {
+ aGlyphOutline.transform(basegfx::utils::createTranslateB2DHomMatrix(aPos.getX(), aPos.getY()));
+ }
+
+ // insert outline at correct position
+ rVector.push_back( aGlyphOutline );
+ }
+ }
+
+ return (bAllOk && bOneOk);
+}
+
+bool SalLayout::GetBoundRect(tools::Rectangle& rRect) const
+{
+ bool bRet = false;
+ rRect.SetEmpty();
+
+ tools::Rectangle aRectangle;
+
+ basegfx::B2DPoint aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ const LogicalFontInstance* pGlyphFont;
+ while (GetNextGlyph(&pGlyph, aPos, nStart, &pGlyphFont))
+ {
+ // get bounding rectangle of individual glyph
+ if (pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle))
+ {
+ if (!aRectangle.IsEmpty())
+ {
+ aRectangle.AdjustLeft(std::floor(aPos.getX()));
+ aRectangle.AdjustRight(std::ceil(aPos.getX()));
+ aRectangle.AdjustTop(std::floor(aPos.getY()));
+ aRectangle.AdjustBottom(std::ceil(aPos.getY()));
+
+ // merge rectangle
+ if (rRect.IsEmpty())
+ rRect = aRectangle;
+ else
+ rRect.Union(aRectangle);
+ }
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+SalLayoutGlyphs SalLayout::GetGlyphs() const
+{
+ return SalLayoutGlyphs(); // invalid
+}
+
+double GenericSalLayout::FillDXArray( std::vector<double>* pCharWidths, const OUString& rStr ) const
+{
+ if (pCharWidths)
+ GetCharWidths(*pCharWidths, rStr);
+
+ return GetTextWidth();
+}
+
+// the text width is the maximum logical extent of all glyphs
+double GenericSalLayout::GetTextWidth() const
+{
+ if (!m_GlyphItems.IsValid())
+ return 0;
+
+ double nWidth = 0;
+ for (auto const& aGlyphItem : m_GlyphItems)
+ nWidth += aGlyphItem.newWidth();
+
+ return nWidth;
+}
+
+void GenericSalLayout::Justify(double nNewWidth)
+{
+ double nOldWidth = GetTextWidth();
+ if( !nOldWidth || nNewWidth==nOldWidth )
+ return;
+
+ if (!m_GlyphItems.IsValid())
+ {
+ return;
+ }
+ // find rightmost glyph, it won't get stretched
+ std::vector<GlyphItem>::iterator pGlyphIterRight = m_GlyphItems.begin();
+ pGlyphIterRight += m_GlyphItems.size() - 1;
+ std::vector<GlyphItem>::iterator pGlyphIter;
+ // count stretchable glyphs
+ int nStretchable = 0;
+ double nMaxGlyphWidth = 0;
+ for(pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter)
+ {
+ if( !pGlyphIter->IsInCluster() )
+ ++nStretchable;
+ if (nMaxGlyphWidth < pGlyphIter->origWidth())
+ nMaxGlyphWidth = pGlyphIter->origWidth();
+ }
+
+ // move rightmost glyph to requested position
+ nOldWidth -= pGlyphIterRight->origWidth();
+ if( nOldWidth <= 0 )
+ return;
+ if( nNewWidth < nMaxGlyphWidth)
+ nNewWidth = nMaxGlyphWidth;
+ nNewWidth -= pGlyphIterRight->origWidth();
+ pGlyphIterRight->setLinearPosX( nNewWidth );
+
+ // justify glyph widths and positions
+ double nDiffWidth = nNewWidth - nOldWidth;
+ if( nDiffWidth >= 0) // expanded case
+ {
+ // expand width by distributing space between glyphs evenly
+ int nDeltaSum = 0;
+ for( pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter )
+ {
+ // move glyph to justified position
+ pGlyphIter->adjustLinearPosX(nDeltaSum);
+
+ // do not stretch non-stretchable glyphs
+ if( pGlyphIter->IsInCluster() || (nStretchable <= 0) )
+ continue;
+
+ // distribute extra space equally to stretchable glyphs
+ double nDeltaWidth = nDiffWidth / nStretchable--;
+ nDiffWidth -= nDeltaWidth;
+ pGlyphIter->addNewWidth(nDeltaWidth);
+ nDeltaSum += nDeltaWidth;
+ }
+ }
+ else // condensed case
+ {
+ // squeeze width by moving glyphs proportionally
+ double fSqueeze = nNewWidth / nOldWidth;
+ if(m_GlyphItems.size() > 1)
+ {
+ for( pGlyphIter = m_GlyphItems.begin(); ++pGlyphIter != pGlyphIterRight;)
+ {
+ double nX = pGlyphIter->linearPos().getX();
+ nX = nX * fSqueeze;
+ pGlyphIter->setLinearPosX( nX );
+ }
+ }
+ // adjust glyph widths to new positions
+ for( pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter )
+ pGlyphIter->setNewWidth( pGlyphIter[1].linearPos().getX() - pGlyphIter[0].linearPos().getX());
+ }
+}
+
+// returns asian kerning values in quarter of character width units
+// to enable automatic halfwidth substitution for fullwidth punctuation
+// return value is negative for l, positive for r, zero for neutral
+// TODO: handle vertical layout as proposed in commit 43bf2ad49c2b3989bbbe893e4fee2e032a3920f5?
+static int lcl_CalcAsianKerning(sal_UCS4 c, bool bLeft)
+{
+ // http://www.asahi-net.or.jp/~sd5a-ucd/freetexts/jis/x4051/1995/appendix.html
+ static const signed char nTable[0x30] =
+ {
+ 0, -2, -2, 0, 0, 0, 0, 0, +2, -2, +2, -2, +2, -2, +2, -2,
+ +2, -2, 0, 0, +2, -2, +2, -2, 0, 0, 0, 0, 0, +2, -2, -2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -2, +2, +2, -2, -2
+ };
+
+ int nResult = 0;
+ if( (c >= 0x3000) && (c < 0x3030) )
+ nResult = nTable[ c - 0x3000 ];
+ else switch( c )
+ {
+ case 0x30FB:
+ nResult = bLeft ? -1 : +1; // 25% left/right/top/bottom
+ break;
+ case 0x2019: case 0x201D:
+ case 0xFF01: case 0xFF09: case 0xFF0C:
+ case 0xFF1A: case 0xFF1B:
+ nResult = -2;
+ break;
+ case 0x2018: case 0x201C:
+ case 0xFF08:
+ nResult = +2;
+ break;
+ default:
+ break;
+ }
+
+ return nResult;
+}
+
+static bool lcl_CanApplyAsianKerning(sal_Unicode cp)
+{
+ return (0x3000 == (cp & 0xFF00)) || (0xFF00 == (cp & 0xFF00)) || (0x2010 == (cp & 0xFFF0));
+}
+
+void GenericSalLayout::ApplyAsianKerning(std::u16string_view rStr)
+{
+ const int nLength = rStr.size();
+ double nOffset = 0;
+
+ for (std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin(),
+ pGlyphIterEnd = m_GlyphItems.end();
+ pGlyphIter != pGlyphIterEnd; ++pGlyphIter)
+ {
+ const int n = pGlyphIter->charPos();
+ if (n < nLength - 1)
+ {
+ // ignore code ranges that are not affected by asian punctuation compression
+ const sal_Unicode cCurrent = rStr[n];
+ if (!lcl_CanApplyAsianKerning(cCurrent))
+ continue;
+ const sal_Unicode cNext = rStr[n + 1];
+ if (!lcl_CanApplyAsianKerning(cNext))
+ continue;
+
+ // calculate compression values
+ const int nKernCurrent = +lcl_CalcAsianKerning(cCurrent, true);
+ if (nKernCurrent == 0)
+ continue;
+ const int nKernNext = -lcl_CalcAsianKerning(cNext, false);
+ if (nKernNext == 0)
+ continue;
+
+ // apply punctuation compression to logical glyph widths
+ double nDelta = (nKernCurrent < nKernNext) ? nKernCurrent : nKernNext;
+ if (nDelta < 0)
+ {
+ nDelta = (nDelta * pGlyphIter->origWidth() + 2) / 4;
+ if( pGlyphIter+1 == pGlyphIterEnd )
+ pGlyphIter->addNewWidth( nDelta );
+ nOffset += nDelta;
+ }
+ }
+
+ // adjust the glyph positions to the new glyph widths
+ if( pGlyphIter+1 != pGlyphIterEnd )
+ pGlyphIter->adjustLinearPosX(nOffset);
+ }
+}
+
+void GenericSalLayout::GetCaretPositions(std::vector<double>& rCaretPositions,
+ const OUString& rStr) const
+{
+ const int nCaretPositions = (mnEndCharPos - mnMinCharPos) * 2;
+
+ rCaretPositions.clear();
+ rCaretPositions.resize(nCaretPositions, -1);
+
+ if (m_GlyphItems.empty())
+ return;
+
+ std::vector<double> aCharWidths;
+ GetCharWidths(aCharWidths, rStr);
+
+ // calculate caret positions using glyph array
+ for (auto const& aGlyphItem : m_GlyphItems)
+ {
+ auto nCurrX = aGlyphItem.linearPos().getX() - aGlyphItem.xOffset();
+ auto nCharStart = aGlyphItem.charPos();
+ auto nCharEnd = nCharStart + aGlyphItem.charCount() - 1;
+ if (!aGlyphItem.IsRTLGlyph())
+ {
+ // unchanged positions for LTR case
+ for (int i = nCharStart; i <= nCharEnd; i++)
+ {
+ int n = i - mnMinCharPos;
+ int nCurrIdx = 2 * n;
+
+ auto nLeft = nCurrX;
+ nCurrX += aCharWidths[n];
+ auto nRight = nCurrX;
+
+ rCaretPositions[nCurrIdx] = nLeft;
+ rCaretPositions[nCurrIdx + 1] = nRight;
+ }
+ }
+ else
+ {
+ // reverse positions for RTL case
+ for (int i = nCharEnd; i >= nCharStart; i--)
+ {
+ int n = i - mnMinCharPos;
+ int nCurrIdx = 2 * n;
+
+ auto nRight = nCurrX;
+ nCurrX += aCharWidths[n];
+ auto nLeft = nCurrX;
+
+ rCaretPositions[nCurrIdx] = nLeft;
+ rCaretPositions[nCurrIdx + 1] = nRight;
+ }
+ }
+ }
+}
+
+sal_Int32 GenericSalLayout::GetTextBreak(double nMaxWidth, double nCharExtra, int nFactor) const
+{
+ std::vector<double> aCharWidths;
+ GetCharWidths(aCharWidths, {});
+
+ double nWidth = 0;
+ for( int i = mnMinCharPos; i < mnEndCharPos; ++i )
+ {
+ double nDelta = aCharWidths[ i - mnMinCharPos ] * nFactor;
+
+ if (nDelta != 0)
+ {
+ nWidth += nDelta;
+ if( nWidth > nMaxWidth )
+ return i;
+
+ nWidth += nCharExtra;
+ }
+ }
+
+ return -1;
+}
+
+bool GenericSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
+ basegfx::B2DPoint& rPos, int& nStart,
+ const LogicalFontInstance** ppGlyphFont) const
+{
+ std::vector<GlyphItem>::const_iterator pGlyphIter = m_GlyphItems.begin();
+ std::vector<GlyphItem>::const_iterator pGlyphIterEnd = m_GlyphItems.end();
+ pGlyphIter += nStart;
+
+ // find next glyph in substring
+ for(; pGlyphIter != pGlyphIterEnd; ++nStart, ++pGlyphIter )
+ {
+ int n = pGlyphIter->charPos();
+ if( (mnMinCharPos <= n) && (n < mnEndCharPos) )
+ break;
+ }
+
+ // return zero if no more glyph found
+ if( nStart >= static_cast<int>(m_GlyphItems.size()) )
+ return false;
+
+ if( pGlyphIter == pGlyphIterEnd )
+ return false;
+
+ // update return data with glyph info
+ *pGlyph = &(*pGlyphIter);
+ ++nStart;
+ if (ppGlyphFont)
+ *ppGlyphFont = m_GlyphItems.GetFont().get();
+
+ // calculate absolute position in pixel units
+ basegfx::B2DPoint aRelativePos = pGlyphIter->linearPos();
+
+ rPos = GetDrawPosition( aRelativePos );
+
+ return true;
+}
+
+void GenericSalLayout::MoveGlyph(int nStart, double nNewXPos)
+{
+ if( nStart >= static_cast<int>(m_GlyphItems.size()) )
+ return;
+
+ std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin();
+ pGlyphIter += nStart;
+
+ // the nNewXPos argument determines the new cell position
+ // as RTL-glyphs are right justified in their cell
+ // the cell position needs to be adjusted to the glyph position
+ if( pGlyphIter->IsRTLGlyph() )
+ nNewXPos += pGlyphIter->newWidth() - pGlyphIter->origWidth();
+ // calculate the x-offset to the old position
+ double nXDelta = nNewXPos - pGlyphIter->linearPos().getX() + pGlyphIter->xOffset();
+ // adjust all following glyph positions if needed
+ if( nXDelta != 0 )
+ {
+ for( std::vector<GlyphItem>::iterator pGlyphIterEnd = m_GlyphItems.end(); pGlyphIter != pGlyphIterEnd; ++pGlyphIter )
+ {
+ pGlyphIter->adjustLinearPosX(nXDelta);
+ }
+ }
+}
+
+void GenericSalLayout::DropGlyph( int nStart )
+{
+ if( nStart >= static_cast<int>(m_GlyphItems.size()))
+ return;
+
+ std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin();
+ pGlyphIter += nStart;
+ pGlyphIter->dropGlyph();
+}
+
+void GenericSalLayout::Simplify( bool bIsBase )
+{
+ // remove dropped glyphs inplace
+ size_t j = 0;
+ for(size_t i = 0; i < m_GlyphItems.size(); i++ )
+ {
+ if (bIsBase && m_GlyphItems[i].IsDropped())
+ continue;
+ if (!bIsBase && m_GlyphItems[i].glyphId() == 0)
+ continue;
+
+ if( i != j )
+ {
+ m_GlyphItems[j] = m_GlyphItems[i];
+ }
+ j += 1;
+ }
+ m_GlyphItems.erase(m_GlyphItems.begin() + j, m_GlyphItems.end());
+}
+
+MultiSalLayout::MultiSalLayout( std::unique_ptr<SalLayout> pBaseLayout )
+: mnLevel( 1 )
+, mbIncomplete( false )
+{
+ assert(dynamic_cast<GenericSalLayout*>(pBaseLayout.get()));
+
+ mpLayouts[ 0 ].reset(static_cast<GenericSalLayout*>(pBaseLayout.release()));
+}
+
+std::unique_ptr<SalLayout> MultiSalLayout::ReleaseBaseLayout()
+{
+ return std::move(mpLayouts[0]);
+}
+
+void MultiSalLayout::SetIncomplete(bool bIncomplete)
+{
+ mbIncomplete = bIncomplete;
+ maFallbackRuns[mnLevel-1] = ImplLayoutRuns();
+}
+
+MultiSalLayout::~MultiSalLayout()
+{
+}
+
+void MultiSalLayout::AddFallback( std::unique_ptr<SalLayout> pFallback,
+ ImplLayoutRuns const & rFallbackRuns)
+{
+ assert(dynamic_cast<GenericSalLayout*>(pFallback.get()));
+ if( mnLevel >= MAX_FALLBACK )
+ return;
+
+ mpLayouts[ mnLevel ].reset(static_cast<GenericSalLayout*>(pFallback.release()));
+ maFallbackRuns[ mnLevel-1 ] = rFallbackRuns;
+ ++mnLevel;
+}
+
+bool MultiSalLayout::LayoutText( vcl::text::ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* )
+{
+ if( mnLevel <= 1 )
+ return false;
+ if (!mbIncomplete)
+ maFallbackRuns[ mnLevel-1 ] = rArgs.maRuns;
+ return true;
+}
+
+void MultiSalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs )
+{
+ SalLayout::AdjustLayout( rArgs );
+ vcl::text::ImplLayoutArgs aMultiArgs = rArgs;
+ std::vector<double> aJustificationArray;
+
+ if( !rArgs.HasDXArray() && rArgs.mnLayoutWidth )
+ {
+ // for stretched text in a MultiSalLayout the target width needs to be
+ // distributed by individually adjusting its virtual character widths
+ double nTargetWidth = aMultiArgs.mnLayoutWidth;
+ aMultiArgs.mnLayoutWidth = 0;
+
+ // we need to get the original unmodified layouts ready
+ for( int n = 0; n < mnLevel; ++n )
+ mpLayouts[n]->SalLayout::AdjustLayout( aMultiArgs );
+ // then we can measure the unmodified metrics
+ int nCharCount = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
+ FillDXArray( &aJustificationArray, {} );
+ // #i17359# multilayout is not simplified yet, so calculating the
+ // unjustified width needs handholding; also count the number of
+ // stretchable virtual char widths
+ double nOrigWidth = 0;
+ int nStretchable = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ // convert array from widths to sum of widths
+ nOrigWidth += aJustificationArray[i];
+ if( aJustificationArray[i] > 0 )
+ ++nStretchable;
+ }
+
+ // now we are able to distribute the extra width over the virtual char widths
+ if( nOrigWidth && (nTargetWidth != nOrigWidth) )
+ {
+ double nDiffWidth = nTargetWidth - nOrigWidth;
+ double nWidthSum = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ double nJustWidth = aJustificationArray[i];
+ if( (nJustWidth > 0) && (nStretchable > 0) )
+ {
+ double nDeltaWidth = nDiffWidth / nStretchable;
+ nJustWidth += nDeltaWidth;
+ nDiffWidth -= nDeltaWidth;
+ --nStretchable;
+ }
+ nWidthSum += nJustWidth;
+ aJustificationArray[i] = nWidthSum;
+ }
+ if( nWidthSum != nTargetWidth )
+ aJustificationArray[ nCharCount-1 ] = nTargetWidth;
+
+ // change the DXArray temporarily (just for the justification)
+ aMultiArgs.mpDXArray = aJustificationArray.data();
+ }
+ }
+
+ ImplAdjustMultiLayout(rArgs, aMultiArgs, aMultiArgs.mpDXArray);
+}
+
+void MultiSalLayout::ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs,
+ vcl::text::ImplLayoutArgs& rMultiArgs,
+ const double* pMultiDXArray)
+{
+ // Compute rtl flags, since in some scripts glyphs/char order can be
+ // reversed for a few character sequences e.g. Myanmar
+ std::vector<bool> vRtl(rArgs.mnEndCharPos - rArgs.mnMinCharPos, false);
+ rArgs.ResetPos();
+ bool bRtl;
+ int nRunStart, nRunEnd;
+ while (rArgs.GetNextRun(&nRunStart, &nRunEnd, &bRtl))
+ {
+ if (bRtl) std::fill(vRtl.begin() + (nRunStart - rArgs.mnMinCharPos),
+ vRtl.begin() + (nRunEnd - rArgs.mnMinCharPos), true);
+ }
+ rArgs.ResetPos();
+
+ // prepare "merge sort"
+ int nStartOld[ MAX_FALLBACK ];
+ int nStartNew[ MAX_FALLBACK ];
+ const GlyphItem* pGlyphs[MAX_FALLBACK];
+ bool bValid[MAX_FALLBACK] = { false };
+
+ basegfx::B2DPoint aPos;
+ int nLevel = 0, n;
+ for( n = 0; n < mnLevel; ++n )
+ {
+ // now adjust the individual components
+ if( n > 0 )
+ {
+ rMultiArgs.maRuns = maFallbackRuns[ n-1 ];
+ rMultiArgs.mnFlags |= SalLayoutFlags::ForFallback;
+ }
+ mpLayouts[n]->AdjustLayout( rMultiArgs );
+
+ // remove unused parts of component
+ if( n > 0 )
+ {
+ if (mbIncomplete && (n == mnLevel-1))
+ mpLayouts[n]->Simplify( true );
+ else
+ mpLayouts[n]->Simplify( false );
+ }
+
+ // prepare merging components
+ nStartNew[ nLevel ] = nStartOld[ nLevel ] = 0;
+ bValid[nLevel] = mpLayouts[n]->GetNextGlyph(&pGlyphs[nLevel], aPos, nStartNew[nLevel]);
+
+ if( (n > 0) && !bValid[ nLevel ] )
+ {
+ // an empty fallback layout can be released
+ mpLayouts[n].reset();
+ }
+ else
+ {
+ // reshuffle used fallbacks if needed
+ if( nLevel != n )
+ {
+ mpLayouts[ nLevel ] = std::move(mpLayouts[ n ]);
+ maFallbackRuns[ nLevel ] = maFallbackRuns[ n ];
+ }
+ ++nLevel;
+ }
+ }
+ mnLevel = nLevel;
+
+ // prepare merge the fallback levels
+ double nXPos = 0;
+ for( n = 0; n < nLevel; ++n )
+ maFallbackRuns[n].ResetPos();
+
+ int nFirstValid = -1;
+ for( n = 0; n < nLevel; ++n )
+ {
+ if(bValid[n])
+ {
+ nFirstValid = n;
+ break;
+ }
+ }
+ assert(nFirstValid >= 0);
+
+ // get the next codepoint index that needs fallback
+ int nActiveCharPos = pGlyphs[nFirstValid]->charPos();
+ int nActiveCharIndex = nActiveCharPos - mnMinCharPos;
+ // get the end index of the active run
+ int nLastRunEndChar = (nActiveCharIndex >= 0 && vRtl[nActiveCharIndex]) ?
+ rArgs.mnEndCharPos : rArgs.mnMinCharPos - 1;
+ int nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos();
+ // merge the fallback levels
+ while( bValid[nFirstValid] && (nLevel > 0))
+ {
+ // find best fallback level
+ for( n = 0; n < nLevel; ++n )
+ if( bValid[n] && !maFallbackRuns[n].PosIsInAnyRun( nActiveCharPos ) )
+ // fallback level n wins when it requested no further fallback
+ break;
+ int nFBLevel = n;
+
+ if( n < nLevel )
+ {
+ // use base(n==0) or fallback(n>=1) level
+ mpLayouts[n]->MoveGlyph( nStartOld[n], nXPos );
+ }
+ else
+ {
+ n = 0; // keep NotDef in base level
+ }
+
+ if( n > 0 )
+ {
+ // drop the NotDef glyphs in the base layout run if a fallback run exists
+ while (
+ (maFallbackRuns[n-1].PosIsInAnyRun(pGlyphs[nFirstValid]->charPos())) &&
+ (!maFallbackRuns[n].PosIsInAnyRun(pGlyphs[nFirstValid]->charPos()))
+ )
+ {
+ mpLayouts[0]->DropGlyph( nStartOld[0] );
+ nStartOld[0] = nStartNew[0];
+ bValid[nFirstValid] = mpLayouts[0]->GetNextGlyph(&pGlyphs[nFirstValid], aPos, nStartNew[0]);
+
+ if( !bValid[nFirstValid] )
+ break;
+ }
+ }
+
+ // skip to end of layout run and calculate its advance width
+ double nRunAdvance = 0;
+ bool bKeepNotDef = (nFBLevel >= nLevel);
+ for(;;)
+ {
+ nRunAdvance += pGlyphs[n]->newWidth();
+
+ // proceed to next glyph
+ nStartOld[n] = nStartNew[n];
+ int nOrigCharPos = pGlyphs[n]->charPos();
+ bValid[n] = mpLayouts[n]->GetNextGlyph(&pGlyphs[n], aPos, nStartNew[n]);
+ // break after last glyph of active layout
+ if( !bValid[n] )
+ {
+ // performance optimization (when a fallback layout is no longer needed)
+ if( n >= nLevel-1 )
+ --nLevel;
+ break;
+ }
+
+ //If the next character is one which belongs to the next level, then we
+ //are finished here for now, and we'll pick up after the next level has
+ //been processed
+ if ((n+1 < nLevel) && (pGlyphs[n]->charPos() != nOrigCharPos))
+ {
+ if (nOrigCharPos < pGlyphs[n]->charPos())
+ {
+ if (pGlyphs[n+1]->charPos() > nOrigCharPos && (pGlyphs[n+1]->charPos() < pGlyphs[n]->charPos()))
+ break;
+ }
+ else if (nOrigCharPos > pGlyphs[n]->charPos())
+ {
+ if (pGlyphs[n+1]->charPos() > pGlyphs[n]->charPos() && (pGlyphs[n+1]->charPos() < nOrigCharPos))
+ break;
+ }
+ }
+
+ // break at end of layout run
+ if( n > 0 )
+ {
+ // skip until end of fallback run
+ if (!maFallbackRuns[n-1].PosIsInRun(pGlyphs[n]->charPos()))
+ break;
+ }
+ else
+ {
+ // break when a fallback is needed and available
+ bool bNeedFallback = maFallbackRuns[0].PosIsInRun(pGlyphs[nFirstValid]->charPos());
+ if( bNeedFallback )
+ if (!maFallbackRuns[nLevel-1].PosIsInRun(pGlyphs[nFirstValid]->charPos()))
+ break;
+ // break when change from resolved to unresolved base layout run
+ if( bKeepNotDef && !bNeedFallback )
+ { maFallbackRuns[0].NextRun(); break; }
+ bKeepNotDef = bNeedFallback;
+ }
+ // check for reordered glyphs
+ if (pMultiDXArray &&
+ nRunVisibleEndChar < mnEndCharPos &&
+ nRunVisibleEndChar >= mnMinCharPos &&
+ pGlyphs[n]->charPos() < mnEndCharPos &&
+ pGlyphs[n]->charPos() >= mnMinCharPos)
+ {
+ if (vRtl[nActiveCharPos - mnMinCharPos])
+ {
+ if (pMultiDXArray[nRunVisibleEndChar-mnMinCharPos]
+ >= pMultiDXArray[pGlyphs[n]->charPos() - mnMinCharPos])
+ {
+ nRunVisibleEndChar = pGlyphs[n]->charPos();
+ }
+ }
+ else if (pMultiDXArray[nRunVisibleEndChar-mnMinCharPos]
+ <= pMultiDXArray[pGlyphs[n]->charPos() - mnMinCharPos])
+ {
+ nRunVisibleEndChar = pGlyphs[n]->charPos();
+ }
+ }
+ }
+
+ // if a justification array is available
+ // => use it directly to calculate the corresponding run width
+ if (pMultiDXArray)
+ {
+ // the run advance is the width from the first char
+ // in the run to the first char in the next run
+ nRunAdvance = 0;
+ nActiveCharIndex = nActiveCharPos - mnMinCharPos;
+ if (nActiveCharIndex >= 0 && vRtl[nActiveCharIndex])
+ {
+ if (nRunVisibleEndChar > mnMinCharPos && nRunVisibleEndChar <= mnEndCharPos)
+ nRunAdvance -= pMultiDXArray[nRunVisibleEndChar - 1 - mnMinCharPos];
+ if (nLastRunEndChar > mnMinCharPos && nLastRunEndChar <= mnEndCharPos)
+ nRunAdvance += pMultiDXArray[nLastRunEndChar - 1 - mnMinCharPos];
+ }
+ else
+ {
+ if (nRunVisibleEndChar >= mnMinCharPos)
+ nRunAdvance += pMultiDXArray[nRunVisibleEndChar - mnMinCharPos];
+ if (nLastRunEndChar >= mnMinCharPos)
+ nRunAdvance -= pMultiDXArray[nLastRunEndChar - mnMinCharPos];
+ }
+ nLastRunEndChar = nRunVisibleEndChar;
+ nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos();
+ }
+
+ // calculate new x position
+ nXPos += nRunAdvance;
+
+ // prepare for next fallback run
+ nActiveCharPos = pGlyphs[nFirstValid]->charPos();
+ // it essential that the runs don't get ahead of themselves and in the
+ // if( bKeepNotDef && !bNeedFallback ) statement above, the next run may
+ // have already been reached on the base level
+ for( int i = nFBLevel; --i >= 0;)
+ {
+ if (maFallbackRuns[i].GetRun(&nRunStart, &nRunEnd, &bRtl))
+ {
+ if (bRtl)
+ {
+ if (nRunStart > nActiveCharPos)
+ maFallbackRuns[i].NextRun();
+ }
+ else
+ {
+ if (nRunEnd <= nActiveCharPos)
+ maFallbackRuns[i].NextRun();
+ }
+ }
+ }
+ }
+
+ mpLayouts[0]->Simplify( true );
+}
+
+void MultiSalLayout::InitFont() const
+{
+ if( mnLevel > 0 )
+ mpLayouts[0]->InitFont();
+}
+
+void MultiSalLayout::DrawText( SalGraphics& rGraphics ) const
+{
+ for( int i = mnLevel; --i >= 0; )
+ {
+ SalLayout& rLayout = *mpLayouts[ i ];
+ rLayout.DrawBase() += maDrawBase;
+ rLayout.DrawOffset() += maDrawOffset;
+ rLayout.InitFont();
+ rLayout.DrawText( rGraphics );
+ rLayout.DrawOffset() -= maDrawOffset;
+ rLayout.DrawBase() -= maDrawBase;
+ }
+ // NOTE: now the baselevel font is active again
+}
+
+sal_Int32 MultiSalLayout::GetTextBreak(double nMaxWidth, double nCharExtra, int nFactor) const
+{
+ if( mnLevel <= 0 )
+ return -1;
+ if( mnLevel == 1 )
+ return mpLayouts[0]->GetTextBreak( nMaxWidth, nCharExtra, nFactor );
+
+ int nCharCount = mnEndCharPos - mnMinCharPos;
+ std::vector<double> aCharWidths;
+ std::vector<double> aFallbackCharWidths;
+ mpLayouts[0]->FillDXArray( &aCharWidths, {} );
+
+ for( int n = 1; n < mnLevel; ++n )
+ {
+ SalLayout& rLayout = *mpLayouts[ n ];
+ rLayout.FillDXArray( &aFallbackCharWidths, {} );
+ for( int i = 0; i < nCharCount; ++i )
+ if( aCharWidths[ i ] == 0 )
+ aCharWidths[i] = aFallbackCharWidths[i];
+ }
+
+ double nWidth = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ nWidth += aCharWidths[ i ] * nFactor;
+ if( nWidth > nMaxWidth )
+ return (i + mnMinCharPos);
+ nWidth += nCharExtra;
+ }
+
+ return -1;
+}
+
+double MultiSalLayout::GetTextWidth() const
+{
+ // Measure text width. There might be holes in each SalLayout due to
+ // missing chars, so we use GetNextGlyph() to get the glyphs across all
+ // layouts.
+ int nStart = 0;
+ basegfx::B2DPoint aPos;
+ const GlyphItem* pGlyphItem;
+
+ double nWidth = 0;
+ while (GetNextGlyph(&pGlyphItem, aPos, nStart))
+ nWidth += pGlyphItem->newWidth();
+
+ return nWidth;
+}
+
+double MultiSalLayout::FillDXArray( std::vector<double>* pCharWidths, const OUString& rStr ) const
+{
+ if (pCharWidths)
+ {
+ // prepare merging of fallback levels
+ std::vector<double> aTempWidths;
+ const int nCharCount = mnEndCharPos - mnMinCharPos;
+ pCharWidths->clear();
+ pCharWidths->resize(nCharCount, 0);
+
+ for (int n = mnLevel; --n >= 0;)
+ {
+ // query every fallback level
+ mpLayouts[n]->FillDXArray(&aTempWidths, rStr);
+
+ // calculate virtual char widths using most probable fallback layout
+ for (int i = 0; i < nCharCount; ++i)
+ {
+ // #i17359# restriction:
+ // one char cannot be resolved from different fallbacks
+ if ((*pCharWidths)[i] != 0)
+ continue;
+ double nCharWidth = aTempWidths[i];
+ if (!nCharWidth)
+ continue;
+ (*pCharWidths)[i] = nCharWidth;
+ }
+ }
+ }
+
+ return GetTextWidth();
+}
+
+void MultiSalLayout::GetCaretPositions(std::vector<double>& rCaretPositions,
+ const OUString& rStr) const
+{
+ // prepare merging of fallback levels
+ std::vector<double> aTempPos;
+ const int nCaretPositions = (mnEndCharPos - mnMinCharPos) * 2;
+ rCaretPositions.clear();
+ rCaretPositions.resize(nCaretPositions, -1);
+
+ for (int n = mnLevel; --n >= 0;)
+ {
+ // query every fallback level
+ mpLayouts[n]->GetCaretPositions(aTempPos, rStr);
+
+ // calculate virtual char widths using most probable fallback layout
+ for (int i = 0; i < nCaretPositions; ++i)
+ {
+ // one char cannot be resolved from different fallbacks
+ if (rCaretPositions[i] != -1)
+ continue;
+ if (aTempPos[i] >= 0)
+ rCaretPositions[i] = aTempPos[i];
+ }
+ }
+}
+
+bool MultiSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
+ basegfx::B2DPoint& rPos, int& nStart,
+ const LogicalFontInstance** ppGlyphFont) const
+{
+ // NOTE: nStart is tagged with current font index
+ int nLevel = static_cast<unsigned>(nStart) >> GF_FONTSHIFT;
+ nStart &= ~GF_FONTMASK;
+ for(; nLevel < mnLevel; ++nLevel, nStart=0 )
+ {
+ GenericSalLayout& rLayout = *mpLayouts[ nLevel ];
+ rLayout.InitFont();
+ if (rLayout.GetNextGlyph(pGlyph, rPos, nStart, ppGlyphFont))
+ {
+ int nFontTag = nLevel << GF_FONTSHIFT;
+ nStart |= nFontTag;
+ rPos.adjustX(maDrawBase.getX() + maDrawOffset.X());
+ rPos.adjustY(maDrawBase.getY() + maDrawOffset.Y());
+ return true;
+ }
+ }
+
+ // #111016# reset to base level font when done
+ mpLayouts[0]->InitFont();
+ return false;
+}
+
+bool MultiSalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rPPV) const
+{
+ bool bRet = false;
+
+ for( int i = mnLevel; --i >= 0; )
+ {
+ SalLayout& rLayout = *mpLayouts[ i ];
+ rLayout.DrawBase() = maDrawBase;
+ rLayout.DrawOffset() += maDrawOffset;
+ rLayout.InitFont();
+ bRet |= rLayout.GetOutline(rPPV);
+ rLayout.DrawOffset() -= maDrawOffset;
+ }
+
+ return bRet;
+}
+
+bool MultiSalLayout::IsKashidaPosValid(int nCharPos, int nNextCharPos) const
+{
+ // Check the base layout
+ bool bValid = mpLayouts[0]->IsKashidaPosValid(nCharPos, nNextCharPos);
+
+ // If base layout returned false, it might be because the character was not
+ // supported there, so we check fallback layouts.
+ if (!bValid)
+ {
+ for (int i = 1; i < mnLevel; ++i)
+ {
+ // - 1 because there is no fallback run for the base layout, IIUC.
+ if (maFallbackRuns[i - 1].PosIsInAnyRun(nCharPos) &&
+ maFallbackRuns[i - 1].PosIsInAnyRun(nNextCharPos))
+ {
+ bValid = mpLayouts[i]->IsKashidaPosValid(nCharPos, nNextCharPos);
+ break;
+ }
+ }
+ }
+
+ return bValid;
+}
+
+SalLayoutGlyphs MultiSalLayout::GetGlyphs() const
+{
+ SalLayoutGlyphs glyphs;
+ for( int n = 0; n < mnLevel; ++n )
+ glyphs.AppendImpl(mpLayouts[n]->GlyphsImpl().clone());
+ return glyphs;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/salmisc.cxx b/vcl/source/gdi/salmisc.cxx
new file mode 100644
index 0000000000..e8c09ab1d8
--- /dev/null
+++ b/vcl/source/gdi/salmisc.cxx
@@ -0,0 +1,433 @@
+/* -*- 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 <vcl/BitmapReadAccess.hxx>
+#include <vcl/salgtype.hxx>
+#include <bitmap/bmpfast.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <tools/helpers.hxx>
+#include <memory>
+
+#define IMPL_CASE_SET_FORMAT( Format, BitCount ) \
+case( ScanlineFormat::Format ): \
+{ \
+ pFncSetPixel = BitmapReadAccess::SetPixelFor##Format; \
+ pDstBuffer->mnBitCount = BitCount; \
+} \
+break
+
+#define DOUBLE_SCANLINES() \
+while( ( nActY < nHeight1 ) && ( pMapY[ nActY + 1 ] == nMapY ) ) \
+{ \
+ memcpy( pDstScanMap[ nActY + 1 ], pDstScan, rDstBuffer.mnScanlineSize ); \
+ nActY++; \
+}
+
+constexpr int TC_TO_PAL_COLORS = 4096;
+
+static tools::Long ImplIndexFromColor( const BitmapColor& rCol )
+{
+ return ( ( static_cast<tools::Long>(rCol.GetBlue()) >> 4) << 8 ) |
+ ( ( static_cast<tools::Long>(rCol.GetGreen()) >> 4 ) << 4 ) |
+ ( static_cast<tools::Long>(rCol.GetRed()) >> 4 );
+}
+
+static void ImplPALToPAL( const BitmapBuffer& rSrcBuffer, BitmapBuffer& rDstBuffer,
+ FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel,
+ Scanline* pSrcScanMap, Scanline* pDstScanMap, sal_Int32 const * pMapX, const sal_Int32* pMapY )
+{
+ const tools::Long nHeight1 = rDstBuffer.mnHeight - 1;
+ const ColorMask& rSrcMask = rSrcBuffer.maColorMask;
+ const ColorMask& rDstMask = rDstBuffer.maColorMask;
+ BitmapPalette aColMap( rSrcBuffer.maPalette.GetEntryCount() );
+ BitmapColor* pColMapBuf = aColMap.ImplGetColorBuffer();
+ BitmapColor aIndex( 0 );
+
+ for( sal_uInt16 i = 0, nSrcCount = aColMap.GetEntryCount(), nDstCount = rDstBuffer.maPalette.GetEntryCount(); i < nSrcCount; i++ )
+ {
+ if( ( i < nDstCount ) && ( rSrcBuffer.maPalette[ i ] == rDstBuffer.maPalette[ i ] ) )
+ aIndex.SetIndex( sal::static_int_cast<sal_uInt8>(i) );
+ else
+ aIndex.SetIndex( sal::static_int_cast<sal_uInt8>(rDstBuffer.maPalette.GetBestIndex( rSrcBuffer.maPalette[ i ] )) );
+
+ pColMapBuf[ i ] = aIndex;
+ }
+
+ for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ tools::Long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ pFncSetPixel( pDstScan, nX, pColMapBuf[ pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ).GetIndex() ], rDstMask );
+
+ DOUBLE_SCANLINES();
+ }
+}
+
+static void ImplPALToTC( const BitmapBuffer& rSrcBuffer, BitmapBuffer const & rDstBuffer,
+ FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel,
+ Scanline* pSrcScanMap, Scanline* pDstScanMap, sal_Int32 const * pMapX, const sal_Int32* pMapY )
+{
+ const tools::Long nHeight1 = rDstBuffer.mnHeight - 1;
+ const ColorMask& rSrcMask = rSrcBuffer.maColorMask;
+ const ColorMask& rDstMask = rDstBuffer.maColorMask;
+ const BitmapColor* pColBuf = rSrcBuffer.maPalette.ImplGetColorBuffer();
+
+ if( RemoveScanline( rSrcBuffer.mnFormat ) == ScanlineFormat::N1BitMsbPal )
+ {
+ const BitmapColor aCol0( pColBuf[ 0 ] );
+ const BitmapColor aCol1( pColBuf[ 1 ] );
+ tools::Long nMapX;
+
+ for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ tools::Long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (tools::Long nX = 0; nX < rDstBuffer.mnWidth;)
+ {
+ nMapX = pMapX[ nX ];
+ pFncSetPixel( pDstScan, nX++,
+ pSrcScan[ nMapX >> 3 ] & ( 1 << ( 7 - ( nMapX & 7 ) ) ) ? aCol1 : aCol0,
+ rDstMask );
+ }
+
+ DOUBLE_SCANLINES();
+ }
+ }
+ else if( RemoveScanline( rSrcBuffer.mnFormat ) == ScanlineFormat::N8BitPal )
+ {
+ for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ tools::Long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ pFncSetPixel( pDstScan, nX, pColBuf[ pSrcScan[ pMapX[ nX ] ] ], rDstMask );
+
+ DOUBLE_SCANLINES();
+ }
+ }
+ else
+ {
+ for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ tools::Long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ pFncSetPixel( pDstScan, nX, pColBuf[ pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ).GetIndex() ], rDstMask );
+
+ DOUBLE_SCANLINES();
+ }
+ }
+}
+
+static void ImplTCToTC( const BitmapBuffer& rSrcBuffer, BitmapBuffer const & rDstBuffer,
+ FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel,
+ Scanline* pSrcScanMap, Scanline* pDstScanMap, sal_Int32 const * pMapX, const sal_Int32* pMapY )
+{
+ const tools::Long nHeight1 = rDstBuffer.mnHeight - 1;
+ const ColorMask& rSrcMask = rSrcBuffer.maColorMask;
+ const ColorMask& rDstMask = rDstBuffer.maColorMask;
+
+ if( RemoveScanline( rSrcBuffer.mnFormat ) == ScanlineFormat::N24BitTcBgr )
+ {
+ BitmapColor aCol;
+ sal_uInt8* pPixel = nullptr;
+
+ for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ tools::Long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ {
+ pPixel = pSrcScan + pMapX[ nX ] * 3;
+ aCol.SetBlue( *pPixel++ );
+ aCol.SetGreen( *pPixel++ );
+ aCol.SetRed( *pPixel );
+ pFncSetPixel( pDstScan, nX, aCol, rDstMask );
+ }
+
+ DOUBLE_SCANLINES()
+ }
+ }
+ else
+ {
+ for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ tools::Long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ pFncSetPixel( pDstScan, nX, pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ), rDstMask );
+
+ DOUBLE_SCANLINES();
+ }
+ }
+}
+
+static void ImplTCToPAL( const BitmapBuffer& rSrcBuffer, BitmapBuffer const & rDstBuffer,
+ FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel,
+ Scanline* pSrcScanMap, Scanline* pDstScanMap, sal_Int32 const * pMapX, const sal_Int32* pMapY )
+{
+ const tools::Long nHeight1 = rDstBuffer.mnHeight- 1;
+ const ColorMask& rSrcMask = rSrcBuffer.maColorMask;
+ const ColorMask& rDstMask = rDstBuffer.maColorMask;
+ std::unique_ptr<sal_uInt8[]> pColToPalMap(new sal_uInt8[ TC_TO_PAL_COLORS ]);
+ BitmapColor aIndex( 0 );
+
+ for( tools::Long nR = 0; nR < 16; nR++ )
+ {
+ for( tools::Long nG = 0; nG < 16; nG++ )
+ {
+ for( tools::Long nB = 0; nB < 16; nB++ )
+ {
+ BitmapColor aCol( sal::static_int_cast<sal_uInt8>(nR << 4),
+ sal::static_int_cast<sal_uInt8>(nG << 4),
+ sal::static_int_cast<sal_uInt8>(nB << 4) );
+ pColToPalMap[ ImplIndexFromColor( aCol ) ] = static_cast<sal_uInt8>(rDstBuffer.maPalette.GetBestIndex( aCol ));
+ }
+ }
+ }
+
+ for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ tools::Long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ {
+ aIndex.SetIndex( pColToPalMap[ ImplIndexFromColor( pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ) ) ] );
+ pFncSetPixel( pDstScan, nX, aIndex, rDstMask );
+ }
+
+ DOUBLE_SCANLINES();
+ }
+}
+
+std::optional<BitmapBuffer> StretchAndConvert(
+ const BitmapBuffer& rSrcBuffer, const SalTwoRect& rTwoRect,
+ ScanlineFormat nDstBitmapFormat, std::optional<BitmapPalette> pDstPal, const ColorMask* pDstMask )
+{
+ FncGetPixel pFncGetPixel;
+ FncSetPixel pFncSetPixel;
+ std::optional<BitmapBuffer> pDstBuffer(std::in_place);
+
+ // set function for getting pixels
+ pFncGetPixel = BitmapReadAccess::GetPixelFunction( rSrcBuffer.mnFormat );
+ if( !pFncGetPixel )
+ {
+ // should never come here
+ // initialize pFncGetPixel to something valid that is
+ // least likely to crash
+ pFncGetPixel = BitmapReadAccess::GetPixelForN1BitMsbPal;
+ OSL_FAIL( "unknown read format" );
+ }
+
+ // set function for setting pixels
+ const ScanlineFormat nDstScanlineFormat = RemoveScanline( nDstBitmapFormat );
+ switch( nDstScanlineFormat )
+ {
+ IMPL_CASE_SET_FORMAT( N1BitMsbPal, 1 );
+ IMPL_CASE_SET_FORMAT( N8BitPal, 8 );
+ IMPL_CASE_SET_FORMAT( N24BitTcBgr, 24 );
+ IMPL_CASE_SET_FORMAT( N24BitTcRgb, 24 );
+ IMPL_CASE_SET_FORMAT( N32BitTcAbgr, 32 );
+ IMPL_CASE_SET_FORMAT( N32BitTcArgb, 32 );
+ IMPL_CASE_SET_FORMAT( N32BitTcBgra, 32 );
+ IMPL_CASE_SET_FORMAT( N32BitTcRgba, 32 );
+ IMPL_CASE_SET_FORMAT( N32BitTcMask, 32 );
+
+ default:
+ // should never come here
+ // initialize pFncSetPixel to something valid that is
+ // least likely to crash
+ pFncSetPixel = BitmapReadAccess::SetPixelForN1BitMsbPal;
+ pDstBuffer->mnBitCount = 1;
+ OSL_FAIL( "unknown write format" );
+ break;
+ }
+
+ // fill destination buffer
+ pDstBuffer->mnFormat = nDstBitmapFormat;
+ pDstBuffer->mnWidth = rTwoRect.mnDestWidth;
+ pDstBuffer->mnHeight = rTwoRect.mnDestHeight;
+ tools::Long nScanlineBase;
+ bool bFail = o3tl::checked_multiply<tools::Long>(pDstBuffer->mnBitCount, pDstBuffer->mnWidth, nScanlineBase);
+ if (bFail)
+ {
+ SAL_WARN("vcl.gdi", "checked multiply failed");
+ pDstBuffer->mpBits = nullptr;
+ return std::nullopt;
+ }
+ pDstBuffer->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
+ if (pDstBuffer->mnScanlineSize < nScanlineBase/8)
+ {
+ SAL_WARN("vcl.gdi", "scanline calculation wraparound");
+ pDstBuffer->mpBits = nullptr;
+ return std::nullopt;
+ }
+ try
+ {
+ pDstBuffer->mpBits = new sal_uInt8[ pDstBuffer->mnScanlineSize * pDstBuffer->mnHeight ];
+ }
+ catch( const std::bad_alloc& )
+ {
+ // memory exception, clean up
+ pDstBuffer->mpBits = nullptr;
+ return std::nullopt;
+ }
+
+ // do we need a destination palette or color mask?
+ if( ( nDstScanlineFormat == ScanlineFormat::N1BitMsbPal ) ||
+ ( nDstScanlineFormat == ScanlineFormat::N8BitPal ) )
+ {
+ assert(pDstPal && "destination buffer requires palette");
+ if (!pDstPal)
+ {
+ return std::nullopt;
+ }
+ pDstBuffer->maPalette = *pDstPal;
+ }
+ else if(nDstScanlineFormat == ScanlineFormat::N32BitTcMask )
+ {
+ assert(pDstMask && "destination buffer requires color mask");
+ if (!pDstMask)
+ {
+ return std::nullopt;
+ }
+ pDstBuffer->maColorMask = *pDstMask;
+ }
+
+ // short circuit the most important conversions
+ bool bFastConvert = ImplFastBitmapConversion( *pDstBuffer, rSrcBuffer, rTwoRect );
+ if( bFastConvert )
+ return pDstBuffer;
+
+ std::unique_ptr<Scanline[]> pSrcScan;
+ std::unique_ptr<Scanline[]> pDstScan;
+ std::unique_ptr<sal_Int32[]> pMapX;
+ std::unique_ptr<sal_Int32[]> pMapY;
+
+ try
+ {
+ pSrcScan.reset(new Scanline[rSrcBuffer.mnHeight]);
+ pDstScan.reset(new Scanline[pDstBuffer->mnHeight]);
+ pMapX.reset(new sal_Int32[pDstBuffer->mnWidth]);
+ pMapY.reset(new sal_Int32[pDstBuffer->mnHeight]);
+ }
+ catch( const std::bad_alloc& )
+ {
+ // memory exception, clean up
+ // remark: the buffer ptr causing the exception
+ // is still NULL here
+ return std::nullopt;
+ }
+
+ // horizontal mapping table
+ if( (pDstBuffer->mnWidth != rTwoRect.mnSrcWidth) && (pDstBuffer->mnWidth != 0) )
+ {
+ const double fFactorX = static_cast<double>(rTwoRect.mnSrcWidth) / pDstBuffer->mnWidth;
+
+ for (tools::Long i = 0; i < pDstBuffer->mnWidth; ++i)
+ pMapX[ i ] = rTwoRect.mnSrcX + static_cast<int>( i * fFactorX );
+ }
+ else
+ {
+ for (tools::Long i = 0, nTmp = rTwoRect.mnSrcX ; i < pDstBuffer->mnWidth; ++i)
+ pMapX[ i ] = nTmp++;
+ }
+
+ // vertical mapping table
+ if( (pDstBuffer->mnHeight != rTwoRect.mnSrcHeight) && (pDstBuffer->mnHeight != 0) )
+ {
+ const double fFactorY = static_cast<double>(rTwoRect.mnSrcHeight) / pDstBuffer->mnHeight;
+
+ for (tools::Long i = 0; i < pDstBuffer->mnHeight; ++i)
+ pMapY[ i ] = rTwoRect.mnSrcY + static_cast<int>( i * fFactorY );
+ }
+ else
+ {
+ for (tools::Long i = 0, nTmp = rTwoRect.mnSrcY; i < pDstBuffer->mnHeight; ++i)
+ pMapY[ i ] = nTmp++;
+ }
+
+ // source scanline buffer
+ Scanline pTmpScan;
+ tools::Long nOffset;
+ if( rSrcBuffer.mnFormat & ScanlineFormat::TopDown )
+ {
+ pTmpScan = rSrcBuffer.mpBits;
+ nOffset = rSrcBuffer.mnScanlineSize;
+ }
+ else
+ {
+ pTmpScan = rSrcBuffer.mpBits + ( rSrcBuffer.mnHeight - 1 ) * rSrcBuffer.mnScanlineSize;
+ nOffset = -rSrcBuffer.mnScanlineSize;
+ }
+
+ for (tools::Long i = 0; i < rSrcBuffer.mnHeight; i++, pTmpScan += nOffset)
+ pSrcScan[ i ] = pTmpScan;
+
+ // destination scanline buffer
+ if( pDstBuffer->mnFormat & ScanlineFormat::TopDown )
+ {
+ pTmpScan = pDstBuffer->mpBits;
+ nOffset = pDstBuffer->mnScanlineSize;
+ }
+ else
+ {
+ pTmpScan = pDstBuffer->mpBits + ( pDstBuffer->mnHeight - 1 ) * pDstBuffer->mnScanlineSize;
+ nOffset = -pDstBuffer->mnScanlineSize;
+ }
+
+ for (tools::Long i = 0; i < pDstBuffer->mnHeight; i++, pTmpScan += nOffset)
+ pDstScan[ i ] = pTmpScan;
+
+ // do buffer scaling and conversion
+ if( rSrcBuffer.mnBitCount <= 8 && pDstBuffer->mnBitCount <= 8 )
+ {
+ ImplPALToPAL( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
+ pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() );
+ }
+ else if( rSrcBuffer.mnBitCount <= 8 && pDstBuffer->mnBitCount > 8 )
+ {
+ ImplPALToTC( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
+ pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() );
+ }
+ else if( rSrcBuffer.mnBitCount > 8 && pDstBuffer->mnBitCount > 8 )
+ {
+ ImplTCToTC( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
+ pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() );
+ }
+ else
+ {
+ ImplTCToPAL( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel,
+ pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() );
+ }
+
+ return pDstBuffer;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/scrptrun.cxx b/vcl/source/gdi/scrptrun.cxx
new file mode 100644
index 0000000000..19cb54772b
--- /dev/null
+++ b/vcl/source/gdi/scrptrun.cxx
@@ -0,0 +1,259 @@
+/*
+ *******************************************************************************
+ *
+ * Copyright (c) 1995-2013 International Business Machines Corporation and others
+ *
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * provided that the above copyright notice(s) and this permission notice appear
+ * in all copies of the Software and that both the above copyright notice(s) and
+ * this permission notice appear in supporting documentation.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
+ * NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE
+ * LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY
+ * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Except as contained in this notice, the name of a copyright holder shall not be
+ * used in advertising or otherwise to promote the sale, use or other dealings in
+ * this Software without prior written authorization of the copyright holder.
+ *
+ *******************************************************************************
+ * file name: scrptrun.cpp
+ *
+ * created on: 10/17/2001
+ * created by: Eric R. Mader
+ */
+/**
+ * This file is largely copied from the ICU project,
+ * under folder source/extra/scrptrun/scrptrun.cpp
+ */
+
+#include <sal/config.h>
+
+#include <rtl/character.hxx>
+#include <unicode/uchar.h>
+#include <unicode/utypes.h>
+#include <unicode/uscript.h>
+
+#include <scrptrun.h>
+#include <algorithm>
+
+namespace {
+
+struct PairIndices
+{
+ int8_t ma00[0xff];
+ int8_t ma20[0x7f];
+ int8_t ma30[0x7f];
+
+ PairIndices()
+ {
+ std::fill_n(ma00, 0xff, -1);
+ std::fill_n(ma20, 0x7f, -1);
+ std::fill_n(ma30, 0x7f, -1);
+
+ // characters in the range 0x0000 - 0x007e (inclusive)
+ // ascii paired punctuation
+ ma00[0x28] = 0;
+ ma00[0x29] = 1;
+ ma00[0x3c] = 2;
+ ma00[0x3e] = 3;
+ ma00[0x5b] = 4;
+ ma00[0x5d] = 5;
+ ma00[0x7b] = 6;
+ ma00[0x7d] = 7;
+ // guillemets
+ ma00[0xab] = 8;
+ ma00[0xbb] = 9;
+
+ // characters in the range 0x2000 - 0x207e (inclusive)
+ // general punctuation
+ ma20[0x18] = 10;
+ ma20[0x19] = 11;
+ ma20[0x1c] = 12;
+ ma20[0x1d] = 13;
+ ma20[0x39] = 14;
+ ma20[0x3a] = 15;
+
+ // characters in the range 0x3000 - 0x307e (inclusive)
+ // chinese paired punctuation
+ ma30[0x08] = 16;
+ ma30[0x09] = 17;
+ ma30[0x0a] = 18;
+ ma30[0x0b] = 19;
+ ma30[0x0c] = 20;
+ ma30[0x0d] = 21;
+ ma30[0x0e] = 22;
+ ma30[0x0f] = 23;
+ ma30[0x10] = 24;
+ ma30[0x11] = 25;
+ ma30[0x14] = 26;
+ ma30[0x15] = 27;
+ ma30[0x16] = 28;
+ ma30[0x17] = 29;
+ ma30[0x18] = 30;
+ ma30[0x19] = 31;
+ ma30[0x1a] = 32;
+ ma30[0x1b] = 33;
+ }
+
+ int32_t getPairIndex(UChar32 ch) const
+ {
+ if (ch < 0xff)
+ return ma00[ch];
+ if (ch >= 0x2000 && ch < 0x207f)
+ return ma20[ch - 0x2000];
+ if (ch >= 0x3000 && ch < 0x307f)
+ return ma30[ch - 0x3000];
+ return -1;
+ }
+
+};
+
+UScriptCode getScript(UChar32 ch, UErrorCode* status)
+{
+ // tdf#154549
+ // Make combining marks inherit the script of their bases, regardless of
+ // their own script.
+ if (u_getIntPropertyValue(ch, UCHAR_GENERAL_CATEGORY) == U_NON_SPACING_MARK)
+ return USCRIPT_INHERITED;
+
+ UScriptCode script = uscript_getScript(ch, status);
+ if (U_FAILURE(*status))
+ return script;
+
+ // There are three Unicode script codes for Japanese text, but only one
+ // OpenType script tag, so we want to keep them in one run as splitting is
+ // pointless for the purpose of OpenType shaping.
+ if (script == USCRIPT_KATAKANA || script == USCRIPT_KATAKANA_OR_HIRAGANA)
+ return USCRIPT_HIRAGANA;
+ return script;
+}
+
+}
+
+const PairIndices gPairIndices;
+
+
+namespace vcl {
+
+const char ScriptRun::fgClassID=0;
+
+static bool sameScript(int32_t scriptOne, int32_t scriptTwo)
+{
+ return scriptOne <= USCRIPT_INHERITED || scriptTwo <= USCRIPT_INHERITED || scriptOne == scriptTwo;
+}
+
+UBool ScriptRun::next()
+{
+ int32_t startSP = parenSP; // used to find the first new open character
+ UErrorCode error = U_ZERO_ERROR;
+
+ // if we've fallen off the end of the text, we're done
+ if (scriptEnd >= charLimit) {
+ return false;
+ }
+
+ scriptCode = USCRIPT_COMMON;
+
+ for (scriptStart = scriptEnd; scriptEnd < charLimit; scriptEnd += 1) {
+ UChar high = charArray[scriptEnd];
+ UChar32 ch = high;
+
+ // if the character is a high surrogate and it's not the last one
+ // in the text, see if it's followed by a low surrogate
+ if (rtl::isHighSurrogate(high) && scriptEnd < charLimit - 1)
+ {
+ UChar low = charArray[scriptEnd + 1];
+
+ // if it is followed by a low surrogate,
+ // consume it and form the full character
+ if (rtl::isLowSurrogate(low)) {
+ ch = rtl::combineSurrogates(high, low);
+ scriptEnd += 1;
+ }
+ }
+
+ UScriptCode sc = getScript(ch, &error);
+ int32_t pairIndex = gPairIndices.getPairIndex(ch);
+
+ // Paired character handling:
+
+ // if it's an open character, push it onto the stack.
+ // if it's a close character, find the matching open on the
+ // stack, and use that script code. Any non-matching open
+ // characters above it on the stack will be popped.
+ if (pairIndex >= 0) {
+ if ((pairIndex & 1) == 0) {
+ ++parenSP;
+ int32_t nVecSize = parenStack.size();
+ if (parenSP == nVecSize)
+ parenStack.resize(nVecSize + 128);
+ parenStack[parenSP].pairIndex = pairIndex;
+ parenStack[parenSP].scriptCode = scriptCode;
+ } else if (parenSP >= 0) {
+ int32_t pi = pairIndex & ~1;
+
+ while (parenSP >= 0 && parenStack[parenSP].pairIndex != pi) {
+ parenSP -= 1;
+ }
+
+ if (parenSP < startSP) {
+ startSP = parenSP;
+ }
+
+ if (parenSP >= 0) {
+ sc = parenStack[parenSP].scriptCode;
+ }
+ }
+ }
+
+ if (sameScript(scriptCode, sc)) {
+ if (scriptCode <= USCRIPT_INHERITED && sc > USCRIPT_INHERITED) {
+ scriptCode = sc;
+
+ // now that we have a final script code, fix any open
+ // characters we pushed before we knew the script code.
+ while (startSP < parenSP) {
+ parenStack[++startSP].scriptCode = scriptCode;
+ }
+ }
+
+ // if this character is a close paired character,
+ // pop it from the stack
+ if (pairIndex >= 0 && (pairIndex & 1) != 0 && parenSP >= 0) {
+ parenSP -= 1;
+ /* decrement startSP only if it is >= 0,
+ decrementing it unnecessarily will lead to memory corruption
+ while processing the above while block.
+ e.g. startSP = -4 , parenSP = -1
+ */
+ if (startSP >= 0) {
+ startSP -= 1;
+ }
+ }
+ } else {
+ // if the run broke on a surrogate pair,
+ // end it before the high surrogate
+ if (ch >= 0x10000) {
+ scriptEnd -= 1;
+ }
+
+ break;
+ }
+ }
+
+ return true;
+}
+
+}
diff --git a/vcl/source/gdi/vectorgraphicdata.cxx b/vcl/source/gdi/vectorgraphicdata.cxx
new file mode 100644
index 0000000000..9d94b171a4
--- /dev/null
+++ b/vcl/source/gdi/vectorgraphicdata.cxx
@@ -0,0 +1,357 @@
+/* -*- 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 <comphelper/diagnose_ex.hxx>
+#include <tools/stream.hxx>
+#include <sal/log.hxx>
+#include <utility>
+#include <vcl/vectorgraphicdata.hxx>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/graphic/PdfTools.hpp>
+#include <com/sun/star/graphic/SvgTools.hpp>
+#include <com/sun/star/graphic/EmfTools.hpp>
+#include <com/sun/star/graphic/Primitive2DTools.hpp>
+#include <com/sun/star/rendering/XIntegerReadOnlyBitmap.hpp>
+#include <com/sun/star/util/XAccounting.hpp>
+#include <com/sun/star/util/XBinaryDataContainer.hpp>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <vcl/canvastools.hxx>
+#include <comphelper/seqstream.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <rtl/crc.h>
+#include <vcl/svapp.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/wmfexternal.hxx>
+#include <vcl/pdfread.hxx>
+#include <unotools/streamwrap.hxx>
+#include <graphic/UnoBinaryDataContainer.hxx>
+
+using namespace ::com::sun::star;
+
+BitmapEx convertPrimitive2DSequenceToBitmapEx(
+ const std::deque< css::uno::Reference< css::graphic::XPrimitive2D > >& rSequence,
+ const basegfx::B2DRange& rTargetRange,
+ const sal_uInt32 nMaximumQuadraticPixels,
+ const o3tl::Length eTargetUnit,
+ const std::optional<Size>& rTargetDPI)
+{
+ BitmapEx aRetval;
+
+ if(!rSequence.empty())
+ {
+ // create replacement graphic from maSequence
+ // create XPrimitive2DRenderer
+ try
+ {
+ uno::Reference< uno::XComponentContext > xContext(::comphelper::getProcessComponentContext());
+ const uno::Reference< graphic::XPrimitive2DRenderer > xPrimitive2DRenderer = graphic::Primitive2DTools::create(xContext);
+
+ uno::Sequence< beans::PropertyValue > aViewParameters = {
+ comphelper::makePropertyValue("RangeUnit", static_cast<sal_Int32>(eTargetUnit)),
+ };
+ geometry::RealRectangle2D aRealRect;
+
+ aRealRect.X1 = rTargetRange.getMinX();
+ aRealRect.Y1 = rTargetRange.getMinY();
+ aRealRect.X2 = rTargetRange.getMaxX();
+ aRealRect.Y2 = rTargetRange.getMaxY();
+
+ // get system DPI
+ Size aDPI(Application::GetDefaultDevice()->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)));
+ if (rTargetDPI.has_value())
+ {
+ aDPI = *rTargetDPI;
+ }
+
+ const uno::Reference< rendering::XBitmap > xBitmap(
+ xPrimitive2DRenderer->rasterize(
+ comphelper::containerToSequence(rSequence),
+ aViewParameters,
+ aDPI.getWidth(),
+ aDPI.getHeight(),
+ aRealRect,
+ nMaximumQuadraticPixels));
+
+ if(xBitmap.is())
+ {
+ const uno::Reference< rendering::XIntegerReadOnlyBitmap> xIntBmp(xBitmap, uno::UNO_QUERY_THROW);
+ aRetval = vcl::unotools::bitmapExFromXBitmap(xIntBmp);
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl", "Got no graphic::XPrimitive2DRenderer!");
+ }
+ catch (const std::exception& e)
+ {
+ SAL_WARN("vcl", "Got no graphic::XPrimitive2DRenderer! : " << e.what());
+ }
+ }
+
+ return aRetval;
+}
+
+static size_t estimateSize(
+ std::deque<uno::Reference<graphic::XPrimitive2D>> const& rSequence)
+{
+ size_t nRet(0);
+ for (auto& it : rSequence)
+ {
+ uno::Reference<util::XAccounting> const xAcc(it, uno::UNO_QUERY);
+ assert(xAcc.is()); // we expect only BasePrimitive2D from SVG parser
+ nRet += xAcc->estimateUsage();
+ }
+ return nRet;
+}
+
+bool VectorGraphicData::operator==(const VectorGraphicData& rCandidate) const
+{
+ if (getType() == rCandidate.getType())
+ {
+ if (maDataContainer.getSize() == rCandidate.maDataContainer.getSize())
+ {
+ if (0 == memcmp(
+ maDataContainer.getData(),
+ rCandidate.maDataContainer.getData(),
+ maDataContainer.getSize()))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void VectorGraphicData::ensurePdfReplacement()
+{
+ assert(getType() == VectorGraphicDataType::Pdf);
+
+ if (!maReplacement.IsEmpty())
+ return; // nothing to do
+
+ // use PDFium directly
+ std::vector<BitmapEx> aBitmaps;
+ sal_Int32 nUsePageIndex = 0;
+ if (mnPageIndex >= 0)
+ nUsePageIndex = mnPageIndex;
+ vcl::RenderPDFBitmaps(maDataContainer.getData(),
+ maDataContainer.getSize(), aBitmaps, nUsePageIndex, 1,
+ &maSizeHint);
+ if (!aBitmaps.empty())
+ maReplacement = aBitmaps[0];
+}
+
+void VectorGraphicData::ensureReplacement()
+{
+ if (!maReplacement.IsEmpty())
+ return; // nothing to do
+
+ // shortcut for PDF - PDFium can generate the replacement bitmap for us
+ // directly
+ if (getType() == VectorGraphicDataType::Pdf)
+ {
+ ensurePdfReplacement();
+ return;
+ }
+
+ ensureSequenceAndRange();
+
+ if (!maSequence.empty())
+ {
+ maReplacement = convertPrimitive2DSequenceToBitmapEx(maSequence, getRange());
+ }
+}
+
+void VectorGraphicData::ensureSequenceAndRange()
+{
+ if (mbSequenceCreated || maDataContainer.isEmpty())
+ return;
+
+ // import SVG to maSequence, also set maRange
+ maRange.reset();
+
+ // create Vector Graphic Data interpreter
+ uno::Reference<uno::XComponentContext> xContext(::comphelper::getProcessComponentContext());
+
+ switch (getType())
+ {
+ case VectorGraphicDataType::Svg:
+ {
+ const uno::Reference<io::XInputStream> xInputStream = maDataContainer.getAsXInputStream();
+
+ const uno::Reference< graphic::XSvgParser > xSvgParser = graphic::SvgTools::create(xContext);
+
+ if (xInputStream.is())
+ maSequence = comphelper::sequenceToContainer<std::deque<css::uno::Reference< css::graphic::XPrimitive2D >>>(xSvgParser->getDecomposition(xInputStream, OUString()));
+
+ break;
+ }
+ case VectorGraphicDataType::Emf:
+ case VectorGraphicDataType::Wmf:
+ {
+ const uno::Reference< graphic::XEmfParser > xEmfParser = graphic::EmfTools::create(xContext);
+
+ const uno::Reference<io::XInputStream> xInputStream = maDataContainer.getAsXInputStream();
+
+ if (xInputStream.is())
+ {
+ uno::Sequence< ::beans::PropertyValue > aPropertySequence;
+
+ // Pass the size hint of the graphic to the EMF parser.
+ geometry::RealPoint2D aSizeHint;
+ aSizeHint.X = maSizeHint.getX();
+ aSizeHint.Y = maSizeHint.getY();
+ xEmfParser->setSizeHint(aSizeHint);
+
+ if (!mbEnableEMFPlus)
+ {
+ aPropertySequence = { comphelper::makePropertyValue("EMFPlusEnable", uno::Any(false)) };
+ }
+
+ maSequence = comphelper::sequenceToContainer<std::deque<css::uno::Reference< css::graphic::XPrimitive2D >>>(xEmfParser->getDecomposition(xInputStream, OUString(), aPropertySequence));
+ }
+
+ break;
+ }
+ case VectorGraphicDataType::Pdf:
+ {
+ const uno::Reference<graphic::XPdfDecomposer> xPdfDecomposer = graphic::PdfTools::create(xContext);
+ uno::Sequence<beans::PropertyValue> aDecompositionParameters = comphelper::InitPropertySequence({
+ {"PageIndex", uno::Any(sal_Int32(mnPageIndex))},
+ });
+
+ rtl::Reference<UnoBinaryDataContainer> xDataContainer = new UnoBinaryDataContainer(getBinaryDataContainer());
+
+ auto xPrimitive2D = xPdfDecomposer->getDecomposition(xDataContainer, aDecompositionParameters);
+ maSequence = comphelper::sequenceToContainer<std::deque<uno::Reference<graphic::XPrimitive2D>>>(xPrimitive2D);
+
+ break;
+ }
+ }
+
+ if(!maSequence.empty())
+ {
+ const sal_Int32 nCount(maSequence.size());
+ geometry::RealRectangle2D aRealRect;
+ uno::Sequence< beans::PropertyValue > aViewParameters;
+
+ for(sal_Int32 a(0); a < nCount; a++)
+ {
+ // get reference
+ const css::uno::Reference< css::graphic::XPrimitive2D > xReference(maSequence[a]);
+
+ if(xReference.is())
+ {
+ aRealRect = xReference->getRange(aViewParameters);
+
+ maRange.expand(
+ basegfx::B2DRange(
+ aRealRect.X1,
+ aRealRect.Y1,
+ aRealRect.X2,
+ aRealRect.Y2));
+ }
+ }
+ }
+ mNestedBitmapSize = estimateSize(maSequence);
+ mbSequenceCreated = true;
+}
+
+std::pair<VectorGraphicData::State, size_t> VectorGraphicData::getSizeBytes() const
+{
+ if (!maSequence.empty() && !maDataContainer.isEmpty())
+ {
+ return std::make_pair(State::PARSED, maDataContainer.getSize() + mNestedBitmapSize);
+ }
+ else
+ {
+ return std::make_pair(State::UNPARSED, maDataContainer.getSize());
+ }
+}
+
+VectorGraphicData::VectorGraphicData(
+ BinaryDataContainer aDataContainer,
+ VectorGraphicDataType eVectorDataType,
+ sal_Int32 nPageIndex)
+: maDataContainer(std::move(aDataContainer)),
+ mbSequenceCreated(false),
+ mNestedBitmapSize(0),
+ meType(eVectorDataType),
+ mnPageIndex(nPageIndex)
+{
+}
+
+VectorGraphicData::VectorGraphicData(
+ const OUString& rPath,
+ VectorGraphicDataType eVectorDataType)
+: mbSequenceCreated(false),
+ mNestedBitmapSize(0),
+ meType(eVectorDataType),
+ mnPageIndex(-1)
+{
+ SvFileStream rIStm(rPath, StreamMode::STD_READ);
+ if(rIStm.GetError())
+ return;
+ const sal_uInt32 nStmLen(rIStm.remainingSize());
+ if (nStmLen)
+ {
+ BinaryDataContainer aData(rIStm, nStmLen);
+
+ if (!rIStm.GetError())
+ {
+ maDataContainer = aData;
+ }
+ }
+}
+
+VectorGraphicData::~VectorGraphicData()
+{
+}
+
+const basegfx::B2DRange& VectorGraphicData::getRange() const
+{
+ const_cast< VectorGraphicData* >(this)->ensureSequenceAndRange();
+
+ return maRange;
+}
+
+const std::deque< css::uno::Reference< css::graphic::XPrimitive2D > >& VectorGraphicData::getPrimitive2DSequence() const
+{
+ const_cast< VectorGraphicData* >(this)->ensureSequenceAndRange();
+
+ return maSequence;
+}
+
+const BitmapEx& VectorGraphicData::getReplacement() const
+{
+ const_cast< VectorGraphicData* >(this)->ensureReplacement();
+
+ return maReplacement;
+}
+
+BitmapChecksum VectorGraphicData::GetChecksum() const
+{
+ return rtl_crc32(0, maDataContainer.getData(), maDataContainer.getSize());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/virdev.cxx b/vcl/source/gdi/virdev.cxx
new file mode 100644
index 0000000000..b86c4ae496
--- /dev/null
+++ b/vcl/source/gdi/virdev.cxx
@@ -0,0 +1,521 @@
+/* -*- 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 <sal/config.h>
+
+#include <comphelper/lok.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/pdfextoutdevdata.hxx>
+#include <vcl/virdev.hxx>
+
+#include <ImplOutDevData.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <impfontcache.hxx>
+#include <salinst.hxx>
+#include <salgdi.hxx>
+#include <salvd.hxx>
+#include <svdata.hxx>
+
+using namespace ::com::sun::star::uno;
+
+bool VirtualDevice::CanEnableNativeWidget() const
+{
+ const vcl::ExtOutDevData* pOutDevData(GetExtOutDevData());
+ const vcl::PDFExtOutDevData* pPDFData(dynamic_cast<const vcl::PDFExtOutDevData*>(pOutDevData));
+ return pPDFData == nullptr;
+}
+
+bool VirtualDevice::AcquireGraphics() const
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( mpGraphics )
+ return true;
+
+ mbInitLineColor = true;
+ mbInitFillColor = true;
+ mbInitFont = true;
+ mbInitTextColor = true;
+ mbInitClipRegion = true;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( mpVirDev )
+ {
+ mpGraphics = mpVirDev->AcquireGraphics();
+ // if needed retry after releasing least recently used virtual device graphics
+ while ( !mpGraphics )
+ {
+ if ( !pSVData->maGDIData.mpLastVirGraphics )
+ break;
+ pSVData->maGDIData.mpLastVirGraphics->ReleaseGraphics();
+ mpGraphics = mpVirDev->AcquireGraphics();
+ }
+ // update global LRU list of virtual device graphics
+ if ( mpGraphics )
+ {
+ mpNextGraphics = pSVData->maGDIData.mpFirstVirGraphics;
+ pSVData->maGDIData.mpFirstVirGraphics = const_cast<VirtualDevice*>(this);
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = const_cast<VirtualDevice*>(this);
+ if ( !pSVData->maGDIData.mpLastVirGraphics )
+ pSVData->maGDIData.mpLastVirGraphics = const_cast<VirtualDevice*>(this);
+ }
+ }
+
+ if ( mpGraphics )
+ {
+ mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp );
+ mpGraphics->setAntiAlias(bool(mnAntialiasing & AntialiasingFlags::Enable));
+ }
+
+ return mpGraphics != nullptr;
+}
+
+void VirtualDevice::ReleaseGraphics( bool bRelease )
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( !mpGraphics )
+ return;
+
+ // release the fonts of the physically released graphics device
+ if ( bRelease )
+ ImplReleaseFonts();
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ VirtualDevice* pVirDev = this;
+
+ if ( bRelease )
+ pVirDev->mpVirDev->ReleaseGraphics( mpGraphics );
+ // remove from global LRU list of virtual device graphics
+ if ( mpPrevGraphics )
+ mpPrevGraphics->mpNextGraphics = mpNextGraphics;
+ else
+ pSVData->maGDIData.mpFirstVirGraphics = mpNextGraphics;
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = mpPrevGraphics;
+ else
+ pSVData->maGDIData.mpLastVirGraphics = mpPrevGraphics;
+
+ mpGraphics = nullptr;
+ mpPrevGraphics = nullptr;
+ mpNextGraphics = nullptr;
+}
+
+void VirtualDevice::ImplInitVirDev( const OutputDevice* pOutDev,
+ tools::Long nDX, tools::Long nDY, const SystemGraphicsData *pData )
+{
+ SAL_INFO( "vcl.virdev", "ImplInitVirDev(" << nDX << "," << nDY << ")" );
+
+ meRefDevMode = RefDevMode::NONE;
+ mbForceZeroExtleadBug = false;
+ mnBitCount = 0;
+ mbScreenComp = false;
+
+
+ bool bErase = nDX > 0 && nDY > 0;
+
+ if ( nDX < 1 )
+ nDX = 1;
+
+ if ( nDY < 1 )
+ nDY = 1;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( !pOutDev )
+ pOutDev = ImplGetDefaultWindow()->GetOutDev();
+ if( !pOutDev )
+ return;
+
+ SalGraphics* pGraphics;
+ if ( !pOutDev->mpGraphics )
+ (void)pOutDev->AcquireGraphics();
+ pGraphics = pOutDev->mpGraphics;
+ if ( pGraphics )
+ mpVirDev = pSVData->mpDefInst->CreateVirtualDevice(*pGraphics, nDX, nDY, meFormatAndAlpha, pData);
+ else
+ mpVirDev = nullptr;
+ if ( !mpVirDev )
+ {
+ // do not abort but throw an exception, may be the current thread terminates anyway (plugin-scenario)
+ throw css::uno::RuntimeException(
+ "Could not create system bitmap!",
+ css::uno::Reference< css::uno::XInterface >() );
+ }
+
+ mnBitCount = pOutDev->GetBitCount();
+ mnOutWidth = nDX;
+ mnOutHeight = nDY;
+
+ mbScreenComp = pOutDev->IsScreenComp();
+
+ mbDevOutput = true;
+ mxFontCollection = pSVData->maGDIData.mxScreenFontList;
+ mxFontCache = pSVData->maGDIData.mxScreenFontCache;
+ mnDPIX = pOutDev->mnDPIX;
+ mnDPIY = pOutDev->mnDPIY;
+ mnDPIScalePercentage = pOutDev->mnDPIScalePercentage;
+ maFont = pOutDev->maFont;
+
+ if( maTextColor != pOutDev->maTextColor )
+ {
+ maTextColor = pOutDev->maTextColor;
+ mbInitTextColor = true;
+ }
+
+ // virtual devices have white background by default
+ SetBackground( Wallpaper( COL_WHITE ) );
+
+ // #i59283# don't erase user-provided surface
+ if( !pData && bErase)
+ Erase();
+
+ // register VirDev in the list
+ mpNext = pSVData->maGDIData.mpFirstVirDev;
+ mpPrev = nullptr;
+ if ( mpNext )
+ mpNext->mpPrev = this;
+ pSVData->maGDIData.mpFirstVirDev = this;
+}
+
+VirtualDevice::VirtualDevice(const OutputDevice* pCompDev, DeviceFormat eFormatAndAlpha,
+ OutDevType eOutDevType)
+ : OutputDevice(eOutDevType)
+ , meFormatAndAlpha(eFormatAndAlpha)
+{
+ SAL_INFO( "vcl.virdev", "VirtualDevice::VirtualDevice( " << static_cast<int>(eFormatAndAlpha)
+ << ", " << static_cast<int>(eOutDevType) << " )" );
+
+ ImplInitVirDev(pCompDev ? pCompDev : Application::GetDefaultDevice(), 0, 0);
+}
+
+VirtualDevice::VirtualDevice(const SystemGraphicsData& rData, const Size &rSize,
+ DeviceFormat eFormat)
+ : OutputDevice(OUTDEV_VIRDEV)
+ , meFormatAndAlpha(eFormat)
+{
+ SAL_INFO( "vcl.virdev", "VirtualDevice::VirtualDevice( " << static_cast<int>(eFormat) << " )" );
+
+ ImplInitVirDev(Application::GetDefaultDevice(), rSize.Width(), rSize.Height(), &rData);
+}
+
+VirtualDevice::~VirtualDevice()
+{
+ SAL_INFO( "vcl.virdev", "VirtualDevice::~VirtualDevice()" );
+ disposeOnce();
+}
+
+void VirtualDevice::dispose()
+{
+ SAL_INFO( "vcl.virdev", "VirtualDevice::dispose()" );
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ ReleaseGraphics();
+
+ mpVirDev.reset();
+
+ // remove this VirtualDevice from the double-linked global list
+ if( mpPrev )
+ mpPrev->mpNext = mpNext;
+ else
+ pSVData->maGDIData.mpFirstVirDev = mpNext;
+
+ if( mpNext )
+ mpNext->mpPrev = mpPrev;
+
+ OutputDevice::dispose();
+}
+
+bool VirtualDevice::InnerImplSetOutputSizePixel( const Size& rNewSize, bool bErase,
+ sal_uInt8 *const pBuffer)
+{
+ SAL_INFO( "vcl.virdev",
+ "VirtualDevice::InnerImplSetOutputSizePixel( " << rNewSize.Width() << ", "
+ << rNewSize.Height() << ", " << int(bErase) << " )" );
+
+ if ( !mpVirDev )
+ return false;
+ else if ( rNewSize == GetOutputSizePixel() )
+ {
+ if ( bErase )
+ Erase();
+ SAL_INFO( "vcl.virdev", "Trying to re-use a VirtualDevice but this time using a pre-allocated buffer");
+ return true;
+ }
+
+ bool bRet;
+ tools::Long nNewWidth = rNewSize.Width(), nNewHeight = rNewSize.Height();
+
+ if ( nNewWidth < 1 )
+ nNewWidth = 1;
+
+ if ( nNewHeight < 1 )
+ nNewHeight = 1;
+
+ if ( bErase )
+ {
+ if ( pBuffer )
+ bRet = mpVirDev->SetSizeUsingBuffer( nNewWidth, nNewHeight, pBuffer );
+ else
+ bRet = mpVirDev->SetSize( nNewWidth, nNewHeight );
+
+ if ( bRet )
+ {
+ mnOutWidth = rNewSize.Width();
+ mnOutHeight = rNewSize.Height();
+ Erase();
+ }
+ }
+ else
+ {
+ std::unique_ptr<SalVirtualDevice> pNewVirDev;
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+
+ assert(mpGraphics);
+
+ pNewVirDev = pSVData->mpDefInst->CreateVirtualDevice(*mpGraphics, nNewWidth, nNewHeight, meFormatAndAlpha);
+ if ( pNewVirDev )
+ {
+ SalGraphics* pGraphics = pNewVirDev->AcquireGraphics();
+ if ( pGraphics )
+ {
+ tools::Long nWidth;
+ tools::Long nHeight;
+ if ( mnOutWidth < nNewWidth )
+ nWidth = mnOutWidth;
+ else
+ nWidth = nNewWidth;
+ if ( mnOutHeight < nNewHeight )
+ nHeight = mnOutHeight;
+ else
+ nHeight = nNewHeight;
+ SalTwoRect aPosAry(0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight);
+ pGraphics->CopyBits( aPosAry, *mpGraphics, *this, *this );
+ pNewVirDev->ReleaseGraphics( pGraphics );
+ ReleaseGraphics();
+ mpVirDev = std::move(pNewVirDev);
+ mnOutWidth = rNewSize.Width();
+ mnOutHeight = rNewSize.Height();
+ bRet = true;
+ }
+ else
+ {
+ bRet = false;
+ }
+ }
+ else
+ bRet = false;
+ }
+
+ return bRet;
+}
+
+// #i32109#: Fill opaque areas correctly (without relying on
+// fill/linecolor state)
+void VirtualDevice::ImplFillOpaqueRectangle( const tools::Rectangle& rRect )
+{
+ // Set line and fill color to opaque,
+ // fill rect with that (linecolor, too, because of
+ // those pesky missing pixel problems)
+ Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
+ SetLineColor( COL_ALPHA_OPAQUE );
+ SetFillColor( COL_ALPHA_OPAQUE );
+ DrawRect( rRect );
+ Pop();
+}
+
+bool VirtualDevice::ImplSetOutputSizePixel( const Size& rNewSize, bool bErase,
+ sal_uInt8 *const pBuffer, bool bAlphaMaskTransparent )
+{
+ if( InnerImplSetOutputSizePixel(rNewSize, bErase, pBuffer) )
+ {
+ if (meFormatAndAlpha != DeviceFormat::WITHOUT_ALPHA)
+ {
+ // #110958# Setup alpha bitmap
+ if(mpAlphaVDev && mpAlphaVDev->GetOutputSizePixel() != rNewSize)
+ {
+ mpAlphaVDev.disposeAndClear();
+ }
+
+ if( !mpAlphaVDev )
+ {
+ mpAlphaVDev = VclPtr<VirtualDevice>::Create(*this, meFormatAndAlpha);
+ mpAlphaVDev->InnerImplSetOutputSizePixel(rNewSize, bErase, nullptr);
+ mpAlphaVDev->SetBackground( Wallpaper(bAlphaMaskTransparent ? COL_ALPHA_TRANSPARENT : COL_ALPHA_OPAQUE) );
+ mpAlphaVDev->Erase();
+ }
+
+ // TODO: copy full outdev state to new one, here. Also needed in outdev2.cxx:DrawOutDev
+ if( GetLineColor() != COL_TRANSPARENT )
+ mpAlphaVDev->SetLineColor( COL_ALPHA_OPAQUE );
+
+ if( GetFillColor() != COL_TRANSPARENT )
+ mpAlphaVDev->SetFillColor( COL_ALPHA_OPAQUE );
+
+ mpAlphaVDev->SetMapMode( GetMapMode() );
+
+ mpAlphaVDev->SetAntialiasing( GetAntialiasing() );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+void VirtualDevice::EnableRTL( bool bEnable )
+{
+ // virdevs default to not mirroring, they will only be set to mirroring
+ // under rare circumstances in the UI, eg the valueset control
+ // because each virdev has its own SalGraphics we can safely switch the SalGraphics here
+ // ...hopefully
+ if( AcquireGraphics() )
+ mpGraphics->SetLayout( bEnable ? SalLayoutFlags::BiDiRtl : SalLayoutFlags::NONE );
+
+ OutputDevice::EnableRTL(bEnable);
+}
+
+bool VirtualDevice::SetOutputSizePixel( const Size& rNewSize, bool bErase, bool bAlphaMaskTransparent )
+{
+ return ImplSetOutputSizePixel(rNewSize, bErase, nullptr, bAlphaMaskTransparent);
+}
+
+bool VirtualDevice::SetOutputSizePixelScaleOffsetAndLOKBuffer(
+ const Size& rNewSize, const Fraction& rScale, const Point& rNewOffset,
+ sal_uInt8 *const pBuffer)
+{
+ // If this is ever needed for something else than LOK, changes will
+ // be needed in SvpSalVirtualDevice::CreateSurface() .
+ assert(comphelper::LibreOfficeKit::isActive());
+ assert(pBuffer);
+ MapMode mm = GetMapMode();
+ mm.SetOrigin( rNewOffset );
+ mm.SetScaleX( rScale );
+ mm.SetScaleY( rScale );
+ SetMapMode( mm );
+ return ImplSetOutputSizePixel(rNewSize, true, pBuffer);
+}
+
+void VirtualDevice::SetReferenceDevice( RefDevMode i_eRefDevMode )
+{
+ sal_Int32 nDPIX = 600, nDPIY = 600;
+ switch( i_eRefDevMode )
+ {
+ case RefDevMode::NONE:
+ default:
+ SAL_WARN( "vcl.virdev", "VDev::SetRefDev illegal argument!" );
+ break;
+ case RefDevMode::Dpi600:
+ nDPIX = nDPIY = 600;
+ break;
+ case RefDevMode::MSO1:
+ nDPIX = nDPIY = 6*1440;
+ break;
+ case RefDevMode::PDF1:
+ nDPIX = nDPIY = 720;
+ break;
+ }
+ ImplSetReferenceDevice( i_eRefDevMode, nDPIX, nDPIY );
+}
+
+void VirtualDevice::SetReferenceDevice( sal_Int32 i_nDPIX, sal_Int32 i_nDPIY )
+{
+ ImplSetReferenceDevice( RefDevMode::Custom, i_nDPIX, i_nDPIY );
+}
+
+bool VirtualDevice::IsVirtual() const
+{
+ return true;
+}
+
+void VirtualDevice::ImplSetReferenceDevice( RefDevMode i_eRefDevMode, sal_Int32 i_nDPIX, sal_Int32 i_nDPIY )
+{
+ mnDPIX = i_nDPIX;
+ mnDPIY = i_nDPIY;
+ mnDPIScalePercentage = 100;
+
+ EnableOutput( false ); // prevent output on reference device
+ mbScreenComp = false;
+
+ // invalidate currently selected fonts
+ mbInitFont = true;
+ mbNewFont = true;
+
+ // avoid adjusting font lists when already in refdev mode
+ RefDevMode nOldRefDevMode = meRefDevMode;
+ meRefDevMode = i_eRefDevMode;
+ if( nOldRefDevMode != RefDevMode::NONE )
+ return;
+
+ // the reference device should have only scalable fonts
+ // => clean up the original font lists before getting new ones
+ mpFontInstance.clear();
+ mpFontFaceCollection.reset();
+
+ // preserve global font lists
+ ImplSVData* pSVData = ImplGetSVData();
+ mxFontCollection.reset();
+ mxFontCache.reset();
+
+ // get font list with scalable fonts only
+ (void)AcquireGraphics();
+ mxFontCollection = pSVData->maGDIData.mxScreenFontList->Clone();
+
+ // prepare to use new font lists
+ mxFontCache = std::make_shared<ImplFontCache>();
+}
+
+sal_uInt16 VirtualDevice::GetBitCount() const
+{
+ return mnBitCount;
+}
+
+bool VirtualDevice::UsePolyPolygonForComplexGradient()
+{
+ return true;
+}
+
+void VirtualDevice::Compat_ZeroExtleadBug()
+{
+ mbForceZeroExtleadBug = true;
+}
+
+tools::Long VirtualDevice::GetFontExtLeading() const
+{
+#ifdef UNX
+ // backwards compatible line metrics after fixing #i60945#
+ if ( mbForceZeroExtleadBug )
+ return 0;
+#endif
+
+ return mpFontInstance->mxFontMetric->GetExternalLeading();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/gdi/wall.cxx b/vcl/source/gdi/wall.cxx
new file mode 100644
index 0000000000..2c7d571e90
--- /dev/null
+++ b/vcl/source/gdi/wall.cxx
@@ -0,0 +1,271 @@
+/* -*- 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 <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/wall.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/TypeSerializer.hxx>
+
+SvStream& ReadWallpaper( SvStream& rIStm, Wallpaper& rImplWallpaper )
+{
+ VersionCompatRead aCompat(rIStm);
+
+ rImplWallpaper.maRect.SetEmpty();
+ rImplWallpaper.mpGradient.reset();
+ rImplWallpaper.maBitmap.SetEmpty();
+
+ // version 1
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readColor(rImplWallpaper.maColor);
+ sal_uInt16 nTmp16(0);
+ rIStm.ReadUInt16(nTmp16);
+ rImplWallpaper.meStyle = static_cast<WallpaperStyle>(nTmp16);
+
+ // version 2
+ if( aCompat.GetVersion() >= 2 )
+ {
+ bool bRect(false), bGrad(false), bBmp(false), bDummy;
+
+ rIStm.ReadCharAsBool( bRect ).ReadCharAsBool( bGrad ).ReadCharAsBool( bBmp ).ReadCharAsBool( bDummy ).ReadCharAsBool( bDummy ).ReadCharAsBool( bDummy );
+
+ if( bRect )
+ {
+ rImplWallpaper.maRect = tools::Rectangle();
+ aSerializer.readRectangle(rImplWallpaper.maRect);
+ }
+
+ if( bGrad )
+ {
+ rImplWallpaper.mpGradient.emplace();
+ aSerializer.readGradient(*rImplWallpaper.mpGradient);
+ }
+
+ if( bBmp )
+ {
+ rImplWallpaper.maBitmap.SetEmpty();
+ ReadDIBBitmapEx(rImplWallpaper.maBitmap, rIStm);
+ }
+
+ // version 3 (new color format)
+ if( aCompat.GetVersion() >= 3 )
+ {
+ sal_uInt32 nTmp;
+ rIStm.ReadUInt32(nTmp);
+ rImplWallpaper.maColor = ::Color(ColorTransparency, nTmp);
+ }
+ }
+
+ return rIStm;
+}
+
+SvStream& WriteWallpaper( SvStream& rOStm, const Wallpaper& rImplWallpaper )
+{
+ VersionCompatWrite aCompat(rOStm, 3);
+ bool bRect = !rImplWallpaper.maRect.IsEmpty();
+ bool bGrad = bool(rImplWallpaper.mpGradient);
+ bool bBmp = !rImplWallpaper.maBitmap.IsEmpty();
+ bool bDummy = false;
+
+ // version 1
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeColor(rImplWallpaper.maColor);
+
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rImplWallpaper.meStyle) );
+
+ // version 2
+ rOStm.WriteBool( bRect ).WriteBool( bGrad ).WriteBool( bBmp ).WriteBool( bDummy ).WriteBool( bDummy ).WriteBool( bDummy );
+
+ if( bRect )
+ {
+ aSerializer.writeRectangle(rImplWallpaper.maRect);
+ }
+
+ if (bGrad)
+ {
+ aSerializer.writeGradient(*rImplWallpaper.mpGradient);
+ }
+
+ if( bBmp )
+ WriteDIBBitmapEx(rImplWallpaper.maBitmap, rOStm);
+
+ // version 3 (new color format)
+ rOStm.WriteUInt32(static_cast<sal_uInt32>(rImplWallpaper.maColor));
+
+ return rOStm;
+}
+
+Wallpaper::Wallpaper() :
+ maColor( COL_TRANSPARENT ), meStyle( WallpaperStyle::NONE )
+{
+}
+
+Wallpaper::Wallpaper( const Wallpaper& ) = default;
+
+Wallpaper::Wallpaper( Wallpaper&& ) = default;
+
+Wallpaper::Wallpaper( const Color& rColor )
+{
+ maColor = rColor;
+ meStyle = WallpaperStyle::Tile;
+}
+
+Wallpaper::Wallpaper( const BitmapEx& rBmpEx )
+{
+ maBitmap = rBmpEx;
+ meStyle = WallpaperStyle::Tile;
+}
+
+Wallpaper::~Wallpaper() = default;
+
+void Wallpaper::ImplSetCachedBitmap( const BitmapEx& rBmp ) const
+{
+ maCache = rBmp;
+}
+
+const BitmapEx* Wallpaper::ImplGetCachedBitmap() const
+{
+ return maCache.IsEmpty() ? nullptr : &maCache;
+}
+
+void Wallpaper::ImplReleaseCachedBitmap() const
+{
+ maCache.SetEmpty();
+}
+
+void Wallpaper::SetColor( const Color& rColor )
+{
+ maCache.SetEmpty();
+ maColor = rColor;
+
+ if( WallpaperStyle::NONE == meStyle || WallpaperStyle::ApplicationGradient == meStyle )
+ meStyle = WallpaperStyle::Tile;
+}
+
+void Wallpaper::SetStyle( WallpaperStyle eStyle )
+{
+ if( eStyle == WallpaperStyle::ApplicationGradient )
+ // set a dummy gradient, the correct gradient
+ // will be created dynamically in GetGradient()
+ SetGradient( ImplGetApplicationGradient() );
+
+ meStyle = eStyle;
+}
+
+void Wallpaper::SetBitmap( const BitmapEx& rBitmap )
+{
+ maCache.SetEmpty();
+ maBitmap = rBitmap;
+
+ if( WallpaperStyle::NONE == meStyle || WallpaperStyle::ApplicationGradient == meStyle)
+ meStyle = WallpaperStyle::Tile;
+}
+
+const BitmapEx & Wallpaper::GetBitmap() const
+{
+ return maBitmap;
+}
+
+bool Wallpaper::IsBitmap() const
+{
+ return !maBitmap.IsEmpty();
+}
+
+void Wallpaper::SetGradient( const Gradient& rGradient )
+{
+ maCache.SetEmpty();
+ mpGradient = rGradient;
+
+ if( WallpaperStyle::NONE == meStyle || WallpaperStyle::ApplicationGradient == meStyle )
+ meStyle = WallpaperStyle::Tile;
+}
+
+Gradient Wallpaper::GetGradient() const
+{
+ if( WallpaperStyle::ApplicationGradient == meStyle )
+ return ImplGetApplicationGradient();
+ else if ( mpGradient )
+ return *mpGradient;
+ else
+ return Gradient();
+}
+
+bool Wallpaper::IsGradient() const
+{
+ return bool(mpGradient);
+}
+
+Gradient Wallpaper::ImplGetApplicationGradient()
+{
+ Gradient g;
+ g.SetAngle( 900_deg10 );
+ g.SetStyle( css::awt::GradientStyle_LINEAR );
+ g.SetStartColor( Application::GetSettings().GetStyleSettings().GetFaceColor() );
+ // no 'extreme' gradient when high contrast
+ if( Application::GetSettings().GetStyleSettings().GetHighContrastMode() )
+ g.SetEndColor( Application::GetSettings().GetStyleSettings().GetFaceColor() );
+ else
+ g.SetEndColor( Application::GetSettings().GetStyleSettings().GetFaceGradientColor() );
+ return g;
+}
+
+bool Wallpaper::IsRect() const
+{
+ return !maRect.IsEmpty();
+}
+
+bool Wallpaper::IsFixed() const
+{
+ if ( meStyle == WallpaperStyle::NONE )
+ return false;
+ else
+ return (maBitmap.IsEmpty() && !mpGradient);
+}
+
+bool Wallpaper::IsScrollable() const
+{
+ if ( meStyle == WallpaperStyle::NONE )
+ return false;
+ else if ( maBitmap.IsEmpty() && !mpGradient )
+ return true;
+ else if ( !maBitmap.IsEmpty() )
+ return (meStyle == WallpaperStyle::Tile);
+ else
+ return false;
+}
+
+Wallpaper& Wallpaper::operator=( const Wallpaper& ) = default;
+
+Wallpaper& Wallpaper::operator=( Wallpaper&& ) = default;
+
+bool Wallpaper::operator==( const Wallpaper& rOther ) const
+{
+ return meStyle == rOther.meStyle &&
+ maColor == rOther.maColor &&
+ maRect == rOther.maRect &&
+ maBitmap == rOther.maBitmap &&
+ mpGradient == rOther.mpGradient;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */