diff options
Diffstat (limited to '')
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 += "&"; + break; + case u'<': + rValue += "<"; + break; + case u'>': + rValue += ">"; + break; + case u'\'': + rValue += "'"; + break; + case u'"': + rValue += """; + 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 ] = ¤tItem; + } + } + + // 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] == ¤tItem ) + 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: */ |