summaryrefslogtreecommitdiffstats
path: root/vcl/source/gdi
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--vcl/source/gdi/CommonSalLayout.cxx854
-rw-r--r--vcl/source/gdi/FileDefinitionWidgetDraw.cxx1080
-rw-r--r--vcl/source/gdi/TypeSerializer.cxx419
-rw-r--r--vcl/source/gdi/VerticalOrientationData.cxx78
-rw-r--r--vcl/source/gdi/WidgetDefinition.cxx186
-rw-r--r--vcl/source/gdi/WidgetDefinitionReader.cxx494
-rw-r--r--vcl/source/gdi/alpha.cxx176
-rw-r--r--vcl/source/gdi/bitmap3.cxx1146
-rw-r--r--vcl/source/gdi/bitmapex.cxx1685
-rw-r--r--vcl/source/gdi/bmpacc.cxx464
-rw-r--r--vcl/source/gdi/bmpacc2.cxx362
-rw-r--r--vcl/source/gdi/bmpacc3.cxx278
-rw-r--r--vcl/source/gdi/bmpfast.cxx743
-rw-r--r--vcl/source/gdi/configsettings.cxx137
-rw-r--r--vcl/source/gdi/cvtgrf.cxx73
-rw-r--r--vcl/source/gdi/dibtools.cxx1904
-rw-r--r--vcl/source/gdi/embeddedfontshelper.cxx332
-rw-r--r--vcl/source/gdi/extoutdevdata.cxx31
-rw-r--r--vcl/source/gdi/gdimetafiletools.cxx1086
-rw-r--r--vcl/source/gdi/gdimtf.cxx2866
-rwxr-xr-xvcl/source/gdi/genVerticalOrientationData.pl206
-rw-r--r--vcl/source/gdi/gfxlink.cxx189
-rw-r--r--vcl/source/gdi/gradient.cxx285
-rw-r--r--vcl/source/gdi/graph.cxx601
-rw-r--r--vcl/source/gdi/graphictools.cxx296
-rw-r--r--vcl/source/gdi/hatch.cxx110
-rw-r--r--vcl/source/gdi/impanmvw.cxx321
-rw-r--r--vcl/source/gdi/impglyphitem.cxx78
-rw-r--r--vcl/source/gdi/impgraph.cxx1882
-rw-r--r--vcl/source/gdi/impvect.cxx1000
-rw-r--r--vcl/source/gdi/impvect.hxx35
-rw-r--r--vcl/source/gdi/jobset.cxx394
-rw-r--r--vcl/source/gdi/lineinfo.cxx261
-rw-r--r--vcl/source/gdi/mapmod.cxx178
-rw-r--r--vcl/source/gdi/metaact.cxx3412
-rw-r--r--vcl/source/gdi/mtfxmldump.cxx1274
-rw-r--r--vcl/source/gdi/oldprintadaptor.cxx112
-rw-r--r--vcl/source/gdi/pdfbuildin_fonts.cxx740
-rw-r--r--vcl/source/gdi/pdfbuildin_fonts.hxx81
-rw-r--r--vcl/source/gdi/pdfextoutdevdata.cxx891
-rw-r--r--vcl/source/gdi/pdffontcache.cxx65
-rw-r--r--vcl/source/gdi/pdffontcache.hxx72
-rw-r--r--vcl/source/gdi/pdfwriter.cxx467
-rw-r--r--vcl/source/gdi/pdfwriter_impl.cxx11212
-rw-r--r--vcl/source/gdi/pdfwriter_impl.hxx1265
-rw-r--r--vcl/source/gdi/pdfwriter_impl2.cxx1989
-rw-r--r--vcl/source/gdi/print.cxx1639
-rw-r--r--vcl/source/gdi/print2.cxx1314
-rw-r--r--vcl/source/gdi/print3.cxx2118
-rw-r--r--vcl/source/gdi/regband.cxx885
-rw-r--r--vcl/source/gdi/region.cxx1778
-rw-r--r--vcl/source/gdi/regionband.cxx1358
-rw-r--r--vcl/source/gdi/salgdiimpl.cxx26
-rw-r--r--vcl/source/gdi/salgdilayout.cxx897
-rw-r--r--vcl/source/gdi/sallayout.cxx1585
-rw-r--r--vcl/source/gdi/salmisc.cxx491
-rw-r--r--vcl/source/gdi/scrptrun.cxx247
-rw-r--r--vcl/source/gdi/svmconverter.cxx1245
-rw-r--r--vcl/source/gdi/textlayout.cxx351
-rw-r--r--vcl/source/gdi/vectorgraphicdata.cxx358
-rw-r--r--vcl/source/gdi/virdev.cxx510
-rw-r--r--vcl/source/gdi/wall.cxx366
62 files changed, 56978 insertions, 0 deletions
diff --git a/vcl/source/gdi/CommonSalLayout.cxx b/vcl/source/gdi/CommonSalLayout.cxx
new file mode 100644
index 000000000..d5ce806fa
--- /dev/null
+++ b/vcl/source/gdi/CommonSalLayout.cxx
@@ -0,0 +1,854 @@
+/* -*- 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 <hb-icu.h>
+#include <hb-ot.h>
+#include <hb-graphite2.h>
+
+#include <sallayout.hxx>
+
+#include <sal/log.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/unohelp.hxx>
+#include <vcl/font/Feature.hxx>
+#include <vcl/font/FeatureParser.hxx>
+#include <scrptrun.h>
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+#include <salgdi.hxx>
+#include <unicode/uchar.h>
+
+#include <fontselect.hxx>
+
+#if !HB_VERSION_ATLEAST(1, 1, 0)
+// Disabled Unicode compatibility decomposition, see fdo#66715
+static unsigned int unicodeDecomposeCompatibility(hb_unicode_funcs_t* /*ufuncs*/,
+ hb_codepoint_t /*u*/,
+ hb_codepoint_t* /*decomposed*/,
+ void* /*user_data*/)
+{
+ return 0;
+}
+
+static hb_unicode_funcs_t* getUnicodeFuncs()
+{
+ static hb_unicode_funcs_t* ufuncs = hb_unicode_funcs_create(hb_icu_get_unicode_funcs());
+ hb_unicode_funcs_set_decompose_compatibility_func(ufuncs, unicodeDecomposeCompatibility, nullptr, nullptr);
+ return ufuncs;
+}
+#endif
+
+GenericSalLayout::GenericSalLayout(LogicalFontInstance &rFont)
+ : mpVertGlyphs(nullptr)
+ , mbFuzzing(utl::ConfigManager::IsFuzzing())
+{
+ new SalLayoutGlyphsImpl(m_GlyphItems, rFont);
+}
+
+GenericSalLayout::~GenericSalLayout()
+{
+}
+
+void GenericSalLayout::ParseFeatures(const OUString& 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 vcl {
+ namespace {
+
+ struct Run
+ {
+ int32_t nStart;
+ int32_t nEnd;
+ UScriptCode nCode;
+ Run(int32_t nStart_, int32_t nEnd_, UScriptCode nCode_)
+ : nStart(nStart_)
+ , nEnd(nEnd_)
+ , nCode(nCode_)
+ {}
+ };
+
+ }
+
+ class TextLayoutCache
+ {
+ public:
+ std::vector<vcl::Run> runs;
+ TextLayoutCache(sal_Unicode const* pStr, sal_Int32 const nEnd)
+ {
+ vcl::ScriptRun aScriptRun(
+ reinterpret_cast<const UChar *>(pStr),
+ nEnd);
+ while (aScriptRun.next())
+ {
+ runs.emplace_back(aScriptRun.getScriptStart(),
+ aScriptRun.getScriptEnd(), aScriptRun.getScriptCode());
+ }
+ }
+ };
+} // namespace vcl
+
+namespace {
+#if U_ICU_VERSION_MAJOR_NUM >= 63
+ enum class VerticalOrientation {
+ Upright = U_VO_UPRIGHT,
+ Rotated = U_VO_ROTATED,
+ TransformedUpright = U_VO_TRANSFORMED_UPRIGHT,
+ TransformedRotated = U_VO_TRANSFORMED_ROTATED
+ };
+#else
+ #include "VerticalOrientationData.cxx"
+
+ // These must match the values in the file included above.
+ enum class VerticalOrientation {
+ Upright = 0,
+ Rotated = 1,
+ TransformedUpright = 2,
+ TransformedRotated = 3
+ };
+#endif
+
+ VerticalOrientation 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 VerticalOrientation::TransformedUpright;
+
+#if U_ICU_VERSION_MAJOR_NUM >= 63
+ int32_t nRet = u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION);
+#else
+ uint8_t nRet = 1;
+
+ if (cCh < 0x10000)
+ {
+ nRet = sVerticalOrientationValues[sVerticalOrientationPages[0][cCh >> kVerticalOrientationCharBits]]
+ [cCh & ((1 << kVerticalOrientationCharBits) - 1)];
+ }
+ else if (cCh < (kVerticalOrientationMaxPlane + 1) * 0x10000)
+ {
+ nRet = sVerticalOrientationValues[sVerticalOrientationPages[sVerticalOrientationPlanes[(cCh >> 16) - 1]]
+ [(cCh & 0xffff) >> kVerticalOrientationCharBits]]
+ [cCh & ((1 << kVerticalOrientationCharBits) - 1)];
+ }
+ else
+ {
+ // Default value for unassigned
+ SAL_WARN("vcl.gdi", "Getting VerticalOrientation for codepoint outside Unicode range");
+ }
+#endif
+
+ return VerticalOrientation(nRet);
+ }
+
+} // namespace
+
+std::shared_ptr<vcl::TextLayoutCache> GenericSalLayout::CreateTextLayoutCache(OUString const& rString)
+{
+ return std::make_shared<vcl::TextLayoutCache>(rString.getStr(), rString.getLength());
+}
+
+const SalLayoutGlyphs* GenericSalLayout::GetGlyphs() const
+{
+ return &m_GlyphItems;
+}
+
+void GenericSalLayout::SetNeedFallback(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;
+ sal_Int32 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);
+ sal_Int32 nGraphemeStartPos =
+ mxBreak->previousCharacters(rArgs.mrStr, nCharPos, aLocale,
+ i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
+
+ rArgs.NeedFallback(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
+}
+
+void GenericSalLayout::AdjustLayout(ImplLayoutArgs& rArgs)
+{
+ SalLayout::AdjustLayout(rArgs);
+
+ if (rArgs.mpDXArray)
+ ApplyDXArray(rArgs);
+ 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)
+{
+ hb_codepoint_t nGlyphIndex = 0;
+ hb_font_t *pHbFont = GetFont().GetHbFont();
+ if (!hb_font_get_glyph(pHbFont, aChar, aVariationSelector, &nGlyphIndex))
+ return false;
+
+ if (!mpVertGlyphs)
+ {
+ hb_face_t* pHbFace = hb_font_get_face(pHbFont);
+ 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 output glyphs in each lookup (i.e. the glyphs that
+ // would result from applying this lookup).
+ 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);
+ }
+ }
+ }
+
+ return hb_set_has(mpVertGlyphs, nGlyphIndex) != 0;
+}
+
+bool GenericSalLayout::LayoutText(ImplLayoutArgs& rArgs, const SalLayoutGlyphs* 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;
+ // Some flags are set as a side effect of text layout, restore them here.
+ rArgs.mnFlags |= pGlyphs->Impl()->mnFlags;
+ return true;
+ }
+
+ hb_font_t *pHbFont = GetFont().GetHbFont();
+ bool isGraphite = GetFont().IsGraphiteFont();
+
+ int nGlyphCapacity = 2 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos);
+ m_GlyphItems.Impl()->reserve(nGlyphCapacity);
+
+ const int nLength = rArgs.mrStr.getLength();
+ const sal_Unicode *pStr = rArgs.mrStr.getStr();
+
+ std::unique_ptr<vcl::TextLayoutCache> pNewScriptRun;
+ vcl::TextLayoutCache const* pTextLayout;
+ if (rArgs.m_pTextLayoutCache)
+ {
+ pTextLayout = rArgs.m_pTextLayoutCache; // use cache!
+ }
+ else
+ {
+ pNewScriptRun.reset(new vcl::TextLayoutCache(pStr, rArgs.mnEndCharPos));
+ pTextLayout = pNewScriptRun.get();
+ }
+
+ hb_buffer_t* pHbBuffer = hb_buffer_create();
+ hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity);
+#if !HB_VERSION_ATLEAST(1, 1, 0)
+ static hb_unicode_funcs_t* pHbUnicodeFuncs = getUnicodeFuncs();
+ hb_buffer_set_unicode_funcs(pHbBuffer, pHbUnicodeFuncs);
+#endif
+
+ const 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) });
+ }
+
+ ParseFeatures(rFontSelData.maTargetName);
+
+ double nXScale = 0;
+ double nYScale = 0;
+ GetFont().GetScale(&nXScale, &nYScale);
+
+ Point 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::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);
+ VerticalOrientation 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;
+ }
+ }
+
+ // Charters 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 == VerticalOrientation::Upright ||
+ aVo == VerticalOrientation::TransformedUpright ||
+ (aVo == VerticalOrientation::TransformedRotated &&
+ HasVerticalAlternate(aChar, aVariationSelector)))
+ {
+ aDirection = HB_DIRECTION_TTB;
+ }
+ else
+ {
+ aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
+ }
+
+ if (aSubRuns.empty() || aSubRuns.back().maDirection != aDirection)
+ 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;
+
+ OString sLanguage = msLanguage;
+ if (sLanguage.isEmpty())
+ sLanguage = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US);
+
+ int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
+ 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);
+ hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), -1));
+ 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);
+ hb_buffer_set_cluster_level(pHbBuffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
+
+ // The shapers that we want HarfBuzz to use, in the order of
+ // preference. The coretext_aat shaper is available only on macOS,
+ // but there is no harm in always including it, HarfBuzz will
+ // ignore unavailable shapers.
+ const char*const pHbShapers[] = { "graphite2", "coretext_aat", "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_Int32 indexUtf16 = nCharPos;
+ sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&indexUtf16, 0);
+
+ if (u_getIntPropertyValue(aChar, UCHAR_GENERAL_CATEGORY) == U_NON_SPACING_MARK)
+ nGlyphFlags |= GlyphItemFlags::IS_DIACRITIC;
+
+ if (u_isUWhiteSpace(aChar))
+ nGlyphFlags |= GlyphItemFlags::IS_SPACING;
+
+ if (aSubRun.maScript == HB_SCRIPT_ARABIC &&
+ HB_DIRECTION_IS_BACKWARD(aSubRun.maDirection) &&
+ !(nGlyphFlags & GlyphItemFlags::IS_SPACING))
+ {
+ nGlyphFlags |= GlyphItemFlags::ALLOW_KASHIDA;
+ rArgs.mnFlags |= SalLayoutFlags::KashidaJustification;
+ }
+
+ DeviceCoordinate nAdvance, nXOffset, nYOffset;
+ if (aSubRun.maDirection == HB_DIRECTION_TTB)
+ {
+ nGlyphFlags |= GlyphItemFlags::IS_VERTICAL;
+
+ // We have glyph offsets that is relative to h origin now,
+ // add the origin back so it is relative to v origin.
+ hb_font_add_glyph_origin_for_direction(pHbFont,
+ nGlyphIndex,
+ HB_DIRECTION_TTB,
+ &pHbPositions[i].x_offset ,
+ &pHbPositions[i].y_offset );
+ nAdvance = -pHbPositions[i].y_advance;
+ nXOffset = -pHbPositions[i].y_offset;
+ nYOffset = -pHbPositions[i].x_offset;
+ }
+ else
+ {
+ nAdvance = pHbPositions[i].x_advance;
+ nXOffset = pHbPositions[i].x_offset;
+ nYOffset = -pHbPositions[i].y_offset;
+ }
+
+ nAdvance = std::lround(nAdvance * nXScale);
+ nXOffset = std::lround(nXOffset * nXScale);
+ nYOffset = std::lround(nYOffset * nYScale);
+
+ Point aNewPos(aCurrPos.X() + nXOffset, aCurrPos.Y() + nYOffset);
+ const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags,
+ nAdvance, nXOffset, &GetFont());
+ m_GlyphItems.Impl()->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.Impl()->mnFlags = rArgs.mnFlags;
+
+ return true;
+}
+
+void GenericSalLayout::GetCharWidths(DeviceCoordinate* pCharWidths) const
+{
+ const int nCharCount = mnEndCharPos - mnMinCharPos;
+
+ for (int i = 0; i < nCharCount; ++i)
+ pCharWidths[i] = 0;
+
+ for (auto const& aGlyphItem : *m_GlyphItems.Impl())
+ {
+ const int nIndex = aGlyphItem.charPos() - mnMinCharPos;
+ if (nIndex >= nCharCount)
+ continue;
+ pCharWidths[nIndex] += aGlyphItem.m_nNewWidth;
+ }
+}
+
+// A note on how Kashida justification is implemented (because it took me 5
+// years to figure it out):
+// The decision to insert Kashidas, where and how much is taken by Writer.
+// This decision is communicated to us in a very indirect way; by increasing
+// the width of the character after which Kashidas should be inserted by the
+// desired amount.
+//
+// Writer eventually calls IsKashidaPosValid() to check whether it can insert a
+// Kashida between two characters or not.
+//
+// Here we do:
+// - In LayoutText() set KashidaJustification flag based on text script.
+// - In ApplyDXArray():
+// * Check the above flag to decide whether to insert Kashidas or not.
+// * For any RTL glyph that has DX adjustment, insert enough Kashidas to
+// fill in the added space.
+
+void GenericSalLayout::ApplyDXArray(const ImplLayoutArgs& rArgs)
+{
+ if (rArgs.mpDXArray == nullptr)
+ return;
+
+ int nCharCount = mnEndCharPos - mnMinCharPos;
+ std::unique_ptr<DeviceCoordinate[]> const pOldCharWidths(new DeviceCoordinate[nCharCount]);
+ std::unique_ptr<DeviceCoordinate[]> const pNewCharWidths(new DeviceCoordinate[nCharCount]);
+
+ // Get the natural character widths (i.e. before applying DX adjustments).
+ GetCharWidths(pOldCharWidths.get());
+
+ // Calculate the character widths after DX adjustments.
+ for (int i = 0; i < nCharCount; ++i)
+ {
+ if (i == 0)
+ pNewCharWidths[i] = rArgs.mpDXArray[i];
+ else
+ pNewCharWidths[i] = rArgs.mpDXArray[i] - rArgs.mpDXArray[i - 1];
+ }
+
+ bool bKashidaJustify = false;
+ DeviceCoordinate nKashidaWidth = 0;
+ hb_codepoint_t nKashidaIndex = 0;
+ if (rArgs.mnFlags & SalLayoutFlags::KashidaJustification)
+ {
+ hb_font_t *pHbFont = GetFont().GetHbFont();
+ // Find Kashida glyph width and index.
+ if (hb_font_get_glyph(pHbFont, 0x0640, 0, &nKashidaIndex))
+ nKashidaWidth = GetFont().GetKashidaWidth();
+ bKashidaJustify = nKashidaWidth != 0;
+ }
+
+ // Map of Kashida insertion points (in the glyph items vector) and the
+ // requested width.
+ std::map<size_t, DeviceCoordinate> pKashidas;
+
+ // The accumulated difference in X position.
+ DeviceCoordinate nDelta = 0;
+
+ // Apply the DX adjustments to glyph positions and widths.
+ size_t i = 0;
+ while (i < m_GlyphItems.Impl()->size())
+ {
+ // Accumulate the width difference for all characters corresponding to
+ // this glyph.
+ int nCharPos = (*m_GlyphItems.Impl())[i].charPos() - mnMinCharPos;
+ DeviceCoordinate nDiff = 0;
+ for (int j = 0; j < (*m_GlyphItems.Impl())[i].charCount(); j++)
+ nDiff += pNewCharWidths[nCharPos + j] - pOldCharWidths[nCharPos + j];
+
+ if (!(*m_GlyphItems.Impl())[i].IsRTLGlyph())
+ {
+ // Adjust the width and position of the first (leftmost) glyph in
+ // the cluster.
+ (*m_GlyphItems.Impl())[i].m_nNewWidth += nDiff;
+ (*m_GlyphItems.Impl())[i].m_aLinearPos.AdjustX(nDelta);
+
+ // Adjust the position of the rest of the glyphs in the cluster.
+ while (++i < m_GlyphItems.Impl()->size())
+ {
+ if (!(*m_GlyphItems.Impl())[i].IsInCluster())
+ break;
+ (*m_GlyphItems.Impl())[i].m_aLinearPos.AdjustX(nDelta);
+ }
+ }
+ else if ((*m_GlyphItems.Impl())[i].IsInCluster())
+ {
+ // RTL glyph in the middle of the cluster, will be handled in the
+ // loop below.
+ i++;
+ }
+ else
+ {
+ // Adjust the width and position of the first (rightmost) glyph in
+ // the cluster.
+ // For RTL, we put all the adjustment to the left of the glyph.
+ (*m_GlyphItems.Impl())[i].m_nNewWidth += nDiff;
+ (*m_GlyphItems.Impl())[i].m_aLinearPos.AdjustX(nDelta + nDiff);
+
+ // Adjust the X position of all glyphs in the cluster.
+ size_t j = i;
+ while (j > 0)
+ {
+ --j;
+ if (!(*m_GlyphItems.Impl())[j].IsInCluster())
+ break;
+ (*m_GlyphItems.Impl())[j].m_aLinearPos.AdjustX(nDelta + nDiff);
+ }
+
+ // If this glyph is Kashida-justifiable, then mark this as a
+ // Kashida position. Since this must be a RTL glyph, we mark the
+ // last glyph in the cluster not the first as this would be the
+ // base glyph.
+ if (bKashidaJustify && (*m_GlyphItems.Impl())[i].AllowKashida() &&
+ nDiff > (*m_GlyphItems.Impl())[i].charCount()) // Rounding errors, 1 pixel per character!
+ {
+ pKashidas[i] = nDiff;
+ // Move any non-spacing marks attached to this cluster as well.
+ // Looping backward because this is RTL glyph.
+ while (j > 0)
+ {
+ if (!(*m_GlyphItems.Impl())[j].IsDiacritic())
+ break;
+ (*m_GlyphItems.Impl())[j--].m_aLinearPos.AdjustX(nDiff);
+ }
+ }
+ 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 glyphs belonging to the same
+ // character which is wrong since DX adjustments are character based).
+ nDelta += nDiff;
+ }
+
+ // Insert Kashida glyphs.
+ if (bKashidaJustify && !pKashidas.empty())
+ {
+ size_t nInserted = 0;
+ for (auto const& pKashida : pKashidas)
+ {
+ auto pGlyphIter = m_GlyphItems.Impl()->begin() + nInserted + pKashida.first;
+
+ // The total Kashida width.
+ DeviceCoordinate nTotalWidth = 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.
+ DeviceCoordinate nOverlap = 0;
+ DeviceCoordinate nShortfall = nTotalWidth - nKashidaWidth * nCopies;
+ if (nShortfall > 0)
+ {
+ ++nCopies;
+ DeviceCoordinate nExcess = nCopies * nKashidaWidth - nTotalWidth;
+ if (nExcess > 0)
+ nOverlap = nExcess / (nCopies - 1);
+ }
+
+ Point aPos(pGlyphIter->m_aLinearPos.getX() - nTotalWidth, 0);
+ int nCharPos = pGlyphIter->charPos();
+ GlyphItemFlags const nFlags = GlyphItemFlags::IS_IN_CLUSTER | GlyphItemFlags::IS_RTL_GLYPH;
+ while (nCopies--)
+ {
+ GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, nKashidaWidth, 0, &GetFont());
+ pGlyphIter = m_GlyphItems.Impl()->insert(pGlyphIter, aKashida);
+ aPos.AdjustX(nKashidaWidth );
+ aPos.AdjustX( -nOverlap );
+ ++pGlyphIter;
+ ++nInserted;
+ }
+ }
+ }
+}
+
+bool GenericSalLayout::IsKashidaPosValid(int nCharPos) const
+{
+ for (auto pIter = m_GlyphItems.Impl()->begin(); pIter != m_GlyphItems.Impl()->end(); ++pIter)
+ {
+ if (pIter->charPos() == nCharPos)
+ {
+ // The position is the first glyph, this would happen if we
+ // changed the text styling in the middle of a word. Since we don’t
+ // do ligatures across layout engine instances, this can’t be a
+ // ligature so it should be fine.
+ if (pIter == m_GlyphItems.Impl()->begin())
+ return true;
+
+ // If the character is not supported by this layout, return false
+ // so that fallback layouts would be checked for it.
+ if (pIter->glyphId() == 0)
+ break;
+
+ // Search backwards for previous glyph belonging to a different
+ // character. We are looking backwards because we are dealing with
+ // RTL glyphs, which will be in visual order.
+ for (auto pPrev = pIter - 1; pPrev != m_GlyphItems.Impl()->begin(); --pPrev)
+ {
+ if (pPrev->charPos() != nCharPos)
+ {
+ // Check if the found glyph belongs to the next character,
+ // otherwise the current glyph will be a ligature which is
+ // invalid kashida position.
+ if (pPrev->charPos() == (nCharPos + 1))
+ return true;
+ break;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/* 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 000000000..29234254b
--- /dev/null
+++ b/vcl/source/gdi/FileDefinitionWidgetDraw.cxx
@@ -0,0 +1,1080 @@
+/* -*- 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 <FileDefinitionWidgetDraw.hxx>
+#include <widgetdraw/WidgetDefinitionReader.hxx>
+
+#include <sal/config.h>
+#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>
+
+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(OUString const& 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(OString const& rValue, int nDefault)
+{
+ if (rValue.isEmpty())
+ return nDefault;
+ if (!comphelper::string::isdigitAsciiString(rValue))
+ return nDefault;
+ return rValue.toInt32();
+}
+
+bool getSettingValueBool(OString const& rValue, bool bDefault)
+{
+ if (rValue.isEmpty())
+ 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("online");
+#ifdef IOS
+ if (!m_pWidgetDefinition)
+ m_pWidgetDefinition = getWidgetDefinitionForTheme("ios");
+#endif
+
+ if (m_pWidgetDefinition)
+ {
+ 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:
+ 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;
+}
+
+namespace
+{
+void drawFromDrawCommands(gfx::DrawRoot const& rDrawRoot, SalGraphics& rGraphics, long nX, long nY,
+ long nWidth, 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));
+ rGraphics.DrawPolyPolygon(basegfx::B2DHomMatrix(),
+ basegfx::B2DPolyPolygon(aB2DPolygon),
+ 1.0 - rRectangle.mnOpacity, nullptr);
+ }
+ 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.SetTransparency(rStop.mfOpacity * (1.0f - rRectangle.mnOpacity));
+ aGradient.maStops.emplace_back(aColor, rStop.mfOffset);
+ }
+ rGraphics.DrawGradient(basegfx::B2DPolyPolygon(aB2DPolygon), aGradient);
+ }
+ }
+ if (rRectangle.mpStrokeColor)
+ {
+ rGraphics.SetLineColor(Color(*rRectangle.mpStrokeColor));
+ rGraphics.SetFillColor();
+ rGraphics.DrawPolyLine(basegfx::B2DHomMatrix(), aB2DPolygon,
+ 1.0 - rRectangle.mnOpacity, rRectangle.mnStrokeWidth,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND,
+ 0.0f, false, nullptr);
+ }
+ }
+ 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));
+ rGraphics.DrawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPolygon,
+ 1.0 - rPath.mnOpacity, nullptr);
+ }
+ if (rPath.mpStrokeColor)
+ {
+ rGraphics.SetLineColor(Color(*rPath.mpStrokeColor));
+ rGraphics.SetFillColor();
+ for (auto const& rPolygon : aPolyPolygon)
+ {
+ rGraphics.DrawPolyLine(basegfx::B2DHomMatrix(), rPolygon,
+ 1.0 - rPath.mnOpacity, rPath.mnStrokeWidth,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::Round,
+ css::drawing::LineCap_ROUND, 0.0f, false, nullptr);
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void munchDrawCommands(std::vector<std::shared_ptr<WidgetDrawAction>> const& rDrawActions,
+ SalGraphics& rGraphics, long nX, long nY, long nWidth, 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);
+ rGraphics.DrawPolyPolygon(basegfx::B2DHomMatrix(),
+ basegfx::B2DPolyPolygon(aB2DPolygon), 0.0f, nullptr);
+ rGraphics.SetLineColor(rWidgetDraw.maStrokeColor);
+ rGraphics.SetFillColor();
+ rGraphics.DrawPolyLine(
+ basegfx::B2DHomMatrix(), aB2DPolygon, 0.0f, rWidgetDraw.mnStrokeWidth,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false, nullptr);
+ }
+ 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) },
+ };
+
+ rGraphics.DrawPolyLine(
+ basegfx::B2DHomMatrix(), aB2DPolygon, 0.0f, rWidgetDraw.mnStrokeWidth,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false, nullptr);
+ }
+ 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)
+ {
+ rCacheImages.insert(std::make_pair(rCacheKey, aBitmap));
+ }
+ }
+ else
+ {
+ aBitmap = aIterator->second;
+ }
+
+ long nImageWidth = aBitmap.GetSizePixel().Width();
+ long nImageHeight = aBitmap.GetSizePixel().Height();
+ SalTwoRect aTR(0, 0, nImageWidth, nImageHeight, nX, nY, nImageWidth / nScaleFactor,
+ nImageHeight / nScaleFactor);
+ if (!!aBitmap)
+ {
+ const std::shared_ptr<SalBitmap> pSalBitmap
+ = aBitmap.GetBitmap().ImplGetSalBitmap();
+ if (aBitmap.IsAlpha())
+ {
+ const std::shared_ptr<SalBitmap> pSalBitmapAlpha
+ = aBitmap.GetAlpha().ImplGetSalBitmap();
+ rGraphics.DrawBitmap(aTR, *pSalBitmap, *pSalBitmapAlpha, nullptr);
+ }
+ else
+ {
+ rGraphics.DrawBitmap(aTR, *pSalBitmap, nullptr);
+ }
+ }
+ }
+ 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, long nX, long nY,
+ long nWidth, 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.getAntiAliasB2DDraw();
+ m_rGraphics.setAntiAliasB2DDraw(true);
+
+ long nWidth = rControlRegion.GetWidth() - 1;
+ long nHeight = rControlRegion.GetHeight() - 1;
+ long nX = rControlRegion.Left();
+ 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;
+
+ long nUpperX = pSpinVal->maUpperRect.Left();
+ long nUpperY = pSpinVal->maUpperRect.Top();
+ long nUpperWidth = pSpinVal->maUpperRect.GetWidth() - 1;
+ 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;
+
+ long nLowerX = pSpinVal->maLowerRect.Left();
+ long nLowerY = pSpinVal->maLowerRect.Top();
+ long nLowerWidth = pSpinVal->maLowerRect.GetWidth() - 1;
+ 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);
+ long nThumbX = pSliderValue->maThumbRect.Left();
+ long nThumbY = pSliderValue->maThumbRect.Top();
+ long nThumbWidth = pSliderValue->maThumbRect.GetWidth() - 1;
+ long nThumbHeight = pSliderValue->maThumbRect.GetHeight() - 1;
+
+ if (ePart == ControlPart::TrackHorzArea)
+ {
+ 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)
+ {
+ 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:
+ {
+ 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.setAntiAliasB2DDraw(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;
+
+ long nWidth = std::max(rBoundingControlRegion.GetWidth() + pPart->mnMarginWidth,
+ long(pPart->mnWidth));
+ long nHeight = std::max(rBoundingControlRegion.GetHeight() + pPart->mnMarginHeight,
+ 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);
+ aStyleSet.SetFontColor(pDefinitionStyle->maFontColor);
+
+ 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 000000000..e2f399f2d
--- /dev/null
+++ b/vcl/source/gdi/TypeSerializer.cxx
@@ -0,0 +1,419 @@
+/* -*- 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 <TypeSerializer.hxx>
+#include <tools/vcompat.hxx>
+#include <sal/log.hxx>
+#include <comphelper/fileformat.h>
+#include <vcl/gdimtf.hxx>
+#include <vcl/dibtools.hxx>
+
+TypeSerializer::TypeSerializer(SvStream& rStream)
+ : GenericTypeSerializer(rStream)
+{
+}
+
+void TypeSerializer::readGradient(Gradient& rGradient)
+{
+ VersionCompat aCompat(mrStream, StreamMode::READ);
+
+ sal_uInt16 nStyle;
+ Color aStartColor;
+ Color aEndColor;
+ sal_uInt16 nAngle;
+ sal_uInt16 nBorder;
+ sal_uInt16 nOffsetX;
+ sal_uInt16 nOffsetY;
+ sal_uInt16 nIntensityStart;
+ sal_uInt16 nIntensityEnd;
+ sal_uInt16 nStepCount;
+
+ 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<GradientStyle>(nStyle));
+ rGradient.SetStartColor(aStartColor);
+ rGradient.SetEndColor(aEndColor);
+ rGradient.SetAngle(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)
+{
+ VersionCompat aCompat(mrStream, StreamMode::WRITE, 1);
+
+ mrStream.WriteUInt16(static_cast<sal_uInt16>(rGradient.GetStyle()));
+ writeColor(rGradient.GetStartColor());
+ writeColor(rGradient.GetEndColor());
+ mrStream.WriteUInt16(rGradient.GetAngle());
+ 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;
+
+ {
+ VersionCompat aCompat(mrStream, StreamMode::READ);
+
+ // Version 1
+ mrStream.ReadUInt16(nType);
+ mrStream.ReadUInt32(nDataSize);
+ mrStream.ReadUInt32(nUserId);
+
+ if (aCompat.GetVersion() >= 2)
+ {
+ readSize(aSize);
+ ReadMapMode(mrStream, aMapMode);
+ bMapAndSizeValid = true;
+ }
+ }
+
+ auto nRemainingData = mrStream.remainingSize();
+ if (nDataSize > nRemainingData)
+ {
+ SAL_WARN("vcl", "graphic link stream is smaller than requested size");
+ nDataSize = nRemainingData;
+ }
+
+ std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nDataSize]);
+ mrStream.ReadBytes(pBuffer.get(), nDataSize);
+
+ rGfxLink = GfxLink(std::move(pBuffer), nDataSize, static_cast<GfxLinkType>(nType));
+ rGfxLink.SetUserId(nUserId);
+
+ if (bMapAndSizeValid)
+ {
+ rGfxLink.SetPrefSize(aSize);
+ rGfxLink.SetPrefMapMode(aMapMode);
+ }
+}
+
+void TypeSerializer::writeGfxLink(const GfxLink& rGfxLink)
+{
+ {
+ VersionCompat aCompat(mrStream, StreamMode::WRITE, 2);
+
+ // Version 1
+ mrStream.WriteUInt16(sal_uInt16(rGfxLink.GetType()));
+ mrStream.WriteUInt32(rGfxLink.GetDataSize());
+ mrStream.WriteUInt32(rGfxLink.GetUserId());
+
+ // Version 2
+ writeSize(rGfxLink.GetPrefSize());
+ WriteMapMode(mrStream, 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')
+
+constexpr sal_uInt32 constSvgMagic = createMagic('s', 'v', 'g', '0');
+constexpr sal_uInt32 constWmfMagic = createMagic('w', 'm', 'f', '0');
+constexpr sal_uInt32 constEmfMagic = createMagic('e', 'm', 'f', '0');
+constexpr sal_uInt32 constPdfMagic = createMagic('p', 'd', 'f', '0');
+
+} // end anonymous namespace
+
+void TypeSerializer::readGraphic(Graphic& rGraphic)
+{
+ if (mrStream.GetError())
+ return;
+
+ const sal_uLong nInitialStreamPosition = mrStream.Tell();
+ sal_uInt32 nType;
+
+ // read Id
+ mrStream.ReadUInt32(nType);
+
+ // if there is no more data, avoid further expensive
+ // reading which will create VDevs and other stuff, just to
+ // read nothing. CAUTION: Eof is only true AFTER reading another
+ // byte, a speciality of SvMemoryStream (!)
+ if (!mrStream.good())
+ return;
+
+ if (NATIVE_FORMAT_50 == nType)
+ {
+ Graphic aGraphic;
+ GfxLink aLink;
+
+ // read compat info, destructor writes stuff into the header
+ {
+ VersionCompat aCompat(mrStream, StreamMode::READ);
+ }
+
+ 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;
+ sal_uInt64 nBeginPoisition = mrStream.Tell();
+
+ mrStream.ReadUInt32(nMagic1);
+ mrStream.ReadUInt32(nMagic2);
+ mrStream.Seek(nBeginPoisition);
+
+ 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();
+ ReadGDIMetaFile(mrStream, 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)
+ {
+ VectorGraphicDataArray aData(nLength);
+
+ mrStream.ReadBytes(aData.getArray(), nLength);
+ OUString aPath = mrStream.ReadUniOrByteString(mrStream.GetStreamCharSet());
+
+ 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>(aData, aPath, 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
+ {
+ VersionCompat aCompat(mrStream, StreamMode::WRITE, 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->getVectorGraphicDataType())
+ {
+ 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->getVectorGraphicDataArrayLength();
+ mrStream.WriteUInt32(nSize);
+ mrStream.WriteBytes(
+ pVectorGraphicData->getVectorGraphicDataArray().getConstArray(), nSize);
+ mrStream.WriteUniOrByteString(pVectorGraphicData->getPath(),
+ mrStream.GetStreamCharSet());
+ }
+ else if (aGraphic.IsAnimated())
+ {
+ WriteAnimation(mrStream, aGraphic.GetAnimation());
+ }
+ else
+ {
+ WriteDIBBitmapEx(aGraphic.GetBitmapEx(), mrStream);
+ }
+ }
+ break;
+
+ default:
+ {
+ if (aGraphic.IsSupportedGraphic())
+ WriteGDIMetaFile(mrStream, rGraphic.GetGDIMetaFile());
+ }
+ break;
+ }
+ mrStream.SetEndian(nOldFormat);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/VerticalOrientationData.cxx b/vcl/source/gdi/VerticalOrientationData.cxx
new file mode 100644
index 000000000..1016b3656
--- /dev/null
+++ b/vcl/source/gdi/VerticalOrientationData.cxx
@@ -0,0 +1,78 @@
+
+/*
+ * 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/.
+ */
+
+/*
+ * Derived from the Unicode Character Database by genVerticalOrientationData.pl
+ *
+ * For Unicode terms of use, see http://www.unicode.org/terms_of_use.html
+ */
+
+/*
+ * Created on Wed Nov 9 21:29:02 2016 from UCD data files with version info:
+ *
+
+
+# VerticalOrientation-17.txt
+# Date: 2016-10-20, 07:00:00 GMT [EM, KI, LI]
+
+ *
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
+
+#define kVerticalOrientationMaxPlane 16
+#define kVerticalOrientationIndexBits 9
+#define kVerticalOrientationCharBits 7
+static const uint8_t sVerticalOrientationPlanes[16] = {1,2,2,3,3,3,3,3,3,3,3,3,3,3,2,2};
+
+static const uint8_t sVerticalOrientationPages[4][512] = {
+ {0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,4,3,3,3,3,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,7,8,9,10,0,11,12,13,3,0,14,15,3,16,17,0,0,0,0,0,0,18,19,0,0,0,0,0,3,3,3,20,21,22,23,3,3,24,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,25,0,0,0,0,0,0,0,0,26,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,27,0,28,29},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,0,0,3,0,0,0,0,0,0,0,0,0,3,3,3,3,3,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,32,3,3,3,3,3,3,3,3,3,3,3,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0},
+ {3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,33},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+};
+
+static const uint8_t sVerticalOrientationValues[34][128] = {
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,0,1,1,1,1,0,0,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,0,1,1,1,0,0,1,0,0,1,1,1,1,1,1,0,0,0,0,0,0,1,0,1,0,1,0,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,3,3,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,2,2,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,2,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,2,0,2,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,2,2,0,0,0,0,2,2,0,0,0,3,2,0,2,0,2,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,2,2,0,0,0,0,0,3,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2},
+ {2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,2,2,2,0,0,0,0,0,1,3,3,3,3,3,3,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,2,0,0,0,0,0,0,3,3,0,0,2,1,2,0,0,0,0,0,0,0,0,0,0,0,3,3,1,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,3,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,1},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
+ {2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1}
+};
+/*
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
diff --git a/vcl/source/gdi/WidgetDefinition.cxx b/vcl/source/gdi/WidgetDefinition.cxx
new file mode 100644
index 000000000..3c93f1ac5
--- /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 <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";
+
+ 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";
+ else if (rTabItemValue.isLeftAligned() || rTabItemValue.isFirst())
+ sExtra = "first";
+ else if (rTabItemValue.isRightAligned() || rTabItemValue.isLast())
+ sExtra = "last";
+ else
+ sExtra = "middle";
+ }
+ break;
+ case ControlType::ListHeader:
+ {
+ if (ePart == ControlPart::Arrow)
+ {
+ if (rValue.getNumericVal() == 1)
+ sExtra = "down";
+ else
+ sExtra = "up";
+ }
+ }
+ break;
+ case ControlType::Pushbutton:
+ {
+ auto const& rPushButtonValue = static_cast<PushButtonValue const&>(rValue);
+ if (rPushButtonValue.mbIsAction)
+ sExtra = "action";
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (state->msExtra != "any" && state->msExtra != sExtra)
+ {
+ bAdd = false;
+ }
+
+ if (bAdd)
+ aStatesToAdd.push_back(state);
+ }
+
+ return aStatesToAdd;
+}
+
+WidgetDefinitionState::WidgetDefinitionState(OString const& sEnabled, OString const& sFocused,
+ OString const& sPressed, OString const& sRollover,
+ OString const& sDefault, OString const& sSelected,
+ OString const& sButtonValue, OString const& sExtra)
+ : msEnabled(sEnabled)
+ , msFocused(sFocused)
+ , msPressed(sPressed)
+ , msRollover(sRollover)
+ , msDefault(sDefault)
+ , msSelected(sSelected)
+ , msButtonValue(sButtonValue)
+ , msExtra(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 000000000..7d3fb7c4c
--- /dev/null
+++ b/vcl/source/gdi/WidgetDefinitionReader.cxx
@@ -0,0 +1,494 @@
+/* -*- 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 <widgetdraw/WidgetDefinitionReader.hxx>
+
+#include <sal/config.h>
+#include <osl/file.hxx>
+#include <tools/stream.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";
+ return rInputString;
+}
+
+ControlPart xmlStringToControlPart(OString const& sPart)
+{
+ if (sPart.equalsIgnoreAsciiCase("NONE"))
+ return ControlPart::NONE;
+ else if (sPart.equalsIgnoreAsciiCase("Entire"))
+ return ControlPart::Entire;
+ else if (sPart.equalsIgnoreAsciiCase("ListboxWindow"))
+ return ControlPart::ListboxWindow;
+ else if (sPart.equalsIgnoreAsciiCase("Button"))
+ return ControlPart::Button;
+ else if (sPart.equalsIgnoreAsciiCase("ButtonUp"))
+ return ControlPart::ButtonUp;
+ else if (sPart.equalsIgnoreAsciiCase("ButtonDown"))
+ return ControlPart::ButtonDown;
+ else if (sPart.equalsIgnoreAsciiCase("ButtonLeft"))
+ return ControlPart::ButtonLeft;
+ else if (sPart.equalsIgnoreAsciiCase("ButtonRight"))
+ return ControlPart::ButtonRight;
+ else if (sPart.equalsIgnoreAsciiCase("AllButtons"))
+ return ControlPart::AllButtons;
+ else if (sPart.equalsIgnoreAsciiCase("SeparatorHorz"))
+ return ControlPart::SeparatorHorz;
+ else if (sPart.equalsIgnoreAsciiCase("SeparatorVert"))
+ return ControlPart::SeparatorVert;
+ else if (sPart.equalsIgnoreAsciiCase("TrackHorzLeft"))
+ return ControlPart::TrackHorzLeft;
+ else if (sPart.equalsIgnoreAsciiCase("TrackVertUpper"))
+ return ControlPart::TrackVertUpper;
+ else if (sPart.equalsIgnoreAsciiCase("TrackHorzRight"))
+ return ControlPart::TrackHorzRight;
+ else if (sPart.equalsIgnoreAsciiCase("TrackVertLower"))
+ return ControlPart::TrackVertLower;
+ else if (sPart.equalsIgnoreAsciiCase("TrackHorzArea"))
+ return ControlPart::TrackHorzArea;
+ else if (sPart.equalsIgnoreAsciiCase("TrackVertArea"))
+ return ControlPart::TrackVertArea;
+ else if (sPart.equalsIgnoreAsciiCase("Arrow"))
+ return ControlPart::Arrow;
+ else if (sPart.equalsIgnoreAsciiCase("ThumbHorz"))
+ return ControlPart::ThumbHorz;
+ else if (sPart.equalsIgnoreAsciiCase("ThumbVert"))
+ return ControlPart::ThumbVert;
+ else if (sPart.equalsIgnoreAsciiCase("MenuItem"))
+ return ControlPart::MenuItem;
+ else if (sPart.equalsIgnoreAsciiCase("MenuItemCheckMark"))
+ return ControlPart::MenuItemCheckMark;
+ else if (sPart.equalsIgnoreAsciiCase("MenuItemRadioMark"))
+ return ControlPart::MenuItemRadioMark;
+ else if (sPart.equalsIgnoreAsciiCase("Separator"))
+ return ControlPart::Separator;
+ else if (sPart.equalsIgnoreAsciiCase("SubmenuArrow"))
+ return ControlPart::SubmenuArrow;
+ else if (sPart.equalsIgnoreAsciiCase("SubEdit"))
+ return ControlPart::SubEdit;
+ else if (sPart.equalsIgnoreAsciiCase("DrawBackgroundHorz"))
+ return ControlPart::DrawBackgroundHorz;
+ else if (sPart.equalsIgnoreAsciiCase("DrawBackgroundVert"))
+ return ControlPart::DrawBackgroundVert;
+ else if (sPart.equalsIgnoreAsciiCase("TabsDrawRtl"))
+ return ControlPart::TabsDrawRtl;
+ else if (sPart.equalsIgnoreAsciiCase("HasBackgroundTexture"))
+ return ControlPart::HasBackgroundTexture;
+ else if (sPart.equalsIgnoreAsciiCase("HasThreeButtons"))
+ return ControlPart::HasThreeButtons;
+ else if (sPart.equalsIgnoreAsciiCase("BackgroundWindow"))
+ return ControlPart::BackgroundWindow;
+ else if (sPart.equalsIgnoreAsciiCase("BackgroundDialog"))
+ return ControlPart::BackgroundDialog;
+ else if (sPart.equalsIgnoreAsciiCase("Border"))
+ return ControlPart::Border;
+ else if (sPart.equalsIgnoreAsciiCase("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 },
+ { "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 const& rDefinitionFile,
+ OUString const& rResourcePath)
+ : m_rDefinitionFile(rDefinitionFile)
+ , m_rResourcePath(rResourcePath)
+{
+}
+
+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"), aStrokeColor);
+ Color aFillColor;
+ readColor(rWalker.attribute("fill"), aFillColor);
+ OString sStrokeWidth = rWalker.attribute("stroke-width");
+ sal_Int32 nStrokeWidth = -1;
+ if (!sStrokeWidth.isEmpty())
+ nStrokeWidth = sStrokeWidth.toInt32();
+
+ sal_Int32 nRx = -1;
+ OString sRx = rWalker.attribute("rx");
+ if (!sRx.isEmpty())
+ nRx = sRx.toInt32();
+
+ sal_Int32 nRy = -1;
+ OString sRy = rWalker.attribute("ry");
+ if (!sRy.isEmpty())
+ nRy = sRy.toInt32();
+
+ OString sX1 = rWalker.attribute("x1");
+ float fX1 = sX1.isEmpty() ? 0.0 : sX1.toFloat();
+
+ OString sY1 = rWalker.attribute("y1");
+ float fY1 = sY1.isEmpty() ? 0.0 : sY1.toFloat();
+
+ OString sX2 = rWalker.attribute("x2");
+ float fX2 = sX2.isEmpty() ? 1.0 : sX2.toFloat();
+
+ OString sY2 = rWalker.attribute("y2");
+ 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"), aStrokeColor);
+
+ OString sStrokeWidth = rWalker.attribute("stroke-width");
+ sal_Int32 nStrokeWidth = -1;
+ if (!sStrokeWidth.isEmpty())
+ nStrokeWidth = sStrokeWidth.toInt32();
+
+ OString sX1 = rWalker.attribute("x1");
+ float fX1 = sX1.isEmpty() ? -1.0 : sX1.toFloat();
+
+ OString sY1 = rWalker.attribute("y1");
+ float fY1 = sY1.isEmpty() ? -1.0 : sY1.toFloat();
+
+ OString sX2 = rWalker.attribute("x2");
+ float fX2 = sX2.isEmpty() ? -1.0 : sX2.toFloat();
+
+ OString sY2 = rWalker.attribute("y2");
+ 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");
+ rpState->addDrawImage(m_rResourcePath
+ + OStringToOUString(sSource, RTL_TEXTENCODING_UTF8));
+ }
+ else if (rWalker.name() == "external")
+ {
+ OString sSource = rWalker.attribute("source");
+ 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");
+ ControlPart ePart = xmlStringToControlPart(sPart);
+
+ std::shared_ptr<WidgetDefinitionPart> pPart = std::make_shared<WidgetDefinitionPart>();
+
+ OString sWidth = rWalker.attribute("width");
+ if (!sWidth.isEmpty())
+ {
+ sal_Int32 nWidth = sWidth.isEmpty() ? 0 : sWidth.toInt32();
+ pPart->mnWidth = nWidth;
+ }
+
+ OString sHeight = rWalker.attribute("height");
+ if (!sHeight.isEmpty())
+ {
+ sal_Int32 nHeight = sHeight.isEmpty() ? 0 : sHeight.toInt32();
+ pPart->mnHeight = nHeight;
+ }
+
+ OString sMarginHeight = rWalker.attribute("margin-height");
+ if (!sMarginHeight.isEmpty())
+ {
+ sal_Int32 nMarginHeight = sMarginHeight.isEmpty() ? 0 : sMarginHeight.toInt32();
+ pPart->mnMarginHeight = nMarginHeight;
+ }
+
+ OString sMarginWidth = rWalker.attribute("margin-width");
+ if (!sMarginWidth.isEmpty())
+ {
+ sal_Int32 nMarginWidth = sMarginWidth.isEmpty() ? 0 : sMarginWidth.toInt32();
+ pPart->mnMarginWidth = nMarginWidth;
+ }
+
+ OString sOrientation = rWalker.attribute("orientation");
+ 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"));
+ OString sFocused = getValueOrAny(rWalker.attribute("focused"));
+ OString sPressed = getValueOrAny(rWalker.attribute("pressed"));
+ OString sRollover = getValueOrAny(rWalker.attribute("rollover"));
+ OString sDefault = getValueOrAny(rWalker.attribute("default"));
+ OString sSelected = getValueOrAny(rWalker.attribute("selected"));
+ OString sButtonValue = getValueOrAny(rWalker.attribute("button-value"));
+ OString sExtra = getValueOrAny(rWalker.attribute("extra"));
+
+ 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 },
+ { "fontColor", &pStyle->maFontColor },
+ };
+
+ 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"), *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"), *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/alpha.cxx b/vcl/source/gdi/alpha.cxx
new file mode 100644
index 000000000..3fa43c8ea
--- /dev/null
+++ b/vcl/source/gdi/alpha.cxx
@@ -0,0 +1,176 @@
+/* -*- 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/bitmapaccess.hxx>
+#include <tools/color.hxx>
+#include <vcl/alpha.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <sal/log.hxx>
+
+AlphaMask::AlphaMask() = default;
+
+AlphaMask::AlphaMask( const Bitmap& rBitmap ) :
+ Bitmap( rBitmap )
+{
+ if( !!rBitmap )
+ Convert( BmpConversion::N8BitNoConversion );
+}
+
+AlphaMask::AlphaMask( const AlphaMask& ) = default;
+
+AlphaMask::AlphaMask( AlphaMask&& ) = default;
+
+AlphaMask::AlphaMask( const Size& rSizePixel, const sal_uInt8* pEraseTransparency ) :
+ Bitmap( rSizePixel, 8, &Bitmap::GetGreyPalette( 256 ) )
+{
+ if( pEraseTransparency )
+ Bitmap::Erase( Color( *pEraseTransparency, *pEraseTransparency, *pEraseTransparency ) );
+}
+
+AlphaMask::~AlphaMask() = default;
+
+AlphaMask& AlphaMask::operator=( const Bitmap& rBitmap )
+{
+ *static_cast<Bitmap*>(this) = rBitmap;
+
+ if( !!rBitmap )
+ Convert( BmpConversion::N8BitNoConversion );
+
+ return *this;
+}
+
+const Bitmap& AlphaMask::ImplGetBitmap() const
+{
+ return *this;
+}
+
+void AlphaMask::ImplSetBitmap( const Bitmap& rBitmap )
+{
+ SAL_WARN_IF( 8 != rBitmap.GetBitCount(), "vcl.gdi", "Bitmap should be 8bpp, not " << rBitmap.GetBitCount() << "bpp" );
+ SAL_WARN_IF( !rBitmap.HasGreyPalette8Bit(), "vcl.gdi", "Bitmap isn't greyscale" );
+ *static_cast<Bitmap*>(this) = rBitmap;
+}
+
+Bitmap const & AlphaMask::GetBitmap() const
+{
+ return ImplGetBitmap();
+}
+
+void AlphaMask::Erase( sal_uInt8 cTransparency )
+{
+ Bitmap::Erase( Color( cTransparency, cTransparency, cTransparency ) );
+}
+
+void AlphaMask::Replace( const Bitmap& rMask, sal_uInt8 cReplaceTransparency )
+{
+ Bitmap::ScopedReadAccess pMaskAcc( const_cast<Bitmap&>(rMask) );
+ AlphaScopedWriteAccess pAcc(*this);
+
+ if( pMaskAcc && pAcc )
+ {
+ const BitmapColor aReplace( cReplaceTransparency );
+ const long nWidth = std::min( pMaskAcc->Width(), pAcc->Width() );
+ const long nHeight = std::min( pMaskAcc->Height(), pAcc->Height() );
+ const BitmapColor aMaskWhite( pMaskAcc->GetBestMatchingColor( COL_WHITE ) );
+
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ Scanline pScanlineMask = pMaskAcc->GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ if( pMaskAcc->GetPixelFromData( pScanlineMask, nX ) == aMaskWhite )
+ pAcc->SetPixelOnData( pScanline, nX, aReplace );
+ }
+ }
+}
+
+void AlphaMask::Replace( sal_uInt8 cSearchTransparency, sal_uInt8 cReplaceTransparency )
+{
+ AlphaScopedWriteAccess pAcc(*this);
+
+ if( pAcc && pAcc->GetBitCount() == 8 )
+ {
+ const long nWidth = pAcc->Width(), nHeight = pAcc->Height();
+
+ if( pAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal )
+ {
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScan = pAcc->GetScanline( nY );
+
+ for( long nX = 0; nX < nWidth; nX++, pScan++ )
+ {
+ if( *pScan == cSearchTransparency )
+ *pScan = cReplaceTransparency;
+ }
+ }
+ }
+ else
+ {
+ BitmapColor aReplace( cReplaceTransparency );
+
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ if( pAcc->GetIndexFromData( pScanline, nX ) == cSearchTransparency )
+ pAcc->SetPixelOnData( pScanline, nX, aReplace );
+ }
+ }
+ }
+ }
+}
+
+void AlphaMask::BlendWith(const Bitmap& rOther)
+{
+ AlphaMask aOther(rOther); // to 8 bits
+ Bitmap::ScopedReadAccess pOtherAcc(aOther);
+ AlphaScopedWriteAccess pAcc(*this);
+ if (pOtherAcc && pAcc && pOtherAcc->GetBitCount() == 8 && pAcc->GetBitCount() == 8)
+ {
+ const long nHeight = std::min(pOtherAcc->Height(), pAcc->Height());
+ const long nWidth = std::min(pOtherAcc->Width(), pAcc->Width());
+ for (long y = 0; y < nHeight; ++y)
+ {
+ Scanline scanline = pAcc->GetScanline( y );
+ ConstScanline otherScanline = pOtherAcc->GetScanline( y );
+ for (long x = 0; x < nWidth; ++x)
+ {
+ // Use sal_uInt16 for following multiplication
+ const sal_uInt16 nGrey1 = *scanline;
+ const sal_uInt16 nGrey2 = *otherScanline;
+ *scanline = static_cast<sal_uInt8>(nGrey1 + nGrey2 - nGrey1 * nGrey2 / 255);
+ ++scanline;
+ ++otherScanline;
+ }
+ }
+ }
+}
+
+void AlphaMask::ReleaseAccess( BitmapReadAccess* pAccess )
+{
+ if( pAccess )
+ {
+ Bitmap::ReleaseAccess( pAccess );
+ Convert( BmpConversion::N8BitNoConversion );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/bitmap3.cxx b/vcl/source/gdi/bitmap3.cxx
new file mode 100644
index 000000000..ec80b03c6
--- /dev/null
+++ b/vcl/source/gdi/bitmap3.cxx
@@ -0,0 +1,1146 @@
+/* -*- 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 <math.h>
+
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/bitmap.hxx>
+#include <config_features.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <tools/helpers.hxx>
+#if HAVE_FEATURE_OPENGL
+#include <vcl/opengl/OpenGLHelper.hxx>
+#endif
+#if HAVE_FEATURE_SKIA
+#include <vcl/skia/SkiaHelper.hxx>
+#endif
+#include <vcl/BitmapMonochromeFilter.hxx>
+
+#include <BitmapScaleSuperFilter.hxx>
+#include <BitmapScaleConvolutionFilter.hxx>
+#include <BitmapFastScaleFilter.hxx>
+#include <BitmapInterpolateScaleFilter.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <bitmap/impoctree.hxx>
+#include <bitmap/Octree.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+#include <salbmp.hxx>
+
+#include "impvect.hxx"
+
+#include <memory>
+
+#define GAMMA( _def_cVal, _def_InvGamma ) (static_cast<sal_uInt8>(MinMax(FRound(pow( _def_cVal/255.0,_def_InvGamma)*255.0),0,255)))
+
+#define CALC_ERRORS \
+ nTemp = p1T[nX++] >> 12; \
+ nBErr = MinMax( nTemp, 0, 255 ); \
+ nBErr = nBErr - FloydIndexMap[ nBC = FloydMap[nBErr] ]; \
+ nTemp = p1T[nX++] >> 12; \
+ nGErr = MinMax( nTemp, 0, 255 ); \
+ nGErr = nGErr - FloydIndexMap[ nGC = FloydMap[nGErr] ]; \
+ nTemp = p1T[nX] >> 12; \
+ nRErr = MinMax( nTemp, 0, 255 ); \
+ nRErr = nRErr - FloydIndexMap[ nRC = FloydMap[nRErr] ];
+
+#define CALC_TABLES3 \
+ p2T[nX++] += FloydError3[nBErr]; \
+ p2T[nX++] += FloydError3[nGErr]; \
+ p2T[nX++] += FloydError3[nRErr];
+
+#define CALC_TABLES5 \
+ p2T[nX++] += FloydError5[nBErr]; \
+ p2T[nX++] += FloydError5[nGErr]; \
+ p2T[nX++] += FloydError5[nRErr];
+
+#define CALC_TABLES7 \
+ p1T[++nX] += FloydError7[nBErr]; \
+ p2T[nX++] += FloydError1[nBErr]; \
+ p1T[nX] += FloydError7[nGErr]; \
+ p2T[nX++] += FloydError1[nGErr]; \
+ p1T[nX] += FloydError7[nRErr]; \
+ p2T[nX] += FloydError1[nRErr];
+
+const extern sal_uLong nVCLRLut[ 6 ] = { 16, 17, 18, 19, 20, 21 };
+const extern sal_uLong nVCLGLut[ 6 ] = { 0, 6, 12, 18, 24, 30 };
+const extern sal_uLong nVCLBLut[ 6 ] = { 0, 36, 72, 108, 144, 180 };
+
+const extern sal_uLong nVCLDitherLut[ 256 ] =
+{
+ 0, 49152, 12288, 61440, 3072, 52224, 15360, 64512, 768, 49920, 13056,
+ 62208, 3840, 52992, 16128, 65280, 32768, 16384, 45056, 28672, 35840, 19456,
+ 48128, 31744, 33536, 17152, 45824, 29440, 36608, 20224, 48896, 32512, 8192,
+ 57344, 4096, 53248, 11264, 60416, 7168, 56320, 8960, 58112, 4864, 54016,
+ 12032, 61184, 7936, 57088, 40960, 24576, 36864, 20480, 44032, 27648, 39936,
+ 23552, 41728, 25344, 37632, 21248, 44800, 28416, 40704, 24320, 2048, 51200,
+ 14336, 63488, 1024, 50176, 13312, 62464, 2816, 51968, 15104, 64256, 1792,
+ 50944, 14080, 63232, 34816, 18432, 47104, 30720, 33792, 17408, 46080, 29696,
+ 35584, 19200, 47872, 31488, 34560, 18176, 46848, 30464, 10240, 59392, 6144,
+ 55296, 9216, 58368, 5120, 54272, 11008, 60160, 6912, 56064, 9984, 59136,
+ 5888, 55040, 43008, 26624, 38912, 22528, 41984, 25600, 37888, 21504, 43776,
+ 27392, 39680, 23296, 42752, 26368, 38656, 22272, 512, 49664, 12800, 61952,
+ 3584, 52736, 15872, 65024, 256, 49408, 12544, 61696, 3328, 52480, 15616,
+ 64768, 33280, 16896, 45568, 29184, 36352, 19968, 48640, 32256, 33024, 16640,
+ 45312, 28928, 36096, 19712, 48384, 32000, 8704, 57856, 4608, 53760, 11776,
+ 60928, 7680, 56832, 8448, 57600, 4352, 53504, 11520, 60672, 7424, 56576,
+ 41472, 25088, 37376, 20992, 44544, 28160, 40448, 24064, 41216, 24832, 37120,
+ 20736, 44288, 27904, 40192, 23808, 2560, 51712, 14848, 64000, 1536, 50688,
+ 13824, 62976, 2304, 51456, 14592, 63744, 1280, 50432, 13568, 62720, 35328,
+ 18944, 47616, 31232, 34304, 17920, 46592, 30208, 35072, 18688, 47360, 30976,
+ 34048, 17664, 46336, 29952, 10752, 59904, 6656, 55808, 9728, 58880, 5632,
+ 54784, 10496, 59648, 6400, 55552, 9472, 58624, 5376, 54528, 43520, 27136,
+ 39424, 23040, 42496, 26112, 38400, 22016, 43264, 26880, 39168, 22784, 42240,
+ 25856, 38144, 21760
+};
+
+const extern sal_uLong nVCLLut[ 256 ] =
+{
+ 0, 1286, 2572, 3858, 5144, 6430, 7716, 9002,
+ 10288, 11574, 12860, 14146, 15432, 16718, 18004, 19290,
+ 20576, 21862, 23148, 24434, 25720, 27006, 28292, 29578,
+ 30864, 32150, 33436, 34722, 36008, 37294, 38580, 39866,
+ 41152, 42438, 43724, 45010, 46296, 47582, 48868, 50154,
+ 51440, 52726, 54012, 55298, 56584, 57870, 59156, 60442,
+ 61728, 63014, 64300, 65586, 66872, 68158, 69444, 70730,
+ 72016, 73302, 74588, 75874, 77160, 78446, 79732, 81018,
+ 82304, 83590, 84876, 86162, 87448, 88734, 90020, 91306,
+ 92592, 93878, 95164, 96450, 97736, 99022,100308,101594,
+ 102880,104166,105452,106738,108024,109310,110596,111882,
+ 113168,114454,115740,117026,118312,119598,120884,122170,
+ 123456,124742,126028,127314,128600,129886,131172,132458,
+ 133744,135030,136316,137602,138888,140174,141460,142746,
+ 144032,145318,146604,147890,149176,150462,151748,153034,
+ 154320,155606,156892,158178,159464,160750,162036,163322,
+ 164608,165894,167180,168466,169752,171038,172324,173610,
+ 174896,176182,177468,178754,180040,181326,182612,183898,
+ 185184,186470,187756,189042,190328,191614,192900,194186,
+ 195472,196758,198044,199330,200616,201902,203188,204474,
+ 205760,207046,208332,209618,210904,212190,213476,214762,
+ 216048,217334,218620,219906,221192,222478,223764,225050,
+ 226336,227622,228908,230194,231480,232766,234052,235338,
+ 236624,237910,239196,240482,241768,243054,244340,245626,
+ 246912,248198,249484,250770,252056,253342,254628,255914,
+ 257200,258486,259772,261058,262344,263630,264916,266202,
+ 267488,268774,270060,271346,272632,273918,275204,276490,
+ 277776,279062,280348,281634,282920,284206,285492,286778,
+ 288064,289350,290636,291922,293208,294494,295780,297066,
+ 298352,299638,300924,302210,303496,304782,306068,307354,
+ 308640,309926,311212,312498,313784,315070,316356,317642,
+ 318928,320214,321500,322786,324072,325358,326644,327930
+};
+
+const long FloydMap[256] =
+{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+};
+
+const long FloydError1[61] =
+{
+ -7680, -7424, -7168, -6912, -6656, -6400, -6144,
+ -5888, -5632, -5376, -5120, -4864, -4608, -4352,
+ -4096, -3840, -3584, -3328, -3072, -2816, -2560,
+ -2304, -2048, -1792, -1536, -1280, -1024, -768,
+ -512, -256, 0, 256, 512, 768, 1024, 1280, 1536,
+ 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584,
+ 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632,
+ 5888, 6144, 6400, 6656, 6912, 7168, 7424, 7680
+};
+
+const long FloydError3[61] =
+{
+ -23040, -22272, -21504, -20736, -19968, -19200,
+ -18432, -17664, -16896, -16128, -15360, -14592,
+ -13824, -13056, -12288, -11520, -10752, -9984,
+ -9216, -8448, -7680, -6912, -6144, -5376, -4608,
+ -3840, -3072, -2304, -1536, -768, 0, 768, 1536,
+ 2304, 3072, 3840, 4608, 5376, 6144, 6912, 7680,
+ 8448, 9216, 9984, 10752, 11520, 12288, 13056,
+ 13824, 14592, 15360, 16128, 16896, 17664, 18432,
+ 19200, 19968, 20736, 21504, 22272, 23040
+};
+
+const long FloydError5[61] =
+{
+ -38400, -37120, -35840, -34560, -33280, -32000,
+ -30720, -29440, -28160, -26880, -25600, -24320,
+ -23040, -21760, -20480, -19200, -17920, -16640,
+ -15360, -14080, -12800, -11520, -10240, -8960,
+ -7680, -6400, -5120, -3840, -2560, -1280, 0,
+ 1280, 2560, 3840, 5120, 6400, 7680, 8960, 10240,
+ 11520, 12800, 14080, 15360, 16640, 17920, 19200,
+ 20480, 21760, 23040, 24320, 25600, 26880, 28160,
+ 29440, 30720, 32000, 33280, 34560, 35840, 37120,
+ 38400
+};
+
+const long FloydError7[61] =
+{
+ -53760, -51968, -50176, -48384, -46592, -44800,
+ -43008, -41216, -39424, -37632, -35840, -34048,
+ -32256, -30464, -28672, -26880, -25088, -23296,
+ -21504, -19712, -17920, -16128, -14336, -12544,
+ -10752, -8960, -7168, -5376, -3584, -1792, 0,
+ 1792, 3584, 5376, 7168, 8960, 10752, 12544, 14336,
+ 16128, 17920, 19712, 21504, 23296, 25088, 26880,
+ 28672, 30464, 32256, 34048, 35840, 37632, 39424,
+ 41216, 43008, 44800, 46592, 48384, 50176, 51968,
+ 53760
+};
+
+const long FloydIndexMap[6] =
+{
+ -30, 21, 72, 123, 174, 225
+};
+
+bool Bitmap::Convert( BmpConversion eConversion )
+{
+ // try to convert in backend
+ if (mxSalBmp)
+ {
+ // avoid large chunk of obsolete and hopefully rarely used conversions.
+ if (eConversion == BmpConversion::N8BitNoConversion)
+ {
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ // frequently used conversion for creating alpha masks
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->InterpretAs8Bit())
+ {
+ ImplSetSalBitmap(xImpBmp);
+ SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
+ return true;
+ }
+ }
+ if (eConversion == BmpConversion::N8BitGreys)
+ {
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->ConvertToGreyscale())
+ {
+ ImplSetSalBitmap(xImpBmp);
+ SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
+ return true;
+ }
+ }
+ }
+
+ const sal_uInt16 nBitCount = GetBitCount ();
+ bool bRet = false;
+
+ switch( eConversion )
+ {
+ case BmpConversion::N1BitThreshold:
+ {
+ BitmapEx aBmpEx(*this);
+ bRet = BitmapFilter::Filter(aBmpEx, BitmapMonochromeFilter(128));
+ *this = aBmpEx.GetBitmap();
+ }
+ break;
+
+ case BmpConversion::N4BitGreys:
+ bRet = ImplMakeGreyscales( 16 );
+ break;
+
+ case BmpConversion::N4BitColors:
+ {
+ if( nBitCount < 4 )
+ bRet = ImplConvertUp( 4 );
+ else if( nBitCount > 4 )
+ bRet = ImplConvertDown( 4 );
+ else
+ bRet = true;
+ }
+ break;
+
+ case BmpConversion::N8BitGreys:
+ case BmpConversion::N8BitNoConversion:
+ bRet = ImplMakeGreyscales( 256 );
+ break;
+
+ case BmpConversion::N8BitColors:
+ {
+ if( nBitCount < 8 )
+ bRet = ImplConvertUp( 8 );
+ else if( nBitCount > 8 )
+ bRet = ImplConvertDown( 8 );
+ else
+ bRet = true;
+ }
+ break;
+
+ case BmpConversion::N8BitTrans:
+ {
+ Color aTrans( BMP_COL_TRANS );
+
+ if( nBitCount < 8 )
+ bRet = ImplConvertUp( 8, &aTrans );
+ else
+ bRet = ImplConvertDown( 8, &aTrans );
+ }
+ break;
+
+ case BmpConversion::N24Bit:
+ {
+ if( nBitCount < 24 )
+ bRet = ImplConvertUp( 24 );
+ else
+ bRet = true;
+ }
+ break;
+
+ case BmpConversion::N32Bit:
+ {
+ if( nBitCount < 32 )
+ bRet = ImplConvertUp( 32 );
+ else
+ bRet = true;
+ }
+ break;
+
+ default:
+ OSL_FAIL( "Bitmap::Convert(): Unsupported conversion" );
+ break;
+ }
+
+ return bRet;
+}
+
+bool Bitmap::ImplMakeGreyscales( sal_uInt16 nGreys )
+{
+ SAL_WARN_IF( nGreys != 16 && nGreys != 256, "vcl", "Only 16 or 256 greyscales are supported!" );
+
+ ScopedReadAccess pReadAcc(*this);
+ bool bRet = false;
+
+ if( pReadAcc )
+ {
+ const BitmapPalette& rPal = GetGreyPalette( nGreys );
+ sal_uLong nShift = ( ( nGreys == 16 ) ? 4UL : 0UL );
+ bool bPalDiffers = !pReadAcc->HasPalette() || ( rPal.GetEntryCount() != pReadAcc->GetPaletteEntryCount() );
+
+ if( !bPalDiffers )
+ bPalDiffers = ( rPal != pReadAcc->GetPalette() );
+
+ if( bPalDiffers )
+ {
+ Bitmap aNewBmp( GetSizePixel(), ( nGreys == 16 ) ? 4 : 8, &rPal );
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if( pWriteAcc )
+ {
+ const long nWidth = pWriteAcc->Width();
+ const long nHeight = pWriteAcc->Height();
+
+ if( pReadAcc->HasPalette() )
+ {
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ const sal_uInt8 cIndex = pReadAcc->GetIndexFromData( pScanlineRead, nX );
+ pWriteAcc->SetPixelOnData( pScanline, nX,
+ BitmapColor(pReadAcc->GetPaletteColor( cIndex ).GetLuminance() >> nShift) );
+ }
+ }
+ }
+ else if( pReadAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr &&
+ pWriteAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal )
+ {
+ nShift += 8;
+
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pReadScan = pReadAcc->GetScanline( nY );
+ Scanline pWriteScan = pWriteAcc->GetScanline( nY );
+
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ const sal_uLong nB = *pReadScan++;
+ const sal_uLong nG = *pReadScan++;
+ const sal_uLong nR = *pReadScan++;
+
+ *pWriteScan++ = static_cast<sal_uInt8>( ( nB * 28UL + nG * 151UL + nR * 77UL ) >> nShift );
+ }
+ }
+ }
+ else if( pReadAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb &&
+ pWriteAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal )
+ {
+ nShift += 8;
+
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pReadScan = pReadAcc->GetScanline( nY );
+ Scanline pWriteScan = pWriteAcc->GetScanline( nY );
+
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ const sal_uLong nR = *pReadScan++;
+ const sal_uLong nG = *pReadScan++;
+ const sal_uLong nB = *pReadScan++;
+
+ *pWriteScan++ = static_cast<sal_uInt8>( ( nB * 28UL + nG * 151UL + nR * 77UL ) >> nShift );
+ }
+ }
+ }
+ else
+ {
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ pWriteAcc->SetPixelOnData( pScanline, nX, BitmapColor(pReadAcc->GetPixelFromData( pScanlineRead, nX ).GetLuminance() >> nShift) );
+ }
+ }
+
+ pWriteAcc.reset();
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if( bRet )
+ {
+ const MapMode aMap( maPrefMapMode );
+ const Size aSize( maPrefSize );
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+ }
+ }
+ else
+ {
+ pReadAcc.reset();
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+bool Bitmap::ImplConvertUp(sal_uInt16 nBitCount, Color const * pExtColor)
+{
+ SAL_WARN_IF( nBitCount <= GetBitCount(), "vcl", "New BitCount must be greater!" );
+
+ Bitmap::ScopedReadAccess pReadAcc(*this);
+ bool bRet = false;
+
+ if (pReadAcc)
+ {
+ BitmapPalette aPalette;
+ Bitmap aNewBmp(GetSizePixel(), nBitCount, pReadAcc->HasPalette() ? &pReadAcc->GetPalette() : &aPalette);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const long nWidth = pWriteAcc->Width();
+ const long nHeight = pWriteAcc->Height();
+
+ if (pWriteAcc->HasPalette())
+ {
+ const BitmapPalette& rOldPalette = pReadAcc->GetPalette();
+ const sal_uInt16 nOldCount = rOldPalette.GetEntryCount();
+ assert(nOldCount <= (1 << GetBitCount()));
+
+ aPalette.SetEntryCount(1 << nBitCount);
+
+ for (sal_uInt16 i = 0; i < nOldCount; i++)
+ aPalette[i] = rOldPalette[i];
+
+ if (pExtColor)
+ aPalette[aPalette.GetEntryCount() - 1] = *pExtColor;
+
+ pWriteAcc->SetPalette(aPalette);
+
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixelFromData(pScanlineRead, nX));
+ }
+ }
+ }
+ else
+ {
+ if (pReadAcc->HasPalette())
+ {
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX)));
+ }
+ }
+ }
+ else
+ {
+ for (long nY = 0; nY < nHeight; nY++)
+ {
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixelFromData(pScanlineRead, nX));
+ }
+ }
+ }
+ }
+ bRet = true;
+ }
+
+ if (bRet)
+ {
+ const MapMode aMap(maPrefMapMode);
+ const Size aSize(maPrefSize);
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+ }
+ }
+
+ return bRet;
+}
+
+bool Bitmap::ImplConvertDown(sal_uInt16 nBitCount, Color const * pExtColor)
+{
+ SAL_WARN_IF(nBitCount > GetBitCount(), "vcl", "New BitCount must be lower ( or equal when pExtColor is set )!");
+
+ Bitmap::ScopedReadAccess pReadAcc(*this);
+ bool bRet = false;
+
+ if (pReadAcc)
+ {
+ BitmapPalette aPalette;
+ Bitmap aNewBmp(GetSizePixel(), nBitCount, &aPalette);
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if (pWriteAcc)
+ {
+ const sal_uInt16 nCount = 1 << nBitCount;
+ const long nWidth = pWriteAcc->Width();
+ const long nWidth1 = nWidth - 1;
+ const long nHeight = pWriteAcc->Height();
+ Octree aOctree(*pReadAcc, pExtColor ? (nCount - 1) : nCount);
+ aPalette = aOctree.GetPalette();
+ InverseColorMap aColorMap(aPalette);
+ BitmapColor aColor;
+ ImpErrorQuad aErrQuad;
+ std::vector<ImpErrorQuad> aErrQuad1(nWidth);
+ std::vector<ImpErrorQuad> aErrQuad2(nWidth);
+ ImpErrorQuad* pQLine1 = aErrQuad1.data();
+ ImpErrorQuad* pQLine2 = nullptr;
+ long nYTmp = 0;
+ sal_uInt8 cIndex;
+ bool bQ1 = true;
+
+ if (pExtColor)
+ {
+ aPalette.SetEntryCount(aPalette.GetEntryCount() + 1);
+ aPalette[aPalette.GetEntryCount() - 1] = *pExtColor;
+ }
+
+ // set Black/White always, if we have enough space
+ if (aPalette.GetEntryCount() < (nCount - 1))
+ {
+ aPalette.SetEntryCount(aPalette.GetEntryCount() + 2);
+ aPalette[aPalette.GetEntryCount() - 2] = COL_BLACK;
+ aPalette[aPalette.GetEntryCount() - 1] = COL_WHITE;
+ }
+
+ pWriteAcc->SetPalette(aPalette);
+
+ for (long nY = 0; nY < std::min(nHeight, 2L); nY++, nYTmp++)
+ {
+ pQLine2 = !nY ? aErrQuad1.data() : aErrQuad2.data();
+ Scanline pScanlineRead = pReadAcc->GetScanline(nYTmp);
+ for (long nX = 0; nX < nWidth; nX++)
+ {
+ if (pReadAcc->HasPalette())
+ pQLine2[nX] = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX));
+ else
+ pQLine2[nX] = pReadAcc->GetPixelFromData(pScanlineRead, nX);
+ }
+ }
+
+ assert(pQLine2 || nHeight == 0);
+
+ for (long nY = 0; nY < nHeight; nY++, nYTmp++)
+ {
+ // first pixel in the line
+ cIndex = static_cast<sal_uInt8>(aColorMap.GetBestPaletteIndex(pQLine1[0].ImplGetColor()));
+ Scanline pScanline = pWriteAcc->GetScanline(nY);
+ pWriteAcc->SetPixelOnData(pScanline, 0, BitmapColor(cIndex));
+
+ long nX;
+ for (nX = 1; nX < nWidth1; nX++)
+ {
+ aColor = pQLine1[nX].ImplGetColor();
+ cIndex = static_cast<sal_uInt8>(aColorMap.GetBestPaletteIndex(aColor));
+ aErrQuad = (ImpErrorQuad(aColor) -= pWriteAcc->GetPaletteColor(cIndex));
+ pQLine1[++nX].ImplAddColorError7(aErrQuad);
+ pQLine2[nX--].ImplAddColorError1(aErrQuad);
+ pQLine2[nX--].ImplAddColorError5(aErrQuad);
+ pQLine2[nX++].ImplAddColorError3(aErrQuad);
+ pWriteAcc->SetPixelOnData(pScanline, nX, BitmapColor(cIndex));
+ }
+
+ // Last RowPixel
+ if (nX < nWidth)
+ {
+ cIndex = static_cast<sal_uInt8>(aColorMap.GetBestPaletteIndex(pQLine1[nWidth1].ImplGetColor()));
+ pWriteAcc->SetPixelOnData(pScanline, nX, BitmapColor(cIndex));
+ }
+
+ // Refill/copy row buffer
+ pQLine1 = pQLine2;
+ bQ1 = !bQ1;
+ pQLine2 = bQ1 ? aErrQuad2.data() : aErrQuad1.data();
+
+ if (nYTmp < nHeight)
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nYTmp);
+ for (nX = 0; nX < nWidth; nX++)
+ {
+ if (pReadAcc->HasPalette())
+ pQLine2[nX] = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX));
+ else
+ pQLine2[nX] = pReadAcc->GetPixelFromData(pScanlineRead, nX);
+ }
+ }
+ }
+
+ bRet = true;
+ }
+ pWriteAcc.reset();
+
+ if(bRet)
+ {
+ const MapMode aMap(maPrefMapMode);
+ const Size aSize(maPrefSize);
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aSize;
+ }
+ }
+
+ return bRet;
+}
+
+bool Bitmap::Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag )
+{
+ if(basegfx::fTools::equalZero(rScaleX) || basegfx::fTools::equalZero(rScaleY))
+ {
+ // no scale
+ return true;
+ }
+
+ if(basegfx::fTools::equal(rScaleX, 1.0) && basegfx::fTools::equal(rScaleY, 1.0))
+ {
+ // no scale
+ return true;
+ }
+
+ const sal_uInt16 nStartCount(GetBitCount());
+
+ if (mxSalBmp && mxSalBmp->ScalingSupported())
+ {
+ // implementation specific scaling
+ std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Scale(rScaleX, rScaleY, nScaleFlag))
+ {
+ ImplSetSalBitmap(xImpBmp);
+ SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
+ maPrefMapMode = MapMode( MapUnit::MapPixel );
+ maPrefSize = xImpBmp->GetSize();
+ return true;
+ }
+ }
+
+ // fdo#33455
+ //
+ // If we start with a 1 bit image, then after scaling it in any mode except
+ // BmpScaleFlag::Fast we have a 24bit image which is perfectly correct, but we
+ // are going to down-shift it to mono again and Bitmap::MakeMonochrome just
+ // has "Bitmap aNewBmp( GetSizePixel(), 1 );" to create a 1 bit bitmap which
+ // will default to black/white and the colors mapped to which ever is closer
+ // to black/white
+ //
+ // So the easiest thing to do to retain the colors of 1 bit bitmaps is to
+ // just use the fast scale rather than attempting to count unique colors in
+ // the other converters and pass all the info down through
+ // Bitmap::MakeMonochrome
+ if (nStartCount == 1)
+ nScaleFlag = BmpScaleFlag::Fast;
+
+ BitmapEx aBmpEx(*this);
+ bool bRetval(false);
+
+ switch(nScaleFlag)
+ {
+ case BmpScaleFlag::Default:
+ if (GetSizePixel().Width() < 2 || GetSizePixel().Height() < 2)
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapFastScaleFilter(rScaleX, rScaleY));
+ else
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapScaleSuperFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::Fast:
+ case BmpScaleFlag::NearestNeighbor:
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapFastScaleFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::Interpolate:
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapInterpolateScaleFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::Super:
+ bRetval = BitmapFilter::Filter(aBmpEx, BitmapScaleSuperFilter(rScaleX, rScaleY));
+ break;
+ case BmpScaleFlag::BestQuality:
+ case BmpScaleFlag::Lanczos:
+ bRetval = BitmapFilter::Filter(aBmpEx, vcl::BitmapScaleLanczos3Filter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::BiCubic:
+ bRetval = BitmapFilter::Filter(aBmpEx, vcl::BitmapScaleBicubicFilter(rScaleX, rScaleY));
+ break;
+
+ case BmpScaleFlag::BiLinear:
+ bRetval = BitmapFilter::Filter(aBmpEx, vcl::BitmapScaleBilinearFilter(rScaleX, rScaleY));
+ break;
+ }
+
+ if (bRetval)
+ *this = aBmpEx.GetBitmap();
+
+ OSL_ENSURE(!bRetval || nStartCount == GetBitCount(), "Bitmap::Scale has changed the ColorDepth, this should *not* happen (!)");
+ return bRetval;
+}
+
+bool Bitmap::Scale( const Size& rNewSize, BmpScaleFlag nScaleFlag )
+{
+ const Size aSize( GetSizePixel() );
+ bool bRet;
+
+ if( aSize.Width() && aSize.Height() )
+ {
+ bRet = Scale( static_cast<double>(rNewSize.Width()) / aSize.Width(),
+ static_cast<double>(rNewSize.Height()) / aSize.Height(),
+ nScaleFlag );
+ }
+ else
+ bRet = true;
+
+ return bRet;
+}
+
+bool Bitmap::HasFastScale()
+{
+#if HAVE_FEATURE_SKIA
+ if( SkiaHelper::isVCLSkiaEnabled() && SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster)
+ return true;
+#endif
+#if HAVE_FEATURE_OPENGL
+ if( OpenGLHelper::isVCLOpenGLEnabled())
+ return true;
+#endif
+ return false;
+}
+
+void Bitmap::AdaptBitCount(Bitmap& rNew) const
+{
+ // aNew is the result of some operation; adapt it's BitCount to the original (this)
+ if(GetBitCount() != rNew.GetBitCount())
+ {
+ switch(GetBitCount())
+ {
+ case 1:
+ {
+ rNew.Convert(BmpConversion::N1BitThreshold);
+ break;
+ }
+ case 4:
+ {
+ if(HasGreyPaletteAny())
+ {
+ rNew.Convert(BmpConversion::N4BitGreys);
+ }
+ else
+ {
+ rNew.Convert(BmpConversion::N4BitColors);
+ }
+ break;
+ }
+ case 8:
+ {
+ if(HasGreyPaletteAny())
+ {
+ rNew.Convert(BmpConversion::N8BitGreys);
+ }
+ else
+ {
+ rNew.Convert(BmpConversion::N8BitColors);
+ }
+ break;
+ }
+ case 24:
+ {
+ rNew.Convert(BmpConversion::N24Bit);
+ break;
+ }
+ case 32:
+ {
+ rNew.Convert(BmpConversion::N32Bit);
+ break;
+ }
+ default:
+ {
+ SAL_WARN("vcl", "BitDepth adaptation failed, from " << rNew.GetBitCount() << " to " << GetBitCount());
+ break;
+ }
+ }
+ }
+}
+
+bool Bitmap::Dither()
+{
+ const Size aSize( GetSizePixel() );
+
+ if( aSize.Width() == 1 || aSize.Height() == 1 )
+ return true;
+
+ bool bRet = false;
+
+ if( ( aSize.Width() > 3 ) && ( aSize.Height() > 2 ) )
+ {
+ ScopedReadAccess pReadAcc(*this);
+ Bitmap aNewBmp( GetSizePixel(), 8 );
+ BitmapScopedWriteAccess pWriteAcc(aNewBmp);
+
+ if( pReadAcc && pWriteAcc )
+ {
+ BitmapColor aColor;
+ long nWidth = pReadAcc->Width();
+ long nWidth1 = nWidth - 1;
+ long nHeight = pReadAcc->Height();
+ long nX;
+ long nW = nWidth * 3;
+ long nW2 = nW - 3;
+ long nRErr, nGErr, nBErr;
+ long nRC, nGC, nBC;
+ std::unique_ptr<long[]> p1(new long[ nW ]);
+ std::unique_ptr<long[]> p2(new long[ nW ]);
+ long* p1T = p1.get();
+ long* p2T = p2.get();
+ long* pTmp;
+ bool bPal = pReadAcc->HasPalette();
+
+ pTmp = p2T;
+
+ if( bPal )
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(0);
+ for( long nZ = 0; nZ < nWidth; nZ++ )
+ {
+ aColor = pReadAcc->GetPaletteColor( pReadAcc->GetIndexFromData( pScanlineRead, nZ ) );
+
+ *pTmp++ = static_cast<long>(aColor.GetBlue()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetGreen()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetRed()) << 12;
+ }
+ }
+ else
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(0);
+ for( long nZ = 0; nZ < nWidth; nZ++ )
+ {
+ aColor = pReadAcc->GetPixelFromData( pScanlineRead, nZ );
+
+ *pTmp++ = static_cast<long>(aColor.GetBlue()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetGreen()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetRed()) << 12;
+ }
+ }
+
+ for( long nY = 1, nYAcc = 0; nY <= nHeight; nY++, nYAcc++ )
+ {
+ pTmp = p1T;
+ p1T = p2T;
+ p2T = pTmp;
+
+ if( nY < nHeight )
+ {
+ if( bPal )
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for( long nZ = 0; nZ < nWidth; nZ++ )
+ {
+ aColor = pReadAcc->GetPaletteColor( pReadAcc->GetIndexFromData( pScanlineRead, nZ ) );
+
+ *pTmp++ = static_cast<long>(aColor.GetBlue()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetGreen()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetRed()) << 12;
+ }
+ }
+ else
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline(nY);
+ for( long nZ = 0; nZ < nWidth; nZ++ )
+ {
+ aColor = pReadAcc->GetPixelFromData( pScanlineRead, nZ );
+
+ *pTmp++ = static_cast<long>(aColor.GetBlue()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetGreen()) << 12;
+ *pTmp++ = static_cast<long>(aColor.GetRed()) << 12;
+ }
+ }
+ }
+
+ // Examine first Pixel separately
+ nX = 0;
+ long nTemp;
+ CALC_ERRORS;
+ CALC_TABLES7;
+ nX -= 5;
+ CALC_TABLES5;
+ Scanline pScanline = pWriteAcc->GetScanline(nYAcc);
+ pWriteAcc->SetPixelOnData( pScanline, 0, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
+
+ // Get middle Pixels using a loop
+ long nXAcc;
+ for ( nX = 3, nXAcc = 1; nX < nW2; nXAcc++ )
+ {
+ CALC_ERRORS;
+ CALC_TABLES7;
+ nX -= 8;
+ CALC_TABLES3;
+ CALC_TABLES5;
+ pWriteAcc->SetPixelOnData( pScanline, nXAcc, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
+ }
+
+ // Treat last Pixel separately
+ CALC_ERRORS;
+ nX -= 5;
+ CALC_TABLES3;
+ CALC_TABLES5;
+ pWriteAcc->SetPixelOnData( pScanline, nWidth1, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
+ }
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+ pWriteAcc.reset();
+
+ if( bRet )
+ {
+ const MapMode aMap( maPrefMapMode );
+ const Size aPrefSize( maPrefSize );
+
+ *this = aNewBmp;
+
+ maPrefMapMode = aMap;
+ maPrefSize = aPrefSize;
+ }
+ }
+
+ return bRet;
+}
+
+void Bitmap::Vectorize( GDIMetaFile& rMtf, sal_uInt8 cReduce, const Link<long,void>* pProgress )
+{
+ ImplVectorizer::ImplVectorize( *this, rMtf, cReduce, pProgress );
+}
+
+bool Bitmap::Adjust( short nLuminancePercent, short nContrastPercent,
+ short nChannelRPercent, short nChannelGPercent, short nChannelBPercent,
+ double fGamma, bool bInvert, bool msoBrightness )
+{
+ bool bRet = false;
+
+ // nothing to do => return quickly
+ if( !nLuminancePercent && !nContrastPercent &&
+ !nChannelRPercent && !nChannelGPercent && !nChannelBPercent &&
+ ( fGamma == 1.0 ) && !bInvert )
+ {
+ bRet = true;
+ }
+ else
+ {
+ BitmapScopedWriteAccess pAcc(*this);
+
+ if( pAcc )
+ {
+ BitmapColor aCol;
+ const long nW = pAcc->Width();
+ const long nH = pAcc->Height();
+ std::unique_ptr<sal_uInt8[]> cMapR(new sal_uInt8[ 256 ]);
+ std::unique_ptr<sal_uInt8[]> cMapG(new sal_uInt8[ 256 ]);
+ std::unique_ptr<sal_uInt8[]> cMapB(new sal_uInt8[ 256 ]);
+ double fM, fROff, fGOff, fBOff, fOff;
+
+ // calculate slope
+ if( nContrastPercent >= 0 )
+ fM = 128.0 / ( 128.0 - 1.27 * MinMax( nContrastPercent, 0, 100 ) );
+ else
+ fM = ( 128.0 + 1.27 * MinMax( nContrastPercent, -100, 0 ) ) / 128.0;
+
+ if(!msoBrightness)
+ // total offset = luminance offset + contrast offset
+ fOff = MinMax( nLuminancePercent, -100, 100 ) * 2.55 + 128.0 - fM * 128.0;
+ else
+ fOff = MinMax( nLuminancePercent, -100, 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( long nX = 0; nX < 256; nX++ )
+ {
+ if(!msoBrightness)
+ {
+ cMapR[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fROff ), 0, 255 ));
+ cMapG[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fGOff ), 0, 255 ));
+ cMapB[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fBOff ), 0, 255 ));
+ }
+ else
+ {
+ // LO simply uses (in a somewhat optimized form) "newcolor = (oldcolor-128)*contrast+brightness+128"
+ // as the formula, i.e. contrast first, brightness afterwards. MSOffice, for whatever weird reason,
+ // use neither first, but apparently it applies half of brightness before contrast and half afterwards.
+ cMapR[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fROff/2-128) * fM + 128 + fROff/2 ), 0, 255 ));
+ cMapG[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fGOff/2-128) * fM + 128 + fGOff/2 ), 0, 255 ));
+ cMapB[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fBOff/2-128) * fM + 128 + fBOff/2 ), 0, 255 ));
+ }
+ if( bGamma )
+ {
+ cMapR[ nX ] = GAMMA( cMapR[ nX ], fGamma );
+ cMapG[ nX ] = GAMMA( cMapG[ nX ], fGamma );
+ cMapB[ nX ] = GAMMA( cMapB[ nX ], fGamma );
+ }
+
+ if( bInvert )
+ {
+ cMapR[ nX ] = ~cMapR[ nX ];
+ cMapG[ nX ] = ~cMapG[ nX ];
+ cMapB[ nX ] = ~cMapB[ nX ];
+ }
+ }
+
+ // do modifying
+ if( pAcc->HasPalette() )
+ {
+ BitmapColor aNewCol;
+
+ for( sal_uInt16 i = 0, nCount = pAcc->GetPaletteEntryCount(); i < nCount; i++ )
+ {
+ const BitmapColor& rCol = pAcc->GetPaletteColor( i );
+ aNewCol.SetRed( cMapR[ rCol.GetRed() ] );
+ aNewCol.SetGreen( cMapG[ rCol.GetGreen() ] );
+ aNewCol.SetBlue( cMapB[ rCol.GetBlue() ] );
+ pAcc->SetPaletteColor( i, aNewCol );
+ }
+ }
+ else if( pAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr )
+ {
+ for( long nY = 0; nY < nH; nY++ )
+ {
+ Scanline pScan = pAcc->GetScanline( nY );
+
+ for( long nX = 0; nX < nW; nX++ )
+ {
+ *pScan = cMapB[ *pScan ]; pScan++;
+ *pScan = cMapG[ *pScan ]; pScan++;
+ *pScan = cMapR[ *pScan ]; pScan++;
+ }
+ }
+ }
+ else if( pAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
+ {
+ for( long nY = 0; nY < nH; nY++ )
+ {
+ Scanline pScan = pAcc->GetScanline( nY );
+
+ for( long nX = 0; nX < nW; nX++ )
+ {
+ *pScan = cMapR[ *pScan ]; pScan++;
+ *pScan = cMapG[ *pScan ]; pScan++;
+ *pScan = cMapB[ *pScan ]; pScan++;
+ }
+ }
+ }
+ else
+ {
+ for( long nY = 0; nY < nH; nY++ )
+ {
+ Scanline pScanline = pAcc->GetScanline(nY);
+ for( long nX = 0; nX < nW; nX++ )
+ {
+ aCol = pAcc->GetPixelFromData( pScanline, nX );
+ aCol.SetRed( cMapR[ aCol.GetRed() ] );
+ aCol.SetGreen( cMapG[ aCol.GetGreen() ] );
+ aCol.SetBlue( cMapB[ aCol.GetBlue() ] );
+ pAcc->SetPixelOnData( pScanline, nX, aCol );
+ }
+ }
+ }
+
+ pAcc.reset();
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/bitmapex.cxx b/vcl/source/gdi/bitmapex.cxx
new file mode 100644
index 000000000..f7e80fd72
--- /dev/null
+++ b/vcl/source/gdi/bitmapex.cxx
@@ -0,0 +1,1685 @@
+/* -*- 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 <rtl/math.hxx>
+#include <o3tl/underlyingenumvalue.hxx>
+#include <osl/diagnose.h>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/color/bcolormodifier.hxx>
+
+#include <vcl/ImageTree.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/alpha.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/BitmapMonochromeFilter.hxx>
+
+// BitmapEx::Create
+#include <salbmp.hxx>
+#include <salinst.hxx>
+#include <svdata.hxx>
+#include <bitmapwriteaccess.hxx>
+
+#include <o3tl/any.hxx>
+
+#include <com/sun/star/beans/XFastPropertySet.hpp>
+
+#include <memory>
+
+using namespace ::com::sun::star;
+
+BitmapEx::BitmapEx()
+ : meTransparent(TransparentType::NONE)
+ , mbAlpha(false)
+{
+}
+
+BitmapEx::BitmapEx( const BitmapEx& ) = default;
+
+BitmapEx::BitmapEx( const BitmapEx& rBitmapEx, Point aSrc, Size aSize )
+ : meTransparent(TransparentType::NONE)
+ , mbAlpha(false)
+{
+ if( rBitmapEx.IsEmpty() )
+ return;
+
+ maBitmap = Bitmap( aSize, rBitmapEx.maBitmap.GetBitCount() );
+ SetSizePixel(aSize);
+ if( rBitmapEx.IsAlpha() )
+ {
+ mbAlpha = true;
+ maMask = AlphaMask( aSize ).ImplGetBitmap();
+ }
+ else if( rBitmapEx.IsTransparent() )
+ maMask = Bitmap( aSize, rBitmapEx.maMask.GetBitCount() );
+
+ tools::Rectangle aDestRect( Point( 0, 0 ), aSize );
+ tools::Rectangle aSrcRect( aSrc, aSize );
+ CopyPixel( aDestRect, aSrcRect, &rBitmapEx );
+}
+
+BitmapEx::BitmapEx( Size aSize, sal_uInt16 nBitCount )
+ : meTransparent(TransparentType::NONE)
+ , mbAlpha(false)
+{
+ maBitmap = Bitmap( aSize, nBitCount );
+ SetSizePixel(aSize);
+}
+
+BitmapEx::BitmapEx( const OUString& rIconName )
+ : meTransparent(TransparentType::NONE)
+ , mbAlpha(false)
+{
+ loadFromIconTheme( rIconName );
+}
+
+void BitmapEx::loadFromIconTheme( const OUString& rIconName )
+{
+ bool bSuccess;
+ OUString aIconTheme;
+
+ try
+ {
+ aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ bSuccess = ImageTree::get().loadImage(rIconName, aIconTheme, *this, true);
+ }
+ catch (...)
+ {
+ bSuccess = false;
+ }
+
+ SAL_WARN_IF( !bSuccess, "vcl", "BitmapEx::BitmapEx(): could not load image " << rIconName << " via icon theme " << aIconTheme);
+}
+
+BitmapEx::BitmapEx( const Bitmap& rBmp ) :
+ maBitmap ( rBmp ),
+ maBitmapSize ( maBitmap.GetSizePixel() ),
+ meTransparent( TransparentType::NONE ),
+ mbAlpha ( false )
+{
+}
+
+BitmapEx::BitmapEx( const Bitmap& rBmp, const Bitmap& rMask ) :
+ maBitmap ( rBmp ),
+ maMask ( rMask ),
+ maBitmapSize ( maBitmap.GetSizePixel() ),
+ meTransparent ( !rMask ? TransparentType::NONE : TransparentType::Bitmap ),
+ mbAlpha ( false )
+{
+ // Ensure a mask is exactly one bit deep
+ if( !!maMask && maMask.GetBitCount() != 1 )
+ {
+ SAL_WARN( "vcl", "BitmapEx: forced mask to monochrome");
+ BitmapEx aMaskEx(maMask);
+ BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(255));
+ maMask = aMaskEx.GetBitmap();
+ }
+
+ if (!!maBitmap && !!maMask && maBitmap.GetSizePixel() != maMask.GetSizePixel())
+ {
+ OSL_ENSURE(false, "Mask size differs from Bitmap size, corrected Mask (!)");
+ maMask.Scale(maBitmap.GetSizePixel());
+ }
+}
+
+BitmapEx::BitmapEx( const Bitmap& rBmp, const AlphaMask& rAlphaMask ) :
+ maBitmap ( rBmp ),
+ maMask ( rAlphaMask.ImplGetBitmap() ),
+ maBitmapSize ( maBitmap.GetSizePixel() ),
+ meTransparent ( !rAlphaMask ? TransparentType::NONE : TransparentType::Bitmap ),
+ mbAlpha ( !rAlphaMask.IsEmpty() )
+{
+ if (!!maBitmap && !!maMask && maBitmap.GetSizePixel() != maMask.GetSizePixel())
+ {
+ OSL_ENSURE(false, "Alpha size differs from Bitmap size, corrected Mask (!)");
+ maMask.Scale(rBmp.GetSizePixel());
+ }
+
+ // #i75531# the workaround below can go when
+ // X11SalGraphics::drawAlphaBitmap()'s render acceleration
+ // can handle the bitmap depth mismatch directly
+ if( maBitmap.GetBitCount() < maMask.GetBitCount() )
+ maBitmap.Convert( BmpConversion::N24Bit );
+}
+
+BitmapEx::BitmapEx( const Bitmap& rBmp, const Color& rTransparentColor ) :
+ maBitmap ( rBmp ),
+ maBitmapSize ( maBitmap.GetSizePixel() ),
+ maTransparentColor ( rTransparentColor ),
+ meTransparent ( TransparentType::Bitmap ),
+ mbAlpha ( false )
+{
+ maMask = maBitmap.CreateMask( maTransparentColor );
+
+ SAL_WARN_IF(rBmp.GetSizePixel() != maMask.GetSizePixel(), "vcl",
+ "BitmapEx::BitmapEx(): size mismatch for bitmap and alpha mask.");
+}
+
+BitmapEx& BitmapEx::operator=( const BitmapEx& ) = default;
+
+bool BitmapEx::operator==( const BitmapEx& rBitmapEx ) const
+{
+ if (meTransparent != rBitmapEx.meTransparent)
+ return false;
+
+ if (GetSizePixel() != rBitmapEx.GetSizePixel())
+ return false;
+
+ if (meTransparent != rBitmapEx.meTransparent)
+ return false;
+
+ if (meTransparent == TransparentType::Color
+ && maTransparentColor != rBitmapEx.maTransparentColor)
+ return false;
+
+ if (mbAlpha != rBitmapEx.mbAlpha)
+ return false;
+
+ if (maBitmap != rBitmapEx.maBitmap)
+ return false;
+
+ return maMask == rBitmapEx.maMask;
+}
+
+bool BitmapEx::IsEmpty() const
+{
+ return( maBitmap.IsEmpty() && maMask.IsEmpty() );
+}
+
+void BitmapEx::SetEmpty()
+{
+ maBitmap.SetEmpty();
+ maMask.SetEmpty();
+ meTransparent = TransparentType::NONE;
+ mbAlpha = false;
+}
+
+void BitmapEx::Clear()
+{
+ SetEmpty();
+}
+
+bool BitmapEx::IsTransparent() const
+{
+ return( meTransparent != TransparentType::NONE );
+}
+
+bool BitmapEx::IsAlpha() const
+{
+ return( IsTransparent() && mbAlpha );
+}
+
+const Bitmap& BitmapEx::GetBitmap() const
+{
+ return maBitmap;
+}
+
+Bitmap BitmapEx::GetBitmap( Color aTransparentReplaceColor ) const
+{
+ Bitmap aRetBmp( maBitmap );
+
+ if( meTransparent != TransparentType::NONE )
+ {
+ Bitmap aTempMask;
+
+ if( meTransparent == TransparentType::Color )
+ aTempMask = maBitmap.CreateMask( maTransparentColor );
+ else
+ aTempMask = maMask;
+
+ if( !IsAlpha() )
+ aRetBmp.Replace( aTempMask, aTransparentReplaceColor );
+ else
+ aRetBmp.Replace( GetAlpha(), aTransparentReplaceColor );
+ }
+
+ return aRetBmp;
+}
+
+Bitmap BitmapEx::GetMask() const
+{
+ if (!IsAlpha())
+ return maMask;
+
+ BitmapEx aMaskEx(maMask);
+ BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(255));
+ return aMaskEx.GetBitmap();
+}
+
+AlphaMask BitmapEx::GetAlpha() const
+{
+ if( IsAlpha() )
+ {
+ AlphaMask aAlpha;
+ aAlpha.ImplSetBitmap( maMask );
+ return aAlpha;
+ }
+ else
+ {
+ return AlphaMask(maMask);
+ }
+}
+
+sal_uLong BitmapEx::GetSizeBytes() const
+{
+ sal_uLong nSizeBytes = maBitmap.GetSizeBytes();
+
+ if( meTransparent == TransparentType::Bitmap )
+ nSizeBytes += maMask.GetSizeBytes();
+
+ return nSizeBytes;
+}
+
+BitmapChecksum BitmapEx::GetChecksum() const
+{
+ BitmapChecksum nCrc = maBitmap.GetChecksum();
+ SVBT32 aBT32;
+ BitmapChecksumOctetArray aBCOA;
+
+ UInt32ToSVBT32( o3tl::underlyingEnumValue(meTransparent), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ UInt32ToSVBT32( sal_uInt32(mbAlpha), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ if( ( TransparentType::Bitmap == meTransparent ) && !maMask.IsEmpty() )
+ {
+ BCToBCOA( maMask.GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+ }
+
+ return nCrc;
+}
+
+void BitmapEx::SetSizePixel(const Size& rNewSize)
+{
+ maBitmapSize = rNewSize;
+}
+
+bool BitmapEx::Invert()
+{
+ bool bRet = false;
+
+ if (!!maBitmap)
+ {
+ bRet = maBitmap.Invert();
+
+ if (bRet && (meTransparent == TransparentType::Color))
+ maTransparentColor.Invert();
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Mirror( BmpMirrorFlags nMirrorFlags )
+{
+ bool bRet = false;
+
+ if( !!maBitmap )
+ {
+ bRet = maBitmap.Mirror( nMirrorFlags );
+
+ if( bRet && ( meTransparent == TransparentType::Bitmap ) && !!maMask )
+ maMask.Mirror( nMirrorFlags );
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag )
+{
+ bool bRet = false;
+
+ if( !!maBitmap )
+ {
+ bRet = maBitmap.Scale( rScaleX, rScaleY, nScaleFlag );
+
+ if( bRet && ( meTransparent == TransparentType::Bitmap ) && !!maMask )
+ {
+ maMask.Scale( rScaleX, rScaleY, nScaleFlag );
+ }
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF( !!maMask && maBitmap.GetSizePixel() != maMask.GetSizePixel(), "vcl",
+ "BitmapEx::Scale(): size mismatch for bitmap and alpha mask." );
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Scale( const Size& rNewSize, BmpScaleFlag nScaleFlag )
+{
+ bool bRet;
+
+ if (GetSizePixel().Width() && GetSizePixel().Height()
+ && (rNewSize.Width() != GetSizePixel().Width()
+ || rNewSize.Height() != GetSizePixel().Height() ) )
+ {
+ bRet = Scale( static_cast<double>(rNewSize.Width()) / GetSizePixel().Width(),
+ static_cast<double>(rNewSize.Height()) / GetSizePixel().Height(),
+ nScaleFlag );
+ }
+ else
+ {
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Rotate( long nAngle10, const Color& rFillColor )
+{
+ bool bRet = false;
+
+ if( !!maBitmap )
+ {
+ const bool bTransRotate = ( COL_TRANSPARENT == rFillColor );
+
+ if( bTransRotate )
+ {
+ if( meTransparent == TransparentType::Color )
+ bRet = maBitmap.Rotate( nAngle10, maTransparentColor );
+ else
+ {
+ bRet = maBitmap.Rotate( nAngle10, COL_BLACK );
+
+ if( meTransparent == TransparentType::NONE )
+ {
+ maMask = Bitmap(GetSizePixel(), 1);
+ maMask.Erase( COL_BLACK );
+ meTransparent = TransparentType::Bitmap;
+ }
+
+ if( bRet && !!maMask )
+ maMask.Rotate( nAngle10, COL_WHITE );
+ }
+ }
+ else
+ {
+ bRet = maBitmap.Rotate( nAngle10, rFillColor );
+
+ if( bRet && ( meTransparent == TransparentType::Bitmap ) && !!maMask )
+ maMask.Rotate( nAngle10, COL_WHITE );
+ }
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF(!!maMask && maBitmap.GetSizePixel() != maMask.GetSizePixel(), "vcl",
+ "BitmapEx::Rotate(): size mismatch for bitmap and alpha mask.");
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Crop( const tools::Rectangle& rRectPixel )
+{
+ bool bRet = false;
+
+ if( !!maBitmap )
+ {
+ bRet = maBitmap.Crop( rRectPixel );
+
+ if( bRet && ( meTransparent == TransparentType::Bitmap ) && !!maMask )
+ maMask.Crop( rRectPixel );
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF(!!maMask && maBitmap.GetSizePixel() != maMask.GetSizePixel(), "vcl",
+ "BitmapEx::Crop(): size mismatch for bitmap and alpha mask.");
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Convert( BmpConversion eConversion )
+{
+ return !!maBitmap && maBitmap.Convert( eConversion );
+}
+
+void BitmapEx::Expand( sal_uLong nDX, sal_uLong nDY, bool bExpandTransparent )
+{
+ bool bRet = false;
+
+ if( !!maBitmap )
+ {
+ bRet = maBitmap.Expand( nDX, nDY );
+
+ if( bRet && ( meTransparent == TransparentType::Bitmap ) && !!maMask )
+ {
+ Color aColor( bExpandTransparent ? COL_WHITE : COL_BLACK );
+ maMask.Expand( nDX, nDY, &aColor );
+ }
+
+ SetSizePixel(maBitmap.GetSizePixel());
+
+ SAL_WARN_IF(!!maMask && maBitmap.GetSizePixel() != maMask.GetSizePixel(), "vcl",
+ "BitmapEx::Expand(): size mismatch for bitmap and alpha mask.");
+ }
+}
+
+bool BitmapEx::CopyPixel( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc,
+ const BitmapEx* pBmpExSrc )
+{
+ bool bRet = false;
+
+ if( !pBmpExSrc || pBmpExSrc->IsEmpty() )
+ {
+ if( !maBitmap.IsEmpty() )
+ {
+ bRet = maBitmap.CopyPixel( rRectDst, rRectSrc );
+
+ if( bRet && ( meTransparent == TransparentType::Bitmap ) && !!maMask )
+ maMask.CopyPixel( rRectDst, rRectSrc );
+ }
+ }
+ else
+ {
+ if( !maBitmap.IsEmpty() )
+ {
+ bRet = maBitmap.CopyPixel( rRectDst, rRectSrc, &pBmpExSrc->maBitmap );
+
+ if( bRet )
+ {
+ if( pBmpExSrc->IsAlpha() )
+ {
+ if( IsAlpha() )
+ // cast to use the optimized AlphaMask::CopyPixel
+ maMask.CopyPixel_AlphaOptimized( rRectDst, rRectSrc, &pBmpExSrc->maMask );
+ else if( IsTransparent() )
+ {
+ std::unique_ptr<AlphaMask> pAlpha(new AlphaMask( maMask ));
+
+ maMask = pAlpha->ImplGetBitmap();
+ pAlpha.reset();
+ mbAlpha = true;
+ maMask.CopyPixel( rRectDst, rRectSrc, &pBmpExSrc->maMask );
+ }
+ else
+ {
+ sal_uInt8 cBlack = 0;
+ std::unique_ptr<AlphaMask> pAlpha(new AlphaMask(GetSizePixel(), &cBlack));
+
+ maMask = pAlpha->ImplGetBitmap();
+ pAlpha.reset();
+ meTransparent = TransparentType::Bitmap;
+ mbAlpha = true;
+ maMask.CopyPixel( rRectDst, rRectSrc, &pBmpExSrc->maMask );
+ }
+ }
+ else if( pBmpExSrc->IsTransparent() )
+ {
+ if (IsAlpha())
+ {
+ AlphaMask aAlpha( pBmpExSrc->maMask );
+ maMask.CopyPixel( rRectDst, rRectSrc, &aAlpha.ImplGetBitmap() );
+ }
+ else if (IsTransparent())
+ {
+ maMask.CopyPixel( rRectDst, rRectSrc, &pBmpExSrc->maMask );
+ }
+ else
+ {
+ maMask = Bitmap(GetSizePixel(), 1);
+ maMask.Erase(COL_BLACK);
+ meTransparent = TransparentType::Bitmap;
+ maMask.CopyPixel( rRectDst, rRectSrc, &pBmpExSrc->maMask );
+ }
+ }
+ else if (IsAlpha())
+ {
+ sal_uInt8 cBlack = 0;
+ const AlphaMask aAlphaSrc(pBmpExSrc->GetSizePixel(), &cBlack);
+
+ maMask.CopyPixel( rRectDst, rRectSrc, &aAlphaSrc.ImplGetBitmap() );
+ }
+ else if (IsTransparent())
+ {
+ Bitmap aMaskSrc(pBmpExSrc->GetSizePixel(), 1);
+
+ aMaskSrc.Erase( COL_BLACK );
+ maMask.CopyPixel( rRectDst, rRectSrc, &aMaskSrc );
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+bool BitmapEx::Erase( const Color& rFillColor )
+{
+ bool bRet = false;
+
+ if( !!maBitmap )
+ {
+ bRet = maBitmap.Erase( rFillColor );
+
+ if( bRet && ( meTransparent == TransparentType::Bitmap ) && !!maMask )
+ {
+ // Respect transparency on fill color
+ if( rFillColor.GetTransparency() )
+ {
+ const Color aFill( rFillColor.GetTransparency(), rFillColor.GetTransparency(), rFillColor.GetTransparency() );
+ maMask.Erase( aFill );
+ }
+ else
+ {
+ const Color aBlack( COL_BLACK );
+ maMask.Erase( aBlack );
+ }
+ }
+ }
+
+ return bRet;
+}
+
+void BitmapEx::Replace( const Color& rSearchColor, const Color& rReplaceColor )
+{
+ if (!!maBitmap)
+ maBitmap.Replace( rSearchColor, rReplaceColor );
+}
+
+void BitmapEx::Replace( const Color* pSearchColors, const Color* pReplaceColors, sal_uLong nColorCount )
+{
+ if (!!maBitmap)
+ maBitmap.Replace( pSearchColors, pReplaceColors, nColorCount, /*pTols*/nullptr );
+}
+
+bool BitmapEx::Adjust( short nLuminancePercent, short nContrastPercent,
+ short nChannelRPercent, short nChannelGPercent, short nChannelBPercent,
+ double fGamma, bool bInvert, bool msoBrightness )
+{
+ return !!maBitmap && maBitmap.Adjust( nLuminancePercent, nContrastPercent,
+ nChannelRPercent, nChannelGPercent, nChannelBPercent,
+ fGamma, bInvert, msoBrightness );
+}
+
+void BitmapEx::Draw( OutputDevice* pOutDev, const Point& rDestPt ) const
+{
+ pOutDev->DrawBitmapEx( rDestPt, *this );
+}
+
+void BitmapEx::Draw( OutputDevice* pOutDev,
+ const Point& rDestPt, const Size& rDestSize ) const
+{
+ pOutDev->DrawBitmapEx( rDestPt, rDestSize, *this );
+}
+
+BitmapEx BitmapEx:: AutoScaleBitmap(BitmapEx const & aBitmap, const long aStandardSize)
+{
+ Point aEmptyPoint(0,0);
+ double imgposX = 0;
+ double imgposY = 0;
+ BitmapEx aRet = aBitmap;
+ double imgOldWidth = aRet.GetSizePixel().Width();
+ double imgOldHeight = aRet.GetSizePixel().Height();
+
+ Size aScaledSize;
+ if (imgOldWidth >= aStandardSize || imgOldHeight >= aStandardSize)
+ {
+ sal_Int32 imgNewWidth = 0;
+ sal_Int32 imgNewHeight = 0;
+ if (imgOldWidth >= imgOldHeight)
+ {
+ imgNewWidth = aStandardSize;
+ imgNewHeight = sal_Int32(imgOldHeight / (imgOldWidth / aStandardSize) + 0.5);
+ imgposX = 0;
+ imgposY = (aStandardSize - (imgOldHeight / (imgOldWidth / aStandardSize) + 0.5)) / 2 + 0.5;
+ }
+ else
+ {
+ imgNewHeight = aStandardSize;
+ imgNewWidth = sal_Int32(imgOldWidth / (imgOldHeight / aStandardSize) + 0.5);
+ imgposY = 0;
+ imgposX = (aStandardSize - (imgOldWidth / (imgOldHeight / aStandardSize) + 0.5)) / 2 + 0.5;
+ }
+
+ aScaledSize = Size( imgNewWidth, imgNewHeight );
+ aRet.Scale( aScaledSize, BmpScaleFlag::BestQuality );
+ }
+ else
+ {
+ imgposX = (aStandardSize - imgOldWidth) / 2 + 0.5;
+ imgposY = (aStandardSize - imgOldHeight) / 2 + 0.5;
+ }
+
+ Size aStdSize( aStandardSize, aStandardSize );
+ tools::Rectangle aRect(aEmptyPoint, aStdSize );
+
+ ScopedVclPtrInstance< VirtualDevice > aVirDevice(*Application::GetDefaultDevice(),
+ DeviceFormat::DEFAULT, DeviceFormat::BITMASK);
+ aVirDevice->SetOutputSizePixel( aStdSize );
+ aVirDevice->SetFillColor( COL_TRANSPARENT );
+ aVirDevice->SetLineColor( COL_TRANSPARENT );
+
+ // Draw a rect into virDevice
+ aVirDevice->DrawRect( aRect );
+ Point aPointPixel( static_cast<long>(imgposX), static_cast<long>(imgposY) );
+ aVirDevice->DrawBitmapEx( aPointPixel, aRet );
+ aRet = aVirDevice->GetBitmapEx( aEmptyPoint, aStdSize );
+
+ return aRet;
+}
+
+sal_uInt8 BitmapEx::GetTransparency(sal_Int32 nX, sal_Int32 nY) const
+{
+ sal_uInt8 nTransparency(0xff);
+
+ if(!maBitmap.IsEmpty())
+ {
+ if (nX >= 0 && nX < GetSizePixel().Width() && nY >= 0 && nY < GetSizePixel().Height())
+ {
+ if (maBitmap.GetBitCount() == 32)
+ return GetPixelColor(nX, nY).GetTransparency();
+ switch(meTransparent)
+ {
+ case TransparentType::NONE:
+ {
+ // Not transparent, ergo all covered
+ nTransparency = 0x00;
+ break;
+ }
+ case TransparentType::Color:
+ {
+ Bitmap aTestBitmap(maBitmap);
+ Bitmap::ScopedReadAccess pRead(aTestBitmap);
+
+ if(pRead)
+ {
+ const BitmapColor aBmpColor = pRead->GetColor(nY, nX);
+
+ // If color is not equal to TransparentColor, we are not transparent
+ if (aBmpColor != maTransparentColor)
+ nTransparency = 0x00;
+
+ }
+ break;
+ }
+ case TransparentType::Bitmap:
+ {
+ if(!maMask.IsEmpty())
+ {
+ Bitmap aTestBitmap(maMask);
+ Bitmap::ScopedReadAccess pRead(aTestBitmap);
+
+ if(pRead)
+ {
+ const BitmapColor aBitmapColor(pRead->GetPixel(nY, nX));
+
+ if(mbAlpha)
+ {
+ nTransparency = aBitmapColor.GetIndex();
+ }
+ else
+ {
+ if(0x00 == aBitmapColor.GetIndex())
+ {
+ nTransparency = 0x00;
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ return nTransparency;
+}
+
+
+Color BitmapEx::GetPixelColor(sal_Int32 nX, sal_Int32 nY) const
+{
+ Bitmap::ScopedReadAccess pReadAccess( const_cast<Bitmap&>(maBitmap) );
+ assert(pReadAccess);
+
+ BitmapColor aColor = pReadAccess->GetColor(nY, nX);
+
+ if (IsAlpha())
+ {
+ AlphaMask aAlpha = GetAlpha();
+ AlphaMask::ScopedReadAccess pAlphaReadAccess(aAlpha);
+ aColor.SetTransparency(pAlphaReadAccess->GetPixel(nY, nX).GetIndex());
+ }
+ else if (maBitmap.GetBitCount() != 32)
+ {
+ aColor.SetTransparency(0);
+ }
+ return aColor;
+}
+
+// Shift alpha transparent pixels between cppcanvas/ implementations
+// and vcl in a generally grotesque and under-performing fashion
+bool BitmapEx::Create( const css::uno::Reference< css::rendering::XBitmapCanvas > &xBitmapCanvas,
+ const Size &rSize )
+{
+ uno::Reference< beans::XFastPropertySet > xFastPropertySet( xBitmapCanvas, uno::UNO_QUERY );
+ if( xFastPropertySet )
+ {
+ // 0 means get BitmapEx
+ uno::Any aAny = xFastPropertySet->getFastPropertyValue( 0 );
+ std::unique_ptr<BitmapEx> xBitmapEx(reinterpret_cast<BitmapEx*>(*o3tl::doAccess<sal_Int64>(aAny)));
+ if( xBitmapEx )
+ {
+ *this = *xBitmapEx;
+ return true;
+ }
+ }
+
+ std::shared_ptr<SalBitmap> pSalBmp;
+ std::shared_ptr<SalBitmap> pSalMask;
+
+ pSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
+
+ Size aLocalSize(rSize);
+ if( pSalBmp->Create( xBitmapCanvas, aLocalSize ) )
+ {
+ pSalMask = ImplGetSVData()->mpDefInst->CreateSalBitmap();
+ if ( pSalMask->Create( xBitmapCanvas, aLocalSize, true ) )
+ {
+ *this = BitmapEx(Bitmap(pSalBmp), Bitmap(pSalMask) );
+ return true;
+ }
+ else
+ {
+ *this = BitmapEx(Bitmap(pSalBmp));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+namespace
+{
+ Bitmap impTransformBitmap(
+ const Bitmap& rSource,
+ const Size& rDestinationSize,
+ const basegfx::B2DHomMatrix& rTransform,
+ bool bSmooth)
+ {
+ Bitmap aDestination(rDestinationSize, 24);
+ BitmapScopedWriteAccess xWrite(aDestination);
+
+ if(xWrite)
+ {
+ Bitmap::ScopedReadAccess xRead(const_cast< Bitmap& >(rSource));
+
+ if (xRead)
+ {
+ const Size aDestinationSizePixel(aDestination.GetSizePixel());
+ const BitmapColor aOutside(BitmapColor(0xff, 0xff, 0xff));
+
+ for(long y(0); y < aDestinationSizePixel.getHeight(); y++)
+ {
+ Scanline pScanline = xWrite->GetScanline( y );
+ for(long x(0); x < aDestinationSizePixel.getWidth(); x++)
+ {
+ const basegfx::B2DPoint aSourceCoor(rTransform * basegfx::B2DPoint(x, y));
+
+ if(bSmooth)
+ {
+ xWrite->SetPixelOnData(
+ pScanline,
+ x,
+ xRead->GetInterpolatedColorWithFallback(
+ aSourceCoor.getY(),
+ aSourceCoor.getX(),
+ aOutside));
+ }
+ else
+ {
+ // this version does the correct <= 0.0 checks, so no need
+ // to do the static_cast< sal_Int32 > self and make an error
+ xWrite->SetPixelOnData(
+ pScanline,
+ x,
+ xRead->GetColorWithFallback(
+ aSourceCoor.getY(),
+ aSourceCoor.getX(),
+ aOutside));
+ }
+ }
+ }
+ }
+ }
+ xWrite.reset();
+
+ rSource.AdaptBitCount(aDestination);
+
+ return aDestination;
+ }
+
+ /// Decides if rTransformation needs smoothing or not (e.g. 180 deg rotation doesn't need it).
+ bool implTransformNeedsSmooth(const basegfx::B2DHomMatrix& rTransformation)
+ {
+ basegfx::B2DVector aScale, aTranslate;
+ double fRotate, fShearX;
+ rTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
+ if (aScale != basegfx::B2DVector(1, 1))
+ {
+ return true;
+ }
+
+ fRotate = fmod( fRotate, F_2PI );
+ if (fRotate < 0)
+ {
+ fRotate += F_2PI;
+ }
+ if (!rtl::math::approxEqual(fRotate, 0)
+ && !rtl::math::approxEqual(fRotate, F_PI2)
+ && !rtl::math::approxEqual(fRotate, F_PI)
+ && !rtl::math::approxEqual(fRotate, 3 * F_PI2))
+ {
+ return true;
+ }
+
+ if (!rtl::math::approxEqual(fShearX, 0))
+ {
+ return true;
+ }
+
+ return false;
+ }
+} // end of anonymous namespace
+
+BitmapEx BitmapEx::TransformBitmapEx(
+ double fWidth,
+ double fHeight,
+ const basegfx::B2DHomMatrix& rTransformation) const
+{
+ if(fWidth <= 1 || fHeight <= 1)
+ return BitmapEx();
+
+ // force destination to 24 bit, we want to smooth output
+ const Size aDestinationSize(basegfx::fround(fWidth), basegfx::fround(fHeight));
+ bool bSmooth = implTransformNeedsSmooth(rTransformation);
+ const Bitmap aDestination(impTransformBitmap(GetBitmap(), aDestinationSize, rTransformation, bSmooth));
+
+ // create mask
+ if(IsTransparent())
+ {
+ if(IsAlpha())
+ {
+ const Bitmap aAlpha(impTransformBitmap(GetAlpha().GetBitmap(), aDestinationSize, rTransformation, bSmooth));
+ return BitmapEx(aDestination, AlphaMask(aAlpha));
+ }
+ else
+ {
+ const Bitmap aLclMask(impTransformBitmap(GetMask(), aDestinationSize, rTransformation, false));
+ return BitmapEx(aDestination, aLclMask);
+ }
+ }
+
+ return BitmapEx(aDestination);
+}
+
+BitmapEx BitmapEx::getTransformed(
+ const basegfx::B2DHomMatrix& rTransformation,
+ const basegfx::B2DRange& rVisibleRange,
+ double fMaximumArea) const
+{
+ BitmapEx aRetval;
+
+ if(IsEmpty())
+ return aRetval;
+
+ const sal_uInt32 nSourceWidth(GetSizePixel().Width());
+ const sal_uInt32 nSourceHeight(GetSizePixel().Height());
+
+ if(!nSourceWidth || !nSourceHeight)
+ return aRetval;
+
+ // Get aOutlineRange
+ basegfx::B2DRange aOutlineRange(0.0, 0.0, 1.0, 1.0);
+
+ aOutlineRange.transform(rTransformation);
+
+ // create visible range from it by moving from relative to absolute
+ basegfx::B2DRange aVisibleRange(rVisibleRange);
+
+ aVisibleRange.transform(
+ basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aOutlineRange.getRange(),
+ aOutlineRange.getMinimum()));
+
+ // get target size (which is visible range's size)
+ double fWidth(aVisibleRange.getWidth());
+ double fHeight(aVisibleRange.getHeight());
+
+ if(fWidth < 1.0 || fHeight < 1.0)
+ {
+ return aRetval;
+ }
+
+ // test if discrete size (pixel) maybe too big and limit it
+ const double fArea(fWidth * fHeight);
+ const bool bNeedToReduce(basegfx::fTools::more(fArea, fMaximumArea));
+ double fReduceFactor(1.0);
+
+ if(bNeedToReduce)
+ {
+ fReduceFactor = sqrt(fMaximumArea / fArea);
+ fWidth *= fReduceFactor;
+ fHeight *= fReduceFactor;
+ }
+
+ // Build complete transform from source pixels to target pixels.
+ // Start by scaling from source pixel size to unit coordinates
+ basegfx::B2DHomMatrix aTransform(
+ basegfx::utils::createScaleB2DHomMatrix(
+ 1.0 / nSourceWidth,
+ 1.0 / nSourceHeight));
+
+ // multiply with given transform which leads from unit coordinates inside
+ // aOutlineRange
+ aTransform = rTransformation * aTransform;
+
+ // subtract top-left of absolute VisibleRange
+ aTransform.translate(
+ -aVisibleRange.getMinX(),
+ -aVisibleRange.getMinY());
+
+ // scale to target pixels (if needed)
+ if(bNeedToReduce)
+ {
+ aTransform.scale(fReduceFactor, fReduceFactor);
+ }
+
+ // invert to get transformation from target pixel coordinates to source pixels
+ aTransform.invert();
+
+ // create bitmap using source, destination and linear back-transformation
+ aRetval = TransformBitmapEx(fWidth, fHeight, aTransform);
+
+ return aRetval;
+}
+
+BitmapEx BitmapEx::ModifyBitmapEx(const basegfx::BColorModifierStack& rBColorModifierStack) const
+{
+ Bitmap aChangedBitmap(GetBitmap());
+ bool bDone(false);
+
+ for(sal_uInt32 a(rBColorModifierStack.count()); a && !bDone; )
+ {
+ const basegfx::BColorModifierSharedPtr& rModifier = rBColorModifierStack.getBColorModifier(--a);
+ const basegfx::BColorModifier_replace* pReplace = dynamic_cast< const basegfx::BColorModifier_replace* >(rModifier.get());
+
+ if(pReplace)
+ {
+ // complete replace
+ if(IsTransparent())
+ {
+ // clear bitmap with dest color
+ if(aChangedBitmap.GetBitCount() <= 8)
+ {
+ // For e.g. 8bit Bitmaps, the nearest color to the given erase color is
+ // determined and used -> this may be different from what is wanted here.
+ // Better create a new bitmap with the needed color explicitly.
+ Bitmap::ScopedReadAccess xReadAccess(aChangedBitmap);
+ OSL_ENSURE(xReadAccess, "Got no Bitmap ReadAccess ?!?");
+
+ if(xReadAccess)
+ {
+ BitmapPalette aNewPalette(xReadAccess->GetPalette());
+ aNewPalette[0] = BitmapColor(Color(pReplace->getBColor()));
+ aChangedBitmap = Bitmap(
+ aChangedBitmap.GetSizePixel(),
+ aChangedBitmap.GetBitCount(),
+ &aNewPalette);
+ }
+ }
+ aChangedBitmap.Erase(Color(pReplace->getBColor()));
+ }
+ else
+ {
+ // erase bitmap, caller will know to paint direct
+ aChangedBitmap.SetEmpty();
+ }
+
+ bDone = true;
+ }
+ else
+ {
+ BitmapScopedWriteAccess xContent(aChangedBitmap);
+
+ if(xContent)
+ {
+ const double fConvertColor(1.0 / 255.0);
+
+ if(xContent->HasPalette())
+ {
+ const sal_uInt16 nCount(xContent->GetPaletteEntryCount());
+
+ for(sal_uInt16 b(0); b < nCount; b++)
+ {
+ const BitmapColor& rCol = xContent->GetPaletteColor(b);
+ const basegfx::BColor aBSource(
+ rCol.GetRed() * fConvertColor,
+ rCol.GetGreen() * fConvertColor,
+ rCol.GetBlue() * fConvertColor);
+ const basegfx::BColor aBDest(rModifier->getModifiedColor(aBSource));
+ xContent->SetPaletteColor(b, BitmapColor(Color(aBDest)));
+ }
+ }
+ else if(ScanlineFormat::N24BitTcBgr == xContent->GetScanlineFormat())
+ {
+ for(long y(0); y < xContent->Height(); y++)
+ {
+ Scanline pScan = xContent->GetScanline(y);
+
+ for(long x(0); x < xContent->Width(); x++)
+ {
+ const basegfx::BColor aBSource(
+ *(pScan + 2)* fConvertColor,
+ *(pScan + 1) * fConvertColor,
+ *pScan * fConvertColor);
+ const basegfx::BColor aBDest(rModifier->getModifiedColor(aBSource));
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getBlue() * 255.0);
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getGreen() * 255.0);
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getRed() * 255.0);
+ }
+ }
+ }
+ else if(ScanlineFormat::N24BitTcRgb == xContent->GetScanlineFormat())
+ {
+ for(long y(0); y < xContent->Height(); y++)
+ {
+ Scanline pScan = xContent->GetScanline(y);
+
+ for(long x(0); x < xContent->Width(); x++)
+ {
+ const basegfx::BColor aBSource(
+ *pScan * fConvertColor,
+ *(pScan + 1) * fConvertColor,
+ *(pScan + 2) * fConvertColor);
+ const basegfx::BColor aBDest(rModifier->getModifiedColor(aBSource));
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getRed() * 255.0);
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getGreen() * 255.0);
+ *pScan++ = static_cast< sal_uInt8 >(aBDest.getBlue() * 255.0);
+ }
+ }
+ }
+ else
+ {
+ for(long y(0); y < xContent->Height(); y++)
+ {
+ Scanline pScanline = xContent->GetScanline( y );
+ for(long x(0); x < xContent->Width(); x++)
+ {
+ const BitmapColor aBMCol(xContent->GetColor(y, x));
+ const basegfx::BColor aBSource(
+ static_cast<double>(aBMCol.GetRed()) * fConvertColor,
+ static_cast<double>(aBMCol.GetGreen()) * fConvertColor,
+ static_cast<double>(aBMCol.GetBlue()) * fConvertColor);
+ const basegfx::BColor aBDest(rModifier->getModifiedColor(aBSource));
+
+ xContent->SetPixelOnData(pScanline, x, BitmapColor(Color(aBDest)));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if(aChangedBitmap.IsEmpty())
+ {
+ return BitmapEx();
+ }
+ else
+ {
+ if(IsTransparent())
+ {
+ if(IsAlpha())
+ {
+ return BitmapEx(aChangedBitmap, GetAlpha());
+ }
+ else
+ {
+ return BitmapEx(aChangedBitmap, GetMask());
+ }
+ }
+ else
+ {
+ return BitmapEx(aChangedBitmap);
+ }
+ }
+}
+
+BitmapEx createBlendFrame(
+ const Size& rSize,
+ sal_uInt8 nAlpha,
+ Color aColorTopLeft,
+ Color aColorBottomRight)
+{
+ const sal_uInt32 nW(rSize.Width());
+ const sal_uInt32 nH(rSize.Height());
+
+ if(nW || nH)
+ {
+ Color aColTopRight(aColorTopLeft);
+ Color aColBottomLeft(aColorTopLeft);
+ const sal_uInt32 nDE(nW + nH);
+
+ aColTopRight.Merge(aColorBottomRight, 255 - sal_uInt8((nW * 255) / nDE));
+ aColBottomLeft.Merge(aColorBottomRight, 255 - sal_uInt8((nH * 255) / nDE));
+
+ return createBlendFrame(rSize, nAlpha, aColorTopLeft, aColTopRight, aColorBottomRight, aColBottomLeft);
+ }
+
+ return BitmapEx();
+}
+
+BitmapEx createBlendFrame(
+ const Size& rSize,
+ sal_uInt8 nAlpha,
+ Color aColorTopLeft,
+ Color aColorTopRight,
+ Color aColorBottomRight,
+ Color aColorBottomLeft)
+{
+ BlendFrameCache* pBlendFrameCache = ImplGetBlendFrameCache();
+
+ if(pBlendFrameCache->m_aLastSize == rSize
+ && pBlendFrameCache->m_nLastAlpha == nAlpha
+ && pBlendFrameCache->m_aLastColorTopLeft == aColorTopLeft
+ && pBlendFrameCache->m_aLastColorTopRight == aColorTopRight
+ && pBlendFrameCache->m_aLastColorBottomRight == aColorBottomRight
+ && pBlendFrameCache->m_aLastColorBottomLeft == aColorBottomLeft)
+ {
+ return pBlendFrameCache->m_aLastResult;
+ }
+
+ pBlendFrameCache->m_aLastSize = rSize;
+ pBlendFrameCache->m_nLastAlpha = nAlpha;
+ pBlendFrameCache->m_aLastColorTopLeft = aColorTopLeft;
+ pBlendFrameCache->m_aLastColorTopRight = aColorTopRight;
+ pBlendFrameCache->m_aLastColorBottomRight = aColorBottomRight;
+ pBlendFrameCache->m_aLastColorBottomLeft = aColorBottomLeft;
+ pBlendFrameCache->m_aLastResult.Clear();
+
+ const long nW(rSize.Width());
+ const long nH(rSize.Height());
+
+ if(nW > 1 && nH > 1)
+ {
+ sal_uInt8 aEraseTrans(0xff);
+ Bitmap aContent(rSize, 24);
+ AlphaMask aAlpha(rSize, &aEraseTrans);
+
+ aContent.Erase(COL_BLACK);
+
+ BitmapScopedWriteAccess pContent(aContent);
+ AlphaScopedWriteAccess pAlpha(aAlpha);
+
+ if(pContent && pAlpha)
+ {
+ long x(0);
+ long y(0);
+ Scanline pScanContent = pContent->GetScanline( 0 );
+ Scanline pScanAlpha = pContent->GetScanline( 0 );
+
+ // x == 0, y == 0, top-left corner
+ pContent->SetPixelOnData(pScanContent, 0, aColorTopLeft);
+ pAlpha->SetPixelOnData(pScanAlpha, 0, BitmapColor(nAlpha));
+
+ // y == 0, top line left to right
+ for(x = 1; x < nW - 1; x++)
+ {
+ Color aMix(aColorTopLeft);
+
+ aMix.Merge(aColorTopRight, 255 - sal_uInt8((x * 255) / nW));
+ pContent->SetPixelOnData(pScanContent, x, aMix);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+
+ // x == nW - 1, y == 0, top-right corner
+ // #i123690# Caution! When nW is 1, x == nW is possible (!)
+ if(x < nW)
+ {
+ pContent->SetPixelOnData(pScanContent, x, aColorTopRight);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+
+ // x == 0 and nW - 1, left and right line top-down
+ for(y = 1; y < nH - 1; y++)
+ {
+ pScanContent = pContent->GetScanline( y );
+ pScanAlpha = pContent->GetScanline( y );
+ Color aMixA(aColorTopLeft);
+
+ aMixA.Merge(aColorBottomLeft, 255 - sal_uInt8((y * 255) / nH));
+ pContent->SetPixelOnData(pScanContent, 0, aMixA);
+ pAlpha->SetPixelOnData(pScanAlpha, 0, BitmapColor(nAlpha));
+
+ // #i123690# Caution! When nW is 1, x == nW is possible (!)
+ if(x < nW)
+ {
+ Color aMixB(aColorTopRight);
+
+ aMixB.Merge(aColorBottomRight, 255 - sal_uInt8((y * 255) / nH));
+ pContent->SetPixelOnData(pScanContent, x, aMixB);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+ }
+
+ // #i123690# Caution! When nH is 1, y == nH is possible (!)
+ if(y < nH)
+ {
+ // x == 0, y == nH - 1, bottom-left corner
+ pContent->SetPixelOnData(pScanContent, 0, aColorBottomLeft);
+ pAlpha->SetPixelOnData(pScanAlpha, 0, BitmapColor(nAlpha));
+
+ // y == nH - 1, bottom line left to right
+ for(x = 1; x < nW - 1; x++)
+ {
+ Color aMix(aColorBottomLeft);
+
+ aMix.Merge(aColorBottomRight, 255 - sal_uInt8(((x - 0)* 255) / nW));
+ pContent->SetPixelOnData(pScanContent, x, aMix);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+
+ // x == nW - 1, y == nH - 1, bottom-right corner
+ // #i123690# Caution! When nW is 1, x == nW is possible (!)
+ if(x < nW)
+ {
+ pContent->SetPixelOnData(pScanContent, x, aColorBottomRight);
+ pAlpha->SetPixelOnData(pScanAlpha, x, BitmapColor(nAlpha));
+ }
+ }
+
+ pContent.reset();
+ pAlpha.reset();
+
+ pBlendFrameCache->m_aLastResult = BitmapEx(aContent, aAlpha);
+ }
+ }
+
+ return pBlendFrameCache->m_aLastResult;
+}
+
+void BitmapEx::Replace(const Color& rSearchColor,
+ const Color& rReplaceColor,
+ sal_uInt8 nTolerance)
+{
+ maBitmap.Replace(rSearchColor, rReplaceColor, nTolerance);
+}
+
+void BitmapEx::Replace( const Color* pSearchColors,
+ const Color* pReplaceColors,
+ sal_uLong nColorCount,
+ sal_uInt8 const * pTols )
+{
+ maBitmap.Replace( pSearchColors, pReplaceColors, nColorCount, pTols );
+}
+
+void BitmapEx::ReplaceTransparency(const Color& rColor)
+{
+ if( IsTransparent() )
+ {
+ maBitmap.Replace( GetMask(), rColor );
+ maMask = Bitmap();
+ maBitmapSize = maBitmap.GetSizePixel();
+ maTransparentColor = Color();
+ meTransparent = TransparentType::NONE;
+ mbAlpha = false;
+ }
+}
+
+static Bitmap DetectEdges( const Bitmap& rBmp )
+{
+ constexpr sal_uInt8 cEdgeDetectThreshold = 128;
+ const Size aSize( rBmp.GetSizePixel() );
+ Bitmap aRetBmp;
+
+ if( ( aSize.Width() > 2 ) && ( aSize.Height() > 2 ) )
+ {
+ Bitmap aWorkBmp( rBmp );
+
+ if( aWorkBmp.Convert( BmpConversion::N8BitGreys ) )
+ {
+ bool bRet = false;
+
+ ScopedVclPtr<VirtualDevice> pVirDev(VclPtr<VirtualDevice>::Create());
+ pVirDev->SetOutputSizePixel(aSize);
+ Bitmap::ScopedReadAccess pReadAcc(aWorkBmp);
+
+ if( pReadAcc )
+ {
+ const long nWidth = aSize.Width();
+ const long nWidth2 = nWidth - 2;
+ const long nHeight = aSize.Height();
+ const long nHeight2 = nHeight - 2;
+ const long lThres2 = static_cast<long>(cEdgeDetectThreshold) * cEdgeDetectThreshold;
+ long nSum1;
+ long nSum2;
+ long lGray;
+
+ // initialize border with white pixels
+ pVirDev->SetLineColor( COL_WHITE );
+ pVirDev->DrawLine( Point(), Point( nWidth - 1, 0L ) );
+ pVirDev->DrawLine( Point( nWidth - 1, 0L ), Point( nWidth - 1, nHeight - 1 ) );
+ pVirDev->DrawLine( Point( nWidth - 1, nHeight - 1 ), Point( 0L, nHeight - 1 ) );
+ pVirDev->DrawLine( Point( 0, nHeight - 1 ), Point() );
+
+ for( long nY = 0, nY1 = 1, nY2 = 2; nY < nHeight2; nY++, nY1++, nY2++ )
+ {
+ Scanline pScanlineRead = pReadAcc->GetScanline( nY );
+ Scanline pScanlineRead1 = pReadAcc->GetScanline( nY1 );
+ Scanline pScanlineRead2 = pReadAcc->GetScanline( nY2 );
+ for( long nX = 0, nXDst = 1, nXTmp; nX < nWidth2; nX++, nXDst++ )
+ {
+ nXTmp = nX;
+
+ nSum2 = pReadAcc->GetIndexFromData( pScanlineRead, nXTmp++ );
+ nSum1 = -nSum2;
+ nSum2 += static_cast<long>(pReadAcc->GetIndexFromData( pScanlineRead, nXTmp++ )) << 1;
+ lGray = pReadAcc->GetIndexFromData( pScanlineRead, nXTmp );
+ nSum1 += lGray;
+ nSum2 += lGray;
+
+ nSum1 += static_cast<long>(pReadAcc->GetIndexFromData( pScanlineRead1, nXTmp )) << 1;
+ nXTmp -= 2;
+ nSum1 -= static_cast<long>(pReadAcc->GetIndexFromData( pScanlineRead1, nXTmp )) << 1;
+
+ lGray = -static_cast<long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp++ ));
+ nSum1 += lGray;
+ nSum2 += lGray;
+ nSum2 -= static_cast<long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp++ )) << 1;
+ lGray = static_cast<long>(pReadAcc->GetIndexFromData( pScanlineRead2, nXTmp ));
+ nSum1 += lGray;
+ nSum2 -= lGray;
+
+ if( ( nSum1 * nSum1 + nSum2 * nSum2 ) < lThres2 )
+ pVirDev->DrawPixel( Point(nXDst, nY), COL_WHITE );
+ else
+ pVirDev->DrawPixel( Point(nXDst, nY), COL_BLACK );
+ }
+ }
+
+ bRet = true;
+ }
+
+ pReadAcc.reset();
+
+ if( bRet )
+ aRetBmp = pVirDev->GetBitmap(Point(0,0), aSize);
+ }
+ }
+
+ if( !aRetBmp )
+ aRetBmp = rBmp;
+ else
+ {
+ aRetBmp.SetPrefMapMode( rBmp.GetPrefMapMode() );
+ aRetBmp.SetPrefSize( rBmp.GetPrefSize() );
+ }
+
+ return aRetBmp;
+}
+
+/** Get contours in image */
+tools::Polygon BitmapEx::GetContour( bool bContourEdgeDetect,
+ const tools::Rectangle* pWorkRectPixel )
+{
+ Bitmap aWorkBmp;
+ tools::Polygon aRetPoly;
+ tools::Rectangle aWorkRect( Point(), maBitmap.GetSizePixel() );
+
+ if( pWorkRectPixel )
+ aWorkRect.Intersection( *pWorkRectPixel );
+
+ aWorkRect.Justify();
+
+ if( ( aWorkRect.GetWidth() > 4 ) && ( aWorkRect.GetHeight() > 4 ) )
+ {
+ // if the flag is set, we need to detect edges
+ if( bContourEdgeDetect )
+ aWorkBmp = DetectEdges( maBitmap );
+ else
+ aWorkBmp = maBitmap;
+
+ BitmapReadAccess* pAcc = aWorkBmp.AcquireReadAccess();
+
+ const long nWidth = pAcc ? pAcc->Width() : 0;
+ const long nHeight = pAcc ? pAcc->Height() : 0;
+
+ if (pAcc && nWidth && nHeight)
+ {
+ const Size& rPrefSize = aWorkBmp.GetPrefSize();
+ const double fFactorX = static_cast<double>(rPrefSize.Width()) / nWidth;
+ const double fFactorY = static_cast<double>(rPrefSize.Height()) / nHeight;
+ const long nStartX1 = aWorkRect.Left() + 1;
+ const long nEndX1 = aWorkRect.Right();
+ const long nStartX2 = nEndX1 - 1;
+ const long nStartY1 = aWorkRect.Top() + 1;
+ const long nEndY1 = aWorkRect.Bottom();
+ std::unique_ptr<Point[]> pPoints1;
+ std::unique_ptr<Point[]> pPoints2;
+ long nX, nY;
+ sal_uInt16 nPolyPos = 0;
+ const BitmapColor aBlack = pAcc->GetBestMatchingColor( COL_BLACK );
+
+ pPoints1.reset(new Point[ nHeight ]);
+ pPoints2.reset(new Point[ nHeight ]);
+
+ for ( nY = nStartY1; nY < nEndY1; nY++ )
+ {
+ nX = nStartX1;
+ Scanline pScanline = pAcc->GetScanline( nY );
+
+ // scan row from left to right
+ while( nX < nEndX1 )
+ {
+ if( aBlack == pAcc->GetPixelFromData( pScanline, nX ) )
+ {
+ pPoints1[ nPolyPos ] = Point( nX, nY );
+ nX = nStartX2;
+
+ // this loop always breaks eventually as there is at least one pixel
+ while( true )
+ {
+ if( aBlack == pAcc->GetPixelFromData( pScanline, nX ) )
+ {
+ pPoints2[ nPolyPos ] = Point( nX, nY );
+ break;
+ }
+
+ nX--;
+ }
+
+ nPolyPos++;
+ break;
+ }
+
+ nX++;
+ }
+ }
+
+ const sal_uInt16 nNewSize1 = nPolyPos << 1;
+
+ aRetPoly = tools::Polygon( nPolyPos, pPoints1.get() );
+ aRetPoly.SetSize( nNewSize1 + 1 );
+ aRetPoly[ nNewSize1 ] = aRetPoly[ 0 ];
+
+ for( sal_uInt16 j = nPolyPos; nPolyPos < nNewSize1; )
+ aRetPoly[ nPolyPos++ ] = pPoints2[ --j ];
+
+ if( ( fFactorX != 0. ) && ( fFactorY != 0. ) )
+ aRetPoly.Scale( fFactorX, fFactorY );
+ }
+
+ Bitmap::ReleaseAccess(pAcc);
+ }
+
+ return aRetPoly;
+}
+
+void BitmapEx::setAlphaFrom( sal_uInt8 cIndexFrom, sal_Int8 nAlphaTo )
+{
+ AlphaMask aAlphaMask(GetAlpha());
+ BitmapScopedWriteAccess pWriteAccess(aAlphaMask);
+ Bitmap::ScopedReadAccess pReadAccess(maBitmap);
+ assert( pReadAccess.get() && pWriteAccess.get() );
+ if ( pReadAccess.get() && pWriteAccess.get() )
+ {
+ for ( long nY = 0; nY < pReadAccess->Height(); nY++ )
+ {
+ Scanline pScanline = pWriteAccess->GetScanline( nY );
+ Scanline pScanlineRead = pReadAccess->GetScanline( nY );
+ for ( long nX = 0; nX < pReadAccess->Width(); nX++ )
+ {
+ const sal_uInt8 cIndex = pReadAccess->GetPixelFromData( pScanlineRead, nX ).GetIndex();
+ if ( cIndex == cIndexFrom )
+ pWriteAccess->SetPixelOnData( pScanline, nX, BitmapColor(nAlphaTo) );
+ }
+ }
+ }
+ *this = BitmapEx( GetBitmap(), aAlphaMask );
+}
+
+void BitmapEx::AdjustTransparency(sal_uInt8 cTrans)
+{
+ AlphaMask aAlpha;
+
+ if (!IsTransparent())
+ {
+ aAlpha = AlphaMask(GetSizePixel(), &cTrans);
+ }
+ else if( !IsAlpha() )
+ {
+ aAlpha = GetMask();
+ aAlpha.Replace( 0, cTrans );
+ }
+ else
+ {
+ aAlpha = GetAlpha();
+ BitmapScopedWriteAccess pA(aAlpha);
+ assert(pA);
+
+ if( !pA )
+ return;
+
+ sal_uLong nTrans = cTrans, nNewTrans;
+ const long nWidth = pA->Width(), nHeight = pA->Height();
+
+ if( pA->GetScanlineFormat() == ScanlineFormat::N8BitPal )
+ {
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pAScan = pA->GetScanline( nY );
+
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ nNewTrans = nTrans + *pAScan;
+ *pAScan++ = static_cast<sal_uInt8>( ( nNewTrans & 0xffffff00 ) ? 255 : nNewTrans );
+ }
+ }
+ }
+ else
+ {
+ BitmapColor aAlphaValue( 0 );
+
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pA->GetScanline( nY );
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ nNewTrans = nTrans + pA->GetIndexFromData( pScanline, nX );
+ aAlphaValue.SetIndex( static_cast<sal_uInt8>( ( nNewTrans & 0xffffff00 ) ? 255 : nNewTrans ) );
+ pA->SetPixelOnData( pScanline, nX, aAlphaValue );
+ }
+ }
+ }
+ }
+ *this = BitmapEx( GetBitmap(), aAlpha );
+}
+
+void BitmapEx::CombineMaskOr(Color maskColor, sal_uInt8 nTol)
+{
+ Bitmap aNewMask = maBitmap.CreateMask( maskColor, nTol );
+ if ( IsTransparent() )
+ aNewMask.CombineSimple( maMask, BmpCombine::Or );
+ maMask = aNewMask;
+ meTransparent = TransparentType::Bitmap;
+}
+
+/**
+ * Retrieves the color model data we need for the XImageConsumer stuff.
+ */
+void BitmapEx::GetColorModel(css::uno::Sequence< sal_Int32 >& rRGBPalette,
+ sal_uInt32& rnRedMask, sal_uInt32& rnGreenMask, sal_uInt32& rnBlueMask, sal_uInt32& rnAlphaMask, sal_uInt32& rnTransparencyIndex,
+ sal_uInt32& rnWidth, sal_uInt32& rnHeight, sal_uInt8& rnBitCount)
+{
+ Bitmap::ScopedReadAccess pReadAccess( maBitmap );
+ assert( pReadAccess );
+
+ if( pReadAccess->HasPalette() )
+ {
+ sal_uInt16 nPalCount = pReadAccess->GetPaletteEntryCount();
+
+ if( nPalCount )
+ {
+ rRGBPalette = css::uno::Sequence< sal_Int32 >( nPalCount + 1 );
+
+ sal_Int32* pTmp = rRGBPalette.getArray();
+
+ for( sal_uInt32 i = 0; i < nPalCount; i++, pTmp++ )
+ {
+ const BitmapColor& rCol = pReadAccess->GetPaletteColor( static_cast<sal_uInt16>(i) );
+
+ *pTmp = static_cast<sal_Int32>(rCol.GetRed()) << sal_Int32(24);
+ *pTmp |= static_cast<sal_Int32>(rCol.GetGreen()) << sal_Int32(16);
+ *pTmp |= static_cast<sal_Int32>(rCol.GetBlue()) << sal_Int32(8);
+ *pTmp |= sal_Int32(0x000000ffL);
+ }
+
+ if( IsTransparent() )
+ {
+ // append transparent entry
+ *pTmp = sal_Int32(0xffffff00L);
+ rnTransparencyIndex = nPalCount;
+ nPalCount++;
+ }
+ else
+ rnTransparencyIndex = 0;
+ }
+ }
+ else
+ {
+ rnRedMask = 0xff000000UL;
+ rnGreenMask = 0x00ff0000UL;
+ rnBlueMask = 0x0000ff00UL;
+ rnAlphaMask = 0x000000ffUL;
+ rnTransparencyIndex = 0;
+ }
+
+ rnWidth = pReadAccess->Width();
+ rnHeight = pReadAccess->Height();
+ rnBitCount = pReadAccess->GetBitCount();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/bmpacc.cxx b/vcl/source/gdi/bmpacc.cxx
new file mode 100644
index 000000000..6910c6e3e
--- /dev/null
+++ b/vcl/source/gdi/bmpacc.cxx
@@ -0,0 +1,464 @@
+/* -*- 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/bitmap.hxx>
+#include <vcl/bitmapaccess.hxx>
+
+#include <bitmapwriteaccess.hxx>
+#include <salbmp.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+#include <string.h>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+BitmapInfoAccess::BitmapInfoAccess( Bitmap& rBitmap, BitmapAccessMode nMode ) :
+ mpBuffer ( nullptr ),
+ mnAccessMode ( nMode )
+{
+ std::shared_ptr<SalBitmap> xImpBmp = rBitmap.ImplGetSalBitmap();
+
+ assert( xImpBmp && "Forbidden Access to empty bitmap!" );
+
+ if( !xImpBmp )
+ return;
+
+ if (mnAccessMode == BitmapAccessMode::Write)
+ {
+ xImpBmp->DropScaledCache();
+
+ if (xImpBmp.use_count() > 2)
+ {
+ xImpBmp.reset();
+ rBitmap.ImplMakeUnique();
+ xImpBmp = rBitmap.ImplGetSalBitmap();
+ }
+ }
+
+ mpBuffer = xImpBmp->AcquireBuffer( mnAccessMode );
+
+ if( !mpBuffer )
+ {
+ std::shared_ptr<SalBitmap> xNewImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
+ if (xNewImpBmp->Create(*xImpBmp, rBitmap.GetBitCount()))
+ {
+ xImpBmp = xNewImpBmp;
+ rBitmap.ImplSetSalBitmap( xImpBmp );
+ mpBuffer = xImpBmp->AcquireBuffer( mnAccessMode );
+ }
+ }
+
+ maBitmap = rBitmap;
+}
+
+BitmapInfoAccess::~BitmapInfoAccess()
+{
+ std::shared_ptr<SalBitmap> xImpBmp = maBitmap.ImplGetSalBitmap();
+
+ if (mpBuffer && xImpBmp)
+ {
+ xImpBmp->ReleaseBuffer( mpBuffer, mnAccessMode );
+ }
+}
+
+sal_uInt16 BitmapInfoAccess::GetBestPaletteIndex( const BitmapColor& rBitmapColor ) const
+{
+ return( HasPalette() ? mpBuffer->maPalette.GetBestIndex( rBitmapColor ) : 0 );
+}
+
+BitmapReadAccess::BitmapReadAccess( Bitmap& rBitmap, BitmapAccessMode nMode ) :
+ BitmapInfoAccess( rBitmap, nMode ),
+ mFncGetPixel ( nullptr ),
+ mFncSetPixel ( nullptr )
+{
+ if (!mpBuffer)
+ return;
+
+ const std::shared_ptr<SalBitmap>& xImpBmp = rBitmap.ImplGetSalBitmap();
+ if (!xImpBmp)
+ return;
+
+ maColorMask = mpBuffer->maColorMask;
+
+ bool bOk = ImplSetAccessPointers(RemoveScanline(mpBuffer->mnFormat));
+
+ if (!bOk)
+ {
+ xImpBmp->ReleaseBuffer( mpBuffer, mnAccessMode );
+ mpBuffer = nullptr;
+ }
+}
+
+BitmapReadAccess::~BitmapReadAccess()
+{
+}
+
+namespace
+{
+ bool Bitmap32IsPreMultipled()
+ {
+ auto pBackendCapabilities = ImplGetSVData()->mpDefInst->GetBackendCapabilities();
+ return pBackendCapabilities->mbSupportsBitmap32;
+ }
+}
+
+bool BitmapReadAccess::ImplSetAccessPointers( ScanlineFormat nFormat )
+{
+ bool bRet = true;
+
+ switch( nFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ {
+ mFncGetPixel = GetPixelForN1BitMsbPal;
+ mFncSetPixel = SetPixelForN1BitMsbPal;
+ }
+ break;
+ case ScanlineFormat::N1BitLsbPal:
+ {
+ mFncGetPixel = GetPixelForN1BitLsbPal;
+ mFncSetPixel = SetPixelForN1BitLsbPal;
+ }
+ break;
+ case ScanlineFormat::N4BitMsnPal:
+ {
+ mFncGetPixel = GetPixelForN4BitMsnPal;
+ mFncSetPixel = SetPixelForN4BitMsnPal;
+ }
+ break;
+ case ScanlineFormat::N4BitLsnPal:
+ {
+ mFncGetPixel = GetPixelForN4BitLsnPal;
+ mFncSetPixel = SetPixelForN4BitLsnPal;
+ }
+ break;
+ case ScanlineFormat::N8BitPal:
+ {
+ mFncGetPixel = GetPixelForN8BitPal;
+ mFncSetPixel = SetPixelForN8BitPal;
+ }
+ break;
+ case ScanlineFormat::N8BitTcMask:
+ {
+ mFncGetPixel = GetPixelForN8BitTcMask;
+ mFncSetPixel = SetPixelForN8BitTcMask;
+ }
+ break;
+ case ScanlineFormat::N24BitTcBgr:
+ {
+ mFncGetPixel = GetPixelForN24BitTcBgr;
+ mFncSetPixel = SetPixelForN24BitTcBgr;
+ }
+ break;
+ case ScanlineFormat::N24BitTcRgb:
+ {
+ mFncGetPixel = GetPixelForN24BitTcRgb;
+ mFncSetPixel = SetPixelForN24BitTcRgb;
+ }
+ break;
+ case ScanlineFormat::N32BitTcAbgr:
+ {
+ if (Bitmap32IsPreMultipled())
+ {
+ mFncGetPixel = GetPixelForN32BitTcAbgr;
+ mFncSetPixel = SetPixelForN32BitTcAbgr;
+ }
+ else
+ {
+ mFncGetPixel = GetPixelForN32BitTcXbgr;
+ mFncSetPixel = SetPixelForN32BitTcXbgr;
+ }
+ }
+ break;
+ case ScanlineFormat::N32BitTcArgb:
+ {
+ if (Bitmap32IsPreMultipled())
+ {
+ mFncGetPixel = GetPixelForN32BitTcArgb;
+ mFncSetPixel = SetPixelForN32BitTcArgb;
+ }
+ else
+ {
+ mFncGetPixel = GetPixelForN32BitTcXrgb;
+ mFncSetPixel = SetPixelForN32BitTcXrgb;
+ }
+ }
+ break;
+ case ScanlineFormat::N32BitTcBgra:
+ {
+ if (Bitmap32IsPreMultipled())
+ {
+ mFncGetPixel = GetPixelForN32BitTcBgra;
+ mFncSetPixel = SetPixelForN32BitTcBgra;
+ }
+ else
+ {
+ mFncGetPixel = GetPixelForN32BitTcBgrx;
+ mFncSetPixel = SetPixelForN32BitTcBgrx;
+ }
+ }
+ break;
+ case ScanlineFormat::N32BitTcRgba:
+ {
+ if (Bitmap32IsPreMultipled())
+ {
+ mFncGetPixel = GetPixelForN32BitTcRgba;
+ mFncSetPixel = SetPixelForN32BitTcRgba;
+ }
+ else
+ {
+ mFncGetPixel = GetPixelForN32BitTcRgbx;
+ mFncSetPixel = SetPixelForN32BitTcRgbx;
+ }
+ }
+ break;
+ case ScanlineFormat::N32BitTcMask:
+ {
+ mFncGetPixel = GetPixelForN32BitTcMask;
+ mFncSetPixel = SetPixelForN32BitTcMask;
+ }
+ break;
+
+ default:
+ bRet = false;
+ break;
+ }
+
+ return bRet;
+}
+
+BitmapColor BitmapReadAccess::GetInterpolatedColorWithFallback( double fY, double fX, const BitmapColor& rFallback ) const
+{
+ // ask directly doubles >= 0.0 here to avoid rounded values of 0 at small negative
+ // double values, e.g. static_cast< sal_Int32 >(-0.25) is 0, not -1, but *has* to be outside (!)
+ if(mpBuffer && fX >= 0.0 && fY >= 0.0)
+ {
+ const sal_Int64 nX(static_cast<sal_Int64>(fX));
+ const sal_Int64 nY(static_cast<sal_Int64>(fY));
+
+ if(nX < mpBuffer->mnWidth && nY < mpBuffer->mnHeight)
+ {
+ // get base-return value from inside pixel
+ BitmapColor aRetval(GetColor(nY, nX));
+
+ // calculate deltas and indices for neighbour accesses
+ sal_Int16 nDeltaX((fX - (nX + 0.5)) * 255.0); // [-255 .. 255]
+ sal_Int16 nDeltaY((fY - (nY + 0.5)) * 255.0); // [-255 .. 255]
+ sal_Int16 nIndX(0);
+ sal_Int16 nIndY(0);
+
+ if(nDeltaX > 0)
+ {
+ nIndX = nX + 1;
+ }
+ else
+ {
+ nIndX = nX - 1;
+ nDeltaX = -nDeltaX;
+ }
+
+ if(nDeltaY > 0)
+ {
+ nIndY = nY + 1;
+ }
+ else
+ {
+ nIndY = nY - 1;
+ nDeltaY = -nDeltaY;
+ }
+
+ // get right/left neighbour
+ BitmapColor aXCol(rFallback);
+
+ if(nDeltaX && nIndX >= 0 && nIndX < mpBuffer->mnWidth)
+ {
+ aXCol = GetColor(nY, nIndX);
+ }
+
+ // get top/bottom neighbour
+ BitmapColor aYCol(rFallback);
+
+ if(nDeltaY && nIndY >= 0 && nIndY < mpBuffer->mnHeight)
+ {
+ aYCol = GetColor(nIndY, nX);
+ }
+
+ // get one of four edge neighbours
+ BitmapColor aXYCol(rFallback);
+
+ if(nDeltaX && nDeltaY && nIndX >=0 && nIndY >= 0 && nIndX < mpBuffer->mnWidth && nIndY < mpBuffer->mnHeight)
+ {
+ aXYCol = GetColor(nIndY, nIndX);
+ }
+
+ // merge return value with right/left neighbour
+ if(aXCol != aRetval)
+ {
+ aRetval.Merge(aXCol, 255 - nDeltaX);
+ }
+
+ // merge top/bottom neighbour with edge
+ if(aYCol != aXYCol)
+ {
+ aYCol.Merge(aXYCol, 255 - nDeltaX);
+ }
+
+ // merge return value with already merged top/bottom neighbour
+ if(aRetval != aYCol)
+ {
+ aRetval.Merge(aYCol, 255 - nDeltaY);
+ }
+
+ return aRetval;
+ }
+ }
+
+ return rFallback;
+}
+
+BitmapColor BitmapReadAccess::GetColorWithFallback( double fY, double fX, const BitmapColor& rFallback ) const
+{
+ // ask directly doubles >= 0.0 here to avoid rounded values of 0 at small negative
+ // double values, e.g. static_cast< sal_Int32 >(-0.25) is 0, not -1, but *has* to be outside (!)
+ if(mpBuffer && fX >= 0.0 && fY >= 0.0)
+ {
+ const sal_Int32 nX(static_cast< sal_Int32 >(fX));
+ const sal_Int32 nY(static_cast< sal_Int32 >(fY));
+
+ if(nX < mpBuffer->mnWidth && nY < mpBuffer->mnHeight)
+ {
+ return GetColor(nY, nX);
+ }
+ }
+
+ return rFallback;
+}
+
+BitmapWriteAccess::BitmapWriteAccess(Bitmap& rBitmap)
+ : BitmapReadAccess(rBitmap, BitmapAccessMode::Write)
+{
+}
+
+BitmapWriteAccess::~BitmapWriteAccess()
+{
+}
+
+void BitmapWriteAccess::CopyScanline( long nY, const BitmapReadAccess& rReadAcc )
+{
+ assert(nY >= 0 && nY < mpBuffer->mnHeight && "y-coordinate in destination out of range!");
+ SAL_WARN_IF( nY >= rReadAcc.Height(), "vcl", "y-coordinate in source out of range!" );
+ SAL_WARN_IF( ( !HasPalette() || !rReadAcc.HasPalette() ) && ( HasPalette() || rReadAcc.HasPalette() ), "vcl", "No copying possible between palette bitmap and TC bitmap!" );
+
+ if( ( GetScanlineFormat() == rReadAcc.GetScanlineFormat() ) &&
+ ( GetScanlineSize() >= rReadAcc.GetScanlineSize() ) )
+ {
+ memcpy(GetScanline(nY), rReadAcc.GetScanline(nY), rReadAcc.GetScanlineSize());
+ }
+ else
+ {
+ // TODO: use fastbmp infrastructure
+ Scanline pScanline = GetScanline( nY );
+ Scanline pScanlineRead = rReadAcc.GetScanline(nY);
+ for( long nX = 0, nWidth = std::min( mpBuffer->mnWidth, rReadAcc.Width() ); nX < nWidth; nX++ )
+ SetPixelOnData( pScanline, nX, rReadAcc.GetPixelFromData( pScanlineRead, nX ) );
+ }
+}
+
+void BitmapWriteAccess::CopyScanline( long nY, ConstScanline aSrcScanline,
+ ScanlineFormat nSrcScanlineFormat, sal_uInt32 nSrcScanlineSize )
+{
+ const ScanlineFormat nFormat = RemoveScanline( nSrcScanlineFormat );
+
+ assert(nY >= 0 && nY < mpBuffer->mnHeight && "y-coordinate in destination out of range!");
+ DBG_ASSERT( ( HasPalette() && nFormat <= ScanlineFormat::N8BitPal ) ||
+ ( !HasPalette() && nFormat > ScanlineFormat::N8BitPal ),
+ "No copying possible between palette and non palette scanlines!" );
+
+ const sal_uLong nCount = std::min( GetScanlineSize(), nSrcScanlineSize );
+
+ if( nCount )
+ {
+ if( GetScanlineFormat() == RemoveScanline( nSrcScanlineFormat ) )
+ memcpy(GetScanline(nY), aSrcScanline, nCount);
+ else
+ {
+ DBG_ASSERT( nFormat != ScanlineFormat::N8BitTcMask &&
+ nFormat != ScanlineFormat::N32BitTcMask,
+ "No support for pixel formats with color masks yet!" );
+
+ // TODO: use fastbmp infrastructure
+ FncGetPixel pFncGetPixel;
+
+ switch( nFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal: pFncGetPixel = GetPixelForN1BitMsbPal; break;
+ case ScanlineFormat::N1BitLsbPal: pFncGetPixel = GetPixelForN1BitLsbPal; break;
+ case ScanlineFormat::N4BitMsnPal: pFncGetPixel = GetPixelForN4BitMsnPal; break;
+ case ScanlineFormat::N4BitLsnPal: pFncGetPixel = GetPixelForN4BitLsnPal; break;
+ case ScanlineFormat::N8BitPal: pFncGetPixel = GetPixelForN8BitPal; break;
+ case ScanlineFormat::N8BitTcMask: pFncGetPixel = GetPixelForN8BitTcMask; break;
+ case ScanlineFormat::N24BitTcBgr: pFncGetPixel = GetPixelForN24BitTcBgr; break;
+ case ScanlineFormat::N24BitTcRgb: pFncGetPixel = GetPixelForN24BitTcRgb; break;
+ case ScanlineFormat::N32BitTcAbgr:
+ if (Bitmap32IsPreMultipled())
+ pFncGetPixel = GetPixelForN32BitTcAbgr;
+ else
+ pFncGetPixel = GetPixelForN32BitTcXbgr;
+ break;
+ case ScanlineFormat::N32BitTcArgb:
+ if (Bitmap32IsPreMultipled())
+ pFncGetPixel = GetPixelForN32BitTcArgb;
+ else
+ pFncGetPixel = GetPixelForN32BitTcXrgb;
+ break;
+ case ScanlineFormat::N32BitTcBgra:
+ if (Bitmap32IsPreMultipled())
+ pFncGetPixel = GetPixelForN32BitTcBgra;
+ else
+ pFncGetPixel = GetPixelForN32BitTcBgrx;
+ break;
+ case ScanlineFormat::N32BitTcRgba:
+ if (Bitmap32IsPreMultipled())
+ pFncGetPixel = GetPixelForN32BitTcRgba;
+ else
+ pFncGetPixel = GetPixelForN32BitTcRgbx;
+ break;
+ case ScanlineFormat::N32BitTcMask:
+ pFncGetPixel = GetPixelForN32BitTcMask;
+ break;
+
+ default:
+ assert(false);
+ pFncGetPixel = nullptr;
+ break;
+ }
+
+ if( pFncGetPixel )
+ {
+ const ColorMask aDummyMask;
+ Scanline pScanline = GetScanline(nY);
+ for (long nX = 0, nWidth = mpBuffer->mnWidth; nX < nWidth; ++nX)
+ SetPixelOnData(pScanline, nX, pFncGetPixel(aSrcScanline, nX, aDummyMask));
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/bmpacc2.cxx b/vcl/source/gdi/bmpacc2.cxx
new file mode 100644
index 000000000..9210d5222
--- /dev/null
+++ b/vcl/source/gdi/bmpacc2.cxx
@@ -0,0 +1,362 @@
+/* -*- 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/bitmapaccess.hxx>
+#include <vcl/BitmapTools.hxx>
+
+BitmapColor BitmapReadAccess::GetPixelForN1BitMsbPal(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ return BitmapColor( pScanline[ nX >> 3 ] & ( 1 << ( 7 - ( nX & 7 ) ) ) ? 1 : 0 );
+}
+
+void BitmapReadAccess::SetPixelForN1BitMsbPal(const Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ sal_uInt8& rByte = pScanline[ nX >> 3 ];
+
+ if ( rBitmapColor.GetIndex() & 1 )
+ rByte |= 1 << ( 7 - ( nX & 7 ) );
+ else
+ rByte &= ~( 1 << ( 7 - ( nX & 7 ) ) );
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN1BitLsbPal(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ return BitmapColor( pScanline[ nX >> 3 ] & ( 1 << ( nX & 7 ) ) ? 1 : 0 );
+}
+
+void BitmapReadAccess::SetPixelForN1BitLsbPal(const Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ sal_uInt8& rByte = pScanline[ nX >> 3 ];
+
+ if ( rBitmapColor.GetIndex() & 1 )
+ rByte |= 1 << ( nX & 7 );
+ else
+ rByte &= ~( 1 << ( nX & 7 ) );
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN4BitMsnPal(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ return BitmapColor( ( pScanline[ nX >> 1 ] >> ( nX & 1 ? 0 : 4 ) ) & 0x0f );
+}
+
+void BitmapReadAccess::SetPixelForN4BitMsnPal(const Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ sal_uInt8& rByte = pScanline[ nX >> 1 ];
+
+ if ( nX & 1 )
+ {
+ rByte &= 0xf0;
+ rByte |= ( rBitmapColor.GetIndex() & 0x0f );
+ }
+ else
+ {
+ rByte &= 0x0f;
+ rByte |= ( rBitmapColor.GetIndex() << 4 );
+ }
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN4BitLsnPal(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ return BitmapColor( ( pScanline[ nX >> 1 ] >> ( nX & 1 ? 4 : 0 ) ) & 0x0f );
+}
+
+void BitmapReadAccess::SetPixelForN4BitLsnPal(const Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ sal_uInt8& rByte = pScanline[ nX >> 1 ];
+
+ if ( nX & 1 )
+ {
+ rByte &= 0x0f;
+ rByte |= ( rBitmapColor.GetIndex() << 4 );
+ }
+ else
+ {
+ rByte &= 0xf0;
+ rByte |= ( rBitmapColor.GetIndex() & 0x0f );
+ }
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN8BitPal(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ return BitmapColor( pScanline[ nX ] );
+}
+
+void BitmapReadAccess::SetPixelForN8BitPal(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline[ nX ] = rBitmapColor.GetIndex();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN8BitTcMask(ConstScanline pScanline, long nX, const ColorMask& rMask)
+{
+ BitmapColor aColor;
+ rMask.GetColorFor8Bit( aColor, pScanline + nX );
+ return aColor;
+}
+
+void BitmapReadAccess::SetPixelForN8BitTcMask(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask& rMask)
+{
+ rMask.SetColorFor8Bit( rBitmapColor, pScanline + nX );
+}
+
+
+BitmapColor BitmapReadAccess::GetPixelForN24BitTcBgr(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + nX * 3;
+ aBitmapColor.SetBlue( *pScanline++ );
+ aBitmapColor.SetGreen( *pScanline++ );
+ aBitmapColor.SetRed( *pScanline );
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN24BitTcBgr(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 3;
+ *pScanline++ = rBitmapColor.GetBlue();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline = rBitmapColor.GetRed();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN24BitTcRgb(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + nX * 3;
+ aBitmapColor.SetRed( *pScanline++ );
+ aBitmapColor.SetGreen( *pScanline++ );
+ aBitmapColor.SetBlue( *pScanline );
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN24BitTcRgb(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 3;
+ *pScanline++ = rBitmapColor.GetRed();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline = rBitmapColor.GetBlue();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcAbgr(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 a = *pScanline++;
+ sal_uInt8 b = *pScanline++;
+ sal_uInt8 g = *pScanline++;
+ sal_uInt8 r = *pScanline;
+
+ return BitmapColor(
+ vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a),
+ vcl::bitmap::unpremultiply(b, a),
+ 0xFF - a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcXbgr(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + ( nX << 2 ) + 1;
+ aBitmapColor.SetBlue( *pScanline++ );
+ aBitmapColor.SetGreen( *pScanline++ );
+ aBitmapColor.SetRed( *pScanline );
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcAbgr(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = 0xFF - rBitmapColor.GetAlpha();
+ *pScanline++ = alpha;
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetBlue(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetGreen(), alpha);
+ *pScanline = vcl::bitmap::premultiply(rBitmapColor.GetRed(), alpha);
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcXbgr(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + ( nX << 2 );
+ *pScanline++ = 0xFF;
+ *pScanline++ = rBitmapColor.GetBlue();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline = rBitmapColor.GetRed();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcArgb(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 a = *pScanline++;
+ sal_uInt8 r = *pScanline++;
+ sal_uInt8 g = *pScanline++;
+ sal_uInt8 b = *pScanline;
+
+ return BitmapColor(
+ vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a),
+ vcl::bitmap::unpremultiply(b, a),
+ 0xFF - a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcXrgb(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + ( nX << 2 ) + 1;
+ aBitmapColor.SetRed( *pScanline++ );
+ aBitmapColor.SetGreen( *pScanline++ );
+ aBitmapColor.SetBlue( *pScanline );
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcArgb(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = 0xFF - rBitmapColor.GetAlpha();
+ *pScanline++ = alpha;
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetRed(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetGreen(), alpha);
+ *pScanline = vcl::bitmap::premultiply(rBitmapColor.GetBlue(), alpha);
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcXrgb(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + ( nX << 2 );
+ *pScanline++ = 0xFF;
+ *pScanline++ = rBitmapColor.GetRed();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline = rBitmapColor.GetBlue();
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcBgra(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 b = *pScanline++;
+ sal_uInt8 g = *pScanline++;
+ sal_uInt8 r = *pScanline++;
+ sal_uInt8 a = *pScanline;
+
+ return BitmapColor(
+ vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a),
+ vcl::bitmap::unpremultiply(b, a),
+ 0xFF - a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcBgrx(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + ( nX << 2 );
+ aBitmapColor.SetBlue( *pScanline++ );
+ aBitmapColor.SetGreen( *pScanline++ );
+ aBitmapColor.SetRed( *pScanline );
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcBgra(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = 0xFF - rBitmapColor.GetAlpha();
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetBlue(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetGreen(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetRed(), alpha);
+ *pScanline = alpha;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcBgrx(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + ( nX << 2 );
+ *pScanline++ = rBitmapColor.GetBlue();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline++ = rBitmapColor.GetRed();
+ *pScanline = 0xFF;
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcRgba(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 r = *pScanline++;
+ sal_uInt8 g = *pScanline++;
+ sal_uInt8 b = *pScanline++;
+ sal_uInt8 a = *pScanline;
+
+ return BitmapColor(
+ vcl::bitmap::unpremultiply(r, a),
+ vcl::bitmap::unpremultiply(g, a),
+ vcl::bitmap::unpremultiply(b, a),
+ 0xFF - a);
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcRgbx(ConstScanline pScanline, long nX, const ColorMask&)
+{
+ BitmapColor aBitmapColor;
+
+ pScanline = pScanline + ( nX << 2 );
+ aBitmapColor.SetRed( *pScanline++ );
+ aBitmapColor.SetGreen( *pScanline++ );
+ aBitmapColor.SetBlue( *pScanline );
+
+ return aBitmapColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcRgba(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + nX * 4;
+
+ sal_uInt8 alpha = 0xFF - rBitmapColor.GetAlpha();
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetRed(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetGreen(), alpha);
+ *pScanline++ = vcl::bitmap::premultiply(rBitmapColor.GetBlue(), alpha);
+ *pScanline = alpha;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcRgbx(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask&)
+{
+ pScanline = pScanline + ( nX << 2 );
+ *pScanline++ = rBitmapColor.GetRed();
+ *pScanline++ = rBitmapColor.GetGreen();
+ *pScanline++ = rBitmapColor.GetBlue();
+ *pScanline = 0xFF;
+}
+
+BitmapColor BitmapReadAccess::GetPixelForN32BitTcMask(ConstScanline pScanline, long nX, const ColorMask& rMask)
+{
+ BitmapColor aColor;
+ rMask.GetColorFor32Bit( aColor, pScanline + ( nX << 2 ) );
+ return aColor;
+}
+
+void BitmapReadAccess::SetPixelForN32BitTcMask(Scanline pScanline, long nX, const BitmapColor& rBitmapColor, const ColorMask& rMask)
+{
+ rMask.SetColorFor32Bit( rBitmapColor, pScanline + ( nX << 2 ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/bmpacc3.cxx b/vcl/source/gdi/bmpacc3.cxx
new file mode 100644
index 000000000..f2fc66427
--- /dev/null
+++ b/vcl/source/gdi/bmpacc3.cxx
@@ -0,0 +1,278 @@
+/* -*- 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/bitmap.hxx>
+
+#include <bmpfast.hxx>
+#include <bitmapwriteaccess.hxx>
+
+void BitmapWriteAccess::SetLineColor( const Color& rColor )
+{
+ if (rColor.GetTransparency() == 255)
+ {
+ mpLineColor.reset();
+ }
+ else
+ {
+ if (HasPalette())
+ {
+ mpLineColor = BitmapColor(static_cast<sal_uInt8>(GetBestPaletteIndex(rColor)));
+ }
+ else
+ {
+ mpLineColor = BitmapColor(rColor);
+ }
+ }
+}
+
+void BitmapWriteAccess::SetFillColor()
+{
+ mpFillColor.reset();
+}
+
+void BitmapWriteAccess::SetFillColor( const Color& rColor )
+{
+ if (rColor.GetTransparency() == 255)
+ {
+ mpFillColor.reset();
+ }
+ else
+ {
+ if (HasPalette())
+ {
+ mpFillColor = BitmapColor(static_cast<sal_uInt8>(GetBestPaletteIndex(rColor)));
+ }
+ else
+ {
+ mpFillColor = BitmapColor(rColor);
+ }
+ }
+}
+
+void BitmapWriteAccess::Erase( const Color& rColor )
+{
+ // convert the color format from RGB to palette index if needed
+ // TODO: provide and use Erase( BitmapColor& method)
+ BitmapColor aColor = rColor;
+ if (HasPalette())
+ {
+ aColor = BitmapColor(static_cast<sal_uInt8>(GetBestPaletteIndex(rColor)));
+ }
+
+ // try fast bitmap method first
+ if (ImplFastEraseBitmap(*mpBuffer, aColor))
+ return;
+
+ tools::Rectangle aRect(Point(), maBitmap.GetSizePixel());
+ if (aRect.IsEmpty())
+ return;
+ // clear the bitmap by filling the first line pixel by pixel,
+ // then dup the first line over each other line
+ Scanline pFirstScanline = GetScanline(0);
+ const long nEndX = aRect.Right();
+ for (long nX = 0; nX <= nEndX; ++nX)
+ SetPixelOnData(pFirstScanline, nX, rColor);
+ const auto nScanlineSize = GetScanlineSize();
+ const long nEndY = aRect.Bottom();
+ for (long nY = 1; nY <= nEndY; nY++)
+ {
+ Scanline pDestScanline = GetScanline(nY);
+ memcpy(pDestScanline, pFirstScanline, nScanlineSize);
+ }
+}
+
+void BitmapWriteAccess::DrawLine( const Point& rStart, const Point& rEnd )
+{
+ if (mpLineColor)
+ {
+ const BitmapColor& rLineColor = *mpLineColor;
+ long nX, nY;
+
+ if (rStart.X() == rEnd.X())
+ {
+ // Vertical Line
+ const long nEndY = rEnd.Y();
+
+ nX = rStart.X();
+ nY = rStart.Y();
+
+ if (nEndY > nY)
+ {
+ for (; nY <= nEndY; nY++ )
+ SetPixel( nY, nX, rLineColor );
+ }
+ else
+ {
+ for (; nY >= nEndY; nY-- )
+ SetPixel( nY, nX, rLineColor );
+ }
+ }
+ else if (rStart.Y() == rEnd.Y())
+ {
+ // Horizontal Line
+ const long nEndX = rEnd.X();
+
+ nX = rStart.X();
+ nY = rStart.Y();
+
+ if (nEndX > nX)
+ {
+ for (; nX <= nEndX; nX++)
+ SetPixel(nY, nX, rLineColor);
+ }
+ else
+ {
+ for (; nX >= nEndX; nX--)
+ SetPixel(nY, nX, rLineColor);
+ }
+ }
+ else
+ {
+ const long nDX = labs( rEnd.X() - rStart.X() );
+ const long nDY = labs( rEnd.Y() - rStart.Y() );
+ long nX1;
+ long nY1;
+ long nX2;
+ long nY2;
+
+ if (nDX >= nDY)
+ {
+ if (rStart.X() < rEnd.X())
+ {
+ nX1 = rStart.X();
+ nY1 = rStart.Y();
+ nX2 = rEnd.X();
+ nY2 = rEnd.Y();
+ }
+ else
+ {
+ nX1 = rEnd.X();
+ nY1 = rEnd.Y();
+ nX2 = rStart.X();
+ nY2 = rStart.Y();
+ }
+
+ const long nDYX = (nDY - nDX) << 1;
+ const long nDY2 = nDY << 1;
+ long nD = nDY2 - nDX;
+ bool bPos = nY1 < nY2;
+
+ for (nX = nX1, nY = nY1; nX <= nX2; nX++)
+ {
+ SetPixel(nY, nX, rLineColor);
+
+ if (nD < 0)
+ nD += nDY2;
+ else
+ {
+ nD += nDYX;
+
+ if (bPos)
+ nY++;
+ else
+ nY--;
+ }
+ }
+ }
+ else
+ {
+ if (rStart.Y() < rEnd.Y())
+ {
+ nX1 = rStart.X();
+ nY1 = rStart.Y();
+ nX2 = rEnd.X();
+ nY2 = rEnd.Y();
+ }
+ else
+ {
+ nX1 = rEnd.X();
+ nY1 = rEnd.Y();
+ nX2 = rStart.X();
+ nY2 = rStart.Y();
+ }
+
+ const long nDYX = (nDX - nDY) << 1;
+ const long nDY2 = nDX << 1;
+ long nD = nDY2 - nDY;
+ bool bPos = nX1 < nX2;
+
+ for (nX = nX1, nY = nY1; nY <= nY2; nY++)
+ {
+ SetPixel(nY, nX, rLineColor);
+
+ if (nD < 0)
+ nD += nDY2;
+ else
+ {
+ nD += nDYX;
+
+ if (bPos)
+ nX++;
+ else
+ nX--;
+ }
+ }
+ }
+ }
+ }
+}
+
+void BitmapWriteAccess::FillRect( const tools::Rectangle& rRect )
+{
+ if (mpFillColor)
+ {
+ const BitmapColor& rFillColor = *mpFillColor;
+ tools::Rectangle aRect(Point(), maBitmap.GetSizePixel());
+
+ aRect.Intersection(rRect);
+
+ if (!aRect.IsEmpty())
+ {
+ const long nStartX = rRect.Left();
+ const long nStartY = rRect.Top();
+ const long nEndX = rRect.Right();
+ const long nEndY = rRect.Bottom();
+
+ for (long nY = nStartY; nY <= nEndY; nY++)
+ {
+ Scanline pScanline = GetScanline( nY );
+ for (long nX = nStartX; nX <= nEndX; nX++)
+ {
+ SetPixelOnData(pScanline, nX, rFillColor);
+ }
+ }
+ }
+ }
+}
+
+void BitmapWriteAccess::DrawRect( const tools::Rectangle& rRect )
+{
+ if (mpFillColor)
+ FillRect(rRect);
+
+ if (mpLineColor && (!mpFillColor || ( *mpFillColor != *mpLineColor)))
+ {
+ DrawLine(rRect.TopLeft(), rRect.TopRight());
+ DrawLine(rRect.TopRight(), rRect.BottomRight());
+ DrawLine(rRect.BottomRight(), rRect.BottomLeft());
+ DrawLine(rRect.BottomLeft(), rRect.TopLeft());
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/bmpfast.cxx b/vcl/source/gdi/bmpfast.cxx
new file mode 100644
index 000000000..cbf72d809
--- /dev/null
+++ b/vcl/source/gdi/bmpfast.cxx
@@ -0,0 +1,743 @@
+/* -*- 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 <bmpfast.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/salgtype.hxx>
+#include <bitmapwriteaccess.hxx>
+
+#include <sal/log.hxx>
+
+typedef unsigned char PIXBYTE;
+
+namespace {
+
+class BasePixelPtr
+{
+public:
+ explicit BasePixelPtr( PIXBYTE* p = nullptr ) : mpPixel( p ) {}
+ void SetRawPtr( PIXBYTE* pRawPtr ) { mpPixel = pRawPtr; }
+ void AddByteOffset( int nByteOffset ) { mpPixel += nByteOffset; }
+
+protected:
+ PIXBYTE* mpPixel;
+};
+
+template <ScanlineFormat PIXFMT>
+class TrueColorPixelPtr : public BasePixelPtr
+{
+public:
+ PIXBYTE GetRed() const;
+ PIXBYTE GetGreen() const;
+ PIXBYTE GetBlue() const;
+ PIXBYTE GetAlpha() const;
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const;
+ void SetAlpha( PIXBYTE a ) const;
+};
+
+// template specializations for truecolor pixel formats
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N24BitTcRgb> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 3; }
+
+ PIXBYTE GetRed() const { return mpPixel[0]; }
+ PIXBYTE GetGreen() const { return mpPixel[1]; }
+ PIXBYTE GetBlue() const { return mpPixel[2]; }
+ static PIXBYTE GetAlpha() { return 0; }
+ static void SetAlpha( PIXBYTE ) {}
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[0] = r;
+ mpPixel[1] = g;
+ mpPixel[2] = b;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N24BitTcBgr> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 3; }
+
+ PIXBYTE GetRed() const { return mpPixel[2]; }
+ PIXBYTE GetGreen() const { return mpPixel[1]; }
+ PIXBYTE GetBlue() const { return mpPixel[0]; }
+ static PIXBYTE GetAlpha() { return 0; }
+ static void SetAlpha( PIXBYTE ) {}
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[0] = b;
+ mpPixel[1] = g;
+ mpPixel[2] = r;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N32BitTcArgb> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 4; }
+
+ PIXBYTE GetRed() const { return mpPixel[1]; }
+ PIXBYTE GetGreen() const { return mpPixel[2]; }
+ PIXBYTE GetBlue() const { return mpPixel[3]; }
+ PIXBYTE GetAlpha() const { return mpPixel[0]; }
+ void SetAlpha( PIXBYTE a ) const { mpPixel[0] = a; }
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[1] = r;
+ mpPixel[2] = g;
+ mpPixel[3] = b;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N32BitTcAbgr> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 4; }
+
+ PIXBYTE GetRed() const { return mpPixel[3]; }
+ PIXBYTE GetGreen() const { return mpPixel[2]; }
+ PIXBYTE GetBlue() const { return mpPixel[1]; }
+ PIXBYTE GetAlpha() const { return mpPixel[0]; }
+ void SetAlpha( PIXBYTE a ) const { mpPixel[0] = a; }
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[1] = b;
+ mpPixel[2] = g;
+ mpPixel[3] = r;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N32BitTcRgba> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 4; }
+
+ PIXBYTE GetRed() const { return mpPixel[0]; }
+ PIXBYTE GetGreen() const { return mpPixel[1]; }
+ PIXBYTE GetBlue() const { return mpPixel[2]; }
+ PIXBYTE GetAlpha() const { return mpPixel[3]; }
+ void SetAlpha( PIXBYTE a ) const{ mpPixel[3] = a; }
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[0] = r;
+ mpPixel[1] = g;
+ mpPixel[2] = b;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N32BitTcBgra> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 4; }
+
+ PIXBYTE GetRed() const { return mpPixel[2]; }
+ PIXBYTE GetGreen() const { return mpPixel[1]; }
+ PIXBYTE GetBlue() const { return mpPixel[0]; }
+ PIXBYTE GetAlpha() const { return mpPixel[3]; }
+ void SetAlpha( PIXBYTE a ) const{ mpPixel[3] = a; }
+
+ void SetColor( PIXBYTE r, PIXBYTE g, PIXBYTE b ) const
+ {
+ mpPixel[0] = b;
+ mpPixel[1] = g;
+ mpPixel[2] = r;
+ }
+};
+
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N8BitTcMask> : public BasePixelPtr
+{
+public:
+ void operator++() { mpPixel += 1; }
+ PIXBYTE GetAlpha() const { return mpPixel[0]; }
+};
+
+// TODO: for some reason many Alpha maps are ScanlineFormat::N8BitPal
+// they should be ScanlineFormat::N8BitTcMask
+template <>
+class TrueColorPixelPtr<ScanlineFormat::N8BitPal>
+: public TrueColorPixelPtr<ScanlineFormat::N8BitTcMask>
+{};
+
+}
+
+// converting truecolor formats
+template <ScanlineFormat SRCFMT, ScanlineFormat DSTFMT>
+static void ImplConvertPixel( const TrueColorPixelPtr<DSTFMT>& rDst,
+ const TrueColorPixelPtr<SRCFMT>& rSrc )
+{
+ rDst.SetColor( rSrc.GetRed(), rSrc.GetGreen(), rSrc.GetBlue() );
+ rDst.SetAlpha( rSrc.GetAlpha() );
+}
+
+template <ScanlineFormat SRCFMT, ScanlineFormat DSTFMT>
+static void ImplConvertLine( const TrueColorPixelPtr<DSTFMT>& rDst,
+ const TrueColorPixelPtr<SRCFMT>& rSrc, int nPixelCount )
+{
+ TrueColorPixelPtr<DSTFMT> aDst( rDst );
+ TrueColorPixelPtr<SRCFMT> aSrc( rSrc );
+ while( --nPixelCount >= 0 )
+ {
+ ImplConvertPixel( aDst, aSrc );
+ ++aSrc;
+ ++aDst;
+ }
+}
+
+// alpha blending truecolor pixels
+template <ScanlineFormat SRCFMT, ScanlineFormat DSTFMT>
+static void ImplBlendPixels( const TrueColorPixelPtr<DSTFMT>& rDst,
+ const TrueColorPixelPtr<SRCFMT>& rSrc, unsigned nAlphaVal )
+{
+ static const unsigned nAlphaShift = 8;
+ if( !nAlphaVal )
+ ImplConvertPixel( rDst, rSrc );
+ else if( nAlphaVal != ~(~0U << nAlphaShift) )
+ {
+ int nR = rDst.GetRed();
+ int nS = rSrc.GetRed();
+ nR = nS + (((nR - nS) * nAlphaVal) >> nAlphaShift);
+
+ int nG = rDst.GetGreen();
+ nS = rSrc.GetGreen();
+ nG = nS + (((nG - nS) * nAlphaVal) >> nAlphaShift);
+
+ int nB = rDst.GetBlue();
+ nS = rSrc.GetBlue();
+ nB = nS + (((nB - nS) * nAlphaVal) >> nAlphaShift);
+
+ rDst.SetColor( sal::static_int_cast<PIXBYTE>(nR),
+ sal::static_int_cast<PIXBYTE>(nG),
+ sal::static_int_cast<PIXBYTE>(nB) );
+ }
+}
+
+template <ScanlineFormat MASKFMT, ScanlineFormat SRCFMT, ScanlineFormat DSTFMT>
+static void ImplBlendLines( const TrueColorPixelPtr<DSTFMT>& rDst,
+ const TrueColorPixelPtr<SRCFMT>& rSrc, const TrueColorPixelPtr<MASKFMT>& rMsk,
+ int nPixelCount )
+{
+ TrueColorPixelPtr<MASKFMT> aMsk( rMsk );
+ TrueColorPixelPtr<DSTFMT> aDst( rDst );
+ TrueColorPixelPtr<SRCFMT> aSrc( rSrc );
+ while( --nPixelCount >= 0 )
+ {
+ ImplBlendPixels(aDst, aSrc, aMsk.GetAlpha());
+ ++aDst;
+ ++aSrc;
+ ++aMsk;
+ }
+}
+
+static bool ImplCopyImage( BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer )
+{
+ const int nSrcLinestep = rSrcBuffer.mnScanlineSize;
+ int nDstLinestep = rDstBuffer.mnScanlineSize;
+
+ const PIXBYTE* pRawSrc = rSrcBuffer.mpBits;
+ PIXBYTE* pRawDst = rDstBuffer.mpBits;
+
+ // source and destination don't match upside down
+ if( ScanlineFormat::TopDown & (rSrcBuffer.mnFormat ^ rDstBuffer.mnFormat) )
+ {
+ pRawDst += (rSrcBuffer.mnHeight - 1) * nDstLinestep;
+ nDstLinestep = -rDstBuffer.mnScanlineSize;
+ }
+ else if( nSrcLinestep == nDstLinestep )
+ {
+ memcpy( pRawDst, pRawSrc, rSrcBuffer.mnHeight * nDstLinestep );
+ return true;
+ }
+
+ int nByteWidth = nSrcLinestep;
+ if( nByteWidth > rDstBuffer.mnScanlineSize )
+ nByteWidth = rDstBuffer.mnScanlineSize;
+
+ for( int y = rSrcBuffer.mnHeight; --y >= 0; )
+ {
+ memcpy( pRawDst, pRawSrc, nByteWidth );
+ pRawSrc += nSrcLinestep;
+ pRawDst += nDstLinestep;
+ }
+
+ return true;
+}
+
+template <ScanlineFormat DSTFMT,ScanlineFormat SRCFMT>
+static bool ImplConvertToBitmap( TrueColorPixelPtr<SRCFMT>& rSrcLine,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer )
+{
+ // help the compiler to avoid instantiations of unneeded conversions
+ SAL_WARN_IF( SRCFMT == DSTFMT, "vcl.gdi", "ImplConvertToBitmap into same format");
+ if( SRCFMT == DSTFMT )
+ return false;
+
+ const int nSrcLinestep = rSrcBuffer.mnScanlineSize;
+ int nDstLinestep = rDstBuffer.mnScanlineSize;
+
+ TrueColorPixelPtr<DSTFMT> aDstLine; aDstLine.SetRawPtr( rDstBuffer.mpBits );
+
+ // source and destination don't match upside down
+ if( ScanlineFormat::TopDown & (rSrcBuffer.mnFormat ^ rDstBuffer.mnFormat) )
+ {
+ aDstLine.AddByteOffset( (rSrcBuffer.mnHeight - 1) * nDstLinestep );
+ nDstLinestep = -nDstLinestep;
+ }
+
+ for( int y = rSrcBuffer.mnHeight; --y >= 0; )
+ {
+ ImplConvertLine( aDstLine, rSrcLine, rSrcBuffer.mnWidth );
+ rSrcLine.AddByteOffset( nSrcLinestep );
+ aDstLine.AddByteOffset( nDstLinestep );
+ }
+
+ return true;
+}
+
+template <ScanlineFormat SRCFMT>
+static bool ImplConvertFromBitmap( BitmapBuffer& rDst, const BitmapBuffer& rSrc )
+{
+ TrueColorPixelPtr<SRCFMT> aSrcType; aSrcType.SetRawPtr( rSrc.mpBits );
+
+ // select the matching instantiation for the destination's bitmap format
+ switch (RemoveScanline(rDst.mnFormat))
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ case ScanlineFormat::N4BitMsnPal:
+ case ScanlineFormat::N4BitLsnPal:
+ case ScanlineFormat::N8BitPal:
+ break;
+
+ case ScanlineFormat::N8BitTcMask:
+// return ImplConvertToBitmap<ScanlineFormat::N8BitTcMask>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N32BitTcMask:
+// return ImplConvertToBitmap<ScanlineFormat::N32BitTcMask>( aSrcType, rDst, rSrc );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplConvertToBitmap<ScanlineFormat::N24BitTcBgr>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplConvertToBitmap<ScanlineFormat::N24BitTcRgb>( aSrcType, rDst, rSrc );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcAbgr>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcArgb>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcBgra>( aSrcType, rDst, rSrc );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplConvertToBitmap<ScanlineFormat::N32BitTcRgba>( aSrcType, rDst, rSrc );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplConvertFromBitmap for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) << ")" );
+
+ return false;
+}
+
+// A universal stretching conversion is overkill in most common situations
+// => performance benefits for speeding up the non-stretching cases
+bool ImplFastBitmapConversion( BitmapBuffer& rDst, const BitmapBuffer& rSrc,
+ const SalTwoRect& rTR )
+{
+ // TODO:horizontal mirroring not implemented yet
+ if( rTR.mnDestWidth < 0 )
+ return false;
+ // vertical mirroring
+ if( rTR.mnDestHeight < 0 )
+ // TODO: rDst.mnFormat ^= ScanlineFormat::TopDown;
+ return false;
+
+ // offsetted conversion is not implemented yet
+ if( rTR.mnSrcX || rTR.mnSrcY )
+ return false;
+ if( rTR.mnDestX || rTR.mnDestY )
+ return false;
+
+ // stretched conversion is not implemented yet
+ if( rTR.mnDestWidth != rTR.mnSrcWidth )
+ return false;
+ if( rTR.mnDestHeight!= rTR.mnSrcHeight )
+ return false;
+
+ // check source image size
+ if( rSrc.mnWidth < rTR.mnSrcX + rTR.mnSrcWidth )
+ return false;
+ if( rSrc.mnHeight < rTR.mnSrcY + rTR.mnSrcHeight )
+ return false;
+
+ // check dest image size
+ if( rDst.mnWidth < rTR.mnDestX + rTR.mnDestWidth )
+ return false;
+ if( rDst.mnHeight < rTR.mnDestY + rTR.mnDestHeight )
+ return false;
+
+ const ScanlineFormat nSrcFormat = RemoveScanline(rSrc.mnFormat);
+ const ScanlineFormat nDstFormat = RemoveScanline(rDst.mnFormat);
+
+ // special handling of trivial cases
+ if( nSrcFormat == nDstFormat )
+ {
+ // accelerated palette conversions not yet implemented
+ if( rSrc.maPalette != rDst.maPalette )
+ return false;
+ return ImplCopyImage( rDst, rSrc );
+ }
+
+ // select the matching instantiation for the source's bitmap format
+ switch( nSrcFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ case ScanlineFormat::N4BitMsnPal:
+ case ScanlineFormat::N4BitLsnPal:
+ case ScanlineFormat::N8BitPal:
+ break;
+
+ case ScanlineFormat::N8BitTcMask:
+// return ImplConvertFromBitmap<ScanlineFormat::N8BitTcMask>( rDst, rSrc );
+ case ScanlineFormat::N32BitTcMask:
+// return ImplConvertFromBitmap<ScanlineFormat::N32BitTcMask>( rDst, rSrc );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplConvertFromBitmap<ScanlineFormat::N24BitTcBgr>( rDst, rSrc );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplConvertFromBitmap<ScanlineFormat::N24BitTcRgb>( rDst, rSrc );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplConvertFromBitmap<ScanlineFormat::N32BitTcAbgr>( rDst, rSrc );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplConvertFromBitmap<ScanlineFormat::N32BitTcArgb>( rDst, rSrc );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplConvertFromBitmap<ScanlineFormat::N32BitTcBgra>( rDst, rSrc );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplConvertFromBitmap<ScanlineFormat::N32BitTcRgba>( rDst, rSrc );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplFastBitmapConversion for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) << ")" );
+
+ return false;
+}
+
+template <ScanlineFormat DSTFMT, ScanlineFormat SRCFMT> //,sal_uLong MSKFMT>
+static bool ImplBlendToBitmap( TrueColorPixelPtr<SRCFMT>& rSrcLine,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+{
+ SAL_WARN_IF( rMskBuffer.mnFormat != ScanlineFormat::N8BitPal, "vcl.gdi", "FastBmp BlendImage: unusual MSKFMT" );
+
+ const int nSrcLinestep = rSrcBuffer.mnScanlineSize;
+ int nMskLinestep = rMskBuffer.mnScanlineSize;
+ int nDstLinestep = rDstBuffer.mnScanlineSize;
+
+ TrueColorPixelPtr<ScanlineFormat::N8BitPal> aMskLine; aMskLine.SetRawPtr( rMskBuffer.mpBits );
+ TrueColorPixelPtr<DSTFMT> aDstLine; aDstLine.SetRawPtr( rDstBuffer.mpBits );
+
+ // special case for single line masks
+ if( rMskBuffer.mnHeight == 1 )
+ nMskLinestep = 0;
+
+ // source and mask don't match: upside down
+ if( (rSrcBuffer.mnFormat ^ rMskBuffer.mnFormat) & ScanlineFormat::TopDown )
+ {
+ aMskLine.AddByteOffset( (rSrcBuffer.mnHeight - 1) * nMskLinestep );
+ nMskLinestep = -nMskLinestep;
+ }
+
+ // source and destination don't match: upside down
+ if( (rSrcBuffer.mnFormat ^ rDstBuffer.mnFormat) & ScanlineFormat::TopDown )
+ {
+ aDstLine.AddByteOffset( (rDstBuffer.mnHeight - 1) * nDstLinestep );
+ nDstLinestep = -nDstLinestep;
+ }
+
+ assert(rDstBuffer.mnHeight <= rSrcBuffer.mnHeight && "not sure about that?");
+ for (int y = rDstBuffer.mnHeight; --y >= 0;)
+ {
+ ImplBlendLines(aDstLine, rSrcLine, aMskLine, rDstBuffer.mnWidth);
+ aDstLine.AddByteOffset( nDstLinestep );
+ rSrcLine.AddByteOffset( nSrcLinestep );
+ aMskLine.AddByteOffset( nMskLinestep );
+ }
+
+ return true;
+}
+
+// some specializations to reduce the code size
+template <>
+bool ImplBlendToBitmap<ScanlineFormat::N24BitTcBgr,ScanlineFormat::N24BitTcBgr>(
+ TrueColorPixelPtr<ScanlineFormat::N24BitTcBgr>&,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+ {
+ TrueColorPixelPtr<ScanlineFormat::N24BitTcRgb> aSrcType; aSrcType.SetRawPtr( rSrcBuffer.mpBits );
+ return ImplBlendToBitmap<ScanlineFormat::N24BitTcRgb>( aSrcType, rDstBuffer, rSrcBuffer, rMskBuffer );
+ }
+
+template <>
+bool ImplBlendToBitmap<ScanlineFormat::N32BitTcAbgr,ScanlineFormat::N32BitTcAbgr>(
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcAbgr>&,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+ {
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcArgb> aSrcType; aSrcType.SetRawPtr( rSrcBuffer.mpBits );
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcArgb>( aSrcType, rDstBuffer, rSrcBuffer, rMskBuffer );
+ }
+
+template <>
+bool ImplBlendToBitmap<ScanlineFormat::N32BitTcBgra,ScanlineFormat::N32BitTcBgra>(
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcBgra>&,
+ BitmapBuffer& rDstBuffer, const BitmapBuffer& rSrcBuffer,
+ const BitmapBuffer& rMskBuffer )
+ {
+ TrueColorPixelPtr<ScanlineFormat::N32BitTcRgba> aSrcType; aSrcType.SetRawPtr( rSrcBuffer.mpBits );
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcRgba>( aSrcType, rDstBuffer, rSrcBuffer, rMskBuffer );
+ }
+
+template <ScanlineFormat SRCFMT>
+static bool ImplBlendFromBitmap( BitmapBuffer& rDst, const BitmapBuffer& rSrc, const BitmapBuffer& rMsk )
+{
+ TrueColorPixelPtr<SRCFMT> aSrcType; aSrcType.SetRawPtr( rSrc.mpBits );
+
+ // select the matching instantiation for the destination's bitmap format
+ switch (RemoveScanline(rDst.mnFormat))
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ case ScanlineFormat::N4BitMsnPal:
+ case ScanlineFormat::N4BitLsnPal:
+ case ScanlineFormat::N8BitPal:
+ break;
+
+ case ScanlineFormat::N8BitTcMask:
+// return ImplBlendToBitmap<ScanlineFormat::N8BitTcMask>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcMask:
+// return ImplBlendToBitmap<ScanlineFormat::N32BitTcMask>( aSrcType, rDst, rSrc, rMsk );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplBlendToBitmap<ScanlineFormat::N24BitTcBgr>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplBlendToBitmap<ScanlineFormat::N24BitTcRgb>( aSrcType, rDst, rSrc, rMsk );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcAbgr>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcArgb>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcBgra>( aSrcType, rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplBlendToBitmap<ScanlineFormat::N32BitTcRgba>( aSrcType, rDst, rSrc, rMsk );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplBlendFromBitmap for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "*" << static_cast<int>(rMsk.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) );
+ return false;
+}
+
+bool ImplFastBitmapBlending( BitmapWriteAccess const & rDstWA,
+ const BitmapReadAccess& rSrcRA, const BitmapReadAccess& rMskRA,
+ const SalTwoRect& rTR )
+{
+ // accelerated blending of paletted bitmaps not implemented yet
+ if( rSrcRA.HasPalette() )
+ return false;
+ if( rDstWA.HasPalette() )
+ return false;
+ // TODO: either get rid of mask's use of 8BIT_PAL or check the palette
+
+ // horizontal mirroring not implemented yet
+ if( rTR.mnDestWidth < 0 )
+ return false;
+ // vertical mirroring
+ if( rTR.mnDestHeight < 0 )
+ // TODO: rDst.mnFormat ^= ScanlineFormat::TopDown;
+ return false;
+
+ // offsetted blending is not implemented yet
+ if( rTR.mnSrcX || rTR.mnSrcY )
+ return false;
+ if( rTR.mnDestX || rTR.mnDestY )
+ return false;
+
+ // stretched blending is not implemented yet
+ if( rTR.mnDestWidth != rTR.mnSrcWidth )
+ return false;
+ if( rTR.mnDestHeight!= rTR.mnSrcHeight )
+ return false;
+
+ // check source image size
+ if( rSrcRA.Width() < rTR.mnSrcX + rTR.mnSrcWidth )
+ return false;
+ if( rSrcRA.Height() < rTR.mnSrcY + rTR.mnSrcHeight )
+ return false;
+
+ // check mask image size
+ if( rMskRA.Width() < rTR.mnSrcX + rTR.mnSrcWidth )
+ return false;
+ if( rMskRA.Height() < rTR.mnSrcY + rTR.mnSrcHeight )
+ if( rMskRA.Height() != 1 )
+ return false;
+
+ // check dest image size
+ if( rDstWA.Width() < rTR.mnDestX + rTR.mnDestWidth )
+ return false;
+ if( rDstWA.Height() < rTR.mnDestY + rTR.mnDestHeight )
+ return false;
+
+ BitmapBuffer& rDst = *rDstWA.ImplGetBitmapBuffer();
+ const BitmapBuffer& rSrc = *rSrcRA.ImplGetBitmapBuffer();
+ const BitmapBuffer& rMsk = *rMskRA.ImplGetBitmapBuffer();
+
+ const ScanlineFormat nSrcFormat = RemoveScanline(rSrc.mnFormat);
+
+ // select the matching instantiation for the source's bitmap format
+ switch( nSrcFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ case ScanlineFormat::N4BitMsnPal:
+ case ScanlineFormat::N4BitLsnPal:
+ case ScanlineFormat::N8BitPal:
+ break;
+
+ case ScanlineFormat::N8BitTcMask:
+// return ImplBlendFromBitmap<ScanlineFormat::N8BitTcMask>( rDst, rSrc );
+ case ScanlineFormat::N32BitTcMask:
+// return ImplBlendFromBitmap<ScanlineFormat::N32BitTcMask>( rDst, rSrc );
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ return ImplBlendFromBitmap<ScanlineFormat::N24BitTcBgr>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N24BitTcRgb:
+ return ImplBlendFromBitmap<ScanlineFormat::N24BitTcRgb>( rDst, rSrc, rMsk );
+
+ case ScanlineFormat::N32BitTcAbgr:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcAbgr>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcArgb:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcArgb>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcBgra:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcBgra>( rDst, rSrc, rMsk );
+ case ScanlineFormat::N32BitTcRgba:
+ return ImplBlendFromBitmap<ScanlineFormat::N32BitTcRgba>( rDst, rSrc, rMsk );
+ default: break;
+ }
+
+ static int nNotAccelerated = 0;
+ SAL_WARN_IF( rSrc.mnWidth * rSrc.mnHeight >= 4000 && ++nNotAccelerated == 100,
+ "vcl.gdi",
+ "ImplFastBlend for not accelerated case (" << std::hex << static_cast<int>(rSrc.mnFormat) << "*" << static_cast<int>(rMsk.mnFormat) << "->" << static_cast<int>(rDst.mnFormat) << ")" );
+
+ return false;
+}
+
+bool ImplFastEraseBitmap( BitmapBuffer& rDst, const BitmapColor& rColor )
+{
+ const ScanlineFormat nDstFormat = RemoveScanline(rDst.mnFormat);
+
+ // erasing a bitmap is often just a byte-wise memory fill
+ bool bByteFill = true;
+ sal_uInt8 nFillByte;
+
+ switch( nDstFormat )
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N1BitLsbPal:
+ nFillByte = rColor.GetIndex();
+ nFillByte = static_cast<sal_uInt8>( -(nFillByte & 1) ); // 0x00 or 0xFF
+ break;
+ case ScanlineFormat::N4BitMsnPal:
+ case ScanlineFormat::N4BitLsnPal:
+ nFillByte = rColor.GetIndex();
+ nFillByte &= 0x0F;
+ nFillByte |= (nFillByte << 4);
+ break;
+ case ScanlineFormat::N8BitPal:
+ case ScanlineFormat::N8BitTcMask:
+ nFillByte = rColor.GetIndex();
+ break;
+
+ case ScanlineFormat::N24BitTcBgr:
+ case ScanlineFormat::N24BitTcRgb:
+ nFillByte = rColor.GetRed();
+ if( (nFillByte != rColor.GetGreen())
+ || (nFillByte != rColor.GetBlue()) )
+ bByteFill = false;
+ break;
+
+ default:
+ bByteFill = false;
+ nFillByte = 0x00;
+ break;
+ }
+
+ if( bByteFill )
+ {
+ long nByteCount = rDst.mnHeight * rDst.mnScanlineSize;
+ memset( rDst.mpBits, nFillByte, nByteCount );
+ return true;
+ }
+
+ // TODO: handle other bitmap formats
+ switch( nDstFormat )
+ {
+ case ScanlineFormat::N32BitTcMask:
+
+ case ScanlineFormat::N24BitTcBgr:
+ case ScanlineFormat::N24BitTcRgb:
+
+ case ScanlineFormat::N32BitTcAbgr:
+ case ScanlineFormat::N32BitTcArgb:
+ case ScanlineFormat::N32BitTcBgra:
+ case ScanlineFormat::N32BitTcRgba:
+ break;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+/* 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 000000000..8d477ec37
--- /dev/null
+++ b/vcl/source/gdi/configsettings.cxx
@@ -0,0 +1,137 @@
+/* -*- 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;
+
+#define SETTINGS_CONFIGNODE "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
+ Sequence< OUString > aKeys( GetNodeNames( aKeyName ) );
+ Sequence< OUString > aSettingsKeys( aKeys.getLength() );
+ std::transform(aKeys.begin(), aKeys.end(), aSettingsKeys.begin(),
+ [&aKeyName](const OUString& rKey) -> OUString { return aKeyName + "/" + rKey; });
+ Sequence< Any > aValues( GetProperties( aSettingsKeys ) );
+ const OUString* pFrom = aKeys.getConstArray();
+ const Any* pValue = aValues.getConstArray();
+ for( int i = 0; i < aValues.getLength(); i++, pValue++ )
+ {
+ if( auto pLine = o3tl::tryAccess<OUString>(*pValue) )
+ {
+ if( !pLine->isEmpty() )
+ m_aSettings[ aKeyName ][ pFrom[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 000000000..9e9ecfb6e
--- /dev/null
+++ b/vcl/source/gdi/cvtgrf.cxx
@@ -0,0 +1,73 @@
+/* -*- 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()
+{
+}
+
+GraphicConverter::~GraphicConverter()
+{
+}
+
+ErrCode GraphicConverter::Import( SvStream& rIStm, Graphic& rGraphic, ConvertDataFormat nFormat )
+{
+ GraphicConverter* pCvt = ImplGetSVData()->maGDIData.mpGrfConverter;
+ 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.mpGrfConverter;
+ 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/dibtools.cxx b/vcl/source/gdi/dibtools.cxx
new file mode 100644
index 000000000..f2164b94d
--- /dev/null
+++ b/vcl/source/gdi/dibtools.cxx
@@ -0,0 +1,1904 @@
+/* -*- 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 <cassert>
+
+#include <o3tl/safeint.hxx>
+#include <vcl/dibtools.hxx>
+#include <comphelper/fileformat.h>
+#include <tools/zcodec.hxx>
+#include <tools/stream.hxx>
+#include <tools/fract.hxx>
+#include <tools/helpers.hxx>
+#include <tools/GenericTypeSerializer.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/outdev.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <memory>
+
+#define DIBCOREHEADERSIZE ( 12UL )
+#define DIBINFOHEADERSIZE ( sizeof(DIBInfoHeader) )
+#define DIBV5HEADERSIZE ( sizeof(DIBV5Header) )
+
+// - DIBInfoHeader and DIBV5Header
+
+typedef sal_Int32 FXPT2DOT30;
+
+namespace
+{
+
+struct CIEXYZ
+{
+ FXPT2DOT30 aXyzX;
+ FXPT2DOT30 aXyzY;
+ FXPT2DOT30 aXyzZ;
+
+ CIEXYZ()
+ : aXyzX(0),
+ aXyzY(0),
+ aXyzZ(0)
+ {}
+};
+
+struct CIEXYZTriple
+{
+ CIEXYZ aXyzRed;
+ CIEXYZ aXyzGreen;
+ CIEXYZ aXyzBlue;
+
+ CIEXYZTriple()
+ : aXyzRed(),
+ aXyzGreen(),
+ aXyzBlue()
+ {}
+};
+
+struct DIBInfoHeader
+{
+ sal_uInt32 nSize;
+ sal_Int32 nWidth;
+ sal_Int32 nHeight;
+ sal_uInt16 nPlanes;
+ sal_uInt16 nBitCount;
+ sal_uInt32 nCompression;
+ sal_uInt32 nSizeImage;
+ sal_Int32 nXPelsPerMeter;
+ sal_Int32 nYPelsPerMeter;
+ sal_uInt32 nColsUsed;
+ sal_uInt32 nColsImportant;
+
+ DIBInfoHeader()
+ : nSize(0),
+ nWidth(0),
+ nHeight(0),
+ nPlanes(0),
+ nBitCount(0),
+ nCompression(0),
+ nSizeImage(0),
+ nXPelsPerMeter(0),
+ nYPelsPerMeter(0),
+ nColsUsed(0),
+ nColsImportant(0)
+ {}
+};
+
+struct DIBV5Header : public DIBInfoHeader
+{
+ sal_uInt32 nV5RedMask;
+ sal_uInt32 nV5GreenMask;
+ sal_uInt32 nV5BlueMask;
+ sal_uInt32 nV5AlphaMask;
+ sal_uInt32 nV5CSType;
+ CIEXYZTriple aV5Endpoints;
+ sal_uInt32 nV5GammaRed;
+ sal_uInt32 nV5GammaGreen;
+ sal_uInt32 nV5GammaBlue;
+ sal_uInt32 nV5Intent;
+ sal_uInt32 nV5ProfileData;
+ sal_uInt32 nV5ProfileSize;
+ sal_uInt32 nV5Reserved;
+
+ DIBV5Header()
+ : DIBInfoHeader(),
+ nV5RedMask(0),
+ nV5GreenMask(0),
+ nV5BlueMask(0),
+ nV5AlphaMask(0),
+ nV5CSType(0),
+ aV5Endpoints(),
+ nV5GammaRed(0),
+ nV5GammaGreen(0),
+ nV5GammaBlue(0),
+ nV5Intent(0),
+ nV5ProfileData(0),
+ nV5ProfileSize(0),
+ nV5Reserved(0)
+ {}
+};
+
+sal_uInt16 discretizeBitcount( sal_uInt16 nInputCount )
+{
+ return ( nInputCount <= 1 ) ? 1 :
+ ( nInputCount <= 4 ) ? 4 :
+ ( nInputCount <= 8 ) ? 8 : 24;
+}
+
+bool isBitfieldCompression( ScanlineFormat nScanlineFormat )
+{
+ return ScanlineFormat::N32BitTcMask == nScanlineFormat;
+}
+
+bool ImplReadDIBInfoHeader(SvStream& rIStm, DIBV5Header& rHeader, bool& bTopDown, bool bMSOFormat)
+{
+ // BITMAPINFOHEADER or BITMAPCOREHEADER or BITMAPV5HEADER
+ sal_uInt64 const aStartPos(rIStm.Tell());
+ rIStm.ReadUInt32( rHeader.nSize );
+
+ // BITMAPCOREHEADER
+ if ( rHeader.nSize == DIBCOREHEADERSIZE )
+ {
+ sal_Int16 nTmp16;
+
+ rIStm.ReadInt16( nTmp16 ); rHeader.nWidth = nTmp16;
+ rIStm.ReadInt16( nTmp16 ); rHeader.nHeight = nTmp16;
+ rIStm.ReadUInt16( rHeader.nPlanes );
+ rIStm.ReadUInt16( rHeader.nBitCount );
+ }
+ else if ( bMSOFormat && rHeader.nSize == DIBINFOHEADERSIZE )
+ {
+ sal_Int16 nTmp16(0);
+ rIStm.ReadInt16(nTmp16);
+ rHeader.nWidth = nTmp16;
+ rIStm.ReadInt16(nTmp16);
+ rHeader.nHeight = nTmp16;
+ sal_uInt8 nTmp8(0);
+ rIStm.ReadUChar(nTmp8);
+ rHeader.nPlanes = nTmp8;
+ rIStm.ReadUChar(nTmp8);
+ rHeader.nBitCount = nTmp8;
+ rIStm.ReadInt16(nTmp16);
+ rHeader.nSizeImage = nTmp16;
+ rIStm.ReadInt16(nTmp16);
+ rHeader.nCompression = nTmp16;
+ if ( !rHeader.nSizeImage ) // uncompressed?
+ rHeader.nSizeImage = ((rHeader.nWidth * rHeader.nBitCount + 31) & ~31) / 8 * rHeader.nHeight;
+ rIStm.ReadInt32( rHeader.nXPelsPerMeter );
+ rIStm.ReadInt32( rHeader.nYPelsPerMeter );
+ rIStm.ReadUInt32( rHeader.nColsUsed );
+ rIStm.ReadUInt32( rHeader.nColsImportant );
+ }
+ else
+ {
+ // BITMAPCOREHEADER, BITMAPV5HEADER or unknown. Read as far as possible
+ std::size_t nUsed(sizeof(rHeader.nSize));
+
+ auto readUInt16 = [&nUsed, &rHeader, &rIStm](sal_uInt16 & v) {
+ if (nUsed < rHeader.nSize) {
+ rIStm.ReadUInt16(v);
+ nUsed += sizeof(v);
+ }
+ };
+ auto readInt32 = [&nUsed, &rHeader, &rIStm](sal_Int32 & v) {
+ if (nUsed < rHeader.nSize) {
+ rIStm.ReadInt32(v);
+ nUsed += sizeof(v);
+ }
+ };
+ auto readUInt32 = [&nUsed, &rHeader, &rIStm](sal_uInt32 & v) {
+ if (nUsed < rHeader.nSize) {
+ rIStm.ReadUInt32(v);
+ nUsed += sizeof(v);
+ }
+ };
+
+ // read DIBInfoHeader entries
+ readInt32( rHeader.nWidth );
+ readInt32( rHeader.nHeight );
+ readUInt16( rHeader.nPlanes );
+ readUInt16( rHeader.nBitCount );
+ readUInt32( rHeader.nCompression );
+ readUInt32( rHeader.nSizeImage );
+ readInt32( rHeader.nXPelsPerMeter );
+ readInt32( rHeader.nYPelsPerMeter );
+ readUInt32( rHeader.nColsUsed );
+ readUInt32( rHeader.nColsImportant );
+
+ // read DIBV5HEADER members
+ readUInt32( rHeader.nV5RedMask );
+ readUInt32( rHeader.nV5GreenMask );
+ readUInt32( rHeader.nV5BlueMask );
+ readUInt32( rHeader.nV5AlphaMask );
+ readUInt32( rHeader.nV5CSType );
+
+ // read contained CIEXYZTriple's
+ readInt32( rHeader.aV5Endpoints.aXyzRed.aXyzX );
+ readInt32( rHeader.aV5Endpoints.aXyzRed.aXyzY );
+ readInt32( rHeader.aV5Endpoints.aXyzRed.aXyzZ );
+ readInt32( rHeader.aV5Endpoints.aXyzGreen.aXyzX );
+ readInt32( rHeader.aV5Endpoints.aXyzGreen.aXyzY );
+ readInt32( rHeader.aV5Endpoints.aXyzGreen.aXyzZ );
+ readInt32( rHeader.aV5Endpoints.aXyzBlue.aXyzX );
+ readInt32( rHeader.aV5Endpoints.aXyzBlue.aXyzY );
+ readInt32( rHeader.aV5Endpoints.aXyzBlue.aXyzZ );
+
+ readUInt32( rHeader.nV5GammaRed );
+ readUInt32( rHeader.nV5GammaGreen );
+ readUInt32( rHeader.nV5GammaBlue );
+ readUInt32( rHeader.nV5Intent );
+ readUInt32( rHeader.nV5ProfileData );
+ readUInt32( rHeader.nV5ProfileSize );
+ readUInt32( rHeader.nV5Reserved );
+
+ // seek to EndPos
+ if (!checkSeek(rIStm, aStartPos + rHeader.nSize))
+ return false;
+ }
+
+ if (rHeader.nHeight == SAL_MIN_INT32)
+ return false;
+
+ if ( rHeader.nHeight < 0 )
+ {
+ bTopDown = true;
+ rHeader.nHeight *= -1;
+ }
+ else
+ {
+ bTopDown = false;
+ }
+
+ if ( rHeader.nWidth < 0 || rHeader.nXPelsPerMeter < 0 || rHeader.nYPelsPerMeter < 0 )
+ {
+ rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR );
+ }
+
+ // #144105# protect a little against damaged files
+ assert(rHeader.nHeight >= 0);
+ if (rHeader.nHeight != 0 && rHeader.nWidth >= 0
+ && (rHeader.nSizeImage / 16 / static_cast<sal_uInt32>(rHeader.nHeight)
+ > o3tl::make_unsigned(rHeader.nWidth)))
+ {
+ rHeader.nSizeImage = 0;
+ }
+
+
+ if (rHeader.nPlanes != 1)
+ return false;
+
+ if (rHeader.nBitCount != 0 && rHeader.nBitCount != 1 &&
+ rHeader.nBitCount != 4 && rHeader.nBitCount != 8 &&
+ rHeader.nBitCount != 16 && rHeader.nBitCount != 24 &&
+ rHeader.nBitCount != 32)
+ {
+ return false;
+ }
+
+ return rIStm.good();
+}
+
+bool ImplReadDIBPalette(SvStream& rIStm, BitmapPalette& rPal, bool bQuad)
+{
+ const sal_uInt16 nColors = rPal.GetEntryCount();
+ const sal_uLong nPalSize = nColors * ( bQuad ? 4UL : 3UL );
+ BitmapColor aPalColor;
+
+ std::unique_ptr<sal_uInt8[]> pEntries(new sal_uInt8[ nPalSize ]);
+ if (rIStm.ReadBytes(pEntries.get(), nPalSize) != nPalSize)
+ {
+ return false;
+ }
+
+ sal_uInt8* pTmpEntry = pEntries.get();
+ for( sal_uInt16 i = 0; i < nColors; i++ )
+ {
+ aPalColor.SetBlue( *pTmpEntry++ );
+ aPalColor.SetGreen( *pTmpEntry++ );
+ aPalColor.SetRed( *pTmpEntry++ );
+
+ if( bQuad )
+ pTmpEntry++;
+
+ rPal[i] = aPalColor;
+ }
+
+ return rIStm.GetError() == ERRCODE_NONE;
+}
+
+BitmapColor SanitizePaletteIndex(sal_uInt8 nIndex, BitmapPalette& rPalette, bool bForceToMonoWhileReading)
+{
+ const sal_uInt16 nPaletteEntryCount = rPalette.GetEntryCount();
+ if (nPaletteEntryCount && nIndex >= nPaletteEntryCount)
+ {
+ auto nSanitizedIndex = nIndex % nPaletteEntryCount;
+ SAL_WARN_IF(nIndex != nSanitizedIndex, "vcl", "invalid colormap index: "
+ << static_cast<unsigned int>(nIndex) << ", colormap len is: "
+ << nPaletteEntryCount);
+ nIndex = nSanitizedIndex;
+ }
+
+ if (nPaletteEntryCount && bForceToMonoWhileReading)
+ {
+ return BitmapColor(static_cast<sal_uInt8>(rPalette[nIndex].GetLuminance() >= 255));
+ }
+
+ return BitmapColor(nIndex);
+}
+
+BitmapColor SanitizeColor(const BitmapColor &rColor, bool bForceToMonoWhileReading)
+{
+ if (!bForceToMonoWhileReading)
+ return rColor;
+ return BitmapColor(static_cast<sal_uInt8>(rColor.GetLuminance() >= 255));
+}
+
+bool ImplDecodeRLE(sal_uInt8* pBuffer, DIBV5Header const & rHeader, BitmapWriteAccess& rAcc, BitmapPalette& rPalette, bool bForceToMonoWhileReading, bool bRLE4)
+{
+ Scanline pRLE = pBuffer;
+ Scanline pEndRLE = pBuffer + rHeader.nSizeImage;
+ long nY = rHeader.nHeight - 1;
+ const sal_uLong nWidth = rAcc.Width();
+ sal_uLong nCountByte;
+ sal_uLong nRunByte;
+ sal_uLong nX = 0;
+ sal_uInt8 cTmp;
+ bool bEndDecoding = false;
+
+ do
+ {
+ if (pRLE == pEndRLE)
+ return false;
+ if( ( nCountByte = *pRLE++ ) == 0 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+ nRunByte = *pRLE++;
+
+ if( nRunByte > 2 )
+ {
+ Scanline pScanline = rAcc.GetScanline(nY);
+ if( bRLE4 )
+ {
+ nCountByte = nRunByte >> 1;
+
+ for( sal_uLong i = 0; i < nCountByte; i++ )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ cTmp = *pRLE++;
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette, bForceToMonoWhileReading));
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp & 0x0f, rPalette, bForceToMonoWhileReading));
+ }
+
+ if( nRunByte & 1 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(*pRLE >> 4, rPalette, bForceToMonoWhileReading));
+
+ pRLE++;
+ }
+
+ if( ( ( nRunByte + 1 ) >> 1 ) & 1 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ pRLE++;
+ }
+ }
+ else
+ {
+ for( sal_uLong i = 0; i < nRunByte; i++ )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(*pRLE, rPalette, bForceToMonoWhileReading));
+
+ pRLE++;
+ }
+
+ if( nRunByte & 1 )
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ pRLE++;
+ }
+ }
+ }
+ else if( !nRunByte )
+ {
+ nY--;
+ nX = 0;
+ }
+ else if( nRunByte == 1 )
+ bEndDecoding = true;
+ else
+ {
+ if (pRLE == pEndRLE)
+ return false;
+
+ nX += *pRLE++;
+
+ if (pRLE == pEndRLE)
+ return false;
+
+ nY -= *pRLE++;
+ }
+ }
+ else
+ {
+ if (pRLE == pEndRLE)
+ return false;
+ cTmp = *pRLE++;
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ if( bRLE4 )
+ {
+ nRunByte = nCountByte >> 1;
+
+ for (sal_uLong i = 0; i < nRunByte && nX < nWidth; ++i)
+ {
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette, bForceToMonoWhileReading));
+ if( nX < nWidth )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp & 0x0f, rPalette, bForceToMonoWhileReading));
+ }
+
+ if( ( nCountByte & 1 ) && ( nX < nWidth ) )
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette, bForceToMonoWhileReading));
+ }
+ else
+ {
+ for (sal_uLong i = 0; i < nCountByte && nX < nWidth; ++i)
+ rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp, rPalette, bForceToMonoWhileReading));
+ }
+ }
+ }
+ while (!bEndDecoding && (nY >= 0));
+
+ return true;
+}
+
+bool ImplReadDIBBits(SvStream& rIStm, DIBV5Header& rHeader, BitmapWriteAccess& rAcc, BitmapPalette& rPalette, BitmapWriteAccess* pAccAlpha,
+ bool bTopDown, bool& rAlphaUsed, const sal_uInt64 nAlignedWidth,
+ const bool bForceToMonoWhileReading)
+{
+ sal_uInt32 nRMask(( rHeader.nBitCount == 16 ) ? 0x00007c00UL : 0x00ff0000UL);
+ sal_uInt32 nGMask(( rHeader.nBitCount == 16 ) ? 0x000003e0UL : 0x0000ff00UL);
+ sal_uInt32 nBMask(( rHeader.nBitCount == 16 ) ? 0x0000001fUL : 0x000000ffUL);
+ bool bNative(false);
+ bool bTCMask(!pAccAlpha && ((16 == rHeader.nBitCount) || (32 == rHeader.nBitCount)));
+ bool bRLE((RLE_8 == rHeader.nCompression && 8 == rHeader.nBitCount) || (RLE_4 == rHeader.nCompression && 4 == rHeader.nBitCount));
+
+ // Is native format?
+ switch(rAcc.GetScanlineFormat())
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N24BitTcBgr:
+ {
+ // we can't trust arbitrary-sourced index based formats to have correct indexes, so we exclude the pal formats
+ // from raw read and force checking their colormap indexes
+ bNative = ( ( rAcc.IsBottomUp() != bTopDown ) && !bRLE && !bTCMask && ( rAcc.GetScanlineSize() == nAlignedWidth ) );
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+
+ // Read data
+ if (bNative)
+ {
+ if (nAlignedWidth
+ > std::numeric_limits<std::size_t>::max() / rHeader.nHeight)
+ {
+ return false;
+ }
+ std::size_t n = nAlignedWidth * rHeader.nHeight;
+ if (rIStm.ReadBytes(rAcc.GetBuffer(), n) != n)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ // Read color mask
+ if(bTCMask && BITFIELDS == rHeader.nCompression)
+ {
+ rIStm.SeekRel( -12 );
+ rIStm.ReadUInt32( nRMask );
+ rIStm.ReadUInt32( nGMask );
+ rIStm.ReadUInt32( nBMask );
+ }
+
+ const long nWidth(rHeader.nWidth);
+ const long nHeight(rHeader.nHeight);
+ long nResult = 0;
+ if (utl::ConfigManager::IsFuzzing() && (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000))
+ return false;
+
+ if (bRLE)
+ {
+ if(!rHeader.nSizeImage)
+ {
+ rHeader.nSizeImage = rIStm.remainingSize();
+ }
+
+ if (rHeader.nSizeImage > rIStm.remainingSize())
+ return false;
+ std::vector<sal_uInt8> aBuffer(rHeader.nSizeImage);
+ if (rIStm.ReadBytes(aBuffer.data(), rHeader.nSizeImage) != rHeader.nSizeImage)
+ return false;
+ if (!ImplDecodeRLE(aBuffer.data(), rHeader, rAcc, rPalette, bForceToMonoWhileReading, RLE_4 == rHeader.nCompression))
+ return false;
+ }
+ else
+ {
+ if (nAlignedWidth > rIStm.remainingSize())
+ {
+ // ofz#11188 avoid timeout
+ // all following paths will enter a case statement, and nCount
+ // is always at least 1, so we can check here before allocation
+ // if at least one row can be read
+ return false;
+ }
+ std::vector<sal_uInt8> aBuf(nAlignedWidth);
+
+ const long nI(bTopDown ? 1 : -1);
+ long nY(bTopDown ? 0 : nHeight - 1);
+ long nCount(nHeight);
+
+ switch(rHeader.nBitCount)
+ {
+ case 1:
+ {
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8 * pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+ sal_uInt8 cTmp = *pTmp++;
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( long nX = 0, nShift = 8; nX < nWidth; nX++ )
+ {
+ if( !nShift )
+ {
+ nShift = 8;
+ cTmp = *pTmp++;
+ }
+
+ auto nIndex = (cTmp >> --nShift) & 1;
+ rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 4:
+ {
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8 * pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+ sal_uInt8 cTmp = *pTmp++;
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( long nX = 0, nShift = 2; nX < nWidth; nX++ )
+ {
+ if( !nShift )
+ {
+ nShift = 2;
+ cTmp = *pTmp++;
+ }
+
+ auto nIndex = (cTmp >> ( --nShift << 2 ) ) & 0x0f;
+ rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 8:
+ {
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8 * pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ auto nIndex = *pTmp++;
+ rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 16:
+ {
+ ColorMaskElement aRedMask(nRMask);
+ if (!aRedMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aGreenMask(nGMask);
+ if (!aGreenMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aBlueMask(nBMask);
+ if (!aBlueMask.CalcMaskShift())
+ return false;
+
+ ColorMask aMask(aRedMask, aGreenMask, aBlueMask);
+ BitmapColor aColor;
+
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt16 * pTmp16 = reinterpret_cast<sal_uInt16*>(aBuf.data());
+ if (rIStm.ReadBytes(pTmp16, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ aMask.GetColorFor16BitLSB( aColor, reinterpret_cast<sal_uInt8*>(pTmp16++) );
+ rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aColor, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 24:
+ {
+ BitmapColor aPixelColor;
+
+ for( ; nCount--; nY += nI )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ if (rIStm.ReadBytes(pTmp, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ aPixelColor.SetBlue( *pTmp++ );
+ aPixelColor.SetGreen( *pTmp++ );
+ aPixelColor.SetRed( *pTmp++ );
+ rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aPixelColor, bForceToMonoWhileReading));
+ }
+ }
+ }
+ break;
+
+ case 32:
+ {
+ ColorMaskElement aRedMask(nRMask);
+ if (!aRedMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aGreenMask(nGMask);
+ if (!aGreenMask.CalcMaskShift())
+ return false;
+ ColorMaskElement aBlueMask(nBMask);
+ if (!aBlueMask.CalcMaskShift())
+ return false;
+ ColorMask aMask(aRedMask, aGreenMask, aBlueMask);
+
+ BitmapColor aColor;
+ sal_uInt32* pTmp32;
+
+ if(pAccAlpha)
+ {
+ sal_uInt8 aAlpha;
+
+ for( ; nCount--; nY += nI )
+ {
+ pTmp32 = reinterpret_cast<sal_uInt32*>(aBuf.data());
+ if (rIStm.ReadBytes(pTmp32, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ Scanline pAlphaScanline = pAccAlpha->GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ aMask.GetColorAndAlphaFor32Bit( aColor, aAlpha, reinterpret_cast<sal_uInt8*>(pTmp32++) );
+ rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aColor, bForceToMonoWhileReading));
+ pAccAlpha->SetPixelOnData(pAlphaScanline, nX, BitmapColor(sal_uInt8(0xff) - aAlpha));
+ rAlphaUsed |= 0xff != aAlpha;
+ }
+ }
+ }
+ else
+ {
+ for( ; nCount--; nY += nI )
+ {
+ pTmp32 = reinterpret_cast<sal_uInt32*>(aBuf.data());
+ if (rIStm.ReadBytes(pTmp32, nAlignedWidth)
+ != nAlignedWidth)
+ {
+ return false;
+ }
+
+ Scanline pScanline = rAcc.GetScanline(nY);
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ aMask.GetColorFor32Bit( aColor, reinterpret_cast<sal_uInt8*>(pTmp32++) );
+ rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aColor, bForceToMonoWhileReading));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return rIStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplReadDIBBody(SvStream& rIStm, Bitmap& rBmp, AlphaMask* pBmpAlpha, sal_uLong nOffset, bool bIsMask, bool bMSOFormat)
+{
+ DIBV5Header aHeader;
+ const sal_uLong nStmPos = rIStm.Tell();
+ bool bTopDown(false);
+
+ if (!ImplReadDIBInfoHeader(rIStm, aHeader, bTopDown, bMSOFormat))
+ return false;
+
+ //BI_BITCOUNT_0 jpeg/png is unsupported
+ if (aHeader.nBitCount == 0)
+ return false;
+
+ if (aHeader.nWidth <= 0 || aHeader.nHeight <= 0)
+ return false;
+
+ // In case ImplReadDIB() didn't call ImplReadDIBFileHeader() before
+ // this method, nOffset is 0, that's OK.
+ if (nOffset && aHeader.nSize > nOffset)
+ {
+ // Header size claims to extend into the image data.
+ // Looks like an error.
+ return false;
+ }
+
+ sal_uInt16 nColors(0);
+ SvStream* pIStm;
+ std::unique_ptr<SvMemoryStream> pMemStm;
+ std::vector<sal_uInt8> aData;
+
+ if (aHeader.nBitCount <= 8)
+ {
+ if(aHeader.nColsUsed)
+ {
+ nColors = static_cast<sal_uInt16>(aHeader.nColsUsed);
+ }
+ else
+ {
+ nColors = ( 1 << aHeader.nBitCount );
+ }
+ }
+
+ if (ZCOMPRESS == aHeader.nCompression)
+ {
+ sal_uInt32 nCodedSize(0);
+ sal_uInt32 nUncodedSize(0);
+
+ // read coding information
+ rIStm.ReadUInt32( nCodedSize ).ReadUInt32( nUncodedSize ).ReadUInt32( aHeader.nCompression );
+ if (nCodedSize > rIStm.remainingSize())
+ nCodedSize = sal_uInt32(rIStm.remainingSize());
+
+ pMemStm.reset(new SvMemoryStream);
+ // There may be bytes left over or the codec might read more than
+ // necessary. So to preserve the correctness of the source stream copy
+ // the encoded block
+ pMemStm->WriteStream(rIStm, nCodedSize);
+ pMemStm->Seek(0);
+
+ size_t nSizeInc(4 * pMemStm->remainingSize());
+ if (nUncodedSize < nSizeInc)
+ nSizeInc = nUncodedSize;
+
+ if (nSizeInc > 0)
+ {
+ // decode buffer
+ ZCodec aCodec;
+ aCodec.BeginCompression();
+ aData.resize(nSizeInc);
+ size_t nDataPos(0);
+ while (nUncodedSize > nDataPos)
+ {
+ assert(aData.size() > nDataPos);
+ const size_t nToRead(std::min<size_t>(nUncodedSize - nDataPos, aData.size() - nDataPos));
+ assert(nToRead > 0);
+ assert(!aData.empty());
+ const long nRead = aCodec.Read(*pMemStm, aData.data() + nDataPos, sal_uInt32(nToRead));
+ if (nRead > 0)
+ {
+ nDataPos += static_cast<unsigned long>(nRead);
+ // we haven't read everything yet: resize buffer and continue
+ if (nDataPos < nUncodedSize)
+ aData.resize(aData.size() + nSizeInc);
+ }
+ else
+ {
+ break;
+ }
+ }
+ // truncate the data buffer to actually read size
+ aData.resize(nDataPos);
+ // set the real uncoded size
+ nUncodedSize = sal_uInt32(aData.size());
+ aCodec.EndCompression();
+ }
+
+ if (aData.empty())
+ {
+ // add something so we can take address of the first element
+ aData.resize(1);
+ nUncodedSize = 0;
+ }
+
+ // set decoded bytes to memory stream,
+ // from which we will read the bitmap data
+ pMemStm.reset(new SvMemoryStream);
+ pIStm = pMemStm.get();
+ assert(!aData.empty());
+ pMemStm->SetBuffer(aData.data(), nUncodedSize, nUncodedSize);
+ nOffset = 0;
+ }
+ else
+ {
+ pIStm = &rIStm;
+ }
+
+ // read palette
+ BitmapPalette aPalette;
+ if (nColors)
+ {
+ aPalette.SetEntryCount(nColors);
+ ImplReadDIBPalette(*pIStm, aPalette, aHeader.nSize != DIBCOREHEADERSIZE);
+ }
+
+ if (pIStm->GetError())
+ return false;
+
+ if (nOffset)
+ {
+ pIStm->SeekRel(nOffset - (pIStm->Tell() - nStmPos));
+ }
+
+ const sal_Int64 nBitsPerLine (static_cast<sal_Int64>(aHeader.nWidth) * static_cast<sal_Int64>(aHeader.nBitCount));
+ if (nBitsPerLine > SAL_MAX_UINT32)
+ return false;
+ const sal_uInt64 nAlignedWidth(AlignedWidth4Bytes(static_cast<sal_uLong>(nBitsPerLine)));
+
+ switch (aHeader.nCompression)
+ {
+ case RLE_8:
+ {
+ if (aHeader.nBitCount != 8)
+ return false;
+ // (partially) check the image dimensions to avoid potential large bitmap allocation if the input is damaged
+ sal_uInt64 nMaxWidth = pIStm->remainingSize();
+ nMaxWidth *= 256; //assume generous compression ratio
+ nMaxWidth /= aHeader.nHeight;
+ if (nMaxWidth < o3tl::make_unsigned(aHeader.nWidth))
+ return false;
+ break;
+ }
+ case RLE_4:
+ {
+ if (aHeader.nBitCount != 4)
+ return false;
+ sal_uInt64 nMaxWidth = pIStm->remainingSize();
+ nMaxWidth *= 512; //assume generous compression ratio
+ nMaxWidth /= aHeader.nHeight;
+ if (nMaxWidth < o3tl::make_unsigned(aHeader.nWidth))
+ return false;
+ break;
+ }
+ default:
+ // tdf#122958 invalid compression value used
+ if (aHeader.nCompression & 0x000F)
+ {
+ // lets assume that there was an error in the generating application
+ // and allow through as COMPRESS_NONE if the bottom byte is 0
+ SAL_WARN( "vcl", "bad bmp compression scheme: " << aHeader.nCompression << ", rejecting bmp");
+ return false;
+ }
+ else
+ SAL_WARN( "vcl", "bad bmp compression scheme: " << aHeader.nCompression << ", assuming meant to be COMPRESS_NONE");
+ [[fallthrough]];
+ case BITFIELDS:
+ case ZCOMPRESS:
+ case COMPRESS_NONE:
+ {
+ // (partially) check the image dimensions to avoid potential large bitmap allocation if the input is damaged
+ sal_uInt64 nMaxWidth = pIStm->remainingSize();
+ nMaxWidth /= aHeader.nHeight;
+ if (nMaxWidth < nAlignedWidth)
+ return false;
+ break;
+ }
+ }
+
+ const Size aSizePixel(aHeader.nWidth, aHeader.nHeight);
+ AlphaMask aNewBmpAlpha;
+ AlphaScopedWriteAccess pAccAlpha;
+ bool bAlphaPossible(pBmpAlpha && aHeader.nBitCount == 32);
+
+ if (bAlphaPossible)
+ {
+ const bool bRedSet(0 != aHeader.nV5RedMask);
+ const bool bGreenSet(0 != aHeader.nV5GreenMask);
+ const bool bBlueSet(0 != aHeader.nV5BlueMask);
+
+ // some clipboard entries have alpha mask on zero to say that there is
+ // no alpha; do only use this when the other masks are set. The MS docu
+ // says that masks are only to be set when bV5Compression is set to
+ // BI_BITFIELDS, but there seem to exist a wild variety of usages...
+ if((bRedSet || bGreenSet || bBlueSet) && (0 == aHeader.nV5AlphaMask))
+ {
+ bAlphaPossible = false;
+ }
+ }
+
+ if (bAlphaPossible)
+ {
+ aNewBmpAlpha = AlphaMask(aSizePixel);
+ pAccAlpha = AlphaScopedWriteAccess(aNewBmpAlpha);
+ }
+
+ sal_uInt16 nBitCount(discretizeBitcount(aHeader.nBitCount));
+ const BitmapPalette* pPal = &aPalette;
+ //ofz#948 match the surrounding logic of case TransparentType::Bitmap of
+ //ReadDIBBitmapEx but do it while reading for performance
+ const bool bIsAlpha = (nBitCount == 8 && !!aPalette && aPalette.IsGreyPalette8Bit());
+ const bool bForceToMonoWhileReading = (bIsMask && !bIsAlpha && nBitCount != 1);
+ if (bForceToMonoWhileReading)
+ {
+ pPal = nullptr;
+ nBitCount = 1;
+ SAL_WARN( "vcl", "forcing mask to monochrome");
+ }
+
+ Bitmap aNewBmp(aSizePixel, nBitCount, pPal);
+ BitmapScopedWriteAccess pAcc(aNewBmp);
+ if (!pAcc)
+ return false;
+ if (pAcc->Width() != aHeader.nWidth || pAcc->Height() != aHeader.nHeight)
+ {
+ return false;
+ }
+
+ // read bits
+ bool bAlphaUsed(false);
+ bool bRet = ImplReadDIBBits(*pIStm, aHeader, *pAcc, aPalette, pAccAlpha.get(), bTopDown, bAlphaUsed, nAlignedWidth, bForceToMonoWhileReading);
+
+ if (bRet && aHeader.nXPelsPerMeter && aHeader.nYPelsPerMeter)
+ {
+ MapMode aMapMode(
+ MapUnit::MapMM,
+ Point(),
+ Fraction(1000, aHeader.nXPelsPerMeter),
+ Fraction(1000, aHeader.nYPelsPerMeter));
+
+ aNewBmp.SetPrefMapMode(aMapMode);
+ aNewBmp.SetPrefSize(Size(aHeader.nWidth, aHeader.nHeight));
+ }
+
+ pAcc.reset();
+
+ if (bAlphaPossible)
+ {
+ pAccAlpha.reset();
+
+ if(!bAlphaUsed)
+ {
+ bAlphaPossible = false;
+ }
+ }
+
+ if (bRet)
+ {
+ rBmp = aNewBmp;
+
+ if(bAlphaPossible)
+ {
+ *pBmpAlpha = aNewBmpAlpha;
+ }
+ }
+
+ return bRet;
+}
+
+bool ImplReadDIBFileHeader( SvStream& rIStm, sal_uLong& rOffset )
+{
+ bool bRet = false;
+
+ const sal_uInt64 nStreamLength = rIStm.TellEnd();
+
+ sal_uInt16 nTmp16 = 0;
+ rIStm.ReadUInt16( nTmp16 );
+
+ if ( ( 0x4D42 == nTmp16 ) || ( 0x4142 == nTmp16 ) )
+ {
+ sal_uInt32 nTmp32(0);
+ if ( 0x4142 == nTmp16 )
+ {
+ rIStm.SeekRel( 12 );
+ rIStm.ReadUInt16( nTmp16 );
+ rIStm.SeekRel( 8 );
+ rIStm.ReadUInt32( nTmp32 );
+ rOffset = nTmp32 - 28;
+ bRet = ( 0x4D42 == nTmp16 );
+ }
+ else // 0x4D42 == nTmp16, 'MB' from BITMAPFILEHEADER
+ {
+ rIStm.SeekRel( 8 ); // we are on bfSize member of BITMAPFILEHEADER, forward to bfOffBits
+ rIStm.ReadUInt32( nTmp32 ); // read bfOffBits
+ rOffset = nTmp32 - 14; // adapt offset by sizeof(BITMAPFILEHEADER)
+ bRet = rIStm.GetError() == ERRCODE_NONE;
+ }
+
+ if ( rOffset >= nStreamLength )
+ {
+ // Offset claims that image starts past the end of the
+ // stream. Unlikely.
+ rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR );
+ bRet = false;
+ }
+ }
+ else
+ rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR );
+
+ return bRet;
+}
+
+bool ImplWriteDIBPalette( SvStream& rOStm, BitmapReadAccess const & rAcc )
+{
+ const sal_uInt16 nColors = rAcc.GetPaletteEntryCount();
+ const sal_uLong nPalSize = nColors * 4UL;
+ std::unique_ptr<sal_uInt8[]> pEntries(new sal_uInt8[ nPalSize ]);
+ sal_uInt8* pTmpEntry = pEntries.get();
+
+ for( sal_uInt16 i = 0; i < nColors; i++ )
+ {
+ const BitmapColor& rPalColor = rAcc.GetPaletteColor( i );
+
+ *pTmpEntry++ = rPalColor.GetBlue();
+ *pTmpEntry++ = rPalColor.GetGreen();
+ *pTmpEntry++ = rPalColor.GetRed();
+ *pTmpEntry++ = 0;
+ }
+
+ rOStm.WriteBytes( pEntries.get(), nPalSize );
+
+ return rOStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplWriteRLE( SvStream& rOStm, BitmapReadAccess const & rAcc, bool bRLE4 )
+{
+ const sal_uLong nWidth = rAcc.Width();
+ const sal_uLong nHeight = rAcc.Height();
+ sal_uLong nX;
+ sal_uLong nSaveIndex;
+ sal_uLong nCount;
+ sal_uLong nBufCount;
+ std::vector<sal_uInt8> aBuf(( nWidth << 1 ) + 2);
+ sal_uInt8 cPix;
+ sal_uInt8 cLast;
+ bool bFound;
+
+ for ( long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ nX = nBufCount = 0;
+ Scanline pScanline = rAcc.GetScanline( nY );
+
+ while( nX < nWidth )
+ {
+ nCount = 1;
+ cPix = rAcc.GetIndexFromData( pScanline, nX++ );
+
+ while( ( nX < nWidth ) && ( nCount < 255 )
+ && ( cPix == rAcc.GetIndexFromData( pScanline, nX ) ) )
+ {
+ nX++;
+ nCount++;
+ }
+
+ if ( nCount > 1 )
+ {
+ *pTmp++ = static_cast<sal_uInt8>(nCount);
+ *pTmp++ = ( bRLE4 ? ( ( cPix << 4 ) | cPix ) : cPix );
+ nBufCount += 2;
+ }
+ else
+ {
+ cLast = cPix;
+ nSaveIndex = nX - 1;
+ bFound = false;
+
+ while( ( nX < nWidth ) && ( nCount < 256 ) )
+ {
+ cPix = rAcc.GetIndexFromData( pScanline, nX );
+ if (cPix == cLast)
+ break;
+ nX++; nCount++;
+ cLast = cPix;
+ bFound = true;
+ }
+
+ if ( bFound )
+ nX--;
+
+ if ( nCount > 3 )
+ {
+ *pTmp++ = 0;
+ *pTmp++ = static_cast<sal_uInt8>(--nCount);
+
+ if( bRLE4 )
+ {
+ for ( sal_uLong i = 0; i < nCount; i++, pTmp++ )
+ {
+ *pTmp = rAcc.GetIndexFromData( pScanline, nSaveIndex++ ) << 4;
+
+ if ( ++i < nCount )
+ *pTmp |= rAcc.GetIndexFromData( pScanline, nSaveIndex++ );
+ }
+
+ nCount = ( nCount + 1 ) >> 1;
+ }
+ else
+ {
+ for( sal_uLong i = 0; i < nCount; i++ )
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, nSaveIndex++ );
+ }
+
+ if ( nCount & 1 )
+ {
+ *pTmp++ = 0;
+ nBufCount += ( nCount + 3 );
+ }
+ else
+ nBufCount += ( nCount + 2 );
+ }
+ else
+ {
+ *pTmp++ = 1;
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, nSaveIndex ) << (bRLE4 ? 4 : 0);
+
+ if ( nCount == 3 )
+ {
+ *pTmp++ = 1;
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, ++nSaveIndex ) << ( bRLE4 ? 4 : 0 );
+ nBufCount += 4;
+ }
+ else
+ nBufCount += 2;
+ }
+ }
+ }
+
+ aBuf[ nBufCount++ ] = 0;
+ aBuf[ nBufCount++ ] = 0;
+
+ rOStm.WriteBytes(aBuf.data(), nBufCount);
+ }
+
+ rOStm.WriteUChar( 0 );
+ rOStm.WriteUChar( 1 );
+
+ return rOStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplWriteDIBBits(SvStream& rOStm, BitmapReadAccess const & rAcc, BitmapReadAccess const * pAccAlpha, sal_uLong nCompression, sal_uInt32& rImageSize)
+{
+ if(!pAccAlpha && BITFIELDS == nCompression)
+ {
+ const ColorMask& rMask = rAcc.GetColorMask();
+ SVBT32 aVal32;
+
+ UInt32ToSVBT32( rMask.GetRedMask(), aVal32 );
+ rOStm.WriteBytes( aVal32, 4UL );
+
+ UInt32ToSVBT32( rMask.GetGreenMask(), aVal32 );
+ rOStm.WriteBytes( aVal32, 4UL );
+
+ UInt32ToSVBT32( rMask.GetBlueMask(), aVal32 );
+ rOStm.WriteBytes( aVal32, 4UL );
+
+ rImageSize = rOStm.Tell();
+
+ if( rAcc.IsBottomUp() )
+ rOStm.WriteBytes(rAcc.GetBuffer(), rAcc.Height() * rAcc.GetScanlineSize());
+ else
+ {
+ for( long nY = rAcc.Height() - 1, nScanlineSize = rAcc.GetScanlineSize(); nY >= 0; nY-- )
+ rOStm.WriteBytes( rAcc.GetScanline(nY), nScanlineSize );
+ }
+ }
+ else if(!pAccAlpha && ((RLE_4 == nCompression) || (RLE_8 == nCompression)))
+ {
+ rImageSize = rOStm.Tell();
+ ImplWriteRLE( rOStm, rAcc, RLE_4 == nCompression );
+ }
+ else if(!nCompression)
+ {
+ // #i5xxx# Limit bitcount to 24bit, the 32 bit cases are not
+ // handled properly below (would have to set color masks, and
+ // nCompression=BITFIELDS - but color mask is not set for
+ // formats != *_TC_*). Note that this very problem might cause
+ // trouble at other places - the introduction of 32 bit RGBA
+ // bitmaps is relatively recent.
+ // #i59239# discretize bitcount for aligned width to 1,4,8,24
+ // (other cases are not written below)
+ const sal_uInt16 nBitCount(pAccAlpha ? 32 : discretizeBitcount(rAcc.GetBitCount()));
+ const sal_uLong nAlignedWidth(AlignedWidth4Bytes(rAcc.Width() * nBitCount));
+ bool bNative(false);
+
+ switch(rAcc.GetScanlineFormat())
+ {
+ case ScanlineFormat::N1BitMsbPal:
+ case ScanlineFormat::N4BitMsnPal:
+ case ScanlineFormat::N8BitPal:
+ case ScanlineFormat::N24BitTcBgr:
+ {
+ if(!pAccAlpha && rAcc.IsBottomUp() && (rAcc.GetScanlineSize() == nAlignedWidth))
+ {
+ bNative = true;
+ }
+
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+
+ rImageSize = rOStm.Tell();
+
+ if(bNative)
+ {
+ rOStm.WriteBytes(rAcc.GetBuffer(), nAlignedWidth * rAcc.Height());
+ }
+ else
+ {
+ const long nWidth(rAcc.Width());
+ const long nHeight(rAcc.Height());
+ std::vector<sal_uInt8> aBuf(nAlignedWidth);
+ switch( nBitCount )
+ {
+ case 1:
+ {
+ //valgrind, zero out the trailing unused alignment bytes
+ size_t nUnusedBytes = nAlignedWidth - ((nWidth+7) / 8);
+ memset(aBuf.data() + nAlignedWidth - nUnusedBytes, 0, nUnusedBytes);
+
+ for( long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ sal_uInt8 cTmp = 0;
+ Scanline pScanline = rAcc.GetScanline( nY );
+
+ for( long nX = 0, nShift = 8; nX < nWidth; nX++ )
+ {
+ if( !nShift )
+ {
+ nShift = 8;
+ *pTmp++ = cTmp;
+ cTmp = 0;
+ }
+
+ cTmp |= rAcc.GetIndexFromData( pScanline, nX ) << --nShift;
+ }
+
+ *pTmp = cTmp;
+ rOStm.WriteBytes(aBuf.data(), nAlignedWidth);
+ }
+ }
+ break;
+
+ case 4:
+ {
+ //valgrind, zero out the trailing unused alignment bytes
+ size_t nUnusedBytes = nAlignedWidth - ((nWidth+1) / 2);
+ memset(aBuf.data() + nAlignedWidth - nUnusedBytes, 0, nUnusedBytes);
+
+ for( long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ sal_uInt8 cTmp = 0;
+ Scanline pScanline = rAcc.GetScanline( nY );
+
+ for( long nX = 0, nShift = 2; nX < nWidth; nX++ )
+ {
+ if( !nShift )
+ {
+ nShift = 2;
+ *pTmp++ = cTmp;
+ cTmp = 0;
+ }
+
+ cTmp |= rAcc.GetIndexFromData( pScanline, nX ) << ( --nShift << 2 );
+ }
+ *pTmp = cTmp;
+ rOStm.WriteBytes(aBuf.data(), nAlignedWidth);
+ }
+ }
+ break;
+
+ case 8:
+ {
+ for( long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ Scanline pScanline = rAcc.GetScanline( nY );
+
+ for( long nX = 0; nX < nWidth; nX++ )
+ *pTmp++ = rAcc.GetIndexFromData( pScanline, nX );
+
+ rOStm.WriteBytes(aBuf.data(), nAlignedWidth);
+ }
+ }
+ break;
+
+ case 24:
+ {
+ //valgrind, zero out the trailing unused alignment bytes
+ size_t nUnusedBytes = nAlignedWidth - nWidth * 3;
+ memset(aBuf.data() + nAlignedWidth - nUnusedBytes, 0, nUnusedBytes);
+ }
+ [[fallthrough]];
+ // #i59239# fallback to 24 bit format, if bitcount is non-default
+ default:
+ {
+ BitmapColor aPixelColor;
+ const bool bWriteAlpha(32 == nBitCount && pAccAlpha);
+
+ for( long nY = nHeight - 1; nY >= 0; nY-- )
+ {
+ sal_uInt8* pTmp = aBuf.data();
+ Scanline pScanlineAlpha = bWriteAlpha ? pAccAlpha->GetScanline( nY ) : nullptr;
+
+ for( long nX = 0; nX < nWidth; nX++ )
+ {
+ // when alpha is used, this may be non-24bit main bitmap, so use GetColor
+ // instead of GetPixel to ensure RGB value
+ aPixelColor = rAcc.GetColor( nY, nX );
+
+ *pTmp++ = aPixelColor.GetBlue();
+ *pTmp++ = aPixelColor.GetGreen();
+ *pTmp++ = aPixelColor.GetRed();
+
+ if(bWriteAlpha)
+ {
+ *pTmp++ = sal_uInt8(0xff) - pAccAlpha->GetIndexFromData( pScanlineAlpha, nX );
+ }
+ }
+
+ rOStm.WriteBytes(aBuf.data(), nAlignedWidth);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ rImageSize = rOStm.Tell() - rImageSize;
+
+ return (!rOStm.GetError());
+}
+
+bool ImplWriteDIBBody(const Bitmap& rBitmap, SvStream& rOStm, BitmapReadAccess const & rAcc, BitmapReadAccess const * pAccAlpha, bool bCompressed)
+{
+ const MapMode aMapPixel(MapUnit::MapPixel);
+ DIBV5Header aHeader;
+ sal_uLong nImageSizePos(0);
+ sal_uLong nEndPos(0);
+ sal_uInt32 nCompression(COMPRESS_NONE);
+ bool bRet(false);
+
+ aHeader.nSize = pAccAlpha ? DIBV5HEADERSIZE : DIBINFOHEADERSIZE; // size dependent on CF_DIB type to use
+ aHeader.nWidth = rAcc.Width();
+ aHeader.nHeight = rAcc.Height();
+ aHeader.nPlanes = 1;
+
+ if(!pAccAlpha && isBitfieldCompression(rAcc.GetScanlineFormat()))
+ {
+ aHeader.nBitCount = 32;
+ aHeader.nSizeImage = rAcc.Height() * rAcc.GetScanlineSize();
+ nCompression = BITFIELDS;
+ }
+ else
+ {
+ // #i5xxx# Limit bitcount to 24bit, the 32 bit cases are
+ // not handled properly below (would have to set color
+ // masks, and nCompression=BITFIELDS - but color mask is
+ // not set for formats != *_TC_*). Note that this very
+ // problem might cause trouble at other places - the
+ // introduction of 32 bit RGBA bitmaps is relatively
+ // recent.
+ // #i59239# discretize bitcount to 1,4,8,24 (other cases
+ // are not written below)
+ const sal_uInt16 nBitCount(pAccAlpha ? 32 : discretizeBitcount(rAcc.GetBitCount()));
+ aHeader.nBitCount = nBitCount;
+ aHeader.nSizeImage = rAcc.Height() * AlignedWidth4Bytes(rAcc.Width() * aHeader.nBitCount);
+
+ if(bCompressed)
+ {
+ if(4 == nBitCount)
+ {
+ nCompression = RLE_4;
+ }
+ else if(8 == nBitCount)
+ {
+ nCompression = RLE_8;
+ }
+ }
+ }
+
+ if((rOStm.GetCompressMode() & SvStreamCompressFlags::ZBITMAP) && (rOStm.GetVersion() >= SOFFICE_FILEFORMAT_40))
+ {
+ aHeader.nCompression = ZCOMPRESS;
+ }
+ else
+ {
+ aHeader.nCompression = nCompression;
+ }
+
+ if(rBitmap.GetPrefSize().Width() && rBitmap.GetPrefSize().Height() && (rBitmap.GetPrefMapMode() != aMapPixel))
+ {
+ // #i48108# Try to recover xpels/ypels as previously stored on
+ // disk. The problem with just converting maPrefSize to 100th
+ // mm and then relating that to the bitmap pixel size is that
+ // MapMode is integer-based, and suffers from roundoffs,
+ // especially if maPrefSize is small. Trying to circumvent
+ // that by performing part of the math in floating point.
+ const Size aScale100000(OutputDevice::LogicToLogic(Size(100000, 100000), MapMode(MapUnit::Map100thMM), rBitmap.GetPrefMapMode()));
+ const double fBmpWidthM(static_cast<double>(rBitmap.GetPrefSize().Width()) / aScale100000.Width());
+ const double fBmpHeightM(static_cast<double>(rBitmap.GetPrefSize().Height()) / aScale100000.Height());
+
+ if(!basegfx::fTools::equalZero(fBmpWidthM) && !basegfx::fTools::equalZero(fBmpHeightM))
+ {
+ aHeader.nXPelsPerMeter = basegfx::fround(rAcc.Width() / fabs(fBmpWidthM));
+ aHeader.nYPelsPerMeter = basegfx::fround(rAcc.Height() / fabs(fBmpHeightM));
+ }
+ }
+
+ aHeader.nColsUsed = ((!pAccAlpha && aHeader.nBitCount <= 8) ? rAcc.GetPaletteEntryCount() : 0);
+ aHeader.nColsImportant = 0;
+
+ rOStm.WriteUInt32( aHeader.nSize );
+ rOStm.WriteInt32( aHeader.nWidth );
+ rOStm.WriteInt32( aHeader.nHeight );
+ rOStm.WriteUInt16( aHeader.nPlanes );
+ rOStm.WriteUInt16( aHeader.nBitCount );
+ rOStm.WriteUInt32( aHeader.nCompression );
+
+ nImageSizePos = rOStm.Tell();
+ rOStm.SeekRel( sizeof( aHeader.nSizeImage ) );
+
+ rOStm.WriteInt32( aHeader.nXPelsPerMeter );
+ rOStm.WriteInt32( aHeader.nYPelsPerMeter );
+ rOStm.WriteUInt32( aHeader.nColsUsed );
+ rOStm.WriteUInt32( aHeader.nColsImportant );
+
+ if(pAccAlpha) // only write DIBV5 when asked to do so
+ {
+ aHeader.nV5CSType = 0x57696E20; // LCS_WINDOWS_COLOR_SPACE
+ aHeader.nV5Intent = 0x00000004; // LCS_GM_IMAGES
+
+ rOStm.WriteUInt32( aHeader.nV5RedMask );
+ rOStm.WriteUInt32( aHeader.nV5GreenMask );
+ rOStm.WriteUInt32( aHeader.nV5BlueMask );
+ rOStm.WriteUInt32( aHeader.nV5AlphaMask );
+ rOStm.WriteUInt32( aHeader.nV5CSType );
+
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzRed.aXyzX );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzRed.aXyzY );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzRed.aXyzZ );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzGreen.aXyzX );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzGreen.aXyzY );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzGreen.aXyzZ );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzBlue.aXyzX );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzBlue.aXyzY );
+ rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzBlue.aXyzZ );
+
+ rOStm.WriteUInt32( aHeader.nV5GammaRed );
+ rOStm.WriteUInt32( aHeader.nV5GammaGreen );
+ rOStm.WriteUInt32( aHeader.nV5GammaBlue );
+ rOStm.WriteUInt32( aHeader.nV5Intent );
+ rOStm.WriteUInt32( aHeader.nV5ProfileData );
+ rOStm.WriteUInt32( aHeader.nV5ProfileSize );
+ rOStm.WriteUInt32( aHeader.nV5Reserved );
+ }
+
+ if(ZCOMPRESS == aHeader.nCompression)
+ {
+ ZCodec aCodec;
+ SvMemoryStream aMemStm(aHeader.nSizeImage + 4096, 65535);
+ sal_uLong nCodedPos(rOStm.Tell());
+ sal_uLong nLastPos(0);
+ sal_uInt32 nCodedSize(0);
+ sal_uInt32 nUncodedSize(0);
+
+ // write uncoded data palette
+ if(aHeader.nColsUsed)
+ {
+ ImplWriteDIBPalette(aMemStm, rAcc);
+ }
+
+ // write uncoded bits
+ bRet = ImplWriteDIBBits(aMemStm, rAcc, pAccAlpha, nCompression, aHeader.nSizeImage);
+
+ // get uncoded size
+ nUncodedSize = aMemStm.Tell();
+
+ // seek over compress info
+ rOStm.SeekRel(12);
+
+ // write compressed data
+ aCodec.BeginCompression(3);
+ aCodec.Write(rOStm, static_cast<sal_uInt8 const *>(aMemStm.GetData()), nUncodedSize);
+ aCodec.EndCompression();
+
+ // update compress info ( coded size, uncoded size, uncoded compression )
+ nLastPos = rOStm.Tell();
+ nCodedSize = nLastPos - nCodedPos - 12;
+ rOStm.Seek(nCodedPos);
+ rOStm.WriteUInt32( nCodedSize ).WriteUInt32( nUncodedSize ).WriteUInt32( nCompression );
+ rOStm.Seek(nLastPos);
+
+ if(bRet)
+ {
+ bRet = (ERRCODE_NONE == rOStm.GetError());
+ }
+ }
+ else
+ {
+ if(aHeader.nColsUsed)
+ {
+ ImplWriteDIBPalette(rOStm, rAcc);
+ }
+
+ bRet = ImplWriteDIBBits(rOStm, rAcc, pAccAlpha, aHeader.nCompression, aHeader.nSizeImage);
+ }
+
+ nEndPos = rOStm.Tell();
+ rOStm.Seek(nImageSizePos);
+ rOStm.WriteUInt32( aHeader.nSizeImage );
+ rOStm.Seek(nEndPos);
+
+ return bRet;
+}
+
+bool ImplWriteDIBFileHeader(SvStream& rOStm, BitmapReadAccess const & rAcc)
+{
+ const sal_uInt32 nPalCount((rAcc.HasPalette() ? rAcc.GetPaletteEntryCount() : isBitfieldCompression(rAcc.GetScanlineFormat()) ? 3UL : 0UL));
+ const sal_uInt32 nOffset(14 + DIBINFOHEADERSIZE + nPalCount * 4UL);
+
+ rOStm.WriteUInt16( 0x4D42 ); // 'MB' from BITMAPFILEHEADER
+ rOStm.WriteUInt32( nOffset + (rAcc.Height() * rAcc.GetScanlineSize()) );
+ rOStm.WriteUInt16( 0 );
+ rOStm.WriteUInt16( 0 );
+ rOStm.WriteUInt32( nOffset );
+
+ return rOStm.GetError() == ERRCODE_NONE;
+}
+
+bool ImplReadDIB(
+ Bitmap& rTarget,
+ AlphaMask* pTargetAlpha,
+ SvStream& rIStm,
+ bool bFileHeader,
+ bool bIsMask=false,
+ bool bMSOFormat=false)
+{
+ const SvStreamEndian nOldFormat(rIStm.GetEndian());
+ const sal_uLong nOldPos(rIStm.Tell());
+ sal_uLong nOffset(0);
+ bool bRet(false);
+
+ rIStm.SetEndian(SvStreamEndian::LITTLE);
+
+ if(bFileHeader)
+ {
+ if(ImplReadDIBFileHeader(rIStm, nOffset))
+ {
+ bRet = ImplReadDIBBody(rIStm, rTarget, nOffset >= DIBV5HEADERSIZE ? pTargetAlpha : nullptr, nOffset, bIsMask, bMSOFormat);
+ }
+ }
+ else
+ {
+ bRet = ImplReadDIBBody(rIStm, rTarget, nullptr, nOffset, bIsMask, bMSOFormat);
+ }
+
+ if(!bRet)
+ {
+ if(!rIStm.GetError())
+ {
+ rIStm.SetError(SVSTREAM_GENERALERROR);
+ }
+
+ rIStm.Seek(nOldPos);
+ }
+
+ rIStm.SetEndian(nOldFormat);
+
+ return bRet;
+}
+
+bool ImplWriteDIB(
+ const Bitmap& rSource,
+ SvStream& rOStm,
+ bool bCompressed,
+ bool bFileHeader)
+{
+ const Size aSizePix(rSource.GetSizePixel());
+ bool bRet(false);
+
+ if(aSizePix.Width() && aSizePix.Height())
+ {
+ Bitmap::ScopedReadAccess pAcc(const_cast< Bitmap& >(rSource));
+ Bitmap::ScopedReadAccess pAccAlpha;
+ const SvStreamEndian nOldFormat(rOStm.GetEndian());
+ const sal_uLong nOldPos(rOStm.Tell());
+
+ rOStm.SetEndian(SvStreamEndian::LITTLE);
+
+ if (pAcc)
+ {
+ if(bFileHeader)
+ {
+ if(ImplWriteDIBFileHeader(rOStm, *pAcc))
+ {
+ bRet = ImplWriteDIBBody(rSource, rOStm, *pAcc, pAccAlpha.get(), bCompressed);
+ }
+ }
+ else
+ {
+ bRet = ImplWriteDIBBody(rSource, rOStm, *pAcc, pAccAlpha.get(), bCompressed);
+ }
+
+ pAcc.reset();
+ }
+
+ pAccAlpha.reset();
+
+ if(!bRet)
+ {
+ rOStm.SetError(SVSTREAM_GENERALERROR);
+ rOStm.Seek(nOldPos);
+ }
+
+ rOStm.SetEndian(nOldFormat);
+ }
+
+ return bRet;
+}
+
+} // unnamed namespace
+
+bool ReadDIB(
+ Bitmap& rTarget,
+ SvStream& rIStm,
+ bool bFileHeader,
+ bool bMSOFormat)
+{
+ return ImplReadDIB(rTarget, nullptr, rIStm, bFileHeader, false, bMSOFormat);
+}
+
+bool ReadDIBBitmapEx(
+ BitmapEx& rTarget,
+ SvStream& rIStm,
+ bool bFileHeader,
+ bool bMSOFormat)
+{
+ Bitmap aBmp;
+ bool bRetval(ImplReadDIB(aBmp, nullptr, rIStm, bFileHeader, /*bMask*/false, bMSOFormat) && !rIStm.GetError());
+
+ if(bRetval)
+ {
+ // base bitmap was read, set as return value and try to read alpha extra-data
+ const sal_uLong nStmPos(rIStm.Tell());
+ sal_uInt32 nMagic1(0);
+ sal_uInt32 nMagic2(0);
+
+ rTarget = BitmapEx(aBmp);
+ rIStm.ReadUInt32( nMagic1 ).ReadUInt32( nMagic2 );
+ bRetval = (0x25091962 == nMagic1) && (0xACB20201 == nMagic2) && !rIStm.GetError();
+
+ if(bRetval)
+ {
+ sal_uInt8 tmp = 0;
+ rIStm.ReadUChar( tmp );
+ TransparentType transparent = static_cast<TransparentType>(tmp);
+ bRetval = !rIStm.GetError();
+
+ if(bRetval)
+ {
+ switch (transparent)
+ {
+ case TransparentType::Bitmap:
+ {
+ Bitmap aMask;
+
+ bRetval = ImplReadDIB(aMask, nullptr, rIStm, true, true);
+
+ if(bRetval)
+ {
+ if(!!aMask)
+ {
+ // do we have an alpha mask?
+ if((8 == aMask.GetBitCount()) && aMask.HasGreyPalette8Bit())
+ {
+ AlphaMask aAlpha;
+
+ // create alpha mask quickly (without greyscale conversion)
+ aAlpha.ImplSetBitmap(aMask);
+ rTarget = BitmapEx(aBmp, aAlpha);
+ }
+ else
+ {
+ rTarget = BitmapEx(aBmp, aMask);
+ }
+ }
+ }
+ break;
+ }
+ case TransparentType::Color:
+ {
+ Color aTransparentColor;
+
+ tools::GenericTypeSerializer aSerializer(rIStm);
+ aSerializer.readColor(aTransparentColor);
+
+ bRetval = !rIStm.GetError();
+
+ if(bRetval)
+ {
+ rTarget = BitmapEx(aBmp, aTransparentColor);
+ }
+ break;
+ }
+ default: break;
+ }
+ }
+ }
+
+ if(!bRetval)
+ {
+ // alpha extra data could not be read; reset, but use base bitmap as result
+ rIStm.ResetError();
+ rIStm.Seek(nStmPos);
+ bRetval = true;
+ }
+ }
+
+ return bRetval;
+}
+
+bool ReadDIBV5(
+ Bitmap& rTarget,
+ AlphaMask& rTargetAlpha,
+ SvStream& rIStm)
+{
+ return ImplReadDIB(rTarget, &rTargetAlpha, rIStm, true);
+}
+
+bool ReadRawDIB(
+ BitmapEx& rTarget,
+ const unsigned char* pBuf,
+ const ScanlineFormat nFormat,
+ const int nHeight,
+ const int nStride)
+{
+ BitmapScopedWriteAccess pWriteAccess(rTarget.maBitmap.AcquireWriteAccess(), rTarget.maBitmap);
+ for (int nRow = 0; nRow < nHeight; ++nRow)
+ {
+ pWriteAccess->CopyScanline(nRow, pBuf + (nStride * nRow), nFormat, nStride);
+ }
+
+ return true;
+}
+
+bool WriteDIB(
+ const Bitmap& rSource,
+ SvStream& rOStm,
+ bool bCompressed,
+ bool bFileHeader)
+{
+ return ImplWriteDIB(rSource, rOStm, bCompressed, bFileHeader);
+}
+
+bool WriteDIB(
+ const BitmapEx& rSource,
+ SvStream& rOStm,
+ bool bCompressed)
+{
+ return ImplWriteDIB(rSource.GetBitmap(), rOStm, bCompressed, /*bFileHeader*/true);
+}
+
+bool WriteDIBBitmapEx(
+ const BitmapEx& rSource,
+ SvStream& rOStm)
+{
+ if(ImplWriteDIB(rSource.GetBitmap(), rOStm, true, true))
+ {
+ rOStm.WriteUInt32( 0x25091962 );
+ rOStm.WriteUInt32( 0xACB20201 );
+ rOStm.WriteUChar( static_cast<sal_uChar>(rSource.meTransparent) );
+
+ if(TransparentType::Bitmap == rSource.meTransparent)
+ {
+ return ImplWriteDIB(rSource.maMask, rOStm, true, true);
+ }
+ else if(TransparentType::Color == rSource.meTransparent)
+ {
+ tools::GenericTypeSerializer aSerializer(rOStm);
+ aSerializer.writeColor(rSource.maTransparentColor);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+sal_uInt32 getDIBV5HeaderSize()
+{
+ return DIBV5HEADERSIZE;
+}
+
+/* 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 000000000..e59f94071
--- /dev/null
+++ b/vcl/source/gdi/embeddedfontshelper.cxx
@@ -0,0 +1,332 @@
+/* -*- 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 <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 <outdev.h>
+#include <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,
+ const char* extra, std::vector< unsigned char > 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 );
+ for( sal_uInt64 pos = 0;
+ pos < read && keyPos < key.size();
+ ++pos )
+ buffer[ 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, const char* 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( strcmp( extra, "?" ) == 0 )
+ filename += OUString::number( uniqueCounter++ );
+ else
+ filename += OStringToOUString( extra, RTL_TEXTENCODING_ASCII_US );
+ 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, 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( const OUString& 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 = 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();
+ PhysicalFontCollection fonts;
+ graphics->GetDevFontList( &fonts );
+ std::unique_ptr< ImplDeviceFontList > fontInfo( fonts.GetDeviceFontList());
+ PhysicalFontFace* selected = nullptr;
+ for( int i = 0;
+ i < fontInfo->Count();
+ ++i )
+ {
+ 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;
+ }
+ }
+ }
+ if( selected != nullptr )
+ {
+ long size;
+ if (const void* data = graphics->GetEmbedFontData(selected, &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( static_cast< const char* >( 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;
+ }
+ }
+ graphics->FreeEmbedFontData( data, size );
+ }
+ }
+ return ok ? url : "";
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/extoutdevdata.cxx b/vcl/source/gdi/extoutdevdata.cxx
new file mode 100644
index 000000000..49f188c85
--- /dev/null
+++ b/vcl/source/gdi/extoutdevdata.cxx
@@ -0,0 +1,31 @@
+/* -*- 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/gdimetafiletools.cxx b/vcl/source/gdi/gdimetafiletools.cxx
new file mode 100644
index 000000000..6a74c4d69
--- /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.IsTransparent())
+ {
+ // use given alpha channel
+ aVDev->DrawBitmap(Point(0, 0), rBitmapEx.GetAlpha().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",
+ 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",
+ 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< 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 long aHorMove(pA->GetHorzMove());
+ const 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 PushFlags nFlags(pA->GetFlags());
+
+ aPushFlags.push_back(nFlags);
+
+ if(nFlags & PushFlags::CLIPREGION)
+ {
+ aClips.push_back(aClips.back());
+ }
+
+ if(nFlags & PushFlags::MAPMODE)
+ {
+ aMapModes.push_back(aMapModes.back());
+ }
+ break;
+ }
+
+ case MetaActionType::POP :
+ {
+
+ if(!aPushFlags.empty())
+ {
+ const PushFlags nFlags(aPushFlags.back());
+ aPushFlags.pop_back();
+
+ if(nFlags & PushFlags::CLIPREGION)
+ {
+ if(aClips.size() > 1)
+ {
+ aClips.pop_back();
+ }
+ else
+ {
+ OSL_ENSURE(false, "Wrong POP() in ClipRegions (!)");
+ }
+ }
+
+ if(nFlags & 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::max(0.0, std::min(1.0, fRadiusX));
+ fRadiusY = std::max(0.0, std::min(1.0, fRadiusY));
+
+ 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 000000000..46a145750
--- /dev/null
+++ b/vcl/source/gdi/gdimtf.cxx
@@ -0,0 +1,2866 @@
+/* -*- 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 <tools/diagnose_ex.h>
+#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 <svmconverter.hxx>
+#include <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;
+
+#define GAMMA( _def_cVal, _def_InvGamma ) (static_cast<sal_uInt8>(MinMax(FRound(pow( _def_cVal/255.0,_def_InvGamma)*255.0),0,255)))
+
+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 )
+{
+}
+
+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 )
+{
+ 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;
+
+ 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 )
+ {
+ MetaAction* pAction = GetCurAction();
+ const size_t nObjCount = m_aList.size();
+
+ rMtf.UseCanvas( rMtf.GetUseCanvas() || m_bUseCanvas );
+
+ for( size_t nCurPos = m_nCurrentActionElement; nCurPos < nObjCount; nCurPos++ )
+ {
+ if( pAction )
+ {
+ rMtf.AddAction( pAction );
+ }
+
+ pAction = NextAction();
+ }
+ }
+}
+
+void GDIMetaFile::Play( OutputDevice* pOut, size_t nPos )
+{
+ if( !m_bRecord )
+ {
+ MetaAction* pAction = GetCurAction();
+ const size_t nObjCount = m_aList.size();
+ size_t nSyncCount = ( pOut->GetOutDevType() == OUTDEV_WINDOW ) ? 0x000000ff : 0xffffffff;
+
+ 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.
+ pOut->Push( PushFlags::TEXTLAYOUTMODE|PushFlags::TEXTLANGUAGE );
+ pOut->SetLayoutMode( ComplexTextLayoutFlags::Default );
+ pOut->SetDigitLanguage( LANGUAGE_SYSTEM );
+
+ SAL_INFO( "vcl.gdi", "GDIMetaFile::Play on device of size: " << pOut->GetOutputSizePixel().Width() << " " << pOut->GetOutputSizePixel().Height());
+
+ if( !ImplPlayWithRenderer( pOut, Point(0,0), pOut->GetOutputSize() ) ) {
+ size_t i = 0;
+ for( size_t nCurPos = m_nCurrentActionElement; nCurPos < nPos; nCurPos++ )
+ {
+ if( pAction )
+ {
+ pAction->Execute( pOut );
+
+ // flush output from time to time
+ if( i++ > nSyncCount )
+ {
+ static_cast<vcl::Window*>( pOut )->Flush();
+ i = 0;
+ }
+ }
+
+ pAction = NextAction();
+ }
+ }
+ pOut->Pop();
+ }
+}
+
+bool GDIMetaFile::ImplPlayWithRenderer( OutputDevice* pOut, const Point& rPos, Size rLogicDestSize )
+{
+ if (!m_bUseCanvas)
+ return false;
+
+ Size rDestSize( pOut->LogicToPixel( rLogicDestSize ) );
+
+ const vcl::Window* win = dynamic_cast <vcl::Window*> ( pOut );
+
+ if (!win)
+ win = Application::GetActiveTopWindow();
+ if (!win)
+ win = Application::GetFirstTopLevelWindow();
+
+ if (!win)
+ return false;
+
+ try
+ {
+ uno::Reference<rendering::XCanvas> xCanvas = win->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 (pOut->GetMapMode().GetMapUnit() == MapUnit::MapPixel)
+ pOut->DrawBitmapEx( rPos, aBitmapEx );
+ else
+ pOut->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* pOut, const Point& rPos,
+ const Size& rSize )
+{
+ MapMode aDrawMap( GetPrefMapMode() );
+ Size aDestSize( pOut->LogicToPixel( rSize ) );
+
+ if( !aDestSize.Width() || !aDestSize.Height() )
+ return;
+
+ GDIMetaFile* pMtf = pOut->GetConnectMetaFile();
+
+ if( ImplPlayWithRenderer( pOut, rPos, rSize ) )
+ return;
+
+ Size aTmpPrefSize( pOut->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(); aDrawMap.SetScaleX( aScaleX );
+ aScaleY *= aDrawMap.GetScaleY(); 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 pOut, 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( pOut->GetPixelOffset() );
+ const Size aEmptySize;
+ pOut->SetPixelOffset( aEmptySize );
+ aDrawMap.SetOrigin( pOut->PixelToLogic( pOut->LogicToPixel( rPos ), aDrawMap ) );
+ pOut->SetPixelOffset( rOldOffset );
+
+ pOut->Push();
+
+ if ( pMtf && pMtf->IsRecord() && ( pOut->GetOutDevType() != OUTDEV_PRINTER ) )
+ pOut->SetRelativeMapMode( aDrawMap );
+ else
+ pOut->SetMapMode( aDrawMap );
+
+ // #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.
+ pOut->SetLayoutMode( ComplexTextLayoutFlags::Default );
+ pOut->SetDigitLanguage( LANGUAGE_SYSTEM );
+
+ Play( pOut );
+
+ pOut->Pop();
+
+}
+
+void GDIMetaFile::Pause( bool _bPause )
+{
+ if( m_bRecord )
+ {
+ 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() );
+ 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( long nX, 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( long nX, long nY, long nDPIX, 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<long>(aOffset.Width() * static_cast<double>(aMap.GetScaleX())) );
+ aOffset.setHeight( static_cast<long>(aOffset.Height() * static_cast<double>(aMap.GetScaleY())) );
+ }
+ else
+ aOffset = OutputDevice::LogicToLogic( aBaseOffset, GetPrefMapMode(), aMapVDev->GetMapMode() );
+ }
+
+ pModAct->Move( aOffset.Width(), aOffset.Height() );
+ }
+}
+
+void GDIMetaFile::Scale( double fScaleX, double 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 );
+ }
+
+ 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( aNewReg, true );
+ m_aList[ m_nCurrentActionElement ] = pNewAct;
+ }
+ }
+}
+
+Point GDIMetaFile::ImplGetRotatedPoint( const Point& rPt, const Point& rRotatePt,
+ const Size& rOffset, double fSin, double fCos )
+{
+ const long nX = rPt.X() - rRotatePt.X();
+ const 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::DEFAULT);
+ 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( long nAngle10 )
+{
+ nAngle10 %= 3600;
+ nAngle10 = ( nAngle10 < 0 ) ? ( 3599 + nAngle10 ) : nAngle10;
+
+ if( !nAngle10 )
+ return;
+
+ GDIMetaFile aMtf;
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev;
+ const double fAngle = F_PI1800 * 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( 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->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::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::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::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::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,
+ ImplGetRotatedPolygon( 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", 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", 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() + static_cast<sal_uInt16>(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::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::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( 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() + static_cast<sal_uInt16>(nAngle10) );
+ aMtf.AddAction( new MetaFontAction( 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* o_pHairline )
+{
+ tools::Rectangle aBounds( i_rInBounds );
+ if( ! i_rInBounds.IsEmpty() && ! i_rClipStack.empty() && ! i_rClipStack.back().IsEmpty() )
+ aBounds.Intersection( i_rClipStack.back() );
+ if( ! aBounds.IsEmpty() )
+ {
+ if( ! o_rOutBounds.IsEmpty() )
+ o_rOutBounds.Union( aBounds );
+ else
+ o_rOutBounds = aBounds;
+
+ if(o_pHairline)
+ {
+ if( ! o_pHairline->IsEmpty() )
+ o_pHairline->Union( aBounds );
+ else
+ *o_pHairline = aBounds;
+ }
+ }
+}
+
+tools::Rectangle GDIMetaFile::GetBoundRect( OutputDevice& i_rReference, tools::Rectangle* pHairline ) const
+{
+ ScopedVclPtrInstance< VirtualDevice > aMapVDev( i_rReference );
+
+ aMapVDev->EnableOutput( false );
+ aMapVDev->SetMapMode( GetPrefMapMode() );
+
+ std::vector<tools::Rectangle> aClipStack( 1, tools::Rectangle() );
+ std::vector<PushFlags> aPushFlagStack;
+
+ tools::Rectangle aBound;
+
+ if(pHairline)
+ *pHairline = tools::Rectangle();
+
+ const sal_uLong nCount(GetActionSize());
+
+ for(sal_uLong a(0); a < nCount; a++)
+ {
+ MetaAction* pAction = GetAction(a);
+ const MetaActionType nActionType = pAction->GetType();
+ tools::Rectangle* pUseHairline = (pHairline && aMapVDev->IsLineColor()) ? pHairline : nullptr;
+
+ 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, pUseHairline );
+ }
+ 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, pUseHairline );
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ MetaLineAction* pAct = static_cast<MetaLineAction*>(pAction);
+ Point aP1( pAct->GetStartPoint() ), aP2( pAct->GetEndPoint() );
+ tools::Rectangle aRect( aP1, aP2 );
+ aRect.Justify();
+
+ if(pUseHairline)
+ {
+ const LineInfo& rLineInfo = pAct->GetLineInfo();
+
+ if(0 != rLineInfo.GetWidth())
+ pUseHairline = nullptr;
+ }
+
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, pUseHairline );
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ MetaRectAction* pAct = static_cast<MetaRectAction*>(pAction);
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, pUseHairline );
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ MetaRoundRectAction* pAct = static_cast<MetaRoundRectAction*>(pAction);
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, pUseHairline );
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ MetaEllipseAction* pAct = static_cast<MetaEllipseAction*>(pAction);
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, pUseHairline );
+ }
+ 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, pUseHairline );
+ }
+ 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, pUseHairline );
+ }
+ 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, pUseHairline );
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ MetaPolyLineAction* pAct = static_cast<MetaPolyLineAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetPolygon().GetBoundRect() );
+
+ if(pUseHairline)
+ {
+ const LineInfo& rLineInfo = pAct->GetLineInfo();
+
+ if(0 != rLineInfo.GetWidth())
+ pUseHairline = nullptr;
+ }
+
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, pUseHairline );
+ }
+ 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, pUseHairline );
+ }
+ 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, pUseHairline );
+ }
+ 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, nullptr );
+ }
+ 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() );
+ Point aPt( pAct->GetPoint() );
+ aRect.Move( aPt.X(), aPt.Y() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, nullptr );
+ }
+ 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, nullptr );
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ {
+ MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pAction);
+ // measure a test string to get ascend and descent right
+ static const sal_Unicode pStr[] = { 0xc4, 0x67, 0 };
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ 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, nullptr );
+ }
+ break;
+
+ case MetaActionType::WALLPAPER:
+ {
+ MetaWallpaperAction* pAct = static_cast<MetaWallpaperAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, nullptr );
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ MetaTextRectAction* pAct = static_cast<MetaTextRectAction*>(pAction);
+ tools::Rectangle aRect( pAct->GetRect() );
+ ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack, nullptr );
+ }
+ 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() & PushFlags::CLIPREGION )
+ {
+ tools::Rectangle aRect( aClipStack.back() );
+ aClipStack.push_back( aRect );
+ }
+ }
+ else if( nActionType == MetaActionType::POP )
+ {
+ // sanity check
+ if( ! aPushFlagStack.empty() )
+ {
+ if( aPushFlagStack.back() & 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( rColor.GetTransparency(),
+ 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( rColor.GetTransparency(), 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(), 4, &aPal );
+ aBmp.Erase( static_cast<const ImplBmpMonoParam*>(pBmpParam)->aColor );
+
+ if( rBmpEx.IsAlpha() )
+ return BitmapEx( aBmp, rBmpEx.GetAlpha() );
+ else if( rBmpEx.IsTransparent() )
+ return BitmapEx( aBmp, rBmpEx.GetMask() );
+ 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;
+
+ 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( 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, 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(), 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(), 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 * MinMax( nContrastPercent, 0, 100 ) );
+ else
+ fM = ( 128.0 + 1.27 * MinMax( nContrastPercent, -100, 0 ) ) / 128.0;
+
+ if(!msoBrightness)
+ // total offset = luminance offset + contrast offset
+ fOff = MinMax( nLuminancePercent, -100, 100 ) * 2.55 + 128.0 - fM * 128.0;
+ else
+ fOff = MinMax( nLuminancePercent, -100, 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( long nX = 0; nX < 256; nX++ )
+ {
+ if(!msoBrightness)
+ {
+ aColParam.pMapR[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fROff ), 0, 255 ));
+ aColParam.pMapG[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fGOff ), 0, 255 ));
+ aColParam.pMapB[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( nX * fM + fBOff ), 0, 255 ));
+ }
+ else
+ {
+ aColParam.pMapR[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fROff/2-128) * fM + 128 + fROff/2 ), 0, 255 ));
+ aColParam.pMapG[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fGOff/2-128) * fM + 128 + fGOff/2 ), 0, 255 ));
+ aColParam.pMapB[ nX ] = static_cast<sal_uInt8>(MinMax( FRound( (nX+fBOff/2-128) * fM + 128 + fBOff/2 ), 0, 255 ));
+ }
+ 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++ )
+ {
+ long nVal;
+
+ nVal = pSearchColors[ i ].GetRed();
+ aColParam.pMinR[ i ] = static_cast<sal_uLong>(std::max( nVal, 0L ));
+ aColParam.pMaxR[ i ] = static_cast<sal_uLong>(std::min( nVal, 255L ));
+
+ nVal = pSearchColors[ i ].GetGreen();
+ aColParam.pMinG[ i ] = static_cast<sal_uLong>(std::max( nVal, 0L ));
+ aColParam.pMaxG[ i ] = static_cast<sal_uLong>(std::min( nVal, 255L ));
+
+ nVal = pSearchColors[ i ].GetBlue();
+ aColParam.pMinB[ i ] = static_cast<sal_uLong>(std::max( nVal, 0L ));
+ aColParam.pMaxB[ i ] = static_cast<sal_uLong>(std::min( nVal, 255L ));
+ }
+
+ 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;
+}
+
+BitmapChecksum GDIMetaFile::GetChecksum() const
+{
+ SvMemoryStream aMemStm( 65535, 65535 );
+ ImplMetaWriteData aWriteData;
+ SVBT16 aBT16;
+ SVBT32 aBT32;
+ BitmapChecksumOctetArray aBCOA;
+ BitmapChecksum nCrc = 0;
+
+ aWriteData.meActualCharSet = aMemStm.GetStreamCharSet();
+ for( size_t i = 0, nObjCount = GetActionSize(); i < nObjCount; i++ )
+ {
+ MetaAction* pAction = GetAction( i );
+
+ switch( pAction->GetType() )
+ {
+ case MetaActionType::BMP:
+ {
+ MetaBmpAction* pAct = static_cast<MetaBmpAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmap().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ Int32ToSVBT32( pAct->GetPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmap().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ Int32ToSVBT32( pAct->GetPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmap().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ Int32ToSVBT32( pAct->GetDestPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ MetaBmpExAction* pAct = static_cast<MetaBmpExAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmapEx().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ Int32ToSVBT32( pAct->GetPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmapEx().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ Int32ToSVBT32( pAct->GetPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmapEx().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ Int32ToSVBT32( pAct->GetDestPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::MASK:
+ {
+ MetaMaskAction* pAct = static_cast<MetaMaskAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmap().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ UInt32ToSVBT32( sal_uInt32(pAct->GetColor()), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::MASKSCALE:
+ {
+ MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmap().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ UInt32ToSVBT32( sal_uInt32(pAct->GetColor()), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ {
+ MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction);
+
+ ShortToSVBT16( static_cast<sal_uInt16>(pAct->GetType()), aBT16 );
+ nCrc = vcl_get_checksum( nCrc, aBT16, 2 );
+
+ BCToBCOA( pAct->GetBitmap().GetChecksum(), aBCOA );
+ nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
+
+ UInt32ToSVBT32( sal_uInt32(pAct->GetColor()), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetDestSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcPoint().X(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcPoint().Y(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcSize().Width(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+
+ Int32ToSVBT32( pAct->GetSrcSize().Height(), aBT32 );
+ nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
+ }
+ break;
+
+ case MetaActionType::EPS :
+ {
+ MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction);
+ nCrc = vcl_get_checksum( nCrc, pAct->GetLink().GetData(), pAct->GetLink().GetDataSize() );
+ }
+ break;
+
+ case MetaActionType::CLIPREGION :
+ {
+ MetaClipRegionAction& rAct = static_cast<MetaClipRegionAction&>(*pAction);
+ const vcl::Region& rRegion = rAct.GetRegion();
+
+ if(rRegion.HasPolyPolygonOrB2DPolyPolygon())
+ {
+ // It has shown that this is a possible bottleneck for checksum calculation.
+ // In worst case a very expensive RegionHandle representation gets created.
+ // In this case it's cheaper to use the PolyPolygon
+ const basegfx::B2DPolyPolygon aPolyPolygon(rRegion.GetAsB2DPolyPolygon());
+ SVBT64 aSVBT64;
+
+ for(auto const& rPolygon : aPolyPolygon)
+ {
+ const sal_uInt32 nPointCount(rPolygon.count());
+ const bool bControl(rPolygon.areControlPointsUsed());
+
+ for(sal_uInt32 b(0); b < nPointCount; b++)
+ {
+ const basegfx::B2DPoint aPoint(rPolygon.getB2DPoint(b));
+
+ DoubleToSVBT64(aPoint.getX(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ DoubleToSVBT64(aPoint.getY(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+
+ if(bControl)
+ {
+ if(rPolygon.isPrevControlPointUsed(b))
+ {
+ const basegfx::B2DPoint aCtrl(rPolygon.getPrevControlPoint(b));
+
+ DoubleToSVBT64(aCtrl.getX(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ DoubleToSVBT64(aCtrl.getY(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ }
+
+ if(rPolygon.isNextControlPointUsed(b))
+ {
+ const basegfx::B2DPoint aCtrl(rPolygon.getNextControlPoint(b));
+
+ DoubleToSVBT64(aCtrl.getX(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ DoubleToSVBT64(aCtrl.getY(), aSVBT64);
+ nCrc = vcl_get_checksum(nCrc, aSVBT64, 8);
+ }
+ }
+ }
+ }
+
+ sal_uInt8 tmp = static_cast<sal_uInt8>(rAct.IsClipping());
+ nCrc = vcl_get_checksum(nCrc, &tmp, 1);
+ }
+ else
+ {
+ pAction->Write( aMemStm, &aWriteData );
+ nCrc = vcl_get_checksum( nCrc, aMemStm.GetData(), aMemStm.Tell() );
+ aMemStm.Seek( 0 );
+ }
+ }
+ break;
+
+ default:
+ {
+ pAction->Write( aMemStm, &aWriteData );
+ nCrc = vcl_get_checksum( nCrc, aMemStm.GetData(), aMemStm.Tell() );
+ aMemStm.Seek( 0 );
+ }
+ break;
+ }
+ }
+
+ return nCrc;
+}
+
+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() )
+ nSizeBytes += ( pTextArrayAction->GetLen() << 2 );
+ }
+ break;
+ default: break;
+ }
+ }
+
+ return nSizeBytes;
+}
+
+namespace
+{
+ class DepthGuard
+ {
+ private:
+ ImplMetaReadData& m_rData;
+ rtl_TextEncoding m_eOrigCharSet;
+ public:
+ DepthGuard(ImplMetaReadData& rData, SvStream const & rIStm)
+ : m_rData(rData)
+ , m_eOrigCharSet(m_rData.meActualCharSet)
+ {
+ ++m_rData.mnParseDepth;
+ m_rData.meActualCharSet = rIStm.GetStreamCharSet();
+ }
+ bool TooDeep() const { return m_rData.mnParseDepth > 1024; }
+ ~DepthGuard()
+ {
+ --m_rData.mnParseDepth;
+ m_rData.meActualCharSet = m_eOrigCharSet;
+ }
+ };
+}
+
+SvStream& ReadGDIMetaFile(SvStream& rIStm, GDIMetaFile& rGDIMetaFile, ImplMetaReadData* pData)
+{
+ if (rIStm.GetError())
+ {
+ SAL_WARN("vcl.gdi", "Stream error: " << rIStm.GetError());
+ return rIStm;
+ }
+
+ sal_uLong nStmPos = rIStm.Tell();
+ SvStreamEndian nOldFormat = rIStm.GetEndian();
+
+ rIStm.SetEndian( SvStreamEndian::LITTLE );
+
+ try
+ {
+ char aId[7];
+ aId[0] = 0;
+ aId[6] = 0;
+ rIStm.ReadBytes( aId, 6 );
+
+ if ( !strcmp( aId, "VCLMTF" ) )
+ {
+ // new format
+ sal_uInt32 nStmCompressMode = 0;
+ sal_uInt32 nCount = 0;
+ std::unique_ptr<VersionCompat> pCompat(new VersionCompat( rIStm, StreamMode::READ ));
+
+ rIStm.ReadUInt32( nStmCompressMode );
+ ReadMapMode( rIStm, rGDIMetaFile.m_aPrefMapMode );
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readSize(rGDIMetaFile.m_aPrefSize);
+ rIStm.ReadUInt32( nCount );
+
+ pCompat.reset(); // destructor writes stuff into the header
+
+ std::unique_ptr<ImplMetaReadData> xReadData;
+ if (!pData)
+ {
+ xReadData.reset(new ImplMetaReadData);
+ pData = xReadData.get();
+ }
+ DepthGuard aDepthGuard(*pData, rIStm);
+
+ if (aDepthGuard.TooDeep())
+ throw std::runtime_error("too much recursion");
+
+ for( sal_uInt32 nAction = 0; ( nAction < nCount ) && !rIStm.eof(); nAction++ )
+ {
+ MetaAction* pAction = MetaAction::ReadMetaAction(rIStm, pData);
+ if( pAction )
+ {
+ if (pAction->GetType() == MetaActionType::COMMENT)
+ {
+ MetaCommentAction* pCommentAct = static_cast<MetaCommentAction*>(pAction);
+ if ( pCommentAct->GetComment() == "EMF_PLUS" )
+ rGDIMetaFile.UseCanvas( true );
+ }
+ rGDIMetaFile.AddAction( pAction );
+ }
+ }
+ }
+ else
+ {
+ rIStm.Seek( nStmPos );
+ SVMConverter( rIStm, rGDIMetaFile );
+ }
+ }
+ catch (...)
+ {
+ SAL_WARN("vcl", "GDIMetaFile exception during load");
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ };
+
+ // check for errors
+ if( rIStm.GetError() )
+ {
+ rGDIMetaFile.Clear();
+ rIStm.Seek( nStmPos );
+ }
+
+ rIStm.SetEndian( nOldFormat );
+ return rIStm;
+}
+
+SvStream& WriteGDIMetaFile( SvStream& rOStm, const GDIMetaFile& rGDIMetaFile )
+{
+ if( !rOStm.GetError() )
+ {
+ const_cast< GDIMetaFile& >( rGDIMetaFile ).Write( rOStm );
+ }
+ return rOStm;
+}
+
+SvStream& GDIMetaFile::Read( SvStream& rIStm )
+{
+ Clear();
+ ReadGDIMetaFile( rIStm, *this );
+
+ return rIStm;
+}
+
+SvStream& GDIMetaFile::Write( SvStream& rOStm )
+{
+ VersionCompat* pCompat;
+ const SvStreamCompressFlags nStmCompressMode = rOStm.GetCompressMode();
+ SvStreamEndian nOldFormat = rOStm.GetEndian();
+
+ rOStm.SetEndian( SvStreamEndian::LITTLE );
+ rOStm.WriteBytes( "VCLMTF", 6 );
+
+ pCompat = new VersionCompat( rOStm, StreamMode::WRITE, 1 );
+
+ rOStm.WriteUInt32( static_cast<sal_uInt32>(nStmCompressMode) );
+ WriteMapMode( rOStm, m_aPrefMapMode );
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeSize(m_aPrefSize);
+ rOStm.WriteUInt32( GetActionSize() );
+
+ delete pCompat;
+
+ ImplMetaWriteData aWriteData;
+
+ aWriteData.meActualCharSet = rOStm.GetStreamCharSet();
+
+ MetaAction* pAct = FirstAction();
+ while ( pAct )
+ {
+ pAct->Write( rOStm, &aWriteData );
+ pAct = NextAction();
+ }
+
+ rOStm.SetEndian( nOldFormat );
+
+ return rOStm;
+}
+
+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;
+ 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( labs( aBRPix.X() - aTLPix.X() ) + 1, labs( aBRPix.Y() - aTLPix.Y() ) + 1 );
+ sal_uInt32 nMaximumExtent = 256;
+
+ 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< unsigned long >(aSizePix.Width()) >
+ nMaximumExtent ||
+ sal::static_int_cast< unsigned long >(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.get(), 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/genVerticalOrientationData.pl b/vcl/source/gdi/genVerticalOrientationData.pl
new file mode 100755
index 000000000..328727b26
--- /dev/null
+++ b/vcl/source/gdi/genVerticalOrientationData.pl
@@ -0,0 +1,206 @@
+#!/usr/bin/env perl
+
+# 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 tool is used to prepare lookup tables of Unicode character properties.
+# The properties are read from the Unicode Character Database and compiled into
+# multi-level arrays for efficient lookup.
+#
+# To regenerate the tables in VerticalOrientationData.cxx:
+#
+# (1) Download the current Unicode data files from
+#
+# We require the latest data file for UTR50, currently revision-17:
+# http://www.unicode.org/Public/vertical/revision-17/VerticalOrientation-17.txt
+#
+#
+# (2) Run this tool using a command line of the form
+#
+# perl genVerticalOrientationData.pl \
+# /path/to/VerticalOrientation-17.txt
+#
+# This will generate (or overwrite!) the files
+#
+# VerticalOrientationData.cxx
+#
+# in the current directory.
+
+use strict;
+use List::Util qw(first);
+
+my $DATA_FILE = $ARGV[0];
+
+my %verticalOrientationCode = (
+ 'U' => 0, # U - Upright, the same orientation as in the code charts
+ 'R' => 1, # R - Rotated 90 degrees clockwise compared to the code charts
+ 'Tu' => 2, # Tu - Transformed typographically, with fallback to Upright
+ 'Tr' => 3 # Tr - Transformed typographically, with fallback to Rotated
+);
+
+my @verticalOrientation;
+for (my $i = 0; $i < 0x110000; ++$i) {
+ $verticalOrientation[$i] = 1; # default for unlisted codepoints is 'R'
+}
+
+# read VerticalOrientation-17.txt
+my @versionInfo;
+open FH, "< $DATA_FILE" or die "can't open UTR50 data file VerticalOrientation-17.txt\n";
+push @versionInfo, "";
+while (<FH>) {
+ chomp;
+ push @versionInfo, $_;
+ last if /Date:/;
+}
+while (<FH>) {
+ chomp;
+ s/#.*//;
+ if (m/([0-9A-F]{4,6})(?:\.\.([0-9A-F]{4,6}))*\s*;\s*([^ ]+)/) {
+ my $vo = $3;
+ warn "unknown Vertical_Orientation code $vo"
+ unless exists $verticalOrientationCode{$vo};
+ $vo = $verticalOrientationCode{$vo};
+ my $start = hex "0x$1";
+ my $end = (defined $2) ? hex "0x$2" : $start;
+ for (my $i = $start; $i <= $end; ++$i) {
+ $verticalOrientation[$i] = $vo;
+ }
+ }
+}
+close FH;
+
+my $timestamp = gmtime();
+
+open DATA_TABLES, "> VerticalOrientationData.cxx" or die "unable to open VerticalOrientationData.cxx for output";
+
+my $licenseBlock = q[
+/*
+ * 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/.
+ */
+
+/*
+ * Derived from the Unicode Character Database by genVerticalOrientationData.pl
+ *
+ * For Unicode terms of use, see http://www.unicode.org/terms_of_use.html
+ */
+];
+
+my $versionInfo = join("\n", @versionInfo);
+
+print DATA_TABLES <<__END;
+$licenseBlock
+/*
+ * Created on $timestamp from UCD data files with version info:
+ *
+
+$versionInfo
+
+ *
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
+
+__END
+
+our $totalData = 0;
+
+sub sprintVerticalOrientation
+{
+ my $usv = shift;
+ return sprintf("%d,",
+ $verticalOrientation[$usv]);
+}
+
+&genTables("VerticalOrientation", "uint8_t", 9, 7, \&sprintVerticalOrientation, 16, 1, 1);
+
+sub genTables
+{
+ my ($prefix, $type, $indexBits, $charBits, $func, $maxPlane, $bytesPerEntry, $charsPerEntry) = @_;
+
+ print DATA_TABLES "#define k${prefix}MaxPlane $maxPlane\n";
+ print DATA_TABLES "#define k${prefix}IndexBits $indexBits\n";
+ print DATA_TABLES "#define k${prefix}CharBits $charBits\n";
+
+ my $indexLen = 1 << $indexBits;
+ my $charsPerPage = 1 << $charBits;
+ my %charIndex = ();
+ my %pageMapIndex = ();
+ my @pageMap = ();
+ my @char = ();
+
+ my $planeMap = "\x00" x $maxPlane;
+ foreach my $plane (0 .. $maxPlane) {
+ my $pageMap = "\x00" x $indexLen * 2;
+ foreach my $page (0 .. $indexLen - 1) {
+ my $charValues = "";
+ for (my $ch = 0; $ch < $charsPerPage; $ch += $charsPerEntry) {
+ my $usv = $plane * 0x10000 + $page * $charsPerPage + $ch;
+ $charValues .= &$func($usv);
+ }
+ chop $charValues;
+
+ unless (exists $charIndex{$charValues}) {
+ $charIndex{$charValues} = scalar keys %charIndex;
+ $char[$charIndex{$charValues}] = $charValues;
+ }
+ substr($pageMap, $page * 2, 2) = pack('S', $charIndex{$charValues});
+ }
+
+ unless (exists $pageMapIndex{$pageMap}) {
+ $pageMapIndex{$pageMap} = scalar keys %pageMapIndex;
+ $pageMap[$pageMapIndex{$pageMap}] = $pageMap;
+ }
+ if ($plane > 0) {
+ substr($planeMap, $plane - 1, 1) = pack('C', $pageMapIndex{$pageMap});
+ }
+ }
+
+ if ($maxPlane) {
+ print DATA_TABLES "static const uint8_t s${prefix}Planes[$maxPlane] = {";
+ print DATA_TABLES join(',', map { sprintf("%d", $_) } unpack('C*', $planeMap));
+ print DATA_TABLES "};\n\n";
+ }
+
+ my $chCount = scalar @char;
+ my $pmBits = $chCount > 255 ? 16 : 8;
+ my $pmCount = scalar @pageMap;
+ if ($maxPlane == 0) {
+ die "there should only be one pageMap entry!" if $pmCount > 1;
+ print DATA_TABLES "static const uint${pmBits}_t s${prefix}Pages[$indexLen] = {\n";
+ } else {
+ print DATA_TABLES "static const uint${pmBits}_t s${prefix}Pages[$pmCount][$indexLen] = {\n";
+ }
+ for (my $i = 0; $i < scalar @pageMap; ++$i) {
+ print DATA_TABLES $maxPlane > 0 ? " {" : " ";
+ print DATA_TABLES join(',', map { sprintf("%d", $_) } unpack('S*', $pageMap[$i]));
+ print DATA_TABLES $maxPlane > 0 ? ($i < $#pageMap ? "},\n" : "}\n") : "\n";
+ }
+ print DATA_TABLES "};\n\n";
+
+ my $pageLen = $charsPerPage / $charsPerEntry;
+ print DATA_TABLES "static const $type s${prefix}Values[$chCount][$pageLen] = {\n";
+ for (my $i = 0; $i < scalar @char; ++$i) {
+ print DATA_TABLES " {";
+ print DATA_TABLES $char[$i];
+ print DATA_TABLES $i < $#char ? "},\n" : "}\n";
+ }
+ print DATA_TABLES "};\n";
+
+ my $dataSize = $pmCount * $indexLen * $pmBits/8 +
+ $chCount * $pageLen * $bytesPerEntry +
+ $maxPlane;
+ $totalData += $dataSize;
+
+ print STDERR "Data for $prefix = $dataSize\n";
+}
+print DATA_TABLES <<__END;
+/*
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
+__END
+
+close DATA_TABLES;
diff --git a/vcl/source/gdi/gfxlink.cxx b/vcl/source/gdi/gfxlink.cxx
new file mode 100644
index 000000000..83936c277
--- /dev/null
+++ b/vcl/source/gdi/gfxlink.cxx
@@ -0,0 +1,189 @@
+/* -*- 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/graph.hxx>
+#include <vcl/gfxlink.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <memory>
+#include <boost/functional/hash.hpp>
+
+GfxLink::GfxLink()
+ : meType(GfxLinkType::NONE)
+ , mnUserId(0)
+ , maHash(0)
+ , mnSwapInDataSize(0)
+ , mbPrefMapModeValid(false)
+ , mbPrefSizeValid(false)
+{
+}
+
+
+
+GfxLink::GfxLink(std::unique_ptr<sal_uInt8[]> pBuf, sal_uInt32 nSize, GfxLinkType nType)
+ : meType(nType)
+ , mnUserId(0)
+ , mpSwapInData(std::shared_ptr<sal_uInt8>(pBuf.release(), pBuf.get_deleter())) // std::move(pBuf) does not compile on Jenkins MacOSX (24 May 2016)
+ , maHash(0)
+ , mnSwapInDataSize(nSize)
+ , mbPrefMapModeValid(false)
+ , mbPrefSizeValid(false)
+{
+ SAL_WARN_IF(mpSwapInData == nullptr || mnSwapInDataSize <= 0, "vcl",
+ "GfxLink::GfxLink(): empty/NULL buffer given");
+}
+
+size_t GfxLink::GetHash() const
+{
+ if (!maHash)
+ {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, mnSwapInDataSize);
+ boost::hash_combine(seed, meType);
+ const sal_uInt8* pData = GetData();
+ if (pData)
+ seed += boost::hash_range(pData, pData + GetDataSize());
+ maHash = seed;
+
+ }
+ return maHash;
+}
+
+bool GfxLink::operator==( const GfxLink& rGfxLink ) const
+{
+ if (GetHash() != rGfxLink.GetHash())
+ return false;
+
+ if ( mnSwapInDataSize != rGfxLink.mnSwapInDataSize ||
+ meType != rGfxLink.meType )
+ return false;
+
+ const sal_uInt8* pSource = GetData();
+ const sal_uInt8* pDest = rGfxLink.GetData();
+ if ( pSource == pDest )
+ return true;
+ sal_uInt32 nSourceSize = GetDataSize();
+ sal_uInt32 nDestSize = rGfxLink.GetDataSize();
+ if ( pSource && pDest && ( nSourceSize == nDestSize ) )
+ return (memcmp( pSource, pDest, nSourceSize ) == 0);
+ return false;
+}
+
+bool GfxLink::IsNative() const
+{
+ return meType >= GfxLinkType::NativeFirst && meType <= GfxLinkType::NativeLast;
+}
+
+
+const sal_uInt8* GfxLink::GetData() const
+{
+ return mpSwapInData.get();
+}
+
+
+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 )
+{
+ bool bRet = false;
+
+ if( IsNative() && mnSwapInDataSize )
+ {
+ const sal_uInt8* pData = GetData();
+ if (pData)
+ {
+ SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(pData), mnSwapInDataSize, 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;
+ default: break;
+ }
+ if (!aShortName.isEmpty())
+ {
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ sal_uInt16 nFormat = rFilter.GetImportFormatNumberForShortName(aShortName);
+ ErrCode nResult = rFilter.ImportGraphic(rGraphic, OUString(), aMemoryStream, nFormat);
+ if (nResult == ERRCODE_NONE)
+ bRet = true;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+bool GfxLink::ExportNative( SvStream& rOStream ) const
+{
+ if( GetDataSize() )
+ {
+ auto pData = GetSwapInData();
+ if (pData)
+ rOStream.WriteBytes( pData.get(), mnSwapInDataSize );
+ }
+
+ return ( rOStream.GetError() == ERRCODE_NONE );
+}
+
+std::shared_ptr<sal_uInt8> GfxLink::GetSwapInData() const
+{
+ return mpSwapInData;
+}
+
+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 000000000..2973f2d82
--- /dev/null
+++ b/vcl/source/gdi/gradient.cxx
@@ -0,0 +1,285 @@
+/* -*- 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>
+
+class Gradient::Impl
+{
+public:
+ GradientStyle meStyle;
+ Color maStartColor;
+ Color maEndColor;
+ sal_uInt16 mnAngle;
+ sal_uInt16 mnBorder;
+ sal_uInt16 mnOfsX;
+ sal_uInt16 mnOfsY;
+ sal_uInt16 mnIntensityStart;
+ sal_uInt16 mnIntensityEnd;
+ sal_uInt16 mnStepCount;
+
+ Impl()
+ : meStyle (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( GradientStyle eStyle,
+ const Color& rStartColor, const Color& rEndColor ) :
+ mpImplGradient()
+{
+ mpImplGradient->meStyle = eStyle;
+ mpImplGradient->maStartColor = rStartColor;
+ mpImplGradient->maEndColor = rEndColor;
+}
+
+Gradient::~Gradient() = default;
+
+
+GradientStyle Gradient::GetStyle() const
+{
+ return mpImplGradient->meStyle;
+}
+
+void Gradient::SetStyle( 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;
+}
+
+sal_uInt16 Gradient::GetAngle() const
+{
+ return mpImplGradient->mnAngle;
+}
+
+void Gradient::SetAngle( sal_uInt16 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 );
+ sal_uInt16 nAngle = GetAngle() % 3600;
+
+ if( GetStyle() == GradientStyle::Linear || GetStyle() == GradientStyle::Axial )
+ {
+ const double fAngle = nAngle * F_PI1800;
+ 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<long>(fDX) );
+ aRect.AdjustRight(static_cast<long>(fDX) );
+ aRect.AdjustTop( -static_cast<long>(fDY) );
+ aRect.AdjustBottom(static_cast<long>(fDY) );
+
+ rBoundRect = aRect;
+ rCenter = rRect.Center();
+ }
+ else
+ {
+ if( GetStyle() == GradientStyle::Square || GetStyle() == GradientStyle::Rect )
+ {
+ const double fAngle = nAngle * F_PI1800;
+ 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<long>(fDX) );
+ aRect.AdjustRight(static_cast<long>(fDX) );
+ aRect.AdjustTop( -static_cast<long>(fDY) );
+ aRect.AdjustBottom(static_cast<long>(fDY) );
+ }
+
+ Size aSize( aRect.GetSize() );
+
+ if( GetStyle() == GradientStyle::Radial )
+ {
+ // Calculation of radii for circle
+ aSize.setWidth( static_cast<long>(0.5 + sqrt(static_cast<double>(aSize.Width())*static_cast<double>(aSize.Width()) + static_cast<double>(aSize.Height())*static_cast<double>(aSize.Height()))) );
+ aSize.setHeight( aSize.Width() );
+ }
+ else if( GetStyle() == GradientStyle::Elliptical )
+ {
+ // Calculation of radii for ellipse
+ aSize.setWidth( static_cast<long>( 0.5 + static_cast<double>(aSize.Width()) * 1.4142 ) );
+ aSize.setHeight( static_cast<long>( 0.5 + static_cast<double>(aSize.Height()) * 1.4142 ) );
+ }
+
+ // Calculate new centers
+ long nZWidth = aRect.GetWidth() * static_cast<long>(GetOfsX()) / 100;
+ long nZHeight = aRect.GetHeight() * static_cast<long>(GetOfsY()) / 100;
+ long nBorderX = static_cast<long>(GetBorder()) * aSize.Width() / 100;
+ long nBorderY = static_cast<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;
+ }
+}
+
+Gradient& Gradient::operator=( const Gradient& ) = default;
+
+Gradient& Gradient::operator=( Gradient&& ) = default;
+
+bool Gradient::operator==( const Gradient& rGradient ) const
+{
+ return mpImplGradient == rGradient.mpImplGradient;
+}
+
+/* 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 000000000..4c635b454
--- /dev/null
+++ b/vcl/source/gdi/graph.cxx
@@ -0,0 +1,601 @@
+/* -*- 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 <cppuhelper/typeprovider.hxx>
+#include <graphic/UnoGraphic.hxx>
+#include <vcl/GraphicExternalLink.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+
+void ImplDrawDefault( OutputDevice* pOutDev, const OUString* pText,
+ vcl::Font* pFont, const BitmapEx* pBitmapEx,
+ const Point& rDestPt, const Size& rDestSize )
+{
+ sal_uInt16 nPixel = static_cast<sal_uInt16>(pOutDev->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 );
+
+ pOutDev->Push();
+
+ pOutDev->SetFillColor();
+
+ // On the printer a black rectangle and on the screen one with 3D effect
+ if ( pOutDev->GetOutDevType() == OUTDEV_PRINTER )
+ pOutDev->SetLineColor( COL_BLACK );
+ else
+ {
+ aBorderRect.AdjustLeft(nPixel );
+ aBorderRect.AdjustTop(nPixel );
+
+ pOutDev->SetLineColor( COL_LIGHTGRAY );
+ pOutDev->DrawRect( aBorderRect );
+
+ aBorderRect.AdjustLeft( -nPixel );
+ aBorderRect.AdjustTop( -nPixel );
+ aBorderRect.AdjustRight( -nPixel );
+ aBorderRect.AdjustBottom( -nPixel );
+ pOutDev->SetLineColor( COL_GRAY );
+ }
+
+ pOutDev->DrawRect( 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 )
+ {
+ Size aBitmapSize( pOutDev->PixelToLogic( pBitmapEx->GetSizePixel() ) );
+
+ if( aSize.Height() > aBitmapSize.Height() && aSize.Width() > aBitmapSize.Width() )
+ {
+ pOutDev->DrawBitmapEx( aPoint, *pBitmapEx );
+ aPoint.AdjustX(aBitmapSize.Width() + 2*nPixel );
+ aSize.AdjustWidth( -(aBitmapSize.Width() + 2*nPixel) );
+ }
+ }
+
+ if ( !aSize.IsEmpty() && pFont && pText && pText->getLength() && pOutDev->IsOutputEnabled() )
+ {
+ MapMode aMapMode( MapUnit::MapPoint );
+ Size aSz = pOutDev->LogicToLogic( Size( 0, 12 ), &aMapMode, nullptr );
+ long nThreshold = aSz.Height() / 2;
+ long nStep = nThreshold / 3;
+
+ if ( !nStep )
+ nStep = aSz.Height() - nThreshold;
+
+ for(;; aSz.AdjustHeight( -nStep ) )
+ {
+ pFont->SetFontSize( aSz );
+ pOutDev->SetFont( *pFont );
+
+ long nTextHeight = pOutDev->GetTextHeight();
+ long nTextWidth = pOutDev->GetTextWidth( *pText );
+ if ( nTextHeight )
+ {
+ // The approximation does not respect imprecisions caused
+ // by word wraps
+ long nLines = aSize.Height() / nTextHeight;
+ 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 = pOutDev->GetTextWidth( *pText, nStart, nNext );
+ if ( nTextWidth > aSize.Width() )
+ break;
+ nLen = nNext;
+ }
+ while ( nStart+nNext < pText->getLength() );
+
+ sal_Int32 n = nLen;
+ nTextWidth = pOutDev->GetTextWidth( *pText, nStart, n );
+ while( nTextWidth > aSize.Width() )
+ nTextWidth = pOutDev->GetTextWidth( *pText, nStart, --n );
+ pOutDev->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 );
+
+ pOutDev->SetLineColor( COL_LIGHTRED );
+ pOutDev->DrawLine( aBorderRect.TopLeft(), aBorderRect.BottomRight() );
+ pOutDev->DrawLine( aBorderRect.TopRight(), aBorderRect.BottomLeft() );
+ }
+
+ pOutDev->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(GraphicExternalLink const & rGraphicExternalLink)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rGraphicExternalLink))
+{
+}
+
+Graphic::Graphic(const Bitmap& rBmp)
+ : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rBmp))
+{
+}
+
+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 ::Graphic* pGraphic = comphelper::getUnoTunnelImplementation<::Graphic>(rxGraphic);
+
+ 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->ImplGetType();
+}
+
+void Graphic::Clear()
+{
+ ImplTestRefCount();
+ mxImpGraphic->ImplClear();
+}
+
+GraphicType Graphic::GetType() const
+{
+ return mxImpGraphic->ImplGetType();
+}
+
+void Graphic::SetDefaultType()
+{
+ ImplTestRefCount();
+ mxImpGraphic->ImplSetDefaultType();
+}
+
+bool Graphic::IsSupportedGraphic() const
+{
+ return mxImpGraphic->ImplIsSupportedGraphic();
+}
+
+bool Graphic::IsTransparent() const
+{
+ return mxImpGraphic->ImplIsTransparent();
+}
+
+bool Graphic::IsAlpha() const
+{
+ return mxImpGraphic->ImplIsAlpha();
+}
+
+bool Graphic::IsAnimated() const
+{
+ return mxImpGraphic->ImplIsAnimated();
+}
+
+bool Graphic::IsEPS() const
+{
+ return mxImpGraphic->ImplIsEPS();
+}
+
+BitmapEx Graphic::GetBitmapEx(const GraphicConversionParameters& rParameters) const
+{
+ return mxImpGraphic->ImplGetBitmapEx(rParameters);
+}
+
+Animation Graphic::GetAnimation() const
+{
+ return mxImpGraphic->ImplGetAnimation();
+}
+
+const GDIMetaFile& Graphic::GetGDIMetaFile() const
+{
+ return mxImpGraphic->ImplGetGDIMetaFile();
+}
+
+const BitmapEx& Graphic::GetBitmapExRef() const
+{
+ return mxImpGraphic->ImplGetBitmapExRef();
+}
+
+uno::Reference<graphic::XGraphic> Graphic::GetXGraphic() const
+{
+ uno::Reference<graphic::XGraphic> xGraphic;
+
+ if (GetType() != GraphicType::NONE)
+ {
+ unographic::Graphic* pUnoGraphic = new unographic::Graphic;
+ pUnoGraphic->init(*this);
+ xGraphic = pUnoGraphic;
+ }
+
+ return xGraphic;
+}
+
+Size Graphic::GetPrefSize() const
+{
+ return mxImpGraphic->ImplGetPrefSize();
+}
+
+void Graphic::SetPrefSize( const Size& rPrefSize )
+{
+ ImplTestRefCount();
+ mxImpGraphic->ImplSetPrefSize( rPrefSize );
+}
+
+MapMode Graphic::GetPrefMapMode() const
+{
+ return mxImpGraphic->ImplGetPrefMapMode();
+}
+
+void Graphic::SetPrefMapMode( const MapMode& rPrefMapMode )
+{
+ ImplTestRefCount();
+ mxImpGraphic->ImplSetPrefMapMode( 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 = 1000.0 * aGrfPixelSize.Width() / aGrf1000thInchSize.Width();
+ nGrfDPIy = 1000.0 * aGrfPixelSize.Height() / aGrf1000thInchSize.Height();
+ }
+
+ return basegfx::B2DSize(nGrfDPIx, nGrfDPIy);
+}
+
+Size Graphic::GetSizePixel( const OutputDevice* pRefDevice ) const
+{
+ Size aRet;
+
+ if( GraphicType::Bitmap == mxImpGraphic->ImplGetType() )
+ aRet = mxImpGraphic->ImplGetSizePixel();
+ else
+ aRet = ( pRefDevice ? pRefDevice : Application::GetDefaultDevice() )->LogicToPixel( GetPrefSize(), GetPrefMapMode() );
+
+ return aRet;
+}
+
+sal_uLong Graphic::GetSizeBytes() const
+{
+ return mxImpGraphic->ImplGetSizeBytes();
+}
+
+void Graphic::Draw( OutputDevice* pOutDev, const Point& rDestPt ) const
+{
+ mxImpGraphic->ImplDraw( pOutDev, rDestPt );
+}
+
+void Graphic::Draw( OutputDevice* pOutDev,
+ const Point& rDestPt, const Size& rDestSz ) const
+{
+ if( GraphicType::Default == mxImpGraphic->ImplGetType() )
+ ImplDrawDefault( pOutDev, nullptr, nullptr, nullptr, rDestPt, rDestSz );
+ else
+ mxImpGraphic->ImplDraw( pOutDev, rDestPt, rDestSz );
+}
+
+void Graphic::DrawEx( OutputDevice* pOutDev, const OUString& rText,
+ vcl::Font& rFont, const BitmapEx& rBitmap,
+ const Point& rDestPt, const Size& rDestSz )
+{
+ ImplDrawDefault( pOutDev, &rText, &rFont, &rBitmap, rDestPt, rDestSz );
+}
+
+void Graphic::StartAnimation( OutputDevice* pOutDev, const Point& rDestPt,
+ const Size& rDestSz, long nExtraData,
+ OutputDevice* pFirstFrameOutDev )
+{
+ ImplTestRefCount();
+ mxImpGraphic->ImplStartAnimation( pOutDev, rDestPt, rDestSz, nExtraData, pFirstFrameOutDev );
+}
+
+void Graphic::StopAnimation( OutputDevice* pOutDev, long nExtraData )
+{
+ ImplTestRefCount();
+ mxImpGraphic->ImplStopAnimation( pOutDev, nExtraData );
+}
+
+void Graphic::SetAnimationNotifyHdl( const Link<Animation*,void>& rLink )
+{
+ mxImpGraphic->ImplSetAnimationNotifyHdl( rLink );
+}
+
+Link<Animation*,void> Graphic::GetAnimationNotifyHdl() const
+{
+ return mxImpGraphic->ImplGetAnimationNotifyHdl();
+}
+
+sal_uInt32 Graphic::GetAnimationLoopCount() const
+{
+ return mxImpGraphic->ImplGetAnimationLoopCount();
+}
+
+std::shared_ptr<GraphicReader>& Graphic::GetReaderContext()
+{
+ return mxImpGraphic->ImplGetContext();
+}
+
+void Graphic::SetReaderContext( const std::shared_ptr<GraphicReader> &pReader )
+{
+ mxImpGraphic->ImplSetContext( pReader );
+}
+
+void Graphic::SetDummyContext( bool value )
+{
+ mxImpGraphic->ImplSetDummyContext( value );
+}
+
+bool Graphic::IsDummyContext() const
+{
+ return mxImpGraphic->ImplIsDummyContext();
+}
+
+void Graphic::SetGfxLink( const std::shared_ptr<GfxLink>& rGfxLink )
+{
+ ImplTestRefCount();
+ mxImpGraphic->ImplSetLink( rGfxLink );
+}
+
+std::shared_ptr<GfxLink> Graphic::GetSharedGfxLink() const
+{
+ return mxImpGraphic->ImplGetSharedGfxLink();
+}
+
+GfxLink Graphic::GetGfxLink() const
+{
+ return mxImpGraphic->ImplGetLink();
+}
+
+bool Graphic::IsGfxLink() const
+{
+ return mxImpGraphic->ImplIsLink();
+}
+
+BitmapChecksum Graphic::GetChecksum() const
+{
+ return mxImpGraphic->ImplGetChecksum();
+}
+
+bool Graphic::ExportNative( SvStream& rOStream ) const
+{
+ return mxImpGraphic->ImplExportNative( rOStream );
+}
+
+void ReadGraphic(SvStream& rIStream, Graphic& rGraphic)
+{
+ rGraphic.ImplTestRefCount();
+ ReadImpGraphic(rIStream, *rGraphic.mxImpGraphic);
+}
+
+void WriteGraphic( SvStream& rOStream, const Graphic& rGraphic )
+{
+ WriteImpGraphic(rOStream, *rGraphic.mxImpGraphic);
+}
+
+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;
+}
+
+namespace {
+
+struct Id: public rtl::Static<cppu::OImplementationId, Id> {};
+
+}
+
+css::uno::Sequence<sal_Int8> Graphic::getUnoTunnelId() {
+ return Id::get().getImplementationId();
+}
+
+/* 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 000000000..4be1b43fa
--- /dev/null
+++ b/vcl/source/gdi/graphictools.cxx
@@ -0,0 +1,296 @@
+/* -*- 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/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() :
+ maPath(),
+ maStartArrow(),
+ maEndArrow(),
+ mfTransparency(),
+ mfStrokeWidth(),
+ maCapType(),
+ maJoinType(),
+ mfMiterLimit( 3.0 ),
+ maDashArray()
+{
+}
+
+SvtGraphicStroke::SvtGraphicStroke( const tools::Polygon& rPath,
+ const tools::PolyPolygon& rStartArrow,
+ const tools::PolyPolygon& rEndArrow,
+ double fTransparency,
+ double fStrokeWidth,
+ CapType aCap,
+ JoinType aJoin,
+ double fMiterLimit,
+ const DashArray& rDashArray ) :
+ maPath( rPath ),
+ maStartArrow( rStartArrow ),
+ maEndArrow( rEndArrow ),
+ mfTransparency( fTransparency ),
+ mfStrokeWidth( fStrokeWidth ),
+ maCapType( aCap ),
+ maJoinType( aJoin ),
+ mfMiterLimit( fMiterLimit ),
+ maDashArray( 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 )
+{
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 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 )
+{
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+
+ 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() :
+ maPath(),
+ maFillColor( COL_BLACK ),
+ mfTransparency(),
+ maFillRule(),
+ maFillType(),
+ maFillTransform(),
+ mbTiling( false ),
+ maHatchType(),
+ maHatchColor( COL_BLACK ),
+ maGradientType(),
+ maGradient1stColor( COL_BLACK ),
+ maGradient2ndColor( COL_BLACK ),
+ maGradientStepCount( gradientStepsInfinite ),
+ maFillGraphic()
+{
+}
+
+SvtGraphicFill::SvtGraphicFill( const tools::PolyPolygon& rPath,
+ 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,
+ const Graphic& aFillGraphic ) :
+ maPath( rPath ),
+ 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( 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 )
+{
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 1 );
+
+ rClass.maPath.Write( rOStm );
+ tools::GenericTypeSerializer 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 );
+ WriteGraphic( rOStm, rClass.maFillGraphic );
+
+ return rOStm;
+}
+
+SvStream& ReadSvtGraphicFill( SvStream& rIStm, SvtGraphicFill& rClass )
+{
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+
+ rClass.maPath.Read( rIStm );
+
+ tools::GenericTypeSerializer 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 );
+ ReadGraphic( rIStm, 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 000000000..06b15f1bb
--- /dev/null
+++ b/vcl/source/gdi/hatch.cxx
@@ -0,0 +1,110 @@
+/* -*- 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,
+ long nDistance, sal_uInt16 nAngle10 ) : mpImplHatch()
+{
+ 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( long nDistance )
+{
+ mpImplHatch->mnDistance = nDistance;
+}
+
+void Hatch::SetAngle( sal_uInt16 nAngle10 )
+{
+ mpImplHatch->mnAngle = nAngle10;
+}
+
+SvStream& ReadHatch( SvStream& rIStm, Hatch& rHatch )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ sal_uInt16 nTmp16;
+ sal_Int32 nTmp32(0);
+
+ rIStm.ReadUInt16(nTmp16);
+ rHatch.mpImplHatch->meStyle = static_cast<HatchStyle>(nTmp16);
+
+ tools::GenericTypeSerializer aSerializer(rIStm);
+ aSerializer.readColor(rHatch.mpImplHatch->maColor);
+ rIStm.ReadInt32(nTmp32);
+ rIStm.ReadUInt16(rHatch.mpImplHatch->mnAngle);
+ rHatch.mpImplHatch->mnDistance = nTmp32;
+
+ return rIStm;
+}
+
+SvStream& WriteHatch( SvStream& rOStm, const Hatch& rHatch )
+{
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 1 );
+
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rHatch.mpImplHatch->meStyle) );
+
+ tools::GenericTypeSerializer aSerializer(rOStm);
+ aSerializer.writeColor(rHatch.mpImplHatch->maColor);
+ rOStm.WriteInt32( rHatch.mpImplHatch->mnDistance ).WriteUInt16( rHatch.mpImplHatch->mnAngle );
+
+ return rOStm;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/impanmvw.cxx b/vcl/source/gdi/impanmvw.cxx
new file mode 100644
index 000000000..abdc376b2
--- /dev/null
+++ b/vcl/source/gdi/impanmvw.cxx
@@ -0,0 +1,321 @@
+/* -*- 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 <impanmvw.hxx>
+
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+#include <tools/helpers.hxx>
+
+#include <window.h>
+
+ImplAnimView::ImplAnimView( Animation* pParent, OutputDevice* pOut,
+ const Point& rPt, const Size& rSz,
+ sal_uLong nExtraData,
+ OutputDevice* pFirstFrameOutDev ) :
+ mpParent ( pParent ),
+ mpRenderContext ( pFirstFrameOutDev ? pFirstFrameOutDev : pOut ),
+ mnExtraData ( nExtraData ),
+ maPt ( rPt ),
+ maSz ( rSz ),
+ maSzPix ( mpRenderContext->LogicToPixel( maSz ) ),
+ maClip ( mpRenderContext->GetClipRegion() ),
+ mpBackground ( VclPtr<VirtualDevice>::Create() ),
+ mpRestore ( VclPtr<VirtualDevice>::Create() ),
+ meLastDisposal ( Disposal::Back ),
+ mbIsPaused ( false ),
+ mbIsMarked ( false ),
+ mbIsMirroredHorizontally ( maSz.Width() < 0 ),
+ mbIsMirroredVertically ( maSz.Height() < 0 )
+{
+ Animation::ImplIncAnimCount();
+
+ // Mirrored horizontally?
+ if( mbIsMirroredHorizontally )
+ {
+ maDispPt.setX( maPt.X() + maSz.Width() + 1 );
+ maDispSz.setWidth( -maSz.Width() );
+ maSzPix.setWidth( -maSzPix.Width() );
+ }
+ else
+ {
+ maDispPt.setX( maPt.X() );
+ maDispSz.setWidth( maSz.Width() );
+ }
+
+ // Mirrored vertically?
+ if( mbIsMirroredVertically )
+ {
+ maDispPt.setY( maPt.Y() + maSz.Height() + 1 );
+ maDispSz.setHeight( -maSz.Height() );
+ maSzPix.setHeight( -maSzPix.Height() );
+ }
+ else
+ {
+ maDispPt.setY( maPt.Y() );
+ maDispSz.setHeight( maSz.Height() );
+ }
+
+ // save background
+ mpBackground->SetOutputSizePixel( maSzPix );
+ mpRenderContext->SaveBackground(*mpBackground, maDispPt, maDispSz, maSzPix);
+
+ // Initialize drawing to actual position
+ drawToPos( mpParent->ImplGetCurPos() );
+
+ // If first frame OutputDevice is set, update variables now for real OutputDevice
+ if( pFirstFrameOutDev )
+ {
+ mpRenderContext = pOut;
+ maClip = mpRenderContext->GetClipRegion();
+ }
+}
+
+ImplAnimView::~ImplAnimView()
+{
+ mpBackground.disposeAndClear();
+ mpRestore.disposeAndClear();
+
+ Animation::ImplDecAnimCount();
+}
+
+bool ImplAnimView::matches(const OutputDevice* pOut, long nExtraData) const
+{
+ return (!pOut || pOut == mpRenderContext) && (nExtraData == 0 || nExtraData == mnExtraData);
+}
+
+void ImplAnimView::getPosSize( const AnimationBitmap& rAnimationBitmap, Point& rPosPix, Size& rSizePix )
+{
+ const Size& rAnmSize = mpParent->GetDisplaySizePixel();
+ Point aPt2( rAnimationBitmap.maPositionPixel.X() + rAnimationBitmap.maSizePixel.Width() - 1,
+ rAnimationBitmap.maPositionPixel.Y() + rAnimationBitmap.maSizePixel.Height() - 1 );
+ double fFactX, fFactY;
+
+ // calculate x scaling
+ if( rAnmSize.Width() > 1 )
+ fFactX = static_cast<double>( maSzPix.Width() - 1 ) / ( rAnmSize.Width() - 1 );
+ else
+ fFactX = 1.0;
+
+ // calculate y scaling
+ if( rAnmSize.Height() > 1 )
+ fFactY = static_cast<double>( maSzPix.Height() - 1 ) / ( rAnmSize.Height() - 1 );
+ else
+ fFactY = 1.0;
+
+ rPosPix.setX( FRound( rAnimationBitmap.maPositionPixel.X() * fFactX ) );
+ rPosPix.setY( FRound( rAnimationBitmap.maPositionPixel.Y() * fFactY ) );
+
+ aPt2.setX( FRound( aPt2.X() * fFactX ) );
+ aPt2.setY( FRound( aPt2.Y() * fFactY ) );
+
+ rSizePix.setWidth( aPt2.X() - rPosPix.X() + 1 );
+ rSizePix.setHeight( aPt2.Y() - rPosPix.Y() + 1 );
+
+ // Mirrored horizontally?
+ if( mbIsMirroredHorizontally )
+ rPosPix.setX( maSzPix.Width() - 1 - aPt2.X() );
+
+ // Mirrored vertically?
+ if( mbIsMirroredVertically )
+ rPosPix.setY( maSzPix.Height() - 1 - aPt2.Y() );
+}
+
+void ImplAnimView::drawToPos( sal_uLong nPos )
+{
+ VclPtr<vcl::RenderContext> pRenderContext = mpRenderContext;
+
+ std::unique_ptr<vcl::PaintBufferGuard> pGuard;
+ if (mpRenderContext->GetOutDevType() == OUTDEV_WINDOW)
+ {
+ vcl::Window* pWindow = static_cast<vcl::Window*>(mpRenderContext.get());
+ pGuard.reset(new vcl::PaintBufferGuard(pWindow->ImplGetWindowImpl()->mpFrameData, pWindow));
+ pRenderContext = pGuard->GetRenderContext();
+ }
+
+ ScopedVclPtrInstance<VirtualDevice> aVDev;
+ std::unique_ptr<vcl::Region> xOldClip(!maClip.IsNull() ? new vcl::Region( pRenderContext->GetClipRegion() ) : nullptr);
+
+ aVDev->SetOutputSizePixel( maSzPix, false );
+ nPos = std::min( nPos, static_cast<sal_uLong>(mpParent->Count()) - 1 );
+
+ for( sal_uLong i = 0; i <= nPos; i++ )
+ draw( i, aVDev.get() );
+
+ if (xOldClip)
+ pRenderContext->SetClipRegion( maClip );
+
+ pRenderContext->DrawOutDev( maDispPt, maDispSz, Point(), maSzPix, *aVDev );
+ if (pGuard)
+ pGuard->SetPaintRect(tools::Rectangle(maDispPt, maDispSz));
+
+ if (xOldClip)
+ pRenderContext->SetClipRegion(*xOldClip);
+}
+
+void ImplAnimView::draw( sal_uLong nPos, VirtualDevice* pVDev )
+{
+ VclPtr<vcl::RenderContext> pRenderContext = mpRenderContext;
+
+ std::unique_ptr<vcl::PaintBufferGuard> pGuard;
+ if (!pVDev && mpRenderContext->GetOutDevType() == OUTDEV_WINDOW)
+ {
+ vcl::Window* pWindow = static_cast<vcl::Window*>(mpRenderContext.get());
+ pGuard.reset(new vcl::PaintBufferGuard(pWindow->ImplGetWindowImpl()->mpFrameData, pWindow));
+ pRenderContext = pGuard->GetRenderContext();
+ }
+
+ tools::Rectangle aOutRect( pRenderContext->PixelToLogic( Point() ), pRenderContext->GetOutputSize() );
+
+ // check, if output lies out of display
+ if( aOutRect.Intersection( tools::Rectangle( maDispPt, maDispSz ) ).IsEmpty() )
+ setMarked( true );
+ else if( !mbIsPaused )
+ {
+ VclPtr<VirtualDevice> pDev;
+ Point aPosPix;
+ Point aBmpPosPix;
+ Size aSizePix;
+ Size aBmpSizePix;
+ const sal_uLong nLastPos = mpParent->Count() - 1;
+ mnActPos = std::min( nPos, nLastPos );
+ const AnimationBitmap& rAnimationBitmap = mpParent->Get( static_cast<sal_uInt16>( mnActPos ) );
+
+ getPosSize( rAnimationBitmap, aPosPix, aSizePix );
+
+ // Mirrored horizontally?
+ if( mbIsMirroredHorizontally )
+ {
+ aBmpPosPix.setX( aPosPix.X() + aSizePix.Width() - 1 );
+ aBmpSizePix.setWidth( -aSizePix.Width() );
+ }
+ else
+ {
+ aBmpPosPix.setX( aPosPix.X() );
+ aBmpSizePix.setWidth( aSizePix.Width() );
+ }
+
+ // Mirrored vertically?
+ if( mbIsMirroredVertically )
+ {
+ aBmpPosPix.setY( aPosPix.Y() + aSizePix.Height() - 1 );
+ aBmpSizePix.setHeight( -aSizePix.Height() );
+ }
+ else
+ {
+ aBmpPosPix.setY( aPosPix.Y() );
+ aBmpSizePix.setHeight( aSizePix.Height() );
+ }
+
+ // get output device
+ if( !pVDev )
+ {
+ pDev = VclPtr<VirtualDevice>::Create();
+ pDev->SetOutputSizePixel( maSzPix, false );
+ pDev->DrawOutDev( Point(), maSzPix, maDispPt, maDispSz, *pRenderContext );
+ }
+ else
+ pDev = pVDev;
+
+ // restore background after each run
+ if( !nPos )
+ {
+ meLastDisposal = Disposal::Back;
+ maRestPt = Point();
+ maRestSz = maSzPix;
+ }
+
+ // restore
+ if( ( Disposal::Not != meLastDisposal ) && maRestSz.Width() && maRestSz.Height() )
+ {
+ if( Disposal::Back == meLastDisposal )
+ pDev->DrawOutDev( maRestPt, maRestSz, maRestPt, maRestSz, *mpBackground );
+ else
+ pDev->DrawOutDev( maRestPt, maRestSz, Point(), maRestSz, *mpRestore );
+ }
+
+ meLastDisposal = rAnimationBitmap.meDisposal;
+ maRestPt = aPosPix;
+ maRestSz = aSizePix;
+
+ // What do we need to restore the next time?
+ // Put it into a bitmap if needed, else delete
+ // SaveBitmap to conserve memory
+ if( ( meLastDisposal == Disposal::Back ) || ( meLastDisposal == Disposal::Not ) )
+ mpRestore->SetOutputSizePixel( Size( 1, 1 ), false );
+ else
+ {
+ mpRestore->SetOutputSizePixel( maRestSz, false );
+ mpRestore->DrawOutDev( Point(), maRestSz, aPosPix, aSizePix, *pDev );
+ }
+
+ pDev->DrawBitmapEx( aBmpPosPix, aBmpSizePix, rAnimationBitmap.maBitmapEx );
+
+ if( !pVDev )
+ {
+ std::unique_ptr<vcl::Region> xOldClip(!maClip.IsNull() ? new vcl::Region( pRenderContext->GetClipRegion() ) : nullptr);
+
+ if (xOldClip)
+ pRenderContext->SetClipRegion( maClip );
+
+ pRenderContext->DrawOutDev( maDispPt, maDispSz, Point(), maSzPix, *pDev );
+ if (pGuard)
+ pGuard->SetPaintRect(tools::Rectangle(maDispPt, maDispSz));
+
+ if( xOldClip)
+ {
+ pRenderContext->SetClipRegion(*xOldClip);
+ xOldClip.reset();
+ }
+
+ pDev.disposeAndClear();
+
+ if( pRenderContext->GetOutDevType() == OUTDEV_WINDOW )
+ static_cast<vcl::Window*>( pRenderContext.get() )->Flush();
+ }
+ }
+}
+
+void ImplAnimView::repaint()
+{
+ const bool bOldPause = mbIsPaused;
+
+ mpRenderContext->SaveBackground(*mpBackground, maDispPt, maDispSz, maSzPix);
+
+ mbIsPaused = false;
+ drawToPos( mnActPos );
+ mbIsPaused = bOldPause;
+}
+
+AInfo* ImplAnimView::createAInfo() const
+{
+ AInfo* pAInfo = new AInfo;
+
+ pAInfo->aStartOrg = maPt;
+ pAInfo->aStartSize = maSz;
+ pAInfo->pOutDev = mpRenderContext;
+ pAInfo->pViewData = const_cast<ImplAnimView *>(this);
+ pAInfo->nExtraData = mnExtraData;
+ pAInfo->bPause = mbIsPaused;
+
+ return pAInfo;
+}
+
+/* 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 000000000..4bb53d4a4
--- /dev/null
+++ b/vcl/source/gdi/impglyphitem.cxx
@@ -0,0 +1,78 @@
+/* -*- 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>
+
+#if (defined UNX && !defined MACOSX && !defined IOS)
+#include <unx/freetype_glyphcache.hxx>
+#endif
+
+SalLayoutGlyphs::SalLayoutGlyphs()
+ : m_pImpl(nullptr)
+{
+}
+
+SalLayoutGlyphs::~SalLayoutGlyphs() { delete m_pImpl; }
+
+SalLayoutGlyphs::SalLayoutGlyphs(const SalLayoutGlyphs& rOther)
+{
+ m_pImpl = rOther.m_pImpl ? rOther.m_pImpl->clone(*this) : nullptr;
+}
+
+SalLayoutGlyphs& SalLayoutGlyphs::operator=(const SalLayoutGlyphs& rOther)
+{
+ if (this != &rOther)
+ {
+ delete m_pImpl;
+ m_pImpl = rOther.m_pImpl ? rOther.m_pImpl->clone(*this) : nullptr;
+ }
+ return *this;
+}
+
+bool SalLayoutGlyphs::IsValid() const { return m_pImpl && m_pImpl->IsValid(); }
+
+void SalLayoutGlyphs::Invalidate()
+{
+ if (m_pImpl)
+ m_pImpl->Invalidate();
+}
+
+SalLayoutGlyphsImpl* SalLayoutGlyphsImpl::clone(SalLayoutGlyphs& rGlyphs) const
+{
+ SalLayoutGlyphsImpl* pNew = new SalLayoutGlyphsImpl(rGlyphs, *m_rFontInstance);
+ *pNew = *this;
+ return pNew;
+}
+
+bool SalLayoutGlyphsImpl::IsValid() const
+{
+ if (!m_rFontInstance.is())
+ return false;
+ if (empty())
+ return false;
+ return true;
+}
+
+void SalLayoutGlyphsImpl::Invalidate()
+{
+ m_rFontInstance.clear();
+ clear();
+}
+
+/* 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 000000000..e06663706
--- /dev/null
+++ b/vcl/source/gdi/impgraph.cxx
@@ -0,0 +1,1882 @@
+/* -*- 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/deleter.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 <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 <vcl/dibtools.hxx>
+#include <map>
+#include <memory>
+#include <vcl/gdimetafiletools.hxx>
+#include <TypeSerializer.hxx>
+#include <vcl/pdfread.hxx>
+
+#define GRAPHIC_MTFTOBMP_MAXEXT 2048
+#define GRAPHIC_STREAMBUFSIZE 8192UL
+
+#define SYS_WINMETAFILE 0x00000003L
+#define SYS_WNTMETAFILE 0x00000004L
+#define SYS_OS2METAFILE 0x00000005L
+#define SYS_MACMETAFILE 0x00000006L
+
+#define GRAPHIC_FORMAT_50 COMPAT_FORMAT( 'G', 'R', 'F', '5' )
+#define NATIVE_FORMAT_50 COMPAT_FORMAT( 'N', 'A', 'T', '5' )
+
+namespace {
+
+constexpr sal_uInt32 constSvgMagic((sal_uInt32('s') << 24) | (sal_uInt32('v') << 16) | (sal_uInt32('g') << 8) | sal_uInt32('0'));
+constexpr sal_uInt32 constWmfMagic((sal_uInt32('w') << 24) | (sal_uInt32('m') << 16) | (sal_uInt32('f') << 8) | sal_uInt32('0'));
+constexpr sal_uInt32 constEmfMagic((sal_uInt32('e') << 24) | (sal_uInt32('m') << 16) | (sal_uInt32('f') << 8) | sal_uInt32('0'));
+constexpr sal_uInt32 constPdfMagic((sal_uInt32('s') << 24) | (sal_uInt32('v') << 16) | (sal_uInt32('g') << 8) | sal_uInt32('0'));
+
+}
+
+using namespace com::sun::star;
+
+class ImpSwapFile
+{
+private:
+ INetURLObject maSwapURL;
+ OUString maOriginURL;
+
+public:
+ ImpSwapFile(INetURLObject const & rSwapURL, OUString const & rOriginURL)
+ : maSwapURL(rSwapURL)
+ , maOriginURL(rOriginURL)
+ {
+ }
+
+ ~ImpSwapFile() COVERITY_NOEXCEPT_FALSE
+ {
+ utl::UCBContentHelper::Kill(maSwapURL.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ }
+
+ INetURLObject getSwapURL()
+ {
+ return maSwapURL;
+ }
+
+ OUString getSwapURLString()
+ {
+ return maSwapURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+ }
+
+ OUString const & getOriginURL() { return maOriginURL; }
+
+ std::unique_ptr<SvStream> openOutputStream()
+ {
+ OUString sSwapURL = getSwapURLString();
+ if (!sSwapURL.isEmpty())
+ {
+ try
+ {
+ return utl::UcbStreamHelper::CreateStream(sSwapURL, StreamMode::READWRITE | StreamMode::SHARE_DENYWRITE);
+ }
+ catch (const css::uno::Exception&)
+ {
+ }
+ }
+ return std::unique_ptr<SvStream>();
+ }
+};
+
+OUString ImpGraphic::getSwapFileURL()
+{
+ if (mpSwapFile)
+ return mpSwapFile->getSwapURL().GetMainURL(INetURLObject::DecodeMechanism::NONE);
+ return OUString();
+}
+
+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.ImplClear();
+ rImpGraphic.mbDummyContext = false;
+}
+
+ImpGraphic::ImpGraphic(GraphicExternalLink const & rGraphicExternalLink) :
+ meType ( GraphicType::Default ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ maGraphicExternalLink(rGraphicExternalLink),
+ maLastUsed (std::chrono::high_resolution_clock::now()),
+ mbPrepared (false)
+{
+}
+
+ImpGraphic::ImpGraphic( const Bitmap& rBitmap ) :
+ maBitmapEx ( rBitmap ),
+ meType ( !rBitmap.IsEmpty() ? GraphicType::Bitmap : GraphicType::NONE ),
+ mnSizeBytes ( 0 ),
+ mbSwapOut ( false ),
+ mbDummyContext ( false ),
+ 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.ImplClear();
+ rImpGraphic.mbDummyContext = false;
+ maLastUsed = std::chrono::high_resolution_clock::now();
+
+ vcl::graphic::Manager::get().changeExisting(this, aOldSizeBytes);
+
+ return *this;
+}
+
+bool ImpGraphic::operator==( const ImpGraphic& rImpGraphic ) const
+{
+ bool bRet = false;
+
+ if( this == &rImpGraphic )
+ bRet = true;
+ else if (mbPrepared && rImpGraphic.mbPrepared)
+ {
+ bRet = (*mpGfxLink == *rImpGraphic.mpGfxLink);
+ }
+ else if (isAvailable() && rImpGraphic.isAvailable())
+ {
+ switch( meType )
+ {
+ case GraphicType::NONE:
+ bRet = true;
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ if( rImpGraphic.maMetaFile == maMetaFile )
+ bRet = true;
+ }
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if(maVectorGraphicData)
+ {
+ if(maVectorGraphicData == rImpGraphic.maVectorGraphicData)
+ {
+ // equal instances
+ bRet = true;
+ }
+ else if(rImpGraphic.maVectorGraphicData)
+ {
+ // equal content
+ bRet = (*maVectorGraphicData) == (*rImpGraphic.maVectorGraphicData);
+ }
+ }
+ else if( mpAnimation )
+ {
+ if( rImpGraphic.mpAnimation && ( *rImpGraphic.mpAnimation == *mpAnimation ) )
+ bRet = true;
+ }
+ else if( !rImpGraphic.mpAnimation && ( rImpGraphic.maBitmapEx == maBitmapEx ) )
+ {
+ bRet = true;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return bRet;
+}
+
+const std::shared_ptr<VectorGraphicData>& ImpGraphic::getVectorGraphicData() const
+{
+ ensureAvailable();
+
+ return maVectorGraphicData;
+}
+
+void ImpGraphic::createSwapInfo()
+{
+ if (isSwappedOut())
+ return;
+
+ maSwapInfo.maPrefMapMode = ImplGetPrefMapMode();
+ maSwapInfo.maPrefSize = ImplGetPrefSize();
+ maSwapInfo.mbIsAnimated = ImplIsAnimated();
+ maSwapInfo.mbIsEPS = ImplIsEPS();
+ maSwapInfo.mbIsTransparent = ImplIsTransparent();
+ maSwapInfo.mbIsAlpha = ImplIsAlpha();
+ maSwapInfo.mnAnimationLoopCount = ImplGetAnimationLoopCount();
+}
+
+void ImpGraphic::ImplClearGraphics()
+{
+ maBitmapEx.Clear();
+ maMetaFile.Clear();
+ mpAnimation.reset();
+ mpGfxLink.reset();
+ maVectorGraphicData.reset();
+}
+
+void ImpGraphic::ImplSetPrepared(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 (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;
+}
+
+void ImpGraphic::ImplClear()
+{
+ mpSwapFile.reset();
+ mbSwapOut = false;
+ mbPrepared = false;
+
+ // cleanup
+ ImplClearGraphics();
+ meType = GraphicType::NONE;
+ sal_Int64 nOldSize = mnSizeBytes;
+ mnSizeBytes = 0;
+ vcl::graphic::Manager::get().changeExisting(this, nOldSize);
+ maGraphicExternalLink.msURL.clear();
+}
+
+void ImpGraphic::ImplSetDefaultType()
+{
+ ImplClear();
+ meType = GraphicType::Default;
+}
+
+bool ImpGraphic::ImplIsSupportedGraphic() const
+{
+ return( meType != GraphicType::NONE );
+}
+
+bool ImpGraphic::ImplIsTransparent() const
+{
+ bool bRet(true);
+
+ if (mbSwapOut)
+ {
+ bRet = maSwapInfo.mbIsTransparent;
+ }
+ else if (meType == GraphicType::Bitmap && !maVectorGraphicData)
+ {
+ bRet = mpAnimation ? mpAnimation->IsTransparent() : maBitmapEx.IsTransparent();
+ }
+
+ return bRet;
+}
+
+bool ImpGraphic::ImplIsAlpha() 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::ImplIsAnimated() const
+{
+ return mbSwapOut ? maSwapInfo.mbIsAnimated : mpAnimation != nullptr;
+}
+
+bool ImpGraphic::ImplIsEPS() 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::ImplGetBitmap(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 ) && ImplIsSupportedGraphic() )
+ {
+ 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 == ImplGetType())
+ {
+ // get hairline and full bound rect
+ tools::Rectangle aHairlineRect;
+ const tools::Rectangle aRect(maMetaFile.GetBoundRect(*aVDev, &aHairlineRect));
+
+ if(!aRect.IsEmpty() && !aHairlineRect.IsEmpty())
+ {
+ // expand if needed to allow bottom and right hairlines to be added
+ if(aRect.Right() == aHairlineRect.Right())
+ {
+ aPixelSize.setWidth(aPixelSize.getWidth() + 1);
+ }
+
+ if(aRect.Bottom() == aHairlineRect.Bottom())
+ {
+ aPixelSize.setHeight(aPixelSize.getHeight() + 1);
+ }
+ }
+ }
+
+ if(aVDev->SetOutputSizePixel(aPixelSize))
+ {
+ if(rParameters.getAntiAliase())
+ {
+ aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::EnableB2dDraw);
+ }
+
+ if(rParameters.getSnapHorVerLines())
+ {
+ aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::PixelSnapHairline);
+ }
+
+ ImplDraw( aVDev.get(), 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 )
+ {
+ aRetBmp.SetPrefMapMode( ImplGetPrefMapMode() );
+ aRetBmp.SetPrefSize( ImplGetPrefSize() );
+ }
+
+ return aRetBmp;
+}
+
+BitmapEx ImpGraphic::ImplGetBitmapEx(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 ) && ImplIsSupportedGraphic() )
+ {
+ if(maBitmapEx.IsEmpty())
+ {
+ const ImpGraphic aMonoMask( maMetaFile.GetMonochromeMtf( COL_BLACK ) );
+
+ // use maBitmapEx as local buffer for rendered metafile
+ const_cast< ImpGraphic* >(this)->maBitmapEx = BitmapEx(ImplGetBitmap(rParameters), aMonoMask.ImplGetBitmap(rParameters));
+ }
+
+ aRetBmpEx = maBitmapEx;
+ }
+
+ return aRetBmpEx;
+}
+
+Animation ImpGraphic::ImplGetAnimation() const
+{
+ Animation aAnimation;
+
+ ensureAvailable();
+ if( mpAnimation )
+ aAnimation = *mpAnimation;
+
+ return aAnimation;
+}
+
+const BitmapEx& ImpGraphic::ImplGetBitmapExRef() const
+{
+ ensureAvailable();
+ return maBitmapEx;
+}
+
+const GDIMetaFile& ImpGraphic::ImplGetGDIMetaFile() const
+{
+ ensureAvailable();
+ if (!maMetaFile.GetActionSize()
+ && maVectorGraphicData
+ && (VectorGraphicDataType::Emf == maVectorGraphicData->getVectorGraphicDataType()
+ || VectorGraphicDataType::Wmf == maVectorGraphicData->getVectorGraphicDataType()))
+ {
+ // 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]);
+ const MetafileAccessor* pMetafileAccessor = dynamic_cast< const MetafileAccessor* >(xReference.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)
+ {
+ // 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.IsTransparent())
+ {
+ 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::ImplGetSizePixel() const
+{
+ Size aSize;
+
+ if (isSwappedOut())
+ aSize = maSwapInfo.maSizePixel;
+ else
+ aSize = ImplGetBitmapEx(GraphicConversionParameters()).GetSizePixel();
+
+ return aSize;
+}
+
+Size ImpGraphic::ImplGetPrefSize() const
+{
+ Size aSize;
+
+ if (isSwappedOut())
+ {
+ aSize = maSwapInfo.maPrefSize;
+ }
+ else
+ {
+ switch( meType )
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+
+ 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();
+
+ 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;
+
+ default:
+ {
+ if( ImplIsSupportedGraphic() )
+ aSize = maMetaFile.GetPrefSize();
+ }
+ break;
+ }
+ }
+
+ return aSize;
+}
+
+void ImpGraphic::ImplSetPrefSize( const Size& rPrefSize )
+{
+ ensureAvailable();
+
+ switch( meType )
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+
+ 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( ImplIsAnimated() )
+ {
+ const_cast< BitmapEx& >(mpAnimation->GetBitmapEx()).SetPrefSize( rPrefSize );
+ }
+
+ if (!maExPrefSize.getWidth() || !maExPrefSize.getHeight())
+ {
+ maBitmapEx.SetPrefSize( rPrefSize );
+ }
+ }
+ break;
+
+ default:
+ {
+ if( ImplIsSupportedGraphic() )
+ maMetaFile.SetPrefSize( rPrefSize );
+ }
+ break;
+ }
+}
+
+MapMode ImpGraphic::ImplGetPrefMapMode() const
+{
+ MapMode aMapMode;
+
+ if (isSwappedOut())
+ {
+ aMapMode = maSwapInfo.maPrefMapMode;
+ }
+ else
+ {
+ switch( meType )
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+
+ 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;
+
+ default:
+ {
+ if( ImplIsSupportedGraphic() )
+ return maMetaFile.GetPrefMapMode();
+ }
+ break;
+ }
+ }
+
+ return aMapMode;
+}
+
+void ImpGraphic::ImplSetPrefMapMode( const MapMode& rPrefMapMode )
+{
+ ensureAvailable();
+
+ switch( meType )
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+
+ 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( ImplIsAnimated() )
+ {
+ const_cast< BitmapEx& >(mpAnimation->GetBitmapEx()).SetPrefMapMode( rPrefMapMode );
+ }
+
+ maBitmapEx.SetPrefMapMode( rPrefMapMode );
+ }
+ }
+ break;
+
+ default:
+ {
+ if( ImplIsSupportedGraphic() )
+ maMetaFile.SetPrefMapMode( rPrefMapMode );
+ }
+ break;
+ }
+}
+
+sal_uLong ImpGraphic::ImplGetSizeBytes() const
+{
+ if( mnSizeBytes )
+ return mnSizeBytes;
+
+ if (mbPrepared)
+ ensureAvailable();
+
+ if( meType == GraphicType::Bitmap )
+ {
+ if(maVectorGraphicData)
+ {
+ std::pair<VectorGraphicData::State, size_t> tmp(maVectorGraphicData->getSizeBytes());
+ if (VectorGraphicData::State::UNPARSED == tmp.first)
+ {
+ return tmp.second; // don't cache it until Vector Graphic Data is parsed
+ }
+ mnSizeBytes = tmp.second;
+ }
+ else
+ {
+ mnSizeBytes = mpAnimation ? mpAnimation->GetSizeBytes() : maBitmapEx.GetSizeBytes();
+ }
+ }
+ else if( meType == GraphicType::GdiMetafile )
+ {
+ mnSizeBytes = maMetaFile.GetSizeBytes();
+ }
+
+ return mnSizeBytes;
+}
+
+void ImpGraphic::ImplDraw( OutputDevice* pOutDev, const Point& rDestPt ) const
+{
+ ensureAvailable();
+ if( !ImplIsSupportedGraphic() || isSwappedOut() )
+ return;
+
+ switch( meType )
+ {
+ case GraphicType::Default:
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if(maVectorGraphicData && !maBitmapEx)
+ {
+ // use maEx as local buffer for rendered svg
+ const_cast< ImpGraphic* >(this)->maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ if ( mpAnimation )
+ {
+ mpAnimation->Draw( pOutDev, rDestPt );
+ }
+ else
+ {
+ maBitmapEx.Draw( pOutDev, rDestPt );
+ }
+ }
+ break;
+
+ default:
+ ImplDraw( pOutDev, rDestPt, maMetaFile.GetPrefSize() );
+ break;
+ }
+}
+
+void ImpGraphic::ImplDraw( OutputDevice* pOutDev,
+ const Point& rDestPt, const Size& rDestSize ) const
+{
+ ensureAvailable();
+ if( !ImplIsSupportedGraphic() || isSwappedOut() )
+ return;
+
+ switch( meType )
+ {
+ case GraphicType::Default:
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if(maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // use maEx as local buffer for rendered svg
+ const_cast< ImpGraphic* >(this)->maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ if( mpAnimation )
+ {
+ mpAnimation->Draw( pOutDev, rDestPt, rDestSize );
+ }
+ else
+ {
+ maBitmapEx.Draw( pOutDev, rDestPt, rDestSize );
+ }
+ }
+ break;
+
+ default:
+ {
+ const_cast<ImpGraphic*>(this)->maMetaFile.WindStart();
+ const_cast<ImpGraphic*>(this)->maMetaFile.Play( pOutDev, rDestPt, rDestSize );
+ const_cast<ImpGraphic*>(this)->maMetaFile.WindStart();
+ }
+ break;
+ }
+}
+
+void ImpGraphic::ImplStartAnimation( OutputDevice* pOutDev, const Point& rDestPt,
+ const Size& rDestSize, long nExtraData,
+ OutputDevice* pFirstFrameOutDev )
+{
+ ensureAvailable();
+
+ if( ImplIsSupportedGraphic() && !isSwappedOut() && mpAnimation )
+ mpAnimation->Start( pOutDev, rDestPt, rDestSize, nExtraData, pFirstFrameOutDev );
+}
+
+void ImpGraphic::ImplStopAnimation( OutputDevice* pOutDev, long nExtraData )
+{
+ ensureAvailable();
+
+ if( ImplIsSupportedGraphic() && !isSwappedOut() && mpAnimation )
+ mpAnimation->Stop( pOutDev, nExtraData );
+}
+
+void ImpGraphic::ImplSetAnimationNotifyHdl( const Link<Animation*,void>& rLink )
+{
+ ensureAvailable();
+
+ if( mpAnimation )
+ mpAnimation->SetNotifyHdl( rLink );
+}
+
+Link<Animation*,void> ImpGraphic::ImplGetAnimationNotifyHdl() const
+{
+ Link<Animation*,void> aLink;
+
+ ensureAvailable();
+
+ if( mpAnimation )
+ aLink = mpAnimation->GetNotifyHdl();
+
+ return aLink;
+}
+
+sal_uInt32 ImpGraphic::ImplGetAnimationLoopCount() const
+{
+ if (mbSwapOut)
+ return maSwapInfo.mnAnimationLoopCount;
+
+ return mpAnimation ? mpAnimation->GetLoopCount() : 0;
+}
+
+void ImpGraphic::ImplSetContext( const std::shared_ptr<GraphicReader>& pReader )
+{
+ mpContext = pReader;
+ mbDummyContext = false;
+}
+
+bool ImpGraphic::ImplReadEmbedded( SvStream& rIStm )
+{
+ ensureAvailable();
+
+ MapMode aMapMode;
+ Size aSize;
+ sal_uInt32 nId;
+ sal_Int32 nType;
+ const SvStreamEndian nOldFormat = rIStm.GetEndian();
+ bool bRet = false;
+
+ rIStm.SetEndian( SvStreamEndian::LITTLE );
+ rIStm.ReadUInt32( nId );
+
+ // check version
+ if( GRAPHIC_FORMAT_50 == nId )
+ {
+ // read new style header
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+
+ rIStm.ReadInt32( nType );
+ sal_Int32 nLen;
+ rIStm.ReadInt32( nLen );
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readSize(aSize);
+ ReadMapMode( rIStm, aMapMode );
+ }
+ else
+ {
+ // read old style header
+ sal_Int32 nWidth, nHeight;
+ sal_Int32 nMapMode, nScaleNumX, nScaleDenomX;
+ sal_Int32 nScaleNumY, nScaleDenomY, nOffsX, nOffsY;
+
+ rIStm.SeekRel( -4 );
+
+ sal_Int32 nLen;
+ rIStm.ReadInt32( nType ).ReadInt32( nLen ).ReadInt32( nWidth ).ReadInt32( nHeight );
+ rIStm.ReadInt32( nMapMode ).ReadInt32( nScaleNumX ).ReadInt32( nScaleDenomX ).ReadInt32( nScaleNumY );
+ rIStm.ReadInt32( nScaleDenomY ).ReadInt32( nOffsX ).ReadInt32( nOffsY );
+
+ // swapped
+ if( nType > 100 )
+ {
+ nType = OSL_SWAPDWORD( nType );
+ nWidth = OSL_SWAPDWORD( nWidth );
+ nHeight = OSL_SWAPDWORD( nHeight );
+ nMapMode = OSL_SWAPDWORD( nMapMode );
+ nScaleNumX = OSL_SWAPDWORD( nScaleNumX );
+ nScaleDenomX = OSL_SWAPDWORD( nScaleDenomX );
+ nScaleNumY = OSL_SWAPDWORD( nScaleNumY );
+ nScaleDenomY = OSL_SWAPDWORD( nScaleDenomY );
+ nOffsX = OSL_SWAPDWORD( nOffsX );
+ nOffsY = OSL_SWAPDWORD( nOffsY );
+ }
+
+ aSize = Size( nWidth, nHeight );
+ aMapMode = MapMode( static_cast<MapUnit>(nMapMode), Point( nOffsX, nOffsY ),
+ Fraction( nScaleNumX, nScaleDenomX ),
+ Fraction( nScaleNumY, nScaleDenomY ) );
+ }
+
+ meType = static_cast<GraphicType>(nType);
+
+ if( meType != GraphicType::NONE )
+ {
+ if( meType == GraphicType::Bitmap )
+ {
+ if(maVectorGraphicData && maBitmapEx.IsEmpty())
+ {
+ // use maBitmapEx as local buffer for rendered svg
+ maBitmapEx = getVectorGraphicReplacement();
+ }
+
+ maBitmapEx.SetSizePixel(aSize);
+
+ if( aMapMode != MapMode() )
+ {
+ maBitmapEx.SetPrefMapMode( aMapMode );
+ maBitmapEx.SetPrefSize( aSize );
+ }
+ }
+ else
+ {
+ maMetaFile.SetPrefMapMode( aMapMode );
+ maMetaFile.SetPrefSize( aSize );
+ }
+
+ if( meType == GraphicType::Bitmap || meType == GraphicType::GdiMetafile )
+ {
+ ReadImpGraphic( rIStm, *this );
+ bRet = rIStm.GetError() == ERRCODE_NONE;
+ }
+ else if( sal::static_int_cast<sal_uLong>(meType) >= SYS_WINMETAFILE
+ && sal::static_int_cast<sal_uLong>(meType) <= SYS_MACMETAFILE )
+ {
+ Graphic aSysGraphic;
+ ConvertDataFormat nCvtType;
+
+ switch( sal::static_int_cast<sal_uLong>(meType) )
+ {
+ case SYS_WINMETAFILE:
+ case SYS_WNTMETAFILE: nCvtType = ConvertDataFormat::WMF; break;
+ case SYS_OS2METAFILE: nCvtType = ConvertDataFormat::MET; break;
+ case SYS_MACMETAFILE: nCvtType = ConvertDataFormat::PCT; break;
+
+ default:
+ nCvtType = ConvertDataFormat::Unknown;
+ break;
+ }
+
+ if( nType && GraphicConverter::Import( rIStm, aSysGraphic, nCvtType ) == ERRCODE_NONE )
+ {
+ *this = ImpGraphic( aSysGraphic.GetGDIMetaFile() );
+ bRet = rIStm.GetError() == ERRCODE_NONE;
+ }
+ else
+ meType = GraphicType::Default;
+ }
+
+ if( bRet )
+ {
+ ImplSetPrefMapMode( aMapMode );
+ ImplSetPrefSize( aSize );
+ }
+ }
+ else
+ bRet = true;
+
+ rIStm.SetEndian( nOldFormat );
+
+ return bRet;
+}
+
+bool ImpGraphic::ImplWriteEmbedded( SvStream& rOStm )
+{
+ ensureAvailable();
+
+ if( ( meType == GraphicType::NONE ) || ( meType == GraphicType::Default ) || isSwappedOut() )
+ return false;
+
+ const MapMode aMapMode( ImplGetPrefMapMode() );
+ const Size aSize( ImplGetPrefSize() );
+ const SvStreamEndian nOldFormat = rOStm.GetEndian();
+ sal_uLong nDataFieldPos;
+
+ rOStm.SetEndian( SvStreamEndian::LITTLE );
+
+ // write correct version ( old style/new style header )
+ if( rOStm.GetVersion() >= SOFFICE_FILEFORMAT_50 )
+ {
+ // write ID for new format (5.0)
+ rOStm.WriteUInt32( GRAPHIC_FORMAT_50 );
+
+ // write new style header
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 1 );
+
+ rOStm.WriteInt32( static_cast<sal_Int32>(meType) );
+
+ // data size is updated later
+ nDataFieldPos = rOStm.Tell();
+ rOStm.WriteInt32( 0 );
+
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeSize(aSize);
+
+ WriteMapMode( rOStm, aMapMode );
+ }
+ else
+ {
+ // write old style (<=4.0) header
+ rOStm.WriteInt32( static_cast<sal_Int32>(meType) );
+
+ // data size is updated later
+ nDataFieldPos = rOStm.Tell();
+ rOStm.WriteInt32( 0 );
+ rOStm.WriteInt32( aSize.Width() );
+ rOStm.WriteInt32( aSize.Height() );
+ rOStm.WriteInt32( static_cast<sal_uInt16>(aMapMode.GetMapUnit()) );
+ rOStm.WriteInt32( aMapMode.GetScaleX().GetNumerator() );
+ rOStm.WriteInt32( aMapMode.GetScaleX().GetDenominator() );
+ rOStm.WriteInt32( aMapMode.GetScaleY().GetNumerator() );
+ rOStm.WriteInt32( aMapMode.GetScaleY().GetDenominator() );
+ rOStm.WriteInt32( aMapMode.GetOrigin().X() );
+ rOStm.WriteInt32( aMapMode.GetOrigin().Y() );
+ }
+
+ bool bRet = false;
+ // write data block
+ if( !rOStm.GetError() )
+ {
+ const sal_uLong nDataStart = rOStm.Tell();
+
+ if( ImplIsSupportedGraphic() )
+ WriteImpGraphic( rOStm, *this );
+
+ if( !rOStm.GetError() )
+ {
+ const sal_uLong nStmPos2 = rOStm.Tell();
+ rOStm.Seek( nDataFieldPos );
+ rOStm.WriteInt32( nStmPos2 - nDataStart );
+ rOStm.Seek( nStmPos2 );
+ bRet = true;
+ }
+ }
+
+ rOStm.SetEndian( nOldFormat );
+
+ return bRet;
+}
+
+bool ImpGraphic::swapOut()
+{
+ if (isSwappedOut())
+ return false;
+
+ // Create a temp filename for the swap file
+ utl::TempFile aTempFile;
+ const INetURLObject aTempFileURL(aTempFile.GetURL());
+
+ // Create a swap file
+ std::shared_ptr<ImpSwapFile> pSwapFile(new ImpSwapFile(aTempFileURL, getOriginURL()), o3tl::default_delete<ImpSwapFile>());
+
+ bool bResult = false;
+
+ // Open a stream to write the swap file to
+ {
+ std::unique_ptr<SvStream> xOutputStream = pSwapFile->openOutputStream();
+
+ if (!xOutputStream)
+ return false;
+
+ // Write to stream
+ xOutputStream->SetVersion(SOFFICE_FILEFORMAT_50);
+ xOutputStream->SetCompressMode(SvStreamCompressFlags::NATIVE);
+ xOutputStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE);
+
+ if (!xOutputStream->GetError() && ImplWriteEmbedded(*xOutputStream))
+ {
+ xOutputStream->Flush();
+ bResult = !xOutputStream->GetError();
+ }
+ }
+
+ // Check if writing was successful
+ if (bResult)
+ {
+ // We have swapped out, so can clean memory and prepare swap info
+ createSwapInfo();
+ ImplClearGraphics();
+
+ mpSwapFile = std::move(pSwapFile);
+ mbSwapOut = true;
+
+ // Signal to manager that we have swapped out
+ vcl::graphic::Manager::get().swappedOut(this);
+ }
+
+ return bResult;
+}
+
+bool ImpGraphic::ensureAvailable() const
+{
+ auto pThis = const_cast<ImpGraphic*>(this);
+
+ if (isSwappedOut())
+ return pThis->swapIn();
+
+ pThis->maLastUsed = std::chrono::high_resolution_clock::now();
+ return true;
+}
+
+bool ImpGraphic::loadPrepared()
+{
+ Graphic aGraphic;
+ if (!mpGfxLink->LoadNative(aGraphic))
+ return false;
+
+ GraphicExternalLink aLink = maGraphicExternalLink;
+
+ Size aPrefSize = maSwapInfo.maPrefSize;
+ MapMode aPrefMapMode = maSwapInfo.maPrefMapMode;
+ *this = *aGraphic.ImplGetImpGraphic();
+ if (aPrefSize.getWidth() && aPrefSize.getHeight() && aPrefMapMode == ImplGetPrefMapMode())
+ {
+ // 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.
+ ImplSetPrefSize(aPrefSize);
+ }
+
+ maGraphicExternalLink = aLink;
+
+ return true;
+}
+
+bool ImpGraphic::swapIn()
+{
+ bool bRet = false;
+
+ if (!isSwappedOut())
+ return bRet;
+
+ if (mbPrepared)
+ {
+ bRet = loadPrepared();
+ }
+ else
+ {
+ OUString aSwapURL;
+
+ if( mpSwapFile )
+ aSwapURL = mpSwapFile->getSwapURL().GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ if( !aSwapURL.isEmpty() )
+ {
+ std::unique_ptr<SvStream> xIStm;
+ try
+ {
+ xIStm = ::utl::UcbStreamHelper::CreateStream( aSwapURL, StreamMode::READWRITE | StreamMode::SHARE_DENYWRITE );
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ if( xIStm )
+ {
+ xIStm->SetVersion( SOFFICE_FILEFORMAT_50 );
+ xIStm->SetCompressMode( SvStreamCompressFlags::NATIVE );
+
+ bRet = swapInFromStream(xIStm.get());
+ xIStm.reset();
+ if (mpSwapFile)
+ setOriginURL(mpSwapFile->getOriginURL());
+ mpSwapFile.reset();
+ }
+ }
+ }
+
+ if (bRet)
+ vcl::graphic::Manager::get().swappedIn(this);
+
+ return bRet;
+}
+
+bool ImpGraphic::swapInFromStream(SvStream* xIStm)
+{
+ bool bRet = false;
+
+ if( !xIStm )
+ return false;
+
+ xIStm->SetBufferSize( GRAPHIC_STREAMBUFSIZE );
+
+ if( xIStm->GetError() )
+ return false;
+
+ //keep the swap file alive, because its quite possibly the backing storage
+ //for xIStm
+ std::shared_ptr<ImpSwapFile> xSwapFile(std::move(mpSwapFile));
+ assert(!mpSwapFile);
+
+ std::shared_ptr<GraphicReader> xContext(std::move(mpContext));
+ assert(!mpContext);
+
+ bool bDummyContext = mbDummyContext;
+ mbDummyContext = false;
+
+ bRet = ImplReadEmbedded( *xIStm );
+
+ //restore ownership of the swap file and context
+ mpSwapFile = std::move(xSwapFile);
+ mpContext = std::move(xContext);
+ mbDummyContext = bDummyContext;
+
+ if (!bRet)
+ {
+ //throw away swapfile, etc.
+ ImplClear();
+ }
+
+ mbSwapOut = false;
+
+ return bRet;
+}
+
+void ImpGraphic::ImplSetLink(const std::shared_ptr<GfxLink>& rGfxLink)
+{
+ ensureAvailable();
+
+ mpGfxLink = rGfxLink;
+}
+
+std::shared_ptr<GfxLink> ImpGraphic::ImplGetSharedGfxLink() const
+{
+ return mpGfxLink;
+}
+
+GfxLink ImpGraphic::ImplGetLink()
+{
+ ensureAvailable();
+
+ return( mpGfxLink ? *mpGfxLink : GfxLink() );
+}
+
+bool ImpGraphic::ImplIsLink() const
+{
+ return ( bool(mpGfxLink) );
+}
+
+BitmapChecksum ImpGraphic::ImplGetChecksum() const
+{
+ if (mnChecksum != 0)
+ return mnChecksum;
+
+ BitmapChecksum nRet = 0;
+
+ ensureAvailable();
+
+ if( ImplIsSupportedGraphic() && !isSwappedOut() )
+ {
+ switch( meType )
+ {
+ case GraphicType::Default:
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if(maVectorGraphicData)
+ nRet = maVectorGraphicData->GetChecksum();
+ else if( mpAnimation )
+ nRet = mpAnimation->GetChecksum();
+ else
+ nRet = maBitmapEx.GetChecksum();
+ }
+ break;
+
+ default:
+ nRet = maMetaFile.GetChecksum();
+ break;
+ }
+ }
+
+ mnChecksum = nRet;
+ return nRet;
+}
+
+bool ImpGraphic::ImplExportNative( SvStream& rOStm ) const
+{
+ ensureAvailable();
+
+ if( rOStm.GetError() )
+ return false;
+
+ bool bResult = false;
+
+ if( !isSwappedOut() )
+ {
+ if( mpGfxLink && mpGfxLink->IsNative() )
+ bResult = mpGfxLink->ExportNative( rOStm );
+ else
+ {
+ WriteImpGraphic( rOStm, *this );
+ bResult = ( rOStm.GetError() == ERRCODE_NONE );
+ }
+ }
+ else
+ rOStm.SetError( SVSTREAM_GENERALERROR );
+
+ return bResult;
+}
+
+sal_Int32 ImpGraphic::getPageNumber() const
+{
+ if (maVectorGraphicData)
+ return maVectorGraphicData->getPageIndex();
+ return -1;
+}
+
+void ReadImpGraphic( SvStream& rIStm, ImpGraphic& rImpGraphic )
+{
+ if (rIStm.GetError())
+ return;
+
+ const sal_uLong nStmPos1 = rIStm.Tell();
+ sal_uInt32 nTmp;
+
+ rImpGraphic.ImplClear();
+
+ // read Id
+ rIStm.ReadUInt32( nTmp );
+
+ // if there is no more data, avoid further expensive
+ // reading which will create VDevs and other stuff, just to
+ // read nothing. CAUTION: Eof is only true AFTER reading another
+ // byte, a speciality of SvMemoryStream (!)
+ if (!rIStm.good())
+ return;
+
+ if (NATIVE_FORMAT_50 == nTmp)
+ {
+ Graphic aGraphic;
+ GfxLink aLink;
+
+ // read compat info, destructor writes stuff into the header
+ {
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+ }
+
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readGfxLink(aLink);
+
+ // set dummy link to avoid creation of additional link after filtering;
+ // we set a default link to avoid unnecessary swapping of native data
+ aGraphic.SetGfxLink(std::make_shared<GfxLink>());
+
+ if( !rIStm.GetError() && aLink.LoadNative( aGraphic ) )
+ {
+ // set link only, if no other link was set
+ const bool bSetLink = !rImpGraphic.mpGfxLink;
+
+ // assign graphic
+ rImpGraphic = *aGraphic.ImplGetImpGraphic();
+
+ if( aLink.IsPrefMapModeValid() )
+ rImpGraphic.ImplSetPrefMapMode( aLink.GetPrefMapMode() );
+
+ if( aLink.IsPrefSizeValid() )
+ rImpGraphic.ImplSetPrefSize( aLink.GetPrefSize() );
+
+ if( bSetLink )
+ rImpGraphic.ImplSetLink(std::make_shared<GfxLink>(aLink));
+ }
+ else
+ {
+ rIStm.Seek( nStmPos1 );
+ rIStm.SetError( ERRCODE_IO_WRONGFORMAT );
+ }
+ return;
+ }
+
+ BitmapEx aBmpEx;
+ const SvStreamEndian nOldFormat = rIStm.GetEndian();
+
+ rIStm.SeekRel( -4 );
+ rIStm.SetEndian( SvStreamEndian::LITTLE );
+ ReadDIBBitmapEx(aBmpEx, rIStm);
+
+ if( !rIStm.GetError() )
+ {
+ sal_uInt32 nMagic1(0), nMagic2(0);
+ sal_uLong nActPos = rIStm.Tell();
+
+ rIStm.ReadUInt32( nMagic1 ).ReadUInt32( nMagic2 );
+ rIStm.Seek( nActPos );
+
+ rImpGraphic = ImpGraphic( aBmpEx );
+
+ if( !rIStm.GetError() && ( 0x5344414e == nMagic1 ) && ( 0x494d4931 == nMagic2 ) )
+ {
+ rImpGraphic.mpAnimation = std::make_unique<Animation>();
+ ReadAnimation( rIStm, *rImpGraphic.mpAnimation );
+
+ // #108077# manually set loaded BmpEx to Animation
+ // (which skips loading its BmpEx if already done)
+ rImpGraphic.mpAnimation->SetBitmapEx(aBmpEx);
+ }
+ else
+ rIStm.ResetError();
+ }
+ else
+ {
+ GDIMetaFile aMtf;
+
+ rIStm.Seek( nStmPos1 );
+ rIStm.ResetError();
+ ReadGDIMetaFile( rIStm, aMtf );
+
+ if( !rIStm.GetError() )
+ {
+ rImpGraphic = aMtf;
+ }
+ else
+ {
+ ErrCode nOrigError = rIStm.GetErrorCode();
+ // try to stream in Svg defining data (length, byte array and evtl. path)
+ // See below (operator<<) for more information
+ sal_uInt32 nMagic;
+ rIStm.Seek(nStmPos1);
+ rIStm.ResetError();
+ rIStm.ReadUInt32( nMagic );
+
+ if (constSvgMagic == nMagic || constWmfMagic == nMagic || constEmfMagic == nMagic || constPdfMagic == nMagic)
+ {
+ sal_uInt32 nVectorGraphicDataArrayLength(0);
+ rIStm.ReadUInt32(nVectorGraphicDataArrayLength);
+
+ if (nVectorGraphicDataArrayLength)
+ {
+ VectorGraphicDataArray aNewData(nVectorGraphicDataArrayLength);
+
+ rIStm.ReadBytes(aNewData.getArray(), nVectorGraphicDataArrayLength);
+ OUString aPath = rIStm.ReadUniOrByteString(rIStm.GetStreamCharSet());
+
+ if (!rIStm.GetError())
+ {
+ VectorGraphicDataType aDataType(VectorGraphicDataType::Svg);
+
+ if (constWmfMagic == nMagic)
+ {
+ aDataType = VectorGraphicDataType::Wmf;
+ }
+ else if (constEmfMagic == nMagic)
+ {
+ aDataType = VectorGraphicDataType::Emf;
+ }
+ else if (constPdfMagic == nMagic)
+ {
+ aDataType = VectorGraphicDataType::Pdf;
+ }
+
+ auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aNewData, aPath, aDataType);
+ rImpGraphic = aVectorGraphicDataPtr;
+ }
+ }
+ }
+ else
+ {
+ rIStm.SetError(nOrigError);
+ }
+
+ rIStm.Seek(nStmPos1);
+ }
+ }
+
+ rIStm.SetEndian( nOldFormat );
+}
+
+void WriteImpGraphic(SvStream& rOStm, const ImpGraphic& rImpGraphic)
+{
+ if (rOStm.GetError())
+ return;
+
+ rImpGraphic.ensureAvailable();
+
+ if (rImpGraphic.isSwappedOut())
+ {
+ rOStm.SetError( SVSTREAM_GENERALERROR );
+ return;
+ }
+
+ if( ( rOStm.GetVersion() >= SOFFICE_FILEFORMAT_50 ) &&
+ ( rOStm.GetCompressMode() & SvStreamCompressFlags::NATIVE ) &&
+ rImpGraphic.mpGfxLink && rImpGraphic.mpGfxLink->IsNative())
+ {
+ // native format
+ rOStm.WriteUInt32( NATIVE_FORMAT_50 );
+
+ // write compat info, destructor writes stuff into the header
+ {
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 1 );
+ }
+ rImpGraphic.mpGfxLink->SetPrefMapMode( rImpGraphic.ImplGetPrefMapMode() );
+ rImpGraphic.mpGfxLink->SetPrefSize( rImpGraphic.ImplGetPrefSize() );
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeGfxLink(*rImpGraphic.mpGfxLink);
+ }
+ else
+ {
+ // own format
+ const SvStreamEndian nOldFormat = rOStm.GetEndian();
+ rOStm.SetEndian( SvStreamEndian::LITTLE );
+
+ switch( rImpGraphic.ImplGetType() )
+ {
+ case GraphicType::NONE:
+ case GraphicType::Default:
+ break;
+
+ case GraphicType::Bitmap:
+ {
+ if(rImpGraphic.getVectorGraphicData())
+ {
+ // 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 (rImpGraphic.getVectorGraphicData()->getVectorGraphicDataType())
+ {
+ case VectorGraphicDataType::Wmf:
+ {
+ rOStm.WriteUInt32(constWmfMagic);
+ break;
+ }
+ case VectorGraphicDataType::Emf:
+ {
+ rOStm.WriteUInt32(constEmfMagic);
+ break;
+ }
+ case VectorGraphicDataType::Svg:
+ {
+ rOStm.WriteUInt32(constSvgMagic);
+ break;
+ }
+ case VectorGraphicDataType::Pdf:
+ {
+ rOStm.WriteUInt32(constPdfMagic);
+ break;
+ }
+ }
+
+ rOStm.WriteUInt32( rImpGraphic.getVectorGraphicData()->getVectorGraphicDataArrayLength() );
+ rOStm.WriteBytes(rImpGraphic.getVectorGraphicData()->getVectorGraphicDataArray().getConstArray(),
+ rImpGraphic.getVectorGraphicData()->getVectorGraphicDataArrayLength());
+ rOStm.WriteUniOrByteString(rImpGraphic.getVectorGraphicData()->getPath(),
+ rOStm.GetStreamCharSet());
+ }
+ else if( rImpGraphic.ImplIsAnimated())
+ {
+ WriteAnimation( rOStm, *rImpGraphic.mpAnimation );
+ }
+ else
+ {
+ WriteDIBBitmapEx(rImpGraphic.maBitmapEx, rOStm);
+ }
+ }
+ break;
+
+ default:
+ {
+ if( rImpGraphic.ImplIsSupportedGraphic() )
+ WriteGDIMetaFile( rOStm, rImpGraphic.maMetaFile );
+ }
+ break;
+ }
+
+ rOStm.SetEndian( nOldFormat );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/impvect.cxx b/vcl/source/gdi/impvect.cxx
new file mode 100644
index 000000000..60027e19c
--- /dev/null
+++ b/vcl/source/gdi/impvect.cxx
@@ -0,0 +1,1000 @@
+/* -*- 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/bitmapaccess.hxx>
+#include <tools/link.hxx>
+#include <tools/poly.hxx>
+#include <tools/helpers.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include "impvect.hxx"
+#include <array>
+#include <memory>
+
+#define VECT_POLY_MAX 8192
+
+#define VECT_FREE_INDEX 0
+#define VECT_CONT_INDEX 1
+#define VECT_DONE_INDEX 2
+
+#define VECT_POLY_INLINE_INNER 1UL
+#define VECT_POLY_INLINE_OUTER 2UL
+#define VECT_POLY_OUTLINE_INNER 4UL
+#define VECT_POLY_OUTLINE_OUTER 8UL
+
+static void VECT_MAP( const std::unique_ptr<long []> & pMapIn, const std::unique_ptr<long []>& pMapOut, long nVal )
+{
+ pMapIn[nVal] = (nVal * 4) + 1;
+ pMapOut[nVal] = pMapIn[nVal] + 5;
+}
+static constexpr long BACK_MAP( long _def_nVal )
+{
+ return ((_def_nVal + 2) >> 2) - 1;
+}
+static void VECT_PROGRESS( const Link<long, void>* pProgress, long _def_nVal )
+{
+ if(pProgress)
+ pProgress->Call(_def_nVal);
+}
+
+namespace {
+
+class ImplVectMap;
+class ImplChain;
+
+}
+
+namespace ImplVectorizer
+{
+ static ImplVectMap* ImplExpand( BitmapReadAccess* pRAcc, const Color& rColor );
+ static void ImplCalculate( ImplVectMap* pMap, tools::PolyPolygon& rPolyPoly, sal_uInt8 cReduce );
+ static bool ImplGetChain( ImplVectMap* pMap, const Point& rStartPt, ImplChain& rChain );
+ static bool ImplIsUp( ImplVectMap const * pMap, long nY, long nX );
+ static void ImplLimitPolyPoly( tools::PolyPolygon& rPolyPoly );
+}
+
+namespace {
+
+struct ChainMove { long nDX; long nDY; };
+
+}
+
+static const ChainMove aImplMove[ 8 ] = {
+ { 1, 0 },
+ { 0, -1 },
+ { -1, 0 },
+ { 0, 1 },
+ { 1, -1 },
+ { -1, -1 },
+ { -1, 1 },
+ { 1, 1 }
+ };
+
+static const ChainMove aImplMoveInner[ 8 ] = {
+ { 0, 1 },
+ { 1, 0 },
+ { 0, -1 },
+ { -1, 0 },
+ { 0, 1 },
+ { 1, 0 },
+ { 0, -1 },
+ { -1, 0 }
+ };
+
+static const ChainMove aImplMoveOuter[ 8 ] = {
+ { 0, -1 },
+ { -1, 0 },
+ { 0, 1 },
+ { 1, 0 },
+ { -1, 0 },
+ { 0, 1 },
+ { 1, 0 },
+ { 0, -1 }
+ };
+
+namespace {
+
+struct ImplColorSet
+{
+ BitmapColor maColor;
+ sal_uInt16 mnIndex = 0;
+ bool mbSet = false;
+};
+
+}
+
+static bool ImplColorSetCmpFnc( const ImplColorSet& lhs, const ImplColorSet& rhs)
+{
+ if( lhs.mbSet && rhs.mbSet )
+ {
+ const sal_uInt8 cLum1 = lhs.maColor.GetLuminance();
+ const sal_uInt8 cLum2 = rhs.maColor.GetLuminance();
+ return cLum1 < cLum2;
+ }
+ return lhs.mbSet > rhs.mbSet;
+}
+
+namespace {
+
+class ImplPointArray
+{
+ std::unique_ptr<Point[]> mpArray;
+ sal_uLong mnSize;
+ sal_uLong mnRealSize;
+
+public:
+
+ ImplPointArray();
+
+ void ImplSetSize( sal_uLong nSize );
+ sal_uLong ImplGetRealSize() const { return mnRealSize; }
+ void ImplSetRealSize( sal_uLong nRealSize ) { mnRealSize = nRealSize; }
+ void ImplCreatePoly( tools::Polygon& rPoly ) const;
+
+ inline Point& operator[]( sal_uLong nPos );
+ inline const Point& operator[]( sal_uLong nPos ) const;
+
+};
+
+}
+
+ImplPointArray::ImplPointArray() :
+ mnSize ( 0 ),
+ mnRealSize ( 0 )
+
+{
+}
+
+void ImplPointArray::ImplSetSize( sal_uLong nSize )
+{
+ const sal_uLong nTotal = nSize * sizeof( Point );
+
+ mnSize = nSize;
+ mnRealSize = 0;
+
+ mpArray = std::make_unique<Point[]>( nTotal );
+}
+
+inline Point& ImplPointArray::operator[]( sal_uLong nPos )
+{
+ SAL_WARN_IF( nPos >= mnSize, "vcl", "ImplPointArray::operator[]: nPos out of range!" );
+ return mpArray[ nPos ];
+}
+
+inline const Point& ImplPointArray::operator[]( sal_uLong nPos ) const
+{
+ SAL_WARN_IF( nPos >= mnSize, "vcl", "ImplPointArray::operator[]: nPos out of range!" );
+ return mpArray[ nPos ];
+}
+
+void ImplPointArray::ImplCreatePoly( tools::Polygon& rPoly ) const
+{
+ rPoly = tools::Polygon( sal::static_int_cast<sal_uInt16>(mnRealSize), mpArray.get() );
+}
+
+namespace {
+
+class ImplVectMap
+{
+private:
+
+ Scanline mpBuf;
+ Scanline* mpScan;
+ long mnWidth;
+ long mnHeight;
+
+public:
+
+ ImplVectMap( long nWidth, long nHeight );
+ ~ImplVectMap();
+
+ long Width() const { return mnWidth; }
+ long Height() const { return mnHeight; }
+
+ inline void Set( long nY, long nX, sal_uInt8 cVal );
+ inline sal_uInt8 Get( long nY, long nX ) const;
+
+ inline bool IsFree( long nY, long nX ) const;
+ inline bool IsCont( long nY, long nX ) const;
+ inline bool IsDone( long nY, long nX ) const;
+
+};
+
+}
+
+ImplVectMap::ImplVectMap( long nWidth, long nHeight ) :
+ mpBuf ( static_cast<Scanline>(rtl_allocateZeroMemory(nWidth * nHeight)) ),
+ mpScan ( static_cast<Scanline*>(std::malloc(nHeight * sizeof(Scanline))) ),
+ mnWidth ( nWidth ),
+ mnHeight( nHeight )
+{
+ const long nWidthAl = ( nWidth >> 2 ) + 1;
+ Scanline pTmp = mpBuf;
+
+ for( long nY = 0; nY < nHeight; pTmp += nWidthAl )
+ mpScan[ nY++ ] = pTmp;
+}
+
+ImplVectMap::~ImplVectMap()
+{
+ std::free( mpBuf );
+ std::free( mpScan );
+}
+
+inline void ImplVectMap::Set( long nY, long nX, sal_uInt8 cVal )
+{
+ const sal_uInt8 cShift = sal::static_int_cast<sal_uInt8>(6 - ( ( nX & 3 ) << 1 ));
+ auto & rPixel = mpScan[ nY ][ nX >> 2 ];
+ rPixel = (rPixel & ~( 3 << cShift ) ) | ( cVal << cShift );
+}
+
+inline sal_uInt8 ImplVectMap::Get( long nY, long nX ) const
+{
+ return sal::static_int_cast<sal_uInt8>( ( ( mpScan[ nY ][ nX >> 2 ] ) >> ( 6 - ( ( nX & 3 ) << 1 ) ) ) & 3 );
+}
+
+inline bool ImplVectMap::IsFree( long nY, long nX ) const
+{
+ return( VECT_FREE_INDEX == Get( nY, nX ) );
+}
+
+inline bool ImplVectMap::IsCont( long nY, long nX ) const
+{
+ return( VECT_CONT_INDEX == Get( nY, nX ) );
+}
+
+inline bool ImplVectMap::IsDone( long nY, long nX ) const
+{
+ return( VECT_DONE_INDEX == Get( nY, nX ) );
+}
+
+namespace {
+
+class ImplChain
+{
+private:
+
+ tools::Polygon maPoly;
+ Point maStartPt;
+ sal_uLong mnArraySize;
+ sal_uLong mnCount;
+ std::unique_ptr<sal_uInt8[]>
+ mpCodes;
+
+ void ImplGetSpace();
+
+ void ImplPostProcess( const ImplPointArray& rArr );
+
+ ImplChain(const ImplChain&) = delete;
+ ImplChain& operator=(const ImplChain&) = delete;
+
+public:
+
+ ImplChain();
+
+ void ImplBeginAdd( const Point& rStartPt );
+ inline void ImplAdd( sal_uInt8 nCode );
+ void ImplEndAdd( sal_uLong nTypeFlag );
+
+ const tools::Polygon& ImplGetPoly() const { return maPoly; }
+};
+
+}
+
+ImplChain::ImplChain() :
+ mnArraySize ( 1024 ),
+ mnCount ( 0 ),
+ mpCodes ( new sal_uInt8[mnArraySize] )
+{
+}
+
+void ImplChain::ImplGetSpace()
+{
+ const sal_uLong nOldArraySize = mnArraySize;
+ sal_uInt8* pNewCodes;
+
+ mnArraySize = mnArraySize << 1;
+ pNewCodes = new sal_uInt8[ mnArraySize ];
+ memcpy( pNewCodes, mpCodes.get(), nOldArraySize );
+ mpCodes.reset( pNewCodes );
+}
+
+void ImplChain::ImplBeginAdd( const Point& rStartPt )
+{
+ maPoly = tools::Polygon();
+ maStartPt = rStartPt;
+ mnCount = 0;
+}
+
+inline void ImplChain::ImplAdd( sal_uInt8 nCode )
+{
+ if( mnCount == mnArraySize )
+ ImplGetSpace();
+
+ mpCodes[ mnCount++ ] = nCode;
+}
+
+void ImplChain::ImplEndAdd( sal_uLong nFlag )
+{
+ if( mnCount )
+ {
+ ImplPointArray aArr;
+
+ if( nFlag & VECT_POLY_INLINE_INNER )
+ {
+ long nFirstX, nFirstY;
+ long nLastX, nLastY;
+
+ nFirstX = nLastX = maStartPt.X();
+ nFirstY = nLastY = maStartPt.Y();
+ aArr.ImplSetSize( mnCount << 1 );
+
+ sal_uInt16 nPolyPos;
+ sal_uLong i;
+ for( i = 0, nPolyPos = 0; i < ( mnCount - 1 ); i++ )
+ {
+ const sal_uInt8 cMove = mpCodes[ i ];
+ const sal_uInt8 cNextMove = mpCodes[ i + 1 ];
+ const ChainMove& rMove = aImplMove[ cMove ];
+ const ChainMove& rMoveInner = aImplMoveInner[ cMove ];
+// Point& rPt = aArr[ nPolyPos ];
+ bool bDone = true;
+
+ nLastX += rMove.nDX;
+ nLastY += rMove.nDY;
+
+ if( cMove < 4 )
+ {
+ if( ( cMove == 0 && cNextMove == 3 ) ||
+ ( cMove == 3 && cNextMove == 2 ) ||
+ ( cMove == 2 && cNextMove == 1 ) ||
+ ( cMove == 1 && cNextMove == 0 ) )
+ {
+ }
+ else if( cMove == 2 && cNextMove == 3 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 3 && cNextMove == 0 )
+ {
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+ }
+ else if( cMove == 0 && cNextMove == 1 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 1 && cNextMove == 2 )
+ {
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+ }
+ else
+ bDone = false;
+ }
+ else if( cMove == 7 && cNextMove == 0 )
+ {
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+ }
+ else if( cMove == 4 && cNextMove == 1 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else
+ bDone = false;
+
+ if( !bDone )
+ {
+ aArr[ nPolyPos ].setX( nLastX + rMoveInner.nDX );
+ aArr[ nPolyPos++ ].setY( nLastY + rMoveInner.nDY );
+ }
+ }
+
+ aArr[ nPolyPos ].setX( nFirstX + 1 );
+ aArr[ nPolyPos++ ].setY( nFirstY + 1 );
+ aArr.ImplSetRealSize( nPolyPos );
+ }
+ else if( nFlag & VECT_POLY_INLINE_OUTER )
+ {
+ long nFirstX, nFirstY;
+ long nLastX, nLastY;
+
+ nFirstX = nLastX = maStartPt.X();
+ nFirstY = nLastY = maStartPt.Y();
+ aArr.ImplSetSize( mnCount << 1 );
+
+ sal_uInt16 nPolyPos;
+ sal_uLong i;
+ for( i = 0, nPolyPos = 0; i < ( mnCount - 1 ); i++ )
+ {
+ const sal_uInt8 cMove = mpCodes[ i ];
+ const sal_uInt8 cNextMove = mpCodes[ i + 1 ];
+ const ChainMove& rMove = aImplMove[ cMove ];
+ const ChainMove& rMoveOuter = aImplMoveOuter[ cMove ];
+// Point& rPt = aArr[ nPolyPos ];
+ bool bDone = true;
+
+ nLastX += rMove.nDX;
+ nLastY += rMove.nDY;
+
+ if( cMove < 4 )
+ {
+ if( ( cMove == 0 && cNextMove == 1 ) ||
+ ( cMove == 1 && cNextMove == 2 ) ||
+ ( cMove == 2 && cNextMove == 3 ) ||
+ ( cMove == 3 && cNextMove == 0 ) )
+ {
+ }
+ else if( cMove == 0 && cNextMove == 3 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 3 && cNextMove == 2 )
+ {
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+ }
+ else if( cMove == 2 && cNextMove == 1 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 1 && cNextMove == 0 )
+ {
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX - 1 );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+ }
+ else
+ bDone = false;
+ }
+ else if( cMove == 7 && cNextMove == 3 )
+ {
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY - 1 );
+
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+ }
+ else if( cMove == 6 && cNextMove == 2 )
+ {
+ aArr[ nPolyPos ].setX( nLastX + 1 );
+ aArr[ nPolyPos++ ].setY( nLastY );
+
+ aArr[ nPolyPos ].setX( nLastX );
+ aArr[ nPolyPos++ ].setY( nLastY + 1 );
+ }
+ else
+ bDone = false;
+
+ if( !bDone )
+ {
+ aArr[ nPolyPos ].setX( nLastX + rMoveOuter.nDX );
+ aArr[ nPolyPos++ ].setY( nLastY + rMoveOuter.nDY );
+ }
+ }
+
+ aArr[ nPolyPos ].setX( nFirstX - 1 );
+ aArr[ nPolyPos++ ].setY( nFirstY - 1 );
+ aArr.ImplSetRealSize( nPolyPos );
+ }
+ else
+ {
+ long nLastX = maStartPt.X(), nLastY = maStartPt.Y();
+
+ aArr.ImplSetSize( mnCount + 1 );
+ aArr[ 0 ] = Point( nLastX, nLastY );
+
+ for( sal_uLong i = 0; i < mnCount; )
+ {
+ const ChainMove& rMove = aImplMove[ mpCodes[ i ] ];
+ nLastX += rMove.nDX;
+ nLastY += rMove.nDY;
+ aArr[ ++i ] = Point( nLastX, nLastY );
+ }
+
+ aArr.ImplSetRealSize( mnCount + 1 );
+ }
+
+ ImplPostProcess( aArr );
+ }
+ else
+ maPoly.SetSize( 0 );
+}
+
+void ImplChain::ImplPostProcess( const ImplPointArray& rArr )
+{
+ ImplPointArray aNewArr1;
+ ImplPointArray aNewArr2;
+ Point* pLast;
+ Point* pLeast;
+ sal_uLong nNewPos;
+ sal_uLong nCount = rArr.ImplGetRealSize();
+ sal_uLong n;
+
+ // pass 1
+ aNewArr1.ImplSetSize( nCount );
+ pLast = &( aNewArr1[ 0 ] );
+ pLast->setX( BACK_MAP( rArr[ 0 ].X() ) );
+ pLast->setY( BACK_MAP( rArr[ 0 ].Y() ) );
+
+ for( n = nNewPos = 1; n < nCount; )
+ {
+ const Point& rPt = rArr[ n++ ];
+ const long nX = BACK_MAP( rPt.X() );
+ const long nY = BACK_MAP( rPt.Y() );
+
+ if( nX != pLast->X() || nY != pLast->Y() )
+ {
+ pLast = pLeast = &( aNewArr1[ nNewPos++ ] );
+ pLeast->setX( nX );
+ pLeast->setY( nY );
+ }
+ }
+
+ nCount = nNewPos;
+ aNewArr1.ImplSetRealSize( nCount );
+
+ // pass 2
+ aNewArr2.ImplSetSize( nCount );
+ pLast = &( aNewArr2[ 0 ] );
+ *pLast = aNewArr1[ 0 ];
+
+ for( n = nNewPos = 1; n < nCount; )
+ {
+ pLeast = &( aNewArr1[ n++ ] );
+
+ if( pLeast->X() == pLast->X() )
+ {
+ while( n < nCount && aNewArr1[ n ].X() == pLast->X() )
+ pLeast = &( aNewArr1[ n++ ] );
+ }
+ else if( pLeast->Y() == pLast->Y() )
+ {
+ while( n < nCount && aNewArr1[ n ].Y() == pLast->Y() )
+ pLeast = &( aNewArr1[ n++ ] );
+ }
+
+ pLast = pLeast;
+ aNewArr2[ nNewPos++ ] = *pLast;
+ }
+
+ aNewArr2.ImplSetRealSize( nNewPos );
+ aNewArr2.ImplCreatePoly( maPoly );
+}
+
+namespace ImplVectorizer {
+
+bool ImplVectorize( const Bitmap& rColorBmp, GDIMetaFile& rMtf,
+ sal_uInt8 cReduce, const Link<long,void>* pProgress )
+{
+ bool bRet = false;
+
+ VECT_PROGRESS( pProgress, 0 );
+
+ std::unique_ptr<Bitmap> xBmp(new Bitmap( rColorBmp ));
+ Bitmap::ScopedReadAccess pRAcc(*xBmp);
+
+ if( pRAcc )
+ {
+ tools::PolyPolygon aPolyPoly;
+ double fPercent = 0.0;
+ double fPercentStep_2 = 0.0;
+ const long nWidth = pRAcc->Width();
+ const long nHeight = pRAcc->Height();
+ const sal_uInt16 nColorCount = pRAcc->GetPaletteEntryCount();
+ sal_uInt16 n;
+ std::array<ImplColorSet, 256> aColorSet;
+
+ rMtf.Clear();
+
+ // get used palette colors and sort them from light to dark colors
+ for( n = 0; n < nColorCount; n++ )
+ {
+ aColorSet[ n ].mnIndex = n;
+ aColorSet[ n ].maColor = pRAcc->GetPaletteColor( n );
+ }
+
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanlineRead = pRAcc->GetScanline( nY );
+ for( long nX = 0; nX < nWidth; nX++ )
+ aColorSet[ pRAcc->GetIndexFromData( pScanlineRead, nX ) ].mbSet = true;
+ }
+
+ std::sort( aColorSet.begin(), aColorSet.end(), ImplColorSetCmpFnc );
+
+ for( n = 0; n < 256; n++ )
+ if( !aColorSet[ n ].mbSet )
+ break;
+
+ if( n )
+ fPercentStep_2 = 45.0 / n;
+
+ fPercent += 10.0;
+ VECT_PROGRESS( pProgress, FRound( fPercent ) );
+
+ for( sal_uInt16 i = 0; i < n; i++ )
+ {
+ const BitmapColor aBmpCol( pRAcc->GetPaletteColor( aColorSet[ i ].mnIndex ) );
+ const Color aFindColor( aBmpCol.GetRed(), aBmpCol.GetGreen(), aBmpCol.GetBlue() );
+ std::unique_ptr<ImplVectMap> xMap(ImplExpand( pRAcc.get(), aFindColor ));
+
+ fPercent += fPercentStep_2;
+ VECT_PROGRESS( pProgress, FRound( fPercent ) );
+
+ if( xMap )
+ {
+ aPolyPoly.Clear();
+ ImplCalculate( xMap.get(), aPolyPoly, cReduce );
+ xMap.reset();
+
+ if( aPolyPoly.Count() )
+ {
+ ImplLimitPolyPoly( aPolyPoly );
+
+ aPolyPoly.Optimize( PolyOptimizeFlags::EDGES );
+
+ if( aPolyPoly.Count() )
+ {
+ rMtf.AddAction( new MetaLineColorAction( aFindColor, true ) );
+ rMtf.AddAction( new MetaFillColorAction( aFindColor, true ) );
+ rMtf.AddAction( new MetaPolyPolygonAction( aPolyPoly ) );
+ }
+ }
+ }
+
+ fPercent += fPercentStep_2;
+ VECT_PROGRESS( pProgress, FRound( fPercent ) );
+ }
+
+ if( rMtf.GetActionSize() )
+ {
+ MapMode aMap( MapUnit::Map100thMM );
+ ScopedVclPtrInstance< VirtualDevice > aVDev;
+ const Size aLogSize1( aVDev->PixelToLogic( Size( 1, 1 ), aMap ) );
+
+ rMtf.SetPrefMapMode( aMap );
+ rMtf.SetPrefSize( Size( nWidth + 2, nHeight + 2 ) );
+ rMtf.Move( 1, 1 );
+ rMtf.Scale( aLogSize1.Width(), aLogSize1.Height() );
+ bRet = true;
+ }
+ }
+
+ pRAcc.reset();
+ xBmp.reset();
+ VECT_PROGRESS( pProgress, 100 );
+
+ return bRet;
+}
+
+void ImplLimitPolyPoly( tools::PolyPolygon& rPolyPoly )
+{
+ if( rPolyPoly.Count() > VECT_POLY_MAX )
+ {
+ tools::PolyPolygon aNewPolyPoly;
+ long nReduce = 0;
+ sal_uInt16 nNewCount;
+
+ do
+ {
+ aNewPolyPoly.Clear();
+ nReduce++;
+
+ for( sal_uInt16 i = 0, nCount = rPolyPoly.Count(); i < nCount; i++ )
+ {
+ const tools::Rectangle aBound( rPolyPoly[ i ].GetBoundRect() );
+
+ if( aBound.GetWidth() > nReduce && aBound.GetHeight() > nReduce )
+ {
+ if( rPolyPoly[ i ].GetSize() )
+ aNewPolyPoly.Insert( rPolyPoly[ i ] );
+ }
+ }
+
+ nNewCount = aNewPolyPoly.Count();
+ }
+ while( nNewCount > VECT_POLY_MAX );
+
+ rPolyPoly = aNewPolyPoly;
+ }
+}
+
+ImplVectMap* ImplExpand( BitmapReadAccess* pRAcc, const Color& rColor )
+{
+ ImplVectMap* pMap = nullptr;
+
+ if( pRAcc && pRAcc->Width() && pRAcc->Height() )
+ {
+ const long nOldWidth = pRAcc->Width();
+ const long nOldHeight = pRAcc->Height();
+ const long nNewWidth = ( nOldWidth << 2 ) + 4;
+ const long nNewHeight = ( nOldHeight << 2 ) + 4;
+ const BitmapColor aTest( pRAcc->GetBestMatchingColor( rColor ) );
+ std::unique_ptr<long[]> pMapIn(new long[ std::max( nOldWidth, nOldHeight ) ]);
+ std::unique_ptr<long[]> pMapOut(new long[ std::max( nOldWidth, nOldHeight ) ]);
+ long nX, nY, nTmpX, nTmpY;
+
+ pMap = new ImplVectMap( nNewWidth, nNewHeight );
+
+ for( nX = 0; nX < nOldWidth; nX++ )
+ VECT_MAP( pMapIn, pMapOut, nX );
+
+ for( nY = 0, nTmpY = 5; nY < nOldHeight; nY++, nTmpY += 4 )
+ {
+ Scanline pScanlineRead = pRAcc->GetScanline( nY );
+ for( nX = 0; nX < nOldWidth; )
+ {
+ if( pRAcc->GetPixelFromData( pScanlineRead, nX ) == aTest )
+ {
+ nTmpX = pMapIn[ nX++ ];
+ nTmpY -= 3;
+
+ pMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ pMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ pMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+
+ while( nX < nOldWidth && pRAcc->GetPixelFromData( pScanlineRead, nX ) == aTest )
+ nX++;
+
+ nTmpX = pMapOut[ nX - 1 ];
+ nTmpY -= 3;
+
+ pMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ pMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ pMap->Set( nTmpY++, nTmpX, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+ }
+ else
+ nX++;
+ }
+ }
+
+ for( nY = 0; nY < nOldHeight; nY++ )
+ VECT_MAP( pMapIn, pMapOut, nY );
+
+ for( nX = 0, nTmpX = 5; nX < nOldWidth; nX++, nTmpX += 4 )
+ {
+ for( nY = 0; nY < nOldHeight; )
+ {
+ if( pRAcc->GetPixel( nY, nX ) == aTest )
+ {
+ nTmpX -= 3;
+ nTmpY = pMapIn[ nY++ ];
+
+ pMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+
+ while( nY < nOldHeight && pRAcc->GetPixel( nY, nX ) == aTest )
+ nY++;
+
+ nTmpX -= 3;
+ nTmpY = pMapOut[ nY - 1 ];
+
+ pMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX++, VECT_CONT_INDEX );
+ pMap->Set( nTmpY, nTmpX, VECT_CONT_INDEX );
+ }
+ else
+ nY++;
+ }
+ }
+ }
+
+ return pMap;
+}
+
+void ImplCalculate( ImplVectMap* pMap, tools::PolyPolygon& rPolyPoly, sal_uInt8 cReduce )
+{
+ const long nWidth = pMap->Width(), nHeight= pMap->Height();
+
+ for( long nY = 0; nY < nHeight; nY++ )
+ {
+ long nX = 0;
+ bool bInner = true;
+
+ while( nX < nWidth )
+ {
+ // skip free
+ while( ( nX < nWidth ) && pMap->IsFree( nY, nX ) )
+ nX++;
+
+ if( nX == nWidth )
+ break;
+
+ if( pMap->IsCont( nY, nX ) )
+ {
+ // new contour
+ ImplChain aChain;
+ const Point aStartPt( nX++, nY );
+
+ // get chain code
+ aChain.ImplBeginAdd( aStartPt );
+ ImplGetChain( pMap, aStartPt, aChain );
+
+ aChain.ImplEndAdd( bInner ? VECT_POLY_OUTLINE_INNER : VECT_POLY_OUTLINE_OUTER );
+
+ const tools::Polygon& rPoly = aChain.ImplGetPoly();
+
+ if( rPoly.GetSize() > 2 )
+ {
+ if( cReduce )
+ {
+ const tools::Rectangle aBound( rPoly.GetBoundRect() );
+
+ if( aBound.GetWidth() > cReduce && aBound.GetHeight() > cReduce )
+ rPolyPoly.Insert( rPoly );
+ }
+ else
+ rPolyPoly.Insert( rPoly );
+ }
+
+ // skip rest of detected contour
+ while( pMap->IsCont( nY, nX ) )
+ nX++;
+ }
+ else
+ {
+ // process done segment
+ const long nStartSegX = nX++;
+
+ while( pMap->IsDone( nY, nX ) )
+ nX++;
+
+ if( ( ( nX - nStartSegX ) == 1 ) || ( ImplIsUp( pMap, nY, nStartSegX ) != ImplIsUp( pMap, nY, nX - 1 ) ) )
+ bInner = !bInner;
+ }
+ }
+ }
+}
+
+bool ImplGetChain( ImplVectMap* pMap, const Point& rStartPt, ImplChain& rChain )
+{
+ long nActX = rStartPt.X();
+ long nActY = rStartPt.Y();
+ sal_uLong nFound;
+ sal_uLong nLastDir = 0;
+ sal_uLong nDir;
+
+ do
+ {
+ nFound = 0;
+
+ // first try last direction
+ long nTryX = nActX + aImplMove[ nLastDir ].nDX;
+ long nTryY = nActY + aImplMove[ nLastDir ].nDY;
+
+ if( pMap->IsCont( nTryY, nTryX ) )
+ {
+ rChain.ImplAdd( static_cast<sal_uInt8>(nLastDir) );
+ nActY = nTryY;
+ nActX = nTryX;
+ pMap->Set( nActY, nActX, VECT_DONE_INDEX );
+ nFound = 1;
+ }
+ else
+ {
+ // try other directions
+ for( nDir = 0; nDir < 8; nDir++ )
+ {
+ // we already tried nLastDir
+ if( nDir != nLastDir )
+ {
+ nTryX = nActX + aImplMove[ nDir ].nDX;
+ nTryY = nActY + aImplMove[ nDir ].nDY;
+
+ if( pMap->IsCont( nTryY, nTryX ) )
+ {
+ rChain.ImplAdd( static_cast<sal_uInt8>(nDir) );
+ nActY = nTryY;
+ nActX = nTryX;
+ pMap->Set( nActY, nActX, VECT_DONE_INDEX );
+ nFound = 1;
+ nLastDir = nDir;
+ break;
+ }
+ }
+ }
+ }
+ }
+ while( nFound );
+
+ return true;
+}
+
+bool ImplIsUp( ImplVectMap const * pMap, long nY, long nX )
+{
+ if( pMap->IsDone( nY - 1, nX ) )
+ return true;
+ else if( pMap->IsDone( nY + 1, nX ) )
+ return false;
+ else if( pMap->IsDone( nY - 1, nX - 1 ) || pMap->IsDone( nY - 1, nX + 1 ) )
+ return true;
+ else
+ return false;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/impvect.hxx b/vcl/source/gdi/impvect.hxx
new file mode 100644
index 000000000..017b2dc1a
--- /dev/null
+++ b/vcl/source/gdi/impvect.hxx
@@ -0,0 +1,35 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_GDI_IMPVECT_HXX
+#define INCLUDED_VCL_SOURCE_GDI_IMPVECT_HXX
+
+#include <vcl/gdimtf.hxx>
+
+namespace tools { class PolyPolygon; }
+
+namespace ImplVectorizer
+{
+ bool ImplVectorize( const Bitmap& rColorBmp, GDIMetaFile& rMtf,
+ sal_uInt8 cReduce, const Link<long,void>* pProgress );
+};
+
+#endif
+
+/* 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 000000000..a861169c9
--- /dev/null
+++ b/vcl/source/gdi/jobset.cxx
@@ -0,0 +1,394 @@
+/* -*- 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>
+#include <rtl/instance.hxx>
+
+#define JOBSET_FILE364_SYSTEM (sal_uInt16(0xFFFF))
+#define JOBSET_FILE605_SYSTEM (sal_uInt16(0xFFFE))
+
+namespace {
+
+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;
+ mpDriverData = nullptr;
+ 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() ),
+ mbPapersizeFromSetup( rJobSetup.GetPapersizeFromSetup() ),
+ meSetupMode( rJobSetup.GetPrinterSetupMode() ),
+ maValueMap( rJobSetup.GetValueMap() )
+ {
+ if ( rJobSetup.GetDriverData() )
+ {
+ mpDriverData = static_cast<sal_uInt8*>(std::malloc( mnDriverDataLen ));
+ memcpy( mpDriverData, rJobSetup.GetDriverData(), mnDriverDataLen );
+ }
+ else
+ mpDriverData = nullptr;
+}
+
+ImplJobSetup::~ImplJobSetup()
+{
+ std::free( mpDriverData );
+}
+
+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(long nPaperWidth)
+{
+ mnPaperWidth = nPaperWidth;
+}
+
+void ImplJobSetup::SetPaperHeight(long nPaperHeight)
+{
+ mnPaperHeight = nPaperHeight;
+}
+
+void ImplJobSetup::SetDriverDataLen(sal_uInt32 nDriverDataLen)
+{
+ mnDriverDataLen = nDriverDataLen;
+}
+
+void ImplJobSetup::SetDriverData(sal_uInt8* pDriverData)
+{
+ mpDriverData = pDriverData;
+}
+
+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, rImplJobSetup.mpDriverData, mnDriverDataLen ) == 0;
+}
+
+namespace
+{
+ struct theGlobalDefault :
+ public rtl::Static< JobSetup::ImplType, theGlobalDefault > {};
+}
+
+JobSetup::JobSetup() : mpData(theGlobalDefault::get())
+{
+}
+
+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(theGlobalDefault::get());
+}
+
+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();
+
+ pData->cPrinterName[SAL_N_ELEMENTS(pData->cPrinterName) - 1] = 0;
+ rJobData.SetPrinterName( OStringToOUString(pData->cPrinterName, aStreamEncoding) );
+ pData->cDriverName[SAL_N_ELEMENTS(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 )
+ {
+ Impl364JobSetupData* pOldJobData = reinterpret_cast<Impl364JobSetupData*>(pTempBuf.get() + sizeof( ImplOldJobSetupData ));
+ sal_uInt16 nOldJobDataSize = SVBT16ToUInt16( pOldJobData->nSize );
+ rJobData.SetSystem( SVBT16ToUInt16( pOldJobData->nSystem ) );
+ rJobData.SetDriverDataLen( SVBT32ToUInt32( pOldJobData->nDriverDataLen ) );
+ rJobData.SetOrientation( static_cast<Orientation>(SVBT16ToUInt16( pOldJobData->nOrientation )) );
+ rJobData.SetDuplexMode( DuplexMode::Unknown );
+ rJobData.SetPaperBin( SVBT16ToUInt16( pOldJobData->nPaperBin ) );
+ rJobData.SetPaperFormat( static_cast<Paper>(SVBT16ToUInt16( pOldJobData->nPaperFormat )) );
+ rJobData.SetPaperWidth( static_cast<long>(SVBT32ToUInt32( pOldJobData->nPaperWidth )) );
+ rJobData.SetPaperHeight( static_cast<long>(SVBT32ToUInt32( pOldJobData->nPaperHeight )) );
+ if ( rJobData.GetDriverDataLen() )
+ {
+ const char* pDriverData = reinterpret_cast<const char*>(pOldJobData) + nOldJobDataSize;
+ const char* pDriverDataEnd = pDriverData + rJobData.GetDriverDataLen();
+ if (pDriverDataEnd > pTempBuf.get() + nRead)
+ {
+ SAL_WARN("vcl", "corrupted job setup");
+ }
+ else
+ {
+ sal_uInt8* pNewDriverData = static_cast<sal_uInt8*>(
+ std::malloc( rJobData.GetDriverDataLen() ));
+ memcpy( pNewDriverData, pDriverData, rJobData.GetDriverDataLen() );
+ rJobData.SetDriverData( pNewDriverData );
+ }
+ }
+ 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
+ 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(), SAL_N_ELEMENTS(aOldData.cPrinterName) - 1);
+ OString aDriverByteName(OUStringToOString(rJobData.GetDriver(), RTL_TEXTENCODING_UTF8));
+ strncpy(aOldData.cDriverName, aDriverByteName.getStr(), SAL_N_ELEMENTS(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;
+ }
+ 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 000000000..dd460d77e
--- /dev/null
+++ b/vcl/source/gdi/lineinfo.cxx
@@ -0,0 +1,261 @@
+/* -*- 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/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, sal_Int32 nWidth ) : mpImplLineInfo()
+{
+ 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( sal_Int32 nWidth )
+{
+ mpImplLineInfo->mnWidth = nWidth;
+}
+
+void LineInfo::SetDashCount( sal_uInt16 nDashCount )
+{
+ mpImplLineInfo->mnDashCount = nDashCount;
+}
+
+void LineInfo::SetDashLen( sal_Int32 nDashLen )
+{
+ mpImplLineInfo->mnDashLen = nDashLen;
+}
+
+void LineInfo::SetDotCount( sal_uInt16 nDotCount )
+{
+ mpImplLineInfo->mnDotCount = nDotCount;
+}
+
+void LineInfo::SetDotLen( sal_Int32 nDotLen )
+{
+ mpImplLineInfo->mnDotLen = nDotLen;
+}
+
+void LineInfo::SetDistance( sal_Int32 nDistance )
+{
+ mpImplLineInfo->mnDistance = nDistance;
+}
+
+void LineInfo::SetLineJoin(basegfx::B2DLineJoin eLineJoin)
+{
+
+ if(eLineJoin != mpImplLineInfo->meLineJoin)
+ {
+ mpImplLineInfo->meLineJoin = eLineJoin;
+ }
+}
+
+void LineInfo::SetLineCap(css::drawing::LineCap eLineCap)
+{
+ if(eLineCap != mpImplLineInfo->meLineCap)
+ {
+ mpImplLineInfo->meLineCap = eLineCap;
+ }
+}
+
+bool LineInfo::IsDefault() const
+{
+ return( !mpImplLineInfo->mnWidth
+ && ( LineStyle::Solid == mpImplLineInfo->meStyle )
+ && ( css::drawing::LineCap_BUTT == mpImplLineInfo->meLineCap));
+}
+
+SvStream& ReadLineInfo( SvStream& rIStm, LineInfo& rLineInfo )
+{
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+ 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);
+ }
+
+ return rIStm;
+}
+
+SvStream& WriteLineInfo( SvStream& rOStm, const LineInfo& rLineInfo )
+{
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 4 );
+
+ // version 1
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rLineInfo.mpImplLineInfo->meStyle) )
+ .WriteInt32( rLineInfo.mpImplLineInfo->mnWidth );
+
+ // since version2
+ rOStm.WriteUInt16( rLineInfo.mpImplLineInfo->mnDashCount )
+ .WriteInt32( rLineInfo.mpImplLineInfo->mnDashLen );
+ rOStm.WriteUInt16( rLineInfo.mpImplLineInfo->mnDotCount )
+ .WriteInt32( rLineInfo.mpImplLineInfo->mnDotLen );
+ rOStm.WriteInt32( 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) );
+
+ return rOStm;
+}
+
+void LineInfo::applyToB2DPolyPolygon(
+ basegfx::B2DPolyPolygon& io_rLinePolyPolygon,
+ basegfx::B2DPolyPolygon& o_rFillPolyPolygon) const
+{
+ o_rFillPolyPolygon.clear();
+
+ if(io_rLinePolyPolygon.count())
+ {
+ if(LineStyle::Dash == GetStyle())
+ {
+ ::std::vector< double > 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);
+ }
+
+ const double fAccumulated(::std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0));
+
+ if(fAccumulated > 0.0)
+ {
+ basegfx::B2DPolyPolygon aResult;
+
+ for(auto const& rPolygon : io_rLinePolyPolygon)
+ {
+ basegfx::B2DPolyPolygon aLineTraget;
+ basegfx::utils::applyLineDashing(
+ rPolygon,
+ fDotDashArray,
+ &aLineTraget);
+ aResult.append(aLineTraget);
+ }
+
+ io_rLinePolyPolygon = aResult;
+ }
+ }
+
+ if(GetWidth() > 1 && io_rLinePolyPolygon.count())
+ {
+ const double fHalfLineWidth((GetWidth() * 0.5) + 0.5);
+
+ for(auto const& rPolygon : 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 000000000..5103da661
--- /dev/null
+++ b/vcl/source/gdi/mapmod.cxx
@@ -0,0 +1,178 @@
+/* -*- 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 <tools/gen.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <rtl/instance.hxx>
+#include <TypeSerializer.hxx>
+
+struct MapMode::ImplMapMode
+{
+ MapUnit meUnit;
+ 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;
+ bool mbSimple;
+
+ ImplMapMode();
+ 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( 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
+{
+ struct theGlobalDefault :
+ public rtl::Static< MapMode::ImplType, theGlobalDefault > {};
+}
+
+MapMode::MapMode() : mpImplMapMode(theGlobalDefault::get())
+{
+}
+
+MapMode::MapMode( const MapMode& ) = default;
+
+MapMode::MapMode( MapUnit eUnit ) : mpImplMapMode()
+{
+ mpImplMapMode->meUnit = 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->maScaleX.ReduceInaccurate(32);
+ mpImplMapMode->maScaleY.ReduceInaccurate(32);
+ 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->maScaleX.ReduceInaccurate(32);
+ mpImplMapMode->mbSimple = false;
+}
+
+void MapMode::SetScaleY( const Fraction& rScaleY )
+{
+ mpImplMapMode->maScaleY = rScaleY;
+ mpImplMapMode->maScaleY.ReduceInaccurate(32);
+ 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(theGlobalDefault::get());
+}
+
+SvStream& ReadMapMode( SvStream& rIStm, MapMode& rMapMode )
+{
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+ sal_uInt16 nTmp16;
+
+ TypeSerializer aSerializer(rIStm);
+
+ rIStm.ReadUInt16( nTmp16 ); rMapMode.mpImplMapMode->meUnit = static_cast<MapUnit>(nTmp16);
+ aSerializer.readPoint(rMapMode.mpImplMapMode->maOrigin);
+ ReadFraction( rIStm, rMapMode.mpImplMapMode->maScaleX );
+ ReadFraction( rIStm, rMapMode.mpImplMapMode->maScaleY );
+ rIStm.ReadCharAsBool( rMapMode.mpImplMapMode->mbSimple );
+
+ return rIStm;
+}
+
+SvStream& WriteMapMode( SvStream& rOStm, const MapMode& rMapMode )
+{
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 1 );
+
+ TypeSerializer aSerializer(rOStm);
+
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(rMapMode.mpImplMapMode->meUnit) );
+ aSerializer.writePoint(rMapMode.mpImplMapMode->maOrigin);
+ WriteFraction( rOStm, rMapMode.mpImplMapMode->maScaleX );
+ WriteFraction( rOStm, rMapMode.mpImplMapMode->maScaleY );
+ rOStm.WriteBool( rMapMode.mpImplMapMode->mbSimple );
+
+ return rOStm;
+}
+
+
+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 000000000..0f55e2fbe
--- /dev/null
+++ b/vcl/source/gdi/metaact.cxx
@@ -0,0 +1,3412 @@
+/* -*- 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 <vcl/dibtools.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/graphictools.hxx>
+#include <unotools/fontdefs.hxx>
+#include <TypeSerializer.hxx>
+
+namespace
+{
+
+const char *
+meta_action_name(MetaActionType nMetaAction)
+{
+#ifndef SAL_LOG_INFO
+ (void) nMetaAction;
+ return "";
+#else
+ switch( nMetaAction )
+ {
+ case MetaActionType::NONE: return "NULL";
+ case MetaActionType::PIXEL: return "PIXEL";
+ case MetaActionType::POINT: return "POINT";
+ case MetaActionType::LINE: return "LINE";
+ case MetaActionType::RECT: return "RECT";
+ case MetaActionType::ROUNDRECT: return "ROUNDRECT";
+ case MetaActionType::ELLIPSE: return "ELLIPSE";
+ case MetaActionType::ARC: return "ARC";
+ case MetaActionType::PIE: return "PIE";
+ case MetaActionType::CHORD: return "CHORD";
+ case MetaActionType::POLYLINE: return "POLYLINE";
+ case MetaActionType::POLYGON: return "POLYGON";
+ case MetaActionType::POLYPOLYGON: return "POLYPOLYGON";
+ case MetaActionType::TEXT: return "TEXT";
+ case MetaActionType::TEXTARRAY: return "TEXTARRAY";
+ case MetaActionType::STRETCHTEXT: return "STRETCHTEXT";
+ case MetaActionType::TEXTRECT: return "TEXTRECT";
+ case MetaActionType::BMP: return "BMP";
+ case MetaActionType::BMPSCALE: return "BMPSCALE";
+ case MetaActionType::BMPSCALEPART: return "BMPSCALEPART";
+ case MetaActionType::BMPEX: return "BMPEX";
+ case MetaActionType::BMPEXSCALE: return "BMPEXSCALE";
+ case MetaActionType::BMPEXSCALEPART: return "BMPEXSCALEPART";
+ case MetaActionType::MASK: return "MASK";
+ case MetaActionType::MASKSCALE: return "MASKSCALE";
+ case MetaActionType::MASKSCALEPART: return "MASKSCALEPART";
+ case MetaActionType::GRADIENT: return "GRADIENT";
+ case MetaActionType::HATCH: return "HATCH";
+ case MetaActionType::WALLPAPER: return "WALLPAPER";
+ case MetaActionType::CLIPREGION: return "CLIPREGION";
+ case MetaActionType::ISECTRECTCLIPREGION: return "ISECTRECTCLIPREGION";
+ case MetaActionType::ISECTREGIONCLIPREGION: return "ISECTREGIONCLIPREGION";
+ case MetaActionType::MOVECLIPREGION: return "MOVECLIPREGION";
+ case MetaActionType::LINECOLOR: return "LINECOLOR";
+ case MetaActionType::FILLCOLOR: return "FILLCOLOR";
+ case MetaActionType::TEXTCOLOR: return "TEXTCOLOR";
+ case MetaActionType::TEXTFILLCOLOR: return "TEXTFILLCOLOR";
+ case MetaActionType::TEXTALIGN: return "TEXTALIGN";
+ case MetaActionType::MAPMODE: return "MAPMODE";
+ case MetaActionType::FONT: return "FONT";
+ case MetaActionType::PUSH: return "PUSH";
+ case MetaActionType::POP: return "POP";
+ case MetaActionType::RASTEROP: return "RASTEROP";
+ case MetaActionType::Transparent: return "TRANSPARENT";
+ case MetaActionType::EPS: return "EPS";
+ case MetaActionType::REFPOINT: return "REFPOINT";
+ case MetaActionType::TEXTLINECOLOR: return "TEXTLINECOLOR";
+ case MetaActionType::TEXTLINE: return "TEXTLINE";
+ case MetaActionType::FLOATTRANSPARENT: return "FLOATTRANSPARENT";
+ case MetaActionType::GRADIENTEX: return "GRADIENTEX";
+ case MetaActionType::LAYOUTMODE: return "LAYOUTMODE";
+ case MetaActionType::TEXTLANGUAGE: return "TEXTLANGUAGE";
+ case MetaActionType::OVERLINECOLOR: return "OVERLINECOLOR";
+ case MetaActionType::COMMENT: return "COMMENT";
+ default:
+ // Yes, return a pointer to a static buffer. This is a very
+ // local debugging output function, so no big deal.
+ static char buffer[11];
+ sprintf(buffer, "%u", static_cast<unsigned int>(nMetaAction));
+ return buffer;
+ }
+#endif
+}
+
+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.Justify();
+}
+
+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()
+{
+ return new MetaAction;
+}
+
+void MetaAction::Move( long, long )
+{
+}
+
+void MetaAction::Scale( double, double )
+{
+}
+
+void MetaAction::Write( SvStream& rOStm, ImplMetaWriteData* )
+{
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(mnType) );
+}
+
+void MetaAction::Read( SvStream&, ImplMetaReadData* )
+{
+ // DO NOT read mnType - ReadMetaAction already did that!
+}
+
+MetaAction* MetaAction::ReadMetaAction( SvStream& rIStm, ImplMetaReadData* pData )
+{
+ MetaAction* pAction = nullptr;
+ sal_uInt16 nTmp = 0;
+ rIStm.ReadUInt16( nTmp );
+ MetaActionType nType = static_cast<MetaActionType>(nTmp);
+
+ SAL_INFO("vcl.gdi", "ReadMetaAction " << meta_action_name( nType ));
+
+ switch( nType )
+ {
+ case MetaActionType::NONE: pAction = new MetaAction; break;
+ case MetaActionType::PIXEL: pAction = new MetaPixelAction; break;
+ case MetaActionType::POINT: pAction = new MetaPointAction; break;
+ case MetaActionType::LINE: pAction = new MetaLineAction; break;
+ case MetaActionType::RECT: pAction = new MetaRectAction; break;
+ case MetaActionType::ROUNDRECT: pAction = new MetaRoundRectAction; break;
+ case MetaActionType::ELLIPSE: pAction = new MetaEllipseAction; break;
+ case MetaActionType::ARC: pAction = new MetaArcAction; break;
+ case MetaActionType::PIE: pAction = new MetaPieAction; break;
+ case MetaActionType::CHORD: pAction = new MetaChordAction; break;
+ case MetaActionType::POLYLINE: pAction = new MetaPolyLineAction; break;
+ case MetaActionType::POLYGON: pAction = new MetaPolygonAction; break;
+ case MetaActionType::POLYPOLYGON: pAction = new MetaPolyPolygonAction; break;
+ case MetaActionType::TEXT: pAction = new MetaTextAction; break;
+ case MetaActionType::TEXTARRAY: pAction = new MetaTextArrayAction; break;
+ case MetaActionType::STRETCHTEXT: pAction = new MetaStretchTextAction; break;
+ case MetaActionType::TEXTRECT: pAction = new MetaTextRectAction; break;
+ case MetaActionType::TEXTLINE: pAction = new MetaTextLineAction; break;
+ case MetaActionType::BMP: pAction = new MetaBmpAction; break;
+ case MetaActionType::BMPSCALE: pAction = new MetaBmpScaleAction; break;
+ case MetaActionType::BMPSCALEPART: pAction = new MetaBmpScalePartAction; break;
+ case MetaActionType::BMPEX: pAction = new MetaBmpExAction; break;
+ case MetaActionType::BMPEXSCALE: pAction = new MetaBmpExScaleAction; break;
+ case MetaActionType::BMPEXSCALEPART: pAction = new MetaBmpExScalePartAction; break;
+ case MetaActionType::MASK: pAction = new MetaMaskAction; break;
+ case MetaActionType::MASKSCALE: pAction = new MetaMaskScaleAction; break;
+ case MetaActionType::MASKSCALEPART: pAction = new MetaMaskScalePartAction; break;
+ case MetaActionType::GRADIENT: pAction = new MetaGradientAction; break;
+ case MetaActionType::GRADIENTEX: pAction = new MetaGradientExAction; break;
+ case MetaActionType::HATCH: pAction = new MetaHatchAction; break;
+ case MetaActionType::WALLPAPER: pAction = new MetaWallpaperAction; break;
+ case MetaActionType::CLIPREGION: pAction = new MetaClipRegionAction; break;
+ case MetaActionType::ISECTRECTCLIPREGION: pAction = new MetaISectRectClipRegionAction; break;
+ case MetaActionType::ISECTREGIONCLIPREGION: pAction = new MetaISectRegionClipRegionAction; break;
+ case MetaActionType::MOVECLIPREGION: pAction = new MetaMoveClipRegionAction; break;
+ case MetaActionType::LINECOLOR: pAction = new MetaLineColorAction; break;
+ case MetaActionType::FILLCOLOR: pAction = new MetaFillColorAction; break;
+ case MetaActionType::TEXTCOLOR: pAction = new MetaTextColorAction; break;
+ case MetaActionType::TEXTFILLCOLOR: pAction = new MetaTextFillColorAction; break;
+ case MetaActionType::TEXTLINECOLOR: pAction = new MetaTextLineColorAction; break;
+ case MetaActionType::OVERLINECOLOR: pAction = new MetaOverlineColorAction; break;
+ case MetaActionType::TEXTALIGN: pAction = new MetaTextAlignAction; break;
+ case MetaActionType::MAPMODE: pAction = new MetaMapModeAction; break;
+ case MetaActionType::FONT: pAction = new MetaFontAction; break;
+ case MetaActionType::PUSH: pAction = new MetaPushAction; break;
+ case MetaActionType::POP: pAction = new MetaPopAction; break;
+ case MetaActionType::RASTEROP: pAction = new MetaRasterOpAction; break;
+ case MetaActionType::Transparent: pAction = new MetaTransparentAction; break;
+ case MetaActionType::FLOATTRANSPARENT: pAction = new MetaFloatTransparentAction; break;
+ case MetaActionType::EPS: pAction = new MetaEPSAction; break;
+ case MetaActionType::REFPOINT: pAction = new MetaRefPointAction; break;
+ case MetaActionType::COMMENT: pAction = new MetaCommentAction; break;
+ case MetaActionType::LAYOUTMODE: pAction = new MetaLayoutModeAction; break;
+ case MetaActionType::TEXTLANGUAGE: pAction = new MetaTextLanguageAction; break;
+
+ default:
+ {
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ }
+ break;
+ }
+
+ if( pAction )
+ pAction->Read( rIStm, pData );
+
+ return pAction;
+}
+
+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()
+{
+ return new MetaPixelAction( *this );
+}
+
+void MetaPixelAction::Move( long nHorzMove, long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaPixelAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+void MetaPixelAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ rOStm.WriteUInt32(maColor.mValue);
+}
+
+void MetaPixelAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+ rIStm.ReadUInt32(maColor.mValue);
+}
+
+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()
+{
+ return new MetaPointAction( *this );
+}
+
+void MetaPointAction::Move( long nHorzMove, long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaPointAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+void MetaPointAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+}
+
+void MetaPointAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+}
+
+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,
+ const LineInfo& rLineInfo ) :
+ MetaAction ( MetaActionType::LINE ),
+ maLineInfo ( rLineInfo ),
+ 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()
+{
+ return new MetaLineAction( *this );
+}
+
+void MetaLineAction::Move( long nHorzMove, 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 );
+}
+
+void MetaLineAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+
+ // Version 1
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maStartPt);
+ aSerializer.writePoint(maEndPt);
+ // Version 2
+ WriteLineInfo( rOStm, maLineInfo );
+}
+
+void MetaLineAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+
+ // Version 1
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maStartPt);
+ aSerializer.readPoint(maEndPt);
+
+ // Version 2
+ if( aCompat.GetVersion() >= 2 )
+ {
+ ReadLineInfo( rIStm, maLineInfo );
+ }
+}
+
+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()
+{
+ return new MetaRectAction( *this );
+}
+
+void MetaRectAction::Move( long nHorzMove, long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaRectAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+void MetaRectAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+}
+
+void MetaRectAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+}
+
+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()
+{
+ return new MetaRoundRectAction( *this );
+}
+
+void MetaRoundRectAction::Move( long nHorzMove, 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) );
+}
+
+void MetaRoundRectAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+ rOStm.WriteUInt32( mnHorzRound ).WriteUInt32( mnVertRound );
+}
+
+void MetaRoundRectAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+ rIStm.ReadUInt32( mnHorzRound ).ReadUInt32( mnVertRound );
+}
+
+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()
+{
+ return new MetaEllipseAction( *this );
+}
+
+void MetaEllipseAction::Move( long nHorzMove, long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaEllipseAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+void MetaEllipseAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+}
+
+void MetaEllipseAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+}
+
+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()
+{
+ return new MetaArcAction( *this );
+}
+
+void MetaArcAction::Move( long nHorzMove, 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 );
+}
+
+void MetaArcAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+ aSerializer.writePoint(maStartPt);
+ aSerializer.writePoint(maEndPt);
+}
+
+void MetaArcAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+ aSerializer.readPoint(maStartPt);
+ aSerializer.readPoint(maEndPt);
+}
+
+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()
+{
+ return new MetaPieAction( *this );
+}
+
+void MetaPieAction::Move( long nHorzMove, 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 );
+}
+
+void MetaPieAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+ aSerializer.writePoint(maStartPt);
+ aSerializer.writePoint(maEndPt);
+}
+
+void MetaPieAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+ aSerializer.readPoint(maStartPt);
+ aSerializer.readPoint(maEndPt);
+}
+
+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()
+{
+ return new MetaChordAction( *this );
+}
+
+void MetaChordAction::Move( long nHorzMove, 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 );
+}
+
+void MetaChordAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+ aSerializer.writePoint(maStartPt);
+ aSerializer.writePoint(maEndPt);
+}
+
+void MetaChordAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+ aSerializer.readPoint(maStartPt);
+ aSerializer.readPoint(maEndPt);
+}
+
+MetaPolyLineAction::MetaPolyLineAction() :
+ MetaAction(MetaActionType::POLYLINE)
+{}
+
+MetaPolyLineAction::~MetaPolyLineAction()
+{}
+
+MetaPolyLineAction::MetaPolyLineAction( const tools::Polygon& rPoly ) :
+ MetaAction ( MetaActionType::POLYLINE ),
+ maPoly ( rPoly )
+{}
+
+MetaPolyLineAction::MetaPolyLineAction( const tools::Polygon& rPoly, const LineInfo& rLineInfo ) :
+ MetaAction ( MetaActionType::POLYLINE ),
+ maLineInfo ( rLineInfo ),
+ maPoly ( rPoly )
+{}
+
+void MetaPolyLineAction::Execute( OutputDevice* pOut )
+{
+ if( maLineInfo.IsDefault() )
+ pOut->DrawPolyLine( maPoly );
+ else
+ pOut->DrawPolyLine( maPoly, maLineInfo );
+}
+
+rtl::Reference<MetaAction> MetaPolyLineAction::Clone()
+{
+ return new MetaPolyLineAction( *this );
+}
+
+void MetaPolyLineAction::Move( long nHorzMove, long nVertMove )
+{
+ maPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaPolyLineAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoly( maPoly, fScaleX, fScaleY );
+ ImplScaleLineInfo( maLineInfo, fScaleX, fScaleY );
+}
+
+void MetaPolyLineAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 3);
+
+ tools::Polygon aSimplePoly;
+ maPoly.AdaptiveSubdivide( aSimplePoly );
+
+ WritePolygon( rOStm, aSimplePoly ); // Version 1
+ WriteLineInfo( rOStm, maLineInfo ); // Version 2
+
+ bool bHasPolyFlags = maPoly.HasFlags(); // Version 3
+ rOStm.WriteBool( bHasPolyFlags );
+ if ( bHasPolyFlags )
+ maPoly.Write( rOStm );
+}
+
+void MetaPolyLineAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+
+ // Version 1
+ ReadPolygon( rIStm, maPoly );
+
+ // Version 2
+ if( aCompat.GetVersion() >= 2 )
+ ReadLineInfo( rIStm, maLineInfo );
+ if ( aCompat.GetVersion() >= 3 )
+ {
+ sal_uInt8 bHasPolyFlags(0);
+ rIStm.ReadUChar( bHasPolyFlags );
+ if ( bHasPolyFlags )
+ maPoly.Read( rIStm );
+ }
+}
+
+MetaPolygonAction::MetaPolygonAction() :
+ MetaAction(MetaActionType::POLYGON)
+{}
+
+MetaPolygonAction::~MetaPolygonAction()
+{}
+
+MetaPolygonAction::MetaPolygonAction( const tools::Polygon& rPoly ) :
+ MetaAction ( MetaActionType::POLYGON ),
+ maPoly ( rPoly )
+{}
+
+void MetaPolygonAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawPolygon( maPoly );
+}
+
+rtl::Reference<MetaAction> MetaPolygonAction::Clone()
+{
+ return new MetaPolygonAction( *this );
+}
+
+void MetaPolygonAction::Move( long nHorzMove, long nVertMove )
+{
+ maPoly.Move( nHorzMove, nVertMove );
+}
+
+void MetaPolygonAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoly( maPoly, fScaleX, fScaleY );
+}
+
+void MetaPolygonAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+
+ tools::Polygon aSimplePoly; // Version 1
+ maPoly.AdaptiveSubdivide( aSimplePoly );
+ WritePolygon( rOStm, aSimplePoly );
+
+ bool bHasPolyFlags = maPoly.HasFlags(); // Version 2
+ rOStm.WriteBool( bHasPolyFlags );
+ if ( bHasPolyFlags )
+ maPoly.Write( rOStm );
+}
+
+void MetaPolygonAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+
+ ReadPolygon( rIStm, maPoly ); // Version 1
+
+ if( aCompat.GetVersion() >= 2 ) // Version 2
+ {
+ sal_uInt8 bHasPolyFlags(0);
+ rIStm.ReadUChar( bHasPolyFlags );
+ if ( bHasPolyFlags )
+ maPoly.Read( rIStm );
+ }
+}
+
+MetaPolyPolygonAction::MetaPolyPolygonAction() :
+ MetaAction(MetaActionType::POLYPOLYGON)
+{}
+
+MetaPolyPolygonAction::~MetaPolyPolygonAction()
+{}
+
+MetaPolyPolygonAction::MetaPolyPolygonAction( const tools::PolyPolygon& rPolyPoly ) :
+ MetaAction ( MetaActionType::POLYPOLYGON ),
+ maPolyPoly ( rPolyPoly )
+{}
+
+void MetaPolyPolygonAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawPolyPolygon( maPolyPoly );
+}
+
+rtl::Reference<MetaAction> MetaPolyPolygonAction::Clone()
+{
+ return new MetaPolyPolygonAction( *this );
+}
+
+void MetaPolyPolygonAction::Move( long nHorzMove, 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 );
+}
+
+void MetaPolyPolygonAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+
+ sal_uInt16 nNumberOfComplexPolygons = 0;
+ sal_uInt16 i, nPolyCount = maPolyPoly.Count();
+
+ tools::Polygon aSimplePoly; // Version 1
+ rOStm.WriteUInt16( nPolyCount );
+ for ( i = 0; i < nPolyCount; i++ )
+ {
+ const tools::Polygon& rPoly = maPolyPoly.GetObject( i );
+ if ( rPoly.HasFlags() )
+ nNumberOfComplexPolygons++;
+ rPoly.AdaptiveSubdivide( aSimplePoly );
+ WritePolygon( rOStm, aSimplePoly );
+ }
+
+ rOStm.WriteUInt16( nNumberOfComplexPolygons ); // Version 2
+ for ( i = 0; nNumberOfComplexPolygons && ( i < nPolyCount ); i++ )
+ {
+ const tools::Polygon& rPoly = maPolyPoly.GetObject( i );
+ if ( rPoly.HasFlags() )
+ {
+ rOStm.WriteUInt16( i );
+ rPoly.Write( rOStm );
+
+ nNumberOfComplexPolygons--;
+ }
+ }
+}
+
+void MetaPolyPolygonAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadPolyPolygon( rIStm, maPolyPoly ); // Version 1
+
+ if ( aCompat.GetVersion() >= 2 ) // Version 2
+ {
+ sal_uInt16 nNumberOfComplexPolygons(0);
+ rIStm.ReadUInt16( nNumberOfComplexPolygons );
+ const size_t nMinRecordSize = sizeof(sal_uInt16);
+ const size_t nMaxRecords = rIStm.remainingSize() / nMinRecordSize;
+ if (nNumberOfComplexPolygons > nMaxRecords)
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: " << nMaxRecords <<
+ " max possible entries, but " << nNumberOfComplexPolygons << " claimed, truncating");
+ nNumberOfComplexPolygons = nMaxRecords;
+ }
+ for (sal_uInt16 i = 0; i < nNumberOfComplexPolygons; ++i)
+ {
+ sal_uInt16 nIndex(0);
+ rIStm.ReadUInt16( nIndex );
+ tools::Polygon aPoly;
+ aPoly.Read( rIStm );
+ if (nIndex >= maPolyPoly.Count())
+ {
+ SAL_WARN("vcl.gdi", "svm contains polygon index " << nIndex
+ << " outside possible range " << maPolyPoly.Count());
+ continue;
+ }
+ maPolyPoly.Replace( aPoly, nIndex );
+ }
+ }
+}
+
+MetaTextAction::MetaTextAction() :
+ MetaAction ( MetaActionType::TEXT ),
+ mnIndex ( 0 ),
+ mnLen ( 0 )
+{}
+
+MetaTextAction::~MetaTextAction()
+{}
+
+MetaTextAction::MetaTextAction( const Point& rPt, const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::TEXT ),
+ maPt ( rPt ),
+ maStr ( rStr ),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{}
+
+void MetaTextAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawText( maPt, maStr, mnIndex, mnLen );
+}
+
+rtl::Reference<MetaAction> MetaTextAction::Clone()
+{
+ return new MetaTextAction( *this );
+}
+
+void MetaTextAction::Move( long nHorzMove, long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+void MetaTextAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ rOStm.WriteUniOrByteString( maStr, pData->meActualCharSet );
+ rOStm.WriteUInt16(mnIndex);
+ rOStm.WriteUInt16(mnLen);
+
+ write_uInt16_lenPrefixed_uInt16s_FromOUString(rOStm, maStr); // version 2
+}
+
+void MetaTextAction::Read( SvStream& rIStm, ImplMetaReadData* pData )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+ maStr = rIStm.ReadUniOrByteString(pData->meActualCharSet);
+ sal_uInt16 nTmpIndex(0);
+ rIStm.ReadUInt16(nTmpIndex);
+ mnIndex = nTmpIndex;
+ sal_uInt16 nTmpLen(0);
+ rIStm.ReadUInt16(nTmpLen);
+ mnLen = nTmpLen;
+
+ if ( aCompat.GetVersion() >= 2 ) // Version 2
+ maStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(rIStm);
+}
+
+MetaTextArrayAction::MetaTextArrayAction() :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ mnIndex ( 0 ),
+ mnLen ( 0 )
+{}
+
+MetaTextArrayAction::MetaTextArrayAction( const MetaTextArrayAction& rAction ) :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ maStartPt ( rAction.maStartPt ),
+ maStr ( rAction.maStr ),
+ mnIndex ( rAction.mnIndex ),
+ mnLen ( rAction.mnLen )
+{
+ if( rAction.mpDXAry )
+ {
+ mpDXAry.reset( new long[ mnLen ] );
+ memcpy( mpDXAry.get(), rAction.mpDXAry.get(), mnLen * sizeof( long ) );
+ }
+}
+
+MetaTextArrayAction::MetaTextArrayAction( const Point& rStartPt,
+ const OUString& rStr,
+ const long* pDXAry,
+ sal_Int32 nIndex,
+ sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::TEXTARRAY ),
+ maStartPt ( rStartPt ),
+ maStr ( rStr ),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{
+ const sal_Int32 nAryLen = pDXAry ? mnLen : 0;
+
+ if (nAryLen > 0)
+ {
+ mpDXAry.reset( new long[ nAryLen ] );
+ memcpy( mpDXAry.get(), pDXAry, nAryLen * sizeof(long) );
+ }
+}
+
+MetaTextArrayAction::~MetaTextArrayAction()
+{
+}
+
+void MetaTextArrayAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTextArray( maStartPt, maStr, mpDXAry.get(), mnIndex, mnLen );
+}
+
+rtl::Reference<MetaAction> MetaTextArrayAction::Clone()
+{
+ return new MetaTextArrayAction( *this );
+}
+
+void MetaTextArrayAction::Move( long nHorzMove, long nVertMove )
+{
+ maStartPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextArrayAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maStartPt, fScaleX, fScaleY );
+
+ if ( mpDXAry && mnLen )
+ {
+ for ( sal_uInt16 i = 0, nCount = mnLen; i < nCount; i++ )
+ mpDXAry[ i ] = FRound( mpDXAry[ i ] * fabs(fScaleX) );
+ }
+}
+
+void MetaTextArrayAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ const sal_Int32 nAryLen = mpDXAry ? mnLen : 0;
+
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maStartPt);
+ rOStm.WriteUniOrByteString( maStr, pData->meActualCharSet );
+ rOStm.WriteUInt16(mnIndex);
+ rOStm.WriteUInt16(mnLen);
+ rOStm.WriteInt32(nAryLen);
+
+ for (sal_Int32 i = 0; i < nAryLen; ++i)
+ rOStm.WriteInt32( mpDXAry[ i ] );
+
+ write_uInt16_lenPrefixed_uInt16s_FromOUString(rOStm, maStr); // version 2
+}
+
+void MetaTextArrayAction::Read( SvStream& rIStm, ImplMetaReadData* pData )
+{
+ mpDXAry.reset();
+
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maStartPt);
+ maStr = rIStm.ReadUniOrByteString(pData->meActualCharSet);
+ sal_uInt16 nTmpIndex(0);
+ rIStm.ReadUInt16(nTmpIndex);
+ mnIndex = nTmpIndex;
+ sal_uInt16 nTmpLen(0);
+ rIStm.ReadUInt16(nTmpLen);
+ mnLen = nTmpLen;
+ sal_Int32 nAryLen(0);
+ rIStm.ReadInt32(nAryLen);
+
+ if (mnLen > maStr.getLength() - mnIndex)
+ {
+ mnIndex = 0;
+ mpDXAry = nullptr;
+ return;
+ }
+
+ if( nAryLen )
+ {
+ // #i9762#, #106172# Ensure that DX array is at least mnLen entries long
+ if ( mnLen >= nAryLen )
+ {
+ mpDXAry.reset( new (std::nothrow)long[ mnLen ] );
+ if ( mpDXAry )
+ {
+ sal_Int32 i;
+ sal_Int32 val;
+ for( i = 0; i < nAryLen; i++ )
+ {
+ rIStm.ReadInt32( val);
+ mpDXAry[ i ] = val;
+ }
+ // #106172# setup remainder
+ for( ; i < mnLen; i++ )
+ mpDXAry[ i ] = 0;
+ }
+ }
+ else
+ {
+ mpDXAry = nullptr;
+ return;
+ }
+ }
+ else
+ mpDXAry = nullptr;
+
+ if ( aCompat.GetVersion() >= 2 ) // Version 2
+ {
+ maStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(rIStm);
+
+ if ( mnIndex + mnLen > maStr.getLength() )
+ {
+ mnIndex = 0;
+ mpDXAry.reset();
+ }
+ }
+}
+
+MetaStretchTextAction::MetaStretchTextAction() :
+ MetaAction ( MetaActionType::STRETCHTEXT ),
+ mnWidth ( 0 ),
+ mnIndex ( 0 ),
+ mnLen ( 0 )
+{}
+
+MetaStretchTextAction::~MetaStretchTextAction()
+{}
+
+MetaStretchTextAction::MetaStretchTextAction( const Point& rPt, sal_uInt32 nWidth,
+ const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) :
+ MetaAction ( MetaActionType::STRETCHTEXT ),
+ maPt ( rPt ),
+ maStr ( rStr ),
+ mnWidth ( nWidth ),
+ mnIndex ( nIndex ),
+ mnLen ( nLen )
+{}
+
+void MetaStretchTextAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawStretchText( maPt, mnWidth, maStr, mnIndex, mnLen );
+}
+
+rtl::Reference<MetaAction> MetaStretchTextAction::Clone()
+{
+ return new MetaStretchTextAction( *this );
+}
+
+void MetaStretchTextAction::Move( long nHorzMove, 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) ));
+}
+
+void MetaStretchTextAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ rOStm.WriteUniOrByteString( maStr, pData->meActualCharSet );
+ rOStm.WriteUInt32( mnWidth );
+ rOStm.WriteUInt16( mnIndex );
+ rOStm.WriteUInt16( mnLen );
+
+ write_uInt16_lenPrefixed_uInt16s_FromOUString(rOStm, maStr); // version 2
+}
+
+void MetaStretchTextAction::Read( SvStream& rIStm, ImplMetaReadData* pData )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+ maStr = rIStm.ReadUniOrByteString(pData->meActualCharSet);
+ rIStm.ReadUInt32( mnWidth );
+ sal_uInt16 nTmpIndex(0);
+ rIStm.ReadUInt16(nTmpIndex);
+ mnIndex = nTmpIndex;
+ sal_uInt16 nTmpLen(0);
+ rIStm.ReadUInt16(nTmpLen);
+ mnLen = nTmpLen;
+
+ if ( aCompat.GetVersion() >= 2 ) // Version 2
+ maStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(rIStm);
+}
+
+MetaTextRectAction::MetaTextRectAction() :
+ MetaAction ( MetaActionType::TEXTRECT ),
+ mnStyle ( DrawTextFlags::NONE )
+{}
+
+MetaTextRectAction::~MetaTextRectAction()
+{}
+
+MetaTextRectAction::MetaTextRectAction( const tools::Rectangle& rRect,
+ const OUString& rStr, DrawTextFlags nStyle ) :
+ MetaAction ( MetaActionType::TEXTRECT ),
+ maRect ( rRect ),
+ maStr ( rStr ),
+ mnStyle ( nStyle )
+{}
+
+void MetaTextRectAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawText( maRect, maStr, mnStyle );
+}
+
+rtl::Reference<MetaAction> MetaTextRectAction::Clone()
+{
+ return new MetaTextRectAction( *this );
+}
+
+void MetaTextRectAction::Move( long nHorzMove, long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextRectAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+void MetaTextRectAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+ rOStm.WriteUniOrByteString( maStr, pData->meActualCharSet );
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(mnStyle) );
+
+ write_uInt16_lenPrefixed_uInt16s_FromOUString(rOStm, maStr); // version 2
+}
+
+void MetaTextRectAction::Read( SvStream& rIStm, ImplMetaReadData* pData )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+ maStr = rIStm.ReadUniOrByteString(pData->meActualCharSet);
+ sal_uInt16 nTmp;
+ rIStm .ReadUInt16( nTmp );
+ mnStyle = static_cast<DrawTextFlags>(nTmp);
+
+ if ( aCompat.GetVersion() >= 2 ) // Version 2
+ maStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(rIStm);
+}
+
+MetaTextLineAction::MetaTextLineAction() :
+ MetaAction ( MetaActionType::TEXTLINE ),
+ mnWidth ( 0 ),
+ meStrikeout ( STRIKEOUT_NONE ),
+ meUnderline ( LINESTYLE_NONE ),
+ meOverline ( LINESTYLE_NONE )
+{}
+
+MetaTextLineAction::~MetaTextLineAction()
+{}
+
+MetaTextLineAction::MetaTextLineAction( const Point& rPos, 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 )
+{
+ pOut->DrawTextLine( maPos, mnWidth, meStrikeout, meUnderline, meOverline );
+}
+
+rtl::Reference<MetaAction> MetaTextLineAction::Clone()
+{
+ return new MetaTextLineAction( *this );
+}
+
+void MetaTextLineAction::Move( long nHorzMove, long nVertMove )
+{
+ maPos.Move( nHorzMove, nVertMove );
+}
+
+void MetaTextLineAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPos, fScaleX, fScaleY );
+ mnWidth = FRound( mnWidth * fabs(fScaleX) );
+}
+
+void MetaTextLineAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 2);
+
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPos);
+
+ rOStm.WriteInt32( mnWidth );
+ rOStm.WriteUInt32( meStrikeout );
+ rOStm.WriteUInt32( meUnderline );
+ // new in version 2
+ rOStm.WriteUInt32( meOverline );
+}
+
+void MetaTextLineAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+
+ sal_Int32 nTempWidth(0);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPos);
+ rIStm.ReadInt32(nTempWidth);
+ mnWidth = nTempWidth;
+
+ sal_uInt32 nTempStrikeout(0);
+ rIStm.ReadUInt32( nTempStrikeout );
+ meStrikeout = static_cast<FontStrikeout>(nTempStrikeout);
+
+ sal_uInt32 nTempUnderline(0);
+ rIStm.ReadUInt32( nTempUnderline );
+ meUnderline = static_cast<FontLineStyle>(nTempUnderline);
+
+ if (aCompat.GetVersion() >= 2)
+ {
+ sal_uInt32 nTempOverline(0);
+ rIStm.ReadUInt32(nTempOverline);
+ meOverline = static_cast<FontLineStyle>(nTempOverline);
+ }
+}
+
+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()
+{
+ return new MetaBmpAction( *this );
+}
+
+void MetaBmpAction::Move( long nHorzMove, long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+void MetaBmpAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmp )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIB(maBmp, rOStm, false, true);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ }
+}
+
+void MetaBmpAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIB(maBmp, rIStm, true);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+}
+
+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 )
+{}
+
+void MetaBmpScaleAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawBitmap( maPt, maSz, maBmp );
+}
+
+rtl::Reference<MetaAction> MetaBmpScaleAction::Clone()
+{
+ return new MetaBmpScaleAction( *this );
+}
+
+void MetaBmpScaleAction::Move( long nHorzMove, 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();
+}
+
+void MetaBmpScaleAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmp )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIB(maBmp, rOStm, false, true);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ aSerializer.writeSize(maSz);
+
+ }
+}
+
+void MetaBmpScaleAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIB(maBmp, rIStm, true);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+ aSerializer.readSize(maSz);
+}
+
+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 )
+{
+ pOut->DrawBitmap( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmp );
+}
+
+rtl::Reference<MetaAction> MetaBmpScalePartAction::Clone()
+{
+ return new MetaBmpScalePartAction( *this );
+}
+
+void MetaBmpScalePartAction::Move( long nHorzMove, 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();
+}
+
+void MetaBmpScalePartAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmp )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIB(maBmp, rOStm, false, true);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maDstPt);
+ aSerializer.writeSize(maDstSz);
+ aSerializer.writePoint(maSrcPt);
+ aSerializer.writeSize(maSrcSz);
+
+ }
+}
+
+void MetaBmpScalePartAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIB(maBmp, rIStm, true);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maDstPt);
+ aSerializer.readSize(maDstSz);
+ aSerializer.readPoint(maSrcPt);
+ aSerializer.readSize(maSrcSz);
+}
+
+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()
+{
+ return new MetaBmpExAction( *this );
+}
+
+void MetaBmpExAction::Move( long nHorzMove, long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaBmpExAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+void MetaBmpExAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmpEx.GetBitmap() )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIBBitmapEx(maBmpEx, rOStm);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ }
+}
+
+void MetaBmpExAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIBBitmapEx(maBmpEx, rIStm);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+}
+
+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 )
+{
+ pOut->DrawBitmapEx( maPt, maSz, maBmpEx );
+}
+
+rtl::Reference<MetaAction> MetaBmpExScaleAction::Clone()
+{
+ return new MetaBmpExScaleAction( *this );
+}
+
+void MetaBmpExScaleAction::Move( long nHorzMove, 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();
+}
+
+void MetaBmpExScaleAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmpEx.GetBitmap() )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIBBitmapEx(maBmpEx, rOStm);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ aSerializer.writeSize(maSz);
+ }
+}
+
+void MetaBmpExScaleAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIBBitmapEx(maBmpEx, rIStm);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+ aSerializer.readSize(maSz);
+}
+
+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 )
+{
+ pOut->DrawBitmapEx( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmpEx );
+}
+
+rtl::Reference<MetaAction> MetaBmpExScalePartAction::Clone()
+{
+ return new MetaBmpExScalePartAction( *this );
+}
+
+void MetaBmpExScalePartAction::Move( long nHorzMove, 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();
+}
+
+void MetaBmpExScalePartAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmpEx.GetBitmap() )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIBBitmapEx(maBmpEx, rOStm);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maDstPt);
+ aSerializer.writeSize(maDstSz);
+ aSerializer.writePoint(maSrcPt);
+ aSerializer.writeSize(maSrcSz);
+ }
+}
+
+void MetaBmpExScalePartAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIBBitmapEx(maBmpEx, rIStm);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maDstPt);
+ aSerializer.readSize(maDstSz);
+ aSerializer.readPoint(maSrcPt);
+ aSerializer.readSize(maSrcSz);
+}
+
+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()
+{
+ return new MetaMaskAction( *this );
+}
+
+void MetaMaskAction::Move( long nHorzMove, long nVertMove )
+{
+ maPt.Move( nHorzMove, nVertMove );
+}
+
+void MetaMaskAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScalePoint( maPt, fScaleX, fScaleY );
+}
+
+void MetaMaskAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmp )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIB(maBmp, rOStm, false, true);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ }
+}
+
+void MetaMaskAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIB(maBmp, rIStm, true);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+}
+
+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 )
+{
+ pOut->DrawMask( maPt, maSz, maBmp, maColor );
+}
+
+rtl::Reference<MetaAction> MetaMaskScaleAction::Clone()
+{
+ return new MetaMaskScaleAction( *this );
+}
+
+void MetaMaskScaleAction::Move( long nHorzMove, 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();
+}
+
+void MetaMaskScaleAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmp )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIB(maBmp, rOStm, false, true);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPt);
+ aSerializer.writeSize(maSz);
+ }
+}
+
+void MetaMaskScaleAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIB(maBmp, rIStm, true);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPt);
+ aSerializer.readSize(maSz);
+}
+
+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 )
+{
+ pOut->DrawMask( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmp, maColor, MetaActionType::MASKSCALE );
+}
+
+rtl::Reference<MetaAction> MetaMaskScalePartAction::Clone()
+{
+ return new MetaMaskScalePartAction( *this );
+}
+
+void MetaMaskScalePartAction::Move( long nHorzMove, 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();
+}
+
+void MetaMaskScalePartAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ if( !!maBmp )
+ {
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteDIB(maBmp, rOStm, false, true);
+ rOStm.WriteUInt32(maColor.mValue);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maDstPt);
+ aSerializer.writeSize(maDstSz);
+ aSerializer.writePoint(maSrcPt);
+ aSerializer.writeSize(maSrcSz);
+ }
+}
+
+void MetaMaskScalePartAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadDIB(maBmp, rIStm, true);
+ rIStm.ReadUInt32(maColor.mValue);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maDstPt);
+ aSerializer.readSize(maDstSz);
+ aSerializer.readPoint(maSrcPt);
+ aSerializer.readSize(maSrcSz);
+}
+
+MetaGradientAction::MetaGradientAction() :
+ MetaAction(MetaActionType::GRADIENT)
+{}
+
+MetaGradientAction::~MetaGradientAction()
+{}
+
+MetaGradientAction::MetaGradientAction( const tools::Rectangle& rRect, const Gradient& rGradient ) :
+ MetaAction ( MetaActionType::GRADIENT ),
+ maRect ( rRect ),
+ maGradient ( rGradient )
+{}
+
+void MetaGradientAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawGradient( maRect, maGradient );
+}
+
+rtl::Reference<MetaAction> MetaGradientAction::Clone()
+{
+ return new MetaGradientAction( *this );
+}
+
+void MetaGradientAction::Move( long nHorzMove, long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaGradientAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+void MetaGradientAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+ aSerializer.writeGradient(maGradient);
+}
+
+void MetaGradientAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+ aSerializer.readGradient(maGradient);
+}
+
+MetaGradientExAction::MetaGradientExAction() :
+ MetaAction ( MetaActionType::GRADIENTEX )
+{}
+
+MetaGradientExAction::MetaGradientExAction( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient ) :
+ MetaAction ( MetaActionType::GRADIENTEX ),
+ maPolyPoly ( rPolyPoly ),
+ maGradient ( rGradient )
+{}
+
+MetaGradientExAction::~MetaGradientExAction()
+{}
+
+void MetaGradientExAction::Execute( OutputDevice* pOut )
+{
+ if( pOut->GetConnectMetaFile() )
+ {
+ pOut->GetConnectMetaFile()->AddAction( this );
+ }
+}
+
+rtl::Reference<MetaAction> MetaGradientExAction::Clone()
+{
+ return new MetaGradientExAction( *this );
+}
+
+void MetaGradientExAction::Move( long nHorzMove, 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 );
+}
+
+void MetaGradientExAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ // #i105373# see comment at MetaTransparentAction::Write
+ tools::PolyPolygon aNoCurvePolyPolygon;
+ maPolyPoly.AdaptiveSubdivide(aNoCurvePolyPolygon);
+
+ WritePolyPolygon( rOStm, aNoCurvePolyPolygon );
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeGradient(maGradient);
+}
+
+void MetaGradientExAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadPolyPolygon( rIStm, maPolyPoly );
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readGradient(maGradient);
+}
+
+MetaHatchAction::MetaHatchAction() :
+ MetaAction(MetaActionType::HATCH)
+{}
+
+MetaHatchAction::~MetaHatchAction()
+{}
+
+MetaHatchAction::MetaHatchAction( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch ) :
+ MetaAction ( MetaActionType::HATCH ),
+ maPolyPoly ( rPolyPoly ),
+ maHatch ( rHatch )
+{}
+
+void MetaHatchAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawHatch( maPolyPoly, maHatch );
+}
+
+rtl::Reference<MetaAction> MetaHatchAction::Clone()
+{
+ return new MetaHatchAction( *this );
+}
+
+void MetaHatchAction::Move( long nHorzMove, 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 );
+}
+
+void MetaHatchAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ // #i105373# see comment at MetaTransparentAction::Write
+ tools::PolyPolygon aNoCurvePolyPolygon;
+ maPolyPoly.AdaptiveSubdivide(aNoCurvePolyPolygon);
+
+ WritePolyPolygon( rOStm, aNoCurvePolyPolygon );
+ WriteHatch( rOStm, maHatch );
+}
+
+void MetaHatchAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadPolyPolygon( rIStm, maPolyPoly );
+ ReadHatch( rIStm, maHatch );
+}
+
+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()
+{
+ return new MetaWallpaperAction( *this );
+}
+
+void MetaWallpaperAction::Move( long nHorzMove, long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaWallpaperAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+void MetaWallpaperAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ WriteWallpaper( rOStm, maWallpaper );
+}
+
+void MetaWallpaperAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadWallpaper( rIStm, maWallpaper );
+}
+
+MetaClipRegionAction::MetaClipRegionAction() :
+ MetaAction ( MetaActionType::CLIPREGION ),
+ mbClip ( false )
+{}
+
+MetaClipRegionAction::~MetaClipRegionAction()
+{}
+
+MetaClipRegionAction::MetaClipRegionAction( const vcl::Region& rRegion, bool bClip ) :
+ MetaAction ( MetaActionType::CLIPREGION ),
+ maRegion ( rRegion ),
+ mbClip ( bClip )
+{}
+
+void MetaClipRegionAction::Execute( OutputDevice* pOut )
+{
+ if( mbClip )
+ pOut->SetClipRegion( maRegion );
+ else
+ pOut->SetClipRegion();
+}
+
+rtl::Reference<MetaAction> MetaClipRegionAction::Clone()
+{
+ return new MetaClipRegionAction( *this );
+}
+
+void MetaClipRegionAction::Move( long nHorzMove, long nVertMove )
+{
+ maRegion.Move( nHorzMove, nVertMove );
+}
+
+void MetaClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ maRegion.Scale( fScaleX, fScaleY );
+}
+
+void MetaClipRegionAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ WriteRegion( rOStm, maRegion );
+ rOStm.WriteBool( mbClip );
+}
+
+void MetaClipRegionAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadRegion( rIStm, maRegion );
+ rIStm.ReadCharAsBool( mbClip );
+}
+
+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()
+{
+ return new MetaISectRectClipRegionAction( *this );
+}
+
+void MetaISectRectClipRegionAction::Move( long nHorzMove, long nVertMove )
+{
+ maRect.Move( nHorzMove, nVertMove );
+}
+
+void MetaISectRectClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ ImplScaleRect( maRect, fScaleX, fScaleY );
+}
+
+void MetaISectRectClipRegionAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeRectangle(maRect);
+}
+
+void MetaISectRectClipRegionAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readRectangle(maRect);
+}
+
+MetaISectRegionClipRegionAction::MetaISectRegionClipRegionAction() :
+ MetaAction(MetaActionType::ISECTREGIONCLIPREGION)
+{}
+
+MetaISectRegionClipRegionAction::~MetaISectRegionClipRegionAction()
+{}
+
+MetaISectRegionClipRegionAction::MetaISectRegionClipRegionAction( const vcl::Region& rRegion ) :
+ MetaAction ( MetaActionType::ISECTREGIONCLIPREGION ),
+ maRegion ( rRegion )
+{
+}
+
+void MetaISectRegionClipRegionAction::Execute( OutputDevice* pOut )
+{
+ pOut->IntersectClipRegion( maRegion );
+}
+
+rtl::Reference<MetaAction> MetaISectRegionClipRegionAction::Clone()
+{
+ return new MetaISectRegionClipRegionAction( *this );
+}
+
+void MetaISectRegionClipRegionAction::Move( long nHorzMove, long nVertMove )
+{
+ maRegion.Move( nHorzMove, nVertMove );
+}
+
+void MetaISectRegionClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ maRegion.Scale( fScaleX, fScaleY );
+}
+
+void MetaISectRegionClipRegionAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteRegion( rOStm, maRegion );
+}
+
+void MetaISectRegionClipRegionAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadRegion( rIStm, maRegion );
+}
+
+MetaMoveClipRegionAction::MetaMoveClipRegionAction() :
+ MetaAction ( MetaActionType::MOVECLIPREGION ),
+ mnHorzMove ( 0 ),
+ mnVertMove ( 0 )
+{}
+
+MetaMoveClipRegionAction::~MetaMoveClipRegionAction()
+{}
+
+MetaMoveClipRegionAction::MetaMoveClipRegionAction( long nHorzMove, long nVertMove ) :
+ MetaAction ( MetaActionType::MOVECLIPREGION ),
+ mnHorzMove ( nHorzMove ),
+ mnVertMove ( nVertMove )
+{}
+
+void MetaMoveClipRegionAction::Execute( OutputDevice* pOut )
+{
+ pOut->MoveClipRegion( mnHorzMove, mnVertMove );
+}
+
+rtl::Reference<MetaAction> MetaMoveClipRegionAction::Clone()
+{
+ return new MetaMoveClipRegionAction( *this );
+}
+
+void MetaMoveClipRegionAction::Scale( double fScaleX, double fScaleY )
+{
+ mnHorzMove = FRound( mnHorzMove * fScaleX );
+ mnVertMove = FRound( mnVertMove * fScaleY );
+}
+
+void MetaMoveClipRegionAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteInt32( mnHorzMove ).WriteInt32( mnVertMove );
+}
+
+void MetaMoveClipRegionAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ sal_Int32 nTmpHM(0), nTmpVM(0);
+ rIStm.ReadInt32( nTmpHM ).ReadInt32( nTmpVM );
+ mnHorzMove = nTmpHM;
+ mnVertMove = nTmpVM;
+}
+
+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()
+{
+ return new MetaLineColorAction( *this );
+}
+
+void MetaLineColorAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt32(maColor.mValue);
+ rOStm.WriteBool( mbSet );
+}
+
+void MetaLineColorAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt32(maColor.mValue);
+ rIStm.ReadCharAsBool( mbSet );
+}
+
+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()
+{
+ return new MetaFillColorAction( *this );
+}
+
+void MetaFillColorAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt32(maColor.mValue);
+ rOStm.WriteBool( mbSet );
+}
+
+void MetaFillColorAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt32(maColor.mValue);
+ rIStm.ReadCharAsBool( mbSet );
+}
+
+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()
+{
+ return new MetaTextColorAction( *this );
+}
+
+void MetaTextColorAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt32(maColor.mValue);
+}
+
+void MetaTextColorAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt32(maColor.mValue);
+}
+
+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()
+{
+ return new MetaTextFillColorAction( *this );
+}
+
+void MetaTextFillColorAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt32(maColor.mValue);
+ rOStm.WriteBool( mbSet );
+}
+
+void MetaTextFillColorAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt32(maColor.mValue);
+ rIStm.ReadCharAsBool( mbSet );
+}
+
+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()
+{
+ return new MetaTextLineColorAction( *this );
+}
+
+void MetaTextLineColorAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt32(maColor.mValue);
+ rOStm.WriteBool( mbSet );
+}
+
+void MetaTextLineColorAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt32(maColor.mValue);
+ rIStm.ReadCharAsBool( mbSet );
+}
+
+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()
+{
+ return new MetaOverlineColorAction( *this );
+}
+
+void MetaOverlineColorAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt32(maColor.mValue);
+ rOStm.WriteBool( mbSet );
+}
+
+void MetaOverlineColorAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt32(maColor.mValue);
+ rIStm.ReadCharAsBool( mbSet );
+}
+
+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()
+{
+ return new MetaTextAlignAction( *this );
+}
+
+void MetaTextAlignAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt16( maAlign );
+}
+
+void MetaTextAlignAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ sal_uInt16 nTmp16(0);
+
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt16( nTmp16 ); maAlign = static_cast<TextAlign>(nTmp16);
+}
+
+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()
+{
+ return new MetaMapModeAction( *this );
+}
+
+void MetaMapModeAction::Scale( double fScaleX, double fScaleY )
+{
+ Point aPoint( maMapMode.GetOrigin() );
+
+ ImplScalePoint( aPoint, fScaleX, fScaleY );
+ maMapMode.SetOrigin( aPoint );
+}
+
+void MetaMapModeAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteMapMode( rOStm, maMapMode );
+}
+
+void MetaMapModeAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadMapMode( rIStm, maMapMode );
+}
+
+MetaFontAction::MetaFontAction() :
+ MetaAction(MetaActionType::FONT)
+{}
+
+MetaFontAction::~MetaFontAction()
+{}
+
+MetaFontAction::MetaFontAction( const vcl::Font& rFont ) :
+ MetaAction ( MetaActionType::FONT ),
+ maFont ( rFont )
+{
+ // #96876: because RTL_TEXTENCODING_SYMBOL is often set at the StarSymbol 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 ( IsStarSymbol( maFont.GetFamilyName() )
+ && ( maFont.GetCharSet() != RTL_TEXTENCODING_UNICODE ) )
+ {
+ maFont.SetCharSet( RTL_TEXTENCODING_UNICODE );
+ }
+}
+
+void MetaFontAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetFont( maFont );
+}
+
+rtl::Reference<MetaAction> MetaFontAction::Clone()
+{
+ 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 );
+}
+
+void MetaFontAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ WriteFont( rOStm, maFont );
+ pData->meActualCharSet = maFont.GetCharSet();
+ if ( pData->meActualCharSet == RTL_TEXTENCODING_DONTKNOW )
+ pData->meActualCharSet = osl_getThreadTextEncoding();
+}
+
+void MetaFontAction::Read( SvStream& rIStm, ImplMetaReadData* pData )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadFont( rIStm, maFont );
+ pData->meActualCharSet = maFont.GetCharSet();
+ if ( pData->meActualCharSet == RTL_TEXTENCODING_DONTKNOW )
+ pData->meActualCharSet = osl_getThreadTextEncoding();
+}
+
+MetaPushAction::MetaPushAction() :
+ MetaAction ( MetaActionType::PUSH ),
+ mnFlags ( PushFlags::NONE )
+{}
+
+MetaPushAction::~MetaPushAction()
+{}
+
+MetaPushAction::MetaPushAction( PushFlags nFlags ) :
+ MetaAction ( MetaActionType::PUSH ),
+ mnFlags ( nFlags )
+{}
+
+void MetaPushAction::Execute( OutputDevice* pOut )
+{
+ pOut->Push( mnFlags );
+}
+
+rtl::Reference<MetaAction> MetaPushAction::Clone()
+{
+ return new MetaPushAction( *this );
+}
+
+void MetaPushAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(mnFlags) );
+}
+
+void MetaPushAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ sal_uInt16 tmp;
+ rIStm.ReadUInt16( tmp );
+ mnFlags = static_cast<PushFlags>(tmp);
+}
+
+MetaPopAction::MetaPopAction() :
+ MetaAction(MetaActionType::POP)
+{}
+
+MetaPopAction::~MetaPopAction()
+{}
+
+void MetaPopAction::Execute( OutputDevice* pOut )
+{
+ pOut->Pop();
+}
+
+rtl::Reference<MetaAction> MetaPopAction::Clone()
+{
+ return new MetaPopAction( *this );
+}
+
+void MetaPopAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+}
+
+void MetaPopAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+}
+
+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()
+{
+ return new MetaRasterOpAction( *this );
+}
+
+void MetaRasterOpAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(meRasterOp) );
+}
+
+void MetaRasterOpAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ sal_uInt16 nTmp16(0);
+
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ rIStm.ReadUInt16( nTmp16 ); meRasterOp = static_cast<RasterOp>(nTmp16);
+}
+
+MetaTransparentAction::MetaTransparentAction() :
+ MetaAction ( MetaActionType::Transparent ),
+ mnTransPercent ( 0 )
+{}
+
+MetaTransparentAction::~MetaTransparentAction()
+{}
+
+MetaTransparentAction::MetaTransparentAction( const tools::PolyPolygon& rPolyPoly, sal_uInt16 nTransPercent ) :
+ MetaAction ( MetaActionType::Transparent ),
+ maPolyPoly ( rPolyPoly ),
+ mnTransPercent ( nTransPercent )
+{}
+
+void MetaTransparentAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTransparent( maPolyPoly, mnTransPercent );
+}
+
+rtl::Reference<MetaAction> MetaTransparentAction::Clone()
+{
+ return new MetaTransparentAction( *this );
+}
+
+void MetaTransparentAction::Move( long nHorzMove, 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 );
+}
+
+void MetaTransparentAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ // #i105373# The tools::PolyPolygon in this action may be a curve; this
+ // was ignored until now what is an error. To make older office
+ // versions work with MetaFiles, i opt for applying AdaptiveSubdivide
+ // to the PolyPolygon.
+ // The alternative would be to really write the curve information
+ // like in MetaPolyPolygonAction::Write (where someone extended it
+ // correctly, but not here :-( ).
+ // The golden solution would be to combine both, but i think it's
+ // not necessary; a good subdivision will be sufficient.
+ tools::PolyPolygon aNoCurvePolyPolygon;
+ maPolyPoly.AdaptiveSubdivide(aNoCurvePolyPolygon);
+
+ WritePolyPolygon( rOStm, aNoCurvePolyPolygon );
+ rOStm.WriteUInt16( mnTransPercent );
+}
+
+void MetaTransparentAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadPolyPolygon( rIStm, maPolyPoly );
+ rIStm.ReadUInt16( mnTransPercent );
+}
+
+MetaFloatTransparentAction::MetaFloatTransparentAction() :
+ MetaAction(MetaActionType::FLOATTRANSPARENT)
+{}
+
+MetaFloatTransparentAction::~MetaFloatTransparentAction()
+{}
+
+MetaFloatTransparentAction::MetaFloatTransparentAction( const GDIMetaFile& rMtf, const Point& rPos,
+ const Size& rSize, const Gradient& rGradient ) :
+ MetaAction ( MetaActionType::FLOATTRANSPARENT ),
+ maMtf ( rMtf ),
+ maPoint ( rPos ),
+ maSize ( rSize ),
+ maGradient ( rGradient )
+{}
+
+void MetaFloatTransparentAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawTransparent( maMtf, maPoint, maSize, maGradient );
+}
+
+rtl::Reference<MetaAction> MetaFloatTransparentAction::Clone()
+{
+ return new MetaFloatTransparentAction( *this );
+}
+
+void MetaFloatTransparentAction::Move( long nHorzMove, 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::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ maMtf.Write( rOStm );
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maPoint);
+ aSerializer.writeSize(maSize);
+ aSerializer.writeGradient(maGradient);
+}
+
+void MetaFloatTransparentAction::Read(SvStream& rIStm, ImplMetaReadData* pData)
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ ReadGDIMetaFile(rIStm, maMtf, pData);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maPoint);
+ aSerializer.readSize(maSize);
+ aSerializer.readGradient(maGradient);
+}
+
+MetaEPSAction::MetaEPSAction() :
+ MetaAction(MetaActionType::EPS)
+{}
+
+MetaEPSAction::~MetaEPSAction()
+{}
+
+MetaEPSAction::MetaEPSAction( const Point& rPoint, const Size& rSize,
+ const GfxLink& rGfxLink, const GDIMetaFile& rSubst ) :
+ MetaAction ( MetaActionType::EPS ),
+ maGfxLink ( rGfxLink ),
+ maSubst ( rSubst ),
+ maPoint ( rPoint ),
+ maSize ( rSize )
+{}
+
+void MetaEPSAction::Execute( OutputDevice* pOut )
+{
+ pOut->DrawEPS( maPoint, maSize, maGfxLink, &maSubst );
+}
+
+rtl::Reference<MetaAction> MetaEPSAction::Clone()
+{
+ return new MetaEPSAction( *this );
+}
+
+void MetaEPSAction::Move( long nHorzMove, 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();
+}
+
+void MetaEPSAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writeGfxLink(maGfxLink);
+ aSerializer.writePoint(maPoint);
+ aSerializer.writeSize(maSize);
+ maSubst.Write( rOStm );
+}
+
+void MetaEPSAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readGfxLink(maGfxLink);
+ aSerializer.readPoint(maPoint);
+ aSerializer.readSize(maSize);
+ ReadGDIMetaFile( rIStm, maSubst );
+}
+
+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()
+{
+ return new MetaRefPointAction( *this );
+}
+
+void MetaRefPointAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+
+ TypeSerializer aSerializer(rOStm);
+ aSerializer.writePoint(maRefPoint);
+ rOStm.WriteBool( mbSet );
+}
+
+void MetaRefPointAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(maRefPoint);
+ rIStm.ReadCharAsBool( mbSet );
+}
+
+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( const OString& rComment, sal_Int32 nValue, const sal_uInt8* pData, sal_uInt32 nDataSize ) :
+ MetaAction ( MetaActionType::COMMENT ),
+ maComment ( rComment ),
+ 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()
+{
+ return new MetaCommentAction( *this );
+}
+
+void MetaCommentAction::Move( long nXMove, long nYMove )
+{
+ if ( nXMove || nYMove )
+ {
+ if ( mnDataSize && mpData )
+ {
+ 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 );
+
+ 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 ) )
+ {
+ if ( mnDataSize && mpData )
+ {
+ 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() );
+ }
+ }
+ }
+}
+
+void MetaCommentAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ write_uInt16_lenPrefixed_uInt8s_FromOString(rOStm, maComment);
+ rOStm.WriteInt32( mnValue ).WriteUInt32( mnDataSize );
+
+ if ( mnDataSize )
+ rOStm.WriteBytes( mpData.get(), mnDataSize );
+}
+
+void MetaCommentAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ maComment = read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm);
+ rIStm.ReadInt32( mnValue ).ReadUInt32( mnDataSize );
+
+ if (mnDataSize > rIStm.remainingSize())
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: " << rIStm.remainingSize() <<
+ " available data, but " << mnDataSize << " claimed, truncating");
+ mnDataSize = rIStm.remainingSize();
+ }
+
+ SAL_INFO("vcl.gdi", "MetaCommentAction::Read " << maComment);
+
+ mpData.reset();
+
+ if( mnDataSize )
+ {
+ mpData.reset(new sal_uInt8[ mnDataSize ]);
+ rIStm.ReadBytes(mpData.get(), mnDataSize);
+ }
+}
+
+MetaLayoutModeAction::MetaLayoutModeAction() :
+ MetaAction ( MetaActionType::LAYOUTMODE ),
+ mnLayoutMode( ComplexTextLayoutFlags::Default )
+{}
+
+MetaLayoutModeAction::~MetaLayoutModeAction()
+{}
+
+MetaLayoutModeAction::MetaLayoutModeAction( ComplexTextLayoutFlags nLayoutMode ) :
+ MetaAction ( MetaActionType::LAYOUTMODE ),
+ mnLayoutMode( nLayoutMode )
+{}
+
+void MetaLayoutModeAction::Execute( OutputDevice* pOut )
+{
+ pOut->SetLayoutMode( mnLayoutMode );
+}
+
+rtl::Reference<MetaAction> MetaLayoutModeAction::Clone()
+{
+ return new MetaLayoutModeAction( *this );
+}
+
+void MetaLayoutModeAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt32( static_cast<sal_uInt32>(mnLayoutMode) );
+}
+
+void MetaLayoutModeAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ sal_uInt32 tmp;
+ rIStm.ReadUInt32( tmp );
+ mnLayoutMode = static_cast<ComplexTextLayoutFlags>(tmp);
+}
+
+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()
+{
+ return new MetaTextLanguageAction( *this );
+}
+
+void MetaTextLanguageAction::Write( SvStream& rOStm, ImplMetaWriteData* pData )
+{
+ MetaAction::Write(rOStm, pData);
+ VersionCompat aCompat(rOStm, StreamMode::WRITE, 1);
+ rOStm.WriteUInt16( static_cast<sal_uInt16>(meTextLanguage) );
+}
+
+void MetaTextLanguageAction::Read( SvStream& rIStm, ImplMetaReadData* )
+{
+ VersionCompat aCompat(rIStm, StreamMode::READ);
+ sal_uInt16 nTmp = 0;
+ rIStm.ReadUInt16( nTmp );
+ meTextLanguage = static_cast<LanguageType>(nTmp);
+}
+
+/* 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 000000000..461321c1c
--- /dev/null
+++ b/vcl/source/gdi/mtfxmldump.cxx
@@ -0,0 +1,1274 @@
+/* -*- 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 <rtl/string.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <sstream>
+
+namespace
+{
+
+OUString collectPushFlags(PushFlags nFlags)
+{
+ if ((nFlags & PushFlags::ALL) == PushFlags::ALL)
+ return "PushAll";
+ else if ((nFlags & PUSH_ALLFONT) == PUSH_ALLFONT)
+ return "PushAllFont";
+ else if ((nFlags & PUSH_ALLTEXT) == PUSH_ALLTEXT)
+ return "PushAllText";
+
+ std::vector<OUString> aStrings;
+
+ if (nFlags & PushFlags::LINECOLOR)
+ aStrings.emplace_back("PushLineColor");
+ if (nFlags & PushFlags::FILLCOLOR)
+ aStrings.emplace_back("PushFillColor");
+ if (nFlags & PushFlags::FONT)
+ aStrings.emplace_back("PushFont");
+ if (nFlags & PushFlags::TEXTCOLOR)
+ aStrings.emplace_back("PushTextColor");
+ if (nFlags & PushFlags::MAPMODE)
+ aStrings.emplace_back("PushMapMode");
+ if (nFlags & PushFlags::CLIPREGION)
+ aStrings.emplace_back("PushClipRegion");
+ if (nFlags & PushFlags::RASTEROP)
+ aStrings.emplace_back("PushRasterOp");
+ if (nFlags & PushFlags::TEXTFILLCOLOR)
+ aStrings.emplace_back("PushTextFillColor");
+ if (nFlags & PushFlags::TEXTALIGN)
+ aStrings.emplace_back("PushTextAlign");
+ if (nFlags & PushFlags::REFPOINT)
+ aStrings.emplace_back("PushRefPoint");
+ if (nFlags & PushFlags::TEXTLINECOLOR)
+ aStrings.emplace_back("PushTextLineColor");
+ if (nFlags & PushFlags::TEXTLAYOUTMODE)
+ aStrings.emplace_back("PushTextLayoutMode");
+ if (nFlags & PushFlags::TEXTLANGUAGE)
+ aStrings.emplace_back("PushTextLanguage");
+ if (nFlags & PushFlags::OVERLINECOLOR)
+ aStrings.emplace_back("PushOverlineColor");
+
+ 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");
+ if (eDrawTextFlags & DrawTextFlags::HideMnemonic)
+ aStrings.emplace_back("HideMnemonic");
+
+ 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 convertFontWeigthToString(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";
+ case MetaActionType::PIXEL: return "pixel";
+ case MetaActionType::POINT: return "point";
+ case MetaActionType::LINE: return "line";
+ case MetaActionType::RECT: return "rect";
+ case MetaActionType::ROUNDRECT: return "roundrect";
+ case MetaActionType::ELLIPSE: return "ellipse";
+ case MetaActionType::ARC: return "arc";
+ case MetaActionType::PIE: return "pie";
+ case MetaActionType::CHORD: return "chord";
+ case MetaActionType::POLYLINE: return "polyline";
+ case MetaActionType::POLYGON: return "polygon";
+ case MetaActionType::POLYPOLYGON: return "polypolygon";
+ case MetaActionType::TEXT: return "text";
+ case MetaActionType::TEXTARRAY: return "textarray";
+ case MetaActionType::STRETCHTEXT: return "stretchtext";
+ case MetaActionType::TEXTRECT: return "textrect";
+ case MetaActionType::TEXTLINE: return "textline";
+ case MetaActionType::BMP: return "bmp";
+ case MetaActionType::BMPSCALE: return "bmpscale";
+ case MetaActionType::BMPSCALEPART: return "bmpscalepart";
+ case MetaActionType::BMPEX: return "bmpex";
+ case MetaActionType::BMPEXSCALE: return "bmpexscale";
+ case MetaActionType::BMPEXSCALEPART: return "bmpexscalepart";
+ case MetaActionType::MASK: return "mask";
+ case MetaActionType::MASKSCALE: return "maskscale";
+ case MetaActionType::MASKSCALEPART: return "maskscalepart";
+ case MetaActionType::GRADIENT: return "gradient";
+ case MetaActionType::GRADIENTEX: return "gradientex";
+ case MetaActionType::HATCH: return "hatch";
+ case MetaActionType::WALLPAPER: return "wallpaper";
+ case MetaActionType::CLIPREGION: return "clipregion";
+ case MetaActionType::ISECTRECTCLIPREGION: return "sectrectclipregion";
+ case MetaActionType::ISECTREGIONCLIPREGION: return "sectregionclipregion";
+ case MetaActionType::MOVECLIPREGION: return "moveclipregion";
+ case MetaActionType::LINECOLOR: return "linecolor";
+ case MetaActionType::FILLCOLOR: return "fillcolor";
+ case MetaActionType::TEXTCOLOR: return "textcolor";
+ case MetaActionType::TEXTFILLCOLOR: return "textfillcolor";
+ case MetaActionType::TEXTLINECOLOR: return "textlinecolor";
+ case MetaActionType::OVERLINECOLOR: return "overlinecolor";
+ case MetaActionType::TEXTALIGN: return "textalign";
+ case MetaActionType::MAPMODE: return "mapmode";
+ case MetaActionType::FONT: return "font";
+ case MetaActionType::PUSH: return "push";
+ case MetaActionType::POP: return "pop";
+ case MetaActionType::RASTEROP: return "rasterop";
+ case MetaActionType::Transparent: return "transparent";
+ case MetaActionType::FLOATTRANSPARENT: return "floattransparent";
+ case MetaActionType::EPS: return "eps";
+ case MetaActionType::REFPOINT: return "refpoint";
+ case MetaActionType::COMMENT: return "comment";
+ case MetaActionType::LAYOUTMODE: return "layoutmode";
+ case MetaActionType::TEXTLANGUAGE: return "textlanguage";
+ }
+ return "";
+}
+
+OUString convertBitmapExTransparentType(TransparentType eType)
+{
+ switch (eType)
+ {
+ default:
+ case TransparentType::NONE: return "none";
+ case TransparentType::Bitmap: return "bitmap";
+ case TransparentType::Color: return "color";
+ }
+}
+
+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().c_str());
+}
+
+OUString convertGradientStyle(GradientStyle eStyle)
+{
+ switch (eStyle)
+ {
+ case GradientStyle::Linear: return "Linear";
+ case GradientStyle::Axial: return "Axial";
+ case GradientStyle::Radial: return "Radial";
+ case GradientStyle::Elliptical: return "Elliptical";
+ case GradientStyle::Square: return "Square";
+ case GradientStyle::Rect: return "Rect";
+ case GradientStyle::FORCE_EQUAL_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 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 hex32(sal_uInt32 nNumber)
+{
+ std::stringstream ss;
+ ss << std::hex << std::setfill('0') << std::setw(8) << nNumber;
+ return OUString::createFromAscii(ss.str().c_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", OString("empty"));
+ else
+ rWriter.attribute("right", rRectangle.Right());
+ if (rRectangle.IsHeightEmpty())
+ rWriter.attribute("bottom", OString("empty"));
+ else
+ rWriter.attribute("bottom", rRectangle.Bottom());
+}
+
+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", convertGradientStyle(rGradient.GetStyle()));
+ rWriter.attribute("startcolor", convertColorToString(rGradient.GetStartColor()));
+ rWriter.attribute("endcolor", convertColorToString(rGradient.GetEndColor()));
+ rWriter.attribute("angle", rGradient.GetAngle());
+ 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());
+}
+
+} // anonymous namespace
+
+MetafileXmlDump::MetafileXmlDump()
+{
+ maFilter.fill(false);
+}
+
+MetafileXmlDump::~MetafileXmlDump()
+{}
+
+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)
+{
+ 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())
+ {
+ rWriter.startElement("dxarray");
+ OUStringBuffer sDxLengthString;
+ for (sal_Int32 i = 0; i < aLength - aIndex; ++i)
+ {
+ sDxLengthString.append(OUString::number(pMetaTextArrayAction->GetDXArray()[aIndex + i]));
+ sDxLengthString.append(" ");
+ }
+ rWriter.content(sDxLengthString.makeStringAndClear());
+ rWriter.endElement();
+ }
+
+ rWriter.startElement("text");
+ rWriter.content(pMetaTextArrayAction->GetText());
+ 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);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ auto pMeta = static_cast<MetaBmpScaleAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeSize(rWriter, pMeta->GetSize());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ auto pMeta = static_cast<MetaBmpScalePartAction*>(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.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ auto pMeta = static_cast<MetaBmpExAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmapEx().GetBitmap().GetChecksum()));
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx().GetTransparentType()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ auto pMeta = static_cast<MetaBmpExScaleAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+ writePoint(rWriter, pMeta->GetPoint());
+ writeSize(rWriter, pMeta->GetSize());
+ rWriter.attribute("crc", hex32(pMeta->GetBitmapEx().GetBitmap().GetChecksum()));
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx().GetTransparentType()));
+ rWriter.endElement();
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ auto pMeta = static_cast<MetaBmpExScalePartAction*>(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->GetBitmapEx().GetBitmap().GetChecksum()));
+ rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx().GetTransparentType()));
+ 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()));
+ 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.GetTransparentType()));
+ rWriter.attribute("bitcount", hex32(rBitmapEx.GetBitmap().GetBitCount()));
+ 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);
+
+ // 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 = pMetaClipRegionAction->GetRegion().GetBoundRect();
+ writeRectangle(rWriter, aRectangle);
+ 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:
+
+ 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", OUString("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());
+ rWriter.attribute("weight", convertFontWeigthToString(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:
+ //case MetaActionType::REFPOINT:
+
+ case MetaActionType::TEXTLINECOLOR:
+ {
+ MetaTextLineColorAction* pMetaTextLineColorAction = static_cast<MetaTextLineColorAction*>(pAction);
+ rWriter.startElement(sCurrentElementTag);
+
+ rWriter.attribute("color", convertColorToString(pMetaTextLineColorAction->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:
+ //case MetaActionType::GRADIENTEX:
+ //case MetaActionType::LAYOUTMODE:
+ //case MetaActionType::TEXTLANGUAGE:
+
+ 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());
+ }
+ if (!pMetaCommentAction->GetComment().isEmpty())
+ {
+ rWriter.startElement("comment");
+ rWriter.content(pMetaCommentAction->GetComment());
+ rWriter.endElement();
+ }
+
+ rWriter.endElement();
+ }
+ break;
+
+ default:
+ {
+ rWriter.startElement(sCurrentElementTag);
+ rWriter.attribute("note", OString("not implemented in xml dump"));
+ 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 000000000..05a6f9bbe
--- /dev/null
+++ b/vcl/source/gdi/oldprintadaptor.cxx
@@ -0,0 +1,112 @@
+/* -*- 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/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
+{
+ Sequence< PropertyValue > aRet( 1 );
+ aRet[0].Name = "PageSize";
+ if( i_nPage < int(mpData->maPages.size() ) )
+ aRet[0].Value <<= mpData->maPages[i_nPage].maPageSize;
+ else
+ {
+ awt::Size aEmpty( 0, 0 );
+ aRet[0].Value <<= aEmpty;
+ }
+ return aRet;
+}
+
+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().get() );
+ }
+}
+
+/* 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 000000000..41c208b93
--- /dev/null
+++ b/vcl/source/gdi/pdfbuildin_fonts.cxx
@@ -0,0 +1,740 @@
+/* -*- 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 "pdfbuildin_fonts.hxx"
+
+#include <rtl/strbuf.hxx>
+
+using namespace vcl;
+
+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();
+}
+
+FontAttributes BuildinFont::GetFontAttributes() const
+{
+ FontAttributes aDFA;
+ aDFA.SetFamilyName(OUString::createFromAscii(m_pName));
+ aDFA.SetStyleName(OUString::createFromAscii(m_pStyleName));
+ aDFA.SetFamilyType(m_eFamily);
+ aDFA.SetSymbolFlag(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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ { "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
+ } },
+
+ // 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
+ } },
+
+ { "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
+ } }
+
+ };
+
+BuildinFontInstance::BuildinFontInstance(const PhysicalFontFace& rFontFace,
+ const FontSelectPattern& rFSP)
+ : LogicalFontInstance(rFontFace, rFSP)
+{
+}
+
+bool BuildinFontInstance::ImplGetGlyphBoundRect(sal_GlyphId, tools::Rectangle&, bool) const
+{
+ return false;
+}
+
+bool BuildinFontInstance::GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const
+{
+ return false;
+}
+
+BuildinFontFace::BuildinFontFace(int nId)
+ : PhysicalFontFace(m_aBuildinFonts[nId].GetFontAttributes())
+ , mrBuildin(m_aBuildinFonts[nId])
+{
+}
+
+rtl::Reference<LogicalFontInstance>
+BuildinFontFace::CreateFontInstance(const FontSelectPattern& rFSP) const
+{
+ return new BuildinFontInstance(*this, rFSP);
+}
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfbuildin_fonts.hxx b/vcl/source/gdi/pdfbuildin_fonts.hxx
new file mode 100644
index 000000000..69bdee5dc
--- /dev/null
+++ b/vcl/source/gdi/pdfbuildin_fonts.hxx
@@ -0,0 +1,81 @@
+/* -*- 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 .
+ */
+#ifndef INCLUDED_VCL_SOURCE_PDF_BUILDIN_FONTS_HXX
+#define INCLUDED_VCL_SOURCE_PDF_BUILDIN_FONTS_HXX
+
+#include <PhysicalFontFace.hxx>
+#include <fontinstance.hxx>
+
+namespace vcl
+{
+namespace pdf
+{
+struct BuildinFont
+{
+ const char* m_pName;
+ const char* m_pStyleName;
+ const char* m_pPSName;
+ int const m_nAscent;
+ int const m_nDescent;
+ FontFamily const m_eFamily;
+ rtl_TextEncoding const m_eCharSet;
+ FontPitch const m_ePitch;
+ FontWidth const m_eWidthType;
+ FontWeight const m_eWeight;
+ FontItalic const m_eItalic;
+ int const m_aWidths[256];
+
+ OString getNameObject() const;
+ FontAttributes GetFontAttributes() const;
+};
+
+class BuildinFontInstance final : public LogicalFontInstance
+{
+ bool ImplGetGlyphBoundRect(sal_GlyphId nID, tools::Rectangle& rRect, bool) const override;
+
+public:
+ BuildinFontInstance(const PhysicalFontFace&, const FontSelectPattern&);
+
+ bool GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rPoly, bool) const override;
+};
+
+class BuildinFontFace final : public PhysicalFontFace
+{
+private:
+ static const BuildinFont m_aBuildinFonts[14];
+ const BuildinFont& mrBuildin;
+
+ rtl::Reference<LogicalFontInstance>
+ CreateFontInstance(const FontSelectPattern& rFSD) const override;
+
+public:
+ explicit BuildinFontFace(int nId);
+
+ const BuildinFont& GetBuildinFont() const { return mrBuildin; }
+ sal_IntPtr GetFontId() const override { return reinterpret_cast<sal_IntPtr>(&mrBuildin); }
+
+ static const BuildinFont& Get(int nId) { return m_aBuildinFonts[nId]; }
+};
+
+} // namespace pdf
+} // namespace vcl
+
+#endif // INCLUDED_VCL_SOURCE_PDF_BUILDIN_FONTS_HXX
+
+/* 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 000000000..da7e78dcb
--- /dev/null
+++ b/vcl/source/gdi/pdfextoutdevdata.cxx
@@ -0,0 +1,891 @@
+/* -*- 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>
+
+namespace vcl
+{
+namespace {
+
+struct PDFExtOutDevDataSync
+{
+ enum Action{ CreateNamedDest,
+ CreateDest,
+ CreateLink,
+ CreateScreen,
+ SetLinkDest,
+ SetLinkURL,
+ SetScreenURL,
+ SetScreenStream,
+ RegisterDest,
+ CreateOutlineItem,
+ CreateNote,
+ SetPageTransition,
+
+ BeginStructureElement,
+ EndStructureElement,
+ SetCurrentStructureElement,
+ SetStructureAttribute,
+ SetStructureAttributeNumerical,
+ SetStructureBoundingBox,
+ SetActualText,
+ SetAlternateText,
+ CreateControl,
+ BeginGroup,
+ EndGroupGfxLink
+ };
+
+ sal_uInt32 nIdx;
+ Action eAct;
+};
+
+struct PDFLinkDestination
+{
+ tools::Rectangle mRect;
+ MapMode mMapMode;
+ sal_Int32 mPageNr;
+ PDFWriter::DestAreaType mAreaType;
+};
+
+}
+
+struct GlobalSyncData
+{
+ std::deque< PDFExtOutDevDataSync::Action > mActions;
+ std::deque< MapMode > mParaMapModes;
+ std::deque< tools::Rectangle > mParaRects;
+ std::deque< sal_Int32 > mParaInts;
+ std::deque< sal_uInt32 > mParauInts;
+ std::deque< OUString > mParaOUStrings;
+ std::deque< PDFWriter::DestAreaType > mParaDestAreaTypes;
+ std::deque< PDFNote > mParaPDFNotes;
+ std::deque< PDFWriter::PageTransition > mParaPageTransitions;
+ ::std::map< sal_Int32, PDFLinkDestination > mFutureDestinations;
+
+ sal_Int32 GetMappedId();
+ sal_Int32 GetMappedStructId( sal_Int32 );
+
+ sal_Int32 mCurId;
+ std::vector< sal_Int32 > mParaIds;
+ std::vector< sal_Int32 > mStructIdMap;
+
+ sal_Int32 mCurrentStructElement;
+ std::vector< sal_Int32 > mStructParents;
+ GlobalSyncData() :
+ mCurId ( 0 ),
+ mCurrentStructElement( 0 )
+ {
+ mStructParents.push_back( 0 );
+ mStructIdMap.push_back( 0 );
+ }
+ void PlayGlobalActions( PDFWriter& rWriter );
+};
+
+sal_Int32 GlobalSyncData::GetMappedId()
+{
+ sal_Int32 nLinkId = mParaInts.front();
+ mParaInts.pop_front();
+
+ /* 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;
+}
+
+sal_Int32 GlobalSyncData::GetMappedStructId( sal_Int32 nStructId )
+{
+ if ( o3tl::make_unsigned(nStructId) < mStructIdMap.size() )
+ nStructId = mStructIdMap[ nStructId ];
+ else
+ nStructId = -1;
+
+ SAL_WARN_IF( nStructId < 0, "vcl", "unmapped structure id in GlobalSyncData" );
+
+ return nStructId;
+}
+
+void GlobalSyncData::PlayGlobalActions( PDFWriter& rWriter )
+{
+ for (auto const& action : mActions)
+ {
+ switch (action)
+ {
+ case PDFExtOutDevDataSync::CreateNamedDest : //i56629
+ {
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( mParaMapModes.front() );
+ mParaMapModes.pop_front();
+ mParaIds.push_back( rWriter.CreateNamedDest( mParaOUStrings.front(), mParaRects.front(), mParaInts.front(), mParaDestAreaTypes.front() ) );
+ mParaOUStrings.pop_front();
+ mParaRects.pop_front();
+ mParaInts.pop_front();
+ mParaDestAreaTypes.pop_front();
+ rWriter.Pop();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateDest :
+ {
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( mParaMapModes.front() );
+ mParaMapModes.pop_front();
+ mParaIds.push_back( rWriter.CreateDest( mParaRects.front(), mParaInts.front(), mParaDestAreaTypes.front() ) );
+ mParaRects.pop_front();
+ mParaInts.pop_front();
+ mParaDestAreaTypes.pop_front();
+ rWriter.Pop();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateLink :
+ {
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( mParaMapModes.front() );
+ mParaMapModes.pop_front();
+ mParaIds.push_back( rWriter.CreateLink( mParaRects.front(), mParaInts.front() ) );
+ // resolve LinkAnnotation structural attribute
+ rWriter.SetLinkPropertyID( mParaIds.back(), sal_Int32(mParaIds.size()-1) );
+ mParaRects.pop_front();
+ mParaInts.pop_front();
+ rWriter.Pop();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateScreen:
+ {
+ rWriter.Push(PushFlags::MAPMODE);
+ rWriter.SetMapMode(mParaMapModes.front());
+ mParaMapModes.pop_front();
+ mParaIds.push_back(rWriter.CreateScreen(mParaRects.front(), mParaInts.front()));
+ mParaRects.pop_front();
+ mParaInts.pop_front();
+ rWriter.Pop();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetLinkDest :
+ {
+ sal_Int32 nLinkId = GetMappedId();
+ sal_Int32 nDestId = GetMappedId();
+ rWriter.SetLinkDest( nLinkId, nDestId );
+ }
+ break;
+ case PDFExtOutDevDataSync::SetLinkURL :
+ {
+ sal_Int32 nLinkId = GetMappedId();
+ rWriter.SetLinkURL( nLinkId, mParaOUStrings.front() );
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetScreenURL:
+ {
+ sal_Int32 nScreenId = GetMappedId();
+ rWriter.SetScreenURL(nScreenId, mParaOUStrings.front());
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetScreenStream:
+ {
+ sal_Int32 nScreenId = GetMappedId();
+ rWriter.SetScreenStream(nScreenId, mParaOUStrings.front());
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::RegisterDest :
+ {
+ const sal_Int32 nDestId = mParaInts.front();
+ mParaInts.pop_front();
+ 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();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateOutlineItem :
+ {
+ sal_Int32 nParent = GetMappedId();
+ sal_Int32 nLinkId = GetMappedId();
+ mParaIds.push_back( rWriter.CreateOutlineItem( nParent, mParaOUStrings.front(), nLinkId ) );
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateNote :
+ {
+ rWriter.Push( PushFlags::MAPMODE );
+ rWriter.SetMapMode( mParaMapModes.front() );
+ rWriter.CreateNote( mParaRects.front(), mParaPDFNotes.front(), mParaInts.front() );
+ mParaMapModes.pop_front();
+ mParaRects.pop_front();
+ mParaPDFNotes.pop_front();
+ mParaInts.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetPageTransition :
+ {
+ rWriter.SetPageTransition( mParaPageTransitions.front(), mParauInts.front(), mParaInts.front() );
+ mParaPageTransitions.pop_front();
+ mParauInts.pop_front();
+ mParaInts.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::BeginStructureElement:
+ case PDFExtOutDevDataSync::EndStructureElement:
+ case PDFExtOutDevDataSync::SetCurrentStructureElement:
+ case PDFExtOutDevDataSync::SetStructureAttribute:
+ case PDFExtOutDevDataSync::SetStructureAttributeNumerical:
+ case PDFExtOutDevDataSync::SetStructureBoundingBox:
+ case PDFExtOutDevDataSync::SetActualText:
+ case PDFExtOutDevDataSync::SetAlternateText:
+ case PDFExtOutDevDataSync::CreateControl:
+ case PDFExtOutDevDataSync::BeginGroup:
+ case PDFExtOutDevDataSync::EndGroupGfxLink:
+ break;
+ }
+ }
+}
+
+struct PageSyncData
+{
+ std::deque< PDFExtOutDevDataSync > mActions;
+ std::deque< tools::Rectangle > mParaRects;
+ std::deque< sal_Int32 > mParaInts;
+ std::deque< OUString > mParaOUStrings;
+ std::deque< PDFWriter::StructElement > mParaStructElements;
+ std::deque< PDFWriter::StructAttribute > mParaStructAttributes;
+ std::deque< PDFWriter::StructAttributeValue > mParaStructAttributeValues;
+ std::deque< Graphic > mGraphics;
+ Graphic mCurrentGraphic;
+ std::deque< std::shared_ptr< PDFWriter::AnyWidget > >
+ mControls;
+ GlobalSyncData* mpGlobalData;
+
+ bool mbGroupIgnoreGDIMtfActions;
+
+
+ explicit PageSyncData( GlobalSyncData* pGlobal )
+ : mbGroupIgnoreGDIMtfActions ( false )
+ { mpGlobalData = pGlobal; }
+
+ void PushAction( const OutputDevice& rOutDev, const PDFExtOutDevDataSync::Action eAct );
+ bool PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAction, const GDIMetaFile& rMtf, const PDFExtOutDevData& rOutDevData );
+};
+
+void PageSyncData::PushAction( const OutputDevice& rOutDev, const PDFExtOutDevDataSync::Action eAct )
+{
+ GDIMetaFile* pMtf = rOutDev.GetConnectMetaFile();
+ SAL_WARN_IF( !pMtf, "vcl", "PageSyncData::PushAction -> no ConnectMetaFile !!!" );
+
+ PDFExtOutDevDataSync aSync;
+ aSync.eAct = eAct;
+ if ( pMtf )
+ aSync.nIdx = pMtf->GetActionSize();
+ else
+ aSync.nIdx = 0x7fffffff; // sync not possible
+ mActions.push_back( 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;
+ PDFExtOutDevDataSync aDataSync = mActions.front();
+ mActions.pop_front();
+ switch( aDataSync.eAct )
+ {
+ case PDFExtOutDevDataSync::BeginStructureElement :
+ {
+ sal_Int32 nNewEl = rWriter.BeginStructureElement( mParaStructElements.front(), mParaOUStrings.front() ) ;
+ mParaStructElements.pop_front();
+ mParaOUStrings.pop_front();
+ mpGlobalData->mStructIdMap.push_back( nNewEl );
+ }
+ break;
+ case PDFExtOutDevDataSync::EndStructureElement :
+ {
+ rWriter.EndStructureElement();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetCurrentStructureElement:
+ {
+ rWriter.SetCurrentStructureElement( mpGlobalData->GetMappedStructId( mParaInts.front() ) );
+ mParaInts.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetStructureAttribute :
+ {
+ rWriter.SetStructureAttribute( mParaStructAttributes.front(), mParaStructAttributeValues.front() );
+ mParaStructAttributeValues.pop_front();
+ mParaStructAttributes.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetStructureAttributeNumerical :
+ {
+ rWriter.SetStructureAttributeNumerical( mParaStructAttributes.front(), mParaInts.front() );
+ mParaStructAttributes.pop_front();
+ mParaInts.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetStructureBoundingBox :
+ {
+ rWriter.SetStructureBoundingBox( mParaRects.front() );
+ mParaRects.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetActualText :
+ {
+ rWriter.SetActualText( mParaOUStrings.front() );
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::SetAlternateText :
+ {
+ rWriter.SetAlternateText( mParaOUStrings.front() );
+ mParaOUStrings.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateControl:
+ {
+ std::shared_ptr< PDFWriter::AnyWidget > pControl( mControls.front() );
+ SAL_WARN_IF( !pControl, "vcl", "PageSyncData::PlaySyncPageAct: invalid widget!" );
+ if ( pControl )
+ rWriter.CreateControl( *pControl );
+ mControls.pop_front();
+ }
+ break;
+ case PDFExtOutDevDataSync::BeginGroup :
+ {
+ /* first determining if this BeginGroup is starting a GfxLink,
+ by searching for an EndGroup or an EndGroupGfxLink */
+ mbGroupIgnoreGDIMtfActions = false;
+ auto isStartingGfxLink = std::any_of(mActions.begin(), mActions.end(),
+ [](const PDFExtOutDevDataSync& rAction) { return rAction.eAct == PDFExtOutDevDataSync::EndGroupGfxLink; });
+ if ( isStartingGfxLink )
+ {
+ Graphic& rGraphic = mGraphics.front();
+ if ( rGraphic.IsGfxLink() && mParaRects.size() >= 2 )
+ {
+ GfxLinkType eType = rGraphic.GetGfxLink().GetType();
+ if ( eType == GfxLinkType::NativeJpg )
+ {
+ mbGroupIgnoreGDIMtfActions = rOutDevData.HasAdequateCompression(rGraphic, mParaRects[0], mParaRects[1]);
+ if ( !mbGroupIgnoreGDIMtfActions )
+ mCurrentGraphic = rGraphic;
+ }
+ else if ( eType == GfxLinkType::NativePng || eType == GfxLinkType::NativePdf )
+ {
+ if ( eType == GfxLinkType::NativePdf || rOutDevData.HasAdequateCompression(rGraphic, mParaRects[0], mParaRects[1]) )
+ mCurrentGraphic = rGraphic;
+ }
+ }
+ }
+ }
+ break;
+ case PDFExtOutDevDataSync::EndGroupGfxLink :
+ {
+ tools::Rectangle aOutputRect, aVisibleOutputRect;
+ Graphic aGraphic( mGraphics.front() );
+
+ mGraphics.pop_front();
+ sal_Int32 nTransparency = mParaInts.front();
+ mParaInts.pop_front();
+ aOutputRect = mParaRects.front();
+ mParaRects.pop_front();
+ aVisibleOutputRect = mParaRects.front();
+ mParaRects.pop_front();
+
+ 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);
+ }
+
+ Bitmap aMask;
+ if (nTransparency)
+ {
+ AlphaMask aAlphaMask(aGraphic.GetSizePixel());
+ aAlphaMask.Erase(nTransparency);
+ aMask = aAlphaMask.GetBitmap();
+ }
+
+ 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());
+ }
+ }
+
+ rWriter.DrawJPGBitmap( aTmp, aGraphic.GetBitmapEx().GetBitCount() > 8, aGraphic.GetSizePixel(), aOutputRect, aMask, aGraphic );
+ }
+
+ if ( bClippingNeeded )
+ rWriter.Pop();
+ }
+ mbGroupIgnoreGDIMtfActions = false;
+ }
+ mCurrentGraphic.Clear();
+ }
+ break;
+ case PDFExtOutDevDataSync::CreateNamedDest:
+ case PDFExtOutDevDataSync::CreateDest:
+ case PDFExtOutDevDataSync::CreateLink:
+ case PDFExtOutDevDataSync::CreateScreen:
+ case PDFExtOutDevDataSync::SetLinkDest:
+ case PDFExtOutDevDataSync::SetLinkURL:
+ case PDFExtOutDevDataSync::SetScreenURL:
+ case PDFExtOutDevDataSync::SetScreenStream:
+ case PDFExtOutDevDataSync::RegisterDest:
+ case PDFExtOutDevDataSync::CreateOutlineItem:
+ case PDFExtOutDevDataSync::CreateNote:
+ case PDFExtOutDevDataSync::SetPageTransition:
+ break;
+ }
+ }
+ else if ( mbGroupIgnoreGDIMtfActions )
+ {
+ rCurGDIMtfAction++;
+ bRet = true;
+ }
+ return bRet;
+}
+
+PDFExtOutDevData::PDFExtOutDevData( const OutputDevice& rOutDev ) :
+ mrOutDev ( rOutDev ),
+ mbTaggedPDF ( false ),
+ mbExportNotes ( true ),
+ 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::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()
+{
+ *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( PDFExtOutDevDataSync::CreateNamedDest );
+ mpGlobalSyncData->mParaOUStrings.push_back( sDestName );
+ mpGlobalSyncData->mParaRects.push_back( rRect );
+ mpGlobalSyncData->mParaMapModes.push_back( mrOutDev.GetMapMode() );
+ mpGlobalSyncData->mParaInts.push_back( nPageNr == -1 ? mnPage : nPageNr );
+ mpGlobalSyncData->mParaDestAreaTypes.push_back( PDFWriter::DestAreaType::XYZ );
+
+ return mpGlobalSyncData->mCurId++;
+}
+//<---i56629
+sal_Int32 PDFExtOutDevData::RegisterDest()
+{
+ const sal_Int32 nLinkDestID = mpGlobalSyncData->mCurId++;
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::RegisterDest );
+ mpGlobalSyncData->mParaInts.push_back( 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( PDFExtOutDevDataSync::CreateDest );
+ mpGlobalSyncData->mParaRects.push_back( rRect );
+ mpGlobalSyncData->mParaMapModes.push_back( mrOutDev.GetMapMode() );
+ mpGlobalSyncData->mParaInts.push_back( nPageNr == -1 ? mnPage : nPageNr );
+ mpGlobalSyncData->mParaDestAreaTypes.push_back( eType );
+ return mpGlobalSyncData->mCurId++;
+}
+sal_Int32 PDFExtOutDevData::CreateLink( const tools::Rectangle& rRect, sal_Int32 nPageNr )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::CreateLink );
+ mpGlobalSyncData->mParaRects.push_back( rRect );
+ mpGlobalSyncData->mParaMapModes.push_back( mrOutDev.GetMapMode() );
+ mpGlobalSyncData->mParaInts.push_back( nPageNr == -1 ? mnPage : nPageNr );
+ return mpGlobalSyncData->mCurId++;
+}
+
+sal_Int32 PDFExtOutDevData::CreateScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr)
+{
+ mpGlobalSyncData->mActions.push_back(PDFExtOutDevDataSync::CreateScreen);
+ mpGlobalSyncData->mParaRects.push_back(rRect);
+ mpGlobalSyncData->mParaMapModes.push_back(mrOutDev.GetMapMode());
+ mpGlobalSyncData->mParaInts.push_back(nPageNr);
+ return mpGlobalSyncData->mCurId++;
+}
+
+void PDFExtOutDevData::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::SetLinkDest );
+ mpGlobalSyncData->mParaInts.push_back( nLinkId );
+ mpGlobalSyncData->mParaInts.push_back( nDestId );
+}
+void PDFExtOutDevData::SetLinkURL( sal_Int32 nLinkId, const OUString& rURL )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::SetLinkURL );
+ mpGlobalSyncData->mParaInts.push_back( nLinkId );
+ mpGlobalSyncData->mParaOUStrings.push_back( rURL );
+}
+
+void PDFExtOutDevData::SetScreenURL(sal_Int32 nScreenId, const OUString& rURL)
+{
+ mpGlobalSyncData->mActions.push_back(PDFExtOutDevDataSync::SetScreenURL);
+ mpGlobalSyncData->mParaInts.push_back(nScreenId);
+ mpGlobalSyncData->mParaOUStrings.push_back(rURL);
+}
+
+void PDFExtOutDevData::SetScreenStream(sal_Int32 nScreenId, const OUString& rURL)
+{
+ mpGlobalSyncData->mActions.push_back(PDFExtOutDevDataSync::SetScreenStream);
+ mpGlobalSyncData->mParaInts.push_back(nScreenId);
+ mpGlobalSyncData->mParaOUStrings.push_back(rURL);
+}
+
+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( PDFExtOutDevDataSync::CreateOutlineItem );
+ mpGlobalSyncData->mParaInts.push_back( nParent );
+ mpGlobalSyncData->mParaOUStrings.push_back( rText );
+ mpGlobalSyncData->mParaInts.push_back( nDestID );
+ return mpGlobalSyncData->mCurId++;
+}
+void PDFExtOutDevData::CreateNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::CreateNote );
+ mpGlobalSyncData->mParaRects.push_back( rRect );
+ mpGlobalSyncData->mParaMapModes.push_back( mrOutDev.GetMapMode() );
+ mpGlobalSyncData->mParaPDFNotes.push_back( rNote );
+ mpGlobalSyncData->mParaInts.push_back( nPageNr == -1 ? mnPage : nPageNr );
+}
+void PDFExtOutDevData::SetPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec )
+{
+ mpGlobalSyncData->mActions.push_back( PDFExtOutDevDataSync::SetPageTransition );
+ mpGlobalSyncData->mParaPageTransitions.push_back( eType );
+ mpGlobalSyncData->mParauInts.push_back( nMilliSec );
+ mpGlobalSyncData->mParaInts.push_back( 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::BeginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::BeginStructureElement );
+ mpPageSyncData->mParaStructElements.push_back( eType );
+ mpPageSyncData->mParaOUStrings.push_back( rAlias );
+ // need a global id
+ sal_Int32 nNewId = mpGlobalSyncData->mStructParents.size();
+ mpGlobalSyncData->mStructParents.push_back( mpGlobalSyncData->mCurrentStructElement );
+ mpGlobalSyncData->mCurrentStructElement = nNewId;
+ return nNewId;
+}
+void PDFExtOutDevData::EndStructureElement()
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::EndStructureElement );
+ mpGlobalSyncData->mCurrentStructElement = mpGlobalSyncData->mStructParents[ mpGlobalSyncData->mCurrentStructElement ];
+}
+bool PDFExtOutDevData::SetCurrentStructureElement( sal_Int32 nStructId )
+{
+ bool bSuccess = false;
+ if( o3tl::make_unsigned(nStructId) < mpGlobalSyncData->mStructParents.size() )
+ {
+ mpGlobalSyncData->mCurrentStructElement = nStructId;
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetCurrentStructureElement );
+ mpPageSyncData->mParaInts.push_back( nStructId );
+ bSuccess = true;
+ }
+ return bSuccess;
+}
+sal_Int32 PDFExtOutDevData::GetCurrentStructureElement() const
+{
+ return mpGlobalSyncData->mCurrentStructElement;
+}
+void PDFExtOutDevData::SetStructureAttribute( PDFWriter::StructAttribute eAttr, PDFWriter::StructAttributeValue eVal )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetStructureAttribute );
+ mpPageSyncData->mParaStructAttributes.push_back( eAttr );
+ mpPageSyncData->mParaStructAttributeValues.push_back( eVal );
+}
+void PDFExtOutDevData::SetStructureAttributeNumerical( PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetStructureAttributeNumerical );
+ mpPageSyncData->mParaStructAttributes.push_back( eAttr );
+ mpPageSyncData->mParaInts.push_back( nValue );
+}
+void PDFExtOutDevData::SetStructureBoundingBox( const tools::Rectangle& rRect )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetStructureBoundingBox );
+ mpPageSyncData->mParaRects.push_back( rRect );
+}
+void PDFExtOutDevData::SetActualText( const OUString& rText )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetActualText );
+ mpPageSyncData->mParaOUStrings.push_back( rText );
+}
+void PDFExtOutDevData::SetAlternateText( const OUString& rText )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetAlternateText );
+ mpPageSyncData->mParaOUStrings.push_back( rText );
+}
+
+void PDFExtOutDevData::CreateControl( const PDFWriter::AnyWidget& rControlType )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::CreateControl );
+
+ std::shared_ptr< PDFWriter::AnyWidget > pClone( rControlType.Clone() );
+ mpPageSyncData->mControls.push_back( pClone );
+}
+
+void PDFExtOutDevData::BeginGroup()
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::BeginGroup );
+}
+
+void PDFExtOutDevData::EndGroup( const Graphic& rGraphic,
+ sal_uInt8 nTransparency,
+ const tools::Rectangle& rOutputRect,
+ const tools::Rectangle& rVisibleOutputRect )
+{
+ mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::EndGroupGfxLink );
+ mpPageSyncData->mGraphics.push_back( rGraphic );
+ mpPageSyncData->mParaInts.push_back( nTransparency );
+ mpPageSyncData->mParaRects.push_back( rOutputRect );
+ mpPageSyncData->mParaRects.push_back( rVisibleOutputRect );
+}
+
+// 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;
+
+ if (rGraphic.GetGfxLink().GetDataSize() == 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) /
+ rGraphic.GetGfxLink().GetDataSize();
+
+ 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/pdffontcache.cxx b/vcl/source/gdi/pdffontcache.cxx
new file mode 100644
index 000000000..b79753c0f
--- /dev/null
+++ b/vcl/source/gdi/pdffontcache.cxx
@@ -0,0 +1,65 @@
+/* -*- 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 <typeinfo>
+
+#include <sal/types.h>
+
+#include <PhysicalFontFace.hxx>
+#include <salgdi.hxx>
+
+#include "pdffontcache.hxx"
+
+using namespace vcl;
+
+PDFFontCache::FontIdentifier::FontIdentifier( const PhysicalFontFace* pFont, bool bVertical ) :
+ m_nFontId( pFont->GetFontId() ),
+ m_bVertical( bVertical ),
+ m_typeFontFace( const_cast<std::type_info*>(&typeid(pFont)) )
+{
+}
+
+PDFFontCache::FontData& PDFFontCache::getFont( const PhysicalFontFace* pFont, bool bVertical )
+{
+ FontIdentifier aId( pFont, bVertical );
+ FontToIndexMap::iterator it = m_aFontToIndex.find( aId );
+ if( it != m_aFontToIndex.end() )
+ return m_aFonts[ it->second ];
+ m_aFontToIndex[ aId ] = sal_uInt32(m_aFonts.size());
+ m_aFonts.emplace_back( );
+ return m_aFonts.back();
+}
+
+sal_Int32 PDFFontCache::getGlyphWidth( const PhysicalFontFace* pFont, sal_GlyphId nGlyph, bool bVertical, SalGraphics* pGraphics )
+{
+ sal_Int32 nWidth = 0;
+ FontData& rFontData( getFont( pFont, bVertical ) );
+ if( rFontData.m_nWidths.empty() )
+ {
+ pGraphics->GetGlyphWidths( pFont, bVertical, rFontData.m_nWidths, rFontData.m_aGlyphIdToIndex );
+ }
+ if( ! rFontData.m_nWidths.empty() )
+ {
+ if (nGlyph < rFontData.m_nWidths.size())
+ nWidth = rFontData.m_nWidths[nGlyph];
+ }
+ return nWidth;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdffontcache.hxx b/vcl/source/gdi/pdffontcache.hxx
new file mode 100644
index 000000000..6eb8e7823
--- /dev/null
+++ b/vcl/source/gdi/pdffontcache.hxx
@@ -0,0 +1,72 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_GDI_PDFFONTCACHE_HXX
+#define INCLUDED_VCL_SOURCE_GDI_PDFFONTCACHE_HXX
+
+#include <typeinfo>
+
+#include <sal/types.h>
+
+#include <salgdi.hxx>
+
+namespace vcl
+{
+ class PDFFontCache
+ {
+ struct FontIdentifier
+ {
+ sal_IntPtr m_nFontId;
+ bool m_bVertical;
+ std::type_info* m_typeFontFace;
+
+ FontIdentifier( const PhysicalFontFace*, bool bVertical );
+
+ // Less than needed for std::set and std::map
+ bool operator<( const FontIdentifier& rRight ) const
+ {
+ std::type_info *pType = rRight.m_typeFontFace;
+
+ return m_nFontId < rRight.m_nFontId ||
+ ( m_nFontId == rRight.m_nFontId &&
+ ( m_typeFontFace->before( *pType ) ||
+ ( *m_typeFontFace == *pType && m_bVertical < rRight.m_bVertical ) ) );
+ }
+ };
+ struct FontData
+ {
+ std::vector< sal_Int32 > m_nWidths;
+ Ucs2UIntMap m_aGlyphIdToIndex;
+ };
+ typedef std::map< FontIdentifier, sal_uInt32 > FontToIndexMap;
+
+ std::vector< FontData > m_aFonts;
+ FontToIndexMap m_aFontToIndex;
+
+ FontData& getFont( const PhysicalFontFace*, bool bVertical );
+ public:
+ PDFFontCache() {}
+
+ sal_Int32 getGlyphWidth( const PhysicalFontFace*, sal_GlyphId, bool bVertical, SalGraphics* );
+ };
+}
+
+#endif
+
+/* 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 000000000..9a418917c
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter.cxx
@@ -0,0 +1,467 @@
+/* -*- 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 "pdfwriter_impl.hxx"
+#include <vcl/bitmapex.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,
+ 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,
+ const long* pDXAry,
+ sal_Int32 nIndex,
+ sal_Int32 nLen )
+{
+ xImplementation->drawTextArray( rStartPt, rStr, pDXAry, nIndex, nLen );
+}
+
+void PDFWriter::DrawStretchText(
+ const Point& rStartPt,
+ sal_uLong 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_uLong nHorzRound, sal_uLong 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( long nHorzMove, 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( 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 Bitmap& rMask, const Graphic& rGraphic )
+{
+ xImplementation->drawJPGBitmap( rStreamData, bIsTrueColor, rSrcSizePixel, rTargetArea, rMask, rGraphic );
+}
+
+sal_Int32 PDFWriter::CreateLink( const tools::Rectangle& rRect, sal_Int32 nPageNr )
+{
+ return xImplementation->createLink( rRect, nPageNr );
+}
+
+sal_Int32 PDFWriter::CreateScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr)
+{
+ return xImplementation->createScreen(rRect, nPageNr);
+}
+
+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, const OUString& 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::BeginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias )
+{
+ return xImplementation->beginStructureElement( eType, rAlias );
+}
+
+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::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::AddStream( const OUString& rMimeType, PDFOutputStream* pStream )
+{
+ xImplementation->addStream( rMimeType, 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 000000000..7e02762d6
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -0,0 +1,11212 @@
+/* -*- 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 <config_features.h>
+
+#include <sal/types.h>
+
+#include <math.h>
+#include <algorithm>
+
+#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 <cppuhelper/implbase.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <o3tl/numeric.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <osl/file.hxx>
+#include <osl/thread.h>
+#include <rtl/digest.h>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <svl/urihelper.hxx>
+#include <tools/fract.hxx>
+#include <tools/helpers.hxx>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/zcodec.hxx>
+#include <svl/cryptosign.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/settings.hxx>
+#include <strhelper.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/filter/pdfdocument.hxx>
+#include <comphelper/hash.hxx>
+
+#include <fontsubset.hxx>
+#include <PhysicalFontFace.hxx>
+#include <salgdi.hxx>
+#include <textlayout.hxx>
+#include <textlineinfo.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <impglyphitem.hxx>
+#include <pdf/XmpMetadata.hxx>
+
+#include "pdfwriter_impl.hxx"
+
+#ifdef _WIN32
+// WinCrypt headers for PDF signing
+// Note: this uses Windows 7 APIs and requires the relevant data types
+#include <prewin.h>
+#include <wincrypt.h>
+#include <postwin.h>
+#endif
+
+#include <config_eot.h>
+
+#if ENABLE_EOT
+#include <libeot/libeot.h>
+#endif
+
+using namespace vcl;
+using namespace::com::sun::star;
+
+static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
+
+#if HAVE_FEATURE_NSS
+// Is this length truly the maximum possible, or just a number that
+// seemed large enough when the author tested this (with some type of
+// certificates)? I suspect the latter.
+
+// Used to be 0x4000 = 16384, but a sample signed PDF (produced by
+// some other software) provided by the customer has a signature
+// content that is 30000 bytes. The SampleSignedPDFDocument.pdf from
+// Adobe has one that is 21942 bytes. So let's be careful. Pity this
+// can't be dynamic, at least not without restructuring the code. Also
+// note that the checks in the code for this being too small
+// apparently are broken, if this overflows you end up with an invalid
+// PDF. Need to fix that.
+
+#define MAX_SIGNATURE_CONTENT_LENGTH 50000
+#endif
+
+static const sal_Int32 nLog10Divisor = 3;
+static const double fDivisor = 1000.0;
+
+static double pixelToPoint( double px ) { return px/fDivisor; }
+static sal_Int32 pointToPixel( double pt ) { return sal_Int32(pt*fDivisor); }
+
+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
+};
+
+static 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 ] );
+}
+
+static void appendName( const OUString& 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 );
+ }
+ }
+}
+
+static 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
+static 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.
+*/
+static 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 );
+ }
+ }
+}
+
+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 = (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2) ? i_rControl.Name : i_rControl.Text;
+ 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 && it->second < sal_Int32( m_aWidgets.size() ), "invalid field index" );
+ if( it->second >= 0 && it->second < sal_Int32(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 = OString( "Widget" );
+ }
+
+ 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
+ {
+ OStringBuffer aUnique( aFullName.getLength() + 16 );
+ aUnique.append( aFullName );
+ aUnique.append( '_' );
+ aUnique.append( nTry++ );
+ aTry = aUnique.makeStringAndClear();
+ 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;
+}
+
+static 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
+static void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 5 )
+{
+ 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 )
+ {
+ 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;
+ }
+ }
+}
+
+static void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
+{
+
+ if( rColor != COL_TRANSPARENT )
+ {
+ 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 );
+ }
+ }
+}
+
+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" );
+ }
+}
+
+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)
+ {
+ case PDFWriter::PDFVersion::PDF_1_6:
+ m_nUserUnit = std::ceil(std::max(nPageWidth, nPageHeight) / 14400.0);
+ break;
+ default:
+ // 1.2 -> 1.5
+ 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;
+ aLine.append( m_aStreamObjects.back() );
+ aLine.append( " 0 obj\n<</Length " );
+ aLine.append( m_nStreamLengthObject );
+ aLine.append( " 0 R" );
+ if (!g_bDebugDisableCompression)
+ aLine.append( "/Filter/FlateDecode" );
+ aLine.append( ">>\nstream\n" );
+ if( ! m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ) )
+ 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", 19 ) )
+ 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.getStr(), aLine.getLength() );
+}
+
+bool PDFPage::emit(sal_Int32 nParentObject )
+{
+ m_pWriter->MARK("PDFPage::emit");
+ // emit page object
+ if( ! m_pWriter->updateObject( m_nPageObject ) )
+ return false;
+ OStringBuffer aLine;
+
+ aLine.append( m_nPageObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/Page/Parent " );
+ aLine.append( nParentObject );
+ aLine.append( " 0 R" );
+ aLine.append( "/Resources " );
+ aLine.append( m_pWriter->getResourceDictObj() );
+ aLine.append( " 0 R" );
+ if( m_nPageWidth && m_nPageHeight )
+ {
+ aLine.append( "/MediaBox[0 0 " );
+ aLine.append(m_nPageWidth / m_nUserUnit);
+ aLine.append( ' ' );
+ aLine.append(m_nPageHeight / m_nUserUnit);
+ aLine.append( "]" );
+ if (m_nUserUnit > 1)
+ {
+ aLine.append("\n/UserUnit ");
+ aLine.append(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( m_aAnnotations[i] );
+ aLine.append( " 0 R" );
+ aLine.append( ((i+1)%15) ? " " : "\n" );
+ }
+ aLine.append( "]\n" );
+ }
+ if( !m_aMCIDParents.empty() )
+ {
+ OStringBuffer aStructParents( 1024 );
+ aStructParents.append( "[ " );
+ int nParents = m_aMCIDParents.size();
+ for( int i = 0; i < nParents; i++ )
+ {
+ aStructParents.append( m_aMCIDParents[i] );
+ aStructParents.append( " 0 R" );
+ aStructParents.append( ((i%10) == 9) ? "\n" : " " );
+ }
+ aStructParents.append( "]" );
+ m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
+
+ aLine.append( "/StructParents " );
+ aLine.append( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) );
+ aLine.append( "\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( "/S/" );
+ aLine.append( pStyle );
+ aLine.append( "\n" );
+ }
+ if( pDm )
+ {
+ aLine.append( "/Dm/" );
+ aLine.append( pDm );
+ aLine.append( "\n" );
+ }
+ if( pM )
+ {
+ aLine.append( "/M/" );
+ aLine.append( pM );
+ aLine.append( "\n" );
+ }
+ if( pDi )
+ {
+ aLine.append( "/Di " );
+ aLine.append( pDi );
+ aLine.append( "\n" );
+ }
+ aLine.append( ">>\n" );
+ }
+ if( m_pWriter->getVersion() > PDFWriter::PDFVersion::PDF_1_3 && ! m_pWriter->m_bIsPDF_A1 )
+ {
+ aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/I true>>" );
+ }
+ aLine.append( "/Contents" );
+ unsigned int nStreamObjects = m_aStreamObjects.size();
+ if( nStreamObjects > 1 )
+ aLine.append( '[' );
+ for(sal_Int32 i : m_aStreamObjects)
+ {
+ aLine.append( ' ' );
+ aLine.append( i );
+ aLine.append( " 0 R" );
+ }
+ if( nStreamObjects > 1 )
+ aLine.append( ']' );
+ aLine.append( ">>\nendobj\n\n" );
+ return m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+namespace vcl
+{
+template < class GEOMETRY >
+static 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 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 )
+ {
+ 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 )
+ {
+ 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(long(rMatrix.get(4)), long(rMatrix.get(5))), rBuffer);
+}
+
+double PDFPage::getHeight() const
+{
+ double fRet = m_nPageHeight ? m_nPageHeight : vcl::pdf::g_nInheritedPageHeight;
+
+ 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::DEFAULT, DeviceFormat::NONE, OUTDEV_PDF),
+ m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ),
+ m_nCurrentStructElement( 0 ),
+ m_bEmitStructure( true ),
+ m_nNextFID( 1 ),
+ m_aPDFBmpCache(
+ 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;
+
+ Font aFont;
+ aFont.SetFamilyName( "Times" );
+ aFont.SetFontSize( Size( 0, 12 ) );
+
+ 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-" );
+ switch( m_aContext.Version )
+ {
+ case PDFWriter::PDFVersion::PDF_1_2: aBuffer.append( "1.2" );break;
+ case PDFWriter::PDFVersion::PDF_1_3: aBuffer.append( "1.3" );break;
+ case PDFWriter::PDFVersion::PDF_A_1:
+ case PDFWriter::PDFVersion::PDF_1_4: aBuffer.append( "1.4" );break;
+ case PDFWriter::PDFVersion::PDF_1_5: aBuffer.append( "1.5" );break;
+ default:
+ case PDFWriter::PDFVersion::PDF_1_6: aBuffer.append( "1.6" );break;
+ }
+ // append something binary as comment (suggested in PDF Reference)
+ aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
+ if( !writeBuffer( aBuffer.getStr(), aBuffer.getLength() ) )
+ {
+ 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_6; //we could even use 1.7 features
+
+ m_bIsPDF_A3 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_3);
+ if( m_bIsPDF_A3 )
+ m_aContext.Version = PDFWriter::PDFVersion::PDF_1_6; //we could even use 1.7 features
+
+ 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();
+}
+
+void PDFWriterImpl::setupDocInfo()
+{
+ std::vector< sal_uInt8 > aId;
+ m_aCreationDateString = PDFWriter::GetDateTime();
+ computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, 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);
+ aRet.append("D:");
+ aRet.append(static_cast<char>('0' + ((aDT.Year / 1000) % 10)));
+ aRet.append(static_cast<char>('0' + ((aDT.Year / 100) % 10)));
+ aRet.append(static_cast<char>('0' + ((aDT.Year / 10) % 10)));
+ aRet.append(static_cast<char>('0' + (aDT.Year % 10)));
+ aRet.append(static_cast<char>('0' + ((aDT.Month / 10) % 10)));
+ aRet.append(static_cast<char>('0' + (aDT.Month % 10)));
+ aRet.append(static_cast<char>('0' + ((aDT.Day / 10) % 10)));
+ aRet.append(static_cast<char>('0' + (aDT.Day % 10)));
+ aRet.append(static_cast<char>('0' + ((aDT.Hours / 10) % 10)));
+ aRet.append(static_cast<char>('0' + (aDT.Hours % 10)));
+ aRet.append(static_cast<char>('0' + ((aDT.Minutes / 10) % 10)));
+ aRet.append(static_cast<char>('0' + (aDT.Minutes % 10)));
+ aRet.append(static_cast<char>('0' + ((aDT.Seconds / 10) % 10)));
+ aRet.append(static_cast<char>('0' + (aDT.Seconds % 10)));
+
+ sal_uInt32 nDelta = 0;
+ if (aGMT.Seconds > aTVal.Seconds)
+ {
+ aRet.append("-");
+ nDelta = aGMT.Seconds-aTVal.Seconds;
+ }
+ else if (aGMT.Seconds < aTVal.Seconds)
+ {
+ aRet.append("+");
+ nDelta = aTVal.Seconds-aGMT.Seconds;
+ }
+ else
+ aRet.append("Z");
+
+ if (nDelta)
+ {
+ aRet.append(static_cast<char>('0' + ((nDelta / 36000) % 10)));
+ aRet.append(static_cast<char>('0' + ((nDelta / 3600) % 10)));
+ aRet.append("'");
+ aRet.append(static_cast<char>('0' + ((nDelta / 600) % 6)));
+ aRet.append(static_cast<char>('0' + ((nDelta / 60) % 10)));
+ }
+ aRet.append( "'" );
+
+ return aRet.makeStringAndClear();
+}
+
+void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier,
+ const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
+ const OString& i_rCString1,
+ 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;
+ osl_getSystemTime( &aGMT );
+ osl_getLocalTimeFromSystemTime( &aGMT, &aTVal );
+ osl_getDateTimeFromTimeValue( &aTVal, &aDT );
+ 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( static_cast<char>('0' + ((aDT.Year/1000)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/100)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year)%10)) );
+ aCreationMetaDateString.append( "-" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Month/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Month)%10)) );
+ aCreationMetaDateString.append( "-" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Day/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Day)%10)) );
+ aCreationMetaDateString.append( "T" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Hours/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Hours)%10)) );
+ aCreationMetaDateString.append( ":" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Minutes/10)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Minutes)%10)) );
+ aCreationMetaDateString.append( ":" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Seconds/10)%10)) );
+ aCreationMetaDateString.append( 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( static_cast<char>('0' + ((nDelta/36000)%10)) );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/3600)%10)) );
+ aCreationMetaDateString.append( ":" );
+ aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/600)%6)) );
+ aCreationMetaDateString.append( 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( OStringBuffer const & rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
+{
+ rOutBuffer.append( "(" );
+ sal_Int32 nChars = rInString.getLength();
+ //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.getStr(), nChars, m_vEncryptionBuffer.data(), nChars );
+ appendLiteralString( reinterpret_cast<char*>(m_vEncryptionBuffer.data()), nChars, rOutBuffer );
+ }
+ else
+ appendLiteralString( rInString.getStr(), nChars , rOutBuffer );
+ rOutBuffer.append( ")" );
+}
+
+inline void PDFWriterImpl::appendLiteralStringEncrypt( const OString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
+{
+ OStringBuffer aBufferString( rInString );
+ appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
+}
+
+void PDFWriterImpl::appendLiteralStringEncrypt( const OUString& 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 = "% " + rtl::OStringView(pComment) + "\n";
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
+{
+ if (!g_bDebugDisableCompression)
+ {
+ sal_uLong 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 );
+ writeBuffer( m_pMemStream->GetData(), nLen );
+ m_pMemStream.reset();
+ }
+}
+
+bool PDFWriterImpl::writeBuffer( 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 );
+
+ sal_Int32 nUserUnit = m_aPages.back().m_nUserUnit;
+ if (nUserUnit > 1)
+ {
+ m_aMapMode = MapMode(MapUnit::MapPoint, Point(), Fraction(nUserUnit, pointToPixel(1)),
+ Fraction(nUserUnit, pointToPixel(1)));
+ }
+
+ 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.getStr(), aBuf.getLength() );
+}
+
+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_aMask = Bitmap();
+ }
+ }
+ 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( nObject );
+ aLine.append( " 0 obj\n"
+ "<</Nums[\n" );
+ sal_Int32 nTreeItems = m_aStructParentTree.size();
+ for( sal_Int32 n = 0; n < nTreeItems; n++ )
+ {
+ aLine.append( n );
+ aLine.append( ' ' );
+ aLine.append( m_aStructParentTree[n] );
+ aLine.append( "\n" );
+ }
+ aLine.append( "]>>\nendobj\n\n" );
+ CHECK_RETURN( updateObject( nObject ) );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+ return nObject;
+}
+
+const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
+{
+ static std::map< PDFWriter::StructAttribute, const char* > aAttributeStrings;
+ // fill maps once
+ if( aAttributeStrings.empty() )
+ {
+ aAttributeStrings[ PDFWriter::Placement ] = "Placement";
+ aAttributeStrings[ PDFWriter::WritingMode ] = "WritingMode";
+ aAttributeStrings[ PDFWriter::SpaceBefore ] = "SpaceBefore";
+ aAttributeStrings[ PDFWriter::SpaceAfter ] = "SpaceAfter";
+ aAttributeStrings[ PDFWriter::StartIndent ] = "StartIndent";
+ aAttributeStrings[ PDFWriter::EndIndent ] = "EndIndent";
+ aAttributeStrings[ PDFWriter::TextIndent ] = "TextIndent";
+ aAttributeStrings[ PDFWriter::TextAlign ] = "TextAlign";
+ aAttributeStrings[ PDFWriter::Width ] = "Width";
+ aAttributeStrings[ PDFWriter::Height ] = "Height";
+ aAttributeStrings[ PDFWriter::BlockAlign ] = "BlockAlign";
+ aAttributeStrings[ PDFWriter::InlineAlign ] = "InlineAlign";
+ aAttributeStrings[ PDFWriter::LineHeight ] = "LineHeight";
+ aAttributeStrings[ PDFWriter::BaselineShift ] = "BaselineShift";
+ aAttributeStrings[ PDFWriter::TextDecorationType ] = "TextDecorationType";
+ aAttributeStrings[ PDFWriter::ListNumbering ] = "ListNumbering";
+ aAttributeStrings[ PDFWriter::RowSpan ] = "RowSpan";
+ aAttributeStrings[ PDFWriter::ColSpan ] = "ColSpan";
+ aAttributeStrings[ PDFWriter::LinkAnnotation ] = "LinkAnnotation";
+ }
+
+ std::map< PDFWriter::StructAttribute, const char* >::const_iterator 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 std::map< PDFWriter::StructAttributeValue, const char* > aValueStrings;
+
+ if( aValueStrings.empty() )
+ {
+ aValueStrings[ PDFWriter::NONE ] = "None";
+ aValueStrings[ PDFWriter::Block ] = "Block";
+ aValueStrings[ PDFWriter::Inline ] = "Inline";
+ aValueStrings[ PDFWriter::Before ] = "Before";
+ aValueStrings[ PDFWriter::After ] = "After";
+ aValueStrings[ PDFWriter::Start ] = "Start";
+ aValueStrings[ PDFWriter::End ] = "End";
+ aValueStrings[ PDFWriter::LrTb ] = "LrTb";
+ aValueStrings[ PDFWriter::RlTb ] = "RlTb";
+ aValueStrings[ PDFWriter::TbRl ] = "TbRl";
+ aValueStrings[ PDFWriter::Center ] = "Center";
+ aValueStrings[ PDFWriter::Justify ] = "Justify";
+ aValueStrings[ PDFWriter::Auto ] = "Auto";
+ aValueStrings[ PDFWriter::Middle ] = "Middle";
+ aValueStrings[ PDFWriter::Normal ] = "Normal";
+ aValueStrings[ PDFWriter::Underline ] = "Underline";
+ aValueStrings[ PDFWriter::Overline ] = "Overline";
+ aValueStrings[ PDFWriter::LineThrough ] = "LineThrough";
+ aValueStrings[ PDFWriter::Disc ] = "Disc";
+ aValueStrings[ PDFWriter::Circle ] = "Circle";
+ aValueStrings[ PDFWriter::Square ] = "Square";
+ aValueStrings[ PDFWriter::Decimal ] = "Decimal";
+ aValueStrings[ PDFWriter::UpperRoman ] = "UpperRoman";
+ aValueStrings[ PDFWriter::LowerRoman ] = "LowerRoman";
+ aValueStrings[ PDFWriter::UpperAlpha ] = "UpperAlpha";
+ aValueStrings[ PDFWriter::LowerAlpha ] = "LowerAlpha";
+ }
+
+ std::map< PDFWriter::StructAttributeValue, const char* >::const_iterator 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" );
+}
+
+OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
+{
+ // create layout, list and table attribute sets
+ OStringBuffer aLayout(256), aList(64), aTable(64);
+ 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::RowSpan ||
+ attribute.first == PDFWriter::ColSpan )
+ 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 && nLink < static_cast<sal_Int32>(m_aLinks.size()) )
+ {
+ // update struct parent of link
+ OString aStructParentEntry =
+ OString::number( i_rEle.m_nObject ) +
+ " 0 R";
+ m_aStructParentTree.push_back( aStructParentEntry );
+ m_aLinks[ nLink ].m_nStructParent = m_aStructParentTree.size()-1;
+
+ sal_Int32 nRefObject = createObject();
+ if (updateObject(nRefObject))
+ {
+ OString aRef =
+ OString::number( nRefObject ) +
+ " 0 obj\n"
+ "<</Type/OBJR/Obj " +
+ OString::number( m_aLinks[ nLink ].m_nObject ) +
+ " 0 R>>\n"
+ "endobj\n\n";
+ writeBuffer( aRef.getStr(), aRef.getLength() );
+ }
+
+ i_rEle.m_aKids.emplace_back( nRefObject );
+ }
+ 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" );
+ }
+
+ std::vector< sal_Int32 > aAttribObjects;
+ if( !aLayout.isEmpty() )
+ {
+ aAttribObjects.push_back( createObject() );
+ if (updateObject( aAttribObjects.back() ))
+ {
+ OStringBuffer aObj( 64 );
+ aObj.append( aAttribObjects.back() );
+ aObj.append( " 0 obj\n"
+ "<</O/Layout\n" );
+ aLayout.append( ">>\nendobj\n\n" );
+ writeBuffer( aObj.getStr(), aObj.getLength() );
+ writeBuffer( aLayout.getStr(), aLayout.getLength() );
+ }
+ }
+ if( !aList.isEmpty() )
+ {
+ aAttribObjects.push_back( createObject() );
+ if (updateObject( aAttribObjects.back() ))
+ {
+ OStringBuffer aObj( 64 );
+ aObj.append( aAttribObjects.back() );
+ aObj.append( " 0 obj\n"
+ "<</O/List\n" );
+ aList.append( ">>\nendobj\n\n" );
+ writeBuffer( aObj.getStr(), aObj.getLength() );
+ writeBuffer( aList.getStr(), aList.getLength() );
+ }
+ }
+ if( !aTable.isEmpty() )
+ {
+ aAttribObjects.push_back( createObject() );
+ if (updateObject( aAttribObjects.back() ))
+ {
+ OStringBuffer aObj( 64 );
+ aObj.append( aAttribObjects.back() );
+ aObj.append( " 0 obj\n"
+ "<</O/Table\n" );
+ aTable.append( ">>\nendobj\n\n" );
+ writeBuffer( aObj.getStr(), aObj.getLength() );
+ writeBuffer( aTable.getStr(), aTable.getLength() );
+ }
+ }
+
+ OStringBuffer aRet( 64 );
+ if( aAttribObjects.size() > 1 )
+ aRet.append( " [" );
+ for (auto const& attrib : aAttribObjects)
+ {
+ aRet.append( " " );
+ aRet.append( attrib );
+ aRet.append( " 0 R" );
+ }
+ if( aAttribObjects.size() > 1 )
+ aRet.append( " ]" );
+ return aRet.makeStringAndClear();
+}
+
+sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
+{
+ if(
+ // do not emit NonStruct and its children
+ rEle.m_eType == PDFWriter::NonStructElement &&
+ rEle.m_nOwnElement != rEle.m_nParentElement // but of course emit the struct tree root
+ )
+ return 0;
+
+ for (auto const& child : rEle.m_aChildren)
+ {
+ if( child > 0 && child < sal_Int32(m_aStructure.size()) )
+ {
+ PDFStructureElement& rChild = m_aStructure[ child ];
+ if( rChild.m_eType != 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( rEle.m_nObject );
+ aLine.append( " 0 obj\n"
+ "<</Type" );
+ sal_Int32 nParentTree = -1;
+ if( rEle.m_nOwnElement == rEle.m_nParentElement )
+ {
+ nParentTree = createObject();
+ CHECK_RETURN( nParentTree );
+ aLine.append( "/StructTreeRoot\n" );
+ aLine.append( "/ParentTree " );
+ aLine.append( nParentTree );
+ aLine.append( " 0 R\n" );
+ if( ! m_aRoleMap.empty() )
+ {
+ aLine.append( "/RoleMap<<" );
+ for (auto const& role : m_aRoleMap)
+ {
+ aLine.append( '/' );
+ aLine.append(role.first);
+ aLine.append( '/' );
+ aLine.append( role.second );
+ aLine.append( '\n' );
+ }
+ 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_eType ) );
+ aLine.append( "\n"
+ "/P " );
+ aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject );
+ aLine.append( " 0 R\n"
+ "/Pg " );
+ aLine.append( rEle.m_nFirstPageObject );
+ aLine.append( " 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" );
+ aLine.append( aAttribs );
+ aLine.append( "\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( '-' );
+ aLocBuf.append( aCountry );
+ }
+ aLine.append( "/Lang" );
+ appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), rEle.m_nObject, aLine );
+ aLine.append( "\n" );
+ }
+ }
+ if( ! rEle.m_aKids.empty() )
+ {
+ unsigned int i = 0;
+ aLine.append( "/K[" );
+ for (auto const& kid : rEle.m_aKids)
+ {
+ if( kid.nMCID == -1 )
+ {
+ aLine.append( kid.nObject );
+ aLine.append( " 0 R" );
+ aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
+ }
+ else
+ {
+ if( kid.nObject == rEle.m_nFirstPageObject )
+ {
+ aLine.append( kid.nMCID );
+ aLine.append( " " );
+ }
+ else
+ {
+ aLine.append( "<</Type/MCR/Pg " );
+ aLine.append( kid.nObject );
+ aLine.append( " 0 R /MCID " );
+ aLine.append( kid.nMCID );
+ aLine.append( ">>\n" );
+ }
+ }
+ ++i;
+ }
+ aLine.append( "]\n" );
+ }
+ aLine.append( ">>\nendobj\n\n" );
+
+ CHECK_RETURN( updateObject( rEle.m_nObject ) );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ CHECK_RETURN( emitStructParentTree( nParentTree ) );
+
+ 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( tiling.m_nObject );
+ aTilingObj.append( " 0 obj\n" );
+ aTilingObj.append( "<</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 " );
+ aTilingObj.append( static_cast<sal_Int32>(nTilingStreamSize) );
+ aTilingObj.append( ">>\nstream\n" );
+ if ( !updateObject( tiling.m_nObject ) ) return false;
+ if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
+ checkAndEnableStreamEncryption( tiling.m_nObject );
+ bool written = writeBuffer( 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.getStr(), aTilingObj.getLength() ) ) 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( nFontObject );
+ aLine.append( " 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.getStr(), aLine.getLength() ) );
+ return nFontObject;
+}
+
+std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const PhysicalFontFace* pFont, EmbedFont const & rEmbed )
+{
+ std::map< sal_Int32, sal_Int32 > aRet;
+
+ sal_Int32 nFontDescriptor = 0;
+ OString aSubType( "/Type1" );
+ 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 = pFont->GetFamilyName();
+ sal_Int32 pWidths[256] = {};
+
+ SalGraphics *pGraphics = GetGraphics();
+
+ assert(pGraphics);
+
+ aSubType = OString( "/TrueType" );
+ std::vector< sal_Int32 > aGlyphWidths;
+ Ucs2UIntMap aUnicodeMap;
+ pGraphics->GetGlyphWidths( pFont, false, aGlyphWidths, aUnicodeMap );
+
+ OUString aTmpName;
+ osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
+ sal_GlyphId aGlyphIds[ 256 ] = {};
+ sal_uInt8 pEncoding[ 256 ] = {};
+ sal_Int32 pDuWidths[ 256 ] = {};
+
+ for( sal_Ucs c = 32; c < 256; c++ )
+ {
+ pEncoding[c] = c;
+ aGlyphIds[c] = 0;
+ if( aUnicodeMap.find( c ) != aUnicodeMap.end() )
+ pWidths[ c ] = aGlyphWidths[ aUnicodeMap[ c ] ];
+ }
+ //TODO: surely this is utterly broken because aGlyphIds is just all zeros, if we
+ //had the right glyphids here then I imagine we could replace pDuWidths with
+ //pWidths and remove pWidths assignment above. i.e. start with the glyph ids
+ //and map those to unicode rather than try and reverse map them ?
+ pGraphics->CreateFontSubset( aTmpName, pFont, aGlyphIds, pEncoding, pDuWidths, 256, aInfo );
+ osl_removeFile( aTmpName.pData );
+
+ // write font descriptor
+ nFontDescriptor = emitFontDescriptor( pFont, aInfo, 0, 0 );
+ if( nFontDescriptor )
+ {
+ // write font object
+ sal_Int32 nObject = createObject();
+ if( updateObject( nObject ) )
+ {
+ OStringBuffer aLine( 1024 );
+ aLine.append( nObject );
+ aLine.append( " 0 obj\n"
+ "<</Type/Font/Subtype" );
+ aLine.append( aSubType );
+ aLine.append( "/BaseFont/" );
+ appendName( aInfo.m_aPSName, aLine );
+ aLine.append( "\n" );
+ if( !pFont->IsSymbolFont() )
+ 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 " );
+ aLine.append( nFontDescriptor );
+ aLine.append( " 0 R>>\n"
+ "endobj\n\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ aRet[ rEmbed.m_nNormalFontID ] = nObject;
+ }
+ }
+
+ return aRet;
+}
+
+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, const OUString& 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 sal_Ucs* pCodeUnits,
+ const sal_Int32* pCodeUnitsPerGlyph,
+ const sal_Int32* pEncToUnicodeIndex,
+ int nGlyphs )
+{
+ int nMapped = 0;
+ for (int n = 0; n < nGlyphs; ++n)
+ if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[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 (int n = 0; n < nGlyphs; ++n)
+ {
+ if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
+ {
+ if( (nCount % 100) == 0 )
+ {
+ if( nCount )
+ aContents.append( "endbfchar\n" );
+ aContents.append( static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) );
+ aContents.append( " 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>(pCodeUnits[nIndex + j] / 256), aContents );
+ appendHex( static_cast<sal_Int8>(pCodeUnits[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( nStream );
+ aLine.append( " 0 obj\n<</Length " );
+ sal_Int32 nLen = 0;
+ if (!g_bDebugDisableCompression)
+ {
+ nLen = static_cast<sal_Int32>(aStream.Tell());
+ aStream.Seek( 0 );
+ aLine.append( nLen );
+ aLine.append( "/Filter/FlateDecode" );
+ }
+ else
+ aLine.append( aContents.getLength() );
+ aLine.append( ">>\nstream\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ checkAndEnableStreamEncryption( nStream );
+ if (!g_bDebugDisableCompression)
+ {
+ CHECK_RETURN( writeBuffer( aStream.GetData(), nLen ) );
+ }
+ else
+ {
+ CHECK_RETURN( writeBuffer( aContents.getStr(), aContents.getLength() ) );
+ }
+ disableStreamEncryption();
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\n"
+ "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ return nStream;
+}
+
+sal_Int32 PDFWriterImpl::emitFontDescriptor( const PhysicalFontFace* pFont, 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( pFont->GetItalic() == ITALIC_NORMAL || pFont->GetItalic() == ITALIC_OBLIQUE )
+ nFontFlags |= (1 << 6);
+ if( pFont->GetPitch() == PITCH_FIXED )
+ nFontFlags |= 1;
+ if( pFont->GetFamilyType() == FAMILY_SCRIPT )
+ nFontFlags |= (1 << 3);
+ else if( pFont->GetFamilyType() == FAMILY_ROMAN )
+ nFontFlags |= (1 << 1);
+
+ sal_Int32 nFontDescriptor = createObject();
+ CHECK_RETURN( updateObject( nFontDescriptor ) );
+ aLine.setLength( 0 );
+ aLine.append( nFontDescriptor );
+ aLine.append( " 0 obj\n"
+ "<</Type/FontDescriptor/FontName/" );
+ appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
+ aLine.append( "\n"
+ "/Flags " );
+ aLine.append( nFontFlags );
+ aLine.append( "\n"
+ "/FontBBox[" );
+ // note: Top and Bottom are reversed in VCL and PDF rectangles
+ aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.TopLeft().X()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.TopLeft().Y()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.BottomRight().X()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.BottomRight().Y()+1) );
+ aLine.append( "]/ItalicAngle " );
+ if( pFont->GetItalic() == ITALIC_OBLIQUE || pFont->GetItalic() == ITALIC_NORMAL )
+ aLine.append( "-30" );
+ else
+ aLine.append( "0" );
+ aLine.append( "\n"
+ "/Ascent " );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_nAscent) );
+ aLine.append( "\n"
+ "/Descent " );
+ aLine.append( static_cast<sal_Int32>(-rInfo.m_nDescent) );
+ aLine.append( "\n"
+ "/CapHeight " );
+ aLine.append( static_cast<sal_Int32>(rInfo.m_nCapHeight) );
+ // According to PDF reference 1.4 StemV is required
+ // seems a tad strange to me, but well ...
+ aLine.append( "\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( ' ' );
+ aLine.append( nFontStream );
+ aLine.append( " 0 R\n" );
+ }
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ 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()
+{
+ SalGraphics *pGraphics = GetGraphics();
+
+ if (!pGraphics)
+ return false;
+
+ OStringBuffer aLine( 1024 );
+
+ std::map< sal_Int32, sal_Int32 > aFontIDToObject;
+
+ OUString aTmpName;
+ osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
+ for (const auto & subset : m_aSubsets)
+ {
+ for (auto & s_subset :subset.second.m_aSubsets)
+ {
+ sal_GlyphId aGlyphIds[ 256 ] = {};
+ sal_Int32 pWidths[ 256 ];
+ sal_uInt8 pEncoding[ 256 ] = {};
+ sal_Int32 pEncToUnicodeIndex[ 256 ] = {};
+ sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {};
+ std::vector<sal_Ucs> aCodeUnits;
+ aCodeUnits.reserve( 256 );
+ int nGlyphs = 1;
+ // fill arrays and prepare encoding index map
+ sal_Int32 nToUnicodeStream = 0;
+
+ for (auto const& item : s_subset.m_aMapping)
+ {
+ sal_uInt8 nEnc = item.second.getGlyphId();
+
+ SAL_WARN_IF( aGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter", "duplicate glyph" );
+ SAL_WARN_IF( nEnc > s_subset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding" );
+
+ aGlyphIds[ nEnc ] = item.first;
+ pEncoding[ nEnc ] = nEnc;
+ pEncToUnicodeIndex[ nEnc ] = static_cast<sal_Int32>(aCodeUnits.size());
+ pCodeUnitsPerGlyph[ nEnc ] = item.second.countCodes();
+ for( sal_Int32 n = 0; n < pCodeUnitsPerGlyph[ nEnc ]; n++ )
+ aCodeUnits.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" );
+ }
+ }
+ FontSubsetInfo aSubsetInfo;
+ if( pGraphics->CreateFontSubset( aTmpName, subset.first, aGlyphIds, pEncoding, pWidths, nGlyphs, aSubsetInfo ) )
+ {
+ // create font stream
+ osl::File aFontFile(aTmpName);
+ if (osl::File::E_None != aFontFile.open(osl_File_OpenFlag_Read)) return false;
+ // get file size
+ sal_uInt64 nLength1;
+ if ( osl::File::E_None != aFontFile.setPos(osl_Pos_End, 0) ) return false;
+ if ( osl::File::E_None != aFontFile.getPos(nLength1) ) return false;
+ if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
+
+ if (g_bDebugDisableCompression)
+ {
+ emitComment( "PDFWriterImpl::emitFonts" );
+ }
+ sal_Int32 nFontStream = createObject();
+ sal_Int32 nStreamLengthObject = createObject();
+ if ( !updateObject( nFontStream ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( nFontStream );
+ aLine.append( " 0 obj\n"
+ "<</Length " );
+ aLine.append( 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( static_cast<sal_Int32>(nLength1) );
+
+ aLine.append( ">>\n"
+ "stream\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
+ if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
+
+ // copy font file
+ beginCompression();
+ checkAndEnableStreamEncryption( nFontStream );
+ sal_Bool bEOF = false;
+ do
+ {
+ char buf[8192];
+ sal_uInt64 nRead;
+ if ( osl::File::E_None != aFontFile.read(buf, sizeof(buf), nRead) ) return false;
+ if ( !writeBuffer( buf, nRead ) ) return false;
+ if ( osl::File::E_None != aFontFile.isEndOfFile(&bEOF) ) return false;
+ } while( ! bEOF );
+ }
+ 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?
+ {
+ std::unique_ptr<unsigned char[]> xBuffer(new unsigned char[nLength1]);
+
+ sal_uInt64 nBytesRead = 0;
+ if ( osl::File::E_None != aFontFile.read(xBuffer.get(), nLength1, nBytesRead) ) return false;
+ SAL_WARN_IF( nBytesRead!=nLength1, "vcl.pdfwriter", "PDF-FontSubset read incomplete!" );
+ if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
+ // get the PFB-segment lengths
+ ThreeInts aSegmentLengths = {0,0,0};
+ getPfbSegmentLengths(xBuffer.get(), static_cast<int>(nBytesRead), aSegmentLengths);
+ // the lengths below are mandatory for PDF-exported Type1 fonts
+ // because the PFB segment headers get stripped! WhyOhWhy.
+ aLine.append( static_cast<sal_Int32>(aSegmentLengths[0]) );
+ aLine.append( "/Length2 " );
+ aLine.append( static_cast<sal_Int32>(aSegmentLengths[1]) );
+ aLine.append( "/Length3 " );
+ aLine.append( static_cast<sal_Int32>(aSegmentLengths[2]) );
+
+ aLine.append( ">>\n"
+ "stream\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
+ if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
+
+ // emit PFB-sections without section headers
+ beginCompression();
+ checkAndEnableStreamEncryption( nFontStream );
+ if ( !writeBuffer( &xBuffer[6], aSegmentLengths[0] ) ) return false;
+ if ( !writeBuffer( &xBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false;
+ if ( !writeBuffer( &xBuffer[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();
+ // close the file
+ aFontFile.close();
+
+ 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.getStr(), aLine.getLength() ) ) return false;
+
+ // emit stream length object
+ if ( !updateObject( nStreamLengthObject ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( nStreamLengthObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
+ aLine.append( "\nendobj\n\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
+
+ // write font descriptor
+ sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream );
+
+ if( nToUnicodeStream )
+ nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits.data(), pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
+
+ sal_Int32 nFontObject = createObject();
+ if ( !updateObject( nFontObject ) ) return false;
+ aLine.setLength( 0 );
+ aLine.append( nFontObject );
+
+ aLine.append( " 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 " );
+ aLine.append( static_cast<sal_Int32>(nGlyphs-1) );
+ aLine.append( "\n"
+ "/Widths[" );
+ for( int i = 0; i < nGlyphs; i++ )
+ {
+ aLine.append( pWidths[ i ] );
+ aLine.append( ((i & 15) == 15) ? "\n" : " " );
+ }
+ aLine.append( "]\n"
+ "/FontDescriptor " );
+ aLine.append( nFontDescriptor );
+ aLine.append( " 0 R\n" );
+ if( nToUnicodeStream )
+ {
+ aLine.append( "/ToUnicode " );
+ aLine.append( nToUnicodeStream );
+ aLine.append( " 0 R\n" );
+ }
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
+
+ aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
+ }
+ else
+ {
+ const PhysicalFontFace* pFont = subset.first;
+ OStringBuffer aErrorComment( 256 );
+ aErrorComment.append( "CreateFontSubset failed for font \"" );
+ aErrorComment.append( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) );
+ aErrorComment.append( '\"' );
+ if( pFont->GetItalic() == ITALIC_NORMAL )
+ aErrorComment.append( " italic" );
+ else if( pFont->GetItalic() == ITALIC_OBLIQUE )
+ aErrorComment.append( " oblique" );
+ aErrorComment.append( " weight=" );
+ aErrorComment.append( sal_Int32(pFont->GetWeight()) );
+ emitComment( aErrorComment.getStr() );
+ }
+ }
+ }
+ osl_removeFile( aTmpName.pData );
+
+ // 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;
+ }
+ }
+
+ OStringBuffer aFontDict( 1024 );
+ aFontDict.append( getFontDictObject() );
+ aFontDict.append( " 0 obj\n"
+ "<<" );
+ int ni = 0;
+ for (auto const& itemMap : aFontIDToObject)
+ {
+ aFontDict.append( "/F" );
+ aFontDict.append( itemMap.first );
+ aFontDict.append( ' ' );
+ aFontDict.append( itemMap.second );
+ aFontDict.append( " 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.getStr(), aFontDict.getLength() ) ) 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( nResourceDict );
+ aLine.append( " 0 obj\n" );
+ m_aGlobalResourceDict.append( aLine, getFontDictObject() );
+ aLine.append( "endobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ 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( rItem.m_nObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( "<<" );
+ // number of visible children (all levels)
+ if( i > 0 || aCounts[0] > 0 )
+ {
+ aLine.append( "/Count " );
+ aLine.append( aCounts[i] );
+ }
+ if( ! rItem.m_aChildren.empty() )
+ {
+ // children list: First, Last
+ aLine.append( "/First " );
+ aLine.append( m_aOutline[rItem.m_aChildren.front()].m_nObject );
+ aLine.append( " 0 R/Last " );
+ aLine.append( m_aOutline[rItem.m_aChildren.back()].m_nObject );
+ aLine.append( " 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 && rItem.m_nDestID < static_cast<sal_Int32>(m_aDests.size()) )
+ {
+ aLine.append( "/Dest" );
+ appendDest( rItem.m_nDestID, aLine );
+ }
+ aLine.append( "/Parent " );
+ aLine.append( rItem.m_nParentObject );
+ aLine.append( " 0 R" );
+ if( rItem.m_nPrevObject )
+ {
+ aLine.append( "/Prev " );
+ aLine.append( rItem.m_nPrevObject );
+ aLine.append( " 0 R" );
+ }
+ if( rItem.m_nNextObject )
+ {
+ aLine.append( "/Next " );
+ aLine.append( rItem.m_nNextObject );
+ aLine.append( " 0 R" );
+ }
+ }
+ aLine.append( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+
+ 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 || nDestID >= static_cast<sal_Int32>(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;
+}
+
+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.getStr(), aLine.getLength()));
+ aLine.setLength(0);
+
+ CHECK_RETURN(writeBuffer(aMemoryStream.GetData(), aMemoryStream.GetSize()));
+
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
+ aLine.setLength(0);
+ }
+
+ if (!updateObject(rScreen.m_nObject))
+ continue;
+
+ // Annot dictionary.
+ aLine.append(rScreen.m_nObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<</Type/Annot");
+ aLine.append("/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 ");
+ aLine.append(rScreen.m_nObject);
+ aLine.append(" 0 R ");
+
+ // Rendition dictionary.
+ aLine.append("/R<</Type/Rendition /S/MR ");
+
+ // MediaClip dictionary.
+ aLine.append("/C<</Type/MediaClip /S/MCD ");
+ if (bEmbed)
+ {
+ aLine.append("/D << /Type /Filespec /F (<embedded file>) /EF << /F ");
+ aLine.append(rScreen.m_nTempFileObject);
+ aLine.append(" 0 R >> >>");
+ }
+ else
+ {
+ // Linked.
+ aLine.append("/D << /Type /Filespec /FS /URL /F ");
+ appendLiteralStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine, osl_getThreadTextEncoding());
+ aLine.append(" >>");
+ }
+ // Allow playing the video via a tempfile.
+ aLine.append("/P <</TF (TEMPACCESS)>>");
+ // Until the real MIME type (instead of application/vnd.sun.star.media) is available here.
+ aLine.append("/CT (video/mp4)");
+ aLine.append(">>");
+
+ // End Rendition dictionary by requesting play/pause/stop controls.
+ aLine.append("/P<</BE<</C true >>>>");
+ aLine.append(">>");
+
+ // End Action dictionary.
+ aLine.append("/OP 0 >>");
+
+ // End Annot dictionary.
+ aLine.append("/P ");
+ aLine.append(m_aPages[rScreen.m_nPage].m_nPageObject);
+ aLine.append(" 0 R\n>>\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
+ }
+
+ 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( "]" );
+ 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;
+
+ // 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 aNewBase( aDocumentURL );//duplicate document URL
+ aNewBase.removeSegment(); //remove last segment from it, obtaining the base URL of the
+ //target document
+ aNewBase.insertName( url );
+ aTargetURL = aNewBase;//reassign the new target URL
+ //recompute the target protocol, with the new URL
+ //normal URL processing resumes
+ eTargetProtocol = aTargetURL.GetProtocol();
+ }
+ }
+
+ 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("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.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US) );
+ }
+ OUString aURL = 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 > 0 )
+ {
+ aLine.append( "/StructParent " );
+ aLine.append( rLink.m_nStructParent );
+ }
+ aLine.append( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+
+ return true;
+}
+
+bool PDFWriterImpl::emitNoteAnnotations()
+{
+ // emit note annotations
+ int nAnnots = m_aNotes.size();
+ for( int i = 0; i < nAnnots; i++ )
+ {
+ const PDFNoteEntry& rNote = m_aNotes[i];
+ if( ! updateObject( rNote.m_nObject ) )
+ return false;
+
+ OStringBuffer aLine( 1024 );
+ aLine.append( rNote.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/Text/Rect[" );
+
+ appendFixedInt( rNote.m_aRect.Left(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rNote.m_aRect.Top(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rNote.m_aRect.Right(), aLine );
+ aLine.append( ' ' );
+ appendFixedInt( rNote.m_aRect.Bottom(), aLine );
+ aLine.append( "]" );
+
+ // contents of the note (type text string)
+ aLine.append( "/Contents\n" );
+ 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( ">>\nendobj\n\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ }
+ 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
+ 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 = Application::GetSettings().GetStyleSettings();
+
+ // 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" ][ "Standard" ] = 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";
+ rButton.m_aMKDictCAString = "";
+}
+
+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 = Application::GetSettings().GetStyleSettings();
+ SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 );
+
+ push( PushFlags::ALL );
+
+ // prepare font to use, draw field border
+ Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
+ sal_Int32 nBest = getSystemFont( aFont );
+
+ // prepare DA string
+ OStringBuffer aDA( 32 );
+ 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" );
+ 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 );
+ OString aAppearance = "/Tx BMC\nEMC\n";
+ writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
+
+ endRedirect();
+ pop();
+
+ rEdit.m_aAppearances[ "N" ][ "Standard" ] = pEditStream;
+
+ rEdit.m_aDAString = aDA.makeStringAndClear();
+}
+
+void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
+{
+ const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
+ 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
+ OString aAppearance = "/Tx BMC\nEMC\n";
+ writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
+
+ endRedirect();
+ pop();
+
+ rBox.m_aAppearances[ "N" ][ "Standard" ] = 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 = Application::GetSettings().GetStyleSettings();
+
+ // 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.getStr(), aLW.getLength() );
+ drawRectangle( aCheckRect );
+ writeBuffer( " Q\n", 3 );
+ 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() ) );
+ FontCharMapRef pMap;
+ GetFontCharMap(pMap);
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ const PhysicalFontFace* pDevFont = pFontInstance->GetFontFace();
+ Pop();
+
+ // make sure OpenSymbol is embedded, and includes our checkmark
+ const sal_Unicode cMark=0x2713;
+ const GlyphItem aItem(0, 0, pMap->GetGlyphIndex(cMark),
+ Point(), GlyphItemFlags::NONE, 0, 0,
+ const_cast<LogicalFontInstance*>(pFontInstance));
+ const std::vector<sal_Ucs> aCodeUnits={ cMark };
+ sal_uInt8 nMappedGlyph;
+ sal_Int32 nMappedFontObject;
+ registerGlyph(&aItem, pDevFont, aCodeUnits, 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";
+ rBox.m_aMKDictCAString = "8";
+ 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.getStr(), aDA.getLength() );
+ endRedirect();
+ rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
+
+ // write 'unchecked' appearance stream
+ SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
+ beginRedirect( pUncheckStream, aCheckRect );
+ writeBuffer( "/Tx BMC\nEMC\n", 12 );
+ endRedirect();
+ rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
+}
+
+void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
+{
+ const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
+
+ // 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.getStr(), aLW.getLength() );
+ drawEllipse( aCheckRect );
+ writeBuffer( " Q\n", 3 );
+ setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
+ drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
+
+ pop();
+
+ OStringBuffer aDA( 256 );
+ appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
+ rBox.m_aDAString = aDA.makeStringAndClear();
+ //to encrypt this (el)
+ rBox.m_aMKDict = "/CA";
+ //after this assignment, to m_aMKDic cannot be added anything
+ rBox.m_aMKDictCAString = "l";
+
+ rBox.m_aRect = aCheckRect;
+
+ // create appearance streams
+ push( PushFlags::ALL);
+ 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( ' ' );
+ m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
+ aDA.append( " 0 0 Td\nET\nQ\n" );
+ writeBuffer( aDA.getStr(), aDA.getLength() );
+ 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", 5 );
+ endRedirect();
+
+ pop();
+ rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
+
+ SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
+ beginRedirect( pUncheckStream, aCheckRect );
+ writeBuffer( "/Tx BMC\nEMC\n", 12 );
+ endRedirect();
+ rBox.m_aAppearances[ "N" ][ "Off" ] = 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* pApppearanceStream = stream_item.second;
+ dict_item.second[ stream_item.first ] = nullptr;
+
+ bool bDeflate = compressStream( pApppearanceStream );
+
+ sal_Int64 nStreamLen = pApppearanceStream->TellEnd();
+ pApppearanceStream->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.getStr(), aLine.getLength() ) );
+ checkAndEnableStreamEncryption( nObject );
+ CHECK_RETURN( writeBuffer( pApppearanceStream->GetData(), nStreamLen ) );
+ disableStreamEncryption();
+ CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n", 19 ) );
+
+ if( bUseSubDict )
+ {
+ rAnnotDict.append( " /" );
+ rAnnotDict.append( stream_item.first );
+ rAnnotDict.append( " " );
+ }
+ rAnnotDict.append( nObject );
+ rAnnotDict.append( " 0 R" );
+
+ delete pApppearanceStream;
+ }
+
+ 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];
+
+ 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;
+ }
+
+ 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 && nEntry < sal_Int32(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 &&
+ rWidget.m_aSelectedEntries[0] < sal_Int32(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( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !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 && rWidget.m_nMaxLen > 0 )
+ {
+ aLine.append( "/MaxLen " );
+ aLine.append( rWidget.m_nMaxLen );
+ 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.makeStringAndClear() );
+ 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:
+ if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ nFlags |= 32;
+ break;
+ case PDFWriter::PDF:
+ if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ 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.getStr(), aLine.getLength() ) );
+ }
+ 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;
+}
+
+bool PDFWriterImpl::emitEmbeddedFiles()
+{
+ for (auto& rEmbeddedFile : m_aEmbeddedFiles)
+ {
+ if (!updateObject(rEmbeddedFile.m_nObject))
+ continue;
+
+ OStringBuffer aLine;
+ aLine.append(rEmbeddedFile.m_nObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /EmbeddedFile /Length ");
+ aLine.append(static_cast<sal_Int64>(rEmbeddedFile.m_pData->size()));
+ aLine.append(" >>\nstream\n");
+ CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
+ aLine.setLength(0);
+
+ CHECK_RETURN(writeBuffer(rEmbeddedFile.m_pData->data(), rEmbeddedFile.m_pData->size()));
+
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
+ }
+ 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)
+ {
+ // 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" );
+
+ sal_Int32 nMediaBoxWidth = 0;
+ sal_Int32 nMediaBoxHeight = 0;
+ sal_Int32 nUserUnit = 1;
+ if( m_aPages.empty() ) // sanity check, this should not happen
+ {
+ nMediaBoxWidth = g_nInheritedPageWidth;
+ nMediaBoxHeight = g_nInheritedPageHeight;
+ }
+ else
+ {
+ for (auto const& page : m_aPages)
+ {
+ if( page.m_nPageWidth > nMediaBoxWidth )
+ {
+ nMediaBoxWidth = page.m_nPageWidth;
+ nUserUnit = page.m_nUserUnit;
+ }
+ if( page.m_nPageHeight > nMediaBoxHeight )
+ {
+ nMediaBoxHeight = page.m_nPageHeight;
+ nUserUnit = page.m_nUserUnit;
+ }
+ }
+ }
+ aLine.append( "/MediaBox[ 0 0 " );
+ aLine.append(nMediaBoxWidth / nUserUnit);
+ aLine.append( ' ' );
+ aLine.append(nMediaBoxHeight / nUserUnit);
+ aLine.append(" ]\n");
+ if (nUserUnit > 1)
+ {
+ aLine.append("/UserUnit ");
+ aLine.append(nUserUnit);
+ aLine.append("\n");
+ }
+ 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.getStr(), aLine.getLength() ) );
+
+ // emit annotation objects
+ CHECK_RETURN( emitAnnotations() );
+ CHECK_RETURN( emitEmbeddedFiles() );
+
+ // 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_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 && m_aContext.InitialPage < static_cast<sal_Int32>(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.makeStringAndClear() );
+ aLine.append( " /XYZ null null 0]\n" );
+ }
+ break;
+ case PDFWriter::FitInWindow :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef.makeStringAndClear() );
+ aLine.append( " /Fit]\n" ); //Open fit page
+ break;
+ case PDFWriter::FitWidth :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef.makeStringAndClear() );
+ aLine.append( " /FitH " );
+ aLine.append( g_nInheritedPageHeight );//Open fit width
+ aLine.append( "]\n" );
+ break;
+ case PDFWriter::FitVisible :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef.makeStringAndClear() );
+ aLine.append( " /FitBH " );
+ aLine.append( g_nInheritedPageHeight );//Open fit visible
+ aLine.append( "]\n" );
+ break;
+ case PDFWriter::ActionZoom :
+ aLine.append( "/OpenAction[" );
+ aLine.append( aInitPageRef.makeStringAndClear() );
+ 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.Version > PDFWriter::PDFVersion::PDF_1_3 && !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.Version > PDFWriter::PDFVersion::PDF_1_3 && !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.makeStringAndClear(), m_nCatalogObject, aLine );
+ aLine.append( "\n" );
+ }
+ }
+ if( m_aContext.Tagged && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ {
+ 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.getStr(), aLine.getLength() );
+}
+
+#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.makeStringAndClear() );
+ 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.makeStringAndClear() );
+ 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.getStr(), aLine.getLength() );
+}
+
+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.getStr(), aLine.getLength() ) )
+ 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( "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.getStr(), aLine.getLength() ) )
+ 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.getStr(), aLine.getLength() ) ) 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 = writeBuffer( 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", 19 ) )
+ 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.getStr(), aLine.getLength() ) ) 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");
+
+ OUString const aComment( "sRGB IEC61966-2.1" );
+ appendLiteralStringEncrypt( aComment ,nOIObject, aLine );
+ aLine.append("/DestOutputProfile ");
+ aLine.append( nICCObject );
+ aLine.append( " 0 R>>\nendobj\n\n" );
+ if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
+
+ return nOIObject;
+}
+
+// formats the string for the XML stream
+static void escapeStringXML( const OUString& rStr, OUString &rValue)
+{
+ const sal_Unicode* pUni = rStr.getStr();
+ int nLen = rStr.getLength();
+ for( ; nLen; nLen--, pUni++ )
+ {
+ switch( *pUni )
+ {
+ case u'&':
+ rValue += "&amp;";
+ break;
+ case u'<':
+ rValue += "&lt;";
+ break;
+ case u'>':
+ rValue += "&gt;";
+ break;
+ case u'\'':
+ rValue += "&apos;";
+ break;
+ case u'"':
+ rValue += "&quot;";
+ break;
+ default:
+ rValue += OUStringChar( *pUni );
+ break;
+ }
+ }
+}
+
+static void lcl_assignMeta(const OUString& aValue, OString& aMeta)
+{
+ if (!aValue.isEmpty())
+ {
+ OUString aTempString;
+ escapeStringXML(aValue, aTempString);
+ aMeta = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8);
+ }
+}
+
+// emits the document metadata
+sal_Int32 PDFWriterImpl::emitDocumentMetadata()
+{
+ if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 )
+ 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);
+ lcl_assignMeta(m_aContext.DocumentInfo.Keywords, aMetadata.msKeywords);
+ 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.getStr(), aMetadataObj.getLength() ) )
+ return 0;
+ //emit the stream
+ if ( !writeBuffer( aMetadata.getData(), aMetadata.getSize() ) )
+ return 0;
+
+ aMetadataObj.setLength( 0 );
+ aMetadataObj.append( "\nendstream\nendobj\n\n" );
+ if( ! writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
+ 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.getStr(), aLineS.getLength() ) )
+ 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", 5 ) );
+
+ 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.getStr(), aLine.getLength() ) );
+
+ 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.getStr(), aLine.getLength() ) );
+ }
+
+ // 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.makeStringAndClear() );
+ aLine.append( "\n" );
+ }
+ if( !m_aAdditionalStreams.empty() )
+ {
+ aLine.append( "/AdditionalStreams [" );
+ for(const PDFAddStream & rAdditionalStream : m_aAdditionalStreams)
+ {
+ aLine.append( "/" );
+ appendName( rAdditionalStream.m_aMimeType, aLine );
+ aLine.append( " " );
+ aLine.append( rAdditionalStream.m_nStreamObject );
+ aLine.append( " 0 R\n" );
+ }
+ aLine.append( "]\n" );
+ }
+ aLine.append( ">>\n"
+ "startxref\n" );
+ aLine.append( static_cast<sal_Int64>(nXRefOffset) );
+ aLine.append( "\n"
+ "%%EOF\n" );
+ return writeBuffer( aLine.getStr(), aLine.getLength() );
+}
+
+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
+{
+ std::set< 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<long int>(item.second.aSortedAnnots.size()) << " sorted and " <<
+ static_cast<long int>(nAnnots) << " unsorted");
+ }
+ }
+
+ // FIXME: implement tab order in structure tree for PDF 1.5
+}
+
+namespace vcl {
+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;
+ virtual void SAL_CALL flush() override;
+ virtual void SAL_CALL closeOutput() override;
+};
+}
+
+void SAL_CALL PDFStreamIf::writeBytes( const css::uno::Sequence< sal_Int8 >& aData )
+{
+ if( m_bWrite && aData.hasElements() )
+ {
+ sal_Int32 nBytes = aData.getLength();
+ m_pWriter->writeBuffer( aData.getConstArray(), nBytes );
+ }
+}
+
+void SAL_CALL PDFStreamIf::flush()
+{
+}
+
+void SAL_CALL PDFStreamIf::closeOutput()
+{
+ m_bWrite = false;
+}
+
+bool PDFWriterImpl::emitAdditionalStreams()
+{
+ unsigned int nStreams = m_aAdditionalStreams.size();
+ for( unsigned int i = 0; i < nStreams; i++ )
+ {
+ PDFAddStream& rStream = m_aAdditionalStreams[i];
+ rStream.m_nStreamObject = createObject();
+ sal_Int32 nSizeObject = createObject();
+
+ if( ! updateObject( rStream.m_nStreamObject ) )
+ return false;
+
+ OStringBuffer aLine;
+ aLine.append( rStream.m_nStreamObject );
+ aLine.append( " 0 obj\n<</Length " );
+ aLine.append( nSizeObject );
+ aLine.append( " 0 R" );
+ if( rStream.m_bCompress )
+ aLine.append( "/Filter/FlateDecode" );
+ aLine.append( ">>\nstream\n" );
+ if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
+ return false;
+ sal_uInt64 nBeginStreamPos = 0, nEndStreamPos = 0;
+ if( osl::File::E_None != m_aFile.getPos(nBeginStreamPos) )
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ }
+ if( rStream.m_bCompress )
+ beginCompression();
+
+ checkAndEnableStreamEncryption( rStream.m_nStreamObject );
+ css::uno::Reference< css::io::XOutputStream > xStream( new PDFStreamIf( this ) );
+ assert(rStream.m_pStream);
+ if (!rStream.m_pStream)
+ return false;
+ rStream.m_pStream->write( xStream );
+ xStream.clear();
+ delete rStream.m_pStream;
+ rStream.m_pStream = nullptr;
+ disableStreamEncryption();
+
+ if( rStream.m_bCompress )
+ endCompression();
+
+ if (osl::File::E_None != m_aFile.getPos(nEndStreamPos))
+ {
+ m_aFile.close();
+ m_bOpen = false;
+ return false;
+ }
+ if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
+ return false ;
+ // emit stream length object
+ if( ! updateObject( nSizeObject ) )
+ return false;
+ aLine.setLength( 0 );
+ aLine.append( nSizeObject );
+ aLine.append( " 0 obj\n" );
+ aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
+ aLine.append( "\nendobj\n\n" );
+ if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
+ return false;
+ }
+ return true;
+}
+
+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 additional streams
+ CHECK_RETURN( emitAdditionalStreams() );
+
+ // 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 PhysicalFontFace* pDevFont = GetFontInstance()->GetFontFace();
+ sal_Int32 nFontID = 0;
+ auto it = m_aSystemFonts.find( pDevFont );
+ if( it != m_aSystemFonts.end() )
+ nFontID = it->second.m_nNormalFontID;
+ else
+ {
+ nFontID = m_nNextFID++;
+ m_aSystemFonts[ pDevFont ] = EmbedFont();
+ m_aSystemFonts[ pDevFont ].m_nNormalFontID = nFontID;
+ }
+
+ Pop();
+ return nFontID;
+}
+
+void PDFWriterImpl::registerGlyph(const GlyphItem* pGlyph,
+ const PhysicalFontFace* pFont,
+ const std::vector<sal_Ucs>& rCodeUnits,
+ sal_uInt8& nMappedGlyph,
+ sal_Int32& nMappedFontObject)
+{
+ const int nFontGlyphId = pGlyph->glyphId();
+ FontSubset& rSubset = m_aSubsets[ pFont ];
+ // 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 );
+ 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::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 );
+ 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();
+
+ long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24);
+ if( rFont.IsOutline() )
+ nOff++;
+ rLayout.DrawBase() += Point( nOff, nOff );
+ drawLayout( rLayout, rText, bTextLines );
+ rLayout.DrawBase() -= Point( 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,
+ double fSkew,
+ sal_Int32 nFontHeight )
+{
+ long nXOffset = 0;
+ Point aCurPos( rGlyphs[0].m_aPos );
+ aCurPos = PixelToLogic( aCurPos );
+ 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;
+ double fSkewB = fSkew;
+ double fSkewA = 0.0;
+
+ Point aDeltaPos;
+ if (rGlyphs[i].m_pGlyph->IsVertical())
+ {
+ fDeltaAngle = M_PI/2.0;
+ aDeltaPos.setX( GetFontMetric().GetAscent() );
+ aDeltaPos.setY( static_cast<int>(static_cast<double>(GetFontMetric().GetDescent()) * fXScale) );
+ fYScale = fXScale;
+ fTempXScale = 1.0;
+ fSkewA = -fSkewB;
+ fSkewB = 0.0;
+ }
+ aDeltaPos += PixelToLogic( Point( static_cast<int>(static_cast<double>(nXOffset)/fXScale), 0 ) ) - PixelToLogic( Point() );
+ if( i < rGlyphs.size()-1 )
+ // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
+ {
+ long nOffsetX = rGlyphs[i+1].m_aPos.X() - rGlyphs[i].m_aPos.X();
+ long nOffsetY = rGlyphs[i+1].m_aPos.Y() - rGlyphs[i].m_aPos.Y();
+ nXOffset += static_cast<int>(sqrt(double(nOffsetX*nOffsetX + nOffsetY*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,
+ double fSkew,
+ 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_aPos.Y() != rGlyphs[i-1].m_aPos.Y() )
+ {
+ 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
+ Point aCurPos = rGlyphs[nBeginRun].m_aPos;
+ // back transformation to current coordinate system
+ aCurPos = PixelToLogic( aCurPos );
+ aCurPos += rAlignOffset;
+ // 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 Point aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
+ const Point aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
+ double fAdvance = aThisPos.X() - aPrevPos.X();
+ 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).makeStringAndClear() );
+
+ // 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;
+ double fSkew = 0.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());
+ }
+ }
+
+ // perform artificial italics if necessary
+ if( ( m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_NORMAL ||
+ m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_OBLIQUE ) &&
+ !( GetFontInstance()->GetFontFace()->GetItalic() == ITALIC_NORMAL ||
+ GetFontInstance()->GetFontFace()->GetItalic() == ITALIC_OBLIQUE )
+ )
+ {
+ fSkew = M_PI/12.0;
+ }
+
+ // 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());
+ }
+
+ int nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
+ // normalize angles
+ while( nAngle < 0 )
+ nAngle += 3600;
+ nAngle = nAngle % 3600;
+ double fAngle = static_cast<double>(nAngle) * M_PI / 1800.0;
+
+ 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()->GetFontFace()->GetWeight() <= WEIGHT_MEDIUM &&
+ GetFontInstance()->GetFontSelectPattern().GetWeight() > WEIGHT_MEDIUM )
+ {
+ 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 PhysicalFontFace* pDevFont = GetFontInstance()->GetFontFace();
+ const GlyphItem* pGlyph = nullptr;
+ const PhysicalFontFace* pFallbackFont = 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
+ Point aPos;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pFallbackFont))
+ {
+ const auto* pFont = pFallbackFont ? pFallbackFont : pDevFont;
+
+ 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;
+
+ // Or part of a complex cluster, will be handled by the ActualText
+ // of its cluster start.
+ if (pGlyph->IsInCluster())
+ assert(aCodeUnits.empty());
+
+ // A glyph can’t have more than one ToUnicode entry, use ActualText
+ // instead.
+ if (!aCodeUnits.empty() && !bUseActualText)
+ {
+ for (const auto& rSubset : m_aSubsets[pFont].m_aSubsets)
+ {
+ const auto& it = rSubset.m_aMapping.find(pGlyph->glyphId());
+ if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
+ {
+ bUseActualText = true;
+ aCodeUnits.clear();
+ }
+ }
+ }
+
+ assert(!aCodeUnits.empty() || bUseActualText || pGlyph->IsInCluster());
+
+ sal_uInt8 nMappedGlyph;
+ sal_Int32 nMappedFontObject;
+ registerGlyph(pGlyph, pFont, aCodeUnits, nMappedGlyph, nMappedFontObject);
+
+ sal_Int32 nGlyphWidth = 0;
+ SalGraphics *pGraphics = GetGraphics();
+ if (pGraphics)
+ nGlyphWidth = m_aFontCache.getGlyphWidth(pFont,
+ pGlyph->glyphId(),
+ pGlyph->IsVertical(),
+ pGraphics);
+
+ int nCharPos = -1;
+ if (bUseActualText || pGlyph->IsInCluster())
+ nCharPos = pGlyph->charPos();
+
+ aGlyphs.emplace_back(aPos,
+ pGlyph,
+ 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.
+ tools::Rectangle aRectangle;
+ // This is the top left of the text without ascent / descent.
+ aRectangle.SetPos(PixelToLogic(rLayout.GetDrawPosition()));
+ aRectangle.setY(aRectangle.getY() - aRefDevFontMetric.GetAscent());
+ aRectangle.SetSize(PixelToLogic(Size(rLayout.GetTextWidth(), 0)));
+ // 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(PixelToLogic(rLayout.GetDrawPosition()), 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, fSkew, nFontHeight);
+ else
+ drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, fSkew, 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.getStr(), aLine.getLength() );
+
+ // 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() )
+ {
+ Point aStartPt;
+ sal_Int32 nWidth = 0;
+ nIndex = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
+ {
+ if (!pGlyph->IsSpacing())
+ {
+ if( !nWidth )
+ aStartPt = aPos;
+
+ nWidth += pGlyph->m_nNewWidth;
+ }
+ else if( nWidth > 0 )
+ {
+ drawTextLine( PixelToLogic( aStartPt ),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ nWidth = 0;
+ }
+ }
+
+ if( nWidth > 0 )
+ {
+ drawTextLine( PixelToLogic( aStartPt ),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+ }
+ else
+ {
+ Point aStartPt = rLayout.GetDrawPosition();
+ int nWidth = rLayout.GetTextWidth() / rLayout.GetUnitsPerPixel();
+ drawTextLine( PixelToLogic( aStartPt ),
+ ImplDevicePixelToLogicWidth( nWidth ),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+ }
+
+ // write eventual emphasis marks
+ if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) )
+ return;
+
+ tools::PolyPolygon aEmphPoly;
+ tools::Rectangle aEmphRect1;
+ tools::Rectangle aEmphRect2;
+ long nEmphYOff;
+ long nEmphWidth;
+ long nEmphHeight;
+ bool bEmphPolyLine;
+ FontEmphasisMark nEmphMark;
+
+ push( PushFlags::ALL );
+
+ aLine.setLength( 0 );
+ aLine.append( "q\n" );
+
+ nEmphMark = OutputDevice::ImplGetEmphasisMarkStyle( m_aCurrentPDFState.m_aFont );
+ if ( nEmphMark & FontEmphasisMark::PosBelow )
+ nEmphHeight = GetEmphasisDescent();
+ else
+ nEmphHeight = GetEmphasisAscent();
+ ImplGetEmphasisMark( aEmphPoly,
+ bEmphPolyLine,
+ aEmphRect1,
+ aEmphRect2,
+ nEmphYOff,
+ nEmphWidth,
+ nEmphMark,
+ ImplDevicePixelToLogicWidth(nEmphHeight) );
+ if ( bEmphPolyLine )
+ {
+ setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
+ setFillColor( COL_TRANSPARENT );
+ }
+ else
+ {
+ setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
+ setLineColor( COL_TRANSPARENT );
+ }
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ Point aOffset(0,0);
+
+ if ( nEmphMark & FontEmphasisMark::PosBelow )
+ aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + nEmphYOff );
+ else
+ aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + nEmphYOff) );
+
+ long nEmphWidth2 = nEmphWidth / 2;
+ 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() );
+
+ nIndex = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
+ {
+ if (pGlyph->IsSpacing())
+ {
+ Point aAdjOffset = aOffset;
+ aAdjOffset.AdjustX((pGlyph->m_nNewWidth - nEmphWidth) / 2 );
+ aAdjOffset = aRotScale.transform( aAdjOffset );
+
+ aAdjOffset -= Point( nEmphWidth2, nEmphHeight2 );
+
+ aPos += aAdjOffset;
+ aPos = PixelToLogic( aPos );
+ drawEmphasisMark( aPos.X(), aPos.Y(),
+ aEmphPoly, bEmphPolyLine,
+ aEmphRect1, aEmphRect2 );
+ }
+ }
+
+ writeBuffer( "Q\n", 2 );
+ pop();
+
+}
+
+void PDFWriterImpl::drawEmphasisMark( long nX, 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
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos );
+ if( pLayout )
+ {
+ drawLayout( *pLayout, rText, bTextLines );
+ }
+}
+
+void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, const long* pDXArray, 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
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray );
+ if( pLayout )
+ {
+ drawLayout( *pLayout, rText, true );
+ }
+}
+
+void PDFWriterImpl::drawStretchText( const Point& rPos, sal_uLong 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
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth );
+ if( pLayout )
+ {
+ drawLayout( *pLayout, rText, true );
+ }
+}
+
+void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle )
+{
+ long nWidth = rRect.GetWidth();
+ 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.getStr(), aLine.getLength() );
+
+ // if disabled text is needed, put in here
+
+ Point aPos = rRect.TopLeft();
+
+ long nTextHeight = GetTextHeight();
+ sal_Int32 nMnemonicPos = -1;
+
+ OUString aStr = rOrigStr;
+ if ( nStyle & DrawTextFlags::Mnemonic )
+ aStr = OutputDevice::GetNonMnemonicString( aStr, nMnemonicPos );
+
+ // multiline text
+ if ( nStyle & DrawTextFlags::MultiLine )
+ {
+ OUString aLastLine;
+ ImplMultiTextLineInfo aMultiLineInfo;
+ ImplTextLineInfo* pLineInfo;
+ sal_Int32 i;
+ sal_Int32 nLines;
+ sal_Int32 nFormatLines;
+
+ if ( nTextHeight )
+ {
+ vcl::DefaultTextLayout aLayout( *this );
+ OutputDevice::ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, aLayout );
+ nLines = nHeight/nTextHeight;
+ nFormatLines = aMultiLineInfo.Count();
+ if ( !nLines )
+ nLines = 1;
+ if ( nFormatLines > nLines )
+ {
+ if ( nStyle & DrawTextFlags::EndEllipsis )
+ {
+ // handle last line
+ nFormatLines = nLines-1;
+
+ pLineInfo = aMultiLineInfo.GetLine( nFormatLines );
+ aLastLine = convertLineEnd(aStr.copy(pLineInfo->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++ )
+ {
+ pLineInfo = aMultiLineInfo.GetLine( i );
+ if ( nStyle & DrawTextFlags::Right )
+ aPos.AdjustX(nWidth-pLineInfo->GetWidth() );
+ else if ( nStyle & DrawTextFlags::Center )
+ aPos.AdjustX((nWidth-pLineInfo->GetWidth())/2 );
+ sal_Int32 nIndex = pLineInfo->GetIndex();
+ sal_Int32 nLineLen = pLineInfo->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
+ {
+ 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.getStr(), aLine.getLength() );
+}
+
+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.getStr(), aLine.getLength() );
+}
+
+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.getStr(), aLine.getLength() );
+ }
+ 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, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
+{
+ // note: units in pFontInstance are ref device pixel
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ long nLineHeight = 0;
+ 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;
+
+ 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 )
+ {
+ long nOrgLineHeight = nLineHeight;
+ nLineHeight /= 3;
+ if ( nLineHeight < 2 )
+ {
+ if ( nOrgLineHeight > 1 )
+ nLineHeight = 2;
+ else
+ nLineHeight = 1;
+ }
+ long nLineDY = nOrgLineHeight-(nLineHeight*2);
+ if ( nLineDY < nLineWidth )
+ nLineDY = nLineWidth;
+ 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, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
+{
+ // note: units in pFontInstance are ref device pixel
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ long nLineHeight = 0;
+ long nLinePos = 0;
+ 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 = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineOffset() );
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetUnderlineSize() );
+ nLinePos = HCONV( 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 = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset() );
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineOffset() );
+ nLinePos += nLineHeight/2;
+ }
+ break;
+ case LINESTYLE_DOUBLE:
+ if ( bIsAbove )
+ {
+ if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
+ ImplInitAboveTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1() );
+ nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2() );
+ }
+ else
+ {
+ if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1() );
+ nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2() );
+ }
+ break;
+ default:
+ break;
+ }
+
+ if ( !nLineHeight )
+ return;
+
+ // 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 * 1.5), 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, long nWidth, FontStrikeout eStrikeout, Color aColor )
+{
+ // note: units in pFontInstance are ref device pixel
+ const LogicalFontInstance* pFontInstance = GetFontInstance();
+ long nLineHeight = 0;
+ long nLinePos = 0;
+ long nLinePos2 = 0;
+
+ if ( eStrikeout > STRIKEOUT_X )
+ eStrikeout = STRIKEOUT_SINGLE;
+
+ switch ( eStrikeout )
+ {
+ case STRIKEOUT_SINGLE:
+ if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetStrikeoutSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetStrikeoutOffset() );
+ break;
+ case STRIKEOUT_BOLD:
+ if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutOffset() );
+ break;
+ case STRIKEOUT_DOUBLE:
+ if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
+ ImplInitTextLineSize();
+ nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() );
+ nLinePos = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1() );
+ nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2() );
+ break;
+ default:
+ break;
+ }
+
+ if ( !nLineHeight )
+ return;
+
+ 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, 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.replaceAt( 0, 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
+ ComplexTextLayoutFlags nOrigTLM = GetLayoutMode();
+ SetLayoutMode(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, 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 = static_cast<double>(m_aCurrentPDFState.m_aFont.GetOrientation()) * M_PI / 1800.0;
+ Matrix3 aMat;
+ aMat.rotate( fAngle );
+ aMat.translate( aPos.X(), aPos.Y() );
+ m_aPages.back().appendMatrix3(aMat, aLine);
+ aLine.append( " cm\n" );
+
+ if ( aUnderlineColor.GetTransparency() != 0 )
+ aUnderlineColor = 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.getStr(), aLine.getLength() );
+}
+
+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.getStr(), aLine.getLength() );
+}
+
+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.getStr(), aLine.getLength() );
+}
+
+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.getStr(), aLine.getLength() );
+
+ 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 )
+{
+ if( nObject >= 0 )
+ {
+ switch( eKind )
+ {
+ case ResourceKind::XObject:
+ m_aGlobalResourceDict.m_aXObjects[ rResource ] = nObject;
+ if( ! m_aOutputStreams.empty() )
+ m_aOutputStreams.front().m_aResourceDict.m_aXObjects[ rResource ] = nObject;
+ break;
+ case ResourceKind::ExtGState:
+ m_aGlobalResourceDict.m_aExtGStates[ rResource ] = nObject;
+ if( ! m_aOutputStreams.empty() )
+ m_aOutputStreams.front().m_aResourceDict.m_aExtGStates[ rResource ] = nObject;
+ break;
+ case ResourceKind::Shading:
+ m_aGlobalResourceDict.m_aShadings[ rResource ] = nObject;
+ if( ! m_aOutputStreams.empty() )
+ m_aOutputStreams.front().m_aResourceDict.m_aShadings[ rResource ] = nObject;
+ break;
+ case ResourceKind::Pattern:
+ m_aGlobalResourceDict.m_aPatterns[ rResource ] = nObject;
+ if( ! m_aOutputStreams.empty() )
+ m_aOutputStreams.front().m_aResourceDict.m_aPatterns[ rResource ] = nObject;
+ break;
+ }
+ }
+}
+
+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();
+ 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.getStr(), aLine.getLength() );
+
+ 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.getStr(), aLine.getLength() );
+}
+
+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.TopLeft().X() + nHorzRound, rRect.TopLeft().Y() );
+ aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
+ aPoints[2] = Point( rRect.TopRight().X()+1 - nHorzRound, aPoints[1].Y() );
+ aPoints[3] = Point( aPoints[2].X()+kx, aPoints[2].Y() );
+
+ aPoints[5] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y()+nVertRound );
+ aPoints[4] = Point( aPoints[5].X(), aPoints[5].Y()-ky );
+ aPoints[6] = Point( aPoints[5].X(), rRect.BottomRight().Y()+1 - nVertRound );
+ aPoints[7] = Point( aPoints[6].X(), aPoints[6].Y()+ky );
+
+ aPoints[9] = Point( rRect.BottomRight().X()+1-nHorzRound, rRect.BottomRight().Y()+1 );
+ aPoints[8] = Point( aPoints[9].X()+kx, aPoints[9].Y() );
+ aPoints[10] = Point( rRect.BottomLeft().X() + nHorzRound, aPoints[9].Y() );
+ aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
+
+ aPoints[13] = Point( rRect.BottomLeft().X(), rRect.BottomLeft().Y()+1-nVertRound );
+ aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
+ aPoints[14] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y()+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.getStr(), aLine.getLength() );
+}
+
+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.TopLeft().X() + rRect.GetWidth()/2, rRect.TopLeft().Y() );
+ aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
+ aPoints[2] = Point( aPoints[1].X() + kx, aPoints[1].Y() );
+
+ aPoints[4] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y() + 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.BottomLeft().X() + rRect.GetWidth()/2, rRect.BottomLeft().Y()+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.TopLeft().X(), rRect.TopLeft().Y() + 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.getStr(), aLine.getLength() );
+}
+
+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.getStr(), aLine.getLength() );
+}
+
+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.getStr(), aLine.getLength() );
+}
+
+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.getStr(), aLine.getLength() );
+ drawPolyLine( rPoly );
+ writeBuffer( "Q\n", 2 );
+ }
+ 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.clear();
+
+ // add DashDot to DashArray
+ const int nDashes = rIn.GetDashCount();
+ const int nDashLen = rIn.GetDashLen();
+ const int nDistance = rIn.GetDistance();
+
+ for( int n = 0; n < nDashes; n++ )
+ {
+ rOut.m_aDashArray.push_back( nDashLen );
+ rOut.m_aDashArray.push_back( nDistance );
+ }
+ const int nDots = rIn.GetDotCount();
+ const int nDotLen = rIn.GetDotLen();
+
+ for( int n = 0; n < nDots; n++ )
+ {
+ rOut.m_aDashArray.push_back( nDotLen );
+ rOut.m_aDashArray.push_back( nDistance );
+ }
+
+ // 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.getStr(), aLine.getLength() );
+ 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.getStr(), aLine.getLength() );
+ }
+ writeBuffer( "Q\n", 2 );
+
+ if( rInfo.m_fTransparency != 0.0 )
+ {
+ // 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.getStr(), aLine.getLength() );
+
+ setFillColor( aOldFillColor );
+}
+
+void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
+{
+ CHECK_RETURN2( updateObject( rObject.m_nObject ) );
+
+ bool bFlateFilter = compressStream( rObject.m_pContentStream.get() );
+ sal_uLong 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( ! rObject.m_pSoftMaskStream )
+ {
+ 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.getStr(), aLine.getLength() ) );
+ checkAndEnableStreamEncryption( rObject.m_nObject );
+ CHECK_RETURN2( writeBuffer( rObject.m_pContentStream->GetData(), nSize ) );
+ disableStreamEncryption();
+ aLine.setLength( 0 );
+ aLine.append( "\n"
+ "endstream\n"
+ "endobj\n\n" );
+ CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ // write ExtGState dict for this XObject
+ aLine.setLength( 0 );
+ aLine.append( rObject.m_nExtGStateObject );
+ aLine.append( " 0 obj\n"
+ "<<" );
+ if( ! rObject.m_pSoftMaskStream )
+ {
+ 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" );
+ }
+ else
+ {
+ if( m_bIsPDF_A1 )
+ {
+ aLine.append( "/SMask/None" );
+ m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
+ }
+ else
+ {
+ sal_Int32 nMaskSize = static_cast<sal_Int32>(rObject.m_pSoftMaskStream->TellEnd());
+ rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_BEGIN );
+ sal_Int32 nMaskObject = createObject();
+ aLine.append( "/SMask<</Type/Mask/S/Luminosity/G " );
+ aLine.append( nMaskObject );
+ aLine.append( " 0 R>>\n" );
+
+ OStringBuffer aMask;
+ aMask.append( nMaskObject );
+ aMask.append( " 0 obj\n"
+ "<</Type/XObject\n"
+ "/Subtype/Form\n"
+ "/BBox[" );
+ appendFixedInt( rObject.m_aBoundRect.Left(), aMask );
+ aMask.append( ' ' );
+ appendFixedInt( rObject.m_aBoundRect.Top(), aMask );
+ aMask.append( ' ' );
+ appendFixedInt( rObject.m_aBoundRect.Right(), aMask );
+ aMask.append( ' ' );
+ appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aMask );
+ aMask.append( "]\n" );
+
+ // 7.8.3 Resource dicts are required for content streams
+ aMask.append( "/Resources " );
+ aMask.append( getResourceDictObj() );
+ aMask.append( " 0 R\n" );
+
+ aMask.append( "/Group<</S/Transparency/CS/DeviceRGB>>\n" );
+ aMask.append( "/Length " );
+ aMask.append( nMaskSize );
+ aMask.append( ">>\n"
+ "stream\n" );
+ CHECK_RETURN2( updateObject( nMaskObject ) );
+ checkAndEnableStreamEncryption( nMaskObject );
+ CHECK_RETURN2( writeBuffer( aMask.getStr(), aMask.getLength() ) );
+ CHECK_RETURN2( writeBuffer( rObject.m_pSoftMaskStream->GetData(), nMaskSize ) );
+ disableStreamEncryption();
+ aMask.setLength( 0 );
+ aMask.append( "\nendstream\n"
+ "endobj\n\n" );
+ CHECK_RETURN2( writeBuffer( aMask.getStr(), aMask.getLength() ) );
+ }
+ }
+ aLine.append( ">>\n"
+ "endobj\n\n" );
+ CHECK_RETURN2( updateObject( rObject.m_nExtGStateObject ) );
+ CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+}
+
+bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject )
+{
+ // LO internal gradient -> PDF shading type:
+ // * GradientStyle::Linear: axial shading, using sampled-function with 2 samples
+ // [t=0:colorStart, t=1:colorEnd]
+ // * 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 );
+ Bitmap::ScopedReadAccess 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 GradientStyle::Linear:
+ case 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 GradientStyle::Linear:
+ aLine.append('2');
+ break;
+ case 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.getStr(), aLine.getLength() ) );
+
+ 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 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( writeBuffer( aCol, 3 ) );
+ [[fallthrough]];
+ case 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( writeBuffer( 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( writeBuffer( aCol, 3 ) );
+ break;
+ }
+ default:
+ for( int y = aSize.Height()-1; y >= 0; y-- )
+ {
+ for( 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( writeBuffer( 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.getStr(), aLine.getLength() ) );
+
+ // 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.getStr(), aLine.getLength() ) );
+
+ 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 GradientStyle::Linear:
+ case 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;
+ sal_uInt16 nAngle = rObject.m_aGradient.GetAngle() % 3600;
+ rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter );
+
+ const bool bLinear = (rObject.m_aGradient.GetStyle() == 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 GradientStyle::Linear:
+ case 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 - 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.getStr(), aLine.getLength() );
+}
+
+void PDFWriterImpl::writeJPG( JPGEmit& rObject )
+{
+ if (!rObject.m_aReferenceXObject.m_aPDFData.empty() && !m_aContext.UseReferenceXObject)
+ {
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+ return;
+ }
+
+ CHECK_RETURN2( rObject.m_pStream );
+ CHECK_RETURN2( updateObject( rObject.m_nObject ) );
+
+ sal_Int32 nLength = rObject.m_pStream->TellEnd();
+ rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
+
+ sal_Int32 nMaskObject = 0;
+ if( !!rObject.m_aMask )
+ {
+ if( rObject.m_aMask.GetBitCount() == 1 ||
+ ( rObject.m_aMask.GetBitCount() == 8 && 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( nLength );
+ if( nMaskObject )
+ {
+ aLine.append( rObject.m_aMask.GetBitCount() == 1 ? " /Mask " : " /SMask " );
+ aLine.append( nMaskObject );
+ aLine.append( " 0 R " );
+ }
+ aLine.append( ">>\nstream\n" );
+ CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ checkAndEnableStreamEncryption( rObject.m_nObject );
+ CHECK_RETURN2( writeBuffer( rObject.m_pStream->GetData(), nLength ) );
+ disableStreamEncryption();
+
+ aLine.setLength( 0 );
+ aLine.append( "\nendstream\nendobj\n\n" );
+ CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+
+ if( nMaskObject )
+ {
+ BitmapEmit aEmit;
+ aEmit.m_nObject = nMaskObject;
+ if( rObject.m_aMask.GetBitCount() == 1 )
+ aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, rObject.m_aMask );
+ else if( rObject.m_aMask.GetBitCount() == 8 )
+ aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, AlphaMask( rObject.m_aMask ) );
+ writeBitmapObject( aEmit, true );
+ }
+
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+}
+
+sal_Int32 PDFWriterImpl::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 = createObject();
+ // Remember what is the ID of this object in our output.
+ rCopiedResources[rObject.GetObjectValue()] = nObject;
+ SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::copyExternalResource: " << rObject.GetObjectValue() << " -> " << nObject);
+
+ OStringBuffer aLine;
+ aLine.append(nObject);
+ aLine.append(" 0 obj\n");
+ if (rObject.GetDictionary())
+ {
+ aLine.append("<<");
+
+ // Complex case: can't copy the dictionary byte array as is, as it may contain references.
+ bool bDone = false;
+ sal_uInt64 nCopyStart = 0;
+ for (auto pReference : rObject.GetDictionaryReferences())
+ {
+ if (pReference)
+ {
+ filter::PDFObjectElement* pReferenced = pReference->LookupObject();
+ if (pReferenced)
+ {
+ // Copy the referenced object.
+ sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
+
+ sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation();
+ sal_uInt64 nReferenceEnd = pReference->GetOffset();
+ sal_uInt64 nOffset = 0;
+ if (nCopyStart == 0)
+ // Dict start -> reference start.
+ nOffset = rObject.GetDictionaryOffset();
+ else
+ // Previous reference end -> reference start.
+ nOffset = nCopyStart;
+ aLine.append(static_cast<const char*>(rDocBuffer.GetData()) + nOffset, nReferenceStart - nOffset);
+ // Write the updated reference.
+ aLine.append(" ");
+ aLine.append(nRef);
+ aLine.append(" 0 R");
+ // Start copying here next time.
+ nCopyStart = nReferenceEnd;
+
+ bDone = true;
+ }
+ }
+ }
+
+ if (bDone)
+ {
+ // Copy the last part here, in the complex case.
+ sal_uInt64 nDictEnd = rObject.GetDictionaryOffset() + rObject.GetDictionaryLength();
+ const sal_Int32 nLen = nDictEnd - nCopyStart;
+ if (nLen < 0)
+ SAL_WARN("vcl.pdfwriter", "copyExternalResource() failed");
+ else
+ aLine.append(static_cast<const char*>(rDocBuffer.GetData()) + nCopyStart, nLen);
+ }
+ else
+ // Can copy it as-is.
+ aLine.append(static_cast<const char*>(rDocBuffer.GetData()) + rObject.GetDictionaryOffset(), rObject.GetDictionaryLength());
+
+ aLine.append(">>\n");
+ }
+
+ if (filter::PDFStreamElement* pStream = rObject.GetStream())
+ {
+ aLine.append("stream\n");
+ SvMemoryStream& rStream = pStream->GetMemory();
+ aLine.append(static_cast<const char*>(rStream.GetData()), rStream.GetSize());
+ aLine.append("\nendstream\n");
+ }
+
+ if (filter::PDFArrayElement* pArray = rObject.GetArray())
+ {
+ aLine.append("[");
+
+ const std::vector<filter::PDFElement*>& rElements = pArray->GetElements();
+ bool bDone = false;
+ // Complex case: can't copy the array byte array as is, as it may contain references.
+ sal_uInt64 nCopyStart = 0;
+ for (const auto pElement : rElements)
+ {
+ auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
+ if (pReference)
+ {
+ filter::PDFObjectElement* pReferenced = pReference->LookupObject();
+ if (pReferenced)
+ {
+ // Copy the referenced object.
+ sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
+
+ sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation();
+ sal_uInt64 nReferenceEnd = pReference->GetOffset();
+ sal_uInt64 nOffset = 0;
+ if (nCopyStart == 0)
+ // Array start -> reference start.
+ nOffset = rObject.GetArrayOffset();
+ else
+ // Previous reference end -> reference start.
+ nOffset = nCopyStart;
+ aLine.append(static_cast<const char*>(rDocBuffer.GetData()) + nOffset, nReferenceStart - nOffset);
+
+ // Write the updated reference.
+ aLine.append(" ");
+ aLine.append(nRef);
+ aLine.append(" 0 R");
+ // Start copying here next time.
+ nCopyStart = nReferenceEnd;
+
+ bDone = true;
+ }
+ }
+ }
+
+ if (bDone)
+ {
+ // Copy the last part here, in the complex case.
+ sal_uInt64 nArrEnd = rObject.GetArrayOffset() + rObject.GetArrayLength();
+ const sal_Int32 nLen = nArrEnd - nCopyStart;
+ if (nLen < 0)
+ SAL_WARN("vcl.pdfwriter", "copyExternalResource() failed");
+ else
+ aLine.append(static_cast<const char*>(rDocBuffer.GetData()) + nCopyStart, nLen);
+ }
+ else
+ // Can copy it as-is.
+ aLine.append(static_cast<const char*>(rDocBuffer.GetData()) + rObject.GetArrayOffset(), rObject.GetArrayLength());
+
+ aLine.append("]\n");
+ }
+
+ // If the object has a number element outside a dictionary or array, copy that.
+ if (filter::PDFNumberElement* pNumber = rObject.GetNumberElement())
+ {
+ aLine.append(static_cast<const char*>(rDocBuffer.GetData()) + pNumber->GetLocation(), pNumber->GetLength());
+ aLine.append("\n");
+ }
+
+
+ aLine.append("endobj\n\n");
+
+ // We have the whole object, now write it to the output.
+ if (!updateObject(nObject))
+ return -1;
+ if (!writeBuffer(aLine.getStr(), aLine.getLength()))
+ return -1;
+
+ return nObject;
+}
+
+OString PDFWriterImpl::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;
+ if (auto pResources = dynamic_cast<filter::PDFDictionaryElement*>(rPage.Lookup("Resources")))
+ {
+ // 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 OString();
+ }
+
+ aItems = pReferenced->GetDictionaryItems();
+ }
+ }
+ else if (filter::PDFObjectElement* pPageResources = rPage.LookupObject("Resources"))
+ {
+ // 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();
+ }
+ if (aItems.empty())
+ return OString();
+
+ SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer();
+
+ 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)
+ 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;
+ }
+
+ // Build the dictionary entry string.
+ OStringBuffer sRet("/" + rKind + "<<");
+ for (const auto& rPair : aRet)
+ {
+ sRet.append("/").append(rPair.first).append(" ").append(OString::number(rPair.second)).append(" 0 R");
+ }
+ sRet.append(">>");
+
+ return sRet.makeStringAndClear();
+}
+
+void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
+{
+ if (rEmit.m_nFormObject <= 0)
+ return;
+
+ // Count /Matrix and /BBox.
+ // vcl::ImportPDF() works with 96 DPI so use the same values here, too.
+ sal_Int32 nOldDPIX = GetDPIX();
+ SetDPIX(96);
+ sal_Int32 nOldDPIY = GetDPIY();
+ SetDPIY(96);
+ 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)
+ {
+ // Parse the PDF data, we need that to write the PDF dictionary of our
+ // object.
+ SvMemoryStream aPDFStream;
+ aPDFStream.WriteBytes(rEmit.m_aPDFData.data(), rEmit.m_aPDFData.size());
+ aPDFStream.Seek(0);
+ filter::PDFDocument aPDFDocument;
+ if (!aPDFDocument.Read(aPDFStream))
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: reading the PDF document failed");
+ return;
+ }
+ std::vector<filter::PDFObjectElement*> aPages = aPDFDocument.GetPages();
+ if (aPages.empty())
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages");
+ return;
+ }
+
+ size_t nPageIndex = rEmit.m_nPDFPageIndex >= 0 ? rEmit.m_nPDFPageIndex : 0;
+
+ filter::PDFObjectElement* pPage = aPages[nPageIndex];
+ if (!pPage)
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page");
+ return;
+ }
+
+ std::vector<filter::PDFObjectElement*> aContentStreams;
+ if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"))
+ aContentStreams.push_back(pContentStream);
+ else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents")))
+ {
+ 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;
+ }
+
+ // Maps from source object id (PDF image) to target object id (export result).
+ std::map<sal_Int32, sal_Int32> aCopiedResources;
+
+ 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");
+
+ long nWidth = aSize.Width();
+ long nHeight = aSize.Height();
+ if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate")))
+ {
+ // The original page was rotated, then construct a transformation matrix which does the
+ // same with our form object.
+ if (rtl::math::approxEqual(pRotate->GetValue(), 90))
+ {
+ std::swap(nWidth, nHeight);
+ basegfx::B2DHomMatrix aMat;
+ aMat.rotate(basegfx::deg2rad(pRotate->GetValue()));
+ // Rotate around the origo (bottom left corner) counter-clockwise, then translate
+ // horizontally to effectively keep the bottom left corner unchanged.
+ aLine.append(" /Matrix [ ");
+ aLine.append(aMat.get(0, 0));
+ aLine.append(" ");
+ aLine.append(aMat.get(0, 1));
+ aLine.append(" ");
+ aLine.append(aMat.get(1, 0));
+ aLine.append(" ");
+ aLine.append(aMat.get(1, 1));
+ aLine.append(" 0 ");
+ aLine.append(nWidth);
+ aLine.append(" ] ");
+ }
+ }
+
+ aLine.append(" /Resources <<");
+ static const std::initializer_list<OString> aKeys =
+ {
+ "ColorSpace",
+ "ExtGState",
+ "Font",
+ "XObject",
+ "Shading"
+ };
+ for (const auto& rKey : aKeys)
+ aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources));
+ aLine.append(">>");
+ aLine.append(" /BBox [ 0 0 ");
+ aLine.append(nWidth);
+ aLine.append(" ");
+ aLine.append(nHeight);
+ aLine.append(" ]");
+
+ if (!g_bDebugDisableCompression)
+ aLine.append(" /Filter/FlateDecode");
+ aLine.append(" /Length ");
+
+ SvMemoryStream aStream;
+ for (auto pContent : aContentStreams)
+ {
+ filter::PDFStreamElement* pPageStream = pContent->GetStream();
+ if (!pPageStream)
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream");
+ continue;
+ }
+
+ SvMemoryStream& rPageStream = pPageStream->GetMemory();
+
+ auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"));
+ 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", "PDFWriterImpl::writeReferenceXObject: decompression failed");
+ continue;
+ }
+
+ aStream.WriteBytes(aMemoryStream.GetData(), aMemoryStream.GetSize());
+ }
+ else
+ aStream.WriteBytes(rPageStream.GetData(), rPageStream.GetSize());
+ }
+
+ compressStream(&aStream);
+ sal_Int32 nLength = aStream.Tell();
+ aLine.append(nLength);
+
+ aLine.append(">>\nstream\n");
+ // Copy the original page streams to the form XObject stream.
+ aLine.append(static_cast<const char*>(aStream.GetData()), aStream.GetSize());
+ aLine.append("\nendstream\nendobj\n\n");
+ if (!updateObject(nWrappedFormObject))
+ return;
+ if (!writeBuffer(aLine.getStr(), aLine.getLength()))
+ return;
+ }
+
+ OStringBuffer aLine;
+ 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 ");
+ aLine.append(aSize.Width());
+ aLine.append(" ");
+ aLine.append(aSize.Height());
+ aLine.append(" ]\n");
+
+ if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
+ {
+ // Write the reference dictionary.
+ aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) /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");
+
+ // 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");
+ aLine.append(aStream.getStr());
+ aLine.append("\nendstream\nendobj\n\n");
+ CHECK_RETURN2(writeBuffer(aLine.getStr(), aLine.getLength()));
+}
+
+namespace
+{
+ unsigned char reverseByte(unsigned char b)
+ {
+ b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
+ b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
+ b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
+ return b;
+ }
+
+ //tdf#103051 convert any N1BitLsbPal to N1BitMsbPal
+ Bitmap getExportBitmap(const Bitmap &rBitmap)
+ {
+ Bitmap::ScopedReadAccess pAccess(const_cast<Bitmap&>(rBitmap));
+ const ScanlineFormat eFormat = pAccess->GetScanlineFormat();
+ if (eFormat != ScanlineFormat::N1BitLsbPal)
+ return rBitmap;
+ Bitmap aNewBmp(rBitmap);
+ BitmapScopedWriteAccess xWriteAcc(aNewBmp);
+ const int nScanLineBytes = (pAccess->Width() + 7U) / 8U;
+ for (long nY = 0L; nY < xWriteAcc->Height(); ++nY)
+ {
+ Scanline pBitSwap = xWriteAcc->GetScanline(nY);
+ for (int x = 0; x < nScanLineBytes; ++x)
+ pBitSwap[x] = reverseByte(pBitSwap[x]);
+ }
+ return aNewBmp;
+ }
+}
+
+bool PDFWriterImpl::writeBitmapObject( BitmapEmit& rObject, bool bMask )
+{
+ if (!rObject.m_aReferenceXObject.m_aPDFData.empty() && !m_aContext.UseReferenceXObject)
+ {
+ writeReferenceXObject(rObject.m_aReferenceXObject);
+ return true;
+ }
+
+ CHECK_RETURN( updateObject( rObject.m_nObject ) );
+
+ Bitmap aBitmap;
+ Color aTransparentColor( COL_TRANSPARENT );
+ bool bWriteMask = false;
+ if( ! bMask )
+ {
+ aBitmap = getExportBitmap(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
+ {
+ switch( rObject.m_aBitmap.GetTransparentType() )
+ {
+ case TransparentType::NONE:
+ break;
+ case TransparentType::Color:
+ aTransparentColor = rObject.m_aBitmap.GetTransparentColor();
+ break;
+ case TransparentType::Bitmap:
+ bWriteMask = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() )
+ {
+ aBitmap = getExportBitmap(rObject.m_aBitmap.GetMask());
+ aBitmap.Convert( BmpConversion::N1BitThreshold );
+ SAL_WARN_IF( aBitmap.GetBitCount() != 1, "vcl.pdfwriter", "mask conversion failed" );
+ }
+ else if( aBitmap.GetBitCount() != 8 )
+ {
+ aBitmap = getExportBitmap(rObject.m_aBitmap.GetAlpha().GetBitmap());
+ aBitmap.Convert( BmpConversion::N8BitGreys );
+ SAL_WARN_IF( aBitmap.GetBitCount() != 8, "vcl.pdfwriter", "alpha mask conversion failed" );
+ }
+ }
+
+ Bitmap::ScopedReadAccess pAccess(aBitmap);
+
+ bool bTrueColor;
+ sal_Int32 nBitsPerComponent;
+ switch( aBitmap.GetBitCount() )
+ {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ bTrueColor = false;
+ nBitsPerComponent = aBitmap.GetBitCount();
+ break;
+ default:
+ bTrueColor = true;
+ nBitsPerComponent = 8;
+ break;
+ }
+
+ 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.GetBitCount() == 1 )
+ {
+ // #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
+ {
+ if( aBitmap.GetBitCount() == 1 )
+ {
+ aLine.append( "/ImageMask true\n" );
+ sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
+ SAL_WARN_IF( nBlackIndex != 0 && nBlackIndex != 1, "vcl.pdfwriter", "wrong black index" );
+ if( nBlackIndex )
+ aLine.append( "/Decode[ 1 0 ]\n" );
+ else
+ aLine.append( "/Decode[ 0 1 ]\n" );
+ }
+ else if( aBitmap.GetBitCount() == 8 )
+ {
+ aLine.append( "/ColorSpace/DeviceGray\n"
+ "/Decode [ 1 0 ]\n" );
+ }
+ }
+
+ if( ! bMask && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !m_bIsPDF_A1 )
+ {
+ if( bWriteMask )
+ {
+ nMaskObject = createObject();
+ if( rObject.m_aBitmap.IsAlpha() && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ aLine.append( "/SMask " );
+ else
+ aLine.append( "/Mask " );
+ aLine.append( nMaskObject );
+ aLine.append( " 0 R\n" );
+ }
+ else if( aTransparentColor != COL_TRANSPARENT )
+ {
+ aLine.append( "/Mask[ " );
+ if( bTrueColor )
+ {
+ aLine.append( static_cast<sal_Int32>(aTransparentColor.GetRed()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(aTransparentColor.GetRed()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(aTransparentColor.GetGreen()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(aTransparentColor.GetGreen()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(aTransparentColor.GetBlue()) );
+ aLine.append( ' ' );
+ aLine.append( static_cast<sal_Int32>(aTransparentColor.GetBlue()) );
+ }
+ else
+ {
+ sal_Int32 nIndex = pAccess->GetBestPaletteIndex( BitmapColor( aTransparentColor ) );
+ aLine.append( nIndex );
+ }
+ aLine.append( " ]\n" );
+ }
+ }
+ else if( m_bIsPDF_A1 && (bWriteMask || aTransparentColor != COL_TRANSPARENT) )
+ m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
+
+ aLine.append( ">>\n"
+ "stream\n" );
+ CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
+ 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( long i = 0; i < pAccess->Height(); i++ )
+ {
+ CHECK_RETURN( writeBuffer( pAccess->GetScanline( i ), nScanLineBytes ) );
+ }
+ }
+ else
+ {
+ const int nScanLineBytes = pAccess->Width()*3;
+ std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]);
+ for( long y = 0; y < pAccess->Height(); y++ )
+ {
+ for( 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(writeBuffer(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.getStr(), aLine.getLength() ) );
+ 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.getStr(), aLine.getLength() ) );
+
+ 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()->getVectorGraphicDataType() != VectorGraphicDataType::Pdf)
+ return;
+
+ sal_uInt32 nLength = rGraphic.getVectorGraphicData()->getVectorGraphicDataArrayLength();
+ auto const & rArray = rGraphic.getVectorGraphicData()->getVectorGraphicDataArray();
+
+ auto pPDFData = std::make_shared<std::vector<sal_Int8>>(rArray.getConstArray(), rArray.getConstArray() + nLength);
+
+ if (m_aContext.UseReferenceXObject)
+ {
+ // Store the original PDF data as an embedded file.
+ m_aEmbeddedFiles.emplace_back();
+ m_aEmbeddedFiles.back().m_nObject = createObject();
+ m_aEmbeddedFiles.back().m_pData = pPDFData;
+ rEmit.m_nEmbeddedObject = m_aEmbeddedFiles.back().m_nObject;
+ }
+ else
+ {
+ rEmit.m_nPDFPageIndex = rGraphic.getVectorGraphicData()->getPageIndex();
+ rEmit.m_aPDFData = *pPDFData;
+ }
+
+ rEmit.m_nFormObject = createObject();
+ rEmit.m_aPixelSize = rGraphic.GetPrefSize();
+}
+
+void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const Bitmap& rMask, 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( !!rMask && rMask.GetSizePixel() == aGraphic.GetSizePixel() )
+ {
+ Bitmap aBmp( aGraphic.GetBitmapEx().GetBitmap() );
+ BitmapEx aBmpEx( aBmp, rMask );
+ 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 = vcl_get_checksum( 0, pStream->GetData(), aID.m_nSize );
+ if( ! rMask.IsEmpty() )
+ aID.m_nMaskChecksum = rMask.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()->getVectorGraphicDataType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
+ rEmit.m_nObject = createObject();
+ rEmit.m_aID = aID;
+ rEmit.m_pStream = std::move( pStream );
+ rEmit.m_bTrueColor = bIsTrueColor;
+ if( !! rMask && rMask.GetSizePixel() == rSizePixel )
+ rEmit.m_aMask = rMask;
+ 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.getStr(), aLine.getLength() );
+
+ 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.getStr(), aLine.getLength() );
+}
+
+const BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& i_rBitmap, const Graphic& rGraphic )
+{
+ BitmapEx aBitmap( i_rBitmap );
+ if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
+ {
+ BmpConversion eConv = BmpConversion::N8BitGreys;
+ int nDepth = aBitmap.GetBitmap().GetBitCount();
+ if( nDepth <= 4 )
+ eConv = BmpConversion::N4BitGreys;
+ if( nDepth > 1 )
+ aBitmap.Convert( eConv );
+ }
+ BitmapID aID;
+ aID.m_aPixelSize = aBitmap.GetSizePixel();
+ aID.m_nSize = aBitmap.GetBitCount();
+ aID.m_nChecksum = aBitmap.GetBitmap().GetChecksum();
+ aID.m_nMaskChecksum = 0;
+ if( aBitmap.IsAlpha() )
+ aID.m_nMaskChecksum = aBitmap.GetAlpha().GetChecksum();
+ else
+ {
+ Bitmap aMask = aBitmap.GetMask();
+ if( ! aMask.IsEmpty() )
+ aID.m_nMaskChecksum = aMask.GetChecksum();
+ }
+ std::list< BitmapEmit >::const_iterator it = std::find_if(m_aBitmaps.begin(), m_aBitmaps.end(),
+ [&](const BitmapEmit& arg) { return aID == arg.m_aID; });
+ if( it == m_aBitmaps.end() )
+ {
+ m_aBitmaps.push_front( BitmapEmit() );
+ m_aBitmaps.front().m_aID = aID;
+ m_aBitmaps.front().m_aBitmap = aBitmap;
+ if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getVectorGraphicDataType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
+ m_aBitmaps.front().m_nObject = createObject();
+ createEmbeddedFile(rGraphic, m_aBitmaps.front().m_aReferenceXObject, m_aBitmaps.front().m_nObject);
+ it = m_aBitmaps.begin();
+ }
+
+ sal_Int32 nObject = it->m_aReferenceXObject.getObject();
+ OString aObjName = "Im" + OString::number(nObject);
+ pushResource( ResourceKind::XObject, aObjName, nObject );
+
+ return *it;
+}
+
+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)" );
+
+ if( m_aContext.Version == PDFWriter::PDFVersion::PDF_1_2 )
+ {
+ drawRectangle( rRect );
+ return;
+ }
+
+ 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.getStr(), aLine.getLength() );
+}
+
+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.getStr(), aLine.getLength() );
+ }
+ }
+ else
+ {
+ aBmpPos = aRect.TopLeft();
+ aBmpSize = aRect.GetSize();
+ bDrawBitmap = true;
+ }
+
+ if( aBitmap.IsTransparent() )
+ {
+ 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.getStr(), aLine.getLength() );
+ drawBitmap( aBmpPos, aBmpSize, aBitmap );
+ writeBuffer( "Q\n", 2 );
+ }
+}
+
+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.getStr(), aLine.getLength() );
+}
+
+/* #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() )
+ {
+ // 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 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
+ return;
+
+ m_aNotes.emplace_back( );
+ m_aNotes.back().m_nObject = createObject();
+ m_aNotes.back().m_aContents = rNote;
+ m_aNotes.back().m_aRect = rRect;
+ // convert to default user space now, since the mapmode may change
+ m_aPages[nPageNr].convertRect( m_aNotes.back().m_aRect );
+
+ // insert note to page's annotation list
+ m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aNotes.back().m_nObject );
+}
+
+sal_Int32 PDFWriterImpl::createLink( const tools::Rectangle& rRect, sal_Int32 nPageNr )
+{
+ if( nPageNr < 0 )
+ nPageNr = m_nCurrentPage;
+
+ if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
+ return -1;
+
+ sal_Int32 nRet = m_aLinks.size();
+
+ m_aLinks.emplace_back( );
+ 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)
+{
+ if (nPageNr < 0)
+ nPageNr = m_nCurrentPage;
+
+ if (nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()))
+ return -1;
+
+ sal_Int32 nRet = m_aScreens.size();
+
+ m_aScreens.emplace_back();
+ 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 || nPageNr >= static_cast<sal_Int32>(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 || nPageNr >= static_cast<sal_Int32>(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 || nLinkId >= static_cast<sal_Int32>(m_aLinks.size()) )
+ return;
+ if( nDestId < 0 || nDestId >= static_cast<sal_Int32>(m_aDests.size()) )
+ return;
+
+ m_aLinks[ nLinkId ].m_nDest = nDestId;
+}
+
+void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
+{
+ if( nLinkId < 0 || nLinkId >= static_cast<sal_Int32>(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 || nScreenId >= static_cast<sal_Int32>(m_aScreens.size()))
+ return;
+
+ m_aScreens[nScreenId].m_aURL = rURL;
+}
+
+void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL)
+{
+ if (nScreenId < 0 || nScreenId >= static_cast<sal_Int32>(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, const OUString& 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 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) )
+ return;
+
+ if( nNewParent < 0 || nNewParent >= static_cast<sal_Int32>(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, const OUString& rText )
+{
+ if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) )
+ return;
+
+ m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText );
+}
+
+void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
+{
+ if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) ) // item does not exist
+ return;
+ if( nDestID < 0 || nDestID >= static_cast<sal_Int32>(m_aDests.size()) ) // dest does not exist
+ return;
+ m_aOutline[nItem].m_nDestID = nDestID;
+}
+
+const char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType )
+{
+ static std::map< PDFWriter::StructElement, const char* > aTagStrings;
+ if( aTagStrings.empty() )
+ {
+ aTagStrings[ PDFWriter::NonStructElement] = "NonStruct";
+ aTagStrings[ PDFWriter::Document ] = "Document";
+ aTagStrings[ PDFWriter::Part ] = "Part";
+ aTagStrings[ PDFWriter::Article ] = "Art";
+ aTagStrings[ PDFWriter::Section ] = "Sect";
+ aTagStrings[ PDFWriter::Division ] = "Div";
+ aTagStrings[ PDFWriter::BlockQuote ] = "BlockQuote";
+ aTagStrings[ PDFWriter::Caption ] = "Caption";
+ aTagStrings[ PDFWriter::TOC ] = "TOC";
+ aTagStrings[ PDFWriter::TOCI ] = "TOCI";
+ aTagStrings[ PDFWriter::Index ] = "Index";
+ aTagStrings[ PDFWriter::Paragraph ] = "P";
+ aTagStrings[ PDFWriter::Heading ] = "H";
+ aTagStrings[ PDFWriter::H1 ] = "H1";
+ aTagStrings[ PDFWriter::H2 ] = "H2";
+ aTagStrings[ PDFWriter::H3 ] = "H3";
+ aTagStrings[ PDFWriter::H4 ] = "H4";
+ aTagStrings[ PDFWriter::H5 ] = "H5";
+ aTagStrings[ PDFWriter::H6 ] = "H6";
+ aTagStrings[ PDFWriter::List ] = "L";
+ aTagStrings[ PDFWriter::ListItem ] = "LI";
+ aTagStrings[ PDFWriter::LILabel ] = "Lbl";
+ aTagStrings[ PDFWriter::LIBody ] = "LBody";
+ aTagStrings[ PDFWriter::Table ] = "Table";
+ aTagStrings[ PDFWriter::TableRow ] = "TR";
+ aTagStrings[ PDFWriter::TableHeader ] = "TH";
+ aTagStrings[ PDFWriter::TableData ] = "TD";
+ aTagStrings[ PDFWriter::Span ] = "Span";
+ aTagStrings[ PDFWriter::Quote ] = "Quote";
+ aTagStrings[ PDFWriter::Note ] = "Note";
+ aTagStrings[ PDFWriter::Reference ] = "Reference";
+ aTagStrings[ PDFWriter::BibEntry ] = "BibEntry";
+ aTagStrings[ PDFWriter::Code ] = "Code";
+ aTagStrings[ PDFWriter::Link ] = "Link";
+ aTagStrings[ PDFWriter::Figure ] = "Figure";
+ aTagStrings[ PDFWriter::Formula ] = "Formula";
+ aTagStrings[ PDFWriter::Form ] = "Form";
+ }
+
+ std::map< PDFWriter::StructElement, const char* >::const_iterator 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 sure if this allowed, necessary or recommended otherwise, so
+ // only enable filtering when PDF/UA is enabled.
+ if (!m_bIsPDF_UA || aAlias != aTag)
+ m_aRoleMap[aAlias] = aTag;
+}
+
+void PDFWriterImpl::beginStructureElementMCSeq()
+{
+ if( m_bEmitStructure &&
+ m_nCurrentStructElement > 0 && // StructTreeRoot
+ ! 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_eType ) );
+ aLine.append( "<</MCID " );
+ aLine.append( nMCID );
+ aLine.append( ">>BDC\n" );
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+
+ // 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( nMCID, m_aPages[m_nCurrentPage].m_nPageObject );
+ // 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_eType == PDFWriter::NonStructElement &&
+ ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
+ )
+ {
+ OString aLine = "/Artifact BMC\n";
+ writeBuffer( aLine.getStr(), aLine.getLength() );
+ // mark element MC sequence as open
+ m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
+ }
+}
+
+void PDFWriterImpl::endStructureElementMCSeq()
+{
+ if( m_nCurrentStructElement > 0 && // StructTreeRoot
+ ( m_bEmitStructure || m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement ) &&
+ m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // must have an opened MC sequence
+ )
+ {
+ writeBuffer( "EMC\n", 4 );
+ 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 && nEle < sal_Int32(m_aStructure.size()) )
+ {
+ if( m_aStructure[ nEle ].m_eType == PDFWriter::NonStructElement )
+ {
+ bEmit = false;
+ break;
+ }
+ nEle = m_aStructure[ nEle ].m_nParentElement;
+ }
+ }
+ return bEmit;
+}
+
+sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias )
+{
+ if( m_nCurrentPage < 0 )
+ return -1;
+
+ if( ! m_aContext.Tagged )
+ return -1;
+
+ // close eventual current MC sequence
+ endStructureElementMCSeq();
+
+ 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::list< 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_eType == 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" );
+ }
+ }
+
+ sal_Int32 nNewId = sal_Int32(m_aStructure.size());
+ m_aStructure.emplace_back( );
+ PDFStructureElement& rEle = m_aStructure.back();
+ rEle.m_eType = eType;
+ rEle.m_nOwnElement = nNewId;
+ rEle.m_nParentElement = m_nCurrentStructElement;
+ rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
+ m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
+ m_nCurrentStructElement = nNewId;
+
+ // handle alias names
+ if( !rAlias.isEmpty() && eType != PDFWriter::NonStructElement )
+ {
+ OStringBuffer aNameBuf( rAlias.getLength() );
+ appendName( rAlias, aNameBuf );
+ OString aAliasName( aNameBuf.makeStringAndClear() );
+ rEle.m_aAlias = aAliasName;
+ addRoleMap(aAliasName, eType);
+ }
+
+ if (g_bDebugDisableCompression)
+ {
+ OStringBuffer aLine( "beginStructureElement " );
+ aLine.append( m_nCurrentStructElement );
+ aLine.append( ": " );
+ aLine.append( getStructureTag( eType ) );
+ 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();
+
+ if( m_bEmitStructure ) // don't create nonexistent objects
+ {
+ rEle.m_nObject = createObject();
+ // update parent's kids list
+ m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(rEle.m_nObject);
+ }
+ return nNewId;
+}
+
+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( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
+ 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_aStructure[ m_nCurrentStructElement ].m_nParentElement;
+
+ // check whether to emit structure henceforth
+ m_bEmitStructure = checkEmitStructure();
+
+ if (g_bDebugDisableCompression && m_bEmitStructure)
+ {
+ emitComment( aLine.getStr() );
+ }
+}
+
+/*
+ * 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_eType == PDFWriter::NonStructElement &&
+ rEle.m_nOwnElement != rEle.m_nParentElement )
+ return;
+
+ for (auto const& child : rEle.m_aChildren)
+ {
+ if( child > 0 && child < sal_Int32(m_aStructure.size()) )
+ {
+ PDFStructureElement& rChild = m_aStructure[ child ];
+ if( rChild.m_eType != PDFWriter::NonStructElement )
+ {
+ //triggered when a child of the rEle element is found
+ 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 )
+ {
+ if( !rEle.m_aKids.empty() )
+ {
+ if( rEle.m_aKids.size() > ncMaxPDFArraySize ) {
+ //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::list< sal_Int32 > aNewChildren;
+
+ // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?)
+ OString aAliasName("Div");
+ 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_eType = 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( rEleNew.m_nObject );
+ aNewChildren.push_back( nNewId );
+
+ std::list< 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.splice( rEleNew.m_aChildren.begin(),
+ rEle.m_aChildren,
+ 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 && nEle < sal_Int32(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( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
+ 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;
+
+ bool bInsert = false;
+ if( m_nCurrentStructElement > 0 && m_bEmitStructure )
+ {
+ PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
+ 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::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_eType )
+ << " (" << 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;
+
+ 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 eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
+ 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_eType )
+ << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
+ << ") element");
+
+ return bInsert;
+}
+
+void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect )
+{
+ sal_Int32 nPageNr = m_nCurrentPage;
+ if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) || !m_aContext.Tagged )
+ return;
+
+ if( m_nCurrentStructElement > 0 && m_bEmitStructure )
+ {
+ PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
+ if( eType == PDFWriter::Figure ||
+ eType == PDFWriter::Formula ||
+ eType == PDFWriter::Form ||
+ 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::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 || nPageNr >= static_cast<sal_Int32>(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];
+ auto app_it = rKid.m_aAppearances.find( "N" );
+ if( app_it != rKid.m_aAppearances.end() )
+ {
+ auto stream_it = app_it->second.find( "Yes" );
+ 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" );
+ }
+ // 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 || nPageNr >= static_cast<sal_Int32>(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 || nRadioGroupWidget >= static_cast<sal_Int32>(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;
+ 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 ? OUStringLiteral("Yes") : OUStringLiteral("Off" );
+ // 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 && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ 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 && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
+ rNewWidget.m_nFlags |= 0x00100000;
+ rNewWidget.m_nMaxLen = rEdit.MaxLen;
+ 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" ][ "Standard" ] = 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::addStream( const OUString& rMimeType, PDFOutputStream* pStream )
+{
+ if( pStream )
+ {
+ m_aAdditionalStreams.emplace_back( );
+ PDFAddStream& rStream = m_aAdditionalStreams.back();
+ rStream.m_aMimeType = !rMimeType.isEmpty()
+ ? rMimeType
+ : OUString( "application/octet-stream" );
+ rStream.m_pStream = pStream;
+ rStream.m_bCompress = false;
+ }
+}
+
+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_impl.hxx b/vcl/source/gdi/pdfwriter_impl.hxx
new file mode 100644
index 000000000..6261a391b
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter_impl.hxx
@@ -0,0 +1,1265 @@
+/* -*- 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 .
+ */
+#ifndef INCLUDED_VCL_SOURCE_GDI_PDFWRITER_IMPL_HXX
+#define INCLUDED_VCL_SOURCE_GDI_PDFWRITER_IMPL_HXX
+
+#include <map>
+#include <list>
+#include <unordered_map>
+#include <memory>
+#include <vector>
+
+#include <pdf/ResourceDict.hxx>
+#include <pdf/BitmapID.hxx>
+#include <pdf/Matrix3.hxx>
+
+#include <com/sun/star/lang/Locale.hpp>
+#include <com/sun/star/util/XURLTransformer.hpp>
+#include <com/sun/star/uno/Sequence.h>
+#include <osl/file.hxx>
+#include <rtl/cipher.h>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <tools/gen.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/graphictools.hxx>
+#include <vcl/hatch.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/pdfwriter.hxx>
+#include <vcl/wall.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/typed_flags_set.hxx>
+#include <o3tl/lru_map.hxx>
+#include <comphelper/hash.hxx>
+#include <tools/stream.hxx>
+
+#include <outdata.hxx>
+#include "pdffontcache.hxx"
+#include "pdfbuildin_fonts.hxx"
+
+class StyleSettings;
+class FontSelectPattern;
+class FontSubsetInfo;
+class ZCodec;
+class EncHashTransporter;
+struct BitStreamState;
+class PhysicalFontFace;
+class SvStream;
+class SvMemoryStream;
+
+// the maximum password length
+constexpr sal_Int32 ENCRYPTED_PWD_SIZE = 32;
+constexpr sal_Int32 MD5_DIGEST_SIZE = 16;
+// security 128 bit
+constexpr sal_Int32 SECUR_128BIT_KEY = 16;
+// maximum length of MD5 digest input, in step 2 of algorithm 3.1
+// PDF spec ver. 1.4: see there for details
+constexpr sal_Int32 MAXIMUM_RC4_KEY_LENGTH = SECUR_128BIT_KEY + 3 + 2;
+
+namespace vcl::pdf
+{
+
+enum class GraphicsStateUpdateFlags {
+ Font = 0x0001,
+ MapMode = 0x0002,
+ LineColor = 0x0004,
+ FillColor = 0x0008,
+ ClipRegion = 0x0040,
+ LayoutMode = 0x0100,
+ TransparentPercent = 0x0200,
+ DigitLanguage = 0x0400,
+ All = 0x077f
+};
+
+} // end vcl::pdf
+
+namespace o3tl {
+ template<> struct typed_flags<vcl::pdf::GraphicsStateUpdateFlags> : is_typed_flags<vcl::pdf::GraphicsStateUpdateFlags, 0x077f> {};
+}
+
+namespace vcl
+{
+
+using namespace vcl::pdf;
+
+class PDFStreamIf;
+
+namespace filter
+{
+class PDFObjectElement;
+}
+
+namespace pdf
+{
+constexpr sal_Int32 g_nInheritedPageWidth = 595; // default A4 in inch/72
+constexpr sal_Int32 g_nInheritedPageHeight = 842; // default A4 in inch/72
+
+struct PDFPage
+{
+ VclPtr<PDFWriterImpl> m_pWriter;
+ double m_nPageWidth; // in inch/72
+ double m_nPageHeight; // in inch/72
+ /**
+ * A positive number that gives the size of default user space units, in multiples of points.
+ * Typically 1, larger if page size is > 508 cm.
+ */
+ sal_Int32 m_nUserUnit;
+ PDFWriter::Orientation m_eOrientation;
+ sal_Int32 m_nPageObject;
+ std::vector<sal_Int32> m_aStreamObjects;
+ sal_Int32 m_nStreamLengthObject;
+ sal_uInt64 m_nBeginStreamPos;
+ std::vector<sal_Int32> m_aAnnotations;
+ std::vector<sal_Int32> m_aMCIDParents;
+ PDFWriter::PageTransition m_eTransition;
+ sal_uInt32 m_nTransTime;
+
+ PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation );
+
+ void beginStream();
+ void endStream();
+ bool emit( sal_Int32 nParentPage );
+
+ // converts point from ref device coordinates to
+ // page coordinates and appends the point to the buffer
+ // if pOutPoint is set it will be updated to the emitted point
+ // (in PDF map mode, that is 10th of point)
+ void appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const;
+ // appends a B2DPoint without further transformation
+ void appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const;
+ // appends a rectangle
+ void appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const;
+ // converts a rectangle to 10th points page space
+ void convertRect( tools::Rectangle& rRect ) const;
+ // appends a polygon optionally closing it
+ void appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose = true ) const;
+ // appends a polygon optionally closing it
+ void appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const;
+ // appends a polypolygon optionally closing the subpaths
+ void appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const;
+ // appends a polypolygon optionally closing the subpaths
+ void appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const;
+ // converts a length (either vertical or horizontal; this
+ // can be important if the source MapMode is not
+ // symmetrical) to page length and appends it to the buffer
+ // if pOutLength is set it will be updated to the emitted length
+ // (in PDF map mode, that is 10th of point)
+ void appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical = true, sal_Int32* pOutLength = nullptr ) const;
+ // the same for double values
+ void appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical = true, sal_Int32 nPrecision = 5 ) const;
+ // appends LineInfo
+ // returns false if too many dash array entry were created for
+ // the implementation limits of some PDF readers
+ bool appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const;
+ // appends a horizontal waveline with vertical offset (helper for drawWaveLine)
+ void appendWaveLine( sal_Int32 nLength, sal_Int32 nYOffset, sal_Int32 nDelta, OStringBuffer& rBuffer ) const;
+
+ void appendMatrix3(Matrix3 const & rMatrix, OStringBuffer& rBuffer);
+
+ double getHeight() const;
+};
+
+/// Contains information to emit a reference XObject.
+struct ReferenceXObjectEmit
+{
+ /// ID of the Form XObject, if any.
+ sal_Int32 m_nFormObject;
+ /// ID of the vector/embedded object, if m_nFormObject is used.
+ sal_Int32 m_nEmbeddedObject;
+ /// ID of the bitmap object, if m_nFormObject is used.
+ sal_Int32 m_nBitmapObject;
+ /// Size of the bitmap replacement, in pixels.
+ Size m_aPixelSize;
+ /// PDF data from the graphic object, if not writing a reference XObject.
+ std::vector<sal_Int8> m_aPDFData;
+ sal_Int32 m_nPDFPageIndex;
+
+ ReferenceXObjectEmit()
+ : m_nFormObject(0),
+ m_nEmbeddedObject(0),
+ m_nBitmapObject(0),
+ m_nPDFPageIndex(-1)
+ {
+ }
+
+ /// Returns the ID one should use when referring to this bitmap.
+ sal_Int32 getObject() const;
+};
+
+struct BitmapEmit
+{
+ BitmapID m_aID;
+ BitmapEx m_aBitmap;
+ sal_Int32 m_nObject;
+ ReferenceXObjectEmit m_aReferenceXObject;
+
+ BitmapEmit()
+ : m_nObject(0)
+ {
+ }
+};
+
+struct JPGEmit
+{
+ BitmapID m_aID;
+ std::unique_ptr<SvMemoryStream>
+ m_pStream;
+ Bitmap m_aMask;
+ sal_Int32 m_nObject;
+ bool m_bTrueColor;
+ ReferenceXObjectEmit m_aReferenceXObject;
+
+ JPGEmit()
+ : m_nObject(0)
+ , m_bTrueColor(false)
+ {
+ }
+};
+
+struct GradientEmit
+{
+ Gradient m_aGradient;
+ Size m_aSize;
+ sal_Int32 m_nObject;
+};
+
+// for tilings (drawWallpaper, begin/endPattern)
+struct TilingEmit
+{
+ sal_Int32 m_nObject;
+ tools::Rectangle m_aRectangle;
+ Size m_aCellSize;
+ SvtGraphicFill::Transform m_aTransform;
+ ResourceDict m_aResources;
+ std::unique_ptr<SvMemoryStream> m_pTilingStream;
+
+ TilingEmit()
+ : m_nObject( 0 )
+ {}
+};
+
+// for transparency group XObjects
+struct TransparencyEmit
+{
+ sal_Int32 m_nObject;
+ sal_Int32 m_nExtGStateObject;
+ double m_fAlpha;
+ tools::Rectangle m_aBoundRect;
+ std::unique_ptr<SvMemoryStream> m_pContentStream;
+ std::unique_ptr<SvMemoryStream> m_pSoftMaskStream;
+
+ TransparencyEmit()
+ : m_nObject( 0 ),
+ m_nExtGStateObject( -1 ),
+ m_fAlpha( 0.0 )
+ {}
+};
+
+// font subsets
+class GlyphEmit
+{
+ // performance: actually this should probably a vector;
+ std::vector<sal_Ucs> m_CodeUnits;
+ sal_uInt8 m_nSubsetGlyphID;
+
+public:
+ GlyphEmit() : m_nSubsetGlyphID(0)
+ {
+ }
+
+ void setGlyphId( sal_uInt8 i_nId ) { m_nSubsetGlyphID = i_nId; }
+ sal_uInt8 getGlyphId() const { return m_nSubsetGlyphID; }
+
+ void addCode( sal_Ucs i_cCode )
+ {
+ m_CodeUnits.push_back(i_cCode);
+ }
+ sal_Int32 countCodes() const { return m_CodeUnits.size(); }
+ const std::vector<sal_Ucs>& codes() const { return m_CodeUnits; }
+ sal_Ucs getCode( sal_Int32 i_nIndex ) const
+ {
+ sal_Ucs nRet = 0;
+ if (o3tl::make_unsigned(i_nIndex) < m_CodeUnits.size())
+ nRet = m_CodeUnits[i_nIndex];
+ return nRet;
+ }
+};
+
+struct FontEmit
+{
+ sal_Int32 m_nFontID;
+ std::map<sal_GlyphId, GlyphEmit> m_aMapping;
+
+ explicit FontEmit( sal_Int32 nID ) : m_nFontID( nID ) {}
+};
+
+struct Glyph
+{
+ sal_Int32 m_nFontID;
+ sal_uInt8 m_nSubsetGlyphID;
+};
+
+struct FontSubset
+{
+ std::vector< FontEmit > m_aSubsets;
+ std::map<sal_GlyphId, Glyph> m_aMapping;
+};
+
+struct EmbedFont
+{
+ sal_Int32 m_nNormalFontID;
+
+ EmbedFont() : m_nNormalFontID( 0 ) {}
+};
+
+struct PDFDest
+{
+ sal_Int32 m_nPage;
+ PDFWriter::DestAreaType m_eType;
+ tools::Rectangle m_aRect;
+};
+
+//--->i56629
+struct PDFNamedDest
+{
+ OUString m_aDestName;
+ sal_Int32 m_nPage;
+ PDFWriter::DestAreaType m_eType;
+ tools::Rectangle m_aRect;
+};
+
+struct PDFOutlineEntry
+{
+ sal_Int32 m_nObject;
+ sal_Int32 m_nParentObject;
+ sal_Int32 m_nNextObject;
+ sal_Int32 m_nPrevObject;
+ std::vector< sal_Int32 > m_aChildren;
+ OUString m_aTitle;
+ sal_Int32 m_nDestID;
+
+ PDFOutlineEntry()
+ : m_nObject( 0 ),
+ m_nParentObject( 0 ),
+ m_nNextObject( 0 ),
+ m_nPrevObject( 0 ),
+ m_nDestID( -1 )
+ {}
+};
+
+struct PDFAnnotation
+{
+ sal_Int32 m_nObject;
+ tools::Rectangle m_aRect;
+ sal_Int32 m_nPage;
+
+ PDFAnnotation()
+ : m_nObject( -1 ),
+ m_nPage( -1 )
+ {}
+};
+
+struct PDFLink : public PDFAnnotation
+{
+ sal_Int32 m_nDest; // set to -1 for URL, to a dest else
+ OUString m_aURL;
+ sal_Int32 m_nStructParent; // struct parent entry
+
+ PDFLink()
+ : m_nDest( -1 ),
+ m_nStructParent( -1 )
+ {}
+};
+
+/// A PDF embedded file.
+struct PDFEmbeddedFile
+{
+ /// ID of the file.
+ sal_Int32 m_nObject;
+ /// Contents of the file.
+ std::shared_ptr<std::vector<sal_Int8>> m_pData;
+
+ PDFEmbeddedFile()
+ : m_nObject(0)
+ {
+ }
+};
+
+struct PDFNoteEntry : public PDFAnnotation
+{
+ PDFNote m_aContents;
+
+ PDFNoteEntry()
+ {}
+};
+
+/// A PDF Screen annotation.
+struct PDFScreen : public PDFAnnotation
+{
+ /// Linked video.
+ OUString m_aURL;
+ /// Embedded video.
+ OUString m_aTempFileURL;
+ /// ID of the EmbeddedFile object.
+ sal_Int32 m_nTempFileObject;
+
+ PDFScreen()
+ : m_nTempFileObject(0)
+ {
+ }
+};
+
+struct PDFWidget : public PDFAnnotation
+{
+ typedef std::unordered_map<OString, SvMemoryStream*> PDFAppearanceStreams;
+
+ PDFWriter::WidgetType m_eType;
+ OString m_aName;
+ OUString m_aDescription;
+ OUString m_aText;
+ DrawTextFlags m_nTextStyle;
+ OUString m_aValue;
+ OString m_aDAString;
+ OString m_aDRDict;
+ OString m_aMKDict;
+ OString m_aMKDictCAString; // i12626, added to be able to encrypt the /CA text string
+ // since the object number is not known at the moment
+ // of filling m_aMKDict, the string will be encrypted when emitted.
+ // the /CA string MUST BE the last added to m_aMKDict
+ // see code for details
+ sal_Int32 m_nFlags;
+ sal_Int32 m_nParent; // if not 0, parent's object number
+ std::vector<sal_Int32> m_aKids; // widget children, contains object numbers
+ std::vector<sal_Int32> m_aKidsIndex; // widget children, contains index to m_aWidgets
+ OUString m_aOnValue;
+ sal_Int32 m_nTabOrder; // lowest number gets first in tab order
+ sal_Int32 m_nRadioGroup;
+ sal_Int32 m_nMaxLen;
+ bool m_bSubmit;
+ bool m_bSubmitGet;
+ sal_Int32 m_nDest;
+ std::vector<OUString> m_aListEntries;
+ std::vector<sal_Int32> m_aSelectedEntries;
+ std::unordered_map<OString, PDFAppearanceStreams> m_aAppearances;
+ PDFWidget()
+ : m_eType( PDFWriter::PushButton ),
+ m_nTextStyle( DrawTextFlags::NONE ),
+ m_nFlags( 0 ),
+ m_nParent( 0 ),
+ m_nTabOrder( 0 ),
+ m_nRadioGroup( -1 ),
+ m_nMaxLen( 0 ),
+ m_bSubmit( false ),
+ m_bSubmitGet( false ),
+ m_nDest( -1 )
+ {}
+};
+
+struct PDFStructureAttribute
+{
+ PDFWriter::StructAttributeValue eValue;
+ sal_Int32 nValue;
+
+ PDFStructureAttribute()
+ : eValue( PDFWriter::Invalid ),
+ nValue( 0 )
+ {}
+
+ explicit PDFStructureAttribute( PDFWriter::StructAttributeValue eVal )
+ : eValue( eVal ),
+ nValue( 0 )
+ {}
+
+ explicit PDFStructureAttribute( sal_Int32 nVal )
+ : eValue( PDFWriter::Invalid ),
+ nValue( nVal )
+ {}
+};
+
+struct PDFStructureElementKid // for Kids entries
+{
+ sal_Int32 const nObject; // an object number if nMCID is -1,
+ // else the page object relevant to MCID
+ sal_Int32 const nMCID; // an MCID if >= 0
+
+ explicit PDFStructureElementKid( sal_Int32 nObj ) : nObject( nObj ), nMCID( -1 ) {}
+ PDFStructureElementKid( sal_Int32 MCID, sal_Int32 nPage ) : nObject( nPage ), nMCID( MCID ) {}
+};
+
+struct PDFStructureElement
+{
+ sal_Int32 m_nObject;
+ PDFWriter::StructElement m_eType;
+ OString m_aAlias;
+ sal_Int32 m_nOwnElement; // index into structure vector
+ sal_Int32 m_nParentElement; // index into structure vector
+ sal_Int32 m_nFirstPageObject;
+ bool m_bOpenMCSeq;
+ std::list< sal_Int32 > m_aChildren; // indexes into structure vector
+ std::list< PDFStructureElementKid > m_aKids;
+ std::map<PDFWriter::StructAttribute, PDFStructureAttribute >
+ m_aAttributes;
+ tools::Rectangle m_aBBox;
+ OUString m_aActualText;
+ OUString m_aAltText;
+ css::lang::Locale m_aLocale;
+
+ // m_aContents contains the element's marked content sequence
+ // as pairs of (page nr, MCID)
+
+ PDFStructureElement()
+ : m_nObject( 0 ),
+ m_eType( PDFWriter::NonStructElement ),
+ m_nOwnElement( -1 ),
+ m_nParentElement( -1 ),
+ m_nFirstPageObject( 0 ),
+ m_bOpenMCSeq( false )
+ {
+ }
+
+};
+
+struct PDFAddStream
+{
+ OUString m_aMimeType;
+ PDFOutputStream* m_pStream;
+ sal_Int32 m_nStreamObject;
+ bool m_bCompress;
+
+ PDFAddStream() : m_pStream( nullptr ), m_nStreamObject( 0 ), m_bCompress( true ) {}
+};
+
+// helper structure for drawLayout and friends
+struct PDFGlyph
+{
+ Point const m_aPos;
+ const GlyphItem* m_pGlyph;
+ sal_Int32 const m_nNativeWidth;
+ sal_Int32 const m_nMappedFontId;
+ sal_uInt8 const m_nMappedGlyphId;
+ int const m_nCharPos;
+
+ PDFGlyph( const Point& rPos,
+ const GlyphItem* pGlyph,
+ sal_Int32 nNativeWidth,
+ sal_Int32 nFontId,
+ sal_uInt8 nMappedGlyphId,
+ int nCharPos )
+ : m_aPos( rPos ), m_pGlyph(pGlyph), m_nNativeWidth( nNativeWidth ),
+ m_nMappedFontId( nFontId ), m_nMappedGlyphId( nMappedGlyphId ),
+ m_nCharPos(nCharPos)
+ {}
+};
+
+struct StreamRedirect
+{
+ SvStream* m_pStream;
+ MapMode m_aMapMode;
+ tools::Rectangle m_aTargetRect;
+ ResourceDict m_aResourceDict;
+};
+
+// graphics state
+struct GraphicsState
+{
+ vcl::Font m_aFont;
+ MapMode m_aMapMode;
+ Color m_aLineColor;
+ Color m_aFillColor;
+ Color m_aTextLineColor;
+ Color m_aOverlineColor;
+ basegfx::B2DPolyPolygon m_aClipRegion;
+ bool m_bClipRegion;
+ ComplexTextLayoutFlags m_nLayoutMode;
+ LanguageType m_aDigitLanguage;
+ PushFlags m_nFlags;
+ GraphicsStateUpdateFlags m_nUpdateFlags;
+
+ GraphicsState() :
+ m_aLineColor( COL_TRANSPARENT ),
+ m_aFillColor( COL_TRANSPARENT ),
+ m_aTextLineColor( COL_TRANSPARENT ),
+ m_aOverlineColor( COL_TRANSPARENT ),
+ m_bClipRegion( false ),
+ m_nLayoutMode( ComplexTextLayoutFlags::Default ),
+ m_aDigitLanguage( 0 ),
+ m_nFlags( PushFlags::ALL ),
+ m_nUpdateFlags( GraphicsStateUpdateFlags::All )
+ {}
+};
+
+enum class Mode { DEFAULT, NOWRITE };
+
+}
+
+class PDFWriterImpl : public VirtualDevice
+{
+ friend class PDFStreamIf;
+
+public:
+ friend struct vcl::pdf::PDFPage;
+
+ static const char* getStructureTag( PDFWriter::StructElement );
+ static const char* getAttributeTag( PDFWriter::StructAttribute eAtr );
+ static const char* getAttributeValueTag( PDFWriter::StructAttributeValue eVal );
+
+ // returns true if compression was done
+ // else false
+ static bool compressStream( SvMemoryStream* );
+
+ static void convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut );
+
+protected:
+ void ImplClearFontData(bool bNewFontLists) override;
+ void ImplRefreshFontData(bool bNewFontLists) override;
+ vcl::Region ClipToDeviceBounds(vcl::Region aRegion) const override;
+ void DrawHatchLine_DrawLine(const Point& rStartPoint, const Point& rEndPoint) override;
+
+private:
+ MapMode m_aMapMode; // PDFWriterImpl scaled units
+ std::vector< PDFPage > m_aPages;
+ /* maps object numbers to file offsets (needed for xref) */
+ std::vector< sal_uInt64 > m_aObjects;
+ /* contains Bitmaps until they are written to the
+ * file stream as XObjects*/
+ std::list< BitmapEmit > m_aBitmaps;
+ /* contains JPG streams until written to file */
+ std::vector<JPGEmit> m_aJPGs;
+ /*--->i56629 contains all named destinations ever set during the PDF creation,
+ destination id is always the destination's position in this vector
+ */
+ std::vector<PDFNamedDest> m_aNamedDests;
+ /* contains all dests ever set during the PDF creation,
+ dest id is always the dest's position in this vector
+ */
+ std::vector<PDFDest> m_aDests;
+ /** contains destinations accessible via a public Id, instead of being linked to by an ordinary link
+ */
+ ::std::map< sal_Int32, sal_Int32 > m_aDestinationIdTranslation;
+ /* contains all links ever set during PDF creation,
+ link id is always the link's position in this vector
+ */
+ std::vector<PDFLink> m_aLinks;
+ /// Contains all screen annotations.
+ std::vector<PDFScreen> m_aScreens;
+ /// Contains embedded files.
+ std::vector<PDFEmbeddedFile> m_aEmbeddedFiles;
+ /* makes correctly encoded for export to PDF URLS
+ */
+ css::uno::Reference< css::util::XURLTransformer > m_xTrans;
+ /* maps arbitrary link ids for structure attributes to real link ids
+ (for setLinkPropertyId)
+ */
+ std::map<sal_Int32, sal_Int32> m_aLinkPropertyMap;
+ /* contains all outline items,
+ object 0 is the outline root
+ */
+ std::vector<PDFOutlineEntry> m_aOutline;
+ /* contains all notes set during PDF creation
+ */
+ std::vector<PDFNoteEntry> m_aNotes;
+ /* the root of the structure tree
+ */
+ std::vector<PDFStructureElement> m_aStructure;
+ /* current object in the structure hierarchy
+ */
+ sal_Int32 m_nCurrentStructElement;
+ /* structure parent tree */
+ std::vector< OString > m_aStructParentTree;
+ /* emit structure marks currently (aka. NonStructElement or not)
+ */
+ bool m_bEmitStructure;
+ /* role map of struct tree root */
+ std::unordered_map< OString, OString >
+ m_aRoleMap;
+
+ /* contains all widgets used in the PDF
+ */
+ std::vector<PDFWidget> m_aWidgets;
+ /* maps radio group id to index of radio group control in m_aWidgets */
+ std::map< sal_Int32, sal_Int32 > m_aRadioGroupWidgets;
+ /* unordered_map for field names, used to ensure unique field names */
+ std::unordered_map< OString, sal_Int32 > m_aFieldNameMap;
+
+ /* contains Bitmaps for gradient functions until they are written
+ * to the file stream */
+ std::list< GradientEmit > m_aGradients;
+ /* contains bitmap tiling patterns */
+ std::vector< TilingEmit > m_aTilings;
+ std::list< TransparencyEmit > m_aTransparentObjects;
+ /* contains all font subsets in use */
+ std::map<const PhysicalFontFace*, FontSubset> m_aSubsets;
+ std::map<const PhysicalFontFace*, EmbedFont> m_aSystemFonts;
+ sal_Int32 m_nNextFID;
+ PDFFontCache m_aFontCache;
+
+ /// Cache some most recent bitmaps we've exported, in case we encounter them again..
+ o3tl::lru_map<BitmapChecksum,
+ std::shared_ptr<SvMemoryStream>> m_aPDFBmpCache;
+
+ sal_Int32 m_nCurrentPage;
+
+ sal_Int32 m_nCatalogObject;
+ // object number of the main signature dictionary
+ sal_Int32 m_nSignatureObject;
+ sal_Int64 m_nSignatureContentOffset;
+ sal_Int64 m_nSignatureLastByteRangeNoOffset;
+ sal_Int32 m_nResourceDict;
+ ResourceDict m_aGlobalResourceDict;
+ sal_Int32 m_nFontDictObject;
+ std::map< sal_Int32, sal_Int32 > m_aBuildinFontToObjectMap;
+
+ PDFWriter::PDFWriterContext m_aContext;
+ osl::File m_aFile;
+ bool m_bOpen;
+
+ /* output redirection; e.g. to accumulate content streams for
+ XObjects
+ */
+ std::list< StreamRedirect > m_aOutputStreams;
+
+ std::list< GraphicsState > m_aGraphicsStack;
+ GraphicsState m_aCurrentPDFState;
+
+ std::unique_ptr<ZCodec> m_pCodec;
+ std::unique_ptr<SvMemoryStream> m_pMemStream;
+
+ std::vector< PDFAddStream > m_aAdditionalStreams;
+ std::set< PDFWriter::ErrorCode > m_aErrors;
+
+ ::comphelper::Hash m_DocDigest;
+
+/*
+variables for PDF security
+i12626
+*/
+/* used to cipher the stream data and for password management */
+ rtlCipher m_aCipher;
+ /* pad string used for password in Standard security handler */
+ static const sal_uInt8 s_nPadString[ENCRYPTED_PWD_SIZE];
+
+ /* the encryption key, formed with the user password according to algorithm 3.2, maximum length is 16 bytes + 3 + 2
+ for 128 bit security */
+ sal_Int32 m_nKeyLength; // key length, 16 or 5
+ sal_Int32 m_nRC4KeyLength; // key length, 16 or 10, to be input to the algorithm 3.1
+
+ /* set to true if the following stream must be encrypted, used inside writeBuffer() */
+ bool m_bEncryptThisStream;
+
+ /* the numerical value of the access permissions, according to PDF spec, must be signed */
+ sal_Int32 m_nAccessPermissions;
+ /* string to hold the PDF creation date */
+ OString m_aCreationDateString;
+ /* string to hold the PDF creation date, for PDF/A metadata */
+ OString m_aCreationMetaDateString;
+ /* the buffer where the data are encrypted, dynamically allocated */
+ std::vector<sal_uInt8> m_vEncryptionBuffer;
+
+ void addRoleMap(OString aAlias, PDFWriter::StructElement eType);
+
+ /* this function implements part of the PDF spec algorithm 3.1 in encryption, the rest (the actual encryption) is in PDFWriterImpl::writeBuffer */
+ void checkAndEnableStreamEncryption( sal_Int32 nObject );
+
+ void disableStreamEncryption() { m_bEncryptThisStream = false; };
+
+ /* */
+ void enableStringEncryption( sal_Int32 nObject );
+
+// test if the encryption is active, if yes than encrypt the unicode string and add to the OStringBuffer parameter
+ void appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer );
+
+ void appendLiteralStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc = RTL_TEXTENCODING_ASCII_US );
+ void appendLiteralStringEncrypt( const OString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer );
+ void appendLiteralStringEncrypt( OStringBuffer const & rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer );
+
+ /* creates fonts and subsets that will be emitted later */
+ void registerGlyph(const GlyphItem* pGlyph, const PhysicalFontFace* pFont, const std::vector<sal_Ucs>& rCodeUnits, sal_uInt8& nMappedGlyph, sal_Int32& nMappedFontObject);
+
+ /* emits a text object according to the passed layout */
+ /* TODO: remove rText as soon as SalLayout will change so that rText is not necessary anymore */
+ void drawVerticalGlyphs( const std::vector<PDFGlyph>& rGlyphs, OStringBuffer& rLine, const Point& rAlignOffset, const Matrix3& rRotScale, double fAngle, double fXScale, double fSkew, sal_Int32 nFontHeight );
+ void drawHorizontalGlyphs( const std::vector<PDFGlyph>& rGlyphs, OStringBuffer& rLine, const Point& rAlignOffset, bool bFirst, double fAngle, double fXScale, double fSkew, sal_Int32 nFontHeight, sal_Int32 nPixelFontHeight );
+ void drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines );
+ void drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines );
+ void drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines );
+
+ /* writes differences between graphics stack and current real PDF
+ * state to the file
+ */
+ void updateGraphicsState(Mode mode = Mode::DEFAULT);
+
+ /* writes a transparency group object */
+ void writeTransparentObject( TransparencyEmit& rObject );
+
+ /* writes an XObject of type image, may create
+ a second for the mask
+ */
+ bool writeBitmapObject( BitmapEmit& rObject, bool bMask = false );
+
+ void writeJPG( JPGEmit& rEmit );
+ /// Writes the form XObject proxy for the image.
+ void writeReferenceXObject(ReferenceXObjectEmit& rEmit);
+ /// Copies resources of a given kind from an external page to the output,
+ /// returning what has to be included in the new resource dictionary.
+ OString copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind, std::map<sal_Int32, sal_Int32>& rCopiedResources);
+ /// Copies a single resource from an external document, returns the new
+ /// object ID in our document.
+ sal_Int32 copyExternalResource(SvMemoryStream& rDocBuffer, filter::PDFObjectElement& rObject, std::map<sal_Int32, sal_Int32>& rCopiedResources);
+
+ /* tries to find the bitmap by its id and returns its emit data if exists,
+ else creates a new emit data block */
+ const BitmapEmit& createBitmapEmit( const BitmapEx& rBitmapEx, const Graphic& rGraphic );
+
+ /* writes the Do operation inside the content stream */
+ void drawBitmap( const Point& rDestPt, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor );
+ /* write the function object for a Gradient */
+ bool writeGradientFunction( GradientEmit const & rObject );
+ /* creates a GradientEmit and returns its object number */
+ sal_Int32 createGradient( const Gradient& rGradient, const Size& rSize );
+
+ /* writes all tilings */
+ bool emitTilings();
+ /* writes all gradient patterns */
+ bool emitGradients();
+ /* writes a builtin font object and returns its objectid (or 0 in case of failure ) */
+ sal_Int32 emitBuildinFont( const pdf::BuildinFontFace*, sal_Int32 nObject );
+ /* writes a type1 system font object and returns its mapping from font ids to object ids (or 0 in case of failure ) */
+ std::map< sal_Int32, sal_Int32 > emitSystemFont( const PhysicalFontFace*, EmbedFont const & );
+ /* writes a font descriptor and returns its object id (or 0) */
+ sal_Int32 emitFontDescriptor( const PhysicalFontFace*, FontSubsetInfo const &, sal_Int32 nSubsetID, sal_Int32 nStream );
+ /* writes a ToUnicode cmap, returns the corresponding stream object */
+ sal_Int32 createToUnicodeCMap( sal_uInt8 const * pEncoding, const sal_Ucs* pCodeUnits, const sal_Int32* pCodeUnitsPerGlyph,
+ const sal_Int32* pEncToUnicodeIndex, int nGlyphs );
+
+ /* get resource dict object number */
+ sal_Int32 getResourceDictObj()
+ {
+ if( m_nResourceDict <= 0 )
+ m_nResourceDict = createObject();
+ return m_nResourceDict;
+ }
+ /* get the font dict object */
+ sal_Int32 getFontDictObject()
+ {
+ if( m_nFontDictObject <= 0 )
+ m_nFontDictObject = createObject();
+ return m_nFontDictObject;
+ }
+ /* push resource into current (redirected) resource dict */
+ void pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject );
+
+ void appendBuildinFontsToDict( OStringBuffer& rDict ) const;
+ /* writes the font dictionary and emits all font objects
+ * returns object id of font directory (or 0 on error)
+ */
+ bool emitFonts();
+ /* writes the Resource dictionary;
+ * returns dict object id (or 0 on error)
+ */
+ sal_Int32 emitResources();
+ // appends a dest
+ bool appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer );
+ // write all links
+ bool emitLinkAnnotations();
+ /// Write all screen annotations.
+ bool emitScreenAnnotations();
+ // write all notes
+ bool emitNoteAnnotations();
+ // write the appearance streams of a widget
+ bool emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict );
+ // clean up radio button "On" values
+ void ensureUniqueRadioOnValues();
+ // write all widgets
+ bool emitWidgetAnnotations();
+ // writes all annotation objects
+ bool emitAnnotations();
+ /// Writes embedded files.
+ bool emitEmbeddedFiles();
+ //write the named destination stuff
+ sal_Int32 emitNamedDestinations();//i56629
+ // writes outline dict and tree
+ sal_Int32 emitOutline();
+ // puts the attribute objects of a structure element into the returned string,
+ // helper for emitStructure
+ OString emitStructureAttributes( PDFStructureElement& rEle );
+ //--->i94258
+ // the maximum array elements allowed for PDF array object
+ static const sal_uInt32 ncMaxPDFArraySize = 8191;
+ //check if internal dummy container are needed in the structure elements
+ void addInternalStructureContainer( PDFStructureElement& rEle );
+ //<---i94258
+ // writes document structure
+ sal_Int32 emitStructure( PDFStructureElement& rEle );
+ // writes structure parent tree
+ sal_Int32 emitStructParentTree( sal_Int32 nTreeObject );
+ // writes page tree and catalog
+ bool emitCatalog();
+ // writes signature dictionary object
+ bool emitSignature();
+ // creates a PKCS7 object using the ByteRange and overwrite /Contents
+ // of the signature dictionary
+ bool finalizeSignature();
+ // writes xref and trailer
+ bool emitTrailer();
+ // emit additional streams collected; also create there object numbers
+ bool emitAdditionalStreams();
+ // emits info dict (if applicable)
+ sal_Int32 emitInfoDict( );
+
+ // acrobat reader 5 and 6 use the order of the annotations
+ // as their tab order; since PDF1.5 one can make the
+ // tab order explicit by using the structure tree
+ void sortWidgets();
+
+ // updates the count numbers of outline items
+ sal_Int32 updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
+ sal_Int32 nItemLevel,
+ sal_Int32 nCurrentItemId );
+ // default appearances for widgets
+ sal_Int32 findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rRadio );
+ Font replaceFont( const Font& rControlFont, const Font& rAppSetFont );
+ sal_Int32 getBestBuildinFont( const Font& rFont );
+ sal_Int32 getSystemFont( const Font& i_rFont );
+
+ // used for edit and listbox
+ Font drawFieldBorder( PDFWidget&, const PDFWriter::AnyWidget&, const StyleSettings& );
+
+ void createDefaultPushButtonAppearance( PDFWidget&, const PDFWriter::PushButtonWidget& rWidget );
+ void createDefaultCheckBoxAppearance( PDFWidget&, const PDFWriter::CheckBoxWidget& rWidget );
+ void createDefaultRadioButtonAppearance( PDFWidget&, const PDFWriter::RadioButtonWidget& rWidget );
+ void createDefaultEditAppearance( PDFWidget&, const PDFWriter::EditWidget& rWidget );
+ void createDefaultListBoxAppearance( PDFWidget&, const PDFWriter::ListBoxWidget& rWidget );
+
+ /* ensure proper escapement and uniqueness of field names */
+ void createWidgetFieldName( sal_Int32 i_nWidgetsIndex, const PDFWriter::AnyWidget& i_rInWidget );
+ /* adds an entry to m_aObjects and returns its index+1,
+ * sets the offset to ~0
+ */
+ sal_Int32 createObject();
+ /* sets the offset of object n to the current position of output file+1
+ */
+ bool updateObject( sal_Int32 n );
+
+ bool writeBuffer( const void* pBuffer, sal_uInt64 nBytes );
+ void beginCompression();
+ void endCompression();
+ void beginRedirect( SvStream* pStream, const tools::Rectangle& );
+ SvStream* endRedirect();
+
+ void endPage();
+
+ void beginStructureElementMCSeq();
+ void endStructureElementMCSeq();
+ /** checks whether a non struct element lies in the ancestor hierarchy
+ of the current structure element
+
+ @returns
+ true if no NonStructElement was found in ancestor path and tagged
+ PDF output is enabled
+ false else
+ */
+ bool checkEmitStructure();
+
+ /* draws an emphasis mark */
+ void drawEmphasisMark( long nX, long nY, const tools::PolyPolygon& rPolyPoly, bool bPolyLine, const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 );
+
+ /* true if PDF/A-1a or PDF/A-1b is output */
+ bool m_bIsPDF_A1;
+ /* true if PDF/A-2a is output */
+ bool m_bIsPDF_A2;
+
+ /* PDF/UA support enabled */
+ bool m_bIsPDF_UA;
+
+ bool m_bIsPDF_A3;
+
+ PDFWriter& m_rOuterFace;
+
+ /*
+ i12626
+ methods for PDF security
+
+ pad a password according algorithm 3.2, step 1 */
+ static void padPassword( const OUString& i_rPassword, sal_uInt8* o_pPaddedPW );
+ /* algorithm 3.2: compute an encryption key */
+ static bool computeEncryptionKey( EncHashTransporter*,
+ vcl::PDFWriter::PDFEncryptionProperties& io_rProperties,
+ sal_Int32 i_nAccessPermissions
+ );
+ /* algorithm 3.3: computing the encryption dictionary'ss owner password value ( /O ) */
+ static bool computeODictionaryValue( const sal_uInt8* i_pPaddedOwnerPassword, const sal_uInt8* i_pPaddedUserPassword,
+ std::vector< sal_uInt8 >& io_rOValue,
+ sal_Int32 i_nKeyLength
+ );
+ /* algorithm 3.4 or 3.5: computing the encryption dictionary's user password value ( /U ) revision 2 or 3 of the standard security handler */
+ static bool computeUDictionaryValue( EncHashTransporter* i_pTransporter,
+ vcl::PDFWriter::PDFEncryptionProperties& io_rProperties,
+ sal_Int32 i_nKeyLength,
+ sal_Int32 i_nAccessPermissions
+ );
+
+ static void computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier,
+ const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
+ const OString& i_rCString1,
+ OString& o_rCString2
+ );
+ static sal_Int32 computeAccessPermissions( const vcl::PDFWriter::PDFEncryptionProperties& i_rProperties,
+ sal_Int32& o_rKeyLength, sal_Int32& o_rRC4KeyLength );
+ void setupDocInfo();
+ bool prepareEncryption( const css::uno::Reference< css::beans::XMaterialHolder >& );
+
+ // helper for playMetafile
+ void implWriteGradient( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient,
+ VirtualDevice* pDummyVDev, const vcl::PDFWriter::PlayMetafileContext& );
+ void implWriteBitmapEx( const Point& rPoint, const Size& rSize, const BitmapEx& rBitmapEx, const Graphic& i_pGraphic,
+ VirtualDevice const * pDummyVDev, const vcl::PDFWriter::PlayMetafileContext& );
+
+ // helpers for CCITT 1bit bitmap stream
+ void putG4Bits( sal_uInt32 i_nLength, sal_uInt32 i_nCode, BitStreamState& io_rState );
+ void putG4Span( long i_nSpan, bool i_bWhitePixel, BitStreamState& io_rState );
+ void writeG4Stream( BitmapReadAccess const * i_pBitmap );
+
+ // color helper functions
+ void appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer );
+ void appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer );
+public:
+ PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext, const css::uno::Reference< css::beans::XMaterialHolder >&, PDFWriter& );
+ ~PDFWriterImpl() override;
+ void dispose() override;
+
+ static css::uno::Reference< css::beans::XMaterialHolder >
+ initEncryption( const OUString& i_rOwnerPassword,
+ const OUString& i_rUserPassword );
+
+ /* document structure */
+ void newPage( double nPageWidth , double nPageHeight, PDFWriter::Orientation eOrientation );
+ bool emit();
+ const std::set< PDFWriter::ErrorCode > & getErrors() const { return m_aErrors;}
+ void insertError( PDFWriter::ErrorCode eErr ) { m_aErrors.insert( eErr ); }
+ void playMetafile( const GDIMetaFile&, vcl::PDFExtOutDevData*, const vcl::PDFWriter::PlayMetafileContext&, VirtualDevice* pDummyDev = nullptr );
+
+ Size getCurPageSize() const
+ {
+ Size aSize;
+ if( m_nCurrentPage >= 0 && m_nCurrentPage < static_cast<sal_Int32>(m_aPages.size()) )
+ aSize = Size( m_aPages[ m_nCurrentPage ].m_nPageWidth, m_aPages[ m_nCurrentPage ].m_nPageHeight );
+ return aSize;
+ }
+
+ PDFWriter::PDFVersion getVersion() const { return m_aContext.Version; }
+
+ void setDocumentLocale( const css::lang::Locale& rLoc )
+ { m_aContext.DocumentLocale = rLoc; }
+
+ /* graphics state */
+ void push( PushFlags nFlags );
+ void pop();
+
+ void setFont( const Font& rFont );
+
+ void setMapMode( const MapMode& rMapMode );
+
+ const MapMode& getMapMode() { return m_aGraphicsStack.front().m_aMapMode; }
+
+ void setLineColor( const Color& rColor )
+ {
+ m_aGraphicsStack.front().m_aLineColor = ImplIsColorTransparent(rColor) ? COL_TRANSPARENT : rColor;
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::LineColor;
+ }
+
+ void setFillColor( const Color& rColor )
+ {
+ m_aGraphicsStack.front().m_aFillColor = ImplIsColorTransparent(rColor) ? COL_TRANSPARENT : rColor;
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::FillColor;
+ }
+
+ void setTextLineColor()
+ {
+ m_aGraphicsStack.front().m_aTextLineColor = COL_TRANSPARENT;
+ }
+
+ void setTextLineColor( const Color& rColor )
+ {
+ m_aGraphicsStack.front().m_aTextLineColor = rColor;
+ }
+
+ void setOverlineColor()
+ {
+ m_aGraphicsStack.front().m_aOverlineColor = COL_TRANSPARENT;
+ }
+
+ void setOverlineColor( const Color& rColor )
+ {
+ m_aGraphicsStack.front().m_aOverlineColor = rColor;
+ }
+
+ void setTextFillColor( const Color& rColor )
+ {
+ m_aGraphicsStack.front().m_aFont.SetFillColor( rColor );
+ m_aGraphicsStack.front().m_aFont.SetTransparent( ImplIsColorTransparent( rColor ) );
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
+ }
+ void setTextFillColor()
+ {
+ m_aGraphicsStack.front().m_aFont.SetFillColor( COL_TRANSPARENT );
+ m_aGraphicsStack.front().m_aFont.SetTransparent( true );
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
+ }
+ void setTextColor( const Color& rColor )
+ {
+ m_aGraphicsStack.front().m_aFont.SetColor( rColor );
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
+ }
+
+ void clearClipRegion()
+ {
+ m_aGraphicsStack.front().m_aClipRegion.clear();
+ m_aGraphicsStack.front().m_bClipRegion = false;
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
+ }
+
+ void setClipRegion( const basegfx::B2DPolyPolygon& rRegion );
+
+ void moveClipRegion( sal_Int32 nX, sal_Int32 nY );
+
+ void intersectClipRegion( const tools::Rectangle& rRect );
+
+ void intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion );
+
+ void setLayoutMode( ComplexTextLayoutFlags nLayoutMode )
+ {
+ m_aGraphicsStack.front().m_nLayoutMode = nLayoutMode;
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::LayoutMode;
+ }
+
+ void setDigitLanguage( LanguageType eLang )
+ {
+ m_aGraphicsStack.front().m_aDigitLanguage = eLang;
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::DigitLanguage;
+ }
+
+ void setTextAlign( TextAlign eAlign )
+ {
+ m_aGraphicsStack.front().m_aFont.SetAlignment( eAlign );
+ m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
+ }
+
+ /* actual drawing functions */
+ void drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines = true );
+ void drawTextArray( const Point& rPos, const OUString& rText, const long* pDXArray, sal_Int32 nIndex, sal_Int32 nLen );
+ void drawStretchText( const Point& rPos, sal_uLong nWidth, const OUString& rText,
+ sal_Int32 nIndex, sal_Int32 nLen );
+ void drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle );
+ void drawTextLine( const Point& rPos, long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove );
+ void drawWaveTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove );
+ void drawStraightTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove );
+ void drawStrikeoutLine( OStringBuffer& aLine, long nWidth, FontStrikeout eStrikeout, Color aColor );
+ void drawStrikeoutChar( const Point& rPos, long nWidth, FontStrikeout eStrikeout );
+
+ void drawLine( const Point& rStart, const Point& rStop );
+ void drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo );
+ void drawPolygon( const tools::Polygon& rPoly );
+ void drawPolyPolygon( const tools::PolyPolygon& rPolyPoly );
+ void drawPolyLine( const tools::Polygon& rPoly );
+ void drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo );
+ void drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo );
+
+ void drawPixel( const Point& rPt, const Color& rColor );
+
+ void drawRectangle( const tools::Rectangle& rRect );
+ void drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound );
+ void drawEllipse( const tools::Rectangle& rRect );
+ void drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWidthChord );
+
+ void drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic );
+ void drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap );
+ void drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const Bitmap& rMask, const Graphic& rGraphic );
+ /// Stores the original PDF data from rGraphic as an embedded file.
+ void createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject);
+
+ void drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient );
+ void drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch );
+ void drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall );
+ void drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent );
+ void beginTransparencyGroup();
+ void endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent );
+
+ void emitComment( const char* pComment );
+
+ //--->i56629 named destinations
+ sal_Int32 createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType );
+
+ //--->i59651
+ //emits output intent
+ sal_Int32 emitOutputIntent();
+
+ //emits the document metadata
+ sal_Int32 emitDocumentMetadata();
+
+ // links
+ sal_Int32 createLink( const tools::Rectangle& rRect, sal_Int32 nPageNr );
+ sal_Int32 createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType );
+ sal_Int32 registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType );
+ void setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId );
+ void setLinkURL( sal_Int32 nLinkId, const OUString& rURL );
+ void setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId );
+
+ // screens
+ sal_Int32 createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr);
+ void setScreenURL(sal_Int32 nScreenId, const OUString& rURL);
+ void setScreenStream(sal_Int32 nScreenId, const OUString& rURL);
+
+ // outline
+ sal_Int32 createOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID );
+ void setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent );
+ void setOutlineItemText( sal_Int32 nItem, const OUString& rText );
+ void setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID );
+
+ // notes
+ void createNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr );
+ // structure elements
+ sal_Int32 beginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias );
+ void endStructureElement();
+ bool setCurrentStructureElement( sal_Int32 nElement );
+ bool setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal );
+ bool setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue );
+ void setStructureBoundingBox( const tools::Rectangle& rRect );
+ void setActualText( const OUString& rText );
+ void setAlternateText( const OUString& rText );
+
+ // transitional effects
+ void setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr );
+
+ // controls
+ sal_Int32 createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr = -1 );
+
+ // additional streams
+ void addStream( const OUString& rMimeType, PDFOutputStream* pStream );
+
+ // helper: eventually begin marked content sequence and
+ // emit a comment in debug case
+ void MARK( const char* pString );
+};
+
+} // namespace vcl
+
+#endif //_VCL_PDFEXPORT_HXX
+
+/* 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 000000000..443e51e3a
--- /dev/null
+++ b/vcl/source/gdi/pdfwriter_impl2.cxx
@@ -0,0 +1,1989 @@
+/* -*- 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 "pdfwriter_impl.hxx"
+
+#include <vcl/pdfextoutdevdata.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/bitmapaccess.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 <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 <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;
+
+ i_pDummyVDev->AddGradientActions( i_rPolyPoly.GetBoundRect(), i_rGradient, 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 = aDstSizeTwip.Width() * i_rContext.m_nMaxImageResolution / 1440.0;
+ const double fMaxPixelY = aDstSizeTwip.Height() * i_rContext.m_nMaxImageResolution / 1440.0;
+
+ // 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() )
+ {
+ if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
+ {
+ BmpConversion eConv = BmpConversion::N8BitGreys;
+ int nDepth = aBitmapEx.GetBitmap().GetBitCount();
+ if( nDepth <= 4 )
+ eConv = BmpConversion::N4BitGreys;
+ if( nDepth > 1 )
+ aBitmapEx.Convert( eConv );
+ }
+ bool bUseJPGCompression = !i_rContext.m_bOnlyLosslessCompression;
+ if ( bIsPng || ( aSizePixel.Width() < 32 ) || ( aSizePixel.Height() < 32 ) )
+ bUseJPGCompression = false;
+
+ auto pStrm=std::make_shared<SvMemoryStream>();
+ Bitmap aMask;
+
+ bool bTrueColorJPG = true;
+ if ( bUseJPGCompression )
+ {
+ // TODO this checks could be done much earlier, saving us
+ // from trying conversion & stores before...
+ if ( !aBitmapEx.IsTransparent() )
+ {
+ const auto& rCacheEntry=m_aPDFBmpCache.find(
+ aBitmapEx.GetChecksum());
+ if ( rCacheEntry != m_aPDFBmpCache.end() )
+ {
+ m_rOuterFace.DrawJPGBitmap( *rCacheEntry->second, true, aSizePixel,
+ tools::Rectangle( aPoint, aSize ), aMask, 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.IsTransparent() )
+ {
+ if ( aBitmapEx.IsAlpha() )
+ aMask = aBitmapEx.GetAlpha().GetBitmap();
+ else
+ aMask = aBitmapEx.GetMask();
+ }
+ Graphic aGraphic( aBitmapEx.GetBitmap() );
+
+ Sequence< PropertyValue > aFilterData( 2 );
+ aFilterData[ 0 ].Name = "Quality";
+ aFilterData[ 0 ].Value <<= sal_Int32(i_rContext.m_nJPEGQuality);
+ aFilterData[ 1 ].Name = "ColorMode";
+ aFilterData[ 1 ].Value <<= 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( 3 );
+ aOutMediaProperties[0].Name = "OutputStream";
+ aOutMediaProperties[0].Value <<= xOut;
+ aOutMediaProperties[1].Name = "MimeType";
+ aOutMediaProperties[1].Value <<= OUString("image/jpeg");
+ aOutMediaProperties[2].Name = "FilterData";
+ aOutMediaProperties[2].Value <<= 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( 1 );
+ aArgs[ 0 ].Name = "InputStream";
+ aArgs[ 0 ].Value <<= 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 ), aMask, i_Graphic );
+ if (!aBitmapEx.IsTransparent() && bTrueColorJPG)
+ {
+ // Cache last jpeg export
+ m_aPDFBmpCache.insert(
+ {aBitmapEx.GetChecksum(), pStrm});
+ }
+ }
+ else if ( aBitmapEx.IsTransparent() )
+ 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();
+ 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 = static_cast<sal_Int32>(static_cast<double>(aDstSizeTwip.Width()) * static_cast<double>(nMaxBmpDPI) / 1440.0);
+ const sal_Int32 nPixelY = static_cast<sal_Int32>(static_cast<double>(aDstSizeTwip.Height()) * static_cast<double>(nMaxBmpDPI) / 1440.0);
+ if ( nPixelX && nPixelY )
+ {
+ Size aDstSizePixel( nPixelX, nPixelY );
+ ScopedVclPtrInstance<VirtualDevice> xVDev;
+ if( xVDev->SetOutputSizePixel( aDstSizePixel ) )
+ {
+ Bitmap aPaint, aMask;
+ AlphaMask aAlpha;
+ Point aPoint;
+
+ MapMode aMapMode( pDummyVDev->GetMapMode() );
+ aMapMode.SetOrigin( aPoint );
+ xVDev->SetMapMode( aMapMode );
+ 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.get(), aPoint, aDstSize );
+ aTmpMtf.WindStart();
+
+ xVDev->EnableMapMode( false );
+ aPaint = xVDev->GetBitmap( aPoint, aDstSizePixel );
+ xVDev->EnableMapMode();
+
+ // create mask bitmap
+ xVDev->SetLineColor( COL_BLACK );
+ xVDev->SetFillColor( COL_BLACK );
+ xVDev->DrawRect( tools::Rectangle( aPoint, aDstSize ) );
+ xVDev->SetDrawMode( DrawModeFlags::WhiteLine | DrawModeFlags::WhiteFill | DrawModeFlags::WhiteText |
+ DrawModeFlags::WhiteBitmap | DrawModeFlags::WhiteGradient );
+ aTmpMtf.WindStart();
+ aTmpMtf.Play( xVDev.get(), aPoint, aDstSize );
+ aTmpMtf.WindStart();
+ xVDev->EnableMapMode( false );
+ aMask = xVDev->GetBitmap( aPoint, aDstSizePixel );
+ xVDev->EnableMapMode();
+
+ // create alpha mask from gradient
+ xVDev->SetDrawMode( DrawModeFlags::GrayGradient );
+ xVDev->DrawGradient( tools::Rectangle( aPoint, aDstSize ), rTransparenceGradient );
+ xVDev->SetDrawMode( DrawModeFlags::Default );
+ xVDev->EnableMapMode( false );
+ xVDev->DrawMask( aPoint, aDstSizePixel, aMask, COL_WHITE );
+ aAlpha = xVDev->GetBitmap( aPoint, aDstSizePixel );
+
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( rPos, rSize, BitmapEx( aPaint, 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 = OString("XPATHSTROKE_SEQ_END");
+ 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 = OString("XPATHFILL_SEQ_END");
+ 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);
+ const BitmapEx& aBitmapEx( pA->GetBitmapEx() );
+ 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);
+ Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic();
+ implWriteBitmapEx( pA->GetPoint(), pA->GetSize(), pA->GetBitmapEx(), aGraphic, pDummyVDev, i_rContext );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction);
+ BitmapEx aBitmapEx( pA->GetBitmapEx() );
+ 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->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::makeAny( 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() )
+ {
+ 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() )
+ {
+ 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() )
+ {
+ 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( const OUString& 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 */
+
+static const 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 */
+};
+
+static const 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, long i_nIndex )
+{
+ return (i_pLine[ i_nIndex/8 ] & (0x80 >> (i_nIndex&7))) != 0;
+}
+
+static long findBitRunImpl( const Scanline i_pLine, long i_nStartIndex, long i_nW, bool i_bSet )
+{
+ 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
+ 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 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 long findBitRun(const Scanline i_pLine, long i_nStartIndex, long i_nW, bool i_bSet)
+{
+ if (i_nStartIndex < 0)
+ return i_nW;
+
+ return findBitRunImpl(i_pLine, i_nStartIndex, i_nW, i_bSet);
+}
+
+static long findBitRun(const Scanline i_pLine, long i_nStartIndex, 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;
+ writeBuffer( &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 )
+ {
+ writeBuffer( &io_rState.getByte(), 1 );
+ io_rState.flush();
+ }
+}
+
+namespace {
+
+struct PixelCode
+{
+ sal_uInt32 mnEncodedPixels;
+ sal_uInt32 mnCodeBits;
+ sal_uInt32 mnCode;
+};
+
+}
+
+static 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
+};
+
+static 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( 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 )
+{
+ long nW = i_pBitmap->Width();
+ 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( long nY = 0; nY < nH; nY++ )
+ {
+ const Scanline pCurLine = i_pBitmap->GetScanline( nY );
+ long nLineIndex = 0;
+ bool bRunSet = (*pCurLine & 0x80) != 0;
+ bool bRefSet = (*pRefLine & 0x80) != 0;
+ long nRunIndex1 = bRunSet ? 0 : findBitRun( pCurLine, 0, nW, bRunSet );
+ long nRefIndex1 = bRefSet ? 0 : findBitRun( pRefLine, 0, nW, bRefSet );
+ for( ; nLineIndex < nW; )
+ {
+ long nRefIndex2 = findBitRun( pRefLine, nRefIndex1, nW );
+ if( nRefIndex2 >= nRunIndex1 )
+ {
+ 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 );
+ 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 )
+ {
+ writeBuffer( &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 GradientStyle::Linear:
+ case 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 000000000..e6386ef17
--- /dev/null
+++ b/vcl/source/gdi/print.cxx
@@ -0,0 +1,1639 @@
+/* -*- 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 <tools/helpers.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/print.hxx>
+
+#include <comphelper/processfactory.hxx>
+
+#include <salinst.hxx>
+#include <salvd.hxx>
+#include <salgdi.hxx>
+#include <salptype.hxx>
+#include <salprn.hxx>
+#include <svdata.hxx>
+#include <print.hrc>
+#include <jobset.h>
+#include <outdev.h>
+#include <PhysicalFontCollection.hxx>
+#include <print.h>
+
+#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( long nWidth100thMM, 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);
+ }
+}
+
+// PrinterOptions
+PrinterOptions::PrinterOptions() :
+ mbReduceTransparency( false ),
+ meReducedTransparencyMode( PrinterTransparencyMode::Auto ),
+ mbReduceGradients( false ),
+ meReducedGradientsMode( PrinterGradientMode::Stripes ),
+ mnReducedGradientStepCount( 64 ),
+ mbReduceBitmaps( false ),
+ meReducedBitmapMode( PrinterBitmapMode::Normal ),
+ mnReducedBitmapResolution( 200 ),
+ mbReducedBitmapsIncludeTransparency( true ),
+ mbConvertToGreyscales( false ),
+ mbPDFAsStandardPrintJobFormat( false )
+{
+}
+
+void PrinterOptions::ReadFromConfig( bool i_bFile )
+{
+ bool bSuccess = false;
+ // save old state in case something goes wrong
+ PrinterOptions aOldValues( *this );
+
+ // get the configuration service
+ css::uno::Reference< css::lang::XMultiServiceFactory > xConfigProvider;
+ css::uno::Reference< css::container::XNameAccess > xConfigAccess;
+ try
+ {
+ // get service provider
+ css::uno::Reference< css::uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ // create configuration hierarchical access name
+ try
+ {
+ xConfigProvider = css::configuration::theDefaultProvider::get( xContext );
+
+ css::uno::Sequence< css::uno::Any > aArgs(1);
+ css::beans::PropertyValue aVal;
+ aVal.Name = "nodepath";
+ if( i_bFile )
+ aVal.Value <<= OUString( "/org.openoffice.Office.Common/Print/Option/File" );
+ else
+ aVal.Value <<= OUString( "/org.openoffice.Office.Common/Print/Option/Printer" );
+ aArgs.getArray()[0] <<= aVal;
+ xConfigAccess.set(
+ xConfigProvider->createInstanceWithArguments(
+ "com.sun.star.configuration.ConfigurationAccess", aArgs ),
+ css::uno::UNO_QUERY );
+ if( xConfigAccess.is() )
+ {
+ css::uno::Reference< css::beans::XPropertySet > xSet( xConfigAccess, css::uno::UNO_QUERY );
+ if( xSet.is() )
+ {
+ sal_Int32 nValue = 0;
+ bool bValue = false;
+ if( xSet->getPropertyValue("ReduceTransparency") >>= bValue )
+ SetReduceTransparency( bValue );
+ if( xSet->getPropertyValue("ReducedTransparencyMode") >>= nValue )
+ SetReducedTransparencyMode( static_cast<PrinterTransparencyMode>(nValue) );
+ if( xSet->getPropertyValue("ReduceGradients") >>= bValue )
+ SetReduceGradients( bValue );
+ if( xSet->getPropertyValue("ReducedGradientMode") >>= nValue )
+ SetReducedGradientMode( static_cast<PrinterGradientMode>(nValue) );
+ if( xSet->getPropertyValue("ReducedGradientStepCount") >>= nValue )
+ SetReducedGradientStepCount( static_cast<sal_uInt16>(nValue) );
+ if( xSet->getPropertyValue("ReduceBitmaps") >>= bValue )
+ SetReduceBitmaps( bValue );
+ if( xSet->getPropertyValue("ReducedBitmapMode") >>= nValue )
+ SetReducedBitmapMode( static_cast<PrinterBitmapMode>(nValue) );
+ if( xSet->getPropertyValue("ReducedBitmapResolution") >>= nValue )
+ SetReducedBitmapResolution( static_cast<sal_uInt16>(nValue) );
+ if( xSet->getPropertyValue("ReducedBitmapIncludesTransparency") >>= bValue )
+ SetReducedBitmapIncludesTransparency( bValue );
+ if( xSet->getPropertyValue("ConvertToGreyscales") >>= bValue )
+ SetConvertToGreyscales( bValue );
+ if( xSet->getPropertyValue("PDFAsStandardPrintJobFormat") >>= bValue )
+ SetPDFAsStandardPrintJobFormat( bValue );
+
+ bSuccess = true;
+ }
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+ }
+ catch( const css::lang::WrappedTargetException& )
+ {
+ }
+
+ if( ! bSuccess )
+ *this = aOldValues;
+}
+
+bool Printer::DrawTransformBitmapExDirect(
+ const basegfx::B2DHomMatrix& /*aFullTransform*/,
+ const BitmapEx& /*rBitmapEx*/)
+{
+ // 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::DrawDeviceBitmap( 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.GetAlpha(), COL_WHITE );
+ DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmp );
+ }
+ else
+ {
+ Bitmap aBmp( rBmpEx.GetBitmap() ), aMask( rBmpEx.GetMask() );
+ aBmp.Replace( aMask, COL_WHITE );
+ ImplPrintTransparent( aBmp, aMask, 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 long nBaseExtent = std::max( FRound( aDPISize.Width() / 300. ), 1L );
+ 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( PushFlags::CLIPREGION | 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 PrinterOptions& i_rOptions )
+{
+ *mpPrinterOptions = i_rOptions;
+}
+
+bool Printer::HasMirroredGraphics() const
+{
+ // due to a "hotfix" for AOO bug i55719, this needs to return false
+ return false;
+}
+
+// QueueInfo
+QueueInfo::QueueInfo()
+{
+ mnStatus = PrintQueueFlags::NONE;
+ mnJobs = 0;
+}
+
+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();
+ 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;
+ mpInfoPrinter = nullptr;
+ mpPrinter = nullptr;
+ mpDisplayDev = nullptr;
+ mpPrinterOptions.reset(new PrinterOptions);
+
+ // 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->setAntiAliasB2DDraw(bool(mnAntialiasing & AntialiasingFlags::EnableB2dDraw));
+ }
+
+ return mpGraphics != nullptr;
+}
+
+void Printer::ImplReleaseFonts()
+{
+#ifdef UNX
+ // HACK to fix an urgent P1 printing issue fast
+ // WinSalPrinter does not respect GetGraphics/ReleaseGraphics conventions
+ // so Printer::mpGraphics often points to a dead WinSalGraphics
+ // TODO: fix WinSalPrinter's GetGraphics/ReleaseGraphics handling
+ mpGraphics->ReleaseFonts();
+#endif
+ mbNewFont = true;
+ mbInitFont = true;
+
+ mpFontInstance.clear();
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.reset();
+}
+
+void Printer::ReleaseGraphics( 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 = mpNextGraphics;
+ if ( mpNextGraphics )
+ mpNextGraphics->mpPrevGraphics = mpPrevGraphics;
+ else
+ pSVData->maGDIData.mpLastPrnGraphics = mpPrevGraphics;
+ }
+ }
+
+ mpGraphics = nullptr;
+ mpPrevGraphics = nullptr;
+ mpNextGraphics = nullptr;
+}
+
+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 )
+ {
+ std::free( const_cast<sal_uInt8*>(rData.GetDriverData()) );
+ rData.SetDriverData(nullptr);
+ rData.SetDriverDataLen(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<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.Justify();
+
+ if( !(!rMask.IsEmpty() && aSrcRect.GetWidth() && aSrcRect.GetHeight() && aDestSz.Width() && aDestSz.Height()) )
+ return;
+
+ Bitmap aMask( rMask );
+ BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE;
+
+ if( aMask.GetBitCount() > 1 )
+ aMask.Convert( BmpConversion::N1BitThreshold );
+
+ // mirrored horizontically
+ 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 long nSrcWidth = aSrcRect.GetWidth(), nSrcHeight = aSrcRect.GetHeight();
+ long nX, nY; //, nWorkX, nWorkY, nWorkWidth, nWorkHeight;
+ std::unique_ptr<long[]> pMapX( new long[ nSrcWidth + 1 ] );
+ std::unique_ptr<long[]> pMapY( new long[ nSrcHeight + 1 ] );
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ const bool bOldMap = mbMap;
+
+ mpMetaFile = nullptr;
+ mbMap = false;
+ Push( PushFlags::FILLCOLOR | 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();
+}
+
+long Printer::GetGradientStepCount( long nMinRect )
+{
+ // use display-equivalent step size calculation
+ 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();
+
+ ReleaseGraphics();
+ 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();
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.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();
+}
+
+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();
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.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();
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.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 ))
+ {
+ const long nRotatedWidth = rData.GetPaperHeight();
+ const 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 )
+ {
+ 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() );
+
+ 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() )
+ {
+ 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
+ };
+ 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 || nPaper >= int(mpInfoPrinter->m_aPaperFormats.size()) )
+ return ImplGetEmptyPaper();
+ return mpInfoPrinter->m_aPaperFormats[nPaper];
+}
+
+Size Printer::GetPaperSize( int nPaper )
+{
+ PaperInfo aInfo = GetPaperInfo( nPaper );
+ return PixelToLogic( Size( aInfo.getWidth(), aInfo.getHeight() ) );
+}
+
+void Printer::SetDuplexMode( DuplexMode eDuplex )
+{
+ if ( mbInPrintPage )
+ return;
+
+ if ( maJobSetup.ImplGetConstData().GetDuplexMode() != eDuplex )
+ {
+ 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();
+}
+
+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 )
+ {
+ 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 )
+ {
+ mpPrinter->EndPage();
+ ReleaseGraphics();
+ mbDevOutput = false;
+
+ mpJobGraphics = nullptr;
+ mbNewJobSetup = false;
+ }
+}
+
+void Printer::updatePrinters()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ ImplPrnQueueList* pPrnList = pSVData->maGDIData.mpPrinterQueueList.get();
+
+ if ( pPrnList )
+ {
+ 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 )
+ {
+ 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( 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 );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/print2.cxx b/vcl/source/gdi/print2.cxx
new file mode 100644
index 000000000..89fec06ff
--- /dev/null
+++ b/vcl/source/gdi/print2.cxx
@@ -0,0 +1,1314 @@
+/* -*- 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 <utility>
+#include <list>
+#include <vector>
+
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <sal/log.hxx>
+#include <officecfg/Office/Common.hxx>
+
+#include <vcl/virdev.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/print.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/bitmapaccess.hxx>
+
+#include "pdfwriter_impl.hxx"
+
+#define MAX_TILE_WIDTH 1024
+#define MAX_TILE_HEIGHT 1024
+
+typedef ::std::pair< MetaAction*, int > Component; // MetaAction plus index in metafile
+
+namespace {
+
+// List of (intersecting) actions, plus overall bounds
+struct ConnectedComponents
+{
+ ConnectedComponents() :
+ aComponentList(),
+ aBounds(),
+ aBgColor(COL_WHITE),
+ bIsSpecial(false),
+ bIsFullyTransparent(false)
+ {}
+
+ ::std::list< Component > aComponentList;
+ tools::Rectangle aBounds;
+ Color aBgColor;
+ bool bIsSpecial;
+ bool bIsFullyTransparent;
+};
+
+}
+
+namespace {
+
+/** Determines whether the action can handle transparency correctly
+ (i.e. when painted on white background, does the action still look
+ correct)?
+ */
+bool DoesActionHandleTransparency( const MetaAction& rAct )
+{
+ // MetaActionType::FLOATTRANSPARENT can contain a whole metafile,
+ // which is to be rendered with the given transparent gradient. We
+ // currently cannot emulate transparent painting on a white
+ // background reliably.
+
+ // the remainder can handle printing itself correctly on a uniform
+ // white background.
+ switch( rAct.GetType() )
+ {
+ case MetaActionType::Transparent:
+ case MetaActionType::BMPEX:
+ case MetaActionType::BMPEXSCALE:
+ case MetaActionType::BMPEXSCALEPART:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+bool doesRectCoverWithUniformColor(
+ tools::Rectangle const & rPrevRect,
+ tools::Rectangle const & rCurrRect,
+ OutputDevice const & rMapModeVDev)
+{
+ // shape needs to fully cover previous content, and have uniform
+ // color
+ return (rMapModeVDev.LogicToPixel(rCurrRect).IsInside(rPrevRect) &&
+ rMapModeVDev.IsFillColor());
+}
+
+/** Check whether rCurrRect rectangle fully covers io_rPrevRect - if
+ yes, return true and update o_rBgColor
+ */
+bool checkRect( tools::Rectangle& io_rPrevRect,
+ Color& o_rBgColor,
+ const tools::Rectangle& rCurrRect,
+ OutputDevice const & rMapModeVDev )
+{
+ bool bRet = doesRectCoverWithUniformColor(io_rPrevRect, rCurrRect, rMapModeVDev);
+
+ if( bRet )
+ {
+ io_rPrevRect = rCurrRect;
+ o_rBgColor = rMapModeVDev.GetFillColor();
+ }
+
+ return bRet;
+}
+
+/** #107169# Convert BitmapEx to Bitmap with appropriately blended
+ color. Convert MetaTransparentAction to plain polygon,
+ appropriately colored
+
+ @param o_rMtf
+ Add converted actions to this metafile
+*/
+void ImplConvertTransparentAction( GDIMetaFile& o_rMtf,
+ const MetaAction& rAct,
+ const OutputDevice& rStateOutDev,
+ Color aBgColor )
+{
+ if (rAct.GetType() == MetaActionType::Transparent)
+ {
+ const MetaTransparentAction* pTransAct = static_cast<const MetaTransparentAction*>(&rAct);
+ sal_uInt16 nTransparency( pTransAct->GetTransparence() );
+
+ // #i10613# Respect transparency for draw color
+ if (nTransparency)
+ {
+ o_rMtf.AddAction(new MetaPushAction(PushFlags::LINECOLOR|PushFlags::FILLCOLOR));
+
+ // assume white background for alpha blending
+ Color aLineColor(rStateOutDev.GetLineColor());
+ aLineColor.SetRed(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetRed()) / 100));
+ aLineColor.SetGreen(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetGreen()) / 100));
+ aLineColor.SetBlue(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetBlue()) / 100));
+ o_rMtf.AddAction(new MetaLineColorAction(aLineColor, true));
+
+ Color aFillColor(rStateOutDev.GetFillColor());
+ aFillColor.SetRed(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetRed()) / 100));
+ aFillColor.SetGreen(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetGreen()) / 100));
+ aFillColor.SetBlue(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetBlue()) / 100));
+ o_rMtf.AddAction(new MetaFillColorAction(aFillColor, true));
+ }
+
+ o_rMtf.AddAction(new MetaPolyPolygonAction(pTransAct->GetPolyPolygon()));
+
+ if(nTransparency)
+ o_rMtf.AddAction(new MetaPopAction());
+ }
+ else
+ {
+ BitmapEx aBmpEx;
+
+ switch (rAct.GetType())
+ {
+ case MetaActionType::BMPEX:
+ aBmpEx = static_cast<const MetaBmpExAction&>(rAct).GetBitmapEx();
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ aBmpEx = static_cast<const MetaBmpExScaleAction&>(rAct).GetBitmapEx();
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ aBmpEx = static_cast<const MetaBmpExScaleAction&>(rAct).GetBitmapEx();
+ break;
+
+ case MetaActionType::Transparent:
+
+ default:
+ OSL_FAIL("Printer::GetPreparedMetafile impossible state reached");
+ break;
+ }
+
+ Bitmap aBmp(aBmpEx.GetBitmap());
+ if (!aBmpEx.IsAlpha())
+ {
+ // blend with mask
+ Bitmap::ScopedReadAccess pRA(aBmp);
+
+ if (!pRA)
+ return; // what else should I do?
+
+ Color aActualColor(aBgColor);
+
+ if (pRA->HasPalette())
+ aActualColor = pRA->GetBestPaletteColor(aBgColor);
+
+ pRA.reset();
+
+ // did we get true white?
+ if (aActualColor.GetColorError(aBgColor))
+ {
+ // no, create truecolor bitmap, then
+ aBmp.Convert(BmpConversion::N24Bit);
+
+ // fill masked out areas white
+ aBmp.Replace(aBmpEx.GetMask(), aBgColor);
+ }
+ else
+ {
+ // fill masked out areas white
+ aBmp.Replace(aBmpEx.GetMask(), aActualColor);
+ }
+ }
+ else
+ {
+ // blend with alpha channel
+ aBmp.Convert(BmpConversion::N24Bit);
+ aBmp.Blend(aBmpEx.GetAlpha(), aBgColor);
+ }
+
+ // add corresponding action
+ switch (rAct.GetType())
+ {
+ case MetaActionType::BMPEX:
+ o_rMtf.AddAction(new MetaBmpAction(
+ static_cast<const MetaBmpExAction&>(rAct).GetPoint(),
+ aBmp));
+ break;
+ case MetaActionType::BMPEXSCALE:
+ o_rMtf.AddAction(new MetaBmpScaleAction(
+ static_cast<const MetaBmpExScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaBmpExScaleAction&>(rAct).GetSize(),
+ aBmp));
+ break;
+ case MetaActionType::BMPEXSCALEPART:
+ o_rMtf.AddAction(new MetaBmpScalePartAction(
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestSize(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetSrcPoint(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetSrcSize(),
+ aBmp));
+ break;
+ default:
+ OSL_FAIL("Unexpected case");
+ break;
+ }
+ }
+}
+
+// #i10613# Extracted from ImplCheckRect::ImplCreate
+// Returns true, if given action creates visible (i.e. non-transparent) output
+bool ImplIsNotTransparent( const MetaAction& rAct, const OutputDevice& rOut )
+{
+ const bool bLineTransparency( !rOut.IsLineColor() || rOut.GetLineColor().GetTransparency() == 255 );
+ const bool bFillTransparency( !rOut.IsFillColor() || rOut.GetFillColor().GetTransparency() == 255 );
+ bool bRet( false );
+
+ switch( rAct.GetType() )
+ {
+ case MetaActionType::POINT:
+ if( !bLineTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::LINE:
+ if( !bLineTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::RECT:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::ELLIPSE:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::ARC:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::PIE:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::CHORD:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::POLYLINE:
+ if( !bLineTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::POLYGON:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction& rTextAct = static_cast<const MetaTextAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+ if (!aString.isEmpty())
+ bRet = true;
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction& rTextAct = static_cast<const MetaTextArrayAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+ if (!aString.isEmpty())
+ bRet = true;
+ }
+ break;
+
+ case MetaActionType::PIXEL:
+ case MetaActionType::BMP:
+ case MetaActionType::BMPSCALE:
+ case MetaActionType::BMPSCALEPART:
+ case MetaActionType::BMPEX:
+ case MetaActionType::BMPEXSCALE:
+ case MetaActionType::BMPEXSCALEPART:
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ case MetaActionType::GRADIENT:
+ case MetaActionType::GRADIENTEX:
+ case MetaActionType::HATCH:
+ case MetaActionType::WALLPAPER:
+ case MetaActionType::Transparent:
+ case MetaActionType::FLOATTRANSPARENT:
+ case MetaActionType::EPS:
+ case MetaActionType::TEXTRECT:
+ case MetaActionType::STRETCHTEXT:
+ case MetaActionType::TEXTLINE:
+ // all other actions: generate non-transparent output
+ bRet = true;
+ break;
+
+ default:
+ break;
+ }
+
+ return bRet;
+}
+
+// #i10613# Extracted from ImplCheckRect::ImplCreate
+tools::Rectangle ImplCalcActionBounds( const MetaAction& rAct, const OutputDevice& rOut )
+{
+ tools::Rectangle aActionBounds;
+
+ switch( rAct.GetType() )
+ {
+ case MetaActionType::PIXEL:
+ aActionBounds = tools::Rectangle( static_cast<const MetaPixelAction&>(rAct).GetPoint(), Size( 1, 1 ) );
+ break;
+
+ case MetaActionType::POINT:
+ aActionBounds = tools::Rectangle( static_cast<const MetaPointAction&>(rAct).GetPoint(), Size( 1, 1 ) );
+ break;
+
+ case MetaActionType::LINE:
+ {
+ const MetaLineAction& rMetaLineAction = static_cast<const MetaLineAction&>(rAct);
+ aActionBounds = tools::Rectangle( rMetaLineAction.GetStartPoint(), rMetaLineAction.GetEndPoint() );
+ aActionBounds.Justify();
+ const long nLineWidth(rMetaLineAction.GetLineInfo().GetWidth());
+ if(nLineWidth)
+ {
+ const long nHalfLineWidth((nLineWidth + 1) / 2);
+ aActionBounds.AdjustLeft( -nHalfLineWidth );
+ aActionBounds.AdjustTop( -nHalfLineWidth );
+ aActionBounds.AdjustRight(nHalfLineWidth );
+ aActionBounds.AdjustBottom(nHalfLineWidth );
+ }
+ break;
+ }
+
+ case MetaActionType::RECT:
+ aActionBounds = static_cast<const MetaRectAction&>(rAct).GetRect();
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ aActionBounds = tools::Polygon( static_cast<const MetaRoundRectAction&>(rAct).GetRect(),
+ static_cast<const MetaRoundRectAction&>(rAct).GetHorzRound(),
+ static_cast<const MetaRoundRectAction&>(rAct).GetVertRound() ).GetBoundRect();
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ const tools::Rectangle& rRect = static_cast<const MetaEllipseAction&>(rAct).GetRect();
+ aActionBounds = tools::Polygon( rRect.Center(),
+ rRect.GetWidth() >> 1,
+ rRect.GetHeight() >> 1 ).GetBoundRect();
+ break;
+ }
+
+ case MetaActionType::ARC:
+ aActionBounds = tools::Polygon( static_cast<const MetaArcAction&>(rAct).GetRect(),
+ static_cast<const MetaArcAction&>(rAct).GetStartPoint(),
+ static_cast<const MetaArcAction&>(rAct).GetEndPoint(), PolyStyle::Arc ).GetBoundRect();
+ break;
+
+ case MetaActionType::PIE:
+ aActionBounds = tools::Polygon( static_cast<const MetaPieAction&>(rAct).GetRect(),
+ static_cast<const MetaPieAction&>(rAct).GetStartPoint(),
+ static_cast<const MetaPieAction&>(rAct).GetEndPoint(), PolyStyle::Pie ).GetBoundRect();
+ break;
+
+ case MetaActionType::CHORD:
+ aActionBounds = tools::Polygon( static_cast<const MetaChordAction&>(rAct).GetRect(),
+ static_cast<const MetaChordAction&>(rAct).GetStartPoint(),
+ static_cast<const MetaChordAction&>(rAct).GetEndPoint(), PolyStyle::Chord ).GetBoundRect();
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ const MetaPolyLineAction& rMetaPolyLineAction = static_cast<const MetaPolyLineAction&>(rAct);
+ aActionBounds = rMetaPolyLineAction.GetPolygon().GetBoundRect();
+ const long nLineWidth(rMetaPolyLineAction.GetLineInfo().GetWidth());
+ if(nLineWidth)
+ {
+ const long nHalfLineWidth((nLineWidth + 1) / 2);
+ aActionBounds.AdjustLeft( -nHalfLineWidth );
+ aActionBounds.AdjustTop( -nHalfLineWidth );
+ aActionBounds.AdjustRight(nHalfLineWidth );
+ aActionBounds.AdjustBottom(nHalfLineWidth );
+ }
+ break;
+ }
+
+ case MetaActionType::POLYGON:
+ aActionBounds = static_cast<const MetaPolygonAction&>(rAct).GetPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ aActionBounds = static_cast<const MetaPolyPolygonAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::BMP:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpAction&>(rAct).GetPoint(),
+ rOut.PixelToLogic( static_cast<const MetaBmpAction&>(rAct).GetBitmap().GetSizePixel() ) );
+ break;
+
+ case MetaActionType::BMPSCALE:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaBmpScaleAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaBmpScalePartAction&>(rAct).GetDestSize() );
+ break;
+
+ case MetaActionType::BMPEX:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpExAction&>(rAct).GetPoint(),
+ rOut.PixelToLogic( static_cast<const MetaBmpExAction&>(rAct).GetBitmapEx().GetSizePixel() ) );
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpExScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaBmpExScaleAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestSize() );
+ break;
+
+ case MetaActionType::MASK:
+ aActionBounds = tools::Rectangle( static_cast<const MetaMaskAction&>(rAct).GetPoint(),
+ rOut.PixelToLogic( static_cast<const MetaMaskAction&>(rAct).GetBitmap().GetSizePixel() ) );
+ break;
+
+ case MetaActionType::MASKSCALE:
+ aActionBounds = tools::Rectangle( static_cast<const MetaMaskScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaMaskScaleAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ aActionBounds = tools::Rectangle( static_cast<const MetaMaskScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaMaskScalePartAction&>(rAct).GetDestSize() );
+ break;
+
+ case MetaActionType::GRADIENT:
+ aActionBounds = static_cast<const MetaGradientAction&>(rAct).GetRect();
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ aActionBounds = static_cast<const MetaGradientExAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::HATCH:
+ aActionBounds = static_cast<const MetaHatchAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::WALLPAPER:
+ aActionBounds = static_cast<const MetaWallpaperAction&>(rAct).GetRect();
+ break;
+
+ case MetaActionType::Transparent:
+ aActionBounds = static_cast<const MetaTransparentAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ aActionBounds = tools::Rectangle( static_cast<const MetaFloatTransparentAction&>(rAct).GetPoint(),
+ static_cast<const MetaFloatTransparentAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::EPS:
+ aActionBounds = tools::Rectangle( static_cast<const MetaEPSAction&>(rAct).GetPoint(),
+ static_cast<const MetaEPSAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction& rTextAct = static_cast<const MetaTextAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+
+ if (!aString.isEmpty())
+ {
+ const Point aPtLog( rTextAct.GetPoint() );
+
+ // #105987# Use API method instead of Impl* methods
+ // #107490# Set base parameter equal to index parameter
+ rOut.GetTextBoundRect( aActionBounds, rTextAct.GetText(), rTextAct.GetIndex(),
+ rTextAct.GetIndex(), rTextAct.GetLen() );
+ aActionBounds.Move( aPtLog.X(), aPtLog.Y() );
+ }
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction& rTextAct = static_cast<const MetaTextArrayAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+
+ if( !aString.isEmpty() )
+ {
+ // #105987# ImplLayout takes everything in logical coordinates
+ std::unique_ptr<SalLayout> pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(),
+ rTextAct.GetLen(), rTextAct.GetPoint(),
+ 0, rTextAct.GetDXArray() );
+ if( pSalLayout )
+ {
+ tools::Rectangle aBoundRect( const_cast<OutputDevice&>(rOut).ImplGetTextBoundRect( *pSalLayout ) );
+ aActionBounds = rOut.PixelToLogic( aBoundRect );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ aActionBounds = static_cast<const MetaTextRectAction&>(rAct).GetRect();
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ const MetaStretchTextAction& rTextAct = static_cast<const MetaStretchTextAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+
+ // #i16195# Literate copy from TextArray action, the
+ // semantics for the ImplLayout call are copied from the
+ // OutDev::DrawStretchText() code. Unfortunately, also in
+ // this case, public outdev methods such as GetTextWidth()
+ // don't provide enough info.
+ if( !aString.isEmpty() )
+ {
+ // #105987# ImplLayout takes everything in logical coordinates
+ std::unique_ptr<SalLayout> pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(),
+ rTextAct.GetLen(), rTextAct.GetPoint(),
+ rTextAct.GetWidth() );
+ if( pSalLayout )
+ {
+ tools::Rectangle aBoundRect( const_cast<OutputDevice&>(rOut).ImplGetTextBoundRect( *pSalLayout ) );
+ aActionBounds = rOut.PixelToLogic( aBoundRect );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ OSL_FAIL("MetaActionType::TEXTLINE not supported");
+ break;
+
+ default:
+ break;
+ }
+
+ if( !aActionBounds.IsEmpty() )
+ {
+ // fdo#40421 limit current action's output to clipped area
+ if( rOut.IsClipRegion() )
+ return rOut.LogicToPixel(
+ rOut.GetClipRegion().GetBoundRect().Intersection( aActionBounds ) );
+ else
+ return rOut.LogicToPixel( aActionBounds );
+ }
+ else
+ return tools::Rectangle();
+}
+
+} // end anon namespace
+
+bool OutputDevice::RemoveTransparenciesFromMetaFile( const GDIMetaFile& rInMtf, GDIMetaFile& rOutMtf,
+ long nMaxBmpDPIX, long nMaxBmpDPIY,
+ bool bReduceTransparency, bool bTransparencyAutoMode,
+ bool bDownsampleBitmaps,
+ const Color& rBackground
+ )
+{
+ MetaAction* pCurrAct;
+ bool bTransparent( false );
+
+ rOutMtf.Clear();
+
+ if(!bReduceTransparency || bTransparencyAutoMode)
+ bTransparent = rInMtf.HasTransparentActions();
+
+ // #i10613# Determine set of connected components containing transparent objects. These are
+ // then processed as bitmaps, the original actions are removed from the metafile.
+ if( !bTransparent )
+ {
+ // nothing transparent -> just copy
+ rOutMtf = rInMtf;
+ }
+ else
+ {
+ // #i10613#
+ // This works as follows: we want a number of distinct sets of
+ // connected components, where each set contains metafile
+ // actions that are intersecting (note: there are possibly
+ // more actions contained as are directly intersecting,
+ // because we can only produce rectangular bitmaps later
+ // on. Thus, each set of connected components is the smallest
+ // enclosing, axis-aligned rectangle that completely bounds a
+ // number of intersecting metafile actions, plus any action
+ // that would otherwise be cut in two). Therefore, we
+ // iteratively add metafile actions from the original metafile
+ // to this connected components list (aCCList), by checking
+ // each element's bounding box against intersection with the
+ // metaaction at hand.
+ // All those intersecting elements are removed from aCCList
+ // and collected in a temporary list (aCCMergeList). After all
+ // elements have been checked, the aCCMergeList elements are
+ // merged with the metaaction at hand into one resulting
+ // connected component, with one big bounding box, and
+ // inserted into aCCList again.
+ // The time complexity of this algorithm is O(n^3), where n is
+ // the number of metafile actions, and it finds all distinct
+ // regions of rectangle-bounded connected components. This
+ // algorithm was designed by AF.
+
+ // STAGE 1: Detect background
+
+ // Receives uniform background content, and is _not_ merged
+ // nor checked for intersection against other aCCList elements
+ ConnectedComponents aBackgroundComponent;
+
+ // Read the configuration value of minimal object area where transparency will be removed
+ double fReduceTransparencyMinArea = officecfg::Office::Common::VCL::ReduceTransparencyMinArea::get() / 100.0;
+ SAL_WARN_IF(fReduceTransparencyMinArea > 1.0, "vcl",
+ "Value of ReduceTransparencyMinArea config option is too high");
+ SAL_WARN_IF(fReduceTransparencyMinArea < 0.0, "vcl",
+ "Value of ReduceTransparencyMinArea config option is too low");
+ fReduceTransparencyMinArea = std::clamp(fReduceTransparencyMinArea, 0.0, 1.0);
+
+ // create an OutputDevice to record mapmode changes and the like
+ ScopedVclPtrInstance< VirtualDevice > aMapModeVDev;
+ aMapModeVDev->mnDPIX = mnDPIX;
+ aMapModeVDev->mnDPIY = mnDPIY;
+ aMapModeVDev->EnableOutput(false);
+
+ int nLastBgAction, nActionNum;
+
+ // weed out page-filling background objects (if they are
+ // uniformly coloured). Keeping them outside the other
+ // connected components often prevents whole-page bitmap
+ // generation.
+ bool bStillBackground=true; // true until first non-bg action
+ nActionNum=0; nLastBgAction=-1;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction();
+ if( rBackground != COL_TRANSPARENT )
+ {
+ aBackgroundComponent.aBgColor = rBackground;
+ aBackgroundComponent.aBounds = GetBackgroundComponentBounds();
+ }
+ while( pCurrAct && bStillBackground )
+ {
+ switch( pCurrAct->GetType() )
+ {
+ case MetaActionType::RECT:
+ {
+ if( !checkRect(
+ aBackgroundComponent.aBounds,
+ aBackgroundComponent.aBgColor,
+ static_cast<const MetaRectAction*>(pCurrAct)->GetRect(),
+ *aMapModeVDev) )
+ bStillBackground=false; // incomplete occlusion of background
+ else
+ nLastBgAction=nActionNum; // this _is_ background
+ break;
+ }
+ case MetaActionType::POLYGON:
+ {
+ const tools::Polygon aPoly(
+ static_cast<const MetaPolygonAction*>(pCurrAct)->GetPolygon());
+ if( !basegfx::utils::isRectangle(
+ aPoly.getB2DPolygon()) ||
+ !checkRect(
+ aBackgroundComponent.aBounds,
+ aBackgroundComponent.aBgColor,
+ aPoly.GetBoundRect(),
+ *aMapModeVDev) )
+ bStillBackground=false; // incomplete occlusion of background
+ else
+ nLastBgAction=nActionNum; // this _is_ background
+ break;
+ }
+ case MetaActionType::POLYPOLYGON:
+ {
+ const tools::PolyPolygon aPoly(
+ static_cast<const MetaPolyPolygonAction*>(pCurrAct)->GetPolyPolygon());
+ if( aPoly.Count() != 1 ||
+ !basegfx::utils::isRectangle(
+ aPoly[0].getB2DPolygon()) ||
+ !checkRect(
+ aBackgroundComponent.aBounds,
+ aBackgroundComponent.aBgColor,
+ aPoly.GetBoundRect(),
+ *aMapModeVDev) )
+ bStillBackground=false; // incomplete occlusion of background
+ else
+ nLastBgAction=nActionNum; // this _is_ background
+ break;
+ }
+ case MetaActionType::WALLPAPER:
+ {
+ if( !checkRect(
+ aBackgroundComponent.aBounds,
+ aBackgroundComponent.aBgColor,
+ static_cast<const MetaWallpaperAction*>(pCurrAct)->GetRect(),
+ *aMapModeVDev) )
+ bStillBackground=false; // incomplete occlusion of background
+ else
+ nLastBgAction=nActionNum; // this _is_ background
+ break;
+ }
+ default:
+ {
+ if( ImplIsNotTransparent( *pCurrAct,
+ *aMapModeVDev ) )
+ bStillBackground=false; // non-transparent action, possibly
+ // not uniform
+ else
+ // extend current bounds (next uniform action
+ // needs to fully cover this area)
+ aBackgroundComponent.aBounds.Union(
+ ImplCalcActionBounds(*pCurrAct, *aMapModeVDev) );
+ break;
+ }
+ }
+
+ // execute action to get correct MapModes etc.
+ pCurrAct->Execute( aMapModeVDev.get() );
+
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction();
+ ++nActionNum;
+ }
+
+ aMapModeVDev->ClearStack(); // clean up aMapModeVDev
+
+ // fast-forward until one after the last background action
+ // (need to reconstruct map mode vdev state)
+ nActionNum=0;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction();
+ while( pCurrAct && nActionNum<=nLastBgAction )
+ {
+ // up to and including last ink-generating background
+ // action go to background component
+ aBackgroundComponent.aComponentList.emplace_back(
+ pCurrAct, nActionNum );
+
+ // execute action to get correct MapModes etc.
+ pCurrAct->Execute( aMapModeVDev.get() );
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction();
+ ++nActionNum;
+ }
+
+ // STAGE 2: Generate connected components list
+
+ ::std::vector<ConnectedComponents> aCCList; // contains distinct sets of connected components as elements.
+
+ // iterate over all actions (start where background action
+ // search left off)
+ for( ;
+ pCurrAct;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum )
+ {
+ // execute action to get correct MapModes etc.
+ pCurrAct->Execute( aMapModeVDev.get() );
+
+ // cache bounds of current action
+ const tools::Rectangle aBBCurrAct( ImplCalcActionBounds(*pCurrAct, *aMapModeVDev) );
+
+ // accumulate collected bounds here, initialize with current action
+ tools::Rectangle aTotalBounds( aBBCurrAct ); // thus, aTotalComponents.aBounds is empty
+ // for non-output-generating actions
+ bool bTreatSpecial( false );
+ ConnectedComponents aTotalComponents;
+
+ // STAGE 2.1: Search for intersecting cc entries
+
+ // if aBBCurrAct is empty, it will intersect with no
+ // aCCList member. Thus, we can save the check.
+ // Furthermore, this ensures that non-output-generating
+ // actions get their own aCCList entry, which is necessary
+ // when copying them to the output metafile (see stage 4
+ // below).
+
+ // #107169# Wholly transparent objects need
+ // not be considered for connected components,
+ // too. Just put each of them into a separate
+ // component.
+ aTotalComponents.bIsFullyTransparent = !ImplIsNotTransparent(*pCurrAct, *aMapModeVDev);
+
+ if( !aBBCurrAct.IsEmpty() &&
+ !aTotalComponents.bIsFullyTransparent )
+ {
+ if( !aBackgroundComponent.aComponentList.empty() &&
+ !aBackgroundComponent.aBounds.IsInside(aTotalBounds) )
+ {
+ // it seems the background is not large enough. to
+ // be on the safe side, combine with this component.
+ aTotalBounds.Union( aBackgroundComponent.aBounds );
+
+ // extract all aCurr actions to aTotalComponents
+ aTotalComponents.aComponentList.splice( aTotalComponents.aComponentList.end(),
+ aBackgroundComponent.aComponentList );
+
+ if( aBackgroundComponent.bIsSpecial )
+ bTreatSpecial = true;
+ }
+
+ bool bSomeComponentsChanged;
+
+ // now, this is unfortunate: since changing anyone of
+ // the aCCList elements (e.g. by merging or addition
+ // of an action) might generate new intersection with
+ // other aCCList elements, have to repeat the whole
+ // element scanning, until nothing changes anymore.
+ // Thus, this loop here makes us O(n^3) in the worst
+ // case.
+ do
+ {
+ // only loop here if 'intersects' branch below was hit
+ bSomeComponentsChanged = false;
+
+ // iterate over all current members of aCCList
+ for( auto aCurrCC=aCCList.begin(); aCurrCC != aCCList.end(); )
+ {
+ // first check if current element's bounds are
+ // empty. This ensures that empty actions are not
+ // merged into one component, as a matter of fact,
+ // they have no position.
+
+ // #107169# Wholly transparent objects need
+ // not be considered for connected components,
+ // too. Just put each of them into a separate
+ // component.
+ if( !aCurrCC->aBounds.IsEmpty() &&
+ !aCurrCC->bIsFullyTransparent &&
+ aCurrCC->aBounds.IsOver( aTotalBounds ) )
+ {
+ // union the intersecting aCCList element into aTotalComponents
+
+ // calc union bounding box
+ aTotalBounds.Union( aCurrCC->aBounds );
+
+ // extract all aCurr actions to aTotalComponents
+ aTotalComponents.aComponentList.splice( aTotalComponents.aComponentList.end(),
+ aCurrCC->aComponentList );
+
+ if( aCurrCC->bIsSpecial )
+ bTreatSpecial = true;
+
+ // remove and delete aCurrCC element from list (we've now merged its content)
+ aCurrCC = aCCList.erase( aCurrCC );
+
+ // at least one component changed, need to rescan everything
+ bSomeComponentsChanged = true;
+ }
+ else
+ {
+ ++aCurrCC;
+ }
+ }
+ }
+ while( bSomeComponentsChanged );
+ }
+
+ // STAGE 2.2: Determine special state for cc element
+
+ // now test whether the whole connected component must be
+ // treated specially (i.e. rendered as a bitmap): if the
+ // added action is the very first action, or all actions
+ // before it are completely transparent, the connected
+ // component need not be treated specially, not even if
+ // the added action contains transparency. This is because
+ // painting of transparent objects on _white background_
+ // works without alpha compositing (you just calculate the
+ // color). Note that for the test "all objects before me
+ // are transparent" no sorting is necessary, since the
+ // added metaaction pCurrAct is always in the order the
+ // metafile is painted. Generally, the order of the
+ // metaactions in the ConnectedComponents are not
+ // guaranteed to be the same as in the metafile.
+ if( bTreatSpecial )
+ {
+ // prev component(s) special -> this one, too
+ aTotalComponents.bIsSpecial = true;
+ }
+ else if(!pCurrAct->IsTransparent())
+ {
+ // added action and none of prev components special ->
+ // this one normal, too
+ aTotalComponents.bIsSpecial = false;
+ }
+ else
+ {
+ // added action is special and none of prev components
+ // special -> do the detailed tests
+
+ // can the action handle transparency correctly
+ // (i.e. when painted on white background, does the
+ // action still look correct)?
+ if( !DoesActionHandleTransparency( *pCurrAct ) )
+ {
+ // no, action cannot handle its transparency on
+ // a printer device, render to bitmap
+ aTotalComponents.bIsSpecial = true;
+ }
+ else
+ {
+ // yes, action can handle its transparency, so
+ // check whether we're on white background
+ if( aTotalComponents.aComponentList.empty() )
+ {
+ // nothing between pCurrAct and page
+ // background -> don't be special
+ aTotalComponents.bIsSpecial = false;
+ }
+ else
+ {
+ // #107169# Fixes above now ensure that _no_
+ // object in the list is fully transparent. Thus,
+ // if the component list is not empty above, we
+ // must assume that we have to treat this
+ // component special.
+
+ // there are non-transparent objects between
+ // pCurrAct and the empty sheet of paper -> be
+ // special, then
+ aTotalComponents.bIsSpecial = true;
+ }
+ }
+ }
+
+ // STAGE 2.3: Add newly generated CC list element
+
+ // set new bounds and add action to list
+ aTotalComponents.aBounds = aTotalBounds;
+ aTotalComponents.aComponentList.emplace_back(
+ pCurrAct, nActionNum );
+
+ // add aTotalComponents as a new entry to aCCList
+ aCCList.push_back( aTotalComponents );
+
+ SAL_WARN_IF( aTotalComponents.aComponentList.empty(), "vcl",
+ "Printer::GetPreparedMetaFile empty component" );
+ SAL_WARN_IF( aTotalComponents.aBounds.IsEmpty() && (aTotalComponents.aComponentList.size() != 1), "vcl",
+ "Printer::GetPreparedMetaFile non-output generating actions must be solitary");
+ SAL_WARN_IF( aTotalComponents.bIsFullyTransparent && (aTotalComponents.aComponentList.size() != 1), "vcl",
+ "Printer::GetPreparedMetaFile fully transparent actions must be solitary");
+ }
+
+ // well now, we've got the list of disjunct connected
+ // components. Now we've got to create a map, which contains
+ // the corresponding aCCList element for every
+ // metaaction. Later on, we always process the complete
+ // metafile for each bitmap to be generated, but switch on
+ // output only for actions contained in the then current
+ // aCCList element. This ensures correct mapmode and attribute
+ // settings for all cases.
+
+ // maps mtf actions to CC list entries
+ ::std::vector< const ConnectedComponents* > aCCList_MemberMap( rInMtf.GetActionSize() );
+
+ // iterate over all aCCList members and their contained metaactions
+ for (auto const& currentItem : aCCList)
+ {
+ for (auto const& currentAction : currentItem.aComponentList)
+ {
+ // set pointer to aCCList element for corresponding index
+ aCCList_MemberMap[ currentAction.second ] = &currentItem;
+ }
+ }
+
+ // STAGE 3.1: Output background mtf actions (if there are any)
+
+ for (auto & component : aBackgroundComponent.aComponentList)
+ {
+ // simply add this action (above, we inserted the actions
+ // starting at index 0 up to and including nLastBgAction)
+ rOutMtf.AddAction( component.first );
+ }
+
+ // STAGE 3.2: Generate banded bitmaps for special regions
+
+ Point aPageOffset;
+ Size aTmpSize( GetOutputSizePixel() );
+ if( meOutDevType == OUTDEV_PDF )
+ {
+ auto pPdfWriter = static_cast<vcl::PDFWriterImpl*>(this);
+ aTmpSize = LogicToPixel(pPdfWriter->getCurPageSize(), MapMode(MapUnit::MapPoint));
+
+ // also add error code to PDFWriter
+ pPdfWriter->insertError(vcl::PDFWriter::Warning_Transparency_Converted);
+ }
+ else if( meOutDevType == OUTDEV_PRINTER )
+ {
+ Printer* pThis = dynamic_cast<Printer*>(this);
+ assert(pThis);
+ aPageOffset = pThis->GetPageOffsetPixel();
+ aPageOffset = Point( 0, 0 ) - aPageOffset;
+ aTmpSize = pThis->GetPaperSizePixel();
+ }
+ const tools::Rectangle aOutputRect( aPageOffset, aTmpSize );
+ bool bTiling = dynamic_cast<Printer*>(this) != nullptr;
+
+ // iterate over all aCCList members and generate bitmaps for the special ones
+ for (auto & currentItem : aCCList)
+ {
+ if( currentItem.bIsSpecial )
+ {
+ tools::Rectangle aBoundRect( currentItem.aBounds );
+ aBoundRect.Intersection( aOutputRect );
+
+ const double fBmpArea( static_cast<double>(aBoundRect.GetWidth()) * aBoundRect.GetHeight() );
+ const double fOutArea( static_cast<double>(aOutputRect.GetWidth()) * aOutputRect.GetHeight() );
+
+ // check if output doesn't exceed given size
+ if( bReduceTransparency && bTransparencyAutoMode && ( fBmpArea > ( fReduceTransparencyMinArea * fOutArea ) ) )
+ {
+ // output normally. Therefore, we simply clear the
+ // special attribute, as everything non-special is
+ // copied to rOutMtf further below.
+ currentItem.bIsSpecial = false;
+ }
+ else
+ {
+ // create new bitmap action first
+ if( aBoundRect.GetWidth() && aBoundRect.GetHeight() )
+ {
+ Point aDstPtPix( aBoundRect.TopLeft() );
+ Size aDstSzPix;
+
+ ScopedVclPtrInstance<VirtualDevice> aMapVDev; // here, we record only mapmode information
+ aMapVDev->EnableOutput(false);
+
+ ScopedVclPtrInstance<VirtualDevice> aPaintVDev; // into this one, we render.
+ aPaintVDev->SetBackground( aBackgroundComponent.aBgColor );
+
+ rOutMtf.AddAction( new MetaPushAction( PushFlags::MAPMODE ) );
+ rOutMtf.AddAction( new MetaMapModeAction() );
+
+ aPaintVDev->SetDrawMode( GetDrawMode() );
+
+ while( aDstPtPix.Y() <= aBoundRect.Bottom() )
+ {
+ aDstPtPix.setX( aBoundRect.Left() );
+ aDstSzPix = bTiling ? Size( MAX_TILE_WIDTH, MAX_TILE_HEIGHT ) : aBoundRect.GetSize();
+
+ if( ( aDstPtPix.Y() + aDstSzPix.Height() - 1 ) > aBoundRect.Bottom() )
+ aDstSzPix.setHeight( aBoundRect.Bottom() - aDstPtPix.Y() + 1 );
+
+ while( aDstPtPix.X() <= aBoundRect.Right() )
+ {
+ if( ( aDstPtPix.X() + aDstSzPix.Width() - 1 ) > aBoundRect.Right() )
+ aDstSzPix.setWidth( aBoundRect.Right() - aDstPtPix.X() + 1 );
+
+ if( !tools::Rectangle( aDstPtPix, aDstSzPix ).Intersection( aBoundRect ).IsEmpty() &&
+ aPaintVDev->SetOutputSizePixel( aDstSzPix ) )
+ {
+ aPaintVDev->Push();
+ aMapVDev->Push();
+
+ aMapVDev->mnDPIX = aPaintVDev->mnDPIX = mnDPIX;
+ aMapVDev->mnDPIY = aPaintVDev->mnDPIY = mnDPIY;
+
+ aPaintVDev->EnableOutput(false);
+
+ // iterate over all actions
+ for( pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(), nActionNum=0;
+ pCurrAct;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum )
+ {
+ // enable output only for
+ // actions that are members of
+ // the current aCCList element
+ // (currentItem)
+ if( aCCList_MemberMap[nActionNum] == &currentItem )
+ aPaintVDev->EnableOutput();
+
+ // but process every action
+ const MetaActionType nType( pCurrAct->GetType() );
+
+ if( MetaActionType::MAPMODE == nType )
+ {
+ pCurrAct->Execute( aMapVDev.get() );
+
+ MapMode aMtfMap( aMapVDev->GetMapMode() );
+ const Point aNewOrg( aMapVDev->PixelToLogic( aDstPtPix ) );
+
+ aMtfMap.SetOrigin( Point( -aNewOrg.X(), -aNewOrg.Y() ) );
+ aPaintVDev->SetMapMode( aMtfMap );
+ }
+ else if( ( MetaActionType::PUSH == nType ) || MetaActionType::POP == nType )
+ {
+ pCurrAct->Execute( aMapVDev.get() );
+ pCurrAct->Execute( aPaintVDev.get() );
+ }
+ else if( MetaActionType::GRADIENT == nType )
+ {
+ MetaGradientAction* pGradientAction = static_cast<MetaGradientAction*>(pCurrAct);
+ Printer* pPrinter = dynamic_cast< Printer* >(this);
+ if( pPrinter )
+ pPrinter->DrawGradientEx( aPaintVDev.get(), pGradientAction->GetRect(), pGradientAction->GetGradient() );
+ else
+ DrawGradient( pGradientAction->GetRect(), pGradientAction->GetGradient() );
+ }
+ else
+ {
+ pCurrAct->Execute( aPaintVDev.get() );
+ }
+
+ Application::Reschedule( true );
+ }
+
+ const bool bOldMap = mbMap;
+ mbMap = aPaintVDev->mbMap = false;
+
+ Bitmap aBandBmp( aPaintVDev->GetBitmap( Point(), aDstSzPix ) );
+
+ // scale down bitmap, if requested
+ if( bDownsampleBitmaps )
+ {
+ aBandBmp = GetDownsampledBitmap( aDstSzPix,
+ Point(), aBandBmp.GetSizePixel(),
+ aBandBmp, nMaxBmpDPIX, nMaxBmpDPIY );
+ }
+
+ rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_BEGIN" ) );
+ rOutMtf.AddAction( new MetaBmpScaleAction( aDstPtPix, aDstSzPix, aBandBmp ) );
+ rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_END" ) );
+
+ aPaintVDev->mbMap = true;
+ mbMap = bOldMap;
+ aMapVDev->Pop();
+ aPaintVDev->Pop();
+ }
+
+ // overlapping bands to avoid missing lines (e.g. PostScript)
+ aDstPtPix.AdjustX(aDstSzPix.Width() );
+ }
+
+ // overlapping bands to avoid missing lines (e.g. PostScript)
+ aDstPtPix.AdjustY(aDstSzPix.Height() );
+ }
+
+ rOutMtf.AddAction( new MetaPopAction() );
+ }
+ }
+ }
+ }
+
+ aMapModeVDev->ClearStack(); // clean up aMapModeVDev
+
+ // STAGE 4: Copy actions to output metafile
+
+ // iterate over all actions and duplicate the ones not in a
+ // special aCCList member into rOutMtf
+ for( pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(), nActionNum=0;
+ pCurrAct;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum )
+ {
+ const ConnectedComponents* pCurrAssociatedComponent = aCCList_MemberMap[nActionNum];
+
+ // NOTE: This relies on the fact that map-mode or draw
+ // mode changing actions are solitary aCCList elements and
+ // have empty bounding boxes, see comment on stage 2.1
+ // above
+ if( pCurrAssociatedComponent &&
+ (pCurrAssociatedComponent->aBounds.IsEmpty() ||
+ !pCurrAssociatedComponent->bIsSpecial) )
+ {
+ // #107169# Treat transparent bitmaps special, if they
+ // are the first (or sole) action in their bounds
+ // list. Note that we previously ensured that no
+ // fully-transparent objects are before us here.
+ if( DoesActionHandleTransparency( *pCurrAct ) &&
+ pCurrAssociatedComponent->aComponentList.begin()->first == pCurrAct )
+ {
+ // convert actions, where masked-out parts are of
+ // given background color
+ ImplConvertTransparentAction(rOutMtf,
+ *pCurrAct,
+ *aMapModeVDev,
+ aBackgroundComponent.aBgColor);
+ }
+ else
+ {
+ // simply add this action
+ rOutMtf.AddAction( pCurrAct );
+ }
+
+ pCurrAct->Execute(aMapModeVDev.get());
+ }
+ }
+
+ rOutMtf.SetPrefMapMode( rInMtf.GetPrefMapMode() );
+ rOutMtf.SetPrefSize( rInMtf.GetPrefSize() );
+
+#if OSL_DEBUG_LEVEL > 1
+ // iterate over all aCCList members and generate rectangles for the bounding boxes
+ rOutMtf.AddAction( new MetaFillColorAction( COL_WHITE, false ) );
+ for(auto const& aCurr:aCCList)
+ {
+ if( aCurr.bIsSpecial )
+ rOutMtf.AddAction( new MetaLineColorAction( COL_RED, true) );
+ else
+ rOutMtf.AddAction( new MetaLineColorAction( COL_BLUE, true) );
+
+ rOutMtf.AddAction( new MetaRectAction( aMapModeVDev->PixelToLogic( aCurr.aBounds ) ) );
+ }
+#endif
+ }
+ return bTransparent;
+}
+
+void Printer::DrawGradientEx( OutputDevice* pOut, const tools::Rectangle& rRect, const Gradient& rGradient )
+{
+ const PrinterOptions& rPrinterOptions = GetPrinterOptions();
+
+ if( rPrinterOptions.IsReduceGradients() )
+ {
+ if( PrinterGradientMode::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 long nR = ( ( static_cast<long>(rStartColor.GetRed()) * rGradient.GetStartIntensity() ) / 100 +
+ ( static_cast<long>(rEndColor.GetRed()) * rGradient.GetEndIntensity() ) / 100 ) >> 1;
+ const long nG = ( ( static_cast<long>(rStartColor.GetGreen()) * rGradient.GetStartIntensity() ) / 100 +
+ ( static_cast<long>(rEndColor.GetGreen()) * rGradient.GetEndIntensity() ) / 100 ) >> 1;
+ const long nB = ( ( static_cast<long>(rStartColor.GetBlue()) * rGradient.GetStartIntensity() ) / 100 +
+ ( static_cast<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( PushFlags::LINECOLOR | 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 000000000..cbf486713
--- /dev/null
+++ b/vcl/source/gdi/print3.cxx
@@ -0,0 +1,2118 @@
+/* -*- 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/weld.hxx>
+#include <vcl/print.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/metaact.hxx>
+#include <configsettings.hxx>
+#include <tools/urlobj.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <sal/types.h>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+#include <printdlg.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+#include <salprn.hxx>
+#include <strings.hrc>
+
+#include <com/sun/star/ui/dialogs/FilePicker.hpp>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <com/sun/star/view/DuplexMode.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/awt/Size.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 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 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 ),
+ mbPrinterModified( false ),
+ meJobState( css::view::PrintableState_JOB_STARTED ),
+ mnDefaultPaperBin( -1 ),
+ mnFixedPaperBin( -1 )
+ {}
+
+ ~ImplPrinterControllerData()
+ {
+ if (mxProgress)
+ {
+ mxProgress->response(RET_CANCEL);
+ mxProgress.reset();
+ }
+ }
+
+ const Size& getRealPaperSize( const Size& i_rPageSize, bool bNoNUP ) const
+ {
+ if ( mbPapersizeFromUser )
+ return maUserPageSize;
+ if( mbPapersizeFromSetup )
+ return maDefaultPageSize;
+ if( maMultiPage.nRows * maMultiPage.nColumns > 1 && ! bNoNUP )
+ return maMultiPage.aPaperSize;
+ return i_rPageSize;
+ }
+ 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&)
+ {
+ SAL_WARN( "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(const std::shared_ptr<PrinterController>& i_xController,
+ const JobSetup& i_rInitSetup)
+ : mxController( 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::makeAny( 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( "-" );
+ aBuf.append( nPages );
+ }
+ xController->setValue("PageRange", css::uno::makeAny(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::makeAny( aFile ) );
+ }
+ else if (aDlg.isSingleJobs())
+ {
+ xController->setValue( "PrintCollateAsSingleJobs",
+ css::uno::makeAny( true ) );
+ }
+ }
+ catch (const std::bad_alloc&)
+ {
+ }
+ }
+
+ 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 = false;
+ css::beans::PropertyValue* pSingleValue = i_xController->getValue("PrintCollateAsSingleJobs");
+ if( pSingleValue )
+ {
+ pSingleValue->Value >>= bSinglePrintJobs;
+ }
+
+ 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::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::makeAny( 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->mxPrinter->Pop();
+ mpImplData->mnFixedPaperBin = -1;
+}
+
+void PrinterController::resetPrinterOptions( bool i_bFileOutput )
+{
+ PrinterOptions 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 )
+ {
+ 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 && nBin < static_cast<sal_Int32>(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 )
+ {
+ // 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();
+ long nDX = (aPaperSize.Width() - aPageSize.aSize.Width()) / 2;
+ 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
+ long nAdvX = aMPArea.Width() / rMPS.nColumns;
+ 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() )
+ {
+ 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"
+ long nOffX = (aSubPageSize.Width() - long(double(aPageSize.aSize.Width()) * fScale)) / 2;
+ long nOffY = (aSubPageSize.Height() - long(double(aPageSize.aSize.Height()) * fScale)) / 2;
+ long nX = rMPS.nLeftMargin + nOffX + nAdvX * nCellX;
+ 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( long(double(aPageSize.aSize.Width())*fScale),
+ 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 PrinterOptions& 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( PrinterBitmapMode::Optimal == rPrinterOptions.GetReducedBitmapMode() )
+ {
+ nMaxBmpDPIX = std::min( sal_Int32(OPTIMAL_BMP_RESOLUTION), nMaxBmpDPIX );
+ nMaxBmpDPIY = std::min( sal_Int32(OPTIMAL_BMP_RESOLUTION), nMaxBmpDPIY );
+ }
+ else if( PrinterBitmapMode::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() && ( PrinterTransparencyMode::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() == PrinterTransparencyMode::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.get() );
+ 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 = !i_bPapersizeFromSetup;
+}
+
+bool PrinterController::getPapersizeFromSetup() const
+{
+ return mpImplData->mbPapersizeFromSetup;
+}
+
+Size& PrinterController::getPaperSizeSetup() const
+{
+ return mpImplData->maDefaultPageSize;
+}
+
+void PrinterController::setPaperSizeFromUser( Size i_aUserSize )
+{
+ mpImplData->mbPapersizeFromUser = true;
+ mpImplData->mbPapersizeFromSetup = false;
+ mpImplData->mxPrinter->SetPrinterSettingsPreferred( false );
+
+ mpImplData->maUserPageSize = i_aUserSize;
+}
+
+Size& PrinterController::getPaperSizeFromUser() const
+{
+ return mpImplData->maUserPageSize;
+}
+
+bool PrinterController::isPaperSizeFromUser() const
+{
+ return mpImplData->mbPapersizeFromUser;
+}
+
+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 );
+ std::copy(i_rMergeList.begin(), i_rMergeList.end(), aResult.begin());
+ int nCur = i_rMergeList.getLength();
+ for(const css::beans::PropertyValue & rPropVal : mpImplData->maUIProperties)
+ {
+ if( aMergeSet.find( rPropVal.Name ) == aMergeSet.end() )
+ aResult[nCur++] = rPropVal;
+ }
+ // append IsFirstPage
+ if( aMergeSet.find( "IsFirstPage" ) == aMergeSet.end() )
+ {
+ css::beans::PropertyValue aVal;
+ aVal.Name = "IsFirstPage";
+ aVal.Value <<= mpImplData->mbFirstPage;
+ aResult[nCur++] = aVal;
+ }
+ // append IsLastPage
+ if( aMergeSet.find( "IsLastPage" ) == aMergeSet.end() )
+ {
+ css::beans::PropertyValue aVal;
+ aVal.Name = "IsLastPage";
+ aVal.Value <<= mpImplData->mbLastPage;
+ aResult[nCur++] = aVal;
+ }
+ // append IsPrinter
+ if( aMergeSet.find( "IsPrinter" ) == aMergeSet.end() )
+ {
+ css::beans::PropertyValue aVal;
+ aVal.Name = "IsPrinter";
+ aVal.Value <<= true;
+ aResult[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::makeAny( sal_Int32( it->second.mnDependsOnEntry ) ) );
+ }
+ }
+ else if( pVal->Value >>= bDepVal )
+ {
+ setValue( aDependency, css::uno::makeAny( 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 );
+
+ // 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 );
+ css::beans::PropertyValue aVal;
+ aVal.Name = "ExtraPrintUIOptions";
+ aVal.Value <<= comphelper::containerToSequence(m_aUIProperties);
+ io_rProps[ nIndex ] = aVal;
+ }
+}
+
+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 );
+ sal_Int32 nUsed = 0;
+ if( !i_rTitle.isEmpty() )
+ {
+ aCtrl[nUsed ].Name = "Text";
+ aCtrl[nUsed++].Value <<= i_rTitle;
+ }
+ if( i_rHelpIds.hasElements() )
+ {
+ aCtrl[nUsed ].Name = "HelpId";
+ aCtrl[nUsed++].Value <<= i_rHelpIds;
+ }
+ aCtrl[nUsed ].Name = "ControlType";
+ aCtrl[nUsed++].Value <<= i_rType;
+ aCtrl[nUsed ].Name = "ID";
+ aCtrl[nUsed++].Value <<= i_rIDs;
+ if( i_pVal )
+ {
+ aCtrl[nUsed ].Name = "Property";
+ aCtrl[nUsed++].Value <<= *i_pVal;
+ }
+ if( !i_rControlOptions.maDependsOnName.isEmpty() )
+ {
+ aCtrl[nUsed ].Name = "DependsOnName";
+ aCtrl[nUsed++].Value <<= i_rControlOptions.maDependsOnName;
+ if( i_rControlOptions.mnDependsOnEntry != -1 )
+ {
+ aCtrl[nUsed ].Name = "DependsOnEntry";
+ aCtrl[nUsed++].Value <<= i_rControlOptions.mnDependsOnEntry;
+ }
+ if( i_rControlOptions.mbAttachToDependency )
+ {
+ aCtrl[nUsed ].Name = "AttachToDependency";
+ aCtrl[nUsed++].Value <<= i_rControlOptions.mbAttachToDependency;
+ }
+ }
+ if( !i_rControlOptions.maGroupHint.isEmpty() )
+ {
+ aCtrl[nUsed ].Name = "GroupingHint";
+ aCtrl[nUsed++].Value <<= i_rControlOptions.maGroupHint;
+ }
+ if( i_rControlOptions.mbInternalOnly )
+ {
+ aCtrl[nUsed ].Name = "InternalUIOnly";
+ aCtrl[nUsed++].Value <<= true;
+ }
+ if( ! i_rControlOptions.mbEnabled )
+ {
+ aCtrl[nUsed ].Name = "Enabled";
+ aCtrl[nUsed++].Value <<= false;
+ }
+
+ sal_Int32 nAddProps = i_rControlOptions.maAddProps.size();
+ for( sal_Int32 i = 0; i < nAddProps; i++ )
+ aCtrl[ nUsed++ ] = i_rControlOptions.maAddProps[i];
+
+ SAL_WARN_IF( nUsed != nElements, "vcl.gdi", "nUsed != nElements, probable heap corruption" );
+
+ return css::uno::makeAny( 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 000000000..c47721107
--- /dev/null
+++ b/vcl/source/gdi/regband.cxx
@@ -0,0 +1,885 @@
+/* -*- 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( long nTop, 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)
+ {
+ // 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( long nX, 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( 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( long nXLeft, 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( long nXLeft, 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( long nXLeft, 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( long nXLeft, 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 )
+ {
+ long nOldLeft( pSep->mnXLeft );
+ 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::IsInside( long nX )
+{
+ ImplRegionBandSep* pSep = mpFirstSep;
+ while ( pSep )
+ {
+ if ( (pSep->mnXLeft <= nX) && (pSep->mnXRight >= nX) )
+ return true;
+
+ pSep = pSep->mpNextSep;
+ }
+
+ return false;
+}
+
+long ImplRegionBand::GetXLeftBoundary() const
+{
+ assert(mpFirstSep && "ImplRegionBand::XLeftBoundary -> no separation in band!");
+
+ return mpFirstSep->mnXLeft;
+}
+
+long ImplRegionBand::GetXRightBoundary() const
+{
+ SAL_WARN_IF( mpFirstSep == nullptr, "vcl", "ImplRegionBand::XRightBoundary -> no separation in band!" );
+
+ // 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
+ long nOwnXLeft = pOwnRectBandSep->mnXLeft;
+ long nSecondXLeft = pSecondRectBandSep->mnXLeft;
+ if ( nOwnXLeft != nSecondXLeft )
+ return false;
+
+ long nOwnXRight = pOwnRectBandSep->mnXRight;
+ 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 000000000..4ebabaa5b
--- /dev/null
+++ b/vcl/source/gdi/region.cxx
@@ -0,0 +1,1778 @@
+/* -*- 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>
+
+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>() );
+ 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 long nTop (::std::min(aStart.Y(), aEnd.Y()));
+ const 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)
+ {
+ 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)
+: mpB2DPolyPolygon(),
+ mpPolyPolygon(),
+ mpRegionBand(),
+ mbIsNull(bIsNull)
+{
+}
+
+Region::Region(const tools::Rectangle& rRect)
+: mpB2DPolyPolygon(),
+ mpPolyPolygon(),
+ mpRegionBand(),
+ mbIsNull(false)
+{
+ mpRegionBand.reset(rRect.IsEmpty() ? nullptr : new RegionBand(rRect));
+}
+
+Region::Region(const tools::Polygon& rPolygon)
+: mpB2DPolyPolygon(),
+ mpPolyPolygon(),
+ mpRegionBand(),
+ mbIsNull(false)
+{
+
+ if(rPolygon.GetSize())
+ {
+ ImplCreatePolyPolyRegion(rPolygon);
+ }
+}
+
+Region::Region(const tools::PolyPolygon& rPolyPoly)
+: mpB2DPolyPolygon(),
+ mpPolyPolygon(),
+ mpRegionBand(),
+ mbIsNull(false)
+{
+
+ if(rPolyPoly.Count())
+ {
+ ImplCreatePolyPolyRegion(rPolyPoly);
+ }
+}
+
+Region::Region(const basegfx::B2DPolyPolygon& rPolyPoly)
+: mpB2DPolyPolygon(),
+ mpPolyPolygon(),
+ mpRegionBand(),
+ 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)
+ {
+ // polypolygon empty? -> empty region
+ const tools::Rectangle aRect(rPolyPoly.GetBoundRect());
+
+ if(!aRect.IsEmpty())
+ {
+ // width OR height == 1 ? => Rectangular region
+ if((1 == aRect.GetWidth()) || (1 == aRect.GetHeight()) || rPolyPoly.IsRect())
+ {
+ mpRegionBand = std::make_shared<RegionBand>(aRect);
+ }
+ else
+ {
+ mpPolyPolygon = std::make_shared<tools::PolyPolygon>(rPolyPoly);
+ }
+
+ mbIsNull = false;
+ }
+ }
+}
+
+void vcl::Region::ImplCreatePolyPolyRegion( const basegfx::B2DPolyPolygon& rPolyPoly )
+{
+ if(rPolyPoly.count() && !rPolyPoly.getB2DRange().isEmpty())
+ {
+ mpB2DPolyPolygon = std::make_shared<basegfx::B2DPolyPolygon>(rPolyPoly);
+ mbIsNull = false;
+ }
+}
+
+void vcl::Region::Move( long nHorzMove, 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));
+ mpB2DPolyPolygon.reset(aPoly.count() ? new basegfx::B2DPolyPolygon(aPoly) : nullptr);
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+ else if(getPolyPolygon())
+ {
+ tools::PolyPolygon aPoly(*getPolyPolygon());
+
+ aPoly.Move(nHorzMove, nVertMove);
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset(aPoly.Count() ? new tools::PolyPolygon(aPoly) : nullptr);
+ mpRegionBand.reset();
+ }
+ else if(getRegionBand())
+ {
+ RegionBand* pNew = new RegionBand(*getRegionBand());
+
+ pNew->Move(nHorzMove, nVertMove);
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset(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));
+ mpB2DPolyPolygon.reset(aPoly.count() ? new basegfx::B2DPolyPolygon(aPoly) : nullptr);
+ mpPolyPolygon.reset();
+ mpRegionBand.reset();
+ }
+ else if(getPolyPolygon())
+ {
+ tools::PolyPolygon aPoly(*getPolyPolygon());
+
+ aPoly.Scale(fScaleX, fScaleY);
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset(aPoly.Count() ? new tools::PolyPolygon(aPoly) : nullptr);
+ mpRegionBand.reset();
+ }
+ else if(getRegionBand())
+ {
+ RegionBand* pNew = new RegionBand(*getRegionBand());
+
+ pNew->Scale(fScaleX, fScaleY);
+ mpB2DPolyPolygon.reset();
+ mpPolyPolygon.reset();
+ mpRegionBand.reset(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 long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const long nRight(std::max(rRect.Left(), rRect.Right()));
+ const 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));
+
+ mpB2DPolyPolygon.reset(aPoly.count() ? new basegfx::B2DPolyPolygon(aPoly) : nullptr);
+ 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();
+ mpPolyPolygon.reset(aPoly.Count() ? new tools::PolyPolygon(aPoly) : nullptr);
+ 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 long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const long nRight(std::max(rRect.Left(), rRect.Right()));
+ const 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
+ const RegionBand* pCurrent = getRegionBand();
+
+ if(!pCurrent)
+ {
+ // empty? -> done!
+ return;
+ }
+
+ std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
+
+ // get justified rectangle
+ const long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const long nRight(std::max(rRect.Left(), rRect.Right()));
+ const 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();
+ }
+
+ mpRegionBand = std::move(pNew);
+}
+
+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 long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const long nRight(std::max(rRect.Left(), rRect.Right()));
+ const 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;
+ }
+
+ const basegfx::B2DPolyPolygon aClip(
+ basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ aOtherPolyPoly,
+ aThisPolyPoly,
+ true,
+ false));
+ *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 = std::make_shared<tools::PolyPolygon>(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 = std::make_shared<tools::PolyPolygon>(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 = std::make_shared<basegfx::B2DPolyPolygon>(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 = std::make_shared<basegfx::B2DPolyPolygon>(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::IsInside( 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()->IsInside( rPoint );
+ //}
+
+ // ensure RegionBand existence
+ const RegionBand* pRegionBand = GetAsRegionBand();
+
+ if(pRegionBand)
+ {
+ return pRegionBand->IsInside(rPoint);
+ }
+
+ return false;
+}
+
+bool vcl::Region::IsOver( 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();
+ mpRegionBand.reset(rRect.IsEmpty() ? nullptr : new RegionBand(rRect));
+ 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)
+{
+ VersionCompat aCompat(rIStrm, StreamMode::READ);
+ 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 };
+ RegionType meStreamedType = static_cast<RegionType>(nTmp16);
+
+ switch(meStreamedType)
+ {
+ case REGION_NULL:
+ {
+ rRegion.SetNull();
+ break;
+ }
+
+ case REGION_EMPTY:
+ {
+ rRegion.SetEmpty();
+ break;
+ }
+
+ default:
+ {
+ RegionBand* pNewRegionBand = new RegionBand();
+ bool bSuccess = pNewRegionBand->load(rIStrm);
+ rRegion.mpRegionBand.reset(pNewRegionBand);
+
+ bool bHasPolyPolygon(false);
+ if (aCompat.GetVersion() >= 2)
+ {
+ rIStrm.ReadCharAsBool( bHasPolyPolygon );
+
+ if (bHasPolyPolygon)
+ {
+ tools::PolyPolygon* pNewPoly = new tools::PolyPolygon();
+ ReadPolyPolygon( rIStrm, *pNewPoly );
+ rRegion.mpPolyPolygon.reset(pNewPoly);
+ }
+ }
+
+ 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);
+ VersionCompat aCompat(rOStrm, StreamMode::WRITE, 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]) )
+ {
+ 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 )
+ {
+ 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 000000000..8478ebb1f
--- /dev/null
+++ b/vcl/source/gdi/regionband.cxx
@@ -0,0 +1,1358 @@
+/* -*- 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 <regionband.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 long nTop(std::min(rRect.Top(), rRect.Bottom()));
+ const long nBottom(std::max(rRect.Top(), rRect.Bottom()));
+ const long nLeft(std::min(rRect.Left(), rRect.Right()));
+ const 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
+ long nOwnXLeft = pOwnRectBandSep->mnXLeft;
+ long nSecondXLeft = pSecondRectBandSep->mnXLeft;
+
+ if ( nOwnXLeft != nSecondXLeft )
+ {
+ return false;
+ }
+
+ long nOwnYTop = pOwnRectBand->mnYTop;
+ long nSecondYTop = pSecondRectBand->mnYTop;
+
+ if ( nOwnYTop != nSecondYTop )
+ {
+ return false;
+ }
+
+ long nOwnXRight = pOwnRectBandSep->mnXRight;
+ long nSecondXRight = pSecondRectBandSep->mnXRight;
+
+ if ( nOwnXRight != nSecondXRight )
+ {
+ return false;
+ }
+
+ long nOwnYBottom = pOwnRectBand->mnYBottom;
+ 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 long nTop, const long nBottom)
+{
+ // Iterate over already existing bands and add missing bands atop the
+ // first and between two bands.
+ ImplRegionBand* pPreviousBand = nullptr;
+ ImplRegionBand* pBand = ImplGetFirstRegionBand();
+ 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(long nYTop, 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 ( 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, long nLineId)
+{
+ 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 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 long nDX = labs( rEndPt.X() - rStartPt.X() );
+ const long nDY = labs( rEndPt.Y() - rStartPt.Y() );
+ const long nStartX = rStartPt.X();
+ const long nStartY = rStartPt.Y();
+ const long nEndX = rEndPt.X();
+ const long nEndY = rEndPt.Y();
+ const long nXInc = ( nStartX < nEndX ) ? 1 : -1;
+ const long nYInc = ( nStartY < nEndY ) ? 1 : -1;
+
+ if ( nDX >= nDY )
+ {
+ const long nDYX = ( nDY - nDX ) * 2;
+ const long nDY2 = nDY << 1;
+ 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 long nDYX = ( nDX - nDY ) * 2;
+ const long nDY2 = nDX << 1;
+ 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, 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(long nHorzMove, long nVertMove)
+{
+ ImplRegionBand* pBand = mpFirstBand;
+
+ while(pBand)
+ {
+ // process the vertical move
+ if(nVertMove)
+ {
+ pBand->mnYTop = pBand->mnYTop + nVertMove;
+ pBand->mnYBottom = 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(long nTop, 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, 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(long nLeft, long nTop, long nRight, 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
+ 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(long nLeft, long nTop, long nRight, 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(long nLeft, long nTop, long nRight, 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
+ 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(long nLeft, long nTop, long nRight, 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
+ 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
+{
+
+ // get the boundaries of the first band
+ long nYTop(mpFirstBand->mnYTop);
+ long nYBottom(mpFirstBand->mnYBottom);
+ long nXLeft(mpFirstBand->GetXLeftBoundary());
+ 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::IsInside(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->IsInside(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 000000000..654ae90ed
--- /dev/null
+++ b/vcl/source/gdi/salgdiimpl.cxx
@@ -0,0 +1,26 @@
+/* -*- 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 000000000..e6581d45c
--- /dev/null
+++ b/vcl/source/gdi/salgdilayout.cxx
@@ -0,0 +1,897 @@
+/* -*- 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 <config_features.h>
+#include <sal/log.hxx>
+#if HAVE_FEATURE_OPENGL
+#include <opengl/gdiimpl.hxx>
+#include <opengl/zone.hxx>
+#include <desktop/exithelper.h>
+#ifdef _WIN32
+#include <svsys.h>
+#endif
+#endif
+#include <salgdi.hxx>
+#include <salframe.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <FileDefinitionWidgetDraw.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;
+ int parent_x = aGeom.nX - pParent->maGeometry.nX;
+ aGeom.nX = pParent->maGeometry.nX + pParent->maGeometry.nWidth - maGeometry.nWidth - parent_x;
+ return aGeom;
+ }
+ else
+ return maGeometry;
+}
+
+SalGraphics::SalGraphics()
+: m_nLayout( SalLayoutFlags::NONE ),
+ m_aLastMirror(),
+ m_aLastMirrorW(0),
+ m_bAntiAliasB2DDraw(false)
+{
+ // read global RTL settings
+ if( AllSettings::GetLayoutRTL() )
+ m_nLayout = SalLayoutFlags::BiDiRtl;
+}
+
+bool SalGraphics::initWidgetDrawBackends(bool bForce)
+{
+ 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)!
+}
+
+#if HAVE_FEATURE_OPENGL
+
+namespace
+{
+ void disableOpenGLAndTerminateForRestart()
+ {
+ OpenGLZone::hardDisable();
+#ifdef _WIN32
+ TerminateProcess(GetCurrentProcess(), EXITHELPER_NORMAL_RESTART);
+#endif
+ }
+}
+
+rtl::Reference<OpenGLContext> SalGraphics::GetOpenGLContext() const
+{
+ OpenGLSalGraphicsImpl *pImpl = dynamic_cast<OpenGLSalGraphicsImpl*>(GetImpl());
+ if (pImpl)
+ {
+ // If we notice that OpenGL is broken the first time being called, it is not too late to call
+ // disableOpenGLAndTerminateForRestart(). The first time this will be called is from displaying
+ // the splash screen, so if OpenGL is broken, it is "early enough" for us to be able to disable
+ // OpenGL and terminate bluntly with EXITHELPER_NORMAL_RESTART, thus causing the wrapper process
+ // to restart us, then without using OpenGL.
+ static bool bFirstCall = true;
+ rtl::Reference<OpenGLContext> xRet(pImpl->GetOpenGLContext());
+ if (!xRet.is() && bFirstCall)
+ disableOpenGLAndTerminateForRestart();
+ bFirstCall = false;
+ return xRet;
+ }
+ return nullptr;
+}
+
+#endif
+
+bool SalGraphics::drawTransformedBitmap(
+ const basegfx::B2DPoint& /* rNull */,
+ const basegfx::B2DPoint& /* rX */,
+ const basegfx::B2DPoint& /* rY */,
+ const SalBitmap& /* rSourceBitmap */,
+ const SalBitmap* /* pAlphaBitmap */)
+{
+ // here direct support for transformed bitmaps can be implemented
+ return false;
+}
+
+long SalGraphics::mirror2( long x, const OutputDevice *pOutDev ) const
+{
+ mirror(x, pOutDev);
+ return x;
+}
+
+inline long SalGraphics::GetDeviceWidth(const OutputDevice* pOutDev) const
+{
+ return (pOutDev && pOutDev->IsVirtual())
+ ? pOutDev->GetOutputWidthPixel() : GetGraphicsWidth();
+}
+
+void SalGraphics::mirror( long& x, const OutputDevice *pOutDev ) const
+{
+ const long w = GetDeviceWidth(pOutDev);
+ if( w )
+ {
+ if( pOutDev && pOutDev->ImplIsAntiparallel() )
+ {
+ OutputDevice *pOutDevRef = const_cast<OutputDevice*>(pOutDev);
+ // mirror this window back
+ if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ long devX = w-pOutDevRef->GetOutputWidthPixel()-pOutDevRef->GetOutOffXPixel(); // re-mirrored mnOutOffX
+ x = devX + (x - pOutDevRef->GetOutOffXPixel());
+ }
+ else
+ {
+ long devX = pOutDevRef->GetOutOffXPixel(); // re-mirrored mnOutOffX
+ x = pOutDevRef->GetOutputWidthPixel() - (x - devX) + pOutDevRef->GetOutOffXPixel() - 1;
+ }
+ }
+ else if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ x = w-1-x;
+ }
+}
+
+void SalGraphics::mirror( long& x, long nWidth, const OutputDevice *pOutDev, bool bBack ) const
+{
+ const long w = GetDeviceWidth(pOutDev);
+ if( w )
+ {
+ if( pOutDev && pOutDev->ImplIsAntiparallel() )
+ {
+ OutputDevice *pOutDevRef = const_cast<OutputDevice*>(pOutDev);
+ // mirror this window back
+ if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ long devX = w-pOutDevRef->GetOutputWidthPixel()-pOutDevRef->GetOutOffXPixel(); // re-mirrored mnOutOffX
+ if( bBack )
+ x = x - devX + pOutDevRef->GetOutOffXPixel();
+ else
+ x = devX + (x - pOutDevRef->GetOutOffXPixel());
+ }
+ else
+ {
+ long devX = pOutDevRef->GetOutOffXPixel(); // re-mirrored mnOutOffX
+ if( bBack )
+ x = devX + (pOutDevRef->GetOutputWidthPixel() + devX) - (x + nWidth);
+ else
+ x = pOutDevRef->GetOutputWidthPixel() - (x - devX) + pOutDevRef->GetOutOffXPixel() - nWidth;
+ }
+ }
+ else if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ x = w-nWidth-x;
+ }
+}
+
+bool SalGraphics::mirror( sal_uInt32 nPoints, const SalPoint *pPtAry, SalPoint *pPtAry2, const OutputDevice *pOutDev ) const
+{
+ const long w = GetDeviceWidth(pOutDev);
+ if( w )
+ {
+ sal_uInt32 i, j;
+
+ if( pOutDev && pOutDev->ImplIsAntiparallel() )
+ {
+ OutputDevice *pOutDevRef = const_cast<OutputDevice*>(pOutDev);
+ // mirror this window back
+ if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ long devX = w-pOutDevRef->GetOutputWidthPixel()-pOutDevRef->GetOutOffXPixel(); // re-mirrored mnOutOffX
+ for( i=0, j=nPoints-1; i<nPoints; i++,j-- )
+ {
+ pPtAry2[j].mnX = devX + (pPtAry[i].mnX - pOutDevRef->GetOutOffXPixel());
+ pPtAry2[j].mnY = pPtAry[i].mnY;
+ }
+ }
+ else
+ {
+ long devX = pOutDevRef->GetOutOffXPixel(); // re-mirrored mnOutOffX
+ for( i=0, j=nPoints-1; i<nPoints; i++,j-- )
+ {
+ pPtAry2[j].mnX = pOutDevRef->GetOutputWidthPixel() - (pPtAry[i].mnX - devX) + pOutDevRef->GetOutOffXPixel() - 1;
+ pPtAry2[j].mnY = pPtAry[i].mnY;
+ }
+ }
+ }
+ else if( m_nLayout & SalLayoutFlags::BiDiRtl )
+ {
+ for( i=0, j=nPoints-1; i<nPoints; i++,j-- )
+ {
+ pPtAry2[j].mnX = w-1-pPtAry[i].mnX;
+ pPtAry2[j].mnY = pPtAry[i].mnY;
+ }
+ }
+ return true;
+ }
+ else
+ return false;
+}
+
+void SalGraphics::mirror( vcl::Region& rRgn, const OutputDevice *pOutDev ) const
+{
+ if( rRgn.HasPolyPolygonOrB2DPolyPolygon() )
+ {
+ const basegfx::B2DPolyPolygon aPolyPoly(mirror(rRgn.GetAsB2DPolyPolygon(), pOutDev));
+
+ rRgn = vcl::Region(aPolyPoly);
+ }
+ else
+ {
+ RectangleVector aRectangles;
+ rRgn.GetRegionRectangles(aRectangles);
+ rRgn.SetEmpty();
+
+ for (auto & rectangle : aRectangles)
+ {
+ mirror(rectangle, pOutDev);
+ 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, pOutDev, bBack );
+ // aMirroredRegion.Union( aRect );
+ // bRegionRect = rRgn.ImplGetNextRect( aInfo, nX, nY, nWidth, nHeight );
+ //}
+ //rRgn = aMirroredRegion;
+ }
+}
+
+void SalGraphics::mirror( tools::Rectangle& rRect, const OutputDevice *pOutDev, bool bBack ) const
+{
+ long nWidth = rRect.GetWidth();
+ long x = rRect.Left();
+ long x_org = x;
+
+ mirror( x, nWidth, pOutDev, bBack );
+ rRect.Move( x - x_org, 0 );
+}
+
+basegfx::B2DPolyPolygon SalGraphics::mirror( const basegfx::B2DPolyPolygon& i_rPoly, const OutputDevice* i_pOutDev ) const
+{
+ const basegfx::B2DHomMatrix& rMirror(getMirror(i_pOutDev));
+
+ if(rMirror.isIdentity())
+ {
+ return i_rPoly;
+ }
+ else
+ {
+ basegfx::B2DPolyPolygon aRet(i_rPoly);
+ aRet.transform(rMirror);
+ aRet.flip();
+ return aRet;
+ }
+}
+
+const basegfx::B2DHomMatrix& SalGraphics::getMirror( const OutputDevice* i_pOutDev ) const
+{
+ // get mirroring transformation
+ const long w = GetDeviceWidth(i_pOutDev);
+ SAL_WARN_IF( !w, "vcl", "missing graphics width" );
+
+ if(w != m_aLastMirrorW)
+ {
+ const_cast<SalGraphics*>(this)->m_aLastMirrorW = w;
+
+ if(w)
+ {
+ if(nullptr != i_pOutDev && !i_pOutDev->IsRTLEnabled())
+ {
+ // Original code was (removed here already pOutDevRef->i_pOutDev):
+ // // mirror this window back
+ // double devX = w-i_pOutDev->GetOutputWidthPixel()-i_pOutDev->GetOutOffXPixel(); // re-mirrored mnOutOffX
+ // aRet.setX( devX + (i_rPoint.getX() - i_pOutDev->GetOutOffXPixel()) );
+ // I do not really understand the comment 'mirror this window back', so cannot guarantee
+ // that this works as before, but I have reduced this (by re-placing and re-formatting) to
+ // a simple translation:
+ const_cast<SalGraphics*>(this)->m_aLastMirror = basegfx::utils::createTranslateB2DHomMatrix(
+ w - i_pOutDev->GetOutputWidthPixel() - (2 * i_pOutDev->GetOutOffXPixel()),
+ 0.0);
+ }
+ else
+ {
+ // 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,
+ w-1,
+ 0.0);
+ }
+ }
+ else
+ {
+ const_cast<SalGraphics*>(this)->m_aLastMirror.identity();
+ }
+ }
+
+ return m_aLastMirror;
+}
+
+bool SalGraphics::SetClipRegion( const vcl::Region& i_rClip, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ vcl::Region aMirror( i_rClip );
+ mirror( aMirror, pOutDev );
+ return setClipRegion( aMirror );
+ }
+ return setClipRegion( i_rClip );
+}
+
+void SalGraphics::DrawPixel( long nX, long nY, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, pOutDev );
+ drawPixel( nX, nY );
+}
+
+void SalGraphics::DrawPixel( long nX, long nY, Color nColor, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, pOutDev );
+ drawPixel( nX, nY, nColor );
+}
+
+void SalGraphics::DrawLine( long nX1, long nY1, long nX2, long nY2, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ mirror( nX1, pOutDev );
+ mirror( nX2, pOutDev );
+ }
+ drawLine( nX1, nY1, nX2, nY2 );
+}
+
+void SalGraphics::DrawRect( long nX, long nY, long nWidth, long nHeight, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, nWidth, pOutDev );
+ drawRect( nX, nY, nWidth, nHeight );
+}
+
+void SalGraphics::DrawPolyLine( sal_uInt32 nPoints, SalPoint const * pPtAry, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ std::unique_ptr<SalPoint[]> pPtAry2(new SalPoint[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), pOutDev );
+ drawPolyLine( nPoints, bCopied ? pPtAry2.get() : pPtAry );
+ }
+ else
+ drawPolyLine( nPoints, pPtAry );
+}
+
+void SalGraphics::DrawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ std::unique_ptr<SalPoint[]> pPtAry2(new SalPoint[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), pOutDev );
+ drawPolygon( nPoints, bCopied ? pPtAry2.get() : pPtAry );
+ }
+ else
+ drawPolygon( nPoints, pPtAry );
+}
+
+void SalGraphics::DrawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, PCONSTSALPOINT* pPtAry, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ // TODO: optimize, reduce new/delete calls
+ std::unique_ptr<SalPoint*[]> pPtAry2( new SalPoint*[nPoly] );
+ sal_uLong i;
+ for(i=0; i<nPoly; i++)
+ {
+ sal_uLong nPoints = pPoints[i];
+ pPtAry2[i] = new SalPoint[ nPoints ];
+ mirror( nPoints, pPtAry[i], pPtAry2[i], pOutDev );
+ }
+
+ drawPolyPolygon( nPoly, pPoints, const_cast<PCONSTSALPOINT*>(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);
+ }
+}
+
+bool SalGraphics::DrawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& i_rPolyPolygon,
+ double i_fTransparency,
+ const OutputDevice* i_pOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (i_pOutDev && i_pOutDev->IsRTLEnabled()) )
+ {
+ // mirroring set
+ const basegfx::B2DHomMatrix& rMirror(getMirror(i_pOutDev));
+ if(!rMirror.isIdentity())
+ {
+ basegfx::B2DRange aBoundingBox(i_rPolyPolygon.getB2DRange());
+ aBoundingBox *= rObjectToDevice;
+ auto aTranslateToMirroredBounds = createTranslateToMirroredBounds(aBoundingBox, rMirror);
+
+ return drawPolyPolygon(
+ aTranslateToMirroredBounds * rObjectToDevice,
+ i_rPolyPolygon,
+ i_fTransparency);
+ }
+ }
+
+ return drawPolyPolygon(
+ rObjectToDevice,
+ i_rPolyPolygon,
+ i_fTransparency);
+}
+
+bool SalGraphics::DrawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry, const OutputDevice* pOutDev )
+{
+ bool bResult = false;
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ std::unique_ptr<SalPoint[]> pPtAry2(new SalPoint[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), pOutDev );
+ bResult = drawPolyLineBezier( nPoints, bCopied ? pPtAry2.get() : pPtAry, pFlgAry );
+ }
+ else
+ bResult = drawPolyLineBezier( nPoints, pPtAry, pFlgAry );
+ return bResult;
+}
+
+bool SalGraphics::DrawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry, const OutputDevice* pOutDev )
+{
+ bool bResult = false;
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ std::unique_ptr<SalPoint[]> pPtAry2(new SalPoint[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), pOutDev );
+ 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 SalPoint* const* i_pPtAry, const PolyFlags* const* i_pFlgAry, const OutputDevice* i_pOutDev )
+{
+ bool bRet = false;
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (i_pOutDev && i_pOutDev->IsRTLEnabled()) )
+ {
+ // TODO: optimize, reduce new/delete calls
+ std::unique_ptr<SalPoint*[]> pPtAry2( new SalPoint*[i_nPoly] );
+ sal_uLong i;
+ for(i=0; i<i_nPoly; i++)
+ {
+ sal_uLong nPoints = i_pPoints[i];
+ pPtAry2[i] = new SalPoint[ nPoints ];
+ mirror( nPoints, i_pPtAry[i], pPtAry2[i], i_pOutDev );
+ }
+
+ bRet = drawPolyPolygonBezier( i_nPoly, i_pPoints, const_cast<PCONSTSALPOINT 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_pOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (i_pOutDev && i_pOutDev->IsRTLEnabled()) )
+ {
+ // mirroring set
+ const basegfx::B2DHomMatrix& rMirror(getMirror(i_pOutDev));
+ if(!rMirror.isIdentity())
+ {
+ basegfx::B2DRange aBoundingBox(i_rPolygon.getB2DRange());
+ aBoundingBox *= rObjectToDevice;
+ auto aTranslateToMirroredBounds = createTranslateToMirroredBounds(aBoundingBox, rMirror);
+
+ return drawPolyLine(
+ aTranslateToMirroredBounds * 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 )
+{
+ return drawGradient( rPolyPoly, rGradient );
+}
+
+bool SalGraphics::DrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon, SalGradient const & rSalGradient)
+{
+ return implDrawGradient(rPolyPolygon, rSalGradient);
+}
+
+void SalGraphics::CopyArea( long nDestX, long nDestY,
+ long nSrcX, long nSrcY,
+ long nSrcWidth, long nSrcHeight,
+ const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ mirror( nDestX, nSrcWidth, pOutDev );
+ mirror( nSrcX, nSrcWidth, pOutDev );
+ }
+ copyArea( nDestX, nDestY, nSrcX, nSrcY, nSrcWidth, nSrcHeight, true/*bWindowInvalidate*/ );
+}
+
+void SalGraphics::CopyBits( const SalTwoRect& rPosAry,
+ SalGraphics* pSrcGraphics, const OutputDevice *pOutDev, const OutputDevice *pSrcOutDev )
+{
+ if( ( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) ) ||
+ (pSrcGraphics && ( (pSrcGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) || (pSrcOutDev && pSrcOutDev->IsRTLEnabled()) ) ) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ if( (pSrcGraphics && (pSrcGraphics->GetLayout() & SalLayoutFlags::BiDiRtl)) || (pSrcOutDev && pSrcOutDev->IsRTLEnabled()) )
+ mirror( aPosAry2.mnSrcX, aPosAry2.mnSrcWidth, pSrcOutDev );
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, pOutDev );
+ copyBits( aPosAry2, pSrcGraphics );
+ }
+ else
+ copyBits( rPosAry, pSrcGraphics );
+}
+
+void SalGraphics::DrawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, pOutDev );
+ drawBitmap( aPosAry2, rSalBitmap );
+ }
+ else
+ drawBitmap( rPosAry, rSalBitmap );
+}
+
+void SalGraphics::DrawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, pOutDev );
+ drawBitmap( aPosAry2, rSalBitmap, rTransparentBitmap );
+ }
+ else
+ drawBitmap( rPosAry, rSalBitmap, rTransparentBitmap );
+}
+
+void SalGraphics::DrawMask( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ Color nMaskColor, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, pOutDev );
+ drawMask( aPosAry2, rSalBitmap, nMaskColor );
+ }
+ else
+ drawMask( rPosAry, rSalBitmap, nMaskColor );
+}
+
+std::shared_ptr<SalBitmap> SalGraphics::GetBitmap( long nX, long nY, long nWidth, long nHeight, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, nWidth, pOutDev );
+ return getBitmap( nX, nY, nWidth, nHeight );
+}
+
+Color SalGraphics::GetPixel( long nX, long nY, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, pOutDev );
+ return getPixel( nX, nY );
+}
+
+void SalGraphics::Invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, nWidth, pOutDev );
+ invert( nX, nY, nWidth, nHeight, nFlags );
+}
+
+void SalGraphics::Invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ std::unique_ptr<SalPoint[]> pPtAry2(new SalPoint[nPoints]);
+ bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), pOutDev );
+ invert( nPoints, bCopied ? pPtAry2.get() : pPtAry, nFlags );
+ }
+ else
+ invert( nPoints, pPtAry, nFlags );
+}
+
+bool SalGraphics::DrawEPS( long nX, long nY, long nWidth, long nHeight, void* pPtr, sal_uInt32 nSize, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, nWidth, pOutDev );
+ return drawEPS( nX, nY, nWidth, nHeight, pPtr, nSize );
+}
+
+bool SalGraphics::HitTestNativeScrollbar( ControlPart nPart, const tools::Rectangle& rControlRegion,
+ const Point& aPos, bool& rIsInside, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ Point pt( aPos );
+ tools::Rectangle rgn( rControlRegion );
+ pt.setX( mirror2( pt.X(), pOutDev ) );
+ mirror( rgn, pOutDev );
+ 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* pOutDev ) const
+{
+ switch( rVal.getType() )
+ {
+ case ControlType::Slider:
+ {
+ SliderValue* pSlVal = static_cast<SliderValue*>(&rVal);
+ mirror(pSlVal->maThumbRect,pOutDev);
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ ScrollbarValue* pScVal = static_cast<ScrollbarValue*>(&rVal);
+ mirror(pScVal->maThumbRect,pOutDev);
+ mirror(pScVal->maButton1Rect,pOutDev);
+ mirror(pScVal->maButton2Rect,pOutDev);
+ }
+ break;
+ case ControlType::Spinbox:
+ case ControlType::SpinButtons:
+ {
+ SpinbuttonValue* pSpVal = static_cast<SpinbuttonValue*>(&rVal);
+ mirror(pSpVal->maUpperRect,pOutDev);
+ mirror(pSpVal->maLowerRect,pOutDev);
+ }
+ break;
+ case ControlType::Toolbar:
+ {
+ ToolbarValue* pTVal = static_cast<ToolbarValue*>(&rVal);
+ mirror(pTVal->maGripRect,pOutDev);
+ }
+ break;
+ default: break;
+ }
+}
+
+bool SalGraphics::DrawNativeControl( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion,
+ ControlState nState, const ImplControlValue& aValue,
+ const OUString& aCaption, const OutputDevice *pOutDev,
+ const Color& rBackgroundColor)
+{
+ bool bRet = false;
+ tools::Rectangle aControlRegion(rControlRegion);
+ if (aControlRegion.IsEmpty() || aControlRegion.GetWidth() <= 0 || aControlRegion.GetHeight() <= 0)
+ return bRet;
+
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ mirror(aControlRegion, pOutDev);
+ std::unique_ptr< ImplControlValue > mirrorValue( aValue.clone());
+ mirror( *mirrorValue, pOutDev );
+ 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 *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ tools::Rectangle rgn( rControlRegion );
+ mirror( rgn, pOutDev );
+ std::unique_ptr< ImplControlValue > mirrorValue( aValue.clone());
+ mirror( *mirrorValue, pOutDev );
+ if (forWidget()->getNativeControlRegion(nType, nPart, rgn, nState, *mirrorValue, OUString(), rNativeBoundingRegion, rNativeContentRegion))
+ {
+ mirror( rNativeBoundingRegion, pOutDev, true );
+ mirror( rNativeContentRegion, pOutDev, 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 *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, pOutDev );
+ 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 *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, pOutDev );
+ 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 *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, pOutDev );
+ 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,
+ const OutputDevice* pOutDev)
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ {
+ // mirroring set
+ const basegfx::B2DHomMatrix& rMirror(getMirror(pOutDev));
+ 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);
+ }
+ }
+
+ return drawTransformedBitmap(rNull, rX, rY, rSourceBitmap, pAlphaBitmap);
+}
+
+bool SalGraphics::DrawAlphaRect( long nX, long nY, long nWidth, long nHeight,
+ sal_uInt8 nTransparency, const OutputDevice *pOutDev )
+{
+ if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (pOutDev && pOutDev->IsRTLEnabled()) )
+ mirror( nX, nWidth, pOutDev );
+
+ return drawAlphaRect( nX, nY, nWidth, nHeight, nTransparency );
+}
+
+OUString SalGraphics::getRenderBackendName() const
+{
+ if (GetImpl())
+ return GetImpl()->getRenderBackendName();
+ return OUString();
+}
+
+/* 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 000000000..eaa016ed1
--- /dev/null
+++ b/vcl/source/gdi/sallayout.cxx
@@ -0,0 +1,1585 @@
+/* -*- 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 <iostream>
+#include <iomanip>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <cstdio>
+
+#include <math.h>
+
+#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 <unicode/ubidi.h>
+#include <unicode/uchar.h>
+
+#include <algorithm>
+#include <memory>
+
+#include <impglyphitem.hxx>
+
+// Glyph Flags
+#define GF_FONTMASK 0xF0000000
+#define GF_FONTSHIFT 28
+
+
+std::ostream &operator <<(std::ostream& s, ImplLayoutArgs const &rArgs)
+{
+#ifndef SAL_LOG_INFO
+ (void) rArgs;
+#else
+ s << "ImplLayoutArgs{";
+
+ s << "Flags=";
+ if (rArgs.mnFlags == SalLayoutFlags::NONE)
+ s << 0;
+ else {
+ bool need_or = false;
+ s << "{";
+#define TEST(x) if (rArgs.mnFlags & SalLayoutFlags::x) { if (need_or) s << "|"; s << #x; need_or = true; }
+ TEST(BiDiRtl);
+ TEST(BiDiStrong);
+ TEST(RightAlign);
+ TEST(DisableKerning);
+ TEST(KerningAsian);
+ TEST(Vertical);
+ TEST(KashidaJustification);
+ TEST(ForFallback);
+#undef TEST
+ s << "}";
+ }
+
+ const int nLength = rArgs.mrStr.getLength();
+
+ s << ",Length=" << nLength;
+ s << ",MinCharPos=" << rArgs.mnMinCharPos;
+ s << ",EndCharPos=" << rArgs.mnEndCharPos;
+
+ s << ",Str=\"";
+ int lim = nLength;
+ if (lim > 10)
+ lim = 7;
+ for (int i = 0; i < lim; i++) {
+ if (rArgs.mrStr[i] == '\n')
+ s << "\\n";
+ else if (rArgs.mrStr[i] < ' ' || (rArgs.mrStr[i] >= 0x7F && rArgs.mrStr[i] <= 0xFF))
+ s << "\\0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec;
+ else if (rArgs.mrStr[i] < 0x7F)
+ s << static_cast<char>(rArgs.mrStr[i]);
+ else
+ s << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec;
+ }
+ if (nLength > lim)
+ s << "...";
+ s << "\"";
+
+ s << ",DXArray=";
+ if (rArgs.mpDXArray) {
+ s << "[";
+ int count = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
+ lim = count;
+ if (lim > 10)
+ lim = 7;
+ for (int i = 0; i < lim; i++) {
+ s << rArgs.mpDXArray[i];
+ if (i < lim-1)
+ s << ",";
+ }
+ if (count > lim) {
+ if (count > lim + 1)
+ s << "...";
+ s << rArgs.mpDXArray[count-1];
+ }
+ s << "]";
+ } else
+ s << "NULL";
+
+ s << ",LayoutWidth=" << rArgs.mnLayoutWidth;
+
+ s << "}";
+
+#endif
+ return s;
+}
+
+sal_UCS4 GetMirroredChar( sal_UCS4 nChar )
+{
+ nChar = u_charMirror( nChar );
+ return nChar;
+}
+
+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;
+}
+
+static bool IsControlChar( sal_UCS4 cChar )
+{
+ // C0 control characters
+ if( (0x0001 <= cChar) && (cChar <= 0x001F) )
+ return true;
+ // formatting characters
+ if( (0x200E <= cChar) && (cChar <= 0x200F) )
+ return true;
+ if( (0x2028 <= cChar) && (cChar <= 0x202E) )
+ return true;
+ // deprecated formatting characters
+ if( (0x206A <= cChar) && (cChar <= 0x206F) )
+ return true;
+ if( 0x2060 == cChar )
+ return true;
+ // byte order markers and invalid unicode
+ if( (cChar == 0xFEFF) || (cChar == 0xFFFE) || (cChar == 0xFFFF) )
+ return true;
+ return false;
+}
+
+void ImplLayoutRuns::AddPos( int nCharPos, bool bRTL )
+{
+ // check if charpos could extend current run
+ int nIndex = maRuns.size();
+ if( nIndex >= 2 )
+ {
+ int nRunPos0 = maRuns[ nIndex-2 ];
+ int nRunPos1 = maRuns[ nIndex-1 ];
+ if( ((nCharPos + int(bRTL)) == nRunPos1) && ((nRunPos0 > nRunPos1) == bRTL) )
+ {
+ // extend current run by new charpos
+ maRuns[ nIndex-1 ] = nCharPos + int(!bRTL);
+ return;
+ }
+ // ignore new charpos when it is in current run
+ if( (nRunPos0 <= nCharPos) && (nCharPos < nRunPos1) )
+ return;
+ if( (nRunPos1 <= nCharPos) && (nCharPos < nRunPos0) )
+ return;
+ }
+
+ // else append a new run consisting of the new charpos
+ maRuns.push_back( nCharPos + (bRTL ? 1 : 0) );
+ maRuns.push_back( nCharPos + (bRTL ? 0 : 1) );
+}
+
+void ImplLayoutRuns::AddRun( int nCharPos0, int nCharPos1, bool bRTL )
+{
+ if( nCharPos0 == nCharPos1 )
+ return;
+
+ // swap if needed
+ if( bRTL == (nCharPos0 < nCharPos1) )
+ {
+ int nTemp = nCharPos0;
+ nCharPos0 = nCharPos1;
+ nCharPos1 = nTemp;
+ }
+
+ if (maRuns.size() >= 2 && nCharPos0 == maRuns[maRuns.size() - 2] && nCharPos1 == maRuns[maRuns.size() - 1])
+ {
+ //this run is the same as the last
+ return;
+ }
+
+ // append new run
+ maRuns.push_back( nCharPos0 );
+ maRuns.push_back( nCharPos1 );
+}
+
+bool ImplLayoutRuns::PosIsInRun( int nCharPos ) const
+{
+ if( mnRunIndex >= static_cast<int>(maRuns.size()) )
+ return false;
+
+ int nMinCharPos = maRuns[ mnRunIndex+0 ];
+ int nEndCharPos = maRuns[ mnRunIndex+1 ];
+ if( nMinCharPos > nEndCharPos ) // reversed in RTL case
+ {
+ int nTemp = nMinCharPos;
+ nMinCharPos = nEndCharPos;
+ nEndCharPos = nTemp;
+ }
+
+ if( nCharPos < nMinCharPos )
+ return false;
+ if( nCharPos >= nEndCharPos )
+ return false;
+ return true;
+}
+
+bool ImplLayoutRuns::PosIsInAnyRun( int nCharPos ) const
+{
+ bool bRet = false;
+ int nRunIndex = mnRunIndex;
+
+ ImplLayoutRuns *pThis = const_cast<ImplLayoutRuns*>(this);
+
+ pThis->ResetPos();
+
+ for (size_t i = 0; i < maRuns.size(); i+=2)
+ {
+ bRet = PosIsInRun( nCharPos );
+ if( bRet )
+ break;
+ pThis->NextRun();
+ }
+
+ pThis->mnRunIndex = nRunIndex;
+ return bRet;
+}
+
+bool ImplLayoutRuns::GetNextPos( int* nCharPos, bool* bRightToLeft )
+{
+ // negative nCharPos => reset to first run
+ if( *nCharPos < 0 )
+ mnRunIndex = 0;
+
+ // return false when all runs completed
+ if( mnRunIndex >= static_cast<int>(maRuns.size()) )
+ return false;
+
+ int nRunPos0 = maRuns[ mnRunIndex+0 ];
+ int nRunPos1 = maRuns[ mnRunIndex+1 ];
+ *bRightToLeft = (nRunPos0 > nRunPos1);
+
+ if( *nCharPos < 0 )
+ {
+ // get first valid nCharPos in run
+ *nCharPos = nRunPos0;
+ }
+ else
+ {
+ // advance to next nCharPos for LTR case
+ if( !*bRightToLeft )
+ ++(*nCharPos);
+
+ // advance to next run if current run is completed
+ if( *nCharPos == nRunPos1 )
+ {
+ if( (mnRunIndex += 2) >= static_cast<int>(maRuns.size()) )
+ return false;
+ nRunPos0 = maRuns[ mnRunIndex+0 ];
+ nRunPos1 = maRuns[ mnRunIndex+1 ];
+ *bRightToLeft = (nRunPos0 > nRunPos1);
+ *nCharPos = nRunPos0;
+ }
+ }
+
+ // advance to next nCharPos for RTL case
+ if( *bRightToLeft )
+ --(*nCharPos);
+
+ return true;
+}
+
+bool ImplLayoutRuns::GetRun( int* nMinRunPos, int* nEndRunPos, bool* bRightToLeft ) const
+{
+ if( mnRunIndex >= static_cast<int>(maRuns.size()) )
+ return false;
+
+ int nRunPos0 = maRuns[ mnRunIndex+0 ];
+ int nRunPos1 = maRuns[ mnRunIndex+1 ];
+ *bRightToLeft = (nRunPos1 < nRunPos0) ;
+ if( !*bRightToLeft )
+ {
+ *nMinRunPos = nRunPos0;
+ *nEndRunPos = nRunPos1;
+ }
+ else
+ {
+ *nMinRunPos = nRunPos1;
+ *nEndRunPos = nRunPos0;
+ }
+ return true;
+}
+
+ImplLayoutArgs::ImplLayoutArgs(const OUString& rStr,
+ int nMinCharPos, int nEndCharPos, SalLayoutFlags nFlags, const LanguageTag& rLanguageTag,
+ vcl::TextLayoutCache const*const pLayoutCache)
+:
+ maLanguageTag( rLanguageTag ),
+ mnFlags( nFlags ),
+ mrStr( rStr ),
+ mnMinCharPos( nMinCharPos ),
+ mnEndCharPos( nEndCharPos ),
+ m_pTextLayoutCache(pLayoutCache),
+ mpDXArray( nullptr ),
+ mnLayoutWidth( 0 ),
+ mnOrientation( 0 )
+{
+ if( mnFlags & SalLayoutFlags::BiDiStrong )
+ {
+ // handle strong BiDi mode
+
+ // do not bother to BiDi analyze strong LTR/RTL
+ // TODO: can we assume these strings do not have unicode control chars?
+ // if not remove the control characters from the runs
+ bool bRTL(mnFlags & SalLayoutFlags::BiDiRtl);
+ AddRun( mnMinCharPos, mnEndCharPos, bRTL );
+ }
+ else
+ {
+ // handle weak BiDi mode
+ UBiDiLevel nLevel = (mnFlags & SalLayoutFlags::BiDiRtl)? 1 : 0;
+
+ // prepare substring for BiDi analysis
+ // TODO: reuse allocated pParaBidi
+ UErrorCode rcI18n = U_ZERO_ERROR;
+ const int nLength = mrStr.getLength();
+ UBiDi* pParaBidi = ubidi_openSized(nLength, 0, &rcI18n);
+ if( !pParaBidi )
+ return;
+ ubidi_setPara(pParaBidi, reinterpret_cast<const UChar *>(mrStr.getStr()), nLength, nLevel, nullptr, &rcI18n);
+
+ UBiDi* pLineBidi = pParaBidi;
+ int nSubLength = mnEndCharPos - mnMinCharPos;
+ if (nSubLength != nLength)
+ {
+ pLineBidi = ubidi_openSized( nSubLength, 0, &rcI18n );
+ ubidi_setLine( pParaBidi, mnMinCharPos, mnEndCharPos, pLineBidi, &rcI18n );
+ }
+
+ // run BiDi algorithm
+ const int nRunCount = ubidi_countRuns( pLineBidi, &rcI18n );
+ //maRuns.resize( 2 * nRunCount );
+ for( int i = 0; i < nRunCount; ++i )
+ {
+ int32_t nMinPos, nRunLength;
+ const UBiDiDirection nDir = ubidi_getVisualRun( pLineBidi, i, &nMinPos, &nRunLength );
+ const int nPos0 = nMinPos + mnMinCharPos;
+ const int nPos1 = nPos0 + nRunLength;
+
+ const bool bRTL = (nDir == UBIDI_RTL);
+ AddRun( nPos0, nPos1, bRTL );
+ }
+
+ // cleanup BiDi engine
+ if( pLineBidi != pParaBidi )
+ ubidi_close( pLineBidi );
+ ubidi_close( pParaBidi );
+ }
+
+ // prepare calls to GetNextPos/GetNextRun
+ maRuns.ResetPos();
+}
+
+// add a run after splitting it up to get rid of control chars
+void ImplLayoutArgs::AddRun( int nCharPos0, int nCharPos1, bool bRTL )
+{
+ SAL_WARN_IF( nCharPos0 > nCharPos1, "vcl", "ImplLayoutArgs::AddRun() nCharPos0>=nCharPos1" );
+
+ // remove control characters from runs by splitting them up
+ if( !bRTL )
+ {
+ for( int i = nCharPos0; i < nCharPos1; ++i )
+ if( IsControlChar( mrStr[i] ) )
+ {
+ // add run until control char
+ maRuns.AddRun( nCharPos0, i, bRTL );
+ nCharPos0 = i + 1;
+ }
+ }
+ else
+ {
+ for( int i = nCharPos1; --i >= nCharPos0; )
+ if( IsControlChar( mrStr[i] ) )
+ {
+ // add run until control char
+ maRuns.AddRun( i+1, nCharPos1, bRTL );
+ nCharPos1 = i;
+ }
+ }
+
+ // add remainder of run
+ maRuns.AddRun( nCharPos0, nCharPos1, bRTL );
+}
+
+bool ImplLayoutArgs::PrepareFallback()
+{
+ // short circuit if no fallback is needed
+ if( maFallbackRuns.IsEmpty() )
+ {
+ maRuns.Clear();
+ return false;
+ }
+
+ // convert the fallback requests to layout requests
+ bool bRTL;
+ int nMin, nEnd;
+
+ // get the individual fallback requests
+ std::vector<int> aPosVector;
+ aPosVector.reserve(mrStr.getLength());
+ maFallbackRuns.ResetPos();
+ for(; maFallbackRuns.GetRun( &nMin, &nEnd, &bRTL ); maFallbackRuns.NextRun() )
+ for( int i = nMin; i < nEnd; ++i )
+ aPosVector.push_back( i );
+ maFallbackRuns.Clear();
+
+ // sort the individual fallback requests
+ std::sort( aPosVector.begin(), aPosVector.end() );
+
+ // adjust fallback runs to have the same order and limits of the original runs
+ ImplLayoutRuns aNewRuns;
+ maRuns.ResetPos();
+ for(; maRuns.GetRun( &nMin, &nEnd, &bRTL ); maRuns.NextRun() )
+ {
+ if( !bRTL) {
+ auto it = std::lower_bound( aPosVector.begin(), aPosVector.end(), nMin );
+ for(; (it != aPosVector.end()) && (*it < nEnd); ++it )
+ aNewRuns.AddPos( *it, bRTL );
+ } else {
+ auto it = std::upper_bound( aPosVector.begin(), aPosVector.end(), nEnd );
+ while( (it != aPosVector.begin()) && (*--it >= nMin) )
+ aNewRuns.AddPos( *it, bRTL );
+ }
+ }
+
+ maRuns = aNewRuns; // TODO: use vector<>::swap()
+ maRuns.ResetPos();
+ return true;
+}
+
+bool ImplLayoutArgs::GetNextRun( int* nMinRunPos, int* nEndRunPos, bool* bRTL )
+{
+ bool bValid = maRuns.GetRun( nMinRunPos, nEndRunPos, bRTL );
+ maRuns.NextRun();
+ return bValid;
+}
+
+SalLayout::SalLayout()
+: mnMinCharPos( -1 ),
+ mnEndCharPos( -1 ),
+ mnUnitsPerPixel( 1 ),
+ mnOrientation( 0 ),
+ maDrawOffset( 0, 0 )
+{}
+
+SalLayout::~SalLayout()
+{}
+
+void SalLayout::AdjustLayout( ImplLayoutArgs& rArgs )
+{
+ mnMinCharPos = rArgs.mnMinCharPos;
+ mnEndCharPos = rArgs.mnEndCharPos;
+ mnOrientation = rArgs.mnOrientation;
+}
+
+Point SalLayout::GetDrawPosition( const Point& rRelative ) const
+{
+ Point aPos = maDrawBase;
+ Point aOfs = rRelative + maDrawOffset;
+
+ if( mnOrientation == 0 )
+ aPos += aOfs;
+ else
+ {
+ // cache trigonometric results
+ static int nOldOrientation = 0;
+ static double fCos = 1.0, fSin = 0.0;
+ if( nOldOrientation != mnOrientation )
+ {
+ nOldOrientation = mnOrientation;
+ double fRad = mnOrientation * (M_PI / 1800.0);
+ fCos = cos( fRad );
+ fSin = sin( fRad );
+ }
+
+ double fX = aOfs.X();
+ double fY = aOfs.Y();
+ long nX = static_cast<long>( +fCos * fX + fSin * fY );
+ long nY = static_cast<long>( +fCos * fY - fSin * fX );
+ aPos += Point( nX, nY );
+ }
+
+ return aPos;
+}
+
+bool SalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rVector) const
+{
+ bool bAllOk = true;
+ bool bOneOk = false;
+
+ basegfx::B2DPolyPolygon aGlyphOutline;
+
+ Point aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ // get outline of individual glyph, ignoring "empty" glyphs
+ bool bSuccess = pGlyph->GetGlyphOutline(aGlyphOutline);
+ bAllOk &= bSuccess;
+ bOneOk |= bSuccess;
+ // only add non-empty outlines
+ if( bSuccess && (aGlyphOutline.count() > 0) )
+ {
+ if( aPos.X() || aPos.Y() )
+ {
+ aGlyphOutline.transform(basegfx::utils::createTranslateB2DHomMatrix(aPos.X(), aPos.Y()));
+ }
+
+ // 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;
+
+ Point aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ // get bounding rectangle of individual glyph
+ if (pGlyph->GetGlyphBoundRect(aRectangle))
+ {
+ // merge rectangle
+ aRectangle += aPos;
+ if (rRect.IsEmpty())
+ rRect = aRectangle;
+ else
+ rRect.Union(aRectangle);
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+DeviceCoordinate GenericSalLayout::FillDXArray( DeviceCoordinate* pCharWidths ) const
+{
+ if (pCharWidths)
+ GetCharWidths(pCharWidths);
+
+ return GetTextWidth();
+}
+
+// the text width is the maximum logical extent of all glyphs
+DeviceCoordinate GenericSalLayout::GetTextWidth() const
+{
+ if (!m_GlyphItems.IsValid())
+ return 0;
+
+ // initialize the extent
+ DeviceCoordinate nMinPos = 0;
+ DeviceCoordinate nMaxPos = 0;
+
+ for (auto const& aGlyphItem : *m_GlyphItems.Impl())
+ {
+ // update the text extent with the glyph extent
+ DeviceCoordinate nXPos = aGlyphItem.m_aLinearPos.getX();
+ if( nMinPos > nXPos )
+ nMinPos = nXPos;
+ nXPos += aGlyphItem.m_nNewWidth - aGlyphItem.xOffset();
+ if( nMaxPos < nXPos )
+ nMaxPos = nXPos;
+ }
+
+ DeviceCoordinate nWidth = nMaxPos - nMinPos;
+ return nWidth;
+}
+
+void GenericSalLayout::Justify( DeviceCoordinate nNewWidth )
+{
+ nNewWidth *= mnUnitsPerPixel;
+ DeviceCoordinate 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.Impl()->begin();
+ pGlyphIterRight += m_GlyphItems.Impl()->size() - 1;
+ std::vector<GlyphItem>::iterator pGlyphIter;
+ // count stretchable glyphs
+ int nStretchable = 0;
+ int nMaxGlyphWidth = 0;
+ for(pGlyphIter = m_GlyphItems.Impl()->begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter)
+ {
+ if( !pGlyphIter->IsDiacritic() )
+ ++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->m_aLinearPos.setX( nNewWidth );
+
+ // justify glyph widths and positions
+ int nDiffWidth = nNewWidth - nOldWidth;
+ if( nDiffWidth >= 0) // expanded case
+ {
+ // expand width by distributing space between glyphs evenly
+ int nDeltaSum = 0;
+ for( pGlyphIter = m_GlyphItems.Impl()->begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter )
+ {
+ // move glyph to justified position
+ pGlyphIter->m_aLinearPos.AdjustX(nDeltaSum );
+
+ // do not stretch non-stretchable glyphs
+ if( pGlyphIter->IsDiacritic() || (nStretchable <= 0) )
+ continue;
+
+ // distribute extra space equally to stretchable glyphs
+ int nDeltaWidth = nDiffWidth / nStretchable--;
+ nDiffWidth -= nDeltaWidth;
+ pGlyphIter->m_nNewWidth += nDeltaWidth;
+ nDeltaSum += nDeltaWidth;
+ }
+ }
+ else // condensed case
+ {
+ // squeeze width by moving glyphs proportionally
+ double fSqueeze = static_cast<double>(nNewWidth) / nOldWidth;
+ if(m_GlyphItems.Impl()->size() > 1)
+ {
+ for( pGlyphIter = m_GlyphItems.Impl()->begin(); ++pGlyphIter != pGlyphIterRight;)
+ {
+ int nX = pGlyphIter->m_aLinearPos.getX();
+ nX = static_cast<int>(nX * fSqueeze);
+ pGlyphIter->m_aLinearPos.setX( nX );
+ }
+ }
+ // adjust glyph widths to new positions
+ for( pGlyphIter = m_GlyphItems.Impl()->begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter )
+ pGlyphIter->m_nNewWidth = pGlyphIter[1].m_aLinearPos.getX() - pGlyphIter[0].m_aLinearPos.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(const OUString& rStr)
+{
+ const int nLength = rStr.getLength();
+ long nOffset = 0;
+
+ for (std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.Impl()->begin(),
+ pGlyphIterEnd = m_GlyphItems.Impl()->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
+ int nDelta = (nKernCurrent < nKernNext) ? nKernCurrent : nKernNext;
+ if (nDelta < 0)
+ {
+ nDelta = (nDelta * pGlyphIter->origWidth() + 2) / 4;
+ if( pGlyphIter+1 == pGlyphIterEnd )
+ pGlyphIter->m_nNewWidth += nDelta;
+ nOffset += nDelta;
+ }
+ }
+
+ // adjust the glyph positions to the new glyph widths
+ if( pGlyphIter+1 != pGlyphIterEnd )
+ pGlyphIter->m_aLinearPos.AdjustX(nOffset);
+ }
+}
+
+void GenericSalLayout::GetCaretPositions( int nMaxIndex, long* pCaretXArray ) const
+{
+ // initialize result array
+ for (int i = 0; i < nMaxIndex; ++i)
+ pCaretXArray[i] = -1;
+
+ // calculate caret positions using glyph array
+ for (auto const& aGlyphItem : *m_GlyphItems.Impl())
+ {
+ long nXPos = aGlyphItem.m_aLinearPos.getX();
+ long nXRight = nXPos + aGlyphItem.origWidth();
+ int n = aGlyphItem.charPos();
+ int nCurrIdx = 2 * (n - mnMinCharPos);
+ // tdf#86399 if this is not the start of a cluster, don't overwrite the caret bounds of the cluster start
+ if (aGlyphItem.IsInCluster() && pCaretXArray[nCurrIdx] != -1)
+ continue;
+ if (!aGlyphItem.IsRTLGlyph() )
+ {
+ // normal positions for LTR case
+ pCaretXArray[ nCurrIdx ] = nXPos;
+ pCaretXArray[ nCurrIdx+1 ] = nXRight;
+ }
+ else
+ {
+ // reverse positions for RTL case
+ pCaretXArray[ nCurrIdx ] = nXRight;
+ pCaretXArray[ nCurrIdx+1 ] = nXPos;
+ }
+ }
+}
+
+sal_Int32 GenericSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor ) const
+{
+ int nCharCapacity = mnEndCharPos - mnMinCharPos;
+ std::unique_ptr<DeviceCoordinate[]> const pCharWidths(new DeviceCoordinate[nCharCapacity]);
+ GetCharWidths(pCharWidths.get());
+
+ DeviceCoordinate nWidth = 0;
+ for( int i = mnMinCharPos; i < mnEndCharPos; ++i )
+ {
+ nWidth += pCharWidths[ i - mnMinCharPos ] * nFactor;
+ if( nWidth > nMaxWidth )
+ return i;
+ nWidth += nCharExtra;
+ }
+
+ return -1;
+}
+
+bool GenericSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
+ Point& rPos, int& nStart,
+ const PhysicalFontFace**, int* const pFallbackLevel) const
+{
+ std::vector<GlyphItem>::const_iterator pGlyphIter = m_GlyphItems.Impl()->begin();
+ std::vector<GlyphItem>::const_iterator pGlyphIterEnd = m_GlyphItems.Impl()->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.Impl()->size()) )
+ return false;
+
+ if( pGlyphIter == pGlyphIterEnd )
+ return false;
+
+ // update return data with glyph info
+ *pGlyph = &(*pGlyphIter);
+ if (pFallbackLevel)
+ *pFallbackLevel = 0;
+ ++nStart;
+
+ // calculate absolute position in pixel units
+ Point aRelativePos = pGlyphIter->m_aLinearPos;
+
+ aRelativePos.setX( aRelativePos.X() / mnUnitsPerPixel );
+ aRelativePos.setY( aRelativePos.Y() / mnUnitsPerPixel );
+ rPos = GetDrawPosition( aRelativePos );
+
+ return true;
+}
+
+void GenericSalLayout::MoveGlyph( int nStart, long nNewXPos )
+{
+ if( nStart >= static_cast<int>(m_GlyphItems.Impl()->size()) )
+ return;
+
+ std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.Impl()->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->m_nNewWidth - pGlyphIter->origWidth();
+ // calculate the x-offset to the old position
+ long nXDelta = nNewXPos - pGlyphIter->m_aLinearPos.getX();
+ // adjust all following glyph positions if needed
+ if( nXDelta != 0 )
+ {
+ for( std::vector<GlyphItem>::iterator pGlyphIterEnd = m_GlyphItems.Impl()->end(); pGlyphIter != pGlyphIterEnd; ++pGlyphIter )
+ {
+ pGlyphIter->m_aLinearPos.AdjustX(nXDelta );
+ }
+ }
+}
+
+void GenericSalLayout::DropGlyph( int nStart )
+{
+ if( nStart >= static_cast<int>(m_GlyphItems.Impl()->size()))
+ return;
+
+ std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.Impl()->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.Impl()->size(); i++ )
+ {
+ if (bIsBase && (*m_GlyphItems.Impl())[i].IsDropped())
+ continue;
+ if (!bIsBase && (*m_GlyphItems.Impl())[i].glyphId() == 0)
+ continue;
+
+ if( i != j )
+ {
+ (*m_GlyphItems.Impl())[j] = (*m_GlyphItems.Impl())[i];
+ }
+ j += 1;
+ }
+ m_GlyphItems.Impl()->erase(m_GlyphItems.Impl()->begin() + j, m_GlyphItems.Impl()->end());
+}
+
+MultiSalLayout::MultiSalLayout( std::unique_ptr<SalLayout> pBaseLayout )
+: SalLayout()
+, mnLevel( 1 )
+, mbIncomplete( false )
+{
+ assert(dynamic_cast<GenericSalLayout*>(pBaseLayout.get()));
+
+ mpLayouts[ 0 ].reset(static_cast<GenericSalLayout*>(pBaseLayout.release()));
+ mnUnitsPerPixel = mpLayouts[ 0 ]->GetUnitsPerPixel();
+}
+
+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( ImplLayoutArgs& rArgs, const SalLayoutGlyphs* )
+{
+ if( mnLevel <= 1 )
+ return false;
+ if (!mbIncomplete)
+ maFallbackRuns[ mnLevel-1 ] = rArgs.maRuns;
+ return true;
+}
+
+void MultiSalLayout::AdjustLayout( ImplLayoutArgs& rArgs )
+{
+ SalLayout::AdjustLayout( rArgs );
+ ImplLayoutArgs aMultiArgs = rArgs;
+ std::unique_ptr<DeviceCoordinate[]> pJustificationArray;
+
+ if( !rArgs.mpDXArray && rArgs.mnLayoutWidth )
+ {
+ // for stretched text in a MultiSalLayout the target width needs to be
+ // distributed by individually adjusting its virtual character widths
+ DeviceCoordinate nTargetWidth = aMultiArgs.mnLayoutWidth;
+ nTargetWidth *= mnUnitsPerPixel; // convert target width to base font units
+ 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;
+ pJustificationArray.reset(new DeviceCoordinate[nCharCount]);
+ FillDXArray( pJustificationArray.get() );
+ // #i17359# multilayout is not simplified yet, so calculating the
+ // unjustified width needs handholding; also count the number of
+ // stretchable virtual char widths
+ DeviceCoordinate nOrigWidth = 0;
+ int nStretchable = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ // convert array from widths to sum of widths
+ nOrigWidth += pJustificationArray[i];
+ if( pJustificationArray[i] > 0 )
+ ++nStretchable;
+ }
+
+ // now we are able to distribute the extra width over the virtual char widths
+ if( nOrigWidth && (nTargetWidth != nOrigWidth) )
+ {
+ DeviceCoordinate nDiffWidth = nTargetWidth - nOrigWidth;
+ DeviceCoordinate nWidthSum = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ DeviceCoordinate nJustWidth = pJustificationArray[i];
+ if( (nJustWidth > 0) && (nStretchable > 0) )
+ {
+ DeviceCoordinate nDeltaWidth = nDiffWidth / nStretchable;
+ nJustWidth += nDeltaWidth;
+ nDiffWidth -= nDeltaWidth;
+ --nStretchable;
+ }
+ nWidthSum += nJustWidth;
+ pJustificationArray[i] = nWidthSum;
+ }
+ if( nWidthSum != nTargetWidth )
+ pJustificationArray[ nCharCount-1 ] = nTargetWidth;
+
+ // the justification array is still in base level units
+ // => convert it to pixel units
+ if( mnUnitsPerPixel > 1 )
+ {
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ DeviceCoordinate nVal = pJustificationArray[ i ];
+ nVal += (mnUnitsPerPixel + 1) / 2;
+ pJustificationArray[ i ] = nVal / mnUnitsPerPixel;
+ }
+ }
+
+ // change the mpDXArray temporarily (just for the justification)
+ aMultiArgs.mpDXArray = pJustificationArray.get();
+ }
+ }
+
+ // 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 };
+
+ Point aPos;
+ int nLevel = 0, n;
+ for( n = 0; n < mnLevel; ++n )
+ {
+ // now adjust the individual components
+ if( n > 0 )
+ {
+ aMultiArgs.maRuns = maFallbackRuns[ n-1 ];
+ aMultiArgs.mnFlags |= SalLayoutFlags::ForFallback;
+ }
+ mpLayouts[n]->AdjustLayout( aMultiArgs );
+
+ // 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
+ long nXPos = 0;
+ double fUnitMul = 1.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
+ fUnitMul = mnUnitsPerPixel;
+ fUnitMul /= mpLayouts[n]->GetUnitsPerPixel();
+ long nNewPos = static_cast<long>(nXPos/fUnitMul + 0.5);
+ mpLayouts[n]->MoveGlyph( nStartOld[n], nNewPos );
+ }
+ else
+ {
+ n = 0; // keep NotDef in base level
+ fUnitMul = 1.0;
+ }
+
+ if( n > 0 )
+ {
+ // drop the NotDef glyphs in the base layout run if a fallback run exists
+ while (
+ (maFallbackRuns[n-1].PosIsInRun(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
+ DeviceCoordinate nRunAdvance = 0;
+ bool bKeepNotDef = (nFBLevel >= nLevel);
+ for(;;)
+ {
+ nRunAdvance += pGlyphs[n]->m_nNewWidth;
+
+ // 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 (aMultiArgs.mpDXArray &&
+ nRunVisibleEndChar < mnEndCharPos &&
+ nRunVisibleEndChar >= mnMinCharPos &&
+ pGlyphs[n]->charPos() < mnEndCharPos &&
+ pGlyphs[n]->charPos() >= mnMinCharPos)
+ {
+ if (vRtl[nActiveCharPos - mnMinCharPos])
+ {
+ if (aMultiArgs.mpDXArray[nRunVisibleEndChar-mnMinCharPos]
+ >= aMultiArgs.mpDXArray[pGlyphs[n]->charPos() - mnMinCharPos])
+ {
+ nRunVisibleEndChar = pGlyphs[n]->charPos();
+ }
+ }
+ else if (aMultiArgs.mpDXArray[nRunVisibleEndChar-mnMinCharPos]
+ <= aMultiArgs.mpDXArray[pGlyphs[n]->charPos() - mnMinCharPos])
+ {
+ nRunVisibleEndChar = pGlyphs[n]->charPos();
+ }
+ }
+ }
+
+ // if a justification array is available
+ // => use it directly to calculate the corresponding run width
+ if( aMultiArgs.mpDXArray )
+ {
+ // 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 -= aMultiArgs.mpDXArray[nRunVisibleEndChar - 1 - mnMinCharPos];
+ if (nLastRunEndChar > mnMinCharPos && nLastRunEndChar <= mnEndCharPos)
+ nRunAdvance += aMultiArgs.mpDXArray[nLastRunEndChar - 1 - mnMinCharPos];
+ }
+ else
+ {
+ if (nRunVisibleEndChar >= mnMinCharPos)
+ nRunAdvance += aMultiArgs.mpDXArray[nRunVisibleEndChar - mnMinCharPos];
+ if (nLastRunEndChar >= mnMinCharPos)
+ nRunAdvance -= aMultiArgs.mpDXArray[nLastRunEndChar - mnMinCharPos];
+ }
+ nLastRunEndChar = nRunVisibleEndChar;
+ nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos();
+ // the requested width is still in pixel units
+ // => convert it to base level font units
+ nRunAdvance *= mnUnitsPerPixel;
+ }
+ else
+ {
+ // the measured width is still in fallback font units
+ // => convert it to base level font units
+ if( n > 0 ) // optimization: because (fUnitMul==1.0) for (n==0)
+ nRunAdvance = static_cast<long>(nRunAdvance*fUnitMul + 0.5);
+ }
+
+ // calculate new x position (in base level units)
+ 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( DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor ) const
+{
+ if( mnLevel <= 0 )
+ return -1;
+ if( mnLevel == 1 )
+ return mpLayouts[0]->GetTextBreak( nMaxWidth, nCharExtra, nFactor );
+
+ int nCharCount = mnEndCharPos - mnMinCharPos;
+ std::unique_ptr<DeviceCoordinate[]> const pCharWidths(new DeviceCoordinate[nCharCount]);
+ std::unique_ptr<DeviceCoordinate[]> const pFallbackCharWidths(new DeviceCoordinate[nCharCount]);
+ mpLayouts[0]->FillDXArray( pCharWidths.get() );
+
+ for( int n = 1; n < mnLevel; ++n )
+ {
+ SalLayout& rLayout = *mpLayouts[ n ];
+ rLayout.FillDXArray( pFallbackCharWidths.get() );
+ double fUnitMul = mnUnitsPerPixel;
+ fUnitMul /= rLayout.GetUnitsPerPixel();
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ if( pCharWidths[ i ] == 0 )
+ {
+ DeviceCoordinate w = pFallbackCharWidths[i];
+ w = static_cast<DeviceCoordinate>(w * fUnitMul + 0.5);
+ pCharWidths[ i ] = w;
+ }
+ }
+ }
+
+ DeviceCoordinate nWidth = 0;
+ for( int i = 0; i < nCharCount; ++i )
+ {
+ nWidth += pCharWidths[ i ] * nFactor;
+ if( nWidth > nMaxWidth )
+ return (i + mnMinCharPos);
+ nWidth += nCharExtra;
+ }
+
+ return -1;
+}
+
+DeviceCoordinate MultiSalLayout::FillDXArray( DeviceCoordinate* pCharWidths ) const
+{
+ DeviceCoordinate nMaxWidth = 0;
+
+ // prepare merging of fallback levels
+ std::unique_ptr<DeviceCoordinate[]> pTempWidths;
+ const int nCharCount = mnEndCharPos - mnMinCharPos;
+ if( pCharWidths )
+ {
+ for( int i = 0; i < nCharCount; ++i )
+ pCharWidths[i] = 0;
+ pTempWidths.reset(new DeviceCoordinate[nCharCount]);
+ }
+
+ for( int n = mnLevel; --n >= 0; )
+ {
+ // query every fallback level
+ DeviceCoordinate nTextWidth = mpLayouts[n]->FillDXArray( pTempWidths.get() );
+ if( !nTextWidth )
+ continue;
+ // merge results from current level
+ double fUnitMul = mnUnitsPerPixel;
+ fUnitMul /= mpLayouts[n]->GetUnitsPerPixel();
+ nTextWidth = static_cast<DeviceCoordinate>(nTextWidth * fUnitMul + 0.5);
+ if( nMaxWidth < nTextWidth )
+ nMaxWidth = nTextWidth;
+ if( !pCharWidths )
+ continue;
+ // 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;
+ DeviceCoordinate nCharWidth = pTempWidths[i];
+ if( !nCharWidth )
+ continue;
+ nCharWidth = static_cast<DeviceCoordinate>(nCharWidth * fUnitMul + 0.5);
+ pCharWidths[i] = nCharWidth;
+ }
+ }
+
+ return nMaxWidth;
+}
+
+void MultiSalLayout::GetCaretPositions( int nMaxIndex, long* pCaretXArray ) const
+{
+ SalLayout& rLayout = *mpLayouts[ 0 ];
+ rLayout.GetCaretPositions( nMaxIndex, pCaretXArray );
+
+ if( mnLevel > 1 )
+ {
+ std::unique_ptr<long[]> const pTempPos(new long[nMaxIndex]);
+ for( int n = 1; n < mnLevel; ++n )
+ {
+ mpLayouts[ n ]->GetCaretPositions( nMaxIndex, pTempPos.get() );
+ double fUnitMul = mnUnitsPerPixel;
+ fUnitMul /= mpLayouts[n]->GetUnitsPerPixel();
+ for( int i = 0; i < nMaxIndex; ++i )
+ if( pTempPos[i] >= 0 )
+ {
+ long w = pTempPos[i];
+ w = static_cast<long>(w*fUnitMul + 0.5);
+ pCaretXArray[i] = w;
+ }
+ }
+ }
+}
+
+bool MultiSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
+ Point& rPos, int& nStart,
+ const PhysicalFontFace** pFallbackFont,
+ int* const pFallbackLevel) 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();
+ const PhysicalFontFace* pFontFace = rLayout.GetFont().GetFontFace();
+ if (rLayout.GetNextGlyph(pGlyph, rPos, nStart))
+ {
+ int nFontTag = nLevel << GF_FONTSHIFT;
+ nStart |= nFontTag;
+ if (pFallbackFont)
+ *pFallbackFont = pFontFace;
+ if (pFallbackLevel)
+ *pFallbackLevel = nLevel;
+ rPos += maDrawBase;
+ rPos += maDrawOffset;
+ 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) const
+{
+ // Check the base layout
+ bool bValid = mpLayouts[0]->IsKashidaPosValid(nCharPos);
+
+ // 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))
+ {
+ bValid = mpLayouts[i]->IsKashidaPosValid(nCharPos);
+ break;
+ }
+ }
+ }
+
+ return bValid;
+}
+
+const SalLayoutGlyphs* SalLayout::GetGlyphs() const
+{
+ // No access to the glyphs by default.
+ return nullptr;
+}
+
+/* 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 000000000..a6a5e3627
--- /dev/null
+++ b/vcl/source/gdi/salmisc.cxx
@@ -0,0 +1,491 @@
+/* -*- 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/bitmapaccess.hxx>
+#include <vcl/salgtype.hxx>
+#include <bmpfast.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <tools/helpers.hxx>
+#include <memory>
+
+#define IMPL_CASE_GET_FORMAT( Format ) \
+case( ScanlineFormat::Format ): \
+ pFncGetPixel = BitmapReadAccess::GetPixelFor##Format; \
+break
+
+#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++; \
+}
+
+#define TC_TO_PAL_COLORS 4096
+
+static long ImplIndexFromColor( const BitmapColor& rCol )
+{
+#if TC_TO_PAL_COLORS == 4096
+
+ return( ( ( static_cast<long>(rCol.GetBlue()) >> 4) << 8 ) |
+ ( ( static_cast<long>(rCol.GetGreen()) >> 4 ) << 4 ) |
+ ( static_cast<long>(rCol.GetRed()) >> 4 ) );
+
+#elif TC_TO_PAL_COLORS == 32768
+
+ return( ( ( (long) rCol.GetBlue() >> 3) << 10 ) |
+ ( ( (long) rCol.GetGreen() >> 3 ) << 5 ) |
+ ( (long) rCol.GetRed() >> 3 ) );
+
+#endif
+}
+
+static void ImplPALToPAL( const BitmapBuffer& rSrcBuffer, BitmapBuffer& rDstBuffer,
+ FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel,
+ Scanline* pSrcScanMap, Scanline* pDstScanMap, long const * pMapX, const long* pMapY )
+{
+ const 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 (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (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, long const * pMapX, const long* pMapY )
+{
+ const 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 ] );
+ long nMapX;
+
+ for (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (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::N4BitMsnPal )
+ {
+ long nMapX;
+
+ for (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (long nX = 0; nX < rDstBuffer.mnWidth;)
+ {
+ nMapX = pMapX[ nX ];
+ pFncSetPixel( pDstScan, nX++,
+ pColBuf[ ( pSrcScan[ nMapX >> 1 ] >> ( nMapX & 1 ? 0 : 4 ) ) & 0x0f ],
+ rDstMask );
+ }
+
+ DOUBLE_SCANLINES();
+ }
+ }
+ else if( RemoveScanline( rSrcBuffer.mnFormat ) == ScanlineFormat::N8BitPal )
+ {
+ for (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ pFncSetPixel( pDstScan, nX, pColBuf[ pSrcScan[ pMapX[ nX ] ] ], rDstMask );
+
+ DOUBLE_SCANLINES();
+ }
+ }
+ else
+ {
+ for (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (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, long const * pMapX, const long* pMapY )
+{
+ const 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 (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (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 (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (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, long const * pMapX, const long* pMapY )
+{
+ const 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( long nR = 0; nR < 16; nR++ )
+ {
+ for( long nG = 0; nG < 16; nG++ )
+ {
+ for( 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 (long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY)
+ {
+ long nMapY = pMapY[nActY];
+ Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]);
+
+ for (long nX = 0; nX < rDstBuffer.mnWidth; ++nX)
+ {
+ aIndex.SetIndex( pColToPalMap[ ImplIndexFromColor( pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ) ) ] );
+ pFncSetPixel( pDstScan, nX, aIndex, rDstMask );
+ }
+
+ DOUBLE_SCANLINES();
+ }
+}
+
+std::unique_ptr<BitmapBuffer> StretchAndConvert(
+ const BitmapBuffer& rSrcBuffer, const SalTwoRect& rTwoRect,
+ ScanlineFormat nDstBitmapFormat, const BitmapPalette* pDstPal, const ColorMask* pDstMask )
+{
+ FncGetPixel pFncGetPixel;
+ FncSetPixel pFncSetPixel;
+ std::unique_ptr<BitmapBuffer> pDstBuffer(new BitmapBuffer);
+
+ // set function for getting pixels
+ switch( RemoveScanline( rSrcBuffer.mnFormat ) )
+ {
+ IMPL_CASE_GET_FORMAT( N1BitMsbPal );
+ IMPL_CASE_GET_FORMAT( N1BitLsbPal );
+ IMPL_CASE_GET_FORMAT( N4BitMsnPal );
+ IMPL_CASE_GET_FORMAT( N4BitLsnPal );
+ IMPL_CASE_GET_FORMAT( N8BitPal );
+ IMPL_CASE_GET_FORMAT( N8BitTcMask );
+ IMPL_CASE_GET_FORMAT( N24BitTcBgr );
+ IMPL_CASE_GET_FORMAT( N24BitTcRgb );
+ IMPL_CASE_GET_FORMAT( N32BitTcAbgr );
+ IMPL_CASE_GET_FORMAT( N32BitTcArgb );
+ IMPL_CASE_GET_FORMAT( N32BitTcBgra );
+ IMPL_CASE_GET_FORMAT( N32BitTcRgba );
+ IMPL_CASE_GET_FORMAT( N32BitTcMask );
+
+ default:
+ // should never come here
+ // initialize pFncGetPixel to something valid that is
+ // least likely to crash
+ pFncGetPixel = BitmapReadAccess::GetPixelForN1BitMsbPal;
+ OSL_FAIL( "unknown read format" );
+ break;
+ }
+
+ // set function for setting pixels
+ const ScanlineFormat nDstScanlineFormat = RemoveScanline( nDstBitmapFormat );
+ switch( nDstScanlineFormat )
+ {
+ IMPL_CASE_SET_FORMAT( N1BitMsbPal, 1 );
+ IMPL_CASE_SET_FORMAT( N1BitLsbPal, 1 );
+ IMPL_CASE_SET_FORMAT( N4BitMsnPal, 1 );
+ IMPL_CASE_SET_FORMAT( N4BitLsnPal, 4 );
+ IMPL_CASE_SET_FORMAT( N8BitPal, 8 );
+ IMPL_CASE_SET_FORMAT( N8BitTcMask, 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;
+ long nScanlineBase;
+ bool bFail = o3tl::checked_multiply<long>(pDstBuffer->mnBitCount, pDstBuffer->mnWidth, nScanlineBase);
+ if (bFail)
+ {
+ SAL_WARN("vcl.gdi", "checked multiply failed");
+ pDstBuffer->mpBits = nullptr;
+ return nullptr;
+ }
+ pDstBuffer->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
+ if (pDstBuffer->mnScanlineSize < nScanlineBase/8)
+ {
+ SAL_WARN("vcl.gdi", "scanline calculation wraparound");
+ pDstBuffer->mpBits = nullptr;
+ return nullptr;
+ }
+ try
+ {
+ pDstBuffer->mpBits = new sal_uInt8[ pDstBuffer->mnScanlineSize * pDstBuffer->mnHeight ];
+ }
+ catch( const std::bad_alloc& )
+ {
+ // memory exception, clean up
+ pDstBuffer->mpBits = nullptr;
+ return nullptr;
+ }
+
+ // do we need a destination palette or color mask?
+ if( ( nDstScanlineFormat == ScanlineFormat::N1BitMsbPal ) ||
+ ( nDstScanlineFormat == ScanlineFormat::N1BitLsbPal ) ||
+ ( nDstScanlineFormat == ScanlineFormat::N4BitMsnPal ) ||
+ ( nDstScanlineFormat == ScanlineFormat::N4BitLsnPal ) ||
+ ( nDstScanlineFormat == ScanlineFormat::N8BitPal ) )
+ {
+ assert(pDstPal && "destination buffer requires palette");
+ if (!pDstPal)
+ {
+ return nullptr;
+ }
+ pDstBuffer->maPalette = *pDstPal;
+ }
+ else if( ( nDstScanlineFormat == ScanlineFormat::N8BitTcMask ) ||
+ ( nDstScanlineFormat == ScanlineFormat::N32BitTcMask ) )
+ {
+ assert(pDstMask && "destination buffer requires color mask");
+ if (!pDstMask)
+ {
+ return nullptr;
+ }
+ 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<long[]> pMapX;
+ std::unique_ptr<long[]> pMapY;
+
+ try
+ {
+ pSrcScan.reset(new Scanline[rSrcBuffer.mnHeight]);
+ pDstScan.reset(new Scanline[pDstBuffer->mnHeight]);
+ pMapX.reset(new long[pDstBuffer->mnWidth]);
+ pMapY.reset(new long[pDstBuffer->mnHeight]);
+ }
+ catch( const std::bad_alloc& )
+ {
+ // memory exception, clean up
+ // remark: the buffer ptr causing the exception
+ // is still NULL here
+ return nullptr;
+ }
+
+ // horizontal mapping table
+ if( (pDstBuffer->mnWidth != rTwoRect.mnSrcWidth) && (pDstBuffer->mnWidth != 0) )
+ {
+ const double fFactorX = static_cast<double>(rTwoRect.mnSrcWidth) / pDstBuffer->mnWidth;
+
+ for (long i = 0; i < pDstBuffer->mnWidth; ++i)
+ pMapX[ i ] = rTwoRect.mnSrcX + static_cast<int>( i * fFactorX );
+ }
+ else
+ {
+ for (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 (long i = 0; i < pDstBuffer->mnHeight; ++i)
+ pMapY[ i ] = rTwoRect.mnSrcY + static_cast<int>( i * fFactorY );
+ }
+ else
+ {
+ for (long i = 0, nTmp = rTwoRect.mnSrcY; i < pDstBuffer->mnHeight; ++i)
+ pMapY[ i ] = nTmp++;
+ }
+
+ // source scanline buffer
+ Scanline pTmpScan;
+ 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 (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 (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 000000000..b18816cac
--- /dev/null
+++ b/vcl/source/gdi/scrptrun.cxx
@@ -0,0 +1,247 @@
+/*
+ *******************************************************************************
+ *
+ * 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 <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;
+ }
+
+};
+
+// 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.
+UScriptCode getScript(UChar32 ch, UErrorCode* status)
+{
+ UScriptCode script = uscript_getScript(ch, status);
+ if (U_FAILURE(*status))
+ return script;
+ if (script == USCRIPT_KATAKANA || script == USCRIPT_KATAKANA_OR_HIRAGANA)
+ return USCRIPT_HIRAGANA;
+ return script;
+}
+
+}
+
+static 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 (high >= 0xD800 && high <= 0xDBFF && scriptEnd < charLimit - 1)
+ {
+ UChar low = charArray[scriptEnd + 1];
+
+ // if it is followed by a low surrogate,
+ // consume it and form the full character
+ if (low >= 0xDC00 && low <= 0xDFFF) {
+ ch = (high - 0xD800) * 0x0400 + low - 0xDC00 + 0x10000;
+ 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/svmconverter.cxx b/vcl/source/gdi/svmconverter.cxx
new file mode 100644
index 000000000..d65b6c52f
--- /dev/null
+++ b/vcl/source/gdi/svmconverter.cxx
@@ -0,0 +1,1245 @@
+/* -*- 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 <algorithm>
+#include <string.h>
+
+#include <o3tl/safeint.hxx>
+#include <osl/thread.h>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metaact.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <TypeSerializer.hxx>
+#include <svmconverter.hxx>
+#include <memory>
+#include <stack>
+
+// Inlines
+static void ImplReadRect( SvStream& rIStm, tools::Rectangle& rRect )
+{
+ Point aTL;
+ Point aBR;
+
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(aTL);
+ aSerializer.readPoint(aBR);
+
+ rRect = tools::Rectangle( aTL, aBR );
+}
+
+static bool ImplReadPoly(SvStream& rIStm, tools::Polygon& rPoly)
+{
+ TypeSerializer aSerializer(rIStm);
+
+ sal_Int32 nSize32(0);
+ rIStm.ReadInt32(nSize32);
+ sal_uInt16 nSize = nSize32;
+
+ const size_t nMaxPossiblePoints = rIStm.remainingSize() / 2 * sizeof(sal_Int32);
+ if (nSize > nMaxPossiblePoints)
+ {
+ SAL_WARN("vcl.gdi", "svm record claims to have: " << nSize << " points, but only " << nMaxPossiblePoints << " possible");
+ return false;
+ }
+
+ rPoly = tools::Polygon(nSize);
+
+ for (sal_uInt16 i = 0; i < nSize && rIStm.good(); ++i)
+ {
+ aSerializer.readPoint(rPoly[i]);
+ }
+ return rIStm.good();
+}
+
+static bool ImplReadPolyPoly(SvStream& rIStm, tools::PolyPolygon& rPolyPoly)
+{
+ bool bSuccess = true;
+
+ tools::Polygon aPoly;
+ sal_Int32 nPolyCount32(0);
+ rIStm.ReadInt32(nPolyCount32);
+ sal_uInt16 nPolyCount = static_cast<sal_uInt16>(nPolyCount32);
+
+ for (sal_uInt16 i = 0; i < nPolyCount && rIStm.good(); ++i)
+ {
+ if (!ImplReadPoly(rIStm, aPoly))
+ {
+ bSuccess = false;
+ break;
+ }
+ rPolyPoly.Insert(aPoly);
+ }
+
+ return bSuccess && rIStm.good();
+}
+
+static void ImplReadColor( SvStream& rIStm, Color& rColor )
+{
+ sal_Int16 nVal(0);
+
+ rIStm.ReadInt16( nVal ); rColor.SetRed( sal::static_int_cast<sal_uInt8>(static_cast<sal_uInt16>(nVal) >> 8) );
+ rIStm.ReadInt16( nVal ); rColor.SetGreen( sal::static_int_cast<sal_uInt8>(static_cast<sal_uInt16>(nVal) >> 8) );
+ rIStm.ReadInt16( nVal ); rColor.SetBlue( sal::static_int_cast<sal_uInt8>(static_cast<sal_uInt16>(nVal) >> 8) );
+}
+
+static bool ImplReadMapMode(SvStream& rIStm, MapMode& rMapMode)
+{
+ sal_Int16 nUnit(0);
+ rIStm.ReadInt16(nUnit);
+
+ Point aOrg;
+ TypeSerializer aSerializer(rIStm);
+ aSerializer.readPoint(aOrg);
+
+ sal_Int32 nXNum(0), nXDenom(0), nYNum(0), nYDenom(0);
+ rIStm.ReadInt32(nXNum).ReadInt32(nXDenom).ReadInt32(nYNum).ReadInt32(nYDenom);
+
+ if (!rIStm.good() || nXDenom <= 0 || nYDenom <= 0 || nXNum <= 0 || nYNum <= 0)
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: invalid mapmode fraction");
+ return false;
+ }
+
+ if (nUnit < sal_Int16(MapUnit::Map100thMM) || nUnit > sal_Int16(MapUnit::LAST))
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: invalid mapmode");
+ return false;
+ }
+
+ rMapMode = MapMode(static_cast<MapUnit>(nUnit), aOrg, Fraction(nXNum, nXDenom), Fraction(nYNum, nYDenom));
+
+ return true;
+}
+
+static void ImplReadUnicodeComment( sal_uInt32 nStrmPos, SvStream& rIStm, OUString& rString )
+{
+ sal_uInt32 nOld = rIStm.Tell();
+ if ( nStrmPos )
+ {
+ sal_uInt16 nType;
+ sal_uInt32 nActionSize;
+ std::size_t nStringLen;
+
+ rIStm.Seek( nStrmPos );
+ rIStm .ReadUInt16( nType )
+ .ReadUInt32( nActionSize );
+
+ nStringLen = (nActionSize - 4) >> 1;
+
+ if ( nStringLen && ( nType == GDI_UNICODE_COMMENT ) )
+ rString = read_uInt16s_ToOUString(rIStm, nStringLen);
+ }
+ rIStm.Seek( nOld );
+}
+
+static void ImplSkipActions(SvStream& rIStm, sal_uLong nSkipCount)
+{
+ sal_Int32 nActionSize;
+ sal_Int16 nType;
+ for (sal_uLong i = 0; i < nSkipCount; ++i)
+ {
+ rIStm.ReadInt16(nType).ReadInt32(nActionSize);
+ if (!rIStm.good() || nActionSize < 4)
+ break;
+ rIStm.SeekRel(nActionSize - 4);
+ }
+}
+
+static void ImplReadExtendedPolyPolygonAction(SvStream& rIStm, tools::PolyPolygon& rPolyPoly)
+{
+ TypeSerializer aSerializer(rIStm);
+
+ rPolyPoly.Clear();
+ sal_uInt16 nPolygonCount(0);
+ rIStm.ReadUInt16( nPolygonCount );
+
+ if (!nPolygonCount)
+ return;
+
+ const size_t nMinRecordSize = sizeof(sal_uInt16);
+ const size_t nMaxRecords = rIStm.remainingSize() / nMinRecordSize;
+ if (nPolygonCount > nMaxRecords)
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: " << nMaxRecords <<
+ " max possible entries, but " << nPolygonCount << " claimed, truncating");
+ nPolygonCount = nMaxRecords;
+ }
+
+ for(sal_uInt16 a(0); a < nPolygonCount; a++)
+ {
+ sal_uInt16 nPointCount(0);
+ rIStm.ReadUInt16(nPointCount);
+
+ const size_t nMinPolygonSize = sizeof(sal_Int32) * 2;
+ const size_t nMaxPolygons = rIStm.remainingSize() / nMinPolygonSize;
+ if (nPointCount > nMaxPolygons)
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: " << nMaxPolygons <<
+ " max possible entries, but " << nPointCount << " claimed, truncating");
+ nPointCount = nMaxPolygons;
+ }
+
+ tools::Polygon aCandidate(nPointCount);
+
+ if (nPointCount)
+ {
+ for(sal_uInt16 b(0); b < nPointCount; b++)
+ {
+ aSerializer.readPoint(aCandidate[b]);
+ }
+
+ sal_uInt8 bHasFlags(int(false));
+ rIStm.ReadUChar( bHasFlags );
+
+ if(bHasFlags)
+ {
+ sal_uInt8 aPolyFlags(0);
+
+ for(sal_uInt16 c(0); c < nPointCount; c++)
+ {
+ rIStm.ReadUChar( aPolyFlags );
+ aCandidate.SetFlags(c, static_cast<PolyFlags>(aPolyFlags));
+ }
+ }
+ }
+
+ rPolyPoly.Insert(aCandidate);
+ }
+}
+
+SVMConverter::SVMConverter( SvStream& rStm, GDIMetaFile& rMtf )
+{
+ if( !rStm.GetError() )
+ {
+ ImplConvertFromSVM1( rStm, rMtf );
+ }
+}
+
+namespace
+{
+ sal_Int32 SkipActions(sal_Int32 i, sal_Int32 nFollowingActionCount, sal_Int32 nActions)
+ {
+ sal_Int32 remainingActions = nActions - i;
+ if (nFollowingActionCount < 0)
+ nFollowingActionCount = remainingActions;
+ return std::min(remainingActions, nFollowingActionCount);
+ }
+}
+
+#define LF_FACESIZE 32
+
+void static lcl_error( SvStream& rIStm, const SvStreamEndian& nOldFormat, sal_uLong nPos)
+{
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ rIStm.SetEndian(nOldFormat);
+ rIStm.Seek(nPos);
+ return;
+}
+void SVMConverter::ImplConvertFromSVM1( SvStream& rIStm, GDIMetaFile& rMtf )
+{
+ const sal_uLong nPos = rIStm.Tell();
+ const SvStreamEndian nOldFormat = rIStm.GetEndian();
+
+ rIStm.SetEndian( SvStreamEndian::LITTLE );
+
+ char aCode[ 5 ];
+ Size aPrefSz;
+
+ // read header
+ rIStm.ReadBytes(aCode, sizeof(aCode)); // Identifier
+ sal_Int16 nSize(0);
+ rIStm.ReadInt16( nSize ); // Size
+ sal_Int16 nVersion(0);
+ rIStm.ReadInt16( nVersion ); // Version
+ sal_Int32 nTmp32(0);
+ rIStm.ReadInt32( nTmp32 );
+ if (nTmp32 < 0)
+ {
+ SAL_WARN("vcl.gdi", "svm: value for width should be positive");
+ lcl_error(rIStm, nOldFormat, nPos);
+ return;
+ }
+ aPrefSz.setWidth( nTmp32 ); // PrefSize.Width()
+ rIStm.ReadInt32( nTmp32 );
+ if (nTmp32 < 0)
+ {
+ SAL_WARN("vcl.gdi", "svm: value for height should be positive");
+ lcl_error(rIStm, nOldFormat, nPos);
+ return;
+ }
+ aPrefSz.setHeight( nTmp32 ); // PrefSize.Height()
+
+ // check header-magic and version
+ if( rIStm.GetError()
+ || ( memcmp( aCode, "SVGDI", sizeof( aCode ) ) != 0 )
+ || ( nVersion != 200 ) )
+ {
+ SAL_WARN("vcl.gdi", "svm: wrong check for header-magic and version");
+ lcl_error(rIStm, nOldFormat, nPos);
+ return;
+ }
+
+ LineInfo aLineInfo( LineStyle::NONE, 0 );
+ std::stack<std::unique_ptr<LineInfo>> aLIStack;
+ ScopedVclPtrInstance< VirtualDevice > aFontVDev;
+ rtl_TextEncoding eActualCharSet = osl_getThreadTextEncoding();
+ bool bFatLine = false;
+
+ tools::Polygon aActionPoly;
+ tools::Rectangle aRect;
+ Point aPt, aPt1;
+ Size aSz;
+ Color aActionColor;
+
+ sal_uInt32 nUnicodeCommentStreamPos = 0;
+ sal_Int32 nUnicodeCommentActionNumber = 0;
+
+ rMtf.SetPrefSize(aPrefSz);
+
+ MapMode aMapMode;
+ if (ImplReadMapMode(rIStm, aMapMode)) // MapMode
+ rMtf.SetPrefMapMode(aMapMode);
+
+ sal_Int32 nActions(0);
+ rIStm.ReadInt32(nActions); // Action count
+ if (nActions < 0)
+ {
+ SAL_WARN("vcl.gdi", "svm claims negative action count (" << nActions << ")");
+ nActions = 0;
+ }
+
+ const size_t nMinActionSize = sizeof(sal_uInt16) + sizeof(sal_Int32);
+ const size_t nMaxPossibleActions = rIStm.remainingSize() / nMinActionSize;
+ if (o3tl::make_unsigned(nActions) > nMaxPossibleActions)
+ {
+ SAL_WARN("vcl.gdi", "svm claims more actions (" << nActions << ") than stream could provide, truncating");
+ nActions = nMaxPossibleActions;
+ }
+
+ size_t nLastPolygonAction(0);
+
+ TypeSerializer aSerializer(rIStm);
+
+ for (sal_Int32 i = 0; i < nActions && rIStm.good(); ++i)
+ {
+ sal_Int16 nType(0);
+ rIStm.ReadInt16(nType);
+ sal_Int32 nActBegin = rIStm.Tell();
+ sal_Int32 nActionSize(0);
+ rIStm.ReadInt32(nActionSize);
+
+ SAL_WARN_IF( ( nType > 33 ) && ( nType < 1024 ), "vcl.gdi", "Unknown GDIMetaAction while converting!" );
+
+ switch( nType )
+ {
+ case GDI_PIXEL_ACTION:
+ {
+ aSerializer.readPoint(aPt);
+ ImplReadColor( rIStm, aActionColor );
+ rMtf.AddAction( new MetaPixelAction( aPt, aActionColor ) );
+ }
+ break;
+
+ case GDI_POINT_ACTION:
+ {
+ aSerializer.readPoint(aPt);
+ rMtf.AddAction( new MetaPointAction( aPt ) );
+ }
+ break;
+
+ case GDI_LINE_ACTION:
+ {
+ aSerializer.readPoint(aPt);
+ aSerializer.readPoint(aPt1);
+ rMtf.AddAction( new MetaLineAction( aPt, aPt1, aLineInfo ) );
+ }
+ break;
+
+ case GDI_LINEJOIN_ACTION :
+ {
+ sal_Int16 nLineJoin(0);
+ rIStm.ReadInt16( nLineJoin );
+ aLineInfo.SetLineJoin(static_cast<basegfx::B2DLineJoin>(nLineJoin));
+ }
+ break;
+
+ case GDI_LINECAP_ACTION :
+ {
+ sal_Int16 nLineCap(0);
+ rIStm.ReadInt16( nLineCap );
+ aLineInfo.SetLineCap(static_cast<css::drawing::LineCap>(nLineCap));
+ }
+ break;
+
+ case GDI_LINEDASHDOT_ACTION :
+ {
+ sal_Int16 a(0);
+ sal_Int32 b(0);
+
+ rIStm.ReadInt16( a ); aLineInfo.SetDashCount(a);
+ rIStm.ReadInt32( b ); aLineInfo.SetDashLen(b);
+ rIStm.ReadInt16( a ); aLineInfo.SetDotCount(a);
+ rIStm.ReadInt32( b ); aLineInfo.SetDotLen(b);
+ rIStm.ReadInt32( b ); aLineInfo.SetDistance(b);
+
+ if(((aLineInfo.GetDashCount() && aLineInfo.GetDashLen())
+ || (aLineInfo.GetDotCount() && aLineInfo.GetDotLen()))
+ && aLineInfo.GetDistance())
+ {
+ aLineInfo.SetStyle(LineStyle::Dash);
+ }
+ }
+ break;
+
+ case GDI_EXTENDEDPOLYGON_ACTION :
+ {
+ // read the tools::PolyPolygon in every case
+ tools::PolyPolygon aInputPolyPolygon;
+ ImplReadExtendedPolyPolygonAction(rIStm, aInputPolyPolygon);
+
+ // now check if it can be set somewhere
+ if(nLastPolygonAction < rMtf.GetActionSize())
+ {
+ MetaPolyLineAction* pPolyLineAction = dynamic_cast< MetaPolyLineAction* >(rMtf.GetAction(nLastPolygonAction));
+
+ if(pPolyLineAction)
+ {
+ // replace MetaPolyLineAction when we have a single polygon. Do not rely on the
+ // same point count; the originally written GDI_POLYLINE_ACTION may have been
+ // Subdivided for better quality for older usages
+ if(1 == aInputPolyPolygon.Count())
+ {
+ rMtf.ReplaceAction(
+ new MetaPolyLineAction(
+ aInputPolyPolygon.GetObject(0),
+ pPolyLineAction->GetLineInfo()),
+ nLastPolygonAction);
+ }
+ }
+ else
+ {
+ MetaPolyPolygonAction* pPolyPolygonAction = dynamic_cast< MetaPolyPolygonAction* >(rMtf.GetAction(nLastPolygonAction));
+
+ if(pPolyPolygonAction)
+ {
+ // replace MetaPolyPolygonAction when we have a curved polygon. Do rely on the
+ // same sub-polygon count
+ if(pPolyPolygonAction->GetPolyPolygon().Count() == aInputPolyPolygon.Count())
+ {
+ rMtf.ReplaceAction(
+ new MetaPolyPolygonAction(
+ aInputPolyPolygon),
+ nLastPolygonAction);
+ }
+ }
+ else
+ {
+ MetaPolygonAction* pPolygonAction = dynamic_cast< MetaPolygonAction* >(rMtf.GetAction(nLastPolygonAction));
+
+ if(pPolygonAction)
+ {
+ // replace MetaPolygonAction
+ if(1 == aInputPolyPolygon.Count())
+ {
+ rMtf.ReplaceAction(
+ new MetaPolygonAction(
+ aInputPolyPolygon.GetObject(0)),
+ nLastPolygonAction);
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case GDI_RECT_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+ sal_Int32 nTmp(0), nTmp1(0);
+ rIStm.ReadInt32( nTmp ).ReadInt32( nTmp1 );
+
+ if( nTmp || nTmp1 )
+ rMtf.AddAction( new MetaRoundRectAction( aRect, nTmp, nTmp1 ) );
+ else
+ {
+ rMtf.AddAction( new MetaRectAction( aRect ) );
+
+ if( bFatLine )
+ rMtf.AddAction( new MetaPolyLineAction( aRect, aLineInfo ) );
+ }
+ }
+ break;
+
+ case GDI_ELLIPSE_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+
+ if( bFatLine )
+ {
+ const tools::Polygon aPoly( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
+
+ rMtf.AddAction( new MetaPushAction( PushFlags::LINECOLOR ) );
+ rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) );
+ rMtf.AddAction( new MetaPolygonAction( aPoly ) );
+ rMtf.AddAction( new MetaPopAction() );
+ rMtf.AddAction( new MetaPolyLineAction( aPoly, aLineInfo ) );
+ }
+ else
+ rMtf.AddAction( new MetaEllipseAction( aRect ) );
+ }
+ break;
+
+ case GDI_ARC_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+ aSerializer.readPoint(aPt);
+ aSerializer.readPoint(aPt1);
+
+ if( bFatLine )
+ {
+ const tools::Polygon aPoly( aRect, aPt, aPt1, PolyStyle::Arc );
+
+ rMtf.AddAction( new MetaPushAction( PushFlags::LINECOLOR ) );
+ rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) );
+ rMtf.AddAction( new MetaPolygonAction( aPoly ) );
+ rMtf.AddAction( new MetaPopAction() );
+ rMtf.AddAction( new MetaPolyLineAction( aPoly, aLineInfo ) );
+ }
+ else
+ rMtf.AddAction( new MetaArcAction( aRect, aPt, aPt1 ) );
+ }
+ break;
+
+ case GDI_PIE_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+ aSerializer.readPoint(aPt);
+ aSerializer.readPoint(aPt1);
+
+ if( bFatLine )
+ {
+ const tools::Polygon aPoly( aRect, aPt, aPt1, PolyStyle::Pie );
+
+ rMtf.AddAction( new MetaPushAction( PushFlags::LINECOLOR ) );
+ rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) );
+ rMtf.AddAction( new MetaPolygonAction( aPoly ) );
+ rMtf.AddAction( new MetaPopAction() );
+ rMtf.AddAction( new MetaPolyLineAction( aPoly, aLineInfo ) );
+ }
+ else
+ rMtf.AddAction( new MetaPieAction( aRect, aPt, aPt1 ) );
+ }
+ break;
+
+ case GDI_INVERTRECT_ACTION:
+ case GDI_HIGHLIGHTRECT_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+ rMtf.AddAction( new MetaPushAction( PushFlags::RASTEROP ) );
+ rMtf.AddAction( new MetaRasterOpAction( RasterOp::Invert ) );
+ rMtf.AddAction( new MetaRectAction( aRect ) );
+ rMtf.AddAction( new MetaPopAction() );
+ }
+ break;
+
+ case GDI_POLYLINE_ACTION:
+ {
+ if (ImplReadPoly(rIStm, aActionPoly))
+ {
+ nLastPolygonAction = rMtf.GetActionSize();
+
+ if( bFatLine )
+ rMtf.AddAction( new MetaPolyLineAction( aActionPoly, aLineInfo ) );
+ else
+ rMtf.AddAction( new MetaPolyLineAction( aActionPoly ) );
+ }
+ }
+ break;
+
+ case GDI_POLYGON_ACTION:
+ {
+ if (ImplReadPoly(rIStm, aActionPoly))
+ {
+ if( bFatLine )
+ {
+ rMtf.AddAction( new MetaPushAction( PushFlags::LINECOLOR ) );
+ rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) );
+ rMtf.AddAction( new MetaPolygonAction( aActionPoly ) );
+ rMtf.AddAction( new MetaPopAction() );
+ rMtf.AddAction( new MetaPolyLineAction( aActionPoly, aLineInfo ) );
+ }
+ else
+ {
+ nLastPolygonAction = rMtf.GetActionSize();
+ rMtf.AddAction( new MetaPolygonAction( aActionPoly ) );
+ }
+ }
+ }
+ break;
+
+ case GDI_POLYPOLYGON_ACTION:
+ {
+ tools::PolyPolygon aPolyPoly;
+
+ if (ImplReadPolyPoly(rIStm, aPolyPoly))
+ {
+ if( bFatLine )
+ {
+ rMtf.AddAction( new MetaPushAction( PushFlags::LINECOLOR ) );
+ rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) );
+ rMtf.AddAction( new MetaPolyPolygonAction( aPolyPoly ) );
+ rMtf.AddAction( new MetaPopAction() );
+
+ for( sal_uInt16 nPoly = 0, nCount = aPolyPoly.Count(); nPoly < nCount; nPoly++ )
+ rMtf.AddAction( new MetaPolyLineAction( aPolyPoly[ nPoly ], aLineInfo ) );
+ }
+ else
+ {
+ nLastPolygonAction = rMtf.GetActionSize();
+ rMtf.AddAction( new MetaPolyPolygonAction( aPolyPoly ) );
+ }
+ }
+ }
+ break;
+
+ case GDI_FONT_ACTION:
+ {
+ vcl::Font aFont;
+ char aName[LF_FACESIZE+1];
+
+ ImplReadColor( rIStm, aActionColor ); aFont.SetColor( aActionColor );
+ ImplReadColor( rIStm, aActionColor ); aFont.SetFillColor( aActionColor );
+ size_t nRet = rIStm.ReadBytes(aName, LF_FACESIZE);
+ aName[nRet] = 0;
+ aFont.SetFamilyName( OUString( aName, strlen(aName), rIStm.GetStreamCharSet() ) );
+
+ sal_Int32 nWidth(0), nHeight(0);
+ rIStm.ReadInt32(nWidth).ReadInt32(nHeight);
+ sal_Int16 nCharOrient(0), nLineOrient(0);
+ rIStm.ReadInt16(nCharOrient).ReadInt16(nLineOrient);
+ sal_Int16 nCharSet(0), nFamily(0), nPitch(0), nAlign(0), nWeight(0), nUnderline(0), nStrikeout(0);
+ rIStm.ReadInt16(nCharSet).ReadInt16(nFamily).ReadInt16(nPitch).ReadInt16(nAlign).ReadInt16(nWeight).ReadInt16(nUnderline).ReadInt16(nStrikeout);
+ bool bItalic(false), bOutline(false), bShadow(false), bTransparent(false);
+ rIStm.ReadCharAsBool(bItalic).ReadCharAsBool(bOutline).ReadCharAsBool(bShadow).ReadCharAsBool(bTransparent);
+
+ aFont.SetFontSize( Size( nWidth, nHeight ) );
+ aFont.SetCharSet( static_cast<rtl_TextEncoding>(nCharSet) );
+ aFont.SetFamily( static_cast<FontFamily>(nFamily) );
+ aFont.SetPitch( static_cast<FontPitch>(nPitch) );
+ aFont.SetAlignment( static_cast<FontAlign>(nAlign) );
+ aFont.SetWeight( ( nWeight == 1 ) ? WEIGHT_LIGHT : ( nWeight == 2 ) ? WEIGHT_NORMAL :
+ ( nWeight == 3 ) ? WEIGHT_BOLD : WEIGHT_DONTKNOW );
+ aFont.SetUnderline( static_cast<FontLineStyle>(nUnderline) );
+ aFont.SetStrikeout( static_cast<FontStrikeout>(nStrikeout) );
+ aFont.SetItalic( bItalic ? ITALIC_NORMAL : ITALIC_NONE );
+ aFont.SetOutline( bOutline );
+ aFont.SetShadow( bShadow );
+ aFont.SetOrientation( nLineOrient );
+ aFont.SetTransparent( bTransparent );
+
+ eActualCharSet = aFont.GetCharSet();
+ if ( eActualCharSet == RTL_TEXTENCODING_DONTKNOW )
+ eActualCharSet = osl_getThreadTextEncoding();
+
+ rMtf.AddAction( new MetaFontAction( aFont ) );
+ rMtf.AddAction( new MetaTextAlignAction( aFont.GetAlignment() ) );
+ rMtf.AddAction( new MetaTextColorAction( aFont.GetColor() ) );
+ rMtf.AddAction( new MetaTextFillColorAction( aFont.GetFillColor(), !aFont.IsTransparent() ) );
+
+ // #106172# Track font relevant data in shadow VDev
+ aFontVDev->SetFont( aFont );
+ }
+ break;
+
+ case GDI_TEXT_ACTION:
+ {
+ sal_Int32 nIndex(0), nLen(0), nTmp(0);
+ aSerializer.readPoint(aPt);
+ rIStm.ReadInt32( nIndex ).ReadInt32( nLen ).ReadInt32( nTmp );
+ if (nTmp > 0)
+ {
+ OString aByteStr = read_uInt8s_ToOString(rIStm, nTmp);
+ sal_uInt8 nTerminator = 0;
+ rIStm.ReadUChar( nTerminator );
+ SAL_WARN_IF( nTerminator != 0, "vcl.gdi", "expected string to be NULL terminated" );
+
+ OUString aStr(OStringToOUString(aByteStr, eActualCharSet));
+ if ( nUnicodeCommentActionNumber == i )
+ ImplReadUnicodeComment( nUnicodeCommentStreamPos, rIStm, aStr );
+ rMtf.AddAction( new MetaTextAction( aPt, aStr, nIndex, nLen ) );
+ }
+
+ if (nActionSize < 24)
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ else
+ rIStm.Seek(nActBegin + nActionSize);
+ }
+ break;
+
+ case GDI_TEXTARRAY_ACTION:
+ {
+ sal_Int32 nIndex(0), nLen(0), nAryLen(0), nTmp(0);
+ aSerializer.readPoint(aPt);
+ rIStm.ReadInt32( nIndex ).ReadInt32( nLen ).ReadInt32( nTmp ).ReadInt32( nAryLen );
+ if (nTmp > 0)
+ {
+ OString aByteStr = read_uInt8s_ToOString(rIStm, nTmp);
+ sal_uInt8 nTerminator = 0;
+ rIStm.ReadUChar( nTerminator );
+ SAL_WARN_IF( nTerminator != 0, "vcl.gdi", "expected string to be NULL terminated" );
+
+ OUString aStr(OStringToOUString(aByteStr, eActualCharSet));
+
+ std::unique_ptr<long[]> pDXAry;
+ sal_Int32 nDXAryLen = 0;
+ if (nAryLen > 0)
+ {
+ const size_t nMinRecordSize = sizeof(sal_Int32);
+ const size_t nMaxRecords = rIStm.remainingSize() / nMinRecordSize;
+ if (o3tl::make_unsigned(nAryLen) > nMaxRecords)
+ {
+ SAL_WARN("vcl.gdi", "Parsing error: " << nMaxRecords <<
+ " max possible entries, but " << nAryLen << " claimed, truncating");
+ nAryLen = nMaxRecords;
+ }
+
+ sal_Int32 nStrLen( aStr.getLength() );
+
+ nDXAryLen = std::max(nAryLen, nStrLen);
+
+ if (nDXAryLen < nLen)
+ {
+ //MetaTextArrayAction ctor expects pDXAry to be >= nLen if set, so if this can't
+ //be achieved, don't read it, it's utterly broken.
+ SAL_WARN("vcl.gdi", "dxary too short, discarding completely");
+ rIStm.SeekRel(sizeof(sal_Int32) * nDXAryLen);
+ nLen = 0;
+ nIndex = 0;
+ }
+ else
+ {
+ pDXAry.reset(new long[nDXAryLen]);
+
+ for (sal_Int32 j = 0; j < nAryLen; ++j)
+ {
+ rIStm.ReadInt32( nTmp );
+ pDXAry[ j ] = nTmp;
+ }
+
+ // #106172# Add last DX array elem, if missing
+ if( nAryLen != nStrLen )
+ {
+ if (nAryLen+1 == nStrLen && nIndex >= 0)
+ {
+ std::unique_ptr<long[]> pTmpAry(new long[nStrLen]);
+
+ aFontVDev->GetTextArray( aStr, pTmpAry.get(), nIndex, nLen );
+
+ // now, the difference between the
+ // last and the second last DX array
+ // is the advancement for the last
+ // glyph. Thus, to complete our meta
+ // action's DX array, just add that
+ // difference to last elem and store
+ // in very last.
+ if( nStrLen > 1 )
+ pDXAry[ nStrLen-1 ] = pDXAry[ nStrLen-2 ] + pTmpAry[ nStrLen-1 ] - pTmpAry[ nStrLen-2 ];
+ else
+ pDXAry[ nStrLen-1 ] = pTmpAry[ nStrLen-1 ]; // len=1: 0th position taken to be 0
+ }
+#ifdef DBG_UTIL
+ else
+ OSL_FAIL("More than one DX array element missing on SVM import");
+#endif
+ }
+ }
+ }
+ if ( nUnicodeCommentActionNumber == i )
+ ImplReadUnicodeComment( nUnicodeCommentStreamPos, rIStm, aStr );
+ rMtf.AddAction( new MetaTextArrayAction( aPt, aStr, pDXAry.get(), nIndex, nLen ) );
+ }
+
+ if (nActionSize < 24)
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ else
+ rIStm.Seek(nActBegin + nActionSize);
+ }
+ break;
+
+ case GDI_STRETCHTEXT_ACTION:
+ {
+ sal_Int32 nIndex(0), nLen(0), nWidth(0), nTmp(0);
+
+ aSerializer.readPoint(aPt);
+ rIStm.ReadInt32( nIndex ).ReadInt32( nLen ).ReadInt32( nTmp ).ReadInt32( nWidth );
+ if (nTmp > 0)
+ {
+ OString aByteStr = read_uInt8s_ToOString(rIStm, nTmp);
+ sal_uInt8 nTerminator = 0;
+ rIStm.ReadUChar( nTerminator );
+ SAL_WARN_IF( nTerminator != 0, "vcl.gdi", "expected string to be NULL terminated" );
+
+ OUString aStr(OStringToOUString(aByteStr, eActualCharSet));
+ if ( nUnicodeCommentActionNumber == i )
+ ImplReadUnicodeComment( nUnicodeCommentStreamPos, rIStm, aStr );
+ rMtf.AddAction( new MetaStretchTextAction( aPt, nWidth, aStr, nIndex, nLen ) );
+ }
+
+ if (nActionSize < 28)
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ else
+ rIStm.Seek(nActBegin + nActionSize);
+ }
+ break;
+
+ case GDI_BITMAP_ACTION:
+ {
+ Bitmap aBmp;
+
+ aSerializer.readPoint(aPt);
+ ReadDIB(aBmp, rIStm, true);
+ rMtf.AddAction( new MetaBmpAction( aPt, aBmp ) );
+ }
+ break;
+
+ case GDI_BITMAPSCALE_ACTION:
+ {
+ Bitmap aBmp;
+
+ aSerializer.readPoint(aPt);
+ aSerializer.readSize(aSz);
+ ReadDIB(aBmp, rIStm, true);
+ rMtf.AddAction( new MetaBmpScaleAction( aPt, aSz, aBmp ) );
+ }
+ break;
+
+ case GDI_BITMAPSCALEPART_ACTION:
+ {
+ Bitmap aBmp;
+ Size aSz2;
+
+ aSerializer.readPoint(aPt);
+ aSerializer.readSize(aSz);
+ aSerializer.readPoint(aPt1);
+ aSerializer.readSize(aSz2);
+ ReadDIB(aBmp, rIStm, true);
+ rMtf.AddAction( new MetaBmpScalePartAction( aPt, aSz, aPt1, aSz2, aBmp ) );
+ }
+ break;
+
+ case GDI_PEN_ACTION:
+ {
+ sal_Int32 nPenWidth;
+ sal_Int16 nPenStyle;
+
+ ImplReadColor( rIStm, aActionColor );
+ rIStm.ReadInt32( nPenWidth ).ReadInt16( nPenStyle );
+
+ aLineInfo.SetStyle( nPenStyle ? LineStyle::Solid : LineStyle::NONE );
+ aLineInfo.SetWidth( nPenWidth );
+ bFatLine = nPenStyle && !aLineInfo.IsDefault();
+
+ rMtf.AddAction( new MetaLineColorAction( aActionColor, nPenStyle != 0 ) );
+ }
+ break;
+
+ case GDI_FILLBRUSH_ACTION:
+ {
+ sal_Int16 nBrushStyle;
+
+ ImplReadColor( rIStm, aActionColor );
+ rIStm.SeekRel( 6 );
+ rIStm.ReadInt16( nBrushStyle );
+ rMtf.AddAction( new MetaFillColorAction( aActionColor, nBrushStyle != 0 ) );
+ rIStm.SeekRel( 2 );
+ }
+ break;
+
+ case GDI_MAPMODE_ACTION:
+ {
+ if (ImplReadMapMode(rIStm, aMapMode))
+ {
+ rMtf.AddAction(new MetaMapModeAction(aMapMode));
+
+ // #106172# Track font relevant data in shadow VDev
+ aFontVDev->SetMapMode(aMapMode);
+ };
+ }
+ break;
+
+ case GDI_CLIPREGION_ACTION:
+ {
+ vcl::Region aRegion;
+ sal_Int16 nRegType;
+ sal_Int16 bIntersect;
+ bool bClip = false;
+
+ rIStm.ReadInt16( nRegType ).ReadInt16( bIntersect );
+ ImplReadRect( rIStm, aRect );
+
+ switch( nRegType )
+ {
+ case 0:
+ break;
+
+ case 1:
+ {
+ tools::Rectangle aRegRect;
+
+ ImplReadRect( rIStm, aRegRect );
+ aRegion = vcl::Region( aRegRect );
+ bClip = true;
+ }
+ break;
+
+ case 2:
+ {
+ if (ImplReadPoly(rIStm, aActionPoly))
+ {
+ aRegion = vcl::Region( aActionPoly );
+ bClip = true;
+ }
+ }
+ break;
+
+ case 3:
+ {
+ bool bSuccess = true;
+ tools::PolyPolygon aPolyPoly;
+ sal_Int32 nPolyCount32(0);
+ rIStm.ReadInt32(nPolyCount32);
+ sal_uInt16 nPolyCount(nPolyCount32);
+
+ for (sal_uInt16 j = 0; j < nPolyCount && rIStm.good(); ++j)
+ {
+ if (!ImplReadPoly(rIStm, aActionPoly))
+ {
+ bSuccess = false;
+ break;
+ }
+ aPolyPoly.Insert(aActionPoly);
+ }
+
+ if (bSuccess)
+ {
+ aRegion = vcl::Region( aPolyPoly );
+ bClip = true;
+ }
+ }
+ break;
+ }
+
+ if( bIntersect )
+ aRegion.Intersect( aRect );
+
+ rMtf.AddAction( new MetaClipRegionAction( aRegion, bClip ) );
+ }
+ break;
+
+ case GDI_MOVECLIPREGION_ACTION:
+ {
+ sal_Int32 nTmp(0), nTmp1(0);
+ rIStm.ReadInt32( nTmp ).ReadInt32( nTmp1 );
+ rMtf.AddAction( new MetaMoveClipRegionAction( nTmp, nTmp1 ) );
+ }
+ break;
+
+ case GDI_ISECTCLIPREGION_ACTION:
+ {
+ ImplReadRect( rIStm, aRect );
+ rMtf.AddAction( new MetaISectRectClipRegionAction( aRect ) );
+ }
+ break;
+
+ case GDI_RASTEROP_ACTION:
+ {
+ RasterOp eRasterOp;
+ sal_Int16 nRasterOp;
+
+ rIStm.ReadInt16( nRasterOp );
+
+ switch( nRasterOp )
+ {
+ case 1:
+ eRasterOp = RasterOp::Invert;
+ break;
+
+ case 4:
+ case 5:
+ eRasterOp = RasterOp::Xor;
+ break;
+
+ default:
+ eRasterOp = RasterOp::OverPaint;
+ break;
+ }
+
+ rMtf.AddAction( new MetaRasterOpAction( eRasterOp ) );
+ }
+ break;
+
+ case GDI_PUSH_ACTION:
+ {
+ aLIStack.push(std::make_unique<LineInfo>(aLineInfo));
+ rMtf.AddAction( new MetaPushAction( PushFlags::ALL ) );
+
+ // #106172# Track font relevant data in shadow VDev
+ aFontVDev->Push();
+ }
+ break;
+
+ case GDI_POP_ACTION:
+ {
+
+ std::unique_ptr<LineInfo> xLineInfo;
+ if (!aLIStack.empty())
+ {
+ xLineInfo = std::move(aLIStack.top());
+ aLIStack.pop();
+ }
+
+ // restore line info
+ if (xLineInfo)
+ {
+ aLineInfo = *xLineInfo;
+ xLineInfo.reset();
+ bFatLine = ( LineStyle::NONE != aLineInfo.GetStyle() ) && !aLineInfo.IsDefault();
+ }
+
+ rMtf.AddAction( new MetaPopAction() );
+
+ // #106172# Track font relevant data in shadow VDev
+ aFontVDev->Pop();
+ }
+ break;
+
+ case GDI_GRADIENT_ACTION:
+ {
+ Color aStartCol;
+ Color aEndCol;
+ sal_Int16 nStyle;
+ sal_Int16 nAngle;
+ sal_Int16 nBorder;
+ sal_Int16 nOfsX;
+ sal_Int16 nOfsY;
+ sal_Int16 nIntensityStart;
+ sal_Int16 nIntensityEnd;
+
+ ImplReadRect( rIStm, aRect );
+ rIStm.ReadInt16( nStyle );
+ ImplReadColor( rIStm, aStartCol );
+ ImplReadColor( rIStm, aEndCol );
+ rIStm.ReadInt16( nAngle ).ReadInt16( nBorder ).ReadInt16( nOfsX ).ReadInt16( nOfsY ).ReadInt16( nIntensityStart ).ReadInt16( nIntensityEnd );
+
+ Gradient aGrad( static_cast<GradientStyle>(nStyle), aStartCol, aEndCol );
+
+ aGrad.SetAngle( nAngle );
+ aGrad.SetBorder( nBorder );
+ aGrad.SetOfsX( nOfsX );
+ aGrad.SetOfsY( nOfsY );
+ aGrad.SetStartIntensity( nIntensityStart );
+ aGrad.SetEndIntensity( nIntensityEnd );
+ rMtf.AddAction( new MetaGradientAction( aRect, aGrad ) );
+ }
+ break;
+
+ case GDI_TRANSPARENT_COMMENT:
+ {
+ tools::PolyPolygon aPolyPoly;
+ sal_Int32 nFollowingActionCount(0);
+ sal_Int16 nTrans(0);
+
+ ReadPolyPolygon( rIStm, aPolyPoly );
+ rIStm.ReadInt16( nTrans ).ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaTransparentAction( aPolyPoly, nTrans ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_FLOATTRANSPARENT_COMMENT:
+ {
+ GDIMetaFile aMtf;
+ Point aPos;
+ Size aSize;
+ Gradient aGradient;
+ sal_Int32 nFollowingActionCount(0);
+
+ ReadGDIMetaFile( rIStm, aMtf );
+ aSerializer.readPoint(aPos);
+ aSerializer.readSize(aSize);
+ aSerializer.readGradient(aGradient);
+ rIStm.ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaFloatTransparentAction( aMtf, aPos, aSize, aGradient ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_HATCH_COMMENT:
+ {
+ tools::PolyPolygon aPolyPoly;
+ Hatch aHatch;
+ sal_Int32 nFollowingActionCount(0);
+
+ ReadPolyPolygon( rIStm, aPolyPoly );
+ ReadHatch( rIStm, aHatch );
+ rIStm.ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaHatchAction( aPolyPoly, aHatch ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_REFPOINT_COMMENT:
+ {
+ Point aRefPoint;
+ bool bSet;
+ sal_Int32 nFollowingActionCount(0);
+
+ aSerializer.readPoint(aRefPoint);
+ rIStm.ReadCharAsBool( bSet ).ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaRefPointAction( aRefPoint, bSet ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+
+ // #106172# Track font relevant data in shadow VDev
+ if( bSet )
+ aFontVDev->SetRefPoint( aRefPoint );
+ else
+ aFontVDev->SetRefPoint();
+ }
+ break;
+
+ case GDI_TEXTLINECOLOR_COMMENT:
+ {
+ Color aColor;
+ bool bSet;
+ sal_Int32 nFollowingActionCount(0);
+
+ aSerializer.readColor(aColor);
+ rIStm.ReadCharAsBool( bSet ).ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaTextLineColorAction( aColor, bSet ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_TEXTLINE_COMMENT:
+ {
+ Point aStartPt;
+ sal_Int32 nWidth(0);
+ sal_uInt32 nStrikeout(0);
+ sal_uInt32 nUnderline(0);
+ sal_Int32 nFollowingActionCount(0);
+
+ aSerializer.readPoint(aStartPt);
+ rIStm.ReadInt32(nWidth ).ReadUInt32(nStrikeout).ReadUInt32(nUnderline).ReadInt32(nFollowingActionCount);
+ ImplSkipActions(rIStm, nFollowingActionCount);
+ rMtf.AddAction( new MetaTextLineAction( aStartPt, nWidth,
+ static_cast<FontStrikeout>(nStrikeout),
+ static_cast<FontLineStyle>(nUnderline),
+ LINESTYLE_NONE ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_GRADIENTEX_COMMENT:
+ {
+ tools::PolyPolygon aPolyPoly;
+ Gradient aGradient;
+ sal_Int32 nFollowingActionCount(0);
+
+ ReadPolyPolygon( rIStm, aPolyPoly );
+ aSerializer.readGradient(aGradient);
+ rIStm.ReadInt32( nFollowingActionCount );
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction( new MetaGradientExAction( aPolyPoly, aGradient ) );
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_COMMENT_COMMENT:
+ {
+ std::vector<sal_uInt8> aData;
+
+ OString aComment = read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm);
+ sal_Int32 nValue(0);
+ sal_uInt32 nDataSize(0);
+ rIStm.ReadInt32(nValue).ReadUInt32(nDataSize);
+
+ if (nDataSize)
+ {
+ const size_t nMaxPossibleData = rIStm.remainingSize();
+ if (nDataSize > nMaxPossibleActions)
+ {
+ SAL_WARN("vcl.gdi", "svm record claims to have: " << nDataSize << " data, but only " << nMaxPossibleData << " possible");
+ nDataSize = nMaxPossibleActions;
+ }
+ aData.resize(nDataSize);
+ nDataSize = rIStm.ReadBytes(aData.data(), nDataSize);
+ }
+
+ sal_Int32 nFollowingActionCount(0);
+ rIStm.ReadInt32(nFollowingActionCount);
+ ImplSkipActions( rIStm, nFollowingActionCount );
+ rMtf.AddAction(new MetaCommentAction(aComment, nValue, aData.data(), nDataSize));
+
+ i = SkipActions(i, nFollowingActionCount, nActions);
+ }
+ break;
+
+ case GDI_UNICODE_COMMENT:
+ {
+ nUnicodeCommentActionNumber = i + 1;
+ nUnicodeCommentStreamPos = rIStm.Tell() - 6;
+ if (nActionSize < 4)
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ else
+ rIStm.SeekRel(nActionSize - 4);
+ }
+ break;
+
+ default:
+ if (nActionSize < 4)
+ rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ else
+ rIStm.SeekRel(nActionSize - 4);
+ break;
+ }
+ }
+
+ rIStm.SetEndian( nOldFormat );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/textlayout.cxx b/vcl/source/gdi/textlayout.cxx
new file mode 100644
index 000000000..1efe1e617
--- /dev/null
+++ b/vcl/source/gdi/textlayout.cxx
@@ -0,0 +1,351 @@
+/* -*- 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/ctrl.hxx>
+#include <vcl/outdev.hxx>
+
+#include <textlayout.hxx>
+
+#include <osl/diagnose.h>
+#include <tools/fract.hxx>
+#include <sal/log.hxx>
+
+#if OSL_DEBUG_LEVEL > 1
+#include <rtl/strbuf.hxx>
+#endif
+
+#include <memory>
+#include <iterator>
+
+namespace vcl
+{
+
+ DefaultTextLayout::~DefaultTextLayout()
+ {
+ }
+
+ long DefaultTextLayout::GetTextWidth( const OUString& _rText, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ return m_rTargetDevice.GetTextWidth( _rText, _nStartIndex, _nLength );
+ }
+
+ void DefaultTextLayout::DrawText( const Point& _rStartPoint, const OUString& _rText, sal_Int32 _nStartIndex,
+ sal_Int32 _nLength, MetricVector* _pVector, OUString* _pDisplayText )
+ {
+ m_rTargetDevice.DrawText( _rStartPoint, _rText, _nStartIndex, _nLength, _pVector, _pDisplayText );
+ }
+
+ void DefaultTextLayout::GetCaretPositions( const OUString& _rText, long* _pCaretXArray,
+ sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ m_rTargetDevice.GetCaretPositions( _rText, _pCaretXArray, _nStartIndex, _nLength );
+ }
+
+ sal_Int32 DefaultTextLayout::GetTextBreak( const OUString& _rText, long _nMaxTextWidth, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ return m_rTargetDevice.GetTextBreak( _rText, _nMaxTextWidth, _nStartIndex, _nLength );
+ }
+
+ bool DefaultTextLayout::DecomposeTextRectAction() const
+ {
+ return false;
+ }
+
+ class ReferenceDeviceTextLayout : public ITextLayout
+ {
+ public:
+ ReferenceDeviceTextLayout( const Control& _rControl, OutputDevice& _rTargetDevice, OutputDevice& _rReferenceDevice );
+ virtual ~ReferenceDeviceTextLayout();
+
+ // ITextLayout
+ virtual long GetTextWidth( const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen ) const override;
+ virtual void DrawText( const Point& _rStartPoint, const OUString& _rText, sal_Int32 _nStartIndex, sal_Int32 _nLength, MetricVector* _pVector, OUString* _pDisplayText ) override;
+ virtual void GetCaretPositions( const OUString& _rText, long* _pCaretXArray, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const override;
+ virtual sal_Int32 GetTextBreak(const OUString& _rText, long _nMaxTextWidth, sal_Int32 _nStartIndex, sal_Int32 _nLength) const override;
+ virtual bool DecomposeTextRectAction() const override;
+
+ public:
+ // equivalents to the respective OutputDevice methods, which take the reference device into account
+ tools::Rectangle DrawText( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle, MetricVector* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize );
+ tools::Rectangle GetTextRect( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle, Size* o_pDeviceSize );
+
+ private:
+ long GetTextArray( const OUString& _rText, long* _pDXAry, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const;
+
+ OutputDevice& m_rTargetDevice;
+ OutputDevice& m_rReferenceDevice;
+ const bool m_bRTLEnabled;
+
+ tools::Rectangle m_aCompleteTextRect;
+ };
+
+ ReferenceDeviceTextLayout::ReferenceDeviceTextLayout( const Control& _rControl, OutputDevice& _rTargetDevice,
+ OutputDevice& _rReferenceDevice )
+ :m_rTargetDevice( _rTargetDevice )
+ ,m_rReferenceDevice( _rReferenceDevice )
+ ,m_bRTLEnabled( _rControl.IsRTLEnabled() )
+ {
+ Font const aUnzoomedPointFont( _rControl.GetUnzoomedControlPointFont() );
+ const Fraction& aZoom( _rControl.GetZoom() );
+ m_rTargetDevice.Push( PushFlags::MAPMODE | PushFlags::FONT | PushFlags::TEXTLAYOUTMODE );
+
+ MapMode aTargetMapMode( m_rTargetDevice.GetMapMode() );
+ OSL_ENSURE( aTargetMapMode.GetOrigin() == Point(), "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: uhm, the code below won't work here ..." );
+
+ // normally, controls simulate "zoom" by "zooming" the font. This is responsible for (part of) the discrepancies
+ // between text in Writer and text in controls in Writer, though both have the same font.
+ // So, if we have a zoom set at the control, then we do not scale the font, but instead modify the map mode
+ // to accommodate for the zoom.
+ aTargetMapMode.SetScaleX( aZoom ); // TODO: shouldn't this be "current_scale * zoom"?
+ aTargetMapMode.SetScaleY( aZoom );
+
+ // also, use a higher-resolution map unit than "pixels", which should save us some rounding errors when
+ // translating coordinates between the reference device and the target device.
+ OSL_ENSURE( aTargetMapMode.GetMapUnit() == MapUnit::MapPixel,
+ "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: this class is not expected to work with such target devices!" );
+ // we *could* adjust all the code in this class to handle this case, but at the moment, it's not necessary
+ const MapUnit eTargetMapUnit = m_rReferenceDevice.GetMapMode().GetMapUnit();
+ aTargetMapMode.SetMapUnit( eTargetMapUnit );
+ OSL_ENSURE( aTargetMapMode.GetMapUnit() != MapUnit::MapPixel,
+ "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: a reference device which has map mode PIXEL?!" );
+
+ m_rTargetDevice.SetMapMode( aTargetMapMode );
+
+ // now that the Zoom is part of the map mode, reset the target device's font to the "unzoomed" version
+ Font aDrawFont( aUnzoomedPointFont );
+ aDrawFont.SetFontSize( OutputDevice::LogicToLogic(aDrawFont.GetFontSize(), MapMode(MapUnit::MapPoint), MapMode(eTargetMapUnit)) );
+ _rTargetDevice.SetFont( aDrawFont );
+
+ // transfer font to the reference device
+ m_rReferenceDevice.Push( PushFlags::FONT | PushFlags::TEXTLAYOUTMODE );
+ Font aRefFont( aUnzoomedPointFont );
+ aRefFont.SetFontSize( OutputDevice::LogicToLogic(
+ aRefFont.GetFontSize(), MapMode(MapUnit::MapPoint), m_rReferenceDevice.GetMapMode()) );
+ m_rReferenceDevice.SetFont( aRefFont );
+ }
+
+ ReferenceDeviceTextLayout::~ReferenceDeviceTextLayout()
+ {
+ m_rReferenceDevice.Pop();
+ m_rTargetDevice.Pop();
+ }
+
+ namespace
+ {
+ bool lcl_normalizeLength( const OUString& _rText, const sal_Int32 _nStartIndex, sal_Int32& _io_nLength )
+ {
+ sal_Int32 nTextLength = _rText.getLength();
+ if ( _nStartIndex > nTextLength )
+ return false;
+ if ( _nStartIndex + _io_nLength > nTextLength )
+ _io_nLength = nTextLength - _nStartIndex;
+ return true;
+ }
+ }
+
+ long ReferenceDeviceTextLayout::GetTextArray( const OUString& _rText, long* _pDXAry, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
+ return 0;
+
+ // retrieve the character widths from the reference device
+ long nTextWidth = m_rReferenceDevice.GetTextArray( _rText, _pDXAry, _nStartIndex, _nLength );
+#if OSL_DEBUG_LEVEL > 1
+ if ( _pDXAry )
+ {
+ OStringBuffer aTrace;
+ aTrace.append( "ReferenceDeviceTextLayout::GetTextArray( " );
+ aTrace.append( OUStringToOString( _rText, RTL_TEXTENCODING_UTF8 ) );
+ aTrace.append( " ): " );
+ aTrace.append( nTextWidth );
+ aTrace.append( " = ( " );
+ for ( sal_Int32 i=0; i<_nLength; )
+ {
+ aTrace.append( _pDXAry[i] );
+ if ( ++i < _nLength )
+ aTrace.append( ", " );
+ }
+ aTrace.append( ")" );
+ SAL_INFO( "vcl", aTrace.makeStringAndClear() );
+ }
+#endif
+ return nTextWidth;
+ }
+
+ long ReferenceDeviceTextLayout::GetTextWidth( const OUString& _rText, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ return GetTextArray( _rText, nullptr, _nStartIndex, _nLength );
+ }
+
+ void ReferenceDeviceTextLayout::DrawText( const Point& _rStartPoint, const OUString& _rText, sal_Int32 _nStartIndex, sal_Int32 _nLength, MetricVector* _pVector, OUString* _pDisplayText )
+ {
+ if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
+ return;
+
+ if ( _pVector && _pDisplayText )
+ {
+ MetricVector aGlyphBounds;
+ m_rReferenceDevice.GetGlyphBoundRects( _rStartPoint, _rText, _nStartIndex, _nLength, aGlyphBounds );
+ ::std::copy(
+ aGlyphBounds.begin(), aGlyphBounds.end(),
+ ::std::insert_iterator< MetricVector > ( *_pVector, _pVector->end() ) );
+ *_pDisplayText += _rText.copy( _nStartIndex, _nLength );
+ return;
+ }
+
+ std::unique_ptr<long[]> pCharWidths(new long[ _nLength ]);
+ long nTextWidth = GetTextArray( _rText, pCharWidths.get(), _nStartIndex, _nLength );
+ m_rTargetDevice.DrawTextArray( _rStartPoint, _rText, pCharWidths.get(), _nStartIndex, _nLength );
+ pCharWidths.reset();
+
+ m_aCompleteTextRect.Union( tools::Rectangle( _rStartPoint, Size( nTextWidth, m_rTargetDevice.GetTextHeight() ) ) );
+ }
+
+ void ReferenceDeviceTextLayout::GetCaretPositions( const OUString& _rText, long* _pCaretXArray,
+ sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
+ return;
+
+ // retrieve the caret positions from the reference device
+ m_rReferenceDevice.GetCaretPositions( _rText, _pCaretXArray, _nStartIndex, _nLength );
+ }
+
+ sal_Int32 ReferenceDeviceTextLayout::GetTextBreak( const OUString& _rText, long _nMaxTextWidth, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
+ {
+ if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
+ return 0;
+
+ return m_rReferenceDevice.GetTextBreak( _rText, _nMaxTextWidth, _nStartIndex, _nLength );
+ }
+
+ bool ReferenceDeviceTextLayout::DecomposeTextRectAction() const
+ {
+ return true;
+ }
+
+ tools::Rectangle ReferenceDeviceTextLayout::DrawText( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle,
+ MetricVector* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize )
+ {
+ if ( _rText.isEmpty() )
+ return tools::Rectangle();
+
+ // determine text layout mode from the RTL-ness of the control whose text we render
+ ComplexTextLayoutFlags nTextLayoutMode = m_bRTLEnabled ? ComplexTextLayoutFlags::BiDiRtl : ComplexTextLayoutFlags::Default;
+ m_rReferenceDevice.SetLayoutMode( nTextLayoutMode );
+ m_rTargetDevice.SetLayoutMode( nTextLayoutMode | ComplexTextLayoutFlags::TextOriginLeft );
+
+ // ComplexTextLayoutFlags::TextOriginLeft is because when we do actually draw the text (in DrawText( Point, ... )), then
+ // our caller gives us the left border of the draw position, regardless of script type, text layout,
+ // and the like in our ctor, we set the map mode of the target device from pixel to twip, but our caller doesn't know this,
+ // but passed pixel coordinates. So, adjust the rect.
+ tools::Rectangle aRect( m_rTargetDevice.PixelToLogic( _rRect ) );
+ if (i_pDeviceSize)
+ {
+ //if i_pDeviceSize is passed in here, it was the original pre logic-to-pixel size of _rRect
+ SAL_WARN_IF(std::abs(_rRect.GetSize().Width() - m_rTargetDevice.LogicToPixel(*i_pDeviceSize).Width()) > 1, "vcl", "DeviceSize width was expected to match Pixel width");
+ SAL_WARN_IF(std::abs(_rRect.GetSize().Height() - m_rTargetDevice.LogicToPixel(*i_pDeviceSize).Height()) > 1, "vcl", "DeviceSize height was expected to match Pixel height");
+ aRect.SetSize(*i_pDeviceSize);
+ }
+
+ m_aCompleteTextRect.SetEmpty();
+ m_rTargetDevice.DrawText( aRect, _rText, _nStyle, _pVector, _pDisplayText, this );
+ tools::Rectangle aTextRect = m_aCompleteTextRect;
+
+ if ( aTextRect.IsEmpty() && !aRect.IsEmpty() )
+ {
+ // this happens for instance if we're in a PaintToDevice call, where only a MetaFile is recorded,
+ // but no actual painting happens, so our "DrawText( Point, ... )" is never called
+ // In this case, calculate the rect from what OutputDevice::GetTextRect would give us. This has
+ // the disadvantage of less accuracy, compared with the approach to calculate the rect from the
+ // single "DrawText( Point, ... )" calls, since more intermediate arithmetic will translate
+ // from ref- to target-units.
+ aTextRect = m_rTargetDevice.GetTextRect( aRect, _rText, _nStyle, nullptr, this );
+ }
+
+ // similar to above, the text rect now contains TWIPs (or whatever unit the ref device has), but the caller
+ // expects pixel coordinates
+ aTextRect = m_rTargetDevice.LogicToPixel( aTextRect );
+
+ // convert the metric vector
+ if ( _pVector )
+ {
+ for ( auto& rCharRect : *_pVector )
+ {
+ rCharRect = m_rTargetDevice.LogicToPixel( rCharRect );
+ }
+ }
+
+ return aTextRect;
+ }
+
+ tools::Rectangle ReferenceDeviceTextLayout::GetTextRect( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle, Size* o_pDeviceSize )
+ {
+ if ( _rText.isEmpty() )
+ return tools::Rectangle();
+
+ // determine text layout mode from the RTL-ness of the control whose text we render
+ ComplexTextLayoutFlags nTextLayoutMode = m_bRTLEnabled ? ComplexTextLayoutFlags::BiDiRtl : ComplexTextLayoutFlags::Default;
+ m_rReferenceDevice.SetLayoutMode( nTextLayoutMode );
+ m_rTargetDevice.SetLayoutMode( nTextLayoutMode | ComplexTextLayoutFlags::TextOriginLeft );
+
+ // ComplexTextLayoutFlags::TextOriginLeft is because when we do actually draw the text (in DrawText( Point, ... )), then
+ // our caller gives us the left border of the draw position, regardless of script type, text layout,
+ // and the like in our ctor, we set the map mode of the target device from pixel to twip, but our caller doesn't know this,
+ // but passed pixel coordinates. So, adjust the rect.
+ tools::Rectangle aRect( m_rTargetDevice.PixelToLogic( _rRect ) );
+
+ tools::Rectangle aTextRect = m_rTargetDevice.GetTextRect( aRect, _rText, _nStyle, nullptr, this );
+
+ //if o_pDeviceSize is available, stash the pre logic-to-pixel size in it
+ if (o_pDeviceSize)
+ {
+ *o_pDeviceSize = aTextRect.GetSize();
+ }
+
+ // similar to above, the text rect now contains TWIPs (or whatever unit the ref device has), but the caller
+ // expects pixel coordinates
+ aTextRect = m_rTargetDevice.LogicToPixel( aTextRect );
+
+ return aTextRect;
+ }
+
+ ControlTextRenderer::ControlTextRenderer( const Control& _rControl, OutputDevice& _rTargetDevice, OutputDevice& _rReferenceDevice )
+ :m_pImpl( new ReferenceDeviceTextLayout( _rControl, _rTargetDevice, _rReferenceDevice ) )
+ {
+ }
+
+ ControlTextRenderer::~ControlTextRenderer()
+ {
+ }
+
+ tools::Rectangle ControlTextRenderer::DrawText( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle,
+ MetricVector* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize )
+ {
+ return m_pImpl->DrawText( _rRect, _rText, _nStyle, _pVector, _pDisplayText, i_pDeviceSize );
+ }
+
+ tools::Rectangle ControlTextRenderer::GetTextRect( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle, Size* o_pDeviceSize = nullptr )
+ {
+ return m_pImpl->GetTextRect( _rRect, _rText, _nStyle, o_pDeviceSize );
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/vectorgraphicdata.cxx b/vcl/source/gdi/vectorgraphicdata.cxx
new file mode 100644
index 000000000..80195dc26
--- /dev/null
+++ b/vcl/source/gdi/vectorgraphicdata.cxx
@@ -0,0 +1,358 @@
+/* -*- 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/diagnose_ex.h>
+#include <tools/stream.hxx>
+#include <sal/log.hxx>
+#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 <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <vcl/canvastools.hxx>
+#include <comphelper/seqstream.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/wmfexternal.hxx>
+#include <vcl/pdfread.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)
+{
+ 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;
+ geometry::RealRectangle2D aRealRect;
+
+ aRealRect.X1 = rTargetRange.getMinX();
+ aRealRect.Y1 = rTargetRange.getMinY();
+ aRealRect.X2 = rTargetRange.getMaxX();
+ aRealRect.Y2 = rTargetRange.getMaxY();
+
+ // get system DPI
+ const Size aDPI(Application::GetDefaultDevice()->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)));
+
+ 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 (getVectorGraphicDataType() == rCandidate.getVectorGraphicDataType())
+ {
+ if (getVectorGraphicDataArrayLength() == rCandidate.getVectorGraphicDataArrayLength())
+ {
+ if (0 == memcmp(
+ getVectorGraphicDataArray().getConstArray(),
+ rCandidate.getVectorGraphicDataArray().getConstArray(),
+ getVectorGraphicDataArrayLength()))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void VectorGraphicData::setWmfExternalHeader(const WmfExternal& aExtHeader)
+{
+ if (!mpExternalHeader)
+ {
+ mpExternalHeader.reset( new WmfExternal );
+ }
+
+ *mpExternalHeader = aExtHeader;
+}
+
+void VectorGraphicData::ensurePdfReplacement()
+{
+ assert(getVectorGraphicDataType() == VectorGraphicDataType::Pdf);
+
+ if (!maReplacement.IsEmpty())
+ return; // nothing to do
+
+ // use PDFium directly
+ std::vector<Bitmap> aBitmaps;
+ sal_Int32 nUsePageIndex = 0;
+ if (mnPageIndex >= 0)
+ nUsePageIndex = mnPageIndex;
+ vcl::RenderPDFBitmaps(maVectorGraphicDataArray.getConstArray(),
+ maVectorGraphicDataArray.getLength(), 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 (getVectorGraphicDataType() == VectorGraphicDataType::Pdf)
+ {
+ ensurePdfReplacement();
+ return;
+ }
+
+ ensureSequenceAndRange();
+
+ if (!maSequence.empty())
+ {
+ maReplacement = convertPrimitive2DSequenceToBitmapEx(maSequence, getRange());
+ }
+}
+
+void VectorGraphicData::ensureSequenceAndRange()
+{
+ if (!mbSequenceCreated && maVectorGraphicDataArray.hasElements())
+ {
+ // import SVG to maSequence, also set maRange
+ maRange.reset();
+
+ // create Vector Graphic Data interpreter
+ uno::Reference<uno::XComponentContext> xContext(::comphelper::getProcessComponentContext());
+
+ switch (getVectorGraphicDataType())
+ {
+ case VectorGraphicDataType::Svg:
+ {
+ const uno::Reference< graphic::XSvgParser > xSvgParser = graphic::SvgTools::create(xContext);
+ const uno::Reference< io::XInputStream > myInputStream(new comphelper::SequenceInputStream(maVectorGraphicDataArray));
+
+ if (myInputStream.is())
+ maSequence = comphelper::sequenceToContainer<std::deque<css::uno::Reference< css::graphic::XPrimitive2D >>>(xSvgParser->getDecomposition(myInputStream, maPath));
+
+ break;
+ }
+ case VectorGraphicDataType::Emf:
+ case VectorGraphicDataType::Wmf:
+ {
+ const uno::Reference< graphic::XEmfParser > xEmfParser = graphic::EmfTools::create(xContext);
+ const uno::Reference< io::XInputStream > myInputStream(new comphelper::SequenceInputStream(maVectorGraphicDataArray));
+ uno::Sequence< ::beans::PropertyValue > aSequence;
+
+ if (mpExternalHeader)
+ {
+ aSequence = mpExternalHeader->getSequence();
+ }
+
+ if (myInputStream.is())
+ {
+ // 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);
+
+ maSequence = comphelper::sequenceToContainer<std::deque<css::uno::Reference< css::graphic::XPrimitive2D >>>(xEmfParser->getDecomposition(myInputStream, maPath, aSequence));
+ }
+
+ break;
+ }
+ case VectorGraphicDataType::Pdf:
+ {
+ const uno::Reference<graphic::XPdfDecomposer> xPdfDecomposer = graphic::PdfTools::create(xContext);
+ uno::Sequence<beans::PropertyValue> aDecompositionParameters = comphelper::InitPropertySequence({
+ {"PageIndex", uno::makeAny<sal_Int32>(mnPageIndex)},
+ });
+ auto xPrimitive2D = xPdfDecomposer->getDecomposition(maVectorGraphicDataArray, 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;
+ }
+}
+
+auto VectorGraphicData::getSizeBytes() const -> std::pair<State, size_t>
+{
+ if (maSequence.empty() && maVectorGraphicDataArray.hasElements())
+ {
+ return std::make_pair(State::UNPARSED, maVectorGraphicDataArray.getLength());
+ }
+ else
+ {
+ return std::make_pair(State::PARSED, maVectorGraphicDataArray.getLength() + mNestedBitmapSize);
+ }
+}
+
+VectorGraphicData::VectorGraphicData(
+ const VectorGraphicDataArray& rVectorGraphicDataArray,
+ const OUString& rPath,
+ VectorGraphicDataType eVectorDataType,
+ sal_Int32 nPageIndex)
+: maVectorGraphicDataArray(rVectorGraphicDataArray),
+ maPath(rPath),
+ mbSequenceCreated(false),
+ maRange(),
+ maSequence(),
+ maReplacement(),
+ mNestedBitmapSize(0),
+ meVectorGraphicDataType(eVectorDataType),
+ mnPageIndex(nPageIndex)
+{
+}
+
+VectorGraphicData::VectorGraphicData(
+ const OUString& rPath,
+ VectorGraphicDataType eVectorDataType)
+: maVectorGraphicDataArray(),
+ maPath(rPath),
+ mbSequenceCreated(false),
+ maRange(),
+ maSequence(),
+ maReplacement(),
+ mNestedBitmapSize(0),
+ meVectorGraphicDataType(eVectorDataType),
+ mnPageIndex(-1)
+{
+ SvFileStream rIStm(rPath, StreamMode::STD_READ);
+ if(rIStm.GetError())
+ return;
+ const sal_uInt32 nStmLen(rIStm.remainingSize());
+ if (nStmLen)
+ {
+ maVectorGraphicDataArray.realloc(nStmLen);
+ rIStm.ReadBytes(maVectorGraphicDataArray.begin(), nStmLen);
+
+ if (rIStm.GetError())
+ {
+ maVectorGraphicDataArray = VectorGraphicDataArray();
+ }
+ }
+}
+
+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 vcl_get_checksum(0, maVectorGraphicDataArray.getConstArray(), maVectorGraphicDataArray.getLength());
+}
+
+/* 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 000000000..1bb163cda
--- /dev/null
+++ b/vcl/source/gdi/virdev.cxx
@@ -0,0 +1,510 @@
+/* -*- 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 <salinst.hxx>
+#include <salgdi.hxx>
+#include <salvd.hxx>
+#include <outdev.h>
+#include <PhysicalFontCollection.hxx>
+#include <svdata.hxx>
+#include <vcl/virdev.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+using namespace ::com::sun::star::uno;
+
+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->setAntiAliasB2DDraw(bool(mnAntialiasing & AntialiasingFlags::EnableB2dDraw));
+ }
+
+ 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,
+ long nDX, long nDY, const SystemGraphicsData *pData )
+{
+ SAL_INFO( "vcl.virdev", "ImplInitVirDev(" << nDX << "," << nDY << ")" );
+
+ meRefDevMode = RefDevMode::NONE;
+ mbForceZeroExtleadBug = false;
+
+ bool bErase = nDX > 0 && nDY > 0;
+
+ if ( nDX < 1 )
+ nDX = 1;
+
+ if ( nDY < 1 )
+ nDY = 1;
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if ( !pOutDev )
+ pOutDev = ImplGetDefaultWindow();
+ if( !pOutDev )
+ return;
+
+ SalGraphics* pGraphics;
+ if ( !pOutDev->mpGraphics )
+ (void)pOutDev->AcquireGraphics();
+ pGraphics = pOutDev->mpGraphics;
+ if ( pGraphics )
+ mpVirDev = pSVData->mpDefInst->CreateVirtualDevice(pGraphics, nDX, nDY, meFormat, 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 >() );
+ }
+
+ switch (meFormat)
+ {
+ case DeviceFormat::BITMASK:
+ mnBitCount = 1;
+ break;
+ default:
+ mnBitCount = pOutDev->GetBitCount();
+ break;
+ }
+ mnOutWidth = nDX;
+ mnOutHeight = nDY;
+
+ if (meFormat == DeviceFormat::BITMASK)
+ SetAntialiasing( AntialiasingFlags::DisableText );
+
+ 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 eFormat,
+ DeviceFormat eAlphaFormat, OutDevType eOutDevType)
+ : OutputDevice(eOutDevType)
+ , meFormat(eFormat)
+ , meAlphaFormat(eAlphaFormat)
+{
+ SAL_INFO( "vcl.virdev", "VirtualDevice::VirtualDevice( " << static_cast<int>(eFormat)
+ << ", " << static_cast<int>(eAlphaFormat)
+ << ", " << static_cast<int>(eOutDevType) << " )" );
+
+ ImplInitVirDev(pCompDev ? pCompDev : Application::GetDefaultDevice(), 0, 0);
+}
+
+VirtualDevice::VirtualDevice(const SystemGraphicsData& rData, const Size &rSize,
+ DeviceFormat eFormat)
+ : OutputDevice(OUTDEV_VIRDEV)
+ , meFormat(eFormat)
+ , meAlphaFormat(DeviceFormat::NONE)
+{
+ 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;
+ 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;
+
+ pNewVirDev = pSVData->mpDefInst->CreateVirtualDevice(mpGraphics, nNewWidth, nNewHeight, meFormat);
+ if ( pNewVirDev )
+ {
+ SalGraphics* pGraphics = pNewVirDev->AcquireGraphics();
+ if ( pGraphics )
+ {
+ long nWidth;
+ 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 black (->opaque),
+ // fill rect with that (linecolor, too, because of
+ // those pesky missing pixel problems)
+ Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR );
+ SetLineColor( COL_BLACK );
+ SetFillColor( COL_BLACK );
+ DrawRect( rRect );
+ Pop();
+}
+
+bool VirtualDevice::ImplSetOutputSizePixel( const Size& rNewSize, bool bErase,
+ sal_uInt8 *const pBuffer)
+{
+ if( InnerImplSetOutputSizePixel(rNewSize, bErase, pBuffer) )
+ {
+ if (meAlphaFormat != DeviceFormat::NONE)
+ {
+ // #110958# Setup alpha bitmap
+ if(mpAlphaVDev && mpAlphaVDev->GetOutputSizePixel() != rNewSize)
+ {
+ mpAlphaVDev.disposeAndClear();
+ }
+
+ if( !mpAlphaVDev )
+ {
+ mpAlphaVDev = VclPtr<VirtualDevice>::Create(*this, meAlphaFormat);
+ mpAlphaVDev->InnerImplSetOutputSizePixel(rNewSize, bErase, nullptr);
+ }
+
+ // TODO: copy full outdev state to new one, here. Also needed in outdev2.cxx:DrawOutDev
+ if( GetLineColor() != COL_TRANSPARENT )
+ mpAlphaVDev->SetLineColor( COL_BLACK );
+
+ if( GetFillColor() != COL_TRANSPARENT )
+ mpAlphaVDev->SetFillColor( COL_BLACK );
+
+ mpAlphaVDev->SetMapMode( GetMapMode() );
+ }
+
+ 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 )
+{
+ return ImplSetOutputSizePixel(rNewSize, bErase, nullptr);
+}
+
+bool VirtualDevice::SetOutputSizePixelScaleOffsetAndBuffer(
+ const Size& rNewSize, const Fraction& rScale, const Point& rNewOffset,
+ sal_uInt8 *const pBuffer)
+{
+ if (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();
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.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;
+}
+
+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: */
diff --git a/vcl/source/gdi/wall.cxx b/vcl/source/gdi/wall.cxx
new file mode 100644
index 000000000..5170134cc
--- /dev/null
+++ b/vcl/source/gdi/wall.cxx
@@ -0,0 +1,366 @@
+/* -*- 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 <wall2.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/settings.hxx>
+
+#include <TypeSerializer.hxx>
+
+ImplWallpaper::ImplWallpaper() :
+ maColor( COL_TRANSPARENT ), meStyle( WallpaperStyle::NONE )
+{
+}
+
+ImplWallpaper::ImplWallpaper( const ImplWallpaper& rImplWallpaper ) :
+ maColor( rImplWallpaper.maColor ), meStyle(rImplWallpaper.meStyle)
+{
+ if ( rImplWallpaper.mpBitmap )
+ mpBitmap = std::make_unique<BitmapEx>( *rImplWallpaper.mpBitmap );
+
+ if( rImplWallpaper.mpCache )
+ mpCache = std::make_unique<BitmapEx>( *rImplWallpaper.mpCache );
+
+ if ( rImplWallpaper.mpGradient )
+ mpGradient = std::make_unique<Gradient>( *rImplWallpaper.mpGradient );
+
+ if ( rImplWallpaper.mpRect )
+ mpRect = *rImplWallpaper.mpRect;
+}
+
+ImplWallpaper::~ImplWallpaper()
+{
+}
+
+SvStream& ReadImplWallpaper( SvStream& rIStm, ImplWallpaper& rImplWallpaper )
+{
+ VersionCompat aCompat( rIStm, StreamMode::READ );
+
+ rImplWallpaper.mpRect.reset();
+ rImplWallpaper.mpGradient.reset();
+ rImplWallpaper.mpBitmap.reset();
+
+ // 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.mpRect = tools::Rectangle();
+ aSerializer.readRectangle(*rImplWallpaper.mpRect);
+ }
+
+ if( bGrad )
+ {
+ rImplWallpaper.mpGradient = std::make_unique<Gradient>();
+ aSerializer.readGradient(*rImplWallpaper.mpGradient);
+ }
+
+ if( bBmp )
+ {
+ rImplWallpaper.mpBitmap = std::make_unique<BitmapEx>();
+ ReadDIBBitmapEx(*rImplWallpaper.mpBitmap, rIStm);
+ }
+
+ // version 3 (new color format)
+ if( aCompat.GetVersion() >= 3 )
+ {
+ rIStm.ReadUInt32(rImplWallpaper.maColor.mValue);
+ }
+ }
+
+ return rIStm;
+}
+
+SvStream& WriteImplWallpaper( SvStream& rOStm, const ImplWallpaper& rImplWallpaper )
+{
+ VersionCompat aCompat( rOStm, StreamMode::WRITE, 3 );
+ bool bRect = bool(rImplWallpaper.mpRect);
+ bool bGrad = bool(rImplWallpaper.mpGradient);
+ bool bBmp = bool(rImplWallpaper.mpBitmap);
+ 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.mpRect);
+ }
+
+ if (bGrad)
+ {
+ aSerializer.writeGradient(*rImplWallpaper.mpGradient);
+ }
+
+ if( bBmp )
+ WriteDIBBitmapEx(*rImplWallpaper.mpBitmap, rOStm);
+
+ // version 3 (new color format)
+ rOStm.WriteUInt32(rImplWallpaper.maColor.mValue);
+
+ return rOStm;
+}
+
+namespace
+{
+ struct theGlobalDefault :
+ public rtl::Static< Wallpaper::ImplType, theGlobalDefault > {};
+}
+
+Wallpaper::Wallpaper() : mpImplWallpaper(theGlobalDefault::get())
+{
+}
+
+Wallpaper::Wallpaper( const Wallpaper& ) = default;
+
+Wallpaper::Wallpaper( Wallpaper&& ) = default;
+
+Wallpaper::Wallpaper( const Color& rColor ) : mpImplWallpaper()
+{
+ mpImplWallpaper->maColor = rColor;
+ mpImplWallpaper->meStyle = WallpaperStyle::Tile;
+}
+
+Wallpaper::Wallpaper( const BitmapEx& rBmpEx ) : mpImplWallpaper()
+{
+ mpImplWallpaper->mpBitmap = std::make_unique<BitmapEx>( rBmpEx );
+ mpImplWallpaper->meStyle = WallpaperStyle::Tile;
+}
+
+Wallpaper::Wallpaper( const Gradient& rGradient ) : mpImplWallpaper()
+{
+ mpImplWallpaper->mpGradient = std::make_unique<Gradient>( rGradient );
+ mpImplWallpaper->meStyle = WallpaperStyle::Tile;
+}
+
+Wallpaper::~Wallpaper() = default;
+
+void Wallpaper::ImplSetCachedBitmap( BitmapEx& rBmp ) const
+{
+ if( !mpImplWallpaper->mpCache )
+ const_cast< ImplWallpaper* >(mpImplWallpaper.get())->mpCache = std::make_unique<BitmapEx>( rBmp );
+ else
+ *const_cast< ImplWallpaper* >(mpImplWallpaper.get())->mpCache = rBmp;
+}
+
+const BitmapEx* Wallpaper::ImplGetCachedBitmap() const
+{
+ return mpImplWallpaper->mpCache.get();
+}
+
+void Wallpaper::ImplReleaseCachedBitmap() const
+{
+ const_cast< ImplWallpaper* >(mpImplWallpaper.get())->mpCache.reset();
+}
+
+void Wallpaper::SetColor( const Color& rColor )
+{
+ ImplReleaseCachedBitmap();
+ mpImplWallpaper->maColor = rColor;
+
+ if( WallpaperStyle::NONE == mpImplWallpaper->meStyle || WallpaperStyle::ApplicationGradient == mpImplWallpaper->meStyle )
+ mpImplWallpaper->meStyle = WallpaperStyle::Tile;
+}
+
+const Color& Wallpaper::GetColor() const
+{
+ return mpImplWallpaper->maColor;
+}
+
+void Wallpaper::SetStyle( WallpaperStyle eStyle )
+{
+ if( eStyle == WallpaperStyle::ApplicationGradient )
+ // set a dummy gradient, the correct gradient
+ // will be created dynamically in GetGradient()
+ SetGradient( ImplGetApplicationGradient() );
+
+ mpImplWallpaper->meStyle = eStyle;
+}
+
+WallpaperStyle Wallpaper::GetStyle() const
+{
+ return mpImplWallpaper->meStyle;
+}
+
+void Wallpaper::SetBitmap( const BitmapEx& rBitmap )
+{
+ if ( !rBitmap )
+ {
+ if ( mpImplWallpaper->mpBitmap )
+ {
+ ImplReleaseCachedBitmap();
+ mpImplWallpaper->mpBitmap.reset();
+ }
+ }
+ else
+ {
+ ImplReleaseCachedBitmap();
+ if ( mpImplWallpaper->mpBitmap )
+ *(mpImplWallpaper->mpBitmap) = rBitmap;
+ else
+ mpImplWallpaper->mpBitmap = std::make_unique<BitmapEx>( rBitmap );
+ }
+
+ if( WallpaperStyle::NONE == mpImplWallpaper->meStyle || WallpaperStyle::ApplicationGradient == mpImplWallpaper->meStyle)
+ mpImplWallpaper->meStyle = WallpaperStyle::Tile;
+}
+
+BitmapEx Wallpaper::GetBitmap() const
+{
+ if ( mpImplWallpaper->mpBitmap )
+ return *(mpImplWallpaper->mpBitmap);
+ else
+ return BitmapEx();
+}
+
+bool Wallpaper::IsBitmap() const
+{
+ return bool(mpImplWallpaper->mpBitmap);
+}
+
+void Wallpaper::SetGradient( const Gradient& rGradient )
+{
+ ImplReleaseCachedBitmap();
+
+ if ( mpImplWallpaper->mpGradient )
+ *(mpImplWallpaper->mpGradient) = rGradient;
+ else
+ mpImplWallpaper->mpGradient = std::make_unique<Gradient>( rGradient );
+
+ if( WallpaperStyle::NONE == mpImplWallpaper->meStyle || WallpaperStyle::ApplicationGradient == mpImplWallpaper->meStyle )
+ mpImplWallpaper->meStyle = WallpaperStyle::Tile;
+}
+
+Gradient Wallpaper::GetGradient() const
+{
+ if( WallpaperStyle::ApplicationGradient == mpImplWallpaper->meStyle )
+ return ImplGetApplicationGradient();
+ else if ( mpImplWallpaper->mpGradient )
+ return *(mpImplWallpaper->mpGradient);
+ else
+ return Gradient();
+}
+
+bool Wallpaper::IsGradient() const
+{
+ return bool(mpImplWallpaper->mpGradient);
+}
+
+Gradient Wallpaper::ImplGetApplicationGradient()
+{
+ Gradient g;
+ g.SetAngle( 900 );
+ g.SetStyle( 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;
+}
+
+void Wallpaper::SetRect( const tools::Rectangle& rRect )
+{
+ if ( rRect.IsEmpty() )
+ {
+ mpImplWallpaper->mpRect.reset();
+ }
+ else
+ {
+ mpImplWallpaper->mpRect = rRect;
+ }
+}
+
+tools::Rectangle Wallpaper::GetRect() const
+{
+ if ( mpImplWallpaper->mpRect )
+ return *mpImplWallpaper->mpRect;
+ else
+ return tools::Rectangle();
+}
+
+bool Wallpaper::IsRect() const
+{
+ return bool(mpImplWallpaper->mpRect);
+}
+
+bool Wallpaper::IsFixed() const
+{
+ if ( mpImplWallpaper->meStyle == WallpaperStyle::NONE )
+ return false;
+ else
+ return (!mpImplWallpaper->mpBitmap && !mpImplWallpaper->mpGradient);
+}
+
+bool Wallpaper::IsScrollable() const
+{
+ if ( mpImplWallpaper->meStyle == WallpaperStyle::NONE )
+ return false;
+ else if ( !mpImplWallpaper->mpBitmap && !mpImplWallpaper->mpGradient )
+ return true;
+ else if ( mpImplWallpaper->mpBitmap )
+ return (mpImplWallpaper->meStyle == WallpaperStyle::Tile);
+ else
+ return false;
+}
+
+Wallpaper& Wallpaper::operator=( const Wallpaper& ) = default;
+
+Wallpaper& Wallpaper::operator=( Wallpaper&& ) = default;
+
+bool Wallpaper::operator==( const Wallpaper& rWallpaper ) const
+{
+ return mpImplWallpaper.same_object(rWallpaper.mpImplWallpaper);
+}
+
+SvStream& ReadWallpaper( SvStream& rIStm, Wallpaper& rWallpaper )
+{
+ return ReadImplWallpaper( rIStm, *rWallpaper.mpImplWallpaper );
+}
+
+SvStream& WriteWallpaper( SvStream& rOStm, const Wallpaper& rWallpaper )
+{
+ return WriteImplWallpaper( rOStm, *rWallpaper.mpImplWallpaper );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */